diff --git a/.aztec-sync-commit b/.aztec-sync-commit index bdaabc69727..18e53f2bd3b 100644 --- a/.aztec-sync-commit +++ b/.aztec-sync-commit @@ -1 +1 @@ -12af650f0d27c37dca06bb329bf76a5574534d78 +e44ef7042c87d3c78a14413ad7d54e4ed642ad89 diff --git a/.github/workflows/mirror-external_libs.yml b/.github/workflows/mirror-external_libs.yml new file mode 100644 index 00000000000..e577ac0ed92 --- /dev/null +++ b/.github/workflows/mirror-external_libs.yml @@ -0,0 +1,8 @@ +name: Mirror Repositories +on: + workflow_dispatch: {} +jobs: + lint: + runs-on: ubuntu-latest + steps: + - run: echo Dummy workflow TODO \ No newline at end of file diff --git a/.github/workflows/publish-nargo.yml b/.github/workflows/publish-nargo.yml index 26a4c701c5b..55ba754e6ae 100644 --- a/.github/workflows/publish-nargo.yml +++ b/.github/workflows/publish-nargo.yml @@ -110,7 +110,7 @@ jobs: strategy: fail-fast: false matrix: - target: [x86_64-unknown-linux-gnu, x86_64-unknown-linux-musl] + target: [x86_64-unknown-linux-gnu, x86_64-unknown-linux-musl, aarch64-unknown-linux-gnu] timeout-minutes: 30 steps: diff --git a/Cargo.lock b/Cargo.lock index cacfc06327d..1df84a80bc7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -451,6 +451,7 @@ dependencies = [ "noirc_errors", "noirc_frontend", "regex", + "tiny-keccak", ] [[package]] @@ -1186,6 +1187,19 @@ dependencies = [ "syn 2.0.64", ] +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if 1.0.0", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core 0.9.8", +] + [[package]] name = "debugid" version = "0.8.0" @@ -1382,6 +1396,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "log", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -1519,7 +1542,6 @@ dependencies = [ "codespan-reporting", "iter-extended", "serde", - "tempfile", ] [[package]] @@ -2017,12 +2039,17 @@ dependencies = [ [[package]] name = "inferno" -version = "0.11.15" +version = "0.11.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fb7c1b80a1dfa604bb4a649a5c5aeef3d913f7c520cb42b40e534e8a61bcdfc" +checksum = "321f0f839cd44a4686e9504b0a62b4d69a50b62072144c71c68f5873c167b8d9" dependencies = [ "ahash 0.8.11", - "indexmap 1.9.3", + "clap", + "crossbeam-channel", + "crossbeam-utils", + "dashmap", + "env_logger", + "indexmap 2.2.6", "is-terminal", "itoa", "log", @@ -2714,6 +2741,28 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "noir_profiler" +version = "0.31.0" +dependencies = [ + "acir", + "clap", + "color-eyre", + "const_format", + "fm", + "im", + "inferno", + "noirc_abi", + "noirc_artifacts", + "noirc_driver", + "noirc_errors", + "serde", + "serde_json", + "tempfile", + "tracing-appender", + "tracing-subscriber", +] + [[package]] name = "noir_wasm" version = "0.31.0" @@ -2836,6 +2885,7 @@ name = "noirc_evaluator" version = "0.31.0" dependencies = [ "acvm", + "bn254_blackbox_solver", "chrono", "fxhash", "im", @@ -2843,6 +2893,7 @@ dependencies = [ "noirc_errors", "noirc_frontend", "num-bigint", + "proptest", "serde", "thiserror", "tracing", @@ -2867,6 +2918,7 @@ dependencies = [ "num-bigint", "num-traits", "petgraph", + "rangemap", "regex", "rustc-hash", "serde", @@ -2875,7 +2927,6 @@ dependencies = [ "smol_str", "strum", "strum_macros", - "tempfile", "thiserror", "tracing", ] @@ -3515,6 +3566,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rangemap" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "977b1e897f9d764566891689e642653e5ed90c6895106acd005eb4c1d0203991" + [[package]] name = "rayon" version = "1.8.0" diff --git a/Cargo.toml b/Cargo.toml index 129661d157d..8cd5defa121 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ members = [ "tooling/noirc_abi", "tooling/noirc_abi_wasm", "tooling/acvm_cli", + "tooling/profiler", # ACVM "acvm-repo/acir_field", "acvm-repo/acir", @@ -36,7 +37,7 @@ members = [ # Utility crates "utils/iter-extended", ] -default-members = ["tooling/nargo_cli", "tooling/acvm_cli"] +default-members = ["tooling/nargo_cli", "tooling/acvm_cli", "tooling/profiler"] resolver = "2" [workspace.package] @@ -84,7 +85,7 @@ acvm_cli = { path = "tooling/acvm_cli" } # Arkworks ark-bn254 = { version = "^0.4.0", default-features = false, features = ["curve"] } ark-bls12-381 = { version = "^0.4.0", default-features = false, features = ["curve"] } -grumpkin = { version = "0.1.0", package = "noir_grumpkin", features = ["std"] } +grumpkin = { version = "0.1.0", package = "noir_grumpkin", features = ["std"] } ark-ec = { version = "^0.4.0", default-features = false } ark-ff = { version = "^0.4.0", default-features = false } ark-std = { version = "^0.4.0", default-features = false } @@ -143,11 +144,11 @@ similar-asserts = "1.5.0" tempfile = "3.6.0" jsonrpc = { version = "0.16.0", features = ["minreq_http"] } flate2 = "1.0.24" +color-eyre = "0.6.2" rand = "0.8.5" proptest = "1.2.0" proptest-derive = "0.4.0" - im = { version = "15.1", features = ["serde"] } tracing = "0.1.40" tracing-web = "0.1.3" diff --git a/acvm-repo/acir/codegen/acir.cpp b/acvm-repo/acir/codegen/acir.cpp index 47e184a6332..c1160930571 100644 --- a/acvm-repo/acir/codegen/acir.cpp +++ b/acvm-repo/acir/codegen/acir.cpp @@ -13,8 +13,33 @@ namespace Program { static Witness bincodeDeserialize(std::vector); }; + struct ConstantOrWitnessEnum { + + struct Constant { + std::string value; + + friend bool operator==(const Constant&, const Constant&); + std::vector bincodeSerialize() const; + static Constant bincodeDeserialize(std::vector); + }; + + struct Witness { + Program::Witness value; + + friend bool operator==(const Witness&, const Witness&); + std::vector bincodeSerialize() const; + static Witness bincodeDeserialize(std::vector); + }; + + std::variant value; + + friend bool operator==(const ConstantOrWitnessEnum&, const ConstantOrWitnessEnum&); + std::vector bincodeSerialize() const; + static ConstantOrWitnessEnum bincodeDeserialize(std::vector); + }; + struct FunctionInput { - Program::Witness witness; + Program::ConstantOrWitnessEnum input; uint32_t num_bits; friend bool operator==(const FunctionInput&, const FunctionInput&); @@ -5716,6 +5741,124 @@ Program::Circuit serde::Deserializable::deserialize(Deserializ return obj; } +namespace Program { + + inline bool operator==(const ConstantOrWitnessEnum &lhs, const ConstantOrWitnessEnum &rhs) { + if (!(lhs.value == rhs.value)) { return false; } + return true; + } + + inline std::vector ConstantOrWitnessEnum::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline ConstantOrWitnessEnum ConstantOrWitnessEnum::bincodeDeserialize(std::vector input) { + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw serde::deserialization_error("Some input bytes were not read"); + } + return value; + } + +} // end of namespace Program + +template <> +template +void serde::Serializable::serialize(const Program::ConstantOrWitnessEnum &obj, Serializer &serializer) { + serializer.increase_container_depth(); + serde::Serializable::serialize(obj.value, serializer); + serializer.decrease_container_depth(); +} + +template <> +template +Program::ConstantOrWitnessEnum serde::Deserializable::deserialize(Deserializer &deserializer) { + deserializer.increase_container_depth(); + Program::ConstantOrWitnessEnum obj; + obj.value = serde::Deserializable::deserialize(deserializer); + deserializer.decrease_container_depth(); + return obj; +} + +namespace Program { + + inline bool operator==(const ConstantOrWitnessEnum::Constant &lhs, const ConstantOrWitnessEnum::Constant &rhs) { + if (!(lhs.value == rhs.value)) { return false; } + return true; + } + + inline std::vector ConstantOrWitnessEnum::Constant::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline ConstantOrWitnessEnum::Constant ConstantOrWitnessEnum::Constant::bincodeDeserialize(std::vector input) { + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw serde::deserialization_error("Some input bytes were not read"); + } + return value; + } + +} // end of namespace Program + +template <> +template +void serde::Serializable::serialize(const Program::ConstantOrWitnessEnum::Constant &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.value, serializer); +} + +template <> +template +Program::ConstantOrWitnessEnum::Constant serde::Deserializable::deserialize(Deserializer &deserializer) { + Program::ConstantOrWitnessEnum::Constant obj; + obj.value = serde::Deserializable::deserialize(deserializer); + return obj; +} + +namespace Program { + + inline bool operator==(const ConstantOrWitnessEnum::Witness &lhs, const ConstantOrWitnessEnum::Witness &rhs) { + if (!(lhs.value == rhs.value)) { return false; } + return true; + } + + inline std::vector ConstantOrWitnessEnum::Witness::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline ConstantOrWitnessEnum::Witness ConstantOrWitnessEnum::Witness::bincodeDeserialize(std::vector input) { + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw serde::deserialization_error("Some input bytes were not read"); + } + return value; + } + +} // end of namespace Program + +template <> +template +void serde::Serializable::serialize(const Program::ConstantOrWitnessEnum::Witness &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.value, serializer); +} + +template <> +template +Program::ConstantOrWitnessEnum::Witness serde::Deserializable::deserialize(Deserializer &deserializer) { + Program::ConstantOrWitnessEnum::Witness obj; + obj.value = serde::Deserializable::deserialize(deserializer); + return obj; +} + namespace Program { inline bool operator==(const Directive &lhs, const Directive &rhs) { @@ -6086,7 +6229,7 @@ Program::ExpressionWidth::Bounded serde::Deserializable template void serde::Serializable::serialize(const Program::FunctionInput &obj, Serializer &serializer) { serializer.increase_container_depth(); - serde::Serializable::serialize(obj.witness, serializer); + serde::Serializable::serialize(obj.input, serializer); serde::Serializable::serialize(obj.num_bits, serializer); serializer.decrease_container_depth(); } @@ -6122,7 +6265,7 @@ template Program::FunctionInput serde::Deserializable::deserialize(Deserializer &deserializer) { deserializer.increase_container_depth(); Program::FunctionInput obj; - obj.witness = serde::Deserializable::deserialize(deserializer); + obj.input = serde::Deserializable::deserialize(deserializer); obj.num_bits = serde::Deserializable::deserialize(deserializer); deserializer.decrease_container_depth(); return obj; diff --git a/acvm-repo/acir/src/circuit/black_box_functions.rs b/acvm-repo/acir/src/circuit/black_box_functions.rs index 419c0266b69..fb7d9eb584c 100644 --- a/acvm-repo/acir/src/circuit/black_box_functions.rs +++ b/acvm-repo/acir/src/circuit/black_box_functions.rs @@ -82,43 +82,10 @@ pub enum BlackBoxFunc { /// /// [grumpkin]: https://hackmd.io/@aztec-network/ByzgNxBfd#2-Grumpkin---A-curve-on-top-of-BN-254-for-SNARK-efficient-group-operations SchnorrVerify, - - /// Calculates a Pedersen commitment to the inputs. - /// - /// Computes a Pedersen commitment of the inputs using generators of the - /// embedded curve - /// - input: vector of (witness, 254) - /// - output: 2 witnesses representing the x,y coordinates of the resulting - /// Grumpkin point - /// - domain separator: a constant public value (a field element) that you - /// can use so that the commitment also depends on the domain separator. - /// Noir uses 0 as domain separator. - /// - /// The backend should handle proper conversion between the inputs being ACIR - /// field elements and the scalar field of the embedded curve. In the case of - /// Aztec's Barretenberg, the latter is bigger than the ACIR field so it is - /// straightforward. The Pedersen generators are managed by the proving - /// system. - /// - /// The commitment is expected to be additively homomorphic + /// Will be deprecated PedersenCommitment, - - /// Calculates a Pedersen hash to the inputs. - /// - /// Computes a Pedersen hash of the inputs and their number, using - /// generators of the embedded curve - /// - input: vector of (witness, 254) - /// - output: the x-coordinate of the pedersen commitment of the - /// 'prepended input' (see below) - /// - domain separator: a constant public value (a field element) that you - /// can use so that the hash also depends on the domain separator. Noir - /// uses 0 as domain separator. - /// - /// In Barretenberg, PedersenHash is doing the same as PedersenCommitment, - /// except that it prepends the inputs with their length. This is expected - /// to not be additively homomorphic. + /// Will be deprecated PedersenHash, - /// Verifies a ECDSA signature over the secp256k1 curve. /// - inputs: /// - x coordinate of public key as 32 bytes @@ -242,8 +209,6 @@ impl BlackBoxFunc { BlackBoxFunc::SchnorrVerify => "schnorr_verify", BlackBoxFunc::Blake2s => "blake2s", BlackBoxFunc::Blake3 => "blake3", - BlackBoxFunc::PedersenCommitment => "pedersen_commitment", - BlackBoxFunc::PedersenHash => "pedersen_hash", BlackBoxFunc::EcdsaSecp256k1 => "ecdsa_secp256k1", BlackBoxFunc::MultiScalarMul => "multi_scalar_mul", BlackBoxFunc::EmbeddedCurveAdd => "embedded_curve_add", @@ -262,6 +227,8 @@ impl BlackBoxFunc { BlackBoxFunc::BigIntToLeBytes => "bigint_to_le_bytes", BlackBoxFunc::Poseidon2Permutation => "poseidon2_permutation", BlackBoxFunc::Sha256Compression => "sha256_compression", + BlackBoxFunc::PedersenCommitment => "pedersen_commitment", + BlackBoxFunc::PedersenHash => "pedersen_hash", } } @@ -272,8 +239,6 @@ impl BlackBoxFunc { "schnorr_verify" => Some(BlackBoxFunc::SchnorrVerify), "blake2s" => Some(BlackBoxFunc::Blake2s), "blake3" => Some(BlackBoxFunc::Blake3), - "pedersen_commitment" => Some(BlackBoxFunc::PedersenCommitment), - "pedersen_hash" => Some(BlackBoxFunc::PedersenHash), "ecdsa_secp256k1" => Some(BlackBoxFunc::EcdsaSecp256k1), "ecdsa_secp256r1" => Some(BlackBoxFunc::EcdsaSecp256r1), "multi_scalar_mul" => Some(BlackBoxFunc::MultiScalarMul), @@ -292,6 +257,8 @@ impl BlackBoxFunc { "bigint_to_le_bytes" => Some(BlackBoxFunc::BigIntToLeBytes), "poseidon2_permutation" => Some(BlackBoxFunc::Poseidon2Permutation), "sha256_compression" => Some(BlackBoxFunc::Sha256Compression), + "pedersen_commitment" => Some(BlackBoxFunc::PedersenCommitment), + "pedersen_hash" => Some(BlackBoxFunc::PedersenHash), _ => None, } } diff --git a/acvm-repo/acir/src/circuit/mod.rs b/acvm-repo/acir/src/circuit/mod.rs index 7f3c1890717..5d749e709b3 100644 --- a/acvm-repo/acir/src/circuit/mod.rs +++ b/acvm-repo/acir/src/circuit/mod.rs @@ -372,34 +372,29 @@ mod tests { fn and_opcode() -> Opcode { Opcode::BlackBoxFuncCall(BlackBoxFuncCall::AND { - lhs: FunctionInput { witness: Witness(1), num_bits: 4 }, - rhs: FunctionInput { witness: Witness(2), num_bits: 4 }, + lhs: FunctionInput::witness(Witness(1), 4), + rhs: FunctionInput::witness(Witness(2), 4), output: Witness(3), }) } fn range_opcode() -> Opcode { Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { - input: FunctionInput { witness: Witness(1), num_bits: 8 }, + input: FunctionInput::witness(Witness(1), 8), }) } fn keccakf1600_opcode() -> Opcode { - let inputs: Box<[FunctionInput; 25]> = Box::new(std::array::from_fn(|i| FunctionInput { - witness: Witness(i as u32 + 1), - num_bits: 8, - })); + let inputs: Box<[FunctionInput; 25]> = + Box::new(std::array::from_fn(|i| FunctionInput::witness(Witness(i as u32 + 1), 8))); let outputs: Box<[Witness; 25]> = Box::new(std::array::from_fn(|i| Witness(i as u32 + 26))); Opcode::BlackBoxFuncCall(BlackBoxFuncCall::Keccakf1600 { inputs, outputs }) } fn schnorr_verify_opcode() -> Opcode { - let public_key_x = - FunctionInput { witness: Witness(1), num_bits: FieldElement::max_num_bits() }; - let public_key_y = - FunctionInput { witness: Witness(2), num_bits: FieldElement::max_num_bits() }; - let signature: Box<[FunctionInput; 64]> = Box::new(std::array::from_fn(|i| { - FunctionInput { witness: Witness(i as u32 + 3), num_bits: 8 } - })); - let message: Vec = vec![FunctionInput { witness: Witness(67), num_bits: 8 }]; + let public_key_x = FunctionInput::witness(Witness(1), FieldElement::max_num_bits()); + let public_key_y = FunctionInput::witness(Witness(2), FieldElement::max_num_bits()); + let signature: Box<[FunctionInput; 64]> = + Box::new(std::array::from_fn(|i| FunctionInput::witness(Witness(i as u32 + 3), 8))); + let message: Vec> = vec![FunctionInput::witness(Witness(67), 8)]; let output = Witness(68); Opcode::BlackBoxFuncCall(BlackBoxFuncCall::SchnorrVerify { @@ -425,7 +420,7 @@ mod tests { }; let program = Program { functions: vec![circuit], unconstrained_functions: Vec::new() }; - fn read_write Deserialize<'a>>( + fn read_write Deserialize<'a> + AcirField>( program: Program, ) -> (Program, Program) { let bytes = Program::serialize_program(&program); diff --git a/acvm-repo/acir/src/circuit/opcodes.rs b/acvm-repo/acir/src/circuit/opcodes.rs index 984422c5e3a..d303f9fbbab 100644 --- a/acvm-repo/acir/src/circuit/opcodes.rs +++ b/acvm-repo/acir/src/circuit/opcodes.rs @@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize}; mod black_box_function_call; mod memory_operation; -pub use black_box_function_call::{BlackBoxFuncCall, FunctionInput}; +pub use black_box_function_call::{BlackBoxFuncCall, ConstantOrWitnessEnum, FunctionInput}; pub use memory_operation::{BlockId, MemOp}; #[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -67,7 +67,7 @@ pub enum Opcode { /// /// Aztec's Barretenberg uses BN254 as the main curve and Grumpkin as the /// embedded curve. - BlackBoxFuncCall(BlackBoxFuncCall), + BlackBoxFuncCall(BlackBoxFuncCall), /// This opcode is a specialization of a Brillig opcode. Instead of having /// some generic assembly code like Brillig, a directive has a hardcoded diff --git a/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs b/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs index 362e9ba5936..6478f0c7a19 100644 --- a/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs +++ b/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs @@ -4,122 +4,161 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; // Note: Some functions will not use all of the witness // So we need to supply how many bits of the witness is needed + #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct FunctionInput { - pub witness: Witness, +pub enum ConstantOrWitnessEnum { + Constant(F), + Witness(Witness), +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct FunctionInput { + pub input: ConstantOrWitnessEnum, pub num_bits: u32, } +impl FunctionInput { + pub fn to_witness(&self) -> Witness { + match self.input { + ConstantOrWitnessEnum::Constant(_) => unreachable!("ICE - Expected Witness"), + ConstantOrWitnessEnum::Witness(witness) => witness, + } + } + + pub fn num_bits(&self) -> u32 { + self.num_bits + } + + pub fn witness(witness: Witness, num_bits: u32) -> FunctionInput { + FunctionInput { input: ConstantOrWitnessEnum::Witness(witness), num_bits } + } + + pub fn constant(value: F, num_bits: u32) -> FunctionInput { + FunctionInput { input: ConstantOrWitnessEnum::Constant(value), num_bits } + } +} + +impl std::fmt::Display for FunctionInput { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self.input { + ConstantOrWitnessEnum::Constant(constant) => write!(f, "{constant}"), + ConstantOrWitnessEnum::Witness(witness) => write!(f, "{}", witness.0), + } + } +} + #[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum BlackBoxFuncCall { +pub enum BlackBoxFuncCall { AES128Encrypt { - inputs: Vec, - iv: Box<[FunctionInput; 16]>, - key: Box<[FunctionInput; 16]>, + inputs: Vec>, + iv: Box<[FunctionInput; 16]>, + key: Box<[FunctionInput; 16]>, outputs: Vec, }, AND { - lhs: FunctionInput, - rhs: FunctionInput, + lhs: FunctionInput, + rhs: FunctionInput, output: Witness, }, XOR { - lhs: FunctionInput, - rhs: FunctionInput, + lhs: FunctionInput, + rhs: FunctionInput, output: Witness, }, RANGE { - input: FunctionInput, + input: FunctionInput, }, SHA256 { - inputs: Vec, + inputs: Vec>, outputs: Box<[Witness; 32]>, }, Blake2s { - inputs: Vec, + inputs: Vec>, outputs: Box<[Witness; 32]>, }, Blake3 { - inputs: Vec, + inputs: Vec>, outputs: Box<[Witness; 32]>, }, SchnorrVerify { - public_key_x: FunctionInput, - public_key_y: FunctionInput, + public_key_x: FunctionInput, + public_key_y: FunctionInput, #[serde( serialize_with = "serialize_big_array", deserialize_with = "deserialize_big_array_into_box" )] - signature: Box<[FunctionInput; 64]>, - message: Vec, + signature: Box<[FunctionInput; 64]>, + message: Vec>, output: Witness, }, + /// Will be deprecated PedersenCommitment { - inputs: Vec, + inputs: Vec>, domain_separator: u32, outputs: (Witness, Witness), }, + /// Will be deprecated PedersenHash { - inputs: Vec, + inputs: Vec>, domain_separator: u32, output: Witness, }, EcdsaSecp256k1 { - public_key_x: Box<[FunctionInput; 32]>, - public_key_y: Box<[FunctionInput; 32]>, + public_key_x: Box<[FunctionInput; 32]>, + public_key_y: Box<[FunctionInput; 32]>, #[serde( serialize_with = "serialize_big_array", deserialize_with = "deserialize_big_array_into_box" )] - signature: Box<[FunctionInput; 64]>, - hashed_message: Box<[FunctionInput; 32]>, + signature: Box<[FunctionInput; 64]>, + hashed_message: Box<[FunctionInput; 32]>, output: Witness, }, EcdsaSecp256r1 { - public_key_x: Box<[FunctionInput; 32]>, - public_key_y: Box<[FunctionInput; 32]>, + public_key_x: Box<[FunctionInput; 32]>, + public_key_y: Box<[FunctionInput; 32]>, #[serde( serialize_with = "serialize_big_array", deserialize_with = "deserialize_big_array_into_box" )] - signature: Box<[FunctionInput; 64]>, - hashed_message: Box<[FunctionInput; 32]>, + signature: Box<[FunctionInput; 64]>, + hashed_message: Box<[FunctionInput; 32]>, output: Witness, }, MultiScalarMul { - points: Vec, - scalars: Vec, + points: Vec>, + scalars: Vec>, outputs: (Witness, Witness, Witness), }, EmbeddedCurveAdd { - input1: Box<[FunctionInput; 3]>, - input2: Box<[FunctionInput; 3]>, + input1: Box<[FunctionInput; 3]>, + input2: Box<[FunctionInput; 3]>, outputs: (Witness, Witness, Witness), }, Keccak256 { - inputs: Vec, + inputs: Vec>, /// This is the number of bytes to take /// from the input. Note: if `var_message_size` /// is more than the number of bytes in the input, /// then an error is returned. - var_message_size: FunctionInput, + var_message_size: FunctionInput, outputs: Box<[Witness; 32]>, }, Keccakf1600 { - inputs: Box<[FunctionInput; 25]>, + inputs: Box<[FunctionInput; 25]>, outputs: Box<[Witness; 25]>, }, RecursiveAggregation { - verification_key: Vec, - proof: Vec, + verification_key: Vec>, + proof: Vec>, /// These represent the public inputs of the proof we are verifying /// They should be checked against in the circuit after construction /// of a new aggregation state - public_inputs: Vec, + public_inputs: Vec>, /// A key hash is used to check the validity of the verification key. /// The circuit implementing this opcode can use this hash to ensure that the /// key provided to the circuit matches the key produced by the circuit creator - key_hash: FunctionInput, + key_hash: FunctionInput, }, BigIntAdd { lhs: u32, @@ -142,7 +181,7 @@ pub enum BlackBoxFuncCall { output: u32, }, BigIntFromLeBytes { - inputs: Vec, + inputs: Vec>, modulus: Vec, output: u32, }, @@ -154,7 +193,7 @@ pub enum BlackBoxFuncCall { /// outputting the permuted state. Poseidon2Permutation { /// Input state for the permutation of Poseidon2 - inputs: Vec, + inputs: Vec>, /// Permuted state outputs: Vec, /// State length (in number of field elements) @@ -170,15 +209,15 @@ pub enum BlackBoxFuncCall { /// * `outputs` - result of the input compressed into 256 bits Sha256Compression { /// 512 bits of the input message, represented by 16 u32s - inputs: Box<[FunctionInput; 16]>, + inputs: Box<[FunctionInput; 16]>, /// Vector of 8 u32s used to compress the input - hash_values: Box<[FunctionInput; 8]>, + hash_values: Box<[FunctionInput; 8]>, /// Output of the compression, represented by 8 u32s outputs: Box<[Witness; 8]>, }, } -impl BlackBoxFuncCall { +impl BlackBoxFuncCall { pub fn get_black_box_func(&self) -> BlackBoxFunc { match self { BlackBoxFuncCall::AES128Encrypt { .. } => BlackBoxFunc::AES128Encrypt, @@ -189,8 +228,6 @@ impl BlackBoxFuncCall { BlackBoxFuncCall::Blake2s { .. } => BlackBoxFunc::Blake2s, BlackBoxFuncCall::Blake3 { .. } => BlackBoxFunc::Blake3, BlackBoxFuncCall::SchnorrVerify { .. } => BlackBoxFunc::SchnorrVerify, - BlackBoxFuncCall::PedersenCommitment { .. } => BlackBoxFunc::PedersenCommitment, - BlackBoxFuncCall::PedersenHash { .. } => BlackBoxFunc::PedersenHash, BlackBoxFuncCall::EcdsaSecp256k1 { .. } => BlackBoxFunc::EcdsaSecp256k1, BlackBoxFuncCall::EcdsaSecp256r1 { .. } => BlackBoxFunc::EcdsaSecp256r1, BlackBoxFuncCall::MultiScalarMul { .. } => BlackBoxFunc::MultiScalarMul, @@ -206,6 +243,8 @@ impl BlackBoxFuncCall { BlackBoxFuncCall::BigIntToLeBytes { .. } => BlackBoxFunc::BigIntToLeBytes, BlackBoxFuncCall::Poseidon2Permutation { .. } => BlackBoxFunc::Poseidon2Permutation, BlackBoxFuncCall::Sha256Compression { .. } => BlackBoxFunc::Sha256Compression, + BlackBoxFuncCall::PedersenCommitment { .. } => BlackBoxFunc::PedersenCommitment, + BlackBoxFuncCall::PedersenHash { .. } => BlackBoxFunc::PedersenHash, } } @@ -213,15 +252,15 @@ impl BlackBoxFuncCall { self.get_black_box_func().name() } - pub fn get_inputs_vec(&self) -> Vec { + pub fn get_inputs_vec(&self) -> Vec> { match self { BlackBoxFuncCall::AES128Encrypt { inputs, .. } | BlackBoxFuncCall::SHA256 { inputs, .. } | BlackBoxFuncCall::Blake2s { inputs, .. } | BlackBoxFuncCall::Blake3 { inputs, .. } + | BlackBoxFuncCall::BigIntFromLeBytes { inputs, .. } | BlackBoxFuncCall::PedersenCommitment { inputs, .. } | BlackBoxFuncCall::PedersenHash { inputs, .. } - | BlackBoxFuncCall::BigIntFromLeBytes { inputs, .. } | BlackBoxFuncCall::Poseidon2Permutation { inputs, .. } => inputs.to_vec(), BlackBoxFuncCall::Keccakf1600 { inputs, .. } => inputs.to_vec(), @@ -238,7 +277,7 @@ impl BlackBoxFuncCall { | BlackBoxFuncCall::BigIntDiv { .. } | BlackBoxFuncCall::BigIntToLeBytes { .. } => Vec::new(), BlackBoxFuncCall::MultiScalarMul { points, scalars, .. } => { - let mut inputs: Vec = Vec::with_capacity(points.len() * 2); + let mut inputs: Vec> = Vec::with_capacity(points.len() * 2); inputs.extend(points.iter().copied()); inputs.extend(scalars.iter().copied()); inputs @@ -254,7 +293,7 @@ impl BlackBoxFuncCall { message, .. } => { - let mut inputs: Vec = + let mut inputs: Vec> = Vec::with_capacity(2 + signature.len() + message.len()); inputs.push(*public_key_x); inputs.push(*public_key_y); @@ -362,7 +401,7 @@ impl BlackBoxFuncCall { const ABBREVIATION_LIMIT: usize = 5; -fn get_inputs_string(inputs: &[FunctionInput]) -> String { +fn get_inputs_string(inputs: &[FunctionInput]) -> String { // Once a vectors length gets above this limit, // instead of listing all of their elements, we use ellipses // to abbreviate them @@ -371,7 +410,7 @@ fn get_inputs_string(inputs: &[FunctionInput]) -> String { if should_abbreviate_inputs { let mut result = String::new(); for (index, inp) in inputs.iter().enumerate() { - result += &format!("(_{}, num_bits: {})", inp.witness.witness_index(), inp.num_bits); + result += &format!("({})", inp); // Add a comma, unless it is the last entry if index != inputs.len() - 1 { result += ", "; @@ -383,14 +422,7 @@ fn get_inputs_string(inputs: &[FunctionInput]) -> String { let last = inputs.last().unwrap(); let mut result = String::new(); - - result += &format!( - "(_{}, num_bits: {})...(_{}, num_bits: {})", - first.witness.witness_index(), - first.num_bits, - last.witness.witness_index(), - last.num_bits, - ); + result += &format!("({})...({})", first, last,); result } @@ -419,8 +451,16 @@ fn get_outputs_string(outputs: &[Witness]) -> String { } } -impl std::fmt::Display for BlackBoxFuncCall { +impl std::fmt::Display for BlackBoxFuncCall { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BlackBoxFuncCall::PedersenCommitment { .. } => { + return write!(f, "BLACKBOX::Deprecated") + } + BlackBoxFuncCall::PedersenHash { .. } => return write!(f, "BLACKBOX::Deprecated"), + _ => (), + } + let uppercase_name = self.name().to_uppercase(); write!(f, "BLACKBOX::{uppercase_name} ")?; // INPUTS @@ -440,23 +480,20 @@ impl std::fmt::Display for BlackBoxFuncCall { write!(f, "]")?; - // SPECIFIC PARAMETERS - match self { - BlackBoxFuncCall::PedersenCommitment { domain_separator, .. } => { - write!(f, " domain_separator: {domain_separator}") - } - _ => write!(f, ""), - } + write!(f, "") } } -impl std::fmt::Debug for BlackBoxFuncCall { +impl std::fmt::Debug for BlackBoxFuncCall { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self, f) } } -fn serialize_big_array(big_array: &[FunctionInput; 64], s: S) -> Result +fn serialize_big_array( + big_array: &[FunctionInput; 64], + s: S, +) -> Result where S: Serializer, { @@ -465,15 +502,15 @@ where (*big_array).serialize(s) } -fn deserialize_big_array_into_box<'de, D>( +fn deserialize_big_array_into_box<'de, D, F: Deserialize<'de>>( deserializer: D, -) -> Result, D::Error> +) -> Result; 64]>, D::Error> where D: Deserializer<'de>, { use serde_big_array::BigArray; - let big_array: [FunctionInput; 64] = BigArray::deserialize(deserializer)?; + let big_array: [FunctionInput; 64] = BigArray::deserialize(deserializer)?; Ok(Box::new(big_array)) } @@ -486,23 +523,18 @@ mod tests { use super::{BlackBoxFuncCall, FunctionInput}; fn keccakf1600_opcode() -> Opcode { - let inputs: Box<[FunctionInput; 25]> = Box::new(std::array::from_fn(|i| FunctionInput { - witness: Witness(i as u32 + 1), - num_bits: 8, - })); + let inputs: Box<[FunctionInput; 25]> = + Box::new(std::array::from_fn(|i| FunctionInput::witness(Witness(i as u32 + 1), 8))); let outputs: Box<[Witness; 25]> = Box::new(std::array::from_fn(|i| Witness(i as u32 + 26))); Opcode::BlackBoxFuncCall(BlackBoxFuncCall::Keccakf1600 { inputs, outputs }) } fn schnorr_verify_opcode() -> Opcode { - let public_key_x = - FunctionInput { witness: Witness(1), num_bits: FieldElement::max_num_bits() }; - let public_key_y = - FunctionInput { witness: Witness(2), num_bits: FieldElement::max_num_bits() }; - let signature: Box<[FunctionInput; 64]> = Box::new(std::array::from_fn(|i| { - FunctionInput { witness: Witness(i as u32 + 3), num_bits: 8 } - })); - let message: Vec = vec![FunctionInput { witness: Witness(67), num_bits: 8 }]; + let public_key_x = FunctionInput::witness(Witness(1), FieldElement::max_num_bits()); + let public_key_y = FunctionInput::witness(Witness(2), FieldElement::max_num_bits()); + let signature: Box<[FunctionInput; 64]> = + Box::new(std::array::from_fn(|i| FunctionInput::witness(Witness(i as u32 + 3), 8))); + let message: Vec> = vec![FunctionInput::witness(Witness(67), 8)]; let output = Witness(68); Opcode::BlackBoxFuncCall(BlackBoxFuncCall::SchnorrVerify { diff --git a/acvm-repo/acir/src/lib.rs b/acvm-repo/acir/src/lib.rs index f064cfaca0e..540e0f07eb5 100644 --- a/acvm-repo/acir/src/lib.rs +++ b/acvm-repo/acir/src/lib.rs @@ -42,7 +42,7 @@ mod reflection { circuit::{ brillig::{BrilligInputs, BrilligOutputs}, directives::Directive, - opcodes::{BlackBoxFuncCall, BlockType}, + opcodes::{BlackBoxFuncCall, BlockType, ConstantOrWitnessEnum, FunctionInput}, AssertionPayload, Circuit, ExpressionOrMemory, ExpressionWidth, Opcode, OpcodeLocation, Program, }, @@ -68,7 +68,9 @@ mod reflection { tracer.trace_simple_type::>().unwrap(); tracer.trace_simple_type::().unwrap(); tracer.trace_simple_type::().unwrap(); - tracer.trace_simple_type::().unwrap(); + tracer.trace_simple_type::>().unwrap(); + tracer.trace_simple_type::>().unwrap(); + tracer.trace_simple_type::>().unwrap(); tracer.trace_simple_type::>().unwrap(); tracer.trace_simple_type::().unwrap(); tracer.trace_simple_type::>().unwrap(); diff --git a/acvm-repo/acir/tests/test_program_serialization.rs b/acvm-repo/acir/tests/test_program_serialization.rs index dfcb1a8bb86..3047ac002e8 100644 --- a/acvm-repo/acir/tests/test_program_serialization.rs +++ b/acvm-repo/acir/tests/test_program_serialization.rs @@ -62,13 +62,13 @@ fn multi_scalar_mul_circuit() { let multi_scalar_mul: Opcode = Opcode::BlackBoxFuncCall(BlackBoxFuncCall::MultiScalarMul { points: vec![ - FunctionInput { witness: Witness(1), num_bits: 128 }, - FunctionInput { witness: Witness(2), num_bits: 128 }, - FunctionInput { witness: Witness(3), num_bits: 1 }, + FunctionInput::witness(Witness(1), 128), + FunctionInput::witness(Witness(2), 128), + FunctionInput::witness(Witness(3), 1), ], scalars: vec![ - FunctionInput { witness: Witness(4), num_bits: 128 }, - FunctionInput { witness: Witness(5), num_bits: 128 }, + FunctionInput::witness(Witness(4), 128), + FunctionInput::witness(Witness(5), 128), ], outputs: (Witness(6), Witness(7), Witness(8)), }); @@ -91,56 +91,26 @@ fn multi_scalar_mul_circuit() { let bytes = Program::serialize_program(&program); let expected_serialization: Vec = vec![ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 93, 141, 219, 10, 0, 32, 8, 67, 243, 214, 5, 250, 232, - 62, 189, 69, 123, 176, 132, 195, 116, 50, 149, 114, 107, 0, 97, 127, 116, 2, 75, 243, 2, - 74, 53, 122, 202, 189, 211, 15, 106, 5, 13, 116, 238, 35, 221, 81, 230, 61, 249, 37, 253, - 250, 179, 79, 109, 218, 22, 67, 227, 173, 0, 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 93, 141, 11, 10, 0, 32, 8, 67, 43, 181, 15, 116, 232, + 142, 158, 210, 130, 149, 240, 112, 234, 212, 156, 78, 12, 39, 67, 71, 158, 142, 80, 29, 44, + 228, 66, 90, 168, 119, 189, 74, 115, 131, 174, 78, 115, 58, 124, 70, 254, 130, 59, 74, 253, + 68, 255, 255, 221, 39, 54, 221, 93, 91, 132, 193, 0, 0, 0, ]; assert_eq!(bytes, expected_serialization) } -#[test] -fn pedersen_circuit() { - let pedersen = Opcode::BlackBoxFuncCall(BlackBoxFuncCall::PedersenCommitment { - inputs: vec![FunctionInput { witness: Witness(1), num_bits: FieldElement::max_num_bits() }], - outputs: (Witness(2), Witness(3)), - domain_separator: 0, - }); - - let circuit: Circuit = Circuit { - current_witness_index: 4, - opcodes: vec![pedersen], - private_parameters: BTreeSet::from([Witness(1)]), - return_values: PublicInputs(BTreeSet::from_iter(vec![Witness(2), Witness(3)])), - ..Circuit::default() - }; - let program = Program { functions: vec![circuit], unconstrained_functions: vec![] }; - - let bytes = Program::serialize_program(&program); - - let expected_serialization: Vec = vec![ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 93, 74, 73, 10, 0, 0, 4, 180, 29, 252, 255, 193, 66, 40, - 76, 77, 179, 34, 20, 36, 136, 237, 83, 245, 101, 107, 79, 65, 94, 253, 214, 217, 255, 239, - 192, 1, 43, 124, 181, 238, 113, 0, 0, 0, - ]; - assert_eq!(bytes, expected_serialization) -} - #[test] fn schnorr_verify_circuit() { - let public_key_x = - FunctionInput { witness: Witness(1), num_bits: FieldElement::max_num_bits() }; - let public_key_y = - FunctionInput { witness: Witness(2), num_bits: FieldElement::max_num_bits() }; - let signature: [FunctionInput; 64] = (3..(3 + 64)) - .map(|i| FunctionInput { witness: Witness(i), num_bits: 8 }) + let public_key_x = FunctionInput::witness(Witness(1), FieldElement::max_num_bits()); + let public_key_y = FunctionInput::witness(Witness(2), FieldElement::max_num_bits()); + let signature: [FunctionInput; 64] = (3..(3 + 64)) + .map(|i| FunctionInput::witness(Witness(i), 8)) .collect::>() .try_into() .unwrap(); - let message = ((3 + 64)..(3 + 64 + 10)) - .map(|i| FunctionInput { witness: Witness(i), num_bits: 8 }) - .collect(); + let message = + ((3 + 64)..(3 + 64 + 10)).map(|i| FunctionInput::witness(Witness(i), 8)).collect(); let output = Witness(3 + 64 + 10); let last_input = output.witness_index() - 1; @@ -164,22 +134,24 @@ fn schnorr_verify_circuit() { let bytes = Program::serialize_program(&program); let expected_serialization: Vec = vec![ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 85, 210, 85, 78, 67, 81, 24, 133, 209, 226, 238, 238, - 238, 238, 238, 165, 148, 82, 102, 193, 252, 135, 64, 232, 78, 87, 147, 114, 147, 147, 5, - 47, 132, 252, 251, 107, 41, 212, 191, 159, 218, 107, 241, 115, 236, 226, 111, 237, 181, - 178, 173, 246, 186, 107, 175, 157, 29, 236, 100, 23, 27, 175, 135, 189, 236, 99, 63, 7, 56, - 200, 33, 14, 115, 132, 163, 28, 227, 56, 39, 56, 201, 41, 78, 115, 134, 179, 156, 227, 60, - 23, 184, 200, 37, 46, 115, 133, 171, 92, 227, 58, 55, 184, 201, 45, 110, 115, 135, 187, - 220, 227, 62, 15, 120, 200, 35, 30, 243, 132, 167, 60, 227, 57, 47, 120, 201, 43, 94, 243, - 134, 183, 188, 227, 61, 31, 248, 200, 39, 62, 243, 133, 175, 77, 59, 230, 123, 243, 123, - 145, 239, 44, 241, 131, 101, 126, 178, 194, 47, 86, 249, 237, 239, 86, 153, 238, 210, 92, - 122, 75, 107, 233, 44, 141, 53, 250, 234, 241, 191, 164, 167, 180, 148, 142, 210, 80, 250, - 73, 59, 233, 38, 205, 164, 151, 180, 146, 78, 210, 72, 250, 72, 27, 233, 34, 77, 164, 135, - 180, 144, 14, 210, 64, 246, 95, 46, 212, 119, 207, 230, 217, 59, 91, 103, 231, 108, 156, - 125, 183, 237, 186, 107, 207, 125, 59, 30, 218, 239, 216, 110, 167, 246, 58, 183, 211, 165, - 125, 174, 237, 114, 107, 143, 123, 59, 60, 186, 255, 179, 187, 191, 186, 115, 209, 125, 75, - 238, 90, 118, 207, 138, 59, 54, 110, 214, 184, 91, 161, 233, 158, 255, 190, 63, 71, 59, 68, - 130, 233, 3, 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 85, 211, 103, 78, 2, 81, 24, 70, 225, 193, 6, 216, 123, + 47, 216, 123, 239, 136, 136, 136, 136, 136, 187, 96, 255, 75, 32, 112, 194, 55, 201, 129, + 100, 50, 79, 244, 7, 228, 222, 243, 102, 146, 254, 167, 221, 123, 50, 97, 222, 217, 120, + 243, 116, 226, 61, 36, 15, 247, 158, 92, 120, 68, 30, 149, 199, 228, 172, 156, 147, 243, + 242, 184, 60, 33, 79, 202, 83, 242, 180, 60, 35, 207, 202, 115, 242, 188, 188, 32, 47, 202, + 75, 242, 178, 188, 34, 175, 202, 107, 242, 186, 188, 33, 111, 202, 91, 242, 182, 188, 35, + 23, 228, 93, 121, 79, 222, 151, 15, 228, 67, 249, 72, 62, 150, 79, 228, 83, 249, 76, 62, + 151, 47, 228, 75, 249, 74, 190, 150, 111, 228, 91, 249, 78, 190, 151, 31, 228, 71, 249, 73, + 126, 150, 95, 228, 87, 185, 40, 191, 201, 37, 249, 93, 46, 203, 31, 114, 69, 254, 148, 171, + 97, 58, 77, 226, 111, 95, 250, 127, 77, 254, 150, 235, 242, 143, 220, 144, 127, 229, 166, + 252, 39, 183, 194, 255, 241, 253, 45, 253, 14, 182, 201, 38, 217, 34, 27, 100, 123, 233, + 230, 242, 241, 155, 217, 20, 91, 98, 67, 108, 135, 205, 176, 21, 54, 194, 54, 216, 4, 91, + 96, 3, 180, 79, 243, 180, 78, 227, 180, 77, 211, 180, 76, 195, 180, 75, 179, 133, 164, 223, + 40, 109, 210, 36, 45, 210, 32, 237, 209, 28, 173, 209, 24, 109, 209, 20, 45, 209, 16, 237, + 208, 12, 173, 208, 8, 109, 208, 4, 45, 208, 0, 119, 207, 157, 115, 215, 220, 113, 49, 238, + 180, 20, 119, 88, 142, 59, 171, 196, 29, 85, 227, 46, 106, 113, 246, 245, 56, 235, 70, 156, + 109, 51, 206, 50, 61, 179, 244, 220, 18, 157, 231, 192, 167, 11, 75, 28, 99, 152, 25, 5, 0, + 0, ]; assert_eq!(bytes, expected_serialization) diff --git a/acvm-repo/acvm/src/compiler/optimizers/redundant_range.rs b/acvm-repo/acvm/src/compiler/optimizers/redundant_range.rs index 7001c953d63..b03b6715abe 100644 --- a/acvm-repo/acvm/src/compiler/optimizers/redundant_range.rs +++ b/acvm-repo/acvm/src/compiler/optimizers/redundant_range.rs @@ -1,6 +1,6 @@ use acir::{ circuit::{ - opcodes::{BlackBoxFuncCall, FunctionInput}, + opcodes::{BlackBoxFuncCall, ConstantOrWitnessEnum, FunctionInput}, Circuit, Opcode, }, native_types::Witness, @@ -74,7 +74,8 @@ impl RangeOptimizer { } Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { - input: FunctionInput { witness, num_bits }, + input: + FunctionInput { input: ConstantOrWitnessEnum::Witness(witness), num_bits }, }) => Some((*witness, *num_bits)), _ => None, @@ -105,14 +106,15 @@ impl RangeOptimizer { let mut new_order_list = Vec::with_capacity(order_list.len()); let mut optimized_opcodes = Vec::with_capacity(self.circuit.opcodes.len()); for (idx, opcode) in self.circuit.opcodes.into_iter().enumerate() { - let (witness, num_bits) = match &opcode { - Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { input }) => { - (input.witness, input.num_bits) - } + let (witness, num_bits) = match opcode { + Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { + input: + FunctionInput { input: ConstantOrWitnessEnum::Witness(w), num_bits: bits }, + }) => (w, bits), _ => { // If its not the range opcode, add it to the opcode // list and continue; - optimized_opcodes.push(opcode); + optimized_opcodes.push(opcode.clone()); new_order_list.push(order_list[idx]); continue; } @@ -133,7 +135,7 @@ impl RangeOptimizer { if is_lowest_bit_size { already_seen_witness.insert(witness); new_order_list.push(order_list[idx]); - optimized_opcodes.push(opcode); + optimized_opcodes.push(opcode.clone()); } } @@ -158,7 +160,7 @@ mod tests { fn test_circuit(ranges: Vec<(Witness, u32)>) -> Circuit { fn test_range_constraint(witness: Witness, num_bits: u32) -> Opcode { Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { - input: FunctionInput { witness, num_bits }, + input: FunctionInput::witness(witness, num_bits), }) } @@ -201,7 +203,7 @@ mod tests { assert_eq!( optimized_circuit.opcodes[0], Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { - input: FunctionInput { witness: Witness(1), num_bits: 16 } + input: FunctionInput::witness(Witness(1), 16) }) ); } @@ -224,13 +226,13 @@ mod tests { assert_eq!( optimized_circuit.opcodes[0], Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { - input: FunctionInput { witness: Witness(1), num_bits: 16 } + input: FunctionInput::witness(Witness(1), 16) }) ); assert_eq!( optimized_circuit.opcodes[1], Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { - input: FunctionInput { witness: Witness(2), num_bits: 23 } + input: FunctionInput::witness(Witness(2), 23) }) ); } diff --git a/acvm-repo/acvm/src/compiler/transformers/csat.rs b/acvm-repo/acvm/src/compiler/transformers/csat.rs index f2a3cc2c84e..19cc18ca7f3 100644 --- a/acvm-repo/acvm/src/compiler/transformers/csat.rs +++ b/acvm-repo/acvm/src/compiler/transformers/csat.rs @@ -432,7 +432,7 @@ fn fits_in_one_identity(expr: &Expression, width: usize) -> boo return true; } - // We now know that we have a single mul term. We also know that the mul term must match up with two other terms + // We now know that we have a single mul term. We also know that the mul term must match up with at least one of the other terms // A polynomial whose mul terms are non zero which do not match up with two terms in the fan-in cannot fit into one opcode // An example of this is: Axy + Bx + Cy + ... // Notice how the bivariate monomial xy has two univariate monomials with their respective coefficients @@ -461,7 +461,25 @@ fn fits_in_one_identity(expr: &Expression, width: usize) -> boo } } - found_x & found_y + // If the multiplication is a squaring then we must assign the two witnesses to separate wires and so we + // can never get a zero contribution to the width. + let multiplication_is_squaring = mul_term.1 == mul_term.2; + + let mul_term_width_contribution = if !multiplication_is_squaring && (found_x & found_y) { + // Both witnesses involved in the multiplication exist elsewhere in the expression. + // They both do not contribute to the width of the expression as this would be double-counting + // due to their appearance in the linear terms. + 0 + } else if found_x || found_y { + // One of the witnesses involved in the multiplication exists elsewhere in the expression. + // The multiplication then only contributes 1 new witness to the width. + 1 + } else { + // Worst case scenario, the multiplication is using completely unique witnesses so has a contribution of 2. + 2 + }; + + mul_term_width_contribution + expr.linear_combinations.len() <= width } #[cfg(test)] @@ -573,4 +591,20 @@ mod tests { let contains_b = got_optimized_opcode_a.linear_combinations.iter().any(|(_, w)| *w == b); assert!(contains_b); } + + #[test] + fn recognize_expr_with_single_shared_witness_which_fits_in_single_identity() { + // Regression test for an expression which Zac found which should have been preserved but + // was being split into two expressions. + let expr = Expression { + mul_terms: vec![(-FieldElement::from(555u128), Witness(8), Witness(10))], + linear_combinations: vec![ + (FieldElement::one(), Witness(10)), + (FieldElement::one(), Witness(11)), + (-FieldElement::one(), Witness(13)), + ], + q_c: FieldElement::zero(), + }; + assert!(fits_in_one_identity(&expr, 4)); + } } diff --git a/acvm-repo/acvm/src/pwg/blackbox/aes128.rs b/acvm-repo/acvm/src/pwg/blackbox/aes128.rs index 181a78a2a6a..e3c8dc78aa6 100644 --- a/acvm-repo/acvm/src/pwg/blackbox/aes128.rs +++ b/acvm-repo/acvm/src/pwg/blackbox/aes128.rs @@ -11,9 +11,9 @@ use super::utils::{to_u8_array, to_u8_vec}; pub(super) fn solve_aes128_encryption_opcode( initial_witness: &mut WitnessMap, - inputs: &[FunctionInput], - iv: &[FunctionInput; 16], - key: &[FunctionInput; 16], + inputs: &[FunctionInput], + iv: &[FunctionInput; 16], + key: &[FunctionInput; 16], outputs: &[Witness], ) -> Result<(), OpcodeResolutionError> { let scalars = to_u8_vec(initial_witness, inputs)?; diff --git a/acvm-repo/acvm/src/pwg/blackbox/bigint.rs b/acvm-repo/acvm/src/pwg/blackbox/bigint.rs index be5a4613a55..1bce4aa6c5e 100644 --- a/acvm-repo/acvm/src/pwg/blackbox/bigint.rs +++ b/acvm-repo/acvm/src/pwg/blackbox/bigint.rs @@ -1,9 +1,9 @@ +use crate::pwg::input_to_value; use acir::{ circuit::opcodes::FunctionInput, native_types::{Witness, WitnessMap}, AcirField, BlackBoxFunc, }; - use acvm_blackbox_solver::BigIntSolver; use crate::pwg::OpcodeResolutionError; @@ -20,14 +20,14 @@ pub(crate) struct AcvmBigIntSolver { impl AcvmBigIntSolver { pub(crate) fn bigint_from_bytes( &mut self, - inputs: &[FunctionInput], + inputs: &[FunctionInput], modulus: &[u8], output: u32, initial_witness: &mut WitnessMap, ) -> Result<(), OpcodeResolutionError> { let bytes = inputs .iter() - .map(|input| initial_witness.get(&input.witness).unwrap().to_u128() as u8) + .map(|input| input_to_value(initial_witness, *input).unwrap().to_u128() as u8) .collect::>(); self.bigint_solver.bigint_from_bytes(&bytes, modulus, output)?; Ok(()) diff --git a/acvm-repo/acvm/src/pwg/blackbox/embedded_curve_ops.rs b/acvm-repo/acvm/src/pwg/blackbox/embedded_curve_ops.rs index 411a6d1b737..c290faeaa4a 100644 --- a/acvm-repo/acvm/src/pwg/blackbox/embedded_curve_ops.rs +++ b/acvm-repo/acvm/src/pwg/blackbox/embedded_curve_ops.rs @@ -5,28 +5,28 @@ use acir::{ }; use acvm_blackbox_solver::BlackBoxFunctionSolver; -use crate::pwg::{insert_value, witness_to_value, OpcodeResolutionError}; +use crate::pwg::{input_to_value, insert_value, OpcodeResolutionError}; pub(super) fn multi_scalar_mul( backend: &impl BlackBoxFunctionSolver, initial_witness: &mut WitnessMap, - points: &[FunctionInput], - scalars: &[FunctionInput], + points: &[FunctionInput], + scalars: &[FunctionInput], outputs: (Witness, Witness, Witness), ) -> Result<(), OpcodeResolutionError> { let points: Result, _> = - points.iter().map(|input| witness_to_value(initial_witness, input.witness)).collect(); - let points: Vec<_> = points?.into_iter().cloned().collect(); + points.iter().map(|input| input_to_value(initial_witness, *input)).collect(); + let points: Vec<_> = points?.into_iter().collect(); let scalars: Result, _> = - scalars.iter().map(|input| witness_to_value(initial_witness, input.witness)).collect(); + scalars.iter().map(|input| input_to_value(initial_witness, *input)).collect(); let mut scalars_lo = Vec::new(); let mut scalars_hi = Vec::new(); for (i, scalar) in scalars?.into_iter().enumerate() { if i % 2 == 0 { - scalars_lo.push(*scalar); + scalars_lo.push(scalar); } else { - scalars_hi.push(*scalar); + scalars_hi.push(scalar); } } // Call the backend's multi-scalar multiplication function @@ -43,18 +43,24 @@ pub(super) fn multi_scalar_mul( pub(super) fn embedded_curve_add( backend: &impl BlackBoxFunctionSolver, initial_witness: &mut WitnessMap, - input1: [FunctionInput; 3], - input2: [FunctionInput; 3], + input1: [FunctionInput; 3], + input2: [FunctionInput; 3], outputs: (Witness, Witness, Witness), ) -> Result<(), OpcodeResolutionError> { - let input1_x = witness_to_value(initial_witness, input1[0].witness)?; - let input1_y = witness_to_value(initial_witness, input1[1].witness)?; - let input1_infinite = witness_to_value(initial_witness, input1[2].witness)?; - let input2_x = witness_to_value(initial_witness, input2[0].witness)?; - let input2_y = witness_to_value(initial_witness, input2[1].witness)?; - let input2_infinite = witness_to_value(initial_witness, input2[2].witness)?; - let (res_x, res_y, res_infinite) = - backend.ec_add(input1_x, input1_y, input1_infinite, input2_x, input2_y, input2_infinite)?; + let input1_x = input_to_value(initial_witness, input1[0])?; + let input1_y = input_to_value(initial_witness, input1[1])?; + let input1_infinite = input_to_value(initial_witness, input1[2])?; + let input2_x = input_to_value(initial_witness, input2[0])?; + let input2_y = input_to_value(initial_witness, input2[1])?; + let input2_infinite = input_to_value(initial_witness, input2[2])?; + let (res_x, res_y, res_infinite) = backend.ec_add( + &input1_x, + &input1_y, + &input1_infinite, + &input2_x, + &input2_y, + &input2_infinite, + )?; insert_value(&outputs.0, res_x, initial_witness)?; insert_value(&outputs.1, res_y, initial_witness)?; diff --git a/acvm-repo/acvm/src/pwg/blackbox/hash.rs b/acvm-repo/acvm/src/pwg/blackbox/hash.rs index fe9bd46b091..b51139f76b7 100644 --- a/acvm-repo/acvm/src/pwg/blackbox/hash.rs +++ b/acvm-repo/acvm/src/pwg/blackbox/hash.rs @@ -5,15 +5,15 @@ use acir::{ }; use acvm_blackbox_solver::{sha256compression, BlackBoxFunctionSolver, BlackBoxResolutionError}; -use crate::pwg::{insert_value, witness_to_value}; +use crate::pwg::{input_to_value, insert_value}; use crate::OpcodeResolutionError; /// Attempts to solve a 256 bit hash function opcode. /// If successful, `initial_witness` will be mutated to contain the new witness assignment. pub(super) fn solve_generic_256_hash_opcode( initial_witness: &mut WitnessMap, - inputs: &[FunctionInput], - var_message_size: Option<&FunctionInput>, + inputs: &[FunctionInput], + var_message_size: Option<&FunctionInput>, outputs: &[Witness; 32], hash_function: fn(data: &[u8]) -> Result<[u8; 32], BlackBoxResolutionError>, ) -> Result<(), OpcodeResolutionError> { @@ -26,16 +26,15 @@ pub(super) fn solve_generic_256_hash_opcode( /// Reads the hash function input from a [`WitnessMap`]. fn get_hash_input( initial_witness: &WitnessMap, - inputs: &[FunctionInput], - message_size: Option<&FunctionInput>, + inputs: &[FunctionInput], + message_size: Option<&FunctionInput>, ) -> Result, OpcodeResolutionError> { // Read witness assignments. let mut message_input = Vec::new(); for input in inputs.iter() { - let witness = input.witness; - let num_bits = input.num_bits as usize; + let num_bits = input.num_bits() as usize; - let witness_assignment = witness_to_value(initial_witness, witness)?; + let witness_assignment = input_to_value(initial_witness, *input)?; let bytes = witness_assignment.fetch_nearest_bytes(num_bits); message_input.extend(bytes); } @@ -43,8 +42,7 @@ fn get_hash_input( // Truncate the message if there is a `message_size` parameter given match message_size { Some(input) => { - let num_bytes_to_take = - witness_to_value(initial_witness, input.witness)?.to_u128() as usize; + let num_bytes_to_take = input_to_value(initial_witness, *input)?.to_u128() as usize; // If the number of bytes to take is more than the amount of bytes available // in the message, then we error. @@ -76,11 +74,11 @@ fn write_digest_to_outputs( fn to_u32_array( initial_witness: &WitnessMap, - inputs: &[FunctionInput; N], + inputs: &[FunctionInput; N], ) -> Result<[u32; N], OpcodeResolutionError> { let mut result = [0; N]; for (it, input) in result.iter_mut().zip(inputs) { - let witness_value = witness_to_value(initial_witness, input.witness)?; + let witness_value = input_to_value(initial_witness, *input)?; *it = witness_value.to_u128() as u32; } Ok(result) @@ -88,8 +86,8 @@ fn to_u32_array( pub(crate) fn solve_sha_256_permutation_opcode( initial_witness: &mut WitnessMap, - inputs: &[FunctionInput; 16], - hash_values: &[FunctionInput; 8], + inputs: &[FunctionInput; 16], + hash_values: &[FunctionInput; 8], outputs: &[Witness; 8], ) -> Result<(), OpcodeResolutionError> { let message = to_u32_array(initial_witness, inputs)?; @@ -107,7 +105,7 @@ pub(crate) fn solve_sha_256_permutation_opcode( pub(crate) fn solve_poseidon2_permutation_opcode( backend: &impl BlackBoxFunctionSolver, initial_witness: &mut WitnessMap, - inputs: &[FunctionInput], + inputs: &[FunctionInput], outputs: &[Witness], len: u32, ) -> Result<(), OpcodeResolutionError> { @@ -135,8 +133,8 @@ pub(crate) fn solve_poseidon2_permutation_opcode( // Read witness assignments let mut state = Vec::new(); for input in inputs.iter() { - let witness_assignment = witness_to_value(initial_witness, input.witness)?; - state.push(*witness_assignment); + let witness_assignment = input_to_value(initial_witness, *input)?; + state.push(witness_assignment); } let state = backend.poseidon2_permutation(&state, len)?; diff --git a/acvm-repo/acvm/src/pwg/blackbox/logic.rs b/acvm-repo/acvm/src/pwg/blackbox/logic.rs index 5be888c8ac6..7ce0827d932 100644 --- a/acvm-repo/acvm/src/pwg/blackbox/logic.rs +++ b/acvm-repo/acvm/src/pwg/blackbox/logic.rs @@ -1,4 +1,4 @@ -use crate::pwg::{insert_value, witness_to_value}; +use crate::pwg::{input_to_value, insert_value}; use crate::OpcodeResolutionError; use acir::{ circuit::opcodes::FunctionInput, @@ -11,16 +11,17 @@ use acvm_blackbox_solver::{bit_and, bit_xor}; /// the result into the supplied witness map pub(super) fn and( initial_witness: &mut WitnessMap, - lhs: &FunctionInput, - rhs: &FunctionInput, + lhs: &FunctionInput, + rhs: &FunctionInput, output: &Witness, ) -> Result<(), OpcodeResolutionError> { assert_eq!( - lhs.num_bits, rhs.num_bits, + lhs.num_bits(), + rhs.num_bits(), "number of bits specified for each input must be the same" ); - solve_logic_opcode(initial_witness, &lhs.witness, &rhs.witness, *output, |left, right| { - bit_and(left, right, lhs.num_bits) + solve_logic_opcode(initial_witness, lhs, rhs, *output, |left, right| { + bit_and(left, right, lhs.num_bits()) }) } @@ -28,30 +29,31 @@ pub(super) fn and( /// the result into the supplied witness map pub(super) fn xor( initial_witness: &mut WitnessMap, - lhs: &FunctionInput, - rhs: &FunctionInput, + lhs: &FunctionInput, + rhs: &FunctionInput, output: &Witness, ) -> Result<(), OpcodeResolutionError> { assert_eq!( - lhs.num_bits, rhs.num_bits, + lhs.num_bits(), + rhs.num_bits(), "number of bits specified for each input must be the same" ); - solve_logic_opcode(initial_witness, &lhs.witness, &rhs.witness, *output, |left, right| { - bit_xor(left, right, lhs.num_bits) + solve_logic_opcode(initial_witness, lhs, rhs, *output, |left, right| { + bit_xor(left, right, lhs.num_bits()) }) } /// Derives the rest of the witness based on the initial low level variables fn solve_logic_opcode( initial_witness: &mut WitnessMap, - a: &Witness, - b: &Witness, + a: &FunctionInput, + b: &FunctionInput, result: Witness, logic_op: impl Fn(F, F) -> F, ) -> Result<(), OpcodeResolutionError> { - let w_l_value = witness_to_value(initial_witness, *a)?; - let w_r_value = witness_to_value(initial_witness, *b)?; - let assignment = logic_op(*w_l_value, *w_r_value); + let w_l_value = input_to_value(initial_witness, *a)?; + let w_r_value = input_to_value(initial_witness, *b)?; + let assignment = logic_op(w_l_value, w_r_value); insert_value(&result, assignment, initial_witness) } diff --git a/acvm-repo/acvm/src/pwg/blackbox/mod.rs b/acvm-repo/acvm/src/pwg/blackbox/mod.rs index 8bda9221d8a..def4216fe15 100644 --- a/acvm-repo/acvm/src/pwg/blackbox/mod.rs +++ b/acvm-repo/acvm/src/pwg/blackbox/mod.rs @@ -1,5 +1,5 @@ use acir::{ - circuit::opcodes::{BlackBoxFuncCall, FunctionInput}, + circuit::opcodes::{BlackBoxFuncCall, ConstantOrWitnessEnum, FunctionInput}, native_types::{Witness, WitnessMap}, AcirField, }; @@ -7,11 +7,11 @@ use acvm_blackbox_solver::{blake2s, blake3, keccak256, keccakf1600, sha256}; use self::{ aes128::solve_aes128_encryption_opcode, bigint::AcvmBigIntSolver, - hash::solve_poseidon2_permutation_opcode, pedersen::pedersen_hash, + hash::solve_poseidon2_permutation_opcode, }; use super::{insert_value, OpcodeNotSolvable, OpcodeResolutionError}; -use crate::{pwg::witness_to_value, BlackBoxFunctionSolver}; +use crate::{pwg::input_to_value, BlackBoxFunctionSolver}; mod aes128; pub(crate) mod bigint; @@ -27,7 +27,7 @@ use embedded_curve_ops::{embedded_curve_add, multi_scalar_mul}; // Hash functions should eventually be exposed for external consumers. use hash::{solve_generic_256_hash_opcode, solve_sha_256_permutation_opcode}; use logic::{and, xor}; -use pedersen::pedersen; +use pedersen::{pedersen, pedersen_hash}; pub(crate) use range::solve_range_opcode; use signature::{ ecdsa::{secp256k1_prehashed, secp256r1_prehashed}, @@ -39,26 +39,33 @@ use signature::{ /// Returns the first missing assignment if any are missing fn first_missing_assignment( witness_assignments: &WitnessMap, - inputs: &[FunctionInput], + inputs: &[FunctionInput], ) -> Option { inputs.iter().find_map(|input| { - if witness_assignments.contains_key(&input.witness) { - None + if let ConstantOrWitnessEnum::Witness(witness) = input.input { + if witness_assignments.contains_key(&witness) { + None + } else { + Some(witness) + } } else { - Some(input.witness) + None } }) } /// Check if all of the inputs to the function have assignments -fn contains_all_inputs(witness_assignments: &WitnessMap, inputs: &[FunctionInput]) -> bool { - inputs.iter().all(|input| witness_assignments.contains_key(&input.witness)) +fn contains_all_inputs( + witness_assignments: &WitnessMap, + inputs: &[FunctionInput], +) -> bool { + first_missing_assignment(witness_assignments, inputs).is_none() } pub(crate) fn solve( backend: &impl BlackBoxFunctionSolver, initial_witness: &mut WitnessMap, - bb_func: &BlackBoxFuncCall, + bb_func: &BlackBoxFuncCall, bigint_solver: &mut AcvmBigIntSolver, ) -> Result<(), OpcodeResolutionError> { let inputs = bb_func.get_inputs_vec(); @@ -99,10 +106,9 @@ pub(crate) fn solve( BlackBoxFuncCall::Keccakf1600 { inputs, outputs } => { let mut state = [0; 25]; for (it, input) in state.iter_mut().zip(inputs.as_ref()) { - let witness = input.witness; - let num_bits = input.num_bits as usize; + let num_bits = input.num_bits() as usize; assert_eq!(num_bits, 64); - let witness_assignment = witness_to_value(initial_witness, witness)?; + let witness_assignment = input_to_value(initial_witness, *input)?; let lane = witness_assignment.try_to_u64(); *it = lane.unwrap(); } diff --git a/acvm-repo/acvm/src/pwg/blackbox/pedersen.rs b/acvm-repo/acvm/src/pwg/blackbox/pedersen.rs index f64a3a79465..b1b95393b19 100644 --- a/acvm-repo/acvm/src/pwg/blackbox/pedersen.rs +++ b/acvm-repo/acvm/src/pwg/blackbox/pedersen.rs @@ -5,20 +5,20 @@ use acir::{ }; use crate::{ - pwg::{insert_value, witness_to_value, OpcodeResolutionError}, + pwg::{input_to_value, insert_value, OpcodeResolutionError}, BlackBoxFunctionSolver, }; pub(super) fn pedersen( backend: &impl BlackBoxFunctionSolver, initial_witness: &mut WitnessMap, - inputs: &[FunctionInput], + inputs: &[FunctionInput], domain_separator: u32, outputs: (Witness, Witness), ) -> Result<(), OpcodeResolutionError> { let scalars: Result, _> = - inputs.iter().map(|input| witness_to_value(initial_witness, input.witness)).collect(); - let scalars: Vec<_> = scalars?.into_iter().cloned().collect(); + inputs.iter().map(|input| input_to_value(initial_witness, *input)).collect(); + let scalars: Vec<_> = scalars?.into_iter().collect(); let (res_x, res_y) = backend.pedersen_commitment(&scalars, domain_separator)?; @@ -31,13 +31,13 @@ pub(super) fn pedersen( pub(super) fn pedersen_hash( backend: &impl BlackBoxFunctionSolver, initial_witness: &mut WitnessMap, - inputs: &[FunctionInput], + inputs: &[FunctionInput], domain_separator: u32, output: Witness, ) -> Result<(), OpcodeResolutionError> { let scalars: Result, _> = - inputs.iter().map(|input| witness_to_value(initial_witness, input.witness)).collect(); - let scalars: Vec<_> = scalars?.into_iter().cloned().collect(); + inputs.iter().map(|input| input_to_value(initial_witness, *input)).collect(); + let scalars: Vec<_> = scalars?.into_iter().collect(); let res = backend.pedersen_hash(&scalars, domain_separator)?; diff --git a/acvm-repo/acvm/src/pwg/blackbox/range.rs b/acvm-repo/acvm/src/pwg/blackbox/range.rs index 0ca001aff7a..054730bb6c0 100644 --- a/acvm-repo/acvm/src/pwg/blackbox/range.rs +++ b/acvm-repo/acvm/src/pwg/blackbox/range.rs @@ -1,15 +1,15 @@ use crate::{ - pwg::{witness_to_value, ErrorLocation}, + pwg::{input_to_value, ErrorLocation}, OpcodeResolutionError, }; use acir::{circuit::opcodes::FunctionInput, native_types::WitnessMap, AcirField}; pub(crate) fn solve_range_opcode( initial_witness: &WitnessMap, - input: &FunctionInput, + input: &FunctionInput, ) -> Result<(), OpcodeResolutionError> { - let w_value = witness_to_value(initial_witness, input.witness)?; - if w_value.num_bits() > input.num_bits { + let w_value = input_to_value(initial_witness, *input)?; + if w_value.num_bits() > input.num_bits() { return Err(OpcodeResolutionError::UnsatisfiedConstrain { opcode_location: ErrorLocation::Unresolved, payload: None, diff --git a/acvm-repo/acvm/src/pwg/blackbox/signature/ecdsa.rs b/acvm-repo/acvm/src/pwg/blackbox/signature/ecdsa.rs index 707e3f26af0..db92d27b871 100644 --- a/acvm-repo/acvm/src/pwg/blackbox/signature/ecdsa.rs +++ b/acvm-repo/acvm/src/pwg/blackbox/signature/ecdsa.rs @@ -15,10 +15,10 @@ use crate::{ pub(crate) fn secp256k1_prehashed( initial_witness: &mut WitnessMap, - public_key_x_inputs: &[FunctionInput; 32], - public_key_y_inputs: &[FunctionInput; 32], - signature_inputs: &[FunctionInput; 64], - hashed_message_inputs: &[FunctionInput], + public_key_x_inputs: &[FunctionInput; 32], + public_key_y_inputs: &[FunctionInput; 32], + signature_inputs: &[FunctionInput; 64], + hashed_message_inputs: &[FunctionInput], output: Witness, ) -> Result<(), OpcodeResolutionError> { let hashed_message = to_u8_vec(initial_witness, hashed_message_inputs)?; @@ -34,10 +34,10 @@ pub(crate) fn secp256k1_prehashed( pub(crate) fn secp256r1_prehashed( initial_witness: &mut WitnessMap, - public_key_x_inputs: &[FunctionInput; 32], - public_key_y_inputs: &[FunctionInput; 32], - signature_inputs: &[FunctionInput; 64], - hashed_message_inputs: &[FunctionInput], + public_key_x_inputs: &[FunctionInput; 32], + public_key_y_inputs: &[FunctionInput; 32], + signature_inputs: &[FunctionInput; 64], + hashed_message_inputs: &[FunctionInput], output: Witness, ) -> Result<(), OpcodeResolutionError> { let hashed_message = to_u8_vec(initial_witness, hashed_message_inputs)?; diff --git a/acvm-repo/acvm/src/pwg/blackbox/signature/schnorr.rs b/acvm-repo/acvm/src/pwg/blackbox/signature/schnorr.rs index 5e0ac94f8be..4f8e88373ba 100644 --- a/acvm-repo/acvm/src/pwg/blackbox/signature/schnorr.rs +++ b/acvm-repo/acvm/src/pwg/blackbox/signature/schnorr.rs @@ -1,7 +1,7 @@ use crate::{ pwg::{ blackbox::utils::{to_u8_array, to_u8_vec}, - insert_value, witness_to_value, OpcodeResolutionError, + input_to_value, insert_value, OpcodeResolutionError, }, BlackBoxFunctionSolver, }; @@ -15,14 +15,14 @@ use acir::{ pub(crate) fn schnorr_verify( backend: &impl BlackBoxFunctionSolver, initial_witness: &mut WitnessMap, - public_key_x: FunctionInput, - public_key_y: FunctionInput, - signature: &[FunctionInput; 64], - message: &[FunctionInput], + public_key_x: FunctionInput, + public_key_y: FunctionInput, + signature: &[FunctionInput; 64], + message: &[FunctionInput], output: Witness, ) -> Result<(), OpcodeResolutionError> { - let public_key_x: &F = witness_to_value(initial_witness, public_key_x.witness)?; - let public_key_y: &F = witness_to_value(initial_witness, public_key_y.witness)?; + let public_key_x: &F = &input_to_value(initial_witness, public_key_x)?; + let public_key_y: &F = &input_to_value(initial_witness, public_key_y)?; let signature = to_u8_array(initial_witness, signature)?; let message = to_u8_vec(initial_witness, message)?; diff --git a/acvm-repo/acvm/src/pwg/blackbox/utils.rs b/acvm-repo/acvm/src/pwg/blackbox/utils.rs index 6880d21a324..9b9157421e5 100644 --- a/acvm-repo/acvm/src/pwg/blackbox/utils.rs +++ b/acvm-repo/acvm/src/pwg/blackbox/utils.rs @@ -1,14 +1,14 @@ use acir::{circuit::opcodes::FunctionInput, native_types::WitnessMap, AcirField}; -use crate::pwg::{witness_to_value, OpcodeResolutionError}; +use crate::pwg::{input_to_value, OpcodeResolutionError}; pub(crate) fn to_u8_array( initial_witness: &WitnessMap, - inputs: &[FunctionInput; N], + inputs: &[FunctionInput; N], ) -> Result<[u8; N], OpcodeResolutionError> { let mut result = [0; N]; for (it, input) in result.iter_mut().zip(inputs) { - let witness_value_bytes = witness_to_value(initial_witness, input.witness)?.to_be_bytes(); + let witness_value_bytes = input_to_value(initial_witness, *input)?.to_be_bytes(); let byte = witness_value_bytes .last() .expect("Field element must be represented by non-zero amount of bytes"); @@ -19,11 +19,11 @@ pub(crate) fn to_u8_array( pub(crate) fn to_u8_vec( initial_witness: &WitnessMap, - inputs: &[FunctionInput], + inputs: &[FunctionInput], ) -> Result, OpcodeResolutionError> { let mut result = Vec::with_capacity(inputs.len()); for input in inputs { - let witness_value_bytes = witness_to_value(initial_witness, input.witness)?.to_be_bytes(); + let witness_value_bytes = input_to_value(initial_witness, *input)?.to_be_bytes(); let byte = witness_value_bytes .last() .expect("Field element must be represented by non-zero amount of bytes"); diff --git a/acvm-repo/acvm/src/pwg/brillig.rs b/acvm-repo/acvm/src/pwg/brillig.rs index 3a639df044a..91dedac8e35 100644 --- a/acvm-repo/acvm/src/pwg/brillig.rs +++ b/acvm-repo/acvm/src/pwg/brillig.rs @@ -162,63 +162,27 @@ impl<'b, B: BlackBoxFunctionSolver, F: AcirField> BrilligSolver<'b, F, B> { VMStatus::Finished { .. } => Ok(BrilligSolverStatus::Finished), VMStatus::InProgress => Ok(BrilligSolverStatus::InProgress), VMStatus::Failure { reason, call_stack } => { + let call_stack = call_stack + .iter() + .map(|brillig_index| OpcodeLocation::Brillig { + acir_index: self.acir_index, + brillig_index: *brillig_index, + }) + .collect(); let payload = match reason { FailureReason::RuntimeError { message } => { Some(ResolvedAssertionPayload::String(message)) } FailureReason::Trap { revert_data_offset, revert_data_size } => { - // Since noir can only revert with strings currently, we can parse return data as a string - if revert_data_size == 0 { - None - } else { - let memory = self.vm.get_memory(); - let mut revert_values_iter = memory - [revert_data_offset..(revert_data_offset + revert_data_size)] - .iter(); - let error_selector = ErrorSelector::new( - revert_values_iter - .next() - .expect("Incorrect revert data size") - .try_into() - .expect("Error selector is not u64"), - ); - - match error_selector { - STRING_ERROR_SELECTOR => { - // If the error selector is 0, it means the error is a string - let string = revert_values_iter - .map(|memory_value| { - let as_u8: u8 = memory_value - .try_into() - .expect("String item is not u8"); - as_u8 as char - }) - .collect(); - Some(ResolvedAssertionPayload::String(string)) - } - _ => { - // If the error selector is not 0, it means the error is a custom error - Some(ResolvedAssertionPayload::Raw(RawAssertionPayload { - selector: error_selector, - data: revert_values_iter - .map(|value| value.to_field()) - .collect(), - })) - } - } - } + extract_failure_payload_from_memory( + self.vm.get_memory(), + revert_data_offset, + revert_data_size, + ) } }; - Err(OpcodeResolutionError::BrilligFunctionFailed { - payload, - call_stack: call_stack - .iter() - .map(|brillig_index| OpcodeLocation::Brillig { - acir_index: self.acir_index, - brillig_index: *brillig_index, - }) - .collect(), - }) + + Err(OpcodeResolutionError::BrilligFunctionFailed { payload, call_stack }) } VMStatus::ForeignCallWait { function, inputs } => { Ok(BrilligSolverStatus::ForeignCallWait(ForeignCallWaitInfo { function, inputs })) @@ -283,6 +247,50 @@ impl<'b, B: BlackBoxFunctionSolver, F: AcirField> BrilligSolver<'b, F, B> { } } +/// Extracts a `ResolvedAssertionPayload` from a block of memory of a Brillig VM instance. +/// +/// Returns `None` if the amount of memory requested is zero. +fn extract_failure_payload_from_memory( + memory: &[MemoryValue], + revert_data_offset: usize, + revert_data_size: usize, +) -> Option> { + // Since noir can only revert with strings currently, we can parse return data as a string + if revert_data_size == 0 { + None + } else { + let mut revert_values_iter = + memory[revert_data_offset..(revert_data_offset + revert_data_size)].iter(); + let error_selector = ErrorSelector::new( + revert_values_iter + .next() + .expect("Incorrect revert data size") + .try_into() + .expect("Error selector is not u64"), + ); + + match error_selector { + STRING_ERROR_SELECTOR => { + // If the error selector is 0, it means the error is a string + let string = revert_values_iter + .map(|memory_value| { + let as_u8: u8 = memory_value.try_into().expect("String item is not u8"); + as_u8 as char + }) + .collect(); + Some(ResolvedAssertionPayload::String(string)) + } + _ => { + // If the error selector is not 0, it means the error is a custom error + Some(ResolvedAssertionPayload::Raw(RawAssertionPayload { + selector: error_selector, + data: revert_values_iter.map(|value| value.to_field()).collect(), + })) + } + } + } +} + /// Encapsulates a request from a Brillig VM process that encounters a [foreign call opcode][acir::brillig_vm::Opcode::ForeignCall] /// where the result of the foreign call has not yet been provided. /// diff --git a/acvm-repo/acvm/src/pwg/mod.rs b/acvm-repo/acvm/src/pwg/mod.rs index 4f88e17d109..4292d72fad5 100644 --- a/acvm-repo/acvm/src/pwg/mod.rs +++ b/acvm-repo/acvm/src/pwg/mod.rs @@ -5,9 +5,10 @@ use std::collections::HashMap; use acir::{ brillig::ForeignCallResult, circuit::{ - brillig::BrilligBytecode, opcodes::BlockId, AssertionPayload, ErrorSelector, - ExpressionOrMemory, Opcode, OpcodeLocation, RawAssertionPayload, ResolvedAssertionPayload, - STRING_ERROR_SELECTOR, + brillig::BrilligBytecode, + opcodes::{BlockId, ConstantOrWitnessEnum, FunctionInput}, + AssertionPayload, ErrorSelector, ExpressionOrMemory, Opcode, OpcodeLocation, + RawAssertionPayload, ResolvedAssertionPayload, STRING_ERROR_SELECTOR, }, native_types::{Expression, Witness, WitnessMap}, AcirField, BlackBoxFunc, @@ -629,6 +630,16 @@ pub fn witness_to_value( } } +pub fn input_to_value( + initial_witness: &WitnessMap, + input: FunctionInput, +) -> Result> { + match input.input { + ConstantOrWitnessEnum::Witness(witness) => Ok(*witness_to_value(initial_witness, witness)?), + ConstantOrWitnessEnum::Constant(value) => Ok(value), + } +} + // TODO: There is an issue open to decide on whether we need to get values from Expressions // TODO versus just getting values from Witness pub fn get_value( diff --git a/acvm-repo/acvm_js/test/browser/execute_circuit.test.ts b/acvm-repo/acvm_js/test/browser/execute_circuit.test.ts index cfd5523b79f..aaa82f8f1e5 100644 --- a/acvm-repo/acvm_js/test/browser/execute_circuit.test.ts +++ b/acvm-repo/acvm_js/test/browser/execute_circuit.test.ts @@ -75,16 +75,6 @@ it('successfully processes complex brillig foreign call opcodes', async () => { expect(solved_witness).to.be.deep.eq(expectedWitnessMap); }); -it('successfully executes a Pedersen opcode', async function () { - const { bytecode, initialWitnessMap, expectedWitnessMap } = await import('../shared/pedersen'); - - const solvedWitness: WitnessMap = await executeCircuit(bytecode, initialWitnessMap, () => { - throw Error('unexpected oracle'); - }); - - expect(solvedWitness).to.be.deep.eq(expectedWitnessMap); -}); - it('successfully executes a MultiScalarMul opcode', async () => { const { bytecode, initialWitnessMap, expectedWitnessMap } = await import('../shared/multi_scalar_mul'); diff --git a/acvm-repo/acvm_js/test/node/execute_circuit.test.ts b/acvm-repo/acvm_js/test/node/execute_circuit.test.ts index 1e3517e8814..120ad0fa738 100644 --- a/acvm-repo/acvm_js/test/node/execute_circuit.test.ts +++ b/acvm-repo/acvm_js/test/node/execute_circuit.test.ts @@ -76,17 +76,6 @@ it('successfully processes complex brillig foreign call opcodes', async () => { expect(solved_witness).to.be.deep.eq(expectedWitnessMap); }); -it('successfully executes a Pedersen opcode', async function () { - this.timeout(10000); - const { bytecode, initialWitnessMap, expectedWitnessMap } = await import('../shared/pedersen'); - - const solvedWitness: WitnessMap = await executeCircuit(bytecode, initialWitnessMap, () => { - throw Error('unexpected oracle'); - }); - - expect(solvedWitness).to.be.deep.eq(expectedWitnessMap); -}); - it('successfully executes a MultiScalarMul opcode', async () => { const { bytecode, initialWitnessMap, expectedWitnessMap } = await import('../shared/multi_scalar_mul'); @@ -117,25 +106,6 @@ it('successfully executes a MemoryOp opcode', async () => { expect(solvedWitness).to.be.deep.eq(expectedWitnessMap); }); -it('successfully executes 500 pedersen circuits', async function () { - this.timeout(100000); - - // Pedersen opcodes used to have a large upfront cost due to generator calculation - // so we'd need to pass around the blackbox solver in JS to avoid redoing this work. - // - // This test now shows that we don't need to do this anymore without a performance regression. - - const { bytecode, initialWitnessMap, expectedWitnessMap } = await import('../shared/pedersen'); - - for (let i = 0; i < 500; i++) { - const solvedWitness = await executeCircuit(bytecode, initialWitnessMap, () => { - throw Error('unexpected oracle'); - }); - - expect(solvedWitness).to.be.deep.eq(expectedWitnessMap); - } -}); - /** * Below are all the same tests as above but using `executeProgram` * TODO: also add a couple tests for executing multiple circuits diff --git a/acvm-repo/acvm_js/test/shared/multi_scalar_mul.ts b/acvm-repo/acvm_js/test/shared/multi_scalar_mul.ts index 5401da76974..80fbf14e8f1 100644 --- a/acvm-repo/acvm_js/test/shared/multi_scalar_mul.ts +++ b/acvm-repo/acvm_js/test/shared/multi_scalar_mul.ts @@ -1,8 +1,8 @@ // See `multi_scalar_mul_circuit` integration test in `acir/tests/test_program_serialization.rs`. export const bytecode = Uint8Array.from([ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 93, 141, 219, 10, 0, 32, 8, 67, 243, 214, 5, 250, 232, 62, 189, 69, 123, 176, 132, - 195, 116, 50, 149, 114, 107, 0, 97, 127, 116, 2, 75, 243, 2, 74, 53, 122, 202, 189, 211, 15, 106, 5, 13, 116, 238, 35, - 221, 81, 230, 61, 249, 37, 253, 250, 179, 79, 109, 218, 22, 67, 227, 173, 0, 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 93, 141, 11, 10, 0, 32, 8, 67, 43, 181, 15, 116, 232, 142, 158, 210, 130, 149, 240, + 112, 234, 212, 156, 78, 12, 39, 67, 71, 158, 142, 80, 29, 44, 228, 66, 90, 168, 119, 189, 74, 115, 131, 174, 78, 115, + 58, 124, 70, 254, 130, 59, 74, 253, 68, 255, 255, 221, 39, 54, 221, 93, 91, 132, 193, 0, 0, 0, ]); export const initialWitnessMap = new Map([ [1, '0x0000000000000000000000000000000000000000000000000000000000000001'], diff --git a/acvm-repo/acvm_js/test/shared/pedersen.ts b/acvm-repo/acvm_js/test/shared/pedersen.ts deleted file mode 100644 index 6e3ec403d65..00000000000 --- a/acvm-repo/acvm_js/test/shared/pedersen.ts +++ /dev/null @@ -1,13 +0,0 @@ -// See `pedersen_circuit` integration test in `acir/tests/test_program_serialization.rs`. -export const bytecode = Uint8Array.from([ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 93, 74, 73, 10, 0, 0, 4, 180, 29, 252, 255, 193, 66, 40, 76, 77, 179, 34, 20, 36, - 136, 237, 83, 245, 101, 107, 79, 65, 94, 253, 214, 217, 255, 239, 192, 1, 43, 124, 181, 238, 113, 0, 0, 0, -]); - -export const initialWitnessMap = new Map([[1, '0x0000000000000000000000000000000000000000000000000000000000000001']]); - -export const expectedWitnessMap = new Map([ - [1, '0x0000000000000000000000000000000000000000000000000000000000000001'], - [2, '0x083e7911d835097629f0067531fc15cafd79a89beecb39903f69572c636f4a5a'], - [3, '0x1a7f5efaad7f315c25a918f30cc8d7333fccab7ad7c90f14de81bcc528f9935d'], -]); diff --git a/acvm-repo/acvm_js/test/shared/schnorr_verify.ts b/acvm-repo/acvm_js/test/shared/schnorr_verify.ts index 05fcc47e3aa..c071c86f61f 100644 --- a/acvm-repo/acvm_js/test/shared/schnorr_verify.ts +++ b/acvm-repo/acvm_js/test/shared/schnorr_verify.ts @@ -1,17 +1,19 @@ // See `schnorr_verify_circuit` integration test in `acir/tests/test_program_serialization.rs`. export const bytecode = Uint8Array.from([ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 85, 210, 85, 78, 67, 81, 24, 133, 209, 226, 238, 238, 238, 238, 238, 165, 148, 82, - 102, 193, 252, 135, 64, 232, 78, 87, 147, 114, 147, 147, 5, 47, 132, 252, 251, 107, 41, 212, 191, 159, 218, 107, 241, - 115, 236, 226, 111, 237, 181, 178, 173, 246, 186, 107, 175, 157, 29, 236, 100, 23, 27, 175, 135, 189, 236, 99, 63, 7, - 56, 200, 33, 14, 115, 132, 163, 28, 227, 56, 39, 56, 201, 41, 78, 115, 134, 179, 156, 227, 60, 23, 184, 200, 37, 46, - 115, 133, 171, 92, 227, 58, 55, 184, 201, 45, 110, 115, 135, 187, 220, 227, 62, 15, 120, 200, 35, 30, 243, 132, 167, - 60, 227, 57, 47, 120, 201, 43, 94, 243, 134, 183, 188, 227, 61, 31, 248, 200, 39, 62, 243, 133, 175, 77, 59, 230, 123, - 243, 123, 145, 239, 44, 241, 131, 101, 126, 178, 194, 47, 86, 249, 237, 239, 86, 153, 238, 210, 92, 122, 75, 107, 233, - 44, 141, 53, 250, 234, 241, 191, 164, 167, 180, 148, 142, 210, 80, 250, 73, 59, 233, 38, 205, 164, 151, 180, 146, 78, - 210, 72, 250, 72, 27, 233, 34, 77, 164, 135, 180, 144, 14, 210, 64, 246, 95, 46, 212, 119, 207, 230, 217, 59, 91, 103, - 231, 108, 156, 125, 183, 237, 186, 107, 207, 125, 59, 30, 218, 239, 216, 110, 167, 246, 58, 183, 211, 165, 125, 174, - 237, 114, 107, 143, 123, 59, 60, 186, 255, 179, 187, 191, 186, 115, 209, 125, 75, 238, 90, 118, 207, 138, 59, 54, 110, - 214, 184, 91, 161, 233, 158, 255, 190, 63, 71, 59, 68, 130, 233, 3, 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 85, 211, 103, 78, 2, 81, 24, 70, 225, 193, 6, 216, 123, 47, 216, 123, 239, 136, + 136, 136, 136, 136, 187, 96, 255, 75, 32, 112, 194, 55, 201, 129, 100, 50, 79, 244, 7, 228, 222, 243, 102, 146, 254, + 167, 221, 123, 50, 97, 222, 217, 120, 243, 116, 226, 61, 36, 15, 247, 158, 92, 120, 68, 30, 149, 199, 228, 172, 156, + 147, 243, 242, 184, 60, 33, 79, 202, 83, 242, 180, 60, 35, 207, 202, 115, 242, 188, 188, 32, 47, 202, 75, 242, 178, + 188, 34, 175, 202, 107, 242, 186, 188, 33, 111, 202, 91, 242, 182, 188, 35, 23, 228, 93, 121, 79, 222, 151, 15, 228, + 67, 249, 72, 62, 150, 79, 228, 83, 249, 76, 62, 151, 47, 228, 75, 249, 74, 190, 150, 111, 228, 91, 249, 78, 190, 151, + 31, 228, 71, 249, 73, 126, 150, 95, 228, 87, 185, 40, 191, 201, 37, 249, 93, 46, 203, 31, 114, 69, 254, 148, 171, 97, + 58, 77, 226, 111, 95, 250, 127, 77, 254, 150, 235, 242, 143, 220, 144, 127, 229, 166, 252, 39, 183, 194, 255, 241, + 253, 45, 253, 14, 182, 201, 38, 217, 34, 27, 100, 123, 233, 230, 242, 241, 155, 217, 20, 91, 98, 67, 108, 135, 205, + 176, 21, 54, 194, 54, 216, 4, 91, 96, 3, 180, 79, 243, 180, 78, 227, 180, 77, 211, 180, 76, 195, 180, 75, 179, 133, + 164, 223, 40, 109, 210, 36, 45, 210, 32, 237, 209, 28, 173, 209, 24, 109, 209, 20, 45, 209, 16, 237, 208, 12, 173, + 208, 8, 109, 208, 4, 45, 208, 0, 119, 207, 157, 115, 215, 220, 113, 49, 238, 180, 20, 119, 88, 142, 59, 171, 196, 29, + 85, 227, 46, 106, 113, 246, 245, 56, 235, 70, 156, 109, 51, 206, 50, 61, 179, 244, 220, 18, 157, 231, 192, 167, 11, + 75, 28, 99, 152, 25, 5, 0, 0, ]); export const initialWitnessMap = new Map([ diff --git a/acvm-repo/blackbox_solver/src/curve_specific_solver.rs b/acvm-repo/blackbox_solver/src/curve_specific_solver.rs index 0ee3a252840..f729a5033fb 100644 --- a/acvm-repo/blackbox_solver/src/curve_specific_solver.rs +++ b/acvm-repo/blackbox_solver/src/curve_specific_solver.rs @@ -81,6 +81,7 @@ impl BlackBoxFunctionSolver for StubbedBlackBoxSolver { ) -> Result { Err(Self::fail(BlackBoxFunc::PedersenHash)) } + fn multi_scalar_mul( &self, _points: &[F], diff --git a/acvm-repo/bn254_blackbox_solver/benches/criterion.rs b/acvm-repo/bn254_blackbox_solver/benches/criterion.rs index cbcb75a3291..e7917fa1adc 100644 --- a/acvm-repo/bn254_blackbox_solver/benches/criterion.rs +++ b/acvm-repo/bn254_blackbox_solver/benches/criterion.rs @@ -13,22 +13,6 @@ fn bench_poseidon2(c: &mut Criterion) { c.bench_function("poseidon2", |b| b.iter(|| poseidon2_permutation(black_box(&inputs), 4))); } -fn bench_pedersen_commitment(c: &mut Criterion) { - let inputs = [FieldElement::one(); 2]; - - c.bench_function("pedersen_commitment", |b| { - b.iter(|| Bn254BlackBoxSolver.pedersen_commitment(black_box(&inputs), 0)) - }); -} - -fn bench_pedersen_hash(c: &mut Criterion) { - let inputs = [FieldElement::one(); 2]; - - c.bench_function("pedersen_hash", |b| { - b.iter(|| Bn254BlackBoxSolver.pedersen_hash(black_box(&inputs), 0)) - }); -} - fn bench_schnorr_verify(c: &mut Criterion) { let pub_key_x = FieldElement::from_hex( "0x04b260954662e97f00cab9adb773a259097f7a274b83b113532bce27fa3fb96a", @@ -62,7 +46,7 @@ fn bench_schnorr_verify(c: &mut Criterion) { criterion_group!( name = benches; config = Criterion::default().sample_size(40).measurement_time(Duration::from_secs(20)).with_profiler(PProfProfiler::new(100, Output::Flamegraph(None))); - targets = bench_poseidon2, bench_pedersen_commitment, bench_pedersen_hash, bench_schnorr_verify + targets = bench_poseidon2, bench_schnorr_verify ); criterion_main!(benches); diff --git a/acvm-repo/bn254_blackbox_solver/src/generator/generators.rs b/acvm-repo/bn254_blackbox_solver/src/generator/generators.rs index f89d582d167..bb51426b33b 100644 --- a/acvm-repo/bn254_blackbox_solver/src/generator/generators.rs +++ b/acvm-repo/bn254_blackbox_solver/src/generator/generators.rs @@ -38,7 +38,7 @@ fn default_generators() -> &'static [Affine; NUM_DEFAULT_GEN /// index-addressable generators. /// /// [hash_to_curve]: super::hash_to_curve::hash_to_curve -pub(crate) fn derive_generators( +pub fn derive_generators( domain_separator_bytes: &[u8], num_generators: u32, starting_index: u32, diff --git a/acvm-repo/bn254_blackbox_solver/src/lib.rs b/acvm-repo/bn254_blackbox_solver/src/lib.rs index 08e0fb66a6d..6897116e90e 100644 --- a/acvm-repo/bn254_blackbox_solver/src/lib.rs +++ b/acvm-repo/bn254_blackbox_solver/src/lib.rs @@ -12,6 +12,7 @@ mod schnorr; use ark_ec::AffineRepr; pub use embedded_curve_ops::{embedded_curve_add, multi_scalar_mul}; +pub use generator::generators::derive_generators; pub use poseidon2::poseidon2_permutation; // Temporary hack, this ensure that we always use a bn254 field here @@ -47,11 +48,13 @@ impl BlackBoxFunctionSolver for Bn254BlackBoxSolver { ) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError> { let inputs: Vec = inputs.iter().map(|input| input.into_repr()).collect(); let result = pedersen::commitment::commit_native_with_index(&inputs, domain_separator); - let res_x = - FieldElement::from_repr(*result.x().expect("should not commit to point at infinity")); - let res_y = - FieldElement::from_repr(*result.y().expect("should not commit to point at infinity")); - Ok((res_x, res_y)) + let result = if let Some((x, y)) = result.xy() { + (FieldElement::from_repr(*x), FieldElement::from_repr(*y)) + } else { + (FieldElement::from(0_u128), FieldElement::from(0_u128)) + }; + + Ok(result) } fn pedersen_hash( diff --git a/acvm-repo/brillig/src/black_box.rs b/acvm-repo/brillig/src/black_box.rs index 3887092a8c2..60e4af11ea2 100644 --- a/acvm-repo/brillig/src/black_box.rs +++ b/acvm-repo/brillig/src/black_box.rs @@ -61,13 +61,13 @@ pub enum BlackBoxOp { signature: HeapVector, result: MemoryAddress, }, - /// Calculates a Pedersen commitment to the inputs. + /// Will be deprecated PedersenCommitment { inputs: HeapVector, domain_separator: MemoryAddress, output: HeapArray, }, - /// Calculates a Pedersen hash to the inputs. + /// Will be deprecated PedersenHash { inputs: HeapVector, domain_separator: MemoryAddress, diff --git a/acvm-repo/brillig_vm/src/black_box.rs b/acvm-repo/brillig_vm/src/black_box.rs index 2053f4e7c86..53599f79bc7 100644 --- a/acvm-repo/brillig_vm/src/black_box.rs +++ b/acvm-repo/brillig_vm/src/black_box.rs @@ -42,7 +42,7 @@ pub(crate) fn evaluate_black_box op: &BlackBoxOp, solver: &Solver, memory: &mut Memory, - bigint_solver: &mut BigIntSolver, + bigint_solver: &mut BrilligBigintSolver, ) -> Result<(), BlackBoxResolutionError> { match op { BlackBoxOp::AES128Encrypt { inputs, iv, key, outputs } => { @@ -241,7 +241,7 @@ pub(crate) fn evaluate_black_box memory.read(*domain_separator).try_into().map_err(|_| { BlackBoxResolutionError::Failed( BlackBoxFunc::PedersenCommitment, - "Invalid signature length".to_string(), + "Invalid separator length".to_string(), ) })?; let (x, y) = solver.pedersen_commitment(&inputs, domain_separator)?; @@ -260,7 +260,7 @@ pub(crate) fn evaluate_black_box memory.read(*domain_separator).try_into().map_err(|_| { BlackBoxResolutionError::Failed( BlackBoxFunc::PedersenCommitment, - "Invalid signature length".to_string(), + "Invalid separator length".to_string(), ) })?; let hash = solver.pedersen_hash(&inputs, domain_separator)?; @@ -270,29 +270,33 @@ pub(crate) fn evaluate_black_box BlackBoxOp::BigIntAdd { lhs, rhs, output } => { let lhs = memory.read(*lhs).try_into().unwrap(); let rhs = memory.read(*rhs).try_into().unwrap(); - let output = memory.read(*output).try_into().unwrap(); - bigint_solver.bigint_op(lhs, rhs, output, BlackBoxFunc::BigIntAdd)?; + + let new_id = bigint_solver.bigint_op(lhs, rhs, BlackBoxFunc::BigIntAdd)?; + memory.write(*output, new_id.into()); Ok(()) } BlackBoxOp::BigIntSub { lhs, rhs, output } => { let lhs = memory.read(*lhs).try_into().unwrap(); let rhs = memory.read(*rhs).try_into().unwrap(); - let output = memory.read(*output).try_into().unwrap(); - bigint_solver.bigint_op(lhs, rhs, output, BlackBoxFunc::BigIntSub)?; + + let new_id = bigint_solver.bigint_op(lhs, rhs, BlackBoxFunc::BigIntSub)?; + memory.write(*output, new_id.into()); Ok(()) } BlackBoxOp::BigIntMul { lhs, rhs, output } => { let lhs = memory.read(*lhs).try_into().unwrap(); let rhs = memory.read(*rhs).try_into().unwrap(); - let output = memory.read(*output).try_into().unwrap(); - bigint_solver.bigint_op(lhs, rhs, output, BlackBoxFunc::BigIntMul)?; + + let new_id = bigint_solver.bigint_op(lhs, rhs, BlackBoxFunc::BigIntMul)?; + memory.write(*output, new_id.into()); Ok(()) } BlackBoxOp::BigIntDiv { lhs, rhs, output } => { let lhs = memory.read(*lhs).try_into().unwrap(); let rhs = memory.read(*rhs).try_into().unwrap(); - let output = memory.read(*output).try_into().unwrap(); - bigint_solver.bigint_op(lhs, rhs, output, BlackBoxFunc::BigIntDiv)?; + + let new_id = bigint_solver.bigint_op(lhs, rhs, BlackBoxFunc::BigIntDiv)?; + memory.write(*output, new_id.into()); Ok(()) } BlackBoxOp::BigIntFromLeBytes { inputs, modulus, output } => { @@ -300,8 +304,10 @@ pub(crate) fn evaluate_black_box let input: Vec = input.iter().map(|x| x.try_into().unwrap()).collect(); let modulus = read_heap_vector(memory, modulus); let modulus: Vec = modulus.iter().map(|x| x.try_into().unwrap()).collect(); - let output = memory.read(*output).try_into().unwrap(); - bigint_solver.bigint_from_bytes(&input, &modulus, output)?; + + let new_id = bigint_solver.bigint_from_bytes(&input, &modulus)?; + memory.write(*output, new_id.into()); + Ok(()) } BlackBoxOp::BigIntToLeBytes { input, output } => { @@ -381,6 +387,46 @@ pub(crate) fn evaluate_black_box } } +/// Wrapper over the generic bigint solver to automatically assign bigint ids in brillig +#[derive(Default, Debug, Clone, PartialEq, Eq)] +pub(crate) struct BrilligBigintSolver { + bigint_solver: BigIntSolver, + last_id: u32, +} + +impl BrilligBigintSolver { + pub(crate) fn create_bigint_id(&mut self) -> u32 { + let output = self.last_id; + self.last_id += 1; + output + } + + pub(crate) fn bigint_from_bytes( + &mut self, + inputs: &[u8], + modulus: &[u8], + ) -> Result { + let id = self.create_bigint_id(); + self.bigint_solver.bigint_from_bytes(inputs, modulus, id)?; + Ok(id) + } + + pub(crate) fn bigint_to_bytes(&self, input: u32) -> Result, BlackBoxResolutionError> { + self.bigint_solver.bigint_to_bytes(input) + } + + pub(crate) fn bigint_op( + &mut self, + lhs: u32, + rhs: u32, + func: BlackBoxFunc, + ) -> Result { + let id = self.create_bigint_id(); + self.bigint_solver.bigint_op(lhs, rhs, id, func)?; + Ok(id) + } +} + fn black_box_function_from_op(op: &BlackBoxOp) -> BlackBoxFunc { match op { BlackBoxOp::AES128Encrypt { .. } => BlackBoxFunc::AES128Encrypt, @@ -392,8 +438,6 @@ fn black_box_function_from_op(op: &BlackBoxOp) -> BlackBoxFunc { BlackBoxOp::EcdsaSecp256k1 { .. } => BlackBoxFunc::EcdsaSecp256k1, BlackBoxOp::EcdsaSecp256r1 { .. } => BlackBoxFunc::EcdsaSecp256r1, BlackBoxOp::SchnorrVerify { .. } => BlackBoxFunc::SchnorrVerify, - BlackBoxOp::PedersenCommitment { .. } => BlackBoxFunc::PedersenCommitment, - BlackBoxOp::PedersenHash { .. } => BlackBoxFunc::PedersenHash, BlackBoxOp::MultiScalarMul { .. } => BlackBoxFunc::MultiScalarMul, BlackBoxOp::EmbeddedCurveAdd { .. } => BlackBoxFunc::EmbeddedCurveAdd, BlackBoxOp::BigIntAdd { .. } => BlackBoxFunc::BigIntAdd, @@ -405,6 +449,8 @@ fn black_box_function_from_op(op: &BlackBoxOp) -> BlackBoxFunc { BlackBoxOp::Poseidon2Permutation { .. } => BlackBoxFunc::Poseidon2Permutation, BlackBoxOp::Sha256Compression { .. } => BlackBoxFunc::Sha256Compression, BlackBoxOp::ToRadix { .. } => unreachable!("ToRadix is not an ACIR BlackBoxFunc"), + BlackBoxOp::PedersenCommitment { .. } => BlackBoxFunc::PedersenCommitment, + BlackBoxOp::PedersenHash { .. } => BlackBoxFunc::PedersenHash, } } @@ -414,10 +460,10 @@ mod test { brillig::{BlackBoxOp, MemoryAddress}, FieldElement, }; - use acvm_blackbox_solver::{BigIntSolver, StubbedBlackBoxSolver}; + use acvm_blackbox_solver::StubbedBlackBoxSolver; use crate::{ - black_box::{evaluate_black_box, to_u8_vec, to_value_vec}, + black_box::{evaluate_black_box, to_u8_vec, to_value_vec, BrilligBigintSolver}, HeapArray, HeapVector, Memory, }; @@ -439,8 +485,13 @@ mod test { output: HeapArray { pointer: 2.into(), size: 32 }, }; - evaluate_black_box(&op, &StubbedBlackBoxSolver, &mut memory, &mut BigIntSolver::default()) - .unwrap(); + evaluate_black_box( + &op, + &StubbedBlackBoxSolver, + &mut memory, + &mut BrilligBigintSolver::default(), + ) + .unwrap(); let result = memory.read_slice(MemoryAddress(result_pointer), 32); diff --git a/acvm-repo/brillig_vm/src/lib.rs b/acvm-repo/brillig_vm/src/lib.rs index 862162ddccf..4d2dd2b8333 100644 --- a/acvm-repo/brillig_vm/src/lib.rs +++ b/acvm-repo/brillig_vm/src/lib.rs @@ -16,9 +16,9 @@ use acir::brillig::{ HeapVector, MemoryAddress, Opcode, ValueOrArray, }; use acir::AcirField; -use acvm_blackbox_solver::{BigIntSolver, BlackBoxFunctionSolver}; +use acvm_blackbox_solver::BlackBoxFunctionSolver; use arithmetic::{evaluate_binary_field_op, evaluate_binary_int_op, BrilligArithmeticError}; -use black_box::evaluate_black_box; +use black_box::{evaluate_black_box, BrilligBigintSolver}; use num_bigint::BigUint; // Re-export `brillig`. @@ -88,7 +88,7 @@ pub struct VM<'a, F, B: BlackBoxFunctionSolver> { /// The solver for blackbox functions black_box_solver: &'a B, // The solver for big integers - bigint_solver: BigIntSolver, + bigint_solver: BrilligBigintSolver, } impl<'a, F: AcirField, B: BlackBoxFunctionSolver> VM<'a, F, B> { @@ -500,9 +500,14 @@ impl<'a, F: AcirField, B: BlackBoxFunctionSolver> VM<'a, F, B> { match output { ForeignCallParam::Array(values) => { if values.len() != *size { - return Err("Foreign call result array doesn't match expected size".to_string()); + // foreign call returning flattened values into a nested type, so the sizes do not match + let destination = self.memory.read_ref(*pointer_index); + let return_type = value_type; + let mut flatten_values_idx = 0; //index of values read from flatten_values + self.write_slice_of_values_to_memory(destination, &output.fields(), &mut flatten_values_idx, return_type)?; + } else { + self.write_values_to_memory_slice(*pointer_index, values, value_types)?; } - self.write_values_to_memory_slice(*pointer_index, values, value_types)?; } _ => { return Err("Function result size does not match brillig bytecode size".to_string()); diff --git a/aztec_macros/Cargo.toml b/aztec_macros/Cargo.toml index ed70066af22..a99a654aeed 100644 --- a/aztec_macros/Cargo.toml +++ b/aztec_macros/Cargo.toml @@ -16,4 +16,4 @@ noirc_errors.workspace = true iter-extended.workspace = true convert_case = "0.6.0" regex = "1.10" - +tiny-keccak = { version = "2.0.0", features = ["keccak"] } diff --git a/aztec_macros/src/lib.rs b/aztec_macros/src/lib.rs index a36b7b17d09..580a132aa5a 100644 --- a/aztec_macros/src/lib.rs +++ b/aztec_macros/src/lib.rs @@ -7,7 +7,7 @@ use transforms::{ contract_interface::{ generate_contract_interface, stub_function, update_fn_signatures_in_contract_interface, }, - events::{generate_selector_impl, transform_events}, + events::{generate_event_impls, transform_event_abi}, functions::{ check_for_public_args, export_fn_abi, transform_function, transform_unconstrained, }, @@ -65,19 +65,14 @@ fn transform( // Usage -> mut ast -> aztec_library::transform(&mut ast) // Covers all functions in the ast for submodule in ast.submodules.iter_mut().filter(|submodule| submodule.is_contract) { - if transform_module( - crate_id, - &file_id, - context, - &mut submodule.contents, - submodule.name.0.contents.as_str(), - ) - .map_err(|err| (err.into(), file_id))? + if transform_module(&file_id, &mut submodule.contents, submodule.name.0.contents.as_str()) + .map_err(|err| (err.into(), file_id))? { check_for_aztec_dependency(crate_id, context)?; } } + generate_event_impls(&mut ast).map_err(|err| (err.into(), file_id))?; generate_note_interface_impl(&mut ast).map_err(|err| (err.into(), file_id))?; Ok(ast) @@ -87,9 +82,7 @@ fn transform( /// For annotated functions it calls the `transform` function which will perform the required transformations. /// Returns true if an annotated node is found, false otherwise fn transform_module( - crate_id: &CrateId, file_id: &FileId, - context: &HirContext, module: &mut SortedModule, module_name: &str, ) -> Result { @@ -106,19 +99,7 @@ fn transform_module( if !check_for_storage_implementation(module, storage_struct_name) { generate_storage_implementation(module, storage_struct_name)?; } - // Make sure we're only generating the storage layout for the root crate - // In case we got a contract importing other contracts for their interface, we - // don't want to generate the storage layout for them - if crate_id == context.root_crate_id() { - generate_storage_layout(module, storage_struct_name.clone())?; - } - } - - for structure in module.types.iter_mut() { - if structure.attributes.iter().any(|attr| is_custom_attribute(attr, "aztec(event)")) { - module.impls.push(generate_selector_impl(structure)); - has_transformed_module = true; - } + generate_storage_layout(module, storage_struct_name.clone(), module_name)?; } let has_initializer = module.functions.iter().any(|func| { @@ -219,7 +200,7 @@ fn transform_module( }); } - generate_contract_interface(module, module_name, &stubs)?; + generate_contract_interface(module, module_name, &stubs, storage_defined)?; } Ok(has_transformed_module) @@ -235,7 +216,7 @@ fn transform_hir( context: &mut HirContext, ) -> Result<(), (AztecMacroError, FileId)> { if has_aztec_dependency(crate_id, context) { - transform_events(crate_id, context)?; + transform_event_abi(crate_id, context)?; inject_compute_note_hash_and_optionally_a_nullifier(crate_id, context)?; assign_storage_slots(crate_id, context)?; inject_note_exports(crate_id, context)?; diff --git a/aztec_macros/src/transforms/contract_interface.rs b/aztec_macros/src/transforms/contract_interface.rs index e6ac43ad2c4..e79cee66407 100644 --- a/aztec_macros/src/transforms/contract_interface.rs +++ b/aztec_macros/src/transforms/contract_interface.rs @@ -35,7 +35,15 @@ use crate::utils::{ // PublicCallInterface { // target_contract: self.target_contract, // selector: FunctionSelector::from_signature("SELECTOR_PLACEHOLDER"), -// args_hash +// args_hash, +// name: "a_function", +// args_hash, +// args: args_acc, +// original: | inputs: dep::aztec::context::inputs::PublicContextInputs | -> Field { +// a_function(inputs, first_arg, second_arg, third_arg) +// }, +// is_static: false, +// gas_opts: dep::aztec::context::gas::GasOpts::default() // } // } // @@ -57,18 +65,14 @@ pub fn stub_function(aztec_visibility: &str, func: &NoirFunction, is_static_call let fn_return_type: noirc_frontend::ast::UnresolvedType = func.return_type(); let fn_selector = format!( - "aztec::protocol_types::abis::function_selector::FunctionSelector::from_signature(\"{}\")", + "dep::aztec::protocol_types::abis::function_selector::FunctionSelector::from_signature(\"{}\")", SELECTOR_PLACEHOLDER ); let parameters = func.parameters(); let is_void = if matches!(fn_return_type.typ, UnresolvedTypeData::Unit) { "Void" } else { "" }; let is_static = if is_static_call { "Static" } else { "" }; - let return_type_hint = if is_void == "Void" { - "".to_string() - } else { - format!("<{}>", fn_return_type.typ.to_string().replace("plain::", "")) - }; + let return_type_hint = fn_return_type.typ.to_string().replace("plain::", ""); let call_args = parameters .iter() .map(|arg| { @@ -76,73 +80,122 @@ pub fn stub_function(aztec_visibility: &str, func: &NoirFunction, is_static_call match &arg.typ.typ { UnresolvedTypeData::Array(_, typ) => { format!( - "let hash_{0} = {0}.map(|x: {1}| x.serialize()); - for i in 0..{0}.len() {{ - args_acc = args_acc.append(hash_{0}[i].as_slice()); - }}\n", + "let serialized_{0} = {0}.map(|x: {1}| x.serialize()); + for i in 0..{0}.len() {{ + args_acc = args_acc.append(serialized_{0}[i].as_slice()); + }}\n", param_name, typ.typ.to_string().replace("plain::", "") ) } - _ => { + UnresolvedTypeData::Named(_, _, _) | UnresolvedTypeData::String(_) => { format!("args_acc = args_acc.append({}.serialize().as_slice());\n", param_name) } + _ => { + format!("args_acc = args_acc.append(&[{}.to_field()]);\n", param_name) + } } }) .collect::>() .join(""); - if aztec_visibility != "Public" { - let args_hash = if !parameters.is_empty() { - format!( - "let mut args_acc: [Field] = &[]; - {} - let args_hash = aztec::hash::hash_args(args_acc); - assert(args_hash == aztec::oracle::arguments::pack_arguments(args_acc));", - call_args - ) + + let param_types = if !parameters.is_empty() { + parameters + .iter() + .map(|param| param.pattern.name_ident().0.contents.clone()) + .collect::>() + .join(", ") + } else { + "".to_string() + }; + + let original = format!( + "| inputs: dep::aztec::context::inputs::{}ContextInputs | -> {} {{ + {}(inputs{}) + }}", + aztec_visibility, + if aztec_visibility == "Private" { + "dep::aztec::protocol_types::abis::private_circuit_public_inputs::PrivateCircuitPublicInputs".to_string() } else { - "let args_hash = 0;".to_string() - }; - - let fn_body = format!( - "{} - aztec::context::{}{}{}CallInterface {{ - target_contract: self.target_contract, - selector: {}, - args_hash, - }}", - args_hash, aztec_visibility, is_static, is_void, fn_selector, - ); - format!( - "pub fn {}(self, {}) -> aztec::context::{}{}{}CallInterface{} {{ - {} - }}", - fn_name, fn_parameters, aztec_visibility, is_static, is_void, return_type_hint, fn_body - ) + return_type_hint.clone() + }, + fn_name, + if param_types.is_empty() { "".to_string() } else { format!(" ,{} ", param_types) } + ); + let arg_types = format!( + "({}{})", + parameters + .iter() + .map(|param| param.typ.typ.to_string().replace("plain::", "")) + .collect::>() + .join(","), + // In order to distinguish between a single element Tuple (Type,) and a single type with unnecessary parenthesis around it (Type), + // The latter gets simplified to Type, that is NOT a valid env + if parameters.len() == 1 { "," } else { "" } + ); + + let generics = if is_void == "Void" { + format!("{}>", arg_types) + } else { + format!("{}, {}>", return_type_hint, arg_types) + }; + + let args = format!( + "let mut args_acc: [Field] = &[]; + {} + {}", + call_args, + if aztec_visibility == "Private" { + "let args_hash = aztec::hash::hash_args(args_acc);" + } else { + "" + } + ); + + let gas_opts = if aztec_visibility == "Public" { + "gas_opts: dep::aztec::context::gas::GasOpts::default()" } else { - let args = format!( - "let mut args_acc: [Field] = &[]; - {} - ", - call_args - ); - let fn_body = format!( - "{} - aztec::context::Public{}{}CallInterface {{ + "" + }; + + let fn_body = format!( + "{} + let selector = {}; + dep::aztec::context::{}{}{}CallInterface {{ target_contract: self.target_contract, - selector: {}, + selector, + name: \"{}\", + {} args: args_acc, - gas_opts: aztec::context::gas::GasOpts::default(), - }}", - args, is_static, is_void, fn_selector, - ); - format!( - "pub fn {}(self, {}) -> aztec::context::Public{}{}CallInterface{} {{ - {} + original: {}, + is_static: {}, + {} }}", - fn_name, fn_parameters, is_static, is_void, return_type_hint, fn_body - ) - } + args, + fn_selector, + aztec_visibility, + is_static, + is_void, + fn_name, + if aztec_visibility == "Private" { "args_hash," } else { "" }, + original, + is_static_call, + gas_opts + ); + + format!( + "pub fn {}(self, {}) -> dep::aztec::context::{}{}{}CallInterface<{},{} {{ + {} + }}", + fn_name, + fn_parameters, + aztec_visibility, + is_static, + is_void, + fn_name.len(), + generics, + fn_body + ) } // Generates the contract interface as a struct with an `at` function that holds the stubbed functions and provides @@ -152,7 +205,15 @@ pub fn generate_contract_interface( module: &mut SortedModule, module_name: &str, stubs: &[(String, Location)], + has_storage_layout: bool, ) -> Result<(), AztecMacroError> { + let storage_layout_getter = format!( + "#[contract_library_method] + pub fn storage() -> StorageLayout {{ + {}_STORAGE_LAYOUT + }}", + module_name, + ); let contract_interface = format!( " struct {0} {{ @@ -167,6 +228,12 @@ pub fn generate_contract_interface( ) -> Self {{ Self {{ target_contract }} }} + + pub fn interface() -> Self {{ + Self {{ target_contract: dep::aztec::protocol_types::address::AztecAddress::zero() }} + }} + + {2} }} #[contract_library_method] @@ -175,9 +242,18 @@ pub fn generate_contract_interface( ) -> {0} {{ {0} {{ target_contract }} }} + + #[contract_library_method] + pub fn interface() -> {0} {{ + {0} {{ target_contract: dep::aztec::protocol_types::address::AztecAddress::zero() }} + }} + + {3} ", module_name, stubs.iter().map(|(src, _)| src.to_owned()).collect::>().join("\n"), + if has_storage_layout { storage_layout_getter.clone() } else { "".to_string() }, + if has_storage_layout { format!("#[contract_library_method]\n{}", storage_layout_getter) } else { "".to_string() } ); let (contract_interface_ast, errors) = parse_program(&contract_interface); @@ -194,7 +270,7 @@ pub fn generate_contract_interface( .iter() .enumerate() .map(|(i, (method, orig_span))| { - if method.name() == "at" { + if method.name() == "at" || method.name() == "interface" || method.name() == "storage" { (method.clone(), *orig_span) } else { let (_, new_location) = stubs[i]; @@ -208,7 +284,9 @@ pub fn generate_contract_interface( module.types.push(contract_interface_ast.types.pop().unwrap()); module.impls.push(impl_with_locations); - module.functions.push(contract_interface_ast.functions.pop().unwrap()); + for function in contract_interface_ast.functions { + module.functions.push(function); + } Ok(()) } @@ -247,7 +325,7 @@ pub fn update_fn_signatures_in_contract_interface( let name = context.def_interner.function_name(func_id); let fn_parameters = &context.def_interner.function_meta(func_id).parameters.clone(); - if name == "at" { + if name == "at" || name == "interface" || name == "storage" { continue; } @@ -260,42 +338,29 @@ pub fn update_fn_signatures_in_contract_interface( .collect::>(), ); let hir_func = context.def_interner.function(func_id).block(&context.def_interner); - let call_interface_constructor_statement = context.def_interner.statement( - hir_func - .statements() - .last() - .ok_or((AztecMacroError::AztecDepNotFound, file_id))?, + + let function_selector_statement = context.def_interner.statement( + hir_func.statements().get(hir_func.statements().len() - 2).ok_or(( + AztecMacroError::CouldNotGenerateContractInterface { + secondary_message: Some( + "Function signature statement not found, invalid body length" + .to_string(), + ), + }, + file_id, + ))?, ); - let call_interface_constructor_expression = - match call_interface_constructor_statement { - HirStatement::Expression(expression_id) => { - match context.def_interner.expression(&expression_id) { - HirExpression::Constructor(hir_constructor_expression) => { - Ok(hir_constructor_expression) - } - _ => Err(( - AztecMacroError::CouldNotGenerateContractInterface { - secondary_message: Some( - "CallInterface constructor statement must be a constructor expression" - .to_string(), - ), - }, - file_id, - )), - } - } - _ => Err(( - AztecMacroError::CouldNotGenerateContractInterface { - secondary_message: Some( - "CallInterface constructor statement must be an expression" - .to_string(), - ), - }, - file_id, - )), - }?; - let (_, function_selector_expression_id) = - call_interface_constructor_expression.fields[1]; + let function_selector_expression_id = match function_selector_statement { + HirStatement::Let(let_statement) => Ok(let_statement.expression), + _ => Err(( + AztecMacroError::CouldNotGenerateContractInterface { + secondary_message: Some( + "Function selector statement must be an expression".to_string(), + ), + }, + file_id, + )), + }?; let function_selector_expression = context.def_interner.expression(&function_selector_expression_id); diff --git a/aztec_macros/src/transforms/events.rs b/aztec_macros/src/transforms/events.rs index 69cb6ddafc3..ecfca40189d 100644 --- a/aztec_macros/src/transforms/events.rs +++ b/aztec_macros/src/transforms/events.rs @@ -1,178 +1,353 @@ -use iter_extended::vecmap; -use noirc_errors::Span; -use noirc_frontend::ast::{ - ExpressionKind, FunctionDefinition, FunctionReturnType, ItemVisibility, Literal, NoirFunction, - Visibility, -}; +use noirc_frontend::ast::{ItemVisibility, NoirFunction, NoirTraitImpl, TraitImplItem}; +use noirc_frontend::macros_api::{NodeInterner, StructId}; +use noirc_frontend::token::SecondaryAttribute; use noirc_frontend::{ graph::CrateId, - macros_api::{ - BlockExpression, FileId, HirContext, HirExpression, HirLiteral, HirStatement, NodeInterner, - NoirStruct, PathKind, StatementKind, StructId, StructType, Type, TypeImpl, - UnresolvedTypeData, - }, - token::SecondaryAttribute, + macros_api::{FileId, HirContext}, + parse_program, + parser::SortedModule, }; -use crate::{ - chained_dep, - utils::{ - ast_utils::{ - call, expression, ident, ident_path, is_custom_attribute, make_statement, make_type, - path, variable_path, - }, - constants::SIGNATURE_PLACEHOLDER, - errors::AztecMacroError, - hir_utils::{collect_crate_structs, signature_of_type}, - }, -}; +use crate::utils::hir_utils::collect_crate_structs; +use crate::utils::{ast_utils::is_custom_attribute, errors::AztecMacroError}; + +// Automatic implementation of most of the methods in the EventInterface trait, guiding the user with meaningful error messages in case some +// methods must be implemented manually. +pub fn generate_event_impls(module: &mut SortedModule) -> Result<(), AztecMacroError> { + // Find structs annotated with #[aztec(event)] + // Why doesn't this work ? Events are not tagged and do not appear, it seems only going through the submodule works + // let annotated_event_structs = module + // .types + // .iter_mut() + // .filter(|typ| typ.attributes.iter().any(|attr: &SecondaryAttribute| is_custom_attribute(attr, "aztec(event)"))); + // This did not work because I needed the submodule itself to add the trait impl back in to, but it would be nice if it was tagged on the module level + // let mut annotated_event_structs = module.submodules.iter_mut() + // .flat_map(|submodule| submodule.contents.types.iter_mut()) + // .filter(|typ| typ.attributes.iter().any(|attr| is_custom_attribute(attr, "aztec(event)"))); + + // To diagnose + // let test = module.types.iter_mut(); + // for event_struct in test { + // print!("\ngenerate_event_interface_impl COUNT: {}\n", event_struct.name.0.contents); + // } + + for submodule in module.submodules.iter_mut() { + let annotated_event_structs = submodule.contents.types.iter_mut().filter(|typ| { + typ.attributes.iter().any(|attr| is_custom_attribute(attr, "aztec(event)")) + }); + + for event_struct in annotated_event_structs { + // event_struct.attributes.push(SecondaryAttribute::Abi("events".to_string())); + // If one impl is pushed, this doesn't throw the "#[abi(tag)] attributes can only be used in contracts" error + // But if more than one impl is pushed, we get an increasing amount of "#[abi(tag)] attributes can only be used in contracts" errors + // We work around this by doing this addition in the HIR pass via transform_event_abi below. + + let event_type = event_struct.name.0.contents.to_string(); + let event_len = event_struct.fields.len() as u32; + // event_byte_len = event fields * 32 + randomness (32) + event_type_id (32) + let event_byte_len = event_len * 32 + 64; + + let mut event_fields = vec![]; + + for (field_ident, field_type) in event_struct.fields.iter() { + event_fields.push(( + field_ident.0.contents.to_string(), + field_type.typ.to_string().replace("plain::", ""), + )); + } -/// Generates the impl for an event selector -/// -/// Inserts the following code: -/// ```noir -/// impl SomeStruct { -/// fn selector() -> FunctionSelector { -/// aztec::protocol_types::abis::function_selector::FunctionSelector::from_signature("SIGNATURE_PLACEHOLDER") -/// } -/// } -/// ``` -/// -/// This allows developers to emit events without having to write the signature of the event every time they emit it. -/// The signature cannot be known at this point since types are not resolved yet, so we use a signature placeholder. -/// It'll get resolved after by transforming the HIR. -pub fn generate_selector_impl(structure: &mut NoirStruct) -> TypeImpl { - structure.attributes.push(SecondaryAttribute::Abi("events".to_string())); - let struct_type = - make_type(UnresolvedTypeData::Named(path(structure.name.clone()), vec![], true)); - - let selector_path = - chained_dep!("aztec", "protocol_types", "abis", "function_selector", "FunctionSelector"); - let mut from_signature_path = selector_path.clone(); - from_signature_path.segments.push(ident("from_signature")); - - let selector_fun_body = BlockExpression { - statements: vec![make_statement(StatementKind::Expression(call( - variable_path(from_signature_path), - vec![expression(ExpressionKind::Literal(Literal::Str( - SIGNATURE_PLACEHOLDER.to_string(), - )))], - )))], - }; - - // Define `FunctionSelector` return type - let return_type = - FunctionReturnType::Ty(make_type(UnresolvedTypeData::Named(selector_path, vec![], true))); - - let mut selector_fn_def = FunctionDefinition::normal( - &ident("selector"), - &vec![], - &[], - &selector_fun_body, - &[], - &return_type, - ); - - selector_fn_def.visibility = ItemVisibility::Public; - - // Seems to be necessary on contract modules - selector_fn_def.return_visibility = Visibility::Public; - - TypeImpl { - object_type: struct_type, - type_span: structure.span, - generics: vec![], - methods: vec![(NoirFunction::normal(selector_fn_def), Span::default())], + let mut event_interface_trait_impl = + generate_trait_impl_stub_event_interface(event_type.as_str(), event_byte_len)?; + event_interface_trait_impl.items.push(TraitImplItem::Function( + generate_fn_get_event_type_id(event_type.as_str(), event_len)?, + )); + event_interface_trait_impl.items.push(TraitImplItem::Function( + generate_fn_private_to_be_bytes(event_type.as_str(), event_byte_len)?, + )); + event_interface_trait_impl.items.push(TraitImplItem::Function( + generate_fn_to_be_bytes(event_type.as_str(), event_byte_len)?, + )); + event_interface_trait_impl + .items + .push(TraitImplItem::Function(generate_fn_emit(event_type.as_str())?)); + submodule.contents.trait_impls.push(event_interface_trait_impl); + + let serialize_trait_impl = + generate_trait_impl_serialize(event_type.as_str(), event_len, &event_fields)?; + submodule.contents.trait_impls.push(serialize_trait_impl); + + let deserialize_trait_impl = + generate_trait_impl_deserialize(event_type.as_str(), event_len, &event_fields)?; + submodule.contents.trait_impls.push(deserialize_trait_impl); + } } + + Ok(()) } -/// Computes the signature for a resolved event type. -/// It has the form 'EventName(Field,(Field),[u8;2])' -fn event_signature(event: &StructType) -> String { - let fields = vecmap(event.get_fields(&[]), |(_, typ)| signature_of_type(&typ)); - format!("{}({})", event.name.0.contents, fields.join(",")) +fn generate_trait_impl_stub_event_interface( + event_type: &str, + byte_length: u32, +) -> Result { + let byte_length_without_randomness = byte_length - 32; + let trait_impl_source = format!( + " +impl dep::aztec::event::event_interface::EventInterface<{byte_length}, {byte_length_without_randomness}> for {event_type} {{ + }} + " + ) + .to_string(); + + let (parsed_ast, errors) = parse_program(&trait_impl_source); + if !errors.is_empty() { + dbg!(errors); + return Err(AztecMacroError::CouldNotImplementEventInterface { + secondary_message: Some(format!("Failed to parse Noir macro code (trait impl of {event_type} for EventInterface). This is either a bug in the compiler or the Noir macro code")), + }); + } + + let mut sorted_ast = parsed_ast.into_sorted(); + let event_interface_impl = sorted_ast.trait_impls.remove(0); + + Ok(event_interface_impl) } -/// Substitutes the signature literal that was introduced in the selector method previously with the actual signature. -fn transform_event( - struct_id: StructId, - interner: &mut NodeInterner, -) -> Result<(), (AztecMacroError, FileId)> { - let struct_type = interner.get_struct(struct_id); - let selector_id = interner - .lookup_method(&Type::Struct(struct_type.clone(), vec![]), struct_id, "selector", false) - .ok_or_else(|| { - let error = AztecMacroError::EventError { - span: struct_type.borrow().location.span, - message: "Selector method not found".to_owned(), - }; - (error, struct_type.borrow().location.file) - })?; - let selector_function = interner.function(&selector_id); - - let compute_selector_statement = interner.statement( - selector_function.block(interner).statements().first().ok_or_else(|| { - let error = AztecMacroError::EventError { - span: struct_type.borrow().location.span, - message: "Compute selector statement not found".to_owned(), - }; - (error, struct_type.borrow().location.file) - })?, - ); - - let compute_selector_expression = match compute_selector_statement { - HirStatement::Expression(expression_id) => match interner.expression(&expression_id) { - HirExpression::Call(hir_call_expression) => Some(hir_call_expression), - _ => None, - }, - _ => None, +fn generate_trait_impl_serialize( + event_type: &str, + event_len: u32, + event_fields: &[(String, String)], +) -> Result { + let field_names = event_fields + .iter() + .map(|field| { + let field_type = field.1.as_str(); + match field_type { + "Field" => format!("self.{}", field.0), + "bool" | "u8" | "u32" | "u64" | "i8" | "i32" | "i64" => { + format!("self.{} as Field", field.0) + } + _ => format!("self.{}.to_field()", field.0), + } + }) + .collect::>(); + let field_input = field_names.join(","); + + let trait_impl_source = format!( + " + impl dep::aztec::protocol_types::traits::Serialize<{event_len}> for {event_type} {{ + fn serialize(self: {event_type}) -> [Field; {event_len}] {{ + [{field_input}] + }} + }} + " + ) + .to_string(); + + let (parsed_ast, errors) = parse_program(&trait_impl_source); + if !errors.is_empty() { + dbg!(errors); + return Err(AztecMacroError::CouldNotImplementEventInterface { + secondary_message: Some(format!("Failed to parse Noir macro code (trait impl of Serialize for {event_type}). This is either a bug in the compiler or the Noir macro code")), + }); } - .ok_or_else(|| { - let error = AztecMacroError::EventError { - span: struct_type.borrow().location.span, - message: "Compute selector statement is not a call expression".to_owned(), - }; - (error, struct_type.borrow().location.file) - })?; - - let first_arg_id = compute_selector_expression.arguments.first().ok_or_else(|| { - let error = AztecMacroError::EventError { - span: struct_type.borrow().location.span, - message: "Compute selector statement is not a call expression".to_owned(), - }; - (error, struct_type.borrow().location.file) - })?; - - match interner.expression(first_arg_id) { - HirExpression::Literal(HirLiteral::Str(signature)) - if signature == SIGNATURE_PLACEHOLDER => - { - let selector_literal_id = *first_arg_id; - - let structure = interner.get_struct(struct_id); - let signature = event_signature(&structure.borrow()); - interner.update_expression(selector_literal_id, |expr| { - *expr = HirExpression::Literal(HirLiteral::Str(signature.clone())); - }); - - // Also update the type! It might have a different length now than the placeholder. - interner.push_expr_type( - selector_literal_id, - Type::String(Box::new(Type::Constant(signature.len() as u32))), - ); - Ok(()) - } - _ => Err(( - AztecMacroError::EventError { - span: struct_type.borrow().location.span, - message: "Signature placeholder literal does not match".to_owned(), - }, - struct_type.borrow().location.file, - )), + + let mut sorted_ast = parsed_ast.into_sorted(); + let serialize_impl = sorted_ast.trait_impls.remove(0); + + Ok(serialize_impl) +} + +fn generate_trait_impl_deserialize( + event_type: &str, + event_len: u32, + event_fields: &[(String, String)], +) -> Result { + let field_names: Vec = event_fields + .iter() + .enumerate() + .map(|(index, field)| { + let field_type = field.1.as_str(); + match field_type { + "Field" => format!("{}: fields[{}]", field.0, index), + "bool" | "u8" | "u32" | "u64" | "i8" | "i32" | "i64" => { + format!("{}: fields[{}] as {}", field.0, index, field_type) + } + _ => format!("{}: {}::from_field(fields[{}])", field.0, field.1, index), + } + }) + .collect::>(); + let field_input = field_names.join(","); + + let trait_impl_source = format!( + " + impl dep::aztec::protocol_types::traits::Deserialize<{event_len}> for {event_type} {{ + fn deserialize(fields: [Field; {event_len}]) -> {event_type} {{ + {event_type} {{ {field_input} }} + }} + }} + " + ) + .to_string(); + + let (parsed_ast, errors) = parse_program(&trait_impl_source); + if !errors.is_empty() { + dbg!(errors); + return Err(AztecMacroError::CouldNotImplementEventInterface { + secondary_message: Some(format!("Failed to parse Noir macro code (trait impl of Deserialize for {event_type}). This is either a bug in the compiler or the Noir macro code")), + }); } + + let mut sorted_ast = parsed_ast.into_sorted(); + let deserialize_impl = sorted_ast.trait_impls.remove(0); + + Ok(deserialize_impl) } -pub fn transform_events( +fn generate_fn_get_event_type_id( + event_type: &str, + field_length: u32, +) -> Result { + let from_signature_input = + std::iter::repeat("Field").take(field_length as usize).collect::>().join(","); + let function_source = format!( + " + fn get_event_type_id() -> dep::aztec::protocol_types::abis::event_selector::EventSelector {{ + dep::aztec::protocol_types::abis::event_selector::EventSelector::from_signature(\"{event_type}({from_signature_input})\") + }} + ", + ) + .to_string(); + + let (function_ast, errors) = parse_program(&function_source); + if !errors.is_empty() { + dbg!(errors); + return Err(AztecMacroError::CouldNotImplementEventInterface { + secondary_message: Some(format!("Failed to parse Noir macro code (fn get_event_type_id, implemented for EventInterface of {event_type}). This is either a bug in the compiler or the Noir macro code")), + }); + } + + let mut function_ast = function_ast.into_sorted(); + let mut noir_fn = function_ast.functions.remove(0); + noir_fn.def.visibility = ItemVisibility::Public; + Ok(noir_fn) +} + +fn generate_fn_private_to_be_bytes( + event_type: &str, + byte_length: u32, +) -> Result { + let function_source = format!( + " + fn private_to_be_bytes(self: {event_type}, randomness: Field) -> [u8; {byte_length}] {{ + let mut buffer: [u8; {byte_length}] = [0; {byte_length}]; + + let randomness_bytes = randomness.to_be_bytes(32); + let event_type_id_bytes = {event_type}::get_event_type_id().to_field().to_be_bytes(32); + + for i in 0..32 {{ + buffer[i] = randomness_bytes[i]; + buffer[32 + i] = event_type_id_bytes[i]; + }} + + let serialized_event = self.serialize(); + + for i in 0..serialized_event.len() {{ + let bytes = serialized_event[i].to_be_bytes(32); + for j in 0..32 {{ + buffer[64 + i * 32 + j] = bytes[j]; + }} + }} + + buffer + }} + " + ) + .to_string(); + + let (function_ast, errors) = parse_program(&function_source); + if !errors.is_empty() { + dbg!(errors); + return Err(AztecMacroError::CouldNotImplementEventInterface { + secondary_message: Some(format!("Failed to parse Noir macro code (fn private_to_be_bytes, implemented for EventInterface of {event_type}). This is either a bug in the compiler or the Noir macro code")), + }); + } + + let mut function_ast = function_ast.into_sorted(); + let mut noir_fn = function_ast.functions.remove(0); + noir_fn.def.visibility = ItemVisibility::Public; + Ok(noir_fn) +} + +fn generate_fn_to_be_bytes( + event_type: &str, + byte_length: u32, +) -> Result { + let byte_length_without_randomness = byte_length - 32; + let function_source = format!( + " + fn to_be_bytes(self: {event_type}) -> [u8; {byte_length_without_randomness}] {{ + let mut buffer: [u8; {byte_length_without_randomness}] = [0; {byte_length_without_randomness}]; + + let event_type_id_bytes = {event_type}::get_event_type_id().to_field().to_be_bytes(32); + + for i in 0..32 {{ + buffer[i] = event_type_id_bytes[i]; + }} + + let serialized_event = self.serialize(); + + for i in 0..serialized_event.len() {{ + let bytes = serialized_event[i].to_be_bytes(32); + for j in 0..32 {{ + buffer[32 + i * 32 + j] = bytes[j]; + }} + }} + + buffer + }} + ") + .to_string(); + + let (function_ast, errors) = parse_program(&function_source); + if !errors.is_empty() { + dbg!(errors); + return Err(AztecMacroError::CouldNotImplementEventInterface { + secondary_message: Some(format!("Failed to parse Noir macro code (fn to_be_bytes, implemented for EventInterface of {event_type}). This is either a bug in the compiler or the Noir macro code")), + }); + } + + let mut function_ast = function_ast.into_sorted(); + let mut noir_fn = function_ast.functions.remove(0); + noir_fn.def.visibility = ItemVisibility::Public; + Ok(noir_fn) +} + +fn generate_fn_emit(event_type: &str) -> Result { + let function_source = format!( + " + fn emit(self: {event_type}, _emit: fn[Env](Self) -> ()) {{ + _emit(self); + }} + " + ) + .to_string(); + + let (function_ast, errors) = parse_program(&function_source); + if !errors.is_empty() { + dbg!(errors); + return Err(AztecMacroError::CouldNotImplementEventInterface { + secondary_message: Some(format!("Failed to parse Noir macro code (fn emit, implemented for EventInterface of {event_type}). This is either a bug in the compiler or the Noir macro code")), + }); + } + + let mut function_ast = function_ast.into_sorted(); + let mut noir_fn = function_ast.functions.remove(0); + noir_fn.def.visibility = ItemVisibility::Public; + Ok(noir_fn) +} + +// We do this pass in the HIR to work around the "#[abi(tag)] attributes can only be used in contracts" error +pub fn transform_event_abi( crate_id: &CrateId, context: &mut HirContext, ) -> Result<(), (AztecMacroError, FileId)> { @@ -184,3 +359,14 @@ pub fn transform_events( } Ok(()) } + +fn transform_event( + struct_id: StructId, + interner: &mut NodeInterner, +) -> Result<(), (AztecMacroError, FileId)> { + interner.update_struct_attributes(struct_id, |struct_attributes| { + struct_attributes.push(SecondaryAttribute::Abi("events".to_string())); + }); + + Ok(()) +} diff --git a/aztec_macros/src/transforms/note_interface.rs b/aztec_macros/src/transforms/note_interface.rs index f0e7d0d5034..49525fc2ae1 100644 --- a/aztec_macros/src/transforms/note_interface.rs +++ b/aztec_macros/src/transforms/note_interface.rs @@ -11,7 +11,10 @@ use noirc_frontend::{ Type, }; +use acvm::AcirField; use regex::Regex; +// TODO(#7165): nuke the following dependency from here and Cargo.toml +use tiny_keccak::{Hasher, Keccak}; use crate::{ chained_dep, @@ -69,6 +72,7 @@ pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), Azt type_span: note_struct.name.span(), generics: vec![], methods: vec![], + where_clause: vec![], }; module.impls.push(default_impl.clone()); module.impls.last_mut().unwrap() @@ -97,7 +101,6 @@ pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), Azt .collect::, _>>()?; let [note_serialized_len, note_bytes_len]: [_; 2] = note_interface_generics.try_into().unwrap(); - let note_type_id = note_type_id(¬e_type); // Automatically inject the header field if it's not present let (header_field_name, _) = if let Some(existing_header) = @@ -184,25 +187,26 @@ pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), Azt } if !check_trait_method_implemented(trait_impl, "get_note_type_id") { + let note_type_id = compute_note_type_id(¬e_type); let get_note_type_id_fn = - generate_note_get_type_id(¬e_type_id, note_interface_impl_span)?; + generate_get_note_type_id(note_type_id, note_interface_impl_span)?; trait_impl.items.push(TraitImplItem::Function(get_note_type_id_fn)); } if !check_trait_method_implemented(trait_impl, "compute_note_content_hash") { - let get_header_fn = + let compute_note_content_hash_fn = generate_compute_note_content_hash(¬e_type, note_interface_impl_span)?; - trait_impl.items.push(TraitImplItem::Function(get_header_fn)); + trait_impl.items.push(TraitImplItem::Function(compute_note_content_hash_fn)); } if !check_trait_method_implemented(trait_impl, "to_be_bytes") { - let get_header_fn = generate_note_to_be_bytes( + let to_be_bytes_fn = generate_note_to_be_bytes( ¬e_type, note_bytes_len.as_str(), note_serialized_len.as_str(), note_interface_impl_span, )?; - trait_impl.items.push(TraitImplItem::Function(get_header_fn)); + trait_impl.items.push(TraitImplItem::Function(to_be_bytes_fn)); } } @@ -324,16 +328,17 @@ fn generate_note_set_header( // Automatically generate the note type id getter method. The id itself its calculated as the concatenation // of the conversion of the characters in the note's struct name to unsigned integers. -fn generate_note_get_type_id( - note_type_id: &str, +fn generate_get_note_type_id( + note_type_id: u32, impl_span: Option, ) -> Result { + // TODO(#7165): replace {} with dep::aztec::protocol_types::abis::note_selector::compute_note_selector(\"{}\") in the function source below let function_source = format!( " - fn get_note_type_id() -> Field {{ - {} - }} - ", + fn get_note_type_id() -> Field {{ + {} + }} + ", note_type_id ) .to_string(); @@ -387,7 +392,7 @@ fn generate_note_properties_struct( // Generate the deserialize_content method as // -// fn deserialize_content(serialized_note: [Field; NOTE_SERILIZED_LEN]) -> Self { +// fn deserialize_content(serialized_note: [Field; NOTE_SERIALIZED_LEN]) -> Self { // NoteType { // note_field1: serialized_note[0] as Field, // note_field2: NoteFieldType2::from_field(serialized_note[1])... @@ -525,10 +530,10 @@ fn generate_note_exports_global( let struct_source = format!( " #[abi(notes)] - global {0}_EXPORTS: (Field, str<{1}>) = ({2},\"{0}\"); + global {0}_EXPORTS: (Field, str<{1}>) = (0x{2},\"{0}\"); ", note_type, - note_type_id.len(), + note_type.len(), note_type_id ) .to_string(); @@ -557,7 +562,9 @@ fn generate_note_properties_struct_source( .iter() .filter_map(|(field_name, _)| { if field_name != note_header_field_name { - Some(format!("{}: aztec::note::note_getter_options::PropertySelector", field_name)) + Some(format!( + "{field_name}: dep::aztec::note::note_getter_options::PropertySelector" + )) } else { None } @@ -662,7 +669,9 @@ fn generate_note_deserialize_content_source( ) } } else { - format!("{}: aztec::note::note_header::NoteHeader::empty()", note_header_field_name) + format!( + "{note_header_field_name}: dep::aztec::note::note_header::NoteHeader::empty()" + ) } }) .collect::>() @@ -679,10 +688,18 @@ fn generate_note_deserialize_content_source( .to_string() } +// TODO(#7165): nuke this function // Utility function to generate the note type id as a Field -fn note_type_id(note_type: &str) -> String { +fn compute_note_type_id(note_type: &str) -> u32 { // TODO(#4519) Improve automatic note id generation and assignment - note_type.chars().map(|c| (c as u32).to_string()).collect::>().join("") + let mut keccak = Keccak::v256(); + let mut result = [0u8; 32]; + keccak.update(note_type.as_bytes()); + keccak.finalize(&mut result); + // Take the first 4 bytes of the hash and convert them to an integer + // If you change the following value you have to change NUM_BYTES_PER_NOTE_TYPE_ID in l1_note_payload.ts as well + let num_bytes_per_note_type_id = 4; + u32::from_be_bytes(result[0..num_bytes_per_note_type_id].try_into().unwrap()) } pub fn inject_note_exports( @@ -711,29 +728,42 @@ pub fn inject_note_exports( }, file_id, ))?; - let init_function = + let get_note_type_id_function = context.def_interner.function(&func_id).block(&context.def_interner); - let init_function_statement_id = init_function.statements().first().ok_or(( - AztecMacroError::CouldNotExportStorageLayout { - span: None, - secondary_message: Some(format!( - "Could not retrieve note id statement from function for note {}", - note.borrow().name.0.contents - )), - }, - file_id, - ))?; - let note_id_statement = context.def_interner.statement(init_function_statement_id); + let get_note_type_id_statement_id = + get_note_type_id_function.statements().first().ok_or(( + AztecMacroError::CouldNotExportStorageLayout { + span: None, + secondary_message: Some(format!( + "Could not retrieve note id statement from function for note {}", + note.borrow().name.0.contents + )), + }, + file_id, + ))?; + let note_type_id_statement = + context.def_interner.statement(get_note_type_id_statement_id); - let note_id_value = match note_id_statement { + let note_type_id = match note_type_id_statement { HirStatement::Expression(expression_id) => { match context.def_interner.expression(&expression_id) { HirExpression::Literal(HirLiteral::Integer(value, _)) => Ok(value), + HirExpression::Literal(_) => Err(( + AztecMacroError::CouldNotExportStorageLayout { + span: None, + secondary_message: Some( + "note_type_id statement must be a literal integer expression" + .to_string(), + ), + }, + file_id, + )), _ => Err(( AztecMacroError::CouldNotExportStorageLayout { span: None, secondary_message: Some( - "note_id statement must be a literal expression".to_string(), + "note_type_id statement must be a literal expression" + .to_string(), ), }, file_id, @@ -741,9 +771,10 @@ pub fn inject_note_exports( } } _ => Err(( - AztecMacroError::CouldNotAssignStorageSlots { + AztecMacroError::CouldNotExportStorageLayout { + span: None, secondary_message: Some( - "note_id statement must be an expression".to_string(), + "note_type_id statement must be an expression".to_string(), ), }, file_id, @@ -751,7 +782,7 @@ pub fn inject_note_exports( }?; let global = generate_note_exports_global( ¬e.borrow().name.0.contents, - ¬e_id_value.to_string(), + ¬e_type_id.to_hex(), ) .map_err(|err| (err, file_id))?; diff --git a/aztec_macros/src/transforms/storage.rs b/aztec_macros/src/transforms/storage.rs index 8b778b4cca6..c302dd87aa5 100644 --- a/aztec_macros/src/transforms/storage.rs +++ b/aztec_macros/src/transforms/storage.rs @@ -91,7 +91,7 @@ pub fn inject_context_in_storage(module: &mut SortedModule) -> Result<(), AztecM r#struct.attributes.iter().any(|attr| is_custom_attribute(attr, "aztec(storage)")) }) .unwrap(); - storage_struct.generics.push(ident("Context")); + storage_struct.generics.push(ident("Context").into()); storage_struct .fields .iter_mut() @@ -243,9 +243,11 @@ pub fn generate_storage_implementation( span: Some(Span::default()), }, type_span: Span::default(), - generics: vec![generic_context_ident], + generics: vec![generic_context_ident.into()], methods: vec![(init, Span::default())], + + where_clause: vec![], }; module.impls.push(storage_impl); @@ -497,6 +499,7 @@ pub fn assign_storage_slots( pub fn generate_storage_layout( module: &mut SortedModule, storage_struct_name: String, + module_name: &str, ) -> Result<(), AztecMacroError> { let definition = module .types @@ -504,33 +507,28 @@ pub fn generate_storage_layout( .find(|r#struct| r#struct.name.0.contents == *storage_struct_name) .unwrap(); - let mut generic_args = vec![]; let mut storable_fields = vec![]; let mut storable_fields_impl = vec![]; - definition.fields.iter().enumerate().for_each(|(index, (field_ident, field_type))| { - storable_fields.push(format!("{}: aztec::prelude::Storable", field_ident, index)); - generic_args.push(format!("N{}", index)); - storable_fields_impl.push(format!( - "{}: aztec::prelude::Storable {{ slot: 0, typ: \"{}\" }}", - field_ident, - field_type.to_string().replace("plain::", "") - )); + definition.fields.iter().for_each(|(field_ident, _)| { + storable_fields.push(format!("{}: dep::aztec::prelude::Storable", field_ident)); + storable_fields_impl + .push(format!("{}: dep::aztec::prelude::Storable {{ slot: 0 }}", field_ident,)); }); let storage_fields_source = format!( " - struct StorageLayout<{}> {{ + struct StorageLayout {{ {} }} #[abi(storage)] - global STORAGE_LAYOUT = StorageLayout {{ + global {}_STORAGE_LAYOUT = StorageLayout {{ {} }}; ", - generic_args.join(", "), storable_fields.join(",\n"), + module_name, storable_fields_impl.join(",\n") ); diff --git a/aztec_macros/src/utils/constants.rs b/aztec_macros/src/utils/constants.rs index 848cca0477d..2178f7a2526 100644 --- a/aztec_macros/src/utils/constants.rs +++ b/aztec_macros/src/utils/constants.rs @@ -1,4 +1,3 @@ pub const FUNCTION_TREE_HEIGHT: u32 = 5; pub const MAX_CONTRACT_PRIVATE_FUNCTIONS: usize = 2_usize.pow(FUNCTION_TREE_HEIGHT); -pub const SIGNATURE_PLACEHOLDER: &str = "SIGNATURE_PLACEHOLDER"; pub const SELECTOR_PLACEHOLDER: &str = "SELECTOR_PLACEHOLDER"; diff --git a/aztec_macros/src/utils/errors.rs b/aztec_macros/src/utils/errors.rs index 852b5f1e57a..557d065cb25 100644 --- a/aztec_macros/src/utils/errors.rs +++ b/aztec_macros/src/utils/errors.rs @@ -14,6 +14,7 @@ pub enum AztecMacroError { CouldNotAssignStorageSlots { secondary_message: Option }, CouldNotImplementComputeNoteHashAndOptionallyANullifier { secondary_message: Option }, CouldNotImplementNoteInterface { span: Option, secondary_message: Option }, + CouldNotImplementEventInterface { secondary_message: Option }, MultipleStorageDefinitions { span: Option }, CouldNotExportStorageLayout { span: Option, secondary_message: Option }, CouldNotInjectContextGenericInStorage { secondary_message: Option }, @@ -67,6 +68,11 @@ impl From for MacroError { secondary_message, span }, + AztecMacroError::CouldNotImplementEventInterface { secondary_message } => MacroError { + primary_message: "Could not implement automatic methods for event, please provide an implementation of the EventInterface trait".to_string(), + secondary_message, + span: None, + }, AztecMacroError::MultipleStorageDefinitions { span } => MacroError { primary_message: "Only one struct can be tagged as #[aztec(storage)]".to_string(), secondary_message: None, diff --git a/aztec_macros/src/utils/hir_utils.rs b/aztec_macros/src/utils/hir_utils.rs index d4b55e1311f..7198ed5bd3d 100644 --- a/aztec_macros/src/utils/hir_utils.rs +++ b/aztec_macros/src/utils/hir_utils.rs @@ -230,6 +230,7 @@ pub fn inject_global( file_id, global.attributes.clone(), false, + false, ); // Add the statement to the scope so its path can be looked up later @@ -268,20 +269,12 @@ pub fn fully_qualified_note_path(context: &HirContext, note_id: StructId) -> Opt if &module_id.krate == context.root_crate_id() { Some(module_path) } else { - find_non_contract_dependencies_bfs(context, context.root_crate_id(), &module_id.krate) + find_dependencies_bfs(context, context.root_crate_id(), &module_id.krate) .map(|crates| crates.join("::") + "::" + &module_path) } } -fn filter_contract_modules(context: &HirContext, crate_id: &CrateId) -> bool { - if let Some(def_map) = context.def_map(crate_id) { - !def_map.modules().iter().any(|(_, module)| module.is_contract) - } else { - true - } -} - -fn find_non_contract_dependencies_bfs( +fn find_dependencies_bfs( context: &HirContext, crate_id: &CrateId, target_crate_id: &CrateId, @@ -289,7 +282,6 @@ fn find_non_contract_dependencies_bfs( context.crate_graph[crate_id] .dependencies .iter() - .filter(|dep| filter_contract_modules(context, &dep.crate_id)) .find_map(|dep| { if &dep.crate_id == target_crate_id { Some(vec![dep.name.to_string()]) @@ -298,20 +290,16 @@ fn find_non_contract_dependencies_bfs( } }) .or_else(|| { - context.crate_graph[crate_id] - .dependencies - .iter() - .filter(|dep| filter_contract_modules(context, &dep.crate_id)) - .find_map(|dep| { - if let Some(mut path) = - find_non_contract_dependencies_bfs(context, &dep.crate_id, target_crate_id) - { - path.insert(0, dep.name.to_string()); - Some(path) - } else { - None - } - }) + context.crate_graph[crate_id].dependencies.iter().find_map(|dep| { + if let Some(mut path) = + find_dependencies_bfs(context, &dep.crate_id, target_crate_id) + { + path.insert(0, dep.name.to_string()); + Some(path) + } else { + None + } + }) }) } diff --git a/compiler/fm/Cargo.toml b/compiler/fm/Cargo.toml index f556f305c78..b48f445be36 100644 --- a/compiler/fm/Cargo.toml +++ b/compiler/fm/Cargo.toml @@ -10,8 +10,8 @@ license.workspace = true [dependencies] codespan-reporting.workspace = true +iter-extended.workspace = true serde.workspace = true [dev-dependencies] -tempfile.workspace = true iter-extended.workspace = true diff --git a/compiler/fm/src/lib.rs b/compiler/fm/src/lib.rs index bb080c62c0e..37da29fc982 100644 --- a/compiler/fm/src/lib.rs +++ b/compiler/fm/src/lib.rs @@ -7,6 +7,7 @@ mod file_map; pub use file_map::{File, FileId, FileMap, PathString}; +use iter_extended::vecmap; // Re-export for the lsp pub use codespan_reporting::files as codespan_files; @@ -103,6 +104,26 @@ impl FileManager { pub fn name_to_id(&self, file_name: PathBuf) -> Option { self.file_map.get_file_id(&PathString::from_path(file_name)) } + + /// Find a file by its path suffix, e.g. "src/main.nr" is a suffix of + /// "some_dir/package_name/src/main.nr"` + pub fn find_by_path_suffix(&self, suffix: &str) -> Result, Vec> { + let suffix_path: Vec<_> = Path::new(suffix).components().rev().collect(); + let results: Vec<_> = self + .path_to_id + .iter() + .filter(|(path, _id)| { + path.components().rev().zip(suffix_path.iter()).all(|(x, y)| &x == y) + }) + .collect(); + if results.is_empty() { + Ok(None) + } else if results.len() == 1 { + Ok(Some(*results[0].1)) + } else { + Err(vecmap(results, |(path, _id)| path.clone())) + } + } } pub trait NormalizePath { @@ -185,24 +206,17 @@ mod path_normalization { #[cfg(test)] mod tests { use super::*; - use tempfile::{tempdir, TempDir}; - // Returns the absolute path to the file - fn create_dummy_file(dir: &TempDir, file_name: &Path) -> PathBuf { - let file_path = dir.path().join(file_name); - let _file = std::fs::File::create(&file_path).unwrap(); - file_path + fn add_file(fm: &mut FileManager, file_name: &Path) -> FileId { + fm.add_file_with_source(file_name, "fn foo() {}".to_string()).unwrap() } #[test] fn path_resolve_file_module_other_ext() { - let dir = tempdir().unwrap(); - let file_name = Path::new("foo.nr"); - create_dummy_file(&dir, file_name); + let dir = PathBuf::new(); + let mut fm = FileManager::new(&dir); - let mut fm = FileManager::new(dir.path()); - - let file_id = fm.add_file_with_source(file_name, "fn foo() {}".to_string()).unwrap(); + let file_id = add_file(&mut fm, &dir.join("foo.nr")); assert!(fm.path(file_id).unwrap().ends_with("foo.nr")); } @@ -213,23 +227,19 @@ mod tests { /// they should both resolve to ../foo.nr #[test] fn path_resolve_modules_with_different_paths_as_same_file() { - let dir = tempdir().unwrap(); - let sub_dir = TempDir::new_in(&dir).unwrap(); - let sub_sub_dir = TempDir::new_in(&sub_dir).unwrap(); - - let mut fm = FileManager::new(dir.path()); - - // Create a lib.nr file at the root. - let file_name = Path::new("lib.nr"); - create_dummy_file(&dir, file_name); - - // Create another path with `./` and `../` inside it - let second_file_name = PathBuf::from(sub_sub_dir.path()).join("./../../lib.nr"); - - // Add both files to the file manager - let file_id = fm.add_file_with_source(file_name, "fn foo() {}".to_string()).unwrap(); - let second_file_id = - fm.add_file_with_source(&second_file_name, "fn foo() {}".to_string()).unwrap(); + let dir = PathBuf::new(); + let mut fm = FileManager::new(&dir); + + // Create a lib.nr file at the root and add it to the file manager. + let file_id = add_file(&mut fm, &dir.join("lib.nr")); + + // Create another path with `./` and `../` inside it, and add it to the file manager + let sub_dir = dir.join("sub_dir"); + let sub_sub_dir = sub_dir.join("sub_sub_dir"); + let second_file_id = add_file( + &mut fm, + PathBuf::from(sub_sub_dir.as_path()).join("./../../lib.nr").as_path(), + ); assert_eq!(file_id, second_file_id); } diff --git a/compiler/noirc_driver/src/lib.rs b/compiler/noirc_driver/src/lib.rs index dde2bd08d74..2b0769e30d4 100644 --- a/compiler/noirc_driver/src/lib.rs +++ b/compiler/noirc_driver/src/lib.rs @@ -103,6 +103,11 @@ pub struct CompileOptions { #[arg(long, hide = true)] pub use_legacy: bool, + /// Enable printing results of comptime evaluation: provide a path suffix + /// for the module to debug, e.g. "package_name/src/main.nr" + #[arg(long)] + pub debug_comptime_in_file: Option, + /// Outputs the paths to any modified artifacts #[arg(long, hide = true)] pub show_artifact_paths: bool, @@ -258,12 +263,14 @@ pub fn check_crate( deny_warnings: bool, disable_macros: bool, use_legacy: bool, + debug_comptime_in_file: Option<&str>, ) -> CompilationResult<()> { let macros: &[&dyn MacroProcessor] = if disable_macros { &[] } else { &[&aztec_macros::AztecMacro as &dyn MacroProcessor] }; let mut errors = vec![]; - let diagnostics = CrateDefMap::collect_defs(crate_id, context, use_legacy, macros); + let diagnostics = + CrateDefMap::collect_defs(crate_id, context, use_legacy, debug_comptime_in_file, macros); errors.extend(diagnostics.into_iter().map(|(error, file_id)| { let diagnostic = CustomDiagnostic::from(&error); diagnostic.in_file(file_id) @@ -301,6 +308,7 @@ pub fn compile_main( options.deny_warnings, options.disable_macros, options.use_legacy, + options.debug_comptime_in_file.as_deref(), )?; let main = context.get_main_function(&crate_id).ok_or_else(|| { @@ -342,6 +350,7 @@ pub fn compile_contract( options.deny_warnings, options.disable_macros, options.use_legacy, + options.debug_comptime_in_file.as_deref(), )?; // TODO: We probably want to error if contracts is empty @@ -544,14 +553,15 @@ pub fn compile_no_check( return Ok(cached_program.expect("cache must exist for hashes to match")); } let return_visibility = program.return_visibility; + let ssa_evaluator_options = noirc_evaluator::ssa::SsaEvaluatorOptions { + enable_ssa_logging: options.show_ssa, + enable_brillig_logging: options.show_brillig, + force_brillig_output: options.force_brillig, + print_codegen_timings: options.benchmark_codegen, + }; - let SsaProgramArtifact { program, debug, warnings, names, error_types, .. } = create_program( - program, - options.show_ssa, - options.show_brillig, - options.force_brillig, - options.benchmark_codegen, - )?; + let SsaProgramArtifact { program, debug, warnings, names, error_types, .. } = + create_program(program, &ssa_evaluator_options)?; let abi = abi_gen::gen_abi(context, &main_function, return_visibility, error_types); let file_map = filter_relevant_files(&debug, &context.file_manager); diff --git a/compiler/noirc_driver/tests/stdlib_warnings.rs b/compiler/noirc_driver/tests/stdlib_warnings.rs index 327c8daad06..0e098d0d087 100644 --- a/compiler/noirc_driver/tests/stdlib_warnings.rs +++ b/compiler/noirc_driver/tests/stdlib_warnings.rs @@ -25,9 +25,9 @@ fn stdlib_does_not_produce_constant_warnings() -> Result<(), ErrorsAndWarnings> let root_crate_id = prepare_crate(&mut context, file_name); let ((), warnings) = - noirc_driver::check_crate(&mut context, root_crate_id, false, false, false)?; + noirc_driver::check_crate(&mut context, root_crate_id, false, false, false, None)?; - assert_eq!(warnings, Vec::new(), "stdlib is producing warnings"); + assert_eq!(warnings, Vec::new(), "stdlib is producing {} warnings", warnings.len()); Ok(()) } diff --git a/compiler/noirc_errors/src/reporter.rs b/compiler/noirc_errors/src/reporter.rs index cb5abbe2079..3ce0f268715 100644 --- a/compiler/noirc_errors/src/reporter.rs +++ b/compiler/noirc_errors/src/reporter.rs @@ -1,3 +1,5 @@ +use std::io::IsTerminal; + use crate::{FileDiagnostic, Location, Span}; use codespan_reporting::diagnostic::{Diagnostic, Label}; use codespan_reporting::files::Files; @@ -15,7 +17,9 @@ pub struct CustomDiagnostic { #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum DiagnosticKind { Error, + Bug, Warning, + Info, } /// A count of errors that have been already reported to stderr @@ -34,29 +38,69 @@ impl CustomDiagnostic { } } - pub fn simple_error( + fn simple_with_kind( primary_message: String, secondary_message: String, secondary_span: Span, + kind: DiagnosticKind, ) -> CustomDiagnostic { CustomDiagnostic { message: primary_message, secondaries: vec![CustomLabel::new(secondary_message, secondary_span)], notes: Vec::new(), - kind: DiagnosticKind::Error, + kind, } } + pub fn simple_error( + primary_message: String, + secondary_message: String, + secondary_span: Span, + ) -> CustomDiagnostic { + Self::simple_with_kind( + primary_message, + secondary_message, + secondary_span, + DiagnosticKind::Error, + ) + } + pub fn simple_warning( primary_message: String, secondary_message: String, secondary_span: Span, + ) -> CustomDiagnostic { + Self::simple_with_kind( + primary_message, + secondary_message, + secondary_span, + DiagnosticKind::Warning, + ) + } + + pub fn simple_info( + primary_message: String, + secondary_message: String, + secondary_span: Span, + ) -> CustomDiagnostic { + Self::simple_with_kind( + primary_message, + secondary_message, + secondary_span, + DiagnosticKind::Info, + ) + } + + pub fn simple_bug( + primary_message: String, + secondary_message: String, + secondary_span: Span, ) -> CustomDiagnostic { CustomDiagnostic { message: primary_message, secondaries: vec![CustomLabel::new(secondary_message, secondary_span)], notes: Vec::new(), - kind: DiagnosticKind::Warning, + kind: DiagnosticKind::Bug, } } @@ -79,6 +123,14 @@ impl CustomDiagnostic { pub fn is_warning(&self) -> bool { matches!(self.kind, DiagnosticKind::Warning) } + + pub fn is_info(&self) -> bool { + matches!(self.kind, DiagnosticKind::Info) + } + + pub fn is_bug(&self) -> bool { + matches!(self.kind, DiagnosticKind::Bug) + } } impl std::fmt::Display for CustomDiagnostic { @@ -118,10 +170,13 @@ pub fn report_all<'files>( silence_warnings: bool, ) -> ReportedErrors { // Report warnings before any errors - let (warnings, mut errors): (Vec<_>, _) = - diagnostics.iter().partition(|item| item.diagnostic.is_warning()); + let (warnings_and_bugs, mut errors): (Vec<_>, _) = + diagnostics.iter().partition(|item| !item.diagnostic.is_error()); + let (warnings, mut bugs): (Vec<_>, _) = + warnings_and_bugs.iter().partition(|item| item.diagnostic.is_warning()); let mut diagnostics = if silence_warnings { Vec::new() } else { warnings }; + diagnostics.append(&mut bugs); diagnostics.append(&mut errors); let error_count = @@ -148,7 +203,9 @@ pub fn report<'files>( call_stack: &[Location], deny_warnings: bool, ) -> bool { - let writer = StandardStream::stderr(ColorChoice::Always); + let color_choice = + if std::io::stderr().is_terminal() { ColorChoice::Auto } else { ColorChoice::Never }; + let writer = StandardStream::stderr(color_choice); let config = codespan_reporting::term::Config::default(); let stack_trace = stack_trace(files, call_stack); @@ -166,6 +223,8 @@ fn convert_diagnostic( ) -> Diagnostic { let diagnostic = match (cd.kind, deny_warnings) { (DiagnosticKind::Warning, false) => Diagnostic::warning(), + (DiagnosticKind::Info, _) => Diagnostic::note(), + (DiagnosticKind::Bug, ..) => Diagnostic::bug(), _ => Diagnostic::error(), }; @@ -202,14 +261,14 @@ fn stack_trace<'files>( let path = files.name(call_item.file).expect("should get file path"); let source = files.source(call_item.file).expect("should get file source"); - let (line, column) = location(source.as_ref(), call_item.span.start()); + let (line, column) = line_and_column_from_span(source.as_ref(), &call_item.span); result += &format!("{}. {}:{}:{}\n", i + 1, path, line, column); } result } -fn location(source: &str, span_start: u32) -> (u32, u32) { +pub fn line_and_column_from_span(source: &str, span: &Span) -> (u32, u32) { let mut line = 1; let mut column = 0; @@ -221,7 +280,7 @@ fn location(source: &str, span_start: u32) -> (u32, u32) { column = 0; } - if span_start <= i as u32 { + if span.start() <= i as u32 { break; } } diff --git a/compiler/noirc_evaluator/Cargo.toml b/compiler/noirc_evaluator/Cargo.toml index aa30eef9156..52f154fd1f3 100644 --- a/compiler/noirc_evaluator/Cargo.toml +++ b/compiler/noirc_evaluator/Cargo.toml @@ -12,6 +12,7 @@ license.workspace = true noirc_frontend.workspace = true noirc_errors.workspace = true acvm.workspace = true +bn254_blackbox_solver.workspace = true fxhash.workspace = true iter-extended.workspace = true thiserror.workspace = true @@ -20,3 +21,6 @@ im.workspace = true serde.workspace = true tracing.workspace = true chrono = "0.4.37" + +[dev-dependencies] +proptest.workspace = true \ No newline at end of file diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_black_box.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_black_box.rs index 367cdbe4973..aa9cb8cd7a3 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_black_box.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_black_box.rs @@ -243,13 +243,7 @@ pub(crate) fn convert_black_box_call( [BrilligVariable::SingleAddr(output), BrilligVariable::SingleAddr(modulus_id)], ) = (function_arguments, function_results) { - prepare_bigint_output( - brillig_context, - lhs_modulus, - rhs_modulus, - output, - modulus_id, - ); + prepare_bigint_output(brillig_context, lhs_modulus, rhs_modulus, modulus_id); brillig_context.black_box_op_instruction(BlackBoxOp::BigIntAdd { lhs: lhs.address, rhs: rhs.address, @@ -267,13 +261,7 @@ pub(crate) fn convert_black_box_call( [BrilligVariable::SingleAddr(output), BrilligVariable::SingleAddr(modulus_id)], ) = (function_arguments, function_results) { - prepare_bigint_output( - brillig_context, - lhs_modulus, - rhs_modulus, - output, - modulus_id, - ); + prepare_bigint_output(brillig_context, lhs_modulus, rhs_modulus, modulus_id); brillig_context.black_box_op_instruction(BlackBoxOp::BigIntSub { lhs: lhs.address, rhs: rhs.address, @@ -291,13 +279,7 @@ pub(crate) fn convert_black_box_call( [BrilligVariable::SingleAddr(output), BrilligVariable::SingleAddr(modulus_id)], ) = (function_arguments, function_results) { - prepare_bigint_output( - brillig_context, - lhs_modulus, - rhs_modulus, - output, - modulus_id, - ); + prepare_bigint_output(brillig_context, lhs_modulus, rhs_modulus, modulus_id); brillig_context.black_box_op_instruction(BlackBoxOp::BigIntMul { lhs: lhs.address, rhs: rhs.address, @@ -315,13 +297,7 @@ pub(crate) fn convert_black_box_call( [BrilligVariable::SingleAddr(output), BrilligVariable::SingleAddr(modulus_id)], ) = (function_arguments, function_results) { - prepare_bigint_output( - brillig_context, - lhs_modulus, - rhs_modulus, - output, - modulus_id, - ); + prepare_bigint_output(brillig_context, lhs_modulus, rhs_modulus, modulus_id); brillig_context.black_box_op_instruction(BlackBoxOp::BigIntDiv { lhs: lhs.address, rhs: rhs.address, @@ -341,8 +317,6 @@ pub(crate) fn convert_black_box_call( { let inputs_vector = convert_array_or_vector(brillig_context, inputs, bb_func); let modulus_vector = convert_array_or_vector(brillig_context, modulus, bb_func); - let output_id = brillig_context.get_new_bigint_id(); - brillig_context.const_instruction(*output, F::from(output_id as u128)); brillig_context.black_box_op_instruction(BlackBoxOp::BigIntFromLeBytes { inputs: inputs_vector.to_heap_vector(), modulus: modulus_vector.to_heap_vector(), @@ -447,7 +421,6 @@ fn prepare_bigint_output( brillig_context: &mut BrilligContext, lhs_modulus: &SingleAddrVariable, rhs_modulus: &SingleAddrVariable, - output: &SingleAddrVariable, modulus_id: &SingleAddrVariable, ) { // Check moduli @@ -464,8 +437,6 @@ fn prepare_bigint_output( Some("moduli should be identical in BigInt operation".to_string()), ); brillig_context.deallocate_register(condition); - // Set output id - let output_id = brillig_context.get_new_bigint_id(); - brillig_context.const_instruction(*output, F::from(output_id as u128)); + brillig_context.mov_instruction(modulus_id.address, lhs_modulus.address); } diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs index b441e8be3eb..c2bc1e32cd6 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs @@ -22,6 +22,7 @@ use acvm::{acir::AcirField, FieldElement}; use fxhash::{FxHashMap as HashMap, FxHashSet as HashSet}; use iter_extended::vecmap; use num_bigint::BigUint; +use std::rc::Rc; use super::brillig_black_box::convert_black_box_call; use super::brillig_block_variables::BlockVariables; @@ -582,6 +583,11 @@ impl<'block> BrilligBlock<'block> { 1, ); } + + // `Intrinsic::AsWitness` is used to provide hints to acir-gen on optimal expression splitting. + // It is then useless in the brillig runtime and so we can ignore it + Value::Intrinsic(Intrinsic::AsWitness) => (), + _ => { unreachable!("unsupported function call type {:?}", dfg[*func]) } @@ -1629,7 +1635,7 @@ impl<'block> BrilligBlock<'block> { new_variable } } - Value::Array { array, .. } => { + Value::Array { array, typ } => { if let Some(variable) = self.variables.get_constant(value_id, dfg) { variable } else { @@ -1664,23 +1670,7 @@ impl<'block> BrilligBlock<'block> { // Write the items - // Allocate a register for the iterator - let iterator_register = - self.brillig_context.make_usize_constant_instruction(0_usize.into()); - - for element_id in array.iter() { - let element_variable = self.convert_ssa_value(*element_id, dfg); - // Store the item in memory - self.store_variable_in_array(pointer, iterator_register, element_variable); - // Increment the iterator - self.brillig_context.codegen_usize_op_in_place( - iterator_register.address, - BrilligBinaryOp::Add, - 1, - ); - } - - self.brillig_context.deallocate_single_addr(iterator_register); + self.initialize_constant_array(array, typ, dfg, pointer); new_variable } @@ -1705,6 +1695,125 @@ impl<'block> BrilligBlock<'block> { } } + fn initialize_constant_array( + &mut self, + data: &im::Vector, + typ: &Type, + dfg: &DataFlowGraph, + pointer: MemoryAddress, + ) { + if data.is_empty() { + return; + } + let item_types = typ.clone().element_types(); + + // Find out if we are repeating the same item over and over + let first_item = data.iter().take(item_types.len()).copied().collect(); + let mut is_repeating = true; + + for item_index in (item_types.len()..data.len()).step_by(item_types.len()) { + let item: Vec<_> = (0..item_types.len()).map(|i| data[item_index + i]).collect(); + if first_item != item { + is_repeating = false; + break; + } + } + + // If all the items are single address, and all have the same initial value, we can initialize the array in a runtime loop. + // Since the cost in instructions for a runtime loop is in the order of magnitude of 10, we only do this if the item_count is bigger than that. + let item_count = data.len() / item_types.len(); + + if item_count > 10 + && is_repeating + && item_types.iter().all(|typ| matches!(typ, Type::Numeric(_))) + { + self.initialize_constant_array_runtime( + item_types, first_item, item_count, pointer, dfg, + ); + } else { + self.initialize_constant_array_comptime(data, dfg, pointer); + } + } + + fn initialize_constant_array_runtime( + &mut self, + item_types: Rc>, + item_to_repeat: Vec, + item_count: usize, + pointer: MemoryAddress, + dfg: &DataFlowGraph, + ) { + let mut subitem_to_repeat_variables = Vec::with_capacity(item_types.len()); + for subitem_id in item_to_repeat.into_iter() { + subitem_to_repeat_variables.push(self.convert_ssa_value(subitem_id, dfg)); + } + + let data_length_variable = self + .brillig_context + .make_usize_constant_instruction((item_count * item_types.len()).into()); + + // If this is an array with complex subitems, we need a custom step in the loop to write all the subitems while iterating. + if item_types.len() > 1 { + let step_variable = + self.brillig_context.make_usize_constant_instruction(item_types.len().into()); + + let subitem_pointer = + SingleAddrVariable::new_usize(self.brillig_context.allocate_register()); + + let initializer_fn = |ctx: &mut BrilligContext<_>, iterator: SingleAddrVariable| { + ctx.mov_instruction(subitem_pointer.address, iterator.address); + for subitem in subitem_to_repeat_variables.into_iter() { + Self::store_variable_in_array_with_ctx(ctx, pointer, subitem_pointer, subitem); + ctx.codegen_usize_op_in_place(subitem_pointer.address, BrilligBinaryOp::Add, 1); + } + }; + + self.brillig_context.codegen_loop_with_bound_and_step( + data_length_variable.address, + step_variable.address, + initializer_fn, + ); + + self.brillig_context.deallocate_single_addr(step_variable); + self.brillig_context.deallocate_single_addr(subitem_pointer); + } else { + let subitem = subitem_to_repeat_variables.into_iter().next().unwrap(); + + let initializer_fn = |ctx: &mut _, iterator_register| { + Self::store_variable_in_array_with_ctx(ctx, pointer, iterator_register, subitem); + }; + + self.brillig_context.codegen_loop(data_length_variable.address, initializer_fn); + } + + self.brillig_context.deallocate_single_addr(data_length_variable); + } + + fn initialize_constant_array_comptime( + &mut self, + data: &im::Vector>, + dfg: &DataFlowGraph, + pointer: MemoryAddress, + ) { + // Allocate a register for the iterator + let iterator_register = + self.brillig_context.make_usize_constant_instruction(0_usize.into()); + + for element_id in data.iter() { + let element_variable = self.convert_ssa_value(*element_id, dfg); + // Store the item in memory + self.store_variable_in_array(pointer, iterator_register, element_variable); + // Increment the iterator + self.brillig_context.codegen_usize_op_in_place( + iterator_register.address, + BrilligBinaryOp::Add, + 1, + ); + } + + self.brillig_context.deallocate_single_addr(iterator_register); + } + /// Converts an SSA `ValueId` into a `MemoryAddress`. Initializes if necessary. fn convert_ssa_single_addr_value( &mut self, diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir.rs index 9785e073be9..80367d07635 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir.rs @@ -91,8 +91,6 @@ pub(crate) struct BrilligContext { next_section: usize, /// IR printer debug_show: DebugShow, - /// Counter for generating bigint ids in unconstrained functions - bigint_new_id: u32, } impl BrilligContext { @@ -105,15 +103,9 @@ impl BrilligContext { section_label: 0, next_section: 1, debug_show: DebugShow::new(enable_debug_trace), - bigint_new_id: 0, } } - pub(crate) fn get_new_bigint_id(&mut self) -> u32 { - let result = self.bigint_new_id; - self.bigint_new_id += 1; - result - } /// Adds a brillig instruction to the brillig byte code fn push_opcode(&mut self, opcode: BrilligOpcode) { self.obj.push_opcode(opcode); diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs index 10badcd7308..5741089a497 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs @@ -38,11 +38,12 @@ impl BrilligContext { self.stop_instruction(); } - /// This codegen will issue a loop that will iterate iteration_count times + /// This codegen will issue a loop do for (let iterator_register = 0; i < loop_bound; i += step) /// The body of the loop should be issued by the caller in the on_iteration closure. - pub(crate) fn codegen_loop( + pub(crate) fn codegen_loop_with_bound_and_step( &mut self, - iteration_count: MemoryAddress, + loop_bound: MemoryAddress, + step: MemoryAddress, on_iteration: impl FnOnce(&mut BrilligContext, SingleAddrVariable), ) { let iterator_register = self.make_usize_constant_instruction(0_u128.into()); @@ -52,13 +53,13 @@ impl BrilligContext { // Loop body - // Check if iterator < iteration_count + // Check if iterator < loop_bound let iterator_less_than_iterations = SingleAddrVariable { address: self.allocate_register(), bit_size: 1 }; self.memory_op_instruction( iterator_register.address, - iteration_count, + loop_bound, iterator_less_than_iterations.address, BrilligBinaryOp::LessThan, ); @@ -72,8 +73,13 @@ impl BrilligContext { // Call the on iteration function on_iteration(self, iterator_register); - // Increment the iterator register - self.codegen_usize_op_in_place(iterator_register.address, BrilligBinaryOp::Add, 1); + // Add step to the iterator register + self.memory_op_instruction( + iterator_register.address, + step, + iterator_register.address, + BrilligBinaryOp::Add, + ); self.jump_instruction(loop_label); @@ -85,6 +91,18 @@ impl BrilligContext { self.deallocate_single_addr(iterator_register); } + /// This codegen will issue a loop that will iterate iteration_count times + /// The body of the loop should be issued by the caller in the on_iteration closure. + pub(crate) fn codegen_loop( + &mut self, + iteration_count: MemoryAddress, + on_iteration: impl FnOnce(&mut BrilligContext, SingleAddrVariable), + ) { + let step = self.make_usize_constant_instruction(1_u128.into()); + self.codegen_loop_with_bound_and_step(iteration_count, step.address, on_iteration); + self.deallocate_single_addr(step); + } + /// This codegen will issue an if-then branch that will check if the condition is true /// and if so, perform the instructions given in `f(self, true)` and otherwise perform the /// instructions given in `f(self, false)`. A boolean is passed instead of two separate diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs index dc06c2fa0d7..d10e31533dc 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs @@ -23,7 +23,6 @@ impl BrilligContext { section_label: 0, next_section: 1, debug_show: DebugShow::new(false), - bigint_new_id: 0, }; context.codegen_entry_point(&arguments, &return_parameters); diff --git a/compiler/noirc_evaluator/src/errors.rs b/compiler/noirc_evaluator/src/errors.rs index dd63a254a18..2c7ec0f8e1a 100644 --- a/compiler/noirc_evaluator/src/errors.rs +++ b/compiler/noirc_evaluator/src/errors.rs @@ -19,12 +19,15 @@ use serde::{Deserialize, Serialize}; pub enum RuntimeError { #[error(transparent)] InternalError(#[from] InternalError), - #[error("Index out of bounds, array has size {array_size}, but index was {index}")] - IndexOutOfBounds { index: usize, array_size: usize, call_stack: CallStack }, #[error("Range constraint of {num_bits} bits is too large for the Field size")] InvalidRangeConstraint { num_bits: u32, call_stack: CallStack }, - #[error("{value} does not fit within the type bounds for {typ}")] - IntegerOutOfBounds { value: FieldElement, typ: NumericType, call_stack: CallStack }, + #[error("The value `{value:?}` cannot fit into `{typ}` which has range `{range}`")] + IntegerOutOfBounds { + value: FieldElement, + typ: NumericType, + range: String, + call_stack: CallStack, + }, #[error("Expected array index to fit into a u64")] TypeConversion { from: String, into: String, call_stack: CallStack }, #[error("{name:?} is not initialized")] @@ -35,6 +38,12 @@ pub enum RuntimeError { UnknownLoopBound { call_stack: CallStack }, #[error("Argument is not constant")] AssertConstantFailed { call_stack: CallStack }, + #[error("The static_assert message is not constant")] + StaticAssertDynamicMessage { call_stack: CallStack }, + #[error("Argument is dynamic")] + StaticAssertDynamicPredicate { call_stack: CallStack }, + #[error("Argument is false")] + StaticAssertFailed { call_stack: CallStack }, #[error("Nested slices are not supported")] NestedSlice { call_stack: CallStack }, #[error("Big Integer modulus do no match")] @@ -50,6 +59,7 @@ pub enum RuntimeError { #[derive(Debug, Clone, Serialize, Deserialize)] pub enum SsaReport { Warning(InternalWarning), + Bug(InternalBug), } impl From for FileDiagnostic { @@ -72,6 +82,19 @@ impl From for FileDiagnostic { Diagnostic::simple_warning(message, secondary_message, location.span); diagnostic.in_file(file_id).with_call_stack(call_stack) } + SsaReport::Bug(bug) => { + let message = bug.to_string(); + let (secondary_message, call_stack) = match bug { + InternalBug::IndependentSubgraph { call_stack } => { + ("There is no path from the output of this brillig call to either return values or inputs of the circuit, which creates an independent subgraph. This is quite likely a soundness vulnerability".to_string(),call_stack) + } + }; + let call_stack = vecmap(call_stack, |location| location); + let file_id = call_stack.last().map(|location| location.file).unwrap_or_default(); + let location = call_stack.last().expect("Expected RuntimeError to have a location"); + let diagnostic = Diagnostic::simple_bug(message, secondary_message, location.span); + diagnostic.in_file(file_id).with_call_stack(call_stack) + } } } } @@ -84,6 +107,12 @@ pub enum InternalWarning { VerifyProof { call_stack: CallStack }, } +#[derive(Debug, PartialEq, Eq, Clone, Error, Serialize, Deserialize)] +pub enum InternalBug { + #[error("Input to brillig function is in a separate subgraph to output")] + IndependentSubgraph { call_stack: CallStack }, +} + #[derive(Debug, PartialEq, Eq, Clone, Error)] pub enum InternalError { #[error("ICE: Both expressions should have degree<=1")] @@ -114,12 +143,14 @@ impl RuntimeError { | InternalError::UndeclaredAcirVar { call_stack } | InternalError::Unexpected { call_stack, .. }, ) - | RuntimeError::IndexOutOfBounds { call_stack, .. } | RuntimeError::InvalidRangeConstraint { call_stack, .. } | RuntimeError::TypeConversion { call_stack, .. } | RuntimeError::UnInitialized { call_stack, .. } | RuntimeError::UnknownLoopBound { call_stack } | RuntimeError::AssertConstantFailed { call_stack } + | RuntimeError::StaticAssertDynamicMessage { call_stack } + | RuntimeError::StaticAssertDynamicPredicate { call_stack } + | RuntimeError::StaticAssertFailed { call_stack } | RuntimeError::IntegerOutOfBounds { call_stack, .. } | RuntimeError::UnsupportedIntegerSize { call_stack, .. } | RuntimeError::NestedSlice { call_stack, .. } diff --git a/compiler/noirc_evaluator/src/ssa.rs b/compiler/noirc_evaluator/src/ssa.rs index 7e362599fb5..820374df9c1 100644 --- a/compiler/noirc_evaluator/src/ssa.rs +++ b/compiler/noirc_evaluator/src/ssa.rs @@ -36,11 +36,14 @@ use self::{ }; mod acir_gen; +mod checks; pub(super) mod function_builder; pub mod ir; mod opt; pub mod ssa_gen; +pub(crate) struct ArtifactsAndWarnings(Artifacts, Vec); + /// Optimize the given program by converting it into SSA /// form and performing optimizations there. When finished, /// convert the final SSA into an ACIR program and return it. @@ -48,47 +51,56 @@ pub mod ssa_gen; /// and Brillig functions for unconstrained execution. pub(crate) fn optimize_into_acir( program: Program, - print_passes: bool, - print_brillig_trace: bool, - force_brillig_output: bool, - print_timings: bool, -) -> Result { + options: &SsaEvaluatorOptions, +) -> Result { let ssa_gen_span = span!(Level::TRACE, "ssa_generation"); let ssa_gen_span_guard = ssa_gen_span.enter(); - let ssa = SsaBuilder::new(program, print_passes, force_brillig_output, print_timings)? - .run_pass(Ssa::defunctionalize, "After Defunctionalization:") - .run_pass(Ssa::remove_paired_rc, "After Removing Paired rc_inc & rc_decs:") - .run_pass(Ssa::separate_runtime, "After Runtime Separation:") - .run_pass(Ssa::resolve_is_unconstrained, "After Resolving IsUnconstrained:") - .run_pass(Ssa::inline_functions, "After Inlining:") - // Run mem2reg with the CFG separated into blocks - .run_pass(Ssa::mem2reg, "After Mem2Reg:") - .run_pass(Ssa::as_slice_optimization, "After `as_slice` optimization") - .try_run_pass(Ssa::evaluate_assert_constant, "After Assert Constant:")? - .try_run_pass(Ssa::unroll_loops_iteratively, "After Unrolling:")? - .run_pass(Ssa::simplify_cfg, "After Simplifying:") - .run_pass(Ssa::flatten_cfg, "After Flattening:") - .run_pass(Ssa::remove_bit_shifts, "After Removing Bit Shifts:") - // Run mem2reg once more with the flattened CFG to catch any remaining loads/stores - .run_pass(Ssa::mem2reg, "After Mem2Reg:") - // Run the inlining pass again to handle functions with `InlineType::NoPredicates`. - // Before flattening is run, we treat functions marked with the `InlineType::NoPredicates` as an entry point. - // This pass must come immediately following `mem2reg` as the succeeding passes - // may create an SSA which inlining fails to handle. - .run_pass(Ssa::inline_functions_with_no_predicates, "After Inlining:") - .run_pass(Ssa::remove_if_else, "After Remove IfElse:") - .run_pass(Ssa::fold_constants, "After Constant Folding:") - .run_pass(Ssa::remove_enable_side_effects, "After EnableSideEffects removal:") - .run_pass(Ssa::fold_constants_using_constraints, "After Constraint Folding:") - .run_pass(Ssa::dead_instruction_elimination, "After Dead Instruction Elimination:") - .run_pass(Ssa::array_set_optimization, "After Array Set Optimizations:") - .finish(); - - let brillig = time("SSA to Brillig", print_timings, || ssa.to_brillig(print_brillig_trace)); + let mut ssa = SsaBuilder::new( + program, + options.enable_ssa_logging, + options.force_brillig_output, + options.print_codegen_timings, + )? + .run_pass(Ssa::defunctionalize, "After Defunctionalization:") + .run_pass(Ssa::remove_paired_rc, "After Removing Paired rc_inc & rc_decs:") + .run_pass(Ssa::separate_runtime, "After Runtime Separation:") + .run_pass(Ssa::resolve_is_unconstrained, "After Resolving IsUnconstrained:") + .run_pass(Ssa::inline_functions, "After Inlining:") + // Run mem2reg with the CFG separated into blocks + .run_pass(Ssa::mem2reg, "After Mem2Reg:") + .run_pass(Ssa::as_slice_optimization, "After `as_slice` optimization") + .try_run_pass( + Ssa::evaluate_static_assert_and_assert_constant, + "After `static_assert` and `assert_constant`:", + )? + .try_run_pass(Ssa::unroll_loops_iteratively, "After Unrolling:")? + .run_pass(Ssa::simplify_cfg, "After Simplifying:") + .run_pass(Ssa::flatten_cfg, "After Flattening:") + .run_pass(Ssa::remove_bit_shifts, "After Removing Bit Shifts:") + // Run mem2reg once more with the flattened CFG to catch any remaining loads/stores + .run_pass(Ssa::mem2reg, "After Mem2Reg:") + // Run the inlining pass again to handle functions with `InlineType::NoPredicates`. + // Before flattening is run, we treat functions marked with the `InlineType::NoPredicates` as an entry point. + // This pass must come immediately following `mem2reg` as the succeeding passes + // may create an SSA which inlining fails to handle. + .run_pass(Ssa::inline_functions_with_no_predicates, "After Inlining:") + .run_pass(Ssa::remove_if_else, "After Remove IfElse:") + .run_pass(Ssa::fold_constants, "After Constant Folding:") + .run_pass(Ssa::remove_enable_side_effects, "After EnableSideEffects removal:") + .run_pass(Ssa::fold_constants_using_constraints, "After Constraint Folding:") + .run_pass(Ssa::dead_instruction_elimination, "After Dead Instruction Elimination:") + .run_pass(Ssa::array_set_optimization, "After Array Set Optimizations:") + .finish(); + + let ssa_level_warnings = ssa.check_for_underconstrained_values(); + let brillig = time("SSA to Brillig", options.print_codegen_timings, || { + ssa.to_brillig(options.enable_brillig_logging) + }); drop(ssa_gen_span_guard); - time("SSA to ACIR", print_timings, || ssa.into_acir(&brillig)) + let artifacts = time("SSA to ACIR", options.print_codegen_timings, || ssa.into_acir(&brillig))?; + Ok(ArtifactsAndWarnings(artifacts, ssa_level_warnings)) } // Helper to time SSA passes @@ -142,19 +154,32 @@ impl SsaProgramArtifact { } self.names.push(circuit_artifact.name); } + + fn add_warnings(&mut self, mut warnings: Vec) { + self.warnings.append(&mut warnings); + } +} + +pub struct SsaEvaluatorOptions { + /// Emit debug information for the intermediate SSA IR + pub enable_ssa_logging: bool, + + pub enable_brillig_logging: bool, + + /// Force Brillig output (for step debugging) + pub force_brillig_output: bool, + + /// Pretty print benchmark times of each code generation pass + pub print_codegen_timings: bool, } /// Compiles the [`Program`] into [`ACIR``][acvm::acir::circuit::Program]. /// /// The output ACIR is backend-agnostic and so must go through a transformation pass before usage in proof generation. -#[allow(clippy::type_complexity)] #[tracing::instrument(level = "trace", skip_all)] pub fn create_program( program: Program, - enable_ssa_logging: bool, - enable_brillig_logging: bool, - force_brillig_output: bool, - print_codegen_timings: bool, + options: &SsaEvaluatorOptions, ) -> Result { let debug_variables = program.debug_variables.clone(); let debug_types = program.debug_types.clone(); @@ -163,13 +188,8 @@ pub fn create_program( let func_sigs = program.function_signatures.clone(); let recursive = program.recursive; - let (generated_acirs, generated_brillig, error_types) = optimize_into_acir( - program, - enable_ssa_logging, - enable_brillig_logging, - force_brillig_output, - print_codegen_timings, - )?; + let ArtifactsAndWarnings((generated_acirs, generated_brillig, error_types), ssa_level_warnings) = + optimize_into_acir(program, options)?; assert_eq!( generated_acirs.len(), func_sigs.len(), @@ -177,6 +197,9 @@ pub fn create_program( ); let mut program_artifact = SsaProgramArtifact::new(generated_brillig, error_types); + + // Add warnings collected at the Ssa stage + program_artifact.add_warnings(ssa_level_warnings); // For setting up the ABI we need separately specify main's input and return witnesses let mut is_main = true; for (acir, func_sig) in generated_acirs.into_iter().zip(func_sigs) { diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs index 56b869fbf6b..74149af25ef 100644 --- a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs +++ b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs @@ -1366,9 +1366,10 @@ impl AcirContext { } _ => (vec![], vec![]), }; - + // Allow constant inputs only for MSM for now + let allow_constant_inputs = name.eq(&BlackBoxFunc::MultiScalarMul); // Convert `AcirVar` to `FunctionInput` - let inputs = self.prepare_inputs_for_black_box_func_call(inputs)?; + let inputs = self.prepare_inputs_for_black_box_func_call(inputs, allow_constant_inputs)?; // Call Black box with `FunctionInput` let mut results = vecmap(&constant_outputs, |c| self.add_constant(*c)); let outputs = self.acir_ir.call_black_box( @@ -1396,18 +1397,23 @@ impl AcirContext { fn prepare_inputs_for_black_box_func_call( &mut self, inputs: Vec, - ) -> Result>, RuntimeError> { + allow_constant_inputs: bool, + ) -> Result>>, RuntimeError> { let mut witnesses = Vec::new(); for input in inputs { let mut single_val_witnesses = Vec::new(); for (input, typ) in self.flatten(input)? { - // Intrinsics only accept Witnesses. This is not a limitation of the - // intrinsics, its just how we have defined things. Ideally, we allow - // constants too. - let witness_var = self.get_or_create_witness_var(input)?; - let witness = self.var_to_witness(witness_var)?; let num_bits = typ.bit_size::(); - single_val_witnesses.push(FunctionInput { witness, num_bits }); + match self.vars[&input].as_constant() { + Some(constant) if allow_constant_inputs => { + single_val_witnesses.push(FunctionInput::constant(*constant, num_bits)); + } + _ => { + let witness_var = self.get_or_create_witness_var(input)?; + let witness = self.var_to_witness(witness_var)?; + single_val_witnesses.push(FunctionInput::witness(witness, num_bits)); + } + } } witnesses.push(single_val_witnesses); } @@ -1547,6 +1553,26 @@ impl AcirContext { brillig_function_index: u32, brillig_stdlib_func: Option, ) -> Result, RuntimeError> { + let predicate = self.var_to_expression(predicate)?; + if predicate.is_zero() { + // If the predicate has a constant value of zero, the brillig call will never be executed. + // We can then immediately zero out all of its outputs as this is the value which would be written + // if we waited until runtime to resolve this call. + let outputs_var = vecmap(outputs, |output| match output { + AcirType::NumericType(_) => { + let var = self.add_constant(F::zero()); + AcirValue::Var(var, output.clone()) + } + AcirType::Array(element_types, size) => { + self.zeroed_array_output(&element_types, size) + } + }); + + return Ok(outputs_var); + } + // Remove "always true" predicates. + let predicate = if predicate == Expression::one() { None } else { Some(predicate) }; + let brillig_inputs: Vec> = try_vecmap(inputs, |i| -> Result<_, InternalError> { match i { @@ -1594,10 +1620,9 @@ impl AcirContext { acir_value } }); - let predicate = self.var_to_expression(predicate)?; self.acir_ir.brillig_call( - Some(predicate), + predicate, generated_brillig, brillig_inputs, brillig_outputs, @@ -1668,6 +1693,27 @@ impl AcirContext { Ok(()) } + /// Recursively create zeroed-out acir values for returned arrays. This is necessary because a brillig returned array can have nested arrays as elements. + fn zeroed_array_output(&mut self, element_types: &[AcirType], size: usize) -> AcirValue { + let mut array_values = im::Vector::new(); + for _ in 0..size { + for element_type in element_types { + match element_type { + AcirType::Array(nested_element_types, nested_size) => { + let nested_acir_value = + self.zeroed_array_output(nested_element_types, *nested_size); + array_values.push_back(nested_acir_value); + } + AcirType::NumericType(_) => { + let var = self.add_constant(F::zero()); + array_values.push_back(AcirValue::Var(var, element_type.clone())); + } + } + } + } + AcirValue::Array(array_values) + } + /// Recursively create acir values for returned arrays. This is necessary because a brillig returned array can have nested arrays as elements. /// A singular array of witnesses is collected for a top level array, by deflattening the assigned witnesses at each level. fn brillig_array_output( @@ -1856,10 +1902,10 @@ impl AcirContext { output_count: usize, predicate: AcirVar, ) -> Result, RuntimeError> { - let inputs = self.prepare_inputs_for_black_box_func_call(inputs)?; + let inputs = self.prepare_inputs_for_black_box_func_call(inputs, false)?; let inputs = inputs .iter() - .flat_map(|input| vecmap(input, |input| input.witness)) + .flat_map(|input| vecmap(input, |input| input.to_witness())) .collect::>(); let outputs = vecmap(0..output_count, |_| self.acir_ir.next_witness_index()); diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs index 9d271f7cd9c..9d29d1d24d6 100644 --- a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs +++ b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs @@ -165,7 +165,7 @@ impl GeneratedAcir { pub(crate) fn call_black_box( &mut self, func_name: BlackBoxFunc, - inputs: &[Vec], + inputs: &[Vec>], constant_inputs: Vec, constant_outputs: Vec, output_count: usize, @@ -439,7 +439,7 @@ impl GeneratedAcir { let inputs = vec![BrilligInputs::Single(expr)]; let outputs = vec![BrilligOutputs::Simple(inverted_witness)]; self.brillig_call( - Some(Expression::one()), + None, &inverse_code, inputs, outputs, @@ -571,7 +571,7 @@ impl GeneratedAcir { }; let constraint = AcirOpcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { - input: FunctionInput { witness, num_bits }, + input: FunctionInput::witness(witness, num_bits), }); self.push_opcode(constraint); @@ -709,6 +709,7 @@ fn black_box_expected_output_size(name: BlackBoxFunc) -> Option { BlackBoxFunc::Poseidon2Permutation => None, BlackBoxFunc::Sha256Compression => Some(8), + // Pedersen commitment returns a point BlackBoxFunc::PedersenCommitment => Some(2), diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs index aaa483b91c9..1bdc9aaf4eb 100644 --- a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -431,6 +431,8 @@ impl<'a> Context<'a> { } warnings.extend(return_warnings); + + // Add the warnings from the alter Ssa passes Ok(self.acir_context.finish(input_witness, return_witnesses, warnings)) } @@ -973,8 +975,18 @@ impl<'a> Context<'a> { .into()) } }; + // Ensure that array id is fully resolved. + let array = dfg.resolve(array); + + let array_id = dfg.resolve(array); + let array_typ = dfg.type_of_value(array_id); + // Compiler sanity checks + assert!(!array_typ.is_nested_slice(), "ICE: Nested slice type has reached ACIR generation"); + let (Type::Array(_, _) | Type::Slice(_)) = &array_typ else { + unreachable!("ICE: expected array or slice type"); + }; - if self.handle_constant_index(instruction, dfg, index, array, store_value)? { + if self.handle_constant_index_wrapper(instruction, dfg, array, index, store_value)? { return Ok(()); } @@ -982,8 +994,6 @@ impl<'a> Context<'a> { // If we find one, we will use it when computing the index under the enable_side_effect predicate // If not, array_get(..) will use a fallback costing one multiplication in the worst case. // cf. https://github.com/noir-lang/noir/pull/4971 - let array_id = dfg.resolve(array); - let array_typ = dfg.type_of_value(array_id); // For simplicity we compute the offset only for simple arrays let is_simple_array = dfg.instruction_results(instruction).len() == 1 && can_omit_element_sizes_array(&array_typ); @@ -1016,83 +1026,91 @@ impl<'a> Context<'a> { Ok(()) } - /// Handle constant index: if there is no predicate and we have the array values, - /// we can perform the operation directly on the array - fn handle_constant_index( + fn handle_constant_index_wrapper( &mut self, instruction: InstructionId, dfg: &DataFlowGraph, + array: ValueId, index: ValueId, - array_id: ValueId, store_value: Option, ) -> Result { - let index_const = dfg.get_numeric_constant(index); - let value_type = dfg.type_of_value(array_id); + let array_id = dfg.resolve(array); + let array_typ = dfg.type_of_value(array_id); // Compiler sanity checks - assert!( - !value_type.is_nested_slice(), - "ICE: Nested slice type has reached ACIR generation" - ); - let (Type::Array(_, _) | Type::Slice(_)) = &value_type else { + assert!(!array_typ.is_nested_slice(), "ICE: Nested slice type has reached ACIR generation"); + let (Type::Array(_, _) | Type::Slice(_)) = &array_typ else { unreachable!("ICE: expected array or slice type"); }; match self.convert_value(array_id, dfg) { AcirValue::Var(acir_var, _) => { - return Err(RuntimeError::InternalError(InternalError::Unexpected { + Err(RuntimeError::InternalError(InternalError::Unexpected { expected: "an array value".to_string(), found: format!("{acir_var:?}"), call_stack: self.acir_context.get_call_stack(), })) } AcirValue::Array(array) => { - if let Some(index_const) = index_const { - let array_size = array.len(); - let index = match index_const.try_to_u64() { - Some(index_const) => index_const as usize, - None => { - let call_stack = self.acir_context.get_call_stack(); - return Err(RuntimeError::TypeConversion { - from: "array index".to_string(), - into: "u64".to_string(), - call_stack, - }); - } - }; - - if self.acir_context.is_constant_one(&self.current_side_effects_enabled_var) { - // Report the error if side effects are enabled. - if index >= array_size { - let call_stack = self.acir_context.get_call_stack(); - return Err(RuntimeError::IndexOutOfBounds { - index, - array_size, - call_stack, - }); - } else { - let value = match store_value { - Some(store_value) => { - let store_value = self.convert_value(store_value, dfg); - AcirValue::Array(array.update(index, store_value)) - } - None => array[index].clone(), - }; - - self.define_result(dfg, instruction, value); - return Ok(true); - } - } - // If there is a predicate and the index is not out of range, we can directly perform the read - else if index < array_size && store_value.is_none() { - self.define_result(dfg, instruction, array[index].clone()); - return Ok(true); - } + // `AcirValue::Array` supports reading/writing to constant indices at compile-time in some cases. + if let Some(constant_index) = dfg.get_numeric_constant(index) { + let store_value = store_value.map(|value| self.convert_value(value, dfg)); + self.handle_constant_index(instruction, dfg, array, constant_index, store_value) + } else { + Ok(false) } } - AcirValue::DynamicArray(_) => (), + AcirValue::DynamicArray(_) => Ok(false), + } + } + + /// Handle constant index: if there is no predicate and we have the array values, + /// we can perform the operation directly on the array + fn handle_constant_index( + &mut self, + instruction: InstructionId, + dfg: &DataFlowGraph, + array: Vector, + index: FieldElement, + store_value: Option, + ) -> Result { + let array_size: usize = array.len(); + let index = match index.try_to_u64() { + Some(index_const) => index_const as usize, + None => { + let call_stack = self.acir_context.get_call_stack(); + return Err(RuntimeError::TypeConversion { + from: "array index".to_string(), + into: "u64".to_string(), + call_stack, + }); + } }; - Ok(false) + if index >= array_size { + return Ok(false); + } + + if let Some(store_value) = store_value { + let side_effects_always_enabled = + self.acir_context.is_constant_one(&self.current_side_effects_enabled_var); + + if side_effects_always_enabled { + // If we know that this write will always occur then we can perform it at compile time. + let value = AcirValue::Array(array.update(index, store_value)); + self.define_result(dfg, instruction, value); + Ok(true) + } else { + // If a predicate is applied however we must wait until runtime. + Ok(false) + } + } else { + // If the index is not out of range, we can optimistically perform the read at compile time + // as if the predicate were true. This is as if the predicate were to resolve to false then + // the result should not affect the rest of circuit execution. + let value = array[index].clone(); + self.define_result(dfg, instruction, value); + Ok(true) + } } /// We need to properly setup the inputs for array operations in ACIR. @@ -1106,13 +1124,14 @@ impl<'a> Context<'a> { /// It is a dummy value because in the case of a false predicate, the value stored at the requested index will be itself. fn convert_array_operation_inputs( &mut self, - array: ValueId, + array_id: ValueId, dfg: &DataFlowGraph, index: ValueId, store_value: Option, offset: usize, ) -> Result<(AcirVar, Option), RuntimeError> { - let (array_id, array_typ, block_id) = self.check_array_is_initialized(array, dfg)?; + let array_typ = dfg.type_of_value(array_id); + let block_id = self.ensure_array_is_initialized(array_id, dfg)?; let index_var = self.convert_numeric_value(index, dfg)?; let index_var = self.get_flattened_index(&array_typ, array_id, index_var, dfg)?; @@ -1231,22 +1250,22 @@ impl<'a> Context<'a> { dfg: &DataFlowGraph, mut index_side_effect: bool, ) -> Result { - let (array_id, _, block_id) = self.check_array_is_initialized(array, dfg)?; + let block_id = self.ensure_array_is_initialized(array, dfg)?; let results = dfg.instruction_results(instruction); let res_typ = dfg.type_of_value(results[0]); // Get operations to call-data parameters are replaced by a get to the call-data-bus array if let Some(call_data) = self.data_bus.call_data { - if self.data_bus.call_data_map.contains_key(&array_id) { + if self.data_bus.call_data_map.contains_key(&array) { // TODO: the block_id of call-data must be notified to the backend // TODO: should we do the same for return-data? let type_size = res_typ.flattened_size(); let type_size = self.acir_context.add_constant(FieldElement::from(type_size as i128)); let offset = self.acir_context.mul_var(var_index, type_size)?; - let bus_index = self.acir_context.add_constant(FieldElement::from( - self.data_bus.call_data_map[&array_id] as i128, - )); + let bus_index = self + .acir_context + .add_constant(FieldElement::from(self.data_bus.call_data_map[&array] as i128)); let new_index = self.acir_context.add_var(offset, bus_index)?; return self.array_get(instruction, call_data, new_index, dfg, index_side_effect); } @@ -1260,8 +1279,7 @@ impl<'a> Context<'a> { let mut value = self.array_get_value(&res_typ, block_id, &mut var_index)?; if let AcirValue::Var(value_var, typ) = &value { - let array_id = dfg.resolve(array_id); - let array_typ = dfg.type_of_value(array_id); + let array_typ = dfg.type_of_value(array); if let (Type::Numeric(numeric_type), AcirType::NumericType(num)) = (array_typ.first(), typ) { @@ -1345,7 +1363,7 @@ impl<'a> Context<'a> { } }; - let (array_id, array_typ, block_id) = self.check_array_is_initialized(array, dfg)?; + let block_id = self.ensure_array_is_initialized(array, dfg)?; // Every array has a length in its type, so we fetch that from // the SSA IR. @@ -1354,10 +1372,11 @@ impl<'a> Context<'a> { // However, this size is simply the capacity of a slice. The capacity is dependent upon the witness // and may contain data for which we want to restrict access. The true slice length is tracked in a // a separate SSA value and restrictions on slice indices should be generated elsewhere in the SSA. + let array_typ = dfg.type_of_value(array); let array_len = if !array_typ.contains_slice_element() { array_typ.flattened_size() } else { - self.flattened_slice_size(array_id, dfg) + self.flattened_slice_size(array, dfg) }; // Since array_set creates a new array, we create a new block ID for this @@ -1380,18 +1399,13 @@ impl<'a> Context<'a> { self.array_set_value(&store_value, result_block_id, &mut var_index)?; let element_type_sizes = if !can_omit_element_sizes_array(&array_typ) { - let acir_value = self.convert_value(array_id, dfg); - Some(self.init_element_type_sizes_array( - &array_typ, - array_id, - Some(&acir_value), - dfg, - )?) + let acir_value = self.convert_value(array, dfg); + Some(self.init_element_type_sizes_array(&array_typ, array, Some(&acir_value), dfg)?) } else { None }; - let value_types = self.convert_value(array_id, dfg).flat_numeric_types(); + let value_types = self.convert_value(array, dfg).flat_numeric_types(); // Compiler sanity check assert_eq!(value_types.len(), array_len, "ICE: The length of the flattened type array should match the length of the dynamic array"); @@ -1437,37 +1451,33 @@ impl<'a> Context<'a> { Ok(()) } - fn check_array_is_initialized( + fn ensure_array_is_initialized( &mut self, array: ValueId, dfg: &DataFlowGraph, - ) -> Result<(ValueId, Type, BlockId), RuntimeError> { - // Fetch the internal SSA ID for the array - let array_id = dfg.resolve(array); - - let array_typ = dfg.type_of_value(array_id); - + ) -> Result { // Use the SSA ID to get or create its block ID - let block_id = self.block_id(&array_id); + let block_id = self.block_id(&array); // Check if the array has already been initialized in ACIR gen // if not, we initialize it using the values from SSA let already_initialized = self.initialized_arrays.contains(&block_id); if !already_initialized { - let value = &dfg[array_id]; + let value = &dfg[array]; match value { Value::Array { .. } | Value::Instruction { .. } => { - let value = self.convert_value(array_id, dfg); + let value = self.convert_value(array, dfg); + let array_typ = dfg.type_of_value(array); let len = if !array_typ.contains_slice_element() { array_typ.flattened_size() } else { - self.flattened_slice_size(array_id, dfg) + self.flattened_slice_size(array, dfg) }; self.initialize_array(block_id, len, Some(value))?; } _ => { return Err(InternalError::General { - message: format!("Array {array_id} should be initialized"), + message: format!("Array {array} should be initialized"), call_stack: self.acir_context.get_call_stack(), } .into()); @@ -1475,7 +1485,7 @@ impl<'a> Context<'a> { } } - Ok((array_id, array_typ, block_id)) + Ok(block_id) } fn init_element_type_sizes_array( @@ -1729,7 +1739,7 @@ impl<'a> Context<'a> { /// Converts an SSA terminator's return values into their ACIR representations fn get_num_return_witnesses( - &mut self, + &self, terminator: &TerminatorInstruction, dfg: &DataFlowGraph, ) -> usize { @@ -1783,7 +1793,7 @@ impl<'a> Context<'a> { has_constant_return |= self.acir_context.is_constant(&acir_var); if is_databus { // We do not return value for the data bus. - self.check_array_is_initialized( + self.ensure_array_is_initialized( self.data_bus.return_data.expect( "`is_databus == true` implies `data_bus.return_data` is `Some`", ), @@ -2150,8 +2160,9 @@ impl<'a> Context<'a> { Ok(vec![AcirValue::Var(self.acir_context.add_constant(len), AcirType::field())]) } Intrinsic::AsSlice => { - let (slice_contents, slice_typ, block_id) = - self.check_array_is_initialized(arguments[0], dfg)?; + let slice_contents = arguments[0]; + let slice_typ = dfg.type_of_value(slice_contents); + let block_id = self.ensure_array_is_initialized(slice_contents, dfg)?; assert!(!slice_typ.is_nested_slice(), "ICE: Nested slice used in ACIR generation"); let result_block_id = self.block_id(&result_ids[1]); @@ -2195,8 +2206,9 @@ impl<'a> Context<'a> { Intrinsic::SlicePushBack => { // arguments = [slice_length, slice_contents, ...elements_to_push] let slice_length = self.convert_value(arguments[0], dfg).into_var()?; - let (slice_contents, slice_typ, _) = - self.check_array_is_initialized(arguments[1], dfg)?; + let slice_contents = arguments[1]; + let slice_typ = dfg.type_of_value(slice_contents); + assert!(!slice_typ.is_nested_slice(), "ICE: Nested slice used in ACIR generation"); let slice = self.convert_value(slice_contents, dfg); @@ -2262,9 +2274,8 @@ impl<'a> Context<'a> { Intrinsic::SlicePushFront => { // arguments = [slice_length, slice_contents, ...elements_to_push] let slice_length = self.convert_value(arguments[0], dfg).into_var()?; - - let (slice_contents, slice_typ, _) = - self.check_array_is_initialized(arguments[1], dfg)?; + let slice_contents = arguments[1]; + let slice_typ = dfg.type_of_value(slice_contents); assert!(!slice_typ.is_nested_slice(), "ICE: Nested slice used in ACIR generation"); let slice: AcirValue = self.convert_value(slice_contents, dfg); @@ -2327,6 +2338,7 @@ impl<'a> Context<'a> { Intrinsic::SlicePopBack => { // arguments = [slice_length, slice_contents] let slice_length = self.convert_value(arguments[0], dfg).into_var()?; + let slice_contents = arguments[1]; let one = self.acir_context.add_constant(FieldElement::one()); let new_slice_length = self.acir_context.sub_var(slice_length, one)?; @@ -2335,8 +2347,8 @@ impl<'a> Context<'a> { // the elements stored at that index will no longer be able to be accessed. let mut var_index = new_slice_length; - let (slice_contents, slice_typ, block_id) = - self.check_array_is_initialized(arguments[1], dfg)?; + let slice_typ = dfg.type_of_value(slice_contents); + let block_id = self.ensure_array_is_initialized(slice_contents, dfg)?; assert!(!slice_typ.is_nested_slice(), "ICE: Nested slice used in ACIR generation"); let mut popped_elements = Vec::new(); @@ -2361,9 +2373,11 @@ impl<'a> Context<'a> { Intrinsic::SlicePopFront => { // arguments = [slice_length, slice_contents] let slice_length = self.convert_value(arguments[0], dfg).into_var()?; + let slice_contents = arguments[1]; + + let slice_typ = dfg.type_of_value(slice_contents); + let block_id = self.ensure_array_is_initialized(slice_contents, dfg)?; - let (slice_contents, slice_typ, block_id) = - self.check_array_is_initialized(arguments[1], dfg)?; assert!(!slice_typ.is_nested_slice(), "ICE: Nested slice used in ACIR generation"); let one = self.acir_context.add_constant(FieldElement::one()); @@ -2402,9 +2416,11 @@ impl<'a> Context<'a> { Intrinsic::SliceInsert => { // arguments = [slice_length, slice_contents, insert_index, ...elements_to_insert] let slice_length = self.convert_value(arguments[0], dfg).into_var()?; + let slice_contents = arguments[1]; + + let slice_typ = dfg.type_of_value(slice_contents); + let block_id = self.ensure_array_is_initialized(slice_contents, dfg)?; - let (slice_contents, slice_typ, block_id) = - self.check_array_is_initialized(arguments[1], dfg)?; assert!(!slice_typ.is_nested_slice(), "ICE: Nested slice used in ACIR generation"); let slice = self.convert_value(slice_contents, dfg); @@ -2541,9 +2557,11 @@ impl<'a> Context<'a> { Intrinsic::SliceRemove => { // arguments = [slice_length, slice_contents, remove_index] let slice_length = self.convert_value(arguments[0], dfg).into_var()?; + let slice_contents = arguments[1]; + + let slice_typ = dfg.type_of_value(slice_contents); + let block_id = self.ensure_array_is_initialized(slice_contents, dfg)?; - let (slice_contents, slice_typ, block_id) = - self.check_array_is_initialized(arguments[1], dfg)?; assert!(!slice_typ.is_nested_slice(), "ICE: Nested slice used in ACIR generation"); let slice = self.convert_value(slice_contents, dfg); diff --git a/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs b/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs new file mode 100644 index 00000000000..5831faa7c4d --- /dev/null +++ b/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs @@ -0,0 +1,411 @@ +//! This module defines an SSA pass that detects if the final function has any subgraphs independent from inputs and outputs. +//! If this is the case, then part of the final circuit can be completely replaced by any other passing circuit, since there are no constraints ensuring connections. +//! So the compiler informs the developer of this as a bug +use im::HashMap; + +use crate::errors::{InternalBug, SsaReport}; +use crate::ssa::ir::basic_block::BasicBlockId; +use crate::ssa::ir::function::RuntimeType; +use crate::ssa::ir::function::{Function, FunctionId}; +use crate::ssa::ir::instruction::{Instruction, InstructionId, Intrinsic}; +use crate::ssa::ir::value::{Value, ValueId}; +use crate::ssa::ssa_gen::Ssa; +use std::collections::{BTreeMap, HashSet}; + +impl Ssa { + /// Go through each top-level non-brillig function and detect if it has independent subgraphs + #[tracing::instrument(level = "trace", skip(self))] + pub(crate) fn check_for_underconstrained_values(&mut self) -> Vec { + let mut warnings: Vec = Vec::new(); + for function in self.functions.values() { + match function.runtime() { + RuntimeType::Acir { .. } => { + warnings.extend(check_for_underconstrained_values_within_function( + function, + &self.functions, + )); + } + RuntimeType::Brillig => (), + } + } + warnings + } +} + +/// Detect independent subgraphs (not connected to function inputs or outputs) and return a vector of bug reports if some are found +fn check_for_underconstrained_values_within_function( + function: &Function, + all_functions: &BTreeMap, +) -> Vec { + let mut context = Context::default(); + let mut warnings: Vec = Vec::new(); + + context.compute_sets_of_connected_value_ids(function, all_functions); + + let all_brillig_generated_values: HashSet = + context.brillig_return_to_argument.keys().copied().collect(); + + let connected_sets_indices = + context.find_sets_connected_to_function_inputs_or_outputs(function); + + // Go through each disconnected set, find brillig calls that caused it and form warnings + for set_index in + HashSet::from_iter(0..(context.value_sets.len())).difference(&connected_sets_indices) + { + let current_set = &context.value_sets[*set_index]; + warnings.append(&mut context.find_disconnecting_brillig_calls_with_results_in_set( + current_set, + &all_brillig_generated_values, + function, + )); + } + warnings +} +#[derive(Default)] +struct Context { + visited_blocks: HashSet, + block_queue: Vec, + value_sets: Vec>, + brillig_return_to_argument: HashMap>, + brillig_return_to_instruction_id: HashMap, +} + +impl Context { + /// Compute sets of variable ValueIds that are connected with constraints + /// + /// Additionally, store information about brillig calls in the context + fn compute_sets_of_connected_value_ids( + &mut self, + function: &Function, + all_functions: &BTreeMap, + ) { + // Go through each block in the function and create a list of sets of ValueIds connected by instructions + self.block_queue.push(function.entry_block()); + while let Some(block) = self.block_queue.pop() { + if self.visited_blocks.contains(&block) { + continue; + } + self.visited_blocks.insert(block); + self.connect_value_ids_in_block(function, block, all_functions); + } + + // Merge ValueIds into sets, where each original small set of ValueIds is merged with another set if they intersect + self.merge_sets(); + } + + /// Find sets that contain input or output value of the function + /// + /// Goes through each set of connected ValueIds and see if function arguments or return values are in the set + fn find_sets_connected_to_function_inputs_or_outputs( + &mut self, + function: &Function, + ) -> HashSet { + let variable_parameters_and_return_values = function + .parameters() + .iter() + .chain(function.returns()) + .filter(|id| function.dfg.get_numeric_constant(**id).is_none()) + .map(|value_id| function.dfg.resolve(*value_id)); + + let mut connected_sets_indices: HashSet = HashSet::new(); + + // Go through each parameter and each set and check if the set contains the parameter + // If it's the case, then that set doesn't present an issue + for parameter_or_return_value in variable_parameters_and_return_values { + for (set_index, final_set) in self.value_sets.iter().enumerate() { + if final_set.contains(¶meter_or_return_value) { + connected_sets_indices.insert(set_index); + } + } + } + connected_sets_indices + } + + /// Find which brillig calls separate this set from others and return bug warnings about them + fn find_disconnecting_brillig_calls_with_results_in_set( + &self, + current_set: &HashSet, + all_brillig_generated_values: &HashSet, + function: &Function, + ) -> Vec { + let mut warnings = Vec::new(); + // Find brillig-generated values in the set + let intersection = all_brillig_generated_values.intersection(current_set).copied(); + + // Go through all brillig outputs in the set + for brillig_output_in_set in intersection { + // Get the inputs that correspond to the output + let inputs: HashSet = + self.brillig_return_to_argument[&brillig_output_in_set].iter().copied().collect(); + + // Check if any of them are not in the set + let unused_inputs = inputs.difference(current_set).next().is_some(); + + // There is a value not in the set, which means that the inputs/outputs of this call have not been properly constrained + if unused_inputs { + warnings.push(SsaReport::Bug(InternalBug::IndependentSubgraph { + call_stack: function.dfg.get_call_stack( + self.brillig_return_to_instruction_id[&brillig_output_in_set], + ), + })); + } + } + warnings + } + /// Go through each instruction in the block and add a set of ValueIds connected through that instruction + /// + /// Additionally, this function adds mappings of brillig return values to call arguments and instruction ids from calls to brillig functions in the block + fn connect_value_ids_in_block( + &mut self, + function: &Function, + block: BasicBlockId, + all_functions: &BTreeMap, + ) { + let instructions = function.dfg[block].instructions(); + + for instruction in instructions.iter() { + let mut instruction_arguments_and_results = HashSet::new(); + + // Insert non-constant instruction arguments + function.dfg[*instruction].for_each_value(|value_id| { + if function.dfg.get_numeric_constant(value_id).is_none() { + instruction_arguments_and_results.insert(function.dfg.resolve(value_id)); + } + }); + // And non-constant results + for value_id in function.dfg.instruction_results(*instruction).iter() { + if function.dfg.get_numeric_constant(*value_id).is_none() { + instruction_arguments_and_results.insert(function.dfg.resolve(*value_id)); + } + } + + // For most instructions we just connect inputs and outputs + match &function.dfg[*instruction] { + Instruction::ArrayGet { .. } + | Instruction::ArraySet { .. } + | Instruction::Binary(..) + | Instruction::Cast(..) + | Instruction::Constrain(..) + | Instruction::IfElse { .. } + | Instruction::Load { .. } + | Instruction::Not(..) + | Instruction::Store { .. } + | Instruction::Truncate { .. } => { + self.value_sets.push(instruction_arguments_and_results); + } + + Instruction::Call { func: func_id, arguments: argument_ids } => { + match &function.dfg[*func_id] { + Value::Intrinsic(intrinsic) => match intrinsic { + Intrinsic::ApplyRangeConstraint + | Intrinsic::AssertConstant + | Intrinsic::AsWitness + | Intrinsic::IsUnconstrained => {} + Intrinsic::ArrayLen + | Intrinsic::AsField + | Intrinsic::AsSlice + | Intrinsic::BlackBox(..) + | Intrinsic::DerivePedersenGenerators + | Intrinsic::FromField + | Intrinsic::SlicePushBack + | Intrinsic::SlicePushFront + | Intrinsic::SlicePopBack + | Intrinsic::SlicePopFront + | Intrinsic::SliceInsert + | Intrinsic::SliceRemove + | Intrinsic::StaticAssert + | Intrinsic::StrAsBytes + | Intrinsic::ToBits(..) + | Intrinsic::ToRadix(..) => { + self.value_sets.push(instruction_arguments_and_results); + } + }, + Value::Function(callee) => match all_functions[&callee].runtime() { + RuntimeType::Brillig => { + // For calls to brillig functions we memorize the mapping of results to argument ValueId's and InstructionId's + // The latter are needed to produce the callstack later + for result in + function.dfg.instruction_results(*instruction).iter().filter( + |value_id| { + function.dfg.get_numeric_constant(**value_id).is_none() + }, + ) + { + self.brillig_return_to_argument + .insert(*result, argument_ids.clone()); + self.brillig_return_to_instruction_id + .insert(*result, *instruction); + } + } + RuntimeType::Acir(..) => { + self.value_sets.push(instruction_arguments_and_results); + } + }, + Value::ForeignFunction(..) => { + panic!("Should not be able to reach foreign function from non-brillig functions"); + } + Value::Array { .. } + | Value::Instruction { .. } + | Value::NumericConstant { .. } + | Value::Param { .. } => { + panic!("At the point we are running disconnect there shouldn't be any other values as arguments") + } + } + } + Instruction::Allocate { .. } + | Instruction::DecrementRc { .. } + | Instruction::EnableSideEffects { .. } + | Instruction::IncrementRc { .. } + | Instruction::RangeCheck { .. } => {} + } + } + + self.block_queue.extend(function.dfg[block].successors()); + } + + /// Merge all small sets into larger ones based on whether the sets intersect or not + /// + /// If two small sets have a common ValueId, we merge them into one + fn merge_sets(&mut self) { + let mut new_set_id: usize = 0; + let mut updated_sets: HashMap> = HashMap::new(); + let mut value_dictionary: HashMap = HashMap::new(); + let mut parsed_value_set: HashSet = HashSet::new(); + + // Go through each set + for set in self.value_sets.iter() { + // Check if the set has any of the ValueIds we've encountered at previous iterations + let intersection: HashSet = + set.intersection(&parsed_value_set).copied().collect(); + parsed_value_set.extend(set.iter()); + + // If there is no intersection, add the new set to updated sets + if intersection.is_empty() { + updated_sets.insert(new_set_id, set.clone()); + + // Add each entry to a dictionary for quick lookups of which ValueId is in which updated set + for entry in set.iter() { + value_dictionary.insert(*entry, new_set_id); + } + new_set_id += 1; + continue; + } + + // If there is an intersection, we have to join the sets + let mut joining_sets_ids: HashSet = + intersection.iter().map(|x| value_dictionary[x]).collect(); + let mut largest_set_size = usize::MIN; + let mut largest_set_index = usize::MAX; + + // We need to find the largest set to move as few elements as possible + for set_id in joining_sets_ids.iter() { + if updated_sets[set_id].len() > largest_set_size { + (largest_set_index, largest_set_size) = (*set_id, updated_sets[set_id].len()); + } + } + joining_sets_ids.remove(&largest_set_index); + + let mut largest_set = + updated_sets.extract(&largest_set_index).expect("Set should be in the hashmap").0; + + // For each of other sets that need to be joined + for set_id in joining_sets_ids.iter() { + // Map each element to the largest set and insert it + for element in updated_sets[set_id].iter() { + value_dictionary[element] = largest_set_index; + largest_set.insert(*element); + } + // Remove the old set + updated_sets.remove(set_id); + } + + // Join the new set with the largest sets + for element in set.iter() { + value_dictionary.insert(*element, largest_set_index); + largest_set.insert(*element); + } + updated_sets.insert(largest_set_index, largest_set); + } + self.value_sets = updated_sets.values().cloned().collect(); + } +} +#[cfg(test)] +mod test { + use crate::ssa::{ + function_builder::FunctionBuilder, + ir::{instruction::BinaryOp, map::Id, types::Type}, + }; + + #[test] + /// Test that a connected function raises no warnings + fn test_simple_connected_function() { + // fn main { + // b0(v0: Field, v1: Field): + // v2 = add v0, 1 + // v3 = mul v1, 2 + // v4 = eq v2, v3 + // return v2 + // } + let main_id = Id::test_new(0); + let mut builder = FunctionBuilder::new("main".into(), main_id); + let v0 = builder.add_parameter(Type::field()); + let v1 = builder.add_parameter(Type::field()); + + let one = builder.field_constant(1u128); + let two = builder.field_constant(2u128); + + let v2 = builder.insert_binary(v0, BinaryOp::Add, one); + let v3 = builder.insert_binary(v1, BinaryOp::Mul, two); + let _v4 = builder.insert_binary(v2, BinaryOp::Eq, v3); + builder.terminate_with_return(vec![v2]); + + let mut ssa = builder.finish(); + let ssa_level_warnings = ssa.check_for_underconstrained_values(); + assert_eq!(ssa_level_warnings.len(), 0); + } + + #[test] + /// Test where the results of a call to a brillig function are not connected to main function inputs or outputs + /// This should be detected. + fn test_simple_function_with_disconnected_part() { + // unconstrained fn br(v0: Field, v1: Field){ + // v2 = add v0, v1 + // return v2 + // } + // + // fn main { + // b0(v0: Field, v1: Field): + // v2 = add v0, 1 + // v3 = mul v1, 2 + // v4 = call br(v2, v3) + // v5 = add v4, 2 + // return + // } + let main_id = Id::test_new(0); + let mut builder = FunctionBuilder::new("main".into(), main_id); + let v0 = builder.add_parameter(Type::field()); + let v1 = builder.add_parameter(Type::field()); + + let one = builder.field_constant(1u128); + let two = builder.field_constant(2u128); + + let v2 = builder.insert_binary(v0, BinaryOp::Add, one); + let v3 = builder.insert_binary(v1, BinaryOp::Mul, two); + + let br_function_id = Id::test_new(1); + let br_function = builder.import_function(br_function_id); + let v4 = builder.insert_call(br_function, vec![v2, v3], vec![Type::field()])[0]; + let v5 = builder.insert_binary(v4, BinaryOp::Add, two); + builder.insert_constrain(v5, one, None); + builder.terminate_with_return(vec![]); + + builder.new_brillig_function("br".into(), br_function_id); + let v0 = builder.add_parameter(Type::field()); + let v1 = builder.add_parameter(Type::field()); + let v2 = builder.insert_binary(v0, BinaryOp::Add, v1); + builder.terminate_with_return(vec![v2]); + let mut ssa = builder.finish(); + let ssa_level_warnings = ssa.check_for_underconstrained_values(); + assert_eq!(ssa_level_warnings.len(), 1); + } +} diff --git a/compiler/noirc_evaluator/src/ssa/checks/mod.rs b/compiler/noirc_evaluator/src/ssa/checks/mod.rs new file mode 100644 index 00000000000..4f1831e5bb0 --- /dev/null +++ b/compiler/noirc_evaluator/src/ssa/checks/mod.rs @@ -0,0 +1 @@ +mod check_for_underconstrained_values; diff --git a/compiler/noirc_evaluator/src/ssa/ir/dfg.rs b/compiler/noirc_evaluator/src/ssa/ir/dfg.rs index f763ae52d50..994386f8197 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/dfg.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/dfg.rs @@ -497,9 +497,22 @@ impl DataFlowGraph { } } - /// True if the given ValueId refers to a constant value + /// True if the given ValueId refers to a (recursively) constant value pub(crate) fn is_constant(&self, argument: ValueId) -> bool { - !matches!(&self[self.resolve(argument)], Value::Instruction { .. } | Value::Param { .. }) + match &self[self.resolve(argument)] { + Value::Instruction { .. } | Value::Param { .. } => false, + Value::Array { array, .. } => array.iter().all(|element| self.is_constant(*element)), + _ => true, + } + } + + /// True that the input is a non-zero `Value::NumericConstant` + pub(crate) fn is_constant_true(&self, argument: ValueId) -> bool { + if let Some(constant) = self.get_numeric_constant(argument) { + !constant.is_zero() + } else { + false + } } } diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs index e21deb9ef79..8cbae732ef9 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs @@ -52,6 +52,7 @@ pub(crate) enum Intrinsic { ArrayLen, AsSlice, AssertConstant, + StaticAssert, SlicePushBack, SlicePushFront, SlicePopBack, @@ -67,6 +68,7 @@ pub(crate) enum Intrinsic { AsField, AsWitness, IsUnconstrained, + DerivePedersenGenerators, } impl std::fmt::Display for Intrinsic { @@ -75,6 +77,7 @@ impl std::fmt::Display for Intrinsic { Intrinsic::ArrayLen => write!(f, "array_len"), Intrinsic::AsSlice => write!(f, "as_slice"), Intrinsic::AssertConstant => write!(f, "assert_constant"), + Intrinsic::StaticAssert => write!(f, "static_assert"), Intrinsic::SlicePushBack => write!(f, "slice_push_back"), Intrinsic::SlicePushFront => write!(f, "slice_push_front"), Intrinsic::SlicePopBack => write!(f, "slice_pop_back"), @@ -92,6 +95,7 @@ impl std::fmt::Display for Intrinsic { Intrinsic::AsField => write!(f, "as_field"), Intrinsic::AsWitness => write!(f, "as_witness"), Intrinsic::IsUnconstrained => write!(f, "is_unconstrained"), + Intrinsic::DerivePedersenGenerators => write!(f, "derive_pedersen_generators"), } } } @@ -102,9 +106,10 @@ impl Intrinsic { /// If there are no side effects then the `Intrinsic` can be removed if the result is unused. pub(crate) fn has_side_effects(&self) -> bool { match self { - Intrinsic::AssertConstant | Intrinsic::ApplyRangeConstraint | Intrinsic::AsWitness => { - true - } + Intrinsic::AssertConstant + | Intrinsic::StaticAssert + | Intrinsic::ApplyRangeConstraint + | Intrinsic::AsWitness => true, // These apply a constraint that the input must fit into a specified number of limbs. Intrinsic::ToBits(_) | Intrinsic::ToRadix(_) => true, @@ -120,7 +125,8 @@ impl Intrinsic { | Intrinsic::StrAsBytes | Intrinsic::FromField | Intrinsic::AsField - | Intrinsic::IsUnconstrained => false, + | Intrinsic::IsUnconstrained + | Intrinsic::DerivePedersenGenerators => false, // Some black box functions have side-effects Intrinsic::BlackBox(func) => matches!( @@ -139,6 +145,7 @@ impl Intrinsic { "array_len" => Some(Intrinsic::ArrayLen), "as_slice" => Some(Intrinsic::AsSlice), "assert_constant" => Some(Intrinsic::AssertConstant), + "static_assert" => Some(Intrinsic::StaticAssert), "apply_range_constraint" => Some(Intrinsic::ApplyRangeConstraint), "slice_push_back" => Some(Intrinsic::SlicePushBack), "slice_push_front" => Some(Intrinsic::SlicePushFront), @@ -155,6 +162,7 @@ impl Intrinsic { "as_field" => Some(Intrinsic::AsField), "as_witness" => Some(Intrinsic::AsWitness), "is_unconstrained" => Some(Intrinsic::IsUnconstrained), + "derive_pedersen_generators" => Some(Intrinsic::DerivePedersenGenerators), other => BlackBoxFunc::lookup(other).map(Intrinsic::BlackBox), } } @@ -280,25 +288,33 @@ impl Instruction { } /// Indicates if the instruction can be safely replaced with the results of another instruction with the same inputs. - pub(crate) fn can_be_deduplicated(&self, dfg: &DataFlowGraph) -> bool { + /// If `deduplicate_with_predicate` is set, we assume we're deduplicating with the instruction + /// and its predicate, rather than just the instruction. Setting this means instructions that + /// rely on predicates can be deduplicated as well. + pub(crate) fn can_be_deduplicated( + &self, + dfg: &DataFlowGraph, + deduplicate_with_predicate: bool, + ) -> bool { use Instruction::*; match self { // These either have side-effects or interact with memory - Constrain(..) - | EnableSideEffects { .. } + EnableSideEffects { .. } | Allocate | Load { .. } | Store { .. } | IncrementRc { .. } - | DecrementRc { .. } - | RangeCheck { .. } => false, + | DecrementRc { .. } => false, Call { func, .. } => match dfg[*func] { Value::Intrinsic(intrinsic) => !intrinsic.has_side_effects(), _ => false, }, + // We can deduplicate these instructions if we know the predicate is also the same. + Constrain(..) | RangeCheck { .. } => deduplicate_with_predicate, + // These can have different behavior depending on the EnableSideEffectsIf context. // Replacing them with a similar instruction potentially enables replacing an instruction // with one that was disabled. See @@ -309,7 +325,9 @@ impl Instruction { | Truncate { .. } | IfElse { .. } | ArrayGet { .. } - | ArraySet { .. } => !self.requires_acir_gen_predicate(dfg), + | ArraySet { .. } => { + deduplicate_with_predicate || !self.requires_acir_gen_predicate(dfg) + } } } @@ -364,16 +382,24 @@ impl Instruction { } /// If true the instruction will depends on enable_side_effects context during acir-gen - fn requires_acir_gen_predicate(&self, dfg: &DataFlowGraph) -> bool { + pub(crate) fn requires_acir_gen_predicate(&self, dfg: &DataFlowGraph) -> bool { match self { Instruction::Binary(binary) if matches!(binary.operator, BinaryOp::Div | BinaryOp::Mod) => { true } - Instruction::EnableSideEffects { .. } - | Instruction::ArrayGet { .. } - | Instruction::ArraySet { .. } => true, + + // `ArrayGet`s which read from "known good" indices from an array don't need a predicate. + Instruction::ArrayGet { array, index } => { + #[allow(clippy::match_like_matches_macro)] + match (dfg.type_of_value(*array), dfg.get_numeric_constant(*index)) { + (Type::Array(_, len), Some(index)) if index.to_u128() < (len as u128) => false, + _ => true, + } + } + + Instruction::EnableSideEffects { .. } | Instruction::ArraySet { .. } => true, Instruction::Call { func, .. } => match dfg[*func] { Value::Function(_) => true, diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction/binary.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction/binary.rs index dbb717b0a10..fba727ca226 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction/binary.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction/binary.rs @@ -329,7 +329,7 @@ fn eval_constant_binary_op( } let result = function(lhs, rhs)?; // Check for overflow - if result >= 2u128.pow(*bit_size) { + if result >= 1 << *bit_size { return None; } result.into() @@ -337,12 +337,8 @@ fn eval_constant_binary_op( Type::Numeric(NumericType::Signed { bit_size }) => { let function = operator.get_i128_function(); - let lhs = truncate(lhs.try_into_u128()?, *bit_size); - let rhs = truncate(rhs.try_into_u128()?, *bit_size); - let l_pos = lhs < 2u128.pow(bit_size - 1); - let r_pos = rhs < 2u128.pow(bit_size - 1); - let lhs = if l_pos { lhs as i128 } else { -((2u128.pow(*bit_size) - lhs) as i128) }; - let rhs = if r_pos { rhs as i128 } else { -((2u128.pow(*bit_size) - rhs) as i128) }; + let lhs = try_convert_field_element_to_signed_integer(lhs, *bit_size)?; + let rhs = try_convert_field_element_to_signed_integer(rhs, *bit_size)?; // The divisor is being truncated into the type of the operand, which can potentially // lead to the rhs being zero. // If the rhs of a division is zero, attempting to evaluate the division will cause a compiler panic. @@ -354,12 +350,11 @@ fn eval_constant_binary_op( let result = function(lhs, rhs)?; // Check for overflow - if result >= 2i128.pow(*bit_size - 1) || result < -(2i128.pow(*bit_size - 1)) { + let two_pow_bit_size_minus_one = 1i128 << (*bit_size - 1); + if result >= two_pow_bit_size_minus_one || result < -two_pow_bit_size_minus_one { return None; } - let result = - if result >= 0 { result as u128 } else { (2i128.pow(*bit_size) + result) as u128 }; - result.into() + convert_signed_integer_to_field_element(result, *bit_size) } _ => return None, }; @@ -371,8 +366,37 @@ fn eval_constant_binary_op( Some((value, operand_type)) } +/// Values in the range `[0, 2^(bit_size-1))` are interpreted as positive integers +/// +/// Values in the range `[2^(bit_size-1), 2^bit_size)` are interpreted as negative integers. +fn try_convert_field_element_to_signed_integer(field: FieldElement, bit_size: u32) -> Option { + let unsigned_int = truncate(field.try_into_u128()?, bit_size); + + let max_positive_value = 1 << (bit_size - 1); + let is_positive = unsigned_int < max_positive_value; + + let signed_int = if is_positive { + unsigned_int as i128 + } else { + let x = (1u128 << bit_size) - unsigned_int; + -(x as i128) + }; + + Some(signed_int) +} + +fn convert_signed_integer_to_field_element(int: i128, bit_size: u32) -> FieldElement { + if int >= 0 { + FieldElement::from(int) + } else { + // We add an offset of `bit_size` bits to shift the negative values into the range [2^(bitsize-1), 2^bitsize) + let offset_int = (1i128 << bit_size) + int; + FieldElement::from(offset_int) + } +} + fn truncate(int: u128, bit_size: u32) -> u128 { - let max = 2u128.pow(bit_size); + let max = 1 << bit_size; int % max } @@ -429,3 +453,24 @@ impl BinaryOp { } } } + +#[cfg(test)] +mod test { + use proptest::prelude::*; + + use super::{ + convert_signed_integer_to_field_element, try_convert_field_element_to_signed_integer, + }; + + proptest! { + #[test] + fn signed_int_roundtrip(int: i128, bit_size in 1u32..=64) { + let int = int % (1i128 << (bit_size - 1)); + + let int_as_field = convert_signed_integer_to_field_element(int, bit_size); + let recovered_int = try_convert_field_element_to_signed_integer(int_as_field, bit_size).unwrap(); + + prop_assert_eq!(int, recovered_int); + } + } +} diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs index 74e5653c7ba..281ab7c3057 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs @@ -2,6 +2,7 @@ use fxhash::FxHashMap as HashMap; use std::{collections::VecDeque, rc::Rc}; use acvm::{acir::AcirField, acir::BlackBoxFunc, BlackBoxResolutionError, FieldElement}; +use bn254_blackbox_solver::derive_generators; use iter_extended::vecmap; use num_bigint::BigUint; @@ -245,6 +246,21 @@ pub(super) fn simplify_call( SimplifyResult::None } } + Intrinsic::StaticAssert => { + if arguments.len() != 2 { + panic!("ICE: static_assert called with wrong number of arguments") + } + + if !dfg.is_constant(arguments[1]) { + return SimplifyResult::None; + } + + if dfg.is_constant_true(arguments[0]) { + SimplifyResult::Remove + } else { + SimplifyResult::None + } + } Intrinsic::ApplyRangeConstraint => { let value = arguments[0]; let max_bit_size = dfg.get_numeric_constant(arguments[1]); @@ -295,6 +311,13 @@ pub(super) fn simplify_call( } Intrinsic::AsWitness => SimplifyResult::None, Intrinsic::IsUnconstrained => SimplifyResult::None, + Intrinsic::DerivePedersenGenerators => { + if let Some(Type::Array(_, len)) = ctrl_typevars.unwrap().first() { + simplify_derive_generators(dfg, arguments, *len as u32) + } else { + unreachable!("Derive Pedersen Generators must return an array"); + } + } } } @@ -438,7 +461,9 @@ fn simplify_black_box_func( BlackBoxFunc::SHA256 => simplify_hash(dfg, arguments, acvm::blackbox_solver::sha256), BlackBoxFunc::Blake2s => simplify_hash(dfg, arguments, acvm::blackbox_solver::blake2s), BlackBoxFunc::Blake3 => simplify_hash(dfg, arguments, acvm::blackbox_solver::blake3), - BlackBoxFunc::Keccakf1600 => SimplifyResult::None, //TODO(Guillaume) + BlackBoxFunc::PedersenCommitment + | BlackBoxFunc::PedersenHash + | BlackBoxFunc::Keccakf1600 => SimplifyResult::None, //TODO(Guillaume) BlackBoxFunc::Keccak256 => { match (dfg.get_array_constant(arguments[0]), dfg.get_numeric_constant(arguments[1])) { (Some((input, _)), Some(num_bytes)) if array_is_constant(dfg, &input) => { @@ -468,8 +493,6 @@ fn simplify_black_box_func( BlackBoxFunc::MultiScalarMul | BlackBoxFunc::SchnorrVerify - | BlackBoxFunc::PedersenCommitment - | BlackBoxFunc::PedersenHash | BlackBoxFunc::EmbeddedCurveAdd => { // Currently unsolvable here as we rely on an implementation in the backend. SimplifyResult::None @@ -626,3 +649,47 @@ fn simplify_signature( _ => SimplifyResult::None, } } + +fn simplify_derive_generators( + dfg: &mut DataFlowGraph, + arguments: &[ValueId], + num_generators: u32, +) -> SimplifyResult { + if arguments.len() == 2 { + let domain_separator_string = dfg.get_array_constant(arguments[0]); + let starting_index = dfg.get_numeric_constant(arguments[1]); + if let (Some(domain_separator_string), Some(starting_index)) = + (domain_separator_string, starting_index) + { + let domain_separator_bytes = domain_separator_string + .0 + .iter() + .map(|&x| dfg.get_numeric_constant(x).unwrap().to_u128() as u8) + .collect::>(); + let generators = derive_generators( + &domain_separator_bytes, + num_generators, + starting_index.try_to_u32().expect("argument is declared as u32"), + ); + let is_infinite = dfg.make_constant(FieldElement::zero(), Type::bool()); + let mut results = Vec::new(); + for gen in generators { + let x_big: BigUint = gen.x.into(); + let x = FieldElement::from_be_bytes_reduce(&x_big.to_bytes_be()); + let y_big: BigUint = gen.y.into(); + let y = FieldElement::from_be_bytes_reduce(&y_big.to_bytes_be()); + results.push(dfg.make_constant(x, Type::field())); + results.push(dfg.make_constant(y, Type::field())); + results.push(is_infinite); + } + let len = results.len(); + let result = + dfg.make_array(results.into(), Type::Array(vec![Type::field()].into(), len)); + SimplifyResult::SimplifiedTo(result) + } else { + SimplifyResult::None + } + } else { + unreachable!("Unexpected number of arguments to derive_generators"); + } +} diff --git a/compiler/noirc_evaluator/src/ssa/ir/types.rs b/compiler/noirc_evaluator/src/ssa/ir/types.rs index 76b5bc43384..56729a5cba9 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/types.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/types.rs @@ -29,15 +29,37 @@ impl NumericType { } } - /// Returns true if the given Field value is within the numeric limits - /// for the current NumericType. - pub(crate) fn value_is_within_limits(self, field: FieldElement) -> bool { + /// Returns None if the given Field value is within the numeric limits + /// for the current NumericType. Otherwise returns a string describing + /// the limits, as a range. + pub(crate) fn value_is_outside_limits( + self, + field: FieldElement, + negative: bool, + ) -> Option { match self { - NumericType::Signed { bit_size } | NumericType::Unsigned { bit_size } => { + NumericType::Unsigned { bit_size } => { let max = 2u128.pow(bit_size) - 1; - field <= max.into() + if negative { + return Some(format!("0..={}", max)); + } + if field <= max.into() { + None + } else { + Some(format!("0..={}", max)) + } } - NumericType::NativeField => true, + NumericType::Signed { bit_size } => { + let min = 2u128.pow(bit_size - 1); + let max = 2u128.pow(bit_size - 1) - 1; + let target_max = if negative { min } else { max }; + if field <= target_max.into() { + None + } else { + Some(format!("-{}..={}", min, max)) + } + } + NumericType::NativeField => None, } } } @@ -210,3 +232,27 @@ impl std::fmt::Display for NumericType { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_u8_value_is_outside_limits() { + let u8 = NumericType::Unsigned { bit_size: 8 }; + assert!(u8.value_is_outside_limits(FieldElement::from(1_i128), true).is_some()); + assert!(u8.value_is_outside_limits(FieldElement::from(0_i128), false).is_none()); + assert!(u8.value_is_outside_limits(FieldElement::from(255_i128), false).is_none()); + assert!(u8.value_is_outside_limits(FieldElement::from(256_i128), false).is_some()); + } + + #[test] + fn test_i8_value_is_outside_limits() { + let i8 = NumericType::Signed { bit_size: 8 }; + assert!(i8.value_is_outside_limits(FieldElement::from(129_i128), true).is_some()); + assert!(i8.value_is_outside_limits(FieldElement::from(128_i128), true).is_none()); + assert!(i8.value_is_outside_limits(FieldElement::from(0_i128), false).is_none()); + assert!(i8.value_is_outside_limits(FieldElement::from(127_i128), false).is_none()); + assert!(i8.value_is_outside_limits(FieldElement::from(128_i128), false).is_some()); + } +} diff --git a/compiler/noirc_evaluator/src/ssa/opt/assert_constant.rs b/compiler/noirc_evaluator/src/ssa/opt/assert_constant.rs index a3608f89612..ae0681a55ff 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/assert_constant.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/assert_constant.rs @@ -22,7 +22,9 @@ impl Ssa { /// since we must go through every instruction to find all references to `assert_constant` /// while loop unrolling only touches blocks with loops in them. #[tracing::instrument(level = "trace", skip(self))] - pub(crate) fn evaluate_assert_constant(mut self) -> Result { + pub(crate) fn evaluate_static_assert_and_assert_constant( + mut self, + ) -> Result { for function in self.functions.values_mut() { for block in function.reachable_blocks() { // Unfortunately we can't just use instructions.retain(...) here since @@ -54,10 +56,13 @@ fn check_instruction( instruction: InstructionId, ) -> Result { let assert_constant_id = function.dfg.import_intrinsic(Intrinsic::AssertConstant); + let static_assert_id = function.dfg.import_intrinsic(Intrinsic::StaticAssert); match &function.dfg[instruction] { Instruction::Call { func, arguments } => { if *func == assert_constant_id { evaluate_assert_constant(function, instruction, arguments) + } else if *func == static_assert_id { + evaluate_static_assert(function, instruction, arguments) } else { Ok(true) } @@ -82,3 +87,35 @@ fn evaluate_assert_constant( Err(RuntimeError::AssertConstantFailed { call_stack }) } } + +/// Evaluate a call to `static_assert`, returning an error if the value is false +/// or not constant (see assert_constant). +/// +/// When it passes, Ok(false) is returned. This signifies a +/// success but also that the instruction need not be reinserted into the block being unrolled +/// since it has already been evaluated. +fn evaluate_static_assert( + function: &Function, + instruction: InstructionId, + arguments: &[ValueId], +) -> Result { + if arguments.len() != 2 { + panic!("ICE: static_assert called with wrong number of arguments") + } + + if !function.dfg.is_constant(arguments[1]) { + let call_stack = function.dfg.get_call_stack(instruction); + return Err(RuntimeError::StaticAssertDynamicMessage { call_stack }); + } + + if function.dfg.is_constant_true(arguments[0]) { + Ok(false) + } else { + let call_stack = function.dfg.get_call_stack(instruction); + if function.dfg.is_constant(arguments[0]) { + Err(RuntimeError::StaticAssertFailed { call_stack }) + } else { + Err(RuntimeError::StaticAssertDynamicPredicate { call_stack }) + } + } +} diff --git a/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs b/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs index 48bd70ff139..160105d27e6 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs @@ -87,12 +87,16 @@ struct Context { block_queue: Vec, } +/// HashMap from (Instruction, side_effects_enabled_var) to the results of the instruction. +/// Stored as a two-level map to avoid cloning Instructions during the `.get` call. +type InstructionResultCache = HashMap, Vec>>; + impl Context { fn fold_constants_in_block(&mut self, function: &mut Function, block: BasicBlockId) { let instructions = function.dfg[block].take_instructions(); // Cache of instructions without any side-effects along with their outputs. - let mut cached_instruction_results: HashMap> = HashMap::default(); + let mut cached_instruction_results = HashMap::default(); // Contains sets of values which are constrained to be equivalent to each other. // @@ -124,7 +128,7 @@ impl Context { dfg: &mut DataFlowGraph, block: BasicBlockId, id: InstructionId, - instruction_result_cache: &mut HashMap>, + instruction_result_cache: &mut InstructionResultCache, constraint_simplification_mappings: &mut HashMap>, side_effects_enabled_var: &mut ValueId, ) { @@ -134,7 +138,9 @@ impl Context { let old_results = dfg.instruction_results(id).to_vec(); // If a copy of this instruction exists earlier in the block, then reuse the previous results. - if let Some(cached_results) = instruction_result_cache.get(&instruction) { + if let Some(cached_results) = + Self::get_cached(dfg, instruction_result_cache, &instruction, *side_effects_enabled_var) + { Self::replace_result_ids(dfg, &old_results, cached_results); return; } @@ -150,6 +156,7 @@ impl Context { dfg, instruction_result_cache, constraint_simplification_mapping, + *side_effects_enabled_var, ); // If we just inserted an `Instruction::EnableSideEffects`, we need to update `side_effects_enabled_var` @@ -224,8 +231,9 @@ impl Context { instruction: Instruction, instruction_results: Vec, dfg: &DataFlowGraph, - instruction_result_cache: &mut HashMap>, + instruction_result_cache: &mut InstructionResultCache, constraint_simplification_mapping: &mut HashMap, + side_effects_enabled_var: ValueId, ) { if self.use_constraint_info { // If the instruction was a constraint, then create a link between the two `ValueId`s @@ -258,8 +266,15 @@ impl Context { // If the instruction doesn't have side-effects and if it won't interact with enable_side_effects during acir_gen, // we cache the results so we can reuse them if the same instruction appears again later in the block. - if instruction.can_be_deduplicated(dfg) { - instruction_result_cache.insert(instruction, instruction_results); + if instruction.can_be_deduplicated(dfg, self.use_constraint_info) { + let use_predicate = + self.use_constraint_info && instruction.requires_acir_gen_predicate(dfg); + let predicate = use_predicate.then_some(side_effects_enabled_var); + + instruction_result_cache + .entry(instruction) + .or_default() + .insert(predicate, instruction_results); } } @@ -273,6 +288,25 @@ impl Context { dfg.set_value_from_id(*old_result, *new_result); } } + + fn get_cached<'a>( + dfg: &DataFlowGraph, + instruction_result_cache: &'a mut InstructionResultCache, + instruction: &Instruction, + side_effects_enabled_var: ValueId, + ) -> Option<&'a Vec> { + let results_for_instruction = instruction_result_cache.get(instruction); + + // See if there's a cached version with no predicate first + if let Some(results) = results_for_instruction.and_then(|map| map.get(&None)) { + return Some(results); + } + + let predicate = + instruction.requires_acir_gen_predicate(dfg).then_some(side_effects_enabled_var); + + results_for_instruction.and_then(|map| map.get(&predicate)) + } } #[cfg(test)] @@ -288,7 +322,7 @@ mod test { value::{Value, ValueId}, }, }; - use acvm::acir::AcirField; + use acvm::{acir::AcirField, FieldElement}; #[test] fn simple_constant_fold() { @@ -545,6 +579,73 @@ mod test { assert_eq!(instruction, &Instruction::Cast(v0, Type::unsigned(32))); } + #[test] + fn constant_index_array_access_deduplication() { + // fn main f0 { + // b0(v0: [Field; 4], v1: u32, v2: bool, v3: bool): + // enable_side_effects v2 + // v4 = array_get v0 u32 0 + // v5 = array_get v0 v1 + // enable_side_effects v3 + // v6 = array_get v0 u32 0 + // v7 = array_get v0 v1 + // constrain v4 v6 + // } + // + // After constructing this IR, we run constant folding which should replace the second constant-index array get + // with a reference to the results to the first. This then allows us to optimize away + // the constrain instruction as both inputs are known to be equal. + // + let main_id = Id::test_new(0); + + // Compiling main + let mut builder = FunctionBuilder::new("main".into(), main_id); + + let v0 = builder.add_parameter(Type::Array(Rc::new(vec![Type::field()]), 4)); + let v1 = builder.add_parameter(Type::unsigned(32)); + let v2 = builder.add_parameter(Type::unsigned(1)); + let v3 = builder.add_parameter(Type::unsigned(1)); + + let zero = builder.numeric_constant(FieldElement::zero(), Type::length_type()); + + builder.insert_enable_side_effects_if(v2); + let v4 = builder.insert_array_get(v0, zero, Type::field()); + let _v5 = builder.insert_array_get(v0, v1, Type::field()); + + builder.insert_enable_side_effects_if(v3); + let v6 = builder.insert_array_get(v0, zero, Type::field()); + let _v7 = builder.insert_array_get(v0, v1, Type::field()); + + builder.insert_constrain(v4, v6, None); + + let ssa = builder.finish(); + + println!("{ssa}"); + + let main = ssa.main(); + let instructions = main.dfg[main.entry_block()].instructions(); + assert_eq!(instructions.len(), 7); + + // Expected output: + // + // fn main f0 { + // b0(v0: [Field; 4], v1: u32, v2: bool, v3: bool): + // enable_side_effects v2 + // v10 = array_get v0 u32 0 + // v11 = array_get v0 v1 + // enable_side_effects v3 + // v12 = array_get v0 v1 + // } + let ssa = ssa.fold_constants(); + + println!("{ssa}"); + + let main = ssa.main(); + let instructions = main.dfg[main.entry_block()].instructions(); + + assert_eq!(instructions.len(), 5); + } + #[test] fn constraint_decomposition() { // fn main f0 { @@ -658,4 +759,86 @@ mod test { let ending_instruction_count = instructions.len(); assert_eq!(starting_instruction_count, ending_instruction_count); } + + #[test] + fn deduplicate_instructions_with_predicates() { + // fn main f0 { + // b0(v0: bool, v1: bool, v2: [u32; 2]): + // enable_side_effects v0 + // v3 = array_get v2, index u32 0 + // v4 = array_set v2, index u32 1, value: u32 2 + // v5 = array_get v4, index u32 0 + // constrain_eq v3, v5 + // enable_side_effects v1 + // v6 = array_get v2, index u32 0 + // v7 = array_set v2, index u32 1, value: u32 2 + // v8 = array_get v7, index u32 0 + // constrain_eq v6, v8 + // enable_side_effects v0 + // v9 = array_get v2, index u32 0 + // v10 = array_set v2, index u32 1, value: u32 2 + // v11 = array_get v10, index u32 0 + // constrain_eq v9, v11 + // } + let main_id = Id::test_new(0); + + // Compiling main + let mut builder = FunctionBuilder::new("main".into(), main_id); + + let v0 = builder.add_parameter(Type::bool()); + let v1 = builder.add_parameter(Type::bool()); + let v2 = builder.add_parameter(Type::Array(Rc::new(vec![Type::field()]), 2)); + + let zero = builder.numeric_constant(0u128, Type::length_type()); + let one = builder.numeric_constant(1u128, Type::length_type()); + let two = builder.numeric_constant(2u128, Type::length_type()); + + builder.insert_enable_side_effects_if(v0); + let v3 = builder.insert_array_get(v2, zero, Type::length_type()); + let v4 = builder.insert_array_set(v2, one, two); + let v5 = builder.insert_array_get(v4, zero, Type::length_type()); + builder.insert_constrain(v3, v5, None); + + builder.insert_enable_side_effects_if(v1); + let v6 = builder.insert_array_get(v2, zero, Type::length_type()); + let v7 = builder.insert_array_set(v2, one, two); + let v8 = builder.insert_array_get(v7, zero, Type::length_type()); + builder.insert_constrain(v6, v8, None); + + // We expect all these instructions after the 'enable side effects' instruction to be removed. + builder.insert_enable_side_effects_if(v0); + let v9 = builder.insert_array_get(v2, zero, Type::length_type()); + let v10 = builder.insert_array_set(v2, one, two); + let v11 = builder.insert_array_get(v10, zero, Type::length_type()); + builder.insert_constrain(v9, v11, None); + + let ssa = builder.finish(); + println!("{ssa}"); + + let main = ssa.main(); + let instructions = main.dfg[main.entry_block()].instructions(); + assert_eq!(instructions.len(), 15); + + // Expected output: + // + // fn main f0 { + // b0(v0: bool, v1: bool, v2: [Field; 2]): + // enable_side_effects v0 + // v3 = array_get v2, index Field 0 + // v4 = array_set v2, index Field 1, value: Field 2 + // v5 = array_get v4, index Field 0 + // constrain_eq v3, v5 + // enable_side_effects v1 + // v7 = array_set v2, index Field 1, value: Field 2 + // v8 = array_get v7, index Field 0 + // constrain_eq v3, v8 + // enable_side_effects v0 + // } + let ssa = ssa.fold_constants_using_constraints(); + println!("{ssa}"); + + let main = ssa.main(); + let instructions = main.dfg[main.entry_block()].instructions(); + assert_eq!(instructions.len(), 10); + } } diff --git a/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs b/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs index 58f70ba9192..c7ce3aaa155 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs @@ -1379,28 +1379,28 @@ mod test { fn should_not_merge_incorrectly_to_false() { // Regression test for #1792 // Tests that it does not simplify a true constraint an always-false constraint - // fn main f1 { - // b0(): - // v4 = call pedersen([Field 0], u32 0) - // v5 = array_get v4, index Field 0 - // v6 = cast v5 as u32 - // v8 = mod v6, u32 2 - // v9 = cast v8 as u1 - // v10 = allocate - // store Field 0 at v10 - // jmpif v9 then: b1, else: b2 - // b1(): - // v14 = add v5, Field 1 - // store v14 at v10 - // jmp b3() - // b3(): - // v12 = eq v9, u1 1 - // constrain v12 - // return - // b2(): - // store Field 0 at v10 - // jmp b3() - // } + // acir(inline) fn main f1 { + // b0(v0: [u8; 2]): + // v4 = call keccak256(v0, u8 2) + // v5 = array_get v4, index u8 0 + // v6 = cast v5 as u32 + // v8 = truncate v6 to 1 bits, max_bit_size: 32 + // v9 = cast v8 as u1 + // v10 = allocate + // store u8 0 at v10 + // jmpif v9 then: b2, else: b3 + // b2(): + // v12 = cast v5 as Field + // v13 = add v12, Field 1 + // store v13 at v10 + // jmp b4() + // b4(): + // constrain v9 == u1 1 + // return + // b3(): + // store u8 0 at v10 + // jmp b4() + // } let main_id = Id::test_new(1); let mut builder = FunctionBuilder::new("main".into(), main_id); @@ -1409,20 +1409,18 @@ mod test { let b2 = builder.insert_block(); let b3 = builder.insert_block(); - let element_type = Rc::new(vec![Type::field()]); - let array_type = Type::Array(element_type.clone(), 1); - - let zero = builder.field_constant(0_u128); - let zero_array = builder.array_constant(im::Vector::unit(zero), array_type); - let i_zero = builder.numeric_constant(0_u128, Type::unsigned(32)); - let pedersen = builder - .import_intrinsic_id(Intrinsic::BlackBox(acvm::acir::BlackBoxFunc::PedersenCommitment)); - let v4 = builder.insert_call( - pedersen, - vec![zero_array, i_zero], - vec![Type::Array(element_type, 2)], - )[0]; - let v5 = builder.insert_array_get(v4, zero, Type::field()); + let element_type = Rc::new(vec![Type::unsigned(8)]); + let array_type = Type::Array(element_type.clone(), 2); + let array = builder.add_parameter(array_type); + + let zero = builder.numeric_constant(0_u128, Type::unsigned(8)); + let two = builder.numeric_constant(2_u128, Type::unsigned(8)); + + let keccak = + builder.import_intrinsic_id(Intrinsic::BlackBox(acvm::acir::BlackBoxFunc::Keccak256)); + let v4 = + builder.insert_call(keccak, vec![array, two], vec![Type::Array(element_type, 32)])[0]; + let v5 = builder.insert_array_get(v4, zero, Type::unsigned(8)); let v6 = builder.insert_cast(v5, Type::unsigned(32)); let i_two = builder.numeric_constant(2_u128, Type::unsigned(32)); let v8 = builder.insert_binary(v6, BinaryOp::Mod, i_two); @@ -1435,7 +1433,9 @@ mod test { builder.switch_to_block(b1); let one = builder.field_constant(1_u128); - let v14 = builder.insert_binary(v5, BinaryOp::Add, one); + let v5b = builder.insert_cast(v5, Type::field()); + let v13: Id = builder.insert_binary(v5b, BinaryOp::Add, one); + let v14 = builder.insert_cast(v13, Type::unsigned(8)); builder.insert_store(v10, v14); builder.terminate_with_jmp(b3, vec![]); @@ -1449,8 +1449,9 @@ mod test { builder.insert_constrain(v12, v_true, None); builder.terminate_with_return(vec![]); - let ssa = builder.finish().flatten_cfg(); - let main = ssa.main(); + let ssa = builder.finish(); + let flattened_ssa = ssa.flatten_cfg(); + let main = flattened_ssa.main(); // Now assert that there is not an always-false constraint after flattening: let mut constrain_count = 0; diff --git a/compiler/noirc_evaluator/src/ssa/opt/inlining.rs b/compiler/noirc_evaluator/src/ssa/opt/inlining.rs index e2a7f51d0a0..09802713363 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/inlining.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/inlining.rs @@ -45,23 +45,23 @@ impl Ssa { /// This step should run after runtime separation, since it relies on the runtime of the called functions being final. #[tracing::instrument(level = "trace", skip(self))] pub(crate) fn inline_functions(self) -> Ssa { - Self::inline_functions_inner(self, true) + Self::inline_functions_inner(self, false) } // Run the inlining pass where functions marked with `InlineType::NoPredicates` as not entry points pub(crate) fn inline_functions_with_no_predicates(self) -> Ssa { - Self::inline_functions_inner(self, false) + Self::inline_functions_inner(self, true) } - fn inline_functions_inner(mut self, no_predicates_is_entry_point: bool) -> Ssa { + fn inline_functions_inner(mut self, inline_no_predicates_functions: bool) -> Ssa { let recursive_functions = find_all_recursive_functions(&self); self.functions = btree_map( - get_functions_to_inline_into(&self, no_predicates_is_entry_point), + get_functions_to_inline_into(&self, inline_no_predicates_functions), |entry_point| { let new_function = InlineContext::new( &self, entry_point, - no_predicates_is_entry_point, + inline_no_predicates_functions, recursive_functions.clone(), ) .inline_all(&self); @@ -86,7 +86,13 @@ struct InlineContext { // The FunctionId of the entry point function we're inlining into in the old, unmodified Ssa. entry_point: FunctionId, - no_predicates_is_entry_point: bool, + /// Whether the inlining pass should inline any functions marked with [`InlineType::NoPredicates`] + /// or whether these should be preserved as entrypoint functions. + /// + /// This is done as we delay inlining of functions with the attribute `#[no_predicates]` until after + /// the control flow graph has been flattened. + inline_no_predicates_functions: bool, + // We keep track of the recursive functions in the SSA to avoid inlining them in a brillig context. recursive_functions: BTreeSet, } @@ -179,7 +185,7 @@ fn find_all_recursive_functions(ssa: &Ssa) -> BTreeSet { /// - Any Acir functions with a [fold inline type][InlineType::Fold], fn get_functions_to_inline_into( ssa: &Ssa, - no_predicates_is_entry_point: bool, + inline_no_predicates_functions: bool, ) -> BTreeSet { let mut brillig_entry_points = BTreeSet::default(); let mut acir_entry_points = BTreeSet::default(); @@ -190,10 +196,9 @@ fn get_functions_to_inline_into( } // If we have not already finished the flattening pass, functions marked - // to not have predicates should be marked as entry points. - let no_predicates_is_entry_point = - no_predicates_is_entry_point && function.is_no_predicates(); - if function.runtime().is_entry_point() || no_predicates_is_entry_point { + // to not have predicates should be preserved. + let preserve_function = !inline_no_predicates_functions && function.is_no_predicates(); + if function.runtime().is_entry_point() || preserve_function { acir_entry_points.insert(*func_id); } @@ -228,7 +233,7 @@ impl InlineContext { fn new( ssa: &Ssa, entry_point: FunctionId, - no_predicates_is_entry_point: bool, + inline_no_predicates_functions: bool, recursive_functions: BTreeSet, ) -> InlineContext { let source = &ssa.functions[&entry_point]; @@ -239,7 +244,7 @@ impl InlineContext { recursion_level: 0, entry_point, call_stack: CallStack::new(), - no_predicates_is_entry_point, + inline_no_predicates_functions, recursive_functions, } } @@ -470,6 +475,8 @@ impl<'function> PerFunctionContext<'function> { /// Inline each instruction in the given block into the function being inlined into. /// This may recurse if it finds another function to inline if a call instruction is within this block. fn inline_block_instructions(&mut self, ssa: &Ssa, block_id: BasicBlockId) { + let mut side_effects_enabled: Option = None; + let block = &self.source_function.dfg[block_id]; for id in block.instructions() { match &self.source_function.dfg[*id] { @@ -477,12 +484,28 @@ impl<'function> PerFunctionContext<'function> { Some(func_id) => { if self.should_inline_call(ssa, func_id) { self.inline_function(ssa, *id, func_id, arguments); + + // This is only relevant during handling functions with `InlineType::NoPredicates` as these + // can pollute the function they're being inlined into with `Instruction::EnabledSideEffects`, + // resulting in predicates not being applied properly. + // + // Note that this doesn't cover the case in which there exists an `Instruction::EnabledSideEffects` + // within the function being inlined whilst the source function has not encountered one yet. + // In practice this isn't an issue as the last `Instruction::EnabledSideEffects` in the + // function being inlined will be to turn off predicates rather than to create one. + if let Some(condition) = side_effects_enabled { + self.context.builder.insert_enable_side_effects_if(condition); + } } else { self.push_instruction(*id); } } None => self.push_instruction(*id), }, + Instruction::EnableSideEffects { condition } => { + side_effects_enabled = Some(self.translate_value(*condition)); + self.push_instruction(*id); + } _ => self.push_instruction(*id), } } @@ -495,10 +518,10 @@ impl<'function> PerFunctionContext<'function> { // If the called function is acir, we inline if it's not an entry point // If we have not already finished the flattening pass, functions marked - // to not have predicates should be marked as entry points. - let no_predicates_is_entry_point = - self.context.no_predicates_is_entry_point && function.is_no_predicates(); - !inline_type.is_entry_point() && !no_predicates_is_entry_point + // to not have predicates should be preserved. + let preserve_function = + !self.context.inline_no_predicates_functions && function.is_no_predicates(); + !inline_type.is_entry_point() && !preserve_function } else { // If the called function is brillig, we inline only if it's into brillig and the function is not recursive ssa.functions[&self.context.entry_point].runtime() == RuntimeType::Brillig @@ -517,19 +540,13 @@ impl<'function> PerFunctionContext<'function> { let old_results = self.source_function.dfg.instruction_results(call_id); let arguments = vecmap(arguments, |arg| self.translate_value(*arg)); - let mut call_stack = self.source_function.dfg.get_call_stack(call_id); - let has_location = !call_stack.is_empty(); - - // Function calls created by the defunctionalization pass will not have source locations - if let Some(location) = call_stack.pop_back() { - self.context.call_stack.push_back(location); - } + let call_stack = self.source_function.dfg.get_call_stack(call_id); + let call_stack_len = call_stack.len(); + self.context.call_stack.append(call_stack); let new_results = self.context.inline_function(ssa, function, &arguments); - if has_location { - self.context.call_stack.pop_back(); - } + self.context.call_stack.truncate(self.context.call_stack.len() - call_stack_len); let new_results = InsertInstructionResult::Results(call_id, &new_results); Self::insert_new_instruction_results(&mut self.values, old_results, new_results); diff --git a/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs b/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs index 6db76996747..1584b848564 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs @@ -18,6 +18,7 @@ use crate::ssa::{ dfg::DataFlowGraph, function::Function, instruction::{BinaryOp, Instruction, Intrinsic}, + types::Type, value::Value, }, ssa_gen::Ssa, @@ -62,6 +63,7 @@ impl Context { ) { let instructions = function.dfg[block].take_instructions(); + let mut active_condition = function.dfg.make_constant(FieldElement::one(), Type::bool()); let mut last_side_effects_enabled_instruction = None; let mut new_instructions = Vec::with_capacity(instructions.len()); @@ -72,19 +74,26 @@ impl Context { // instructions with side effects then we can drop the instruction we're holding and // continue with the new `Instruction::EnableSideEffects`. if let Instruction::EnableSideEffects { condition } = instruction { + // If this instruction isn't changing the currently active condition then we can ignore it. + if active_condition == *condition { + continue; + } + // If we're seeing an `enable_side_effects u1 1` then we want to insert it immediately. // This is because we want to maximize the effect it will have. - if function + let condition_is_one = function .dfg .get_numeric_constant(*condition) - .map_or(false, |condition| condition.is_one()) - { + .map_or(false, |condition| condition.is_one()); + if condition_is_one { new_instructions.push(instruction_id); last_side_effects_enabled_instruction = None; + active_condition = *condition; continue; } last_side_effects_enabled_instruction = Some(instruction_id); + active_condition = *condition; continue; } @@ -150,6 +159,7 @@ impl Context { Intrinsic::ArrayLen | Intrinsic::AssertConstant + | Intrinsic::StaticAssert | Intrinsic::ApplyRangeConstraint | Intrinsic::StrAsBytes | Intrinsic::ToBits(_) @@ -159,7 +169,8 @@ impl Context { | Intrinsic::AsField | Intrinsic::AsSlice | Intrinsic::AsWitness - | Intrinsic::IsUnconstrained => false, + | Intrinsic::IsUnconstrained + | Intrinsic::DerivePedersenGenerators => false, }, // We must assume that functions contain a side effect as we cannot inspect more deeply. @@ -170,3 +181,88 @@ impl Context { } } } + +#[cfg(test)] +mod test { + + use crate::ssa::{ + function_builder::FunctionBuilder, + ir::{ + instruction::{BinaryOp, Instruction}, + map::Id, + types::Type, + }, + }; + + #[test] + fn remove_chains_of_same_condition() { + // acir(inline) fn main f0 { + // b0(v0: Field): + // enable_side_effects u1 1 + // v4 = mul v0, Field 2 + // enable_side_effects u1 1 + // v5 = mul v0, Field 2 + // enable_side_effects u1 1 + // v6 = mul v0, Field 2 + // enable_side_effects u1 1 + // v7 = mul v0, Field 2 + // enable_side_effects u1 1 + // (no terminator instruction) + // } + // + // After constructing this IR, we run constant folding which should replace the second cast + // with a reference to the results to the first. This then allows us to optimize away + // the constrain instruction as both inputs are known to be equal. + // + // The first cast instruction is retained and will be removed in the dead instruction elimination pass. + let main_id = Id::test_new(0); + + // Compiling main + let mut builder = FunctionBuilder::new("main".into(), main_id); + let v0 = builder.add_parameter(Type::field()); + + let two = builder.numeric_constant(2u128, Type::field()); + + let one = builder.numeric_constant(1u128, Type::bool()); + + builder.insert_enable_side_effects_if(one); + builder.insert_binary(v0, BinaryOp::Mul, two); + builder.insert_enable_side_effects_if(one); + builder.insert_binary(v0, BinaryOp::Mul, two); + builder.insert_enable_side_effects_if(one); + builder.insert_binary(v0, BinaryOp::Mul, two); + builder.insert_enable_side_effects_if(one); + builder.insert_binary(v0, BinaryOp::Mul, two); + builder.insert_enable_side_effects_if(one); + + let ssa = builder.finish(); + + println!("{ssa}"); + + let main = ssa.main(); + let instructions = main.dfg[main.entry_block()].instructions(); + assert_eq!(instructions.len(), 9); + + // Expected output: + // + // acir(inline) fn main f0 { + // b0(v0: Field): + // v3 = mul v0, Field 2 + // v4 = mul v0, Field 2 + // v5 = mul v0, Field 2 + // v6 = mul v0, Field 2 + // (no terminator instruction) + // } + let ssa = ssa.remove_enable_side_effects(); + + println!("{ssa}"); + + let main = ssa.main(); + let instructions = main.dfg[main.entry_block()].instructions(); + + assert_eq!(instructions.len(), 4); + for instruction in instructions.iter().take(4) { + assert_eq!(&main.dfg[*instruction], &Instruction::binary(BinaryOp::Mul, v0, two)); + } + } +} diff --git a/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs b/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs index 6ca7eb74e9d..242eea7d6f4 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs @@ -226,6 +226,7 @@ fn slice_capacity_change( // These cases don't affect slice capacities Intrinsic::AssertConstant + | Intrinsic::StaticAssert | Intrinsic::ApplyRangeConstraint | Intrinsic::ArrayLen | Intrinsic::StrAsBytes @@ -233,6 +234,7 @@ fn slice_capacity_change( | Intrinsic::FromField | Intrinsic::AsField | Intrinsic::AsWitness - | Intrinsic::IsUnconstrained => SizeChange::None, + | Intrinsic::IsUnconstrained + | Intrinsic::DerivePedersenGenerators => SizeChange::None, } } diff --git a/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs b/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs index a16e0f93c61..e013546f14a 100644 --- a/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs +++ b/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs @@ -266,20 +266,38 @@ impl<'a> FunctionContext<'a> { pub(super) fn checked_numeric_constant( &mut self, value: impl Into, + negative: bool, typ: Type, ) -> Result { let value = value.into(); - if let Type::Numeric(typ) = typ { - if !typ.value_is_within_limits(value) { + if let Type::Numeric(numeric_type) = typ { + if let Some(range) = numeric_type.value_is_outside_limits(value, negative) { let call_stack = self.builder.get_call_stack(); - return Err(RuntimeError::IntegerOutOfBounds { value, typ, call_stack }); + return Err(RuntimeError::IntegerOutOfBounds { + value: if negative { -value } else { value }, + typ: numeric_type, + range, + call_stack, + }); } + + let value = if negative { + match numeric_type { + NumericType::NativeField => -value, + NumericType::Signed { bit_size } | NumericType::Unsigned { bit_size } => { + let base = 1_u128 << bit_size; + FieldElement::from(base) - value + } + } + } else { + value + }; + + Ok(self.builder.numeric_constant(value, typ)) } else { panic!("Expected type for numeric constant to be a numeric type, found {typ}"); } - - Ok(self.builder.numeric_constant(value, typ)) } /// helper function which add instructions to the block computing the absolute value of the diff --git a/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs b/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs index 254e907fb0e..afe44881830 100644 --- a/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs @@ -219,10 +219,10 @@ impl<'a> FunctionContext<'a> { _ => unreachable!("ICE: unexpected slice literal type, got {}", array.typ), }) } - ast::Literal::Integer(value, typ, location) => { + ast::Literal::Integer(value, negative, typ, location) => { self.builder.set_location(*location); let typ = Self::convert_non_tuple_type(typ); - self.checked_numeric_constant(*value, typ).map(Into::into) + self.checked_numeric_constant(*value, *negative, typ).map(Into::into) } ast::Literal::Bool(value) => { // Don't need to call checked_numeric_constant here since `value` can only be true or false diff --git a/compiler/noirc_frontend/Cargo.toml b/compiler/noirc_frontend/Cargo.toml index 381eb4a9bb9..052d2c5f484 100644 --- a/compiler/noirc_frontend/Cargo.toml +++ b/compiler/noirc_frontend/Cargo.toml @@ -29,6 +29,7 @@ regex = "1.9.1" cfg-if = "1.0.0" tracing.workspace = true petgraph = "0.6" +rangemap = "1.4.0" lalrpop-util = { version = "0.20.2", features = ["lexer"] } @@ -36,7 +37,6 @@ lalrpop-util = { version = "0.20.2", features = ["lexer"] } base64.workspace = true strum = "0.24" strum_macros = "0.24" -tempfile.workspace = true [build-dependencies] lalrpop = "0.20.2" diff --git a/compiler/noirc_frontend/src/ast/expression.rs b/compiler/noirc_frontend/src/ast/expression.rs index 075df4ee4e3..87cc7990753 100644 --- a/compiler/noirc_frontend/src/ast/expression.rs +++ b/compiler/noirc_frontend/src/ast/expression.rs @@ -5,9 +5,11 @@ use crate::ast::{ Ident, ItemVisibility, Path, Pattern, Recoverable, Statement, StatementKind, UnresolvedTraitConstraint, UnresolvedType, UnresolvedTypeData, Visibility, }; +use crate::hir::def_collector::errors::DefCollectorErrorKind; use crate::macros_api::StructId; use crate::node_interner::ExprId; -use crate::token::{Attributes, Token}; +use crate::token::{Attributes, Token, Tokens}; +use crate::{Kind, Type}; use acvm::{acir::AcirField, FieldElement}; use iter_extended::vecmap; use noirc_errors::{Span, Spanned}; @@ -33,18 +35,10 @@ pub enum ExpressionKind { Tuple(Vec), Lambda(Box), Parenthesized(Box), - Quote(BlockExpression, Span), + Quote(Tokens), Unquote(Box), Comptime(BlockExpression, Span), - /// Unquote expressions are replaced with UnquoteMarkers when Quoted - /// expressions are resolved. Since the expression being quoted remains an - /// ExpressionKind (rather than a resolved ExprId), the UnquoteMarker must be - /// here in the AST even though it is technically HIR-only. - /// Each usize in an UnquoteMarker is an index which corresponds to the index of the - /// expression in the Hir::Quote expression list to replace it with. - UnquoteMarker(usize), - // This variant is only emitted when inlining the result of comptime // code. It is used to translate function values back into the AST while // guaranteeing they have the same instantiated type and definition id without resolving again. @@ -54,7 +48,72 @@ pub enum ExpressionKind { /// A Vec of unresolved names for type variables. /// For `fn foo(...)` this corresponds to vec!["A", "B"]. -pub type UnresolvedGenerics = Vec; +pub type UnresolvedGenerics = Vec; + +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +pub enum UnresolvedGeneric { + Variable(Ident), + Numeric { ident: Ident, typ: UnresolvedType }, +} + +impl UnresolvedGeneric { + pub fn span(&self) -> Span { + match self { + UnresolvedGeneric::Variable(ident) => ident.0.span(), + UnresolvedGeneric::Numeric { ident, typ } => { + ident.0.span().merge(typ.span.unwrap_or_default()) + } + } + } + + pub fn kind(&self) -> Result { + match self { + UnresolvedGeneric::Variable(_) => Ok(Kind::Normal), + UnresolvedGeneric::Numeric { typ, .. } => { + let typ = self.resolve_numeric_kind_type(typ)?; + Ok(Kind::Numeric(Box::new(typ))) + } + } + } + + fn resolve_numeric_kind_type( + &self, + typ: &UnresolvedType, + ) -> Result { + use crate::ast::UnresolvedTypeData::{FieldElement, Integer}; + + match typ.typ { + FieldElement => Ok(Type::FieldElement), + Integer(sign, bits) => Ok(Type::Integer(sign, bits)), + // Only fields and integers are supported for numeric kinds + _ => Err(DefCollectorErrorKind::UnsupportedNumericGenericType { + ident: self.ident().clone(), + typ: typ.typ.clone(), + }), + } + } + + pub(crate) fn ident(&self) -> &Ident { + match self { + UnresolvedGeneric::Variable(ident) | UnresolvedGeneric::Numeric { ident, .. } => ident, + } + } +} + +impl Display for UnresolvedGeneric { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + UnresolvedGeneric::Variable(ident) => write!(f, "{ident}"), + UnresolvedGeneric::Numeric { ident, typ } => write!(f, "let {ident}: {typ}"), + } + } +} + +impl From for UnresolvedGeneric { + fn from(value: Ident) -> Self { + UnresolvedGeneric::Variable(value) + } +} impl ExpressionKind { pub fn into_path(self) -> Option { @@ -130,23 +189,6 @@ impl ExpressionKind { struct_type: None, })) } - - /// Returns true if the expression is a literal integer - pub fn is_integer(&self) -> bool { - self.as_integer().is_some() - } - - fn as_integer(&self) -> Option { - let literal = match self { - ExpressionKind::Literal(literal) => literal, - _ => return None, - }; - - match literal { - Literal::Integer(integer, _) => Some(*integer), - _ => None, - } - } } impl Recoverable for ExpressionKind { @@ -557,12 +599,14 @@ impl Display for ExpressionKind { } Lambda(lambda) => lambda.fmt(f), Parenthesized(sub_expr) => write!(f, "({sub_expr})"), - Quote(block, _) => write!(f, "quote {block}"), Comptime(block, _) => write!(f, "comptime {block}"), Error => write!(f, "Error"), Resolved(_) => write!(f, "?Resolved"), Unquote(expr) => write!(f, "$({expr})"), - UnquoteMarker(index) => write!(f, "${index}"), + Quote(tokens) => { + let tokens = vecmap(&tokens.0, ToString::to_string); + write!(f, "quote {{ {} }}", tokens.join(" ")) + } } } } @@ -763,22 +807,32 @@ impl Display for FunctionDefinition { writeln!(f, "{:?}", self.attributes)?; let parameters = vecmap(&self.parameters, |Param { visibility, pattern, typ, span: _ }| { - format!("{pattern}: {visibility} {typ}") + if *visibility == Visibility::Public { + format!("{pattern}: {visibility} {typ}") + } else { + format!("{pattern}: {typ}") + } }); let where_clause = vecmap(&self.where_clause, ToString::to_string); let where_clause_str = if !where_clause.is_empty() { - format!("where {}", where_clause.join(", ")) + format!(" where {}", where_clause.join(", ")) } else { "".to_string() }; + let return_type = if matches!(&self.return_type, FunctionReturnType::Default(_)) { + String::new() + } else { + format!(" -> {}", self.return_type) + }; + write!( f, - "fn {}({}) -> {} {} {}", + "fn {}({}){}{} {}", self.name, parameters.join(", "), - self.return_type, + return_type, where_clause_str, self.body ) diff --git a/compiler/noirc_frontend/src/ast/mod.rs b/compiler/noirc_frontend/src/ast/mod.rs index dd96a4a9077..d5689cc8ade 100644 --- a/compiler/noirc_frontend/src/ast/mod.rs +++ b/compiler/noirc_frontend/src/ast/mod.rs @@ -22,6 +22,7 @@ pub use traits::*; pub use type_alias::*; use crate::{ + node_interner::QuotedTypeId, parser::{ParserError, ParserErrorReason}, token::IntType, BinaryTypeOperator, @@ -119,6 +120,10 @@ pub enum UnresolvedTypeData { // The type of quoted code for metaprogramming Quoted(crate::QuotedType), + /// An already resolved type. These can only be parsed if they were present in the token stream + /// as a result of being spliced into a macro's token stream input. + Resolved(QuotedTypeId), + Unspecified, // This is for when the user declares a variable without specifying it's type Error, } @@ -146,7 +151,7 @@ pub struct UnaryRhsMethodCall { } /// The precursor to TypeExpression, this is the type that the parser allows -/// to be used in the length position of an array type. Only constants, variables, +/// to be used in the length position of an array type. Only constant integers, variables, /// and numeric binary operators are allowed here. #[derive(Debug, PartialEq, Eq, Clone, Hash)] pub enum UnresolvedTypeExpression { @@ -221,6 +226,7 @@ impl std::fmt::Display for UnresolvedTypeData { Error => write!(f, "error"), Unspecified => write!(f, "unspecified"), Parenthesized(typ) => write!(f, "({typ})"), + Resolved(_) => write!(f, "(resolved type)"), } } } @@ -259,6 +265,10 @@ impl UnresolvedType { pub fn unspecified() -> UnresolvedType { UnresolvedType { typ: UnresolvedTypeData::Unspecified, span: None } } + + pub(crate) fn is_type_expression(&self) -> bool { + matches!(&self.typ, UnresolvedTypeData::Expression(_)) + } } impl UnresolvedTypeData { diff --git a/compiler/noirc_frontend/src/ast/statement.rs b/compiler/noirc_frontend/src/ast/statement.rs index ceba1845faa..57800792971 100644 --- a/compiler/noirc_frontend/src/ast/statement.rs +++ b/compiler/noirc_frontend/src/ast/statement.rs @@ -10,6 +10,7 @@ use super::{ BlockExpression, Expression, ExpressionKind, IndexExpression, MemberAccessExpression, MethodCallExpression, UnresolvedType, }; +use crate::hir::resolution::resolver::SELF_TYPE_NAME; use crate::lexer::token::SpannedToken; use crate::macros_api::SecondaryAttribute; use crate::parser::{ParserError, ParserErrorReason}; @@ -165,6 +166,12 @@ impl StatementKind { #[derive(Eq, Debug, Clone)] pub struct Ident(pub Spanned); +impl Ident { + pub fn is_self_type_name(&self) -> bool { + self.0.contents == SELF_TYPE_NAME + } +} + impl PartialEq for Ident { fn eq(&self, other: &Ident) -> bool { self.0.contents == other.0.contents diff --git a/compiler/noirc_frontend/src/ast/structure.rs b/compiler/noirc_frontend/src/ast/structure.rs index bda6b8c0b11..bb2d89841b9 100644 --- a/compiler/noirc_frontend/src/ast/structure.rs +++ b/compiler/noirc_frontend/src/ast/structure.rs @@ -20,7 +20,7 @@ impl NoirStruct { pub fn new( name: Ident, attributes: Vec, - generics: Vec, + generics: UnresolvedGenerics, fields: Vec<(Ident, UnresolvedType)>, span: Span, ) -> NoirStruct { diff --git a/compiler/noirc_frontend/src/ast/traits.rs b/compiler/noirc_frontend/src/ast/traits.rs index 772675723b5..b1b14e3f657 100644 --- a/compiler/noirc_frontend/src/ast/traits.rs +++ b/compiler/noirc_frontend/src/ast/traits.rs @@ -14,7 +14,7 @@ use crate::node_interner::TraitId; #[derive(Clone, Debug)] pub struct NoirTrait { pub name: Ident, - pub generics: Vec, + pub generics: UnresolvedGenerics, pub where_clause: Vec, pub span: Span, pub items: Vec, @@ -26,7 +26,7 @@ pub struct NoirTrait { pub enum TraitItem { Function { name: Ident, - generics: Vec, + generics: UnresolvedGenerics, parameters: Vec<(Ident, UnresolvedType)>, return_type: FunctionReturnType, where_clause: Vec, @@ -49,6 +49,7 @@ pub struct TypeImpl { pub object_type: UnresolvedType, pub type_span: Span, pub generics: UnresolvedGenerics, + pub where_clause: Vec, pub methods: Vec<(NoirFunction, Span)>, } diff --git a/compiler/noirc_frontend/src/elaborator/expressions.rs b/compiler/noirc_frontend/src/elaborator/expressions.rs index d6ad752b67a..5cda8787241 100644 --- a/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -9,25 +9,26 @@ use crate::{ UnresolvedTypeExpression, }, hir::{ - comptime::{self, Interpreter, InterpreterError}, + comptime::{self, InterpreterError}, resolution::{errors::ResolverError, resolver::LambdaContext}, type_check::TypeCheckError, }, hir_def::{ expr::{ HirArrayLiteral, HirBinaryOp, HirBlockExpression, HirCallExpression, HirCastExpression, - HirConstructorExpression, HirIfExpression, HirIndexExpression, HirInfixExpression, - HirLambda, HirMemberAccess, HirMethodCallExpression, HirMethodReference, - HirPrefixExpression, HirQuoted, + HirConstructorExpression, HirExpression, HirIfExpression, HirIndexExpression, + HirInfixExpression, HirLambda, HirMemberAccess, HirMethodCallExpression, + HirMethodReference, HirPrefixExpression, }, traits::TraitConstraint, }, macros_api::{ - BlockExpression, CallExpression, CastExpression, Expression, ExpressionKind, HirExpression, - HirLiteral, HirStatement, Ident, IndexExpression, Literal, MemberAccessExpression, + BlockExpression, CallExpression, CastExpression, Expression, ExpressionKind, HirLiteral, + HirStatement, Ident, IndexExpression, Literal, MemberAccessExpression, MethodCallExpression, PrefixExpression, }, - node_interner::{DefinitionKind, ExprId, FuncId}, + node_interner::{DefinitionKind, ExprId, FuncId, ReferenceId, TraitMethodId}, + token::Tokens, QuotedType, Shared, StructType, Type, }; @@ -38,7 +39,7 @@ impl<'context> Elaborator<'context> { let (hir_expr, typ) = match expr.kind { ExpressionKind::Literal(literal) => self.elaborate_literal(literal, expr.span), ExpressionKind::Block(block) => self.elaborate_block(block), - ExpressionKind::Prefix(prefix) => self.elaborate_prefix(*prefix), + ExpressionKind::Prefix(prefix) => return self.elaborate_prefix(*prefix), ExpressionKind::Index(index) => self.elaborate_index(*index), ExpressionKind::Call(call) => self.elaborate_call(*call, expr.span), ExpressionKind::MethodCall(call) => self.elaborate_method_call(*call, expr.span), @@ -50,15 +51,12 @@ impl<'context> Elaborator<'context> { ExpressionKind::Infix(infix) => return self.elaborate_infix(*infix, expr.span), ExpressionKind::If(if_) => self.elaborate_if(*if_), ExpressionKind::Variable(variable, generics) => { - let generics = generics.map(|option_inner| { - option_inner.into_iter().map(|generic| self.resolve_type(generic)).collect() - }); - return self.elaborate_variable(variable, generics); + return self.elaborate_variable(variable, generics) } ExpressionKind::Tuple(tuple) => self.elaborate_tuple(tuple), ExpressionKind::Lambda(lambda) => self.elaborate_lambda(*lambda), ExpressionKind::Parenthesized(expr) => return self.elaborate_expression(*expr), - ExpressionKind::Quote(quote, _) => self.elaborate_quote(quote), + ExpressionKind::Quote(quote) => self.elaborate_quote(quote), ExpressionKind::Comptime(comptime, _) => { return self.elaborate_comptime_block(comptime, expr.span) } @@ -68,9 +66,6 @@ impl<'context> Elaborator<'context> { self.push_err(ResolverError::UnquoteUsedOutsideQuote { span: expr.span }); (HirExpression::Error, Type::Error) } - ExpressionKind::UnquoteMarker(index) => { - unreachable!("UnquoteMarker({index}) remaining in runtime code") - } }; let id = self.interner.push_expr(hir_expr); self.interner.push_expr_location(id, expr.span, self.file); @@ -232,11 +227,22 @@ impl<'context> Elaborator<'context> { (HirExpression::Literal(HirLiteral::FmtStr(str, fmt_str_idents)), typ) } - fn elaborate_prefix(&mut self, prefix: PrefixExpression) -> (HirExpression, Type) { + fn elaborate_prefix(&mut self, prefix: PrefixExpression) -> (ExprId, Type) { let span = prefix.rhs.span; let (rhs, rhs_type) = self.elaborate_expression(prefix.rhs); - let ret_type = self.type_check_prefix_operand(&prefix.operator, &rhs_type, span); - (HirExpression::Prefix(HirPrefixExpression { operator: prefix.operator, rhs }), ret_type) + let trait_id = self.interner.get_prefix_operator_trait_method(&prefix.operator); + + let operator = prefix.operator; + let expr = + HirExpression::Prefix(HirPrefixExpression { operator, rhs, trait_method_id: trait_id }); + let expr_id = self.interner.push_expr(expr); + self.interner.push_expr_location(expr_id, span, self.file); + + let result = self.prefix_operand_type_rules(&operator, &rhs_type, span); + let typ = self.handle_operand_type_rules_result(result, &rhs_type, trait_id, expr_id, span); + + self.interner.push_expr_type(expr_id, typ.clone()); + (expr_id, typ) } fn elaborate_index(&mut self, index_expr: IndexExpression) -> (HirExpression, Type) { @@ -331,14 +337,18 @@ impl<'context> Elaborator<'context> { } }; - if func_id != FuncId::dummy_id() { + let generics = if func_id != FuncId::dummy_id() { let function_type = self.interner.function_meta(&func_id).typ.clone(); self.try_add_mutable_reference_to_object( &function_type, &mut object_type, &mut object, ); - } + + self.resolve_turbofish_generics(&func_id, method_call.generics, span) + } else { + None + }; // These arguments will be given to the desugared function call. // Compared to the method arguments, they also contain the object. @@ -356,9 +366,6 @@ impl<'context> Elaborator<'context> { let location = Location::new(span, self.file); let method = method_call.method_name; - let generics = method_call.generics.map(|option_inner| { - option_inner.into_iter().map(|generic| self.resolve_type(generic)).collect() - }); let turbofish_generics = generics.clone(); let method_call = HirMethodCallExpression { method, object, arguments, location, generics }; @@ -392,6 +399,7 @@ impl<'context> Elaborator<'context> { constructor: ConstructorExpression, ) -> (HirExpression, Type) { let span = constructor.type_name.span(); + let is_self_type = constructor.type_name.last_segment().is_self_type_name(); let (r#type, struct_generics) = if let Some(struct_id) = constructor.struct_type { let typ = self.interner.get_struct(struct_id); @@ -420,6 +428,11 @@ impl<'context> Elaborator<'context> { r#type, struct_generics, }); + + let referenced = ReferenceId::Struct(struct_type.borrow().id); + let reference = ReferenceId::Reference(Location::new(span, self.file), is_self_type); + self.interner.add_reference(referenced, reference); + (expr, Type::Struct(struct_type, generics)) } @@ -438,8 +451,13 @@ impl<'context> Elaborator<'context> { let mut unseen_fields = struct_type.borrow().field_names(); for (field_name, field) in fields { - let expected_type = field_types.iter().find(|(name, _)| name == &field_name.0.contents); - let expected_type = expected_type.map(|(_, typ)| typ).unwrap_or(&Type::Error); + let expected_field_with_index = field_types + .iter() + .enumerate() + .find(|(_, (name, _))| name == &field_name.0.contents); + let expected_index = expected_field_with_index.map(|(index, _)| index); + let expected_type = + expected_field_with_index.map(|(_, (_, typ))| typ).unwrap_or(&Type::Error); let field_span = field.span; let (resolved, field_type) = self.elaborate_expression(field); @@ -466,6 +484,14 @@ impl<'context> Elaborator<'context> { }); } + if let Some(expected_index) = expected_index { + let struct_id = struct_type.borrow().id; + let referenced = ReferenceId::StructMember(struct_id, expected_index); + let reference = + ReferenceId::Reference(Location::new(field_name.span(), self.file), false); + self.interner.add_reference(referenced, reference); + } + ret.push((field_name, resolved)); } @@ -487,10 +513,11 @@ impl<'context> Elaborator<'context> { ) -> (ExprId, Type) { let (lhs, lhs_type) = self.elaborate_expression(access.lhs); let rhs = access.rhs; + let rhs_span = rhs.span(); // `is_offset` is only used when lhs is a reference and we want to return a reference to rhs let access = HirMemberAccess { lhs, rhs, is_offset: false }; let expr_id = self.intern_expr(HirExpression::MemberAccess(access.clone()), span); - let typ = self.type_check_member_access(access, expr_id, lhs_type, span); + let typ = self.type_check_member_access(access, expr_id, lhs_type, rhs_span); self.interner.push_expr_type(expr_id, typ.clone()); (expr_id, typ) } @@ -525,19 +552,38 @@ impl<'context> Elaborator<'context> { let expr_id = self.interner.push_expr(expr); self.interner.push_expr_location(expr_id, span, self.file); - let typ = match self.infix_operand_type_rules(&lhs_type, &operator, &rhs_type, span) { + let result = self.infix_operand_type_rules(&lhs_type, &operator, &rhs_type, span); + let typ = + self.handle_operand_type_rules_result(result, &lhs_type, Some(trait_id), expr_id, span); + + self.interner.push_expr_type(expr_id, typ.clone()); + (expr_id, typ) + } + + fn handle_operand_type_rules_result( + &mut self, + result: Result<(Type, bool), TypeCheckError>, + operand_type: &Type, + trait_id: Option, + expr_id: ExprId, + span: Span, + ) -> Type { + match result { Ok((typ, use_impl)) => { if use_impl { + let trait_id = + trait_id.expect("ice: expected some trait_id when use_impl is true"); + // Delay checking the trait constraint until the end of the function. // Checking it now could bind an unbound type variable to any type // that implements the trait. let constraint = TraitConstraint { - typ: lhs_type.clone(), + typ: operand_type.clone(), trait_id: trait_id.trait_id, trait_generics: Vec::new(), }; - self.trait_constraints.push((constraint, expr_id)); - self.type_check_operator_method(expr_id, trait_id, &lhs_type, span); + self.push_trait_constraint(constraint, expr_id); + self.type_check_operator_method(expr_id, trait_id, operand_type, span); } typ } @@ -545,10 +591,7 @@ impl<'context> Elaborator<'context> { self.push_err(error); Type::Error } - }; - - self.interner.push_expr_type(expr_id, typ.clone()); - (expr_id, typ) + } } fn elaborate_if(&mut self, if_expr: IfExpression) -> (HirExpression, Type) { @@ -646,18 +689,32 @@ impl<'context> Elaborator<'context> { (expr, Type::Function(arg_types, Box::new(body_type), Box::new(env_type))) } - fn elaborate_quote(&mut self, mut block: BlockExpression) -> (HirExpression, Type) { - let mut unquoted_exprs = Vec::new(); - self.find_unquoted_exprs_in_block(&mut block, &mut unquoted_exprs); - let quoted = HirQuoted { quoted_block: block, unquoted_exprs }; - (HirExpression::Quote(quoted), Type::Quoted(QuotedType::Expr)) + fn elaborate_quote(&mut self, mut tokens: Tokens) -> (HirExpression, Type) { + tokens = self.find_unquoted_exprs_tokens(tokens); + (HirExpression::Quote(tokens), Type::Quoted(QuotedType::Quoted)) } fn elaborate_comptime_block(&mut self, block: BlockExpression, span: Span) -> (ExprId, Type) { + // We have to push a new FunctionContext so that we can resolve any constraints + // in this comptime block early before the function as a whole finishes elaborating. + // Otherwise the interpreter below may find expressions for which the underlying trait + // call is not yet solved for. + self.function_context.push(Default::default()); let (block, _typ) = self.elaborate_block_expression(block); - let mut interpreter = Interpreter::new(self.interner, &mut self.comptime_scopes); + + self.check_and_pop_function_context(); + let mut interpreter_errors = vec![]; + let mut interpreter = self.setup_interpreter(&mut interpreter_errors); let value = interpreter.evaluate_block(block); - self.inline_comptime_value(value, span) + self.include_interpreter_errors(interpreter_errors); + let (id, typ) = self.inline_comptime_value(value, span); + + let location = self.interner.id_location(id); + self.debug_comptime(location, |interner| { + interner.expression(&id).to_display_ast(interner, location.span).kind + }); + + (id, typ) } pub(super) fn inline_comptime_value( @@ -716,7 +773,7 @@ impl<'context> Elaborator<'context> { location: Location, return_type: Type, ) -> Option<(HirExpression, Type)> { - self.unify(&return_type, &Type::Quoted(QuotedType::Expr), || { + self.unify(&return_type, &Type::Quoted(QuotedType::Quoted), || { TypeCheckError::MacroReturningNonExpr { typ: return_type.clone(), span: location.span } }); @@ -728,8 +785,9 @@ impl<'context> Elaborator<'context> { } }; - let mut interpreter = Interpreter::new(self.interner, &mut self.comptime_scopes); - + let file = self.file; + let mut interpreter_errors = vec![]; + let mut interpreter = self.setup_interpreter(&mut interpreter_errors); let mut comptime_args = Vec::new(); let mut errors = Vec::new(); @@ -739,16 +797,19 @@ impl<'context> Elaborator<'context> { let location = interpreter.interner.expr_location(&argument); comptime_args.push((arg, location)); } - Err(error) => errors.push((error.into(), self.file)), + Err(error) => errors.push((error.into(), file)), } } + let bindings = interpreter.interner.get_instantiation_bindings(func).clone(); + let result = interpreter.call_function(function, comptime_args, bindings, location); + self.include_interpreter_errors(interpreter_errors); + if !errors.is_empty() { self.errors.append(&mut errors); return None; } - let result = interpreter.call_function(function, comptime_args, location); let (expr_id, typ) = self.inline_comptime_value(result, location.span); Some((self.interner.expression(&expr_id), typ)) } diff --git a/compiler/noirc_frontend/src/elaborator/lints.rs b/compiler/noirc_frontend/src/elaborator/lints.rs index b86912940eb..a4140043ac1 100644 --- a/compiler/noirc_frontend/src/elaborator/lints.rs +++ b/compiler/noirc_frontend/src/elaborator/lints.rs @@ -7,13 +7,12 @@ use crate::{ }, hir_def::expr::HirIdent, macros_api::{ - HirExpression, HirLiteral, NodeInterner, NoirFunction, UnaryOp, UnresolvedTypeData, - Visibility, + HirExpression, HirLiteral, NodeInterner, NoirFunction, Signedness, UnaryOp, + UnresolvedTypeData, Visibility, }, node_interner::{DefinitionKind, ExprId, FuncId}, Type, }; -use acvm::AcirField; use noirc_errors::Span; @@ -137,7 +136,7 @@ pub(super) fn unconstrained_function_args( function_args .iter() .filter_map(|(typ, _, span)| { - if type_contains_mutable_reference(typ) { + if !typ.is_valid_for_unconstrained_boundary() { Some(TypeCheckError::ConstrainedReferenceToUnconstrained { span: *span }) } else { None @@ -153,17 +152,13 @@ pub(super) fn unconstrained_function_return( ) -> Option { if return_type.contains_slice() { Some(TypeCheckError::UnconstrainedSliceReturnToConstrained { span }) - } else if type_contains_mutable_reference(return_type) { + } else if !return_type.is_valid_for_unconstrained_boundary() { Some(TypeCheckError::UnconstrainedReferenceToConstrained { span }) } else { None } } -fn type_contains_mutable_reference(typ: &Type) -> bool { - matches!(&typ.follow_bindings(), Type::MutableReference(_)) -} - /// Only entrypoint functions require a `pub` visibility modifier applied to their return types. /// /// Application of `pub` to other functions is not meaningful and is a mistake. @@ -200,8 +195,8 @@ pub(super) fn unnecessary_pub_argument( } /// Check if an assignment is overflowing with respect to `annotated_type` -/// in a declaration statement where `annotated_type` is an unsigned integer -pub(crate) fn overflowing_uint( +/// in a declaration statement where `annotated_type` is a signed or unsigned integer +pub(crate) fn overflowing_int( interner: &NodeInterner, rhs_expr: &ExprId, annotated_type: &Type, @@ -211,23 +206,36 @@ pub(crate) fn overflowing_uint( let mut errors = Vec::with_capacity(2); match expr { - HirExpression::Literal(HirLiteral::Integer(value, false)) => { - let v = value.to_u128(); - if let Type::Integer(_, bit_count) = annotated_type { + HirExpression::Literal(HirLiteral::Integer(value, negative)) => match annotated_type { + Type::Integer(Signedness::Unsigned, bit_count) => { let bit_count: u32 = (*bit_count).into(); - let max = 1 << bit_count; - if v >= max { + let max = 2u128.pow(bit_count) - 1; + if value > max.into() || negative { errors.push(TypeCheckError::OverflowingAssignment { - expr: value, + expr: if negative { -value } else { value }, ty: annotated_type.clone(), - range: format!("0..={}", max - 1), + range: format!("0..={}", max), span, }); - }; - }; - } + } + } + Type::Integer(Signedness::Signed, bit_count) => { + let bit_count: u32 = (*bit_count).into(); + let min = 2u128.pow(bit_count - 1); + let max = 2u128.pow(bit_count - 1) - 1; + if (negative && value > min.into()) || (!negative && value > max.into()) { + errors.push(TypeCheckError::OverflowingAssignment { + expr: if negative { -value } else { value }, + ty: annotated_type.clone(), + range: format!("-{}..={}", min, max), + span, + }); + } + } + _ => (), + }, HirExpression::Prefix(expr) => { - overflowing_uint(interner, &expr.rhs, annotated_type); + overflowing_int(interner, &expr.rhs, annotated_type); if expr.operator == UnaryOp::Minus { errors.push(TypeCheckError::InvalidUnaryOp { kind: "annotated_type".to_string(), @@ -236,8 +244,8 @@ pub(crate) fn overflowing_uint( } } HirExpression::Infix(expr) => { - errors.extend(overflowing_uint(interner, &expr.lhs, annotated_type)); - errors.extend(overflowing_uint(interner, &expr.rhs, annotated_type)); + errors.extend(overflowing_int(interner, &expr.lhs, annotated_type)); + errors.extend(overflowing_int(interner, &expr.rhs, annotated_type)); } _ => {} } diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 91d6ba71927..e1d104c4971 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -1,17 +1,19 @@ use std::{ collections::{BTreeMap, BTreeSet}, + fmt::Display, rc::Rc, }; use crate::{ ast::{FunctionKind, UnresolvedTraitConstraint}, hir::{ - comptime::{self, Interpreter, Value}, + comptime::{self, Interpreter, InterpreterError, Value}, def_collector::{ dc_crate::{ filter_literal_globals, CompilationError, ImplMap, UnresolvedGlobal, UnresolvedStruct, UnresolvedTypeAlias, }, + dc_mod, errors::DuplicateType, }, resolution::{errors::ResolverError, path_resolver::PathResolver, resolver::LambdaContext}, @@ -22,18 +24,21 @@ use crate::{ expr::HirIdent, function::{FunctionBody, Parameters}, traits::TraitConstraint, + types::{Generics, Kind, ResolvedGeneric}, }, macros_api::{ BlockExpression, Ident, NodeInterner, NoirFunction, NoirStruct, Pattern, SecondaryAttribute, StructId, }, node_interner::{ - DefinitionId, DefinitionKind, DependencyId, ExprId, FuncId, GlobalId, TraitId, TypeAliasId, + DefinitionId, DefinitionKind, DependencyId, ExprId, FuncId, GlobalId, ReferenceId, TraitId, + TypeAliasId, }, - Shared, Type, TypeVariable, + parser::TopLevelStatement, + Shared, Type, TypeBindings, TypeVariable, }; use crate::{ - ast::{TraitBound, UnresolvedGenerics}, + ast::{TraitBound, UnresolvedGeneric, UnresolvedGenerics}, graph::CrateId, hir::{ def_collector::{dc_crate::CollectedItems, errors::DefCollectorErrorKind}, @@ -44,7 +49,6 @@ use crate::{ hir_def::function::{FuncMeta, HirFunction}, macros_api::{Param, Path, UnresolvedType, UnresolvedTypeData}, node_interner::TraitImplId, - Generics, }; use crate::{ hir::{ @@ -90,22 +94,13 @@ pub struct Elaborator<'context> { file: FileId, - in_unconstrained_fn: bool, nested_loops: usize, - /// True if the current module is a contract. - /// This is usually determined by self.path_resolver.module_id(), but it can - /// be overridden for impls. Impls are an odd case since the methods within resolve - /// as if they're in the parent module, but should be placed in a child module. - /// Since they should be within a child module, in_contract is manually set to false - /// for these so we can still resolve them in the parent module without them being in a contract. - in_contract: bool, - /// Contains a mapping of the current struct or functions's generics to /// unique type variables if we're resolving a struct. Empty otherwise. /// This is a Vec rather than a map to preserve the order a functions generics /// were declared in. - generics: Vec<(Rc, TypeVariable, Span)>, + generics: Vec, /// When resolving lambda expressions, we need to keep track of the variables /// that are captured. We do this in order to create the hidden environment @@ -143,18 +138,14 @@ pub struct Elaborator<'context> { /// Each constraint in the `where` clause of the function currently being resolved. trait_bounds: Vec, - current_function: Option, - - /// All type variables created in the current function. - /// This map is used to default any integer type variables at the end of - /// a function (before checking trait constraints) if a type wasn't already chosen. - type_variables: Vec, - - /// Trait constraints are collected during type checking until they are - /// verified at the end of a function. This is because constraints arise - /// on each variable, but it is only until function calls when the types - /// needed for the trait constraint may become known. - trait_constraints: Vec<(TraitConstraint, ExprId)>, + /// This is a stack of function contexts. Most of the time, for each function we + /// expect this to be of length one, containing each type variable and trait constraint + /// used in the function. This is also pushed to when a `comptime {}` block is used within + /// the function. Since it can force us to resolve that block's trait constraints earlier + /// so that they are resolved when the interpreter is run before the enclosing function + /// is finished elaborating. When this happens, we need to resolve any type variables + /// that were made within this block as well so that we can solve these traits. + function_context: Vec, /// The current module this elaborator is in. /// Initially empty, it is set whenever a new top-level item is resolved. @@ -167,23 +158,42 @@ pub struct Elaborator<'context> { /// up all currently visible definitions. The first scope is always the global scope. comptime_scopes: Vec>, + /// The scope of --debug-comptime, or None if unset + debug_comptime_in_file: Option, + /// These are the globals that have yet to be elaborated. /// This map is used to lazily evaluate these globals if they're encountered before /// they are elaborated (e.g. in a function's type or another global's RHS). unresolved_globals: BTreeMap, } +#[derive(Default)] +struct FunctionContext { + /// All type variables created in the current function. + /// This map is used to default any integer type variables at the end of + /// a function (before checking trait constraints) if a type wasn't already chosen. + type_variables: Vec, + + /// Trait constraints are collected during type checking until they are + /// verified at the end of a function. This is because constraints arise + /// on each variable, but it is only until function calls when the types + /// needed for the trait constraint may become known. + trait_constraints: Vec<(TraitConstraint, ExprId)>, +} + impl<'context> Elaborator<'context> { - pub fn new(context: &'context mut Context, crate_id: CrateId) -> Self { + pub fn new( + context: &'context mut Context, + crate_id: CrateId, + debug_comptime_in_file: Option, + ) -> Self { Self { scopes: ScopeForest::default(), errors: Vec::new(), interner: &mut context.def_interner, def_maps: &mut context.def_maps, file: FileId::dummy(), - in_unconstrained_fn: false, nested_loops: 0, - in_contract: false, generics: Vec::new(), lambda_stack: Vec::new(), self_type: None, @@ -193,11 +203,10 @@ impl<'context> Elaborator<'context> { crate_id, resolving_ids: BTreeSet::new(), trait_bounds: Vec::new(), - current_function: None, - type_variables: Vec::new(), - trait_constraints: Vec::new(), + function_context: vec![FunctionContext::default()], current_trait_impl: None, comptime_scopes: vec![HashMap::default()], + debug_comptime_in_file, unresolved_globals: BTreeMap::new(), } } @@ -206,8 +215,9 @@ impl<'context> Elaborator<'context> { context: &'context mut Context, crate_id: CrateId, items: CollectedItems, + debug_comptime_in_file: Option, ) -> Vec<(CompilationError, FileId)> { - let mut this = Self::new(context, crate_id); + let mut this = Self::new(context, crate_id, debug_comptime_in_file); // Filter out comptime items to execute their functions first if needed. // This step is why comptime items can only refer to other comptime items @@ -239,11 +249,12 @@ impl<'context> Elaborator<'context> { self.define_type_alias(alias_id, alias); } + // Must resolve structs before we resolve globals. + let generated_items = self.collect_struct_definitions(items.types); + self.define_function_metas(&mut items.functions, &mut items.impls, &mut items.trait_impls); - self.collect_traits(items.traits); - // Must resolve structs before we resolve globals. - self.collect_struct_definitions(items.types); + self.collect_traits(items.traits); // Before we resolve any function symbols we must go through our impls and // re-collect the methods within into their proper module. This cannot be @@ -265,6 +276,16 @@ impl<'context> Elaborator<'context> { self.elaborate_global(global); } + // After everything is collected, we can elaborate our generated items. + // It may be better to inline these within `items` entirely since elaborating them + // all here means any globals will not see these. Inlining them completely within `items` + // means we must be more careful about missing any additional items that need to be already + // elaborated. E.g. if a new struct is created, we've already passed the code path to + // elaborate them. + if !generated_items.is_empty() { + self.elaborate_items(generated_items); + } + for functions in items.functions { self.elaborate_functions(functions); } @@ -316,33 +337,34 @@ impl<'context> Elaborator<'context> { FunctionBody::Resolving => return, }; - let old_function = std::mem::replace(&mut self.current_function, Some(id)); - - // Without this, impl methods can accidentally be placed in contracts. See #3254 - let was_in_contract = self.in_contract; - if self.self_type.is_some() { - self.in_contract = false; - } - self.scopes.start_function(); let old_item = std::mem::replace(&mut self.current_item, Some(DependencyId::Function(id))); let func_meta = func_meta.clone(); self.trait_bounds = func_meta.trait_constraints.clone(); - - if self.interner.function_modifiers(&id).is_unconstrained { - self.in_unconstrained_fn = true; + self.function_context.push(FunctionContext::default()); + + // Introduce all numeric generics into scope + for generic in &func_meta.all_generics { + if let Kind::Numeric(typ) = &generic.kind { + let definition = DefinitionKind::GenericType(generic.type_var.clone()); + let ident = Ident::new(generic.name.to_string(), generic.span); + let hir_ident = + self.add_variable_decl_inner(ident, false, false, false, definition); + self.interner.push_definition_type(hir_ident.id, *typ.clone()); + } } // The DefinitionIds for each parameter were already created in define_function_meta // so we need to reintroduce the same IDs into scope here. for parameter in &func_meta.parameter_idents { let name = self.interner.definition_name(parameter.id).to_owned(); - self.add_existing_variable_to_scope(name, parameter.clone()); + self.add_existing_variable_to_scope(name, parameter.clone(), true); } self.generics = func_meta.all_generics.clone(); + self.declare_numeric_generics(&func_meta.parameters, func_meta.return_type()); self.add_trait_constraints_to_scope(&func_meta); @@ -363,34 +385,11 @@ impl<'context> Elaborator<'context> { self.type_check_function_body(body_type, &func_meta, hir_func.as_expr()); } - // Default any type variables that still need defaulting. + // Default any type variables that still need defaulting and + // verify any remaining trait constraints arising from the function body. // This is done before trait impl search since leaving them bindable can lead to errors // when multiple impls are available. Instead we default first to choose the Field or u64 impl. - for typ in &self.type_variables { - if let Type::TypeVariable(variable, kind) = typ.follow_bindings() { - let msg = "TypeChecker should only track defaultable type vars"; - variable.bind(kind.default_type().expect(msg)); - } - } - - // Verify any remaining trait constraints arising from the function body - for (mut constraint, expr_id) in std::mem::take(&mut self.trait_constraints) { - let span = self.interner.expr_span(&expr_id); - - if matches!(&constraint.typ, Type::MutableReference(_)) { - let (_, dereferenced_typ) = - self.insert_auto_dereferences(expr_id, constraint.typ.clone()); - constraint.typ = dereferenced_typ; - } - - self.verify_trait_constraint( - &constraint.typ, - constraint.trait_id, - &constraint.trait_generics, - expr_id, - span, - ); - } + self.check_and_pop_function_context(); // Now remove all the `where` clause constraints we added for constraint in &func_meta.trait_constraints { @@ -413,14 +412,41 @@ impl<'context> Elaborator<'context> { meta.function_body = FunctionBody::Resolved; self.trait_bounds.clear(); - self.type_variables.clear(); - self.in_unconstrained_fn = false; self.interner.update_fn(id, hir_func); - self.in_contract = was_in_contract; - self.current_function = old_function; self.current_item = old_item; } + /// Defaults all type variables used in this function context then solves + /// all still-unsolved trait constraints in this context. + fn check_and_pop_function_context(&mut self) { + let context = self.function_context.pop().expect("Imbalanced function_context pushes"); + + for typ in context.type_variables { + if let Type::TypeVariable(variable, kind) = typ.follow_bindings() { + let msg = "TypeChecker should only track defaultable type vars"; + variable.bind(kind.default_type().expect(msg)); + } + } + + for (mut constraint, expr_id) in context.trait_constraints { + let span = self.interner.expr_span(&expr_id); + + if matches!(&constraint.typ, Type::MutableReference(_)) { + let (_, dereferenced_typ) = + self.insert_auto_dereferences(expr_id, constraint.typ.clone()); + constraint.typ = dereferenced_typ; + } + + self.verify_trait_constraint( + &constraint.typ, + constraint.trait_id, + &constraint.trait_generics, + expr_id, + span, + ); + } + } + /// This turns function parameters of the form: /// `fn foo(x: impl Bar)` /// @@ -439,7 +465,7 @@ impl<'context> Elaborator<'context> { generics.push(new_generic.clone()); let name = format!("impl {trait_path}"); - let generic_type = Type::NamedGeneric(new_generic, Rc::new(name)); + let generic_type = Type::NamedGeneric(new_generic, Rc::new(name), Kind::Normal); let trait_bound = TraitBound { trait_path, trait_id: None, trait_generics }; if let Some(new_constraint) = self.resolve_trait_bound(&trait_bound, generic_type.clone()) { @@ -456,25 +482,56 @@ impl<'context> Elaborator<'context> { // Map the generic to a fresh type variable let id = self.interner.next_type_variable_id(); let typevar = TypeVariable::unbound(id); - let span = generic.0.span(); + let ident = generic.ident(); + let span = ident.0.span(); + + // Resolve the generic's kind + let kind = self.resolve_generic_kind(generic); // Check for name collisions of this generic - let name = Rc::new(generic.0.contents.clone()); + let name = Rc::new(ident.0.contents.clone()); + + let resolved_generic = + ResolvedGeneric { name: name.clone(), type_var: typevar.clone(), kind, span }; - if let Some((_, _, first_span)) = self.find_generic(&name) { + if let Some(generic) = self.find_generic(&name) { self.push_err(ResolverError::DuplicateDefinition { - name: generic.0.contents.clone(), - first_span: *first_span, + name: ident.0.contents.clone(), + first_span: generic.span, second_span: span, }); } else { - self.generics.push((name, typevar.clone(), span)); + self.generics.push(resolved_generic.clone()); } - typevar + resolved_generic }) } + /// Return the kind of an unresolved generic. + /// If a numeric generic has been specified, resolve the annotated type to make + /// sure only primitive numeric types are being used. + pub(super) fn resolve_generic_kind(&mut self, generic: &UnresolvedGeneric) -> Kind { + if let UnresolvedGeneric::Numeric { ident, typ } = generic { + let typ = typ.clone(); + let typ = if typ.is_type_expression() { + self.resolve_type_inner(typ, &Kind::Numeric(Box::new(Type::default_int_type()))) + } else { + self.resolve_type(typ.clone()) + }; + if !matches!(typ, Type::FieldElement | Type::Integer(_, _)) { + let unsupported_typ_err = ResolverError::UnsupportedNumericGenericType { + ident: ident.clone(), + typ: typ.clone(), + }; + self.push_err(unsupported_typ_err); + } + Kind::Numeric(Box::new(typ)) + } else { + Kind::Normal + } + } + fn push_err(&mut self, error: impl Into) { self.errors.push((error.into(), self.file)); } @@ -488,7 +545,7 @@ impl<'context> Elaborator<'context> { fn resolve_trait_by_path(&mut self, path: Path) -> Option { let path_resolver = StandardPathResolver::new(self.module_id()); - let error = match path_resolver.resolve(self.def_maps, path.clone()) { + let error = match path_resolver.resolve(self.def_maps, path.clone(), &mut None) { Ok(PathResolution { module_def_id: ModuleDefId::TraitId(trait_id), error }) => { if let Some(error) = error { self.push_err(error); @@ -523,12 +580,20 @@ impl<'context> Elaborator<'context> { } fn resolve_trait_bound(&mut self, bound: &TraitBound, typ: Type) -> Option { - let trait_generics = vecmap(&bound.trait_generics, |typ| self.resolve_type(typ.clone())); + let the_trait = self.lookup_trait_or_error(bound.trait_path.clone())?; + + let resolved_generics = &the_trait.generics.clone(); + assert_eq!(resolved_generics.len(), bound.trait_generics.len()); + let generics_with_types = resolved_generics.iter().zip(&bound.trait_generics); + let trait_generics = vecmap(generics_with_types, |(generic, typ)| { + self.resolve_type_inner(typ.clone(), &generic.kind) + }); - let span = bound.trait_path.span(); let the_trait = self.lookup_trait_or_error(bound.trait_path.clone())?; let trait_id = the_trait.id; + let span = bound.trait_path.span(); + let expected_generics = the_trait.generics.len(); let actual_generics = trait_generics.len(); @@ -555,13 +620,13 @@ impl<'context> Elaborator<'context> { func_id: FuncId, is_trait_function: bool, ) { - self.current_function = Some(func_id); - - // Without this, impl methods can accidentally be placed in contracts. See #3254 - let was_in_contract = self.in_contract; - if self.self_type.is_some() { - self.in_contract = false; - } + let in_contract = if self.self_type.is_some() { + // Without this, impl methods can accidentally be placed in contracts. + // See: https://github.com/noir-lang/noir/issues/3254 + false + } else { + self.in_contract() + }; self.scopes.start_function(); self.current_item = Some(DependencyId::Function(func_id)); @@ -570,12 +635,13 @@ impl<'context> Elaborator<'context> { let id = self.interner.function_definition_id(func_id); let name_ident = HirIdent::non_trait_method(id, location); - let is_entry_point = self.is_entry_point_function(func); + let is_entry_point = self.is_entry_point_function(func, in_contract); self.run_lint(|_| lints::inlining_attributes(func).map(Into::into)); self.run_lint(|_| lints::missing_pub(func, is_entry_point).map(Into::into)); self.run_lint(|elaborator| { - lints::unnecessary_pub_return(func, elaborator.pub_allowed(func)).map(Into::into) + lints::unnecessary_pub_return(func, elaborator.pub_allowed(func, in_contract)) + .map(Into::into) }); self.run_lint(|_| lints::oracle_not_marked_unconstrained(func).map(Into::into)); self.run_lint(|elaborator| { @@ -591,12 +657,12 @@ impl<'context> Elaborator<'context> { let has_no_predicates_attribute = func.attributes().is_no_predicates(); let should_fold = func.attributes().is_foldable(); let has_inline_attribute = has_no_predicates_attribute || should_fold; - let is_pub_allowed = self.pub_allowed(func); + let is_pub_allowed = self.pub_allowed(func, in_contract); self.add_generics(&func.def.generics); let mut trait_constraints = self.resolve_trait_constraints(&func.def.where_clause); - let mut generics = vecmap(&self.generics, |(_, typevar, _)| typevar.clone()); + let mut generics = vecmap(&self.generics, |generic| generic.type_var.clone()); let mut parameters = Vec::new(); let mut parameter_types = Vec::new(); let mut parameter_idents = Vec::new(); @@ -612,7 +678,7 @@ impl<'context> Elaborator<'context> { UnresolvedTypeData::TraitAsType(path, args) => { self.desugar_impl_trait_arg(path, args, &mut generics, &mut trait_constraints) } - _ => self.resolve_type_inner(typ), + _ => self.resolve_type_inner(typ, &Kind::Normal), }; self.check_if_type_is_valid_for_program_input( @@ -621,6 +687,7 @@ impl<'context> Elaborator<'context> { has_inline_attribute, type_span, ); + let pattern = self.elaborate_pattern_and_store_ids( pattern, typ.clone(), @@ -645,8 +712,7 @@ impl<'context> Elaborator<'context> { let direct_generics = func.def.generics.iter(); let direct_generics = direct_generics - .filter_map(|generic| self.find_generic(&generic.0.contents)) - .map(|(name, typevar, _span)| (name.clone(), typevar.clone())) + .filter_map(|generic| self.find_generic(&generic.ident().0.contents).cloned()) .collect(); let statements = std::mem::take(&mut func.def.body.statements); @@ -669,12 +735,11 @@ impl<'context> Elaborator<'context> { is_entry_point, is_trait_function, has_inline_attribute, + source_crate: self.crate_id, function_body: FunctionBody::Unresolved(func.kind, body, func.def.span), }; self.interner.push_fn_meta(meta, func_id); - self.in_contract = was_in_contract; - self.current_function = None; self.scopes.end_function(); self.current_item = None; } @@ -698,18 +763,30 @@ impl<'context> Elaborator<'context> { /// True if the `pub` keyword is allowed on parameters in this function /// `pub` on function parameters is only allowed for entry point functions - fn pub_allowed(&self, func: &NoirFunction) -> bool { - self.is_entry_point_function(func) || func.attributes().is_foldable() + fn pub_allowed(&self, func: &NoirFunction, in_contract: bool) -> bool { + self.is_entry_point_function(func, in_contract) || func.attributes().is_foldable() + } + + /// Returns `true` if the current module is a contract. + /// + /// This is usually determined by `self.module_id()`, but it can + /// be overridden for impls. Impls are an odd case since the methods within resolve + /// as if they're in the parent module, but should be placed in a child module. + /// Since they should be within a child module, they should be elaborated as if + /// `in_contract` is `false` so we can still resolve them in the parent module without them being in a contract. + fn in_contract(&self) -> bool { + self.module_id().module(self.def_maps).is_contract } - fn is_entry_point_function(&self, func: &NoirFunction) -> bool { - if self.in_contract { + fn is_entry_point_function(&self, func: &NoirFunction, in_contract: bool) -> bool { + if in_contract { func.attributes().is_contract_entry_point() } else { func.name() == MAIN_FUNCTION } } + // TODO(https://github.com/noir-lang/noir/issues/5156): Remove implicit numeric generics fn declare_numeric_generics(&mut self, params: &Parameters, return_type: &Type) { if self.generics.is_empty() { return; @@ -722,12 +799,27 @@ impl<'context> Elaborator<'context> { // We can fail to find the generic in self.generics if it is an implicit one created // by the compiler. This can happen when, e.g. eliding array lengths using the slice // syntax [T]. - if let Some((name, _, span)) = - self.generics.iter().find(|(name, _, _)| name.as_ref() == &name_to_find) + if let Some(ResolvedGeneric { name, span, kind, .. }) = + self.generics.iter_mut().find(|generic| generic.name.as_ref() == &name_to_find) { + let scope = self.scopes.get_mut_scope(); + let value = scope.find(&name_to_find); + if value.is_some() { + // With the addition of explicit numeric generics we do not want to introduce numeric generics in this manner + // However, this is going to be a big breaking change so for now we simply issue a warning while users have time + // to transition to the new syntax + // e.g. this code would break with a duplicate definition error: + // ``` + // fn foo(arr: [Field; N]) { } + // ``` + continue; + } + *kind = Kind::Numeric(Box::new(Type::default_int_type())); let ident = Ident::new(name.to_string(), *span); let definition = DefinitionKind::GenericType(type_variable); - self.add_variable_decl_inner(ident, false, false, false, definition); + self.add_variable_decl_inner(ident.clone(), false, false, false, definition); + + self.push_err(ResolverError::UseExplicitNumericGeneric { ident }); } } } @@ -753,7 +845,7 @@ impl<'context> Elaborator<'context> { } } - fn elaborate_impls(&mut self, impls: Vec<(Vec, Span, UnresolvedFunctions)>) { + fn elaborate_impls(&mut self, impls: Vec<(UnresolvedGenerics, Span, UnresolvedFunctions)>) { for (_, _, functions) in impls { self.file = functions.file_id; self.recover_generics(|this| this.elaborate_functions(functions)); @@ -783,7 +875,7 @@ impl<'context> Elaborator<'context> { fn collect_impls( &mut self, module: LocalModuleId, - impls: &mut [(Vec, Span, UnresolvedFunctions)], + impls: &mut [(UnresolvedGenerics, Span, UnresolvedFunctions)], ) { self.local_module = module; @@ -800,7 +892,6 @@ impl<'context> Elaborator<'context> { self.local_module = trait_impl.module_id; self.file = trait_impl.file_id; self.current_trait_impl = trait_impl.impl_id; - trait_impl.trait_id = self.resolve_trait_by_path(trait_impl.trait_path.clone()); let self_type = trait_impl.methods.self_type.clone(); let self_type = @@ -844,7 +935,7 @@ impl<'context> Elaborator<'context> { methods, }); - let generics = vecmap(&self.generics, |(_, type_variable, _)| type_variable.clone()); + let generics = vecmap(&self.generics, |generic| generic.type_var.clone()); if let Err((prev_span, prev_file)) = self.interner.add_trait_implementation( self_type.clone(), @@ -868,6 +959,7 @@ impl<'context> Elaborator<'context> { } self.generics.clear(); + self.current_trait_impl = None; self.self_type = None; } @@ -1087,11 +1179,18 @@ impl<'context> Elaborator<'context> { self.generics.clear(); } - fn collect_struct_definitions(&mut self, structs: BTreeMap) { + fn collect_struct_definitions( + &mut self, + structs: BTreeMap, + ) -> CollectedItems { // This is necessary to avoid cloning the entire struct map // when adding checks after each struct field is resolved. let struct_ids = structs.keys().copied().collect::>(); + // This will contain any additional top-level items that are generated at compile-time + // via macros. This often includes derived trait impls. + let mut generated_items = CollectedItems::default(); + // Resolve each field in each struct. // Each struct should already be present in the NodeInterner after def collection. for (type_id, mut typ) in structs { @@ -1100,14 +1199,41 @@ impl<'context> Elaborator<'context> { let attributes = std::mem::take(&mut typ.struct_def.attributes); let span = typ.struct_def.span; - let (generics, fields) = self.resolve_struct_fields(typ.struct_def, type_id); + let fields = self.resolve_struct_fields(typ.struct_def, type_id); + let fields_len = fields.len(); self.interner.update_struct(type_id, |struct_def| { struct_def.set_fields(fields); - struct_def.generics = generics; + + // TODO(https://github.com/noir-lang/noir/issues/5156): Remove this with implicit numeric generics + // This is only necessary for resolving named types when implicit numeric generics are used. + let mut found_names = Vec::new(); + struct_def.find_numeric_generics_in_fields(&mut found_names); + for generic in struct_def.generics.iter_mut() { + for found_generic in found_names.iter() { + if found_generic == generic.name.as_str() { + if matches!(generic.kind, Kind::Normal) { + let ident = Ident::new(generic.name.to_string(), generic.span); + self.errors.push(( + CompilationError::ResolverError( + ResolverError::UseExplicitNumericGeneric { ident }, + ), + self.file, + )); + generic.kind = Kind::Numeric(Box::new(Type::default_int_type())); + } + break; + } + } + } }); - self.run_comptime_attributes_on_struct(attributes, type_id, span); + for field_index in 0..fields_len { + self.interner + .add_definition_location(ReferenceId::StructMember(type_id, field_index)); + } + + self.run_comptime_attributes_on_struct(attributes, type_id, span, &mut generated_items); } // Check whether the struct fields have nested slices @@ -1129,6 +1255,8 @@ impl<'context> Elaborator<'context> { } } } + + generated_items } fn run_comptime_attributes_on_struct( @@ -1136,48 +1264,73 @@ impl<'context> Elaborator<'context> { attributes: Vec, struct_id: StructId, span: Span, + generated_items: &mut CollectedItems, ) { for attribute in attributes { if let SecondaryAttribute::Custom(name) = attribute { - match self.lookup_global(Path::from_single(name, span)) { - Ok(id) => { - let definition = self.interner.definition(id); - if let DefinitionKind::Function(function) = &definition.kind { - let function = *function; - let mut interpreter = - Interpreter::new(self.interner, &mut self.comptime_scopes); - - let location = Location::new(span, self.file); - let arguments = vec![(Value::TypeDefinition(struct_id), location)]; - let result = interpreter.call_function(function, arguments, location); - if let Err(error) = result { - self.errors.push(error.into_compilation_error_pair()); - } - } else { - self.push_err(ResolverError::NonFunctionInAnnotation { span }); - } - } - Err(_) => self.push_err(ResolverError::UnknownAnnotation { span }), + if let Err(error) = + self.run_comptime_attribute_on_struct(name, struct_id, span, generated_items) + { + self.errors.push(error); } } } } + fn run_comptime_attribute_on_struct( + &mut self, + attribute: String, + struct_id: StructId, + span: Span, + generated_items: &mut CollectedItems, + ) -> Result<(), (CompilationError, FileId)> { + let id = self + .lookup_global(Path::from_single(attribute, span)) + .map_err(|_| (ResolverError::UnknownAnnotation { span }.into(), self.file))?; + + let definition = self.interner.definition(id); + let DefinitionKind::Function(function) = definition.kind else { + return Err((ResolverError::NonFunctionInAnnotation { span }.into(), self.file)); + }; + let location = Location::new(span, self.file); + let mut interpreter_errors = vec![]; + let mut interpreter = self.setup_interpreter(&mut interpreter_errors); + let arguments = vec![(Value::StructDefinition(struct_id), location)]; + + let value = interpreter + .call_function(function, arguments, TypeBindings::new(), location) + .map_err(|error| error.into_compilation_error_pair())?; + self.include_interpreter_errors(interpreter_errors); + + if value != Value::Unit { + let items = value + .into_top_level_items(location) + .map_err(|error| error.into_compilation_error_pair())?; + + self.add_items(items, generated_items, location); + } + + Ok(()) + } + pub fn resolve_struct_fields( &mut self, unresolved: NoirStruct, struct_id: StructId, - ) -> (Generics, Vec<(Ident, Type)>) { + ) -> Vec<(Ident, Type)> { self.recover_generics(|this| { - let generics = this.add_generics(&unresolved.generics); - this.current_item = Some(DependencyId::Struct(struct_id)); this.resolving_ids.insert(struct_id); + + let struct_def = this.interner.get_struct(struct_id); + this.add_existing_generics(&unresolved.generics, &struct_def.borrow().generics); + let fields = vecmap(unresolved.fields, |(ident, typ)| (ident, this.resolve_type(typ))); + this.resolving_ids.remove(&struct_id); - (generics, fields) + fields }) } @@ -1190,7 +1343,7 @@ impl<'context> Elaborator<'context> { self.current_item = Some(DependencyId::Global(global_id)); let let_stmt = global.stmt_def; - if !self.in_contract + if !self.in_contract() && let_stmt.attributes.iter().any(|attr| matches!(attr, SecondaryAttribute::Abi(_))) { let span = let_stmt.pattern.span(); @@ -1212,10 +1365,8 @@ impl<'context> Elaborator<'context> { self.elaborate_comptime_global(global_id); } - // Avoid defaulting the types of globals here since they may be used in any function. - // Otherwise we may prematurely default to a Field inside the next function if this - // global was unused there, even if it is consistently used as a u8 everywhere else. - self.type_variables.clear(); + self.interner.add_definition_location(ReferenceId::Global(global_id)); + self.local_module = old_module; self.file = old_file; self.current_item = old_item; @@ -1230,8 +1381,8 @@ impl<'context> Elaborator<'context> { let global = self.interner.get_global(global_id); let definition_id = global.definition_id; let location = global.location; - - let mut interpreter = Interpreter::new(self.interner, &mut self.comptime_scopes); + let mut interpreter_errors = vec![]; + let mut interpreter = self.setup_interpreter(&mut interpreter_errors); if let Err(error) = interpreter.evaluate_let(let_statement) { self.errors.push(error.into_compilation_error_pair()); @@ -1240,8 +1391,13 @@ impl<'context> Elaborator<'context> { .lookup_id(definition_id, location) .expect("The global should be defined since evaluate_let did not error"); + self.debug_comptime(location, |interner| { + interner.get_global(global_id).let_statement.to_display_ast(interner).kind + }); + self.interner.get_global_mut(global_id).value = Some(value); } + self.include_interpreter_errors(interpreter_errors); } fn define_function_metas( @@ -1273,12 +1429,30 @@ impl<'context> Elaborator<'context> { self.file = trait_impl.file_id; self.local_module = trait_impl.module_id; + let trait_id = self.resolve_trait_by_path(trait_impl.trait_path.clone()); + trait_impl.trait_id = trait_id; let unresolved_type = &trait_impl.object_type; + self.add_generics(&trait_impl.generics); trait_impl.resolved_generics = self.generics.clone(); - let trait_generics = - vecmap(&trait_impl.trait_generics, |generic| self.resolve_type(generic.clone())); + // Fetch trait constraints here + let trait_generics = if let Some(trait_id) = trait_impl.trait_id { + let trait_def = self.interner.get_trait(trait_id); + let resolved_generics = trait_def.generics.clone(); + assert_eq!(resolved_generics.len(), trait_impl.trait_generics.len()); + trait_impl + .trait_generics + .iter() + .enumerate() + .map(|(i, generic)| { + self.resolve_type_inner(generic.clone(), &resolved_generics[i].kind) + }) + .collect() + } else { + // We still resolve as to continue type checking + vecmap(&trait_impl.trait_generics, |generic| self.resolve_type(generic.clone())) + }; trait_impl.resolved_trait_generics = trait_generics; @@ -1294,6 +1468,17 @@ impl<'context> Elaborator<'context> { trait_impl.resolved_object_type = self.self_type.take(); trait_impl.impl_id = self.current_trait_impl.take(); self.generics.clear(); + + if let Some(trait_id) = trait_id { + let trait_name = trait_impl.trait_path.last_segment(); + + let referenced = ReferenceId::Trait(trait_id); + let reference = ReferenceId::Reference( + Location::new(trait_name.span(), trait_impl.file_id), + trait_name.is_self_type_name(), + ); + self.interner.add_reference(referenced, reference); + } } } @@ -1308,6 +1493,34 @@ impl<'context> Elaborator<'context> { } } + fn include_interpreter_errors(&mut self, errors: Vec) { + self.errors.extend(errors.into_iter().map(InterpreterError::into_compilation_error_pair)); + } + + /// True if we're currently within a `comptime` block, function, or global + fn in_comptime_context(&self) -> bool { + // The first context is the global context, followed by the function-specific context. + // Any context after that is a `comptime {}` block's. + if self.function_context.len() > 2 { + return true; + } + + match self.current_item { + Some(DependencyId::Function(id)) => self.interner.function_modifiers(&id).is_comptime, + Some(DependencyId::Global(id)) => self.interner.get_global_definition(id).comptime, + _ => false, + } + } + + /// True if we're currently within a constrained function. + /// Defaults to `true` if the current function is unknown. + fn in_constrained_function(&self) -> bool { + self.current_item.map_or(true, |id| match id { + DependencyId::Function(id) => !self.interner.function_modifiers(&id).is_unconstrained, + _ => true, + }) + } + /// Filters out comptime items from non-comptime items. /// Returns a pair of (comptime items, non-comptime items) fn filter_comptime_items(mut items: CollectedItems) -> (CollectedItems, CollectedItems) { @@ -1349,10 +1562,116 @@ impl<'context> Elaborator<'context> { traits: BTreeMap::new(), trait_impls: Vec::new(), globals: Vec::new(), - impls: std::collections::HashMap::new(), + impls: rustc_hash::FxHashMap::default(), }; items.functions = function_sets; (comptime, items) } + + fn add_items( + &mut self, + items: Vec, + generated_items: &mut CollectedItems, + location: Location, + ) { + for item in items { + match item { + TopLevelStatement::Function(function) => { + let id = self.interner.push_empty_fn(); + let module = self.module_id(); + self.interner.push_function(id, &function.def, module, location); + let functions = vec![(self.local_module, id, function)]; + generated_items.functions.push(UnresolvedFunctions { + file_id: self.file, + functions, + trait_id: None, + self_type: None, + }); + } + TopLevelStatement::TraitImpl(mut trait_impl) => { + let methods = dc_mod::collect_trait_impl_functions( + self.interner, + &mut trait_impl, + self.crate_id, + self.file, + self.local_module, + ); + + generated_items.trait_impls.push(UnresolvedTraitImpl { + file_id: self.file, + module_id: self.local_module, + trait_generics: trait_impl.trait_generics, + trait_path: trait_impl.trait_name, + object_type: trait_impl.object_type, + methods, + generics: trait_impl.impl_generics, + where_clause: trait_impl.where_clause, + + // These last fields are filled in later + trait_id: None, + impl_id: None, + resolved_object_type: None, + resolved_generics: Vec::new(), + resolved_trait_generics: Vec::new(), + }); + } + TopLevelStatement::Global(global) => { + let (global, error) = dc_mod::collect_global( + self.interner, + self.def_maps.get_mut(&self.crate_id).unwrap(), + global, + self.file, + self.local_module, + ); + + generated_items.globals.push(global); + if let Some(error) = error { + self.errors.push(error); + } + } + // Assume that an error has already been issued + TopLevelStatement::Error => (), + + TopLevelStatement::Module(_) + | TopLevelStatement::Import(_) + | TopLevelStatement::Struct(_) + | TopLevelStatement::Trait(_) + | TopLevelStatement::Impl(_) + | TopLevelStatement::TypeAlias(_) + | TopLevelStatement::SubModule(_) => { + let item = item.to_string(); + let error = InterpreterError::UnsupportedTopLevelItemUnquote { item, location }; + self.errors.push(error.into_compilation_error_pair()); + } + } + } + } + + fn setup_interpreter<'a>( + &'a mut self, + interpreter_errors: &'a mut Vec, + ) -> Interpreter { + Interpreter::new( + self.interner, + &mut self.comptime_scopes, + self.crate_id, + self.debug_comptime_in_file, + interpreter_errors, + ) + } + + fn debug_comptime T>( + &mut self, + location: Location, + mut expr_f: F, + ) { + if Some(location.file) == self.debug_comptime_in_file { + let displayed_expr = expr_f(self.interner); + self.errors.push(( + InterpreterError::debug_evaluate_comptime(displayed_expr, location).into(), + location.file, + )); + } + } } diff --git a/compiler/noirc_frontend/src/elaborator/patterns.rs b/compiler/noirc_frontend/src/elaborator/patterns.rs index e337726b579..7c920230b9d 100644 --- a/compiler/noirc_frontend/src/elaborator/patterns.rs +++ b/compiler/noirc_frontend/src/elaborator/patterns.rs @@ -3,19 +3,21 @@ use noirc_errors::{Location, Span}; use rustc_hash::FxHashSet as HashSet; use crate::{ - ast::ERROR_IDENT, + ast::{UnresolvedType, ERROR_IDENT}, hir::{ + comptime::Interpreter, def_collector::dc_crate::CompilationError, resolution::errors::ResolverError, type_check::{Source, TypeCheckError}, }, hir_def::{ expr::{HirIdent, ImplKind}, - function::FunctionBody, stmt::HirPattern, }, macros_api::{HirExpression, Ident, Path, Pattern}, - node_interner::{DefinitionId, DefinitionKind, DependencyId, ExprId, GlobalId, TraitImplKind}, + node_interner::{ + DefinitionId, DefinitionKind, ExprId, FuncId, GlobalId, ReferenceId, TraitImplKind, + }, Shared, StructType, Type, TypeBindings, }; @@ -158,6 +160,9 @@ impl<'context> Elaborator<'context> { mutable: Option, new_definitions: &mut Vec, ) -> HirPattern { + let name_span = name.last_segment().span(); + let is_self_type = name.last_segment().is_self_type_name(); + let error_identifier = |this: &mut Self| { // Must create a name here to return a HirPattern::Identifier. Allowing // shadowing here lets us avoid further errors if we define ERROR_IDENT @@ -197,6 +202,18 @@ impl<'context> Elaborator<'context> { new_definitions, ); + let struct_id = struct_type.borrow().id; + + let referenced = ReferenceId::Struct(struct_id); + let reference = ReferenceId::Reference(Location::new(name_span, self.file), is_self_type); + self.interner.add_reference(referenced, reference); + + for (field_index, field) in fields.iter().enumerate() { + let referenced = ReferenceId::StructMember(struct_id, field_index); + let reference = ReferenceId::Reference(Location::new(field.0.span(), self.file), false); + self.interner.add_reference(referenced, reference); + } + HirPattern::Struct(expected_type, fields, location) } @@ -280,19 +297,21 @@ impl<'context> Elaborator<'context> { } let location = Location::new(name.span(), self.file); + let name = name.0.contents; + let comptime = self.in_comptime_context(); let id = - self.interner.push_definition(name.0.contents.clone(), mutable, definition, location); + self.interner.push_definition(name.clone(), mutable, comptime, definition, location); let ident = HirIdent::non_trait_method(id, location); let resolver_meta = ResolverMeta { num_times_used: 0, ident: ident.clone(), warn_if_unused }; let scope = self.scopes.get_mut_scope(); - let old_value = scope.add_key_value(name.0.contents.clone(), resolver_meta); + let old_value = scope.add_key_value(name.clone(), resolver_meta); if !allow_shadowing { if let Some(old_value) = old_value { self.push_err(ResolverError::DuplicateDefinition { - name: name.0.contents, + name, first_span: old_value.ident.location.span, second_span: location.span, }); @@ -302,9 +321,14 @@ impl<'context> Elaborator<'context> { ident } - pub fn add_existing_variable_to_scope(&mut self, name: String, ident: HirIdent) { + pub fn add_existing_variable_to_scope( + &mut self, + name: String, + ident: HirIdent, + warn_if_unused: bool, + ) { let second_span = ident.location.span; - let resolver_meta = ResolverMeta { num_times_used: 0, ident, warn_if_unused: true }; + let resolver_meta = ResolverMeta { num_times_used: 0, ident, warn_if_unused }; let old_value = self.scopes.get_mut_scope().add_key_value(name.clone(), resolver_meta); @@ -319,6 +343,7 @@ impl<'context> Elaborator<'context> { name: Ident, definition: DefinitionKind, ) -> HirIdent { + let comptime = self.in_comptime_context(); let scope = self.scopes.get_mut_scope(); // This check is necessary to maintain the same definition ids in the interner. Currently, each function uses a new resolver that has its own ScopeForest and thus global scope. @@ -340,8 +365,8 @@ impl<'context> Elaborator<'context> { (hir_ident, resolver_meta) } else { let location = Location::new(name.span(), self.file); - let id = - self.interner.push_definition(name.0.contents.clone(), false, definition, location); + let name = name.0.contents.clone(); + let id = self.interner.push_definition(name, false, comptime, definition, location); let ident = HirIdent::non_trait_method(id, location); let resolver_meta = ResolverMeta { num_times_used: 0, ident: ident.clone(), warn_if_unused: true }; @@ -383,17 +408,76 @@ impl<'context> Elaborator<'context> { } } + /// Resolve generics using the expected kinds of the function we are calling + pub(super) fn resolve_turbofish_generics( + &mut self, + func_id: &FuncId, + unresolved_turbofish: Option>, + span: Span, + ) -> Option> { + let direct_generics = self.interner.function_meta(func_id).direct_generics.clone(); + + unresolved_turbofish.map(|option_inner| { + if option_inner.len() != direct_generics.len() { + let type_check_err = TypeCheckError::IncorrectTurbofishGenericCount { + expected_count: direct_generics.len(), + actual_count: option_inner.len(), + span, + }; + self.push_err(type_check_err); + } + + let generics_with_types = direct_generics.iter().zip(option_inner); + vecmap(generics_with_types, |(generic, unresolved_type)| { + self.resolve_type_inner(unresolved_type, &generic.kind) + }) + }) + } + pub(super) fn elaborate_variable( &mut self, variable: Path, - generics: Option>, + unresolved_turbofish: Option>, ) -> (ExprId, Type) { let span = variable.span; let expr = self.resolve_variable(variable); + let definition_id = expr.id; + + let definition_kind = + self.interner.try_definition(definition_id).map(|definition| definition.kind.clone()); + + // Resolve any generics if we the variable we have resolved is a function + // and if the turbofish operator was used. + let generics = definition_kind.and_then(|definition_kind| match &definition_kind { + DefinitionKind::Function(function) => { + self.resolve_turbofish_generics(function, unresolved_turbofish, span) + } + _ => None, + }); + let id = self.interner.push_expr(HirExpression::Ident(expr.clone(), generics.clone())); + self.interner.push_expr_location(id, span, self.file); let typ = self.type_check_variable(expr, id, generics); self.interner.push_expr_type(id, typ.clone()); + + // Comptime variables must be replaced with their values + if let Some(definition) = self.interner.try_definition(definition_id) { + if definition.comptime && !self.in_comptime_context() { + let mut interpreter_errors = vec![]; + let mut interpreter = Interpreter::new( + self.interner, + &mut self.comptime_scopes, + self.crate_id, + self.debug_comptime_in_file, + &mut interpreter_errors, + ); + let value = interpreter.evaluate(id); + self.include_interpreter_errors(interpreter_errors); + return self.inline_comptime_value(value, span); + } + } + (id, typ) } @@ -409,24 +493,21 @@ impl<'context> Elaborator<'context> { // Otherwise, then it is referring to an Identifier // This lookup allows support of such statements: let x = foo::bar::SOME_GLOBAL + 10; // If the expression is a singular indent, we search the resolver's current scope as normal. + let span = path.span(); + let is_self_type_name = path.last_segment().is_self_type_name(); let (hir_ident, var_scope_index) = self.get_ident_from_path(path); if hir_ident.id != DefinitionId::dummy_id() { match self.interner.definition(hir_ident.id).kind { - DefinitionKind::Function(id) => { + DefinitionKind::Function(func_id) => { if let Some(current_item) = self.current_item { - // Lazily evaluate functions found within globals if necessary. - // Otherwise if we later attempt to evaluate the global it will - // see an empty function body. - if matches!(current_item, DependencyId::Global(_)) { - let meta = self.interner.function_meta(&id); - - if matches!(&meta.function_body, FunctionBody::Unresolved(..)) { - self.elaborate_function(id); - } - } - self.interner.add_function_dependency(current_item, id); + self.interner.add_function_dependency(current_item, func_id); } + + let variable = + ReferenceId::Reference(hir_ident.location, is_self_type_name); + let function = ReferenceId::Function(func_id); + self.interner.add_reference(function, variable); } DefinitionKind::Global(global_id) => { if let Some(global) = self.unresolved_globals.remove(&global_id) { @@ -435,6 +516,11 @@ impl<'context> Elaborator<'context> { if let Some(current_item) = self.current_item { self.interner.add_global_dependency(current_item, global_id); } + + let variable = + ReferenceId::Reference(hir_ident.location, is_self_type_name); + let global = ReferenceId::Global(global_id); + self.interner.add_reference(global, variable); } DefinitionKind::GenericType(_) => { // Initialize numeric generics to a polymorphic integer type in case @@ -449,6 +535,11 @@ impl<'context> Elaborator<'context> { DefinitionKind::Local(_) => { // only local variables can be captured by closures. self.resolve_local_variable(hir_ident.clone(), var_scope_index); + + let referenced = ReferenceId::Local(hir_ident.id); + let reference = + ReferenceId::Reference(Location::new(span, self.file), false); + self.interner.add_reference(referenced, reference); } } } @@ -475,8 +566,8 @@ impl<'context> Elaborator<'context> { for (param, arg) in the_trait.generics.iter().zip(&constraint.trait_generics) { // Avoid binding t = t - if !arg.occurs(param.id()) { - bindings.insert(param.id(), (param.clone(), arg.clone())); + if !arg.occurs(param.type_var.id()) { + bindings.insert(param.type_var.id(), (param.type_var.clone(), arg.clone())); } } @@ -521,7 +612,7 @@ impl<'context> Elaborator<'context> { for mut constraint in function.trait_constraints.clone() { constraint.apply_bindings(&bindings); - self.trait_constraints.push((constraint, expr_id)); + self.push_trait_constraint(constraint, expr_id); } } } @@ -538,7 +629,7 @@ impl<'context> Elaborator<'context> { // Currently only one impl can be selected per expr_id, so this // constraint needs to be pushed after any other constraints so // that monomorphization can resolve this trait method to the correct impl. - self.trait_constraints.push((constraint, expr_id)); + self.push_trait_constraint(constraint, expr_id); } } @@ -580,7 +671,7 @@ impl<'context> Elaborator<'context> { } pub fn get_ident_from_path(&mut self, path: Path) -> (HirIdent, usize) { - let location = Location::new(path.span(), self.file); + let location = Location::new(path.last_segment().span(), self.file); let error = match path.as_ident().map(|ident| self.use_variable(ident)) { Some(Ok(found)) => return found, diff --git a/compiler/noirc_frontend/src/elaborator/scope.rs b/compiler/noirc_frontend/src/elaborator/scope.rs index 9fd3be0a354..b7016280453 100644 --- a/compiler/noirc_frontend/src/elaborator/scope.rs +++ b/compiler/noirc_frontend/src/elaborator/scope.rs @@ -1,4 +1,4 @@ -use noirc_errors::Spanned; +use noirc_errors::{Location, Spanned}; use crate::ast::ERROR_IDENT; use crate::hir::def_map::{LocalModuleId, ModuleId}; @@ -6,6 +6,7 @@ use crate::hir::resolution::path_resolver::{PathResolver, StandardPathResolver}; use crate::hir::resolution::resolver::SELF_TYPE_NAME; use crate::hir::scope::{Scope as GenericScope, ScopeTree as GenericScopeTree}; use crate::macros_api::Ident; +use crate::node_interner::ReferenceId; use crate::{ hir::{ def_map::{ModuleDefId, TryFromModuleDefId}, @@ -44,7 +45,23 @@ impl<'context> Elaborator<'context> { pub(super) fn resolve_path(&mut self, path: Path) -> Result { let resolver = StandardPathResolver::new(self.module_id()); - let path_resolution = resolver.resolve(self.def_maps, path)?; + let path_resolution; + + if self.interner.track_references { + let mut references: Vec = Vec::new(); + path_resolution = + resolver.resolve(self.def_maps, path.clone(), &mut Some(&mut references))?; + + for (referenced, ident) in references.iter().zip(path.segments) { + let reference = ReferenceId::Reference( + Location::new(ident.span(), self.file), + ident.is_self_type_name(), + ); + self.interner.add_reference(*referenced, reference); + } + } else { + path_resolution = resolver.resolve(self.def_maps, path, &mut None)?; + } if let Some(error) = path_resolution.error { self.push_err(error); diff --git a/compiler/noirc_frontend/src/elaborator/statements.rs b/compiler/noirc_frontend/src/elaborator/statements.rs index dd3e2778726..13c59e3494e 100644 --- a/compiler/noirc_frontend/src/elaborator/statements.rs +++ b/compiler/noirc_frontend/src/elaborator/statements.rs @@ -3,7 +3,6 @@ use noirc_errors::{Location, Span}; use crate::{ ast::{AssignStatement, ConstrainStatement, LValue}, hir::{ - comptime::Interpreter, resolution::errors::ResolverError, type_check::{Source, TypeCheckError}, }, @@ -16,7 +15,7 @@ use crate::{ macros_api::{ ForLoopStatement, ForRange, HirStatement, LetStatement, Path, Statement, StatementKind, }, - node_interner::{DefinitionId, DefinitionKind, GlobalId, StmtId}, + node_interner::{DefinitionId, DefinitionKind, GlobalId, ReferenceId, StmtId}, Type, }; @@ -86,8 +85,8 @@ impl<'context> Elaborator<'context> { expr_span, } }); - if annotated_type.is_unsigned() { - let errors = lints::overflowing_uint(self.interner, &expression, &annotated_type); + if annotated_type.is_integer() { + let errors = lints::overflowing_int(self.interner, &expression, &annotated_type); for error in errors { self.push_err(error); } @@ -206,7 +205,9 @@ impl<'context> Elaborator<'context> { } fn elaborate_jump(&mut self, is_break: bool, span: noirc_errors::Span) -> (HirStatement, Type) { - if !self.in_unconstrained_fn { + let in_constrained_function = self.in_constrained_function(); + + if in_constrained_function { self.push_err(ResolverError::JumpInConstrainedFn { is_break, span }); } if self.nested_loops == 0 { @@ -254,6 +255,10 @@ impl<'context> Elaborator<'context> { typ.follow_bindings() }; + let referenced = ReferenceId::Local(ident.id); + let reference = ReferenceId::Reference(Location::new(span, self.file), false); + self.interner.add_reference(referenced, reference); + (HirLValue::Ident(ident.clone(), typ.clone()), typ, mutable) } LValue::MemberAccess { object, field_name, span } => { @@ -375,6 +380,10 @@ impl<'context> Elaborator<'context> { Type::Struct(s, args) => { let s = s.borrow(); if let Some((field, index)) = s.get_field(field_name, args) { + let referenced = ReferenceId::StructMember(s.id, index); + let reference = ReferenceId::Reference(Location::new(span, self.file), false); + self.interner.add_reference(referenced, reference); + return Some((field, index)); } } @@ -430,11 +439,23 @@ impl<'context> Elaborator<'context> { } fn elaborate_comptime_statement(&mut self, statement: Statement) -> (HirStatement, Type) { + // We have to push a new FunctionContext so that we can resolve any constraints + // in this comptime block early before the function as a whole finishes elaborating. + // Otherwise the interpreter below may find expressions for which the underlying trait + // call is not yet solved for. + self.function_context.push(Default::default()); let span = statement.span; let (hir_statement, _typ) = self.elaborate_statement(statement); - let mut interpreter = Interpreter::new(self.interner, &mut self.comptime_scopes); + self.check_and_pop_function_context(); + let mut interpreter_errors = vec![]; + let mut interpreter = self.setup_interpreter(&mut interpreter_errors); let value = interpreter.evaluate_statement(hir_statement); let (expr, typ) = self.inline_comptime_value(value, span); + self.include_interpreter_errors(interpreter_errors); + + let location = self.interner.id_location(hir_statement); + self.debug_comptime(location, |interner| expr.to_display_ast(interner).kind); + (HirStatement::Expression(expr), typ) } } diff --git a/compiler/noirc_frontend/src/elaborator/traits.rs b/compiler/noirc_frontend/src/elaborator/traits.rs index 3e04dbc784a..4cd20820c56 100644 --- a/compiler/noirc_frontend/src/elaborator/traits.rs +++ b/compiler/noirc_frontend/src/elaborator/traits.rs @@ -1,10 +1,12 @@ -use std::collections::BTreeMap; +use std::{collections::BTreeMap, rc::Rc}; use iter_extended::vecmap; use noirc_errors::Location; use crate::{ - ast::{FunctionKind, TraitItem, UnresolvedGenerics, UnresolvedTraitConstraint}, + ast::{ + FunctionKind, TraitItem, UnresolvedGeneric, UnresolvedGenerics, UnresolvedTraitConstraint, + }, hir::def_collector::dc_crate::UnresolvedTrait, hir_def::traits::{TraitConstant, TraitFunction, TraitType}, macros_api::{ @@ -13,7 +15,7 @@ use crate::{ }, node_interner::{FuncId, TraitId}, token::Attributes, - Type, TypeVariableKind, + Kind, ResolvedGeneric, Type, TypeVariableKind, }; use super::Elaborator; @@ -22,7 +24,11 @@ impl<'context> Elaborator<'context> { pub fn collect_traits(&mut self, traits: BTreeMap) { for (trait_id, unresolved_trait) in traits { self.recover_generics(|this| { - this.add_generics(&unresolved_trait.trait_def.generics); + let resolved_generics = this.interner.get_trait(trait_id).generics.clone(); + this.add_existing_generics( + &unresolved_trait.trait_def.generics, + &resolved_generics, + ); // Resolve order // 1. Trait Types ( Trait constants can have a trait type, therefore types before constants) @@ -34,7 +40,6 @@ impl<'context> Elaborator<'context> { this.interner.update_trait(trait_id, |trait_def| { trait_def.set_methods(methods); - trait_def.generics = vecmap(&this.generics, |(_, generic, _)| generic.clone()); }); }); @@ -42,7 +47,8 @@ impl<'context> Elaborator<'context> { // the interner may set `interner.ordering_type` based on the result type // of the Cmp trait, if this is it. if self.crate_id.is_stdlib() { - self.interner.try_add_operator_trait(trait_id); + self.interner.try_add_infix_operator_trait(trait_id); + self.interner.try_add_prefix_operator_trait(trait_id); } } } @@ -87,10 +93,20 @@ impl<'context> Elaborator<'context> { Type::TypeVariable(self_typevar.clone(), TypeVariableKind::Normal); let name_span = the_trait.name.span(); - this.add_existing_generic("Self", name_span, self_typevar); + this.add_existing_generic( + &UnresolvedGeneric::Variable(Ident::from("Self")), + name_span, + &ResolvedGeneric { + name: Rc::new("Self".to_owned()), + type_var: self_typevar, + span: name_span, + kind: Kind::Normal, + }, + ); this.self_type = Some(self_type.clone()); let func_id = unresolved_trait.method_ids[&name.0.contents]; + this.resolve_trait_function( name, generics, @@ -105,7 +121,7 @@ impl<'context> Elaborator<'context> { let arguments = vecmap(&func_meta.parameters.0, |(_, typ, _)| typ.clone()); let return_type = func_meta.return_type().clone(); - let generics = vecmap(&this.generics, |(_, type_var, _)| type_var.clone()); + let generics = vecmap(&this.generics, |generic| generic.type_var.clone()); let default_impl_list: Vec<_> = unresolved_trait .fns_with_default_impl @@ -147,6 +163,7 @@ impl<'context> Elaborator<'context> { func_id: FuncId, ) { let old_generic_count = self.generics.len(); + self.scopes.start_function(); let kind = FunctionKind::Normal; diff --git a/compiler/noirc_frontend/src/elaborator/types.rs b/compiler/noirc_frontend/src/elaborator/types.rs index fcb7ac94c26..4e9b3620760 100644 --- a/compiler/noirc_frontend/src/elaborator/types.rs +++ b/compiler/noirc_frontend/src/elaborator/types.rs @@ -5,7 +5,10 @@ use iter_extended::vecmap; use noirc_errors::{Location, Span}; use crate::{ - ast::{BinaryOpKind, IntegerBitSize, UnresolvedGenerics, UnresolvedTypeExpression}, + ast::{ + BinaryOpKind, IntegerBitSize, UnresolvedGeneric, UnresolvedGenerics, + UnresolvedTypeExpression, + }, hir::{ comptime::{Interpreter, Value}, def_map::ModuleDefId, @@ -13,7 +16,7 @@ use crate::{ errors::ResolverError, resolver::{verify_mutable_reference, SELF_TYPE_NAME, WILDCARD_TYPE}, }, - type_check::{Source, TypeCheckError}, + type_check::{NoMatchingImplFoundError, Source, TypeCheckError}, }, hir_def::{ expr::{ @@ -27,49 +30,85 @@ use crate::{ HirExpression, HirLiteral, HirStatement, Path, PathKind, SecondaryAttribute, Signedness, UnaryOp, UnresolvedType, UnresolvedTypeData, }, - node_interner::{DefinitionKind, ExprId, GlobalId, TraitId, TraitImplKind, TraitMethodId}, - Generics, Type, TypeBinding, TypeVariable, TypeVariableKind, + node_interner::{ + DefinitionKind, DependencyId, ExprId, GlobalId, ReferenceId, TraitId, TraitImplKind, + TraitMethodId, + }, + Generics, Kind, ResolvedGeneric, Type, TypeBinding, TypeVariable, TypeVariableKind, }; use super::{lints, Elaborator}; impl<'context> Elaborator<'context> { - /// Translates an UnresolvedType to a Type + /// Translates an UnresolvedType to a Type with a `TypeKind::Normal` pub(super) fn resolve_type(&mut self, typ: UnresolvedType) -> Type { let span = typ.span; - let resolved_type = self.resolve_type_inner(typ); + let resolved_type = self.resolve_type_inner(typ, &Kind::Normal); if resolved_type.is_nested_slice() { - self.push_err(ResolverError::NestedSlices { span: span.unwrap() }); + self.push_err(ResolverError::NestedSlices { + span: span.expect("Type should have span"), + }); } resolved_type } /// Translates an UnresolvedType into a Type and appends any /// freshly created TypeVariables created to new_variables. - pub fn resolve_type_inner(&mut self, typ: UnresolvedType) -> Type { + pub fn resolve_type_inner(&mut self, typ: UnresolvedType, kind: &Kind) -> Type { use crate::ast::UnresolvedTypeData::*; + let span = typ.span; + let (is_self_type_name, is_synthetic) = if let Named(ref named_path, _, synthetic) = typ.typ + { + (named_path.last_segment().is_self_type_name(), synthetic) + } else { + (false, false) + }; + let resolved_type = match typ.typ { FieldElement => Type::FieldElement, Array(size, elem) => { - let elem = Box::new(self.resolve_type_inner(*elem)); - let size = self.convert_expression_type(size); + let elem = Box::new(self.resolve_type_inner(*elem, kind)); + let mut size = self.convert_expression_type(size); + // TODO(https://github.com/noir-lang/noir/issues/5156): Remove this once we only have explicit numeric generics + if let Type::NamedGeneric(type_var, name, _) = size { + size = Type::NamedGeneric( + type_var, + name, + Kind::Numeric(Box::new(Type::default_int_type())), + ); + } Type::Array(Box::new(size), elem) } Slice(elem) => { - let elem = Box::new(self.resolve_type_inner(*elem)); + let elem = Box::new(self.resolve_type_inner(*elem, kind)); Type::Slice(elem) } Expression(expr) => self.convert_expression_type(expr), Integer(sign, bits) => Type::Integer(sign, bits), Bool => Type::Bool, String(size) => { - let resolved_size = self.convert_expression_type(size); + let mut resolved_size = self.convert_expression_type(size); + // TODO(https://github.com/noir-lang/noir/issues/5156): Remove this once we only have explicit numeric generics + if let Type::NamedGeneric(type_var, name, _) = resolved_size { + resolved_size = Type::NamedGeneric( + type_var, + name, + Kind::Numeric(Box::new(Type::default_int_type())), + ); + } Type::String(Box::new(resolved_size)) } FormatString(size, fields) => { - let resolved_size = self.convert_expression_type(size); - let fields = self.resolve_type_inner(*fields); + let mut resolved_size = self.convert_expression_type(size); + if let Type::NamedGeneric(type_var, name, _) = resolved_size { + resolved_size = Type::NamedGeneric( + type_var, + name, + Kind::Numeric(Box::new(Type::default_int_type())), + ); + } + let fields = self.resolve_type_inner(*fields, kind); Type::FmtString(Box::new(resolved_size), Box::new(fields)) } Quoted(quoted) => Type::Quoted(quoted), @@ -79,10 +118,12 @@ impl<'context> Elaborator<'context> { Named(path, args, _) => self.resolve_named_type(path, args), TraitAsType(path, args) => self.resolve_trait_as_type(path, args), - Tuple(fields) => Type::Tuple(vecmap(fields, |field| self.resolve_type_inner(field))), + Tuple(fields) => { + Type::Tuple(vecmap(fields, |field| self.resolve_type_inner(field, kind))) + } Function(args, ret, env) => { - let args = vecmap(args, |arg| self.resolve_type_inner(arg)); - let ret = Box::new(self.resolve_type_inner(*ret)); + let args = vecmap(args, |arg| self.resolve_type_inner(arg, kind)); + let ret = Box::new(self.resolve_type_inner(*ret, kind)); // expect() here is valid, because the only places we don't have a span are omitted types // e.g. a function without return type implicitly has a spanless UnresolvedType::Unit return type @@ -90,10 +131,10 @@ impl<'context> Elaborator<'context> { let env_span = env.span.expect("Unexpected missing span for closure environment type"); - let env = Box::new(self.resolve_type_inner(*env)); + let env = Box::new(self.resolve_type_inner(*env, kind)); match *env { - Type::Unit | Type::Tuple(_) | Type::NamedGeneric(_, _) => { + Type::Unit | Type::Tuple(_) | Type::NamedGeneric(_, _, _) => { Type::Function(args, ret, env) } _ => { @@ -106,25 +147,71 @@ impl<'context> Elaborator<'context> { } } MutableReference(element) => { - Type::MutableReference(Box::new(self.resolve_type_inner(*element))) + Type::MutableReference(Box::new(self.resolve_type_inner(*element, kind))) } - Parenthesized(typ) => self.resolve_type_inner(*typ), + Parenthesized(typ) => self.resolve_type_inner(*typ, kind), + Resolved(id) => self.interner.get_quoted_type(id).clone(), }; - if let Type::Struct(_, _) = resolved_type { - if let Some(unresolved_span) = typ.span { - // Record the location of the type reference - self.interner.push_type_ref_location( - resolved_type.clone(), - Location::new(unresolved_span, self.file), - ); + if let Some(unresolved_span) = typ.span { + match resolved_type { + Type::Struct(ref struct_type, _) => { + // Record the location of the type reference + self.interner.push_type_ref_location( + resolved_type.clone(), + Location::new(unresolved_span, self.file), + ); + + if !is_synthetic { + let referenced = ReferenceId::Struct(struct_type.borrow().id); + let reference = ReferenceId::Reference( + Location::new(unresolved_span, self.file), + is_self_type_name, + ); + self.interner.add_reference(referenced, reference); + } + } + Type::Alias(ref alias_type, _) => { + let referenced = ReferenceId::Alias(alias_type.borrow().id); + let reference = ReferenceId::Reference( + Location::new(unresolved_span, self.file), + is_self_type_name, + ); + self.interner.add_reference(referenced, reference); + } + _ => (), + } + } + + // Check that any types with a type kind match the expected type kind supplied to this function + // TODO(https://github.com/noir-lang/noir/issues/5156): make this named generic check more general with `*resolved_kind != kind` + // as implicit numeric generics still existing makes this check more challenging to enforce + // An example of a more general check that we should switch to: + // if resolved_type.kind() != kind.clone() { + // let expected_typ_err = CompilationError::TypeError(TypeCheckError::TypeKindMismatch { + // expected_kind: kind.to_string(), + // expr_kind: resolved_type.kind().to_string(), + // expr_span: span.expect("Type should have span"), + // }); + // self.errors.push((expected_typ_err, self.file)); + // return Type::Error; + // } + if let Type::NamedGeneric(_, name, resolved_kind) = &resolved_type { + if matches!(resolved_kind, Kind::Numeric { .. }) && matches!(kind, Kind::Normal) { + let expected_typ_err = ResolverError::NumericGenericUsedForType { + name: name.to_string(), + span: span.expect("Type should have span"), + }; + self.push_err(expected_typ_err); + return Type::Error; } } + resolved_type } - pub fn find_generic(&self, target_name: &str) -> Option<&(Rc, TypeVariable, Span)> { - self.generics.iter().find(|(name, _, _)| name.as_ref() == target_name) + pub fn find_generic(&self, target_name: &str) -> Option<&ResolvedGeneric> { + self.generics.iter().find(|generic| generic.name.as_ref() == target_name) } fn resolve_named_type(&mut self, path: Path, args: Vec) -> Type { @@ -152,7 +239,6 @@ impl<'context> Elaborator<'context> { } let span = path.span(); - let mut args = vecmap(args, |arg| self.resolve_type_inner(arg)); if let Some(type_alias) = self.lookup_type_alias(path.clone()) { let type_alias = type_alias.borrow(); @@ -160,6 +246,10 @@ impl<'context> Elaborator<'context> { let type_alias_string = type_alias.to_string(); let id = type_alias.id; + let mut args = vecmap(type_alias.generics.iter().zip(args), |(generic, arg)| { + self.resolve_type_inner(arg, &generic.kind) + }); + self.verify_generics_count(expected_generic_count, &mut args, span, || { type_alias_string }); @@ -192,7 +282,7 @@ impl<'context> Elaborator<'context> { } let expected_generic_count = struct_type.borrow().generics.len(); - if !self.in_contract + if !self.in_contract() && self .interner .struct_attributes(&struct_type.borrow().id) @@ -203,6 +293,12 @@ impl<'context> Elaborator<'context> { span: struct_type.borrow().name.span(), }); } + + let mut args = + vecmap(struct_type.borrow().generics.iter().zip(args), |(generic, arg)| { + self.resolve_type_inner(arg, &generic.kind) + }); + self.verify_generics_count(expected_generic_count, &mut args, span, || { struct_type.borrow().to_string() }); @@ -219,10 +315,19 @@ impl<'context> Elaborator<'context> { } fn resolve_trait_as_type(&mut self, path: Path, args: Vec) -> Type { - let args = vecmap(args, |arg| self.resolve_type_inner(arg)); - - if let Some(t) = self.lookup_trait_or_error(path) { - Type::TraitAsType(t.id, Rc::new(t.name.to_string()), args) + // Fetch information needed from the trait as the closure for resolving all the `args` + // requires exclusive access to `self` + let trait_as_type_info = self + .lookup_trait_or_error(path) + .map(|t| (t.id, Rc::new(t.name.to_string()), t.generics.clone())); + + if let Some((id, name, resolved_generics)) = trait_as_type_info { + assert_eq!(resolved_generics.len(), args.len()); + let generics_with_types = resolved_generics.iter().zip(args); + let args = vecmap(generics_with_types, |(generic, typ)| { + self.resolve_type_inner(typ, &generic.kind) + }); + Type::TraitAsType(id, Rc::new(name.to_string()), args) } else { Type::Error } @@ -251,8 +356,9 @@ impl<'context> Elaborator<'context> { pub fn lookup_generic_or_global_type(&mut self, path: &Path) -> Option { if path.segments.len() == 1 { let name = &path.last_segment().0.contents; - if let Some((name, var, _)) = self.find_generic(name) { - return Some(Type::NamedGeneric(var.clone(), name.clone())); + if let Some(generic) = self.find_generic(name) { + let generic = generic.clone(); + return Some(Type::NamedGeneric(generic.type_var, generic.name, generic.kind)); } } @@ -263,6 +369,11 @@ impl<'context> Elaborator<'context> { self.interner.add_global_dependency(current_item, id); } + let referenced = ReferenceId::Global(id); + let reference = + ReferenceId::Reference(Location::new(path.span(), self.file), false); + self.interner.add_reference(referenced, reference); + Some(Type::Constant(self.eval_global_as_array_length(id, path))) } _ => None, @@ -318,9 +429,12 @@ impl<'context> Elaborator<'context> { let constraint = TraitConstraint { typ: self.self_type.clone()?, - trait_generics: Type::from_generics(&the_trait.generics), + trait_generics: Type::from_generics(&vecmap(&the_trait.generics, |generic| { + generic.type_var.clone() + })), trait_id, }; + return Some((method, constraint, false)); } } @@ -349,7 +463,9 @@ impl<'context> Elaborator<'context> { the_trait.self_type_typevar.clone(), TypeVariableKind::Normal, ), - trait_generics: Type::from_generics(&the_trait.generics), + trait_generics: Type::from_generics(&vecmap(&the_trait.generics, |generic| { + generic.type_var.clone() + })), trait_id, }; return Some((method, constraint, false)); @@ -371,7 +487,7 @@ impl<'context> Elaborator<'context> { } for constraint in self.trait_bounds.clone() { - if let Type::NamedGeneric(_, name) = &constraint.typ { + if let Type::NamedGeneric(_, name, _) = &constraint.typ { // if `path` is `T::method_name`, we're looking for constraint of the form `T: SomeTrait` if path.segments[0].0.contents != name.as_str() { continue; @@ -533,7 +649,7 @@ impl<'context> Elaborator<'context> { /// in self.type_variables to default it later. pub(super) fn polymorphic_integer_or_field(&mut self) -> Type { let typ = Type::polymorphic_integer_or_field(self.interner); - self.type_variables.push(typ.clone()); + self.push_type_variable(typ.clone()); typ } @@ -541,7 +657,7 @@ impl<'context> Elaborator<'context> { /// in self.type_variables to default it later. pub(super) fn polymorphic_integer(&mut self) -> Type { let typ = Type::polymorphic_integer(self.interner); - self.type_variables.push(typ.clone()); + self.push_type_variable(typ.clone()); typ } @@ -554,57 +670,6 @@ impl<'context> Elaborator<'context> { } } - pub(super) fn type_check_prefix_operand( - &mut self, - op: &crate::ast::UnaryOp, - rhs_type: &Type, - span: Span, - ) -> Type { - let unify = |this: &mut Self, expected| { - this.unify(rhs_type, &expected, || TypeCheckError::TypeMismatch { - expr_typ: rhs_type.to_string(), - expected_typ: expected.to_string(), - expr_span: span, - }); - expected - }; - - match op { - crate::ast::UnaryOp::Minus => { - if rhs_type.is_unsigned() { - self.push_err(TypeCheckError::InvalidUnaryOp { - kind: rhs_type.to_string(), - span, - }); - } - let expected = self.polymorphic_integer_or_field(); - self.unify(rhs_type, &expected, || TypeCheckError::InvalidUnaryOp { - kind: rhs_type.to_string(), - span, - }); - expected - } - crate::ast::UnaryOp::Not => { - let rhs_type = rhs_type.follow_bindings(); - - // `!` can work on booleans or integers - if matches!(rhs_type, Type::Integer(..)) { - return rhs_type; - } - - unify(self, Type::Bool) - } - crate::ast::UnaryOp::MutableReference => { - Type::MutableReference(Box::new(rhs_type.follow_bindings())) - } - crate::ast::UnaryOp::Dereference { implicitly_added: _ } => { - let element_type = self.interner.next_type_variable(); - unify(self, Type::MutableReference(Box::new(element_type.clone()))); - element_type - } - } - } - /// Insert as many dereference operations as necessary to automatically dereference a method /// call object to its base value type T. pub(super) fn insert_auto_dereferences(&mut self, object: ExprId, typ: Type) -> (ExprId, Type) { @@ -614,6 +679,7 @@ impl<'context> Elaborator<'context> { let object = self.interner.push_expr(HirExpression::Prefix(HirPrefixExpression { operator: UnaryOp::Dereference { implicitly_added: true }, rhs: object, + trait_method_id: None, })); self.interner.push_expr_type(object, element.as_ref().clone()); self.interner.push_expr_location(object, location.span, location.file); @@ -957,6 +1023,84 @@ impl<'context> Elaborator<'context> { } } + // Given a unary operator and a type, this method will produce the output type + // and a boolean indicating whether to use the trait impl corresponding to the operator + // or not. A value of false indicates the caller to use a primitive operation for this + // operator, while a true value indicates a user-provided trait impl is required. + pub(super) fn prefix_operand_type_rules( + &mut self, + op: &UnaryOp, + rhs_type: &Type, + span: Span, + ) -> Result<(Type, bool), TypeCheckError> { + use Type::*; + + match op { + crate::ast::UnaryOp::Minus | crate::ast::UnaryOp::Not => { + match rhs_type { + // An error type will always return an error + Error => Ok((Error, false)), + Alias(alias, args) => { + let alias = alias.borrow().get_type(args); + self.prefix_operand_type_rules(op, &alias, span) + } + + // Matches on TypeVariable must be first so that we follow any type + // bindings. + TypeVariable(int, _) => { + if let TypeBinding::Bound(binding) = &*int.borrow() { + return self.prefix_operand_type_rules(op, binding, span); + } + + // The `!` prefix operator is not valid for Field, so if this is a numeric + // type we constrain it to just (non-Field) integer types. + if matches!(op, crate::ast::UnaryOp::Not) && rhs_type.is_numeric() { + let integer_type = Type::polymorphic_integer(self.interner); + self.unify(rhs_type, &integer_type, || { + TypeCheckError::InvalidUnaryOp { kind: rhs_type.to_string(), span } + }); + } + + Ok((rhs_type.clone(), !rhs_type.is_numeric())) + } + Integer(sign_x, bit_width_x) => { + if *op == UnaryOp::Minus && *sign_x == Signedness::Unsigned { + return Err(TypeCheckError::InvalidUnaryOp { + kind: rhs_type.to_string(), + span, + }); + } + Ok((Integer(*sign_x, *bit_width_x), false)) + } + // The result of a Field is always a witness + FieldElement => { + if *op == UnaryOp::Not { + return Err(TypeCheckError::FieldNot { span }); + } + Ok((FieldElement, false)) + } + + Bool => Ok((Bool, false)), + + _ => Ok((rhs_type.clone(), true)), + } + } + crate::ast::UnaryOp::MutableReference => { + Ok((Type::MutableReference(Box::new(rhs_type.follow_bindings())), false)) + } + crate::ast::UnaryOp::Dereference { implicitly_added: _ } => { + let element_type = self.interner.next_type_variable(); + let expected = Type::MutableReference(Box::new(element_type.clone())); + self.unify(rhs_type, &expected, || TypeCheckError::TypeMismatch { + expr_typ: rhs_type.to_string(), + expected_typ: expected.to_string(), + expr_span: span, + }); + Ok((element_type, false)) + } + } + } + /// Prerequisite: verify_trait_constraint of the operator's trait constraint. /// /// Although by this point the operator is expected to already have a trait impl, @@ -1024,6 +1168,7 @@ impl<'context> Elaborator<'context> { *access_lhs = this.interner.push_expr(HirExpression::Prefix(HirPrefixExpression { operator: crate::ast::UnaryOp::Dereference { implicitly_added: true }, rhs: old_lhs, + trait_method_id: None, })); this.interner.push_expr_type(old_lhs, lhs_type); this.interner.push_expr_type(*access_lhs, element); @@ -1077,10 +1222,12 @@ impl<'context> Elaborator<'context> { }); None } - Type::NamedGeneric(_, _) => { - let func_meta = self.interner.function_meta( - &self.current_function.expect("unexpected method outside a function"), - ); + Type::NamedGeneric(_, _, _) => { + let func_id = match self.current_item { + Some(DependencyId::Function(id)) => id, + _ => panic!("unexpected method outside a function"), + }; + let func_meta = self.interner.function_meta(&func_id); for constraint in &func_meta.trait_constraints { if *object_type == constraint.typ { @@ -1151,9 +1298,8 @@ impl<'context> Elaborator<'context> { lints::deprecated_function(elaborator.interner, call.func).map(Into::into) }); - let func_mod = self.current_function.map(|func| self.interner.function_modifiers(&func)); - let is_current_func_constrained = - func_mod.map_or(true, |func_mod| !func_mod.is_unconstrained); + let is_current_func_constrained = self.in_constrained_function(); + let is_unconstrained_call = self.is_unconstrained_call(call.func); let crossing_runtime_boundary = is_current_func_constrained && is_unconstrained_call; if crossing_runtime_boundary { @@ -1245,6 +1391,7 @@ impl<'context> Elaborator<'context> { self.interner.push_expr(HirExpression::Prefix(HirPrefixExpression { operator: UnaryOp::MutableReference, rhs: *object, + trait_method_id: None, })); self.interner.push_expr_type(new_object, new_type); self.interner.push_expr_location(new_object, location.span, location.file); @@ -1328,51 +1475,43 @@ impl<'context> Elaborator<'context> { Err(erroring_constraints) => { if erroring_constraints.is_empty() { self.push_err(TypeCheckError::TypeAnnotationsNeeded { span }); - } else { - // Don't show any errors where try_get_trait returns None. - // This can happen if a trait is used that was never declared. - let constraints = erroring_constraints - .into_iter() - .map(|constraint| { - let r#trait = self.interner.try_get_trait(constraint.trait_id)?; - let mut name = r#trait.name.to_string(); - if !constraint.trait_generics.is_empty() { - let generics = - vecmap(&constraint.trait_generics, ToString::to_string); - name += &format!("<{}>", generics.join(", ")); - } - Some((constraint.typ, name)) - }) - .collect::>>(); - - if let Some(constraints) = constraints { - self.push_err(TypeCheckError::NoMatchingImplFound { constraints, span }); - } + } else if let Some(error) = + NoMatchingImplFoundError::new(self.interner, erroring_constraints, span) + { + self.push_err(TypeCheckError::NoMatchingImplFound(error)); } } } } - pub fn add_existing_generics(&mut self, names: &UnresolvedGenerics, generics: &Generics) { - assert_eq!(names.len(), generics.len()); + pub fn add_existing_generics( + &mut self, + unresolved_generics: &UnresolvedGenerics, + generics: &Generics, + ) { + assert_eq!(unresolved_generics.len(), generics.len()); - for (name, typevar) in names.iter().zip(generics) { - self.add_existing_generic(&name.0.contents, name.0.span(), typevar.clone()); + for (unresolved_generic, generic) in unresolved_generics.iter().zip(generics) { + self.add_existing_generic(unresolved_generic, unresolved_generic.span(), generic); } } - pub fn add_existing_generic(&mut self, name: &str, span: Span, typevar: TypeVariable) { - // Check for name collisions of this generic - let rc_name = Rc::new(name.to_owned()); + pub fn add_existing_generic( + &mut self, + unresolved_generic: &UnresolvedGeneric, + span: Span, + resolved_generic: &ResolvedGeneric, + ) { + let name = &unresolved_generic.ident().0.contents; - if let Some((_, _, first_span)) = self.find_generic(&rc_name) { + if let Some(generic) = self.find_generic(name) { self.push_err(ResolverError::DuplicateDefinition { - name: name.to_owned(), - first_span: *first_span, + name: name.clone(), + first_span: generic.span, second_span: span, }); } else { - self.generics.push((rc_name, typevar, span)); + self.generics.push(resolved_generic.clone()); } } @@ -1397,7 +1536,7 @@ impl<'context> Elaborator<'context> { | Type::Error | Type::TypeVariable(_, _) | Type::Constant(_) - | Type::NamedGeneric(_, _) + | Type::NamedGeneric(_, _, _) | Type::Quoted(_) | Type::Forall(_, _) => (), @@ -1408,7 +1547,7 @@ impl<'context> Elaborator<'context> { } Type::Array(length, element_type) => { - if let Type::NamedGeneric(type_variable, name) = length.as_ref() { + if let Type::NamedGeneric(type_variable, name, _) = length.as_ref() { found.insert(name.to_string(), type_variable.clone()); } Self::find_numeric_generics_in_type(element_type, found); @@ -1433,7 +1572,7 @@ impl<'context> Elaborator<'context> { Type::Struct(struct_type, generics) => { for (i, generic) in generics.iter().enumerate() { - if let Type::NamedGeneric(type_variable, name) = generic { + if let Type::NamedGeneric(type_variable, name, _) = generic { if struct_type.borrow().generic_is_numeric(i) { found.insert(name.to_string(), type_variable.clone()); } @@ -1444,7 +1583,7 @@ impl<'context> Elaborator<'context> { } Type::Alias(alias, generics) => { for (i, generic) in generics.iter().enumerate() { - if let Type::NamedGeneric(type_variable, name) = generic { + if let Type::NamedGeneric(type_variable, name, _) = generic { if alias.borrow().generic_is_numeric(i) { found.insert(name.to_string(), type_variable.clone()); } @@ -1455,16 +1594,32 @@ impl<'context> Elaborator<'context> { } Type::MutableReference(element) => Self::find_numeric_generics_in_type(element, found), Type::String(length) => { - if let Type::NamedGeneric(type_variable, name) = length.as_ref() { + if let Type::NamedGeneric(type_variable, name, _) = length.as_ref() { found.insert(name.to_string(), type_variable.clone()); } } Type::FmtString(length, fields) => { - if let Type::NamedGeneric(type_variable, name) = length.as_ref() { + if let Type::NamedGeneric(type_variable, name, _) = length.as_ref() { found.insert(name.to_string(), type_variable.clone()); } Self::find_numeric_generics_in_type(fields, found); } } } + + /// Push a type variable into the current FunctionContext to be defaulted if needed + /// at the end of the earlier of either the current function or the current comptime scope. + fn push_type_variable(&mut self, typ: Type) { + let context = self.function_context.last_mut(); + let context = context.expect("The function_context stack should always be non-empty"); + context.type_variables.push(typ); + } + + /// Push a trait constraint into the current FunctionContext to be solved if needed + /// at the end of the earlier of either the current function or the current comptime scope. + pub fn push_trait_constraint(&mut self, constraint: TraitConstraint, expr_id: ExprId) { + let context = self.function_context.last_mut(); + let context = context.expect("The function_context stack should always be non-empty"); + context.trait_constraints.push((constraint, expr_id)); + } } diff --git a/compiler/noirc_frontend/src/elaborator/unquote.rs b/compiler/noirc_frontend/src/elaborator/unquote.rs index 6767f471f16..ed12ba21398 100644 --- a/compiler/noirc_frontend/src/elaborator/unquote.rs +++ b/compiler/noirc_frontend/src/elaborator/unquote.rs @@ -1,306 +1,41 @@ use crate::{ - ast::{ - ArrayLiteral, AssignStatement, ConstrainStatement, ConstructorExpression, IfExpression, - InfixExpression, Lambda, - }, - macros_api::{ - BlockExpression, CallExpression, CastExpression, Expression, ExpressionKind, - ForLoopStatement, ForRange, IndexExpression, LetStatement, Literal, MemberAccessExpression, - MethodCallExpression, PrefixExpression, Statement, StatementKind, - }, - node_interner::ExprId, + macros_api::Path, + token::{SpannedToken, Token, Tokens}, }; use super::Elaborator; impl<'a> Elaborator<'a> { - pub fn find_unquoted_exprs_in_block( - &mut self, - block: &mut BlockExpression, - unquoted_exprs: &mut Vec, - ) { - for statement in &mut block.statements { - self.find_unquoted_exprs_in_statement(statement, unquoted_exprs); - } - } - - fn find_unquoted_exprs_in_statement( - &mut self, - statement: &mut Statement, - unquoted_exprs: &mut Vec, - ) { - match &mut statement.kind { - StatementKind::Let(let_) => self.find_unquoted_exprs_in_let(let_, unquoted_exprs), - StatementKind::Constrain(constrain) => { - self.find_unquoted_exprs_in_constrain(constrain, unquoted_exprs); - } - StatementKind::Expression(expr) => { - self.find_unquoted_exprs_in_expr(expr, unquoted_exprs); - } - StatementKind::Assign(assign) => { - self.find_unquoted_exprs_in_assign(assign, unquoted_exprs); - } - StatementKind::For(for_) => self.find_unquoted_exprs_in_for(for_, unquoted_exprs), - StatementKind::Break => (), - StatementKind::Continue => (), - StatementKind::Comptime(comptime) => { - self.find_unquoted_exprs_in_statement(comptime, unquoted_exprs); - } - StatementKind::Semi(expr) => self.find_unquoted_exprs_in_expr(expr, unquoted_exprs), - StatementKind::Error => (), - } - } - - fn find_unquoted_exprs_in_constrain( - &mut self, - constrain: &mut ConstrainStatement, - unquoted_exprs: &mut Vec, - ) { - self.find_unquoted_exprs_in_expr(&mut constrain.0, unquoted_exprs); - if let Some(msg) = constrain.1.as_mut() { - self.find_unquoted_exprs_in_expr(msg, unquoted_exprs); - } - } - - fn find_unquoted_exprs_in_let( - &mut self, - let_: &mut LetStatement, - unquoted_exprs: &mut Vec, - ) { - self.find_unquoted_exprs_in_expr(&mut let_.expression, unquoted_exprs); - } - - fn find_unquoted_exprs_in_assign( - &mut self, - assign: &mut AssignStatement, - unquoted_exprs: &mut Vec, - ) { - self.find_unquoted_exprs_in_expr(&mut assign.expression, unquoted_exprs); - } - - fn find_unquoted_exprs_in_for( - &mut self, - for_: &mut ForLoopStatement, - unquoted_exprs: &mut Vec, - ) { - match &mut for_.range { - ForRange::Range(start, end) => { - self.find_unquoted_exprs_in_expr(start, unquoted_exprs); - self.find_unquoted_exprs_in_expr(end, unquoted_exprs); - } - ForRange::Array(array) => { - self.find_unquoted_exprs_in_expr(array, unquoted_exprs); - } - }; - self.find_unquoted_exprs_in_expr(&mut for_.block, unquoted_exprs); - } - - fn find_unquoted_exprs_in_expr( - &mut self, - expr: &mut Expression, - unquoted_exprs: &mut Vec, - ) { - match &mut expr.kind { - ExpressionKind::Literal(literal) => { - self.find_unquoted_exprs_in_literal(literal, unquoted_exprs); - } - ExpressionKind::Block(block) => { - self.find_unquoted_exprs_in_block(block, unquoted_exprs); - } - ExpressionKind::Prefix(prefix) => { - self.find_unquoted_exprs_in_prefix(prefix, unquoted_exprs); - } - ExpressionKind::Index(index) => { - self.find_unquoted_exprs_in_index(index, unquoted_exprs); - } - ExpressionKind::Call(call) => self.find_unquoted_exprs_in_call(call, unquoted_exprs), - ExpressionKind::MethodCall(call) => { - self.find_unquoted_exprs_in_method_call(call, unquoted_exprs); - } - ExpressionKind::Constructor(constructor) => { - self.find_unquoted_exprs_in_constructor(constructor, unquoted_exprs); - } - ExpressionKind::MemberAccess(access) => { - self.find_unquoted_exprs_in_access(access, unquoted_exprs); - } - ExpressionKind::Cast(cast) => self.find_unquoted_exprs_in_cast(cast, unquoted_exprs), - ExpressionKind::Infix(infix) => { - self.find_unquoted_exprs_in_infix(infix, unquoted_exprs); - } - ExpressionKind::If(if_) => self.find_unquoted_exprs_in_if(if_, unquoted_exprs), - ExpressionKind::Variable(_, _) => (), - ExpressionKind::Tuple(tuple) => { - self.find_unquoted_exprs_in_tuple(tuple, unquoted_exprs); - } - ExpressionKind::Lambda(lambda) => { - self.find_unquoted_exprs_in_lambda(lambda, unquoted_exprs); - } - ExpressionKind::Parenthesized(expr) => { - self.find_unquoted_exprs_in_expr(expr, unquoted_exprs); - } - ExpressionKind::Quote(quote, _) => { - self.find_unquoted_exprs_in_block(quote, unquoted_exprs); - } - ExpressionKind::Comptime(block, _) => { - self.find_unquoted_exprs_in_block(block, unquoted_exprs); - } - ExpressionKind::Resolved(_) => (), - ExpressionKind::Error => (), - ExpressionKind::UnquoteMarker(_) => (), - ExpressionKind::Unquote(unquoted) => { - // Avoid an expensive clone for unquoted - let empty_expr = Expression::new(ExpressionKind::Error, unquoted.span); - let unquote = std::mem::replace(unquoted.as_mut(), empty_expr); - self.replace_unquote(expr, unquote, unquoted_exprs); - } - } - } - - fn find_unquoted_exprs_in_literal( - &mut self, - literal: &mut Literal, - unquoted_exprs: &mut Vec, - ) { - match literal { - Literal::Array(array) | Literal::Slice(array) => match array { - ArrayLiteral::Standard(elements) => { - for element in elements { - self.find_unquoted_exprs_in_expr(element, unquoted_exprs); + /// Go through the given tokens looking for a '$' token followed by a variable to unquote. + /// Each time these two tokens are found, they are replaced by a new UnquoteMarker token + /// containing the ExprId of the resolved variable to unquote. + pub fn find_unquoted_exprs_tokens(&mut self, tokens: Tokens) -> Tokens { + let token_count = tokens.0.len(); + let mut new_tokens = Vec::with_capacity(token_count); + let mut tokens = tokens.0.into_iter(); + + while let Some(token) = tokens.next() { + let is_unquote = matches!(token.token(), Token::DollarSign); + new_tokens.push(token); + + if is_unquote { + if let Some(next) = tokens.next() { + let span = next.to_span(); + + match next.into_token() { + Token::Ident(name) => { + // Don't want the leading `$` anymore + new_tokens.pop(); + let path = Path::from_single(name, span); + let (expr_id, _) = self.elaborate_variable(path, None); + new_tokens.push(SpannedToken::new(Token::UnquoteMarker(expr_id), span)); + } + other_next => new_tokens.push(SpannedToken::new(other_next, span)), } } - ArrayLiteral::Repeated { repeated_element, length } => { - self.find_unquoted_exprs_in_expr(repeated_element, unquoted_exprs); - self.find_unquoted_exprs_in_expr(length, unquoted_exprs); - } - }, - Literal::Bool(_) - | Literal::Integer(_, _) - | Literal::Str(_) - | Literal::RawStr(_, _) - | Literal::FmtStr(_) - | Literal::Unit => (), - } - } - - fn find_unquoted_exprs_in_prefix( - &mut self, - prefix: &mut PrefixExpression, - unquoted_exprs: &mut Vec, - ) { - self.find_unquoted_exprs_in_expr(&mut prefix.rhs, unquoted_exprs); - } - - fn find_unquoted_exprs_in_index( - &mut self, - index: &mut IndexExpression, - unquoted_exprs: &mut Vec, - ) { - self.find_unquoted_exprs_in_expr(&mut index.collection, unquoted_exprs); - self.find_unquoted_exprs_in_expr(&mut index.index, unquoted_exprs); - } - - fn find_unquoted_exprs_in_call( - &mut self, - call: &mut CallExpression, - unquoted_exprs: &mut Vec, - ) { - self.find_unquoted_exprs_in_expr(&mut call.func, unquoted_exprs); - - for arg in &mut call.arguments { - self.find_unquoted_exprs_in_expr(arg, unquoted_exprs); - } - } - - fn find_unquoted_exprs_in_method_call( - &mut self, - call: &mut MethodCallExpression, - unquoted_exprs: &mut Vec, - ) { - self.find_unquoted_exprs_in_expr(&mut call.object, unquoted_exprs); - - for arg in &mut call.arguments { - self.find_unquoted_exprs_in_expr(arg, unquoted_exprs); - } - } - - fn find_unquoted_exprs_in_constructor( - &mut self, - constructor: &mut ConstructorExpression, - unquoted_exprs: &mut Vec, - ) { - for (_, field) in &mut constructor.fields { - self.find_unquoted_exprs_in_expr(field, unquoted_exprs); - } - } - - fn find_unquoted_exprs_in_access( - &mut self, - member_access: &mut MemberAccessExpression, - unquoted_exprs: &mut Vec, - ) { - self.find_unquoted_exprs_in_expr(&mut member_access.lhs, unquoted_exprs); - } - - fn find_unquoted_exprs_in_cast( - &mut self, - cast: &mut CastExpression, - unquoted_exprs: &mut Vec, - ) { - self.find_unquoted_exprs_in_expr(&mut cast.lhs, unquoted_exprs); - } - - fn find_unquoted_exprs_in_infix( - &mut self, - infix: &mut InfixExpression, - unquoted_exprs: &mut Vec, - ) { - self.find_unquoted_exprs_in_expr(&mut infix.lhs, unquoted_exprs); - self.find_unquoted_exprs_in_expr(&mut infix.rhs, unquoted_exprs); - } - - fn find_unquoted_exprs_in_if( - &mut self, - if_: &mut IfExpression, - unquoted_exprs: &mut Vec, - ) { - self.find_unquoted_exprs_in_expr(&mut if_.condition, unquoted_exprs); - self.find_unquoted_exprs_in_expr(&mut if_.consequence, unquoted_exprs); - - if let Some(alternate) = if_.alternative.as_mut() { - self.find_unquoted_exprs_in_expr(alternate, unquoted_exprs); - } - } - - fn find_unquoted_exprs_in_tuple( - &mut self, - tuple: &mut [Expression], - unquoted_exprs: &mut Vec, - ) { - for field in tuple { - self.find_unquoted_exprs_in_expr(field, unquoted_exprs); + } } - } - - fn find_unquoted_exprs_in_lambda( - &mut self, - lambda: &mut Lambda, - unquoted_exprs: &mut Vec, - ) { - self.find_unquoted_exprs_in_expr(&mut lambda.body, unquoted_exprs); - } - /// Elaborate and store the unquoted expression in the given vector, then - /// replace it with an unquote expression with an UnquoteMarker expression to mark the position - /// to replace it with later. - fn replace_unquote( - &mut self, - expr: &mut Expression, - unquoted: Expression, - unquoted_exprs: &mut Vec, - ) { - let (expr_id, _) = self.elaborate_expression(unquoted); - let unquote_marker_id = unquoted_exprs.len(); - unquoted_exprs.push(expr_id); - expr.kind = ExpressionKind::UnquoteMarker(unquote_marker_id); + Tokens(new_tokens) } } diff --git a/compiler/noirc_frontend/src/hir/comptime/errors.rs b/compiler/noirc_frontend/src/hir/comptime/errors.rs index 05962420f8a..0472b0040e5 100644 --- a/compiler/noirc_frontend/src/hir/comptime/errors.rs +++ b/compiler/noirc_frontend/src/hir/comptime/errors.rs @@ -1,5 +1,15 @@ -use crate::{hir::def_collector::dc_crate::CompilationError, Type}; +use std::fmt::Display; +use std::rc::Rc; + +use crate::{ + hir::{def_collector::dc_crate::CompilationError, type_check::NoMatchingImplFoundError}, + parser::ParserError, + token::Tokens, + Type, +}; use acvm::{acir::AcirField, FieldElement}; +use fm::FileId; +use iter_extended::vecmap; use noirc_errors::{CustomDiagnostic, Location}; use super::value::Value; @@ -10,6 +20,7 @@ pub enum InterpreterError { ArgumentCountMismatch { expected: usize, actual: usize, location: Location }, TypeMismatch { expected: Type, value: Value, location: Location }, NonComptimeVarReferenced { name: String, location: Location }, + VariableNotInScope { location: Location }, IntegerOutOfRangeForType { value: FieldElement, typ: Type, location: Location }, ErrorNodeEncountered { location: Location }, NonFunctionCalled { value: Value, location: Location }, @@ -35,6 +46,13 @@ pub enum InterpreterError { NonStructInConstructor { typ: Type, location: Location }, CannotInlineMacro { value: Value, location: Location }, UnquoteFoundDuringEvaluation { location: Location }, + DebugEvaluateComptime { diagnostic: CustomDiagnostic, location: Location }, + FailedToParseMacro { error: ParserError, tokens: Rc, rule: &'static str, file: FileId }, + UnsupportedTopLevelItemUnquote { item: String, location: Location }, + NonComptimeFnCallInSameCrate { function: String, location: Location }, + NoImpl { location: Location }, + NoMatchingImplFound { error: NoMatchingImplFoundError, file: FileId }, + ImplMethodTypeMismatch { expected: Type, actual: Type, location: Location }, Unimplemented { item: String, location: Location }, @@ -69,6 +87,7 @@ impl InterpreterError { InterpreterError::ArgumentCountMismatch { location, .. } | InterpreterError::TypeMismatch { location, .. } | InterpreterError::NonComptimeVarReferenced { location, .. } + | InterpreterError::VariableNotInScope { location, .. } | InterpreterError::IntegerOutOfRangeForType { location, .. } | InterpreterError::ErrorNodeEncountered { location, .. } | InterpreterError::NonFunctionCalled { location, .. } @@ -94,14 +113,39 @@ impl InterpreterError { | InterpreterError::NonStructInConstructor { location, .. } | InterpreterError::CannotInlineMacro { location, .. } | InterpreterError::UnquoteFoundDuringEvaluation { location, .. } + | InterpreterError::UnsupportedTopLevelItemUnquote { location, .. } + | InterpreterError::NonComptimeFnCallInSameCrate { location, .. } | InterpreterError::Unimplemented { location, .. } + | InterpreterError::NoImpl { location, .. } + | InterpreterError::ImplMethodTypeMismatch { location, .. } | InterpreterError::BreakNotInLoop { location, .. } - | InterpreterError::ContinueNotInLoop { location, .. } => *location, + | InterpreterError::DebugEvaluateComptime { location, .. } => *location, + InterpreterError::ContinueNotInLoop { location, .. } => *location, + InterpreterError::FailedToParseMacro { error, file, .. } => { + Location::new(error.span(), *file) + } + InterpreterError::NoMatchingImplFound { error, file } => { + Location::new(error.span, *file) + } InterpreterError::Break | InterpreterError::Continue => { panic!("Tried to get the location of Break/Continue error!") } } } + + pub(crate) fn debug_evaluate_comptime(expr: impl Display, location: Location) -> Self { + let mut formatted_result = format!("{}", expr); + // if multi-line, display on a separate line from the message + if formatted_result.contains('\n') { + formatted_result.insert(0, '\n'); + } + let diagnostic = CustomDiagnostic::simple_info( + "`comptime` expression ran:".to_string(), + format!("After evaluation: {}", formatted_result), + location.span, + ); + InterpreterError::DebugEvaluateComptime { diagnostic, location } + } } impl<'a> From<&'a InterpreterError> for CustomDiagnostic { @@ -129,6 +173,11 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { let secondary = "Non-comptime variables can't be used in comptime code".to_string(); CustomDiagnostic::simple_error(msg, secondary, location.span) } + InterpreterError::VariableNotInScope { location } => { + let msg = "Variable not in scope".to_string(); + let secondary = "Could not find variable".to_string(); + CustomDiagnostic::simple_error(msg, secondary, location.span) + } InterpreterError::IntegerOutOfRangeForType { value, typ, location } => { let int = match value.try_into_u128() { Some(int) => int.to_string(), @@ -249,7 +298,8 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { CustomDiagnostic::simple_error(msg, String::new(), location.span) } InterpreterError::CannotInlineMacro { value, location } => { - let msg = "Cannot inline value into runtime code if it contains references".into(); + let typ = value.get_type(); + let msg = format!("Cannot inline values of type `{typ}` into this position"); let secondary = format!("Cannot inline value {value:?}"); CustomDiagnostic::simple_error(msg, secondary, location.span) } @@ -258,6 +308,46 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { let secondary = "This is a bug".into(); CustomDiagnostic::simple_error(msg, secondary, location.span) } + InterpreterError::DebugEvaluateComptime { diagnostic, .. } => diagnostic.clone(), + InterpreterError::FailedToParseMacro { error, tokens, rule, file: _ } => { + let message = format!("Failed to parse macro's token stream into {rule}"); + let tokens = vecmap(&tokens.0, ToString::to_string).join(" "); + + // 10 is an aribtrary number of tokens here chosen to fit roughly onto one line + let token_stream = if tokens.len() > 10 { + format!("The resulting token stream was: {tokens}") + } else { + format!( + "The resulting token stream was: (stream starts on next line)\n {tokens}" + ) + }; + + let push_the_problem_on_the_library_author = "To avoid this error in the future, try adding input validation to your macro. Erroring out early with an `assert` can be a good way to provide a user-friendly error message".into(); + + let mut diagnostic = CustomDiagnostic::from(error); + // Swap the parser's primary note to become the secondary note so that it is + // more clear this error originates from failing to parse a macro. + let secondary = std::mem::take(&mut diagnostic.message); + diagnostic.add_secondary(secondary, error.span()); + diagnostic.message = message; + diagnostic.add_note(token_stream); + diagnostic.add_note(push_the_problem_on_the_library_author); + diagnostic + } + InterpreterError::UnsupportedTopLevelItemUnquote { item, location } => { + let msg = "Unsupported statement type to unquote".into(); + let secondary = + "Only functions, globals, and trait impls can be unquoted here".into(); + let mut error = CustomDiagnostic::simple_error(msg, secondary, location.span); + error.add_note(format!("Unquoted item was:\n{item}")); + error + } + InterpreterError::NonComptimeFnCallInSameCrate { function, location } => { + let msg = format!("`{function}` cannot be called in a `comptime` context here"); + let secondary = + "This function must be `comptime` or in a separate crate to be called".into(); + CustomDiagnostic::simple_error(msg, secondary, location.span) + } InterpreterError::Unimplemented { item, location } => { let msg = format!("{item} is currently unimplemented"); CustomDiagnostic::simple_error(msg, String::new(), location.span) @@ -270,6 +360,17 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { let msg = "There is no loop to continue!".into(); CustomDiagnostic::simple_error(msg, String::new(), location.span) } + InterpreterError::NoImpl { location } => { + let msg = "No impl found due to prior type error".into(); + CustomDiagnostic::simple_error(msg, String::new(), location.span) + } + InterpreterError::ImplMethodTypeMismatch { expected, actual, location } => { + let msg = format!( + "Impl method type {actual} does not unify with trait method type {expected}" + ); + CustomDiagnostic::simple_error(msg, String::new(), location.span) + } + InterpreterError::NoMatchingImplFound { error, .. } => error.into(), InterpreterError::Break => unreachable!("Uncaught InterpreterError::Break"), InterpreterError::Continue => unreachable!("Uncaught InterpreterError::Continue"), } diff --git a/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs b/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs new file mode 100644 index 00000000000..22763c9cb64 --- /dev/null +++ b/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs @@ -0,0 +1,428 @@ +use iter_extended::vecmap; +use noirc_errors::{Span, Spanned}; + +use crate::ast::{ + ArrayLiteral, AssignStatement, BlockExpression, CallExpression, CastExpression, ConstrainKind, + ConstructorExpression, ExpressionKind, ForLoopStatement, ForRange, Ident, IfExpression, + IndexExpression, InfixExpression, LValue, Lambda, LetStatement, Literal, + MemberAccessExpression, MethodCallExpression, Path, Pattern, PrefixExpression, UnresolvedType, + UnresolvedTypeData, UnresolvedTypeExpression, +}; +use crate::ast::{ConstrainStatement, Expression, Statement, StatementKind}; +use crate::hir_def::expr::{HirArrayLiteral, HirBlockExpression, HirExpression, HirIdent}; +use crate::hir_def::stmt::{HirLValue, HirPattern, HirStatement}; +use crate::hir_def::types::{Type, TypeBinding, TypeVariableKind}; +use crate::macros_api::HirLiteral; +use crate::node_interner::{ExprId, NodeInterner, StmtId}; + +// TODO: +// - Full path for idents & types +// - Assert/AssertEq information lost +// - The type name span is lost in constructor patterns & expressions +// - All type spans are lost +// - Type::TypeVariable has no equivalent in the Ast + +impl HirStatement { + pub fn to_display_ast(&self, interner: &NodeInterner, span: Span) -> Statement { + let kind = match self { + HirStatement::Let(let_stmt) => { + let pattern = let_stmt.pattern.to_display_ast(interner); + let r#type = interner.id_type(let_stmt.expression).to_display_ast(); + let expression = let_stmt.expression.to_display_ast(interner); + StatementKind::Let(LetStatement { + pattern, + r#type, + expression, + comptime: false, + attributes: Vec::new(), + }) + } + HirStatement::Constrain(constrain) => { + let expr = constrain.0.to_display_ast(interner); + let message = constrain.2.map(|message| message.to_display_ast(interner)); + + // TODO: Find difference in usage between Assert & AssertEq + StatementKind::Constrain(ConstrainStatement(expr, message, ConstrainKind::Assert)) + } + HirStatement::Assign(assign) => StatementKind::Assign(AssignStatement { + lvalue: assign.lvalue.to_display_ast(interner), + expression: assign.expression.to_display_ast(interner), + }), + HirStatement::For(for_stmt) => StatementKind::For(ForLoopStatement { + identifier: for_stmt.identifier.to_display_ast(interner), + range: ForRange::Range( + for_stmt.start_range.to_display_ast(interner), + for_stmt.end_range.to_display_ast(interner), + ), + block: for_stmt.block.to_display_ast(interner), + span, + }), + HirStatement::Break => StatementKind::Break, + HirStatement::Continue => StatementKind::Continue, + HirStatement::Expression(expr) => { + StatementKind::Expression(expr.to_display_ast(interner)) + } + HirStatement::Semi(expr) => StatementKind::Semi(expr.to_display_ast(interner)), + HirStatement::Error => StatementKind::Error, + HirStatement::Comptime(statement) => { + StatementKind::Comptime(Box::new(statement.to_display_ast(interner))) + } + }; + + Statement { kind, span } + } +} + +impl StmtId { + /// Convert to AST for display (some details lost) + pub fn to_display_ast(self, interner: &NodeInterner) -> Statement { + let statement = interner.statement(&self); + let span = interner.statement_span(self); + + statement.to_display_ast(interner, span) + } +} + +impl HirExpression { + /// Convert to AST for display (some details lost) + pub fn to_display_ast(&self, interner: &NodeInterner, span: Span) -> Expression { + let kind = match self { + HirExpression::Ident(ident, generics) => { + let path = Path::from_ident(ident.to_display_ast(interner)); + ExpressionKind::Variable( + path, + generics.as_ref().map(|option| { + option.iter().map(|generic| generic.to_display_ast()).collect() + }), + ) + } + HirExpression::Literal(HirLiteral::Array(array)) => { + let array = array.to_display_ast(interner, span); + ExpressionKind::Literal(Literal::Array(array)) + } + HirExpression::Literal(HirLiteral::Slice(array)) => { + let array = array.to_display_ast(interner, span); + ExpressionKind::Literal(Literal::Slice(array)) + } + HirExpression::Literal(HirLiteral::Bool(value)) => { + ExpressionKind::Literal(Literal::Bool(*value)) + } + HirExpression::Literal(HirLiteral::Integer(value, sign)) => { + ExpressionKind::Literal(Literal::Integer(*value, *sign)) + } + HirExpression::Literal(HirLiteral::Str(string)) => { + ExpressionKind::Literal(Literal::Str(string.clone())) + } + HirExpression::Literal(HirLiteral::FmtStr(string, _exprs)) => { + // TODO: Is throwing away the exprs here valid? + ExpressionKind::Literal(Literal::FmtStr(string.clone())) + } + HirExpression::Literal(HirLiteral::Unit) => ExpressionKind::Literal(Literal::Unit), + HirExpression::Block(expr) => ExpressionKind::Block(expr.to_display_ast(interner)), + HirExpression::Prefix(prefix) => ExpressionKind::Prefix(Box::new(PrefixExpression { + operator: prefix.operator, + rhs: prefix.rhs.to_display_ast(interner), + })), + HirExpression::Infix(infix) => ExpressionKind::Infix(Box::new(InfixExpression { + lhs: infix.lhs.to_display_ast(interner), + operator: Spanned::from(infix.operator.location.span, infix.operator.kind), + rhs: infix.rhs.to_display_ast(interner), + })), + HirExpression::Index(index) => ExpressionKind::Index(Box::new(IndexExpression { + collection: index.collection.to_display_ast(interner), + index: index.index.to_display_ast(interner), + })), + HirExpression::Constructor(constructor) => { + let type_name = constructor.r#type.borrow().name.to_string(); + let type_name = Path::from_single(type_name, span); + let fields = vecmap(constructor.fields.clone(), |(name, expr): (Ident, ExprId)| { + (name, expr.to_display_ast(interner)) + }); + let struct_type = None; + + ExpressionKind::Constructor(Box::new(ConstructorExpression { + type_name, + fields, + struct_type, + })) + } + HirExpression::MemberAccess(access) => { + ExpressionKind::MemberAccess(Box::new(MemberAccessExpression { + lhs: access.lhs.to_display_ast(interner), + rhs: access.rhs.clone(), + })) + } + HirExpression::Call(call) => { + let func = Box::new(call.func.to_display_ast(interner)); + let arguments = vecmap(call.arguments.clone(), |arg| arg.to_display_ast(interner)); + let is_macro_call = false; + ExpressionKind::Call(Box::new(CallExpression { func, arguments, is_macro_call })) + } + HirExpression::MethodCall(method_call) => { + ExpressionKind::MethodCall(Box::new(MethodCallExpression { + object: method_call.object.to_display_ast(interner), + method_name: method_call.method.clone(), + arguments: vecmap(method_call.arguments.clone(), |arg| { + arg.to_display_ast(interner) + }), + generics: method_call.generics.clone().map(|option| { + option.iter().map(|generic| generic.to_display_ast()).collect() + }), + is_macro_call: false, + })) + } + HirExpression::Cast(cast) => { + let lhs = cast.lhs.to_display_ast(interner); + let r#type = cast.r#type.to_display_ast(); + ExpressionKind::Cast(Box::new(CastExpression { lhs, r#type })) + } + HirExpression::If(if_expr) => ExpressionKind::If(Box::new(IfExpression { + condition: if_expr.condition.to_display_ast(interner), + consequence: if_expr.consequence.to_display_ast(interner), + alternative: if_expr.alternative.map(|expr| expr.to_display_ast(interner)), + })), + HirExpression::Tuple(fields) => { + ExpressionKind::Tuple(vecmap(fields, |field| field.to_display_ast(interner))) + } + HirExpression::Lambda(lambda) => { + let parameters = vecmap(lambda.parameters.clone(), |(pattern, typ)| { + (pattern.to_display_ast(interner), typ.to_display_ast()) + }); + let return_type = lambda.return_type.to_display_ast(); + let body = lambda.body.to_display_ast(interner); + ExpressionKind::Lambda(Box::new(Lambda { parameters, return_type, body })) + } + HirExpression::Error => ExpressionKind::Error, + HirExpression::Comptime(block) => { + ExpressionKind::Comptime(block.to_display_ast(interner), span) + } + HirExpression::Quote(block) => ExpressionKind::Quote(block.clone()), + + // A macro was evaluated here: return the quoted result + HirExpression::Unquote(block) => ExpressionKind::Quote(block.clone()), + }; + + Expression::new(kind, span) + } +} + +impl ExprId { + /// Convert to AST for display (some details lost) + pub fn to_display_ast(self, interner: &NodeInterner) -> Expression { + let expression = interner.expression(&self); + // TODO: empty 0 span + let span = interner.try_expr_span(&self).unwrap_or_else(|| Span::empty(0)); + expression.to_display_ast(interner, span) + } +} + +impl HirPattern { + /// Convert to AST for display (some details lost) + fn to_display_ast(&self, interner: &NodeInterner) -> Pattern { + match self { + HirPattern::Identifier(ident) => Pattern::Identifier(ident.to_display_ast(interner)), + HirPattern::Mutable(pattern, location) => { + let pattern = Box::new(pattern.to_display_ast(interner)); + Pattern::Mutable(pattern, location.span, false) + } + HirPattern::Tuple(patterns, location) => { + let patterns = vecmap(patterns, |pattern| pattern.to_display_ast(interner)); + Pattern::Tuple(patterns, location.span) + } + HirPattern::Struct(typ, patterns, location) => { + let patterns = vecmap(patterns, |(name, pattern)| { + (name.clone(), pattern.to_display_ast(interner)) + }); + let name = match typ.follow_bindings() { + Type::Struct(struct_def, _) => { + let struct_def = struct_def.borrow(); + struct_def.name.0.contents.clone() + } + // This pass shouldn't error so if the type isn't a struct we just get a string + // representation of any other type and use that. We're relying on name + // resolution to fail later when this Ast is re-converted to Hir. + other => other.to_string(), + }; + // The name span is lost here + let path = Path::from_single(name, location.span); + Pattern::Struct(path, patterns, location.span) + } + } + } +} + +impl HirIdent { + /// Convert to AST for display (some details lost) + fn to_display_ast(&self, interner: &NodeInterner) -> Ident { + let name = interner.definition_name(self.id).to_owned(); + Ident(Spanned::from(self.location.span, name)) + } +} + +impl Type { + /// Convert to AST for display (some details lost) + fn to_display_ast(&self) -> UnresolvedType { + let typ = match self { + Type::FieldElement => UnresolvedTypeData::FieldElement, + Type::Array(length, element) => { + let length = length.to_type_expression(); + let element = Box::new(element.to_display_ast()); + UnresolvedTypeData::Array(length, element) + } + Type::Slice(element) => { + let element = Box::new(element.to_display_ast()); + UnresolvedTypeData::Slice(element) + } + Type::Integer(sign, bit_size) => UnresolvedTypeData::Integer(*sign, *bit_size), + Type::Bool => UnresolvedTypeData::Bool, + Type::String(length) => { + let length = length.to_type_expression(); + UnresolvedTypeData::String(length) + } + Type::FmtString(length, element) => { + let length = length.to_type_expression(); + let element = Box::new(element.to_display_ast()); + UnresolvedTypeData::FormatString(length, element) + } + Type::Unit => UnresolvedTypeData::Unit, + Type::Tuple(fields) => { + let fields = vecmap(fields, |field| field.to_display_ast()); + UnresolvedTypeData::Tuple(fields) + } + Type::Struct(def, generics) => { + let struct_def = def.borrow(); + let generics = vecmap(generics, |generic| generic.to_display_ast()); + let name = Path::from_ident(struct_def.name.clone()); + UnresolvedTypeData::Named(name, generics, false) + } + Type::Alias(type_def, generics) => { + // Keep the alias name instead of expanding this in case the + // alias' definition was changed + let type_def = type_def.borrow(); + let generics = vecmap(generics, |generic| generic.to_display_ast()); + let name = Path::from_ident(type_def.name.clone()); + UnresolvedTypeData::Named(name, generics, false) + } + Type::TypeVariable(binding, kind) => { + match &*binding.borrow() { + TypeBinding::Bound(typ) => return typ.to_display_ast(), + TypeBinding::Unbound(id) => { + let expression = match kind { + // TODO: fix span or make Option + TypeVariableKind::Constant(value) => { + UnresolvedTypeExpression::Constant(*value, Span::empty(0)) + } + other_kind => { + let name = format!("var_{:?}_{}", other_kind, id); + + // TODO: fix span or make Option + let path = Path::from_single(name, Span::empty(0)); + UnresolvedTypeExpression::Variable(path) + } + }; + + UnresolvedTypeData::Expression(expression) + } + } + } + Type::TraitAsType(_, name, generics) => { + let generics = vecmap(generics, |generic| generic.to_display_ast()); + let name = Path::from_single(name.as_ref().clone(), Span::default()); + UnresolvedTypeData::TraitAsType(name, generics) + } + Type::NamedGeneric(_var, name, _kind) => { + let name = Path::from_single(name.as_ref().clone(), Span::default()); + UnresolvedTypeData::TraitAsType(name, Vec::new()) + } + Type::Function(args, ret, env) => { + let args = vecmap(args, |arg| arg.to_display_ast()); + let ret = Box::new(ret.to_display_ast()); + let env = Box::new(env.to_display_ast()); + UnresolvedTypeData::Function(args, ret, env) + } + Type::MutableReference(element) => { + let element = Box::new(element.to_display_ast()); + UnresolvedTypeData::MutableReference(element) + } + // Type::Forall is only for generic functions which don't store a type + // in their Ast so they don't need to call to_display_ast for their Forall type. + // Since there is no UnresolvedTypeData equivalent for Type::Forall, we use + // this to ignore this case since it shouldn't be needed anyway. + Type::Forall(_, typ) => return typ.to_display_ast(), + Type::Constant(_) => panic!("Type::Constant where a type was expected: {self:?}"), + Type::Quoted(quoted_type) => UnresolvedTypeData::Quoted(*quoted_type), + Type::Error => UnresolvedTypeData::Error, + }; + + UnresolvedType { typ, span: None } + } + + /// Convert to AST for display (some details lost) + fn to_type_expression(&self) -> UnresolvedTypeExpression { + let span = Span::default(); + + match self.follow_bindings() { + Type::Constant(length) => UnresolvedTypeExpression::Constant(length, span), + Type::NamedGeneric(_var, name, _kind) => { + let path = Path::from_single(name.as_ref().clone(), span); + UnresolvedTypeExpression::Variable(path) + } + // TODO: This should be turned into a proper error. + other => panic!("Cannot represent {other:?} as type expression"), + } + } +} + +impl HirLValue { + /// Convert to AST for display (some details lost) + fn to_display_ast(&self, interner: &NodeInterner) -> LValue { + match self { + HirLValue::Ident(ident, _) => LValue::Ident(ident.to_display_ast(interner)), + HirLValue::MemberAccess { object, field_name, field_index: _, typ: _, location } => { + let object = Box::new(object.to_display_ast(interner)); + LValue::MemberAccess { object, field_name: field_name.clone(), span: location.span } + } + HirLValue::Index { array, index, typ: _, location } => { + let array = Box::new(array.to_display_ast(interner)); + let index = index.to_display_ast(interner); + LValue::Index { array, index, span: location.span } + } + HirLValue::Dereference { lvalue, element_type: _, location } => { + let lvalue = Box::new(lvalue.to_display_ast(interner)); + LValue::Dereference(lvalue, location.span) + } + } + } +} + +impl HirArrayLiteral { + /// Convert to AST for display (some details lost) + fn to_display_ast(&self, interner: &NodeInterner, span: Span) -> ArrayLiteral { + match self { + HirArrayLiteral::Standard(elements) => { + ArrayLiteral::Standard(vecmap(elements, |element| element.to_display_ast(interner))) + } + HirArrayLiteral::Repeated { repeated_element, length } => { + let repeated_element = Box::new(repeated_element.to_display_ast(interner)); + let length = match length { + Type::Constant(length) => { + let literal = Literal::Integer((*length as u128).into(), false); + let kind = ExpressionKind::Literal(literal); + Box::new(Expression::new(kind, span)) + } + other => panic!("Cannot convert non-constant type for repeated array literal from Hir -> Ast: {other:?}"), + }; + ArrayLiteral::Repeated { repeated_element, length } + } + } + } +} + +impl HirBlockExpression { + /// Convert to AST for display (some details lost) + fn to_display_ast(&self, interner: &NodeInterner) -> BlockExpression { + let statements = + vecmap(self.statements.clone(), |statement| statement.to_display_ast(interner)); + BlockExpression { statements } + } +} diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs index 21b6897f5dd..02714f77605 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs @@ -1,18 +1,26 @@ use std::{collections::hash_map::Entry, rc::Rc}; use acvm::{acir::AcirField, FieldElement}; +use fm::FileId; use im::Vector; use iter_extended::try_vecmap; use noirc_errors::Location; use rustc_hash::FxHashMap as HashMap; use crate::ast::{BinaryOpKind, FunctionKind, IntegerBitSize, Signedness}; -use crate::hir_def::expr::HirQuoted; +use crate::graph::CrateId; +use crate::hir_def::expr::ImplKind; +use crate::macros_api::UnaryOp; +use crate::monomorphization::{ + perform_impl_bindings, perform_instantiation_bindings, resolve_trait_method, + undo_instantiation_bindings, +}; +use crate::token::Tokens; use crate::{ hir_def::{ expr::{ HirArrayLiteral, HirBlockExpression, HirCallExpression, HirCastExpression, - HirConstructorExpression, HirIdent, HirIfExpression, HirIndexExpression, + HirConstructorExpression, HirExpression, HirIdent, HirIfExpression, HirIndexExpression, HirInfixExpression, HirLambda, HirMemberAccess, HirMethodCallExpression, HirPrefixExpression, }, @@ -21,15 +29,13 @@ use crate::{ HirPattern, }, }, - macros_api::{HirExpression, HirLiteral, HirStatement, NodeInterner}, + macros_api::{HirLiteral, HirStatement, NodeInterner}, node_interner::{DefinitionId, DefinitionKind, ExprId, FuncId, StmtId}, Shared, Type, TypeBinding, TypeBindings, TypeVariableKind, }; -use self::unquote::UnquoteArgs; - use super::errors::{IResult, InterpreterError}; -use super::value::Value; +use super::value::{unwrap_rc, Value}; mod builtin; mod unquote; @@ -44,6 +50,12 @@ pub struct Interpreter<'interner> { /// up all currently visible definitions. scopes: &'interner mut Vec>, + crate_id: CrateId, + + /// The scope of --debug-comptime, or None if unset + pub(super) debug_comptime_in_file: Option, + pub(super) debug_comptime_evaluations: &'interner mut Vec, + in_loop: bool, } @@ -52,11 +64,38 @@ impl<'a> Interpreter<'a> { pub(crate) fn new( interner: &'a mut NodeInterner, scopes: &'a mut Vec>, + crate_id: CrateId, + debug_comptime_in_file: Option, + debug_comptime_evaluations: &'a mut Vec, ) -> Self { - Self { interner, scopes, in_loop: false } + Self { + interner, + scopes, + crate_id, + debug_comptime_in_file, + debug_comptime_evaluations, + in_loop: false, + } } pub(crate) fn call_function( + &mut self, + function: FuncId, + arguments: Vec<(Value, Location)>, + instantiation_bindings: TypeBindings, + location: Location, + ) -> IResult { + let trait_method = self.interner.get_trait_method_id(function); + + perform_instantiation_bindings(&instantiation_bindings); + let impl_bindings = perform_impl_bindings(self.interner, trait_method, function, location)?; + let result = self.call_function_inner(function, arguments, location); + undo_instantiation_bindings(impl_bindings); + undo_instantiation_bindings(instantiation_bindings); + result + } + + fn call_function_inner( &mut self, function: FuncId, arguments: Vec<(Value, Location)>, @@ -71,6 +110,14 @@ impl<'a> Interpreter<'a> { }); } + let is_comptime = self.interner.function_modifiers(&function).is_comptime; + if !is_comptime && meta.source_crate == self.crate_id { + // Calling non-comptime functions from within the current crate is restricted + // as non-comptime items will have not been elaborated yet. + let function = self.interner.function_name(&function).to_owned(); + return Err(InterpreterError::NonComptimeFnCallInSameCrate { function, location }); + } + if meta.kind != FunctionKind::Normal { return self.call_builtin(function, arguments, location); } @@ -100,14 +147,8 @@ impl<'a> Interpreter<'a> { .expect("all builtin functions must contain a function attribute which contains the opcode which it links to"); if let Some(builtin) = func_attrs.builtin() { - match builtin.as_str() { - "array_len" => builtin::array_len(&arguments), - "as_slice" => builtin::as_slice(arguments), - _ => { - let item = format!("Comptime evaluation for builtin function {builtin}"); - Err(InterpreterError::Unimplemented { item, location }) - } - } + let builtin = builtin.clone(); + builtin::call_builtin(self.interner, &builtin, arguments, location) } else if let Some(foreign) = func_attrs.foreign() { let item = format!("Comptime evaluation for foreign functions like {foreign}"); Err(InterpreterError::Unimplemented { item, location }) @@ -196,7 +237,8 @@ impl<'a> Interpreter<'a> { ) -> IResult<()> { match pattern { HirPattern::Identifier(identifier) => { - self.define(identifier.id, typ, argument, location) + self.define(identifier.id, argument); + Ok(()) } HirPattern::Mutable(pattern, _) => { self.define_pattern(pattern, typ, argument, location) @@ -218,8 +260,6 @@ impl<'a> Interpreter<'a> { }, HirPattern::Struct(struct_type, pattern_fields, _) => { self.push_scope(); - self.type_check(typ, &argument, location)?; - self.type_check(struct_type, &argument, location)?; let res = match argument { Value::Struct(fields, struct_type) if fields.len() == pattern_fields.len() => { @@ -255,30 +295,8 @@ impl<'a> Interpreter<'a> { } /// Define a new variable in the current scope - fn define( - &mut self, - id: DefinitionId, - typ: &Type, - argument: Value, - location: Location, - ) -> IResult<()> { - // Temporarily disabled since this fails on generic types - // self.type_check(typ, &argument, location)?; + fn define(&mut self, id: DefinitionId, argument: Value) { self.current_scope_mut().insert(id, argument); - Ok(()) - } - - /// Mutate an existing variable, potentially from a prior scope. - /// Also type checks the value being assigned - fn checked_mutate( - &mut self, - id: DefinitionId, - typ: &Type, - argument: Value, - location: Location, - ) -> IResult<()> { - self.type_check(typ, &argument, location)?; - self.mutate(id, argument, location) } /// Mutate an existing variable, potentially from a prior scope @@ -294,8 +312,7 @@ impl<'a> Interpreter<'a> { return Ok(()); } } - let name = self.interner.definition(id).name.clone(); - Err(InterpreterError::NonComptimeVarReferenced { name, location }) + Err(InterpreterError::VariableNotInScope { location }) } pub(super) fn lookup(&self, ident: &HirIdent) -> IResult { @@ -309,21 +326,12 @@ impl<'a> Interpreter<'a> { } } - // Justification for `NonComptimeVarReferenced`: - // If we have an id to lookup at all that means name resolution successfully - // found another variable in scope for this name. If the name is in scope - // but unknown by the interpreter it must be because it was not a comptime variable. - let name = self.interner.definition(id).name.clone(); - Err(InterpreterError::NonComptimeVarReferenced { name, location }) - } - - fn type_check(&self, typ: &Type, value: &Value, location: Location) -> IResult<()> { - let typ = typ.follow_bindings(); - let value_type = value.get_type(); - - typ.try_unify(&value_type, &mut TypeBindings::new()).map_err(|_| { - InterpreterError::TypeMismatch { expected: typ, value: value.clone(), location } - }) + if id == DefinitionId::dummy_id() { + Err(InterpreterError::VariableNotInScope { location }) + } else { + let name = self.interner.definition_name(id).to_string(); + Err(InterpreterError::NonComptimeVarReferenced { name, location }) + } } /// Evaluate an expression and return the result @@ -343,9 +351,9 @@ impl<'a> Interpreter<'a> { HirExpression::If(if_) => self.evaluate_if(if_, id), HirExpression::Tuple(tuple) => self.evaluate_tuple(tuple), HirExpression::Lambda(lambda) => self.evaluate_lambda(lambda, id), - HirExpression::Quote(block) => self.evaluate_quote(block, id), + HirExpression::Quote(tokens) => self.evaluate_quote(tokens, id), HirExpression::Comptime(block) => self.evaluate_block(block), - HirExpression::Unquote(block) => { + HirExpression::Unquote(tokens) => { // An Unquote expression being found is indicative of a macro being // expanded within another comptime fn which we don't currently support. let location = self.interner.expr_location(&id); @@ -359,12 +367,23 @@ impl<'a> Interpreter<'a> { } pub(super) fn evaluate_ident(&mut self, ident: HirIdent, id: ExprId) -> IResult { - let definition = self.interner.definition(ident.id); + let definition = self.interner.try_definition(ident.id).ok_or_else(|| { + let location = self.interner.expr_location(&id); + InterpreterError::VariableNotInScope { location } + })?; + + if let ImplKind::TraitMethod(method, _, _) = ident.impl_kind { + let method_id = resolve_trait_method(self.interner, method, id)?; + let typ = self.interner.id_type(id).follow_bindings(); + let bindings = self.interner.get_instantiation_bindings(id).clone(); + return Ok(Value::Function(method_id, typ, Rc::new(bindings))); + } match &definition.kind { DefinitionKind::Function(function_id) => { - let typ = self.interner.id_type(id); - Ok(Value::Function(*function_id, typ)) + let typ = self.interner.id_type(id).follow_bindings(); + let bindings = Rc::new(self.interner.get_instantiation_bindings(id).clone()); + Ok(Value::Function(*function_id, typ, bindings)) } DefinitionKind::Local(_) => self.lookup(&ident), DefinitionKind::Global(global_id) => { @@ -372,7 +391,12 @@ impl<'a> Interpreter<'a> { if let Ok(value) = self.lookup(&ident) { Ok(value) } else { - let let_ = self.interner.get_global_let_statement(*global_id).unwrap(); + let let_ = + self.interner.get_global_let_statement(*global_id).ok_or_else(|| { + let location = self.interner.expr_location(&id); + InterpreterError::VariableNotInScope { location } + })?; + if let_.comptime { self.evaluate_let(let_.clone())?; } @@ -535,7 +559,7 @@ impl<'a> Interpreter<'a> { } fn evaluate_array(&mut self, array: HirArrayLiteral, id: ExprId) -> IResult { - let typ = self.interner.id_type(id); + let typ = self.interner.id_type(id).follow_bindings(); match array { HirArrayLiteral::Standard(elements) => { @@ -569,8 +593,17 @@ impl<'a> Interpreter<'a> { fn evaluate_prefix(&mut self, prefix: HirPrefixExpression, id: ExprId) -> IResult { let rhs = self.evaluate(prefix.rhs)?; - match prefix.operator { - crate::ast::UnaryOp::Minus => match rhs { + self.evaluate_prefix_with_value(rhs, prefix.operator, id) + } + + fn evaluate_prefix_with_value( + &mut self, + rhs: Value, + operator: UnaryOp, + id: ExprId, + ) -> IResult { + match operator { + UnaryOp::Minus => match rhs { Value::Field(value) => Ok(Value::Field(FieldElement::zero() - value)), Value::I8(value) => Ok(Value::I8(-value)), Value::I16(value) => Ok(Value::I16(-value)), @@ -586,7 +619,7 @@ impl<'a> Interpreter<'a> { Err(InterpreterError::InvalidValueForUnary { value, location, operator }) } }, - crate::ast::UnaryOp::Not => match rhs { + UnaryOp::Not => match rhs { Value::Bool(value) => Ok(Value::Bool(!value)), Value::I8(value) => Ok(Value::I8(!value)), Value::I16(value) => Ok(Value::I16(!value)), @@ -601,8 +634,8 @@ impl<'a> Interpreter<'a> { Err(InterpreterError::InvalidValueForUnary { value, location, operator: "not" }) } }, - crate::ast::UnaryOp::MutableReference => Ok(Value::Pointer(Shared::new(rhs))), - crate::ast::UnaryOp::Dereference { implicitly_added: _ } => match rhs { + UnaryOp::MutableReference => Ok(Value::Pointer(Shared::new(rhs))), + UnaryOp::Dereference { implicitly_added: _ } => match rhs { Value::Pointer(element) => Ok(element.borrow().clone()), value => { let location = self.interner.expr_location(&id); @@ -616,11 +649,9 @@ impl<'a> Interpreter<'a> { let lhs = self.evaluate(infix.lhs)?; let rhs = self.evaluate(infix.rhs)?; - // TODO: Need to account for operator overloading - assert!( - self.interner.get_selected_impl_for_expression(id).is_none(), - "Operator overloading is unimplemented in the interpreter" - ); + if self.interner.get_selected_impl_for_expression(id).is_some() { + return self.evaluate_overloaded_infix(infix, lhs, rhs, id); + } use InterpreterError::InvalidValuesForBinary; match infix.operator.kind { @@ -864,6 +895,64 @@ impl<'a> Interpreter<'a> { } } + fn evaluate_overloaded_infix( + &mut self, + infix: HirInfixExpression, + lhs: Value, + rhs: Value, + id: ExprId, + ) -> IResult { + let method = infix.trait_method_id; + let operator = infix.operator.kind; + + let method_id = resolve_trait_method(self.interner, method, id)?; + let type_bindings = self.interner.get_instantiation_bindings(id).clone(); + + let lhs = (lhs, self.interner.expr_location(&infix.lhs)); + let rhs = (rhs, self.interner.expr_location(&infix.rhs)); + + let location = self.interner.expr_location(&id); + let value = self.call_function(method_id, vec![lhs, rhs], type_bindings, location)?; + + // Certain operators add additional operations after the trait call: + // - `!=`: Reverse the result of Eq + // - Comparator operators: Convert the returned `Ordering` to a boolean. + use BinaryOpKind::*; + match operator { + NotEqual => self.evaluate_prefix_with_value(value, UnaryOp::Not, id), + Less | LessEqual | Greater | GreaterEqual => self.evaluate_ordering(value, operator), + _ => Ok(value), + } + } + + /// Given the result of a `cmp` operation, convert it into the boolean result of the given operator. + /// - `<`: `ordering == Ordering::Less` + /// - `<=`: `ordering != Ordering::Greater` + /// - `>`: `ordering == Ordering::Greater` + /// - `<=`: `ordering != Ordering::Less` + fn evaluate_ordering(&self, ordering: Value, operator: BinaryOpKind) -> IResult { + let ordering = match ordering { + Value::Struct(fields, _) => match fields.into_iter().next().unwrap().1 { + Value::Field(ordering) => ordering, + _ => unreachable!("`cmp` should always return an Ordering value"), + }, + _ => unreachable!("`cmp` should always return an Ordering value"), + }; + + use BinaryOpKind::*; + let less_or_greater = if matches!(operator, Less | GreaterEqual) { + FieldElement::zero() // Ordering::Less + } else { + 2u128.into() // Ordering::Greater + }; + + if matches!(operator, Less | Greater) { + Ok(Value::Bool(ordering == less_or_greater)) + } else { + Ok(Value::Bool(ordering != less_or_greater)) + } + } + fn evaluate_index(&mut self, index: HirIndexExpression, id: ExprId) -> IResult { let array = self.evaluate(index.collection)?; let index = self.evaluate(index.index)?; @@ -892,7 +981,10 @@ impl<'a> Interpreter<'a> { let index = match index { Value::Field(value) => { - value.try_to_u64().expect("index could not fit into u64") as usize + value.try_to_u64().and_then(|value| value.try_into().ok()).ok_or_else(|| { + let typ = Type::default_int_type(); + InterpreterError::IntegerOutOfRangeForType { value, typ, location } + })? } Value::I8(value) => value as usize, Value::I16(value) => value as usize, @@ -929,13 +1021,25 @@ impl<'a> Interpreter<'a> { }) .collect::>()?; - let typ = self.interner.id_type(id); + let typ = self.interner.id_type(id).follow_bindings(); Ok(Value::Struct(fields, typ)) } fn evaluate_access(&mut self, access: HirMemberAccess, id: ExprId) -> IResult { let (fields, struct_type) = match self.evaluate(access.lhs)? { Value::Struct(fields, typ) => (fields, typ), + Value::Tuple(fields) => { + let (fields, field_types): (HashMap, Value>, Vec) = fields + .into_iter() + .enumerate() + .map(|(i, field)| { + let field_type = field.get_type().into_owned(); + let key_val_pair = (Rc::new(i.to_string()), field); + (key_val_pair, field_type) + }) + .unzip(); + (fields, Type::Tuple(field_types)) + } value => { let location = self.interner.expr_location(&id); return Err(InterpreterError::NonTupleOrStructInMemberAccess { value, location }); @@ -958,7 +1062,10 @@ impl<'a> Interpreter<'a> { let location = self.interner.expr_location(&id); match function { - Value::Function(function_id, _) => self.call_function(function_id, arguments, location), + Value::Function(function_id, _, bindings) => { + let bindings = unwrap_rc(bindings); + self.call_function(function_id, arguments, bindings, location) + } Value::Closure(closure, env, _) => self.call_closure(closure, env, arguments, location), value => Err(InterpreterError::NonFunctionCalled { value, location }), } @@ -987,7 +1094,7 @@ impl<'a> Interpreter<'a> { }; if let Some(method) = method { - self.call_function(method, arguments, location) + self.call_function(method, arguments, TypeBindings::new(), location) } else { Err(InterpreterError::NoMethodFound { name: method_name.clone(), typ, location }) } @@ -1132,17 +1239,14 @@ impl<'a> Interpreter<'a> { let environment = try_vecmap(&lambda.captures, |capture| self.lookup_id(capture.ident.id, location))?; - let typ = self.interner.id_type(id); + let typ = self.interner.id_type(id).follow_bindings(); Ok(Value::Closure(lambda, environment, typ)) } - fn evaluate_quote(&mut self, mut quoted: HirQuoted, expr_id: ExprId) -> IResult { - let file = self.interner.expr_location(&expr_id).file; - let values = try_vecmap(quoted.unquoted_exprs, |value| self.evaluate(value))?; - let args = UnquoteArgs { values, file }; - - self.substitute_unquoted_values_into_block(&mut quoted.quoted_block, &args); - Ok(Value::Code(Rc::new(quoted.quoted_block))) + fn evaluate_quote(&mut self, mut tokens: Tokens, expr_id: ExprId) -> IResult { + let location = self.interner.expr_location(&expr_id); + tokens = self.substitute_unquoted_values_into_tokens(tokens, location)?; + Ok(Value::Code(Rc::new(tokens))) } pub fn evaluate_statement(&mut self, statement: StmtId) -> IResult { @@ -1196,9 +1300,7 @@ impl<'a> Interpreter<'a> { fn store_lvalue(&mut self, lvalue: HirLValue, rhs: Value) -> IResult<()> { match lvalue { - HirLValue::Ident(ident, typ) => { - self.checked_mutate(ident.id, &typ, rhs, ident.location) - } + HirLValue::Ident(ident, typ) => self.mutate(ident.id, rhs, ident.location), HirLValue::Dereference { lvalue, element_type: _, location } => { match self.evaluate_lvalue(&lvalue)? { Value::Pointer(value) => { @@ -1209,15 +1311,22 @@ impl<'a> Interpreter<'a> { } } HirLValue::MemberAccess { object, field_name, field_index, typ: _, location } => { - let index = field_index.expect("The field index should be set after type checking"); - match self.evaluate_lvalue(&object)? { + let object_value = self.evaluate_lvalue(&object)?; + + let index = field_index.ok_or_else(|| { + let value = object_value.clone(); + let field_name = field_name.to_string(); + InterpreterError::ExpectedStructToHaveField { value, field_name, location } + })?; + + match object_value { Value::Tuple(mut fields) => { fields[index] = rhs; self.store_lvalue(*object, Value::Tuple(fields)) } Value::Struct(mut fields, typ) => { fields.insert(Rc::new(field_name.0.contents), rhs); - self.store_lvalue(*object, Value::Struct(fields, typ)) + self.store_lvalue(*object, Value::Struct(fields, typ.follow_bindings())) } value => { Err(InterpreterError::NonTupleOrStructInMemberAccess { value, location }) @@ -1254,9 +1363,16 @@ impl<'a> Interpreter<'a> { } } HirLValue::MemberAccess { object, field_name, field_index, typ: _, location } => { - let index = field_index.expect("The field index should be set after type checking"); + let object_value = self.evaluate_lvalue(object)?; + + let index = field_index.ok_or_else(|| { + let value = object_value.clone(); + let field_name = field_name.to_string(); + let location = *location; + InterpreterError::ExpectedStructToHaveField { value, field_name, location } + })?; - match self.evaluate_lvalue(object)? { + match object_value { Value::Tuple(mut values) => Ok(values.swap_remove(index)), Value::Struct(fields, _) => Ok(fields[&field_name.0.contents].clone()), value => Err(InterpreterError::NonTupleOrStructInMemberAccess { diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index 9216ba271c1..399d9905269 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -1,25 +1,309 @@ +use std::rc::Rc; + use noirc_errors::Location; use crate::{ - hir::comptime::{errors::IResult, Value}, - Type, + ast::IntegerBitSize, + hir::comptime::{errors::IResult, InterpreterError, Value}, + macros_api::{NodeInterner, Signedness}, + token::{SpannedToken, Token, Tokens}, + QuotedType, Type, }; -pub(super) fn array_len(arguments: &[(Value, Location)]) -> IResult { - assert_eq!(arguments.len(), 1, "ICE: `array_len` should only receive a single argument"); - match &arguments[0].0 { +pub(super) fn call_builtin( + interner: &mut NodeInterner, + name: &str, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + match name { + "array_len" => array_len(interner, arguments, location), + "as_slice" => as_slice(interner, arguments, location), + "is_unconstrained" => Ok(Value::Bool(true)), + "slice_insert" => slice_insert(interner, arguments, location), + "slice_pop_back" => slice_pop_back(interner, arguments, location), + "slice_pop_front" => slice_pop_front(interner, arguments, location), + "slice_push_back" => slice_push_back(interner, arguments, location), + "slice_push_front" => slice_push_front(interner, arguments, location), + "slice_remove" => slice_remove(interner, arguments, location), + "struct_def_as_type" => struct_def_as_type(interner, arguments, location), + "struct_def_fields" => struct_def_fields(interner, arguments, location), + "struct_def_generics" => struct_def_generics(interner, arguments, location), + _ => { + let item = format!("Comptime evaluation for builtin function {name}"); + Err(InterpreterError::Unimplemented { item, location }) + } + } +} + +fn check_argument_count( + expected: usize, + arguments: &[(Value, Location)], + location: Location, +) -> IResult<()> { + if arguments.len() == expected { + Ok(()) + } else { + let actual = arguments.len(); + Err(InterpreterError::ArgumentCountMismatch { expected, actual, location }) + } +} + +fn failing_constraint(message: impl Into, location: Location) -> IResult { + let message = Some(Value::String(Rc::new(message.into()))); + Err(InterpreterError::FailingConstraint { message, location }) +} + +fn get_slice( + interner: &NodeInterner, + value: Value, + location: Location, +) -> IResult<(im::Vector, Type)> { + match value { + Value::Slice(values, typ) => Ok((values, typ)), + value => { + let type_var = Box::new(interner.next_type_variable()); + let expected = Type::Slice(type_var); + Err(InterpreterError::TypeMismatch { expected, value, location }) + } + } +} + +fn get_u32(value: Value, location: Location) -> IResult { + match value { + Value::U32(value) => Ok(value), + value => { + let expected = Type::Integer(Signedness::Unsigned, IntegerBitSize::ThirtyTwo); + Err(InterpreterError::TypeMismatch { expected, value, location }) + } + } +} + +fn array_len( + interner: &NodeInterner, + mut arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + check_argument_count(1, &arguments, location)?; + + match arguments.pop().unwrap().0 { Value::Array(values, _) | Value::Slice(values, _) => Ok(Value::U32(values.len() as u32)), - // Type checking should prevent this branch being taken. - _ => unreachable!("ICE: Cannot query length of types other than arrays or slices"), + value => { + let type_var = Box::new(interner.next_type_variable()); + let expected = Type::Array(type_var.clone(), type_var); + Err(InterpreterError::TypeMismatch { expected, value, location }) + } } } -pub(super) fn as_slice(mut arguments: Vec<(Value, Location)>) -> IResult { - assert_eq!(arguments.len(), 1, "ICE: `as_slice` should only receive a single argument"); +fn as_slice( + interner: &NodeInterner, + mut arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + check_argument_count(1, &arguments, location)?; + let (array, _) = arguments.pop().unwrap(); match array { Value::Array(values, Type::Array(_, typ)) => Ok(Value::Slice(values, Type::Slice(typ))), - // Type checking should prevent this branch being taken. - _ => unreachable!("ICE: Cannot convert types other than arrays into slices"), + value => { + let type_var = Box::new(interner.next_type_variable()); + let expected = Type::Array(type_var.clone(), type_var); + Err(InterpreterError::TypeMismatch { expected, value, location }) + } } } + +fn slice_push_back( + interner: &NodeInterner, + mut arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + check_argument_count(2, &arguments, location)?; + + let (element, _) = arguments.pop().unwrap(); + let (mut values, typ) = get_slice(interner, arguments.pop().unwrap().0, location)?; + values.push_back(element); + Ok(Value::Slice(values, typ)) +} + +/// fn as_type(self) -> Quoted +fn struct_def_as_type( + interner: &NodeInterner, + mut arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + check_argument_count(1, &arguments, location)?; + + let (struct_def, span) = match arguments.pop().unwrap() { + (Value::StructDefinition(id), location) => (id, location.span), + value => { + let expected = Type::Quoted(QuotedType::StructDefinition); + return Err(InterpreterError::TypeMismatch { expected, location, value: value.0 }); + } + }; + + let struct_def = interner.get_struct(struct_def); + let struct_def = struct_def.borrow(); + let make_token = |name| SpannedToken::new(Token::Ident(name), span); + + let mut tokens = vec![make_token(struct_def.name.to_string())]; + + for (i, generic) in struct_def.generics.iter().enumerate() { + if i != 0 { + tokens.push(SpannedToken::new(Token::Comma, span)); + } + tokens.push(make_token(generic.type_var.borrow().to_string())); + } + + Ok(Value::Code(Rc::new(Tokens(tokens)))) +} + +/// fn generics(self) -> [Quoted] +fn struct_def_generics( + interner: &NodeInterner, + mut arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + check_argument_count(1, &arguments, location)?; + + let (struct_def, span) = match arguments.pop().unwrap() { + (Value::StructDefinition(id), location) => (id, location.span), + value => { + let expected = Type::Quoted(QuotedType::StructDefinition); + return Err(InterpreterError::TypeMismatch { expected, location, value: value.0 }); + } + }; + + let struct_def = interner.get_struct(struct_def); + let struct_def = struct_def.borrow(); + + let generics = struct_def.generics.iter().map(|generic| { + let name = SpannedToken::new(Token::Ident(generic.type_var.borrow().to_string()), span); + Value::Code(Rc::new(Tokens(vec![name]))) + }); + + let typ = Type::Slice(Box::new(Type::Quoted(QuotedType::Quoted))); + Ok(Value::Slice(generics.collect(), typ)) +} + +/// fn fields(self) -> [(Quoted, Quoted)] +/// Returns (name, type) pairs of each field of this StructDefinition +fn struct_def_fields( + interner: &mut NodeInterner, + mut arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + check_argument_count(1, &arguments, location)?; + + let (struct_def, span) = match arguments.pop().unwrap() { + (Value::StructDefinition(id), location) => (id, location.span), + value => { + let expected = Type::Quoted(QuotedType::StructDefinition); + return Err(InterpreterError::TypeMismatch { expected, location, value: value.0 }); + } + }; + + let struct_def = interner.get_struct(struct_def); + let struct_def = struct_def.borrow(); + + let make_token = |name| SpannedToken::new(Token::Ident(name), span); + let make_quoted = |tokens| Value::Code(Rc::new(Tokens(tokens))); + + let mut fields = im::Vector::new(); + + for (name, typ) in struct_def.get_fields_as_written() { + let name = make_quoted(vec![make_token(name)]); + let id = interner.push_quoted_type(typ); + let typ = SpannedToken::new(Token::QuotedType(id), span); + let typ = Value::Code(Rc::new(Tokens(vec![typ]))); + fields.push_back(Value::Tuple(vec![name, typ])); + } + + let typ = Type::Slice(Box::new(Type::Tuple(vec![ + Type::Quoted(QuotedType::Quoted), + Type::Quoted(QuotedType::Quoted), + ]))); + Ok(Value::Slice(fields, typ)) +} + +fn slice_remove( + interner: &mut NodeInterner, + mut arguments: Vec<(Value, Location)>, + location: Location, +) -> Result { + check_argument_count(2, &arguments, location)?; + + let index = get_u32(arguments.pop().unwrap().0, location)? as usize; + let (mut values, typ) = get_slice(interner, arguments.pop().unwrap().0, location)?; + + if values.is_empty() { + return failing_constraint("slice_remove called on empty slice", location); + } + + if index >= values.len() { + let message = format!( + "slice_remove: index {index} is out of bounds for a slice of length {}", + values.len() + ); + return failing_constraint(message, location); + } + + let element = values.remove(index); + Ok(Value::Tuple(vec![Value::Slice(values, typ), element])) +} + +fn slice_push_front( + interner: &mut NodeInterner, + mut arguments: Vec<(Value, Location)>, + location: Location, +) -> Result { + check_argument_count(2, &arguments, location)?; + + let (element, _) = arguments.pop().unwrap(); + let (mut values, typ) = get_slice(interner, arguments.pop().unwrap().0, location)?; + values.push_front(element); + Ok(Value::Slice(values, typ)) +} + +fn slice_pop_front( + interner: &mut NodeInterner, + mut arguments: Vec<(Value, Location)>, + location: Location, +) -> Result { + check_argument_count(1, &arguments, location)?; + + let (mut values, typ) = get_slice(interner, arguments.pop().unwrap().0, location)?; + match values.pop_front() { + Some(element) => Ok(Value::Tuple(vec![element, Value::Slice(values, typ)])), + None => failing_constraint("slice_pop_front called on empty slice", location), + } +} + +fn slice_pop_back( + interner: &mut NodeInterner, + mut arguments: Vec<(Value, Location)>, + location: Location, +) -> Result { + check_argument_count(1, &arguments, location)?; + + let (mut values, typ) = get_slice(interner, arguments.pop().unwrap().0, location)?; + match values.pop_back() { + Some(element) => Ok(Value::Tuple(vec![Value::Slice(values, typ), element])), + None => failing_constraint("slice_pop_back called on empty slice", location), + } +} + +fn slice_insert( + interner: &mut NodeInterner, + mut arguments: Vec<(Value, Location)>, + location: Location, +) -> Result { + check_argument_count(3, &arguments, location)?; + + let (element, _) = arguments.pop().unwrap(); + let index = get_u32(arguments.pop().unwrap().0, location)?; + let (mut values, typ) = get_slice(interner, arguments.pop().unwrap().0, location)?; + values.insert(index as usize, element); + Ok(Value::Slice(values, typ)) +} diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/unquote.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/unquote.rs index 58ca345ca90..a1ceb27afb2 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/unquote.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/unquote.rs @@ -1,287 +1,42 @@ -use fm::FileId; use noirc_errors::Location; use crate::{ - ast::{ - ArrayLiteral, AssignStatement, ConstrainStatement, ConstructorExpression, IfExpression, - InfixExpression, Lambda, - }, - hir::comptime::{errors::IResult, Value}, - macros_api::{ - BlockExpression, CallExpression, CastExpression, Expression, ExpressionKind, - ForLoopStatement, ForRange, IndexExpression, LetStatement, Literal, MemberAccessExpression, - MethodCallExpression, PrefixExpression, Statement, StatementKind, - }, + hir::comptime::{errors::IResult, value::unwrap_rc, Value}, + token::{SpannedToken, Token, Tokens}, }; use super::Interpreter; -pub(super) struct UnquoteArgs { - pub(super) values: Vec, - pub(super) file: FileId, -} - impl<'a> Interpreter<'a> { - pub(super) fn substitute_unquoted_values_into_block( - &mut self, - block: &mut BlockExpression, - args: &UnquoteArgs, - ) -> IResult<()> { - for statement in &mut block.statements { - self.substitute_unquoted_into_statement(statement, args)?; - } - Ok(()) - } - - fn substitute_unquoted_into_statement( - &mut self, - statement: &mut Statement, - args: &UnquoteArgs, - ) -> IResult<()> { - match &mut statement.kind { - StatementKind::Let(let_) => self.substitute_unquoted_into_let(let_, args), - StatementKind::Constrain(constrain) => { - self.substitute_unquoted_into_constrain(constrain, args) - } - StatementKind::Expression(expr) => self.substitute_unquoted_into_expr(expr, args), - StatementKind::Assign(assign) => self.substitute_unquoted_into_assign(assign, args), - StatementKind::For(for_) => self.substitute_unquoted_into_for(for_, args), - StatementKind::Break => Ok(()), - StatementKind::Continue => Ok(()), - StatementKind::Comptime(comptime) => { - self.substitute_unquoted_into_statement(comptime, args) - } - StatementKind::Semi(expr) => self.substitute_unquoted_into_expr(expr, args), - StatementKind::Error => Ok(()), - } - } - - fn substitute_unquoted_into_constrain( - &mut self, - constrain: &mut ConstrainStatement, - args: &UnquoteArgs, - ) -> IResult<()> { - self.substitute_unquoted_into_expr(&mut constrain.0, args)?; - if let Some(msg) = constrain.1.as_mut() { - self.substitute_unquoted_into_expr(msg, args)?; - } - Ok(()) - } - - fn substitute_unquoted_into_let( - &mut self, - let_: &mut LetStatement, - args: &UnquoteArgs, - ) -> IResult<()> { - self.substitute_unquoted_into_expr(&mut let_.expression, args) - } - - fn substitute_unquoted_into_assign( - &mut self, - assign: &mut AssignStatement, - args: &UnquoteArgs, - ) -> IResult<()> { - self.substitute_unquoted_into_expr(&mut assign.expression, args) - } - - fn substitute_unquoted_into_for( - &mut self, - for_: &mut ForLoopStatement, - args: &UnquoteArgs, - ) -> IResult<()> { - match &mut for_.range { - ForRange::Range(start, end) => { - self.substitute_unquoted_into_expr(start, args)?; - self.substitute_unquoted_into_expr(end, args)?; - } - ForRange::Array(array) => { - self.substitute_unquoted_into_expr(array, args)?; - } - }; - self.substitute_unquoted_into_expr(&mut for_.block, args) - } - - fn substitute_unquoted_into_expr( - &mut self, - expr: &mut Expression, - args: &UnquoteArgs, - ) -> IResult<()> { - match &mut expr.kind { - ExpressionKind::Literal(literal) => { - self.substitute_unquoted_into_literal(literal, args) - } - ExpressionKind::Block(block) => self.substitute_unquoted_values_into_block(block, args), - ExpressionKind::Prefix(prefix) => self.substitute_unquoted_into_prefix(prefix, args), - ExpressionKind::Index(index) => self.substitute_unquoted_into_index(index, args), - ExpressionKind::Call(call) => self.substitute_unquoted_into_call(call, args), - ExpressionKind::MethodCall(call) => { - self.substitute_unquoted_into_method_call(call, args) - } - ExpressionKind::Constructor(constructor) => { - self.substitute_unquoted_into_constructor(constructor, args) - } - ExpressionKind::MemberAccess(access) => { - self.substitute_unquoted_into_access(access, args) - } - ExpressionKind::Cast(cast) => self.substitute_unquoted_into_cast(cast, args), - ExpressionKind::Infix(infix) => self.substitute_unquoted_into_infix(infix, args), - ExpressionKind::If(if_) => self.substitute_unquoted_into_if(if_, args), - ExpressionKind::Variable(_, _) => Ok(()), - ExpressionKind::Tuple(tuple) => self.substitute_unquoted_into_tuple(tuple, args), - ExpressionKind::Lambda(lambda) => self.substitute_unquoted_into_lambda(lambda, args), - ExpressionKind::Parenthesized(expr) => self.substitute_unquoted_into_expr(expr, args), - ExpressionKind::Quote(quote, _) => { - self.substitute_unquoted_values_into_block(quote, args) - } - ExpressionKind::Unquote(unquote) => self.substitute_unquoted_into_expr(unquote, args), - ExpressionKind::Comptime(comptime, _) => { - self.substitute_unquoted_values_into_block(comptime, args) - } - ExpressionKind::Resolved(_) => Ok(()), - ExpressionKind::Error => Ok(()), - ExpressionKind::UnquoteMarker(index) => { - let location = Location::new(expr.span, args.file); - *expr = args.values[*index].clone().into_expression(self.interner, location)?; - Ok(()) - } - } - } - - fn substitute_unquoted_into_literal( - &mut self, - literal: &mut Literal, - args: &UnquoteArgs, - ) -> IResult<()> { - match literal { - Literal::Array(array) | Literal::Slice(array) => match array { - ArrayLiteral::Standard(elements) => { - for element in elements { - self.substitute_unquoted_into_expr(element, args)?; + /// Evaluates any expressions within UnquoteMarkers in the given token list + /// and replaces the expression held by the marker with the evaluated value + /// in expression form. + pub(super) fn substitute_unquoted_values_into_tokens( + &mut self, + tokens: Tokens, + location: Location, + ) -> IResult { + let mut new_tokens = Vec::with_capacity(tokens.0.len()); + + for token in tokens.0 { + let span = token.to_span(); + match token.token() { + Token::UnquoteMarker(id) => { + match self.evaluate(*id)? { + // If the value is already quoted we don't want to change the token stream by + // turning it into a Quoted block (which would add `quote`, `{`, and `}` tokens). + Value::Code(stream) => new_tokens.extend(unwrap_rc(stream).0), + value => { + let new_id = value.into_hir_expression(self.interner, location)?; + let new_token = Token::UnquoteMarker(new_id); + new_tokens.push(SpannedToken::new(new_token, span)); + } } - Ok(()) - } - ArrayLiteral::Repeated { repeated_element, length } => { - self.substitute_unquoted_into_expr(repeated_element, args)?; - self.substitute_unquoted_into_expr(length, args)?; - Ok(()) } - }, - Literal::Bool(_) - | Literal::Integer(_, _) - | Literal::Str(_) - | Literal::RawStr(_, _) - | Literal::FmtStr(_) - | Literal::Unit => Ok(()), - } - } - - fn substitute_unquoted_into_prefix( - &mut self, - prefix: &mut PrefixExpression, - args: &UnquoteArgs, - ) -> IResult<()> { - self.substitute_unquoted_into_expr(&mut prefix.rhs, args) - } - - fn substitute_unquoted_into_index( - &mut self, - index: &mut IndexExpression, - args: &UnquoteArgs, - ) -> IResult<()> { - self.substitute_unquoted_into_expr(&mut index.collection, args)?; - self.substitute_unquoted_into_expr(&mut index.index, args) - } - - fn substitute_unquoted_into_call( - &mut self, - call: &mut CallExpression, - args: &UnquoteArgs, - ) -> IResult<()> { - self.substitute_unquoted_into_expr(&mut call.func, args)?; - for arg in &mut call.arguments { - self.substitute_unquoted_into_expr(arg, args)?; - } - Ok(()) - } - - fn substitute_unquoted_into_method_call( - &mut self, - call: &mut MethodCallExpression, - args: &UnquoteArgs, - ) -> IResult<()> { - self.substitute_unquoted_into_expr(&mut call.object, args)?; - for arg in &mut call.arguments { - self.substitute_unquoted_into_expr(arg, args)?; - } - Ok(()) - } - - fn substitute_unquoted_into_constructor( - &mut self, - constructor: &mut ConstructorExpression, - args: &UnquoteArgs, - ) -> IResult<()> { - for (_, field) in &mut constructor.fields { - self.substitute_unquoted_into_expr(field, args)?; - } - Ok(()) - } - - fn substitute_unquoted_into_access( - &mut self, - access: &mut MemberAccessExpression, - args: &UnquoteArgs, - ) -> IResult<()> { - self.substitute_unquoted_into_expr(&mut access.lhs, args) - } - - fn substitute_unquoted_into_cast( - &mut self, - cast: &mut CastExpression, - args: &UnquoteArgs, - ) -> IResult<()> { - self.substitute_unquoted_into_expr(&mut cast.lhs, args) - } - - fn substitute_unquoted_into_infix( - &mut self, - infix: &mut InfixExpression, - args: &UnquoteArgs, - ) -> IResult<()> { - self.substitute_unquoted_into_expr(&mut infix.lhs, args)?; - self.substitute_unquoted_into_expr(&mut infix.rhs, args) - } - - fn substitute_unquoted_into_if( - &mut self, - if_: &mut IfExpression, - args: &UnquoteArgs, - ) -> IResult<()> { - self.substitute_unquoted_into_expr(&mut if_.condition, args)?; - self.substitute_unquoted_into_expr(&mut if_.consequence, args)?; - - if let Some(alternative) = if_.alternative.as_mut() { - self.substitute_unquoted_into_expr(alternative, args)?; - } - Ok(()) - } - - fn substitute_unquoted_into_tuple( - &mut self, - tuple: &mut [Expression], - args: &UnquoteArgs, - ) -> IResult<()> { - for field in tuple { - self.substitute_unquoted_into_expr(field, args)?; + _ => new_tokens.push(token), + } } - Ok(()) - } - fn substitute_unquoted_into_lambda( - &mut self, - lambda: &mut Lambda, - args: &UnquoteArgs, - ) -> IResult<()> { - self.substitute_unquoted_into_expr(&mut lambda.body, args) + Ok(Tokens(new_tokens)) } } diff --git a/compiler/noirc_frontend/src/hir/comptime/mod.rs b/compiler/noirc_frontend/src/hir/comptime/mod.rs index ab984d2f2be..3cc7b5f7e98 100644 --- a/compiler/noirc_frontend/src/hir/comptime/mod.rs +++ b/compiler/noirc_frontend/src/hir/comptime/mod.rs @@ -1,4 +1,5 @@ mod errors; +mod hir_to_display_ast; mod interpreter; mod scan; mod tests; diff --git a/compiler/noirc_frontend/src/hir/comptime/scan.rs b/compiler/noirc_frontend/src/hir/comptime/scan.rs index f2e21e608ea..f9cc54ef9e4 100644 --- a/compiler/noirc_frontend/src/hir/comptime/scan.rs +++ b/compiler/noirc_frontend/src/hir/comptime/scan.rs @@ -29,6 +29,8 @@ use super::{ Value, }; +use noirc_errors::Location; + #[allow(dead_code)] impl<'interner> Interpreter<'interner> { /// Scan through a function, evaluating any Comptime nodes found. @@ -80,9 +82,10 @@ impl<'interner> Interpreter<'interner> { HirExpression::Lambda(lambda) => self.scan_lambda(lambda), HirExpression::Comptime(block) => { let location = self.interner.expr_location(&expr); - let new_expr = + let new_expr_id = self.evaluate_block(block)?.into_hir_expression(self.interner, location)?; - let new_expr = self.interner.expression(&new_expr); + let new_expr = self.interner.expression(&new_expr_id); + self.debug_comptime(new_expr_id, location); self.interner.replace_expr(&expr, new_expr); Ok(()) } @@ -100,7 +103,7 @@ impl<'interner> Interpreter<'interner> { // missed it somehow. In the future we may allow users to manually write unquote // expressions in their code but for now this is unreachable. HirExpression::Unquote(block) => { - unreachable!("Found unquote block while scanning: {block}") + unreachable!("Found unquote block while scanning: {block:?}") } } } @@ -118,7 +121,9 @@ impl<'interner> Interpreter<'interner> { if let Ok(value) = self.evaluate_ident(ident, id) { // TODO(#4922): Inlining closures is currently unimplemented if !matches!(value, Value::Closure(..)) { - self.inline_expression(value, id)?; + let new_expr = self.inline_expression(value, id)?; + let location = self.interner.id_location(id); + self.debug_comptime(new_expr, location); } } Ok(()) @@ -231,6 +236,7 @@ impl<'interner> Interpreter<'interner> { let new_expr = self .evaluate_comptime(comptime)? .into_hir_expression(self.interner, location)?; + self.debug_comptime(new_expr, location); self.interner.replace_statement(statement, HirStatement::Expression(new_expr)); Ok(()) } @@ -247,11 +253,19 @@ impl<'interner> Interpreter<'interner> { Ok(()) } - fn inline_expression(&mut self, value: Value, expr: ExprId) -> IResult<()> { + fn inline_expression(&mut self, value: Value, expr: ExprId) -> IResult { let location = self.interner.expr_location(&expr); - let new_expr = value.into_hir_expression(self.interner, location)?; - let new_expr = self.interner.expression(&new_expr); + let new_expr_id = value.into_hir_expression(self.interner, location)?; + let new_expr = self.interner.expression(&new_expr_id); self.interner.replace_expr(&expr, new_expr); - Ok(()) + Ok(new_expr_id) + } + + fn debug_comptime(&mut self, expr: ExprId, location: Location) { + if Some(location.file) == self.debug_comptime_in_file { + let expr = expr.to_display_ast(self.interner); + self.debug_comptime_evaluations + .push(InterpreterError::debug_evaluate_comptime(expr, location)); + } } } diff --git a/compiler/noirc_frontend/src/hir/comptime/tests.rs b/compiler/noirc_frontend/src/hir/comptime/tests.rs index 43f6e21905b..e8e05506c94 100644 --- a/compiler/noirc_frontend/src/hir/comptime/tests.rs +++ b/compiler/noirc_frontend/src/hir/comptime/tests.rs @@ -7,15 +7,24 @@ use noirc_errors::Location; use super::errors::InterpreterError; use super::interpreter::Interpreter; use super::value::Value; +use crate::graph::CrateId; use crate::hir::type_check::test::type_check_src_code; fn interpret_helper(src: &str, func_namespace: Vec) -> Result { let (mut interner, main_id) = type_check_src_code(src, func_namespace); let mut scopes = vec![HashMap::default()]; - let mut interpreter = Interpreter::new(&mut interner, &mut scopes); + let no_debug_evaluate_comptime = None; + let mut interpreter_errors = vec![]; + let mut interpreter = Interpreter::new( + &mut interner, + &mut scopes, + CrateId::Root(0), + no_debug_evaluate_comptime, + &mut interpreter_errors, + ); let no_location = Location::dummy(); - interpreter.call_function(main_id, Vec::new(), no_location) + interpreter.call_function(main_id, Vec::new(), HashMap::new(), no_location) } fn interpret(src: &str, func_namespace: Vec) -> Value { @@ -30,14 +39,14 @@ fn interpret_expect_error(src: &str, func_namespace: Vec) -> Interpreter #[test] fn interpreter_works() { - let program = "fn main() -> pub Field { 3 }"; + let program = "comptime fn main() -> pub Field { 3 }"; let result = interpret(program, vec!["main".into()]); assert_eq!(result, Value::Field(3u128.into())); } #[test] fn mutation_works() { - let program = "fn main() -> pub i8 { + let program = "comptime fn main() -> pub i8 { let mut x = 3; x = 4; x @@ -48,7 +57,7 @@ fn mutation_works() { #[test] fn mutating_references() { - let program = "fn main() -> pub i32 { + let program = "comptime fn main() -> pub i32 { let x = &mut 3; *x = 4; *x @@ -59,7 +68,7 @@ fn mutating_references() { #[test] fn mutating_mutable_references() { - let program = "fn main() -> pub i64 { + let program = "comptime fn main() -> pub i64 { let mut x = &mut 3; *x = 4; *x @@ -70,7 +79,7 @@ fn mutating_mutable_references() { #[test] fn mutating_arrays() { - let program = "fn main() -> pub u8 { + let program = "comptime fn main() -> pub u8 { let mut a1 = [1, 2, 3, 4]; a1[1] = 22; a1[1] @@ -81,7 +90,7 @@ fn mutating_arrays() { #[test] fn mutate_in_new_scope() { - let program = "fn main() -> pub u8 { + let program = "comptime fn main() -> pub u8 { let mut x = 0; x += 1; { @@ -95,7 +104,7 @@ fn mutate_in_new_scope() { #[test] fn for_loop() { - let program = "fn main() -> pub u8 { + let program = "comptime fn main() -> pub u8 { let mut x = 0; for i in 0 .. 6 { x += i; @@ -108,7 +117,7 @@ fn for_loop() { #[test] fn for_loop_u16() { - let program = "fn main() -> pub u16 { + let program = "comptime fn main() -> pub u16 { let mut x = 0; for i in 0 .. 6 { x += i; @@ -121,7 +130,7 @@ fn for_loop_u16() { #[test] fn for_loop_with_break() { - let program = "unconstrained fn main() -> pub u32 { + let program = "unconstrained comptime fn main() -> pub u32 { let mut x = 0; for i in 0 .. 6 { if i == 4 { @@ -137,7 +146,7 @@ fn for_loop_with_break() { #[test] fn for_loop_with_continue() { - let program = "unconstrained fn main() -> pub u64 { + let program = "unconstrained comptime fn main() -> pub u64 { let mut x = 0; for i in 0 .. 6 { if i == 4 { @@ -153,7 +162,7 @@ fn for_loop_with_continue() { #[test] fn assert() { - let program = "fn main() { + let program = "comptime fn main() { assert(1 == 1); }"; let result = interpret(program, vec!["main".into()]); @@ -162,7 +171,7 @@ fn assert() { #[test] fn assert_fail() { - let program = "fn main() { + let program = "comptime fn main() { assert(1 == 2); }"; let result = interpret_expect_error(program, vec!["main".into()]); @@ -171,7 +180,7 @@ fn assert_fail() { #[test] fn lambda() { - let program = "fn main() -> pub u8 { + let program = "comptime fn main() -> pub u8 { let f = |x: u8| x + 1; f(1) }"; @@ -182,11 +191,11 @@ fn lambda() { #[test] fn non_deterministic_recursion() { let program = " - fn main() -> pub u64 { + comptime fn main() -> pub u64 { fib(10) } - fn fib(x: u64) -> u64 { + comptime fn fib(x: u64) -> u64 { if x <= 1 { x } else { @@ -196,3 +205,18 @@ fn non_deterministic_recursion() { let result = interpret(program, vec!["main".into(), "fib".into()]); assert_eq!(result, Value::U64(55)); } + +#[test] +fn generic_functions() { + let program = " + fn main() -> pub u8 { + apply(1, |x| x + 1) + } + + fn apply(x: T, f: fn[Env](T) -> U) -> U { + f(x) + } + "; + let result = interpret(program, vec!["main".into(), "apply".into()]); + assert!(matches!(result, Value::U8(2))); +} diff --git a/compiler/noirc_frontend/src/hir/comptime/value.rs b/compiler/noirc_frontend/src/hir/comptime/value.rs index a75d4fd2b24..9e15b73324f 100644 --- a/compiler/noirc_frontend/src/hir/comptime/value.rs +++ b/compiler/noirc_frontend/src/hir/comptime/value.rs @@ -1,21 +1,22 @@ use std::{borrow::Cow, fmt::Display, rc::Rc}; use acvm::{AcirField, FieldElement}; +use chumsky::Parser; use im::Vector; use iter_extended::{try_vecmap, vecmap}; use noirc_errors::Location; use crate::{ - ast::{ - ArrayLiteral, BlockExpression, ConstructorExpression, Ident, IntegerBitSize, Signedness, - }, + ast::{ArrayLiteral, ConstructorExpression, Ident, IntegerBitSize, Signedness}, hir_def::expr::{HirArrayLiteral, HirConstructorExpression, HirIdent, HirLambda, ImplKind}, macros_api::{ Expression, ExpressionKind, HirExpression, HirLiteral, Literal, NodeInterner, Path, StructId, }, node_interner::{ExprId, FuncId}, - QuotedType, Shared, Type, + parser::{self, NoirParser, TopLevelStatement}, + token::{SpannedToken, Token, Tokens}, + QuotedType, Shared, Type, TypeBindings, }; use rustc_hash::FxHashMap as HashMap; @@ -35,15 +36,15 @@ pub enum Value { U32(u32), U64(u64), String(Rc), - Function(FuncId, Type), + Function(FuncId, Type, Rc), Closure(HirLambda, Vec, Type), Tuple(Vec), Struct(HashMap, Value>, Type), Pointer(Shared), Array(Vector, Type), Slice(Vector, Type), - Code(Rc), - TypeDefinition(StructId), + Code(Rc), + StructDefinition(StructId), } impl Value { @@ -64,7 +65,7 @@ impl Value { let length = Type::Constant(value.len() as u32); Type::String(Box::new(length)) } - Value::Function(_, typ) => return Cow::Borrowed(typ), + Value::Function(_, typ, _) => return Cow::Borrowed(typ), Value::Closure(_, _, typ) => return Cow::Borrowed(typ), Value::Tuple(fields) => { Type::Tuple(vecmap(fields, |field| field.get_type().into_owned())) @@ -72,8 +73,8 @@ impl Value { Value::Struct(_, typ) => return Cow::Borrowed(typ), Value::Array(_, typ) => return Cow::Borrowed(typ), Value::Slice(_, typ) => return Cow::Borrowed(typ), - Value::Code(_) => Type::Quoted(QuotedType::Expr), - Value::TypeDefinition(_) => Type::Quoted(QuotedType::TypeDefinition), + Value::Code(_) => Type::Quoted(QuotedType::Quoted), + Value::StructDefinition(_) => Type::Quoted(QuotedType::StructDefinition), Value::Pointer(element) => { let element = element.borrow().get_type().into_owned(); Type::MutableReference(Box::new(element)) @@ -127,13 +128,14 @@ impl Value { ExpressionKind::Literal(Literal::Integer((value as u128).into(), false)) } Value::String(value) => ExpressionKind::Literal(Literal::Str(unwrap_rc(value))), - Value::Function(id, typ) => { + Value::Function(id, typ, bindings) => { let id = interner.function_definition_id(id); let impl_kind = ImplKind::NotATraitMethod; let ident = HirIdent { location, id, impl_kind }; let expr_id = interner.push_expr(HirExpression::Ident(ident, None)); interner.push_expr_location(expr_id, location.span, location.file); interner.push_expr_type(expr_id, typ); + interner.store_instantiation_bindings(expr_id, unwrap_rc(bindings)); ExpressionKind::Resolved(expr_id) } Value::Closure(_lambda, _env, _typ) => { @@ -174,8 +176,23 @@ impl Value { try_vecmap(elements, |element| element.into_expression(interner, location))?; ExpressionKind::Literal(Literal::Slice(ArrayLiteral::Standard(elements))) } - Value::Code(block) => ExpressionKind::Block(unwrap_rc(block)), - Value::Pointer(_) | Value::TypeDefinition(_) => { + Value::Code(tokens) => { + // Wrap the tokens in '{' and '}' so that we can parse statements as well. + let mut tokens_to_parse = tokens.as_ref().clone(); + tokens_to_parse.0.insert(0, SpannedToken::new(Token::LeftBrace, location.span)); + tokens_to_parse.0.push(SpannedToken::new(Token::RightBrace, location.span)); + + return match parser::expression().parse(tokens_to_parse) { + Ok(expr) => Ok(expr), + Err(mut errors) => { + let error = errors.swap_remove(0); + let file = location.file; + let rule = "an expression"; + Err(InterpreterError::FailedToParseMacro { error, file, tokens, rule }) + } + }; + } + Value::Pointer(_) | Value::StructDefinition(_) => { return Err(InterpreterError::CannotInlineMacro { value: self, location }) } }; @@ -231,10 +248,15 @@ impl Value { HirExpression::Literal(HirLiteral::Integer((value as u128).into(), false)) } Value::String(value) => HirExpression::Literal(HirLiteral::Str(unwrap_rc(value))), - Value::Function(id, _typ) => { + Value::Function(id, typ, bindings) => { let id = interner.function_definition_id(id); let impl_kind = ImplKind::NotATraitMethod; - HirExpression::Ident(HirIdent { location, id, impl_kind }, None) + let ident = HirIdent { location, id, impl_kind }; + let expr_id = interner.push_expr(HirExpression::Ident(ident, None)); + interner.push_expr_location(expr_id, location.span, location.file); + interner.push_expr_type(expr_id, typ); + interner.store_instantiation_bindings(expr_id, unwrap_rc(bindings)); + return Ok(expr_id); } Value::Closure(_lambda, _env, _typ) => { // TODO: How should a closure's environment be inlined? @@ -276,7 +298,7 @@ impl Value { HirExpression::Literal(HirLiteral::Slice(HirArrayLiteral::Standard(elements))) } Value::Code(block) => HirExpression::Unquote(unwrap_rc(block)), - Value::Pointer(_) | Value::TypeDefinition(_) => { + Value::Pointer(_) | Value::StructDefinition(_) => { return Err(InterpreterError::CannotInlineMacro { value: self, location }) } }; @@ -303,13 +325,34 @@ impl Value { _ => None, } } + + pub(crate) fn into_top_level_items( + self, + location: Location, + ) -> IResult> { + match self { + Value::Code(tokens) => parse_tokens(tokens, parser::top_level_items(), location.file), + value => Err(InterpreterError::CannotInlineMacro { value, location }), + } + } } /// Unwraps an Rc value without cloning the inner value if the reference count is 1. Clones otherwise. -fn unwrap_rc(rc: Rc) -> T { +pub(crate) fn unwrap_rc(rc: Rc) -> T { Rc::try_unwrap(rc).unwrap_or_else(|rc| (*rc).clone()) } +fn parse_tokens(tokens: Rc, parser: impl NoirParser, file: fm::FileId) -> IResult { + match parser.parse(tokens.as_ref().clone()) { + Ok(expr) => Ok(expr), + Err(mut errors) => { + let error = errors.swap_remove(0); + let rule = "an expression"; + Err(InterpreterError::FailedToParseMacro { error, file, tokens, rule }) + } + } +} + impl Display for Value { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -328,7 +371,7 @@ impl Display for Value { Value::U32(value) => write!(f, "{value}"), Value::U64(value) => write!(f, "{value}"), Value::String(value) => write!(f, "{value}"), - Value::Function(_, _) => write!(f, "(function)"), + Value::Function(..) => write!(f, "(function)"), Value::Closure(_, _, _) => write!(f, "(closure)"), Value::Tuple(fields) => { let fields = vecmap(fields, ToString::to_string); @@ -351,8 +394,14 @@ impl Display for Value { let values = vecmap(values, ToString::to_string); write!(f, "&[{}]", values.join(", ")) } - Value::Code(block) => write!(f, "quote {block}"), - Value::TypeDefinition(_) => write!(f, "(type definition)"), + Value::Code(tokens) => { + write!(f, "quote {{")?; + for token in tokens.0.iter() { + write!(f, " {token}")?; + } + write!(f, " }}") + } + Value::StructDefinition(_) => write!(f, "(struct definition)"), } } } diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index 216ed5fc545..b474ccff0cc 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -5,7 +5,7 @@ use crate::graph::CrateId; use crate::hir::comptime::{Interpreter, InterpreterError}; use crate::hir::def_map::{CrateDefMap, LocalModuleId, ModuleId}; use crate::hir::resolution::errors::ResolverError; -use crate::{Type, TypeVariable}; +use crate::{ResolvedGeneric, Type}; use crate::hir::resolution::import::{resolve_import, ImportDirective, PathResolution}; use crate::hir::resolution::{ @@ -20,20 +20,23 @@ use crate::hir::Context; use crate::macros_api::{MacroError, MacroProcessor}; use crate::node_interner::{ - FuncId, GlobalId, NodeInterner, StructId, TraitId, TraitImplId, TypeAliasId, + FuncId, GlobalId, NodeInterner, ReferenceId, StructId, TraitId, TraitImplId, TypeAliasId, }; use crate::ast::{ ExpressionKind, Ident, LetStatement, Literal, NoirFunction, NoirStruct, NoirTrait, NoirTypeAlias, Path, PathKind, UnresolvedGenerics, UnresolvedTraitConstraint, UnresolvedType, }; + use crate::parser::{ParserError, SortedModule}; +use noirc_errors::{CustomDiagnostic, Location, Span}; + use fm::FileId; use iter_extended::vecmap; -use noirc_errors::{CustomDiagnostic, Span}; -use std::collections::{BTreeMap, HashMap}; - -use std::rc::Rc; +use rustc_hash::FxHashMap as HashMap; +use std::collections::BTreeMap; +use std::fmt::Write; +use std::path::PathBuf; use std::vec; #[derive(Default)] @@ -41,6 +44,7 @@ pub struct ResolvedModule { pub globals: Vec<(FileId, GlobalId)>, pub functions: Vec<(FileId, FuncId)>, pub trait_impl_functions: Vec<(FileId, FuncId)>, + pub debug_comptime_in_file: Option, pub errors: Vec<(CompilationError, FileId)>, } @@ -125,7 +129,7 @@ pub struct UnresolvedTraitImpl { pub trait_id: Option, pub impl_id: Option, pub resolved_object_type: Option, - pub resolved_generics: Vec<(Rc, TypeVariable, Span)>, + pub resolved_generics: Vec, // The resolved generic on the trait itself. E.g. it is the `` in // `impl Foo for Bar { ... }` @@ -154,6 +158,7 @@ pub struct DefCollector { pub(crate) items: CollectedItems, } +#[derive(Default)] pub struct CollectedItems { pub(crate) functions: Vec, pub(crate) types: BTreeMap, @@ -164,6 +169,18 @@ pub struct CollectedItems { pub(crate) trait_impls: Vec, } +impl CollectedItems { + pub fn is_empty(&self) -> bool { + self.functions.is_empty() + && self.types.is_empty() + && self.type_aliases.is_empty() + && self.traits.is_empty() + && self.globals.is_empty() + && self.impls.is_empty() + && self.trait_impls.is_empty() + } +} + /// Maps the type and the module id in which the impl is defined to the functions contained in that /// impl along with the generics declared on the impl itself. This also contains the Span /// of the object_type of the impl, used to issue an error if the object type fails to resolve. @@ -181,6 +198,7 @@ pub enum CompilationError { ResolverError(ResolverError), TypeError(TypeCheckError), InterpreterError(InterpreterError), + DebugComptimeScopeNotFound(Vec), } impl CompilationError { @@ -198,6 +216,16 @@ impl<'a> From<&'a CompilationError> for CustomDiagnostic { CompilationError::ResolverError(error) => error.into(), CompilationError::TypeError(error) => error.into(), CompilationError::InterpreterError(error) => error.into(), + CompilationError::DebugComptimeScopeNotFound(error) => { + let msg = "multiple files found matching --debug-comptime path".into(); + let secondary = error.iter().fold(String::new(), |mut output, path| { + let _ = writeln!(output, " {}", path.display()); + output + }); + // NOTE: this span is empty as it is not expected to be displayed + let dummy_span = Span::default(); + CustomDiagnostic::simple_error(msg, secondary, dummy_span) + } } } } @@ -241,7 +269,7 @@ impl DefCollector { types: BTreeMap::new(), type_aliases: BTreeMap::new(), traits: BTreeMap::new(), - impls: HashMap::new(), + impls: HashMap::default(), globals: vec![], trait_impls: vec![], }, @@ -257,6 +285,7 @@ impl DefCollector { ast: SortedModule, root_file_id: FileId, use_legacy: bool, + debug_comptime_in_file: Option<&str>, macro_processors: &[&dyn MacroProcessor], ) -> Vec<(CompilationError, FileId)> { let mut errors: Vec<(CompilationError, FileId)> = vec![]; @@ -274,6 +303,7 @@ impl DefCollector { dep.crate_id, context, use_legacy, + debug_comptime_in_file, macro_processors, )); @@ -315,7 +345,30 @@ impl DefCollector { // Resolve unresolved imports collected from the crate, one by one. for collected_import in std::mem::take(&mut def_collector.imports) { - match resolve_import(crate_id, &collected_import, &context.def_maps) { + let module_id = collected_import.module_id; + let resolved_import = if context.def_interner.track_references { + let mut references: Vec = Vec::new(); + let resolved_import = resolve_import( + crate_id, + &collected_import, + &context.def_maps, + &mut Some(&mut references), + ); + + let current_def_map = context.def_maps.get(&crate_id).unwrap(); + let file_id = current_def_map.file_id(module_id); + + for (referenced, ident) in references.iter().zip(&collected_import.path.segments) { + let reference = + ReferenceId::Reference(Location::new(ident.span(), file_id), false); + context.def_interner.add_reference(*referenced, reference); + } + + resolved_import + } else { + resolve_import(crate_id, &collected_import, &context.def_maps, &mut None) + }; + match resolved_import { Ok(resolved_import) => { if let Some(error) = resolved_import.error { errors.push(( @@ -332,6 +385,14 @@ impl DefCollector { let result = current_def_map.modules[resolved_import.module_scope.0] .import(name.clone(), ns, resolved_import.is_prelude); + let file_id = current_def_map.file_id(module_id); + let last_segment = collected_import.path.last_segment(); + + add_import_reference(ns, &last_segment, &mut context.def_interner, file_id); + if let Some(ref alias) = collected_import.alias { + add_import_reference(ns, alias, &mut context.def_interner, file_id); + } + if let Err((first_def, second_def)) = result { let err = DefCollectorErrorKind::Duplicate { typ: DuplicateType::Import, @@ -351,13 +412,31 @@ impl DefCollector { } } + let handle_missing_file = |err| { + errors.push((CompilationError::DebugComptimeScopeNotFound(err), root_file_id)); + None + }; + let debug_comptime_in_file: Option = + debug_comptime_in_file.and_then(|debug_comptime_in_file| { + context + .file_manager + .find_by_path_suffix(debug_comptime_in_file) + .unwrap_or_else(handle_missing_file) + }); + if !use_legacy { - let mut more_errors = Elaborator::elaborate(context, crate_id, def_collector.items); + let mut more_errors = Elaborator::elaborate( + context, + crate_id, + def_collector.items, + debug_comptime_in_file, + ); errors.append(&mut more_errors); return errors; } - let mut resolved_module = ResolvedModule { errors, ..Default::default() }; + let mut resolved_module = + ResolvedModule { errors, debug_comptime_in_file, ..Default::default() }; // We must first resolve and intern the globals before we can resolve any stmts inside each function. // Each function uses its own resolver with a newly created ScopeForest, and must be resolved again to be within a function's scope @@ -379,6 +458,7 @@ impl DefCollector { def_collector.items.traits, crate_id, )); + // Must resolve structs before we resolve globals. resolved_module.errors.extend(resolve_structs( context, @@ -447,13 +527,38 @@ impl DefCollector { resolved_module.type_check(context); if !cycles_present { - resolved_module.evaluate_comptime(&mut context.def_interner); + resolved_module.evaluate_comptime(&mut context.def_interner, crate_id); } resolved_module.errors } } +fn add_import_reference( + def_id: crate::macros_api::ModuleDefId, + name: &Ident, + interner: &mut NodeInterner, + file_id: FileId, +) { + if name.span() == Span::empty(0) { + // We ignore empty spans at 0 location, this must be Stdlib + return; + } + + let referenced = match def_id { + crate::macros_api::ModuleDefId::ModuleId(module_id) => ReferenceId::Module(module_id), + crate::macros_api::ModuleDefId::FunctionId(func_id) => ReferenceId::Function(func_id), + crate::macros_api::ModuleDefId::TypeId(struct_id) => ReferenceId::Struct(struct_id), + crate::macros_api::ModuleDefId::TraitId(trait_id) => ReferenceId::Trait(trait_id), + crate::macros_api::ModuleDefId::TypeAliasId(type_alias_id) => { + ReferenceId::Alias(type_alias_id) + } + crate::macros_api::ModuleDefId::GlobalId(global_id) => ReferenceId::Global(global_id), + }; + let reference = ReferenceId::Reference(Location::new(name.span(), file_id), false); + interner.add_reference(referenced, reference); +} + fn inject_prelude( crate_id: CrateId, context: &Context, @@ -476,6 +581,7 @@ fn inject_prelude( &context.def_maps, ModuleId { krate: crate_id, local_id: crate_root }, path, + &mut None, ) { assert!(error.is_none(), "Tried to add private item to prelude"); let module_id = module_def_id.as_module().expect("std::prelude should be a module"); @@ -546,10 +652,17 @@ impl ResolvedModule { } /// Evaluate all `comptime` expressions in this module - fn evaluate_comptime(&mut self, interner: &mut NodeInterner) { + fn evaluate_comptime(&mut self, interner: &mut NodeInterner, crate_id: CrateId) { if self.count_errors() == 0 { let mut scopes = vec![HashMap::default()]; - let mut interpreter = Interpreter::new(interner, &mut scopes); + let mut interpreter_errors = vec![]; + let mut interpreter = Interpreter::new( + interner, + &mut scopes, + crate_id, + self.debug_comptime_in_file, + &mut interpreter_errors, + ); for (_file, global) in &self.globals { if let Err(error) = interpreter.scan_global(*global) { @@ -564,6 +677,9 @@ impl ResolvedModule { self.errors.push(error.into_compilation_error_pair()); } } + self.errors.extend( + interpreter_errors.into_iter().map(InterpreterError::into_compilation_error_pair), + ); } } diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs index afa6711a768..f8254295389 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs @@ -1,16 +1,20 @@ -use std::{collections::HashMap, path::Path, vec}; +use std::path::Path; +use std::vec; use acvm::{AcirField, FieldElement}; use fm::{FileId, FileManager, FILE_EXTENSION}; -use noirc_errors::Location; +use noirc_errors::{Location, Span}; use num_bigint::BigUint; use num_traits::Num; +use rustc_hash::FxHashMap as HashMap; use crate::ast::{ FunctionDefinition, Ident, ItemVisibility, LetStatement, ModuleDeclaration, NoirFunction, NoirStruct, NoirTrait, NoirTraitImpl, NoirTypeAlias, Pattern, TraitImplItem, TraitItem, TypeImpl, }; +use crate::macros_api::NodeInterner; +use crate::node_interner::ReferenceId; use crate::{ graph::CrateId, hir::def_collector::dc_crate::{UnresolvedStruct, UnresolvedTrait}, @@ -26,7 +30,7 @@ use super::{ }, errors::{DefCollectorErrorKind, DuplicateType}, }; -use crate::hir::def_map::{LocalModuleId, ModuleData, ModuleId}; +use crate::hir::def_map::{CrateDefMap, LocalModuleId, ModuleData, ModuleId}; use crate::hir::resolution::import::ImportDirective; use crate::hir::Context; @@ -105,35 +109,19 @@ impl<'a> ModCollector<'a> { ) -> Vec<(CompilationError, fm::FileId)> { let mut errors = vec![]; for global in globals { - let name = global.pattern.name_ident().clone(); - - let global_id = context.def_interner.push_empty_global( - name.clone(), - self.module_id, + let (global, error) = collect_global( + &mut context.def_interner, + &mut self.def_collector.def_map, + global, self.file_id, - global.attributes.clone(), - matches!(global.pattern, Pattern::Mutable { .. }), + self.module_id, ); - // 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] - .declare_global(name, global_id); - - if let Err((first_def, second_def)) = result { - let err = DefCollectorErrorKind::Duplicate { - typ: DuplicateType::Global, - first_def, - second_def, - }; - errors.push((err.into(), self.file_id)); + if let Some(error) = error { + errors.push(error); } - self.def_collector.items.globals.push(UnresolvedGlobal { - file_id: self.file_id, - module_id: self.module_id, - global_id, - stmt_def: global, - }); + self.def_collector.items.globals.push(global); } errors } @@ -149,8 +137,9 @@ impl<'a> ModCollector<'a> { self_type: None, }; - for (method, _) in r#impl.methods { + for (mut method, _) in r#impl.methods { let func_id = context.def_interner.push_empty_fn(); + method.def.where_clause.extend(r#impl.where_clause.clone()); let location = Location::new(method.span(), self.file_id); context.def_interner.push_function(func_id, &method.def, module_id, location); unresolved_functions.push_fn(self.module_id, func_id, method); @@ -168,11 +157,16 @@ impl<'a> ModCollector<'a> { impls: Vec, krate: CrateId, ) { - for trait_impl in impls { + for mut trait_impl in impls { let trait_name = trait_impl.trait_name.clone(); - let mut unresolved_functions = - self.collect_trait_impl_function_overrides(context, &trait_impl, krate); + let mut unresolved_functions = collect_trait_impl_functions( + &mut context.def_interner, + &mut trait_impl, + krate, + self.file_id, + self.module_id, + ); let module = ModuleId { krate, local_id: self.module_id }; @@ -204,33 +198,6 @@ impl<'a> ModCollector<'a> { } } - fn collect_trait_impl_function_overrides( - &mut self, - context: &mut Context, - trait_impl: &NoirTraitImpl, - krate: CrateId, - ) -> UnresolvedFunctions { - let mut unresolved_functions = UnresolvedFunctions { - file_id: self.file_id, - functions: Vec::new(), - trait_id: None, - self_type: None, - }; - - let module = ModuleId { krate, local_id: self.module_id }; - - for item in &trait_impl.items { - if let TraitImplItem::Function(impl_method) = item { - let func_id = context.def_interner.push_empty_fn(); - let location = Location::new(impl_method.span(), self.file_id); - context.def_interner.push_function(func_id, &impl_method.def, module, location); - unresolved_functions.push_fn(self.module_id, func_id, impl_method.clone()); - } - } - - unresolved_functions - } - fn collect_functions( &mut self, context: &mut Context, @@ -308,17 +275,28 @@ impl<'a> ModCollector<'a> { struct_def: struct_definition, }; + let resolved_generics = context.resolve_generics( + &unresolved.struct_def.generics, + &mut definition_errors, + self.file_id, + ); + // Create the corresponding module for the struct namespace let id = match self.push_child_module( + context, &name, ItemVisibility::Public, - self.file_id, + Location::new(name.span(), self.file_id), false, false, ) { - Ok(local_id) => { - context.def_interner.new_struct(&unresolved, krate, local_id, self.file_id) - } + Ok(module_id) => context.def_interner.new_struct( + &unresolved, + resolved_generics, + krate, + module_id.local_id, + self.file_id, + ), Err(error) => { definition_errors.push((error.into(), self.file_id)); continue; @@ -340,6 +318,8 @@ impl<'a> ModCollector<'a> { // And store the TypeId -> StructType mapping somewhere it is reachable self.def_collector.items.types.insert(id, unresolved); + + context.def_interner.add_definition_location(ReferenceId::Struct(id)); } definition_errors } @@ -362,7 +342,14 @@ impl<'a> ModCollector<'a> { type_alias_def: type_alias, }; - let type_alias_id = context.def_interner.push_type_alias(&unresolved); + let resolved_generics = context.resolve_generics( + &unresolved.type_alias_def.generics, + &mut errors, + self.file_id, + ); + + let type_alias_id = + context.def_interner.push_type_alias(&unresolved, resolved_generics); // Add the type alias to scope so its path can be looked up later let result = self.def_collector.def_map.modules[self.module_id.0] @@ -378,6 +365,8 @@ impl<'a> ModCollector<'a> { } self.def_collector.items.type_aliases.insert(type_alias_id, unresolved); + + context.def_interner.add_definition_location(ReferenceId::Alias(type_alias_id)); } errors } @@ -396,13 +385,14 @@ impl<'a> ModCollector<'a> { // Create the corresponding module for the trait namespace let trait_id = match self.push_child_module( + context, &name, ItemVisibility::Public, - self.file_id, + Location::new(name.span(), self.file_id), false, false, ) { - Ok(local_id) => TraitId(ModuleId { krate, local_id }), + Ok(module_id) => TraitId(ModuleId { krate, local_id: module_id.local_id }), Err(error) => { errors.push((error.into(), self.file_id)); continue; @@ -430,7 +420,7 @@ impl<'a> ModCollector<'a> { self_type: None, }; - let mut method_ids = HashMap::new(); + let mut method_ids = HashMap::default(); for trait_item in &trait_definition.items { match trait_item { TraitItem::Function { @@ -444,6 +434,7 @@ impl<'a> ModCollector<'a> { let func_id = context.def_interner.push_empty_fn(); method_ids.insert(name.to_string(), func_id); + let location = Location::new(name.span(), self.file_id); let modifiers = FunctionModifiers { name: name.to_string(), visibility: ItemVisibility::Public, @@ -452,9 +443,9 @@ impl<'a> ModCollector<'a> { is_unconstrained: false, generic_count: generics.len(), is_comptime: false, + name_location: location, }; - let location = Location::new(name.span(), self.file_id); context .def_interner .push_function_definition(func_id, modifiers, trait_id.0, location); @@ -497,6 +488,7 @@ impl<'a> ModCollector<'a> { self.file_id, vec![], false, + false, ); if let Err((first_def, second_def)) = self.def_collector.def_map.modules @@ -528,6 +520,9 @@ impl<'a> ModCollector<'a> { } } + let resolved_generics = + context.resolve_generics(&trait_definition.generics, &mut errors, self.file_id); + // And store the TraitId -> TraitType mapping somewhere it is reachable let unresolved = UnresolvedTrait { file_id: self.file_id, @@ -537,7 +532,10 @@ impl<'a> ModCollector<'a> { method_ids, fns_with_default_impl: unresolved_functions, }; - context.def_interner.push_empty_trait(trait_id, &unresolved); + context.def_interner.push_empty_trait(trait_id, &unresolved, resolved_generics); + + context.def_interner.add_definition_location(ReferenceId::Trait(trait_id)); + self.def_collector.items.traits.insert(trait_id, unresolved); } errors @@ -554,9 +552,10 @@ impl<'a> ModCollector<'a> { let mut errors: Vec<(CompilationError, FileId)> = vec![]; for submodule in submodules { match self.push_child_module( + context, &submodule.name, ItemVisibility::Public, - file_id, + Location::new(submodule.name.span(), file_id), true, submodule.is_contract, ) { @@ -565,7 +564,7 @@ impl<'a> ModCollector<'a> { self.def_collector, submodule.contents, file_id, - child, + child.local_id, crate_id, context, macro_processors, @@ -590,17 +589,14 @@ impl<'a> ModCollector<'a> { macro_processors: &[&dyn MacroProcessor], ) -> Vec<(CompilationError, FileId)> { let mut errors: Vec<(CompilationError, FileId)> = vec![]; - let child_file_id = - match find_module(&context.file_manager, self.file_id, &mod_decl.ident.0.contents) { - Ok(child_file_id) => child_file_id, - Err(expected_path) => { - let mod_name = mod_decl.ident.clone(); - let err = - DefCollectorErrorKind::UnresolvedModuleDecl { mod_name, expected_path }; - errors.push((err.into(), self.file_id)); - return errors; - } - }; + let child_file_id = match find_module(&context.file_manager, self.file_id, &mod_decl.ident) + { + Ok(child_file_id) => child_file_id, + Err(err) => { + errors.push((err.into(), self.file_id)); + return errors; + } + }; let location = Location { file: self.file_id, span: mod_decl.ident.span() }; @@ -648,18 +644,24 @@ impl<'a> ModCollector<'a> { // Add module into def collector and get a ModuleId match self.push_child_module( + context, &mod_decl.ident, - mod_decl.visibility, - child_file_id, + ItemVisibility::Public, + Location::new(Span::empty(0), child_file_id), true, false, ) { Ok(child_mod_id) => { + // Track that the "foo" in `mod foo;` points to the module "foo" + let referenced = ReferenceId::Module(child_mod_id); + let reference = ReferenceId::Reference(location, false); + context.def_interner.add_reference(referenced, reference); + errors.extend(collect_defs( self.def_collector, ast, child_file_id, - child_mod_id, + child_mod_id.local_id, crate_id, context, macro_processors, @@ -676,14 +678,23 @@ impl<'a> ModCollector<'a> { /// On error this returns None and pushes to `errors` fn push_child_module( &mut self, + context: &mut Context, mod_name: &Ident, visibility: ItemVisibility, - file_id: FileId, + mod_location: Location, add_to_parent_scope: bool, is_contract: bool, - ) -> Result { + ) -> Result { let parent = Some(self.module_id); - let location = Location::new(mod_name.span(), file_id); + + // Note: the difference between `location` and `mod_location` is: + // - `mod_location` will point to either the token "foo" in `mod foo { ... }` + // if it's an inline module, or the first char of a the file if it's an external module. + // - `location` will always point to the token "foo" in `mod foo` regardless of whether + // it's inline or external. + // Eventually the location put in `ModuleData` is used for codelenses about `contract`s, + // so we keep using `location` so that it continues to work as usual. + let location = Location::new(mod_name.span(), mod_location.file); let new_module = ModuleData::new(parent, location, is_contract); let module_id = self.def_collector.def_map.modules.insert(new_module); @@ -692,6 +703,11 @@ impl<'a> ModCollector<'a> { // Update the parent module to reference the child modules[self.module_id.0].children.insert(mod_name.clone(), LocalModuleId(module_id)); + let mod_id = ModuleId { + krate: self.def_collector.def_map.krate, + local_id: LocalModuleId(module_id), + }; + // Add this child module into the scope of the parent module as a module definition // module definitions are definitions which can only exist at the module level. // ModuleDefinitionIds can be used across crates since they contain the CrateId @@ -700,11 +716,6 @@ impl<'a> ModCollector<'a> { // to a child module containing its methods) since the module name should not shadow // the struct name. if add_to_parent_scope { - let mod_id = ModuleId { - krate: self.def_collector.def_map.krate, - local_id: LocalModuleId(module_id), - }; - if let Err((first_def, second_def)) = modules[self.module_id.0].declare_child_module( mod_name.to_owned(), visibility, @@ -717,35 +728,58 @@ impl<'a> ModCollector<'a> { }; return Err(err); } + + context.def_interner.add_module_location(mod_id, mod_location); } - Ok(LocalModuleId(module_id)) + Ok(mod_id) } } fn find_module( file_manager: &FileManager, anchor: FileId, - mod_name: &str, -) -> Result { + mod_name: &Ident, +) -> Result { let anchor_path = file_manager .path(anchor) .expect("File must exist in file manager in order for us to be resolving its imports.") .with_extension(""); let anchor_dir = anchor_path.parent().unwrap(); - // if `anchor` is a `main.nr`, `lib.nr`, `mod.nr` or `{mod_name}.nr`, we check siblings of - // the anchor at `base/mod_name.nr`. - let candidate = if should_check_siblings_for_module(&anchor_path, anchor_dir) { - anchor_dir.join(format!("{mod_name}.{FILE_EXTENSION}")) + // Assuming anchor is called "anchor.nr" and we are looking up a module named "mod_name"... + // This is "mod_name" + let mod_name_str = &mod_name.0.contents; + + // If we are in a special name like "main.nr", "lib.nr", "mod.nr" or "{mod_name}.nr", + // the search starts at the same directory, otherwise it starts in a nested directory. + let start_dir = if should_check_siblings_for_module(&anchor_path, anchor_dir) { + anchor_dir } else { - // Otherwise, we check for children of the anchor at `base/anchor/mod_name.nr` - anchor_path.join(format!("{mod_name}.{FILE_EXTENSION}")) + anchor_path.as_path() }; - file_manager - .name_to_id(candidate.clone()) - .ok_or_else(|| candidate.as_os_str().to_string_lossy().to_string()) + // Check "mod_name.nr" + let mod_name_candidate = start_dir.join(format!("{mod_name_str}.{FILE_EXTENSION}")); + let mod_name_result = file_manager.name_to_id(mod_name_candidate.clone()); + + // Check "mod_name/mod.nr" + let mod_nr_candidate = start_dir.join(mod_name_str).join(format!("mod.{FILE_EXTENSION}")); + let mod_nr_result = file_manager.name_to_id(mod_nr_candidate.clone()); + + match (mod_nr_result, mod_name_result) { + (Some(_), Some(_)) => Err(DefCollectorErrorKind::OverlappingModuleDecls { + mod_name: mod_name.clone(), + expected_path: mod_name_candidate.as_os_str().to_string_lossy().to_string(), + alternative_path: mod_nr_candidate.as_os_str().to_string_lossy().to_string(), + }), + (Some(id), None) | (None, Some(id)) => Ok(id), + (None, None) => Err(DefCollectorErrorKind::UnresolvedModuleDecl { + mod_name: mod_name.clone(), + expected_path: mod_name_candidate.as_os_str().to_string_lossy().to_string(), + alternative_path: mod_nr_candidate.as_os_str().to_string_lossy().to_string(), + }), + } } /// Returns true if a module's child modules are expected to be in the same directory. @@ -788,74 +822,242 @@ fn is_native_field(str: &str) -> bool { } } +pub(crate) fn collect_trait_impl_functions( + interner: &mut NodeInterner, + trait_impl: &mut NoirTraitImpl, + krate: CrateId, + file_id: FileId, + local_id: LocalModuleId, +) -> UnresolvedFunctions { + let mut unresolved_functions = + UnresolvedFunctions { file_id, functions: Vec::new(), trait_id: None, self_type: None }; + + let module = ModuleId { krate, local_id }; + + for item in std::mem::take(&mut trait_impl.items) { + if let TraitImplItem::Function(impl_method) = item { + let func_id = interner.push_empty_fn(); + let location = Location::new(impl_method.span(), file_id); + interner.push_function(func_id, &impl_method.def, module, location); + unresolved_functions.push_fn(local_id, func_id, impl_method); + } + } + + unresolved_functions +} + +pub(crate) fn collect_global( + interner: &mut NodeInterner, + def_map: &mut CrateDefMap, + global: LetStatement, + file_id: FileId, + module_id: LocalModuleId, +) -> (UnresolvedGlobal, Option<(CompilationError, FileId)>) { + let name = global.pattern.name_ident().clone(); + + let global_id = interner.push_empty_global( + name.clone(), + module_id, + file_id, + global.attributes.clone(), + matches!(global.pattern, Pattern::Mutable { .. }), + global.comptime, + ); + + // Add the statement to the scope so its path can be looked up later + let result = def_map.modules[module_id.0].declare_global(name, global_id); + + let error = result.err().map(|(first_def, second_def)| { + let err = + DefCollectorErrorKind::Duplicate { typ: DuplicateType::Global, first_def, second_def }; + (err.into(), file_id) + }); + + let global = UnresolvedGlobal { file_id, module_id, global_id, stmt_def: global }; + (global, error) +} + #[cfg(test)] -mod tests { +mod find_module_tests { use super::*; - use std::path::PathBuf; - use tempfile::{tempdir, TempDir}; + use noirc_errors::Spanned; + use std::path::{Path, PathBuf}; + + fn add_file(file_manager: &mut FileManager, dir: &Path, file_name: &str) -> FileId { + let mut target_filename = PathBuf::from(&dir); + for path in file_name.split('/') { + target_filename = target_filename.join(path); + } + + file_manager + .add_file_with_source(&target_filename, "fn foo() {}".to_string()) + .expect("could not add file to file manager and obtain a FileId") + } + + fn find_module( + file_manager: &FileManager, + anchor: FileId, + mod_name: &str, + ) -> Result { + let mod_name = Ident(Spanned::from_position(0, 1, mod_name.to_string())); + super::find_module(file_manager, anchor, &mod_name) + } + + #[test] + fn errors_if_cannot_find_file() { + let dir = PathBuf::new(); + let mut fm = FileManager::new(&PathBuf::new()); + + let file_id = add_file(&mut fm, &dir, "my_dummy_file.nr"); + + let result = find_module(&fm, file_id, "foo"); + assert!(matches!(result, Err(DefCollectorErrorKind::UnresolvedModuleDecl { .. }))); + } + + #[test] + fn errors_because_cannot_find_mod_relative_to_main() { + let dir = PathBuf::new(); + let mut fm = FileManager::new(&dir); + + let main_file_id = add_file(&mut fm, &dir, "main.nr"); + add_file(&mut fm, &dir, "main/foo.nr"); + + let result = find_module(&fm, main_file_id, "foo"); + assert!(matches!(result, Err(DefCollectorErrorKind::UnresolvedModuleDecl { .. }))); + } + + #[test] + fn errors_because_cannot_find_mod_relative_to_lib() { + let dir = PathBuf::new(); + let mut fm = FileManager::new(&dir); + + let lib_file_id = add_file(&mut fm, &dir, "lib.nr"); + add_file(&mut fm, &dir, "lib/foo.nr"); + + let result = find_module(&fm, lib_file_id, "foo"); + assert!(matches!(result, Err(DefCollectorErrorKind::UnresolvedModuleDecl { .. }))); + } + + #[test] + fn errors_because_cannot_find_sibling_mod_for_regular_name() { + let dir = PathBuf::new(); + let mut fm = FileManager::new(&dir); + + let foo_file_id = add_file(&mut fm, &dir, "foo.nr"); + add_file(&mut fm, &dir, "bar.nr"); - // Returns the absolute path to the file - fn create_dummy_file(dir: &TempDir, file_name: &Path) -> PathBuf { - let file_path = dir.path().join(file_name); - let _file = std::fs::File::create(&file_path).unwrap(); - file_path + let result = find_module(&fm, foo_file_id, "bar"); + assert!(matches!(result, Err(DefCollectorErrorKind::UnresolvedModuleDecl { .. }))); } #[test] - fn path_resolve_file_module() { - let dir = tempdir().unwrap(); + fn cannot_find_module_in_the_same_directory_for_regular_name() { + let dir = PathBuf::new(); + let mut fm = FileManager::new(&dir); + + let lib_file_id = add_file(&mut fm, &dir, "lib.nr"); + add_file(&mut fm, &dir, "bar.nr"); + add_file(&mut fm, &dir, "foo.nr"); + + // `mod bar` from `lib.nr` should find `bar.nr` + let bar_file_id = find_module(&fm, lib_file_id, "bar").unwrap(); + + // `mod foo` from `bar.nr` should fail to find `foo.nr` + let result = find_module(&fm, bar_file_id, "foo"); + assert!(matches!(result, Err(DefCollectorErrorKind::UnresolvedModuleDecl { .. }))); + } - let entry_file_name = Path::new("my_dummy_file.nr"); - create_dummy_file(&dir, entry_file_name); + #[test] + fn finds_module_in_sibling_dir_for_regular_name() { + let dir = PathBuf::new(); + let mut fm = FileManager::new(&dir); + + let sub_dir_file_id = add_file(&mut fm, &dir, "sub_dir.nr"); + add_file(&mut fm, &dir, "sub_dir/foo.nr"); + + // `mod foo` from `sub_dir.nr` should find `sub_dir/foo.nr` + find_module(&fm, sub_dir_file_id, "foo").unwrap(); + } - let mut fm = FileManager::new(dir.path()); + #[test] + fn finds_module_in_sibling_dir_mod_nr_for_regular_name() { + let dir = PathBuf::new(); + let mut fm = FileManager::new(&dir); - let file_id = fm.add_file_with_source(entry_file_name, "fn foo() {}".to_string()).unwrap(); + let sub_dir_file_id = add_file(&mut fm, &dir, "sub_dir.nr"); + add_file(&mut fm, &dir, "sub_dir/foo/mod.nr"); - let dep_file_name = Path::new("foo.nr"); - create_dummy_file(&dir, dep_file_name); - find_module(&fm, file_id, "foo").unwrap_err(); + // `mod foo` from `sub_dir.nr` should find `sub_dir/foo.nr` + find_module(&fm, sub_dir_file_id, "foo").unwrap(); } #[test] - fn path_resolve_sub_module() { - let dir = tempdir().unwrap(); - let mut fm = FileManager::new(dir.path()); - - // Create a lib.nr file at the root. - // we now have dir/lib.nr - let lib_nr_path = create_dummy_file(&dir, Path::new("lib.nr")); - let file_id = fm - .add_file_with_source(lib_nr_path.as_path(), "fn foo() {}".to_string()) - .expect("could not add file to file manager and obtain a FileId"); - - // Create a sub directory - // we now have: - // - dir/lib.nr - // - dir/sub_dir - let sub_dir = TempDir::new_in(&dir).unwrap(); - let sub_dir_name = sub_dir.path().file_name().unwrap().to_str().unwrap(); - - // Add foo.nr to the subdirectory - // we no have: - // - dir/lib.nr - // - dir/sub_dir/foo.nr - let foo_nr_path = create_dummy_file(&sub_dir, Path::new("foo.nr")); - fm.add_file_with_source(foo_nr_path.as_path(), "fn foo() {}".to_string()); - - // Add a parent module for the sub_dir - // we no have: - // - dir/lib.nr - // - dir/sub_dir.nr - // - dir/sub_dir/foo.nr - let sub_dir_nr_path = create_dummy_file(&dir, Path::new(&format!("{sub_dir_name}.nr"))); - fm.add_file_with_source(sub_dir_nr_path.as_path(), "fn foo() {}".to_string()); - - // First check for the sub_dir.nr file and add it to the FileManager - let sub_dir_file_id = find_module(&fm, file_id, sub_dir_name).unwrap(); - - // Now check for files in it's subdirectory + fn finds_module_in_sibling_dir_for_special_name() { + let dir = PathBuf::new(); + let mut fm = FileManager::new(&dir); + + let lib_file_id = add_file(&mut fm, &dir, "lib.nr"); + add_file(&mut fm, &dir, "sub_dir.nr"); + add_file(&mut fm, &dir, "sub_dir/foo.nr"); + + // `mod sub_dir` from `lib.nr` should find `sub_dir.nr` + let sub_dir_file_id = find_module(&fm, lib_file_id, "sub_dir").unwrap(); + + // `mod foo` from `sub_dir.nr` should find `sub_dir/foo.nr` find_module(&fm, sub_dir_file_id, "foo").unwrap(); } + + #[test] + fn finds_mod_dot_nr_for_special_name() { + let dir = PathBuf::new(); + let mut fm = FileManager::new(&dir); + + let lib_file_id = add_file(&mut fm, &dir, "lib.nr"); + add_file(&mut fm, &dir, "foo/mod.nr"); + + // Check that searching "foo" finds the mod.nr file + find_module(&fm, lib_file_id, "foo").unwrap(); + } + + #[test] + fn errors_mod_dot_nr_in_same_directory() { + let dir = PathBuf::new(); + let mut fm = FileManager::new(&dir); + + let lib_file_id = add_file(&mut fm, &dir, "lib.nr"); + add_file(&mut fm, &dir, "mod.nr"); + + // Check that searching "foo" does not pick up the mod.nr file + let result = find_module(&fm, lib_file_id, "foo"); + assert!(matches!(result, Err(DefCollectorErrorKind::UnresolvedModuleDecl { .. }))); + } + + #[test] + fn errors_if_file_exists_at_both_potential_module_locations_for_regular_name() { + let dir = PathBuf::new(); + let mut fm = FileManager::new(&dir); + + let foo_file_id = add_file(&mut fm, &dir, "foo.nr"); + add_file(&mut fm, &dir, "foo/bar.nr"); + add_file(&mut fm, &dir, "foo/bar/mod.nr"); + + // Check that `mod bar` from `foo` gives an error + let result = find_module(&fm, foo_file_id, "bar"); + assert!(matches!(result, Err(DefCollectorErrorKind::OverlappingModuleDecls { .. }))); + } + + #[test] + fn errors_if_file_exists_at_both_potential_module_locations_for_special_name() { + let dir = PathBuf::new(); + let mut fm = FileManager::new(&dir); + + let lib_file_id = add_file(&mut fm, &dir, "lib.nr"); + add_file(&mut fm, &dir, "foo.nr"); + add_file(&mut fm, &dir, "foo/mod.nr"); + + // Check that searching "foo" gives an error + let result = find_module(&fm, lib_file_id, "foo"); + assert!(matches!(result, Err(DefCollectorErrorKind::OverlappingModuleDecls { .. }))); + } } diff --git a/compiler/noirc_frontend/src/hir/def_collector/errors.rs b/compiler/noirc_frontend/src/hir/def_collector/errors.rs index edeb463e10d..37c5a460667 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/errors.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/errors.rs @@ -1,4 +1,4 @@ -use crate::ast::{Ident, Path}; +use crate::ast::{Ident, Path, UnresolvedTypeData}; use crate::hir::resolution::import::PathResolutionError; use noirc_errors::CustomDiagnostic as Diagnostic; @@ -27,7 +27,9 @@ pub enum DefCollectorErrorKind { #[error("duplicate {typ} found in namespace")] Duplicate { typ: DuplicateType, first_def: Ident, second_def: Ident }, #[error("unresolved import")] - UnresolvedModuleDecl { mod_name: Ident, expected_path: String }, + UnresolvedModuleDecl { mod_name: Ident, expected_path: String, alternative_path: String }, + #[error("overlapping imports")] + OverlappingModuleDecls { mod_name: Ident, expected_path: String, alternative_path: String }, #[error("path resolution error")] PathResolutionError(PathResolutionError), #[error("Non-struct type used in impl")] @@ -66,6 +68,8 @@ pub enum DefCollectorErrorKind { TraitImplOrphaned { span: Span }, #[error("macro error : {0:?}")] MacroError(MacroError), + #[error("The only supported types of numeric generics are integers, fields, and booleans")] + UnsupportedNumericGenericType { ident: Ident, typ: UnresolvedTypeData }, } /// An error struct that macro processors can return. @@ -119,12 +123,22 @@ impl<'a> From<&'a DefCollectorErrorKind> for Diagnostic { diag } } - DefCollectorErrorKind::UnresolvedModuleDecl { mod_name, expected_path } => { + DefCollectorErrorKind::UnresolvedModuleDecl { mod_name, expected_path, alternative_path } => { let span = mod_name.0.span(); let mod_name = &mod_name.0.contents; Diagnostic::simple_error( - format!("No module `{mod_name}` at path `{expected_path}`"), + format!("No module `{mod_name}` at path `{expected_path}` or `{alternative_path}`"), + String::new(), + span, + ) + } + DefCollectorErrorKind::OverlappingModuleDecls { mod_name, expected_path, alternative_path } => { + let span = mod_name.0.span(); + let mod_name = &mod_name.0.contents; + + Diagnostic::simple_error( + format!("Overlapping modules `{mod_name}` at path `{expected_path}` and `{alternative_path}`"), String::new(), span, ) @@ -228,6 +242,15 @@ impl<'a> From<&'a DefCollectorErrorKind> for Diagnostic { DefCollectorErrorKind::MacroError(macro_error) => { Diagnostic::simple_error(macro_error.primary_message.clone(), macro_error.secondary_message.clone().unwrap_or_default(), macro_error.span.unwrap_or_default()) }, + DefCollectorErrorKind::UnsupportedNumericGenericType { ident, typ } => { + let name = &ident.0.contents; + + Diagnostic::simple_error( + format!("{name} has a type of {typ}. The only supported types of numeric generics are integers and fields"), + "Unsupported numeric generic type".to_string(), + ident.0.span(), + ) + } } } } diff --git a/compiler/noirc_frontend/src/hir/def_map/mod.rs b/compiler/noirc_frontend/src/hir/def_map/mod.rs index 59205f74d89..43d1548dc29 100644 --- a/compiler/noirc_frontend/src/hir/def_map/mod.rs +++ b/compiler/noirc_frontend/src/hir/def_map/mod.rs @@ -74,6 +74,7 @@ impl CrateDefMap { crate_id: CrateId, context: &mut Context, use_legacy: bool, + debug_comptime_in_file: Option<&str>, macro_processors: &[&dyn MacroProcessor], ) -> Vec<(CompilationError, FileId)> { // Check if this Crate has already been compiled @@ -123,6 +124,7 @@ impl CrateDefMap { ast, root_file_id, use_legacy, + debug_comptime_in_file, macro_processors, )); diff --git a/compiler/noirc_frontend/src/hir/mod.rs b/compiler/noirc_frontend/src/hir/mod.rs index 55dc22d6c5d..87c4133d68e 100644 --- a/compiler/noirc_frontend/src/hir/mod.rs +++ b/compiler/noirc_frontend/src/hir/mod.rs @@ -5,17 +5,21 @@ pub mod resolution; pub mod scope; pub mod type_check; +use crate::ast::UnresolvedGenerics; use crate::debug::DebugInstrumenter; use crate::graph::{CrateGraph, CrateId}; use crate::hir_def::function::FuncMeta; use crate::node_interner::{FuncId, NodeInterner, StructId}; use crate::parser::ParserError; -use crate::ParsedModule; +use crate::{Generics, Kind, ParsedModule, ResolvedGeneric, Type, TypeVariable}; +use def_collector::dc_crate::CompilationError; use def_map::{Contract, CrateDefMap}; -use fm::FileManager; +use fm::{FileId, FileManager}; +use iter_extended::vecmap; use noirc_errors::Location; use std::borrow::Cow; use std::collections::{BTreeMap, HashMap}; +use std::rc::Rc; use self::def_map::TestFunction; @@ -80,7 +84,7 @@ impl Context<'_, '_> { } } - pub fn parsed_file_results(&self, file_id: fm::FileId) -> (ParsedModule, Vec) { + pub fn parsed_file_results(&self, file_id: FileId) -> (ParsedModule, Vec) { self.parsed_files.get(&file_id).expect("noir file wasn't parsed").clone() } @@ -256,4 +260,39 @@ impl Context<'_, '_> { pub fn module(&self, module_id: def_map::ModuleId) -> &def_map::ModuleData { module_id.module(&self.def_maps) } + + /// Generics need to be resolved before elaboration to distinguish + /// between normal and numeric generics. + /// This method is expected to be used during definition collection. + /// Each result is returned in a list rather than returned as a single result as to allow + /// definition collection to provide an error for each ill-formed numeric generic. + pub(crate) fn resolve_generics( + &mut self, + generics: &UnresolvedGenerics, + errors: &mut Vec<(CompilationError, FileId)>, + file_id: FileId, + ) -> Generics { + vecmap(generics, |generic| { + // Map the generic to a fresh type variable + let id = self.def_interner.next_type_variable_id(); + let type_var = TypeVariable::unbound(id); + let ident = generic.ident(); + let span = ident.0.span(); + + // Check for name collisions of this generic + let name = Rc::new(ident.0.contents.clone()); + + let kind = generic.kind().unwrap_or_else(|err| { + errors.push((err.into(), file_id)); + Kind::Numeric(Box::new(Type::Error)) + }); + + ResolvedGeneric { name, type_var, kind, span } + }) + } + + // Enables reference tracking (useful for tools like LSP). + pub fn track_references(&mut self) { + self.def_interner.track_references = true; + } } diff --git a/compiler/noirc_frontend/src/hir/resolution/errors.rs b/compiler/noirc_frontend/src/hir/resolution/errors.rs index dad0149ec73..bf6de746791 100644 --- a/compiler/noirc_frontend/src/hir/resolution/errors.rs +++ b/compiler/noirc_frontend/src/hir/resolution/errors.rs @@ -98,6 +98,12 @@ pub enum ResolverError { NoPredicatesAttributeOnUnconstrained { ident: Ident }, #[error("#[fold] attribute is only allowed on constrained functions")] FoldAttributeOnUnconstrained { ident: Ident }, + #[error("The only supported types of numeric generics are integers, fields, and booleans")] + UnsupportedNumericGenericType { ident: Ident, typ: Type }, + #[error("Numeric generics should be explicit")] + UseExplicitNumericGeneric { ident: Ident }, + #[error("expected type, found numeric generic parameter")] + NumericGenericUsedForType { name: String, span: Span }, #[error("Invalid array length construction")] ArrayLengthInterpreter { error: InterpreterError }, #[error("The unquote operator '$' can only be used within a quote expression")] @@ -400,6 +406,31 @@ impl<'a> From<&'a ResolverError> for Diagnostic { diag.add_note("The `#[fold]` attribute specifies whether a constrained function should be treated as a separate circuit rather than inlined into the program entry point".to_owned()); diag } + ResolverError::UnsupportedNumericGenericType { ident , typ } => { + let name = &ident.0.contents; + + Diagnostic::simple_error( + format!("{name} has a type of {typ}. The only supported types of numeric generics are integers, fields, and booleans."), + "Unsupported numeric generic type".to_string(), + ident.0.span(), + ) + } + ResolverError::UseExplicitNumericGeneric { ident } => { + let name = &ident.0.contents; + + Diagnostic::simple_warning( + String::from("Noir now supports explicit numeric generics. Support for implicit numeric generics will be removed in the following release."), + format!("Numeric generic `{name}` should now be specified with `let {name}: `"), + ident.0.span(), + ) + } + ResolverError::NumericGenericUsedForType { name, span } => { + Diagnostic::simple_error( + format!("expected type, found numeric generic parameter {name}"), + String::from("not a type"), + *span, + ) + } ResolverError::ArrayLengthInterpreter { error } => Diagnostic::from(error), ResolverError::UnquoteUsedOutsideQuote { span } => { Diagnostic::simple_error( diff --git a/compiler/noirc_frontend/src/hir/resolution/functions.rs b/compiler/noirc_frontend/src/hir/resolution/functions.rs index e63de9b9173..fe46796ed24 100644 --- a/compiler/noirc_frontend/src/hir/resolution/functions.rs +++ b/compiler/noirc_frontend/src/hir/resolution/functions.rs @@ -1,8 +1,7 @@ -use std::{collections::BTreeMap, rc::Rc}; +use std::collections::BTreeMap; use fm::FileId; use iter_extended::vecmap; -use noirc_errors::Span; use crate::{ graph::CrateId, @@ -11,10 +10,10 @@ use crate::{ def_map::{CrateDefMap, ModuleId}, }, node_interner::{FuncId, NodeInterner, TraitImplId}, - Type, TypeVariable, + ResolvedGeneric, Type, }; -use super::{path_resolver::StandardPathResolver, resolver::Resolver}; +use super::{path_resolver::StandardPathResolver, Resolver}; #[allow(clippy::too_many_arguments)] pub(crate) fn resolve_function_set( @@ -24,7 +23,7 @@ pub(crate) fn resolve_function_set( mut unresolved_functions: UnresolvedFunctions, self_type: Option, trait_impl_id: Option, - impl_generics: Vec<(Rc, TypeVariable, Span)>, + impl_generics: Vec, errors: &mut Vec<(CompilationError, FileId)>, ) -> Vec<(FileId, FuncId)> { let file_id = unresolved_functions.file_id; diff --git a/compiler/noirc_frontend/src/hir/resolution/import.rs b/compiler/noirc_frontend/src/hir/resolution/import.rs index 48641bda0da..7dea08e1a0e 100644 --- a/compiler/noirc_frontend/src/hir/resolution/import.rs +++ b/compiler/noirc_frontend/src/hir/resolution/import.rs @@ -3,6 +3,7 @@ use thiserror::Error; use crate::graph::CrateId; use crate::hir::def_collector::dc_crate::CompilationError; +use crate::node_interner::ReferenceId; use std::collections::BTreeMap; use crate::ast::{Ident, ItemVisibility, Path, PathKind}; @@ -38,8 +39,6 @@ pub(crate) type PathResolutionResult = Result From<&'a PathResolutionError> for CustomDiagnostic { PathResolutionError::Unresolved(ident) => { CustomDiagnostic::simple_error(error.to_string(), String::new(), ident.span()) } - PathResolutionError::ExternalContractUsed(ident) => CustomDiagnostic::simple_error( - error.to_string(), - "Contracts may only be referenced from within a contract".to_string(), - ident.span(), - ), // This will be upgraded to an error in future versions PathResolutionError::Private(ident) => CustomDiagnostic::simple_warning( error.to_string(), @@ -87,16 +81,14 @@ pub fn resolve_import( crate_id: CrateId, import_directive: &ImportDirective, def_maps: &BTreeMap, + path_references: &mut Option<&mut Vec>, ) -> Result { - let allow_contracts = - allow_referencing_contracts(def_maps, crate_id, import_directive.module_id); - let module_scope = import_directive.module_id; let NamespaceResolution { module_id: resolved_module, namespace: resolved_namespace, mut error, - } = resolve_path_to_ns(import_directive, crate_id, crate_id, def_maps, allow_contracts)?; + } = resolve_path_to_ns(import_directive, crate_id, crate_id, def_maps, path_references)?; let name = resolve_path_name(import_directive); @@ -129,20 +121,12 @@ pub fn resolve_import( }) } -fn allow_referencing_contracts( - def_maps: &BTreeMap, - krate: CrateId, - local_id: LocalModuleId, -) -> bool { - ModuleId { krate, local_id }.module(def_maps).is_contract -} - fn resolve_path_to_ns( import_directive: &ImportDirective, crate_id: CrateId, importing_crate: CrateId, def_maps: &BTreeMap, - allow_contracts: bool, + path_references: &mut Option<&mut Vec>, ) -> NamespaceResolutionResult { let import_path = &import_directive.path.segments; let def_map = &def_maps[&crate_id]; @@ -155,7 +139,7 @@ fn resolve_path_to_ns( importing_crate, import_path, def_maps, - allow_contracts, + path_references, ) } crate::ast::PathKind::Plain => { @@ -168,7 +152,7 @@ fn resolve_path_to_ns( import_path, import_directive.module_id, def_maps, - allow_contracts, + path_references, ); } @@ -181,7 +165,7 @@ fn resolve_path_to_ns( def_map, import_directive, def_maps, - allow_contracts, + path_references, importing_crate, ); } @@ -192,7 +176,7 @@ fn resolve_path_to_ns( import_path, import_directive.module_id, def_maps, - allow_contracts, + path_references, ) } @@ -200,7 +184,7 @@ fn resolve_path_to_ns( def_map, import_directive, def_maps, - allow_contracts, + path_references, importing_crate, ), } @@ -212,7 +196,7 @@ fn resolve_path_from_crate_root( import_path: &[Ident], def_maps: &BTreeMap, - allow_contracts: bool, + path_references: &mut Option<&mut Vec>, ) -> NamespaceResolutionResult { resolve_name_in_module( crate_id, @@ -220,7 +204,7 @@ fn resolve_path_from_crate_root( import_path, def_maps[&crate_id].root, def_maps, - allow_contracts, + path_references, ) } @@ -230,7 +214,7 @@ fn resolve_name_in_module( import_path: &[Ident], starting_mod: LocalModuleId, def_maps: &BTreeMap, - allow_contracts: bool, + path_references: &mut Option<&mut Vec>, ) -> NamespaceResolutionResult { let def_map = &def_maps[&krate]; let mut current_mod_id = ModuleId { krate, local_id: starting_mod }; @@ -261,12 +245,27 @@ fn resolve_name_in_module( // In the type namespace, only Mod can be used in a path. current_mod_id = match typ { - ModuleDefId::ModuleId(id) => id, + ModuleDefId::ModuleId(id) => { + if let Some(path_references) = path_references { + path_references.push(ReferenceId::Module(id)); + } + id + } ModuleDefId::FunctionId(_) => panic!("functions cannot be in the type namespace"), // TODO: If impls are ever implemented, types can be used in a path - ModuleDefId::TypeId(id) => id.module_id(), + ModuleDefId::TypeId(id) => { + if let Some(path_references) = path_references { + path_references.push(ReferenceId::Struct(id)); + } + id.module_id() + } ModuleDefId::TypeAliasId(_) => panic!("type aliases cannot be used in type namespace"), - ModuleDefId::TraitId(id) => id.0, + ModuleDefId::TraitId(id) => { + if let Some(path_references) = path_references { + path_references.push(ReferenceId::Trait(id)); + } + id.0 + } ModuleDefId::GlobalId(_) => panic!("globals cannot be in the type namespace"), }; @@ -293,10 +292,6 @@ fn resolve_name_in_module( return Err(PathResolutionError::Unresolved(current_segment.clone())); } - // Check if it is a contract and we're calling from a non-contract context - if current_mod.is_contract && !allow_contracts { - return Err(PathResolutionError::ExternalContractUsed(current_segment.clone())); - } current_ns = found_ns; } @@ -314,7 +309,7 @@ fn resolve_external_dep( current_def_map: &CrateDefMap, directive: &ImportDirective, def_maps: &BTreeMap, - allow_contracts: bool, + path_references: &mut Option<&mut Vec>, importing_crate: CrateId, ) -> NamespaceResolutionResult { // Use extern_prelude to get the dep @@ -344,7 +339,7 @@ fn resolve_external_dep( is_prelude: false, }; - resolve_path_to_ns(&dep_directive, dep_module.krate, importing_crate, def_maps, allow_contracts) + resolve_path_to_ns(&dep_directive, dep_module.krate, importing_crate, def_maps, path_references) } // Issue an error if the given private function is being called from a non-child module, or diff --git a/compiler/noirc_frontend/src/hir/resolution/path_resolver.rs b/compiler/noirc_frontend/src/hir/resolution/path_resolver.rs index e423e10b712..c3dc76b635f 100644 --- a/compiler/noirc_frontend/src/hir/resolution/path_resolver.rs +++ b/compiler/noirc_frontend/src/hir/resolution/path_resolver.rs @@ -1,5 +1,6 @@ use super::import::{resolve_import, ImportDirective, PathResolution, PathResolutionResult}; use crate::ast::Path; +use crate::node_interner::ReferenceId; use std::collections::BTreeMap; use crate::graph::CrateId; @@ -7,10 +8,13 @@ use crate::hir::def_map::{CrateDefMap, LocalModuleId, ModuleId}; pub trait PathResolver { /// Resolve the given path returning the resolved ModuleDefId. + /// If `path_references` is `Some`, a `ReferenceId` for each segment in `path` + /// will be resolved and pushed. fn resolve( &self, def_maps: &BTreeMap, path: Path, + path_references: &mut Option<&mut Vec>, ) -> PathResolutionResult; fn local_module_id(&self) -> LocalModuleId; @@ -34,8 +38,9 @@ impl PathResolver for StandardPathResolver { &self, def_maps: &BTreeMap, path: Path, + path_references: &mut Option<&mut Vec>, ) -> PathResolutionResult { - resolve_path(def_maps, self.module_id, path) + resolve_path(def_maps, self.module_id, path, path_references) } fn local_module_id(&self) -> LocalModuleId { @@ -53,11 +58,12 @@ pub fn resolve_path( def_maps: &BTreeMap, module_id: ModuleId, path: Path, + path_references: &mut Option<&mut Vec>, ) -> PathResolutionResult { // lets package up the path into an ImportDirective and resolve it using that let import = ImportDirective { module_id: module_id.local_id, path, alias: None, is_prelude: false }; - let resolved_import = resolve_import(module_id.krate, &import, def_maps)?; + let resolved_import = resolve_import(module_id.krate, &import, def_maps, path_references)?; let namespace = resolved_import.resolved_namespace; let id = diff --git a/compiler/noirc_frontend/src/hir/resolution/resolver.rs b/compiler/noirc_frontend/src/hir/resolution/resolver.rs index 97de66be817..856a769c9dd 100644 --- a/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ b/compiler/noirc_frontend/src/hir/resolution/resolver.rs @@ -17,7 +17,7 @@ use crate::hir_def::expr::{ HirArrayLiteral, HirBinaryOp, HirBlockExpression, HirCallExpression, HirCapturedVar, HirCastExpression, HirConstructorExpression, HirExpression, HirIdent, HirIfExpression, HirIndexExpression, HirInfixExpression, HirLambda, HirLiteral, HirMemberAccess, - HirMethodCallExpression, HirPrefixExpression, HirQuoted, ImplKind, + HirMethodCallExpression, HirPrefixExpression, ImplKind, }; use crate::hir_def::function::FunctionBody; @@ -32,8 +32,9 @@ use crate::ast::{ ArrayLiteral, BinaryOpKind, BlockExpression, Expression, ExpressionKind, ForRange, FunctionDefinition, FunctionKind, FunctionReturnType, Ident, ItemVisibility, LValue, LetStatement, Literal, NoirFunction, NoirStruct, NoirTypeAlias, Param, Path, PathKind, Pattern, - Statement, StatementKind, TraitBound, UnaryOp, UnresolvedGenerics, UnresolvedTraitConstraint, - UnresolvedType, UnresolvedTypeData, UnresolvedTypeExpression, Visibility, ERROR_IDENT, + Statement, StatementKind, TraitBound, UnaryOp, UnresolvedGeneric, UnresolvedGenerics, + UnresolvedTraitConstraint, UnresolvedType, UnresolvedTypeData, UnresolvedTypeExpression, + Visibility, ERROR_IDENT, }; use crate::graph::CrateId; use crate::hir::def_map::{ModuleDefId, TryFromModuleDefId, MAIN_FUNCTION}; @@ -47,7 +48,10 @@ use crate::node_interner::{ DefinitionId, DefinitionKind, DependencyId, ExprId, FuncId, GlobalId, NodeInterner, StmtId, StructId, TraitId, TraitImplId, TraitMethodId, TypeAliasId, }; -use crate::{Generics, Shared, StructType, Type, TypeAlias, TypeVariable, TypeVariableKind}; +use crate::{ + GenericTypeVars, Generics, Kind, ResolvedGeneric, Shared, StructType, Type, TypeAlias, + TypeVariable, TypeVariableKind, +}; use fm::FileId; use iter_extended::vecmap; use noirc_errors::{Location, Span, Spanned}; @@ -131,7 +135,7 @@ pub struct Resolver<'a> { /// unique type variables if we're resolving a struct. Empty otherwise. /// This is a Vec rather than a map to preserve the order a functions generics /// were declared in. - generics: Vec<(Rc, TypeVariable, Span)>, + generics: Vec, /// When resolving lambda expressions, we need to keep track of the variables /// that are captured. We do this in order to create the hidden environment @@ -223,7 +227,8 @@ impl<'a> Resolver<'a> { let mut new_generic_ident: Ident = format!("T{}_impl_{}", func_id, path.as_string()).into(); let mut new_generic_path = Path::from_ident(new_generic_ident.clone()); - while impl_trait_generics.contains(&new_generic_ident) + let new_generic = UnresolvedGeneric::from(new_generic_ident.clone()); + while impl_trait_generics.contains(&new_generic) || self.lookup_generic_or_global_type(&new_generic_path).is_some() { new_generic_ident = @@ -231,7 +236,7 @@ impl<'a> Resolver<'a> { new_generic_path = Path::from_ident(new_generic_ident.clone()); counter += 1; } - impl_trait_generics.insert(new_generic_ident.clone()); + impl_trait_generics.insert(UnresolvedGeneric::from(new_generic_ident.clone())); let is_synthesized = true; let new_generic_type_data = @@ -249,7 +254,7 @@ impl<'a> Resolver<'a> { }; parameter.typ.typ = new_generic_type_data; - func.def.generics.push(new_generic_ident); + func.def.generics.push(new_generic_ident.into()); func.def.where_clause.push(new_trait_constraint); } } @@ -394,8 +399,8 @@ impl<'a> Resolver<'a> { } let location = Location::new(name.span(), self.file); - let id = - self.interner.push_definition(name.0.contents.clone(), mutable, definition, location); + let var_name = name.0.contents.clone(); + let id = self.interner.push_definition(var_name, mutable, false, definition, location); let ident = HirIdent::non_trait_method(id, location); let resolver_meta = ResolverMeta { num_times_used: 0, ident: ident.clone(), warn_if_unused }; @@ -440,8 +445,8 @@ impl<'a> Resolver<'a> { (hir_ident, resolver_meta) } else { let location = Location::new(name.span(), self.file); - let id = - self.interner.push_definition(name.0.contents.clone(), false, definition, location); + let var_name = name.0.contents.clone(); + let id = self.interner.push_definition(var_name, false, false, definition, location); let ident = HirIdent::non_trait_method(id, location); let resolver_meta = ResolverMeta { num_times_used: 0, ident: ident.clone(), warn_if_unused: true }; @@ -591,7 +596,7 @@ impl<'a> Resolver<'a> { let env = Box::new(self.resolve_type_inner(*env)); match *env { - Type::Unit | Type::Tuple(_) | Type::NamedGeneric(_, _) => { + Type::Unit | Type::Tuple(_) | Type::NamedGeneric(_, _, _) => { Type::Function(args, ret, env) } _ => { @@ -607,6 +612,7 @@ impl<'a> Resolver<'a> { Type::MutableReference(Box::new(self.resolve_type_inner(*element))) } Parenthesized(typ) => self.resolve_type_inner(*typ), + Resolved(id) => self.interner.get_quoted_type(id).clone(), }; if let Type::Struct(_, _) = resolved_type { @@ -621,8 +627,8 @@ impl<'a> Resolver<'a> { resolved_type } - fn find_generic(&self, target_name: &str) -> Option<&(Rc, TypeVariable, Span)> { - self.generics.iter().find(|(name, _, _)| name.as_ref() == target_name) + fn find_generic(&self, target_name: &str) -> Option<&ResolvedGeneric> { + self.generics.iter().find(|generic| generic.name.as_ref() == target_name) } fn resolve_named_type(&mut self, path: Path, args: Vec) -> Type { @@ -747,13 +753,19 @@ impl<'a> Resolver<'a> { fn lookup_generic_or_global_type(&mut self, path: &Path) -> Option { if path.segments.len() == 1 { let name = &path.last_segment().0.contents; - if let Some((name, var, _)) = self.find_generic(name) { - return Some(Type::NamedGeneric(var.clone(), name.clone())); - } + if let Some(generic) = self.find_generic(name) { + // We always insert a `TypeKind::Normal` as we do not support explicit numeric generics + // in the resolver + return Some(Type::NamedGeneric( + generic.type_var.clone(), + generic.name.clone(), + Kind::Normal, + )); + }; } // If we cannot find a local generic of the same name, try to look up a global - match self.path_resolver.resolve(self.def_maps, path.clone()) { + match self.path_resolver.resolve(self.def_maps, path.clone(), &mut None) { Ok(PathResolution { module_def_id: ModuleDefId::GlobalId(id), error }) => { if let Some(current_item) = self.current_item { self.interner.add_global_dependency(current_item, id); @@ -848,14 +860,14 @@ impl<'a> Resolver<'a> { /// Return the current generics. /// Needed to keep referring to the same type variables across many /// methods in a single impl. - pub fn get_generics(&self) -> &[(Rc, TypeVariable, Span)] { + pub fn get_generics(&self) -> &[ResolvedGeneric] { &self.generics } /// Set the current generics that are in scope. /// Unlike add_generics, this function will not create any new type variables, /// opting to reuse the existing ones it is directly given. - pub fn set_generics(&mut self, generics: Vec<(Rc, TypeVariable, Span)>) { + pub fn set_generics(&mut self, generics: Vec) { self.generics = generics; } @@ -875,48 +887,79 @@ impl<'a> Resolver<'a> { // Map the generic to a fresh type variable let id = self.interner.next_type_variable_id(); let typevar = TypeVariable::unbound(id); - let span = generic.0.span(); + let ident = generic.ident(); + let span = ident.0.span(); // Check for name collisions of this generic - let name = Rc::new(generic.0.contents.clone()); + let name = Rc::new(ident.0.contents.clone()); - if let Some((_, _, first_span)) = self.find_generic(&name) { + let resolved_generic = ResolvedGeneric { + name: name.clone(), + type_var: typevar, + // We only support numeric generics in the elaborator + kind: Kind::Normal, + span, + }; + if let Some(generic) = self.find_generic(&name) { self.errors.push(ResolverError::DuplicateDefinition { - name: generic.0.contents.clone(), - first_span: *first_span, + name: ident.0.contents.clone(), + first_span: generic.span, second_span: span, }); } else { - self.generics.push((name, typevar.clone(), span)); + self.generics.push(resolved_generic.clone()); } - typevar + resolved_generic }) } /// Add the given existing generics to scope. /// This is useful for adding the same generics to many items. E.g. apply impl generics /// to each function in the impl or trait generics to each item in the trait. - pub fn add_existing_generics(&mut self, names: &UnresolvedGenerics, generics: &Generics) { - assert_eq!(names.len(), generics.len()); + pub fn add_existing_generics( + &mut self, + unresolved_generics: &UnresolvedGenerics, + generics: &GenericTypeVars, + ) { + assert_eq!(unresolved_generics.len(), generics.len()); - for (name, typevar) in names.iter().zip(generics) { - self.add_existing_generic(&name.0.contents, name.0.span(), typevar.clone()); + for (unresolved_generic, typevar) in unresolved_generics.iter().zip(generics) { + self.add_existing_generic( + unresolved_generic, + unresolved_generic.span(), + typevar.clone(), + ); } } - pub fn add_existing_generic(&mut self, name: &str, span: Span, typevar: TypeVariable) { + pub fn add_existing_generic( + &mut self, + unresolved_generic: &UnresolvedGeneric, + span: Span, + typevar: TypeVariable, + ) { + let name = &unresolved_generic.ident().0.contents; + // Check for name collisions of this generic - let rc_name = Rc::new(name.to_owned()); + let rc_name = Rc::new(name.clone()); - if let Some((_, _, first_span)) = self.find_generic(&rc_name) { + if let Some(generic) = self.find_generic(&rc_name) { self.errors.push(ResolverError::DuplicateDefinition { - name: name.to_owned(), - first_span: *first_span, + name: name.clone(), + first_span: generic.span, second_span: span, }); } else { - self.generics.push((rc_name, typevar, span)); + let resolved_generic = ResolvedGeneric { + name: rc_name, + type_var: typevar.clone(), + kind: unresolved_generic + .kind() + .expect("ICE: Deprecated code should only support normal kinds"), + span, + }; + self.generics.push(resolved_generic); } } @@ -992,7 +1035,7 @@ impl<'a> Resolver<'a> { // indicate we should code generate in the same way. Thus, we unify the attributes into one flag here. let has_inline_attribute = has_no_predicates_attribute || should_fold; - let generics = vecmap(&self.generics, |(_, typevar, _)| typevar.clone()); + let generics = vecmap(&self.generics, |generic| generic.type_var.clone()); let mut parameters = vec![]; let mut parameter_types = vec![]; @@ -1053,8 +1096,7 @@ impl<'a> Resolver<'a> { let direct_generics = func.def.generics.iter(); let direct_generics = direct_generics - .filter_map(|generic| self.find_generic(&generic.0.contents)) - .map(|(name, typevar, _span)| (name.clone(), typevar.clone())) + .filter_map(|generic| self.find_generic(&generic.ident().0.contents).cloned()) .collect(); FuncMeta { @@ -1071,6 +1113,7 @@ impl<'a> Resolver<'a> { trait_constraints: self.resolve_trait_constraints(&func.def.where_clause), is_entry_point: self.is_entry_point_function(func), has_inline_attribute, + source_crate: self.path_resolver.module_id().krate, // These fields are only used by the elaborator all_generics: Vec::new(), @@ -1107,6 +1150,7 @@ impl<'a> Resolver<'a> { !func.def.is_unconstrained } + // TODO(https://github.com/noir-lang/noir/issues/5156): Remove this method in favor of explicit numeric generics fn declare_numeric_generics(&mut self, params: &[Type], return_type: &Type) { if self.generics.is_empty() { return; @@ -1119,12 +1163,12 @@ impl<'a> Resolver<'a> { // We can fail to find the generic in self.generics if it is an implicit one created // by the compiler. This can happen when, e.g. eliding array lengths using the slice // syntax [T]. - if let Some((name, _, span)) = - self.generics.iter().find(|(name, _, _)| name.as_ref() == &name_to_find) + if let Some(ResolvedGeneric { name, span, .. }) = + self.generics.iter().find(|generic| generic.name.as_ref() == &name_to_find) { let ident = Ident::new(name.to_string(), *span); let definition = DefinitionKind::GenericType(type_variable); - self.add_variable_decl_inner(ident, false, false, false, definition); + self.add_variable_decl_inner(ident.clone(), false, false, false, definition); } } } @@ -1150,7 +1194,7 @@ impl<'a> Resolver<'a> { | Type::Error | Type::TypeVariable(_, _) | Type::Constant(_) - | Type::NamedGeneric(_, _) + | Type::NamedGeneric(_, _, _) | Type::Quoted(_) | Type::Forall(_, _) => (), @@ -1161,7 +1205,7 @@ impl<'a> Resolver<'a> { } Type::Array(length, element_type) => { - if let Type::NamedGeneric(type_variable, name) = length.as_ref() { + if let Type::NamedGeneric(type_variable, name, _) = length.as_ref() { found.insert(name.to_string(), type_variable.clone()); } Self::find_numeric_generics_in_type(element_type, found); @@ -1186,7 +1230,7 @@ impl<'a> Resolver<'a> { Type::Struct(struct_type, generics) => { for (i, generic) in generics.iter().enumerate() { - if let Type::NamedGeneric(type_variable, name) = generic { + if let Type::NamedGeneric(type_variable, name, _) = generic { if struct_type.borrow().generic_is_numeric(i) { found.insert(name.to_string(), type_variable.clone()); } @@ -1197,7 +1241,7 @@ impl<'a> Resolver<'a> { } Type::Alias(alias, generics) => { for (i, generic) in generics.iter().enumerate() { - if let Type::NamedGeneric(type_variable, name) = generic { + if let Type::NamedGeneric(type_variable, name, _) = generic { if alias.borrow().generic_is_numeric(i) { found.insert(name.to_string(), type_variable.clone()); } @@ -1208,12 +1252,12 @@ impl<'a> Resolver<'a> { } Type::MutableReference(element) => Self::find_numeric_generics_in_type(element, found), Type::String(length) => { - if let Type::NamedGeneric(type_variable, name) = length.as_ref() { + if let Type::NamedGeneric(type_variable, name, _) = length.as_ref() { found.insert(name.to_string(), type_variable.clone()); } } Type::FmtString(length, fields) => { - if let Type::NamedGeneric(type_variable, name) = length.as_ref() { + if let Type::NamedGeneric(type_variable, name, _) = length.as_ref() { found.insert(name.to_string(), type_variable.clone()); } Self::find_numeric_generics_in_type(fields, found); @@ -1467,7 +1511,7 @@ impl<'a> Resolver<'a> { // Otherwise, then it is referring to an Identifier // This lookup allows support of such statements: let x = foo::bar::SOME_GLOBAL + 10; // If the expression is a singular indent, we search the resolver's current scope as normal. - let (hir_ident, var_scope_index) = self.get_ident_from_path(path); + let (hir_ident, var_scope_index) = self.get_ident_from_path(path.clone()); if hir_ident.id != DefinitionId::dummy_id() { match self.interner.definition(hir_ident.id).kind { @@ -1504,6 +1548,7 @@ impl<'a> Resolver<'a> { ExpressionKind::Prefix(prefix) => { let operator = prefix.operator; let rhs = self.resolve_expression(prefix.rhs); + let trait_method_id = self.interner.get_prefix_operator_trait_method(&operator); if operator == UnaryOp::MutableReference { if let Err(error) = verify_mutable_reference(self.interner, rhs) { @@ -1511,7 +1556,7 @@ impl<'a> Resolver<'a> { } } - HirExpression::Prefix(HirPrefixExpression { operator, rhs }) + HirExpression::Prefix(HirPrefixExpression { operator, rhs, trait_method_id }) } ExpressionKind::Infix(infix) => { let lhs = self.resolve_expression(infix.lhs); @@ -1634,10 +1679,7 @@ impl<'a> Resolver<'a> { ExpressionKind::Parenthesized(sub_expr) => return self.resolve_expression(*sub_expr), // The quoted expression isn't resolved since we don't want errors if variables aren't defined - ExpressionKind::Quote(block, _) => { - let quoted = HirQuoted { quoted_block: block, unquoted_exprs: Vec::new() }; - HirExpression::Quote(quoted) - } + ExpressionKind::Quote(block) => HirExpression::Quote(block), ExpressionKind::Comptime(block, _) => { HirExpression::Comptime(self.resolve_block(block)) } @@ -1648,9 +1690,6 @@ impl<'a> Resolver<'a> { self.push_err(ResolverError::UnquoteUsedOutsideQuote { span: expr.span }); HirExpression::Literal(HirLiteral::Unit) } - ExpressionKind::UnquoteMarker(index) => { - unreachable!("UnquoteMarker({index}) remaining in runtime code") - } }; // If these lines are ever changed, make sure to change the early return @@ -1880,7 +1919,9 @@ impl<'a> Resolver<'a> { let constraint = TraitConstraint { typ: self.self_type.clone()?, - trait_generics: Type::from_generics(&the_trait.generics), + trait_generics: Type::from_generics(&vecmap(&the_trait.generics, |generic| { + generic.type_var.clone() + })), trait_id, }; return Some((method, constraint, false)); @@ -1908,7 +1949,9 @@ impl<'a> Resolver<'a> { the_trait.self_type_typevar.clone(), TypeVariableKind::Normal, ), - trait_generics: Type::from_generics(&the_trait.generics), + trait_generics: Type::from_generics(&vecmap(&the_trait.generics, |generic| { + generic.type_var.clone() + })), trait_id, }; return Some((method, constraint, false)); @@ -1974,7 +2017,7 @@ impl<'a> Resolver<'a> { } fn resolve_path(&mut self, path: Path) -> Result { - let path_resolution = self.path_resolver.resolve(self.def_maps, path)?; + let path_resolution = self.path_resolver.resolve(self.def_maps, path, &mut None)?; if let Some(error) = path_resolution.error { self.push_err(error.into()); diff --git a/compiler/noirc_frontend/src/hir/resolution/traits.rs b/compiler/noirc_frontend/src/hir/resolution/traits.rs index 4c360731711..28ee70393cd 100644 --- a/compiler/noirc_frontend/src/hir/resolution/traits.rs +++ b/compiler/noirc_frontend/src/hir/resolution/traits.rs @@ -4,7 +4,7 @@ use fm::FileId; use iter_extended::vecmap; use noirc_errors::Location; -use crate::ast::{ItemVisibility, Path, TraitItem}; +use crate::ast::{Ident, ItemVisibility, Path, TraitItem, UnresolvedGeneric}; use crate::{ graph::CrateId, hir::{ @@ -17,7 +17,7 @@ use crate::{ }, hir_def::traits::{TraitConstant, TraitFunction, TraitImpl, TraitType}, node_interner::{FuncId, NodeInterner, TraitId}, - Generics, Shared, Type, TypeVariable, TypeVariableKind, + GenericTypeVars, Shared, Type, TypeVariableKind, }; use super::{ @@ -35,15 +35,18 @@ pub(crate) fn resolve_traits( traits: BTreeMap, crate_id: CrateId, ) -> Vec<(CompilationError, FileId)> { - for (trait_id, unresolved_trait) in &traits { - context.def_interner.push_empty_trait(*trait_id, unresolved_trait); - } let mut all_errors = Vec::new(); for (trait_id, unresolved_trait) in traits { - let generics = vecmap(&unresolved_trait.trait_def.generics, |_| { - TypeVariable::unbound(context.def_interner.next_type_variable_id()) - }); + let file_id = context.def_maps[&crate_id].file_id(unresolved_trait.module_id); + let generics = context.resolve_generics( + &unresolved_trait.trait_def.generics, + &mut all_errors, + file_id, + ); + let generic_type_vars = generics.iter().map(|generic| generic.type_var.clone()).collect(); + + context.def_interner.push_empty_trait(trait_id, &unresolved_trait, generics); // Resolve order // 1. Trait Types ( Trait constants can have a trait type, therefore types before constants) @@ -51,21 +54,26 @@ pub(crate) fn resolve_traits( // 2. Trait Constants ( Trait's methods can use trait types & constants, therefore they should be after) let _ = resolve_trait_constants(context, crate_id, &unresolved_trait); // 3. Trait Methods - let (methods, errors) = - resolve_trait_methods(context, trait_id, crate_id, &unresolved_trait, &generics); + let (methods, errors) = resolve_trait_methods( + context, + trait_id, + crate_id, + &unresolved_trait, + &generic_type_vars, + ); all_errors.extend(errors); context.def_interner.update_trait(trait_id, |trait_def| { trait_def.set_methods(methods); - trait_def.generics = generics; }); // This check needs to be after the trait's methods are set since // the interner may set `interner.ordering_type` based on the result type // of the Cmp trait, if this is it. if crate_id.is_stdlib() { - context.def_interner.try_add_operator_trait(trait_id); + context.def_interner.try_add_infix_operator_trait(trait_id); + context.def_interner.try_add_prefix_operator_trait(trait_id); } } all_errors @@ -93,7 +101,7 @@ fn resolve_trait_methods( trait_id: TraitId, crate_id: CrateId, unresolved_trait: &UnresolvedTrait, - trait_generics: &Generics, + trait_generics: &GenericTypeVars, ) -> (Vec, Vec<(CompilationError, FileId)>) { let interner = &mut context.def_interner; let def_maps = &mut context.def_maps; @@ -126,7 +134,11 @@ fn resolve_trait_methods( resolver.add_generics(generics); resolver.add_existing_generics(&unresolved_trait.trait_def.generics, trait_generics); - resolver.add_existing_generic("Self", name_span, self_typevar); + resolver.add_existing_generic( + &UnresolvedGeneric::Variable(Ident::from("Self")), + name_span, + self_typevar, + ); resolver.set_self_type(Some(self_type.clone())); let func_id = unresolved_trait.method_ids[&name.0.contents]; @@ -143,7 +155,7 @@ fn resolve_trait_methods( let arguments = vecmap(parameters, |param| resolver.resolve_type(param.1.clone())); let return_type = resolver.resolve_type(return_type.get_type().into_owned()); - let generics = vecmap(resolver.get_generics(), |(_, type_var, _)| type_var.clone()); + let generics = vecmap(resolver.get_generics(), |generic| generic.type_var.clone()); let default_impl_list: Vec<_> = unresolved_trait .fns_with_default_impl @@ -377,7 +389,7 @@ pub(crate) fn resolve_trait_by_path( ) -> Result<(TraitId, Option), DefCollectorErrorKind> { let path_resolver = StandardPathResolver::new(module); - match path_resolver.resolve(def_maps, path.clone()) { + match path_resolver.resolve(def_maps, path.clone(), &mut None) { Ok(PathResolution { module_def_id: ModuleDefId::TraitId(trait_id), error }) => { Ok((trait_id, error)) } @@ -464,7 +476,7 @@ pub(crate) fn resolve_trait_impls( methods: vecmap(&impl_methods, |(_, func_id)| *func_id), }); - let impl_generics = vecmap(impl_generics, |(_, type_variable, _)| type_variable); + let impl_generics = vecmap(impl_generics, |generic| generic.type_var); if let Err((prev_span, prev_file)) = interner.add_trait_implementation( self_type.clone(), diff --git a/compiler/noirc_frontend/src/hir/type_check/errors.rs b/compiler/noirc_frontend/src/hir/type_check/errors.rs index bd32ba2fce5..af168a10df9 100644 --- a/compiler/noirc_frontend/src/hir/type_check/errors.rs +++ b/compiler/noirc_frontend/src/hir/type_check/errors.rs @@ -1,4 +1,5 @@ use acvm::FieldElement; +use iter_extended::vecmap; use noirc_errors::CustomDiagnostic as Diagnostic; use noirc_errors::Span; use thiserror::Error; @@ -6,7 +7,9 @@ use thiserror::Error; use crate::ast::{BinaryOpKind, FunctionReturnType, IntegerBitSize, Signedness}; use crate::hir::resolution::errors::ResolverError; use crate::hir_def::expr::HirBinaryOp; +use crate::hir_def::traits::TraitConstraint; use crate::hir_def::types::Type; +use crate::macros_api::NodeInterner; #[derive(Error, Debug, Clone, PartialEq, Eq)] pub enum Source { @@ -32,7 +35,7 @@ pub enum Source { pub enum TypeCheckError { #[error("Operator {op:?} cannot be used in a {place:?}")] OpCannotBeUsed { op: HirBinaryOp, place: &'static str, span: Span }, - #[error("The literal `{expr:?}` cannot fit into `{ty}` which has range `{range}`")] + #[error("The value `{expr:?}` cannot fit into `{ty}` which has range `{range}`")] OverflowingAssignment { expr: FieldElement, ty: Type, range: String, span: Span }, #[error("Type {typ:?} cannot be used in a {place:?}")] TypeCannotBeUsed { typ: Type, place: &'static str, span: Span }, @@ -40,6 +43,8 @@ pub enum TypeCheckError { TypeMismatch { expected_typ: String, expr_typ: String, expr_span: Span }, #[error("Expected type {expected} is not the same as {actual}")] TypeMismatchWithSource { expected: Type, actual: Type, span: Span, source: Source }, + #[error("Expected type {expected_kind:?} is not the same as {expr_kind:?}")] + TypeKindMismatch { expected_kind: String, expr_kind: String, expr_span: Span }, #[error("Expected {expected:?} found {found:?}")] ArityMisMatch { expected: usize, found: usize, span: Span }, #[error("Return type in a function cannot be public")] @@ -78,6 +83,8 @@ pub enum TypeCheckError { IntegerAndFieldBinaryOperation { span: Span }, #[error("Cannot do modulo on Fields, try casting to an integer first")] FieldModulo { span: Span }, + #[error("Cannot do not (`!`) on Fields, try casting to an integer first")] + FieldNot { span: Span }, #[error("Fields cannot be compared, try casting to an integer first")] FieldComparison { span: Span }, #[error("The bit count in a bit-shift operation must fit in a u8, try casting the right hand side into a u8 first")] @@ -112,7 +119,7 @@ pub enum TypeCheckError { parameter_index: usize, }, #[error("No matching impl found")] - NoMatchingImplFound { constraints: Vec<(Type, String)>, span: Span }, + NoMatchingImplFound(NoMatchingImplFoundError), #[error("Constraint for `{typ}: {trait_name}` is not needed, another matching impl is already in scope")] UnneededTraitConstraint { trait_name: String, typ: Type, span: Span }, #[error( @@ -143,10 +150,16 @@ pub enum TypeCheckError { }, #[error("Strings do not support indexed assignment")] StringIndexAssign { span: Span }, - #[error("Macro calls may only return Expr values")] + #[error("Macro calls may only return `Quoted` values")] MacroReturningNonExpr { typ: Type, span: Span }, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct NoMatchingImplFoundError { + constraints: Vec<(Type, String)>, + pub span: Span, +} + impl TypeCheckError { pub fn add_context(self, ctx: &'static str) -> Self { TypeCheckError::Context { err: Box::new(self), ctx } @@ -178,6 +191,13 @@ impl<'a> From<&'a TypeCheckError> for Diagnostic { *expr_span, ) } + TypeCheckError::TypeKindMismatch { expected_kind, expr_kind, expr_span } => { + Diagnostic::simple_error( + format!("Expected kind {expected_kind}, found kind {expr_kind}"), + String::new(), + *expr_span, + ) + } TypeCheckError::TraitMethodParameterTypeMismatch { method_name, expected_typ, actual_typ, parameter_index, parameter_span } => { Diagnostic::simple_error( format!("Parameter #{parameter_index} of method `{method_name}` must be of type {expected_typ}, not {actual_typ}"), @@ -238,6 +258,7 @@ impl<'a> From<&'a TypeCheckError> for Diagnostic { | TypeCheckError::IntegerAndFieldBinaryOperation { span } | TypeCheckError::OverflowingAssignment { span, .. } | TypeCheckError::FieldModulo { span } + | TypeCheckError::FieldNot { span } | TypeCheckError::ConstrainedReferenceToUnconstrained { span } | TypeCheckError::UnconstrainedReferenceToConstrained { span } | TypeCheckError::UnconstrainedSliceReturnToConstrained { span } @@ -298,20 +319,7 @@ impl<'a> From<&'a TypeCheckError> for Diagnostic { let msg = format!("Unused expression result of type {expr_type}"); Diagnostic::simple_warning(msg, String::new(), *expr_span) } - TypeCheckError::NoMatchingImplFound { constraints, span } => { - assert!(!constraints.is_empty()); - let msg = format!("No matching impl found for `{}: {}`", constraints[0].0, constraints[0].1); - let mut diagnostic = Diagnostic::from_message(&msg); - - diagnostic.add_secondary(format!("No impl for `{}: {}`", constraints[0].0, constraints[0].1), *span); - - // These must be notes since secondaries are unordered - for (typ, trait_name) in &constraints[1..] { - diagnostic.add_note(format!("Required by `{typ}: {trait_name}`")); - } - - diagnostic - } + TypeCheckError::NoMatchingImplFound(error) => error.into(), TypeCheckError::UnneededTraitConstraint { trait_name, typ, span } => { let msg = format!("Constraint for `{typ}: {trait_name}` is not needed, another matching impl is already in scope"); Diagnostic::simple_warning(msg, "Unnecessary trait constraint in where clause".into(), *span) @@ -338,10 +346,57 @@ impl<'a> From<&'a TypeCheckError> for Diagnostic { Diagnostic::simple_error(msg, "".into(), *span) }, TypeCheckError::MacroReturningNonExpr { typ, span } => Diagnostic::simple_error( - format!("Expected macro call to return an `Expr` but found a(n) {typ}"), - "Macro calls must return quoted expressions, otherwise there is no code to insert".into(), + format!("Expected macro call to return a `Quoted` but found a(n) `{typ}`"), + "Macro calls must return quoted values, otherwise there is no code to insert".into(), *span, - ), + ), } } } + +impl<'a> From<&'a NoMatchingImplFoundError> for Diagnostic { + fn from(error: &'a NoMatchingImplFoundError) -> Self { + let constraints = &error.constraints; + let span = error.span; + + assert!(!constraints.is_empty()); + let msg = + format!("No matching impl found for `{}: {}`", constraints[0].0, constraints[0].1); + let mut diagnostic = Diagnostic::from_message(&msg); + + let secondary = format!("No impl for `{}: {}`", constraints[0].0, constraints[0].1); + diagnostic.add_secondary(secondary, span); + + // These must be notes since secondaries are unordered + for (typ, trait_name) in &constraints[1..] { + diagnostic.add_note(format!("Required by `{typ}: {trait_name}`")); + } + + diagnostic + } +} + +impl NoMatchingImplFoundError { + pub fn new( + interner: &NodeInterner, + failing_constraints: Vec, + span: Span, + ) -> Option { + // Don't show any errors where try_get_trait returns None. + // This can happen if a trait is used that was never declared. + let constraints = failing_constraints + .into_iter() + .map(|constraint| { + let r#trait = interner.try_get_trait(constraint.trait_id)?; + let mut name = r#trait.name.to_string(); + if !constraint.trait_generics.is_empty() { + let generics = vecmap(&constraint.trait_generics, ToString::to_string); + name += &format!("<{}>", generics.join(", ")); + } + Some((constraint.typ, name)) + }) + .collect::>>()?; + + Some(Self { constraints, span }) + } +} diff --git a/compiler/noirc_frontend/src/hir/type_check/expr.rs b/compiler/noirc_frontend/src/hir/type_check/expr.rs index 46e8db8f5ff..9dfe0901016 100644 --- a/compiler/noirc_frontend/src/hir/type_check/expr.rs +++ b/compiler/noirc_frontend/src/hir/type_check/expr.rs @@ -17,6 +17,7 @@ use crate::{ TypeBinding, TypeBindings, TypeVariableKind, }; +use super::NoMatchingImplFoundError; use super::{errors::TypeCheckError, TypeChecker}; impl<'interner> TypeChecker<'interner> { @@ -307,13 +308,13 @@ impl<'interner> TypeChecker<'interner> { Type::Function(params, Box::new(lambda.return_type), Box::new(env_type)) } - HirExpression::Quote(_) => Type::Quoted(crate::QuotedType::Expr), + HirExpression::Quote(_) => Type::Quoted(crate::QuotedType::Quoted), HirExpression::Comptime(block) => self.check_block(block), // Unquote should be inserted & removed by the comptime interpreter. // Even if we allowed it here, we wouldn't know what type to give to the result. HirExpression::Unquote(block) => { - unreachable!("Unquote remaining during type checking {block}") + unreachable!("Unquote remaining during type checking {block:?}") } }; @@ -340,7 +341,7 @@ impl<'interner> TypeChecker<'interner> { // Check that we are not passing a mutable reference from a constrained runtime to an unconstrained runtime if is_current_func_constrained && is_unconstrained_call { for (typ, _, _) in args.iter() { - if matches!(&typ.follow_bindings(), Type::MutableReference(_)) { + if !typ.is_valid_for_unconstrained_boundary() { self.errors.push(TypeCheckError::ConstrainedReferenceToUnconstrained { span }); } } @@ -404,8 +405,8 @@ impl<'interner> TypeChecker<'interner> { for (param, arg) in the_trait.generics.iter().zip(&constraint.trait_generics) { // Avoid binding t = t - if !arg.occurs(param.id()) { - bindings.insert(param.id(), (param.clone(), arg.clone())); + if !arg.occurs(param.type_var.id()) { + bindings.insert(param.type_var.id(), (param.type_var.clone(), arg.clone())); } } @@ -518,26 +519,10 @@ impl<'interner> TypeChecker<'interner> { Err(erroring_constraints) => { if erroring_constraints.is_empty() { self.errors.push(TypeCheckError::TypeAnnotationsNeeded { span }); - } else { - // Don't show any errors where try_get_trait returns None. - // This can happen if a trait is used that was never declared. - let constraints = erroring_constraints - .into_iter() - .map(|constraint| { - let r#trait = self.interner.try_get_trait(constraint.trait_id)?; - let mut name = r#trait.name.to_string(); - if !constraint.trait_generics.is_empty() { - let generics = - vecmap(&constraint.trait_generics, ToString::to_string); - name += &format!("<{}>", generics.join(", ")); - } - Some((constraint.typ, name)) - }) - .collect::>>(); - - if let Some(constraints) = constraints { - self.errors.push(TypeCheckError::NoMatchingImplFound { constraints, span }); - } + } else if let Some(error) = + NoMatchingImplFoundError::new(self.interner, erroring_constraints, span) + { + self.errors.push(TypeCheckError::NoMatchingImplFound(error)); } } } @@ -593,6 +578,7 @@ impl<'interner> TypeChecker<'interner> { self.interner.push_expr(HirExpression::Prefix(HirPrefixExpression { operator: UnaryOp::MutableReference, rhs: method_call.object, + trait_method_id: None, })); self.interner.push_expr_type(new_object, new_type); self.interner.push_expr_location(new_object, location.span, location.file); @@ -619,6 +605,7 @@ impl<'interner> TypeChecker<'interner> { let object = self.interner.push_expr(HirExpression::Prefix(HirPrefixExpression { operator: UnaryOp::Dereference { implicitly_added: true }, rhs: object, + trait_method_id: None, })); self.interner.push_expr_type(object, element.as_ref().clone()); self.interner.push_expr_location(object, location.span, location.file); @@ -814,9 +801,11 @@ impl<'interner> TypeChecker<'interner> { let dereference_lhs = |this: &mut Self, lhs_type, element| { let old_lhs = *access_lhs; + *access_lhs = this.interner.push_expr(HirExpression::Prefix(HirPrefixExpression { operator: crate::ast::UnaryOp::Dereference { implicitly_added: true }, rhs: old_lhs, + trait_method_id: None, })); this.interner.push_expr_type(old_lhs, lhs_type); this.interner.push_expr_type(*access_lhs, element); @@ -1025,7 +1014,7 @@ impl<'interner> TypeChecker<'interner> { }); None } - Type::NamedGeneric(_, _) => { + Type::NamedGeneric(_, _, _) => { let func_meta = self.interner.function_meta( &self.current_function.expect("unexpected method outside a function"), ); diff --git a/compiler/noirc_frontend/src/hir/type_check/mod.rs b/compiler/noirc_frontend/src/hir/type_check/mod.rs index 98e1cd9c72a..1a70bade863 100644 --- a/compiler/noirc_frontend/src/hir/type_check/mod.rs +++ b/compiler/noirc_frontend/src/hir/type_check/mod.rs @@ -11,7 +11,7 @@ mod errors; mod expr; mod stmt; -pub use errors::TypeCheckError; +pub use errors::{NoMatchingImplFoundError, TypeCheckError}; use noirc_errors::Span; use crate::{ @@ -22,7 +22,7 @@ use crate::{ traits::TraitConstraint, }, node_interner::{ExprId, FuncId, GlobalId, NodeInterner}, - Type, TypeBindings, + Kind, ResolvedGeneric, Type, TypeBindings, }; pub use self::errors::Source; @@ -263,7 +263,7 @@ pub(crate) fn check_trait_impl_method_matches_declaration( // Substitute each generic on the trait with the corresponding generic on the impl for (generic, arg) in trait_info.generics.iter().zip(&impl_.trait_generics) { - bindings.insert(generic.id(), (generic.clone(), arg.clone())); + bindings.insert(generic.type_var.id(), (generic.type_var.clone(), arg.clone())); } // If this is None, the trait does not have the corresponding function. @@ -281,10 +281,12 @@ pub(crate) fn check_trait_impl_method_matches_declaration( } // Substitute each generic on the trait function with the corresponding generic on the impl function - for ((_, trait_fn_generic), (name, impl_fn_generic)) in - trait_fn_meta.direct_generics.iter().zip(&meta.direct_generics) + for ( + ResolvedGeneric { type_var: trait_fn_generic, .. }, + ResolvedGeneric { name, type_var: impl_fn_generic, .. }, + ) in trait_fn_meta.direct_generics.iter().zip(&meta.direct_generics) { - let arg = Type::NamedGeneric(impl_fn_generic.clone(), name.clone()); + let arg = Type::NamedGeneric(impl_fn_generic.clone(), name.clone(), Kind::Normal); bindings.insert(trait_fn_generic.id(), (trait_fn_generic.clone(), arg)); } @@ -461,7 +463,9 @@ pub mod test { function::{FuncMeta, HirFunction}, stmt::HirStatement, }; - use crate::node_interner::{DefinitionKind, FuncId, NodeInterner, TraitId, TraitMethodId}; + use crate::node_interner::{ + DefinitionKind, FuncId, NodeInterner, ReferenceId, TraitId, TraitMethodId, + }; use crate::{ hir::{ def_map::{CrateDefMap, LocalModuleId, ModuleDefId}, @@ -483,19 +487,34 @@ pub mod test { // let z = x + y; // // Push x variable - let x_id = - interner.push_definition("x".into(), false, DefinitionKind::Local(None), location); + let x_id = interner.push_definition( + "x".into(), + false, + false, + DefinitionKind::Local(None), + location, + ); let x = HirIdent::non_trait_method(x_id, location); // Push y variable - let y_id = - interner.push_definition("y".into(), false, DefinitionKind::Local(None), location); + let y_id = interner.push_definition( + "y".into(), + false, + false, + DefinitionKind::Local(None), + location, + ); let y = HirIdent::non_trait_method(y_id, location); // Push z variable - let z_id = - interner.push_definition("z".into(), false, DefinitionKind::Local(None), location); + let z_id = interner.push_definition( + "z".into(), + false, + false, + DefinitionKind::Local(None), + location, + ); let z = HirIdent::non_trait_method(z_id, location); // Push x and y as expressions @@ -531,7 +550,7 @@ pub mod test { let func_id = interner.push_fn(func); let definition = DefinitionKind::Local(None); - let id = interner.push_definition("test_func".into(), false, definition, location); + let id = interner.push_definition("test_func".into(), false, false, definition, location); let name = HirIdent::non_trait_method(id, location); // Add function meta @@ -561,6 +580,7 @@ pub mod test { all_generics: Vec::new(), parameter_idents: Vec::new(), function_body: FunctionBody::Resolved, + source_crate: CrateId::dummy_id(), }; interner.push_fn_meta(func_meta, func_id); @@ -676,6 +696,7 @@ pub mod test { &self, _def_maps: &BTreeMap, path: Path, + _path_references: &mut Option<&mut Vec>, ) -> PathResolutionResult { // Not here that foo::bar and hello::foo::bar would fetch the same thing let name = path.segments.last().unwrap(); @@ -716,13 +737,15 @@ pub mod test { let mut interner = NodeInterner::default(); interner.populate_dummy_operator_traits(); - assert_eq!( - errors.len(), - 0, - "expected 0 parser errors, but got {}, errors: {:?}", - errors.len(), - errors - ); + if !errors.iter().all(|error| error.is_warning()) { + assert_eq!( + errors.len(), + 0, + "expected 0 parser errors, but got {}, errors: {:?}", + errors.len(), + errors + ); + } let func_ids = btree_map(&func_namespace, |name| { (name.to_string(), interner.push_test_function_definition(name.into())) diff --git a/compiler/noirc_frontend/src/hir/type_check/stmt.rs b/compiler/noirc_frontend/src/hir/type_check/stmt.rs index 3a570922c81..9abd1b34690 100644 --- a/compiler/noirc_frontend/src/hir/type_check/stmt.rs +++ b/compiler/noirc_frontend/src/hir/type_check/stmt.rs @@ -368,7 +368,7 @@ impl<'interner> TypeChecker<'interner> { let max = 1 << bit_count; if v >= max { self.errors.push(TypeCheckError::OverflowingAssignment { - expr: value, + expr: -value, ty: annotated_type.clone(), range: format!("0..={}", max - 1), span, diff --git a/compiler/noirc_frontend/src/hir_def/expr.rs b/compiler/noirc_frontend/src/hir_def/expr.rs index 9547aef866b..ab2344746d1 100644 --- a/compiler/noirc_frontend/src/hir_def/expr.rs +++ b/compiler/noirc_frontend/src/hir_def/expr.rs @@ -4,6 +4,7 @@ use noirc_errors::Location; use crate::ast::{BinaryOp, BinaryOpKind, Ident, UnaryOp}; use crate::node_interner::{DefinitionId, ExprId, FuncId, NodeInterner, StmtId, TraitMethodId}; +use crate::token::Tokens; use crate::Shared; use super::stmt::HirPattern; @@ -33,8 +34,8 @@ pub enum HirExpression { If(HirIfExpression), Tuple(Vec), Lambda(HirLambda), - Quote(HirQuoted), - Unquote(crate::ast::BlockExpression), + Quote(Tokens), + Unquote(Tokens), Comptime(HirBlockExpression), Error, } @@ -123,6 +124,10 @@ pub enum HirArrayLiteral { pub struct HirPrefixExpression { pub operator: UnaryOp, pub rhs: ExprId, + + /// The trait method id for the operator trait method that corresponds to this operator, + /// if such a trait exists (for example, there's no trait for the dereference operator). + pub trait_method_id: Option, } #[derive(Debug, Clone)] @@ -291,13 +296,3 @@ pub struct HirLambda { pub body: ExprId, pub captures: Vec, } - -#[derive(Debug, Clone)] -pub struct HirQuoted { - pub quoted_block: crate::ast::BlockExpression, - - /// Each expression here corresponds to a `ExpressionKind::UnquoteMarker(index)` in `quoted_block`. - /// The values of these expressions after evaluation will be inlined into the position - /// indicated by their corresponding UnquoteMarker with that index. - pub unquoted_exprs: Vec, -} diff --git a/compiler/noirc_frontend/src/hir_def/function.rs b/compiler/noirc_frontend/src/hir_def/function.rs index 53eabe21081..fa8bb55abee 100644 --- a/compiler/noirc_frontend/src/hir_def/function.rs +++ b/compiler/noirc_frontend/src/hir_def/function.rs @@ -1,15 +1,14 @@ use iter_extended::vecmap; use noirc_errors::{Location, Span}; -use std::rc::Rc; - use super::expr::{HirBlockExpression, HirExpression, HirIdent}; use super::stmt::HirPattern; use super::traits::TraitConstraint; use crate::ast::{FunctionKind, FunctionReturnType, Visibility}; +use crate::graph::CrateId; use crate::macros_api::BlockExpression; use crate::node_interner::{ExprId, NodeInterner, TraitImplId}; -use crate::{Type, TypeVariable}; +use crate::{ResolvedGeneric, Type}; /// A Hir function is a block expression /// with a list of statements @@ -112,13 +111,13 @@ pub struct FuncMeta { /// This does not include generics from an outer scope, like those introduced by /// an `impl` block. This also does not include implicit generics added by the compiler /// such as a trait's `Self` type variable. - pub direct_generics: Vec<(Rc, TypeVariable)>, + pub direct_generics: Vec, /// All the generics used by this function, which includes any implicit generics or generics /// from outer scopes, such as those introduced by an impl. /// This is stored when the FuncMeta is first created to later be used to set the current /// generics when the function's body is later resolved. - pub all_generics: Vec<(Rc, TypeVariable, Span)>, + pub all_generics: Vec, pub location: Location, @@ -145,6 +144,9 @@ pub struct FuncMeta { pub has_inline_attribute: bool, pub function_body: FunctionBody, + + /// The crate this function was defined in + pub source_crate: CrateId, } #[derive(Debug, Clone)] diff --git a/compiler/noirc_frontend/src/hir_def/traits.rs b/compiler/noirc_frontend/src/hir_def/traits.rs index e4959cb3dd9..0600706922b 100644 --- a/compiler/noirc_frontend/src/hir_def/traits.rs +++ b/compiler/noirc_frontend/src/hir_def/traits.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use rustc_hash::FxHashMap as HashMap; use crate::ast::{Ident, NoirFunction}; use crate::{ diff --git a/compiler/noirc_frontend/src/hir_def/types.rs b/compiler/noirc_frontend/src/hir_def/types.rs index ce36a22cf88..8183911c845 100644 --- a/compiler/noirc_frontend/src/hir_def/types.rs +++ b/compiler/noirc_frontend/src/hir_def/types.rs @@ -82,7 +82,7 @@ pub enum Type { /// NamedGenerics are the 'T' or 'U' in a user-defined generic function /// like `fn foo(...) {}`. Unlike TypeVariables, they cannot be bound over. - NamedGeneric(TypeVariable, Rc), + NamedGeneric(TypeVariable, Rc, Kind), /// A functions with arguments, a return type and environment. /// the environment should be `Unit` by default, @@ -98,7 +98,7 @@ pub enum Type { /// but it makes handling them both easier. The TypeVariableId should /// never be bound over during type checking, but during monomorphization it /// will be and thus needs the full TypeVariable link. - Forall(Generics, Box), + Forall(GenericTypeVars, Box), /// A type-level integer. Included to let an Array's size type variable /// bind to an integer without special checks to bind it to a non-type. @@ -142,7 +142,7 @@ impl Type { | Type::Unit | Type::TypeVariable(_, _) | Type::TraitAsType(..) - | Type::NamedGeneric(_, _) + | Type::NamedGeneric(_, _, _) | Type::Function(_, _, _) | Type::MutableReference(_) | Type::Forall(_, _) @@ -187,12 +187,34 @@ impl Type { } } +/// A Kind is the type of a Type. These are used since only certain kinds of types are allowed in +/// certain positions. +/// +/// For example, the type of a struct field or a function parameter is expected to be +/// a type of kind * (represented here as `Normal`). Types used in positions where a number +/// is expected (such as in an array length position) are expected to be of kind `Kind::Numeric`. +#[derive(PartialEq, Eq, Clone, Hash, Debug)] +pub enum Kind { + Normal, + Numeric(Box), +} + +impl std::fmt::Display for Kind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Kind::Normal => write!(f, "normal"), + Kind::Numeric(typ) => write!(f, "numeric {}", typ), + } + } +} + #[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)] pub enum QuotedType { Expr, - TypeDefinition, + Quoted, TopLevelItem, Type, + StructDefinition, } /// A list of TypeVariableIds to bind to a type. Storing the @@ -221,7 +243,22 @@ pub struct StructType { } /// Corresponds to generic lists such as `` in the source program. -pub type Generics = Vec; +/// Used mainly for resolved types which no longer need information such +/// as names or kinds. +pub type GenericTypeVars = Vec; + +/// Corresponds to generic lists such as `` with additional +/// information gathered during name resolution that is necessary +/// correctly resolving types. +pub type Generics = Vec; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ResolvedGeneric { + pub name: Rc, + pub type_var: TypeVariable, + pub kind: Kind, + pub span: Span, +} impl std::hash::Hash for StructType { fn hash(&self, state: &mut H) { @@ -270,7 +307,7 @@ impl StructType { .generics .iter() .zip(generic_args) - .map(|(old, new)| (old.id(), (old.clone(), new.clone()))) + .map(|(old, new)| (old.type_var.id(), (old.type_var.clone(), new.clone()))) .collect(); (typ.substitute(&substitutions), i) @@ -286,7 +323,7 @@ impl StructType { .generics .iter() .zip(generic_args) - .map(|(old, new)| (old.id(), (old.clone(), new.clone()))) + .map(|(old, new)| (old.type_var.id(), (old.type_var.clone(), new.clone()))) .collect(); vecmap(&self.fields, |(name, typ)| { @@ -295,15 +332,38 @@ impl StructType { }) } + /// Returns the name and raw types of each field of this type. + /// This will not substitute any generic arguments so a generic field like `x` + /// in `struct Foo { x: T }` will return a `("x", T)` pair. + /// + /// This method is almost never what is wanted for type checking or monomorphization, + /// prefer to use `get_fields` whenever possible. + pub fn get_fields_as_written(&self) -> Vec<(String, Type)> { + vecmap(&self.fields, |(name, typ)| (name.0.contents.clone(), typ.clone())) + } + + /// Returns the field at the given index. Panics if no field exists at the given index. + pub fn field_at(&self, index: usize) -> &(Ident, Type) { + &self.fields[index] + } + pub fn field_names(&self) -> BTreeSet { self.fields.iter().map(|(name, _)| name.clone()).collect() } + /// Search the fields of a struct for any types with a `TypeKind::Numeric` + pub fn find_numeric_generics_in_fields(&self, found_names: &mut Vec) { + for (_, field) in self.fields.iter() { + field.find_numeric_type_vars(found_names); + } + } + /// True if the given index is the same index as a generic type of this struct /// which is expected to be a numeric generic. /// This is needed because we infer type kinds in Noir and don't have extensive kind checking. + /// TODO(https://github.com/noir-lang/noir/issues/5156): This is outdated and we should remove this implicit searching for numeric generics pub fn generic_is_numeric(&self, index_of_generic: usize) -> bool { - let target_id = self.generics[index_of_generic].0; + let target_id = self.generics[index_of_generic].type_var.id(); self.fields.iter().any(|(_, field)| field.contains_numeric_typevar(target_id)) } @@ -372,7 +432,7 @@ impl TypeAlias { .generics .iter() .zip(generic_args) - .map(|(old, new)| (old.id(), (old.clone(), new.clone()))) + .map(|(old, new)| (old.type_var.id(), (old.type_var.clone(), new.clone()))) .collect(); self.typ.substitute(&substitutions) @@ -382,7 +442,7 @@ impl TypeAlias { /// which is expected to be a numeric generic. /// This is needed because we infer type kinds in Noir and don't have extensive kind checking. pub fn generic_is_numeric(&self, index_of_generic: usize) -> bool { - let target_id = self.generics[index_of_generic].0; + let target_id = self.generics[index_of_generic].type_var.id(); self.typ.contains_numeric_typevar(target_id) } } @@ -492,7 +552,7 @@ impl TypeVariable { TypeBinding::Unbound(id) => *id, }; - assert!(!typ.occurs(id)); + assert!(!typ.occurs(id), "{self:?} occurs within {typ:?}"); *self.1.borrow_mut() = TypeBinding::Bound(typ); } @@ -610,6 +670,10 @@ impl Type { matches!(self.follow_bindings(), Type::Bool) } + pub fn is_integer(&self) -> bool { + matches!(self.follow_bindings(), Type::Integer(_, _)) + } + pub fn is_signed(&self) -> bool { matches!(self.follow_bindings(), Type::Integer(Signedness::Signed, _)) } @@ -630,7 +694,7 @@ impl Type { fn contains_numeric_typevar(&self, target_id: TypeVariableId) -> bool { // True if the given type is a NamedGeneric with the target_id let named_generic_id_matches_target = |typ: &Type| { - if let Type::NamedGeneric(type_variable, _) = typ { + if let Type::NamedGeneric(type_variable, _, _) = typ { match &*type_variable.borrow() { TypeBinding::Bound(_) => { unreachable!("Named generics should not be bound until monomorphization") @@ -650,7 +714,7 @@ impl Type { | Type::Error | Type::TypeVariable(_, _) | Type::Constant(_) - | Type::NamedGeneric(_, _) + | Type::NamedGeneric(_, _, _) | Type::Forall(_, _) | Type::Quoted(_) => false, @@ -694,6 +758,85 @@ impl Type { } } + /// TODO(https://github.com/noir-lang/noir/issues/5156): Remove with explicit numeric generics + pub fn find_numeric_type_vars(&self, found_names: &mut Vec) { + // Return whether the named generic has a TypeKind::Numeric and save its name + let named_generic_is_numeric = |typ: &Type, found_names: &mut Vec| { + if let Type::NamedGeneric(_, name, Kind::Numeric { .. }) = typ { + found_names.push(name.to_string()); + true + } else { + false + } + }; + + match self { + Type::FieldElement + | Type::Integer(_, _) + | Type::Bool + | Type::Unit + | Type::Error + | Type::Constant(_) + | Type::Forall(_, _) + | Type::Quoted(_) => {} + + Type::TypeVariable(type_var, _) => { + if let TypeBinding::Bound(typ) = &*type_var.borrow() { + named_generic_is_numeric(typ, found_names); + } + } + + Type::NamedGeneric(_, _, _) => { + named_generic_is_numeric(self, found_names); + } + + Type::TraitAsType(_, _, args) => { + for arg in args.iter() { + arg.find_numeric_type_vars(found_names); + } + } + Type::Array(length, elem) => { + elem.find_numeric_type_vars(found_names); + named_generic_is_numeric(length, found_names); + } + Type::Slice(elem) => elem.find_numeric_type_vars(found_names), + Type::Tuple(fields) => { + for field in fields.iter() { + field.find_numeric_type_vars(found_names); + } + } + Type::Function(parameters, return_type, env) => { + for parameter in parameters.iter() { + parameter.find_numeric_type_vars(found_names); + } + return_type.find_numeric_type_vars(found_names); + env.find_numeric_type_vars(found_names); + } + Type::Struct(_, generics) => { + for generic in generics.iter() { + if !named_generic_is_numeric(generic, found_names) { + generic.find_numeric_type_vars(found_names); + } + } + } + Type::Alias(_, generics) => { + for generic in generics.iter() { + if !named_generic_is_numeric(generic, found_names) { + generic.find_numeric_type_vars(found_names); + } + } + } + Type::MutableReference(element) => element.find_numeric_type_vars(found_names), + Type::String(length) => { + named_generic_is_numeric(length, found_names); + } + Type::FmtString(length, elements) => { + elements.find_numeric_type_vars(found_names); + named_generic_is_numeric(length, found_names); + } + } + } + /// True if this type can be used as a parameter to `main` or a contract function. /// This is only false for unsized types like slices or slices that do not make sense /// as a program input such as named generics or mutable references. @@ -714,7 +857,7 @@ impl Type { Type::FmtString(_, _) | Type::TypeVariable(_, _) - | Type::NamedGeneric(_, _) + | Type::NamedGeneric(_, _, _) | Type::Function(_, _, _) | Type::MutableReference(_) | Type::Forall(_, _) @@ -756,7 +899,7 @@ impl Type { | Type::Unit | Type::Constant(_) | Type::TypeVariable(_, _) - | Type::NamedGeneric(_, _) + | Type::NamedGeneric(_, _, _) | Type::Error => true, Type::FmtString(_, _) @@ -788,12 +931,55 @@ impl Type { } } + /// Returns true if a value of this type can safely pass between constrained and + /// unconstrained functions (and vice-versa). + pub(crate) fn is_valid_for_unconstrained_boundary(&self) -> bool { + match self { + Type::FieldElement + | Type::Integer(_, _) + | Type::Bool + | Type::Unit + | Type::Constant(_) + | Type::Slice(_) + | Type::TypeVariable(_, _) + | Type::NamedGeneric(_, _, _) + | Type::Function(_, _, _) + | Type::FmtString(_, _) + | Type::Error => true, + + // Quoted objects only exist at compile-time where the only execution + // environment is the interpreter. In this environment, they are valid. + Type::Quoted(_) => true, + + Type::MutableReference(_) | Type::Forall(_, _) | Type::TraitAsType(..) => false, + + Type::Alias(alias, generics) => { + let alias = alias.borrow(); + alias.get_type(generics).is_valid_for_unconstrained_boundary() + } + + Type::Array(length, element) => { + length.is_valid_for_unconstrained_boundary() + && element.is_valid_for_unconstrained_boundary() + } + Type::String(length) => length.is_valid_for_unconstrained_boundary(), + Type::Tuple(elements) => { + elements.iter().all(|elem| elem.is_valid_for_unconstrained_boundary()) + } + Type::Struct(definition, generics) => definition + .borrow() + .get_fields(generics) + .into_iter() + .all(|(_, field)| field.is_valid_for_unconstrained_boundary()), + } + } + /// Returns the number of `Forall`-quantified type variables on this type. /// Returns 0 if this is not a Type::Forall pub fn generic_count(&self) -> usize { match self { Type::Forall(generics, _) => generics.len(), - Type::TypeVariable(type_variable, _) | Type::NamedGeneric(type_variable, _) => { + Type::TypeVariable(type_variable, _) | Type::NamedGeneric(type_variable, _, _) => { match &*type_variable.borrow() { TypeBinding::Bound(binding) => binding.generic_count(), TypeBinding::Unbound(_) => 0, @@ -822,12 +1008,42 @@ impl Type { /// Return the generics and type within this `Type::Forall`. /// Panics if `self` is not `Type::Forall` - pub fn unwrap_forall(&self) -> (Cow, &Type) { + pub fn unwrap_forall(&self) -> (Cow, &Type) { match self { Type::Forall(generics, typ) => (Cow::Borrowed(generics), typ.as_ref()), - other => (Cow::Owned(Generics::new()), other), + other => (Cow::Owned(GenericTypeVars::new()), other), } } + + // TODO(https://github.com/noir-lang/noir/issues/5156): Bring back this method when we remove implicit numeric generics + // It has been commented out as to not trigger clippy for an unused method + // pub(crate) fn kind(&self) -> Kind { + // match self { + // Type::NamedGeneric(_, _, kind) => kind.clone(), + // Type::Constant(_) => Kind::Numeric(Box::new(Type::Integer( + // Signedness::Unsigned, + // IntegerBitSize::ThirtyTwo, + // ))), + // Type::FieldElement + // | Type::Array(_, _) + // | Type::Slice(_) + // | Type::Integer(_, _) + // | Type::Bool + // | Type::String(_) + // | Type::FmtString(_, _) + // | Type::Unit + // | Type::Tuple(_) + // | Type::Struct(_, _) + // | Type::Alias(_, _) + // | Type::TypeVariable(_, _) + // | Type::TraitAsType(_, _, _) + // | Type::Function(_, _, _) + // | Type::MutableReference(_) + // | Type::Forall(_, _) + // | Type::Quoted(_) + // | Type::Error => Kind::Normal, + // } + // } } impl std::fmt::Display for Type { @@ -907,7 +1123,7 @@ impl std::fmt::Display for Type { } Type::Unit => write!(f, "()"), Type::Error => write!(f, "error"), - Type::NamedGeneric(binding, name) => match &*binding.borrow() { + Type::NamedGeneric(binding, name, _) => match &*binding.borrow() { TypeBinding::Bound(binding) => binding.fmt(f), TypeBinding::Unbound(_) if name.is_empty() => write!(f, "_"), TypeBinding::Unbound(_) => write!(f, "{name}"), @@ -966,9 +1182,10 @@ impl std::fmt::Display for QuotedType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { QuotedType::Expr => write!(f, "Expr"), - QuotedType::TypeDefinition => write!(f, "TypeDefinition"), + QuotedType::Quoted => write!(f, "Quoted"), QuotedType::TopLevelItem => write!(f, "TopLevelItem"), QuotedType::Type => write!(f, "Type"), + QuotedType::StructDefinition => write!(f, "StructDefinition"), } } } @@ -1135,8 +1352,7 @@ impl Type { TypeBinding::Unbound(id) => *id, }; - let this = self.substitute(bindings); - + let this = self.substitute(bindings).follow_bindings(); if let Some(binding) = this.get_inner_type_variable() { match &*binding.borrow() { TypeBinding::Bound(typ) => return typ.try_bind_to(var, bindings), @@ -1158,7 +1374,7 @@ impl Type { fn get_inner_type_variable(&self) -> Option> { match self { - Type::TypeVariable(var, _) | Type::NamedGeneric(var, _) => Some(var.1.clone()), + Type::TypeVariable(var, _) | Type::NamedGeneric(var, _, _) => Some(var.1.clone()), _ => None, } } @@ -1269,7 +1485,7 @@ impl Type { } } - (NamedGeneric(binding, _), other) | (other, NamedGeneric(binding, _)) + (NamedGeneric(binding, _, _), other) | (other, NamedGeneric(binding, _, _)) if !binding.borrow().is_unbound() => { if let TypeBinding::Bound(link) = &*binding.borrow() { @@ -1279,7 +1495,7 @@ impl Type { } } - (NamedGeneric(binding_a, name_a), NamedGeneric(binding_b, name_b)) => { + (NamedGeneric(binding_a, name_a, _), NamedGeneric(binding_b, name_b, _)) => { // Bound NamedGenerics are caught by the check above assert!(binding_a.borrow().is_unbound()); assert!(binding_b.borrow().is_unbound()); @@ -1535,6 +1751,15 @@ impl Type { } } + fn type_variable_id(&self) -> Option { + match self { + Type::TypeVariable(variable, _) | Type::NamedGeneric(variable, _, _) => { + Some(variable.0) + } + _ => None, + } + } + /// Substitute any type variables found within this type with the /// given bindings if found. If a type variable is not found within /// the given TypeBindings, it is unchanged. @@ -1569,18 +1794,29 @@ impl Type { return self.clone(); } + let recur_on_binding = |id, replacement: &Type| { + // Prevent recuring forever if there's a `T := T` binding + if replacement.type_variable_id() == Some(id) { + replacement.clone() + } else { + replacement.substitute_helper(type_bindings, substitute_bound_typevars) + } + }; + let substitute_binding = |binding: &TypeVariable| { // Check the id first to allow substituting to // type variables that have already been bound over. // This is needed for monomorphizing trait impl methods. match type_bindings.get(&binding.0) { - Some((_, binding)) if substitute_bound_typevars => binding.clone(), + Some((_, replacement)) if substitute_bound_typevars => { + recur_on_binding(binding.0, replacement) + } _ => match &*binding.borrow() { TypeBinding::Bound(binding) => { binding.substitute_helper(type_bindings, substitute_bound_typevars) } TypeBinding::Unbound(id) => match type_bindings.get(id) { - Some((_, binding)) => binding.clone(), + Some((_, replacement)) => recur_on_binding(binding.0, replacement), None => self.clone(), }, }, @@ -1606,7 +1842,7 @@ impl Type { let fields = fields.substitute_helper(type_bindings, substitute_bound_typevars); Type::FmtString(Box::new(size), Box::new(fields)) } - Type::NamedGeneric(binding, _) | Type::TypeVariable(binding, _) => { + Type::NamedGeneric(binding, _, _) | Type::TypeVariable(binding, _) => { substitute_binding(binding) } // Do not substitute_helper fields, it can lead to infinite recursion @@ -1684,7 +1920,7 @@ impl Type { generic_args.iter().any(|arg| arg.occurs(target_id)) } Type::Tuple(fields) => fields.iter().any(|field| field.occurs(target_id)), - Type::NamedGeneric(binding, _) | Type::TypeVariable(binding, _) => { + Type::NamedGeneric(binding, _, _) | Type::TypeVariable(binding, _) => { match &*binding.borrow() { TypeBinding::Bound(binding) => binding.occurs(target_id), TypeBinding::Unbound(id) => *id == target_id, @@ -1739,7 +1975,7 @@ impl Type { def.borrow().get_type(args).follow_bindings() } Tuple(args) => Tuple(vecmap(args, |arg| arg.follow_bindings())), - TypeVariable(var, _) | NamedGeneric(var, _) => { + TypeVariable(var, _) | NamedGeneric(var, _, _) => { if let TypeBinding::Bound(typ) = &*var.borrow() { return typ.follow_bindings(); } @@ -1768,7 +2004,7 @@ impl Type { } } - pub fn from_generics(generics: &Generics) -> Vec { + pub fn from_generics(generics: &GenericTypeVars) -> Vec { vecmap(generics, |var| Type::TypeVariable(var.clone(), TypeVariableKind::Normal)) } } @@ -1965,7 +2201,14 @@ impl std::fmt::Debug for Type { } Type::Unit => write!(f, "()"), Type::Error => write!(f, "error"), - Type::NamedGeneric(binding, name) => write!(f, "{}{:?}", name, binding), + Type::NamedGeneric(binding, name, kind) => match kind { + Kind::Normal => { + write!(f, "{} -> {:?}", name, binding) + } + Kind::Numeric(typ) => { + write!(f, "({} : {}) -> {:?}", name, typ, binding) + } + }, Type::Constant(x) => x.fmt(f), Type::Forall(typevars, typ) => { let typevars = vecmap(typevars, |var| format!("{:?}", var)); diff --git a/compiler/noirc_frontend/src/lexer/errors.rs b/compiler/noirc_frontend/src/lexer/errors.rs index 73c75af4cd7..387ced05258 100644 --- a/compiler/noirc_frontend/src/lexer/errors.rs +++ b/compiler/noirc_frontend/src/lexer/errors.rs @@ -15,6 +15,8 @@ pub enum LexerErrorKind { NotADoubleChar { span: Span, found: Token }, #[error("Invalid integer literal, {:?} is not a integer", found)] InvalidIntegerLiteral { span: Span, found: String }, + #[error("Integer literal is too large")] + IntegerLiteralTooLarge { span: Span, limit: String }, #[error("{:?} is not a valid attribute", found)] MalformedFuncAttribute { span: Span, found: String }, #[error("Logical and used instead of bitwise and")] @@ -27,6 +29,10 @@ pub enum LexerErrorKind { "'\\{escaped}' is not a valid escape sequence. Use '\\' for a literal backslash character." )] InvalidEscape { escaped: char, span: Span }, + #[error("Invalid quote delimiter `{delimiter}`, valid delimiters are `{{`, `[`, and `(`")] + InvalidQuoteDelimiter { delimiter: SpannedToken }, + #[error("Expected `{end_delim}` to close this {start_delim}")] + UnclosedQuote { start_delim: SpannedToken, end_delim: Token }, } impl From for ParserError { @@ -42,11 +48,14 @@ impl LexerErrorKind { LexerErrorKind::UnexpectedCharacter { span, .. } => *span, LexerErrorKind::NotADoubleChar { span, .. } => *span, LexerErrorKind::InvalidIntegerLiteral { span, .. } => *span, + LexerErrorKind::IntegerLiteralTooLarge { span, .. } => *span, LexerErrorKind::MalformedFuncAttribute { span, .. } => *span, LexerErrorKind::LogicalAnd { span } => *span, LexerErrorKind::UnterminatedBlockComment { span } => *span, LexerErrorKind::UnterminatedStringLiteral { span } => *span, LexerErrorKind::InvalidEscape { span, .. } => *span, + LexerErrorKind::InvalidQuoteDelimiter { delimiter } => delimiter.to_span(), + LexerErrorKind::UnclosedQuote { start_delim, .. } => start_delim.to_span(), } } @@ -77,6 +86,11 @@ impl LexerErrorKind { format!(" {found} is not an integer"), *span, ), + LexerErrorKind::IntegerLiteralTooLarge { span, limit } => ( + "Integer literal is too large".to_string(), + format!("value exceeds limit of {limit}"), + *span, + ), LexerErrorKind::MalformedFuncAttribute { span, found } => ( "Malformed function attribute".to_string(), format!(" {found} is not a valid attribute"), @@ -92,6 +106,12 @@ impl LexerErrorKind { ("Unterminated string literal".to_string(), "Unterminated string literal".to_string(), *span), LexerErrorKind::InvalidEscape { escaped, span } => (format!("'\\{escaped}' is not a valid escape sequence. Use '\\' for a literal backslash character."), "Invalid escape sequence".to_string(), *span), + LexerErrorKind::InvalidQuoteDelimiter { delimiter } => { + (format!("Invalid quote delimiter `{delimiter}`"), "Valid delimiters are `{`, `[`, and `(`".to_string(), delimiter.to_span()) + }, + LexerErrorKind::UnclosedQuote { start_delim, end_delim } => { + ("Unclosed `quote` expression".to_string(), format!("Expected a `{end_delim}` to close this `{start_delim}`"), start_delim.to_span()) + } } } } diff --git a/compiler/noirc_frontend/src/lexer/lexer.rs b/compiler/noirc_frontend/src/lexer/lexer.rs index 3d052e22e36..0afcb02caac 100644 --- a/compiler/noirc_frontend/src/lexer/lexer.rs +++ b/compiler/noirc_frontend/src/lexer/lexer.rs @@ -6,9 +6,11 @@ use super::{ token_to_borrowed_token, BorrowedToken, IntType, Keyword, SpannedToken, Token, Tokens, }, }; -use acvm::FieldElement; +use acvm::{AcirField, FieldElement}; use noirc_errors::{Position, Span}; -use std::str::CharIndices; +use num_bigint::BigInt; +use num_traits::{Num, One}; +use std::str::{CharIndices, FromStr}; /// The job of the lexer is to transform an iterator of characters (`char_iter`) /// into an iterator of `SpannedToken`. Each `Token` corresponds roughly to 1 word or operator. @@ -19,6 +21,7 @@ pub struct Lexer<'a> { done: bool, skip_comments: bool, skip_whitespaces: bool, + max_integer: BigInt, } pub type SpannedTokenResult = Result; @@ -61,6 +64,8 @@ impl<'a> Lexer<'a> { done: false, skip_comments: true, skip_whitespaces: true, + max_integer: BigInt::from_biguint(num_bigint::Sign::Plus, FieldElement::modulus()) + - BigInt::one(), } } @@ -145,6 +150,7 @@ impl<'a> Lexer<'a> { Some('"') => self.eat_string_literal(), Some('f') => self.eat_format_string_or_alpha_numeric(), Some('r') => self.eat_raw_string_or_alpha_numeric(), + Some('q') => self.eat_quote_or_alpha_numeric(), Some('#') => self.eat_attribute(), Some(ch) if ch.is_ascii_alphanumeric() || ch == '_' => self.eat_alpha_numeric(ch), Some(ch) => { @@ -310,14 +316,25 @@ impl<'a> Lexer<'a> { //XXX(low): Can increase performance if we use iterator semantic and utilize some of the methods on String. See below // https://doc.rust-lang.org/stable/std/primitive.str.html#method.rsplit fn eat_word(&mut self, initial_char: char) -> SpannedTokenResult { - let start = self.position; + let (start, word, end) = self.lex_word(initial_char); + self.lookup_word_token(word, start, end) + } + /// Lex the next word in the input stream. Returns (start position, word, end position) + fn lex_word(&mut self, initial_char: char) -> (Position, String, Position) { + let start = self.position; let word = self.eat_while(Some(initial_char), |ch| { ch.is_ascii_alphabetic() || ch.is_numeric() || ch == '_' }); + (start, word, self.position) + } - let end = self.position; - + fn lookup_word_token( + &self, + word: String, + start: Position, + end: Position, + ) -> SpannedTokenResult { // Check if word either an identifier or a keyword if let Some(keyword_token) = Keyword::lookup_keyword(&word) { return Ok(keyword_token.into_span(start, end)); @@ -364,14 +381,28 @@ impl<'a> Lexer<'a> { // Underscores needs to be stripped out before the literal can be converted to a `FieldElement. let integer_str = integer_str.replace('_', ""); - let integer = match FieldElement::try_from_str(&integer_str) { - None => { + let bigint_result = match integer_str.strip_prefix("0x") { + Some(integer_str) => BigInt::from_str_radix(integer_str, 16), + None => BigInt::from_str(&integer_str), + }; + + let integer = match bigint_result { + Ok(bigint) => { + if bigint > self.max_integer { + return Err(LexerErrorKind::IntegerLiteralTooLarge { + span: Span::inclusive(start, end), + limit: self.max_integer.to_string(), + }); + } + let big_uint = bigint.magnitude(); + FieldElement::from_be_bytes_reduce(&big_uint.to_bytes_be()) + } + Err(_) => { return Err(LexerErrorKind::InvalidIntegerLiteral { span: Span::inclusive(start, end), found: integer_str, }) } - Some(integer) => integer, }; let integer_token = Token::Int(integer); @@ -509,6 +540,50 @@ impl<'a> Lexer<'a> { } } + fn eat_quote_or_alpha_numeric(&mut self) -> SpannedTokenResult { + let (start, word, end) = self.lex_word('q'); + if word != "quote" { + return self.lookup_word_token(word, start, end); + } + + let delimiter = self.next_token()?; + let (start_delim, end_delim) = match delimiter.token() { + Token::LeftBrace => (Token::LeftBrace, Token::RightBrace), + Token::LeftBracket => (Token::LeftBracket, Token::RightBracket), + Token::LeftParen => (Token::LeftParen, Token::RightParen), + _ => return Err(LexerErrorKind::InvalidQuoteDelimiter { delimiter }), + }; + + let mut tokens = Vec::new(); + + // Keep track of each nested delimiter we need to close. + let mut nested_delimiters = vec![delimiter]; + + while !nested_delimiters.is_empty() { + let token = self.next_token()?; + + if *token.token() == start_delim { + nested_delimiters.push(token.clone()); + } else if *token.token() == end_delim { + nested_delimiters.pop(); + } else if *token.token() == Token::EOF { + let start_delim = + nested_delimiters.pop().expect("If this were empty, we wouldn't be looping"); + return Err(LexerErrorKind::UnclosedQuote { start_delim, end_delim }); + } + + tokens.push(token); + } + + // Pop the closing delimiter from the token stream + if !tokens.is_empty() { + tokens.pop(); + } + + let end = self.position; + Ok(Token::Quote(Tokens(tokens)).into_span(start, end)) + } + fn parse_comment(&mut self, start: u32) -> SpannedTokenResult { let doc_style = match self.peek_char() { Some('!') => { @@ -604,6 +679,8 @@ impl<'a> Iterator for Lexer<'a> { #[cfg(test)] mod tests { + use iter_extended::vecmap; + use super::*; use crate::token::{FunctionAttribute, SecondaryAttribute, TestScope}; @@ -841,6 +918,19 @@ mod tests { } } + #[test] + fn test_int_too_large() { + let modulus = FieldElement::modulus(); + let input = modulus.to_string(); + + let mut lexer = Lexer::new(&input); + let token = lexer.next_token(); + assert!( + matches!(token, Err(LexerErrorKind::IntegerLiteralTooLarge { .. })), + "expected {input} to throw error" + ); + } + #[test] fn test_arithmetic_sugar() { let input = "+= -= *= /= %="; @@ -1232,4 +1322,45 @@ mod tests { } } } + + #[test] + fn test_quote() { + // cases is a vector of pairs of (test string, expected # of tokens in token stream) + let cases = vec![ + ("quote {}", 0), + ("quote { a.b }", 3), + ("quote { ) ( }", 2), // invalid syntax is fine in a quote + ("quote { { } }", 2), // Nested `{` and `}` shouldn't close the quote as long as they are matched. + ("quote { 1 { 2 { 3 { 4 { 5 } 4 4 } 3 3 } 2 2 } 1 1 }", 21), + ("quote [ } } ]", 2), // In addition to `{}`, `[]`, and `()` can also be used as delimiters. + ("quote [ } foo[] } ]", 5), + ("quote ( } () } )", 4), + ]; + + for (source, expected_stream_length) in cases { + let mut tokens = vecmap(Lexer::new(source), |result| result.unwrap().into_token()); + + // All examples should be a single TokenStream token followed by an EOF token. + assert_eq!(tokens.len(), 2, "Unexpected token count: {tokens:?}"); + + tokens.pop(); + match tokens.pop().unwrap() { + Token::Quote(stream) => assert_eq!(stream.0.len(), expected_stream_length), + other => panic!("test_quote test failure! Expected a single TokenStream token, got {other} for input `{source}`") + } + } + } + + #[test] + fn test_unclosed_quote() { + let cases = vec!["quote {", "quote { { }", "quote [ []", "quote (((((((())))"]; + + for source in cases { + // `quote` is not itself a keyword so if the token stream fails to + // parse we don't expect any valid tokens from the quote construct + for token in Lexer::new(source) { + assert!(token.is_err(), "Expected Err, found {token:?}"); + } + } + } } diff --git a/compiler/noirc_frontend/src/lexer/token.rs b/compiler/noirc_frontend/src/lexer/token.rs index ec589a816f6..41de13fb17e 100644 --- a/compiler/noirc_frontend/src/lexer/token.rs +++ b/compiler/noirc_frontend/src/lexer/token.rs @@ -2,7 +2,10 @@ use acvm::{acir::AcirField, FieldElement}; use noirc_errors::{Position, Span, Spanned}; use std::{fmt, iter::Map, vec::IntoIter}; -use crate::lexer::errors::LexerErrorKind; +use crate::{ + lexer::errors::LexerErrorKind, + node_interner::{ExprId, QuotedTypeId}, +}; /// Represents a token in noir's grammar - a word, number, /// or symbol that can be used in noir's syntax. This is the @@ -23,6 +26,8 @@ pub enum BorrowedToken<'input> { Attribute(Attribute), LineComment(&'input str, Option), BlockComment(&'input str, Option), + Quote(&'input Tokens), + QuotedType(QuotedTypeId), /// < Less, /// <= @@ -94,6 +99,11 @@ pub enum BorrowedToken<'input> { Whitespace(&'input str), + /// This is an implementation detail on how macros are implemented by quoting token streams. + /// This token marks where an unquote operation is performed. The ExprId argument is the + /// resolved variable which is being unquoted at this position in the token stream. + UnquoteMarker(ExprId), + /// An invalid character is one that is not in noir's language or grammar. /// /// We don't report invalid tokens in the source as errors until parsing to @@ -117,6 +127,13 @@ pub enum Token { Attribute(Attribute), LineComment(String, Option), BlockComment(String, Option), + // A `quote { ... }` along with the tokens in its token stream. + Quote(Tokens), + /// A quoted type resulting from a `Type` object in noir code being + /// spliced into a macro's token stream. We preserve the original type + /// to avoid having to tokenize it, re-parse it, and re-resolve it which + /// may change the underlying type. + QuotedType(QuotedTypeId), /// < Less, /// <= @@ -188,6 +205,11 @@ pub enum Token { Whitespace(String), + /// This is an implementation detail on how macros are implemented by quoting token streams. + /// This token marks where an unquote operation is performed. The ExprId argument is the + /// resolved variable which is being unquoted at this position in the token stream. + UnquoteMarker(ExprId), + /// An invalid character is one that is not in noir's language or grammar. /// /// We don't report invalid tokens in the source as errors until parsing to @@ -209,6 +231,8 @@ pub fn token_to_borrowed_token(token: &Token) -> BorrowedToken<'_> { Token::Attribute(ref a) => BorrowedToken::Attribute(a.clone()), Token::LineComment(ref s, _style) => BorrowedToken::LineComment(s, *_style), Token::BlockComment(ref s, _style) => BorrowedToken::BlockComment(s, *_style), + Token::Quote(stream) => BorrowedToken::Quote(stream), + Token::QuotedType(id) => BorrowedToken::QuotedType(*id), Token::IntType(ref i) => BorrowedToken::IntType(i.clone()), Token::Less => BorrowedToken::Less, Token::LessEqual => BorrowedToken::LessEqual, @@ -246,6 +270,7 @@ pub fn token_to_borrowed_token(token: &Token) -> BorrowedToken<'_> { Token::EOF => BorrowedToken::EOF, Token::Invalid(c) => BorrowedToken::Invalid(*c), Token::Whitespace(ref s) => BorrowedToken::Whitespace(s), + Token::UnquoteMarker(id) => BorrowedToken::UnquoteMarker(*id), } } @@ -255,7 +280,7 @@ pub enum DocStyle { Inner, } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct SpannedToken(Spanned); impl PartialEq for Token { @@ -321,6 +346,15 @@ impl fmt::Display for Token { Token::Attribute(ref a) => write!(f, "{a}"), Token::LineComment(ref s, _style) => write!(f, "//{s}"), Token::BlockComment(ref s, _style) => write!(f, "/*{s}*/"), + Token::Quote(ref stream) => { + write!(f, "quote {{")?; + for token in stream.0.iter() { + write!(f, " {token}")?; + } + write!(f, "}}") + } + // Quoted types only have an ID so there is nothing to display + Token::QuotedType(_) => write!(f, "(type)"), Token::IntType(ref i) => write!(f, "{i}"), Token::Less => write!(f, "<"), Token::LessEqual => write!(f, "<="), @@ -358,6 +392,7 @@ impl fmt::Display for Token { Token::EOF => write!(f, "end of input"), Token::Invalid(c) => write!(f, "{c}"), Token::Whitespace(ref s) => write!(f, "{s}"), + Token::UnquoteMarker(_) => write!(f, "(UnquoteMarker)"), } } } @@ -370,6 +405,9 @@ pub enum TokenKind { Literal, Keyword, Attribute, + Quote, + QuotedType, + UnquoteMarker, } impl fmt::Display for TokenKind { @@ -380,13 +418,16 @@ impl fmt::Display for TokenKind { TokenKind::Literal => write!(f, "literal"), TokenKind::Keyword => write!(f, "keyword"), TokenKind::Attribute => write!(f, "attribute"), + TokenKind::Quote => write!(f, "quote"), + TokenKind::QuotedType => write!(f, "quoted type"), + TokenKind::UnquoteMarker => write!(f, "macro result"), } } } impl Token { pub fn kind(&self) -> TokenKind { - match *self { + match self { Token::Ident(_) => TokenKind::Ident, Token::Int(_) | Token::Bool(_) @@ -395,7 +436,10 @@ impl Token { | Token::FmtStr(_) => TokenKind::Literal, Token::Keyword(_) => TokenKind::Keyword, Token::Attribute(_) => TokenKind::Attribute, - ref tok => TokenKind::Token(tok.clone()), + Token::UnquoteMarker(_) => TokenKind::UnquoteMarker, + Token::Quote(_) => TokenKind::Quote, + Token::QuotedType(_) => TokenKind::QuotedType, + tok => TokenKind::Token(tok.clone()), } } @@ -859,7 +903,7 @@ pub enum Keyword { Mod, Mut, Pub, - Quote, + Quoted, Return, ReturnData, String, @@ -869,7 +913,7 @@ pub enum Keyword { Trait, Type, TypeType, - TypeDefinition, + StructDefinition, Unchecked, Unconstrained, Use, @@ -907,7 +951,7 @@ impl fmt::Display for Keyword { Keyword::Mod => write!(f, "mod"), Keyword::Mut => write!(f, "mut"), Keyword::Pub => write!(f, "pub"), - Keyword::Quote => write!(f, "quote"), + Keyword::Quoted => write!(f, "Quoted"), Keyword::Return => write!(f, "return"), Keyword::ReturnData => write!(f, "return_data"), Keyword::String => write!(f, "str"), @@ -917,7 +961,7 @@ impl fmt::Display for Keyword { Keyword::Trait => write!(f, "trait"), Keyword::Type => write!(f, "type"), Keyword::TypeType => write!(f, "Type"), - Keyword::TypeDefinition => write!(f, "TypeDefinition"), + Keyword::StructDefinition => write!(f, "StructDefinition"), Keyword::Unchecked => write!(f, "unchecked"), Keyword::Unconstrained => write!(f, "unconstrained"), Keyword::Use => write!(f, "use"), @@ -958,7 +1002,7 @@ impl Keyword { "mod" => Keyword::Mod, "mut" => Keyword::Mut, "pub" => Keyword::Pub, - "quote" => Keyword::Quote, + "Quoted" => Keyword::Quoted, "return" => Keyword::Return, "return_data" => Keyword::ReturnData, "str" => Keyword::String, @@ -968,7 +1012,7 @@ impl Keyword { "trait" => Keyword::Trait, "type" => Keyword::Type, "Type" => Keyword::TypeType, - "TypeDefinition" => Keyword::TypeDefinition, + "StructDefinition" => Keyword::StructDefinition, "unchecked" => Keyword::Unchecked, "unconstrained" => Keyword::Unconstrained, "use" => Keyword::Use, @@ -984,6 +1028,7 @@ impl Keyword { } } +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Tokens(pub Vec); type TokenMapIter = Map, fn(SpannedToken) -> (Token, Span)>; diff --git a/compiler/noirc_frontend/src/lib.rs b/compiler/noirc_frontend/src/lib.rs index b05c635f436..b14f65a3e35 100644 --- a/compiler/noirc_frontend/src/lib.rs +++ b/compiler/noirc_frontend/src/lib.rs @@ -15,6 +15,7 @@ pub mod debug; pub mod elaborator; pub mod graph; pub mod lexer; +pub mod locations; pub mod monomorphization; pub mod node_interner; pub mod parser; diff --git a/compiler/noirc_frontend/src/locations.rs b/compiler/noirc_frontend/src/locations.rs new file mode 100644 index 00000000000..0efe385aa0a --- /dev/null +++ b/compiler/noirc_frontend/src/locations.rs @@ -0,0 +1,174 @@ +use fm::FileId; +use noirc_errors::Location; +use rangemap::RangeMap; +use rustc_hash::FxHashMap; + +use crate::{macros_api::NodeInterner, node_interner::ReferenceId}; +use petgraph::prelude::NodeIndex as PetGraphIndex; + +#[derive(Debug, Default)] +pub(crate) struct LocationIndices { + map_file_to_range: FxHashMap>, +} + +impl LocationIndices { + pub(crate) fn add_location(&mut self, location: Location, node_index: PetGraphIndex) { + // Some location spans are empty: maybe they are from ficticious nodes? + if location.span.start() == location.span.end() { + return; + } + + let range_map = self.map_file_to_range.entry(location.file).or_default(); + range_map.insert(location.span.start()..location.span.end(), node_index); + } + + pub(crate) fn get_node_from_location(&self, location: Location) -> Option { + let range_map = self.map_file_to_range.get(&location.file)?; + Some(*range_map.get(&location.span.start())?) + } +} + +impl NodeInterner { + pub fn reference_location(&self, reference: ReferenceId) -> Location { + match reference { + ReferenceId::Module(id) => self.module_location(&id), + ReferenceId::Function(id) => self.function_modifiers(&id).name_location, + ReferenceId::Struct(id) => { + let struct_type = self.get_struct(id); + let struct_type = struct_type.borrow(); + Location::new(struct_type.name.span(), struct_type.location.file) + } + ReferenceId::StructMember(id, field_index) => { + let struct_type = self.get_struct(id); + let struct_type = struct_type.borrow(); + Location::new(struct_type.field_at(field_index).0.span(), struct_type.location.file) + } + ReferenceId::Trait(id) => { + let trait_type = self.get_trait(id); + Location::new(trait_type.name.span(), trait_type.location.file) + } + ReferenceId::Global(id) => self.get_global(id).location, + ReferenceId::Alias(id) => { + let alias_type = self.get_type_alias(id); + let alias_type = alias_type.borrow(); + Location::new(alias_type.name.span(), alias_type.location.file) + } + ReferenceId::Local(id) => self.definition(id).location, + ReferenceId::Reference(location, _) => location, + } + } + + pub(crate) fn add_reference(&mut self, referenced: ReferenceId, reference: ReferenceId) { + if !self.track_references { + return; + } + + let referenced_index = self.get_or_insert_reference(referenced); + let reference_location = self.reference_location(reference); + let reference_index = self.reference_graph.add_node(reference); + + self.reference_graph.add_edge(reference_index, referenced_index, ()); + self.location_indices.add_location(reference_location, reference_index); + } + + pub(crate) fn add_definition_location(&mut self, referenced: ReferenceId) { + if !self.track_references { + return; + } + + let referenced_index = self.get_or_insert_reference(referenced); + let referenced_location = self.reference_location(referenced); + self.location_indices.add_location(referenced_location, referenced_index); + } + + #[tracing::instrument(skip(self), ret)] + pub(crate) fn get_or_insert_reference(&mut self, id: ReferenceId) -> PetGraphIndex { + if let Some(index) = self.reference_graph_indices.get(&id) { + return *index; + } + + let index = self.reference_graph.add_node(id); + self.reference_graph_indices.insert(id, index); + index + } + + // Given a reference location, find the location of the referenced node. + pub fn find_referenced_location(&self, reference_location: Location) -> Option { + self.location_indices + .get_node_from_location(reference_location) + .and_then(|node_index| self.referenced_index(node_index)) + .map(|node_index| self.reference_location(self.reference_graph[node_index])) + } + + // Returns the `ReferenceId` that exists at a given location, if any. + pub fn reference_at_location(&self, location: Location) -> Option { + self.location_indices.get_node_from_location(location)?; + + let node_index = self.location_indices.get_node_from_location(location)?; + Some(self.reference_graph[node_index]) + } + + // Starting at the given location, find the node referenced by it. Then, gather + // all locations that reference that node, and return all of them + // (the references and optionally the referenced node if `include_referencedd` is true). + // If `include_self_type_name` is true, references where "Self" is written are returned, + // otherwise they are not. + // Returns `None` if the location is not known to this interner. + pub fn find_all_references( + &self, + location: Location, + include_referenced: bool, + include_self_type_name: bool, + ) -> Option> { + let node_index = self.location_indices.get_node_from_location(location)?; + + let reference_node = self.reference_graph[node_index]; + let referenced_node_index = if let ReferenceId::Reference(_, _) = reference_node { + self.referenced_index(node_index)? + } else { + node_index + }; + + let found_locations = self.find_all_references_for_index( + referenced_node_index, + include_referenced, + include_self_type_name, + ); + + Some(found_locations) + } + + // Given a referenced node index, find all references to it and return their locations, optionally together + // with the reference node's location if `include_referenced` is true. + // If `include_self_type_name` is true, references where "Self" is written are returned, + // otherwise they are not. + fn find_all_references_for_index( + &self, + referenced_node_index: PetGraphIndex, + include_referenced: bool, + include_self_type_name: bool, + ) -> Vec { + let id = self.reference_graph[referenced_node_index]; + let mut edit_locations = Vec::new(); + if include_referenced && (include_self_type_name || !id.is_self_type_name()) { + edit_locations.push(self.reference_location(id)); + } + + self.reference_graph + .neighbors_directed(referenced_node_index, petgraph::Direction::Incoming) + .for_each(|reference_node_index| { + let id = self.reference_graph[reference_node_index]; + if include_self_type_name || !id.is_self_type_name() { + edit_locations.push(self.reference_location(id)); + } + }); + edit_locations + } + + // Given a reference index, returns the referenced index, if any. + fn referenced_index(&self, reference_index: PetGraphIndex) -> Option { + self.reference_graph + .neighbors_directed(reference_index, petgraph::Direction::Outgoing) + .next() + } +} diff --git a/compiler/noirc_frontend/src/monomorphization/ast.rs b/compiler/noirc_frontend/src/monomorphization/ast.rs index 99ac08ddfc2..47698c5b65c 100644 --- a/compiler/noirc_frontend/src/monomorphization/ast.rs +++ b/compiler/noirc_frontend/src/monomorphization/ast.rs @@ -93,7 +93,7 @@ pub struct For { pub enum Literal { Array(ArrayLiteral), Slice(ArrayLiteral), - Integer(FieldElement, Type, Location), + Integer(FieldElement, /*sign*/ bool, Type, Location), // false for positive integer and true for negative Bool(bool), Unit, Str(String), diff --git a/compiler/noirc_frontend/src/monomorphization/errors.rs b/compiler/noirc_frontend/src/monomorphization/errors.rs index 2db570540d6..df61c138c02 100644 --- a/compiler/noirc_frontend/src/monomorphization/errors.rs +++ b/compiler/noirc_frontend/src/monomorphization/errors.rs @@ -6,6 +6,7 @@ use crate::hir::comptime::InterpreterError; pub enum MonomorphizationError { UnknownArrayLength { location: Location }, TypeAnnotationsNeeded { location: Location }, + InternalError { message: &'static str, location: Location }, InterpreterError(InterpreterError), } @@ -13,6 +14,7 @@ impl MonomorphizationError { fn location(&self) -> Location { match self { MonomorphizationError::UnknownArrayLength { location } + | MonomorphizationError::InternalError { location, .. } | MonomorphizationError::TypeAnnotationsNeeded { location } => *location, MonomorphizationError::InterpreterError(error) => error.get_location(), } @@ -36,6 +38,7 @@ impl MonomorphizationError { } MonomorphizationError::TypeAnnotationsNeeded { .. } => "Type annotations needed", MonomorphizationError::InterpreterError(error) => return (&error).into(), + MonomorphizationError::InternalError { message, .. } => message, }; let location = self.location(); diff --git a/compiler/noirc_frontend/src/monomorphization/mod.rs b/compiler/noirc_frontend/src/monomorphization/mod.rs index 821ae7e7c4c..be222cc4e35 100644 --- a/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -9,6 +9,9 @@ //! The entry point to this pass is the `monomorphize` function which, starting from a given //! function, will monomorphize the entire reachable program. use crate::ast::{FunctionKind, IntegerBitSize, Signedness, UnaryOp, Visibility}; +use crate::hir::comptime::InterpreterError; +use crate::hir::type_check::NoMatchingImplFoundError; +use crate::node_interner::ExprId; use crate::{ debug::DebugInstrumenter, hir_def::{ @@ -69,7 +72,7 @@ struct Monomorphizer<'interner> { /// Queue of functions to monomorphize next each item in the queue is a tuple of: /// (old_id, new_monomorphized_id, any type bindings to apply, the trait method if old_id is from a trait impl) - queue: VecDeque<(node_interner::FuncId, FuncId, TypeBindings, Option)>, + queue: VecDeque<(node_interner::FuncId, FuncId, TypeBindings, Option, Location)>, /// When a function finishes being monomorphized, the monomorphized ast::Function is /// stored here along with its FuncId. @@ -121,11 +124,15 @@ pub fn monomorphize_debug( let function_sig = monomorphizer.compile_main(main)?; while !monomorphizer.queue.is_empty() { - let (next_fn_id, new_id, bindings, trait_method) = monomorphizer.queue.pop_front().unwrap(); + let (next_fn_id, new_id, bindings, trait_method, location) = + monomorphizer.queue.pop_front().unwrap(); monomorphizer.locals.clear(); perform_instantiation_bindings(&bindings); - let impl_bindings = monomorphizer.perform_impl_bindings(trait_method, next_fn_id); + let interner = &monomorphizer.interner; + let impl_bindings = perform_impl_bindings(interner, trait_method, next_fn_id, location) + .map_err(MonomorphizationError::InterpreterError)?; + monomorphizer.function(next_fn_id, new_id)?; undo_instantiation_bindings(impl_bindings); undo_instantiation_bindings(bindings); @@ -241,7 +248,9 @@ impl<'interner> Monomorphizer<'interner> { Definition::Oracle(opcode.to_string()) } FunctionKind::Recursive => { - unreachable!("Only main can be specified as recursive, which should already be checked"); + let id = + self.queue_function(id, expr_id, typ, turbofish_generics, trait_method); + Definition::Function(id) } } } @@ -417,20 +426,7 @@ impl<'interner> Monomorphizer<'interner> { HirExpression::Literal(HirLiteral::Integer(value, sign)) => { let location = self.interner.id_location(expr); let typ = Self::convert_type(&self.interner.id_type(expr), location)?; - - if sign { - match typ { - ast::Type::Field => Literal(Integer(-value, typ, location)), - ast::Type::Integer(_, bit_size) => { - let bit_size: u32 = bit_size.into(); - let base = 1_u128 << bit_size; - Literal(Integer(FieldElement::from(base) - value, typ, location)) - } - _ => unreachable!("Integer literal must be numeric"), - } - } else { - Literal(Integer(value, typ, location)) - } + Literal(Integer(value, sign, typ, location)) } HirExpression::Literal(HirLiteral::Array(array)) => match array { HirArrayLiteral::Standard(array) => self.standard_array(expr, array, false)?, @@ -448,13 +444,26 @@ impl<'interner> Monomorphizer<'interner> { HirExpression::Block(block) => self.block(block.statements)?, HirExpression::Prefix(prefix) => { + let rhs = self.expr(prefix.rhs)?; let location = self.interner.expr_location(&expr); - ast::Expression::Unary(ast::Unary { - operator: prefix.operator, - rhs: Box::new(self.expr(prefix.rhs)?), - result_type: Self::convert_type(&self.interner.id_type(expr), location)?, - location, - }) + + if self.interner.get_selected_impl_for_expression(expr).is_some() { + // If an impl was selected for this prefix operator, replace it + // with a method call to the appropriate trait impl method. + let (function_type, ret) = + self.interner.get_prefix_operator_type(expr, prefix.rhs); + + let method = prefix + .trait_method_id + .expect("ice: missing trait method if when impl was found"); + let func = self.resolve_trait_method_expr(expr, function_type, method)?; + self.create_prefix_operator_impl_call(func, rhs, ret, location)? + } else { + let operator = prefix.operator; + let rhs = Box::new(rhs); + let result_type = Self::convert_type(&self.interner.id_type(expr), location)?; + ast::Expression::Unary(ast::Unary { operator, rhs, result_type, location }) + } } HirExpression::Infix(infix) => { @@ -465,24 +474,13 @@ impl<'interner> Monomorphizer<'interner> { if self.interner.get_selected_impl_for_expression(expr).is_some() { // If an impl was selected for this infix operator, replace it // with a method call to the appropriate trait impl method. - let lhs_type = self.interner.id_type(infix.lhs); - let args = vec![lhs_type.clone(), lhs_type]; - - // If this is a comparison operator, the result is a boolean but - // the actual method call returns an Ordering - use crate::ast::BinaryOpKind::*; - let ret = if matches!(operator, Less | LessEqual | Greater | GreaterEqual) { - self.interner.ordering_type() - } else { - self.interner.id_type(expr) - }; - - let env = Box::new(Type::Unit); - let function_type = Type::Function(args, Box::new(ret.clone()), env); + let (function_type, ret) = + self.interner.get_infix_operator_type(infix.lhs, operator, expr); let method = infix.trait_method_id; - let func = self.resolve_trait_method_reference(expr, function_type, method)?; - self.create_operator_impl_call(func, lhs, infix.operator, rhs, ret, location)? + let func = self.resolve_trait_method_expr(expr, function_type, method)?; + let operator = infix.operator; + self.create_infix_operator_impl_call(func, lhs, operator, rhs, ret, location)? } else { let lhs = Box::new(lhs); let rhs = Box::new(rhs); @@ -610,6 +608,7 @@ impl<'interner> Monomorphizer<'interner> { }) .transpose()? .map(Box::new); + Ok(ast::Expression::Constrain(Box::new(expr), location, assert_message)) } HirStatement::Assign(assign) => self.assign(assign), @@ -840,7 +839,7 @@ impl<'interner> Monomorphizer<'interner> { let typ = self.interner.id_type(expr_id); if let ImplKind::TraitMethod(method, _, _) = ident.impl_kind { - return self.resolve_trait_method_reference(expr_id, typ, method); + return self.resolve_trait_method_expr(expr_id, typ, method); } let definition = self.interner.definition(ident.id); @@ -889,7 +888,11 @@ impl<'interner> Monomorphizer<'interner> { DefinitionKind::Local(_) => match self.lookup_captured_expr(ident.id) { Some(expr) => expr, None => { - let ident = self.local_ident(&ident)?.unwrap(); + let Some(ident) = self.local_ident(&ident)? else { + let location = self.interner.id_location(expr_id); + let message = "ICE: Variable not found during monomorphization"; + return Err(MonomorphizationError::InternalError { location, message }); + }; ast::Expression::Ident(ident) } }, @@ -906,7 +909,7 @@ impl<'interner> Monomorphizer<'interner> { let value = FieldElement::from(value as u128); let location = self.interner.id_location(expr_id); let typ = Self::convert_type(&typ, ident.location)?; - ast::Expression::Literal(ast::Literal::Integer(value, typ, location)) + ast::Expression::Literal(ast::Literal::Integer(value, false, typ, location)) } }; @@ -941,7 +944,7 @@ impl<'interner> Monomorphizer<'interner> { HirType::TraitAsType(..) => { unreachable!("All TraitAsType should be replaced before calling convert_type"); } - HirType::NamedGeneric(binding, _) => { + HirType::NamedGeneric(binding, _, _) => { if let TypeBinding::Bound(binding) = &*binding.borrow() { return Self::convert_type(binding, location); } @@ -1039,53 +1042,14 @@ impl<'interner> Monomorphizer<'interner> { } } - fn resolve_trait_method_reference( + fn resolve_trait_method_expr( &mut self, expr_id: node_interner::ExprId, function_type: HirType, method: TraitMethodId, ) -> Result { - let trait_impl = self - .interner - .get_selected_impl_for_expression(expr_id) - .expect("ICE: missing trait impl - should be caught during type checking"); - - let func_id = match trait_impl { - node_interner::TraitImplKind::Normal(impl_id) => { - self.interner.get_trait_implementation(impl_id).borrow().methods - [method.method_index] - } - node_interner::TraitImplKind::Assumed { object_type, trait_generics } => { - match self.interner.lookup_trait_implementation( - &object_type, - method.trait_id, - &trait_generics, - ) { - Ok(TraitImplKind::Normal(impl_id)) => { - self.interner.get_trait_implementation(impl_id).borrow().methods - [method.method_index] - } - Ok(TraitImplKind::Assumed { .. }) => unreachable!( - "There should be no remaining Assumed impls during monomorphization" - ), - Err(constraints) => { - let failed_constraints = vecmap(constraints, |constraint| { - let id = constraint.trait_id; - let mut name = self.interner.get_trait(id).name.to_string(); - if !constraint.trait_generics.is_empty() { - let types = - vecmap(&constraint.trait_generics, |t| format!("{t:?}")); - name += &format!("<{}>", types.join(", ")); - } - format!(" {}: {name}", constraint.typ) - }) - .join("\n"); - - unreachable!("Failed to find trait impl during monomorphization. The failed constraint(s) are:\n{failed_constraints}") - } - } - } - }; + let func_id = resolve_trait_method(self.interner, method, expr_id) + .map_err(MonomorphizationError::InterpreterError)?; let func_id = match self.lookup_function(func_id, expr_id, &function_type, vec![], Some(method)) { @@ -1263,7 +1227,9 @@ impl<'interner> Monomorphizer<'interner> { let bits = (FieldElement::max_num_bits() as u128).into(); let typ = ast::Type::Integer(Signedness::Unsigned, IntegerBitSize::SixtyFour); - Some(ast::Expression::Literal(ast::Literal::Integer(bits, typ, location))) + Some(ast::Expression::Literal(ast::Literal::Integer( + bits, false, typ, location, + ))) } "zeroed" => { let location = self.interner.expr_location(expr_id); @@ -1271,19 +1237,19 @@ impl<'interner> Monomorphizer<'interner> { } "modulus_le_bits" => { let bits = FieldElement::modulus().to_radix_le(2); - Some(self.modulus_array_literal(bits, IntegerBitSize::One, location)) + Some(self.modulus_slice_literal(bits, IntegerBitSize::One, location)) } "modulus_be_bits" => { let bits = FieldElement::modulus().to_radix_be(2); - Some(self.modulus_array_literal(bits, IntegerBitSize::One, location)) + Some(self.modulus_slice_literal(bits, IntegerBitSize::One, location)) } "modulus_be_bytes" => { let bytes = FieldElement::modulus().to_bytes_be(); - Some(self.modulus_array_literal(bytes, IntegerBitSize::Eight, location)) + Some(self.modulus_slice_literal(bytes, IntegerBitSize::Eight, location)) } "modulus_le_bytes" => { let bytes = FieldElement::modulus().to_bytes_le(); - Some(self.modulus_array_literal(bytes, IntegerBitSize::Eight, location)) + Some(self.modulus_slice_literal(bytes, IntegerBitSize::Eight, location)) } _ => None, }; @@ -1292,7 +1258,7 @@ impl<'interner> Monomorphizer<'interner> { None } - fn modulus_array_literal( + fn modulus_slice_literal( &self, bytes: Vec, arr_elem_bits: IntegerBitSize, @@ -1303,13 +1269,17 @@ impl<'interner> Monomorphizer<'interner> { let int_type = Type::Integer(crate::ast::Signedness::Unsigned, arr_elem_bits); let bytes_as_expr = vecmap(bytes, |byte| { - Expression::Literal(Literal::Integer((byte as u128).into(), int_type.clone(), location)) + Expression::Literal(Literal::Integer( + (byte as u128).into(), + false, + int_type.clone(), + location, + )) }); - let typ = Type::Array(bytes_as_expr.len() as u32, Box::new(int_type)); - + let typ = Type::Slice(Box::new(int_type)); let arr_literal = ArrayLiteral { typ, contents: bytes_as_expr }; - Expression::Literal(Literal::Array(arr_literal)) + Expression::Literal(Literal::Slice(arr_literal)) } fn queue_function( @@ -1323,9 +1293,10 @@ impl<'interner> Monomorphizer<'interner> { let new_id = self.next_function_id(); self.define_function(id, function_type.clone(), turbofish_generics, new_id); + let location = self.interner.expr_location(&expr_id); let bindings = self.interner.get_instantiation_bindings(expr_id); let bindings = self.follow_bindings(bindings); - self.queue.push_back((id, new_id, bindings, trait_method)); + self.queue.push_back((id, new_id, bindings, trait_method, location)); new_id } @@ -1591,7 +1562,7 @@ impl<'interner> Monomorphizer<'interner> { match typ { ast::Type::Field | ast::Type::Integer(..) => { let typ = typ.clone(); - ast::Expression::Literal(ast::Literal::Integer(0_u128.into(), typ, location)) + ast::Expression::Literal(ast::Literal::Integer(0_u128.into(), false, typ, location)) } ast::Type::Bool => ast::Expression::Literal(ast::Literal::Bool(false)), ast::Type::Unit => ast::Expression::Literal(ast::Literal::Unit), @@ -1694,12 +1665,12 @@ impl<'interner> Monomorphizer<'interner> { }) } - /// Call an operator overloading method for the given operator. + /// Call an infix operator overloading method for the given operator. /// This function handles the special cases some operators have which don't map /// 1 to 1 onto their operator function. For example: != requires a negation on /// the result of its `eq` method, and the comparison operators each require a /// conversion from the `Ordering` result to a boolean. - fn create_operator_impl_call( + fn create_infix_operator_impl_call( &self, func: ast::Expression, lhs: ast::Expression, @@ -1746,7 +1717,8 @@ impl<'interner> Monomorphizer<'interner> { let operator = if matches!(operator.kind, Less | Greater) { Equal } else { NotEqual }; - let int_value = ast::Literal::Integer(ordering_value, ast::Type::Field, location); + let int_value = + ast::Literal::Integer(ordering_value, false, ast::Type::Field, location); let rhs = Box::new(ast::Expression::Literal(int_value)); let lhs = Box::new(ast::Expression::ExtractTupleField(Box::new(result), 0)); @@ -1758,44 +1730,19 @@ impl<'interner> Monomorphizer<'interner> { Ok(result) } - /// Call sites are instantiated against the trait method, but when an impl is later selected, - /// the corresponding method in the impl will have a different set of generics. `perform_impl_bindings` - /// is needed to apply the generics from the trait method to the impl method. Without this, - /// static method references to generic impls (e.g. `Eq::eq` for `[T; N]`) will fail to re-apply - /// the correct type bindings during monomorphization. - fn perform_impl_bindings( + /// Call an operator overloading method for the given prefix operator. + fn create_prefix_operator_impl_call( &self, - trait_method: Option, - impl_method: node_interner::FuncId, - ) -> TypeBindings { - let mut bindings = TypeBindings::new(); - - if let Some(trait_method) = trait_method { - let the_trait = self.interner.get_trait(trait_method.trait_id); - - let trait_method_type = the_trait.methods[trait_method.method_index].typ.as_monotype(); - - // Make each NamedGeneric in this type bindable by replacing it with a TypeVariable - // with the same internal id and binding. - let (generics, impl_method_type) = - self.interner.function_meta(&impl_method).typ.unwrap_forall(); - - let replace_type_variable = |var: &TypeVariable| { - (var.id(), (var.clone(), Type::TypeVariable(var.clone(), TypeVariableKind::Normal))) - }; - - // Replace each NamedGeneric with a TypeVariable containing the same internal type variable - let type_bindings = generics.iter().map(replace_type_variable).collect(); - let impl_method_type = impl_method_type.force_substitute(&type_bindings); - - trait_method_type.try_unify(&impl_method_type, &mut bindings).unwrap_or_else(|_| { - unreachable!("Impl method type {} does not unify with trait method type {} during monomorphization", impl_method_type, trait_method_type) - }); - - perform_instantiation_bindings(&bindings); - } + func: ast::Expression, + rhs: ast::Expression, + ret: Type, + location: Location, + ) -> Result { + let arguments = vec![rhs]; + let func = Box::new(func); + let return_type = Self::convert_type(&ret, location)?; - bindings + Ok(ast::Expression::Call(ast::Call { func, arguments, return_type, location })) } } @@ -1813,14 +1760,100 @@ fn unwrap_struct_type(typ: &HirType) -> Vec<(String, HirType)> { } } -fn perform_instantiation_bindings(bindings: &TypeBindings) { +pub fn perform_instantiation_bindings(bindings: &TypeBindings) { for (var, binding) in bindings.values() { var.force_bind(binding.clone()); } } -fn undo_instantiation_bindings(bindings: TypeBindings) { +pub fn undo_instantiation_bindings(bindings: TypeBindings) { for (id, (var, _)) in bindings { var.unbind(id); } } + +/// Call sites are instantiated against the trait method, but when an impl is later selected, +/// the corresponding method in the impl will have a different set of generics. `perform_impl_bindings` +/// is needed to apply the generics from the trait method to the impl method. Without this, +/// static method references to generic impls (e.g. `Eq::eq` for `[T; N]`) will fail to re-apply +/// the correct type bindings during monomorphization. +pub fn perform_impl_bindings( + interner: &NodeInterner, + trait_method: Option, + impl_method: node_interner::FuncId, + location: Location, +) -> Result { + let mut bindings = TypeBindings::new(); + + if let Some(trait_method) = trait_method { + let the_trait = interner.get_trait(trait_method.trait_id); + + let trait_method_type = the_trait.methods[trait_method.method_index].typ.as_monotype(); + + // Make each NamedGeneric in this type bindable by replacing it with a TypeVariable + // with the same internal id and binding. + let (generics, impl_method_type) = interner.function_meta(&impl_method).typ.unwrap_forall(); + + let replace_type_variable = |var: &TypeVariable| { + (var.id(), (var.clone(), Type::TypeVariable(var.clone(), TypeVariableKind::Normal))) + }; + + // Replace each NamedGeneric with a TypeVariable containing the same internal type variable + let type_bindings = generics.iter().map(replace_type_variable).collect(); + let impl_method_type = impl_method_type.force_substitute(&type_bindings); + + trait_method_type.try_unify(&impl_method_type, &mut bindings).map_err(|_| { + InterpreterError::ImplMethodTypeMismatch { + expected: trait_method_type.clone(), + actual: impl_method_type, + location, + } + })?; + + perform_instantiation_bindings(&bindings); + } + + Ok(bindings) +} + +pub fn resolve_trait_method( + interner: &NodeInterner, + method: TraitMethodId, + expr_id: ExprId, +) -> Result { + let trait_impl = interner.get_selected_impl_for_expression(expr_id).ok_or_else(|| { + let location = interner.expr_location(&expr_id); + InterpreterError::NoImpl { location } + })?; + + let impl_id = match trait_impl { + TraitImplKind::Normal(impl_id) => impl_id, + TraitImplKind::Assumed { object_type, trait_generics } => { + match interner.lookup_trait_implementation( + &object_type, + method.trait_id, + &trait_generics, + ) { + Ok(TraitImplKind::Normal(impl_id)) => impl_id, + Ok(TraitImplKind::Assumed { .. }) => { + let location = interner.expr_location(&expr_id); + return Err(InterpreterError::NoImpl { location }); + } + Err(constraints) => { + let location = interner.expr_location(&expr_id); + if let Some(error) = + NoMatchingImplFoundError::new(interner, constraints, location.span) + { + let file = location.file; + return Err(InterpreterError::NoMatchingImplFound { error, file }); + } else { + let location = interner.expr_location(&expr_id); + return Err(InterpreterError::NoImpl { location }); + } + } + } + } + }; + + Ok(interner.get_trait_implementation(impl_id).borrow().methods[method.method_index]) +} diff --git a/compiler/noirc_frontend/src/monomorphization/printer.rs b/compiler/noirc_frontend/src/monomorphization/printer.rs index ea8f079cc2f..9b1eeecdc1a 100644 --- a/compiler/noirc_frontend/src/monomorphization/printer.rs +++ b/compiler/noirc_frontend/src/monomorphization/printer.rs @@ -102,7 +102,7 @@ impl AstPrinter { self.print_comma_separated(&array.contents, f)?; write!(f, "]") } - super::ast::Literal::Integer(x, _, _) => x.fmt(f), + super::ast::Literal::Integer(x, _, _, _) => x.fmt(f), super::ast::Literal::Bool(x) => x.fmt(f), super::ast::Literal::Str(s) => s.fmt(f), super::ast::Literal::FmtStr(s, _, _) => { diff --git a/compiler/noirc_frontend/src/node_interner.rs b/compiler/noirc_frontend/src/node_interner.rs index 915093df514..a009a42df53 100644 --- a/compiler/noirc_frontend/src/node_interner.rs +++ b/compiler/noirc_frontend/src/node_interner.rs @@ -1,6 +1,8 @@ use std::borrow::Cow; use std::collections::HashMap; use std::fmt; +use std::hash::Hash; +use std::marker::Copy; use std::ops::Deref; use fm::FileId; @@ -17,6 +19,7 @@ use crate::hir::comptime; use crate::hir::def_collector::dc_crate::CompilationError; use crate::hir::def_collector::dc_crate::{UnresolvedStruct, UnresolvedTrait, UnresolvedTypeAlias}; use crate::hir::def_map::{LocalModuleId, ModuleId}; +use crate::macros_api::UnaryOp; use crate::QuotedType; use crate::ast::{BinaryOpKind, FunctionDefinition, ItemVisibility}; @@ -31,10 +34,11 @@ use crate::hir_def::{ function::{FuncMeta, HirFunction}, stmt::HirStatement, }; +use crate::locations::LocationIndices; use crate::token::{Attributes, SecondaryAttribute}; -use crate::{ - Generics, Shared, TypeAlias, TypeBindings, TypeVariable, TypeVariableId, TypeVariableKind, -}; +use crate::GenericTypeVars; +use crate::Generics; +use crate::{Shared, TypeAlias, TypeBindings, TypeVariable, TypeVariableId, TypeVariableKind}; /// An arbitrary number to limit the recursion depth when searching for trait impls. /// This is needed to stop recursing for cases such as `impl Foo for T where T: Eq` @@ -63,8 +67,11 @@ pub struct NodeInterner { // Contains the source module each function was defined in function_modules: HashMap, + // The location of each module + module_locations: HashMap, + /// This graph tracks dependencies between different global definitions. - /// This is used to ensure the absense of dependency cycles for globals and types. + /// This is used to ensure the absence of dependency cycles for globals and types. dependency_graph: DiGraph, /// To keep track of where each DependencyId is in `dependency_graph`, we need @@ -134,8 +141,11 @@ pub struct NodeInterner { /// the context to get the concrete type of the object and select the correct impl itself. selected_trait_implementations: HashMap, - /// Holds the trait ids of the traits used for operator overloading - operator_traits: HashMap, + /// Holds the trait ids of the traits used for infix operator overloading + infix_operator_traits: HashMap, + + /// Holds the trait ids of the traits used for prefix operator overloading + prefix_operator_traits: HashMap, /// The `Ordering` type is a semi-builtin type that is the result of the comparison traits. ordering_type: Option, @@ -176,6 +186,39 @@ pub struct NodeInterner { /// Stores the [Location] of a [Type] reference pub(crate) type_ref_locations: Vec<(Type, Location)>, + + /// In Noir's metaprogramming, a noir type has the type `Type`. When these are spliced + /// into `quoted` expressions, we preserve the original type by assigning it a unique id + /// and creating a `Token::QuotedType(id)` from this id. We cannot create a token holding + /// the actual type since types do not implement Send or Sync. + quoted_types: noirc_arena::Arena, + + /// Whether to track references. In regular compilations this is false, but tools set it to true. + pub(crate) track_references: bool, + + /// Store the location of the references in the graph. + /// Edges are directed from reference nodes to referenced nodes. + /// For example: + /// + /// ```text + /// let foo = 3; + /// // referenced + /// // ^ + /// // | + /// // +------------+ + /// let bar = foo; | + /// // reference | + /// // v | + /// // | | + /// // +------+ + /// ``` + pub(crate) reference_graph: DiGraph, + + /// Tracks the index of the references in the graph + pub(crate) reference_graph_indices: HashMap, + + /// Store the location of the references in the graph + pub(crate) location_indices: LocationIndices, } /// A dependency in the dependency graph may be a type or a definition. @@ -194,6 +237,28 @@ pub enum DependencyId { Global(GlobalId), Function(FuncId), Alias(TypeAliasId), + Variable(Location), +} + +/// A reference to a module, struct, trait, etc., mainly used by the LSP code +/// to keep track of how symbols reference each other. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum ReferenceId { + Module(ModuleId), + Struct(StructId), + StructMember(StructId, usize), + Trait(TraitId), + Global(GlobalId), + Function(FuncId), + Alias(TypeAliasId), + Local(DefinitionId), + Reference(Location, bool /* is Self */), +} + +impl ReferenceId { + pub fn is_self_type_name(&self) -> bool { + matches!(self, Self::Reference(_, true)) + } } /// A trait implementation is either a normal implementation that is present in the source @@ -252,6 +317,9 @@ pub struct FunctionModifiers { pub generic_count: usize, pub is_comptime: bool, + + /// The location of the function's name rather than the entire function + pub name_location: Location, } impl FunctionModifiers { @@ -266,6 +334,7 @@ impl FunctionModifiers { is_unconstrained: false, generic_count: 0, is_comptime: false, + name_location: Location::dummy(), } } } @@ -303,7 +372,7 @@ impl StmtId { } } -#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)] +#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone, PartialOrd, Ord)] pub struct ExprId(Index); impl ExprId { @@ -417,6 +486,7 @@ pub(crate) enum Node { pub struct DefinitionInfo { pub name: String, pub mutable: bool, + pub comptime: bool, pub kind: DefinitionKind, pub location: Location, } @@ -472,6 +542,9 @@ pub struct GlobalInfo { pub value: Option, } +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct QuotedTypeId(noirc_arena::Index); + impl Default for NodeInterner { fn default() -> Self { let mut interner = NodeInterner { @@ -480,6 +553,7 @@ impl Default for NodeInterner { function_definition_ids: HashMap::new(), function_modifiers: HashMap::new(), function_modules: HashMap::new(), + module_locations: HashMap::new(), func_id_to_trait: HashMap::new(), dependency_graph: petgraph::graph::DiGraph::new(), dependency_graph_indices: HashMap::new(), @@ -495,7 +569,8 @@ impl Default for NodeInterner { next_trait_implementation_id: 0, trait_implementation_map: HashMap::new(), selected_trait_implementations: HashMap::new(), - operator_traits: HashMap::new(), + infix_operator_traits: HashMap::new(), + prefix_operator_traits: HashMap::new(), ordering_type: None, instantiation_bindings: HashMap::new(), field_indices: HashMap::new(), @@ -506,6 +581,11 @@ impl Default for NodeInterner { primitive_methods: HashMap::new(), type_alias_ref: Vec::new(), type_ref_locations: Vec::new(), + quoted_types: Default::default(), + track_references: false, + location_indices: LocationIndices::default(), + reference_graph: petgraph::graph::DiGraph::new(), + reference_graph_indices: HashMap::new(), }; // An empty block expression is used often, we add this into the `node` on startup @@ -547,7 +627,12 @@ impl NodeInterner { self.definition_to_type.insert(definition_id, typ); } - pub fn push_empty_trait(&mut self, type_id: TraitId, unresolved_trait: &UnresolvedTrait) { + pub fn push_empty_trait( + &mut self, + type_id: TraitId, + unresolved_trait: &UnresolvedTrait, + generics: Generics, + ) { let self_type_typevar_id = self.next_type_variable_id(); let new_trait = Trait { @@ -555,13 +640,7 @@ impl NodeInterner { name: unresolved_trait.trait_def.name.clone(), crate_id: unresolved_trait.crate_id, location: Location::new(unresolved_trait.trait_def.span, unresolved_trait.file_id), - generics: vecmap(&unresolved_trait.trait_def.generics, |_| { - // Temporary type variable ids before the trait is resolved to its actual ids. - // This lets us record how many arguments the type expects so that other types - // can refer to it with generic arguments before the generic parameters themselves - // are resolved. - TypeVariable::unbound(TypeVariableId(0)) - }), + generics, self_type_typevar_id, self_type_typevar: TypeVariable::unbound(self_type_typevar_id), methods: Vec::new(), @@ -576,6 +655,7 @@ impl NodeInterner { pub fn new_struct( &mut self, typ: &UnresolvedStruct, + generics: Generics, krate: CrateId, local_id: LocalModuleId, file_id: FileId, @@ -585,13 +665,6 @@ impl NodeInterner { // Fields will be filled in later let no_fields = Vec::new(); - let generics = vecmap(&typ.struct_def.generics, |_| { - // Temporary type variable ids before the struct is resolved to its actual ids. - // This lets us record how many arguments the type expects so that other types - // can refer to it with generic arguments before the generic parameters themselves - // are resolved. - TypeVariable::unbound(TypeVariableId(0)) - }); let location = Location::new(typ.struct_def.span, file_id); let new_struct = StructType::new(struct_id, name, location, no_fields, generics); @@ -600,7 +673,11 @@ impl NodeInterner { struct_id } - pub fn push_type_alias(&mut self, typ: &UnresolvedTypeAlias) -> TypeAliasId { + pub fn push_type_alias( + &mut self, + typ: &UnresolvedTypeAlias, + generics: Generics, + ) -> TypeAliasId { let type_id = TypeAliasId(self.type_aliases.len()); self.type_aliases.push(Shared::new(TypeAlias::new( @@ -608,7 +685,7 @@ impl NodeInterner { typ.type_alias_def.name.clone(), Location::new(typ.type_alias_def.span, typ.file_id), Type::Error, - vecmap(&typ.type_alias_def.generics, |_| TypeVariable::unbound(TypeVariableId(0))), + generics, ))); type_id @@ -629,6 +706,15 @@ impl NodeInterner { f(value); } + pub fn update_struct_attributes( + &mut self, + type_id: StructId, + f: impl FnOnce(&mut StructAttributes), + ) { + let value = self.struct_attributes.get_mut(&type_id).unwrap(); + f(value); + } + pub fn set_type_alias(&mut self, type_id: TypeAliasId, typ: Type, generics: Generics) { let type_alias_type = &mut self.type_aliases[type_id.0]; type_alias_type.borrow_mut().set_type_and_generics(typ, generics); @@ -663,6 +749,7 @@ impl NodeInterner { self.type_ref_locations.push((typ, location)); } + #[allow(clippy::too_many_arguments)] fn push_global( &mut self, ident: Ident, @@ -671,12 +758,13 @@ impl NodeInterner { file: FileId, attributes: Vec, mutable: bool, + comptime: bool, ) -> GlobalId { let id = GlobalId(self.globals.len()); let location = Location::new(ident.span(), file); let name = ident.to_string(); let definition_id = - self.push_definition(name, mutable, DefinitionKind::Global(id), location); + self.push_definition(name, mutable, comptime, DefinitionKind::Global(id), location); self.globals.push(GlobalInfo { id, @@ -703,10 +791,11 @@ impl NodeInterner { file: FileId, attributes: Vec, mutable: bool, + comptime: bool, ) -> GlobalId { let statement = self.push_stmt(HirStatement::Error); let span = name.span(); - let id = self.push_global(name, local_id, statement, file, attributes, mutable); + let id = self.push_global(name, local_id, statement, file, attributes, mutable, comptime); self.push_stmt_location(statement, span, file); id } @@ -750,15 +839,24 @@ impl NodeInterner { &mut self, name: String, mutable: bool, + comptime: bool, definition: DefinitionKind, location: Location, ) -> DefinitionId { let id = DefinitionId(self.definitions.len()); + let is_local = matches!(definition, DefinitionKind::Local(_)); + if let DefinitionKind::Function(func_id) = definition { self.function_definition_ids.insert(func_id, id); } - self.definitions.push(DefinitionInfo { name, mutable, kind: definition, location }); + let kind = definition; + self.definitions.push(DefinitionInfo { name, mutable, comptime, kind, location }); + + if is_local { + self.add_definition_location(ReferenceId::Local(id)); + } + id } @@ -788,8 +886,14 @@ impl NodeInterner { is_unconstrained: function.is_unconstrained, generic_count: function.generics.len(), is_comptime: function.is_comptime, + name_location: Location::new(function.name.span(), location.file), }; - self.push_function_definition(id, modifiers, module, location) + let definition_id = self.push_function_definition(id, modifiers, module, location); + + // This needs to be done after pushing the definition since it will reference the + // location that was stored + self.add_definition_location(ReferenceId::Function(id)); + definition_id } pub fn push_function_definition( @@ -800,9 +904,10 @@ impl NodeInterner { location: Location, ) -> DefinitionId { let name = modifiers.name.clone(); + let comptime = modifiers.is_comptime; self.function_modifiers.insert(func, modifiers); self.function_modules.insert(func, module); - self.push_definition(name, false, DefinitionKind::Function(func), location) + self.push_definition(name, false, comptime, DefinitionKind::Function(func), location) } pub fn set_function_trait(&mut self, func: FuncId, self_type: Type, trait_id: TraitId) { @@ -888,6 +993,14 @@ impl NodeInterner { &self.struct_attributes[struct_id] } + pub fn add_module_location(&mut self, module_id: ModuleId, location: Location) { + self.module_locations.insert(module_id, location); + } + + pub fn module_location(&self, module_id: &ModuleId) -> Location { + self.module_locations[module_id] + } + pub fn global_attributes(&self, global_id: &GlobalId) -> &[SecondaryAttribute] { &self.global_attributes[global_id] } @@ -965,6 +1078,10 @@ impl NodeInterner { self.id_location(expr_id).span } + pub fn try_expr_span(&self, expr_id: &ExprId) -> Option { + self.try_id_location(expr_id).map(|location| location.span) + } + pub fn expr_location(&self, expr_id: &ExprId) -> Location { self.id_location(expr_id) } @@ -1069,8 +1186,14 @@ impl NodeInterner { } /// Returns the span of an item stored in the Interner - pub fn id_location(&self, index: impl Into) -> Location { - self.id_to_location.get(&index.into()).copied().unwrap() + pub fn id_location(&self, index: impl Into + Copy) -> Location { + self.try_id_location(index) + .unwrap_or_else(|| panic!("ID is missing a source location: {:?}", index.into())) + } + + /// Returns the span of an item stored in the Interner, if present + pub fn try_id_location(&self, index: impl Into) -> Option { + self.id_to_location.get(&index.into()).copied() } /// Replaces the HirExpression at the given ExprId with a new HirExpression @@ -1178,6 +1301,17 @@ impl NodeInterner { self.trait_implementations[&id].clone() } + /// If the given function belongs to a trait impl, return its trait method id. + /// Otherwise, return None. + pub fn get_trait_method_id(&self, function: FuncId) -> Option { + let impl_id = self.function_meta(&function).trait_impl?; + let trait_impl = self.get_trait_implementation(impl_id); + let trait_impl = trait_impl.borrow(); + + let method_index = trait_impl.methods.iter().position(|id| *id == function)?; + Some(TraitMethodId { trait_id: trait_impl.trait_id, method_index }) + } + /// Given a `ObjectType: TraitId` pair, try to find an existing impl that satisfies the /// constraint. If an impl cannot be found, this will return a vector of each constraint /// in the path to get to the failing constraint. Usually this is just the single failing @@ -1407,7 +1541,7 @@ impl NodeInterner { trait_id: TraitId, trait_generics: Vec, impl_id: TraitImplId, - impl_generics: Generics, + impl_generics: GenericTypeVars, trait_impl: Shared, ) -> Result<(), (Span, FileId)> { self.trait_implementations.insert(impl_id, trait_impl.clone()); @@ -1548,18 +1682,29 @@ impl NodeInterner { /// Retrieves the trait id for a given binary operator. /// All binary operators correspond to a trait - although multiple may correspond /// to the same trait (such as `==` and `!=`). - /// `self.operator_traits` is expected to be filled before name resolution, + /// `self.infix_operator_traits` is expected to be filled before name resolution, /// during definition collection. pub fn get_operator_trait_method(&self, operator: BinaryOpKind) -> TraitMethodId { - let trait_id = self.operator_traits[&operator]; + let trait_id = self.infix_operator_traits[&operator]; // Assume that the operator's method to be overloaded is the first method of the trait. TraitMethodId { trait_id, method_index: 0 } } + /// Retrieves the trait id for a given unary operator. + /// Only some unary operators correspond to a trait: `-` and `!`, but for example `*` does not. + /// `self.prefix_operator_traits` is expected to be filled before name resolution, + /// during definition collection. + pub fn get_prefix_operator_trait_method(&self, operator: &UnaryOp) -> Option { + let trait_id = self.prefix_operator_traits.get(operator)?; + + // Assume that the operator's method to be overloaded is the first method of the trait. + Some(TraitMethodId { trait_id: *trait_id, method_index: 0 }) + } + /// Add the given trait as an operator trait if its name matches one of the /// operator trait names (Add, Sub, ...). - pub fn try_add_operator_trait(&mut self, trait_id: TraitId) { + pub fn try_add_infix_operator_trait(&mut self, trait_id: TraitId) { let the_trait = self.get_trait(trait_id); let operator = match the_trait.name.0.contents.as_str() { @@ -1578,17 +1723,17 @@ impl NodeInterner { _ => return, }; - self.operator_traits.insert(operator, trait_id); + self.infix_operator_traits.insert(operator, trait_id); // Some operators also require we insert a matching entry for related operators match operator { BinaryOpKind::Equal => { - self.operator_traits.insert(BinaryOpKind::NotEqual, trait_id); + self.infix_operator_traits.insert(BinaryOpKind::NotEqual, trait_id); } BinaryOpKind::Less => { - self.operator_traits.insert(BinaryOpKind::LessEqual, trait_id); - self.operator_traits.insert(BinaryOpKind::Greater, trait_id); - self.operator_traits.insert(BinaryOpKind::GreaterEqual, trait_id); + self.infix_operator_traits.insert(BinaryOpKind::LessEqual, trait_id); + self.infix_operator_traits.insert(BinaryOpKind::Greater, trait_id); + self.infix_operator_traits.insert(BinaryOpKind::GreaterEqual, trait_id); let the_trait = self.get_trait(trait_id); self.ordering_type = match &the_trait.methods[0].typ { @@ -1603,27 +1748,43 @@ impl NodeInterner { } } + /// Add the given trait as an operator trait if its name matches one of the + /// prefix operator trait names (Not or Neg). + pub fn try_add_prefix_operator_trait(&mut self, trait_id: TraitId) { + let the_trait = self.get_trait(trait_id); + + let operator = match the_trait.name.0.contents.as_str() { + "Neg" => UnaryOp::Minus, + "Not" => UnaryOp::Not, + _ => return, + }; + + self.prefix_operator_traits.insert(operator, trait_id); + } + /// This function is needed when creating a NodeInterner for testing so that calls /// to `get_operator_trait` do not panic when the stdlib isn't present. #[cfg(test)] pub fn populate_dummy_operator_traits(&mut self) { let dummy_trait = TraitId(ModuleId::dummy_id()); - self.operator_traits.insert(BinaryOpKind::Add, dummy_trait); - self.operator_traits.insert(BinaryOpKind::Subtract, dummy_trait); - self.operator_traits.insert(BinaryOpKind::Multiply, dummy_trait); - self.operator_traits.insert(BinaryOpKind::Divide, dummy_trait); - self.operator_traits.insert(BinaryOpKind::Modulo, dummy_trait); - self.operator_traits.insert(BinaryOpKind::Equal, dummy_trait); - self.operator_traits.insert(BinaryOpKind::NotEqual, dummy_trait); - self.operator_traits.insert(BinaryOpKind::Less, dummy_trait); - self.operator_traits.insert(BinaryOpKind::LessEqual, dummy_trait); - self.operator_traits.insert(BinaryOpKind::Greater, dummy_trait); - self.operator_traits.insert(BinaryOpKind::GreaterEqual, dummy_trait); - self.operator_traits.insert(BinaryOpKind::And, dummy_trait); - self.operator_traits.insert(BinaryOpKind::Or, dummy_trait); - self.operator_traits.insert(BinaryOpKind::Xor, dummy_trait); - self.operator_traits.insert(BinaryOpKind::ShiftLeft, dummy_trait); - self.operator_traits.insert(BinaryOpKind::ShiftRight, dummy_trait); + self.infix_operator_traits.insert(BinaryOpKind::Add, dummy_trait); + self.infix_operator_traits.insert(BinaryOpKind::Subtract, dummy_trait); + self.infix_operator_traits.insert(BinaryOpKind::Multiply, dummy_trait); + self.infix_operator_traits.insert(BinaryOpKind::Divide, dummy_trait); + self.infix_operator_traits.insert(BinaryOpKind::Modulo, dummy_trait); + self.infix_operator_traits.insert(BinaryOpKind::Equal, dummy_trait); + self.infix_operator_traits.insert(BinaryOpKind::NotEqual, dummy_trait); + self.infix_operator_traits.insert(BinaryOpKind::Less, dummy_trait); + self.infix_operator_traits.insert(BinaryOpKind::LessEqual, dummy_trait); + self.infix_operator_traits.insert(BinaryOpKind::Greater, dummy_trait); + self.infix_operator_traits.insert(BinaryOpKind::GreaterEqual, dummy_trait); + self.infix_operator_traits.insert(BinaryOpKind::And, dummy_trait); + self.infix_operator_traits.insert(BinaryOpKind::Or, dummy_trait); + self.infix_operator_traits.insert(BinaryOpKind::Xor, dummy_trait); + self.infix_operator_traits.insert(BinaryOpKind::ShiftLeft, dummy_trait); + self.infix_operator_traits.insert(BinaryOpKind::ShiftRight, dummy_trait); + self.prefix_operator_traits.insert(UnaryOp::Minus, dummy_trait); + self.prefix_operator_traits.insert(UnaryOp::Not, dummy_trait); } pub(crate) fn ordering_type(&self) -> Type { @@ -1648,13 +1809,13 @@ impl NodeInterner { self.add_dependency(dependent, DependencyId::Alias(dependency)); } - fn add_dependency(&mut self, dependent: DependencyId, dependency: DependencyId) { + pub fn add_dependency(&mut self, dependent: DependencyId, dependency: DependencyId) { let dependent_index = self.get_or_insert_dependency(dependent); let dependency_index = self.get_or_insert_dependency(dependency); self.dependency_graph.update_edge(dependent_index, dependency_index, ()); } - fn get_or_insert_dependency(&mut self, id: DependencyId) -> PetGraphIndex { + pub fn get_or_insert_dependency(&mut self, id: DependencyId) -> PetGraphIndex { if let Some(index) = self.dependency_graph_indices.get(&id) { return *index; } @@ -1705,6 +1866,11 @@ impl NodeInterner { } // Mutually recursive functions are allowed DependencyId::Function(_) => (), + // Local variables should never be in a dependency cycle, scoping rules + // prevents referring to them before they're defined + DependencyId::Variable(loc) => unreachable!( + "Variable used at location {loc:?} caught in a dependency cycle" + ), } } } @@ -1726,6 +1892,9 @@ impl NodeInterner { DependencyId::Global(id) => { Cow::Borrowed(self.get_global(id).ident.0.contents.as_ref()) } + DependencyId::Variable(loc) => { + unreachable!("Variable used at location {loc:?} caught in a dependency cycle") + } }; let mut cycle = index_to_string(scc[start_index]).to_string(); @@ -1738,6 +1907,46 @@ impl NodeInterner { cycle } + + pub fn push_quoted_type(&mut self, typ: Type) -> QuotedTypeId { + QuotedTypeId(self.quoted_types.insert(typ)) + } + + pub fn get_quoted_type(&self, id: QuotedTypeId) -> &Type { + &self.quoted_types[id.0] + } + + /// Returns the type of an operator (which is always a function), along with its return type. + pub fn get_infix_operator_type( + &self, + lhs: ExprId, + operator: BinaryOpKind, + operator_expr: ExprId, + ) -> (Type, Type) { + let lhs_type = self.id_type(lhs); + let args = vec![lhs_type.clone(), lhs_type]; + + // If this is a comparison operator, the result is a boolean but + // the actual method call returns an Ordering + use crate::ast::BinaryOpKind::*; + let ret = if matches!(operator, Less | LessEqual | Greater | GreaterEqual) { + self.ordering_type() + } else { + self.id_type(operator_expr) + }; + + let env = Box::new(Type::Unit); + (Type::Function(args, Box::new(ret.clone()), env), ret) + } + + /// Returns the type of a prefix operator (which is always a function), along with its return type. + pub fn get_prefix_operator_type(&self, operator_expr: ExprId, rhs: ExprId) -> (Type, Type) { + let rhs_type = self.id_type(rhs); + let args = vec![rhs_type]; + let ret = self.id_type(operator_expr); + let env = Box::new(Type::Unit); + (Type::Function(args, Box::new(ret.clone()), env), ret) + } } impl Methods { @@ -1825,7 +2034,7 @@ fn get_type_method_key(typ: &Type) -> Option { Type::Unit => Some(Unit), Type::Tuple(_) => Some(Tuple), Type::Function(_, _, _) => Some(Function), - Type::NamedGeneric(_, _) => Some(Generic), + Type::NamedGeneric(_, _, _) => Some(Generic), Type::Quoted(quoted) => Some(Quoted(*quoted)), Type::MutableReference(element) => get_type_method_key(element), Type::Alias(alias, _) => get_type_method_key(&alias.borrow().typ), diff --git a/compiler/noirc_frontend/src/noir_parser.lalrpop b/compiler/noirc_frontend/src/noir_parser.lalrpop index 5768e3c1d17..5bf48a764d6 100644 --- a/compiler/noirc_frontend/src/noir_parser.lalrpop +++ b/compiler/noirc_frontend/src/noir_parser.lalrpop @@ -80,7 +80,6 @@ extern { "mod" => BorrowedToken::Keyword(noir_token::Keyword::Mod), "mut" => BorrowedToken::Keyword(noir_token::Keyword::Mut), "pub" => BorrowedToken::Keyword(noir_token::Keyword::Pub), - "quote" => BorrowedToken::Keyword(noir_token::Keyword::Quote), "return" => BorrowedToken::Keyword(noir_token::Keyword::Return), "return_data" => BorrowedToken::Keyword(noir_token::Keyword::ReturnData), "str" => BorrowedToken::Keyword(noir_token::Keyword::String), diff --git a/compiler/noirc_frontend/src/parser/errors.rs b/compiler/noirc_frontend/src/parser/errors.rs index af3d4caa145..41ea9f88c19 100644 --- a/compiler/noirc_frontend/src/parser/errors.rs +++ b/compiler/noirc_frontend/src/parser/errors.rs @@ -133,7 +133,7 @@ impl std::fmt::Display for ParserError { } else { let expected = expected.iter().map(ToString::to_string).collect::>().join(", "); - write!(f, "Unexpected {}, expected one of {}{}", self.found, expected, reason_str) + write!(f, "Unexpected {:?}, expected one of {}{}", self.found, expected, reason_str) } } } diff --git a/compiler/noirc_frontend/src/parser/mod.rs b/compiler/noirc_frontend/src/parser/mod.rs index 9e60383afee..c4aa0654ecd 100644 --- a/compiler/noirc_frontend/src/parser/mod.rs +++ b/compiler/noirc_frontend/src/parser/mod.rs @@ -22,10 +22,10 @@ use chumsky::primitive::Container; pub use errors::ParserError; pub use errors::ParserErrorReason; use noirc_errors::Span; -pub use parser::parse_program; +pub use parser::{expression, parse_program, top_level_items}; #[derive(Debug, Clone)] -pub(crate) enum TopLevelStatement { +pub enum TopLevelStatement { Function(NoirFunction), Module(ModuleDeclaration), Import(UseTree), @@ -45,7 +45,7 @@ pub trait NoirParser: Parser + Sized + Clone { impl NoirParser for P where P: Parser + Clone {} // ExprParser just serves as a type alias for NoirParser + Clone -trait ExprParser: NoirParser {} +pub trait ExprParser: NoirParser {} impl

ExprParser for P where P: NoirParser {} fn parenthesized(parser: P) -> impl NoirParser @@ -197,7 +197,7 @@ fn parameter_name_recovery() -> impl NoirParser { } fn top_level_statement_recovery() -> impl NoirParser { - none_of([Token::Semicolon, Token::RightBrace, Token::EOF]) + none_of([Token::RightBrace, Token::EOF]) .repeated() .ignore_then(one_of([Token::Semicolon])) .map(|_| TopLevelStatement::Error) diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index 727d9ad47a8..e51f6515b88 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -23,7 +23,7 @@ //! prevent other parsers from being tried afterward since there is no longer an error. Thus, they should //! be limited to cases like the above `fn` example where it is clear we shouldn't back out of the //! current parser to try alternative parsers in a `choice` expression. -use self::primitives::{keyword, mutable_reference, variable}; +use self::primitives::{keyword, macro_quote_marker, mutable_reference, variable}; use self::types::{generic_type_args, maybe_comp_time, parse_type}; use super::{ @@ -191,6 +191,11 @@ fn module() -> impl NoirParser { }) } +/// This parser is used for parsing top level statements in macros +pub fn top_level_items() -> impl NoirParser> { + top_level_statement(module()).repeated() +} + /// top_level_statement: function_definition /// | struct_definition /// | trait_definition @@ -225,11 +230,20 @@ fn implementation() -> impl NoirParser { keyword(Keyword::Impl) .ignore_then(function::generics()) .then(parse_type().map_with_span(|typ, span| (typ, span))) + .then(where_clause()) .then_ignore(just(Token::LeftBrace)) .then(spanned(function::function_definition(true)).repeated()) .then_ignore(just(Token::RightBrace)) - .map(|((generics, (object_type, type_span)), methods)| { - TopLevelStatement::Impl(TypeImpl { generics, object_type, type_span, methods }) + .map(|args| { + let ((other_args, where_clause), methods) = args; + let (generics, (object_type, type_span)) = other_args; + TopLevelStatement::Impl(TypeImpl { + generics, + object_type, + type_span, + where_clause, + methods, + }) }) } @@ -718,7 +732,7 @@ fn optional_visibility() -> impl NoirParser { }) } -fn expression() -> impl ExprParser { +pub fn expression() -> impl ExprParser { recursive(|expr| { expression_with_precedence( Precedence::Lowest, @@ -1101,11 +1115,12 @@ where }, lambdas::lambda(expr_parser.clone()), block(statement.clone()).map(ExpressionKind::Block), - comptime_expr(statement.clone()), - quote(statement), + comptime_expr(statement), + quote(), unquote(expr_parser.clone()), variable(), literal(), + macro_quote_marker(), )) .map_with_span(Expression::new) .or(parenthesized(expr_parser.clone()).map_with_span(|sub_expr, span| { @@ -1128,19 +1143,13 @@ where .labelled(ParsingRuleLabel::Atom) } -fn quote<'a, P>(statement: P) -> impl NoirParser + 'a -where - P: NoirParser + 'a, -{ - keyword(Keyword::Quote).ignore_then(spanned(block(statement))).validate( - |(block, block_span), span, emit| { - emit(ParserError::with_reason( - ParserErrorReason::ExperimentalFeature("quoted expressions"), - span, - )); - ExpressionKind::Quote(block, block_span) - }, - ) +fn quote() -> impl NoirParser { + token_kind(TokenKind::Quote).map(|token| { + ExpressionKind::Quote(match token { + Token::Quote(tokens) => tokens, + _ => unreachable!("token_kind(Quote) should guarantee parsing only a quote token"), + }) + }) } /// unquote: '$' variable @@ -1669,4 +1678,19 @@ mod test { check_cases_with_errors(&cases[..], block(fresh_statement())); } + + #[test] + fn test_quote() { + let cases = vec![ + "quote {}", + "quote { a.b }", + "quote { ) ( }", // invalid syntax is fine in a quote + "quote { { } }", // Nested `{` and `}` shouldn't close the quote as long as they are matched. + "quote { 1 { 2 { 3 { 4 { 5 } 4 4 } 3 3 } 2 2 } 1 1 }", + ]; + parse_all(quote(), cases); + + let failing = vec!["quote {}}", "quote a", "quote { { { } } } }"]; + parse_all_failing(quote(), failing); + } } diff --git a/compiler/noirc_frontend/src/parser/parser/function.rs b/compiler/noirc_frontend/src/parser/parser/function.rs index 72a656d7948..471c063d441 100644 --- a/compiler/noirc_frontend/src/parser/parser/function.rs +++ b/compiler/noirc_frontend/src/parser/parser/function.rs @@ -5,11 +5,14 @@ use super::{ self_parameter, where_clause, NoirParser, }; use crate::ast::{ - FunctionDefinition, FunctionReturnType, Ident, ItemVisibility, NoirFunction, Param, Visibility, + FunctionDefinition, FunctionReturnType, ItemVisibility, NoirFunction, Param, Visibility, }; -use crate::parser::labels::ParsingRuleLabel; use crate::parser::spanned; use crate::token::{Keyword, Token}; +use crate::{ + ast::{UnresolvedGeneric, UnresolvedGenerics}, + parser::labels::ParsingRuleLabel, +}; use chumsky::prelude::*; @@ -61,16 +64,31 @@ fn function_modifiers() -> impl NoirParser<(bool, ItemVisibility, bool)> { }) } +pub(super) fn numeric_generic() -> impl NoirParser { + keyword(Keyword::Let) + .ignore_then(ident()) + .then_ignore(just(Token::Colon)) + .then(parse_type()) + .map(|(ident, typ)| UnresolvedGeneric::Numeric { ident, typ }) +} + +pub(super) fn generic_type() -> impl NoirParser { + ident().map(UnresolvedGeneric::Variable) +} + +pub(super) fn generic() -> impl NoirParser { + generic_type().or(numeric_generic()) +} + /// non_empty_ident_list: ident ',' non_empty_ident_list /// | ident /// /// generics: '<' non_empty_ident_list '>' /// | %empty -pub(super) fn generics() -> impl NoirParser> { - ident() +pub(super) fn generics() -> impl NoirParser { + generic() .separated_by(just(Token::Comma)) .allow_trailing() - .at_least(1) .delimited_by(just(Token::Less), just(Token::Greater)) .or_not() .map(|opt| opt.unwrap_or_default()) @@ -178,6 +196,7 @@ mod test { // fn func_name(x: impl Eq) {} with error Expected an end of input but found end of input // "fn func_name(x: impl Eq) {}", "fn func_name(x: impl Eq, y : T) where T: SomeTrait + Eq {}", + "fn func_name(x: [Field; N]) {}", ], ); @@ -194,6 +213,11 @@ mod test { // A leading plus is not allowed. "fn func_name(f: Field, y : T) where T: + SomeTrait {}", "fn func_name(f: Field, y : T) where T: TraitX + {}", + // Test ill-formed numeric generics + "fn func_name(y: T) {}", + "fn func_name(y: T) {}", + "fn func_name(y: T) {}", + "fn func_name(y: T) {}", ], ); } diff --git a/compiler/noirc_frontend/src/parser/parser/primitives.rs b/compiler/noirc_frontend/src/parser/parser/primitives.rs index 9da19c0a185..88f9e591aba 100644 --- a/compiler/noirc_frontend/src/parser/parser/primitives.rs +++ b/compiler/noirc_frontend/src/parser/parser/primitives.rs @@ -94,6 +94,13 @@ pub(super) fn variable_no_turbofish() -> impl NoirParser { path().map(|path| ExpressionKind::Variable(path, None)) } +pub(super) fn macro_quote_marker() -> impl NoirParser { + token_kind(TokenKind::UnquoteMarker).map(|token| match token { + Token::UnquoteMarker(expr_id) => ExpressionKind::Resolved(expr_id), + other => unreachable!("Non-unquote-marker parsed as an unquote marker: {other:?}"), + }) +} + #[cfg(test)] mod test { use crate::parser::parser::{ diff --git a/compiler/noirc_frontend/src/parser/parser/types.rs b/compiler/noirc_frontend/src/parser/parser/types.rs index a961b32f9d7..32929312d54 100644 --- a/compiler/noirc_frontend/src/parser/parser/types.rs +++ b/compiler/noirc_frontend/src/parser/parser/types.rs @@ -1,3 +1,4 @@ +use super::primitives::token_kind; use super::{ expression_with_precedence, keyword, nothing, parenthesized, path, NoirParser, ParserError, ParserErrorReason, Precedence, @@ -6,7 +7,7 @@ use crate::ast::{Recoverable, UnresolvedType, UnresolvedTypeData, UnresolvedType use crate::QuotedType; use crate::parser::labels::ParsingRuleLabel; -use crate::token::{Keyword, Token}; +use crate::token::{Keyword, Token, TokenKind}; use chumsky::prelude::*; use noirc_errors::Span; @@ -24,9 +25,11 @@ pub(super) fn parse_type_inner<'a>( bool_type(), string_type(), expr_type(), - type_definition_type(), + struct_definition_type(), top_level_item_type(), + type_of_quoted_types(), quoted_type(), + resolved_type(), format_string_type(recursive_type_parser.clone()), named_type(recursive_type_parser.clone()), named_trait(recursive_type_parser.clone()), @@ -77,10 +80,10 @@ pub(super) fn expr_type() -> impl NoirParser { .map_with_span(|_, span| UnresolvedTypeData::Quoted(QuotedType::Expr).with_span(span)) } -/// This is the type `TypeDefinition` - the type of a quoted type definition -pub(super) fn type_definition_type() -> impl NoirParser { - keyword(Keyword::TypeDefinition).map_with_span(|_, span| { - UnresolvedTypeData::Quoted(QuotedType::TypeDefinition).with_span(span) +/// This is the type `StructDefinition` - the type of a quoted struct definition +pub(super) fn struct_definition_type() -> impl NoirParser { + keyword(Keyword::StructDefinition).map_with_span(|_, span| { + UnresolvedTypeData::Quoted(QuotedType::StructDefinition).with_span(span) }) } @@ -93,11 +96,27 @@ fn top_level_item_type() -> impl NoirParser { } /// This is the type `Type` - the type of a quoted noir type. -fn quoted_type() -> impl NoirParser { +fn type_of_quoted_types() -> impl NoirParser { keyword(Keyword::TypeType) .map_with_span(|_, span| UnresolvedTypeData::Quoted(QuotedType::Type).with_span(span)) } +/// This is the type of a quoted, unparsed token stream. +fn quoted_type() -> impl NoirParser { + keyword(Keyword::Quoted) + .map_with_span(|_, span| UnresolvedTypeData::Quoted(QuotedType::Quoted).with_span(span)) +} + +/// This is the type of an already resolved type. +/// The only way this can appear in the token input is if an already resolved `Type` object +/// was spliced into a macro's token stream via the `$` operator. +fn resolved_type() -> impl NoirParser { + token_kind(TokenKind::QuotedType).map_with_span(|token, span| match token { + Token::QuotedType(id) => UnresolvedTypeData::Resolved(id).with_span(span), + _ => unreachable!("token_kind(QuotedType) guarantees we parse a quoted type"), + }) +} + pub(super) fn string_type() -> impl NoirParser { keyword(Keyword::String) .ignore_then(type_expression().delimited_by(just(Token::Less), just(Token::Greater))) diff --git a/compiler/noirc_frontend/src/resolve_locations.rs b/compiler/noirc_frontend/src/resolve_locations.rs index 5efe2e4a041..efb430b75eb 100644 --- a/compiler/noirc_frontend/src/resolve_locations.rs +++ b/compiler/noirc_frontend/src/resolve_locations.rs @@ -38,12 +38,16 @@ impl NodeInterner { location: Location, return_type_location_instead: bool, ) -> Option { - self.find_location_index(location) - .and_then(|index| self.resolve_location(index, return_type_location_instead)) - .or_else(|| self.try_resolve_trait_impl_location(location)) - .or_else(|| self.try_resolve_trait_method_declaration(location)) - .or_else(|| self.try_resolve_type_ref(location)) - .or_else(|| self.try_resolve_type_alias(location)) + // First try to find the location in the reference graph + self.find_referenced_location(location).or_else(|| { + // Otherwise fallback to the location indices + self.find_location_index(location) + .and_then(|index| self.resolve_location(index, return_type_location_instead)) + .or_else(|| self.try_resolve_trait_impl_location(location)) + .or_else(|| self.try_resolve_trait_method_declaration(location)) + .or_else(|| self.try_resolve_type_ref(location)) + .or_else(|| self.try_resolve_type_alias(location)) + }) } pub fn get_declaration_location_from(&self, location: Location) -> Option { diff --git a/compiler/noirc_frontend/src/tests.rs b/compiler/noirc_frontend/src/tests.rs index 1acf1fbf3f2..8cedeeeff0d 100644 --- a/compiler/noirc_frontend/src/tests.rs +++ b/compiler/noirc_frontend/src/tests.rs @@ -14,6 +14,7 @@ use fm::FileId; use iter_extended::vecmap; use noirc_errors::Location; +use crate::hir::comptime::InterpreterError; use crate::hir::def_collector::dc_crate::CompilationError; use crate::hir::def_collector::errors::{DefCollectorErrorKind, DuplicateType}; use crate::hir::def_map::ModuleData; @@ -49,7 +50,10 @@ pub(crate) fn remove_experimental_warnings(errors: &mut Vec<(CompilationError, F }); } -pub(crate) fn get_program(src: &str) -> (ParsedModule, Context, Vec<(CompilationError, FileId)>) { +pub(crate) fn get_program( + src: &str, + use_legacy: bool, +) -> (ParsedModule, Context, Vec<(CompilationError, FileId)>) { let root = std::path::Path::new("/"); let fm = FileManager::new(root); @@ -81,15 +85,16 @@ pub(crate) fn get_program(src: &str) -> (ParsedModule, Context, Vec<(Compilation &mut context, program.clone().into_sorted(), root_file_id, - false, - &[], // No macro processors + use_legacy, + None, // No debug_comptime_in_file + &[], // No macro processors )); } (program, context, errors) } pub(crate) fn get_program_errors(src: &str) -> Vec<(CompilationError, FileId)> { - get_program(src).2 + get_program(src, false).2 } #[test] @@ -832,7 +837,7 @@ fn check_trait_as_type_as_two_fn_parameters() { } fn get_program_captures(src: &str) -> Vec> { - let (program, context, _errors) = get_program(src); + let (program, context, _errors) = get_program(src, false); let interner = context.def_interner; let mut all_captures: Vec> = Vec::new(); for func in program.into_sorted().functions { @@ -1194,7 +1199,7 @@ fn resolve_fmt_strings() { } fn check_rewrite(src: &str, expected: &str) { - let (_program, mut context, _errors) = get_program(src); + let (_program, mut context, _errors) = get_program(src, false); let main_func_id = context.def_interner.find_function("main").unwrap(); let program = monomorphize(main_func_id, &mut context.def_interner).unwrap(); assert!(format!("{}", program) == expected); @@ -1325,14 +1330,20 @@ fn for_loop_over_array() { hello(array); } "#; - assert_eq!(get_program_errors(src).len(), 0); + let errors = get_program_errors(src); + assert_eq!(get_program_errors(src).len(), 1); + + assert!(matches!( + errors[0].0, + CompilationError::ResolverError(ResolverError::UseExplicitNumericGeneric { .. }) + )); } // Regression for #4545 #[test] fn type_aliases_in_main() { let src = r#" - type Outer = [u8; N]; + type Outer = [u8; N]; fn main(_arg: Outer<1>) {} "#; assert_eq!(get_program_errors(src).len(), 0); @@ -1444,3 +1455,658 @@ fn specify_method_types_with_turbofish() { let errors = get_program_errors(src); assert_eq!(errors.len(), 0); } + +#[test] +fn incorrect_turbofish_count_function_call() { + let src = r#" + trait Default { + fn default() -> Self; + } + + impl Default for Field { + fn default() -> Self { 0 } + } + + impl Default for u64 { + fn default() -> Self { 0 } + } + + // Need the above as we don't have access to the stdlib here. + // We also need to construct a concrete value of `U` without giving away its type + // as otherwise the unspecified type is ignored. + + fn generic_func() -> (T, U) where T: Default, U: Default { + (T::default(), U::default()) + } + + fn main() { + let _ = generic_func::(); + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + assert!(matches!( + errors[0].0, + CompilationError::TypeError(TypeCheckError::IncorrectTurbofishGenericCount { .. }), + )); +} + +#[test] +fn incorrect_turbofish_count_method_call() { + let src = r#" + trait Default { + fn default() -> Self; + } + + impl Default for Field { + fn default() -> Self { 0 } + } + + // Need the above as we don't have access to the stdlib here. + // We also need to construct a concrete value of `U` without giving away its type + // as otherwise the unspecified type is ignored. + + struct Foo { + inner: T + } + + impl Foo { + fn generic_method(_self: Self) -> U where U: Default { + U::default() + } + } + + fn main() { + let foo: Foo = Foo { inner: 1 }; + let _ = foo.generic_method::(); + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + assert!(matches!( + errors[0].0, + CompilationError::TypeError(TypeCheckError::IncorrectTurbofishGenericCount { .. }), + )); +} + +#[test] +fn struct_numeric_generic_in_function() { + let src = r#" + struct Foo { + inner: u64 + } + + fn bar() { } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + assert!(matches!( + errors[0].0, + CompilationError::ResolverError(ResolverError::UnsupportedNumericGenericType { .. }), + )); +} + +#[test] +fn struct_numeric_generic_in_struct() { + let src = r#" + struct Foo { + inner: u64 + } + + struct Bar { } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + assert!(matches!( + errors[0].0, + CompilationError::DefinitionError( + DefCollectorErrorKind::UnsupportedNumericGenericType { .. } + ), + )); +} + +#[test] +fn bool_numeric_generic() { + let src = r#" + fn read() -> Field { + if N { + 0 + } else { + 1 + } + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + assert!(matches!( + errors[0].0, + CompilationError::ResolverError(ResolverError::UnsupportedNumericGenericType { .. }), + )); +} + +#[test] +fn numeric_generic_binary_operation_type_mismatch() { + let src = r#" + fn foo() -> bool { + let mut check: bool = true; + check = N; + check + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + assert!(matches!( + errors[0].0, + CompilationError::TypeError(TypeCheckError::TypeMismatchWithSource { .. }), + )); +} + +#[test] +fn bool_generic_as_loop_bound() { + let src = r#" + fn read() { + let mut fields = [0; N]; + for i in 0..N { + fields[i] = i + 1; + } + assert(fields[0] == 1); + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 2); + + assert!(matches!( + errors[0].0, + CompilationError::ResolverError(ResolverError::UnsupportedNumericGenericType { .. }), + )); + + let CompilationError::TypeError(TypeCheckError::TypeMismatch { + expected_typ, expr_typ, .. + }) = &errors[1].0 + else { + panic!("Got an error other than a type mismatch"); + }; + + assert_eq!(expected_typ, "Field"); + assert_eq!(expr_typ, "bool"); +} + +#[test] +fn numeric_generic_in_function_signature() { + let src = r#" + fn foo(arr: [Field; N]) -> [Field; N] { arr } + "#; + let errors = get_program_errors(src); + assert!(errors.is_empty()); +} + +#[test] +fn numeric_generic_as_struct_field_type() { + let src = r#" + struct Foo { + a: Field, + b: N, + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + assert!(matches!( + errors[0].0, + CompilationError::ResolverError(ResolverError::NumericGenericUsedForType { .. }), + )); +} + +#[test] +fn normal_generic_as_array_length() { + let src = r#" + struct Foo { + a: Field, + b: [Field; N], + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + // TODO(https://github.com/noir-lang/noir/issues/5156): This should be switched to a hard type error rather than + // the `UseExplicitNumericGeneric` once implicit numeric generics are removed. + assert!(matches!( + errors[0].0, + CompilationError::ResolverError(ResolverError::UseExplicitNumericGeneric { .. }), + )); +} + +#[test] +fn numeric_generic_as_param_type() { + let src = r#" + fn foo(x: I) -> I { + let _q: I = 5; + x + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 3); + // Error from the parameter type + assert!(matches!( + errors[0].0, + CompilationError::ResolverError(ResolverError::NumericGenericUsedForType { .. }), + )); + // Error from the let statement annotated type + assert!(matches!( + errors[1].0, + CompilationError::ResolverError(ResolverError::NumericGenericUsedForType { .. }), + )); + // Error from the return type + assert!(matches!( + errors[2].0, + CompilationError::ResolverError(ResolverError::NumericGenericUsedForType { .. }), + )); +} + +#[test] +fn numeric_generic_used_in_nested_type_fail() { + let src = r#" + struct Foo { + a: Field, + b: Bar, + } + struct Bar { + inner: N + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + assert!(matches!( + errors[0].0, + CompilationError::ResolverError(ResolverError::NumericGenericUsedForType { .. }), + )); +} + +#[test] +fn normal_generic_used_in_nested_array_length_fail() { + let src = r#" + struct Foo { + a: Field, + b: Bar, + } + struct Bar { + inner: [Field; N] + } + "#; + let errors = get_program_errors(src); + // TODO(https://github.com/noir-lang/noir/issues/5156): This should be switched to a hard type error once implicit numeric generics are removed. + assert_eq!(errors.len(), 0); +} + +#[test] +fn numeric_generic_used_in_nested_type_pass() { + // The order of these structs should not be changed to make sure + // that we are accurately resolving all struct generics before struct fields + let src = r#" + struct NestedNumeric { + a: Field, + b: InnerNumeric + } + struct InnerNumeric { + inner: [u64; N], + } + "#; + let errors = get_program_errors(src); + assert!(errors.is_empty()); +} + +#[test] +fn numeric_generic_used_in_trait() { + let src = r#" + struct MyType { + a: Field, + b: Field, + c: Field, + d: T, + } + + impl Deserialize for MyType { + fn deserialize(fields: [Field; N], other: T) -> Self { + MyType { a: fields[0], b: fields[1], c: fields[2], d: other } + } + } + + trait Deserialize { + fn deserialize(fields: [Field; N], other: T) -> Self; + } + "#; + let errors = get_program_errors(src); + // We want to make sure that `N` in `impl Deserialize` does + // not trigger `expected type, found numeric generic parameter N` as the trait + // does in fact expect a numeric generic. + assert!(errors.is_empty()); +} + +#[test] +fn numeric_generic_in_trait_impl_with_extra_impl_generics() { + let src = r#" + trait Default { + fn default() -> Self; + } + + struct MyType { + a: Field, + b: Field, + c: Field, + d: T, + } + + // Make sure that `T` is placed before `N` as we want to test that the order of the generics is correctly maintained. + // `N` is used first in the trait impl generics (`Deserialize for MyType`). + // We want to make sure that the compiler correctly accounts for that `N` has a numeric kind + // while `T` has a normal kind. + impl Deserialize for MyType where T: Default { + fn deserialize(fields: [Field; N]) -> Self { + MyType { a: fields[0], b: fields[1], c: fields[2], d: T::default() } + } + } + + trait Deserialize { + fn deserialize(fields: [Field; N]) -> Self; + } + "#; + let errors = get_program_errors(src); + assert!(errors.is_empty()); +} + +#[test] +fn numeric_generic_used_in_where_clause() { + let src = r#" + trait Deserialize { + fn deserialize(fields: [Field; N]) -> Self; + } + + fn read() -> T where T: Deserialize { + let mut fields: [Field; N] = [0; N]; + for i in 0..N { + fields[i] = i as Field + 1; + } + T::deserialize(fields) + } + "#; + let errors = get_program_errors(src); + assert!(errors.is_empty()); +} + +#[test] +fn numeric_generic_used_in_turbofish() { + let src = r#" + fn double() -> u32 { + // Used as an expression + N * 2 + } + + fn double_numeric_generics_test() { + // Example usage of a numeric generic arguments. + assert(double::<9>() == 18); + assert(double::<7 + 8>() == 30); + } + "#; + let errors = get_program_errors(src); + assert!(errors.is_empty()); +} + +#[test] +fn constant_used_with_numeric_generic() { + let src = r#" + struct ValueNote { + value: Field, + } + + trait Serialize { + fn serialize(self) -> [Field; N]; + } + + impl Serialize<1> for ValueNote { + fn serialize(self) -> [Field; 1] { + [self.value] + } + } + "#; + let errors = get_program_errors(src); + assert!(errors.is_empty()); +} + +#[test] +fn normal_generic_used_when_numeric_expected_in_where_clause() { + let src = r#" + trait Deserialize { + fn deserialize(fields: [Field; N]) -> Self; + } + + fn read() -> T where T: Deserialize { + T::deserialize([0, 1]) + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + assert!(matches!( + errors[0].0, + CompilationError::TypeError(TypeCheckError::TypeMismatch { .. }), + )); + + let src = r#" + trait Deserialize { + fn deserialize(fields: [Field; N]) -> Self; + } + + fn read() -> T where T: Deserialize { + let mut fields: [Field; N] = [0; N]; + for i in 0..N { + fields[i] = i as Field + 1; + } + T::deserialize(fields) + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + assert!(matches!( + errors[0].0, + CompilationError::ResolverError(ResolverError::VariableNotDeclared { .. }), + )); +} + +// TODO(https://github.com/noir-lang/noir/issues/5156): Remove this test once we ban implicit numeric generics +#[test] +fn implicit_numeric_generics_elaborator() { + let src = r#" + struct BoundedVec { + storage: [T; MaxLen], + len: u64, + } + + impl BoundedVec { + + // Test that we have an implicit numeric generic for "Len" as well as "MaxLen" + pub fn extend_from_bounded_vec(&mut self, _vec: BoundedVec) { + // We do this to avoid an unused variable warning on `self` + let _ = self.len; + for _ in 0..Len { } + } + + pub fn push(&mut self, elem: T) { + assert(self.len < MaxLen, "push out of bounds"); + self.storage[self.len] = elem; + self.len += 1; + } + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 4); + + for error in errors.iter() { + if let CompilationError::ResolverError(ResolverError::UseExplicitNumericGeneric { ident }) = + &errors[0].0 + { + assert!(matches!(ident.0.contents.as_str(), "MaxLen" | "Len")); + } else { + panic!("Expected ResolverError::UseExplicitNumericGeneric but got {:?}", error); + } + } +} + +#[test] +fn quote_code_fragments() { + // This test ensures we can quote (and unquote/splice) code fragments + // which by themselves are not valid code. They only need to be valid + // by the time they are unquoted into the macro's call site. + let src = r#" + fn main() { + comptime { + concat!(quote { assert( }, quote { false); }); + } + } + + comptime fn concat(a: Quoted, b: Quoted) -> Quoted { + quote { $a $b } + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + use InterpreterError::FailingConstraint; + assert!(matches!(&errors[0].0, CompilationError::InterpreterError(FailingConstraint { .. }))); +} + +// Regression for #5388 +#[test] +fn comptime_let() { + let src = r#"fn main() { + comptime let my_var = 2; + assert_eq(my_var, 2); + }"#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 0); +} + +#[test] +fn overflowing_u8() { + let src = r#" + fn main() { + let _: u8 = 256; + }"#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + if let CompilationError::TypeError(error) = &errors[0].0 { + assert_eq!( + error.to_string(), + "The value `2⁸` cannot fit into `u8` which has range `0..=255`" + ); + } else { + panic!("Expected OverflowingAssignment error, got {:?}", errors[0].0); + } +} + +#[test] +fn underflowing_u8() { + let src = r#" + fn main() { + let _: u8 = -1; + }"#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + if let CompilationError::TypeError(error) = &errors[0].0 { + assert_eq!( + error.to_string(), + "The value `-1` cannot fit into `u8` which has range `0..=255`" + ); + } else { + panic!("Expected OverflowingAssignment error, got {:?}", errors[0].0); + } +} + +#[test] +fn overflowing_i8() { + let src = r#" + fn main() { + let _: i8 = 128; + }"#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + if let CompilationError::TypeError(error) = &errors[0].0 { + assert_eq!( + error.to_string(), + "The value `2⁷` cannot fit into `i8` which has range `-128..=127`" + ); + } else { + panic!("Expected OverflowingAssignment error, got {:?}", errors[0].0); + } +} + +#[test] +fn underflowing_i8() { + let src = r#" + fn main() { + let _: i8 = -129; + }"#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + if let CompilationError::TypeError(error) = &errors[0].0 { + assert_eq!( + error.to_string(), + "The value `-129` cannot fit into `i8` which has range `-128..=127`" + ); + } else { + panic!("Expected OverflowingAssignment error, got {:?}", errors[0].0); + } +} + +#[test] +fn turbofish_numeric_generic_nested_call() { + // Check for turbofish numeric generics used with function calls + let src = r#" + fn foo() -> [u8; N] { + [0; N] + } + + fn bar() -> [u8; N] { + foo::() + } + + global M: u32 = 3; + + fn main() { + let _ = bar::(); + } + "#; + let errors = get_program_errors(src); + assert!(errors.is_empty()); + + // Check for turbofish numeric generics used with method calls + let src = r#" + struct Foo { + a: T + } + + impl Foo { + fn static_method() -> [u8; N] { + [0; N] + } + + fn impl_method(self) -> [T; N] { + [self.a; N] + } + } + + fn bar() -> [u8; N] { + let _ = Foo::static_method::(); + let x: Foo = Foo { a: 0 }; + x.impl_method::() + } + + global M: u32 = 3; + + fn main() { + let _ = bar::(); + } + "#; + let errors = get_program_errors(src); + assert!(errors.is_empty()); +} diff --git a/cspell.json b/cspell.json index 2fb20ae2ba4..2a9bfb4b544 100644 --- a/cspell.json +++ b/cspell.json @@ -148,8 +148,10 @@ "nomicfoundation", "noncanonical", "nouner", + "overflowing", "pedersen", "peekable", + "petgraph", "plonkc", "PLONKish", "pprof", @@ -161,6 +163,7 @@ "pseudocode", "pubkey", "quantile", + "rangemap", "repr", "reqwest", "rfind", @@ -185,6 +188,7 @@ "subtyping", "swcurve", "Taiko", + "tarjan", "tecurve", "tempdir", "tempfile", @@ -203,6 +207,7 @@ "urem", "USERPROFILE", "vecmap", + "vitkov", "wasi", "wasmer", "Weierstraß", diff --git a/deny.toml b/deny.toml index db7e53cad24..2d6d3e658b5 100644 --- a/deny.toml +++ b/deny.toml @@ -73,7 +73,7 @@ exceptions = [ { allow = ["CC0-1.0"], name = "tiny-keccak" }, { allow = ["MPL-2.0"], name = "sized-chunks" }, { allow = ["MPL-2.0"], name = "webpki-roots" }, - + { allow = ["CDDL-1.0"], name = "inferno" }, ] [[licenses.clarify]] diff --git a/docs/docs/how_to/how-to-oracles.md b/docs/docs/how_to/how-to-oracles.md index 2d2ed5c94b9..df41276cfe1 100644 --- a/docs/docs/how_to/how-to-oracles.md +++ b/docs/docs/how_to/how-to-oracles.md @@ -236,9 +236,9 @@ const foreignCallHandler = async (name, input) => { // notice that the "inputs" parameter contains *all* the inputs // in this case we to make the RPC request with the first parameter "numbers", which would be input[0] const oracleReturn = await client.request(name, [ - { Array: input[0].map((i) => i.toString("hex")) }, + input[0].map((i) => i.toString("hex")), ]); - return [oracleReturn.values[0].Array]; + return { values: oracleReturn }; }; // the rest of your NoirJS code diff --git a/docs/docs/noir/concepts/assert.md b/docs/docs/noir/concepts/assert.md index bcff613a695..2132de42072 100644 --- a/docs/docs/noir/concepts/assert.md +++ b/docs/docs/noir/concepts/assert.md @@ -1,9 +1,9 @@ --- title: Assert Function description: - Learn about the assert function in Noir, which can be used to explicitly constrain the predicate or - comparison expression that follows to be true, and what happens if the expression is false at - runtime. + Learn about the `assert` and `static_assert` functions in Noir, which can be used to explicitly + constrain the predicate or comparison expression that follows to be true, and what happens if + the expression is false at runtime or compile-time, respectively. keywords: [Noir programming language, assert statement, predicate expression, comparison expression] sidebar_position: 4 --- @@ -43,3 +43,36 @@ let s = myStruct { myField: y }; assert(s.myField == x, s); ``` +There is also a special `static_assert` function that behaves like `assert`, +but that runs at compile-time. + +```rust +fn main(xs: [Field; 3]) { + let x = 2 + 2; + let y = 4; + static_assert(x == y, "expected 2 + 2 to equal 4"); + + // This passes since the length of `xs` is known at compile-time + static_assert(xs.len() == 3, "expected the input to have 3 elements"); +} +``` + +This function fails when passed a dynamic (run-time) argument: + +```rust +fn main(x : Field, y : Field) { + // this fails because `x` is not known at compile-time + static_assert(x == 2, "expected x to be known at compile-time and equal to 2"); + + let mut example_slice = &[]; + if y == 4 { + example_slice = example_slice.push_back(0); + } + + // This fails because the length of `example_slice` is not known at + // compile-time + let error_message = "expected an empty slice, known at compile-time"; + static_assert(example_slice.len() == 0, error_message); +} +``` + diff --git a/docs/docs/noir/concepts/data_types/arrays.md b/docs/docs/noir/concepts/data_types/arrays.md index 95d749053e2..9a4ab5d3c1f 100644 --- a/docs/docs/noir/concepts/data_types/arrays.md +++ b/docs/docs/noir/concepts/data_types/arrays.md @@ -199,7 +199,7 @@ fn main() { ### reduce -Same as fold, but uses the first element as starting element. +Same as fold, but uses the first element as the starting element. ```rust fn reduce(self, f: fn(T, T) -> T) -> T diff --git a/docs/docs/noir/concepts/data_types/slices.mdx b/docs/docs/noir/concepts/data_types/slices.mdx index dff08d63ffb..95da2030843 100644 --- a/docs/docs/noir/concepts/data_types/slices.mdx +++ b/docs/docs/noir/concepts/data_types/slices.mdx @@ -191,3 +191,168 @@ fn main() { assert(array[1] == slice[1]); } ``` + +### map + +Applies a function to each element of the slice, returning a new slice containing the mapped elements. + +```rust +fn map(self, f: fn[Env](T) -> U) -> [U] +``` + +example + +```rust +let a = &[1, 2, 3]; +let b = a.map(|a| a * 2); // b is now &[2, 4, 6] +``` + +### fold + +Applies a function to each element of the slice, returning the final accumulated value. The first +parameter is the initial value. + +```rust +fn fold(self, mut accumulator: U, f: fn[Env](U, T) -> U) -> U +``` + +This is a left fold, so the given function will be applied to the accumulator and first element of +the slice, then the second, and so on. For a given call the expected result would be equivalent to: + +```rust +let a1 = &[1]; +let a2 = &[1, 2]; +let a3 = &[1, 2, 3]; + +let f = |a, b| a - b; +a1.fold(10, f) //=> f(10, 1) +a2.fold(10, f) //=> f(f(10, 1), 2) +a3.fold(10, f) //=> f(f(f(10, 1), 2), 3) +``` + +example: + +```rust + +fn main() { + let slice = &[2, 2, 2, 2, 2]; + let folded = slice.fold(0, |a, b| a + b); + assert(folded == 10); +} + +``` + +### reduce + +Same as fold, but uses the first element as the starting element. + +```rust +fn reduce(self, f: fn[Env](T, T) -> T) -> T +``` + +example: + +```rust +fn main() { + let slice = &[2, 2, 2, 2, 2]; + let reduced = slice.reduce(|a, b| a + b); + assert(reduced == 10); +} +``` + +### filter + +Returns a new slice containing only elements for which the given predicate returns true. + +```rust +fn filter(self, f: fn[Env](T) -> bool) -> Self +``` + +example: + +```rust +fn main() { + let slice = &[1, 2, 3, 4, 5]; + let odds = slice.filter(|x| x % 2 == 1); + assert_eq(odds, &[1, 3, 5]); +} +``` + +### join + +Flatten each element in the slice into one value, separated by `separator`. + +Note that although slices implement `Append`, `join` cannot be used on slice +elements since nested slices are prohibited. + +```rust +fn join(self, separator: T) -> T where T: Append +``` + +example: + +```rust +struct Accumulator { + total: Field, +} + +// "Append" two accumulators by adding them +impl Append for Accumulator { + fn empty() -> Self { + Self { total: 0 } + } + + fn append(self, other: Self) -> Self { + Self { total: self.total + other.total } + } +} + +fn main() { + let slice = &[1, 2, 3, 4, 5].map(|total| Accumulator { total }); + + let result = slice.join(Accumulator::empty()); + assert_eq(result, Accumulator { total: 15 }); + + // We can use a non-empty separator to insert additional elements to sum: + let separator = Accumulator { total: 10 }; + let result = slice.join(separator); + assert_eq(result, Accumulator { total: 55 }); +} +``` + +### all + +Returns true if all the elements satisfy the given predicate + +```rust +fn all(self, predicate: fn[Env](T) -> bool) -> bool +``` + +example: + +```rust +fn main() { + let slice = &[2, 2, 2, 2, 2]; + let all = slice.all(|a| a == 2); + assert(all); +} +``` + +### any + +Returns true if any of the elements satisfy the given predicate + +```rust +fn any(self, predicate: fn[Env](T) -> bool) -> bool +``` + +example: + +```rust +fn main() { + let slice = &[2, 2, 2, 2, 5]; + let any = slice.any(|a| a == 5); + assert(any); +} + +``` diff --git a/docs/docs/noir/concepts/traits.md b/docs/docs/noir/concepts/traits.md index df7cb9ebda0..51305b38c16 100644 --- a/docs/docs/noir/concepts/traits.md +++ b/docs/docs/noir/concepts/traits.md @@ -147,7 +147,7 @@ fn main() { ### Generic Trait Implementations With Where Clauses -Where clauses can also be placed on trait implementations themselves to restrict generics in a similar way. +Where clauses can be placed on trait implementations themselves to restrict generics in a similar way. For example, while `impl Foo for T` implements the trait `Foo` for every type, `impl Foo for T where T: Bar` will implement `Foo` only for types that also implement `Bar`. This is often used for implementing generic types. For example, here is the implementation for array equality: @@ -169,6 +169,22 @@ impl Eq for [T; N] where T: Eq { } ``` +Where clauses can also be placed on struct implementations. +For example, here is a method utilizing a generic type that implements the equality trait. + +```rust +struct Foo { + a: u32, + b: T, +} + +impl Foo where T: Eq { + fn eq(self, other: Self) -> bool { + (self.a == other.a) & self.b.eq(other.b) + } +} +``` + ## Generic Traits Traits themselves can also be generic by placing the generic arguments after the trait name. These generics are in diff --git a/docs/docs/noir/modules_packages_crates/modules.md b/docs/docs/noir/modules_packages_crates/modules.md index ae822a1cff4..9fffd925b7b 100644 --- a/docs/docs/noir/modules_packages_crates/modules.md +++ b/docs/docs/noir/modules_packages_crates/modules.md @@ -49,6 +49,27 @@ crate ``` +The module filename may also be the name of the module as a directory with the contents in a +file named `mod.nr` within that directory. The above example can alternatively be expressed like this: + +Filename : `src/main.nr` + +```rust +mod foo; + +fn main() { + foo::hello_world(); +} +``` + +Filename : `src/foo/mod.nr` + +```rust +fn from_foo() {} +``` + +Note that it's an error to have both files `src/foo.nr` and `src/foo/mod.nr` in the filesystem. + ### Importing a module throughout the tree All modules are accessible from the `crate::` namespace. @@ -103,3 +124,28 @@ crate └── bar └── from_bar ``` + +Similar to importing a module in the crate root, modules can be placed in a `mod.nr` file, like this: + +Filename : `src/main.nr` + +```rust +mod foo; + +fn main() { + foo::from_foo(); +} +``` + +Filename : `src/foo/mod.nr` + +```rust +mod bar; +fn from_foo() {} +``` + +Filename : `src/foo/bar/mod.nr` + +```rust +fn from_bar() {} +``` \ No newline at end of file diff --git a/docs/docs/noir/standard_library/cryptographic_primitives/ec_primitives.md b/docs/docs/noir/standard_library/cryptographic_primitives/ec_primitives.md index f839b4a228e..f262d8160d6 100644 --- a/docs/docs/noir/standard_library/cryptographic_primitives/ec_primitives.md +++ b/docs/docs/noir/standard_library/cryptographic_primitives/ec_primitives.md @@ -18,7 +18,7 @@ curve you want to use, which would be specified using any one of the methods `std::ec::{tecurve,montcurve,swcurve}::{affine,curvegroup}::new` which take the coefficients in the defining equation together with a generator point as parameters. You can find more detail in the comments in -[`noir_stdlib/src/ec.nr`](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec.nr), but +[`noir_stdlib/src/ec/mod.nr`](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec/mod.nr), but the gist of it is that the elliptic curves of interest are usually expressed in one of the standard forms implemented here (Twisted Edwards, Montgomery and Short Weierstraß), and in addition to that, you could choose to use `affine` coordinates (Cartesian coordinates - the usual (x,y) - possibly @@ -67,7 +67,7 @@ does indeed lie on `c` by calling `c.contains(p1)`. the curve configurations, the SWU map-to-curve method may be called as `c.swu_map(z,n)`, where `z: Field` depends on `Field` and `c` and must be chosen by the user (the conditions it needs to satisfy are specified in the comments - [here](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec.nr)). + [here](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec/mod.nr)). ## Examples diff --git a/docs/docs/noir/standard_library/cryptographic_primitives/hashes.mdx b/docs/docs/noir/standard_library/cryptographic_primitives/hashes.mdx index efa52b2c3f2..0abd7a12a22 100644 --- a/docs/docs/noir/standard_library/cryptographic_primitives/hashes.mdx +++ b/docs/docs/noir/standard_library/cryptographic_primitives/hashes.mdx @@ -15,7 +15,7 @@ import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; Given an array of bytes, returns the resulting sha256 hash. Specify a message_size to hash only the first `message_size` bytes of the input. -#include_code sha256 noir_stdlib/src/hash.nr rust +#include_code sha256 noir_stdlib/src/hash/mod.nr rust example: #include_code sha256_var test_programs/execution_success/sha256/src/main.nr rust @@ -34,7 +34,7 @@ fn main() { Given an array of bytes, returns an array with the Blake2 hash -#include_code blake2s noir_stdlib/src/hash.nr rust +#include_code blake2s noir_stdlib/src/hash/mod.nr rust example: @@ -51,7 +51,7 @@ fn main() { Given an array of bytes, returns an array with the Blake3 hash -#include_code blake3 noir_stdlib/src/hash.nr rust +#include_code blake3 noir_stdlib/src/hash/mod.nr rust example: @@ -68,7 +68,7 @@ fn main() { Given an array of Fields, returns the Pedersen hash. -#include_code pedersen_hash noir_stdlib/src/hash.nr rust +#include_code pedersen_hash noir_stdlib/src/hash/mod.nr rust example: @@ -80,7 +80,7 @@ example: Given an array of Fields, returns the Pedersen commitment. -#include_code pedersen_commitment noir_stdlib/src/hash.nr rust +#include_code pedersen_commitment noir_stdlib/src/hash/mod.nr rust example: @@ -94,7 +94,7 @@ Given an array of bytes (`u8`), returns the resulting keccak hash as an array of 32 bytes (`[u8; 32]`). Specify a message_size to hash only the first `message_size` bytes of the input. -#include_code keccak256 noir_stdlib/src/hash.nr rust +#include_code keccak256 noir_stdlib/src/hash/mod.nr rust example: @@ -127,7 +127,9 @@ function, there is only one hash and you can specify a message_size to hash only Poseidon2::hash(input, 3); ``` -The above example for Poseidon also includes Poseidon2. +example: + +#include_code poseidon2 test_programs/execution_success/poseidon2/src/main.nr rust ## mimc_bn254 and mimc diff --git a/docs/docs/noir/standard_library/is_unconstrained.md b/docs/docs/noir/standard_library/is_unconstrained.md index bb157e719dc..51bb1bda8f1 100644 --- a/docs/docs/noir/standard_library/is_unconstrained.md +++ b/docs/docs/noir/standard_library/is_unconstrained.md @@ -56,4 +56,14 @@ pub fn external_interface(){ ``` -The is_unconstrained result is resolved at compile time, so in unconstrained contexts the compiler removes the else branch, and in constrained contexts the compiler removes the if branch, reducing the amount of compute necessary to run external_interface. \ No newline at end of file +The is_unconstrained result is resolved at compile time, so in unconstrained contexts the compiler removes the else branch, and in constrained contexts the compiler removes the if branch, reducing the amount of compute necessary to run external_interface. + +Note that using `is_unconstrained` in a `comptime` context will also return `true`: + +``` +fn main() { + comptime { + assert(is_unconstrained()); + } +} +``` diff --git a/docs/docs/noir/standard_library/traits.md b/docs/docs/noir/standard_library/traits.md index 96a7b8e2f22..e6f6f80ff03 100644 --- a/docs/docs/noir/standard_library/traits.md +++ b/docs/docs/noir/standard_library/traits.md @@ -51,6 +51,7 @@ For primitive integer types, the return value of `default` is `0`. Container types such as arrays are filled with default values of their element type, except slices whose length is unknown and thus defaulted to zero. +--- ## `std::convert` @@ -85,6 +86,7 @@ For this reason, implementing `From` on a type will automatically generate a mat `Into` is most useful when passing function arguments where the types don't quite match up with what the function expects. In this case, the compiler has enough type information to perform the necessary conversion by just appending `.into()` onto the arguments in question. +--- ## `std::cmp` @@ -178,6 +180,8 @@ impl Ord for (A, B, C, D, E) where A: Ord, B: Ord, C: Ord, D: Ord, E: Ord { .. } ``` +--- + ## `std::ops` ### `std::ops::Add`, `std::ops::Sub`, `std::ops::Mul`, and `std::ops::Div` @@ -301,3 +305,29 @@ impl Shl for u16 { fn shl(self, other: u16) -> u16 { self << other } } impl Shl for u32 { fn shl(self, other: u32) -> u32 { self << other } } impl Shl for u64 { fn shl(self, other: u64) -> u64 { self << other } } ``` + +--- + +## `std::append` + +### `std::append::Append` + +`Append` can abstract over types that can be appended to - usually container types: + +#include_code append-trait noir_stdlib/src/append.nr rust + +`Append` requires two methods: + +- `empty`: Constructs an empty value of `Self`. +- `append`: Append two values together, returning the result. + +Additionally, it is expected that for any implementation: + +- `T::empty().append(x) == x` +- `x.append(T::empty()) == x` + +Implementations: +```rust +impl Append for [T] +impl Append for Quoted +``` diff --git a/docs/docs/tooling/testing.md b/docs/docs/tooling/testing.md index d3e0c522473..866677da567 100644 --- a/docs/docs/tooling/testing.md +++ b/docs/docs/tooling/testing.md @@ -42,7 +42,7 @@ fn test_add() { } ``` -You can be more specific and make it fail with a specific reason by using `should_fail_with = "`: +You can be more specific and make it fail with a specific reason by using `should_fail_with = ""`: ```rust fn main(african_swallow_avg_speed : Field) { @@ -58,5 +58,22 @@ fn test_king_arthur() { fn test_bridgekeeper() { main(32); } - ``` + +The string given to `should_fail_with` doesn't need to exactly match the failure reason, it just needs to be a substring of it: + +```rust +fn main(african_swallow_avg_speed : Field) { + assert(african_swallow_avg_speed == 65, "What is the airspeed velocity of an unladen swallow"); +} + +#[test] +fn test_king_arthur() { + main(65); +} + +#[test(should_fail_with = "airspeed velocity")] +fn test_bridgekeeper() { + main(32); +} +``` \ No newline at end of file diff --git a/docs/src/pages/index.jsx b/docs/src/pages/index.jsx index c7de44b300d..e6532b1db85 100644 --- a/docs/src/pages/index.jsx +++ b/docs/src/pages/index.jsx @@ -36,38 +36,6 @@ export default function Landing() { - -

-
-

Learn

- - - - - - -
-
-

Coming from...

- - - - - - -
-
-

New to Everything

- - - - - - -
-
diff --git a/docs/versioned_docs/version-v0.17.0/standard_library/cryptographic_primitives/04_ec_primitives.md b/docs/versioned_docs/version-v0.17.0/standard_library/cryptographic_primitives/04_ec_primitives.md index d3af3cf7c3b..0f431e40056 100644 --- a/docs/versioned_docs/version-v0.17.0/standard_library/cryptographic_primitives/04_ec_primitives.md +++ b/docs/versioned_docs/version-v0.17.0/standard_library/cryptographic_primitives/04_ec_primitives.md @@ -17,7 +17,7 @@ curve you want to use, which would be specified using any one of the methods `std::ec::{tecurve,montcurve,swcurve}::{affine,curvegroup}::new` which take the coefficients in the defining equation together with a generator point as parameters. You can find more detail in the comments in -[`noir_stdlib/src/ec.nr`](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec.nr), but +[`noir_stdlib/src/ec.nr`](https://github.com/noir-lang/noir/blob/v0.17.0/noir_stdlib/src/ec.nr), but the gist of it is that the elliptic curves of interest are usually expressed in one of the standard forms implemented here (Twisted Edwards, Montgomery and Short Weierstraß), and in addition to that, you could choose to use `affine` coordinates (Cartesian coordinates - the usual (x,y) - possibly @@ -66,7 +66,7 @@ does indeed lie on `c` by calling `c.contains(p1)`. the curve configurations, the SWU map-to-curve method may be called as `c.swu_map(z,n)`, where `z: Field` depends on `Field` and `c` and must be chosen by the user (the conditions it needs to satisfy are specified in the comments - [here](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec.nr)). + [here](https://github.com/noir-lang/noir/blob/v0.17.0/noir_stdlib/src/ec.nr). ## Examples diff --git a/docs/versioned_docs/version-v0.19.0/standard_library/cryptographic_primitives/04_ec_primitives.md b/docs/versioned_docs/version-v0.19.0/standard_library/cryptographic_primitives/04_ec_primitives.md index d3af3cf7c3b..a3780552682 100644 --- a/docs/versioned_docs/version-v0.19.0/standard_library/cryptographic_primitives/04_ec_primitives.md +++ b/docs/versioned_docs/version-v0.19.0/standard_library/cryptographic_primitives/04_ec_primitives.md @@ -17,7 +17,7 @@ curve you want to use, which would be specified using any one of the methods `std::ec::{tecurve,montcurve,swcurve}::{affine,curvegroup}::new` which take the coefficients in the defining equation together with a generator point as parameters. You can find more detail in the comments in -[`noir_stdlib/src/ec.nr`](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec.nr), but +[`noir_stdlib/src/ec.nr`](https://github.com/noir-lang/noir/blob/v0.19.0/noir_stdlib/src/ec.nr), but the gist of it is that the elliptic curves of interest are usually expressed in one of the standard forms implemented here (Twisted Edwards, Montgomery and Short Weierstraß), and in addition to that, you could choose to use `affine` coordinates (Cartesian coordinates - the usual (x,y) - possibly @@ -66,7 +66,7 @@ does indeed lie on `c` by calling `c.contains(p1)`. the curve configurations, the SWU map-to-curve method may be called as `c.swu_map(z,n)`, where `z: Field` depends on `Field` and `c` and must be chosen by the user (the conditions it needs to satisfy are specified in the comments - [here](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec.nr)). + [here](https://github.com/noir-lang/noir/blob/v0.19.0/noir_stdlib/src/ec.nr). ## Examples diff --git a/docs/versioned_docs/version-v0.19.1/standard_library/cryptographic_primitives/04_ec_primitives.md b/docs/versioned_docs/version-v0.19.1/standard_library/cryptographic_primitives/04_ec_primitives.md index d3af3cf7c3b..a493d52a083 100644 --- a/docs/versioned_docs/version-v0.19.1/standard_library/cryptographic_primitives/04_ec_primitives.md +++ b/docs/versioned_docs/version-v0.19.1/standard_library/cryptographic_primitives/04_ec_primitives.md @@ -17,7 +17,7 @@ curve you want to use, which would be specified using any one of the methods `std::ec::{tecurve,montcurve,swcurve}::{affine,curvegroup}::new` which take the coefficients in the defining equation together with a generator point as parameters. You can find more detail in the comments in -[`noir_stdlib/src/ec.nr`](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec.nr), but +[`noir_stdlib/src/ec.nr`](https://github.com/noir-lang/noir/blob/v0.19.1/noir_stdlib/src/ec.nr), but the gist of it is that the elliptic curves of interest are usually expressed in one of the standard forms implemented here (Twisted Edwards, Montgomery and Short Weierstraß), and in addition to that, you could choose to use `affine` coordinates (Cartesian coordinates - the usual (x,y) - possibly @@ -66,7 +66,7 @@ does indeed lie on `c` by calling `c.contains(p1)`. the curve configurations, the SWU map-to-curve method may be called as `c.swu_map(z,n)`, where `z: Field` depends on `Field` and `c` and must be chosen by the user (the conditions it needs to satisfy are specified in the comments - [here](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec.nr)). + [here](https://github.com/noir-lang/noir/blob/v0.19.1/noir_stdlib/src/ec.nr). ## Examples diff --git a/docs/versioned_docs/version-v0.19.2/standard_library/cryptographic_primitives/04_ec_primitives.md b/docs/versioned_docs/version-v0.19.2/standard_library/cryptographic_primitives/04_ec_primitives.md index d3af3cf7c3b..14ce71d4d39 100644 --- a/docs/versioned_docs/version-v0.19.2/standard_library/cryptographic_primitives/04_ec_primitives.md +++ b/docs/versioned_docs/version-v0.19.2/standard_library/cryptographic_primitives/04_ec_primitives.md @@ -17,7 +17,7 @@ curve you want to use, which would be specified using any one of the methods `std::ec::{tecurve,montcurve,swcurve}::{affine,curvegroup}::new` which take the coefficients in the defining equation together with a generator point as parameters. You can find more detail in the comments in -[`noir_stdlib/src/ec.nr`](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec.nr), but +[`noir_stdlib/src/ec.nr`](https://github.com/noir-lang/noir/blob/v0.19.2/noir_stdlib/src/ec.nr), but the gist of it is that the elliptic curves of interest are usually expressed in one of the standard forms implemented here (Twisted Edwards, Montgomery and Short Weierstraß), and in addition to that, you could choose to use `affine` coordinates (Cartesian coordinates - the usual (x,y) - possibly @@ -66,7 +66,7 @@ does indeed lie on `c` by calling `c.contains(p1)`. the curve configurations, the SWU map-to-curve method may be called as `c.swu_map(z,n)`, where `z: Field` depends on `Field` and `c` and must be chosen by the user (the conditions it needs to satisfy are specified in the comments - [here](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec.nr)). + [here](https://github.com/noir-lang/noir/blob/v0.19.2/noir_stdlib/src/ec.nr). ## Examples diff --git a/docs/versioned_docs/version-v0.19.3/standard_library/cryptographic_primitives/04_ec_primitives.md b/docs/versioned_docs/version-v0.19.3/standard_library/cryptographic_primitives/04_ec_primitives.md index d3af3cf7c3b..b4e20e091ba 100644 --- a/docs/versioned_docs/version-v0.19.3/standard_library/cryptographic_primitives/04_ec_primitives.md +++ b/docs/versioned_docs/version-v0.19.3/standard_library/cryptographic_primitives/04_ec_primitives.md @@ -17,7 +17,7 @@ curve you want to use, which would be specified using any one of the methods `std::ec::{tecurve,montcurve,swcurve}::{affine,curvegroup}::new` which take the coefficients in the defining equation together with a generator point as parameters. You can find more detail in the comments in -[`noir_stdlib/src/ec.nr`](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec.nr), but +[`noir_stdlib/src/ec.nr`](https://github.com/noir-lang/noir/blob/v0.19.3/noir_stdlib/src/ec.nr), but the gist of it is that the elliptic curves of interest are usually expressed in one of the standard forms implemented here (Twisted Edwards, Montgomery and Short Weierstraß), and in addition to that, you could choose to use `affine` coordinates (Cartesian coordinates - the usual (x,y) - possibly @@ -66,7 +66,7 @@ does indeed lie on `c` by calling `c.contains(p1)`. the curve configurations, the SWU map-to-curve method may be called as `c.swu_map(z,n)`, where `z: Field` depends on `Field` and `c` and must be chosen by the user (the conditions it needs to satisfy are specified in the comments - [here](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec.nr)). + [here](https://github.com/noir-lang/noir/blob/v0.19.3/noir_stdlib/src/ec.nr). ## Examples diff --git a/docs/versioned_docs/version-v0.19.4/standard_library/cryptographic_primitives/04_ec_primitives.md b/docs/versioned_docs/version-v0.19.4/standard_library/cryptographic_primitives/04_ec_primitives.md index d3af3cf7c3b..6f69e468402 100644 --- a/docs/versioned_docs/version-v0.19.4/standard_library/cryptographic_primitives/04_ec_primitives.md +++ b/docs/versioned_docs/version-v0.19.4/standard_library/cryptographic_primitives/04_ec_primitives.md @@ -17,7 +17,7 @@ curve you want to use, which would be specified using any one of the methods `std::ec::{tecurve,montcurve,swcurve}::{affine,curvegroup}::new` which take the coefficients in the defining equation together with a generator point as parameters. You can find more detail in the comments in -[`noir_stdlib/src/ec.nr`](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec.nr), but +[`noir_stdlib/src/ec.nr`](https://github.com/noir-lang/noir/blob/v0.19.4/noir_stdlib/src/ec.nr), but the gist of it is that the elliptic curves of interest are usually expressed in one of the standard forms implemented here (Twisted Edwards, Montgomery and Short Weierstraß), and in addition to that, you could choose to use `affine` coordinates (Cartesian coordinates - the usual (x,y) - possibly @@ -66,7 +66,7 @@ does indeed lie on `c` by calling `c.contains(p1)`. the curve configurations, the SWU map-to-curve method may be called as `c.swu_map(z,n)`, where `z: Field` depends on `Field` and `c` and must be chosen by the user (the conditions it needs to satisfy are specified in the comments - [here](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec.nr)). + [here](https://github.com/noir-lang/noir/blob/v0.19.4/noir_stdlib/src/ec.nr). ## Examples diff --git a/docs/versioned_docs/version-v0.22.0/noir/standard_library/cryptographic_primitives/ec_primitives.md b/docs/versioned_docs/version-v0.22.0/noir/standard_library/cryptographic_primitives/ec_primitives.md index d2b42d67b7c..fc8ed59f09d 100644 --- a/docs/versioned_docs/version-v0.22.0/noir/standard_library/cryptographic_primitives/ec_primitives.md +++ b/docs/versioned_docs/version-v0.22.0/noir/standard_library/cryptographic_primitives/ec_primitives.md @@ -18,7 +18,7 @@ curve you want to use, which would be specified using any one of the methods `std::ec::{tecurve,montcurve,swcurve}::{affine,curvegroup}::new` which take the coefficients in the defining equation together with a generator point as parameters. You can find more detail in the comments in -[`noir_stdlib/src/ec.nr`](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec.nr), but +[`noir_stdlib/src/ec.nr`](https://github.com/noir-lang/noir/blob/v0.22.0/noir_stdlib/src/ec.nr), but the gist of it is that the elliptic curves of interest are usually expressed in one of the standard forms implemented here (Twisted Edwards, Montgomery and Short Weierstraß), and in addition to that, you could choose to use `affine` coordinates (Cartesian coordinates - the usual (x,y) - possibly @@ -67,7 +67,7 @@ does indeed lie on `c` by calling `c.contains(p1)`. the curve configurations, the SWU map-to-curve method may be called as `c.swu_map(z,n)`, where `z: Field` depends on `Field` and `c` and must be chosen by the user (the conditions it needs to satisfy are specified in the comments - [here](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec.nr)). + [here](https://github.com/noir-lang/noir/blob/v0.22.0/noir_stdlib/src/ec.nr). ## Examples diff --git a/docs/versioned_docs/version-v0.23.0/noir/standard_library/cryptographic_primitives/ec_primitives.md b/docs/versioned_docs/version-v0.23.0/noir/standard_library/cryptographic_primitives/ec_primitives.md index d2b42d67b7c..8067d38d465 100644 --- a/docs/versioned_docs/version-v0.23.0/noir/standard_library/cryptographic_primitives/ec_primitives.md +++ b/docs/versioned_docs/version-v0.23.0/noir/standard_library/cryptographic_primitives/ec_primitives.md @@ -18,7 +18,7 @@ curve you want to use, which would be specified using any one of the methods `std::ec::{tecurve,montcurve,swcurve}::{affine,curvegroup}::new` which take the coefficients in the defining equation together with a generator point as parameters. You can find more detail in the comments in -[`noir_stdlib/src/ec.nr`](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec.nr), but +[`noir_stdlib/src/ec.nr`](https://github.com/noir-lang/noir/blob/v0.23.0/noir_stdlib/src/ec.nr), but the gist of it is that the elliptic curves of interest are usually expressed in one of the standard forms implemented here (Twisted Edwards, Montgomery and Short Weierstraß), and in addition to that, you could choose to use `affine` coordinates (Cartesian coordinates - the usual (x,y) - possibly @@ -67,7 +67,7 @@ does indeed lie on `c` by calling `c.contains(p1)`. the curve configurations, the SWU map-to-curve method may be called as `c.swu_map(z,n)`, where `z: Field` depends on `Field` and `c` and must be chosen by the user (the conditions it needs to satisfy are specified in the comments - [here](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec.nr)). + [here](https://github.com/noir-lang/noir/blob/v0.23.0/noir_stdlib/src/ec.nr). ## Examples diff --git a/docs/versioned_docs/version-v0.24.0/noir/standard_library/cryptographic_primitives/ec_primitives.md b/docs/versioned_docs/version-v0.24.0/noir/standard_library/cryptographic_primitives/ec_primitives.md index d2b42d67b7c..48d2408e1e4 100644 --- a/docs/versioned_docs/version-v0.24.0/noir/standard_library/cryptographic_primitives/ec_primitives.md +++ b/docs/versioned_docs/version-v0.24.0/noir/standard_library/cryptographic_primitives/ec_primitives.md @@ -18,7 +18,7 @@ curve you want to use, which would be specified using any one of the methods `std::ec::{tecurve,montcurve,swcurve}::{affine,curvegroup}::new` which take the coefficients in the defining equation together with a generator point as parameters. You can find more detail in the comments in -[`noir_stdlib/src/ec.nr`](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec.nr), but +[`noir_stdlib/src/ec.nr`](https://github.com/noir-lang/noir/blob/v0.24.0/noir_stdlib/src/ec.nr), but the gist of it is that the elliptic curves of interest are usually expressed in one of the standard forms implemented here (Twisted Edwards, Montgomery and Short Weierstraß), and in addition to that, you could choose to use `affine` coordinates (Cartesian coordinates - the usual (x,y) - possibly @@ -67,7 +67,7 @@ does indeed lie on `c` by calling `c.contains(p1)`. the curve configurations, the SWU map-to-curve method may be called as `c.swu_map(z,n)`, where `z: Field` depends on `Field` and `c` and must be chosen by the user (the conditions it needs to satisfy are specified in the comments - [here](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec.nr)). + [here](https://github.com/noir-lang/noir/blob/v0.24.0/noir_stdlib/src/ec.nr). ## Examples diff --git a/docs/versioned_docs/version-v0.25.0/noir/standard_library/cryptographic_primitives/ec_primitives.md b/docs/versioned_docs/version-v0.25.0/noir/standard_library/cryptographic_primitives/ec_primitives.md index d2b42d67b7c..694b385e5ae 100644 --- a/docs/versioned_docs/version-v0.25.0/noir/standard_library/cryptographic_primitives/ec_primitives.md +++ b/docs/versioned_docs/version-v0.25.0/noir/standard_library/cryptographic_primitives/ec_primitives.md @@ -18,7 +18,7 @@ curve you want to use, which would be specified using any one of the methods `std::ec::{tecurve,montcurve,swcurve}::{affine,curvegroup}::new` which take the coefficients in the defining equation together with a generator point as parameters. You can find more detail in the comments in -[`noir_stdlib/src/ec.nr`](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec.nr), but +[`noir_stdlib/src/ec.nr`](https://github.com/noir-lang/noir/blob/v0.25.0/noir_stdlib/src/ec.nr), but the gist of it is that the elliptic curves of interest are usually expressed in one of the standard forms implemented here (Twisted Edwards, Montgomery and Short Weierstraß), and in addition to that, you could choose to use `affine` coordinates (Cartesian coordinates - the usual (x,y) - possibly @@ -67,7 +67,7 @@ does indeed lie on `c` by calling `c.contains(p1)`. the curve configurations, the SWU map-to-curve method may be called as `c.swu_map(z,n)`, where `z: Field` depends on `Field` and `c` and must be chosen by the user (the conditions it needs to satisfy are specified in the comments - [here](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec.nr)). + [here](https://github.com/noir-lang/noir/blob/v0.25.0/noir_stdlib/src/ec.nr). ## Examples diff --git a/docs/versioned_docs/version-v0.26.0/noir/standard_library/cryptographic_primitives/ec_primitives.md b/docs/versioned_docs/version-v0.26.0/noir/standard_library/cryptographic_primitives/ec_primitives.md index d2b42d67b7c..aa679fa1086 100644 --- a/docs/versioned_docs/version-v0.26.0/noir/standard_library/cryptographic_primitives/ec_primitives.md +++ b/docs/versioned_docs/version-v0.26.0/noir/standard_library/cryptographic_primitives/ec_primitives.md @@ -18,7 +18,7 @@ curve you want to use, which would be specified using any one of the methods `std::ec::{tecurve,montcurve,swcurve}::{affine,curvegroup}::new` which take the coefficients in the defining equation together with a generator point as parameters. You can find more detail in the comments in -[`noir_stdlib/src/ec.nr`](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec.nr), but +[`noir_stdlib/src/ec.nr`](https://github.com/noir-lang/noir/blob/v0.26.0/noir_stdlib/src/ec.nr), but the gist of it is that the elliptic curves of interest are usually expressed in one of the standard forms implemented here (Twisted Edwards, Montgomery and Short Weierstraß), and in addition to that, you could choose to use `affine` coordinates (Cartesian coordinates - the usual (x,y) - possibly @@ -67,7 +67,7 @@ does indeed lie on `c` by calling `c.contains(p1)`. the curve configurations, the SWU map-to-curve method may be called as `c.swu_map(z,n)`, where `z: Field` depends on `Field` and `c` and must be chosen by the user (the conditions it needs to satisfy are specified in the comments - [here](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec.nr)). + [here](https://github.com/noir-lang/noir/blob/v0.26.0/noir_stdlib/src/ec.nr). ## Examples diff --git a/docs/versioned_docs/version-v0.27.0/noir/standard_library/cryptographic_primitives/ec_primitives.md b/docs/versioned_docs/version-v0.27.0/noir/standard_library/cryptographic_primitives/ec_primitives.md index d2b42d67b7c..042347cb98f 100644 --- a/docs/versioned_docs/version-v0.27.0/noir/standard_library/cryptographic_primitives/ec_primitives.md +++ b/docs/versioned_docs/version-v0.27.0/noir/standard_library/cryptographic_primitives/ec_primitives.md @@ -18,7 +18,7 @@ curve you want to use, which would be specified using any one of the methods `std::ec::{tecurve,montcurve,swcurve}::{affine,curvegroup}::new` which take the coefficients in the defining equation together with a generator point as parameters. You can find more detail in the comments in -[`noir_stdlib/src/ec.nr`](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec.nr), but +[`noir_stdlib/src/ec.nr`](https://github.com/noir-lang/noir/blob/v0.27.0/noir_stdlib/src/ec.nr), but the gist of it is that the elliptic curves of interest are usually expressed in one of the standard forms implemented here (Twisted Edwards, Montgomery and Short Weierstraß), and in addition to that, you could choose to use `affine` coordinates (Cartesian coordinates - the usual (x,y) - possibly @@ -67,7 +67,7 @@ does indeed lie on `c` by calling `c.contains(p1)`. the curve configurations, the SWU map-to-curve method may be called as `c.swu_map(z,n)`, where `z: Field` depends on `Field` and `c` and must be chosen by the user (the conditions it needs to satisfy are specified in the comments - [here](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec.nr)). + [here](https://github.com/noir-lang/noir/blob/v0.27.0/noir_stdlib/src/ec.nr). ## Examples diff --git a/docs/versioned_docs/version-v0.28.0/noir/standard_library/cryptographic_primitives/ec_primitives.md b/docs/versioned_docs/version-v0.28.0/noir/standard_library/cryptographic_primitives/ec_primitives.md index d2b42d67b7c..9c6987b693e 100644 --- a/docs/versioned_docs/version-v0.28.0/noir/standard_library/cryptographic_primitives/ec_primitives.md +++ b/docs/versioned_docs/version-v0.28.0/noir/standard_library/cryptographic_primitives/ec_primitives.md @@ -18,7 +18,7 @@ curve you want to use, which would be specified using any one of the methods `std::ec::{tecurve,montcurve,swcurve}::{affine,curvegroup}::new` which take the coefficients in the defining equation together with a generator point as parameters. You can find more detail in the comments in -[`noir_stdlib/src/ec.nr`](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec.nr), but +[`noir_stdlib/src/ec.nr`](https://github.com/noir-lang/noir/blob/v0.28.0/noir_stdlib/src/ec.nr), but the gist of it is that the elliptic curves of interest are usually expressed in one of the standard forms implemented here (Twisted Edwards, Montgomery and Short Weierstraß), and in addition to that, you could choose to use `affine` coordinates (Cartesian coordinates - the usual (x,y) - possibly @@ -67,7 +67,7 @@ does indeed lie on `c` by calling `c.contains(p1)`. the curve configurations, the SWU map-to-curve method may be called as `c.swu_map(z,n)`, where `z: Field` depends on `Field` and `c` and must be chosen by the user (the conditions it needs to satisfy are specified in the comments - [here](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec.nr)). + [here](https://github.com/noir-lang/noir/blob/v0.28.0/noir_stdlib/src/ec.nr). ## Examples diff --git a/docs/versioned_docs/version-v0.29.0/noir/standard_library/cryptographic_primitives/ec_primitives.md b/docs/versioned_docs/version-v0.29.0/noir/standard_library/cryptographic_primitives/ec_primitives.md index d2b42d67b7c..f8a1c25ccf6 100644 --- a/docs/versioned_docs/version-v0.29.0/noir/standard_library/cryptographic_primitives/ec_primitives.md +++ b/docs/versioned_docs/version-v0.29.0/noir/standard_library/cryptographic_primitives/ec_primitives.md @@ -18,7 +18,7 @@ curve you want to use, which would be specified using any one of the methods `std::ec::{tecurve,montcurve,swcurve}::{affine,curvegroup}::new` which take the coefficients in the defining equation together with a generator point as parameters. You can find more detail in the comments in -[`noir_stdlib/src/ec.nr`](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec.nr), but +[`noir_stdlib/src/ec.nr`](https://github.com/noir-lang/noir/blob/v0.29.0/noir_stdlib/src/ec.nr), but the gist of it is that the elliptic curves of interest are usually expressed in one of the standard forms implemented here (Twisted Edwards, Montgomery and Short Weierstraß), and in addition to that, you could choose to use `affine` coordinates (Cartesian coordinates - the usual (x,y) - possibly @@ -67,7 +67,7 @@ does indeed lie on `c` by calling `c.contains(p1)`. the curve configurations, the SWU map-to-curve method may be called as `c.swu_map(z,n)`, where `z: Field` depends on `Field` and `c` and must be chosen by the user (the conditions it needs to satisfy are specified in the comments - [here](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec.nr)). + [here](https://github.com/noir-lang/noir/blob/v0.29.0/noir_stdlib/src/ec.nr). ## Examples diff --git a/docs/versioned_docs/version-v0.30.0/noir/standard_library/cryptographic_primitives/ec_primitives.md b/docs/versioned_docs/version-v0.30.0/noir/standard_library/cryptographic_primitives/ec_primitives.md index d2b42d67b7c..8f9f47ce7d0 100644 --- a/docs/versioned_docs/version-v0.30.0/noir/standard_library/cryptographic_primitives/ec_primitives.md +++ b/docs/versioned_docs/version-v0.30.0/noir/standard_library/cryptographic_primitives/ec_primitives.md @@ -18,7 +18,7 @@ curve you want to use, which would be specified using any one of the methods `std::ec::{tecurve,montcurve,swcurve}::{affine,curvegroup}::new` which take the coefficients in the defining equation together with a generator point as parameters. You can find more detail in the comments in -[`noir_stdlib/src/ec.nr`](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec.nr), but +[`noir_stdlib/src/ec.nr`](https://github.com/noir-lang/noir/blob/v0.30.0/noir_stdlib/src/ec.nr), but the gist of it is that the elliptic curves of interest are usually expressed in one of the standard forms implemented here (Twisted Edwards, Montgomery and Short Weierstraß), and in addition to that, you could choose to use `affine` coordinates (Cartesian coordinates - the usual (x,y) - possibly @@ -67,7 +67,7 @@ does indeed lie on `c` by calling `c.contains(p1)`. the curve configurations, the SWU map-to-curve method may be called as `c.swu_map(z,n)`, where `z: Field` depends on `Field` and `c` and must be chosen by the user (the conditions it needs to satisfy are specified in the comments - [here](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec.nr)). + [here](https://github.com/noir-lang/noir/blob/v0.30.0/noir_stdlib/src/ec.nr). ## Examples diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 00000000000..c4fdcad6719 --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1,2 @@ +target +proofs \ No newline at end of file diff --git a/examples/codegen_verifier/.gitignore b/examples/codegen_verifier/.gitignore index c0d62c447d3..ae13a24752c 100644 --- a/examples/codegen_verifier/.gitignore +++ b/examples/codegen_verifier/.gitignore @@ -1,4 +1,3 @@ out cache -target src/contract.sol \ No newline at end of file diff --git a/examples/prove_and_verify/proofs/proof b/examples/prove_and_verify/proofs/proof deleted file mode 100644 index 01d5ad27686..00000000000 Binary files a/examples/prove_and_verify/proofs/proof and /dev/null differ diff --git a/examples/prove_and_verify/prove_and_verify.sh b/examples/prove_and_verify/prove_and_verify.sh index 01ee6c70738..df3ec3ff97a 100755 --- a/examples/prove_and_verify/prove_and_verify.sh +++ b/examples/prove_and_verify/prove_and_verify.sh @@ -11,4 +11,4 @@ $BACKEND prove -b ./target/hello_world.json -w ./target/witness.gz # TODO: backend should automatically generate vk if necessary. $BACKEND write_vk -b ./target/hello_world.json -$BACKEND verify -v ./target/vk -p ./proofs/proof \ No newline at end of file +$BACKEND verify -k ./target/vk -p ./proofs/proof \ No newline at end of file diff --git a/noir_stdlib/src/aes128.nr b/noir_stdlib/src/aes128.nr index e6e2a5e4997..7b0876b86f3 100644 --- a/noir_stdlib/src/aes128.nr +++ b/noir_stdlib/src/aes128.nr @@ -1,4 +1,4 @@ #[foreign(aes128_encrypt)] // docs:start:aes128 -pub fn aes128_encrypt(input: [u8; N], iv: [u8; 16], key: [u8; 16]) -> [u8] {} +pub fn aes128_encrypt(input: [u8; N], iv: [u8; 16], key: [u8; 16]) -> [u8] {} // docs:end:aes128 diff --git a/noir_stdlib/src/append.nr b/noir_stdlib/src/append.nr new file mode 100644 index 00000000000..4577ae199b8 --- /dev/null +++ b/noir_stdlib/src/append.nr @@ -0,0 +1,35 @@ +// Appends two values together, returning the result. +// +// An alternate name for this trait is `Monoid` if that is familiar. +// If not, it can be ignored. +// +// It is expected that for any implementation: +// - `T::empty().append(x) == x` +// - `x.append(T::empty()) == x` +// docs:start:append-trait +trait Append { + fn empty() -> Self; + fn append(self, other: Self) -> Self; +} +// docs:end:append-trait + +impl Append for [T] { + fn empty() -> Self { + &[] + } + + fn append(self, other: Self) -> Self { + // Slices have an existing append function which this will resolve to. + self.append(other) + } +} + +impl Append for Quoted { + fn empty() -> Self { + quote {} + } + + fn append(self, other: Self) -> Self { + quote { $self $other } + } +} diff --git a/noir_stdlib/src/array.nr b/noir_stdlib/src/array.nr index 6fba197dd05..ad9c7093d07 100644 --- a/noir_stdlib/src/array.nr +++ b/noir_stdlib/src/array.nr @@ -2,7 +2,7 @@ use crate::cmp::Ord; // TODO: Once we fully move to the new SSA pass this module can be removed and replaced // by the methods in the `slice` module -impl [T; N] { +impl [T; N] { #[builtin(array_len)] pub fn len(self) -> u32 {} @@ -110,7 +110,7 @@ impl [T; N] { // helper function used to look up the position of a value in an array of Field // Note that function returns 0 if the value is not found -unconstrained fn find_index(a: [u32; N], find: u32) -> u32 { +unconstrained fn find_index(a: [u32; N], find: u32) -> u32 { let mut result = 0; for i in 0..a.len() { if a[i] == find { diff --git a/noir_stdlib/src/cmp.nr b/noir_stdlib/src/cmp.nr index 457b2cfa167..bdd5e2bc5ec 100644 --- a/noir_stdlib/src/cmp.nr +++ b/noir_stdlib/src/cmp.nr @@ -18,7 +18,7 @@ impl Eq for i64 { fn eq(self, other: i64) -> bool { self == other } } impl Eq for () { fn eq(_self: Self, _other: ()) -> bool { true } } impl Eq for bool { fn eq(self, other: bool) -> bool { self == other } } -impl Eq for [T; N] where T: Eq { +impl Eq for [T; N] where T: Eq { fn eq(self, other: [T; N]) -> bool { let mut result = true; for i in 0 .. self.len() { @@ -38,7 +38,7 @@ impl Eq for [T] where T: Eq { } } -impl Eq for str { +impl Eq for str { fn eq(self, other: str) -> bool { let self_bytes = self.as_bytes(); let other_bytes = other.as_bytes(); @@ -203,7 +203,7 @@ impl Ord for bool { } } -impl Ord for [T; N] where T: Ord { +impl Ord for [T; N] where T: Ord { // The first non-equal element of both arrays determines // the ordering for the whole array. fn cmp(self, other: [T; N]) -> Ordering { diff --git a/noir_stdlib/src/collections/bounded_vec.nr b/noir_stdlib/src/collections/bounded_vec.nr index 6fde9e70f4d..a56d6f60390 100644 --- a/noir_stdlib/src/collections/bounded_vec.nr +++ b/noir_stdlib/src/collections/bounded_vec.nr @@ -1,11 +1,11 @@ use crate::{cmp::Eq, convert::From}; -struct BoundedVec { +struct BoundedVec { storage: [T; MaxLen], len: u32, } -impl BoundedVec { +impl BoundedVec { pub fn new() -> Self { let zeroed = crate::unsafe::zeroed(); BoundedVec { storage: [zeroed; MaxLen], len: 0 } @@ -14,7 +14,7 @@ impl BoundedVec { /// Get an element from the vector at the given index. /// Panics if the given index points beyond the end of the vector (`self.len()`). pub fn get(self, index: u32) -> T { - assert(index < self.len); + assert(index < self.len, "Attempted to read past end of BoundedVec"); self.get_unchecked(index) } @@ -61,7 +61,7 @@ impl BoundedVec { self.storage } - pub fn extend_from_array(&mut self, array: [T; Len]) { + pub fn extend_from_array(&mut self, array: [T; Len]) { let new_len = self.len + array.len(); assert(new_len <= MaxLen, "extend_from_array out of bounds"); for i in 0..array.len() { @@ -79,7 +79,7 @@ impl BoundedVec { self.len = new_len; } - pub fn extend_from_bounded_vec(&mut self, vec: BoundedVec) { + pub fn extend_from_bounded_vec(&mut self, vec: BoundedVec) { let append_len = vec.len(); let new_len = self.len + append_len; assert(new_len <= MaxLen, "extend_from_bounded_vec out of bounds"); @@ -94,7 +94,7 @@ impl BoundedVec { self.len = new_len; } - pub fn from_array(array: [T; Len]) -> Self { + pub fn from_array(array: [T; Len]) -> Self { assert(Len <= MaxLen, "from array out of bounds"); let mut vec: BoundedVec = BoundedVec::new(); vec.extend_from_array(array); @@ -134,7 +134,7 @@ impl BoundedVec { } } -impl Eq for BoundedVec where T: Eq { +impl Eq for BoundedVec where T: Eq { fn eq(self, other: BoundedVec) -> bool { // TODO: https://github.com/noir-lang/noir/issues/4837 // @@ -145,32 +145,23 @@ impl Eq for BoundedVec where T: Eq { } } -impl From<[T; Len]> for BoundedVec { +impl From<[T; Len]> for BoundedVec { fn from(array: [T; Len]) -> BoundedVec { BoundedVec::from_array(array) } } mod bounded_vec_tests { - // TODO: Allow imports from "super" - use crate::collections::bounded_vec::BoundedVec; - #[test] - fn empty_equality() { - let mut bounded_vec1: BoundedVec = BoundedVec::new(); - let mut bounded_vec2: BoundedVec = BoundedVec::new(); - - assert_eq(bounded_vec1, bounded_vec2); - } + mod get { + use crate::collections::bounded_vec::BoundedVec; - #[test] - fn inequality() { - let mut bounded_vec1: BoundedVec = BoundedVec::new(); - let mut bounded_vec2: BoundedVec = BoundedVec::new(); - bounded_vec1.push(1); - bounded_vec2.push(2); + #[test(should_fail_with = "Attempted to read past end of BoundedVec")] + fn panics_when_reading_elements_past_end_of_vec() { + let vec: BoundedVec = BoundedVec::new(); - assert(bounded_vec1 != bounded_vec2); + crate::println(vec.get(0)); + } } mod set { @@ -295,4 +286,26 @@ mod bounded_vec_tests { assert_eq(bounded_vec.storage()[1], 2); } } + + mod trait_eq { + use crate::collections::bounded_vec::BoundedVec; + + #[test] + fn empty_equality() { + let mut bounded_vec1: BoundedVec = BoundedVec::new(); + let mut bounded_vec2: BoundedVec = BoundedVec::new(); + + assert_eq(bounded_vec1, bounded_vec2); + } + + #[test] + fn inequality() { + let mut bounded_vec1: BoundedVec = BoundedVec::new(); + let mut bounded_vec2: BoundedVec = BoundedVec::new(); + bounded_vec1.push(1); + bounded_vec2.push(2); + + assert(bounded_vec1 != bounded_vec2); + } + } } diff --git a/noir_stdlib/src/collections/map.nr b/noir_stdlib/src/collections/map.nr index 84e94166869..8324583632f 100644 --- a/noir_stdlib/src/collections/map.nr +++ b/noir_stdlib/src/collections/map.nr @@ -15,7 +15,7 @@ global MAX_LOAD_FACTOR_DEN0MINATOR = 4; // Size of the underlying table must be known at compile time. // It is advised to select capacity N as a power of two, or a prime number // because utilized probing scheme is best tailored for it. -struct HashMap { +struct HashMap { _table: [Slot; N], // Amount of valid elements in the map. @@ -77,7 +77,7 @@ impl Slot { // While conducting lookup, we iterate attempt from 0 to N - 1 due to heuristic, // that if we have went that far without finding desired, // it is very unlikely to be after - performance will be heavily degraded. -impl HashMap { +impl HashMap { // Creates a new instance of HashMap with specified BuildHasher. // docs:start:with_hasher pub fn with_hasher(_build_hasher: B) -> Self @@ -424,7 +424,7 @@ impl HashMap { // equal sets of key-value entries, // thus one is a subset of the other and vice versa. // docs:start:eq -impl Eq for HashMap +impl Eq for HashMap where K: Eq + Hash, V: Eq, @@ -460,7 +460,7 @@ where } // docs:start:default -impl Default for HashMap +impl Default for HashMap where B: BuildHasher + Default, H: Hasher + Default diff --git a/noir_stdlib/src/collections.nr b/noir_stdlib/src/collections/mod.nr similarity index 100% rename from noir_stdlib/src/collections.nr rename to noir_stdlib/src/collections/mod.nr diff --git a/noir_stdlib/src/compat.nr b/noir_stdlib/src/compat.nr index 30b7f73f130..06da8150767 100644 --- a/noir_stdlib/src/compat.nr +++ b/noir_stdlib/src/compat.nr @@ -1,18 +1,7 @@ -global BN254_MODULUS_BE_BYTES: [u8; 32] = [ +global BN254_MODULUS_BE_BYTES: [u8] = &[ 48, 100, 78, 114, 225, 49, 160, 41, 184, 80, 69, 182, 129, 129, 88, 93, 40, 51, 232, 72, 121, 185, 112, 145, 67, 225, 245, 147, 240, 0, 0, 1 ]; pub fn is_bn254() -> bool { - // TODO: refactor this once https://github.com/noir-lang/noir/issues/5245 is resolved. - let modulus_bytes = crate::field::modulus_be_bytes(); - if modulus_bytes.len() == 32 { - let mut modulus_bytes_array: [u8; 32] = [0; 32]; - for i in 0..32 { - modulus_bytes_array[i] = modulus_bytes[i]; - } - - modulus_bytes_array == BN254_MODULUS_BE_BYTES - } else { - false - } + crate::field::modulus_be_bytes() == BN254_MODULUS_BE_BYTES } diff --git a/noir_stdlib/src/default.nr b/noir_stdlib/src/default.nr index bd2f1ce0cd2..0acb3966034 100644 --- a/noir_stdlib/src/default.nr +++ b/noir_stdlib/src/default.nr @@ -17,7 +17,7 @@ impl Default for i64 { fn default() -> i64 { 0 } } impl Default for () { fn default() -> () { () } } impl Default for bool { fn default() -> bool { false } } -impl Default for [T; N] where T: Default { +impl Default for [T; N] where T: Default { fn default() -> [T; N] { [T::default(); N] } diff --git a/noir_stdlib/src/ec/consts.nr b/noir_stdlib/src/ec/consts/mod.nr similarity index 100% rename from noir_stdlib/src/ec/consts.nr rename to noir_stdlib/src/ec/consts/mod.nr diff --git a/noir_stdlib/src/ec.nr b/noir_stdlib/src/ec/mod.nr similarity index 100% rename from noir_stdlib/src/ec.nr rename to noir_stdlib/src/ec/mod.nr diff --git a/noir_stdlib/src/ec/montcurve.nr b/noir_stdlib/src/ec/montcurve.nr index 7dc756781c0..12b48d66b9d 100644 --- a/noir_stdlib/src/ec/montcurve.nr +++ b/noir_stdlib/src/ec/montcurve.nr @@ -114,7 +114,7 @@ mod affine { // Scalar multiplication with scalar represented by a bit array (little-endian convention). // If k is the natural number represented by `bits`, then this computes p + ... + p k times. - fn bit_mul(self, bits: [u1; N], p: Point) -> Point { + fn bit_mul(self, bits: [u1; N], p: Point) -> Point { self.into_tecurve().bit_mul(bits, p.into_tecurve()).into_montcurve() } @@ -124,7 +124,7 @@ mod affine { } // Multi-scalar multiplication (n[0]*p[0] + ... + n[N]*p[N], where * denotes scalar multiplication) - fn msm(self, n: [Field; N], p: [Point; N]) -> Point { + fn msm(self, n: [Field; N], p: [Point; N]) -> Point { let mut out = Point::zero(); for i in 0..N { @@ -315,7 +315,7 @@ mod curvegroup { // Scalar multiplication with scalar represented by a bit array (little-endian convention). // If k is the natural number represented by `bits`, then this computes p + ... + p k times. - fn bit_mul(self, bits: [u1; N], p: Point) -> Point { + fn bit_mul(self, bits: [u1; N], p: Point) -> Point { self.into_tecurve().bit_mul(bits, p.into_tecurve()).into_montcurve() } @@ -325,7 +325,7 @@ mod curvegroup { } // Multi-scalar multiplication (n[0]*p[0] + ... + n[N]*p[N], where * denotes scalar multiplication) - fn msm(self, n: [Field; N], p: [Point; N]) -> Point { + fn msm(self, n: [Field; N], p: [Point; N]) -> Point { let mut out = Point::zero(); for i in 0..N { diff --git a/noir_stdlib/src/ec/swcurve.nr b/noir_stdlib/src/ec/swcurve.nr index 9dd324f3085..3ad3af41cff 100644 --- a/noir_stdlib/src/ec/swcurve.nr +++ b/noir_stdlib/src/ec/swcurve.nr @@ -134,7 +134,7 @@ mod affine { // Scalar multiplication with scalar represented by a bit array (little-endian convention). // If k is the natural number represented by `bits`, then this computes p + ... + p k times. - fn bit_mul(self, bits: [u1; N], p: Point) -> Point { + fn bit_mul(self, bits: [u1; N], p: Point) -> Point { self.into_group().bit_mul(bits, p.into_group()).into_affine() } @@ -144,7 +144,7 @@ mod affine { } // Multi-scalar multiplication (n[0]*p[0] + ... + n[N]*p[N], where * denotes scalar multiplication) - pub fn msm(self, n: [Field; N], p: [Point; N]) -> Point { + pub fn msm(self, n: [Field; N], p: [Point; N]) -> Point { let mut out = Point::zero(); for i in 0..N { @@ -336,7 +336,7 @@ mod curvegroup { // Scalar multiplication with scalar represented by a bit array (little-endian convention). // If k is the natural number represented by `bits`, then this computes p + ... + p k times. - fn bit_mul(self, bits: [u1; N], p: Point) -> Point { + fn bit_mul(self, bits: [u1; N], p: Point) -> Point { let mut out = Point::zero(); for i in 0..N { @@ -363,7 +363,7 @@ mod curvegroup { } // Multi-scalar multiplication (n[0]*p[0] + ... + n[N]*p[N], where * denotes scalar multiplication) - fn msm(self, n: [Field; N], p: [Point; N]) -> Point { + fn msm(self, n: [Field; N], p: [Point; N]) -> Point { let mut out = Point::zero(); for i in 0..N { diff --git a/noir_stdlib/src/ec/tecurve.nr b/noir_stdlib/src/ec/tecurve.nr index 506fe89313a..aaf66f903cc 100644 --- a/noir_stdlib/src/ec/tecurve.nr +++ b/noir_stdlib/src/ec/tecurve.nr @@ -132,7 +132,7 @@ mod affine { // Scalar multiplication with scalar represented by a bit array (little-endian convention). // If k is the natural number represented by `bits`, then this computes p + ... + p k times. - fn bit_mul(self, bits: [u1; N], p: Point) -> Point { + fn bit_mul(self, bits: [u1; N], p: Point) -> Point { self.into_group().bit_mul(bits, p.into_group()).into_affine() } @@ -142,7 +142,7 @@ mod affine { } // Multi-scalar multiplication (n[0]*p[0] + ... + n[N]*p[N], where * denotes scalar multiplication) - fn msm(self, n: [Field; N], p: [Point; N]) -> Point { + fn msm(self, n: [Field; N], p: [Point; N]) -> Point { let mut out = Point::zero(); for i in 0..N { @@ -340,7 +340,7 @@ mod curvegroup { // Scalar multiplication with scalar represented by a bit array (little-endian convention). // If k is the natural number represented by `bits`, then this computes p + ... + p k times. - fn bit_mul(self, bits: [u1; N], p: Point) -> Point { + fn bit_mul(self, bits: [u1; N], p: Point) -> Point { let mut out = Point::zero(); for i in 0..N { @@ -367,7 +367,7 @@ mod curvegroup { } // Multi-scalar multiplication (n[0]*p[0] + ... + n[N]*p[N], where * denotes scalar multiplication) - fn msm(self, n: [Field; N], p: [Point; N]) -> Point { + fn msm(self, n: [Field; N], p: [Point; N]) -> Point { let mut out = Point::zero(); for i in 0..N { diff --git a/noir_stdlib/src/ecdsa_secp256k1.nr b/noir_stdlib/src/ecdsa_secp256k1.nr index f84e2221f57..8a70184dca8 100644 --- a/noir_stdlib/src/ecdsa_secp256k1.nr +++ b/noir_stdlib/src/ecdsa_secp256k1.nr @@ -1,6 +1,6 @@ #[foreign(ecdsa_secp256k1)] // docs:start:ecdsa_secp256k1 -pub fn verify_signature( +pub fn verify_signature( public_key_x: [u8; 32], public_key_y: [u8; 32], signature: [u8; 64], diff --git a/noir_stdlib/src/ecdsa_secp256r1.nr b/noir_stdlib/src/ecdsa_secp256r1.nr index 76e68aeeafa..8772fa7c2ca 100644 --- a/noir_stdlib/src/ecdsa_secp256r1.nr +++ b/noir_stdlib/src/ecdsa_secp256r1.nr @@ -1,6 +1,6 @@ #[foreign(ecdsa_secp256r1)] // docs:start:ecdsa_secp256r1 -pub fn verify_signature( +pub fn verify_signature( public_key_x: [u8; 32], public_key_y: [u8; 32], signature: [u8; 64], diff --git a/noir_stdlib/src/embedded_curve_ops.nr b/noir_stdlib/src/embedded_curve_ops.nr index cd8c421e136..f54072d8cbd 100644 --- a/noir_stdlib/src/embedded_curve_ops.nr +++ b/noir_stdlib/src/embedded_curve_ops.nr @@ -1,7 +1,9 @@ use crate::ops::arith::{Add, Sub, Neg}; use crate::cmp::Eq; -// TODO(https://github.com/noir-lang/noir/issues/4931) +/// A point on the embedded elliptic curve +/// By definition, the base field of the embedded curve is the scalar field of the proof system curve, i.e the Noir Field. +/// x and y denotes the Weierstrass coordinates of the point, if is_infinite is false. struct EmbeddedCurvePoint { x: Field, y: Field, @@ -9,28 +11,35 @@ struct EmbeddedCurvePoint { } impl EmbeddedCurvePoint { + /// Elliptic curve point doubling operation + /// returns the doubled point of a point P, i.e P+P fn double(self) -> EmbeddedCurvePoint { embedded_curve_add(self, self) } + /// Returns the null element of the curve; 'the point at infinity' fn point_at_infinity() -> EmbeddedCurvePoint { EmbeddedCurvePoint { x: 0, y: 0, is_infinite: true } } } impl Add for EmbeddedCurvePoint { + /// Adds two points P+Q, using the curve addition formula, and also handles point at infinity fn add(self, other: EmbeddedCurvePoint) -> EmbeddedCurvePoint { embedded_curve_add(self, other) } } impl Sub for EmbeddedCurvePoint { + /// Points subtraction operation, using addition and negation fn sub(self, other: EmbeddedCurvePoint) -> EmbeddedCurvePoint { self + other.neg() } } impl Neg for EmbeddedCurvePoint { + /// Negates a point P, i.e returns -P, by negating the y coordinate. + /// If the point is at infinity, then the result is also at infinity. fn neg(self) -> EmbeddedCurvePoint { EmbeddedCurvePoint { x: self.x, @@ -41,17 +50,38 @@ impl Neg for EmbeddedCurvePoint { } impl Eq for EmbeddedCurvePoint { + /// Checks whether two points are equal fn eq(self: Self, b: EmbeddedCurvePoint) -> bool { (self.is_infinite & b.is_infinite) | ((self.is_infinite == b.is_infinite) & (self.x == b.x) & (self.y == b.y)) } } -// Scalar represented as low and high limbs +/// Scalar for the embedded curve represented as low and high limbs +/// By definition, the scalar field of the embedded curve is base field of the proving system curve. +/// It may not fit into a Field element, so it is represented with two Field elements; its low and high limbs. struct EmbeddedCurveScalar { lo: Field, hi: Field, } +impl EmbeddedCurveScalar { + pub fn new(lo: Field, hi: Field) -> Self { + EmbeddedCurveScalar { lo, hi } + } + + #[field(bn254)] + fn from_field(scalar: Field) -> EmbeddedCurveScalar { + let (a,b) = crate::field::bn254::decompose(scalar); + EmbeddedCurveScalar { lo: a, hi: b } + } +} + +impl Eq for EmbeddedCurveScalar { + fn eq(self, other: Self) -> bool { + (other.hi == self.hi) & (other.lo == self.lo) + } +} + // Computes a multi scalar multiplication over the embedded curve. // For bn254, We have Grumpkin and Baby JubJub. // For bls12-381, we have JubJub and Bandersnatch. @@ -60,7 +90,7 @@ struct EmbeddedCurveScalar { // underlying proof system. #[foreign(multi_scalar_mul)] // docs:start:multi_scalar_mul -pub fn multi_scalar_mul( +pub fn multi_scalar_mul( points: [EmbeddedCurvePoint; N], scalars: [EmbeddedCurveScalar; N] ) -> [Field; 3] diff --git a/noir_stdlib/src/field.nr b/noir_stdlib/src/field/mod.nr similarity index 100% rename from noir_stdlib/src/field.nr rename to noir_stdlib/src/field/mod.nr diff --git a/noir_stdlib/src/hash/mimc.nr b/noir_stdlib/src/hash/mimc.nr index e90bacb75c2..a16a73c5bc5 100644 --- a/noir_stdlib/src/hash/mimc.nr +++ b/noir_stdlib/src/hash/mimc.nr @@ -6,7 +6,7 @@ use crate::default::Default; // You must use constants generated for the native field // Rounds number should be ~ log(p)/log(exp) // For 254 bit primes, exponent 7 and 91 rounds seems to be recommended -fn mimc(x: Field, k: Field, constants: [Field; N], exp: Field) -> Field { +fn mimc(x: Field, k: Field, constants: [Field; N], exp: Field) -> Field { //round 0 let mut t = x + k; let mut h = t.pow_32(exp); @@ -116,8 +116,7 @@ global MIMC_BN254_CONSTANTS: [Field; MIMC_BN254_ROUNDS] = [ //mimc implementation with hardcoded parameters for BN254 curve. #[field(bn254)] -#[no_predicates] -pub fn mimc_bn254(array: [Field; N]) -> Field { +pub fn mimc_bn254(array: [Field; N]) -> Field { let exponent = 7; let mut r = 0; for elem in array { diff --git a/noir_stdlib/src/hash.nr b/noir_stdlib/src/hash/mod.nr similarity index 60% rename from noir_stdlib/src/hash.nr rename to noir_stdlib/src/hash/mod.nr index b7769dd87d8..2f1bf46fb41 100644 --- a/noir_stdlib/src/hash.nr +++ b/noir_stdlib/src/hash/mod.nr @@ -6,49 +6,89 @@ pub mod pedersen; use crate::default::Default; use crate::uint128::U128; use crate::sha256::{digest, sha256_var}; -use crate::embedded_curve_ops::EmbeddedCurvePoint; +use crate::embedded_curve_ops::{EmbeddedCurvePoint, EmbeddedCurveScalar, multi_scalar_mul}; #[foreign(sha256)] // docs:start:sha256 -pub fn sha256(input: [u8; N]) -> [u8; 32] +pub fn sha256(input: [u8; N]) -> [u8; 32] // docs:end:sha256 {} #[foreign(blake2s)] // docs:start:blake2s -pub fn blake2s(input: [u8; N]) -> [u8; 32] +pub fn blake2s(input: [u8; N]) -> [u8; 32] // docs:end:blake2s {} #[foreign(blake3)] // docs:start:blake3 -pub fn blake3(input: [u8; N]) -> [u8; 32] +pub fn blake3(input: [u8; N]) -> [u8; 32] // docs:end:blake3 {} +#[no_predicates] // docs:start:pedersen_commitment -pub fn pedersen_commitment(input: [Field; N]) -> EmbeddedCurvePoint { +pub fn pedersen_commitment(input: [Field; N]) -> EmbeddedCurvePoint { // docs:end:pedersen_commitment - pedersen_commitment_with_separator(input, 0) + let value = pedersen_commitment_with_separator(input, 0); + if (value.x == 0) & (value.y == 0) { + EmbeddedCurvePoint { x: 0, y: 0, is_infinite: true } + } else { + EmbeddedCurvePoint { x: value.x, y: value.y, is_infinite: false } + } } -#[foreign(pedersen_commitment)] -pub fn __pedersen_commitment_with_separator(input: [Field; N], separator: u32) -> [Field; 2] {} +fn pedersen_commitment_with_separator_noir(input: [Field; N], separator: u32) -> EmbeddedCurvePoint { + let mut points = [EmbeddedCurveScalar { lo: 0, hi: 0 }; N]; + for i in 0..N { + points[i] = EmbeddedCurveScalar::from_field(input[i]); + } + let generators = derive_generators("DEFAULT_DOMAIN_SEPARATOR".as_bytes(), separator); + let values = multi_scalar_mul(generators, points); + EmbeddedCurvePoint { x: values[0], y: values[1], is_infinite: values[2] as bool } +} -pub fn pedersen_commitment_with_separator(input: [Field; N], separator: u32) -> EmbeddedCurvePoint { +#[no_predicates] +pub fn pedersen_commitment_with_separator(input: [Field; N], separator: u32) -> EmbeddedCurvePoint { let values = __pedersen_commitment_with_separator(input, separator); EmbeddedCurvePoint { x: values[0], y: values[1], is_infinite: false } } // docs:start:pedersen_hash -pub fn pedersen_hash(input: [Field; N]) -> Field +pub fn pedersen_hash(input: [Field; N]) -> Field // docs:end:pedersen_hash { pedersen_hash_with_separator(input, 0) } +#[field(bn254)] +fn derive_generators( + domain_separator_bytes: [u8; M], + starting_index: u32 +) -> [EmbeddedCurvePoint; N] { + crate::assert_constant(domain_separator_bytes); + crate::assert_constant(starting_index); + __derive_generators(domain_separator_bytes, starting_index) +} + +#[builtin(derive_pedersen_generators)] +#[field(bn254)] +fn __derive_generators(domain_separator_bytes: [u8; M], starting_index: u32) -> [EmbeddedCurvePoint; N] {} + +fn pedersen_hash_with_separator_noir(input: [Field; N], separator: u32) -> Field { + let v1 = pedersen_commitment_with_separator(input, separator); + let length_generator : [EmbeddedCurvePoint; 1] = derive_generators("pedersen_hash_length".as_bytes(), 0); + multi_scalar_mul( + [length_generator[0], v1], + [EmbeddedCurveScalar { lo: N as Field, hi: 0 }, EmbeddedCurveScalar { lo: 1, hi: 0 }] + )[0] +} + #[foreign(pedersen_hash)] -pub fn pedersen_hash_with_separator(input: [Field; N], separator: u32) -> Field {} +pub fn pedersen_hash_with_separator(input: [Field; N], separator: u32) -> Field {} + +#[foreign(pedersen_commitment)] +fn __pedersen_commitment_with_separator(input: [Field; N], separator: u32) -> [Field; 2] {} pub fn hash_to_field(inputs: [Field]) -> Field { let mut sum = 0; @@ -63,12 +103,12 @@ pub fn hash_to_field(inputs: [Field]) -> Field { #[foreign(keccak256)] // docs:start:keccak256 -pub fn keccak256(input: [u8; N], message_size: u32) -> [u8; 32] +pub fn keccak256(input: [u8; N], message_size: u32) -> [u8; 32] // docs:end:keccak256 {} #[foreign(poseidon2_permutation)] -pub fn poseidon2_permutation(_input: [Field; N], _state_length: u32) -> [Field; N] {} +pub fn poseidon2_permutation(_input: [Field; N], _state_length: u32) -> [Field; N] {} #[foreign(sha256_compression)] pub fn sha256_compression(_input: [u32; 16], _state: [u32; 8]) -> [u32; 8] {} @@ -173,7 +213,7 @@ impl Hash for U128 { } } -impl Hash for [T; N] where T: Hash { +impl Hash for [T; N] where T: Hash { fn hash(self, state: &mut H) where H: Hasher{ for elem in self { elem.hash(state); @@ -223,3 +263,11 @@ impl Hash for (A, B, C, D, E) where A: Hash, B: Hash, C: Hash, D: self.4.hash(state); } } + +#[test] +fn assert_pedersen_noir() { + // TODO: make this a fuzzer test once fuzzer supports curve-specific blackbox functions. + let input = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + assert_eq(pedersen_hash_with_separator(input, 4), pedersen_hash_with_separator_noir(input, 4)); + assert_eq(pedersen_commitment_with_separator(input, 4), pedersen_commitment_with_separator_noir(input, 4)); +} diff --git a/noir_stdlib/src/hash/poseidon/bn254.nr b/noir_stdlib/src/hash/poseidon/bn254.nr index 463c2db43bf..fd647322a25 100644 --- a/noir_stdlib/src/hash/poseidon/bn254.nr +++ b/noir_stdlib/src/hash/poseidon/bn254.nr @@ -6,8 +6,7 @@ use crate::hash::poseidon::{PoseidonConfig, absorb}; // Variable-length Poseidon-128 sponge as suggested in second bullet point of §3 of https://eprint.iacr.org/2019/458.pdf #[field(bn254)] -#[no_predicates] -pub fn sponge(msg: [Field; N]) -> Field { +pub fn sponge(msg: [Field; N]) -> Field { absorb(consts::x5_5_config(), [0; 5], 4, 1, msg)[1] } diff --git a/noir_stdlib/src/hash/poseidon.nr b/noir_stdlib/src/hash/poseidon/mod.nr similarity index 94% rename from noir_stdlib/src/hash/poseidon.nr rename to noir_stdlib/src/hash/poseidon/mod.nr index 5c042a2b6f7..f040a77b612 100644 --- a/noir_stdlib/src/hash/poseidon.nr +++ b/noir_stdlib/src/hash/poseidon/mod.nr @@ -6,7 +6,7 @@ use crate::default::Default; // A config struct defining the parameters of the Poseidon instance to use. // // A thorough writeup of this method (along with an unoptimized method) can be found at: https://spec.filecoin.io/algorithms/crypto/poseidon/ -struct PoseidonConfig { +struct PoseidonConfig { // State width, should be equal to `T` t: Field, // Number of full rounds. should be even @@ -28,7 +28,7 @@ struct PoseidonConfig { sparse_mds: [Field; X], } -pub fn config( +pub fn config( t: Field, rf: u8, rp: u8, @@ -40,14 +40,17 @@ pub fn config( ) -> PoseidonConfig { // Input checks assert_eq(rf & 1, 0); - assert_eq((t as u8) * rf + rp, N); - assert_eq(t, T); + assert_eq((t as u8) * rf + rp, N as u8); + assert_eq(t, T as Field); assert(alpha != 0); PoseidonConfig { t, rf, rp, alpha, round_constants, mds, presparse_mds, sparse_mds } } -pub fn permute(pos_conf: PoseidonConfig, mut state: [Field; T]) -> [Field; T] { +pub fn permute( + pos_conf: PoseidonConfig, + mut state: [Field; T] +) -> [Field; T] { let PoseidonConfig {t, rf, rp, alpha, round_constants, mds, presparse_mds, sparse_mds } = pos_conf; for i in 0..state.len() { @@ -109,7 +112,7 @@ pub fn permute(pos_conf: PoseidonConfig, mut state: [Field; T] } // Performs matrix multiplication on a vector -fn apply_matrix(matrix: [[Field; N]; N], vec: [Field; N]) -> [Field; N] { +fn apply_matrix(matrix: [[Field; N]; N], vec: [Field; N]) -> [Field; N] { let mut out = [0; N]; for i in 0..N { @@ -122,7 +125,7 @@ fn apply_matrix(matrix: [[Field; N]; N], vec: [Field; N]) -> [Field; N] { } // Corresponding absorption. -fn absorb( +fn absorb( pos_conf: PoseidonConfig, // Initial state; usually [0; O] mut state: [Field; T], @@ -152,7 +155,7 @@ fn absorb( state } -fn sigma(x: [Field; O]) -> [Field; O] { +fn sigma(x: [Field; O]) -> [Field; O] { let mut y = x; for i in 0..O { let t = y[i]; diff --git a/noir_stdlib/src/hash/poseidon2.nr b/noir_stdlib/src/hash/poseidon2.nr index 4a68925255a..08cf68d1f82 100644 --- a/noir_stdlib/src/hash/poseidon2.nr +++ b/noir_stdlib/src/hash/poseidon2.nr @@ -12,7 +12,7 @@ struct Poseidon2 { impl Poseidon2 { #[no_predicates] - pub fn hash(input: [Field; N], message_size: u32) -> Field { + pub fn hash(input: [Field; N], message_size: u32) -> Field { if message_size == N { Poseidon2::hash_internal(input, N, false) } else { @@ -95,7 +95,7 @@ impl Poseidon2 { result } - fn hash_internal(input: [Field; N], in_len: u32, is_variable_length: bool) -> Field { + fn hash_internal(input: [Field; N], in_len: u32, is_variable_length: bool) -> Field { let two_pow_64 = 18446744073709551616; let iv : Field = (in_len as Field) * two_pow_64; let mut sponge = Poseidon2::new(iv); diff --git a/noir_stdlib/src/lib.nr b/noir_stdlib/src/lib.nr index 94819ed50d3..06ad633e333 100644 --- a/noir_stdlib/src/lib.nr +++ b/noir_stdlib/src/lib.nr @@ -26,6 +26,8 @@ pub mod prelude; pub mod uint128; pub mod bigint; pub mod runtime; +pub mod meta; +pub mod append; // Oracle calls are required to be wrapped in an unconstrained function // Thus, the only argument to the `println` oracle is expected to always be an ident @@ -47,6 +49,11 @@ pub fn verify_proof(verification_key: [Field], proof: [Field], public_inputs: // Useful for debugging for-loop bounds. #[builtin(assert_constant)] pub fn assert_constant(x: T) {} + +// Asserts that the given value is both true and known at compile-time +#[builtin(static_assert)] +pub fn static_assert(predicate: bool, message: str) {} + // from_field and as_field are private since they are not valid for every type. // `as` should be the default for users to cast between primitive types, and in the future // traits can be used to work with generic types. diff --git a/noir_stdlib/src/merkle.nr b/noir_stdlib/src/merkle.nr index 9b15fe7313d..17e539ab9b7 100644 --- a/noir_stdlib/src/merkle.nr +++ b/noir_stdlib/src/merkle.nr @@ -2,7 +2,7 @@ // Currently we assume that it is a binary tree, so depth k implies a width of 2^k // XXX: In the future we can add an arity parameter // Returns the merkle root of the tree from the provided leaf, its hashpath, using a pedersen hash function. -pub fn compute_merkle_root(leaf: Field, index: Field, hash_path: [Field; N]) -> Field { +pub fn compute_merkle_root(leaf: Field, index: Field, hash_path: [Field; N]) -> Field { let n = hash_path.len(); let index_bits = index.to_le_bits(n as u32); let mut current = leaf; diff --git a/noir_stdlib/src/meta/mod.nr b/noir_stdlib/src/meta/mod.nr new file mode 100644 index 00000000000..1825888130b --- /dev/null +++ b/noir_stdlib/src/meta/mod.nr @@ -0,0 +1 @@ +mod type_def; diff --git a/noir_stdlib/src/meta/type_def.nr b/noir_stdlib/src/meta/type_def.nr new file mode 100644 index 00000000000..c01aab4b141 --- /dev/null +++ b/noir_stdlib/src/meta/type_def.nr @@ -0,0 +1,16 @@ +impl StructDefinition { + /// Return a syntactic version of this struct definition as a type. + /// For example, `as_type(quote { type Foo { ... } })` would return `Foo` + #[builtin(struct_def_as_type)] + fn as_type(self) -> Quoted {} + + /// Return each generic on this struct. The names of these generics are unchanged + /// so users may need to keep name collisions in mind if this is used directly in a macro. + #[builtin(struct_def_generics)] + fn generics(self) -> [Quoted] {} + + /// Returns (name, type) pairs of each field in this struct. Each type is as-is + /// with any generic arguments unchanged. + #[builtin(struct_def_fields)] + fn fields(self) -> [(Quoted, Quoted)] {} +} diff --git a/noir_stdlib/src/ops.nr b/noir_stdlib/src/ops/mod.nr similarity index 100% rename from noir_stdlib/src/ops.nr rename to noir_stdlib/src/ops/mod.nr diff --git a/noir_stdlib/src/option.nr b/noir_stdlib/src/option.nr index c94a1cf836e..df020e75615 100644 --- a/noir_stdlib/src/option.nr +++ b/noir_stdlib/src/option.nr @@ -57,7 +57,7 @@ impl Option { } /// Asserts `self.is_some()` with a provided custom message and returns the contained `Some` value - fn expect(self, message: fmtstr) -> T { + fn expect(self, message: fmtstr) -> T { assert(self.is_some(), message); self._value } diff --git a/noir_stdlib/src/schnorr.nr b/noir_stdlib/src/schnorr.nr index c63915061cb..24ca514025c 100644 --- a/noir_stdlib/src/schnorr.nr +++ b/noir_stdlib/src/schnorr.nr @@ -1,6 +1,6 @@ #[foreign(schnorr_verify)] // docs:start:schnorr_verify -pub fn verify_signature( +pub fn verify_signature( public_key_x: Field, public_key_y: Field, signature: [u8; 64], diff --git a/noir_stdlib/src/sha256.nr b/noir_stdlib/src/sha256.nr index 6057876951d..b5dc958e9d1 100644 --- a/noir_stdlib/src/sha256.nr +++ b/noir_stdlib/src/sha256.nr @@ -15,9 +15,11 @@ fn msg_u8_to_u32(msg: [u8; 64]) -> [u32; 16] { msg32 } + // SHA-256 hash function -pub fn digest(msg: [u8; N]) -> [u8; 32] { - sha256_var(msg, N) +#[no_predicates] +pub fn digest(msg: [u8; N]) -> [u8; 32] { + sha256_var(msg, N as u64) } fn hash_final_block(msg_block: [u8; 64], mut state: [u32; 8]) -> [u8; 32] { @@ -38,13 +40,12 @@ fn hash_final_block(msg_block: [u8; 64], mut state: [u32; 8]) -> [u8; 32] { } // Variable size SHA-256 hash -#[no_predicates] -pub fn sha256_var(msg: [u8; N], message_size: u64) -> [u8; 32] { +pub fn sha256_var(msg: [u8; N], message_size: u64) -> [u8; 32] { let mut msg_block: [u8; 64] = [0; 64]; let mut h: [u32; 8] = [1779033703, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225]; // Intermediate hash, starting with the canonical initial value let mut i: u64 = 0; // Message byte pointer for k in 0..N { - if k < message_size { + if k as u64 < message_size { // Populate msg_block msg_block[i] = msg[k]; i = i + 1; diff --git a/noir_stdlib/src/sha512.nr b/noir_stdlib/src/sha512.nr index 4e46840ebb7..be255a594af 100644 --- a/noir_stdlib/src/sha512.nr +++ b/noir_stdlib/src/sha512.nr @@ -49,7 +49,9 @@ fn sha_w(msg: [u64; 16]) -> [u64; 80] // Expanded message blocks } w } + // SHA-512 compression function +#[no_predicates] fn sha_c(msg: [u64; 16], hash: [u64; 8]) -> [u64; 8] { // noir-fmt:ignore let K: [u64; 80] = [4794697086780616226, 8158064640168781261, 13096744586834688815, 16840607885511220156, 4131703408338449720, 6480981068601479193, 10538285296894168987, 12329834152419229976, 15566598209576043074, 1334009975649890238, 2608012711638119052, 6128411473006802146, 8268148722764581231, 9286055187155687089, 11230858885718282805, 13951009754708518548, 16472876342353939154, 17275323862435702243, 1135362057144423861, 2597628984639134821, 3308224258029322869, 5365058923640841347, 6679025012923562964, 8573033837759648693, 10970295158949994411, 12119686244451234320, 12683024718118986047, 13788192230050041572, 14330467153632333762, 15395433587784984357, 489312712824947311, 1452737877330783856, 2861767655752347644, 3322285676063803686, 5560940570517711597, 5996557281743188959, 7280758554555802590, 8532644243296465576, 9350256976987008742, 10552545826968843579, 11727347734174303076, 12113106623233404929, 14000437183269869457, 14369950271660146224, 15101387698204529176, 15463397548674623760, 17586052441742319658, 1182934255886127544, 1847814050463011016, 2177327727835720531, 2830643537854262169, 3796741975233480872, 4115178125766777443, 5681478168544905931, 6601373596472566643, 7507060721942968483, 8399075790359081724, 8693463985226723168, 9568029438360202098, 10144078919501101548, 10430055236837252648, 11840083180663258601, 13761210420658862357, 14299343276471374635, 14566680578165727644, 15097957966210449927, 16922976911328602910, 17689382322260857208, 500013540394364858, 748580250866718886, 1242879168328830382, 1977374033974150939, 2944078676154940804, 3659926193048069267, 4368137639120453308, 4836135668995329356, 5532061633213252278, 6448918945643986474, 6902733635092675308, 7801388544844847127]; // first 64 bits of fractional parts of cube roots of first 80 primes @@ -87,8 +89,7 @@ fn msg_u8_to_u64(msg: [u8; 128]) -> [u64; 16] { msg64 } // SHA-512 hash function -#[no_predicates] -pub fn digest(msg: [u8; N]) -> [u8; 64] { +pub fn digest(msg: [u8; N]) -> [u8; 64] { let mut msg_block: [u8; 128] = [0; 128]; // noir-fmt:ignore let mut h: [u64; 8] = [7640891576956012808, 13503953896175478587, 4354685564936845355, 11912009170470909681, 5840696475078001361, 11170449401992604703, 2270897969802886507, 6620516959819538809]; // Intermediate hash, starting with the canonical initial value diff --git a/noir_stdlib/src/slice.nr b/noir_stdlib/src/slice.nr index bf05ae0cf64..8d3f395f080 100644 --- a/noir_stdlib/src/slice.nr +++ b/noir_stdlib/src/slice.nr @@ -1,3 +1,5 @@ +use crate::append::Append; + impl [T] { #[builtin(array_len)] pub fn len(self) -> u32 {} @@ -44,7 +46,7 @@ impl [T] { self } - pub fn as_array(self) -> [T; N] { + pub fn as_array(self) -> [T; N] { assert(self.len() == N); let mut array = [crate::unsafe::zeroed(); N]; @@ -53,4 +55,80 @@ impl [T] { } array } + + // Apply a function to each element of the slice, returning a new slice + // containing the mapped elements. + pub fn map(self, f: fn[Env](T) -> U) -> [U] { + let mut ret = &[]; + for elem in self { + ret = ret.push_back(f(elem)); + } + ret + } + + // Apply a function to each element of the slice and an accumulator value, + // returning the final accumulated value. This function is also sometimes + // called `foldl`, `fold_left`, `reduce`, or `inject`. + pub fn fold(self, mut accumulator: U, f: fn[Env](U, T) -> U) -> U { + for elem in self { + accumulator = f(accumulator, elem); + } + accumulator + } + + // Apply a function to each element of the slice and an accumulator value, + // returning the final accumulated value. Unlike fold, reduce uses the first + // element of the given slice as its starting accumulator value. + pub fn reduce(self, f: fn[Env](T, T) -> T) -> T { + let mut accumulator = self[0]; + for i in 1..self.len() { + accumulator = f(accumulator, self[i]); + } + accumulator + } + + // Returns a new slice containing only elements for which the given predicate + // returns true. + pub fn filter(self, predicate: fn[Env](T) -> bool) -> Self { + let mut ret = &[]; + for elem in self { + if predicate(elem) { + ret = ret.push_back(elem); + } + } + ret + } + + // Flatten each element in the slice into one value, separated by `separator`. + pub fn join(self, separator: T) -> T where T: Append { + let mut ret = T::empty(); + + if self.len() != 0 { + ret = self[0]; + + for i in 1..self.len() { + ret = ret.append(separator).append(self[i]); + } + } + + ret + } + + // Returns true if all elements in the slice satisfy the predicate + pub fn all(self, predicate: fn[Env](T) -> bool) -> bool { + let mut ret = true; + for elem in self { + ret &= predicate(elem); + } + ret + } + + // Returns true if any element in the slice satisfies the predicate + pub fn any(self, predicate: fn[Env](T) -> bool) -> bool { + let mut ret = false; + for elem in self { + ret |= predicate(elem); + } + ret + } } diff --git a/noir_stdlib/src/string.nr b/noir_stdlib/src/string.nr index 12b5a1e75ec..5f8f3de775d 100644 --- a/noir_stdlib/src/string.nr +++ b/noir_stdlib/src/string.nr @@ -1,5 +1,5 @@ use crate::collections::vec::Vec; -impl str { +impl str { /// Converts the given string into a byte array #[builtin(str_as_bytes)] pub fn as_bytes(self) -> [u8; N] {} diff --git a/noir_stdlib/src/test.nr b/noir_stdlib/src/test.nr index e6a7e03fefc..f8db6079193 100644 --- a/noir_stdlib/src/test.nr +++ b/noir_stdlib/src/test.nr @@ -1,5 +1,5 @@ #[oracle(create_mock)] -unconstrained fn create_mock_oracle(name: str) -> Field {} +unconstrained fn create_mock_oracle(name: str) -> Field {} #[oracle(set_mock_params)] unconstrained fn set_mock_params_oracle

(id: Field, params: P) {} @@ -21,7 +21,7 @@ struct OracleMock { } impl OracleMock { - unconstrained pub fn mock(name: str) -> Self { + unconstrained pub fn mock(name: str) -> Self { Self { id: create_mock_oracle(name) } } diff --git a/noir_stdlib/src/uint128.nr b/noir_stdlib/src/uint128.nr index 829ab09ee1e..e99818bafa0 100644 --- a/noir_stdlib/src/uint128.nr +++ b/noir_stdlib/src/uint128.nr @@ -66,7 +66,7 @@ impl U128 { bytes } - pub fn from_hex(hex: str) -> U128 { + pub fn from_hex(hex: str) -> U128 { let N = N as u32; let bytes = hex.as_bytes(); // string must starts with "0x" diff --git a/scripts/bump-bb.sh b/scripts/bump-bb.sh new file mode 100755 index 00000000000..36c1f78ca05 --- /dev/null +++ b/scripts/bump-bb.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +BB_VERSION=$1 + +sed -i.bak "s/^VERSION=.*/VERSION=\"$BB_VERSION\"/" ./scripts/install_bb.sh && rm ./scripts/install_bb.sh.bak + +tmp=$(mktemp) +BACKEND_BARRETENBERG_PACKAGE_JSON=./tooling/noir_js_backend_barretenberg/package.json +jq --arg v $BB_VERSION '.dependencies."@aztec/bb.js" = $v' $BACKEND_BARRETENBERG_PACKAGE_JSON > $tmp && mv $tmp $BACKEND_BARRETENBERG_PACKAGE_JSON + +YARN_ENABLE_IMMUTABLE_INSTALLS=false yarn install diff --git a/scripts/install_bb.sh b/scripts/install_bb.sh index c3ed476200a..95dcfdda880 100755 --- a/scripts/install_bb.sh +++ b/scripts/install_bb.sh @@ -1,6 +1,6 @@ #!/bin/bash -VERSION="0.41.0" +VERSION="0.46.1" BBUP_PATH=~/.bb/bbup diff --git a/test_programs/benchmarks/bench_poseidon2_hash/Nargo.toml b/test_programs/benchmarks/bench_poseidon2_hash/Nargo.toml new file mode 100644 index 00000000000..47bf6af3c27 --- /dev/null +++ b/test_programs/benchmarks/bench_poseidon2_hash/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "bench_poseidon2_hash" +version = "0.1.0" +type = "bin" +authors = [""] + +[dependencies] diff --git a/test_programs/benchmarks/bench_poseidon2_hash/Prover.toml b/test_programs/benchmarks/bench_poseidon2_hash/Prover.toml new file mode 100644 index 00000000000..66779dea9d7 --- /dev/null +++ b/test_programs/benchmarks/bench_poseidon2_hash/Prover.toml @@ -0,0 +1 @@ +input = [1,2] diff --git a/test_programs/benchmarks/bench_poseidon2_hash/src/main.nr b/test_programs/benchmarks/bench_poseidon2_hash/src/main.nr new file mode 100644 index 00000000000..a3528253f5d --- /dev/null +++ b/test_programs/benchmarks/bench_poseidon2_hash/src/main.nr @@ -0,0 +1,5 @@ +use std::hash::poseidon2; + +fn main(input: [Field; 2]) -> pub Field { + poseidon2::Poseidon2::hash(input, input.len()) +} diff --git a/test_programs/benchmarks/bench_poseidon2_hash_100/Nargo.toml b/test_programs/benchmarks/bench_poseidon2_hash_100/Nargo.toml new file mode 100644 index 00000000000..4a8bd608939 --- /dev/null +++ b/test_programs/benchmarks/bench_poseidon2_hash_100/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "bench_poseidon2_hash_100" +version = "0.1.0" +type = "bin" +authors = [""] + +[dependencies] diff --git a/test_programs/benchmarks/bench_poseidon2_hash_100/Prover.toml b/test_programs/benchmarks/bench_poseidon2_hash_100/Prover.toml new file mode 100644 index 00000000000..542b4a08dd6 --- /dev/null +++ b/test_programs/benchmarks/bench_poseidon2_hash_100/Prover.toml @@ -0,0 +1,102 @@ +input = [ + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], +] \ No newline at end of file diff --git a/test_programs/benchmarks/bench_poseidon2_hash_100/src/main.nr b/test_programs/benchmarks/bench_poseidon2_hash_100/src/main.nr new file mode 100644 index 00000000000..39c714e524f --- /dev/null +++ b/test_programs/benchmarks/bench_poseidon2_hash_100/src/main.nr @@ -0,0 +1,12 @@ +use std::hash::poseidon2; + +global SIZE = 100; + +fn main(input: [[Field; 2]; SIZE]) -> pub [Field; SIZE] { + let mut results: [Field; SIZE] = [0; SIZE]; + for i in 0..SIZE { + results[i] = poseidon2::Poseidon2::hash(input[i], 2); + } + + results +} diff --git a/test_programs/benchmarks/bench_poseidon2_hash_30/Nargo.toml b/test_programs/benchmarks/bench_poseidon2_hash_30/Nargo.toml new file mode 100644 index 00000000000..0a245bb572e --- /dev/null +++ b/test_programs/benchmarks/bench_poseidon2_hash_30/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "bench_poseidon2_hash_30" +version = "0.1.0" +type = "bin" +authors = [""] + +[dependencies] diff --git a/test_programs/benchmarks/bench_poseidon2_hash_30/Prover.toml b/test_programs/benchmarks/bench_poseidon2_hash_30/Prover.toml new file mode 100644 index 00000000000..7792a9ab8e3 --- /dev/null +++ b/test_programs/benchmarks/bench_poseidon2_hash_30/Prover.toml @@ -0,0 +1,32 @@ +input = [ + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], +] \ No newline at end of file diff --git a/test_programs/benchmarks/bench_poseidon2_hash_30/src/main.nr b/test_programs/benchmarks/bench_poseidon2_hash_30/src/main.nr new file mode 100644 index 00000000000..d1251a4c853 --- /dev/null +++ b/test_programs/benchmarks/bench_poseidon2_hash_30/src/main.nr @@ -0,0 +1,12 @@ +use std::hash::poseidon2; + +global SIZE = 30; + +fn main(input: [[Field; 2]; SIZE]) -> pub [Field; SIZE] { + let mut results: [Field; SIZE] = [0; SIZE]; + for i in 0..SIZE { + results[i] = poseidon2::Poseidon2::hash(input[i], 2); + } + + results +} diff --git a/test_programs/benchmarks/bench_poseidon_hash/bench_poseidon_hash_100/Nargo.toml b/test_programs/benchmarks/bench_poseidon_hash/bench_poseidon_hash_100/Nargo.toml new file mode 100644 index 00000000000..b23716b2a20 --- /dev/null +++ b/test_programs/benchmarks/bench_poseidon_hash/bench_poseidon_hash_100/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "bench_poseidon_hash_100" +version = "0.1.0" +type = "bin" +authors = [""] + +[dependencies] diff --git a/test_programs/benchmarks/bench_poseidon_hash/bench_poseidon_hash_100/Prover.toml b/test_programs/benchmarks/bench_poseidon_hash/bench_poseidon_hash_100/Prover.toml new file mode 100644 index 00000000000..542b4a08dd6 --- /dev/null +++ b/test_programs/benchmarks/bench_poseidon_hash/bench_poseidon_hash_100/Prover.toml @@ -0,0 +1,102 @@ +input = [ + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], +] \ No newline at end of file diff --git a/test_programs/benchmarks/bench_poseidon_hash/bench_poseidon_hash_100/src/main.nr b/test_programs/benchmarks/bench_poseidon_hash/bench_poseidon_hash_100/src/main.nr new file mode 100644 index 00000000000..1c9bbfe61bf --- /dev/null +++ b/test_programs/benchmarks/bench_poseidon_hash/bench_poseidon_hash_100/src/main.nr @@ -0,0 +1,12 @@ +use std::hash; + +global SIZE = 100; + +fn main(input: [[Field; 2]; SIZE]) -> pub [Field; SIZE] { + let mut results: [Field; SIZE] = [0; SIZE]; + for i in 0..SIZE { + results[i] = hash::poseidon::bn254::hash_2(input[i]); + } + + results +} diff --git a/test_programs/benchmarks/bench_poseidon_hash/bench_poseidon_hash_30/Nargo.toml b/test_programs/benchmarks/bench_poseidon_hash/bench_poseidon_hash_30/Nargo.toml new file mode 100644 index 00000000000..dbcbc07b1ba --- /dev/null +++ b/test_programs/benchmarks/bench_poseidon_hash/bench_poseidon_hash_30/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "bench_poseidon_hash_30" +version = "0.1.0" +type = "bin" +authors = [""] + +[dependencies] diff --git a/test_programs/benchmarks/bench_poseidon_hash/bench_poseidon_hash_30/Prover.toml b/test_programs/benchmarks/bench_poseidon_hash/bench_poseidon_hash_30/Prover.toml new file mode 100644 index 00000000000..7792a9ab8e3 --- /dev/null +++ b/test_programs/benchmarks/bench_poseidon_hash/bench_poseidon_hash_30/Prover.toml @@ -0,0 +1,32 @@ +input = [ + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], + [1,2], +] \ No newline at end of file diff --git a/test_programs/benchmarks/bench_poseidon_hash/bench_poseidon_hash_30/src/main.nr b/test_programs/benchmarks/bench_poseidon_hash/bench_poseidon_hash_30/src/main.nr new file mode 100644 index 00000000000..3edb47e9f72 --- /dev/null +++ b/test_programs/benchmarks/bench_poseidon_hash/bench_poseidon_hash_30/src/main.nr @@ -0,0 +1,12 @@ +use std::hash; + +global SIZE = 30; + +fn main(input: [[Field; 2]; SIZE]) -> pub [Field; SIZE] { + let mut results: [Field; SIZE] = [0; SIZE]; + for i in 0..SIZE { + results[i] = hash::poseidon::bn254::hash_2(input[i]); + } + + results +} diff --git a/test_programs/benchmarks/bench_sha256/Nargo.toml b/test_programs/benchmarks/bench_sha256/Nargo.toml new file mode 100644 index 00000000000..488b94ca858 --- /dev/null +++ b/test_programs/benchmarks/bench_sha256/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "bench_sha256" +version = "0.1.0" +type = "bin" +authors = [""] + +[dependencies] diff --git a/test_programs/benchmarks/bench_sha256/src/main.nr b/test_programs/benchmarks/bench_sha256/src/main.nr index fc873fb4afb..c94d359239d 100644 --- a/test_programs/benchmarks/bench_sha256/src/main.nr +++ b/test_programs/benchmarks/bench_sha256/src/main.nr @@ -1,4 +1,3 @@ -use dep::std; fn main(input: [u8; 2]) -> pub [u8; 32] { std::hash::sha256(input) diff --git a/test_programs/compile_failure/assert_constant_dynamic_array/Nargo.toml b/test_programs/compile_failure/assert_constant_dynamic_array/Nargo.toml new file mode 100644 index 00000000000..6171770b62b --- /dev/null +++ b/test_programs/compile_failure/assert_constant_dynamic_array/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "assert_constant_dynamic_array" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/assert_constant_dynamic_array/src/main.nr b/test_programs/compile_failure/assert_constant_dynamic_array/src/main.nr new file mode 100644 index 00000000000..43da3ef0eaa --- /dev/null +++ b/test_programs/compile_failure/assert_constant_dynamic_array/src/main.nr @@ -0,0 +1,5 @@ +fn main( + dynamic_one: Field, // == 1 +) { + assert_constant([dynamic_one]); +} diff --git a/test_programs/compile_failure/assert_constant_dynamic_plus/Nargo.toml b/test_programs/compile_failure/assert_constant_dynamic_plus/Nargo.toml new file mode 100644 index 00000000000..e5583e53126 --- /dev/null +++ b/test_programs/compile_failure/assert_constant_dynamic_plus/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "assert_constant_dynamic_plus" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/assert_constant_dynamic_plus/src/main.nr b/test_programs/compile_failure/assert_constant_dynamic_plus/src/main.nr new file mode 100644 index 00000000000..f8a377092a2 --- /dev/null +++ b/test_programs/compile_failure/assert_constant_dynamic_plus/src/main.nr @@ -0,0 +1,5 @@ +fn main( + dynamic_one: Field, // == 1 +) { + assert_constant(dynamic_one + 1 == 3); +} diff --git a/test_programs/compile_failure/assert_constant_dynamic_slice/Nargo.toml b/test_programs/compile_failure/assert_constant_dynamic_slice/Nargo.toml new file mode 100644 index 00000000000..d1d068a79b8 --- /dev/null +++ b/test_programs/compile_failure/assert_constant_dynamic_slice/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "assert_constant_dynamic_slice" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/assert_constant_dynamic_slice/src/main.nr b/test_programs/compile_failure/assert_constant_dynamic_slice/src/main.nr new file mode 100644 index 00000000000..f07002d7d4c --- /dev/null +++ b/test_programs/compile_failure/assert_constant_dynamic_slice/src/main.nr @@ -0,0 +1,5 @@ +fn main( + dynamic_one: Field, // == 1 +) { + assert_constant(&[dynamic_one]); +} diff --git a/test_programs/compile_failure/assert_constant_dynamic_struct_array/Nargo.toml b/test_programs/compile_failure/assert_constant_dynamic_struct_array/Nargo.toml new file mode 100644 index 00000000000..18781f4d57d --- /dev/null +++ b/test_programs/compile_failure/assert_constant_dynamic_struct_array/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "assert_constant_dynamic_struct_array" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/assert_constant_dynamic_struct_array/src/main.nr b/test_programs/compile_failure/assert_constant_dynamic_struct_array/src/main.nr new file mode 100644 index 00000000000..b9f4dceafc0 --- /dev/null +++ b/test_programs/compile_failure/assert_constant_dynamic_struct_array/src/main.nr @@ -0,0 +1,12 @@ +struct Foo { + field: Field, + array: [Field; 3], + slice: [Field], +} + +fn main( + dynamic_one: Field, // == 1 +) { + let foo_dynamic_array = Foo { field: 0, array: [dynamic_one, 2, 3], slice: &[] }; + assert_constant(foo_dynamic_array); +} diff --git a/test_programs/compile_failure/assert_constant_dynamic_struct_field/Nargo.toml b/test_programs/compile_failure/assert_constant_dynamic_struct_field/Nargo.toml new file mode 100644 index 00000000000..173ea44101a --- /dev/null +++ b/test_programs/compile_failure/assert_constant_dynamic_struct_field/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "assert_constant_dynamic_struct_field" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/assert_constant_dynamic_struct_field/src/main.nr b/test_programs/compile_failure/assert_constant_dynamic_struct_field/src/main.nr new file mode 100644 index 00000000000..e7986c93b6b --- /dev/null +++ b/test_programs/compile_failure/assert_constant_dynamic_struct_field/src/main.nr @@ -0,0 +1,12 @@ +struct Foo { + field: Field, + array: [Field; 3], + slice: [Field], +} + +fn main( + dynamic_one: Field, // == 1 +) { + let foo_dynamic = Foo { field: dynamic_one, array: [1, 2, 3], slice: &[] }; + assert_constant(foo_dynamic); +} diff --git a/test_programs/compile_failure/assert_constant_dynamic_struct_slice/Nargo.toml b/test_programs/compile_failure/assert_constant_dynamic_struct_slice/Nargo.toml new file mode 100644 index 00000000000..426f4826a0b --- /dev/null +++ b/test_programs/compile_failure/assert_constant_dynamic_struct_slice/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "assert_constant_dynamic_struct_slice" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/assert_constant_dynamic_struct_slice/src/main.nr b/test_programs/compile_failure/assert_constant_dynamic_struct_slice/src/main.nr new file mode 100644 index 00000000000..c775b563928 --- /dev/null +++ b/test_programs/compile_failure/assert_constant_dynamic_struct_slice/src/main.nr @@ -0,0 +1,12 @@ +struct Foo { + field: Field, + array: [Field; 3], + slice: [Field], +} + +fn main( + dynamic_one: Field, // == 1 +) { + let foo_dynamic_slice = Foo { field: 0, array: [1, 2, 3], slice: &[dynamic_one] }; + assert_constant(foo_dynamic_slice); +} diff --git a/test_programs/compile_failure/assert_constant_dynamic_tuple/Nargo.toml b/test_programs/compile_failure/assert_constant_dynamic_tuple/Nargo.toml new file mode 100644 index 00000000000..de7b2031300 --- /dev/null +++ b/test_programs/compile_failure/assert_constant_dynamic_tuple/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "assert_constant_dynamic_tuple" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/assert_constant_dynamic_tuple/src/main.nr b/test_programs/compile_failure/assert_constant_dynamic_tuple/src/main.nr new file mode 100644 index 00000000000..579a5a0991f --- /dev/null +++ b/test_programs/compile_failure/assert_constant_dynamic_tuple/src/main.nr @@ -0,0 +1,5 @@ +fn main( + dynamic_one: Field, // == 1 +) { + assert_constant((dynamic_one, 2)); +} diff --git a/test_programs/compile_failure/assert_constant_false/Nargo.toml b/test_programs/compile_failure/assert_constant_false/Nargo.toml new file mode 100644 index 00000000000..cb8d59f4293 --- /dev/null +++ b/test_programs/compile_failure/assert_constant_false/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "assert_constant_false" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/assert_constant_false/src/main.nr b/test_programs/compile_failure/assert_constant_false/src/main.nr new file mode 100644 index 00000000000..f4e98cfec37 --- /dev/null +++ b/test_programs/compile_failure/assert_constant_false/src/main.nr @@ -0,0 +1,3 @@ +fn main() { + std::static_assert(false, ""); +} diff --git a/test_programs/compile_failure/builtin_function_declaration/Nargo.toml b/test_programs/compile_failure/builtin_function_declaration/Nargo.toml index 3835292a6ba..80312a7aec1 100644 --- a/test_programs/compile_failure/builtin_function_declaration/Nargo.toml +++ b/test_programs/compile_failure/builtin_function_declaration/Nargo.toml @@ -2,6 +2,6 @@ name = "builtin_function_declaration" type = "bin" authors = [""] -compiler_version = ">=0.23.0" +compiler_version = ">=0.31.0" -[dependencies] +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/builtin_function_declaration/src/main.nr b/test_programs/compile_failure/builtin_function_declaration/src/main.nr index ed376557371..473b5405691 100644 --- a/test_programs/compile_failure/builtin_function_declaration/src/main.nr +++ b/test_programs/compile_failure/builtin_function_declaration/src/main.nr @@ -7,4 +7,4 @@ fn to_le_bits(_x: Field, _bit_size: u32) -> [u1] {} fn main(x: Field) -> pub u1 { let bits = to_le_bits(x, 100); bits[0] -} +} \ No newline at end of file diff --git a/test_programs/compile_failure/comptime_var_not_defined/Nargo.toml b/test_programs/compile_failure/comptime_var_not_defined/Nargo.toml new file mode 100644 index 00000000000..f2604ee0692 --- /dev/null +++ b/test_programs/compile_failure/comptime_var_not_defined/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "comptime_var_not_defined" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] diff --git a/test_programs/compile_failure/comptime_var_not_defined/src/main.nr b/test_programs/compile_failure/comptime_var_not_defined/src/main.nr new file mode 100644 index 00000000000..ab4ea69e96c --- /dev/null +++ b/test_programs/compile_failure/comptime_var_not_defined/src/main.nr @@ -0,0 +1,5 @@ +fn main() { + comptime { + foo(); + } +} diff --git a/test_programs/compile_failure/overflowing_assignment/Nargo.toml b/test_programs/compile_failure/integer_too_large/Nargo.toml similarity index 61% rename from test_programs/compile_failure/overflowing_assignment/Nargo.toml rename to test_programs/compile_failure/integer_too_large/Nargo.toml index 612e3e7aaf6..08439d2f02e 100644 --- a/test_programs/compile_failure/overflowing_assignment/Nargo.toml +++ b/test_programs/compile_failure/integer_too_large/Nargo.toml @@ -1,5 +1,5 @@ [package] -name = "overflowing_assignment" +name = "integer_too_large" type = "bin" authors = [""] [dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/integer_too_large/src/main.nr b/test_programs/compile_failure/integer_too_large/src/main.nr new file mode 100644 index 00000000000..a8a2c383fe6 --- /dev/null +++ b/test_programs/compile_failure/integer_too_large/src/main.nr @@ -0,0 +1,4 @@ +fn main(x: Field) { + let too_large: Field = 233149999999999999999999999999999999999999999999999999999999923314999999999999999999999999999999999999999999999999999999999923314999999999999999999999999999999999999999999999999999999999; + assert(x == too_large); +} \ No newline at end of file diff --git a/test_programs/compile_failure/invalid_main_sub_lib/Nargo.toml b/test_programs/compile_failure/invalid_main_sub_lib/Nargo.toml new file mode 100644 index 00000000000..12776f47712 --- /dev/null +++ b/test_programs/compile_failure/invalid_main_sub_lib/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "invalid_main_sub_lib" +type = "bin" +authors = [""] +compiler_version = ">=0.30.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/invalid_main_sub_lib/src/main.nr b/test_programs/compile_failure/invalid_main_sub_lib/src/main.nr new file mode 100644 index 00000000000..4658900a47a --- /dev/null +++ b/test_programs/compile_failure/invalid_main_sub_lib/src/main.nr @@ -0,0 +1,5 @@ +mod lib; + +use crate::lib::foo; + +fn main() {} diff --git a/test_programs/compile_failure/invalid_main_sub_lib/src/main/lib.nr b/test_programs/compile_failure/invalid_main_sub_lib/src/main/lib.nr new file mode 100644 index 00000000000..0f89316b5bd --- /dev/null +++ b/test_programs/compile_failure/invalid_main_sub_lib/src/main/lib.nr @@ -0,0 +1,3 @@ +pub fn foo() -> bool { + true +} diff --git a/test_programs/compile_failure/invalid_mod_mod_path/Nargo.toml b/test_programs/compile_failure/invalid_mod_mod_path/Nargo.toml new file mode 100644 index 00000000000..7a93d385c1c --- /dev/null +++ b/test_programs/compile_failure/invalid_mod_mod_path/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "invalid_mod_mod_path" +type = "bin" +authors = [""] +compiler_version = ">=0.30.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/invalid_mod_mod_path/main/lib.nr b/test_programs/compile_failure/invalid_mod_mod_path/main/lib.nr new file mode 100644 index 00000000000..0f89316b5bd --- /dev/null +++ b/test_programs/compile_failure/invalid_mod_mod_path/main/lib.nr @@ -0,0 +1,3 @@ +pub fn foo() -> bool { + true +} diff --git a/test_programs/compile_failure/invalid_mod_mod_path/src/main.nr b/test_programs/compile_failure/invalid_mod_mod_path/src/main.nr new file mode 100644 index 00000000000..86fa197360f --- /dev/null +++ b/test_programs/compile_failure/invalid_mod_mod_path/src/main.nr @@ -0,0 +1,4 @@ +mod crate::mod; + +fn main() { +} diff --git a/test_programs/compile_failure/invalid_mod_mod_path/src/mod.nr b/test_programs/compile_failure/invalid_mod_mod_path/src/mod.nr new file mode 100644 index 00000000000..e2f24cb9226 --- /dev/null +++ b/test_programs/compile_failure/invalid_mod_mod_path/src/mod.nr @@ -0,0 +1,3 @@ +pub foo() -> bool { + true +} diff --git a/test_programs/compile_failure/non_comptime_local_fn_call/Nargo.toml b/test_programs/compile_failure/non_comptime_local_fn_call/Nargo.toml new file mode 100644 index 00000000000..8bdefbbbd21 --- /dev/null +++ b/test_programs/compile_failure/non_comptime_local_fn_call/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "non_comptime_local_fn_call" +type = "bin" +authors = [""] +compiler_version = ">=0.23.0" + +[dependencies] diff --git a/test_programs/compile_failure/non_comptime_local_fn_call/src/main.nr b/test_programs/compile_failure/non_comptime_local_fn_call/src/main.nr new file mode 100644 index 00000000000..d75bb1a922a --- /dev/null +++ b/test_programs/compile_failure/non_comptime_local_fn_call/src/main.nr @@ -0,0 +1,9 @@ +fn main() { + comptime { + let _a = id(3); + } +} + +fn id(x: Field) -> Field { + x +} diff --git a/test_programs/compile_failure/overflowing_assignment/src/main.nr b/test_programs/compile_failure/overflowing_assignment/src/main.nr deleted file mode 100644 index 6b529103ca3..00000000000 --- a/test_programs/compile_failure/overflowing_assignment/src/main.nr +++ /dev/null @@ -1,5 +0,0 @@ -fn main() { - let x:u8 = -1; - let y:u8 = 300; - assert(x != y); -} diff --git a/test_programs/compile_failure/overlapping_mod/Nargo.toml b/test_programs/compile_failure/overlapping_mod/Nargo.toml new file mode 100644 index 00000000000..c9d59b22a39 --- /dev/null +++ b/test_programs/compile_failure/overlapping_mod/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "overlapping_lib_and_mod" +type = "bin" +authors = [""] +compiler_version = ">=0.30.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/overlapping_mod/src/foo.nr b/test_programs/compile_failure/overlapping_mod/src/foo.nr new file mode 100644 index 00000000000..f17d7d7cec5 --- /dev/null +++ b/test_programs/compile_failure/overlapping_mod/src/foo.nr @@ -0,0 +1,4 @@ +pub fn bar() -> bool { + true +} + diff --git a/test_programs/compile_failure/overlapping_mod/src/foo/mod.nr b/test_programs/compile_failure/overlapping_mod/src/foo/mod.nr new file mode 100644 index 00000000000..261729b812b --- /dev/null +++ b/test_programs/compile_failure/overlapping_mod/src/foo/mod.nr @@ -0,0 +1,3 @@ +pub fn bar() -> bool { + true +} diff --git a/test_programs/compile_failure/overlapping_mod/src/main.nr b/test_programs/compile_failure/overlapping_mod/src/main.nr new file mode 100644 index 00000000000..12a3d91f941 --- /dev/null +++ b/test_programs/compile_failure/overlapping_mod/src/main.nr @@ -0,0 +1,7 @@ +mod foo; + +use foo::bar; + +fn main() { + assert(bar()); +} diff --git a/test_programs/compile_failure/regression_5008/Nargo.toml b/test_programs/compile_failure/regression_5008/Nargo.toml new file mode 100644 index 00000000000..920c00660cf --- /dev/null +++ b/test_programs/compile_failure/regression_5008/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "regression_5008" +type = "bin" +authors = [""] +compiler_version = ">=0.28.0" + +[dependencies] diff --git a/test_programs/compile_failure/regression_5008/src/main.nr b/test_programs/compile_failure/regression_5008/src/main.nr new file mode 100644 index 00000000000..6d9645ee6eb --- /dev/null +++ b/test_programs/compile_failure/regression_5008/src/main.nr @@ -0,0 +1,17 @@ +struct Bar { + value: Field, +} + +struct Foo{ + bar: &mut Bar, +} + +impl Foo { + unconstrained fn crash_fn(self) {} +} + +fn main() { + let foo = Foo { bar: &mut Bar { value: 0 } }; + + foo.crash_fn(); +} diff --git a/test_programs/compile_failure/integer_literal_overflow/Nargo.toml b/test_programs/compile_failure/signed_integer_literal_overflow/Nargo.toml similarity index 55% rename from test_programs/compile_failure/integer_literal_overflow/Nargo.toml rename to test_programs/compile_failure/signed_integer_literal_overflow/Nargo.toml index ed607023878..d6aaf3a79d4 100644 --- a/test_programs/compile_failure/integer_literal_overflow/Nargo.toml +++ b/test_programs/compile_failure/signed_integer_literal_overflow/Nargo.toml @@ -1,5 +1,5 @@ [package] -name = "integer_literal_overflow" +name = "signed_integer_literal_overflow" type = "bin" authors = [""] [dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/signed_integer_literal_overflow/src/main.nr b/test_programs/compile_failure/signed_integer_literal_overflow/src/main.nr new file mode 100644 index 00000000000..e6c87da9c29 --- /dev/null +++ b/test_programs/compile_failure/signed_integer_literal_overflow/src/main.nr @@ -0,0 +1,5 @@ +fn main() { + foo(128) +} + +fn foo(_x: i8) {} diff --git a/test_programs/compile_failure/signed_integer_literal_underflow/Nargo.toml b/test_programs/compile_failure/signed_integer_literal_underflow/Nargo.toml new file mode 100644 index 00000000000..75a01881d2e --- /dev/null +++ b/test_programs/compile_failure/signed_integer_literal_underflow/Nargo.toml @@ -0,0 +1,5 @@ +[package] +name = "signed_integer_literal_underflow" +type = "bin" +authors = [""] +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/signed_integer_literal_underflow/src/main.nr b/test_programs/compile_failure/signed_integer_literal_underflow/src/main.nr new file mode 100644 index 00000000000..eb50a3890e6 --- /dev/null +++ b/test_programs/compile_failure/signed_integer_literal_underflow/src/main.nr @@ -0,0 +1,5 @@ +fn main() { + foo(-129) +} + +fn foo(_x: i8) {} diff --git a/test_programs/compile_failure/static_assert_dynamic_array_len/Nargo.toml b/test_programs/compile_failure/static_assert_dynamic_array_len/Nargo.toml new file mode 100644 index 00000000000..1c6c13ce225 --- /dev/null +++ b/test_programs/compile_failure/static_assert_dynamic_array_len/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "static_assert_dynamic_array_len" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/static_assert_dynamic_array_len/src/main.nr b/test_programs/compile_failure/static_assert_dynamic_array_len/src/main.nr new file mode 100644 index 00000000000..9e4665f8ad8 --- /dev/null +++ b/test_programs/compile_failure/static_assert_dynamic_array_len/src/main.nr @@ -0,0 +1,5 @@ +fn main( + dynamic_one: Field, // == 1 +) { + std::static_assert([1, 2, dynamic_one].len() == 4, ""); +} diff --git a/test_programs/compile_failure/static_assert_dynamic_slice/Nargo.toml b/test_programs/compile_failure/static_assert_dynamic_slice/Nargo.toml new file mode 100644 index 00000000000..45e39ae24a8 --- /dev/null +++ b/test_programs/compile_failure/static_assert_dynamic_slice/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "static_assert_dynamic_slice" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/static_assert_dynamic_slice/src/main.nr b/test_programs/compile_failure/static_assert_dynamic_slice/src/main.nr new file mode 100644 index 00000000000..6621b8a1fb3 --- /dev/null +++ b/test_programs/compile_failure/static_assert_dynamic_slice/src/main.nr @@ -0,0 +1,15 @@ +fn main( + dynamic_one: Field, // == 1 + dynamic_two: Field, // == 2 +) { + // length unknown at compile time + let mut dynamic_built_slice_pair = &[]; + if dynamic_one == 1 { + dynamic_built_slice_pair = dynamic_built_slice_pair.push_back(dynamic_one); + } + if dynamic_two == 2 { + dynamic_built_slice_pair = dynamic_built_slice_pair.push_back(dynamic_two); + } + + std::static_assert(dynamic_built_slice_pair.len() == 3, ""); +} diff --git a/test_programs/compile_failure/static_assert_plus/Nargo.toml b/test_programs/compile_failure/static_assert_plus/Nargo.toml new file mode 100644 index 00000000000..fdb90c0dc1c --- /dev/null +++ b/test_programs/compile_failure/static_assert_plus/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "static_assert_plus" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/static_assert_plus/src/main.nr b/test_programs/compile_failure/static_assert_plus/src/main.nr new file mode 100644 index 00000000000..2241743ddb0 --- /dev/null +++ b/test_programs/compile_failure/static_assert_plus/src/main.nr @@ -0,0 +1,7 @@ +fn main() { + let x = 2; + let y = 3; + let xy = x + y; + + std::static_assert(xy == 6, "2 + 3 != 6"); +} diff --git a/test_programs/compile_failure/type_definition_annotation/src/main.nr b/test_programs/compile_failure/type_definition_annotation/src/main.nr index 91f9c3a52f4..d4fef84442d 100644 --- a/test_programs/compile_failure/type_definition_annotation/src/main.nr +++ b/test_programs/compile_failure/type_definition_annotation/src/main.nr @@ -1,7 +1,7 @@ #[fail_assert] struct Foo { x: Field } -comptime fn fail_assert(_typ: TypeDefinition) { +comptime fn fail_assert(_typ: StructDefinition) { assert(false); } diff --git a/test_programs/compile_failure/unary_not_on_field_type_variable/Nargo.toml b/test_programs/compile_failure/unary_not_on_field_type_variable/Nargo.toml new file mode 100644 index 00000000000..ae5c9ecb9c9 --- /dev/null +++ b/test_programs/compile_failure/unary_not_on_field_type_variable/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "unary_not_on_field_type_variable" +type = "bin" +authors = [""] +compiler_version = "" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/unary_not_on_field_type_variable/src/main.nr b/test_programs/compile_failure/unary_not_on_field_type_variable/src/main.nr new file mode 100644 index 00000000000..1023f753c32 --- /dev/null +++ b/test_programs/compile_failure/unary_not_on_field_type_variable/src/main.nr @@ -0,0 +1,4 @@ +fn main() { + let num: Field = 0; + assert_eq(!0, num); +} diff --git a/test_programs/compile_failure/unsigned_integer_literal_overflow/Nargo.toml b/test_programs/compile_failure/unsigned_integer_literal_overflow/Nargo.toml new file mode 100644 index 00000000000..1e787d9082e --- /dev/null +++ b/test_programs/compile_failure/unsigned_integer_literal_overflow/Nargo.toml @@ -0,0 +1,5 @@ +[package] +name = "unsigned_integer_literal_overflow" +type = "bin" +authors = [""] +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/integer_literal_overflow/src/main.nr b/test_programs/compile_failure/unsigned_integer_literal_overflow/src/main.nr similarity index 100% rename from test_programs/compile_failure/integer_literal_overflow/src/main.nr rename to test_programs/compile_failure/unsigned_integer_literal_overflow/src/main.nr diff --git a/test_programs/compile_failure/unsigned_integer_literal_underflow/Nargo.toml b/test_programs/compile_failure/unsigned_integer_literal_underflow/Nargo.toml new file mode 100644 index 00000000000..ae8152caecc --- /dev/null +++ b/test_programs/compile_failure/unsigned_integer_literal_underflow/Nargo.toml @@ -0,0 +1,5 @@ +[package] +name = "unsigned_integer_literal_underflow" +type = "bin" +authors = [""] +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/unsigned_integer_literal_underflow/src/main.nr b/test_programs/compile_failure/unsigned_integer_literal_underflow/src/main.nr new file mode 100644 index 00000000000..217003a013f --- /dev/null +++ b/test_programs/compile_failure/unsigned_integer_literal_underflow/src/main.nr @@ -0,0 +1,5 @@ +fn main() { + foo(-1) +} + +fn foo(_x: u8) {} diff --git a/test_programs/compile_success_contract/abi_attribute/Nargo.toml b/test_programs/compile_success_contract/abi_attribute/Nargo.toml new file mode 100644 index 00000000000..56fa88ccb68 --- /dev/null +++ b/test_programs/compile_success_contract/abi_attribute/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "abi_attribute" +type = "contract" +authors = [""] + +[dependencies] diff --git a/test_programs/compile_success_contract/abi_attribute/src/main.nr b/test_programs/compile_success_contract/abi_attribute/src/main.nr new file mode 100644 index 00000000000..164512b03db --- /dev/null +++ b/test_programs/compile_success_contract/abi_attribute/src/main.nr @@ -0,0 +1,9 @@ +contract Foo { + #[abi(foo)] + global foo: Field = 42; + + #[abi(bar)] + struct Bar { + inner: Field + } +} diff --git a/test_programs/compile_success_contract/recursive_method/Nargo.toml b/test_programs/compile_success_contract/recursive_method/Nargo.toml new file mode 100644 index 00000000000..8142e5b3278 --- /dev/null +++ b/test_programs/compile_success_contract/recursive_method/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "recursive_method" +type = "contract" +authors = [""] + +[dependencies] diff --git a/test_programs/compile_success_contract/recursive_method/src/main.nr b/test_programs/compile_success_contract/recursive_method/src/main.nr new file mode 100644 index 00000000000..6fd4bf3338d --- /dev/null +++ b/test_programs/compile_success_contract/recursive_method/src/main.nr @@ -0,0 +1,6 @@ +contract Foo { + #[recursive] + fn contract_entrypoint() -> pub Field { + 1 + } +} diff --git a/test_programs/compile_success_empty/assert_constant/Nargo.toml b/test_programs/compile_success_empty/assert_constant/Nargo.toml new file mode 100644 index 00000000000..f18d4828fdf --- /dev/null +++ b/test_programs/compile_success_empty/assert_constant/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "assert_constant" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_success_empty/assert_constant/src/main.nr b/test_programs/compile_success_empty/assert_constant/src/main.nr new file mode 100644 index 00000000000..6910a2d17b2 --- /dev/null +++ b/test_programs/compile_success_empty/assert_constant/src/main.nr @@ -0,0 +1,59 @@ +use std::static_assert; + +global GLOBAL_ONE = 1; +global GLOBAL_TWO = 2; +global GLOBAL_THREE = GLOBAL_ONE + GLOBAL_TWO; + +// contents known at compile time +// length known at compile time +global GLOBAL_ARRAY_PAIR = [GLOBAL_ONE, GLOBAL_TWO]; +global GLOBAL_SLICE_PAIR = &[GLOBAL_ONE, GLOBAL_TWO]; + +struct Foo { + field: Field, + array: [Field; 3], + slice: [Field], +} + +fn main( + dynamic_one: Field, // == 1 + dynamic_two: Field // == 2 +) { + // contents unknown at compile time + // length known at compile time + let dynamic_array_pair = [dynamic_one, dynamic_two]; + let dynamic_slice_pair = &[dynamic_one, dynamic_two]; + + assert_constant(true); + assert_constant(false); + + assert_constant(2 == 2); + assert_constant(1 + 1 == 2); + + // assert_constant doesn't check for true + assert_constant(1 + 1 == 3); + + let local_one = 1; + let local_two = 2; + let local_three = local_one + local_two; + let local_array_pair = [local_one, local_two]; + let local_slice_pair = &[local_one, local_two]; + + assert_constant(local_one); + assert_constant(local_three); + assert_constant(local_array_pair); + assert_constant(local_slice_pair); + + assert_constant(GLOBAL_ONE); + assert_constant(GLOBAL_THREE); + assert_constant(GLOBAL_ARRAY_PAIR); + assert_constant(GLOBAL_SLICE_PAIR); + + assert_constant([1, 2, dynamic_one].len() == 4); + + static_assert(dynamic_array_pair.len() == 2, ""); + static_assert(dynamic_slice_pair.len() == 2, ""); + + let foo = Foo { field: 0, array: [1, 2, 3], slice: &[] }; + assert_constant(foo); +} diff --git a/test_programs/compile_success_empty/comptime_slice_methods/Nargo.toml b/test_programs/compile_success_empty/comptime_slice_methods/Nargo.toml new file mode 100644 index 00000000000..8ff281cf9e3 --- /dev/null +++ b/test_programs/compile_success_empty/comptime_slice_methods/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "comptime_slice_methods" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] diff --git a/test_programs/compile_success_empty/comptime_slice_methods/src/main.nr b/test_programs/compile_success_empty/comptime_slice_methods/src/main.nr new file mode 100644 index 00000000000..b76e8d70b83 --- /dev/null +++ b/test_programs/compile_success_empty/comptime_slice_methods/src/main.nr @@ -0,0 +1,27 @@ +fn main() { + comptime + { + // Comptime scopes are counted as unconstrained + assert(std::runtime::is_unconstrained()); + + let slice = &[2]; + let slice = slice.push_back(3); + let slice = slice.push_front(1); + assert_eq(slice, &[1, 2, 3]); + + let slice = slice.insert(1, 10); + assert_eq(slice, &[1, 10, 2, 3]); + + let (slice, ten) = slice.remove(1); + assert_eq(slice, &[1, 2, 3]); + assert_eq(ten, 10); + + let (one, slice) = slice.pop_front(); + assert_eq(slice, &[2, 3]); + assert_eq(one, 1); + + let (slice, three) = slice.pop_back(); + assert_eq(slice, &[2]); + assert_eq(three, 3); + } +} diff --git a/test_programs/compile_success_empty/comptime_traits/Nargo.toml b/test_programs/compile_success_empty/comptime_traits/Nargo.toml new file mode 100644 index 00000000000..75df4dc5c20 --- /dev/null +++ b/test_programs/compile_success_empty/comptime_traits/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "comptime_traits" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] diff --git a/test_programs/compile_success_empty/comptime_traits/src/main.nr b/test_programs/compile_success_empty/comptime_traits/src/main.nr new file mode 100644 index 00000000000..143c9cda274 --- /dev/null +++ b/test_programs/compile_success_empty/comptime_traits/src/main.nr @@ -0,0 +1,15 @@ +fn main() { + comptime + { + // impl Eq for Field + assert(3 == 3); + + // impl Default for [T; N] where T: Default + // impl Default for Field + let array = Default::default(); + + // impl Eq for [T; N] where T: Eq + // impl Eq for Field + assert([1, 2] != array); + } +} diff --git a/test_programs/compile_success_empty/comptime_type_definition/Nargo.toml b/test_programs/compile_success_empty/comptime_type_definition/Nargo.toml new file mode 100644 index 00000000000..099545a9e71 --- /dev/null +++ b/test_programs/compile_success_empty/comptime_type_definition/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "comptime_type_definition" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_success_empty/comptime_type_definition/src/main.nr b/test_programs/compile_success_empty/comptime_type_definition/src/main.nr new file mode 100644 index 00000000000..cdfc9bd6b75 --- /dev/null +++ b/test_programs/compile_success_empty/comptime_type_definition/src/main.nr @@ -0,0 +1,13 @@ +fn main() {} + +#[my_comptime_fn] +struct MyType { + field1: [A; 10], + field2: (B, C), +} + +comptime fn my_comptime_fn(typ: StructDefinition) { + let _ = typ.as_type(); + assert_eq(typ.generics().len(), 3); + assert_eq(typ.fields().len(), 2); +} diff --git a/test_programs/execution_success/regression_5202/Nargo.toml b/test_programs/compile_success_empty/derive_impl/Nargo.toml similarity index 59% rename from test_programs/execution_success/regression_5202/Nargo.toml rename to test_programs/compile_success_empty/derive_impl/Nargo.toml index da3da06a306..26a6020a6b1 100644 --- a/test_programs/execution_success/regression_5202/Nargo.toml +++ b/test_programs/compile_success_empty/derive_impl/Nargo.toml @@ -1,8 +1,7 @@ [package] -name = "regression_5202" +name = "derive_impl" type = "bin" authors = [""] compiler_version = ">=0.30.0" [dependencies] -fraction = { path = "fraction" } diff --git a/test_programs/compile_success_empty/derive_impl/src/main.nr b/test_programs/compile_success_empty/derive_impl/src/main.nr new file mode 100644 index 00000000000..5463a61d969 --- /dev/null +++ b/test_programs/compile_success_empty/derive_impl/src/main.nr @@ -0,0 +1,49 @@ +comptime fn derive_default(typ: StructDefinition) -> Quoted { + let generics: [Quoted] = typ.generics(); + assert_eq( + generics.len(), 0, "derive_default: Deriving Default on generic types is currently unimplemented" + ); + + let type_name = typ.as_type(); + let fields = typ.fields(); + + let fields = join(make_field_exprs(fields)); + + quote { + impl Default for $type_name { + fn default() -> Self { + Self { $fields } + } + } + } +} + +#[derive_default] +struct Foo { + x: Field, + y: Bar, +} + +#[derive_default] +struct Bar {} + +comptime fn make_field_exprs(fields: [(Quoted, Quoted)]) -> [Quoted] { + let mut result = &[]; + for my_field in fields { + let name = my_field.0; + result = result.push_back(quote { $name: Default::default(), }); + } + result +} + +comptime fn join(slice: [Quoted]) -> Quoted { + let mut result = quote {}; + for elem in slice { + result = quote { $result $elem }; + } + result +} + +fn main() { + let _foo: Foo = Default::default(); +} diff --git a/test_programs/compile_success_empty/ec_baby_jubjub/src/main.nr b/test_programs/compile_success_empty/ec_baby_jubjub/src/main.nr index fa6be84c26e..616ac7ef6ee 100644 --- a/test_programs/compile_success_empty/ec_baby_jubjub/src/main.nr +++ b/test_programs/compile_success_empty/ec_baby_jubjub/src/main.nr @@ -9,10 +9,11 @@ use std::ec::swcurve::curvegroup::Point as SWG; use std::ec::montcurve::affine::Point as MGaffine; use std::ec::montcurve::curvegroup::Point as MG; +use std::compat; fn main() { // This test only makes sense if Field is the right prime field. - if 21888242871839275222246405745257275088548364400416034343698204186575808495617 == 0 { + if compat::is_bn254() { // Define Baby Jubjub (ERC-2494) parameters in affine representation let bjj_affine = AffineCurve::new( 168700, diff --git a/test_programs/compile_success_empty/impl_where_clause/Nargo.toml b/test_programs/compile_success_empty/impl_where_clause/Nargo.toml new file mode 100644 index 00000000000..7d0d5f3513e --- /dev/null +++ b/test_programs/compile_success_empty/impl_where_clause/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "impl_where_clause" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_success_empty/impl_where_clause/src/main.nr b/test_programs/compile_success_empty/impl_where_clause/src/main.nr new file mode 100644 index 00000000000..2f3223efaae --- /dev/null +++ b/test_programs/compile_success_empty/impl_where_clause/src/main.nr @@ -0,0 +1,34 @@ +struct MyStruct { + a: u32, + b: T, +} + +struct InnerStruct { + a: Field, + b: Field, +} + +trait MyEq { + fn my_eq(self, other: Self) -> bool; +} + +impl MyEq for InnerStruct { + fn my_eq(self, other: InnerStruct) -> bool { + (self.a == other.a) & (self.b == other.b) + } +} + +impl MyStruct where T: MyEq { + fn my_eq(self, other: Self) -> bool { + (self.a == other.a) & self.b.my_eq(other.b) + } +} + +fn main() { + let inner = InnerStruct { a: 1, b: 2 }; + let my_struct = MyStruct { a: 5, b: inner }; + assert(my_struct.my_eq(my_struct)); + + let mut my_struct_new = MyStruct { a: 5, b: InnerStruct { a: 10, b: 15 } }; + assert(my_struct_new.my_eq(my_struct_new)); +} diff --git a/test_programs/compile_success_empty/intrinsic_die/src/main.nr b/test_programs/compile_success_empty/intrinsic_die/src/main.nr index c6e269c155d..17aaf02c283 100644 --- a/test_programs/compile_success_empty/intrinsic_die/src/main.nr +++ b/test_programs/compile_success_empty/intrinsic_die/src/main.nr @@ -1,6 +1,5 @@ // This test checks that we perform dead-instruction-elimination on intrinsic functions. fn main(x: Field) { - let hash = std::hash::pedersen_commitment([x]); let g1_x = 0x0000000000000000000000000000000000000000000000000000000000000001; let g1_y = 0x0000000000000002cf135e7506a45d632d270d45f1181294833fc48d823f272c; let g1 = std::embedded_curve_ops::EmbeddedCurvePoint { x: g1_x, y: g1_y, is_infinite: false }; diff --git a/test_programs/compile_success_empty/macros/src/main.nr b/test_programs/compile_success_empty/macros/src/main.nr index f466226d575..587c2c4c077 100644 --- a/test_programs/compile_success_empty/macros/src/main.nr +++ b/test_programs/compile_success_empty/macros/src/main.nr @@ -1,4 +1,4 @@ -comptime fn my_macro(x: Field, y: Field) -> Expr { +comptime fn my_macro(x: Field, y: Field) -> Quoted { // Current version of macros in Noir are not hygienic // so we can quote a and b here and expect them to resolve // to the a and b in main at the callsite of my_macro. diff --git a/test_programs/compile_success_empty/mod_nr_entrypoint/Nargo.toml b/test_programs/compile_success_empty/mod_nr_entrypoint/Nargo.toml new file mode 100644 index 00000000000..b90b7326186 --- /dev/null +++ b/test_programs/compile_success_empty/mod_nr_entrypoint/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "mod_nr_entrypoint" +type = "bin" +authors = [""] +compiler_version = ">=0.29.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_success_empty/mod_nr_entrypoint/src/baz.nr b/test_programs/compile_success_empty/mod_nr_entrypoint/src/baz.nr new file mode 100644 index 00000000000..0bcfe8b02ad --- /dev/null +++ b/test_programs/compile_success_empty/mod_nr_entrypoint/src/baz.nr @@ -0,0 +1,3 @@ +pub fn in_baz_mod() -> bool { + true +} diff --git a/test_programs/compile_success_empty/mod_nr_entrypoint/src/foo/bar.nr b/test_programs/compile_success_empty/mod_nr_entrypoint/src/foo/bar.nr new file mode 100644 index 00000000000..f2efe64906d --- /dev/null +++ b/test_programs/compile_success_empty/mod_nr_entrypoint/src/foo/bar.nr @@ -0,0 +1,3 @@ +pub fn in_bar_mod() -> Field { + 2 +} diff --git a/test_programs/compile_success_empty/mod_nr_entrypoint/src/foo/mod.nr b/test_programs/compile_success_empty/mod_nr_entrypoint/src/foo/mod.nr new file mode 100644 index 00000000000..4eac6cb8514 --- /dev/null +++ b/test_programs/compile_success_empty/mod_nr_entrypoint/src/foo/mod.nr @@ -0,0 +1,5 @@ +mod bar; + +pub fn in_foo_mod() -> Field { + 1 +} diff --git a/test_programs/compile_success_empty/mod_nr_entrypoint/src/main.nr b/test_programs/compile_success_empty/mod_nr_entrypoint/src/main.nr new file mode 100644 index 00000000000..620fd99f6ec --- /dev/null +++ b/test_programs/compile_success_empty/mod_nr_entrypoint/src/main.nr @@ -0,0 +1,11 @@ +use crate::foo::in_foo_mod; +use crate::foo::bar::in_bar_mod; +use crate::baz::in_baz_mod; + +mod foo; +mod baz; + +fn main() { + assert(in_foo_mod() != in_bar_mod()); + assert(in_baz_mod()); +} diff --git a/test_programs/compile_success_empty/numeric_generics/src/main.nr b/test_programs/compile_success_empty/numeric_generics/src/main.nr index 1e03a382fed..340c18c2a1d 100644 --- a/test_programs/compile_success_empty/numeric_generics/src/main.nr +++ b/test_programs/compile_success_empty/numeric_generics/src/main.nr @@ -36,4 +36,3 @@ fn foo(mut s: MyStruct<2+1>) -> MyStruct<10/2-2> { s.data[0] = s.data[0] + 1; s } - diff --git a/test_programs/compile_success_empty/numeric_generics_explicit/Nargo.toml b/test_programs/compile_success_empty/numeric_generics_explicit/Nargo.toml new file mode 100644 index 00000000000..980e5db588a --- /dev/null +++ b/test_programs/compile_success_empty/numeric_generics_explicit/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "numeric_generics_explicit" +type = "bin" +authors = [""] + +[dependencies] diff --git a/test_programs/compile_success_empty/numeric_generics_explicit/src/main.nr b/test_programs/compile_success_empty/numeric_generics_explicit/src/main.nr new file mode 100644 index 00000000000..7c4f7761ff6 --- /dev/null +++ b/test_programs/compile_success_empty/numeric_generics_explicit/src/main.nr @@ -0,0 +1,111 @@ +// Regression that a global of the same name does not trigger a duplicate definition error +global N = 1000; + +fn main() { + let a = id([1, 2]); + let b = id([1, 2, 3]); + + let itWorks1 = MyStruct { data: a }; + assert(itWorks1.data[1] == 2); + let itWorks2 = MyStruct { data: b }; + assert(itWorks2.data[1] == 2); + + let c = [1, 2]; + let itAlsoWorks = MyStruct { data: c }; + assert(itAlsoWorks.data[1] == 2); + + assert(foo(itWorks2).data[0] == itWorks2.data[0] + 1); + + double_numeric_generics_test(); + + let my_type = PublicStorage::read::(); + assert(my_type.a == 1); + assert(my_type.b == 2); + assert(my_type.c == 3); + + let foo = baz::<10>(); + assert(foo.data == [1; 10]); +} + +// Used in the signature of a function +fn id(x: [Field; I]) -> [Field; I] { + x +} + +// Used as a field of a struct +struct MyStruct { + data: [Field; S], +} + +// Used in an impl +impl MyStruct { + fn insert(mut self: Self, index: Field, elem: Field) -> Self { + // Regression test for numeric generics on impls + assert(index as u32 < S); + + self.data[index] = elem; + self + } +} + +fn foo(mut s: MyStruct<2+1>) -> MyStruct<10/2-2> { + s.data[0] = s.data[0] + 1; + s +} + +fn baz() -> MyStruct { + MyStruct { data: [1; N] } +} + +fn double() -> u32 { + // Used as an expression + N * 2 +} + +fn double_numeric_generics_test() { + // Example usage of a numeric generic arguments. + assert(double::<9>() == 18); + assert(double::<123>() == 246); + assert(double::<7 + 8>() == 30); +} + +struct MyType { + a: Field, + b: Field, + c: Field, +} + +impl Deserialize for MyType { + fn deserialize(fields: [Field; N]) -> Self { + MyType { a: fields[0], b: fields[1], c: fields[2] } + } +} + +trait Deserialize { + fn deserialize(fields: [Field; N]) -> Self; +} + +struct PublicStorage {} + +impl PublicStorage { + fn read() -> T where T: Deserialize { + // Used as a type within a function body + let mut fields: [Field; N] = [0; N]; + // Used a loop bound + for i in 0..N { + fields[i] = i as Field + 1; + } + T::deserialize(fields) + } +} + +// Check that we can thread numeric generics into nested structs +// and also that we can handle nested structs with numeric generics +// which are declared after the parent struct +struct NestedNumeric { + a: Field, + b: InnerNumeric +} +struct InnerNumeric { + inner: [u32; N], +} diff --git a/test_programs/compile_success_empty/regression_4635/src/main.nr b/test_programs/compile_success_empty/regression_4635/src/main.nr index 23918e30785..350b60ba3f7 100644 --- a/test_programs/compile_success_empty/regression_4635/src/main.nr +++ b/test_programs/compile_success_empty/regression_4635/src/main.nr @@ -8,7 +8,7 @@ impl FromField for Field { } } -trait Deserialize { +trait Deserialize { fn deserialize(fields: [Field; N]) -> Self; } diff --git a/test_programs/compile_success_empty/slice_join/Nargo.toml b/test_programs/compile_success_empty/slice_join/Nargo.toml new file mode 100644 index 00000000000..44be002efb4 --- /dev/null +++ b/test_programs/compile_success_empty/slice_join/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "slice_join" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_success_empty/slice_join/src/main.nr b/test_programs/compile_success_empty/slice_join/src/main.nr new file mode 100644 index 00000000000..217000694b0 --- /dev/null +++ b/test_programs/compile_success_empty/slice_join/src/main.nr @@ -0,0 +1,16 @@ +use std::append::Append; + +fn main() { + let slice = &[1, 2, 3, 4, 5]; + + let odds = slice.filter(|x| x % 2 == 1); + assert_eq(odds, &[1, 3, 5]); + + let odds_and_evens = append_three(odds, &[100], &[2, 4]); + assert_eq(odds_and_evens, &[1, 3, 5, 100, 2, 4]); +} + +fn append_three(one: T, two: T, three: T) -> T where T: Append { + // The `T::empty()`s here should do nothing + T::empty().append(one).append(two).append(three).append(T::empty()) +} diff --git a/test_programs/compile_success_empty/static_assert/Nargo.toml b/test_programs/compile_success_empty/static_assert/Nargo.toml new file mode 100644 index 00000000000..5dabd7803e3 --- /dev/null +++ b/test_programs/compile_success_empty/static_assert/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "static_assert" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_success_empty/static_assert/src/main.nr b/test_programs/compile_success_empty/static_assert/src/main.nr new file mode 100644 index 00000000000..e61d9388ceb --- /dev/null +++ b/test_programs/compile_success_empty/static_assert/src/main.nr @@ -0,0 +1,46 @@ +use std::static_assert; + +global GLOBAL_ONE = 1; +global GLOBAL_TWO = 2; +global GLOBAL_THREE = GLOBAL_ONE + GLOBAL_TWO; + +// contents known at compile time +// length known at compile time +global GLOBAL_ARRAY_PAIR = [GLOBAL_ONE, GLOBAL_TWO]; +global GLOBAL_SLICE_PAIR = &[GLOBAL_ONE, GLOBAL_TWO]; + +struct Foo { + field: Field, + array: [Field; 3], + slice: [Field], +} + +fn main( + dynamic_one: Field, // == 1 + dynamic_two: Field // == 2 +) { + // contents unknown at compile time + // length known at compile time + let dynamic_array_pair = [dynamic_one, dynamic_two]; + let dynamic_slice_pair = &[dynamic_one, dynamic_two]; + + static_assert(true, ""); + + static_assert(1 + 1 == 2, ""); + + let x = 2; + let y = 3; + let xy = x + y; + static_assert(xy == 5, ""); + + static_assert(3 == GLOBAL_THREE, ""); + + static_assert(GLOBAL_ARRAY_PAIR.len() == 2, ""); + static_assert(GLOBAL_SLICE_PAIR.len() == 2, ""); + + static_assert(dynamic_array_pair.len() == 2, ""); + static_assert(dynamic_slice_pair.len() == 2, ""); + + assert_constant([1, 2, dynamic_one].len() == 4); + static_assert([1, 2, dynamic_one].len() == 3, ""); +} diff --git a/test_programs/compile_success_empty/trait_generics/src/main.nr b/test_programs/compile_success_empty/trait_generics/src/main.nr index 30b2e79d579..15591f2f2ea 100644 --- a/test_programs/compile_success_empty/trait_generics/src/main.nr +++ b/test_programs/compile_success_empty/trait_generics/src/main.nr @@ -15,7 +15,7 @@ trait MyInto { fn into(self) -> T; } -impl MyInto<[U; N]> for [T; N] where T: MyInto { +impl MyInto<[U; N]> for [T; N] where T: MyInto { fn into(self) -> [U; N] { self.map(|x: T| x.into()) } @@ -29,7 +29,7 @@ impl MyInto for Field { /// Serialize example -trait Serializable { +trait Serializable { fn serialize(self) -> [Field; N]; } diff --git a/test_programs/compile_success_empty/trait_impl_with_where_clause/Nargo.toml b/test_programs/compile_success_empty/trait_impl_with_where_clause/Nargo.toml new file mode 100644 index 00000000000..672569634ea --- /dev/null +++ b/test_programs/compile_success_empty/trait_impl_with_where_clause/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "trait_impl_with_where_clause" +type = "bin" +authors = [""] + +[dependencies] diff --git a/test_programs/compile_success_empty/impl_with_where_clause/src/main.nr b/test_programs/compile_success_empty/trait_impl_with_where_clause/src/main.nr similarity index 100% rename from test_programs/compile_success_empty/impl_with_where_clause/src/main.nr rename to test_programs/compile_success_empty/trait_impl_with_where_clause/src/main.nr diff --git a/test_programs/compile_success_empty/unquote_multiple_items_from_annotation/Nargo.toml b/test_programs/compile_success_empty/unquote_multiple_items_from_annotation/Nargo.toml new file mode 100644 index 00000000000..63f15f2b349 --- /dev/null +++ b/test_programs/compile_success_empty/unquote_multiple_items_from_annotation/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "unquote_multiple_items_from_annotation" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] diff --git a/test_programs/compile_success_empty/unquote_multiple_items_from_annotation/src/main.nr b/test_programs/compile_success_empty/unquote_multiple_items_from_annotation/src/main.nr new file mode 100644 index 00000000000..04f07f038e5 --- /dev/null +++ b/test_programs/compile_success_empty/unquote_multiple_items_from_annotation/src/main.nr @@ -0,0 +1,14 @@ +#[foo] +struct Foo {} + +fn main() { + assert_eq(ONE, 1); + assert_eq(TWO, 2); +} + +comptime fn foo(_: StructDefinition) -> Quoted { + quote { + global ONE = 1; + global TWO = 2; + } +} diff --git a/test_programs/execution_failure/regression_5202/Nargo.toml b/test_programs/execution_failure/regression_5202/Nargo.toml new file mode 100644 index 00000000000..dcdc044836e --- /dev/null +++ b/test_programs/execution_failure/regression_5202/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "regression_5202" +type = "bin" +authors = [""] +compiler_version = ">=0.29.0" + +[dependencies] diff --git a/test_programs/execution_failure/regression_5202/src/main.nr b/test_programs/execution_failure/regression_5202/src/main.nr new file mode 100644 index 00000000000..45ffafca4c7 --- /dev/null +++ b/test_programs/execution_failure/regression_5202/src/main.nr @@ -0,0 +1,41 @@ +trait ToField { + fn to_field(self) -> Field; +} + +impl ToField for bool { fn to_field(self) -> Field { self as Field } } + +unconstrained fn get_unconstrained_option() -> Option { + Option::some(13) +} + +unconstrained fn should_i_assert() -> bool { + false +} + +fn get_magical_boolean() -> bool { + let option = get_unconstrained_option(); + + let pre_assert = option.is_some().to_field(); + + if should_i_assert() { + // Note that `should_i_assert` is unconstrained, so Noir should not be able to infer + // any behavior from the contents of this block. In this case it is actually false, so the + // assertion below is not even executed (if it did it'd fail since the values are not equal). + + assert_eq(option, Option::some(42)); /// <- this seems to be the trigger for the bug + } + + // In my testing, the `option` value exhibits weird behavior from this point on, as if it had been mutated + let post_assert = option.is_some().to_field(); + + // The following expression should be true, but I can get it to evaluate to false depending on how I call it + pre_assert == post_assert +} + +fn main() { + let magic = get_magical_boolean(); + + // One of these asserts should fail. Before #5202, they would both pass + assert_eq(magic, true); + assert_eq(magic, false); +} diff --git a/test_programs/compile_success_empty/impl_with_where_clause/Nargo.toml b/test_programs/execution_success/as_witness/Nargo.toml similarity index 62% rename from test_programs/compile_success_empty/impl_with_where_clause/Nargo.toml rename to test_programs/execution_success/as_witness/Nargo.toml index ef9bdce2640..18f3f99b5b5 100644 --- a/test_programs/compile_success_empty/impl_with_where_clause/Nargo.toml +++ b/test_programs/execution_success/as_witness/Nargo.toml @@ -1,5 +1,5 @@ [package] -name = "impl_with_where_clause" +name = "as_witness" type = "bin" authors = [""] diff --git a/test_programs/execution_success/as_witness/Prover.toml b/test_programs/execution_success/as_witness/Prover.toml new file mode 100644 index 00000000000..cd8a5b5e03c --- /dev/null +++ b/test_programs/execution_success/as_witness/Prover.toml @@ -0,0 +1 @@ +a = 42 \ No newline at end of file diff --git a/test_programs/execution_success/as_witness/src/main.nr b/test_programs/execution_success/as_witness/src/main.nr new file mode 100644 index 00000000000..a24f4af7669 --- /dev/null +++ b/test_programs/execution_success/as_witness/src/main.nr @@ -0,0 +1,5 @@ +// Simple example of checking where two arrays are different +fn main(a: Field) -> pub Field { + std::as_witness(a); + a +} diff --git a/test_programs/execution_success/binary_operator_overloading/Nargo.toml b/test_programs/execution_success/binary_operator_overloading/Nargo.toml new file mode 100644 index 00000000000..a43f38bdf30 --- /dev/null +++ b/test_programs/execution_success/binary_operator_overloading/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "binary_operator_overloading" +type = "bin" +authors = [""] +compiler_version = ">=0.20.0" + +[dependencies] diff --git a/test_programs/execution_success/operator_overloading/Prover.toml b/test_programs/execution_success/binary_operator_overloading/Prover.toml similarity index 100% rename from test_programs/execution_success/operator_overloading/Prover.toml rename to test_programs/execution_success/binary_operator_overloading/Prover.toml diff --git a/test_programs/execution_success/operator_overloading/src/main.nr b/test_programs/execution_success/binary_operator_overloading/src/main.nr similarity index 100% rename from test_programs/execution_success/operator_overloading/src/main.nr rename to test_programs/execution_success/binary_operator_overloading/src/main.nr diff --git a/test_programs/execution_success/brillig_cow_regression/src/main.nr b/test_programs/execution_success/brillig_cow_regression/src/main.nr index 7cd50860978..ae3adf6425c 100644 --- a/test_programs/execution_success/brillig_cow_regression/src/main.nr +++ b/test_programs/execution_success/brillig_cow_regression/src/main.nr @@ -1,8 +1,8 @@ // Tests a performance regression found in aztec-packages with brillig cow optimization -global MAX_NEW_NOTE_HASHES_PER_TX: u64 = 64; -global MAX_NEW_NULLIFIERS_PER_TX: u64 = 64; -global MAX_NEW_L2_TO_L1_MSGS_PER_TX: u64 = 2; +global MAX_NOTE_HASHES_PER_TX: u64 = 64; +global MAX_NULLIFIERS_PER_TX: u64 = 64; +global MAX_L2_TO_L1_MSGS_PER_TX: u64 = 2; global MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX: u64 = 16; global MAX_NEW_CONTRACTS_PER_TX: u64 = 1; global NUM_ENCRYPTED_LOGS_HASHES_PER_TX: u64 = 1; @@ -30,10 +30,10 @@ impl NewContractData { } struct DataToHash { - new_note_hashes: [Field; MAX_NEW_NOTE_HASHES_PER_TX], - new_nullifiers: [Field; MAX_NEW_NULLIFIERS_PER_TX], + new_note_hashes: [Field; MAX_NOTE_HASHES_PER_TX], + new_nullifiers: [Field; MAX_NULLIFIERS_PER_TX], public_data_update_requests: [PublicDataUpdateRequest; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], - new_l2_to_l1_msgs: [Field; MAX_NEW_L2_TO_L1_MSGS_PER_TX], + new_l2_to_l1_msgs: [Field; MAX_L2_TO_L1_MSGS_PER_TX], encrypted_logs_hash: [Field; NUM_FIELDS_PER_SHA256], unencrypted_logs_hash: [Field; NUM_FIELDS_PER_SHA256], new_contracts: [NewContractData; MAX_NEW_CONTRACTS_PER_TX], @@ -104,21 +104,21 @@ unconstrained fn main(kernel_data: DataToHash) -> pub [Field; NUM_FIELDS_PER_SHA let new_note_hashes = kernel_data.new_note_hashes; let new_nullifiers = kernel_data.new_nullifiers; let public_data_update_requests = kernel_data.public_data_update_requests; - let newL2ToL1msgs = kernel_data.new_l2_to_l1_msgs; + let l2ToL1Msgs = kernel_data.new_l2_to_l1_msgs; let encryptedLogsHash = kernel_data.encrypted_logs_hash; let unencryptedLogsHash = kernel_data.unencrypted_logs_hash; let mut offset = 0; - for j in 0..MAX_NEW_NOTE_HASHES_PER_TX { + for j in 0..MAX_NOTE_HASHES_PER_TX { tx_effects_hash_inputs[offset + j] = new_note_hashes[j]; } - offset += MAX_NEW_NOTE_HASHES_PER_TX ; + offset += MAX_NOTE_HASHES_PER_TX ; - for j in 0..MAX_NEW_NULLIFIERS_PER_TX { + for j in 0..MAX_NULLIFIERS_PER_TX { tx_effects_hash_inputs[offset + j] = new_nullifiers[j]; } - offset += MAX_NEW_NULLIFIERS_PER_TX ; + offset += MAX_NULLIFIERS_PER_TX ; for j in 0..MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX { tx_effects_hash_inputs[offset + j * 2] = @@ -128,10 +128,10 @@ unconstrained fn main(kernel_data: DataToHash) -> pub [Field; NUM_FIELDS_PER_SHA } offset += MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX * 2; - for j in 0..MAX_NEW_L2_TO_L1_MSGS_PER_TX { - tx_effects_hash_inputs[offset + j] = newL2ToL1msgs[j]; + for j in 0..MAX_L2_TO_L1_MSGS_PER_TX { + tx_effects_hash_inputs[offset + j] = l2ToL1Msgs[j]; } - offset += MAX_NEW_L2_TO_L1_MSGS_PER_TX; + offset += MAX_L2_TO_L1_MSGS_PER_TX; let contract_leaf = kernel_data.new_contracts[0]; tx_effects_hash_inputs[offset] = contract_leaf.hash(); diff --git a/test_programs/execution_success/fold_numeric_generic_poseidon/src/main.nr b/test_programs/execution_success/fold_numeric_generic_poseidon/src/main.nr index 7d12d63634b..8727b2a23de 100644 --- a/test_programs/execution_success/fold_numeric_generic_poseidon/src/main.nr +++ b/test_programs/execution_success/fold_numeric_generic_poseidon/src/main.nr @@ -4,7 +4,7 @@ global NUM_HASHES = 2; global HASH_LENGTH = 10; #[fold] -pub fn poseidon_hash(inputs: [Field; N]) -> Field { +pub fn poseidon_hash(inputs: [Field; N]) -> Field { Poseidon2::hash(inputs, inputs.len()) } diff --git a/test_programs/execution_success/hashmap/src/main.nr b/test_programs/execution_success/hashmap/src/main.nr index 8cf70cc5970..56b13d6779b 100644 --- a/test_programs/execution_success/hashmap/src/main.nr +++ b/test_programs/execution_success/hashmap/src/main.nr @@ -3,7 +3,6 @@ mod utils; use std::collections::map::HashMap; use std::hash::BuildHasherDefault; use std::hash::poseidon2::Poseidon2Hasher; -use std::cmp::Eq; use utils::cut; diff --git a/test_programs/execution_success/modulus/src/main.nr b/test_programs/execution_success/modulus/src/main.nr index c7d6a2e2c7d..1627cc0dba2 100644 --- a/test_programs/execution_success/modulus/src/main.nr +++ b/test_programs/execution_success/modulus/src/main.nr @@ -3,6 +3,11 @@ fn main(bn254_modulus_be_bytes: [u8; 32], bn254_modulus_be_bits: [u1; 254]) { // NOTE: The constraints used in this circuit will only work when testing nargo with the plonk bn254 backend assert(modulus_size == 254); + assert_reverse( + std::field::modulus_be_bytes(), + std::field::modulus_le_bytes() + ); + let modulus_be_byte_array = std::field::modulus_be_bytes(); for i in 0..32 { assert(modulus_be_byte_array[i] == bn254_modulus_be_bytes[i]); @@ -21,3 +26,9 @@ fn main(bn254_modulus_be_bytes: [u8; 32], bn254_modulus_be_bits: [u1; 254]) { assert(modulus_le_bits[i] == bn254_modulus_be_bits[253 - i]); } } + +fn assert_reverse(forwards: [u8], backwards: [u8]) { + for i in 0..32 { + assert_eq(forwards[i], backwards[31 - i]); + } +} diff --git a/test_programs/execution_success/no_predicates_numeric_generic_poseidon/src/main.nr b/test_programs/execution_success/no_predicates_numeric_generic_poseidon/src/main.nr index 94db8c2b414..60a7d2f8d17 100644 --- a/test_programs/execution_success/no_predicates_numeric_generic_poseidon/src/main.nr +++ b/test_programs/execution_success/no_predicates_numeric_generic_poseidon/src/main.nr @@ -4,7 +4,7 @@ global NUM_HASHES = 2; global HASH_LENGTH = 10; #[no_predicates] -pub fn poseidon_hash(inputs: [Field; N]) -> Field { +pub fn poseidon_hash(inputs: [Field; N]) -> Field { Poseidon2::hash(inputs, inputs.len()) } diff --git a/test_programs/execution_success/poseidon2/Nargo.toml b/test_programs/execution_success/poseidon2/Nargo.toml new file mode 100644 index 00000000000..ad812b40398 --- /dev/null +++ b/test_programs/execution_success/poseidon2/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "poseidon2" +type = "bin" +authors = [""] + +[dependencies] diff --git a/test_programs/execution_success/poseidon2/Prover.toml b/test_programs/execution_success/poseidon2/Prover.toml new file mode 100644 index 00000000000..b795ec36904 --- /dev/null +++ b/test_programs/execution_success/poseidon2/Prover.toml @@ -0,0 +1,5 @@ +inputs = ["4218458030232820015255714794613421442512497197372123294583664908262453897094", + "4218458030232820015255714794613421442512497197372123294583664908262453897094", + "4218458030232820015255714794613421442512497197372123294583664908262453897094", + "4218458030232820015255714794613421442512497197372123294583664908262453897094"] +expected_hash = "0x2f43a0f83b51a6f5fc839dea0ecec74947637802a579fa9841930a25a0bcec11" diff --git a/test_programs/execution_success/poseidon2/src/main.nr b/test_programs/execution_success/poseidon2/src/main.nr new file mode 100644 index 00000000000..3186617bfc8 --- /dev/null +++ b/test_programs/execution_success/poseidon2/src/main.nr @@ -0,0 +1,8 @@ +// docs:start:poseidon2 +use std::hash::poseidon2; + +fn main(inputs: [Field; 4], expected_hash: Field) { + let hash = poseidon2::Poseidon2::hash(inputs, inputs.len()); + assert_eq(hash, expected_hash); +} +// docs:end:poseidon2 diff --git a/test_programs/execution_success/poseidon_bn254_hash/Prover.toml b/test_programs/execution_success/poseidon_bn254_hash/Prover.toml index fa6fd05b0a3..8eecf9a3db2 100644 --- a/test_programs/execution_success/poseidon_bn254_hash/Prover.toml +++ b/test_programs/execution_success/poseidon_bn254_hash/Prover.toml @@ -2,8 +2,3 @@ x1 = [1,2] y1 = "0x115cc0f5e7d690413df64c6b9662e9cf2a3617f2743245519e19607a4417189a" x2 = [1,2,3,4] y2 = "0x299c867db6c1fdd79dcefa40e4510b9837e60ebb1ce0663dbaa525df65250465" -x3 = ["4218458030232820015255714794613421442512497197372123294583664908262453897094", - "4218458030232820015255714794613421442512497197372123294583664908262453897094", - "4218458030232820015255714794613421442512497197372123294583664908262453897094", - "4218458030232820015255714794613421442512497197372123294583664908262453897094"] - y3 = "0x2f43a0f83b51a6f5fc839dea0ecec74947637802a579fa9841930a25a0bcec11" diff --git a/test_programs/execution_success/poseidon_bn254_hash/src/main.nr b/test_programs/execution_success/poseidon_bn254_hash/src/main.nr index cf1c190e5c9..5ea02d53e96 100644 --- a/test_programs/execution_success/poseidon_bn254_hash/src/main.nr +++ b/test_programs/execution_success/poseidon_bn254_hash/src/main.nr @@ -1,15 +1,11 @@ // docs:start:poseidon use std::hash::poseidon; -use std::hash::poseidon2; -fn main(x1: [Field; 2], y1: pub Field, x2: [Field; 4], y2: pub Field, x3: [Field; 4], y3: Field) { +fn main(x1: [Field; 2], y1: pub Field, x2: [Field; 4], y2: pub Field) { let hash1 = poseidon::bn254::hash_2(x1); assert(hash1 == y1); let hash2 = poseidon::bn254::hash_4(x2); assert(hash2 == y2); - - let hash3 = poseidon2::Poseidon2::hash(x3, x3.len()); - assert(hash3 == y3); } // docs:end:poseidon diff --git a/test_programs/execution_success/regression_4088/src/main.nr b/test_programs/execution_success/regression_4088/src/main.nr index 9e4d7892fc3..12a7afca68c 100644 --- a/test_programs/execution_success/regression_4088/src/main.nr +++ b/test_programs/execution_success/regression_4088/src/main.nr @@ -1,4 +1,4 @@ -trait Serialize { +trait Serialize { fn serialize(self) -> [Field; N]; } @@ -12,7 +12,7 @@ impl Serialize<1> for ValueNote { } } -fn check(serialized_note: [Field; N]) { +fn check(serialized_note: [Field; N]) { assert(serialized_note[0] == 0); } diff --git a/test_programs/execution_success/regression_4124/src/main.nr b/test_programs/execution_success/regression_4124/src/main.nr index 2b0e65a0b6c..6caea017798 100644 --- a/test_programs/execution_success/regression_4124/src/main.nr +++ b/test_programs/execution_success/regression_4124/src/main.nr @@ -1,6 +1,6 @@ use std::option::Option; -trait MyDeserialize { +trait MyDeserialize { fn deserialize(fields: [Field; N]) -> Self; } @@ -10,7 +10,7 @@ impl MyDeserialize<1> for Field { } } -pub fn storage_read() -> [Field; N] { +pub fn storage_read() -> [Field; N] { std::unsafe::zeroed() } diff --git a/test_programs/execution_success/regression_5202/fraction/LICENSE b/test_programs/execution_success/regression_5202/fraction/LICENSE deleted file mode 100644 index 929af4807ca..00000000000 --- a/test_programs/execution_success/regression_5202/fraction/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2023 Resurgence Labs - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/test_programs/execution_success/regression_5202/fraction/README.md b/test_programs/execution_success/regression_5202/fraction/README.md deleted file mode 100644 index 17c81e5555a..00000000000 --- a/test_programs/execution_success/regression_5202/fraction/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# fraction -The Noir repository for accessing Fractions by Resurgence Labs - -# usage -To Use as a Dependency, add the following line to your project's Nargo.toml under [dependencies]: -fraction = { tag = "v1.2.2", git = "https://github.com/resurgencelabs/fraction" } - -Note: This is a highly dynamic repository. The code will change as more features are added, both at the repository level by Resurgence Labs, as well as at the language level itself by the Noir team. - - diff --git a/test_programs/execution_success/regression_5202/fraction/src/lib.nr b/test_programs/execution_success/regression_5202/fraction/src/lib.nr deleted file mode 100644 index 4b20eb6b76c..00000000000 --- a/test_programs/execution_success/regression_5202/fraction/src/lib.nr +++ /dev/null @@ -1,201 +0,0 @@ -use dep::std; - -global MAX: Fraction = Fraction { sign: true, num: 3050913689, den: 1 }; -global MIN: Fraction = Fraction { sign: false, num: 3050913689, den: 1 }; -global ZERO: Fraction = Fraction { sign: true, num: 0, den: 1 }; -global ONE: Fraction = Fraction { sign: true, num: 1, den: 1 }; -global NEGATIVE_ONE: Fraction = Fraction { sign: false, num: 1, den: 1 }; - -struct Fraction { - sign: bool, - num: u32, - den: u32, -} - -// Create a Fraction type variable without lenghtier code -pub fn toFraction(s: bool, n: u32, d: u32) -> Fraction { - assert(d != 0); - Fraction { sign: s, num: n, den: d } -} - -// Swaps the numerator and denominator -fn invertFraction(f: Fraction) -> Fraction { - assert(f.num != 0); - Fraction { sign: f.sign, num: f.den, den: f.num } -} - -// Changes the Sign of the Fraction -fn signChangeFraction(f: Fraction) -> Fraction { - Fraction { sign: !f.sign, num: f.num, den: f.den } -} - -// this method will only work till numerator and denominator values are under 100 -// this has been set for efficiency reasons, and will be modified once the Noir team -// can implement dynamic limit for loops -fn reduceFraction(f: Fraction) -> Fraction { - let mut a = f.num; - let mut b = f.den; - let mut j = 1; - let mut gcd = 1; - - let min = if a > b { b } else { a }; - - for i in 2..100 { - j = i as u32; - if (j <= min) { - if (a % j == 0) & (b % j == 0) { - gcd = j; - } - } - } - - Fraction { sign: f.sign, num: f.num / gcd, den: f.den / gcd } -} - -// Adds two fractions -pub fn addFraction(f1: Fraction, f2: Fraction) -> Fraction { - let mut an = U128::from_integer(f1.num); - let mut ad = U128::from_integer(f1.den); - let mut bn = U128::from_integer(f2.num); - let mut bd = U128::from_integer(f2.den); - let mut m = f1; - let mut n = f2; - - if f1.sign == f2.sign { - if ((ad * bd > U128::from_integer(2000000000)) - | ((an * bd + ad * bn) > U128::from_integer(2000000000)) - | ((an * bd) > U128::from_integer(2000000000)) - | ((ad * bn) > U128::from_integer(2000000000))) { - m = reduceFraction(m); - n = reduceFraction(n); - } - an = U128::from_integer(m.num); - ad = U128::from_integer(m.den); - bn = U128::from_integer(n.num); - bd = U128::from_integer(n.den); - if ((ad * bd > U128::from_integer(2000000000)) - | ((an * bd + ad * bn) > U128::from_integer(2000000000)) - | ((an * bd) > U128::from_integer(2000000000)) - | ((ad * bn) > U128::from_integer(2000000000))) { - let mut ddd = (an * bd + ad * bn) / (ad * bd); - let mut factor = U128::from_integer(1); - for _ in 1..5 { - if ddd * U128::from_integer(10) < U128::from_integer(2000000000) { - ddd *= U128::from_integer(10); - factor *= U128::from_integer(10); - } - } - let np: u32 = U128::to_integer(((an * bd + ad * bn) * factor) / (ad * bd)); - let fx: u32 = U128::to_integer(factor); - Fraction { sign: f1.sign, num: np, den: fx } - } else { - Fraction { sign: f1.sign, num: (m.num * n.den + n.num * m.den), den: m.den * n.den } - } - } else if ((an * bd) > (bn * ad)) { - if ((ad * bd > U128::from_integer(2000000000)) - | ((an * bd - ad * bn) > U128::from_integer(2000000000)) - | ((an * bd) > U128::from_integer(2000000000))) { - m = reduceFraction(m); - n = reduceFraction(n); - } - an = U128::from_integer(m.num); - ad = U128::from_integer(m.den); - bn = U128::from_integer(n.num); - bd = U128::from_integer(n.den); - - if ((ad * bd > U128::from_integer(2000000000)) - | ((an * bd - ad * bn) > U128::from_integer(2000000000)) - | ((an * bd) > U128::from_integer(2000000000))) { - let mut ddd = (an * bd - ad * bn) / (ad * bd); - let mut factor = U128::from_integer(1); - for _ in 1..5 { - if ddd * U128::from_integer(10) < U128::from_integer(2000000000) { - ddd *= U128::from_integer(10); - factor *= U128::from_integer(10); - } - } - let np: u32 = U128::to_integer(((an * bd - ad * bn) * factor) / (ad * bd)); - let fx: u32 = U128::to_integer(factor); - Fraction { sign: f1.sign, num: np, den: fx } - } else { - Fraction { sign: f1.sign, num: (m.num * n.den - n.num * m.den), den: m.den * n.den } - } - } else { - if ((ad * bd > U128::from_integer(2000000000)) - | ((bn * ad - bd * an) > U128::from_integer(2000000000)) - | ((bn * ad) > U128::from_integer(2000000000))) { - m = reduceFraction(m); - n = reduceFraction(n); - } - an = U128::from_integer(m.num); - ad = U128::from_integer(m.den); - bn = U128::from_integer(n.num); - bd = U128::from_integer(n.den); - if ((ad * bd > U128::from_integer(2000000000)) - | ((bn * ad - bd * an) > U128::from_integer(2000000000)) - | ((bn * ad) > U128::from_integer(2000000000))) { - let mut ddd = (bn * ad - bd * an) / (ad * bd); - let mut factor = U128::from_integer(1); - for _ in 1..5 { - if ddd * U128::from_integer(10) < U128::from_integer(2000000000) { - ddd *= U128::from_integer(10); - factor *= U128::from_integer(10); - } - } - let np: u32 = U128::to_integer(((bn * ad - bd * an) * factor) / (ad * bd)); - let fx: u32 = U128::to_integer(factor); - Fraction { sign: f2.sign, num: np, den: fx } - } else { - Fraction { sign: f2.sign, num: (n.num * m.den - m.num * n.den), den: m.den * n.den } - } - } -} - -// Returns the closest but smaller Integer to the Given Fraction, but typecast to Fraction for convenience -pub fn floor(f: Fraction) -> Fraction { - let q = f.num / f.den; - if q * f.den == f.num { - Fraction { sign: f.sign, num: f.num, den: f.den } - } else if f.sign { - Fraction { sign: f.sign, num: q, den: 1 } - } else { - Fraction { sign: f.sign, num: q + 1, den: 1 } - } -} - -#[test] -fn test_sum() { - let f1 = toFraction(true, 3, 5); - let f2 = toFraction(true, 2, 5); - let f = addFraction(f1, f2); - assert(f.num == f.den); -} - -#[test] -fn test_reduce() { - let f1 = toFraction(true, 2, 10); - let f2 = reduceFraction(f1); - assert(f2.num == 1); -} - -#[test] -fn test_floor() { - let f = toFraction(true, 7, 5); - let fl = floor(f); - assert(fl.num == 1); - assert(fl.den == 1); -} - -#[test] -fn test_floor2() { - let f = toFraction(false, 12, 5); - let fl = floor(f); - assert(fl.num == 3); - assert(fl.den == 1); -} - -#[test] -fn test_globals() { - let a = addFraction(ONE, NEGATIVE_ONE); - assert(a.num == ZERO.num); -} diff --git a/test_programs/execution_success/regression_5202/src/main.nr b/test_programs/execution_success/regression_5202/src/main.nr deleted file mode 100644 index 2ebfd7890ba..00000000000 --- a/test_programs/execution_success/regression_5202/src/main.nr +++ /dev/null @@ -1,23 +0,0 @@ -use fraction::{Fraction, MAX, floor, toFraction, addFraction}; - -fn main() { - let g1 = toFraction(true, 33333333, 5); - let g2 = toFraction(true, 500000, 33333333); - let a = addFraction(g1, g2); - - let f1 = floor(a); - let f2 = MAX; - assert(f1.sign); - assert(f2.sign); - - if f1.sign != f2.sign { - if (f1.sign) { () } else { () } - } else { - // Test fails here before the fix to #5202. - // An optimization which assumes an if condition to be true/false - // within the then/else branch respectively wasn't being set properly - // causing f1.sign to be assumed to be false in this else branch. - assert(f1.sign); - assert(f2.sign); - } -} diff --git a/test_programs/execution_success/regression_5252/src/main.nr b/test_programs/execution_success/regression_5252/src/main.nr index 0bfd596a777..4b4d1937c0e 100644 --- a/test_programs/execution_success/regression_5252/src/main.nr +++ b/test_programs/execution_success/regression_5252/src/main.nr @@ -3,7 +3,7 @@ use std::hash::{mimc, poseidon, poseidon2::Poseidon2}; global NUM_HASHES = 3; global HASH_LENGTH = 20; -pub fn poseidon_hash(inputs: [Field; N]) -> Field { +pub fn poseidon_hash(inputs: [Field; N]) -> Field { Poseidon2::hash(inputs, inputs.len()) } diff --git a/test_programs/execution_success/regression_5435/Nargo.toml b/test_programs/execution_success/regression_5435/Nargo.toml new file mode 100644 index 00000000000..6affc423b7a --- /dev/null +++ b/test_programs/execution_success/regression_5435/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "regression_5435" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] diff --git a/test_programs/execution_success/regression_5435/Prover.toml b/test_programs/execution_success/regression_5435/Prover.toml new file mode 100644 index 00000000000..f3e2bbe32e7 --- /dev/null +++ b/test_programs/execution_success/regression_5435/Prover.toml @@ -0,0 +1,2 @@ +input = "0" +enable = false diff --git a/test_programs/execution_success/regression_5435/src/main.nr b/test_programs/execution_success/regression_5435/src/main.nr new file mode 100644 index 00000000000..65f13c5b201 --- /dev/null +++ b/test_programs/execution_success/regression_5435/src/main.nr @@ -0,0 +1,18 @@ +fn main(input: Field, enable: bool) { + if enable { + let hash = no_predicate_function(input); + // `EnableSideEffects` instruction from above instruction leaks out and removes the predicate from this call, + // resulting in execution failure. + fail(hash); + } +} + +#[no_predicates] +fn no_predicate_function(enable: Field) -> Field { + // if-statement ensures that an `EnableSideEffects` instruction is emitted. + if enable == 0 { 1 } else { 0 } +} + +unconstrained fn fail(_: Field) { + assert(false); +} diff --git a/test_programs/execution_success/slices/src/main.nr b/test_programs/execution_success/slices/src/main.nr index 8be79cdc3c4..2bd4dbd97b0 100644 --- a/test_programs/execution_success/slices/src/main.nr +++ b/test_programs/execution_success/slices/src/main.nr @@ -45,6 +45,14 @@ fn main(x: Field, y: pub Field) { assert(append[0] == 1); assert(append[4] == 5); + let mapped = &[1, 2].map(|x| x + 1); + assert_eq(mapped, &[2, 3]); + + assert_eq(&[1, 2, 3].fold(0, |acc, x| acc + x), 6); + assert_eq(&[1, 2, 3].reduce(|acc, x| acc + x), 6); + assert(&[2, 4, 6].all(|x| x > 0)); + assert(&[2, 4, 6].any(|x| x > 5)); + regression_2083(); // The parameters to this function must come from witness values (inputs to main) regression_merge_slices(x, y); diff --git a/test_programs/execution_success/operator_overloading/Nargo.toml b/test_programs/execution_success/unary_operator_overloading/Nargo.toml similarity index 70% rename from test_programs/execution_success/operator_overloading/Nargo.toml rename to test_programs/execution_success/unary_operator_overloading/Nargo.toml index 7f9f18ff567..ebc88faaaf4 100644 --- a/test_programs/execution_success/operator_overloading/Nargo.toml +++ b/test_programs/execution_success/unary_operator_overloading/Nargo.toml @@ -1,5 +1,5 @@ [package] -name = "operator_overloading" +name = "unary_operator_overloading" type = "bin" authors = [""] compiler_version = ">=0.20.0" diff --git a/test_programs/execution_success/unary_operator_overloading/Prover.toml b/test_programs/execution_success/unary_operator_overloading/Prover.toml new file mode 100644 index 00000000000..b95c23a4d31 --- /dev/null +++ b/test_programs/execution_success/unary_operator_overloading/Prover.toml @@ -0,0 +1 @@ +x = 3 diff --git a/test_programs/execution_success/unary_operator_overloading/src/main.nr b/test_programs/execution_success/unary_operator_overloading/src/main.nr new file mode 100644 index 00000000000..20cdac51434 --- /dev/null +++ b/test_programs/execution_success/unary_operator_overloading/src/main.nr @@ -0,0 +1,36 @@ +use std::ops::{Neg, Not}; + +// x = 3 +fn main(x: u32) { + let wx = Wrapper::new(x as i32); + let ex: i32 = 3; + + assert((-wx).inner == -ex); + assert((!wx).inner == !ex); + + // Check that it works with type variables (x's type isn't immediately known) + let x = 3; + assert(-3 == -x); +} + +struct Wrapper { + inner: i32 +} + +impl Wrapper { + fn new(inner: i32) -> Self { + Wrapper { inner } + } +} + +impl Neg for Wrapper { + fn neg(self) -> Wrapper { + Wrapper::new(-self.inner) + } +} + +impl Not for Wrapper { + fn not(self) -> Wrapper { + Wrapper::new(!self.inner) + } +} diff --git a/test_programs/gates_report.sh b/test_programs/gates_report.sh index b33e81b037c..6c901ff24bc 100755 --- a/test_programs/gates_report.sh +++ b/test_programs/gates_report.sh @@ -4,7 +4,7 @@ set -e BACKEND=${BACKEND:-bb} # These tests are incompatible with gas reporting -excluded_dirs=("workspace" "workspace_default_member" "double_verify_nested_proof") +excluded_dirs=("workspace" "workspace_default_member" "databus") current_dir=$(pwd) artifacts_path="$current_dir/acir_artifacts" @@ -18,6 +18,10 @@ NUM_ARTIFACTS=$(ls -1q "$artifacts_path" | wc -l) ITER="1" for pathname in $test_dirs; do ARTIFACT_NAME=$(basename "$pathname") + if [[ " ${excluded_dirs[@]} " =~ "$ARTIFACT_NAME" ]]; then + ITER=$(( $ITER + 1 )) + continue + fi GATES_INFO=$($BACKEND gates -b "$artifacts_path/$ARTIFACT_NAME/target/program.json") MAIN_FUNCTION_INFO=$(echo $GATES_INFO | jq -r '.functions[0] | .name = "main"') diff --git a/test_programs/noir_test_failure/should_fail_mismatch/src/main.nr b/test_programs/noir_test_failure/should_fail_mismatch/src/main.nr index 08a9234a752..59b99c85c0b 100644 --- a/test_programs/noir_test_failure/should_fail_mismatch/src/main.nr +++ b/test_programs/noir_test_failure/should_fail_mismatch/src/main.nr @@ -2,14 +2,9 @@ fn test_different_string() { assert_eq(0, 1, "Different string"); } -// The assert message has a space -#[test(should_fail_with = "Not equal")] -fn test_with_extra_space() { - assert_eq(0, 1, "Not equal "); -} -// The assert message has a space -#[test(should_fail_with = "Not equal")] -fn test_runtime_mismatch() { - // We use a pedersen commitment here so that the assertion failure is only known at runtime. - assert_eq(std::hash::pedersen_commitment([27]).x, 0, "Not equal "); + +// The failure reason is a substring of the expected message, but it should be the other way around +#[test(should_fail_with = "Definitely Not equal!")] +fn test_wrong_expectation() { + assert_eq(0, 1, "Not equal"); } diff --git a/test_programs/noir_test_success/comptime_globals/src/main.nr b/test_programs/noir_test_success/comptime_globals/src/main.nr index efe9f0742b9..95c54b96609 100644 --- a/test_programs/noir_test_success/comptime_globals/src/main.nr +++ b/test_programs/noir_test_success/comptime_globals/src/main.nr @@ -5,7 +5,7 @@ comptime global FOO: Field = foo(); // Due to this function's mutability and branching, SSA currently fails // to fold this function into a constant before the assert_constant check // is evaluated before loop unrolling. -fn foo() -> Field { +comptime fn foo() -> Field { let mut three = 3; if three == 3 { 5 } else { 6 } } diff --git a/test_programs/noir_test_success/fuzzer_checks/Nargo.toml b/test_programs/noir_test_success/fuzzer_checks/Nargo.toml new file mode 100644 index 00000000000..cd09d0d344d --- /dev/null +++ b/test_programs/noir_test_success/fuzzer_checks/Nargo.toml @@ -0,0 +1,5 @@ +[package] +name = "fuzzer_checks" +type = "bin" +authors = [""] +[dependencies] diff --git a/test_programs/noir_test_success/fuzzer_checks/src/main.nr b/test_programs/noir_test_success/fuzzer_checks/src/main.nr new file mode 100644 index 00000000000..2b928db092e --- /dev/null +++ b/test_programs/noir_test_success/fuzzer_checks/src/main.nr @@ -0,0 +1,6 @@ + +#[test(should_fail_with = "42 is not allowed")] +fn finds_magic_value(x: u32) { + let x = x as u64; + assert(2 * x != 42, "42 is not allowed"); +} diff --git a/test_programs/noir_test_success/regression_4561/src/main.nr b/test_programs/noir_test_success/regression_4561/src/main.nr index 70c447b49af..ad40941ff51 100644 --- a/test_programs/noir_test_success/regression_4561/src/main.nr +++ b/test_programs/noir_test_success/regression_4561/src/main.nr @@ -42,3 +42,37 @@ fn two_nested_return() { OracleMock::mock("two_nested_return").returns((0, [1, 2, 3, 4, 5, 6], 7, [1, 2, 3, 4, 5, 6])); assert_eq(two_nested_return_unconstrained(), (0, [[1, 2, 3], [4, 5, 6]], 7, [[1, 2, 3], [4, 5, 6]])); } + +#[oracle(foo_return)] +unconstrained fn foo_return() -> (Field, TReturn, TestTypeFoo) {} +unconstrained fn foo_return_unconstrained() -> (Field, TReturn, TestTypeFoo) { + foo_return() +} + +struct TestTypeFoo { + a: Field, + b: [[[Field; 3]; 4]; 2], + c: [TReturnElem; 2], + d: TReturnElem, +} + +#[test] +fn complexe_struct_return() { + OracleMock::mock("foo_return").returns( + ( + 0, [1, 2, 3, 4, 5, 6], 7, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], [1, 2, 3, 4, 5, 6] + ) + ); + let foo_x = foo_return_unconstrained(); + assert_eq((foo_x.0, foo_x.1), (0, [[1, 2, 3], [4, 5, 6]])); + assert_eq(foo_x.2.a, 7); + assert_eq( + foo_x.2.b, [ + [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]], [[13, 14, 15], [16, 17, 18], [19, 20, 21], [22, 23, 24]] + ] + ); + let a: TReturnElem = [1, 2, 3]; + let b: TReturnElem = [4, 5, 6]; + assert_eq(foo_x.2.c, [a, b]); + assert_eq(foo_x.2.d, a); +} diff --git a/test_programs/noir_test_success/should_fail_with_matches/src/main.nr b/test_programs/noir_test_success/should_fail_with_matches/src/main.nr index b713976643b..42696762ffe 100644 --- a/test_programs/noir_test_success/should_fail_with_matches/src/main.nr +++ b/test_programs/noir_test_success/should_fail_with_matches/src/main.nr @@ -3,6 +3,11 @@ fn test_should_fail_with_match() { assert_eq(0, 1, "Not equal"); } +#[test(should_fail_with = "Not equal")] +fn test_should_fail_with_match_partial_match() { + assert_eq(0, 1, "Definitely Not equal!"); +} + #[test(should_fail)] fn test_should_fail_without_match() { assert_eq(0, 1); @@ -48,6 +53,11 @@ unconstrained fn unconstrained_test_should_fail_with_match() { assert_eq(0, 1, "Not equal"); } +#[test(should_fail_with = "Not equal")] +unconstrained fn unconstrained_test_should_fail_with_match_partial_match() { + assert_eq(0, 1, "Definitely Not equal!"); +} + #[test(should_fail)] unconstrained fn unconstrained_test_should_fail_without_match() { assert_eq(0, 1); diff --git a/test_programs/rebuild.sh b/test_programs/rebuild.sh index 094c3902583..13479f58b4b 100755 --- a/test_programs/rebuild.sh +++ b/test_programs/rebuild.sh @@ -60,7 +60,6 @@ for dir in $current_dir/benchmarks/*; do dirs_to_process+=("$dir") done - parallel -j0 process_dir {} "$current_dir" ::: ${dirs_to_process[@]} echo "Rebuild Succeeded!" diff --git a/tooling/acvm_cli/Cargo.toml b/tooling/acvm_cli/Cargo.toml index 1cfd1f3b270..a592f2d65f3 100644 --- a/tooling/acvm_cli/Cargo.toml +++ b/tooling/acvm_cli/Cargo.toml @@ -20,7 +20,7 @@ path = "src/main.rs" [dependencies] thiserror.workspace = true toml.workspace = true -color-eyre = "0.6.2" +color-eyre.workspace = true clap.workspace = true acvm.workspace = true nargo.workspace = true diff --git a/tooling/fuzzer/src/dictionary/mod.rs b/tooling/fuzzer/src/dictionary/mod.rs new file mode 100644 index 00000000000..a45b9c3abb2 --- /dev/null +++ b/tooling/fuzzer/src/dictionary/mod.rs @@ -0,0 +1,132 @@ +//! This module defines how to build a dictionary of values which are likely to be correspond +//! to significant inputs during fuzzing by inspecting the [Program] being fuzzed. +//! +//! This dictionary can be fed into the fuzzer's [strategy][proptest::strategy::Strategy] in order to bias it towards +//! generating these values to ensure they get proper coverage. +use std::collections::HashSet; + +use acvm::{ + acir::{ + circuit::{ + brillig::{BrilligBytecode, BrilligInputs}, + directives::Directive, + opcodes::{BlackBoxFuncCall, ConstantOrWitnessEnum, FunctionInput}, + Circuit, Opcode, Program, + }, + native_types::Expression, + }, + brillig_vm::brillig::Opcode as BrilligOpcode, + AcirField, +}; + +/// Constructs a [HashSet] of values pulled from a [Program] which are likely to be correspond +/// to significant inputs during fuzzing. +pub(super) fn build_dictionary_from_program(program: &Program) -> HashSet { + let constrained_dictionaries = program.functions.iter().map(build_dictionary_from_circuit); + let unconstrained_dictionaries = + program.unconstrained_functions.iter().map(build_dictionary_from_unconstrained_function); + let dictionaries = constrained_dictionaries.chain(unconstrained_dictionaries); + + let mut constants: HashSet = HashSet::new(); + for dictionary in dictionaries { + constants.extend(dictionary); + } + constants +} + +fn build_dictionary_from_circuit(circuit: &Circuit) -> HashSet { + let mut constants: HashSet = HashSet::new(); + + fn insert_expr(dictionary: &mut HashSet, expr: &Expression) { + let quad_coefficients = expr.mul_terms.iter().map(|(k, _, _)| *k); + let linear_coefficients = expr.linear_combinations.iter().map(|(k, _)| *k); + let coefficients = linear_coefficients.chain(quad_coefficients); + + dictionary.extend(coefficients.clone()); + dictionary.insert(expr.q_c); + + // We divide the constant term by any coefficients in the expression to aid solving constraints such as `2 * x - 4 == 0`. + let scaled_constants = coefficients.map(|coefficient| expr.q_c / coefficient); + dictionary.extend(scaled_constants); + } + + fn insert_array_len(dictionary: &mut HashSet, array: &[T]) { + let array_length = array.len() as u128; + dictionary.insert(F::from(array_length)); + dictionary.insert(F::from(array_length - 1)); + } + + for opcode in &circuit.opcodes { + match opcode { + Opcode::AssertZero(expr) + | Opcode::Call { predicate: Some(expr), .. } + | Opcode::MemoryOp { predicate: Some(expr), .. } + | Opcode::Directive(Directive::ToLeRadix { a: expr, .. }) => { + insert_expr(&mut constants, expr) + } + + Opcode::MemoryInit { init, .. } => insert_array_len(&mut constants, init), + + Opcode::BrilligCall { inputs, predicate, .. } => { + for input in inputs { + match input { + BrilligInputs::Single(expr) => insert_expr(&mut constants, expr), + BrilligInputs::Array(exprs) => { + exprs.iter().for_each(|expr| insert_expr(&mut constants, expr)); + insert_array_len(&mut constants, exprs); + } + BrilligInputs::MemoryArray(_) => (), + } + } + if let Some(predicate) = predicate { + insert_expr(&mut constants, predicate) + } + } + + Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { + input: FunctionInput { input: ConstantOrWitnessEnum::Constant(c), num_bits }, + }) => { + let field = 1u128.wrapping_shl(*num_bits); + constants.insert(F::from(field)); + constants.insert(F::from(field - 1)); + constants.insert(*c); + } + Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { + input: FunctionInput { input: ConstantOrWitnessEnum::Witness(_), num_bits }, + }) => { + let field = 1u128.wrapping_shl(*num_bits); + constants.insert(F::from(field)); + constants.insert(F::from(field - 1)); + } + _ => (), + } + } + + constants +} + +fn build_dictionary_from_unconstrained_function( + function: &BrilligBytecode, +) -> HashSet { + let mut constants: HashSet = HashSet::new(); + + for opcode in &function.bytecode { + match opcode { + BrilligOpcode::Cast { bit_size, .. } => { + let field = 1u128.wrapping_shl(*bit_size); + constants.insert(F::from(field)); + constants.insert(F::from(field - 1)); + } + BrilligOpcode::Const { bit_size, value, .. } => { + constants.insert(*value); + + let field = 1u128.wrapping_shl(*bit_size); + constants.insert(F::from(field)); + constants.insert(F::from(field - 1)); + } + _ => (), + } + } + + constants +} diff --git a/tooling/fuzzer/src/lib.rs b/tooling/fuzzer/src/lib.rs index 42dccc1dc83..28d7353f35a 100644 --- a/tooling/fuzzer/src/lib.rs +++ b/tooling/fuzzer/src/lib.rs @@ -4,9 +4,11 @@ //! Code is used under the MIT license. use acvm::{blackbox_solver::StubbedBlackBoxSolver, FieldElement}; +use dictionary::build_dictionary_from_program; use noirc_abi::InputMap; use proptest::test_runner::{TestCaseError, TestError, TestRunner}; +mod dictionary; mod strategies; mod types; @@ -37,7 +39,8 @@ impl FuzzedExecutor { /// Fuzzes the provided program. pub fn fuzz(&self) -> FuzzTestResult { - let strategy = strategies::arb_input_map(&self.program.abi); + let dictionary = build_dictionary_from_program(&self.program.bytecode); + let strategy = strategies::arb_input_map(&self.program.abi, dictionary); let run_result: Result<(), TestError> = self.runner.clone().run(&strategy, |input_map| { diff --git a/tooling/fuzzer/src/strategies/mod.rs b/tooling/fuzzer/src/strategies/mod.rs index f5b03953ba8..46187a28d5b 100644 --- a/tooling/fuzzer/src/strategies/mod.rs +++ b/tooling/fuzzer/src/strategies/mod.rs @@ -5,28 +5,22 @@ use proptest::prelude::*; use acvm::{AcirField, FieldElement}; use noirc_abi::{input_parser::InputValue, Abi, AbiType, InputMap, Sign}; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashSet}; use uint::UintStrategy; mod int; mod uint; -proptest::prop_compose! { - pub(super) fn arb_field_from_integer(bit_size: u32)(value: u128)-> FieldElement { - let width = (bit_size % 128).clamp(1, 127); - let max_value = 2u128.pow(width) - 1; - let value = value % max_value; - FieldElement::from(value) - } -} - -pub(super) fn arb_value_from_abi_type(abi_type: &AbiType) -> SBoxedStrategy { +pub(super) fn arb_value_from_abi_type( + abi_type: &AbiType, + dictionary: HashSet, +) -> SBoxedStrategy { match abi_type { AbiType::Field => vec(any::(), 32) .prop_map(|bytes| InputValue::Field(FieldElement::from_be_bytes_reduce(&bytes))) .sboxed(), AbiType::Integer { width, sign } if sign == &Sign::Unsigned => { - UintStrategy::new(*width as usize) + UintStrategy::new(*width as usize, dictionary) .prop_map(|uint| InputValue::Field(uint.into())) .sboxed() } @@ -55,7 +49,7 @@ pub(super) fn arb_value_from_abi_type(abi_type: &AbiType) -> SBoxedStrategy { let length = *length as usize; - let elements = vec(arb_value_from_abi_type(typ), length..=length); + let elements = vec(arb_value_from_abi_type(typ, dictionary), length..=length); elements.prop_map(InputValue::Vec).sboxed() } @@ -63,7 +57,9 @@ pub(super) fn arb_value_from_abi_type(abi_type: &AbiType) -> SBoxedStrategy { let fields: Vec> = fields .iter() - .map(|(name, typ)| (Just(name.clone()), arb_value_from_abi_type(typ)).sboxed()) + .map(|(name, typ)| { + (Just(name.clone()), arb_value_from_abi_type(typ, dictionary.clone())).sboxed() + }) .collect(); fields @@ -75,17 +71,23 @@ pub(super) fn arb_value_from_abi_type(abi_type: &AbiType) -> SBoxedStrategy { - let fields: Vec<_> = fields.iter().map(arb_value_from_abi_type).collect(); + let fields: Vec<_> = + fields.iter().map(|typ| arb_value_from_abi_type(typ, dictionary.clone())).collect(); fields.prop_map(InputValue::Vec).sboxed() } } } -pub(super) fn arb_input_map(abi: &Abi) -> BoxedStrategy { +pub(super) fn arb_input_map( + abi: &Abi, + dictionary: HashSet, +) -> BoxedStrategy { let values: Vec<_> = abi .parameters .iter() - .map(|param| (Just(param.name.clone()), arb_value_from_abi_type(¶m.typ))) + .map(|param| { + (Just(param.name.clone()), arb_value_from_abi_type(¶m.typ, dictionary.clone())) + }) .collect(); values diff --git a/tooling/fuzzer/src/strategies/uint.rs b/tooling/fuzzer/src/strategies/uint.rs index 5021e832b97..94610dbc829 100644 --- a/tooling/fuzzer/src/strategies/uint.rs +++ b/tooling/fuzzer/src/strategies/uint.rs @@ -1,3 +1,6 @@ +use std::collections::HashSet; + +use acvm::{AcirField, FieldElement}; use proptest::{ strategy::{NewTree, Strategy}, test_runner::TestRunner, @@ -13,9 +16,12 @@ use rand::Rng; pub struct UintStrategy { /// Bit size of uint (e.g. 128) bits: usize, - + /// A set of fixtures to be generated + fixtures: Vec, /// The weight for edge cases (+/- 3 around 0 and max possible value) edge_weight: usize, + /// The weight for fixtures + fixtures_weight: usize, /// The weight for purely random values random_weight: usize, } @@ -24,8 +30,15 @@ impl UintStrategy { /// Create a new strategy. /// # Arguments /// * `bits` - Size of uint in bits - pub fn new(bits: usize) -> Self { - Self { bits, edge_weight: 10usize, random_weight: 50usize } + /// * `fixtures` - Set of `FieldElements` representing values which the fuzzer weight towards testing. + pub fn new(bits: usize, fixtures: HashSet) -> Self { + Self { + bits, + fixtures: fixtures.into_iter().collect(), + edge_weight: 10usize, + fixtures_weight: 40usize, + random_weight: 50usize, + } } fn generate_edge_tree(&self, runner: &mut TestRunner) -> NewTree { @@ -37,6 +50,22 @@ impl UintStrategy { Ok(proptest::num::u128::BinarySearch::new(start)) } + fn generate_fixtures_tree(&self, runner: &mut TestRunner) -> NewTree { + // generate random cases if there's no fixtures + if self.fixtures.is_empty() { + return self.generate_random_tree(runner); + } + + // Generate value tree from fixture. + let fixture = &self.fixtures[runner.rng().gen_range(0..self.fixtures.len())]; + if fixture.num_bits() <= self.bits as u32 { + return Ok(proptest::num::u128::BinarySearch::new(fixture.to_u128())); + } + + // If fixture is not a valid type, generate random value. + self.generate_random_tree(runner) + } + fn generate_random_tree(&self, runner: &mut TestRunner) -> NewTree { let rng = runner.rng(); let start = rng.gen_range(0..=self.type_max()); @@ -57,11 +86,12 @@ impl Strategy for UintStrategy { type Tree = proptest::num::u128::BinarySearch; type Value = u128; fn new_tree(&self, runner: &mut TestRunner) -> NewTree { - let total_weight = self.random_weight + self.edge_weight; + let total_weight = self.random_weight + self.fixtures_weight + self.edge_weight; let bias = runner.rng().gen_range(0..total_weight); - // randomly select one of 2 strategies + // randomly select one of 3 strategies match bias { x if x < self.edge_weight => self.generate_edge_tree(runner), + x if x < self.edge_weight + self.fixtures_weight => self.generate_fixtures_tree(runner), _ => self.generate_random_tree(runner), } } diff --git a/tooling/lsp/src/lib.rs b/tooling/lsp/src/lib.rs index 304a2d34e47..b62f97a4918 100644 --- a/tooling/lsp/src/lib.rs +++ b/tooling/lsp/src/lib.rs @@ -20,7 +20,10 @@ use async_lsp::{ }; use fm::{codespan_files as files, FileManager}; use fxhash::FxHashSet; -use lsp_types::CodeLens; +use lsp_types::{ + request::{PrepareRenameRequest, References, Rename}, + CodeLens, +}; use nargo::{ package::{Package, PackageType}, parse_all, @@ -43,7 +46,8 @@ use notifications::{ }; use requests::{ on_code_lens_request, on_formatting, on_goto_declaration_request, on_goto_definition_request, - on_goto_type_definition_request, on_initialize, on_profile_run_request, on_shutdown, + on_goto_type_definition_request, on_initialize, on_prepare_rename_request, + on_profile_run_request, on_references_request, on_rename_request, on_shutdown, on_test_run_request, on_tests_request, }; use serde_json::Value as JsonValue; @@ -55,6 +59,9 @@ mod requests; mod solver; mod types; +#[cfg(test)] +mod test_utils; + use solver::WrapperSolver; use types::{notification, request, NargoTest, NargoTestId, Position, Range, Url}; @@ -119,6 +126,9 @@ impl NargoLspService { .request::(on_goto_definition_request) .request::(on_goto_declaration_request) .request::(on_goto_type_definition_request) + .request::(on_references_request) + .request::(on_prepare_rename_request) + .request::(on_rename_request) .notification::(on_initialized) .notification::(on_did_change_configuration) .notification::(on_did_open_text_document) @@ -258,6 +268,16 @@ pub(crate) fn resolve_workspace_for_source_path(file_path: &Path) -> Result( + file_manager: &'file_manager FileManager, + parsed_files: &'parsed_files ParsedFiles, + package: &Package, +) -> (Context<'file_manager, 'parsed_files>, CrateId) { + let (mut context, crate_id) = nargo::prepare_package(file_manager, parsed_files, package); + context.track_references(); + (context, crate_id) +} + /// Prepares a package from a source string /// This is useful for situations when we don't need dependencies /// and just need to operate on single file. @@ -275,6 +295,8 @@ fn prepare_source(source: String, state: &mut LspState) -> (Context<'static, 'st let parsed_files = parse_diff(&file_manager, state); let mut context = Context::new(file_manager, parsed_files); + context.track_references(); + let root_crate_id = prepare_crate(&mut context, file_name); (context, root_crate_id) @@ -351,7 +373,8 @@ fn prepare_package_from_source_string() { let mut state = LspState::new(&client, acvm::blackbox_solver::StubbedBlackBoxSolver); let (mut context, crate_id) = crate::prepare_source(source.to_string(), &mut state); - let _check_result = noirc_driver::check_crate(&mut context, crate_id, false, false, false); + let _check_result = + noirc_driver::check_crate(&mut context, crate_id, false, false, false, None); let main_func_id = context.get_main_function(&crate_id); assert!(main_func_id.is_some()); } diff --git a/tooling/lsp/src/notifications/mod.rs b/tooling/lsp/src/notifications/mod.rs index 3856bdc79e9..46a7b1cf866 100644 --- a/tooling/lsp/src/notifications/mod.rs +++ b/tooling/lsp/src/notifications/mod.rs @@ -1,7 +1,7 @@ use std::ops::ControlFlow; use async_lsp::{ErrorCode, LanguageClient, ResponseError}; -use nargo::{insert_all_files_for_workspace_into_file_manager, prepare_package}; +use nargo::insert_all_files_for_workspace_into_file_manager; use noirc_driver::{check_crate, file_manager_with_stdlib}; use noirc_errors::{DiagnosticKind, FileDiagnostic}; @@ -56,7 +56,7 @@ pub(super) fn on_did_change_text_document( state.input_files.insert(params.text_document.uri.to_string(), text.clone()); let (mut context, crate_id) = prepare_source(text, state); - let _ = check_crate(&mut context, crate_id, false, false, false); + let _ = check_crate(&mut context, crate_id, false, false, false, None); let workspace = match resolve_workspace_for_source_path( params.text_document.uri.to_file_path().unwrap().as_path(), @@ -137,12 +137,13 @@ fn process_noir_document( .into_iter() .flat_map(|package| -> Vec { let (mut context, crate_id) = - prepare_package(&workspace_file_manager, &parsed_files, package); + crate::prepare_package(&workspace_file_manager, &parsed_files, package); - let file_diagnostics = match check_crate(&mut context, crate_id, false, false, false) { - Ok(((), warnings)) => warnings, - Err(errors_and_warnings) => errors_and_warnings, - }; + let file_diagnostics = + match check_crate(&mut context, crate_id, false, false, false, None) { + Ok(((), warnings)) => warnings, + Err(errors_and_warnings) => errors_and_warnings, + }; let package_root_dir: String = package.root_dir.as_os_str().to_string_lossy().into(); @@ -190,6 +191,8 @@ fn process_noir_document( let severity = match diagnostic.kind { DiagnosticKind::Error => DiagnosticSeverity::ERROR, DiagnosticKind::Warning => DiagnosticSeverity::WARNING, + DiagnosticKind::Info => DiagnosticSeverity::INFORMATION, + DiagnosticKind::Bug => DiagnosticSeverity::WARNING, }; Some(Diagnostic { range, diff --git a/tooling/lsp/src/requests/code_lens_request.rs b/tooling/lsp/src/requests/code_lens_request.rs index 744bddedd9d..0b8803edc6f 100644 --- a/tooling/lsp/src/requests/code_lens_request.rs +++ b/tooling/lsp/src/requests/code_lens_request.rs @@ -67,7 +67,7 @@ fn on_code_lens_request_inner( let (mut context, crate_id) = prepare_source(source_string, state); // We ignore the warnings and errors produced by compilation for producing code lenses // because we can still get the test functions even if compilation fails - let _ = check_crate(&mut context, crate_id, false, false, false); + let _ = check_crate(&mut context, crate_id, false, false, false, None); let collected_lenses = collect_lenses_for_package(&context, crate_id, &workspace, package, None); diff --git a/tooling/lsp/src/requests/goto_declaration.rs b/tooling/lsp/src/requests/goto_declaration.rs index 4ffe6abf88a..627cd8d203e 100644 --- a/tooling/lsp/src/requests/goto_declaration.rs +++ b/tooling/lsp/src/requests/goto_declaration.rs @@ -2,16 +2,11 @@ use std::future::{self, Future}; use crate::types::GotoDeclarationResult; use crate::LspState; -use crate::{parse_diff, resolve_workspace_for_source_path}; -use async_lsp::{ErrorCode, ResponseError}; +use async_lsp::ResponseError; -use fm::PathString; use lsp_types::request::{GotoDeclarationParams, GotoDeclarationResponse}; -use nargo::insert_all_files_for_workspace_into_file_manager; -use noirc_driver::file_manager_with_stdlib; - -use super::{position_to_location, to_lsp_location}; +use super::{process_request, to_lsp_location}; pub(crate) fn on_goto_declaration_request( state: &mut LspState, @@ -25,42 +20,12 @@ fn on_goto_definition_inner( state: &mut LspState, params: GotoDeclarationParams, ) -> Result { - let file_path = - params.text_document_position_params.text_document.uri.to_file_path().map_err(|_| { - ResponseError::new(ErrorCode::REQUEST_FAILED, "URI is not a valid file path") - })?; - - let workspace = resolve_workspace_for_source_path(file_path.as_path()).unwrap(); - let package = workspace.members.first().unwrap(); - - let mut workspace_file_manager = file_manager_with_stdlib(&workspace.root_dir); - insert_all_files_for_workspace_into_file_manager(&workspace, &mut workspace_file_manager); - let parsed_files = parse_diff(&workspace_file_manager, state); - - let (mut context, crate_id) = - nargo::prepare_package(&workspace_file_manager, &parsed_files, package); - - let package_root_path = package.root_dir.as_os_str().to_string_lossy().into_owned(); - let interner = if let Some(def_interner) = state.cached_definitions.get(&package_root_path) { - def_interner - } else { - // We ignore the warnings and errors produced by compilation while resolving the definition - let _ = noirc_driver::check_crate(&mut context, crate_id, false, false, false); - &context.def_interner - }; - - let files = workspace_file_manager.as_file_map(); - let file_path = PathString::from(file_path); - let search_for_location = - position_to_location(files, &file_path, ¶ms.text_document_position_params.position)?; - - let goto_declaration_response = - interner.get_declaration_location_from(search_for_location).and_then(|found_location| { + process_request(state, params.text_document_position_params, |location, interner, files| { + interner.get_declaration_location_from(location).and_then(|found_location| { let file_id = found_location.file; let definition_position = to_lsp_location(files, file_id, found_location.span)?; let response = GotoDeclarationResponse::from(definition_position).to_owned(); Some(response) - }); - - Ok(goto_declaration_response) + }) + }) } diff --git a/tooling/lsp/src/requests/goto_definition.rs b/tooling/lsp/src/requests/goto_definition.rs index 3a92e28cc11..3713e8b646a 100644 --- a/tooling/lsp/src/requests/goto_definition.rs +++ b/tooling/lsp/src/requests/goto_definition.rs @@ -1,16 +1,12 @@ use std::future::{self, Future}; -use crate::{parse_diff, resolve_workspace_for_source_path}; use crate::{types::GotoDefinitionResult, LspState}; -use async_lsp::{ErrorCode, ResponseError}; +use async_lsp::ResponseError; -use fm::PathString; use lsp_types::request::GotoTypeDefinitionParams; use lsp_types::{GotoDefinitionParams, GotoDefinitionResponse}; -use nargo::insert_all_files_for_workspace_into_file_manager; -use noirc_driver::file_manager_with_stdlib; -use super::{position_to_location, to_lsp_location}; +use super::{process_request, to_lsp_location}; pub(crate) fn on_goto_definition_request( state: &mut LspState, @@ -33,93 +29,81 @@ fn on_goto_definition_inner( params: GotoDefinitionParams, return_type_location_instead: bool, ) -> Result { - let file_path = - params.text_document_position_params.text_document.uri.to_file_path().map_err(|_| { - ResponseError::new(ErrorCode::REQUEST_FAILED, "URI is not a valid file path") - })?; - - let workspace = resolve_workspace_for_source_path(file_path.as_path()).unwrap(); - let package = workspace.members.first().unwrap(); - - let mut workspace_file_manager = file_manager_with_stdlib(&workspace.root_dir); - insert_all_files_for_workspace_into_file_manager(&workspace, &mut workspace_file_manager); - let parsed_files = parse_diff(&workspace_file_manager, state); - - let (mut context, crate_id) = - nargo::prepare_package(&workspace_file_manager, &parsed_files, package); - - let package_root_path = package.root_dir.as_os_str().to_string_lossy().into_owned(); - let interner = if let Some(def_interner) = state.cached_definitions.get(&package_root_path) { - def_interner - } else { - // We ignore the warnings and errors produced by compilation while resolving the definition - let _ = noirc_driver::check_crate(&mut context, crate_id, false, false, false); - &context.def_interner - }; - - let files = workspace_file_manager.as_file_map(); - let file_path = PathString::from(file_path); - let search_for_location = - position_to_location(files, &file_path, ¶ms.text_document_position_params.position)?; - - let goto_definition_response = interner - .get_definition_location_from(search_for_location, return_type_location_instead) - .and_then(|found_location| { - let file_id = found_location.file; - let definition_position = to_lsp_location(files, file_id, found_location.span)?; - let response = GotoDefinitionResponse::from(definition_position).to_owned(); - Some(response) - }); - - Ok(goto_definition_response) + process_request(state, params.text_document_position_params, |location, interner, files| { + interner.get_definition_location_from(location, return_type_location_instead).and_then( + |found_location| { + let file_id = found_location.file; + let definition_position = to_lsp_location(files, file_id, found_location.span)?; + let response = GotoDefinitionResponse::from(definition_position).to_owned(); + Some(response) + }, + ) + }) } #[cfg(test)] mod goto_definition_tests { + use std::panic; - use acvm::blackbox_solver::StubbedBlackBoxSolver; - use async_lsp::ClientSocket; - use lsp_types::{Position, Url}; + use crate::test_utils::{self, search_in_file}; + use lsp_types::{Position, Range}; use tokio::test; use super::*; - #[test] - async fn test_on_goto_definition() { - let client = ClientSocket::new_closed(); - let mut state = LspState::new(&client, StubbedBlackBoxSolver); - - let root_path = std::env::current_dir() - .unwrap() - .join("../../test_programs/execution_success/7_function") - .canonicalize() - .expect("Could not resolve root path"); - let noir_text_document = Url::from_file_path(root_path.join("src/main.nr").as_path()) - .expect("Could not convert text document path to URI"); - let root_uri = Some( - Url::from_file_path(root_path.as_path()).expect("Could not convert root path to URI"), - ); - - #[allow(deprecated)] - let initialize_params = lsp_types::InitializeParams { - process_id: Default::default(), - root_path: None, - root_uri, - initialization_options: None, - capabilities: Default::default(), - trace: Some(lsp_types::TraceValue::Verbose), - workspace_folders: None, - client_info: None, - locale: None, - }; - let _initialize_response = crate::requests::on_initialize(&mut state, initialize_params) - .await - .expect("Could not initialize LSP server"); + async fn expect_goto_for_all_references(directory: &str, name: &str, definition_index: usize) { + let (mut state, noir_text_document) = test_utils::init_lsp_server(directory).await; + + let ranges = search_in_file(noir_text_document.path(), name); + let expected_range = ranges[definition_index]; + + for (index, range) in ranges.iter().enumerate() { + // Ideally "go to" at the definition should return the same location, but this isn't currently + // working. But it's also not that important, so we'll keep it for later. + if index == definition_index { + continue; + } + + let params = GotoDefinitionParams { + text_document_position_params: lsp_types::TextDocumentPositionParams { + text_document: lsp_types::TextDocumentIdentifier { + uri: noir_text_document.clone(), + }, + position: range.start, + }, + work_done_progress_params: Default::default(), + partial_result_params: Default::default(), + }; + + let response = on_goto_definition_request(&mut state, params) + .await + .expect("Could execute on_goto_definition_request") + .unwrap_or_else(|| { + panic!("Didn't get a goto definition response for index {index}") + }); + + if let GotoDefinitionResponse::Scalar(location) = response { + assert_eq!(location.range, expected_range); + } else { + panic!("Expected a scalar response"); + }; + } + } + + async fn expect_goto( + directory: &str, + position: Position, + expected_file: &str, + expected_range: Range, + ) { + let (mut state, noir_text_document) = test_utils::init_lsp_server(directory).await; let params = GotoDefinitionParams { text_document_position_params: lsp_types::TextDocumentPositionParams { - text_document: lsp_types::TextDocumentIdentifier { uri: noir_text_document }, - position: Position { line: 95, character: 5 }, + text_document: lsp_types::TextDocumentIdentifier { + uri: noir_text_document.clone(), + }, + position, }, work_done_progress_params: Default::default(), partial_result_params: Default::default(), @@ -127,8 +111,94 @@ mod goto_definition_tests { let response = on_goto_definition_request(&mut state, params) .await - .expect("Could execute on_goto_definition_request"); + .expect("Could execute on_goto_definition_request") + .unwrap_or_else(|| panic!("Didn't get a goto definition response")); + + if let GotoDefinitionResponse::Scalar(location) = response { + assert!(location.uri.to_string().ends_with(expected_file)); + assert_eq!(location.range, expected_range); + } else { + panic!("Expected a scalar response"); + }; + } + + #[test] + async fn goto_from_function_location_to_declaration() { + expect_goto_for_all_references("go_to_definition", "another_function", 0).await; + } + + #[test] + async fn goto_from_use_as() { + expect_goto( + "go_to_definition", + Position { line: 7, character: 29 }, // The word after `as`, + "src/main.nr", + Range { + start: Position { line: 1, character: 11 }, + end: Position { line: 1, character: 27 }, + }, + ) + .await; + } + + #[test] + async fn goto_module_from_call_path() { + expect_goto( + "go_to_definition", + Position { line: 17, character: 4 }, // "bar" in "bar::baz()" + "src/bar.nr", + Range { + start: Position { line: 0, character: 0 }, + end: Position { line: 0, character: 0 }, + }, + ) + .await; + } + + #[test] + async fn goto_inline_module_from_call_path() { + expect_goto( + "go_to_definition", + Position { line: 18, character: 9 }, // "inline" in "bar::inline::qux()" + "src/bar.nr", + Range { + start: Position { line: 2, character: 4 }, + end: Position { line: 2, character: 10 }, + }, + ) + .await; + } + + #[test] + async fn goto_module_from_use_path() { + expect_goto( + "go_to_definition", + Position { line: 6, character: 4 }, // "foo" in "use foo::another_function;" + "src/main.nr", + Range { + start: Position { line: 0, character: 4 }, + end: Position { line: 0, character: 7 }, + }, + ) + .await; + } + + #[test] + async fn goto_module_from_mod() { + expect_goto( + "go_to_definition", + Position { line: 9, character: 4 }, // "bar" in "mod bar;" + "src/bar.nr", + Range { + start: Position { line: 0, character: 0 }, + end: Position { line: 0, character: 0 }, + }, + ) + .await; + } - assert!(&response.is_some()); + #[test] + async fn goto_for_local_variable() { + expect_goto_for_all_references("local_variable", "some_var", 0).await; } } diff --git a/tooling/lsp/src/requests/mod.rs b/tooling/lsp/src/requests/mod.rs index 769e9ba17ce..48299ff7459 100644 --- a/tooling/lsp/src/requests/mod.rs +++ b/tooling/lsp/src/requests/mod.rs @@ -1,13 +1,20 @@ use std::future::Future; -use crate::types::{CodeLensOptions, InitializeParams}; +use crate::{ + parse_diff, resolve_workspace_for_source_path, + types::{CodeLensOptions, InitializeParams}, +}; use async_lsp::{ErrorCode, ResponseError}; use fm::{codespan_files::Error, FileMap, PathString}; use lsp_types::{ - DeclarationCapability, Location, Position, TextDocumentSyncCapability, TextDocumentSyncKind, - TypeDefinitionProviderCapability, Url, + DeclarationCapability, Location, Position, TextDocumentPositionParams, + TextDocumentSyncCapability, TextDocumentSyncKind, TypeDefinitionProviderCapability, Url, + WorkDoneProgressOptions, }; +use nargo::insert_all_files_for_workspace_into_file_manager; use nargo_fmt::Config; +use noirc_driver::file_manager_with_stdlib; +use noirc_frontend::macros_api::NodeInterner; use serde::{Deserialize, Serialize}; use crate::{ @@ -29,6 +36,8 @@ mod code_lens_request; mod goto_declaration; mod goto_definition; mod profile_run; +mod references; +mod rename; mod test_run; mod tests; @@ -36,7 +45,8 @@ pub(crate) use { code_lens_request::collect_lenses_for_package, code_lens_request::on_code_lens_request, goto_declaration::on_goto_declaration_request, goto_definition::on_goto_definition_request, goto_definition::on_goto_type_definition_request, profile_run::on_profile_run_request, - test_run::on_test_run_request, tests::on_tests_request, + references::on_references_request, rename::on_prepare_rename_request, + rename::on_rename_request, test_run::on_test_run_request, tests::on_tests_request, }; /// LSP client will send initialization request after the server has started. @@ -106,6 +116,17 @@ pub(crate) fn on_initialize( definition_provider: Some(lsp_types::OneOf::Left(true)), declaration_provider: Some(DeclarationCapability::Simple(true)), type_definition_provider: Some(TypeDefinitionProviderCapability::Simple(true)), + rename_provider: Some(lsp_types::OneOf::Right(lsp_types::RenameOptions { + prepare_provider: Some(true), + work_done_progress_options: WorkDoneProgressOptions { + work_done_progress: None, + }, + })), + references_provider: Some(lsp_types::OneOf::Right(lsp_types::ReferencesOptions { + work_done_progress_options: WorkDoneProgressOptions { + work_done_progress: None, + }, + })), }, server_info: None, }) @@ -243,6 +264,51 @@ pub(crate) fn on_shutdown( async { Ok(()) } } +pub(crate) fn process_request( + state: &mut LspState, + text_document_position_params: TextDocumentPositionParams, + callback: F, +) -> Result +where + F: FnOnce(noirc_errors::Location, &NodeInterner, &FileMap) -> T, +{ + let file_path = + text_document_position_params.text_document.uri.to_file_path().map_err(|_| { + ResponseError::new(ErrorCode::REQUEST_FAILED, "URI is not a valid file path") + })?; + + let workspace = resolve_workspace_for_source_path(file_path.as_path()).unwrap(); + let package = workspace.members.first().unwrap(); + + let package_root_path: String = package.root_dir.as_os_str().to_string_lossy().into(); + + let mut workspace_file_manager = file_manager_with_stdlib(&workspace.root_dir); + insert_all_files_for_workspace_into_file_manager(&workspace, &mut workspace_file_manager); + let parsed_files = parse_diff(&workspace_file_manager, state); + + let (mut context, crate_id) = + crate::prepare_package(&workspace_file_manager, &parsed_files, package); + + let interner; + if let Some(def_interner) = state.cached_definitions.get(&package_root_path) { + interner = def_interner; + } else { + // We ignore the warnings and errors produced by compilation while resolving the definition + let _ = noirc_driver::check_crate(&mut context, crate_id, false, false, false, None); + interner = &context.def_interner; + } + + let files = context.file_manager.as_file_map(); + + let location = position_to_location( + files, + &PathString::from(file_path), + &text_document_position_params.position, + )?; + + Ok(callback(location, interner, files)) +} + #[cfg(test)] mod initialization { use acvm::blackbox_solver::StubbedBlackBoxSolver; diff --git a/tooling/lsp/src/requests/references.rs b/tooling/lsp/src/requests/references.rs new file mode 100644 index 00000000000..f8c23632936 --- /dev/null +++ b/tooling/lsp/src/requests/references.rs @@ -0,0 +1,94 @@ +use std::future::{self, Future}; + +use async_lsp::ResponseError; +use lsp_types::{Location, ReferenceParams}; + +use crate::LspState; + +use super::{process_request, to_lsp_location}; + +pub(crate) fn on_references_request( + state: &mut LspState, + params: ReferenceParams, +) -> impl Future>, ResponseError>> { + let result = + process_request(state, params.text_document_position, |location, interner, files| { + interner.find_all_references(location, params.context.include_declaration, true).map( + |locations| { + locations + .iter() + .filter_map(|location| to_lsp_location(files, location.file, location.span)) + .collect() + }, + ) + }); + future::ready(result) +} + +#[cfg(test)] +mod references_tests { + use super::*; + use crate::test_utils::{self, search_in_file}; + use lsp_types::{ + PartialResultParams, ReferenceContext, TextDocumentPositionParams, WorkDoneProgressParams, + }; + use tokio::test; + + async fn check_references_succeeds( + directory: &str, + name: &str, + declaration_index: usize, + include_declaration: bool, + ) { + let (mut state, noir_text_document) = test_utils::init_lsp_server(directory).await; + + // First we find out all of the occurrences of `name` in the main.nr file. + // Note that this only works if that name doesn't show up in other places where we don't + // expect a rename, but we craft our tests to avoid that. + let ranges = search_in_file(noir_text_document.path(), name); + + // Test getting references works on any instance of the symbol. + for target_range in &ranges { + let target_position = target_range.start; + + let params = ReferenceParams { + text_document_position: TextDocumentPositionParams { + text_document: lsp_types::TextDocumentIdentifier { + uri: noir_text_document.clone(), + }, + position: target_position, + }, + work_done_progress_params: WorkDoneProgressParams { work_done_token: None }, + partial_result_params: PartialResultParams { partial_result_token: None }, + context: ReferenceContext { include_declaration }, + }; + + let locations = on_references_request(&mut state, params) + .await + .expect("Could not execute on_references_request") + .unwrap(); + + let mut references_ranges: Vec<_> = + locations.iter().map(|location| location.range).collect(); + references_ranges.sort_by_key(|range| range.start.line); + + if include_declaration { + assert_eq!(ranges, references_ranges); + } else { + let mut ranges_without_declaration = ranges.clone(); + ranges_without_declaration.remove(declaration_index); + assert_eq!(ranges_without_declaration, references_ranges); + } + } + } + + #[test] + async fn test_on_references_request_including_declaration() { + check_references_succeeds("rename_function", "another_function", 0, true).await; + } + + #[test] + async fn test_on_references_request_without_including_declaration() { + check_references_succeeds("rename_function", "another_function", 0, false).await; + } +} diff --git a/tooling/lsp/src/requests/rename.rs b/tooling/lsp/src/requests/rename.rs new file mode 100644 index 00000000000..906a5cbcaab --- /dev/null +++ b/tooling/lsp/src/requests/rename.rs @@ -0,0 +1,207 @@ +use std::{ + collections::HashMap, + future::{self, Future}, +}; + +use async_lsp::ResponseError; +use lsp_types::{ + PrepareRenameResponse, RenameParams, TextDocumentPositionParams, TextEdit, Url, WorkspaceEdit, +}; +use noirc_frontend::node_interner::ReferenceId; + +use crate::LspState; + +use super::{process_request, to_lsp_location}; + +pub(crate) fn on_prepare_rename_request( + state: &mut LspState, + params: TextDocumentPositionParams, +) -> impl Future, ResponseError>> { + let result = process_request(state, params, |location, interner, _| { + let reference_id = interner.reference_at_location(location); + let rename_possible = match reference_id { + // Rename shouldn't be possible when triggered on top of "Self" + Some(ReferenceId::Reference(_, true /* is self type name */)) => false, + Some(_) => true, + None => false, + }; + Some(PrepareRenameResponse::DefaultBehavior { default_behavior: rename_possible }) + }); + future::ready(result) +} + +pub(crate) fn on_rename_request( + state: &mut LspState, + params: RenameParams, +) -> impl Future, ResponseError>> { + let result = + process_request(state, params.text_document_position, |location, interner, files| { + let rename_changes = + interner.find_all_references(location, true, false).map(|locations| { + let rs = locations.iter().fold( + HashMap::new(), + |mut acc: HashMap>, location| { + let file_id = location.file; + let span = location.span; + + let Some(lsp_location) = to_lsp_location(files, file_id, span) else { + return acc; + }; + + let edit = TextEdit { + range: lsp_location.range, + new_text: params.new_name.clone(), + }; + + acc.entry(lsp_location.uri).or_default().push(edit); + + acc + }, + ); + rs + }); + + let response = WorkspaceEdit { + changes: rename_changes, + document_changes: None, + change_annotations: None, + }; + + Some(response) + }); + future::ready(result) +} + +#[cfg(test)] +mod rename_tests { + use super::*; + use crate::test_utils::{self, search_in_file}; + use lsp_types::{Range, WorkDoneProgressParams}; + use tokio::test; + + async fn check_rename_succeeds(directory: &str, name: &str) { + let (mut state, noir_text_document) = test_utils::init_lsp_server(directory).await; + + // First we find out all of the occurrences of `name` in the main.nr file. + // Note that this only works if that name doesn't show up in other places where we don't + // expect a rename, but we craft our tests to avoid that. + let ranges = search_in_file(noir_text_document.path(), name); + + // Test renaming works on any instance of the symbol. + for target_range in &ranges { + let target_position = target_range.start; + + let params = RenameParams { + text_document_position: TextDocumentPositionParams { + text_document: lsp_types::TextDocumentIdentifier { + uri: noir_text_document.clone(), + }, + position: target_position, + }, + new_name: "renamed_function".to_string(), + work_done_progress_params: WorkDoneProgressParams { work_done_token: None }, + }; + + let response = on_rename_request(&mut state, params) + .await + .expect("Could not execute on_prepare_rename_request") + .unwrap(); + + let changes = response.changes.expect("Expected to find rename changes"); + let mut changes: Vec = + changes.values().flatten().map(|edit| edit.range).collect(); + changes.sort_by_key(|range| (range.start.line, range.start.character)); + if changes != ranges { + let extra_in_changes: Vec<_> = + changes.iter().filter(|range| !ranges.contains(range)).collect(); + let extra_in_ranges: Vec<_> = + ranges.iter().filter(|range| !changes.contains(range)).collect(); + panic!("Rename locations did not match.\nThese renames were not found: {:?}\nThese renames should not have been found: {:?}", extra_in_ranges, extra_in_changes); + } + assert_eq!(changes, ranges); + } + } + + #[test] + async fn test_on_prepare_rename_request_cannot_be_applied_if_there_are_no_matches() { + let (mut state, noir_text_document) = test_utils::init_lsp_server("rename_function").await; + + let params = TextDocumentPositionParams { + text_document: lsp_types::TextDocumentIdentifier { uri: noir_text_document }, + position: lsp_types::Position { line: 0, character: 0 }, // This is at the "f" of an "fn" keyword + }; + + let response = on_prepare_rename_request(&mut state, params) + .await + .expect("Could not execute on_prepare_rename_request"); + + assert_eq!( + response, + Some(PrepareRenameResponse::DefaultBehavior { default_behavior: false }) + ); + } + + #[test] + async fn test_on_prepare_rename_request_cannot_be_applied_on_self_type_name() { + let (mut state, noir_text_document) = test_utils::init_lsp_server("rename_struct").await; + + let params = TextDocumentPositionParams { + text_document: lsp_types::TextDocumentIdentifier { uri: noir_text_document }, + position: lsp_types::Position { line: 11, character: 24 }, // At "Self" + }; + + let response = on_prepare_rename_request(&mut state, params) + .await + .expect("Could not execute on_prepare_rename_request"); + + assert_eq!( + response, + Some(PrepareRenameResponse::DefaultBehavior { default_behavior: false }) + ); + } + + #[test] + async fn test_rename_function() { + check_rename_succeeds("rename_function", "another_function").await; + } + + #[test] + async fn test_rename_qualified_function() { + check_rename_succeeds("rename_qualified_function", "bar").await; + } + + #[test] + async fn test_rename_function_in_use_statement() { + check_rename_succeeds("rename_function_use", "some_function").await; + } + + #[test] + async fn test_rename_struct() { + check_rename_succeeds("rename_struct", "Foo").await; + } + + #[test] + async fn test_rename_trait() { + check_rename_succeeds("rename_trait", "Foo").await; + } + + #[test] + async fn test_rename_type_alias() { + check_rename_succeeds("rename_type_alias", "Bar").await; + } + + #[test] + async fn test_rename_global() { + check_rename_succeeds("rename_global", "FOO").await; + } + + #[test] + async fn test_rename_local_variable() { + check_rename_succeeds("local_variable", "some_var").await; + } + + #[test] + async fn test_rename_struct_member() { + check_rename_succeeds("struct_member", "some_member").await; + } +} diff --git a/tooling/lsp/src/requests/test_run.rs b/tooling/lsp/src/requests/test_run.rs index 83b05ba06a2..b4b9b62d6b6 100644 --- a/tooling/lsp/src/requests/test_run.rs +++ b/tooling/lsp/src/requests/test_run.rs @@ -4,7 +4,6 @@ use async_lsp::{ErrorCode, ResponseError}; use nargo::{ insert_all_files_for_workspace_into_file_manager, ops::{run_test, TestStatus}, - prepare_package, }; use nargo_toml::{find_package_manifest, resolve_workspace_from_toml, PackageSelection}; use noirc_driver::{ @@ -59,8 +58,8 @@ fn on_test_run_request_inner( match workspace.into_iter().next() { Some(package) => { let (mut context, crate_id) = - prepare_package(&workspace_file_manager, &parsed_files, package); - if check_crate(&mut context, crate_id, false, false, false).is_err() { + crate::prepare_package(&workspace_file_manager, &parsed_files, package); + if check_crate(&mut context, crate_id, false, false, false, None).is_err() { let result = NargoTestRunResult { id: params.id.clone(), result: "error".to_string(), diff --git a/tooling/lsp/src/requests/tests.rs b/tooling/lsp/src/requests/tests.rs index cdf4ad338c4..fb8b845df04 100644 --- a/tooling/lsp/src/requests/tests.rs +++ b/tooling/lsp/src/requests/tests.rs @@ -2,7 +2,7 @@ use std::future::{self, Future}; use async_lsp::{ErrorCode, LanguageClient, ResponseError}; use lsp_types::{LogMessageParams, MessageType}; -use nargo::{insert_all_files_for_workspace_into_file_manager, prepare_package}; +use nargo::insert_all_files_for_workspace_into_file_manager; use nargo_toml::{find_package_manifest, resolve_workspace_from_toml, PackageSelection}; use noirc_driver::{check_crate, file_manager_with_stdlib, NOIR_ARTIFACT_VERSION_STRING}; @@ -58,10 +58,10 @@ fn on_tests_request_inner( .into_iter() .filter_map(|package| { let (mut context, crate_id) = - prepare_package(&workspace_file_manager, &parsed_files, package); + crate::prepare_package(&workspace_file_manager, &parsed_files, package); // We ignore the warnings and errors produced by compilation for producing tests // because we can still get the test functions even if compilation fails - let _ = check_crate(&mut context, crate_id, false, false, false); + let _ = check_crate(&mut context, crate_id, false, false, false, None); // We don't add test headings for a package if it contains no `#[test]` functions get_package_tests_in_crate(&context, &crate_id, &package.name) diff --git a/tooling/lsp/src/solver.rs b/tooling/lsp/src/solver.rs index 0fcac73b905..9d1185e3a79 100644 --- a/tooling/lsp/src/solver.rs +++ b/tooling/lsp/src/solver.rs @@ -24,6 +24,14 @@ impl BlackBoxFunctionSolver for WrapperSolver { self.0.pedersen_commitment(inputs, domain_separator) } + fn pedersen_hash( + &self, + inputs: &[acvm::FieldElement], + domain_separator: u32, + ) -> Result { + self.0.pedersen_hash(inputs, domain_separator) + } + fn multi_scalar_mul( &self, points: &[acvm::FieldElement], @@ -36,14 +44,6 @@ impl BlackBoxFunctionSolver for WrapperSolver { self.0.multi_scalar_mul(points, scalars_lo, scalars_hi) } - fn pedersen_hash( - &self, - inputs: &[acvm::FieldElement], - domain_separator: u32, - ) -> Result { - self.0.pedersen_hash(inputs, domain_separator) - } - fn ec_add( &self, input1_x: &acvm::FieldElement, diff --git a/tooling/lsp/src/test_utils.rs b/tooling/lsp/src/test_utils.rs new file mode 100644 index 00000000000..c0505107842 --- /dev/null +++ b/tooling/lsp/src/test_utils.rs @@ -0,0 +1,60 @@ +use crate::LspState; +use acvm::blackbox_solver::StubbedBlackBoxSolver; +use async_lsp::ClientSocket; +use lsp_types::{Position, Range, Url}; + +pub(crate) async fn init_lsp_server(directory: &str) -> (LspState, Url) { + let client = ClientSocket::new_closed(); + let mut state = LspState::new(&client, StubbedBlackBoxSolver); + + let root_path = std::env::current_dir() + .unwrap() + .join("test_programs") + .join(directory) + .canonicalize() + .expect("Could not resolve root path"); + let noir_text_document = Url::from_file_path(root_path.join("src/main.nr").as_path()) + .expect("Could not convert text document path to URI"); + let root_uri = + Some(Url::from_file_path(root_path.as_path()).expect("Could not convert root path to URI")); + + #[allow(deprecated)] + let initialize_params = lsp_types::InitializeParams { + process_id: Default::default(), + root_path: None, + root_uri, + initialization_options: None, + capabilities: Default::default(), + trace: Some(lsp_types::TraceValue::Verbose), + workspace_folders: None, + client_info: None, + locale: None, + }; + + let _initialize_response = crate::requests::on_initialize(&mut state, initialize_params) + .await + .expect("Could not initialize LSP server"); + + (state, noir_text_document) +} + +/// Searches for all instances of `search_string` in file `file_name` and returns a list of their locations. +pub(crate) fn search_in_file(filename: &str, search_string: &str) -> Vec { + let file_contents = std::fs::read_to_string(filename) + .unwrap_or_else(|_| panic!("Couldn't read file {}", filename)); + let file_lines: Vec<&str> = file_contents.lines().collect(); + file_lines + .iter() + .enumerate() + .flat_map(|(line_num, line)| { + line.match_indices(search_string).map(move |(index, _)| { + let start = Position { line: line_num as u32, character: index as u32 }; + let end = Position { + line: line_num as u32, + character: (index + search_string.len()) as u32, + }; + Range { start, end } + }) + }) + .collect() +} diff --git a/tooling/lsp/src/types.rs b/tooling/lsp/src/types.rs index e3492f21346..57eb2dd3618 100644 --- a/tooling/lsp/src/types.rs +++ b/tooling/lsp/src/types.rs @@ -1,6 +1,7 @@ use fm::FileId; use lsp_types::{ - DeclarationCapability, DefinitionOptions, OneOf, TypeDefinitionProviderCapability, + DeclarationCapability, DefinitionOptions, OneOf, ReferencesOptions, RenameOptions, + TypeDefinitionProviderCapability, }; use noirc_driver::DebugFile; use noirc_errors::{debug_info::OpCodesCount, Location}; @@ -135,6 +136,14 @@ pub(crate) struct ServerCapabilities { /// The server handles and provides custom nargo messages. #[serde(skip_serializing_if = "Option::is_none")] pub(crate) nargo: Option, + + /// The server provides rename support. + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) rename_provider: Option>, + + /// The server provides references support. + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) references_provider: Option>, } #[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)] diff --git a/tooling/lsp/test_programs/go_to_definition/Nargo.toml b/tooling/lsp/test_programs/go_to_definition/Nargo.toml new file mode 100644 index 00000000000..c894a050c40 --- /dev/null +++ b/tooling/lsp/test_programs/go_to_definition/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "go_to_definition" +type = "bin" +authors = [""] + +[dependencies] diff --git a/tooling/lsp/test_programs/go_to_definition/src/bar.nr b/tooling/lsp/test_programs/go_to_definition/src/bar.nr new file mode 100644 index 00000000000..6792022dc10 --- /dev/null +++ b/tooling/lsp/test_programs/go_to_definition/src/bar.nr @@ -0,0 +1,5 @@ +pub fn baz() {} + +mod inline { + pub fn qux() {} +} diff --git a/tooling/lsp/test_programs/go_to_definition/src/main.nr b/tooling/lsp/test_programs/go_to_definition/src/main.nr new file mode 100644 index 00000000000..76a367259b5 --- /dev/null +++ b/tooling/lsp/test_programs/go_to_definition/src/main.nr @@ -0,0 +1,20 @@ +mod foo { + pub fn another_function() -> Field { + 3 + 4 + } +} + +use foo::another_function; +use foo::another_function as aliased_function; + +mod bar; + +fn some_function() -> Field { + 1 + 2 +} + +fn main() { + let _ = another_function(); + bar::baz(); + bar::inline::qux(); +} diff --git a/tooling/lsp/test_programs/local_variable/Nargo.toml b/tooling/lsp/test_programs/local_variable/Nargo.toml new file mode 100644 index 00000000000..df881fb5f4d --- /dev/null +++ b/tooling/lsp/test_programs/local_variable/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "local_variable" +type = "bin" +authors = [""] + +[dependencies] diff --git a/tooling/lsp/test_programs/local_variable/src/main.nr b/tooling/lsp/test_programs/local_variable/src/main.nr new file mode 100644 index 00000000000..e41cbed085f --- /dev/null +++ b/tooling/lsp/test_programs/local_variable/src/main.nr @@ -0,0 +1,5 @@ +fn main() { + let mut some_var = 1; + some_var = 2; + let _ = some_var; +} diff --git a/test_programs/execution_success/regression_5202/fraction/Nargo.toml b/tooling/lsp/test_programs/rename/Nargo.toml similarity index 56% rename from test_programs/execution_success/regression_5202/fraction/Nargo.toml rename to tooling/lsp/test_programs/rename/Nargo.toml index 82e929d0bc7..2d5b6415dc9 100644 --- a/test_programs/execution_success/regression_5202/fraction/Nargo.toml +++ b/tooling/lsp/test_programs/rename/Nargo.toml @@ -1,6 +1,6 @@ [package] -name = "fraction" -type = "lib" +name = "rename" +type = "bin" authors = [""] [dependencies] diff --git a/tooling/lsp/test_programs/rename/src/main.nr b/tooling/lsp/test_programs/rename/src/main.nr new file mode 100644 index 00000000000..4c28249582e --- /dev/null +++ b/tooling/lsp/test_programs/rename/src/main.nr @@ -0,0 +1,22 @@ +fn some_function() -> Field { + 1 + 2 +} + +fn another_function() -> Field { + 3 + 4 +} + +fn main() { + let _ = another_function(); + + let _ = 1; + + let _ = another_function(); +} + + +mod foo { + fn yet_another_function() -> Field { + crate::another_function() + } +} \ No newline at end of file diff --git a/tooling/lsp/test_programs/rename_function/Nargo.toml b/tooling/lsp/test_programs/rename_function/Nargo.toml new file mode 100644 index 00000000000..529fde06128 --- /dev/null +++ b/tooling/lsp/test_programs/rename_function/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "rename_function" +type = "bin" +authors = [""] + +[dependencies] diff --git a/tooling/lsp/test_programs/rename_function/src/main.nr b/tooling/lsp/test_programs/rename_function/src/main.nr new file mode 100644 index 00000000000..7a70084276e --- /dev/null +++ b/tooling/lsp/test_programs/rename_function/src/main.nr @@ -0,0 +1,27 @@ +fn some_function() -> Field { + 1 + 2 +} + +fn another_function() -> Field { + 3 + 4 +} + +fn main() { + let _ = another_function(); + + let _ = 1; + + let _ = another_function(); +} + +mod foo { + fn some_other_function() -> Field { + crate::another_function() + } +} + +use foo::some_other_function as bar; + +fn x() { + bar(); +} diff --git a/tooling/lsp/test_programs/rename_function_use/Nargo.toml b/tooling/lsp/test_programs/rename_function_use/Nargo.toml new file mode 100644 index 00000000000..e2eb25886bf --- /dev/null +++ b/tooling/lsp/test_programs/rename_function_use/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "rename_function_use" +type = "bin" +authors = [""] + +[dependencies] diff --git a/tooling/lsp/test_programs/rename_function_use/src/main.nr b/tooling/lsp/test_programs/rename_function_use/src/main.nr new file mode 100644 index 00000000000..e1b7c98ab0f --- /dev/null +++ b/tooling/lsp/test_programs/rename_function_use/src/main.nr @@ -0,0 +1,12 @@ +mod foo { + pub fn some_function() -> Field { + 1 + 2 + } +} + +use foo::some_function; + +fn main() { + let _ = some_function(); +} + diff --git a/tooling/lsp/test_programs/rename_global/Nargo.toml b/tooling/lsp/test_programs/rename_global/Nargo.toml new file mode 100644 index 00000000000..350c6fe5506 --- /dev/null +++ b/tooling/lsp/test_programs/rename_global/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "rename_global" +type = "bin" +authors = [""] + +[dependencies] diff --git a/tooling/lsp/test_programs/rename_global/src/main.nr b/tooling/lsp/test_programs/rename_global/src/main.nr new file mode 100644 index 00000000000..3ae8cf4bfc3 --- /dev/null +++ b/tooling/lsp/test_programs/rename_global/src/main.nr @@ -0,0 +1,22 @@ +mod foo { + global FOO = 1; +} + +use foo::FOO; + +fn main() { + let _ = foo::FOO; + let _ = FOO; + let _: [Field; FOO] = [1]; +} + +trait WithNumber { + +} + +struct Some { +} + +impl WithNumber for Some { + +} diff --git a/tooling/lsp/test_programs/rename_qualified/Nargo.toml b/tooling/lsp/test_programs/rename_qualified/Nargo.toml new file mode 100644 index 00000000000..7de13ef6b34 --- /dev/null +++ b/tooling/lsp/test_programs/rename_qualified/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "rename_qualified" +type = "bin" +authors = [""] + +[dependencies] diff --git a/tooling/lsp/test_programs/rename_qualified/src/main.nr b/tooling/lsp/test_programs/rename_qualified/src/main.nr new file mode 100644 index 00000000000..f1b77796210 --- /dev/null +++ b/tooling/lsp/test_programs/rename_qualified/src/main.nr @@ -0,0 +1,9 @@ +fn main() -> pub Field { + foo::bar() +} + +mod foo { + pub fn bar() -> Field { + 1 + } +} diff --git a/tooling/lsp/test_programs/rename_qualified_function/Nargo.toml b/tooling/lsp/test_programs/rename_qualified_function/Nargo.toml new file mode 100644 index 00000000000..c0aaa3ce658 --- /dev/null +++ b/tooling/lsp/test_programs/rename_qualified_function/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "rename_qualified_function" +type = "bin" +authors = [""] + +[dependencies] diff --git a/tooling/lsp/test_programs/rename_qualified_function/src/main.nr b/tooling/lsp/test_programs/rename_qualified_function/src/main.nr new file mode 100644 index 00000000000..f1b77796210 --- /dev/null +++ b/tooling/lsp/test_programs/rename_qualified_function/src/main.nr @@ -0,0 +1,9 @@ +fn main() -> pub Field { + foo::bar() +} + +mod foo { + pub fn bar() -> Field { + 1 + } +} diff --git a/tooling/lsp/test_programs/rename_struct/Nargo.toml b/tooling/lsp/test_programs/rename_struct/Nargo.toml new file mode 100644 index 00000000000..e5822098e58 --- /dev/null +++ b/tooling/lsp/test_programs/rename_struct/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "rename_struct" +type = "bin" +authors = [""] + +[dependencies] diff --git a/tooling/lsp/test_programs/rename_struct/src/main.nr b/tooling/lsp/test_programs/rename_struct/src/main.nr new file mode 100644 index 00000000000..d9da3e75763 --- /dev/null +++ b/tooling/lsp/test_programs/rename_struct/src/main.nr @@ -0,0 +1,31 @@ +mod foo { + mod bar { + struct Foo { + field: Field, + } + + impl Foo { + fn foo() {} + + fn bar(self) {} + + fn baz() -> Self { + Self { field: 1 } + } + } + } +} + +use foo::bar::Foo; + +fn main(x: Field) -> pub Field { + let foo1 = Foo { field: 1 }; + let foo2 = Foo { field: 2 }; + let Foo { field } = foo1; + Foo::foo(); + x +} + +fn foo(foo: Foo) -> Foo { + foo +} diff --git a/tooling/lsp/test_programs/rename_trait/Nargo.toml b/tooling/lsp/test_programs/rename_trait/Nargo.toml new file mode 100644 index 00000000000..a8cc34898bd --- /dev/null +++ b/tooling/lsp/test_programs/rename_trait/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "rename_trait" +type = "bin" +authors = [""] + +[dependencies] diff --git a/tooling/lsp/test_programs/rename_trait/src/main.nr b/tooling/lsp/test_programs/rename_trait/src/main.nr new file mode 100644 index 00000000000..dd0783985a7 --- /dev/null +++ b/tooling/lsp/test_programs/rename_trait/src/main.nr @@ -0,0 +1,21 @@ +mod foo { + mod bar { + trait Foo { + fn foo() {} + } + } +} + +use foo::bar::Foo; + +struct Bar { + +} + +impl Foo for Bar { + +} + +fn main() { + foo::bar::Foo::foo(); +} diff --git a/tooling/lsp/test_programs/rename_type_alias/Nargo.toml b/tooling/lsp/test_programs/rename_type_alias/Nargo.toml new file mode 100644 index 00000000000..1b95727ed8d --- /dev/null +++ b/tooling/lsp/test_programs/rename_type_alias/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "rename_type_alias" +type = "bin" +authors = [""] + +[dependencies] diff --git a/tooling/lsp/test_programs/rename_type_alias/src/main.nr b/tooling/lsp/test_programs/rename_type_alias/src/main.nr new file mode 100644 index 00000000000..2072d9cae87 --- /dev/null +++ b/tooling/lsp/test_programs/rename_type_alias/src/main.nr @@ -0,0 +1,24 @@ +mod foo { + struct Foo { + } + + type Bar = Foo; + + mod bar { + struct Baz { + + } + } +} + +use foo::Foo; +use foo::Bar; +use foo::bar; + +fn main() { + let x: Bar = Foo {}; +} + +fn x(b: Bar) -> Bar { + b +} diff --git a/tooling/lsp/test_programs/struct_member/Nargo.toml b/tooling/lsp/test_programs/struct_member/Nargo.toml new file mode 100644 index 00000000000..5272b9abb68 --- /dev/null +++ b/tooling/lsp/test_programs/struct_member/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "struct_member" +type = "bin" +authors = [""] + +[dependencies] diff --git a/tooling/lsp/test_programs/struct_member/src/main.nr b/tooling/lsp/test_programs/struct_member/src/main.nr new file mode 100644 index 00000000000..3f1bac9df66 --- /dev/null +++ b/tooling/lsp/test_programs/struct_member/src/main.nr @@ -0,0 +1,12 @@ +struct Foo { + some_member: Field +} + +fn main() { + let mut foo = Foo { some_member: 1 }; + foo.some_member = 2; + let _ = foo.some_member; + + let Foo { some_member } = foo; + let Foo { some_member: some_var } = foo; +} diff --git a/tooling/nargo/src/ops/test.rs b/tooling/nargo/src/ops/test.rs index ace2e9f0d0c..18c6f2530b9 100644 --- a/tooling/nargo/src/ops/test.rs +++ b/tooling/nargo/src/ops/test.rs @@ -128,7 +128,7 @@ fn check_expected_failure_message( }; let expected_failure_message_matches = - matches!(&failed_assertion, Some(message) if message == expected_failure_message); + matches!(&failed_assertion, Some(message) if message.contains(expected_failure_message)); if expected_failure_message_matches { return TestStatus::Pass; } diff --git a/tooling/nargo_cli/Cargo.toml b/tooling/nargo_cli/Cargo.toml index b9d7d7e3e48..c6cf842a623 100644 --- a/tooling/nargo_cli/Cargo.toml +++ b/tooling/nargo_cli/Cargo.toml @@ -47,7 +47,7 @@ async-lsp = { workspace = true, features = ["client-monitor", "stdio", "tracing" const_format.workspace = true similar-asserts.workspace = true termcolor = "1.1.2" -color-eyre = "0.6.2" +color-eyre.workspace = true tokio = { version = "1.0", features = ["io-std", "rt"] } dap.workspace = true clap-markdown = { git = "https://github.com/noir-lang/clap-markdown", rev = "450d759532c88f0dba70891ceecdbc9ff8f25d2b", optional = true } diff --git a/tooling/nargo_cli/benches/criterion.rs b/tooling/nargo_cli/benches/criterion.rs index 9f67bcffd6e..effab7d7c27 100644 --- a/tooling/nargo_cli/benches/criterion.rs +++ b/tooling/nargo_cli/benches/criterion.rs @@ -28,16 +28,10 @@ macro_rules! criterion_command { }; } criterion_command!(execution, "execute"); -criterion_command!(prove, "prove"); criterion_group! { name = execution_benches; config = Criterion::default().sample_size(20).measurement_time(Duration::from_secs(20)).with_profiler(PProfProfiler::new(100, Output::Flamegraph(None))); targets = criterion_selected_tests_execution } -criterion_group! { - name = prove_benches; - config = Criterion::default().sample_size(10).measurement_time(Duration::from_secs(20)).with_profiler(PProfProfiler::new(100, Output::Flamegraph(None))); - targets = criterion_selected_tests_prove -} -criterion_main!(execution_benches, prove_benches); +criterion_main!(execution_benches); diff --git a/tooling/nargo_cli/build.rs b/tooling/nargo_cli/build.rs index 43c277ba03e..9dfa0dfe861 100644 --- a/tooling/nargo_cli/build.rs +++ b/tooling/nargo_cli/build.rs @@ -61,395 +61,303 @@ const IGNORED_BRILLIG_TESTS: [&str; 11] = [ /// Certain features are only available in the elaborator. /// We skip these tests for non-elaborator code since they are not /// expected to work there. This can be removed once the old code is removed. -const IGNORED_NEW_FEATURE_TESTS: [&str; 3] = - ["macros", "wildcard_type", "type_definition_annotation"]; +const IGNORED_NEW_FEATURE_TESTS: [&str; 9] = [ + "macros", + "wildcard_type", + "type_definition_annotation", + "numeric_generics_explicit", + "derive_impl", + "comptime_traits", + "comptime_slice_methods", + "unary_operator_overloading", + "unquote_multiple_items_from_annotation", +]; -fn generate_execution_success_tests(test_file: &mut File, test_data_dir: &Path) { - let test_sub_dir = "execution_success"; +fn read_test_cases( + test_data_dir: &Path, + test_sub_dir: &str, +) -> impl Iterator { let test_data_dir = test_data_dir.join(test_sub_dir); - let test_case_dirs = fs::read_dir(test_data_dir).unwrap().flatten().filter(|c| c.path().is_dir()); - for test_dir in test_case_dirs { + test_case_dirs.into_iter().map(|dir| { let test_name = - test_dir.file_name().into_string().expect("Directory can't be converted to string"); + dir.file_name().into_string().expect("Directory can't be converted to string"); if test_name.contains('-') { panic!( "Invalid test directory: {test_name}. Cannot include `-`, please convert to `_`" ); - }; - let test_dir = &test_dir.path(); - - let brillig_ignored = - if IGNORED_BRILLIG_TESTS.contains(&test_name.as_str()) { "\n#[ignore]" } else { "" }; - let new_features_ignored = if IGNORED_NEW_FEATURE_TESTS.contains(&test_name.as_str()) { - "\n#[ignore]" - } else { - "" - }; - - write!( - test_file, - r#" -#[test]{new_features_ignored} -fn execution_success_legacy_{test_name}() {{ - let test_program_dir = PathBuf::from("{test_dir}"); - - let mut cmd = Command::cargo_bin("nargo").unwrap(); - cmd.arg("--program-dir").arg(test_program_dir); - cmd.arg("execute").arg("--force").arg("--use-legacy"); - - cmd.assert().success(); -}} + } + (test_name, dir.path()) + }) +} +fn generate_test_case( + test_file: &mut File, + test_type: &str, + test_name: &str, + test_dir: &std::path::Display, + test_content: &str, +) { + write!( + test_file, + r#" #[test] -fn execution_success_{test_name}() {{ +fn {test_type}_{test_name}() {{ let test_program_dir = PathBuf::from("{test_dir}"); - let mut cmd = Command::cargo_bin("nargo").unwrap(); - cmd.arg("--program-dir").arg(test_program_dir); - cmd.arg("execute").arg("--force"); - - cmd.assert().success(); + let mut nargo = Command::cargo_bin("nargo").unwrap(); + nargo.arg("--program-dir").arg(test_program_dir); + {test_content} }} +"# + ) + .expect("Could not write templated test file."); +} -#[test]{brillig_ignored} -fn execution_success_{test_name}_brillig() {{ - let test_program_dir = PathBuf::from("{test_dir}"); - - let mut cmd = Command::cargo_bin("nargo").unwrap(); - cmd.arg("--program-dir").arg(test_program_dir); - cmd.arg("execute").arg("--force").arg("--force-brillig"); +fn generate_execution_success_tests(test_file: &mut File, test_data_dir: &Path) { + let test_type = "execution_success"; + let test_cases = read_test_cases(test_data_dir, test_type); + for (test_name, test_dir) in test_cases { + let test_dir = test_dir.display(); - cmd.assert().success(); -}} - "#, - test_dir = test_dir.display(), - ) - .expect("Could not write templated test file."); + generate_test_case( + test_file, + test_type, + &test_name, + &test_dir, + r#" + nargo.arg("execute").arg("--force"); + + nargo.assert().success();"#, + ); + + if !IGNORED_NEW_FEATURE_TESTS.contains(&test_name.as_str()) { + generate_test_case( + test_file, + test_type, + &format!("legacy_{test_name}"), + &test_dir, + r#" + nargo.arg("execute").arg("--force").arg("--use-legacy"); + + nargo.assert().success();"#, + ); + } + + if !IGNORED_BRILLIG_TESTS.contains(&test_name.as_str()) { + generate_test_case( + test_file, + test_type, + &format!("{test_name}_brillig"), + &test_dir, + r#" + nargo.arg("execute").arg("--force").arg("--force-brillig"); + + nargo.assert().success();"#, + ); + } } } fn generate_execution_failure_tests(test_file: &mut File, test_data_dir: &Path) { - let test_sub_dir = "execution_failure"; - let test_data_dir = test_data_dir.join(test_sub_dir); - - let test_case_dirs = - fs::read_dir(test_data_dir).unwrap().flatten().filter(|c| c.path().is_dir()); + let test_type = "execution_failure"; + let test_cases = read_test_cases(test_data_dir, test_type); + for (test_name, test_dir) in test_cases { + let test_dir = test_dir.display(); - for test_dir in test_case_dirs { - let test_name = - test_dir.file_name().into_string().expect("Directory can't be converted to string"); - if test_name.contains('-') { - panic!( - "Invalid test directory: {test_name}. Cannot include `-`, please convert to `_`" - ); - }; - let test_dir = &test_dir.path(); - - write!( + generate_test_case( test_file, + test_type, + &test_name, + &test_dir, r#" -#[test] -fn execution_failure_legacy_{test_name}() {{ - let test_program_dir = PathBuf::from("{test_dir}"); - - let mut cmd = Command::cargo_bin("nargo").unwrap(); - cmd.arg("--program-dir").arg(test_program_dir); - cmd.arg("execute").arg("--force").arg("--use-legacy"); - - cmd.assert().failure().stderr(predicate::str::contains("The application panicked (crashed).").not()); -}} - -#[test] -fn execution_failure_{test_name}() {{ - let test_program_dir = PathBuf::from("{test_dir}"); + nargo.arg("execute").arg("--force"); + + nargo.assert().failure().stderr(predicate::str::contains("The application panicked (crashed).").not());"#, + ); - let mut cmd = Command::cargo_bin("nargo").unwrap(); - cmd.arg("--program-dir").arg(test_program_dir); - cmd.arg("execute").arg("--force"); - - cmd.assert().failure().stderr(predicate::str::contains("The application panicked (crashed).").not()); -}} - "#, - test_dir = test_dir.display(), - ) - .expect("Could not write templated test file."); + generate_test_case( + test_file, + test_type, + &format!("legacy_{test_name}"), + &test_dir, + r#" + nargo.arg("execute").arg("--force").arg("--use-legacy"); + + nargo.assert().failure().stderr(predicate::str::contains("The application panicked (crashed).").not());"#, + ); } } fn generate_noir_test_success_tests(test_file: &mut File, test_data_dir: &Path) { - let test_sub_dir = "noir_test_success"; - let test_data_dir = test_data_dir.join(test_sub_dir); + let test_type = "noir_test_success"; + let test_cases = read_test_cases(test_data_dir, "noir_test_success"); + for (test_name, test_dir) in test_cases { + let test_dir = test_dir.display(); - let test_case_dirs = - fs::read_dir(test_data_dir).unwrap().flatten().filter(|c| c.path().is_dir()); - - for test_dir in test_case_dirs { - let test_name = - test_dir.file_name().into_string().expect("Directory can't be converted to string"); - if test_name.contains('-') { - panic!( - "Invalid test directory: {test_name}. Cannot include `-`, please convert to `_`" - ); - }; - let test_dir = &test_dir.path(); - - write!( + generate_test_case( test_file, + test_type, + &test_name, + &test_dir, r#" -#[test] -fn noir_test_success_legacy_{test_name}() {{ - let test_program_dir = PathBuf::from("{test_dir}"); - - let mut cmd = Command::cargo_bin("nargo").unwrap(); - cmd.arg("--program-dir").arg(test_program_dir); - cmd.arg("test").arg("--use-legacy"); - - cmd.assert().success(); -}} + nargo.arg("test"); + + nargo.assert().success();"#, + ); -#[test] -fn noir_test_success_{test_name}() {{ - let test_program_dir = PathBuf::from("{test_dir}"); - - let mut cmd = Command::cargo_bin("nargo").unwrap(); - cmd.arg("--program-dir").arg(test_program_dir); - cmd.arg("test"); - - cmd.assert().success(); -}} - "#, - test_dir = test_dir.display(), - ) - .expect("Could not write templated test file."); + generate_test_case( + test_file, + test_type, + &format!("legacy_{test_name}"), + &test_dir, + r#" + nargo.arg("test").arg("--use-legacy"); + + nargo.assert().success();"#, + ); } } fn generate_noir_test_failure_tests(test_file: &mut File, test_data_dir: &Path) { - let test_sub_dir = "noir_test_failure"; - let test_data_dir = test_data_dir.join(test_sub_dir); - - let test_case_dirs = - fs::read_dir(test_data_dir).unwrap().flatten().filter(|c| c.path().is_dir()); - - for test_dir in test_case_dirs { - let test_name = - test_dir.file_name().into_string().expect("Directory can't be converted to string"); - if test_name.contains('-') { - panic!( - "Invalid test directory: {test_name}. Cannot include `-`, please convert to `_`" - ); - }; - let test_dir = &test_dir.path(); - - write!( + let test_type = "noir_test_failure"; + let test_cases = read_test_cases(test_data_dir, test_type); + for (test_name, test_dir) in test_cases { + let test_dir = test_dir.display(); + generate_test_case( test_file, + test_type, + &test_name, + &test_dir, r#" -#[test] -fn noir_test_failure_legacy_{test_name}() {{ - let test_program_dir = PathBuf::from("{test_dir}"); - - let mut cmd = Command::cargo_bin("nargo").unwrap(); - cmd.arg("--program-dir").arg(test_program_dir); - cmd.arg("test").arg("--use-legacy"); - - cmd.assert().failure(); -}} + nargo.arg("test"); + + nargo.assert().failure();"#, + ); -#[test] -fn noir_test_failure_{test_name}() {{ - let test_program_dir = PathBuf::from("{test_dir}"); - - let mut cmd = Command::cargo_bin("nargo").unwrap(); - cmd.arg("--program-dir").arg(test_program_dir); - cmd.arg("test"); - - cmd.assert().failure(); -}} - "#, - test_dir = test_dir.display(), - ) - .expect("Could not write templated test file."); + generate_test_case( + test_file, + test_type, + &format!("legacy_{test_name}"), + &test_dir, + r#" + nargo.arg("test").arg("--use-legacy"); + + nargo.assert().failure();"#, + ); } } fn generate_compile_success_empty_tests(test_file: &mut File, test_data_dir: &Path) { - let test_sub_dir = "compile_success_empty"; - let test_data_dir = test_data_dir.join(test_sub_dir); - - let test_case_dirs = - fs::read_dir(test_data_dir).unwrap().flatten().filter(|c| c.path().is_dir()); - - for test_dir in test_case_dirs { - let test_name = - test_dir.file_name().into_string().expect("Directory can't be converted to string"); - if test_name.contains('-') { - panic!( - "Invalid test directory: {test_name}. Cannot include `-`, please convert to `_`" - ); - }; - let test_dir = &test_dir.path(); - - let new_feature_ignored = if IGNORED_NEW_FEATURE_TESTS.contains(&test_name.as_str()) { - "\n#[ignore]" - } else { - "" - }; - - write!( + let test_type = "compile_success_empty"; + let test_cases = read_test_cases(test_data_dir, test_type); + for (test_name, test_dir) in test_cases { + let test_dir = test_dir.display(); + + let assert_zero_opcodes = r#" + let output = nargo.output().expect("Failed to execute command"); + + if !output.status.success() {{ + panic!("`nargo info` failed with: {}", String::from_utf8(output.stderr).unwrap_or_default()); + }} + + // `compile_success_empty` tests should be able to compile down to an empty circuit. + let json: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap_or_else(|e| {{ + panic!("JSON was not well-formatted {:?}\n\n{:?}", e, std::str::from_utf8(&output.stdout)) + }}); + let num_opcodes = &json["programs"][0]["functions"][0]["acir_opcodes"]; + assert_eq!(num_opcodes.as_u64().expect("number of opcodes should fit in a u64"), 0); + "#; + + generate_test_case( test_file, - r#" -#[test]{new_feature_ignored} -fn compile_success_empty_legacy_{test_name}() {{ - let test_program_dir = PathBuf::from("{test_dir}"); - let mut cmd = Command::cargo_bin("nargo").unwrap(); - cmd.arg("--program-dir").arg(test_program_dir); - cmd.arg("info"); - cmd.arg("--json"); - cmd.arg("--force"); - cmd.arg("--use-legacy"); - - let output = cmd.output().expect("Failed to execute command"); - - if !output.status.success() {{ - panic!("`nargo info` failed with: {{}}", String::from_utf8(output.stderr).unwrap_or_default()); - }} - - // `compile_success_empty` tests should be able to compile down to an empty circuit. - let json: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap_or_else(|e| {{ - panic!("JSON was not well-formatted {{:?}}\n\n{{:?}}", e, std::str::from_utf8(&output.stdout)) - }}); - let num_opcodes = &json["programs"][0]["functions"][0]["acir_opcodes"]; - assert_eq!(num_opcodes.as_u64().expect("number of opcodes should fit in a u64"), 0); -}} - -#[test] -fn compile_success_empty_{test_name}() {{ - let test_program_dir = PathBuf::from("{test_dir}"); - let mut cmd = Command::cargo_bin("nargo").unwrap(); - cmd.arg("--program-dir").arg(test_program_dir); - cmd.arg("info"); - cmd.arg("--json"); - cmd.arg("--force"); - - let output = cmd.output().expect("Failed to execute command"); - - if !output.status.success() {{ - panic!("`nargo info` failed with: {{}}", String::from_utf8(output.stderr).unwrap_or_default()); - }} - - // `compile_success_empty` tests should be able to compile down to an empty circuit. - let json: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap_or_else(|e| {{ - panic!("JSON was not well-formatted {{:?}}\n\n{{:?}}", e, std::str::from_utf8(&output.stdout)) - }}); - let num_opcodes = &json["programs"][0]["functions"][0]["acir_opcodes"]; - assert_eq!(num_opcodes.as_u64().expect("number of opcodes should fit in a u64"), 0); -}} - "#, - test_dir = test_dir.display(), - ) - .expect("Could not write templated test file."); + test_type, + &test_name, + &test_dir, + &format!( + r#" + nargo.arg("info").arg("--json").arg("--force"); + + {assert_zero_opcodes}"#, + ), + ); + + if !IGNORED_NEW_FEATURE_TESTS.contains(&test_name.as_str()) { + generate_test_case( + test_file, + test_type, + &format!("legacy_{test_name}"), + &test_dir, + &format!( + r#" + nargo.arg("info").arg("--json").arg("--force").arg("--use-legacy"); + + {assert_zero_opcodes}"#, + ), + ); + } } } fn generate_compile_success_contract_tests(test_file: &mut File, test_data_dir: &Path) { - let test_sub_dir = "compile_success_contract"; - let test_data_dir = test_data_dir.join(test_sub_dir); + let test_type = "compile_success_contract"; + let test_cases = read_test_cases(test_data_dir, test_type); + for (test_name, test_dir) in test_cases { + let test_dir = test_dir.display(); - let test_case_dirs = - fs::read_dir(test_data_dir).unwrap().flatten().filter(|c| c.path().is_dir()); - - for test_dir in test_case_dirs { - let test_name = - test_dir.file_name().into_string().expect("Directory can't be converted to string"); - if test_name.contains('-') { - panic!( - "Invalid test directory: {test_name}. Cannot include `-`, please convert to `_`" - ); - }; - let test_dir = &test_dir.path(); - - write!( + generate_test_case( test_file, + test_type, + &test_name, + &test_dir, r#" -#[test] -fn compile_success_contract_legacy_{test_name}() {{ - let test_program_dir = PathBuf::from("{test_dir}"); - - let mut cmd = Command::cargo_bin("nargo").unwrap(); - cmd.arg("--program-dir").arg(test_program_dir); - cmd.arg("compile").arg("--force").arg("--use-legacy"); + nargo.arg("compile").arg("--force"); + + nargo.assert().success();"#, + ); - cmd.assert().success(); -}} -#[test] -fn compile_success_contract_{test_name}() {{ - let test_program_dir = PathBuf::from("{test_dir}"); - - let mut cmd = Command::cargo_bin("nargo").unwrap(); - cmd.arg("--program-dir").arg(test_program_dir); - cmd.arg("compile").arg("--force"); - - cmd.assert().success(); -}} - "#, - test_dir = test_dir.display(), - ) - .expect("Could not write templated test file."); + generate_test_case( + test_file, + test_type, + &format!("legacy_{test_name}"), + &test_dir, + r#" + nargo.arg("compile").arg("--force").arg("--use-legacy"); + + nargo.assert().success();"#, + ); } } fn generate_compile_failure_tests(test_file: &mut File, test_data_dir: &Path) { - let test_sub_dir = "compile_failure"; - let test_data_dir = test_data_dir.join(test_sub_dir); - - let test_case_dirs = - fs::read_dir(test_data_dir).unwrap().flatten().filter(|c| c.path().is_dir()); + let test_type = "compile_failure"; + let test_cases = read_test_cases(test_data_dir, test_type); + for (test_name, test_dir) in test_cases { + let test_dir = test_dir.display(); - for test_dir in test_case_dirs { - let test_name = - test_dir.file_name().into_string().expect("Directory can't be converted to string"); - if test_name.contains('-') { - panic!( - "Invalid test directory: {test_name}. Cannot include `-`, please convert to `_`" - ); - }; - let test_dir = &test_dir.path(); - - let new_feature_ignored = if IGNORED_NEW_FEATURE_TESTS.contains(&test_name.as_str()) { - "\n#[ignore]" - } else { - "" - }; - - write!( + generate_test_case( test_file, - r#" -#[test]{new_feature_ignored} -fn compile_failure_legacy_{test_name}() {{ - let test_program_dir = PathBuf::from("{test_dir}"); - - let mut cmd = Command::cargo_bin("nargo").unwrap(); - cmd.arg("--program-dir").arg(test_program_dir); - cmd.arg("compile").arg("--force").arg("--use-legacy"); - - cmd.assert().failure().stderr(predicate::str::contains("The application panicked (crashed).").not()); -}} -#[test] -fn compile_failure_{test_name}() {{ - let test_program_dir = PathBuf::from("{test_dir}"); - - let mut cmd = Command::cargo_bin("nargo").unwrap(); - cmd.arg("--program-dir").arg(test_program_dir); - cmd.arg("compile").arg("--force"); - - cmd.assert().failure().stderr(predicate::str::contains("The application panicked (crashed).").not()); -}} - "#, - test_dir = test_dir.display(), - ) - .expect("Could not write templated test file."); + test_type, + &test_name, + &test_dir, + r#"nargo.arg("compile").arg("--force"); + + nargo.assert().failure().stderr(predicate::str::contains("The application panicked (crashed).").not());"#, + ); + + if !IGNORED_NEW_FEATURE_TESTS.contains(&test_name.as_str()) { + generate_test_case( + test_file, + test_type, + &format!("legacy_{test_name}"), + &test_dir, + r#" + nargo.arg("compile").arg("--force").arg("--use-legacy"); + + nargo.assert().failure().stderr(predicate::str::contains("The application panicked (crashed).").not());"#, + ); + } } } diff --git a/tooling/nargo_cli/src/cli/check_cmd.rs b/tooling/nargo_cli/src/cli/check_cmd.rs index 2db3b10429a..95726492418 100644 --- a/tooling/nargo_cli/src/cli/check_cmd.rs +++ b/tooling/nargo_cli/src/cli/check_cmd.rs @@ -88,6 +88,7 @@ fn check_package( compile_options.disable_macros, compile_options.silence_warnings, compile_options.use_legacy, + compile_options.debug_comptime_in_file.as_deref(), )?; if package.is_library() || package.is_contract() { @@ -161,8 +162,16 @@ pub(crate) fn check_crate_and_report_errors( disable_macros: bool, silence_warnings: bool, use_legacy: bool, + debug_comptime_in_file: Option<&str>, ) -> Result<(), CompileError> { - let result = check_crate(context, crate_id, deny_warnings, disable_macros, use_legacy); + let result = check_crate( + context, + crate_id, + deny_warnings, + disable_macros, + use_legacy, + debug_comptime_in_file, + ); report_errors(result, &context.file_manager, deny_warnings, silence_warnings) } diff --git a/tooling/nargo_cli/src/cli/export_cmd.rs b/tooling/nargo_cli/src/cli/export_cmd.rs index ee30b29b0f0..105190c653f 100644 --- a/tooling/nargo_cli/src/cli/export_cmd.rs +++ b/tooling/nargo_cli/src/cli/export_cmd.rs @@ -90,6 +90,7 @@ fn compile_exported_functions( compile_options.disable_macros, compile_options.silence_warnings, compile_options.use_legacy, + compile_options.debug_comptime_in_file.as_deref(), )?; let exported_functions = context.get_all_exported_functions_in_crate(&crate_id); diff --git a/tooling/nargo_cli/src/cli/test_cmd.rs b/tooling/nargo_cli/src/cli/test_cmd.rs index de9e8dc5d7c..27c66c956d9 100644 --- a/tooling/nargo_cli/src/cli/test_cmd.rs +++ b/tooling/nargo_cli/src/cli/test_cmd.rs @@ -177,6 +177,7 @@ fn run_test + Default>( compile_options.deny_warnings, compile_options.disable_macros, compile_options.use_legacy, + compile_options.debug_comptime_in_file.as_deref(), ) .expect("Any errors should have occurred when collecting test functions"); @@ -244,6 +245,7 @@ fn get_tests_in_package( compile_options.disable_macros, compile_options.silence_warnings, compile_options.use_legacy, + compile_options.debug_comptime_in_file.as_deref(), )?; Ok(context diff --git a/tooling/nargo_cli/tests/stdlib-tests.rs b/tooling/nargo_cli/tests/stdlib-tests.rs index 5badd76605a..10711b6d011 100644 --- a/tooling/nargo_cli/tests/stdlib-tests.rs +++ b/tooling/nargo_cli/tests/stdlib-tests.rs @@ -1,7 +1,6 @@ use std::io::Write; use std::{collections::BTreeMap, path::PathBuf}; -use acvm::blackbox_solver::StubbedBlackBoxSolver; use fm::FileManager; use noirc_driver::{check_crate, compile_no_check, file_manager_with_stdlib, CompileOptions}; use noirc_frontend::hir::FunctionNameMatch; @@ -33,7 +32,7 @@ fn run_stdlib_tests() { let (mut context, dummy_crate_id) = prepare_package(&file_manager, &parsed_files, &dummy_package); - let result = check_crate(&mut context, dummy_crate_id, true, false, false); + let result = check_crate(&mut context, dummy_crate_id, false, false, false, None); report_errors(result, &context.file_manager, true, false) .expect("Error encountered while compiling standard library"); @@ -56,7 +55,7 @@ fn run_stdlib_tests() { let status = if test_function_has_no_arguments { run_test( - &StubbedBlackBoxSolver, + &bn254_blackbox_solver::Bn254BlackBoxSolver, &mut context, &test_function, false, diff --git a/tooling/nargo_fmt/src/items.rs b/tooling/nargo_fmt/src/items.rs index 7f998f45b59..80b641fd830 100644 --- a/tooling/nargo_fmt/src/items.rs +++ b/tooling/nargo_fmt/src/items.rs @@ -74,7 +74,8 @@ impl<'me, T> Items<'me, T> { let mut different_line = false; let leading = self.visitor.slice(start..end); - let leading_trimmed = leading.trim(); + // Trim any possible whitespace before and after a comma separator + let leading_trimmed = leading.trim().trim_start_matches(',').trim(); let starts_with_block_comment = leading_trimmed.starts_with("/*"); let ends_with_block_comment = leading_trimmed.ends_with("*/"); diff --git a/tooling/nargo_fmt/src/rewrite/expr.rs b/tooling/nargo_fmt/src/rewrite/expr.rs index ec8ac4abec7..015644c15cb 100644 --- a/tooling/nargo_fmt/src/rewrite/expr.rs +++ b/tooling/nargo_fmt/src/rewrite/expr.rs @@ -168,9 +168,7 @@ pub(crate) fn rewrite( format!("{path_string}{turbofish}") } ExpressionKind::Lambda(_) => visitor.slice(span).to_string(), - ExpressionKind::Quote(block, block_span) => { - format!("quote {}", rewrite_block(visitor, block, block_span)) - } + ExpressionKind::Quote(_) => visitor.slice(span).to_string(), ExpressionKind::Comptime(block, block_span) => { format!("comptime {}", rewrite_block(visitor, block, block_span)) } @@ -185,7 +183,6 @@ pub(crate) fn rewrite( format!("$({})", rewrite_sub_expr(visitor, shape, *expr)) } } - ExpressionKind::UnquoteMarker(_) => unreachable!("UnquoteMarker in runtime code"), } } diff --git a/tooling/nargo_fmt/src/rewrite/typ.rs b/tooling/nargo_fmt/src/rewrite/typ.rs index 3eb398346c3..3298ed8ae73 100644 --- a/tooling/nargo_fmt/src/rewrite/typ.rs +++ b/tooling/nargo_fmt/src/rewrite/typ.rs @@ -55,6 +55,10 @@ pub(crate) fn rewrite(visitor: &FmtVisitor, _shape: Shape, typ: UnresolvedType) format!("fn{env}({args}) -> {return_type}") } + UnresolvedTypeData::Resolved(_) => { + unreachable!("Unexpected macro expansion of a type in nargo fmt input") + } + UnresolvedTypeData::Unspecified => todo!(), UnresolvedTypeData::FieldElement | UnresolvedTypeData::Integer(_, _) diff --git a/tooling/nargo_fmt/src/utils.rs b/tooling/nargo_fmt/src/utils.rs index 2c5c3085e66..020f411ae2f 100644 --- a/tooling/nargo_fmt/src/utils.rs +++ b/tooling/nargo_fmt/src/utils.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use crate::items::HasItem; use crate::rewrite; use crate::visitor::{FmtVisitor, Shape}; -use noirc_frontend::ast::{Expression, Ident, Param, Visibility}; +use noirc_frontend::ast::{Expression, Ident, Param, UnresolvedGeneric, Visibility}; use noirc_frontend::hir::resolution::errors::Span; use noirc_frontend::lexer::Lexer; use noirc_frontend::token::Token; @@ -80,6 +80,7 @@ pub(crate) fn find_comment_end(slice: &str, is_last: bool) -> usize { std::cmp::max(find_comment_end(slice) + block, separator_index + 1) } (_, Some(newline)) if newline > separator_index => newline + 1, + (None, None) => 0, _ => slice.len(), } } else if let Some(newline_index) = newline_index { @@ -170,6 +171,26 @@ impl HasItem for Ident { } } +impl HasItem for UnresolvedGeneric { + fn span(&self) -> Span { + self.span() + } + + fn format(self, visitor: &FmtVisitor, _shape: Shape) -> String { + match self { + UnresolvedGeneric::Variable(_) => visitor.slice(self.span()).into(), + UnresolvedGeneric::Numeric { ident, typ } => { + let mut result = "".to_owned(); + result.push_str(&ident.0.contents); + result.push_str(": "); + let typ = rewrite::typ(visitor, _shape, typ); + result.push_str(&typ); + result + } + } + } +} + pub(crate) fn first_line_width(exprs: &str) -> usize { exprs.lines().next().map_or(0, |line: &str| line.chars().count()) } diff --git a/tooling/nargo_fmt/src/visitor/item.rs b/tooling/nargo_fmt/src/visitor/item.rs index a5d042dc71e..5aaaf20ff47 100644 --- a/tooling/nargo_fmt/src/visitor/item.rs +++ b/tooling/nargo_fmt/src/visitor/item.rs @@ -44,7 +44,7 @@ impl super::FmtVisitor<'_> { if !func.def.generics.is_empty() { let full_span = name_span.end()..params_open; - let start = name_span.end(); + let start = self.span_before(full_span.clone(), Token::Less).start(); let end = self.span_after(full_span, Token::Greater).start(); let generics = func.def.generics; @@ -188,8 +188,8 @@ impl super::FmtVisitor<'_> { continue; } - let slice = - self.slice(self.last_position..impl_.object_type.span.unwrap().end()); + let before_brace = self.span_before(span, Token::LeftBrace).start(); + let slice = self.slice(self.last_position..before_brace).trim(); let after_brace = self.span_after(span, Token::LeftBrace).start(); self.last_position = after_brace; diff --git a/tooling/nargo_fmt/tests/expected/fn.nr b/tooling/nargo_fmt/tests/expected/fn.nr index 3d231cd3f7f..4dde9a1b3ec 100644 --- a/tooling/nargo_fmt/tests/expected/fn.nr +++ b/tooling/nargo_fmt/tests/expected/fn.nr @@ -61,3 +61,11 @@ fn main( ) {} pub fn from_baz(x: [Field; crate::foo::MAGIC_NUMBER]) {} + +fn id(x: [Field; I]) -> [Field; I] {} + +fn id_two(x: [Field; I]) -> [Field; I] {} + +fn whitespace_before_generics(foo: T) {} + +fn more_whitespace_before_generics(foo: T) {} diff --git a/tooling/nargo_fmt/tests/expected/impl.nr b/tooling/nargo_fmt/tests/expected/impl.nr index ec734b57970..3c2fa42837a 100644 --- a/tooling/nargo_fmt/tests/expected/impl.nr +++ b/tooling/nargo_fmt/tests/expected/impl.nr @@ -19,3 +19,9 @@ impl MyType { impl MyType { fn method(self) {} } + +impl MyStruct where T: MyEq { + fn my_eq(self, other: Self) -> bool { + (self.a == other.a) & self.b.my_eq(other.b) + } +} diff --git a/tooling/nargo_fmt/tests/input/fn.nr b/tooling/nargo_fmt/tests/input/fn.nr index 1c6d201fa39..16ed95a540d 100644 --- a/tooling/nargo_fmt/tests/input/fn.nr +++ b/tooling/nargo_fmt/tests/input/fn.nr @@ -44,3 +44,13 @@ fn main( ) {} pub fn from_baz(x: [Field; crate::foo::MAGIC_NUMBER]) {} + +fn id< T , let I : Field > ( x : [ Field ; I ] ) -> [Field; I ] { } + +fn id_two(x: [Field ; I]) -> [ Field; I] {} + +fn whitespace_before_generics < T > (foo: T) {} + +fn more_whitespace_before_generics < +T > (foo: T) {} diff --git a/tooling/nargo_fmt/tests/input/impl.nr b/tooling/nargo_fmt/tests/input/impl.nr index ea909dfad44..21ce6a2e175 100644 --- a/tooling/nargo_fmt/tests/input/impl.nr +++ b/tooling/nargo_fmt/tests/input/impl.nr @@ -19,3 +19,9 @@ fn method(self) {} impl MyType { fn method(self) {} } + +impl MyStruct where T: MyEq { + fn my_eq(self, other: Self) -> bool { + (self.a == other.a) & self.b.my_eq(other.b) + } +} diff --git a/tooling/noir_js_backend_barretenberg/package.json b/tooling/noir_js_backend_barretenberg/package.json index 2bb142c9fa0..12793d70545 100644 --- a/tooling/noir_js_backend_barretenberg/package.json +++ b/tooling/noir_js_backend_barretenberg/package.json @@ -41,7 +41,7 @@ "lint": "NODE_NO_WARNINGS=1 eslint . --ext .ts --ignore-path ./.eslintignore --max-warnings 0" }, "dependencies": { - "@aztec/bb.js": "0.41.0", + "@aztec/bb.js": "0.46.1", "@noir-lang/types": "workspace:*", "fflate": "^0.8.0" }, diff --git a/tooling/noir_js_backend_barretenberg/src/backend.ts b/tooling/noir_js_backend_barretenberg/src/backend.ts index d07681dd8c1..8ede6a07b50 100644 --- a/tooling/noir_js_backend_barretenberg/src/backend.ts +++ b/tooling/noir_js_backend_barretenberg/src/backend.ts @@ -10,7 +10,7 @@ import { type Barretenberg } from '@aztec/bb.js'; // minus the public inputs. const numBytesInProofWithoutPublicInputs: number = 2144; -export class BarretenbergVerifierBackend implements VerifierBackend { +export class BarretenbergBackend implements Backend, VerifierBackend { // These type assertions are used so that we don't // have to initialize `api` and `acirComposer` in the constructor. // These are initialized asynchronously in the `init` function, @@ -45,7 +45,11 @@ export class BarretenbergVerifierBackend implements VerifierBackend { const { Barretenberg, RawBuffer, Crs } = await import('@aztec/bb.js'); const api = await Barretenberg.new(this.options); - const [_exact, _total, subgroupSize] = await api.acirGetCircuitSizes(this.acirUncompressedBytecode); + const honkRecursion = false; + const [_exact, _total, subgroupSize] = await api.acirGetCircuitSizes( + this.acirUncompressedBytecode, + honkRecursion, + ); const crs = await Crs.new(subgroupSize + 1); await api.commonInitSlabAllocator(subgroupSize); await api.srsInitSrs(new RawBuffer(crs.getG1Data()), crs.numPoints, new RawBuffer(crs.getG2Data())); @@ -56,29 +60,6 @@ export class BarretenbergVerifierBackend implements VerifierBackend { } } - /** @description Verifies a proof */ - async verifyProof(proofData: ProofData): Promise { - const proof = reconstructProofWithPublicInputs(proofData); - await this.instantiate(); - await this.api.acirInitVerificationKey(this.acirComposer); - return await this.api.acirVerifyProof(this.acirComposer, proof); - } - - async getVerificationKey(): Promise { - await this.instantiate(); - await this.api.acirInitVerificationKey(this.acirComposer); - return await this.api.acirGetVerificationKey(this.acirComposer); - } - - async destroy(): Promise { - if (!this.api) { - return; - } - await this.api.destroy(); - } -} - -export class BarretenbergBackend extends BarretenbergVerifierBackend implements Backend { /** @description Generates a proof */ async generateProof(compressedWitness: Uint8Array): Promise { await this.instantiate(); @@ -140,4 +121,25 @@ export class BarretenbergBackend extends BarretenbergVerifierBackend implements vkHash: vk[1].toString(), }; } + + /** @description Verifies a proof */ + async verifyProof(proofData: ProofData): Promise { + const proof = reconstructProofWithPublicInputs(proofData); + await this.instantiate(); + await this.api.acirInitVerificationKey(this.acirComposer); + return await this.api.acirVerifyProof(this.acirComposer, proof); + } + + async getVerificationKey(): Promise { + await this.instantiate(); + await this.api.acirInitVerificationKey(this.acirComposer); + return await this.api.acirGetVerificationKey(this.acirComposer); + } + + async destroy(): Promise { + if (!this.api) { + return; + } + await this.api.destroy(); + } } diff --git a/tooling/noirc_abi/src/lib.rs b/tooling/noirc_abi/src/lib.rs index b3d80099137..c3e1ade04fa 100644 --- a/tooling/noirc_abi/src/lib.rs +++ b/tooling/noirc_abi/src/lib.rs @@ -171,7 +171,7 @@ pub struct AbiReturnType { pub visibility: AbiVisibility, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] #[cfg_attr(test, derive(arbitrary::Arbitrary))] pub struct Abi { /// An ordered list of the arguments to the program's `main` function, specifying their types and visibility. diff --git a/tooling/noirc_abi_wasm/build.sh b/tooling/noirc_abi_wasm/build.sh index c07d2d8a4c1..16fb26e55db 100755 --- a/tooling/noirc_abi_wasm/build.sh +++ b/tooling/noirc_abi_wasm/build.sh @@ -25,7 +25,7 @@ function run_if_available { require_command jq require_command cargo require_command wasm-bindgen -#require_command wasm-opt +require_command wasm-opt self_path=$(dirname "$(readlink -f "$0")") pname=$(cargo read-manifest | jq -r '.name') diff --git a/tooling/profiler/Cargo.toml b/tooling/profiler/Cargo.toml new file mode 100644 index 00000000000..0ccd56b791f --- /dev/null +++ b/tooling/profiler/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "noir_profiler" +description = "Profiler for noir circuits" +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +repository.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[[bin]] +name = "noir-profiler" +path = "src/main.rs" + +[dependencies] +color-eyre.workspace = true +clap.workspace = true +noirc_artifacts.workspace = true +const_format.workspace = true +serde.workspace = true +serde_json.workspace = true +fm.workspace = true +inferno = "0.11.19" +im.workspace = true +acir.workspace = true +noirc_errors.workspace = true + +# Logs +tracing-subscriber.workspace = true +tracing-appender = "0.2.3" + +[dev-dependencies] +noirc_abi.workspace = true +noirc_driver.workspace = true +tempfile.workspace = true + +[features] +default = ["bn254"] +bn254 = ["acir/bn254"] diff --git a/tooling/profiler/src/cli/gates_flamegraph_cmd.rs b/tooling/profiler/src/cli/gates_flamegraph_cmd.rs new file mode 100644 index 00000000000..154ac38f4bb --- /dev/null +++ b/tooling/profiler/src/cli/gates_flamegraph_cmd.rs @@ -0,0 +1,171 @@ +use std::path::{Path, PathBuf}; + +use clap::Args; +use color_eyre::eyre::{self, Context}; + +use noirc_artifacts::debug::DebugArtifact; + +use crate::flamegraph::{FlamegraphGenerator, InfernoFlamegraphGenerator}; +use crate::fs::read_program_from_file; +use crate::gates_provider::{BackendGatesProvider, GatesProvider}; + +#[derive(Debug, Clone, Args)] +pub(crate) struct GatesFlamegraphCommand { + /// The path to the artifact JSON file + #[clap(long, short)] + artifact_path: String, + + /// Path to the noir backend binary + #[clap(long, short)] + backend_path: String, + + /// The output folder for the flamegraph svg files + #[clap(long, short)] + output: String, +} + +pub(crate) fn run(args: GatesFlamegraphCommand) -> eyre::Result<()> { + run_with_provider( + &PathBuf::from(args.artifact_path), + &BackendGatesProvider { backend_path: PathBuf::from(args.backend_path) }, + &InfernoFlamegraphGenerator { count_name: "gates".to_string() }, + &PathBuf::from(args.output), + ) +} + +fn run_with_provider( + artifact_path: &Path, + gates_provider: &Provider, + flamegraph_generator: &Generator, + output_path: &Path, +) -> eyre::Result<()> { + let mut program = + read_program_from_file(artifact_path).context("Error reading program from file")?; + + let backend_gates_response = + gates_provider.get_gates(artifact_path).context("Error querying backend for gates")?; + + let function_names = program.names.clone(); + + let bytecode = std::mem::take(&mut program.bytecode); + + let debug_artifact: DebugArtifact = program.into(); + + for (func_idx, ((func_gates, func_name), bytecode)) in backend_gates_response + .functions + .into_iter() + .zip(function_names) + .zip(bytecode.functions) + .enumerate() + { + println!( + "Opcode count: {}, Total gates by opcodes: {}, Circuit size: {}", + func_gates.acir_opcodes, + func_gates.gates_per_opcode.iter().sum::(), + func_gates.circuit_size + ); + + flamegraph_generator.generate_flamegraph( + func_gates.gates_per_opcode, + bytecode.opcodes, + &debug_artifact.debug_symbols[func_idx], + &debug_artifact, + artifact_path.to_str().unwrap(), + &func_name, + &Path::new(&output_path).join(Path::new(&format!("{}_gates.svg", &func_name))), + )?; + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use acir::circuit::{Circuit, Opcode, Program}; + use color_eyre::eyre::{self}; + use fm::codespan_files::Files; + use noirc_artifacts::program::ProgramArtifact; + use noirc_errors::debug_info::{DebugInfo, ProgramDebugInfo}; + use std::{ + collections::{BTreeMap, HashMap}, + path::{Path, PathBuf}, + }; + + use crate::gates_provider::{BackendGatesReport, BackendGatesResponse, GatesProvider}; + + struct TestGateProvider { + mock_responses: HashMap, + } + + impl GatesProvider for TestGateProvider { + fn get_gates(&self, artifact_path: &std::path::Path) -> eyre::Result { + let response = self + .mock_responses + .get(artifact_path) + .expect("should have a mock response for the artifact path"); + + Ok(response.clone()) + } + } + + #[derive(Default)] + struct TestFlamegraphGenerator {} + + impl super::FlamegraphGenerator for TestFlamegraphGenerator { + fn generate_flamegraph<'files, F>( + &self, + _samples_per_opcode: Vec, + _opcodes: Vec>, + _debug_symbols: &DebugInfo, + _files: &'files impl Files<'files, FileId = fm::FileId>, + _artifact_name: &str, + _function_name: &str, + output_path: &Path, + ) -> eyre::Result<()> { + let output_file = std::fs::File::create(output_path).unwrap(); + std::io::Write::write_all(&mut std::io::BufWriter::new(output_file), b"success") + .unwrap(); + + Ok(()) + } + } + + #[test] + fn smoke_test() { + let temp_dir = tempfile::tempdir().unwrap(); + + let artifact_path = temp_dir.path().join("test.json"); + + let artifact = ProgramArtifact { + noir_version: "0.0.0".to_string(), + hash: 27, + abi: noirc_abi::Abi::default(), + bytecode: Program { functions: vec![Circuit::default()], ..Program::default() }, + debug_symbols: ProgramDebugInfo { debug_infos: vec![DebugInfo::default()] }, + file_map: BTreeMap::default(), + names: vec!["main".to_string()], + }; + + // Write the artifact to a file + let artifact_file = std::fs::File::create(&artifact_path).unwrap(); + serde_json::to_writer(artifact_file, &artifact).unwrap(); + + let mock_gates_response = BackendGatesResponse { + functions: vec![ + (BackendGatesReport { acir_opcodes: 0, gates_per_opcode: vec![], circuit_size: 0 }), + ], + }; + + let provider = TestGateProvider { + mock_responses: HashMap::from([(artifact_path.clone(), mock_gates_response)]), + }; + let flamegraph_generator = TestFlamegraphGenerator::default(); + + super::run_with_provider(&artifact_path, &provider, &flamegraph_generator, temp_dir.path()) + .expect("should run without errors"); + + // Check that the output file was written to + let output_file = temp_dir.path().join("main_gates.svg"); + assert!(output_file.exists()); + } +} diff --git a/tooling/profiler/src/cli/mod.rs b/tooling/profiler/src/cli/mod.rs new file mode 100644 index 00000000000..4c2503fbe4f --- /dev/null +++ b/tooling/profiler/src/cli/mod.rs @@ -0,0 +1,36 @@ +use clap::{Parser, Subcommand}; +use color_eyre::eyre; +use const_format::formatcp; + +mod gates_flamegraph_cmd; +mod opcodes_flamegraph_cmd; + +const PROFILER_VERSION: &str = env!("CARGO_PKG_VERSION"); + +static VERSION_STRING: &str = formatcp!("version = {}\n", PROFILER_VERSION,); + +#[derive(Parser, Debug)] +#[command(name="Noir profiler", author, version=VERSION_STRING, about, long_about = None)] +struct ProfilerCli { + #[command(subcommand)] + command: ProfilerCommand, +} + +#[non_exhaustive] +#[derive(Subcommand, Clone, Debug)] +enum ProfilerCommand { + GatesFlamegraph(gates_flamegraph_cmd::GatesFlamegraphCommand), + OpcodesFlamegraph(opcodes_flamegraph_cmd::OpcodesFlamegraphCommand), +} + +pub(crate) fn start_cli() -> eyre::Result<()> { + let ProfilerCli { command } = ProfilerCli::parse(); + + match command { + ProfilerCommand::GatesFlamegraph(args) => gates_flamegraph_cmd::run(args), + ProfilerCommand::OpcodesFlamegraph(args) => opcodes_flamegraph_cmd::run(args), + } + .map_err(|err| eyre::eyre!("{}", err))?; + + Ok(()) +} diff --git a/tooling/profiler/src/cli/opcodes_flamegraph_cmd.rs b/tooling/profiler/src/cli/opcodes_flamegraph_cmd.rs new file mode 100644 index 00000000000..e1dc1464f6f --- /dev/null +++ b/tooling/profiler/src/cli/opcodes_flamegraph_cmd.rs @@ -0,0 +1,123 @@ +use std::path::{Path, PathBuf}; + +use clap::Args; +use color_eyre::eyre::{self, Context}; + +use noirc_artifacts::debug::DebugArtifact; + +use crate::flamegraph::{FlamegraphGenerator, InfernoFlamegraphGenerator}; +use crate::fs::read_program_from_file; + +#[derive(Debug, Clone, Args)] +pub(crate) struct OpcodesFlamegraphCommand { + /// The path to the artifact JSON file + #[clap(long, short)] + artifact_path: String, + + /// The output folder for the flamegraph svg files + #[clap(long, short)] + output: String, +} + +pub(crate) fn run(args: OpcodesFlamegraphCommand) -> eyre::Result<()> { + run_with_generator( + &PathBuf::from(args.artifact_path), + &InfernoFlamegraphGenerator { count_name: "opcodes".to_string() }, + &PathBuf::from(args.output), + ) +} + +fn run_with_generator( + artifact_path: &Path, + flamegraph_generator: &Generator, + output_path: &Path, +) -> eyre::Result<()> { + let mut program = + read_program_from_file(artifact_path).context("Error reading program from file")?; + + let function_names = program.names.clone(); + + let bytecode = std::mem::take(&mut program.bytecode); + + let debug_artifact: DebugArtifact = program.into(); + + for (func_idx, (func_name, bytecode)) in + function_names.into_iter().zip(bytecode.functions).enumerate() + { + println!("Opcode count: {}", bytecode.opcodes.len()); + + flamegraph_generator.generate_flamegraph( + bytecode.opcodes.iter().map(|_op| 1).collect(), + bytecode.opcodes, + &debug_artifact.debug_symbols[func_idx], + &debug_artifact, + artifact_path.to_str().unwrap(), + &func_name, + &Path::new(&output_path).join(Path::new(&format!("{}_opcodes.svg", &func_name))), + )?; + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use acir::circuit::{Circuit, Opcode, Program}; + use color_eyre::eyre::{self}; + use fm::codespan_files::Files; + use noirc_artifacts::program::ProgramArtifact; + use noirc_errors::debug_info::{DebugInfo, ProgramDebugInfo}; + use std::{collections::BTreeMap, path::Path}; + + #[derive(Default)] + struct TestFlamegraphGenerator {} + + impl super::FlamegraphGenerator for TestFlamegraphGenerator { + fn generate_flamegraph<'files, F>( + &self, + _samples_per_opcode: Vec, + _opcodes: Vec>, + _debug_symbols: &DebugInfo, + _files: &'files impl Files<'files, FileId = fm::FileId>, + _artifact_name: &str, + _function_name: &str, + output_path: &Path, + ) -> eyre::Result<()> { + let output_file = std::fs::File::create(output_path).unwrap(); + std::io::Write::write_all(&mut std::io::BufWriter::new(output_file), b"success") + .unwrap(); + + Ok(()) + } + } + + #[test] + fn smoke_test() { + let temp_dir = tempfile::tempdir().unwrap(); + + let artifact_path = temp_dir.path().join("test.json"); + + let artifact = ProgramArtifact { + noir_version: "0.0.0".to_string(), + hash: 27, + abi: noirc_abi::Abi::default(), + bytecode: Program { functions: vec![Circuit::default()], ..Program::default() }, + debug_symbols: ProgramDebugInfo { debug_infos: vec![DebugInfo::default()] }, + file_map: BTreeMap::default(), + names: vec!["main".to_string()], + }; + + // Write the artifact to a file + let artifact_file = std::fs::File::create(&artifact_path).unwrap(); + serde_json::to_writer(artifact_file, &artifact).unwrap(); + + let flamegraph_generator = TestFlamegraphGenerator::default(); + + super::run_with_generator(&artifact_path, &flamegraph_generator, temp_dir.path()) + .expect("should run without errors"); + + // Check that the output file was written to + let output_file = temp_dir.path().join("main_opcodes.svg"); + assert!(output_file.exists()); + } +} diff --git a/tooling/profiler/src/flamegraph.rs b/tooling/profiler/src/flamegraph.rs new file mode 100644 index 00000000000..6b5a06405b3 --- /dev/null +++ b/tooling/profiler/src/flamegraph.rs @@ -0,0 +1,301 @@ +use std::path::Path; +use std::{collections::BTreeMap, io::BufWriter}; + +use acir::circuit::{Opcode, OpcodeLocation}; +use color_eyre::eyre::{self}; +use fm::codespan_files::Files; +use inferno::flamegraph::{from_lines, Options, TextTruncateDirection}; +use noirc_errors::debug_info::DebugInfo; +use noirc_errors::reporter::line_and_column_from_span; +use noirc_errors::Location; + +use super::opcode_formatter::format_opcode; + +#[derive(Debug, Default)] +pub(crate) struct FoldedStackItem { + pub(crate) total_samples: usize, + pub(crate) nested_items: BTreeMap, +} + +pub(crate) trait FlamegraphGenerator { + #[allow(clippy::too_many_arguments)] + fn generate_flamegraph<'files, F>( + &self, + samples_per_opcode: Vec, + opcodes: Vec>, + debug_symbols: &DebugInfo, + files: &'files impl Files<'files, FileId = fm::FileId>, + artifact_name: &str, + function_name: &str, + output_path: &Path, + ) -> eyre::Result<()>; +} + +pub(crate) struct InfernoFlamegraphGenerator { + pub(crate) count_name: String, +} + +impl FlamegraphGenerator for InfernoFlamegraphGenerator { + fn generate_flamegraph<'files, F>( + &self, + samples_per_opcode: Vec, + opcodes: Vec>, + debug_symbols: &DebugInfo, + files: &'files impl Files<'files, FileId = fm::FileId>, + artifact_name: &str, + function_name: &str, + output_path: &Path, + ) -> eyre::Result<()> { + let folded_lines = + generate_folded_sorted_lines(samples_per_opcode, opcodes, debug_symbols, files); + + let flamegraph_file = std::fs::File::create(output_path)?; + let flamegraph_writer = BufWriter::new(flamegraph_file); + + let mut options = Options::default(); + options.hash = true; + options.deterministic = true; + options.title = format!("{}-{}", artifact_name, function_name); + options.frame_height = 24; + options.color_diffusion = true; + options.min_width = 0.0; + options.count_name = self.count_name.clone(); + options.text_truncate_direction = TextTruncateDirection::Right; + + from_lines( + &mut options, + folded_lines.iter().map(|as_string| as_string.as_str()), + flamegraph_writer, + )?; + + Ok(()) + } +} + +fn generate_folded_sorted_lines<'files, F>( + samples_per_opcode: Vec, + opcodes: Vec>, + debug_symbols: &DebugInfo, + files: &'files impl Files<'files, FileId = fm::FileId>, +) -> Vec { + // Create a nested hashmap with the stack items, folding the gates for all the callsites that are equal + let mut folded_stack_items = BTreeMap::new(); + + samples_per_opcode.into_iter().enumerate().for_each(|(opcode_index, gates)| { + let call_stack = debug_symbols.locations.get(&OpcodeLocation::Acir(opcode_index)); + let location_names = if let Some(call_stack) = call_stack { + call_stack + .iter() + .map(|location| location_to_callsite_label(*location, files)) + .chain(std::iter::once(format_opcode(&opcodes[opcode_index]))) + .collect::>() + } else { + vec!["unknown".to_string()] + }; + + add_locations_to_folded_stack_items(&mut folded_stack_items, location_names, gates); + }); + + to_folded_sorted_lines(&folded_stack_items, Default::default()) +} + +fn location_to_callsite_label<'files>( + location: Location, + files: &'files impl Files<'files, FileId = fm::FileId>, +) -> String { + let filename = + Path::new(&files.name(location.file).expect("should have a file path").to_string()) + .file_name() + .map(|os_str| os_str.to_string_lossy().to_string()) + .unwrap_or("invalid_path".to_string()); + let source = files.source(location.file).expect("should have a file source"); + + let code_slice = source + .as_ref() + .chars() + .skip(location.span.start() as usize) + .take(location.span.end() as usize - location.span.start() as usize) + .collect::(); + + // ";" is used for frame separation, and is not allowed by inferno + // Check code slice for ";" and replace it with 'GREEK QUESTION MARK' (U+037E) + let code_slice = code_slice.replace(';', "\u{037E}"); + + let (line, column) = line_and_column_from_span(source.as_ref(), &location.span); + + format!("{}:{}:{}::{}", filename, line, column, code_slice) +} + +fn add_locations_to_folded_stack_items( + stack_items: &mut BTreeMap, + locations: Vec, + gates: usize, +) { + let mut child_map = stack_items; + for (index, location) in locations.iter().enumerate() { + let current_item = child_map.entry(location.clone()).or_default(); + + child_map = &mut current_item.nested_items; + + if index == locations.len() - 1 { + current_item.total_samples += gates; + } + } +} + +/// Creates a vector of lines in the format that inferno expects from a nested hashmap of stack items +/// The lines have to be sorted in the following way, exploring the graph in a depth-first manner: +/// main 100 +/// main::foo 0 +/// main::foo::bar 200 +/// main::baz 27 +/// main::baz::qux 800 +fn to_folded_sorted_lines( + folded_stack_items: &BTreeMap, + parent_stacks: im::Vector, +) -> Vec { + let mut result_vector = Vec::with_capacity(folded_stack_items.len()); + + for (location, folded_stack_item) in folded_stack_items.iter() { + if folded_stack_item.total_samples > 0 { + let frame_list: Vec = + parent_stacks.iter().cloned().chain(std::iter::once(location.clone())).collect(); + let line: String = + format!("{} {}", frame_list.join(";"), folded_stack_item.total_samples); + + result_vector.push(line); + }; + + let mut new_parent_stacks = parent_stacks.clone(); + new_parent_stacks.push_back(location.clone()); + let child_lines = + to_folded_sorted_lines(&folded_stack_item.nested_items, new_parent_stacks); + + result_vector.extend(child_lines); + } + + result_vector +} + +#[cfg(test)] +mod tests { + use acir::{ + circuit::{opcodes::BlockId, Opcode, OpcodeLocation}, + native_types::Expression, + FieldElement, + }; + use fm::FileManager; + use noirc_errors::{debug_info::DebugInfo, Location, Span}; + use std::{collections::BTreeMap, path::Path}; + + use super::generate_folded_sorted_lines; + + fn find_spans_for(source: &str, needle: &str) -> Vec { + let mut spans = Vec::new(); + let mut start = 0; + while let Some(start_idx) = source[start..].find(needle) { + let start_idx = start + start_idx; + let end_idx = start_idx + needle.len(); + spans.push(Span::inclusive(start_idx as u32, end_idx as u32 - 1)); + start = end_idx; + } + spans + } + + #[test] + fn simple_test_case() { + let source_code = r##" + fn main() { + foo(); + bar(); + whatever(); + } + fn foo() { + baz(); + } + fn bar () { + whatever() + } + fn baz () { + whatever() + } + "##; + + let source_file_name = Path::new("main.nr"); + let temp_dir = tempfile::tempdir().unwrap(); + + let mut fm = FileManager::new(temp_dir.path()); + let file_id = fm.add_file_with_source(source_file_name, source_code.to_string()).unwrap(); + + let main_declaration_location = + Location::new(find_spans_for(source_code, "fn main()")[0], file_id); + let main_foo_call_location = + Location::new(find_spans_for(source_code, "foo()")[0], file_id); + let main_bar_call_location = + Location::new(find_spans_for(source_code, "bar()")[0], file_id); + let main_whatever_call_location = + Location::new(find_spans_for(source_code, "whatever()")[0], file_id); + let foo_baz_call_location = Location::new(find_spans_for(source_code, "baz()")[0], file_id); + let bar_whatever_call_location = + Location::new(find_spans_for(source_code, "whatever()")[1], file_id); + let baz_whatever_call_location = + Location::new(find_spans_for(source_code, "whatever()")[2], file_id); + + let mut opcode_locations = BTreeMap::>::new(); + // main::foo::baz::whatever + opcode_locations.insert( + OpcodeLocation::Acir(0), + vec![ + main_declaration_location, + main_foo_call_location, + foo_baz_call_location, + baz_whatever_call_location, + ], + ); + + // main::bar::whatever + opcode_locations.insert( + OpcodeLocation::Acir(1), + vec![main_declaration_location, main_bar_call_location, bar_whatever_call_location], + ); + // main::whatever + opcode_locations.insert( + OpcodeLocation::Acir(2), + vec![main_declaration_location, main_whatever_call_location], + ); + + let debug_info = DebugInfo::new( + opcode_locations, + BTreeMap::default(), + BTreeMap::default(), + BTreeMap::default(), + ); + + let samples_per_opcode = vec![10, 20, 30]; + + let expected_folded_sorted_lines = vec![ + "main.nr:2:9::fn main();main.nr:3:13::foo();main.nr:8:13::baz();main.nr:14:13::whatever();opcode::arithmetic 10".to_string(), + "main.nr:2:9::fn main();main.nr:4:13::bar();main.nr:11:13::whatever();opcode::arithmetic 20".to_string(), + "main.nr:2:9::fn main();main.nr:5:13::whatever();opcode::memory::init 30".to_string(), + ]; + + let opcodes: Vec> = vec![ + Opcode::AssertZero(Expression::default()), + Opcode::AssertZero(Expression::default()), + Opcode::MemoryInit { + block_id: BlockId(0), + init: vec![], + block_type: acir::circuit::opcodes::BlockType::Memory, + }, + ]; + + let actual_folded_sorted_lines = generate_folded_sorted_lines( + samples_per_opcode, + opcodes, + &debug_info, + fm.as_file_map(), + ); + + assert_eq!(expected_folded_sorted_lines, actual_folded_sorted_lines); + } +} diff --git a/tooling/profiler/src/fs.rs b/tooling/profiler/src/fs.rs new file mode 100644 index 00000000000..e8eec2cbb14 --- /dev/null +++ b/tooling/profiler/src/fs.rs @@ -0,0 +1,15 @@ +use std::path::Path; + +use color_eyre::eyre; +use noirc_artifacts::program::ProgramArtifact; + +pub(crate) fn read_program_from_file>( + circuit_path: P, +) -> eyre::Result { + let file_path = circuit_path.as_ref().with_extension("json"); + + let input_string = std::fs::read(file_path)?; + let program = serde_json::from_slice(&input_string)?; + + Ok(program) +} diff --git a/tooling/profiler/src/gates_provider.rs b/tooling/profiler/src/gates_provider.rs new file mode 100644 index 00000000000..caed2666426 --- /dev/null +++ b/tooling/profiler/src/gates_provider.rs @@ -0,0 +1,37 @@ +use std::path::{Path, PathBuf}; +use std::process::Command; + +use color_eyre::eyre::{self}; +use serde::{Deserialize, Serialize}; + +pub(crate) trait GatesProvider { + fn get_gates(&self, artifact_path: &Path) -> eyre::Result; +} + +pub(crate) struct BackendGatesProvider { + pub(crate) backend_path: PathBuf, +} + +impl GatesProvider for BackendGatesProvider { + fn get_gates(&self, artifact_path: &Path) -> eyre::Result { + let backend_gates_response = + Command::new(&self.backend_path).arg("gates").arg("-b").arg(artifact_path).output()?; + + // Parse the backend gates command stdout as json + let backend_gates_response: BackendGatesResponse = + serde_json::from_slice(&backend_gates_response.stdout)?; + Ok(backend_gates_response) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct BackendGatesReport { + pub(crate) acir_opcodes: usize, + pub(crate) circuit_size: usize, + pub(crate) gates_per_opcode: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct BackendGatesResponse { + pub(crate) functions: Vec, +} diff --git a/tooling/profiler/src/main.rs b/tooling/profiler/src/main.rs new file mode 100644 index 00000000000..215feb0a4e7 --- /dev/null +++ b/tooling/profiler/src/main.rs @@ -0,0 +1,39 @@ +#![forbid(unsafe_code)] +#![warn(unreachable_pub)] +#![warn(clippy::semicolon_if_nothing_returned)] +#![cfg_attr(not(test), warn(unused_crate_dependencies, unused_extern_crates))] + +mod cli; +mod flamegraph; +mod fs; +mod gates_provider; +mod opcode_formatter; + +use std::env; + +use tracing_appender::rolling; +use tracing_subscriber::{fmt::format::FmtSpan, EnvFilter}; + +fn main() { + // Setup tracing + if let Ok(log_dir) = env::var("PROFILER_LOG_DIR") { + let debug_file = rolling::daily(log_dir, "profiler-log"); + tracing_subscriber::fmt() + .with_span_events(FmtSpan::ACTIVE) + .with_writer(debug_file) + .with_ansi(false) + .with_env_filter(EnvFilter::from_default_env()) + .init(); + } else { + tracing_subscriber::fmt() + .with_span_events(FmtSpan::ACTIVE) + .with_ansi(true) + .with_env_filter(EnvFilter::from_env("NOIR_LOG")) + .init(); + } + + if let Err(report) = cli::start_cli() { + eprintln!("{report}"); + std::process::exit(1); + } +} diff --git a/tooling/profiler/src/opcode_formatter.rs b/tooling/profiler/src/opcode_formatter.rs new file mode 100644 index 00000000000..a33de42a0ff --- /dev/null +++ b/tooling/profiler/src/opcode_formatter.rs @@ -0,0 +1,53 @@ +use acir::circuit::{directives::Directive, opcodes::BlackBoxFuncCall, Opcode}; + +fn format_blackbox_function(call: &BlackBoxFuncCall) -> String { + match call { + BlackBoxFuncCall::AES128Encrypt { .. } => "aes128_encrypt".to_string(), + BlackBoxFuncCall::AND { .. } => "and".to_string(), + BlackBoxFuncCall::XOR { .. } => "xor".to_string(), + BlackBoxFuncCall::RANGE { .. } => "range".to_string(), + BlackBoxFuncCall::SHA256 { .. } => "sha256".to_string(), + BlackBoxFuncCall::Blake2s { .. } => "blake2s".to_string(), + BlackBoxFuncCall::Blake3 { .. } => "blake3".to_string(), + BlackBoxFuncCall::SchnorrVerify { .. } => "schnorr_verify".to_string(), + BlackBoxFuncCall::PedersenCommitment { .. } => "pedersen_commitment".to_string(), + BlackBoxFuncCall::PedersenHash { .. } => "pedersen_hash".to_string(), + BlackBoxFuncCall::EcdsaSecp256k1 { .. } => "ecdsa_secp256k1".to_string(), + BlackBoxFuncCall::EcdsaSecp256r1 { .. } => "ecdsa_secp256r1".to_string(), + BlackBoxFuncCall::MultiScalarMul { .. } => "multi_scalar_mul".to_string(), + BlackBoxFuncCall::EmbeddedCurveAdd { .. } => "embedded_curve_add".to_string(), + BlackBoxFuncCall::Keccak256 { .. } => "keccak256".to_string(), + BlackBoxFuncCall::Keccakf1600 { .. } => "keccakf1600".to_string(), + BlackBoxFuncCall::RecursiveAggregation { .. } => "recursive_aggregation".to_string(), + BlackBoxFuncCall::BigIntAdd { .. } => "big_int_add".to_string(), + BlackBoxFuncCall::BigIntSub { .. } => "big_int_sub".to_string(), + BlackBoxFuncCall::BigIntMul { .. } => "big_int_mul".to_string(), + BlackBoxFuncCall::BigIntDiv { .. } => "big_int_div".to_string(), + BlackBoxFuncCall::BigIntFromLeBytes { .. } => "big_int_from_le_bytes".to_string(), + BlackBoxFuncCall::BigIntToLeBytes { .. } => "big_int_to_le_bytes".to_string(), + BlackBoxFuncCall::Poseidon2Permutation { .. } => "poseidon2_permutation".to_string(), + BlackBoxFuncCall::Sha256Compression { .. } => "sha256_compression".to_string(), + } +} + +fn format_directive_kind(directive: &Directive) -> String { + match directive { + Directive::ToLeRadix { .. } => "to_le_radix".to_string(), + } +} + +fn format_opcode_kind(opcode: &Opcode) -> String { + match opcode { + Opcode::AssertZero(_) => "arithmetic".to_string(), + Opcode::BlackBoxFuncCall(call) => format!("blackbox::{}", format_blackbox_function(call)), + Opcode::MemoryOp { .. } => "memory::op".to_string(), + Opcode::MemoryInit { .. } => "memory::init".to_string(), + Opcode::Directive(directive) => format!("directive::{}", format_directive_kind(directive)), + Opcode::BrilligCall { .. } => "brillig_call".to_string(), + Opcode::Call { .. } => "acir_call".to_string(), + } +} + +pub(crate) fn format_opcode(opcode: &Opcode) -> String { + format!("opcode::{}", format_opcode_kind(opcode)) +} diff --git a/yarn.lock b/yarn.lock index a947ec56aba..dc033a7344f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -221,9 +221,9 @@ __metadata: languageName: node linkType: hard -"@aztec/bb.js@npm:0.41.0": - version: 0.41.0 - resolution: "@aztec/bb.js@npm:0.41.0" +"@aztec/bb.js@npm:0.46.1": + version: 0.46.1 + resolution: "@aztec/bb.js@npm:0.46.1" dependencies: comlink: ^4.4.1 commander: ^10.0.1 @@ -231,7 +231,7 @@ __metadata: tslib: ^2.4.0 bin: bb.js: dest/node/main.js - checksum: e5e0095eaff3de45726366726337b131bb6ff7cf2cb53be705572c7d6715dae4c948bf86a03cfad68bc98c0c2d83e64cbe3723cc72260c8dbfa262af8cb81f9b + checksum: 9475388f994e430ab3282a2c9769cd116f334358049955ed520f467d1abec8237bfeae7fa2fed9cd292f24c09c466e32e2af2d0a5cec2d10cc0c727728d96b0d languageName: node linkType: hard @@ -4396,7 +4396,7 @@ __metadata: version: 0.0.0-use.local resolution: "@noir-lang/backend_barretenberg@workspace:tooling/noir_js_backend_barretenberg" dependencies: - "@aztec/bb.js": 0.41.0 + "@aztec/bb.js": 0.46.1 "@noir-lang/types": "workspace:*" "@types/node": ^20.6.2 "@types/prettier": ^3