diff --git a/.aztec-sync-commit b/.aztec-sync-commit index 0e17b00d0d2..18e53f2bd3b 100644 --- a/.aztec-sync-commit +++ b/.aztec-sync-commit @@ -1 +1 @@ -ed815a3713fc311056a8bd0a616945f12d9be2a8 +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 d2afe025c69..1df84a80bc7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1542,7 +1542,6 @@ dependencies = [ "codespan-reporting", "iter-extended", "serde", - "tempfile", ] [[package]] @@ -2748,13 +2747,11 @@ version = "0.31.0" dependencies = [ "acir", "clap", - "codespan-reporting", "color-eyre", "const_format", "fm", "im", "inferno", - "nargo", "noirc_abi", "noirc_artifacts", "noirc_driver", @@ -2896,6 +2893,7 @@ dependencies = [ "noirc_errors", "noirc_frontend", "num-bigint", + "proptest", "serde", "thiserror", "tracing", @@ -2920,6 +2918,7 @@ dependencies = [ "num-bigint", "num-traits", "petgraph", + "rangemap", "regex", "rustc-hash", "serde", @@ -2928,7 +2927,6 @@ dependencies = [ "smol_str", "strum", "strum_macros", - "tempfile", "thiserror", "tracing", ] @@ -3568,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/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/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 b8be81fcdef..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,124 +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, @@ -144,7 +181,7 @@ pub enum BlackBoxFuncCall { output: u32, }, BigIntFromLeBytes { - inputs: Vec, + inputs: Vec>, modulus: Vec, output: u32, }, @@ -156,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) @@ -172,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, @@ -215,7 +252,7 @@ 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, .. } @@ -240,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 @@ -256,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); @@ -364,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 @@ -373,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 += ", "; @@ -385,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 } @@ -421,7 +451,7 @@ 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 { .. } => { @@ -454,13 +484,16 @@ impl std::fmt::Display for BlackBoxFuncCall { } } -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, { @@ -469,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)) } @@ -490,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 84a9aa719f2..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,10 +91,10 @@ 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) @@ -102,18 +102,15 @@ fn multi_scalar_mul_circuit() { #[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; @@ -137,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/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 0c65759ebcd..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, }; @@ -11,7 +11,7 @@ use self::{ }; 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; @@ -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/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/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/brillig_vm/src/black_box.rs b/acvm-repo/brillig_vm/src/black_box.rs index 36d045efabf..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 } => { @@ -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, @@ -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 01f45bf653c..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> { diff --git a/aztec_macros/src/transforms/compute_note_hash_and_optionally_a_nullifier.rs b/aztec_macros/src/transforms/compute_note_hash_and_optionally_a_nullifier.rs index 30c0f63a2d4..40fde39a06f 100644 --- a/aztec_macros/src/transforms/compute_note_hash_and_optionally_a_nullifier.rs +++ b/aztec_macros/src/transforms/compute_note_hash_and_optionally_a_nullifier.rs @@ -176,7 +176,7 @@ fn generate_compute_note_hash_and_optionally_a_nullifier_source( format!( " unconstrained fn compute_note_hash_and_optionally_a_nullifier( - contract_address: dep::aztec::protocol_types::address::AztecAddress, + contract_address: aztec::protocol_types::address::AztecAddress, nonce: Field, storage_slot: Field, note_type_id: Field, @@ -194,7 +194,7 @@ fn generate_compute_note_hash_and_optionally_a_nullifier_source( let if_statements: Vec = note_types.iter().map(|note_type| format!( "if (note_type_id == {0}::get_note_type_id()) {{ - dep::aztec::note::utils::compute_note_hash_and_optionally_a_nullifier({0}::deserialize_content, note_header, compute_nullifier, serialized_note) + aztec::note::utils::compute_note_hash_and_optionally_a_nullifier({0}::deserialize_content, note_header, compute_nullifier, serialized_note) }}" , note_type)).collect(); @@ -208,14 +208,14 @@ fn generate_compute_note_hash_and_optionally_a_nullifier_source( format!( " unconstrained fn compute_note_hash_and_optionally_a_nullifier( - contract_address: dep::aztec::protocol_types::address::AztecAddress, + contract_address: aztec::protocol_types::address::AztecAddress, nonce: Field, storage_slot: Field, note_type_id: Field, compute_nullifier: bool, serialized_note: [Field; {}], ) -> pub [Field; 4] {{ - let note_header = dep::aztec::prelude::NoteHeader::new(contract_address, nonce, storage_slot); + let note_header = aztec::prelude::NoteHeader::new(contract_address, nonce, storage_slot); {} }}", diff --git a/aztec_macros/src/transforms/contract_interface.rs b/aztec_macros/src/transforms/contract_interface.rs index ed451fdd998..e79cee66407 100644 --- a/aztec_macros/src/transforms/contract_interface.rs +++ b/aztec_macros/src/transforms/contract_interface.rs @@ -30,12 +30,20 @@ use crate::utils::{ // for i in 0..third_arg.len() { // args_acc = args_acc.append(third_arg[i].serialize().as_slice()); // } -// let args_hash = dep::aztec::hash::hash_args(args_acc); -// assert(args_hash == dep::aztec::oracle::arguments::pack_arguments(args_acc)); +// let args_hash = aztec::hash::hash_args(args_acc); +// assert(args_hash == aztec::oracle::arguments::pack_arguments(args_acc)); // 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() // } // } // @@ -132,73 +140,48 @@ pub fn stub_function(aztec_visibility: &str, func: &NoirFunction, is_static_call format!("{}, {}>", return_type_hint, arg_types) }; - let fn_body = if aztec_visibility != "Public" { - let args_hash = if !parameters.is_empty() { - format!( - "let mut args_acc: [Field] = &[]; - {} - let args_hash = dep::aztec::hash::hash_args(args_acc); - assert(args_hash == dep::aztec::oracle::arguments::pack_arguments(args_acc));", - call_args - ) + 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 mut args_acc: [Field] = &[]; - let args_hash = 0; - " - .to_string() - }; - - format!( - "{} - let selector = {}; - dep::aztec::context::{}{}{}CallInterface {{ - target_contract: self.target_contract, - selector, - name: \"{}\", - args_hash, - args: args_acc, - original: {}, - is_static: {} - }}", - args_hash, - fn_selector, - aztec_visibility, - is_static, - is_void, - fn_name, - original, - is_static_call - ) + "" + } + ); + + 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 - ); - format!( - "{} + "" + }; + + let fn_body = format!( + "{} let selector = {}; dep::aztec::context::{}{}{}CallInterface {{ target_contract: self.target_contract, selector, name: \"{}\", + {} args: args_acc, - gas_opts: dep::aztec::context::gas::GasOpts::default(), original: {}, - is_static: {} + is_static: {}, + {} }}", - args, - fn_selector, - aztec_visibility, - is_static, - is_void, - fn_name, - original, - is_static_call - ) - }; + 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<{},{} {{ @@ -234,14 +217,14 @@ pub fn generate_contract_interface( let contract_interface = format!( " struct {0} {{ - target_contract: dep::aztec::protocol_types::address::AztecAddress + target_contract: aztec::protocol_types::address::AztecAddress }} impl {0} {{ {1} pub fn at( - target_contract: dep::aztec::protocol_types::address::AztecAddress + target_contract: aztec::protocol_types::address::AztecAddress ) -> Self {{ Self {{ target_contract }} }} @@ -255,7 +238,7 @@ pub fn generate_contract_interface( #[contract_library_method] pub fn at( - target_contract: dep::aztec::protocol_types::address::AztecAddress + target_contract: aztec::protocol_types::address::AztecAddress ) -> {0} {{ {0} {{ target_contract }} }} diff --git a/aztec_macros/src/transforms/events.rs b/aztec_macros/src/transforms/events.rs index 05861b96eb4..ecfca40189d 100644 --- a/aztec_macros/src/transforms/events.rs +++ b/aztec_macros/src/transforms/events.rs @@ -117,8 +117,19 @@ fn generate_trait_impl_serialize( event_len: u32, event_fields: &[(String, String)], ) -> Result { - let field_names = - event_fields.iter().map(|field| format!("self.{}", field.0)).collect::>(); + 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!( @@ -154,7 +165,16 @@ fn generate_trait_impl_deserialize( let field_names: Vec = event_fields .iter() .enumerate() - .map(|(index, field)| format!("{}: fields[{}]", field.0, index)) + .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(","); diff --git a/aztec_macros/src/transforms/note_interface.rs b/aztec_macros/src/transforms/note_interface.rs index c2dfb3d86d4..49525fc2ae1 100644 --- a/aztec_macros/src/transforms/note_interface.rs +++ b/aztec_macros/src/transforms/note_interface.rs @@ -272,7 +272,7 @@ fn generate_note_get_header( ) -> Result { let function_source = format!( " - fn get_header(note: {}) -> dep::aztec::note::note_header::NoteHeader {{ + fn get_header(note: {}) -> aztec::note::note_header::NoteHeader {{ note.{} }} ", @@ -303,7 +303,7 @@ fn generate_note_set_header( ) -> Result { let function_source = format!( " - fn set_header(self: &mut {}, header: dep::aztec::note::note_header::NoteHeader) {{ + fn set_header(self: &mut {}, header: aztec::note::note_header::NoteHeader) {{ self.{} = header; }} ", @@ -493,7 +493,7 @@ fn generate_note_properties_fn( // Automatically generate the method to compute the note's content hash as: // fn compute_note_content_hash(self: NoteType) -> Field { -// dep::aztec::hash::pedersen_hash(self.serialize_content(), dep::aztec::protocol_types::constants::GENERATOR_INDEX__NOTE_CONTENT_HASH) +// aztec::hash::pedersen_hash(self.serialize_content(), aztec::protocol_types::constants::GENERATOR_INDEX__NOTE_CONTENT_HASH) // } // fn generate_compute_note_content_hash( @@ -503,7 +503,7 @@ fn generate_compute_note_content_hash( let function_source = format!( " fn compute_note_content_hash(self: {}) -> Field {{ - dep::aztec::hash::pedersen_hash(self.serialize_content(), dep::aztec::protocol_types::constants::GENERATOR_INDEX__NOTE_CONTENT_HASH) + aztec::hash::pedersen_hash(self.serialize_content(), aztec::protocol_types::constants::GENERATOR_INDEX__NOTE_CONTENT_HASH) }} ", note_type @@ -592,7 +592,7 @@ fn generate_note_properties_fn_source( .filter_map(|(index, (field_name, _))| { if field_name != note_header_field_name { Some(format!( - "{}: dep::aztec::note::note_getter_options::PropertySelector {{ index: {}, offset: 0, length: 32 }}", + "{}: aztec::note::note_getter_options::PropertySelector {{ index: {}, offset: 0, length: 32 }}", field_name, index )) diff --git a/aztec_macros/src/utils/ast_utils.rs b/aztec_macros/src/utils/ast_utils.rs index 48b3b25747b..4467c4bca4b 100644 --- a/aztec_macros/src/utils/ast_utils.rs +++ b/aztec_macros/src/utils/ast_utils.rs @@ -161,7 +161,7 @@ macro_rules! chained_dep { ( $base:expr $(, $tail:expr)* ) => { { let mut base_path = ident_path($base); - base_path.kind = PathKind::Dep; + base_path.kind = PathKind::Plain; $( base_path.segments.push(ident($tail)); )* 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 4ba9b85f967..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 diff --git a/compiler/noirc_driver/tests/stdlib_warnings.rs b/compiler/noirc_driver/tests/stdlib_warnings.rs index 47ce893d202..0e098d0d087 100644 --- a/compiler/noirc_driver/tests/stdlib_warnings.rs +++ b/compiler/noirc_driver/tests/stdlib_warnings.rs @@ -25,7 +25,7 @@ fn stdlib_does_not_produce_constant_warnings() -> Result<(), ErrorsAndWarnings> let root_crate_id = prepare_crate(&mut context, file_name); let ((), warnings) = - noirc_driver::check_crate(&mut context, root_crate_id, false, false, false)?; + noirc_driver::check_crate(&mut context, root_crate_id, false, false, false, None)?; assert_eq!(warnings, Vec::new(), "stdlib is producing {} warnings", warnings.len()); diff --git a/compiler/noirc_errors/src/reporter.rs b/compiler/noirc_errors/src/reporter.rs index d817b48691f..3ce0f268715 100644 --- a/compiler/noirc_errors/src/reporter.rs +++ b/compiler/noirc_errors/src/reporter.rs @@ -17,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 @@ -36,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, } } @@ -81,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 { @@ -120,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 = @@ -170,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(), }; diff --git a/compiler/noirc_evaluator/Cargo.toml b/compiler/noirc_evaluator/Cargo.toml index 72a52b43741..52f154fd1f3 100644 --- a/compiler/noirc_evaluator/Cargo.toml +++ b/compiler/noirc_evaluator/Cargo.toml @@ -21,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_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/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 0879056ad36..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")] @@ -56,6 +59,7 @@ pub enum RuntimeError { #[derive(Debug, Clone, Serialize, Deserialize)] pub enum SsaReport { Warning(InternalWarning), + Bug(InternalBug), } impl From for FileDiagnostic { @@ -78,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) + } } } } @@ -90,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")] @@ -120,7 +143,6 @@ 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, .. } diff --git a/compiler/noirc_evaluator/src/ssa.rs b/compiler/noirc_evaluator/src/ssa.rs index 0e0c3f1e631..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. @@ -49,10 +52,10 @@ pub mod ssa_gen; pub(crate) fn optimize_into_acir( program: Program, options: &SsaEvaluatorOptions, -) -> Result { +) -> Result { let ssa_gen_span = span!(Level::TRACE, "ssa_generation"); let ssa_gen_span_guard = ssa_gen_span.enter(); - let ssa = SsaBuilder::new( + let mut ssa = SsaBuilder::new( program, options.enable_ssa_logging, options.force_brillig_output, @@ -89,13 +92,15 @@ pub(crate) fn optimize_into_acir( .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", options.print_codegen_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 @@ -149,6 +154,10 @@ impl SsaProgramArtifact { } self.names.push(circuit_artifact.name); } + + fn add_warnings(&mut self, mut warnings: Vec) { + self.warnings.append(&mut warnings); + } } pub struct SsaEvaluatorOptions { @@ -179,7 +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, options)?; + let ArtifactsAndWarnings((generated_acirs, generated_brillig, error_types), ssa_level_warnings) = + optimize_into_acir(program, options)?; assert_eq!( generated_acirs.len(), func_sigs.len(), @@ -187,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 928a7b105ea..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); } @@ -1896,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 bcccfac8950..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, @@ -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); 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/instruction.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs index f854e8e0693..8cbae732ef9 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs @@ -288,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 @@ -317,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) + } } } @@ -372,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/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/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/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 f9a3c9a55eb..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; } @@ -172,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/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/statement.rs b/compiler/noirc_frontend/src/ast/statement.rs index 48e34ad7fc9..3e6a140ff93 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/elaborator/expressions.rs b/compiler/noirc_frontend/src/elaborator/expressions.rs index 7d304990dd8..5cda8787241 100644 --- a/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -9,27 +9,27 @@ 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, + 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, - Kind, QuotedType, Shared, StructType, Type, + QuotedType, Shared, StructType, Type, }; use super::Elaborator; @@ -39,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), @@ -51,23 +51,7 @@ 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| { - // All type expressions should resolve to a `Type::Constant` - if generic.is_type_expression() { - self.resolve_type_inner( - generic, - &Kind::Numeric(Box::new(Type::default_int_type())), - ) - } else { - 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), @@ -243,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) { @@ -342,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. @@ -367,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 }; @@ -403,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); @@ -431,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)) } @@ -449,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); @@ -477,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)); } @@ -498,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) } @@ -536,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 } @@ -556,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) { @@ -663,11 +695,26 @@ impl<'context> Elaborator<'context> { } 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.crate_id); + + 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( @@ -738,9 +785,9 @@ impl<'context> Elaborator<'context> { } }; - let mut interpreter = - Interpreter::new(self.interner, &mut self.comptime_scopes, self.crate_id); - + 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(); @@ -750,17 +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 bindings = interpreter.interner.get_instantiation_bindings(func).clone(); - let result = interpreter.call_function(function, comptime_args, bindings, 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 af6f4cdb42f..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; @@ -196,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, @@ -207,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(), @@ -232,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 ae8237706cc..e1d104c4971 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -1,5 +1,6 @@ use std::{ collections::{BTreeMap, BTreeSet}, + fmt::Display, rc::Rc, }; @@ -30,7 +31,8 @@ use crate::{ SecondaryAttribute, StructId, }, node_interner::{ - DefinitionId, DefinitionKind, DependencyId, ExprId, FuncId, GlobalId, TraitId, TypeAliasId, + DefinitionId, DefinitionKind, DependencyId, ExprId, FuncId, GlobalId, ReferenceId, TraitId, + TypeAliasId, }, parser::TopLevelStatement, Shared, Type, TypeBindings, TypeVariable, @@ -136,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. @@ -160,14 +158,35 @@ 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(), @@ -184,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(), } } @@ -197,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 @@ -318,14 +337,13 @@ impl<'context> Elaborator<'context> { FunctionBody::Resolving => return, }; - let old_function = std::mem::replace(&mut self.current_function, Some(id)); - 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(); + self.function_context.push(FunctionContext::default()); // Introduce all numeric generics into scope for generic in &func_meta.all_generics { @@ -367,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 { @@ -417,12 +412,41 @@ impl<'context> Elaborator<'context> { meta.function_body = FunctionBody::Resolved; self.trait_bounds.clear(); - self.type_variables.clear(); self.interner.update_fn(id, hir_func); - 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)` /// @@ -521,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); @@ -596,8 +620,6 @@ impl<'context> Elaborator<'context> { func_id: FuncId, is_trait_function: bool, ) { - self.current_function = Some(func_id); - 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 @@ -690,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.ident().0.contents)) - .map(|ResolvedGeneric { name, type_var, .. }| (name.clone(), type_var.clone())) + .filter_map(|generic| self.find_generic(&generic.ident().0.contents).cloned()) .collect(); let statements = std::mem::take(&mut func.def.body.statements); @@ -719,7 +740,6 @@ impl<'context> Elaborator<'context> { }; self.interner.push_fn_meta(meta, func_id); - self.current_function = None; self.scopes.end_function(); self.current_item = None; } @@ -1181,6 +1201,7 @@ impl<'context> Elaborator<'context> { let span = typ.struct_def.span; 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); @@ -1207,6 +1228,11 @@ impl<'context> Elaborator<'context> { } }); + 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); } @@ -1266,22 +1292,22 @@ impl<'context> Elaborator<'context> { let DefinitionKind::Function(function) = definition.kind else { return Err((ResolverError::NonFunctionInAnnotation { span }.into(), self.file)); }; - let mut interpreter = - Interpreter::new(self.interner, &mut self.comptime_scopes, self.crate_id); - let location = Location::new(span, self.file); - let arguments = vec![(Value::TypeDefinition(struct_id), location)]; + 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 item = value - .into_top_level_item(location) + let items = value + .into_top_level_items(location) .map_err(|error| error.into_compilation_error_pair())?; - self.add_item(item, generated_items, location); + self.add_items(items, generated_items, location); } Ok(()) @@ -1339,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; @@ -1357,9 +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, self.crate_id); + 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()); @@ -1368,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( @@ -1401,7 +1429,8 @@ impl<'context> Elaborator<'context> { self.file = trait_impl.file_id; self.local_module = trait_impl.module_id; - trait_impl.trait_id = self.resolve_trait_by_path(trait_impl.trait_path.clone()); + 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); @@ -1439,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); + } } } @@ -1453,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) { @@ -1494,87 +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_item( + fn add_items( &mut self, - item: TopLevelStatement, + items: Vec, generated_items: &mut CollectedItems, location: Location, ) { - 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, - ); + 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.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); + 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()); } } - // 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 4f04f5c523c..7c920230b9d 100644 --- a/compiler/noirc_frontend/src/elaborator/patterns.rs +++ b/compiler/noirc_frontend/src/elaborator/patterns.rs @@ -3,8 +3,9 @@ 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}, @@ -14,7 +15,9 @@ use crate::{ stmt::HirPattern, }, macros_api::{HirExpression, Ident, Path, Pattern}, - node_interner::{DefinitionId, DefinitionKind, ExprId, GlobalId, TraitImplKind}, + node_interner::{ + DefinitionId, DefinitionKind, ExprId, FuncId, GlobalId, ReferenceId, TraitImplKind, + }, Shared, StructType, Type, TypeBindings, }; @@ -157,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 @@ -196,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) } @@ -279,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, }); @@ -323,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. @@ -344,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 }; @@ -387,18 +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) } @@ -414,14 +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 { - 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) { @@ -430,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 @@ -444,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); } } } @@ -516,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); } } } @@ -533,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); } } @@ -575,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 0d67c9ed3e3..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,9 +205,8 @@ impl<'context> Elaborator<'context> { } fn elaborate_jump(&mut self, is_break: bool, span: noirc_errors::Span) -> (HirStatement, Type) { - let in_constrained_function = self - .current_function - .map_or(true, |func_id| !self.interner.function_modifiers(&func_id).is_unconstrained); + let in_constrained_function = self.in_constrained_function(); + if in_constrained_function { self.push_err(ResolverError::JumpInConstrainedFn { is_break, span }); } @@ -257,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 } => { @@ -378,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)); } } @@ -433,12 +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.crate_id); + 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 77ac8e476f8..4cd20820c56 100644 --- a/compiler/noirc_frontend/src/elaborator/traits.rs +++ b/compiler/noirc_frontend/src/elaborator/traits.rs @@ -47,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); } } } diff --git a/compiler/noirc_frontend/src/elaborator/types.rs b/compiler/noirc_frontend/src/elaborator/types.rs index 63cab40f9d3..4e9b3620760 100644 --- a/compiler/noirc_frontend/src/elaborator/types.rs +++ b/compiler/noirc_frontend/src/elaborator/types.rs @@ -16,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::{ @@ -30,7 +30,10 @@ use crate::{ HirExpression, HirLiteral, HirStatement, Path, PathKind, SecondaryAttribute, Signedness, UnaryOp, UnresolvedType, UnresolvedTypeData, }, - node_interner::{DefinitionKind, ExprId, GlobalId, TraitId, TraitImplKind, TraitMethodId}, + node_interner::{ + DefinitionKind, DependencyId, ExprId, GlobalId, ReferenceId, TraitId, TraitImplKind, + TraitMethodId, + }, Generics, Kind, ResolvedGeneric, Type, TypeBinding, TypeVariable, TypeVariableKind, }; @@ -55,6 +58,12 @@ impl<'context> Elaborator<'context> { 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, @@ -144,13 +153,33 @@ impl<'context> Elaborator<'context> { 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); + } + _ => (), } } @@ -340,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, @@ -615,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 } @@ -623,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 } @@ -636,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) { @@ -696,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); @@ -1039,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, @@ -1106,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); @@ -1160,9 +1223,11 @@ impl<'context> Elaborator<'context> { None } Type::NamedGeneric(_, _, _) => { - let func_meta = self.interner.function_meta( - &self.current_function.expect("unexpected method outside a function"), - ); + 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 { @@ -1233,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 { @@ -1327,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); @@ -1410,26 +1475,10 @@ 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)); } } } @@ -1557,4 +1606,20 @@ impl<'context> Elaborator<'context> { } } } + + /// 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/hir/comptime/errors.rs b/compiler/noirc_frontend/src/hir/comptime/errors.rs index d2c7acee2a3..0472b0040e5 100644 --- a/compiler/noirc_frontend/src/hir/comptime/errors.rs +++ b/compiler/noirc_frontend/src/hir/comptime/errors.rs @@ -1,7 +1,11 @@ +use std::fmt::Display; use std::rc::Rc; use crate::{ - hir::def_collector::dc_crate::CompilationError, parser::ParserError, token::Tokens, Type, + hir::{def_collector::dc_crate::CompilationError, type_check::NoMatchingImplFoundError}, + parser::ParserError, + token::Tokens, + Type, }; use acvm::{acir::AcirField, FieldElement}; use fm::FileId; @@ -16,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 }, @@ -41,9 +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 }, @@ -78,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, .. } @@ -106,16 +116,36 @@ impl InterpreterError { | 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 { @@ -143,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(), @@ -273,6 +308,7 @@ 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(" "); @@ -324,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 d2b98569bbb..02714f77605 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs @@ -1,6 +1,7 @@ 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; @@ -8,13 +9,18 @@ use rustc_hash::FxHashMap as HashMap; use crate::ast::{BinaryOpKind, FunctionKind, IntegerBitSize, Signedness}; use crate::graph::CrateId; -use crate::monomorphization::{perform_instantiation_bindings, undo_instantiation_bindings}; +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, }, @@ -23,7 +29,7 @@ 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, }; @@ -46,6 +52,10 @@ pub struct Interpreter<'interner> { 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, } @@ -55,8 +65,17 @@ impl<'a> Interpreter<'a> { 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, crate_id, in_loop: false } + Self { + interner, + scopes, + crate_id, + debug_comptime_in_file, + debug_comptime_evaluations, + in_loop: false, + } } pub(crate) fn call_function( @@ -66,8 +85,12 @@ impl<'a> Interpreter<'a> { 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 } @@ -289,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 { @@ -304,12 +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 }) + 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 @@ -345,7 +367,17 @@ 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) => { @@ -359,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())?; } @@ -556,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)), @@ -573,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)), @@ -588,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); @@ -603,13 +649,8 @@ impl<'a> Interpreter<'a> { let lhs = self.evaluate(infix.lhs)?; let rhs = self.evaluate(infix.rhs)?; - // TODO: Need to account for operator overloading - // See https://github.com/noir-lang/noir/issues/4925 if self.interner.get_selected_impl_for_expression(id).is_some() { - return Err(InterpreterError::Unimplemented { - item: "Operator overloading in the interpreter".to_string(), - location: infix.operator.location, - }); + return self.evaluate_overloaded_infix(infix, lhs, rhs, id); } use InterpreterError::InvalidValuesForBinary; @@ -854,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)?; @@ -882,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, @@ -1209,8 +1311,15 @@ 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)) @@ -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 1c0c4e6f274..399d9905269 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -3,8 +3,9 @@ use std::rc::Rc; use noirc_errors::Location; use crate::{ + ast::IntegerBitSize, hir::comptime::{errors::IResult, InterpreterError, Value}, - macros_api::NodeInterner, + macros_api::{NodeInterner, Signedness}, token::{SpannedToken, Token, Tokens}, QuotedType, Type, }; @@ -16,12 +17,18 @@ pub(super) fn call_builtin( location: Location, ) -> IResult { match name { - "array_len" => array_len(&arguments), - "as_slice" => as_slice(arguments), - "slice_push_back" => slice_push_back(arguments), - "type_def_as_type" => type_def_as_type(interner, arguments), - "type_def_generics" => type_def_generics(interner, arguments), - "type_def_fields" => type_def_fields(interner, arguments), + "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 }) @@ -29,53 +36,114 @@ pub(super) fn call_builtin( } } -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 { +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 }) + } } } -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(mut arguments: Vec<(Value, Location)>) -> IResult { - assert_eq!(arguments.len(), 2, "ICE: `slice_push_back` should only receive two arguments"); +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 (slice, _) = arguments.pop().unwrap(); - match slice { - Value::Slice(mut values, typ) => { - values.push_back(element); - Ok(Value::Slice(values, typ)) - } - // Type checking should prevent this branch being taken. - _ => unreachable!("ICE: `slice_push_back` expects a slice as its first argument"), - } + 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 type_def_as_type( +fn struct_def_as_type( interner: &NodeInterner, mut arguments: Vec<(Value, Location)>, + location: Location, ) -> IResult { - assert_eq!(arguments.len(), 1, "ICE: `generics` should only receive a single argument"); - let (type_def, span) = match arguments.pop() { - Some((Value::TypeDefinition(id), location)) => (id, location.span), - other => { - unreachable!("ICE: `as_type` expected a `TypeDefinition` argument, found {other:?}") + 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(type_def); + let struct_def = interner.get_struct(struct_def); let struct_def = struct_def.borrow(); let make_token = |name| SpannedToken::new(Token::Ident(name), span); @@ -92,49 +160,51 @@ fn type_def_as_type( } /// fn generics(self) -> [Quoted] -fn type_def_generics( +fn struct_def_generics( interner: &NodeInterner, mut arguments: Vec<(Value, Location)>, + location: Location, ) -> IResult { - assert_eq!(arguments.len(), 1, "ICE: `generics` should only receive a single argument"); - let (type_def, span) = match arguments.pop() { - Some((Value::TypeDefinition(id), location)) => (id, location.span), - other => { - unreachable!("ICE: `as_type` expected a `TypeDefinition` argument, found {other:?}") + 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(type_def); + let struct_def = interner.get_struct(struct_def); + let struct_def = struct_def.borrow(); - let generics = struct_def - .borrow() - .generics - .iter() - .map(|generic| { - let name = SpannedToken::new(Token::Ident(generic.type_var.borrow().to_string()), span); - Value::Code(Rc::new(Tokens(vec![name]))) - }) - .collect(); + 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, typ)) + Ok(Value::Slice(generics.collect(), typ)) } /// fn fields(self) -> [(Quoted, Quoted)] -/// Returns (name, type) pairs of each field of this TypeDefinition -fn type_def_fields( +/// 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 { - assert_eq!(arguments.len(), 1, "ICE: `generics` should only receive a single argument"); - let (type_def, span) = match arguments.pop() { - Some((Value::TypeDefinition(id), location)) => (id, location.span), - other => { - unreachable!("ICE: `as_type` expected a `TypeDefinition` argument, found {other:?}") + 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(type_def); + let struct_def = interner.get_struct(struct_def); let struct_def = struct_def.borrow(); let make_token = |name| SpannedToken::new(Token::Ident(name), span); @@ -156,3 +226,84 @@ fn type_def_fields( ]))); 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/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 770c523bd7f..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(()) } @@ -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 870f2bc458a..e8e05506c94 100644 --- a/compiler/noirc_frontend/src/hir/comptime/tests.rs +++ b/compiler/noirc_frontend/src/hir/comptime/tests.rs @@ -13,7 +13,15 @@ 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, CrateId::Root(0)); + 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(), HashMap::new(), no_location) diff --git a/compiler/noirc_frontend/src/hir/comptime/value.rs b/compiler/noirc_frontend/src/hir/comptime/value.rs index c956cdb5796..9e15b73324f 100644 --- a/compiler/noirc_frontend/src/hir/comptime/value.rs +++ b/compiler/noirc_frontend/src/hir/comptime/value.rs @@ -44,7 +44,7 @@ pub enum Value { Array(Vector, Type), Slice(Vector, Type), Code(Rc), - TypeDefinition(StructId), + StructDefinition(StructId), } impl Value { @@ -74,7 +74,7 @@ impl Value { Value::Array(_, typ) => return Cow::Borrowed(typ), Value::Slice(_, typ) => return Cow::Borrowed(typ), Value::Code(_) => Type::Quoted(QuotedType::Quoted), - Value::TypeDefinition(_) => Type::Quoted(QuotedType::TypeDefinition), + Value::StructDefinition(_) => Type::Quoted(QuotedType::StructDefinition), Value::Pointer(element) => { let element = element.borrow().get_type().into_owned(); Type::MutableReference(Box::new(element)) @@ -192,7 +192,7 @@ impl Value { } }; } - Value::Pointer(_) | Value::TypeDefinition(_) => { + Value::Pointer(_) | Value::StructDefinition(_) => { return Err(InterpreterError::CannotInlineMacro { value: self, location }) } }; @@ -298,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 }) } }; @@ -326,9 +326,12 @@ impl Value { } } - pub(crate) fn into_top_level_item(self, location: Location) -> IResult { + pub(crate) fn into_top_level_items( + self, + location: Location, + ) -> IResult> { match self { - Value::Code(tokens) => parse_tokens(tokens, parser::top_level_item(), location.file), + Value::Code(tokens) => parse_tokens(tokens, parser::top_level_items(), location.file), value => Err(InterpreterError::CannotInlineMacro { value, location }), } } @@ -398,7 +401,7 @@ impl Display for Value { } write!(f, " }}") } - Value::TypeDefinition(_) => write!(f, "(type definition)"), + 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 37ece01c805..b474ccff0cc 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -20,19 +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 rustc_hash::FxHashMap as HashMap; +use std::collections::BTreeMap; +use std::fmt::Write; +use std::path::PathBuf; use std::vec; #[derive(Default)] @@ -40,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)>, } @@ -193,6 +198,7 @@ pub enum CompilationError { ResolverError(ResolverError), TypeError(TypeCheckError), InterpreterError(InterpreterError), + DebugComptimeScopeNotFound(Vec), } impl CompilationError { @@ -210,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) + } } } } @@ -253,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![], }, @@ -269,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![]; @@ -286,6 +303,7 @@ impl DefCollector { dep.crate_id, context, use_legacy, + debug_comptime_in_file, macro_processors, )); @@ -327,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(( @@ -344,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, @@ -363,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 @@ -467,6 +534,31 @@ impl DefCollector { } } +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, @@ -489,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"); @@ -562,7 +655,14 @@ impl ResolvedModule { 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, crate_id); + 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) { @@ -577,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 ab9de6c25c4..48985116f4f 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs @@ -1,10 +1,12 @@ -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, @@ -12,6 +14,7 @@ use crate::ast::{ TypeImpl, }; use crate::macros_api::NodeInterner; +use crate::node_interner::ReferenceId; use crate::{ graph::CrateId, hir::def_collector::dc_crate::{UnresolvedStruct, UnresolvedTrait}, @@ -279,12 +282,18 @@ impl<'a> ModCollector<'a> { ); // Create the corresponding module for the struct namespace - let id = match self.push_child_module(&name, self.file_id, false, false) { - Ok(local_id) => context.def_interner.new_struct( + let id = match self.push_child_module( + context, + &name, + Location::new(name.span(), self.file_id), + false, + false, + ) { + Ok(module_id) => context.def_interner.new_struct( &unresolved, resolved_generics, krate, - local_id, + module_id.local_id, self.file_id, ), Err(error) => { @@ -308,6 +317,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 } @@ -353,6 +364,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 } @@ -370,8 +383,14 @@ impl<'a> ModCollector<'a> { let name = trait_definition.name.clone(); // Create the corresponding module for the trait namespace - let trait_id = match self.push_child_module(&name, self.file_id, false, false) { - Ok(local_id) => TraitId(ModuleId { krate, local_id }), + let trait_id = match self.push_child_module( + context, + &name, + Location::new(name.span(), self.file_id), + false, + false, + ) { + Ok(module_id) => TraitId(ModuleId { krate, local_id: module_id.local_id }), Err(error) => { errors.push((error.into(), self.file_id)); continue; @@ -399,7 +418,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 { @@ -413,6 +432,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, @@ -421,9 +441,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); @@ -466,6 +486,7 @@ impl<'a> ModCollector<'a> { self.file_id, vec![], false, + false, ); if let Err((first_def, second_def)) = self.def_collector.def_map.modules @@ -511,6 +532,8 @@ impl<'a> ModCollector<'a> { }; 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 @@ -526,13 +549,19 @@ impl<'a> ModCollector<'a> { ) -> Vec<(CompilationError, FileId)> { let mut errors: Vec<(CompilationError, FileId)> = vec![]; for submodule in submodules { - match self.push_child_module(&submodule.name, file_id, true, submodule.is_contract) { + match self.push_child_module( + context, + &submodule.name, + Location::new(submodule.name.span(), file_id), + true, + submodule.is_contract, + ) { Ok(child) => { errors.extend(collect_defs( self.def_collector, submodule.contents, file_id, - child, + child.local_id, crate_id, context, macro_processors, @@ -557,17 +586,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() }; @@ -614,13 +640,24 @@ impl<'a> ModCollector<'a> { ); // Add module into def collector and get a ModuleId - match self.push_child_module(&mod_decl.ident, child_file_id, true, false) { + match self.push_child_module( + context, + &mod_decl.ident, + 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, @@ -637,13 +674,22 @@ 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, - 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); @@ -652,6 +698,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 @@ -660,11 +711,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(), mod_id) { @@ -675,35 +721,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. @@ -785,6 +854,7 @@ pub(crate) fn collect_global( 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 @@ -801,73 +871,186 @@ pub(crate) fn collect_global( } #[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"); - // 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, file_id, "foo"); + assert!(matches!(result, Err(DefCollectorErrorKind::UnresolvedModuleDecl { .. }))); } #[test] - fn path_resolve_file_module() { - let dir = tempdir().unwrap(); + fn errors_because_cannot_find_mod_relative_to_main() { + let dir = PathBuf::new(); + let mut fm = FileManager::new(&dir); - let entry_file_name = Path::new("my_dummy_file.nr"); - create_dummy_file(&dir, entry_file_name); + let main_file_id = add_file(&mut fm, &dir, "main.nr"); + add_file(&mut fm, &dir, "main/foo.nr"); - let mut fm = FileManager::new(dir.path()); + let result = find_module(&fm, main_file_id, "foo"); + assert!(matches!(result, Err(DefCollectorErrorKind::UnresolvedModuleDecl { .. }))); + } - let file_id = fm.add_file_with_source(entry_file_name, "fn foo() {}".to_string()).unwrap(); + #[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 dep_file_name = Path::new("foo.nr"); - create_dummy_file(&dir, dep_file_name); - find_module(&fm, file_id, "foo").unwrap_err(); + let result = find_module(&fm, lib_file_id, "foo"); + assert!(matches!(result, Err(DefCollectorErrorKind::UnresolvedModuleDecl { .. }))); } #[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 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"); + + let result = find_module(&fm, foo_file_id, "bar"); + assert!(matches!(result, Err(DefCollectorErrorKind::UnresolvedModuleDecl { .. }))); + } + + #[test] + 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 { .. }))); + } + + #[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(); + } + + #[test] + fn finds_module_in_sibling_dir_mod_nr_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/mod.nr"); + + // `mod foo` from `sub_dir.nr` should find `sub_dir/foo.nr` find_module(&fm, sub_dir_file_id, "foo").unwrap(); } + + #[test] + 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 af2264c3843..37c5a460667 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/errors.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/errors.rs @@ -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")] @@ -121,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, ) 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 71fdc6b30d2..87c4133d68e 100644 --- a/compiler/noirc_frontend/src/hir/mod.rs +++ b/compiler/noirc_frontend/src/hir/mod.rs @@ -290,4 +290,9 @@ impl Context<'_, '_> { 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/import.rs b/compiler/noirc_frontend/src/hir/resolution/import.rs index 282ee8a23c2..710c12a91bf 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,13 +81,14 @@ pub fn resolve_import( crate_id: CrateId, import_directive: &ImportDirective, def_maps: &BTreeMap, + path_references: &mut Option<&mut Vec>, ) -> Result { 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)?; + } = resolve_path_to_ns(import_directive, crate_id, crate_id, def_maps, path_references)?; let name = resolve_path_name(import_directive); @@ -131,6 +126,7 @@ fn resolve_path_to_ns( crate_id: CrateId, importing_crate: CrateId, def_maps: &BTreeMap, + path_references: &mut Option<&mut Vec>, ) -> NamespaceResolutionResult { let import_path = &import_directive.path.segments; let def_map = &def_maps[&crate_id]; @@ -138,7 +134,13 @@ fn resolve_path_to_ns( match import_directive.path.kind { crate::ast::PathKind::Crate => { // Resolve from the root of the crate - resolve_path_from_crate_root(crate_id, importing_crate, import_path, def_maps) + resolve_path_from_crate_root( + crate_id, + importing_crate, + import_path, + def_maps, + path_references, + ) } crate::ast::PathKind::Plain => { // There is a possibility that the import path is empty @@ -150,6 +152,7 @@ fn resolve_path_to_ns( import_path, import_directive.module_id, def_maps, + path_references, ); } @@ -158,7 +161,13 @@ fn resolve_path_to_ns( let first_segment = import_path.first().expect("ice: could not fetch first segment"); if current_mod.find_name(first_segment).is_none() { // Resolve externally when first segment is unresolved - return resolve_external_dep(def_map, import_directive, def_maps, importing_crate); + return resolve_external_dep( + def_map, + import_directive, + def_maps, + path_references, + importing_crate, + ); } resolve_name_in_module( @@ -167,12 +176,17 @@ fn resolve_path_to_ns( import_path, import_directive.module_id, def_maps, + path_references, ) } - crate::ast::PathKind::Dep => { - resolve_external_dep(def_map, import_directive, def_maps, importing_crate) - } + crate::ast::PathKind::Dep => resolve_external_dep( + def_map, + import_directive, + def_maps, + path_references, + importing_crate, + ), } } @@ -182,6 +196,7 @@ fn resolve_path_from_crate_root( import_path: &[Ident], def_maps: &BTreeMap, + path_references: &mut Option<&mut Vec>, ) -> NamespaceResolutionResult { resolve_name_in_module( crate_id, @@ -189,6 +204,7 @@ fn resolve_path_from_crate_root( import_path, def_maps[&crate_id].root, def_maps, + path_references, ) } @@ -198,6 +214,7 @@ fn resolve_name_in_module( import_path: &[Ident], starting_mod: LocalModuleId, def_maps: &BTreeMap, + path_references: &mut Option<&mut Vec>, ) -> NamespaceResolutionResult { let def_map = &def_maps[&krate]; let mut current_mod_id = ModuleId { krate, local_id: starting_mod }; @@ -228,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"), }; @@ -277,6 +309,7 @@ fn resolve_external_dep( current_def_map: &CrateDefMap, directive: &ImportDirective, def_maps: &BTreeMap, + path_references: &mut Option<&mut Vec>, importing_crate: CrateId, ) -> NamespaceResolutionResult { // Use extern_prelude to get the dep @@ -306,7 +339,7 @@ fn resolve_external_dep( is_prelude: false, }; - resolve_path_to_ns(&dep_directive, dep_module.krate, importing_crate, def_maps) + 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 6d547aaf0b7..856a769c9dd 100644 --- a/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ b/compiler/noirc_frontend/src/hir/resolution/resolver.rs @@ -399,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 }; @@ -445,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 }; @@ -765,7 +765,7 @@ impl<'a> Resolver<'a> { } // 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); @@ -1096,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.ident().0.contents)) - .map(|ResolvedGeneric { name, type_var, .. }| (name.clone(), type_var.clone())) + .filter_map(|generic| self.find_generic(&generic.ident().0.contents).cloned()) .collect(); FuncMeta { @@ -1512,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 { @@ -1549,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) { @@ -1556,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); @@ -2017,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 a6b732439b0..28ee70393cd 100644 --- a/compiler/noirc_frontend/src/hir/resolution/traits.rs +++ b/compiler/noirc_frontend/src/hir/resolution/traits.rs @@ -35,9 +35,6 @@ 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, vec![]); - } let mut all_errors = Vec::new(); for (trait_id, unresolved_trait) in traits { @@ -49,6 +46,8 @@ pub(crate) fn resolve_traits( ); 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) let _ = resolve_trait_types(context, crate_id, &unresolved_trait); @@ -67,14 +66,14 @@ pub(crate) fn resolve_traits( 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 @@ -390,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)) } diff --git a/compiler/noirc_frontend/src/hir/type_check/errors.rs b/compiler/noirc_frontend/src/hir/type_check/errors.rs index f18e8a9e843..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 }, @@ -80,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")] @@ -114,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( @@ -149,6 +154,12 @@ pub enum TypeCheckError { 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 } @@ -247,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 } @@ -307,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) @@ -350,7 +349,54 @@ impl<'a> From<&'a TypeCheckError> for Diagnostic { 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 77861a6d8f8..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> { @@ -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); diff --git a/compiler/noirc_frontend/src/hir/type_check/mod.rs b/compiler/noirc_frontend/src/hir/type_check/mod.rs index 1d3c7fcda9b..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}, - Kind, Type, TypeBindings, + Kind, ResolvedGeneric, Type, TypeBindings, }; pub use self::errors::Source; @@ -281,8 +281,10 @@ 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(), 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 @@ -677,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(); 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 8de4f118774..ab2344746d1 100644 --- a/compiler/noirc_frontend/src/hir_def/expr.rs +++ b/compiler/noirc_frontend/src/hir_def/expr.rs @@ -124,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)] diff --git a/compiler/noirc_frontend/src/hir_def/function.rs b/compiler/noirc_frontend/src/hir_def/function.rs index a4a9f855c62..fa8bb55abee 100644 --- a/compiler/noirc_frontend/src/hir_def/function.rs +++ b/compiler/noirc_frontend/src/hir_def/function.rs @@ -1,8 +1,6 @@ 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; @@ -10,7 +8,7 @@ use crate::ast::{FunctionKind, FunctionReturnType, Visibility}; use crate::graph::CrateId; use crate::macros_api::BlockExpression; use crate::node_interner::{ExprId, NodeInterner, TraitImplId}; -use crate::{ResolvedGeneric, Type, TypeVariable}; +use crate::{ResolvedGeneric, Type}; /// A Hir function is a block expression /// with a list of statements @@ -113,7 +111,7 @@ 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. 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 0a7797c2bfb..8183911c845 100644 --- a/compiler/noirc_frontend/src/hir_def/types.rs +++ b/compiler/noirc_frontend/src/hir_def/types.rs @@ -214,7 +214,7 @@ pub enum QuotedType { Quoted, TopLevelItem, Type, - TypeDefinition, + StructDefinition, } /// A list of TypeVariableIds to bind to a type. Storing the @@ -342,6 +342,11 @@ impl StructType { 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() } @@ -665,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, _)) } @@ -1176,7 +1185,7 @@ impl std::fmt::Display for QuotedType { QuotedType::Quoted => write!(f, "Quoted"), QuotedType::TopLevelItem => write!(f, "TopLevelItem"), QuotedType::Type => write!(f, "Type"), - QuotedType::TypeDefinition => write!(f, "TypeDefinition"), + QuotedType::StructDefinition => write!(f, "StructDefinition"), } } } diff --git a/compiler/noirc_frontend/src/lexer/errors.rs b/compiler/noirc_frontend/src/lexer/errors.rs index 2452e034c1c..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")] @@ -46,6 +48,7 @@ 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, @@ -83,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"), diff --git a/compiler/noirc_frontend/src/lexer/lexer.rs b/compiler/noirc_frontend/src/lexer/lexer.rs index d2bc3d0f519..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(), } } @@ -376,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); @@ -899,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 = "+= -= *= /= %="; diff --git a/compiler/noirc_frontend/src/lexer/token.rs b/compiler/noirc_frontend/src/lexer/token.rs index f98343ba52f..41de13fb17e 100644 --- a/compiler/noirc_frontend/src/lexer/token.rs +++ b/compiler/noirc_frontend/src/lexer/token.rs @@ -913,7 +913,7 @@ pub enum Keyword { Trait, Type, TypeType, - TypeDefinition, + StructDefinition, Unchecked, Unconstrained, Use, @@ -961,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"), @@ -1012,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, 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/mod.rs b/compiler/noirc_frontend/src/monomorphization/mod.rs index 0ed043914f3..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); @@ -841,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); @@ -911,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)) } }; @@ -1044,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)) { @@ -1268,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); @@ -1308,7 +1269,12 @@ 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::Slice(Box::new(int_type)); @@ -1327,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 } @@ -1595,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), @@ -1698,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, @@ -1750,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)); @@ -1762,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 })) } } @@ -1828,3 +1771,89 @@ pub fn undo_instantiation_bindings(bindings: TypeBindings) { 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 17531d09eac..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,6 +34,7 @@ use crate::hir_def::{ function::{FuncMeta, HirFunction}, stmt::HirStatement, }; +use crate::locations::LocationIndices; use crate::token::{Attributes, SecondaryAttribute}; use crate::GenericTypeVars; use crate::Generics; @@ -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, @@ -182,6 +192,33 @@ pub struct NodeInterner { /// 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. @@ -200,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 @@ -258,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 { @@ -272,6 +334,7 @@ impl FunctionModifiers { is_unconstrained: false, generic_count: 0, is_comptime: false, + name_location: Location::dummy(), } } } @@ -423,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, } @@ -489,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(), @@ -504,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(), @@ -516,6 +582,10 @@ impl Default for NodeInterner { 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 @@ -679,6 +749,7 @@ impl NodeInterner { self.type_ref_locations.push((typ, location)); } + #[allow(clippy::too_many_arguments)] fn push_global( &mut self, ident: Ident, @@ -687,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, @@ -719,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 } @@ -766,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 } @@ -804,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( @@ -816,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) { @@ -904,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] } @@ -981,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) } @@ -1085,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 @@ -1194,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 @@ -1564,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() { @@ -1594,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 { @@ -1619,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 { @@ -1664,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; } @@ -1721,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" + ), } } } @@ -1742,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(); @@ -1762,6 +1915,38 @@ impl NodeInterner { 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 { diff --git a/compiler/noirc_frontend/src/parser/mod.rs b/compiler/noirc_frontend/src/parser/mod.rs index d7a282dbfc7..c4aa0654ecd 100644 --- a/compiler/noirc_frontend/src/parser/mod.rs +++ b/compiler/noirc_frontend/src/parser/mod.rs @@ -22,7 +22,7 @@ use chumsky::primitive::Container; pub use errors::ParserError; pub use errors::ParserErrorReason; use noirc_errors::Span; -pub use parser::{expression, parse_program, top_level_item}; +pub use parser::{expression, parse_program, top_level_items}; #[derive(Debug, Clone)] pub enum TopLevelStatement { diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index afeee889ede..de9095aaff2 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -192,8 +192,8 @@ fn module() -> impl NoirParser { } /// This parser is used for parsing top level statements in macros -pub fn top_level_item() -> impl NoirParser { - top_level_statement(module()) +pub fn top_level_items() -> impl NoirParser> { + top_level_statement(module()).repeated() } /// top_level_statement: function_definition @@ -1117,16 +1117,11 @@ where } fn quote() -> impl NoirParser { - token_kind(TokenKind::Quote).validate(|token, span, emit| { - let tokens = match token { + 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"), - }; - emit(ParserError::with_reason( - ParserErrorReason::ExperimentalFeature("quoted expressions"), - span, - )); - ExpressionKind::Quote(tokens) + }) }) } diff --git a/compiler/noirc_frontend/src/parser/parser/types.rs b/compiler/noirc_frontend/src/parser/parser/types.rs index 493ebd1fb2f..32929312d54 100644 --- a/compiler/noirc_frontend/src/parser/parser/types.rs +++ b/compiler/noirc_frontend/src/parser/parser/types.rs @@ -25,7 +25,7 @@ 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(), @@ -80,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) }) } 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 9251eb3db6b..8cedeeeff0d 100644 --- a/compiler/noirc_frontend/src/tests.rs +++ b/compiler/noirc_frontend/src/tests.rs @@ -86,7 +86,8 @@ pub(crate) fn get_program( program.clone().into_sorted(), root_file_id, use_legacy, - &[], // No macro processors + None, // No debug_comptime_in_file + &[], // No macro processors )); } (program, context, errors) @@ -1455,6 +1456,79 @@ fn specify_method_types_with_turbofish() { 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#" @@ -1896,3 +1970,143 @@ fn quote_code_fragments() { 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/docs/docs/how_to/how-to-oracles.md b/docs/docs/how_to/how-to-oracles.md index d6834c09c84..df41276cfe1 100644 --- a/docs/docs/how_to/how-to-oracles.md +++ b/docs/docs/how_to/how-to-oracles.md @@ -141,10 +141,10 @@ server.addMethod("resolve_function_call", async (params) => { if params.function !== "getSqrt" { throw Error("Unexpected foreign call") }; - const values = params.inputs[0].map((field) => { + const values = params.inputs[0].Array.map((field) => { return `${Math.sqrt(parseInt(field, 16))}`; }); - return { values }; + return { values: [{ Array: values }] }; }); ``` diff --git a/docs/docs/noir/concepts/data_types/slices.mdx b/docs/docs/noir/concepts/data_types/slices.mdx index d619117b799..95da2030843 100644 --- a/docs/docs/noir/concepts/data_types/slices.mdx +++ b/docs/docs/noir/concepts/data_types/slices.mdx @@ -197,7 +197,7 @@ fn main() { Applies a function to each element of the slice, returning a new slice containing the mapped elements. ```rust -fn map(self, f: fn(T) -> U) -> [U] +fn map(self, f: fn[Env](T) -> U) -> [U] ``` example @@ -213,7 +213,7 @@ Applies a function to each element of the slice, returning the final accumulated parameter is the initial value. ```rust -fn fold(self, mut accumulator: U, f: fn(U, T) -> U) -> U +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 @@ -247,7 +247,7 @@ fn main() { Same as fold, but uses the first element as the starting element. ```rust -fn reduce(self, f: fn(T, T) -> T) -> T +fn reduce(self, f: fn[Env](T, T) -> T) -> T ``` example: @@ -260,12 +260,72 @@ fn main() { } ``` +### 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(T) -> bool) -> bool +fn all(self, predicate: fn[Env](T) -> bool) -> bool ``` example: @@ -283,7 +343,7 @@ fn main() { Returns true if any of the elements satisfy the given predicate ```rust -fn any(self, predicate: fn(T) -> bool) -> bool +fn any(self, predicate: fn[Env](T) -> bool) -> bool ``` example: 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/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/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/collections/bounded_vec.nr b/noir_stdlib/src/collections/bounded_vec.nr index c218ecd2348..a56d6f60390 100644 --- a/noir_stdlib/src/collections/bounded_vec.nr +++ b/noir_stdlib/src/collections/bounded_vec.nr @@ -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) } @@ -152,25 +152,16 @@ impl From<[T; Len]> for BoundedVec } 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.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/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/embedded_curve_ops.nr b/noir_stdlib/src/embedded_curve_ops.nr index c5617094c0a..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,18 +50,25 @@ 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); @@ -60,6 +76,12 @@ impl EmbeddedCurveScalar { } } +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. 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/keccak.nr b/noir_stdlib/src/hash/keccak.nr new file mode 100644 index 00000000000..7623f5a2256 --- /dev/null +++ b/noir_stdlib/src/hash/keccak.nr @@ -0,0 +1,122 @@ +global LIMBS_PER_BLOCK = 17; //BLOCK_SIZE / 8; +global NUM_KECCAK_LANES = 25; +global BLOCK_SIZE = 136; //(1600 - BITS * 2) / WORD_SIZE; +global WORD_SIZE = 8; + +use crate::collections::bounded_vec::BoundedVec; + +#[foreign(keccakf1600)] +fn keccakf1600(input: [u64; 25]) -> [u64; 25] {} + +fn keccak256(input: [u8; N], message_size: u32) -> [u8; 32] { + // No var keccak for now + assert(N == message_size); + + //1. format_input_lanes + let max_blocks = (N + BLOCK_SIZE) / BLOCK_SIZE; + //maximum number of bytes to hash + let max_blocks_length = (BLOCK_SIZE * (max_blocks)); + assert(max_blocks_length < 1000); + let mut block_bytes :BoundedVec = BoundedVec::from_array(input); + for i in N..N + BLOCK_SIZE { + let data = if i == N { + 1 + } else if i == max_blocks_length - 1 { + 0x80 + } else { + 0 + }; + block_bytes.push(data); + } + + // keccak lanes interpret memory as little-endian integers, + // means we need to swap our byte ordering + let num_limbs = max_blocks * LIMBS_PER_BLOCK; //max_blocks_length / WORD_SIZE; + for i in 0..num_limbs { + let mut temp = [0; 8]; + + for j in 0..8 { + temp[j] = block_bytes.get(8*i+j); + } + for j in 0..8 { + block_bytes.set(8 * i + j, temp[7 - j]); + } + } + let byte_size = max_blocks_length; + assert(num_limbs < 1000); + let mut sliced_buffer = [0; 1000]; + // populate a vector of 64-bit limbs from our byte array + for i in 0..num_limbs { + let mut sliced = 0; + if (i * WORD_SIZE + WORD_SIZE > byte_size) { + let slice_size = byte_size - (i * WORD_SIZE); + let byte_shift = (WORD_SIZE - slice_size) * 8; + let mut v = 1; + for k in 0..slice_size { + sliced += v * (block_bytes.get(i * WORD_SIZE+7-k) as Field); + v *= 256; + } + let w = 1 << (byte_shift as u8); + sliced *= w as Field; + } else { + let mut v = 1; + for k in 0..WORD_SIZE { + sliced += v * (block_bytes.get(i * WORD_SIZE+7-k) as Field); + v *= 256; + } + } + sliced_buffer[i] = sliced as u64; + } + + //2. sponge_absorb + let num_blocks = max_blocks; + let mut state : [u64;NUM_KECCAK_LANES]= [0; NUM_KECCAK_LANES]; + + for i in 0..num_blocks { + if (i == 0) { + for j in 0..LIMBS_PER_BLOCK { + state[j] = sliced_buffer[j]; + } + } else { + for j in 0..LIMBS_PER_BLOCK { + state[j] = state[j] ^ sliced_buffer[i * LIMBS_PER_BLOCK + j]; + } + } + state = keccakf1600(state); + } + + //3. sponge_squeeze + let mut result = [0; 32]; + for i in 0..4 { + let lane = state[i] as Field; + let lane_le = lane.to_le_bytes(8); + for j in 0..8 { + result[8*i+j] = lane_le[j]; + } + } + result +} + +mod tests { + use crate::hash::keccak::keccak256; + + #[test] + fn smoke_test() { + let input = [0xbd]; + let result = [ + 0x5a, 0x50, 0x2f, 0x9f, 0xca, 0x46, 0x7b, 0x26, 0x6d, 0x5b, 0x78, 0x33, 0x65, 0x19, 0x37, 0xe8, 0x05, 0x27, 0x0c, 0xa3, 0xf3, 0xaf, 0x1c, 0x0d, 0xd2, 0x46, 0x2d, 0xca, 0x4b, 0x3b, 0x1a, 0xbf + ]; + assert_eq(keccak256(input, input.len()), result); + } + + #[test] + fn hash_hello_world() { + // "hello world" + let input = [72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33]; + let result = [ + 0xec, 0xd0, 0xe1, 0x8, 0xa9, 0x8e, 0x19, 0x2a, 0xf1, 0xd2, 0xc2, 0x50, 0x55, 0xf4, 0xe3, 0xbe, 0xd7, 0x84, 0xb5, 0xc8, 0x77, 0x20, 0x4e, 0x73, 0x21, 0x9a, 0x52, 0x3, 0x25, 0x1f, 0xea, 0xab + ]; + assert_eq(keccak256(input, input.len()), result); + } +} + diff --git a/noir_stdlib/src/hash/mimc.nr b/noir_stdlib/src/hash/mimc.nr index de4475d9446..a16a73c5bc5 100644 --- a/noir_stdlib/src/hash/mimc.nr +++ b/noir_stdlib/src/hash/mimc.nr @@ -116,7 +116,6 @@ 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 { let exponent = 7; let mut r = 0; diff --git a/noir_stdlib/src/hash.nr b/noir_stdlib/src/hash/mod.nr similarity index 96% rename from noir_stdlib/src/hash.nr rename to noir_stdlib/src/hash/mod.nr index 493430c99a4..42555348949 100644 --- a/noir_stdlib/src/hash.nr +++ b/noir_stdlib/src/hash/mod.nr @@ -1,6 +1,7 @@ mod poseidon; mod mimc; mod poseidon2; +mod keccak; use crate::default::Default; use crate::uint128::U128; @@ -25,6 +26,7 @@ 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 { // docs:end:pedersen_commitment @@ -46,6 +48,7 @@ fn pedersen_commitment_with_separator_noir(input: [Field; N], separa EmbeddedCurvePoint { x: values[0], y: values[1], is_infinite: values[2] as bool } } +#[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 } @@ -59,10 +62,7 @@ pub fn pedersen_hash(input: [Field; N]) -> Field } #[field(bn254)] -fn derive_generators( - domain_separator_bytes: [u8; M], - starting_index: u32 -) -> [EmbeddedCurvePoint; N] { +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) @@ -70,7 +70,10 @@ fn derive_generators( #[builtin(derive_pedersen_generators)] #[field(bn254)] -fn __derive_generators(domain_separator_bytes: [u8; M], starting_index: u32) -> [EmbeddedCurvePoint; N] {} +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); diff --git a/noir_stdlib/src/hash/poseidon/bn254.nr b/noir_stdlib/src/hash/poseidon/bn254.nr index 6800fac421d..103ab3d166a 100644 --- a/noir_stdlib/src/hash/poseidon/bn254.nr +++ b/noir_stdlib/src/hash/poseidon/bn254.nr @@ -6,7 +6,6 @@ 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 { 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 100% rename from noir_stdlib/src/hash/poseidon.nr rename to noir_stdlib/src/hash/poseidon/mod.nr diff --git a/noir_stdlib/src/lib.nr b/noir_stdlib/src/lib.nr index 65da7e6e9ab..ac53941e752 100644 --- a/noir_stdlib/src/lib.nr +++ b/noir_stdlib/src/lib.nr @@ -27,6 +27,7 @@ mod uint128; mod bigint; mod runtime; mod meta; +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 diff --git a/noir_stdlib/src/meta.nr b/noir_stdlib/src/meta/mod.nr similarity index 100% rename from noir_stdlib/src/meta.nr rename to noir_stdlib/src/meta/mod.nr diff --git a/noir_stdlib/src/meta/type_def.nr b/noir_stdlib/src/meta/type_def.nr index b9354485921..c01aab4b141 100644 --- a/noir_stdlib/src/meta/type_def.nr +++ b/noir_stdlib/src/meta/type_def.nr @@ -1,16 +1,16 @@ -impl TypeDefinition { - /// Return a syntactic version of this type definition as a type. +impl StructDefinition { + /// Return a syntactic version of this struct definition as a type. /// For example, `as_type(quote { type Foo { ... } })` would return `Foo` - #[builtin(type_def_as_type)] + #[builtin(struct_def_as_type)] fn as_type(self) -> Quoted {} - /// Return each generic on this type. The names of these generics are unchanged + /// 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(type_def_generics)] + #[builtin(struct_def_generics)] fn generics(self) -> [Quoted] {} - /// Returns (name, type) pairs of each field in this type. Each type is as-is + /// Returns (name, type) pairs of each field in this struct. Each type is as-is /// with any generic arguments unchanged. - #[builtin(type_def_fields)] + #[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/sha256.nr b/noir_stdlib/src/sha256.nr index 96ea8bb82c3..b5dc958e9d1 100644 --- a/noir_stdlib/src/sha256.nr +++ b/noir_stdlib/src/sha256.nr @@ -15,7 +15,9 @@ fn msg_u8_to_u32(msg: [u8; 64]) -> [u32; 16] { msg32 } + // SHA-256 hash function +#[no_predicates] pub fn digest(msg: [u8; N]) -> [u8; 32] { sha256_var(msg, N as u64) } @@ -38,7 +40,6 @@ 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] { 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 diff --git a/noir_stdlib/src/sha512.nr b/noir_stdlib/src/sha512.nr index 993f328341f..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,7 +89,6 @@ 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] { let mut msg_block: [u8; 128] = [0; 128]; // noir-fmt:ignore diff --git a/noir_stdlib/src/slice.nr b/noir_stdlib/src/slice.nr index 1a40abcf704..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 {} @@ -85,6 +87,33 @@ impl [T] { 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; 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 b0d55b6ff1d..95dcfdda880 100755 --- a/scripts/install_bb.sh +++ b/scripts/install_bb.sh @@ -1,6 +1,6 @@ #!/bin/bash -VERSION="0.43.0" +VERSION="0.46.1" BBUP_PATH=~/.bb/bbup diff --git a/test_programs/benchmarks/bench_eddsa_poseidon/src/main.nr b/test_programs/benchmarks/bench_eddsa_poseidon/src/main.nr index 2d38f5b1063..cb853e48c30 100644 --- a/test_programs/benchmarks/bench_eddsa_poseidon/src/main.nr +++ b/test_programs/benchmarks/bench_eddsa_poseidon/src/main.nr @@ -9,4 +9,4 @@ fn main( s: Field ) -> pub bool { eddsa_poseidon_verify(pub_key_x, pub_key_y, s, r8_x, r8_y, msg) -} \ No newline at end of file +} 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_poseidon_hash_100/src/main.nr b/test_programs/benchmarks/bench_poseidon_hash_100/src/main.nr index 5fc9e313179..1c9bbfe61bf 100644 --- a/test_programs/benchmarks/bench_poseidon_hash_100/src/main.nr +++ b/test_programs/benchmarks/bench_poseidon_hash_100/src/main.nr @@ -9,4 +9,4 @@ fn main(input: [[Field; 2]; SIZE]) -> pub [Field; SIZE] { } results -} \ No newline at end of file +} diff --git a/test_programs/benchmarks/bench_poseidon_hash_30/src/main.nr b/test_programs/benchmarks/bench_poseidon_hash_30/src/main.nr index 3e319d2b025..3edb47e9f72 100644 --- a/test_programs/benchmarks/bench_poseidon_hash_30/src/main.nr +++ b/test_programs/benchmarks/bench_poseidon_hash_30/src/main.nr @@ -9,4 +9,4 @@ fn main(input: [[Field; 2]; SIZE]) -> pub [Field; SIZE] { } results -} \ No newline at end of file +} 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_100/src/main.nr b/test_programs/benchmarks/bench_sha256_100/src/main.nr index 48b7fbac93c..6df856a83fc 100644 --- a/test_programs/benchmarks/bench_sha256_100/src/main.nr +++ b/test_programs/benchmarks/bench_sha256_100/src/main.nr @@ -1,4 +1,3 @@ - global SIZE = 100; fn main(input: [[u8; 2]; SIZE]) -> pub [[u8; 32]; SIZE] { @@ -8,4 +7,4 @@ fn main(input: [[u8; 2]; SIZE]) -> pub [[u8; 32]; SIZE] { } results -} \ No newline at end of file +} diff --git a/test_programs/benchmarks/bench_sha256_30/src/main.nr b/test_programs/benchmarks/bench_sha256_30/src/main.nr index 37c742f9667..220c1cfbbed 100644 --- a/test_programs/benchmarks/bench_sha256_30/src/main.nr +++ b/test_programs/benchmarks/bench_sha256_30/src/main.nr @@ -1,4 +1,3 @@ - global SIZE = 30; fn main(input: [[u8; 2]; SIZE]) -> pub [[u8; 32]; SIZE] { @@ -8,4 +7,4 @@ fn main(input: [[u8; 2]; SIZE]) -> pub [[u8; 32]; SIZE] { } results -} \ 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/negate_unsigned/src/main.nr b/test_programs/compile_failure/negate_unsigned/src/main.nr index af4802a4ce6..4d3c5abe5a4 100644 --- a/test_programs/compile_failure/negate_unsigned/src/main.nr +++ b/test_programs/compile_failure/negate_unsigned/src/main.nr @@ -1,4 +1,3 @@ - fn main() { let var = -1 as u8; std::println(var); 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 index 4ea2b75aa85..8bdefbbbd21 100644 --- a/test_programs/compile_failure/non_comptime_local_fn_call/Nargo.toml +++ b/test_programs/compile_failure/non_comptime_local_fn_call/Nargo.toml @@ -4,4 +4,4 @@ type = "bin" authors = [""] compiler_version = ">=0.23.0" -[dependencies] \ No newline at end of file +[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 index 7cbf4213425..d75bb1a922a 100644 --- 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 @@ -6,4 +6,4 @@ fn main() { fn id(x: Field) -> Field { x -} \ No newline at end of file +} diff --git a/test_programs/compile_failure/orphaned_trait_impl/src/main.nr b/test_programs/compile_failure/orphaned_trait_impl/src/main.nr index dfd88d8f074..dd04aa454b2 100644 --- a/test_programs/compile_failure/orphaned_trait_impl/src/main.nr +++ b/test_programs/compile_failure/orphaned_trait_impl/src/main.nr @@ -1,4 +1,4 @@ -impl dep::crate1::MyTrait for dep::crate2::MyStruct { +impl crate1::MyTrait for crate2::MyStruct { } fn main(x: Field, y: pub Field) { 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/restricted_bit_sizes/src/main.nr b/test_programs/compile_failure/restricted_bit_sizes/src/main.nr index 4298c2052a7..a3fea13cc3a 100644 --- a/test_programs/compile_failure/restricted_bit_sizes/src/main.nr +++ b/test_programs/compile_failure/restricted_bit_sizes/src/main.nr @@ -1,3 +1,5 @@ +use std::assert_constant; + fn main() -> pub u63 { 5 } 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/turbofish_generic_count/src/main.nr b/test_programs/compile_failure/turbofish_generic_count/src/main.nr index a5f46adb6a5..4091b2f0581 100644 --- a/test_programs/compile_failure/turbofish_generic_count/src/main.nr +++ b/test_programs/compile_failure/turbofish_generic_count/src/main.nr @@ -1,4 +1,3 @@ - struct Bar { one: Field, two: Field, 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 index 9fcd61c235e..56fa88ccb68 100644 --- a/test_programs/compile_success_contract/abi_attribute/Nargo.toml +++ b/test_programs/compile_success_contract/abi_attribute/Nargo.toml @@ -3,4 +3,4 @@ name = "abi_attribute" type = "contract" authors = [""] -[dependencies] \ No newline at end of file +[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 index 0c376b54686..164512b03db 100644 --- a/test_programs/compile_success_contract/abi_attribute/src/main.nr +++ b/test_programs/compile_success_contract/abi_attribute/src/main.nr @@ -6,4 +6,4 @@ contract Foo { struct Bar { inner: Field } -} \ No newline at end of file +} 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/src/main.nr b/test_programs/compile_success_empty/comptime_type_definition/src/main.nr index 025f6a0b0bf..cdfc9bd6b75 100644 --- a/test_programs/compile_success_empty/comptime_type_definition/src/main.nr +++ b/test_programs/compile_success_empty/comptime_type_definition/src/main.nr @@ -6,7 +6,7 @@ struct MyType { field2: (B, C), } -comptime fn my_comptime_fn(typ: TypeDefinition) { +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/compile_success_empty/derive_impl/src/main.nr b/test_programs/compile_success_empty/derive_impl/src/main.nr index 9636e4c7383..5463a61d969 100644 --- a/test_programs/compile_success_empty/derive_impl/src/main.nr +++ b/test_programs/compile_success_empty/derive_impl/src/main.nr @@ -1,4 +1,4 @@ -comptime fn derive_default(typ: TypeDefinition) -> Quoted { +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" 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 index 58e774846cf..7d0d5f3513e 100644 --- a/test_programs/compile_success_empty/impl_where_clause/Nargo.toml +++ b/test_programs/compile_success_empty/impl_where_clause/Nargo.toml @@ -2,5 +2,6 @@ name = "impl_where_clause" type = "bin" authors = [""] +compiler_version = ">=0.31.0" -[dependencies] +[dependencies] \ No newline at end of file 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/reexports/src/main.nr b/test_programs/compile_success_empty/reexports/src/main.nr index ed469ff77d0..0fd65a33564 100644 --- a/test_programs/compile_success_empty/reexports/src/main.nr +++ b/test_programs/compile_success_empty/reexports/src/main.nr @@ -1,4 +1,4 @@ -use dep::reexporting_lib::{FooStruct, MyStruct, lib}; +use reexporting_lib::{FooStruct, MyStruct, lib}; fn main() { let x: FooStruct = MyStruct { inner: 0 }; 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/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/compile_success_empty/workspace_reexport_bug/binary/src/main.nr b/test_programs/compile_success_empty/workspace_reexport_bug/binary/src/main.nr index ab0ae9a48b8..a4207794a8a 100644 --- a/test_programs/compile_success_empty/workspace_reexport_bug/binary/src/main.nr +++ b/test_programs/compile_success_empty/workspace_reexport_bug/binary/src/main.nr @@ -1,2 +1,2 @@ -use dep::library::ReExportMeFromAnotherLib; +use library::ReExportMeFromAnotherLib; fn main(_x: ReExportMeFromAnotherLib) {} diff --git a/test_programs/compile_success_empty/workspace_reexport_bug/library/src/lib.nr b/test_programs/compile_success_empty/workspace_reexport_bug/library/src/lib.nr index 8e84662ed03..e3a1539ea65 100644 --- a/test_programs/compile_success_empty/workspace_reexport_bug/library/src/lib.nr +++ b/test_programs/compile_success_empty/workspace_reexport_bug/library/src/lib.nr @@ -1,2 +1,2 @@ // Re-export -use dep::library2::ReExportMeFromAnotherLib; +use library2::ReExportMeFromAnotherLib; diff --git a/test_programs/execution_failure/div_by_zero_numerator_witness/src/main.nr b/test_programs/execution_failure/div_by_zero_numerator_witness/src/main.nr index 7c6cae4932e..012e823b297 100644 --- a/test_programs/execution_failure/div_by_zero_numerator_witness/src/main.nr +++ b/test_programs/execution_failure/div_by_zero_numerator_witness/src/main.nr @@ -1,4 +1,3 @@ - fn main(x: Field) { let a: Field = x / 0; std::println(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/diamond_deps_0/src/main.nr b/test_programs/execution_success/diamond_deps_0/src/main.nr index ca95c6e0aa8..690d6fc9fc8 100644 --- a/test_programs/execution_success/diamond_deps_0/src/main.nr +++ b/test_programs/execution_success/diamond_deps_0/src/main.nr @@ -1,6 +1,6 @@ -use dep::dep1::call_dep1_then_dep2; -use dep::dep2::call_dep2; -use dep::dep2::RESOLVE_THIS; +use dep1::call_dep1_then_dep2; +use dep2::call_dep2; +use dep2::RESOLVE_THIS; fn main(x: Field, y: pub Field) -> pub Field { call_dep1_then_dep2(x, y) + call_dep2(x, y) + RESOLVE_THIS 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/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_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/traits_in_crates_1/crate1/src/lib.nr b/test_programs/execution_success/traits_in_crates_1/crate1/src/lib.nr index 62dd5a2c111..e36a263093a 100644 --- a/test_programs/execution_success/traits_in_crates_1/crate1/src/lib.nr +++ b/test_programs/execution_success/traits_in_crates_1/crate1/src/lib.nr @@ -2,7 +2,7 @@ trait MyTrait { fn Add10(&mut self); } -impl MyTrait for dep::crate2::MyStruct { +impl MyTrait for crate2::MyStruct { fn Add10(&mut self) { self.Q += 10; } diff --git a/test_programs/execution_success/traits_in_crates_1/src/main.nr b/test_programs/execution_success/traits_in_crates_1/src/main.nr index 7ba2f63c5c0..2afec29ee1f 100644 --- a/test_programs/execution_success/traits_in_crates_1/src/main.nr +++ b/test_programs/execution_success/traits_in_crates_1/src/main.nr @@ -1,5 +1,5 @@ fn main(x: Field, y: pub Field) { - let mut V = dep::crate2::MyStruct { Q: x }; + let mut V = crate2::MyStruct { Q: x }; V.Add10(); assert(V.Q == y); } diff --git a/test_programs/execution_success/traits_in_crates_2/crate2/src/lib.nr b/test_programs/execution_success/traits_in_crates_2/crate2/src/lib.nr index 38870489131..fe6a94a4a95 100644 --- a/test_programs/execution_success/traits_in_crates_2/crate2/src/lib.nr +++ b/test_programs/execution_success/traits_in_crates_2/crate2/src/lib.nr @@ -2,7 +2,7 @@ struct MyStruct { Q: Field, } -impl dep::crate1::MyTrait for MyStruct { +impl crate1::MyTrait for MyStruct { fn Add10(&mut self) { self.Q += 10; } diff --git a/test_programs/execution_success/traits_in_crates_2/src/main.nr b/test_programs/execution_success/traits_in_crates_2/src/main.nr index 7ba2f63c5c0..2afec29ee1f 100644 --- a/test_programs/execution_success/traits_in_crates_2/src/main.nr +++ b/test_programs/execution_success/traits_in_crates_2/src/main.nr @@ -1,5 +1,5 @@ fn main(x: Field, y: pub Field) { - let mut V = dep::crate2::MyStruct { Q: x }; + let mut V = crate2::MyStruct { Q: x }; V.Add10(); assert(V.Q == 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 62e03c153ed..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 @@ -7,4 +7,4 @@ fn test_different_string() { #[test(should_fail_with = "Definitely Not equal!")] fn test_wrong_expectation() { assert_eq(0, 1, "Not equal"); -} \ No newline at end of file +} 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/test_programs/test_libraries/diamond_deps_1/src/lib.nr b/test_programs/test_libraries/diamond_deps_1/src/lib.nr index 60c001ec64e..d76ce5a05e9 100644 --- a/test_programs/test_libraries/diamond_deps_1/src/lib.nr +++ b/test_programs/test_libraries/diamond_deps_1/src/lib.nr @@ -1,4 +1,4 @@ -use dep::dep2::call_dep2; +use dep2::call_dep2; pub fn call_dep1_then_dep2(x: Field, y: Field) -> Field { call_dep2(x, y) diff --git a/test_programs/test_libraries/reexporting_lib/src/lib.nr b/test_programs/test_libraries/reexporting_lib/src/lib.nr index f12dfe01ecd..1bced548304 100644 --- a/test_programs/test_libraries/reexporting_lib/src/lib.nr +++ b/test_programs/test_libraries/reexporting_lib/src/lib.nr @@ -1,3 +1,3 @@ -use dep::exporting_lib::{MyStruct, FooStruct}; +use exporting_lib::{MyStruct, FooStruct}; -use dep::exporting_lib as lib; +use exporting_lib as lib; diff --git a/tooling/fuzzer/src/dictionary/mod.rs b/tooling/fuzzer/src/dictionary/mod.rs index bf2ab87be29..a45b9c3abb2 100644 --- a/tooling/fuzzer/src/dictionary/mod.rs +++ b/tooling/fuzzer/src/dictionary/mod.rs @@ -10,7 +10,7 @@ use acvm::{ circuit::{ brillig::{BrilligBytecode, BrilligInputs}, directives::Directive, - opcodes::{BlackBoxFuncCall, FunctionInput}, + opcodes::{BlackBoxFuncCall, ConstantOrWitnessEnum, FunctionInput}, Circuit, Opcode, Program, }, native_types::Expression, @@ -84,7 +84,15 @@ fn build_dictionary_from_circuit(circuit: &Circuit) -> HashSet< } Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { - input: FunctionInput { num_bits, .. }, + 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)); 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/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/tooling/lsp/test_programs/rename/Nargo.toml b/tooling/lsp/test_programs/rename/Nargo.toml new file mode 100644 index 00000000000..2d5b6415dc9 --- /dev/null +++ b/tooling/lsp/test_programs/rename/Nargo.toml @@ -0,0 +1,6 @@ +[package] +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_cli/Cargo.toml b/tooling/nargo_cli/Cargo.toml index e0e54449a6f..c6cf842a623 100644 --- a/tooling/nargo_cli/Cargo.toml +++ b/tooling/nargo_cli/Cargo.toml @@ -43,12 +43,7 @@ prettytable-rs = "0.10" rayon = "1.8.0" thiserror.workspace = true tower.workspace = true -async-lsp = { workspace = true, features = [ - "client-monitor", - "stdio", - "tracing", - "tokio", -] } +async-lsp = { workspace = true, features = ["client-monitor", "stdio", "tracing", "tokio"] } const_format.workspace = true similar-asserts.workspace = true termcolor = "1.1.2" diff --git a/tooling/nargo_cli/build.rs b/tooling/nargo_cli/build.rs index a6873910524..9dfa0dfe861 100644 --- a/tooling/nargo_cli/build.rs +++ b/tooling/nargo_cli/build.rs @@ -61,12 +61,16 @@ 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; 5] = [ +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 read_test_cases( 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 bf6614860e2..10711b6d011 100644 --- a/tooling/nargo_cli/tests/stdlib-tests.rs +++ b/tooling/nargo_cli/tests/stdlib-tests.rs @@ -32,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, false, 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"); diff --git a/tooling/nargo_fmt/tests/expected/contract.nr b/tooling/nargo_fmt/tests/expected/contract.nr index 14b1af4a848..e3a5877725a 100644 --- a/tooling/nargo_fmt/tests/expected/contract.nr +++ b/tooling/nargo_fmt/tests/expected/contract.nr @@ -3,11 +3,11 @@ // Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. // Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. contract Benchmarking { - use dep::aztec::protocol_types::abis::function_selector::FunctionSelector; + use aztec::protocol_types::abis::function_selector::FunctionSelector; use value_note::{utils::{increment, decrement}, value_note::{VALUE_NOTE_LEN, ValueNote, ValueNoteMethods}}; - use dep::aztec::{ + use aztec::{ context::Context, note::{note_getter_options::NoteGetterOptions, note_header::NoteHeader}, log::emit_unencrypted_log, state_vars::{Map, PublicMutable, PrivateSet}, types::type_serialization::field_serialization::{FieldSerializationMethods, FIELD_SERIALIZED_LEN}, diff --git a/tooling/nargo_fmt/tests/expected/let.nr b/tooling/nargo_fmt/tests/expected/let.nr index 7ff69e74306..0edc0eaf922 100644 --- a/tooling/nargo_fmt/tests/expected/let.nr +++ b/tooling/nargo_fmt/tests/expected/let.nr @@ -51,10 +51,10 @@ fn let_() { let expr = MyExpr { /*A boolean literal (true, false).*/ kind: ExprKind::Bool(true) }; - let mut V = dep::crate2::MyStruct { Q: x }; - let mut V = dep::crate2::MyStruct {}; - let mut V = dep::crate2::MyStruct {/*test*/}; - let mut V = dep::crate2::MyStruct { + let mut V = crate2::MyStruct { Q: x }; + let mut V = crate2::MyStruct {}; + let mut V = crate2::MyStruct {/*test*/}; + let mut V = crate2::MyStruct { // sad }; } diff --git a/tooling/nargo_fmt/tests/input/contract.nr b/tooling/nargo_fmt/tests/input/contract.nr index 14b1af4a848..e3a5877725a 100644 --- a/tooling/nargo_fmt/tests/input/contract.nr +++ b/tooling/nargo_fmt/tests/input/contract.nr @@ -3,11 +3,11 @@ // Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. // Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. contract Benchmarking { - use dep::aztec::protocol_types::abis::function_selector::FunctionSelector; + use aztec::protocol_types::abis::function_selector::FunctionSelector; use value_note::{utils::{increment, decrement}, value_note::{VALUE_NOTE_LEN, ValueNote, ValueNoteMethods}}; - use dep::aztec::{ + use aztec::{ context::Context, note::{note_getter_options::NoteGetterOptions, note_header::NoteHeader}, log::emit_unencrypted_log, state_vars::{Map, PublicMutable, PrivateSet}, types::type_serialization::field_serialization::{FieldSerializationMethods, FIELD_SERIALIZED_LEN}, diff --git a/tooling/nargo_fmt/tests/input/let.nr b/tooling/nargo_fmt/tests/input/let.nr index 37cdc6655c7..16ce0a9d7f1 100644 --- a/tooling/nargo_fmt/tests/input/let.nr +++ b/tooling/nargo_fmt/tests/input/let.nr @@ -26,10 +26,10 @@ kind: ExprKind::Bool(true), let expr = MyExpr {/*A boolean literal (true, false).*/kind: ExprKind::Bool(true),}; - let mut V = dep::crate2::MyStruct { Q: x }; - let mut V = dep::crate2::MyStruct {}; - let mut V = dep::crate2::MyStruct {/*test*/}; - let mut V = dep::crate2::MyStruct { + let mut V = crate2::MyStruct { Q: x }; + let mut V = crate2::MyStruct {}; + let mut V = crate2::MyStruct {/*test*/}; + let mut V = crate2::MyStruct { // sad }; } diff --git a/tooling/noir_js_backend_barretenberg/package.json b/tooling/noir_js_backend_barretenberg/package.json index b0c9e85315a..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.43.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 96c4d13aa61..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, @@ -60,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(); @@ -144,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/profiler/Cargo.toml b/tooling/profiler/Cargo.toml index d33e99f1a4c..0ccd56b791f 100644 --- a/tooling/profiler/Cargo.toml +++ b/tooling/profiler/Cargo.toml @@ -18,12 +18,10 @@ path = "src/main.rs" color-eyre.workspace = true clap.workspace = true noirc_artifacts.workspace = true -nargo.workspace = true const_format.workspace = true serde.workspace = true serde_json.workspace = true fm.workspace = true -codespan-reporting.workspace = true inferno = "0.11.19" im.workspace = true acir.workspace = true diff --git a/tooling/profiler/src/cli/gates_flamegraph_cmd.rs b/tooling/profiler/src/cli/gates_flamegraph_cmd.rs index 38e7fff5f42..154ac38f4bb 100644 --- a/tooling/profiler/src/cli/gates_flamegraph_cmd.rs +++ b/tooling/profiler/src/cli/gates_flamegraph_cmd.rs @@ -1,19 +1,13 @@ -use std::collections::BTreeMap; -use std::io::BufWriter; use std::path::{Path, PathBuf}; -use std::process::Command; use clap::Args; -use codespan_reporting::files::Files; use color_eyre::eyre::{self, Context}; -use inferno::flamegraph::{from_lines, Options}; -use serde::{Deserialize, Serialize}; -use acir::circuit::OpcodeLocation; -use nargo::errors::Location; use noirc_artifacts::debug::DebugArtifact; -use noirc_artifacts::program::ProgramArtifact; -use noirc_errors::reporter::line_and_column_from_span; + +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 { @@ -30,85 +24,11 @@ pub(crate) struct GatesFlamegraphCommand { output: String, } -trait GatesProvider { - fn get_gates(&self, artifact_path: &Path) -> eyre::Result; -} - -struct BackendGatesProvider { - 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) - } -} - -trait FlamegraphGenerator { - fn generate_flamegraph<'lines, I: IntoIterator>( - &self, - folded_lines: I, - artifact_name: &str, - function_name: &str, - output_path: &Path, - ) -> eyre::Result<()>; -} - -struct InfernoFlamegraphGenerator {} - -impl FlamegraphGenerator for InfernoFlamegraphGenerator { - fn generate_flamegraph<'lines, I: IntoIterator>( - &self, - folded_lines: I, - artifact_name: &str, - function_name: &str, - output_path: &Path, - ) -> eyre::Result<()> { - 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.subtitle = Some("Sample = Gate".to_string()); - options.frame_height = 24; - options.color_diffusion = true; - - from_lines(&mut options, folded_lines, flamegraph_writer)?; - - Ok(()) - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -struct BackendGatesReport { - acir_opcodes: usize, - circuit_size: usize, - gates_per_opcode: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -struct BackendGatesResponse { - functions: Vec, -} - -struct FoldedStackItem { - total_gates: usize, - nested_items: BTreeMap, -} - 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 {}, + &InfernoFlamegraphGenerator { count_name: "gates".to_string() }, &PathBuf::from(args.output), ) } @@ -119,7 +39,7 @@ fn run_with_provider( flamegraph_generator: &Generator, output_path: &Path, ) -> eyre::Result<()> { - let program = + let mut program = read_program_from_file(artifact_path).context("Error reading program from file")?; let backend_gates_response = @@ -127,10 +47,16 @@ fn run_with_provider( 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)) in - backend_gates_response.functions.into_iter().zip(function_names).enumerate() + 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: {}", @@ -139,143 +65,33 @@ fn run_with_provider( func_gates.circuit_size ); - // 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(); - - func_gates.gates_per_opcode.into_iter().enumerate().for_each(|(opcode_index, gates)| { - let call_stack = &debug_artifact.debug_symbols[func_idx] - .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, &debug_artifact)) - .collect::>() - } else { - vec!["unknown".to_string()] - }; - - add_locations_to_folded_stack_items(&mut folded_stack_items, location_names, gates); - }); - let folded_lines = to_folded_sorted_lines(&folded_stack_items, Default::default()); - flamegraph_generator.generate_flamegraph( - folded_lines.iter().map(|as_string| as_string.as_str()), + 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!("{}.svg", &func_name))), + &Path::new(&output_path).join(Path::new(&format!("{}_gates.svg", &func_name))), )?; } Ok(()) } -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) -} - -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_insert(FoldedStackItem { total_gates: 0, nested_items: BTreeMap::new() }); - - child_map = &mut current_item.nested_items; - - if index == locations.len() - 1 { - current_item.total_gates += 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 { - folded_stack_items - .iter() - .flat_map(move |(location, folded_stack_item)| { - 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_gates); - - let mut new_parent_stacks = parent_stacks.clone(); - new_parent_stacks.push_back(location.clone()); - - let child_lines: Vec = - to_folded_sorted_lines(&folded_stack_item.nested_items, new_parent_stacks); - - std::iter::once(line).chain(child_lines) - }) - .collect() -} - #[cfg(test)] mod tests { - use acir::circuit::{OpcodeLocation, Program}; + use acir::circuit::{Circuit, Opcode, Program}; use color_eyre::eyre::{self}; - use fm::{FileId, FileManager}; + use fm::codespan_files::Files; use noirc_artifacts::program::ProgramArtifact; - use noirc_driver::DebugFile; - use noirc_errors::{ - debug_info::{DebugInfo, ProgramDebugInfo}, - Location, Span, - }; + use noirc_errors::debug_info::{DebugInfo, ProgramDebugInfo}; use std::{ - cell::RefCell, collections::{BTreeMap, HashMap}, path::{Path, PathBuf}, }; - use tempfile::TempDir; - use super::{BackendGatesReport, BackendGatesResponse, GatesProvider}; + use crate::gates_provider::{BackendGatesReport, BackendGatesResponse, GatesProvider}; struct TestGateProvider { mock_responses: HashMap, @@ -293,194 +109,63 @@ mod tests { } #[derive(Default)] - struct TestFlamegraphGenerator { - lines_received: RefCell>>, - } + struct TestFlamegraphGenerator {} impl super::FlamegraphGenerator for TestFlamegraphGenerator { - fn generate_flamegraph<'lines, I: IntoIterator>( + fn generate_flamegraph<'files, F>( &self, - folded_lines: I, + _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: &std::path::Path, + output_path: &Path, ) -> eyre::Result<()> { - let lines = folded_lines.into_iter().map(|line| line.to_string()).collect(); - self.lines_received.borrow_mut().push(lines); - Ok(()) - } - } + 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(); - 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; + Ok(()) } - spans } - struct TestCase { - expected_folded_sorted_lines: Vec>, - debug_symbols: ProgramDebugInfo, - file_map: BTreeMap, - gates_report: BackendGatesResponse, - } + #[test] + fn smoke_test() { + let temp_dir = tempfile::tempdir().unwrap(); - fn simple_test_case(temp_dir: &TempDir) -> TestCase { - let source_code = r##" - fn main() { - foo(); - bar(); - whatever(); - } - fn foo() { - baz(); - } - fn bar () { - whatever() - } - fn baz () { - whatever() - } - "##; + let artifact_path = temp_dir.path().join("test.json"); - let source_file_name = Path::new("main.nr"); - 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 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()], + }; - 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); + // 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 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, + let mock_gates_response = BackendGatesResponse { + functions: vec![ + (BackendGatesReport { acir_opcodes: 0, gates_per_opcode: vec![], circuit_size: 0 }), ], - ); - - // 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 file_map = BTreeMap::from_iter(vec![( - file_id, - DebugFile { source: source_code.to_string(), path: source_file_name.to_path_buf() }, - )]); - - let debug_symbols = ProgramDebugInfo { - debug_infos: vec![DebugInfo::new( - opcode_locations, - BTreeMap::default(), - BTreeMap::default(), - BTreeMap::default(), - )], }; - let backend_gates_response = BackendGatesResponse { - functions: vec![BackendGatesReport { - acir_opcodes: 3, - circuit_size: 100, - gates_per_opcode: vec![10, 20, 30], - }], + let provider = TestGateProvider { + mock_responses: HashMap::from([(artifact_path.clone(), mock_gates_response)]), }; + let flamegraph_generator = TestFlamegraphGenerator::default(); - let expected_folded_sorted_lines = vec![ - "main.nr:2:9::fn main() 0".to_string(), - "main.nr:2:9::fn main();main.nr:3:13::foo() 0".to_string(), - "main.nr:2:9::fn main();main.nr:3:13::foo();main.nr:8:13::baz() 0".to_string(), - "main.nr:2:9::fn main();main.nr:3:13::foo();main.nr:8:13::baz();main.nr:14:13::whatever() 10".to_string(), - "main.nr:2:9::fn main();main.nr:4:13::bar() 0".to_string(), - "main.nr:2:9::fn main();main.nr:4:13::bar();main.nr:11:13::whatever() 20".to_string(), - "main.nr:2:9::fn main();main.nr:5:13::whatever() 30".to_string(), - ]; - - TestCase { - expected_folded_sorted_lines: vec![expected_folded_sorted_lines], - debug_symbols, - file_map, - gates_report: backend_gates_response, - } - } - - #[test] - fn test_flamegraph() { - let temp_dir = tempfile::tempdir().unwrap(); - - let test_cases = vec![simple_test_case(&temp_dir)]; - let artifact_names: Vec<_> = - test_cases.iter().enumerate().map(|(idx, _)| format!("test{}.json", idx)).collect(); - - let test_cases_with_names: Vec<_> = test_cases.into_iter().zip(artifact_names).collect(); - - let mut mock_responses: HashMap = HashMap::new(); - // Collect mock responses - for (test_case, artifact_name) in test_cases_with_names.iter() { - mock_responses.insert( - temp_dir.path().join(artifact_name.clone()), - test_case.gates_report.clone(), - ); - } - - let provider = TestGateProvider { mock_responses }; - - for (test_case, artifact_name) in test_cases_with_names.iter() { - let artifact_path = temp_dir.path().join(artifact_name.clone()); - - let artifact = ProgramArtifact { - noir_version: "0.0.0".to_string(), - hash: 27, - abi: noirc_abi::Abi::default(), - bytecode: Program::default(), - debug_symbols: test_case.debug_symbols.clone(), - file_map: test_case.file_map.clone(), - 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_provider( - &artifact_path, - &provider, - &flamegraph_generator, - temp_dir.path(), - ) + super::run_with_provider(&artifact_path, &provider, &flamegraph_generator, temp_dir.path()) .expect("should run without errors"); - // Check that the flamegraph generator was called with the correct folded sorted lines - let calls_received = flamegraph_generator.lines_received.borrow().clone(); - - assert_eq!(calls_received, test_case.expected_folded_sorted_lines); - } + // 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 index d54a3f6167c..4c2503fbe4f 100644 --- a/tooling/profiler/src/cli/mod.rs +++ b/tooling/profiler/src/cli/mod.rs @@ -3,6 +3,7 @@ use color_eyre::eyre; use const_format::formatcp; mod gates_flamegraph_cmd; +mod opcodes_flamegraph_cmd; const PROFILER_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -12,20 +13,22 @@ static VERSION_STRING: &str = formatcp!("version = {}\n", PROFILER_VERSION,); #[command(name="Noir profiler", author, version=VERSION_STRING, about, long_about = None)] struct ProfilerCli { #[command(subcommand)] - command: GatesFlamegraphCommand, + command: ProfilerCommand, } #[non_exhaustive] #[derive(Subcommand, Clone, Debug)] -enum GatesFlamegraphCommand { +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 { - GatesFlamegraphCommand::GatesFlamegraph(args) => gates_flamegraph_cmd::run(args), + ProfilerCommand::GatesFlamegraph(args) => gates_flamegraph_cmd::run(args), + ProfilerCommand::OpcodesFlamegraph(args) => opcodes_flamegraph_cmd::run(args), } .map_err(|err| eyre::eyre!("{}", err))?; 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 index 8e08644de23..215feb0a4e7 100644 --- a/tooling/profiler/src/main.rs +++ b/tooling/profiler/src/main.rs @@ -4,6 +4,10 @@ #![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; 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 73dfbf6e82e..dc033a7344f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -221,9 +221,9 @@ __metadata: languageName: node linkType: hard -"@aztec/bb.js@npm:0.43.0": - version: 0.43.0 - resolution: "@aztec/bb.js@npm:0.43.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: 63d2617529e00a05e1ac9364639dc10761e50cb6a16e010ac6354011440de037112a82d7cdd29a65b139af528c7d865b047e157b25d15ac36ff701863d550a5b + 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.43.0 + "@aztec/bb.js": 0.46.1 "@noir-lang/types": "workspace:*" "@types/node": ^20.6.2 "@types/prettier": ^3