diff --git a/.PR_BODY_MESSAGE b/.PR_BODY_MESSAGE new file mode 100644 index 000000000000..a723d46c574f --- /dev/null +++ b/.PR_BODY_MESSAGE @@ -0,0 +1,7 @@ +chore: remove unnecessary trait bounds (https://github.com/noir-lang/noir/pull/7635) +feat: add optional oracle resolver url in `acvm_cli` (https://github.com/noir-lang/noir/pull/7630) +chore: Rename `StructDefinition` to `TypeDefinition` (https://github.com/noir-lang/noir/pull/7614) +fix: Error on infinitely recursive types (https://github.com/noir-lang/noir/pull/7579) +fix: update error message to display 128 bits as valid bit size (https://github.com/noir-lang/noir/pull/7626) +chore: update docs to reflect u128 type (https://github.com/noir-lang/noir/pull/7623) +feat: array concat method (https://github.com/noir-lang/noir/pull/7199) diff --git a/.noir-sync-commit b/.noir-sync-commit index 73dc17fe6af1..5be2bf4f42bb 100644 --- a/.noir-sync-commit +++ b/.noir-sync-commit @@ -1 +1 @@ -5a3d2bc0e13a12b039c793c73d7817924c13e159 +8e0e5abb66cb43ea2350047f539b914195c2324e diff --git a/noir-repo b/noir-repo new file mode 160000 index 000000000000..1fa0dd95a95b --- /dev/null +++ b/noir-repo @@ -0,0 +1 @@ +Subproject commit 1fa0dd95a95b01652332e94952ade14019a51fd1 diff --git a/noir/noir-repo-ref b/noir/noir-repo-ref new file mode 100644 index 000000000000..25b3bca45ee0 --- /dev/null +++ b/noir/noir-repo-ref @@ -0,0 +1 @@ +nightly-2025-03-10 diff --git a/noir/noir-repo/.github/benchmark_projects.yml b/noir/noir-repo/.github/benchmark_projects.yml index 5d9266a2d1a5..3e63a12ec02a 100644 --- a/noir/noir-repo/.github/benchmark_projects.yml +++ b/noir/noir-repo/.github/benchmark_projects.yml @@ -1,4 +1,4 @@ -define: &AZ_COMMIT 3b981f9217f9b859bdfbcdba2f5c080392c98da6 +define: &AZ_COMMIT fd9f1458557f2d67bcf2d58ba4194e000f58600c projects: private-kernel-inner: repo: AztecProtocol/aztec-packages diff --git a/noir/noir-repo/.github/workflows/publish-nargo.yml b/noir/noir-repo/.github/workflows/publish-nargo.yml index e18dac52ca48..a0c9247f0a4f 100644 --- a/noir/noir-repo/.github/workflows/publish-nargo.yml +++ b/noir/noir-repo/.github/workflows/publish-nargo.yml @@ -54,6 +54,8 @@ jobs: cargo build --package nargo_cli --release --target ${{ matrix.target }} --no-default-features --features "${{ inputs.features }}" cargo build --package noir_profiler --release --target ${{ matrix.target }} --no-default-features --features "${{ inputs.features }}" cargo build --package noir_inspector --release --target ${{ matrix.target }} --no-default-features --features "${{ inputs.features }}" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Package artifacts run: | @@ -237,5 +239,3 @@ jobs: make_latest: false overwrite: true tag: ${{ format('{0}-{1}', 'nightly', steps.date.outputs.date) }} - - diff --git a/noir/noir-repo/.github/workflows/reports.yml b/noir/noir-repo/.github/workflows/reports.yml index 9219f90e4d56..4bd3605498f6 100644 --- a/noir/noir-repo/.github/workflows/reports.yml +++ b/noir/noir-repo/.github/workflows/reports.yml @@ -42,6 +42,8 @@ jobs: - name: Build Nargo run: cargo build --package nargo_cli --release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Package artifacts run: | @@ -305,7 +307,7 @@ jobs: repository: ${{ matrix.repo }} path: test-repo ref: ${{ matrix.ref }} - + - name: Fetch noir dependencies working-directory: ./test-repo/${{ matrix.path }} run: | @@ -341,7 +343,7 @@ jobs: path: ${{ steps.compilation_report.outputs.report_path }} retention-days: 3 overwrite: true - + - name: Generate execution report id: execution_report working-directory: ./test-repo/${{ matrix.path }} @@ -359,7 +361,7 @@ jobs: echo "report_name=$REPORT_NAME" >> $GITHUB_OUTPUT echo "report_path=$REPORT_PATH" >> $GITHUB_OUTPUT - + - name: Upload execution report if: ${{ !matrix.cannot_execute }} uses: actions/upload-artifact@v4 @@ -716,7 +718,7 @@ jobs: - upload_compilation_memory_report - upload_execution_report - upload_execution_memory_report - + steps: - name: Report overall success run: | diff --git a/noir/noir-repo/.github/workflows/test-js-packages.yml b/noir/noir-repo/.github/workflows/test-js-packages.yml index 9c523554167f..d65533d11cb2 100644 --- a/noir/noir-repo/.github/workflows/test-js-packages.yml +++ b/noir/noir-repo/.github/workflows/test-js-packages.yml @@ -64,6 +64,8 @@ jobs: - name: Build Nargo run: cargo build --package nargo_cli --release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Package artifacts run: | @@ -535,7 +537,7 @@ jobs: fail-fast: false matrix: include: ${{ fromJson( needs.critical-library-list.outputs.libraries )}} - + name: Check external repo - ${{ matrix.repo }}/${{ matrix.path }} steps: - name: Checkout diff --git a/noir/noir-repo/Cargo.lock b/noir/noir-repo/Cargo.lock index fff8a72fd922..7a64f0ec49e1 100644 --- a/noir/noir-repo/Cargo.lock +++ b/noir/noir-repo/Cargo.lock @@ -6,14 +6,23 @@ version = 4 name = "acir" version = "1.0.0-beta.3" dependencies = [ + "acir", "acir_field", "base64 0.21.7", "bincode", "brillig", + "color-eyre", "criterion", "flate2", "fxhash", + "noir_protobuf", + "num-bigint", "pprof", + "proptest", + "proptest-derive", + "prost", + "prost-build", + "protoc-prebuilt", "serde", "serde-big-array", "serde-generate", @@ -643,9 +652,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.7.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c103cbbedac994e292597ab79342dbd5b306a362045095db54917d92a9fdfd92" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "binary-merge" @@ -789,6 +798,8 @@ name = "brillig" version = "1.0.0-beta.3" dependencies = [ "acir_field", + "proptest", + "proptest-derive", "serde", ] @@ -3000,6 +3011,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "multimap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" + [[package]] name = "nargo" version = "1.0.0-beta.3" @@ -3300,6 +3317,14 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "noir_protobuf" +version = "1.0.0-beta.3" +dependencies = [ + "color-eyre", + "prost", +] + [[package]] name = "noir_wasm" version = "1.0.0-beta.3" @@ -3961,6 +3986,16 @@ dependencies = [ "termtree", ] +[[package]] +name = "prettyplease" +version = "0.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1ccf34da56fc294e7d4ccf69a85992b7dfb826b7cf57bac6a70bba3494cc08a" +dependencies = [ + "proc-macro2", + "syn 2.0.96", +] + [[package]] name = "prettytable-rs" version = "0.10.0" @@ -4030,6 +4065,68 @@ dependencies = [ "syn 2.0.96", ] +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" +dependencies = [ + "heck 0.5.0", + "itertools 0.13.0", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn 2.0.96", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools 0.13.0", + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "prost-types" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +dependencies = [ + "prost", +] + +[[package]] +name = "protoc-prebuilt" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d85d4641fe3b8c6e853dfd09fe35379bc6b6e66bd692ac29ed4f7087de69ed5" +dependencies = [ + "ureq", + "zip", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -4258,9 +4355,9 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.11" +version = "0.17.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da5349ae27d3887ca812fb375b45a4fbb36d8d12d2df394968cd86e35683fe73" +checksum = "ed9b823fa29b721a59671b41d6b06e66b29e0628e207e8b1c3ceeda701ec928d" dependencies = [ "cc", "cfg-if", @@ -5548,6 +5645,21 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" +dependencies = [ + "base64 0.22.1", + "log", + "once_cell", + "rustls", + "rustls-pki-types", + "url", + "webpki-roots", +] + [[package]] name = "url" version = "2.5.4" @@ -6130,6 +6242,18 @@ dependencies = [ "syn 2.0.96", ] +[[package]] +name = "zip" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +dependencies = [ + "byteorder", + "crc32fast", + "crossbeam-utils", + "flate2", +] + [[package]] name = "zkhash" version = "0.2.0" diff --git a/noir/noir-repo/Cargo.toml b/noir/noir-repo/Cargo.toml index 9c5bf1351d1c..308609aa511b 100644 --- a/noir/noir-repo/Cargo.toml +++ b/noir/noir-repo/Cargo.toml @@ -37,6 +37,7 @@ members = [ "acvm-repo/bn254_blackbox_solver", # Utility crates "utils/iter-extended", + "utils/protobuf", ] default-members = [ "tooling/nargo_cli", @@ -94,6 +95,7 @@ noirc_abi = { path = "tooling/noirc_abi" } noirc_artifacts = { path = "tooling/noirc_artifacts" } noirc_artifacts_info = { path = "tooling/noirc_artifacts_info" } noir_artifact_cli = { path = "tooling/artifact_cli" } +noir_protobuf = { path = "utils/protobuf" } # Arkworks ark-bn254 = { version = "^0.5.0", default-features = false, features = [ @@ -137,6 +139,11 @@ criterion = "0.5.0" # https://github.com/tikv/pprof-rs/pull/172 pprof = { version = "0.14", features = ["flamegraph", "criterion"] } +# Protobuf +prost = "0.13" +prost-build = "0.13" +protoc-prebuilt = "0.3" + cfg-if = "1.0.0" dirs = "4" serde = { version = "1.0.136", features = ["derive"] } diff --git a/noir/noir-repo/EXTERNAL_NOIR_LIBRARIES.yml b/noir/noir-repo/EXTERNAL_NOIR_LIBRARIES.yml index 1d86c3e5d5b1..b4eece545a46 100644 --- a/noir/noir-repo/EXTERNAL_NOIR_LIBRARIES.yml +++ b/noir/noir-repo/EXTERNAL_NOIR_LIBRARIES.yml @@ -1,4 +1,4 @@ -define: &AZ_COMMIT 3b981f9217f9b859bdfbcdba2f5c080392c98da6 +define: &AZ_COMMIT fd9f1458557f2d67bcf2d58ba4194e000f58600c libraries: noir_check_shuffle: repo: noir-lang/noir_check_shuffle diff --git a/noir/noir-repo/acvm-repo/acir/Cargo.toml b/noir/noir-repo/acvm-repo/acir/Cargo.toml index 2b15c2abf095..5d7b347c511b 100644 --- a/noir/noir-repo/acvm-repo/acir/Cargo.toml +++ b/noir/noir-repo/acvm-repo/acir/Cargo.toml @@ -18,15 +18,26 @@ workspace = true [dependencies] acir_field.workspace = true brillig.workspace = true +noir_protobuf.workspace = true + +color-eyre.workspace = true serde.workspace = true thiserror.workspace = true flate2.workspace = true bincode.workspace = true base64.workspace = true +prost.workspace = true serde-big-array = "0.5.1" strum = { workspace = true } strum_macros = { workspace = true } +proptest = { workspace = true, optional = true } +proptest-derive = { workspace = true, optional = true } + +[build-dependencies] +prost-build.workspace = true +protoc-prebuilt.workspace = true + [dev-dependencies] serde_json = "1.0" serde-reflection = "0.3.6" @@ -34,10 +45,15 @@ serde-generate = "0.25.1" fxhash.workspace = true criterion.workspace = true pprof.workspace = true +num-bigint.workspace = true + +acir = { path = ".", features = ["arb"] } # Self to turn on `arb`. [features] +default = [] bn254 = ["acir_field/bn254"] bls12_381 = ["acir_field/bls12_381"] +arb = ["proptest", "proptest-derive", "brillig/arb"] [[bench]] name = "serialization" diff --git a/noir/noir-repo/acvm-repo/acir/build.rs b/noir/noir-repo/acvm-repo/acir/build.rs new file mode 100644 index 000000000000..e52a06fbce4b --- /dev/null +++ b/noir/noir-repo/acvm-repo/acir/build.rs @@ -0,0 +1,24 @@ +use std::path::Path; + +fn main() { + let (protoc_bin, include_dir) = + protoc_prebuilt::init("29.3").expect("failed to initialize protoc"); + + unsafe { + std::env::set_var("PROTOC", protoc_bin); + } + + prost_build::compile_protos( + &[ + // DTOs for a `Program`, which work with the types in `acir.cpp` + "./src/proto/program.proto", + // DTOs for the `WitnessStack`, which work with the types in `witness.cpp` + "./src/proto/acir/witness.proto", + // A pared down DTO for `Program`, so Barretenberg can ignore the Brillig part. + // This is only included to make sure it compiles. + "./src/proto/acir/program.proto", + ], + &[Path::new("./src/proto"), include_dir.as_path()], + ) + .expect("failed to compile .proto schemas"); +} diff --git a/noir/noir-repo/acvm-repo/acir/src/circuit/brillig.rs b/noir/noir-repo/acvm-repo/acir/src/circuit/brillig.rs index ef75d088f8c4..972a08dd32a5 100644 --- a/noir/noir-repo/acvm-repo/acir/src/circuit/brillig.rs +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/brillig.rs @@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize}; /// Inputs for the Brillig VM. These are the initial inputs /// that the Brillig VM will use to start. #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub enum BrilligInputs { Single(Expression), Array(Vec>), @@ -15,6 +16,7 @@ pub enum BrilligInputs { /// Outputs for the Brillig VM. Once the VM has completed /// execution, this will be the object that is returned. #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub enum BrilligOutputs { Simple(Witness), Array(Vec), @@ -24,6 +26,7 @@ pub enum BrilligOutputs { /// a full Brillig function to be executed by the Brillig VM. /// This is stored separately on a program and accessed through a [BrilligPointer]. #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default, Debug, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub struct BrilligBytecode { pub bytecode: Vec>, } @@ -32,6 +35,7 @@ pub struct BrilligBytecode { #[derive( Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash, Copy, Default, PartialOrd, Ord, )] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] #[serde(transparent)] pub struct BrilligFunctionId(pub u32); diff --git a/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs b/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs index 68c3c832b5cd..2f7bb92b184b 100644 --- a/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs @@ -2,8 +2,12 @@ pub mod black_box_functions; pub mod brillig; pub mod opcodes; -use crate::native_types::{Expression, Witness}; +use crate::{ + native_types::{Expression, Witness}, + proto::convert::ProtoSchema, +}; use acir_field::AcirField; +use noir_protobuf::ProtoCodec as _; pub use opcodes::Opcode; use thiserror::Error; @@ -26,6 +30,7 @@ use self::{brillig::BrilligBytecode, opcodes::BlockId}; /// into a proving system which supports PLONK, where arithmetic expressions have a /// finite fan-in. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub enum ExpressionWidth { #[default] Unbounded, @@ -37,13 +42,15 @@ pub enum ExpressionWidth { /// A program represented by multiple ACIR circuits. The execution trace of these /// circuits is dictated by construction of the [crate::native_types::WitnessStack]. #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default, Hash)] -pub struct Program { +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] +pub struct Program { pub functions: Vec>, pub unconstrained_functions: Vec>, } #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default, Hash)] -pub struct Circuit { +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] +pub struct Circuit { // current_witness_index is the highest witness index in the circuit. The next witness to be added to this circuit // will take on this value. (The value is cached here as an optimization.) pub current_witness_index: u32, @@ -70,12 +77,14 @@ pub struct Circuit { } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub enum ExpressionOrMemory { Expression(Expression), Memory(BlockId), } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub struct AssertionPayload { pub error_selector: u64, pub payload: Vec>, @@ -137,6 +146,7 @@ pub struct ResolvedOpcodeLocation { } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] /// Opcodes are locatable so that callers can /// map opcodes to debug information related to their context. pub enum OpcodeLocation { @@ -218,7 +228,7 @@ impl std::fmt::Display for BrilligOpcodeLocation { } } -impl Circuit { +impl Circuit { pub fn num_vars(&self) -> u32 { self.current_witness_index + 1 } @@ -237,9 +247,9 @@ impl Circuit { } } -impl Program { +impl Program { fn write(&self, writer: W) -> std::io::Result<()> { - let buf = bincode::serialize(self).unwrap(); + let buf = self.bincode_serialize()?; let mut encoder = flate2::write::GzEncoder::new(writer, Compression::default()); encoder.write_all(&buf)?; encoder.finish()?; @@ -263,13 +273,39 @@ impl Program { } } -impl Deserialize<'a>> Program { +impl Program { + /// Serialize the program using `bincode`, which is what we have to use until Barretenberg can read another format. + pub(crate) fn bincode_serialize(&self) -> std::io::Result> { + bincode::serialize(self).map_err(std::io::Error::other) + } +} + +impl Deserialize<'a>> Program { + pub(crate) fn bincode_deserialize(buf: &[u8]) -> std::io::Result { + bincode::deserialize(buf) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e)) + } +} + +#[allow(dead_code)] // TODO: Remove once we switch to protobuf +impl Program { + /// Serialize the program using `protobuf`, which is what we try to replace `bincode` with. + pub(crate) fn proto_serialize(&self) -> Vec { + ProtoSchema::::serialize_to_vec(self) + } + pub(crate) fn proto_deserialize(buf: &[u8]) -> std::io::Result { + ProtoSchema::::deserialize_from_vec(buf) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e)) + } +} + +impl Deserialize<'a>> Program { fn read(reader: R) -> std::io::Result { let mut gz_decoder = flate2::read::GzDecoder::new(reader); - let mut buf_d = Vec::new(); - gz_decoder.read_to_end(&mut buf_d)?; - bincode::deserialize(&buf_d) - .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidInput, err)) + let mut buf = Vec::new(); + gz_decoder.read_to_end(&mut buf)?; + let program = Self::bincode_deserialize(&buf)?; + Ok(program) } /// Deserialize bytecode. @@ -357,6 +393,7 @@ impl std::fmt::Debug for Program { } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub struct PublicInputs(pub BTreeSet); impl PublicInputs { @@ -477,4 +514,153 @@ mod tests { Program::deserialize_program(&zipped_bad_circuit); assert!(deserialization_result.is_err()); } + + /// Property based testing for serialization + mod props { + use acir_field::FieldElement; + use proptest::prelude::*; + use proptest::test_runner::{TestCaseResult, TestRunner}; + + use crate::circuit::Program; + use crate::native_types::{WitnessMap, WitnessStack}; + + // It's not possible to set the maximum size of collections via `ProptestConfig`, only an env var, + // because e.g. the `VecStrategy` uses `Config::default().max_default_size_range`. On top of that, + // `Config::default()` reads a static `DEFAULT_CONFIG`, which gets the env vars only once at the + // beginning, so we can't override this on a test-by-test basis, unless we use `fork`, + // which is a feature that is currently disabled, because it doesn't work with Wasm. + // We could add it as a `dev-dependency` just for this crate, but when I tried it just crashed. + // For now using a const so it's obvious we can't set it to different values for different tests. + const MAX_SIZE_RANGE: usize = 5; + const SIZE_RANGE_KEY: &str = "PROPTEST_MAX_DEFAULT_SIZE_RANGE"; + + // Define a wrapper around field so we can implement `Arbitrary`. + // NB there are other methods like `arbitrary_field_elements` around the codebase, + // but for `proptest_derive::Arbitrary` we need `F: AcirField + Arbitrary`. + acir_field::field_wrapper!(TestField, FieldElement); + + impl Arbitrary for TestField { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + any::().prop_map(|v| Self(FieldElement::from(v))).boxed() + } + } + + /// Override the maximum size of collections created by `proptest`. + fn run_with_max_size_range(cases: u32, f: F) + where + T: Arbitrary, + F: Fn(T) -> TestCaseResult, + { + let orig_size_range = std::env::var(SIZE_RANGE_KEY).ok(); + // The defaults are only read once. If they are already set, leave them be. + if orig_size_range.is_none() { + unsafe { + std::env::set_var(SIZE_RANGE_KEY, MAX_SIZE_RANGE.to_string()); + } + } + + let mut runner = TestRunner::new(ProptestConfig { cases, ..Default::default() }); + let result = runner.run(&any::(), f); + + // Restore the original. + unsafe { + std::env::set_var(SIZE_RANGE_KEY, orig_size_range.unwrap_or_default()); + } + + result.unwrap(); + } + + #[test] + fn prop_program_proto_roundtrip() { + run_with_max_size_range(100, |program: Program| { + let bz = Program::proto_serialize(&program); + let de = Program::proto_deserialize(&bz)?; + prop_assert_eq!(program, de); + Ok(()) + }); + } + + #[test] + fn prop_program_bincode_roundtrip() { + run_with_max_size_range(100, |program: Program| { + let bz = Program::bincode_serialize(&program)?; + let de = Program::bincode_deserialize(&bz)?; + prop_assert_eq!(program, de); + Ok(()) + }); + } + + #[test] + fn prop_program_roundtrip() { + run_with_max_size_range(10, |program: Program| { + let bz = Program::serialize_program(&program); + let de = Program::deserialize_program(&bz)?; + prop_assert_eq!(program, de); + Ok(()) + }); + } + + #[test] + fn prop_witness_stack_proto_roundtrip() { + run_with_max_size_range(10, |witness: WitnessStack| { + let bz = WitnessStack::proto_serialize(&witness); + let de = WitnessStack::proto_deserialize(&bz)?; + prop_assert_eq!(witness, de); + Ok(()) + }); + } + + #[test] + fn prop_witness_stack_bincode_roundtrip() { + run_with_max_size_range(10, |witness: WitnessStack| { + let bz = WitnessStack::bincode_serialize(&witness)?; + let de = WitnessStack::bincode_deserialize(&bz)?; + prop_assert_eq!(witness, de); + Ok(()) + }); + } + + #[test] + fn prop_witness_stack_roundtrip() { + run_with_max_size_range(10, |witness: WitnessStack| { + let bz = Vec::::try_from(&witness)?; + let de = WitnessStack::try_from(bz.as_slice())?; + prop_assert_eq!(witness, de); + Ok(()) + }); + } + + #[test] + fn prop_witness_map_proto_roundtrip() { + run_with_max_size_range(10, |witness: WitnessMap| { + let bz = WitnessMap::proto_serialize(&witness); + let de = WitnessMap::proto_deserialize(&bz)?; + prop_assert_eq!(witness, de); + Ok(()) + }); + } + + #[test] + fn prop_witness_map_bincode_roundtrip() { + run_with_max_size_range(10, |witness: WitnessMap| { + let bz = WitnessMap::bincode_serialize(&witness)?; + let de = WitnessMap::bincode_deserialize(&bz)?; + prop_assert_eq!(witness, de); + Ok(()) + }); + } + + #[test] + fn prop_witness_map_roundtrip() { + run_with_max_size_range(10, |witness: WitnessMap| { + let bz = Vec::::try_from(witness.clone())?; + let de = WitnessMap::try_from(bz.as_slice())?; + prop_assert_eq!(witness, de); + Ok(()) + }); + } + } } diff --git a/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes.rs b/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes.rs index dec58b5f90b9..87f7c42d27de 100644 --- a/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes.rs +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes.rs @@ -15,7 +15,8 @@ pub use black_box_function_call::{ }; pub use memory_operation::{BlockId, MemOp}; -#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub enum BlockType { Memory, CallData(u32), @@ -30,7 +31,8 @@ impl BlockType { #[allow(clippy::large_enum_variant)] #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] -pub enum Opcode { +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] +pub enum Opcode { /// An `AssertZero` opcode adds the constraint that `P(w) = 0`, where /// `w=(w_1,..w_n)` is a tuple of `n` witnesses, and `P` is a multi-variate /// polynomial of total degree at most `2`. @@ -132,20 +134,8 @@ pub enum Opcode { impl std::fmt::Display for Opcode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Opcode::AssertZero(expr) => { - write!(f, "EXPR [ ")?; - for i in &expr.mul_terms { - write!(f, "({}, _{}, _{}) ", i.0, i.1.witness_index(), i.2.witness_index())?; - } - for i in &expr.linear_combinations { - write!(f, "({}, _{}) ", i.0, i.1.witness_index())?; - } - write!(f, "{}", expr.q_c)?; - - write!(f, " ]") - } - - Opcode::BlackBoxFuncCall(g) => write!(f, "{g}"), + Opcode::AssertZero(expr) => expr.fmt(f), + Opcode::BlackBoxFuncCall(g) => g.fmt(f), Opcode::MemoryOp { block_id, op, predicate } => { write!(f, "MEM ")?; if let Some(pred) = predicate { diff --git a/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs b/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs index 9cf31e94eb49..da70e9b6eb50 100644 --- a/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs @@ -10,12 +10,14 @@ use thiserror::Error; // So we need to supply how many bits of the witness is needed #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub enum ConstantOrWitnessEnum { Constant(F), Witness(Witness), } #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub struct FunctionInput { input: ConstantOrWitnessEnum, num_bits: u32, @@ -214,7 +216,7 @@ pub enum BlackBoxFuncCall { }, } -impl BlackBoxFuncCall { +impl BlackBoxFuncCall { pub fn get_black_box_func(&self) -> BlackBoxFunc { match self { BlackBoxFuncCall::AES128Encrypt { .. } => BlackBoxFunc::AES128Encrypt, @@ -244,6 +246,41 @@ impl BlackBoxFuncCall { self.get_black_box_func().name() } + pub fn get_outputs_vec(&self) -> Vec { + match self { + BlackBoxFuncCall::Blake2s { outputs, .. } + | BlackBoxFuncCall::Blake3 { outputs, .. } => outputs.to_vec(), + + BlackBoxFuncCall::Keccakf1600 { outputs, .. } => outputs.to_vec(), + + BlackBoxFuncCall::Sha256Compression { outputs, .. } => outputs.to_vec(), + + BlackBoxFuncCall::AES128Encrypt { outputs, .. } + | BlackBoxFuncCall::Poseidon2Permutation { outputs, .. } => outputs.to_vec(), + + BlackBoxFuncCall::AND { output, .. } + | BlackBoxFuncCall::XOR { output, .. } + | BlackBoxFuncCall::EcdsaSecp256k1 { output, .. } + | BlackBoxFuncCall::EcdsaSecp256r1 { output, .. } => vec![*output], + BlackBoxFuncCall::MultiScalarMul { outputs, .. } + | BlackBoxFuncCall::EmbeddedCurveAdd { outputs, .. } => { + vec![outputs.0, outputs.1, outputs.2] + } + BlackBoxFuncCall::RANGE { .. } + | BlackBoxFuncCall::RecursiveAggregation { .. } + | BlackBoxFuncCall::BigIntFromLeBytes { .. } + | BlackBoxFuncCall::BigIntAdd { .. } + | BlackBoxFuncCall::BigIntSub { .. } + | BlackBoxFuncCall::BigIntMul { .. } + | BlackBoxFuncCall::BigIntDiv { .. } => { + vec![] + } + BlackBoxFuncCall::BigIntToLeBytes { outputs, .. } => outputs.to_vec(), + } + } +} + +impl BlackBoxFuncCall { pub fn get_inputs_vec(&self) -> Vec> { match self { BlackBoxFuncCall::AES128Encrypt { inputs, .. } @@ -331,39 +368,6 @@ impl BlackBoxFuncCall { } } - pub fn get_outputs_vec(&self) -> Vec { - match self { - BlackBoxFuncCall::Blake2s { outputs, .. } - | BlackBoxFuncCall::Blake3 { outputs, .. } => outputs.to_vec(), - - BlackBoxFuncCall::Keccakf1600 { outputs, .. } => outputs.to_vec(), - - BlackBoxFuncCall::Sha256Compression { outputs, .. } => outputs.to_vec(), - - BlackBoxFuncCall::AES128Encrypt { outputs, .. } - | BlackBoxFuncCall::Poseidon2Permutation { outputs, .. } => outputs.to_vec(), - - BlackBoxFuncCall::AND { output, .. } - | BlackBoxFuncCall::XOR { output, .. } - | BlackBoxFuncCall::EcdsaSecp256k1 { output, .. } - | BlackBoxFuncCall::EcdsaSecp256r1 { output, .. } => vec![*output], - BlackBoxFuncCall::MultiScalarMul { outputs, .. } - | BlackBoxFuncCall::EmbeddedCurveAdd { outputs, .. } => { - vec![outputs.0, outputs.1, outputs.2] - } - BlackBoxFuncCall::RANGE { .. } - | BlackBoxFuncCall::RecursiveAggregation { .. } - | BlackBoxFuncCall::BigIntFromLeBytes { .. } - | BlackBoxFuncCall::BigIntAdd { .. } - | BlackBoxFuncCall::BigIntSub { .. } - | BlackBoxFuncCall::BigIntMul { .. } - | BlackBoxFuncCall::BigIntDiv { .. } => { - vec![] - } - BlackBoxFuncCall::BigIntToLeBytes { outputs, .. } => outputs.to_vec(), - } - } - pub fn get_input_witnesses(&self) -> BTreeSet { let mut result = BTreeSet::new(); for input in self.get_inputs_vec() { @@ -506,3 +510,207 @@ mod tests { assert_eq!(opcode, recovered_opcode); } } + +#[cfg(feature = "arb")] +mod arb { + use acir_field::AcirField; + use proptest::prelude::*; + + use crate::native_types::Witness; + + use super::{BlackBoxFuncCall, FunctionInput}; + + // Implementing this separately because trying to derive leads to stack overflow. + impl Arbitrary for BlackBoxFuncCall + where + F: AcirField + Arbitrary, + { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + let input = any::>(); + let input_vec = any::>>(); + let input_arr_3 = any::; 3]>>(); + let input_arr_8 = any::; 8]>>(); + let input_arr_16 = any::; 16]>>(); + let input_arr_25 = any::; 25]>>(); + let input_arr_32 = any::; 32]>>(); + let input_arr_64 = any::; 64]>>(); + let witness = any::(); + let witness_vec = any::>(); + let witness_arr_8 = any::>(); + let witness_arr_25 = any::>(); + let witness_arr_32 = any::>(); + + let case_aes128_encrypt = ( + input_vec.clone(), + input_arr_16.clone(), + input_arr_16.clone(), + witness_vec.clone(), + ) + .prop_map(|(inputs, iv, key, outputs)| { + BlackBoxFuncCall::AES128Encrypt { inputs, iv, key, outputs } + }); + + let case_and = (input.clone(), input.clone(), witness.clone()) + .prop_map(|(lhs, rhs, output)| BlackBoxFuncCall::AND { lhs, rhs, output }); + + let case_xor = (input.clone(), input.clone(), witness.clone()) + .prop_map(|(lhs, rhs, output)| BlackBoxFuncCall::XOR { lhs, rhs, output }); + + let case_range = input.clone().prop_map(|input| BlackBoxFuncCall::RANGE { input }); + + let case_blake2s = (input_vec.clone(), witness_arr_32.clone()) + .prop_map(|(inputs, outputs)| BlackBoxFuncCall::Blake2s { inputs, outputs }); + + let case_blake3 = (input_vec.clone(), witness_arr_32.clone()) + .prop_map(|(inputs, outputs)| BlackBoxFuncCall::Blake3 { inputs, outputs }); + + let case_ecdsa_secp256k1 = ( + input_arr_32.clone(), + input_arr_32.clone(), + input_arr_64.clone(), + input_arr_32.clone(), + witness.clone(), + ) + .prop_map( + |(public_key_x, public_key_y, signature, hashed_message, output)| { + BlackBoxFuncCall::EcdsaSecp256k1 { + public_key_x, + public_key_y, + signature, + hashed_message, + output, + } + }, + ); + + let case_ecdsa_secp256r1 = ( + input_arr_32.clone(), + input_arr_32.clone(), + input_arr_64.clone(), + input_arr_32.clone(), + witness.clone(), + ) + .prop_map( + |(public_key_x, public_key_y, signature, hashed_message, output)| { + BlackBoxFuncCall::EcdsaSecp256r1 { + public_key_x, + public_key_y, + signature, + hashed_message, + output, + } + }, + ); + + let case_multi_scalar_mul = ( + input_vec.clone(), + input_vec.clone(), + witness.clone(), + witness.clone(), + witness.clone(), + ) + .prop_map(|(points, scalars, w1, w2, w3)| { + BlackBoxFuncCall::MultiScalarMul { points, scalars, outputs: (w1, w2, w3) } + }); + + let case_embedded_curve_add = ( + input_arr_3.clone(), + input_arr_3.clone(), + witness.clone(), + witness.clone(), + witness.clone(), + ) + .prop_map(|(input1, input2, w1, w2, w3)| { + BlackBoxFuncCall::EmbeddedCurveAdd { input1, input2, outputs: (w1, w2, w3) } + }); + + let case_keccakf1600 = (input_arr_25.clone(), witness_arr_25.clone()) + .prop_map(|(inputs, outputs)| BlackBoxFuncCall::Keccakf1600 { inputs, outputs }); + + let case_recursive_aggregation = ( + input_vec.clone(), + input_vec.clone(), + input_vec.clone(), + input.clone(), + any::(), + ) + .prop_map( + |(verification_key, proof, public_inputs, key_hash, proof_type)| { + BlackBoxFuncCall::RecursiveAggregation { + verification_key, + proof, + public_inputs, + key_hash, + proof_type, + } + }, + ); + + let big_int_args = (any::(), any::(), any::()); + + let case_big_int_add = big_int_args + .prop_map(|(lhs, rhs, output)| BlackBoxFuncCall::BigIntAdd { lhs, rhs, output }); + + let case_big_int_sub = big_int_args + .prop_map(|(lhs, rhs, output)| BlackBoxFuncCall::BigIntSub { lhs, rhs, output }); + + let case_big_int_mul = big_int_args + .prop_map(|(lhs, rhs, output)| BlackBoxFuncCall::BigIntMul { lhs, rhs, output }); + + let case_big_int_div = big_int_args + .prop_map(|(lhs, rhs, output)| BlackBoxFuncCall::BigIntDiv { lhs, rhs, output }); + + let case_big_int_from_le_bytes = (input_vec.clone(), any::>(), any::()) + .prop_map(|(inputs, modulus, output)| BlackBoxFuncCall::BigIntFromLeBytes { + inputs, + modulus, + output, + }); + + let case_big_int_to_le_bytes = (any::(), witness_vec.clone()) + .prop_map(|(input, outputs)| BlackBoxFuncCall::BigIntToLeBytes { input, outputs }); + + let case_poseidon2_permutation = (input_vec.clone(), witness_vec.clone(), any::()) + .prop_map(|(inputs, outputs, len)| BlackBoxFuncCall::Poseidon2Permutation { + inputs, + outputs, + len, + }); + + let case_sha256_compression = (input_arr_16, input_arr_8, witness_arr_8).prop_map( + |(inputs, hash_values, outputs)| BlackBoxFuncCall::Sha256Compression { + inputs, + hash_values, + outputs, + }, + ); + + prop_oneof![ + case_aes128_encrypt, + case_and, + case_xor, + case_range, + case_blake2s, + case_blake3, + case_ecdsa_secp256k1, + case_ecdsa_secp256r1, + case_multi_scalar_mul, + case_embedded_curve_add, + case_keccakf1600, + case_recursive_aggregation, + case_big_int_add, + case_big_int_sub, + case_big_int_mul, + case_big_int_div, + case_big_int_from_le_bytes, + case_big_int_to_le_bytes, + case_poseidon2_permutation, + case_sha256_compression, + ] + .boxed() + } + } +} diff --git a/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/function_id.rs b/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/function_id.rs index b5abb1b3942a..e87e52ce9679 100644 --- a/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/function_id.rs +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/function_id.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] #[serde(transparent)] pub struct AcirFunctionId(pub u32); diff --git a/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/memory_operation.rs b/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/memory_operation.rs index c9a789832047..66034166b23a 100644 --- a/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/memory_operation.rs +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/memory_operation.rs @@ -3,11 +3,13 @@ use acir_field::AcirField; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash, Copy, Default)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub struct BlockId(pub u32); /// Operation on a block of memory /// We can either write or read at an index in memory #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub struct MemOp { /// A constant expression that can be 0 (read) or 1 (write) pub operation: Expression, diff --git a/noir/noir-repo/acvm-repo/acir/src/lib.rs b/noir/noir-repo/acvm-repo/acir/src/lib.rs index e49ab60f9e06..63a1253cbe12 100644 --- a/noir/noir-repo/acvm-repo/acir/src/lib.rs +++ b/noir/noir-repo/acvm-repo/acir/src/lib.rs @@ -1,4 +1,4 @@ -#![forbid(unsafe_code)] +#![cfg_attr(not(test), forbid(unsafe_code))] // `std::env::set_var` is used in tests. #![warn(unreachable_pub)] #![warn(clippy::semicolon_if_nothing_returned)] #![cfg_attr(not(test), warn(unused_crate_dependencies, unused_extern_crates))] @@ -7,6 +7,7 @@ pub mod circuit; pub mod native_types; +mod proto; pub use acir_field; pub use acir_field::{AcirField, FieldElement}; diff --git a/noir/noir-repo/acvm-repo/acir/src/native_types/expression/mod.rs b/noir/noir-repo/acvm-repo/acir/src/native_types/expression/mod.rs index cdb8974526fa..b82b67294227 100644 --- a/noir/noir-repo/acvm-repo/acir/src/native_types/expression/mod.rs +++ b/noir/noir-repo/acvm-repo/acir/src/native_types/expression/mod.rs @@ -13,6 +13,7 @@ mod ordering; // In the multiplication polynomial // XXX: If we allow the degree of the quotient polynomial to be arbitrary, then we will need a vector of wire values #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub struct Expression { // To avoid having to create intermediate variables pre-optimization // We collect all of the multiplication terms in the assert-zero opcode @@ -32,13 +33,18 @@ impl Default for Expression { } } -impl std::fmt::Display for Expression { +impl std::fmt::Display for Expression { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - if let Some(witness) = self.to_witness() { - write!(f, "x{}", witness.witness_index()) - } else { - write!(f, "%{:?}%", crate::circuit::opcodes::Opcode::AssertZero(self.clone())) + write!(f, "EXPR [ ")?; + for i in &self.mul_terms { + write!(f, "({}, _{}, _{}) ", i.0, i.1.witness_index(), i.2.witness_index())?; } + for i in &self.linear_combinations { + write!(f, "({}, _{}) ", i.0, i.1.witness_index())?; + } + write!(f, "{}", self.q_c)?; + + write!(f, " ]") } } diff --git a/noir/noir-repo/acvm-repo/acir/src/native_types/witness.rs b/noir/noir-repo/acvm-repo/acir/src/native_types/witness.rs index a570968f948f..d97702174b8f 100644 --- a/noir/noir-repo/acvm-repo/acir/src/native_types/witness.rs +++ b/noir/noir-repo/acvm-repo/acir/src/native_types/witness.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize}; #[derive( Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize, Deserialize, )] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub struct Witness(pub u32); impl Witness { diff --git a/noir/noir-repo/acvm-repo/acir/src/native_types/witness_map.rs b/noir/noir-repo/acvm-repo/acir/src/native_types/witness_map.rs index 77745c714a39..edb6047b5db3 100644 --- a/noir/noir-repo/acvm-repo/acir/src/native_types/witness_map.rs +++ b/noir/noir-repo/acvm-repo/acir/src/native_types/witness_map.rs @@ -4,18 +4,24 @@ use std::{ ops::Index, }; +use acir_field::AcirField; use flate2::Compression; use flate2::bufread::GzDecoder; use flate2::bufread::GzEncoder; +use noir_protobuf::ProtoCodec as _; use serde::{Deserialize, Serialize}; use thiserror::Error; -use crate::native_types::Witness; +use crate::{native_types::Witness, proto::convert::ProtoSchema}; #[derive(Debug, Error)] enum SerializationError { #[error(transparent)] Deflate(#[from] std::io::Error), + + #[allow(dead_code)] + #[error("error deserializing witness map: {0}")] + Deserialize(String), } #[derive(Debug, Error)] @@ -24,6 +30,7 @@ pub struct WitnessMapError(#[from] SerializationError); /// A map from the witnesses in a constraint system to the field element values #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize, Deserialize)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub struct WitnessMap(BTreeMap); impl WitnessMap { @@ -77,15 +84,39 @@ impl From> for WitnessMap { } } -impl TryFrom> for Vec { +impl WitnessMap { + pub(crate) fn bincode_serialize(&self) -> Result, WitnessMapError> { + bincode::serialize(self).map_err(|e| SerializationError::Deserialize(e.to_string()).into()) + } +} + +impl Deserialize<'a>> WitnessMap { + pub(crate) fn bincode_deserialize(buf: &[u8]) -> Result { + bincode::deserialize(buf).map_err(|e| SerializationError::Deserialize(e.to_string()).into()) + } +} + +#[allow(dead_code)] +impl WitnessMap { + pub(crate) fn proto_serialize(&self) -> Vec { + ProtoSchema::::serialize_to_vec(self) + } + + pub(crate) fn proto_deserialize(buf: &[u8]) -> Result { + ProtoSchema::::deserialize_from_vec(buf) + .map_err(|e| SerializationError::Deserialize(e.to_string()).into()) + } +} + +impl TryFrom> for Vec { type Error = WitnessMapError; fn try_from(val: WitnessMap) -> Result { - let buf = bincode::serialize(&val).unwrap(); + let buf = val.bincode_serialize()?; let mut deflater = GzEncoder::new(buf.as_slice(), Compression::best()); - let mut buf_c = Vec::new(); - deflater.read_to_end(&mut buf_c).map_err(|err| WitnessMapError(err.into()))?; - Ok(buf_c) + let mut buf = Vec::new(); + deflater.read_to_end(&mut buf).map_err(|err| WitnessMapError(err.into()))?; + Ok(buf) } } @@ -94,9 +125,9 @@ impl Deserialize<'a>> TryFrom<&[u8]> for WitnessMap { fn try_from(bytes: &[u8]) -> Result { let mut deflater = GzDecoder::new(bytes); - let mut buf_d = Vec::new(); - deflater.read_to_end(&mut buf_d).map_err(|err| WitnessMapError(err.into()))?; - let witness_map = bincode::deserialize(&buf_d).unwrap(); - Ok(Self(witness_map)) + let mut buf = Vec::new(); + deflater.read_to_end(&mut buf).map_err(|err| WitnessMapError(err.into()))?; + let witness_map = Self::bincode_deserialize(&buf)?; + Ok(witness_map) } } diff --git a/noir/noir-repo/acvm-repo/acir/src/native_types/witness_stack.rs b/noir/noir-repo/acvm-repo/acir/src/native_types/witness_stack.rs index 6338ad630d6c..686b3ee98fdc 100644 --- a/noir/noir-repo/acvm-repo/acir/src/native_types/witness_stack.rs +++ b/noir/noir-repo/acvm-repo/acir/src/native_types/witness_stack.rs @@ -1,11 +1,15 @@ use std::io::Read; +use acir_field::AcirField; use flate2::Compression; use flate2::bufread::GzDecoder; use flate2::bufread::GzEncoder; +use noir_protobuf::ProtoCodec as _; use serde::{Deserialize, Serialize}; use thiserror::Error; +use crate::proto::convert::ProtoSchema; + use super::WitnessMap; #[derive(Debug, Error)] @@ -15,6 +19,10 @@ enum SerializationError { #[error(transparent)] BincodeError(#[from] bincode::Error), + + #[allow(dead_code)] + #[error("error deserializing witness stack: {0}")] + Deserialize(String), } #[derive(Debug, Error)] @@ -23,11 +31,13 @@ pub struct WitnessStackError(#[from] SerializationError); /// An ordered set of witness maps for separate circuits #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize, Deserialize)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub struct WitnessStack { stack: Vec>, } #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize, Deserialize)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub struct StackItem { /// Index into a [crate::circuit::Program] function list for which we have an associated witness pub index: u32, @@ -60,11 +70,35 @@ impl From> for WitnessStack { } } -impl TryFrom<&WitnessStack> for Vec { +impl WitnessStack { + pub(crate) fn bincode_serialize(&self) -> Result, WitnessStackError> { + bincode::serialize(self).map_err(|e| WitnessStackError(e.into())) + } +} + +impl Deserialize<'a>> WitnessStack { + pub(crate) fn bincode_deserialize(buf: &[u8]) -> Result { + bincode::deserialize(buf).map_err(|e| WitnessStackError(e.into())) + } +} + +#[allow(dead_code)] +impl WitnessStack { + pub(crate) fn proto_serialize(&self) -> Vec { + ProtoSchema::::serialize_to_vec(self) + } + + pub(crate) fn proto_deserialize(buf: &[u8]) -> Result { + ProtoSchema::::deserialize_from_vec(buf) + .map_err(|e| SerializationError::Deserialize(e.to_string()).into()) + } +} + +impl TryFrom<&WitnessStack> for Vec { type Error = WitnessStackError; fn try_from(val: &WitnessStack) -> Result { - let buf = bincode::serialize(val).map_err(|e| WitnessStackError(e.into()))?; + let buf = val.bincode_serialize()?; let mut deflater = GzEncoder::new(buf.as_slice(), Compression::best()); let mut buf_c = Vec::new(); deflater.read_to_end(&mut buf_c).map_err(|err| WitnessStackError(err.into()))?; @@ -72,7 +106,7 @@ impl TryFrom<&WitnessStack> for Vec { } } -impl TryFrom> for Vec { +impl TryFrom> for Vec { type Error = WitnessStackError; fn try_from(val: WitnessStack) -> Result { @@ -85,10 +119,9 @@ impl Deserialize<'a>> TryFrom<&[u8]> for WitnessStack { fn try_from(bytes: &[u8]) -> Result { let mut deflater = GzDecoder::new(bytes); - let mut buf_d = Vec::new(); - deflater.read_to_end(&mut buf_d).map_err(|err| WitnessStackError(err.into()))?; - let witness_stack = - bincode::deserialize(&buf_d).map_err(|e| WitnessStackError(e.into()))?; + let mut buf = Vec::new(); + deflater.read_to_end(&mut buf).map_err(|err| WitnessStackError(err.into()))?; + let witness_stack = Self::bincode_deserialize(&buf)?; Ok(witness_stack) } } diff --git a/noir/noir-repo/acvm-repo/acir/src/proto/acir/circuit.proto b/noir/noir-repo/acvm-repo/acir/src/proto/acir/circuit.proto new file mode 100644 index 000000000000..c0981d6e30cd --- /dev/null +++ b/noir/noir-repo/acvm-repo/acir/src/proto/acir/circuit.proto @@ -0,0 +1,255 @@ +syntax = "proto3"; + +package acvm.acir.circuit; + +import "acir/native.proto"; + +message Circuit { + uint32 current_witness_index = 1; + repeated Opcode opcodes = 2; + ExpressionWidth expression_width = 3; + repeated native.Witness private_parameters = 4; + repeated native.Witness public_parameters = 5; + repeated native.Witness return_values = 6; + repeated AssertMessage assert_messages = 7; +} + +message ExpressionWidth { + oneof value { + Unbounded unbounded = 1; + Bounded bounded = 2; + } + message Unbounded {} + message Bounded { uint64 width = 1; } +} + +message AssertMessage { + OpcodeLocation location = 1; + AssertionPayload payload = 2; +} + +message OpcodeLocation { + oneof value { + uint64 acir = 1; + BrilligLocation brillig = 2; + } + message BrilligLocation { + uint64 acir_index = 1; + uint64 brillig_index = 2; + } +} + +message AssertionPayload { + uint64 error_selector = 1; + repeated ExpressionOrMemory payload = 2; +} + +message ExpressionOrMemory { + oneof value { + native.Expression expression = 1; + uint32 memory = 2; + } +} + +message Opcode { + oneof value { + native.Expression assert_zero = 1; + BlackBoxFuncCall blackbox_func_call = 2; + MemoryOp memory_op = 3; + MemoryInit memory_init = 4; + BrilligCall brillig_call = 5; + Call call = 6; + } + message MemoryOp { + uint32 block_id = 1; + MemOp op = 2; + optional native.Expression predicate = 3; + } + message MemoryInit { + uint32 block_id = 1; + repeated native.Witness init = 2; + BlockType block_type = 3; + } + message BrilligCall { + uint32 id = 1; + repeated BrilligInputs inputs = 2; + repeated BrilligOutputs outputs = 3; + optional native.Expression predicate = 4; + } + message Call { + uint32 id = 1; + repeated native.Witness inputs = 2; + repeated native.Witness outputs = 3; + optional native.Expression predicate = 4; + } +} + +message BlackBoxFuncCall { + oneof value { + AES128Encrypt aes128_encrypt = 1; + AND and = 2; + XOR xor = 3; + RANGE range = 4; + Blake2s blake2s = 5; + Blake3 blake3 = 6; + EcdsaSecp256k1 ecdsa_secp256k1 = 7; + EcdsaSecp256r1 ecdsa_secp256r1 = 8; + MultiScalarMul multi_scalar_mul = 9; + EmbeddedCurveAdd embedded_curve_add = 10; + Keccakf1600 keccak_f1600 = 11; + RecursiveAggregation recursive_aggregation = 12; + BigIntAdd big_int_add = 13; + BigIntSub big_int_sub = 14; + BigIntMul big_int_mul = 15; + BigIntDiv big_int_div = 16; + BigIntFromLeBytes big_int_from_le_bytes = 17; + BigIntToLeBytes big_int_to_le_bytes = 18; + Poseidon2Permutation poseidon2_permutation = 19; + Sha256Compression sha256_compression = 20; + } + message AES128Encrypt { + repeated FunctionInput inputs = 1; + repeated FunctionInput iv = 2; + repeated FunctionInput key = 3; + repeated native.Witness outputs = 4; + } + message AND { + FunctionInput lhs = 1; + FunctionInput rhs = 2; + native.Witness output = 3; + } + message XOR { + FunctionInput lhs = 1; + FunctionInput rhs = 2; + native.Witness output = 3; + } + message RANGE { FunctionInput input = 1; } + message Blake2s { + repeated FunctionInput inputs = 1; + repeated native.Witness outputs = 2; + } + message Blake3 { + repeated FunctionInput inputs = 1; + repeated native.Witness outputs = 2; + } + message EcdsaSecp256k1 { + repeated FunctionInput public_key_x = 1; + repeated FunctionInput public_key_y = 2; + repeated FunctionInput signature = 3; + repeated FunctionInput hashed_message = 4; + native.Witness output = 5; + } + message EcdsaSecp256r1 { + repeated FunctionInput public_key_x = 1; + repeated FunctionInput public_key_y = 2; + repeated FunctionInput signature = 3; + repeated FunctionInput hashed_message = 4; + native.Witness output = 5; + } + message MultiScalarMul { + repeated FunctionInput points = 1; + repeated FunctionInput scalars = 2; + repeated native.Witness outputs = 3; + } + message EmbeddedCurveAdd { + repeated FunctionInput input1 = 1; + repeated FunctionInput input2 = 2; + repeated native.Witness outputs = 3; + } + message Keccakf1600 { + repeated FunctionInput inputs = 1; + repeated native.Witness outputs = 2; + } + message RecursiveAggregation { + repeated FunctionInput verification_key = 1; + repeated FunctionInput proof = 2; + repeated FunctionInput public_inputs = 3; + FunctionInput key_hash = 4; + uint32 proof_type = 5; + } + message BigIntAdd { + uint32 lhs = 1; + uint32 rhs = 2; + uint32 output = 3; + } + message BigIntSub { + uint32 lhs = 1; + uint32 rhs = 2; + uint32 output = 3; + } + message BigIntMul { + uint32 lhs = 1; + uint32 rhs = 2; + uint32 output = 3; + } + message BigIntDiv { + uint32 lhs = 1; + uint32 rhs = 2; + uint32 output = 3; + } + message BigIntFromLeBytes { + repeated FunctionInput inputs = 1; + bytes modulus = 2; + uint32 output = 3; + } + message BigIntToLeBytes { + uint32 input = 1; + repeated native.Witness outputs = 2; + } + message Poseidon2Permutation { + repeated FunctionInput inputs = 1; + repeated native.Witness outputs = 2; + uint32 len = 3; + } + message Sha256Compression { + repeated FunctionInput inputs = 1; + repeated FunctionInput hash_values = 2; + repeated native.Witness outputs = 3; + } +} + +message FunctionInput { + ConstantOrWitnessEnum input = 1; + uint32 num_bits = 2; +} + +message ConstantOrWitnessEnum { + oneof value { + native.Field constant = 1; + native.Witness witness = 2; + } +} + +message MemOp { + native.Expression operation = 1; + native.Expression index = 2; + native.Expression value = 3; +} + +message BlockType { + oneof value { + Memory memory = 1; + CallData call_data = 2; + ReturnData return_data = 3; + } + message Memory {} + message CallData { uint32 value = 1; } + message ReturnData {} +} + +message BrilligInputs { + oneof value { + native.Expression single = 1; + Array array = 2; + uint32 memory_array = 3; + } + message Array { repeated native.Expression values = 2; } +} + +message BrilligOutputs { + oneof value { + native.Witness simple = 1; + Array array = 2; + } + message Array { repeated native.Witness values = 1; } +} diff --git a/noir/noir-repo/acvm-repo/acir/src/proto/acir/native.proto b/noir/noir-repo/acvm-repo/acir/src/proto/acir/native.proto new file mode 100644 index 000000000000..561a79c5701f --- /dev/null +++ b/noir/noir-repo/acvm-repo/acir/src/proto/acir/native.proto @@ -0,0 +1,24 @@ +syntax = "proto3"; + +package acvm.acir.native; + +message Field { bytes value = 1; } + +message Witness { uint32 index = 1; } + +message Expression { + repeated MulTerm mul_terms = 1; + repeated LinearCombination linear_combinations = 2; + Field q_c = 3; + + message MulTerm { + Field q_m = 1; + Witness witness_left = 2; + Witness witness_right = 3; + } + + message LinearCombination { + Field q_l = 1; + Witness witness = 2; + } +} \ No newline at end of file diff --git a/noir/noir-repo/acvm-repo/acir/src/proto/acir/program.proto b/noir/noir-repo/acvm-repo/acir/src/proto/acir/program.proto new file mode 100644 index 000000000000..0032966d0cb2 --- /dev/null +++ b/noir/noir-repo/acvm-repo/acir/src/proto/acir/program.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +package acvm.acir.program; + +// Only including the ACIR types for Berratenberg, not Brillig. +import "acir/circuit.proto"; + +// Same as the top level `program.proto` but ignores the +// `unconstrained_functions` field, so that Berratenberg doesn't need to +// deserialize it. +message Program { + // ACIR circuits + repeated acvm.acir.circuit.Circuit functions = 1; + + reserved 2; + reserved "unconstrained_functions"; +} \ No newline at end of file diff --git a/noir/noir-repo/acvm-repo/acir/src/proto/acir/witness.proto b/noir/noir-repo/acvm-repo/acir/src/proto/acir/witness.proto new file mode 100644 index 000000000000..bb6a29341ee8 --- /dev/null +++ b/noir/noir-repo/acvm-repo/acir/src/proto/acir/witness.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +package acvm.acir.witness; + +import "acir/native.proto"; + +message WitnessMap { + repeated WitnessValue values = 1; + + message WitnessValue { + native.Witness witness = 1; + native.Field field = 2; + } +} + +message WitnessStack { + repeated StackItem stack = 1; + + message StackItem { + uint32 index = 1; + WitnessMap witness = 2; + } +} \ No newline at end of file diff --git a/noir/noir-repo/acvm-repo/acir/src/proto/brillig.proto b/noir/noir-repo/acvm-repo/acir/src/proto/brillig.proto new file mode 100644 index 000000000000..cba8c797889d --- /dev/null +++ b/noir/noir-repo/acvm-repo/acir/src/proto/brillig.proto @@ -0,0 +1,308 @@ +syntax = "proto3"; + +package acvm.brillig; + +import "acir/native.proto"; + +message BrilligBytecode { repeated BrilligOpcode bytecode = 1; } + +message BrilligOpcode { + oneof value { + BinaryFieldOp binary_field_op = 1; + BinaryIntOp binary_int_op = 2; + Not not = 3; + Cast cast = 4; + JumpIfNot jump_if_not = 5; + JumpIf jump_if = 6; + Jump jump = 7; + CalldataCopy calldata_copy = 8; + Call call = 9; + Const const = 10; + IndirectConst indirect_const = 11; + Return return = 12; + ForeignCall foreign_call = 13; + Mov mov = 14; + ConditionalMov conditional_mov = 15; + Load load = 16; + Store store = 17; + BlackBox black_box = 18; + Trap trap = 19; + Stop stop = 20; + } + message BinaryFieldOp { + MemoryAddress destination = 1; + BinaryFieldOpKind op = 2; + MemoryAddress lhs = 3; + MemoryAddress rhs = 4; + } + message BinaryIntOp { + MemoryAddress destination = 1; + BinaryIntOpKind op = 2; + IntegerBitSize bit_size = 3; + MemoryAddress lhs = 4; + MemoryAddress rhs = 5; + } + message Not { + MemoryAddress destination = 1; + MemoryAddress source = 2; + IntegerBitSize bit_size = 3; + } + message Cast { + MemoryAddress destination = 1; + MemoryAddress source = 2; + BitSize bit_size = 3; + } + message JumpIfNot { + MemoryAddress condition = 1; + uint64 location = 2; + } + message JumpIf { + MemoryAddress condition = 1; + uint64 location = 2; + } + message Jump { uint64 location = 1; } + message CalldataCopy { + MemoryAddress destination_address = 1; + MemoryAddress size_address = 2; + MemoryAddress offset_address = 3; + } + message Call { uint64 location = 1; } + message Const { + MemoryAddress destination = 1; + BitSize bit_size = 2; + acir.native.Field value = 3; + } + message IndirectConst { + MemoryAddress destination_pointer = 1; + BitSize bit_size = 2; + acir.native.Field value = 3; + } + message Return {} + message ForeignCall { + string function = 1; + repeated ValueOrArray destinations = 2; + repeated HeapValueType destination_value_types = 3; + repeated ValueOrArray inputs = 4; + repeated HeapValueType input_value_types = 5; + } + message Mov { + MemoryAddress destination = 1; + MemoryAddress source = 2; + } + message ConditionalMov { + MemoryAddress destination = 1; + MemoryAddress source_a = 2; + MemoryAddress source_b = 3; + MemoryAddress condition = 4; + } + message Load { + MemoryAddress destination = 1; + MemoryAddress source_pointer = 2; + } + message Store { + MemoryAddress destination_pointer = 1; + MemoryAddress source = 2; + } + message BlackBox { BlackBoxOp op = 1; } + message Trap { HeapVector revert_data = 1; } + message Stop { HeapVector return_data = 1; } +} + +message MemoryAddress { + oneof value { + uint64 direct = 1; + uint64 relative = 2; + } +} + +message ValueOrArray { + oneof value { + MemoryAddress memory_address = 1; + HeapArray heap_array = 2; + HeapVector heap_vector = 3; + } +} + +message HeapArray { + MemoryAddress pointer = 1; + uint64 size = 2; +} + +message HeapVector { + MemoryAddress pointer = 1; + MemoryAddress size = 2; +} + +message HeapValueType { + oneof value { + BitSize simple = 1; + Array array = 2; + Vector vector = 3; + } + message Array { + repeated HeapValueType value_types = 1; + uint64 size = 2; + } + message Vector { repeated HeapValueType value_types = 1; } +} + +enum BinaryFieldOpKind { + BFO_UNSPECIFIED = 0; + BFO_ADD = 1; + BFO_SUB = 2; + BFO_MUL = 3; + BFO_DIV = 4; + BFO_INTEGER_DIV = 5; + BFO_EQUALS = 6; + BFO_LESS_THAN = 7; + BFO_LESS_THAN_EQUALS = 8; +} + +enum BinaryIntOpKind { + BIO_UNSPECIFIED = 0; + BIO_ADD = 1; + BIO_SUB = 2; + BIO_MUL = 3; + BIO_DIV = 4; + BIO_EQUALS = 5; + BIO_LESS_THAN = 6; + BIO_LESS_THAN_EQUALS = 7; + BIO_AND = 8; + BIO_OR = 9; + BIO_XOR = 10; + BIO_SHL = 11; + BIO_SHR = 12; +} + +enum IntegerBitSize { + IBS_UNSPECIFIED = 0; + IBS_U1 = 1; + IBS_U8 = 8; + IBS_U16 = 16; + IBS_U32 = 32; + IBS_U64 = 64; + IBS_U128 = 128; +} + +message BitSize { + oneof value { + Field field = 1; + IntegerBitSize integer = 2; + } + message Field {} +} + +message BlackBoxOp { + oneof value { + AES128Encrypt aes128_encrypt = 1; + Blake2s blake2s = 2; + Blake3 blake3 = 3; + Keccakf1600 keccak_f1600 = 4; + EcdsaSecp256k1 ecdsa_secp256k1 = 5; + EcdsaSecp256r1 ecdsa_secp256r1 = 6; + MultiScalarMul multi_scalar_mul = 7; + EmbeddedCurveAdd embedded_curve_add = 8; + BigIntAdd big_int_add = 9; + BigIntSub big_int_sub = 10; + BigIntMul big_int_mul = 11; + BigIntDiv big_int_div = 12; + BigIntFromLeBytes big_int_from_le_bytes = 13; + BigIntToLeBytes big_int_to_le_bytes = 14; + Poseidon2Permutation poseidon2_permutation = 15; + Sha256Compression sha256_compression = 16; + ToRadix to_radix = 17; + } + message AES128Encrypt { + HeapVector inputs = 1; + HeapArray iv = 2; + HeapArray key = 3; + HeapVector outputs = 4; + } + message Blake2s { + HeapVector message = 1; + HeapArray output = 2; + } + message Blake3 { + HeapVector message = 1; + HeapArray output = 2; + } + message Keccakf1600 { + HeapArray input = 1; + HeapArray output = 2; + } + message EcdsaSecp256k1 { + HeapVector hashed_msg = 1; + HeapArray public_key_x = 2; + HeapArray public_key_y = 3; + HeapArray signature = 4; + MemoryAddress result = 5; + } + message EcdsaSecp256r1 { + HeapVector hashed_msg = 1; + HeapArray public_key_x = 2; + HeapArray public_key_y = 3; + HeapArray signature = 4; + MemoryAddress result = 5; + } + + message MultiScalarMul { + HeapVector points = 1; + HeapVector scalars = 2; + HeapArray outputs = 3; + } + message EmbeddedCurveAdd { + MemoryAddress input1_x = 1; + MemoryAddress input1_y = 2; + MemoryAddress input1_infinite = 3; + MemoryAddress input2_x = 4; + MemoryAddress input2_y = 5; + MemoryAddress input2_infinite = 6; + HeapArray result = 7; + } + message BigIntAdd { + MemoryAddress lhs = 1; + MemoryAddress rhs = 2; + MemoryAddress output = 3; + } + message BigIntSub { + MemoryAddress lhs = 1; + MemoryAddress rhs = 2; + MemoryAddress output = 3; + } + message BigIntMul { + MemoryAddress lhs = 1; + MemoryAddress rhs = 2; + MemoryAddress output = 3; + } + message BigIntDiv { + MemoryAddress lhs = 1; + MemoryAddress rhs = 2; + MemoryAddress output = 3; + } + message BigIntFromLeBytes { + HeapVector inputs = 1; + HeapVector modulus = 2; + MemoryAddress output = 3; + } + message BigIntToLeBytes { + MemoryAddress input = 1; + HeapVector output = 2; + } + message Poseidon2Permutation { + HeapVector message = 1; + HeapArray output = 2; + MemoryAddress len = 3; + } + message Sha256Compression { + HeapArray input = 1; + HeapArray hash_values = 2; + HeapArray output = 3; + } + message ToRadix { + MemoryAddress input = 1; + MemoryAddress radix = 2; + MemoryAddress output_pointer = 3; + MemoryAddress num_limbs = 4; + MemoryAddress output_bits = 5; + } +} \ No newline at end of file diff --git a/noir/noir-repo/acvm-repo/acir/src/proto/convert/acir.rs b/noir/noir-repo/acvm-repo/acir/src/proto/convert/acir.rs new file mode 100644 index 000000000000..4c3304613e49 --- /dev/null +++ b/noir/noir-repo/acvm-repo/acir/src/proto/convert/acir.rs @@ -0,0 +1,679 @@ +use crate::{ + circuit::{ + self, + brillig::BrilligFunctionId, + opcodes::{self, AcirFunctionId, BlockId}, + }, + proto::acir::circuit::{ + AssertMessage, AssertionPayload, BlackBoxFuncCall, BlockType, BrilligInputs, + BrilligOutputs, Circuit, ConstantOrWitnessEnum, ExpressionOrMemory, ExpressionWidth, + FunctionInput, MemOp, Opcode, OpcodeLocation, + }, +}; +use acir_field::AcirField; +use color_eyre::eyre::{self, Context}; +use noir_protobuf::{ProtoCodec, decode_oneof_map}; + +use super::ProtoSchema; + +impl ProtoCodec, Circuit> for ProtoSchema { + fn encode(value: &circuit::Circuit) -> Circuit { + Circuit { + current_witness_index: value.current_witness_index, + opcodes: Self::encode_vec(&value.opcodes), + expression_width: Self::encode_some(&value.expression_width), + private_parameters: Self::encode_vec(value.private_parameters.iter()), + public_parameters: Self::encode_vec(value.public_parameters.0.iter()), + return_values: Self::encode_vec(value.return_values.0.iter()), + assert_messages: Self::encode_vec(&value.assert_messages), + } + } + + fn decode(value: &Circuit) -> eyre::Result> { + Ok(circuit::Circuit { + current_witness_index: value.current_witness_index, + opcodes: Self::decode_vec_wrap(&value.opcodes, "opcodes")?, + expression_width: Self::decode_some_wrap(&value.expression_width, "expression_width")?, + private_parameters: Self::decode_vec_wrap( + &value.private_parameters, + "private_parameters", + )? + .into_iter() + .collect(), + public_parameters: circuit::PublicInputs( + Self::decode_vec_wrap(&value.public_parameters, "public_parameters")? + .into_iter() + .collect(), + ), + return_values: circuit::PublicInputs( + Self::decode_vec_wrap(&value.return_values, "return_values")?.into_iter().collect(), + ), + assert_messages: Self::decode_vec_wrap(&value.assert_messages, "assert_messages")?, + }) + } +} + +impl ProtoCodec for ProtoSchema { + fn encode(value: &circuit::ExpressionWidth) -> ExpressionWidth { + use crate::proto::acir::circuit::expression_width::*; + let value = match value { + circuit::ExpressionWidth::Unbounded => Value::Unbounded(Unbounded {}), + circuit::ExpressionWidth::Bounded { width } => { + Value::Bounded(Bounded { width: Self::encode(width) }) + } + }; + ExpressionWidth { value: Some(value) } + } + + fn decode(value: &ExpressionWidth) -> eyre::Result { + use crate::proto::acir::circuit::expression_width::*; + decode_oneof_map(&value.value, |value| match value { + Value::Unbounded(_) => Ok(circuit::ExpressionWidth::Unbounded), + Value::Bounded(v) => Ok(circuit::ExpressionWidth::Bounded { + width: Self::decode_wrap(&v.width, "width")?, + }), + }) + } +} + +impl ProtoCodec<(circuit::OpcodeLocation, circuit::AssertionPayload), AssertMessage> + for ProtoSchema +where + F: AcirField, +{ + fn encode(value: &(circuit::OpcodeLocation, circuit::AssertionPayload)) -> AssertMessage { + AssertMessage { + location: Self::encode_some(&value.0), + payload: Self::encode_some(&value.1), + } + } + + fn decode( + value: &AssertMessage, + ) -> eyre::Result<(circuit::OpcodeLocation, circuit::AssertionPayload)> { + let location = Self::decode_some_wrap(&value.location, "location")?; + let payload = Self::decode_some_wrap(&value.payload, "payload")?; + Ok((location, payload)) + } +} + +impl ProtoCodec for ProtoSchema { + fn encode(value: &circuit::OpcodeLocation) -> OpcodeLocation { + use crate::proto::acir::circuit::opcode_location::*; + let value = match value { + circuit::OpcodeLocation::Acir(size) => Value::Acir(Self::encode(size)), + circuit::OpcodeLocation::Brillig { acir_index, brillig_index } => { + Value::Brillig(BrilligLocation { + acir_index: Self::encode(acir_index), + brillig_index: Self::encode(brillig_index), + }) + } + }; + OpcodeLocation { value: Some(value) } + } + + fn decode(value: &OpcodeLocation) -> eyre::Result { + use crate::proto::acir::circuit::opcode_location::*; + decode_oneof_map(&value.value, |value| match value { + Value::Acir(location) => { + Ok(circuit::OpcodeLocation::Acir(Self::decode_wrap(location, "location")?)) + } + Value::Brillig(location) => Ok(circuit::OpcodeLocation::Brillig { + acir_index: Self::decode_wrap(&location.acir_index, "acir_index")?, + brillig_index: Self::decode_wrap(&location.brillig_index, "brillig_index")?, + }), + }) + } +} + +impl ProtoCodec, AssertionPayload> for ProtoSchema +where + F: AcirField, +{ + fn encode(value: &circuit::AssertionPayload) -> AssertionPayload { + AssertionPayload { + error_selector: value.error_selector, + payload: Self::encode_vec(&value.payload), + } + } + + fn decode(value: &AssertionPayload) -> eyre::Result> { + Ok(circuit::AssertionPayload { + error_selector: value.error_selector, + payload: Self::decode_vec_wrap(&value.payload, "payload")?, + }) + } +} + +impl ProtoCodec, ExpressionOrMemory> for ProtoSchema +where + F: AcirField, +{ + fn encode(value: &circuit::ExpressionOrMemory) -> ExpressionOrMemory { + use crate::proto::acir::circuit::expression_or_memory::*; + let value = match value { + circuit::ExpressionOrMemory::Expression(expression) => { + Value::Expression(Self::encode(expression)) + } + circuit::ExpressionOrMemory::Memory(block_id) => Value::Memory(block_id.0), + }; + ExpressionOrMemory { value: Some(value) } + } + + fn decode(value: &ExpressionOrMemory) -> eyre::Result> { + use crate::proto::acir::circuit::expression_or_memory::*; + decode_oneof_map(&value.value, |value| match value { + Value::Expression(expression) => Ok(circuit::ExpressionOrMemory::Expression( + Self::decode_wrap(expression, "expression")?, + )), + Value::Memory(id) => Ok(circuit::ExpressionOrMemory::Memory(BlockId(*id))), + }) + } +} + +impl ProtoCodec, Opcode> for ProtoSchema +where + F: AcirField, +{ + fn encode(value: &circuit::Opcode) -> Opcode { + use crate::proto::acir::circuit::opcode::*; + let value = match value { + circuit::Opcode::AssertZero(expression) => Value::AssertZero(Self::encode(expression)), + circuit::Opcode::BlackBoxFuncCall(black_box_func_call) => { + Value::BlackboxFuncCall(Self::encode(black_box_func_call)) + } + circuit::Opcode::MemoryOp { block_id, op, predicate } => Value::MemoryOp(MemoryOp { + block_id: block_id.0, + op: Self::encode_some(op), + predicate: predicate.as_ref().map(Self::encode), + }), + circuit::Opcode::MemoryInit { block_id, init, block_type } => { + Value::MemoryInit(MemoryInit { + block_id: block_id.0, + init: Self::encode_vec(init), + block_type: Self::encode_some(block_type), + }) + } + circuit::Opcode::BrilligCall { id, inputs, outputs, predicate } => { + Value::BrilligCall(BrilligCall { + id: id.0, + inputs: Self::encode_vec(inputs), + outputs: Self::encode_vec(outputs), + predicate: predicate.as_ref().map(Self::encode), + }) + } + circuit::Opcode::Call { id, inputs, outputs, predicate } => Value::Call(Call { + id: id.0, + inputs: Self::encode_vec(inputs), + outputs: Self::encode_vec(outputs), + predicate: predicate.as_ref().map(Self::encode), + }), + }; + Opcode { value: Some(value) } + } + + fn decode(value: &Opcode) -> eyre::Result> { + use crate::proto::acir::circuit::opcode::*; + decode_oneof_map(&value.value, |value| match value { + Value::AssertZero(expression) => { + Ok(circuit::Opcode::AssertZero(Self::decode_wrap(expression, "assert_zero")?)) + } + Value::BlackboxFuncCall(black_box_func_call) => Ok(circuit::Opcode::BlackBoxFuncCall( + Self::decode_wrap(black_box_func_call, "blackbox_func_call")?, + )), + Value::MemoryOp(memory_op) => Ok(circuit::Opcode::MemoryOp { + block_id: BlockId(memory_op.block_id), + op: Self::decode_some_wrap(&memory_op.op, "op")?, + predicate: Self::decode_opt_wrap(&memory_op.predicate, "predicate")?, + }), + Value::MemoryInit(memory_init) => Ok(circuit::Opcode::MemoryInit { + block_id: BlockId(memory_init.block_id), + init: Self::decode_vec_wrap(&memory_init.init, "init")?, + block_type: Self::decode_some_wrap(&memory_init.block_type, "block_type")?, + }), + Value::BrilligCall(brillig_call) => Ok(circuit::Opcode::BrilligCall { + id: BrilligFunctionId(brillig_call.id), + inputs: Self::decode_vec_wrap(&brillig_call.inputs, "inputs")?, + outputs: Self::decode_vec_wrap(&brillig_call.outputs, "outputs")?, + predicate: Self::decode_opt_wrap(&brillig_call.predicate, "predicate")?, + }), + Value::Call(call) => Ok(circuit::Opcode::Call { + id: AcirFunctionId(call.id), + inputs: Self::decode_vec_wrap(&call.inputs, "inputs")?, + outputs: Self::decode_vec_wrap(&call.outputs, "outputs")?, + predicate: Self::decode_opt_wrap(&call.predicate, "predicate")?, + }), + }) + } +} + +impl ProtoCodec, MemOp> for ProtoSchema +where + F: AcirField, +{ + fn encode(value: &opcodes::MemOp) -> MemOp { + MemOp { + operation: Self::encode_some(&value.operation), + index: Self::encode_some(&value.index), + value: Self::encode_some(&value.value), + } + } + + fn decode(value: &MemOp) -> eyre::Result> { + Ok(opcodes::MemOp { + operation: Self::decode_some_wrap(&value.operation, "operation")?, + index: Self::decode_some_wrap(&value.index, "index")?, + value: Self::decode_some_wrap(&value.value, "value")?, + }) + } +} + +impl ProtoCodec, BlackBoxFuncCall> for ProtoSchema +where + F: AcirField, +{ + fn encode(value: &opcodes::BlackBoxFuncCall) -> BlackBoxFuncCall { + use crate::proto::acir::circuit::black_box_func_call::*; + let value = match value { + opcodes::BlackBoxFuncCall::AES128Encrypt { inputs, iv, key, outputs } => { + Value::Aes128Encrypt(Aes128Encrypt { + inputs: Self::encode_vec(inputs), + iv: Self::encode_vec(iv.as_ref()), + key: Self::encode_vec(key.as_ref()), + outputs: Self::encode_vec(outputs), + }) + } + opcodes::BlackBoxFuncCall::AND { lhs, rhs, output } => Value::And(And { + lhs: Self::encode_some(lhs), + rhs: Self::encode_some(rhs), + output: Self::encode_some(output), + }), + opcodes::BlackBoxFuncCall::XOR { lhs, rhs, output } => Value::Xor(Xor { + lhs: Self::encode_some(lhs), + rhs: Self::encode_some(rhs), + output: Self::encode_some(output), + }), + opcodes::BlackBoxFuncCall::RANGE { input } => { + Value::Range(Range { input: Self::encode_some(input) }) + } + opcodes::BlackBoxFuncCall::Blake2s { inputs, outputs } => Value::Blake2s(Blake2s { + inputs: Self::encode_vec(inputs), + outputs: Self::encode_vec(outputs.as_ref()), + }), + opcodes::BlackBoxFuncCall::Blake3 { inputs, outputs } => Value::Blake3(Blake3 { + inputs: Self::encode_vec(inputs), + outputs: Self::encode_vec(outputs.as_ref()), + }), + opcodes::BlackBoxFuncCall::EcdsaSecp256k1 { + public_key_x, + public_key_y, + signature, + hashed_message, + output, + } => Value::EcdsaSecp256k1(EcdsaSecp256k1 { + public_key_x: Self::encode_vec(public_key_x.as_ref()), + public_key_y: Self::encode_vec(public_key_y.as_ref()), + signature: Self::encode_vec(signature.as_ref()), + hashed_message: Self::encode_vec(hashed_message.as_ref()), + output: Self::encode_some(output), + }), + opcodes::BlackBoxFuncCall::EcdsaSecp256r1 { + public_key_x, + public_key_y, + signature, + hashed_message, + output, + } => Value::EcdsaSecp256r1(EcdsaSecp256r1 { + public_key_x: Self::encode_vec(public_key_x.as_ref()), + public_key_y: Self::encode_vec(public_key_y.as_ref()), + signature: Self::encode_vec(signature.as_ref()), + hashed_message: Self::encode_vec(hashed_message.as_ref()), + output: Self::encode_some(output), + }), + opcodes::BlackBoxFuncCall::MultiScalarMul { points, scalars, outputs } => { + let (w1, w2, w3) = outputs; + Value::MultiScalarMul(MultiScalarMul { + points: Self::encode_vec(points), + scalars: Self::encode_vec(scalars), + outputs: Self::encode_vec([w1, w2, w3]), + }) + } + opcodes::BlackBoxFuncCall::EmbeddedCurveAdd { input1, input2, outputs } => { + let (w1, w2, w3) = outputs; + Value::EmbeddedCurveAdd(EmbeddedCurveAdd { + input1: Self::encode_vec(input1.as_ref()), + input2: Self::encode_vec(input2.as_ref()), + outputs: Self::encode_vec([w1, w2, w3]), + }) + } + opcodes::BlackBoxFuncCall::Keccakf1600 { inputs, outputs } => { + Value::KeccakF1600(Keccakf1600 { + inputs: Self::encode_vec(inputs.as_ref()), + outputs: Self::encode_vec(outputs.as_ref()), + }) + } + opcodes::BlackBoxFuncCall::RecursiveAggregation { + verification_key, + proof, + public_inputs, + key_hash, + proof_type, + } => Value::RecursiveAggregation(RecursiveAggregation { + verification_key: Self::encode_vec(verification_key), + proof: Self::encode_vec(proof), + public_inputs: Self::encode_vec(public_inputs), + key_hash: Self::encode_some(key_hash), + proof_type: *proof_type, + }), + opcodes::BlackBoxFuncCall::BigIntAdd { lhs, rhs, output } => { + Value::BigIntAdd(BigIntAdd { lhs: *lhs, rhs: *rhs, output: *output }) + } + opcodes::BlackBoxFuncCall::BigIntSub { lhs, rhs, output } => { + Value::BigIntSub(BigIntSub { lhs: *lhs, rhs: *rhs, output: *output }) + } + opcodes::BlackBoxFuncCall::BigIntMul { lhs, rhs, output } => { + Value::BigIntMul(BigIntMul { lhs: *lhs, rhs: *rhs, output: *output }) + } + opcodes::BlackBoxFuncCall::BigIntDiv { lhs, rhs, output } => { + Value::BigIntDiv(BigIntDiv { lhs: *lhs, rhs: *rhs, output: *output }) + } + opcodes::BlackBoxFuncCall::BigIntFromLeBytes { inputs, modulus, output } => { + Value::BigIntFromLeBytes(BigIntFromLeBytes { + inputs: Self::encode_vec(inputs), + modulus: modulus.clone(), + output: *output, + }) + } + opcodes::BlackBoxFuncCall::BigIntToLeBytes { input, outputs } => { + Value::BigIntToLeBytes(BigIntToLeBytes { + input: *input, + outputs: Self::encode_vec(outputs), + }) + } + opcodes::BlackBoxFuncCall::Poseidon2Permutation { inputs, outputs, len } => { + Value::Poseidon2Permutation(Poseidon2Permutation { + inputs: Self::encode_vec(inputs), + outputs: Self::encode_vec(outputs), + len: *len, + }) + } + opcodes::BlackBoxFuncCall::Sha256Compression { inputs, hash_values, outputs } => { + Value::Sha256Compression(Sha256Compression { + inputs: Self::encode_vec(inputs.as_ref()), + hash_values: Self::encode_vec(hash_values.as_ref()), + outputs: Self::encode_vec(outputs.as_ref()), + }) + } + }; + BlackBoxFuncCall { value: Some(value) } + } + + fn decode(value: &BlackBoxFuncCall) -> eyre::Result> { + use crate::proto::acir::circuit::black_box_func_call::*; + decode_oneof_map( + &value.value, + |value| -> Result, eyre::Error> { + match value { + Value::Aes128Encrypt(v) => Ok(opcodes::BlackBoxFuncCall::AES128Encrypt { + inputs: Self::decode_vec_wrap(&v.inputs, "inputs")?, + iv: Self::decode_box_arr_wrap(&v.iv, "iv")?, + key: Self::decode_box_arr_wrap(&v.key, "key")?, + outputs: Self::decode_vec_wrap(&v.outputs, "witness")?, + }), + Value::And(v) => Ok(opcodes::BlackBoxFuncCall::AND { + lhs: Self::decode_some_wrap(&v.lhs, "lhs")?, + rhs: Self::decode_some_wrap(&v.rhs, "rhs")?, + output: Self::decode_some_wrap(&v.output, "output")?, + }), + Value::Xor(v) => Ok(opcodes::BlackBoxFuncCall::XOR { + lhs: Self::decode_some_wrap(&v.lhs, "lhs")?, + rhs: Self::decode_some_wrap(&v.rhs, "rhs")?, + output: Self::decode_some_wrap(&v.output, "output")?, + }), + Value::Range(v) => Ok(opcodes::BlackBoxFuncCall::RANGE { + input: Self::decode_some_wrap(&v.input, "input")?, + }), + Value::Blake2s(v) => Ok(opcodes::BlackBoxFuncCall::Blake2s { + inputs: Self::decode_vec_wrap(&v.inputs, "inputs")?, + outputs: Self::decode_box_arr_wrap(&v.outputs, "outputs")?, + }), + Value::Blake3(v) => Ok(opcodes::BlackBoxFuncCall::Blake3 { + inputs: Self::decode_vec_wrap(&v.inputs, "inputs")?, + outputs: Self::decode_box_arr_wrap(&v.outputs, "outputs")?, + }), + Value::EcdsaSecp256k1(v) => Ok(opcodes::BlackBoxFuncCall::EcdsaSecp256k1 { + public_key_x: Self::decode_box_arr_wrap(&v.public_key_x, "public_key_x")?, + public_key_y: Self::decode_box_arr_wrap(&v.public_key_y, "public_key_y")?, + signature: Self::decode_box_arr_wrap(&v.signature, "signature")?, + hashed_message: Self::decode_box_arr_wrap( + &v.hashed_message, + "hashed_message", + )?, + output: Self::decode_some_wrap(&v.output, "output")?, + }), + Value::EcdsaSecp256r1(v) => Ok(opcodes::BlackBoxFuncCall::EcdsaSecp256r1 { + public_key_x: Self::decode_box_arr_wrap(&v.public_key_x, "public_key_x")?, + public_key_y: Self::decode_box_arr_wrap(&v.public_key_y, "public_key_y")?, + signature: Self::decode_box_arr_wrap(&v.signature, "signature")?, + hashed_message: Self::decode_box_arr_wrap( + &v.hashed_message, + "hashed_message", + )?, + output: Self::decode_some_wrap(&v.output, "output")?, + }), + Value::MultiScalarMul(v) => Ok(opcodes::BlackBoxFuncCall::MultiScalarMul { + points: Self::decode_vec_wrap(&v.points, "points")?, + scalars: Self::decode_vec_wrap(&v.scalars, "scalars")?, + outputs: Self::decode_arr_wrap(&v.outputs, "outputs") + .map(|[w1, w2, w3]| (w1, w2, w3))?, + }), + Value::EmbeddedCurveAdd(v) => Ok(opcodes::BlackBoxFuncCall::EmbeddedCurveAdd { + input1: Self::decode_box_arr_wrap(&v.input1, "input1")?, + input2: Self::decode_box_arr_wrap(&v.input2, "input2")?, + outputs: Self::decode_arr_wrap(&v.outputs, "outputs") + .map(|[w1, w2, w3]| (w1, w2, w3))?, + }), + Value::KeccakF1600(v) => Ok(opcodes::BlackBoxFuncCall::Keccakf1600 { + inputs: Self::decode_box_arr_wrap(&v.inputs, "inputs")?, + outputs: Self::decode_box_arr_wrap(&v.outputs, "outputs")?, + }), + Value::RecursiveAggregation(v) => { + Ok(opcodes::BlackBoxFuncCall::RecursiveAggregation { + verification_key: Self::decode_vec_wrap( + &v.verification_key, + "verification_key", + )?, + proof: Self::decode_vec_wrap(&v.proof, "proof")?, + public_inputs: Self::decode_vec_wrap( + &v.public_inputs, + "public_inputs", + )?, + key_hash: Self::decode_some_wrap(&v.key_hash, "key_hash")?, + proof_type: v.proof_type, + }) + } + Value::BigIntAdd(v) => Ok(opcodes::BlackBoxFuncCall::BigIntAdd { + lhs: v.lhs, + rhs: v.rhs, + output: v.output, + }), + Value::BigIntSub(v) => Ok(opcodes::BlackBoxFuncCall::BigIntSub { + lhs: v.lhs, + rhs: v.rhs, + output: v.output, + }), + Value::BigIntMul(v) => Ok(opcodes::BlackBoxFuncCall::BigIntMul { + lhs: v.lhs, + rhs: v.rhs, + output: v.output, + }), + Value::BigIntDiv(v) => Ok(opcodes::BlackBoxFuncCall::BigIntDiv { + lhs: v.lhs, + rhs: v.rhs, + output: v.output, + }), + Value::BigIntFromLeBytes(v) => { + Ok(opcodes::BlackBoxFuncCall::BigIntFromLeBytes { + inputs: Self::decode_vec_wrap(&v.inputs, "inputs")?, + modulus: v.modulus.clone(), + output: v.output, + }) + } + Value::BigIntToLeBytes(v) => Ok(opcodes::BlackBoxFuncCall::BigIntToLeBytes { + input: v.input, + outputs: Self::decode_vec_wrap(&v.outputs, "outputs")?, + }), + Value::Poseidon2Permutation(v) => { + Ok(opcodes::BlackBoxFuncCall::Poseidon2Permutation { + inputs: Self::decode_vec_wrap(&v.inputs, "inputs")?, + outputs: Self::decode_vec_wrap(&v.outputs, "outputs")?, + len: v.len, + }) + } + Value::Sha256Compression(v) => { + Ok(opcodes::BlackBoxFuncCall::Sha256Compression { + inputs: Self::decode_box_arr_wrap(&v.inputs, "inputs")?, + hash_values: Self::decode_box_arr_wrap(&v.hash_values, "hash_values")?, + outputs: Self::decode_box_arr_wrap(&v.outputs, "outputs")?, + }) + } + } + }, + ) + } +} + +impl ProtoCodec, FunctionInput> for ProtoSchema +where + F: AcirField, +{ + fn encode(value: &opcodes::FunctionInput) -> FunctionInput { + FunctionInput { input: Self::encode_some(value.input_ref()), num_bits: value.num_bits() } + } + + fn decode(value: &FunctionInput) -> eyre::Result> { + let input = Self::decode_some_wrap(&value.input, "input")?; + + match input { + opcodes::ConstantOrWitnessEnum::Constant(c) => { + opcodes::FunctionInput::constant(c, value.num_bits).wrap_err("constant") + } + opcodes::ConstantOrWitnessEnum::Witness(w) => { + Ok(opcodes::FunctionInput::witness(w, value.num_bits)) + } + } + } +} + +impl ProtoCodec, ConstantOrWitnessEnum> for ProtoSchema +where + F: AcirField, +{ + fn encode(value: &opcodes::ConstantOrWitnessEnum) -> ConstantOrWitnessEnum { + use crate::proto::acir::circuit::constant_or_witness_enum::*; + let value = match value { + opcodes::ConstantOrWitnessEnum::Constant(field) => Value::Constant(Self::encode(field)), + opcodes::ConstantOrWitnessEnum::Witness(witness) => { + Value::Witness(Self::encode(witness)) + } + }; + ConstantOrWitnessEnum { value: Some(value) } + } + + fn decode(value: &ConstantOrWitnessEnum) -> eyre::Result> { + use crate::proto::acir::circuit::constant_or_witness_enum::*; + decode_oneof_map(&value.value, |value| match value { + Value::Constant(field) => { + Ok(opcodes::ConstantOrWitnessEnum::Constant(Self::decode_wrap(field, "constant")?)) + } + Value::Witness(witness) => { + Ok(opcodes::ConstantOrWitnessEnum::Witness(Self::decode_wrap(witness, "witness")?)) + } + }) + } +} + +impl ProtoCodec for ProtoSchema { + fn encode(value: &opcodes::BlockType) -> BlockType { + use crate::proto::acir::circuit::block_type::*; + let value = match value { + opcodes::BlockType::Memory => Value::Memory(Memory {}), + opcodes::BlockType::CallData(value) => Value::CallData(CallData { value: *value }), + opcodes::BlockType::ReturnData => Value::ReturnData(ReturnData {}), + }; + BlockType { value: Some(value) } + } + + fn decode(value: &BlockType) -> eyre::Result { + use crate::proto::acir::circuit::block_type::*; + decode_oneof_map(&value.value, |value| match value { + Value::Memory(_) => Ok(opcodes::BlockType::Memory), + Value::CallData(v) => Ok(opcodes::BlockType::CallData(v.value)), + Value::ReturnData(_) => Ok(opcodes::BlockType::ReturnData), + }) + } +} + +impl ProtoCodec, BrilligInputs> for ProtoSchema +where + F: AcirField, +{ + fn encode(value: &circuit::brillig::BrilligInputs) -> BrilligInputs { + use crate::proto::acir::circuit::brillig_inputs::*; + let value = match value { + circuit::brillig::BrilligInputs::Single(expression) => { + Value::Single(Self::encode(expression)) + } + circuit::brillig::BrilligInputs::Array(expressions) => { + Value::Array(Array { values: Self::encode_vec(expressions) }) + } + circuit::brillig::BrilligInputs::MemoryArray(block_id) => { + Value::MemoryArray(block_id.0) + } + }; + BrilligInputs { value: Some(value) } + } + + fn decode(value: &BrilligInputs) -> eyre::Result> { + use crate::proto::acir::circuit::brillig_inputs::*; + decode_oneof_map(&value.value, |value| match value { + Value::Single(expression) => Ok(circuit::brillig::BrilligInputs::Single( + Self::decode_wrap(expression, "single")?, + )), + Value::Array(array) => Ok(circuit::brillig::BrilligInputs::Array( + Self::decode_vec_wrap(&array.values, "array")?, + )), + Value::MemoryArray(id) => { + Ok(circuit::brillig::BrilligInputs::MemoryArray(BlockId(*id))) + } + }) + } +} + +impl ProtoCodec for ProtoSchema { + fn encode(value: &circuit::brillig::BrilligOutputs) -> BrilligOutputs { + use crate::proto::acir::circuit::brillig_outputs::*; + let value = match value { + circuit::brillig::BrilligOutputs::Simple(witness) => { + Value::Simple(Self::encode(witness)) + } + circuit::brillig::BrilligOutputs::Array(witnesses) => { + Value::Array(Array { values: Self::encode_vec(witnesses) }) + } + }; + BrilligOutputs { value: Some(value) } + } + + fn decode(value: &BrilligOutputs) -> eyre::Result { + use crate::proto::acir::circuit::brillig_outputs::*; + + decode_oneof_map(&value.value, |value| match value { + Value::Simple(witness) => { + Ok(circuit::brillig::BrilligOutputs::Simple(Self::decode_wrap(witness, "simple")?)) + } + Value::Array(array) => Ok(circuit::brillig::BrilligOutputs::Array( + Self::decode_vec_wrap(&array.values, "array")?, + )), + }) + } +} diff --git a/noir/noir-repo/acvm-repo/acir/src/proto/convert/brillig.rs b/noir/noir-repo/acvm-repo/acir/src/proto/convert/brillig.rs new file mode 100644 index 000000000000..54df85ec4edd --- /dev/null +++ b/noir/noir-repo/acvm-repo/acir/src/proto/convert/brillig.rs @@ -0,0 +1,717 @@ +use crate::{ + circuit, + proto::brillig::{BitSize, BlackBoxOp, HeapArray, HeapValueType, HeapVector, ValueOrArray}, +}; +use acir_field::AcirField; +use color_eyre::eyre::{self, bail}; +use noir_protobuf::{ProtoCodec, decode_oneof_map}; + +use crate::proto::brillig::{ + BinaryFieldOpKind, BinaryIntOpKind, BrilligBytecode, BrilligOpcode, IntegerBitSize, + MemoryAddress, brillig_opcode, +}; + +use super::ProtoSchema; + +impl ProtoCodec, BrilligBytecode> + for ProtoSchema +{ + fn encode(value: &circuit::brillig::BrilligBytecode) -> BrilligBytecode { + BrilligBytecode { bytecode: Self::encode_vec(&value.bytecode) } + } + + fn decode(value: &BrilligBytecode) -> eyre::Result> { + Ok(circuit::brillig::BrilligBytecode { + bytecode: Self::decode_vec_wrap(&value.bytecode, "bytecode")?, + }) + } +} + +impl ProtoCodec, BrilligOpcode> for ProtoSchema { + fn encode(value: &brillig::Opcode) -> BrilligOpcode { + use brillig_opcode::*; + + let value = match value { + brillig::Opcode::BinaryFieldOp { destination, op, lhs, rhs } => { + Value::BinaryFieldOp(BinaryFieldOp { + destination: Self::encode_some(destination), + op: Self::encode_enum(op), + lhs: Self::encode_some(lhs), + rhs: Self::encode_some(rhs), + }) + } + brillig::Opcode::BinaryIntOp { destination, op, bit_size, lhs, rhs } => { + Value::BinaryIntOp(BinaryIntOp { + destination: Self::encode_some(destination), + op: Self::encode_enum(op), + bit_size: Self::encode_enum(bit_size), + lhs: Self::encode_some(lhs), + rhs: Self::encode_some(rhs), + }) + } + brillig::Opcode::Not { destination, source, bit_size } => Value::Not(Not { + destination: Self::encode_some(destination), + source: Self::encode_some(source), + bit_size: Self::encode_enum(bit_size), + }), + brillig::Opcode::Cast { destination, source, bit_size } => Value::Cast(Cast { + destination: Self::encode_some(destination), + source: Self::encode_some(source), + bit_size: Self::encode_some(bit_size), + }), + brillig::Opcode::JumpIfNot { condition, location } => Value::JumpIfNot(JumpIfNot { + condition: Self::encode_some(condition), + location: Self::encode(location), + }), + brillig::Opcode::JumpIf { condition, location } => Value::JumpIf(JumpIf { + condition: Self::encode_some(condition), + location: Self::encode(location), + }), + brillig::Opcode::Jump { location } => { + Value::Jump(Jump { location: Self::encode(location) }) + } + brillig::Opcode::CalldataCopy { destination_address, size_address, offset_address } => { + Value::CalldataCopy(CalldataCopy { + destination_address: Self::encode_some(destination_address), + size_address: Self::encode_some(size_address), + offset_address: Self::encode_some(offset_address), + }) + } + brillig::Opcode::Call { location } => { + Value::Call(Call { location: Self::encode(location) }) + } + brillig::Opcode::Const { destination, bit_size, value } => Value::Const(Const { + destination: Self::encode_some(destination), + bit_size: Self::encode_some(bit_size), + value: Self::encode_some(value), + }), + brillig::Opcode::IndirectConst { destination_pointer, bit_size, value } => { + Value::IndirectConst(IndirectConst { + destination_pointer: Self::encode_some(destination_pointer), + bit_size: Self::encode_some(bit_size), + value: Self::encode_some(value), + }) + } + brillig::Opcode::Return => Value::Return(Return {}), + brillig::Opcode::ForeignCall { + function, + destinations, + destination_value_types, + inputs, + input_value_types, + } => Value::ForeignCall(ForeignCall { + function: function.to_string(), + destinations: Self::encode_vec(destinations), + destination_value_types: Self::encode_vec(destination_value_types), + inputs: Self::encode_vec(inputs), + input_value_types: Self::encode_vec(input_value_types), + }), + brillig::Opcode::Mov { destination, source } => Value::Mov(Mov { + destination: Self::encode_some(destination), + source: Self::encode_some(source), + }), + brillig::Opcode::ConditionalMov { destination, source_a, source_b, condition } => { + Value::ConditionalMov(ConditionalMov { + destination: Self::encode_some(destination), + source_a: Self::encode_some(source_a), + source_b: Self::encode_some(source_b), + condition: Self::encode_some(condition), + }) + } + brillig::Opcode::Load { destination, source_pointer } => Value::Load(Load { + destination: Self::encode_some(destination), + source_pointer: Self::encode_some(source_pointer), + }), + brillig::Opcode::Store { destination_pointer, source } => Value::Store(Store { + destination_pointer: Self::encode_some(destination_pointer), + source: Self::encode_some(source), + }), + brillig::Opcode::BlackBox(black_box_op) => { + Value::BlackBox(BlackBox { op: Self::encode_some(black_box_op) }) + } + brillig::Opcode::Trap { revert_data } => { + Value::Trap(Trap { revert_data: Self::encode_some(revert_data) }) + } + brillig::Opcode::Stop { return_data } => { + Value::Stop(Stop { return_data: Self::encode_some(return_data) }) + } + }; + BrilligOpcode { value: Some(value) } + } + + fn decode(value: &BrilligOpcode) -> eyre::Result> { + use brillig_opcode::*; + + decode_oneof_map(&value.value, |value| match value { + Value::BinaryFieldOp(v) => Ok(brillig::Opcode::BinaryFieldOp { + destination: Self::decode_some_wrap(&v.destination, "destination")?, + op: Self::decode_enum_wrap(v.op, "op")?, + lhs: Self::decode_some_wrap(&v.lhs, "lhs")?, + rhs: Self::decode_some_wrap(&v.rhs, "rhs")?, + }), + Value::BinaryIntOp(v) => Ok(brillig::Opcode::BinaryIntOp { + destination: Self::decode_some_wrap(&v.destination, "destination")?, + op: Self::decode_enum_wrap(v.op, "op")?, + bit_size: Self::decode_enum_wrap(v.bit_size, "bit_size")?, + lhs: Self::decode_some_wrap(&v.lhs, "lhs")?, + rhs: Self::decode_some_wrap(&v.rhs, "rhs")?, + }), + Value::Not(v) => Ok(brillig::Opcode::Not { + destination: Self::decode_some_wrap(&v.destination, "destination")?, + source: Self::decode_some_wrap(&v.source, "source")?, + bit_size: Self::decode_enum_wrap(v.bit_size, "bit_size")?, + }), + Value::Cast(v) => Ok(brillig::Opcode::Cast { + destination: Self::decode_some_wrap(&v.destination, "destination")?, + source: Self::decode_some_wrap(&v.source, "source")?, + bit_size: Self::decode_some_wrap(&v.bit_size, "bit_size")?, + }), + Value::JumpIfNot(v) => Ok(brillig::Opcode::JumpIfNot { + condition: Self::decode_some_wrap(&v.condition, "condition")?, + location: Self::decode_wrap(&v.location, "location")?, + }), + Value::JumpIf(v) => Ok(brillig::Opcode::JumpIf { + condition: Self::decode_some_wrap(&v.condition, "condition")?, + location: Self::decode_wrap(&v.location, "location")?, + }), + Value::Jump(v) => { + Ok(brillig::Opcode::Jump { location: Self::decode_wrap(&v.location, "location")? }) + } + Value::CalldataCopy(v) => Ok(brillig::Opcode::CalldataCopy { + destination_address: Self::decode_some_wrap( + &v.destination_address, + "destination_address", + )?, + size_address: Self::decode_some_wrap(&v.size_address, "size_address")?, + offset_address: Self::decode_some_wrap(&v.offset_address, "offset_address")?, + }), + Value::Call(v) => { + Ok(brillig::Opcode::Call { location: Self::decode_wrap(&v.location, "location")? }) + } + Value::Const(v) => Ok(brillig::Opcode::Const { + destination: Self::decode_some_wrap(&v.destination, "destination")?, + bit_size: Self::decode_some_wrap(&v.bit_size, "bit_size")?, + value: Self::decode_some_wrap(&v.value, "value")?, + }), + Value::IndirectConst(v) => Ok(brillig::Opcode::IndirectConst { + destination_pointer: Self::decode_some_wrap( + &v.destination_pointer, + "destination_pointer", + )?, + bit_size: Self::decode_some_wrap(&v.bit_size, "bit_size")?, + value: Self::decode_some_wrap(&v.value, "value")?, + }), + Value::Return(_) => Ok(brillig::Opcode::Return), + Value::ForeignCall(v) => Ok(brillig::Opcode::ForeignCall { + function: v.function.clone(), + destinations: Self::decode_vec_wrap(&v.destinations, "destinations")?, + destination_value_types: Self::decode_vec_wrap( + &v.destination_value_types, + "destination_value_types", + )?, + inputs: Self::decode_vec_wrap(&v.inputs, "inputs")?, + input_value_types: Self::decode_vec_wrap( + &v.input_value_types, + "input_value_types", + )?, + }), + Value::Mov(v) => Ok(brillig::Opcode::Mov { + destination: Self::decode_some_wrap(&v.destination, "destination")?, + source: Self::decode_some_wrap(&v.source, "source")?, + }), + Value::ConditionalMov(v) => Ok(brillig::Opcode::ConditionalMov { + destination: Self::decode_some_wrap(&v.destination, "destination")?, + source_a: Self::decode_some_wrap(&v.source_a, "source_a")?, + source_b: Self::decode_some_wrap(&v.source_b, "source_b")?, + condition: Self::decode_some_wrap(&v.condition, "condition")?, + }), + Value::Load(v) => Ok(brillig::Opcode::Load { + destination: Self::decode_some_wrap(&v.destination, "destination")?, + source_pointer: Self::decode_some_wrap(&v.source_pointer, "source_pointer")?, + }), + Value::Store(v) => Ok(brillig::Opcode::Store { + destination_pointer: Self::decode_some_wrap( + &v.destination_pointer, + "destination_pointer", + )?, + source: Self::decode_some_wrap(&v.source, "source")?, + }), + Value::BlackBox(v) => { + Ok(brillig::Opcode::BlackBox(Self::decode_some_wrap(&v.op, "black_box")?)) + } + Value::Trap(v) => Ok(brillig::Opcode::Trap { + revert_data: Self::decode_some_wrap(&v.revert_data, "revert_data")?, + }), + Value::Stop(v) => Ok(brillig::Opcode::Stop { + return_data: Self::decode_some_wrap(&v.return_data, "return_data")?, + }), + }) + } +} + +impl ProtoCodec for ProtoSchema { + fn encode(value: &brillig::MemoryAddress) -> MemoryAddress { + use crate::proto::brillig::memory_address::*; + let value = match value { + brillig::MemoryAddress::Direct(addr) => Value::Direct(Self::encode(addr)), + brillig::MemoryAddress::Relative(addr) => Value::Relative(Self::encode(addr)), + }; + MemoryAddress { value: Some(value) } + } + + fn decode(value: &MemoryAddress) -> eyre::Result { + use crate::proto::brillig::memory_address::*; + decode_oneof_map(&value.value, |value| match value { + Value::Direct(v) => Self::decode_wrap(v, "direct").map(brillig::MemoryAddress::Direct), + Value::Relative(v) => { + Self::decode_wrap(v, "relative").map(brillig::MemoryAddress::Relative) + } + }) + } +} + +impl ProtoCodec for ProtoSchema { + fn encode(value: &brillig::BinaryFieldOp) -> BinaryFieldOpKind { + match value { + brillig::BinaryFieldOp::Add => BinaryFieldOpKind::BfoAdd, + brillig::BinaryFieldOp::Sub => BinaryFieldOpKind::BfoSub, + brillig::BinaryFieldOp::Mul => BinaryFieldOpKind::BfoMul, + brillig::BinaryFieldOp::Div => BinaryFieldOpKind::BfoDiv, + brillig::BinaryFieldOp::IntegerDiv => BinaryFieldOpKind::BfoIntegerDiv, + brillig::BinaryFieldOp::Equals => BinaryFieldOpKind::BfoEquals, + brillig::BinaryFieldOp::LessThan => BinaryFieldOpKind::BfoLessThan, + brillig::BinaryFieldOp::LessThanEquals => BinaryFieldOpKind::BfoLessThanEquals, + } + } + + fn decode(value: &BinaryFieldOpKind) -> eyre::Result { + match value { + BinaryFieldOpKind::BfoUnspecified => bail!("unspecified BinaryFieldOp"), + BinaryFieldOpKind::BfoAdd => Ok(brillig::BinaryFieldOp::Add), + BinaryFieldOpKind::BfoSub => Ok(brillig::BinaryFieldOp::Sub), + BinaryFieldOpKind::BfoMul => Ok(brillig::BinaryFieldOp::Mul), + BinaryFieldOpKind::BfoDiv => Ok(brillig::BinaryFieldOp::Div), + BinaryFieldOpKind::BfoIntegerDiv => Ok(brillig::BinaryFieldOp::IntegerDiv), + BinaryFieldOpKind::BfoEquals => Ok(brillig::BinaryFieldOp::Equals), + BinaryFieldOpKind::BfoLessThan => Ok(brillig::BinaryFieldOp::LessThan), + BinaryFieldOpKind::BfoLessThanEquals => Ok(brillig::BinaryFieldOp::LessThanEquals), + } + } +} + +impl ProtoCodec for ProtoSchema { + fn encode(value: &brillig::BinaryIntOp) -> BinaryIntOpKind { + match value { + brillig::BinaryIntOp::Add => BinaryIntOpKind::BioAdd, + brillig::BinaryIntOp::Sub => BinaryIntOpKind::BioSub, + brillig::BinaryIntOp::Mul => BinaryIntOpKind::BioMul, + brillig::BinaryIntOp::Div => BinaryIntOpKind::BioDiv, + brillig::BinaryIntOp::Equals => BinaryIntOpKind::BioEquals, + brillig::BinaryIntOp::LessThan => BinaryIntOpKind::BioLessThan, + brillig::BinaryIntOp::LessThanEquals => BinaryIntOpKind::BioLessThanEquals, + brillig::BinaryIntOp::And => BinaryIntOpKind::BioAnd, + brillig::BinaryIntOp::Or => BinaryIntOpKind::BioOr, + brillig::BinaryIntOp::Xor => BinaryIntOpKind::BioXor, + brillig::BinaryIntOp::Shl => BinaryIntOpKind::BioShl, + brillig::BinaryIntOp::Shr => BinaryIntOpKind::BioShr, + } + } + + fn decode(value: &BinaryIntOpKind) -> eyre::Result { + match value { + BinaryIntOpKind::BioUnspecified => bail!("unspecified BinaryIntOp"), + BinaryIntOpKind::BioAdd => Ok(brillig::BinaryIntOp::Add), + BinaryIntOpKind::BioSub => Ok(brillig::BinaryIntOp::Sub), + BinaryIntOpKind::BioMul => Ok(brillig::BinaryIntOp::Mul), + BinaryIntOpKind::BioDiv => Ok(brillig::BinaryIntOp::Div), + BinaryIntOpKind::BioEquals => Ok(brillig::BinaryIntOp::Equals), + BinaryIntOpKind::BioLessThan => Ok(brillig::BinaryIntOp::LessThan), + BinaryIntOpKind::BioLessThanEquals => Ok(brillig::BinaryIntOp::LessThanEquals), + BinaryIntOpKind::BioAnd => Ok(brillig::BinaryIntOp::And), + BinaryIntOpKind::BioOr => Ok(brillig::BinaryIntOp::Or), + BinaryIntOpKind::BioXor => Ok(brillig::BinaryIntOp::Xor), + BinaryIntOpKind::BioShl => Ok(brillig::BinaryIntOp::Shl), + BinaryIntOpKind::BioShr => Ok(brillig::BinaryIntOp::Shr), + } + } +} + +impl ProtoCodec for ProtoSchema { + fn encode(value: &brillig::IntegerBitSize) -> IntegerBitSize { + match value { + brillig::IntegerBitSize::U1 => IntegerBitSize::IbsU1, + brillig::IntegerBitSize::U8 => IntegerBitSize::IbsU8, + brillig::IntegerBitSize::U16 => IntegerBitSize::IbsU16, + brillig::IntegerBitSize::U32 => IntegerBitSize::IbsU32, + brillig::IntegerBitSize::U64 => IntegerBitSize::IbsU64, + brillig::IntegerBitSize::U128 => IntegerBitSize::IbsU128, + } + } + + fn decode(value: &IntegerBitSize) -> eyre::Result { + match value { + IntegerBitSize::IbsUnspecified => bail!("unspecified IntegerBitSize"), + IntegerBitSize::IbsU1 => Ok(brillig::IntegerBitSize::U1), + IntegerBitSize::IbsU8 => Ok(brillig::IntegerBitSize::U8), + IntegerBitSize::IbsU16 => Ok(brillig::IntegerBitSize::U16), + IntegerBitSize::IbsU32 => Ok(brillig::IntegerBitSize::U32), + IntegerBitSize::IbsU64 => Ok(brillig::IntegerBitSize::U64), + IntegerBitSize::IbsU128 => Ok(brillig::IntegerBitSize::U128), + } + } +} + +impl ProtoCodec for ProtoSchema { + fn encode(value: &brillig::BitSize) -> BitSize { + use crate::proto::brillig::bit_size::*; + let value = match value { + brillig::BitSize::Field => Value::Field(Field {}), + brillig::BitSize::Integer(integer_bit_size) => { + Value::Integer(Self::encode_enum(integer_bit_size)) + } + }; + BitSize { value: Some(value) } + } + + fn decode(value: &BitSize) -> eyre::Result { + use crate::proto::brillig::bit_size::*; + decode_oneof_map(&value.value, |value| match value { + Value::Field(_) => Ok(brillig::BitSize::Field), + Value::Integer(size) => { + Ok(brillig::BitSize::Integer(Self::decode_enum_wrap(*size, "size")?)) + } + }) + } +} + +impl ProtoCodec for ProtoSchema { + fn encode(value: &brillig::ValueOrArray) -> ValueOrArray { + use crate::proto::brillig::value_or_array::*; + let value = match value { + brillig::ValueOrArray::MemoryAddress(memory_address) => { + Value::MemoryAddress(Self::encode(memory_address)) + } + brillig::ValueOrArray::HeapArray(heap_array) => { + Value::HeapArray(Self::encode(heap_array)) + } + brillig::ValueOrArray::HeapVector(heap_vector) => { + Value::HeapVector(Self::encode(heap_vector)) + } + }; + ValueOrArray { value: Some(value) } + } + + fn decode(value: &ValueOrArray) -> eyre::Result { + use crate::proto::brillig::value_or_array::*; + decode_oneof_map(&value.value, |value| match value { + Value::MemoryAddress(v) => { + Ok(brillig::ValueOrArray::MemoryAddress(Self::decode_wrap(v, "memory_address")?)) + } + Value::HeapArray(v) => { + Ok(brillig::ValueOrArray::HeapArray(Self::decode_wrap(v, "heap_array")?)) + } + Value::HeapVector(v) => { + Ok(brillig::ValueOrArray::HeapVector(Self::decode_wrap(v, "heap_vector")?)) + } + }) + } +} + +impl ProtoCodec for ProtoSchema { + fn encode(value: &brillig::HeapValueType) -> HeapValueType { + use crate::proto::brillig::heap_value_type::*; + let value = match value { + brillig::HeapValueType::Simple(bit_size) => Value::Simple(Self::encode(bit_size)), + brillig::HeapValueType::Array { value_types, size } => Value::Array(Array { + value_types: Self::encode_vec(value_types), + size: *size as u64, + }), + brillig::HeapValueType::Vector { value_types } => { + Value::Vector(Vector { value_types: Self::encode_vec(value_types) }) + } + }; + HeapValueType { value: Some(value) } + } + + fn decode(value: &HeapValueType) -> eyre::Result { + use crate::proto::brillig::heap_value_type::*; + decode_oneof_map(&value.value, |value| match value { + Value::Simple(bit_size) => { + Ok(brillig::HeapValueType::Simple(Self::decode_wrap(bit_size, "simple")?)) + } + Value::Array(v) => Ok(brillig::HeapValueType::Array { + value_types: Self::decode_vec_wrap(&v.value_types, "value_types")?, + size: Self::decode_wrap(&v.size, "size")?, + }), + Value::Vector(v) => Ok(brillig::HeapValueType::Vector { + value_types: Self::decode_vec_wrap(&v.value_types, "value_types")?, + }), + }) + } +} + +impl ProtoCodec for ProtoSchema { + fn encode(value: &brillig::HeapArray) -> HeapArray { + HeapArray { pointer: Self::encode_some(&value.pointer), size: Self::encode(&value.size) } + } + + fn decode(value: &HeapArray) -> eyre::Result { + Ok(brillig::HeapArray { + pointer: Self::decode_some_wrap(&value.pointer, "pointer")?, + size: Self::decode_wrap(&value.size, "size")?, + }) + } +} + +impl ProtoCodec for ProtoSchema { + fn encode(value: &brillig::HeapVector) -> HeapVector { + HeapVector { + pointer: Self::encode_some(&value.pointer), + size: Self::encode_some(&value.size), + } + } + + fn decode(value: &HeapVector) -> eyre::Result { + Ok(brillig::HeapVector { + pointer: Self::decode_some_wrap(&value.pointer, "pointer")?, + size: Self::decode_some_wrap(&value.size, "size")?, + }) + } +} + +impl ProtoCodec for ProtoSchema { + fn encode(value: &brillig::BlackBoxOp) -> BlackBoxOp { + use crate::proto::brillig::black_box_op::*; + let value = match value { + brillig::BlackBoxOp::AES128Encrypt { inputs, iv, key, outputs } => { + Value::Aes128Encrypt(Aes128Encrypt { + inputs: Self::encode_some(inputs), + iv: Self::encode_some(iv), + key: Self::encode_some(key), + outputs: Self::encode_some(outputs), + }) + } + brillig::BlackBoxOp::Blake2s { message, output } => Value::Blake2s(Blake2s { + message: Self::encode_some(message), + output: Self::encode_some(output), + }), + brillig::BlackBoxOp::Blake3 { message, output } => Value::Blake3(Blake3 { + message: Self::encode_some(message), + output: Self::encode_some(output), + }), + brillig::BlackBoxOp::Keccakf1600 { input, output } => Value::KeccakF1600(Keccakf1600 { + input: Self::encode_some(input), + output: Self::encode_some(output), + }), + brillig::BlackBoxOp::EcdsaSecp256k1 { + hashed_msg, + public_key_x, + public_key_y, + signature, + result, + } => Value::EcdsaSecp256k1(EcdsaSecp256k1 { + hashed_msg: Self::encode_some(hashed_msg), + public_key_x: Self::encode_some(public_key_x), + public_key_y: Self::encode_some(public_key_y), + signature: Self::encode_some(signature), + result: Self::encode_some(result), + }), + brillig::BlackBoxOp::EcdsaSecp256r1 { + hashed_msg, + public_key_x, + public_key_y, + signature, + result, + } => Value::EcdsaSecp256r1(EcdsaSecp256r1 { + hashed_msg: Self::encode_some(hashed_msg), + public_key_x: Self::encode_some(public_key_x), + public_key_y: Self::encode_some(public_key_y), + signature: Self::encode_some(signature), + result: Self::encode_some(result), + }), + brillig::BlackBoxOp::MultiScalarMul { points, scalars, outputs } => { + Value::MultiScalarMul(MultiScalarMul { + points: Self::encode_some(points), + scalars: Self::encode_some(scalars), + outputs: Self::encode_some(outputs), + }) + } + brillig::BlackBoxOp::EmbeddedCurveAdd { + input1_x, + input1_y, + input1_infinite, + input2_x, + input2_y, + input2_infinite, + result, + } => Value::EmbeddedCurveAdd(EmbeddedCurveAdd { + input1_x: Self::encode_some(input1_x), + input1_y: Self::encode_some(input1_y), + input1_infinite: Self::encode_some(input1_infinite), + input2_x: Self::encode_some(input2_x), + input2_y: Self::encode_some(input2_y), + input2_infinite: Self::encode_some(input2_infinite), + result: Self::encode_some(result), + }), + brillig::BlackBoxOp::BigIntAdd { lhs, rhs, output } => Value::BigIntAdd(BigIntAdd { + lhs: Self::encode_some(lhs), + rhs: Self::encode_some(rhs), + output: Self::encode_some(output), + }), + brillig::BlackBoxOp::BigIntSub { lhs, rhs, output } => Value::BigIntSub(BigIntSub { + lhs: Self::encode_some(lhs), + rhs: Self::encode_some(rhs), + output: Self::encode_some(output), + }), + brillig::BlackBoxOp::BigIntMul { lhs, rhs, output } => Value::BigIntMul(BigIntMul { + lhs: Self::encode_some(lhs), + rhs: Self::encode_some(rhs), + output: Self::encode_some(output), + }), + brillig::BlackBoxOp::BigIntDiv { lhs, rhs, output } => Value::BigIntDiv(BigIntDiv { + lhs: Self::encode_some(lhs), + rhs: Self::encode_some(rhs), + output: Self::encode_some(output), + }), + brillig::BlackBoxOp::BigIntFromLeBytes { inputs, modulus, output } => { + Value::BigIntFromLeBytes(BigIntFromLeBytes { + inputs: Self::encode_some(inputs), + modulus: Self::encode_some(modulus), + output: Self::encode_some(output), + }) + } + brillig::BlackBoxOp::BigIntToLeBytes { input, output } => { + Value::BigIntToLeBytes(BigIntToLeBytes { + input: Self::encode_some(input), + output: Self::encode_some(output), + }) + } + brillig::BlackBoxOp::Poseidon2Permutation { message, output, len } => { + Value::Poseidon2Permutation(Poseidon2Permutation { + message: Self::encode_some(message), + output: Self::encode_some(output), + len: Self::encode_some(len), + }) + } + brillig::BlackBoxOp::Sha256Compression { input, hash_values, output } => { + Value::Sha256Compression(Sha256Compression { + input: Self::encode_some(input), + hash_values: Self::encode_some(hash_values), + output: Self::encode_some(output), + }) + } + brillig::BlackBoxOp::ToRadix { + input, + radix, + output_pointer, + num_limbs, + output_bits, + } => Value::ToRadix(ToRadix { + input: Self::encode_some(input), + radix: Self::encode_some(radix), + output_pointer: Self::encode_some(output_pointer), + num_limbs: Self::encode_some(num_limbs), + output_bits: Self::encode_some(output_bits), + }), + }; + BlackBoxOp { value: Some(value) } + } + + fn decode(value: &BlackBoxOp) -> eyre::Result { + use crate::proto::brillig::black_box_op::*; + decode_oneof_map(&value.value, |value| match value { + Value::Aes128Encrypt(v) => Ok(brillig::BlackBoxOp::AES128Encrypt { + inputs: Self::decode_some_wrap(&v.inputs, "inputs")?, + iv: Self::decode_some_wrap(&v.iv, "iv")?, + key: Self::decode_some_wrap(&v.key, "key")?, + outputs: Self::decode_some_wrap(&v.outputs, "outputs")?, + }), + Value::Blake2s(v) => Ok(brillig::BlackBoxOp::Blake2s { + message: Self::decode_some_wrap(&v.message, "message")?, + output: Self::decode_some_wrap(&v.output, "output")?, + }), + Value::Blake3(v) => Ok(brillig::BlackBoxOp::Blake3 { + message: Self::decode_some_wrap(&v.message, "message")?, + output: Self::decode_some_wrap(&v.output, "output")?, + }), + Value::KeccakF1600(v) => Ok(brillig::BlackBoxOp::Keccakf1600 { + input: Self::decode_some_wrap(&v.input, "input")?, + output: Self::decode_some_wrap(&v.output, "output")?, + }), + Value::EcdsaSecp256k1(v) => Ok(brillig::BlackBoxOp::EcdsaSecp256k1 { + hashed_msg: Self::decode_some_wrap(&v.hashed_msg, "hashed_msg")?, + public_key_x: Self::decode_some_wrap(&v.public_key_x, "public_key_x")?, + public_key_y: Self::decode_some_wrap(&v.public_key_y, "public_key_y")?, + signature: Self::decode_some_wrap(&v.signature, "signature")?, + result: Self::decode_some_wrap(&v.result, "result")?, + }), + Value::EcdsaSecp256r1(v) => Ok(brillig::BlackBoxOp::EcdsaSecp256r1 { + hashed_msg: Self::decode_some_wrap(&v.hashed_msg, "hashed_msg")?, + public_key_x: Self::decode_some_wrap(&v.public_key_x, "public_key_x")?, + public_key_y: Self::decode_some_wrap(&v.public_key_y, "public_key_y")?, + signature: Self::decode_some_wrap(&v.signature, "signature")?, + result: Self::decode_some_wrap(&v.result, "result")?, + }), + Value::MultiScalarMul(v) => Ok(brillig::BlackBoxOp::MultiScalarMul { + points: Self::decode_some_wrap(&v.points, "points")?, + scalars: Self::decode_some_wrap(&v.scalars, "scalars")?, + outputs: Self::decode_some_wrap(&v.outputs, "outputs")?, + }), + Value::EmbeddedCurveAdd(v) => Ok(brillig::BlackBoxOp::EmbeddedCurveAdd { + input1_x: Self::decode_some_wrap(&v.input1_x, "input1_x")?, + input1_y: Self::decode_some_wrap(&v.input1_y, "input1_y")?, + input1_infinite: Self::decode_some_wrap(&v.input1_infinite, "input1_infinite")?, + input2_x: Self::decode_some_wrap(&v.input2_x, "input2_x")?, + input2_y: Self::decode_some_wrap(&v.input2_y, "input2_y")?, + input2_infinite: Self::decode_some_wrap(&v.input2_infinite, "input2_infinite")?, + result: Self::decode_some_wrap(&v.result, "result")?, + }), + Value::BigIntAdd(v) => Ok(brillig::BlackBoxOp::BigIntAdd { + lhs: Self::decode_some_wrap(&v.lhs, "lhs")?, + rhs: Self::decode_some_wrap(&v.rhs, "rhs")?, + output: Self::decode_some_wrap(&v.output, "output")?, + }), + Value::BigIntSub(v) => Ok(brillig::BlackBoxOp::BigIntSub { + lhs: Self::decode_some_wrap(&v.lhs, "lhs")?, + rhs: Self::decode_some_wrap(&v.rhs, "rhs")?, + output: Self::decode_some_wrap(&v.output, "output")?, + }), + Value::BigIntMul(v) => Ok(brillig::BlackBoxOp::BigIntMul { + lhs: Self::decode_some_wrap(&v.lhs, "lhs")?, + rhs: Self::decode_some_wrap(&v.rhs, "rhs")?, + output: Self::decode_some_wrap(&v.output, "output")?, + }), + Value::BigIntDiv(v) => Ok(brillig::BlackBoxOp::BigIntDiv { + lhs: Self::decode_some_wrap(&v.lhs, "lhs")?, + rhs: Self::decode_some_wrap(&v.rhs, "rhs")?, + output: Self::decode_some_wrap(&v.output, "output")?, + }), + Value::BigIntFromLeBytes(v) => Ok(brillig::BlackBoxOp::BigIntFromLeBytes { + inputs: Self::decode_some_wrap(&v.inputs, "inputs")?, + modulus: Self::decode_some_wrap(&v.modulus, "modulus")?, + output: Self::decode_some_wrap(&v.output, "output")?, + }), + Value::BigIntToLeBytes(v) => Ok(brillig::BlackBoxOp::BigIntToLeBytes { + input: Self::decode_some_wrap(&v.input, "input")?, + output: Self::decode_some_wrap(&v.output, "output")?, + }), + Value::Poseidon2Permutation(v) => Ok(brillig::BlackBoxOp::Poseidon2Permutation { + message: Self::decode_some_wrap(&v.message, "message")?, + output: Self::decode_some_wrap(&v.output, "output")?, + len: Self::decode_some_wrap(&v.len, "len")?, + }), + Value::Sha256Compression(v) => Ok(brillig::BlackBoxOp::Sha256Compression { + input: Self::decode_some_wrap(&v.input, "input")?, + hash_values: Self::decode_some_wrap(&v.hash_values, "hash_values")?, + output: Self::decode_some_wrap(&v.output, "output")?, + }), + Value::ToRadix(v) => Ok(brillig::BlackBoxOp::ToRadix { + input: Self::decode_some_wrap(&v.input, "input")?, + radix: Self::decode_some_wrap(&v.radix, "radix")?, + output_pointer: Self::decode_some_wrap(&v.output_pointer, "output_pointer")?, + num_limbs: Self::decode_some_wrap(&v.num_limbs, "num_limbs")?, + output_bits: Self::decode_some_wrap(&v.output_bits, "output_bits")?, + }), + }) + } +} diff --git a/noir/noir-repo/acvm-repo/acir/src/proto/convert/mod.rs b/noir/noir-repo/acvm-repo/acir/src/proto/convert/mod.rs new file mode 100644 index 000000000000..4d7b487da37a --- /dev/null +++ b/noir/noir-repo/acvm-repo/acir/src/proto/convert/mod.rs @@ -0,0 +1,46 @@ +use std::marker::PhantomData; + +use acir_field::AcirField; +use color_eyre::eyre::{self, Context}; +use noir_protobuf::ProtoCodec; + +use crate::circuit; +use crate::proto::program::Program; + +mod acir; +mod brillig; +mod native; +mod witness; + +pub(crate) struct ProtoSchema { + field: PhantomData, +} + +impl ProtoCodec, Program> for ProtoSchema { + fn encode(value: &circuit::Program) -> Program { + Program { + functions: Self::encode_vec(&value.functions), + unconstrained_functions: Self::encode_vec(&value.unconstrained_functions), + } + } + + fn decode(value: &Program) -> eyre::Result> { + Ok(circuit::Program { + functions: Self::decode_vec_wrap(&value.functions, "functions")?, + unconstrained_functions: Self::decode_vec_wrap( + &value.unconstrained_functions, + "unconstrained_functions", + )?, + }) + } +} + +impl ProtoCodec for ProtoSchema { + fn encode(value: &usize) -> u64 { + *value as u64 + } + + fn decode(value: &u64) -> eyre::Result { + (*value).try_into().wrap_err("failed to convert u64 to usize") + } +} diff --git a/noir/noir-repo/acvm-repo/acir/src/proto/convert/native.rs b/noir/noir-repo/acvm-repo/acir/src/proto/convert/native.rs new file mode 100644 index 000000000000..5d34ecf14f93 --- /dev/null +++ b/noir/noir-repo/acvm-repo/acir/src/proto/convert/native.rs @@ -0,0 +1,80 @@ +use acir_field::AcirField; +use color_eyre::eyre; +use noir_protobuf::{ProtoCodec, decode_vec_map_wrap}; + +use crate::{ + native_types, + proto::acir::native::{Expression, Field, Witness}, +}; + +use super::ProtoSchema; + +impl ProtoCodec for ProtoSchema { + fn encode(value: &F) -> Field { + Field { value: value.to_le_bytes() } + } + + fn decode(value: &Field) -> eyre::Result { + Ok(F::from_le_bytes_reduce(&value.value)) + } +} + +impl ProtoCodec for ProtoSchema { + fn encode(value: &native_types::Witness) -> Witness { + Witness { index: value.0 } + } + + fn decode(value: &Witness) -> eyre::Result { + Ok(native_types::Witness(value.index)) + } +} + +impl ProtoCodec, Expression> for ProtoSchema +where + F: AcirField, +{ + fn encode(value: &native_types::Expression) -> Expression { + use crate::proto::acir::native::expression::*; + Expression { + mul_terms: value + .mul_terms + .iter() + .map(|(q_m, wl, wr)| MulTerm { + q_m: Self::encode_some(q_m), + witness_left: Self::encode_some(wl), + witness_right: Self::encode_some(wr), + }) + .collect(), + linear_combinations: value + .linear_combinations + .iter() + .map(|(q_l, w)| LinearCombination { + q_l: Self::encode_some(q_l), + witness: Self::encode_some(w), + }) + .collect(), + q_c: Self::encode_some(&value.q_c), + } + } + + fn decode(value: &Expression) -> eyre::Result> { + Ok(native_types::Expression { + mul_terms: decode_vec_map_wrap(&value.mul_terms, "mul_terms", |mt| { + let q_m = Self::decode_some_wrap(&mt.q_m, "q_m")?; + let wl = Self::decode_some_wrap(&mt.witness_left, "witness_left")?; + let wr = Self::decode_some_wrap(&mt.witness_right, "witness_right")?; + Ok((q_m, wl, wr)) + })?, + linear_combinations: decode_vec_map_wrap( + &value.linear_combinations, + "linear_combinations", + |lc| { + let q_l = Self::decode_some_wrap(&lc.q_l, "q_l")?; + let w = Self::decode_some_wrap(&lc.witness, "witness")?; + Ok((q_l, w)) + }, + )?, + q_c: Self::decode_some_wrap(&value.q_c, "q_c")?, + }) + } +} diff --git a/noir/noir-repo/acvm-repo/acir/src/proto/convert/witness.rs b/noir/noir-repo/acvm-repo/acir/src/proto/convert/witness.rs new file mode 100644 index 000000000000..e926b9ea00b7 --- /dev/null +++ b/noir/noir-repo/acvm-repo/acir/src/proto/convert/witness.rs @@ -0,0 +1,64 @@ +use acir_field::AcirField; +use noir_protobuf::ProtoCodec; + +use crate::native_types; +use crate::proto::acir::witness::{WitnessMap, WitnessStack}; + +use super::ProtoSchema; + +impl ProtoCodec, WitnessMap> for ProtoSchema +where + F: AcirField, +{ + fn encode(value: &native_types::WitnessMap) -> WitnessMap { + use crate::proto::acir::witness::witness_map::*; + + let values = value + .clone() + .into_iter() + .map(|(w, f)| WitnessValue { + witness: Self::encode_some(&w), + field: Self::encode_some(&f), + }) + .collect(); + + WitnessMap { values } + } + + fn decode(value: &WitnessMap) -> color_eyre::eyre::Result> { + let mut wm = native_types::WitnessMap::default(); + for wv in &value.values { + wm.insert( + Self::decode_some_wrap(&wv.witness, "witness")?, + Self::decode_some_wrap(&wv.field, "field")?, + ); + } + Ok(wm) + } +} + +impl ProtoCodec, WitnessStack> for ProtoSchema +where + F: AcirField, +{ + fn encode(value: &native_types::WitnessStack) -> WitnessStack { + use crate::proto::acir::witness::witness_stack::*; + + let mut value = value.clone(); + let mut stack = Vec::new(); + while let Some(item) = value.pop() { + stack.push(StackItem { index: item.index, witness: Self::encode_some(&item.witness) }); + } + stack.reverse(); + + WitnessStack { stack } + } + + fn decode(value: &WitnessStack) -> color_eyre::eyre::Result> { + let mut ws = native_types::WitnessStack::default(); + for item in &value.stack { + ws.push(item.index, Self::decode_some_wrap(&item.witness, "witness")?); + } + Ok(ws) + } +} diff --git a/noir/noir-repo/acvm-repo/acir/src/proto/mod.rs b/noir/noir-repo/acvm-repo/acir/src/proto/mod.rs new file mode 100644 index 000000000000..ba5bbc5108c4 --- /dev/null +++ b/noir/noir-repo/acvm-repo/acir/src/proto/mod.rs @@ -0,0 +1,29 @@ +pub(crate) mod convert; + +pub(crate) mod acir { + + #[allow(unreachable_pub)] + pub(crate) mod native { + include!(concat!(env!("OUT_DIR"), "/acvm.acir.native.rs")); + } + + #[allow(unreachable_pub)] + pub(crate) mod witness { + include!(concat!(env!("OUT_DIR"), "/acvm.acir.witness.rs")); + } + + #[allow(unreachable_pub)] + pub(crate) mod circuit { + include!(concat!(env!("OUT_DIR"), "/acvm.acir.circuit.rs")); + } +} + +#[allow(unreachable_pub, clippy::enum_variant_names)] +pub(crate) mod brillig { + include!(concat!(env!("OUT_DIR"), "/acvm.brillig.rs")); +} + +#[allow(unreachable_pub)] +pub(crate) mod program { + include!(concat!(env!("OUT_DIR"), "/acvm.program.rs")); +} diff --git a/noir/noir-repo/acvm-repo/acir/src/proto/program.proto b/noir/noir-repo/acvm-repo/acir/src/proto/program.proto new file mode 100644 index 000000000000..d54d0aff5a97 --- /dev/null +++ b/noir/noir-repo/acvm-repo/acir/src/proto/program.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package acvm.program; + +import public "acir/circuit.proto"; +import public "brillig.proto"; + +// A program represents an entire circuit with ACIR and Brillig functions and +// potentially multiple endpoints. +message Program { + // ACIR circuits + repeated acvm.acir.circuit.Circuit functions = 1; + // Brillig functions + repeated acvm.brillig.BrilligBytecode unconstrained_functions = 2; +} diff --git a/noir/noir-repo/acvm-repo/acir/tests/test_program_serialization.rs b/noir/noir-repo/acvm-repo/acir/tests/test_program_serialization.rs index 4ff571106a1b..4f9a5fb76c55 100644 --- a/noir/noir-repo/acvm-repo/acir/tests/test_program_serialization.rs +++ b/noir/noir-repo/acvm-repo/acir/tests/test_program_serialization.rs @@ -56,7 +56,10 @@ fn addition_circuit() { 135, 223, 13, 27, 135, 121, 106, 119, 3, 58, 173, 124, 163, 140, 1, 0, 0, ]; - assert_eq!(bytes, expected_serialization) + assert_eq!(bytes, expected_serialization); + + let program_de = Program::deserialize_program(&bytes).unwrap(); + assert_eq!(program_de, program); } #[test] @@ -99,7 +102,10 @@ fn multi_scalar_mul_circuit() { 179, 90, 23, 212, 196, 199, 187, 192, 0, 0, 0, ]; - assert_eq!(bytes, expected_serialization) + assert_eq!(bytes, expected_serialization); + + let program_de = Program::deserialize_program(&bytes).unwrap(); + assert_eq!(program_de, program); } #[test] @@ -173,7 +179,10 @@ fn simple_brillig_foreign_call() { 191, 40, 237, 37, 127, 1, 190, 36, 121, 0, 128, 254, 118, 42, 127, 2, 0, 0, ]; - assert_eq!(bytes, expected_serialization) + assert_eq!(bytes, expected_serialization); + + let program_de = Program::deserialize_program(&bytes).unwrap(); + assert_eq!(program_de, program); } #[test] @@ -323,7 +332,10 @@ fn complex_brillig_foreign_call() { 250, 76, 4, 233, 188, 7, 0, 0, ]; - assert_eq!(bytes, expected_serialization) + assert_eq!(bytes, expected_serialization); + + let program_de = Program::deserialize_program(&bytes).unwrap(); + assert_eq!(program_de, program); } #[test] @@ -365,7 +377,10 @@ fn memory_op_circuit() { 0, 0, ]; - assert_eq!(bytes, expected_serialization) + assert_eq!(bytes, expected_serialization); + + let program_de = Program::deserialize_program(&bytes).unwrap(); + assert_eq!(program_de, program); } #[test] @@ -472,4 +487,7 @@ fn nested_acir_call_circuit() { 253, 11, 4, 0, 0, ]; assert_eq!(bytes, expected_serialization); + + let program_de = Program::deserialize_program(&bytes).unwrap(); + assert_eq!(program_de, program); } diff --git a/noir/noir-repo/acvm-repo/acir_field/src/generic_ark.rs b/noir/noir-repo/acvm-repo/acir_field/src/generic_ark.rs index 04761dd1ed0f..3f2b27b864cc 100644 --- a/noir/noir-repo/acvm-repo/acir_field/src/generic_ark.rs +++ b/noir/noir-repo/acvm-repo/acir_field/src/generic_ark.rs @@ -25,6 +25,7 @@ pub trait AcirField: + From + std::hash::Hash + Eq + + 'static { fn one() -> Self; fn zero() -> Self; @@ -85,3 +86,198 @@ pub trait AcirField: /// This method truncates fn fetch_nearest_bytes(&self, num_bits: usize) -> Vec; } + +/// Define a _newtype_ wrapper around an `AcirField` by implementing all the +/// boilerplate for forwarding the field operations. +/// +/// This allows the wrapper to implement traits such as `Arbitrary`, and then +/// be used by code that is generic in `F: AcirField`. +/// +/// # Example +/// ```ignore +/// field_wrapper!(TestField, FieldElement); +/// ``` +#[macro_export] +macro_rules! field_wrapper { + ($wrapper:ident, $field:ident) => { + #[derive( + Clone, + Debug, + PartialEq, + Eq, + Hash, + PartialOrd, + Copy, + Default, + serde::Serialize, + serde::Deserialize, + )] + struct $wrapper(pub $field); + + impl $crate::AcirField for $wrapper { + fn one() -> Self { + Self($field::one()) + } + + fn zero() -> Self { + Self($field::zero()) + } + + fn is_zero(&self) -> bool { + self.0.is_zero() + } + + fn is_one(&self) -> bool { + self.0.is_one() + } + + fn pow(&self, exponent: &Self) -> Self { + Self(self.0.pow(&exponent.0)) + } + + fn max_num_bits() -> u32 { + $field::max_num_bits() + } + + fn max_num_bytes() -> u32 { + $field::max_num_bytes() + } + + fn modulus() -> num_bigint::BigUint { + $field::modulus() + } + + fn num_bits(&self) -> u32 { + self.0.num_bits() + } + + fn to_u128(self) -> u128 { + self.0.to_u128() + } + + fn try_into_u128(self) -> Option { + self.0.try_into_u128() + } + + fn to_i128(self) -> i128 { + self.0.to_i128() + } + + fn try_to_u64(&self) -> Option { + self.0.try_to_u64() + } + + fn try_to_u32(&self) -> Option { + self.0.try_to_u32() + } + + fn inverse(&self) -> Self { + Self(self.0.inverse()) + } + + fn to_hex(self) -> String { + self.0.to_hex() + } + + fn from_hex(hex_str: &str) -> Option { + $field::from_hex(hex_str).map(Self) + } + + fn to_be_bytes(self) -> Vec { + self.0.to_be_bytes() + } + + fn from_be_bytes_reduce(bytes: &[u8]) -> Self { + Self($field::from_be_bytes_reduce(bytes)) + } + + fn from_le_bytes_reduce(bytes: &[u8]) -> Self { + Self($field::from_le_bytes_reduce(bytes)) + } + + fn to_le_bytes(self) -> Vec { + self.0.to_le_bytes() + } + + fn fetch_nearest_bytes(&self, num_bits: usize) -> Vec { + self.0.fetch_nearest_bytes(num_bits) + } + } + + impl From for $wrapper { + fn from(value: bool) -> Self { + Self($field::from(value)) + } + } + + impl From for $wrapper { + fn from(value: u128) -> Self { + Self($field::from(value)) + } + } + + impl From for $wrapper { + fn from(value: usize) -> Self { + Self($field::from(value)) + } + } + + impl std::ops::SubAssign<$wrapper> for $wrapper { + fn sub_assign(&mut self, rhs: $wrapper) { + self.0.sub_assign(rhs.0); + } + } + + impl std::ops::AddAssign<$wrapper> for $wrapper { + fn add_assign(&mut self, rhs: $wrapper) { + self.0.add_assign(rhs.0); + } + } + + impl std::ops::Add<$wrapper> for $wrapper { + type Output = Self; + + fn add(self, rhs: $wrapper) -> Self::Output { + Self(self.0.add(rhs.0)) + } + } + + impl std::ops::Sub<$wrapper> for $wrapper { + type Output = Self; + + fn sub(self, rhs: $wrapper) -> Self::Output { + Self(self.0.sub(rhs.0)) + } + } + + impl std::ops::Mul<$wrapper> for $wrapper { + type Output = Self; + + fn mul(self, rhs: $wrapper) -> Self::Output { + Self(self.0.mul(rhs.0)) + } + } + + impl std::ops::Div<$wrapper> for $wrapper { + type Output = Self; + + fn div(self, rhs: $wrapper) -> Self::Output { + Self(self.0.div(rhs.0)) + } + } + + impl std::ops::Neg for $wrapper { + type Output = Self; + + fn neg(self) -> Self::Output { + Self(self.0.neg()) + } + } + + impl std::fmt::Display for $wrapper { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } + } + }; +} diff --git a/noir/noir-repo/acvm-repo/acvm/src/compiler/optimizers/merge_expressions.rs b/noir/noir-repo/acvm-repo/acvm/src/compiler/optimizers/merge_expressions.rs index 2590c5f208aa..0e0d22edcd8c 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/compiler/optimizers/merge_expressions.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/compiler/optimizers/merge_expressions.rs @@ -12,7 +12,7 @@ use acir::{ use crate::compiler::CircuitSimulator; -pub(crate) struct MergeExpressionsOptimizer { +pub(crate) struct MergeExpressionsOptimizer { resolved_blocks: HashMap>, modified_gates: HashMap>, deleted_gates: BTreeSet, diff --git a/noir/noir-repo/acvm-repo/acvm/src/compiler/optimizers/redundant_range.rs b/noir/noir-repo/acvm-repo/acvm/src/compiler/optimizers/redundant_range.rs index 67dce75411e8..120a963192eb 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/compiler/optimizers/redundant_range.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/compiler/optimizers/redundant_range.rs @@ -27,7 +27,7 @@ use std::collections::{BTreeMap, HashSet}; /// /// This optimization pass will keep the 16-bit range constraint /// and remove the 32-bit range constraint opcode. -pub(crate) struct RangeOptimizer { +pub(crate) struct RangeOptimizer { /// Maps witnesses to their lowest known bit sizes. lists: BTreeMap, circuit: Circuit, diff --git a/noir/noir-repo/acvm-repo/acvm/src/compiler/optimizers/unused_memory.rs b/noir/noir-repo/acvm-repo/acvm/src/compiler/optimizers/unused_memory.rs index 8b7e52d66f2c..3a256aafe63b 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/compiler/optimizers/unused_memory.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/compiler/optimizers/unused_memory.rs @@ -1,13 +1,16 @@ -use acir::circuit::{Circuit, Opcode, brillig::BrilligInputs, opcodes::BlockId}; +use acir::{ + AcirField, + circuit::{Circuit, Opcode, brillig::BrilligInputs, opcodes::BlockId}, +}; use std::collections::HashSet; /// `UnusedMemoryOptimizer` will remove initializations of memory blocks which are unused. -pub(crate) struct UnusedMemoryOptimizer { +pub(crate) struct UnusedMemoryOptimizer { unused_memory_initializations: HashSet, circuit: Circuit, } -impl UnusedMemoryOptimizer { +impl UnusedMemoryOptimizer { /// Creates a new `UnusedMemoryOptimizer ` by collecting unused memory init /// opcodes from `Circuit`. pub(crate) fn new(circuit: Circuit) -> Self { diff --git a/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs b/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs index f5d56df17c1e..3c66b08eb4c7 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs @@ -178,7 +178,7 @@ pub struct ProfilingSample { pub brillig_function_id: Option, } -pub struct ACVM<'a, F, B: BlackBoxFunctionSolver> { +pub struct ACVM<'a, F: AcirField, B: BlackBoxFunctionSolver> { status: ACVMStatus, backend: &'a B, diff --git a/noir/noir-repo/acvm-repo/brillig/Cargo.toml b/noir/noir-repo/acvm-repo/brillig/Cargo.toml index 5a9720238aca..0c052d8f7003 100644 --- a/noir/noir-repo/acvm-repo/brillig/Cargo.toml +++ b/noir/noir-repo/acvm-repo/brillig/Cargo.toml @@ -18,7 +18,11 @@ workspace = true [dependencies] acir_field.workspace = true serde.workspace = true +proptest = { workspace = true, optional = true } +proptest-derive = { workspace = true, optional = true } [features] +default = [] bn254 = ["acir_field/bn254"] bls12_381 = ["acir_field/bls12_381"] +arb = ["proptest", "proptest-derive"] diff --git a/noir/noir-repo/acvm-repo/brillig/src/black_box.rs b/noir/noir-repo/acvm-repo/brillig/src/black_box.rs index eb496d0f826c..67ddf21589f8 100644 --- a/noir/noir-repo/acvm-repo/brillig/src/black_box.rs +++ b/noir/noir-repo/acvm-repo/brillig/src/black_box.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize}; /// These opcodes provide an equivalent of ACIR blackbox functions. /// They are implemented as native functions in the VM. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub enum BlackBoxOp { /// Encrypts a message using AES128. AES128Encrypt { diff --git a/noir/noir-repo/acvm-repo/brillig/src/opcodes.rs b/noir/noir-repo/acvm-repo/brillig/src/opcodes.rs index 1cb31ca3d0ae..bd083b914d4d 100644 --- a/noir/noir-repo/acvm-repo/brillig/src/opcodes.rs +++ b/noir/noir-repo/acvm-repo/brillig/src/opcodes.rs @@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize}; pub type Label = usize; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub enum MemoryAddress { Direct(usize), Relative(usize), @@ -82,6 +83,7 @@ impl HeapValueType { /// A fixed-sized array starting from a Brillig memory location. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub struct HeapArray { pub pointer: MemoryAddress, pub size: usize, @@ -95,12 +97,14 @@ impl Default for HeapArray { /// A memory-sized vector passed starting from a Brillig memory location and with a memory-held size #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub struct HeapVector { pub pointer: MemoryAddress, pub size: MemoryAddress, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub enum IntegerBitSize { U1, U8, @@ -153,6 +157,7 @@ impl std::fmt::Display for IntegerBitSize { } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub enum BitSize { Field, Integer(IntegerBitSize), @@ -182,6 +187,7 @@ impl BitSize { /// this needs to be encoded somehow when dealing with an external system. /// For simplicity, the extra type information is given right in the ForeignCall instructions. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub enum ValueOrArray { /// A single value passed to or from an external call /// It is an 'immediate' value - used without dereferencing. @@ -199,6 +205,7 @@ pub enum ValueOrArray { } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub enum BrilligOpcode { /// Takes the fields in addresses `lhs` and `rhs` /// Performs the specified binary operation @@ -315,6 +322,7 @@ pub enum BrilligOpcode { /// Binary fixed-length field expressions #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub enum BinaryFieldOp { Add, Sub, @@ -333,6 +341,7 @@ pub enum BinaryFieldOp { /// Binary fixed-length integer expressions #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub enum BinaryIntOp { Add, Sub, @@ -355,3 +364,31 @@ pub enum BinaryIntOp { /// (>>) Shift right Shr, } + +#[cfg(feature = "arb")] +mod tests { + use proptest::arbitrary::Arbitrary; + use proptest::prelude::*; + + use super::{BitSize, HeapValueType}; + + // Need to define recursive strategy for `HeapValueType` + impl Arbitrary for HeapValueType { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + let leaf = any::().prop_map(HeapValueType::Simple); + leaf.prop_recursive(2, 3, 2, |inner| { + prop_oneof![ + (prop::collection::vec(inner.clone(), 1..3), any::()).prop_map( + |(value_types, size)| { HeapValueType::Array { value_types, size } } + ), + (prop::collection::vec(inner.clone(), 1..3)) + .prop_map(|value_types| { HeapValueType::Vector { value_types } }), + ] + }) + .boxed() + } + } +} diff --git a/noir/noir-repo/acvm-repo/brillig_vm/src/lib.rs b/noir/noir-repo/acvm-repo/brillig_vm/src/lib.rs index 935c296d5ae8..a2c52eaa4293 100644 --- a/noir/noir-repo/acvm-repo/brillig_vm/src/lib.rs +++ b/noir/noir-repo/acvm-repo/brillig_vm/src/lib.rs @@ -1737,7 +1737,7 @@ mod tests { ) -> VM<'a, F, StubbedBlackBoxSolver> { let mut vm = VM::new(calldata, opcodes, solver, false); brillig_execute(&mut vm); - assert_eq!(vm.call_stack, vec![]); + assert!(vm.call_stack.is_empty()); vm } diff --git a/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs b/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs index 3bbe2181798e..25d79bf16a29 100644 --- a/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs +++ b/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs @@ -136,7 +136,7 @@ pub(super) fn abi_type_from_hir_type(context: &Context, typ: &Type) -> AbiType { | Type::Slice(_) | Type::Function(_, _, _, _) => unreachable!("{typ} cannot be used in the abi"), Type::FmtString(_, _) => unreachable!("format strings cannot be used in the abi"), - Type::MutableReference(_) => unreachable!("&mut cannot be used in the abi"), + Type::Reference(..) => unreachable!("references cannot be used in the abi"), } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs index a954ac3ab93c..fbfb9d06de28 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs @@ -24,6 +24,7 @@ use self::{ value::{Tree, Values}, }; +use super::ir::basic_block::BasicBlockId; use super::ir::dfg::GlobalsGraph; use super::ir::instruction::ErrorType; use super::ir::types::NumericType; @@ -370,7 +371,7 @@ impl FunctionContext<'_> { unary.location, )) } - UnaryOp::MutableReference => { + UnaryOp::Reference { mutable: _ } => { Ok(self.codegen_reference(&unary.rhs)?.map(|rhs| { match rhs { value::Value::Normal(value) => { @@ -767,7 +768,15 @@ impl FunctionContext<'_> { let tag = self.enum_tag(&variable); let tag_type = self.builder.type_of_value(tag).unwrap_numeric(); - let end_block = self.builder.insert_block(); + let make_end_block = |this: &mut Self| -> (BasicBlockId, Values) { + let block = this.builder.insert_block(); + let results = Self::map_type(&match_expr.typ, |typ| { + this.builder.add_block_parameter(block, typ).into() + }); + (block, results) + }; + + let (end_block, end_results) = make_end_block(self); // Optimization: if there is no default case we can jump directly to the last case // when finished with the previous case instead of using a jmpif with an unreachable @@ -778,6 +787,8 @@ impl FunctionContext<'_> { match_expr.cases.len() - 1 }; + let mut blocks_to_merge = Vec::with_capacity(last_case); + for i in 0..last_case { let case = &match_expr.cases[i]; let variant_tag = self.variant_index_value(&case.constructor, tag_type)?; @@ -790,28 +801,70 @@ impl FunctionContext<'_> { self.builder.switch_to_block(case_block); self.bind_case_arguments(variable.clone(), case); let results = self.codegen_expression(&case.branch)?.into_value_list(self); - self.builder.terminate_with_jmp(end_block, results); + + // Each branch will jump to a different end block for now. We have to merge them all + // later since SSA doesn't support more than two blocks jumping to the same end block. + let local_end_block = make_end_block(self); + self.builder.terminate_with_jmp(local_end_block.0, results); + blocks_to_merge.push(local_end_block); self.builder.switch_to_block(else_block); } + let (last_local_end_block, last_results) = make_end_block(self); + blocks_to_merge.push((last_local_end_block, last_results)); + if let Some(branch) = &match_expr.default_case { let results = self.codegen_expression(branch)?.into_value_list(self); - self.builder.terminate_with_jmp(end_block, results); + self.builder.terminate_with_jmp(last_local_end_block, results); } else { // If there is no default case, assume we saved the last case from the // last_case optimization above let case = match_expr.cases.last().unwrap(); self.bind_case_arguments(variable, case); let results = self.codegen_expression(&case.branch)?.into_value_list(self); - self.builder.terminate_with_jmp(end_block, results); + self.builder.terminate_with_jmp(last_local_end_block, results); + } + + // Merge blocks as last-in first-out: + // + // local_end_block0-----------------------------------------\ + // end block + // / + // local_end_block1---------------------\ / + // new merge block2-/ + // local_end_block2--\ / + // new merge block1-/ + // local_end_block3--/ + // + // This is necessary since SSA panics during flattening if we immediately + // try to jump directly to end block instead: https://github.com/noir-lang/noir/issues/7323. + // + // It'd also be more efficient to merge them tournament-bracket style but that + // also leads to panics during flattening for similar reasons. + while let Some((block, results)) = blocks_to_merge.pop() { + self.builder.switch_to_block(block); + + if let Some((block2, results2)) = blocks_to_merge.pop() { + // Merge two blocks in the queue together + let (new_merge, new_merge_results) = make_end_block(self); + blocks_to_merge.push((new_merge, new_merge_results)); + + let results = results.into_value_list(self); + self.builder.terminate_with_jmp(new_merge, results); + + self.builder.switch_to_block(block2); + let results2 = results2.into_value_list(self); + self.builder.terminate_with_jmp(new_merge, results2); + } else { + // Finally done, jump to the end + let results = results.into_value_list(self); + self.builder.terminate_with_jmp(end_block, results); + } } self.builder.switch_to_block(end_block); - let result = Self::map_type(&match_expr.typ, |typ| { - self.builder.add_block_parameter(end_block, typ).into() - }); - Ok(result) + Ok(end_results) } fn variant_index_value( diff --git a/noir/noir-repo/compiler/noirc_frontend/Cargo.toml b/noir/noir-repo/compiler/noirc_frontend/Cargo.toml index e5231565041c..ca2612f3d92b 100644 --- a/noir/noir-repo/compiler/noirc_frontend/Cargo.toml +++ b/noir/noir-repo/compiler/noirc_frontend/Cargo.toml @@ -36,7 +36,7 @@ fxhash.workspace = true [dev-dependencies] base64.workspace = true proptest.workspace = true -proptest-derive = "0.5.0" +proptest-derive.workspace = true [features] experimental_parser = [] diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs index 398e52676950..cc4cebafbbe9 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs @@ -383,7 +383,9 @@ impl BinaryOpKind { pub enum UnaryOp { Minus, Not, - MutableReference, + Reference { + mutable: bool, + }, /// If implicitly_added is true, this operation was implicitly added by the compiler for a /// field dereference. The compiler may undo some of these implicitly added dereferences if @@ -732,7 +734,8 @@ impl Display for UnaryOp { match self { UnaryOp::Minus => write!(f, "-"), UnaryOp::Not => write!(f, "!"), - UnaryOp::MutableReference => write!(f, "&mut"), + UnaryOp::Reference { mutable } if *mutable => write!(f, "&mut"), + UnaryOp::Reference { .. } => write!(f, "&"), UnaryOp::Dereference { .. } => write!(f, "*"), } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/mod.rs index 8e74ce8877e9..f39832e22b38 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/mod.rs @@ -43,8 +43,11 @@ use crate::{ use acvm::acir::AcirField; use iter_extended::vecmap; +use strum::IntoEnumIterator; +use strum_macros::EnumIter; + #[cfg_attr(test, derive(Arbitrary))] -#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, Ord, PartialOrd)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, Ord, PartialOrd, EnumIter)] pub enum IntegerBitSize { One, Eight, @@ -69,7 +72,7 @@ impl IntegerBitSize { impl IntegerBitSize { pub fn allowed_sizes() -> Vec { - vec![Self::One, Self::Eight, Self::ThirtyTwo, Self::SixtyFour] + IntegerBitSize::iter().collect() } } @@ -135,8 +138,8 @@ pub enum UnresolvedTypeData { /// A Trait as return type or parameter of function, including its generics TraitAsType(Path, GenericTypeArgs), - /// &mut T - MutableReference(Box), + /// &T and &mut T + Reference(Box, /*mutable*/ bool), // Note: Tuples have no visibility, instead each of their elements may have one. Tuple(Vec), @@ -311,7 +314,8 @@ impl std::fmt::Display for UnresolvedTypeData { other => write!(f, "fn[{other}]({args}) -> {ret}"), } } - MutableReference(element) => write!(f, "&mut {element}"), + Reference(element, false) => write!(f, "&{element}"), + Reference(element, true) => write!(f, "&mut {element}"), Quoted(quoted) => write!(f, "{}", quoted), Unit => write!(f, "()"), Error => write!(f, "error"), @@ -346,7 +350,7 @@ impl std::fmt::Display for UnresolvedTypeExpression { impl UnresolvedType { pub fn is_synthesized(&self) -> bool { match &self.typ { - UnresolvedTypeData::MutableReference(ty) => ty.is_synthesized(), + UnresolvedTypeData::Reference(ty, _) => ty.is_synthesized(), UnresolvedTypeData::Named(_, _, synthesized) => *synthesized, _ => false, } @@ -424,7 +428,7 @@ impl UnresolvedTypeData { path_is_wildcard || an_arg_is_unresolved } UnresolvedTypeData::TraitAsType(_path, args) => args.contains_unspecified(), - UnresolvedTypeData::MutableReference(typ) => typ.contains_unspecified(), + UnresolvedTypeData::Reference(typ, _) => typ.contains_unspecified(), UnresolvedTypeData::Tuple(args) => args.iter().any(|arg| arg.contains_unspecified()), UnresolvedTypeData::Function(args, ret, env, _unconstrained) => { let args_contains_unspecified = args.iter().any(|arg| arg.contains_unspecified()); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/visitor.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/visitor.rs index b2142f266551..8e78ca5ec544 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/visitor.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/visitor.rs @@ -393,7 +393,7 @@ pub trait Visitor { true } - fn visit_mutable_reference_type(&mut self, _: &UnresolvedType, _: Span) -> bool { + fn visit_reference_type(&mut self, _: &UnresolvedType, _mutable: bool, _: Span) -> bool { true } @@ -1382,8 +1382,8 @@ impl UnresolvedType { generic_type_args.accept(visitor); } } - UnresolvedTypeData::MutableReference(unresolved_type) => { - if visitor.visit_mutable_reference_type(unresolved_type, self.location.span) { + UnresolvedTypeData::Reference(unresolved_type, mutable) => { + if visitor.visit_reference_type(unresolved_type, *mutable, self.location.span) { unresolved_type.accept(visitor); } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/comptime.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/comptime.rs index 7091f7b261cd..7acedfcb933d 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/comptime.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/comptime.rs @@ -49,9 +49,10 @@ impl<'context> Elaborator<'context> { pub fn elaborate_item_from_comptime_in_function<'a, T>( &'a mut self, current_function: Option, + reason: Option, f: impl FnOnce(&mut Elaborator<'a>) -> T, ) -> T { - self.elaborate_item_from_comptime(f, |elaborator| { + self.elaborate_item_from_comptime(reason, f, |elaborator| { if let Some(function) = current_function { let meta = elaborator.interner.function_meta(&function); elaborator.current_item = Some(DependencyId::Function(function)); @@ -65,9 +66,10 @@ impl<'context> Elaborator<'context> { pub fn elaborate_item_from_comptime_in_module<'a, T>( &'a mut self, module: ModuleId, + reason: Option, f: impl FnOnce(&mut Elaborator<'a>) -> T, ) -> T { - self.elaborate_item_from_comptime(f, |elaborator| { + self.elaborate_item_from_comptime(reason, f, |elaborator| { elaborator.current_item = None; elaborator.crate_id = module.krate; elaborator.local_module = module.local_id; @@ -76,6 +78,7 @@ impl<'context> Elaborator<'context> { fn elaborate_item_from_comptime<'a, T>( &'a mut self, + reason: Option, f: impl FnOnce(&mut Elaborator<'a>) -> T, setup: impl FnOnce(&mut Elaborator<'a>), ) -> T { @@ -104,7 +107,14 @@ impl<'context> Elaborator<'context> { let result = f(&mut elaborator); elaborator.check_and_pop_function_context(); - self.errors.append(&mut elaborator.errors); + let mut errors = std::mem::take(&mut elaborator.errors); + if let Some(reason) = reason { + errors = vecmap(errors, |error| { + CompilationError::ComptimeError(reason.to_macro_error(error)) + }); + }; + + self.errors.extend(errors); result } @@ -342,14 +352,11 @@ impl<'context> Elaborator<'context> { generated_items: &mut CollectedItems, location: Location, ) { - let previous_errors = - self.push_elaborate_reason_and_take_errors(ElaborateReason::RunningAttribute, location); - - for item in items { - self.add_item(item, generated_items, location); - } - - self.pop_elaborate_reason(previous_errors); + self.with_elaborate_reason(ElaborateReason::RunningAttribute(location), |elaborator| { + for item in items { + elaborator.add_item(item, generated_items, location); + } + }); } pub(crate) fn add_item( @@ -526,7 +533,7 @@ impl<'context> Elaborator<'context> { for (struct_id, struct_def) in types { let attributes = &struct_def.struct_def.attributes; - let item = Value::StructDefinition(*struct_id); + let item = Value::TypeDefinition(*struct_id); let context = AttributeContext::new(struct_def.module_id); self.collect_comptime_attributes_on_item( attributes, @@ -558,14 +565,10 @@ impl<'context> Elaborator<'context> { }); if !generated_items.is_empty() { - let previous_errors = self.push_elaborate_reason_and_take_errors( - ElaborateReason::RunningAttribute, - location, - ); - - self.elaborate_items(generated_items); - - self.pop_elaborate_reason(previous_errors); + let reason = ElaborateReason::RunningAttribute(location); + self.with_elaborate_reason(reason, |elaborator| { + elaborator.elaborate_items(generated_items); + }); } } } @@ -652,24 +655,22 @@ impl<'context> Elaborator<'context> { } } - /// Pushes an ElaborateReason but also `std::mem::take`s the current errors and returns them. - pub(crate) fn push_elaborate_reason_and_take_errors( - &mut self, - reason: ElaborateReason, - location: Location, - ) -> Vec { - self.elaborate_reasons.push_back((reason, location)); - std::mem::take(&mut self.errors) - } + pub(crate) fn with_elaborate_reason(&mut self, reason: ElaborateReason, f: F) -> T + where + F: FnOnce(&mut Elaborator) -> T, + { + self.elaborate_reasons.push_back(reason); + let previous_errors = std::mem::take(&mut self.errors); + + let value = f(self); - /// Pops en ElaborateREason. Receives the errors that were returned by `push_elaborate_reason` - /// so they are restored, while also wrapping errors in the current Elaborator in a ComptimeError. - pub(crate) fn pop_elaborate_reason(&mut self, previous_errors: Vec) { let new_errors = std::mem::take(&mut self.errors); let new_errors = self.wrap_errors_in_macro_error(new_errors); self.errors = previous_errors; self.push_errors(new_errors); self.elaborate_reasons.pop_back(); + + value } fn wrap_errors_in_macro_error(&self, errors: Vec) -> Vec { @@ -677,8 +678,8 @@ impl<'context> Elaborator<'context> { } fn wrap_error_in_macro_error(&self, mut error: CompilationError) -> CompilationError { - for (reason, location) in self.elaborate_reasons.iter().rev() { - error = CompilationError::ComptimeError(reason.to_macro_error(error, *location)); + for reason in self.elaborate_reasons.iter().rev() { + error = CompilationError::ComptimeError(reason.to_macro_error(error)); } error } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/enums.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/enums.rs index c0bc86b51b0d..b5933f9dc376 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/enums.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/enums.rs @@ -938,7 +938,7 @@ impl<'elab, 'ctx> MatchCompiler<'elab, 'ctx> { | Type::NamedGeneric(_, _) | Type::CheckedCast { .. } | Type::Function(_, _, _, _) - | Type::MutableReference(_) + | Type::Reference(..) | Type::Forall(_, _) | Type::Constant(_, _) | Type::Quoted(_) diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs index 2ac779d3e772..e5df79522c6f 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -38,7 +38,7 @@ use crate::{ token::{FmtStrFragment, Tokens}, }; -use super::{Elaborator, LambdaContext, UnsafeBlockStatus}; +use super::{Elaborator, LambdaContext, UnsafeBlockStatus, UnstableFeature}; impl Elaborator<'_> { pub(crate) fn elaborate_expression(&mut self, expr: Expression) -> (ExprId, Type) { @@ -344,8 +344,12 @@ impl Elaborator<'_> { let operator = prefix.operator; - if let UnaryOp::MutableReference = operator { - self.check_can_mutate(rhs, rhs_location); + if let UnaryOp::Reference { mutable } = operator { + if mutable { + self.check_can_mutate(rhs, rhs_location); + } else { + self.use_unstable_feature(UnstableFeature::Ownership, location); + } } let expr = diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs index 23a9ec5246e8..1fde7e1ffbc0 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs @@ -199,25 +199,26 @@ pub struct Elaborator<'context> { /// Sometimes items are elaborated because a function attribute ran and generated items. /// The Elaborator keeps track of these reasons so that when an error is produced it will /// be wrapped in another error that will include this reason. - pub(crate) elaborate_reasons: im::Vector<(ElaborateReason, Location)>, + pub(crate) elaborate_reasons: im::Vector, } #[derive(Copy, Clone)] pub enum ElaborateReason { /// A function attribute generated an item that's being elaborated. - RunningAttribute, - /// Evaluating `Module::add_item` - AddingItemToModule, + RunningAttribute(Location), + /// Evaluating a comptime call like `Module::add_item` + EvaluatingComptimeCall(&'static str, Location), } impl ElaborateReason { - fn to_macro_error(self, error: CompilationError, location: Location) -> ComptimeError { + fn to_macro_error(self, error: CompilationError) -> ComptimeError { match self { - ElaborateReason::RunningAttribute => { + ElaborateReason::RunningAttribute(location) => { ComptimeError::ErrorRunningAttribute { error: Box::new(error), location } } - ElaborateReason::AddingItemToModule => { - ComptimeError::ErrorAddingItemToModule { error: Box::new(error), location } + ElaborateReason::EvaluatingComptimeCall(method_name, location) => { + let error = Box::new(error); + ComptimeError::ErrorEvaluatingComptimeCall { method_name, error, location } } } } @@ -251,7 +252,7 @@ impl<'context> Elaborator<'context> { crate_id: CrateId, interpreter_call_stack: im::Vector, options: ElaboratorOptions<'context>, - elaborate_reasons: im::Vector<(ElaborateReason, Location)>, + elaborate_reasons: im::Vector, ) -> Self { Self { scopes: ScopeForest::default(), @@ -588,7 +589,7 @@ impl<'context> Elaborator<'context> { for (mut constraint, expr_id, select_impl) in context.trait_constraints { let location = self.interner.expr_location(&expr_id); - if matches!(&constraint.typ, Type::MutableReference(_)) { + if matches!(&constraint.typ, Type::Reference(..)) { let (_, dereferenced_typ) = self.insert_auto_dereferences(expr_id, constraint.typ.clone()); constraint.typ = dereferenced_typ; @@ -1078,7 +1079,7 @@ impl<'context> Elaborator<'context> { self.mark_type_as_used(from); self.mark_type_as_used(to); } - Type::MutableReference(typ) => { + Type::Reference(typ, _) => { self.mark_type_as_used(typ); } Type::InfixExpr(left, _op, right, _) => { @@ -1461,8 +1462,8 @@ impl<'context> Elaborator<'context> { self.self_type = Some(self_type.clone()); let self_type_location = trait_impl.object_type.location; - if matches!(self_type, Type::MutableReference(_)) { - self.push_err(DefCollectorErrorKind::MutableReferenceInTraitImpl { + if matches!(self_type, Type::Reference(..)) { + self.push_err(DefCollectorErrorKind::ReferenceInTraitImpl { location: self_type_location, }); } @@ -1755,7 +1756,7 @@ impl<'context> Elaborator<'context> { ); self.check_type_is_not_more_private_then_item(name, visibility, env, location); } - Type::MutableReference(typ) | Type::Array(_, typ) | Type::Slice(typ) => { + Type::Reference(typ, _) | Type::Array(_, typ) | Type::Slice(typ) => { self.check_type_is_not_more_private_then_item(name, visibility, typ, location); } Type::InfixExpr(left, _op, right, _) => { @@ -1892,6 +1893,7 @@ impl<'context> Elaborator<'context> { datatype.borrow_mut().init_variants(); let module_id = ModuleId { krate: self.crate_id, local_id: typ.module_id }; + self.resolving_ids.insert(*type_id); for (i, variant) in typ.enum_def.variants.iter().enumerate() { let parameters = variant.item.parameters.as_ref(); @@ -1918,7 +1920,10 @@ impl<'context> Elaborator<'context> { let location = variant.item.name.location(); self.interner.add_definition_location(reference_id, location, Some(module_id)); } + + self.resolving_ids.remove(type_id); } + self.generics.clear(); } fn elaborate_global(&mut self, global: UnresolvedGlobal) { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/options.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/options.rs index 58bb5e73a618..0d72d2955ba6 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/options.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/options.rs @@ -3,14 +3,14 @@ use std::str::FromStr; #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum UnstableFeature { Enums, - ArrayOwnership, + Ownership, } impl std::fmt::Display for UnstableFeature { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { Self::Enums => write!(f, "enums"), - Self::ArrayOwnership => write!(f, "array-ownership"), + Self::Ownership => write!(f, "ownership"), } } } @@ -21,7 +21,7 @@ impl FromStr for UnstableFeature { fn from_str(s: &str) -> Result { match s { "enums" => Ok(Self::Enums), - "array-ownership" => Ok(Self::ArrayOwnership), + "ownership" => Ok(Self::Ownership), other => Err(format!("Unknown unstable feature '{other}'")), } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/statements.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/statements.rs index f00b2a87b1ed..b9810c7d2225 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/statements.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/statements.rs @@ -437,8 +437,8 @@ impl Elaborator<'_> { let (mut lvalue, mut lvalue_type, mut mutable) = self.elaborate_lvalue(*array); // Before we check that the lvalue is an array, try to dereference it as many times - // as needed to unwrap any &mut wrappers. - while let Type::MutableReference(element) = lvalue_type.follow_bindings() { + // as needed to unwrap any `&` or `&mut` wrappers. + while let Type::Reference(element, _) = lvalue_type.follow_bindings() { let element_type = element.as_ref().clone(); lvalue = HirLValue::Dereference { lvalue: Box::new(lvalue), element_type, location }; @@ -482,7 +482,9 @@ impl Elaborator<'_> { let lvalue = Box::new(lvalue); let element_type = Type::type_variable(self.interner.next_type_variable_id()); - let expected_type = Type::MutableReference(Box::new(element_type.clone())); + + // Always expect a mutable reference here since we're storing to it + let expected_type = Type::Reference(Box::new(element_type.clone()), true); self.unify(&reference_type, &expected_type, || TypeCheckError::TypeMismatch { expected_typ: expected_type.to_string(), @@ -539,9 +541,8 @@ impl Elaborator<'_> { } } } - // If the lhs is a mutable reference we automatically transform - // lhs.field into (*lhs).field - Type::MutableReference(element) => { + // If the lhs is a reference we automatically transform `lhs.field` into `(*lhs).field` + Type::Reference(element, mutable) => { if let Some(mut dereference_lhs) = dereference_lhs { dereference_lhs(self, lhs_type.clone(), element.as_ref().clone()); return self.check_field_access( @@ -553,7 +554,7 @@ impl Elaborator<'_> { } else { let (element, index) = self.check_field_access(element, field_name, location, dereference_lhs)?; - return Some((Type::MutableReference(Box::new(element)), index)); + return Some((Type::Reference(Box::new(element), *mutable), index)); } } _ => (), diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs index 6e2f92a1a098..6f6423017a43 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs @@ -12,6 +12,7 @@ use crate::{ Signedness, UnaryOp, UnresolvedGeneric, UnresolvedGenerics, UnresolvedType, UnresolvedTypeData, UnresolvedTypeExpression, WILDCARD_TYPE, }, + elaborator::UnstableFeature, hir::{ def_collector::dc_crate::CompilationError, def_map::{ModuleDefId, fully_qualified_module_path}, @@ -141,8 +142,11 @@ impl Elaborator<'_> { } } } - MutableReference(element) => { - Type::MutableReference(Box::new(self.resolve_type_inner(*element, kind))) + Reference(element, mutable) => { + if !mutable { + self.use_unstable_feature(UnstableFeature::Ownership, location); + } + Type::Reference(Box::new(self.resolve_type_inner(*element, kind)), mutable) } Parenthesized(typ) => self.resolve_type_inner(*typ, kind), Resolved(id) => self.interner.get_quoted_type(id).clone(), @@ -845,7 +849,7 @@ impl Elaborator<'_> { /// 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) { - if let Type::MutableReference(element) = typ.follow_bindings() { + if let Type::Reference(element, _mut) = typ.follow_bindings() { let location = self.interner.id_location(object); let object = self.interner.push_expr(HirExpression::Prefix(HirPrefixExpression { @@ -1039,6 +1043,7 @@ impl Elaborator<'_> { location: Location, ) -> Result<(Type, bool), TypeCheckError> { use Type::*; + match (lhs_type, rhs_type) { // Avoid reporting errors multiple times (Error, _) | (_, Error) => Ok((Bool, false)), @@ -1249,6 +1254,7 @@ impl Elaborator<'_> { location: Location, ) -> Result<(Type, bool), TypeCheckError> { use Type::*; + match op { crate::ast::UnaryOp::Minus | crate::ast::UnaryOp::Not => { match rhs_type { @@ -1302,17 +1308,26 @@ impl Elaborator<'_> { _ => Ok((rhs_type.clone(), true)), } } - crate::ast::UnaryOp::MutableReference => { - Ok((Type::MutableReference(Box::new(rhs_type.follow_bindings())), false)) + crate::ast::UnaryOp::Reference { mutable } => { + let typ = Type::Reference(Box::new(rhs_type.follow_bindings()), *mutable); + Ok((typ, 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_location: location, - }); + let make_expected = + |mutable| Type::Reference(Box::new(element_type.clone()), mutable); + + let immutable = make_expected(false); + let mutable = make_expected(true); + + // Both `&mut T` and `&T` should coerce to an expected `&T`. + if !rhs_type.try_reference_coercion(&immutable) { + self.unify(rhs_type, &mutable, || TypeCheckError::TypeMismatch { + expr_typ: rhs_type.to_string(), + expected_typ: mutable.to_string(), + expr_location: location, + }); + } Ok((element_type, false)) } } @@ -1435,9 +1450,9 @@ impl Elaborator<'_> { Type::NamedGeneric(_, _) => { self.lookup_method_in_trait_constraints(object_type, method_name, location) } - // Mutable references to another type should resolve to methods of their element type. + // References to another type should resolve to methods of their element type. // This may be a data type or a primitive type. - Type::MutableReference(element) => { + Type::Reference(element, _mutable) => { self.lookup_method(&element, method_name, location, check_self_param) } @@ -1834,12 +1849,12 @@ impl Elaborator<'_> { if let Some(expected_object_type) = expected_object_type { let actual_type = object_type.follow_bindings(); - if matches!(expected_object_type.follow_bindings(), Type::MutableReference(_)) { - if !matches!(actual_type, Type::MutableReference(_)) { + if let Type::Reference(_, mutable) = expected_object_type.follow_bindings() { + if !matches!(actual_type, Type::Reference(..)) { let location = self.interner.id_location(*object); self.check_can_mutate(*object, location); - let new_type = Type::MutableReference(Box::new(actual_type)); + let new_type = Type::Reference(Box::new(actual_type), mutable); *object_type = new_type.clone(); // First try to remove a dereference operator that may have been implicitly @@ -1850,7 +1865,7 @@ impl Elaborator<'_> { *object = new_object.unwrap_or_else(|| { let new_object = self.interner.push_expr(HirExpression::Prefix(HirPrefixExpression { - operator: UnaryOp::MutableReference, + operator: UnaryOp::Reference { mutable }, rhs: *object, trait_method_id: None, })); @@ -1861,7 +1876,7 @@ impl Elaborator<'_> { } // Otherwise if the object type is a mutable reference and the method is not, insert as // many dereferences as needed. - } else if matches!(actual_type, Type::MutableReference(_)) { + } else if matches!(actual_type, Type::Reference(..)) { let (new_object, new_type) = self.insert_auto_dereferences(*object, actual_type); *object_type = new_type; *object = new_object; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/display.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/display.rs index c0283d9701b4..bc2e0574086d 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/display.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/display.rs @@ -86,6 +86,9 @@ pub(super) fn tokens_to_string(tokens: &[LocatedToken], interner: &NodeInterner) struct TokenPrettyPrinter<'interner> { interner: &'interner NodeInterner, indent: usize, + /// Determines whether the last outputted byte was alphanumeric. + /// This is used to add a space after the last token and before another token + /// that starts with an alphanumeric byte. last_was_alphanumeric: bool, last_was_right_brace: bool, last_was_semicolon: bool, @@ -169,30 +172,33 @@ impl<'interner> TokenPrettyPrinter<'interner> { } match token { - Token::QuotedType(id) => write!(f, "{}", self.interner.get_quoted_type(*id)), + Token::QuotedType(id) => { + let value = Value::Type(self.interner.get_quoted_type(*id).clone()); + self.print_value(&value, last_was_alphanumeric, f) + } Token::InternedExpr(id) => { let value = Value::expression(ExpressionKind::Interned(*id)); - self.print_value(&value, f) + self.print_value(&value, last_was_alphanumeric, f) } Token::InternedStatement(id) => { let value = Value::statement(StatementKind::Interned(*id)); - self.print_value(&value, f) + self.print_value(&value, last_was_alphanumeric, f) } Token::InternedLValue(id) => { let value = Value::lvalue(LValue::Interned(*id, Location::dummy())); - self.print_value(&value, f) + self.print_value(&value, last_was_alphanumeric, f) } Token::InternedUnresolvedTypeData(id) => { let value = Value::UnresolvedType(UnresolvedTypeData::Interned(*id)); - self.print_value(&value, f) + self.print_value(&value, last_was_alphanumeric, f) } Token::InternedPattern(id) => { let value = Value::pattern(Pattern::Interned(*id, Location::dummy())); - self.print_value(&value, f) + self.print_value(&value, last_was_alphanumeric, f) } Token::UnquoteMarker(id) => { let value = Value::TypedExpr(TypedExpr::ExprId(*id)); - self.print_value(&value, f) + self.print_value(&value, last_was_alphanumeric, f) } Token::Keyword(..) | Token::Ident(..) @@ -254,6 +260,7 @@ impl<'interner> TokenPrettyPrinter<'interner> { | Token::Slash | Token::Percent | Token::Ampersand + | Token::SliceStart | Token::ShiftLeft | Token::ShiftRight => { self.last_was_op = true; @@ -291,8 +298,21 @@ impl<'interner> TokenPrettyPrinter<'interner> { } } - fn print_value(&mut self, value: &Value, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn print_value( + &mut self, + value: &Value, + last_was_alphanumeric: bool, + f: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { let string = value.display(self.interner).to_string(); + if string.is_empty() { + return Ok(()); + } + + if last_was_alphanumeric && string.bytes().next().unwrap().is_ascii_alphanumeric() { + write!(f, " ")?; + } + for (index, line) in string.lines().enumerate() { if index > 0 { writeln!(f)?; @@ -301,7 +321,7 @@ impl<'interner> TokenPrettyPrinter<'interner> { line.fmt(f)?; } - self.last_was_alphanumeric = string.bytes().all(|byte| byte.is_ascii_alphanumeric()); + self.last_was_alphanumeric = string.bytes().last().unwrap().is_ascii_alphanumeric(); self.last_was_right_brace = string.ends_with('}'); self.last_was_semicolon = string.ends_with(';'); @@ -381,7 +401,13 @@ impl Display for ValuePrinter<'_, '_> { other => write!(f, "{other}(args)"), } } - Value::Pointer(value, _) => write!(f, "&mut {}", value.borrow().display(self.interner)), + Value::Pointer(value, _, mutable) => { + if *mutable { + write!(f, "&mut {}", value.borrow().display(self.interner)) + } else { + write!(f, "&{}", value.borrow().display(self.interner)) + } + } Value::Array(values, _) => { let values = vecmap(values, |value| value.display(self.interner).to_string()); write!(f, "[{}]", values.join(", ")) @@ -391,7 +417,7 @@ impl Display for ValuePrinter<'_, '_> { write!(f, "&[{}]", values.join(", ")) } Value::Quoted(tokens) => display_quoted(tokens, 0, self.interner, f), - Value::StructDefinition(id) => { + Value::TypeDefinition(id) => { let def = self.interner.get_type(*id); let def = def.borrow(); write!(f, "{}", def.name) @@ -856,8 +882,9 @@ fn remove_interned_in_unresolved_type_data( remove_interned_in_generic_type_args(interner, generic_type_args), ) } - UnresolvedTypeData::MutableReference(typ) => UnresolvedTypeData::MutableReference( + UnresolvedTypeData::Reference(typ, mutable) => UnresolvedTypeData::Reference( Box::new(remove_interned_in_unresolved_type(interner, *typ)), + mutable, ), UnresolvedTypeData::Tuple(types) => UnresolvedTypeData::Tuple(vecmap(types, |typ| { remove_interned_in_unresolved_type(interner, typ) diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/errors.rs index 27440069c023..52b50d54566d 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/errors.rs @@ -703,15 +703,22 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { /// comptime call or macro "something" that eventually led to that error. #[derive(Debug, Clone, PartialEq, Eq)] pub enum ComptimeError { - ErrorRunningAttribute { error: Box, location: Location }, - ErrorAddingItemToModule { error: Box, location: Location }, + ErrorRunningAttribute { + error: Box, + location: Location, + }, + ErrorEvaluatingComptimeCall { + method_name: &'static str, + error: Box, + location: Location, + }, } impl ComptimeError { pub fn location(&self) -> Location { match self { ComptimeError::ErrorRunningAttribute { location, .. } - | ComptimeError::ErrorAddingItemToModule { location, .. } => *location, + | ComptimeError::ErrorEvaluatingComptimeCall { location, .. } => *location, } } } @@ -724,9 +731,9 @@ impl<'a> From<&'a ComptimeError> for CustomDiagnostic { diagnostic.add_secondary("While running this function attribute".into(), *location); diagnostic } - ComptimeError::ErrorAddingItemToModule { error, location } => { + ComptimeError::ErrorEvaluatingComptimeCall { method_name, error, location } => { let mut diagnostic = CustomDiagnostic::from(&**error); - diagnostic.add_secondary("While interpreting `Module::add_item`".into(), *location); + diagnostic.add_secondary(format!("While evaluating `{method_name}`"), *location); diagnostic } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs index 4a4835c8bf73..cb030e1a80d7 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs @@ -465,9 +465,9 @@ impl Type { let env = Box::new(env.to_display_ast()); UnresolvedTypeData::Function(args, ret, env, *unconstrained) } - Type::MutableReference(element) => { + Type::Reference(element, mutable) => { let element = Box::new(element.to_display_ast()); - UnresolvedTypeData::MutableReference(element) + UnresolvedTypeData::Reference(element, *mutable) } // 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. diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter.rs index 5c87e70949a1..3e83c5f3fb9d 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter.rs @@ -10,7 +10,7 @@ use rustc_hash::FxHashMap as HashMap; use crate::TypeVariable; use crate::ast::{BinaryOpKind, FunctionKind, IntegerBitSize, Signedness, UnaryOp}; -use crate::elaborator::Elaborator; +use crate::elaborator::{ElaborateReason, Elaborator}; use crate::graph::CrateId; use crate::hir::def_map::ModuleId; use crate::hir::type_check::TypeCheckError; @@ -191,7 +191,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { Some(body) => Ok(body), None => { if matches!(&meta.function_body, FunctionBody::Unresolved(..)) { - self.elaborate_in_function(None, |elaborator| { + self.elaborate_in_function(None, None, |elaborator| { elaborator.elaborate_function(function); }); @@ -207,10 +207,11 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { fn elaborate_in_function( &mut self, function: Option, + reason: Option, f: impl FnOnce(&mut Elaborator) -> T, ) -> T { self.unbind_generics_from_previous_function(); - let result = self.elaborator.elaborate_item_from_comptime_in_function(function, f); + let result = self.elaborator.elaborate_item_from_comptime_in_function(function, reason, f); self.rebind_generics_from_previous_function(); result } @@ -218,10 +219,11 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { fn elaborate_in_module( &mut self, module: ModuleId, + reason: Option, f: impl FnOnce(&mut Elaborator) -> T, ) -> T { self.unbind_generics_from_previous_function(); - let result = self.elaborator.elaborate_item_from_comptime_in_module(module, f); + let result = self.elaborator.elaborate_item_from_comptime_in_module(module, reason, f); self.rebind_generics_from_previous_function(); result } @@ -393,7 +395,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { } HirPattern::Mutable(pattern, _) => { // Create a mutable reference to store to - let argument = Value::Pointer(Shared::new(argument), true); + let argument = Value::Pointer(Shared::new(argument), true, true); self.define_pattern(pattern, typ, argument, location) } HirPattern::Tuple(pattern_fields, _) => { @@ -471,7 +473,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { for scope in self.elaborator.interner.comptime_scopes.iter_mut().rev() { if let Entry::Occupied(mut entry) = scope.entry(id) { match entry.get() { - Value::Pointer(reference, true) => { + Value::Pointer(reference, true, _) => { *reference.borrow_mut() = argument; } _ => { @@ -507,7 +509,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { /// This will automatically dereference a mutable variable if used. pub fn evaluate(&mut self, id: ExprId) -> IResult { match self.evaluate_no_dereference(id)? { - Value::Pointer(elem, true) => Ok(elem.borrow().clone()), + Value::Pointer(elem, true, _) => Ok(elem.borrow().clone()), other => Ok(other), } } @@ -848,7 +850,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { fn evaluate_prefix(&mut self, prefix: HirPrefixExpression, id: ExprId) -> IResult { let rhs = match prefix.operator { - UnaryOp::MutableReference => self.evaluate_no_dereference(prefix.rhs)?, + UnaryOp::Reference { .. } => self.evaluate_no_dereference(prefix.rhs)?, _ => self.evaluate(prefix.rhs)?, }; @@ -899,17 +901,17 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { Err(InterpreterError::InvalidValueForUnary { typ, location, operator: "not" }) } }, - UnaryOp::MutableReference => { + UnaryOp::Reference { mutable } => { // If this is a mutable variable (auto_deref = true), turn this into an explicit // mutable reference just by switching the value of `auto_deref`. Otherwise, wrap // the value in a fresh reference. match rhs { - Value::Pointer(elem, true) => Ok(Value::Pointer(elem, false)), - other => Ok(Value::Pointer(Shared::new(other), false)), + Value::Pointer(elem, true, _) => Ok(Value::Pointer(elem, false, mutable)), + other => Ok(Value::Pointer(Shared::new(other), false, mutable)), } } UnaryOp::Dereference { implicitly_added: _ } => match rhs { - Value::Pointer(element, _) => Ok(element.borrow().clone()), + Value::Pointer(element, _, _) => Ok(element.borrow().clone()), value => { let location = self.elaborator.interner.expr_location(&id); let typ = value.get_type().into_owned(); @@ -1325,9 +1327,10 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { let mut result = self.call_function(function_id, arguments, bindings, location)?; if call.is_macro_call { let expr = result.into_expression(self.elaborator, location)?; - let expr = self.elaborate_in_function(self.current_function, |elaborator| { - elaborator.elaborate_expression(expr).0 - }); + let expr = + self.elaborate_in_function(self.current_function, None, |elaborator| { + elaborator.elaborate_expression(expr).0 + }); result = self.evaluate(expr)?; // Macro calls are typed as type variables during type checking. @@ -1613,7 +1616,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { HirLValue::Ident(ident, typ) => self.mutate(ident.id, rhs, ident.location), HirLValue::Dereference { lvalue, element_type: _, location } => { match self.evaluate_lvalue(&lvalue)? { - Value::Pointer(value, _) => { + Value::Pointer(value, _, _) => { *value.borrow_mut() = rhs; Ok(()) } @@ -1669,12 +1672,12 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { fn evaluate_lvalue(&mut self, lvalue: &HirLValue) -> IResult { match lvalue { HirLValue::Ident(ident, _) => match self.lookup(ident)? { - Value::Pointer(elem, true) => Ok(elem.borrow().clone()), + Value::Pointer(elem, true, _) => Ok(elem.borrow().clone()), other => Ok(other), }, HirLValue::Dereference { lvalue, element_type, location } => { match self.evaluate_lvalue(lvalue)? { - Value::Pointer(value, _) => Ok(value.borrow().clone()), + Value::Pointer(value, _, _) => Ok(value.borrow().clone()), value => { let typ = value.get_type().into_owned(); Err(InterpreterError::NonPointerDereferenced { typ, location: *location }) diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index 34a5535f63c2..b9ad6a2c9f18 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -5,8 +5,8 @@ use builtin_helpers::{ block_expression_to_value, byte_array_type, check_argument_count, check_function_not_yet_resolved, check_one_argument, check_three_arguments, check_two_arguments, get_bool, get_expr, get_field, get_format_string, get_function_def, - get_module, get_quoted, get_slice, get_struct, get_trait_constraint, get_trait_def, - get_trait_impl, get_tuple, get_type, get_typed_expr, get_u32, get_unresolved_type, + get_module, get_quoted, get_slice, get_trait_constraint, get_trait_def, get_trait_impl, + get_tuple, get_type, get_type_id, get_typed_expr, get_u32, get_unresolved_type, has_named_attribute, hir_pattern_to_tokens, mutate_func_meta_type, parse, quote_ident, replace_func_meta_parameters, replace_func_meta_return_type, }; @@ -28,18 +28,21 @@ use crate::{ hir::{ comptime::{ InterpreterError, Value, + display::tokens_to_string, errors::IResult, value::{ExprValue, TypedExpr}, }, def_collector::dc_crate::CollectedItems, def_map::ModuleDefId, + type_check::generics::TraitGenerics, }, hir_def::{ self, - expr::{HirExpression, HirIdent, HirLiteral}, + expr::{HirExpression, HirIdent, HirLiteral, ImplKind, TraitMethod}, function::FunctionBody, + traits::{ResolvedTraitBound, TraitConstraint}, }, - node_interner::{DefinitionKind, NodeInterner, TraitImplKind}, + node_interner::{DefinitionKind, NodeInterner, TraitImplKind, TraitMethodId}, parser::{Parser, StatementOrExpressionOrLValue}, token::{Attribute, LocatedToken, Token}, }; @@ -166,7 +169,7 @@ impl Interpreter<'_, '_> { "quoted_as_module" => quoted_as_module(self, arguments, return_type, location), "quoted_as_trait_constraint" => quoted_as_trait_constraint(self, arguments, location), "quoted_as_type" => quoted_as_type(self, arguments, location), - "quoted_eq" => quoted_eq(arguments, location), + "quoted_eq" => quoted_eq(self.elaborator.interner, arguments, location), "quoted_hash" => quoted_hash(arguments, location), "quoted_tokens" => quoted_tokens(arguments, location), "slice_insert" => slice_insert(interner, arguments, location), @@ -179,24 +182,6 @@ impl Interpreter<'_, '_> { "static_assert" => static_assert(interner, arguments, location, call_stack), "str_as_bytes" => str_as_bytes(interner, arguments, location), "str_as_ctstring" => str_as_ctstring(interner, arguments, location), - "struct_def_add_attribute" => struct_def_add_attribute(interner, arguments, location), - "struct_def_add_generic" => struct_def_add_generic(interner, arguments, location), - "struct_def_as_type" => struct_def_as_type(interner, arguments, location), - "struct_def_eq" => struct_def_eq(arguments, location), - "struct_def_fields" => struct_def_fields(interner, arguments, location, call_stack), - "struct_def_fields_as_written" => { - struct_def_fields_as_written(interner, arguments, location) - } - "struct_def_generics" => { - struct_def_generics(interner, arguments, return_type, location) - } - "struct_def_has_named_attribute" => { - struct_def_has_named_attribute(interner, arguments, location) - } - "struct_def_hash" => struct_def_hash(arguments, location), - "struct_def_module" => struct_def_module(self, arguments, location), - "struct_def_name" => struct_def_name(interner, arguments, location), - "struct_def_set_fields" => struct_def_set_fields(interner, arguments, location), "to_be_radix" => to_be_radix(arguments, return_type, location), "to_le_radix" => to_le_radix(arguments, return_type, location), "to_be_bits" => to_be_bits(arguments, return_type, location), @@ -220,8 +205,24 @@ impl Interpreter<'_, '_> { } "type_as_slice" => type_as_slice(arguments, return_type, location), "type_as_str" => type_as_str(arguments, return_type, location), - "type_as_struct" => type_as_struct(arguments, return_type, location), + "type_as_data_type" => type_as_data_type(arguments, return_type, location), "type_as_tuple" => type_as_tuple(arguments, return_type, location), + "type_def_add_attribute" => type_def_add_attribute(interner, arguments, location), + "type_def_add_generic" => type_def_add_generic(interner, arguments, location), + "type_def_as_type" => type_def_as_type(interner, arguments, location), + "type_def_eq" => type_def_eq(arguments, location), + "type_def_fields" => type_def_fields(interner, arguments, location, call_stack), + "type_def_fields_as_written" => { + type_def_fields_as_written(interner, arguments, location) + } + "type_def_generics" => type_def_generics(interner, arguments, return_type, location), + "type_def_has_named_attribute" => { + type_def_has_named_attribute(interner, arguments, location) + } + "type_def_hash" => type_def_hash(arguments, location), + "type_def_module" => type_def_module(self, arguments, location), + "type_def_name" => type_def_name(interner, arguments, location), + "type_def_set_fields" => type_def_set_fields(interner, arguments, location), "type_eq" => type_eq(arguments, location), "type_get_trait_impl" => { type_get_trait_impl(interner, arguments, return_type, location) @@ -376,7 +377,7 @@ fn str_as_ctstring( } // fn add_attribute(self, attribute: str) -fn struct_def_add_attribute( +fn type_def_add_attribute( interner: &mut NodeInterner, arguments: Vec<(Value, Location)>, location: Location, @@ -393,8 +394,8 @@ fn struct_def_add_attribute( }); }; - let struct_id = get_struct(self_argument)?; - interner.update_type_attributes(struct_id, |attributes| { + let type_id = get_type_id(self_argument)?; + interner.update_type_attributes(type_id, |attributes| { attributes.push(attribute); }); @@ -402,7 +403,7 @@ fn struct_def_add_attribute( } // fn add_generic(self, generic_name: str) -fn struct_def_add_generic( +fn type_def_add_generic( interner: &NodeInterner, arguments: Vec<(Value, Location)>, location: Location, @@ -426,7 +427,7 @@ fn struct_def_add_generic( }); }; - let struct_id = get_struct(self_argument)?; + let struct_id = get_type_id(self_argument)?; let the_struct = interner.get_type(struct_id); let mut the_struct = the_struct.borrow_mut(); let name = Rc::new(generic_name); @@ -452,35 +453,35 @@ fn struct_def_add_generic( } /// fn as_type(self) -> Type -fn struct_def_as_type( +fn type_def_as_type( interner: &NodeInterner, arguments: Vec<(Value, Location)>, location: Location, ) -> IResult { let argument = check_one_argument(arguments, location)?; - let struct_id = get_struct(argument)?; - let struct_def_rc = interner.get_type(struct_id); - let struct_def = struct_def_rc.borrow(); + let struct_id = get_type_id(argument)?; + let type_def_rc = interner.get_type(struct_id); + let type_def = type_def_rc.borrow(); - let generics = vecmap(&struct_def.generics, |generic| { + let generics = vecmap(&type_def.generics, |generic| { Type::NamedGeneric(generic.type_var.clone(), generic.name.clone()) }); - drop(struct_def); - Ok(Value::Type(Type::DataType(struct_def_rc, generics))) + drop(type_def); + Ok(Value::Type(Type::DataType(type_def_rc, generics))) } /// fn generics(self) -> [(Type, Option)] -fn struct_def_generics( +fn type_def_generics( interner: &NodeInterner, arguments: Vec<(Value, Location)>, return_type: Type, location: Location, ) -> IResult { let argument = check_one_argument(arguments, location)?; - let struct_id = get_struct(argument)?; - let struct_def = interner.get_type(struct_id); - let struct_def = struct_def.borrow(); + let type_id = get_type_id(argument)?; + let type_def = interner.get_type(type_id); + let type_def = type_def.borrow(); let expected = Type::Slice(Box::new(Type::Tuple(vec![ Type::Quoted(QuotedType::Type), @@ -499,7 +500,7 @@ fn struct_def_generics( _ => return Err(InterpreterError::TypeMismatch { expected, actual, location }), }; - let generics = struct_def + let generics = type_def .generics .iter() .map(|generic| { @@ -516,39 +517,39 @@ fn struct_def_generics( Ok(Value::Slice(generics, slice_item_type)) } -fn struct_def_hash(arguments: Vec<(Value, Location)>, location: Location) -> IResult { - hash_item(arguments, location, get_struct) +fn type_def_hash(arguments: Vec<(Value, Location)>, location: Location) -> IResult { + hash_item(arguments, location, get_type_id) } -fn struct_def_eq(arguments: Vec<(Value, Location)>, location: Location) -> IResult { - eq_item(arguments, location, get_struct) +fn type_def_eq(arguments: Vec<(Value, Location)>, location: Location) -> IResult { + eq_item(arguments, location, get_type_id) } // fn has_named_attribute(self, name: str) -> bool {} -fn struct_def_has_named_attribute( +fn type_def_has_named_attribute( interner: &NodeInterner, arguments: Vec<(Value, Location)>, location: Location, ) -> IResult { let (self_argument, name) = check_two_arguments(arguments, location)?; - let struct_id = get_struct(self_argument)?; + let type_id = get_type_id(self_argument)?; let name = get_str(interner, name)?; - Ok(Value::Bool(has_named_attribute(&name, interner.type_attributes(&struct_id)))) + Ok(Value::Bool(has_named_attribute(&name, interner.type_attributes(&type_id)))) } /// fn fields(self, generic_args: [Type]) -> [(Quoted, Type)] -/// Returns (name, type) pairs of each field of this StructDefinition. +/// Returns (name, type) pairs of each field of this TypeDefinition. /// Applies the given generic arguments to each field. -fn struct_def_fields( +fn type_def_fields( interner: &mut NodeInterner, arguments: Vec<(Value, Location)>, location: Location, call_stack: &im::Vector, ) -> IResult { let (typ, generic_args) = check_two_arguments(arguments, location)?; - let struct_id = get_struct(typ)?; + let struct_id = get_type_id(typ)?; let struct_def = interner.get_type(struct_id); let struct_def = struct_def.borrow(); @@ -562,7 +563,7 @@ fn struct_def_fields( let s = if expected == 1 { "" } else { "s" }; let was_were = if actual == 1 { "was" } else { "were" }; let message = Some(format!( - "`StructDefinition::fields` expected {expected} generic{s} for `{}` but {actual} {was_were} given", + "`TypeDefinition::fields` expected {expected} generic{s} for `{}` but {actual} {was_were} given", struct_def.name )); let location = args_location; @@ -588,16 +589,16 @@ fn struct_def_fields( } /// fn fields_as_written(self) -> [(Quoted, Type)] -/// Returns (name, type) pairs of each field of this StructDefinition. +/// Returns (name, type) pairs of each field of this TypeDefinition. /// /// Note that any generic arguments won't be applied: if you need them to be, use `fields`. -fn struct_def_fields_as_written( +fn type_def_fields_as_written( interner: &mut NodeInterner, arguments: Vec<(Value, Location)>, location: Location, ) -> IResult { let argument = check_one_argument(arguments, location)?; - let struct_id = get_struct(argument)?; + let struct_id = get_type_id(argument)?; let struct_def = interner.get_type(struct_id); let struct_def = struct_def.borrow(); @@ -620,25 +621,25 @@ fn struct_def_fields_as_written( } // fn module(self) -> Module -fn struct_def_module( +fn type_def_module( interpreter: &Interpreter, arguments: Vec<(Value, Location)>, location: Location, ) -> IResult { let self_argument = check_one_argument(arguments, location)?; - let struct_id = get_struct(self_argument)?; + let struct_id = get_type_id(self_argument)?; let parent = struct_id.parent_module_id(interpreter.elaborator.def_maps); Ok(Value::ModuleDefinition(parent)) } // fn name(self) -> Quoted -fn struct_def_name( +fn type_def_name( interner: &NodeInterner, arguments: Vec<(Value, Location)>, location: Location, ) -> IResult { let self_argument = check_one_argument(arguments, location)?; - let struct_id = get_struct(self_argument)?; + let struct_id = get_type_id(self_argument)?; let the_struct = interner.get_type(struct_id); let name = Token::Ident(the_struct.borrow().name.to_string()); @@ -647,14 +648,14 @@ fn struct_def_name( } /// fn set_fields(self, new_fields: [(Quoted, Type)]) {} -/// Returns (name, type) pairs of each field of this StructDefinition -fn struct_def_set_fields( +/// Returns (name, type) pairs of each field of this TypeDefinition +fn type_def_set_fields( interner: &mut NodeInterner, arguments: Vec<(Value, Location)>, location: Location, ) -> IResult { let (the_struct, fields) = check_two_arguments(arguments, location)?; - let struct_id = get_struct(the_struct)?; + let struct_id = get_type_id(the_struct)?; let struct_def = interner.get_type(struct_id); let mut struct_def = struct_def.borrow_mut(); @@ -833,8 +834,9 @@ fn quoted_as_module( parse(interpreter.elaborator, argument, Parser::parse_path_no_turbofish_or_error, "a path") .ok(); let option_value = path.and_then(|path| { - let module = interpreter - .elaborate_in_function(interpreter.current_function, |elaborator| { + let reason = Some(ElaborateReason::EvaluatingComptimeCall("Quoted::as_module", location)); + let module = + interpreter.elaborate_in_function(interpreter.current_function, reason, |elaborator| { elaborator.resolve_module_by_path(path) }); module.map(Value::ModuleDefinition) @@ -856,8 +858,10 @@ fn quoted_as_trait_constraint( Parser::parse_trait_bound_or_error, "a trait constraint", )?; + let reason = + Some(ElaborateReason::EvaluatingComptimeCall("Quoted::as_trait_constraint", location)); let bound = interpreter - .elaborate_in_function(interpreter.current_function, |elaborator| { + .elaborate_in_function(interpreter.current_function, reason, |elaborator| { elaborator.resolve_trait_bound(&trait_bound) }) .ok_or(InterpreterError::FailedToResolveTraitBound { trait_bound, location })?; @@ -873,8 +877,11 @@ fn quoted_as_type( ) -> IResult { let argument = check_one_argument(arguments, location)?; let typ = parse(interpreter.elaborator, argument, Parser::parse_type_or_error, "a type")?; - let typ = interpreter - .elaborate_in_function(interpreter.current_function, |elab| elab.resolve_type(typ)); + let reason = Some(ElaborateReason::EvaluatingComptimeCall("Quoted::as_type", location)); + let typ = + interpreter.elaborate_in_function(interpreter.current_function, reason, |elaborator| { + elaborator.resolve_type(typ) + }); Ok(Value::Type(typ)) } @@ -1041,7 +1048,7 @@ fn type_as_mutable_reference( location: Location, ) -> IResult { type_as(arguments, return_type, location, |typ| { - if let Type::MutableReference(typ) = typ { Some(Value::Type(*typ)) } else { None } + if let Type::Reference(typ, true) = typ { Some(Value::Type(*typ)) } else { None } }) } @@ -1067,8 +1074,8 @@ fn type_as_str( }) } -// fn as_struct(self) -> Option<(StructDefinition, [Type])> -fn type_as_struct( +// fn as_data_type(self) -> Option<(TypeDefinition, [Type])> +fn type_as_data_type( arguments: Vec<(Value, Location)>, return_type: Type, location: Location, @@ -1076,7 +1083,7 @@ fn type_as_struct( type_as(arguments, return_type, location, |typ| { if let Type::DataType(struct_type, generics) = typ { Some(Value::Tuple(vec![ - Value::StructDefinition(struct_type.borrow().id), + Value::TypeDefinition(struct_type.borrow().id), Value::Slice( generics.into_iter().map(Value::Type).collect(), Type::Slice(Box::new(Type::Quoted(QuotedType::Type))), @@ -1321,7 +1328,7 @@ fn unresolved_type_as_mutable_reference( location: Location, ) -> IResult { unresolved_type_as(interner, arguments, return_type, location, |typ| { - if let UnresolvedTypeData::MutableReference(typ) = typ { + if let UnresolvedTypeData::Reference(typ, true) = typ { Some(Value::UnresolvedType(typ.typ)) } else { None @@ -1483,9 +1490,9 @@ fn zeroed(return_type: Type, location: Location) -> Value { // Using Value::Zeroed here is probably safer than using FuncId::dummy_id() or similar Value::Zeroed(typ) } - Type::MutableReference(element) => { + Type::Reference(element, mutable) => { let element = zeroed(*element, location); - Value::Pointer(Shared::new(element), false) + Value::Pointer(Shared::new(element), false, mutable) } // Optimistically assume we can resolve this type later or that the value is unused Type::TypeVariable(_) @@ -2177,7 +2184,11 @@ fn expr_as_unary_op( let unary_op_value: u128 = match prefix_expr.operator { UnaryOp::Minus => 0, UnaryOp::Not => 1, - UnaryOp::MutableReference => 2, + UnaryOp::Reference { mutable: true } => 2, + UnaryOp::Reference { mutable: false } => { + // `&` alone is experimental and currently hidden from the comptime API + return None; + } UnaryOp::Dereference { .. } => 3, }; @@ -2292,7 +2303,9 @@ fn expr_resolve( interpreter.current_function }; - interpreter.elaborate_in_function(function_to_resolve_in, |elaborator| match expr_value { + let reason = Some(ElaborateReason::EvaluatingComptimeCall("Expr::resolve", location)); + interpreter.elaborate_in_function(function_to_resolve_in, reason, |elaborator| match expr_value + { ExprValue::Expression(expression_kind) => { let expr = Expression { kind: expression_kind, location: self_argument_location }; let (expr_id, _) = elaborator.elaborate_expression(expr); @@ -2420,13 +2433,39 @@ fn function_def_as_typed_expr( ) -> IResult { let self_argument = check_one_argument(arguments, location)?; let func_id = get_function_def(self_argument)?; + let trait_impl_id = interpreter.elaborator.interner.function_meta(&func_id).trait_impl; let definition_id = interpreter.elaborator.interner.function_definition_id(func_id); - let hir_ident = HirIdent::non_trait_method(definition_id, location); + let hir_ident = if let Some(trait_impl_id) = trait_impl_id { + let trait_impl = interpreter.elaborator.interner.get_trait_implementation(trait_impl_id); + let trait_impl = trait_impl.borrow(); + let ordered = trait_impl.trait_generics.clone(); + let named = + interpreter.elaborator.interner.get_associated_types_for_impl(trait_impl_id).to_vec(); + let trait_generics = TraitGenerics { ordered, named }; + let trait_bound = + ResolvedTraitBound { trait_id: trait_impl.trait_id, trait_generics, location }; + let constraint = TraitConstraint { typ: trait_impl.typ.clone(), trait_bound }; + let method_index = trait_impl.methods.iter().position(|id| *id == func_id); + let method_index = method_index.expect("Expected to find the method"); + let method_id = TraitMethodId { trait_id: trait_impl.trait_id, method_index }; + let trait_method = TraitMethod { method_id, constraint, assumed: true }; + let id = interpreter.elaborator.interner.trait_method_id(trait_method.method_id); + HirIdent { location, id, impl_kind: ImplKind::TraitMethod(trait_method) } + } else { + HirIdent::non_trait_method(definition_id, location) + }; let generics = None; let hir_expr = HirExpression::Ident(hir_ident.clone(), generics.clone()); let expr_id = interpreter.elaborator.interner.push_expr(hir_expr); interpreter.elaborator.interner.push_expr_location(expr_id, location); - let typ = interpreter.elaborator.type_check_variable(hir_ident, expr_id, generics); + let reason = Some(ElaborateReason::EvaluatingComptimeCall( + "FunctionDefinition::as_typed_expr", + location, + )); + let typ = + interpreter.elaborate_in_function(interpreter.current_function, reason, |elaborator| { + elaborator.type_check_variable(hir_ident, expr_id, generics) + }); interpreter.elaborator.interner.push_expr_type(expr_id, typ); Ok(Value::TypedExpr(TypedExpr::ExprId(expr_id))) } @@ -2632,7 +2671,10 @@ fn function_def_set_parameters( "a pattern", )?; - let hir_pattern = interpreter.elaborate_in_function(Some(func_id), |elaborator| { + let reason = + ElaborateReason::EvaluatingComptimeCall("FunctionDefinition::set_parameters", location); + let reason = Some(reason); + let hir_pattern = interpreter.elaborate_in_function(Some(func_id), reason, |elaborator| { elaborator.elaborate_pattern_and_store_ids( parameter_pattern, parameter_type.clone(), @@ -2747,10 +2789,8 @@ fn module_add_item( let parser = Parser::parse_top_level_items; let top_level_statements = parse(interpreter.elaborator, item, parser, "a top-level item")?; - interpreter.elaborate_in_module(module_id, |elaborator| { - let previous_errors = elaborator - .push_elaborate_reason_and_take_errors(ElaborateReason::AddingItemToModule, location); - + let reason = Some(ElaborateReason::EvaluatingComptimeCall("Module::add_item", location)); + interpreter.elaborate_in_module(module_id, reason, |elaborator| { let mut generated_items = CollectedItems::default(); for top_level_statement in top_level_statements { @@ -2760,8 +2800,6 @@ fn module_add_item( if !generated_items.is_empty() { elaborator.elaborate_items(generated_items); } - - elaborator.pop_elaborate_reason(previous_errors); }); Ok(Value::Unit) @@ -2801,7 +2839,7 @@ fn module_functions( Ok(Value::Slice(func_ids, slice_type)) } -// fn structs(self) -> [StructDefinition] +// fn structs(self) -> [TypeDefinition] fn module_structs( interpreter: &Interpreter, arguments: Vec<(Value, Location)>, @@ -2816,14 +2854,14 @@ fn module_structs( .iter() .filter_map(|module_def_id| { if let ModuleDefId::TypeId(id) = module_def_id { - Some(Value::StructDefinition(*id)) + Some(Value::TypeDefinition(*id)) } else { None } }) .collect(); - let slice_type = Type::Slice(Box::new(Type::Quoted(QuotedType::StructDefinition))); + let slice_type = Type::Slice(Box::new(Type::Quoted(QuotedType::TypeDefinition))); Ok(Value::Slice(struct_ids, slice_type)) } @@ -2913,10 +2951,24 @@ fn modulus_num_bits(arguments: Vec<(Value, Location)>, location: Location) -> IR } // fn quoted_eq(_first: Quoted, _second: Quoted) -> bool -fn quoted_eq(arguments: Vec<(Value, Location)>, location: Location) -> IResult { - eq_item(arguments, location, get_quoted) -} +fn quoted_eq( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let (self_arg, other_arg) = check_two_arguments(arguments, location)?; + let self_arg = get_quoted(self_arg)?; + let other_arg = get_quoted(other_arg)?; + // Comparing tokens one against each other doesn't work in the general case because tokens + // might be refer to interned expressions/statements/etc. We'd need to convert those nodes + // to tokens and compare the final result, but comparing their string representation works + // equally well and, for simplicity, that's what we do here. + let self_string = tokens_to_string(&self_arg, interner); + let other_string = tokens_to_string(&other_arg, interner); + + Ok(Value::Bool(self_string == other_string)) +} fn quoted_hash(arguments: Vec<(Value, Location)>, location: Location) -> IResult { hash_item(arguments, location, get_quoted) } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs index e552cf0c5a22..3c5ff519933b 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs @@ -328,10 +328,10 @@ pub(crate) fn get_module((value, location): (Value, Location)) -> IResult IResult { +pub(crate) fn get_type_id((value, location): (Value, Location)) -> IResult { match value { - Value::StructDefinition(id) => Ok(id), - _ => type_mismatch(value, Type::Quoted(QuotedType::StructDefinition), location), + Value::TypeDefinition(id) => Ok(id), + _ => type_mismatch(value, Type::Quoted(QuotedType::TypeDefinition), location), } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/value.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/value.rs index 8d07669497f6..f2cfa8f11a95 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/value.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/value.rs @@ -61,11 +61,11 @@ pub enum Value { Tuple(Vec), Struct(HashMap, Value>, Type), Enum(/*tag*/ usize, /*args*/ Vec, Type), - Pointer(Shared, /* auto_deref */ bool), + Pointer(Shared, /* auto_deref */ bool, /* mutable */ bool), Array(Vector, Type), Slice(Vector, Type), Quoted(Rc>), - StructDefinition(TypeId), + TypeDefinition(TypeId), TraitConstraint(TraitId, TraitGenerics), TraitDefinition(TraitId), TraitImpl(TraitImplId), @@ -150,13 +150,13 @@ impl Value { Value::Array(_, typ) => return Cow::Borrowed(typ), Value::Slice(_, typ) => return Cow::Borrowed(typ), Value::Quoted(_) => Type::Quoted(QuotedType::Quoted), - Value::StructDefinition(_) => Type::Quoted(QuotedType::StructDefinition), - Value::Pointer(element, auto_deref) => { + Value::TypeDefinition(_) => Type::Quoted(QuotedType::TypeDefinition), + Value::Pointer(element, auto_deref, mutable) => { if *auto_deref { element.borrow().get_type().into_owned() } else { let element = element.borrow().get_type().into_owned(); - Type::MutableReference(Box::new(element)) + Type::Reference(Box::new(element), *mutable) } } Value::TraitConstraint { .. } => Type::Quoted(QuotedType::TraitConstraint), @@ -321,7 +321,7 @@ impl Value { } Value::TypedExpr(..) | Value::Pointer(..) - | Value::StructDefinition(_) + | Value::TypeDefinition(_) | Value::TraitConstraint(..) | Value::TraitDefinition(_) | Value::TraitImpl(_) @@ -452,14 +452,14 @@ impl Value { Value::TypedExpr(TypedExpr::ExprId(expr_id)) => interner.expression(&expr_id), // Only convert pointers with auto_deref = true. These are mutable variables // and we don't need to wrap them in `&mut`. - Value::Pointer(element, true) => { + Value::Pointer(element, true, _) => { return element.unwrap_or_clone().into_hir_expression(interner, location); } Value::Closure(closure) => HirExpression::Lambda(closure.lambda.clone()), Value::TypedExpr(TypedExpr::StmtId(..)) | Value::Expr(..) | Value::Pointer(..) - | Value::StructDefinition(_) + | Value::TypeDefinition(_) | Value::TraitConstraint(..) | Value::TraitDefinition(_) | Value::TraitImpl(_) diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/errors.rs index 7f17b1e30430..07738c5f7de4 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/errors.rs @@ -37,8 +37,8 @@ pub enum DefCollectorErrorKind { CannotReexportItemWithLessVisibility { item_name: Ident, desired_visibility: ItemVisibility }, #[error("Non-struct type used in impl")] NonStructTypeInImpl { location: Location }, - #[error("Cannot implement trait on a mutable reference type")] - MutableReferenceInTraitImpl { location: Location }, + #[error("Cannot implement trait on a reference type")] + ReferenceInTraitImpl { location: Location }, #[error("Impl for type `{typ}` overlaps with existing impl")] OverlappingImpl { typ: crate::Type, location: Location, prev_location: Location }, #[error("Cannot `impl` a type defined outside the current crate")] @@ -97,7 +97,7 @@ impl DefCollectorErrorKind { | DefCollectorErrorKind::TestOnAssociatedFunction { location } | DefCollectorErrorKind::ExportOnAssociatedFunction { location } | DefCollectorErrorKind::NonStructTypeInImpl { location } - | DefCollectorErrorKind::MutableReferenceInTraitImpl { location } + | DefCollectorErrorKind::ReferenceInTraitImpl { location } | DefCollectorErrorKind::OverlappingImpl { location, .. } | DefCollectorErrorKind::ModuleAlreadyPartOfCrate { location, .. } | DefCollectorErrorKind::ModuleOriginallyDefined { location, .. } @@ -199,8 +199,8 @@ impl<'a> From<&'a DefCollectorErrorKind> for Diagnostic { "Only struct types may have implementation methods".into(), *location, ), - DefCollectorErrorKind::MutableReferenceInTraitImpl { location } => Diagnostic::simple_error( - "Trait impls are not allowed on mutable reference types".into(), + DefCollectorErrorKind::ReferenceInTraitImpl { location } => Diagnostic::simple_error( + "Trait impls are not allowed on reference types".into(), "Try using a struct type here instead".into(), *location, ), diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/import.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/import.rs index 16e25b804651..14e0e5e5386f 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/import.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/import.rs @@ -112,7 +112,7 @@ impl<'a> From<&'a PathResolutionError> for CustomDiagnostic { CustomDiagnostic::simple_error(error.to_string(), String::new(), ident.location()) } PathResolutionError::TraitMethodNotInScope { ident, .. } => { - CustomDiagnostic::simple_warning(error.to_string(), String::new(), ident.location()) + CustomDiagnostic::simple_error(error.to_string(), String::new(), ident.location()) } PathResolutionError::UnresolvedWithPossibleTraitsToImport { ident, traits } => { let mut traits = vecmap(traits, |trait_name| format!("`{}`", trait_name)); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types.rs index 1f4b21cb9a90..f25a40197abb 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types.rs @@ -111,8 +111,8 @@ pub enum Type { /*unconstrained*/ bool, ), - /// &mut T - MutableReference(Box), + /// &T + Reference(Box, /*mutable*/ bool), /// A type generic over the given type variables. /// Storing both the TypeVariableId and TypeVariable isn't necessary @@ -311,8 +311,7 @@ pub enum QuotedType { TopLevelItem, Type, TypedExpr, - StructDefinition, - EnumDefinition, + TypeDefinition, TraitConstraint, TraitDefinition, TraitImpl, @@ -1047,9 +1046,12 @@ impl std::fmt::Display for Type { write!(f, "fn{closure_env_text}({}) -> {ret}", args.join(", ")) } - Type::MutableReference(element) => { + Type::Reference(element, mutable) if *mutable => { write!(f, "&mut {element}") } + Type::Reference(element, _) => { + write!(f, "&{element}") + } Type::Quoted(quoted) => write!(f, "{}", quoted), Type::InfixExpr(lhs, op, rhs, _) => { let this = self.canonicalize_checked(); @@ -1096,8 +1098,7 @@ impl std::fmt::Display for QuotedType { QuotedType::TopLevelItem => write!(f, "TopLevelItem"), QuotedType::Type => write!(f, "Type"), QuotedType::TypedExpr => write!(f, "TypedExpr"), - QuotedType::StructDefinition => write!(f, "StructDefinition"), - QuotedType::EnumDefinition => write!(f, "EnumDefinition"), + QuotedType::TypeDefinition => write!(f, "TypeDefinition"), QuotedType::TraitDefinition => write!(f, "TraitDefinition"), QuotedType::TraitConstraint => write!(f, "TraitConstraint"), QuotedType::TraitImpl => write!(f, "TraitImpl"), @@ -1221,7 +1222,7 @@ impl Type { Type::Alias(alias_type, generics) => { alias_type.borrow().get_type(&generics).is_primitive() } - Type::MutableReference(typ) => typ.is_primitive(), + Type::Reference(typ, _) => typ.is_primitive(), Type::DataType(..) | Type::TypeVariable(..) | Type::TraitAsType(..) @@ -1244,7 +1245,7 @@ impl Type { } pub(crate) fn is_mutable_ref(&self) -> bool { - matches!(self.follow_bindings_shallow().as_ref(), Type::MutableReference(_)) + matches!(self.follow_bindings_shallow().as_ref(), Type::Reference(_, true)) } /// True if this type can be used as a parameter to `main` or a contract function. @@ -1269,7 +1270,7 @@ impl Type { | Type::TypeVariable(_) | Type::NamedGeneric(_, _) | Type::Function(_, _, _, _) - | Type::MutableReference(_) + | Type::Reference(..) | Type::Forall(_, _) | Type::Quoted(_) | Type::Slice(_) @@ -1327,7 +1328,7 @@ impl Type { // This is possible as long as the output size is not dependent upon a witness condition. | Type::Function(_, _, _, _) | Type::Slice(_) - | Type::MutableReference(_) + | Type::Reference(..) | Type::Forall(_, _) // TODO: probably can allow code as it is all compile time | Type::Quoted(_) @@ -1385,7 +1386,7 @@ impl Type { // environment is the interpreter. In this environment, they are valid. Type::Quoted(_) => true, - Type::MutableReference(_) | Type::Forall(_, _) | Type::TraitAsType(..) => false, + Type::Reference(..) | Type::Forall(_, _) | Type::TraitAsType(..) => false, Type::Alias(alias, generics) => { let alias = alias.borrow(); @@ -1477,7 +1478,7 @@ impl Type { | Type::DataType(..) | Type::TraitAsType(..) | Type::Function(..) - | Type::MutableReference(..) + | Type::Reference(..) | Type::Forall(..) | Type::Quoted(..) => Kind::Normal, Type::Error => Kind::Any, @@ -1574,7 +1575,7 @@ impl Type { | Type::TraitAsType(..) | Type::NamedGeneric(_, _) | Type::Function(_, _, _, _) - | Type::MutableReference(_) + | Type::Reference(..) | Type::Forall(_, _) | Type::Constant(_, _) | Type::Quoted(_) @@ -1908,8 +1909,12 @@ impl Type { } } - (MutableReference(elem_a), MutableReference(elem_b)) => { - elem_a.try_unify(elem_b, bindings) + (Reference(elem_a, mutable_a), Reference(elem_b, mutable_b)) => { + if mutable_a == mutable_b { + elem_a.try_unify(elem_b, bindings) + } else { + Err(UnificationError) + } } (InfixExpr(lhs_a, op_a, rhs_a, _), InfixExpr(lhs_b, op_b, rhs_b, _)) => { @@ -2026,6 +2031,10 @@ impl Type { return; } + if self.try_reference_coercion(expected) { + return; + } + // Try to coerce `fn (..) -> T` to `unconstrained fn (..) -> T` match self.try_fn_to_unconstrained_fn_coercion(expected) { FunctionCoercionResult::NoCoercion => errors.push(make_error()), @@ -2094,6 +2103,25 @@ impl Type { false } + /// Attempt to coerce `&mut T` to `&T`, returning true if this is possible. + pub fn try_reference_coercion(&self, target: &Type) -> bool { + let this = self.follow_bindings(); + let target = target.follow_bindings(); + + if let (Type::Reference(this_elem, true), Type::Reference(target_elem, false)) = + (&this, &target) + { + // Still have to ensure the element types match. + // Don't need to issue an error here if not, it will be done in unify_with_coercions + let mut bindings = TypeBindings::new(); + if this_elem.try_unify(target_elem, &mut bindings).is_ok() { + Self::apply_type_bindings(bindings); + return true; + } + } + false + } + /// Apply the given type bindings, making them permanently visible for each /// clone of each type variable bound. pub fn apply_type_bindings(bindings: TypeBindings) { @@ -2453,9 +2481,10 @@ impl Type { let env = Box::new(env.substitute_helper(type_bindings, substitute_bound_typevars)); Type::Function(args, ret, env, *unconstrained) } - Type::MutableReference(element) => Type::MutableReference(Box::new( - element.substitute_helper(type_bindings, substitute_bound_typevars), - )), + Type::Reference(element, mutable) => Type::Reference( + Box::new(element.substitute_helper(type_bindings, substitute_bound_typevars)), + *mutable, + ), Type::TraitAsType(s, name, generics) => { let ordered = vecmap(&generics.ordered, |arg| { @@ -2519,7 +2548,7 @@ impl Type { || ret.occurs(target_id) || env.occurs(target_id) } - Type::MutableReference(element) => element.occurs(target_id), + Type::Reference(element, _) => element.occurs(target_id), Type::InfixExpr(lhs, _op, rhs, _) => lhs.occurs(target_id) || rhs.occurs(target_id), Type::FieldElement @@ -2579,7 +2608,7 @@ impl Type { Function(args, ret, env, *unconstrained) } - MutableReference(element) => MutableReference(Box::new(element.follow_bindings())), + Reference(element, mutable) => Reference(Box::new(element.follow_bindings()), *mutable), TraitAsType(s, name, args) => { let ordered = vecmap(&args.ordered, |arg| arg.follow_bindings()); @@ -2704,7 +2733,7 @@ impl Type { ret.replace_named_generics_with_type_variables(); env.replace_named_generics_with_type_variables(); } - Type::MutableReference(elem) => elem.replace_named_generics_with_type_variables(), + Type::Reference(elem, _) => elem.replace_named_generics_with_type_variables(), Type::Forall(_, typ) => typ.replace_named_generics_with_type_variables(), Type::InfixExpr(lhs, _op, rhs, _) => { lhs.replace_named_generics_with_type_variables(); @@ -2748,7 +2777,7 @@ impl Type { TypeBinding::Bound(typ) => typ.integral_maximum_size(), TypeBinding::Unbound(_, kind) => kind.integral_maximum_size(), }, - Type::MutableReference(typ) => typ.integral_maximum_size(), + Type::Reference(typ, _) => typ.integral_maximum_size(), Type::InfixExpr(lhs, _op, rhs, _) => lhs.infix_kind(rhs).integral_maximum_size(), Type::Constant(_, kind) => kind.integral_maximum_size(), @@ -2975,8 +3004,8 @@ impl From<&Type> for PrintableType { env: Box::new(env.as_ref().into()), unconstrained: *unconstrained, }, - Type::MutableReference(typ) => { - PrintableType::MutableReference { typ: Box::new(typ.as_ref().into()) } + Type::Reference(typ, mutable) => { + PrintableType::Reference { typ: Box::new(typ.as_ref().into()), mutable: *mutable } } Type::Quoted(_) => unreachable!(), Type::InfixExpr(..) => unreachable!(), @@ -3069,7 +3098,10 @@ impl std::fmt::Debug for Type { write!(f, "fn({}) -> {ret:?}{closure_env_text}", args.join(", ")) } - Type::MutableReference(element) => { + Type::Reference(element, false) => { + write!(f, "&{element:?}") + } + Type::Reference(element, true) => { write!(f, "&mut {element:?}") } Type::Quoted(quoted) => write!(f, "{}", quoted), @@ -3151,7 +3183,10 @@ impl std::hash::Hash for Type { env.hash(state); is_unconstrained.hash(state); } - Type::MutableReference(elem) => elem.hash(state), + Type::Reference(elem, mutable) => { + elem.hash(state); + mutable.hash(state); + } Type::Forall(vars, typ) => { vars.hash(state); typ.hash(state); @@ -3219,7 +3254,9 @@ impl PartialEq for Type { let args_and_ret_eq = lhs_args == rhs_args && lhs_ret == rhs_ret; args_and_ret_eq && lhs_env == rhs_env && lhs_unconstrained == rhs_unconstrained } - (MutableReference(lhs_elem), MutableReference(rhs_elem)) => lhs_elem == rhs_elem, + (Reference(lhs_elem, lhs_mut), Reference(rhs_elem, rhs_mut)) => { + lhs_elem == rhs_elem && lhs_mut == rhs_mut + } (Forall(lhs_vars, lhs_type), Forall(rhs_vars, rhs_type)) => { lhs_vars == rhs_vars && lhs_type == rhs_type } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/lexer/lexer.rs b/noir/noir-repo/compiler/noirc_frontend/src/lexer/lexer.rs index 630f192c1095..a6657d638667 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/lexer/lexer.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/lexer/lexer.rs @@ -106,6 +106,8 @@ impl<'a> Lexer<'a> { // and the next token issued will be the next '&'. let span = Span::inclusive(self.position, self.position + 1); Err(LexerErrorKind::LogicalAnd { location: self.location(span) }) + } else if self.peek_char_is('[') { + self.single_char_token(Token::SliceStart) } else { self.single_char_token(Token::Ampersand) } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs b/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs index 7367489f6251..75108537de38 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs @@ -188,6 +188,10 @@ pub enum Token { Percent, /// & Ampersand, + /// & followed immediately by '[' + /// This is a lexer hack to distinguish slices + /// from taking a reference to an array + SliceStart, /// ^ Caret, /// << @@ -287,6 +291,7 @@ pub fn token_to_borrowed_token(token: &Token) -> BorrowedToken<'_> { Token::Slash => BorrowedToken::Slash, Token::Percent => BorrowedToken::Percent, Token::Ampersand => BorrowedToken::Ampersand, + Token::SliceStart => BorrowedToken::Ampersand, Token::Caret => BorrowedToken::Caret, Token::ShiftLeft => BorrowedToken::ShiftLeft, Token::ShiftRight => BorrowedToken::ShiftRight, @@ -522,6 +527,7 @@ impl fmt::Display for Token { Token::Slash => write!(f, "/"), Token::Percent => write!(f, "%"), Token::Ampersand => write!(f, "&"), + Token::SliceStart => write!(f, "&"), Token::Caret => write!(f, "^"), Token::ShiftLeft => write!(f, "<<"), Token::ShiftRight => write!(f, ">>"), @@ -1110,6 +1116,7 @@ pub enum Keyword { TraitDefinition, TraitImpl, Type, + TypeDefinition, TypedExpr, TypeType, Unchecked, @@ -1171,6 +1178,7 @@ impl fmt::Display for Keyword { Keyword::TraitDefinition => write!(f, "TraitDefinition"), Keyword::TraitImpl => write!(f, "TraitImpl"), Keyword::Type => write!(f, "type"), + Keyword::TypeDefinition => write!(f, "TypeDefinition"), Keyword::TypedExpr => write!(f, "TypedExpr"), Keyword::TypeType => write!(f, "Type"), Keyword::Unchecked => write!(f, "unchecked"), @@ -1235,6 +1243,7 @@ impl Keyword { "TraitImpl" => Keyword::TraitImpl, "type" => Keyword::Type, "Type" => Keyword::TypeType, + "TypeDefinition" => Keyword::TypeDefinition, "TypedExpr" => Keyword::TypedExpr, "StructDefinition" => Keyword::StructDefinition, "unchecked" => Keyword::Unchecked, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/errors.rs index 93a12a46591b..2f5eaccffe92 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/errors.rs @@ -16,6 +16,7 @@ pub enum MonomorphizationError { ComptimeTypeInRuntimeCode { typ: String, location: Location }, CheckedTransmuteFailed { actual: Type, expected: Type, location: Location }, CheckedCastFailed { actual: Type, expected: Type, location: Location }, + RecursiveType { typ: Type, location: Location }, } impl MonomorphizationError { @@ -28,6 +29,7 @@ impl MonomorphizationError { | MonomorphizationError::ComptimeTypeInRuntimeCode { location, .. } | MonomorphizationError::CheckedTransmuteFailed { location, .. } | MonomorphizationError::CheckedCastFailed { location, .. } + | MonomorphizationError::RecursiveType { location, .. } | MonomorphizationError::NoDefaultType { location, .. } => *location, MonomorphizationError::InterpreterError(error) => error.location(), } @@ -67,6 +69,11 @@ impl From for CustomDiagnostic { let secondary = "Comptime type used here".into(); return CustomDiagnostic::simple_error(message, secondary, *location); } + MonomorphizationError::RecursiveType { typ, location } => { + let message = format!("Type `{typ}` is recursive"); + let secondary = "All types in Noir must have a known size at compile-time".into(); + return CustomDiagnostic::simple_error(message, secondary, *location); + } }; let location = error.location(); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs index 4b0c4b683454..7a8f32b58d43 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -27,7 +27,7 @@ use crate::{ }; use acvm::{FieldElement, acir::AcirField}; use ast::{GlobalId, While}; -use fxhash::FxHashMap as HashMap; +use fxhash::{FxHashMap as HashMap, FxHashSet as HashSet}; use iter_extended::{btree_map, try_vecmap, vecmap}; use noirc_errors::Location; use noirc_printable_type::PrintableType; @@ -48,6 +48,7 @@ mod debug; pub mod debug_types; pub mod errors; pub mod printer; +pub mod tests; struct LambdaContext { env_ident: ast::Ident, @@ -378,7 +379,10 @@ impl<'interner> Monomorphizer<'interner> { other => other, }; - let return_type = Self::convert_type(return_type, meta.location)?; + // If `convert_type` fails here it is most likely because of generics at the + // call site after instantiating this function's type. So show the error there + // instead of at the function definition. + let return_type = Self::convert_type(return_type, location)?; let unconstrained = self.in_unconstrained_function; let attributes = self.interner.function_attributes(&f); @@ -1120,6 +1124,14 @@ impl<'interner> Monomorphizer<'interner> { /// Convert a non-tuple/struct type to a monomorphized type fn convert_type(typ: &HirType, location: Location) -> Result { + Self::convert_type_helper(typ, location, &mut HashSet::default()) + } + + fn convert_type_helper( + typ: &HirType, + location: Location, + seen_types: &mut HashSet, + ) -> Result { let typ = typ.follow_bindings_shallow(); Ok(match typ.as_ref() { HirType::FieldElement => ast::Type::Field, @@ -1155,12 +1167,14 @@ impl<'interner> Monomorphizer<'interner> { }); } }; - let fields = Box::new(Self::convert_type(fields.as_ref(), location)?); + let fields = + Box::new(Self::convert_type_helper(fields.as_ref(), location, seen_types)?); ast::Type::FmtString(size, fields) } HirType::Unit => ast::Type::Unit, HirType::Array(length, element) => { - let element = Box::new(Self::convert_type(element.as_ref(), location)?); + let element = + Box::new(Self::convert_type_helper(element.as_ref(), location, seen_types)?); let length = match length.evaluate_to_u32(location) { Ok(length) => length, Err(err) => { @@ -1175,7 +1189,8 @@ impl<'interner> Monomorphizer<'interner> { ast::Type::Array(length, element) } HirType::Slice(element) => { - let element = Box::new(Self::convert_type(element.as_ref(), location)?); + let element = + Box::new(Self::convert_type_helper(element.as_ref(), location, seen_types)?); ast::Type::Slice(element) } HirType::TraitAsType(..) => { @@ -1183,7 +1198,7 @@ impl<'interner> Monomorphizer<'interner> { } HirType::NamedGeneric(binding, _) => { if let TypeBinding::Bound(binding) = &*binding.borrow() { - return Self::convert_type(binding, location); + return Self::convert_type_helper(binding, location, seen_types); } // Default any remaining unbound type variables. @@ -1195,13 +1210,21 @@ impl<'interner> Monomorphizer<'interner> { HirType::CheckedCast { from, to } => { Self::check_checked_cast(from, to, location)?; - Self::convert_type(to, location)? + Self::convert_type_helper(to, location, seen_types)? } HirType::TypeVariable(binding) => { + let input_type = typ.as_ref().clone(); + if !seen_types.insert(input_type.clone()) { + let typ = input_type; + return Err(MonomorphizationError::RecursiveType { typ, location }); + } + let type_var_kind = match &*binding.borrow() { TypeBinding::Bound(binding) => { - return Self::convert_type(binding, location); + let typ = Self::convert_type_helper(binding, location, seen_types); + seen_types.remove(&input_type); + return typ; } TypeBinding::Unbound(_, type_var_kind) => type_var_kind.clone(), }; @@ -1214,7 +1237,8 @@ impl<'interner> Monomorphizer<'interner> { None => return Err(MonomorphizationError::NoDefaultType { location }), }; - let monomorphized_default = Self::convert_type(&default, location)?; + let monomorphized_default = + Self::convert_type_helper(&default, location, seen_types)?; binding.bind(default); monomorphized_default } @@ -1226,19 +1250,30 @@ impl<'interner> Monomorphizer<'interner> { Self::check_type(arg, location)?; } + let input_type = typ.as_ref().clone(); + if !seen_types.insert(input_type.clone()) { + let typ = input_type; + return Err(MonomorphizationError::RecursiveType { typ, location }); + } + let def = def.borrow(); if let Some(fields) = def.get_fields(args) { - let fields = - try_vecmap(fields, |(_, field)| Self::convert_type(&field, location))?; + let fields = try_vecmap(fields, |(_, field)| { + Self::convert_type_helper(&field, location, seen_types) + })?; + + seen_types.remove(&input_type); ast::Type::Tuple(fields) } else if let Some(variants) = def.get_variants(args) { // Enums are represented as (tag, variant1, variant2, .., variantN) let mut fields = vec![ast::Type::Field]; for (_, variant_fields) in variants { - let variant_fields = - try_vecmap(variant_fields, |typ| Self::convert_type(&typ, location))?; + let variant_fields = try_vecmap(variant_fields, |typ| { + Self::convert_type_helper(&typ, location, seen_types) + })?; fields.push(ast::Type::Tuple(variant_fields)); } + seen_types.remove(&input_type); ast::Type::Tuple(fields) } else { unreachable!("Data type has no body") @@ -1252,18 +1287,20 @@ impl<'interner> Monomorphizer<'interner> { Self::check_type(arg, location)?; } - Self::convert_type(&def.borrow().get_type(args), location)? + Self::convert_type_helper(&def.borrow().get_type(args), location, seen_types)? } HirType::Tuple(fields) => { - let fields = try_vecmap(fields, |x| Self::convert_type(x, location))?; + let fields = + try_vecmap(fields, |x| Self::convert_type_helper(x, location, seen_types))?; ast::Type::Tuple(fields) } HirType::Function(args, ret, env, unconstrained) => { - let args = try_vecmap(args, |x| Self::convert_type(x, location))?; - let ret = Box::new(Self::convert_type(ret, location)?); - let env = Self::convert_type(env, location)?; + let args = + try_vecmap(args, |x| Self::convert_type_helper(x, location, seen_types))?; + let ret = Box::new(Self::convert_type_helper(ret, location, seen_types)?); + let env = Self::convert_type_helper(env, location, seen_types)?; match &env { ast::Type::Unit => { ast::Type::Function(args, ret, Box::new(env), *unconstrained) @@ -1280,8 +1317,9 @@ impl<'interner> Monomorphizer<'interner> { } } - HirType::MutableReference(element) => { - let element = Self::convert_type(element, location)?; + // Lower both mutable & immutable references to the same reference type + HirType::Reference(element, _mutable) => { + let element = Self::convert_type_helper(element, location, seen_types)?; ast::Type::MutableReference(Box::new(element)) } @@ -1386,7 +1424,7 @@ impl<'interner> Monomorphizer<'interner> { Self::check_type(env, location) } - HirType::MutableReference(element) => Self::check_type(element, location), + HirType::Reference(element, _mutable) => Self::check_type(element, location), HirType::InfixExpr(lhs, _, rhs, _) => { Self::check_type(lhs, location)?; Self::check_type(rhs, location) @@ -1601,8 +1639,8 @@ impl<'interner> Monomorphizer<'interner> { fn append_printable_type_info_inner(typ: &Type, arguments: &mut Vec) { // Disallow printing slices and mutable references for consistency, // since they cannot be passed from ACIR into Brillig - if matches!(typ, HirType::MutableReference(_)) { - unreachable!("println and format strings do not support mutable references."); + if matches!(typ, HirType::Reference(..)) { + unreachable!("println and format strings do not support references."); } let printable_type: PrintableType = typ.into(); @@ -2102,13 +2140,13 @@ impl<'interner> Monomorphizer<'interner> { })) } ast::Type::MutableReference(element) => { - use crate::ast::UnaryOp::MutableReference; + use crate::ast::UnaryOp::Reference; let rhs = Box::new(self.zeroed_value_of_type(element, location)); let result_type = typ.clone(); ast::Expression::Unary(ast::Unary { rhs, result_type, - operator: MutableReference, + operator: Reference { mutable: true }, location, }) } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/tests.rs b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/tests.rs new file mode 100644 index 000000000000..77190b675766 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/tests.rs @@ -0,0 +1,129 @@ +#![cfg(test)] +use crate::tests::get_program; + +use super::{ast::Program, errors::MonomorphizationError, monomorphize}; + +pub fn get_monomorphized(src: &str) -> Result { + let (_parsed_module, mut context, errors) = get_program(src); + assert!( + errors.iter().all(|err| !err.is_error()), + "Expected monomorphized program to have no errors before monomorphization, but found: {errors:?}" + ); + + let main = context + .get_main_function(context.root_crate_id()) + .unwrap_or_else(|| panic!("get_monomorphized: test program contains no 'main' function")); + + monomorphize(main, &mut context.def_interner, false) +} + +fn check_rewrite(src: &str, expected: &str) { + let program = get_monomorphized(src).unwrap(); + assert!(format!("{}", program) == expected); +} + +#[test] +fn bounded_recursive_type_errors() { + // We want to eventually allow bounded recursive types like this, but for now they are + // disallowed because they cause a panic in convert_type during monomorphization. + let src = " + fn main() { + let _tree: Tree>> = Tree::Branch( + Tree::Branch(Tree::Leaf, Tree::Leaf), + Tree::Branch(Tree::Leaf, Tree::Leaf), + ); + } + + enum Tree { + Branch(T, T), + Leaf, + }"; + + let error = get_monomorphized(src).unwrap_err(); + assert!(matches!(error, MonomorphizationError::RecursiveType { .. })); +} + +#[test] +fn recursive_type_with_alias_errors() { + // We want to eventually allow bounded recursive types like this, but for now they are + // disallowed because they cause a panic in convert_type during monomorphization. + // + // In the future we could lower this type to: + // struct OptOptUnit { + // is_some: Field, + // some: OptUnit, + // none: (), + // } + // + // struct OptUnit { + // is_some: Field, + // some: (), + // none: (), + // } + let src = " + fn main() { + let _tree: Opt> = Opt::Some(OptAlias::None); + } + + type OptAlias = Opt; + + enum Opt { + Some(T), + None, + }"; + + let error = get_monomorphized(src).unwrap_err(); + assert!(matches!(error, MonomorphizationError::RecursiveType { .. })); +} + +#[test] +fn mutually_recursive_types_error() { + let src = " + fn main() { + let _zero = Even::Zero; + } + + enum Even { + Zero, + Succ(Odd), + } + + enum Odd { + One, + Succ(Even), + }"; + + let error = get_monomorphized(src).unwrap_err(); + assert!(matches!(error, MonomorphizationError::RecursiveType { .. })); +} + +#[test] +fn simple_closure_with_no_captured_variables() { + let src = r#" + fn main() -> pub Field { + let x = 1; + let closure = || x; + closure() + } + "#; + + let expected_rewrite = r#"fn main$f0() -> Field { + let x$0 = 1; + let closure$3 = { + let closure_variable$2 = { + let env$1 = (x$l0); + (env$l1, lambda$f1) + }; + closure_variable$l2 + }; + { + let tmp$4 = closure$l3; + tmp$l4.1(tmp$l4.0) + } +} +fn lambda$f1(mut env$l1: (Field)) -> Field { + env$l1.0 +} +"#; + check_rewrite(src, expected_rewrite); +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs b/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs index 2430dca8fcad..12ef39f6b18c 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs @@ -1411,7 +1411,7 @@ impl NodeInterner { ) -> Option { match self_type { Type::Error => None, - Type::MutableReference(element) => { + Type::Reference(element, _mutable) => { self.add_method(element, method_name, method_id, trait_id) } _ => { @@ -2415,8 +2415,8 @@ impl Methods { return true; } - // Handle auto-dereferencing `&mut T` into `T` - if let Type::MutableReference(object) = object { + // Handle auto-dereferencing `&T` and `&mut T` into `T` + if let Type::Reference(object, _mutable) = object { if object.unify(typ).is_ok() { return true; } @@ -2430,8 +2430,8 @@ impl Methods { return true; } - // Handle auto-dereferencing `&mut T` into `T` - if let Type::MutableReference(method_type) = method_type { + // Handle auto-dereferencing `&T` and `&mut T` into `T` + if let Type::Reference(method_type, _mutable) = method_type { if method_type.unify(typ).is_ok() { return true; } @@ -2488,7 +2488,7 @@ fn get_type_method_key(typ: &Type) -> Option { Type::Function(_, _, _, _) => Some(Function), Type::NamedGeneric(_, _) => Some(Generic), Type::Quoted(quoted) => Some(Quoted(*quoted)), - Type::MutableReference(element) => get_type_method_key(element), + Type::Reference(element, _) => get_type_method_key(element), Type::Alias(alias, _) => get_type_method_key(&alias.borrow().typ), Type::DataType(struct_type, _) => Some(Struct(struct_type.borrow().id)), diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/errors.rs index 76e2958f668d..2989bdca94cc 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/errors.rs @@ -76,8 +76,6 @@ pub enum ParserErrorReason { TraitVisibilityIgnored, #[error("Visibility is ignored on a trait impl method")] TraitImplVisibilityIgnored, - #[error("comptime keyword is deprecated")] - ComptimeDeprecated, #[error("This requires the unstable feature '{0}' which is not enabled")] ExperimentalFeature(UnstableFeature), #[error( @@ -115,6 +113,8 @@ pub enum ParserErrorReason { MissingSafetyComment, #[error("Missing parameters for function definition")] MissingParametersForFunctionDefinition, + #[error("`StructDefinition` is deprecated. It has been renamed to `TypeDefinition`")] + StructDefinitionDeprecated, } /// Represents a parsing error, or a parsing error in the making. @@ -254,15 +254,6 @@ impl<'a> From<&'a ParserError> for Diagnostic { diagnostic.deprecated = true; diagnostic } - ParserErrorReason::ComptimeDeprecated => { - let mut diagnostic = Diagnostic::simple_warning( - "Use of deprecated keyword 'comptime'".into(), - "The 'comptime' keyword has been deprecated. It can be removed without affecting your program".into(), - error.location(), - ) ; - diagnostic.deprecated = true; - diagnostic - } ParserErrorReason::InvalidBitSize(bit_size) => Diagnostic::simple_error( format!("Use of invalid bit size {}", bit_size), format!( @@ -315,6 +306,9 @@ impl<'a> From<&'a ParserError> for Diagnostic { let secondary = "Consider changing it to a regular `//` comment".to_string(); Diagnostic::simple_warning(primary, secondary, error.location()) } + ParserErrorReason::StructDefinitionDeprecated => { + Diagnostic::simple_warning(format!("{reason}"), String::new(), error.location()) + } other => { Diagnostic::simple_error(format!("{other}"), String::new(), error.location()) } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs index a5ea2ea5fe9e..b797a15f0c1f 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs @@ -557,6 +557,7 @@ impl<'a> Parser<'a> { ); } + #[allow(unused)] fn expected_mut_after_ampersand(&mut self) { self.push_error( ParserErrorReason::ExpectedMutAfterAmpersand { found: self.token.token().clone() }, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/expression.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/expression.rs index d0f335414da4..672d9428d9ad 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/expression.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/expression.rs @@ -79,10 +79,14 @@ impl Parser<'_> { /// UnaryOp = '&' 'mut' | '-' | '!' | '*' fn parse_unary_op(&mut self) -> Option { - if self.at(Token::Ampersand) && self.next_is(Token::Keyword(Keyword::Mut)) { - self.bump(); + if self.at(Token::Ampersand) { + let mut mutable = false; + if self.next_is(Token::Keyword(Keyword::Mut)) { + mutable = true; + self.bump(); + } self.bump(); - Some(UnaryOp::MutableReference) + Some(UnaryOp::Reference { mutable }) } else if self.eat(Token::Minus) { Some(UnaryOp::Minus) } else if self.eat(Token::Bang) { @@ -738,7 +742,7 @@ impl Parser<'_> { /// SliceExpression = '&' ArrayLiteral fn parse_slice_literal(&mut self) -> Option { - if !(self.at(Token::Ampersand) && self.next_is(Token::LeftBracket)) { + if !(self.at(Token::SliceStart) && self.next_is(Token::LeftBracket)) { return None; } @@ -1257,7 +1261,7 @@ mod tests { let ExpressionKind::Prefix(prefix) = expr.kind else { panic!("Expected prefix expression"); }; - assert!(matches!(prefix.operator, UnaryOp::MutableReference)); + assert!(matches!(prefix.operator, UnaryOp::Reference { mutable: true })); let ExpressionKind::Variable(path) = prefix.rhs.kind else { panic!("Expected variable"); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/function.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/function.rs index f10b790e63f6..caf2cdeb1c31 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/function.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/function.rs @@ -229,7 +229,7 @@ impl Parser<'_> { let mut pattern = Pattern::Identifier(ident); if self_pattern.reference { - self_type = UnresolvedTypeData::MutableReference(Box::new(self_type)) + self_type = UnresolvedTypeData::Reference(Box::new(self_type), self_pattern.mutable) .with_location(ident_location); } else if self_pattern.mutable { pattern = Pattern::Mutable(Box::new(pattern), ident_location, true); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/pattern.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/pattern.rs index 61fb1572c172..e1468845ded5 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/pattern.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/pattern.rs @@ -66,14 +66,12 @@ impl Parser<'_> { } } - if self.at(Token::Ampersand) && self.next_is(Token::Keyword(Keyword::Mut)) { - self.bump(); + if self.at(Token::Ampersand) { self.bump(); + + let mutable = self.eat_keyword(Keyword::Mut); if !self.next_is_colon() && self.eat_self() { - return Some(PatternOrSelf::SelfPattern(SelfPattern { - reference: true, - mutable: true, - })); + return Some(PatternOrSelf::SelfPattern(SelfPattern { reference: true, mutable })); } else { self.push_error( ParserErrorReason::RefMutCanOnlyBeUsedWithSelf, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/types.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/types.rs index bcbf57d863d6..638bbde866de 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/types.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/types.rs @@ -236,11 +236,14 @@ impl Parser<'_> { if self.eat_keyword(Keyword::TypedExpr) { return Some(UnresolvedTypeData::Quoted(QuotedType::TypedExpr)); } + + let location = self.current_token_location; if self.eat_keyword(Keyword::StructDefinition) { - return Some(UnresolvedTypeData::Quoted(QuotedType::StructDefinition)); + self.push_error(ParserErrorReason::StructDefinitionDeprecated, location); + return Some(UnresolvedTypeData::Quoted(QuotedType::TypeDefinition)); } - if self.eat_keyword(Keyword::EnumDefinition) { - return Some(UnresolvedTypeData::Quoted(QuotedType::EnumDefinition)); + if self.eat_keyword(Keyword::TypeDefinition) { + return Some(UnresolvedTypeData::Quoted(QuotedType::TypeDefinition)); } if self.eat_keyword(Keyword::TraitConstraint) { return Some(UnresolvedTypeData::Quoted(QuotedType::TraitConstraint)); @@ -370,15 +373,15 @@ impl Parser<'_> { } fn parses_mutable_reference_type(&mut self) -> Option { - if self.eat(Token::Ampersand) { - if !self.eat_keyword(Keyword::Mut) { - self.expected_mut_after_ampersand(); - } + // The `&` may be lexed as a slice start if this is an array or slice type + if self.eat(Token::Ampersand) || self.eat(Token::SliceStart) { + let mutable = self.eat_keyword(Keyword::Mut); - return Some(UnresolvedTypeData::MutableReference(Box::new( - self.parse_type_or_error(), - ))); - }; + return Some(UnresolvedTypeData::Reference( + Box::new(self.parse_type_or_error()), + mutable, + )); + } None } @@ -604,11 +607,21 @@ mod tests { assert!(matches!(typ.typ, UnresolvedTypeData::FieldElement)); } + #[test] + fn parses_reference_type() { + let src = "&Field"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::Reference(typ, false) = typ.typ else { + panic!("Expected a reference type") + }; + assert!(matches!(typ.typ, UnresolvedTypeData::FieldElement)); + } + #[test] fn parses_mutable_reference_type() { let src = "&mut Field"; let typ = parse_type_no_errors(src); - let UnresolvedTypeData::MutableReference(typ) = typ.typ else { + let UnresolvedTypeData::Reference(typ, true) = typ.typ else { panic!("Expected a mutable reference type") }; assert!(matches!(typ.typ, UnresolvedTypeData::FieldElement)); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/tests.rs b/noir/noir-repo/compiler/noirc_frontend/src/tests.rs index 52d5f79c5e82..f89b0960d3c2 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/tests.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/tests.rs @@ -33,9 +33,6 @@ use crate::hir::def_collector::dc_crate::DefCollector; use crate::hir::def_map::{CrateDefMap, LocalModuleId}; use crate::hir_def::expr::HirExpression; use crate::hir_def::stmt::HirStatement; -use crate::monomorphization::ast::Program; -use crate::monomorphization::errors::MonomorphizationError; -use crate::monomorphization::monomorphize; use crate::parser::{ItemKind, ParserErrorReason}; use crate::token::SecondaryAttribute; use crate::{ParsedModule, parse_program}; @@ -381,7 +378,6 @@ fn check_trait_implementation_duplicate_method() { #[test] fn check_trait_wrong_method_return_type() { - // TODO: improve the error location let src = " trait Default { fn default() -> Self; @@ -406,7 +402,6 @@ fn check_trait_wrong_method_return_type() { #[test] fn check_trait_wrong_method_return_type2() { - // TODO: improve the error location let src = " trait Default { fn default(x: Field, y: Field) -> Self; @@ -531,7 +526,6 @@ fn check_trait_wrong_method_name() { #[test] fn check_trait_wrong_parameter() { - // TODO: improve the error location let src = " trait Default { fn default(x: Field) -> Self; @@ -1127,52 +1121,6 @@ fn resolve_fmt_strings() { check_errors(src); } -fn monomorphize_program(src: &str) -> Result { - let (_program, mut context, _errors) = get_program(src); - let main_func_id = context.def_interner.find_function("main").unwrap(); - monomorphize(main_func_id, &mut context.def_interner, false) -} - -fn get_monomorphization_error(src: &str) -> Option { - monomorphize_program(src).err() -} - -fn check_rewrite(src: &str, expected: &str) { - let program = monomorphize_program(src).unwrap(); - assert!(format!("{}", program) == expected); -} - -#[test] -fn simple_closure_with_no_captured_variables() { - let src = r#" - fn main() -> pub Field { - let x = 1; - let closure = || x; - closure() - } - "#; - - let expected_rewrite = r#"fn main$f0() -> Field { - let x$0 = 1; - let closure$3 = { - let closure_variable$2 = { - let env$1 = (x$l0); - (env$l1, lambda$f1) - }; - closure_variable$l2 - }; - { - let tmp$4 = closure$l3; - tmp$l4.1(tmp$l4.0) - } -} -fn lambda$f1(mut env$l1: (Field)) -> Field { - env$l1.0 -} -"#; - check_rewrite(src, expected_rewrite); -} - #[test] fn deny_cyclic_globals() { let src = r#" @@ -1524,7 +1472,6 @@ fn numeric_generic_binary_operation_type_mismatch() { #[test] fn bool_generic_as_loop_bound() { - // TODO: improve the error location of the last error (should be just on N) let src = r#" pub fn read() { ^ N has a type of bool. The only supported numeric generic types are `u1`, `u8`, `u16`, and `u32`. @@ -1565,8 +1512,6 @@ fn numeric_generic_in_function_signature() { #[test] fn numeric_generic_as_struct_field_type_fails() { - // TODO: improve error message, in Rust it says "expected type, found const parameter `N`" - // which might be more understandable let src = r#" pub struct Foo { a: Field, @@ -1594,7 +1539,6 @@ fn normal_generic_as_array_length() { #[test] fn numeric_generic_as_param_type() { - // TODO: improve the error message, see what Rust does let src = r#" pub fn foo(x: I) -> I { ^ Expected type, found numeric generic @@ -1614,7 +1558,6 @@ fn numeric_generic_as_param_type() { #[test] fn numeric_generic_as_unused_param_type() { - // TODO: improve the error message let src = r#" pub fn foo(_x: I) { } ^ Expected type, found numeric generic @@ -1625,7 +1568,6 @@ fn numeric_generic_as_unused_param_type() { #[test] fn numeric_generic_as_unused_trait_fn_param_type() { - // TODO: improve the error message let src = r#" trait Foo { ^^^ unused trait Foo @@ -1640,7 +1582,6 @@ fn numeric_generic_as_unused_trait_fn_param_type() { #[test] fn numeric_generic_as_return_type() { - // TODO: improve the error message let src = r#" // std::mem::zeroed() without stdlib trait Zeroed { @@ -1662,7 +1603,6 @@ fn numeric_generic_as_return_type() { #[test] fn numeric_generic_used_in_nested_type_fails() { - // TODO: improve the error message let src = r#" pub struct Foo { a: Field, @@ -1679,7 +1619,6 @@ fn numeric_generic_used_in_nested_type_fails() { #[test] fn normal_generic_used_in_nested_array_length_fail() { - // TODO: improve the error message let src = r#" pub struct Foo { a: Field, @@ -2479,7 +2418,6 @@ fn bit_not_on_untyped_integer() { #[test] fn duplicate_struct_field() { - // TODO: the primary error location should be on the second field let src = r#" pub struct Foo { x: i32, @@ -4127,3 +4065,47 @@ fn deny_attaching_mut_ref_to_immutable_object() { "#; check_errors(src); } + +#[test] +fn immutable_references_with_ownership_feature() { + let src = r#" + unconstrained fn main() { + let mut array = [1, 2, 3]; + borrow(&array); + } + + fn borrow(_array: &[Field; 3]) {} + "#; + + let (_, _, errors) = get_program_using_features(src, &[UnstableFeature::Ownership]); + assert_eq!(errors.len(), 0); +} + +#[test] +fn immutable_references_without_ownership_feature() { + let src = r#" + fn main() { + let mut array = [1, 2, 3]; + borrow(&array); + ^^^^^^ This requires the unstable feature 'ownership' which is not enabled + ~~~~~~ Pass -Zownership to nargo to enable this feature at your own risk. + } + + fn borrow(_array: &[Field; 3]) {} + ^^^^^^^^^^^ This requires the unstable feature 'ownership' which is not enabled + ~~~~~~~~~~~ Pass -Zownership to nargo to enable this feature at your own risk. + "#; + check_errors(src); +} + +#[test] +fn errors_on_invalid_integer_bit_size() { + let src = r#" + fn main() { + let _: u42 = 4; + ^^^ Use of invalid bit size 42 + ~~~ Allowed bit sizes for integers are 1, 8, 16, 32, 64, 128 + } + "#; + check_errors(src); +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/tests/arithmetic_generics.rs b/noir/noir-repo/compiler/noirc_frontend/src/tests/arithmetic_generics.rs index bcbdbdd6211e..f1ef49669c3d 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/tests/arithmetic_generics.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/tests/arithmetic_generics.rs @@ -5,7 +5,8 @@ use acvm::{AcirField, FieldElement}; use crate::hir::type_check::TypeCheckError; use crate::hir_def::types::{BinaryTypeOperator, Type}; use crate::monomorphization::errors::MonomorphizationError; -use crate::tests::{assert_no_errors, get_monomorphization_error}; +use crate::monomorphization::tests::get_monomorphized; +use crate::tests::assert_no_errors; #[test] fn arithmetic_generics_canonicalization_deduplication_regression() { @@ -72,13 +73,10 @@ fn arithmetic_generics_checked_cast_zeros() { bar(w) } "#; - assert_no_errors(source); - let monomorphization_error = get_monomorphization_error(source); - assert!(monomorphization_error.is_some()); + let monomorphization_error = get_monomorphized(source).unwrap_err(); // Expect a CheckedCast (0 % 0) failure - let monomorphization_error = monomorphization_error.unwrap(); if let MonomorphizationError::UnknownArrayLength { ref length, ref err, location: _ } = monomorphization_error { @@ -117,13 +115,10 @@ fn arithmetic_generics_checked_cast_indirect_zeros() { let _ = bar(w); } "#; - assert_no_errors(source); - let monomorphization_error = get_monomorphization_error(source); - assert!(monomorphization_error.is_some()); + let monomorphization_error = get_monomorphized(source).unwrap_err(); // Expect a CheckedCast (0 % 0) failure - let monomorphization_error = monomorphization_error.unwrap(); if let MonomorphizationError::UnknownArrayLength { ref length, ref err, location: _ } = monomorphization_error { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/tests/metaprogramming.rs b/noir/noir-repo/compiler/noirc_frontend/src/tests/metaprogramming.rs index a19ef17d8353..9a660bc998bf 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/tests/metaprogramming.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/tests/metaprogramming.rs @@ -96,7 +96,7 @@ fn unquoted_integer_as_integer_token() { #[test] fn allows_references_to_structs_generated_by_macros() { let src = r#" - comptime fn make_new_struct(_s: StructDefinition) -> Quoted { + comptime fn make_new_struct(_s: TypeDefinition) -> Quoted { quote { struct Bar {} } } @@ -117,7 +117,7 @@ fn errors_if_macros_inject_functions_with_name_collisions() { // This can't be tested using `check_errors` right now because the two secondary // errors land on the same span. let src = r#" - comptime fn make_colliding_functions(_s: StructDefinition) -> Quoted { + comptime fn make_colliding_functions(_s: TypeDefinition) -> Quoted { quote { fn foo() {} } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/tests/traits.rs b/noir/noir-repo/compiler/noirc_frontend/src/tests/traits.rs index 80ce9b87002d..d2f9d9a96724 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/tests/traits.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/tests/traits.rs @@ -102,7 +102,6 @@ fn trait_inheritance_with_generics_4() { #[test] fn trait_inheritance_dependency_cycle() { - // TODO: maybe the error location should be just on Foo let src = r#" trait Foo: Bar {} ^^^ Dependency cycle found @@ -115,7 +114,6 @@ fn trait_inheritance_dependency_cycle() { #[test] fn trait_inheritance_missing_parent_implementation() { - // TODO: the secondary errors are missing a closing backtick let src = r#" pub trait Foo {} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/tests/visibility.rs b/noir/noir-repo/compiler/noirc_frontend/src/tests/visibility.rs index ee53dcbc84ff..4ccfebf578b1 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/tests/visibility.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/tests/visibility.rs @@ -519,7 +519,7 @@ fn errors_if_accessing_private_struct_member_inside_function_generated_at_compti bar_inner: Foo, } - comptime fn generate_inner_accessor(_s: StructDefinition) -> Quoted { + comptime fn generate_inner_accessor(_s: TypeDefinition) -> Quoted { quote { fn bar_get_foo_inner(x: Bar) -> Field { x.bar_inner.foo_inner diff --git a/noir/noir-repo/compiler/noirc_printable_type/src/lib.rs b/noir/noir-repo/compiler/noirc_printable_type/src/lib.rs index 6ff211cac2a7..8e25de58b439 100644 --- a/noir/noir-repo/compiler/noirc_printable_type/src/lib.rs +++ b/noir/noir-repo/compiler/noirc_printable_type/src/lib.rs @@ -49,8 +49,9 @@ pub enum PrintableType { env: Box, unconstrained: bool, }, - MutableReference { + Reference { typ: Box, + mutable: bool, }, Unit, } @@ -127,7 +128,10 @@ fn to_string(value: &PrintableValue, typ: &PrintableType) -> Op (PrintableValue::Field(_), PrintableType::Function { arguments, return_type, .. }) => { output.push_str(&format!("< {:?}>>", arguments, return_type,)); } - (_, PrintableType::MutableReference { .. }) => { + (_, PrintableType::Reference { mutable: false, .. }) => { + output.push_str("<>"); + } + (_, PrintableType::Reference { mutable: true, .. }) => { output.push_str("<>"); } (PrintableValue::Vec { array_elements, is_slice }, PrintableType::Array { typ, .. }) diff --git a/noir/noir-repo/cspell.json b/noir/noir-repo/cspell.json index 3bbbede78ccc..8093ee508166 100644 --- a/noir/noir-repo/cspell.json +++ b/noir/noir-repo/cspell.json @@ -60,6 +60,7 @@ "combinators", "compinit", "comptime", + "concat", "cpus", "cranelift", "critesjosh", @@ -173,6 +174,7 @@ "noncanonical", "nouner", "oneshot", + "oneof", "overflowing", "pedersen", "peekable", @@ -188,6 +190,11 @@ "printstd", "proptest", "proptests", + "prost", + "proto", + "protobuf", + "protoc", + "protos", "pseudocode", "pubkey", "quantile", diff --git a/noir/noir-repo/deny.toml b/noir/noir-repo/deny.toml index 48628fb00458..c2ff9aee70e2 100644 --- a/noir/noir-repo/deny.toml +++ b/noir/noir-repo/deny.toml @@ -7,8 +7,6 @@ yanked = "warn" ignore = [ "RUSTSEC-2024-0388", # derivative unmaintained - "RUSTSEC-2024-0384", # instant unmaintained - "RUSTSEC-2020-0016", # net2 unmaintained ] # This section is considered when running `cargo deny check bans`. @@ -49,7 +47,6 @@ allow = [ "BSD-3-Clause", "ISC", "0BSD", - "Unicode-DFS-2016", "Unicode-3.0", "Unlicense", "Zlib", @@ -65,20 +62,12 @@ exceptions = [ # CC0 is a permissive license but somewhat unclear status for source code # so we prefer to not have dependencies using it # https://tldrlegal.com/license/creative-commons-cc0-1.0-universal - { allow = ["CC0-1.0"], name = "more-asserts" }, { allow = ["CC0-1.0"], name = "notify" }, - { allow = ["CC0-1.0"], name = "tiny-keccak" }, { allow = ["MPL-2.0"], name = "sized-chunks" }, { allow = ["MPL-2.0"], name = "webpki-roots" }, { allow = ["CDDL-1.0"], name = "inferno" }, - { allow = ["OpenSSL"], name = "ring" }, ] -[[licenses.clarify]] -crate = "ring" -expression = "ISC" -license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] - # This section is considered when running `cargo deny check sources`. # More documentation about the 'sources' section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html diff --git a/noir/noir-repo/docs/docs/noir/concepts/comptime.md b/noir/noir-repo/docs/docs/noir/concepts/comptime.md index 9661dc1a6cad..b3b6d784fb27 100644 --- a/noir/noir-repo/docs/docs/noir/concepts/comptime.md +++ b/noir/noir-repo/docs/docs/noir/concepts/comptime.md @@ -222,7 +222,7 @@ When you use an attribute, the function with the same name will be called with t #[my_struct_attribute] struct Foo {} -comptime fn my_struct_attribute(s: StructDefinition) { +comptime fn my_struct_attribute(s: TypeDefinition) { println("Called my_struct_attribute!"); } @@ -308,10 +308,10 @@ The following is an incomplete list of some `comptime` types along with some use - Methods: - `fn parameters(self) -> [(Quoted, Type)]` - Returns a slice of `(name, type)` pairs for each parameter -- `StructDefinition`: A struct definition +- `TypeDefinition`: A struct or enum definition - Methods: - `fn as_type(self) -> Type` - - Returns this `StructDefinition` as a `Type`. Any generics are kept as-is + - Returns this `TypeDefinition` as a `Type`. Any generics are kept as-is - `fn generics(self) -> [Quoted]` - Return the name of each generic on this struct - `fn fields(self) -> [(Quoted, Type)]` diff --git a/noir/noir-repo/docs/docs/noir/concepts/data_types/arrays.md b/noir/noir-repo/docs/docs/noir/concepts/data_types/arrays.md index 289145a8c4d1..f831a5f57b84 100644 --- a/noir/noir-repo/docs/docs/noir/concepts/data_types/arrays.md +++ b/noir/noir-repo/docs/docs/noir/concepts/data_types/arrays.md @@ -255,6 +255,23 @@ fn main() { } ``` +### concat + +Concatenates this array with another array. + +```rust +fn concat(self, array2: [T; M]) -> [T; N + M] +``` + +```rust +fn main() { + let arr1 = [1, 2, 3, 4]; + let arr2 = [6, 7, 8, 9, 10, 11]; + let concatenated_arr = arr1.concat(arr2); + assert(concatenated_arr == [1, 2, 3, 4, 6, 7, 8, 9, 10, 11]); +} +``` + ### as_str_unchecked Converts a byte array of type `[u8; N]` to a string. Note that this performs no UTF-8 validation - diff --git a/noir/noir-repo/docs/docs/noir/concepts/data_types/integers.md b/noir/noir-repo/docs/docs/noir/concepts/data_types/integers.md index ff3fafa1f905..f1639fb7818c 100644 --- a/noir/noir-repo/docs/docs/noir/concepts/data_types/integers.md +++ b/noir/noir-repo/docs/docs/noir/concepts/data_types/integers.md @@ -7,13 +7,13 @@ sidebar_position: 1 An integer type is a range constrained field type. The Noir frontend supports both unsigned and signed integer types. -The allowed sizes are 1, 8, 16, 32 and 64 bits. +The allowed sizes are 1, 8, 16, 32, 64 and 128 bits. ([currently only unsigned integers for 128 bits](https://github.com/noir-lang/noir/issues/7591)) :::info When an integer is defined in Noir without a specific type, it will default to `Field`. -The one exception is for loop indices which default to `u64` since comparisons on `Field`s are not possible. +The one exception is for loop indices which default to `u32` since comparisons on `Field`s are not possible. ::: diff --git a/noir/noir-repo/docs/docs/noir/concepts/functions.md b/noir/noir-repo/docs/docs/noir/concepts/functions.md index f656cdfd97a1..9f40ea9d1d2a 100644 --- a/noir/noir-repo/docs/docs/noir/concepts/functions.md +++ b/noir/noir-repo/docs/docs/noir/concepts/functions.md @@ -34,6 +34,23 @@ is pre-pended with a colon and the parameter type. Multiple parameters are separ fn foo(x : Field, y : Field){} ``` +You can use an underscore `_` as a parameter name when you don't need to use the parameter in the function body. This is useful when you need to satisfy a function signature but don't need to use all the parameters: + +```rust +fn foo(_ : Field, y : Field) { + // Only using y parameter +} +``` + +Alternatively, you can prefix a parameter name with an underscore (e.g. `_x`), which also indicates that the parameter is unused. This approach is often preferred as it preserves the parameter name for documentation purposes: + +```rust +fn foo(_x : Field, y : Field) -> Field { + // Only using y parameter + y +} +``` + The return type of a function can be stated by using the `->` arrow notation. The function below states that the foo function must return a `Field`. If the function returns no value, then the arrow is omitted. diff --git a/noir/noir-repo/docs/docs/noir/standard_library/meta/index.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/index.md index 76daa594b1fe..4b7fe38fb902 100644 --- a/noir/noir-repo/docs/docs/noir/standard_library/meta/index.md +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/index.md @@ -45,7 +45,7 @@ comptime { #include_code derive noir_stdlib/src/meta/mod.nr rust -Attribute placed on struct definitions. +Attribute placed on type definitions. Creates a trait impl for each trait passed in as an argument. To do this, the trait must have a derive handler registered @@ -79,7 +79,7 @@ this to register their own functions to enable their traits to be derived by `derive`. Because this function requires a function as an argument which -should produce a trait impl for any given struct, users may find +should produce a trait impl for any given type definition, users may find it helpful to use a function like `std::meta::make_trait_impl` to help creating these impls. @@ -90,7 +90,7 @@ trait DoNothing { fn do_nothing(self); } -comptime fn derive_do_nothing(s: StructDefinition) -> Quoted { +comptime fn derive_do_nothing(s: TypeDefinition) -> Quoted { let typ = s.as_type(); quote { impl DoNothing for $typ { @@ -103,7 +103,7 @@ comptime fn derive_do_nothing(s: StructDefinition) -> Quoted { ``` As another example, `derive_eq` in the stdlib is used to derive the `Eq` -trait for any struct. It makes use of `make_trait_impl` to do this: +trait for any type definition. It makes use of `make_trait_impl` to do this: #include_code derive_eq noir_stdlib/src/cmp.nr rust @@ -122,7 +122,7 @@ Note that this function only works for traits which: If your trait fits these criteria then `make_trait_impl` is likely the easiest way to write your derive handler. The arguments are as follows: -- `s`: The struct to make the impl for +- `s`: The type definition to make the impl for - `trait_name`: The name of the trait to derive. E.g. `quote { Eq }`. - `function_signature`: The signature of the trait method to derive. E.g. `fn eq(self, other: Self) -> bool`. - `for_each_field`: An operation to be performed on each field. E.g. `|name| quote { (self.$name == other.$name) }`. diff --git a/noir/noir-repo/docs/docs/noir/standard_library/meta/struct_def.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/struct_def.md index 18e722af31e6..a179f6523be9 100644 --- a/noir/noir-repo/docs/docs/noir/standard_library/meta/struct_def.md +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/struct_def.md @@ -1,30 +1,30 @@ --- -title: StructDefinition +title: TypeDefinition --- -`std::meta::struct_def` contains methods on the built-in `StructDefinition` type. -This type corresponds to `struct Name { field1: Type1, ... }` items in the source program. +`std::meta::type_def` contains methods on the built-in `TypeDefinition` type. +This type corresponds to `struct Name { field1: Type1, ... }` and `enum Name { Variant1(Fields1), ... }` items in the source program. ## Methods ### add_attribute -#include_code add_attribute noir_stdlib/src/meta/struct_def.nr rust +#include_code add_attribute noir_stdlib/src/meta/type_def.nr rust -Adds an attribute to the struct. +Adds an attribute to the data type. ### add_generic -#include_code add_generic noir_stdlib/src/meta/struct_def.nr rust +#include_code add_generic noir_stdlib/src/meta/type_def.nr rust -Adds an generic to the struct. Returns the new generic type. +Adds an generic to the type. Returns the new generic type. Errors if the given generic name isn't a single identifier or if -the struct already has a generic with the same name. +the type already has a generic with the same name. This method should be used carefully, if there is existing code referring -to the struct type it may be checked before this function is called and -see the struct with the original number of generics. This method should -thus be preferred to use on code generated from other macros and structs +to the type it may be checked before this function is called and +see the type with the original number of generics. This method should +thus be preferred to use on code generated from other macros and types that are not used in function signatures. Example: @@ -33,16 +33,16 @@ Example: ### as_type -#include_code as_type noir_stdlib/src/meta/struct_def.nr rust +#include_code as_type noir_stdlib/src/meta/type_def.nr rust -Returns this struct as a type in the source program. If this struct has +Returns this type definition as a type in the source program. If this definition has any generics, the generics are also included as-is. ### generics -#include_code generics noir_stdlib/src/meta/struct_def.nr rust +#include_code generics noir_stdlib/src/meta/type_def.nr rust -Returns each generic on this struct. Each generic is represented as a tuple containing the type, +Returns each generic on this type definition. Each generic is represented as a tuple containing the type, and an optional containing the numeric type if it's a numeric generic. Example: @@ -54,7 +54,7 @@ struct Foo { baz: Baz, } -comptime fn example(foo: StructDefinition) { +comptime fn example(foo: TypeDefinition) { assert_eq(foo.generics().len(), 3); // Fails because `T` isn't in scope @@ -69,45 +69,45 @@ comptime fn example(foo: StructDefinition) { ### fields -#include_code fields noir_stdlib/src/meta/struct_def.nr rust +#include_code fields noir_stdlib/src/meta/type_def.nr rust -Returns (name, type) pairs of each field in this struct. +Returns (name, type) pairs of each field in this struct type. Any generic types used in each field type is automatically substituted with the provided generic arguments. ### fields_as_written -#include_code fields_as_written noir_stdlib/src/meta/struct_def.nr rust +#include_code fields_as_written noir_stdlib/src/meta/type_def.nr rust -Returns (name, type) pairs of each field in this struct. Each type is as-is +Returns (name, type) pairs of each field in this struct type. Each type is as-is with any generic arguments unchanged. Unless the field types are not needed, -users should generally prefer to use `StructDefinition::fields` over this +users should generally prefer to use `TypeDefinition::fields` over this function if possible. ### has_named_attribute -#include_code has_named_attribute noir_stdlib/src/meta/struct_def.nr rust +#include_code has_named_attribute noir_stdlib/src/meta/type_def.nr rust -Returns true if this struct has a custom attribute with the given name. +Returns true if this type has a custom attribute with the given name. ### module -#include_code module noir_stdlib/src/meta/struct_def.nr rust +#include_code module noir_stdlib/src/meta/type_def.nr rust -Returns the module where the struct is defined. +Returns the module where the type is defined. ### name -#include_code name noir_stdlib/src/meta/struct_def.nr rust +#include_code name noir_stdlib/src/meta/type_def.nr rust -Returns the name of this struct +Returns the name of this type -Note that the returned quoted value will be just the struct name, it will -not be the full path to the struct, nor will it include any generics. +Note that the returned quoted value will be just the type name, it will +not be the full path to the type definition, nor will it include any generics. ### set_fields -#include_code set_fields noir_stdlib/src/meta/struct_def.nr rust +#include_code set_fields noir_stdlib/src/meta/type_def.nr rust Sets the fields of this struct to the given fields list where each element is a pair of the field's name and the field's type. Expects each field name @@ -126,7 +126,7 @@ Example: #[mangle_fields] struct Foo { x: Field } -comptime fn mangle_fields(s: StructDefinition) { +comptime fn mangle_fields(s: TypeDefinition) { s.set_fields(&[ (quote { a }, quote { u32 }.as_type()), (quote { b }, quote { i8 }.as_type()), @@ -137,10 +137,10 @@ comptime fn mangle_fields(s: StructDefinition) { ## Trait Implementations ```rust -impl Eq for StructDefinition -impl Hash for StructDefinition +impl Eq for TypeDefinition +impl Hash for TypeDefinition ``` -Note that each struct is assigned a unique ID internally and this is what is used for -equality and hashing. So even structs with identical generics and fields may not +Note that each type definition is assigned a unique ID internally and this is what is used for +equality and hashing. So even type definitions with identical generics and fields may not be equal in this sense if they were originally different items in the source program. diff --git a/noir/noir-repo/docs/docs/noir/standard_library/meta/typ.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/typ.md index 455853bfea35..7925b361f9e7 100644 --- a/noir/noir-repo/docs/docs/noir/standard_library/meta/typ.md +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/typ.md @@ -81,9 +81,9 @@ If this is a slice type, return the element type of the slice. If this is a `str` type, returns the length `N` as a type. -### as_struct +### as_data_type -#include_code as_struct noir_stdlib/src/meta/typ.nr rust +#include_code as_data_type noir_stdlib/src/meta/typ.nr rust If this is a struct type, returns the struct in addition to any generic arguments on this type. diff --git a/noir/noir-repo/noir_stdlib/src/array/mod.nr b/noir/noir-repo/noir_stdlib/src/array/mod.nr index bca59d5ade06..261ea57746e5 100644 --- a/noir/noir-repo/noir_stdlib/src/array/mod.nr +++ b/noir/noir-repo/noir_stdlib/src/array/mod.nr @@ -136,6 +136,29 @@ impl [T; N] { } ret } + + /// Concatenates this array with another array. + /// + /// Example: + /// + /// ```noir + /// fn main() { + /// let arr1 = [1, 2, 3, 4]; + /// let arr2 = [6, 7, 8, 9, 10, 11]; + /// let concatenated_arr = arr1.concat(arr2); + /// assert(concatenated_arr == [1, 2, 3, 4, 6, 7, 8, 9, 10, 11]); + /// } + /// ``` + pub fn concat(self, array2: [T; M]) -> [T; N + M] { + let mut result = [crate::mem::zeroed(); N + M]; + for i in 0..N { + result[i] = self[i]; + } + for i in 0..M { + result[i + N] = array2[i]; + } + result + } } impl [T; N] @@ -232,4 +255,36 @@ mod test { fn map_empty() { assert_eq([].map(|x| x + 1), []); } + + #[test] + fn concat() { + let arr1 = [1, 2, 3, 4]; + let arr2 = [6, 7, 8, 9, 10, 11]; + let concatenated_arr = arr1.concat(arr2); + assert_eq(concatenated_arr, [1, 2, 3, 4, 6, 7, 8, 9, 10, 11]); + } + + #[test] + fn concat_zero_length_with_something() { + let arr1 = []; + let arr2 = [1]; + let concatenated_arr = arr1.concat(arr2); + assert_eq(concatenated_arr, [1]); + } + + #[test] + fn concat_something_with_zero_length() { + let arr1 = [1]; + let arr2 = []; + let concatenated_arr = arr1.concat(arr2); + assert_eq(concatenated_arr, [1]); + } + + #[test] + fn concat_zero_lengths() { + let arr1: [Field; 0] = []; + let arr2: [Field; 0] = []; + let concatenated_arr = arr1.concat(arr2); + assert_eq(concatenated_arr, []); + } } diff --git a/noir/noir-repo/noir_stdlib/src/cmp.nr b/noir/noir-repo/noir_stdlib/src/cmp.nr index 24621bb37934..3a7d1c7298d1 100644 --- a/noir/noir-repo/noir_stdlib/src/cmp.nr +++ b/noir/noir-repo/noir_stdlib/src/cmp.nr @@ -8,7 +8,7 @@ pub trait Eq { // docs:end:eq-trait // docs:start:derive_eq -comptime fn derive_eq(s: StructDefinition) -> Quoted { +comptime fn derive_eq(s: TypeDefinition) -> Quoted { let signature = quote { fn eq(_self: Self, _other: Self) -> bool }; let for_each_field = |name| quote { (_self.$name == _other.$name) }; let body = |fields| { @@ -219,7 +219,7 @@ pub trait Ord { // docs:end:ord-trait // docs:start:derive_ord -comptime fn derive_ord(s: StructDefinition) -> Quoted { +comptime fn derive_ord(s: TypeDefinition) -> Quoted { let signature = quote { fn cmp(_self: Self, _other: Self) -> std::cmp::Ordering }; let for_each_field = |name| quote { if result == std::cmp::Ordering::equal() { diff --git a/noir/noir-repo/noir_stdlib/src/default.nr b/noir/noir-repo/noir_stdlib/src/default.nr index 229e5e92003f..9f28c0562728 100644 --- a/noir/noir-repo/noir_stdlib/src/default.nr +++ b/noir/noir-repo/noir_stdlib/src/default.nr @@ -7,7 +7,7 @@ pub trait Default { } // docs:end:default-trait -comptime fn derive_default(s: StructDefinition) -> Quoted { +comptime fn derive_default(s: TypeDefinition) -> Quoted { let name = quote { Default }; let signature = quote { fn default() -> Self }; let for_each_field = |name| quote { $name: Default::default() }; diff --git a/noir/noir-repo/noir_stdlib/src/hash/mod.nr b/noir/noir-repo/noir_stdlib/src/hash/mod.nr index 7a492d373cc9..bd1c2b891798 100644 --- a/noir/noir-repo/noir_stdlib/src/hash/mod.nr +++ b/noir/noir-repo/noir_stdlib/src/hash/mod.nr @@ -139,7 +139,7 @@ pub trait Hash { } // docs:start:derive_hash -comptime fn derive_hash(s: StructDefinition) -> Quoted { +comptime fn derive_hash(s: TypeDefinition) -> Quoted { let name = quote { Hash }; let signature = quote { fn hash(_self: Self, _state: &mut H) where H: std::hash::Hasher }; let for_each_field = |name| quote { _self.$name.hash(_state); }; diff --git a/noir/noir-repo/noir_stdlib/src/meta/mod.nr b/noir/noir-repo/noir_stdlib/src/meta/mod.nr index 35ba05ba74da..425a7e5255fc 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/mod.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/mod.nr @@ -4,11 +4,11 @@ pub mod format_string; pub mod function_def; pub mod module; pub mod op; -pub mod struct_def; pub mod trait_constraint; pub mod trait_def; pub mod trait_impl; pub mod typ; +pub mod type_def; pub mod typed_expr; pub mod quoted; pub mod unresolved_type; @@ -36,20 +36,20 @@ use crate::collections::umap::UHashMap; use crate::hash::BuildHasherDefault; use crate::hash::poseidon2::Poseidon2Hasher; -// A derive function is one that given a struct definition can +// A derive function is one that given a type definition can // create us a quoted trait impl from it. -pub type DeriveFunction = fn(StructDefinition) -> Quoted; +pub type DeriveFunction = fn(TypeDefinition) -> Quoted; // We'll keep a global HANDLERS map to keep track of the derive handler for each trait comptime mut global HANDLERS: UHashMap> = UHashMap::default(); -// Given a struct and a slice of traits to derive, create trait impls for each. +// Given a type definition and a slice of traits to derive, create trait impls for each. // This function is as simple as iterating over the slice, checking if we have a trait // handler registered for the given trait, calling it, and appending the result. // docs:start:derive #[varargs] -pub comptime fn derive(s: StructDefinition, traits: [TraitDefinition]) -> Quoted { +pub comptime fn derive(s: TypeDefinition, traits: [TraitDefinition]) -> Quoted { // docs:end:derive let mut result = quote {}; @@ -79,8 +79,8 @@ pub comptime fn derive_via(t: TraitDefinition, f: DeriveFunction) { /// 1. The impl only has one function, with the signature `function_signature` /// 2. The trait itself does not have any generics. /// -/// While these assumptions are met, `make_impl` will create an impl from a StructDefinition, -/// automatically filling in the required generics from the struct, along with the where clause. +/// While these assumptions are met, `make_impl` will create an impl from a TypeDefinition, +/// automatically filling in the required generics from the type, along with the where clause. /// The function body is created by mapping each field with `for_each_field` and joining the /// results with `join_fields_with`. The result of this is passed to the `body` function for /// any final processing - e.g. wrapping each field in a `StructConstructor { .. }` expression. @@ -88,7 +88,7 @@ pub comptime fn derive_via(t: TraitDefinition, f: DeriveFunction) { /// See `derive_eq` and `derive_default` for example usage. // docs:start:make_trait_impl pub comptime fn make_trait_impl( - s: StructDefinition, + s: TypeDefinition, trait_name: Quoted, function_signature: Quoted, for_each_field: fn[Env1](Quoted) -> Quoted, @@ -161,7 +161,7 @@ mod tests { y: [Field; 2], } - comptime fn derive_field_count(s: StructDefinition) -> Quoted { + comptime fn derive_field_count(s: TypeDefinition) -> Quoted { let typ = s.as_type(); let field_count = s.fields_as_written().len(); quote { @@ -180,7 +180,7 @@ mod tests { my_field: i32, } - comptime fn assert_field_is_type(s: StructDefinition, typ: Type) { + comptime fn assert_field_is_type(s: TypeDefinition, typ: Type) { // Assert the first field in `s` has type `typ` let fields = s.fields([]); assert_eq(fields[0].1, typ); @@ -194,7 +194,7 @@ mod tests { } #[varargs] - comptime fn assert_three_args(_s: StructDefinition, args: [Field]) { + comptime fn assert_three_args(_s: TypeDefinition, args: [Field]) { assert_eq(args.len(), 3); } // docs:end:annotation-varargs-example @@ -207,7 +207,7 @@ mod tests { fn do_nothing(self); } - comptime fn derive_do_nothing(s: StructDefinition) -> Quoted { + comptime fn derive_do_nothing(s: TypeDefinition) -> Quoted { // This is simplified since we don't handle generics or where clauses! // In a real example we'd likely also need to introduce each of // `s.generics()` as well as a trait constraint for each generic @@ -230,7 +230,7 @@ mod tests { // This helper function will generate our impl for us along with any // necessary where clauses and still provides a flexible interface // for us to work on each field on the struct. - comptime fn derive_do_nothing_alt(s: StructDefinition) -> Quoted { + comptime fn derive_do_nothing_alt(s: TypeDefinition) -> Quoted { let trait_name = quote { DoNothing }; let method_signature = quote { fn do_nothing(self) }; diff --git a/noir/noir-repo/noir_stdlib/src/meta/module.nr b/noir/noir-repo/noir_stdlib/src/meta/module.nr index 0298282761a2..586b771b352a 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/module.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/module.nr @@ -21,7 +21,7 @@ impl Module { #[builtin(module_structs)] // docs:start:structs - pub comptime fn structs(self) -> [StructDefinition] {} + pub comptime fn structs(self) -> [TypeDefinition] {} // docs:end:structs #[builtin(module_name)] diff --git a/noir/noir-repo/noir_stdlib/src/meta/typ.nr b/noir/noir-repo/noir_stdlib/src/meta/typ.nr index 8076c692ca5e..a2f48dbfa004 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/typ.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/typ.nr @@ -109,11 +109,16 @@ impl Type { pub comptime fn as_str(self) -> Option {} // docs:end:as_str - /// If this is a struct type, returns the struct in addition to any generic arguments on this type. - #[builtin(type_as_struct)] - // docs:start:as_struct - pub comptime fn as_struct(self) -> Option<(StructDefinition, [Type])> {} - // docs:end:as_struct + #[deprecated("This method has been renamed to `as_data_type`")] + pub comptime fn as_struct(self) -> Option<(TypeDefinition, [Type])> { + self.as_data_type() + } + + /// If this is a struct or enum type, returns the type in addition to any generic arguments on this type. + #[builtin(type_as_data_type)] + // docs:start:as_data_type + pub comptime fn as_data_type(self) -> Option<(TypeDefinition, [Type])> {} + // docs:end:as_data_type /// If this is a tuple type, returns each element type of the tuple. #[builtin(type_as_tuple)] diff --git a/noir/noir-repo/noir_stdlib/src/meta/struct_def.nr b/noir/noir-repo/noir_stdlib/src/meta/type_def.nr similarity index 61% rename from noir/noir-repo/noir_stdlib/src/meta/struct_def.nr rename to noir/noir-repo/noir_stdlib/src/meta/type_def.nr index d561e326fbd5..0e39a2890d05 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/struct_def.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/type_def.nr @@ -1,89 +1,89 @@ use crate::option::Option; -impl StructDefinition { - #[builtin(struct_def_add_attribute)] +impl TypeDefinition { + #[builtin(type_def_add_attribute)] // docs:start:add_attribute pub comptime fn add_attribute(self, attribute: str) {} // docs:end:add_attribute - #[builtin(struct_def_add_generic)] + #[builtin(type_def_add_generic)] // docs:start:add_generic pub comptime fn add_generic(self, generic_name: str) -> Type {} // docs:end:add_generic - /// Return a syntactic version of this struct definition as a type. + /// Return a syntactic version of this type definition as a type. /// For example, `as_type(quote { type Foo { ... } })` would return `Foo` - #[builtin(struct_def_as_type)] + #[builtin(type_def_as_type)] // docs:start:as_type pub comptime fn as_type(self) -> Type {} // docs:end:as_type - #[builtin(struct_def_has_named_attribute)] + #[builtin(type_def_has_named_attribute)] // docs:start:has_named_attribute pub comptime fn has_named_attribute(self, name: str) -> bool {} // docs:end:has_named_attribute - /// Return (type, option) pairs of each generic in this struct definition. + /// Return (type, option) pairs of each generic in this type definition. /// If a generic is numeric, the second element of the pair will contain the numeric type. - #[builtin(struct_def_generics)] + #[builtin(type_def_generics)] // docs:start:generics pub comptime fn generics(self) -> [(Type, Option)] {} // docs:end:generics - /// Returns (name, type) pairs of each field in this struct. + /// Returns (name, type) pairs of each field in this type. /// Any generic types used in each field type is automatically substituted with the /// provided generic arguments. - #[builtin(struct_def_fields)] + #[builtin(type_def_fields)] // docs:start:fields pub comptime fn fields(self, generic_args: [Type]) -> [(Quoted, Type)] {} // docs:end:fields - /// Returns (name, type) pairs of each field in this struct. Each type is as-is + /// Returns (name, type) pairs of each field in this type. Each type is as-is /// with any generic arguments unchanged. Unless the field types are not needed, - /// users should generally prefer to use `StructDefinition::fields` over this + /// users should generally prefer to use `TypeDefinition::fields` over this /// function if possible. - #[builtin(struct_def_fields_as_written)] + #[builtin(type_def_fields_as_written)] // docs:start:fields_as_written pub comptime fn fields_as_written(self) -> [(Quoted, Type)] {} // docs:end:fields_as_written - #[builtin(struct_def_module)] + #[builtin(type_def_module)] // docs:start:module pub comptime fn module(self) -> Module {} // docs:end:module - #[builtin(struct_def_name)] + #[builtin(type_def_name)] // docs:start:name pub comptime fn name(self) -> Quoted {} // docs:end:name - /// Sets the fields of this struct to the given fields list. - /// All existing fields of the struct will be overridden with the given fields. + /// Sets the fields of this type to the given fields list. + /// All existing fields of the type will be overridden with the given fields. /// Each element of the fields list corresponds to the name and type of a field. /// Each name is expected to be a single identifier. - #[builtin(struct_def_set_fields)] + #[builtin(type_def_set_fields)] // docs:start:set_fields pub comptime fn set_fields(self, new_fields: [(Quoted, Type)]) {} // docs:end:set_fields } -impl crate::hash::Hash for StructDefinition { +impl crate::hash::Hash for TypeDefinition { comptime fn hash(self, state: &mut H) where H: crate::hash::Hasher, { - state.write(struct_def_hash(self)) + state.write(type_def_hash(self)) } } -impl crate::cmp::Eq for StructDefinition { +impl crate::cmp::Eq for TypeDefinition { comptime fn eq(self, other: Self) -> bool { - struct_def_eq(self, other) + type_def_eq(self, other) } } -#[builtin(struct_def_eq)] -comptime fn struct_def_eq(_first: StructDefinition, _second: StructDefinition) -> bool {} +#[builtin(type_def_eq)] +comptime fn type_def_eq(_first: TypeDefinition, _second: TypeDefinition) -> bool {} -#[builtin(struct_def_hash)] -comptime fn struct_def_hash(_struct: StructDefinition) -> Field {} +#[builtin(type_def_hash)] +comptime fn type_def_hash(_type: TypeDefinition) -> Field {} diff --git a/noir/noir-repo/scripts/bytecode-sizes/README.md b/noir/noir-repo/scripts/bytecode-sizes/README.md new file mode 100644 index 000000000000..d860e8434281 --- /dev/null +++ b/noir/noir-repo/scripts/bytecode-sizes/README.md @@ -0,0 +1,41 @@ +# Bytecode Size Comparison + +These scripts can be used to compare the bytecode size of circuits in `aztec-packages` between two different versions of `nargo`. +For example we can see what happens if we change the serialization format from `bincode` to `protobuf` in https://github.com/noir-lang/noir/pull/7513 + +## Compiling contracts + +Run these commands to compile Noir protocol circuits and contracts in `aztec-packages` after rebuilding `nargo`: + +```shell +cargo build -p nargo_cli --release +./target/release/nargo --program-dir ../aztec-packages/noir-projects/noir-protocol-circuits compile --force --silence-warnings --skip-underconstrained-check +./target/release/nargo --program-dir ../aztec-packages/noir-projects/noir-contracts compile --force --silence-warnings --skip-underconstrained-check +``` + +## Baseline + +Record the baseline bytecode size with before switching to other implementations: +```shell +./scripts/bytecode-sizes/print-bytecode-size.sh ../aztec-packages > ./scripts/bytecode-sizes/baseline.jsonl +``` + +## Alternative + +After making some changes to `nargo`, compile the contracts again with the commands above, then run the following +commands to record a new measurement, and compare it against the baseline recorded earlier. + +```shell +BASELINE=baseline +ALTERNATIVE=alternative +./scripts/bytecode-sizes/print-bytecode-size.sh ../aztec-packages \ + > ./scripts/bytecode-sizes/$ALTERNATIVE.jsonl +./scripts/bytecode-sizes/compare-bytecode-size.sh \ + ./scripts/bytecode-sizes/$BASELINE.jsonl \ + ./scripts/bytecode-sizes/$ALTERNATIVE.jsonl \ + > ./scripts/bytecode-sizes/$BASELINE-vs-$ALTERNATIVE.jsonl +./scripts/bytecode-sizes/plot-bytecode-size.sh \ + ./scripts/bytecode-sizes/$BASELINE-vs-$ALTERNATIVE.jsonl +``` + +You can look at the impact in `./scripts/bytecode-sizes/$BASELINE-vs-$ALTERNATIVE.png`. \ No newline at end of file diff --git a/noir/noir-repo/scripts/bytecode-sizes/bytecode-size-scatter.plt b/noir/noir-repo/scripts/bytecode-sizes/bytecode-size-scatter.plt new file mode 100644 index 000000000000..713d66db8b88 --- /dev/null +++ b/noir/noir-repo/scripts/bytecode-sizes/bytecode-size-scatter.plt @@ -0,0 +1,9 @@ +set term png size 1200,800; +set output FILEOUT; +unset key; +set title NAME; +set logscale x; +set xlabel "Base Bytecode Size (Log)"; +set ylabel "Alt Bytecode Ratio"; + +plot FILEIN using 2:4 with points; diff --git a/noir/noir-repo/scripts/bytecode-sizes/compare-bytecode-size.sh b/noir/noir-repo/scripts/bytecode-sizes/compare-bytecode-size.sh new file mode 100755 index 000000000000..12c0bdace38f --- /dev/null +++ b/noir/noir-repo/scripts/bytecode-sizes/compare-bytecode-size.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -eu + +IN1=$1 +IN2=$2 + +jq --slurp -c ' +. as $top | +($top[] | select(.encoding == "base") | .data[]) as $base | +($top[] | select(.encoding == "alt") | .data[] | select(.name == $base.name)) as $alt | +{ + name: $base.name, + base_size: $base.bytecode_size, + alt_size: $alt.bytecode_size, + ratio: ($alt.bytecode_size / $base.bytecode_size) +} +' \ + <(cat $IN1 | jq --slurp '{encoding: "base", data: .}') \ + <(cat $IN2 | jq --slurp '{encoding: "alt", data: .}') \ diff --git a/noir/noir-repo/scripts/bytecode-sizes/plot-bytecode-size.sh b/noir/noir-repo/scripts/bytecode-sizes/plot-bytecode-size.sh new file mode 100755 index 000000000000..39d62876171f --- /dev/null +++ b/noir/noir-repo/scripts/bytecode-sizes/plot-bytecode-size.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -eu + +IN=$1 +NAME=$(basename $IN .jsonl) +DAT=$(dirname $IN)/$NAME.dat +PNG=$(dirname $IN)/$NAME.png +PLT=$(dirname $0)/bytecode-size-scatter.plt + +cat $IN | jq -r '[.name, .base_size, .alt_size, .ratio] | @tsv' > $DAT + +gnuplot \ + -e "NAME='$(echo $NAME | tr _ - )'" \ + -e "FILEIN='$DAT'" \ + -e "FILEOUT='$PNG'" \ + $PLT + +rm $DAT diff --git a/noir/noir-repo/scripts/bytecode-sizes/print-bytecode-size.sh b/noir/noir-repo/scripts/bytecode-sizes/print-bytecode-size.sh new file mode 100755 index 000000000000..8b68c5907efb --- /dev/null +++ b/noir/noir-repo/scripts/bytecode-sizes/print-bytecode-size.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -eu + +AZTEC_PACKAGES_DIR=$1 + +for file in $AZTEC_PACKAGES_DIR/noir-projects/noir-protocol-circuits/target/*.json; do + PROGRAM=$(basename $file .json) + cat $file \ + | jq --arg PROGRAM $PROGRAM \ + -c '{name: $PROGRAM, bytecode_size: .bytecode | @base64d | length}' +done + +for file in $AZTEC_PACKAGES_DIR/noir-projects/noir-contracts/target/*.json; do + CONTRACT=$(basename $file .json) + cat $file \ + | jq --arg CONTRACT $CONTRACT \ + -c '.functions | sort_by(.name) | .[] | {name: ($CONTRACT + "::" + .name), "bytecode_size": .bytecode | @base64d | length}' +done diff --git a/noir/noir-repo/test_programs/compile_failure/type_definition_annotation/src/main.nr b/noir/noir-repo/test_programs/compile_failure/type_definition_annotation/src/main.nr index d4fef84442d0..91f9c3a52f41 100644 --- a/noir/noir-repo/test_programs/compile_failure/type_definition_annotation/src/main.nr +++ b/noir/noir-repo/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: StructDefinition) { +comptime fn fail_assert(_typ: TypeDefinition) { assert(false); } diff --git a/noir/noir-repo/test_programs/compile_success_empty/attribute_args/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/attribute_args/src/main.nr index 29690ba36c77..d8ba7b167e02 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/attribute_args/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/attribute_args/src/main.nr @@ -3,7 +3,7 @@ #[attr_with_varargs(1, 2, 3, 4)] pub struct Foo {} -comptime fn attr_with_args(s: StructDefinition, a: Field, b: Field) { +comptime fn attr_with_args(s: TypeDefinition, a: Field, b: Field) { // Ensure all variables are in scope. // We can't print them since that breaks the test runner. let _ = s; @@ -12,7 +12,7 @@ comptime fn attr_with_args(s: StructDefinition, a: Field, b: Field) { } #[varargs] -comptime fn attr_with_varargs(s: StructDefinition, t: [Field]) { +comptime fn attr_with_varargs(s: TypeDefinition, t: [Field]) { let _ = s; for _ in t {} assert(t.len() < 5); diff --git a/noir/noir-repo/test_programs/compile_success_empty/attribute_order/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/attribute_order/src/main.nr index 9d5ba32b58ed..b834526a243b 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/attribute_order/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/attribute_order/src/main.nr @@ -13,7 +13,7 @@ pub comptime fn assert_run_order_function(_f: FunctionDefinition, order: Field) attributes_run += 1; } -pub comptime fn assert_run_order_struct(_s: StructDefinition, order: Field) { +pub comptime fn assert_run_order_struct(_s: TypeDefinition, order: Field) { assert_eq(order, attributes_run); attributes_run += 1; } diff --git a/noir/noir-repo/test_programs/compile_success_empty/attributes_struct/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/attributes_struct/src/main.nr index 713e6321f4b9..3f9dca12c8c1 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/attributes_struct/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/attributes_struct/src/main.nr @@ -13,7 +13,7 @@ fn main() {} #[add_attribute] pub struct Foo {} -comptime fn add_attribute(s: StructDefinition) { +comptime fn add_attribute(s: TypeDefinition) { assert(!s.has_named_attribute("foo")); s.add_attribute("foo"); assert(s.has_named_attribute("foo")); diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_function_definition/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_function_definition/src/main.nr index 57bcd2ba90ec..59b0ee70f7a3 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/comptime_function_definition/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_function_definition/src/main.nr @@ -208,3 +208,33 @@ mod test_as_typed_expr_4 { } } } + +mod test_as_typed_expr_5 { + trait Trait {} + + impl Trait for i32 {} + + trait Packable { + fn pack(self); + } + + pub struct Foo {} + + impl Packable<10> for Foo + where + T: Trait, + { + fn pack(self) {} + } + + fn foo() { + comptime { + let foo = quote { Foo }.as_type(); + let t = quote { Packable<10> }.as_trait_constraint(); + let _ = foo.get_trait_impl(t).unwrap().methods().filter(|method| { + method.name() == quote { pack } + })[0] + .as_typed_expr(); + } + } +} diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_quoted/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/comptime_quoted/Nargo.toml new file mode 100644 index 000000000000..67172a1cc996 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_quoted/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "comptime_quoted" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_quoted/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_quoted/src/main.nr new file mode 100644 index 000000000000..a620d66d86a3 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_quoted/src/main.nr @@ -0,0 +1,8 @@ +fn main() { + comptime { + let array = quote { [1, 2, 3] }.as_expr().unwrap(); + let expr1 = quote { [1, 2, 3]}; + let expr2 = quote { $array }; + assert_eq(expr1, expr2); + } +} diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_struct_definition/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_struct_definition/src/main.nr index 41ba76171a8b..0496d59b570d 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/comptime_struct_definition/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_struct_definition/src/main.nr @@ -9,14 +9,14 @@ pub struct I32AndField { z: i8, } -comptime fn my_comptime_fn(typ: StructDefinition) { +comptime fn my_comptime_fn(typ: TypeDefinition) { let _ = typ.as_type(); assert_eq(typ.generics().len(), 3); assert_eq(typ.fields_as_written().len(), 2); assert_eq(typ.name(), quote { MyType }); } -comptime fn mutate_struct_fields(s: StructDefinition) { +comptime fn mutate_struct_fields(s: TypeDefinition) { let fields = &[(quote {x}, quote {i32}.as_type()), (quote {y}, quote {Field}.as_type())]; s.set_fields(fields); } @@ -25,7 +25,7 @@ mod foo { #[attr] pub struct Foo {} - comptime fn attr(s: StructDefinition) { + comptime fn attr(s: TypeDefinition) { assert_eq(s.module().name(), quote { foo }); } @@ -33,7 +33,7 @@ mod foo { pub struct Bar {} // docs:start:add-generic-example - comptime fn add_generic(s: StructDefinition) { + comptime fn add_generic(s: TypeDefinition) { assert_eq(s.generics().len(), 0); let new_generic = s.add_generic("T"); @@ -49,7 +49,7 @@ mod foo { fn main() { comptime { let typ = quote { MyType }.as_type(); - let (struct_def, generics) = typ.as_struct().unwrap(); + let (struct_def, generics) = typ.as_data_type().unwrap(); let fields = struct_def.fields(generics); assert_eq(fields.len(), 2); diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_type/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_type/src/main.nr index 6a2453ff0f29..9e1a50aca454 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/comptime_type/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_type/src/main.nr @@ -94,12 +94,12 @@ fn main() { let bool_type = type_of(yes); assert(bool_type.is_bool()); - // Check Type::as_struct - assert(u8_type.as_struct().is_none()); + // Check Type::as_data_type + assert(u8_type.as_data_type().is_none()); let foo = Foo { x: 0 }; let foo_type = type_of(foo); - let (struct_definition, generics) = foo_type.as_struct().unwrap(); + let (struct_definition, generics) = foo_type.as_data_type().unwrap(); let fields = struct_definition.fields(generics); assert_eq(fields.len(), 1); diff --git a/noir/noir-repo/test_programs/compile_success_empty/derive_impl/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/derive_impl/src/main.nr index fe7a7140280e..97a5eb3cd92a 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/derive_impl/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/derive_impl/src/main.nr @@ -1,4 +1,4 @@ -comptime fn derive_default(typ: StructDefinition) -> Quoted { +comptime fn derive_default(typ: TypeDefinition) -> Quoted { let generics = typ.generics(); assert_eq( generics.len(), diff --git a/noir/noir-repo/test_programs/compile_success_empty/regression_5671/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/regression_5671/src/main.nr index 2bac98ef7c42..23a532b66078 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/regression_5671/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/regression_5671/src/main.nr @@ -4,7 +4,7 @@ struct MyOtherStruct { field2: B, } -comptime fn foo(_s: StructDefinition) -> Quoted { +comptime fn foo(_s: TypeDefinition) -> Quoted { quote { impl Eq for MyOtherStruct where A: Eq, B: Eq { fn eq(self, other: Self) -> bool { diff --git a/noir/noir-repo/test_programs/compile_success_empty/unquote_multiple_items_from_annotation/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/unquote_multiple_items_from_annotation/src/main.nr index 591c03de9053..8cb44dc9f4e6 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/unquote_multiple_items_from_annotation/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/unquote_multiple_items_from_annotation/src/main.nr @@ -6,7 +6,7 @@ fn main() { assert_eq(TWO, 2); } -comptime fn foo(_: StructDefinition) -> Quoted { +comptime fn foo(_: TypeDefinition) -> Quoted { quote { global ONE: Field = 1; global TWO: u32 = 2; diff --git a/noir/noir-repo/test_programs/compile_success_empty/use_callers_scope/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/use_callers_scope/src/main.nr index 26429997f0e0..5b2f8e4b5817 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/use_callers_scope/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/use_callers_scope/src/main.nr @@ -8,7 +8,7 @@ fn main() {} mod bar { #[use_callers_scope] - pub comptime fn struct_attr(_: StructDefinition) { + pub comptime fn struct_attr(_: TypeDefinition) { let _ = quote { Bar }.as_type(); } diff --git a/noir/noir-repo/test_programs/execution_success/derive/src/main.nr b/noir/noir-repo/test_programs/execution_success/derive/src/main.nr index f7d4f6b607ac..76c4d79d8feb 100644 --- a/noir/noir-repo/test_programs/execution_success/derive/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/derive/src/main.nr @@ -1,4 +1,4 @@ -use std::hash::Hash; +use std::hash::{Hash, Hasher}; #[derive_via(derive_do_nothing)] trait DoNothing { @@ -10,7 +10,7 @@ struct MyStruct { my_field: u32, } -comptime fn derive_do_nothing(s: StructDefinition) -> Quoted { +comptime fn derive_do_nothing(s: TypeDefinition) -> Quoted { let typ = s.as_type(); let generics = s.generics().map(|g| quote { $g }).join(quote {,}); quote { diff --git a/noir/noir-repo/test_programs/execution_success/embedded_curve_ops/src/main.nr b/noir/noir-repo/test_programs/execution_success/embedded_curve_ops/src/main.nr index 85cf60dc7967..dcccdfd16fb7 100644 --- a/noir/noir-repo/test_programs/execution_success/embedded_curve_ops/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/embedded_curve_ops/src/main.nr @@ -1,3 +1,5 @@ +use std::ops::Add; + fn main(priv_key: Field, pub_x: pub Field, pub_y: pub Field) { let g1_y = 17631683881184975370165255887551781615748388533673675138860; let g1 = std::embedded_curve_ops::EmbeddedCurvePoint { x: 1, y: g1_y, is_infinite: false }; diff --git a/noir/noir-repo/test_programs/execution_success/inline_decompose_hint_brillig_call/src/main.nr b/noir/noir-repo/test_programs/execution_success/inline_decompose_hint_brillig_call/src/main.nr index e500f0f976d9..a5d57d0596d7 100644 --- a/noir/noir-repo/test_programs/execution_success/inline_decompose_hint_brillig_call/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/inline_decompose_hint_brillig_call/src/main.nr @@ -1,4 +1,7 @@ -use std::embedded_curve_ops::{EmbeddedCurvePoint, EmbeddedCurveScalar, fixed_base_scalar_mul}; +use std::{ + embedded_curve_ops::{EmbeddedCurvePoint, EmbeddedCurveScalar, fixed_base_scalar_mul}, + ops::Add, +}; fn main() -> pub Field { let pre_address = 0x23d95e303879a5d0bbef78ecbc335e559da37431f6dcd11da54ed375c2846813; diff --git a/noir/noir-repo/test_programs/execution_success/regression_7323/Nargo.toml b/noir/noir-repo/test_programs/execution_success/regression_7323/Nargo.toml new file mode 100644 index 000000000000..588887df5bb7 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/regression_7323/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "regression_7323" +type = "bin" +authors = [""] + +[dependencies] diff --git a/noir/noir-repo/test_programs/execution_success/regression_7323/Prover.toml b/noir/noir-repo/test_programs/execution_success/regression_7323/Prover.toml new file mode 100644 index 000000000000..fed79d05f4a4 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/regression_7323/Prover.toml @@ -0,0 +1 @@ +x = 5 diff --git a/noir/noir-repo/test_programs/execution_success/regression_7323/src/main.nr b/noir/noir-repo/test_programs/execution_success/regression_7323/src/main.nr new file mode 100644 index 000000000000..9a7bcd6f9416 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/regression_7323/src/main.nr @@ -0,0 +1,11 @@ +// This program lead to panics previously due to the compiler lowering it to multiple blocks +// which all jumped to the same end block. It runs now due to the compiler lowering to the +// equivalent of a nested series of if-else instead. +fn main(x: Field) { + match x { + 1 => panic(f"Branch 1 should not be taken"), + 2 => panic(f"Branch 2 should not be taken"), + 3 => panic(f"Branch 3 should not be taken"), + _ => (), + } +} diff --git a/noir/noir-repo/test_programs/execution_success/traits_in_crates_1/crate1/src/lib.nr b/noir/noir-repo/test_programs/execution_success/traits_in_crates_1/crate1/src/lib.nr index e36a263093ae..d472755e1fda 100644 --- a/noir/noir-repo/test_programs/execution_success/traits_in_crates_1/crate1/src/lib.nr +++ b/noir/noir-repo/test_programs/execution_success/traits_in_crates_1/crate1/src/lib.nr @@ -1,4 +1,4 @@ -trait MyTrait { +pub trait MyTrait { fn Add10(&mut self); } diff --git a/noir/noir-repo/test_programs/execution_success/traits_in_crates_1/crate2/src/lib.nr b/noir/noir-repo/test_programs/execution_success/traits_in_crates_1/crate2/src/lib.nr index c59bf0387c18..8f8df053f80b 100644 --- a/noir/noir-repo/test_programs/execution_success/traits_in_crates_1/crate2/src/lib.nr +++ b/noir/noir-repo/test_programs/execution_success/traits_in_crates_1/crate2/src/lib.nr @@ -1,3 +1,3 @@ -struct MyStruct { - Q: Field, +pub struct MyStruct { + pub Q: Field, } diff --git a/noir/noir-repo/test_programs/execution_success/traits_in_crates_1/src/main.nr b/noir/noir-repo/test_programs/execution_success/traits_in_crates_1/src/main.nr index 2afec29ee1f1..9f2768fc9bf6 100644 --- a/noir/noir-repo/test_programs/execution_success/traits_in_crates_1/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/traits_in_crates_1/src/main.nr @@ -1,3 +1,5 @@ +use crate1::MyTrait; + fn main(x: Field, y: pub Field) { let mut V = crate2::MyStruct { Q: x }; V.Add10(); diff --git a/noir/noir-repo/test_programs/execution_success/traits_in_crates_2/crate1/src/lib.nr b/noir/noir-repo/test_programs/execution_success/traits_in_crates_2/crate1/src/lib.nr index 59a28a50c79b..72cac406138b 100644 --- a/noir/noir-repo/test_programs/execution_success/traits_in_crates_2/crate1/src/lib.nr +++ b/noir/noir-repo/test_programs/execution_success/traits_in_crates_2/crate1/src/lib.nr @@ -1,3 +1,3 @@ -trait MyTrait { +pub trait MyTrait { fn Add10(&mut self); } diff --git a/noir/noir-repo/test_programs/execution_success/traits_in_crates_2/crate2/src/lib.nr b/noir/noir-repo/test_programs/execution_success/traits_in_crates_2/crate2/src/lib.nr index fe6a94a4a954..e80ff59474fc 100644 --- a/noir/noir-repo/test_programs/execution_success/traits_in_crates_2/crate2/src/lib.nr +++ b/noir/noir-repo/test_programs/execution_success/traits_in_crates_2/crate2/src/lib.nr @@ -1,5 +1,5 @@ -struct MyStruct { - Q: Field, +pub struct MyStruct { + pub Q: Field, } impl crate1::MyTrait for MyStruct { diff --git a/noir/noir-repo/test_programs/execution_success/traits_in_crates_2/src/main.nr b/noir/noir-repo/test_programs/execution_success/traits_in_crates_2/src/main.nr index 2afec29ee1f1..9f2768fc9bf6 100644 --- a/noir/noir-repo/test_programs/execution_success/traits_in_crates_2/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/traits_in_crates_2/src/main.nr @@ -1,3 +1,5 @@ +use crate1::MyTrait; + fn main(x: Field, y: pub Field) { let mut V = crate2::MyStruct { Q: x }; V.Add10(); diff --git a/noir/noir-repo/test_programs/rebuild.sh b/noir/noir-repo/test_programs/rebuild.sh index bf67a8942912..526b90caa3df 100755 --- a/noir/noir-repo/test_programs/rebuild.sh +++ b/noir/noir-repo/test_programs/rebuild.sh @@ -47,7 +47,8 @@ process_dir() { export -f process_dir -excluded_dirs=("workspace" "workspace_default_member") +# Reactive `regression_7323` once enums are ready +excluded_dirs=("workspace" "workspace_default_member" "regression_7323") current_dir=$(pwd) base_path="$current_dir/execution_success" dirs_to_process=() diff --git a/noir/noir-repo/tooling/acvm_cli/src/cli/execute_cmd.rs b/noir/noir-repo/tooling/acvm_cli/src/cli/execute_cmd.rs index c1d4533230e7..ad9ba9704d13 100644 --- a/noir/noir-repo/tooling/acvm_cli/src/cli/execute_cmd.rs +++ b/noir/noir-repo/tooling/acvm_cli/src/cli/execute_cmd.rs @@ -38,6 +38,10 @@ pub(crate) struct ExecuteCommand { #[clap(long, short, action)] print: bool, + /// JSON RPC url to resolve oracle calls + #[clap(long)] + oracle_resolver: Option, + /// Use pedantic ACVM solving, i.e. double-check some black-box function /// assumptions when solving. /// This is disabled by default. @@ -48,8 +52,12 @@ pub(crate) struct ExecuteCommand { fn run_command(args: ExecuteCommand) -> Result { let bytecode = read_bytecode_from_file(&args.working_directory, &args.bytecode)?; let input_witness = read_witness_from_file(&args.working_directory.join(&args.input_witness))?; - let output_witness = - execute_program_from_witness(input_witness, &bytecode, args.pedantic_solving)?; + let output_witness = execute_program_from_witness( + input_witness, + &bytecode, + args.pedantic_solving, + args.oracle_resolver, + )?; assert_eq!(output_witness.length(), 1, "ACVM CLI only supports a witness stack of size 1"); let output_witness_string = create_output_witness_string( &output_witness.peek().expect("Should have a witness stack item").witness, @@ -77,6 +85,7 @@ pub(crate) fn execute_program_from_witness( inputs_map: WitnessMap, bytecode: &[u8], pedantic_solving: bool, + resolver_url: Option, ) -> Result, CliError> { let program: Program = Program::deserialize_program(bytecode).map_err(CliError::CircuitDeserializationError)?; @@ -88,6 +97,7 @@ pub(crate) fn execute_program_from_witness( &mut DefaultForeignCallBuilder { output: PrintOutput::Stdout, enable_mocks: false, + resolver_url, ..Default::default() } .build(), diff --git a/noir/noir-repo/tooling/debugger/ignored-tests.txt b/noir/noir-repo/tooling/debugger/ignored-tests.txt index e0548fe1e1a4..1515e58a90fd 100644 --- a/noir/noir-repo/tooling/debugger/ignored-tests.txt +++ b/noir/noir-repo/tooling/debugger/ignored-tests.txt @@ -5,5 +5,6 @@ macros reference_counts references regression_4709 +regression_7323 reference_only_used_as_alias brillig_rc_regression_6123 diff --git a/noir/noir-repo/tooling/lsp/src/requests/code_action/tests.rs b/noir/noir-repo/tooling/lsp/src/requests/code_action/tests.rs index 23026d16d943..35d19324020a 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/code_action/tests.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/code_action/tests.rs @@ -1,6 +1,9 @@ #![cfg(test)] -use crate::{notifications::on_did_open_text_document, test_utils, tests::apply_text_edits}; +use crate::{ + notifications::on_did_open_text_document, test_utils, tests::apply_text_edits, + utils::get_cursor_line_and_column, +}; use lsp_types::{ CodeActionContext, CodeActionOrCommand, CodeActionParams, CodeActionResponse, @@ -10,16 +13,12 @@ use lsp_types::{ use super::on_code_action_request; -async fn get_code_action(src: &str) -> CodeActionResponse { +/// Given a string with ">|<" (cursor) in it, returns all code actions that are available +/// at that position together with the string with ">|<" removed. +async fn get_code_action(src: &str) -> (CodeActionResponse, String) { let (mut state, noir_text_document) = test_utils::init_lsp_server("document_symbol").await; - let (line, column) = src - .lines() - .enumerate() - .find_map(|(line_index, line)| line.find(">|<").map(|char_index| (line_index, char_index))) - .expect("Expected to find one >|< in the source code"); - - let src = src.replace(">|<", ""); + let (line, column, src) = get_cursor_line_and_column(src); on_did_open_text_document( &mut state, @@ -35,7 +34,7 @@ async fn get_code_action(src: &str) -> CodeActionResponse { let position = Position { line: line as u32, character: column as u32 }; - on_code_action_request( + let response = on_code_action_request( &mut state, CodeActionParams { text_document: TextDocumentIdentifier { uri: noir_text_document }, @@ -47,11 +46,12 @@ async fn get_code_action(src: &str) -> CodeActionResponse { ) .await .expect("Could not execute on_code_action_request") - .expect("Expected to get a CodeActionResponse, got None") + .expect("Expected to get a CodeActionResponse, got None"); + (response, src) } pub(crate) async fn assert_code_action(title: &str, src: &str, expected: &str) { - let actions = get_code_action(src).await; + let (actions, src) = get_code_action(src).await; let action = actions .iter() .filter_map(|action| { diff --git a/noir/noir-repo/tooling/lsp/src/requests/completion.rs b/noir/noir-repo/tooling/lsp/src/requests/completion.rs index 9d11aba95349..65207b4d95d6 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/completion.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/completion.rs @@ -579,7 +579,7 @@ impl<'a> NodeFinder<'a> { Type::DataType(struct_type, generics) => { self.complete_struct_fields(&struct_type.borrow(), generics, prefix, self_prefix); } - Type::MutableReference(typ) => { + Type::Reference(typ, _) => { return self.complete_type_fields_and_methods( typ, prefix, diff --git a/noir/noir-repo/tooling/lsp/src/requests/completion/builtins.rs b/noir/noir-repo/tooling/lsp/src/requests/completion/builtins.rs index ce5b5f35f461..430e97d3ad14 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/completion/builtins.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/completion/builtins.rs @@ -166,10 +166,11 @@ pub(super) fn keyword_builtin_type(keyword: &Keyword) -> Option<&'static str> { Keyword::FunctionDefinition => Some("FunctionDefinition"), Keyword::Module => Some("Module"), Keyword::Quoted => Some("Quoted"), - Keyword::StructDefinition => Some("StructDefinition"), + Keyword::StructDefinition => Some("TypeDefinition"), Keyword::TraitConstraint => Some("TraitConstraint"), Keyword::TraitDefinition => Some("TraitDefinition"), Keyword::TraitImpl => Some("TraitImpl"), + Keyword::TypeDefinition => Some("TypeDefinition"), Keyword::TypedExpr => Some("TypedExpr"), Keyword::TypeType => Some("Type"), Keyword::UnresolvedType => Some("UnresolvedType"), @@ -283,6 +284,7 @@ pub(super) fn keyword_builtin_function(keyword: &Keyword) -> Option { if let RequestedItems::OnlyAttributeFunctions(target) = requested_items { match target { AttributeTarget::Module => Some(Type::Quoted(QuotedType::Module)), - AttributeTarget::Struct => Some(Type::Quoted(QuotedType::StructDefinition)), - AttributeTarget::Enum => Some(Type::Quoted(QuotedType::EnumDefinition)), + AttributeTarget::Struct => Some(Type::Quoted(QuotedType::TypeDefinition)), + AttributeTarget::Enum => Some(Type::Quoted(QuotedType::TypeDefinition)), AttributeTarget::Trait => Some(Type::Quoted(QuotedType::TraitDefinition)), AttributeTarget::Function => Some(Type::Quoted(QuotedType::FunctionDefinition)), AttributeTarget::Let => { @@ -188,8 +188,8 @@ impl NodeFinder<'_> { let func_self_type = if let Some((pattern, typ, _)) = func_meta.parameters.0.first() { if self.hir_pattern_is_self_type(pattern) { - if let Type::MutableReference(mut_typ) = typ { - let typ: &Type = mut_typ; + if let Type::Reference(elem_type, _) = typ { + let typ: &Type = elem_type; Some(typ) } else { Some(typ) @@ -222,9 +222,8 @@ impl NodeFinder<'_> { // Check that the pattern type is the same as self type. // We do this because some types (only Field and integer types) // have their methods in the same HashMap. - - if let Type::MutableReference(mut_typ) = self_type { - self_type = mut_typ; + if let Type::Reference(elem_type, _) = self_type { + self_type = elem_type; } if self_type != func_self_type { @@ -597,7 +596,7 @@ fn func_meta_type_to_string(func_meta: &FuncMeta, name: &str, has_self_type: boo } fn type_to_self_string(typ: &Type, string: &mut String) { - if let Type::MutableReference(..) = typ { + if let Type::Reference(..) = typ { string.push_str("&mut self"); } else { string.push_str("self"); diff --git a/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs b/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs index 98bb2aae6e6f..64c91dd673d1 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs @@ -16,6 +16,7 @@ mod completion_tests { }, test_utils, tests::apply_text_edits, + utils::get_cursor_line_and_column, }; use lsp_types::{ @@ -26,19 +27,12 @@ mod completion_tests { }; use tokio::test; - async fn get_completions(src: &str) -> Vec { + /// Given a string with ">|<" (cursor) in it, returns all completions that are available + /// at that position together with the string with ">|<" removed. + async fn get_completions(src: &str) -> (Vec, String) { let (mut state, noir_text_document) = test_utils::init_lsp_server("document_symbol").await; - let (line, column) = src - .lines() - .enumerate() - .filter_map(|(line_index, line)| { - line.find(">|<").map(|char_index| (line_index, char_index)) - }) - .next() - .expect("Expected to find one >|< in the source code"); - - let src = src.replace(">|<", ""); + let (line, column, src) = get_cursor_line_and_column(src); on_did_open_text_document( &mut state, @@ -67,7 +61,9 @@ mod completion_tests { .await .expect("Could not execute on_completion_request"); - if let Some(CompletionResponse::Array(items)) = response { items } else { vec![] } + let items = + if let Some(CompletionResponse::Array(items)) = response { items } else { vec![] }; + (items, src) } fn assert_items_match(mut items: Vec, mut expected: Vec) { @@ -90,12 +86,12 @@ mod completion_tests { } async fn assert_completion(src: &str, expected: Vec) { - let items = get_completions(src).await; + let (items, _) = get_completions(src).await; assert_items_match(items, expected); } async fn assert_completion_excluding_auto_import(src: &str, expected: Vec) { - let items = get_completions(src).await; + let (items, _) = get_completions(src).await; let items = items.into_iter().filter(|item| item.additional_text_edits.is_none()).collect(); assert_items_match(items, expected); } @@ -760,7 +756,7 @@ mod completion_tests { fn foo(x: i>|<) {} "#; - let items = get_completions(src).await; + let (items, _) = get_completions(src).await; let items = items.into_iter().filter(|item| item.label.starts_with('i')).collect(); assert_items_match( @@ -782,7 +778,7 @@ mod completion_tests { } "#; - let items = get_completions(src).await; + let (items, _) = get_completions(src).await; assert!(items.iter().any(|item| item.label == "i8")); } @@ -794,7 +790,7 @@ mod completion_tests { } "#; - let items = get_completions(src).await; + let (items, _) = get_completions(src).await; assert!( items .iter() @@ -1341,7 +1337,7 @@ mod completion_tests { } "#; - let items = get_completions(src).await; + let (items, _) = get_completions(src).await; let items = items.into_iter().filter(|item| item.kind == Some(CompletionItemKind::FIELD)); let items = items.collect(); @@ -1431,7 +1427,7 @@ fn main() { } "#; - let mut items = get_completions(src).await; + let (mut items, src) = get_completions(src).await; assert_eq!(items.len(), 1); let item = items.remove(0); @@ -1444,8 +1440,7 @@ fn main() { }) ); - let changed = - apply_text_edits(&src.replace(">|<", ""), &item.additional_text_edits.unwrap()); + let changed = apply_text_edits(&src, &item.additional_text_edits.unwrap()); assert_eq!(changed, expected); assert_eq!(item.sort_text, Some(auto_import_sort_text())); } @@ -1477,7 +1472,7 @@ mod foo { } } "#; - let mut items = get_completions(src).await; + let (mut items, src) = get_completions(src).await; assert_eq!(items.len(), 1); let item = items.remove(0); @@ -1490,8 +1485,7 @@ mod foo { }) ); - let changed = - apply_text_edits(&src.replace(">|<", ""), &item.additional_text_edits.unwrap()); + let changed = apply_text_edits(&src, &item.additional_text_edits.unwrap()); assert_eq!(changed, expected); } @@ -1522,7 +1516,7 @@ mod foo { } } }"#; - let mut items = get_completions(src).await; + let (mut items, src) = get_completions(src).await; assert_eq!(items.len(), 1); let item = items.remove(0); @@ -1535,8 +1529,7 @@ mod foo { }) ); - let changed = - apply_text_edits(&src.replace(">|<", ""), &item.additional_text_edits.unwrap()); + let changed = apply_text_edits(&src, &item.additional_text_edits.unwrap()); assert_eq!(changed, expected); } @@ -1574,13 +1567,12 @@ use foo::bar::hello_world; fn main() { hel }"#; - let mut items = get_completions(src).await; + let (mut items, src) = get_completions(src).await; assert_eq!(items.len(), 1); let item = items.remove(0); - let changed = - apply_text_edits(&src.replace(">|<", ""), &item.additional_text_edits.unwrap()); + let changed = apply_text_edits(&src, &item.additional_text_edits.unwrap()); assert_eq!(changed, expected); } @@ -1622,13 +1614,12 @@ mod other { hel } }"#; - let mut items = get_completions(src).await; + let (mut items, src) = get_completions(src).await; assert_eq!(items.len(), 1); let item = items.remove(0); - let changed = - apply_text_edits(&src.replace(">|<", ""), &item.additional_text_edits.unwrap()); + let changed = apply_text_edits(&src, &item.additional_text_edits.unwrap()); assert_eq!(changed, expected); } @@ -1648,7 +1639,7 @@ mod other { hel>|< } "#; - let items = get_completions(src).await; + let (items, _) = get_completions(src).await; assert!(items.is_empty()); } @@ -1667,7 +1658,7 @@ mod other { hel>|< } "#; - let items = get_completions(src).await; + let (items, _) = get_completions(src).await; assert!(items.is_empty()); } @@ -1684,7 +1675,7 @@ mod other { hel>|< } "#; - let items = get_completions(src).await; + let (items, _) = get_completions(src).await; assert!(items.is_empty()); } @@ -1703,7 +1694,7 @@ mod other { hello_w>|< } "#; - let mut items = get_completions(src).await; + let (mut items, _) = get_completions(src).await; assert_eq!(items.len(), 1); let item = items.remove(0); @@ -1737,7 +1728,7 @@ mod foo { } }"#; - let mut items = get_completions(src).await; + let (mut items, src) = get_completions(src).await; assert_eq!(items.len(), 1); let item = items.remove(0); @@ -1751,8 +1742,7 @@ mod foo { }) ); - let changed = - apply_text_edits(&src.replace(">|<", ""), &item.additional_text_edits.unwrap()); + let changed = apply_text_edits(&src, &item.additional_text_edits.unwrap()); assert_eq!(changed, expected); } @@ -1784,12 +1774,11 @@ fn main() { two_hello_ }"#; - let mut items = get_completions(src).await; + let (mut items, src) = get_completions(src).await; assert_eq!(items.len(), 1); let item = items.remove(0); - let changed = - apply_text_edits(&src.replace(">|<", ""), &item.additional_text_edits.unwrap()); + let changed = apply_text_edits(&src, &item.additional_text_edits.unwrap()); assert_eq!(changed, expected); assert_eq!(item.sort_text, Some(auto_import_sort_text())); } @@ -1822,12 +1811,11 @@ fn main() { two_hello_ }"#; - let mut items = get_completions(src).await; + let (mut items, src) = get_completions(src).await; assert_eq!(items.len(), 1); let item = items.remove(0); - let changed = - apply_text_edits(&src.replace(">|<", ""), &item.additional_text_edits.unwrap()); + let changed = apply_text_edits(&src, &item.additional_text_edits.unwrap()); assert_eq!(changed, expected); assert_eq!(item.sort_text, Some(auto_import_sort_text())); } @@ -1862,12 +1850,11 @@ fn main() { two_hello_ }"#; - let mut items = get_completions(src).await; + let (mut items, src) = get_completions(src).await; assert_eq!(items.len(), 1); let item = items.remove(0); - let changed = - apply_text_edits(&src.replace(">|<", ""), &item.additional_text_edits.unwrap()); + let changed = apply_text_edits(&src, &item.additional_text_edits.unwrap()); assert_eq!(changed, expected); assert_eq!(item.sort_text, Some(auto_import_sort_text())); } @@ -1908,12 +1895,11 @@ fn main() { two_hello_ }"#; - let mut items = get_completions(src).await; + let (mut items, src) = get_completions(src).await; assert_eq!(items.len(), 1); let item = items.remove(0); - let changed = - apply_text_edits(&src.replace(">|<", ""), &item.additional_text_edits.unwrap()); + let changed = apply_text_edits(&src, &item.additional_text_edits.unwrap()); assert_eq!(changed, expected); assert_eq!(item.sort_text, Some(auto_import_sort_text())); } @@ -1946,12 +1932,11 @@ fn main() { two_hello_ }"#; - let mut items = get_completions(src).await; + let (mut items, src) = get_completions(src).await; assert_eq!(items.len(), 1); let item = items.remove(0); - let changed = - apply_text_edits(&src.replace(">|<", ""), &item.additional_text_edits.unwrap()); + let changed = apply_text_edits(&src, &item.additional_text_edits.unwrap()); assert_eq!(changed, expected); assert_eq!(item.sort_text, Some(auto_import_sort_text())); } @@ -1986,12 +1971,11 @@ fn main() { two_hello_ }"#; - let mut items = get_completions(src).await; + let (mut items, src) = get_completions(src).await; assert_eq!(items.len(), 1); let item = items.remove(0); - let changed = - apply_text_edits(&src.replace(">|<", ""), &item.additional_text_edits.unwrap()); + let changed = apply_text_edits(&src, &item.additional_text_edits.unwrap()); assert_eq!(changed, expected); assert_eq!(item.sort_text, Some(auto_import_sort_text())); } @@ -2022,12 +2006,11 @@ fn main() { two_hello_ }"#; - let mut items = get_completions(src).await; + let (mut items, src) = get_completions(src).await; assert_eq!(items.len(), 1); let item = items.remove(0); - let changed = - apply_text_edits(&src.replace(">|<", ""), &item.additional_text_edits.unwrap()); + let changed = apply_text_edits(&src, &item.additional_text_edits.unwrap()); assert_eq!(changed, expected); assert_eq!(item.sort_text, Some(auto_import_sort_text())); } @@ -2097,7 +2080,7 @@ fn main() { } } "#; - let items = get_completions(src).await; + let (items, _) = get_completions(src).await; assert_eq!(items.len(), 1); let item = &items[0]; @@ -2118,7 +2101,7 @@ fn main() { zeroe>|< } "#; - let items = get_completions(src).await; + let (items, _) = get_completions(src).await; assert_eq!(items.len(), 1); let item = &items[0]; @@ -2264,7 +2247,7 @@ fn main() { x.>|< } "#; - let items = get_completions(src).await; + let (items, _) = get_completions(src).await; if items.iter().any(|item| item.label == "__assert_max_bit_size") { panic!("Private method __assert_max_bit_size was suggested"); } @@ -2304,7 +2287,7 @@ fn main() { } "#; - let items = get_completions(src).await; + let (items, _) = get_completions(src).await; assert_eq!(items.len(), 1); let item = &items[0]; @@ -2331,7 +2314,7 @@ fn main() { } "#; - let items = get_completions(src).await; + let (items, _) = get_completions(src).await; assert_eq!(items.len(), 1); let item = &items[0]; @@ -2356,7 +2339,7 @@ fn main() { fn main() {} "#; - let items = get_completions(src).await; + let (items, _) = get_completions(src).await; assert_eq!(items.len(), 1); let item = &items[0]; @@ -2684,9 +2667,9 @@ fn main() { } "#; - let completions = get_completions(src).await; - assert_eq!(completions.len(), 1); - assert_eq!(completions[0].label, "unquote!(…)"); + let (items, _) = get_completions(src).await; + assert_eq!(items.len(), 1); + assert_eq!(items[0].label, "unquote!(…)"); } #[test] @@ -2855,7 +2838,7 @@ fn main() { foo.b>|< } "#; - let items = get_completions(src).await; + let (items, _) = get_completions(src).await; assert_eq!(items.len(), 1); assert!(items[0].label == "bar_baz()"); } @@ -2874,7 +2857,7 @@ fn main() { x.fo>|< } "#; - let items = get_completions(src).await; + let (items, _) = get_completions(src).await; assert_eq!(items.len(), 1); } @@ -2925,7 +2908,7 @@ fn main() { } "#; - let items = get_completions(src).await; + let (items, _) = get_completions(src).await; assert_eq!(items.len(), 2); } @@ -2946,14 +2929,13 @@ fn main() { Field::fooba>|< } "#; - let mut items = get_completions(src).await; + let (mut items, src) = get_completions(src).await; assert_eq!(items.len(), 1); let item = items.remove(0); assert_eq!(item.label_details.unwrap().detail, Some("(use moo::Foo)".to_string())); - let new_code = - apply_text_edits(&src.replace(">|<", ""), &item.additional_text_edits.unwrap()); + let new_code = apply_text_edits(&src, &item.additional_text_edits.unwrap()); let expected = r#"use moo::Foo; @@ -2992,14 +2974,13 @@ fn main() { x.fooba>|< } "#; - let mut items = get_completions(src).await; + let (mut items, src) = get_completions(src).await; assert_eq!(items.len(), 1); let item = items.remove(0); assert_eq!(item.label_details.unwrap().detail, Some("(use moo::Foo)".to_string())); - let new_code = - apply_text_edits(&src.replace(">|<", ""), &item.additional_text_edits.unwrap()); + let new_code = apply_text_edits(&src, &item.additional_text_edits.unwrap()); let expected = r#"use moo::Foo; @@ -3043,14 +3024,13 @@ fn main() { x.fooba>|< } "#; - let mut items = get_completions(src).await; + let (mut items, src) = get_completions(src).await; assert_eq!(items.len(), 1); let item = items.remove(0); assert_eq!(item.label_details.unwrap().detail, Some("(use moo::Bar)".to_string())); - let new_code = - apply_text_edits(&src.replace(">|<", ""), &item.additional_text_edits.unwrap()); + let new_code = apply_text_edits(&src, &item.additional_text_edits.unwrap()); let expected = r#"use moo::Bar; @@ -3088,7 +3068,7 @@ fn main() { Enum::Var>|< } "#; - let items = get_completions(src).await; + let (items, _) = get_completions(src).await; assert_eq!(items.len(), 1); let item = &items[0]; @@ -3120,7 +3100,7 @@ fn main() { Enum::Var>|< } "#; - let items = get_completions(src).await; + let (items, _) = get_completions(src).await; assert_eq!(items.len(), 1); let item = &items[0]; @@ -3149,7 +3129,7 @@ fn main() { ThisIsA>|< } "#; - let items = get_completions(src).await; + let (items, _) = get_completions(src).await; assert_eq!(items.len(), 1); let item = &items[0]; @@ -3172,7 +3152,7 @@ fn main() { SomeStru>|< }"#; - let mut items = get_completions(src).await; + let (mut items, src) = get_completions(src).await; assert_eq!(items.len(), 1); let item = items.remove(0); @@ -3200,8 +3180,7 @@ fn main() { SomeStru }"#; - let changed = - apply_text_edits(&src.replace(">|<", ""), &item.additional_text_edits.unwrap()); + let changed = apply_text_edits(&src, &item.additional_text_edits.unwrap()); assert_eq!(changed, expected); } @@ -3221,7 +3200,7 @@ fn main() { SomeStru>|< }"#; - let mut items = get_completions(src).await; + let (mut items, src) = get_completions(src).await; assert_eq!(items.len(), 1); let item = items.remove(0); @@ -3249,8 +3228,7 @@ fn main() { SomeStru }"#; - let changed = - apply_text_edits(&src.replace(">|<", ""), &item.additional_text_edits.unwrap()); + let changed = apply_text_edits(&src, &item.additional_text_edits.unwrap()); assert_eq!(changed, expected); } @@ -3272,7 +3250,7 @@ fn main() { SomeStru>|< }"#; - let mut items = get_completions(src).await; + let (mut items, src) = get_completions(src).await; assert_eq!(items.len(), 1); let item = items.remove(0); @@ -3302,8 +3280,7 @@ fn main() { SomeStru }"#; - let changed = - apply_text_edits(&src.replace(">|<", ""), &item.additional_text_edits.unwrap()); + let changed = apply_text_edits(&src, &item.additional_text_edits.unwrap()); assert_eq!(changed, expected); } @@ -3325,7 +3302,7 @@ fn main() { SomeStru>|< }"#; - let items = get_completions(src).await; + let (items, _) = get_completions(src).await; assert_eq!(items.len(), 0); } @@ -3349,7 +3326,7 @@ fn main() { SomeStru>|< }"#; - let mut items = get_completions(src).await; + let (mut items, src) = get_completions(src).await; assert_eq!(items.len(), 1); let item = items.remove(0); @@ -3381,8 +3358,7 @@ fn main() { SomeStru }"#; - let changed = - apply_text_edits(&src.replace(">|<", ""), &item.additional_text_edits.unwrap()); + let changed = apply_text_edits(&src, &item.additional_text_edits.unwrap()); assert_eq!(changed, expected); } } diff --git a/noir/noir-repo/tooling/lsp/src/requests/hover/from_reference.rs b/noir/noir-repo/tooling/lsp/src/requests/hover/from_reference.rs index 3bc3b3bded70..a8ed06c08968 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/hover/from_reference.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/hover/from_reference.rs @@ -498,7 +498,7 @@ fn format_function(id: FuncId, args: &ProcessRequestCallbackArgs) -> String { let is_self = pattern_is_self(pattern, args.interner); // `&mut self` is represented as a mutable reference type, not as a mutable pattern - if is_self && matches!(typ, Type::MutableReference(..)) { + if is_self && matches!(typ, Type::Reference(..)) { string.push_str("&mut "); } @@ -814,7 +814,7 @@ impl TypeLinksGatherer<'_> { self.gather_type_links(return_type); self.gather_type_links(env); } - Type::MutableReference(typ) => self.gather_type_links(typ), + Type::Reference(typ, _) => self.gather_type_links(typ), Type::InfixExpr(lhs, _, rhs, _) => { self.gather_type_links(lhs); self.gather_type_links(rhs); diff --git a/noir/noir-repo/tooling/lsp/src/requests/inlay_hint.rs b/noir/noir-repo/tooling/lsp/src/requests/inlay_hint.rs index 8c794d288686..37cccb789a21 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/inlay_hint.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/inlay_hint.rs @@ -511,7 +511,11 @@ fn push_type_parts(typ: &Type, parts: &mut Vec, files: &File parts.push(string_part(") -> ")); push_type_parts(return_type, parts, files); } - Type::MutableReference(typ) => { + Type::Reference(typ, false) => { + parts.push(string_part("&")); + push_type_parts(typ, parts, files); + } + Type::Reference(typ, true) => { parts.push(string_part("&mut ")); push_type_parts(typ, parts, files); } diff --git a/noir/noir-repo/tooling/lsp/src/requests/mod.rs b/noir/noir-repo/tooling/lsp/src/requests/mod.rs index 9126ab38e103..a5ffd1155fa7 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/mod.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/mod.rs @@ -19,6 +19,7 @@ use nargo_fmt::Config; use noirc_frontend::ast::Ident; use noirc_frontend::graph::CrateId; use noirc_frontend::hir::def_map::{CrateDefMap, ModuleId}; +use noirc_frontend::node_interner::ReferenceId; use noirc_frontend::parser::ParserError; use noirc_frontend::usage_tracker::UsageTracker; use noirc_frontend::{graph::Dependency, node_interner::NodeInterner}; @@ -590,68 +591,104 @@ pub(crate) fn find_all_references_in_workspace( include_self_type_name: bool, ) -> Option> { // First find the node that's referenced by the given location, if any - let referenced = interner.find_referenced(location); + let referenced = interner.find_referenced(location)?; + let name = get_reference_name(referenced, interner)?; - if let Some(referenced) = referenced { - // If we found the referenced node, find its location - let referenced_location = interner.reference_location(referenced); + // If we found the referenced node, find its location + let referenced_location = interner.reference_location(referenced); - // Now we find all references that point to this location, in all interners - // (there's one interner per package, and all interners in a workspace rely on the - // same FileManager so a Location/FileId in one package is the same as in another package) - let mut locations = find_all_references( + // Now we find all references that point to this location, in all interners + // (there's one interner per package, and all interners in a workspace rely on the + // same FileManager so a Location/FileId in one package is the same as in another package) + let mut locations = find_all_references( + referenced_location, + interner, + include_declaration, + include_self_type_name, + ); + for cache_data in package_cache.values() { + locations.extend(find_all_references( referenced_location, - interner, - files, + &cache_data.node_interner, include_declaration, include_self_type_name, - ); - for cache_data in package_cache.values() { - locations.extend(find_all_references( - referenced_location, - &cache_data.node_interner, - files, - include_declaration, - include_self_type_name, - )); - } - - // The LSP client usually removes duplicate locations, but we do it here just in case they don't - locations.sort_by_key(|location| { - ( - location.uri.to_string(), - location.range.start.line, - location.range.start.character, - location.range.end.line, - location.range.end.character, - ) - }); - locations.dedup(); - - if locations.is_empty() { None } else { Some(locations) } - } else { - None + )); } + + // Only keep locations whose span, when read from the file, matches "name" + // (it might not match because of macro expansions) + locations.retain(|location| { + let Some(file) = files.get_file(location.file) else { + return false; + }; + + let Some(substring) = + file.source().get(location.span.start() as usize..location.span.end() as usize) + else { + return false; + }; + + substring == name + }); + + let mut locations = locations + .iter() + .filter_map(|location| to_lsp_location(files, location.file, location.span)) + .collect::>(); + + // The LSP client usually removes duplicate locations, but we do it here just in case they don't + locations.sort_by_key(|location| { + ( + location.uri.to_string(), + location.range.start.line, + location.range.start.character, + location.range.end.line, + location.range.end.character, + ) + }); + locations.dedup(); + + if locations.is_empty() { None } else { Some(locations) } } pub(crate) fn find_all_references( referenced_location: noirc_errors::Location, interner: &NodeInterner, - files: &FileMap, include_declaration: bool, include_self_type_name: bool, -) -> Vec { +) -> Vec { interner .find_all_references(referenced_location, include_declaration, include_self_type_name) - .map(|locations| { - locations - .iter() - .filter_map(|location| to_lsp_location(files, location.file, location.span)) - .collect() - }) .unwrap_or_default() } +fn get_reference_name(reference: ReferenceId, interner: &NodeInterner) -> Option { + match reference { + ReferenceId::Module(module_id) => { + Some(interner.try_module_attributes(&module_id)?.name.clone()) + } + ReferenceId::Type(type_id) => Some(interner.get_type(type_id).borrow().name.to_string()), + ReferenceId::StructMember(type_id, index) => { + Some(interner.get_type(type_id).borrow().field_at(index).name.to_string()) + } + ReferenceId::EnumVariant(type_id, index) => { + Some(interner.get_type(type_id).borrow().variant_at(index).name.to_string()) + } + ReferenceId::Trait(trait_id) => Some(interner.get_trait(trait_id).name.to_string()), + ReferenceId::Global(global_id) => Some(interner.get_global(global_id).ident.to_string()), + ReferenceId::Function(func_id) => Some(interner.function_name(&func_id).to_string()), + ReferenceId::Alias(type_alias_id) => { + Some(interner.get_type_alias(type_alias_id).borrow().name.to_string()) + } + ReferenceId::Local(definition_id) => { + Some(interner.definition_name(definition_id).to_string()) + } + ReferenceId::Reference(location, _) => { + get_reference_name(interner.find_referenced(location)?, interner) + } + } +} + /// Represents a trait reexported from a given module with a name. pub(crate) struct TraitReexport { pub(super) module_id: ModuleId, diff --git a/noir/noir-repo/tooling/lsp/src/requests/references.rs b/noir/noir-repo/tooling/lsp/src/requests/references.rs index fbe69c99871d..73188b18adaa 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/references.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/references.rs @@ -28,10 +28,12 @@ pub(crate) fn on_references_request( #[cfg(test)] mod references_tests { use super::*; - use crate::notifications; use crate::test_utils::{self, search_in_file}; + use crate::utils::get_cursor_line_and_column; + use crate::{notifications, on_did_open_text_document}; use lsp_types::{ - PartialResultParams, Position, Range, ReferenceContext, TextDocumentPositionParams, Url, + DidOpenTextDocumentParams, PartialResultParams, Position, Range, ReferenceContext, + TextDocumentIdentifier, TextDocumentItem, TextDocumentPositionParams, Url, WorkDoneProgressParams, }; use tokio::test; @@ -55,9 +57,7 @@ mod references_tests { let params = ReferenceParams { text_document_position: TextDocumentPositionParams { - text_document: lsp_types::TextDocumentIdentifier { - uri: noir_text_document.clone(), - }, + text_document: TextDocumentIdentifier { uri: noir_text_document.clone() }, position: target_position, }, work_done_progress_params: WorkDoneProgressParams { work_done_token: None }, @@ -123,7 +123,7 @@ mod references_tests { let params = ReferenceParams { text_document_position: TextDocumentPositionParams { - text_document: lsp_types::TextDocumentIdentifier { uri: one_lib.clone() }, + text_document: TextDocumentIdentifier { uri: one_lib.clone() }, position: Position { line: 0, character: 7 }, }, work_done_progress_params: WorkDoneProgressParams { work_done_token: None }, @@ -170,4 +170,56 @@ mod references_tests { } ); } + + #[test] + async fn ignores_macro_expansions() { + let src = " + #[foo] + struct Fo>| Quoted { + let t = s.as_type(); + quote { + impl Trait for $t {} + } + } + "; + + let (mut state, noir_text_document) = test_utils::init_lsp_server("document_symbol").await; + + let (line, column, src) = get_cursor_line_and_column(src); + + on_did_open_text_document( + &mut state, + DidOpenTextDocumentParams { + text_document: TextDocumentItem { + uri: noir_text_document.clone(), + language_id: "noir".to_string(), + version: 0, + text: src.to_string(), + }, + }, + ); + + let result = on_references_request( + &mut state, + ReferenceParams { + text_document_position: TextDocumentPositionParams { + text_document: TextDocumentIdentifier { uri: noir_text_document }, + position: Position { line: line as u32, character: column as u32 }, + }, + work_done_progress_params: WorkDoneProgressParams { work_done_token: None }, + partial_result_params: PartialResultParams { partial_result_token: None }, + context: ReferenceContext { include_declaration: true }, + }, + ) + .await; + let locations = result.unwrap().unwrap(); + assert_eq!(locations.len(), 1); + assert_eq!(locations[0].range.start.line, 2); // Just the one for "struct Foo" + } } diff --git a/noir/noir-repo/tooling/lsp/src/requests/signature_help.rs b/noir/noir-repo/tooling/lsp/src/requests/signature_help.rs index 7563dc50c987..6706bb098bfb 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/signature_help.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/signature_help.rs @@ -146,11 +146,13 @@ impl<'a> SignatureFinder<'a> { } if has_self && index == 0 { - if let Type::MutableReference(..) = typ { - label.push_str("&mut self"); - } else { - label.push_str("self"); + if let Type::Reference(_, mutable) = typ { + label.push('&'); + if *mutable { + label.push_str("mut "); + } } + label.push_str("self"); } else { let parameter_start = label.chars().count(); diff --git a/noir/noir-repo/tooling/lsp/src/requests/signature_help/tests.rs b/noir/noir-repo/tooling/lsp/src/requests/signature_help/tests.rs index a5cf7c32e1e2..b102a3f03b1a 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/signature_help/tests.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/signature_help/tests.rs @@ -2,6 +2,7 @@ mod signature_help_tests { use crate::{ notifications::on_did_open_text_document, requests::on_signature_help_request, test_utils, + utils::get_cursor_line_and_column, }; use lsp_types::{ @@ -14,15 +15,7 @@ mod signature_help_tests { async fn get_signature_help(src: &str) -> SignatureHelp { let (mut state, noir_text_document) = test_utils::init_lsp_server("document_symbol").await; - let (line, column) = src - .lines() - .enumerate() - .find_map(|(line_index, line)| { - line.find(">|<").map(|char_index| (line_index, char_index)) - }) - .expect("Expected to find one >|< in the source code"); - - let src = src.replace(">|<", ""); + let (line, column, src) = get_cursor_line_and_column(src); on_did_open_text_document( &mut state, diff --git a/noir/noir-repo/tooling/lsp/src/trait_impl_method_stub_generator.rs b/noir/noir-repo/tooling/lsp/src/trait_impl_method_stub_generator.rs index 3a60a882aeab..b24f4dd7d872 100644 --- a/noir/noir-repo/tooling/lsp/src/trait_impl_method_stub_generator.rs +++ b/noir/noir-repo/tooling/lsp/src/trait_impl_method_stub_generator.rs @@ -359,7 +359,11 @@ impl<'a> TraitImplMethodStubGenerator<'a> { self.append_type(ret); } } - Type::MutableReference(typ) => { + Type::Reference(typ, false) => { + self.string.push('&'); + self.append_type(typ); + } + Type::Reference(typ, true) => { self.string.push_str("&mut "); self.append_type(typ); } diff --git a/noir/noir-repo/tooling/lsp/src/utils.rs b/noir/noir-repo/tooling/lsp/src/utils.rs index ca607128bf2e..5fe5d14bc6d0 100644 --- a/noir/noir-repo/tooling/lsp/src/utils.rs +++ b/noir/noir-repo/tooling/lsp/src/utils.rs @@ -53,3 +53,19 @@ pub(crate) fn character_to_line_offset(line: &str, character: u32) -> Option|<" (cursor) in it, returns: +/// 1. The line where the cursor is (zero-based) +/// 2. The column where the cursor is (zero-based) +/// 3. that string with ">|<" removed +#[cfg(test)] +pub(crate) fn get_cursor_line_and_column(src: &str) -> (usize, usize, String) { + let (line, column) = src + .lines() + .enumerate() + .find_map(|(line_index, line)| line.find(">|<").map(|char_index| (line_index, char_index))) + .expect("Expected to find one >|< in the source code"); + + let src = src.replace(">|<", ""); + (line, column, src) +} diff --git a/noir/noir-repo/tooling/lsp/src/with_file.rs b/noir/noir-repo/tooling/lsp/src/with_file.rs index 1f0d02db421b..3ba8aecf74a2 100644 --- a/noir/noir-repo/tooling/lsp/src/with_file.rs +++ b/noir/noir-repo/tooling/lsp/src/with_file.rs @@ -485,8 +485,8 @@ fn unresolved_type_data_with_file(typ: UnresolvedTypeData, file: FileId) -> Unre generic_type_args_with_file(generic_type_args, file), ) } - UnresolvedTypeData::MutableReference(typ) => { - UnresolvedTypeData::MutableReference(Box::new(unresolved_type_with_file(*typ, file))) + UnresolvedTypeData::Reference(typ, mutable) => { + UnresolvedTypeData::Reference(Box::new(unresolved_type_with_file(*typ, file)), mutable) } UnresolvedTypeData::Tuple(types) => { UnresolvedTypeData::Tuple(unresolved_types_with_file(types, file)) diff --git a/noir/noir-repo/tooling/nargo/src/ops/execute.rs b/noir/noir-repo/tooling/nargo/src/ops/execute.rs index 699c54e3f528..b12871c75e47 100644 --- a/noir/noir-repo/tooling/nargo/src/ops/execute.rs +++ b/noir/noir-repo/tooling/nargo/src/ops/execute.rs @@ -13,7 +13,7 @@ use crate::NargoError; use crate::errors::ExecutionError; use crate::foreign_calls::ForeignCallExecutor; -struct ProgramExecutor<'a, F, B: BlackBoxFunctionSolver, E: ForeignCallExecutor> { +struct ProgramExecutor<'a, F: AcirField, B: BlackBoxFunctionSolver, E: ForeignCallExecutor> { functions: &'a [Circuit], unconstrained_functions: &'a [BrilligBytecode], diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/compile_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/compile_cmd.rs index 2ee19f5edc01..fb67a9c3ca74 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/compile_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/compile_cmd.rs @@ -325,7 +325,7 @@ mod tests { use nargo::ops::compile_program; use nargo_toml::PackageSelection; use noirc_driver::{CompileOptions, CrateName}; - use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; + use noirc_frontend::elaborator::UnstableFeature; use crate::cli::test_cmd::formatters::diagnostic_to_string; use crate::cli::{ @@ -398,17 +398,23 @@ mod tests { assert!(!test_workspaces.is_empty(), "should find some test workspaces"); - test_workspaces.par_iter().for_each(|workspace| { + // This could be `.par_iter()` but then error messages are no longer reported + test_workspaces.iter().for_each(|workspace| { let (file_manager, parsed_files) = parse_workspace(workspace); let binary_packages = workspace.into_iter().filter(|package| package.is_binary()); for package in binary_packages { + let options = CompileOptions { + unstable_features: vec![UnstableFeature::Enums], + ..Default::default() + }; + let (program_0, _warnings) = compile_program( &file_manager, &parsed_files, workspace, package, - &CompileOptions::default(), + &options, None, ) .unwrap_or_else(|err| { diff --git a/noir/noir-repo/tooling/nargo_cli/tests/stdlib-props.rs b/noir/noir-repo/tooling/nargo_cli/tests/stdlib-props.rs index 7c3794d03ab0..b00d50c8348f 100644 --- a/noir/noir-repo/tooling/nargo_cli/tests/stdlib-props.rs +++ b/noir/noir-repo/tooling/nargo_cli/tests/stdlib-props.rs @@ -177,7 +177,10 @@ fn fuzz_poseidon_equivalence() { // Noir has hashes up to length 16, but the reference library won't work with more than 12. for len in 1..light_poseidon::MAX_X5_LEN { let source = format!( - "fn main(input: [Field; {len}]) -> pub Field {{ + " + use std::hash::{{Hash, Hasher}}; + + fn main(input: [Field; {len}]) -> pub Field {{ let h1 = std::hash::poseidon::bn254::hash_{len}(input); let h2 = {{ let mut hasher = std::hash::poseidon::PoseidonHasher::default(); diff --git a/noir/noir-repo/tooling/nargo_fmt/src/formatter/expression.rs b/noir/noir-repo/tooling/nargo_fmt/src/formatter/expression.rs index 6b46a4557a2b..eb20245e6b64 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/formatter/expression.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/formatter/expression.rs @@ -155,7 +155,7 @@ impl ChunkFormatter<'_, '_> { group.text(self.chunk(|formatter| { if is_slice { - formatter.write_token(Token::Ampersand); + formatter.write_token(Token::SliceStart); } formatter.write_left_bracket(); })); @@ -715,7 +715,7 @@ impl ChunkFormatter<'_, '_> { fn format_prefix(&mut self, prefix: PrefixExpression) -> ChunkGroup { let mut group = ChunkGroup::new(); group.text(self.chunk(|formatter| { - if let UnaryOp::MutableReference = prefix.operator { + if let UnaryOp::Reference { mutable: true } = prefix.operator { formatter.write_current_token(); formatter.bump(); formatter.skip_comments_and_whitespace(); diff --git a/noir/noir-repo/tooling/nargo_fmt/src/formatter/types.rs b/noir/noir-repo/tooling/nargo_fmt/src/formatter/types.rs index 6a0e66bc1f98..04e78c04162e 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/formatter/types.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/formatter/types.rs @@ -75,10 +75,12 @@ impl Formatter<'_> { self.format_path(path); self.format_generic_type_args(generic_type_args); } - UnresolvedTypeData::MutableReference(typ) => { + UnresolvedTypeData::Reference(typ, mutable) => { self.write_token(Token::Ampersand); - self.write_keyword(Keyword::Mut); - self.write_space(); + if mutable { + self.write_keyword(Keyword::Mut); + self.write_space(); + } self.format_type(*typ); } UnresolvedTypeData::Tuple(types) => { diff --git a/noir/noir-repo/tooling/noirc_abi/src/printable_type.rs b/noir/noir-repo/tooling/noirc_abi/src/printable_type.rs index e13cab06e9f6..75c977cd91a7 100644 --- a/noir/noir-repo/tooling/noirc_abi/src/printable_type.rs +++ b/noir/noir-repo/tooling/noirc_abi/src/printable_type.rs @@ -69,7 +69,7 @@ pub fn decode_value( decode_value(field_iterator, env); func_ref } - PrintableType::MutableReference { typ } => { + PrintableType::Reference { typ, .. } => { // we decode the reference, but it's not really used for printing decode_value(field_iterator, typ) } diff --git a/noir/noir-repo/tooling/profiler/src/cli/opcodes_flamegraph_cmd.rs b/noir/noir-repo/tooling/profiler/src/cli/opcodes_flamegraph_cmd.rs index 8ce9ba1de398..d6d00b075038 100644 --- a/noir/noir-repo/tooling/profiler/src/cli/opcodes_flamegraph_cmd.rs +++ b/noir/noir-repo/tooling/profiler/src/cli/opcodes_flamegraph_cmd.rs @@ -1,5 +1,6 @@ use std::path::{Path, PathBuf}; +use acir::AcirField; use acir::circuit::brillig::BrilligFunctionId; use acir::circuit::{Circuit, Opcode, OpcodeLocation}; use clap::Args; @@ -134,7 +135,7 @@ fn run_with_generator( Ok(()) } -fn locate_brillig_call( +fn locate_brillig_call( brillig_fn_index: usize, acir_functions: &[Circuit], ) -> Option<(usize, usize)> { diff --git a/noir/noir-repo/tooling/profiler/src/opcode_formatter.rs b/noir/noir-repo/tooling/profiler/src/opcode_formatter.rs index 2276bcb4403f..591c825e452c 100644 --- a/noir/noir-repo/tooling/profiler/src/opcode_formatter.rs +++ b/noir/noir-repo/tooling/profiler/src/opcode_formatter.rs @@ -2,7 +2,7 @@ use acir::AcirField; use acir::brillig::{BinaryFieldOp, BinaryIntOp, BlackBoxOp, Opcode as BrilligOpcode}; use acir::circuit::{Opcode as AcirOpcode, opcodes::BlackBoxFuncCall}; -fn format_blackbox_function(call: &BlackBoxFuncCall) -> String { +fn format_blackbox_function(call: &BlackBoxFuncCall) -> String { match call { BlackBoxFuncCall::AES128Encrypt { .. } => "aes128_encrypt".to_string(), BlackBoxFuncCall::AND { .. } => "and".to_string(), @@ -49,7 +49,7 @@ fn format_blackbox_op(call: &BlackBoxOp) -> String { } } -fn format_acir_opcode_kind(opcode: &AcirOpcode) -> String { +fn format_acir_opcode_kind(opcode: &AcirOpcode) -> String { match opcode { AcirOpcode::AssertZero(_) => "arithmetic".to_string(), AcirOpcode::BlackBoxFuncCall(call) => { diff --git a/noir/noir-repo/utils/protobuf/Cargo.toml b/noir/noir-repo/utils/protobuf/Cargo.toml new file mode 100644 index 000000000000..fdd8fb0ba2ec --- /dev/null +++ b/noir/noir-repo/utils/protobuf/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "noir_protobuf" +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +color-eyre.workspace = true +prost.workspace = true diff --git a/noir/noir-repo/utils/protobuf/src/lib.rs b/noir/noir-repo/utils/protobuf/src/lib.rs new file mode 100644 index 000000000000..c3d72086c830 --- /dev/null +++ b/noir/noir-repo/utils/protobuf/src/lib.rs @@ -0,0 +1,162 @@ +use color_eyre::eyre::{self, Context, bail, eyre}; + +/// A protobuf codec to convert between a domain type `T` +/// and its protobuf representation `R`. +/// +/// It is to be implemented on a `Self` independent of `T` and `R`, +/// so that `T` can be in a third party crate, and `Self` can be +/// generic in the `F` _field_ type as well, which would be cumbersome +/// if we had to implement traits on `R` because `T` is in another +/// crate from the schema, or to scatter the `.proto` schema around +/// so that the traits can be co-defined with `T` which is what can +/// actually be generic in `F`. +pub trait ProtoCodec { + /// Convert domain type `T` to protobuf representation `R`. + fn encode(value: &T) -> R; + /// Encode a field as `Some`. + fn encode_some(value: &T) -> Option { + Some(Self::encode(value)) + } + /// Encode an `enum` to the `i32` value that `prost` represents it with. + fn encode_enum(value: &T) -> i32 + where + R: Into, + { + Self::encode(value).into() + } + /// Encode multiple values as a vector. + fn encode_vec<'a, I>(values: I) -> Vec + where + I: IntoIterator, + T: 'a, + { + values.into_iter().map(Self::encode).collect() + } + + /// Try to convert protobuf representation `R` to domain type `T`. + fn decode(value: &R) -> eyre::Result; + /// Decode a field and attach the name of the field if it fails. + fn decode_wrap(value: &R, msg: &'static str) -> eyre::Result { + Self::decode(value).wrap_err(msg) + } + /// Decode multiple values into a vector. + fn decode_vec(values: &[R]) -> eyre::Result> { + values.iter().map(Self::decode).collect() + } + /// Decode multiple values into a vector, attaching a field name to any errors. + fn decode_vec_wrap(values: &[R], msg: &'static str) -> eyre::Result> { + Self::decode_vec(values).wrap_err(msg) + } + /// Decode a fixed size array. + fn decode_arr(values: &[R]) -> eyre::Result<[T; N]> { + match Self::decode_vec(values)?.try_into() { + Ok(arr) => Ok(arr), + Err(vec) => { + bail!("expected {N} items, got {}", vec.len()); + } + } + } + /// Decode a fixed size array, attaching a field name to any errors + fn decode_arr_wrap(values: &[R], msg: &'static str) -> eyre::Result<[T; N]> { + Self::decode_arr(values).wrap_err(msg) + } + /// Decode a boxed fixed size array. + fn decode_box_arr(values: &[R]) -> eyre::Result> { + Self::decode_arr(values).map(Box::new) + } + /// Decode a boxed fixed size array, attaching a field name to any errors + fn decode_box_arr_wrap( + values: &[R], + msg: &'static str, + ) -> eyre::Result> { + Self::decode_box_arr(values).wrap_err(msg) + } + /// Decode an optional field as a required one; fails if it's `None`. + fn decode_some(value: &Option) -> eyre::Result { + match value { + Some(value) => Self::decode(value), + None => Err(eyre!("missing field")), + } + } + /// Decode an optional field as a required one, attaching a field name to any errors. + /// Returns error if the field is missing. + fn decode_some_wrap(value: &Option, msg: &'static str) -> eyre::Result { + Self::decode_some(value).wrap_err(msg) + } + /// Decode an optional field, attaching a field name to any errors. + /// Return `None` if the field is missing. + fn decode_opt_wrap(value: &Option, msg: &'static str) -> eyre::Result> { + value.as_ref().map(|value| Self::decode_wrap(value, msg)).transpose() + } + /// Decode the numeric representation of an enum into the domain type. + /// Return an error if the value cannot be recognized. + fn decode_enum(value: i32) -> eyre::Result + where + R: TryFrom, + { + let r = R::try_from(value)?; + Self::decode(&r) + } + /// Decode the numeric representation of an enum, attaching the field name to any errors. + fn decode_enum_wrap(value: i32, msg: &'static str) -> eyre::Result + where + R: TryFrom, + { + Self::decode_enum(value).wrap_err(msg) + } + + /// Encode a domain type to protobuf and serialize it to bytes. + fn serialize_to_vec(value: &T) -> Vec + where + R: prost::Message, + { + Self::encode(value).encode_to_vec() + } + /// Deserialize a buffer into protobuf and then decode into the domain type. + fn deserialize_from_vec(buf: &[u8]) -> eyre::Result + where + R: prost::Message + Default, + { + let repr = R::decode(buf).wrap_err("failed to decode into protobuf")?; + Self::decode(&repr).wrap_err("failed to decode protobuf into domain") + } +} + +/// Decode repeated items by mapping a function over them, attaching an error message if it fails. +/// Useful when a lambda needs to be applied before we can use one of the type class methods. +pub fn decode_vec_map_wrap(rs: &[R], msg: &'static str, f: F) -> eyre::Result> +where + F: Fn(&R) -> eyre::Result, +{ + rs.iter().map(f).collect::>>().wrap_err(msg) +} + +/// Decode an optional item, returning an error if it's `None`. +/// Useful when a lambda needs to be applied before we can use one of the type class methods. +pub fn decode_some_map(r: &Option, f: F) -> eyre::Result +where + F: Fn(&R) -> eyre::Result, +{ + match r { + Some(r) => f(r), + None => Err(eyre!("missing field")), + } +} + +/// Decode an optional item, attaching a field name to any errors. +/// Useful when a lambda needs to be applied before we can use one of the type class methods. +pub fn decode_some_map_wrap(r: &Option, msg: &'static str, f: F) -> eyre::Result +where + F: Fn(&R) -> eyre::Result, +{ + decode_some_map(r, f).wrap_err(msg) +} + +/// Decode a `oneof` field, returning an error if it's missing. +/// Useful when a lambda needs to be applied before we can use one of the type class methods. +pub fn decode_oneof_map(r: &Option, f: F) -> eyre::Result +where + F: Fn(&R) -> eyre::Result, +{ + decode_some_map_wrap(r, "oneof value", f) +}