From 7979d7679d28ff5c34f9f143c3a508c96d9aed1f Mon Sep 17 00:00:00 2001 From: AztecBot Date: Tue, 13 Aug 2024 08:02:00 +0000 Subject: [PATCH] [1 changes] chore: allow passing custom executors to fuzzer (https://github.com/noir-lang/noir/pull/5710) feat: LSP path completion (https://github.com/noir-lang/noir/pull/5712) fix(debugger): Update the debugger to handle the new Brillig debug metadata format (https://github.com/noir-lang/noir/pull/5706) feat: add `Quoted::as_expr` and `Expr::as_function_call` (https://github.com/noir-lang/noir/pull/5708) feat: LSP autocompletion for use statement (https://github.com/noir-lang/noir/pull/5704) feat: add `Type::implements` (https://github.com/noir-lang/noir/pull/5701) fix: Do not duplicate redundant Brillig debug metadata (https://github.com/noir-lang/noir/pull/5696) feat: add mutating FunctionDefinition functions (https://github.com/noir-lang/noir/pull/5685) chore: Add docs tip about filenames between commands (https://github.com/noir-lang/noir/pull/5695) fix: Add locations to most SSA instructions (https://github.com/noir-lang/noir/pull/5697) feat: Add array_to_str_lossy (https://github.com/noir-lang/noir/pull/5613) chore: Add parser support for `::ident` (https://github.com/noir-lang/noir/pull/5688) feat: add some `Module` comptime functions (https://github.com/noir-lang/noir/pull/5684) chore: Release Noir(0.33.0) (https://github.com/noir-lang/noir/pull/5550) feat: Derive `Ord` and `Hash` in the stdlib; add `std::meta::make_impl` helper (https://github.com/noir-lang/noir/pull/5683) feat(ssa): Simple serialization of unoptimized SSA to file (https://github.com/noir-lang/noir/pull/5679) feat: LSP closing brace hints (https://github.com/noir-lang/noir/pull/5686) feat: Implement closures in the comptime interpreter (https://github.com/noir-lang/noir/pull/5682) feat: add `FunctionDefinition::parameters`, `FunctionDefinition::return_type` and `impl Eq for Quoted` (https://github.com/noir-lang/noir/pull/5681) feat: add `Type::as_struct` (https://github.com/noir-lang/noir/pull/5680) feat: LSP hover now includes "Go to" links (https://github.com/noir-lang/noir/pull/5677) feat: add `Type` methods: `as_tuple`, `as_slice`, `as_array`, `as_constant`, `is_bool` (https://github.com/noir-lang/noir/pull/5678) fix: Derive generic types (https://github.com/noir-lang/noir/pull/5674) feat: Add a limited form of arithmetic on generics (https://github.com/noir-lang/noir/pull/5625) feat: add `Type::is_field` and `Type::as_integer` (https://github.com/noir-lang/noir/pull/5670) fix: Fix where clause issue in items generated from attributes (https://github.com/noir-lang/noir/pull/5673) feat(noir_js): Expose UltraHonk and integration tests (https://github.com/noir-lang/noir/pull/5656) fix: workaround from_slice with nested slices (https://github.com/noir-lang/noir/pull/5648) fix: Switch verify proof to arrays (https://github.com/noir-lang/noir/pull/5664) feat: Resolve arguments to attributes (https://github.com/noir-lang/noir/pull/5649) fix: Elaborate struct & trait annotations in the correct module (https://github.com/noir-lang/noir/pull/5643) fix: let a trait impl that relies on another trait work (https://github.com/noir-lang/noir/pull/5646) --- .noir-sync-commit | 2 +- .../.github/ISSUE_TEMPLATE/bug_report.yml | 120 - .../ISSUE_TEMPLATE/feature_request.yml | 71 - noir/noir-repo/.release-please-manifest.json | 4 +- noir/noir-repo/CHANGELOG.md | 91 + noir/noir-repo/Cargo.lock | 67 +- noir/noir-repo/Cargo.toml | 16 +- noir/noir-repo/acvm-repo/CHANGELOG.md | 125 + noir/noir-repo/acvm-repo/acir/Cargo.toml | 2 +- .../acvm-repo/acir/src/circuit/brillig.rs | 19 + .../acvm-repo/acir/src/circuit/mod.rs | 3 + .../acvm-repo/acir/src/circuit/opcodes.rs | 4 +- .../acir/src/native_types/expression/mod.rs | 54 + .../acir/tests/test_program_serialization.rs | 6 +- .../noir-repo/acvm-repo/acir_field/Cargo.toml | 2 +- noir/noir-repo/acvm-repo/acvm/Cargo.toml | 3 +- .../acvm/src/compiler/transformers/csat.rs | 65 +- .../acvm-repo/acvm/src/pwg/brillig.rs | 15 +- noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs | 11 +- .../acvm/tests/solver.proptest-regressions | 13 + noir/noir-repo/acvm-repo/acvm/tests/solver.rs | 203 +- noir/noir-repo/acvm-repo/acvm_js/Cargo.toml | 2 +- noir/noir-repo/acvm-repo/acvm_js/build.sh | 2 +- noir/noir-repo/acvm-repo/acvm_js/package.json | 2 +- .../acvm-repo/blackbox_solver/Cargo.toml | 2 +- .../bn254_blackbox_solver/Cargo.toml | 2 +- noir/noir-repo/acvm-repo/brillig/Cargo.toml | 2 +- .../noir-repo/acvm-repo/brillig_vm/Cargo.toml | 2 +- noir/noir-repo/aztec_macros/src/lib.rs | 22 +- ...te_note_hash_and_optionally_a_nullifier.rs | 14 +- .../src/transforms/contract_interface.rs | 5 +- .../aztec_macros/src/transforms/events.rs | 60 +- .../aztec_macros/src/transforms/functions.rs | 13 +- .../src/transforms/note_interface.rs | 90 +- .../aztec_macros/src/transforms/storage.rs | 18 +- .../aztec_macros/src/utils/ast_utils.rs | 18 +- .../aztec_macros/src/utils/hir_utils.rs | 4 +- noir/noir-repo/aztec_macros/src/utils/mod.rs | 1 + .../aztec_macros/src/utils/parse_utils.rs | 544 ++++ noir/noir-repo/compiler/fm/src/file_map.rs | 18 +- .../circuits/recursion/src/main.nr | 7 +- .../test/node/prove_and_verify.test.ts | 142 +- .../compiler/noirc_driver/src/abi_gen.rs | 3 +- .../compiler/noirc_driver/src/debug.rs | 17 +- .../compiler/noirc_driver/src/lib.rs | 74 +- .../noirc_driver/tests/stdlib_warnings.rs | 2 +- .../compiler/noirc_errors/src/debug_info.rs | 6 +- .../compiler/noirc_errors/src/position.rs | 4 + .../compiler/noirc_evaluator/Cargo.toml | 2 + .../src/brillig/brillig_gen/brillig_block.rs | 7 +- .../compiler/noirc_evaluator/src/ssa.rs | 89 +- .../src/ssa/acir_gen/acir_ir/acir_variable.rs | 87 +- .../ssa/acir_gen/acir_ir/generated_acir.rs | 44 +- .../noirc_evaluator/src/ssa/acir_gen/mod.rs | 193 +- .../check_for_underconstrained_values.rs | 1 + .../src/ssa/function_builder/data_bus.rs | 103 +- .../src/ssa/function_builder/mod.rs | 2 + .../noirc_evaluator/src/ssa/ir/basic_block.rs | 3 +- .../noirc_evaluator/src/ssa/ir/cfg.rs | 3 + .../noirc_evaluator/src/ssa/ir/dfg.rs | 14 +- .../noirc_evaluator/src/ssa/ir/function.rs | 5 +- .../noirc_evaluator/src/ssa/ir/instruction.rs | 34 +- .../src/ssa/ir/instruction/binary.rs | 5 +- .../src/ssa/ir/instruction/call.rs | 25 +- .../noirc_evaluator/src/ssa/ir/map.rs | 63 +- .../noirc_evaluator/src/ssa/ir/printer.rs | 7 +- .../noirc_evaluator/src/ssa/ir/types.rs | 10 +- .../noirc_evaluator/src/ssa/ir/value.rs | 3 +- .../noirc_evaluator/src/ssa/opt/die.rs | 4 +- .../src/ssa/opt/flatten_cfg.rs | 83 +- .../src/ssa/opt/flatten_cfg/value_merger.rs | 37 +- .../noirc_evaluator/src/ssa/opt/inlining.rs | 17 +- .../src/ssa/opt/remove_enable_side_effects.rs | 1 + .../src/ssa/opt/remove_if_else.rs | 3 + .../src/ssa/opt/simplify_cfg.rs | 14 +- .../noirc_evaluator/src/ssa/opt/unrolling.rs | 28 +- .../src/ssa/ssa_gen/context.rs | 2 +- .../noirc_evaluator/src/ssa/ssa_gen/mod.rs | 2 +- .../src/ssa/ssa_gen/program.rs | 50 + .../compiler/noirc_frontend/Cargo.toml | 4 +- .../noirc_frontend/src/ast/expression.rs | 70 +- .../compiler/noirc_frontend/src/ast/mod.rs | 39 +- .../noirc_frontend/src/ast/statement.rs | 123 +- .../noirc_frontend/src/ast/structure.rs | 1 - .../compiler/noirc_frontend/src/ast/traits.rs | 3 - .../compiler/noirc_frontend/src/debug/mod.rs | 76 +- .../noirc_frontend/src/elaborator/comptime.rs | 418 +++- .../src/elaborator/expressions.rs | 32 +- .../noirc_frontend/src/elaborator/mod.rs | 593 ++--- .../noirc_frontend/src/elaborator/patterns.rs | 80 +- .../noirc_frontend/src/elaborator/scope.rs | 45 +- .../noirc_frontend/src/elaborator/traits.rs | 29 +- .../noirc_frontend/src/elaborator/types.rs | 177 +- .../noirc_frontend/src/elaborator/unquote.rs | 2 +- .../noirc_frontend/src/hir/comptime/errors.rs | 311 ++- .../src/hir/comptime/hir_to_display_ast.rs | 27 +- .../src/hir/comptime/interpreter.rs | 413 +-- .../src/hir/comptime/interpreter/builtin.rs | 1005 ++++++-- .../interpreter/builtin/builtin_helpers.rs | 376 +++ .../src/hir/comptime/interpreter/foreign.rs | 15 +- .../src/hir/comptime/interpreter/unquote.rs | 27 +- .../noirc_frontend/src/hir/comptime/tests.rs | 2 +- .../noirc_frontend/src/hir/comptime/value.rs | 154 +- .../src/hir/def_collector/dc_crate.rs | 34 +- .../src/hir/def_collector/dc_mod.rs | 31 +- .../src/hir/def_collector/errors.rs | 19 + .../noirc_frontend/src/hir/def_map/mod.rs | 6 +- .../src/hir/def_map/module_data.rs | 6 +- .../compiler/noirc_frontend/src/hir/mod.rs | 11 +- .../src/hir/resolution/errors.rs | 14 +- .../src/hir/resolution/import.rs | 18 +- .../src/hir/type_check/errors.rs | 6 + .../noirc_frontend/src/hir_def/function.rs | 8 + .../noirc_frontend/src/hir_def/types.rs | 250 +- .../noirc_frontend/src/lexer/errors.rs | 7 + .../noirc_frontend/src/lexer/token.rs | 13 +- .../compiler/noirc_frontend/src/locations.rs | 4 +- .../src/monomorphization/ast.rs | 3 +- .../src/monomorphization/mod.rs | 126 +- .../noirc_frontend/src/node_interner.rs | 90 +- .../noirc_frontend/src/noir_parser.lalrpop | 18 +- .../noirc_frontend/src/parser/errors.rs | 6 + .../compiler/noirc_frontend/src/parser/mod.rs | 6 +- .../noirc_frontend/src/parser/parser.rs | 203 +- .../noirc_frontend/src/parser/parser.rs:28:9 | 45 + .../src/parser/parser/function.rs | 12 +- .../noirc_frontend/src/parser/parser/path.rs | 89 +- .../src/parser/parser/primitives.rs | 27 +- .../src/parser/parser/structs.rs | 13 +- .../src/parser/parser/traits.rs | 52 +- .../noirc_frontend/src/parser/parser/types.rs | 38 +- .../compiler/noirc_frontend/src/tests.rs | 429 +++- noir/noir-repo/compiler/wasm/package.json | 2 +- noir/noir-repo/cspell.json | 3 + .../docs/docs/explainers/cspell.json | 5 + .../docs/explainers/explainer-writing-noir.md | 173 ++ .../getting_started/backend/_category_.json | 6 + .../docs/getting_started/backend/index.md | 31 + .../getting_started/barretenberg/index.md | 47 - .../docs/getting_started/hello_noir/index.md | 64 +- .../hello_noir/project_breakdown.md | 3 +- .../getting_started/installation/index.md | 6 +- .../docs/noir/concepts/data_types/arrays.md | 20 + .../docs/noir/standard_library/recursion.md | 12 +- .../tooling => reference}/noir_codegen.md | 2 +- .../docs/docs/tutorials/noirjs_app.md | 123 +- noir/noir-repo/docs/docusaurus.config.ts | 1 - .../docs/src/components/Notes/_blackbox.mdx | 2 +- .../getting_started/01_hello_world.md | 2 +- .../data_types/02_booleans.md | 4 +- .../language_concepts/data_types/04_arrays.md | 2 +- .../modules_packages_crates/dependencies.md | 2 +- .../version-v0.19.4/nargo/01_commands.md | 2 +- .../standard_library/black_box_fns.md | 18 +- .../getting_started/backend/_category_.json | 6 + .../getting_started/backend/index.md | 31 + .../getting_started/barretenberg/index.md | 47 - .../getting_started/hello_noir/index.md | 59 +- .../hello_noir/project_breakdown.md | 4 +- .../getting_started/installation/index.md | 6 +- .../version-v0.31.0/tutorials/noirjs_app.md | 109 +- .../getting_started/backend/_category_.json | 6 + .../getting_started/backend/index.md | 31 + .../getting_started/barretenberg/index.md | 47 - .../getting_started/hello_noir/index.md | 59 +- .../hello_noir/project_breakdown.md | 3 +- .../getting_started/installation/index.md | 6 +- .../version-v0.32.0/tutorials/noirjs_app.md | 109 +- .../version-v0.33.0/explainers/cspell.json | 5 + .../explainers/explainer-oracle.md | 57 + .../explainers/explainer-recursion.md | 176 ++ .../explainers/explainer-writing-noir.md | 173 ++ .../getting_started/_category_.json | 5 + .../getting_started/backend/_category_.json | 6 + .../getting_started/backend/index.md | 31 + .../hello_noir/_category_.json | 5 + .../getting_started/hello_noir/index.md | 152 ++ .../hello_noir/project_breakdown.md | 159 ++ .../installation/_category_.json | 6 + .../getting_started/installation/index.md | 46 + .../installation/other_install_methods.md | 102 + .../how_to}/_category_.json | 1 - .../how_to/debugger/_category_.json | 6 + .../debugger/debugging_with_the_repl.md | 164 ++ .../how_to/debugger/debugging_with_vs_code.md | 68 + .../version-v0.33.0/how_to/how-to-oracles.md | 273 ++ .../how_to/how-to-recursion.md | 180 ++ .../how_to/how-to-solidity-verifier.md | 251 ++ .../version-v0.33.0/how_to/merkle-proof.mdx | 48 + .../how_to/using-devcontainers.mdx | 110 + .../versioned_docs/version-v0.33.0/index.mdx | 67 + .../version-v0.33.0/migration_notes.md | 105 + .../noir/concepts/_category_.json | 6 + .../version-v0.33.0/noir/concepts/assert.md | 78 + .../version-v0.33.0/noir/concepts/comments.md | 33 + .../noir/concepts/control_flow.md | 77 + .../noir/concepts/data_bus.mdx | 23 + .../noir/concepts/data_types/_category_.json | 5 + .../noir/concepts/data_types/arrays.md | 253 ++ .../noir/concepts/data_types/booleans.md | 28 + .../noir/concepts/data_types/fields.md | 192 ++ .../concepts/data_types/function_types.md | 26 + .../noir/concepts/data_types/index.md | 118 + .../noir/concepts/data_types/integers.md | 156 ++ .../noir/concepts/data_types/references.md | 23 + .../noir/concepts/data_types/slices.mdx | 358 +++ .../noir/concepts/data_types/strings.md | 79 + .../noir/concepts/data_types/structs.md | 70 + .../noir/concepts/data_types/tuples.md | 48 + .../noir/concepts/functions.md | 226 ++ .../version-v0.33.0/noir/concepts/generics.md | 163 ++ .../version-v0.33.0/noir/concepts/globals.md | 72 + .../version-v0.33.0/noir/concepts/lambdas.md | 81 + .../noir/concepts/mutability.md | 121 + .../version-v0.33.0/noir/concepts/ops.md | 98 + .../version-v0.33.0/noir/concepts/oracles.mdx | 29 + .../noir/concepts/shadowing.md | 44 + .../version-v0.33.0/noir/concepts/traits.md | 405 +++ .../noir/concepts/unconstrained.md | 99 + .../modules_packages_crates/_category_.json | 6 + .../crates_and_packages.md | 43 + .../modules_packages_crates/dependencies.md | 124 + .../noir/modules_packages_crates/modules.md | 185 ++ .../modules_packages_crates/workspaces.md | 42 + .../noir/standard_library}/_category_.json | 2 +- .../noir/standard_library/bigint.md | 122 + .../noir/standard_library/black_box_fns.md | 32 + .../noir/standard_library/bn254.md | 46 + .../standard_library/containers/boundedvec.md | 419 ++++ .../standard_library/containers/hashmap.md | 570 +++++ .../noir/standard_library/containers/index.md | 5 + .../noir/standard_library/containers/vec.mdx | 170 ++ .../cryptographic_primitives/_category_.json | 5 + .../cryptographic_primitives/ciphers.mdx | 32 + .../cryptographic_primitives/ec_primitives.md | 102 + .../ecdsa_sig_verification.mdx | 98 + .../cryptographic_primitives/eddsa.mdx | 37 + .../embedded_curve_ops.mdx | 95 + .../cryptographic_primitives/hashes.mdx | 253 ++ .../cryptographic_primitives/index.md | 14 + .../cryptographic_primitives/schnorr.mdx | 64 + .../noir/standard_library/is_unconstrained.md | 69 + .../noir/standard_library/logging.md | 78 + .../noir/standard_library/merkle_trees.md | 58 + .../noir/standard_library/options.md | 101 + .../noir/standard_library/recursion.md | 85 + .../noir/standard_library/traits.md | 501 ++++ .../noir/standard_library/zeroed.md | 26 + .../NoirJS/backend_barretenberg/.nojekyll | 1 + .../classes/BarretenbergBackend.md | 141 ++ .../classes/BarretenbergVerifier.md | 58 + .../classes/UltraHonkBackend.md | 116 + .../classes/UltraHonkVerifier.md | 58 + .../NoirJS/backend_barretenberg/index.md | 42 + .../type-aliases/BackendOptions.md | 21 + .../backend_barretenberg/typedoc-sidebar.cjs | 4 + .../reference/NoirJS/noir_js/.nojekyll | 1 + .../reference/NoirJS/noir_js/classes/Noir.md | 52 + .../reference/NoirJS/noir_js/functions/and.md | 22 + .../NoirJS/noir_js/functions/blake2s256.md | 21 + .../functions/ecdsa_secp256k1_verify.md | 28 + .../functions/ecdsa_secp256r1_verify.md | 28 + .../NoirJS/noir_js/functions/keccak256.md | 21 + .../NoirJS/noir_js/functions/sha256.md | 21 + .../reference/NoirJS/noir_js/functions/xor.md | 22 + .../reference/NoirJS/noir_js/index.md | 49 + .../noir_js/type-aliases/ErrorWithPayload.md | 15 + .../type-aliases/ForeignCallHandler.md | 24 + .../noir_js/type-aliases/ForeignCallInput.md | 9 + .../noir_js/type-aliases/ForeignCallOutput.md | 9 + .../NoirJS/noir_js/type-aliases/WitnessMap.md | 9 + .../NoirJS/noir_js/typedoc-sidebar.cjs | 4 + .../reference/NoirJS/noir_wasm/.nojekyll | 1 + .../NoirJS/noir_wasm/functions/compile.md | 51 + .../noir_wasm/functions/compile_contract.md | 51 + .../noir_wasm/functions/createFileManager.md | 21 + .../functions/inflateDebugSymbols.md | 21 + .../reference/NoirJS/noir_wasm/index.md | 49 + .../NoirJS/noir_wasm/typedoc-sidebar.cjs | 4 + .../version-v0.33.0/reference/_category_.json | 5 + .../reference/debugger}/_category_.json | 2 +- .../debugger/debugger_known_limitations.md | 59 + .../reference/debugger/debugger_repl.md | 360 +++ .../reference/debugger/debugger_vscode.md | 82 + .../reference/nargo_commands.md | 289 +++ .../version-v0.33.0/reference/noir_codegen.md | 114 + .../version-v0.33.0/tooling/debugger.md | 26 + .../tooling/language_server.md | 43 + .../version-v0.33.0/tooling/testing.md | 79 + .../version-v0.33.0/tutorials/noirjs_app.md | 362 +++ .../version-v0.33.0-sidebars.json | 93 + .../recursion/recurse_leaf/src/main.nr | 7 +- .../recursion/recurse_node/src/main.nr | 7 +- noir/noir-repo/noir_stdlib/src/array.nr | 17 +- noir/noir-repo/noir_stdlib/src/cmp.nr | 26 + noir/noir-repo/noir_stdlib/src/default.nr | 11 + noir/noir-repo/noir_stdlib/src/hash/mod.nr | 22 +- .../noir_stdlib/src/hash/poseidon2.nr | 2 +- noir/noir-repo/noir_stdlib/src/lib.nr | 7 +- noir/noir-repo/noir_stdlib/src/meta/expr.nr | 6 + .../noir_stdlib/src/meta/function_def.nr | 19 + noir/noir-repo/noir_stdlib/src/meta/mod.nr | 79 +- noir/noir-repo/noir_stdlib/src/meta/module.nr | 10 + noir/noir-repo/noir_stdlib/src/meta/quoted.nr | 22 + .../src/meta/{type_def.nr => struct_def.nr} | 9 +- .../noir_stdlib/src/meta/trait_def.nr | 21 + noir/noir-repo/noir_stdlib/src/meta/typ.nr | 40 + noir/noir-repo/noir_stdlib/src/prelude.nr | 1 + noir/noir-repo/noir_stdlib/src/string.nr | 8 + noir/noir-repo/noir_stdlib/src/uint128.nr | 4 +- noir/noir-repo/scripts/install_bb.sh | 2 +- .../non_comptime_local_fn_call/Nargo.toml | 7 - .../non_comptime_local_fn_call/src/main.nr | 9 - .../Nargo.toml | 7 + .../src/main.nr | 6 + .../Nargo.toml | 7 + .../src/main.nr | 12 + .../arithmetic_generics/Nargo.toml | 7 + .../arithmetic_generics/src/main.nr | 103 + .../attribute_args/src/main.nr | 11 +- .../comptime_closures/Nargo.toml | 7 + .../comptime_closures/src/main.nr | 39 + .../comptime_exp/Nargo.toml | 7 + .../comptime_exp/src/main.nr | 8 + .../comptime_fmt_strings/Nargo.toml | 7 + .../comptime_fmt_strings/src/main.nr | 15 + .../comptime_function_definition/Nargo.toml | 7 + .../comptime_function_definition/src/main.nr | 57 + .../comptime_module/Nargo.toml | 7 + .../comptime_module/src/main.nr | 26 + .../comptime_trait_constraint/src/main.nr | 8 +- .../comptime_traits/src/main.nr | 4 +- .../comptime_type/Nargo.toml | 7 + .../comptime_type/src/main.nr | 121 + .../derive_impl/src/main.nr | 4 +- .../quoted_as_type/Nargo.toml | 7 + .../quoted_as_type/src/main.nr | 21 + .../regression_5671/Nargo.toml | 7 + .../regression_5671/src/main.nr | 20 + .../trait_call_in_global}/Nargo.toml | 2 +- .../trait_call_in_global/src/main.nr | 5 + .../zeroed_slice/Nargo.toml | 7 + .../zeroed_slice/src/main.nr | 3 + .../execution_success/databus/src/main.nr | 2 +- .../execution_success/derive/Nargo.toml | 7 + .../execution_success/derive/src/main.nr | 70 + .../double_verify_nested_proof/src/main.nr | 14 +- .../double_verify_proof/src/main.nr | 14 +- .../double_verify_proof_recursive/src/main.nr | 14 +- .../regression_5615/Nargo.toml | 7 + .../regression_5615/src/main.nr | 12 + .../execution_success/slice_regex/Nargo.toml | 7 + .../execution_success/slice_regex/src/main.nr | 811 ++++++ .../verify_honk_proof/Prover.toml | 4 - .../verify_honk_proof/src/main.nr | 21 - .../noir-repo/tooling/debugger/src/context.rs | 290 ++- noir/noir-repo/tooling/debugger/src/repl.rs | 41 +- .../debugger/src/source_code_printer.rs | 1 + noir/noir-repo/tooling/fuzzer/Cargo.toml | 1 - noir/noir-repo/tooling/fuzzer/src/lib.rs | 36 +- noir/noir-repo/tooling/lsp/Cargo.toml | 1 + noir/noir-repo/tooling/lsp/src/lib.rs | 69 +- .../tooling/lsp/src/notifications/mod.rs | 58 +- .../lsp/src/requests/code_lens_request.rs | 5 +- .../tooling/lsp/src/requests/completion.rs | 2204 +++++++++++++++++ .../lsp/src/requests/completion/builtins.rs | 127 + .../lsp/src/requests/document_symbol.rs | 2 +- .../tooling/lsp/src/requests/hover.rs | 212 +- .../tooling/lsp/src/requests/inlay_hint.rs | 350 ++- .../noir-repo/tooling/lsp/src/requests/mod.rs | 81 +- .../tooling/lsp/src/requests/references.rs | 6 +- .../tooling/lsp/src/requests/test_run.rs | 2 +- .../tooling/lsp/src/requests/tests.rs | 2 +- noir/noir-repo/tooling/lsp/src/types.rs | 9 +- noir/noir-repo/tooling/lsp/src/utils.rs | 59 + .../lsp/test_programs/inlay_hints/src/main.nr | 28 + noir/noir-repo/tooling/nargo/Cargo.toml | 4 + noir/noir-repo/tooling/nargo/src/errors.rs | 23 +- .../tooling/nargo/src/ops/compile.rs | 8 +- noir/noir-repo/tooling/nargo/src/ops/test.rs | 84 +- noir/noir-repo/tooling/nargo_cli/Cargo.toml | 2 - noir/noir-repo/tooling/nargo_cli/build.rs | 2 +- .../tooling/nargo_cli/src/cli/check_cmd.rs | 19 +- .../tooling/nargo_cli/src/cli/compile_cmd.rs | 9 +- .../tooling/nargo_cli/src/cli/debug_cmd.rs | 10 +- .../tooling/nargo_cli/src/cli/export_cmd.rs | 9 +- .../tooling/nargo_cli/src/cli/fs/program.rs | 1 - .../tooling/nargo_cli/src/cli/test_cmd.rs | 71 +- .../tooling/nargo_cli/tests/stdlib-tests.rs | 58 +- noir/noir-repo/tooling/nargo_fmt/src/items.rs | 4 - .../tooling/nargo_fmt/src/rewrite/expr.rs | 33 +- .../tooling/nargo_fmt/src/rewrite/typ.rs | 1 + noir/noir-repo/tooling/nargo_fmt/src/utils.rs | 10 +- .../tooling/nargo_fmt/src/visitor/item.rs | 5 +- .../tooling/nargo_fmt/tests/expected/expr.nr | 4 +- .../tooling/nargo_fmt/tests/input/expr.nr | 2 +- .../tooling/noir_codegen/package.json | 2 +- noir/noir-repo/tooling/noir_js/package.json | 2 +- .../noir_js_backend_barretenberg/package.json | 4 +- .../src/backend.ts | 144 +- .../noir_js_backend_barretenberg/src/index.ts | 4 +- .../src/public_inputs.ts | 10 +- .../src/verifier.ts | 80 +- .../tooling/noir_js_types/package.json | 2 +- .../noir-repo/tooling/noirc_abi_wasm/build.sh | 2 +- .../tooling/noirc_abi_wasm/package.json | 2 +- .../tooling/noirc_artifacts/src/debug.rs | 18 +- .../tooling/profiler/src/flamegraph.rs | 1 + noir/noir-repo/yarn.lock | 13 +- 409 files changed, 23427 insertions(+), 3219 deletions(-) delete mode 100644 noir/noir-repo/.github/ISSUE_TEMPLATE/bug_report.yml delete mode 100644 noir/noir-repo/.github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 noir/noir-repo/acvm-repo/acvm/tests/solver.proptest-regressions create mode 100644 noir/noir-repo/aztec_macros/src/utils/parse_utils.rs create mode 100644 noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs create mode 100644 noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs:28:9 create mode 100644 noir/noir-repo/docs/docs/explainers/cspell.json create mode 100644 noir/noir-repo/docs/docs/explainers/explainer-writing-noir.md create mode 100644 noir/noir-repo/docs/docs/getting_started/backend/_category_.json create mode 100644 noir/noir-repo/docs/docs/getting_started/backend/index.md delete mode 100644 noir/noir-repo/docs/docs/getting_started/barretenberg/index.md rename noir/noir-repo/docs/docs/{getting_started/tooling => reference}/noir_codegen.md (97%) create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.31.0/getting_started/backend/_category_.json create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.31.0/getting_started/backend/index.md delete mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.31.0/getting_started/barretenberg/index.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.32.0/getting_started/backend/_category_.json create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.32.0/getting_started/backend/index.md delete mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.32.0/getting_started/barretenberg/index.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/explainers/cspell.json create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/explainers/explainer-oracle.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/explainers/explainer-recursion.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/explainers/explainer-writing-noir.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/_category_.json create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/backend/_category_.json create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/backend/index.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/hello_noir/_category_.json create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/hello_noir/index.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/hello_noir/project_breakdown.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/installation/_category_.json create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/installation/index.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/installation/other_install_methods.md rename noir/noir-repo/docs/versioned_docs/{version-v0.31.0/getting_started/barretenberg => version-v0.33.0/how_to}/_category_.json (65%) create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/debugger/_category_.json create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/debugger/debugging_with_the_repl.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/debugger/debugging_with_vs_code.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/how-to-oracles.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/how-to-recursion.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/how-to-solidity-verifier.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/merkle-proof.mdx create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/using-devcontainers.mdx create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/index.mdx create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/migration_notes.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/_category_.json create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/assert.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/comments.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/control_flow.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_bus.mdx create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/_category_.json create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/arrays.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/booleans.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/fields.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/function_types.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/index.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/integers.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/references.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/slices.mdx create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/strings.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/structs.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/tuples.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/functions.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/generics.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/globals.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/lambdas.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/mutability.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/ops.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/oracles.mdx create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/shadowing.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/traits.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/unconstrained.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/modules_packages_crates/_category_.json create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/modules_packages_crates/crates_and_packages.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/modules_packages_crates/dependencies.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/modules_packages_crates/modules.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/modules_packages_crates/workspaces.md rename noir/noir-repo/docs/versioned_docs/{version-v0.32.0/getting_started/barretenberg => version-v0.33.0/noir/standard_library}/_category_.json (65%) create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/bigint.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/black_box_fns.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/bn254.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/containers/boundedvec.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/containers/hashmap.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/containers/index.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/containers/vec.mdx create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/_category_.json create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/ciphers.mdx create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/ec_primitives.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/eddsa.mdx create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/hashes.mdx create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/index.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/schnorr.mdx create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/is_unconstrained.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/logging.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/merkle_trees.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/options.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/recursion.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/traits.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/zeroed.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/backend_barretenberg/.nojekyll create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/backend_barretenberg/classes/BarretenbergBackend.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/backend_barretenberg/classes/BarretenbergVerifier.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/backend_barretenberg/classes/UltraHonkBackend.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/backend_barretenberg/classes/UltraHonkVerifier.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/backend_barretenberg/index.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/backend_barretenberg/type-aliases/BackendOptions.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/backend_barretenberg/typedoc-sidebar.cjs create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/.nojekyll create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/classes/Noir.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/functions/and.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/functions/blake2s256.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/functions/ecdsa_secp256k1_verify.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/functions/ecdsa_secp256r1_verify.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/functions/keccak256.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/functions/sha256.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/functions/xor.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/index.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/type-aliases/ErrorWithPayload.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/type-aliases/ForeignCallHandler.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/type-aliases/ForeignCallInput.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/type-aliases/ForeignCallOutput.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/type-aliases/WitnessMap.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/typedoc-sidebar.cjs create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_wasm/.nojekyll create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_wasm/functions/compile.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_wasm/functions/compile_contract.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_wasm/functions/createFileManager.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_wasm/functions/inflateDebugSymbols.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_wasm/index.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_wasm/typedoc-sidebar.cjs create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/_category_.json rename noir/noir-repo/docs/{docs/getting_started/barretenberg => versioned_docs/version-v0.33.0/reference/debugger}/_category_.json (65%) create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/debugger/debugger_known_limitations.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/debugger/debugger_repl.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/debugger/debugger_vscode.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/nargo_commands.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/noir_codegen.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/tooling/debugger.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/tooling/language_server.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/tooling/testing.md create mode 100644 noir/noir-repo/docs/versioned_docs/version-v0.33.0/tutorials/noirjs_app.md create mode 100644 noir/noir-repo/docs/versioned_sidebars/version-v0.33.0-sidebars.json create mode 100644 noir/noir-repo/noir_stdlib/src/meta/expr.nr create mode 100644 noir/noir-repo/noir_stdlib/src/meta/function_def.nr create mode 100644 noir/noir-repo/noir_stdlib/src/meta/module.nr rename noir/noir-repo/noir_stdlib/src/meta/{type_def.nr => struct_def.nr} (58%) create mode 100644 noir/noir-repo/noir_stdlib/src/meta/typ.nr delete mode 100644 noir/noir-repo/test_programs/compile_failure/non_comptime_local_fn_call/Nargo.toml delete mode 100644 noir/noir-repo/test_programs/compile_failure/non_comptime_local_fn_call/src/main.nr create mode 100644 noir/noir-repo/test_programs/compile_failure/type_annotation_needed_on_struct_constructor/Nargo.toml create mode 100644 noir/noir-repo/test_programs/compile_failure/type_annotation_needed_on_struct_constructor/src/main.nr create mode 100644 noir/noir-repo/test_programs/compile_failure/type_annotation_needed_on_struct_new/Nargo.toml create mode 100644 noir/noir-repo/test_programs/compile_failure/type_annotation_needed_on_struct_new/src/main.nr create mode 100644 noir/noir-repo/test_programs/compile_success_empty/arithmetic_generics/Nargo.toml create mode 100644 noir/noir-repo/test_programs/compile_success_empty/arithmetic_generics/src/main.nr create mode 100644 noir/noir-repo/test_programs/compile_success_empty/comptime_closures/Nargo.toml create mode 100644 noir/noir-repo/test_programs/compile_success_empty/comptime_closures/src/main.nr create mode 100644 noir/noir-repo/test_programs/compile_success_empty/comptime_exp/Nargo.toml create mode 100644 noir/noir-repo/test_programs/compile_success_empty/comptime_exp/src/main.nr create mode 100644 noir/noir-repo/test_programs/compile_success_empty/comptime_fmt_strings/Nargo.toml create mode 100644 noir/noir-repo/test_programs/compile_success_empty/comptime_fmt_strings/src/main.nr create mode 100644 noir/noir-repo/test_programs/compile_success_empty/comptime_function_definition/Nargo.toml create mode 100644 noir/noir-repo/test_programs/compile_success_empty/comptime_function_definition/src/main.nr create mode 100644 noir/noir-repo/test_programs/compile_success_empty/comptime_module/Nargo.toml create mode 100644 noir/noir-repo/test_programs/compile_success_empty/comptime_module/src/main.nr create mode 100644 noir/noir-repo/test_programs/compile_success_empty/comptime_type/Nargo.toml create mode 100644 noir/noir-repo/test_programs/compile_success_empty/comptime_type/src/main.nr create mode 100644 noir/noir-repo/test_programs/compile_success_empty/quoted_as_type/Nargo.toml create mode 100644 noir/noir-repo/test_programs/compile_success_empty/quoted_as_type/src/main.nr create mode 100644 noir/noir-repo/test_programs/compile_success_empty/regression_5671/Nargo.toml create mode 100644 noir/noir-repo/test_programs/compile_success_empty/regression_5671/src/main.nr rename noir/noir-repo/test_programs/{execution_success/verify_honk_proof => compile_success_empty/trait_call_in_global}/Nargo.toml (64%) create mode 100644 noir/noir-repo/test_programs/compile_success_empty/trait_call_in_global/src/main.nr create mode 100644 noir/noir-repo/test_programs/compile_success_empty/zeroed_slice/Nargo.toml create mode 100644 noir/noir-repo/test_programs/compile_success_empty/zeroed_slice/src/main.nr create mode 100644 noir/noir-repo/test_programs/execution_success/derive/Nargo.toml create mode 100644 noir/noir-repo/test_programs/execution_success/derive/src/main.nr create mode 100644 noir/noir-repo/test_programs/execution_success/regression_5615/Nargo.toml create mode 100644 noir/noir-repo/test_programs/execution_success/regression_5615/src/main.nr create mode 100644 noir/noir-repo/test_programs/execution_success/slice_regex/Nargo.toml create mode 100644 noir/noir-repo/test_programs/execution_success/slice_regex/src/main.nr delete mode 100644 noir/noir-repo/test_programs/execution_success/verify_honk_proof/Prover.toml delete mode 100644 noir/noir-repo/test_programs/execution_success/verify_honk_proof/src/main.nr create mode 100644 noir/noir-repo/tooling/lsp/src/requests/completion.rs create mode 100644 noir/noir-repo/tooling/lsp/src/requests/completion/builtins.rs create mode 100644 noir/noir-repo/tooling/lsp/src/utils.rs diff --git a/.noir-sync-commit b/.noir-sync-commit index b2216c735d46..d575ffe3e8c9 100644 --- a/.noir-sync-commit +++ b/.noir-sync-commit @@ -1 +1 @@ -453ed590ae3ae6ee8a8d3113419fc51b825b2538 +0ebf1fee471641db0bffcc8307d20327613c78c1 diff --git a/noir/noir-repo/.github/ISSUE_TEMPLATE/bug_report.yml b/noir/noir-repo/.github/ISSUE_TEMPLATE/bug_report.yml deleted file mode 100644 index 71207793e53f..000000000000 --- a/noir/noir-repo/.github/ISSUE_TEMPLATE/bug_report.yml +++ /dev/null @@ -1,120 +0,0 @@ -name: Bug Report -description: Report an unexpected behavior. -labels: ["bug"] -body: - - type: markdown - attributes: - value: | - # Description - Thanks for taking the time to create the Issue and welcome to the Noir community! - - type: textarea - id: aim - attributes: - label: Aim - description: Describe what you tried to achieve. - validations: - required: true - - type: textarea - id: expected - attributes: - label: Expected Behavior - description: Describe what you expected to happen. - validations: - required: true - - type: textarea - id: bug - attributes: - label: Bug - description: Describe the bug. Supply error codes / terminal logs if applicable. - validations: - required: true - - type: textarea - id: reproduction - attributes: - label: To Reproduce - description: Describe the steps to reproduce the behavior. - value: | - 1. - 2. - 3. - 4. - - type: dropdown - id: impact - attributes: - label: Project Impact - description: How does this affect a project you or others are working on? - options: - - "Nice-to-have" - - "Blocker" - - type: textarea - id: impact_context - attributes: - label: Impact Context - description: If a nice-to-have / blocker, supplement how does this Issue affect the project. - - type: dropdown - id: workaround - attributes: - label: Workaround - description: Is there a workaround for this Issue? - options: - - "Yes" - - type: textarea - id: workaround_description - attributes: - label: Workaround Description - description: If yes, supplement how could the Issue be worked around. - - type: textarea - id: additional - attributes: - label: Additional Context - description: Supplement further information if applicable. - - type: markdown - attributes: - value: | - # Environment - Specify your version of Noir tooling used. - - type: markdown - attributes: - value: | - ## Nargo (CLI) - - type: dropdown - id: nargo-install - attributes: - label: Installation Method - description: How did you install Nargo? - options: - - Binary (`noirup` default) - - Compiled from source - - type: input - id: nargo-version - attributes: - label: Nargo Version - description: Output of running `nargo --version` - placeholder: "nargo version = 0.23.0 noirc version = 0.23.0+5be9f9d7e2f39ca228df10e5a530474af0331704 (git version hash: 5be9f9d7e2f39ca228df10e5a530474af0331704, is dirty: false)" - - type: markdown - attributes: - value: | - ## NoirJS (JavaScript) - - type: input - id: noirjs-version - attributes: - label: NoirJS Version - description: Version number of `noir_js` in `package.json` - placeholder: "0.23.0" - - type: markdown - attributes: - value: | - # Pull Request - - type: dropdown - id: pr_preference - attributes: - label: Would you like to submit a PR for this Issue? - description: Fellow contributors are happy to provide support where applicable. - options: - - "Maybe" - - "Yes" - - type: textarea - id: pr_support - attributes: - label: Support Needs - description: Support from other contributors you are looking for to create a PR for this Issue. diff --git a/noir/noir-repo/.github/ISSUE_TEMPLATE/feature_request.yml b/noir/noir-repo/.github/ISSUE_TEMPLATE/feature_request.yml deleted file mode 100644 index abbfe392454b..000000000000 --- a/noir/noir-repo/.github/ISSUE_TEMPLATE/feature_request.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: Feature Request -description: Suggest an idea for this project. -labels: ["enhancement"] -body: - - type: markdown - attributes: - value: | - ## Description - Thanks for taking the time to create the Issue and welcome to the Noir community! - - type: textarea - id: problem - attributes: - label: Problem - description: Describe what you feel lacking. Supply code / step-by-step examples if applicable. - validations: - required: true - - type: textarea - id: solution - attributes: - label: Happy Case - description: Describe how you think it should work. Supply pseudocode / step-by-step examples if applicable. - validations: - required: true - - type: dropdown - id: impact - attributes: - label: Project Impact - description: How does this affect a project you or others are working on? - options: - - "Nice-to-have" - - "Blocker" - - type: textarea - id: impact_context - attributes: - label: Impact Context - description: If a nice-to-have / blocker, supplement how does this Issue affect the project. - - type: dropdown - id: workaround - attributes: - label: Workaround - description: Is there a workaround for this Issue? - options: - - "Yes" - - type: textarea - id: workaround_description - attributes: - label: Workaround Description - description: If yes, supplement how could the Issue be worked around. - - type: textarea - id: additional - attributes: - label: Additional Context - description: Supplement further information if applicable. - - type: markdown - attributes: - value: | - ## Pull Request - - type: dropdown - id: pr-preference - attributes: - label: Would you like to submit a PR for this Issue? - description: Fellow contributors are happy to provide support where applicable. - multiple: false - options: - - "Maybe" - - "Yes" - - type: textarea - id: pr-support - attributes: - label: Support Needs - description: Support from other contributors you are looking for to create a PR for this Issue. diff --git a/noir/noir-repo/.release-please-manifest.json b/noir/noir-repo/.release-please-manifest.json index 8f269310c5d5..af171a74a630 100644 --- a/noir/noir-repo/.release-please-manifest.json +++ b/noir/noir-repo/.release-please-manifest.json @@ -1,4 +1,4 @@ { - ".": "0.32.0", - "acvm-repo": "0.48.0" + ".": "0.33.0", + "acvm-repo": "0.49.0" } diff --git a/noir/noir-repo/CHANGELOG.md b/noir/noir-repo/CHANGELOG.md index 1e32f8364a7f..d9b71a82e4b8 100644 --- a/noir/noir-repo/CHANGELOG.md +++ b/noir/noir-repo/CHANGELOG.md @@ -1,5 +1,96 @@ # Changelog +## [0.33.0](https://github.com/noir-lang/noir/compare/v0.32.0...v0.33.0) (2024-08-06) + + +### ⚠ BREAKING CHANGES + +* parse block and if statements independently of expressions in statements ([#5634](https://github.com/noir-lang/noir/issues/5634)) +* **frontend:** Restrict numeric generic types to unsigned ints up to `u32` ([#5581](https://github.com/noir-lang/noir/issues/5581)) + +### Features + +* **acir_gen:** Width aware ACIR gen addition ([#5493](https://github.com/noir-lang/noir/issues/5493)) ([85fa592](https://github.com/noir-lang/noir/commit/85fa592fdef3b8589ce03b232e1b51565837b540)) +* Add `FunctionDefinition::parameters`, `FunctionDefinition::return_type` and `impl Eq for Quoted` ([#5681](https://github.com/noir-lang/noir/issues/5681)) ([d52fc05](https://github.com/noir-lang/noir/commit/d52fc056ae5dec4457dd869d50dd9a6e5b0b5cb1)) +* Add `std::meta::type_of` and `impl Eq for Type` ([#5669](https://github.com/noir-lang/noir/issues/5669)) ([0503956](https://github.com/noir-lang/noir/commit/05039568e317c16cecc173717e63288e7ca3890c)) +* Add `TraitDefinition::as_trait_constraint()` ([#5541](https://github.com/noir-lang/noir/issues/5541)) ([0943223](https://github.com/noir-lang/noir/commit/094322381da67e2b9aef27b5558ba98a47abfccc)) +* Add `Type::as_struct` ([#5680](https://github.com/noir-lang/noir/issues/5680)) ([ade69a9](https://github.com/noir-lang/noir/commit/ade69a9e5f1546249e9b43b40e9ff0da87c4632e)) +* Add `Type::is_field` and `Type::as_integer` ([#5670](https://github.com/noir-lang/noir/issues/5670)) ([939357a](https://github.com/noir-lang/noir/commit/939357acae87eae833ff10572f7a1ad52b35d94e)) +* Add `Type` methods: `as_tuple`, `as_slice`, `as_array`, `as_constant`, `is_bool` ([#5678](https://github.com/noir-lang/noir/issues/5678)) ([604fa0d](https://github.com/noir-lang/noir/commit/604fa0d85c1a0c47c7c0b8402576afb61ceb664b)) +* Add a compile-time hash map type ([#5543](https://github.com/noir-lang/noir/issues/5543)) ([c6e5c4b](https://github.com/noir-lang/noir/commit/c6e5c4b304eb4e87ca519151f610e7e03b1a4fba)) +* Add a limited form of arithmetic on generics ([#5625](https://github.com/noir-lang/noir/issues/5625)) ([0afb680](https://github.com/noir-lang/noir/commit/0afb6805e3c3cbe0f84f39d9e2e57e038450547d)) +* Add parameter to call_data attribute ([#5599](https://github.com/noir-lang/noir/issues/5599)) ([e8bb341](https://github.com/noir-lang/noir/commit/e8bb3417d276d17db85b408b825e61c32fa20744)) +* Allow inserting LSP inlay type hints ([#5620](https://github.com/noir-lang/noir/issues/5620)) ([b33495d](https://github.com/noir-lang/noir/commit/b33495d0799f7c296cf6e284ea19abbbe5821793)) +* Avoid heap allocs when going to/from field (https://github.com/AztecProtocol/aztec-packages/pull/7547) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* Derive `Ord` and `Hash` in the stdlib; add `std::meta::make_impl` helper ([#5683](https://github.com/noir-lang/noir/issues/5683)) ([38397d3](https://github.com/noir-lang/noir/commit/38397d346aac4ec19026ede2776e776fdceb854c)) +* Don't eagerly error on cast expressions ([#5635](https://github.com/noir-lang/noir/issues/5635)) ([0ca5d9d](https://github.com/noir-lang/noir/commit/0ca5d9d7b389d24cf48fa62a29cb437b1855438a)) +* Implement `poseidon2_permutation` in comptime interpreter ([#5590](https://github.com/noir-lang/noir/issues/5590)) ([89dfbbf](https://github.com/noir-lang/noir/commit/89dfbbfe6efffcce9a44c39ccf7e92036a0e222a)) +* Implement `Value::Type` in comptime interpreter ([#5593](https://github.com/noir-lang/noir/issues/5593)) ([4c3bf97](https://github.com/noir-lang/noir/commit/4c3bf97fe7475f1027285cb5ad26b3c578a632b7)) +* Implement `zeroed` in the interpreter ([#5540](https://github.com/noir-lang/noir/issues/5540)) ([ff8ca91](https://github.com/noir-lang/noir/commit/ff8ca91efc925bf8bdbe7f2d2feb651981c5c1b9)) +* Implement closures in the comptime interpreter ([#5682](https://github.com/noir-lang/noir/issues/5682)) ([9e2a323](https://github.com/noir-lang/noir/commit/9e2a3232c8849f19732472e75840717b8b95a4a9)) +* Implement format strings in the comptime interpreter ([#5596](https://github.com/noir-lang/noir/issues/5596)) ([fd7002c](https://github.com/noir-lang/noir/commit/fd7002caaf15c297227ce53047dd3361674a527d)) +* Integrate new proving systems in e2e (https://github.com/AztecProtocol/aztec-packages/pull/6971) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* Let filenames in errors be relative to the current dir if possible ([#5642](https://github.com/noir-lang/noir/issues/5642)) ([f656681](https://github.com/noir-lang/noir/commit/f656681dfccf01bac2811835fc9ab482e54e3da8)) +* Let LSP work will with code generated by macros ([#5665](https://github.com/noir-lang/noir/issues/5665)) ([8122624](https://github.com/noir-lang/noir/commit/812262413770d2f20cba04eb0e3176320a3b704a)) +* LSP closing brace hints ([#5686](https://github.com/noir-lang/noir/issues/5686)) ([2b18151](https://github.com/noir-lang/noir/commit/2b18151168b4fa0b1a4173b11f047ff2fc338d28)) +* LSP hover now includes "Go to" links ([#5677](https://github.com/noir-lang/noir/issues/5677)) ([d466d49](https://github.com/noir-lang/noir/commit/d466d491ea50b495be7d5a45a8c3d85771f9b1c0)) +* LSP inlay parameter hints ([#5553](https://github.com/noir-lang/noir/issues/5553)) ([822fe2c](https://github.com/noir-lang/noir/commit/822fe2ce38184243789a97f79ee412b9cef614e2)) +* LSP inlay type hints on lambda parameters ([#5639](https://github.com/noir-lang/noir/issues/5639)) ([80128ff](https://github.com/noir-lang/noir/commit/80128ff0a5d54fc777ffef89a7acc27d347181e6)) +* Make Brillig do integer arithmetic operations using u128 instead of Bigint (https://github.com/AztecProtocol/aztec-packages/pull/7518) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* **noir_js:** Expose UltraHonk and integration tests ([#5656](https://github.com/noir-lang/noir/issues/5656)) ([4552b4f](https://github.com/noir-lang/noir/commit/4552b4f357f023a9d054a34b3dc94d7a659c7d09)) +* Remove 'comptime or separate crate' restriction on comptime code ([#5609](https://github.com/noir-lang/noir/issues/5609)) ([1cddf42](https://github.com/noir-lang/noir/commit/1cddf427b7f52b3cb394c8c4c682cfd176d5eb93)) +* Resolve arguments to attributes ([#5649](https://github.com/noir-lang/noir/issues/5649)) ([e139002](https://github.com/noir-lang/noir/commit/e1390020c5ffc9c252b47eeffb3219ffa20c4879)) +* **ssa:** Simple serialization of unoptimized SSA to file ([#5679](https://github.com/noir-lang/noir/issues/5679)) ([07ea107](https://github.com/noir-lang/noir/commit/07ea1077cad8a332970d9d5f40a520009976a033)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/7432) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/7444) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/7454) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/7512) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/7577) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/7583) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* Turbofish in struct pattern ([#5616](https://github.com/noir-lang/noir/issues/5616)) ([b3c408b](https://github.com/noir-lang/noir/commit/b3c408b62424c87f9be5b58c33be7d77e62af98e)) +* Turbofish operator in struct constructor ([#5607](https://github.com/noir-lang/noir/issues/5607)) ([106abd7](https://github.com/noir-lang/noir/commit/106abd71299a54dc68eb6ff39a0a8135b3f8eb49)) +* Turbofish operator on path segments ([#5603](https://github.com/noir-lang/noir/issues/5603)) ([0bb8372](https://github.com/noir-lang/noir/commit/0bb8372e118036a34709da37c26d11a539a86bb3)) +* Typing return values of embedded_curve_ops (https://github.com/AztecProtocol/aztec-packages/pull/7413) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) + + +### Bug Fixes + +* 'cannot eval non-comptime global' error ([#5586](https://github.com/noir-lang/noir/issues/5586)) ([0a987c7](https://github.com/noir-lang/noir/commit/0a987c774e0349e2cdc17ad2aaee634c732b8785)) +* `NoMatchingImplFound` in comptime code only ([#5617](https://github.com/noir-lang/noir/issues/5617)) ([28211a3](https://github.com/noir-lang/noir/commit/28211a397b810c661204b45b7da06f7cad345278)) +* Add trailing extra arguments for backend in gates_flamegraph (https://github.com/AztecProtocol/aztec-packages/pull/7472) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* Allow calling a trait method with paths that don't consist of exactly two segments ([#5577](https://github.com/noir-lang/noir/issues/5577)) ([88c0a40](https://github.com/noir-lang/noir/commit/88c0a40ea35c7a64e4361e3022286be6fa7da666)) +* Allow trailing comma when parsing where clauses ([#5594](https://github.com/noir-lang/noir/issues/5594)) ([75bfe13](https://github.com/noir-lang/noir/commit/75bfe134457788f912da5173c395d7fbe0bb731a)) +* Allow using Self for function calls ([#5629](https://github.com/noir-lang/noir/issues/5629)) ([b7e4f42](https://github.com/noir-lang/noir/commit/b7e4f424f3fc461122447c30989a3ce52ddea09d)) +* Correct span for prefix operator ([#5624](https://github.com/noir-lang/noir/issues/5624)) ([5824785](https://github.com/noir-lang/noir/commit/58247854fba4309991529317671280bccd5cf21f)) +* Correctly track sources for open LSP documents ([#5561](https://github.com/noir-lang/noir/issues/5561)) ([9e61e97](https://github.com/noir-lang/noir/commit/9e61e97a6cc5aecaf673742f4b333879eeb687d0)) +* Derive generic types ([#5674](https://github.com/noir-lang/noir/issues/5674)) ([19e58a9](https://github.com/noir-lang/noir/commit/19e58a91a3b1d0534d8b0198347e3a2fb5488599)) +* Don't panic when a macro fails to resolve ([#5537](https://github.com/noir-lang/noir/issues/5537)) ([6109ddc](https://github.com/noir-lang/noir/commit/6109ddc4a12a4f7593c87fcc42a059737febd470)) +* Elaborate struct & trait annotations in the correct module ([#5643](https://github.com/noir-lang/noir/issues/5643)) ([d0a957b](https://github.com/noir-lang/noir/commit/d0a957ba9bd743ae00959b0680e275a0a3992308)) +* Error on duplicate struct field ([#5585](https://github.com/noir-lang/noir/issues/5585)) ([3aed671](https://github.com/noir-lang/noir/commit/3aed671d2fdca661fdef160b3e2468ce10eda028)) +* Error on incorrect generic count for impl and type alias ([#5623](https://github.com/noir-lang/noir/issues/5623)) ([1f5d000](https://github.com/noir-lang/noir/commit/1f5d0007430cd5cf057ce61ebc87304bb8cb557c)) +* Error on trait impl generics count mismatch ([#5582](https://github.com/noir-lang/noir/issues/5582)) ([da3d607](https://github.com/noir-lang/noir/commit/da3d607fb30143f7fd4077765119f98e664f31f7)) +* Error on unbound generics in structs ([#5619](https://github.com/noir-lang/noir/issues/5619)) ([efef6b4](https://github.com/noir-lang/noir/commit/efef6b4c9f2ff4bce7e83bed004eb05332ac349f)) +* Filter comptime globals ([#5538](https://github.com/noir-lang/noir/issues/5538)) ([2adc6ac](https://github.com/noir-lang/noir/commit/2adc6ac372b41ab7f9e803311d3d733b1a5ca4fb)) +* Fix `uhashmap` test name ([#5563](https://github.com/noir-lang/noir/issues/5563)) ([d5de83f](https://github.com/noir-lang/noir/commit/d5de83f7fefe014c5e5d9c3d82ab6aff6fd3217c)) +* Fix occurs check ([#5535](https://github.com/noir-lang/noir/issues/5535)) ([51dd529](https://github.com/noir-lang/noir/commit/51dd529872da96ad7f7770d6bc5f7c23f5415b5a)) +* Fix where clause issue in items generated from attributes ([#5673](https://github.com/noir-lang/noir/issues/5673)) ([9a8cfc9](https://github.com/noir-lang/noir/commit/9a8cfc9cfbf1861ca2b6563030b315bfcfbab130)) +* **frontend:** Disallow signed numeric generics ([#5572](https://github.com/noir-lang/noir/issues/5572)) ([2b4853e](https://github.com/noir-lang/noir/commit/2b4853e71859f225acc123160e87c522212b16b5)) +* **frontend:** Error for when impl is stricter than trait ([#5343](https://github.com/noir-lang/noir/issues/5343)) ([ece033f](https://github.com/noir-lang/noir/commit/ece033fcbf90ffbea992b4519d40076bf573b7af)) +* **frontend:** Restrict numeric generic types to unsigned ints up to `u32` ([#5581](https://github.com/noir-lang/noir/issues/5581)) ([b85e764](https://github.com/noir-lang/noir/commit/b85e764c2156ebb68acb7fba68e63856f9d1235b)) +* Let a trait impl that relies on another trait work ([#5646](https://github.com/noir-lang/noir/issues/5646)) ([e00c370](https://github.com/noir-lang/noir/commit/e00c3705794657ea8f8faa16bc2325511567e185)) +* Let std::unsafe::zeroed() work for slices ([#5592](https://github.com/noir-lang/noir/issues/5592)) ([7daee20](https://github.com/noir-lang/noir/commit/7daee20a3c3628044e2e76b0a52abd5285a4432a)) +* Let trait calls work in globals ([#5602](https://github.com/noir-lang/noir/issues/5602)) ([c02a6f6](https://github.com/noir-lang/noir/commit/c02a6f64b1920ec5a1a05e20df34227cc95d7b0a)) +* Let unary traits work at comptime ([#5507](https://github.com/noir-lang/noir/issues/5507)) ([aa62d8a](https://github.com/noir-lang/noir/commit/aa62d8adce7ca2a4e021c8ec18b6056a7600fe95)) +* Lookup trait constraints methods in composite types ([#5595](https://github.com/noir-lang/noir/issues/5595)) ([cec6390](https://github.com/noir-lang/noir/commit/cec63902c52710ba7df433eda310296f7b7652d2)) +* Parse block and if statements independently of expressions in statements ([#5634](https://github.com/noir-lang/noir/issues/5634)) ([9341113](https://github.com/noir-lang/noir/commit/9341113840294d6d895d5ed9713ba551cf8a1db9)) +* Revert "feat: Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/7512)" (https://github.com/AztecProtocol/aztec-packages/pull/7558) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* Run macros within comptime contexts ([#5576](https://github.com/noir-lang/noir/issues/5576)) ([df44919](https://github.com/noir-lang/noir/commit/df449191a4edf669e1d3412789422177c54a9304)) +* Speed up LSP ([#5650](https://github.com/noir-lang/noir/issues/5650)) ([e5f1b36](https://github.com/noir-lang/noir/commit/e5f1b368c8894b3e37209b22c48fde8c82851cba)) +* **ssa:** More robust array deduplication check ([#5547](https://github.com/noir-lang/noir/issues/5547)) ([dd89b90](https://github.com/noir-lang/noir/commit/dd89b900dfabbea889f6f181aa562e73a87215b7)) +* Switch verify proof to arrays ([#5664](https://github.com/noir-lang/noir/issues/5664)) ([c1ed9fb](https://github.com/noir-lang/noir/commit/c1ed9fb3f6e6778b519685fe218178b52c12076f)) +* Type_of for pointer types ([#5536](https://github.com/noir-lang/noir/issues/5536)) ([edb3810](https://github.com/noir-lang/noir/commit/edb3810c6863e3c7802fcaece82eb6de21ebd71f)) +* Workaround from_slice with nested slices ([#5648](https://github.com/noir-lang/noir/issues/5648)) ([6310a55](https://github.com/noir-lang/noir/commit/6310a55163ba3ce84e50a6df68d8963cd5222bd9)) + ## [0.32.0](https://github.com/noir-lang/noir/compare/v0.31.0...v0.32.0) (2024-07-18) diff --git a/noir/noir-repo/Cargo.lock b/noir/noir-repo/Cargo.lock index f6011b705e54..bacbf939786b 100644 --- a/noir/noir-repo/Cargo.lock +++ b/noir/noir-repo/Cargo.lock @@ -4,7 +4,7 @@ version = 3 [[package]] name = "acir" -version = "0.48.0" +version = "0.49.0" dependencies = [ "acir_field", "base64 0.21.2", @@ -26,7 +26,7 @@ dependencies = [ [[package]] name = "acir_field" -version = "0.48.0" +version = "0.49.0" dependencies = [ "ark-bls12-381", "ark-bn254", @@ -40,7 +40,7 @@ dependencies = [ [[package]] name = "acvm" -version = "0.48.0" +version = "0.49.0" dependencies = [ "acir", "acvm_blackbox_solver", @@ -48,6 +48,7 @@ dependencies = [ "brillig_vm", "indexmap 1.9.3", "num-bigint", + "proptest", "serde", "thiserror", "tracing", @@ -55,7 +56,7 @@ dependencies = [ [[package]] name = "acvm_blackbox_solver" -version = "0.48.0" +version = "0.49.0" dependencies = [ "acir", "blake2", @@ -93,7 +94,7 @@ dependencies = [ [[package]] name = "acvm_js" -version = "0.48.0" +version = "0.49.0" dependencies = [ "acvm", "bn254_blackbox_solver", @@ -443,7 +444,7 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "aztec_macros" -version = "0.32.0" +version = "0.33.0" dependencies = [ "acvm", "convert_case 0.6.0", @@ -571,7 +572,7 @@ dependencies = [ [[package]] name = "bn254_blackbox_solver" -version = "0.48.0" +version = "0.49.0" dependencies = [ "acir", "acvm_blackbox_solver", @@ -589,7 +590,7 @@ dependencies = [ [[package]] name = "brillig" -version = "0.48.0" +version = "0.49.0" dependencies = [ "acir_field", "serde", @@ -597,7 +598,7 @@ dependencies = [ [[package]] name = "brillig_vm" -version = "0.48.0" +version = "0.49.0" dependencies = [ "acir", "acvm_blackbox_solver", @@ -1537,7 +1538,7 @@ dependencies = [ [[package]] name = "fm" -version = "0.32.0" +version = "0.33.0" dependencies = [ "codespan-reporting", "iter-extended", @@ -2102,7 +2103,7 @@ dependencies = [ [[package]] name = "iter-extended" -version = "0.32.0" +version = "0.33.0" [[package]] name = "itertools" @@ -2497,7 +2498,7 @@ dependencies = [ [[package]] name = "nargo" -version = "0.32.0" +version = "0.33.0" dependencies = [ "acvm", "fm", @@ -2507,11 +2508,13 @@ dependencies = [ "jsonrpc-core-client", "jsonrpc-derive", "jsonrpc-http-server", + "noir_fuzzer", "noirc_abi", "noirc_driver", "noirc_errors", "noirc_frontend", "noirc_printable_type", + "proptest", "rand 0.8.5", "rayon", "serde", @@ -2522,7 +2525,7 @@ dependencies = [ [[package]] name = "nargo_cli" -version = "0.32.0" +version = "0.33.0" dependencies = [ "acvm", "assert_cmd", @@ -2544,7 +2547,6 @@ dependencies = [ "nargo_fmt", "nargo_toml", "noir_debugger", - "noir_fuzzer", "noir_lsp", "noirc_abi", "noirc_artifacts", @@ -2557,7 +2559,6 @@ dependencies = [ "pprof 0.13.0", "predicates 2.1.5", "prettytable-rs", - "proptest", "rayon", "serde", "serde_json", @@ -2577,7 +2578,7 @@ dependencies = [ [[package]] name = "nargo_fmt" -version = "0.32.0" +version = "0.33.0" dependencies = [ "bytecount", "noirc_frontend", @@ -2589,7 +2590,7 @@ dependencies = [ [[package]] name = "nargo_toml" -version = "0.32.0" +version = "0.33.0" dependencies = [ "dirs", "fm", @@ -2669,7 +2670,7 @@ dependencies = [ [[package]] name = "noir_debugger" -version = "0.32.0" +version = "0.33.0" dependencies = [ "acvm", "assert_cmd", @@ -2693,10 +2694,9 @@ dependencies = [ [[package]] name = "noir_fuzzer" -version = "0.32.0" +version = "0.33.0" dependencies = [ "acvm", - "nargo", "noirc_abi", "noirc_artifacts", "proptest", @@ -2717,7 +2717,7 @@ dependencies = [ [[package]] name = "noir_lsp" -version = "0.32.0" +version = "0.33.0" dependencies = [ "acvm", "async-lsp", @@ -2736,6 +2736,7 @@ dependencies = [ "serde", "serde_json", "serde_with", + "strum", "thiserror", "tokio", "tower", @@ -2744,7 +2745,7 @@ dependencies = [ [[package]] name = "noir_profiler" -version = "0.32.0" +version = "0.33.0" dependencies = [ "acir", "clap", @@ -2766,7 +2767,7 @@ dependencies = [ [[package]] name = "noir_wasm" -version = "0.32.0" +version = "0.33.0" dependencies = [ "acvm", "build-data", @@ -2790,7 +2791,7 @@ dependencies = [ [[package]] name = "noirc_abi" -version = "0.32.0" +version = "0.33.0" dependencies = [ "acvm", "iter-extended", @@ -2809,7 +2810,7 @@ dependencies = [ [[package]] name = "noirc_abi_wasm" -version = "0.32.0" +version = "0.33.0" dependencies = [ "acvm", "build-data", @@ -2826,11 +2827,11 @@ dependencies = [ [[package]] name = "noirc_arena" -version = "0.32.0" +version = "0.33.0" [[package]] name = "noirc_artifacts" -version = "0.32.0" +version = "0.33.0" dependencies = [ "acvm", "codespan-reporting", @@ -2845,7 +2846,7 @@ dependencies = [ [[package]] name = "noirc_driver" -version = "0.32.0" +version = "0.33.0" dependencies = [ "acvm", "aztec_macros", @@ -2865,7 +2866,7 @@ dependencies = [ [[package]] name = "noirc_errors" -version = "0.32.0" +version = "0.33.0" dependencies = [ "acvm", "base64 0.21.2", @@ -2883,7 +2884,7 @@ dependencies = [ [[package]] name = "noirc_evaluator" -version = "0.32.0" +version = "0.33.0" dependencies = [ "acvm", "bn254_blackbox_solver", @@ -2896,13 +2897,15 @@ dependencies = [ "num-bigint", "proptest", "serde", + "serde_json", + "serde_with", "thiserror", "tracing", ] [[package]] name = "noirc_frontend" -version = "0.32.0" +version = "0.33.0" dependencies = [ "acvm", "base64 0.21.2", @@ -2935,7 +2938,7 @@ dependencies = [ [[package]] name = "noirc_printable_type" -version = "0.32.0" +version = "0.33.0" dependencies = [ "acvm", "iter-extended", diff --git a/noir/noir-repo/Cargo.toml b/noir/noir-repo/Cargo.toml index 2263a72d1eb0..635091700572 100644 --- a/noir/noir-repo/Cargo.toml +++ b/noir/noir-repo/Cargo.toml @@ -42,7 +42,7 @@ resolver = "2" [workspace.package] # x-release-please-start-version -version = "0.32.0" +version = "0.33.0" # x-release-please-end authors = ["The Noir Team "] edition = "2021" @@ -53,13 +53,13 @@ repository = "https://github.com/noir-lang/noir/" [workspace.dependencies] # ACVM workspace dependencies -acir_field = { version = "0.48.0", path = "acvm-repo/acir_field", default-features = false } -acir = { version = "0.48.0", path = "acvm-repo/acir", default-features = false } -acvm = { version = "0.48.0", path = "acvm-repo/acvm" } -brillig = { version = "0.48.0", path = "acvm-repo/brillig", default-features = false } -brillig_vm = { version = "0.48.0", path = "acvm-repo/brillig_vm", default-features = false } -acvm_blackbox_solver = { version = "0.48.0", path = "acvm-repo/blackbox_solver", default-features = false } -bn254_blackbox_solver = { version = "0.48.0", path = "acvm-repo/bn254_blackbox_solver", default-features = false } +acir_field = { version = "0.49.0", path = "acvm-repo/acir_field", default-features = false } +acir = { version = "0.49.0", path = "acvm-repo/acir", default-features = false } +acvm = { version = "0.49.0", path = "acvm-repo/acvm" } +brillig = { version = "0.49.0", path = "acvm-repo/brillig", default-features = false } +brillig_vm = { version = "0.49.0", path = "acvm-repo/brillig_vm", default-features = false } +acvm_blackbox_solver = { version = "0.49.0", path = "acvm-repo/blackbox_solver", default-features = false } +bn254_blackbox_solver = { version = "0.49.0", path = "acvm-repo/bn254_blackbox_solver", default-features = false } # Noir compiler workspace dependencies fm = { path = "compiler/fm" } diff --git a/noir/noir-repo/acvm-repo/CHANGELOG.md b/noir/noir-repo/acvm-repo/CHANGELOG.md index 971f8ae0448a..4b262d6b00f8 100644 --- a/noir/noir-repo/acvm-repo/CHANGELOG.md +++ b/noir/noir-repo/acvm-repo/CHANGELOG.md @@ -5,6 +5,131 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.49.0](https://github.com/noir-lang/noir/compare/v0.48.0...v0.49.0) (2024-08-06) + + +### ⚠ BREAKING CHANGES + +* constant inputs for blackbox (https://github.com/AztecProtocol/aztec-packages/pull/7222) +* add session id to foreign call RPC requests ([#5205](https://github.com/noir-lang/noir/issues/5205)) +* restrict noir word size to u32 ([#5180](https://github.com/noir-lang/noir/issues/5180)) +* switch `bb` over to read ACIR from nargo artifacts (https://github.com/AztecProtocol/aztec-packages/pull/6283) +* specify databus arrays for BB (https://github.com/AztecProtocol/aztec-packages/pull/6239) +* remove `Opcode::Brillig` from ACIR (https://github.com/AztecProtocol/aztec-packages/pull/5995) +* AES blackbox (https://github.com/AztecProtocol/aztec-packages/pull/6016) +* Bit shift is restricted to u8 right operand ([#4907](https://github.com/noir-lang/noir/issues/4907)) +* contract interfaces and better function calls (https://github.com/AztecProtocol/aztec-packages/pull/5687) +* change backend width to 4 (https://github.com/AztecProtocol/aztec-packages/pull/5374) +* Use fixed size arrays in black box functions where sizes are known (https://github.com/AztecProtocol/aztec-packages/pull/5620) +* trap with revert data (https://github.com/AztecProtocol/aztec-packages/pull/5732) +* **acir:** BrilligCall opcode (https://github.com/AztecProtocol/aztec-packages/pull/5709) +* remove fixed-length keccak256 (https://github.com/AztecProtocol/aztec-packages/pull/5617) +* storage_layout and `#[aztec(storage)]` (https://github.com/AztecProtocol/aztec-packages/pull/5387) +* **acir:** Add predicate to call opcode (https://github.com/AztecProtocol/aztec-packages/pull/5616) +* contract_abi-exports (https://github.com/AztecProtocol/aztec-packages/pull/5386) +* Brillig typed memory (https://github.com/AztecProtocol/aztec-packages/pull/5395) + +### Features + +* `multi_scalar_mul` blackbox func (https://github.com/AztecProtocol/aztec-packages/pull/6097) ([73a635e](https://github.com/noir-lang/noir/commit/73a635e5086cf3407f9846ce39807cd15b4e485a)) +* `variable_base_scalar_mul` blackbox func (https://github.com/AztecProtocol/aztec-packages/pull/6039) ([73a635e](https://github.com/noir-lang/noir/commit/73a635e5086cf3407f9846ce39807cd15b4e485a)) +* **acir_gen:** Brillig stdlib ([#4848](https://github.com/noir-lang/noir/issues/4848)) ([0c8175c](https://github.com/noir-lang/noir/commit/0c8175cb539efd9427c73ae5af0d48abe688ebab)) +* **acir_gen:** Fold attribute at compile-time and initial non inlined ACIR (https://github.com/AztecProtocol/aztec-packages/pull/5341) ([a0f7474](https://github.com/noir-lang/noir/commit/a0f7474ae6bd74132efdb945d2eb2383f3913cce)) +* **acir_gen:** Width aware ACIR gen addition ([#5493](https://github.com/noir-lang/noir/issues/5493)) ([85fa592](https://github.com/noir-lang/noir/commit/85fa592fdef3b8589ce03b232e1b51565837b540)) +* **acir:** Add predicate to call opcode (https://github.com/AztecProtocol/aztec-packages/pull/5616) ([2bd006a](https://github.com/noir-lang/noir/commit/2bd006ae07499e8702b0fa9565855f0a5ef1a589)) +* **acir:** BrilligCall opcode (https://github.com/AztecProtocol/aztec-packages/pull/5709) ([0f9ae0a](https://github.com/noir-lang/noir/commit/0f9ae0ac1d68714b56ba4524aedcc67212494f1b)) +* Activate return_data in ACIR opcodes ([#5080](https://github.com/noir-lang/noir/issues/5080)) ([c9fda3c](https://github.com/noir-lang/noir/commit/c9fda3c7fd4575bfe7d457e8d4230e071f0129a0)) +* **acvm_js:** Execute program ([#4694](https://github.com/noir-lang/noir/issues/4694)) ([386f6d0](https://github.com/noir-lang/noir/commit/386f6d0a5822912db878285cb001032a7c0ff622)) +* **acvm:** Execute multiple circuits (https://github.com/AztecProtocol/aztec-packages/pull/5380) ([a0f7474](https://github.com/noir-lang/noir/commit/a0f7474ae6bd74132efdb945d2eb2383f3913cce)) +* Add native rust implementation of schnorr signature verification ([#5053](https://github.com/noir-lang/noir/issues/5053)) ([fab1c35](https://github.com/noir-lang/noir/commit/fab1c3567d731ea7902635a7a020a8d14f94fd27)) +* Add native rust implementations of pedersen functions ([#4871](https://github.com/noir-lang/noir/issues/4871)) ([fb039f7](https://github.com/noir-lang/noir/commit/fb039f74df23aea39bc0593a5d538d82b4efadf0)) +* Add return values to aztec fns (https://github.com/AztecProtocol/aztec-packages/pull/5389) ([2bd006a](https://github.com/noir-lang/noir/commit/2bd006ae07499e8702b0fa9565855f0a5ef1a589)) +* Add session id to foreign call RPC requests ([#5205](https://github.com/noir-lang/noir/issues/5205)) ([14adafc](https://github.com/noir-lang/noir/commit/14adafc965fa9c833e096ec037e086aae67703ad)) +* AES blackbox (https://github.com/AztecProtocol/aztec-packages/pull/6016) ([73a635e](https://github.com/noir-lang/noir/commit/73a635e5086cf3407f9846ce39807cd15b4e485a)) +* **avm:** Integrate AVM with initializers (https://github.com/AztecProtocol/aztec-packages/pull/5469) ([2bd006a](https://github.com/noir-lang/noir/commit/2bd006ae07499e8702b0fa9565855f0a5ef1a589)) +* Avoid heap allocs when going to/from field (https://github.com/AztecProtocol/aztec-packages/pull/7547) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* Bit shift is restricted to u8 right operand ([#4907](https://github.com/noir-lang/noir/issues/4907)) ([c4b0369](https://github.com/noir-lang/noir/commit/c4b03691feca17ef268acab523292f3051f672ea)) +* Brillig heterogeneous memory cells (https://github.com/AztecProtocol/aztec-packages/pull/5608) ([305bcdc](https://github.com/noir-lang/noir/commit/305bcdcbd01cb84dbaac900f14cb6cf867f83bda)) +* Brillig pointer codegen and execution (https://github.com/AztecProtocol/aztec-packages/pull/5737) ([0f9ae0a](https://github.com/noir-lang/noir/commit/0f9ae0ac1d68714b56ba4524aedcc67212494f1b)) +* Brillig typed memory (https://github.com/AztecProtocol/aztec-packages/pull/5395) ([0bc18c4](https://github.com/noir-lang/noir/commit/0bc18c4f78171590dd58bded959f68f53a44cc8c)) +* Change backend width to 4 (https://github.com/AztecProtocol/aztec-packages/pull/5374) ([0f9ae0a](https://github.com/noir-lang/noir/commit/0f9ae0ac1d68714b56ba4524aedcc67212494f1b)) +* Constant inputs for blackbox (https://github.com/AztecProtocol/aztec-packages/pull/7222) ([fb97bb9](https://github.com/noir-lang/noir/commit/fb97bb9b795c9d7af395b82fd6f0ea8111d59c11)) +* Contract interfaces and better function calls (https://github.com/AztecProtocol/aztec-packages/pull/5687) ([0f9ae0a](https://github.com/noir-lang/noir/commit/0f9ae0ac1d68714b56ba4524aedcc67212494f1b)) +* Contract_abi-exports (https://github.com/AztecProtocol/aztec-packages/pull/5386) ([2bd006a](https://github.com/noir-lang/noir/commit/2bd006ae07499e8702b0fa9565855f0a5ef1a589)) +* Dynamic assertion payloads v2 (https://github.com/AztecProtocol/aztec-packages/pull/5949) ([73a635e](https://github.com/noir-lang/noir/commit/73a635e5086cf3407f9846ce39807cd15b4e485a)) +* Handle `BrilligCall` opcodes in the debugger ([#4897](https://github.com/noir-lang/noir/issues/4897)) ([b380dc4](https://github.com/noir-lang/noir/commit/b380dc44de5c9f8de278ece3d531ebbc2c9238ba)) +* Impl of missing functionality in new key store (https://github.com/AztecProtocol/aztec-packages/pull/5750) ([0f9ae0a](https://github.com/noir-lang/noir/commit/0f9ae0ac1d68714b56ba4524aedcc67212494f1b)) +* Increase default expression width to 4 ([#4995](https://github.com/noir-lang/noir/issues/4995)) ([f01d309](https://github.com/noir-lang/noir/commit/f01d3090759a5ff0f1f83c5616d22890c6bd76be)) +* Integrate new proving systems in e2e (https://github.com/AztecProtocol/aztec-packages/pull/6971) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* Make ACVM generic across fields ([#5114](https://github.com/noir-lang/noir/issues/5114)) ([70f374c](https://github.com/noir-lang/noir/commit/70f374c06642962d8f2b95b80f8c938fcf7761d7)) +* Make Brillig do integer arithmetic operations using u128 instead of Bigint (https://github.com/AztecProtocol/aztec-packages/pull/7518) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* Move abi demonomorphizer to noir_codegen and use noir_codegen in protocol types (https://github.com/AztecProtocol/aztec-packages/pull/6302) ([436bbda](https://github.com/noir-lang/noir/commit/436bbdaadb2a294b94f93e53d7d3cad3859c7e46)) +* Move to_radix to a blackbox (https://github.com/AztecProtocol/aztec-packages/pull/6294) ([436bbda](https://github.com/noir-lang/noir/commit/436bbdaadb2a294b94f93e53d7d3cad3859c7e46)) +* **nargo:** Handle call stacks for multiple Acir calls ([#4711](https://github.com/noir-lang/noir/issues/4711)) ([5b23171](https://github.com/noir-lang/noir/commit/5b231714740447d82cde7cdbe65d4a8b46a31df4)) +* **nargo:** Hidden option to show contract artifact paths written by `nargo compile` (https://github.com/AztecProtocol/aztec-packages/pull/6131) ([ff67e14](https://github.com/noir-lang/noir/commit/ff67e145d086bf6fdf58fb5e57927033e52e03d3)) +* Parsing non-string assertion payloads in noir js (https://github.com/AztecProtocol/aztec-packages/pull/6079) ([73a635e](https://github.com/noir-lang/noir/commit/73a635e5086cf3407f9846ce39807cd15b4e485a)) +* Private Kernel Recursion (https://github.com/AztecProtocol/aztec-packages/pull/6278) ([436bbda](https://github.com/noir-lang/noir/commit/436bbdaadb2a294b94f93e53d7d3cad3859c7e46)) +* Proper padding in ts AES and constrained AES in body and header computations (https://github.com/AztecProtocol/aztec-packages/pull/6269) ([436bbda](https://github.com/noir-lang/noir/commit/436bbdaadb2a294b94f93e53d7d3cad3859c7e46)) +* Remove conditional compilation of `bn254_blackbox_solver` ([#5058](https://github.com/noir-lang/noir/issues/5058)) ([9420d7c](https://github.com/noir-lang/noir/commit/9420d7c2ba6bbbf5ecb9a066837c505310955b6c)) +* Remove external blackbox solver from acir simulator (https://github.com/AztecProtocol/aztec-packages/pull/6586) ([a40a9a5](https://github.com/noir-lang/noir/commit/a40a9a55571deed386688fb84260bdf2794d4d38)) +* Restore hashing args via slice for performance (https://github.com/AztecProtocol/aztec-packages/pull/5539) ([2bd006a](https://github.com/noir-lang/noir/commit/2bd006ae07499e8702b0fa9565855f0a5ef1a589)) +* Restrict noir word size to u32 ([#5180](https://github.com/noir-lang/noir/issues/5180)) ([bdb2bc6](https://github.com/noir-lang/noir/commit/bdb2bc608ea8fd52d46545a38b68dd2558b28110)) +* Separate runtimes of SSA functions before inlining ([#5121](https://github.com/noir-lang/noir/issues/5121)) ([69eca9b](https://github.com/noir-lang/noir/commit/69eca9b8671fa54192bef814dd584fdb5387a5f7)) +* Set aztec private functions to be recursive (https://github.com/AztecProtocol/aztec-packages/pull/6192) ([73a635e](https://github.com/noir-lang/noir/commit/73a635e5086cf3407f9846ce39807cd15b4e485a)) +* **simulator:** Fetch return values at circuit execution (https://github.com/AztecProtocol/aztec-packages/pull/5642) ([305bcdc](https://github.com/noir-lang/noir/commit/305bcdcbd01cb84dbaac900f14cb6cf867f83bda)) +* Specify databus arrays for BB (https://github.com/AztecProtocol/aztec-packages/pull/6239) ([436bbda](https://github.com/noir-lang/noir/commit/436bbdaadb2a294b94f93e53d7d3cad3859c7e46)) +* Storage_layout and `#[aztec(storage)]` (https://github.com/AztecProtocol/aztec-packages/pull/5387) ([2bd006a](https://github.com/noir-lang/noir/commit/2bd006ae07499e8702b0fa9565855f0a5ef1a589)) +* Switch `bb` over to read ACIR from nargo artifacts (https://github.com/AztecProtocol/aztec-packages/pull/6283) ([436bbda](https://github.com/noir-lang/noir/commit/436bbdaadb2a294b94f93e53d7d3cad3859c7e46)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/5572) ([2bd006a](https://github.com/noir-lang/noir/commit/2bd006ae07499e8702b0fa9565855f0a5ef1a589)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/5619) ([2bd006a](https://github.com/noir-lang/noir/commit/2bd006ae07499e8702b0fa9565855f0a5ef1a589)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/5697) ([305bcdc](https://github.com/noir-lang/noir/commit/305bcdcbd01cb84dbaac900f14cb6cf867f83bda)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/5794) ([0f9ae0a](https://github.com/noir-lang/noir/commit/0f9ae0ac1d68714b56ba4524aedcc67212494f1b)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/5814) ([0f9ae0a](https://github.com/noir-lang/noir/commit/0f9ae0ac1d68714b56ba4524aedcc67212494f1b)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/5935) ([1b867b1](https://github.com/noir-lang/noir/commit/1b867b121fba5db3087ca845b4934e6732b23fd1)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/5955) ([1b867b1](https://github.com/noir-lang/noir/commit/1b867b121fba5db3087ca845b4934e6732b23fd1)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/5999) ([1b867b1](https://github.com/noir-lang/noir/commit/1b867b121fba5db3087ca845b4934e6732b23fd1)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/6280) ([436bbda](https://github.com/noir-lang/noir/commit/436bbdaadb2a294b94f93e53d7d3cad3859c7e46)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/6332) ([436bbda](https://github.com/noir-lang/noir/commit/436bbdaadb2a294b94f93e53d7d3cad3859c7e46)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/6573) ([436bbda](https://github.com/noir-lang/noir/commit/436bbdaadb2a294b94f93e53d7d3cad3859c7e46)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/7392) ([fb97bb9](https://github.com/noir-lang/noir/commit/fb97bb9b795c9d7af395b82fd6f0ea8111d59c11)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/7400) ([fb97bb9](https://github.com/noir-lang/noir/commit/fb97bb9b795c9d7af395b82fd6f0ea8111d59c11)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/7432) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/7444) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/7454) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/7512) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/7577) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/7583) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* ToRadix BB + avm transpiler support (https://github.com/AztecProtocol/aztec-packages/pull/6330) ([436bbda](https://github.com/noir-lang/noir/commit/436bbdaadb2a294b94f93e53d7d3cad3859c7e46)) +* Trap with revert data (https://github.com/AztecProtocol/aztec-packages/pull/5732) ([0f9ae0a](https://github.com/noir-lang/noir/commit/0f9ae0ac1d68714b56ba4524aedcc67212494f1b)) +* Typing return values of embedded_curve_ops (https://github.com/AztecProtocol/aztec-packages/pull/7413) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* Use fixed size arrays in black box functions where sizes are known (https://github.com/AztecProtocol/aztec-packages/pull/5620) ([0f9ae0a](https://github.com/noir-lang/noir/commit/0f9ae0ac1d68714b56ba4524aedcc67212494f1b)) +* Variable length returns (https://github.com/AztecProtocol/aztec-packages/pull/5633) ([305bcdc](https://github.com/noir-lang/noir/commit/305bcdcbd01cb84dbaac900f14cb6cf867f83bda)) + + +### Bug Fixes + +* **acvm:** Mark outputs of Opcode::Call solvable ([#4708](https://github.com/noir-lang/noir/issues/4708)) ([8fea405](https://github.com/noir-lang/noir/commit/8fea40576f262bd5bb588923c0660d8967404e56)) +* Add support for nested arrays returned by oracles ([#5132](https://github.com/noir-lang/noir/issues/5132)) ([f846879](https://github.com/noir-lang/noir/commit/f846879dd038328bd0a1d39a72b448ef52a1002b)) +* Add trailing extra arguments for backend in gates_flamegraph (https://github.com/AztecProtocol/aztec-packages/pull/7472) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* Avoid huge unrolling in hash_args (https://github.com/AztecProtocol/aztec-packages/pull/5703) ([305bcdc](https://github.com/noir-lang/noir/commit/305bcdcbd01cb84dbaac900f14cb6cf867f83bda)) +* Avoid unnecessarily splitting expressions with multiplication terms with a shared term ([#5291](https://github.com/noir-lang/noir/issues/5291)) ([19884f1](https://github.com/noir-lang/noir/commit/19884f161dfc7d7ce75dd2c404b8ef39cdad2240)) +* Catch panics from EC point creation (e.g. the point is at infinity) ([#4790](https://github.com/noir-lang/noir/issues/4790)) ([645dba1](https://github.com/noir-lang/noir/commit/645dba192f16ef34018828186ffb297422a8dc73)) +* Check for public args in aztec functions (https://github.com/AztecProtocol/aztec-packages/pull/6355) ([436bbda](https://github.com/noir-lang/noir/commit/436bbdaadb2a294b94f93e53d7d3cad3859c7e46)) +* Don't reuse brillig with slice arguments (https://github.com/AztecProtocol/aztec-packages/pull/5800) ([0f9ae0a](https://github.com/noir-lang/noir/commit/0f9ae0ac1d68714b56ba4524aedcc67212494f1b)) +* Handle struct with nested arrays in oracle return values ([#5244](https://github.com/noir-lang/noir/issues/5244)) ([a30814f](https://github.com/noir-lang/noir/commit/a30814f1f767bf874cd7e2969f5061c68f16b9a7)) +* Issue 4682 and add solver for unconstrained bigintegers ([#4729](https://github.com/noir-lang/noir/issues/4729)) ([e4d33c1](https://github.com/noir-lang/noir/commit/e4d33c126a2795d9aaa6048d4e91b64cb4bbe4f2)) +* Move BigInt modulus checks to runtime in brillig ([#5374](https://github.com/noir-lang/noir/issues/5374)) ([741d339](https://github.com/noir-lang/noir/commit/741d33991f8e2918bf092c354ca56047e0274533)) +* Proper field inversion for bigints ([#4802](https://github.com/noir-lang/noir/issues/4802)) ([b46d0e3](https://github.com/noir-lang/noir/commit/b46d0e39f4252f8bbaa987f88d112e4c233b3d61)) +* Revert "feat: Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/7512)" (https://github.com/AztecProtocol/aztec-packages/pull/7558) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* Runtime brillig bigint id assignment ([#5369](https://github.com/noir-lang/noir/issues/5369)) ([a8928dd](https://github.com/noir-lang/noir/commit/a8928ddcffcae15babf7aa5aff0e462e4549552e)) +* Temporarily revert to_radix blackbox (https://github.com/AztecProtocol/aztec-packages/pull/6304) ([436bbda](https://github.com/noir-lang/noir/commit/436bbdaadb2a294b94f93e53d7d3cad3859c7e46)) + + +### Miscellaneous Chores + +* Remove `Opcode::Brillig` from ACIR (https://github.com/AztecProtocol/aztec-packages/pull/5995) ([73a635e](https://github.com/noir-lang/noir/commit/73a635e5086cf3407f9846ce39807cd15b4e485a)) +* Remove fixed-length keccak256 (https://github.com/AztecProtocol/aztec-packages/pull/5617) ([305bcdc](https://github.com/noir-lang/noir/commit/305bcdcbd01cb84dbaac900f14cb6cf867f83bda)) + ## [0.48.0](https://github.com/noir-lang/noir/compare/v0.47.0...v0.48.0) (2024-07-18) diff --git a/noir/noir-repo/acvm-repo/acir/Cargo.toml b/noir/noir-repo/acvm-repo/acir/Cargo.toml index 68c28dccc66d..88616ccffb59 100644 --- a/noir/noir-repo/acvm-repo/acir/Cargo.toml +++ b/noir/noir-repo/acvm-repo/acir/Cargo.toml @@ -2,7 +2,7 @@ name = "acir" description = "ACIR is the IR that the VM processes, it is analogous to LLVM IR" # x-release-please-start-version -version = "0.48.0" +version = "0.49.0" # x-release-please-end authors.workspace = true edition.workspace = true 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 ee25d05afb02..a9714ce29b25 100644 --- a/noir/noir-repo/acvm-repo/acir/src/circuit/brillig.rs +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/brillig.rs @@ -27,3 +27,22 @@ pub enum BrilligOutputs { pub struct BrilligBytecode { pub bytecode: Vec>, } + +/// Id for the function being called. +#[derive( + Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash, Copy, Default, PartialOrd, Ord, +)] +#[serde(transparent)] +pub struct BrilligFunctionId(pub u32); + +impl BrilligFunctionId { + pub fn as_usize(&self) -> usize { + self.0 as usize + } +} + +impl std::fmt::Display for BrilligFunctionId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} 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 5d749e709b34..00d0933a3aaa 100644 --- a/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs @@ -377,11 +377,13 @@ mod tests { output: Witness(3), }) } + fn range_opcode() -> Opcode { Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { input: FunctionInput::witness(Witness(1), 8), }) } + fn keccakf1600_opcode() -> Opcode { let inputs: Box<[FunctionInput; 25]> = Box::new(std::array::from_fn(|i| FunctionInput::witness(Witness(i as u32 + 1), 8))); @@ -389,6 +391,7 @@ mod tests { Opcode::BlackBoxFuncCall(BlackBoxFuncCall::Keccakf1600 { inputs, outputs }) } + fn schnorr_verify_opcode() -> Opcode { let public_key_x = FunctionInput::witness(Witness(1), FieldElement::max_num_bits()); let public_key_y = FunctionInput::witness(Witness(2), FieldElement::max_num_bits()); 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 d303f9fbbab5..17b5388faa0f 100644 --- a/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes.rs +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes.rs @@ -1,5 +1,5 @@ use super::{ - brillig::{BrilligInputs, BrilligOutputs}, + brillig::{BrilligFunctionId, BrilligInputs, BrilligOutputs}, directives::Directive, }; use crate::native_types::{Expression, Witness}; @@ -111,7 +111,7 @@ pub enum Opcode { BrilligCall { /// Id for the function being called. It is the responsibility of the executor /// to fetch the appropriate Brillig bytecode from this id. - id: u32, + id: BrilligFunctionId, /// Inputs to the function call inputs: Vec>, /// Outputs to the function call 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 1feda5703c85..2bbbc39d0ca9 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 @@ -273,6 +273,60 @@ impl Expression { Expression { mul_terms, linear_combinations, q_c } } + + /// Determine the width of this expression. + /// The width meaning the number of unique witnesses needed for this expression. + pub fn width(&self) -> usize { + let mut width = 0; + + for mul_term in &self.mul_terms { + // The coefficient should be non-zero, as this method is ran after the compiler removes all zero coefficient terms + assert_ne!(mul_term.0, F::zero()); + + let mut found_x = false; + let mut found_y = false; + + for term in self.linear_combinations.iter() { + let witness = &term.1; + let x = &mul_term.1; + let y = &mul_term.2; + if witness == x { + found_x = true; + }; + if witness == y { + found_y = true; + }; + if found_x & found_y { + break; + } + } + + // If the multiplication is a squaring then we must assign the two witnesses to separate wires and so we + // can never get a zero contribution to the width. + let multiplication_is_squaring = mul_term.1 == mul_term.2; + + let mul_term_width_contribution = if !multiplication_is_squaring && (found_x & found_y) + { + // Both witnesses involved in the multiplication exist elsewhere in the expression. + // They both do not contribute to the width of the expression as this would be double-counting + // due to their appearance in the linear terms. + 0 + } else if found_x || found_y { + // One of the witnesses involved in the multiplication exists elsewhere in the expression. + // The multiplication then only contributes 1 new witness to the width. + 1 + } else { + // Worst case scenario, the multiplication is using completely unique witnesses so has a contribution of 2. + 2 + }; + + width += mul_term_width_contribution; + } + + width += self.linear_combinations.len(); + + width + } } impl From for Expression { 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 3610ce6493ee..1a634eeea9cd 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 @@ -13,7 +13,7 @@ use std::collections::BTreeSet; use acir::{ circuit::{ - brillig::{BrilligBytecode, BrilligInputs, BrilligOutputs}, + brillig::{BrilligBytecode, BrilligFunctionId, BrilligInputs, BrilligOutputs}, opcodes::{BlackBoxFuncCall, BlockId, FunctionInput, MemOp}, Circuit, Opcode, Program, PublicInputs, }, @@ -181,7 +181,7 @@ fn simple_brillig_foreign_call() { }; let opcodes = vec![Opcode::BrilligCall { - id: 0, + id: BrilligFunctionId(0), inputs: vec![ BrilligInputs::Single(w_input.into()), // Input Register 0, ], @@ -272,7 +272,7 @@ fn complex_brillig_foreign_call() { }; let opcodes = vec![Opcode::BrilligCall { - id: 0, + id: BrilligFunctionId(0), inputs: vec![ // Input 0,1,2 BrilligInputs::Array(vec![ diff --git a/noir/noir-repo/acvm-repo/acir_field/Cargo.toml b/noir/noir-repo/acvm-repo/acir_field/Cargo.toml index 3696423979b6..a037a453348e 100644 --- a/noir/noir-repo/acvm-repo/acir_field/Cargo.toml +++ b/noir/noir-repo/acvm-repo/acir_field/Cargo.toml @@ -2,7 +2,7 @@ name = "acir_field" description = "The field implementation being used by ACIR." # x-release-please-start-version -version = "0.48.0" +version = "0.49.0" # x-release-please-end authors.workspace = true edition.workspace = true diff --git a/noir/noir-repo/acvm-repo/acvm/Cargo.toml b/noir/noir-repo/acvm-repo/acvm/Cargo.toml index 5b6397a1011b..2ee4d529e5a5 100644 --- a/noir/noir-repo/acvm-repo/acvm/Cargo.toml +++ b/noir/noir-repo/acvm-repo/acvm/Cargo.toml @@ -2,7 +2,7 @@ name = "acvm" description = "The virtual machine that processes ACIR given a backend/proof system." # x-release-please-start-version -version = "0.48.0" +version = "0.49.0" # x-release-please-end authors.workspace = true edition.workspace = true @@ -38,3 +38,4 @@ bls12_381 = [ [dev-dependencies] ark-bls12-381 = { version = "^0.4.0", default-features = false, features = ["curve"] } +proptest.workspace = true diff --git a/noir/noir-repo/acvm-repo/acvm/src/compiler/transformers/csat.rs b/noir/noir-repo/acvm-repo/acvm/src/compiler/transformers/csat.rs index 19cc18ca7f38..f258e0a88187 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/compiler/transformers/csat.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/compiler/transformers/csat.rs @@ -415,71 +415,8 @@ fn fits_in_one_identity(expr: &Expression, width: usize) -> boo if expr.mul_terms.len() > 1 { return false; }; - // A Polynomial with more terms than fan-in cannot fit within a single opcode - if expr.linear_combinations.len() > width { - return false; - } - - // A polynomial with no mul term and a fan-in that fits inside of the width can fit into a single opcode - if expr.mul_terms.is_empty() { - return true; - } - - // A polynomial with width-2 fan-in terms and a single non-zero mul term can fit into one opcode - // Example: Axy + Dz . Notice, that the mul term places a constraint on the first two terms, but not the last term - // XXX: This would change if our arithmetic polynomial equation was changed to Axyz for example, but for now it is not. - if expr.linear_combinations.len() <= (width - 2) { - return true; - } - - // We now know that we have a single mul term. We also know that the mul term must match up with at least one of the other terms - // A polynomial whose mul terms are non zero which do not match up with two terms in the fan-in cannot fit into one opcode - // An example of this is: Axy + Bx + Cy + ... - // Notice how the bivariate monomial xy has two univariate monomials with their respective coefficients - // XXX: note that if x or y is zero, then we could apply a further optimization, but this would be done in another algorithm. - // It would be the same as when we have zero coefficients - Can only work if wire is constrained to be zero publicly - let mul_term = &expr.mul_terms[0]; - - // The coefficient should be non-zero, as this method is ran after the compiler removes all zero coefficient terms - assert_ne!(mul_term.0, F::zero()); - - let mut found_x = false; - let mut found_y = false; - - for term in expr.linear_combinations.iter() { - let witness = &term.1; - let x = &mul_term.1; - let y = &mul_term.2; - if witness == x { - found_x = true; - }; - if witness == y { - found_y = true; - }; - if found_x & found_y { - break; - } - } - - // If the multiplication is a squaring then we must assign the two witnesses to separate wires and so we - // can never get a zero contribution to the width. - let multiplication_is_squaring = mul_term.1 == mul_term.2; - - let mul_term_width_contribution = if !multiplication_is_squaring && (found_x & found_y) { - // Both witnesses involved in the multiplication exist elsewhere in the expression. - // They both do not contribute to the width of the expression as this would be double-counting - // due to their appearance in the linear terms. - 0 - } else if found_x || found_y { - // One of the witnesses involved in the multiplication exists elsewhere in the expression. - // The multiplication then only contributes 1 new witness to the width. - 1 - } else { - // Worst case scenario, the multiplication is using completely unique witnesses so has a contribution of 2. - 2 - }; - mul_term_width_contribution + expr.linear_combinations.len() <= width + expr.width() <= width } #[cfg(test)] diff --git a/noir/noir-repo/acvm-repo/acvm/src/pwg/brillig.rs b/noir/noir-repo/acvm-repo/acvm/src/pwg/brillig.rs index 635aa154c3ef..5ec3224dbaaa 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/pwg/brillig.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/pwg/brillig.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use acir::{ brillig::{ForeignCallParam, ForeignCallResult, Opcode as BrilligOpcode}, circuit::{ - brillig::{BrilligInputs, BrilligOutputs}, + brillig::{BrilligFunctionId, BrilligInputs, BrilligOutputs}, opcodes::BlockId, ErrorSelector, OpcodeLocation, RawAssertionPayload, ResolvedAssertionPayload, STRING_ERROR_SELECTOR, @@ -29,6 +29,10 @@ pub enum BrilligSolverStatus { pub struct BrilligSolver<'b, F, B: BlackBoxFunctionSolver> { vm: VM<'b, F, B>, acir_index: usize, + /// This id references which Brillig function within the main ACIR program we are solving. + /// This is used for appropriately resolving errors as the ACIR program artifacts + /// set up their Brillig debug metadata by function id. + pub function_id: BrilligFunctionId, } impl<'b, B: BlackBoxFunctionSolver, F: AcirField> BrilligSolver<'b, F, B> { @@ -61,10 +65,11 @@ impl<'b, B: BlackBoxFunctionSolver, F: AcirField> BrilligSolver<'b, F, B> { brillig_bytecode: &'b [BrilligOpcode], bb_solver: &'b B, acir_index: usize, + brillig_function_id: BrilligFunctionId, ) -> Result> { let vm = Self::setup_brillig_vm(initial_witness, memory, inputs, brillig_bytecode, bb_solver)?; - Ok(Self { vm, acir_index }) + Ok(Self { vm, acir_index, function_id: brillig_function_id }) } fn setup_brillig_vm( @@ -182,7 +187,11 @@ impl<'b, B: BlackBoxFunctionSolver, F: AcirField> BrilligSolver<'b, F, B> { } }; - Err(OpcodeResolutionError::BrilligFunctionFailed { payload, call_stack }) + Err(OpcodeResolutionError::BrilligFunctionFailed { + function_id: self.function_id, + payload, + call_stack, + }) } VMStatus::ForeignCallWait { function, inputs } => { Ok(BrilligSolverStatus::ForeignCallWait(ForeignCallWaitInfo { function, inputs })) 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 4292d72fad52..83c5aeb62960 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs @@ -5,7 +5,7 @@ use std::collections::HashMap; use acir::{ brillig::ForeignCallResult, circuit::{ - brillig::BrilligBytecode, + brillig::{BrilligBytecode, BrilligFunctionId}, opcodes::{BlockId, ConstantOrWitnessEnum, FunctionInput}, AssertionPayload, ErrorSelector, ExpressionOrMemory, Opcode, OpcodeLocation, RawAssertionPayload, ResolvedAssertionPayload, STRING_ERROR_SELECTOR, @@ -132,6 +132,7 @@ pub enum OpcodeResolutionError { BlackBoxFunctionFailed(BlackBoxFunc, String), #[error("Failed to solve brillig function")] BrilligFunctionFailed { + function_id: BrilligFunctionId, call_stack: Vec, payload: Option>, }, @@ -475,9 +476,10 @@ impl<'a, F: AcirField, B: BlackBoxFunctionSolver> ACVM<'a, F, B> { &self.witness_map, &self.block_solvers, inputs, - &self.unconstrained_functions[*id as usize].bytecode, + &self.unconstrained_functions[id.as_usize()].bytecode, self.backend, self.instruction_pointer, + *id, )?, }; @@ -502,7 +504,7 @@ impl<'a, F: AcirField, B: BlackBoxFunctionSolver> ACVM<'a, F, B> { fn map_brillig_error(&self, mut err: OpcodeResolutionError) -> OpcodeResolutionError { match &mut err { - OpcodeResolutionError::BrilligFunctionFailed { call_stack, payload } => { + OpcodeResolutionError::BrilligFunctionFailed { call_stack, payload, .. } => { // Some brillig errors have static strings as payloads, we can resolve them here let last_location = call_stack.last().expect("Call stacks should have at least one item"); @@ -546,9 +548,10 @@ impl<'a, F: AcirField, B: BlackBoxFunctionSolver> ACVM<'a, F, B> { witness, &self.block_solvers, inputs, - &self.unconstrained_functions[*id as usize].bytecode, + &self.unconstrained_functions[id.as_usize()].bytecode, self.backend, self.instruction_pointer, + *id, ); match solver { Ok(solver) => StepResult::IntoBrillig(solver), diff --git a/noir/noir-repo/acvm-repo/acvm/tests/solver.proptest-regressions b/noir/noir-repo/acvm-repo/acvm/tests/solver.proptest-regressions new file mode 100644 index 000000000000..35627c1fbae2 --- /dev/null +++ b/noir/noir-repo/acvm-repo/acvm/tests/solver.proptest-regressions @@ -0,0 +1,13 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc e4dd0e141df173f5dfdfb186bba4154247ec284b71d8f294fa3282da953a0e92 # shrinks to x = 0, y = 1 +cc 419ed6fdf1bf1f2513889c42ec86c665c9d0500ceb075cbbd07f72444dbd78c6 # shrinks to x = 266672725 +cc 0810fc9e126b56cf0a0ddb25e0dc498fa3b2f1980951550403479fc01c209833 # shrinks to modulus = [71, 253, 124, 216, 22, 140, 32, 60, 141, 202, 113, 104, 145, 106, 129, 151, 93, 88, 129, 129, 182, 69, 80, 184, 41, 160, 49, 225, 114, 78, 100, 48], zero_or_ones_constant = false, use_constant = false +cc 735ee9beb1a1dbb82ded6f30e544d7dfde149957e5d45a8c96fc65a690b6b71c # shrinks to (xs, modulus) = ([(0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (49, false)], [71, 253, 124, 216, 22, 140, 32, 60, 141, 202, 113, 104, 145, 106, 129, 151, 93, 88, 129, 129, 182, 69, 80, 184, 41, 160, 49, 225, 114, 78, 100, 48]) +cc ca81bc11114a2a2b34021f44ecc1e10cb018e35021ef4d728e07a6791dad38d6 # shrinks to (xs, modulus) = ([(0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (49, false)], [71, 253, 124, 216, 22, 140, 32, 60, 141, 202, 113, 104, 145, 106, 129, 151, 93, 88, 129, 129, 182, 69, 80, 184, 41, 160, 49, 225, 114, 78, 100, 48]) +cc 6c1d571a0111e6b4c244dc16da122ebab361e77b71db7770d638076ab21a717b # shrinks to (xs, modulus) = ([(0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (49, false)], [71, 253, 124, 216, 22, 140, 32, 60, 141, 202, 113, 104, 145, 106, 129, 151, 93, 88, 129, 129, 182, 69, 80, 184, 41, 160, 49, 225, 114, 78, 100, 48]) +cc ccb7061ab6b85e2554d00bf03d74204977ed7a4109d7e2d5c6b5aaa2179cfaf9 # shrinks to (xs, modulus) = ([(0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (49, false)], [71, 253, 124, 216, 22, 140, 32, 60, 141, 202, 113, 104, 145, 106, 129, 151, 93, 88, 129, 129, 182, 69, 80, 184, 41, 160, 49, 225, 114, 78, 100, 48]) diff --git a/noir/noir-repo/acvm-repo/acvm/tests/solver.rs b/noir/noir-repo/acvm-repo/acvm/tests/solver.rs index e55dbb73ae1e..a1b8b62f8bff 100644 --- a/noir/noir-repo/acvm-repo/acvm/tests/solver.rs +++ b/noir/noir-repo/acvm-repo/acvm/tests/solver.rs @@ -4,8 +4,8 @@ use acir::{ acir_field::GenericFieldElement, brillig::{BinaryFieldOp, HeapArray, MemoryAddress, Opcode as BrilligOpcode, ValueOrArray}, circuit::{ - brillig::{BrilligBytecode, BrilligInputs, BrilligOutputs}, - opcodes::{BlockId, BlockType, MemOp}, + brillig::{BrilligBytecode, BrilligFunctionId, BrilligInputs, BrilligOutputs}, + opcodes::{BlackBoxFuncCall, BlockId, BlockType, FunctionInput, MemOp}, Opcode, OpcodeLocation, }, native_types::{Expression, Witness, WitnessMap}, @@ -16,6 +16,10 @@ use acvm::pwg::{ACVMStatus, ErrorLocation, ForeignCallWaitInfo, OpcodeResolution use acvm_blackbox_solver::StubbedBlackBoxSolver; use brillig_vm::brillig::HeapValueType; +use proptest::arbitrary::any; +use proptest::prelude::*; +use proptest::result::maybe_ok; + // Reenable these test cases once we move the brillig implementation of inversion down into the acvm stdlib. #[test] @@ -78,7 +82,7 @@ fn inversion_brillig_oracle_equivalence() { let opcodes = vec![ Opcode::BrilligCall { - id: 0, + id: BrilligFunctionId(0), inputs: vec![ BrilligInputs::Single(Expression { // Input Register 0 @@ -207,7 +211,7 @@ fn double_inversion_brillig_oracle() { let opcodes = vec![ Opcode::BrilligCall { - id: 0, + id: BrilligFunctionId(0), inputs: vec![ BrilligInputs::Single(Expression { // Input Register 0 @@ -402,7 +406,7 @@ fn oracle_dependent_execution() { let opcodes = vec![ Opcode::AssertZero(equality_check), Opcode::BrilligCall { - id: 0, + id: BrilligFunctionId(0), inputs: vec![ BrilligInputs::Single(w_x.into()), // Input Register 0 BrilligInputs::Single(Expression::default()), // Input Register 1 @@ -510,7 +514,7 @@ fn brillig_oracle_predicate() { }; let opcodes = vec![Opcode::BrilligCall { - id: 0, + id: BrilligFunctionId(0), inputs: vec![ BrilligInputs::Single(Expression { mul_terms: vec![], @@ -646,7 +650,7 @@ fn unsatisfied_opcode_resolved_brillig() { let opcodes = vec![ Opcode::BrilligCall { - id: 0, + id: BrilligFunctionId(0), inputs: vec![ BrilligInputs::Single(Expression { mul_terms: vec![], @@ -671,6 +675,7 @@ fn unsatisfied_opcode_resolved_brillig() { assert_eq!( solver_status, ACVMStatus::Failure(OpcodeResolutionError::BrilligFunctionFailed { + function_id: BrilligFunctionId(0), payload: None, call_stack: vec![OpcodeLocation::Brillig { acir_index: 0, brillig_index: 3 }] }), @@ -722,3 +727,187 @@ fn memory_operations() { assert_eq!(witness_map[&Witness(8)], FieldElement::from(6u128)); } + +// Solve the given BlackBoxFuncCall with witnesses: 1, 2 as x, y, resp. +#[cfg(test)] +fn solve_blackbox_func_call( + blackbox_func_call: impl Fn( + Option, + Option, + ) -> BlackBoxFuncCall, + x: (FieldElement, bool), // if false, use a Witness + y: (FieldElement, bool), // if false, use a Witness +) -> FieldElement { + let (x, x_constant) = x; + let (y, y_constant) = y; + + let initial_witness = WitnessMap::from(BTreeMap::from_iter([(Witness(1), x), (Witness(2), y)])); + + let mut lhs = None; + if x_constant { + lhs = Some(x); + } + + let mut rhs = None; + if y_constant { + rhs = Some(y); + } + + let op = Opcode::BlackBoxFuncCall(blackbox_func_call(lhs, rhs)); + let opcodes = vec![op]; + let unconstrained_functions = vec![]; + let mut acvm = + ACVM::new(&StubbedBlackBoxSolver, &opcodes, initial_witness, &unconstrained_functions, &[]); + let solver_status = acvm.solve(); + assert_eq!(solver_status, ACVMStatus::Solved); + let witness_map = acvm.finalize(); + + witness_map[&Witness(3)] +} + +fn function_input_from_option( + witness: Witness, + opt_constant: Option, +) -> FunctionInput { + opt_constant + .map(|constant| FunctionInput::constant(constant, FieldElement::max_num_bits())) + .unwrap_or(FunctionInput::witness(witness, FieldElement::max_num_bits())) +} + +fn and_op(x: Option, y: Option) -> BlackBoxFuncCall { + let lhs = function_input_from_option(Witness(1), x); + let rhs = function_input_from_option(Witness(2), y); + BlackBoxFuncCall::AND { lhs, rhs, output: Witness(3) } +} + +fn xor_op(x: Option, y: Option) -> BlackBoxFuncCall { + let lhs = function_input_from_option(Witness(1), x); + let rhs = function_input_from_option(Witness(2), y); + BlackBoxFuncCall::XOR { lhs, rhs, output: Witness(3) } +} + +fn prop_assert_commutative( + op: impl Fn(Option, Option) -> BlackBoxFuncCall, + x: (FieldElement, bool), + y: (FieldElement, bool), +) -> (FieldElement, FieldElement) { + (solve_blackbox_func_call(&op, x, y), solve_blackbox_func_call(&op, y, x)) +} + +fn prop_assert_associative( + op: impl Fn(Option, Option) -> BlackBoxFuncCall, + x: (FieldElement, bool), + y: (FieldElement, bool), + z: (FieldElement, bool), + use_constant_xy: bool, + use_constant_yz: bool, +) -> (FieldElement, FieldElement) { + let f_xy = (solve_blackbox_func_call(&op, x, y), use_constant_xy); + let f_f_xy_z = solve_blackbox_func_call(&op, f_xy, z); + + let f_yz = (solve_blackbox_func_call(&op, y, z), use_constant_yz); + let f_x_f_yz = solve_blackbox_func_call(&op, x, f_yz); + + (f_f_xy_z, f_x_f_yz) +} + +fn prop_assert_identity_l( + op: impl Fn(Option, Option) -> BlackBoxFuncCall, + op_identity: (FieldElement, bool), + x: (FieldElement, bool), +) -> (FieldElement, FieldElement) { + (solve_blackbox_func_call(op, op_identity, x), x.0) +} + +fn prop_assert_zero_l( + op: impl Fn(Option, Option) -> BlackBoxFuncCall, + op_zero: (FieldElement, bool), + x: (FieldElement, bool), +) -> (FieldElement, FieldElement) { + (solve_blackbox_func_call(op, op_zero, x), FieldElement::zero()) +} + +prop_compose! { + // Use both `u128` and hex proptest strategies + fn field_element() + (u128_or_hex in maybe_ok(any::(), "[0-9a-f]{64}"), + constant_input: bool) + -> (FieldElement, bool) + { + match u128_or_hex { + Ok(number) => (FieldElement::from(number), constant_input), + Err(hex) => (FieldElement::from_hex(&hex).expect("should accept any 32 byte hex string"), constant_input), + } + } +} + +fn field_element_ones() -> FieldElement { + let exponent: FieldElement = (253_u128).into(); + FieldElement::from(2u128).pow(&exponent) - FieldElement::one() +} + +proptest! { + + #[test] + fn and_commutative(x in field_element(), y in field_element()) { + let (lhs, rhs) = prop_assert_commutative(and_op, x, y); + prop_assert_eq!(lhs, rhs); + } + + #[test] + fn xor_commutative(x in field_element(), y in field_element()) { + let (lhs, rhs) = prop_assert_commutative(xor_op, x, y); + prop_assert_eq!(lhs, rhs); + } + + #[test] + fn and_associative(x in field_element(), y in field_element(), z in field_element(), use_constant_xy: bool, use_constant_yz: bool) { + let (lhs, rhs) = prop_assert_associative(and_op, x, y, z, use_constant_xy, use_constant_yz); + prop_assert_eq!(lhs, rhs); + } + + #[test] + // TODO(https://github.com/noir-lang/noir/issues/5638) + #[should_panic(expected = "assertion failed: `(left == right)`")] + fn xor_associative(x in field_element(), y in field_element(), z in field_element(), use_constant_xy: bool, use_constant_yz: bool) { + let (lhs, rhs) = prop_assert_associative(xor_op, x, y, z, use_constant_xy, use_constant_yz); + prop_assert_eq!(lhs, rhs); + } + + // test that AND(x, x) == x + #[test] + fn and_self_identity(x in field_element()) { + prop_assert_eq!(solve_blackbox_func_call(and_op, x, x), x.0); + } + + // test that XOR(x, x) == 0 + #[test] + fn xor_self_zero(x in field_element()) { + prop_assert_eq!(solve_blackbox_func_call(xor_op, x, x), FieldElement::zero()); + } + + #[test] + fn and_identity_l(x in field_element(), ones_constant: bool) { + let ones = (field_element_ones(), ones_constant); + let (lhs, rhs) = prop_assert_identity_l(and_op, ones, x); + if x <= ones { + prop_assert_eq!(lhs, rhs); + } else { + prop_assert!(lhs != rhs); + } + } + + #[test] + fn xor_identity_l(x in field_element(), zero_constant: bool) { + let zero = (FieldElement::zero(), zero_constant); + let (lhs, rhs) = prop_assert_identity_l(xor_op, zero, x); + prop_assert_eq!(lhs, rhs); + } + + #[test] + fn and_zero_l(x in field_element(), ones_constant: bool) { + let zero = (FieldElement::zero(), ones_constant); + let (lhs, rhs) = prop_assert_zero_l(and_op, zero, x); + prop_assert_eq!(lhs, rhs); + } +} diff --git a/noir/noir-repo/acvm-repo/acvm_js/Cargo.toml b/noir/noir-repo/acvm-repo/acvm_js/Cargo.toml index 10ab2f62fdd5..0d90b9ba54f9 100644 --- a/noir/noir-repo/acvm-repo/acvm_js/Cargo.toml +++ b/noir/noir-repo/acvm-repo/acvm_js/Cargo.toml @@ -2,7 +2,7 @@ name = "acvm_js" description = "Typescript wrapper around the ACVM allowing execution of ACIR code" # x-release-please-start-version -version = "0.48.0" +version = "0.49.0" # x-release-please-end authors.workspace = true edition.workspace = true diff --git a/noir/noir-repo/acvm-repo/acvm_js/build.sh b/noir/noir-repo/acvm-repo/acvm_js/build.sh index c07d2d8a4c1d..16fb26e55db1 100755 --- a/noir/noir-repo/acvm-repo/acvm_js/build.sh +++ b/noir/noir-repo/acvm-repo/acvm_js/build.sh @@ -25,7 +25,7 @@ function run_if_available { require_command jq require_command cargo require_command wasm-bindgen -#require_command wasm-opt +require_command wasm-opt self_path=$(dirname "$(readlink -f "$0")") pname=$(cargo read-manifest | jq -r '.name') diff --git a/noir/noir-repo/acvm-repo/acvm_js/package.json b/noir/noir-repo/acvm-repo/acvm_js/package.json index fe192471744b..f1e9cf5eef05 100644 --- a/noir/noir-repo/acvm-repo/acvm_js/package.json +++ b/noir/noir-repo/acvm-repo/acvm_js/package.json @@ -1,6 +1,6 @@ { "name": "@noir-lang/acvm_js", - "version": "0.48.0", + "version": "0.49.0", "publishConfig": { "access": "public" }, diff --git a/noir/noir-repo/acvm-repo/blackbox_solver/Cargo.toml b/noir/noir-repo/acvm-repo/blackbox_solver/Cargo.toml index c390c8117881..10b491c7f676 100644 --- a/noir/noir-repo/acvm-repo/blackbox_solver/Cargo.toml +++ b/noir/noir-repo/acvm-repo/blackbox_solver/Cargo.toml @@ -2,7 +2,7 @@ name = "acvm_blackbox_solver" description = "A solver for the blackbox functions found in ACIR and Brillig" # x-release-please-start-version -version = "0.48.0" +version = "0.49.0" # x-release-please-end authors.workspace = true edition.workspace = true diff --git a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/Cargo.toml b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/Cargo.toml index 6129adada754..ab677396c220 100644 --- a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/Cargo.toml +++ b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/Cargo.toml @@ -2,7 +2,7 @@ name = "bn254_blackbox_solver" description = "Solvers for black box functions which are specific for the bn254 curve" # x-release-please-start-version -version = "0.48.0" +version = "0.49.0" # x-release-please-end authors.workspace = true edition.workspace = true diff --git a/noir/noir-repo/acvm-repo/brillig/Cargo.toml b/noir/noir-repo/acvm-repo/brillig/Cargo.toml index 7c962964303e..631acbd55d87 100644 --- a/noir/noir-repo/acvm-repo/brillig/Cargo.toml +++ b/noir/noir-repo/acvm-repo/brillig/Cargo.toml @@ -2,7 +2,7 @@ name = "brillig" description = "Brillig is the bytecode ACIR uses for non-determinism." # x-release-please-start-version -version = "0.48.0" +version = "0.49.0" # x-release-please-end authors.workspace = true edition.workspace = true diff --git a/noir/noir-repo/acvm-repo/brillig_vm/Cargo.toml b/noir/noir-repo/acvm-repo/brillig_vm/Cargo.toml index 68ac094bac86..ebd35f1579fe 100644 --- a/noir/noir-repo/acvm-repo/brillig_vm/Cargo.toml +++ b/noir/noir-repo/acvm-repo/brillig_vm/Cargo.toml @@ -2,7 +2,7 @@ name = "brillig_vm" description = "The virtual machine that processes Brillig bytecode, used to introduce non-determinism to the ACVM" # x-release-please-start-version -version = "0.48.0" +version = "0.49.0" # x-release-please-end authors.workspace = true edition.workspace = true diff --git a/noir/noir-repo/aztec_macros/src/lib.rs b/noir/noir-repo/aztec_macros/src/lib.rs index 580a132aa5a4..ec1d395725db 100644 --- a/noir/noir-repo/aztec_macros/src/lib.rs +++ b/noir/noir-repo/aztec_macros/src/lib.rs @@ -62,18 +62,25 @@ fn transform( file_id: FileId, context: &HirContext, ) -> Result { + let empty_spans = context.def_interner.is_in_lsp_mode(); + // Usage -> mut ast -> aztec_library::transform(&mut ast) // Covers all functions in the ast for submodule in ast.submodules.iter_mut().filter(|submodule| submodule.is_contract) { - if transform_module(&file_id, &mut submodule.contents, submodule.name.0.contents.as_str()) - .map_err(|err| (err.into(), file_id))? + if transform_module( + &file_id, + &mut submodule.contents, + submodule.name.0.contents.as_str(), + empty_spans, + ) + .map_err(|err| (err.into(), file_id))? { check_for_aztec_dependency(crate_id, context)?; } } - generate_event_impls(&mut ast).map_err(|err| (err.into(), file_id))?; - generate_note_interface_impl(&mut ast).map_err(|err| (err.into(), file_id))?; + generate_event_impls(&mut ast, empty_spans).map_err(|err| (err.into(), file_id))?; + generate_note_interface_impl(&mut ast, empty_spans).map_err(|err| (err.into(), file_id))?; Ok(ast) } @@ -85,6 +92,7 @@ fn transform_module( file_id: &FileId, module: &mut SortedModule, module_name: &str, + empty_spans: bool, ) -> Result { let mut has_transformed_module = false; @@ -99,7 +107,7 @@ fn transform_module( if !check_for_storage_implementation(module, storage_struct_name) { generate_storage_implementation(module, storage_struct_name)?; } - generate_storage_layout(module, storage_struct_name.clone(), module_name)?; + generate_storage_layout(module, storage_struct_name.clone(), module_name, empty_spans)?; } let has_initializer = module.functions.iter().any(|func| { @@ -144,7 +152,7 @@ fn transform_module( let stub_src = stub_function(fn_type, func, is_static); stubs.push((stub_src, Location { file: *file_id, span: func.name_ident().span() })); - export_fn_abi(&mut module.types, func)?; + export_fn_abi(&mut module.types, func, empty_spans)?; transform_function( fn_type, func, @@ -200,7 +208,7 @@ fn transform_module( }); } - generate_contract_interface(module, module_name, &stubs, storage_defined)?; + generate_contract_interface(module, module_name, &stubs, storage_defined, empty_spans)?; } Ok(has_transformed_module) diff --git a/noir/noir-repo/aztec_macros/src/transforms/compute_note_hash_and_optionally_a_nullifier.rs b/noir/noir-repo/aztec_macros/src/transforms/compute_note_hash_and_optionally_a_nullifier.rs index 40fde39a06f9..8983266dab9e 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/compute_note_hash_and_optionally_a_nullifier.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/compute_note_hash_and_optionally_a_nullifier.rs @@ -3,9 +3,10 @@ use noirc_frontend::ast::{FunctionReturnType, NoirFunction, UnresolvedTypeData}; use noirc_frontend::{ graph::CrateId, macros_api::{FileId, HirContext}, - parse_program, Type, + Type, }; +use crate::utils::parse_utils::parse_program; use crate::utils::{ errors::AztecMacroError, hir_utils::{ @@ -125,8 +126,12 @@ pub fn inject_compute_note_hash_and_optionally_a_nullifier( notes_and_lengths.iter().map(|(note_type, _)| note_type.clone()).collect::>(); // We can now generate a version of compute_note_hash_and_optionally_a_nullifier tailored for the contract in this crate. - let func = - generate_compute_note_hash_and_optionally_a_nullifier(¬e_types, max_note_length); + let empty_spans = context.def_interner.is_in_lsp_mode(); + let func = generate_compute_note_hash_and_optionally_a_nullifier( + ¬e_types, + max_note_length, + empty_spans, + ); // And inject the newly created function into the contract. @@ -149,11 +154,12 @@ pub fn inject_compute_note_hash_and_optionally_a_nullifier( fn generate_compute_note_hash_and_optionally_a_nullifier( note_types: &[String], max_note_length: u128, + empty_spans: bool, ) -> NoirFunction { let function_source = generate_compute_note_hash_and_optionally_a_nullifier_source(note_types, max_note_length); - let (function_ast, errors) = parse_program(&function_source); + let (function_ast, errors) = parse_program(&function_source, empty_spans); if !errors.is_empty() { dbg!(errors.clone()); } diff --git a/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs b/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs index 56107de77c56..dd3ec7f6a757 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs @@ -5,13 +5,13 @@ use noirc_frontend::ast::{Ident, NoirFunction, UnresolvedTypeData}; use noirc_frontend::{ graph::CrateId, macros_api::{FieldElement, FileId, HirContext, HirExpression, HirLiteral, HirStatement}, - parse_program, parser::SortedModule, Type, }; use tiny_keccak::{Hasher, Keccak}; +use crate::utils::parse_utils::parse_program; use crate::utils::{ errors::AztecMacroError, hir_utils::{collect_crate_structs, get_contract_module_data, signature_of_type}, @@ -203,6 +203,7 @@ pub fn generate_contract_interface( module_name: &str, stubs: &[(String, Location)], has_storage_layout: bool, + empty_spans: bool, ) -> Result<(), AztecMacroError> { let storage_layout_getter = format!( "#[contract_library_method] @@ -253,7 +254,7 @@ pub fn generate_contract_interface( if has_storage_layout { format!("#[contract_library_method]\n{}", storage_layout_getter) } else { "".to_string() } ); - let (contract_interface_ast, errors) = parse_program(&contract_interface); + let (contract_interface_ast, errors) = parse_program(&contract_interface, empty_spans); if !errors.is_empty() { dbg!(errors); return Err(AztecMacroError::CouldNotGenerateContractInterface { secondary_message: Some("Failed to parse Noir macro code during contract interface generation. This is either a bug in the compiler or the Noir macro code".to_string()), }); diff --git a/noir/noir-repo/aztec_macros/src/transforms/events.rs b/noir/noir-repo/aztec_macros/src/transforms/events.rs index ecfca40189dc..8b71bd77ae68 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/events.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/events.rs @@ -4,16 +4,19 @@ use noirc_frontend::token::SecondaryAttribute; use noirc_frontend::{ graph::CrateId, macros_api::{FileId, HirContext}, - parse_program, parser::SortedModule, }; use crate::utils::hir_utils::collect_crate_structs; +use crate::utils::parse_utils::parse_program; use crate::utils::{ast_utils::is_custom_attribute, errors::AztecMacroError}; // Automatic implementation of most of the methods in the EventInterface trait, guiding the user with meaningful error messages in case some // methods must be implemented manually. -pub fn generate_event_impls(module: &mut SortedModule) -> Result<(), AztecMacroError> { +pub fn generate_event_impls( + module: &mut SortedModule, + empty_spans: bool, +) -> Result<(), AztecMacroError> { // Find structs annotated with #[aztec(event)] // Why doesn't this work ? Events are not tagged and do not appear, it seems only going through the submodule works // let annotated_event_structs = module @@ -56,28 +59,39 @@ pub fn generate_event_impls(module: &mut SortedModule) -> Result<(), AztecMacroE )); } - let mut event_interface_trait_impl = - generate_trait_impl_stub_event_interface(event_type.as_str(), event_byte_len)?; + let mut event_interface_trait_impl = generate_trait_impl_stub_event_interface( + event_type.as_str(), + event_byte_len, + empty_spans, + )?; event_interface_trait_impl.items.push(TraitImplItem::Function( - generate_fn_get_event_type_id(event_type.as_str(), event_len)?, + generate_fn_get_event_type_id(event_type.as_str(), event_len, empty_spans)?, )); event_interface_trait_impl.items.push(TraitImplItem::Function( - generate_fn_private_to_be_bytes(event_type.as_str(), event_byte_len)?, + generate_fn_private_to_be_bytes(event_type.as_str(), event_byte_len, empty_spans)?, )); event_interface_trait_impl.items.push(TraitImplItem::Function( - generate_fn_to_be_bytes(event_type.as_str(), event_byte_len)?, + generate_fn_to_be_bytes(event_type.as_str(), event_byte_len, empty_spans)?, )); event_interface_trait_impl .items - .push(TraitImplItem::Function(generate_fn_emit(event_type.as_str())?)); + .push(TraitImplItem::Function(generate_fn_emit(event_type.as_str(), empty_spans)?)); submodule.contents.trait_impls.push(event_interface_trait_impl); - let serialize_trait_impl = - generate_trait_impl_serialize(event_type.as_str(), event_len, &event_fields)?; + let serialize_trait_impl = generate_trait_impl_serialize( + event_type.as_str(), + event_len, + &event_fields, + empty_spans, + )?; submodule.contents.trait_impls.push(serialize_trait_impl); - let deserialize_trait_impl = - generate_trait_impl_deserialize(event_type.as_str(), event_len, &event_fields)?; + let deserialize_trait_impl = generate_trait_impl_deserialize( + event_type.as_str(), + event_len, + &event_fields, + empty_spans, + )?; submodule.contents.trait_impls.push(deserialize_trait_impl); } } @@ -88,6 +102,7 @@ pub fn generate_event_impls(module: &mut SortedModule) -> Result<(), AztecMacroE fn generate_trait_impl_stub_event_interface( event_type: &str, byte_length: u32, + empty_spans: bool, ) -> Result { let byte_length_without_randomness = byte_length - 32; let trait_impl_source = format!( @@ -98,7 +113,7 @@ impl dep::aztec::event::event_interface::EventInterface<{byte_length}, {byte_len ) .to_string(); - let (parsed_ast, errors) = parse_program(&trait_impl_source); + let (parsed_ast, errors) = parse_program(&trait_impl_source, empty_spans); if !errors.is_empty() { dbg!(errors); return Err(AztecMacroError::CouldNotImplementEventInterface { @@ -116,6 +131,7 @@ fn generate_trait_impl_serialize( event_type: &str, event_len: u32, event_fields: &[(String, String)], + empty_spans: bool, ) -> Result { let field_names = event_fields .iter() @@ -143,7 +159,7 @@ fn generate_trait_impl_serialize( ) .to_string(); - let (parsed_ast, errors) = parse_program(&trait_impl_source); + let (parsed_ast, errors) = parse_program(&trait_impl_source, empty_spans); if !errors.is_empty() { dbg!(errors); return Err(AztecMacroError::CouldNotImplementEventInterface { @@ -161,6 +177,7 @@ fn generate_trait_impl_deserialize( event_type: &str, event_len: u32, event_fields: &[(String, String)], + empty_spans: bool, ) -> Result { let field_names: Vec = event_fields .iter() @@ -189,7 +206,7 @@ fn generate_trait_impl_deserialize( ) .to_string(); - let (parsed_ast, errors) = parse_program(&trait_impl_source); + let (parsed_ast, errors) = parse_program(&trait_impl_source, empty_spans); if !errors.is_empty() { dbg!(errors); return Err(AztecMacroError::CouldNotImplementEventInterface { @@ -206,6 +223,7 @@ fn generate_trait_impl_deserialize( fn generate_fn_get_event_type_id( event_type: &str, field_length: u32, + empty_spans: bool, ) -> Result { let from_signature_input = std::iter::repeat("Field").take(field_length as usize).collect::>().join(","); @@ -218,7 +236,7 @@ fn generate_fn_get_event_type_id( ) .to_string(); - let (function_ast, errors) = parse_program(&function_source); + let (function_ast, errors) = parse_program(&function_source, empty_spans); if !errors.is_empty() { dbg!(errors); return Err(AztecMacroError::CouldNotImplementEventInterface { @@ -235,6 +253,7 @@ fn generate_fn_get_event_type_id( fn generate_fn_private_to_be_bytes( event_type: &str, byte_length: u32, + empty_spans: bool, ) -> Result { let function_source = format!( " @@ -264,7 +283,7 @@ fn generate_fn_private_to_be_bytes( ) .to_string(); - let (function_ast, errors) = parse_program(&function_source); + let (function_ast, errors) = parse_program(&function_source, empty_spans); if !errors.is_empty() { dbg!(errors); return Err(AztecMacroError::CouldNotImplementEventInterface { @@ -281,6 +300,7 @@ fn generate_fn_private_to_be_bytes( fn generate_fn_to_be_bytes( event_type: &str, byte_length: u32, + empty_spans: bool, ) -> Result { let byte_length_without_randomness = byte_length - 32; let function_source = format!( @@ -308,7 +328,7 @@ fn generate_fn_to_be_bytes( ") .to_string(); - let (function_ast, errors) = parse_program(&function_source); + let (function_ast, errors) = parse_program(&function_source, empty_spans); if !errors.is_empty() { dbg!(errors); return Err(AztecMacroError::CouldNotImplementEventInterface { @@ -322,7 +342,7 @@ fn generate_fn_to_be_bytes( Ok(noir_fn) } -fn generate_fn_emit(event_type: &str) -> Result { +fn generate_fn_emit(event_type: &str, empty_spans: bool) -> Result { let function_source = format!( " fn emit(self: {event_type}, _emit: fn[Env](Self) -> ()) {{ @@ -332,7 +352,7 @@ fn generate_fn_emit(event_type: &str) -> Result { ) .to_string(); - let (function_ast, errors) = parse_program(&function_source); + let (function_ast, errors) = parse_program(&function_source, empty_spans); if !errors.is_empty() { dbg!(errors); return Err(AztecMacroError::CouldNotImplementEventInterface { diff --git a/noir/noir-repo/aztec_macros/src/transforms/functions.rs b/noir/noir-repo/aztec_macros/src/transforms/functions.rs index 4d8b6ef7cdfc..cd3fdd1fc62d 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/functions.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/functions.rs @@ -8,16 +8,18 @@ use noirc_frontend::ast::{ UnresolvedTypeData, Visibility, }; -use noirc_frontend::{macros_api::FieldElement, parse_program}; +use noirc_frontend::macros_api::FieldElement; use crate::utils::ast_utils::member_access; +use crate::utils::parse_utils::parse_program; use crate::{ chained_dep, chained_path, utils::{ ast_utils::{ assignment, assignment_with_type, call, cast, expression, ident, ident_path, index_array, make_eq, make_statement, make_type, method_call, mutable_assignment, - mutable_reference, path, return_type, variable, variable_ident, variable_path, + mutable_reference, path, path_segment, return_type, variable, variable_ident, + variable_path, }, errors::AztecMacroError, }, @@ -131,6 +133,7 @@ pub fn transform_function( pub fn export_fn_abi( types: &mut Vec, func: &NoirFunction, + empty_spans: bool, ) -> Result<(), AztecMacroError> { let mut parameters_struct_source: Option<&str> = None; @@ -197,7 +200,7 @@ pub fn export_fn_abi( program.push_str(&export_struct_source); - let (ast, errors) = parse_program(&program); + let (ast, errors) = parse_program(&program, empty_spans); if !errors.is_empty() { return Err(AztecMacroError::CouldNotExportFunctionAbi { span: None, @@ -722,8 +725,8 @@ fn add_struct_to_hasher(identifier: &Ident, hasher_name: &str) -> Statement { fn str_to_bytes(identifier: &Ident) -> (Statement, Ident) { // let identifier_as_bytes = identifier.as_bytes(); let var = variable_ident(identifier.clone()); - let contents = if let ExpressionKind::Variable(p, _) = &var.kind { - p.segments.first().cloned().unwrap_or_else(|| panic!("No segments")).0.contents + let contents = if let ExpressionKind::Variable(p) = &var.kind { + p.first_name() } else { panic!("Unexpected identifier type") }; diff --git a/noir/noir-repo/aztec_macros/src/transforms/note_interface.rs b/noir/noir-repo/aztec_macros/src/transforms/note_interface.rs index 3233e12ab73a..6fccded45ef8 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/note_interface.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/note_interface.rs @@ -6,7 +6,6 @@ use noirc_frontend::ast::{ use noirc_frontend::{ graph::CrateId, macros_api::{FileId, HirContext, HirExpression, HirLiteral, HirStatement}, - parse_program, parser::SortedModule, Type, }; @@ -16,11 +15,13 @@ use regex::Regex; // TODO(#7165): nuke the following dependency from here and Cargo.toml use tiny_keccak::{Hasher, Keccak}; +use crate::utils::parse_utils::parse_program; use crate::{ chained_dep, utils::{ ast_utils::{ check_trait_method_implemented, ident, ident_path, is_custom_attribute, make_type, + path_segment, }, errors::AztecMacroError, hir_utils::{fetch_notes, get_contract_module_data, inject_global}, @@ -29,7 +30,10 @@ use crate::{ // Automatic implementation of most of the methods in the NoteInterface trait, guiding the user with meaningful error messages in case some // methods must be implemented manually. -pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), AztecMacroError> { +pub fn generate_note_interface_impl( + module: &mut SortedModule, + empty_spans: bool, +) -> Result<(), AztecMacroError> { // Find structs annotated with #[aztec(note)] let annotated_note_structs = module .types @@ -45,8 +49,8 @@ pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), Azt .iter_mut() .find(|trait_impl| { if let UnresolvedTypeData::Named(struct_path, _, _) = &trait_impl.object_type.typ { - struct_path.last_segment() == note_struct.name - && trait_impl.trait_name.last_segment().0.contents == "NoteInterface" + struct_path.last_ident() == note_struct.name + && trait_impl.trait_name.last_name() == "NoteInterface" } else { false } @@ -58,10 +62,11 @@ pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), Azt note_struct.name.0.contents )), })?; - let note_interface_impl_span: Option = trait_impl.object_type.span; + let note_interface_impl_span: Option = + if empty_spans { None } else { trait_impl.object_type.span }; // Look for the note struct implementation, generate a default one if it doesn't exist (in order to append methods to it) let existing_impl = module.impls.iter_mut().find(|r#impl| match &r#impl.object_type.typ { - UnresolvedTypeData::Named(path, _, _) => path.last_segment().eq(¬e_struct.name), + UnresolvedTypeData::Named(path, _, _) => path.last_ident().eq(¬e_struct.name), _ => false, }); let note_impl = if let Some(note_impl) = existing_impl { @@ -73,7 +78,6 @@ pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), Azt generics: vec![], methods: vec![], where_clause: vec![], - is_comptime: false, }; module.impls.push(default_impl.clone()); module.impls.last_mut().unwrap() @@ -85,9 +89,7 @@ pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), Azt .trait_generics .iter() .map(|gen| match gen.typ.clone() { - UnresolvedTypeData::Named(path, _, _) => { - Ok(path.last_segment().0.contents.to_string()) - } + UnresolvedTypeData::Named(path, _, _) => Ok(path.last_name().to_string()), UnresolvedTypeData::Expression(UnresolvedTypeExpression::Constant(val, _)) => { Ok(val.to_string()) } @@ -108,9 +110,7 @@ pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), Azt // Automatically inject the header field if it's not present let (header_field_name, _) = if let Some(existing_header) = note_struct.fields.iter().find(|(_, field_type)| match &field_type.typ { - UnresolvedTypeData::Named(path, _, _) => { - path.last_segment().0.contents == "NoteHeader" - } + UnresolvedTypeData::Named(path, _, _) => path.last_name() == "NoteHeader", _ => false, }) { existing_header.clone() @@ -144,6 +144,7 @@ pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), Azt ¬e_serialized_len, &header_field_name.0.contents, note_interface_impl_span, + empty_spans, )?; trait_impl.items.push(TraitImplItem::Function(note_serialize_content_fn)); @@ -153,6 +154,7 @@ pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), Azt ¬e_serialized_len, &header_field_name.0.contents, note_interface_impl_span, + empty_spans, )?; trait_impl.items.push(TraitImplItem::Function(note_deserialize_content_fn)); @@ -161,6 +163,7 @@ pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), Azt ¬e_fields, &header_field_name.0.contents, note_interface_impl_span, + empty_spans, )?; structs_to_inject.push(note_properties_struct); let note_properties_fn = generate_note_properties_fn( @@ -168,6 +171,7 @@ pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), Azt ¬e_fields, &header_field_name.0.contents, note_interface_impl_span, + empty_spans, )?; note_impl.methods.push((note_properties_fn, note_impl.type_span)); } @@ -177,6 +181,7 @@ pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), Azt ¬e_type, &header_field_name.0.contents, note_interface_impl_span, + empty_spans, )?; trait_impl.items.push(TraitImplItem::Function(get_header_fn)); } @@ -185,6 +190,7 @@ pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), Azt ¬e_type, &header_field_name.0.contents, note_interface_impl_span, + empty_spans, )?; trait_impl.items.push(TraitImplItem::Function(set_header_fn)); } @@ -192,13 +198,16 @@ pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), Azt if !check_trait_method_implemented(trait_impl, "get_note_type_id") { let note_type_id = compute_note_type_id(¬e_type); let get_note_type_id_fn = - generate_get_note_type_id(note_type_id, note_interface_impl_span)?; + generate_get_note_type_id(note_type_id, note_interface_impl_span, empty_spans)?; trait_impl.items.push(TraitImplItem::Function(get_note_type_id_fn)); } if !check_trait_method_implemented(trait_impl, "compute_note_content_hash") { - let compute_note_content_hash_fn = - generate_compute_note_content_hash(¬e_type, note_interface_impl_span)?; + let compute_note_content_hash_fn = generate_compute_note_content_hash( + ¬e_type, + note_interface_impl_span, + empty_spans, + )?; trait_impl.items.push(TraitImplItem::Function(compute_note_content_hash_fn)); } @@ -208,6 +217,7 @@ pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), Azt note_bytes_len.as_str(), note_serialized_len.as_str(), note_interface_impl_span, + empty_spans, )?; trait_impl.items.push(TraitImplItem::Function(to_be_bytes_fn)); } @@ -222,6 +232,7 @@ fn generate_note_to_be_bytes( byte_length: &str, serialized_length: &str, impl_span: Option, + empty_spans: bool, ) -> Result { let function_source = format!( " @@ -252,7 +263,7 @@ fn generate_note_to_be_bytes( ) .to_string(); - let (function_ast, errors) = parse_program(&function_source); + let (function_ast, errors) = parse_program(&function_source, empty_spans); if !errors.is_empty() { dbg!(errors); return Err(AztecMacroError::CouldNotImplementNoteInterface { @@ -263,7 +274,7 @@ fn generate_note_to_be_bytes( let mut function_ast = function_ast.into_sorted(); let mut noir_fn = function_ast.functions.remove(0); - noir_fn.def.span = impl_span.unwrap(); + noir_fn.def.span = impl_span.unwrap_or_default(); noir_fn.def.visibility = ItemVisibility::Public; Ok(noir_fn) } @@ -272,6 +283,7 @@ fn generate_note_get_header( note_type: &String, note_header_field_name: &String, impl_span: Option, + empty_spans: bool, ) -> Result { let function_source = format!( " @@ -283,7 +295,7 @@ fn generate_note_get_header( ) .to_string(); - let (function_ast, errors) = parse_program(&function_source); + let (function_ast, errors) = parse_program(&function_source, empty_spans); if !errors.is_empty() { dbg!(errors); return Err(AztecMacroError::CouldNotImplementNoteInterface { @@ -294,7 +306,7 @@ fn generate_note_get_header( let mut function_ast = function_ast.into_sorted(); let mut noir_fn = function_ast.functions.remove(0); - noir_fn.def.span = impl_span.unwrap(); + noir_fn.def.span = impl_span.unwrap_or_default(); noir_fn.def.visibility = ItemVisibility::Public; Ok(noir_fn) } @@ -303,6 +315,7 @@ fn generate_note_set_header( note_type: &String, note_header_field_name: &String, impl_span: Option, + empty_spans: bool, ) -> Result { let function_source = format!( " @@ -313,7 +326,7 @@ fn generate_note_set_header( note_type, note_header_field_name ); - let (function_ast, errors) = parse_program(&function_source); + let (function_ast, errors) = parse_program(&function_source, empty_spans); if !errors.is_empty() { dbg!(errors); return Err(AztecMacroError::CouldNotImplementNoteInterface { @@ -324,7 +337,7 @@ fn generate_note_set_header( let mut function_ast = function_ast.into_sorted(); let mut noir_fn = function_ast.functions.remove(0); - noir_fn.def.span = impl_span.unwrap(); + noir_fn.def.span = impl_span.unwrap_or_default(); noir_fn.def.visibility = ItemVisibility::Public; Ok(noir_fn) } @@ -334,6 +347,7 @@ fn generate_note_set_header( fn generate_get_note_type_id( note_type_id: u32, impl_span: Option, + empty_spans: bool, ) -> Result { // TODO(#7165): replace {} with dep::aztec::protocol_types::abis::note_selector::compute_note_selector(\"{}\") in the function source below let function_source = format!( @@ -346,7 +360,7 @@ fn generate_get_note_type_id( ) .to_string(); - let (function_ast, errors) = parse_program(&function_source); + let (function_ast, errors) = parse_program(&function_source, empty_spans); if !errors.is_empty() { dbg!(errors); return Err(AztecMacroError::CouldNotImplementNoteInterface { @@ -357,7 +371,7 @@ fn generate_get_note_type_id( let mut function_ast = function_ast.into_sorted(); let mut noir_fn = function_ast.functions.remove(0); - noir_fn.def.span = impl_span.unwrap(); + noir_fn.def.span = impl_span.unwrap_or_default(); noir_fn.def.visibility = ItemVisibility::Public; Ok(noir_fn) } @@ -376,11 +390,12 @@ fn generate_note_properties_struct( note_fields: &[(String, String)], note_header_field_name: &String, impl_span: Option, + empty_spans: bool, ) -> Result { let struct_source = generate_note_properties_struct_source(note_type, note_fields, note_header_field_name); - let (struct_ast, errors) = parse_program(&struct_source); + let (struct_ast, errors) = parse_program(&struct_source, empty_spans); if !errors.is_empty() { dbg!(errors); return Err(AztecMacroError::CouldNotImplementNoteInterface { @@ -409,6 +424,7 @@ fn generate_note_deserialize_content( note_serialize_len: &String, note_header_field_name: &String, impl_span: Option, + empty_spans: bool, ) -> Result { let function_source = generate_note_deserialize_content_source( note_type, @@ -417,7 +433,7 @@ fn generate_note_deserialize_content( note_header_field_name, ); - let (function_ast, errors) = parse_program(&function_source); + let (function_ast, errors) = parse_program(&function_source, empty_spans); if !errors.is_empty() { dbg!(errors); return Err(AztecMacroError::CouldNotImplementNoteInterface { @@ -428,7 +444,7 @@ fn generate_note_deserialize_content( let mut function_ast = function_ast.into_sorted(); let mut noir_fn = function_ast.functions.remove(0); - noir_fn.def.span = impl_span.unwrap(); + noir_fn.def.span = impl_span.unwrap_or_default(); noir_fn.def.visibility = ItemVisibility::Public; Ok(noir_fn) } @@ -446,6 +462,7 @@ fn generate_note_serialize_content( note_serialize_len: &String, note_header_field_name: &String, impl_span: Option, + empty_spans: bool, ) -> Result { let function_source = generate_note_serialize_content_source( note_type, @@ -454,7 +471,7 @@ fn generate_note_serialize_content( note_header_field_name, ); - let (function_ast, errors) = parse_program(&function_source); + let (function_ast, errors) = parse_program(&function_source, empty_spans); if !errors.is_empty() { dbg!(errors); return Err(AztecMacroError::CouldNotImplementNoteInterface { @@ -465,7 +482,7 @@ fn generate_note_serialize_content( let mut function_ast = function_ast.into_sorted(); let mut noir_fn = function_ast.functions.remove(0); - noir_fn.def.span = impl_span.unwrap(); + noir_fn.def.span = impl_span.unwrap_or_default(); noir_fn.def.visibility = ItemVisibility::Public; Ok(noir_fn) } @@ -476,10 +493,11 @@ fn generate_note_properties_fn( note_fields: &[(String, String)], note_header_field_name: &String, impl_span: Option, + empty_spans: bool, ) -> Result { let function_source = generate_note_properties_fn_source(note_type, note_fields, note_header_field_name); - let (function_ast, errors) = parse_program(&function_source); + let (function_ast, errors) = parse_program(&function_source, empty_spans); if !errors.is_empty() { dbg!(errors); return Err(AztecMacroError::CouldNotImplementNoteInterface { @@ -489,7 +507,7 @@ fn generate_note_properties_fn( } let mut function_ast = function_ast.into_sorted(); let mut noir_fn = function_ast.functions.remove(0); - noir_fn.def.span = impl_span.unwrap(); + noir_fn.def.span = impl_span.unwrap_or_default(); noir_fn.def.visibility = ItemVisibility::Public; Ok(noir_fn) } @@ -502,6 +520,7 @@ fn generate_note_properties_fn( fn generate_compute_note_content_hash( note_type: &String, impl_span: Option, + empty_spans: bool, ) -> Result { let function_source = format!( " @@ -511,7 +530,7 @@ fn generate_compute_note_content_hash( ", note_type ); - let (function_ast, errors) = parse_program(&function_source); + let (function_ast, errors) = parse_program(&function_source, empty_spans); if !errors.is_empty() { dbg!(errors); return Err(AztecMacroError::CouldNotImplementNoteInterface { @@ -521,7 +540,7 @@ fn generate_compute_note_content_hash( } let mut function_ast = function_ast.into_sorted(); let mut noir_fn = function_ast.functions.remove(0); - noir_fn.def.span = impl_span.unwrap(); + noir_fn.def.span = impl_span.unwrap_or_default(); noir_fn.def.visibility = ItemVisibility::Public; Ok(noir_fn) } @@ -529,6 +548,7 @@ fn generate_compute_note_content_hash( fn generate_note_exports_global( note_type: &str, note_type_id: &str, + empty_spans: bool, ) -> Result { let struct_source = format!( " @@ -541,7 +561,7 @@ fn generate_note_exports_global( ) .to_string(); - let (global_ast, errors) = parse_program(&struct_source); + let (global_ast, errors) = parse_program(&struct_source, empty_spans); if !errors.is_empty() { dbg!(errors); return Err(AztecMacroError::CouldNotImplementNoteInterface { @@ -783,9 +803,11 @@ pub fn inject_note_exports( file_id, )), }?; + let empty_spans = context.def_interner.is_in_lsp_mode(); let global = generate_note_exports_global( ¬e.borrow().name.0.contents, ¬e_type_id.to_hex(), + empty_spans, ) .map_err(|err| (err, file_id))?; diff --git a/noir/noir-repo/aztec_macros/src/transforms/storage.rs b/noir/noir-repo/aztec_macros/src/transforms/storage.rs index 1c6ef6340708..dacea1a95e37 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/storage.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/storage.rs @@ -10,18 +10,18 @@ use noirc_frontend::{ FieldElement, FileId, HirContext, HirExpression, HirLiteral, HirStatement, NodeInterner, }, node_interner::TraitId, - parse_program, parser::SortedModule, token::SecondaryAttribute, Type, }; +use crate::utils::parse_utils::parse_program; use crate::{ chained_path, utils::{ ast_utils::{ call, expression, ident, ident_path, is_custom_attribute, lambda, make_statement, - make_type, pattern, return_type, variable, variable_path, + make_type, path_segment, pattern, return_type, variable, variable_path, }, errors::AztecMacroError, hir_utils::{ @@ -59,7 +59,7 @@ fn inject_context_in_storage_field(field: &mut UnresolvedType) -> Result<(), Azt vec![], false, ))); - match path.segments.last().unwrap().0.contents.as_str() { + match path.last_name() { "Map" => inject_context_in_storage_field(&mut generics[1]), _ => Ok(()), } @@ -106,9 +106,7 @@ pub fn check_for_storage_implementation( storage_struct_name: &String, ) -> bool { module.impls.iter().any(|r#impl| match &r#impl.object_type.typ { - UnresolvedTypeData::Named(path, _, _) => { - path.segments.last().is_some_and(|segment| segment.0.contents == *storage_struct_name) - } + UnresolvedTypeData::Named(path, _, _) => path.last_name() == *storage_struct_name, _ => false, }) } @@ -123,8 +121,8 @@ pub fn generate_storage_field_constructor( match typ { UnresolvedTypeData::Named(path, generics, _) => { let mut new_path = path.clone().to_owned(); - new_path.segments.push(ident("new")); - match path.segments.last().unwrap().0.contents.as_str() { + new_path.segments.push(path_segment("new")); + match path.last_name() { "Map" => Ok(call( variable_path(new_path), vec![ @@ -248,7 +246,6 @@ pub fn generate_storage_implementation( methods: vec![(init, Span::default())], where_clause: vec![], - is_comptime: false, }; module.impls.push(storage_impl); @@ -501,6 +498,7 @@ pub fn generate_storage_layout( module: &mut SortedModule, storage_struct_name: String, module_name: &str, + empty_spans: bool, ) -> Result<(), AztecMacroError> { let definition = module .types @@ -533,7 +531,7 @@ pub fn generate_storage_layout( storable_fields_impl.join(",\n") ); - let (struct_ast, errors) = parse_program(&storage_fields_source); + let (struct_ast, errors) = parse_program(&storage_fields_source, empty_spans); if !errors.is_empty() { dbg!(errors); return Err(AztecMacroError::CouldNotExportStorageLayout { diff --git a/noir/noir-repo/aztec_macros/src/utils/ast_utils.rs b/noir/noir-repo/aztec_macros/src/utils/ast_utils.rs index 4467c4bca4b5..a74ec5b777a3 100644 --- a/noir/noir-repo/aztec_macros/src/utils/ast_utils.rs +++ b/noir/noir-repo/aztec_macros/src/utils/ast_utils.rs @@ -2,8 +2,8 @@ use noirc_errors::{Span, Spanned}; use noirc_frontend::ast::{ BinaryOpKind, CallExpression, CastExpression, Expression, ExpressionKind, FunctionReturnType, Ident, IndexExpression, InfixExpression, Lambda, LetStatement, MemberAccessExpression, - MethodCallExpression, NoirTraitImpl, Path, Pattern, PrefixExpression, Statement, StatementKind, - TraitImplItem, UnaryOp, UnresolvedType, UnresolvedTypeData, + MethodCallExpression, NoirTraitImpl, Path, PathSegment, Pattern, PrefixExpression, Statement, + StatementKind, TraitImplItem, UnaryOp, UnresolvedType, UnresolvedTypeData, }; use noirc_frontend::token::SecondaryAttribute; @@ -18,6 +18,10 @@ pub fn ident_path(name: &str) -> Path { Path::from_ident(ident(name)) } +pub fn path_segment(name: &str) -> PathSegment { + PathSegment::from(ident(name)) +} + pub fn path(ident: Ident) -> Path { Path::from_ident(ident) } @@ -27,15 +31,15 @@ pub fn expression(kind: ExpressionKind) -> Expression { } pub fn variable(name: &str) -> Expression { - expression(ExpressionKind::Variable(ident_path(name), None)) + expression(ExpressionKind::Variable(ident_path(name))) } pub fn variable_ident(identifier: Ident) -> Expression { - expression(ExpressionKind::Variable(path(identifier), None)) + expression(ExpressionKind::Variable(path(identifier))) } pub fn variable_path(path: Path) -> Expression { - expression(ExpressionKind::Variable(path, None)) + expression(ExpressionKind::Variable(path)) } pub fn method_call( @@ -149,7 +153,7 @@ macro_rules! chained_path { { let mut base_path = ident_path($base); $( - base_path.segments.push(ident($tail)); + base_path.segments.push(path_segment($tail)); )* base_path } @@ -163,7 +167,7 @@ macro_rules! chained_dep { let mut base_path = ident_path($base); base_path.kind = PathKind::Plain; $( - base_path.segments.push(ident($tail)); + base_path.segments.push(path_segment($tail)); )* base_path } diff --git a/noir/noir-repo/aztec_macros/src/utils/hir_utils.rs b/noir/noir-repo/aztec_macros/src/utils/hir_utils.rs index 200ce3099cbd..0a8ce371708d 100644 --- a/noir/noir-repo/aztec_macros/src/utils/hir_utils.rs +++ b/noir/noir-repo/aztec_macros/src/utils/hir_utils.rs @@ -195,7 +195,7 @@ pub fn inject_fn( let trait_id = None; items.functions.push(UnresolvedFunctions { file_id, functions, trait_id, self_type: None }); - let mut errors = Elaborator::elaborate(context, *crate_id, items, None); + let mut errors = Elaborator::elaborate(context, *crate_id, items, None, false); errors.retain(|(error, _)| !CustomDiagnostic::from(error).is_warning()); if !errors.is_empty() { @@ -241,7 +241,7 @@ pub fn inject_global( let mut items = CollectedItems::default(); items.globals.push(UnresolvedGlobal { file_id, module_id, global_id, stmt_def: global }); - let _errors = Elaborator::elaborate(context, *crate_id, items, None); + let _errors = Elaborator::elaborate(context, *crate_id, items, None, false); } pub fn fully_qualified_note_path(context: &HirContext, note_id: StructId) -> Option { diff --git a/noir/noir-repo/aztec_macros/src/utils/mod.rs b/noir/noir-repo/aztec_macros/src/utils/mod.rs index c8914f830250..6809fe9f1541 100644 --- a/noir/noir-repo/aztec_macros/src/utils/mod.rs +++ b/noir/noir-repo/aztec_macros/src/utils/mod.rs @@ -3,3 +3,4 @@ pub mod checks; pub mod constants; pub mod errors; pub mod hir_utils; +pub mod parse_utils; diff --git a/noir/noir-repo/aztec_macros/src/utils/parse_utils.rs b/noir/noir-repo/aztec_macros/src/utils/parse_utils.rs new file mode 100644 index 000000000000..3b3813da6ee3 --- /dev/null +++ b/noir/noir-repo/aztec_macros/src/utils/parse_utils.rs @@ -0,0 +1,544 @@ +use noirc_frontend::{ + ast::{ + ArrayLiteral, AssignStatement, BlockExpression, CallExpression, CastExpression, + ConstrainStatement, ConstructorExpression, Expression, ExpressionKind, ForLoopStatement, + ForRange, FunctionReturnType, Ident, IfExpression, IndexExpression, InfixExpression, + LValue, Lambda, LetStatement, Literal, MemberAccessExpression, MethodCallExpression, + ModuleDeclaration, NoirFunction, NoirStruct, NoirTrait, NoirTraitImpl, NoirTypeAlias, Path, + PathSegment, Pattern, PrefixExpression, Statement, StatementKind, TraitImplItem, TraitItem, + TypeImpl, UnresolvedGeneric, UnresolvedGenerics, UnresolvedTraitConstraint, UnresolvedType, + UnresolvedTypeData, UnresolvedTypeExpression, UseTree, UseTreeKind, + }, + parser::{Item, ItemKind, ParsedSubModule, ParserError}, + ParsedModule, +}; + +/// Parses a program and will clear out (set them to a default) any spans in it if `empty_spans` is true. +/// We want to do this in code generated by macros when running in LSP mode so that the generated +/// code doesn't end up overlapping real code, messing with how inlay hints, hover, etc., work. +pub fn parse_program(source_program: &str, empty_spans: bool) -> (ParsedModule, Vec) { + let (mut parsed_program, errors) = noirc_frontend::parse_program(source_program); + if empty_spans { + empty_parsed_module(&mut parsed_program); + } + (parsed_program, errors) +} + +fn empty_parsed_module(parsed_module: &mut ParsedModule) { + for item in parsed_module.items.iter_mut() { + empty_item(item); + } +} + +fn empty_item(item: &mut Item) { + item.span = Default::default(); + + match &mut item.kind { + ItemKind::Function(noir_function) => empty_noir_function(noir_function), + ItemKind::Trait(noir_trait) => { + empty_noir_trait(noir_trait); + } + ItemKind::TraitImpl(noir_trait_impl) => { + empty_noir_trait_impl(noir_trait_impl); + } + ItemKind::Impl(type_impl) => { + empty_type_impl(type_impl); + } + ItemKind::Global(let_statement) => empty_let_statement(let_statement), + ItemKind::Submodules(parsed_submodule) => { + empty_parsed_submodule(parsed_submodule); + } + ItemKind::ModuleDecl(module_declaration) => empty_module_declaration(module_declaration), + ItemKind::Import(use_tree) => empty_use_tree(use_tree), + ItemKind::Struct(noir_struct) => empty_noir_struct(noir_struct), + ItemKind::TypeAlias(noir_type_alias) => empty_noir_type_alias(noir_type_alias), + } +} + +fn empty_noir_trait(noir_trait: &mut NoirTrait) { + noir_trait.span = Default::default(); + + empty_ident(&mut noir_trait.name); + empty_unresolved_generics(&mut noir_trait.generics); + empty_unresolved_trait_constraints(&mut noir_trait.where_clause); + for item in noir_trait.items.iter_mut() { + empty_trait_item(item); + } +} + +fn empty_noir_trait_impl(noir_trait_impl: &mut NoirTraitImpl) { + empty_path(&mut noir_trait_impl.trait_name); + empty_unresolved_generics(&mut noir_trait_impl.impl_generics); + empty_unresolved_type(&mut noir_trait_impl.object_type); + empty_unresolved_trait_constraints(&mut noir_trait_impl.where_clause); + for item in noir_trait_impl.items.iter_mut() { + empty_trait_impl_item(item); + } +} + +fn empty_type_impl(type_impl: &mut TypeImpl) { + empty_unresolved_type(&mut type_impl.object_type); + type_impl.type_span = Default::default(); + empty_unresolved_generics(&mut type_impl.generics); + empty_unresolved_trait_constraints(&mut type_impl.where_clause); + for (noir_function, _) in type_impl.methods.iter_mut() { + empty_noir_function(noir_function); + } +} + +fn empty_noir_function(noir_function: &mut NoirFunction) { + let def = &mut noir_function.def; + + def.span = Default::default(); + empty_ident(&mut def.name); + empty_unresolved_generics(&mut def.generics); + + for param in def.parameters.iter_mut() { + param.span = Default::default(); + empty_unresolved_type(&mut param.typ); + empty_pattern(&mut param.pattern); + } + + empty_unresolved_trait_constraints(&mut def.where_clause); + empty_function_return_type(&mut def.return_type); + empty_block_expression(&mut def.body); +} + +fn empty_trait_item(trait_item: &mut TraitItem) { + match trait_item { + TraitItem::Function { name, generics, parameters, return_type, where_clause, body } => { + empty_ident(name); + empty_unresolved_generics(generics); + for (name, typ) in parameters.iter_mut() { + empty_ident(name); + empty_unresolved_type(typ); + } + empty_function_return_type(return_type); + for trait_constraint in where_clause.iter_mut() { + empty_unresolved_trait_constraint(trait_constraint); + } + if let Some(body) = body { + empty_block_expression(body); + } + } + TraitItem::Constant { name, typ, default_value } => { + empty_ident(name); + empty_unresolved_type(typ); + if let Some(default_value) = default_value { + empty_expression(default_value); + } + } + TraitItem::Type { name } => { + empty_ident(name); + } + } +} + +fn empty_trait_impl_item(trait_impl_item: &mut TraitImplItem) { + match trait_impl_item { + TraitImplItem::Function(noir_function) => empty_noir_function(noir_function), + TraitImplItem::Constant(name, typ, default_value) => { + empty_ident(name); + empty_unresolved_type(typ); + empty_expression(default_value); + } + TraitImplItem::Type { name, alias } => { + empty_ident(name); + empty_unresolved_type(alias); + } + } +} + +fn empty_let_statement(let_statement: &mut LetStatement) { + empty_pattern(&mut let_statement.pattern); + empty_unresolved_type(&mut let_statement.r#type); + empty_expression(&mut let_statement.expression); +} + +fn empty_parsed_submodule(parsed_submodule: &mut ParsedSubModule) { + empty_ident(&mut parsed_submodule.name); + empty_parsed_module(&mut parsed_submodule.contents); +} + +fn empty_module_declaration(module_declaration: &mut ModuleDeclaration) { + empty_ident(&mut module_declaration.ident); +} + +fn empty_use_tree(use_tree: &mut UseTree) { + empty_path(&mut use_tree.prefix); + + match &mut use_tree.kind { + UseTreeKind::Path(name, alias) => { + empty_ident(name); + if let Some(alias) = alias { + empty_ident(alias); + } + } + UseTreeKind::List(use_trees) => { + for use_tree in use_trees.iter_mut() { + empty_use_tree(use_tree); + } + } + } +} + +fn empty_noir_struct(noir_struct: &mut NoirStruct) { + noir_struct.span = Default::default(); + empty_ident(&mut noir_struct.name); + for (name, typ) in noir_struct.fields.iter_mut() { + empty_ident(name); + empty_unresolved_type(typ); + } + empty_unresolved_generics(&mut noir_struct.generics); +} + +fn empty_noir_type_alias(noir_type_alias: &mut NoirTypeAlias) { + noir_type_alias.span = Default::default(); + empty_ident(&mut noir_type_alias.name); + empty_unresolved_type(&mut noir_type_alias.typ); +} + +fn empty_block_expression(block_expression: &mut BlockExpression) { + for statement in block_expression.statements.iter_mut() { + empty_statement(statement); + } +} + +fn empty_statement(statement: &mut Statement) { + statement.span = Default::default(); + + match &mut statement.kind { + StatementKind::Let(let_statement) => empty_let_statement(let_statement), + StatementKind::Constrain(constrain_statement) => { + empty_constrain_statement(constrain_statement) + } + StatementKind::Expression(expression) => empty_expression(expression), + StatementKind::Assign(assign_statement) => empty_assign_statement(assign_statement), + StatementKind::For(for_loop_statement) => empty_for_loop_statement(for_loop_statement), + StatementKind::Comptime(statement) => empty_statement(statement), + StatementKind::Semi(expression) => empty_expression(expression), + StatementKind::Break | StatementKind::Continue | StatementKind::Error => (), + } +} + +fn empty_constrain_statement(constrain_statement: &mut ConstrainStatement) { + empty_expression(&mut constrain_statement.0); + if let Some(expression) = &mut constrain_statement.1 { + empty_expression(expression); + } +} + +fn empty_expressions(expressions: &mut [Expression]) { + for expression in expressions.iter_mut() { + empty_expression(expression); + } +} + +fn empty_expression(expression: &mut Expression) { + expression.span = Default::default(); + + match &mut expression.kind { + ExpressionKind::Literal(literal) => empty_literal(literal), + ExpressionKind::Block(block_expression) => empty_block_expression(block_expression), + ExpressionKind::Prefix(prefix_expression) => empty_prefix_expression(prefix_expression), + ExpressionKind::Index(index_expression) => empty_index_expression(index_expression), + ExpressionKind::Call(call_expression) => empty_call_expression(call_expression), + ExpressionKind::MethodCall(method_call_expression) => { + empty_method_call_expression(method_call_expression) + } + ExpressionKind::Constructor(constructor_expression) => { + empty_constructor_expression(constructor_expression) + } + ExpressionKind::MemberAccess(member_access_expression) => { + empty_member_access_expression(member_access_expression) + } + ExpressionKind::Cast(cast_expression) => empty_cast_expression(cast_expression), + ExpressionKind::Infix(infix_expression) => empty_infix_expression(infix_expression), + ExpressionKind::If(if_expression) => empty_if_expression(if_expression), + ExpressionKind::Variable(path) => empty_path(path), + ExpressionKind::Tuple(expressions) => { + empty_expressions(expressions); + } + ExpressionKind::Lambda(lambda) => empty_lambda(lambda), + ExpressionKind::Parenthesized(expression) => empty_expression(expression), + ExpressionKind::Unquote(expression) => { + empty_expression(expression); + } + ExpressionKind::Comptime(block_expression, _span) => { + empty_block_expression(block_expression); + } + ExpressionKind::Quote(..) | ExpressionKind::Resolved(_) | ExpressionKind::Error => (), + ExpressionKind::AsTraitPath(path) => { + empty_unresolved_type(&mut path.typ); + empty_path(&mut path.trait_path); + empty_ident(&mut path.impl_item); + } + } +} + +fn empty_assign_statement(assign_statement: &mut AssignStatement) { + empty_lvalue(&mut assign_statement.lvalue); + empty_expression(&mut assign_statement.expression); +} + +fn empty_for_loop_statement(for_loop_statement: &mut ForLoopStatement) { + for_loop_statement.span = Default::default(); + empty_ident(&mut for_loop_statement.identifier); + empty_for_range(&mut for_loop_statement.range); + empty_expression(&mut for_loop_statement.block); +} + +fn empty_unresolved_types(unresolved_types: &mut [UnresolvedType]) { + for unresolved_type in unresolved_types.iter_mut() { + empty_unresolved_type(unresolved_type); + } +} + +fn empty_unresolved_type(unresolved_type: &mut UnresolvedType) { + unresolved_type.span = Default::default(); + + match &mut unresolved_type.typ { + UnresolvedTypeData::Array(unresolved_type_expression, unresolved_type) => { + empty_unresolved_type_expression(unresolved_type_expression); + empty_unresolved_type(unresolved_type); + } + UnresolvedTypeData::Slice(unresolved_type) => empty_unresolved_type(unresolved_type), + UnresolvedTypeData::Expression(unresolved_type_expression) => { + empty_unresolved_type_expression(unresolved_type_expression) + } + UnresolvedTypeData::FormatString(unresolved_type_expression, unresolved_type) => { + empty_unresolved_type_expression(unresolved_type_expression); + empty_unresolved_type(unresolved_type); + } + UnresolvedTypeData::Parenthesized(unresolved_type) => { + empty_unresolved_type(unresolved_type) + } + UnresolvedTypeData::Named(path, unresolved_types, _) => { + empty_path(path); + empty_unresolved_types(unresolved_types); + } + UnresolvedTypeData::TraitAsType(path, unresolved_types) => { + empty_path(path); + empty_unresolved_types(unresolved_types); + } + UnresolvedTypeData::MutableReference(unresolved_type) => { + empty_unresolved_type(unresolved_type) + } + UnresolvedTypeData::Tuple(unresolved_types) => empty_unresolved_types(unresolved_types), + UnresolvedTypeData::Function(args, ret, _env) => { + empty_unresolved_types(args); + empty_unresolved_type(ret); + } + UnresolvedTypeData::AsTraitPath(path) => { + empty_unresolved_type(&mut path.typ); + empty_path(&mut path.trait_path); + empty_ident(&mut path.impl_item); + } + UnresolvedTypeData::FieldElement + | UnresolvedTypeData::Integer(_, _) + | UnresolvedTypeData::Bool + | UnresolvedTypeData::String(_) + | UnresolvedTypeData::Unit + | UnresolvedTypeData::Quoted(_) + | UnresolvedTypeData::Resolved(_) + | UnresolvedTypeData::Unspecified + | UnresolvedTypeData::Error => (), + } +} + +fn empty_unresolved_generics(unresolved_generic: &mut UnresolvedGenerics) { + for generic in unresolved_generic.iter_mut() { + empty_unresolved_generic(generic); + } +} + +fn empty_unresolved_generic(unresolved_generic: &mut UnresolvedGeneric) { + match unresolved_generic { + UnresolvedGeneric::Variable(ident) => empty_ident(ident), + UnresolvedGeneric::Numeric { ident, typ } => { + empty_ident(ident); + empty_unresolved_type(typ); + } + UnresolvedGeneric::Resolved(..) => (), + } +} + +fn empty_pattern(pattern: &mut Pattern) { + match pattern { + Pattern::Identifier(ident) => empty_ident(ident), + Pattern::Mutable(pattern, _span, _) => { + empty_pattern(pattern); + } + Pattern::Tuple(patterns, _) => { + for pattern in patterns.iter_mut() { + empty_pattern(pattern); + } + } + Pattern::Struct(path, patterns, _) => { + empty_path(path); + for (name, pattern) in patterns.iter_mut() { + empty_ident(name); + empty_pattern(pattern); + } + } + } +} + +fn empty_unresolved_trait_constraints( + unresolved_trait_constriants: &mut [UnresolvedTraitConstraint], +) { + for trait_constraint in unresolved_trait_constriants.iter_mut() { + empty_unresolved_trait_constraint(trait_constraint); + } +} + +fn empty_unresolved_trait_constraint(unresolved_trait_constraint: &mut UnresolvedTraitConstraint) { + empty_unresolved_type(&mut unresolved_trait_constraint.typ); +} + +fn empty_function_return_type(function_return_type: &mut FunctionReturnType) { + match function_return_type { + FunctionReturnType::Ty(unresolved_type) => empty_unresolved_type(unresolved_type), + FunctionReturnType::Default(_) => (), + } +} + +fn empty_ident(ident: &mut Ident) { + ident.0.set_span(Default::default()); +} + +fn empty_path(path: &mut Path) { + path.span = Default::default(); + for segment in path.segments.iter_mut() { + empty_path_segment(segment); + } +} + +fn empty_path_segment(segment: &mut PathSegment) { + segment.span = Default::default(); + empty_ident(&mut segment.ident); +} + +fn empty_literal(literal: &mut Literal) { + match literal { + Literal::Array(array_literal) => empty_array_literal(array_literal), + Literal::Slice(array_literal) => empty_array_literal(array_literal), + Literal::Bool(_) + | Literal::Integer(_, _) + | Literal::Str(_) + | Literal::RawStr(_, _) + | Literal::FmtStr(_) + | Literal::Unit => (), + } +} + +fn empty_array_literal(array_literal: &mut ArrayLiteral) { + match array_literal { + ArrayLiteral::Standard(expressions) => { + empty_expressions(expressions); + } + ArrayLiteral::Repeated { repeated_element, length } => { + empty_expression(repeated_element); + empty_expression(length); + } + } +} + +fn empty_prefix_expression(prefix_expression: &mut PrefixExpression) { + empty_expression(&mut prefix_expression.rhs); +} + +fn empty_index_expression(index_expression: &mut IndexExpression) { + empty_expression(&mut index_expression.collection); + empty_expression(&mut index_expression.index); +} + +fn empty_call_expression(call_expression: &mut CallExpression) { + empty_expression(&mut call_expression.func); + empty_expressions(&mut call_expression.arguments); +} + +fn empty_method_call_expression(method_call_expression: &mut MethodCallExpression) { + empty_expression(&mut method_call_expression.object); + empty_ident(&mut method_call_expression.method_name); + if let Some(generics) = &mut method_call_expression.generics { + empty_unresolved_types(generics); + } + empty_expressions(&mut method_call_expression.arguments); +} + +fn empty_constructor_expression(constructor_expression: &mut ConstructorExpression) { + empty_path(&mut constructor_expression.type_name); + for (name, expression) in constructor_expression.fields.iter_mut() { + empty_ident(name); + empty_expression(expression); + } +} + +fn empty_member_access_expression(member_access_expression: &mut MemberAccessExpression) { + empty_expression(&mut member_access_expression.lhs); + empty_ident(&mut member_access_expression.rhs); +} + +fn empty_cast_expression(cast_expression: &mut CastExpression) { + empty_expression(&mut cast_expression.lhs); + empty_unresolved_type(&mut cast_expression.r#type); +} + +fn empty_infix_expression(infix_expression: &mut InfixExpression) { + empty_expression(&mut infix_expression.lhs); + empty_expression(&mut infix_expression.rhs); +} + +fn empty_if_expression(if_expression: &mut IfExpression) { + empty_expression(&mut if_expression.condition); + empty_expression(&mut if_expression.consequence); + if let Some(alternative) = &mut if_expression.alternative { + empty_expression(alternative); + } +} + +fn empty_lambda(lambda: &mut Lambda) { + for (name, typ) in lambda.parameters.iter_mut() { + empty_pattern(name); + empty_unresolved_type(typ); + } + empty_unresolved_type(&mut lambda.return_type); + empty_expression(&mut lambda.body); +} + +fn empty_lvalue(lvalue: &mut LValue) { + match lvalue { + LValue::Ident(ident) => empty_ident(ident), + LValue::MemberAccess { ref mut object, ref mut field_name, span: _ } => { + empty_lvalue(object); + empty_ident(field_name); + } + LValue::Index { ref mut array, ref mut index, span: _ } => { + empty_lvalue(array); + empty_expression(index); + } + LValue::Dereference(lvalue, _) => empty_lvalue(lvalue), + } +} + +fn empty_for_range(for_range: &mut ForRange) { + match for_range { + ForRange::Range(from, to) => { + empty_expression(from); + empty_expression(to); + } + ForRange::Array(expression) => empty_expression(expression), + } +} + +fn empty_unresolved_type_expression(unresolved_type_expression: &mut UnresolvedTypeExpression) { + match unresolved_type_expression { + UnresolvedTypeExpression::Variable(path) => empty_path(path), + UnresolvedTypeExpression::BinaryOperation(lhs, _, rhs, _) => { + empty_unresolved_type_expression(lhs); + empty_unresolved_type_expression(rhs); + } + UnresolvedTypeExpression::Constant(_, _) => (), + } +} diff --git a/noir/noir-repo/compiler/fm/src/file_map.rs b/noir/noir-repo/compiler/fm/src/file_map.rs index 50412d352ec4..ba552fe51561 100644 --- a/noir/noir-repo/compiler/fm/src/file_map.rs +++ b/noir/noir-repo/compiler/fm/src/file_map.rs @@ -34,6 +34,7 @@ impl From<&PathBuf> for PathString { pub struct FileMap { files: SimpleFiles, name_to_id: HashMap, + current_dir: Option, } // XXX: Note that we derive Default here due to ModuleOrigin requiring us to set a FileId @@ -82,7 +83,11 @@ impl FileMap { } impl Default for FileMap { fn default() -> Self { - FileMap { files: SimpleFiles::new(), name_to_id: HashMap::new() } + FileMap { + files: SimpleFiles::new(), + name_to_id: HashMap::new(), + current_dir: std::env::current_dir().ok(), + } } } @@ -92,7 +97,16 @@ impl<'a> Files<'a> for FileMap { type Source = &'a str; fn name(&self, file_id: Self::FileId) -> Result { - Ok(self.files.get(file_id.as_usize())?.name().clone()) + let name = self.files.get(file_id.as_usize())?.name().clone(); + + // See if we can make the file name a bit shorter/easier to read if it starts with the current directory + if let Some(current_dir) = &self.current_dir { + if let Ok(name_without_prefix) = name.0.strip_prefix(current_dir) { + return Ok(PathString::from_path(name_without_prefix.to_path_buf())); + } + } + + Ok(name) } fn source(&'a self, file_id: Self::FileId) -> Result { diff --git a/noir/noir-repo/compiler/integration-tests/circuits/recursion/src/main.nr b/noir/noir-repo/compiler/integration-tests/circuits/recursion/src/main.nr index 94cae14daa72..41f94baff4e8 100644 --- a/noir/noir-repo/compiler/integration-tests/circuits/recursion/src/main.nr +++ b/noir/noir-repo/compiler/integration-tests/circuits/recursion/src/main.nr @@ -4,10 +4,5 @@ fn main( public_inputs: [Field; 1], key_hash: Field ) { - std::verify_proof( - verification_key.as_slice(), - proof.as_slice(), - public_inputs.as_slice(), - key_hash - ) + std::verify_proof(verification_key, proof, public_inputs, key_hash) } diff --git a/noir/noir-repo/compiler/integration-tests/test/node/prove_and_verify.test.ts b/noir/noir-repo/compiler/integration-tests/test/node/prove_and_verify.test.ts index 699dcf5e9184..babc8ca5bb82 100644 --- a/noir/noir-repo/compiler/integration-tests/test/node/prove_and_verify.test.ts +++ b/noir/noir-repo/compiler/integration-tests/test/node/prove_and_verify.test.ts @@ -2,7 +2,12 @@ import { expect } from 'chai'; import assert_lt_json from '../../circuits/assert_lt/target/assert_lt.json' assert { type: 'json' }; import fold_fibonacci_json from '../../circuits/fold_fibonacci/target/fold_fibonacci.json' assert { type: 'json' }; import { Noir } from '@noir-lang/noir_js'; -import { BarretenbergBackend as Backend, BarretenbergVerifier as Verifier } from '@noir-lang/backend_barretenberg'; +import { + BarretenbergBackend as Backend, + BarretenbergVerifier as Verifier, + UltraHonkBackend, + UltraHonkVerifier, +} from '@noir-lang/backend_barretenberg'; import { CompiledCircuit } from '@noir-lang/types'; const assert_lt_program = assert_lt_json as CompiledCircuit; @@ -150,3 +155,138 @@ it('end-to-end proof creation and verification for multiple ACIR circuits (inner const isValid = await backend.verifyProof(proof); expect(isValid).to.be.true; }); + +const honkBackend = new UltraHonkBackend(assert_lt_program); + +it('UltraHonk end-to-end proof creation and verification (outer)', async () => { + // Noir.Js part + const inputs = { + x: '2', + y: '3', + }; + + const program = new Noir(assert_lt_program); + + const { witness } = await program.execute(inputs); + + // bb.js part + // + // Proof creation + const proof = await honkBackend.generateProof(witness); + + // Proof verification + const isValid = await honkBackend.verifyProof(proof); + expect(isValid).to.be.true; +}); + +it('UltraHonk end-to-end proof creation and verification (outer) -- Verifier API', async () => { + // Noir.Js part + const inputs = { + x: '2', + y: '3', + }; + + // Execute program + const program = new Noir(assert_lt_program); + const { witness } = await program.execute(inputs); + + // Generate proof + const proof = await honkBackend.generateProof(witness); + + const verificationKey = await honkBackend.getVerificationKey(); + + // Proof verification + const verifier = new UltraHonkVerifier(); + const isValid = await verifier.verifyProof(proof, verificationKey); + expect(isValid).to.be.true; +}); + +it('UltraHonk end-to-end proof creation and verification (inner)', async () => { + // Noir.Js part + const inputs = { + x: '2', + y: '3', + }; + + const program = new Noir(assert_lt_program); + + const { witness } = await program.execute(inputs); + + // bb.js part + // + // Proof creation + const proof = await honkBackend.generateProof(witness); + + // Proof verification + const isValid = await honkBackend.verifyProof(proof); + expect(isValid).to.be.true; +}); + +it('UltraHonk end-to-end proving and verification with different instances', async () => { + // Noir.Js part + const inputs = { + x: '2', + y: '3', + }; + + const program = new Noir(assert_lt_program); + + const { witness } = await program.execute(inputs); + + // bb.js part + const proof = await honkBackend.generateProof(witness); + + const verifier = new UltraHonkBackend(assert_lt_program); + const proof_is_valid = await verifier.verifyProof(proof); + expect(proof_is_valid).to.be.true; +}); + +it('[BUG] -- UltraHonk bb.js null function or function signature mismatch (outer-inner) ', async () => { + // Noir.Js part + const inputs = { + x: '2', + y: '3', + }; + + const program = new Noir(assert_lt_program); + + const { witness } = await program.execute(inputs); + + // bb.js part + // + // Proof creation + // + // Create a proof using both proving systems, the majority of the time + // one would only use outer proofs. + const proofOuter = await honkBackend.generateProof(witness); + const _proofInner = await honkBackend.generateProof(witness); + + // Proof verification + // + const isValidOuter = await honkBackend.verifyProof(proofOuter); + expect(isValidOuter).to.be.true; + // We can also try verifying an inner proof and it will fail. + const isValidInner = await honkBackend.verifyProof(_proofInner); + expect(isValidInner).to.be.true; +}); + +it('UltraHonk end-to-end proof creation and verification for multiple ACIR circuits (inner)', async () => { + // Noir.Js part + const inputs = { + x: '10', + }; + + const program = new Noir(fold_fibonacci_program); + + const { witness } = await program.execute(inputs); + + // bb.js part + // + // Proof creation + const honkBackend = new UltraHonkBackend(fold_fibonacci_program); + const proof = await honkBackend.generateProof(witness); + + // Proof verification + const isValid = await honkBackend.verifyProof(proof); + expect(isValid).to.be.true; +}); 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 d0b33945f40e..87181b285de8 100644 --- a/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs +++ b/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs @@ -100,6 +100,7 @@ pub(super) fn abi_type_from_hir_type(context: &Context, typ: &Type) -> AbiType { Type::Error | Type::Unit | Type::Constant(_) + | Type::InfixExpr(..) | Type::TraitAsType(..) | Type::TypeVariable(_, _) | Type::NamedGeneric(..) @@ -116,7 +117,7 @@ fn to_abi_visibility(value: Visibility) -> AbiVisibility { match value { Visibility::Public => AbiVisibility::Public, Visibility::Private => AbiVisibility::Private, - Visibility::DataBus => AbiVisibility::DataBus, + Visibility::CallData(_) | Visibility::ReturnData => AbiVisibility::DataBus, } } diff --git a/noir/noir-repo/compiler/noirc_driver/src/debug.rs b/noir/noir-repo/compiler/noirc_driver/src/debug.rs index 5e309398cc5f..f5eaede89b2a 100644 --- a/noir/noir-repo/compiler/noirc_driver/src/debug.rs +++ b/noir/noir-repo/compiler/noirc_driver/src/debug.rs @@ -18,7 +18,7 @@ pub(crate) fn filter_relevant_files( debug_symbols: &[DebugInfo], file_manager: &FileManager, ) -> BTreeMap { - let files_with_debug_symbols: BTreeSet = debug_symbols + let mut files_with_debug_symbols: BTreeSet = debug_symbols .iter() .flat_map(|function_symbols| { function_symbols @@ -28,6 +28,21 @@ pub(crate) fn filter_relevant_files( }) .collect(); + let files_with_brillig_debug_symbols: BTreeSet = debug_symbols + .iter() + .flat_map(|function_symbols| { + let brillig_location_maps = + function_symbols.brillig_locations.values().flat_map(|brillig_location_map| { + brillig_location_map + .values() + .flat_map(|call_stack| call_stack.iter().map(|location| location.file)) + }); + brillig_location_maps + }) + .collect(); + + files_with_debug_symbols.extend(files_with_brillig_debug_symbols); + let mut file_map = BTreeMap::new(); for file_id in files_with_debug_symbols { diff --git a/noir/noir-repo/compiler/noirc_driver/src/lib.rs b/noir/noir-repo/compiler/noirc_driver/src/lib.rs index dd774a1eeec7..72c95823553f 100644 --- a/noir/noir-repo/compiler/noirc_driver/src/lib.rs +++ b/noir/noir-repo/compiler/noirc_driver/src/lib.rs @@ -56,14 +56,26 @@ pub struct CompileOptions { #[arg(long, value_parser = parse_expression_width)] pub expression_width: Option, + /// Generate ACIR with the target backend expression width. + /// The default is to generate ACIR without a bound and split expressions after code generation. + /// Activating this flag can sometimes provide optimizations for certain programs. + #[arg(long, default_value = "false")] + pub bounded_codegen: bool, + /// Force a full recompilation. #[arg(long = "force")] pub force_compile: bool, - /// Emit debug information for the intermediate SSA IR + /// Emit debug information for the intermediate SSA IR to stdout #[arg(long, hide = true)] pub show_ssa: bool, + /// Emit the unoptimized SSA IR to file. + /// The IR will be dumped into the workspace target directory, + /// under `[compiled-package].ssa.json`. + #[arg(long, hide = true)] + pub emit_ssa: bool, + #[arg(long, hide = true)] pub show_brillig: bool, @@ -107,6 +119,10 @@ pub struct CompileOptions { /// Outputs the paths to any modified artifacts #[arg(long, hide = true)] pub show_artifact_paths: bool, + + /// Temporary flag to enable the experimental arithmetic generics feature + #[arg(long, hide = true)] + pub arithmetic_generics: bool, } pub fn parse_expression_width(input: &str) -> Result { @@ -256,21 +272,28 @@ pub fn add_dep( pub fn check_crate( context: &mut Context, crate_id: CrateId, - deny_warnings: bool, - disable_macros: bool, - debug_comptime_in_file: Option<&str>, + options: &CompileOptions, ) -> CompilationResult<()> { - let macros: &[&dyn MacroProcessor] = - if disable_macros { &[] } else { &[&aztec_macros::AztecMacro as &dyn MacroProcessor] }; + let macros: &[&dyn MacroProcessor] = if options.disable_macros { + &[] + } else { + &[&aztec_macros::AztecMacro as &dyn MacroProcessor] + }; let mut errors = vec![]; - let diagnostics = CrateDefMap::collect_defs(crate_id, context, debug_comptime_in_file, macros); + let diagnostics = CrateDefMap::collect_defs( + crate_id, + context, + options.debug_comptime_in_file.as_deref(), + options.arithmetic_generics, + macros, + ); errors.extend(diagnostics.into_iter().map(|(error, file_id)| { let diagnostic = CustomDiagnostic::from(&error); diagnostic.in_file(file_id) })); - if has_errors(&errors, deny_warnings) { + if has_errors(&errors, options.deny_warnings) { Err(errors) } else { Ok(((), errors)) @@ -296,13 +319,7 @@ pub fn compile_main( options: &CompileOptions, cached_program: Option, ) -> CompilationResult { - let (_, mut warnings) = check_crate( - context, - crate_id, - options.deny_warnings, - options.disable_macros, - options.debug_comptime_in_file.as_deref(), - )?; + let (_, mut warnings) = check_crate(context, crate_id, options)?; let main = context.get_main_function(&crate_id).ok_or_else(|| { // TODO(#2155): This error might be a better to exist in Nargo @@ -337,13 +354,7 @@ pub fn compile_contract( crate_id: CrateId, options: &CompileOptions, ) -> CompilationResult { - let (_, warnings) = check_crate( - context, - crate_id, - options.deny_warnings, - options.disable_macros, - options.debug_comptime_in_file.as_deref(), - )?; + let (_, warnings) = check_crate(context, crate_id, options)?; // TODO: We probably want to error if contracts is empty let contracts = context.get_all_contracts(&crate_id); @@ -512,6 +523,12 @@ fn compile_contract_inner( } } +/// Default expression width used for Noir compilation. +/// The ACVM native type `ExpressionWidth` has its own default which should always be unbounded, +/// while we can sometimes expect the compilation target width to change. +/// Thus, we set it separately here rather than trying to alter the default derivation of the type. +pub const DEFAULT_EXPRESSION_WIDTH: ExpressionWidth = ExpressionWidth::Bounded { width: 4 }; + /// Compile the current crate using `main_function` as the entrypoint. /// /// This function assumes [`check_crate`] is called beforehand. @@ -537,8 +554,11 @@ pub fn compile_no_check( // If user has specified that they want to see intermediate steps printed then we should // force compilation even if the program hasn't changed. - let force_compile = - force_compile || options.print_acir || options.show_brillig || options.show_ssa; + let force_compile = force_compile + || options.print_acir + || options.show_brillig + || options.show_ssa + || options.emit_ssa; if !force_compile && hashes_match { info!("Program matches existing artifact, returning early"); @@ -550,6 +570,12 @@ pub fn compile_no_check( enable_brillig_logging: options.show_brillig, force_brillig_output: options.force_brillig, print_codegen_timings: options.benchmark_codegen, + expression_width: if options.bounded_codegen { + options.expression_width.unwrap_or(DEFAULT_EXPRESSION_WIDTH) + } else { + ExpressionWidth::default() + }, + emit_ssa: if options.emit_ssa { Some(context.package_build_path.clone()) } else { None }, }; let SsaProgramArtifact { program, debug, warnings, names, error_types, .. } = diff --git a/noir/noir-repo/compiler/noirc_driver/tests/stdlib_warnings.rs b/noir/noir-repo/compiler/noirc_driver/tests/stdlib_warnings.rs index d2474444d130..e290842480d0 100644 --- a/noir/noir-repo/compiler/noirc_driver/tests/stdlib_warnings.rs +++ b/noir/noir-repo/compiler/noirc_driver/tests/stdlib_warnings.rs @@ -25,7 +25,7 @@ fn stdlib_does_not_produce_constant_warnings() -> Result<(), ErrorsAndWarnings> let root_crate_id = prepare_crate(&mut context, file_name); let ((), warnings) = - noirc_driver::check_crate(&mut context, root_crate_id, false, false, None)?; + noirc_driver::check_crate(&mut context, root_crate_id, &Default::default())?; assert_eq!(warnings, Vec::new(), "stdlib is producing {} warnings", warnings.len()); diff --git a/noir/noir-repo/compiler/noirc_errors/src/debug_info.rs b/noir/noir-repo/compiler/noirc_errors/src/debug_info.rs index 54e2521e413a..1a254175c0a5 100644 --- a/noir/noir-repo/compiler/noirc_errors/src/debug_info.rs +++ b/noir/noir-repo/compiler/noirc_errors/src/debug_info.rs @@ -1,3 +1,4 @@ +use acvm::acir::circuit::brillig::BrilligFunctionId; use acvm::acir::circuit::OpcodeLocation; use acvm::compiler::AcirTransformationMap; @@ -97,6 +98,8 @@ pub struct DebugInfo { /// that they should be serialized to/from strings. #[serde_as(as = "BTreeMap")] pub locations: BTreeMap>, + #[serde_as(as = "BTreeMap<_, BTreeMap>")] + pub brillig_locations: BTreeMap>>, pub variables: DebugVariables, pub functions: DebugFunctions, pub types: DebugTypes, @@ -113,11 +116,12 @@ pub struct OpCodesCount { impl DebugInfo { pub fn new( locations: BTreeMap>, + brillig_locations: BTreeMap>>, variables: DebugVariables, functions: DebugFunctions, types: DebugTypes, ) -> Self { - Self { locations, variables, functions, types } + Self { locations, brillig_locations, variables, functions, types } } /// Updates the locations map when the [`Circuit`][acvm::acir::circuit::Circuit] is modified. diff --git a/noir/noir-repo/compiler/noirc_errors/src/position.rs b/noir/noir-repo/compiler/noirc_errors/src/position.rs index 9f9879e1d1b0..02b242e8b4da 100644 --- a/noir/noir-repo/compiler/noirc_errors/src/position.rs +++ b/noir/noir-repo/compiler/noirc_errors/src/position.rs @@ -43,6 +43,10 @@ impl Spanned { pub fn span(&self) -> Span { self.span } + + pub fn set_span(&mut self, span: Span) { + self.span = span; + } } impl std::borrow::Borrow for Spanned { diff --git a/noir/noir-repo/compiler/noirc_evaluator/Cargo.toml b/noir/noir-repo/compiler/noirc_evaluator/Cargo.toml index 52f154fd1f34..66c770b50648 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/Cargo.toml +++ b/noir/noir-repo/compiler/noirc_evaluator/Cargo.toml @@ -19,6 +19,8 @@ thiserror.workspace = true num-bigint = "0.4" im.workspace = true serde.workspace = true +serde_json.workspace = true +serde_with = "3.2.0" tracing.workspace = true chrono = "0.4.37" diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs index c2bc1e32cd69..4487187de3b9 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs @@ -116,7 +116,12 @@ impl<'block> BrilligBlock<'block> { dfg: &DataFlowGraph, ) { match terminator_instruction { - TerminatorInstruction::JmpIf { condition, then_destination, else_destination } => { + TerminatorInstruction::JmpIf { + condition, + then_destination, + else_destination, + call_stack: _, + } => { let condition = self.convert_ssa_single_addr_value(*condition, dfg); self.brillig_context.jump_if_instruction( condition.address, diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs index 81327cec0137..b632958f37f1 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs @@ -7,7 +7,12 @@ //! This module heavily borrows from Cranelift #![allow(dead_code)] -use std::collections::{BTreeMap, BTreeSet}; +use std::{ + collections::{BTreeMap, BTreeSet}, + fs::File, + io::Write, + path::{Path, PathBuf}, +}; use crate::errors::{RuntimeError, SsaReport}; use acvm::{ @@ -42,6 +47,25 @@ pub mod ir; mod opt; pub mod ssa_gen; +pub struct SsaEvaluatorOptions { + /// Emit debug information for the intermediate SSA IR + pub enable_ssa_logging: bool, + + pub enable_brillig_logging: bool, + + /// Force Brillig output (for step debugging) + pub force_brillig_output: bool, + + /// Pretty print benchmark times of each code generation pass + pub print_codegen_timings: bool, + + /// Width of expressions to be used for ACIR + pub expression_width: ExpressionWidth, + + /// Dump the unoptimized SSA to the supplied path if it exists + pub emit_ssa: Option, +} + pub(crate) struct ArtifactsAndWarnings(Artifacts, Vec); /// Optimize the given program by converting it into SSA @@ -60,6 +84,7 @@ pub(crate) fn optimize_into_acir( options.enable_ssa_logging, options.force_brillig_output, options.print_codegen_timings, + &options.emit_ssa, )? .run_pass(Ssa::defunctionalize, "After Defunctionalization:") .run_pass(Ssa::remove_paired_rc, "After Removing Paired rc_inc & rc_decs:") @@ -99,7 +124,9 @@ pub(crate) fn optimize_into_acir( drop(ssa_gen_span_guard); - let artifacts = time("SSA to ACIR", options.print_codegen_timings, || ssa.into_acir(&brillig))?; + let artifacts = time("SSA to ACIR", options.print_codegen_timings, || { + ssa.into_acir(&brillig, options.expression_width) + })?; Ok(ArtifactsAndWarnings(artifacts, ssa_level_warnings)) } @@ -160,19 +187,6 @@ impl SsaProgramArtifact { } } -pub struct SsaEvaluatorOptions { - /// Emit debug information for the intermediate SSA IR - pub enable_ssa_logging: bool, - - pub enable_brillig_logging: bool, - - /// Force Brillig output (for step debugging) - pub force_brillig_output: bool, - - /// Pretty print benchmark times of each code generation pass - pub print_codegen_timings: bool, -} - /// Compiles the [`Program`] into [`ACIR``][acvm::acir::circuit::Program]. /// /// The output ACIR is backend-agnostic and so must go through a transformation pass before usage in proof generation. @@ -248,6 +262,7 @@ fn convert_generated_acir_into_circuit( let GeneratedAcir { return_witnesses, locations, + brillig_locations, input_witnesses, assertion_payloads: assert_messages, warnings, @@ -278,7 +293,19 @@ fn convert_generated_acir_into_circuit( .map(|(index, locations)| (index, locations.into_iter().collect())) .collect(); - let mut debug_info = DebugInfo::new(locations, debug_variables, debug_functions, debug_types); + let brillig_locations = brillig_locations + .into_iter() + .map(|(function_index, locations)| { + let locations = locations + .into_iter() + .map(|(index, locations)| (index, locations.into_iter().collect())) + .collect(); + (function_index, locations) + }) + .collect(); + + let mut debug_info = + DebugInfo::new(locations, brillig_locations, debug_variables, debug_functions, debug_types); // Perform any ACIR-level optimizations let (optimized_circuit, transformation_map) = acvm::compiler::optimize(circuit); @@ -341,8 +368,18 @@ impl SsaBuilder { print_ssa_passes: bool, force_brillig_runtime: bool, print_codegen_timings: bool, + emit_ssa: &Option, ) -> Result { let ssa = ssa_gen::generate_ssa(program, force_brillig_runtime)?; + if let Some(emit_ssa) = emit_ssa { + let mut emit_ssa_dir = emit_ssa.clone(); + // We expect the full package artifact path to be passed in here, + // and attempt to create the target directory if it does not exist. + emit_ssa_dir.pop(); + create_named_dir(emit_ssa_dir.as_ref(), "target"); + let ssa_path = emit_ssa.with_extension("ssa.json"); + write_to_file(&serde_json::to_vec(&ssa).unwrap(), &ssa_path); + } Ok(SsaBuilder { print_ssa_passes, print_codegen_timings, ssa }.print("Initial SSA:")) } @@ -373,3 +410,23 @@ impl SsaBuilder { self } } + +fn create_named_dir(named_dir: &Path, name: &str) -> PathBuf { + std::fs::create_dir_all(named_dir) + .unwrap_or_else(|_| panic!("could not create the `{name}` directory")); + + PathBuf::from(named_dir) +} + +fn write_to_file(bytes: &[u8], path: &Path) { + let display = path.display(); + + let mut file = match File::create(path) { + Err(why) => panic!("couldn't create {display}: {why}"), + Ok(file) => file, + }; + + if let Err(why) = file.write_all(bytes) { + panic!("couldn't write to {display}: {why}"); + } +} diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs index 629cc491ba6b..b2a731064687 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs @@ -7,9 +7,9 @@ use crate::ssa::acir_gen::{AcirDynamicArray, AcirValue}; use crate::ssa::ir::dfg::CallStack; use crate::ssa::ir::types::Type as SsaType; use crate::ssa::ir::{instruction::Endian, types::NumericType}; -use acvm::acir::circuit::brillig::{BrilligInputs, BrilligOutputs}; +use acvm::acir::circuit::brillig::{BrilligFunctionId, BrilligInputs, BrilligOutputs}; use acvm::acir::circuit::opcodes::{BlockId, BlockType, MemOp}; -use acvm::acir::circuit::{AssertionPayload, ExpressionOrMemory, Opcode}; +use acvm::acir::circuit::{AssertionPayload, ExpressionOrMemory, ExpressionWidth, Opcode}; use acvm::blackbox_solver; use acvm::brillig_vm::{MemoryValue, VMStatus, VM}; use acvm::{ @@ -24,6 +24,7 @@ use acvm::{ use fxhash::FxHashMap as HashMap; use iter_extended::{try_vecmap, vecmap}; use num_bigint::BigUint; +use std::cmp::Ordering; use std::{borrow::Cow, hash::Hash}; #[derive(Clone, Debug, PartialEq, Eq, Hash)] @@ -124,9 +125,15 @@ pub(crate) struct AcirContext { /// The BigIntContext, used to generate identifiers for BigIntegers big_int_ctx: BigIntContext, + + expression_width: ExpressionWidth, } impl AcirContext { + pub(crate) fn set_expression_width(&mut self, expression_width: ExpressionWidth) { + self.expression_width = expression_width; + } + pub(crate) fn current_witness_index(&self) -> Witness { self.acir_ir.current_witness_index() } @@ -584,6 +591,7 @@ impl AcirContext { pub(crate) fn mul_var(&mut self, lhs: AcirVar, rhs: AcirVar) -> Result { let lhs_data = self.vars[&lhs].clone(); let rhs_data = self.vars[&rhs].clone(); + let result = match (lhs_data, rhs_data) { // (x * 1) == (1 * x) == x (AcirVarData::Const(constant), _) if constant.is_one() => rhs, @@ -655,6 +663,7 @@ impl AcirContext { self.mul_var(lhs, rhs)? } }; + Ok(result) } @@ -670,9 +679,62 @@ impl AcirContext { pub(crate) fn add_var(&mut self, lhs: AcirVar, rhs: AcirVar) -> Result { let lhs_expr = self.var_to_expression(lhs)?; let rhs_expr = self.var_to_expression(rhs)?; + let sum_expr = &lhs_expr + &rhs_expr; + if fits_in_one_identity(&sum_expr, self.expression_width) { + let sum_var = self.add_data(AcirVarData::from(sum_expr)); + + return Ok(sum_var); + } + + let sum_expr = match lhs_expr.width().cmp(&rhs_expr.width()) { + Ordering::Greater => { + let lhs_witness_var = self.get_or_create_witness_var(lhs)?; + let lhs_witness_expr = self.var_to_expression(lhs_witness_var)?; + + let new_sum_expr = &lhs_witness_expr + &rhs_expr; + if fits_in_one_identity(&new_sum_expr, self.expression_width) { + new_sum_expr + } else { + let rhs_witness_var = self.get_or_create_witness_var(rhs)?; + let rhs_witness_expr = self.var_to_expression(rhs_witness_var)?; + + &lhs_expr + &rhs_witness_expr + } + } + Ordering::Less => { + let rhs_witness_var = self.get_or_create_witness_var(rhs)?; + let rhs_witness_expr = self.var_to_expression(rhs_witness_var)?; + + let new_sum_expr = &lhs_expr + &rhs_witness_expr; + if fits_in_one_identity(&new_sum_expr, self.expression_width) { + new_sum_expr + } else { + let lhs_witness_var = self.get_or_create_witness_var(lhs)?; + let lhs_witness_expr = self.var_to_expression(lhs_witness_var)?; - Ok(self.add_data(AcirVarData::from(sum_expr))) + &lhs_witness_expr + &rhs_expr + } + } + Ordering::Equal => { + let lhs_witness_var = self.get_or_create_witness_var(lhs)?; + let lhs_witness_expr = self.var_to_expression(lhs_witness_var)?; + + let new_sum_expr = &lhs_witness_expr + &rhs_expr; + if fits_in_one_identity(&new_sum_expr, self.expression_width) { + new_sum_expr + } else { + let rhs_witness_var = self.get_or_create_witness_var(rhs)?; + let rhs_witness_expr = self.var_to_expression(rhs_witness_var)?; + + &lhs_witness_expr + &rhs_witness_expr + } + } + }; + + let sum_var = self.add_data(AcirVarData::from(sum_expr)); + + Ok(sum_var) } /// Adds a new Variable to context whose value will @@ -1550,7 +1612,7 @@ impl AcirContext { outputs: Vec, attempt_execution: bool, unsafe_return_values: bool, - brillig_function_index: u32, + brillig_function_index: BrilligFunctionId, brillig_stdlib_func: Option, ) -> Result, RuntimeError> { let predicate = self.var_to_expression(predicate)?; @@ -1990,6 +2052,23 @@ impl From> for AcirVarData { } } +/// Checks if this expression can fit into one arithmetic identity +fn fits_in_one_identity(expr: &Expression, width: ExpressionWidth) -> bool { + let width = match &width { + ExpressionWidth::Unbounded => { + return true; + } + ExpressionWidth::Bounded { width } => *width, + }; + + // A Polynomial with more than one mul term cannot fit into one opcode + if expr.mul_terms.len() > 1 { + return false; + }; + + expr.width() <= width +} + /// A Reference to an `AcirVarData` #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) struct AcirVar(usize); diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs index 1395d04f99e2..661371c5de6c 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs @@ -9,7 +9,7 @@ use crate::{ }; use acvm::acir::{ circuit::{ - brillig::{BrilligInputs, BrilligOutputs}, + brillig::{BrilligFunctionId, BrilligInputs, BrilligOutputs}, opcodes::{BlackBoxFuncCall, FunctionInput, Opcode as AcirOpcode}, AssertionPayload, OpcodeLocation, }, @@ -27,7 +27,7 @@ use num_bigint::BigUint; /// This index should be used when adding a Brillig call during code generation. /// Code generation should then keep track of that unresolved call opcode which will be resolved with the /// correct function index after code generation. -pub(crate) const PLACEHOLDER_BRILLIG_INDEX: u32 = 0; +pub(crate) const PLACEHOLDER_BRILLIG_INDEX: BrilligFunctionId = BrilligFunctionId(0); #[derive(Debug, Default)] /// The output of the Acir-gen pass, which should only be produced for entry point Acir functions @@ -49,8 +49,11 @@ pub(crate) struct GeneratedAcir { /// All witness indices which are inputs to the main function pub(crate) input_witnesses: Vec, - /// Correspondence between an opcode index (in opcodes) and the source code call stack which generated it - pub(crate) locations: BTreeMap, + pub(crate) locations: OpcodeToLocationsMap, + + /// Brillig function id -> Opcodes locations map + /// This map is used to prevent redundant locations being stored for the same Brillig entry point. + pub(crate) brillig_locations: BTreeMap, /// Source code location of the current instruction being processed /// None if we do not know the location @@ -71,6 +74,9 @@ pub(crate) struct GeneratedAcir { pub(crate) brillig_stdlib_func_locations: BTreeMap, } +/// Correspondence between an opcode index (in opcodes) and the source code call stack which generated it +pub(crate) type OpcodeToLocationsMap = BTreeMap; + #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] pub(crate) enum BrilligStdlibFunc { Inverse, @@ -564,33 +570,47 @@ impl GeneratedAcir { generated_brillig: &GeneratedBrillig, inputs: Vec>, outputs: Vec, - brillig_function_index: u32, + brillig_function_index: BrilligFunctionId, stdlib_func: Option, ) { + // Check whether we have a call to this Brillig function already exists. + // This helps us optimize the Brillig metadata to only be stored once per Brillig entry point. + let inserted_func_before = self.brillig_locations.get(&brillig_function_index).is_some(); + let opcode = AcirOpcode::BrilligCall { id: brillig_function_index, inputs, outputs, predicate }; self.push_opcode(opcode); + if let Some(stdlib_func) = stdlib_func { self.brillig_stdlib_func_locations .insert(self.last_acir_opcode_location(), stdlib_func); + // The Brillig stdlib functions are handwritten and do not have any locations or assert messages. + // To avoid inserting the `PLACEHOLDER_BRILLIG_INDEX` into `self.brillig_locations` before the first + // user-specified Brillig call we can simply return after the Brillig stdlib function call. + return; } - for (brillig_index, call_stack) in generated_brillig.locations.iter() { - self.locations.insert( + for (brillig_index, message) in generated_brillig.assert_messages.iter() { + self.assertion_payloads.insert( OpcodeLocation::Brillig { acir_index: self.opcodes.len() - 1, brillig_index: *brillig_index, }, - call_stack.clone(), + AssertionPayload::StaticString(message.clone()), ); } - for (brillig_index, message) in generated_brillig.assert_messages.iter() { - self.assertion_payloads.insert( + + if inserted_func_before { + return; + } + + for (brillig_index, call_stack) in generated_brillig.locations.iter() { + self.brillig_locations.entry(brillig_function_index).or_default().insert( OpcodeLocation::Brillig { acir_index: self.opcodes.len() - 1, brillig_index: *brillig_index, }, - AssertionPayload::StaticString(message.clone()), + call_stack.clone(), ); } } @@ -599,7 +619,7 @@ impl GeneratedAcir { pub(crate) fn resolve_brillig_stdlib_call( &mut self, opcode_location: OpcodeLocation, - brillig_function_index: u32, + brillig_function_index: BrilligFunctionId, ) { let acir_index = match opcode_location { OpcodeLocation::Acir(index) => index, diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs index a75aabe6a03c..3e855ecbf9b5 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -32,8 +32,8 @@ pub(crate) use acir_ir::generated_acir::GeneratedAcir; use acvm::acir::circuit::opcodes::BlockType; use noirc_frontend::monomorphization::ast::InlineType; -use acvm::acir::circuit::brillig::BrilligBytecode; -use acvm::acir::circuit::{AssertionPayload, ErrorSelector, OpcodeLocation}; +use acvm::acir::circuit::brillig::{BrilligBytecode, BrilligFunctionId}; +use acvm::acir::circuit::{AssertionPayload, ErrorSelector, ExpressionWidth, OpcodeLocation}; use acvm::acir::native_types::Witness; use acvm::acir::BlackBoxFunc; use acvm::{acir::circuit::opcodes::BlockId, acir::AcirField, FieldElement}; @@ -54,15 +54,16 @@ struct SharedContext { /// This mapping is necessary to use the correct function pointer for a Brillig call. /// This uses the brillig parameters in the map since using slices with different lengths /// needs to create different brillig entrypoints - brillig_generated_func_pointers: BTreeMap<(FunctionId, Vec), u32>, + brillig_generated_func_pointers: + BTreeMap<(FunctionId, Vec), BrilligFunctionId>, /// Maps a Brillig std lib function (a handwritten primitive such as for inversion) -> Final generated Brillig artifact index. /// A separate mapping from normal Brillig calls is necessary as these methods do not have an associated function id from SSA. - brillig_stdlib_func_pointer: HashMap, + brillig_stdlib_func_pointer: HashMap, /// Keeps track of Brillig std lib calls per function that need to still be resolved /// with the correct function pointer from the `brillig_stdlib_func_pointer` map. - brillig_stdlib_calls_to_resolve: HashMap>, + brillig_stdlib_calls_to_resolve: HashMap>, } impl SharedContext { @@ -70,7 +71,7 @@ impl SharedContext { &self, func_id: FunctionId, arguments: Vec, - ) -> Option<&u32> { + ) -> Option<&BrilligFunctionId> { self.brillig_generated_func_pointers.get(&(func_id, arguments)) } @@ -82,15 +83,15 @@ impl SharedContext { &mut self, func_id: FunctionId, arguments: Vec, - generated_pointer: u32, + generated_pointer: BrilligFunctionId, code: GeneratedBrillig, ) { self.brillig_generated_func_pointers.insert((func_id, arguments), generated_pointer); self.generated_brillig.push(code); } - fn new_generated_pointer(&self) -> u32 { - self.generated_brillig.len() as u32 + fn new_generated_pointer(&self) -> BrilligFunctionId { + BrilligFunctionId(self.generated_brillig.len() as u32) } fn generate_brillig_calls_to_resolve( @@ -120,7 +121,7 @@ impl SharedContext { fn insert_generated_brillig_stdlib( &mut self, brillig_stdlib_func: BrilligStdlibFunc, - generated_pointer: u32, + generated_pointer: BrilligFunctionId, func_id: FunctionId, opcode_location: OpcodeLocation, code: GeneratedBrillig, @@ -130,7 +131,11 @@ impl SharedContext { self.generated_brillig.push(code); } - fn add_call_to_resolve(&mut self, func_id: FunctionId, call_to_resolve: (OpcodeLocation, u32)) { + fn add_call_to_resolve( + &mut self, + func_id: FunctionId, + call_to_resolve: (OpcodeLocation, BrilligFunctionId), + ) { self.brillig_stdlib_calls_to_resolve.entry(func_id).or_default().push(call_to_resolve); } } @@ -282,12 +287,16 @@ pub(crate) type Artifacts = ( impl Ssa { #[tracing::instrument(level = "trace", skip_all)] - pub(crate) fn into_acir(self, brillig: &Brillig) -> Result { + pub(crate) fn into_acir( + self, + brillig: &Brillig, + expression_width: ExpressionWidth, + ) -> Result { let mut acirs = Vec::new(); - // TODO: can we parallelise this? + // TODO: can we parallelize this? let mut shared_context = SharedContext::default(); for function in self.functions.values() { - let context = Context::new(&mut shared_context); + let context = Context::new(&mut shared_context, expression_width); if let Some(mut generated_acir) = context.convert_ssa_function(&self, function, brillig)? { @@ -334,8 +343,12 @@ impl Ssa { } impl<'a> Context<'a> { - fn new(shared_context: &'a mut SharedContext) -> Context<'a> { + fn new( + shared_context: &'a mut SharedContext, + expression_width: ExpressionWidth, + ) -> Context<'a> { let mut acir_context = AcirContext::default(); + acir_context.set_expression_width(expression_width); let current_side_effects_enabled_var = acir_context.add_constant(FieldElement::one()); Context { @@ -422,6 +435,12 @@ impl<'a> Context<'a> { let (return_vars, return_warnings) = self.convert_ssa_return(entry_block.unwrap_terminator(), dfg)?; + let call_data_arrays: Vec = + self.data_bus.call_data.iter().map(|cd| cd.array_id).collect(); + for call_data_array in call_data_arrays { + self.ensure_array_is_initialized(call_data_array, dfg)?; + } + // TODO: This is a naive method of assigning the return values to their witnesses as // we're likely to get a number of constraints which are asserting one witness to be equal to another. // @@ -466,10 +485,15 @@ impl<'a> Context<'a> { false, true, // We are guaranteed to have a Brillig function pointer of `0` as main itself is marked as unconstrained - 0, + BrilligFunctionId(0), None, )?; - self.shared_context.insert_generated_brillig(main_func.id(), arguments, 0, code); + self.shared_context.insert_generated_brillig( + main_func.id(), + arguments, + BrilligFunctionId(0), + code, + ); let return_witnesses: Vec = output_values .iter() @@ -786,7 +810,7 @@ impl<'a> Context<'a> { { let code = self .shared_context - .generated_brillig(*generated_pointer as usize); + .generated_brillig(generated_pointer.as_usize()); self.acir_context.brillig_call( self.current_side_effects_enabled_var, code, @@ -1255,20 +1279,23 @@ impl<'a> Context<'a> { let res_typ = dfg.type_of_value(results[0]); // Get operations to call-data parameters are replaced by a get to the call-data-bus array - if let Some(call_data) = self.data_bus.call_data { - if self.data_bus.call_data_map.contains_key(&array) { - // TODO: the block_id of call-data must be notified to the backend - // TODO: should we do the same for return-data? - let type_size = res_typ.flattened_size(); - let type_size = - self.acir_context.add_constant(FieldElement::from(type_size as i128)); - let offset = self.acir_context.mul_var(var_index, type_size)?; - let bus_index = self - .acir_context - .add_constant(FieldElement::from(self.data_bus.call_data_map[&array] as i128)); - let new_index = self.acir_context.add_var(offset, bus_index)?; - return self.array_get(instruction, call_data, new_index, dfg, index_side_effect); - } + if let Some(call_data) = + self.data_bus.call_data.iter().find(|cd| cd.index_map.contains_key(&array)) + { + let type_size = res_typ.flattened_size(); + let type_size = self.acir_context.add_constant(FieldElement::from(type_size as i128)); + let offset = self.acir_context.mul_var(var_index, type_size)?; + let bus_index = self + .acir_context + .add_constant(FieldElement::from(call_data.index_map[&array] as i128)); + let new_index = self.acir_context.add_var(offset, bus_index)?; + return self.array_get( + instruction, + call_data.array_id, + new_index, + dfg, + index_side_effect, + ); } // Compiler sanity check @@ -1288,6 +1315,7 @@ impl<'a> Context<'a> { index_side_effect = false; } } + // Fallback to multiplication if the index side_effects have not already been handled if index_side_effect { // Set the value to 0 if current_side_effects is 0, to ensure it fits in any value type @@ -1698,17 +1726,20 @@ impl<'a> Context<'a> { len: usize, value: Option, ) -> Result<(), InternalError> { - let databus = if self.data_bus.call_data.is_some() - && self.block_id(&self.data_bus.call_data.unwrap()) == array - { - BlockType::CallData - } else if self.data_bus.return_data.is_some() + let mut databus = BlockType::Memory; + if self.data_bus.return_data.is_some() && self.block_id(&self.data_bus.return_data.unwrap()) == array { - BlockType::ReturnData - } else { - BlockType::Memory - }; + databus = BlockType::ReturnData; + } + for array_id in self.data_bus.call_data_array() { + if self.block_id(&array_id) == array { + assert!(databus == BlockType::Memory); + databus = BlockType::CallData; + break; + } + } + self.acir_context.initialize_array(array, len, value, databus)?; self.initialized_arrays.insert(array); Ok(()) @@ -2693,7 +2724,22 @@ impl<'a> Context<'a> { .get_or_create_witness_var(input) .map(|val| self.convert_vars_to_values(vec![val], dfg, result_ids))?) } - _ => todo!("expected a black box function"), + Intrinsic::ArrayAsStrUnchecked => Ok(vec![self.convert_value(arguments[0], dfg)]), + Intrinsic::AssertConstant => { + unreachable!("Expected assert_constant to be removed by this point") + } + Intrinsic::StaticAssert => { + unreachable!("Expected static_assert to be removed by this point") + } + Intrinsic::StrAsBytes => unreachable!("Expected as_bytes to be removed by this point"), + Intrinsic::FromField => unreachable!("Expected from_field to be removed by this point"), + Intrinsic::AsField => unreachable!("Expected as_field to be removed by this point"), + Intrinsic::IsUnconstrained => { + unreachable!("Expected is_unconstrained to be removed by this point") + } + Intrinsic::DerivePedersenGenerators => { + unreachable!("DerivePedersenGenerators can only be called with constants") + } } } @@ -2816,16 +2862,17 @@ fn can_omit_element_sizes_array(array_typ: &Type) -> bool { #[cfg(test)] mod test { - use std::collections::BTreeMap; - use acvm::{ acir::{ - circuit::{Opcode, OpcodeLocation}, + circuit::{brillig::BrilligFunctionId, ExpressionWidth, Opcode, OpcodeLocation}, native_types::Witness, }, FieldElement, }; + use im::vector; + use noirc_errors::Location; use noirc_frontend::monomorphization::ast::InlineType; + use std::collections::BTreeMap; use crate::{ brillig::Brillig, @@ -2853,6 +2900,9 @@ mod test { } else { builder.new_brillig_function("foo".into(), foo_id); } + // Set a call stack for testing whether `brillig_locations` in the `GeneratedAcir` was accurately set. + builder.set_call_stack(vector![Location::dummy(), Location::dummy()]); + let foo_v0 = builder.add_parameter(Type::field()); let foo_v1 = builder.add_parameter(Type::field()); @@ -2917,7 +2967,7 @@ mod test { let ssa = builder.finish(); let (acir_functions, _, _) = ssa - .into_acir(&Brillig::default()) + .into_acir(&Brillig::default(), ExpressionWidth::default()) .expect("Should compile manually written SSA into ACIR"); // Expected result: // main f0 @@ -3012,7 +3062,7 @@ mod test { let ssa = builder.finish(); let (acir_functions, _, _) = ssa - .into_acir(&Brillig::default()) + .into_acir(&Brillig::default(), ExpressionWidth::default()) .expect("Should compile manually written SSA into ACIR"); // The expected result should look very similar to the above test expect that the input witnesses of the `Call` // opcodes will be different. The changes can discerned from the checks below. @@ -3102,7 +3152,7 @@ mod test { let ssa = builder.finish(); let (acir_functions, _, _) = ssa - .into_acir(&Brillig::default()) + .into_acir(&Brillig::default(), ExpressionWidth::default()) .expect("Should compile manually written SSA into ACIR"); assert_eq!(acir_functions.len(), 3, "Should have three ACIR functions"); @@ -3215,8 +3265,9 @@ mod test { let ssa = builder.finish(); let brillig = ssa.to_brillig(false); - let (acir_functions, brillig_functions, _) = - ssa.into_acir(&brillig).expect("Should compile manually written SSA into ACIR"); + let (acir_functions, brillig_functions, _) = ssa + .into_acir(&brillig, ExpressionWidth::default()) + .expect("Should compile manually written SSA into ACIR"); assert_eq!(acir_functions.len(), 1, "Should only have a `main` ACIR function"); assert_eq!(brillig_functions.len(), 2, "Should only have generated two Brillig functions"); @@ -3230,11 +3281,18 @@ mod test { match opcode { Opcode::BrilligCall { id, .. } => { let expected_id = if i == 3 || i == 5 { 1 } else { 0 }; + let expected_id = BrilligFunctionId(expected_id); assert_eq!(*id, expected_id, "Expected an id of {expected_id} but got {id}"); } _ => panic!("Expected only Brillig call opcode"), } } + + // We have two normal Brillig functions that was called multiple times. + // We should have a single locations map for each function's debug metadata. + assert_eq!(main_acir.brillig_locations.len(), 2); + assert!(main_acir.brillig_locations.get(&BrilligFunctionId(0)).is_some()); + assert!(main_acir.brillig_locations.get(&BrilligFunctionId(1)).is_some()); } // Test that given multiple primitive operations that are represented by Brillig directives (e.g. invert/quotient), @@ -3272,7 +3330,7 @@ mod test { // The Brillig bytecode we insert for the stdlib is hardcoded so we do not need to provide any // Brillig artifacts to the ACIR gen pass. let (acir_functions, brillig_functions, _) = ssa - .into_acir(&Brillig::default()) + .into_acir(&Brillig::default(), ExpressionWidth::default()) .expect("Should compile manually written SSA into ACIR"); assert_eq!(acir_functions.len(), 1, "Should only have a `main` ACIR function"); @@ -3290,6 +3348,8 @@ mod test { 4, 0, ); + + assert_eq!(main_acir.brillig_locations.len(), 0); } // Test that given both hardcoded Brillig directives and calls to normal Brillig functions, @@ -3343,8 +3403,9 @@ mod test { let brillig = ssa.to_brillig(false); println!("{}", ssa); - let (acir_functions, brillig_functions, _) = - ssa.into_acir(&brillig).expect("Should compile manually written SSA into ACIR"); + let (acir_functions, brillig_functions, _) = ssa + .into_acir(&brillig, ExpressionWidth::default()) + .expect("Should compile manually written SSA into ACIR"); assert_eq!(acir_functions.len(), 1, "Should only have a `main` ACIR function"); // We expect 3 brillig functions: @@ -3359,13 +3420,12 @@ mod test { let main_acir = &acir_functions[0]; let main_opcodes = main_acir.opcodes(); - check_brillig_calls( - &acir_functions[0].brillig_stdlib_func_locations, - main_opcodes, - 1, - 4, - 2, - ); + check_brillig_calls(&main_acir.brillig_stdlib_func_locations, main_opcodes, 1, 4, 2); + + // We have one normal Brillig functions that was called twice. + // We should have a single locations map for each function's debug metadata. + assert_eq!(main_acir.brillig_locations.len(), 1); + assert!(main_acir.brillig_locations.get(&BrilligFunctionId(0)).is_some()); } // Test that given both normal Brillig calls, Brillig stdlib calls, and non-inlined ACIR calls, that we accurately generate ACIR. @@ -3431,8 +3491,9 @@ mod test { let brillig = ssa.to_brillig(false); println!("{}", ssa); - let (acir_functions, brillig_functions, _) = - ssa.into_acir(&brillig).expect("Should compile manually written SSA into ACIR"); + let (acir_functions, brillig_functions, _) = ssa + .into_acir(&brillig, ExpressionWidth::default()) + .expect("Should compile manually written SSA into ACIR"); assert_eq!(acir_functions.len(), 2, "Should only have two ACIR functions"); // We expect 3 brillig functions: @@ -3455,9 +3516,14 @@ mod test { 2, ); + assert_eq!(main_acir.brillig_locations.len(), 1); + assert!(main_acir.brillig_locations.get(&BrilligFunctionId(0)).is_some()); + let foo_acir = &acir_functions[1]; let foo_opcodes = foo_acir.opcodes(); check_brillig_calls(&acir_functions[1].brillig_stdlib_func_locations, foo_opcodes, 1, 1, 0); + + assert_eq!(foo_acir.brillig_locations.len(), 0); } fn check_brillig_calls( @@ -3488,6 +3554,7 @@ mod test { // IDs are expected to always reference Brillig bytecode at the end of the Brillig functions list. // We have one normal Brillig call so we add one here to the std lib function's index within the std lib. let expected_id = stdlib_func_index + num_normal_brillig_functions; + let expected_id = BrilligFunctionId(expected_id); assert_eq!(id, expected_id, "Expected {expected_id} but got {id}"); num_brillig_stdlib_calls += 1; } @@ -3513,7 +3580,7 @@ mod test { continue; } // We only generate one normal Brillig call so we should expect a function ID of `0` - let expected_id = 0u32; + let expected_id = BrilligFunctionId(0); assert_eq!(*id, expected_id, "Expected an id of {expected_id} but got {id}"); num_normal_brillig_calls += 1; } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs index 5831faa7c4d7..24fcb8f61dfd 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs @@ -202,6 +202,7 @@ impl Context { | Intrinsic::AsWitness | Intrinsic::IsUnconstrained => {} Intrinsic::ArrayLen + | Intrinsic::ArrayAsStrUnchecked | Intrinsic::AsField | Intrinsic::AsSlice | Intrinsic::BlackBox(..) diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/data_bus.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/data_bus.rs index 5f0660f5a79d..0cdeed6153cf 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/data_bus.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/data_bus.rs @@ -1,3 +1,4 @@ +use std::collections::BTreeMap; use std::rc::Rc; use crate::ssa::ir::{types::Type, value::ValueId}; @@ -5,9 +6,16 @@ use acvm::FieldElement; use fxhash::FxHashMap as HashMap; use noirc_frontend::ast; use noirc_frontend::hir_def::function::FunctionSignature; +use serde::{Deserialize, Serialize}; use super::FunctionBuilder; +#[derive(Clone)] +pub(crate) enum DatabusVisibility { + None, + CallData(u32), + ReturnData, +} /// Used to create a data bus, which is an array of private inputs /// replacing public inputs pub(crate) struct DataBusBuilder { @@ -27,15 +35,16 @@ impl DataBusBuilder { } } - /// Generates a boolean vector telling which (ssa) parameter from the given function signature + /// Generates a vector telling which (ssa) parameters from the given function signature /// are tagged with databus visibility - pub(crate) fn is_databus(main_signature: &FunctionSignature) -> Vec { + pub(crate) fn is_databus(main_signature: &FunctionSignature) -> Vec { let mut params_is_databus = Vec::new(); for param in &main_signature.0 { let is_databus = match param.2 { - ast::Visibility::Public | ast::Visibility::Private => false, - ast::Visibility::DataBus => true, + ast::Visibility::Public | ast::Visibility::Private => DatabusVisibility::None, + ast::Visibility::CallData(id) => DatabusVisibility::CallData(id), + ast::Visibility::ReturnData => DatabusVisibility::ReturnData, }; let len = param.1.field_count() as usize; params_is_databus.extend(vec![is_databus; len]); @@ -44,34 +53,51 @@ impl DataBusBuilder { } } -#[derive(Clone, Default, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub(crate) struct CallData { + pub(crate) array_id: ValueId, + pub(crate) index_map: HashMap, +} + +#[derive(Clone, Default, Debug, Serialize, Deserialize)] pub(crate) struct DataBus { - pub(crate) call_data: Option, - pub(crate) call_data_map: HashMap, + pub(crate) call_data: Vec, pub(crate) return_data: Option, } impl DataBus { /// Updates the databus values with the provided function pub(crate) fn map_values(&self, mut f: impl FnMut(ValueId) -> ValueId) -> DataBus { - let mut call_data_map = HashMap::default(); - for (k, v) in self.call_data_map.iter() { - call_data_map.insert(f(*k), *v); - } - DataBus { - call_data: self.call_data.map(&mut f), - call_data_map, - return_data: self.return_data.map(&mut f), - } + let call_data = self + .call_data + .iter() + .map(|cd| { + let mut call_data_map = HashMap::default(); + for (k, v) in cd.index_map.iter() { + call_data_map.insert(f(*k), *v); + } + CallData { array_id: f(cd.array_id), index_map: call_data_map } + }) + .collect(); + DataBus { call_data, return_data: self.return_data.map(&mut f) } } + pub(crate) fn call_data_array(&self) -> Vec { + self.call_data.iter().map(|cd| cd.array_id).collect() + } /// Construct a databus from call_data and return_data data bus builders - pub(crate) fn get_data_bus(call_data: DataBusBuilder, return_data: DataBusBuilder) -> DataBus { - DataBus { - call_data: call_data.databus, - call_data_map: call_data.map, - return_data: return_data.databus, + pub(crate) fn get_data_bus( + call_data: Vec, + return_data: DataBusBuilder, + ) -> DataBus { + let mut call_data_args = Vec::new(); + for call_data_item in call_data { + if let Some(array_id) = call_data_item.databus { + call_data_args.push(CallData { array_id, index_map: call_data_item.map }); + } } + + DataBus { call_data: call_data_args, return_data: return_data.databus } } } @@ -129,19 +155,36 @@ impl FunctionBuilder { } /// Generate the data bus for call-data, based on the parameters of the entry block - /// and a boolean vector telling which ones are call-data - pub(crate) fn call_data_bus(&mut self, is_params_databus: Vec) -> DataBusBuilder { + /// and a vector telling which ones are call-data + pub(crate) fn call_data_bus( + &mut self, + is_params_databus: Vec, + ) -> Vec { //filter parameters of the first block that have call-data visibility let first_block = self.current_function.entry_block(); let params = self.current_function.dfg[first_block].parameters(); - let mut databus_param = Vec::new(); - for (param, is_databus) in params.iter().zip(is_params_databus) { - if is_databus { - databus_param.push(param.to_owned()); + let mut databus_param: BTreeMap> = BTreeMap::new(); + for (param, databus_attribute) in params.iter().zip(is_params_databus) { + match databus_attribute { + DatabusVisibility::None | DatabusVisibility::ReturnData => continue, + DatabusVisibility::CallData(call_data_id) => { + if let std::collections::btree_map::Entry::Vacant(e) = + databus_param.entry(call_data_id) + { + e.insert(vec![param.to_owned()]); + } else { + databus_param.get_mut(&call_data_id).unwrap().push(param.to_owned()); + } + } } } - // create the call-data-bus from the filtered list - let call_data = DataBusBuilder::new(); - self.initialize_data_bus(&databus_param, call_data) + // create the call-data-bus from the filtered lists + let mut result = Vec::new(); + for id in databus_param.keys() { + let builder = DataBusBuilder::new(); + let call_databus = self.initialize_data_bus(&databus_param[id], builder); + result.push(call_databus); + } + result } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs index b24c5632b249..49184bf4c63d 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs @@ -371,10 +371,12 @@ impl FunctionBuilder { then_destination: BasicBlockId, else_destination: BasicBlockId, ) { + let call_stack = self.call_stack.clone(); self.terminate_block_with(TerminatorInstruction::JmpIf { condition, then_destination, else_destination, + call_stack, }); } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/basic_block.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/basic_block.rs index 981afa3e3807..a7c637dedd0d 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/basic_block.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/basic_block.rs @@ -4,6 +4,7 @@ use super::{ map::Id, value::ValueId, }; +use serde::{Deserialize, Serialize}; /// A Basic block is a maximal collection of instructions /// such that there are only jumps at the end of block @@ -11,7 +12,7 @@ use super::{ /// /// This means that if one instruction is executed in a basic /// block, then all instructions are executed. ie single-entry single-exit. -#[derive(Debug, PartialEq, Eq, Hash, Clone)] +#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)] pub(crate) struct BasicBlock { /// Parameters to the basic block. parameters: Vec, diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/cfg.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/cfg.rs index 5a3f07cd6734..b9166bf1d56d 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/cfg.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/cfg.rs @@ -168,11 +168,13 @@ mod tests { condition: cond, then_destination: block2_id, else_destination: block1_id, + call_stack: CallStack::new(), }); func.dfg[block1_id].set_terminator(TerminatorInstruction::JmpIf { condition: cond, then_destination: block1_id, else_destination: block2_id, + call_stack: CallStack::new(), }); func.dfg[block2_id].set_terminator(TerminatorInstruction::Return { return_values: vec![], @@ -235,6 +237,7 @@ mod tests { condition: cond, then_destination: block1_id, else_destination: ret_block_id, + call_stack: CallStack::new(), }); // Recompute new and changed blocks diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/dfg.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/dfg.rs index 994386f8197d..34d7d595eb93 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/dfg.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/dfg.rs @@ -17,12 +17,16 @@ use acvm::{acir::AcirField, FieldElement}; use fxhash::FxHashMap as HashMap; use iter_extended::vecmap; use noirc_errors::Location; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; +use serde_with::DisplayFromStr; /// The DataFlowGraph contains most of the actual data in a function including /// its blocks, instructions, and values. This struct is largely responsible for /// owning most data in a function and handing out Ids to this data that can be /// shared without worrying about ownership. -#[derive(Debug, Default, Clone)] +#[serde_as] +#[derive(Debug, Default, Clone, Serialize, Deserialize)] pub(crate) struct DataFlowGraph { /// All of the instructions in a function instructions: DenseMap, @@ -36,6 +40,7 @@ pub(crate) struct DataFlowGraph { /// Currently, we need to define them in a better way /// Call instructions require the func signature, but /// other instructions may need some more reading on my part + #[serde_as(as = "HashMap")] results: HashMap>, /// Storage for all of the values defined in this @@ -44,21 +49,25 @@ pub(crate) struct DataFlowGraph { /// Each constant is unique, attempting to insert the same constant /// twice will return the same ValueId. + #[serde(skip)] constants: HashMap<(FieldElement, Type), ValueId>, /// Contains each function that has been imported into the current function. /// A unique `ValueId` for each function's [`Value::Function`] is stored so any given FunctionId /// will always have the same ValueId within this function. + #[serde(skip)] functions: HashMap, /// Contains each intrinsic that has been imported into the current function. /// This map is used to ensure that the ValueId for any given intrinsic is always /// represented by only 1 ValueId within this function. + #[serde(skip)] intrinsics: HashMap, /// Contains each foreign function that has been imported into the current function. /// This map is used to ensure that the ValueId for any given foreign function is always /// represented by only 1 ValueId within this function. + #[serde(skip)] foreign_functions: HashMap, /// All blocks in a function @@ -67,6 +76,7 @@ pub(crate) struct DataFlowGraph { /// Debugging information about which `ValueId`s have had their underlying `Value` substituted /// for that of another. This information is purely used for printing the SSA, and has no /// material effect on the SSA itself. + #[serde(skip)] replaced_value_ids: HashMap, /// Source location of each instruction for debugging and issuing errors. @@ -79,8 +89,10 @@ pub(crate) struct DataFlowGraph { /// /// Instructions inserted by internal SSA passes that don't correspond to user code /// may not have a corresponding location. + #[serde(skip)] locations: HashMap, + #[serde(skip)] pub(crate) data_bus: DataBus, } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/function.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/function.rs index c44824b464b1..bae9f82e4f10 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/function.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/function.rs @@ -2,6 +2,7 @@ use std::collections::BTreeSet; use iter_extended::vecmap; use noirc_frontend::monomorphization::ast::InlineType; +use serde::{Deserialize, Serialize}; use super::basic_block::BasicBlockId; use super::dfg::DataFlowGraph; @@ -10,7 +11,7 @@ use super::map::Id; use super::types::Type; use super::value::ValueId; -#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Serialize, Deserialize)] pub(crate) enum RuntimeType { // A noir function, to be compiled in ACIR and executed by ACVM Acir(InlineType), @@ -37,7 +38,7 @@ impl RuntimeType { /// All functions outside of the current function are seen as external. /// To reference external functions its FunctionId can be used but this /// cannot be checked for correctness until inlining is performed. -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] pub(crate) struct Function { /// The first basic block in the function entry_block: BasicBlockId, diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs index 8cbae732ef96..7dcb50762f5b 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs @@ -1,3 +1,5 @@ +use noirc_errors::Location; +use serde::{Deserialize, Serialize}; use std::hash::{Hash, Hasher}; use acvm::{ @@ -47,9 +49,10 @@ pub(crate) type InstructionId = Id; /// - Opcodes which have no function definition in the /// source code and must be processed by the IR. An example /// of this is println. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub(crate) enum Intrinsic { ArrayLen, + ArrayAsStrUnchecked, AsSlice, AssertConstant, StaticAssert, @@ -75,6 +78,7 @@ impl std::fmt::Display for Intrinsic { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Intrinsic::ArrayLen => write!(f, "array_len"), + Intrinsic::ArrayAsStrUnchecked => write!(f, "array_as_str_unchecked"), Intrinsic::AsSlice => write!(f, "as_slice"), Intrinsic::AssertConstant => write!(f, "assert_constant"), Intrinsic::StaticAssert => write!(f, "static_assert"), @@ -115,6 +119,7 @@ impl Intrinsic { Intrinsic::ToBits(_) | Intrinsic::ToRadix(_) => true, Intrinsic::ArrayLen + | Intrinsic::ArrayAsStrUnchecked | Intrinsic::AsSlice | Intrinsic::SlicePushBack | Intrinsic::SlicePushFront @@ -143,6 +148,7 @@ impl Intrinsic { pub(crate) fn lookup(name: &str) -> Option { match name { "array_len" => Some(Intrinsic::ArrayLen), + "array_as_str_unchecked" => Some(Intrinsic::ArrayAsStrUnchecked), "as_slice" => Some(Intrinsic::AsSlice), "assert_constant" => Some(Intrinsic::AssertConstant), "static_assert" => Some(Intrinsic::StaticAssert), @@ -169,13 +175,13 @@ impl Intrinsic { } /// The endian-ness of bits when encoding values as bits in e.g. ToBits or ToRadix -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] pub(crate) enum Endian { Big, Little, } -#[derive(Debug, PartialEq, Eq, Hash, Clone)] +#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)] /// Instructions are used to perform tasks. /// The instructions that the IR is able to specify are listed below. pub(crate) enum Instruction { @@ -753,7 +759,7 @@ pub(crate) fn error_selector_from_type(typ: &ErrorType) -> ErrorSelector { } } -#[derive(Debug, PartialEq, Eq, Hash, Clone)] +#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)] pub(crate) enum ConstrainError { // These are errors which have been hardcoded during SSA gen Intrinsic(String), @@ -795,7 +801,7 @@ pub(crate) enum InstructionResultType { /// Since our IR needs to be in SSA form, it makes sense /// to split up instructions like this, as we are sure that these instructions /// will not be in the list of instructions for a basic block. -#[derive(Debug, PartialEq, Eq, Hash, Clone)] +#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)] pub(crate) enum TerminatorInstruction { /// Control flow /// @@ -803,7 +809,12 @@ pub(crate) enum TerminatorInstruction { /// /// If the condition is true: jump to the specified `then_destination`. /// Otherwise, jump to the specified `else_destination`. - JmpIf { condition: ValueId, then_destination: BasicBlockId, else_destination: BasicBlockId }, + JmpIf { + condition: ValueId, + then_destination: BasicBlockId, + else_destination: BasicBlockId, + call_stack: CallStack, + }, /// Unconditional Jump /// @@ -830,10 +841,11 @@ impl TerminatorInstruction { ) -> TerminatorInstruction { use TerminatorInstruction::*; match self { - JmpIf { condition, then_destination, else_destination } => JmpIf { + JmpIf { condition, then_destination, else_destination, call_stack } => JmpIf { condition: f(*condition), then_destination: *then_destination, else_destination: *else_destination, + call_stack: call_stack.clone(), }, Jmp { destination, arguments, call_stack } => Jmp { destination: *destination, @@ -901,6 +913,14 @@ impl TerminatorInstruction { Return { .. } => (), } } + + pub(crate) fn call_stack(&self) -> im::Vector { + match self { + TerminatorInstruction::JmpIf { call_stack, .. } + | TerminatorInstruction::Jmp { call_stack, .. } + | TerminatorInstruction::Return { call_stack, .. } => call_stack.clone(), + } + } } /// Contains the result to Instruction::simplify, specifying how the instruction diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/binary.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/binary.rs index fba727ca2261..03262be0a061 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/binary.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/binary.rs @@ -1,4 +1,5 @@ use acvm::{acir::AcirField, FieldElement}; +use serde::{Deserialize, Serialize}; use super::{ DataFlowGraph, Instruction, InstructionResultType, NumericType, SimplifyResult, Type, ValueId, @@ -11,7 +12,7 @@ use super::{ /// All binary operators are also only for numeric types. To implement /// e.g. equality for a compound type like a struct, one must add a /// separate Eq operation for each field and combine them later with And. -#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] +#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize)] pub(crate) enum BinaryOp { /// Addition of lhs + rhs. Add, @@ -64,7 +65,7 @@ impl std::fmt::Display for BinaryOp { } /// A binary instruction in the IR. -#[derive(Debug, PartialEq, Eq, Hash, Clone)] +#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)] pub(crate) struct Binary { /// Left hand side of the binary operation pub(crate) lhs: ValueId, diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs index ad01edbd0b29..ea2523e873e2 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs @@ -85,6 +85,8 @@ pub(super) fn simplify_call( SimplifyResult::None } } + // Strings are already arrays of bytes in SSA + Intrinsic::ArrayAsStrUnchecked => SimplifyResult::SimplifiedTo(arguments[0]), Intrinsic::AsSlice => { let array = dfg.get_array_constant(arguments[0]); if let Some((array, array_type)) = array { @@ -124,7 +126,14 @@ pub(super) fn simplify_call( return SimplifyResult::SimplifiedToMultiple(vec![new_slice_length, new_slice]); } - simplify_slice_push_back(slice, element_type, arguments, dfg, block) + simplify_slice_push_back( + slice, + element_type, + arguments, + dfg, + block, + call_stack.clone(), + ) } else { SimplifyResult::None } @@ -147,7 +156,7 @@ pub(super) fn simplify_call( Intrinsic::SlicePopBack => { let slice = dfg.get_array_constant(arguments[1]); if let Some((_, typ)) = slice { - simplify_slice_pop_back(typ, arguments, dfg, block) + simplify_slice_pop_back(typ, arguments, dfg, block, call_stack.clone()) } else { SimplifyResult::None } @@ -346,12 +355,12 @@ fn simplify_slice_push_back( arguments: &[ValueId], dfg: &mut DataFlowGraph, block: BasicBlockId, + call_stack: CallStack, ) -> SimplifyResult { // The capacity must be an integer so that we can compare it against the slice length let capacity = dfg.make_constant((slice.len() as u128).into(), Type::length_type()); let len_equals_capacity_instr = Instruction::Binary(Binary { lhs: arguments[0], operator: BinaryOp::Eq, rhs: capacity }); - let call_stack = dfg.get_value_call_stack(arguments[0]); let len_equals_capacity = dfg .insert_instruction_and_results(len_equals_capacity_instr, block, None, call_stack.clone()) .first(); @@ -382,7 +391,7 @@ fn simplify_slice_push_back( }; let set_last_slice_value = dfg - .insert_instruction_and_results(set_last_slice_value_instr, block, None, call_stack) + .insert_instruction_and_results(set_last_slice_value_instr, block, None, call_stack.clone()) .first(); let mut slice_sizes = HashMap::default(); @@ -390,7 +399,8 @@ fn simplify_slice_push_back( slice_sizes.insert(new_slice, slice_size / element_size); let unknown = &mut HashMap::default(); - let mut value_merger = ValueMerger::new(dfg, block, &mut slice_sizes, unknown, None); + let mut value_merger = + ValueMerger::new(dfg, block, &mut slice_sizes, unknown, None, call_stack); let new_slice = value_merger.merge_values( len_not_equals_capacity, @@ -407,6 +417,7 @@ fn simplify_slice_pop_back( arguments: &[ValueId], dfg: &mut DataFlowGraph, block: BasicBlockId, + call_stack: CallStack, ) -> SimplifyResult { let element_types = match element_type.clone() { Type::Slice(element_types) | Type::Array(element_types, _) => element_types, @@ -423,7 +434,7 @@ fn simplify_slice_pop_back( let element_size = dfg.make_constant((element_count as u128).into(), Type::length_type()); let flattened_len_instr = Instruction::binary(BinaryOp::Mul, arguments[0], element_size); let mut flattened_len = dfg - .insert_instruction_and_results(flattened_len_instr, block, None, CallStack::new()) + .insert_instruction_and_results(flattened_len_instr, block, None, call_stack.clone()) .first(); flattened_len = update_slice_length(flattened_len, dfg, BinaryOp::Sub, block); @@ -436,7 +447,7 @@ fn simplify_slice_pop_back( get_last_elem_instr, block, Some(element_types.to_vec()), - CallStack::new(), + call_stack.clone(), ) .first(); results.push_front(get_last_elem); diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/map.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/map.rs index 3c3feabc3906..1c9a31f0c99d 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/map.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/map.rs @@ -1,8 +1,11 @@ use fxhash::FxHashMap as HashMap; +use serde::{Deserialize, Serialize}; use std::{ hash::Hash, + str::FromStr, sync::atomic::{AtomicUsize, Ordering}, }; +use thiserror::Error; /// A unique ID corresponding to a value of type T. /// This type can be used to retrieve a value of type T from @@ -12,8 +15,11 @@ use std::{ /// DenseMap or SparseMap. If an Id was created to correspond to one /// particular map type, users need to take care not to use it with /// another map where it will likely be invalid. +#[derive(Serialize, Deserialize)] pub(crate) struct Id { index: usize, + // If we do not skip this field it will simply serialize as `"_marker":null` which is useless extra data + #[serde(skip)] _marker: std::marker::PhantomData, } @@ -106,7 +112,58 @@ impl std::fmt::Display for Id { impl std::fmt::Display for Id { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "f{}", self.index) + write!(f, "i{}", self.index) + } +} + +#[derive(Error, Debug)] +pub(crate) enum IdDisplayFromStrErr { + #[error("Invalid id when deserializing SSA: {0}")] + InvalidId(String), +} + +/// The implementation of display and FromStr allows serializing and deserializing an Id to a string. +/// This is useful when used as key in a map that has to be serialized to JSON/TOML. +impl FromStr for Id { + type Err = IdDisplayFromStrErr; + fn from_str(s: &str) -> Result { + id_from_str_helper::(s, 'b') + } +} + +impl FromStr for Id { + type Err = IdDisplayFromStrErr; + fn from_str(s: &str) -> Result { + id_from_str_helper::(s, 'v') + } +} + +impl FromStr for Id { + type Err = IdDisplayFromStrErr; + fn from_str(s: &str) -> Result { + id_from_str_helper::(s, 'f') + } +} + +impl FromStr for Id { + type Err = IdDisplayFromStrErr; + fn from_str(s: &str) -> Result { + id_from_str_helper::(s, 'i') + } +} + +fn id_from_str_helper(s: &str, value_prefix: char) -> Result, IdDisplayFromStrErr> { + if s.len() < 2 { + return Err(IdDisplayFromStrErr::InvalidId(s.to_string())); + } + + let index = &s[1..]; + let index = index.parse().map_err(|_| IdDisplayFromStrErr::InvalidId(s.to_string()))?; + + if s.chars().next().unwrap() == value_prefix { + Ok(Id::::new(index)) + } else { + Err(IdDisplayFromStrErr::InvalidId(s.to_string())) } } @@ -115,7 +172,7 @@ impl std::fmt::Display for Id { /// access to indices is provided. Since IDs must be stable and correspond /// to indices in the internal Vec, operations that would change element /// ordering like pop, remove, swap_remove, etc, are not possible. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct DenseMap { storage: Vec, } @@ -300,7 +357,7 @@ impl std::ops::Index<&K> for TwoWayMap { /// for types that have no single owner. /// /// This type wraps an AtomicUsize so it can safely be used across threads. -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] pub(crate) struct AtomicCounter { next: AtomicUsize, _marker: std::marker::PhantomData, diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs index f7ffe2406ec5..656bd26620ec 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs @@ -105,7 +105,12 @@ pub(crate) fn display_terminator( Some(TerminatorInstruction::Jmp { destination, arguments, call_stack: _ }) => { writeln!(f, " jmp {}({})", destination, value_list(function, arguments)) } - Some(TerminatorInstruction::JmpIf { condition, then_destination, else_destination }) => { + Some(TerminatorInstruction::JmpIf { + condition, + then_destination, + else_destination, + call_stack: _, + }) => { writeln!( f, " jmpif {} then: {}, else: {}", diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/types.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/types.rs index 56729a5cba96..e467fa5400dc 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/types.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/types.rs @@ -1,3 +1,4 @@ +use serde::{Deserialize, Serialize}; use std::rc::Rc; use acvm::{acir::AcirField, FieldElement}; @@ -13,7 +14,7 @@ use crate::ssa::ssa_gen::SSA_WORD_SIZE; /// /// Fields do not have a notion of ordering, so this distinction /// is reasonable. -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd, Serialize, Deserialize)] pub enum NumericType { Signed { bit_size: u32 }, Unsigned { bit_size: u32 }, @@ -65,7 +66,7 @@ impl NumericType { } /// All types representable in the IR. -#[derive(Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)] +#[derive(Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd, Serialize, Deserialize)] pub(crate) enum Type { /// Represents numeric types in the IR, including field elements Numeric(NumericType), @@ -109,6 +110,11 @@ impl Type { Type::unsigned(8) } + /// Creates the str type, of the given length N + pub(crate) fn str(length: usize) -> Type { + Type::Array(Rc::new(vec![Type::char()]), length) + } + /// Creates the native field type. pub(crate) fn field() -> Type { Type::Numeric(NumericType::NativeField) diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/value.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/value.rs index c83609dec1fb..795d45c75e96 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/value.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/value.rs @@ -1,4 +1,5 @@ use acvm::FieldElement; +use serde::{Deserialize, Serialize}; use crate::ssa::ir::basic_block::BasicBlockId; @@ -13,7 +14,7 @@ pub(crate) type ValueId = Id; /// Value is the most basic type allowed in the IR. /// Transition Note: A Id is similar to `NodeId` in our previous IR. -#[derive(Debug, PartialEq, Eq, Hash, Clone)] +#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)] pub(crate) enum Value { /// This value was created due to an instruction /// diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/die.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/die.rs index 42383680f445..24519d530ee8 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/die.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/die.rs @@ -34,8 +34,8 @@ impl Ssa { /// of its instructions are needed elsewhere. fn dead_instruction_elimination(function: &mut Function) { let mut context = Context::default(); - if let Some(call_data) = function.dfg.data_bus.call_data { - context.mark_used_instruction_results(&function.dfg, call_data); + for call_data in &function.dfg.data_bus.call_data { + context.mark_used_instruction_results(&function.dfg, call_data.array_id); } let blocks = PostOrder::with_function(function); diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs index 4deb21ef712c..288e41cb994b 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs @@ -214,6 +214,7 @@ struct Context<'f> { pub(crate) struct Store { old_value: ValueId, new_value: ValueId, + call_stack: CallStack, } #[derive(Clone)] @@ -239,6 +240,8 @@ struct ConditionalContext { then_branch: ConditionalBranch, // First block of the else branch else_branch: Option, + // Call stack where the final location is that of the entire `if` expression + call_stack: CallStack, } fn flatten_function_cfg(function: &mut Function) { @@ -289,7 +292,8 @@ impl<'f> Context<'f> { if let Some(context) = self.condition_stack.last() { let previous_branch = context.else_branch.as_ref().unwrap_or(&context.then_branch); let and = Instruction::binary(BinaryOp::And, previous_branch.condition, condition); - self.insert_instruction(and, CallStack::new()) + let call_stack = self.inserter.function.dfg.get_value_call_stack(condition); + self.insert_instruction(and, call_stack) } else { condition } @@ -333,9 +337,20 @@ impl<'f> Context<'f> { ) -> Vec { let terminator = self.inserter.function.dfg[block].unwrap_terminator().clone(); match &terminator { - TerminatorInstruction::JmpIf { condition, then_destination, else_destination } => { + TerminatorInstruction::JmpIf { + condition, + then_destination, + else_destination, + call_stack, + } => { self.arguments_stack.push(vec![]); - self.if_start(condition, then_destination, else_destination, &block) + self.if_start( + condition, + then_destination, + else_destination, + &block, + call_stack.clone(), + ) } TerminatorInstruction::Jmp { destination, arguments, call_stack: _ } => { let arguments = vecmap(arguments.clone(), |value| self.inserter.resolve(value)); @@ -370,6 +385,7 @@ impl<'f> Context<'f> { then_destination: &BasicBlockId, else_destination: &BasicBlockId, if_entry: &BasicBlockId, + call_stack: CallStack, ) -> Vec { // manage conditions let old_condition = *condition; @@ -389,6 +405,7 @@ impl<'f> Context<'f> { entry_block: *if_entry, then_branch: branch, else_branch: None, + call_stack, }; self.condition_stack.push(cond_context); self.insert_current_side_effects_enabled(); @@ -400,8 +417,12 @@ impl<'f> Context<'f> { let mut cond_context = self.condition_stack.pop().unwrap(); cond_context.then_branch.last_block = *block; - let else_condition = - self.insert_instruction(Instruction::Not(cond_context.condition), CallStack::new()); + let condition_call_stack = + self.inserter.function.dfg.get_value_call_stack(cond_context.condition); + let else_condition = self.insert_instruction( + Instruction::Not(cond_context.condition), + condition_call_stack.clone(), + ); let else_condition = self.link_condition(else_condition); // Make sure the else branch sees the previous values of each store @@ -504,14 +525,16 @@ impl<'f> Context<'f> { else_condition: cond_context.else_branch.as_ref().unwrap().condition, else_value: else_arg, }; + let call_stack = cond_context.call_stack.clone(); self.inserter .function .dfg - .insert_instruction_and_results(instruction, block, None, CallStack::new()) + .insert_instruction_and_results(instruction, block, None, call_stack) .first() }); - self.merge_stores(cond_context.then_branch, cond_context.else_branch); + let call_stack = cond_context.call_stack; + self.merge_stores(cond_context.then_branch, cond_context.else_branch, call_stack); self.arguments_stack.pop(); self.arguments_stack.pop(); self.arguments_stack.push(args); @@ -538,13 +561,14 @@ impl<'f> Context<'f> { &mut self, instruction: Instruction, ctrl_typevars: Option>, + call_stack: CallStack, ) -> InsertInstructionResult { let block = self.inserter.function.entry_block(); self.inserter.function.dfg.insert_instruction_and_results( instruction, block, ctrl_typevars, - CallStack::new(), + call_stack, ) } @@ -561,7 +585,8 @@ impl<'f> Context<'f> { } }; let enable_side_effects = Instruction::EnableSideEffects { condition }; - self.insert_instruction_with_typevars(enable_side_effects, None); + let call_stack = self.inserter.function.dfg.get_value_call_stack(condition); + self.insert_instruction_with_typevars(enable_side_effects, None, call_stack); } /// Merge any store instructions found in each branch. @@ -573,6 +598,7 @@ impl<'f> Context<'f> { &mut self, then_branch: ConditionalBranch, else_branch: Option, + call_stack: CallStack, ) { // Address -> (then_value, else_value, value_before_the_if) let mut new_map = BTreeMap::new(); @@ -608,11 +634,9 @@ impl<'f> Context<'f> { else_condition, else_value: *else_case, }; - let value = self - .inserter - .function - .dfg - .insert_instruction_and_results(instruction, block, None, CallStack::new()) + let dfg = &mut self.inserter.function.dfg; + let value = dfg + .insert_instruction_and_results(instruction, block, None, call_stack.clone()) .first(); new_values.insert(address, value); @@ -622,18 +646,28 @@ impl<'f> Context<'f> { for (address, (_, _, old_value)) in &new_map { let value = new_values[address]; let address = *address; - self.insert_instruction_with_typevars(Instruction::Store { address, value }, None); + self.insert_instruction_with_typevars( + Instruction::Store { address, value }, + None, + call_stack.clone(), + ); if let Some(store) = self.store_values.get_mut(&address) { store.new_value = value; } else { - self.store_values - .insert(address, Store { old_value: *old_value, new_value: value }); + self.store_values.insert( + address, + Store { + old_value: *old_value, + new_value: value, + call_stack: call_stack.clone(), + }, + ); } } } - fn remember_store(&mut self, address: ValueId, new_value: ValueId) { + fn remember_store(&mut self, address: ValueId, new_value: ValueId, call_stack: CallStack) { if !self.local_allocations.contains(&address) { if let Some(store_value) = self.store_values.get_mut(&address) { store_value.new_value = new_value; @@ -641,10 +675,11 @@ impl<'f> Context<'f> { let load = Instruction::Load { address }; let load_type = Some(vec![self.inserter.function.dfg.type_of_value(new_value)]); - let old_value = - self.insert_instruction_with_typevars(load.clone(), load_type).first(); + let old_value = self + .insert_instruction_with_typevars(load.clone(), load_type, call_stack.clone()) + .first(); - self.store_values.insert(address, Store { old_value, new_value }); + self.store_values.insert(address, Store { old_value, new_value, call_stack }); } } } @@ -706,7 +741,7 @@ impl<'f> Context<'f> { Instruction::Constrain(lhs, rhs, message) } Instruction::Store { address, value } => { - self.remember_store(address, value); + self.remember_store(address, value, call_stack); Instruction::Store { address, value } } Instruction::RangeCheck { value, max_bit_size, assert_message } => { @@ -834,7 +869,9 @@ impl<'f> Context<'f> { for (address, store) in store_values { let address = *address; let value = store.old_value; - self.insert_instruction_with_typevars(Instruction::Store { address, value }, None); + let instruction = Instruction::Store { address, value }; + // Considering the location of undoing a store to be the same as the original store. + self.insert_instruction_with_typevars(instruction, None, store.call_stack.clone()); } } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/value_merger.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/value_merger.rs index de75d34565e9..90e24a1d5e31 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/value_merger.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/value_merger.rs @@ -20,6 +20,8 @@ pub(crate) struct ValueMerger<'a> { slice_sizes: &'a mut HashMap, array_set_conditionals: &'a mut HashMap, + + call_stack: CallStack, } impl<'a> ValueMerger<'a> { @@ -29,8 +31,16 @@ impl<'a> ValueMerger<'a> { slice_sizes: &'a mut HashMap, array_set_conditionals: &'a mut HashMap, current_condition: Option, + call_stack: CallStack, ) -> Self { - ValueMerger { dfg, block, slice_sizes, array_set_conditionals, current_condition } + ValueMerger { + dfg, + block, + slice_sizes, + array_set_conditionals, + current_condition, + call_stack, + } } /// Merge two values a and b from separate basic blocks to a single value. @@ -164,7 +174,12 @@ impl<'a> ValueMerger<'a> { let mut get_element = |array, typevars| { let get = Instruction::ArrayGet { array, index }; self.dfg - .insert_instruction_and_results(get, self.block, typevars, CallStack::new()) + .insert_instruction_and_results( + get, + self.block, + typevars, + self.call_stack.clone(), + ) .first() }; @@ -234,7 +249,7 @@ impl<'a> ValueMerger<'a> { get, self.block, typevars, - CallStack::new(), + self.call_stack.clone(), ) .first() } @@ -365,7 +380,12 @@ impl<'a> ValueMerger<'a> { let mut get_element = |array, typevars| { let get = Instruction::ArrayGet { array, index }; self.dfg - .insert_instruction_and_results(get, self.block, typevars, CallStack::new()) + .insert_instruction_and_results( + get, + self.block, + typevars, + self.call_stack.clone(), + ) .first() }; @@ -384,7 +404,12 @@ impl<'a> ValueMerger<'a> { } fn insert_instruction(&mut self, instruction: Instruction) -> InsertInstructionResult { - self.dfg.insert_instruction_and_results(instruction, self.block, None, CallStack::new()) + self.dfg.insert_instruction_and_results( + instruction, + self.block, + None, + self.call_stack.clone(), + ) } fn insert_array_set( @@ -399,7 +424,7 @@ impl<'a> ValueMerger<'a> { instruction, self.block, None, - CallStack::new(), + self.call_stack.clone(), ); if let Some(condition) = condition { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/inlining.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/inlining.rs index 09802713363b..d78399a3e6b8 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/inlining.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/inlining.rs @@ -625,9 +625,17 @@ impl<'function> PerFunctionContext<'function> { .terminate_with_jmp(destination, arguments); None } - TerminatorInstruction::JmpIf { condition, then_destination, else_destination } => { + TerminatorInstruction::JmpIf { + condition, + then_destination, + else_destination, + call_stack, + } => { let condition = self.translate_value(*condition); + let mut new_call_stack = self.context.call_stack.clone(); + new_call_stack.append(call_stack.clone()); + // See if the value of the condition is known, and if so only inline the reachable // branch. This lets us inline some recursive functions without recurring forever. let dfg = &mut self.context.builder.current_function.dfg; @@ -635,14 +643,19 @@ impl<'function> PerFunctionContext<'function> { Some(constant) => { let next_block = if constant.is_zero() { *else_destination } else { *then_destination }; + let next_block = self.translate_block(next_block, block_queue); - self.context.builder.terminate_with_jmp(next_block, vec![]); + self.context + .builder + .set_call_stack(new_call_stack) + .terminate_with_jmp(next_block, vec![]); } None => { let then_block = self.translate_block(*then_destination, block_queue); let else_block = self.translate_block(*else_destination, block_queue); self.context .builder + .set_call_stack(new_call_stack) .terminate_with_jmpif(condition, then_block, else_block); } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs index 1584b8485641..224060e131fb 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs @@ -158,6 +158,7 @@ impl Context { | Intrinsic::SliceRemove => true, Intrinsic::ArrayLen + | Intrinsic::ArrayAsStrUnchecked | Intrinsic::AssertConstant | Intrinsic::StaticAssert | Intrinsic::ApplyRangeConstraint diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs index 242eea7d6f4c..b1ca5fa25a07 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs @@ -68,12 +68,14 @@ impl Context { let typ = function.dfg.type_of_value(then_value); assert!(!matches!(typ, Type::Numeric(_))); + let call_stack = function.dfg.get_call_stack(instruction); let mut value_merger = ValueMerger::new( &mut function.dfg, block, &mut self.slice_sizes, &mut self.array_set_conditionals, Some(current_conditional), + call_stack, ); let value = value_merger.merge_values( @@ -229,6 +231,7 @@ fn slice_capacity_change( | Intrinsic::StaticAssert | Intrinsic::ApplyRangeConstraint | Intrinsic::ArrayLen + | Intrinsic::ArrayAsStrUnchecked | Intrinsic::StrAsBytes | Intrinsic::BlackBox(_) | Intrinsic::FromField diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/simplify_cfg.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/simplify_cfg.rs index 9d5d7879dcb4..0a3b2a800cad 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/simplify_cfg.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/simplify_cfg.rs @@ -15,7 +15,7 @@ use acvm::acir::AcirField; use crate::ssa::{ ir::{ - basic_block::BasicBlockId, cfg::ControlFlowGraph, dfg::CallStack, function::Function, + basic_block::BasicBlockId, cfg::ControlFlowGraph, function::Function, instruction::TerminatorInstruction, }, ssa_gen::Ssa, @@ -82,16 +82,20 @@ fn check_for_constant_jmpif( block: BasicBlockId, cfg: &mut ControlFlowGraph, ) { - if let Some(TerminatorInstruction::JmpIf { condition, then_destination, else_destination }) = - function.dfg[block].terminator() + if let Some(TerminatorInstruction::JmpIf { + condition, + then_destination, + else_destination, + call_stack, + }) = function.dfg[block].terminator() { if let Some(constant) = function.dfg.get_numeric_constant(*condition) { let destination = if constant.is_zero() { *else_destination } else { *then_destination }; let arguments = Vec::new(); - let jmp = - TerminatorInstruction::Jmp { destination, arguments, call_stack: CallStack::new() }; + let call_stack = call_stack.clone(); + let jmp = TerminatorInstruction::Jmp { destination, arguments, call_stack }; function.dfg[block].set_terminator(jmp); cfg.recompute_block(function, block); } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/unrolling.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/unrolling.rs index 5f58be414223..f3e11e04e3ac 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/unrolling.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/unrolling.rs @@ -258,7 +258,8 @@ fn get_induction_variable(function: &Function, block: BasicBlockId) -> Result Err(CallStack::new()), + Some(terminator) => Err(terminator.call_stack()), + None => Err(CallStack::new()), } } @@ -286,9 +287,9 @@ fn unroll_loop_header<'a>( context.inline_instructions_from_block(); match context.dfg()[fresh_block].unwrap_terminator() { - TerminatorInstruction::JmpIf { condition, then_destination, else_destination } => { + TerminatorInstruction::JmpIf { condition, then_destination, else_destination, call_stack } => { let condition = *condition; - let next_blocks = context.handle_jmpif(condition, *then_destination, *else_destination); + let next_blocks = context.handle_jmpif(condition, *then_destination, *else_destination, call_stack.clone()); // If there is only 1 next block the jmpif evaluated to a single known block. // This is the expected case and lets us know if we should loop again or not. @@ -392,9 +393,17 @@ impl<'f> LoopIteration<'f> { self.visited_blocks.insert(self.source_block); match self.inserter.function.dfg[self.insert_block].unwrap_terminator() { - TerminatorInstruction::JmpIf { condition, then_destination, else_destination } => { - self.handle_jmpif(*condition, *then_destination, *else_destination) - } + TerminatorInstruction::JmpIf { + condition, + then_destination, + else_destination, + call_stack, + } => self.handle_jmpif( + *condition, + *then_destination, + *else_destination, + call_stack.clone(), + ), TerminatorInstruction::Jmp { destination, arguments, call_stack: _ } => { if self.get_original_block(*destination) == self.loop_.header { assert_eq!(arguments.len(), 1); @@ -414,6 +423,7 @@ impl<'f> LoopIteration<'f> { condition: ValueId, then_destination: BasicBlockId, else_destination: BasicBlockId, + call_stack: CallStack, ) -> Vec { let condition = self.inserter.resolve(condition); @@ -425,11 +435,7 @@ impl<'f> LoopIteration<'f> { self.source_block = self.get_original_block(destination); let arguments = Vec::new(); - let jmp = TerminatorInstruction::Jmp { - destination, - arguments, - call_stack: CallStack::new(), - }; + let jmp = TerminatorInstruction::Jmp { destination, arguments, call_stack }; self.inserter.function.dfg.set_block_terminator(self.insert_block, jmp); vec![destination] } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs index 8e55debec1df..e16f6697c70e 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs @@ -242,7 +242,7 @@ impl<'a> FunctionContext<'a> { ast::Type::Integer(Signedness::Signed, bits) => Type::signed((*bits).into()), ast::Type::Integer(Signedness::Unsigned, bits) => Type::unsigned((*bits).into()), ast::Type::Bool => Type::unsigned(1), - ast::Type::String(len) => Type::Array(Rc::new(vec![Type::char()]), *len as usize), + ast::Type::String(len) => Type::str(*len as usize), ast::Type::FmtString(_, _) => { panic!("convert_non_tuple_type called on a fmt string: {typ}") } 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 abd251b008fb..468a85733071 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 @@ -44,7 +44,7 @@ pub(crate) fn generate_ssa( // see which parameter has call_data/return_data attribute let is_databus = DataBusBuilder::is_databus(&program.main_function_signature); - let is_return_data = matches!(program.return_visibility, Visibility::DataBus); + let is_return_data = matches!(program.return_visibility, Visibility::ReturnData); let return_location = program.return_location; let context = SharedContext::new(program); diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs index 7a77aa76101a..fe786da16ca3 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs @@ -2,6 +2,8 @@ use std::{collections::BTreeMap, fmt::Display}; use acvm::acir::circuit::ErrorSelector; use iter_extended::btree_map; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; use crate::ssa::ir::{ function::{Function, FunctionId, RuntimeType}, @@ -10,15 +12,23 @@ use crate::ssa::ir::{ use noirc_frontend::hir_def::types::Type as HirType; /// Contains the entire SSA representation of the program. +#[serde_as] +#[derive(Serialize, Deserialize)] pub(crate) struct Ssa { + #[serde_as(as = "Vec<(_, _)>")] pub(crate) functions: BTreeMap, pub(crate) main_id: FunctionId, + #[serde(skip)] pub(crate) next_id: AtomicCounter, /// Maps SSA entry point function ID -> Final generated ACIR artifact index. /// There can be functions specified in SSA which do not act as ACIR entry points. /// This mapping is necessary to use the correct function pointer for an ACIR call, /// as the final program artifact will be a list of only entry point functions. + #[serde(skip)] pub(crate) entry_point_to_generated_index: BTreeMap, + // We can skip serializing this field as the error selector types end up as part of the + // ABI not the actual SSA IR. + #[serde(skip)] pub(crate) error_selector_to_type: BTreeMap, } @@ -98,3 +108,43 @@ impl Display for Ssa { Ok(()) } } + +#[cfg(test)] +mod test { + use crate::ssa::ir::map::Id; + + use crate::ssa::ssa_gen::Ssa; + use crate::ssa::{ + function_builder::FunctionBuilder, + ir::{instruction::BinaryOp, types::Type}, + }; + + #[test] + fn serialization_roundtrip() { + let main_id = Id::test_new(0); + + // Compiling main + let mut builder = FunctionBuilder::new("main".into(), main_id); + let v0 = builder.add_parameter(Type::field()); + + let one = builder.field_constant(1u128); + let three = builder.field_constant(3u128); + + let v1 = builder.insert_binary(v0, BinaryOp::Add, one); + let v2 = builder.insert_binary(v1, BinaryOp::Mul, three); + builder.terminate_with_return(vec![v2]); + + let ssa = builder.finish(); + let serialized_ssa = &serde_json::to_string(&ssa).unwrap(); + let deserialized_ssa: Ssa = serde_json::from_str(serialized_ssa).unwrap(); + let actual_string = format!("{}", deserialized_ssa); + + let expected_string = "acir(inline) fn main f0 {\n \ + b0(v0: Field):\n \ + v3 = add v0, Field 1\n \ + v4 = mul v3, Field 3\n \ + return v4\n\ + }\n"; + assert_eq!(actual_string, expected_string); + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/Cargo.toml b/noir/noir-repo/compiler/noirc_frontend/Cargo.toml index f7439a092044..7ef8870eaa86 100644 --- a/noir/noir-repo/compiler/noirc_frontend/Cargo.toml +++ b/noir/noir-repo/compiler/noirc_frontend/Cargo.toml @@ -32,12 +32,12 @@ tracing.workspace = true petgraph = "0.6" rangemap = "1.4.0" lalrpop-util = { version = "0.20.2", features = ["lexer"] } +strum = "0.24" +strum_macros = "0.24" [dev-dependencies] base64.workspace = true -strum = "0.24" -strum_macros = "0.24" [build-dependencies] lalrpop = "0.20.2" 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 057daa2bdde4..aab995c49a15 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs @@ -7,14 +7,14 @@ use crate::ast::{ }; use crate::hir::def_collector::errors::DefCollectorErrorKind; use crate::macros_api::StructId; -use crate::node_interner::ExprId; +use crate::node_interner::{ExprId, QuotedTypeId}; use crate::token::{Attributes, Token, Tokens}; use crate::{Kind, Type}; use acvm::{acir::AcirField, FieldElement}; use iter_extended::vecmap; use noirc_errors::{Span, Spanned}; -use super::UnaryRhsMemberAccess; +use super::{AsTraitPath, UnaryRhsMemberAccess}; #[derive(Debug, PartialEq, Eq, Clone)] pub enum ExpressionKind { @@ -29,15 +29,14 @@ pub enum ExpressionKind { Cast(Box), Infix(Box), If(Box), - // The optional vec here is the optional list of generics - // provided by the turbofish operator, if used - Variable(Path, Option>), + Variable(Path), Tuple(Vec), Lambda(Box), Parenthesized(Box), Quote(Tokens), Unquote(Box), Comptime(BlockExpression, Span), + AsTraitPath(AsTraitPath), // This variant is only emitted when inlining the result of comptime // code. It is used to translate function values back into the AST while @@ -53,7 +52,16 @@ pub type UnresolvedGenerics = Vec; #[derive(Debug, PartialEq, Eq, Clone, Hash)] pub enum UnresolvedGeneric { Variable(Ident), - Numeric { ident: Ident, typ: UnresolvedType }, + Numeric { + ident: Ident, + typ: UnresolvedType, + }, + + /// Already-resolved generics can be parsed as generics when a macro + /// splices existing types into a generic list. In this case we have + /// to validate the type refers to a named generic and treat that + /// as a ResolvedGeneric when this is resolved. + Resolved(QuotedTypeId, Span), } impl UnresolvedGeneric { @@ -63,6 +71,7 @@ impl UnresolvedGeneric { UnresolvedGeneric::Numeric { ident, typ } => { ident.0.span().merge(typ.span.unwrap_or_default()) } + UnresolvedGeneric::Resolved(_, span) => *span, } } @@ -73,6 +82,9 @@ impl UnresolvedGeneric { let typ = self.resolve_numeric_kind_type(typ)?; Ok(Kind::Numeric(Box::new(typ))) } + UnresolvedGeneric::Resolved(..) => { + panic!("Don't know the kind of a resolved generic here") + } } } @@ -96,6 +108,7 @@ impl UnresolvedGeneric { pub(crate) fn ident(&self) -> &Ident { match self { UnresolvedGeneric::Variable(ident) | UnresolvedGeneric::Numeric { ident, .. } => ident, + UnresolvedGeneric::Resolved(..) => panic!("UnresolvedGeneric::Resolved no ident"), } } } @@ -105,6 +118,7 @@ impl Display for UnresolvedGeneric { match self { UnresolvedGeneric::Variable(ident) => write!(f, "{ident}"), UnresolvedGeneric::Numeric { ident, typ } => write!(f, "let {ident}: {typ}"), + UnresolvedGeneric::Resolved(..) => write!(f, "(resolved)"), } } } @@ -118,7 +132,7 @@ impl From for UnresolvedGeneric { impl ExpressionKind { pub fn into_path(self) -> Option { match self { - ExpressionKind::Variable(path, _) => Some(path), + ExpressionKind::Variable(path) => Some(path), _ => None, } } @@ -265,29 +279,9 @@ impl Expression { arguments: Vec, span: Span, ) -> Expression { - // Need to check if lhs is an if expression since users can sequence if expressions - // with tuples without calling them. E.g. `if c { t } else { e }(a, b)` is interpreted - // as a sequence of { if, tuple } rather than a function call. This behavior matches rust. - let kind = if matches!(&lhs.kind, ExpressionKind::If(..)) { - ExpressionKind::Block(BlockExpression { - statements: vec![ - Statement { kind: StatementKind::Expression(lhs), span }, - Statement { - kind: StatementKind::Expression(Expression::new( - ExpressionKind::Tuple(arguments), - span, - )), - span, - }, - ], - }) - } else { - ExpressionKind::Call(Box::new(CallExpression { - func: Box::new(lhs), - is_macro_call, - arguments, - })) - }; + let func = Box::new(lhs); + let kind = + ExpressionKind::Call(Box::new(CallExpression { func, is_macro_call, arguments })); Expression::new(kind, span) } } @@ -583,14 +577,7 @@ impl Display for ExpressionKind { Cast(cast) => cast.fmt(f), Infix(infix) => infix.fmt(f), If(if_expr) => if_expr.fmt(f), - Variable(path, generics) => { - if let Some(generics) = generics { - let generics = vecmap(generics, ToString::to_string); - write!(f, "{path}::<{}>", generics.join(", ")) - } else { - path.fmt(f) - } - } + Variable(path) => path.fmt(f), Constructor(constructor) => constructor.fmt(f), MemberAccess(access) => access.fmt(f), Tuple(elements) => { @@ -607,6 +594,7 @@ impl Display for ExpressionKind { let tokens = vecmap(&tokens.0, ToString::to_string); write!(f, "quote {{ {} }}", tokens.join(" ")) } + AsTraitPath(path) => write!(f, "{path}"), } } } @@ -766,6 +754,12 @@ impl Display for Lambda { } } +impl Display for AsTraitPath { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "<{} as {}>::{}", self.typ, self.trait_path, self.impl_item) + } +} + impl FunctionDefinition { pub fn normal( name: &Ident, 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 038a13529d72..8e27f0bdda90 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/mod.rs @@ -39,6 +39,18 @@ pub enum IntegerBitSize { SixtyFour, } +impl IntegerBitSize { + pub fn bit_size(&self) -> u8 { + match self { + IntegerBitSize::One => 1, + IntegerBitSize::Eight => 8, + IntegerBitSize::Sixteen => 16, + IntegerBitSize::ThirtyTwo => 32, + IntegerBitSize::SixtyFour => 64, + } + } +} + impl IntegerBitSize { pub fn allowed_sizes() -> Vec { vec![Self::One, Self::Eight, Self::ThirtyTwo, Self::SixtyFour] @@ -117,9 +129,13 @@ pub enum UnresolvedTypeData { /*env:*/ Box, ), - // The type of quoted code for metaprogramming + /// The type of quoted code for metaprogramming Quoted(crate::QuotedType), + /// An "as Trait" path leading to an associated type. + /// E.g. `::Bar` + AsTraitPath(Box), + /// An already resolved type. These can only be parsed if they were present in the token stream /// as a result of being spliced into a macro's token stream input. Resolved(QuotedTypeId), @@ -227,6 +243,7 @@ impl std::fmt::Display for UnresolvedTypeData { Unspecified => write!(f, "unspecified"), Parenthesized(typ) => write!(f, "({typ})"), Resolved(_) => write!(f, "(resolved type)"), + AsTraitPath(path) => write!(f, "{path}"), } } } @@ -291,12 +308,21 @@ impl UnresolvedTypeData { } } -#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)] +#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash, PartialOrd, Ord)] pub enum Signedness { Unsigned, Signed, } +impl Signedness { + pub fn is_signed(&self) -> bool { + match self { + Signedness::Unsigned => false, + Signedness::Signed => true, + } + } +} + impl UnresolvedTypeExpression { // This large error size is justified because it improves parsing speeds by around 40% in // release mode. See `ParserError` definition for further explanation. @@ -324,7 +350,7 @@ impl UnresolvedTypeExpression { Some(int) => Ok(UnresolvedTypeExpression::Constant(int, expr.span)), None => Err(expr), }, - ExpressionKind::Variable(path, _) => Ok(UnresolvedTypeExpression::Variable(path)), + ExpressionKind::Variable(path) => Ok(UnresolvedTypeExpression::Variable(path)), ExpressionKind::Prefix(prefix) if prefix.operator == UnaryOp::Minus => { let lhs = Box::new(UnresolvedTypeExpression::Constant(0, expr.span)); let rhs = Box::new(UnresolvedTypeExpression::from_expr_helper(prefix.rhs)?); @@ -390,7 +416,9 @@ pub enum Visibility { Private, /// DataBus is public input handled as private input. We use the fact that return values are properly computed by the program to avoid having them as public inputs /// it is useful for recursion and is handled by the proving system. - DataBus, + /// The u32 value is used to group inputs having the same value. + CallData(u32), + ReturnData, } impl std::fmt::Display for Visibility { @@ -398,7 +426,8 @@ impl std::fmt::Display for Visibility { match self { Self::Public => write!(f, "pub"), Self::Private => write!(f, "priv"), - Self::DataBus => write!(f, "databus"), + Self::CallData(id) => write!(f, "calldata{id}"), + Self::ReturnData => write!(f, "returndata"), } } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs index b41efebc9058..5d9a97fa6cf3 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs @@ -236,10 +236,11 @@ impl From for Expression { fn from(i: Ident) -> Expression { Expression { span: i.0.span(), - kind: ExpressionKind::Variable( - Path { span: i.span(), segments: vec![i], kind: PathKind::Plain }, - None, - ), + kind: ExpressionKind::Variable(Path { + span: i.span(), + segments: vec![PathSegment::from(i)], + kind: PathKind::Plain, + }), } } } @@ -357,23 +358,37 @@ impl UseTree { } } +/// A special kind of path in the form `::ident`. +/// Note that this path must consist of exactly two segments. +/// +/// An AsTraitPath may be used in either a type context where `ident` +/// refers to an associated type of a particular impl, or in a value +/// context where `ident` may refer to an associated constant or a +/// function within the impl. +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +pub struct AsTraitPath { + pub typ: UnresolvedType, + pub trait_path: Path, + pub impl_item: Ident, +} + // Note: Path deliberately doesn't implement Recoverable. // No matter which default value we could give in Recoverable::error, // it would most likely cause further errors during name resolution #[derive(Debug, PartialEq, Eq, Clone, Hash)] pub struct Path { - pub segments: Vec, + pub segments: Vec, pub kind: PathKind, pub span: Span, } impl Path { - pub fn pop(&mut self) -> Ident { + pub fn pop(&mut self) -> PathSegment { self.segments.pop().unwrap() } fn join(mut self, ident: Ident) -> Path { - self.segments.push(ident); + self.segments.push(PathSegment::from(ident)); self } @@ -384,18 +399,37 @@ impl Path { } pub fn from_ident(name: Ident) -> Path { - Path { span: name.span(), segments: vec![name], kind: PathKind::Plain } + Path { span: name.span(), segments: vec![PathSegment::from(name)], kind: PathKind::Plain } } pub fn span(&self) -> Span { self.span } - pub fn last_segment(&self) -> Ident { + pub fn first_segment(&self) -> PathSegment { + assert!(!self.segments.is_empty()); + self.segments.first().unwrap().clone() + } + + pub fn last_segment(&self) -> PathSegment { assert!(!self.segments.is_empty()); self.segments.last().unwrap().clone() } + pub fn last_ident(&self) -> Ident { + self.last_segment().ident + } + + pub fn first_name(&self) -> &str { + assert!(!self.segments.is_empty()); + &self.segments.first().unwrap().ident.0.contents + } + + pub fn last_name(&self) -> &str { + assert!(!self.segments.is_empty()); + &self.segments.last().unwrap().ident.0.contents + } + pub fn is_ident(&self) -> bool { self.segments.len() == 1 && self.kind == PathKind::Plain } @@ -404,14 +438,14 @@ impl Path { if !self.is_ident() { return None; } - self.segments.first() + self.segments.first().map(|segment| &segment.ident) } pub fn to_ident(&self) -> Option { if !self.is_ident() { return None; } - self.segments.first().cloned() + self.segments.first().cloned().map(|segment| segment.ident) } pub fn as_string(&self) -> String { @@ -421,19 +455,58 @@ impl Path { match segments.next() { None => panic!("empty segment"), Some(seg) => { - string.push_str(&seg.0.contents); + string.push_str(&seg.ident.0.contents); } } for segment in segments { string.push_str("::"); - string.push_str(&segment.0.contents); + string.push_str(&segment.ident.0.contents); } string } } +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +pub struct PathSegment { + pub ident: Ident, + pub generics: Option>, + pub span: Span, +} + +impl PathSegment { + /// Returns the span where turbofish happen. For example: + /// + /// foo:: + /// ~^^^^ + /// + /// Returns an empty span at the end of `foo` if there's no turbofish. + pub fn turbofish_span(&self) -> Span { + Span::from(self.ident.span().end()..self.span.end()) + } +} + +impl From for PathSegment { + fn from(ident: Ident) -> PathSegment { + let span = ident.span(); + PathSegment { ident, generics: None, span } + } +} + +impl Display for PathSegment { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.ident.fmt(f)?; + + if let Some(generics) = &self.generics { + let generics = vecmap(generics, ToString::to_string); + write!(f, "::<{}>", generics.join(", "))?; + } + + Ok(()) + } +} + #[derive(Debug, PartialEq, Eq, Clone)] pub struct LetStatement { pub pattern: Pattern, @@ -517,7 +590,7 @@ impl Recoverable for Pattern { impl LValue { fn as_expression(&self) -> Expression { let kind = match self { - LValue::Ident(ident) => ExpressionKind::Variable(Path::from_ident(ident.clone()), None), + LValue::Ident(ident) => ExpressionKind::Variable(Path::from_ident(ident.clone())), LValue::MemberAccess { object, field_name, span: _ } => { ExpressionKind::MemberAccess(Box::new(MemberAccessExpression { lhs: object.as_expression(), @@ -606,11 +679,12 @@ impl ForRange { }; // array.len() - let segments = vec![array_ident]; - let array_ident = ExpressionKind::Variable( - Path { segments, kind: PathKind::Plain, span: array_span }, - None, - ); + let segments = vec![PathSegment::from(array_ident)]; + let array_ident = ExpressionKind::Variable(Path { + segments, + kind: PathKind::Plain, + span: array_span, + }); let end_range = ExpressionKind::MethodCall(Box::new(MethodCallExpression { object: Expression::new(array_ident.clone(), array_span), @@ -626,11 +700,12 @@ impl ForRange { let fresh_identifier = Ident::new(index_name.clone(), array_span); // array[i] - let segments = vec![Ident::new(index_name, array_span)]; - let index_ident = ExpressionKind::Variable( - Path { segments, kind: PathKind::Plain, span: array_span }, - None, - ); + let segments = vec![PathSegment::from(Ident::new(index_name, array_span))]; + let index_ident = ExpressionKind::Variable(Path { + segments, + kind: PathKind::Plain, + span: array_span, + }); let loop_element = ExpressionKind::Index(Box::new(IndexExpression { collection: Expression::new(array_ident, array_span), diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/structure.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/structure.rs index 112747e09fba..732cbee92328 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/structure.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/structure.rs @@ -14,7 +14,6 @@ pub struct NoirStruct { pub generics: UnresolvedGenerics, pub fields: Vec<(Ident, UnresolvedType)>, pub span: Span, - pub is_comptime: bool, } impl Display for NoirStruct { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/traits.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/traits.rs index b23fbaede61e..f8f8ef667b42 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/traits.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/traits.rs @@ -53,7 +53,6 @@ pub struct TypeImpl { pub generics: UnresolvedGenerics, pub where_clause: Vec, pub methods: Vec<(NoirFunction, Span)>, - pub is_comptime: bool, } /// Ast node for an implementation of a trait for a particular type @@ -70,8 +69,6 @@ pub struct NoirTraitImpl { pub where_clause: Vec, pub items: Vec, - - pub is_comptime: bool, } /// Represents a simple trait constraint such as `where Foo: TraitY` diff --git a/noir/noir-repo/compiler/noirc_frontend/src/debug/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/debug/mod.rs index 443267380b54..598ffed14335 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/debug/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/debug/mod.rs @@ -1,3 +1,4 @@ +use crate::ast::PathSegment; use crate::parser::{parse_program, ParsedModule}; use crate::{ ast, @@ -171,14 +172,11 @@ impl DebugInstrumenter { let last_stmt = if has_ret_expr { ast::Statement { kind: ast::StatementKind::Expression(ast::Expression { - kind: ast::ExpressionKind::Variable( - ast::Path { - segments: vec![ident("__debug_expr", span)], - kind: PathKind::Plain, - span, - }, - None, - ), + kind: ast::ExpressionKind::Variable(ast::Path { + segments: vec![PathSegment::from(ident("__debug_expr", span))], + kind: PathKind::Plain, + span, + }), span, }), span, @@ -571,14 +569,11 @@ fn build_assign_var_stmt(var_id: SourceVarId, expr: ast::Expression) -> ast::Sta let span = expr.span; let kind = ast::ExpressionKind::Call(Box::new(ast::CallExpression { func: Box::new(ast::Expression { - kind: ast::ExpressionKind::Variable( - ast::Path { - segments: vec![ident("__debug_var_assign", span)], - kind: PathKind::Plain, - span, - }, - None, - ), + kind: ast::ExpressionKind::Variable(ast::Path { + segments: vec![PathSegment::from(ident("__debug_var_assign", span))], + kind: PathKind::Plain, + span, + }), span, }), is_macro_call: false, @@ -590,14 +585,11 @@ fn build_assign_var_stmt(var_id: SourceVarId, expr: ast::Expression) -> ast::Sta fn build_drop_var_stmt(var_id: SourceVarId, span: Span) -> ast::Statement { let kind = ast::ExpressionKind::Call(Box::new(ast::CallExpression { func: Box::new(ast::Expression { - kind: ast::ExpressionKind::Variable( - ast::Path { - segments: vec![ident("__debug_var_drop", span)], - kind: PathKind::Plain, - span, - }, - None, - ), + kind: ast::ExpressionKind::Variable(ast::Path { + segments: vec![PathSegment::from(ident("__debug_var_drop", span))], + kind: PathKind::Plain, + span, + }), span, }), is_macro_call: false, @@ -618,14 +610,14 @@ fn build_assign_member_stmt( let span = expr.span; let kind = ast::ExpressionKind::Call(Box::new(ast::CallExpression { func: Box::new(ast::Expression { - kind: ast::ExpressionKind::Variable( - ast::Path { - segments: vec![ident(&format!["__debug_member_assign_{arity}"], span)], - kind: PathKind::Plain, + kind: ast::ExpressionKind::Variable(ast::Path { + segments: vec![PathSegment::from(ident( + &format!["__debug_member_assign_{arity}"], span, - }, - None, - ), + ))], + kind: PathKind::Plain, + span, + }), span, }), is_macro_call: false, @@ -642,14 +634,11 @@ fn build_assign_member_stmt( fn build_debug_call_stmt(fname: &str, fn_id: DebugFnId, span: Span) -> ast::Statement { let kind = ast::ExpressionKind::Call(Box::new(ast::CallExpression { func: Box::new(ast::Expression { - kind: ast::ExpressionKind::Variable( - ast::Path { - segments: vec![ident(&format!["__debug_fn_{fname}"], span)], - kind: PathKind::Plain, - span, - }, - None, - ), + kind: ast::ExpressionKind::Variable(ast::Path { + segments: vec![PathSegment::from(ident(&format!["__debug_fn_{fname}"], span))], + kind: PathKind::Plain, + span, + }), span, }), is_macro_call: false, @@ -712,10 +701,11 @@ fn ident(s: &str, span: Span) -> ast::Ident { fn id_expr(id: &ast::Ident) -> ast::Expression { ast::Expression { - kind: ast::ExpressionKind::Variable( - Path { segments: vec![id.clone()], kind: PathKind::Plain, span: id.span() }, - None, - ), + kind: ast::ExpressionKind::Variable(Path { + segments: vec![PathSegment::from(id.clone())], + kind: PathKind::Plain, + span: id.span(), + }), span: id.span(), } } 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 0cbd2db55dae..afa2e7fa7a8f 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/comptime.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/comptime.rs @@ -1,9 +1,30 @@ -use std::mem::replace; +use std::{collections::BTreeMap, fmt::Display}; + +use chumsky::Parser; +use fm::FileId; +use iter_extended::vecmap; +use noirc_errors::{Location, Span}; use crate::{ + hir::{ + comptime::{Interpreter, InterpreterError, Value}, + def_collector::{ + dc_crate::{ + CollectedItems, CompilationError, UnresolvedFunctions, UnresolvedStruct, + UnresolvedTrait, UnresolvedTraitImpl, + }, + dc_mod, + }, + resolution::errors::ResolverError, + }, hir_def::expr::HirIdent, - macros_api::Expression, - node_interner::{DependencyId, ExprId, FuncId}, + lexer::Lexer, + macros_api::{ + Expression, ExpressionKind, HirExpression, NodeInterner, SecondaryAttribute, StructId, + }, + node_interner::{DefinitionKind, DependencyId, FuncId, TraitId}, + parser::{self, TopLevelStatement}, + Type, TypeBindings, }; use super::{Elaborator, FunctionContext, ResolverMeta}; @@ -12,50 +33,47 @@ impl<'context> Elaborator<'context> { /// Elaborate an expression from the middle of a comptime scope. /// When this happens we require additional information to know /// what variables should be in scope. - pub fn elaborate_expression_from_comptime( - &mut self, - expr: Expression, - function: Option, - ) -> ExprId { - self.function_context.push(FunctionContext::default()); - let old_scope = self.scopes.end_function(); - self.scopes.start_function(); - let function_id = function.map(DependencyId::Function); - let old_item = replace(&mut self.current_item, function_id); - - // Note: recover_generics isn't good enough here because any existing generics - // should not be in scope of this new function - let old_generics = std::mem::take(&mut self.generics); - - let old_crate_and_module = function.map(|function| { - let meta = self.interner.function_meta(&function); - let old_crate = replace(&mut self.crate_id, meta.source_crate); - let old_module = replace(&mut self.local_module, meta.source_module); - self.introduce_generics_into_scope(meta.all_generics.clone()); - (old_crate, old_module) - }); - - self.populate_scope_from_comptime_scopes(); - let expr = self.elaborate_expression(expr).0; - - if let Some((old_crate, old_module)) = old_crate_and_module { - self.crate_id = old_crate; - self.local_module = old_module; + pub fn elaborate_item_from_comptime<'a, T>( + &'a mut self, + current_function: Option, + f: impl FnOnce(&mut Elaborator<'a>) -> T, + ) -> T { + // Create a fresh elaborator to ensure no state is changed from + // this elaborator + let mut elaborator = Elaborator::new( + self.interner, + self.def_maps, + self.crate_id, + self.debug_comptime_in_file, + self.enable_arithmetic_generics, + ); + + elaborator.function_context.push(FunctionContext::default()); + elaborator.scopes.start_function(); + + if let Some(function) = current_function { + let meta = elaborator.interner.function_meta(&function); + elaborator.current_item = Some(DependencyId::Function(function)); + elaborator.crate_id = meta.source_crate; + elaborator.local_module = meta.source_module; + elaborator.file = meta.source_file; + elaborator.introduce_generics_into_scope(meta.all_generics.clone()); } - self.generics = old_generics; - self.current_item = old_item; - self.scopes.end_function(); - self.scopes.0.push(old_scope); - self.check_and_pop_function_context(); - expr + elaborator.populate_scope_from_comptime_scopes(); + + let result = f(&mut elaborator); + elaborator.check_and_pop_function_context(); + + self.errors.append(&mut elaborator.errors); + result } fn populate_scope_from_comptime_scopes(&mut self) { // Take the comptime scope to be our runtime scope. // Iterate from global scope to the most local scope so that the // later definitions will naturally shadow the former. - for scope in &self.comptime_scopes { + for scope in &self.interner.comptime_scopes { for definition_id in scope.keys() { let definition = self.interner.definition(*definition_id); let name = definition.name.clone(); @@ -68,4 +86,326 @@ impl<'context> Elaborator<'context> { } } } + + pub(super) fn run_comptime_attributes_on_item( + &mut self, + attributes: &[SecondaryAttribute], + item: Value, + span: Span, + generated_items: &mut CollectedItems, + ) { + for attribute in attributes { + if let SecondaryAttribute::Custom(name) = attribute { + if let Err(error) = + self.run_comptime_attribute_on_item(name, item.clone(), span, generated_items) + { + self.errors.push(error); + } + } + } + } + + fn run_comptime_attribute_on_item( + &mut self, + attribute: &str, + item: Value, + span: Span, + generated_items: &mut CollectedItems, + ) -> Result<(), (CompilationError, FileId)> { + let location = Location::new(span, self.file); + let Some((function, arguments)) = Self::parse_attribute(attribute, self.file)? else { + // Do not issue an error if the attribute is unknown + return Ok(()); + }; + + // Elaborate the function, rolling back any errors generated in case it is unknown + let error_count = self.errors.len(); + let function = self.elaborate_expression(function).0; + self.errors.truncate(error_count); + + let definition_id = match self.interner.expression(&function) { + HirExpression::Ident(ident, _) => ident.id, + _ => return Ok(()), + }; + + let Some(definition) = self.interner.try_definition(definition_id) else { + // If there's no such function, don't return an error. + // This preserves backwards compatibility in allowing custom attributes that + // do not refer to comptime functions. + return Ok(()); + }; + + let DefinitionKind::Function(function) = definition.kind else { + return Err((ResolverError::NonFunctionInAnnotation { span }.into(), self.file)); + }; + + let mut interpreter = self.setup_interpreter(); + let mut arguments = + Self::handle_attribute_arguments(&mut interpreter, function, arguments, location) + .map_err(|error| { + let file = error.get_location().file; + (error.into(), file) + })?; + + arguments.insert(0, (item, location)); + + let value = interpreter + .call_function(function, arguments, TypeBindings::new(), location) + .map_err(|error| error.into_compilation_error_pair())?; + + if value != Value::Unit { + let items = value + .into_top_level_items(location, self.interner) + .map_err(|error| error.into_compilation_error_pair())?; + + self.add_items(items, generated_items, location); + } + + Ok(()) + } + + /// Parses an attribute in the form of a function call (e.g. `#[foo(a b, c d)]`) into + /// the function and quoted arguments called (e.g. `("foo", vec![(a b, location), (c d, location)])`) + #[allow(clippy::type_complexity)] + fn parse_attribute( + annotation: &str, + file: FileId, + ) -> Result)>, (CompilationError, FileId)> { + let (tokens, mut lexing_errors) = Lexer::lex(annotation); + if !lexing_errors.is_empty() { + return Err((lexing_errors.swap_remove(0).into(), file)); + } + + let expression = parser::expression() + .parse(tokens) + .map_err(|mut errors| (errors.swap_remove(0).into(), file))?; + + Ok(match expression.kind { + ExpressionKind::Call(call) => Some((*call.func, call.arguments)), + ExpressionKind::Variable(_) => Some((expression, Vec::new())), + _ => None, + }) + } + + fn handle_attribute_arguments( + interpreter: &mut Interpreter, + function: FuncId, + arguments: Vec, + location: Location, + ) -> Result, InterpreterError> { + let meta = interpreter.elaborator.interner.function_meta(&function); + let mut parameters = vecmap(&meta.parameters.0, |(_, typ, _)| typ.clone()); + + // Remove the initial parameter for the comptime item since that is not included + // in `arguments` at this point. + parameters.remove(0); + + // If the function is varargs, push the type of the last slice element N times + // to account for N extra arguments. + let modifiers = interpreter.elaborator.interner.function_modifiers(&function); + let is_varargs = modifiers.attributes.is_varargs(); + let varargs_type = if is_varargs { parameters.pop() } else { None }; + + let varargs_elem_type = varargs_type.as_ref().and_then(|t| t.slice_element_type()); + + let mut new_arguments = Vec::with_capacity(arguments.len()); + let mut varargs = im::Vector::new(); + + for (i, arg) in arguments.into_iter().enumerate() { + let param_type = parameters.get(i).or(varargs_elem_type).unwrap_or(&Type::Error); + + let mut push_arg = |arg| { + if i >= parameters.len() { + varargs.push_back(arg); + } else { + new_arguments.push((arg, location)); + } + }; + + if *param_type == Type::Quoted(crate::QuotedType::TraitDefinition) { + let trait_id = match arg.kind { + ExpressionKind::Variable(path) => interpreter + .elaborator + .resolve_trait_by_path(path) + .ok_or(InterpreterError::FailedToResolveTraitDefinition { location }), + _ => Err(InterpreterError::TraitDefinitionMustBeAPath { location }), + }?; + push_arg(Value::TraitDefinition(trait_id)); + } else { + let expr_id = interpreter.elaborator.elaborate_expression(arg).0; + push_arg(interpreter.evaluate(expr_id)?); + } + } + + if is_varargs { + let typ = varargs_type.unwrap_or(Type::Error); + new_arguments.push((Value::Slice(varargs, typ), location)); + } + + Ok(new_arguments) + } + + fn add_items( + &mut self, + items: Vec, + generated_items: &mut CollectedItems, + location: Location, + ) { + for item in items { + self.add_item(item, generated_items, location); + } + } + + fn add_item( + &mut self, + item: TopLevelStatement, + generated_items: &mut CollectedItems, + location: Location, + ) { + match item { + TopLevelStatement::Function(function) => { + let id = self.interner.push_empty_fn(); + let module = self.module_id(); + self.interner.push_function(id, &function.def, module, location); + let functions = vec![(self.local_module, id, function)]; + generated_items.functions.push(UnresolvedFunctions { + file_id: self.file, + functions, + trait_id: None, + self_type: None, + }); + } + TopLevelStatement::TraitImpl(mut trait_impl) => { + let methods = dc_mod::collect_trait_impl_functions( + self.interner, + &mut trait_impl, + self.crate_id, + self.file, + self.local_module, + ); + + generated_items.trait_impls.push(UnresolvedTraitImpl { + file_id: self.file, + module_id: self.local_module, + trait_generics: trait_impl.trait_generics, + trait_path: trait_impl.trait_name, + object_type: trait_impl.object_type, + methods, + generics: trait_impl.impl_generics, + where_clause: trait_impl.where_clause, + + // These last fields are filled in later + trait_id: None, + impl_id: None, + resolved_object_type: None, + resolved_generics: Vec::new(), + resolved_trait_generics: Vec::new(), + }); + } + TopLevelStatement::Global(global) => { + let (global, error) = dc_mod::collect_global( + self.interner, + self.def_maps.get_mut(&self.crate_id).unwrap(), + global, + self.file, + self.local_module, + self.crate_id, + ); + + generated_items.globals.push(global); + if let Some(error) = error { + self.errors.push(error); + } + } + // Assume that an error has already been issued + TopLevelStatement::Error => (), + + TopLevelStatement::Module(_) + | TopLevelStatement::Import(_) + | TopLevelStatement::Struct(_) + | TopLevelStatement::Trait(_) + | TopLevelStatement::Impl(_) + | TopLevelStatement::TypeAlias(_) + | TopLevelStatement::SubModule(_) => { + let item = item.to_string(); + let error = InterpreterError::UnsupportedTopLevelItemUnquote { item, location }; + self.errors.push(error.into_compilation_error_pair()); + } + } + } + + pub fn setup_interpreter<'local>(&'local mut self) -> Interpreter<'local, 'context> { + let current_function = match self.current_item { + Some(DependencyId::Function(function)) => Some(function), + _ => None, + }; + Interpreter::new(self, self.crate_id, current_function) + } + + pub(super) fn debug_comptime T>( + &mut self, + location: Location, + mut expr_f: F, + ) { + if Some(location.file) == self.debug_comptime_in_file { + let displayed_expr = expr_f(self.interner); + self.errors.push(( + InterpreterError::debug_evaluate_comptime(displayed_expr, location).into(), + location.file, + )); + } + } + + /// Run all the attributes on each item. The ordering is unspecified to users but currently + /// we run trait attributes first to (e.g.) register derive handlers before derive is + /// called on structs. + /// Returns any new items generated by attributes. + pub(super) fn run_attributes( + &mut self, + traits: &BTreeMap, + types: &BTreeMap, + functions: &[UnresolvedFunctions], + ) -> CollectedItems { + let mut generated_items = CollectedItems::default(); + + for (trait_id, trait_) in traits { + let attributes = &trait_.trait_def.attributes; + let item = Value::TraitDefinition(*trait_id); + let span = trait_.trait_def.span; + self.local_module = trait_.module_id; + self.file = trait_.file_id; + self.run_comptime_attributes_on_item(attributes, item, span, &mut generated_items); + } + + for (struct_id, struct_def) in types { + let attributes = &struct_def.struct_def.attributes; + let item = Value::StructDefinition(*struct_id); + let span = struct_def.struct_def.span; + self.local_module = struct_def.module_id; + self.file = struct_def.file_id; + self.run_comptime_attributes_on_item(attributes, item, span, &mut generated_items); + } + + self.run_attributes_on_functions(functions, &mut generated_items); + generated_items + } + + fn run_attributes_on_functions( + &mut self, + function_sets: &[UnresolvedFunctions], + generated_items: &mut CollectedItems, + ) { + for function_set in function_sets { + self.file = function_set.file_id; + self.self_type = function_set.self_type.clone(); + + for (local_module, function_id, function) in &function_set.functions { + self.local_module = *local_module; + let attributes = function.secondary_attributes(); + let item = Value::FunctionDefinition(*function_id); + let span = function.span(); + self.run_comptime_attributes_on_item(attributes, item, span, generated_items); + } + } + } } 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 853098ce9316..5ba448f890ea 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -35,11 +35,11 @@ use crate::{ use super::{Elaborator, LambdaContext}; impl<'context> Elaborator<'context> { - pub(super) fn elaborate_expression(&mut self, expr: Expression) -> (ExprId, Type) { + pub(crate) fn elaborate_expression(&mut self, expr: Expression) -> (ExprId, Type) { let (hir_expr, typ) = match expr.kind { ExpressionKind::Literal(literal) => self.elaborate_literal(literal, expr.span), ExpressionKind::Block(block) => self.elaborate_block(block), - ExpressionKind::Prefix(prefix) => return self.elaborate_prefix(*prefix), + ExpressionKind::Prefix(prefix) => return self.elaborate_prefix(*prefix, expr.span), ExpressionKind::Index(index) => self.elaborate_index(*index), ExpressionKind::Call(call) => self.elaborate_call(*call, expr.span), ExpressionKind::MethodCall(call) => self.elaborate_method_call(*call, expr.span), @@ -50,9 +50,7 @@ impl<'context> Elaborator<'context> { ExpressionKind::Cast(cast) => self.elaborate_cast(*cast, expr.span), ExpressionKind::Infix(infix) => return self.elaborate_infix(*infix, expr.span), ExpressionKind::If(if_) => self.elaborate_if(*if_), - ExpressionKind::Variable(variable, generics) => { - return self.elaborate_variable(variable, generics) - } + ExpressionKind::Variable(variable) => return self.elaborate_variable(variable), ExpressionKind::Tuple(tuple) => self.elaborate_tuple(tuple), ExpressionKind::Lambda(lambda) => self.elaborate_lambda(*lambda), ExpressionKind::Parenthesized(expr) => return self.elaborate_expression(*expr), @@ -66,6 +64,7 @@ impl<'context> Elaborator<'context> { self.push_err(ResolverError::UnquoteUsedOutsideQuote { span: expr.span }); (HirExpression::Error, Type::Error) } + ExpressionKind::AsTraitPath(_) => todo!("Implement AsTraitPath"), }; let id = self.interner.push_expr(hir_expr); self.interner.push_expr_location(id, expr.span, self.file); @@ -227,8 +226,7 @@ impl<'context> Elaborator<'context> { (HirExpression::Literal(HirLiteral::FmtStr(str, fmt_str_idents)), typ) } - fn elaborate_prefix(&mut self, prefix: PrefixExpression) -> (ExprId, Type) { - let span = prefix.rhs.span; + fn elaborate_prefix(&mut self, prefix: PrefixExpression, span: Span) -> (ExprId, Type) { let (rhs, rhs_type) = self.elaborate_expression(prefix.rhs); let trait_id = self.interner.get_prefix_operator_trait_method(&prefix.operator); @@ -352,7 +350,7 @@ impl<'context> Elaborator<'context> { &mut object, ); - self.resolve_turbofish_generics(&func_id, method_call.generics, span) + self.resolve_function_turbofish_generics(&func_id, method_call.generics, span) } else { None }; @@ -410,9 +408,12 @@ impl<'context> Elaborator<'context> { &mut self, constructor: ConstructorExpression, ) -> (HirExpression, Type) { + let exclude_last_segment = true; + self.check_unsupported_turbofish_usage(&constructor.type_name, exclude_last_segment); + let span = constructor.type_name.span(); let last_segment = constructor.type_name.last_segment(); - let is_self_type = last_segment.is_self_type_name(); + let is_self_type = last_segment.ident.is_self_type_name(); let (r#type, struct_generics) = if let Some(struct_id) = constructor.struct_type { let typ = self.interner.get_struct(struct_id); @@ -429,6 +430,15 @@ impl<'context> Elaborator<'context> { } }; + let turbofish_span = last_segment.turbofish_span(); + + let struct_generics = self.resolve_struct_turbofish_generics( + &r#type.borrow(), + struct_generics, + last_segment.generics, + turbofish_span, + ); + let struct_type = r#type.clone(); let generics = struct_generics.clone(); @@ -443,7 +453,7 @@ impl<'context> Elaborator<'context> { }); let struct_id = struct_type.borrow().id; - let reference_location = Location::new(last_segment.span(), self.file); + let reference_location = Location::new(last_segment.ident.span(), self.file); self.interner.add_struct_reference(struct_id, reference_location, is_self_type); (expr, Type::Struct(struct_type, generics)) @@ -544,7 +554,7 @@ impl<'context> Elaborator<'context> { fn elaborate_cast(&mut self, cast: CastExpression, span: Span) -> (HirExpression, Type) { let (lhs, lhs_type) = self.elaborate_expression(cast.lhs); let r#type = self.resolve_type(cast.r#type); - let result = self.check_cast(lhs_type, &r#type, span); + let result = self.check_cast(&lhs_type, &r#type, span); let expr = HirExpression::Cast(HirCastExpression { lhs, r#type }); (expr, result) } 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 e0affad1fbf9..e60308aaddd9 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs @@ -1,20 +1,16 @@ use std::{ collections::{BTreeMap, BTreeSet}, - fmt::Display, rc::Rc, }; use crate::{ ast::{FunctionKind, UnresolvedTraitConstraint}, hir::{ - comptime::{Interpreter, InterpreterError, Value}, - def_collector::{ - dc_crate::{ - filter_literal_globals, CompilationError, ImplMap, UnresolvedGlobal, - UnresolvedStruct, UnresolvedTypeAlias, - }, - dc_mod, + def_collector::dc_crate::{ + filter_literal_globals, CompilationError, ImplMap, UnresolvedGlobal, UnresolvedStruct, + UnresolvedTypeAlias, }, + def_map::DefMaps, resolution::{errors::ResolverError, path_resolver::PathResolver}, scope::ScopeForest as GenericScopeForest, type_check::TypeCheckError, @@ -25,18 +21,14 @@ use crate::{ traits::TraitConstraint, types::{Generics, Kind, ResolvedGeneric}, }, - lexer::Lexer, macros_api::{ BlockExpression, Ident, NodeInterner, NoirFunction, NoirStruct, Pattern, SecondaryAttribute, StructId, }, node_interner::{ - DefinitionId, DefinitionKind, DependencyId, ExprId, FuncId, GlobalId, ReferenceId, TraitId, - TypeAliasId, + DefinitionKind, DependencyId, ExprId, FuncId, GlobalId, ReferenceId, TraitId, TypeAliasId, }, - parser::TopLevelStatement, - token::Tokens, - Shared, Type, TypeBindings, TypeVariable, + Shared, Type, TypeVariable, }; use crate::{ ast::{TraitBound, UnresolvedGeneric, UnresolvedGenerics}, @@ -54,7 +46,7 @@ use crate::{ use crate::{ hir::{ def_collector::dc_crate::{UnresolvedFunctions, UnresolvedTraitImpl}, - def_map::{CrateDefMap, ModuleData}, + def_map::ModuleData, }, hir_def::traits::TraitImpl, macros_api::ItemVisibility, @@ -74,7 +66,6 @@ mod unquote; use fm::FileId; use iter_extended::vecmap; use noirc_errors::{Location, Span}; -use rustc_hash::FxHashMap as HashMap; use self::traits::check_trait_impl_method_matches_declaration; @@ -102,7 +93,7 @@ pub struct Elaborator<'context> { pub(crate) interner: &'context mut NodeInterner, - def_maps: &'context mut BTreeMap, + def_maps: &'context mut DefMaps, file: FileId, @@ -130,8 +121,6 @@ pub struct Elaborator<'context> { /// to the corresponding trait impl ID. current_trait_impl: Option, - trait_id: Option, - /// In-resolution names /// /// This needs to be a set because we can have multiple in-resolution @@ -165,11 +154,6 @@ pub struct Elaborator<'context> { crate_id: CrateId, - /// Each value currently in scope in the comptime interpreter. - /// Each element of the Vec represents a scope with every scope together making - /// up all currently visible definitions. The first scope is always the global scope. - pub(crate) comptime_scopes: Vec>, - /// The scope of --debug-comptime, or None if unset debug_comptime_in_file: Option, @@ -177,6 +161,9 @@ pub struct Elaborator<'context> { /// This map is used to lazily evaluate these globals if they're encountered before /// they are elaborated (e.g. in a function's type or another global's RHS). unresolved_globals: BTreeMap, + + /// Temporary flag to enable the experimental arithmetic generics feature + enable_arithmetic_generics: bool, } #[derive(Default)] @@ -195,41 +182,65 @@ struct FunctionContext { impl<'context> Elaborator<'context> { pub fn new( - context: &'context mut Context, + interner: &'context mut NodeInterner, + def_maps: &'context mut DefMaps, crate_id: CrateId, debug_comptime_in_file: Option, + enable_arithmetic_generics: bool, ) -> Self { Self { scopes: ScopeForest::default(), errors: Vec::new(), - interner: &mut context.def_interner, - def_maps: &mut context.def_maps, + interner, + def_maps, file: FileId::dummy(), nested_loops: 0, generics: Vec::new(), lambda_stack: Vec::new(), self_type: None, current_item: None, - trait_id: None, local_module: LocalModuleId::dummy_id(), crate_id, resolving_ids: BTreeSet::new(), trait_bounds: Vec::new(), function_context: vec![FunctionContext::default()], current_trait_impl: None, - comptime_scopes: vec![HashMap::default()], debug_comptime_in_file, unresolved_globals: BTreeMap::new(), + enable_arithmetic_generics, } } + pub fn from_context( + context: &'context mut Context, + crate_id: CrateId, + debug_comptime_in_file: Option, + enable_arithmetic_generics: bool, + ) -> Self { + Self::new( + &mut context.def_interner, + &mut context.def_maps, + crate_id, + debug_comptime_in_file, + enable_arithmetic_generics, + ) + } + pub fn elaborate( context: &'context mut Context, crate_id: CrateId, items: CollectedItems, debug_comptime_in_file: Option, + enable_arithmetic_generics: bool, ) -> Vec<(CompilationError, FileId)> { - Self::elaborate_and_return_self(context, crate_id, items, debug_comptime_in_file).errors + Self::elaborate_and_return_self( + context, + crate_id, + items, + debug_comptime_in_file, + enable_arithmetic_generics, + ) + .errors } pub fn elaborate_and_return_self( @@ -237,17 +248,16 @@ impl<'context> Elaborator<'context> { crate_id: CrateId, items: CollectedItems, debug_comptime_in_file: Option, + enable_arithmetic_generics: bool, ) -> Self { - let mut this = Self::new(context, crate_id, debug_comptime_in_file); - - // Filter out comptime items to execute their functions first if needed. - // This step is why comptime items can only refer to other comptime items - // in the same crate, but can refer to any item in dependencies. Trying to - // run these at the same time as other items would lead to them seeing empty - // function bodies from functions that have yet to be elaborated. - let (comptime_items, runtime_items) = Self::filter_comptime_items(items); - this.elaborate_items(comptime_items); - this.elaborate_items(runtime_items); + let mut this = Self::from_context( + context, + crate_id, + debug_comptime_in_file, + enable_arithmetic_generics, + ); + this.elaborate_items(items); + this.check_and_pop_function_context(); this } @@ -271,11 +281,11 @@ impl<'context> Elaborator<'context> { } // Must resolve structs before we resolve globals. - let mut generated_items = self.collect_struct_definitions(items.types); + self.collect_struct_definitions(&items.types); self.define_function_metas(&mut items.functions, &mut items.impls, &mut items.trait_impls); - self.collect_traits(items.traits, &mut generated_items); + self.collect_traits(&items.traits); // Before we resolve any function symbols we must go through our impls and // re-collect the methods within into their proper module. This cannot be @@ -299,7 +309,7 @@ impl<'context> Elaborator<'context> { // We have to run any comptime attributes on functions before the function is elaborated // since the generated items are checked beforehand as well. - self.run_attributes_on_functions(&items.functions, &mut generated_items); + let generated_items = self.run_attributes(&items.traits, &items.types, &items.functions); // After everything is collected, we can elaborate our generated items. // It may be better to inline these within `items` entirely since elaborating them @@ -336,17 +346,12 @@ impl<'context> Elaborator<'context> { } fn elaborate_functions(&mut self, functions: UnresolvedFunctions) { - self.file = functions.file_id; - self.trait_id = functions.trait_id; // TODO: Resolve? - self.self_type = functions.self_type; - - for (local_module, id, _) in functions.functions { - self.local_module = local_module; - self.recover_generics(|this| this.elaborate_function(id)); + for (_, id, _) in functions.functions { + self.elaborate_function(id); } + self.generics.clear(); self.self_type = None; - self.trait_id = None; } fn introduce_generics_into_scope(&mut self, all_generics: Vec) { @@ -364,7 +369,7 @@ impl<'context> Elaborator<'context> { self.generics = all_generics; } - fn elaborate_function(&mut self, id: FuncId) { + pub(crate) fn elaborate_function(&mut self, id: FuncId) { let func_meta = self.interner.func_meta.get_mut(&id); let func_meta = func_meta.expect("FuncMetas should be declared before a function is elaborated"); @@ -377,11 +382,21 @@ impl<'context> Elaborator<'context> { FunctionBody::Resolving => return, }; + let func_meta = func_meta.clone(); + + assert_eq!( + self.crate_id, func_meta.source_crate, + "Functions in other crates should be already elaborated" + ); + + self.local_module = func_meta.source_module; + self.file = func_meta.source_file; + self.self_type = func_meta.self_type.clone(); + self.current_trait_impl = func_meta.trait_impl; + self.scopes.start_function(); let old_item = std::mem::replace(&mut self.current_item, Some(DependencyId::Function(id))); - let func_meta = func_meta.clone(); - self.trait_bounds = func_meta.trait_constraints.clone(); self.function_context.push(FunctionContext::default()); @@ -508,35 +523,72 @@ impl<'context> Elaborator<'context> { /// Each generic will have a fresh Shared associated with it. pub fn add_generics(&mut self, generics: &UnresolvedGenerics) -> Generics { vecmap(generics, |generic| { - // Map the generic to a fresh type variable - let id = self.interner.next_type_variable_id(); - let typevar = TypeVariable::unbound(id); - let ident = generic.ident(); - let span = ident.0.span(); + let mut is_error = false; + let (type_var, name, kind) = match self.resolve_generic(generic) { + Ok(values) => values, + Err(error) => { + self.push_err(error); + is_error = true; + let id = self.interner.next_type_variable_id(); + (TypeVariable::unbound(id), Rc::new("(error)".into()), Kind::Normal) + } + }; - // Resolve the generic's kind - let kind = self.resolve_generic_kind(generic); + let span = generic.span(); + let name_owned = name.as_ref().clone(); + let resolved_generic = ResolvedGeneric { name, type_var, kind, span }; // Check for name collisions of this generic - let name = Rc::new(ident.0.contents.clone()); - - let resolved_generic = - ResolvedGeneric { name: name.clone(), type_var: typevar.clone(), kind, span }; - - if let Some(generic) = self.find_generic(&name) { - self.push_err(ResolverError::DuplicateDefinition { - name: ident.0.contents.clone(), - first_span: generic.span, - second_span: span, - }); - } else { - self.generics.push(resolved_generic.clone()); + // Checking `is_error` here prevents DuplicateDefinition errors when + // we have multiple generics from macros which fail to resolve and + // are all given the same default name "(error)". + if !is_error { + if let Some(generic) = self.find_generic(&name_owned) { + self.push_err(ResolverError::DuplicateDefinition { + name: name_owned, + first_span: generic.span, + second_span: span, + }); + } else { + self.generics.push(resolved_generic.clone()); + } } resolved_generic }) } + fn resolve_generic( + &mut self, + generic: &UnresolvedGeneric, + ) -> Result<(TypeVariable, Rc, Kind), ResolverError> { + // Map the generic to a fresh type variable + match generic { + UnresolvedGeneric::Variable(_) | UnresolvedGeneric::Numeric { .. } => { + let id = self.interner.next_type_variable_id(); + let typevar = TypeVariable::unbound(id); + let ident = generic.ident(); + + let kind = self.resolve_generic_kind(generic); + let name = Rc::new(ident.0.contents.clone()); + Ok((typevar, name, kind)) + } + // An already-resolved generic is only possible if it is the result of a + // previous macro call being inserted into a generics list. + UnresolvedGeneric::Resolved(id, span) => { + match self.interner.get_quoted_type(*id).follow_bindings() { + Type::NamedGeneric(type_variable, name, kind) => { + Ok((type_variable, name, kind)) + } + other => Err(ResolverError::MacroResultInGenericsListNotAGeneric { + span: *span, + typ: other.clone(), + }), + } + } + } + } + /// Return the kind of an unresolved generic. /// If a numeric generic has been specified, resolve the annotated type to make /// sure only primitive numeric types are being used. @@ -571,6 +623,21 @@ impl<'context> Elaborator<'context> { } } + pub fn resolve_module_by_path(&self, path: Path) -> Option { + let path_resolver = StandardPathResolver::new(self.module_id()); + + match path_resolver.resolve(self.def_maps, path.clone(), &mut None) { + Ok(PathResolution { module_def_id: ModuleDefId::ModuleId(module_id), error }) => { + if error.is_some() { + None + } else { + Some(module_id) + } + } + _ => None, + } + } + fn resolve_trait_by_path(&mut self, path: Path) -> Option { let path_resolver = StandardPathResolver::new(self.module_id()); @@ -608,7 +675,11 @@ impl<'context> Elaborator<'context> { self.resolve_trait_bound(&constraint.trait_bound, typ) } - fn resolve_trait_bound(&mut self, bound: &TraitBound, typ: Type) -> Option { + pub fn resolve_trait_bound( + &mut self, + bound: &TraitBound, + typ: Type, + ) -> Option { let the_trait = self.lookup_trait_or_error(bound.trait_path.clone())?; let resolved_generics = &the_trait.generics.clone(); @@ -774,6 +845,8 @@ impl<'context> Elaborator<'context> { source_crate: self.crate_id, source_module: self.local_module, function_body: FunctionBody::Unresolved(func.kind, body, func.def.span), + self_type: self.self_type.clone(), + source_file: self.file, }; self.interner.push_fn_meta(meta, func_id); @@ -812,7 +885,11 @@ impl<'context> Elaborator<'context> { /// Since they should be within a child module, they should be elaborated as if /// `in_contract` is `false` so we can still resolve them in the parent module without them being in a contract. fn in_contract(&self) -> bool { - self.module_id().module(self.def_maps).is_contract + self.module_is_contract(self.module_id()) + } + + pub(crate) fn module_is_contract(&self, module_id: ModuleId) -> bool { + module_id.module(self.def_maps).is_contract } fn is_entry_point_function(&self, func: &NoirFunction, in_contract: bool) -> bool { @@ -953,7 +1030,16 @@ impl<'context> Elaborator<'context> { self.collect_trait_impl_methods(trait_id, trait_impl, &where_clause); - let span = trait_impl.object_type.span.expect("All trait self types should have spans"); + let span = trait_impl.object_type.span; + + let span = if let Some(span) = span { + span + } else if self.interner.is_in_lsp_mode() { + // A span might not be set if this was generated by a macro + Default::default() + } else { + span.expect("All trait self types should have spans") + }; self.declare_methods_on_struct(true, &mut trait_impl.methods, span); let methods = trait_impl.methods.function_ids(); @@ -964,7 +1050,7 @@ impl<'context> Elaborator<'context> { let trait_generics = trait_impl.resolved_trait_generics.clone(); let resolved_trait_impl = Shared::new(TraitImpl { - ident: trait_impl.trait_path.last_segment().clone(), + ident: trait_impl.trait_path.last_ident(), typ: self_type.clone(), trait_id, trait_generics: trait_generics.clone(), @@ -1002,10 +1088,12 @@ impl<'context> Elaborator<'context> { self.self_type = None; } - fn get_module_mut( - def_maps: &mut BTreeMap, - module: ModuleId, - ) -> &mut ModuleData { + pub fn get_module(&self, module: ModuleId) -> &ModuleData { + let message = "A crate should always be present for a given crate id"; + &self.def_maps.get(&module.krate).expect(message).modules[module.local_id.0] + } + + fn get_module_mut(def_maps: &mut DefMaps, module: ModuleId) -> &mut ModuleData { let message = "A crate should always be present for a given crate id"; &mut def_maps.get_mut(&module.krate).expect(message).modules[module.local_id.0] } @@ -1102,30 +1190,20 @@ impl<'context> Elaborator<'context> { self.generics.clear(); } - fn collect_struct_definitions( - &mut self, - structs: BTreeMap, - ) -> CollectedItems { + fn collect_struct_definitions(&mut self, structs: &BTreeMap) { // This is necessary to avoid cloning the entire struct map // when adding checks after each struct field is resolved. let struct_ids = structs.keys().copied().collect::>(); - // This will contain any additional top-level items that are generated at compile-time - // via macros. This often includes derived trait impls. - let mut generated_items = CollectedItems::default(); - // Resolve each field in each struct. // Each struct should already be present in the NodeInterner after def collection. - for (type_id, mut typ) in structs { + for (type_id, typ) in structs { self.file = typ.file_id; self.local_module = typ.module_id; - let attributes = std::mem::take(&mut typ.struct_def.attributes); - let span = typ.struct_def.span; - - let fields = self.resolve_struct_fields(typ.struct_def, type_id); + let fields = self.resolve_struct_fields(&typ.struct_def, *type_id); let fields_len = fields.len(); - self.interner.update_struct(type_id, |struct_def| { + self.interner.update_struct(*type_id, |struct_def| { struct_def.set_fields(fields); // TODO(https://github.com/noir-lang/noir/issues/5156): Remove this with implicit numeric generics @@ -1152,12 +1230,11 @@ impl<'context> Elaborator<'context> { }); for field_index in 0..fields_len { - self.interner - .add_definition_location(ReferenceId::StructMember(type_id, field_index), None); + self.interner.add_definition_location( + ReferenceId::StructMember(*type_id, field_index), + None, + ); } - - let item = Value::StructDefinition(type_id); - self.run_comptime_attributes_on_item(&attributes, item, span, &mut generated_items); } // Check whether the struct fields have nested slices @@ -1179,125 +1256,11 @@ impl<'context> Elaborator<'context> { } } } - - generated_items - } - - fn run_comptime_attributes_on_item( - &mut self, - attributes: &[SecondaryAttribute], - item: Value, - span: Span, - generated_items: &mut CollectedItems, - ) { - for attribute in attributes { - if let SecondaryAttribute::Custom(name) = attribute { - if let Err(error) = - self.run_comptime_attribute_on_item(name, item.clone(), span, generated_items) - { - self.errors.push(error); - } - } - } - } - - fn run_comptime_attribute_on_item( - &mut self, - attribute: &str, - item: Value, - span: Span, - generated_items: &mut CollectedItems, - ) -> Result<(), (CompilationError, FileId)> { - let location = Location::new(span, self.file); - let (function_name, mut arguments) = Self::parse_attribute(attribute, location) - .unwrap_or_else(|| (attribute.to_string(), Vec::new())); - - let Ok(id) = self.lookup_global(Path::from_single(function_name, span)) else { - // Do not issue an error if the attribute is unknown - return Ok(()); - }; - - let definition = self.interner.definition(id); - let DefinitionKind::Function(function) = definition.kind else { - return Err((ResolverError::NonFunctionInAnnotation { span }.into(), self.file)); - }; - - self.handle_varargs_attribute(function, &mut arguments, location); - arguments.insert(0, (item, location)); - - let mut interpreter = self.setup_interpreter(); - - let value = interpreter - .call_function(function, arguments, TypeBindings::new(), location) - .map_err(|error| error.into_compilation_error_pair())?; - - if value != Value::Unit { - let items = value - .into_top_level_items(location) - .map_err(|error| error.into_compilation_error_pair())?; - - self.add_items(items, generated_items, location); - } - - Ok(()) - } - - /// Parses an attribute in the form of a function call (e.g. `#[foo(a b, c d)]`) into - /// the function and quoted arguments called (e.g. `("foo", vec![(a b, location), (c d, location)])`) - fn parse_attribute( - annotation: &str, - location: Location, - ) -> Option<(String, Vec<(Value, Location)>)> { - let (tokens, errors) = Lexer::lex(annotation); - if !errors.is_empty() { - return None; - } - - let mut tokens = tokens.0; - if tokens.len() >= 4 { - // Remove the outer `ident ( )` wrapping the function arguments - let first = tokens.remove(0).into_token(); - let second = tokens.remove(0).into_token(); - - // Last token is always an EndOfInput - let _ = tokens.pop().unwrap().into_token(); - let last = tokens.pop().unwrap().into_token(); - - use crate::lexer::token::Token::*; - if let (Ident(name), LeftParen, RightParen) = (first, second, last) { - let args = tokens.split(|token| *token.token() == Comma); - let args = - vecmap(args, |arg| (Value::Code(Rc::new(Tokens(arg.to_vec()))), location)); - return Some((name, args)); - } - } - - None - } - - /// Checks if the given attribute function is a varargs function. - /// If so, we should pass its arguments in one slice rather than as separate arguments. - fn handle_varargs_attribute( - &mut self, - function: FuncId, - arguments: &mut Vec<(Value, Location)>, - location: Location, - ) { - let meta = self.interner.function_meta(&function); - let parameters = &meta.parameters.0; - - // If the last parameter is a slice, this is a varargs function. - if parameters.last().map_or(false, |(_, typ, _)| matches!(typ, Type::Slice(_))) { - let typ = Type::Slice(Box::new(Type::Quoted(crate::QuotedType::Quoted))); - let slice_elements = arguments.drain(..).map(|(value, _)| value); - let slice = Value::Slice(slice_elements.collect(), typ); - arguments.push((slice, location)); - } } pub fn resolve_struct_fields( &mut self, - unresolved: NoirStruct, + unresolved: &NoirStruct, struct_id: StructId, ) -> Vec<(Ident, Type)> { self.recover_generics(|this| { @@ -1308,7 +1271,9 @@ impl<'context> Elaborator<'context> { let struct_def = this.interner.get_struct(struct_id); this.add_existing_generics(&unresolved.generics, &struct_def.borrow().generics); - let fields = vecmap(unresolved.fields, |(ident, typ)| (ident, this.resolve_type(typ))); + let fields = vecmap(&unresolved.fields, |(ident, typ)| { + (ident.clone(), this.resolve_type(typ.clone())) + }); this.resolving_ids.remove(&struct_id); @@ -1417,6 +1382,11 @@ impl<'context> Elaborator<'context> { self.add_generics(&trait_impl.generics); trait_impl.resolved_generics = self.generics.clone(); + for (_, _, method) in trait_impl.methods.functions.iter_mut() { + // Attach any trait constraints on the impl to the function + method.def.where_clause.append(&mut trait_impl.where_clause.clone()); + } + // Fetch trait constraints here let trait_generics = trait_impl .trait_id @@ -1442,7 +1412,7 @@ impl<'context> Elaborator<'context> { self.generics.clear(); if let Some(trait_id) = trait_id { - let trait_name = trait_impl.trait_path.last_segment(); + let trait_name = trait_impl.trait_path.last_ident(); self.interner.add_trait_reference( trait_id, Location::new(trait_name.span(), trait_impl.file_id), @@ -1486,191 +1456,4 @@ impl<'context> Elaborator<'context> { _ => true, }) } - - /// Filters out comptime items from non-comptime items. - /// Returns a pair of (comptime items, non-comptime items) - fn filter_comptime_items(mut items: CollectedItems) -> (CollectedItems, CollectedItems) { - let mut function_sets = Vec::with_capacity(items.functions.len()); - let mut comptime_function_sets = Vec::new(); - - for function_set in items.functions { - let mut functions = Vec::with_capacity(function_set.functions.len()); - let mut comptime_functions = Vec::new(); - - for function in function_set.functions { - if function.2.def.is_comptime { - comptime_functions.push(function); - } else { - functions.push(function); - } - } - - let file_id = function_set.file_id; - let self_type = function_set.self_type; - let trait_id = function_set.trait_id; - - if !comptime_functions.is_empty() { - comptime_function_sets.push(UnresolvedFunctions { - functions: comptime_functions, - file_id, - trait_id, - self_type: self_type.clone(), - }); - } - - function_sets.push(UnresolvedFunctions { functions, file_id, trait_id, self_type }); - } - - let (comptime_trait_impls, trait_impls) = - items.trait_impls.into_iter().partition(|trait_impl| trait_impl.is_comptime); - - let (comptime_structs, structs) = - items.types.into_iter().partition(|typ| typ.1.struct_def.is_comptime); - - let comptime = CollectedItems { - functions: comptime_function_sets, - types: comptime_structs, - type_aliases: BTreeMap::new(), - traits: BTreeMap::new(), - trait_impls: comptime_trait_impls, - globals: Vec::new(), - impls: rustc_hash::FxHashMap::default(), - }; - - items.functions = function_sets; - items.trait_impls = trait_impls; - items.types = structs; - (comptime, items) - } - - fn add_items( - &mut self, - items: Vec, - generated_items: &mut CollectedItems, - location: Location, - ) { - for item in items { - self.add_item(item, generated_items, location); - } - } - - fn add_item( - &mut self, - item: TopLevelStatement, - generated_items: &mut CollectedItems, - location: Location, - ) { - match item { - TopLevelStatement::Function(function) => { - let id = self.interner.push_empty_fn(); - let module = self.module_id(); - self.interner.push_function(id, &function.def, module, location); - let functions = vec![(self.local_module, id, function)]; - generated_items.functions.push(UnresolvedFunctions { - file_id: self.file, - functions, - trait_id: None, - self_type: None, - }); - } - TopLevelStatement::TraitImpl(mut trait_impl) => { - let methods = dc_mod::collect_trait_impl_functions( - self.interner, - &mut trait_impl, - self.crate_id, - self.file, - self.local_module, - ); - - generated_items.trait_impls.push(UnresolvedTraitImpl { - file_id: self.file, - module_id: self.local_module, - trait_generics: trait_impl.trait_generics, - trait_path: trait_impl.trait_name, - object_type: trait_impl.object_type, - methods, - generics: trait_impl.impl_generics, - where_clause: trait_impl.where_clause, - is_comptime: trait_impl.is_comptime, - - // These last fields are filled in later - trait_id: None, - impl_id: None, - resolved_object_type: None, - resolved_generics: Vec::new(), - resolved_trait_generics: Vec::new(), - }); - } - TopLevelStatement::Global(global) => { - let (global, error) = dc_mod::collect_global( - self.interner, - self.def_maps.get_mut(&self.crate_id).unwrap(), - global, - self.file, - self.local_module, - self.crate_id, - ); - - generated_items.globals.push(global); - if let Some(error) = error { - self.errors.push(error); - } - } - // Assume that an error has already been issued - TopLevelStatement::Error => (), - - TopLevelStatement::Module(_) - | TopLevelStatement::Import(_) - | TopLevelStatement::Struct(_) - | TopLevelStatement::Trait(_) - | TopLevelStatement::Impl(_) - | TopLevelStatement::TypeAlias(_) - | TopLevelStatement::SubModule(_) => { - let item = item.to_string(); - let error = InterpreterError::UnsupportedTopLevelItemUnquote { item, location }; - self.errors.push(error.into_compilation_error_pair()); - } - } - } - - pub fn setup_interpreter<'local>(&'local mut self) -> Interpreter<'local, 'context> { - let current_function = match self.current_item { - Some(DependencyId::Function(function)) => Some(function), - _ => None, - }; - Interpreter::new(self, self.crate_id, current_function) - } - - fn debug_comptime T>( - &mut self, - location: Location, - mut expr_f: F, - ) { - if Some(location.file) == self.debug_comptime_in_file { - let displayed_expr = expr_f(self.interner); - self.errors.push(( - InterpreterError::debug_evaluate_comptime(displayed_expr, location).into(), - location.file, - )); - } - } - - fn run_attributes_on_functions( - &mut self, - function_sets: &[UnresolvedFunctions], - generated_items: &mut CollectedItems, - ) { - for function_set in function_sets { - self.file = function_set.file_id; - self.self_type = function_set.self_type.clone(); - - for (local_module, function_id, function) in &function_set.functions { - self.local_module = *local_module; - let attributes = function.secondary_attributes(); - let item = Value::FunctionDefinition(*function_id); - let span = function.span(); - self.run_comptime_attributes_on_item(attributes, item, span, generated_items); - } - } - } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/patterns.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/patterns.rs index e24b6a3a067a..bd44e087e704 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/patterns.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/patterns.rs @@ -15,7 +15,7 @@ use crate::{ }, macros_api::{HirExpression, Ident, Path, Pattern}, node_interner::{DefinitionId, DefinitionKind, ExprId, FuncId, GlobalId, TraitImplKind}, - Shared, StructType, Type, TypeBindings, + ResolvedGeneric, Shared, StructType, Type, TypeBindings, }; use super::{Elaborator, ResolverMeta}; @@ -39,7 +39,7 @@ impl<'context> Elaborator<'context> { /// Equivalent to `elaborate_pattern`, this version just also /// adds any new DefinitionIds that were created to the given Vec. - pub(super) fn elaborate_pattern_and_store_ids( + pub fn elaborate_pattern_and_store_ids( &mut self, pattern: Pattern, expected_type: Type, @@ -157,8 +157,12 @@ impl<'context> Elaborator<'context> { mutable: Option, new_definitions: &mut Vec, ) -> HirPattern { - let name_span = name.last_segment().span(); - let is_self_type = name.last_segment().is_self_type_name(); + let exclude_last_segment = true; + self.check_unsupported_turbofish_usage(&name, exclude_last_segment); + + let last_segment = name.last_segment(); + let name_span = last_segment.ident.span(); + let is_self_type = last_segment.ident.is_self_type_name(); let error_identifier = |this: &mut Self| { // Must create a name here to return a HirPattern::Identifier. Allowing @@ -178,6 +182,15 @@ impl<'context> Elaborator<'context> { } }; + let turbofish_span = last_segment.turbofish_span(); + + let generics = self.resolve_struct_turbofish_generics( + &struct_type.borrow(), + generics, + last_segment.generics, + turbofish_span, + ); + let actual_type = Type::Struct(struct_type.clone(), generics); let location = Location::new(span, self.file); @@ -404,7 +417,7 @@ impl<'context> Elaborator<'context> { } /// Resolve generics using the expected kinds of the function we are calling - pub(super) fn resolve_turbofish_generics( + pub(super) fn resolve_function_turbofish_generics( &mut self, func_id: &FuncId, unresolved_turbofish: Option>, @@ -412,28 +425,61 @@ impl<'context> Elaborator<'context> { ) -> Option> { let direct_generics = self.interner.function_meta(func_id).direct_generics.clone(); - unresolved_turbofish.map(|option_inner| { - if option_inner.len() != direct_generics.len() { + unresolved_turbofish.map(|unresolved_turbofish| { + if unresolved_turbofish.len() != direct_generics.len() { let type_check_err = TypeCheckError::IncorrectTurbofishGenericCount { expected_count: direct_generics.len(), - actual_count: option_inner.len(), + actual_count: unresolved_turbofish.len(), span, }; self.push_err(type_check_err); } - let generics_with_types = direct_generics.iter().zip(option_inner); - vecmap(generics_with_types, |(generic, unresolved_type)| { - self.resolve_type_inner(unresolved_type, &generic.kind) - }) + self.resolve_turbofish_generics(&direct_generics, unresolved_turbofish) }) } - pub(super) fn elaborate_variable( + pub(super) fn resolve_struct_turbofish_generics( &mut self, - variable: Path, + struct_type: &StructType, + generics: Vec, unresolved_turbofish: Option>, - ) -> (ExprId, Type) { + span: Span, + ) -> Vec { + let Some(turbofish_generics) = unresolved_turbofish else { + return generics; + }; + + if turbofish_generics.len() != generics.len() { + self.push_err(TypeCheckError::GenericCountMismatch { + item: format!("struct {}", struct_type.name), + expected: generics.len(), + found: turbofish_generics.len(), + span, + }); + return generics; + } + + self.resolve_turbofish_generics(&struct_type.generics, turbofish_generics) + } + + pub(super) fn resolve_turbofish_generics( + &mut self, + generics: &[ResolvedGeneric], + turbofish_generics: Vec, + ) -> Vec { + let generics_with_types = generics.iter().zip(turbofish_generics); + vecmap(generics_with_types, |(generic, unresolved_type)| { + self.resolve_type_inner(unresolved_type, &generic.kind) + }) + } + + pub(super) fn elaborate_variable(&mut self, variable: Path) -> (ExprId, Type) { + let exclude_last_segment = true; + self.check_unsupported_turbofish_usage(&variable, exclude_last_segment); + + let unresolved_turbofish = variable.segments.last().unwrap().generics.clone(); + let span = variable.span; let expr = self.resolve_variable(variable); let definition_id = expr.id; @@ -445,7 +491,7 @@ impl<'context> Elaborator<'context> { // and if the turbofish operator was used. let generics = definition_kind.and_then(|definition_kind| match &definition_kind { DefinitionKind::Function(function) => { - self.resolve_turbofish_generics(function, unresolved_turbofish, span) + self.resolve_function_turbofish_generics(function, unresolved_turbofish, span) } _ => None, }); @@ -648,7 +694,7 @@ impl<'context> Elaborator<'context> { } pub fn get_ident_from_path(&mut self, path: Path) -> (HirIdent, usize) { - let location = Location::new(path.last_segment().span(), self.file); + let location = Location::new(path.last_ident().span(), self.file); let error = match path.as_ident().map(|ident| self.use_variable(ident)) { Some(Ok(found)) => return found, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/scope.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/scope.rs index 23638b03cf54..b2367e0cf0e9 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/scope.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/scope.rs @@ -1,6 +1,6 @@ use noirc_errors::{Location, Spanned}; -use crate::ast::ERROR_IDENT; +use crate::ast::{PathKind, ERROR_IDENT}; use crate::hir::def_map::{LocalModuleId, ModuleId}; use crate::hir::resolution::path_resolver::{PathResolver, StandardPathResolver}; use crate::hir::scope::{Scope as GenericScope, ScopeTree as GenericScopeTree}; @@ -43,11 +43,38 @@ impl<'context> Elaborator<'context> { } pub(super) fn resolve_path(&mut self, path: Path) -> Result { - let resolver = StandardPathResolver::new(self.module_id()); + let mut module_id = self.module_id(); + let mut path = path; + + if path.kind == PathKind::Plain && path.first_name() == SELF_TYPE_NAME { + if let Some(Type::Struct(struct_type, _)) = &self.self_type { + let struct_type = struct_type.borrow(); + if path.segments.len() == 1 { + return Ok(ModuleDefId::TypeId(struct_type.id)); + } + + module_id = struct_type.id.module_id(); + path = Path { + segments: path.segments[1..].to_vec(), + kind: PathKind::Plain, + span: path.span(), + }; + } + } + + self.resolve_path_in_module(path, module_id) + } + + fn resolve_path_in_module( + &mut self, + path: Path, + module_id: ModuleId, + ) -> Result { + let resolver = StandardPathResolver::new(module_id); let path_resolution; - if self.interner.track_references { - let last_segment = path.last_segment(); + if self.interner.lsp_mode { + let last_segment = path.last_ident(); let location = Location::new(last_segment.span(), self.file); let is_self_type_name = last_segment.is_self_type_name(); @@ -55,14 +82,14 @@ impl<'context> Elaborator<'context> { path_resolution = resolver.resolve(self.def_maps, path.clone(), &mut Some(&mut references))?; - for (referenced, ident) in references.iter().zip(path.segments) { + for (referenced, segment) in references.iter().zip(path.segments) { let Some(referenced) = referenced else { continue; }; self.interner.add_reference( *referenced, - Location::new(ident.span(), self.file), - ident.is_self_type_name(), + Location::new(segment.ident.span(), self.file), + segment.ident.is_self_type_name(), ); } @@ -144,12 +171,12 @@ impl<'context> Elaborator<'context> { pub fn push_scope(&mut self) { self.scopes.start_scope(); - self.comptime_scopes.push(Default::default()); + self.interner.comptime_scopes.push(Default::default()); } pub fn pop_scope(&mut self) { let scope = self.scopes.end_scope(); - self.comptime_scopes.pop(); + self.interner.comptime_scopes.pop(); self.check_for_unused_variables_in_scope_tree(scope.into()); } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/traits.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/traits.rs index a00e770218ed..1e48fdd07e7c 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/traits.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/traits.rs @@ -8,9 +8,7 @@ use crate::{ FunctionKind, TraitItem, UnresolvedGeneric, UnresolvedGenerics, UnresolvedTraitConstraint, }, hir::{ - def_collector::dc_crate::{ - CollectedItems, CompilationError, UnresolvedTrait, UnresolvedTraitImpl, - }, + def_collector::dc_crate::{CompilationError, UnresolvedTrait, UnresolvedTraitImpl}, type_check::TypeCheckError, }, hir_def::{ @@ -29,14 +27,10 @@ use crate::{ use super::Elaborator; impl<'context> Elaborator<'context> { - pub fn collect_traits( - &mut self, - traits: BTreeMap, - generated_items: &mut CollectedItems, - ) { + pub fn collect_traits(&mut self, traits: &BTreeMap) { for (trait_id, unresolved_trait) in traits { self.recover_generics(|this| { - let resolved_generics = this.interner.get_trait(trait_id).generics.clone(); + let resolved_generics = this.interner.get_trait(*trait_id).generics.clone(); this.add_existing_generics( &unresolved_trait.trait_def.generics, &resolved_generics, @@ -44,28 +38,23 @@ impl<'context> Elaborator<'context> { // Resolve order // 1. Trait Types ( Trait constants can have a trait type, therefore types before constants) - let _ = this.resolve_trait_types(&unresolved_trait); + let _ = this.resolve_trait_types(unresolved_trait); // 2. Trait Constants ( Trait's methods can use trait types & constants, therefore they should be after) - let _ = this.resolve_trait_constants(&unresolved_trait); + let _ = this.resolve_trait_constants(unresolved_trait); // 3. Trait Methods - let methods = this.resolve_trait_methods(trait_id, &unresolved_trait); + let methods = this.resolve_trait_methods(*trait_id, unresolved_trait); - this.interner.update_trait(trait_id, |trait_def| { + this.interner.update_trait(*trait_id, |trait_def| { trait_def.set_methods(methods); }); - - let attributes = &unresolved_trait.trait_def.attributes; - let item = crate::hir::comptime::Value::TraitDefinition(trait_id); - let span = unresolved_trait.trait_def.span; - this.run_comptime_attributes_on_item(attributes, item, span, generated_items); }); // This check needs to be after the trait's methods are set since // the interner may set `interner.ordering_type` based on the result type // of the Cmp trait, if this is it. if self.crate_id.is_stdlib() { - self.interner.try_add_infix_operator_trait(trait_id); - self.interner.try_add_prefix_operator_trait(trait_id); + self.interner.try_add_infix_operator_trait(*trait_id); + self.interner.try_add_prefix_operator_trait(*trait_id); } } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs index d5dbb170843d..f8ba994f66b7 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs @@ -41,7 +41,7 @@ pub const WILDCARD_TYPE: &str = "_"; impl<'context> Elaborator<'context> { /// Translates an UnresolvedType to a Type with a `TypeKind::Normal` - pub(super) fn resolve_type(&mut self, typ: UnresolvedType) -> Type { + pub(crate) fn resolve_type(&mut self, typ: UnresolvedType) -> Type { let span = typ.span; let resolved_type = self.resolve_type_inner(typ, &Kind::Normal); if resolved_type.is_nested_slice() { @@ -61,8 +61,8 @@ impl<'context> Elaborator<'context> { let (named_path_span, is_self_type_name, is_synthetic) = if let Named(ref named_path, _, synthetic) = typ.typ { ( - Some(named_path.last_segment().span()), - named_path.last_segment().is_self_type_name(), + Some(named_path.last_ident().span()), + named_path.last_ident().is_self_type_name(), synthetic, ) } else { @@ -155,6 +155,7 @@ impl<'context> Elaborator<'context> { } Parenthesized(typ) => self.resolve_type_inner(*typ, kind), Resolved(id) => self.interner.get_quoted_type(id).clone(), + AsTraitPath(_) => todo!("Resolve AsTraitPath"), }; if let Some(unresolved_span) = typ.span { @@ -221,7 +222,7 @@ impl<'context> Elaborator<'context> { // Check if the path is a type variable first. We currently disallow generics on type // variables since we do not support higher-kinded types. if path.segments.len() == 1 { - let name = &path.last_segment().0.contents; + let name = path.last_name(); if name == SELF_TYPE_NAME { if let Some(self_type) = self.self_type.clone() { @@ -239,6 +240,7 @@ impl<'context> Elaborator<'context> { if let Some(type_alias) = self.lookup_type_alias(path.clone()) { let type_alias = type_alias.borrow(); + let actual_generic_count = args.len(); let expected_generic_count = type_alias.generics.len(); let type_alias_string = type_alias.to_string(); let id = type_alias.id; @@ -247,9 +249,13 @@ impl<'context> Elaborator<'context> { self.resolve_type_inner(arg, &generic.kind) }); - self.verify_generics_count(expected_generic_count, &mut args, span, || { - type_alias_string - }); + self.verify_generics_count( + expected_generic_count, + actual_generic_count, + &mut args, + span, + || type_alias_string, + ); if let Some(item) = self.current_item { self.interner.add_type_alias_dependency(item, id); @@ -279,6 +285,8 @@ impl<'context> Elaborator<'context> { } let expected_generic_count = struct_type.borrow().generics.len(); + let actual_generic_count = args.len(); + if !self.in_contract() && self .interner @@ -296,9 +304,13 @@ impl<'context> Elaborator<'context> { self.resolve_type_inner(arg, &generic.kind) }); - self.verify_generics_count(expected_generic_count, &mut args, span, || { - struct_type.borrow().to_string() - }); + self.verify_generics_count( + expected_generic_count, + actual_generic_count, + &mut args, + span, + || struct_type.borrow().to_string(), + ); if let Some(current_item) = self.current_item { let dependency_id = struct_type.borrow().id; @@ -333,15 +345,16 @@ impl<'context> Elaborator<'context> { fn verify_generics_count( &mut self, expected_count: usize, + actual_count: usize, args: &mut Vec, span: Span, type_name: impl FnOnce() -> String, ) { - if args.len() != expected_count { + if actual_count != expected_count { self.push_err(ResolverError::IncorrectGenericCount { span, item_name: type_name(), - actual: args.len(), + actual: actual_count, expected: expected_count, }); @@ -352,7 +365,7 @@ impl<'context> Elaborator<'context> { pub fn lookup_generic_or_global_type(&mut self, path: &Path) -> Option { if path.segments.len() == 1 { - let name = &path.last_segment().0.contents; + let name = path.last_name(); if let Some(generic) = self.find_generic(name) { let generic = generic.clone(); return Some(Type::NamedGeneric(generic.type_var, generic.name, generic.kind)); @@ -391,13 +404,16 @@ impl<'context> Elaborator<'context> { match (lhs, rhs) { (Type::Constant(lhs), Type::Constant(rhs)) => { - Type::Constant(op.function()(lhs, rhs)) + Type::Constant(op.function(lhs, rhs)) } - (lhs, _) => { - let span = - if !matches!(lhs, Type::Constant(_)) { lhs_span } else { rhs_span }; - self.push_err(ResolverError::InvalidArrayLengthExpr { span }); - Type::Constant(0) + (lhs, rhs) => { + if !self.enable_arithmetic_generics { + let span = + if !matches!(lhs, Type::Constant(_)) { lhs_span } else { rhs_span }; + self.push_err(ResolverError::InvalidArrayLengthExpr { span }); + } + + Type::InfixExpr(Box::new(lhs), op, Box::new(rhs)).canonicalize() } } } @@ -412,11 +428,12 @@ impl<'context> Elaborator<'context> { &mut self, path: &Path, ) -> Option<(TraitMethodId, TraitConstraint, bool)> { - let trait_id = self.trait_id?; + let trait_impl = self.current_trait_impl?; + let trait_id = self.interner.try_get_trait_implementation(trait_impl)?.borrow().trait_id; if path.kind == PathKind::Plain && path.segments.len() == 2 { - let name = &path.segments[0].0.contents; - let method = &path.segments[1]; + let name = &path.segments[0].ident.0.contents; + let method = &path.segments[1].ident; if name == SELF_TYPE_NAME { let the_trait = self.interner.get_trait(trait_id); @@ -449,7 +466,7 @@ impl<'context> Elaborator<'context> { let meta = self.interner.function_meta(&func_id); let trait_id = meta.trait_id?; let the_trait = self.interner.get_trait(trait_id); - let method = the_trait.find_method(&path.last_segment().0.contents)?; + let method = the_trait.find_method(path.last_name())?; let constraint = TraitConstraint { typ: Type::TypeVariable(the_trait.self_type_typevar.clone(), TypeVariableKind::Normal), trait_generics: Type::from_generics(&vecmap(&the_trait.generics, |generic| { @@ -477,14 +494,12 @@ impl<'context> Elaborator<'context> { for constraint in self.trait_bounds.clone() { if let Type::NamedGeneric(_, name, _) = &constraint.typ { // if `path` is `T::method_name`, we're looking for constraint of the form `T: SomeTrait` - if path.segments[0].0.contents != name.as_str() { + if path.segments[0].ident.0.contents != name.as_str() { continue; } let the_trait = self.interner.get_trait(constraint.trait_id); - if let Some(method) = - the_trait.find_method(path.segments.last().unwrap().0.contents.as_str()) - { + if let Some(method) = the_trait.find_method(path.last_name()) { return Some((method, constraint, true)); } } @@ -768,7 +783,7 @@ impl<'context> Elaborator<'context> { } } - pub(super) fn check_cast(&mut self, from: Type, to: &Type, span: Span) -> Type { + pub(super) fn check_cast(&mut self, from: &Type, to: &Type, span: Span) -> Type { match from.follow_bindings() { Type::Integer(..) | Type::FieldElement @@ -777,8 +792,13 @@ impl<'context> Elaborator<'context> { | Type::Bool => (), Type::TypeVariable(_, _) => { - self.push_err(TypeCheckError::TypeAnnotationsNeeded { span }); - return Type::Error; + // NOTE: in reality the expected type can also include bool, but for the compiler's simplicity + // we only allow integer types. If a bool is in `from` it will need an explicit type annotation. + let expected = Type::polymorphic_integer_or_field(self.interner); + self.unify(from, &expected, || TypeCheckError::InvalidCast { + from: from.clone(), + span, + }); } Type::Error => return Type::Error, from => { @@ -1211,37 +1231,7 @@ impl<'context> Elaborator<'context> { None } Type::NamedGeneric(_, _, _) => { - let func_id = match self.current_item { - Some(DependencyId::Function(id)) => id, - _ => panic!("unexpected method outside a function"), - }; - let func_meta = self.interner.function_meta(&func_id); - - for constraint in &func_meta.trait_constraints { - if *object_type == constraint.typ { - if let Some(the_trait) = self.interner.try_get_trait(constraint.trait_id) { - for (method_index, method) in the_trait.methods.iter().enumerate() { - if method.name.0.contents == method_name { - let trait_method = TraitMethodId { - trait_id: constraint.trait_id, - method_index, - }; - return Some(HirMethodReference::TraitMethodId( - trait_method, - constraint.trait_generics.clone(), - )); - } - } - } - } - } - - self.push_err(TypeCheckError::UnresolvedMethodCall { - method_name: method_name.to_string(), - object_type: object_type.clone(), - span, - }); - None + self.lookup_method_in_trait_constraints(object_type, method_name, span) } // Mutable references to another type should resolve to methods of their element type. // This may be a struct or a primitive type. @@ -1264,17 +1254,53 @@ impl<'context> Elaborator<'context> { other => match self.interner.lookup_primitive_method(&other, method_name) { Some(method_id) => Some(HirMethodReference::FuncId(method_id)), None => { - self.push_err(TypeCheckError::UnresolvedMethodCall { - method_name: method_name.to_string(), - object_type: object_type.clone(), - span, - }); - None + // It could be that this type is a composite type that is bound to a trait, + // for example `x: (T, U) ... where (T, U): SomeTrait` + // (so this case is a generalization of the NamedGeneric case) + self.lookup_method_in_trait_constraints(object_type, method_name, span) } }, } } + fn lookup_method_in_trait_constraints( + &mut self, + object_type: &Type, + method_name: &str, + span: Span, + ) -> Option { + let func_id = match self.current_item { + Some(DependencyId::Function(id)) => id, + _ => panic!("unexpected method outside a function"), + }; + let func_meta = self.interner.function_meta(&func_id); + + for constraint in &func_meta.trait_constraints { + if *object_type == constraint.typ { + if let Some(the_trait) = self.interner.try_get_trait(constraint.trait_id) { + for (method_index, method) in the_trait.methods.iter().enumerate() { + if method.name.0.contents == method_name { + let trait_method = + TraitMethodId { trait_id: constraint.trait_id, method_index }; + return Some(HirMethodReference::TraitMethodId( + trait_method, + constraint.trait_generics.clone(), + )); + } + } + } + } + } + + self.push_err(TypeCheckError::UnresolvedMethodCall { + method_name: method_name.to_string(), + object_type: object_type.clone(), + span, + }); + + None + } + pub(super) fn type_check_call( &mut self, call: &HirCallExpression, @@ -1592,6 +1618,10 @@ impl<'context> Elaborator<'context> { } Self::find_numeric_generics_in_type(fields, found); } + Type::InfixExpr(lhs, _op, rhs) => { + Self::find_numeric_generics_in_type(lhs, found); + Self::find_numeric_generics_in_type(rhs, found); + } } } @@ -1610,6 +1640,19 @@ impl<'context> Elaborator<'context> { let context = context.expect("The function_context stack should always be non-empty"); context.trait_constraints.push((constraint, expr_id)); } + + pub fn check_unsupported_turbofish_usage(&mut self, path: &Path, exclude_last_segment: bool) { + for (index, segment) in path.segments.iter().enumerate() { + if exclude_last_segment && index == path.segments.len() - 1 { + continue; + } + + if segment.generics.is_some() { + let span = segment.turbofish_span(); + self.push_err(TypeCheckError::UnsupportedTurbofishUsage { span }); + } + } + } } /// Gives an error if a user tries to create a mutable reference diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/unquote.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/unquote.rs index ed12ba21398a..fd7e02df905f 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/unquote.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/unquote.rs @@ -27,7 +27,7 @@ impl<'a> Elaborator<'a> { // Don't want the leading `$` anymore new_tokens.pop(); let path = Path::from_single(name, span); - let (expr_id, _) = self.elaborate_variable(path, None); + let (expr_id, _) = self.elaborate_variable(path); new_tokens.push(SpannedToken::new(Token::UnquoteMarker(expr_id), span)); } other_next => new_tokens.push(SpannedToken::new(other_next, span)), 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 b52201146dd5..b7b490902320 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 @@ -2,9 +2,10 @@ use std::fmt::Display; use std::rc::Rc; use crate::{ + ast::TraitBound, hir::{def_collector::dc_crate::CompilationError, type_check::NoMatchingImplFoundError}, parser::ParserError, - token::Tokens, + token::Token, Type, }; use acvm::{acir::AcirField, BlackBoxResolutionError, FieldElement}; @@ -12,52 +13,186 @@ use fm::FileId; use iter_extended::vecmap; use noirc_errors::{CustomDiagnostic, Location}; -use super::value::Value; - /// The possible errors that can halt the interpreter. #[derive(Debug, Clone, PartialEq, Eq)] pub enum InterpreterError { - ArgumentCountMismatch { expected: usize, actual: usize, location: Location }, - TypeMismatch { expected: Type, value: Value, location: Location }, - NonComptimeVarReferenced { name: String, location: Location }, - VariableNotInScope { location: Location }, - IntegerOutOfRangeForType { value: FieldElement, typ: Type, location: Location }, - ErrorNodeEncountered { location: Location }, - NonFunctionCalled { value: Value, location: Location }, - NonBoolUsedInIf { value: Value, location: Location }, - NonBoolUsedInConstrain { value: Value, location: Location }, - FailingConstraint { message: Option, location: Location }, - NoMethodFound { name: String, typ: Type, location: Location }, - NonIntegerUsedInLoop { value: Value, location: Location }, - NonPointerDereferenced { value: Value, location: Location }, - NonTupleOrStructInMemberAccess { value: Value, location: Location }, - NonArrayIndexed { value: Value, location: Location }, - NonIntegerUsedAsIndex { value: Value, location: Location }, - NonIntegerIntegerLiteral { typ: Type, location: Location }, - NonIntegerArrayLength { typ: Type, location: Location }, - NonNumericCasted { value: Value, location: Location }, - IndexOutOfBounds { index: usize, length: usize, location: Location }, - ExpectedStructToHaveField { value: Value, field_name: String, location: Location }, - TypeUnsupported { typ: Type, location: Location }, - InvalidValueForUnary { value: Value, operator: &'static str, location: Location }, - InvalidValuesForBinary { lhs: Value, rhs: Value, operator: &'static str, location: Location }, - CastToNonNumericType { typ: Type, location: Location }, - QuoteInRuntimeCode { location: Location }, - NonStructInConstructor { typ: Type, location: Location }, - CannotInlineMacro { value: Value, location: Location }, - UnquoteFoundDuringEvaluation { location: Location }, - DebugEvaluateComptime { diagnostic: CustomDiagnostic, location: Location }, - FailedToParseMacro { error: ParserError, tokens: Rc, rule: &'static str, file: FileId }, - UnsupportedTopLevelItemUnquote { item: String, location: Location }, - NonComptimeFnCallInSameCrate { function: String, location: Location }, - NoImpl { location: Location }, - NoMatchingImplFound { error: NoMatchingImplFoundError, file: FileId }, - ImplMethodTypeMismatch { expected: Type, actual: Type, location: Location }, - BreakNotInLoop { location: Location }, - ContinueNotInLoop { location: Location }, + ArgumentCountMismatch { + expected: usize, + actual: usize, + location: Location, + }, + TypeMismatch { + expected: Type, + actual: Type, + location: Location, + }, + NonComptimeVarReferenced { + name: String, + location: Location, + }, + VariableNotInScope { + location: Location, + }, + IntegerOutOfRangeForType { + value: FieldElement, + typ: Type, + location: Location, + }, + ErrorNodeEncountered { + location: Location, + }, + NonFunctionCalled { + typ: Type, + location: Location, + }, + NonBoolUsedInIf { + typ: Type, + location: Location, + }, + NonBoolUsedInConstrain { + typ: Type, + location: Location, + }, + FailingConstraint { + message: Option, + location: Location, + }, + NoMethodFound { + name: String, + typ: Type, + location: Location, + }, + NonIntegerUsedInLoop { + typ: Type, + location: Location, + }, + NonPointerDereferenced { + typ: Type, + location: Location, + }, + NonTupleOrStructInMemberAccess { + typ: Type, + location: Location, + }, + NonArrayIndexed { + typ: Type, + location: Location, + }, + NonIntegerUsedAsIndex { + typ: Type, + location: Location, + }, + NonIntegerIntegerLiteral { + typ: Type, + location: Location, + }, + NonIntegerArrayLength { + typ: Type, + location: Location, + }, + NonNumericCasted { + typ: Type, + location: Location, + }, + IndexOutOfBounds { + index: usize, + length: usize, + location: Location, + }, + ExpectedStructToHaveField { + typ: Type, + field_name: String, + location: Location, + }, + TypeUnsupported { + typ: Type, + location: Location, + }, + InvalidValueForUnary { + typ: Type, + operator: &'static str, + location: Location, + }, + InvalidValuesForBinary { + lhs: Type, + rhs: Type, + operator: &'static str, + location: Location, + }, + CastToNonNumericType { + typ: Type, + location: Location, + }, + QuoteInRuntimeCode { + location: Location, + }, + NonStructInConstructor { + typ: Type, + location: Location, + }, + CannotInlineMacro { + value: String, + typ: Type, + location: Location, + }, + UnquoteFoundDuringEvaluation { + location: Location, + }, + DebugEvaluateComptime { + diagnostic: CustomDiagnostic, + location: Location, + }, + FailedToParseMacro { + error: ParserError, + tokens: Rc>, + rule: &'static str, + file: FileId, + }, + UnsupportedTopLevelItemUnquote { + item: String, + location: Location, + }, + ComptimeDependencyCycle { + function: String, + location: Location, + }, + NoImpl { + location: Location, + }, + NoMatchingImplFound { + error: NoMatchingImplFoundError, + file: FileId, + }, + ImplMethodTypeMismatch { + expected: Type, + actual: Type, + location: Location, + }, + BreakNotInLoop { + location: Location, + }, + ContinueNotInLoop { + location: Location, + }, BlackBoxError(BlackBoxResolutionError, Location), + FailedToResolveTraitBound { + trait_bound: TraitBound, + location: Location, + }, + TraitDefinitionMustBeAPath { + location: Location, + }, + FailedToResolveTraitDefinition { + location: Location, + }, + FunctionAlreadyResolved { + location: Location, + }, - Unimplemented { item: String, location: Location }, + Unimplemented { + item: String, + location: Location, + }, // These cases are not errors, they are just used to prevent us from running more code // until the loop can be resumed properly. These cases will never be displayed to users. @@ -112,14 +247,18 @@ impl InterpreterError { | InterpreterError::CannotInlineMacro { location, .. } | InterpreterError::UnquoteFoundDuringEvaluation { location, .. } | InterpreterError::UnsupportedTopLevelItemUnquote { location, .. } - | InterpreterError::NonComptimeFnCallInSameCrate { location, .. } + | InterpreterError::ComptimeDependencyCycle { location, .. } | InterpreterError::Unimplemented { location, .. } | InterpreterError::NoImpl { location, .. } | InterpreterError::ImplMethodTypeMismatch { location, .. } | InterpreterError::DebugEvaluateComptime { location, .. } | InterpreterError::BlackBoxError(_, location) | InterpreterError::BreakNotInLoop { location, .. } - | InterpreterError::ContinueNotInLoop { location, .. } => *location, + | InterpreterError::ContinueNotInLoop { location, .. } + | InterpreterError::TraitDefinitionMustBeAPath { location } + | InterpreterError::FailedToResolveTraitDefinition { location } + | InterpreterError::FailedToResolveTraitBound { location, .. } => *location, + InterpreterError::FunctionAlreadyResolved { location, .. } => *location, InterpreterError::FailedToParseMacro { error, file, .. } => { Location::new(error.span(), *file) @@ -163,9 +302,8 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { let secondary = format!("Too {few_many} arguments"); CustomDiagnostic::simple_error(msg, secondary, location.span) } - InterpreterError::TypeMismatch { expected, value, location } => { - let typ = value.get_type(); - let msg = format!("Expected `{expected}` but a value of type `{typ}` was given"); + InterpreterError::TypeMismatch { expected, actual, location } => { + let msg = format!("Expected `{expected}` but a value of type `{actual}` was given"); CustomDiagnostic::simple_error(msg, String::new(), location.span) } InterpreterError::NonComptimeVarReferenced { name, location } => { @@ -191,23 +329,23 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { let secondary = "This is a bug, please report this if found!".to_string(); CustomDiagnostic::simple_error(msg, secondary, location.span) } - InterpreterError::NonFunctionCalled { value, location } => { + InterpreterError::NonFunctionCalled { typ, location } => { let msg = "Only functions may be called".to_string(); - let secondary = format!("Expression has type {}", value.get_type()); + let secondary = format!("Expression has type {typ}"); CustomDiagnostic::simple_error(msg, secondary, location.span) } - InterpreterError::NonBoolUsedInIf { value, location } => { - let msg = format!("Expected a `bool` but found `{}`", value.get_type()); + InterpreterError::NonBoolUsedInIf { typ, location } => { + let msg = format!("Expected a `bool` but found `{typ}`"); let secondary = "If conditions must be a boolean value".to_string(); CustomDiagnostic::simple_error(msg, secondary, location.span) } - InterpreterError::NonBoolUsedInConstrain { value, location } => { - let msg = format!("Expected a `bool` but found `{}`", value.get_type()); + InterpreterError::NonBoolUsedInConstrain { typ, location } => { + let msg = format!("Expected a `bool` but found `{typ}`"); CustomDiagnostic::simple_error(msg, String::new(), location.span) } InterpreterError::FailingConstraint { message, location } => { let (primary, secondary) = match message { - Some(msg) => (format!("{msg:?}"), "Assertion failed".into()), + Some(msg) => (msg.clone(), "Assertion failed".into()), None => ("Assertion failed".into(), String::new()), }; CustomDiagnostic::simple_error(primary, secondary, location.span) @@ -216,32 +354,30 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { let msg = format!("No method named `{name}` found for type `{typ}`"); CustomDiagnostic::simple_error(msg, String::new(), location.span) } - InterpreterError::NonIntegerUsedInLoop { value, location } => { - let typ = value.get_type(); + InterpreterError::NonIntegerUsedInLoop { typ, location } => { let msg = format!("Non-integer type `{typ}` used in for loop"); - let secondary = if matches!(typ.as_ref(), &Type::FieldElement) { + let secondary = if matches!(typ, Type::FieldElement) { "`field` is not an integer type, try `u32` instead".to_string() } else { String::new() }; CustomDiagnostic::simple_error(msg, secondary, location.span) } - InterpreterError::NonPointerDereferenced { value, location } => { - let typ = value.get_type(); + InterpreterError::NonPointerDereferenced { typ, location } => { let msg = format!("Only references may be dereferenced, but found `{typ}`"); CustomDiagnostic::simple_error(msg, String::new(), location.span) } - InterpreterError::NonTupleOrStructInMemberAccess { value, location } => { - let msg = format!("The type `{}` has no fields to access", value.get_type()); + InterpreterError::NonTupleOrStructInMemberAccess { typ, location } => { + let msg = format!("The type `{typ}` has no fields to access"); CustomDiagnostic::simple_error(msg, String::new(), location.span) } - InterpreterError::NonArrayIndexed { value, location } => { - let msg = format!("Expected an array or slice but found a(n) {}", value.get_type()); + InterpreterError::NonArrayIndexed { typ, location } => { + let msg = format!("Expected an array or slice but found a(n) {typ}"); let secondary = "Only arrays or slices may be indexed".into(); CustomDiagnostic::simple_error(msg, secondary, location.span) } - InterpreterError::NonIntegerUsedAsIndex { value, location } => { - let msg = format!("Expected an integer but found a(n) {}", value.get_type()); + InterpreterError::NonIntegerUsedAsIndex { typ, location } => { + let msg = format!("Expected an integer but found a(n) {typ}"); let secondary = "Only integers may be indexed. Note that this excludes `field`s".into(); CustomDiagnostic::simple_error(msg, secondary, location.span) @@ -256,17 +392,16 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { let secondary = "Array lengths must be integers".into(); CustomDiagnostic::simple_error(msg, secondary, location.span) } - InterpreterError::NonNumericCasted { value, location } => { + InterpreterError::NonNumericCasted { typ, location } => { let msg = "Only numeric types may be casted".into(); - let secondary = format!("`{}` is non-numeric", value.get_type()); + let secondary = format!("`{typ}` is non-numeric"); CustomDiagnostic::simple_error(msg, secondary, location.span) } InterpreterError::IndexOutOfBounds { index, length, location } => { let msg = format!("{index} is out of bounds for the array of length {length}"); CustomDiagnostic::simple_error(msg, String::new(), location.span) } - InterpreterError::ExpectedStructToHaveField { value, field_name, location } => { - let typ = value.get_type(); + InterpreterError::ExpectedStructToHaveField { typ, field_name, location } => { let msg = format!("The type `{typ}` has no field named `{field_name}`"); CustomDiagnostic::simple_error(msg, String::new(), location.span) } @@ -275,13 +410,11 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { format!("The type `{typ}` is currently unsupported in comptime expressions"); CustomDiagnostic::simple_error(msg, String::new(), location.span) } - InterpreterError::InvalidValueForUnary { value, operator, location } => { - let msg = format!("`{}` cannot be used with unary {operator}", value.get_type()); + InterpreterError::InvalidValueForUnary { typ, operator, location } => { + let msg = format!("`{typ}` cannot be used with unary {operator}"); CustomDiagnostic::simple_error(msg, String::new(), location.span) } InterpreterError::InvalidValuesForBinary { lhs, rhs, operator, location } => { - let lhs = lhs.get_type(); - let rhs = rhs.get_type(); let msg = format!("No implementation for `{lhs}` {operator} `{rhs}`",); CustomDiagnostic::simple_error(msg, String::new(), location.span) } @@ -297,10 +430,9 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { let msg = format!("`{typ}` is not a struct type"); CustomDiagnostic::simple_error(msg, String::new(), location.span) } - InterpreterError::CannotInlineMacro { value, location } => { - let typ = value.get_type(); + InterpreterError::CannotInlineMacro { value, typ, location } => { let msg = format!("Cannot inline values of type `{typ}` into this position"); - let secondary = format!("Cannot inline value {value:?}"); + let secondary = format!("Cannot inline value `{value}`"); CustomDiagnostic::simple_error(msg, secondary, location.span) } InterpreterError::UnquoteFoundDuringEvaluation { location } => { @@ -311,7 +443,7 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { InterpreterError::DebugEvaluateComptime { diagnostic, .. } => diagnostic.clone(), InterpreterError::FailedToParseMacro { error, tokens, rule, file: _ } => { let message = format!("Failed to parse macro's token stream into {rule}"); - let tokens = vecmap(&tokens.0, ToString::to_string).join(" "); + let tokens = vecmap(tokens.iter(), ToString::to_string).join(" "); // 10 is an aribtrary number of tokens here chosen to fit roughly onto one line let token_stream = if tokens.len() > 10 { @@ -342,10 +474,10 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { error.add_note(format!("Unquoted item was:\n{item}")); error } - InterpreterError::NonComptimeFnCallInSameCrate { function, location } => { - let msg = format!("`{function}` cannot be called in a `comptime` context here"); + InterpreterError::ComptimeDependencyCycle { function, location } => { + let msg = format!("Comptime dependency cycle while resolving `{function}`"); let secondary = - "This function must be `comptime` or in a separate crate to be called".into(); + "This function uses comptime code internally which calls into itself".into(); CustomDiagnostic::simple_error(msg, secondary, location.span) } InterpreterError::Unimplemented { item, location } => { @@ -373,9 +505,28 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { InterpreterError::BlackBoxError(error, location) => { CustomDiagnostic::simple_error(error.to_string(), String::new(), location.span) } + InterpreterError::FailedToResolveTraitBound { trait_bound, location } => { + let msg = format!("Failed to resolve trait bound `{trait_bound}`"); + CustomDiagnostic::simple_error(msg, String::new(), location.span) + } InterpreterError::NoMatchingImplFound { error, .. } => error.into(), InterpreterError::Break => unreachable!("Uncaught InterpreterError::Break"), InterpreterError::Continue => unreachable!("Uncaught InterpreterError::Continue"), + InterpreterError::TraitDefinitionMustBeAPath { location } => { + let msg = "Trait definition arguments must be a variable or path".to_string(); + CustomDiagnostic::simple_error(msg, String::new(), location.span) + } + InterpreterError::FailedToResolveTraitDefinition { location } => { + let msg = "Failed to resolve to a trait definition".to_string(); + CustomDiagnostic::simple_error(msg, String::new(), location.span) + } + InterpreterError::FunctionAlreadyResolved { location } => { + let msg = "Function already resolved".to_string(); + let secondary = + "The function was previously called at compile-time or is in another crate" + .to_string(); + CustomDiagnostic::simple_error(msg, secondary, location.span) + } } } } 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 22763c9cb648..bc48b2875c8b 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 @@ -5,8 +5,8 @@ use crate::ast::{ ArrayLiteral, AssignStatement, BlockExpression, CallExpression, CastExpression, ConstrainKind, ConstructorExpression, ExpressionKind, ForLoopStatement, ForRange, Ident, IfExpression, IndexExpression, InfixExpression, LValue, Lambda, LetStatement, Literal, - MemberAccessExpression, MethodCallExpression, Path, Pattern, PrefixExpression, UnresolvedType, - UnresolvedTypeData, UnresolvedTypeExpression, + MemberAccessExpression, MethodCallExpression, Path, PathSegment, Pattern, PrefixExpression, + UnresolvedType, UnresolvedTypeData, UnresolvedTypeExpression, }; use crate::ast::{ConstrainStatement, Expression, Statement, StatementKind}; use crate::hir_def::expr::{HirArrayLiteral, HirBlockExpression, HirExpression, HirIdent}; @@ -88,13 +88,19 @@ impl HirExpression { pub fn to_display_ast(&self, interner: &NodeInterner, span: Span) -> Expression { let kind = match self { HirExpression::Ident(ident, generics) => { - let path = Path::from_ident(ident.to_display_ast(interner)); - ExpressionKind::Variable( - path, - generics.as_ref().map(|option| { + let ident = ident.to_display_ast(interner); + let segment = PathSegment { + ident, + generics: generics.as_ref().map(|option| { option.iter().map(|generic| generic.to_display_ast()).collect() }), - ) + span, + }; + + let path = + Path { segments: vec![segment], kind: crate::ast::PathKind::Plain, span }; + + ExpressionKind::Variable(path) } HirExpression::Literal(HirLiteral::Array(array)) => { let array = array.to_display_ast(interner, span); @@ -352,6 +358,13 @@ impl Type { Type::Constant(_) => panic!("Type::Constant where a type was expected: {self:?}"), Type::Quoted(quoted_type) => UnresolvedTypeData::Quoted(*quoted_type), Type::Error => UnresolvedTypeData::Error, + Type::InfixExpr(lhs, op, rhs) => { + let lhs = Box::new(lhs.to_type_expression()); + let rhs = Box::new(rhs.to_type_expression()); + let span = Span::default(); + let expr = UnresolvedTypeExpression::BinaryOperation(lhs, *op, rhs, span); + UnresolvedTypeData::Expression(expr) + } }; UnresolvedType { typ, span: None } 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 2090310585ce..72b92e288c7f 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 @@ -1,3 +1,4 @@ +use std::collections::VecDeque; use std::{collections::hash_map::Entry, rc::Rc}; use acvm::{acir::AcirField, FieldElement}; @@ -10,12 +11,14 @@ use crate::ast::{BinaryOpKind, FunctionKind, IntegerBitSize, Signedness}; use crate::elaborator::Elaborator; use crate::graph::CrateId; use crate::hir_def::expr::ImplKind; +use crate::hir_def::function::FunctionBody; use crate::macros_api::UnaryOp; use crate::monomorphization::{ perform_impl_bindings, perform_instantiation_bindings, resolve_trait_method, undo_instantiation_bindings, }; use crate::token::Tokens; +use crate::TypeVariable; use crate::{ hir_def::{ expr::{ @@ -51,6 +54,12 @@ pub struct Interpreter<'local, 'interner> { in_loop: bool, current_function: Option, + + /// Maps each bound generic to each binding it has in the current callstack. + /// Since the interpreter monomorphizes as it interprets, we can bind over the same generic + /// multiple times. Without this map, when one of these inner functions exits we would + /// unbind the generic completely instead of resetting it to its previous binding. + bound_generics: Vec>, } #[allow(unused)] @@ -60,28 +69,41 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { crate_id: CrateId, current_function: Option, ) -> Self { - Self { elaborator, crate_id, current_function, in_loop: false } + let bound_generics = Vec::new(); + Self { elaborator, crate_id, current_function, bound_generics, in_loop: false } } pub(crate) fn call_function( &mut self, function: FuncId, arguments: Vec<(Value, Location)>, - instantiation_bindings: TypeBindings, + mut instantiation_bindings: TypeBindings, location: Location, ) -> IResult { let trait_method = self.elaborator.interner.get_trait_method_id(function); + // To match the monomorphizer, we need to call follow_bindings on each of + // the instantiation bindings before we unbind the generics from the previous function. + // This is because the instantiation bindings refer to variables from the call site. + for (_, binding) in instantiation_bindings.values_mut() { + *binding = binding.follow_bindings(); + } + + self.unbind_generics_from_previous_function(); perform_instantiation_bindings(&instantiation_bindings); - let impl_bindings = + let mut impl_bindings = perform_impl_bindings(self.elaborator.interner, trait_method, function, location)?; - let old_function = self.current_function.replace(function); + for (_, binding) in impl_bindings.values_mut() { + *binding = binding.follow_bindings(); + } + + self.remember_bindings(&instantiation_bindings, &impl_bindings); let result = self.call_function_inner(function, arguments, location); - self.current_function = old_function; undo_instantiation_bindings(impl_bindings); undo_instantiation_bindings(instantiation_bindings); + self.rebind_generics_from_previous_function(); result } @@ -100,19 +122,27 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { }); } - let is_comptime = self.elaborator.interner.function_modifiers(&function).is_comptime; - if !is_comptime && meta.source_crate == self.crate_id { - // Calling non-comptime functions from within the current crate is restricted - // as non-comptime items will have not been elaborated yet. - let function = self.elaborator.interner.function_name(&function).to_owned(); - return Err(InterpreterError::NonComptimeFnCallInSameCrate { function, location }); - } - if meta.kind != FunctionKind::Normal { let return_type = meta.return_type().follow_bindings(); - return self.call_builtin(function, arguments, return_type, location); + return self.call_special(function, arguments, return_type, location); } + // Wait until after call_special to set the current function so that builtin functions like + // `.as_type()` still call the resolver in the caller's scope. + let old_function = self.current_function.replace(function); + let result = self.call_user_defined_function(function, arguments, location); + self.current_function = old_function; + result + } + + /// Call a non-builtin function + fn call_user_defined_function( + &mut self, + function: FuncId, + arguments: Vec<(Value, Location)>, + location: Location, + ) -> IResult { + let meta = self.elaborator.interner.function_meta(&function); let parameters = meta.parameters.0.clone(); let previous_state = self.enter_function(); @@ -120,19 +150,47 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { self.define_pattern(parameter, typ, argument, arg_location)?; } - let function_body = - self.elaborator.interner.function(&function).try_as_expr().ok_or_else(|| { - let function = self.elaborator.interner.function_name(&function).to_owned(); - InterpreterError::NonComptimeFnCallInSameCrate { function, location } - })?; - + let function_body = self.get_function_body(function, location)?; let result = self.evaluate(function_body)?; - self.exit_function(previous_state); Ok(result) } - fn call_builtin( + /// Try to retrieve a function's body. + /// If the function has not yet been resolved this will attempt to lazily resolve it. + /// Afterwards, if the function's body is still not known or the function is still + /// in a Resolving state we issue an error. + fn get_function_body(&mut self, function: FuncId, location: Location) -> IResult { + let meta = self.elaborator.interner.function_meta(&function); + match self.elaborator.interner.function(&function).try_as_expr() { + Some(body) => Ok(body), + None => { + if matches!(&meta.function_body, FunctionBody::Unresolved(..)) { + self.elaborate_item(None, |elaborator| { + elaborator.elaborate_function(function); + }); + + self.get_function_body(function, location) + } else { + let function = self.elaborator.interner.function_name(&function).to_owned(); + Err(InterpreterError::ComptimeDependencyCycle { function, location }) + } + } + } + } + + fn elaborate_item( + &mut self, + function: Option, + f: impl FnOnce(&mut Elaborator) -> T, + ) -> T { + self.unbind_generics_from_previous_function(); + let result = self.elaborator.elaborate_item_from_comptime(function, f); + self.rebind_generics_from_previous_function(); + result + } + + fn call_special( &mut self, function: FuncId, arguments: Vec<(Value, Location)>, @@ -145,19 +203,16 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { if let Some(builtin) = func_attrs.builtin() { let builtin = builtin.clone(); - builtin::call_builtin( - self.elaborator.interner, - &builtin, - arguments, - return_type, - location, - ) + self.call_builtin(&builtin, arguments, return_type, location) } else if let Some(foreign) = func_attrs.foreign() { let foreign = foreign.clone(); foreign::call_foreign(self.elaborator.interner, &foreign, arguments, location) } else if let Some(oracle) = func_attrs.oracle() { if oracle == "print" { self.print_oracle(arguments) + // Ignore debugger functions + } else if oracle.starts_with("__debug") { + Ok(Value::Unit) } else { let item = format!("Comptime evaluation for oracle functions like {oracle}"); Err(InterpreterError::Unimplemented { item, location }) @@ -171,8 +226,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { fn call_closure( &mut self, closure: HirLambda, - // TODO: How to define environment here? - _environment: Vec, + environment: Vec, arguments: Vec<(Value, Location)>, call_location: Location, ) -> IResult { @@ -191,6 +245,10 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { self.define_pattern(parameter, typ, argument, arg_location)?; } + for (param, arg) in closure.captures.into_iter().zip(environment) { + self.define(param.ident.id, arg); + } + let result = self.evaluate(closure.body)?; self.exit_function(previous_state); @@ -203,8 +261,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { pub(super) fn enter_function(&mut self) -> (bool, Vec>) { // Drain every scope except the global scope let mut scope = Vec::new(); - if self.elaborator.comptime_scopes.len() > 1 { - scope = self.elaborator.comptime_scopes.drain(1..).collect(); + if self.elaborator.interner.comptime_scopes.len() > 1 { + scope = self.elaborator.interner.comptime_scopes.drain(1..).collect(); } self.push_scope(); (std::mem::take(&mut self.in_loop), scope) @@ -214,21 +272,57 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { self.in_loop = state.0; // Keep only the global scope - self.elaborator.comptime_scopes.truncate(1); - self.elaborator.comptime_scopes.append(&mut state.1); + self.elaborator.interner.comptime_scopes.truncate(1); + self.elaborator.interner.comptime_scopes.append(&mut state.1); } pub(super) fn push_scope(&mut self) { - self.elaborator.comptime_scopes.push(HashMap::default()); + self.elaborator.interner.comptime_scopes.push(HashMap::default()); } pub(super) fn pop_scope(&mut self) { - self.elaborator.comptime_scopes.pop(); + self.elaborator.interner.comptime_scopes.pop(); } fn current_scope_mut(&mut self) -> &mut HashMap { // the global scope is always at index zero, so this is always Some - self.elaborator.comptime_scopes.last_mut().unwrap() + self.elaborator.interner.comptime_scopes.last_mut().unwrap() + } + + fn unbind_generics_from_previous_function(&mut self) { + if let Some(bindings) = self.bound_generics.last() { + for var in bindings.keys() { + var.unbind(var.id()); + } + } + // Push a new bindings list for the current function + self.bound_generics.push(HashMap::default()); + } + + fn rebind_generics_from_previous_function(&mut self) { + // Remove the currently bound generics first. + self.bound_generics.pop(); + + if let Some(bindings) = self.bound_generics.last() { + for (var, binding) in bindings { + var.force_bind(binding.clone()); + } + } + } + + fn remember_bindings(&mut self, main_bindings: &TypeBindings, impl_bindings: &TypeBindings) { + let bound_generics = self + .bound_generics + .last_mut() + .expect("remember_bindings called with no bound_generics on the stack"); + + for (var, binding) in main_bindings.values() { + bound_generics.insert(var.clone(), binding.follow_bindings()); + } + + for (var, binding) in impl_bindings.values() { + bound_generics.insert(var.clone(), binding.follow_bindings()); + } } pub(super) fn define_pattern( @@ -248,21 +342,30 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { let argument = Value::Pointer(Shared::new(argument), true); self.define_pattern(pattern, typ, argument, location) } - HirPattern::Tuple(pattern_fields, _) => match (argument, typ) { - (Value::Tuple(fields), Type::Tuple(type_fields)) - if fields.len() == pattern_fields.len() => - { - for ((pattern, typ), argument) in - pattern_fields.iter().zip(type_fields).zip(fields) + HirPattern::Tuple(pattern_fields, _) => { + let typ = &typ.follow_bindings(); + + match (argument, typ) { + (Value::Tuple(fields), Type::Tuple(type_fields)) + if fields.len() == pattern_fields.len() => { - self.define_pattern(pattern, typ, argument, location)?; + for ((pattern, typ), argument) in + pattern_fields.iter().zip(type_fields).zip(fields) + { + self.define_pattern(pattern, typ, argument, location)?; + } + Ok(()) + } + (value, _) => { + let actual = value.get_type().into_owned(); + Err(InterpreterError::TypeMismatch { + expected: typ.clone(), + actual, + location, + }) } - Ok(()) - } - (value, _) => { - Err(InterpreterError::TypeMismatch { expected: typ.clone(), value, location }) } - }, + } HirPattern::Struct(struct_type, pattern_fields, _) => { self.push_scope(); @@ -271,7 +374,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { for (field_name, field_pattern) in pattern_fields { let field = fields.get(&field_name.0.contents).ok_or_else(|| { InterpreterError::ExpectedStructToHaveField { - value: Value::Struct(fields.clone(), struct_type.clone()), + typ: struct_type.clone(), field_name: field_name.0.contents.clone(), location, } @@ -289,7 +392,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { } value => Err(InterpreterError::TypeMismatch { expected: typ.clone(), - value, + actual: value.get_type().into_owned(), location, }), }; @@ -311,7 +414,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { return Ok(()); } - for scope in self.elaborator.comptime_scopes.iter_mut().rev() { + for scope in self.elaborator.interner.comptime_scopes.iter_mut().rev() { if let Entry::Occupied(mut entry) = scope.entry(id) { entry.insert(argument); return Ok(()); @@ -325,7 +428,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { } pub fn lookup_id(&self, id: DefinitionId, location: Location) -> IResult { - for scope in self.elaborator.comptime_scopes.iter().rev() { + for scope in self.elaborator.interner.comptime_scopes.iter().rev() { if let Some(value) = scope.get(&id) { return Ok(value.clone()); } @@ -449,16 +552,50 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { self.evaluate_integer(value, is_negative, id) } HirLiteral::Str(string) => Ok(Value::String(Rc::new(string))), - HirLiteral::FmtStr(_, _) => { - let item = "format strings in a comptime context".into(); - let location = self.elaborator.interner.expr_location(&id); - Err(InterpreterError::Unimplemented { item, location }) + HirLiteral::FmtStr(string, captures) => { + self.evaluate_format_string(string, captures, id) } HirLiteral::Array(array) => self.evaluate_array(array, id), HirLiteral::Slice(array) => self.evaluate_slice(array, id), } } + fn evaluate_format_string( + &mut self, + string: String, + captures: Vec, + id: ExprId, + ) -> IResult { + let mut result = String::new(); + let mut escaped = false; + let mut consuming = false; + + let mut values: VecDeque<_> = + captures.into_iter().map(|capture| self.evaluate(capture)).collect::>()?; + + for character in string.chars() { + match character { + '\\' => escaped = true, + '{' if !escaped => consuming = true, + '}' if !escaped && consuming => { + consuming = false; + + if let Some(value) = values.pop_front() { + result.push_str(&value.display(self.elaborator.interner).to_string()); + } + } + other if !consuming => { + escaped = false; + result.push(other); + } + _ => (), + } + } + + let typ = self.elaborator.interner.id_type(id); + Ok(Value::FormatString(Rc::new(result), typ)) + } + fn evaluate_integer( &self, value: FieldElement, @@ -644,7 +781,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { value => { let location = self.elaborator.interner.expr_location(&id); let operator = "minus"; - Err(InterpreterError::InvalidValueForUnary { value, location, operator }) + let typ = value.get_type().into_owned(); + Err(InterpreterError::InvalidValueForUnary { typ, location, operator }) } }, UnaryOp::Not => match rhs { @@ -659,7 +797,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { Value::U64(value) => Ok(Value::U64(!value)), value => { let location = self.elaborator.interner.expr_location(&id); - Err(InterpreterError::InvalidValueForUnary { value, location, operator: "not" }) + let typ = value.get_type().into_owned(); + Err(InterpreterError::InvalidValueForUnary { typ, location, operator: "not" }) } }, UnaryOp::MutableReference => { @@ -675,7 +814,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { Value::Pointer(element, _) => Ok(element.borrow().clone()), value => { let location = self.elaborator.interner.expr_location(&id); - Err(InterpreterError::NonPointerDereferenced { value, location }) + let typ = value.get_type().into_owned(); + Err(InterpreterError::NonPointerDereferenced { typ, location }) } }, } @@ -689,6 +829,13 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { return self.evaluate_overloaded_infix(infix, lhs, rhs, id); } + let make_error = |this: &mut Self, lhs: Value, rhs: Value, operator| { + let location = this.elaborator.interner.expr_location(&id); + let lhs = lhs.get_type().into_owned(); + let rhs = rhs.get_type().into_owned(); + Err(InvalidValuesForBinary { lhs, rhs, location, operator }) + }; + use InterpreterError::InvalidValuesForBinary; match infix.operator.kind { BinaryOpKind::Add => match (lhs, rhs) { @@ -701,10 +848,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::U16(lhs + rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs + rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs + rhs)), - (lhs, rhs) => { - let location = self.elaborator.interner.expr_location(&id); - Err(InvalidValuesForBinary { lhs, rhs, location, operator: "+" }) - } + (lhs, rhs) => make_error(self, lhs, rhs, "+"), }, BinaryOpKind::Subtract => match (lhs, rhs) { (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Field(lhs - rhs)), @@ -716,10 +860,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::U16(lhs - rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs - rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs - rhs)), - (lhs, rhs) => { - let location = self.elaborator.interner.expr_location(&id); - Err(InvalidValuesForBinary { lhs, rhs, location, operator: "-" }) - } + (lhs, rhs) => make_error(self, lhs, rhs, "-"), }, BinaryOpKind::Multiply => match (lhs, rhs) { (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Field(lhs * rhs)), @@ -731,10 +872,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::U16(lhs * rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs * rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs * rhs)), - (lhs, rhs) => { - let location = self.elaborator.interner.expr_location(&id); - Err(InvalidValuesForBinary { lhs, rhs, location, operator: "*" }) - } + (lhs, rhs) => make_error(self, lhs, rhs, "*"), }, BinaryOpKind::Divide => match (lhs, rhs) { (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Field(lhs / rhs)), @@ -746,10 +884,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::U16(lhs / rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs / rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs / rhs)), - (lhs, rhs) => { - let location = self.elaborator.interner.expr_location(&id); - Err(InvalidValuesForBinary { lhs, rhs, location, operator: "/" }) - } + (lhs, rhs) => make_error(self, lhs, rhs, "/"), }, BinaryOpKind::Equal => match (lhs, rhs) { (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Bool(lhs == rhs)), @@ -761,10 +896,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::Bool(lhs == rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::Bool(lhs == rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::Bool(lhs == rhs)), - (lhs, rhs) => { - let location = self.elaborator.interner.expr_location(&id); - Err(InvalidValuesForBinary { lhs, rhs, location, operator: "==" }) - } + (lhs, rhs) => make_error(self, lhs, rhs, "=="), }, BinaryOpKind::NotEqual => match (lhs, rhs) { (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Bool(lhs != rhs)), @@ -776,10 +908,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::Bool(lhs != rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::Bool(lhs != rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::Bool(lhs != rhs)), - (lhs, rhs) => { - let location = self.elaborator.interner.expr_location(&id); - Err(InvalidValuesForBinary { lhs, rhs, location, operator: "!=" }) - } + (lhs, rhs) => make_error(self, lhs, rhs, "!="), }, BinaryOpKind::Less => match (lhs, rhs) { (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Bool(lhs < rhs)), @@ -791,10 +920,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::Bool(lhs < rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::Bool(lhs < rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::Bool(lhs < rhs)), - (lhs, rhs) => { - let location = self.elaborator.interner.expr_location(&id); - Err(InvalidValuesForBinary { lhs, rhs, location, operator: "<" }) - } + (lhs, rhs) => make_error(self, lhs, rhs, "<"), }, BinaryOpKind::LessEqual => match (lhs, rhs) { (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Bool(lhs <= rhs)), @@ -806,10 +932,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::Bool(lhs <= rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::Bool(lhs <= rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::Bool(lhs <= rhs)), - (lhs, rhs) => { - let location = self.elaborator.interner.expr_location(&id); - Err(InvalidValuesForBinary { lhs, rhs, location, operator: "<=" }) - } + (lhs, rhs) => make_error(self, lhs, rhs, "<="), }, BinaryOpKind::Greater => match (lhs, rhs) { (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Bool(lhs > rhs)), @@ -821,10 +944,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::Bool(lhs > rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::Bool(lhs > rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::Bool(lhs > rhs)), - (lhs, rhs) => { - let location = self.elaborator.interner.expr_location(&id); - Err(InvalidValuesForBinary { lhs, rhs, location, operator: ">" }) - } + (lhs, rhs) => make_error(self, lhs, rhs, ">"), }, BinaryOpKind::GreaterEqual => match (lhs, rhs) { (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Bool(lhs >= rhs)), @@ -836,10 +956,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::Bool(lhs >= rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::Bool(lhs >= rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::Bool(lhs >= rhs)), - (lhs, rhs) => { - let location = self.elaborator.interner.expr_location(&id); - Err(InvalidValuesForBinary { lhs, rhs, location, operator: ">=" }) - } + (lhs, rhs) => make_error(self, lhs, rhs, ">="), }, BinaryOpKind::And => match (lhs, rhs) { (Value::Bool(lhs), Value::Bool(rhs)) => Ok(Value::Bool(lhs & rhs)), @@ -851,10 +968,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::U16(lhs & rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs & rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs & rhs)), - (lhs, rhs) => { - let location = self.elaborator.interner.expr_location(&id); - Err(InvalidValuesForBinary { lhs, rhs, location, operator: "&" }) - } + (lhs, rhs) => make_error(self, lhs, rhs, "&"), }, BinaryOpKind::Or => match (lhs, rhs) { (Value::Bool(lhs), Value::Bool(rhs)) => Ok(Value::Bool(lhs | rhs)), @@ -866,10 +980,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::U16(lhs | rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs | rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs | rhs)), - (lhs, rhs) => { - let location = self.elaborator.interner.expr_location(&id); - Err(InvalidValuesForBinary { lhs, rhs, location, operator: "|" }) - } + (lhs, rhs) => make_error(self, lhs, rhs, "|"), }, BinaryOpKind::Xor => match (lhs, rhs) { (Value::Bool(lhs), Value::Bool(rhs)) => Ok(Value::Bool(lhs ^ rhs)), @@ -881,10 +992,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::U16(lhs ^ rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs ^ rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs ^ rhs)), - (lhs, rhs) => { - let location = self.elaborator.interner.expr_location(&id); - Err(InvalidValuesForBinary { lhs, rhs, location, operator: "^" }) - } + (lhs, rhs) => make_error(self, lhs, rhs, "^"), }, BinaryOpKind::ShiftRight => match (lhs, rhs) { (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::I8(lhs >> rhs)), @@ -895,10 +1003,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::U16(lhs >> rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs >> rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs >> rhs)), - (lhs, rhs) => { - let location = self.elaborator.interner.expr_location(&id); - Err(InvalidValuesForBinary { lhs, rhs, location, operator: ">>" }) - } + (lhs, rhs) => make_error(self, lhs, rhs, ">>"), }, BinaryOpKind::ShiftLeft => match (lhs, rhs) { (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::I8(lhs << rhs)), @@ -909,10 +1014,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::U16(lhs << rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs << rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs << rhs)), - (lhs, rhs) => { - let location = self.elaborator.interner.expr_location(&id); - Err(InvalidValuesForBinary { lhs, rhs, location, operator: "<<" }) - } + (lhs, rhs) => make_error(self, lhs, rhs, "<<"), }, BinaryOpKind::Modulo => match (lhs, rhs) { (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::I8(lhs % rhs)), @@ -923,10 +1025,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::U16(lhs % rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs % rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs % rhs)), - (lhs, rhs) => { - let location = self.elaborator.interner.expr_location(&id); - Err(InvalidValuesForBinary { lhs, rhs, location, operator: "%" }) - } + (lhs, rhs) => make_error(self, lhs, rhs, "%"), }, } } @@ -1030,7 +1129,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { Value::Array(array, _) => array, Value::Slice(array, _) => array, value => { - return Err(InterpreterError::NonArrayIndexed { value, location }); + let typ = value.get_type().into_owned(); + return Err(InterpreterError::NonArrayIndexed { typ, location }); } }; @@ -1050,7 +1150,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { Value::U32(value) => value as usize, Value::U64(value) => value as usize, value => { - return Err(InterpreterError::NonIntegerUsedAsIndex { value, location }); + let typ = value.get_type().into_owned(); + return Err(InterpreterError::NonIntegerUsedAsIndex { typ, location }); } }; @@ -1097,7 +1198,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { } value => { let location = self.elaborator.interner.expr_location(&id); - return Err(InterpreterError::NonTupleOrStructInMemberAccess { value, location }); + let typ = value.get_type().into_owned(); + return Err(InterpreterError::NonTupleOrStructInMemberAccess { typ, location }); } }; @@ -1105,7 +1207,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { let location = self.elaborator.interner.expr_location(&id); let value = Value::Struct(fields, struct_type); let field_name = access.rhs.0.contents; - InterpreterError::ExpectedStructToHaveField { value, field_name, location } + let typ = value.get_type().into_owned(); + InterpreterError::ExpectedStructToHaveField { typ, field_name, location } }) } @@ -1122,15 +1225,18 @@ 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.interner, location)?; - let expr = self - .elaborator - .elaborate_expression_from_comptime(expr, self.current_function); + let expr = self.elaborate_item(self.current_function, |elaborator| { + elaborator.elaborate_expression(expr).0 + }); result = self.evaluate(expr)?; } Ok(result) } Value::Closure(closure, env, _) => self.call_closure(closure, env, arguments, location), - value => Err(InterpreterError::NonFunctionCalled { value, location }), + value => { + let typ = value.get_type().into_owned(); + Err(InterpreterError::NonFunctionCalled { typ, location }) + } } } @@ -1206,7 +1312,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { } value => { let location = interner.expr_location(&id); - return Err(InterpreterError::NonNumericCasted { value, location }); + let typ = value.get_type().into_owned(); + return Err(InterpreterError::NonNumericCasted { typ, location }); } }; @@ -1271,7 +1378,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { Value::Bool(value) => value, value => { let location = self.elaborator.interner.expr_location(&id); - return Err(InterpreterError::NonBoolUsedInIf { value, location }); + let typ = value.get_type().into_owned(); + return Err(InterpreterError::NonBoolUsedInIf { typ, location }); } }; @@ -1311,8 +1419,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { fn evaluate_quote(&mut self, mut tokens: Tokens, expr_id: ExprId) -> IResult { let location = self.elaborator.interner.expr_location(&expr_id); - tokens = self.substitute_unquoted_values_into_tokens(tokens, location)?; - Ok(Value::Code(Rc::new(tokens))) + let tokens = self.substitute_unquoted_values_into_tokens(tokens, location)?; + Ok(Value::Quoted(Rc::new(tokens))) } pub fn evaluate_statement(&mut self, statement: StmtId) -> IResult { @@ -1349,11 +1457,14 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { Value::Bool(false) => { let location = self.elaborator.interner.expr_location(&constrain.0); let message = constrain.2.and_then(|expr| self.evaluate(expr).ok()); + let message = + message.map(|value| value.display(self.elaborator.interner).to_string()); Err(InterpreterError::FailingConstraint { location, message }) } value => { let location = self.elaborator.interner.expr_location(&constrain.0); - Err(InterpreterError::NonBoolUsedInConstrain { value, location }) + let typ = value.get_type().into_owned(); + Err(InterpreterError::NonBoolUsedInConstrain { typ, location }) } } } @@ -1373,7 +1484,10 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { *value.borrow_mut() = rhs; Ok(()) } - value => Err(InterpreterError::NonPointerDereferenced { value, location }), + value => { + let typ = value.get_type().into_owned(); + Err(InterpreterError::NonPointerDereferenced { typ, location }) + } } } HirLValue::MemberAccess { object, field_name, field_index, typ: _, location } => { @@ -1382,7 +1496,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { let index = field_index.ok_or_else(|| { let value = object_value.clone(); let field_name = field_name.to_string(); - InterpreterError::ExpectedStructToHaveField { value, field_name, location } + let typ = value.get_type().into_owned(); + InterpreterError::ExpectedStructToHaveField { typ, field_name, location } })?; match object_value { @@ -1395,7 +1510,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { self.store_lvalue(*object, Value::Struct(fields, typ.follow_bindings())) } value => { - Err(InterpreterError::NonTupleOrStructInMemberAccess { value, location }) + let typ = value.get_type().into_owned(); + Err(InterpreterError::NonTupleOrStructInMemberAccess { typ, location }) } } } @@ -1427,7 +1543,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { match self.evaluate_lvalue(lvalue)? { Value::Pointer(value, _) => Ok(value.borrow().clone()), value => { - Err(InterpreterError::NonPointerDereferenced { value, location: *location }) + let typ = value.get_type().into_owned(); + Err(InterpreterError::NonPointerDereferenced { typ, location: *location }) } } } @@ -1438,14 +1555,15 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { let value = object_value.clone(); let field_name = field_name.to_string(); let location = *location; - InterpreterError::ExpectedStructToHaveField { value, field_name, location } + let typ = value.get_type().into_owned(); + InterpreterError::ExpectedStructToHaveField { typ, field_name, location } })?; match object_value { Value::Tuple(mut values) => Ok(values.swap_remove(index)), Value::Struct(fields, _) => Ok(fields[&field_name.0.contents].clone()), value => Err(InterpreterError::NonTupleOrStructInMemberAccess { - value, + typ: value.get_type().into_owned(), location: *location, }), } @@ -1473,7 +1591,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { Value::U64(value) => Ok((value as i128, |i| Value::U64(i as u64))), value => { let location = this.elaborator.interner.expr_location(&expr); - Err(InterpreterError::NonIntegerUsedInLoop { value, location }) + let typ = value.get_type().into_owned(); + Err(InterpreterError::NonIntegerUsedInLoop { typ, location }) } } }; @@ -1527,9 +1646,9 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { let print_newline = arguments[0].0 == Value::Bool(true); if print_newline { - println!("{}", arguments[1].0); + println!("{}", arguments[1].0.display(self.elaborator.interner)); } else { - print!("{}", arguments[1].0); + print!("{}", arguments[1].0.display(self.elaborator.interner)); } Ok(Value::Unit) 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 02c45165ee36..ef7b9f2be550 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 @@ -4,314 +4,246 @@ use std::{ }; use acvm::{AcirField, FieldElement}; -use chumsky::Parser; +use builtin_helpers::{ + check_argument_count, check_function_not_yet_resolved, check_one_argument, + check_three_arguments, check_two_arguments, get_expr, get_function_def, get_module, get_quoted, + get_slice, get_struct, get_trait_constraint, get_trait_def, get_tuple, get_type, get_u32, + hir_pattern_to_tokens, mutate_func_meta_type, parse, parse_tokens, + replace_func_meta_parameters, replace_func_meta_return_type, +}; use iter_extended::{try_vecmap, vecmap}; -use noirc_errors::{Location, Span}; +use noirc_errors::Location; use rustc_hash::FxHashMap as HashMap; use crate::{ - ast::{IntegerBitSize, TraitBound}, - hir::comptime::{errors::IResult, InterpreterError, Value}, - macros_api::{NodeInterner, Path, Signedness, UnresolvedTypeData}, - node_interner::TraitId, - parser, - token::{SpannedToken, Token, Tokens}, + ast::{ + ExpressionKind, FunctionKind, FunctionReturnType, IntegerBitSize, UnresolvedType, + UnresolvedTypeData, Visibility, + }, + hir::comptime::{errors::IResult, value::add_token_spans, InterpreterError, Value}, + hir_def::function::FunctionBody, + macros_api::{ModuleDefId, NodeInterner, Signedness}, + node_interner::DefinitionKind, + parser::{self}, + token::{SpannedToken, Token}, QuotedType, Shared, Type, }; -pub(super) fn call_builtin( - interner: &mut NodeInterner, - name: &str, - arguments: Vec<(Value, Location)>, - return_type: Type, - location: Location, -) -> IResult { - match name { - "array_len" => array_len(interner, arguments, location), - "as_slice" => as_slice(interner, arguments, location), - "is_unconstrained" => Ok(Value::Bool(true)), - "modulus_be_bits" => modulus_be_bits(interner, arguments, location), - "modulus_be_bytes" => modulus_be_bytes(interner, arguments, location), - "modulus_le_bits" => modulus_le_bits(interner, arguments, location), - "modulus_le_bytes" => modulus_le_bytes(interner, arguments, location), - "modulus_num_bits" => modulus_num_bits(interner, arguments, location), - "slice_insert" => slice_insert(interner, arguments, location), - "slice_pop_back" => slice_pop_back(interner, arguments, location), - "slice_pop_front" => slice_pop_front(interner, arguments, location), - "slice_push_back" => slice_push_back(interner, arguments, location), - "slice_push_front" => slice_push_front(interner, arguments, location), - "slice_remove" => slice_remove(interner, arguments, location), - "struct_def_as_type" => struct_def_as_type(interner, arguments, location), - "struct_def_fields" => struct_def_fields(interner, arguments, location), - "struct_def_generics" => struct_def_generics(interner, arguments, location), - "trait_constraint_eq" => trait_constraint_eq(interner, arguments, location), - "trait_constraint_hash" => trait_constraint_hash(interner, arguments, location), - "trait_def_as_trait_constraint" => { - trait_def_as_trait_constraint(interner, arguments, location) - } - "quoted_as_trait_constraint" => quoted_as_trait_constraint(interner, arguments, location), - "zeroed" => zeroed(return_type, location), - _ => { - let item = format!("Comptime evaluation for builtin function {name}"); - Err(InterpreterError::Unimplemented { item, location }) +use self::builtin_helpers::{get_array, get_u8}; +use super::Interpreter; + +pub(crate) mod builtin_helpers; + +impl<'local, 'context> Interpreter<'local, 'context> { + pub(super) fn call_builtin( + &mut self, + name: &str, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, + ) -> IResult { + let interner = &mut self.elaborator.interner; + match name { + "array_as_str_unchecked" => array_as_str_unchecked(interner, arguments, location), + "array_len" => array_len(interner, arguments, location), + "as_slice" => as_slice(interner, arguments, location), + "expr_as_function_call" => expr_as_function_call(arguments, return_type, location), + "is_unconstrained" => Ok(Value::Bool(true)), + "function_def_name" => function_def_name(interner, arguments, location), + "function_def_parameters" => function_def_parameters(interner, arguments, location), + "function_def_return_type" => function_def_return_type(interner, arguments, location), + "function_def_set_body" => function_def_set_body(self, arguments, location), + "function_def_set_parameters" => function_def_set_parameters(self, arguments, location), + "function_def_set_return_type" => { + function_def_set_return_type(self, arguments, location) + } + "module_functions" => module_functions(self, arguments, location), + "module_is_contract" => module_is_contract(self, arguments, location), + "module_name" => module_name(interner, arguments, location), + "modulus_be_bits" => modulus_be_bits(interner, arguments, location), + "modulus_be_bytes" => modulus_be_bytes(interner, arguments, location), + "modulus_le_bits" => modulus_le_bits(interner, arguments, location), + "modulus_le_bytes" => modulus_le_bytes(interner, arguments, location), + "modulus_num_bits" => modulus_num_bits(interner, arguments, location), + "quoted_as_expr" => quoted_as_expr(arguments, return_type, location), + "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), + "slice_insert" => slice_insert(interner, arguments, location), + "slice_pop_back" => slice_pop_back(interner, arguments, location), + "slice_pop_front" => slice_pop_front(interner, arguments, location), + "slice_push_back" => slice_push_back(interner, arguments, location), + "slice_push_front" => slice_push_front(interner, arguments, location), + "slice_remove" => slice_remove(interner, arguments, location), + "struct_def_as_type" => struct_def_as_type(interner, arguments, location), + "struct_def_fields" => struct_def_fields(interner, arguments, location), + "struct_def_generics" => struct_def_generics(interner, arguments, location), + "trait_constraint_eq" => trait_constraint_eq(interner, arguments, location), + "trait_constraint_hash" => trait_constraint_hash(interner, arguments, location), + "trait_def_as_trait_constraint" => { + trait_def_as_trait_constraint(interner, arguments, location) + } + "trait_def_eq" => trait_def_eq(interner, arguments, location), + "trait_def_hash" => trait_def_hash(interner, arguments, location), + "type_as_array" => type_as_array(arguments, return_type, location), + "type_as_constant" => type_as_constant(arguments, return_type, location), + "type_as_integer" => type_as_integer(arguments, return_type, location), + "type_as_slice" => type_as_slice(arguments, return_type, location), + "type_as_struct" => type_as_struct(arguments, return_type, location), + "type_as_tuple" => type_as_tuple(arguments, return_type, location), + "type_eq" => type_eq(arguments, location), + "type_implements" => type_implements(interner, arguments, location), + "type_is_bool" => type_is_bool(arguments, location), + "type_is_field" => type_is_field(arguments, location), + "type_of" => type_of(arguments, location), + "zeroed" => zeroed(return_type), + _ => { + let item = format!("Comptime evaluation for builtin function {name}"); + Err(InterpreterError::Unimplemented { item, location }) + } } } } -pub(super) fn check_argument_count( - expected: usize, - arguments: &[(Value, Location)], - location: Location, -) -> IResult<()> { - if arguments.len() == expected { - Ok(()) - } else { - let actual = arguments.len(); - Err(InterpreterError::ArgumentCountMismatch { expected, actual, location }) - } -} - fn failing_constraint(message: impl Into, location: Location) -> IResult { - let message = Some(Value::String(Rc::new(message.into()))); - Err(InterpreterError::FailingConstraint { message, location }) + Err(InterpreterError::FailingConstraint { message: Some(message.into()), location }) } -pub(super) fn get_array( +fn array_len( interner: &NodeInterner, - value: Value, + arguments: Vec<(Value, Location)>, location: Location, -) -> IResult<(im::Vector, Type)> { - match value { - Value::Array(values, typ) => Ok((values, typ)), - value => { - let type_var = Box::new(interner.next_type_variable()); - let expected = Type::Array(type_var.clone(), type_var); - Err(InterpreterError::TypeMismatch { expected, value, location }) - } - } -} +) -> IResult { + let (argument, argument_location) = check_one_argument(arguments, location)?; -fn get_slice( - interner: &NodeInterner, - value: Value, - location: Location, -) -> IResult<(im::Vector, Type)> { - match value { - Value::Slice(values, typ) => Ok((values, typ)), + match argument { + Value::Array(values, _) | Value::Slice(values, _) => Ok(Value::U32(values.len() as u32)), value => { let type_var = Box::new(interner.next_type_variable()); - let expected = Type::Slice(type_var); - Err(InterpreterError::TypeMismatch { expected, value, location }) - } - } -} - -pub(super) fn get_field(value: Value, location: Location) -> IResult { - match value { - Value::Field(value) => Ok(value), - value => { - Err(InterpreterError::TypeMismatch { expected: Type::FieldElement, value, location }) - } - } -} - -pub(super) fn get_u32(value: Value, location: Location) -> IResult { - match value { - Value::U32(value) => Ok(value), - value => { - let expected = Type::Integer(Signedness::Unsigned, IntegerBitSize::ThirtyTwo); - Err(InterpreterError::TypeMismatch { expected, value, location }) - } - } -} - -fn get_trait_constraint(value: Value, location: Location) -> IResult { - match value { - Value::TraitConstraint(bound) => Ok(bound), - value => { - let expected = Type::Quoted(QuotedType::TraitConstraint); - Err(InterpreterError::TypeMismatch { expected, value, location }) - } - } -} - -fn get_trait_def(value: Value, location: Location) -> IResult { - match value { - Value::TraitDefinition(id) => Ok(id), - value => { - let expected = Type::Quoted(QuotedType::TraitDefinition); - Err(InterpreterError::TypeMismatch { expected, value, location }) - } - } -} - -fn get_quoted(value: Value, location: Location) -> IResult> { - match value { - Value::Code(tokens) => Ok(tokens), - value => { - let expected = Type::Quoted(QuotedType::Quoted); - Err(InterpreterError::TypeMismatch { expected, value, location }) + let expected = Type::Array(type_var.clone(), type_var); + let actual = value.get_type().into_owned(); + Err(InterpreterError::TypeMismatch { expected, actual, location: argument_location }) } } } -fn array_len( +fn array_as_str_unchecked( interner: &NodeInterner, - mut arguments: Vec<(Value, Location)>, + arguments: Vec<(Value, Location)>, location: Location, ) -> IResult { - check_argument_count(1, &arguments, location)?; + let argument = check_one_argument(arguments, location)?; - match arguments.pop().unwrap().0 { - Value::Array(values, _) | Value::Slice(values, _) => Ok(Value::U32(values.len() as u32)), - value => { - let type_var = Box::new(interner.next_type_variable()); - let expected = Type::Array(type_var.clone(), type_var); - Err(InterpreterError::TypeMismatch { expected, value, location }) - } - } + let array = get_array(interner, argument)?.0; + let string_bytes = try_vecmap(array, |byte| get_u8((byte, location)))?; + let string = String::from_utf8_lossy(&string_bytes).into_owned(); + Ok(Value::String(Rc::new(string))) } fn as_slice( interner: &NodeInterner, - mut arguments: Vec<(Value, Location)>, + arguments: Vec<(Value, Location)>, location: Location, ) -> IResult { - check_argument_count(1, &arguments, location)?; + let (array, array_location) = check_one_argument(arguments, location)?; - let (array, _) = arguments.pop().unwrap(); match array { Value::Array(values, Type::Array(_, typ)) => Ok(Value::Slice(values, Type::Slice(typ))), value => { let type_var = Box::new(interner.next_type_variable()); let expected = Type::Array(type_var.clone(), type_var); - Err(InterpreterError::TypeMismatch { expected, value, location }) + let actual = value.get_type().into_owned(); + Err(InterpreterError::TypeMismatch { expected, actual, location: array_location }) } } } fn slice_push_back( interner: &NodeInterner, - mut arguments: Vec<(Value, Location)>, + arguments: Vec<(Value, Location)>, location: Location, ) -> IResult { - check_argument_count(2, &arguments, location)?; + let (slice, (element, _)) = check_two_arguments(arguments, location)?; - let (element, _) = arguments.pop().unwrap(); - let (mut values, typ) = get_slice(interner, arguments.pop().unwrap().0, location)?; + let (mut values, typ) = get_slice(interner, slice)?; values.push_back(element); Ok(Value::Slice(values, typ)) } -/// fn as_type(self) -> Quoted +/// fn as_type(self) -> Type fn struct_def_as_type( interner: &NodeInterner, - mut arguments: Vec<(Value, Location)>, + arguments: Vec<(Value, Location)>, location: Location, ) -> IResult { - check_argument_count(1, &arguments, location)?; - - let (struct_def, span) = match arguments.pop().unwrap() { - (Value::StructDefinition(id), location) => (id, location.span), - value => { - let expected = Type::Quoted(QuotedType::StructDefinition); - return Err(InterpreterError::TypeMismatch { expected, location, value: value.0 }); - } - }; - - let struct_def = interner.get_struct(struct_def); - let struct_def = struct_def.borrow(); - let make_token = |name| SpannedToken::new(Token::Ident(name), span); + let argument = check_one_argument(arguments, location)?; + let struct_id = get_struct(argument)?; + let struct_def_rc = interner.get_struct(struct_id); + let struct_def = struct_def_rc.borrow(); - let mut tokens = vec![make_token(struct_def.name.to_string())]; - - for (i, generic) in struct_def.generics.iter().enumerate() { - if i != 0 { - tokens.push(SpannedToken::new(Token::Comma, span)); - } - tokens.push(make_token(generic.type_var.borrow().to_string())); - } + let generics = vecmap(&struct_def.generics, |generic| { + Type::NamedGeneric(generic.type_var.clone(), generic.name.clone(), generic.kind.clone()) + }); - Ok(Value::Code(Rc::new(Tokens(tokens)))) + drop(struct_def); + Ok(Value::Type(Type::Struct(struct_def_rc, generics))) } -/// fn generics(self) -> [Quoted] +/// fn generics(self) -> [Type] fn struct_def_generics( interner: &NodeInterner, - mut arguments: Vec<(Value, Location)>, + arguments: Vec<(Value, Location)>, location: Location, ) -> IResult { - check_argument_count(1, &arguments, location)?; - - let (struct_def, span) = match arguments.pop().unwrap() { - (Value::StructDefinition(id), location) => (id, location.span), - value => { - let expected = Type::Quoted(QuotedType::StructDefinition); - return Err(InterpreterError::TypeMismatch { expected, location, value: value.0 }); - } - }; - - let struct_def = interner.get_struct(struct_def); + let argument = check_one_argument(arguments, location)?; + let struct_id = get_struct(argument)?; + let struct_def = interner.get_struct(struct_id); let struct_def = struct_def.borrow(); - let generics = struct_def.generics.iter().map(|generic| { - let name = SpannedToken::new(Token::Ident(generic.type_var.borrow().to_string()), span); - Value::Code(Rc::new(Tokens(vec![name]))) - }); + let generics = + struct_def.generics.iter().map(|generic| Value::Type(generic.clone().as_named_generic())); - let typ = Type::Slice(Box::new(Type::Quoted(QuotedType::Quoted))); + let typ = Type::Slice(Box::new(Type::Quoted(QuotedType::Type))); Ok(Value::Slice(generics.collect(), typ)) } -/// fn fields(self) -> [(Quoted, Quoted)] +/// fn fields(self) -> [(Quoted, Type)] /// Returns (name, type) pairs of each field of this StructDefinition fn struct_def_fields( interner: &mut NodeInterner, - mut arguments: Vec<(Value, Location)>, + arguments: Vec<(Value, Location)>, location: Location, ) -> IResult { - check_argument_count(1, &arguments, location)?; - - let (struct_def, span) = match arguments.pop().unwrap() { - (Value::StructDefinition(id), location) => (id, location.span), - value => { - let expected = Type::Quoted(QuotedType::StructDefinition); - return Err(InterpreterError::TypeMismatch { expected, location, value: value.0 }); - } - }; - - let struct_def = interner.get_struct(struct_def); + let argument = check_one_argument(arguments, location)?; + let struct_id = get_struct(argument)?; + let struct_def = interner.get_struct(struct_id); let struct_def = struct_def.borrow(); - let make_token = |name| SpannedToken::new(Token::Ident(name), span); - let make_quoted = |tokens| Value::Code(Rc::new(Tokens(tokens))); - let mut fields = im::Vector::new(); for (name, typ) in struct_def.get_fields_as_written() { - let name = make_quoted(vec![make_token(name)]); - let id = interner.push_quoted_type(typ); - let typ = SpannedToken::new(Token::QuotedType(id), span); - let typ = Value::Code(Rc::new(Tokens(vec![typ]))); + let name = Value::Quoted(Rc::new(vec![Token::Ident(name)])); + let typ = Value::Type(typ); fields.push_back(Value::Tuple(vec![name, typ])); } let typ = Type::Slice(Box::new(Type::Tuple(vec![ Type::Quoted(QuotedType::Quoted), - Type::Quoted(QuotedType::Quoted), + Type::Quoted(QuotedType::Type), ]))); Ok(Value::Slice(fields, typ)) } fn slice_remove( interner: &mut NodeInterner, - mut arguments: Vec<(Value, Location)>, + arguments: Vec<(Value, Location)>, location: Location, ) -> IResult { - check_argument_count(2, &arguments, location)?; + let (slice, index) = check_two_arguments(arguments, location)?; - let index = get_u32(arguments.pop().unwrap().0, location)? as usize; - let (mut values, typ) = get_slice(interner, arguments.pop().unwrap().0, location)?; + let (mut values, typ) = get_slice(interner, slice)?; + let index = get_u32(index)? as usize; if values.is_empty() { return failing_constraint("slice_remove called on empty slice", location); @@ -331,25 +263,24 @@ fn slice_remove( fn slice_push_front( interner: &mut NodeInterner, - mut arguments: Vec<(Value, Location)>, + arguments: Vec<(Value, Location)>, location: Location, ) -> IResult { - check_argument_count(2, &arguments, location)?; + let (slice, (element, _)) = check_two_arguments(arguments, location)?; - let (element, _) = arguments.pop().unwrap(); - let (mut values, typ) = get_slice(interner, arguments.pop().unwrap().0, location)?; + let (mut values, typ) = get_slice(interner, slice)?; values.push_front(element); Ok(Value::Slice(values, typ)) } fn slice_pop_front( interner: &mut NodeInterner, - mut arguments: Vec<(Value, Location)>, + arguments: Vec<(Value, Location)>, location: Location, ) -> IResult { - check_argument_count(1, &arguments, location)?; + let argument = check_one_argument(arguments, location)?; - let (mut values, typ) = get_slice(interner, arguments.pop().unwrap().0, location)?; + let (mut values, typ) = get_slice(interner, argument)?; match values.pop_front() { Some(element) => Ok(Value::Tuple(vec![element, Value::Slice(values, typ)])), None => failing_constraint("slice_pop_front called on empty slice", location), @@ -358,12 +289,12 @@ fn slice_pop_front( fn slice_pop_back( interner: &mut NodeInterner, - mut arguments: Vec<(Value, Location)>, + arguments: Vec<(Value, Location)>, location: Location, ) -> IResult { - check_argument_count(1, &arguments, location)?; + let argument = check_one_argument(arguments, location)?; - let (mut values, typ) = get_slice(interner, arguments.pop().unwrap().0, location)?; + let (mut values, typ) = get_slice(interner, argument)?; match values.pop_back() { Some(element) => Ok(Value::Tuple(vec![Value::Slice(values, typ), element])), None => failing_constraint("slice_pop_back called on empty slice", location), @@ -372,47 +303,257 @@ fn slice_pop_back( fn slice_insert( interner: &mut NodeInterner, - mut arguments: Vec<(Value, Location)>, + arguments: Vec<(Value, Location)>, location: Location, ) -> IResult { - check_argument_count(3, &arguments, location)?; + let (slice, index, (element, _)) = check_three_arguments(arguments, location)?; - let (element, _) = arguments.pop().unwrap(); - let index = get_u32(arguments.pop().unwrap().0, location)?; - let (mut values, typ) = get_slice(interner, arguments.pop().unwrap().0, location)?; - values.insert(index as usize, element); + let (mut values, typ) = get_slice(interner, slice)?; + let index = get_u32(index)? as usize; + values.insert(index, element); Ok(Value::Slice(values, typ)) } +// fn as_expr(quoted: Quoted) -> Option +fn quoted_as_expr( + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + let argument = check_one_argument(arguments, location)?; + + let expr = parse(argument, parser::expression(), "an expression").ok(); + let value = expr.map(|expr| Value::Expr(expr.kind)); + + option(return_type, value) +} + +// fn as_module(quoted: Quoted) -> Option +fn quoted_as_module( + interpreter: &mut Interpreter, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + let argument = check_one_argument(arguments, location)?; + + let path = parse(argument, parser::path_no_turbofish(), "a path").ok(); + let option_value = path.and_then(|path| { + let module = interpreter.elaborate_item(interpreter.current_function, |elaborator| { + elaborator.resolve_module_by_path(path) + }); + module.map(Value::ModuleDefinition) + }); + + option(return_type, option_value) +} + // fn as_trait_constraint(quoted: Quoted) -> TraitConstraint fn quoted_as_trait_constraint( - _interner: &mut NodeInterner, - mut arguments: Vec<(Value, Location)>, + interpreter: &mut Interpreter, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let argument = check_one_argument(arguments, location)?; + let trait_bound = parse(argument, parser::trait_bound(), "a trait constraint")?; + let bound = interpreter + .elaborate_item(interpreter.current_function, |elaborator| { + elaborator.resolve_trait_bound(&trait_bound, Type::Unit) + }) + .ok_or(InterpreterError::FailedToResolveTraitBound { trait_bound, location })?; + Ok(Value::TraitConstraint(bound.trait_id, bound.trait_generics)) +} + +// fn as_type(quoted: Quoted) -> Type +fn quoted_as_type( + interpreter: &mut Interpreter, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let argument = check_one_argument(arguments, location)?; + let typ = parse(argument, parser::parse_type(), "a type")?; + let typ = + interpreter.elaborate_item(interpreter.current_function, |elab| elab.resolve_type(typ)); + Ok(Value::Type(typ)) +} + +// fn as_array(self) -> Option<(Type, Type)> +fn type_as_array( + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + type_as(arguments, return_type, location, |typ| { + if let Type::Array(length, array_type) = typ { + Some(Value::Tuple(vec![Value::Type(*array_type), Value::Type(*length)])) + } else { + None + } + }) +} + +// fn as_constant(self) -> Option +fn type_as_constant( + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + type_as(arguments, return_type, location, |typ| { + if let Type::Constant(n) = typ { + Some(Value::U32(n)) + } else { + None + } + }) +} + +// fn as_integer(self) -> Option<(bool, u8)> +fn type_as_integer( + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + type_as(arguments, return_type, location, |typ| { + if let Type::Integer(sign, bits) = typ { + Some(Value::Tuple(vec![Value::Bool(sign.is_signed()), Value::U8(bits.bit_size())])) + } else { + None + } + }) +} + +// fn as_slice(self) -> Option +fn type_as_slice( + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + type_as(arguments, return_type, location, |typ| { + if let Type::Slice(slice_type) = typ { + Some(Value::Type(*slice_type)) + } else { + None + } + }) +} + +// fn as_struct(self) -> Option<(StructDefinition, [Type])> +fn type_as_struct( + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + type_as(arguments, return_type, location, |typ| { + if let Type::Struct(struct_type, generics) = typ { + Some(Value::Tuple(vec![ + Value::StructDefinition(struct_type.borrow().id), + Value::Slice( + generics.into_iter().map(Value::Type).collect(), + Type::Slice(Box::new(Type::Quoted(QuotedType::Type))), + ), + ])) + } else { + None + } + }) +} + +// fn as_tuple(self) -> Option<[Type]> +fn type_as_tuple( + arguments: Vec<(Value, Location)>, + return_type: Type, location: Location, ) -> IResult { - check_argument_count(1, &arguments, location)?; + type_as(arguments, return_type.clone(), location, |typ| { + if let Type::Tuple(types) = typ { + let t = extract_option_generic_type(return_type); + + let Type::Slice(slice_type) = t else { + panic!("Expected T to be a slice"); + }; + + Some(Value::Slice(types.into_iter().map(Value::Type).collect(), *slice_type)) + } else { + None + } + }) +} + +// Helper function for implementing the `type_as_...` functions. +fn type_as( + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, + f: F, +) -> IResult +where + F: FnOnce(Type) -> Option, +{ + let value = check_one_argument(arguments, location)?; + let typ = get_type(value)?; + + let option_value = f(typ); - let tokens = get_quoted(arguments.pop().unwrap().0, location)?; - let quoted = tokens.as_ref().clone(); + option(return_type, option_value) +} + +// fn type_eq(_first: Type, _second: Type) -> bool +fn type_eq(arguments: Vec<(Value, Location)>, location: Location) -> IResult { + let (self_type, other_type) = check_two_arguments(arguments, location)?; + + let self_type = get_type(self_type)?; + let other_type = get_type(other_type)?; + + Ok(Value::Bool(self_type == other_type)) +} + +// fn implements(self, constraint: TraitConstraint) -> bool +fn type_implements( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let (typ, constraint) = check_two_arguments(arguments, location)?; - let trait_bound = parser::trait_bound().parse(quoted).map_err(|mut errors| { - let error = errors.swap_remove(0); - let rule = "a trait constraint"; - InterpreterError::FailedToParseMacro { error, tokens, rule, file: location.file } - })?; + let typ = get_type(typ)?; + let (trait_id, generics) = get_trait_constraint(constraint)?; - Ok(Value::TraitConstraint(trait_bound)) + let implements = interner.try_lookup_trait_implementation(&typ, trait_id, &generics).is_ok(); + Ok(Value::Bool(implements)) +} + +// fn is_bool(self) -> bool +fn type_is_bool(arguments: Vec<(Value, Location)>, location: Location) -> IResult { + let value = check_one_argument(arguments, location)?; + let typ = get_type(value)?; + + Ok(Value::Bool(matches!(typ, Type::Bool))) +} + +// fn is_field(self) -> bool +fn type_is_field(arguments: Vec<(Value, Location)>, location: Location) -> IResult { + let value = check_one_argument(arguments, location)?; + let typ = get_type(value)?; + + Ok(Value::Bool(matches!(typ, Type::FieldElement))) +} + +// fn type_of(x: T) -> Type +fn type_of(arguments: Vec<(Value, Location)>, location: Location) -> IResult { + let (value, _) = check_one_argument(arguments, location)?; + let typ = value.get_type().into_owned(); + Ok(Value::Type(typ)) } // fn constraint_hash(constraint: TraitConstraint) -> Field fn trait_constraint_hash( _interner: &mut NodeInterner, - mut arguments: Vec<(Value, Location)>, + arguments: Vec<(Value, Location)>, location: Location, ) -> IResult { - check_argument_count(1, &arguments, location)?; + let argument = check_one_argument(arguments, location)?; - let bound = get_trait_constraint(arguments.pop().unwrap().0, location)?; + let bound = get_trait_constraint(argument)?; let mut hasher = std::collections::hash_map::DefaultHasher::new(); bound.hash(&mut hasher); @@ -424,24 +565,55 @@ fn trait_constraint_hash( // fn constraint_eq(constraint_a: TraitConstraint, constraint_b: TraitConstraint) -> bool fn trait_constraint_eq( _interner: &mut NodeInterner, - mut arguments: Vec<(Value, Location)>, + arguments: Vec<(Value, Location)>, location: Location, ) -> IResult { - check_argument_count(2, &arguments, location)?; + let (value_a, value_b) = check_two_arguments(arguments, location)?; - let constraint_b = get_trait_constraint(arguments.pop().unwrap().0, location)?; - let constraint_a = get_trait_constraint(arguments.pop().unwrap().0, location)?; + let constraint_a = get_trait_constraint(value_a)?; + let constraint_b = get_trait_constraint(value_b)?; Ok(Value::Bool(constraint_a == constraint_b)) } +// fn trait_def_hash(def: TraitDefinition) -> Field +fn trait_def_hash( + _interner: &mut NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let argument = check_one_argument(arguments, location)?; + + let id = get_trait_def(argument)?; + + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + id.hash(&mut hasher); + let hash = hasher.finish(); + + Ok(Value::Field((hash as u128).into())) +} + +// fn trait_def_eq(def_a: TraitDefinition, def_b: TraitDefinition) -> bool +fn trait_def_eq( + _interner: &mut NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let (id_a, id_b) = check_two_arguments(arguments, location)?; + + let id_a = get_trait_def(id_a)?; + let id_b = get_trait_def(id_b)?; + + Ok(Value::Bool(id_a == id_b)) +} + // fn zeroed() -> T -fn zeroed(return_type: Type, location: Location) -> IResult { +fn zeroed(return_type: Type) -> IResult { match return_type { Type::FieldElement => Ok(Value::Field(0u128.into())), Type::Array(length_type, elem) => { if let Some(length) = length_type.evaluate_to_u32() { - let element = zeroed(elem.as_ref().clone(), location)?; + let element = zeroed(elem.as_ref().clone())?; let array = std::iter::repeat(element).take(length as usize).collect(); Ok(Value::Array(array, Type::Array(length_type, elem))) } else { @@ -471,44 +643,44 @@ fn zeroed(return_type: Type, location: Location) -> IResult { Ok(Value::Zeroed(Type::String(length_type))) } } - Type::FmtString(_, _) => { - let item = "format strings in a comptime context".into(); - Err(InterpreterError::Unimplemented { item, location }) + Type::FmtString(length_type, captures) => { + let length = length_type.evaluate_to_u32(); + let typ = Type::FmtString(length_type, captures); + if let Some(length) = length { + Ok(Value::FormatString(Rc::new("\0".repeat(length as usize)), typ)) + } else { + // Assume we can resolve the length later + Ok(Value::Zeroed(typ)) + } } Type::Unit => Ok(Value::Unit), - Type::Tuple(fields) => { - Ok(Value::Tuple(try_vecmap(fields, |field| zeroed(field, location))?)) - } + Type::Tuple(fields) => Ok(Value::Tuple(try_vecmap(fields, zeroed)?)), Type::Struct(struct_type, generics) => { let fields = struct_type.borrow().get_fields(&generics); let mut values = HashMap::default(); for (field_name, field_type) in fields { - let field_value = zeroed(field_type, location)?; + let field_value = zeroed(field_type)?; values.insert(Rc::new(field_name), field_value); } let typ = Type::Struct(struct_type, generics); Ok(Value::Struct(values, typ)) } - Type::Alias(alias, generics) => zeroed(alias.borrow().get_type(&generics), location), + Type::Alias(alias, generics) => zeroed(alias.borrow().get_type(&generics)), typ @ Type::Function(..) => { // Using Value::Zeroed here is probably safer than using FuncId::dummy_id() or similar Ok(Value::Zeroed(typ)) } Type::MutableReference(element) => { - let element = zeroed(*element, location)?; + let element = zeroed(*element)?; Ok(Value::Pointer(Shared::new(element), false)) } - Type::Quoted(QuotedType::TraitConstraint) => Ok(Value::TraitConstraint(TraitBound { - trait_path: Path::from_single(String::new(), Span::default()), - trait_id: None, - trait_generics: Vec::new(), - })), // Optimistically assume we can resolve this type later or that the value is unused Type::TypeVariable(_, _) | Type::Forall(_, _) | Type::Constant(_) + | Type::InfixExpr(..) | Type::Quoted(_) | Type::Error | Type::TraitAsType(_, _, _) @@ -516,6 +688,258 @@ fn zeroed(return_type: Type, location: Location) -> IResult { } } +// fn as_function_call(self) -> Option<(Expr, [Expr])> +fn expr_as_function_call( + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(arguments, return_type, location, |expr| { + if let ExpressionKind::Call(call_expression) = expr { + let function = Value::Expr(call_expression.func.kind); + let arguments = call_expression.arguments.into_iter(); + let arguments = arguments.map(|argument| Value::Expr(argument.kind)).collect(); + let arguments = + Value::Slice(arguments, Type::Slice(Box::new(Type::Quoted(QuotedType::Expr)))); + Some(Value::Tuple(vec![function, arguments])) + } else { + None + } + }) +} + +// Helper function for implementing the `expr_as_...` functions. +fn expr_as( + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, + f: F, +) -> IResult +where + F: FnOnce(ExpressionKind) -> Option, +{ + let self_argument = check_one_argument(arguments, location)?; + let expr = get_expr(self_argument)?; + let option_value = f(expr); + option(return_type, option_value) +} + +// fn name(self) -> Quoted +fn function_def_name( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let self_argument = check_one_argument(arguments, location)?; + let func_id = get_function_def(self_argument)?; + let name = interner.function_name(&func_id).to_string(); + let tokens = Rc::new(vec![Token::Ident(name)]); + Ok(Value::Quoted(tokens)) +} + +// fn parameters(self) -> [(Quoted, Type)] +fn function_def_parameters( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let self_argument = check_one_argument(arguments, location)?; + let func_id = get_function_def(self_argument)?; + let func_meta = interner.function_meta(&func_id); + + let parameters = func_meta + .parameters + .iter() + .map(|(hir_pattern, typ, _visibility)| { + let name = Value::Quoted(Rc::new(hir_pattern_to_tokens(interner, hir_pattern))); + let typ = Value::Type(typ.clone()); + Value::Tuple(vec![name, typ]) + }) + .collect(); + + let typ = Type::Slice(Box::new(Type::Tuple(vec![ + Type::Quoted(QuotedType::Quoted), + Type::Quoted(QuotedType::Type), + ]))); + + Ok(Value::Slice(parameters, typ)) +} + +// fn return_type(self) -> Type +fn function_def_return_type( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let self_argument = check_one_argument(arguments, location)?; + let func_id = get_function_def(self_argument)?; + let func_meta = interner.function_meta(&func_id); + + Ok(Value::Type(func_meta.return_type().follow_bindings())) +} + +// fn set_body(self, body: Quoted) +fn function_def_set_body( + interpreter: &mut Interpreter, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let (self_argument, body_argument) = check_two_arguments(arguments, location)?; + let body_argument_location = body_argument.1; + + let func_id = get_function_def(self_argument)?; + check_function_not_yet_resolved(interpreter, func_id, location)?; + + let body_tokens = get_quoted(body_argument)?; + let mut body_quoted = add_token_spans(body_tokens.clone(), body_argument_location.span); + + // Surround the body in `{ ... }` so we can parse it as a block + body_quoted.0.insert(0, SpannedToken::new(Token::LeftBrace, location.span)); + body_quoted.0.push(SpannedToken::new(Token::RightBrace, location.span)); + + let body = parse_tokens( + body_tokens, + body_quoted, + body_argument_location, + parser::block(parser::fresh_statement()), + "a block", + )?; + + let func_meta = interpreter.elaborator.interner.function_meta_mut(&func_id); + func_meta.has_body = true; + func_meta.function_body = FunctionBody::Unresolved(FunctionKind::Normal, body, location.span); + + Ok(Value::Unit) +} + +// fn set_parameters(self, parameters: [(Quoted, Type)]) +fn function_def_set_parameters( + interpreter: &mut Interpreter, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let (self_argument, parameters_argument) = check_two_arguments(arguments, location)?; + let parameters_argument_location = parameters_argument.1; + + let func_id = get_function_def(self_argument)?; + check_function_not_yet_resolved(interpreter, func_id, location)?; + + let (input_parameters, _type) = + get_slice(interpreter.elaborator.interner, parameters_argument)?; + + // What follows is very similar to what happens in Elaborator::define_function_meta + let mut parameters = Vec::new(); + let mut parameter_types = Vec::new(); + let mut parameter_idents = Vec::new(); + + for input_parameter in input_parameters { + let mut tuple = get_tuple( + interpreter.elaborator.interner, + (input_parameter, parameters_argument_location), + )?; + let parameter_type = get_type((tuple.pop().unwrap(), parameters_argument_location))?; + let parameter_pattern = parse( + (tuple.pop().unwrap(), parameters_argument_location), + parser::pattern(), + "a pattern", + )?; + + let hir_pattern = interpreter.elaborate_item(Some(func_id), |elaborator| { + elaborator.elaborate_pattern_and_store_ids( + parameter_pattern, + parameter_type.clone(), + DefinitionKind::Local(None), + &mut parameter_idents, + None, + ) + }); + + parameters.push((hir_pattern, parameter_type.clone(), Visibility::Private)); + parameter_types.push(parameter_type); + } + + mutate_func_meta_type(interpreter.elaborator.interner, func_id, |func_meta| { + func_meta.parameters = parameters.into(); + func_meta.parameter_idents = parameter_idents; + replace_func_meta_parameters(&mut func_meta.typ, parameter_types); + }); + + Ok(Value::Unit) +} + +// fn set_return_type(self, return_type: Type) +fn function_def_set_return_type( + interpreter: &mut Interpreter, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let (self_argument, return_type_argument) = check_two_arguments(arguments, location)?; + let return_type = get_type(return_type_argument)?; + + let func_id = get_function_def(self_argument)?; + check_function_not_yet_resolved(interpreter, func_id, location)?; + + let quoted_type_id = interpreter.elaborator.interner.push_quoted_type(return_type.clone()); + + mutate_func_meta_type(interpreter.elaborator.interner, func_id, |func_meta| { + func_meta.return_type = FunctionReturnType::Ty(UnresolvedType { + typ: UnresolvedTypeData::Resolved(quoted_type_id), + span: Some(location.span), + }); + replace_func_meta_return_type(&mut func_meta.typ, return_type); + }); + + Ok(Value::Unit) +} + +// fn functions(self) -> [FunctionDefinition] +fn module_functions( + interpreter: &Interpreter, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let self_argument = check_one_argument(arguments, location)?; + let module_id = get_module(self_argument)?; + let module_data = interpreter.elaborator.get_module(module_id); + let func_ids = module_data + .value_definitions() + .filter_map(|module_def_id| { + if let ModuleDefId::FunctionId(func_id) = module_def_id { + Some(Value::FunctionDefinition(func_id)) + } else { + None + } + }) + .collect(); + + let slice_type = Type::Slice(Box::new(Type::Quoted(QuotedType::FunctionDefinition))); + Ok(Value::Slice(func_ids, slice_type)) +} + +// fn is_contract(self) -> bool +fn module_is_contract( + interpreter: &Interpreter, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let self_argument = check_one_argument(arguments, location)?; + let module_id = get_module(self_argument)?; + Ok(Value::Bool(interpreter.elaborator.module_is_contract(module_id))) +} + +// fn name(self) -> Quoted +fn module_name( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let self_argument = check_one_argument(arguments, location)?; + let module_id = get_module(self_argument)?; + let name = &interner.module_attributes(&module_id).name; + let tokens = Rc::new(vec![Token::Ident(name.clone())]); + Ok(Value::Quoted(tokens)) +} + fn modulus_be_bits( _interner: &mut NodeInterner, arguments: Vec<(Value, Location)>, @@ -580,23 +1004,56 @@ fn modulus_num_bits( Ok(Value::U64(bits)) } +// fn quoted_eq(_first: Quoted, _second: Quoted) -> bool +fn quoted_eq(arguments: Vec<(Value, Location)>, location: Location) -> IResult { + let (self_value, other_value) = check_two_arguments(arguments, location)?; + + let self_quoted = get_quoted(self_value)?; + let other_quoted = get_quoted(other_value)?; + + Ok(Value::Bool(self_quoted == other_quoted)) +} + fn trait_def_as_trait_constraint( interner: &mut NodeInterner, - mut arguments: Vec<(Value, Location)>, + arguments: Vec<(Value, Location)>, location: Location, ) -> Result { - check_argument_count(1, &arguments, location)?; + let argument = check_one_argument(arguments, location)?; - let trait_id = get_trait_def(arguments.pop().unwrap().0, location)?; + let trait_id = get_trait_def(argument)?; let the_trait = interner.get_trait(trait_id); - - let trait_path = Path::from_ident(the_trait.name.clone()); - let trait_generics = vecmap(&the_trait.generics, |generic| { - let name = Path::from_single(generic.name.as_ref().clone(), generic.span); - UnresolvedTypeData::Named(name, Vec::new(), false).with_span(generic.span) + Type::NamedGeneric(generic.type_var.clone(), generic.name.clone(), generic.kind.clone()) }); - let trait_id = Some(trait_id); - Ok(Value::TraitConstraint(TraitBound { trait_path, trait_id, trait_generics })) + Ok(Value::TraitConstraint(trait_id, trait_generics)) +} + +/// Creates a value that holds an `Option`. +/// `option_type` must be a Type referencing the `Option` type. +pub(crate) fn option(option_type: Type, value: Option) -> IResult { + let t = extract_option_generic_type(option_type.clone()); + + let (is_some, value) = match value { + Some(value) => (Value::Bool(true), value), + None => (Value::Bool(false), zeroed(t)?), + }; + + let mut fields = HashMap::default(); + fields.insert(Rc::new("_is_some".to_string()), is_some); + fields.insert(Rc::new("_value".to_string()), value); + Ok(Value::Struct(fields, option_type)) +} + +/// Given a type, assert that it's an Option and return the Type for T +pub(crate) fn extract_option_generic_type(typ: Type) -> Type { + let Type::Struct(struct_type, mut generics) = typ else { + panic!("Expected type to be a struct"); + }; + + let struct_type = struct_type.borrow(); + assert_eq!(struct_type.name.0.contents, "Option"); + + generics.pop().expect("Expected Option to have a T generic type") } 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 new file mode 100644 index 000000000000..56f6c11974f3 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs @@ -0,0 +1,376 @@ +use std::rc::Rc; + +use acvm::FieldElement; +use noirc_errors::Location; + +use crate::{ + ast::{ExpressionKind, IntegerBitSize, Signedness}, + hir::{ + comptime::{errors::IResult, value::add_token_spans, Interpreter, InterpreterError, Value}, + def_map::ModuleId, + }, + hir_def::{ + function::{FuncMeta, FunctionBody}, + stmt::HirPattern, + }, + macros_api::{NodeInterner, StructId}, + node_interner::{FuncId, TraitId}, + parser::NoirParser, + token::{Token, Tokens}, + QuotedType, Type, +}; + +pub(crate) fn check_argument_count( + expected: usize, + arguments: &[(Value, Location)], + location: Location, +) -> IResult<()> { + if arguments.len() == expected { + Ok(()) + } else { + let actual = arguments.len(); + Err(InterpreterError::ArgumentCountMismatch { expected, actual, location }) + } +} + +pub(crate) fn check_one_argument( + mut arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult<(Value, Location)> { + check_argument_count(1, &arguments, location)?; + + Ok(arguments.pop().unwrap()) +} + +pub(crate) fn check_two_arguments( + mut arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult<((Value, Location), (Value, Location))> { + check_argument_count(2, &arguments, location)?; + + let argument2 = arguments.pop().unwrap(); + let argument1 = arguments.pop().unwrap(); + + Ok((argument1, argument2)) +} + +#[allow(clippy::type_complexity)] +pub(crate) fn check_three_arguments( + mut arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult<((Value, Location), (Value, Location), (Value, Location))> { + check_argument_count(3, &arguments, location)?; + + let argument3 = arguments.pop().unwrap(); + let argument2 = arguments.pop().unwrap(); + let argument1 = arguments.pop().unwrap(); + + Ok((argument1, argument2, argument3)) +} + +pub(crate) fn get_array( + interner: &NodeInterner, + (value, location): (Value, Location), +) -> IResult<(im::Vector, Type)> { + match value { + Value::Array(values, typ) => Ok((values, typ)), + value => { + let type_var = Box::new(interner.next_type_variable()); + let expected = Type::Array(type_var.clone(), type_var); + let actual = value.get_type().into_owned(); + Err(InterpreterError::TypeMismatch { expected, actual, location }) + } + } +} + +pub(crate) fn get_slice( + interner: &NodeInterner, + (value, location): (Value, Location), +) -> IResult<(im::Vector, Type)> { + match value { + Value::Slice(values, typ) => Ok((values, typ)), + value => { + let type_var = Box::new(interner.next_type_variable()); + let expected = Type::Slice(type_var); + let actual = value.get_type().into_owned(); + Err(InterpreterError::TypeMismatch { expected, actual, location }) + } + } +} + +pub(crate) fn get_tuple( + interner: &NodeInterner, + (value, location): (Value, Location), +) -> IResult> { + match value { + Value::Tuple(values) => Ok(values), + value => { + let type_var = interner.next_type_variable(); + let expected = Type::Tuple(vec![type_var]); + let actual = value.get_type().into_owned(); + Err(InterpreterError::TypeMismatch { expected, actual, location }) + } + } +} + +pub(crate) fn get_field((value, location): (Value, Location)) -> IResult { + match value { + Value::Field(value) => Ok(value), + value => { + let actual = value.get_type().into_owned(); + Err(InterpreterError::TypeMismatch { expected: Type::FieldElement, actual, location }) + } + } +} + +pub(crate) fn get_u8((value, location): (Value, Location)) -> IResult { + match value { + Value::U8(value) => Ok(value), + value => { + let expected = Type::Integer(Signedness::Unsigned, IntegerBitSize::Eight); + let actual = value.get_type().into_owned(); + Err(InterpreterError::TypeMismatch { expected, actual, location }) + } + } +} + +pub(crate) fn get_u32((value, location): (Value, Location)) -> IResult { + match value { + Value::U32(value) => Ok(value), + value => { + let expected = Type::Integer(Signedness::Unsigned, IntegerBitSize::ThirtyTwo); + let actual = value.get_type().into_owned(); + Err(InterpreterError::TypeMismatch { expected, actual, location }) + } + } +} + +pub(crate) fn get_expr((value, location): (Value, Location)) -> IResult { + match value { + Value::Expr(expr) => Ok(expr), + value => { + let expected = Type::Quoted(QuotedType::Expr); + let actual = value.get_type().into_owned(); + Err(InterpreterError::TypeMismatch { expected, actual, location }) + } + } +} + +pub(crate) fn get_function_def((value, location): (Value, Location)) -> IResult { + match value { + Value::FunctionDefinition(id) => Ok(id), + value => { + let expected = Type::Quoted(QuotedType::FunctionDefinition); + let actual = value.get_type().into_owned(); + Err(InterpreterError::TypeMismatch { expected, actual, location }) + } + } +} + +pub(crate) fn get_module((value, location): (Value, Location)) -> IResult { + match value { + Value::ModuleDefinition(module_id) => Ok(module_id), + value => { + let expected = Type::Quoted(QuotedType::Module); + let actual = value.get_type().into_owned(); + Err(InterpreterError::TypeMismatch { expected, actual, location }) + } + } +} + +pub(crate) fn get_struct((value, location): (Value, Location)) -> IResult { + match value { + Value::StructDefinition(id) => Ok(id), + _ => { + let expected = Type::Quoted(QuotedType::StructDefinition); + let actual = value.get_type().into_owned(); + Err(InterpreterError::TypeMismatch { expected, location, actual }) + } + } +} + +pub(crate) fn get_trait_constraint( + (value, location): (Value, Location), +) -> IResult<(TraitId, Vec)> { + match value { + Value::TraitConstraint(trait_id, generics) => Ok((trait_id, generics)), + value => { + let expected = Type::Quoted(QuotedType::TraitConstraint); + let actual = value.get_type().into_owned(); + Err(InterpreterError::TypeMismatch { expected, actual, location }) + } + } +} + +pub(crate) fn get_trait_def((value, location): (Value, Location)) -> IResult { + match value { + Value::TraitDefinition(id) => Ok(id), + value => { + let expected = Type::Quoted(QuotedType::TraitDefinition); + let actual = value.get_type().into_owned(); + Err(InterpreterError::TypeMismatch { expected, actual, location }) + } + } +} + +pub(crate) fn get_type((value, location): (Value, Location)) -> IResult { + match value { + Value::Type(typ) => Ok(typ), + value => { + let expected = Type::Quoted(QuotedType::Type); + let actual = value.get_type().into_owned(); + Err(InterpreterError::TypeMismatch { expected, actual, location }) + } + } +} + +pub(crate) fn get_quoted((value, location): (Value, Location)) -> IResult>> { + match value { + Value::Quoted(tokens) => Ok(tokens), + value => { + let expected = Type::Quoted(QuotedType::Quoted); + let actual = value.get_type().into_owned(); + Err(InterpreterError::TypeMismatch { expected, actual, location }) + } + } +} + +pub(crate) fn hir_pattern_to_tokens( + interner: &NodeInterner, + hir_pattern: &HirPattern, +) -> Vec { + let mut tokens = Vec::new(); + gather_hir_pattern_tokens(interner, hir_pattern, &mut tokens); + tokens +} + +fn gather_hir_pattern_tokens( + interner: &NodeInterner, + hir_pattern: &HirPattern, + tokens: &mut Vec, +) { + match hir_pattern { + HirPattern::Identifier(hir_ident) => { + let name = interner.definition_name(hir_ident.id).to_string(); + tokens.push(Token::Ident(name)); + } + HirPattern::Mutable(pattern, _) => { + tokens.push(Token::Keyword(crate::token::Keyword::Mut)); + gather_hir_pattern_tokens(interner, pattern, tokens); + } + HirPattern::Tuple(patterns, _) => { + tokens.push(Token::LeftParen); + for (index, pattern) in patterns.iter().enumerate() { + if index != 0 { + tokens.push(Token::Comma); + } + gather_hir_pattern_tokens(interner, pattern, tokens); + } + tokens.push(Token::RightParen); + } + HirPattern::Struct(typ, fields, _) => { + let Type::Struct(struct_type, _) = typ.follow_bindings() else { + panic!("Expected type to be a struct"); + }; + + let name = struct_type.borrow().name.to_string(); + tokens.push(Token::Ident(name)); + + tokens.push(Token::LeftBrace); + for (index, (field_name, pattern)) in fields.iter().enumerate() { + if index != 0 { + tokens.push(Token::Comma); + } + + let field_name = &field_name.0.contents; + tokens.push(Token::Ident(field_name.to_string())); + + // If we have a pattern like `Foo { x }`, that's internally represented as `Foo { x: x }` so + // here we check if the field name is the same as the pattern and, if so, omit the `: x` part. + let field_name_is_same_as_pattern = if let HirPattern::Identifier(pattern) = pattern + { + let pattern_name = interner.definition_name(pattern.id); + field_name == pattern_name + } else { + false + }; + + if !field_name_is_same_as_pattern { + tokens.push(Token::Colon); + gather_hir_pattern_tokens(interner, pattern, tokens); + } + } + tokens.push(Token::RightBrace); + } + } +} + +pub(super) fn check_function_not_yet_resolved( + interpreter: &Interpreter, + func_id: FuncId, + location: Location, +) -> Result<(), InterpreterError> { + let func_meta = interpreter.elaborator.interner.function_meta(&func_id); + match func_meta.function_body { + FunctionBody::Unresolved(_, _, _) => Ok(()), + FunctionBody::Resolving | FunctionBody::Resolved => { + Err(InterpreterError::FunctionAlreadyResolved { location }) + } + } +} + +pub(super) fn parse( + (value, location): (Value, Location), + parser: impl NoirParser, + rule: &'static str, +) -> IResult { + let tokens = get_quoted((value, location))?; + let quoted = add_token_spans(tokens.clone(), location.span); + parse_tokens(tokens, quoted, location, parser, rule) +} + +pub(super) fn parse_tokens( + tokens: Rc>, + quoted: Tokens, + location: Location, + parser: impl NoirParser, + rule: &'static str, +) -> IResult { + parser.parse(quoted).map_err(|mut errors| { + let error = errors.swap_remove(0); + InterpreterError::FailedToParseMacro { error, tokens, rule, file: location.file } + }) +} + +pub(super) fn mutate_func_meta_type(interner: &mut NodeInterner, func_id: FuncId, f: F) +where + F: FnOnce(&mut FuncMeta), +{ + let (name_id, function_type) = { + let func_meta = interner.function_meta_mut(&func_id); + f(func_meta); + (func_meta.name.id, func_meta.typ.clone()) + }; + + interner.push_definition_type(name_id, function_type); +} + +pub(super) fn replace_func_meta_parameters(typ: &mut Type, parameter_types: Vec) { + match typ { + Type::Function(parameters, _, _) => { + *parameters = parameter_types; + } + Type::Forall(_, typ) => replace_func_meta_parameters(typ, parameter_types), + _ => {} + } +} + +pub(super) fn replace_func_meta_return_type(typ: &mut Type, return_type: Type) { + match typ { + Type::Function(_, ret, _) => { + *ret = Box::new(return_type); + } + Type::Forall(_, typ) => replace_func_meta_return_type(typ, return_type), + _ => {} + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/foreign.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/foreign.rs index fc8c57ab634e..f7caf84ec422 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/foreign.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/foreign.rs @@ -4,11 +4,11 @@ use iter_extended::try_vecmap; use noirc_errors::Location; use crate::{ - hir::comptime::{errors::IResult, interpreter::builtin::get_field, InterpreterError, Value}, + hir::comptime::{errors::IResult, InterpreterError, Value}, macros_api::NodeInterner, }; -use super::builtin::{check_argument_count, get_array, get_u32}; +use super::builtin::builtin_helpers::{check_two_arguments, get_array, get_field, get_u32}; pub(super) fn call_foreign( interner: &mut NodeInterner, @@ -28,15 +28,16 @@ pub(super) fn call_foreign( // poseidon2_permutation(_input: [Field; N], _state_length: u32) -> [Field; N] fn poseidon2_permutation( interner: &mut NodeInterner, - mut arguments: Vec<(Value, Location)>, + arguments: Vec<(Value, Location)>, location: Location, ) -> IResult { - check_argument_count(2, &arguments, location)?; + let (input, state_length) = check_two_arguments(arguments, location)?; + let input_location = input.1; - let state_length = get_u32(arguments.pop().unwrap().0, location)?; - let (input, typ) = get_array(interner, arguments.pop().unwrap().0, location)?; + let (input, typ) = get_array(interner, input)?; + let state_length = get_u32(state_length)?; - let input = try_vecmap(input, |integer| get_field(integer, location))?; + let input = try_vecmap(input, |integer| get_field((integer, input_location)))?; // Currently locked to only bn254! let fields = Bn254BlackBoxSolver diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/unquote.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/unquote.rs index 94a848b891d3..c7b1532c9b7e 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/unquote.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/unquote.rs @@ -1,8 +1,8 @@ use noirc_errors::Location; use crate::{ - hir::comptime::{errors::IResult, value::unwrap_rc, Value}, - token::{SpannedToken, Token, Tokens}, + hir::comptime::errors::IResult, + token::{Token, Tokens}, }; use super::Interpreter; @@ -15,29 +15,20 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { &mut self, tokens: Tokens, location: Location, - ) -> IResult { + ) -> IResult> { let mut new_tokens = Vec::with_capacity(tokens.0.len()); for token in tokens.0 { - let span = token.to_span(); - match token.token() { + match token.into_token() { Token::UnquoteMarker(id) => { - match self.evaluate(*id)? { - // If the value is already quoted we don't want to change the token stream by - // turning it into a Quoted block (which would add `quote`, `{`, and `}` tokens). - Value::Code(stream) => new_tokens.extend(unwrap_rc(stream).0), - value => { - let new_id = - value.into_hir_expression(self.elaborator.interner, location)?; - let new_token = Token::UnquoteMarker(new_id); - new_tokens.push(SpannedToken::new(new_token, span)); - } - } + let value = self.evaluate(id)?; + let tokens = value.into_tokens(self.elaborator.interner, location)?; + new_tokens.extend(tokens); } - _ => new_tokens.push(token), + token => new_tokens.push(token), } } - Ok(Tokens(new_tokens)) + Ok(new_tokens) } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/tests.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/tests.rs index b4ffa1bd01d1..4c1adf9fca01 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/tests.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/tests.rs @@ -45,7 +45,7 @@ fn interpret_helper(src: &str) -> Result { let main = context.get_main_function(&krate).expect("Expected 'main' function"); let mut elaborator = - Elaborator::elaborate_and_return_self(&mut context, krate, collector.items, None); + Elaborator::elaborate_and_return_self(&mut context, krate, collector.items, None, false); assert_eq!(elaborator.errors.len(), 0); let mut interpreter = elaborator.setup_interpreter(); 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 f29b67bfc4ec..d5408309e554 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 @@ -4,10 +4,10 @@ use acvm::{AcirField, FieldElement}; use chumsky::Parser; use im::Vector; use iter_extended::{try_vecmap, vecmap}; -use noirc_errors::Location; +use noirc_errors::{Location, Span}; use crate::{ - ast::{ArrayLiteral, ConstructorExpression, Ident, IntegerBitSize, Signedness, TraitBound}, + ast::{ArrayLiteral, ConstructorExpression, Ident, IntegerBitSize, Signedness}, hir::def_map::ModuleId, hir_def::expr::{HirArrayLiteral, HirConstructorExpression, HirIdent, HirLambda, ImplKind}, macros_api::{ @@ -38,6 +38,7 @@ pub enum Value { U32(u32), U64(u64), String(Rc), + FormatString(Rc, Type), Function(FuncId, Type, Rc), Closure(HirLambda, Vec, Type), Tuple(Vec), @@ -45,13 +46,18 @@ pub enum Value { Pointer(Shared, /* auto_deref */ bool), Array(Vector, Type), Slice(Vector, Type), - Code(Rc), + /// Quoted tokens don't have spans because otherwise inserting them in the middle of other + /// tokens can cause larger spans to be before lesser spans, causing an assert. They may also + /// be inserted into separate files entirely. + Quoted(Rc>), StructDefinition(StructId), - TraitConstraint(TraitBound), + TraitConstraint(TraitId, /* trait generics */ Vec), TraitDefinition(TraitId), FunctionDefinition(FuncId), ModuleDefinition(ModuleId), + Type(Type), Zeroed(Type), + Expr(ExpressionKind), } impl Value { @@ -73,6 +79,7 @@ impl Value { let length = Type::Constant(value.len() as u32); Type::String(Box::new(length)) } + Value::FormatString(_, typ) => return Cow::Borrowed(typ), Value::Function(_, typ, _) => return Cow::Borrowed(typ), Value::Closure(_, _, typ) => return Cow::Borrowed(typ), Value::Tuple(fields) => { @@ -81,7 +88,7 @@ impl Value { Value::Struct(_, typ) => return Cow::Borrowed(typ), Value::Array(_, typ) => return Cow::Borrowed(typ), Value::Slice(_, typ) => return Cow::Borrowed(typ), - Value::Code(_) => Type::Quoted(QuotedType::Quoted), + Value::Quoted(_) => Type::Quoted(QuotedType::Quoted), Value::StructDefinition(_) => Type::Quoted(QuotedType::StructDefinition), Value::Pointer(element, auto_deref) => { if *auto_deref { @@ -95,7 +102,9 @@ impl Value { Value::TraitDefinition(_) => Type::Quoted(QuotedType::TraitDefinition), Value::FunctionDefinition(_) => Type::Quoted(QuotedType::FunctionDefinition), Value::ModuleDefinition(_) => Type::Quoted(QuotedType::Module), + Value::Type(_) => Type::Quoted(QuotedType::Type), Value::Zeroed(typ) => return Cow::Borrowed(typ), + Value::Expr(_) => Type::Quoted(QuotedType::Expr), }) } @@ -148,6 +157,10 @@ impl Value { ExpressionKind::Literal(Literal::Integer((value as u128).into(), false)) } Value::String(value) => ExpressionKind::Literal(Literal::Str(unwrap_rc(value))), + // Format strings are lowered as normal strings since they are already interpolated. + Value::FormatString(value, _) => { + ExpressionKind::Literal(Literal::Str(unwrap_rc(value))) + } Value::Function(id, typ, bindings) => { let id = interner.function_definition_id(id); let impl_kind = ImplKind::NotATraitMethod; @@ -196,9 +209,9 @@ impl Value { try_vecmap(elements, |element| element.into_expression(interner, location))?; ExpressionKind::Literal(Literal::Slice(ArrayLiteral::Standard(elements))) } - Value::Code(tokens) => { + Value::Quoted(tokens) => { // Wrap the tokens in '{' and '}' so that we can parse statements as well. - let mut tokens_to_parse = tokens.as_ref().clone(); + let mut tokens_to_parse = add_token_spans(tokens.clone(), location.span); tokens_to_parse.0.insert(0, SpannedToken::new(Token::LeftBrace, location.span)); tokens_to_parse.0.push(SpannedToken::new(Token::RightBrace, location.span)); @@ -212,14 +225,18 @@ impl Value { } }; } + Value::Expr(expr) => expr, Value::Pointer(..) | Value::StructDefinition(_) - | Value::TraitConstraint(_) + | Value::TraitConstraint(..) | Value::TraitDefinition(_) | Value::FunctionDefinition(_) | Value::Zeroed(_) + | Value::Type(_) | Value::ModuleDefinition(_) => { - return Err(InterpreterError::CannotInlineMacro { value: self, location }) + let typ = self.get_type().into_owned(); + let value = self.display(interner).to_string(); + return Err(InterpreterError::CannotInlineMacro { typ, value, location }); } }; @@ -277,6 +294,10 @@ impl Value { HirExpression::Literal(HirLiteral::Integer((value as u128).into(), false)) } Value::String(value) => HirExpression::Literal(HirLiteral::Str(unwrap_rc(value))), + // Format strings are lowered as normal strings since they are already interpolated. + Value::FormatString(value, _) => { + HirExpression::Literal(HirLiteral::Str(unwrap_rc(value))) + } Value::Function(id, typ, bindings) => { let id = interner.function_definition_id(id); let impl_kind = ImplKind::NotATraitMethod; @@ -326,15 +347,19 @@ impl Value { })?; HirExpression::Literal(HirLiteral::Slice(HirArrayLiteral::Standard(elements))) } - Value::Code(block) => HirExpression::Unquote(unwrap_rc(block)), - Value::Pointer(..) + Value::Quoted(tokens) => HirExpression::Unquote(add_token_spans(tokens, location.span)), + Value::Expr(..) + | Value::Pointer(..) | Value::StructDefinition(_) - | Value::TraitConstraint(_) + | Value::TraitConstraint(..) | Value::TraitDefinition(_) | Value::FunctionDefinition(_) | Value::Zeroed(_) + | Value::Type(_) | Value::ModuleDefinition(_) => { - return Err(InterpreterError::CannotInlineMacro { value: self, location }) + let typ = self.get_type().into_owned(); + let value = self.display(interner).to_string(); + return Err(InterpreterError::CannotInlineMacro { value, typ, location }); } }; @@ -344,6 +369,19 @@ impl Value { Ok(id) } + pub(crate) fn into_tokens( + self, + interner: &mut NodeInterner, + location: Location, + ) -> IResult> { + let token = match self { + Value::Quoted(tokens) => return Ok(unwrap_rc(tokens)), + Value::Type(typ) => Token::QuotedType(interner.push_quoted_type(typ)), + other => Token::UnquoteMarker(other.into_hir_expression(interner, location)?), + }; + Ok(vec![token]) + } + /// Converts any unsigned `Value` into a `u128`. /// Returns `None` for negative integers. pub(crate) fn to_u128(&self) -> Option { @@ -364,12 +402,24 @@ impl Value { pub(crate) fn into_top_level_items( self, location: Location, + interner: &NodeInterner, ) -> IResult> { match self { - Value::Code(tokens) => parse_tokens(tokens, parser::top_level_items(), location.file), - value => Err(InterpreterError::CannotInlineMacro { value, location }), + Value::Quoted(tokens) => parse_tokens(tokens, parser::top_level_items(), location), + _ => { + let typ = self.get_type().into_owned(); + let value = self.display(interner).to_string(); + Err(InterpreterError::CannotInlineMacro { value, typ, location }) + } } } + + pub fn display<'value, 'interner>( + &'value self, + interner: &'interner NodeInterner, + ) -> ValuePrinter<'value, 'interner> { + ValuePrinter { value: self, interner } + } } /// Unwraps an Rc value without cloning the inner value if the reference count is 1. Clones otherwise. @@ -377,20 +427,35 @@ pub(crate) fn unwrap_rc(rc: Rc) -> T { Rc::try_unwrap(rc).unwrap_or_else(|rc| (*rc).clone()) } -fn parse_tokens(tokens: Rc, parser: impl NoirParser, file: fm::FileId) -> IResult { - match parser.parse(tokens.as_ref().clone()) { +fn parse_tokens( + tokens: Rc>, + parser: impl NoirParser, + location: Location, +) -> IResult { + match parser.parse(add_token_spans(tokens.clone(), location.span)) { Ok(expr) => Ok(expr), Err(mut errors) => { let error = errors.swap_remove(0); let rule = "an expression"; + let file = location.file; Err(InterpreterError::FailedToParseMacro { error, file, tokens, rule }) } } } -impl Display for Value { +pub(crate) fn add_token_spans(tokens: Rc>, span: Span) -> Tokens { + let tokens = unwrap_rc(tokens); + Tokens(vecmap(tokens, |token| SpannedToken::new(token, span))) +} + +pub struct ValuePrinter<'value, 'interner> { + value: &'value Value, + interner: &'interner NodeInterner, +} + +impl<'value, 'interner> Display for ValuePrinter<'value, 'interner> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { + match self.value { Value::Unit => write!(f, "()"), Value::Bool(value) => { let msg = if *value { "true" } else { "false" }; @@ -407,10 +472,11 @@ impl Display for Value { Value::U32(value) => write!(f, "{value}"), Value::U64(value) => write!(f, "{value}"), Value::String(value) => write!(f, "{value}"), + Value::FormatString(value, _) => write!(f, "{value}"), Value::Function(..) => write!(f, "(function)"), Value::Closure(_, _, _) => write!(f, "(closure)"), Value::Tuple(fields) => { - let fields = vecmap(fields, ToString::to_string); + let fields = vecmap(fields, |field| field.display(self.interner).to_string()); write!(f, "({})", fields.join(", ")) } Value::Struct(fields, typ) => { @@ -418,31 +484,57 @@ impl Display for Value { Type::Struct(def, _) => def.borrow().name.to_string(), other => other.to_string(), }; - let fields = vecmap(fields, |(name, value)| format!("{}: {}", name, value)); + let fields = vecmap(fields, |(name, value)| { + format!("{}: {}", name, value.display(self.interner)) + }); write!(f, "{typename} {{ {} }}", fields.join(", ")) } - Value::Pointer(value, _) => write!(f, "&mut {}", value.borrow()), + Value::Pointer(value, _) => write!(f, "&mut {}", value.borrow().display(self.interner)), Value::Array(values, _) => { - let values = vecmap(values, ToString::to_string); + let values = vecmap(values, |value| value.display(self.interner).to_string()); write!(f, "[{}]", values.join(", ")) } Value::Slice(values, _) => { - let values = vecmap(values, ToString::to_string); + let values = vecmap(values, |value| value.display(self.interner).to_string()); write!(f, "&[{}]", values.join(", ")) } - Value::Code(tokens) => { + Value::Quoted(tokens) => { write!(f, "quote {{")?; - for token in tokens.0.iter() { - write!(f, " {token}")?; + for token in tokens.iter() { + match token { + Token::QuotedType(id) => { + write!(f, " {}", self.interner.get_quoted_type(*id))?; + } + other => write!(f, " {other}")?, + } } write!(f, " }}") } - Value::StructDefinition(_) => write!(f, "(struct definition)"), - Value::TraitConstraint { .. } => write!(f, "(trait constraint)"), - Value::TraitDefinition(_) => write!(f, "(trait definition)"), - Value::FunctionDefinition(_) => write!(f, "(function definition)"), + Value::StructDefinition(id) => { + let def = self.interner.get_struct(*id); + let def = def.borrow(); + write!(f, "{}", def.name) + } + Value::TraitConstraint(trait_id, generics) => { + let trait_ = self.interner.get_trait(*trait_id); + let generic_string = vecmap(generics, ToString::to_string).join(", "); + if generics.is_empty() { + write!(f, "{}", trait_.name) + } else { + write!(f, "{}<{generic_string}>", trait_.name) + } + } + Value::TraitDefinition(trait_id) => { + let trait_ = self.interner.get_trait(*trait_id); + write!(f, "{}", trait_.name) + } + Value::FunctionDefinition(function_id) => { + write!(f, "{}", self.interner.function_name(function_id)) + } Value::ModuleDefinition(_) => write!(f, "(module)"), Value::Zeroed(typ) => write!(f, "(zeroed {typ})"), + Value::Type(typ) => write!(f, "{}", typ), + Value::Expr(expr) => write!(f, "{}", expr), } } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index 80186c19c769..fabd76a28183 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -19,7 +19,8 @@ use crate::node_interner::{ use crate::ast::{ ExpressionKind, Ident, LetStatement, Literal, NoirFunction, NoirStruct, NoirTrait, - NoirTypeAlias, Path, PathKind, UnresolvedGenerics, UnresolvedTraitConstraint, UnresolvedType, + NoirTypeAlias, Path, PathKind, PathSegment, UnresolvedGenerics, UnresolvedTraitConstraint, + UnresolvedType, }; use crate::parser::{ParserError, SortedModule}; @@ -79,7 +80,6 @@ pub struct UnresolvedTraitImpl { pub methods: UnresolvedFunctions, pub generics: UnresolvedGenerics, pub where_clause: Vec, - pub is_comptime: bool, // Every field after this line is filled in later in the elaborator pub trait_id: Option, @@ -247,6 +247,7 @@ impl DefCollector { ast: SortedModule, root_file_id: FileId, debug_comptime_in_file: Option<&str>, + enable_arithmetic_generics: bool, macro_processors: &[&dyn MacroProcessor], ) -> Vec<(CompilationError, FileId)> { let mut errors: Vec<(CompilationError, FileId)> = vec![]; @@ -264,6 +265,7 @@ impl DefCollector { dep.crate_id, context, debug_comptime_in_file, + enable_arithmetic_generics, macro_processors, )); @@ -306,7 +308,7 @@ impl DefCollector { // Resolve unresolved imports collected from the crate, one by one. for collected_import in std::mem::take(&mut def_collector.imports) { let module_id = collected_import.module_id; - let resolved_import = if context.def_interner.track_references { + let resolved_import = if context.def_interner.lsp_mode { let mut references: Vec> = Vec::new(); let resolved_import = resolve_import( crate_id, @@ -318,13 +320,14 @@ impl DefCollector { let current_def_map = context.def_maps.get(&crate_id).unwrap(); let file_id = current_def_map.file_id(module_id); - for (referenced, ident) in references.iter().zip(&collected_import.path.segments) { + for (referenced, segment) in references.iter().zip(&collected_import.path.segments) + { let Some(referenced) = referenced else { continue; }; context.def_interner.add_reference( *referenced, - Location::new(ident.span(), file_id), + Location::new(segment.ident.span(), file_id), false, ); } @@ -351,7 +354,7 @@ impl DefCollector { .import(name.clone(), ns, resolved_import.is_prelude); let file_id = current_def_map.file_id(module_id); - let last_segment = collected_import.path.last_segment(); + let last_segment = collected_import.path.last_ident(); add_import_reference(ns, &last_segment, &mut context.def_interner, file_id); if let Some(ref alias) = collected_import.alias { @@ -385,8 +388,14 @@ impl DefCollector { }) }); - let mut more_errors = - Elaborator::elaborate(context, crate_id, def_collector.items, debug_comptime_in_file); + let mut more_errors = Elaborator::elaborate( + context, + crate_id, + def_collector.items, + debug_comptime_in_file, + enable_arithmetic_generics, + ); + errors.append(&mut more_errors); for macro_processor in macro_processors { @@ -425,7 +434,12 @@ fn inject_prelude( if !crate_id.is_stdlib() { let segments: Vec<_> = "std::prelude" .split("::") - .map(|segment| crate::ast::Ident::new(segment.into(), Span::default())) + .map(|segment| { + crate::ast::PathSegment::from(crate::ast::Ident::new( + segment.into(), + Span::default(), + )) + }) .collect(); let path = Path { @@ -446,7 +460,7 @@ fn inject_prelude( for path in prelude { let mut segments = segments.clone(); - segments.push(Ident::new(path.to_string(), Span::default())); + segments.push(PathSegment::from(Ident::new(path.to_string(), Span::default()))); collected_imports.insert( 0, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs index e5893dc43d51..be2afd13507e 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs @@ -173,8 +173,6 @@ impl<'a> ModCollector<'a> { let module = ModuleId { krate, local_id: self.module_id }; for (_, func_id, noir_function) in &mut unresolved_functions.functions { - // Attach any trait constraints on the impl to the function - noir_function.def.where_clause.append(&mut trait_impl.where_clause.clone()); let location = Location::new(noir_function.def.span, self.file_id); context.def_interner.push_function(*func_id, &noir_function.def, module, location); } @@ -188,7 +186,6 @@ impl<'a> ModCollector<'a> { generics: trait_impl.impl_generics, where_clause: trait_impl.where_clause, trait_generics: trait_impl.trait_generics, - is_comptime: trait_impl.is_comptime, // These last fields are filled later on trait_id: None, @@ -262,7 +259,8 @@ impl<'a> ModCollector<'a> { } /// Collect any struct definitions declared within the ast. - /// Returns a vector of errors if any structs were already defined. + /// Returns a vector of errors if any structs were already defined, + /// or if a struct has duplicate fields in it. fn collect_structs( &mut self, context: &mut Context, @@ -271,6 +269,8 @@ impl<'a> ModCollector<'a> { ) -> Vec<(CompilationError, FileId)> { let mut definition_errors = vec![]; for struct_definition in types { + self.check_duplicate_field_names(&struct_definition, &mut definition_errors); + let name = struct_definition.name.clone(); let unresolved = UnresolvedStruct { @@ -330,6 +330,29 @@ impl<'a> ModCollector<'a> { definition_errors } + fn check_duplicate_field_names( + &self, + struct_definition: &NoirStruct, + definition_errors: &mut Vec<(CompilationError, FileId)>, + ) { + let mut seen_field_names = std::collections::HashSet::new(); + for (field_name, _) in &struct_definition.fields { + if seen_field_names.insert(field_name) { + continue; + } + + let previous_field_name = *seen_field_names.get(field_name).unwrap(); + definition_errors.push(( + DefCollectorErrorKind::DuplicateField { + first_def: previous_field_name.clone(), + second_def: field_name.clone(), + } + .into(), + self.file_id, + )); + } + } + /// Collect any type aliases definitions declared within the ast. /// Returns a vector of errors if any type aliases were already defined. fn collect_type_aliases( 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 1ccf8dd47924..9e9471c0cb35 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 @@ -26,6 +26,8 @@ pub enum DuplicateType { pub enum DefCollectorErrorKind { #[error("duplicate {typ} found in namespace")] Duplicate { typ: DuplicateType, first_def: Ident, second_def: Ident }, + #[error("duplicate struct field {first_def}")] + DuplicateField { first_def: Ident, second_def: Ident }, #[error("unresolved import")] UnresolvedModuleDecl { mod_name: Ident, expected_path: String, alternative_path: String }, #[error("overlapping imports")] @@ -132,6 +134,23 @@ impl<'a> From<&'a DefCollectorErrorKind> for Diagnostic { diag } } + DefCollectorErrorKind::DuplicateField { first_def, second_def } => { + let primary_message = format!( + "Duplicate definitions of struct field with name {} found", + &first_def.0.contents + ); + { + let first_span = first_def.0.span(); + let second_span = second_def.0.span(); + let mut diag = Diagnostic::simple_error( + primary_message, + "First definition found here".to_string(), + first_span, + ); + diag.add_secondary("Second definition found here".to_string(), second_span); + diag + } + } DefCollectorErrorKind::UnresolvedModuleDecl { mod_name, expected_path, alternative_path } => { let span = mod_name.0.span(); let mod_name = &mod_name.0.contents; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/mod.rs index 9de96ab06e89..e607de52ff1e 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/mod.rs @@ -48,11 +48,13 @@ impl ModuleId { } impl ModuleId { - pub fn module(self, def_maps: &BTreeMap) -> &ModuleData { + pub fn module(self, def_maps: &DefMaps) -> &ModuleData { &def_maps[&self.krate].modules()[self.local_id.0] } } +pub type DefMaps = BTreeMap; + /// Map of all modules and scopes defined within a crate. /// /// The definitions of the crate are accessible indirectly via the scopes of each module. @@ -74,6 +76,7 @@ impl CrateDefMap { crate_id: CrateId, context: &mut Context, debug_comptime_in_file: Option<&str>, + enable_arithmetic_generics: bool, macro_processors: &[&dyn MacroProcessor], ) -> Vec<(CompilationError, FileId)> { // Check if this Crate has already been compiled @@ -123,6 +126,7 @@ impl CrateDefMap { ast, root_file_id, debug_comptime_in_file, + enable_arithmetic_generics, macro_processors, )); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/module_data.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/module_data.rs index 488ccc476d70..8a0125cfe958 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/module_data.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/module_data.rs @@ -38,10 +38,14 @@ impl ModuleData { } } - pub(crate) fn scope(&self) -> &ItemScope { + pub fn scope(&self) -> &ItemScope { &self.scope } + pub fn definitions(&self) -> &ItemScope { + &self.definitions + } + fn declare( &mut self, name: Ident, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/mod.rs index 87c4133d68ef..e4f000778d1f 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/mod.rs @@ -19,6 +19,7 @@ use iter_extended::vecmap; use noirc_errors::Location; use std::borrow::Cow; use std::collections::{BTreeMap, HashMap}; +use std::path::PathBuf; use std::rc::Rc; use self::def_map::TestFunction; @@ -47,6 +48,8 @@ pub struct Context<'file_manager, 'parsed_files> { // Same as the file manager, we take ownership of the parsed files in the WASM context. // Parsed files is also read only. pub parsed_files: Cow<'parsed_files, ParsedFiles>, + + pub package_build_path: PathBuf, } #[derive(Debug, Copy, Clone)] @@ -66,6 +69,7 @@ impl Context<'_, '_> { file_manager: Cow::Owned(file_manager), debug_instrumenter: DebugInstrumenter::default(), parsed_files: Cow::Owned(parsed_files), + package_build_path: PathBuf::default(), } } @@ -81,6 +85,7 @@ impl Context<'_, '_> { file_manager: Cow::Borrowed(file_manager), debug_instrumenter: DebugInstrumenter::default(), parsed_files: Cow::Borrowed(parsed_files), + package_build_path: PathBuf::default(), } } @@ -291,8 +296,8 @@ impl Context<'_, '_> { }) } - // Enables reference tracking (useful for tools like LSP). - pub fn track_references(&mut self) { - self.def_interner.track_references = true; + /// Activates LSP mode, which will track references for all definitions. + pub fn activate_lsp_mode(&mut self) { + self.def_interner.lsp_mode = true; } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/errors.rs index bf6de746791a..cfaa2063c40e 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/errors.rs @@ -114,8 +114,8 @@ pub enum ResolverError { MacroIsNotComptime { span: Span }, #[error("Annotation name must refer to a comptime function")] NonFunctionInAnnotation { span: Span }, - #[error("Unknown annotation")] - UnknownAnnotation { span: Span }, + #[error("Type `{typ}` was inserted into the generics list from a macro, but is not a generic")] + MacroResultInGenericsListNotAGeneric { span: Span, typ: Type }, } impl ResolverError { @@ -460,13 +460,13 @@ impl<'a> From<&'a ResolverError> for Diagnostic { *span, ) }, - ResolverError::UnknownAnnotation { span } => { - Diagnostic::simple_warning( - "Unknown annotation".into(), - "No matching comptime function found in scope".into(), + ResolverError::MacroResultInGenericsListNotAGeneric { span, typ } => { + Diagnostic::simple_error( + format!("Type `{typ}` was inserted into a generics list from a macro, but it is not a generic"), + format!("Type `{typ}` is not a generic"), *span, ) - }, + } } } } 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 10e18248dec6..4693d3826a80 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 @@ -6,7 +6,7 @@ use crate::hir::def_collector::dc_crate::CompilationError; use crate::node_interner::ReferenceId; use std::collections::BTreeMap; -use crate::ast::{Ident, ItemVisibility, Path, PathKind}; +use crate::ast::{Ident, ItemVisibility, Path, PathKind, PathSegment}; use crate::hir::def_map::{CrateDefMap, LocalModuleId, ModuleDefId, ModuleId, PerNs}; use super::errors::ResolverError; @@ -163,7 +163,8 @@ fn resolve_path_to_ns( let current_mod_id = ModuleId { krate: crate_id, local_id: import_directive.module_id }; let current_mod = &def_map.modules[current_mod_id.local_id.0]; - let first_segment = import_path.first().expect("ice: could not fetch first segment"); + let first_segment = + &import_path.first().expect("ice: could not fetch first segment").ident; if current_mod.find_name(first_segment).is_none() { // Resolve externally when first segment is unresolved return resolve_external_dep( @@ -218,7 +219,7 @@ fn resolve_path_from_crate_root( crate_id: CrateId, importing_crate: CrateId, - import_path: &[Ident], + import_path: &[PathSegment], def_maps: &BTreeMap, path_references: &mut Option<&mut Vec>>, ) -> NamespaceResolutionResult { @@ -235,7 +236,7 @@ fn resolve_path_from_crate_root( fn resolve_name_in_module( krate: CrateId, importing_crate: CrateId, - import_path: &[Ident], + import_path: &[PathSegment], starting_mod: LocalModuleId, def_maps: &BTreeMap, path_references: &mut Option<&mut Vec>>, @@ -254,7 +255,7 @@ fn resolve_name_in_module( }); } - let first_segment = import_path.first().expect("ice: could not fetch first segment"); + let first_segment = &import_path.first().expect("ice: could not fetch first segment").ident; let mut current_ns = current_mod.find_name(first_segment); if current_ns.is_none() { return Err(PathResolutionError::Unresolved(first_segment.clone())); @@ -262,6 +263,9 @@ fn resolve_name_in_module( let mut warning: Option = None; for (last_segment, current_segment) in import_path.iter().zip(import_path.iter().skip(1)) { + let last_segment = &last_segment.ident; + let current_segment = ¤t_segment.ident; + let (typ, visibility) = match current_ns.types { None => return Err(PathResolutionError::Unresolved(last_segment.clone())), Some((typ, visibility, _)) => (typ, visibility), @@ -324,7 +328,7 @@ fn resolve_name_in_module( fn resolve_path_name(import_directive: &ImportDirective) -> Ident { match &import_directive.alias { - None => import_directive.path.segments.last().unwrap().clone(), + None => import_directive.path.last_ident(), Some(ident) => ident.clone(), } } @@ -340,7 +344,7 @@ fn resolve_external_dep( let path = &directive.path.segments; // Fetch the root module from the prelude - let crate_name = path.first().unwrap(); + let crate_name = &path.first().unwrap().ident; let dep_module = current_def_map .extern_prelude .get(&crate_name.0.contents) diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/errors.rs index af168a10df9a..8eba8215f84e 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/errors.rs @@ -152,6 +152,8 @@ pub enum TypeCheckError { StringIndexAssign { span: Span }, #[error("Macro calls may only return `Quoted` values")] MacroReturningNonExpr { typ: Type, span: Span }, + #[error("turbofish (`::<_>`) usage at this position isn't supported yet")] + UnsupportedTurbofishUsage { span: Span }, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -350,6 +352,10 @@ impl<'a> From<&'a TypeCheckError> for Diagnostic { "Macro calls must return quoted values, otherwise there is no code to insert".into(), *span, ), + TypeCheckError::UnsupportedTurbofishUsage { span } => { + let msg = "turbofish (`::<_>`) usage at this position isn't supported yet"; + Diagnostic::simple_error(msg.to_string(), "".to_string(), *span) + }, } } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/function.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/function.rs index b9f6af0c4c3b..6b66cf1ab4a3 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/function.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/function.rs @@ -1,3 +1,4 @@ +use fm::FileId; use iter_extended::vecmap; use noirc_errors::{Location, Span}; @@ -156,6 +157,13 @@ pub struct FuncMeta { /// The module this function was defined in pub source_module: LocalModuleId, + + /// THe file this function was defined in + pub source_file: FileId, + + /// If this function is from an impl (trait or regular impl), this + /// is the object type of the impl. Otherwise this is None. + pub self_type: Option, } #[derive(Debug, Clone)] 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 0ec975a04dba..177d23c74dde 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 @@ -21,7 +21,7 @@ use crate::{ use super::expr::{HirCallExpression, HirExpression, HirIdent}; -#[derive(PartialEq, Eq, Clone, Hash)] +#[derive(PartialEq, Eq, Clone, Hash, Ord, PartialOrd)] pub enum Type { /// A primitive Field type FieldElement, @@ -107,6 +107,8 @@ pub enum Type { /// The type of quoted code in macros. This is always a comptime-only type Quoted(QuotedType), + InfixExpr(Box, BinaryTypeOperator, Box), + /// The result of some type error. Remembering type errors as their own type variant lets /// us avoid issuing repeat type errors for the same item. For example, a lambda with /// an invalid type would otherwise issue a new error each time it is called @@ -120,7 +122,7 @@ pub enum Type { /// For example, the type of a struct field or a function parameter is expected to be /// a type of kind * (represented here as `Normal`). Types used in positions where a number /// is expected (such as in an array length position) are expected to be of kind `Kind::Numeric`. -#[derive(PartialEq, Eq, Clone, Hash, Debug)] +#[derive(PartialEq, Eq, Clone, Hash, Debug, PartialOrd, Ord)] pub enum Kind { Normal, Numeric(Box), @@ -135,7 +137,7 @@ impl std::fmt::Display for Kind { } } -#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)] +#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash, PartialOrd, Ord)] pub enum QuotedType { Expr, Quoted, @@ -191,6 +193,12 @@ pub struct ResolvedGeneric { pub span: Span, } +impl ResolvedGeneric { + pub fn as_named_generic(self) -> Type { + Type::NamedGeneric(self.type_var, self.name, self.kind) + } +} + impl std::hash::Hash for StructType { fn hash(&self, state: &mut H) { self.id.hash(state); @@ -203,6 +211,18 @@ impl PartialEq for StructType { } } +impl PartialOrd for StructType { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for StructType { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.id.cmp(&other.id) + } +} + impl StructType { pub fn new( id: StructId, @@ -333,6 +353,18 @@ impl PartialEq for TypeAlias { } } +impl Ord for TypeAlias { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.id.cmp(&other.id) + } +} + +impl PartialOrd for TypeAlias { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + impl std::fmt::Display for TypeAlias { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.name) @@ -425,7 +457,7 @@ impl Shared { /// A restricted subset of binary operators useable on /// type level integers for use in the array length positions of types. -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub enum BinaryTypeOperator { Addition, Subtraction, @@ -434,7 +466,7 @@ pub enum BinaryTypeOperator { Modulo, } -#[derive(Debug, PartialEq, Eq, Clone, Hash)] +#[derive(Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord)] pub enum TypeVariableKind { /// Can bind to any type Normal, @@ -458,7 +490,7 @@ pub enum TypeVariableKind { /// A TypeVariable is a mutable reference that is either /// bound to some type, or unbound with a given TypeVariableId. -#[derive(PartialEq, Eq, Clone, Hash)] +#[derive(PartialEq, Eq, Clone, Hash, PartialOrd, Ord)] pub struct TypeVariable(TypeVariableId, Shared); impl TypeVariable { @@ -527,7 +559,7 @@ impl TypeVariable { /// TypeBindings are the mutable insides of a TypeVariable. /// They are either bound to some type, or are unbound. -#[derive(Clone, PartialEq, Eq, Hash, Debug)] +#[derive(Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] pub enum TypeBinding { Bound(Type), Unbound(TypeVariableId), @@ -540,7 +572,7 @@ impl TypeBinding { } /// A unique ID used to differentiate different type variables -#[derive(Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct TypeVariableId(pub usize); impl std::fmt::Display for Type { @@ -644,6 +676,16 @@ impl std::fmt::Display for Type { write!(f, "&mut {element}") } Type::Quoted(quoted) => write!(f, "{}", quoted), + Type::InfixExpr(lhs, op, rhs) => { + let this = self.canonicalize(); + + // Prevent infinite recursion + if this != *self { + write!(f, "{this}") + } else { + write!(f, "({lhs} {op} {rhs})") + } + } } } } @@ -838,6 +880,9 @@ impl Type { elements.contains_numeric_typevar(target_id) || named_generic_id_matches_target(length) } + Type::InfixExpr(lhs, _op, rhs) => { + lhs.contains_numeric_typevar(target_id) || rhs.contains_numeric_typevar(target_id) + } } } @@ -917,6 +962,10 @@ impl Type { elements.find_numeric_type_vars(found_names); named_generic_is_numeric(length, found_names); } + Type::InfixExpr(lhs, _op, rhs) => { + lhs.find_numeric_type_vars(found_names); + rhs.find_numeric_type_vars(found_names); + } } } @@ -946,6 +995,7 @@ impl Type { | Type::Forall(_, _) | Type::Quoted(_) | Type::Slice(_) + | Type::InfixExpr(_, _, _) | Type::TraitAsType(..) => false, Type::Alias(alias, generics) => { @@ -983,6 +1033,7 @@ impl Type { | Type::Constant(_) | Type::TypeVariable(_, _) | Type::NamedGeneric(_, _, _) + | Type::InfixExpr(..) | Type::Error => true, Type::FmtString(_, _) @@ -1028,6 +1079,7 @@ impl Type { | Type::NamedGeneric(_, _, _) | Type::Function(_, _, _) | Type::FmtString(_, _) + | Type::InfixExpr(..) | Type::Error => true, // Quoted objects only exist at compile-time where the only execution @@ -1162,6 +1214,7 @@ impl Type { | Type::Constant(_) | Type::Quoted(_) | Type::Slice(_) + | Type::InfixExpr(..) | Type::Error => unreachable!("This type cannot exist as a parameter to main"), } } @@ -1416,7 +1469,17 @@ impl Type { use Type::*; use TypeVariableKind as Kind; - match (self, other) { + let lhs = match self { + Type::InfixExpr(..) => Cow::Owned(self.canonicalize()), + other => Cow::Borrowed(other), + }; + + let rhs = match other { + Type::InfixExpr(..) => Cow::Owned(other.canonicalize()), + other => Cow::Borrowed(other), + }; + + match (lhs.as_ref(), rhs.as_ref()) { (Error, _) | (_, Error) => Ok(()), (Alias(alias, args), other) | (other, Alias(alias, args)) => { @@ -1530,6 +1593,27 @@ impl Type { elem_a.try_unify(elem_b, bindings) } + (InfixExpr(lhs_a, op_a, rhs_a), InfixExpr(lhs_b, op_b, rhs_b)) => { + if op_a == op_b { + lhs_a.try_unify(lhs_b, bindings)?; + rhs_a.try_unify(rhs_b, bindings) + } else { + Err(UnificationError) + } + } + + (Constant(value), other) | (other, Constant(value)) => { + if let Some(other_value) = other.evaluate_to_u32() { + if *value == other_value { + Ok(()) + } else { + Err(UnificationError) + } + } else { + Err(UnificationError) + } + } + (other_a, other_b) => { if other_a == other_b { Ok(()) @@ -1540,6 +1624,107 @@ impl Type { } } + /// Try to canonicalize the representation of this type. + /// Currently the only type with a canonical representation is + /// `Type::Infix` where for each consecutive commutative operator + /// we sort the non-constant operands by `Type: Ord` and place all constant + /// operands at the end, constant folded. + /// + /// For example: + /// - `canonicalize[((1 + N) + M) + 2] = (M + N) + 3` + /// - `canonicalize[A + 2 * B + 3 - 2] = A + (B * 2) + 3 - 2` + pub fn canonicalize(&self) -> Type { + match self.follow_bindings() { + Type::InfixExpr(lhs, op, rhs) => { + if let Some(value) = self.evaluate_to_u32() { + return Type::Constant(value); + } + + let lhs = lhs.canonicalize(); + let rhs = rhs.canonicalize(); + + if let Some(result) = Self::try_simplify_subtraction(&lhs, op, &rhs) { + return result; + } + + if op.is_commutative() { + return Self::sort_commutative(&lhs, op, &rhs); + } + + Type::InfixExpr(Box::new(lhs), op, Box::new(rhs)) + } + other => other, + } + } + + fn sort_commutative(lhs: &Type, op: BinaryTypeOperator, rhs: &Type) -> Type { + let mut queue = vec![lhs.clone(), rhs.clone()]; + + let mut sorted = BTreeSet::new(); + + let zero_value = if op == BinaryTypeOperator::Addition { 0 } else { 1 }; + let mut constant = zero_value; + + // Push each non-constant term to `sorted` to sort them. Recur on InfixExprs with the same operator. + while let Some(item) = queue.pop() { + match item.canonicalize() { + Type::InfixExpr(lhs, new_op, rhs) if new_op == op => { + queue.push(*lhs); + queue.push(*rhs); + } + Type::Constant(new_constant) => { + constant = op.function(constant, new_constant); + } + other => { + sorted.insert(other); + } + } + } + + if let Some(first) = sorted.pop_first() { + let mut typ = first.clone(); + + for rhs in sorted { + typ = Type::InfixExpr(Box::new(typ), op, Box::new(rhs.clone())); + } + + if constant != zero_value { + typ = Type::InfixExpr(Box::new(typ), op, Box::new(Type::Constant(constant))); + } + + typ + } else { + // Every type must have been a constant + Type::Constant(constant) + } + } + + /// Try to simplify a subtraction expression of `lhs - rhs`. + /// + /// - Simplifies `(a + C1) - C2` to `a + (C1 - C2)` if C1 and C2 are constants. + fn try_simplify_subtraction(lhs: &Type, op: BinaryTypeOperator, rhs: &Type) -> Option { + use BinaryTypeOperator::*; + match lhs { + Type::InfixExpr(l_lhs, l_op, l_rhs) => { + // Simplify `(N + 2) - 1` + if op == Subtraction && *l_op == Addition { + if let (Some(lhs_const), Some(rhs_const)) = + (l_rhs.evaluate_to_u32(), rhs.evaluate_to_u32()) + { + if lhs_const > rhs_const { + let constant = Box::new(Type::Constant(lhs_const - rhs_const)); + return Some( + Type::InfixExpr(l_lhs.clone(), *l_op, constant).canonicalize(), + ); + } + } + } + None + } + _ => None, + } + } + /// Try to unify a type variable to `self`. /// This is a helper function factored out from try_unify. fn try_unify_to_type_variable( @@ -1637,6 +1822,11 @@ impl Type { Type::TypeVariable(_, TypeVariableKind::Constant(size)) => Some(*size), Type::Array(len, _elem) => len.evaluate_to_u32(), Type::Constant(x) => Some(*x), + Type::InfixExpr(lhs, op, rhs) => { + let lhs = lhs.evaluate_to_u32()?; + let rhs = rhs.evaluate_to_u32()?; + Some(op.function(lhs, rhs)) + } _ => None, } } @@ -1898,6 +2088,11 @@ impl Type { }); Type::TraitAsType(*s, name.clone(), args) } + Type::InfixExpr(lhs, op, rhs) => { + let lhs = lhs.substitute_helper(type_bindings, substitute_bound_typevars); + let rhs = rhs.substitute_helper(type_bindings, substitute_bound_typevars); + Type::InfixExpr(Box::new(lhs), *op, Box::new(rhs)) + } Type::FieldElement | Type::Integer(_, _) @@ -1943,6 +2138,7 @@ impl Type { || env.occurs(target_id) } Type::MutableReference(element) => element.occurs(target_id), + Type::InfixExpr(lhs, _op, rhs) => lhs.occurs(target_id) || rhs.occurs(target_id), Type::FieldElement | Type::Integer(_, _) @@ -2003,6 +2199,11 @@ impl Type { let args = vecmap(args, |arg| arg.follow_bindings()); TraitAsType(*s, name.clone(), args) } + InfixExpr(lhs, op, rhs) => { + let lhs = lhs.follow_bindings(); + let rhs = rhs.follow_bindings(); + InfixExpr(Box::new(lhs), *op, Box::new(rhs)) + } // Expect that this function should only be called on instantiated types Forall(..) => unreachable!(), @@ -2090,6 +2291,17 @@ impl Type { } Type::MutableReference(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(); + rhs.replace_named_generics_with_type_variables(); + } + } + } + + pub fn slice_element_type(&self) -> Option<&Type> { + match self { + Type::Slice(element) => Some(element), + _ => None, } } } @@ -2130,16 +2342,20 @@ fn convert_array_expression_to_slice( } impl BinaryTypeOperator { - /// Return the actual rust numeric function associated with this operator - pub fn function(self) -> fn(u32, u32) -> u32 { + /// Perform the actual rust numeric operation associated with this operator + pub fn function(self, a: u32, b: u32) -> u32 { match self { - BinaryTypeOperator::Addition => |a, b| a.wrapping_add(b), - BinaryTypeOperator::Subtraction => |a, b| a.wrapping_sub(b), - BinaryTypeOperator::Multiplication => |a, b| a.wrapping_mul(b), - BinaryTypeOperator::Division => |a, b| a.wrapping_div(b), - BinaryTypeOperator::Modulo => |a, b| a.wrapping_rem(b), // % b, + BinaryTypeOperator::Addition => a.wrapping_add(b), + BinaryTypeOperator::Subtraction => a.wrapping_sub(b), + BinaryTypeOperator::Multiplication => a.wrapping_mul(b), + BinaryTypeOperator::Division => a.wrapping_div(b), + BinaryTypeOperator::Modulo => a.wrapping_rem(b), } } + + fn is_commutative(self) -> bool { + matches!(self, BinaryTypeOperator::Addition | BinaryTypeOperator::Multiplication) + } } impl TypeVariableKind { @@ -2222,6 +2438,7 @@ impl From<&Type> for PrintableType { PrintableType::MutableReference { typ: Box::new(typ.as_ref().into()) } } Type::Quoted(_) => unreachable!(), + Type::InfixExpr(..) => unreachable!(), } } } @@ -2314,6 +2531,7 @@ impl std::fmt::Debug for Type { write!(f, "&mut {element:?}") } Type::Quoted(quoted) => write!(f, "{}", quoted), + Type::InfixExpr(lhs, op, rhs) => write!(f, "({lhs:?} {op} {rhs:?})"), } } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/lexer/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/lexer/errors.rs index 387ced05258c..be5180a777b7 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/lexer/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/lexer/errors.rs @@ -1,3 +1,4 @@ +use crate::hir::def_collector::dc_crate::CompilationError; use crate::parser::ParserError; use crate::parser::ParserErrorReason; use crate::token::SpannedToken; @@ -42,6 +43,12 @@ impl From for ParserError { } } +impl From for CompilationError { + fn from(error: LexerErrorKind) -> Self { + ParserError::from(error).into() + } +} + impl LexerErrorKind { pub fn span(&self) -> Span { match self { 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 c6a1d44f26ba..4222d2b585fe 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs @@ -635,6 +635,10 @@ impl Attributes { pub fn is_no_predicates(&self) -> bool { self.function.as_ref().map_or(false, |func_attribute| func_attribute.is_no_predicates()) } + + pub fn is_varargs(&self) -> bool { + self.secondary.iter().any(|attr| matches!(attr, SecondaryAttribute::Varargs)) + } } /// An Attribute can be either a Primary Attribute or a Secondary Attribute @@ -728,6 +732,7 @@ impl Attribute { name.trim_matches('"').to_string().into(), )) } + ["varargs"] => Attribute::Secondary(SecondaryAttribute::Varargs), tokens => { tokens.iter().try_for_each(|token| validate(token))?; Attribute::Secondary(SecondaryAttribute::Custom(word.to_owned())) @@ -825,6 +830,9 @@ pub enum SecondaryAttribute { Field(String), Custom(String), Abi(String), + + /// A variable-argument comptime function. + Varargs, } impl fmt::Display for SecondaryAttribute { @@ -839,6 +847,7 @@ impl fmt::Display for SecondaryAttribute { SecondaryAttribute::Export => write!(f, "#[export]"), SecondaryAttribute::Field(ref k) => write!(f, "#[field({k})]"), SecondaryAttribute::Abi(ref k) => write!(f, "#[abi({k})]"), + SecondaryAttribute::Varargs => write!(f, "#[varargs]"), } } } @@ -867,14 +876,14 @@ impl AsRef for SecondaryAttribute { | SecondaryAttribute::Abi(string) => string, SecondaryAttribute::ContractLibraryMethod => "", SecondaryAttribute::Export => "", + SecondaryAttribute::Varargs => "", } } } /// Note that `self` is not present - it is a contextual keyword rather than a true one as it is /// only special within `impl`s. Otherwise `self` functions as a normal identifier. -#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone, PartialOrd, Ord)] -#[cfg_attr(test, derive(strum_macros::EnumIter))] +#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone, PartialOrd, Ord, strum_macros::EnumIter)] pub enum Keyword { As, Assert, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/locations.rs b/noir/noir-repo/compiler/noirc_frontend/src/locations.rs index 0ba74e22781f..c437676b605d 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/locations.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/locations.rs @@ -147,7 +147,7 @@ impl NodeInterner { location: Location, is_self_type: bool, ) { - if !self.track_references { + if !self.lsp_mode { return; } @@ -166,7 +166,7 @@ impl NodeInterner { referenced: ReferenceId, module_id: Option, ) { - if !self.track_references { + if !self.lsp_mode { return; } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/ast.rs b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/ast.rs index 47698c5b65c5..f2ed9433e615 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/ast.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/ast.rs @@ -10,6 +10,7 @@ use crate::{ ast::{BinaryOpKind, IntegerBitSize, Signedness, Visibility}, token::{Attributes, FunctionAttribute}, }; +use serde::{Deserialize, Serialize}; use super::HirType; @@ -207,7 +208,7 @@ pub type Parameters = Vec<(LocalId, /*mutable:*/ bool, /*name:*/ String, Type)>; /// Represents how an Acir function should be inlined. /// This type is only relevant for ACIR functions as we do not inline any Brillig functions -#[derive(Default, Clone, Copy, PartialEq, Eq, Debug, Hash)] +#[derive(Default, Clone, Copy, PartialEq, Eq, Debug, Hash, Serialize, Deserialize)] pub enum InlineType { /// The most basic entry point can expect all its functions to be inlined. /// All function calls are expected to be inlined into a single ACIR. 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 a46f32e30949..5ac730db4006 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -383,8 +383,8 @@ impl<'interner> Monomorphizer<'interner> { self.parameter(field, &typ, new_params)?; } } - HirPattern::Struct(_, fields, _) => { - let struct_field_types = unwrap_struct_type(typ); + HirPattern::Struct(_, fields, location) => { + let struct_field_types = unwrap_struct_type(typ, *location)?; assert_eq!(struct_field_types.len(), fields.len()); let mut fields = @@ -663,8 +663,10 @@ impl<'interner> Monomorphizer<'interner> { constructor: HirConstructorExpression, id: node_interner::ExprId, ) -> Result { + let location = self.interner.expr_location(&id); + let typ = self.interner.id_type(id); - let field_types = unwrap_struct_type(&typ); + let field_types = unwrap_struct_type(&typ, location)?; let field_type_map = btree_map(&field_types, |x| x.clone()); @@ -740,8 +742,8 @@ impl<'interner> Monomorphizer<'interner> { let fields = unwrap_tuple_type(typ); self.unpack_tuple_pattern(value, patterns.into_iter().zip(fields)) } - HirPattern::Struct(_, patterns, _) => { - let fields = unwrap_struct_type(typ); + HirPattern::Struct(_, patterns, location) => { + let fields = unwrap_struct_type(typ, location)?; assert_eq!(patterns.len(), fields.len()); let mut patterns = @@ -975,12 +977,24 @@ impl<'interner> Monomorphizer<'interner> { } HirType::Struct(def, args) => { + // Not all generic arguments may be used in a struct's fields so we have to check + // the arguments as well as the fields in case any need to be defaulted or are unbound. + for arg in args { + Self::check_type(arg, location)?; + } + let fields = def.borrow().get_fields(args); let fields = try_vecmap(fields, |(_, field)| Self::convert_type(&field, location))?; ast::Type::Tuple(fields) } HirType::Alias(def, args) => { + // Similar to the struct case above: generics of an alias might not end up being + // used in the type that is aliased. + for arg in args { + Self::check_type(arg, location)?; + } + Self::convert_type(&def.borrow().get_type(args), location)? } @@ -1012,13 +1026,97 @@ impl<'interner> Monomorphizer<'interner> { ast::Type::MutableReference(Box::new(element)) } - HirType::Forall(_, _) | HirType::Constant(_) | HirType::Error => { + HirType::Forall(_, _) + | HirType::Constant(_) + | HirType::InfixExpr(..) + | HirType::Error => { unreachable!("Unexpected type {} found", typ) } HirType::Quoted(_) => unreachable!("Tried to translate Code type into runtime code"), }) } + // Similar to `convert_type` but returns an error if any type variable can't be defaulted. + fn check_type(typ: &HirType, location: Location) -> Result<(), MonomorphizationError> { + match typ { + HirType::FieldElement + | HirType::Integer(..) + | HirType::Bool + | HirType::String(..) + | HirType::Unit + | HirType::TraitAsType(..) + | HirType::Forall(_, _) + | HirType::Constant(_) + | HirType::Error + | HirType::Quoted(_) => Ok(()), + HirType::FmtString(_size, fields) => Self::check_type(fields.as_ref(), location), + HirType::Array(_length, element) => Self::check_type(element.as_ref(), location), + HirType::Slice(element) => Self::check_type(element.as_ref(), location), + HirType::NamedGeneric(binding, _, _) => { + if let TypeBinding::Bound(binding) = &*binding.borrow() { + return Self::check_type(binding, location); + } + + Ok(()) + } + + HirType::TypeVariable(binding, kind) => { + if let TypeBinding::Bound(binding) = &*binding.borrow() { + return Self::check_type(binding, location); + } + + // Default any remaining unbound type variables. + // This should only happen if the variable in question is unused + // and within a larger generic type. + let default = match kind.default_type() { + Some(typ) => typ, + None => return Err(MonomorphizationError::TypeAnnotationsNeeded { location }), + }; + + Self::check_type(&default, location) + } + + HirType::Struct(_def, args) => { + for arg in args { + Self::check_type(arg, location)?; + } + + Ok(()) + } + + HirType::Alias(_def, args) => { + for arg in args { + Self::check_type(arg, location)?; + } + + Ok(()) + } + + HirType::Tuple(fields) => { + for field in fields { + Self::check_type(field, location)?; + } + + Ok(()) + } + + HirType::Function(args, ret, env) => { + for arg in args { + Self::check_type(arg, location)?; + } + + Self::check_type(ret, location)?; + Self::check_type(env, location) + } + + HirType::MutableReference(element) => Self::check_type(element, location), + HirType::InfixExpr(lhs, _, rhs) => { + Self::check_type(lhs, location)?; + Self::check_type(rhs, location) + } + } + } + fn is_function_closure(&self, t: ast::Type) -> bool { if self.is_function_closure_type(&t) { true @@ -1595,7 +1693,7 @@ impl<'interner> Monomorphizer<'interner> { self.create_zeroed_function(parameter_types, ret_type, env, location) } ast::Type::Slice(element_type) => { - ast::Expression::Literal(ast::Literal::Array(ast::ArrayLiteral { + ast::Expression::Literal(ast::Literal::Slice(ast::ArrayLiteral { contents: vec![], typ: ast::Type::Slice(element_type.clone()), })) @@ -1753,9 +1851,19 @@ fn unwrap_tuple_type(typ: &HirType) -> Vec { } } -fn unwrap_struct_type(typ: &HirType) -> Vec<(String, HirType)> { +fn unwrap_struct_type( + typ: &HirType, + location: Location, +) -> Result, MonomorphizationError> { match typ.follow_bindings() { - HirType::Struct(def, args) => def.borrow().get_fields(&args), + HirType::Struct(def, args) => { + // Some of args might not be mentioned in fields, so we need to check that they aren't unbound. + for arg in &args { + Monomorphizer::check_type(arg, location)?; + } + + Ok(def.borrow().get_fields(&args)) + } other => unreachable!("unwrap_struct_type: expected struct, found {:?}", other), } } 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 87ff45f8f1a4..43e742b940e7 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs @@ -1,5 +1,4 @@ use std::borrow::Cow; -use std::collections::HashMap; use std::fmt; use std::hash::Hash; use std::marker::Copy; @@ -12,6 +11,7 @@ use noirc_errors::{Location, Span, Spanned}; use petgraph::algo::tarjan_scc; use petgraph::prelude::DiGraph; use petgraph::prelude::NodeIndex as PetGraphIndex; +use rustc_hash::FxHashMap as HashMap; use crate::ast::Ident; use crate::graph::CrateId; @@ -200,8 +200,8 @@ pub struct NodeInterner { /// the actual type since types do not implement Send or Sync. quoted_types: noirc_arena::Arena, - /// Whether to track references. In regular compilations this is false, but tools set it to true. - pub(crate) track_references: bool, + /// Determins whether to run in LSP mode. In LSP mode references are tracked. + pub(crate) lsp_mode: bool, /// Store the location of the references in the graph. /// Edges are directed from reference nodes to referenced nodes. @@ -230,6 +230,14 @@ pub struct NodeInterner { // The module where each reference is // (ReferenceId::Reference and ReferenceId::Local aren't included here) pub(crate) reference_modules: HashMap, + + /// Each value currently in scope in the comptime interpreter. + /// Each element of the Vec represents a scope with every scope together making + /// up all currently visible definitions. The first scope is always the global scope. + /// + /// This is stored in the NodeInterner so that the Elaborator from each crate can + /// share the same global values. + pub(crate) comptime_scopes: Vec>, } /// A dependency in the dependency graph may be a type or a definition. @@ -556,44 +564,45 @@ impl Default for NodeInterner { fn default() -> Self { NodeInterner { nodes: Arena::default(), - func_meta: HashMap::new(), - function_definition_ids: HashMap::new(), - function_modifiers: HashMap::new(), - function_modules: HashMap::new(), - module_attributes: HashMap::new(), - func_id_to_trait: HashMap::new(), + func_meta: HashMap::default(), + function_definition_ids: HashMap::default(), + function_modifiers: HashMap::default(), + function_modules: HashMap::default(), + module_attributes: HashMap::default(), + func_id_to_trait: HashMap::default(), dependency_graph: petgraph::graph::DiGraph::new(), - dependency_graph_indices: HashMap::new(), - id_to_location: HashMap::new(), + dependency_graph_indices: HashMap::default(), + id_to_location: HashMap::default(), definitions: vec![], - id_to_type: HashMap::new(), - definition_to_type: HashMap::new(), - structs: HashMap::new(), - struct_attributes: HashMap::new(), + id_to_type: HashMap::default(), + definition_to_type: HashMap::default(), + structs: HashMap::default(), + struct_attributes: HashMap::default(), type_aliases: Vec::new(), - traits: HashMap::new(), - trait_implementations: HashMap::new(), + traits: HashMap::default(), + trait_implementations: HashMap::default(), next_trait_implementation_id: 0, - trait_implementation_map: HashMap::new(), - selected_trait_implementations: HashMap::new(), - infix_operator_traits: HashMap::new(), - prefix_operator_traits: HashMap::new(), + trait_implementation_map: HashMap::default(), + selected_trait_implementations: HashMap::default(), + infix_operator_traits: HashMap::default(), + prefix_operator_traits: HashMap::default(), ordering_type: None, - instantiation_bindings: HashMap::new(), - field_indices: HashMap::new(), + instantiation_bindings: HashMap::default(), + field_indices: HashMap::default(), next_type_variable_id: std::cell::Cell::new(0), globals: Vec::new(), - global_attributes: HashMap::new(), - struct_methods: HashMap::new(), - primitive_methods: HashMap::new(), + global_attributes: HashMap::default(), + struct_methods: HashMap::default(), + primitive_methods: HashMap::default(), type_alias_ref: Vec::new(), type_ref_locations: Vec::new(), quoted_types: Default::default(), - track_references: false, + lsp_mode: false, location_indices: LocationIndices::default(), reference_graph: petgraph::graph::DiGraph::new(), - reference_graph_indices: HashMap::new(), - reference_modules: HashMap::new(), + reference_graph_indices: HashMap::default(), + reference_modules: HashMap::default(), + comptime_scopes: vec![HashMap::default()], } } } @@ -971,6 +980,10 @@ impl NodeInterner { self.func_meta.get(func_id).expect("ice: all function ids should have metadata") } + pub fn function_meta_mut(&mut self, func_id: &FuncId) -> &mut FuncMeta { + self.func_meta.get_mut(func_id).expect("ice: all function ids should have metadata") + } + pub fn try_function_meta(&self, func_id: &FuncId) -> Option<&FuncMeta> { self.func_meta.get(func_id) } @@ -1437,6 +1450,8 @@ impl NodeInterner { let mut matching_impls = Vec::new(); + let mut where_clause_errors = Vec::new(); + for (existing_object_type2, impl_kind) in impls { // Bug: We're instantiating only the object type's generics here, not all of the trait's generics like we need to let (existing_object_type, instantiation_bindings) = @@ -1471,14 +1486,17 @@ impl NodeInterner { let trait_impl = self.get_trait_implementation(*impl_id); let trait_impl = trait_impl.borrow(); - if let Err(mut errors) = self.validate_where_clause( + if let Err(errors) = self.validate_where_clause( &trait_impl.where_clause, &mut fresh_bindings, &instantiation_bindings, recursion_limit, ) { - errors.push(make_constraint()); - return Err(errors); + // Only keep the first errors we get from a failing where clause + if where_clause_errors.is_empty() { + where_clause_errors.extend(errors); + } + continue; } } @@ -1491,7 +1509,8 @@ impl NodeInterner { *type_bindings = fresh_bindings; Ok(impl_) } else if matching_impls.is_empty() { - Err(vec![make_constraint()]) + where_clause_errors.push(make_constraint()); + Err(where_clause_errors) } else { // multiple matching impls, type annotations needed Err(vec![]) @@ -1969,6 +1988,10 @@ impl NodeInterner { let env = Box::new(Type::Unit); (Type::Function(args, Box::new(ret.clone()), env), ret) } + + pub fn is_in_lsp_mode(&self) -> bool { + self.lsp_mode + } } impl Methods { @@ -2067,6 +2090,7 @@ fn get_type_method_key(typ: &Type) -> Option { | Type::Constant(_) | Type::Error | Type::Struct(_, _) + | Type::InfixExpr(..) | Type::TraitAsType(..) => None, } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/noir_parser.lalrpop b/noir/noir-repo/compiler/noirc_frontend/src/noir_parser.lalrpop index 5bf48a764d63..1488a53183e1 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/noir_parser.lalrpop +++ b/noir/noir-repo/compiler/noirc_frontend/src/noir_parser.lalrpop @@ -4,7 +4,7 @@ use crate::lexer::token::BorrowedToken; use crate::lexer::token as noir_token; use crate::lexer::errors::LexerErrorKind; use crate::parser::TopLevelStatement; -use crate::ast::{Ident, Path, PathKind, UseTree, UseTreeKind}; +use crate::ast::{Ident, Path, PathKind, PathSegment, UseTree, UseTreeKind}; use lalrpop_util::ErrorRecovery; @@ -110,7 +110,7 @@ pub(crate) TopLevelStatement: TopLevelStatement = { UseTree: UseTree = { // path::to::ident as SomeAlias => { - let ident = prefix.pop(); + let ident = prefix.pop().ident; let kind = UseTreeKind::Path(ident, alias); UseTree { prefix, kind } }, @@ -129,7 +129,7 @@ pub(crate) Path: Path = { Path { segments, kind, span } }, - => { + => { segments.insert(0, id); let kind = PathKind::Plain; let span = Span::from(lo as u32..hi as u32); @@ -137,12 +137,20 @@ pub(crate) Path: Path = { }, } -PathSegments: Vec = { - )*> => { +PathSegments: Vec = { + )*> => { segments } } +PathSegment: PathSegment = { + => { + let token = noir_token::Token::Ident(i.to_string()); + let span = Span::from(lo as u32..hi as u32); + PathSegment::from(Ident::from_token(token, span)) + }, +} + Alias: Ident = { r"[\t\r\n ]+" "as" r"[\t\r\n ]+" => <>, } 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 c566489eb40e..36d3ce5898cc 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/errors.rs @@ -16,6 +16,8 @@ pub enum ParserErrorReason { ExpectedFieldName(Token), #[error("expected a pattern but found a type - {0}")] ExpectedPatternButFoundType(Token), + #[error("expected an identifier after ::")] + ExpectedIdentifierAfterColons, #[error("Expected a ; separating these two statements")] MissingSeparatingSemi, #[error("constrain keyword is deprecated")] @@ -26,6 +28,8 @@ pub enum ParserErrorReason { EarlyReturn, #[error("Patterns aren't allowed in a trait's function declarations")] PatternInTraitFunctionParameter, + #[error("Patterns aren't allowed in a trait impl's associated constants")] + PatternInAssociatedConstant, #[error("Modifiers are ignored on a trait impl method")] TraitImplFunctionModifiers, #[error("comptime keyword is deprecated")] @@ -46,6 +50,8 @@ pub enum ParserErrorReason { Lexer(LexerErrorKind), #[error("The only supported numeric generic types are `u1`, `u8`, `u16`, and `u32`")] ForbiddenNumericGenericType, + #[error("Invalid call data identifier, must be a number. E.g `call_data(0)`")] + InvalidCallDataIdentifier, } /// Represents a parsing error, or a parsing error in the making. diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs index c62d66769ac2..f1972bcb9b50 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs @@ -22,7 +22,11 @@ use chumsky::primitive::Container; pub use errors::ParserError; pub use errors::ParserErrorReason; use noirc_errors::Span; -pub use parser::{expression, parse_program, top_level_items, trait_bound}; +pub use parser::path::path_no_turbofish; +pub use parser::traits::trait_bound; +pub use parser::{ + block, expression, fresh_statement, parse_program, parse_type, pattern, top_level_items, +}; #[derive(Debug, Clone)] pub enum TopLevelStatement { 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 7f3e0e68bbc7..5a97d66df9a8 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs @@ -23,8 +23,10 @@ //! prevent other parsers from being tried afterward since there is no longer an error. Thus, they should //! be limited to cases like the above `fn` example where it is clear we shouldn't back out of the //! current parser to try alternative parsers in a `choice` expression. +use self::path::as_trait_path; use self::primitives::{keyword, macro_quote_marker, mutable_reference, variable}; -use self::types::{generic_type_args, maybe_comp_time, parse_type}; +use self::types::{generic_type_args, maybe_comp_time}; +pub use types::parse_type; use super::{ foldl_with_span, labels::ParsingRuleLabel, parameter_name_recovery, parameter_recovery, @@ -36,8 +38,8 @@ use super::{spanned, Item, ItemKind}; use crate::ast::{ BinaryOp, BinaryOpKind, BlockExpression, ForLoopStatement, ForRange, Ident, IfExpression, InfixExpression, LValue, Literal, ModuleDeclaration, NoirTypeAlias, Param, Path, Pattern, - Recoverable, Statement, TraitBound, TypeImpl, UnaryRhsMemberAccess, UnaryRhsMethodCall, - UnresolvedTraitConstraint, UseTree, UseTreeKind, Visibility, + Recoverable, Statement, TypeImpl, UnaryRhsMemberAccess, UnaryRhsMethodCall, UseTree, + UseTreeKind, Visibility, }; use crate::ast::{ Expression, ExpressionKind, LetStatement, StatementKind, UnresolvedType, UnresolvedTypeData, @@ -45,6 +47,7 @@ use crate::ast::{ use crate::lexer::{lexer::from_spanned_token_result, Lexer}; use crate::parser::{force, ignore_then_commit, statement_recovery}; use crate::token::{Keyword, Token, TokenKind}; +use acvm::AcirField; use chumsky::prelude::*; use iter_extended::vecmap; @@ -56,10 +59,10 @@ mod attributes; mod function; mod lambdas; mod literals; -mod path; +pub(super) mod path; mod primitives; mod structs; -mod traits; +pub(super) mod traits; mod types; // synthesized by LALRPOP @@ -71,6 +74,7 @@ mod test_helpers; use literals::literal; use path::{maybe_empty_path, path}; use primitives::{dereference, ident, negation, not, nothing, right_shift_operator, token_kind}; +use traits::where_clause; /// Entry function for the parser - also handles lexing internally. /// @@ -215,9 +219,8 @@ fn top_level_statement<'a>( /// /// implementation: 'impl' generics type '{' function_definition ... '}' fn implementation() -> impl NoirParser { - maybe_comp_time() - .then_ignore(keyword(Keyword::Impl)) - .then(function::generics()) + keyword(Keyword::Impl) + .ignore_then(function::generics()) .then(parse_type().map_with_span(|typ, span| (typ, span))) .then(where_clause()) .then_ignore(just(Token::LeftBrace)) @@ -225,14 +228,13 @@ fn implementation() -> impl NoirParser { .then_ignore(just(Token::RightBrace)) .map(|args| { let ((other_args, where_clause), methods) = args; - let ((is_comptime, generics), (object_type, type_span)) = other_args; + let (generics, (object_type, type_span)) = other_args; TopLevelStatement::Impl(TypeImpl { generics, object_type, type_span, where_clause, methods, - is_comptime, }) }) } @@ -365,54 +367,13 @@ fn function_declaration_parameters() -> impl NoirParser impl NoirParser> { - struct MultiTraitConstraint { - typ: UnresolvedType, - trait_bounds: Vec, - } - - let constraints = parse_type() - .then_ignore(just(Token::Colon)) - .then(trait_bounds()) - .map(|(typ, trait_bounds)| MultiTraitConstraint { typ, trait_bounds }); - - keyword(Keyword::Where) - .ignore_then(constraints.separated_by(just(Token::Comma))) - .or_not() - .map(|option| option.unwrap_or_default()) - .map(|x: Vec| { - let mut result: Vec = Vec::new(); - for constraint in x { - for bound in constraint.trait_bounds { - result.push(UnresolvedTraitConstraint { - typ: constraint.typ.clone(), - trait_bound: bound, - }); - } - } - result - }) -} - -fn trait_bounds() -> impl NoirParser> { - trait_bound().separated_by(just(Token::Plus)).at_least(1).allow_trailing() -} - -pub fn trait_bound() -> impl NoirParser { - path().then(generic_type_args(parse_type())).map(|(trait_path, trait_generics)| TraitBound { - trait_path, - trait_generics, - trait_id: None, - }) -} - fn block_expr<'a>( statement: impl NoirParser + 'a, ) -> impl NoirParser + 'a { block(statement).map(ExpressionKind::Block).map_with_span(Expression::new) } -fn block<'a>( +pub fn block<'a>( statement: impl NoirParser + 'a, ) -> impl NoirParser + 'a { use Token::*; @@ -467,8 +428,8 @@ fn rename() -> impl NoirParser> { fn use_tree() -> impl NoirParser { recursive(|use_tree| { - let simple = path().then(rename()).map(|(mut prefix, alias)| { - let ident = prefix.pop(); + let simple = path::path_no_turbofish().then(rename()).map(|(mut prefix, alias)| { + let ident = prefix.pop().ident; UseTree { prefix, kind: UseTreeKind::Path(ident, alias) } }); @@ -502,6 +463,8 @@ where assertion::assertion_eq(expr_parser.clone()), declaration(expr_parser.clone()), assignment(expr_parser.clone()), + if_statement(expr_no_constructors.clone(), statement.clone()), + block_statement(statement.clone()), for_loop(expr_no_constructors.clone(), statement.clone()), break_statement(), continue_statement(), @@ -512,7 +475,7 @@ where }) } -fn fresh_statement() -> impl NoirParser { +pub fn fresh_statement() -> impl NoirParser { statement(expression(), expression_no_constructors(expression())) } @@ -556,7 +519,9 @@ where .map(|(block, span)| ExpressionKind::Comptime(block, span)) } -fn declaration<'a, P>(expr_parser: P) -> impl NoirParser + 'a +fn let_statement<'a, P>( + expr_parser: P, +) -> impl NoirParser<((Pattern, UnresolvedType), Expression)> + 'a where P: ExprParser + 'a, { @@ -564,11 +529,17 @@ where ignore_then_commit(keyword(Keyword::Let).labelled(ParsingRuleLabel::Statement), pattern()); let p = p.then(optional_type_annotation()); let p = then_commit_ignore(p, just(Token::Assign)); - let p = then_commit(p, expr_parser); - p.map(StatementKind::new_let) + then_commit(p, expr_parser) +} + +fn declaration<'a, P>(expr_parser: P) -> impl NoirParser + 'a +where + P: ExprParser + 'a, +{ + let_statement(expr_parser).map(StatementKind::new_let) } -fn pattern() -> impl NoirParser { +pub fn pattern() -> impl NoirParser { recursive(|pattern| { let ident_pattern = ident().map(Pattern::Identifier).map_err(|mut error| { if matches!(error.found(), Token::IntType(..)) { @@ -593,7 +564,7 @@ fn pattern() -> impl NoirParser { .separated_by(just(Token::Comma)) .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)); - let struct_pattern = path() + let struct_pattern = path(super::parse_type()) .then(struct_pattern_fields) .map_with_span(|(typename, fields), span| Pattern::Struct(typename, fields, span)); @@ -680,19 +651,28 @@ where }) } +fn call_data() -> impl NoirParser { + keyword(Keyword::CallData).then(parenthesized(literal())).validate(|token, span, emit| { + match token { + (_, ExpressionKind::Literal(Literal::Integer(x, _))) => { + let id = x.to_u128() as u32; + Visibility::CallData(id) + } + _ => { + emit(ParserError::with_reason(ParserErrorReason::InvalidCallDataIdentifier, span)); + Visibility::CallData(0) + } + } + }) +} + fn optional_visibility() -> impl NoirParser { keyword(Keyword::Pub) - .or(keyword(Keyword::CallData)) - .or(keyword(Keyword::ReturnData)) + .map(|_| Visibility::Public) + .or(call_data()) + .or(keyword(Keyword::ReturnData).map(|_| Visibility::ReturnData)) .or_not() - .map(|opt| match opt { - Some(Token::Keyword(Keyword::Pub)) => Visibility::Public, - Some(Token::Keyword(Keyword::CallData)) | Some(Token::Keyword(Keyword::ReturnData)) => { - Visibility::DataBus - } - None => Visibility::Private, - _ => unreachable!("unexpected token found"), - }) + .map(|opt| opt.unwrap_or(Visibility::Private)) } pub fn expression() -> impl ExprParser { @@ -959,6 +939,28 @@ where }) } +fn if_statement<'a, P, S>( + expr_no_constructors: P, + statement: S, +) -> impl NoirParser + 'a +where + P: ExprParser + 'a, + S: NoirParser + 'a, +{ + if_expr(expr_no_constructors, statement).map_with_span(|expression_kind, span| { + StatementKind::Expression(Expression::new(expression_kind, span)) + }) +} + +fn block_statement<'a, S>(statement: S) -> impl NoirParser + 'a +where + S: NoirParser + 'a, +{ + block(statement).map_with_span(|block, span| { + StatementKind::Expression(Expression::new(ExpressionKind::Block(block), span)) + }) +} + fn for_loop<'a, P, S>(expr_no_constructors: P, statement: S) -> impl NoirParser + 'a where P: ExprParser + 'a, @@ -1083,6 +1085,7 @@ where unquote(expr_parser.clone()), variable(), literal(), + as_trait_path(parse_type()).map(ExpressionKind::AsTraitPath), macro_quote_marker(), )) .map_with_span(Expression::new) @@ -1155,7 +1158,7 @@ fn constructor(expr_parser: impl ExprParser) -> impl NoirParser .allow_trailing() .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)); - path().then(args).map(ExpressionKind::constructor) + path(super::parse_type()).then(args).map(ExpressionKind::constructor) } fn constructor_field

(expr_parser: P) -> impl NoirParser<(Ident, Expression)> @@ -1325,20 +1328,6 @@ mod test { fn parse_block() { parse_with(block(fresh_statement()), "{ [0,1,2,3,4] }").unwrap(); - // Regression for #1310: this should be parsed as a block and not a function call - let res = - parse_with(block(fresh_statement()), "{ if true { 1 } else { 2 } (3, 4) }").unwrap(); - match unwrap_expr(&res.statements.last().unwrap().kind) { - // The `if` followed by a tuple is currently creates a block around both in case - // there was none to start with, so there is an extra block here. - ExpressionKind::Block(block) => { - assert_eq!(block.statements.len(), 2); - assert!(matches!(unwrap_expr(&block.statements[0].kind), ExpressionKind::If(_))); - assert!(matches!(unwrap_expr(&block.statements[1].kind), ExpressionKind::Tuple(_))); - } - _ => unreachable!(), - } - parse_all_failing( block(fresh_statement()), vec![ @@ -1352,14 +1341,6 @@ mod test { ); } - /// Extract an Statement::Expression from a statement or panic - fn unwrap_expr(stmt: &StatementKind) -> &ExpressionKind { - match stmt { - StatementKind::Expression(expr) => &expr.kind, - _ => unreachable!(), - } - } - #[test] fn parse_let() { // Why is it valid to specify a let declaration as having type u8? @@ -1656,4 +1637,40 @@ mod test { let failing = vec!["quote {}}", "quote a", "quote { { { } } } }"]; parse_all_failing(quote(), failing); } + + #[test] + fn test_parses_block_statement_not_infix_expression() { + let src = r#" + { + {} + -1 + }"#; + let (block_expr, _) = parse_recover(block(fresh_statement()), src); + let block_expr = block_expr.expect("Failed to parse module"); + assert_eq!(block_expr.statements.len(), 2); + } + + #[test] + fn test_parses_if_statement_not_infix_expression() { + let src = r#" + { + if 1 { 2 } else { 3 } + -1 + }"#; + let (block_expr, _) = parse_recover(block(fresh_statement()), src); + let block_expr = block_expr.expect("Failed to parse module"); + assert_eq!(block_expr.statements.len(), 2); + } + + #[test] + fn test_parses_if_statement_followed_by_tuple_as_two_separate_statements() { + // Regression for #1310: this should not be parsed as a function call + let src = r#" + { + if 1 { 2 } else { 3 } (1, 2) + }"#; + let (block_expr, _) = parse_recover(block(fresh_statement()), src); + let block_expr = block_expr.expect("Failed to parse module"); + assert_eq!(block_expr.statements.len(), 2); + } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs:28:9 b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs:28:9 new file mode 100644 index 000000000000..47dfb32b53b3 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs:28:9 @@ -0,0 +1,45 @@ +[?1049h[?1h[?2004h[?2026$p[?u[?12h[?25h[?25l(B[38:2:235:219:178m[48:2:168:153:132m [No Name]  (B[38:2:168:153:132m(B[38:2:235:219:178m (B[38:2:80:73:69m(B[38:2:168:153:132m[48:2:80:73:69m buffers +(B[38:2:124:111:100m 1 (B[38:2:235:219:178m +(B[38:2:80:73:69m~ +~ +~ +~ +~ +~ +~ +~ +~ +~ +~ +~ +~ +~ +~ +~ +~ +~ +~ +~ +(B[38:2:235:219:178m[48:2:168:153:132m (B[38:2:235:219:178m[48:2:168:153:132mNORMAL(B[38:2:235:219:178m[48:2:168:153:132m (B[38:2:168:153:132m[48:2:80:73:69m  jf/quoted-as-type (B[38:2:80:73:69m[48:2:60:56:54m(B[38:2:168:153:132m[48:2:60:56:54m (B[38:2:60:56:54m[48:2:60:56:54m(B[38:2:168:153:132m[48:2:60:56:54m  (B[38:2:80:73:69m[48:2:60:56:54m(B[38:2:168:153:132m[48:2:80:73:69m(B[38:2:235:219:178m[48:2:168:153:132m 100% (B[38:2:235:219:178m[48:2:168:153:132m☰ 0/1 (B[38:2:235:219:178m[48:2:168:153:132m : 1 (B[38:2:254:128:25m[48:2:168:153:132m(B[38:2:235:219:178mNVIM v0.10.0Nvim is open source and freely distributablehttps://neovim.io/#chattype :help nvim(B[38:2:80:73:69m(B[38:2:235:219:178m if you are new! type :checkhealth(B[38:2:80:73:69m(B[38:2:235:219:178m to optimize Nvimtype :q(B[38:2:80:73:69m(B[38:2:235:219:178m to exit type :help(B[38:2:80:73:69m(B[38:2:235:219:178m for help type :help news(B[38:2:80:73:69m(B[38:2:235:219:178m to see changes in v0.10Help poor children in Uganda!type :help iccf(B[38:2:80:73:69m(B[38:2:235:219:178m for information ]112[2 q]112[2 q[?1002h[?1006h(B[38:2:235:219:178m[48:2:168:153:132m [No Name]  (B[38:2:168:153:132m(B[38:2:235:219:178m (B[38:2:80:73:69m(B[38:2:168:153:132m[48:2:80:73:69m buffers +(B[38:2:124:111:100m 1 (B[38:2:235:219:178m +(B[38:2:80:73:69m~ +~ +~ (B[38:2:235:219:178mNVIM v0.10.0(B[38:2:80:73:69m +~ +~ (B[38:2:235:219:178mNvim is open source and freely distributable(B[38:2:80:73:69m +~ (B[38:2:235:219:178mhttps://neovim.io/#chat(B[38:2:80:73:69m +~ +~ (B[38:2:235:219:178mtype :help nvim(B[38:2:80:73:69m(B[38:2:235:219:178m if you are new! (B[38:2:80:73:69m +~ (B[38:2:235:219:178mtype :checkhealth(B[38:2:80:73:69m(B[38:2:235:219:178m to optimize Nvim(B[38:2:80:73:69m +~ (B[38:2:235:219:178mtype :q(B[38:2:80:73:69m(B[38:2:235:219:178m to exit (B[38:2:80:73:69m +~ (B[38:2:235:219:178mtype :help(B[38:2:80:73:69m(B[38:2:235:219:178m for help (B[38:2:80:73:69m +~ +~ (B[38:2:235:219:178mtype :help news(B[38:2:80:73:69m(B[38:2:235:219:178m to see changes in v0.10(B[38:2:80:73:69m +~ +~ (B[38:2:235:219:178mHelp poor children in Uganda!(B[38:2:80:73:69m +~ (B[38:2:235:219:178mtype :help iccf(B[38:2:80:73:69m(B[38:2:235:219:178m for information (B[38:2:80:73:69m +~ +~ +~ +~ +(B[38:2:235:219:178m[48:2:168:153:132m (B[38:2:235:219:178m[48:2:168:153:132mNORMAL(B[38:2:235:219:178m[48:2:168:153:132m (B[38:2:168:153:132m[48:2:80:73:69m  jf/quoted-as-type (B[38:2:80:73:69m[48:2:60:56:54m(B[38:2:168:153:132m[48:2:60:56:54m (B[38:2:60:56:54m[48:2:60:56:54m(B[38:2:168:153:132m[48:2:60:56:54m  (B[38:2:80:73:69m[48:2:60:56:54m(B[38:2:168:153:132m[48:2:80:73:69m(B[38:2:235:219:178m[48:2:168:153:132m 100% (B[38:2:235:219:178m[48:2:168:153:132m☰ 0/1 (B[38:2:235:219:178m[48:2:168:153:132m : 1 (B[38:2:254:128:25m[48:2:168:153:132m(B[38:2:235:219:178m[?12h[?25h[?25l[?1004h[?12h[?25h \ No newline at end of file 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 2fd337e1cb1f..3de48d2e02a1 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 @@ -2,9 +2,10 @@ use super::{ attributes::{attributes, validate_attributes}, block, fresh_statement, ident, keyword, maybe_comp_time, nothing, optional_visibility, parameter_name_recovery, parameter_recovery, parenthesized, parse_type, pattern, + primitives::token_kind, self_parameter, where_clause, NoirParser, }; -use crate::token::{Keyword, Token}; +use crate::token::{Keyword, Token, TokenKind}; use crate::{ast::IntegerBitSize, parser::spanned}; use crate::{ ast::{ @@ -110,8 +111,15 @@ pub(super) fn generic_type() -> impl NoirParser { ident().map(UnresolvedGeneric::Variable) } +pub(super) fn resolved_generic() -> impl NoirParser { + token_kind(TokenKind::QuotedType).map_with_span(|token, span| match token { + Token::QuotedType(id) => UnresolvedGeneric::Resolved(id, span), + _ => unreachable!("token_kind(QuotedType) guarantees we parse a quoted type"), + }) +} + pub(super) fn generic() -> impl NoirParser { - generic_type().or(numeric_generic()) + generic_type().or(numeric_generic()).or(resolved_generic()) } /// non_empty_ident_list: ident ',' non_empty_ident_list diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/path.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/path.rs index 8957fb7c40be..ae3a1bc0b93d 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/path.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/path.rs @@ -1,27 +1,70 @@ -use crate::ast::{Path, PathKind}; -use crate::parser::NoirParser; +use crate::ast::{AsTraitPath, Path, PathKind, PathSegment, UnresolvedType}; +use crate::parser::{NoirParser, ParserError, ParserErrorReason}; use crate::token::{Keyword, Token}; use chumsky::prelude::*; -use super::{ident, keyword}; +use super::keyword; +use super::primitives::{ident, path_segment, path_segment_no_turbofish}; -pub(super) fn path() -> impl NoirParser { - let idents = || ident().separated_by(just(Token::DoubleColon)).at_least(1); +pub(super) fn path<'a>( + type_parser: impl NoirParser + 'a, +) -> impl NoirParser + 'a { + path_inner(path_segment(type_parser)) +} + +pub fn path_no_turbofish() -> impl NoirParser { + path_inner(path_segment_no_turbofish()) +} + +fn path_inner<'a>(segment: impl NoirParser + 'a) -> impl NoirParser + 'a { + let segments = segment + .separated_by(just(Token::DoubleColon)) + .at_least(1) + .then(just(Token::DoubleColon).then_ignore(none_of(Token::LeftBrace).rewind()).or_not()) + .validate(|(path_segments, trailing_colons), span, emit_error| { + if trailing_colons.is_some() { + emit_error(ParserError::with_reason( + ParserErrorReason::ExpectedIdentifierAfterColons, + span, + )); + } + path_segments + }); let make_path = |kind| move |segments, span| Path { segments, kind, span }; let prefix = |key| keyword(key).ignore_then(just(Token::DoubleColon)); - let path_kind = |key, kind| prefix(key).ignore_then(idents()).map_with_span(make_path(kind)); + let path_kind = + |key, kind| prefix(key).ignore_then(segments.clone()).map_with_span(make_path(kind)); choice(( path_kind(Keyword::Crate, PathKind::Crate), path_kind(Keyword::Dep, PathKind::Dep), path_kind(Keyword::Super, PathKind::Super), - idents().map_with_span(make_path(PathKind::Plain)), + segments.map_with_span(make_path(PathKind::Plain)), )) } +/// Parses `::path_segment` +/// These paths only support exactly two segments. +pub(super) fn as_trait_path<'a>( + type_parser: impl NoirParser + 'a, +) -> impl NoirParser + 'a { + just(Token::Less) + .ignore_then(type_parser.clone()) + .then_ignore(keyword(Keyword::As)) + .then(path(type_parser)) + .then_ignore(just(Token::Greater)) + .then_ignore(just(Token::DoubleColon)) + .then(ident()) + .validate(|((typ, trait_path), impl_item), span, emit| { + let reason = ParserErrorReason::ExperimentalFeature("Fully qualified trait impl paths"); + emit(ParserError::with_reason(reason, span)); + AsTraitPath { typ, trait_path, impl_item } + }) +} + fn empty_path() -> impl NoirParser { let make_path = |kind| move |_, span| Path { segments: Vec::new(), kind, span }; let path_kind = |key, kind| keyword(key).map_with_span(make_path(kind)); @@ -30,13 +73,16 @@ fn empty_path() -> impl NoirParser { } pub(super) fn maybe_empty_path() -> impl NoirParser { - path().or(empty_path()) + path_no_turbofish().or(empty_path()) } #[cfg(test)] mod test { use super::*; - use crate::parser::parser::test_helpers::{parse_all_failing, parse_with}; + use crate::parser::{ + parse_type, + parser::test_helpers::{parse_all_failing, parse_recover, parse_with}, + }; #[test] fn parse_path() { @@ -45,18 +91,17 @@ mod test { ("std::hash", vec!["std", "hash"]), ("std::hash::collections", vec!["std", "hash", "collections"]), ("foo::bar", vec!["foo", "bar"]), - ("foo::bar", vec!["foo", "bar"]), ("crate::std::hash", vec!["std", "hash"]), ]; for (src, expected_segments) in cases { - let path: Path = parse_with(path(), src).unwrap(); + let path: Path = parse_with(path(parse_type()), src).unwrap(); for (segment, expected) in path.segments.into_iter().zip(expected_segments) { - assert_eq!(segment.0.contents, expected); + assert_eq!(segment.ident.0.contents, expected); } } - parse_all_failing(path(), vec!["std::", "::std", "std::hash::", "foo::1"]); + parse_all_failing(path(parse_type()), vec!["std::", "::std", "std::hash::", "foo::1"]); } #[test] @@ -69,13 +114,27 @@ mod test { ]; for (src, expected_path_kind) in cases { - let path = parse_with(path(), src).unwrap(); + let path = parse_with(path(parse_type()), src).unwrap(); assert_eq!(path.kind, expected_path_kind); } parse_all_failing( - path(), + path(parse_type()), vec!["crate", "crate::std::crate", "foo::bar::crate", "foo::dep"], ); } + + #[test] + fn parse_path_with_trailing_colons() { + let src = "foo::bar::"; + + let (path, errors) = parse_recover(path_no_turbofish(), src); + let path = path.unwrap(); + assert_eq!(path.segments.len(), 2); + assert_eq!(path.segments[0].ident.0.contents, "foo"); + assert_eq!(path.segments[1].ident.0.contents, "bar"); + + assert_eq!(errors.len(), 1); + assert_eq!(errors[0].message, "expected an identifier after ::"); + } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/primitives.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/primitives.rs index 88f9e591aba5..25f693bf5040 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/primitives.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/primitives.rs @@ -1,13 +1,14 @@ use chumsky::prelude::*; -use crate::ast::{ExpressionKind, Ident, UnaryOp}; +use crate::ast::{ExpressionKind, Ident, PathSegment, UnaryOp}; use crate::macros_api::UnresolvedType; use crate::{ parser::{labels::ParsingRuleLabel, ExprParser, NoirParser, ParserError}, token::{Keyword, Token, TokenKind}, }; -use super::path; +use super::path::{path, path_no_turbofish}; +use super::types::required_generic_type_args; /// This parser always parses no input and fails pub(super) fn nothing() -> impl NoirParser { @@ -32,6 +33,20 @@ pub(super) fn token_kind(token_kind: TokenKind) -> impl NoirParser { }) } +pub(super) fn path_segment<'a>( + type_parser: impl NoirParser + 'a, +) -> impl NoirParser + 'a { + ident().then(turbofish(type_parser)).map_with_span(|(ident, generics), span| PathSegment { + ident, + generics, + span, + }) +} + +pub(super) fn path_segment_no_turbofish() -> impl NoirParser { + ident().map(PathSegment::from) +} + pub(super) fn ident() -> impl NoirParser { token_kind(TokenKind::Ident).map_with_span(Ident::from_token) } @@ -81,17 +96,15 @@ where pub(super) fn turbofish<'a>( type_parser: impl NoirParser + 'a, ) -> impl NoirParser>> + 'a { - just(Token::DoubleColon).ignore_then(super::generic_type_args(type_parser)).or_not() + just(Token::DoubleColon).ignore_then(required_generic_type_args(type_parser)).or_not() } pub(super) fn variable() -> impl NoirParser { - path() - .then(turbofish(super::parse_type())) - .map(|(path, generics)| ExpressionKind::Variable(path, generics)) + path(super::parse_type()).map(ExpressionKind::Variable) } pub(super) fn variable_no_turbofish() -> impl NoirParser { - path().map(|path| ExpressionKind::Variable(path, None)) + path_no_turbofish().map(ExpressionKind::Variable) } pub(super) fn macro_quote_marker() -> impl NoirParser { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/structs.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/structs.rs index 9a3adf74d7f5..58bf1693eee4 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/structs.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/structs.rs @@ -1,7 +1,6 @@ use chumsky::prelude::*; use crate::ast::{Ident, NoirStruct, UnresolvedType}; -use crate::parser::parser::types::maybe_comp_time; use crate::{ parser::{ parser::{ @@ -29,21 +28,13 @@ pub(super) fn struct_definition() -> impl NoirParser { .or(just(Semicolon).to(Vec::new())); attributes() - .then(maybe_comp_time()) .then_ignore(keyword(Struct)) .then(ident()) .then(function::generics()) .then(fields) - .validate(|((((attributes, is_comptime), name), generics), fields), span, emit| { + .validate(|(((attributes, name), generics), fields), span, emit| { let attributes = validate_secondary_attributes(attributes, span, emit); - TopLevelStatement::Struct(NoirStruct { - name, - attributes, - generics, - fields, - span, - is_comptime, - }) + TopLevelStatement::Struct(NoirStruct { name, attributes, generics, fields, span }) }) } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/traits.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/traits.rs index 4e4c9d5c0db1..0874cadd34e3 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/traits.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/traits.rs @@ -2,13 +2,16 @@ use chumsky::prelude::*; use super::attributes::{attributes, validate_secondary_attributes}; use super::function::function_return_type; -use super::types::maybe_comp_time; -use super::{block, expression, fresh_statement, function, function_declaration_parameters}; +use super::path::path_no_turbofish; +use super::{ + block, expression, fresh_statement, function, function_declaration_parameters, let_statement, +}; use crate::ast::{ Expression, ItemVisibility, NoirTrait, NoirTraitImpl, TraitBound, TraitImplItem, TraitItem, UnresolvedTraitConstraint, UnresolvedType, }; +use crate::macros_api::Pattern; use crate::{ parser::{ ignore_then_commit, parenthesized, parser::primitives::keyword, NoirParser, ParserError, @@ -17,7 +20,7 @@ use crate::{ token::{Keyword, Token}, }; -use super::{generic_type_args, parse_type, path, primitives::ident}; +use super::{generic_type_args, parse_type, primitives::ident}; pub(super) fn trait_definition() -> impl NoirParser { attributes() @@ -59,13 +62,7 @@ fn trait_constant_declaration() -> impl NoirParser { .then(parse_type()) .then(optional_default_value()) .then_ignore(just(Token::Semicolon)) - .validate(|((name, typ), default_value), span, emit| { - emit(ParserError::with_reason( - ParserErrorReason::ExperimentalFeature("Associated constants"), - span, - )); - TraitItem::Constant { name, typ, default_value } - }) + .map(|((name, typ), default_value)| TraitItem::Constant { name, typ, default_value }) } /// trait_function_declaration: 'fn' ident generics '(' declaration_parameters ')' function_return_type @@ -104,10 +101,9 @@ fn trait_type_declaration() -> impl NoirParser { /// /// trait_implementation: 'impl' generics ident generic_args for type '{' trait_implementation_body '}' pub(super) fn trait_implementation() -> impl NoirParser { - maybe_comp_time() - .then_ignore(keyword(Keyword::Impl)) - .then(function::generics()) - .then(path()) + keyword(Keyword::Impl) + .ignore_then(function::generics()) + .then(path_no_turbofish()) .then(generic_type_args(parse_type())) .then_ignore(keyword(Keyword::For)) .then(parse_type()) @@ -117,7 +113,7 @@ pub(super) fn trait_implementation() -> impl NoirParser { .then_ignore(just(Token::RightBrace)) .map(|args| { let (((other_args, object_type), where_clause), items) = args; - let (((is_comptime, impl_generics), trait_name), trait_generics) = other_args; + let ((impl_generics, trait_name), trait_generics) = other_args; TopLevelStatement::TraitImpl(NoirTraitImpl { impl_generics, @@ -126,7 +122,6 @@ pub(super) fn trait_implementation() -> impl NoirParser { object_type, items, where_clause, - is_comptime, }) }) } @@ -148,10 +143,20 @@ fn trait_implementation_body() -> impl NoirParser> { .then_ignore(just(Token::Semicolon)) .map(|(name, alias)| TraitImplItem::Type { name, alias }); - function.or(alias).repeated() + let let_statement = let_statement(expression()).then_ignore(just(Token::Semicolon)).try_map( + |((pattern, typ), expr), span| match pattern { + Pattern::Identifier(ident) => Ok(TraitImplItem::Constant(ident, typ, expr)), + _ => Err(ParserError::with_reason( + ParserErrorReason::PatternInTraitFunctionParameter, + span, + )), + }, + ); + + choice((function, alias, let_statement)).repeated() } -fn where_clause() -> impl NoirParser> { +pub(super) fn where_clause() -> impl NoirParser> { struct MultiTraitConstraint { typ: UnresolvedType, trait_bounds: Vec, @@ -163,7 +168,7 @@ fn where_clause() -> impl NoirParser> { .map(|(typ, trait_bounds)| MultiTraitConstraint { typ, trait_bounds }); keyword(Keyword::Where) - .ignore_then(constraints.separated_by(just(Token::Comma))) + .ignore_then(constraints.separated_by(just(Token::Comma)).allow_trailing()) .or_not() .map(|option| option.unwrap_or_default()) .map(|x: Vec| { @@ -184,11 +189,9 @@ fn trait_bounds() -> impl NoirParser> { trait_bound().separated_by(just(Token::Plus)).at_least(1).allow_trailing() } -fn trait_bound() -> impl NoirParser { - path().then(generic_type_args(parse_type())).map(|(trait_path, trait_generics)| TraitBound { - trait_path, - trait_generics, - trait_id: None, +pub fn trait_bound() -> impl NoirParser { + path_no_turbofish().then(generic_type_args(parse_type())).map(|(trait_path, trait_generics)| { + TraitBound { trait_path, trait_generics, trait_id: None } }) } @@ -215,6 +218,7 @@ mod test { "trait GenericTrait { fn elem(&mut self, index: Field) -> T; }", "trait GenericTraitWithConstraints where T: SomeTrait { fn elem(self, index: Field) -> T; }", "trait TraitWithMultipleGenericParams where A: SomeTrait, B: AnotherTrait { let Size: Field; fn zero() -> Self; }", + "trait TraitWithMultipleGenericParams where A: SomeTrait, B: AnotherTrait, { let Size: Field; fn zero() -> Self; }", ], ); 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 cecc1cbcd4cf..7c551ca96d19 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 @@ -1,6 +1,7 @@ +use super::path::{as_trait_path, path_no_turbofish}; use super::primitives::token_kind; use super::{ - expression_with_precedence, keyword, nothing, parenthesized, path, NoirParser, ParserError, + expression_with_precedence, keyword, nothing, parenthesized, NoirParser, ParserError, ParserErrorReason, Precedence, }; use crate::ast::{ @@ -14,7 +15,7 @@ use crate::token::{Keyword, Token, TokenKind}; use chumsky::prelude::*; use noirc_errors::Span; -pub(super) fn parse_type<'a>() -> impl NoirParser + 'a { +pub fn parse_type<'a>() -> impl NoirParser + 'a { recursive(parse_type_inner) } @@ -44,10 +45,18 @@ pub(super) fn parse_type_inner<'a>( parenthesized_type(recursive_type_parser.clone()), tuple_type(recursive_type_parser.clone()), function_type(recursive_type_parser.clone()), - mutable_reference_type(recursive_type_parser), + mutable_reference_type(recursive_type_parser.clone()), + as_trait_path_type(recursive_type_parser), )) } +fn as_trait_path_type<'a>( + type_parser: impl NoirParser + 'a, +) -> impl NoirParser + 'a { + as_trait_path(type_parser) + .map_with_span(|path, span| UnresolvedTypeData::AsTraitPath(Box::new(path)).with_span(span)) +} + pub(super) fn parenthesized_type( recursive_type_parser: impl NoirParser, ) -> impl NoirParser { @@ -132,7 +141,7 @@ fn quoted_type() -> impl NoirParser { /// This is the type of an already resolved type. /// The only way this can appear in the token input is if an already resolved `Type` object /// was spliced into a macro's token stream via the `$` operator. -fn resolved_type() -> impl NoirParser { +pub(super) fn resolved_type() -> impl NoirParser { token_kind(TokenKind::QuotedType).map_with_span(|token, span| match token { Token::QuotedType(id) => UnresolvedTypeData::Resolved(id).with_span(span), _ => unreachable!("token_kind(QuotedType) guarantees we parse a quoted type"), @@ -180,7 +189,7 @@ pub(super) fn int_type() -> impl NoirParser { pub(super) fn named_type<'a>( type_parser: impl NoirParser + 'a, ) -> impl NoirParser + 'a { - path().then(generic_type_args(type_parser)).map_with_span(|(path, args), span| { + path_no_turbofish().then(generic_type_args(type_parser)).map_with_span(|(path, args), span| { UnresolvedTypeData::Named(path, args, false).with_span(span) }) } @@ -188,13 +197,22 @@ pub(super) fn named_type<'a>( pub(super) fn named_trait<'a>( type_parser: impl NoirParser + 'a, ) -> impl NoirParser + 'a { - keyword(Keyword::Impl).ignore_then(path()).then(generic_type_args(type_parser)).map_with_span( - |(path, args), span| UnresolvedTypeData::TraitAsType(path, args).with_span(span), - ) + keyword(Keyword::Impl) + .ignore_then(path_no_turbofish()) + .then(generic_type_args(type_parser)) + .map_with_span(|(path, args), span| { + UnresolvedTypeData::TraitAsType(path, args).with_span(span) + }) } pub(super) fn generic_type_args<'a>( type_parser: impl NoirParser + 'a, +) -> impl NoirParser> + 'a { + required_generic_type_args(type_parser).or_not().map(Option::unwrap_or_default) +} + +pub(super) fn required_generic_type_args<'a>( + type_parser: impl NoirParser + 'a, ) -> impl NoirParser> + 'a { type_parser .clone() @@ -208,8 +226,6 @@ pub(super) fn generic_type_args<'a>( .allow_trailing() .at_least(1) .delimited_by(just(Token::Less), just(Token::Greater)) - .or_not() - .map(Option::unwrap_or_default) } pub(super) fn array_type<'a>( @@ -241,7 +257,7 @@ fn type_expression() -> impl NoirParser { /// This parser is the same as `type_expression()`, however, it continues parsing and /// emits a parser error in the case of an invalid type expression rather than halting the parser. -fn type_expression_validated() -> impl NoirParser { +pub(super) fn type_expression_validated() -> impl NoirParser { type_expression_inner().validate(|expr, span, emit| { let type_expr = UnresolvedTypeExpression::from_expr(expr, span); match type_expr { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/tests.rs b/noir/noir-repo/compiler/noirc_frontend/src/tests.rs index cbc15da20ff5..9124567b4e50 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/tests.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/tests.rs @@ -82,8 +82,9 @@ pub(crate) fn get_program(src: &str) -> (ParsedModule, Context, Vec<(Compilation &mut context, program.clone().into_sorted(), root_file_id, - None, // No debug_comptime_in_file - &[], // No macro processors + None, // No debug_comptime_in_file + false, // Disallow arithmetic generics + &[], // No macro processors )); } (program, context, errors) @@ -2494,3 +2495,427 @@ fn bit_not_on_untyped_integer() { "#; assert_no_errors(src); } + +#[test] +fn duplicate_struct_field() { + let src = r#" + struct Foo { + x: i32, + x: i32, + } + + fn main() {} + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::DefinitionError(DefCollectorErrorKind::DuplicateField { + first_def, + second_def, + }) = &errors[0].0 + else { + panic!("Expected a duplicate field error, got {:?}", errors[0].0); + }; + + assert_eq!(first_def.to_string(), "x"); + assert_eq!(second_def.to_string(), "x"); + + assert_eq!(first_def.span().start(), 26); + assert_eq!(second_def.span().start(), 42); +} + +#[test] +fn trait_constraint_on_tuple_type() { + let src = r#" + trait Foo { + fn foo(self, x: A) -> bool; + } + + fn bar(x: (T, U), y: V) -> bool where (T, U): Foo { + x.foo(y) + } + + fn main() {}"#; + assert_no_errors(src); +} + +#[test] +fn turbofish_in_constructor_generics_mismatch() { + let src = r#" + struct Foo { + x: T + } + + fn main() { + let _ = Foo:: { x: 1 }; + } + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + assert!(matches!( + errors[0].0, + CompilationError::TypeError(TypeCheckError::GenericCountMismatch { .. }), + )); +} + +#[test] +fn turbofish_in_constructor() { + let src = r#" + struct Foo { + x: T + } + + fn main() { + let x: Field = 0; + let _ = Foo:: { x: x }; + } + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::TypeError(TypeCheckError::TypeMismatch { + expected_typ, expr_typ, .. + }) = &errors[0].0 + else { + panic!("Expected a type mismatch error, got {:?}", errors[0].0); + }; + + assert_eq!(expected_typ, "i32"); + assert_eq!(expr_typ, "Field"); +} + +#[test] +fn turbofish_in_middle_of_variable_unsupported_yet() { + let src = r#" + struct Foo { + x: T + } + + impl Foo { + fn new(x: T) -> Self { + Foo { x } + } + } + + fn main() { + let _ = Foo::::new(1); + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + assert!(matches!( + errors[0].0, + CompilationError::TypeError(TypeCheckError::UnsupportedTurbofishUsage { .. }), + )); +} + +#[test] +fn turbofish_in_struct_pattern() { + let src = r#" + struct Foo { + x: T + } + + fn main() { + let value: Field = 0; + let Foo:: { x } = Foo { x: value }; + let _ = x; + } + "#; + assert_no_errors(src); +} + +#[test] +fn turbofish_in_struct_pattern_errors_if_type_mismatch() { + let src = r#" + struct Foo { + x: T + } + + fn main() { + let value: Field = 0; + let Foo:: { x } = Foo { x: value }; + let _ = x; + } + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::TypeError(TypeCheckError::TypeMismatchWithSource { .. }) = &errors[0].0 + else { + panic!("Expected a type mismatch error, got {:?}", errors[0].0); + }; +} + +#[test] +fn turbofish_in_struct_pattern_generic_count_mismatch() { + let src = r#" + struct Foo { + x: T + } + + fn main() { + let value = 0; + let Foo:: { x } = Foo { x: value }; + let _ = x; + } + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::TypeError(TypeCheckError::GenericCountMismatch { + item, + expected, + found, + .. + }) = &errors[0].0 + else { + panic!("Expected a generic count mismatch error, got {:?}", errors[0].0); + }; + + assert_eq!(item, "struct Foo"); + assert_eq!(*expected, 1); + assert_eq!(*found, 2); +} + +#[test] +fn incorrect_generic_count_on_struct_impl() { + let src = r#" + struct Foo {} + impl Foo {} + fn main() {} + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::ResolverError(ResolverError::IncorrectGenericCount { + actual, + expected, + .. + }) = errors[0].0 + else { + panic!("Expected an incorrect generic count mismatch error, got {:?}", errors[0].0); + }; + + assert_eq!(actual, 1); + assert_eq!(expected, 0); +} + +#[test] +fn incorrect_generic_count_on_type_alias() { + let src = r#" + struct Foo {} + type Bar = Foo; + fn main() {} + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::ResolverError(ResolverError::IncorrectGenericCount { + actual, + expected, + .. + }) = errors[0].0 + else { + panic!("Expected an incorrect generic count mismatch error, got {:?}", errors[0].0); + }; + + assert_eq!(actual, 1); + assert_eq!(expected, 0); +} + +#[test] +fn uses_self_type_for_struct_function_call() { + let src = r#" + struct S { } + + impl S { + fn one() -> Field { + 1 + } + + fn two() -> Field { + Self::one() + Self::one() + } + } + + fn main() {} + "#; + assert_no_errors(src); +} + +#[test] +fn uses_self_type_inside_trait() { + let src = r#" + trait Foo { + fn foo() -> Self { + Self::bar() + } + + fn bar() -> Self; + } + + impl Foo for Field { + fn bar() -> Self { + 1 + } + } + + fn main() { + let _: Field = Foo::foo(); + } + "#; + assert_no_errors(src); +} + +#[test] +fn uses_self_type_in_trait_where_clause() { + let src = r#" + trait Trait { + fn trait_func() -> bool; + } + + trait Foo where Self: Trait { + fn foo(self) -> bool { + self.trait_func() + } + } + + struct Bar { + + } + + impl Foo for Bar { + + } + + fn main() {} + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::TypeError(TypeCheckError::UnresolvedMethodCall { method_name, .. }) = + &errors[0].0 + else { + panic!("Expected an unresolved method call error, got {:?}", errors[0].0); + }; + + assert_eq!(method_name, "trait_func"); +} + +#[test] +fn do_not_eagerly_error_on_cast_on_type_variable() { + let src = r#" + pub fn foo(x: T, f: fn(T) -> U) -> U { + f(x) + } + + fn main() { + let x: u8 = 1; + let _: Field = foo(x, |x| x as Field); + } + "#; + assert_no_errors(src); +} + +#[test] +fn error_on_cast_over_type_variable() { + let src = r#" + pub fn foo(x: T, f: fn(T) -> U) -> U { + f(x) + } + + fn main() { + let x = "a"; + let _: Field = foo(x, |x| x as Field); + } + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + assert!(matches!( + errors[0].0, + CompilationError::TypeError(TypeCheckError::TypeMismatch { .. }) + )); +} + +#[test] +fn trait_impl_for_a_type_that_implements_another_trait() { + let src = r#" + trait One { + fn one(self) -> i32; + } + + impl One for i32 { + fn one(self) -> i32 { + self + } + } + + trait Two { + fn two(self) -> i32; + } + + impl Two for T where T: One { + fn two(self) -> i32 { + self.one() + 1 + } + } + + fn use_it(t: T) -> i32 where T: Two { + Two::two(t) + } + + fn main() {} + "#; + assert_no_errors(src); +} + +#[test] +fn trait_impl_for_a_type_that_implements_another_trait_with_another_impl_used() { + let src = r#" + trait One { + fn one(self) -> i32; + } + + impl One for i32 { + fn one(self) -> i32 { + let _ = self; + 1 + } + } + + trait Two { + fn two(self) -> i32; + } + + impl Two for T where T: One { + fn two(self) -> i32 { + self.one() + 1 + } + } + + impl Two for u32 { + fn two(self) -> i32 { + let _ = self; + 0 + } + } + + fn use_it(t: u32) -> i32 { + Two::two(t) + } + + fn main() {} + "#; + assert_no_errors(src); +} diff --git a/noir/noir-repo/compiler/wasm/package.json b/noir/noir-repo/compiler/wasm/package.json index f9606003c7a0..5abe5dd4c2f4 100644 --- a/noir/noir-repo/compiler/wasm/package.json +++ b/noir/noir-repo/compiler/wasm/package.json @@ -3,7 +3,7 @@ "contributors": [ "The Noir Team " ], - "version": "0.32.0", + "version": "0.33.0", "license": "(MIT OR Apache-2.0)", "main": "dist/main.js", "types": "./dist/types/src/index.d.cts", diff --git a/noir/noir-repo/cspell.json b/noir/noir-repo/cspell.json index 689b72435efc..e939cd583e85 100644 --- a/noir/noir-repo/cspell.json +++ b/noir/noir-repo/cspell.json @@ -88,6 +88,7 @@ "foralls", "formatcp", "frontends", + "fuzzer", "fxhash", "getrandom", "gloo", @@ -126,6 +127,7 @@ "memset", "merkle", "metas", + "microcontroller", "minreq", "monomorphization", "monomorphize", @@ -135,6 +137,7 @@ "monomorphizing", "montcurve", "MSRV", + "multicall", "nand", "nargo", "neovim", diff --git a/noir/noir-repo/docs/docs/explainers/cspell.json b/noir/noir-repo/docs/docs/explainers/cspell.json new file mode 100644 index 000000000000..c60b0a597b10 --- /dev/null +++ b/noir/noir-repo/docs/docs/explainers/cspell.json @@ -0,0 +1,5 @@ +{ + "words": [ + "Cryptdoku" + ] +} diff --git a/noir/noir-repo/docs/docs/explainers/explainer-writing-noir.md b/noir/noir-repo/docs/docs/explainers/explainer-writing-noir.md new file mode 100644 index 000000000000..c8a42c379e66 --- /dev/null +++ b/noir/noir-repo/docs/docs/explainers/explainer-writing-noir.md @@ -0,0 +1,173 @@ +--- +title: Writing Performant Noir +description: Understand new considerations when writing Noir +keywords: [Noir, programming, rust] +tags: [Optimization] +sidebar_position: 0 +--- + + +This article intends to set you up with key concepts essential for writing more viable applications that use zero knowledge proofs, namely around efficient circuits. + +## Context - 'Efficient' is subjective + +When writing a web application for a performant computer with high-speed internet connection, writing efficient code sometimes is seen as an afterthought only if needed. Large multiplications running at the innermost of nested loops may not even be on a dev's radar. +When writing firmware for a battery-powered microcontroller, you think of cpu cycles as rations to keep within a product's power budget. + +> Code is written to create applications that perform specific tasks within specific constraints + +And these constraints differ depending on where the compiled code is execute. + +### The Ethereum Virtual Machine (EVM) + +In scenarios where extremely low gas costs are required for an Ethereum application to be viable/competitive, Ethereum smart contract developers get into what is colloquially known as: "*gas golfing*". Finding the lowest execution cost of their compiled code (EVM bytecode) to achieve a specific task. + +The equivalent optimization task when writing zk circuits is affectionately referred to as "*gate golfing*", finding the lowest gate representation of the compiled Noir code. + +### Coding for circuits - a paradigm shift + +In zero knowledge cryptography, code is compiled to "circuits" consisting of arithmetic gates, and gate count is the significant cost. Depending on the proving system this is linearly proportionate to proving time, and so from a product point this should be kept as low as possible. + +Whilst writing efficient code for web apps and Solidity has a few key differences, writing efficient circuits have a different set of considerations. It is a bit of a paradigm shift, like writing code for GPUs for the first time... + +For example, drawing a circle at (0, 0) of radius `r`: +- For a single CPU thread, +``` +for theta in 0..2*pi { + let x = r * cos(theta); + let y = r * sin(theta); + draw(x, y); +} // note: would do 0 - pi/2 and draw +ve/-ve x and y. +``` + +- For GPUs (simultaneous parallel calls with x, y across image), +``` +if (x^2 + y^2 = r^2) { + draw(x, y); +} +``` + +([Related](https://www.youtube.com/watch?v=-P28LKWTzrI)) + +Whilst this CPU -> GPU does not translate to circuits exactly, it is intended to exemplify the difference in intuition when coding for different machine capabilities/constraints. + +### Context Takeaway + +For those coming from a primarily web app background, this article will explain what you need to consider when writing circuits. Furthermore, for those experienced writing efficient machine code, prepare to shift what you think is efficient 😬 + +## Translating from Rust + +For some applications using Noir, existing code might be a convenient starting point to then proceed to optimize the gate count of. + +:::note +Many valuable functions and algorithms have been written in more established languages (C/C++), and converted to modern ones (like Rust). +::: + +Fortunately for Noir developers, when needing a particular function a Rust implementation can be readily compiled into Noir with some key changes. While the compiler does a decent amount of optimizations, it won't be able to change code that has been optimized for clock-cycles into code optimized for arithmetic gates. + +A few things to do when converting Rust code to Noir: +- `println!` is not a macro, use `println` function (same for `assert_eq`) +- No early `return` in function. Use constrain via assertion instead +- No passing by reference. Remove `&` operator to pass by value (copy) +- No boolean operators (`&&`, `||`). Use bitwise operators (`&`, `|`) with boolean values +- No type `usize`. Use types `u8`, `u32`, `u64`, ... +- `main` return must be public, `pub` +- No `const`, use `global` +- Noir's LSP is your friend, so error message should be informative enough to resolve syntax issues. + +## Writing efficient Noir for performant products + +The following points help refine our understanding over time. + +:::note +A Noir program makes a statement that can be verified. +::: + +It compiles to a structure that represents the calculation, and can assert results within the calculation at any stage (via the `constrain` keyword). + +A Noir program compiles to an Abstract Circuit Intermediate Representation which is: + - A tree structure + - Leaves (inputs) are the `Field` type + - Nodes contain arithmetic operations to combine them (gates) + - The root is the final result (return value) + +:::tip +The command `nargo info` shows the programs circuit size, and is useful to compare the value of changes made. +You can dig deeper and use the `--print-acir` param to take a closer look at individual ACIR opcodes, and the proving backend to see its gate count (eg for barretenberg, `bb gates -b ./target/program.json`). +::: + +### Use the `Field` type + +Since the native type of values in circuits are `Field`s, using them for variables in Noir means less gates converting them under the hood. + +:::tip +Where possible, use `Field` type for values. Using smaller value types, and bit-packing strategies, will result in MORE gates +::: + +**Note:** Need to remain mindful of overflow. Types with less bits may be used to limit the range of possible values prior to a calculation. + +### Use Arithmetic over non-arithmetic operations + +Since circuits are made of arithmetic gates, the cost of arithmetic operations tends to be one gate. Whereas for procedural code, they represent several clock cycles. + +Inversely, non-arithmetic operators are achieved with multiple gates, vs 1 clock cycle for procedural code. + +| (cost\op) | arithmetic
(`*`, `+`) | bit-wise ops
(eg `<`, `\|`, `>>`) | +| - | - | - | +| **cycles** | 10+ | 1 | +| **gates** | 1 | 10+ | + +Bit-wise operations (e.g. bit shifts `<<` and `>>`), albeit commonly used in general programming and especially for clock cycle optimizations, are on the contrary expensive in gates when performed within circuits. + +Translate away from bit shifts when writing constrained functions for the best performance. + +On the flip side, feel free to use bit shifts in unconstrained functions and tests if necessary, as they are executed outside of circuits and does not induce performance hits. + +### Use static over dynamic values + +Another general theme that manifests in different ways is that static reads are represented with less gates than dynamic ones. + +Reading from read-only memory (ROM) adds less gates than random-access memory (RAM), 2 vs ~3.25 due to the additional bounds checks. Arrays of fixed length (albeit used at a lower capacity), will generate less gates than dynamic storage. + +Related to this, if an index used to access an array is not known at compile time (ie unknown until run time), then ROM will be converted to RAM, expanding the gate count. + +:::tip +Use arrays and indices that are known at compile time where possible. +Using `assert_constant(i);` before an index, `i`, is used in an array will give a compile error if `i` is NOT known at compile time. +::: + +### Leverage unconstrained execution + +Constrained verification can leverage unconstrained execution, this is especially useful for operations that are represented by many gates. +Use an [unconstrained function](../noir/concepts/unconstrained.md) to perform gate-heavy calculations, then verify and constrain the result. + +Eg division generates more gates than multiplication, so calculating the quotient in an unconstrained function then constraining the product for the quotient and divisor (+ any remainder) equals the dividend will be more efficient. + +Use ` if is_unconstrained() { /`, to conditionally execute code if being called in an unconstrained vs constrained way. + +## Advanced + +Unless you're well into the depth of gate optimization, this advanced section can be ignored. + +### Combine arithmetic operations + +A Noir program can be honed further by combining arithmetic operators in a way that makes the most of each constraint of the backend proving system. This is in scenarios where the backend might not be doing this perfectly. + +Eg Barretenberg backend (current default for Noir) is a width-4 PLONKish constraint system +$ w_1*w_2*q_m + w_1*q_1 + w_2*q_2 + w_3*q_3 + w_4*q_4 + q_c = 0 $ + +Here we see there is one occurrence of witness 1 and 2 ($w_1$, $w_2$) being multiplied together, with addition to witnesses 1-4 ($w_1$ .. $w_4$) multiplied by 4 corresponding circuit constants ($q_1$ .. $q_4$) (plus a final circuit constant, $q_c$). + +Use `nargo info --print-acir`, to inspect the ACIR opcodes (and the proving system for gates), and it may present opportunities to amend the order of operations and reduce the number of constraints. + +#### Variable as witness vs expression + +If you've come this far and really know what you're doing at the equation level, a temporary lever (that will become unnecessary/useless over time) is: `std::as_witness`. This informs the compiler to save a variable as a witness not an expression. + +The compiler will mostly be correct and optimal, but this may help some near term edge cases that are yet to optimize. +Note: When used incorrectly it will create **less** efficient circuits (higher gate count). + +## References +- Guillaume's ["`Cryptdoku`" talk](https://www.youtube.com/watch?v=MrQyzuogxgg) (Jun'23) +- Tips from Tom, Jake and Zac. +- [Idiomatic Noir](https://www.vlayer.xyz/blog/idiomatic-noir-part-1-collections) blog post diff --git a/noir/noir-repo/docs/docs/getting_started/backend/_category_.json b/noir/noir-repo/docs/docs/getting_started/backend/_category_.json new file mode 100644 index 000000000000..b82e92beb0ca --- /dev/null +++ b/noir/noir-repo/docs/docs/getting_started/backend/_category_.json @@ -0,0 +1,6 @@ +{ + "position": 1, + "label": "Install Proving Backend", + "collapsible": true, + "collapsed": true +} diff --git a/noir/noir-repo/docs/docs/getting_started/backend/index.md b/noir/noir-repo/docs/docs/getting_started/backend/index.md new file mode 100644 index 000000000000..7192d9548774 --- /dev/null +++ b/noir/noir-repo/docs/docs/getting_started/backend/index.md @@ -0,0 +1,31 @@ +--- +title: Proving Backend Installation +description: Proving backends offer command line tools for proving and verifying Noir programs. This page describes how to install `bb` as an example. +keywords: [ + Proving + Backend + Barretenberg + bb + bbup + Installation + Terminal + Command + CLI + Version +] +pagination_next: getting_started/hello_noir/index +--- + +Proving backends each provide their own tools for working with Noir programs, providing functionality like proof generation, proof verification, and verifier smart contract generation. + +For the latest information on tooling provided by each proving backend, installation instructions, Noir version compatibility... you may refer to the proving backends' own documentation. + +You can find the full list of proving backends compatible with Noir in [Awesome Noir](https://github.com/noir-lang/awesome-noir/?tab=readme-ov-file#proving-backends). + +## Example: Installing `bb` + +`bb` is the CLI tool provided by the [Barretenberg proving backend](https://github.com/AztecProtocol/barretenberg) developed by Aztec Labs. + +You can find the instructions for installation in [`bb`'s documentation](https://github.com/AztecProtocol/aztec-packages/blob/master/barretenberg/cpp/src/barretenberg/bb/readme.md#installation). + +Once installed, we are ready to start working on [our first Noir program](../hello_noir/index.md). diff --git a/noir/noir-repo/docs/docs/getting_started/barretenberg/index.md b/noir/noir-repo/docs/docs/getting_started/barretenberg/index.md deleted file mode 100644 index 0102c86770b7..000000000000 --- a/noir/noir-repo/docs/docs/getting_started/barretenberg/index.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -title: Barretenberg Installation -description: bb is a command line tool for interacting with Aztec's proving backend Barretenberg. This page is a quick guide on how to install `bb` -keywords: [ - Barretenberg - bb - Installation - Terminal Commands - Version Check - Nightlies - Specific Versions - Branches -] -pagination_next: getting_started/hello_noir/index ---- - -`bb` is the CLI tool for generating and verifying proofs for Noir programs using the Barretenberg proving library. It also allows generating solidity verifier contracts for which you can verify contracts which were constructed using `bb`. - -## Installing `bb` - -Open a terminal on your machine, and write: - -##### macOS (Apple Silicon) - -```bash -curl -L https://raw.githubusercontent.com/AztecProtocol/aztec-packages/master/barretenberg/cpp/installation/install | bash -source ~/.zshrc -bbup -v 0.41.0 -``` - -##### macOS (Intel) - -```bash -curl -L https://raw.githubusercontent.com/AztecProtocol/aztec-packages/master/barretenberg/cpp/installation/install | bash -source ~/.zshrc -bbup -v 0.41.0 -``` - -##### Linux (Bash) - -```bash -curl -L https://raw.githubusercontent.com/AztecProtocol/aztec-packages/master/barretenberg/cpp/installation/install | bash -source ~/.bashrc -bbup -v 0.41.0 -``` - -Now we're ready to start working on [our first Noir program!](../hello_noir/index.md) diff --git a/noir/noir-repo/docs/docs/getting_started/hello_noir/index.md b/noir/noir-repo/docs/docs/getting_started/hello_noir/index.md index 1ade3f09ae3d..6760e54aad10 100644 --- a/noir/noir-repo/docs/docs/getting_started/hello_noir/index.md +++ b/noir/noir-repo/docs/docs/getting_started/hello_noir/index.md @@ -17,22 +17,25 @@ sidebar_position: 1 --- -Now that we have installed Nargo, it is time to make our first hello world program! +Now that we have installed Nargo and a proving backend, it is time to make our first hello world program! -## Create a Project Directory +### 1. Create a new project directory Noir code can live anywhere on your computer. Let us create a _projects_ folder in the home -directory to house our Noir programs. +directory to house our first Noir program. -For Linux, macOS, and Windows PowerShell, create the directory and change directory into it by -running: +Create the directory and change directory into it by running: ```sh mkdir ~/projects cd ~/projects ``` -## Create Our First Nargo Project +## Nargo + +Nargo provides the ability to initiate and execute Noir projects. Read the [Nargo installation](../installation/index.md) section to learn more about Nargo and how to install it. + +### 2. Create a new Noir project Now that we are in the projects directory, create a new Nargo project by running: @@ -40,18 +43,15 @@ Now that we are in the projects directory, create a new Nargo project by running nargo new hello_world ``` -> **Note:** `hello_world` can be any arbitrary project name, we are simply using `hello_world` for -> demonstration. -> -> In production, the common practice is to name the project folder as `circuits` for better -> identifiability when sitting alongside other folders in the codebase (e.g. `contracts`, `scripts`, -> `test`). +`hello_world` can be any arbitrary project name, we are simply using `hello_world` for demonstration. + +In production, it is common practice to name the project folder, `circuits`, for clarity amongst other folders in the codebase (like: `contracts`, `scripts`, and `test`). A `hello_world` folder would be created. Similar to Rust, the folder houses _src/main.nr_ and _Nargo.toml_ which contain the source code and environmental options of your Noir program respectively. -### Intro to Noir Syntax +#### Intro to Noir Syntax Let us take a closer look at _main.nr_. The default _main.nr_ generated should look like this: @@ -81,7 +81,7 @@ The Noir syntax `assert` can be interpreted as something similar to constraints For more Noir syntax, check the [Language Concepts](../../noir/concepts/comments.md) chapter. -## Build In/Output Files +### 3. Build in/output files Change directory into _hello_world_ and build in/output files for your Noir program by running: @@ -92,7 +92,7 @@ nargo check A _Prover.toml_ file will be generated in your project directory, to allow specifying input values to the program. -## Execute Our Noir Program +### 4. Execute the Noir program Now that the project is set up, we can execute our Noir program. @@ -111,34 +111,46 @@ nargo execute witness-name The witness corresponding to this execution will then be written to the file `./target/witness-name.gz`. -## Prove Our Noir Program +The command also automatically compiles your Noir program if it was not already / was edited, which you may notice the compiled artifacts being written to the file `./target/hello_world.json`. -:::info +## Proving Backend -Nargo no longer handles communicating with backends in order to generate proofs. In order to prove/verify your Noir programs, you'll need an installation of [bb](../barretenberg/index.md). +Proving backends provide the ability to generate and verify proofs of executing Noir programs, following Noir's tooling that compiles and executes the programs. Read the [proving backend installation](../backend/index.md) section to learn more about proving backends and how to install them. -::: +Barretenberg is used as an example here to demonstrate how proving and verifying could be implemented and used. Read the [`bb` installation](../backend/index.md#example-installing-bb) section for how to install Barretenberg's CLI tool; refer to [`bb`'s documentation](https://github.com/AztecProtocol/aztec-packages/blob/master/barretenberg/cpp/src/barretenberg/bb/readme.md) for full details about the tool. + +### 5. Prove an execution of the Noir program -Prove the valid execution of your Noir program using `bb`: +Using Barretenberg as an example, prove the valid execution of your Noir program running: ```sh -bb prove -b ./target/hello_world.json -w ./target/witness-name.gz -o ./proof +bb prove -b ./target/hello_world.json -w ./target/witness-name.gz -o ./target/proof ``` -A new file called `proof` will be generated in your project directory, containing the generated proof for your program. +The proof generated will then be written to the file `./target/proof`. -## Verify Our Noir Program +:::tip +Since the params for `nargo` and `bb` often specify multiple filenames to read from or write to, remember to check each command is referring to the desired filenames. +Or for greater certainty, delete the target folder and go through each step again (compile, witness, prove, ...) to ensure files generated in past commands are being referenced in future ones. +::: + +### 6. Verify the execution proof Once a proof is generated, we can verify correct execution of our Noir program by verifying the proof file. -Verify your proof by running: +Using Barretenberg as an example, compute the verification key for the Noir program by running: ```sh bb write_vk -b ./target/hello_world.json -o ./target/vk -bb verify -k ./target/vk -p ./proof ``` -The verification will complete in silence if it is successful. If it fails, it will log the corresponding error instead. +And verify your proof by running: + +```sh +bb verify -k ./target/vk -p ./target/proof +``` + +If successful, the verification will complete in silence; if unsuccessful, the command will trigger logging of the corresponding error. Congratulations, you have now created and verified a proof for your very first Noir program! diff --git a/noir/noir-repo/docs/docs/getting_started/hello_noir/project_breakdown.md b/noir/noir-repo/docs/docs/getting_started/hello_noir/project_breakdown.md index 525b8dabdd84..96e653f6c08d 100644 --- a/noir/noir-repo/docs/docs/getting_started/hello_noir/project_breakdown.md +++ b/noir/noir-repo/docs/docs/getting_started/hello_noir/project_breakdown.md @@ -8,8 +8,7 @@ keywords: sidebar_position: 2 --- -This section breaks down our hello world program from the previous section. We elaborate on the project -structure and what the `prove` and `verify` commands did. +This section breaks down our hello world program from the previous section. ## Anatomy of a Nargo Project diff --git a/noir/noir-repo/docs/docs/getting_started/installation/index.md b/noir/noir-repo/docs/docs/getting_started/installation/index.md index 4ef86aa59147..53ea9c7891ca 100644 --- a/noir/noir-repo/docs/docs/getting_started/installation/index.md +++ b/noir/noir-repo/docs/docs/getting_started/installation/index.md @@ -19,11 +19,9 @@ keywords: [ pagination_next: getting_started/hello_noir/index --- -`nargo` is the one-stop-shop for almost everything related with Noir. The name comes from our love for Rust and its package manager `cargo`. +`nargo` is a tool for working with Noir programs on the CLI, providing you with the ability to start new projects, compile, execute and test Noir programs from the terminal. -With `nargo`, you can start new projects, compile, execute, prove, verify, test, generate solidity contracts, and do pretty much all that is available in Noir. - -Similarly to `rustup`, we also maintain an easy installation method that covers most machines: `noirup`. +The name is inspired by Rust's package manager `cargo`; and similar to Rust's `rustup`, Noir also has an easy installation script `noirup`. ## Installing Noirup 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 9a4ab5d3c1f2..e5e9f5a1d3bb 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 @@ -251,3 +251,23 @@ fn main() { } ``` + +### as_str_unchecked + +Converts a byte array of type `[u8; N]` to a string. Note that this performs no UTF-8 validation - +the given array is interpreted as-is as a string. + +```rust +impl [u8; N] { + pub fn as_str_unchecked(self) -> str +} +``` + +example: + +```rust +fn main() { + let hi = [104, 105].as_str_unchecked(); + assert_eq(hi, "hi"); +} +``` diff --git a/noir/noir-repo/docs/docs/noir/standard_library/recursion.md b/noir/noir-repo/docs/docs/noir/standard_library/recursion.md index 8cfb37fc52df..7f4dcebf0841 100644 --- a/noir/noir-repo/docs/docs/noir/standard_library/recursion.md +++ b/noir/noir-repo/docs/docs/noir/standard_library/recursion.md @@ -49,16 +49,16 @@ fn main( proof_b : [Field; 93], ) { std::verify_proof( - verification_key.as_slice(), - proof.as_slice(), - public_inputs.as_slice(), + verification_key, + proof, + public_inputs, key_hash ); std::verify_proof( - verification_key.as_slice(), - proof_b.as_slice(), - public_inputs.as_slice(), + verification_key, + proof_b, + public_inputs, key_hash ); } diff --git a/noir/noir-repo/docs/docs/getting_started/tooling/noir_codegen.md b/noir/noir-repo/docs/docs/reference/noir_codegen.md similarity index 97% rename from noir/noir-repo/docs/docs/getting_started/tooling/noir_codegen.md rename to noir/noir-repo/docs/docs/reference/noir_codegen.md index f7505bef7abe..db8f07dc22e6 100644 --- a/noir/noir-repo/docs/docs/getting_started/tooling/noir_codegen.md +++ b/noir/noir-repo/docs/docs/reference/noir_codegen.md @@ -33,7 +33,7 @@ yarn add @noir-lang/noir_codegen -D ``` ### Nargo library -Make sure you have Nargo, v0.25.0 or greater, installed. If you don't, follow the [installation guide](../installation/index.md). +Make sure you have Nargo, v0.25.0 or greater, installed. If you don't, follow the [installation guide](../getting_started/installation/index.md). If you're in a new project, make a `circuits` folder and create a new Noir library: diff --git a/noir/noir-repo/docs/docs/tutorials/noirjs_app.md b/noir/noir-repo/docs/docs/tutorials/noirjs_app.md index cbb1938a5c6f..eac28168445f 100644 --- a/noir/noir-repo/docs/docs/tutorials/noirjs_app.md +++ b/noir/noir-repo/docs/docs/tutorials/noirjs_app.md @@ -14,13 +14,13 @@ You can find the complete app code for this guide [here](https://github.com/noir :::note -Feel free to use whatever versions, just keep in mind that Nargo and the NoirJS packages are meant to be in sync. For example, Nargo 0.27.x matches `noir_js@0.27.x`, etc. +Feel free to use whatever versions, just keep in mind that Nargo and the NoirJS packages are meant to be in sync. For example, Nargo 0.31.x matches `noir_js@0.31.x`, etc. -In this guide, we will be pinned to 0.27.0. +In this guide, we will be pinned to 0.31.0. ::: -Before we start, we want to make sure we have Node and Nargo installed. +Before we start, we want to make sure we have Node, Nargo and the Barretenberg proving system (`bb`) installed. We start by opening a terminal and executing `node --version`. If we don't get an output like `v20.10.0`, that means node is not installed. Let's do that by following the handy [nvm guide](https://github.com/nvm-sh/nvm?tab=readme-ov-file#install--update-script). @@ -30,6 +30,9 @@ As for `Nargo`, we can follow the [Nargo guide](../getting_started/installation/ curl -L https://raw.githubusercontent.com/noir-lang/noirup/main/install | bash ``` +Follow the instructions on [this page](https://github.com/AztecProtocol/aztec-packages/tree/master/barretenberg/cpp/src/barretenberg/bb#installation) to install `bb`. +Version 0.41.0 is compatible with `nargo` version 0.31.0, which you can install with `bbup -v 0.41.0` once `bbup` is installed. + Easy enough. Onwards! ## Our project @@ -42,13 +45,17 @@ In fact, it's so simple that it comes nicely packaged in `nargo`. Let's do that! Run: -`nargo new circuit` +```bash +nargo new circuit +``` And... That's about it. Your program is ready to be compiled and run. To compile, let's `cd` into the `circuit` folder to enter our project, and call: -`nargo compile` +```bash +nargo compile +``` This compiles our circuit into `json` format and add it to a new `target` folder. @@ -92,30 +99,53 @@ Before we proceed with any coding, let's get our environment tailored for Noir. In your freshly minted `vite-project` folder, create a new file named `vite.config.js` and open it in your code editor. Paste the following to set the stage: ```javascript -import { defineConfig } from "vite"; -import copy from "rollup-plugin-copy"; - -export default defineConfig({ - esbuild: { - target: "esnext", +import { defineConfig } from 'vite'; +import copy from 'rollup-plugin-copy'; +import fs from 'fs'; +import path from 'path'; + +const wasmContentTypePlugin = { + name: 'wasm-content-type-plugin', + configureServer(server) { + server.middlewares.use(async (req, res, next) => { + if (req.url.endsWith('.wasm')) { + res.setHeader('Content-Type', 'application/wasm'); + const newPath = req.url.replace('deps', 'dist'); + const targetPath = path.join(__dirname, newPath); + const wasmContent = fs.readFileSync(targetPath); + return res.end(wasmContent); + } + next(); + }); }, - optimizeDeps: { - esbuildOptions: { - target: "esnext", - }, - }, - plugins: [ - copy({ - targets: [ - { src: "node_modules/**/*.wasm", dest: "node_modules/.vite/dist" }, +}; + +export default defineConfig(({ command }) => { + if (command === 'serve') { + return { + build: { + target: 'esnext', + rollupOptions: { + external: ['@aztec/bb.js'] + } + }, + optimizeDeps: { + esbuildOptions: { + target: 'esnext' + } + }, + plugins: [ + copy({ + targets: [{ src: 'node_modules/**/*.wasm', dest: 'node_modules/.vite/dist' }], + copySync: true, + hook: 'buildStart', + }), + command === 'serve' ? wasmContentTypePlugin : [], ], - copySync: true, - hook: "buildStart", - }), - ], - server: { - port: 3000, - }, + }; + } + + return {}; }); ``` @@ -124,7 +154,7 @@ export default defineConfig({ Now that our stage is set, install the necessary NoirJS packages along with our other dependencies: ```bash -npm install && npm install @noir-lang/backend_barretenberg@0.27.0 @noir-lang/noir_js@0.27.0 +npm install && npm install @noir-lang/backend_barretenberg@0.31.0 @noir-lang/noir_js@0.31.0 npm install rollup-plugin-copy --save-dev ``` @@ -193,17 +223,6 @@ Our love for Noir needs undivided attention, so let's just open `main.js` and de Start by pasting in this boilerplate code: ```js -const setup = async () => { - await Promise.all([ - import('@noir-lang/noirc_abi').then((module) => - module.default(new URL('@noir-lang/noirc_abi/web/noirc_abi_wasm_bg.wasm', import.meta.url).toString()), - ), - import('@noir-lang/acvm_js').then((module) => - module.default(new URL('@noir-lang/acvm_js/web/acvm_js_bg.wasm', import.meta.url).toString()), - ), - ]); -}; - function display(container, msg) { const c = document.getElementById(container); const p = document.createElement('p'); @@ -222,8 +241,6 @@ document.getElementById('submitGuess').addEventListener('click', async () => { The display function doesn't do much. We're simply manipulating our website to see stuff happening. For example, if the proof fails, it will simply log a broken heart 😢 -As for the `setup` function, it's just a sad reminder that dealing with `wasm` on the browser is not as easy as it should. Just copy, paste, and forget. - :::info At this point in the tutorial, your folder structure should look like this: @@ -310,9 +327,13 @@ Time to celebrate, yes! But we shouldn't trust machines so blindly. Let's add th ```js display('logs', 'Verifying proof... ⌛'); -const verificationKey = await backend.getVerificationKey(); -const verifier = new Verifier(); -const isValid = await verifier.verifyProof(proof, verificationKey); +const isValid = await backend.verifyProof(proof); + +// or to cache and use the verification key: +// const verificationKey = await backend.getVerificationKey(); +// const verifier = new Verifier(); +// const isValid = await verifier.verifyProof(proof, verificationKey); + if (isValid) display('logs', 'Verifying proof... ✅'); ``` @@ -325,3 +346,17 @@ You have successfully generated a client-side Noir web app! You can see how noirjs is used in a full stack Next.js hardhat application in the [noir-starter repo here](https://github.com/noir-lang/noir-starter/tree/main/vite-hardhat). The example shows how to calculate a proof in the browser and verify it with a deployed Solidity verifier contract from noirjs. You should also check out the more advanced examples in the [noir-examples repo](https://github.com/noir-lang/noir-examples), where you'll find reference usage for some cool apps. + +## UltraHonk Backend + +Barretenberg has recently exposed a new UltraHonk backend. We can use UltraHonk in NoirJS after version 0.33.0. Everything will be the same as the tutorial above, except that the class we need to import will change: +```js +import { UltraHonkBackend, UltraHonkVerifier as Verifier } from '@noir-lang/backend_barretenberg'; +``` +The backend will then be instantiated as such: +```js +const backend = new UltraHonkBackend(circuit); +``` +Then all the commands to prove and verify your circuit will be same. + +The only feature currently unsupported with UltraHonk are [recursive proofs](../explainers/explainer-recursion.md). \ No newline at end of file diff --git a/noir/noir-repo/docs/docusaurus.config.ts b/noir/noir-repo/docs/docusaurus.config.ts index f0c986f1c28c..29f612b01097 100644 --- a/noir/noir-repo/docs/docusaurus.config.ts +++ b/noir/noir-repo/docs/docusaurus.config.ts @@ -14,7 +14,6 @@ export default { favicon: 'img/favicon.ico', url: 'https://noir-lang.org', baseUrl: '/', - trailingSlash: true, onBrokenLinks: 'throw', onBrokenMarkdownLinks: 'throw', i18n: { diff --git a/noir/noir-repo/docs/src/components/Notes/_blackbox.mdx b/noir/noir-repo/docs/src/components/Notes/_blackbox.mdx index 226017072c88..514ca00a7e7c 100644 --- a/noir/noir-repo/docs/src/components/Notes/_blackbox.mdx +++ b/noir/noir-repo/docs/src/components/Notes/_blackbox.mdx @@ -1,5 +1,5 @@ :::info -This is a black box function. Read [this section](../../black_box_fns) to learn more about black box functions in Noir. +This is a black box function. Read [this section](/docs/noir/standard_library/black_box_fns) to learn more about black box functions in Noir. ::: diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.19.4/getting_started/01_hello_world.md b/noir/noir-repo/docs/versioned_docs/version-v0.19.4/getting_started/01_hello_world.md index 34f8cd96fcd8..0384ba4a0cda 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.19.4/getting_started/01_hello_world.md +++ b/noir/noir-repo/docs/versioned_docs/version-v0.19.4/getting_started/01_hello_world.md @@ -84,7 +84,7 @@ assert(x != y); The Noir syntax `assert` can be interpreted as something similar to constraints in other zk-contract languages. -For more Noir syntax, check the [Language Concepts](../language_concepts/comments.md) chapter. +For more Noir syntax, check the [Language Concepts](../language_concepts/09_comments.md) chapter. ## Build In/Output Files diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.19.4/language_concepts/data_types/02_booleans.md b/noir/noir-repo/docs/versioned_docs/version-v0.19.4/language_concepts/data_types/02_booleans.md index d353606210a5..67baa00f930e 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.19.4/language_concepts/data_types/02_booleans.md +++ b/noir/noir-repo/docs/versioned_docs/version-v0.19.4/language_concepts/data_types/02_booleans.md @@ -26,5 +26,5 @@ fn main() { > `false` in _Verifier.toml_. The boolean type is most commonly used in conditionals like `if` expressions and `assert` -statements. More about conditionals is covered in the [Control Flow](../control_flow.md) and -[Assert Function](../assert.md) sections. +statements. More about conditionals is covered in the [Control Flow](../02_control_flow.md) and +[Assert Function](../04_assert.md) sections. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.19.4/language_concepts/data_types/04_arrays.md b/noir/noir-repo/docs/versioned_docs/version-v0.19.4/language_concepts/data_types/04_arrays.md index 1424ca2df14e..5b4a544cf379 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.19.4/language_concepts/data_types/04_arrays.md +++ b/noir/noir-repo/docs/versioned_docs/version-v0.19.4/language_concepts/data_types/04_arrays.md @@ -56,7 +56,7 @@ You can instantiate a new array of a fixed size with the same value repeated for let array: [Field; 32] = [0; 32]; ``` -Like in Rust, arrays in Noir are a fixed size. However, if you wish to convert an array to a [slice](./slices.mdx), you can just call `as_slice` on your array: +Like in Rust, arrays in Noir are a fixed size. However, if you wish to convert an array to a [slice](./05_slices.mdx), you can just call `as_slice` on your array: ```rust let array: [Field; 32] = [0; 32]; diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.19.4/modules_packages_crates/dependencies.md b/noir/noir-repo/docs/versioned_docs/version-v0.19.4/modules_packages_crates/dependencies.md index 87a09293ea87..753b60387033 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.19.4/modules_packages_crates/dependencies.md +++ b/noir/noir-repo/docs/versioned_docs/version-v0.19.4/modules_packages_crates/dependencies.md @@ -81,7 +81,7 @@ use dep::std::scalar_mul::fixed_base_embedded_curve; ``` Lastly, as demonstrated in the -[elliptic curve example](../standard_library/cryptographic_primitives/ec_primitives.md#examples), you +[elliptic curve example](../standard_library/cryptographic_primitives/04_ec_primitives.md#examples), you can import multiple items in the same line by enclosing them in curly braces: ```rust diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.19.4/nargo/01_commands.md b/noir/noir-repo/docs/versioned_docs/version-v0.19.4/nargo/01_commands.md index e2b0af522f43..914856a0f435 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.19.4/nargo/01_commands.md +++ b/noir/noir-repo/docs/versioned_docs/version-v0.19.4/nargo/01_commands.md @@ -213,7 +213,7 @@ you run `nargo test`. To print `println` statements in tests, use the `--show-ou Takes an optional `--exact` flag which allows you to select tests based on an exact name. -See an example on the [testing page](./testing.md). +See an example on the [testing page](./02_testing.md). ### Options diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.19.4/standard_library/black_box_fns.md b/noir/noir-repo/docs/versioned_docs/version-v0.19.4/standard_library/black_box_fns.md index 985bb7c879dc..b115a450ed31 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.19.4/standard_library/black_box_fns.md +++ b/noir/noir-repo/docs/versioned_docs/version-v0.19.4/standard_library/black_box_fns.md @@ -26,19 +26,19 @@ fn sha256(_input : [u8; N]) -> [u8; 32] {} Here is a list of the current black box functions that are supported by UltraPlonk: - AES -- [SHA256](./cryptographic_primitives/hashes.mdx#sha256) -- [Schnorr signature verification](./cryptographic_primitives/schnorr.mdx) -- [Blake2s](./cryptographic_primitives/hashes.mdx#blake2s) -- [Pedersen Hash](./cryptographic_primitives/hashes.mdx#pedersen_hash) -- [Pedersen Commitment](./cryptographic_primitives/hashes.mdx#pedersen_commitment) -- [HashToField128Security](./cryptographic_primitives/hashes.mdx#hash_to_field) -- [ECDSA signature verification](./cryptographic_primitives/ecdsa_sig_verification.mdx) -- [Fixed base scalar multiplication](./cryptographic_primitives/scalar.mdx) +- [SHA256](./cryptographic_primitives/00_hashes.mdx#sha256) +- [Schnorr signature verification](./cryptographic_primitives/02_schnorr.mdx) +- [Blake2s](./cryptographic_primitives/00_hashes.mdx#blake2s) +- [Pedersen Hash](./cryptographic_primitives/00_hashes.mdx#pedersen_hash) +- [Pedersen Commitment](./cryptographic_primitives/00_hashes.mdx#pedersen_commitment) +- [HashToField128Security](./cryptographic_primitives/00_hashes.mdx#hash_to_field) +- [ECDSA signature verification](./cryptographic_primitives/03_ecdsa_sig_verification.mdx) +- [Fixed base scalar multiplication](./cryptographic_primitives/01_scalar.mdx) - [Compute merkle root](./merkle_trees.md#compute_merkle_root) - AND - XOR - RANGE -- [Keccak256](./cryptographic_primitives/hashes.mdx#keccak256) +- [Keccak256](./cryptographic_primitives/00_hashes.mdx#keccak256) - [Recursive proof verification](./recursion.md) Most black box functions are included as part of the Noir standard library, however `AND`, `XOR` and `RANGE` are used as part of the Noir language syntax. For instance, using the bitwise operator `&` will invoke the `AND` black box function. To ensure compatibility across backends, the ACVM has fallback implementations of `AND`, `XOR` and `RANGE` defined in its standard library which it can seamlessly fallback to if the backend doesn't support them. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.31.0/getting_started/backend/_category_.json b/noir/noir-repo/docs/versioned_docs/version-v0.31.0/getting_started/backend/_category_.json new file mode 100644 index 000000000000..b82e92beb0ca --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.31.0/getting_started/backend/_category_.json @@ -0,0 +1,6 @@ +{ + "position": 1, + "label": "Install Proving Backend", + "collapsible": true, + "collapsed": true +} diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.31.0/getting_started/backend/index.md b/noir/noir-repo/docs/versioned_docs/version-v0.31.0/getting_started/backend/index.md new file mode 100644 index 000000000000..7192d9548774 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.31.0/getting_started/backend/index.md @@ -0,0 +1,31 @@ +--- +title: Proving Backend Installation +description: Proving backends offer command line tools for proving and verifying Noir programs. This page describes how to install `bb` as an example. +keywords: [ + Proving + Backend + Barretenberg + bb + bbup + Installation + Terminal + Command + CLI + Version +] +pagination_next: getting_started/hello_noir/index +--- + +Proving backends each provide their own tools for working with Noir programs, providing functionality like proof generation, proof verification, and verifier smart contract generation. + +For the latest information on tooling provided by each proving backend, installation instructions, Noir version compatibility... you may refer to the proving backends' own documentation. + +You can find the full list of proving backends compatible with Noir in [Awesome Noir](https://github.com/noir-lang/awesome-noir/?tab=readme-ov-file#proving-backends). + +## Example: Installing `bb` + +`bb` is the CLI tool provided by the [Barretenberg proving backend](https://github.com/AztecProtocol/barretenberg) developed by Aztec Labs. + +You can find the instructions for installation in [`bb`'s documentation](https://github.com/AztecProtocol/aztec-packages/blob/master/barretenberg/cpp/src/barretenberg/bb/readme.md#installation). + +Once installed, we are ready to start working on [our first Noir program](../hello_noir/index.md). diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.31.0/getting_started/barretenberg/index.md b/noir/noir-repo/docs/versioned_docs/version-v0.31.0/getting_started/barretenberg/index.md deleted file mode 100644 index 0102c86770b7..000000000000 --- a/noir/noir-repo/docs/versioned_docs/version-v0.31.0/getting_started/barretenberg/index.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -title: Barretenberg Installation -description: bb is a command line tool for interacting with Aztec's proving backend Barretenberg. This page is a quick guide on how to install `bb` -keywords: [ - Barretenberg - bb - Installation - Terminal Commands - Version Check - Nightlies - Specific Versions - Branches -] -pagination_next: getting_started/hello_noir/index ---- - -`bb` is the CLI tool for generating and verifying proofs for Noir programs using the Barretenberg proving library. It also allows generating solidity verifier contracts for which you can verify contracts which were constructed using `bb`. - -## Installing `bb` - -Open a terminal on your machine, and write: - -##### macOS (Apple Silicon) - -```bash -curl -L https://raw.githubusercontent.com/AztecProtocol/aztec-packages/master/barretenberg/cpp/installation/install | bash -source ~/.zshrc -bbup -v 0.41.0 -``` - -##### macOS (Intel) - -```bash -curl -L https://raw.githubusercontent.com/AztecProtocol/aztec-packages/master/barretenberg/cpp/installation/install | bash -source ~/.zshrc -bbup -v 0.41.0 -``` - -##### Linux (Bash) - -```bash -curl -L https://raw.githubusercontent.com/AztecProtocol/aztec-packages/master/barretenberg/cpp/installation/install | bash -source ~/.bashrc -bbup -v 0.41.0 -``` - -Now we're ready to start working on [our first Noir program!](../hello_noir/index.md) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.31.0/getting_started/hello_noir/index.md b/noir/noir-repo/docs/versioned_docs/version-v0.31.0/getting_started/hello_noir/index.md index 1ade3f09ae3d..3baae217eb30 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.31.0/getting_started/hello_noir/index.md +++ b/noir/noir-repo/docs/versioned_docs/version-v0.31.0/getting_started/hello_noir/index.md @@ -17,22 +17,25 @@ sidebar_position: 1 --- -Now that we have installed Nargo, it is time to make our first hello world program! +Now that we have installed Nargo and a proving backend, it is time to make our first hello world program! -## Create a Project Directory +### 1. Create a new project directory Noir code can live anywhere on your computer. Let us create a _projects_ folder in the home -directory to house our Noir programs. +directory to house our first Noir program. -For Linux, macOS, and Windows PowerShell, create the directory and change directory into it by -running: +Create the directory and change directory into it by running: ```sh mkdir ~/projects cd ~/projects ``` -## Create Our First Nargo Project +## Nargo + +Nargo provides the ability to initiate and execute Noir projects. Read the [Nargo installation](../installation/index.md) section to learn more about Nargo and how to install it. + +### 2. Create a new Noir project Now that we are in the projects directory, create a new Nargo project by running: @@ -40,18 +43,15 @@ Now that we are in the projects directory, create a new Nargo project by running nargo new hello_world ``` -> **Note:** `hello_world` can be any arbitrary project name, we are simply using `hello_world` for -> demonstration. -> -> In production, the common practice is to name the project folder as `circuits` for better -> identifiability when sitting alongside other folders in the codebase (e.g. `contracts`, `scripts`, -> `test`). +`hello_world` can be any arbitrary project name, we are simply using `hello_world` for demonstration. + +In production, it is common practice to name the project folder, `circuits`, for clarity amongst other folders in the codebase (like: `contracts`, `scripts`, and `test`). A `hello_world` folder would be created. Similar to Rust, the folder houses _src/main.nr_ and _Nargo.toml_ which contain the source code and environmental options of your Noir program respectively. -### Intro to Noir Syntax +#### Intro to Noir Syntax Let us take a closer look at _main.nr_. The default _main.nr_ generated should look like this: @@ -81,7 +81,7 @@ The Noir syntax `assert` can be interpreted as something similar to constraints For more Noir syntax, check the [Language Concepts](../../noir/concepts/comments.md) chapter. -## Build In/Output Files +### 3. Build in/output files Change directory into _hello_world_ and build in/output files for your Noir program by running: @@ -92,7 +92,7 @@ nargo check A _Prover.toml_ file will be generated in your project directory, to allow specifying input values to the program. -## Execute Our Noir Program +### 4. Execute the Noir program Now that the project is set up, we can execute our Noir program. @@ -111,34 +111,41 @@ nargo execute witness-name The witness corresponding to this execution will then be written to the file `./target/witness-name.gz`. -## Prove Our Noir Program +The command also automatically compiles your Noir program if it was not already / was edited, which you may notice the compiled artifacts being written to the file `./target/hello_world.json`. + +## Proving Backend -:::info +Proving backends provide the ability to generate and verify proofs of executing Noir programs, following Noir's tooling that compiles and executes the programs. Read the [proving backend installation](../backend/index.md) section to learn more about proving backends and how to install them. -Nargo no longer handles communicating with backends in order to generate proofs. In order to prove/verify your Noir programs, you'll need an installation of [bb](../barretenberg/index.md). +Barretenberg is used as an example here to demonstrate how proving and verifying could be implemented and used. Read the [`bb` installation](../backend/index.md#example-installing-bb) section for how to install Barretenberg's CLI tool; refer to [`bb`'s documentation](https://github.com/AztecProtocol/aztec-packages/blob/master/barretenberg/cpp/src/barretenberg/bb/readme.md) for full details about the tool. -::: +### 5. Prove an execution of the Noir program -Prove the valid execution of your Noir program using `bb`: +Using Barretenberg as an example, prove the valid execution of your Noir program running: ```sh -bb prove -b ./target/hello_world.json -w ./target/witness-name.gz -o ./proof +bb prove -b ./target/hello_world.json -w ./target/witness-name.gz -o ./target/proof ``` -A new file called `proof` will be generated in your project directory, containing the generated proof for your program. +The proof generated will then be written to the file `./target/proof`. -## Verify Our Noir Program +### 6. Verify the execution proof Once a proof is generated, we can verify correct execution of our Noir program by verifying the proof file. -Verify your proof by running: +Using Barretenberg as an example, compute the verification key for the Noir program by running: ```sh bb write_vk -b ./target/hello_world.json -o ./target/vk -bb verify -k ./target/vk -p ./proof ``` -The verification will complete in silence if it is successful. If it fails, it will log the corresponding error instead. +And verify your proof by running: + +```sh +bb verify -k ./target/vk -p ./target/proof +``` + +If successful, the verification will complete in silence; if unsuccessful, the command will trigger logging of the corresponding error. Congratulations, you have now created and verified a proof for your very first Noir program! diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.31.0/getting_started/hello_noir/project_breakdown.md b/noir/noir-repo/docs/versioned_docs/version-v0.31.0/getting_started/hello_noir/project_breakdown.md index 29688df148fe..96e653f6c08d 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.31.0/getting_started/hello_noir/project_breakdown.md +++ b/noir/noir-repo/docs/versioned_docs/version-v0.31.0/getting_started/hello_noir/project_breakdown.md @@ -8,8 +8,7 @@ keywords: sidebar_position: 2 --- -This section breaks down our hello world program from the previous section. We elaborate on the project -structure and what the `prove` and `verify` commands did. +This section breaks down our hello world program from the previous section. ## Anatomy of a Nargo Project @@ -67,6 +66,7 @@ The package section defines a number of fields including: - `entry` (optional) - a relative filepath to use as the entry point into your package (overrides the default of `src/lib.nr` or `src/main.nr`) - `backend` (optional) - `license` (optional) +- `expression_width` (optional) - Sets the default backend expression width. This field will override the default backend expression width specified by the Noir compiler (currently set to width 4). #### Dependencies section diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.31.0/getting_started/installation/index.md b/noir/noir-repo/docs/versioned_docs/version-v0.31.0/getting_started/installation/index.md index 4ef86aa59147..53ea9c7891ca 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.31.0/getting_started/installation/index.md +++ b/noir/noir-repo/docs/versioned_docs/version-v0.31.0/getting_started/installation/index.md @@ -19,11 +19,9 @@ keywords: [ pagination_next: getting_started/hello_noir/index --- -`nargo` is the one-stop-shop for almost everything related with Noir. The name comes from our love for Rust and its package manager `cargo`. +`nargo` is a tool for working with Noir programs on the CLI, providing you with the ability to start new projects, compile, execute and test Noir programs from the terminal. -With `nargo`, you can start new projects, compile, execute, prove, verify, test, generate solidity contracts, and do pretty much all that is available in Noir. - -Similarly to `rustup`, we also maintain an easy installation method that covers most machines: `noirup`. +The name is inspired by Rust's package manager `cargo`; and similar to Rust's `rustup`, Noir also has an easy installation script `noirup`. ## Installing Noirup diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.31.0/tutorials/noirjs_app.md b/noir/noir-repo/docs/versioned_docs/version-v0.31.0/tutorials/noirjs_app.md index cbb1938a5c6f..8c23b639f123 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.31.0/tutorials/noirjs_app.md +++ b/noir/noir-repo/docs/versioned_docs/version-v0.31.0/tutorials/noirjs_app.md @@ -14,13 +14,13 @@ You can find the complete app code for this guide [here](https://github.com/noir :::note -Feel free to use whatever versions, just keep in mind that Nargo and the NoirJS packages are meant to be in sync. For example, Nargo 0.27.x matches `noir_js@0.27.x`, etc. +Feel free to use whatever versions, just keep in mind that Nargo and the NoirJS packages are meant to be in sync. For example, Nargo 0.31.x matches `noir_js@0.31.x`, etc. -In this guide, we will be pinned to 0.27.0. +In this guide, we will be pinned to 0.31.0. ::: -Before we start, we want to make sure we have Node and Nargo installed. +Before we start, we want to make sure we have Node, Nargo and the Barretenberg proving system (`bb`) installed. We start by opening a terminal and executing `node --version`. If we don't get an output like `v20.10.0`, that means node is not installed. Let's do that by following the handy [nvm guide](https://github.com/nvm-sh/nvm?tab=readme-ov-file#install--update-script). @@ -30,6 +30,9 @@ As for `Nargo`, we can follow the [Nargo guide](../getting_started/installation/ curl -L https://raw.githubusercontent.com/noir-lang/noirup/main/install | bash ``` +Follow the instructions on [this page](https://github.com/AztecProtocol/aztec-packages/tree/master/barretenberg/cpp/src/barretenberg/bb#installation) to install `bb`. +Version 0.41.0 is compatible with `nargo` version 0.31.0, which you can install with `bbup -v 0.41.0` once `bbup` is installed. + Easy enough. Onwards! ## Our project @@ -42,13 +45,17 @@ In fact, it's so simple that it comes nicely packaged in `nargo`. Let's do that! Run: -`nargo new circuit` +```bash +nargo new circuit +``` And... That's about it. Your program is ready to be compiled and run. To compile, let's `cd` into the `circuit` folder to enter our project, and call: -`nargo compile` +```bash +nargo compile +``` This compiles our circuit into `json` format and add it to a new `target` folder. @@ -92,30 +99,53 @@ Before we proceed with any coding, let's get our environment tailored for Noir. In your freshly minted `vite-project` folder, create a new file named `vite.config.js` and open it in your code editor. Paste the following to set the stage: ```javascript -import { defineConfig } from "vite"; -import copy from "rollup-plugin-copy"; - -export default defineConfig({ - esbuild: { - target: "esnext", - }, - optimizeDeps: { - esbuildOptions: { - target: "esnext", - }, +import { defineConfig } from 'vite'; +import copy from 'rollup-plugin-copy'; +import fs from 'fs'; +import path from 'path'; + +const wasmContentTypePlugin = { + name: 'wasm-content-type-plugin', + configureServer(server) { + server.middlewares.use(async (req, res, next) => { + if (req.url.endsWith('.wasm')) { + res.setHeader('Content-Type', 'application/wasm'); + const newPath = req.url.replace('deps', 'dist'); + const targetPath = path.join(__dirname, newPath); + const wasmContent = fs.readFileSync(targetPath); + return res.end(wasmContent); + } + next(); + }); }, - plugins: [ - copy({ - targets: [ - { src: "node_modules/**/*.wasm", dest: "node_modules/.vite/dist" }, +}; + +export default defineConfig(({ command }) => { + if (command === 'serve') { + return { + build: { + target: 'esnext', + rollupOptions: { + external: ['@aztec/bb.js'] + } + }, + optimizeDeps: { + esbuildOptions: { + target: 'esnext' + } + }, + plugins: [ + copy({ + targets: [{ src: 'node_modules/**/*.wasm', dest: 'node_modules/.vite/dist' }], + copySync: true, + hook: 'buildStart', + }), + command === 'serve' ? wasmContentTypePlugin : [], ], - copySync: true, - hook: "buildStart", - }), - ], - server: { - port: 3000, - }, + }; + } + + return {}; }); ``` @@ -124,7 +154,7 @@ export default defineConfig({ Now that our stage is set, install the necessary NoirJS packages along with our other dependencies: ```bash -npm install && npm install @noir-lang/backend_barretenberg@0.27.0 @noir-lang/noir_js@0.27.0 +npm install && npm install @noir-lang/backend_barretenberg@0.31.0 @noir-lang/noir_js@0.31.0 npm install rollup-plugin-copy --save-dev ``` @@ -193,17 +223,6 @@ Our love for Noir needs undivided attention, so let's just open `main.js` and de Start by pasting in this boilerplate code: ```js -const setup = async () => { - await Promise.all([ - import('@noir-lang/noirc_abi').then((module) => - module.default(new URL('@noir-lang/noirc_abi/web/noirc_abi_wasm_bg.wasm', import.meta.url).toString()), - ), - import('@noir-lang/acvm_js').then((module) => - module.default(new URL('@noir-lang/acvm_js/web/acvm_js_bg.wasm', import.meta.url).toString()), - ), - ]); -}; - function display(container, msg) { const c = document.getElementById(container); const p = document.createElement('p'); @@ -222,8 +241,6 @@ document.getElementById('submitGuess').addEventListener('click', async () => { The display function doesn't do much. We're simply manipulating our website to see stuff happening. For example, if the proof fails, it will simply log a broken heart 😢 -As for the `setup` function, it's just a sad reminder that dealing with `wasm` on the browser is not as easy as it should. Just copy, paste, and forget. - :::info At this point in the tutorial, your folder structure should look like this: @@ -310,9 +327,13 @@ Time to celebrate, yes! But we shouldn't trust machines so blindly. Let's add th ```js display('logs', 'Verifying proof... ⌛'); -const verificationKey = await backend.getVerificationKey(); -const verifier = new Verifier(); -const isValid = await verifier.verifyProof(proof, verificationKey); +const isValid = await backend.verifyProof(proof); + +// or to cache and use the verification key: +// const verificationKey = await backend.getVerificationKey(); +// const verifier = new Verifier(); +// const isValid = await verifier.verifyProof(proof, verificationKey); + if (isValid) display('logs', 'Verifying proof... ✅'); ``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/getting_started/backend/_category_.json b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/getting_started/backend/_category_.json new file mode 100644 index 000000000000..b82e92beb0ca --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/getting_started/backend/_category_.json @@ -0,0 +1,6 @@ +{ + "position": 1, + "label": "Install Proving Backend", + "collapsible": true, + "collapsed": true +} diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/getting_started/backend/index.md b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/getting_started/backend/index.md new file mode 100644 index 000000000000..7192d9548774 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/getting_started/backend/index.md @@ -0,0 +1,31 @@ +--- +title: Proving Backend Installation +description: Proving backends offer command line tools for proving and verifying Noir programs. This page describes how to install `bb` as an example. +keywords: [ + Proving + Backend + Barretenberg + bb + bbup + Installation + Terminal + Command + CLI + Version +] +pagination_next: getting_started/hello_noir/index +--- + +Proving backends each provide their own tools for working with Noir programs, providing functionality like proof generation, proof verification, and verifier smart contract generation. + +For the latest information on tooling provided by each proving backend, installation instructions, Noir version compatibility... you may refer to the proving backends' own documentation. + +You can find the full list of proving backends compatible with Noir in [Awesome Noir](https://github.com/noir-lang/awesome-noir/?tab=readme-ov-file#proving-backends). + +## Example: Installing `bb` + +`bb` is the CLI tool provided by the [Barretenberg proving backend](https://github.com/AztecProtocol/barretenberg) developed by Aztec Labs. + +You can find the instructions for installation in [`bb`'s documentation](https://github.com/AztecProtocol/aztec-packages/blob/master/barretenberg/cpp/src/barretenberg/bb/readme.md#installation). + +Once installed, we are ready to start working on [our first Noir program](../hello_noir/index.md). diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/getting_started/barretenberg/index.md b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/getting_started/barretenberg/index.md deleted file mode 100644 index 0102c86770b7..000000000000 --- a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/getting_started/barretenberg/index.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -title: Barretenberg Installation -description: bb is a command line tool for interacting with Aztec's proving backend Barretenberg. This page is a quick guide on how to install `bb` -keywords: [ - Barretenberg - bb - Installation - Terminal Commands - Version Check - Nightlies - Specific Versions - Branches -] -pagination_next: getting_started/hello_noir/index ---- - -`bb` is the CLI tool for generating and verifying proofs for Noir programs using the Barretenberg proving library. It also allows generating solidity verifier contracts for which you can verify contracts which were constructed using `bb`. - -## Installing `bb` - -Open a terminal on your machine, and write: - -##### macOS (Apple Silicon) - -```bash -curl -L https://raw.githubusercontent.com/AztecProtocol/aztec-packages/master/barretenberg/cpp/installation/install | bash -source ~/.zshrc -bbup -v 0.41.0 -``` - -##### macOS (Intel) - -```bash -curl -L https://raw.githubusercontent.com/AztecProtocol/aztec-packages/master/barretenberg/cpp/installation/install | bash -source ~/.zshrc -bbup -v 0.41.0 -``` - -##### Linux (Bash) - -```bash -curl -L https://raw.githubusercontent.com/AztecProtocol/aztec-packages/master/barretenberg/cpp/installation/install | bash -source ~/.bashrc -bbup -v 0.41.0 -``` - -Now we're ready to start working on [our first Noir program!](../hello_noir/index.md) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/getting_started/hello_noir/index.md b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/getting_started/hello_noir/index.md index 1ade3f09ae3d..3baae217eb30 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/getting_started/hello_noir/index.md +++ b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/getting_started/hello_noir/index.md @@ -17,22 +17,25 @@ sidebar_position: 1 --- -Now that we have installed Nargo, it is time to make our first hello world program! +Now that we have installed Nargo and a proving backend, it is time to make our first hello world program! -## Create a Project Directory +### 1. Create a new project directory Noir code can live anywhere on your computer. Let us create a _projects_ folder in the home -directory to house our Noir programs. +directory to house our first Noir program. -For Linux, macOS, and Windows PowerShell, create the directory and change directory into it by -running: +Create the directory and change directory into it by running: ```sh mkdir ~/projects cd ~/projects ``` -## Create Our First Nargo Project +## Nargo + +Nargo provides the ability to initiate and execute Noir projects. Read the [Nargo installation](../installation/index.md) section to learn more about Nargo and how to install it. + +### 2. Create a new Noir project Now that we are in the projects directory, create a new Nargo project by running: @@ -40,18 +43,15 @@ Now that we are in the projects directory, create a new Nargo project by running nargo new hello_world ``` -> **Note:** `hello_world` can be any arbitrary project name, we are simply using `hello_world` for -> demonstration. -> -> In production, the common practice is to name the project folder as `circuits` for better -> identifiability when sitting alongside other folders in the codebase (e.g. `contracts`, `scripts`, -> `test`). +`hello_world` can be any arbitrary project name, we are simply using `hello_world` for demonstration. + +In production, it is common practice to name the project folder, `circuits`, for clarity amongst other folders in the codebase (like: `contracts`, `scripts`, and `test`). A `hello_world` folder would be created. Similar to Rust, the folder houses _src/main.nr_ and _Nargo.toml_ which contain the source code and environmental options of your Noir program respectively. -### Intro to Noir Syntax +#### Intro to Noir Syntax Let us take a closer look at _main.nr_. The default _main.nr_ generated should look like this: @@ -81,7 +81,7 @@ The Noir syntax `assert` can be interpreted as something similar to constraints For more Noir syntax, check the [Language Concepts](../../noir/concepts/comments.md) chapter. -## Build In/Output Files +### 3. Build in/output files Change directory into _hello_world_ and build in/output files for your Noir program by running: @@ -92,7 +92,7 @@ nargo check A _Prover.toml_ file will be generated in your project directory, to allow specifying input values to the program. -## Execute Our Noir Program +### 4. Execute the Noir program Now that the project is set up, we can execute our Noir program. @@ -111,34 +111,41 @@ nargo execute witness-name The witness corresponding to this execution will then be written to the file `./target/witness-name.gz`. -## Prove Our Noir Program +The command also automatically compiles your Noir program if it was not already / was edited, which you may notice the compiled artifacts being written to the file `./target/hello_world.json`. + +## Proving Backend -:::info +Proving backends provide the ability to generate and verify proofs of executing Noir programs, following Noir's tooling that compiles and executes the programs. Read the [proving backend installation](../backend/index.md) section to learn more about proving backends and how to install them. -Nargo no longer handles communicating with backends in order to generate proofs. In order to prove/verify your Noir programs, you'll need an installation of [bb](../barretenberg/index.md). +Barretenberg is used as an example here to demonstrate how proving and verifying could be implemented and used. Read the [`bb` installation](../backend/index.md#example-installing-bb) section for how to install Barretenberg's CLI tool; refer to [`bb`'s documentation](https://github.com/AztecProtocol/aztec-packages/blob/master/barretenberg/cpp/src/barretenberg/bb/readme.md) for full details about the tool. -::: +### 5. Prove an execution of the Noir program -Prove the valid execution of your Noir program using `bb`: +Using Barretenberg as an example, prove the valid execution of your Noir program running: ```sh -bb prove -b ./target/hello_world.json -w ./target/witness-name.gz -o ./proof +bb prove -b ./target/hello_world.json -w ./target/witness-name.gz -o ./target/proof ``` -A new file called `proof` will be generated in your project directory, containing the generated proof for your program. +The proof generated will then be written to the file `./target/proof`. -## Verify Our Noir Program +### 6. Verify the execution proof Once a proof is generated, we can verify correct execution of our Noir program by verifying the proof file. -Verify your proof by running: +Using Barretenberg as an example, compute the verification key for the Noir program by running: ```sh bb write_vk -b ./target/hello_world.json -o ./target/vk -bb verify -k ./target/vk -p ./proof ``` -The verification will complete in silence if it is successful. If it fails, it will log the corresponding error instead. +And verify your proof by running: + +```sh +bb verify -k ./target/vk -p ./target/proof +``` + +If successful, the verification will complete in silence; if unsuccessful, the command will trigger logging of the corresponding error. Congratulations, you have now created and verified a proof for your very first Noir program! diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/getting_started/hello_noir/project_breakdown.md b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/getting_started/hello_noir/project_breakdown.md index 525b8dabdd84..96e653f6c08d 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/getting_started/hello_noir/project_breakdown.md +++ b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/getting_started/hello_noir/project_breakdown.md @@ -8,8 +8,7 @@ keywords: sidebar_position: 2 --- -This section breaks down our hello world program from the previous section. We elaborate on the project -structure and what the `prove` and `verify` commands did. +This section breaks down our hello world program from the previous section. ## Anatomy of a Nargo Project diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/getting_started/installation/index.md b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/getting_started/installation/index.md index 4ef86aa59147..53ea9c7891ca 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/getting_started/installation/index.md +++ b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/getting_started/installation/index.md @@ -19,11 +19,9 @@ keywords: [ pagination_next: getting_started/hello_noir/index --- -`nargo` is the one-stop-shop for almost everything related with Noir. The name comes from our love for Rust and its package manager `cargo`. +`nargo` is a tool for working with Noir programs on the CLI, providing you with the ability to start new projects, compile, execute and test Noir programs from the terminal. -With `nargo`, you can start new projects, compile, execute, prove, verify, test, generate solidity contracts, and do pretty much all that is available in Noir. - -Similarly to `rustup`, we also maintain an easy installation method that covers most machines: `noirup`. +The name is inspired by Rust's package manager `cargo`; and similar to Rust's `rustup`, Noir also has an easy installation script `noirup`. ## Installing Noirup diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/tutorials/noirjs_app.md b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/tutorials/noirjs_app.md index cbb1938a5c6f..8c23b639f123 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/tutorials/noirjs_app.md +++ b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/tutorials/noirjs_app.md @@ -14,13 +14,13 @@ You can find the complete app code for this guide [here](https://github.com/noir :::note -Feel free to use whatever versions, just keep in mind that Nargo and the NoirJS packages are meant to be in sync. For example, Nargo 0.27.x matches `noir_js@0.27.x`, etc. +Feel free to use whatever versions, just keep in mind that Nargo and the NoirJS packages are meant to be in sync. For example, Nargo 0.31.x matches `noir_js@0.31.x`, etc. -In this guide, we will be pinned to 0.27.0. +In this guide, we will be pinned to 0.31.0. ::: -Before we start, we want to make sure we have Node and Nargo installed. +Before we start, we want to make sure we have Node, Nargo and the Barretenberg proving system (`bb`) installed. We start by opening a terminal and executing `node --version`. If we don't get an output like `v20.10.0`, that means node is not installed. Let's do that by following the handy [nvm guide](https://github.com/nvm-sh/nvm?tab=readme-ov-file#install--update-script). @@ -30,6 +30,9 @@ As for `Nargo`, we can follow the [Nargo guide](../getting_started/installation/ curl -L https://raw.githubusercontent.com/noir-lang/noirup/main/install | bash ``` +Follow the instructions on [this page](https://github.com/AztecProtocol/aztec-packages/tree/master/barretenberg/cpp/src/barretenberg/bb#installation) to install `bb`. +Version 0.41.0 is compatible with `nargo` version 0.31.0, which you can install with `bbup -v 0.41.0` once `bbup` is installed. + Easy enough. Onwards! ## Our project @@ -42,13 +45,17 @@ In fact, it's so simple that it comes nicely packaged in `nargo`. Let's do that! Run: -`nargo new circuit` +```bash +nargo new circuit +``` And... That's about it. Your program is ready to be compiled and run. To compile, let's `cd` into the `circuit` folder to enter our project, and call: -`nargo compile` +```bash +nargo compile +``` This compiles our circuit into `json` format and add it to a new `target` folder. @@ -92,30 +99,53 @@ Before we proceed with any coding, let's get our environment tailored for Noir. In your freshly minted `vite-project` folder, create a new file named `vite.config.js` and open it in your code editor. Paste the following to set the stage: ```javascript -import { defineConfig } from "vite"; -import copy from "rollup-plugin-copy"; - -export default defineConfig({ - esbuild: { - target: "esnext", - }, - optimizeDeps: { - esbuildOptions: { - target: "esnext", - }, +import { defineConfig } from 'vite'; +import copy from 'rollup-plugin-copy'; +import fs from 'fs'; +import path from 'path'; + +const wasmContentTypePlugin = { + name: 'wasm-content-type-plugin', + configureServer(server) { + server.middlewares.use(async (req, res, next) => { + if (req.url.endsWith('.wasm')) { + res.setHeader('Content-Type', 'application/wasm'); + const newPath = req.url.replace('deps', 'dist'); + const targetPath = path.join(__dirname, newPath); + const wasmContent = fs.readFileSync(targetPath); + return res.end(wasmContent); + } + next(); + }); }, - plugins: [ - copy({ - targets: [ - { src: "node_modules/**/*.wasm", dest: "node_modules/.vite/dist" }, +}; + +export default defineConfig(({ command }) => { + if (command === 'serve') { + return { + build: { + target: 'esnext', + rollupOptions: { + external: ['@aztec/bb.js'] + } + }, + optimizeDeps: { + esbuildOptions: { + target: 'esnext' + } + }, + plugins: [ + copy({ + targets: [{ src: 'node_modules/**/*.wasm', dest: 'node_modules/.vite/dist' }], + copySync: true, + hook: 'buildStart', + }), + command === 'serve' ? wasmContentTypePlugin : [], ], - copySync: true, - hook: "buildStart", - }), - ], - server: { - port: 3000, - }, + }; + } + + return {}; }); ``` @@ -124,7 +154,7 @@ export default defineConfig({ Now that our stage is set, install the necessary NoirJS packages along with our other dependencies: ```bash -npm install && npm install @noir-lang/backend_barretenberg@0.27.0 @noir-lang/noir_js@0.27.0 +npm install && npm install @noir-lang/backend_barretenberg@0.31.0 @noir-lang/noir_js@0.31.0 npm install rollup-plugin-copy --save-dev ``` @@ -193,17 +223,6 @@ Our love for Noir needs undivided attention, so let's just open `main.js` and de Start by pasting in this boilerplate code: ```js -const setup = async () => { - await Promise.all([ - import('@noir-lang/noirc_abi').then((module) => - module.default(new URL('@noir-lang/noirc_abi/web/noirc_abi_wasm_bg.wasm', import.meta.url).toString()), - ), - import('@noir-lang/acvm_js').then((module) => - module.default(new URL('@noir-lang/acvm_js/web/acvm_js_bg.wasm', import.meta.url).toString()), - ), - ]); -}; - function display(container, msg) { const c = document.getElementById(container); const p = document.createElement('p'); @@ -222,8 +241,6 @@ document.getElementById('submitGuess').addEventListener('click', async () => { The display function doesn't do much. We're simply manipulating our website to see stuff happening. For example, if the proof fails, it will simply log a broken heart 😢 -As for the `setup` function, it's just a sad reminder that dealing with `wasm` on the browser is not as easy as it should. Just copy, paste, and forget. - :::info At this point in the tutorial, your folder structure should look like this: @@ -310,9 +327,13 @@ Time to celebrate, yes! But we shouldn't trust machines so blindly. Let's add th ```js display('logs', 'Verifying proof... ⌛'); -const verificationKey = await backend.getVerificationKey(); -const verifier = new Verifier(); -const isValid = await verifier.verifyProof(proof, verificationKey); +const isValid = await backend.verifyProof(proof); + +// or to cache and use the verification key: +// const verificationKey = await backend.getVerificationKey(); +// const verifier = new Verifier(); +// const isValid = await verifier.verifyProof(proof, verificationKey); + if (isValid) display('logs', 'Verifying proof... ✅'); ``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/explainers/cspell.json b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/explainers/cspell.json new file mode 100644 index 000000000000..c60b0a597b10 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/explainers/cspell.json @@ -0,0 +1,5 @@ +{ + "words": [ + "Cryptdoku" + ] +} diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/explainers/explainer-oracle.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/explainers/explainer-oracle.md new file mode 100644 index 000000000000..821e1f95c04e --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/explainers/explainer-oracle.md @@ -0,0 +1,57 @@ +--- +title: Oracles +description: This guide provides an in-depth understanding of how Oracles work in Noir programming. Learn how to use outside calculations in your programs, constrain oracles, and understand their uses and limitations. +keywords: + - Noir Programming + - Oracles + - JSON-RPC + - Foreign Call Handlers + - Constrained Functions + - Blockchain Programming +sidebar_position: 1 +--- + +If you've seen "The Matrix" you may recall "The Oracle" as Gloria Foster smoking cigarettes and baking cookies. While she appears to "know things", she is actually providing a calculation of a pre-determined future. Noir Oracles are similar, in a way. They don't calculate the future (yet), but they allow you to use outside calculations in your programs. + +![matrix oracle prediction](@site/static/img/memes/matrix_oracle.jpeg) + +A Noir program is usually self-contained. You can pass certain inputs to it, and it will generate a deterministic output for those inputs. But what if you wanted to defer some calculation to an outside process or source? + +Oracles are functions that provide this feature. + +## Use cases + +An example usage for Oracles is proving something on-chain. For example, proving that the ETH-USDC quote was below a certain target at a certain block time. Or even making more complex proofs like proving the ownership of an NFT as an anonymous login method. + +Another interesting use case is to defer expensive calculations to be made outside of the Noir program, and then constraining the result; similar to the use of [unconstrained functions](../noir/concepts//unconstrained.md). + +In short, anything that can be constrained in a Noir program but needs to be fetched from an external source is a great candidate to be used in oracles. + +## Constraining oracles + +Just like in The Matrix, Oracles are powerful. But with great power, comes great responsibility. Just because you're using them in a Noir program doesn't mean they're true. Noir has no superpowers. If you want to prove that Portugal won the Euro Cup 2016, you're still relying on potentially untrusted information. + +To give a concrete example, Alice wants to login to the [NounsDAO](https://nouns.wtf/) forum with her username "noir_nouner" by proving she owns a noun without revealing her ethereum address. Her Noir program could have an oracle call like this: + +```rust +#[oracle(getNoun)] +unconstrained fn get_noun(address: Field) -> Field +``` + +This oracle could naively resolve with the number of Nouns she possesses. However, it is useless as a trusted source, as the oracle could resolve to anything Alice wants. In order to make this oracle call actually useful, Alice would need to constrain the response from the oracle, by proving her address and the noun count belongs to the state tree of the contract. + +In short, **Oracles don't prove anything. Your Noir program does.** + +:::danger + +If you don't constrain the return of your oracle, you could be clearly opening an attack vector on your Noir program. Make double-triple sure that the return of an oracle call is constrained! + +::: + +## How to use Oracles + +On CLI, Nargo resolves oracles by making JSON RPC calls, which means it would require an RPC node to be running. + +In JavaScript, NoirJS accepts and resolves arbitrary call handlers (that is, not limited to JSON) as long as they match the expected types the developer defines. Refer to [Foreign Call Handler](../reference/NoirJS/noir_js/type-aliases/ForeignCallHandler.md) to learn more about NoirJS's call handling. + +If you want to build using oracles, follow through to the [oracle guide](../how_to/how-to-oracles.md) for a simple example on how to do that. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/explainers/explainer-recursion.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/explainers/explainer-recursion.md new file mode 100644 index 000000000000..18846176ca74 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/explainers/explainer-recursion.md @@ -0,0 +1,176 @@ +--- +title: Recursive proofs +description: Explore the concept of recursive proofs in Zero-Knowledge programming. Understand how recursion works in Noir, a language for writing smart contracts on the EVM blockchain. Learn through practical examples like Alice and Bob's guessing game, Charlie's recursive merkle tree, and Daniel's reusable components. Discover how to use recursive proofs to optimize computational resources and improve efficiency. + +keywords: + [ + "Recursive Proofs", + "Zero-Knowledge Programming", + "Noir", + "EVM Blockchain", + "Smart Contracts", + "Recursion in Noir", + "Alice and Bob Guessing Game", + "Recursive Merkle Tree", + "Reusable Components", + "Optimizing Computational Resources", + "Improving Efficiency", + "Verification Key", + "Aggregation", + "Recursive zkSNARK schemes", + "PLONK", + "Proving and Verification Keys" + ] +sidebar_position: 1 +pagination_next: how_to/how-to-recursion +--- + +In programming, we tend to think of recursion as something calling itself. A classic example would be the calculation of the factorial of a number: + +```js +function factorial(n) { + if (n === 0 || n === 1) { + return 1; + } else { + return n * factorial(n - 1); + } +} +``` + +In this case, while `n` is not `1`, this function will keep calling itself until it hits the base case, bubbling up the result on the call stack: + +```md + Is `n` 1? <--------- + /\ / + / \ n = n -1 + / \ / + Yes No -------- +``` + +In Zero-Knowledge, recursion has some similarities. + +It is not a Noir function calling itself, but a proof being used as an input to another circuit. In short, you verify one proof *inside* another proof, returning the proof that both proofs are valid. + +This means that, given enough computational resources, you can prove the correctness of any arbitrary number of proofs in a single proof. This could be useful to design state channels (for which a common example would be [Bitcoin's Lightning Network](https://en.wikipedia.org/wiki/Lightning_Network)), to save on gas costs by settling one proof on-chain, or simply to make business logic less dependent on a consensus mechanism. + +## Examples + +Let us look at some of these examples + +### Alice and Bob - Guessing game + +Alice and Bob are friends, and they like guessing games. They want to play a guessing game online, but for that, they need a trusted third-party that knows both of their secrets and finishes the game once someone wins. + +So, they use zero-knowledge proofs. Alice tries to guess Bob's number, and Bob will generate a ZK proof stating whether she succeeded or failed. + +This ZK proof can go on a smart contract, revealing the winner and even giving prizes. However, this means every turn needs to be verified on-chain. This incurs some cost and waiting time that may simply make the game too expensive or time-consuming to be worth it. + +As a solution, Alice proposes the following: "what if Bob generates his proof, and instead of sending it on-chain, I verify it *within* my own proof before playing my own turn?". + +She can then generate a proof that she verified his proof, and so on. + +```md + Did you fail? <-------------------------- + / \ / + / \ n = n -1 + / \ / + Yes No / + | | / + | | / + | You win / + | / + | / +Generate proof of that / + + / + my own guess ---------------- +``` + +### Charlie - Recursive merkle tree + +Charlie is a concerned citizen, and wants to be sure his vote in an election is accounted for. He votes with a ZK proof, but he has no way of knowing that his ZK proof was included in the total vote count! + +If the vote collector puts all of the votes into a [Merkle tree](https://en.wikipedia.org/wiki/Merkle_tree), everyone can prove the verification of two proofs within one proof, as such: + +```md + abcd + __________|______________ + | | + ab cd + _____|_____ ______|______ + | | | | + alice bob charlie daniel +``` + +Doing this recursively allows us to arrive on a final proof `abcd` which if true, verifies the correctness of all the votes. + +### Daniel - Reusable components + +Daniel has a big circuit and a big headache. A part of his circuit is a setup phase that finishes with some assertions that need to be made. But that section alone takes most of the proving time, and is largely independent of the rest of the circuit. + +He might find it more efficient to generate a proof for that setup phase separately, and verify that proof recursively in the actual business logic section of his circuit. This will allow for parallelization of both proofs, which results in a considerable speedup. + +## What params do I need + +As you can see in the [recursion reference](noir/standard_library/recursion.md), a simple recursive proof requires: + +- The proof to verify +- The Verification Key of the circuit that generated the proof +- A hash of this verification key, as it's needed for some backends +- The public inputs for the proof + +:::info + +Recursive zkSNARK schemes do not necessarily "verify a proof" in the sense that you expect a true or false to be spit out by the verifier. Rather an aggregation object is built over the public inputs. + +So, taking the example of Alice and Bob and their guessing game: + +- Alice makes her guess. Her proof is *not* recursive: it doesn't verify any proof within it! It's just a standard `assert(x != y)` circuit +- Bob verifies Alice's proof and makes his own guess. In this circuit, he doesn't exactly *prove* the verification of Alice's proof. Instead, he *aggregates* his proof to Alice's proof. The actual verification is done when the full proof is verified, for example when using `nargo verify` or through the verifier smart contract. + +We can imagine recursive proofs a [relay race](https://en.wikipedia.org/wiki/Relay_race). The first runner doesn't have to receive the baton from anyone else, as he/she already starts with it. But when his/her turn is over, the next runner needs to receive it, run a bit more, and pass it along. Even though every runner could theoretically verify the baton mid-run (why not? 🏃🔍), only at the end of the race does the referee verify that the whole race is valid. + +::: + +## Some architecture + +As with everything in computer science, there's no one-size-fits all. But there are some patterns that could help understanding and implementing them. To give three examples: + +### Adding some logic to a proof verification + +This would be an approach for something like our guessing game, where proofs are sent back and forth and are verified by each opponent. This circuit would be divided in two sections: + +- A `recursive verification` section, which would be just the call to `std::verify_proof`, and that would be skipped on the first move (since there's no proof to verify) +- A `guessing` section, which is basically the logic part where the actual guessing happens + +In such a situation, and assuming Alice is first, she would skip the first part and try to guess Bob's number. Bob would then verify her proof on the first section of his run, and try to guess Alice's number on the second part, and so on. + +### Aggregating proofs + +In some one-way interaction situations, recursion would allow for aggregation of simple proofs that don't need to be immediately verified on-chain or elsewhere. + +To give a practical example, a barman wouldn't need to verify a "proof-of-age" on-chain every time he serves alcohol to a customer. Instead, the architecture would comprise two circuits: + +- A `main`, non-recursive circuit with some logic +- A `recursive` circuit meant to verify two proofs in one proof + +The customer's proofs would be intermediate, and made on their phones, and the barman could just verify them locally. He would then aggregate them into a final proof sent on-chain (or elsewhere) at the end of the day. + +### Recursively verifying different circuits + +Nothing prevents you from verifying different circuits in a recursive proof, for example: + +- A `circuit1` circuit +- A `circuit2` circuit +- A `recursive` circuit + +In this example, a regulator could verify that taxes were paid for a specific purchase by aggregating both a `payer` circuit (proving that a purchase was made and taxes were paid), and a `receipt` circuit (proving that the payment was received) + +## How fast is it + +At the time of writing, verifying recursive proofs is surprisingly fast. This is because most of the time is spent on generating the verification key that will be used to generate the next proof. So you are able to cache the verification key and reuse it later. + +Currently, Noir JS packages don't expose the functionality of loading proving and verification keys, but that feature exists in the underlying `bb.js` package. + +## How can I try it + +Learn more about using recursion in Nargo and NoirJS in the [how-to guide](../how_to/how-to-recursion.md) and see a full example in [noir-examples](https://github.com/noir-lang/noir-examples). diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/explainers/explainer-writing-noir.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/explainers/explainer-writing-noir.md new file mode 100644 index 000000000000..c8a42c379e66 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/explainers/explainer-writing-noir.md @@ -0,0 +1,173 @@ +--- +title: Writing Performant Noir +description: Understand new considerations when writing Noir +keywords: [Noir, programming, rust] +tags: [Optimization] +sidebar_position: 0 +--- + + +This article intends to set you up with key concepts essential for writing more viable applications that use zero knowledge proofs, namely around efficient circuits. + +## Context - 'Efficient' is subjective + +When writing a web application for a performant computer with high-speed internet connection, writing efficient code sometimes is seen as an afterthought only if needed. Large multiplications running at the innermost of nested loops may not even be on a dev's radar. +When writing firmware for a battery-powered microcontroller, you think of cpu cycles as rations to keep within a product's power budget. + +> Code is written to create applications that perform specific tasks within specific constraints + +And these constraints differ depending on where the compiled code is execute. + +### The Ethereum Virtual Machine (EVM) + +In scenarios where extremely low gas costs are required for an Ethereum application to be viable/competitive, Ethereum smart contract developers get into what is colloquially known as: "*gas golfing*". Finding the lowest execution cost of their compiled code (EVM bytecode) to achieve a specific task. + +The equivalent optimization task when writing zk circuits is affectionately referred to as "*gate golfing*", finding the lowest gate representation of the compiled Noir code. + +### Coding for circuits - a paradigm shift + +In zero knowledge cryptography, code is compiled to "circuits" consisting of arithmetic gates, and gate count is the significant cost. Depending on the proving system this is linearly proportionate to proving time, and so from a product point this should be kept as low as possible. + +Whilst writing efficient code for web apps and Solidity has a few key differences, writing efficient circuits have a different set of considerations. It is a bit of a paradigm shift, like writing code for GPUs for the first time... + +For example, drawing a circle at (0, 0) of radius `r`: +- For a single CPU thread, +``` +for theta in 0..2*pi { + let x = r * cos(theta); + let y = r * sin(theta); + draw(x, y); +} // note: would do 0 - pi/2 and draw +ve/-ve x and y. +``` + +- For GPUs (simultaneous parallel calls with x, y across image), +``` +if (x^2 + y^2 = r^2) { + draw(x, y); +} +``` + +([Related](https://www.youtube.com/watch?v=-P28LKWTzrI)) + +Whilst this CPU -> GPU does not translate to circuits exactly, it is intended to exemplify the difference in intuition when coding for different machine capabilities/constraints. + +### Context Takeaway + +For those coming from a primarily web app background, this article will explain what you need to consider when writing circuits. Furthermore, for those experienced writing efficient machine code, prepare to shift what you think is efficient 😬 + +## Translating from Rust + +For some applications using Noir, existing code might be a convenient starting point to then proceed to optimize the gate count of. + +:::note +Many valuable functions and algorithms have been written in more established languages (C/C++), and converted to modern ones (like Rust). +::: + +Fortunately for Noir developers, when needing a particular function a Rust implementation can be readily compiled into Noir with some key changes. While the compiler does a decent amount of optimizations, it won't be able to change code that has been optimized for clock-cycles into code optimized for arithmetic gates. + +A few things to do when converting Rust code to Noir: +- `println!` is not a macro, use `println` function (same for `assert_eq`) +- No early `return` in function. Use constrain via assertion instead +- No passing by reference. Remove `&` operator to pass by value (copy) +- No boolean operators (`&&`, `||`). Use bitwise operators (`&`, `|`) with boolean values +- No type `usize`. Use types `u8`, `u32`, `u64`, ... +- `main` return must be public, `pub` +- No `const`, use `global` +- Noir's LSP is your friend, so error message should be informative enough to resolve syntax issues. + +## Writing efficient Noir for performant products + +The following points help refine our understanding over time. + +:::note +A Noir program makes a statement that can be verified. +::: + +It compiles to a structure that represents the calculation, and can assert results within the calculation at any stage (via the `constrain` keyword). + +A Noir program compiles to an Abstract Circuit Intermediate Representation which is: + - A tree structure + - Leaves (inputs) are the `Field` type + - Nodes contain arithmetic operations to combine them (gates) + - The root is the final result (return value) + +:::tip +The command `nargo info` shows the programs circuit size, and is useful to compare the value of changes made. +You can dig deeper and use the `--print-acir` param to take a closer look at individual ACIR opcodes, and the proving backend to see its gate count (eg for barretenberg, `bb gates -b ./target/program.json`). +::: + +### Use the `Field` type + +Since the native type of values in circuits are `Field`s, using them for variables in Noir means less gates converting them under the hood. + +:::tip +Where possible, use `Field` type for values. Using smaller value types, and bit-packing strategies, will result in MORE gates +::: + +**Note:** Need to remain mindful of overflow. Types with less bits may be used to limit the range of possible values prior to a calculation. + +### Use Arithmetic over non-arithmetic operations + +Since circuits are made of arithmetic gates, the cost of arithmetic operations tends to be one gate. Whereas for procedural code, they represent several clock cycles. + +Inversely, non-arithmetic operators are achieved with multiple gates, vs 1 clock cycle for procedural code. + +| (cost\op) | arithmetic
(`*`, `+`) | bit-wise ops
(eg `<`, `\|`, `>>`) | +| - | - | - | +| **cycles** | 10+ | 1 | +| **gates** | 1 | 10+ | + +Bit-wise operations (e.g. bit shifts `<<` and `>>`), albeit commonly used in general programming and especially for clock cycle optimizations, are on the contrary expensive in gates when performed within circuits. + +Translate away from bit shifts when writing constrained functions for the best performance. + +On the flip side, feel free to use bit shifts in unconstrained functions and tests if necessary, as they are executed outside of circuits and does not induce performance hits. + +### Use static over dynamic values + +Another general theme that manifests in different ways is that static reads are represented with less gates than dynamic ones. + +Reading from read-only memory (ROM) adds less gates than random-access memory (RAM), 2 vs ~3.25 due to the additional bounds checks. Arrays of fixed length (albeit used at a lower capacity), will generate less gates than dynamic storage. + +Related to this, if an index used to access an array is not known at compile time (ie unknown until run time), then ROM will be converted to RAM, expanding the gate count. + +:::tip +Use arrays and indices that are known at compile time where possible. +Using `assert_constant(i);` before an index, `i`, is used in an array will give a compile error if `i` is NOT known at compile time. +::: + +### Leverage unconstrained execution + +Constrained verification can leverage unconstrained execution, this is especially useful for operations that are represented by many gates. +Use an [unconstrained function](../noir/concepts/unconstrained.md) to perform gate-heavy calculations, then verify and constrain the result. + +Eg division generates more gates than multiplication, so calculating the quotient in an unconstrained function then constraining the product for the quotient and divisor (+ any remainder) equals the dividend will be more efficient. + +Use ` if is_unconstrained() { /`, to conditionally execute code if being called in an unconstrained vs constrained way. + +## Advanced + +Unless you're well into the depth of gate optimization, this advanced section can be ignored. + +### Combine arithmetic operations + +A Noir program can be honed further by combining arithmetic operators in a way that makes the most of each constraint of the backend proving system. This is in scenarios where the backend might not be doing this perfectly. + +Eg Barretenberg backend (current default for Noir) is a width-4 PLONKish constraint system +$ w_1*w_2*q_m + w_1*q_1 + w_2*q_2 + w_3*q_3 + w_4*q_4 + q_c = 0 $ + +Here we see there is one occurrence of witness 1 and 2 ($w_1$, $w_2$) being multiplied together, with addition to witnesses 1-4 ($w_1$ .. $w_4$) multiplied by 4 corresponding circuit constants ($q_1$ .. $q_4$) (plus a final circuit constant, $q_c$). + +Use `nargo info --print-acir`, to inspect the ACIR opcodes (and the proving system for gates), and it may present opportunities to amend the order of operations and reduce the number of constraints. + +#### Variable as witness vs expression + +If you've come this far and really know what you're doing at the equation level, a temporary lever (that will become unnecessary/useless over time) is: `std::as_witness`. This informs the compiler to save a variable as a witness not an expression. + +The compiler will mostly be correct and optimal, but this may help some near term edge cases that are yet to optimize. +Note: When used incorrectly it will create **less** efficient circuits (higher gate count). + +## References +- Guillaume's ["`Cryptdoku`" talk](https://www.youtube.com/watch?v=MrQyzuogxgg) (Jun'23) +- Tips from Tom, Jake and Zac. +- [Idiomatic Noir](https://www.vlayer.xyz/blog/idiomatic-noir-part-1-collections) blog post diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/_category_.json b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/_category_.json new file mode 100644 index 000000000000..5d694210bbf3 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/_category_.json @@ -0,0 +1,5 @@ +{ + "position": 0, + "collapsible": true, + "collapsed": true +} diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/backend/_category_.json b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/backend/_category_.json new file mode 100644 index 000000000000..b82e92beb0ca --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/backend/_category_.json @@ -0,0 +1,6 @@ +{ + "position": 1, + "label": "Install Proving Backend", + "collapsible": true, + "collapsed": true +} diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/backend/index.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/backend/index.md new file mode 100644 index 000000000000..7192d9548774 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/backend/index.md @@ -0,0 +1,31 @@ +--- +title: Proving Backend Installation +description: Proving backends offer command line tools for proving and verifying Noir programs. This page describes how to install `bb` as an example. +keywords: [ + Proving + Backend + Barretenberg + bb + bbup + Installation + Terminal + Command + CLI + Version +] +pagination_next: getting_started/hello_noir/index +--- + +Proving backends each provide their own tools for working with Noir programs, providing functionality like proof generation, proof verification, and verifier smart contract generation. + +For the latest information on tooling provided by each proving backend, installation instructions, Noir version compatibility... you may refer to the proving backends' own documentation. + +You can find the full list of proving backends compatible with Noir in [Awesome Noir](https://github.com/noir-lang/awesome-noir/?tab=readme-ov-file#proving-backends). + +## Example: Installing `bb` + +`bb` is the CLI tool provided by the [Barretenberg proving backend](https://github.com/AztecProtocol/barretenberg) developed by Aztec Labs. + +You can find the instructions for installation in [`bb`'s documentation](https://github.com/AztecProtocol/aztec-packages/blob/master/barretenberg/cpp/src/barretenberg/bb/readme.md#installation). + +Once installed, we are ready to start working on [our first Noir program](../hello_noir/index.md). diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/hello_noir/_category_.json b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/hello_noir/_category_.json new file mode 100644 index 000000000000..976a2325de05 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/hello_noir/_category_.json @@ -0,0 +1,5 @@ +{ + "position": 2, + "collapsible": true, + "collapsed": true +} diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/hello_noir/index.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/hello_noir/index.md new file mode 100644 index 000000000000..3baae217eb30 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/hello_noir/index.md @@ -0,0 +1,152 @@ +--- +title: Creating a Project +description: + Learn how to create and verify your first Noir program using Nargo, a programming language for + zero-knowledge proofs. +keywords: + [ + Nargo, + Noir, + zero-knowledge proofs, + programming language, + create Noir program, + verify Noir program, + step-by-step guide, + ] +sidebar_position: 1 + +--- + +Now that we have installed Nargo and a proving backend, it is time to make our first hello world program! + +### 1. Create a new project directory + +Noir code can live anywhere on your computer. Let us create a _projects_ folder in the home +directory to house our first Noir program. + +Create the directory and change directory into it by running: + +```sh +mkdir ~/projects +cd ~/projects +``` + +## Nargo + +Nargo provides the ability to initiate and execute Noir projects. Read the [Nargo installation](../installation/index.md) section to learn more about Nargo and how to install it. + +### 2. Create a new Noir project + +Now that we are in the projects directory, create a new Nargo project by running: + +```sh +nargo new hello_world +``` + +`hello_world` can be any arbitrary project name, we are simply using `hello_world` for demonstration. + +In production, it is common practice to name the project folder, `circuits`, for clarity amongst other folders in the codebase (like: `contracts`, `scripts`, and `test`). + +A `hello_world` folder would be created. Similar to Rust, the folder houses _src/main.nr_ and +_Nargo.toml_ which contain the source code and environmental options of your Noir program +respectively. + +#### Intro to Noir Syntax + +Let us take a closer look at _main.nr_. The default _main.nr_ generated should look like this: + +```rust +fn main(x : Field, y : pub Field) { + assert(x != y); +} +``` + +The first line of the program specifies the program's inputs: + +```rust +x : Field, y : pub Field +``` + +Program inputs in Noir are private by default (e.g. `x`), but can be labeled public using the +keyword `pub` (e.g. `y`). To learn more about private and public values, check the +[Data Types](../../noir/concepts/data_types/index.md) section. + +The next line of the program specifies its body: + +```rust +assert(x != y); +``` + +The Noir syntax `assert` can be interpreted as something similar to constraints in other zk-contract languages. + +For more Noir syntax, check the [Language Concepts](../../noir/concepts/comments.md) chapter. + +### 3. Build in/output files + +Change directory into _hello_world_ and build in/output files for your Noir program by running: + +```sh +cd hello_world +nargo check +``` + +A _Prover.toml_ file will be generated in your project directory, to allow specifying input values to the program. + +### 4. Execute the Noir program + +Now that the project is set up, we can execute our Noir program. + +Fill in input values for execution in the _Prover.toml_ file. For example: + +```toml +x = "1" +y = "2" +``` + +Execute your Noir program: + +```sh +nargo execute witness-name +``` + +The witness corresponding to this execution will then be written to the file `./target/witness-name.gz`. + +The command also automatically compiles your Noir program if it was not already / was edited, which you may notice the compiled artifacts being written to the file `./target/hello_world.json`. + +## Proving Backend + +Proving backends provide the ability to generate and verify proofs of executing Noir programs, following Noir's tooling that compiles and executes the programs. Read the [proving backend installation](../backend/index.md) section to learn more about proving backends and how to install them. + +Barretenberg is used as an example here to demonstrate how proving and verifying could be implemented and used. Read the [`bb` installation](../backend/index.md#example-installing-bb) section for how to install Barretenberg's CLI tool; refer to [`bb`'s documentation](https://github.com/AztecProtocol/aztec-packages/blob/master/barretenberg/cpp/src/barretenberg/bb/readme.md) for full details about the tool. + +### 5. Prove an execution of the Noir program + +Using Barretenberg as an example, prove the valid execution of your Noir program running: + +```sh +bb prove -b ./target/hello_world.json -w ./target/witness-name.gz -o ./target/proof +``` + +The proof generated will then be written to the file `./target/proof`. + +### 6. Verify the execution proof + +Once a proof is generated, we can verify correct execution of our Noir program by verifying the proof file. + +Using Barretenberg as an example, compute the verification key for the Noir program by running: + +```sh +bb write_vk -b ./target/hello_world.json -o ./target/vk +``` + +And verify your proof by running: + +```sh +bb verify -k ./target/vk -p ./target/proof +``` + +If successful, the verification will complete in silence; if unsuccessful, the command will trigger logging of the corresponding error. + +Congratulations, you have now created and verified a proof for your very first Noir program! + +In the [next section](./project_breakdown.md), we will go into more detail on each step performed. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/hello_noir/project_breakdown.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/hello_noir/project_breakdown.md new file mode 100644 index 000000000000..96e653f6c08d --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/hello_noir/project_breakdown.md @@ -0,0 +1,159 @@ +--- +title: Project Breakdown +description: + Learn about the anatomy of a Nargo project, including the purpose of the Prover TOML + file, and how to prove and verify your program. +keywords: + [Nargo, Nargo project, Prover.toml, proof verification, private asset transfer] +sidebar_position: 2 +--- + +This section breaks down our hello world program from the previous section. + +## Anatomy of a Nargo Project + +Upon creating a new project with `nargo new` and building the in/output files with `nargo check` +commands, you would get a minimal Nargo project of the following structure: + + - src + - Prover.toml + - Nargo.toml + +The source directory _src_ holds the source code for your Noir program. By default only a _main.nr_ +file will be generated within it. + +### Prover.toml + +_Prover.toml_ is used for specifying the input values for executing and proving the program. You can specify `toml` files with different names by using the `--prover-name` or `-p` flags, see the [Prover](#provertoml) section below. Optionally you may specify expected output values for prove-time checking as well. + +### Nargo.toml + +_Nargo.toml_ contains the environmental options of your project. It contains a "package" section and a "dependencies" section. + +Example Nargo.toml: + +```toml +[package] +name = "noir_starter" +type = "bin" +authors = ["Alice"] +compiler_version = "0.9.0" +description = "Getting started with Noir" +entry = "circuit/main.nr" +license = "MIT" + +[dependencies] +ecrecover = {tag = "v0.9.0", git = "https://github.com/colinnielsen/ecrecover-noir.git"} +``` + +Nargo.toml for a [workspace](../../noir/modules_packages_crates/workspaces.md) will look a bit different. For example: + +```toml +[workspace] +members = ["crates/a", "crates/b"] +default-member = "crates/a" +``` + +#### Package section + +The package section defines a number of fields including: + +- `name` (**required**) - the name of the package +- `type` (**required**) - can be "bin", "lib", or "contract" to specify whether its a binary, library or Aztec contract +- `authors` (optional) - authors of the project +- `compiler_version` - specifies the version of the compiler to use. This is enforced by the compiler and follow's [Rust's versioning](https://doc.rust-lang.org/cargo/reference/manifest.html#the-version-field), so a `compiler_version = 0.18.0` will enforce Nargo version 0.18.0, `compiler_version = ^0.18.0` will enforce anything above 0.18.0 but below 0.19.0, etc. For more information, see how [Rust handles these operators](https://docs.rs/semver/latest/semver/enum.Op.html) +- `description` (optional) +- `entry` (optional) - a relative filepath to use as the entry point into your package (overrides the default of `src/lib.nr` or `src/main.nr`) +- `backend` (optional) +- `license` (optional) +- `expression_width` (optional) - Sets the default backend expression width. This field will override the default backend expression width specified by the Noir compiler (currently set to width 4). + +#### Dependencies section + +This is where you will specify any dependencies for your project. See the [Dependencies page](../../noir/modules_packages_crates/dependencies.md) for more info. + +`./proofs/` and `./contract/` directories will not be immediately visible until you create a proof or +verifier contract respectively. + +### main.nr + +The _main.nr_ file contains a `main` method, this method is the entry point into your Noir program. + +In our sample program, _main.nr_ looks like this: + +```rust +fn main(x : Field, y : Field) { + assert(x != y); +} +``` + +The parameters `x` and `y` can be seen as the API for the program and must be supplied by the prover. Since neither `x` nor `y` is marked as public, the verifier does not supply any inputs, when verifying the proof. + +The prover supplies the values for `x` and `y` in the _Prover.toml_ file. + +As for the program body, `assert` ensures that the condition to be satisfied (e.g. `x != y`) is constrained by the proof of the execution of said program (i.e. if the condition was not met, the verifier would reject the proof as an invalid proof). + +### Prover.toml + +The _Prover.toml_ file is a file which the prover uses to supply the inputs to the Noir program (both private and public). + +In our hello world program the _Prover.toml_ file looks like this: + +```toml +x = "1" +y = "2" +``` + +When the command `nargo execute` is executed, nargo will execute the Noir program using the inputs specified in `Prover.toml`, aborting if it finds that these do not satisfy the constraints defined by `main`. In this example, `x` and `y` must satisfy the inequality constraint `assert(x != y)`. + +If an output name is specified such as `nargo execute foo`, the witness generated by this execution will be written to `./target/foo.gz`. This can then be used to generate a proof of the execution. + +#### Arrays of Structs + +The following code shows how to pass an array of structs to a Noir program to generate a proof. + +```rust +// main.nr +struct Foo { + bar: Field, + baz: Field, +} + +fn main(foos: [Foo; 3]) -> pub Field { + foos[2].bar + foos[2].baz +} +``` + +Prover.toml: + +```toml +[[foos]] # foos[0] +bar = 0 +baz = 0 + +[[foos]] # foos[1] +bar = 0 +baz = 0 + +[[foos]] # foos[2] +bar = 1 +baz = 2 +``` + +#### Custom toml files + +You can specify a `toml` file with a different name to use for execution by using the `--prover-name` or `-p` flags. + +This command looks for proof inputs in the default **Prover.toml** and generates the witness and saves it at `./target/foo.gz`: + +```bash +nargo execute foo +``` + +This command looks for proof inputs in the custom **OtherProver.toml** and generates the witness and saves it at `./target/bar.gz`: + +```bash +nargo execute -p OtherProver bar +``` + +Now that you understand the concepts, you'll probably want some editor feedback while you are writing more complex code. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/installation/_category_.json b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/installation/_category_.json new file mode 100644 index 000000000000..0c02fb5d4d79 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/installation/_category_.json @@ -0,0 +1,6 @@ +{ + "position": 0, + "label": "Install Nargo", + "collapsible": true, + "collapsed": true +} diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/installation/index.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/installation/index.md new file mode 100644 index 000000000000..53ea9c7891ca --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/installation/index.md @@ -0,0 +1,46 @@ +--- +title: Nargo Installation +description: + nargo is a command line tool for interacting with Noir programs. This page is a quick guide on how to install Nargo through the most common and easy method, noirup +keywords: [ + Nargo + Noir + Rust + Cargo + Noirup + Installation + Terminal Commands + Version Check + Nightlies + Specific Versions + Branches + Noirup Repository +] +pagination_next: getting_started/hello_noir/index +--- + +`nargo` is a tool for working with Noir programs on the CLI, providing you with the ability to start new projects, compile, execute and test Noir programs from the terminal. + +The name is inspired by Rust's package manager `cargo`; and similar to Rust's `rustup`, Noir also has an easy installation script `noirup`. + +## Installing Noirup + +Open a terminal on your machine, and write: + +```bash +curl -L https://raw.githubusercontent.com/noir-lang/noirup/main/install | bash +``` + +Close the terminal, open another one, and run + +```bash +noirup +``` + +Done. That's it. You should have the latest version working. You can check with `nargo --version`. + +You can also install nightlies, specific versions +or branches. Check out the [noirup repository](https://github.com/noir-lang/noirup) for more +information. + +Now we're ready to start working on [our first Noir program!](../hello_noir/index.md) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/installation/other_install_methods.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/installation/other_install_methods.md new file mode 100644 index 000000000000..3634723562bf --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/getting_started/installation/other_install_methods.md @@ -0,0 +1,102 @@ +--- +title: Alternative Installations +description: There are different ways to install Nargo, the one-stop shop and command-line tool for developing Noir programs. This guide explains how to specify which version to install when using noirup, and using WSL for windows. +keywords: [ + Installation + Nargo + Noirup + Binaries + Compiling from Source + WSL for Windows + macOS + Linux + Nix + Direnv + Uninstalling Nargo + ] +sidebar_position: 1 +--- + +## Encouraged Installation Method: Noirup + +Noirup is the endorsed method for installing Nargo, streamlining the process of fetching binaries or compiling from source. It supports a range of options to cater to your specific needs, from nightly builds and specific versions to compiling from various sources. + +### Installing Noirup + +First, ensure you have `noirup` installed: + +```sh +curl -L https://raw.githubusercontent.com/noir-lang/noirup/main/install | bash +``` + +### Fetching Binaries + +With `noirup`, you can easily switch between different Nargo versions, including nightly builds: + +- **Nightly Version**: Install the latest nightly build. + + ```sh + noirup --version nightly + ``` + +- **Specific Version**: Install a specific version of Nargo. + ```sh + noirup --version + ``` + +### Compiling from Source + +`noirup` also enables compiling Nargo from various sources: + +- **From a Specific Branch**: Install from the latest commit on a branch. + + ```sh + noirup --branch + ``` + +- **From a Fork**: Install from the main branch of a fork. + + ```sh + noirup --repo + ``` + +- **From a Specific Branch in a Fork**: Install from a specific branch in a fork. + + ```sh + noirup --repo --branch + ``` + +- **From a Specific Pull Request**: Install from a specific PR. + + ```sh + noirup --pr + ``` + +- **From a Specific Commit**: Install from a specific commit. + + ```sh + noirup -C + ``` + +- **From Local Source**: Compile and install from a local directory. + ```sh + noirup --path ./path/to/local/source + ``` + +## Installation on Windows + +The default backend for Noir (Barretenberg) doesn't provide Windows binaries at this time. For that reason, Noir cannot be installed natively. However, it is available by using Windows Subsystem for Linux (WSL). + +Step 1: Follow the instructions [here](https://learn.microsoft.com/en-us/windows/wsl/install) to install and run WSL. + +step 2: Follow the [Noirup instructions](#encouraged-installation-method-noirup). + +## Uninstalling Nargo + +If you installed Nargo with `noirup`, you can uninstall Nargo by removing the files in `~/.nargo`, `~/nargo`, and `~/noir_cache`. This ensures that all installed binaries, configurations, and cache related to Nargo are fully removed from your system. + +```bash +rm -r ~/.nargo +rm -r ~/nargo +rm -r ~/noir_cache +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.31.0/getting_started/barretenberg/_category_.json b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/_category_.json similarity index 65% rename from noir/noir-repo/docs/versioned_docs/version-v0.31.0/getting_started/barretenberg/_category_.json rename to noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/_category_.json index 27a8e89228de..23b560f610b8 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.31.0/getting_started/barretenberg/_category_.json +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/_category_.json @@ -1,6 +1,5 @@ { "position": 1, - "label": "Install Barretenberg", "collapsible": true, "collapsed": true } diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/debugger/_category_.json b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/debugger/_category_.json new file mode 100644 index 000000000000..cc2cbb1c2533 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/debugger/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Debugging", + "position": 5, + "collapsible": true, + "collapsed": true +} diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/debugger/debugging_with_the_repl.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/debugger/debugging_with_the_repl.md new file mode 100644 index 000000000000..09e5bae68ad7 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/debugger/debugging_with_the_repl.md @@ -0,0 +1,164 @@ +--- +title: Using the REPL Debugger +description: + Step by step guide on how to debug your Noir circuits with the REPL Debugger. +keywords: + [ + Nargo, + Noir CLI, + Noir Debugger, + REPL, + ] +sidebar_position: 1 +--- + +#### Pre-requisites + +In order to use the REPL debugger, first you need to install recent enough versions of Nargo and vscode-noir. + +## Debugging a simple circuit + +Let's debug a simple circuit: + +```rust +fn main(x : Field, y : pub Field) { + assert(x != y); +} +``` + +To start the REPL debugger, using a terminal, go to a Noir circuit's home directory. Then: + +`$ nargo debug` + +You should be seeing this in your terminal: + +``` +[main] Starting debugger +At ~/noir-examples/recursion/circuits/main/src/main.nr:1:9 + 1 -> fn main(x : Field, y : pub Field) { + 2 assert(x != y); + 3 } +> +``` + +The debugger displays the current Noir code location, and it is now waiting for us to drive it. + +Let's first take a look at the available commands. For that we'll use the `help` command. + +``` +> help +Available commands: + + opcodes display ACIR opcodes + into step into to the next opcode + next step until a new source location is reached + out step until a new source location is reached + and the current stack frame is finished + break LOCATION:OpcodeLocation add a breakpoint at an opcode location + over step until a new source location is reached + without diving into function calls + restart restart the debugging session + delete LOCATION:OpcodeLocation delete breakpoint at an opcode location + witness show witness map + witness index:u32 display a single witness from the witness map + witness index:u32 value:String update a witness with the given value + memset index:usize value:String update a memory cell with the given + value + continue continue execution until the end of the + program + vars show variable values available at this point + in execution + stacktrace display the current stack trace + memory show memory (valid when executing unconstrained code) + step step to the next ACIR opcode + +Other commands: + + help Show this help message + quit Quit repl + +``` + +Some commands operate only for unconstrained functions, such as `memory` and `memset`. If you try to use them while execution is paused at an ACIR opcode, the debugger will simply inform you that you are not executing unconstrained code: + +``` +> memory +Unconstrained VM memory not available +> +``` + +Before continuing, we can take a look at the initial witness map: + +``` +> witness +_0 = 1 +_1 = 2 +> +``` + +Cool, since `x==1`, `y==2`, and we want to check that `x != y`, our circuit should succeed. At this point we could intervene and use the witness setter command to change one of the witnesses. Let's set `y=3`, then back to 2, so we don't affect the expected result: + +``` +> witness +_0 = 1 +_1 = 2 +> witness 1 3 +_1 = 3 +> witness +_0 = 1 +_1 = 3 +> witness 1 2 +_1 = 2 +> witness +_0 = 1 +_1 = 2 +> +``` + +Now we can inspect the current state of local variables. For that we use the `vars` command. + +``` +> vars +> +``` + +We currently have no vars in context, since we are at the entry point of the program. Let's use `next` to execute until the next point in the program. + +``` +> vars +> next +At ~/noir-examples/recursion/circuits/main/src/main.nr:1:20 + 1 -> fn main(x : Field, y : pub Field) { + 2 assert(x != y); + 3 } +> vars +x:Field = 0x01 +``` + +As a result of stepping, the variable `x`, whose initial value comes from the witness map, is now in context and returned by `vars`. + +``` +> next + 1 fn main(x : Field, y : pub Field) { + 2 -> assert(x != y); + 3 } +> vars +y:Field = 0x02 +x:Field = 0x01 +``` + +Stepping again we can finally see both variables and their values. And now we can see that the next assertion should succeed. + +Let's continue to the end: + +``` +> continue +(Continuing execution...) +Finished execution +> q +[main] Circuit witness successfully solved +``` + +Upon quitting the debugger after a solved circuit, the resulting circuit witness gets saved, equivalent to what would happen if we had run the same circuit with `nargo execute`. + +We just went through the basics of debugging using Noir REPL debugger. For a comprehensive reference, check out [the reference page](../../reference/debugger/debugger_repl.md). diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/debugger/debugging_with_vs_code.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/debugger/debugging_with_vs_code.md new file mode 100644 index 000000000000..a5858c1a5eb5 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/debugger/debugging_with_vs_code.md @@ -0,0 +1,68 @@ +--- +title: Using the VS Code Debugger +description: + Step by step guide on how to debug your Noir circuits with the VS Code Debugger configuration and features. +keywords: + [ + Nargo, + Noir CLI, + Noir Debugger, + VS Code, + IDE, + ] +sidebar_position: 0 +--- + +This guide will show you how to use VS Code with the vscode-noir extension to debug a Noir project. + +#### Pre-requisites + +- Nargo +- vscode-noir +- A Noir project with a `Nargo.toml`, `Prover.toml` and at least one Noir (`.nr`) containing an entry point function (typically `main`). + +## Running the debugger + +The easiest way to start debugging is to open the file you want to debug, and press `F5`. This will cause the debugger to launch, using your `Prover.toml` file as input. + +You should see something like this: + +![Debugger launched](@site/static/img/debugger/1-started.png) + +Let's inspect the state of the program. For that, we open VS Code's _Debug pane_. Look for this icon: + +![Debug pane icon](@site/static/img/debugger/2-icon.png) + +You will now see two categories of variables: Locals and Witness Map. + +![Debug pane expanded](@site/static/img/debugger/3-debug-pane.png) + +1. **Locals**: variables of your program. At this point in execution this section is empty, but as we step through the code it will get populated by `x`, `result`, `digest`, etc. + +2. **Witness map**: these are initially populated from your project's `Prover.toml` file. In this example, they will be used to populate `x` and `result` at the beginning of the `main` function. + +Most of the time you will probably be focusing mostly on locals, as they represent the high level state of your program. + +You might be interested in inspecting the witness map in case you are trying to solve a really low level issue in the compiler or runtime itself, so this concerns mostly advanced or niche users. + +Let's step through the program, by using the debugger buttons or their corresponding keyboard shortcuts. + +![Debugger buttons](@site/static/img/debugger/4-debugger-buttons.png) + +Now we can see in the variables pane that there's values for `digest`, `result` and `x`. + +![Inspecting locals](@site/static/img/debugger/5-assert.png) + +We can also inspect the values of variables by directly hovering on them on the code. + +![Hover locals](@site/static/img/debugger/6-hover.png) + +Let's set a break point at the `keccak256` function, so we can continue execution up to the point when it's first invoked without having to go one step at a time. + +We just need to click the to the right of the line number 18. Once the breakpoint appears, we can click the `continue` button or use its corresponding keyboard shortcut (`F5` by default). + +![Breakpoint](@site/static/img/debugger/7-break.png) + +Now we are debugging the `keccak256` function, notice the _Call Stack pane_ at the lower right. This lets us inspect the current call stack of our process. + +That covers most of the current debugger functionalities. Check out [the reference](../../reference/debugger/debugger_vscode.md) for more details on how to configure the debugger. \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/how-to-oracles.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/how-to-oracles.md new file mode 100644 index 000000000000..2f69902062c7 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/how-to-oracles.md @@ -0,0 +1,273 @@ +--- +title: How to use Oracles +description: Learn how to use oracles in your Noir program with examples in both Nargo and NoirJS. This guide also covers writing a JSON RPC server and providing custom foreign call handlers for NoirJS. +keywords: + - Noir Programming + - Oracles + - Nargo + - NoirJS + - JSON RPC Server + - Foreign Call Handlers +sidebar_position: 1 +--- + +This guide shows you how to use oracles in your Noir program. For the sake of clarity, it assumes that: + +- You have read the [explainer on Oracles](../explainers/explainer-oracle.md) and are comfortable with the concept. +- You have a Noir program to add oracles to. You can create one using the [vite-hardhat starter](https://github.com/noir-lang/noir-starter/tree/main/vite-hardhat) as a boilerplate. +- You understand the concept of a JSON-RPC server. Visit the [JSON-RPC website](https://www.jsonrpc.org/) if you need a refresher. +- You are comfortable with server-side JavaScript (e.g. Node.js, managing packages, etc.). + +For reference, you can find the snippets used in this tutorial on the [Aztec DevRel Repository](https://github.com/AztecProtocol/dev-rel/tree/main/code-snippets/how-to-oracles). + +## Rundown + +This guide has 3 major steps: + +1. How to modify our Noir program to make use of oracle calls as unconstrained functions +2. How to write a JSON RPC Server to resolve these oracle calls with Nargo +3. How to use them in Nargo and how to provide a custom resolver in NoirJS + +## Step 1 - Modify your Noir program + +An oracle is defined in a Noir program by defining two methods: + +- An unconstrained method - This tells the compiler that it is executing an [unconstrained functions](../noir/concepts//unconstrained.md). +- A decorated oracle method - This tells the compiler that this method is an RPC call. + +An example of an oracle that returns a `Field` would be: + +```rust +#[oracle(getSqrt)] +unconstrained fn sqrt(number: Field) -> Field { } + +unconstrained fn get_sqrt(number: Field) -> Field { + sqrt(number) +} +``` + +In this example, we're wrapping our oracle function in an unconstrained method, and decorating it with `oracle(getSqrt)`. We can then call the unconstrained function as we would call any other function: + +```rust +fn main(input: Field) { + let sqrt = get_sqrt(input); +} +``` + +In the next section, we will make this `getSqrt` (defined on the `sqrt` decorator) be a method of the RPC server Noir will use. + +:::danger + +As explained in the [Oracle Explainer](../explainers/explainer-oracle.md), this `main` function is unsafe unless you constrain its return value. For example: + +```rust +fn main(input: Field) { + let sqrt = get_sqrt(input); + assert(sqrt.pow_32(2) as u64 == input as u64); // <---- constrain the return of an oracle! +} +``` + +::: + +:::info + +Currently, oracles only work with single params or array params. For example: + +```rust +#[oracle(getSqrt)] +unconstrained fn sqrt([Field; 2]) -> [Field; 2] { } +``` + +::: + +## Step 2 - Write an RPC server + +Brillig will call *one* RPC server. Most likely you will have to write your own, and you can do it in whatever language you prefer. In this guide, we will do it in Javascript. + +Let's use the above example of an oracle that consumes an array with two `Field` and returns their square roots: + +```rust +#[oracle(getSqrt)] +unconstrained fn sqrt(input: [Field; 2]) -> [Field; 2] { } + +unconstrained fn get_sqrt(input: [Field; 2]) -> [Field; 2] { + sqrt(input) +} + +fn main(input: [Field; 2]) { + let sqrt = get_sqrt(input); + assert(sqrt[0].pow_32(2) as u64 == input[0] as u64); + assert(sqrt[1].pow_32(2) as u64 == input[1] as u64); +} +``` + +:::info + +Why square root? + +In general, computing square roots is computationally more expensive than multiplications, which takes a toll when speaking about ZK applications. In this case, instead of calculating the square root in Noir, we are using our oracle to offload that computation to be made in plain. In our circuit we can simply multiply the two values. + +::: + +Now, we should write the correspondent RPC server, starting with the [default JSON-RPC 2.0 boilerplate](https://www.npmjs.com/package/json-rpc-2.0#example): + +```js +import { JSONRPCServer } from "json-rpc-2.0"; +import express from "express"; +import bodyParser from "body-parser"; + +const app = express(); +app.use(bodyParser.json()); + +const server = new JSONRPCServer(); +app.post("/", (req, res) => { + const jsonRPCRequest = req.body; + server.receive(jsonRPCRequest).then((jsonRPCResponse) => { + if (jsonRPCResponse) { + res.json(jsonRPCResponse); + } else { + res.sendStatus(204); + } + }); +}); + +app.listen(5555); +``` + +Now, we will add our `getSqrt` method, as expected by the `#[oracle(getSqrt)]` decorator in our Noir code. It maps through the params array and returns their square roots: + +```js +server.addMethod("resolve_function_call", async (params) => { + if params.function !== "getSqrt" { + throw Error("Unexpected foreign call") + }; + const values = params.inputs[0].Array.map((field) => { + return `${Math.sqrt(parseInt(field, 16))}`; + }); + return { values: [{ Array: values }] }; +}); +``` + +If you're using Typescript, the following types may be helpful in understanding the expected return value and making sure they're easy to follow: + +```js +interface SingleForeignCallParam { + Single: string, +} + +interface ArrayForeignCallParam { + Array: string[], +} + +type ForeignCallParam = SingleForeignCallParam | ArrayForeignCallParam; + +interface ForeignCallResult { + values: ForeignCallParam[], +} +``` + +::: Multidimensional Arrays + +If the Oracle function is returning an array containing other arrays, such as `[['1','2],['3','4']]`, you need to provide the values in json as flattened values. In the previous example, it would be `['1', '2', '3', '4']`. In the noir program, the Oracle signature can use a nested type, the flattened values will be automatically converted to the nested type. + +::: + +## Step 3 - Usage with Nargo + +Using the [`nargo` CLI tool](../getting_started/installation/index.md), you can use oracles in the `nargo test` and `nargo execute` commands by passing a value to `--oracle-resolver`. For example: + +```bash +nargo test --oracle-resolver http://localhost:5555 +``` + +This tells `nargo` to use your RPC Server URL whenever it finds an oracle decorator. + +## Step 4 - Usage with NoirJS + +In a JS environment, an RPC server is not strictly necessary, as you may want to resolve your oracles without needing any JSON call at all. NoirJS simply expects that you pass a callback function when you generate proofs, and that callback function can be anything. + +For example, if your Noir program expects the host machine to provide CPU pseudo-randomness, you could simply pass it as the `foreignCallHandler`. You don't strictly need to create an RPC server to serve pseudo-randomness, as you may as well get it directly in your app: + +```js +const foreignCallHandler = (name, inputs) => crypto.randomBytes(16) // etc + +await noir.execute(inputs, foreignCallHandler) +``` + +As one can see, in NoirJS, the [`foreignCallHandler`](../reference/NoirJS/noir_js/type-aliases/ForeignCallHandler.md) function simply means "a callback function that returns a value of type [`ForeignCallOutput`](../reference/NoirJS/noir_js/type-aliases/ForeignCallOutput.md). It doesn't have to be an RPC call like in the case for Nargo. + +:::tip + +Does this mean you don't have to write an RPC server like in [Step #2](#step-2---write-an-rpc-server)? + +You don't technically have to, but then how would you run `nargo test`? To use both `Nargo` and `NoirJS` in your development flow, you will have to write a JSON RPC server. + +::: + +In this case, let's make `foreignCallHandler` call the JSON RPC Server we created in [Step #2](#step-2---write-an-rpc-server), by making it a JSON RPC Client. + +For example, using the same `getSqrt` program in [Step #1](#step-1---modify-your-noir-program) (comments in the code): + +```js +import { JSONRPCClient } from "json-rpc-2.0"; + +// declaring the JSONRPCClient +const client = new JSONRPCClient((jsonRPCRequest) => { +// hitting the same JSON RPC Server we coded above + return fetch("http://localhost:5555", { + method: "POST", + headers: { + "content-type": "application/json", + }, + body: JSON.stringify(jsonRPCRequest), + }).then((response) => { + if (response.status === 200) { + return response + .json() + .then((jsonRPCResponse) => client.receive(jsonRPCResponse)); + } else if (jsonRPCRequest.id !== undefined) { + return Promise.reject(new Error(response.statusText)); + } + }); +}); + +// declaring a function that takes the name of the foreign call (getSqrt) and the inputs +const foreignCallHandler = async (name, input) => { + // notice that the "inputs" parameter contains *all* the inputs + // in this case we make the RPC request with the first parameter "numbers", which would be input[0] + const oracleReturn = await client.request(name, [ + input[0].map((i) => i.toString("hex")), + ]); + return { values: oracleReturn }; +}; + +// the rest of your NoirJS code +const input = { input: [4, 16] }; +const { witness } = await noir.execute(numbers, foreignCallHandler); +``` + +:::tip + +If you're in a NoirJS environment running your RPC server together with a frontend app, you'll probably hit a familiar problem in full-stack development: requests being blocked by [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) policy. For development only, you can simply install and use the [`cors` npm package](https://www.npmjs.com/package/cors) to get around the problem: + +```bash +yarn add cors +``` + +and use it as a middleware: + +```js +import cors from "cors"; + +const app = express(); +app.use(cors()) +``` + +::: + +## Conclusion + +Hopefully by the end of this guide, you should be able to: + +- Write your own logic around Oracles and how to write a JSON RPC server to make them work with your Nargo commands. +- Provide custom foreign call handlers for NoirJS. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/how-to-recursion.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/how-to-recursion.md new file mode 100644 index 000000000000..71f02fa54353 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/how-to-recursion.md @@ -0,0 +1,180 @@ +--- +title: How to use recursion on NoirJS +description: Learn how to implement recursion with NoirJS, a powerful tool for creating smart contracts on the EVM blockchain. This guide assumes familiarity with NoirJS, solidity verifiers, and the Barretenberg proving backend. Discover how to generate both final and intermediate proofs using `noir_js` and `backend_barretenberg`. +keywords: + [ + "NoirJS", + "EVM blockchain", + "smart contracts", + "recursion", + "solidity verifiers", + "Barretenberg backend", + "noir_js", + "backend_barretenberg", + "intermediate proofs", + "final proofs", + "nargo compile", + "json import", + "recursive circuit", + "recursive app" + ] +sidebar_position: 1 +--- + +This guide shows you how to use recursive proofs in your NoirJS app. For the sake of clarity, it is assumed that: + +- You already have a NoirJS app. If you don't, please visit the [NoirJS tutorial](../tutorials/noirjs_app.md) and the [reference](../reference/NoirJS/noir_js/index.md). +- You are familiar with what are recursive proofs and you have read the [recursion explainer](../explainers/explainer-recursion.md) +- You already built a recursive circuit following [the reference](../noir/standard_library/recursion.md), and understand how it works. + +It is also assumed that you're not using `noir_wasm` for compilation, and instead you've used [`nargo compile`](../reference/nargo_commands.md) to generate the `json` you're now importing into your project. However, the guide should work just the same if you're using `noir_wasm`. + +:::info + +As you've read in the [explainer](../explainers/explainer-recursion.md), a recursive proof is an intermediate proof. This means that it doesn't necessarily generate the final step that makes it verifiable in a smart contract. However, it is easy to verify within another circuit. + +While "standard" usage of NoirJS packages abstracts final proofs, it currently lacks the necessary interface to abstract away intermediate proofs. This means that these proofs need to be created by using the backend directly. + +In short: + +- `noir_js` generates *only* final proofs +- `backend_barretenberg` generates both types of proofs + +::: + +In a standard recursive app, you're also dealing with at least two circuits. For the purpose of this guide, we will assume the following: + +- `main`: a circuit of type `assert(x != y)`, where `main` is marked with a `#[recursive]` attribute. This attribute states that the backend should generate proofs that are friendly for verification within another circuit. +- `recursive`: a circuit that verifies `main` + +For a full example of how recursive proofs work, please refer to the [noir-examples](https://github.com/noir-lang/noir-examples) repository. We will *not* be using it as a reference for this guide. + +## Step 1: Setup + +In a common NoirJS app, you need to instantiate a backend with something like `const backend = new Backend(circuit)`. Then you feed it to the `noir_js` interface. + +For recursion, this doesn't happen, and the only need for `noir_js` is only to `execute` a circuit and get its witness and return value. Everything else is not interfaced, so it needs to happen on the `backend` object. + +It is also recommended that you instantiate the backend with as many threads as possible, to allow for maximum concurrency: + +```js +const backend = new Backend(circuit, { threads: 8 }) +``` + +:::tip +You can use the [`os.cpus()`](https://nodejs.org/api/os.html#oscpus) object in `nodejs` or [`navigator.hardwareConcurrency`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/hardwareConcurrency) on the browser to make the most out of those glorious cpu cores +::: + +## Step 2: Generating the witness and the proof for `main` + +After instantiating the backend, you should also instantiate `noir_js`. We will use it to execute the circuit and get the witness. + +```js +const noir = new Noir(circuit) +const { witness } = noir.execute(input) +``` + +With this witness, you are now able to generate the intermediate proof for the main circuit: + +```js +const { proof, publicInputs } = await backend.generateProof(witness) +``` + +:::warning + +Always keep in mind what is actually happening on your development process, otherwise you'll quickly become confused about what circuit we are actually running and why! + +In this case, you can imagine that Alice (running the `main` circuit) is proving something to Bob (running the `recursive` circuit), and Bob is verifying her proof within his proof. + +With this in mind, it becomes clear that our intermediate proof is the one *meant to be verified within another circuit*, so it must be Alice's. Actually, the only final proof in this theoretical scenario would be the last one, sent on-chain. + +::: + +## Step 3 - Verification and proof artifacts + +Optionally, you are able to verify the intermediate proof: + +```js +const verified = await backend.verifyProof({ proof, publicInputs }) +``` + +This can be useful to make sure our intermediate proof was correctly generated. But the real goal is to do it within another circuit. For that, we need to generate recursive proof artifacts that will be passed to the circuit that is verifying the proof we just generated. Instead of passing the proof and verification key as a byte array, we pass them as fields which makes it cheaper to verify in a circuit: + +```js +const { proofAsFields, vkAsFields, vkHash } = await backend.generateRecursiveProofArtifacts( { publicInputs, proof }, publicInputsCount) +``` + +This call takes the public inputs and the proof, but also the public inputs count. While this is easily retrievable by simply counting the `publicInputs` length, the backend interface doesn't currently abstract it away. + +:::info + +The `proofAsFields` has a constant size `[Field; 93]` and verification keys in Barretenberg are always `[Field; 114]`. + +::: + +:::warning + +One common mistake is to forget *who* makes this call. + +In a situation where Alice is generating the `main` proof, if she generates the proof artifacts and sends them to Bob, which gladly takes them as true, this would mean Alice could prove anything! + +Instead, Bob needs to make sure *he* extracts the proof artifacts, using his own instance of the `main` circuit backend. This way, Alice has to provide a valid proof for the correct `main` circuit. + +::: + +## Step 4 - Recursive proof generation + +With the artifacts, generating a recursive proof is no different from a normal proof. You simply use the `backend` (with the recursive circuit) to generate it: + +```js +const recursiveInputs = { + verification_key: vkAsFields, // array of length 114 + proof: proofAsFields, // array of length 93 + size of public inputs + publicInputs: [mainInput.y], // using the example above, where `y` is the only public input + key_hash: vkHash, +} + +const { witness, returnValue } = noir.execute(recursiveInputs) // we're executing the recursive circuit now! +const { proof, publicInputs } = backend.generateProof(witness) +const verified = backend.verifyProof({ proof, publicInputs }) +``` + +You can obviously chain this proof into another proof. In fact, if you're using recursive proofs, you're probably interested of using them this way! + +:::tip + +Managing circuits and "who does what" can be confusing. To make sure your naming is consistent, you can keep them in an object. For example: + +```js +const circuits = { + main: mainJSON, + recursive: recursiveJSON +} +const backends = { + main: new BarretenbergBackend(circuits.main), + recursive: new BarretenbergBackend(circuits.recursive) +} +const noir_programs = { + main: new Noir(circuits.main), + recursive: new Noir(circuits.recursive) +} +``` + +This allows you to neatly call exactly the method you want without conflicting names: + +```js +// Alice runs this 👇 +const { witness: mainWitness } = await noir_programs.main.execute(input) +const proof = await backends.main.generateProof(mainWitness) + +// Bob runs this 👇 +const verified = await backends.main.verifyProof(proof) +const { proofAsFields, vkAsFields, vkHash } = await backends.main.generateRecursiveProofArtifacts( + proof, + numPublicInputs, +); +const { witness: recursiveWitness } = await noir_programs.recursive.execute(recursiveInputs) +const recursiveProof = await backends.recursive.generateProof(recursiveWitness); +``` + +::: diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/how-to-solidity-verifier.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/how-to-solidity-verifier.md new file mode 100644 index 000000000000..c800d91ac695 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/how-to-solidity-verifier.md @@ -0,0 +1,251 @@ +--- +title: Generate a Solidity Verifier +description: + Learn how to run the verifier as a smart contract on the blockchain. Compile a Solidity verifier + contract for your Noir program and deploy it on any EVM blockchain acting as a verifier smart + contract. Read more to find out +keywords: + [ + solidity verifier, + smart contract, + blockchain, + compiler, + plonk_vk.sol, + EVM blockchain, + verifying Noir programs, + proving backend, + Barretenberg, + ] +sidebar_position: 0 +pagination_next: tutorials/noirjs_app +--- + +Noir has the ability to generate a verifier contract in Solidity, which can be deployed in many EVM-compatible blockchains such as Ethereum. + +This allows for a powerful feature set, as one can make use of the conciseness and the privacy provided by Noir in an immutable ledger. Applications can range from simple P2P guessing games, to complex private DeFi interactions. + +This guide shows you how to generate a Solidity Verifier and deploy it on the [Remix IDE](https://remix.ethereum.org/). It is assumed that: + +- You are comfortable with the Solidity programming language and understand how contracts are deployed on the Ethereum network +- You have Noir installed and you have a Noir program. If you don't, [get started](../getting_started/installation/index.md) with Nargo and the example Hello Noir circuit +- You are comfortable navigating RemixIDE. If you aren't or you need a refresher, you can find some video tutorials [here](https://www.youtube.com/channel/UCjTUPyFEr2xDGN6Cg8nKDaA) that could help you. + +## Rundown + +Generating a Solidity Verifier contract is actually a one-command process. However, compiling it and deploying it can have some caveats. Here's the rundown of this guide: + +1. How to generate a solidity smart contract +2. How to compile the smart contract in the RemixIDE +3. How to deploy it to a testnet + +## Step 1 - Generate a contract + +This is by far the most straightforward step. Just run: + +```sh +nargo compile +``` + +This will compile your source code into a Noir build artifact to be stored in the `./target` directory, you can then generate the smart contract using the commands: + +```sh +# Here we pass the path to the newly generated Noir artifact. +bb write_vk -b ./target/.json +bb contract +``` + +replacing `` with the name of your Noir project. A new `contract` folder would then be generated in your project directory, containing the Solidity +file `contract.sol`. It can be deployed to any EVM blockchain acting as a verifier smart contract. + +:::info + +It is possible to generate verifier contracts of Noir programs for other smart contract platforms as long as the proving backend supplies an implementation. + +Barretenberg, the default proving backend for Nargo, supports generation of verifier contracts, for the time being these are only in Solidity. +::: + +## Step 2 - Compiling + +We will mostly skip the details of RemixIDE, as the UI can change from version to version. For now, we can just open +
Remix and create a blank workspace. + +![Create Workspace](@site/static/img/how-tos/solidity_verifier_1.png) + +We will create a new file to contain the contract Nargo generated, and copy-paste its content. + +:::warning + +You'll likely see a warning advising you to not trust pasted code. While it is an important warning, it is irrelevant in the context of this guide and can be ignored. We will not be deploying anywhere near a mainnet. + +::: + +To compile our the verifier, we can navigate to the compilation tab: + +![Compilation Tab](@site/static/img/how-tos/solidity_verifier_2.png) + +Remix should automatically match a suitable compiler version. However, hitting the "Compile" button will most likely generate a "Stack too deep" error: + +![Stack too deep](@site/static/img/how-tos/solidity_verifier_3.png) + +This is due to the verify function needing to put many variables on the stack, but enabling the optimizer resolves the issue. To do this, let's open the "Advanced Configurations" tab and enable optimization. The default 200 runs will suffice. + +:::info + +This time we will see a warning about an unused function parameter. This is expected, as the `verify` function doesn't use the `_proof` parameter inside a solidity block, it is loaded from calldata and used in assembly. + +::: + +![Compilation success](@site/static/img/how-tos/solidity_verifier_4.png) + +## Step 3 - Deploying + +At this point we should have a compiled contract ready to deploy. If we navigate to the deploy section in Remix, we will see many different environments we can deploy to. The steps to deploy on each environment would be out-of-scope for this guide, so we will just use the default Remix VM. + +Looking closely, we will notice that our "Solidity Verifier" is actually three contracts working together: + +- An `UltraVerificationKey` library which simply stores the verification key for our circuit. +- An abstract contract `BaseUltraVerifier` containing most of the verifying logic. +- A main `UltraVerifier` contract that inherits from the Base and uses the Key contract. + +Remix will take care of the dependencies for us so we can simply deploy the UltraVerifier contract by selecting it and hitting "deploy": + +![Deploying UltraVerifier](@site/static/img/how-tos/solidity_verifier_5.png) + +A contract will show up in the "Deployed Contracts" section, where we can retrieve the Verification Key Hash. This is particularly useful for double-checking that the deployer contract is the correct one. + +:::note + +Why "UltraVerifier"? + +To be precise, the Noir compiler (`nargo`) doesn't generate the verifier contract directly. It compiles the Noir code into an intermediate language (ACIR), which is then executed by the backend. So it is the backend that returns the verifier smart contract, not Noir. + +In this case, the Barretenberg Backend uses the UltraPlonk proving system, hence the "UltraVerifier" name. + +::: + +## Step 4 - Verifying + +To verify a proof using the Solidity verifier contract, we call the `verify` function in this extended contract: + +```solidity +function verify(bytes calldata _proof, bytes32[] calldata _publicInputs) external view returns (bool) +``` + +When using the default example in the [Hello Noir](../getting_started/hello_noir/index.md) guide, the easiest way to confirm that the verifier contract is doing its job is by calling the `verify` function via remix with the required parameters. Note that the public inputs must be passed in separately to the rest of the proof so we must split the proof as returned from `bb`. + +First generate a proof with `bb` at the location `./proof` using the steps in [get started](../getting_started/hello_noir/index.md), this proof is in a binary format but we want to convert it into a hex string to pass into Remix, this can be done with the + +```bash +# This value must be changed to match the number of public inputs (including return values!) in your program. +NUM_PUBLIC_INPUTS=1 +PUBLIC_INPUT_BYTES=32*NUM_PUBLIC_INPUTS +HEX_PUBLIC_INPUTS=$(head -c $PUBLIC_INPUT_BYTES ./proof | od -An -v -t x1 | tr -d $' \n') +HEX_PROOF=$(tail -c +$(($PUBLIC_INPUT_BYTES + 1)) ./proof | od -An -v -t x1 | tr -d $' \n') + +echo "Public inputs:" +echo $HEX_PUBLIC_INPUTS + +echo "Proof:" +echo "0x$HEX_PROOF" +``` + +Remix expects that the public inputs will be split into an array of `bytes32` values so `HEX_PUBLIC_INPUTS` needs to be split up into 32 byte chunks which are prefixed with `0x` accordingly. + +A programmatic example of how the `verify` function is called can be seen in the example zk voting application [here](https://github.com/noir-lang/noir-examples/blob/33e598c257e2402ea3a6b68dd4c5ad492bce1b0a/foundry-voting/src/zkVote.sol#L35): + +```solidity +function castVote(bytes calldata proof, uint proposalId, uint vote, bytes32 nullifierHash) public returns (bool) { + // ... + bytes32[] memory publicInputs = new bytes32[](4); + publicInputs[0] = merkleRoot; + publicInputs[1] = bytes32(proposalId); + publicInputs[2] = bytes32(vote); + publicInputs[3] = nullifierHash; + require(verifier.verify(proof, publicInputs), "Invalid proof"); +``` + +:::info[Return Values] + +A circuit doesn't have the concept of a return value. Return values are just syntactic sugar in Noir. + +Under the hood, the return value is passed as an input to the circuit and is checked at the end of the circuit program. + +For example, if you have Noir program like this: + +```rust +fn main( + // Public inputs + pubkey_x: pub Field, + pubkey_y: pub Field, + // Private inputs + priv_key: Field, +) -> pub Field +``` + +the `verify` function will expect the public inputs array (second function parameter) to be of length 3, the two inputs and the return value. + +Passing only two inputs will result in an error such as `PUBLIC_INPUT_COUNT_INVALID(3, 2)`. + +In this case, the inputs parameter to `verify` would be an array ordered as `[pubkey_x, pubkey_y, return`. + +::: + +:::tip[Structs] + +You can pass structs to the verifier contract. They will be flattened so that the array of inputs is 1-dimensional array. + +For example, consider the following program: + +```rust +struct Type1 { + val1: Field, + val2: Field, +} + +struct Nested { + t1: Type1, + is_true: bool, +} + +fn main(x: pub Field, nested: pub Nested, y: pub Field) { + //... +} +``` + +The order of these inputs would be flattened to: `[x, nested.t1.val1, nested.t1.val2, nested.is_true, y]` + +::: + +The other function you can call is our entrypoint `verify` function, as defined above. + +:::tip + +It's worth noticing that the `verify` function is actually a `view` function. A `view` function does not alter the blockchain state, so it doesn't need to be distributed (i.e. it will run only on the executing node), and therefore doesn't cost any gas. + +This can be particularly useful in some situations. If Alice generated a proof and wants Bob to verify its correctness, Bob doesn't need to run Nargo, NoirJS, or any Noir specific infrastructure. He can simply make a call to the blockchain with the proof and verify it is correct without paying any gas. + +It would be incorrect to say that a Noir proof verification costs any gas at all. However, most of the time the result of `verify` is used to modify state (for example, to update a balance, a game state, etc). In that case the whole network needs to execute it, which does incur gas costs (calldata and execution, but not storage). + +::: + +## A Note on EVM chains + +Noir proof verification requires the ecMul, ecAdd and ecPairing precompiles. Not all EVM chains support EC Pairings, notably some of the ZK-EVMs. This means that you won't be able to use the verifier contract in all of them. You can find an incomplete list of which EVM chains support these precompiles [here](https://www.evmdiff.com/features?feature=precompiles). + +For example, chains like `zkSync ERA` and `Polygon zkEVM` do not currently support these precompiles, so proof verification via Solidity verifier contracts won't work. Here's a quick list of EVM chains that have been tested and are known to work: + +- Optimism +- Arbitrum +- Polygon PoS +- Scroll +- Celo + +If you test any other chains, please open a PR on this page to update the list. See [this doc](https://github.com/noir-lang/noir-starter/tree/main/with-foundry#testing-on-chain) for more info about testing verifier contracts on different EVM chains. + +## What's next + +Now that you know how to call a Noir Solidity Verifier on a smart contract using Remix, you should be comfortable with using it with some programmatic frameworks, such as [hardhat](https://github.com/noir-lang/noir-starter/tree/main/vite-hardhat) and [foundry](https://github.com/noir-lang/noir-starter/tree/main/with-foundry). + +You can find other tools, examples, boilerplates and libraries in the [awesome-noir](https://github.com/noir-lang/awesome-noir) repository. + +You should also be ready to write and deploy your first NoirJS app and start generating proofs on websites, phones, and NodeJS environments! Head on to the [NoirJS tutorial](../tutorials/noirjs_app.md) to learn how to do that. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/merkle-proof.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/merkle-proof.mdx new file mode 100644 index 000000000000..0a128adb2de6 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/merkle-proof.mdx @@ -0,0 +1,48 @@ +--- +title: Prove Merkle Tree Membership +description: + Learn how to use merkle membership proof in Noir to prove that a given leaf is a member of a + merkle tree with a specified root, at a given index. +keywords: + [merkle proof, merkle membership proof, Noir, rust, hash function, Pedersen, sha256, merkle tree] +sidebar_position: 4 +--- + +Let's walk through an example of a merkle membership proof in Noir that proves that a given leaf is +in a merkle tree. + +```rust + +fn main(message : [Field; 62], index : Field, hashpath : [Field; 40], root : Field) { + let leaf = std::hash::hash_to_field(message.as_slice()); + let merkle_root = std::merkle::compute_merkle_root(leaf, index, hashpath); + assert(merkle_root == root); +} + +``` + +The message is hashed using `hash_to_field`. The specific hash function that is being used is chosen +by the backend. The only requirement is that this hash function can heuristically be used as a +random oracle. If only collision resistance is needed, then one can call `std::hash::pedersen_hash` +instead. + +```rust +let leaf = std::hash::hash_to_field(message.as_slice()); +``` + +The leaf is then passed to a compute_merkle_root function with the root, index and hashpath. The returned root can then be asserted to be the same as the provided root. + +```rust +let merkle_root = std::merkle::compute_merkle_root(leaf, index, hashpath); +assert (merkle_root == root); +``` + +> **Note:** It is possible to re-implement the merkle tree implementation without standard library. +> However, for most usecases, it is enough. In general, the standard library will always opt to be +> as conservative as possible, while striking a balance with efficiency. + +An example, the merkle membership proof, only requires a hash function that has collision +resistance, hence a hash function like Pedersen is allowed, which in most cases is more efficient +than the even more conservative sha256. + +[View an example on the starter repo](https://github.com/noir-lang/noir-examples/blob/3ea09545cabfa464124ec2f3ea8e60c608abe6df/stealthdrop/circuits/src/main.nr#L20) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/using-devcontainers.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/using-devcontainers.mdx new file mode 100644 index 000000000000..727ec6ca6672 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/using-devcontainers.mdx @@ -0,0 +1,110 @@ +--- +title: Developer Containers and Codespaces +description: "Learn how to set up a devcontainer in your GitHub repository for a seamless coding experience with Codespaces. Follow our easy 8-step guide to create your own Noir environment without installing Nargo locally." +keywords: ["Devcontainer", "Codespaces", "GitHub", "Noir Environment", "Docker Image", "Development Environment", "Remote Coding", "GitHub Codespaces", "Noir Programming", "Nargo", "VSCode Extensions", "Noirup"] +sidebar_position: 1 +--- + +Adding a developer container configuration file to your Noir project is one of the easiest way to unlock coding in browser. + +## What's a devcontainer after all? + +A [Developer Container](https://containers.dev/) (devcontainer for short) is a Docker image that comes preloaded with tools, extensions, and other tools you need to quickly get started or continue a project, without having to install Nargo locally. Think of it as a development environment in a box. + +There are many advantages to this: + +- It's platform and architecture agnostic +- You don't need to have an IDE installed, or Nargo, or use a terminal at all +- It's safer for using on a public machine or public network + +One of the best ways of using devcontainers is... not using your machine at all, for maximum control, performance, and ease of use. +Enter Codespaces. + +## Codespaces + +If a devcontainer is just a Docker image, then what stops you from provisioning a `p3dn.24xlarge` AWS EC2 instance with 92 vCPUs and 768 GiB RAM and using it to prove your 10-gate SNARK proof? + +Nothing! Except perhaps the 30-40$ per hour it will cost you. + +The problem is that provisioning takes time, and I bet you don't want to see the AWS console every time you want to code something real quick. + +Fortunately, there's an easy and free way to get a decent remote machine ready and loaded in less than 2 minutes: Codespaces. [Codespaces is a Github feature](https://github.com/features/codespaces) that allows you to code in a remote machine by using devcontainers, and it's pretty cool: + +- You can start coding Noir in less than a minute +- It uses the resources of a remote machine, so you can code on your grandma's phone if needed be +- It makes it easy to share work with your frens +- It's fully reusable, you can stop and restart whenever you need to + +:::info + +Don't take out your wallet just yet. Free GitHub accounts get about [15-60 hours of coding](https://github.com/features/codespaces) for free per month, depending on the size of your provisioned machine. + +::: + +## Tell me it's _actually_ easy + +It is! + +Github comes with a default codespace and you can use it to code your own devcontainer. That's exactly what we will be doing in this guide. + + + +8 simple steps: + +#### 1. Create a new repository on GitHub. + +#### 2. Click "Start coding with Codespaces". This will use the default image. + +#### 3. Create a folder called `.devcontainer` in the root of your repository. + +#### 4. Create a Dockerfile in that folder, and paste the following code: + +```docker +FROM --platform=linux/amd64 node:lts-bookworm-slim +SHELL ["/bin/bash", "-c"] +RUN apt update && apt install -y curl bash git tar gzip libc++-dev +RUN curl -L https://raw.githubusercontent.com/noir-lang/noirup/main/install | bash +ENV PATH="/root/.nargo/bin:$PATH" +RUN noirup +ENTRYPOINT ["nargo"] +``` +#### 5. Create a file called `devcontainer.json` in the same folder, and paste the following code: + +```json +{ + "name": "Noir on Codespaces", + "build": { + "context": ".", + "dockerfile": "Dockerfile" + }, + "customizations": { + "vscode": { + "extensions": ["noir-lang.vscode-noir"] + } + } +} +``` +#### 6. Commit and push your changes + +This will pull the new image and build it, so it could take a minute or so + +#### 8. Done! +Just wait for the build to finish, and there's your easy Noir environment. + + +Refer to [noir-starter](https://github.com/noir-lang/noir-starter/) as an example of how devcontainers can be used together with codespaces. + + + +## How do I use it? + +Using the codespace is obviously much easier than setting it up. +Just navigate to your repository and click "Code" -> "Open with Codespaces". It should take a few seconds to load, and you're ready to go. + +:::info + +If you really like the experience, you can add a badge to your readme, links to existing codespaces, and more. +Check out the [official docs](https://docs.github.com/en/codespaces/setting-up-your-project-for-codespaces/setting-up-your-repository/facilitating-quick-creation-and-resumption-of-codespaces) for more info. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/index.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/index.mdx new file mode 100644 index 000000000000..a6bd306f91da --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/index.mdx @@ -0,0 +1,67 @@ +--- +title: Noir Lang +hide_title: true +description: + Learn about the public alpha release of Noir, a domain specific language heavily influenced by Rust that compiles to + an intermediate language which can be compiled to an arithmetic circuit or a rank-1 constraint system. +keywords: + [Noir, + Domain Specific Language, + Rust, + Intermediate Language, + Arithmetic Circuit, + Rank-1 Constraint System, + Ethereum Developers, + Protocol Developers, + Blockchain Developers, + Proving System, + Smart Contract Language] +sidebar_position: 0 +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Noir Logo + +Noir is an open-source Domain-Specific Language for safe and seamless construction of privacy-preserving Zero-Knowledge programs, requiring no previous knowledge on the underlying mathematics or cryptography. + +ZK programs are programs that can generate short proofs of statements without revealing all inputs to the statements. You can read more about Zero-Knowledge Proofs [here](https://dev.to/spalladino/a-beginners-intro-to-coding-zero-knowledge-proofs-c56). + +## What's new about Noir? + +Noir works differently from most ZK languages by taking a two-pronged path. First, it compiles the program to an adaptable intermediate language known as ACIR. From there, depending on a given project's needs, ACIR can be further compiled into an arithmetic circuit for integration with the proving backend. + +:::info + +Noir is backend agnostic, which means it makes no assumptions on which proving backend powers the ZK proof. Being the language that powers [Aztec Contracts](https://docs.aztec.network/developers/contracts/main), it defaults to Aztec's Barretenberg proving backend. + +However, the ACIR output can be transformed to be compatible with other PLONK-based backends, or into a [rank-1 constraint system](https://www.rareskills.io/post/rank-1-constraint-system) suitable for backends such as Arkwork's Marlin. + +::: + +## Who is Noir for? + +Noir can be used both in complex cloud-based backends and in user's smartphones, requiring no knowledge on the underlying math or cryptography. From authorization systems that keep a password in the user's device, to complex on-chain verification of recursive proofs, Noir is designed to abstract away complexity without any significant overhead. Here are some examples of situations where Noir can be used: + + + + Noir Logo + + Aztec Contracts leverage Noir to allow for the storage and execution of private information. Writing an Aztec Contract is as easy as writing Noir, and Aztec developers can easily interact with the network storage and execution through the [Aztec.nr](https://docs.aztec.network/developers/contracts/main) library. + + + Soliditry Verifier Example + Noir can auto-generate Solidity verifier contracts that verify Noir proofs. This allows for non-interactive verification of proofs containing private information in an immutable system. This feature powers a multitude of use-case scenarios, from P2P chess tournaments, to [Aztec Layer-2 Blockchain](https://docs.aztec.network/) + + + Aztec Labs developed NoirJS, an easy interface to generate and verify Noir proofs in a Javascript environment. This allows for Noir to be used in webpages, mobile apps, games, and any other environment supporting JS execution in a standalone manner. + + + + +## Libraries + +Noir is meant to be easy to extend by simply importing Noir libraries just like in Rust. +The [awesome-noir repo](https://github.com/noir-lang/awesome-noir#libraries) is a collection of libraries developed by the Noir community. +Writing a new library is easy and makes code be composable and easy to reuse. See the section on [dependencies](noir/modules_packages_crates/dependencies.md) for more information. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/migration_notes.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/migration_notes.md new file mode 100644 index 000000000000..6bd740024e5b --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/migration_notes.md @@ -0,0 +1,105 @@ +--- +title: Migration notes +description: Read about migration notes from previous versions, which could solve problems while updating +keywords: [Noir, notes, migration, updating, upgrading] +--- + +Noir is in full-speed development. Things break fast, wild, and often. This page attempts to leave some notes on errors you might encounter when upgrading and how to resolve them until proper patches are built. + +### `backend encountered an error: libc++.so.1` + +Depending on your OS, you may encounter the following error when running `nargo prove` for the first time: + +```text +The backend encountered an error: "/home/codespace/.nargo/backends/acvm-backend-barretenberg/backend_binary: error while loading shared libraries: libc++.so.1: cannot open shared object file: No such file or directory\n" +``` + +Install the `libc++-dev` library with: + +```bash +sudo apt install libc++-dev +``` + +## ≥0.19 + +### Enforcing `compiler_version` + +From this version on, the compiler will check for the `compiler_version` field in `Nargo.toml`, and will error if it doesn't match the current Nargo version in use. + +To update, please make sure this field in `Nargo.toml` matches the output of `nargo --version`. + +## ≥0.14 + +The index of the [for loops](noir/concepts/control_flow.md#loops) is now of type `u64` instead of `Field`. An example refactor would be: + +```rust +for i in 0..10 { + let i = i as Field; +} +``` + +## ≥v0.11.0 and Nargo backend + +From this version onwards, Nargo starts managing backends through the `nargo backend` command. Upgrading to the versions per usual steps might lead to: + +### `backend encountered an error` + +This is likely due to the existing locally installed version of proving backend (e.g. barretenberg) is incompatible with the version of Nargo in use. + +To fix the issue: + +1. Uninstall the existing backend + +```bash +nargo backend uninstall acvm-backend-barretenberg +``` + +You may replace _acvm-backend-barretenberg_ with the name of your backend listed in `nargo backend ls` or in ~/.nargo/backends. + +2. Reinstall a compatible version of the proving backend. + +If you are using the default barretenberg backend, simply run: + +``` +nargo prove +``` + +with your Noir program. + +This will trigger the download and installation of the latest version of barretenberg compatible with your Nargo in use. + +### `backend encountered an error: illegal instruction` + +On certain Intel-based systems, an `illegal instruction` error may arise due to incompatibility of barretenberg with certain CPU instructions. + +To fix the issue: + +1. Uninstall the existing backend + +```bash +nargo backend uninstall acvm-backend-barretenberg +``` + +You may replace _acvm-backend-barretenberg_ with the name of your backend listed in `nargo backend ls` or in ~/.nargo/backends. + +2. Reinstall a compatible version of the proving backend. + +If you are using the default barretenberg backend, simply run: + +``` +nargo backend install acvm-backend-barretenberg https://github.com/noir-lang/barretenberg-js-binary/raw/master/run-bb.tar.gz +``` + +This downloads and installs a specific bb.js based version of barretenberg binary from GitHub. + +The gzipped file is running [this bash script](https://github.com/noir-lang/barretenberg-js-binary/blob/master/run-bb-js.sh), where we need to gzip it as the Nargo currently expect the backend to be zipped up. + +Then run: + +``` +DESIRED_BINARY_VERSION=0.8.1 nargo info +``` + +This overrides the bb native binary with a bb.js node application instead, which should be compatible with most if not all hardware. This does come with the drawback of being generally slower than native binary. + +0.8.1 indicates bb.js version 0.8.1, so if you change that it will update to a different version or the default version in the script if none was supplied. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/_category_.json b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/_category_.json new file mode 100644 index 000000000000..7da08f8a8c5d --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Concepts", + "position": 0, + "collapsible": true, + "collapsed": true +} \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/assert.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/assert.md new file mode 100644 index 000000000000..2132de42072d --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/assert.md @@ -0,0 +1,78 @@ +--- +title: Assert Function +description: + Learn about the `assert` and `static_assert` functions in Noir, which can be used to explicitly + constrain the predicate or comparison expression that follows to be true, and what happens if + the expression is false at runtime or compile-time, respectively. +keywords: [Noir programming language, assert statement, predicate expression, comparison expression] +sidebar_position: 4 +--- + +Noir includes a special `assert` function which will explicitly constrain the predicate/comparison +expression that follows to be true. If this expression is false at runtime, the program will fail to +be proven. Example: + +```rust +fn main(x : Field, y : Field) { + assert(x == y); +} +``` + +> Assertions only work for predicate operations, such as `==`. If there's any ambiguity on the operation, the program will fail to compile. For example, it is unclear if `assert(x + y)` would check for `x + y == 0` or simply would return `true`. + +You can optionally provide a message to be logged when the assertion fails: + +```rust +assert(x == y, "x and y are not equal"); +``` + +Aside string literals, the optional message can be a format string or any other type supported as input for Noir's [print](../standard_library/logging.md) functions. This feature lets you incorporate runtime variables into your failed assertion logs: + +```rust +assert(x == y, f"Expected x == y, but got {x} == {y}"); +``` + +Using a variable as an assertion message directly: + +```rust +struct myStruct { + myField: Field +} + +let s = myStruct { myField: y }; +assert(s.myField == x, s); +``` + +There is also a special `static_assert` function that behaves like `assert`, +but that runs at compile-time. + +```rust +fn main(xs: [Field; 3]) { + let x = 2 + 2; + let y = 4; + static_assert(x == y, "expected 2 + 2 to equal 4"); + + // This passes since the length of `xs` is known at compile-time + static_assert(xs.len() == 3, "expected the input to have 3 elements"); +} +``` + +This function fails when passed a dynamic (run-time) argument: + +```rust +fn main(x : Field, y : Field) { + // this fails because `x` is not known at compile-time + static_assert(x == 2, "expected x to be known at compile-time and equal to 2"); + + let mut example_slice = &[]; + if y == 4 { + example_slice = example_slice.push_back(0); + } + + // This fails because the length of `example_slice` is not known at + // compile-time + let error_message = "expected an empty slice, known at compile-time"; + static_assert(example_slice.len() == 0, error_message); +} +``` + diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/comments.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/comments.md new file mode 100644 index 000000000000..b51a85f5c949 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/comments.md @@ -0,0 +1,33 @@ +--- +title: Comments +description: + Learn how to write comments in Noir programming language. A comment is a line of code that is + ignored by the compiler, but it can be read by programmers. Single-line and multi-line comments + are supported in Noir. +keywords: [Noir programming language, comments, single-line comments, multi-line comments] +sidebar_position: 10 +--- + +A comment is a line in your codebase which the compiler ignores, however it can be read by +programmers. + +Here is a single line comment: + +```rust +// This is a comment and is ignored +``` + +`//` is used to tell the compiler to ignore the rest of the line. + +Noir also supports multi-line block comments. Start a block comment with `/*` and end the block with `*/`. + +Noir does not natively support doc comments. You may be able to use [Rust doc comments](https://doc.rust-lang.org/reference/comments.html) in your code to leverage some Rust documentation build tools with Noir code. + +```rust +/* + This is a block comment describing a complex function. +*/ +fn main(x : Field, y : pub Field) { + assert(x != y); +} +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/control_flow.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/control_flow.md new file mode 100644 index 000000000000..045d3c3a5f58 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/control_flow.md @@ -0,0 +1,77 @@ +--- +title: Control Flow +description: + Learn how to use loops and if expressions in the Noir programming language. Discover the syntax + and examples for for loops and if-else statements. +keywords: [Noir programming language, loops, for loop, if-else statements, Rust syntax] +sidebar_position: 2 +--- + +## If Expressions + +Noir supports `if-else` statements. The syntax is most similar to Rust's where it is not required +for the statement's conditional to be surrounded by parentheses. + +```rust +let a = 0; +let mut x: u32 = 0; + +if a == 0 { + if a != 0 { + x = 6; + } else { + x = 2; + } +} else { + x = 5; + assert(x == 5); +} +assert(x == 2); +``` + +## Loops + +Noir has one kind of loop: the `for` loop. `for` loops allow you to repeat a block of code multiple +times. + +The following block of code between the braces is run 10 times. + +```rust +for i in 0..10 { + // do something +} +``` + +The index for loops is of type `u64`. + +### Break and Continue + +In unconstrained code, `break` and `continue` are also allowed in `for` loops. These are only allowed +in unconstrained code since normal constrained code requires that Noir knows exactly how many iterations +a loop may have. `break` and `continue` can be used like so: + +```rust +for i in 0 .. 10 { + println("Iteration start") + + if i == 2 { + continue; + } + + if i == 5 { + break; + } + + println(i); +} +println("Loop end") +``` + +When used, `break` will end the current loop early and jump to the statement after the for loop. In the example +above, the `break` will stop the loop and jump to the `println("Loop end")`. + +`continue` will stop the current iteration of the loop, and jump to the start of the next iteration. In the example +above, `continue` will jump to `println("Iteration start")` when used. Note that the loop continues as normal after this. +The iteration variable `i` is still increased by one as normal when `continue` is used. + +`break` and `continue` cannot currently be used to jump out of more than a single loop at a time. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_bus.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_bus.mdx new file mode 100644 index 000000000000..e55e58622ce8 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_bus.mdx @@ -0,0 +1,23 @@ +--- +title: Data Bus +sidebar_position: 13 +--- +import Experimental from '@site/src/components/Notes/_experimental.mdx'; + + + +The data bus is an optimization that the backend can use to make recursion more efficient. +In order to use it, you must define some inputs of the program entry points (usually the `main()` +function) with the `call_data` modifier, and the return values with the `return_data` modifier. +These modifiers are incompatible with `pub` and `mut` modifiers. + +## Example + +```rust +fn main(mut x: u32, y: call_data u32, z: call_data [u32;4] ) -> return_data u32 { + let a = z[x]; + a+y +} +``` + +As a result, both call_data and return_data will be treated as private inputs and encapsulated into a read-only array each, for the backend to process. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/_category_.json b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/_category_.json new file mode 100644 index 000000000000..5d694210bbf3 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/_category_.json @@ -0,0 +1,5 @@ +{ + "position": 0, + "collapsible": true, + "collapsed": true +} diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/arrays.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/arrays.md new file mode 100644 index 000000000000..9a4ab5d3c1f2 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/arrays.md @@ -0,0 +1,253 @@ +--- +title: Arrays +description: + Dive into the Array data type in Noir. Grasp its methods, practical examples, and best practices for efficiently using Arrays in your Noir code. +keywords: + [ + noir, + array type, + methods, + examples, + indexing, + ] +sidebar_position: 4 +--- + +An array is one way of grouping together values into one compound type. Array types can be inferred +or explicitly specified via the syntax `[; ]`: + +```rust +fn main(x : Field, y : Field) { + let my_arr = [x, y]; + let your_arr: [Field; 2] = [x, y]; +} +``` + +Here, both `my_arr` and `your_arr` are instantiated as an array containing two `Field` elements. + +Array elements can be accessed using indexing: + +```rust +fn main() { + let a = [1, 2, 3, 4, 5]; + + let first = a[0]; + let second = a[1]; +} +``` + +All elements in an array must be of the same type (i.e. homogeneous). That is, an array cannot group +a `Field` value and a `u8` value together for example. + +You can write mutable arrays, like: + +```rust +fn main() { + let mut arr = [1, 2, 3, 4, 5]; + assert(arr[0] == 1); + + arr[0] = 42; + assert(arr[0] == 42); +} +``` + +You can instantiate a new array of a fixed size with the same value repeated for each element. The following example instantiates an array of length 32 where each element is of type Field and has the value 0. + +```rust +let array: [Field; 32] = [0; 32]; +``` + +Like in Rust, arrays in Noir are a fixed size. However, if you wish to convert an array to a [slice](./slices.mdx), you can just call `as_slice` on your array: + +```rust +let array: [Field; 32] = [0; 32]; +let sl = array.as_slice() +``` + +You can define multidimensional arrays: + +```rust +let array : [[Field; 2]; 2]; +let element = array[0][0]; +``` + +However, multidimensional slices are not supported. For example, the following code will error at compile time: + +```rust +let slice : [[Field]] = &[]; +``` + +## Types + +You can create arrays of primitive types or structs. There is not yet support for nested arrays +(arrays of arrays) or arrays of structs that contain arrays. + +## Methods + +For convenience, the STD provides some ready-to-use, common methods for arrays. +Each of these functions are located within the generic impl `impl [T; N] {`. +So anywhere `self` appears, it refers to the variable `self: [T; N]`. + +### len + +Returns the length of an array + +```rust +fn len(self) -> Field +``` + +example + +```rust +fn main() { + let array = [42, 42]; + assert(array.len() == 2); +} +``` + +### sort + +Returns a new sorted array. The original array remains untouched. Notice that this function will +only work for arrays of fields or integers, not for any arbitrary type. This is because the sorting +logic it uses internally is optimized specifically for these values. If you need a sort function to +sort any type, you should use the function `sort_via` described below. + +```rust +fn sort(self) -> [T; N] +``` + +example + +```rust +fn main() { + let arr = [42, 32]; + let sorted = arr.sort(); + assert(sorted == [32, 42]); +} +``` + +### sort_via + +Sorts the array with a custom comparison function + +```rust +fn sort_via(self, ordering: fn(T, T) -> bool) -> [T; N] +``` + +example + +```rust +fn main() { + let arr = [42, 32] + let sorted_ascending = arr.sort_via(|a, b| a < b); + assert(sorted_ascending == [32, 42]); // verifies + + let sorted_descending = arr.sort_via(|a, b| a > b); + assert(sorted_descending == [32, 42]); // does not verify +} +``` + +### map + +Applies a function to each element of the array, returning a new array containing the mapped elements. + +```rust +fn map(self, f: fn(T) -> U) -> [U; N] +``` + +example + +```rust +let a = [1, 2, 3]; +let b = a.map(|a| a * 2); // b is now [2, 4, 6] +``` + +### fold + +Applies a function to each element of the array, returning the final accumulated value. The first +parameter is the initial value. + +```rust +fn fold(self, mut accumulator: U, f: fn(U, T) -> U) -> U +``` + +This is a left fold, so the given function will be applied to the accumulator and first element of +the array, then the second, and so on. For a given call the expected result would be equivalent to: + +```rust +let a1 = [1]; +let a2 = [1, 2]; +let a3 = [1, 2, 3]; + +let f = |a, b| a - b; +a1.fold(10, f) //=> f(10, 1) +a2.fold(10, f) //=> f(f(10, 1), 2) +a3.fold(10, f) //=> f(f(f(10, 1), 2), 3) +``` + +example: + +```rust + +fn main() { + let arr = [2, 2, 2, 2, 2]; + let folded = arr.fold(0, |a, b| a + b); + assert(folded == 10); +} + +``` + +### reduce + +Same as fold, but uses the first element as the starting element. + +```rust +fn reduce(self, f: fn(T, T) -> T) -> T +``` + +example: + +```rust +fn main() { + let arr = [2, 2, 2, 2, 2]; + let reduced = arr.reduce(|a, b| a + b); + assert(reduced == 10); +} +``` + +### all + +Returns true if all the elements satisfy the given predicate + +```rust +fn all(self, predicate: fn(T) -> bool) -> bool +``` + +example: + +```rust +fn main() { + let arr = [2, 2, 2, 2, 2]; + let all = arr.all(|a| a == 2); + assert(all); +} +``` + +### any + +Returns true if any of the elements satisfy the given predicate + +```rust +fn any(self, predicate: fn(T) -> bool) -> bool +``` + +example: + +```rust +fn main() { + let arr = [2, 2, 2, 2, 5]; + let any = arr.any(|a| a == 5); + assert(any); +} + +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/booleans.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/booleans.md new file mode 100644 index 000000000000..2507af710e71 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/booleans.md @@ -0,0 +1,28 @@ +--- +title: Booleans +description: + Delve into the Boolean data type in Noir. Understand its methods, practical examples, and best practices for using Booleans in your Noir programs. +keywords: + [ + noir, + boolean type, + methods, + examples, + logical operations, + ] +sidebar_position: 2 +--- + + +The `bool` type in Noir has two possible values: `true` and `false`: + +```rust +fn main() { + let t = true; + let f: bool = false; +} +``` + +The boolean type is most commonly used in conditionals like `if` expressions and `assert` +statements. More about conditionals is covered in the [Control Flow](../control_flow.md) and +[Assert Function](../assert.md) sections. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/fields.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/fields.md new file mode 100644 index 000000000000..a10a48107883 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/fields.md @@ -0,0 +1,192 @@ +--- +title: Fields +description: + Dive deep into the Field data type in Noir. Understand its methods, practical examples, and best practices to effectively use Fields in your Noir programs. +keywords: + [ + noir, + field type, + methods, + examples, + best practices, + ] +sidebar_position: 0 +--- + +The field type corresponds to the native field type of the proving backend. + +The size of a Noir field depends on the elliptic curve's finite field for the proving backend +adopted. For example, a field would be a 254-bit integer when paired with the default backend that +spans the Grumpkin curve. + +Fields support integer arithmetic and are often used as the default numeric type in Noir: + +```rust +fn main(x : Field, y : Field) { + let z = x + y; +} +``` + +`x`, `y` and `z` are all private fields in this example. Using the `let` keyword we defined a new +private value `z` constrained to be equal to `x + y`. + +If proving efficiency is of priority, fields should be used as a default for solving problems. +Smaller integer types (e.g. `u64`) incur extra range constraints. + +## Methods + +After declaring a Field, you can use these common methods on it: + +### to_le_bits + +Transforms the field into an array of bits, Little Endian. + +```rust +fn to_le_bits(_x : Field, _bit_size: u32) -> [u1] +``` + +example: + +```rust +fn main() { + let field = 2; + let bits = field.to_le_bits(32); +} +``` + +### to_be_bits + +Transforms the field into an array of bits, Big Endian. + +```rust +fn to_be_bits(_x : Field, _bit_size: u32) -> [u1] +``` + +example: + +```rust +fn main() { + let field = 2; + let bits = field.to_be_bits(32); +} +``` + +### to_le_bytes + +Transforms into an array of bytes, Little Endian + +```rust +fn to_le_bytes(_x : Field, byte_size: u32) -> [u8] +``` + +example: + +```rust +fn main() { + let field = 2; + let bytes = field.to_le_bytes(4); +} +``` + +### to_be_bytes + +Transforms into an array of bytes, Big Endian + +```rust +fn to_be_bytes(_x : Field, byte_size: u32) -> [u8] +``` + +example: + +```rust +fn main() { + let field = 2; + let bytes = field.to_be_bytes(4); +} +``` + +### to_le_radix + +Decomposes into a vector over the specified base, Little Endian + +```rust +fn to_le_radix(_x : Field, _radix: u32, _result_len: u32) -> [u8] +``` + +example: + +```rust +fn main() { + let field = 2; + let radix = field.to_le_radix(256, 4); +} +``` + +### to_be_radix + +Decomposes into a vector over the specified base, Big Endian + +```rust +fn to_be_radix(_x : Field, _radix: u32, _result_len: u32) -> [u8] +``` + +example: + +```rust +fn main() { + let field = 2; + let radix = field.to_be_radix(256, 4); +} +``` + +### pow_32 + +Returns the value to the power of the specified exponent + +```rust +fn pow_32(self, exponent: Field) -> Field +``` + +example: + +```rust +fn main() { + let field = 2 + let pow = field.pow_32(4); + assert(pow == 16); +} +``` + +### assert_max_bit_size + +Adds a constraint to specify that the field can be represented with `bit_size` number of bits + +```rust +fn assert_max_bit_size(self, bit_size: u32) +``` + +example: + +```rust +fn main() { + let field = 2 + field.assert_max_bit_size(32); +} +``` + +### sgn0 + +Parity of (prime) Field element, i.e. sgn0(x mod p) = 0 if x ∈ \{0, ..., p-1\} is even, otherwise sgn0(x mod p) = 1. + +```rust +fn sgn0(self) -> u1 +``` + + +### lt + +Returns true if the field is less than the other field + +```rust +pub fn lt(self, another: Field) -> bool +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/function_types.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/function_types.md new file mode 100644 index 000000000000..f6121af17e24 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/function_types.md @@ -0,0 +1,26 @@ +--- +title: Function types +sidebar_position: 10 +--- + +Noir supports higher-order functions. The syntax for a function type is as follows: + +```rust +fn(arg1_type, arg2_type, ...) -> return_type +``` + +Example: + +```rust +fn assert_returns_100(f: fn() -> Field) { // f takes no args and returns a Field + assert(f() == 100); +} + +fn main() { + assert_returns_100(|| 100); // ok + assert_returns_100(|| 150); // fails +} +``` + +A function type also has an optional capture environment - this is necessary to support closures. +See [Lambdas](../lambdas.md) for more details. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/index.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/index.md new file mode 100644 index 000000000000..3eadb2dc8a46 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/index.md @@ -0,0 +1,118 @@ +--- +title: Data Types +description: + Get a clear understanding of the two categories of Noir data types - primitive types and compound + types. Learn about their characteristics, differences, and how to use them in your Noir + programming. +keywords: + [ + noir, + data types, + primitive types, + compound types, + private types, + public types, + ] +--- + +Every value in Noir has a type, which determines which operations are valid for it. + +All values in Noir are fundamentally composed of `Field` elements. For a more approachable +developing experience, abstractions are added on top to introduce different data types in Noir. + +Noir has two category of data types: primitive types (e.g. `Field`, integers, `bool`) and compound +types that group primitive types (e.g. arrays, tuples, structs). Each value can either be private or +public. + +## Private & Public Types + +A **private value** is known only to the Prover, while a **public value** is known by both the +Prover and Verifier. Mark values as `private` when the value should only be known to the prover. All +primitive types (including individual fields of compound types) in Noir are private by default, and +can be marked public when certain values are intended to be revealed to the Verifier. + +> **Note:** For public values defined in Noir programs paired with smart contract verifiers, once +> the proofs are verified on-chain the values can be considered known to everyone that has access to +> that blockchain. + +Public data types are treated no differently to private types apart from the fact that their values +will be revealed in proofs generated. Simply changing the value of a public type will not change the +circuit (where the same goes for changing values of private types as well). + +_Private values_ are also referred to as _witnesses_ sometimes. + +> **Note:** The terms private and public when applied to a type (e.g. `pub Field`) have a different +> meaning than when applied to a function (e.g. `pub fn foo() {}`). +> +> The former is a visibility modifier for the Prover to interpret if a value should be made known to +> the Verifier, while the latter is a visibility modifier for the compiler to interpret if a +> function should be made accessible to external Noir programs like in other languages. + +### pub Modifier + +All data types in Noir are private by default. Types are explicitly declared as public using the +`pub` modifier: + +```rust +fn main(x : Field, y : pub Field) -> pub Field { + x + y +} +``` + +In this example, `x` is **private** while `y` and `x + y` (the return value) are **public**. Note +that visibility is handled **per variable**, so it is perfectly valid to have one input that is +private and another that is public. + +> **Note:** Public types can only be declared through parameters on `main`. + +## Type Aliases + +A type alias is a new name for an existing type. Type aliases are declared with the keyword `type`: + +```rust +type Id = u8; + +fn main() { + let id: Id = 1; + let zero: u8 = 0; + assert(zero + 1 == id); +} +``` + +Type aliases can also be used with [generics](../generics.md): + +```rust +type Id = Size; + +fn main() { + let id: Id = 1; + let zero: u32 = 0; + assert(zero + 1 == id); +} +``` + +Type aliases can even refer to other aliases. An error will be issued if they form a cycle: + +```rust +// Ok! +type A = B; +type B = Field; + +type Bad1 = Bad2; + +// error: Dependency cycle found +type Bad2 = Bad1; +// ^^^^^^^^^^^ 'Bad2' recursively depends on itself: Bad2 -> Bad1 -> Bad2 +``` + +## Wildcard Type +Noir can usually infer the type of the variable from the context, so specifying the type of a variable is only required when it cannot be inferred. However, specifying a complex type can be tedious, especially when it has multiple generic arguments. Often some of the generic types can be inferred from the context, and Noir only needs a hint to properly infer the other types. We can partially specify a variable's type by using `_` as a marker, indicating where we still want the compiler to infer the type. + +```rust +let a: [_; 4] = foo(b); +``` + + +### BigInt + +You can achieve BigInt functionality using the [Noir BigInt](https://github.com/shuklaayush/noir-bigint) library. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/integers.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/integers.md new file mode 100644 index 000000000000..a1d59bf31662 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/integers.md @@ -0,0 +1,156 @@ +--- +title: Integers +description: Explore the Integer data type in Noir. Learn about its methods, see real-world examples, and grasp how to efficiently use Integers in your Noir code. +keywords: [noir, integer types, methods, examples, arithmetic] +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. + +:::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. + +::: + +## Unsigned Integers + +An unsigned integer type is specified first with the letter `u` (indicating its unsigned nature) followed by its bit size (e.g. `8`): + +```rust +fn main() { + let x: u8 = 1; + let y: u8 = 1; + let z = x + y; + assert (z == 2); +} +``` + +The bit size determines the maximum value the integer type can store. For example, a `u8` variable can store a value in the range of 0 to 255 (i.e. $\\2^{8}-1\\$). + +## Signed Integers + +A signed integer type is specified first with the letter `i` (which stands for integer) followed by its bit size (e.g. `8`): + +```rust +fn main() { + let x: i8 = -1; + let y: i8 = -1; + let z = x + y; + assert (z == -2); +} +``` + +The bit size determines the maximum and minimum range of value the integer type can store. For example, an `i8` variable can store a value in the range of -128 to 127 (i.e. $\\-2^{7}\\$ to $\\2^{7}-1\\$). + +## 128 bits Unsigned Integers + +The built-in structure `U128` allows you to use 128-bit unsigned integers almost like a native integer type. However, there are some differences to keep in mind: +- You cannot cast between a native integer and `U128` +- There is a higher performance cost when using `U128`, compared to a native type. + +Conversion between unsigned integer types and U128 are done through the use of `from_integer` and `to_integer` functions. `from_integer` also accepts the `Field` type as input. + +```rust +fn main() { + let x = U128::from_integer(23); + let y = U128::from_hex("0x7"); + let z = x + y; + assert(z.to_integer() == 30); +} +``` + +`U128` is implemented with two 64 bits limbs, representing the low and high bits, which explains the performance cost. You should expect `U128` to be twice more costly for addition and four times more costly for multiplication. +You can construct a U128 from its limbs: +```rust +fn main(x: u64, y: u64) { + let x = U128::from_u64s_be(x,y); + assert(z.hi == x as Field); + assert(z.lo == y as Field); +} +``` + +Note that the limbs are stored as Field elements in order to avoid unnecessary conversions. +Apart from this, most operations will work as usual: + +```rust +fn main(x: U128, y: U128) { + // multiplication + let c = x * y; + // addition and subtraction + let c = c - x + y; + // division + let c = x / y; + // bit operation; + let c = x & y | y; + // bit shift + let c = x << y; + // comparisons; + let c = x < y; + let c = x == y; +} +``` + +## Overflows + +Computations that exceed the type boundaries will result in overflow errors. This happens with both signed and unsigned integers. For example, attempting to prove: + +```rust +fn main(x: u8, y: u8) { + let z = x + y; +} +``` + +With: + +```toml +x = "255" +y = "1" +``` + +Would result in: + +``` +$ nargo execute +error: Assertion failed: 'attempt to add with overflow' +┌─ ~/src/main.nr:9:13 +│ +│ let z = x + y; +│ ----- +│ += Call stack: + ... +``` + +A similar error would happen with signed integers: + +```rust +fn main() { + let x: i8 = -118; + let y: i8 = -11; + let z = x + y; +} +``` + +### Wrapping methods + +Although integer overflow is expected to error, some use-cases rely on wrapping. For these use-cases, the standard library provides `wrapping` variants of certain common operations: + +```rust +fn wrapping_add(x: T, y: T) -> T; +fn wrapping_sub(x: T, y: T) -> T; +fn wrapping_mul(x: T, y: T) -> T; +``` + +Example of how it is used: + +```rust + +fn main(x: u8, y: u8) -> pub u8 { + std::wrapping_add(x, y) +} +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/references.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/references.md new file mode 100644 index 000000000000..a5293d11cfb9 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/references.md @@ -0,0 +1,23 @@ +--- +title: References +sidebar_position: 9 +--- + +Noir supports first-class references. References are a bit like pointers: they point to a specific address that can be followed to access the data stored at that address. You can use Rust-like syntax to use pointers in Noir: the `&` operator references the variable, the `*` operator dereferences it. + +Example: + +```rust +fn main() { + let mut x = 2; + + // you can reference x as &mut and pass it to multiplyBy2 + multiplyBy2(&mut x); +} + +// you can access &mut here +fn multiplyBy2(x: &mut Field) { + // and dereference it with * + *x = *x * 2; +} +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/slices.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/slices.mdx new file mode 100644 index 000000000000..95da2030843e --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/slices.mdx @@ -0,0 +1,358 @@ +--- +title: Slices +description: Explore the Slice data type in Noir. Understand its methods, see real-world examples, and learn how to effectively use Slices in your Noir programs. +keywords: [noir, slice type, methods, examples, subarrays] +sidebar_position: 5 +--- + +import Experimental from '@site/src/components/Notes/_experimental.mdx'; + + + +A slice is a dynamically-sized view into a sequence of elements. They can be resized at runtime, but because they don't own the data, they cannot be returned from a circuit. You can treat slices as arrays without a constrained size. + +```rust +fn main() -> pub u32 { + let mut slice: [Field] = &[0; 2]; + + let mut new_slice = slice.push_back(6); + new_slice.len() +} +``` + +To write a slice literal, use a preceeding ampersand as in: `&[0; 2]` or +`&[1, 2, 3]`. + +It is important to note that slices are not references to arrays. In Noir, +`&[..]` is more similar to an immutable, growable vector. + +View the corresponding test file [here][test-file]. + +[test-file]: https://github.com/noir-lang/noir/blob/f387ec1475129732f72ba294877efdf6857135ac/crates/nargo_cli/tests/test_data_ssa_refactor/slices/src/main.nr + +## Methods + +For convenience, the STD provides some ready-to-use, common methods for slices: + +### push_back + +Pushes a new element to the end of the slice, returning a new slice with a length one greater than the original unmodified slice. + +```rust +fn push_back(_self: [T], _elem: T) -> [T] +``` + +example: + +```rust +fn main() -> pub Field { + let mut slice: [Field] = &[0; 2]; + + let mut new_slice = slice.push_back(6); + new_slice.len() +} +``` + +View the corresponding test file [here][test-file]. + +### push_front + +Returns a new array with the specified element inserted at index 0. The existing elements indexes are incremented by 1. + +```rust +fn push_front(_self: Self, _elem: T) -> Self +``` + +Example: + +```rust +let mut new_slice: [Field] = &[]; +new_slice = new_slice.push_front(20); +assert(new_slice[0] == 20); // returns true +``` + +View the corresponding test file [here][test-file]. + +### pop_front + +Returns a tuple of two items, the first element of the array and the rest of the array. + +```rust +fn pop_front(_self: Self) -> (T, Self) +``` + +Example: + +```rust +let (first_elem, rest_of_slice) = slice.pop_front(); +``` + +View the corresponding test file [here][test-file]. + +### pop_back + +Returns a tuple of two items, the beginning of the array with the last element omitted and the last element. + +```rust +fn pop_back(_self: Self) -> (Self, T) +``` + +Example: + +```rust +let (popped_slice, last_elem) = slice.pop_back(); +``` + +View the corresponding test file [here][test-file]. + +### append + +Loops over a slice and adds it to the end of another. + +```rust +fn append(mut self, other: Self) -> Self +``` + +Example: + +```rust +let append = &[1, 2].append(&[3, 4, 5]); +``` + +### insert + +Inserts an element at a specified index and shifts all following elements by 1. + +```rust +fn insert(_self: Self, _index: Field, _elem: T) -> Self +``` + +Example: + +```rust +new_slice = rest_of_slice.insert(2, 100); +assert(new_slice[2] == 100); +``` + +View the corresponding test file [here][test-file]. + +### remove + +Remove an element at a specified index, shifting all elements after it to the left, returning the altered slice and the removed element. + +```rust +fn remove(_self: Self, _index: Field) -> (Self, T) +``` + +Example: + +```rust +let (remove_slice, removed_elem) = slice.remove(3); +``` + +### len + +Returns the length of a slice + +```rust +fn len(self) -> Field +``` + +Example: + +```rust +fn main() { + let slice = &[42, 42]; + assert(slice.len() == 2); +} +``` + +### as_array + +Converts this slice into an array. + +Make sure to specify the size of the resulting array. +Panics if the resulting array length is different than the slice's length. + +```rust +fn as_array(self) -> [T; N] +``` + +Example: + +```rust +fn main() { + let slice = &[5, 6]; + + // Always specify the length of the resulting array! + let array: [Field; 2] = slice.as_array(); + + assert(array[0] == slice[0]); + assert(array[1] == slice[1]); +} +``` + +### map + +Applies a function to each element of the slice, returning a new slice containing the mapped elements. + +```rust +fn map(self, f: fn[Env](T) -> U) -> [U] +``` + +example + +```rust +let a = &[1, 2, 3]; +let b = a.map(|a| a * 2); // b is now &[2, 4, 6] +``` + +### fold + +Applies a function to each element of the slice, returning the final accumulated value. The first +parameter is the initial value. + +```rust +fn fold(self, mut accumulator: U, f: fn[Env](U, T) -> U) -> U +``` + +This is a left fold, so the given function will be applied to the accumulator and first element of +the slice, then the second, and so on. For a given call the expected result would be equivalent to: + +```rust +let a1 = &[1]; +let a2 = &[1, 2]; +let a3 = &[1, 2, 3]; + +let f = |a, b| a - b; +a1.fold(10, f) //=> f(10, 1) +a2.fold(10, f) //=> f(f(10, 1), 2) +a3.fold(10, f) //=> f(f(f(10, 1), 2), 3) +``` + +example: + +```rust + +fn main() { + let slice = &[2, 2, 2, 2, 2]; + let folded = slice.fold(0, |a, b| a + b); + assert(folded == 10); +} + +``` + +### reduce + +Same as fold, but uses the first element as the starting element. + +```rust +fn reduce(self, f: fn[Env](T, T) -> T) -> T +``` + +example: + +```rust +fn main() { + let slice = &[2, 2, 2, 2, 2]; + let reduced = slice.reduce(|a, b| a + b); + assert(reduced == 10); +} +``` + +### filter + +Returns a new slice containing only elements for which the given predicate returns true. + +```rust +fn filter(self, f: fn[Env](T) -> bool) -> Self +``` + +example: + +```rust +fn main() { + let slice = &[1, 2, 3, 4, 5]; + let odds = slice.filter(|x| x % 2 == 1); + assert_eq(odds, &[1, 3, 5]); +} +``` + +### join + +Flatten each element in the slice into one value, separated by `separator`. + +Note that although slices implement `Append`, `join` cannot be used on slice +elements since nested slices are prohibited. + +```rust +fn join(self, separator: T) -> T where T: Append +``` + +example: + +```rust +struct Accumulator { + total: Field, +} + +// "Append" two accumulators by adding them +impl Append for Accumulator { + fn empty() -> Self { + Self { total: 0 } + } + + fn append(self, other: Self) -> Self { + Self { total: self.total + other.total } + } +} + +fn main() { + let slice = &[1, 2, 3, 4, 5].map(|total| Accumulator { total }); + + let result = slice.join(Accumulator::empty()); + assert_eq(result, Accumulator { total: 15 }); + + // We can use a non-empty separator to insert additional elements to sum: + let separator = Accumulator { total: 10 }; + let result = slice.join(separator); + assert_eq(result, Accumulator { total: 55 }); +} +``` + +### all + +Returns true if all the elements satisfy the given predicate + +```rust +fn all(self, predicate: fn[Env](T) -> bool) -> bool +``` + +example: + +```rust +fn main() { + let slice = &[2, 2, 2, 2, 2]; + let all = slice.all(|a| a == 2); + assert(all); +} +``` + +### any + +Returns true if any of the elements satisfy the given predicate + +```rust +fn any(self, predicate: fn[Env](T) -> bool) -> bool +``` + +example: + +```rust +fn main() { + let slice = &[2, 2, 2, 2, 5]; + let any = slice.any(|a| a == 5); + assert(any); +} + +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/strings.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/strings.md new file mode 100644 index 000000000000..1fdee42425e5 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/strings.md @@ -0,0 +1,79 @@ +--- +title: Strings +description: + Discover the String data type in Noir. Learn about its methods, see real-world examples, and understand how to effectively manipulate and use Strings in Noir. +keywords: + [ + noir, + string type, + methods, + examples, + concatenation, + ] +sidebar_position: 3 +--- + + +The string type is a fixed length value defined with `str`. + +You can use strings in `assert()` functions or print them with +`println()`. See more about [Logging](../../standard_library/logging.md). + +```rust + +fn main(message : pub str<11>, hex_as_string : str<4>) { + println(message); + assert(message == "hello world"); + assert(hex_as_string == "0x41"); +} +``` + +You can convert a `str` to a byte array by calling `as_bytes()` +or a vector by calling `as_bytes_vec()`. + +```rust +fn main() { + let message = "hello world"; + let message_bytes = message.as_bytes(); + let mut message_vec = message.as_bytes_vec(); + assert(message_bytes.len() == 11); + assert(message_bytes[0] == 104); + assert(message_bytes[0] == message_vec.get(0)); +} +``` + +## Escape characters + +You can use escape characters for your strings: + +| Escape Sequence | Description | +|-----------------|-----------------| +| `\r` | Carriage Return | +| `\n` | Newline | +| `\t` | Tab | +| `\0` | Null Character | +| `\"` | Double Quote | +| `\\` | Backslash | + +Example: + +```rust +let s = "Hello \"world" // prints "Hello "world" +let s = "hey \tyou"; // prints "hey you" +``` + +## Raw strings + +A raw string begins with the letter `r` and is optionally delimited by a number of hashes `#`. + +Escape characters are *not* processed within raw strings. All contents are interpreted literally. + +Example: + +```rust +let s = r"Hello world"; +let s = r#"Simon says "hello world""#; + +// Any number of hashes may be used (>= 1) as long as the string also terminates with the same number of hashes +let s = r#####"One "#, Two "##, Three "###, Four "####, Five will end the string."#####; +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/structs.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/structs.md new file mode 100644 index 000000000000..dbf68c99813c --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/structs.md @@ -0,0 +1,70 @@ +--- +title: Structs +description: + Explore the Struct data type in Noir. Learn about its methods, see real-world examples, and grasp how to effectively define and use Structs in your Noir programs. +keywords: + [ + noir, + struct type, + methods, + examples, + data structures, + ] +sidebar_position: 8 +--- + +A struct also allows for grouping multiple values of different types. Unlike tuples, we can also +name each field. + +> **Note:** The usage of _field_ here refers to each element of the struct and is unrelated to the +> field type of Noir. + +Defining a struct requires giving it a name and listing each field within as `: ` pairs: + +```rust +struct Animal { + hands: Field, + legs: Field, + eyes: u8, +} +``` + +An instance of a struct can then be created with actual values in `: ` pairs in any +order. Struct fields are accessible using their given names: + +```rust +fn main() { + let legs = 4; + + let dog = Animal { + eyes: 2, + hands: 0, + legs, + }; + + let zero = dog.hands; +} +``` + +Structs can also be destructured in a pattern, binding each field to a new variable: + +```rust +fn main() { + let Animal { hands, legs: feet, eyes } = get_octopus(); + + let ten = hands + feet + eyes as u8; +} + +fn get_octopus() -> Animal { + let octopus = Animal { + hands: 0, + legs: 8, + eyes: 2, + }; + + octopus +} +``` + +The new variables can be bound with names different from the original struct field names, as +showcased in the `legs --> feet` binding in the example above. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/tuples.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/tuples.md new file mode 100644 index 000000000000..2ec5c9c41135 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/data_types/tuples.md @@ -0,0 +1,48 @@ +--- +title: Tuples +description: + Dive into the Tuple data type in Noir. Understand its methods, practical examples, and best practices for efficiently using Tuples in your Noir code. +keywords: + [ + noir, + tuple type, + methods, + examples, + multi-value containers, + ] +sidebar_position: 7 +--- + +A tuple collects multiple values like an array, but with the added ability to collect values of +different types: + +```rust +fn main() { + let tup: (u8, u64, Field) = (255, 500, 1000); +} +``` + +One way to access tuple elements is via destructuring using pattern matching: + +```rust +fn main() { + let tup = (1, 2); + + let (one, two) = tup; + + let three = one + two; +} +``` + +Another way to access tuple elements is via direct member access, using a period (`.`) followed by +the index of the element we want to access. Index `0` corresponds to the first tuple element, `1` to +the second and so on: + +```rust +fn main() { + let tup = (5, 6, 7, 8); + + let five = tup.0; + let eight = tup.3; +} +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/functions.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/functions.md new file mode 100644 index 000000000000..f656cdfd97a1 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/functions.md @@ -0,0 +1,226 @@ +--- +title: Functions +description: + Learn how to declare functions and methods in Noir, a programming language with Rust semantics. + This guide covers parameter declaration, return types, call expressions, and more. +keywords: [Noir, Rust, functions, methods, parameter declaration, return types, call expressions] +sidebar_position: 1 +--- + +Functions in Noir follow the same semantics of Rust, though Noir does not support early returns. + +To declare a function the `fn` keyword is used. + +```rust +fn foo() {} +``` + +By default, functions are visible only within the package they are defined. To make them visible outside of that package (for example, as part of a [library](../modules_packages_crates/crates_and_packages.md#libraries)), you should mark them as `pub`: + +```rust +pub fn foo() {} +``` + +You can also restrict the visibility of the function to only the crate it was defined in, by specifying `pub(crate)`: + +```rust +pub(crate) fn foo() {} //foo can only be called within its crate +``` + +All parameters in a function must have a type and all types are known at compile time. The parameter +is pre-pended with a colon and the parameter type. Multiple parameters are separated using a comma. + +```rust +fn foo(x : Field, y : Field){} +``` + +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. + +```rust +fn foo(x : Field, y : Field) -> Field { + x + y +} +``` + +Note that a `return` keyword is unneeded in this case - the last expression in a function's body is +returned. + +## Main function + +If you're writing a binary, the `main` function is the starting point of your program. You can pass all types of expressions to it, as long as they have a fixed size at compile time: + +```rust +fn main(x : Field) // this is fine: passing a Field +fn main(x : [Field; 2]) // this is also fine: passing a Field with known size at compile-time +fn main(x : (Field, bool)) // 👌: passing a (Field, bool) tuple means size 2 +fn main(x : str<5>) // this is fine, as long as you pass a string of size 5 + +fn main(x : Vec) // can't compile, has variable size +fn main(x : [Field]) // can't compile, has variable size +fn main(....// i think you got it by now +``` + +Keep in mind [tests](../../tooling/testing.md) don't differentiate between `main` and any other function. The following snippet passes tests, but won't compile or prove: + +```rust +fn main(x : [Field]) { + assert(x[0] == 1); +} + +#[test] +fn test_one() { + main(&[1, 2]); +} +``` + +```bash +$ nargo test +[testing] Running 1 test functions +[testing] Testing test_one... ok +[testing] All tests passed + +$ nargo check +The application panicked (crashed). +Message: Cannot have variable sized arrays as a parameter to main +``` + +## Call Expressions + +Calling a function in Noir is executed by using the function name and passing in the necessary +arguments. + +Below we show how to call the `foo` function from the `main` function using a call expression: + +```rust +fn main(x : Field, y : Field) { + let z = foo(x); +} + +fn foo(x : Field) -> Field { + x + x +} +``` + +## Methods + +You can define methods in Noir on any struct type in scope. + +```rust +struct MyStruct { + foo: Field, + bar: Field, +} + +impl MyStruct { + fn new(foo: Field) -> MyStruct { + MyStruct { + foo, + bar: 2, + } + } + + fn sum(self) -> Field { + self.foo + self.bar + } +} + +fn main() { + let s = MyStruct::new(40); + assert(s.sum() == 42); +} +``` + +Methods are just syntactic sugar for functions, so if we wanted to we could also call `sum` as +follows: + +```rust +assert(MyStruct::sum(s) == 42); +``` + +It is also possible to specialize which method is chosen depending on the [generic](./generics.md) type that is used. In this example, the `foo` function returns different values depending on its type: + +```rust +struct Foo {} + +impl Foo { + fn foo(self) -> Field { 1 } +} + +impl Foo { + fn foo(self) -> Field { 2 } +} + +fn main() { + let f1: Foo = Foo{}; + let f2: Foo = Foo{}; + assert(f1.foo() + f2.foo() == 3); +} +``` + +Also note that impls with the same method name defined in them cannot overlap. For example, if we already have `foo` defined for `Foo` and `Foo` like we do above, we cannot also define `foo` in an `impl Foo` since it would be ambiguous which version of `foo` to choose. + +```rust +// Including this impl in the same project as the above snippet would +// cause an overlapping impls error +impl Foo { + fn foo(self) -> Field { 3 } +} +``` + +## Lambdas + +Lambdas are anonymous functions. They follow the syntax of Rust - `|arg1, arg2, ..., argN| return_expression`. + +```rust +let add_50 = |val| val + 50; +assert(add_50(100) == 150); +``` + +See [Lambdas](./lambdas.md) for more details. + +## Attributes + +Attributes are metadata that can be applied to a function, using the following syntax: `#[attribute(value)]`. + +Supported attributes include: + +- **builtin**: the function is implemented by the compiler, for efficiency purposes. +- **deprecated**: mark the function as _deprecated_. Calling the function will generate a warning: `warning: use of deprecated function` +- **field**: Used to enable conditional compilation of code depending on the field size. See below for more details +- **oracle**: mark the function as _oracle_; meaning it is an external unconstrained function, implemented in noir_js. See [Unconstrained](./unconstrained.md) and [NoirJS](../../reference/NoirJS/noir_js/index.md) for more details. +- **test**: mark the function as unit tests. See [Tests](../../tooling/testing.md) for more details + +### Field Attribute + +The field attribute defines which field the function is compatible for. The function is conditionally compiled, under the condition that the field attribute matches the Noir native field. +The field can be defined implicitly, by using the name of the elliptic curve usually associated to it - for instance bn254, bls12_381 - or explicitly by using the field (prime) order, in decimal or hexadecimal form. +As a result, it is possible to define multiple versions of a function with each version specialized for a different field attribute. This can be useful when a function requires different parameters depending on the underlying elliptic curve. + +Example: we define the function `foo()` three times below. Once for the default Noir bn254 curve, once for the field $\mathbb F_{23}$, which will normally never be used by Noir, and once again for the bls12_381 curve. + +```rust +#[field(bn254)] +fn foo() -> u32 { + 1 +} + +#[field(23)] +fn foo() -> u32 { + 2 +} + +// This commented code would not compile as foo would be defined twice because it is the same field as bn254 +// #[field(21888242871839275222246405745257275088548364400416034343698204186575808495617)] +// fn foo() -> u32 { +// 2 +// } + +#[field(bls12_381)] +fn foo() -> u32 { + 3 +} +``` + +If the field name is not known to Noir, it will discard the function. Field names are case insensitive. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/generics.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/generics.md new file mode 100644 index 000000000000..3e416eee0936 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/generics.md @@ -0,0 +1,163 @@ +--- +title: Generics +description: Learn how to use Generics in Noir +keywords: [Noir, Rust, generics, functions, structs] +sidebar_position: 7 +--- + +Generics allow you to use the same functions with multiple different concrete data types. You can +read more about the concept of generics in the Rust documentation +[here](https://doc.rust-lang.org/book/ch10-01-syntax.html). + +Here is a trivial example showing the identity function that supports any type. In Rust, it is +common to refer to the most general type as `T`. We follow the same convention in Noir. + +```rust +fn id(x: T) -> T { + x +} +``` + +## In Structs + +Generics are useful for specifying types in structs. For example, we can specify that a field in a +struct will be of a certain generic type. In this case `value` is of type `T`. + +```rust +struct RepeatedValue { + value: T, + count: Field, +} + +impl RepeatedValue { + fn print(self) { + for _i in 0 .. self.count { + println(self.value); + } + } +} + +fn main() { + let repeated = RepeatedValue { value: "Hello!", count: 2 }; + repeated.print(); +} +``` + +The `print` function will print `Hello!` an arbitrary number of times, twice in this case. + +If we want to be generic over array lengths (which are type-level integers), we can use numeric +generics. Using these looks just like using regular generics, but these generics can resolve to +integers at compile-time, rather than resolving to types. Here's an example of a struct that is +generic over the size of the array it contains internally: + +```rust +struct BigInt { + limbs: [u32; N], +} + +impl BigInt { + // `N` is in scope of all methods in the impl + fn first(first: BigInt, second: BigInt) -> Self { + assert(first.limbs != second.limbs); + first + + fn second(first: BigInt, second: Self) -> Self { + assert(first.limbs != second.limbs); + second + } +} +``` + +## Calling functions on generic parameters + +Since a generic type `T` can represent any type, how can we call functions on the underlying type? +In other words, how can we go from "any type `T`" to "any type `T` that has certain methods available?" + +This is what [traits](../concepts/traits.md) are for in Noir. Here's an example of a function generic over +any type `T` that implements the `Eq` trait for equality: + +```rust +fn first_element_is_equal(array1: [T; N], array2: [T; N]) -> bool + where T: Eq +{ + if (array1.len() == 0) | (array2.len() == 0) { + true + } else { + array1[0] == array2[0] + } +} + +fn main() { + assert(first_element_is_equal([1, 2, 3], [1, 5, 6])); + + // We can use first_element_is_equal for arrays of any type + // as long as we have an Eq impl for the types we pass in + let array = [MyStruct::new(), MyStruct::new()]; + assert(array_eq(array, array, MyStruct::eq)); +} + +impl Eq for MyStruct { + fn eq(self, other: MyStruct) -> bool { + self.foo == other.foo + } +} +``` + +You can find more details on traits and trait implementations on the [traits page](../concepts/traits.md). + +## Manually Specifying Generics with the Turbofish Operator + +There are times when the compiler cannot reasonably infer what type should be used for a generic, or when the developer themselves may want to manually distinguish generic type parameters. This is where the `::<>` turbofish operator comes into play. + +The `::<>` operator can follow a variable or path and can be used to manually specify generic arguments within the angle brackets. +The name "turbofish" comes from that `::<>` looks like a little fish. + +Examples: +```rust +fn main() { + let mut slice = []; + slice = slice.push_back(1); + slice = slice.push_back(2); + // Without turbofish a type annotation would be needed on the left hand side + let array = slice.as_array::<2>(); +} +``` +```rust +fn double() -> u32 { + N * 2 +} +fn example() { + assert(double::<9>() == 18); + assert(double::<7 + 8>() == 30); +} +``` +```rust +trait MyTrait { + fn ten() -> Self; +} + +impl MyTrait for Field { + fn ten() -> Self { 10 } +} + +struct Foo { + inner: T +} + +impl Foo { + fn generic_method(_self: Self) -> U where U: MyTrait { + U::ten() + } +} + +fn example() { + let foo: Foo = Foo { inner: 1 }; + // Using a type other than `Field` here (e.g. u32) would fail as + // there is no matching impl for `u32: MyTrait`. + // + // Substituting the `10` on the left hand side of this assert + // with `10 as u32` would also fail with a type mismatch as we + // are expecting a `Field` from the right hand side. + assert(10 as u32 == foo.generic_method::()); +} +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/globals.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/globals.md new file mode 100644 index 000000000000..063a3d89248d --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/globals.md @@ -0,0 +1,72 @@ +--- +title: Global Variables +description: + Learn about global variables in Noir. Discover how + to declare, modify, and use them in your programs. +keywords: [noir programming language, globals, global variables, constants] +sidebar_position: 8 +--- + +## Globals + + +Noir supports global variables. The global's type can be inferred by the compiler entirely: + +```rust +global N = 5; // Same as `global N: Field = 5` + +global TUPLE = (3, 2); + +fn main() { + assert(N == 5); + assert(N == TUPLE.0 + TUPLE.1); +} +``` + +:::info + +Globals can be defined as any expression, so long as they don't depend on themselves - otherwise there would be a dependency cycle! For example: + +```rust +global T = foo(T); // dependency error +``` + +::: + + +If they are initialized to a literal integer, globals can be used to specify an array's length: + +```rust +global N: Field = 2; + +fn main(y : [Field; N]) { + assert(y[0] == y[1]) +} +``` + +A global from another module can be imported or referenced externally like any other name: + +```rust +global N = 20; + +fn main() { + assert(my_submodule::N != N); +} + +mod my_submodule { + global N: Field = 10; +} +``` + +When a global is used, Noir replaces the name with its definition on each occurrence. +This means globals defined using function calls will repeat the call each time they're used: + +```rust +global RESULT = foo(); + +fn foo() -> [Field; 100] { ... } +``` + +This is usually fine since Noir will generally optimize any function call that does not +refer to a program input into a constant. It should be kept in mind however, if the called +function performs side-effects like `println`, as these will still occur on each use. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/lambdas.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/lambdas.md new file mode 100644 index 000000000000..be3c7e0b5caa --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/lambdas.md @@ -0,0 +1,81 @@ +--- +title: Lambdas +description: Learn how to use anonymous functions in Noir programming language. +keywords: [Noir programming language, lambda, closure, function, anonymous function] +sidebar_position: 9 +--- + +## Introduction + +Lambdas are anonymous functions. The syntax is `|arg1, arg2, ..., argN| return_expression`. + +```rust +let add_50 = |val| val + 50; +assert(add_50(100) == 150); +``` + +A block can be used as the body of a lambda, allowing you to declare local variables inside it: + +```rust +let cool = || { + let x = 100; + let y = 100; + x + y +} + +assert(cool() == 200); +``` + +## Closures + +Inside the body of a lambda, you can use variables defined in the enclosing function. Such lambdas are called **closures**. In this example `x` is defined inside `main` and is accessed from within the lambda: + +```rust +fn main() { + let x = 100; + let closure = || x + 150; + assert(closure() == 250); +} +``` + +## Passing closures to higher-order functions + +It may catch you by surprise that the following code fails to compile: + +```rust +fn foo(f: fn () -> Field) -> Field { + f() +} + +fn main() { + let (x, y) = (50, 50); + assert(foo(|| x + y) == 100); // error :( +} +``` + +The reason is that the closure's capture environment affects its type - we have a closure that captures two Fields and `foo` +expects a regular function as an argument - those are incompatible. +:::note + +Variables contained within the `||` are the closure's parameters, and the expression that follows it is the closure's body. The capture environment is comprised of any variables used in the closure's body that are not parameters. + +E.g. in |x| x + y, y would be a captured variable, but x would not be, since it is a parameter of the closure. + +::: +The syntax for the type of a closure is `fn[env](args) -> ret_type`, where `env` is the capture environment of the closure - +in this example that's `(Field, Field)`. + +The best solution in our case is to make `foo` generic over the environment type of its parameter, so that it can be called +with closures with any environment, as well as with regular functions: + +```rust +fn foo(f: fn[Env]() -> Field) -> Field { + f() +} + +fn main() { + let (x, y) = (50, 50); + assert(foo(|| x + y) == 100); // compiles fine + assert(foo(|| 60) == 60); // compiles fine +} +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/mutability.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/mutability.md new file mode 100644 index 000000000000..fdeef6a87c53 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/mutability.md @@ -0,0 +1,121 @@ +--- +title: Mutability +description: + Learn about mutable variables in Noir. Discover how + to declare, modify, and use them in your programs. +keywords: [noir programming language, mutability in noir, mutable variables] +sidebar_position: 8 +--- + +Variables in noir can be declared mutable via the `mut` keyword. Mutable variables can be reassigned +to via an assignment expression. + +```rust +let x = 2; +x = 3; // error: x must be mutable to be assigned to + +let mut y = 3; +let y = 4; // OK +``` + +The `mut` modifier can also apply to patterns: + +```rust +let (a, mut b) = (1, 2); +a = 11; // error: a must be mutable to be assigned to +b = 12; // OK + +let mut (c, d) = (3, 4); +c = 13; // OK +d = 14; // OK + +// etc. +let MyStruct { x: mut y } = MyStruct { x: a }; +// y is now in scope +``` + +Note that mutability in noir is local and everything is passed by value, so if a called function +mutates its parameters then the parent function will keep the old value of the parameters. + +```rust +fn main() -> pub Field { + let x = 3; + helper(x); + x // x is still 3 +} + +fn helper(mut x: i32) { + x = 4; +} +``` + +## Non-local mutability + +Non-local mutability can be achieved through the mutable reference type `&mut T`: + +```rust +fn set_to_zero(x: &mut Field) { + *x = 0; +} + +fn main() { + let mut y = 42; + set_to_zero(&mut y); + assert(*y == 0); +} +``` + +When creating a mutable reference, the original variable being referred to (`y` in this +example) must also be mutable. Since mutable references are a reference type, they must +be explicitly dereferenced via `*` to retrieve the underlying value. Note that this yields +a copy of the value, so mutating this copy will not change the original value behind the +reference: + +```rust +fn main() { + let mut x = 1; + let x_ref = &mut x; + + let mut y = *x_ref; + let y_ref = &mut y; + + x = 2; + *x_ref = 3; + + y = 4; + *y_ref = 5; + + assert(x == 3); + assert(*x_ref == 3); + assert(y == 5); + assert(*y_ref == 5); +} +``` + +Note that types in Noir are actually deeply immutable so the copy that occurs when +dereferencing is only a conceptual copy - no additional constraints will occur. + +Mutable references can also be stored within structs. Note that there is also +no lifetime parameter on these unlike rust. This is because the allocated memory +always lasts the entire program - as if it were an array of one element. + +```rust +struct Foo { + x: &mut Field +} + +impl Foo { + fn incr(mut self) { + *self.x += 1; + } +} + +fn main() { + let foo = Foo { x: &mut 0 }; + foo.incr(); + assert(*foo.x == 1); +} +``` + +In general, you should avoid non-local & shared mutability unless it is needed. Sticking +to only local mutability will improve readability and potentially improve compiler optimizations as well. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/ops.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/ops.md new file mode 100644 index 000000000000..c35c36c38a90 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/ops.md @@ -0,0 +1,98 @@ +--- +title: Logical Operations +description: + Learn about the supported arithmetic and logical operations in the Noir programming language. + Discover how to perform operations on private input types, integers, and booleans. +keywords: + [ + Noir programming language, + supported operations, + arithmetic operations, + logical operations, + predicate operators, + bitwise operations, + short-circuiting, + backend, + ] +sidebar_position: 3 +--- + +# Operations + +## Table of Supported Operations + +| Operation | Description | Requirements | +| :-------- | :------------------------------------------------------------: | -------------------------------------: | +| + | Adds two private input types together | Types must be private input | +| - | Subtracts two private input types together | Types must be private input | +| \* | Multiplies two private input types together | Types must be private input | +| / | Divides two private input types together | Types must be private input | +| ^ | XOR two private input types together | Types must be integer | +| & | AND two private input types together | Types must be integer | +| \| | OR two private input types together | Types must be integer | +| \<\< | Left shift an integer by another integer amount | Types must be integer, shift must be u8 | +| >> | Right shift an integer by another integer amount | Types must be integer, shift must be u8 | +| ! | Bitwise not of a value | Type must be integer or boolean | +| \< | returns a bool if one value is less than the other | Upper bound must have a known bit size | +| \<= | returns a bool if one value is less than or equal to the other | Upper bound must have a known bit size | +| > | returns a bool if one value is more than the other | Upper bound must have a known bit size | +| >= | returns a bool if one value is more than or equal to the other | Upper bound must have a known bit size | +| == | returns a bool if one value is equal to the other | Both types must not be constants | +| != | returns a bool if one value is not equal to the other | Both types must not be constants | + +### Predicate Operators + +`<,<=, !=, == , >, >=` are known as predicate/comparison operations because they compare two values. +This differs from the operations such as `+` where the operands are used in _computation_. + +### Bitwise Operations Example + +```rust +fn main(x : Field) { + let y = x as u32; + let z = y & y; +} +``` + +`z` is implicitly constrained to be the result of `y & y`. The `&` operand is used to denote bitwise +`&`. + +> `x & x` would not compile as `x` is a `Field` and not an integer type. + +### Logical Operators + +Noir has no support for the logical operators `||` and `&&`. This is because encoding the +short-circuiting that these operators require can be inefficient for Noir's backend. Instead you can +use the bitwise operators `|` and `&` which operate identically for booleans, just without the +short-circuiting. + +```rust +let my_val = 5; + +let mut flag = 1; +if (my_val > 6) | (my_val == 0) { + flag = 0; +} +assert(flag == 1); + +if (my_val != 10) & (my_val < 50) { + flag = 0; +} +assert(flag == 0); +``` + +### Shorthand operators + +Noir shorthand operators for most of the above operators, namely `+=, -=, *=, /=, %=, &=, |=, ^=, <<=`, and `>>=`. These allow for more concise syntax. For example: + +```rust +let mut i = 0; +i = i + 1; +``` + +could be written as: + +```rust +let mut i = 0; +i += 1; +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/oracles.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/oracles.mdx new file mode 100644 index 000000000000..77a2ac1550ac --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/oracles.mdx @@ -0,0 +1,29 @@ +--- +title: Oracles +description: Dive into how Noir supports Oracles via RPC calls, and learn how to declare an Oracle in Noir with our comprehensive guide. +keywords: + - Noir + - Oracles + - RPC Calls + - Unconstrained Functions + - Programming + - Blockchain +sidebar_position: 6 +--- + +import Experimental from '@site/src/components/Notes/_experimental.mdx'; + + + +Noir has support for Oracles via RPC calls. This means Noir will make an RPC call and use the return value for proof generation. + +Since Oracles are not resolved by Noir, they are [`unconstrained` functions](./unconstrained.md) + +You can declare an Oracle through the `#[oracle()]` flag. Example: + +```rust +#[oracle(get_number_sequence)] +unconstrained fn get_number_sequence(_size: Field) -> [Field] {} +``` + +The timeout for when using an external RPC oracle resolver can be set with the `NARGO_FOREIGN_CALL_TIMEOUT` environment variable. This timeout is in units of milliseconds. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/shadowing.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/shadowing.md new file mode 100644 index 000000000000..5ce6130d2011 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/shadowing.md @@ -0,0 +1,44 @@ +--- +title: Shadowing +sidebar_position: 12 +--- + +Noir allows for inheriting variables' values and re-declaring them with the same name similar to Rust, known as shadowing. + +For example, the following function is valid in Noir: + +```rust +fn main() { + let x = 5; + + { + let x = x * 2; + assert (x == 10); + } + + assert (x == 5); +} +``` + +In this example, a variable x is first defined with the value 5. + +The local scope that follows shadows the original x, i.e. creates a local mutable x based on the value of the original x. It is given a value of 2 times the original x. + +When we return to the main scope, x once again refers to just the original x, which stays at the value of 5. + +## Temporal mutability + +One way that shadowing is useful, in addition to ergonomics across scopes, is for temporarily mutating variables. + +```rust +fn main() { + let age = 30; + // age = age + 5; // Would error as `age` is immutable by default. + + let mut age = age + 5; // Temporarily mutates `age` with a new value. + + let age = age; // Locks `age`'s mutability again. + + assert (age == 35); +} +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/traits.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/traits.md new file mode 100644 index 000000000000..51305b38c16f --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/traits.md @@ -0,0 +1,405 @@ +--- +title: Traits +description: + Traits in Noir can be used to abstract out a common interface for functions across + several data types. +keywords: [noir programming language, traits, interfaces, generic, protocol] +sidebar_position: 14 +--- + +## Overview + +Traits in Noir are a useful abstraction similar to interfaces or protocols in other languages. Each trait defines +the interface of several methods contained within the trait. Types can then implement this trait by providing +implementations for these methods. For example in the program: + +```rust +struct Rectangle { + width: Field, + height: Field, +} + +impl Rectangle { + fn area(self) -> Field { + self.width * self.height + } +} + +fn log_area(r: Rectangle) { + println(r.area()); +} +``` + +We have a function `log_area` to log the area of a `Rectangle`. Now how should we change the program if we want this +function to work on `Triangle`s as well?: + +```rust +struct Triangle { + width: Field, + height: Field, +} + +impl Triangle { + fn area(self) -> Field { + self.width * self.height / 2 + } +} +``` + +Making `log_area` generic over all types `T` would be invalid since not all types have an `area` method. Instead, we can +introduce a new `Area` trait and make `log_area` generic over all types `T` that implement `Area`: + +```rust +trait Area { + fn area(self) -> Field; +} + +fn log_area(shape: T) where T: Area { + println(shape.area()); +} +``` + +We also need to explicitly implement `Area` for `Rectangle` and `Triangle`. We can do that by changing their existing +impls slightly. Note that the parameter types and return type of each of our `area` methods must match those defined +by the `Area` trait. + +```rust +impl Area for Rectangle { + fn area(self) -> Field { + self.width * self.height + } +} + +impl Area for Triangle { + fn area(self) -> Field { + self.width * self.height / 2 + } +} +``` + +Now we have a working program that is generic over any type of Shape that is used! Others can even use this program +as a library with their own types - such as `Circle` - as long as they also implement `Area` for these types. + +## Where Clauses + +As seen in `log_area` above, when we want to create a function or method that is generic over any type that implements +a trait, we can add a where clause to the generic function. + +```rust +fn log_area(shape: T) where T: Area { + println(shape.area()); +} +``` + +It is also possible to apply multiple trait constraints on the same variable at once by combining traits with the `+` +operator. Similarly, we can have multiple trait constraints by separating each with a comma: + +```rust +fn foo(elements: [T], thing: U) where + T: Default + Add + Eq, + U: Bar, +{ + let mut sum = T::default(); + + for element in elements { + sum += element; + } + + if sum == T::default() { + thing.bar(); + } +} +``` + +## Generic Implementations + +You can add generics to a trait implementation by adding the generic list after the `impl` keyword: + +```rust +trait Second { + fn second(self) -> Field; +} + +impl Second for (T, Field) { + fn second(self) -> Field { + self.1 + } +} +``` + +You can also implement a trait for every type this way: + +```rust +trait Debug { + fn debug(self); +} + +impl Debug for T { + fn debug(self) { + println(self); + } +} + +fn main() { + 1.debug(); +} +``` + +### Generic Trait Implementations With Where Clauses + +Where clauses can be placed on trait implementations themselves to restrict generics in a similar way. +For example, while `impl Foo for T` implements the trait `Foo` for every type, `impl Foo for T where T: Bar` +will implement `Foo` only for types that also implement `Bar`. This is often used for implementing generic types. +For example, here is the implementation for array equality: + +```rust +impl Eq for [T; N] where T: Eq { + // Test if two arrays have the same elements. + // Because both arrays must have length N, we know their lengths already match. + fn eq(self, other: Self) -> bool { + let mut result = true; + + for i in 0 .. self.len() { + // The T: Eq constraint is needed to call == on the array elements here + result &= self[i] == other[i]; + } + + result + } +} +``` + +Where clauses can also be placed on struct implementations. +For example, here is a method utilizing a generic type that implements the equality trait. + +```rust +struct Foo { + a: u32, + b: T, +} + +impl Foo where T: Eq { + fn eq(self, other: Self) -> bool { + (self.a == other.a) & self.b.eq(other.b) + } +} +``` + +## Generic Traits + +Traits themselves can also be generic by placing the generic arguments after the trait name. These generics are in +scope of every item within the trait. + +```rust +trait Into { + // Convert `self` to type `T` + fn into(self) -> T; +} +``` + +When implementing generic traits the generic arguments of the trait must be specified. This is also true anytime +when referencing a generic trait (e.g. in a `where` clause). + +```rust +struct MyStruct { + array: [Field; 2], +} + +impl Into<[Field; 2]> for MyStruct { + fn into(self) -> [Field; 2] { + self.array + } +} + +fn as_array(x: T) -> [Field; 2] + where T: Into<[Field; 2]> +{ + x.into() +} + +fn main() { + let array = [1, 2]; + let my_struct = MyStruct { array }; + + assert_eq(as_array(my_struct), array); +} +``` + +## Trait Methods With No `self` + +A trait can contain any number of methods, each of which have access to the `Self` type which represents each type +that eventually implements the trait. Similarly, the `self` variable is available as well but is not required to be used. +For example, we can define a trait to create a default value for a type. This trait will need to return the `Self` type +but doesn't need to take any parameters: + +```rust +trait Default { + fn default() -> Self; +} +``` + +Implementing this trait can be done similarly to any other trait: + +```rust +impl Default for Field { + fn default() -> Field { + 0 + } +} + +struct MyType {} + +impl Default for MyType { + fn default() -> Field { + MyType {} + } +} +``` + +However, since there is no `self` parameter, we cannot call it via the method call syntax `object.method()`. +Instead, we'll need to refer to the function directly. This can be done either by referring to the +specific impl `MyType::default()` or referring to the trait itself `Default::default()`. In the later +case, type inference determines the impl that is selected. + +```rust +let my_struct = MyStruct::default(); + +let x: Field = Default::default(); +let result = x + Default::default(); +``` + +:::warning + +```rust +let _ = Default::default(); +``` + +If type inference cannot select which impl to use because of an ambiguous `Self` type, an impl will be +arbitrarily selected. This occurs most often when the result of a trait function call with no parameters +is unused. To avoid this, when calling a trait function with no `self` or `Self` parameters or return type, +always refer to it via the implementation type's namespace - e.g. `MyType::default()`. +This is set to change to an error in future Noir versions. + +::: + +## Default Method Implementations + +A trait can also have default implementations of its methods by giving a body to the desired functions. +Note that this body must be valid for all types that may implement the trait. As a result, the only +valid operations on `self` will be operations valid for any type or other operations on the trait itself. + +```rust +trait Numeric { + fn add(self, other: Self) -> Self; + + // Default implementation of double is (self + self) + fn double(self) -> Self { + self.add(self) + } +} +``` + +When implementing a trait with default functions, a type may choose to implement only the required functions: + +```rust +impl Numeric for Field { + fn add(self, other: Field) -> Field { + self + other + } +} +``` + +Or it may implement the optional methods as well: + +```rust +impl Numeric for u32 { + fn add(self, other: u32) -> u32 { + self + other + } + + fn double(self) -> u32 { + self * 2 + } +} +``` + +## Impl Specialization + +When implementing traits for a generic type it is possible to implement the trait for only a certain combination +of generics. This can be either as an optimization or because those specific generics are required to implement the trait. + +```rust +trait Sub { + fn sub(self, other: Self) -> Self; +} + +struct NonZero { + value: T, +} + +impl Sub for NonZero { + fn sub(self, other: Self) -> Self { + let value = self.value - other.value; + assert(value != 0); + NonZero { value } + } +} +``` + +## Overlapping Implementations + +Overlapping implementations are disallowed by Noir to ensure Noir's decision on which impl to select is never ambiguous. +This means if a trait `Foo` is already implemented +by a type `Bar` for all `T`, then we cannot also have a separate impl for `Bar` (or any other +type argument). Similarly, if there is an impl for all `T` such as `impl Debug for T`, we cannot create +any more impls to `Debug` for other types since it would be ambiguous which impl to choose for any given +method call. + +```rust +trait Trait {} + +// Previous impl defined here +impl Trait for (A, B) {} + +// error: Impl for type `(Field, Field)` overlaps with existing impl +impl Trait for (Field, Field) {} +``` + +## Trait Coherence + +Another restriction on trait implementations is coherence. This restriction ensures other crates cannot create +impls that may overlap with other impls, even if several unrelated crates are used as dependencies in the same +program. + +The coherence restriction is: to implement a trait, either the trait itself or the object type must be declared +in the crate the impl is in. + +In practice this often comes up when using types provided by libraries. If a library provides a type `Foo` that does +not implement a trait in the standard library such as `Default`, you may not `impl Default for Foo` in your own crate. +While restrictive, this prevents later issues or silent changes in the program if the `Foo` library later added its +own impl for `Default`. If you are a user of the `Foo` library in this scenario and need a trait not implemented by the +library your choices are to either submit a patch to the library or use the newtype pattern. + +### The Newtype Pattern + +The newtype pattern gets around the coherence restriction by creating a new wrapper type around the library type +that we cannot create `impl`s for. Since the new wrapper type is defined in our current crate, we can create +impls for any trait we need on it. + +```rust +struct Wrapper { + foo: some_library::Foo, +} + +impl Default for Wrapper { + fn default() -> Wrapper { + Wrapper { + foo: some_library::Foo::new(), + } + } +} +``` + +Since we have an impl for our own type, the behavior of this code will not change even if `some_library` is updated +to provide its own `impl Default for Foo`. The downside of this pattern is that it requires extra wrapping and +unwrapping of values when converting to and from the `Wrapper` and `Foo` types. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/unconstrained.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/unconstrained.md new file mode 100644 index 000000000000..96f824c5e425 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/concepts/unconstrained.md @@ -0,0 +1,99 @@ +--- +title: Unconstrained Functions +description: "Learn about what unconstrained functions in Noir are, how to use them and when you'd want to." + +keywords: [Noir programming language, unconstrained, open] +sidebar_position: 5 +--- + +Unconstrained functions are functions which do not constrain any of the included computation and allow for non-deterministic computation. + +## Why? + +Zero-knowledge (ZK) domain-specific languages (DSL) enable developers to generate ZK proofs from their programs by compiling code down to the constraints of an NP complete language (such as R1CS or PLONKish languages). However, the hard bounds of a constraint system can be very limiting to the functionality of a ZK DSL. + +Enabling a circuit language to perform unconstrained execution is a powerful tool. Said another way, unconstrained execution lets developers generate witnesses from code that does not generate any constraints. Being able to execute logic outside of a circuit is critical for both circuit performance and constructing proofs on information that is external to a circuit. + +Fetching information from somewhere external to a circuit can also be used to enable developers to improve circuit efficiency. + +A ZK DSL does not just prove computation, but proves that some computation was handled correctly. Thus, it is necessary that when we switch from performing some operation directly inside of a circuit to inside of an unconstrained environment that the appropriate constraints are still laid down elsewhere in the circuit. + +## Example + +An in depth example might help drive the point home. This example comes from the excellent [post](https://discord.com/channels/1113924620781883405/1124022445054111926/1128747641853972590) by Tom in the Noir Discord. + +Let's look at how we can optimize a function to turn a `u72` into an array of `u8`s. + +```rust +fn main(num: u72) -> pub [u8; 8] { + let mut out: [u8; 8] = [0; 8]; + for i in 0..8 { + out[i] = (num >> (56 - (i * 8)) as u72 & 0xff) as u8; + } + + out +} +``` + +``` +Total ACIR opcodes generated for language PLONKCSat { width: 3 }: 91 +Backend circuit size: 3619 +``` + +A lot of the operations in this function are optimized away by the compiler (all the bit-shifts turn into divisions by constants). However we can save a bunch of gates by casting to u8 a bit earlier. This automatically truncates the bit-shifted value to fit in a u8 which allows us to remove the AND against 0xff. This saves us ~480 gates in total. + +```rust +fn main(num: u72) -> pub [u8; 8] { + let mut out: [u8; 8] = [0; 8]; + for i in 0..8 { + out[i] = (num >> (56 - (i * 8)) as u8; + } + + out +} +``` + +``` +Total ACIR opcodes generated for language PLONKCSat { width: 3 }: 75 +Backend circuit size: 3143 +``` + +Those are some nice savings already but we can do better. This code is all constrained so we're proving every step of calculating out using num, but we don't actually care about how we calculate this, just that it's correct. This is where brillig comes in. + +It turns out that truncating a u72 into a u8 is hard to do inside a snark, each time we do as u8 we lay down 4 ACIR opcodes which get converted into multiple gates. It's actually much easier to calculate num from out than the other way around. All we need to do is multiply each element of out by a constant and add them all together, both relatively easy operations inside a snark. + +We can then run u72_to_u8 as unconstrained brillig code in order to calculate out, then use that result in our constrained function and assert that if we were to do the reverse calculation we'd get back num. This looks a little like the below: + +```rust +fn main(num: u72) -> pub [u8; 8] { + let out = u72_to_u8(num); + + let mut reconstructed_num: u72 = 0; + for i in 0..8 { + reconstructed_num += (out[i] as u72 << (56 - (8 * i))); + } + assert(num == reconstructed_num); + out +} + +unconstrained fn u72_to_u8(num: u72) -> [u8; 8] { + let mut out: [u8; 8] = [0; 8]; + for i in 0..8 { + out[i] = (num >> (56 - (i * 8))) as u8; + } + out +} +``` + +``` +Total ACIR opcodes generated for language PLONKCSat { width: 3 }: 78 +Backend circuit size: 2902 +``` + +This ends up taking off another ~250 gates from our circuit! We've ended up with more ACIR opcodes than before but they're easier for the backend to prove (resulting in fewer gates). + +Generally we want to use brillig whenever there's something that's easy to verify but hard to compute within the circuit. For example, if you wanted to calculate a square root of a number it'll be a much better idea to calculate this in brillig and then assert that if you square the result you get back your number. + +## Break and Continue + +In addition to loops over runtime bounds, `break` and `continue` are also available in unconstrained code. See [break and continue](../concepts/control_flow.md#break-and-continue) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/modules_packages_crates/_category_.json b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/modules_packages_crates/_category_.json new file mode 100644 index 000000000000..1debcfe76753 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/modules_packages_crates/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Modules, Packages and Crates", + "position": 2, + "collapsible": true, + "collapsed": true +} diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/modules_packages_crates/crates_and_packages.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/modules_packages_crates/crates_and_packages.md new file mode 100644 index 000000000000..95ee9f52ab21 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/modules_packages_crates/crates_and_packages.md @@ -0,0 +1,43 @@ +--- +title: Crates and Packages +description: Learn how to use Crates and Packages in your Noir project +keywords: [Nargo, dependencies, package management, crates, package] +sidebar_position: 0 +--- + +## Crates + +A crate is the smallest amount of code that the Noir compiler considers at a time. +Crates can contain modules, and the modules may be defined in other files that get compiled with the crate, as we’ll see in the coming sections. + +### Crate Types + +A Noir crate can come in several forms: binaries, libraries or contracts. + +#### Binaries + +_Binary crates_ are programs which you can compile to an ACIR circuit which you can then create proofs against. Each must have a function called `main` that defines the ACIR circuit which is to be proved. + +#### Libraries + +_Library crates_ don't have a `main` function and they don't compile down to ACIR. Instead they define functionality intended to be shared with multiple projects, and eventually included in a binary crate. + +#### Contracts + +Contract crates are similar to binary crates in that they compile to ACIR which you can create proofs against. They are different in that they do not have a single `main` function, but are a collection of functions to be deployed to the [Aztec network](https://aztec.network). You can learn more about the technical details of Aztec in the [monorepo](https://github.com/AztecProtocol/aztec-packages) or contract [examples](https://github.com/AztecProtocol/aztec-packages/tree/master/noir-projects/noir-contracts/contracts). + +### Crate Root + +Every crate has a root, which is the source file that the compiler starts, this is also known as the root module. The Noir compiler does not enforce any conditions on the name of the file which is the crate root, however if you are compiling via Nargo the crate root must be called `lib.nr` or `main.nr` for library or binary crates respectively. + +## Packages + +A Nargo _package_ is a collection of one of more crates that provides a set of functionality. A package must include a Nargo.toml file. + +A package _must_ contain either a library or a binary crate, but not both. + +### Differences from Cargo Packages + +One notable difference between Rust's Cargo and Noir's Nargo is that while Cargo allows a package to contain an unlimited number of binary crates and a single library crate, Nargo currently only allows a package to contain a single crate. + +In future this restriction may be lifted to allow a Nargo package to contain both a binary and library crate or multiple binary crates. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/modules_packages_crates/dependencies.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/modules_packages_crates/dependencies.md new file mode 100644 index 000000000000..24e02de08fe8 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/modules_packages_crates/dependencies.md @@ -0,0 +1,124 @@ +--- +title: Dependencies +description: + Learn how to specify and manage dependencies in Nargo, allowing you to upload packages to GitHub + and use them easily in your project. +keywords: [Nargo, dependencies, GitHub, package management, versioning] +sidebar_position: 1 +--- + +Nargo allows you to upload packages to GitHub and use them as dependencies. + +## Specifying a dependency + +Specifying a dependency requires a tag to a specific commit and the git url to the url containing +the package. + +Currently, there are no requirements on the tag contents. If requirements are added, it would follow +semver 2.0 guidelines. + +> Note: Without a `tag` , there would be no versioning and dependencies would change each time you +> compile your project. + +For example, to add the [ecrecover-noir library](https://github.com/colinnielsen/ecrecover-noir) to your project, add it to `Nargo.toml`: + +```toml +# Nargo.toml + +[dependencies] +ecrecover = {tag = "v0.8.0", git = "https://github.com/colinnielsen/ecrecover-noir"} +``` + +If the module is in a subdirectory, you can define a subdirectory in your git repository, for example: + +```toml +# Nargo.toml + +[dependencies] +easy_private_token_contract = {tag ="v0.1.0-alpha62", git = "https://github.com/AztecProtocol/aztec-packages", directory = "noir-contracts/contracts/easy_private_token_contract"} +``` + +## Specifying a local dependency + +You can also specify dependencies that are local to your machine. + +For example, this file structure has a library and binary crate + +```tree +├── binary_crate +│   ├── Nargo.toml +│   └── src +│   └── main.nr +└── lib_a + ├── Nargo.toml + └── src + └── lib.nr +``` + +Inside of the binary crate, you can specify: + +```toml +# Nargo.toml + +[dependencies] +lib_a = { path = "../lib_a" } +``` + +## Importing dependencies + +You can import a dependency to a Noir file using the following syntax. For example, to import the +ecrecover-noir library and local lib_a referenced above: + +```rust +use ecrecover; +use lib_a; +``` + +You can also import only the specific parts of dependency that you want to use, like so: + +```rust +use std::hash::sha256; +use std::scalar_mul::fixed_base_embedded_curve; +``` + +Lastly, as demonstrated in the +[elliptic curve example](../standard_library/cryptographic_primitives/ec_primitives.md#examples), you +can import multiple items in the same line by enclosing them in curly braces: + +```rust +use std::ec::tecurve::affine::{Curve, Point}; +``` + +We don't have a way to consume libraries from inside a [workspace](./workspaces.md) as external dependencies right now. + +Inside a workspace, these are consumed as `{ path = "../to_lib" }` dependencies in Nargo.toml. + +## Dependencies of Dependencies + +Note that when you import a dependency, you also get access to all of the dependencies of that package. + +For example, the [phy_vector](https://github.com/resurgencelabs/phy_vector) library imports an [fraction](https://github.com/resurgencelabs/fraction) library. If you're importing the phy_vector library, then you can access the functions in fractions library like so: + +```rust +use phy_vector; + +fn main(x : Field, y : pub Field) { + //... + let f = phy_vector::fraction::toFraction(true, 2, 1); + //... +} +``` + +## Available Libraries + +Noir does not currently have an official package manager. You can find a list of available Noir libraries in the [awesome-noir repo here](https://github.com/noir-lang/awesome-noir#libraries). + +Some libraries that are available today include: + +- [Standard Library](https://github.com/noir-lang/noir/tree/master/noir_stdlib) - the Noir Standard Library +- [Ethereum Storage Proof Verification](https://github.com/aragonzkresearch/noir-trie-proofs) - a library that contains the primitives necessary for RLP decoding (in the form of look-up table construction) and Ethereum state and storage proof verification (or verification of any trie proof involving 32-byte long keys) +- [BigInt](https://github.com/shuklaayush/noir-bigint) - a library that provides a custom BigUint56 data type, allowing for computations on large unsigned integers +- [ECrecover](https://github.com/colinnielsen/ecrecover-noir/tree/main) - a library to verify an ECDSA signature and return the source Ethereum address +- [Sparse Merkle Tree Verifier](https://github.com/vocdoni/smtverifier-noir/tree/main) - a library for verification of sparse Merkle trees +- [Signed Int](https://github.com/resurgencelabs/signed_int) - a library for accessing a custom Signed Integer data type, allowing access to negative numbers on Noir +- [Fraction](https://github.com/resurgencelabs/fraction) - a library for accessing fractional number data type in Noir, allowing results that aren't whole numbers diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/modules_packages_crates/modules.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/modules_packages_crates/modules.md new file mode 100644 index 000000000000..16b6307d2fdd --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/modules_packages_crates/modules.md @@ -0,0 +1,185 @@ +--- +title: Modules +description: + Learn how to organize your files using modules in Noir, following the same convention as Rust's + module system. Examples included. +keywords: [Noir, Rust, modules, organizing files, sub-modules] +sidebar_position: 2 +--- + +Noir's module system follows the same convention as the _newer_ version of Rust's module system. + +## Purpose of Modules + +Modules are used to organize files. Without modules all of your code would need to live in a single +file. In Noir, the compiler does not automatically scan all of your files to detect modules. This +must be done explicitly by the developer. + +## Examples + +### Importing a module in the crate root + +Filename : `src/main.nr` + +```rust +mod foo; + +fn main() { + foo::hello_world(); +} +``` + +Filename : `src/foo.nr` + +```rust +fn from_foo() {} +``` + +In the above snippet, the crate root is the `src/main.nr` file. The compiler sees the module +declaration `mod foo` which prompts it to look for a foo.nr file. + +Visually this module hierarchy looks like the following : + +``` +crate + ├── main + │ + └── foo + └── from_foo + +``` + +The module filename may also be the name of the module as a directory with the contents in a +file named `mod.nr` within that directory. The above example can alternatively be expressed like this: + +Filename : `src/main.nr` + +```rust +mod foo; + +fn main() { + foo::hello_world(); +} +``` + +Filename : `src/foo/mod.nr` + +```rust +fn from_foo() {} +``` + +Note that it's an error to have both files `src/foo.nr` and `src/foo/mod.nr` in the filesystem. + +### Importing a module throughout the tree + +All modules are accessible from the `crate::` namespace. + +``` +crate + ├── bar + ├── foo + └── main + +``` + +In the above snippet, if `bar` would like to use functions in `foo`, it can do so by `use crate::foo::function_name`. + +### Sub-modules + +Filename : `src/main.nr` + +```rust +mod foo; + +fn main() { + foo::from_foo(); +} +``` + +Filename : `src/foo.nr` + +```rust +mod bar; +fn from_foo() {} +``` + +Filename : `src/foo/bar.nr` + +```rust +fn from_bar() {} +``` + +In the above snippet, we have added an extra module to the module tree; `bar`. `bar` is a submodule +of `foo` hence we declare bar in `foo.nr` with `mod bar`. Since `foo` is not the crate root, the +compiler looks for the file associated with the `bar` module in `src/foo/bar.nr` + +Visually the module hierarchy looks as follows: + +``` +crate + ├── main + │ + └── foo + ├── from_foo + └── bar + └── from_bar +``` + +Similar to importing a module in the crate root, modules can be placed in a `mod.nr` file, like this: + +Filename : `src/main.nr` + +```rust +mod foo; + +fn main() { + foo::from_foo(); +} +``` + +Filename : `src/foo/mod.nr` + +```rust +mod bar; +fn from_foo() {} +``` + +Filename : `src/foo/bar/mod.nr` + +```rust +fn from_bar() {} +``` + +### Referencing a parent module + +Given a submodule, you can refer to its parent module using the `super` keyword. + +Filename : `src/main.nr` + +```rust +mod foo; + +fn main() { + foo::from_foo(); +} +``` + +Filename : `src/foo.nr` + +```rust +mod bar; + +fn from_foo() {} +``` + +Filename : `src/foo/bar.nr` + +```rust +// Same as bar::from_foo +use super::from_foo; + +fn from_bar() { + from_foo(); // invokes super::from_foo(), which is bar::from_foo() + super::from_foo(); // also invokes bar::from_foo() +} +``` \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/modules_packages_crates/workspaces.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/modules_packages_crates/workspaces.md new file mode 100644 index 000000000000..513497f12bf7 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/modules_packages_crates/workspaces.md @@ -0,0 +1,42 @@ +--- +title: Workspaces +sidebar_position: 3 +--- + +Workspaces are a feature of nargo that allow you to manage multiple related Noir packages in a single repository. A workspace is essentially a group of related projects that share common build output directories and configurations. + +Each Noir project (with it's own Nargo.toml file) can be thought of as a package. Each package is expected to contain exactly one "named circuit", being the "name" defined in Nargo.toml with the program logic defined in `./src/main.nr`. + +For a project with the following structure: + +```tree +├── crates +│ ├── a +│ │ ├── Nargo.toml +│ │ └── Prover.toml +│ │ └── src +│ │ └── main.nr +│ └── b +│ ├── Nargo.toml +│ └── Prover.toml +│ └── src +│ └── main.nr +│ +└── Nargo.toml +``` + +You can define a workspace in Nargo.toml like so: + +```toml +[workspace] +members = ["crates/a", "crates/b"] +default-member = "crates/a" +``` + +`members` indicates which packages are included in the workspace. As such, all member packages of a workspace will be processed when the `--workspace` flag is used with various commands or if a `default-member` is not specified. + +`default-member` indicates which package various commands process by default. + +Libraries can be defined in a workspace. Inside a workspace, these are consumed as `{ path = "../to_lib" }` dependencies in Nargo.toml. + +Inside a workspace, these are consumed as `{ path = "../to_lib" }` dependencies in Nargo.toml. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/getting_started/barretenberg/_category_.json b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/_category_.json similarity index 65% rename from noir/noir-repo/docs/versioned_docs/version-v0.32.0/getting_started/barretenberg/_category_.json rename to noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/_category_.json index 27a8e89228de..af04c0933fdb 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/getting_started/barretenberg/_category_.json +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/_category_.json @@ -1,6 +1,6 @@ { + "label": "Standard Library", "position": 1, - "label": "Install Barretenberg", "collapsible": true, "collapsed": true } diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/bigint.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/bigint.md new file mode 100644 index 000000000000..2bfdeec6631d --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/bigint.md @@ -0,0 +1,122 @@ +--- +title: Big Integers +description: How to use big integers from Noir standard library +keywords: + [ + Big Integer, + Noir programming language, + Noir libraries, + ] +--- + +The BigInt module in the standard library exposes some class of integers which do not fit (well) into a Noir native field. It implements modulo arithmetic, modulo a 'big' prime number. + +:::note + +The module can currently be considered as `Field`s with fixed modulo sizes used by a set of elliptic curves, in addition to just the native curve. [More work](https://github.com/noir-lang/noir/issues/510) is needed to achieve arbitrarily sized big integers. + +::: + +Currently 6 classes of integers (i.e 'big' prime numbers) are available in the module, namely: + +- BN254 Fq: Bn254Fq +- BN254 Fr: Bn254Fr +- Secp256k1 Fq: Secpk1Fq +- Secp256k1 Fr: Secpk1Fr +- Secp256r1 Fr: Secpr1Fr +- Secp256r1 Fq: Secpr1Fq + +Where XXX Fq and XXX Fr denote respectively the order of the base and scalar field of the (usual) elliptic curve XXX. +For instance the big integer 'Secpk1Fq' in the standard library refers to integers modulo $2^{256}-2^{32}-977$. + +Feel free to explore the source code for the other primes: + +```rust title="big_int_definition" showLineNumbers +struct BigInt { + pointer: u32, + modulus: u32, +} +``` +> Source code: noir_stdlib/src/bigint.nr#L14-L19 + + +## Example usage + +A common use-case is when constructing a big integer from its bytes representation, and performing arithmetic operations on it: + +```rust title="big_int_example" showLineNumbers +fn big_int_example(x: u8, y: u8) { + let a = Secpk1Fq::from_le_bytes(&[x, y, 0, 45, 2]); + let b = Secpk1Fq::from_le_bytes(&[y, x, 9]); + let c = (a + b) * b / a; + let d = c.to_le_bytes(); + println(d[0]); +} +``` +> Source code: test_programs/execution_success/bigint/src/main.nr#L70-L78 + + +## Methods + +The available operations for each big integer are: + +### from_le_bytes + +Construct a big integer from its little-endian bytes representation. Example: + +```rust + // Construct a big integer from a slice of bytes + let a = Secpk1Fq::from_le_bytes(&[x, y, 0, 45, 2]); + // Construct a big integer from an array of 32 bytes + let a = Secpk1Fq::from_le_bytes_32([1;32]); + ``` + +Sure, here's the formatted version of the remaining methods: + +### to_le_bytes + +Return the little-endian bytes representation of a big integer. Example: + +```rust +let bytes = a.to_le_bytes(); +``` + +### add + +Add two big integers. Example: + +```rust +let sum = a + b; +``` + +### sub + +Subtract two big integers. Example: + +```rust +let difference = a - b; +``` + +### mul + +Multiply two big integers. Example: + +```rust +let product = a * b; +``` + +### div + +Divide two big integers. Note that division is field division and not euclidean division. Example: + +```rust +let quotient = a / b; +``` + +### eq + +Compare two big integers. Example: + +```rust +let are_equal = a == b; +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/black_box_fns.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/black_box_fns.md new file mode 100644 index 000000000000..d5694250f052 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/black_box_fns.md @@ -0,0 +1,32 @@ +--- +title: Black Box Functions +description: Black box functions are functions in Noir that rely on backends implementing support for specialized constraints. +keywords: [noir, black box functions] +--- + +Black box functions are functions in Noir that rely on backends implementing support for specialized constraints. This makes certain zk-snark unfriendly computations cheaper than if they were implemented in Noir. + +The ACVM spec defines a set of blackbox functions which backends will be expected to implement. This allows backends to use optimized implementations of these constraints if they have them, however they may also fallback to less efficient naive implementations if not. + +## Function list + +Here is a list of the current black box functions: + +- [AES128](./cryptographic_primitives/ciphers.mdx#aes128) +- [SHA256](./cryptographic_primitives/hashes.mdx#sha256) +- [Schnorr signature verification](./cryptographic_primitives/schnorr.mdx) +- [Blake2s](./cryptographic_primitives/hashes.mdx#blake2s) +- [Blake3](./cryptographic_primitives/hashes.mdx#blake3) +- [Pedersen Hash](./cryptographic_primitives/hashes.mdx#pedersen_hash) +- [Pedersen Commitment](./cryptographic_primitives/hashes.mdx#pedersen_commitment) +- [ECDSA signature verification](./cryptographic_primitives/ecdsa_sig_verification.mdx) +- [Embedded curve operations (MSM, addition, ...)](./cryptographic_primitives/embedded_curve_ops.mdx) +- AND +- XOR +- RANGE +- [Keccak256](./cryptographic_primitives/hashes.mdx#keccak256) +- [Recursive proof verification](./recursion.md) + +Most black box functions are included as part of the Noir standard library, however `AND`, `XOR` and `RANGE` are used as part of the Noir language syntax. For instance, using the bitwise operator `&` will invoke the `AND` black box function. + +You can view the black box functions defined in the ACVM code [here](https://github.com/noir-lang/noir/blob/master/acvm-repo/acir/src/circuit/black_box_functions.rs). diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/bn254.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/bn254.md new file mode 100644 index 000000000000..3294f005dbb4 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/bn254.md @@ -0,0 +1,46 @@ +--- +title: Bn254 Field Library +--- + +Noir provides a module in standard library with some optimized functions for bn254 Fr in `std::field::bn254`. + +## decompose + +```rust +fn decompose(x: Field) -> (Field, Field) {} +``` + +Decomposes a single field into two fields, low and high. The low field contains the lower 16 bytes of the input field and the high field contains the upper 16 bytes of the input field. Both field results are range checked to 128 bits. + + +## assert_gt + +```rust +fn assert_gt(a: Field, b: Field) {} +``` + +Asserts that a > b. This will generate less constraints than using `assert(gt(a, b))`. + +## assert_lt + +```rust +fn assert_lt(a: Field, b: Field) {} +``` + +Asserts that a < b. This will generate less constraints than using `assert(lt(a, b))`. + +## gt + +```rust +fn gt(a: Field, b: Field) -> bool {} +``` + +Returns true if a > b. + +## lt + +```rust +fn lt(a: Field, b: Field) -> bool {} +``` + +Returns true if a < b. \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/containers/boundedvec.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/containers/boundedvec.md new file mode 100644 index 000000000000..604d84d5ba4e --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/containers/boundedvec.md @@ -0,0 +1,419 @@ +--- +title: Bounded Vectors +keywords: [noir, vector, bounded vector, slice] +sidebar_position: 1 +--- + +A `BoundedVec` is a growable storage similar to a `Vec` except that it +is bounded with a maximum possible length. Unlike `Vec`, `BoundedVec` is not implemented +via slices and thus is not subject to the same restrictions slices are (notably, nested +slices - and thus nested vectors as well - are disallowed). + +Since a BoundedVec is backed by a normal array under the hood, growing the BoundedVec by +pushing an additional element is also more efficient - the length only needs to be increased +by one. + +For these reasons `BoundedVec` should generally be preferred over `Vec` when there +is a reasonable maximum bound that can be placed on the vector. + +Example: + +```rust +let mut vector: BoundedVec = BoundedVec::new(); +for i in 0..5 { + vector.push(i); +} +assert(vector.len() == 5); +assert(vector.max_len() == 10); +``` + +## Methods + +### new + +```rust +pub fn new() -> Self +``` + +Creates a new, empty vector of length zero. + +Since this container is backed by an array internally, it still needs an initial value +to give each element. To resolve this, each element is zeroed internally. This value +is guaranteed to be inaccessible unless `get_unchecked` is used. + +Example: + +```rust +let empty_vector: BoundedVec = BoundedVec::new(); +assert(empty_vector.len() == 0); +``` + +Note that whenever calling `new` the maximum length of the vector should always be specified +via a type signature: + +```rust title="new_example" showLineNumbers +fn foo() -> BoundedVec { + // Ok! MaxLen is specified with a type annotation + let v1: BoundedVec = BoundedVec::new(); + let v2 = BoundedVec::new(); + + // Ok! MaxLen is known from the type of foo's return value + v2 +} + +fn bad() { + let mut v3 = BoundedVec::new(); + + // Not Ok! We don't know if v3's MaxLen is at least 1, and the compiler often infers 0 by default. + v3.push(5); +} +``` +> Source code: test_programs/noir_test_success/bounded_vec/src/main.nr#L11-L27 + + +This defaulting of `MaxLen` (and numeric generics in general) to zero may change in future noir versions +but for now make sure to use type annotations when using bounded vectors. Otherwise, you will receive a constraint failure at runtime when the vec is pushed to. + +### get + +```rust +pub fn get(self, index: u64) -> T { +``` + +Retrieves an element from the vector at the given index, starting from zero. + +If the given index is equal to or greater than the length of the vector, this +will issue a constraint failure. + +Example: + +```rust +fn foo(v: BoundedVec) { + let first = v.get(0); + let last = v.get(v.len() - 1); + assert(first != last); +} +``` + +### get_unchecked + +```rust +pub fn get_unchecked(self, index: u64) -> T { +``` + +Retrieves an element from the vector at the given index, starting from zero, without +performing a bounds check. + +Since this function does not perform a bounds check on length before accessing the element, +it is unsafe! Use at your own risk! + +Example: + +```rust title="get_unchecked_example" showLineNumbers +fn sum_of_first_three(v: BoundedVec) -> u32 { + // Always ensure the length is larger than the largest + // index passed to get_unchecked + assert(v.len() > 2); + let first = v.get_unchecked(0); + let second = v.get_unchecked(1); + let third = v.get_unchecked(2); + first + second + third +} +``` +> Source code: test_programs/noir_test_success/bounded_vec/src/main.nr#L54-L64 + + +### set + +```rust +pub fn set(&mut self: Self, index: u64, value: T) { +``` + +Writes an element to the vector at the given index, starting from zero. + +If the given index is equal to or greater than the length of the vector, this will issue a constraint failure. + +Example: + +```rust +fn foo(v: BoundedVec) { + let first = v.get(0); + assert(first != 42); + v.set(0, 42); + let new_first = v.get(0); + assert(new_first == 42); +} +``` + +### set_unchecked + +```rust +pub fn set_unchecked(&mut self: Self, index: u64, value: T) -> T { +``` + +Writes an element to the vector at the given index, starting from zero, without performing a bounds check. + +Since this function does not perform a bounds check on length before accessing the element, it is unsafe! Use at your own risk! + +Example: + +```rust title="set_unchecked_example" showLineNumbers +fn set_unchecked_example() { + let mut vec: BoundedVec = BoundedVec::new(); + vec.extend_from_array([1, 2]); + + // Here we're safely writing within the valid range of `vec` + // `vec` now has the value [42, 2] + vec.set_unchecked(0, 42); + + // We can then safely read this value back out of `vec`. + // Notice that we use the checked version of `get` which would prevent reading unsafe values. + assert_eq(vec.get(0), 42); + + // We've now written past the end of `vec`. + // As this index is still within the maximum potential length of `v`, + // it won't cause a constraint failure. + vec.set_unchecked(2, 42); + println(vec); + + // This will write past the end of the maximum potential length of `vec`, + // it will then trigger a constraint failure. + vec.set_unchecked(5, 42); + println(vec); +} +``` +> Source code: test_programs/noir_test_success/bounded_vec/src/main.nr#L67-L91 + + + +### push + +```rust +pub fn push(&mut self, elem: T) { +``` + +Pushes an element to the end of the vector. This increases the length +of the vector by one. + +Panics if the new length of the vector will be greater than the max length. + +Example: + +```rust title="bounded-vec-push-example" showLineNumbers +let mut v: BoundedVec = BoundedVec::new(); + + v.push(1); + v.push(2); + + // Panics with failed assertion "push out of bounds" + v.push(3); +``` +> Source code: test_programs/noir_test_success/bounded_vec/src/main.nr#L95-L103 + + +### pop + +```rust +pub fn pop(&mut self) -> T +``` + +Pops the element at the end of the vector. This will decrease the length +of the vector by one. + +Panics if the vector is empty. + +Example: + +```rust title="bounded-vec-pop-example" showLineNumbers +let mut v: BoundedVec = BoundedVec::new(); + v.push(1); + v.push(2); + + let two = v.pop(); + let one = v.pop(); + + assert(two == 2); + assert(one == 1); + // error: cannot pop from an empty vector + // let _ = v.pop(); +``` +> Source code: test_programs/noir_test_success/bounded_vec/src/main.nr#L108-L120 + + +### len + +```rust +pub fn len(self) -> u64 { +``` + +Returns the current length of this vector + +Example: + +```rust title="bounded-vec-len-example" showLineNumbers +let mut v: BoundedVec = BoundedVec::new(); + assert(v.len() == 0); + + v.push(100); + assert(v.len() == 1); + + v.push(200); + v.push(300); + v.push(400); + assert(v.len() == 4); + + let _ = v.pop(); + let _ = v.pop(); + assert(v.len() == 2); +``` +> Source code: test_programs/noir_test_success/bounded_vec/src/main.nr#L125-L140 + + +### max_len + +```rust +pub fn max_len(_self: BoundedVec) -> u64 { +``` + +Returns the maximum length of this vector. This is always +equal to the `MaxLen` parameter this vector was initialized with. + +Example: + +```rust title="bounded-vec-max-len-example" showLineNumbers +let mut v: BoundedVec = BoundedVec::new(); + + assert(v.max_len() == 5); + v.push(10); + assert(v.max_len() == 5); +``` +> Source code: test_programs/noir_test_success/bounded_vec/src/main.nr#L145-L151 + + +### storage + +```rust +pub fn storage(self) -> [T; MaxLen] { +``` + +Returns the internal array within this vector. +Since arrays in Noir are immutable, mutating the returned storage array will not mutate +the storage held internally by this vector. + +Note that uninitialized elements may be zeroed out! + +Example: + +```rust title="bounded-vec-storage-example" showLineNumbers +let mut v: BoundedVec = BoundedVec::new(); + + assert(v.storage() == [0, 0, 0, 0, 0]); + + v.push(57); + assert(v.storage() == [57, 0, 0, 0, 0]); +``` +> Source code: test_programs/noir_test_success/bounded_vec/src/main.nr#L156-L163 + + +### extend_from_array + +```rust +pub fn extend_from_array(&mut self, array: [T; Len]) +``` + +Pushes each element from the given array to this vector. + +Panics if pushing each element would cause the length of this vector +to exceed the maximum length. + +Example: + +```rust title="bounded-vec-extend-from-array-example" showLineNumbers +let mut vec: BoundedVec = BoundedVec::new(); + vec.extend_from_array([2, 4]); + + assert(vec.len == 2); + assert(vec.get(0) == 2); + assert(vec.get(1) == 4); +``` +> Source code: test_programs/noir_test_success/bounded_vec/src/main.nr#L168-L175 + + +### extend_from_bounded_vec + +```rust +pub fn extend_from_bounded_vec(&mut self, vec: BoundedVec) +``` + +Pushes each element from the other vector to this vector. The length of +the other vector is left unchanged. + +Panics if pushing each element would cause the length of this vector +to exceed the maximum length. + +Example: + +```rust title="bounded-vec-extend-from-bounded-vec-example" showLineNumbers +let mut v1: BoundedVec = BoundedVec::new(); + let mut v2: BoundedVec = BoundedVec::new(); + + v2.extend_from_array([1, 2, 3]); + v1.extend_from_bounded_vec(v2); + + assert(v1.storage() == [1, 2, 3, 0, 0]); + assert(v2.storage() == [1, 2, 3, 0, 0, 0, 0]); +``` +> Source code: test_programs/noir_test_success/bounded_vec/src/main.nr#L180-L189 + + +### from_array + +```rust +pub fn from_array(array: [T; Len]) -> Self +``` + +Creates a new vector, populating it with values derived from an array input. +The maximum length of the vector is determined based on the type signature. + +Example: +```rust +let bounded_vec: BoundedVec = BoundedVec::from_array([1, 2, 3]) +``` + +### map + +```rust +pub fn map(self, f: fn[Env](T) -> U) -> BoundedVec +``` + +Creates a new vector of equal size by calling a closure on each element in this vector. + +Example: + +```rust title="bounded-vec-map-example" showLineNumbers +let vec: BoundedVec = BoundedVec::from_array([1, 2, 3, 4]); + let result = vec.map(|value| value * 2); +``` +> Source code: noir_stdlib/src/collections/bounded_vec.nr#L205-L208 + + +### any + +```rust +pub fn any(self, predicate: fn[Env](T) -> bool) -> bool +``` + +Returns true if the given predicate returns true for any element +in this vector. + +Example: + +```rust title="bounded-vec-any-example" showLineNumbers +let mut v: BoundedVec = BoundedVec::new(); + v.extend_from_array([2, 4, 6]); + + let all_even = !v.any(|elem: u32| elem % 2 != 0); + assert(all_even); +``` +> Source code: test_programs/noir_test_success/bounded_vec/src/main.nr#L256-L262 + diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/containers/hashmap.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/containers/hashmap.md new file mode 100644 index 000000000000..8c50c7e774cc --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/containers/hashmap.md @@ -0,0 +1,570 @@ +--- +title: HashMap +keywords: [noir, map, hash, hashmap] +sidebar_position: 1 +--- + +`HashMap` is used to efficiently store and look up key-value pairs. + +`HashMap` is a bounded type which can store anywhere from zero to `MaxLen` total elements. +Note that due to hash collisions, the actual maximum number of elements stored by any particular +hashmap is likely lower than `MaxLen`. This is true even with cryptographic hash functions since +every hash value will be performed modulo `MaxLen`. + +When creating `HashMap`s, the `MaxLen` generic should always be specified if it is not already +known. Otherwise, the compiler may infer a different value for `MaxLen` (such as zero), which +will likely change the result of the program. This behavior is set to become an error in future +versions instead. + +Example: + +```rust +// Create a mapping from Fields to u32s with a maximum length of 12 +// using a poseidon2 hasher +use std::hash::poseidon2::Poseidon2Hasher; +let mut map: HashMap> = HashMap::default(); + +map.insert(1, 2); +map.insert(3, 4); + +let two = map.get(1).unwrap(); +``` + +## Methods + +### default + +```rust title="default" showLineNumbers +impl Default for HashMap +where + B: BuildHasher + Default, + H: Hasher + Default +{ + fn default() -> Self { +``` +> Source code: noir_stdlib/src/collections/map.nr#L462-L469 + + +Creates a fresh, empty HashMap. + +When using this function, always make sure to specify the maximum size of the hash map. + +This is the same `default` from the `Default` implementation given further below. It is +repeated here for convenience since it is the recommended way to create a hashmap. + +Example: + +```rust title="default_example" showLineNumbers +let hashmap: HashMap> = HashMap::default(); + assert(hashmap.is_empty()); +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L201-L204 + + +Because `HashMap` has so many generic arguments that are likely to be the same throughout +your program, it may be helpful to create a type alias: + +```rust title="type_alias" showLineNumbers +type MyMap = HashMap>; +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L195-L197 + + +### with_hasher + +```rust title="with_hasher" showLineNumbers +pub fn with_hasher(_build_hasher: B) -> Self + where + B: BuildHasher { +``` +> Source code: noir_stdlib/src/collections/map.nr#L82-L86 + + +Creates a hashmap with an existing `BuildHasher`. This can be used to ensure multiple +hashmaps are created with the same hasher instance. + +Example: + +```rust title="with_hasher_example" showLineNumbers +let my_hasher: BuildHasherDefault = Default::default(); + let hashmap: HashMap> = HashMap::with_hasher(my_hasher); + assert(hashmap.is_empty()); +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L206-L210 + + +### get + +```rust title="get" showLineNumbers +pub fn get( + self, + key: K + ) -> Option + where + K: Eq + Hash, + B: BuildHasher, + H: Hasher { +``` +> Source code: noir_stdlib/src/collections/map.nr#L278-L287 + + +Retrieves a value from the hashmap, returning `Option::none()` if it was not found. + +Example: + +```rust title="get_example" showLineNumbers +fn get_example(map: HashMap>) { + let x = map.get(12); + + if x.is_some() { + assert(x.unwrap() == 42); + } +} +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L298-L306 + + +### insert + +```rust title="insert" showLineNumbers +pub fn insert( + &mut self, + key: K, + value: V + ) + where + K: Eq + Hash, + B: BuildHasher, + H: Hasher { +``` +> Source code: noir_stdlib/src/collections/map.nr#L313-L323 + + +Inserts a new key-value pair into the map. If the key was already in the map, its +previous value will be overridden with the newly provided one. + +Example: + +```rust title="insert_example" showLineNumbers +let mut map: HashMap> = HashMap::default(); + map.insert(12, 42); + assert(map.len() == 1); +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L212-L216 + + +### remove + +```rust title="remove" showLineNumbers +pub fn remove( + &mut self, + key: K + ) + where + K: Eq + Hash, + B: BuildHasher, + H: Hasher { +``` +> Source code: noir_stdlib/src/collections/map.nr#L356-L365 + + +Removes the given key-value pair from the map. If the key was not already present +in the map, this does nothing. + +Example: + +```rust title="remove_example" showLineNumbers +map.remove(12); + assert(map.is_empty()); + + // If a key was not present in the map, remove does nothing + map.remove(12); + assert(map.is_empty()); +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L220-L227 + + +### is_empty + +```rust title="is_empty" showLineNumbers +pub fn is_empty(self) -> bool { +``` +> Source code: noir_stdlib/src/collections/map.nr#L115-L117 + + +True if the length of the hash map is empty. + +Example: + +```rust title="is_empty_example" showLineNumbers +assert(map.is_empty()); + + map.insert(1, 2); + assert(!map.is_empty()); + + map.remove(1); + assert(map.is_empty()); +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L229-L237 + + +### len + +```rust title="len" showLineNumbers +pub fn len(self) -> u32 { +``` +> Source code: noir_stdlib/src/collections/map.nr#L264-L266 + + +Returns the current length of this hash map. + +Example: + +```rust title="len_example" showLineNumbers +// This is equivalent to checking map.is_empty() + assert(map.len() == 0); + + map.insert(1, 2); + map.insert(3, 4); + map.insert(5, 6); + assert(map.len() == 3); + + // 3 was already present as a key in the hash map, so the length is unchanged + map.insert(3, 7); + assert(map.len() == 3); + + map.remove(1); + assert(map.len() == 2); +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L239-L254 + + +### capacity + +```rust title="capacity" showLineNumbers +pub fn capacity(_self: Self) -> u32 { +``` +> Source code: noir_stdlib/src/collections/map.nr#L271-L273 + + +Returns the maximum capacity of this hashmap. This is always equal to the capacity +specified in the hashmap's type. + +Unlike hashmaps in general purpose programming languages, hashmaps in Noir have a +static capacity that does not increase as the map grows larger. Thus, this capacity +is also the maximum possible element count that can be inserted into the hashmap. +Due to hash collisions (modulo the hashmap length), it is likely the actual maximum +element count will be lower than the full capacity. + +Example: + +```rust title="capacity_example" showLineNumbers +let empty_map: HashMap> = HashMap::default(); + assert(empty_map.len() == 0); + assert(empty_map.capacity() == 42); +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L256-L260 + + +### clear + +```rust title="clear" showLineNumbers +pub fn clear(&mut self) { +``` +> Source code: noir_stdlib/src/collections/map.nr#L93-L95 + + +Clears the hashmap, removing all key-value pairs from it. + +Example: + +```rust title="clear_example" showLineNumbers +assert(!map.is_empty()); + map.clear(); + assert(map.is_empty()); +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L262-L266 + + +### contains_key + +```rust title="contains_key" showLineNumbers +pub fn contains_key( + self, + key: K + ) -> bool + where + K: Hash + Eq, + B: BuildHasher, + H: Hasher { +``` +> Source code: noir_stdlib/src/collections/map.nr#L101-L110 + + +True if the hashmap contains the given key. Unlike `get`, this will not also return +the value associated with the key. + +Example: + +```rust title="contains_key_example" showLineNumbers +if map.contains_key(7) { + let value = map.get(7); + assert(value.is_some()); + } else { + println("No value for key 7!"); + } +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L268-L275 + + +### entries + +```rust title="entries" showLineNumbers +pub fn entries(self) -> BoundedVec<(K, V), N> { +``` +> Source code: noir_stdlib/src/collections/map.nr#L123-L125 + + +Returns a vector of each key-value pair present in the hashmap. + +The length of the returned vector is always equal to the length of the hashmap. + +Example: + +```rust title="entries_example" showLineNumbers +let entries = map.entries(); + + // The length of a hashmap may not be compile-time known, so we + // need to loop over its capacity instead + for i in 0..map.capacity() { + if i < entries.len() { + let (key, value) = entries.get(i); + println(f"{key} -> {value}"); + } + } +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L309-L320 + + +### keys + +```rust title="keys" showLineNumbers +pub fn keys(self) -> BoundedVec { +``` +> Source code: noir_stdlib/src/collections/map.nr#L144-L146 + + +Returns a vector of each key present in the hashmap. + +The length of the returned vector is always equal to the length of the hashmap. + +Example: + +```rust title="keys_example" showLineNumbers +let keys = map.keys(); + + for i in 0..keys.max_len() { + if i < keys.len() { + let key = keys.get_unchecked(i); + let value = map.get(key).unwrap_unchecked(); + println(f"{key} -> {value}"); + } + } +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L322-L332 + + +### values + +```rust title="values" showLineNumbers +pub fn values(self) -> BoundedVec { +``` +> Source code: noir_stdlib/src/collections/map.nr#L164-L166 + + +Returns a vector of each value present in the hashmap. + +The length of the returned vector is always equal to the length of the hashmap. + +Example: + +```rust title="values_example" showLineNumbers +let values = map.values(); + + for i in 0..values.max_len() { + if i < values.len() { + let value = values.get_unchecked(i); + println(f"Found value {value}"); + } + } +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L334-L343 + + +### iter_mut + +```rust title="iter_mut" showLineNumbers +pub fn iter_mut( + &mut self, + f: fn(K, V) -> (K, V) + ) + where + K: Eq + Hash, + B: BuildHasher, + H: Hasher { +``` +> Source code: noir_stdlib/src/collections/map.nr#L183-L192 + + +Iterates through each key-value pair of the HashMap, setting each key-value pair to the +result returned from the given function. + +Note that since keys can be mutated, the HashMap needs to be rebuilt as it is iterated +through. If this is not desired, use `iter_values_mut` if only values need to be mutated, +or `entries` if neither keys nor values need to be mutated. + +The iteration order is left unspecified. As a result, if two keys are mutated to become +equal, which of the two values that will be present for the key in the resulting map is also unspecified. + +Example: + +```rust title="iter_mut_example" showLineNumbers +// Add 1 to each key in the map, and double the value associated with that key. + map.iter_mut(|k, v| (k + 1, v * 2)); +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L347-L350 + + +### iter_keys_mut + +```rust title="iter_keys_mut" showLineNumbers +pub fn iter_keys_mut( + &mut self, + f: fn(K) -> K + ) + where + K: Eq + Hash, + B: BuildHasher, + H: Hasher { +``` +> Source code: noir_stdlib/src/collections/map.nr#L208-L217 + + +Iterates through the HashMap, mutating each key to the result returned from +the given function. + +Note that since keys can be mutated, the HashMap needs to be rebuilt as it is iterated +through. If only iteration is desired and the keys are not intended to be mutated, +prefer using `entries` instead. + +The iteration order is left unspecified. As a result, if two keys are mutated to become +equal, which of the two values that will be present for the key in the resulting map is also unspecified. + +Example: + +```rust title="iter_keys_mut_example" showLineNumbers +// Double each key, leaving the value associated with that key untouched + map.iter_keys_mut(|k| k * 2); +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L352-L355 + + +### iter_values_mut + +```rust title="iter_values_mut" showLineNumbers +pub fn iter_values_mut(&mut self, f: fn(V) -> V) { +``` +> Source code: noir_stdlib/src/collections/map.nr#L233-L235 + + +Iterates through the HashMap, applying the given function to each value and mutating the +value to equal the result. This function is more efficient than `iter_mut` and `iter_keys_mut` +because the keys are untouched and the underlying hashmap thus does not need to be reordered. + +Example: + +```rust title="iter_values_mut_example" showLineNumbers +// Halve each value + map.iter_values_mut(|v| v / 2); +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L357-L360 + + +### retain + +```rust title="retain" showLineNumbers +pub fn retain(&mut self, f: fn(K, V) -> bool) { +``` +> Source code: noir_stdlib/src/collections/map.nr#L247-L249 + + +Retains only the key-value pairs for which the given function returns true. +Any key-value pairs for which the function returns false will be removed from the map. + +Example: + +```rust title="retain_example" showLineNumbers +map.retain(|k, v| (k != 0) & (v != 0)); +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L280-L282 + + +## Trait Implementations + +### default + +```rust title="default" showLineNumbers +impl Default for HashMap +where + B: BuildHasher + Default, + H: Hasher + Default +{ + fn default() -> Self { +``` +> Source code: noir_stdlib/src/collections/map.nr#L462-L469 + + +Constructs an empty HashMap. + +Example: + +```rust title="default_example" showLineNumbers +let hashmap: HashMap> = HashMap::default(); + assert(hashmap.is_empty()); +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L201-L204 + + +### eq + +```rust title="eq" showLineNumbers +impl Eq for HashMap +where + K: Eq + Hash, + V: Eq, + B: BuildHasher, + H: Hasher +{ + fn eq(self, other: HashMap) -> bool { +``` +> Source code: noir_stdlib/src/collections/map.nr#L426-L435 + + +Checks if two HashMaps are equal. + +Example: + +```rust title="eq_example" showLineNumbers +let mut map1: HashMap> = HashMap::default(); + let mut map2: HashMap> = HashMap::default(); + + map1.insert(1, 2); + map1.insert(3, 4); + + map2.insert(3, 4); + map2.insert(1, 2); + + assert(map1 == map2); +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L284-L295 + diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/containers/index.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/containers/index.md new file mode 100644 index 000000000000..ea84c6d5c21e --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/containers/index.md @@ -0,0 +1,5 @@ +--- +title: Containers +description: Container types provided by Noir's standard library for storing and retrieving data +keywords: [containers, data types, vec, hashmap] +--- diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/containers/vec.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/containers/vec.mdx new file mode 100644 index 000000000000..475011922f81 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/containers/vec.mdx @@ -0,0 +1,170 @@ +--- +title: Vectors +description: Delve into the Vec data type in Noir. Learn about its methods, practical examples, and best practices for using Vectors in your Noir code. +keywords: [noir, vector type, methods, examples, dynamic arrays] +sidebar_position: 6 +--- + +import Experimental from '@site/src/components/Notes/_experimental.mdx'; + + + +A vector is a collection type similar to Rust's `Vec` type. In Noir, it is a convenient way to use slices as mutable arrays. + +Example: + +```rust +let mut vector: Vec = Vec::new(); +for i in 0..5 { + vector.push(i); +} +assert(vector.len() == 5); +``` + +## Methods + +### new + +Creates a new, empty vector. + +```rust +pub fn new() -> Self +``` + +Example: + +```rust +let empty_vector: Vec = Vec::new(); +assert(empty_vector.len() == 0); +``` + +### from_slice + +Creates a vector containing each element from a given slice. Mutations to the resulting vector will not affect the original slice. + +```rust +pub fn from_slice(slice: [T]) -> Self +``` + +Example: + +```rust +let slice: [Field] = &[1, 2, 3]; +let vector_from_slice = Vec::from_slice(slice); +assert(vector_from_slice.len() == 3); +``` + +### len + +Returns the number of elements in the vector. + +```rust +pub fn len(self) -> Field +``` + +Example: + +```rust +let empty_vector: Vec = Vec::new(); +assert(empty_vector.len() == 0); +``` + +### get + +Retrieves an element from the vector at a given index. Panics if the index points beyond the vector's end. + +```rust +pub fn get(self, index: Field) -> T +``` + +Example: + +```rust +let vector: Vec = Vec::from_slice(&[10, 20, 30]); +assert(vector.get(1) == 20); +``` + +### set + +```rust +pub fn set(&mut self: Self, index: u64, value: T) { +``` + +Writes an element to the vector at the given index, starting from zero. + +Panics if the index points beyond the vector's end. + +Example: + +```rust +let vector: Vec = Vec::from_slice(&[10, 20, 30]); +assert(vector.get(1) == 20); +vector.set(1, 42); +assert(vector.get(1) == 42); +``` + +### push + +Adds a new element to the vector's end, returning a new vector with a length one greater than the original unmodified vector. + +```rust +pub fn push(&mut self, elem: T) +``` + +Example: + +```rust +let mut vector: Vec = Vec::new(); +vector.push(10); +assert(vector.len() == 1); +``` + +### pop + +Removes an element from the vector's end, returning a new vector with a length one less than the original vector, along with the removed element. Panics if the vector's length is zero. + +```rust +pub fn pop(&mut self) -> T +``` + +Example: + +```rust +let mut vector = Vec::from_slice(&[10, 20]); +let popped_elem = vector.pop(); +assert(popped_elem == 20); +assert(vector.len() == 1); +``` + +### insert + +Inserts an element at a specified index, shifting subsequent elements to the right. + +```rust +pub fn insert(&mut self, index: Field, elem: T) +``` + +Example: + +```rust +let mut vector = Vec::from_slice(&[10, 30]); +vector.insert(1, 20); +assert(vector.get(1) == 20); +``` + +### remove + +Removes an element at a specified index, shifting subsequent elements to the left, and returns the removed element. + +```rust +pub fn remove(&mut self, index: Field) -> T +``` + +Example: + +```rust +let mut vector = Vec::from_slice(&[10, 20, 30]); +let removed_elem = vector.remove(1); +assert(removed_elem == 20); +assert(vector.len() == 2); +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/_category_.json b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/_category_.json new file mode 100644 index 000000000000..5d694210bbf3 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/_category_.json @@ -0,0 +1,5 @@ +{ + "position": 0, + "collapsible": true, + "collapsed": true +} diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/ciphers.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/ciphers.mdx new file mode 100644 index 000000000000..d75e50d4b89b --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/ciphers.mdx @@ -0,0 +1,32 @@ +--- +title: Ciphers +description: + Learn about the implemented ciphers ready to use for any Noir project +keywords: + [ciphers, Noir project, aes128, encrypt] +sidebar_position: 0 +--- + +import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; + +## aes128 + +Given a plaintext as an array of bytes, returns the corresponding aes128 ciphertext (CBC mode). Input padding is automatically performed using PKCS#7, so that the output length is `input.len() + (16 - input.len() % 16)`. + +```rust title="aes128" showLineNumbers +pub fn aes128_encrypt(input: [u8; N], iv: [u8; 16], key: [u8; 16]) -> [u8] {} +``` +> Source code: noir_stdlib/src/aes128.nr#L2-L4 + + +```rust +fn main() { + let input: [u8; 4] = [0, 12, 3, 15] // Random bytes, will be padded to 16 bytes. + let iv: [u8; 16] = [0; 16]; // Initialisation vector + let key: [u8; 16] = [0; 16] // AES key + let ciphertext = std::aes128::aes128_encrypt(inputs.as_bytes(), iv.as_bytes(), key.as_bytes()); // In this case, the output length will be 16 bytes. +} +``` + + + \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/ec_primitives.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/ec_primitives.md new file mode 100644 index 000000000000..f262d8160d60 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/ec_primitives.md @@ -0,0 +1,102 @@ +--- +title: Elliptic Curve Primitives +keywords: [cryptographic primitives, Noir project] +sidebar_position: 4 +--- + +Data structures and methods on them that allow you to carry out computations involving elliptic +curves over the (mathematical) field corresponding to `Field`. For the field currently at our +disposal, applications would involve a curve embedded in BN254, e.g. the +[Baby Jubjub curve](https://eips.ethereum.org/EIPS/eip-2494). + +## Data structures + +### Elliptic curve configurations + +(`std::ec::{tecurve,montcurve,swcurve}::{affine,curvegroup}::Curve`), i.e. the specific elliptic +curve you want to use, which would be specified using any one of the methods +`std::ec::{tecurve,montcurve,swcurve}::{affine,curvegroup}::new` which take the coefficients in the +defining equation together with a generator point as parameters. You can find more detail in the +comments in +[`noir_stdlib/src/ec/mod.nr`](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec/mod.nr), but +the gist of it is that the elliptic curves of interest are usually expressed in one of the standard +forms implemented here (Twisted Edwards, Montgomery and Short Weierstraß), and in addition to that, +you could choose to use `affine` coordinates (Cartesian coordinates - the usual (x,y) - possibly +together with a point at infinity) or `curvegroup` coordinates (some form of projective coordinates +requiring more coordinates but allowing for more efficient implementations of elliptic curve +operations). Conversions between all of these forms are provided, and under the hood these +conversions are done whenever an operation is more efficient in a different representation (or a +mixed coordinate representation is employed). + +### Points + +(`std::ec::{tecurve,montcurve,swcurve}::{affine,curvegroup}::Point`), i.e. points lying on the +elliptic curve. For a curve configuration `c` and a point `p`, it may be checked that `p` +does indeed lie on `c` by calling `c.contains(p1)`. + +## Methods + +(given a choice of curve representation, e.g. use `std::ec::tecurve::affine::Curve` and use +`std::ec::tecurve::affine::Point`) + +- The **zero element** is given by `Point::zero()`, and we can verify whether a point `p: Point` is + zero by calling `p.is_zero()`. +- **Equality**: Points `p1: Point` and `p2: Point` may be checked for equality by calling + `p1.eq(p2)`. +- **Addition**: For `c: Curve` and points `p1: Point` and `p2: Point` on the curve, adding these two + points is accomplished by calling `c.add(p1,p2)`. +- **Negation**: For a point `p: Point`, `p.negate()` is its negation. +- **Subtraction**: For `c` and `p1`, `p2` as above, subtracting `p2` from `p1` is accomplished by + calling `c.subtract(p1,p2)`. +- **Scalar multiplication**: For `c` as above, `p: Point` a point on the curve and `n: Field`, + scalar multiplication is given by `c.mul(n,p)`. If instead `n :: [u1; N]`, i.e. `n` is a bit + array, the `bit_mul` method may be used instead: `c.bit_mul(n,p)` +- **Multi-scalar multiplication**: For `c` as above and arrays `n: [Field; N]` and `p: [Point; N]`, + multi-scalar multiplication is given by `c.msm(n,p)`. +- **Coordinate representation conversions**: The `into_group` method converts a point or curve + configuration in the affine representation to one in the CurveGroup representation, and + `into_affine` goes in the other direction. +- **Curve representation conversions**: `tecurve` and `montcurve` curves and points are equivalent + and may be converted between one another by calling `into_montcurve` or `into_tecurve` on their + configurations or points. `swcurve` is more general and a curve c of one of the other two types + may be converted to this representation by calling `c.into_swcurve()`, whereas a point `p` lying + on the curve given by `c` may be mapped to its corresponding `swcurve` point by calling + `c.map_into_swcurve(p)`. +- **Map-to-curve methods**: The Elligator 2 method of mapping a field element `n: Field` into a + `tecurve` or `montcurve` with configuration `c` may be called as `c.elligator2_map(n)`. For all of + the curve configurations, the SWU map-to-curve method may be called as `c.swu_map(z,n)`, where + `z: Field` depends on `Field` and `c` and must be chosen by the user (the conditions it needs to + satisfy are specified in the comments + [here](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec/mod.nr)). + +## Examples + +The +[ec_baby_jubjub test](https://github.com/noir-lang/noir/blob/master/test_programs/compile_success_empty/ec_baby_jubjub/src/main.nr) +illustrates all of the above primitives on various forms of the Baby Jubjub curve. A couple of more +interesting examples in Noir would be: + +Public-key cryptography: Given an elliptic curve and a 'base point' on it, determine the public key +from the private key. This is a matter of using scalar multiplication. In the case of Baby Jubjub, +for example, this code would do: + +```rust +use std::ec::tecurve::affine::{Curve, Point}; + +fn bjj_pub_key(priv_key: Field) -> Point +{ + + let bjj = Curve::new(168700, 168696, G::new(995203441582195749578291179787384436505546430278305826713579947235728471134,5472060717959818805561601436314318772137091100104008585924551046643952123905)); + + let base_pt = Point::new(5299619240641551281634865583518297030282874472190772894086521144482721001553, 16950150798460657717958625567821834550301663161624707787222815936182638968203); + + bjj.mul(priv_key,base_pt) +} +``` + +This would come in handy in a Merkle proof. + +- EdDSA signature verification: This is a matter of combining these primitives with a suitable hash + function. See + [feat(stdlib): EdDSA sig verification noir#1136](https://github.com/noir-lang/noir/pull/1136) for + the case of Baby Jubjub and the Poseidon hash function. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx new file mode 100644 index 000000000000..8520071e95fb --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx @@ -0,0 +1,98 @@ +--- +title: ECDSA Signature Verification +description: Learn about the cryptographic primitives regarding ECDSA over the secp256k1 and secp256r1 curves +keywords: [cryptographic primitives, Noir project, ecdsa, secp256k1, secp256r1, signatures] +sidebar_position: 3 +--- + +import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; + +Noir supports ECDSA signatures verification over the secp256k1 and secp256r1 curves. + +## ecdsa_secp256k1::verify_signature + +Verifier for ECDSA Secp256k1 signatures. +See ecdsa_secp256k1::verify_signature_slice for a version that accepts slices directly. + +```rust title="ecdsa_secp256k1" showLineNumbers +pub fn verify_signature( + public_key_x: [u8; 32], + public_key_y: [u8; 32], + signature: [u8; 64], + message_hash: [u8; N] +) -> bool +``` +> Source code: noir_stdlib/src/ecdsa_secp256k1.nr#L2-L9 + + +example: + +```rust +fn main(hashed_message : [u8;32], pub_key_x : [u8;32], pub_key_y : [u8;32], signature : [u8;64]) { + let valid_signature = std::ecdsa_secp256k1::verify_signature(pub_key_x, pub_key_y, signature, hashed_message); + assert(valid_signature); +} +``` + + + +## ecdsa_secp256k1::verify_signature_slice + +Verifier for ECDSA Secp256k1 signatures where the message is a slice. + +```rust title="ecdsa_secp256k1_slice" showLineNumbers +pub fn verify_signature_slice( + public_key_x: [u8; 32], + public_key_y: [u8; 32], + signature: [u8; 64], + message_hash: [u8] +) -> bool +``` +> Source code: noir_stdlib/src/ecdsa_secp256k1.nr#L13-L20 + + + + +## ecdsa_secp256r1::verify_signature + +Verifier for ECDSA Secp256r1 signatures. +See ecdsa_secp256r1::verify_signature_slice for a version that accepts slices directly. + +```rust title="ecdsa_secp256r1" showLineNumbers +pub fn verify_signature( + public_key_x: [u8; 32], + public_key_y: [u8; 32], + signature: [u8; 64], + message_hash: [u8; N] +) -> bool +``` +> Source code: noir_stdlib/src/ecdsa_secp256r1.nr#L2-L9 + + +example: + +```rust +fn main(hashed_message : [u8;32], pub_key_x : [u8;32], pub_key_y : [u8;32], signature : [u8;64]) { + let valid_signature = std::ecdsa_secp256r1::verify_signature(pub_key_x, pub_key_y, signature, hashed_message); + assert(valid_signature); +} +``` + + + +## ecdsa_secp256r1::verify_signature + +Verifier for ECDSA Secp256r1 signatures where the message is a slice. + +```rust title="ecdsa_secp256r1_slice" showLineNumbers +pub fn verify_signature_slice( + public_key_x: [u8; 32], + public_key_y: [u8; 32], + signature: [u8; 64], + message_hash: [u8] +) -> bool +``` +> Source code: noir_stdlib/src/ecdsa_secp256r1.nr#L13-L20 + + + diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/eddsa.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/eddsa.mdx new file mode 100644 index 000000000000..1ad42a5ac96b --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/eddsa.mdx @@ -0,0 +1,37 @@ +--- +title: EdDSA Verification +description: Learn about the cryptographic primitives regarding EdDSA +keywords: [cryptographic primitives, Noir project, eddsa, signatures] +sidebar_position: 5 +--- + +import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; + +## eddsa::eddsa_poseidon_verify + +Verifier for EdDSA signatures + +```rust +fn eddsa_poseidon_verify(public_key_x : Field, public_key_y : Field, signature_s: Field, signature_r8_x: Field, signature_r8_y: Field, message: Field) -> bool +``` + +It is also possible to specify the hash algorithm used for the signature by using the `eddsa_verify` function by passing a type implementing the Hasher trait with the turbofish operator. +For instance, if you want to use Poseidon2 instead, you can do the following: +```rust +use std::hash::poseidon2::Poseidon2Hasher; + +eddsa_verify::(pub_key_a.x, pub_key_a.y, s_a, r8_a.x, r8_a.y, msg); +``` + + + +## eddsa::eddsa_to_pub + +Private to public key conversion. + +Returns `(pub_key_x, pub_key_y)` + +```rust +fn eddsa_to_pub(secret : Field) -> (Field, Field) +``` + diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx new file mode 100644 index 000000000000..719549bc4184 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx @@ -0,0 +1,95 @@ +--- +title: Scalar multiplication +description: See how you can perform scalar multiplication in Noir +keywords: [cryptographic primitives, Noir project, scalar multiplication] +sidebar_position: 1 +--- + +import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; + +The following functions perform operations over the embedded curve whose coordinates are defined by the configured noir field. +For the BN254 scalar field, this is BabyJubJub or Grumpkin. + +:::note +Suffixes `_low` and `_high` denote low and high limbs of a scalar. +::: + +## embedded_curve_ops::multi_scalar_mul + +Performs multi scalar multiplication over the embedded curve. +The function accepts arbitrary amount of point-scalar pairs on the input, it multiplies the individual pairs over +the curve and returns a sum of the resulting points. + +Points represented as x and y coordinates [x1, y1, x2, y2, ...], scalars as low and high limbs [low1, high1, low2, high2, ...]. + +```rust title="multi_scalar_mul" showLineNumbers +pub fn multi_scalar_mul( + points: [EmbeddedCurvePoint; N], + scalars: [EmbeddedCurveScalar; N] +) -> EmbeddedCurvePoint +``` +> Source code: noir_stdlib/src/embedded_curve_ops.nr#L91-L96 + + +example + +```rust +fn main(point_x: Field, point_y: Field, scalar_low: Field, scalar_high: Field) { + let point = std::embedded_curve_ops::multi_scalar_mul([point_x, point_y], [scalar_low, scalar_high]); + println(point); +} +``` + +## embedded_curve_ops::fixed_base_scalar_mul + +Performs fixed base scalar multiplication over the embedded curve (multiplies input scalar with a generator point). +The function accepts a single scalar on the input represented as 2 fields. + +```rust title="fixed_base_scalar_mul" showLineNumbers +pub fn fixed_base_scalar_mul(scalar: EmbeddedCurveScalar) -> EmbeddedCurvePoint +``` +> Source code: noir_stdlib/src/embedded_curve_ops.nr#L108-L110 + + +example + +```rust +fn main(scalar_low: Field, scalar_high: Field) { + let point = std::embedded_curve_ops::fixed_base_scalar_mul(scalar_low, scalar_high); + println(point); +} +``` + +## embedded_curve_ops::embedded_curve_add + +Adds two points on the embedded curve. +This function takes two `EmbeddedCurvePoint` structures as parameters, representing points on the curve, and returns a new `EmbeddedCurvePoint` structure that represents their sum. + +### Parameters: +- `point1` (`EmbeddedCurvePoint`): The first point to add. +- `point2` (`EmbeddedCurvePoint`): The second point to add. + +### Returns: +- `EmbeddedCurvePoint`: The resulting point after the addition of `point1` and `point2`. + +```rust title="embedded_curve_add" showLineNumbers +fn embedded_curve_add( + point1: EmbeddedCurvePoint, + point2: EmbeddedCurvePoint +) -> EmbeddedCurvePoint +``` +> Source code: noir_stdlib/src/embedded_curve_ops.nr#L118-L123 + + +example + +```rust +fn main() { + let point1 = EmbeddedCurvePoint { x: 1, y: 2 }; + let point2 = EmbeddedCurvePoint { x: 3, y: 4 }; + let result = std::embedded_curve_ops::embedded_curve_add(point1, point2); + println!("Resulting Point: ({}, {})", result.x, result.y); +} +``` + + diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/hashes.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/hashes.mdx new file mode 100644 index 000000000000..63a4a2afd532 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/hashes.mdx @@ -0,0 +1,253 @@ +--- +title: Hash methods +description: + Learn about the cryptographic primitives ready to use for any Noir project, including sha256, + blake2s, pedersen, mimc_bn254 and mimc +keywords: + [cryptographic primitives, Noir project, sha256, blake2s, pedersen, mimc_bn254, mimc, hash] +sidebar_position: 0 +--- + +import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; + +## sha256 + +Given an array of bytes, returns the resulting sha256 hash. +Specify a message_size to hash only the first `message_size` bytes of the input. + +```rust title="sha256" showLineNumbers +pub fn sha256(input: [u8; N]) -> [u8; 32] +``` +> Source code: noir_stdlib/src/hash/mod.nr#L14-L16 + + +example: +```rust title="sha256_var" showLineNumbers +let digest = std::hash::sha256_var([x as u8], 1); +``` +> Source code: test_programs/execution_success/sha256/src/main.nr#L16-L18 + + +```rust +fn main() { + let x = [163, 117, 178, 149]; // some random bytes + let hash = std::sha256::sha256_var(x, 4); +} +``` + + + + +## blake2s + +Given an array of bytes, returns an array with the Blake2 hash + +```rust title="blake2s" showLineNumbers +pub fn blake2s(input: [u8; N]) -> [u8; 32] +``` +> Source code: noir_stdlib/src/hash/mod.nr#L20-L22 + + +example: + +```rust +fn main() { + let x = [163, 117, 178, 149]; // some random bytes + let hash = std::hash::blake2s(x); +} +``` + + + +## blake3 + +Given an array of bytes, returns an array with the Blake3 hash + +```rust title="blake3" showLineNumbers +pub fn blake3(input: [u8; N]) -> [u8; 32] +``` +> Source code: noir_stdlib/src/hash/mod.nr#L26-L28 + + +example: + +```rust +fn main() { + let x = [163, 117, 178, 149]; // some random bytes + let hash = std::hash::blake3(x); +} +``` + + + +## pedersen_hash + +Given an array of Fields, returns the Pedersen hash. + +```rust title="pedersen_hash" showLineNumbers +pub fn pedersen_hash(input: [Field; N]) -> Field +``` +> Source code: noir_stdlib/src/hash/mod.nr#L79-L81 + + +example: + +```rust title="pedersen-hash" showLineNumbers +fn main(x: Field, y: Field, expected_hash: Field) { + let hash = std::hash::pedersen_hash([x, y]); + assert_eq(hash, expected_hash); +} +``` +> Source code: test_programs/execution_success/pedersen_hash/src/main.nr#L1-L7 + + + + +## pedersen_commitment + +Given an array of Fields, returns the Pedersen commitment. + +```rust title="pedersen_commitment" showLineNumbers +pub fn pedersen_commitment(input: [Field; N]) -> EmbeddedCurvePoint { +``` +> Source code: noir_stdlib/src/hash/mod.nr#L31-L33 + + +example: + +```rust title="pedersen-commitment" showLineNumbers +fn main(x: Field, y: Field, expected_commitment: std::embedded_curve_ops::EmbeddedCurvePoint) { + let commitment = std::hash::pedersen_commitment([x, y]); + assert_eq(commitment.x, expected_commitment.x); + assert_eq(commitment.y, expected_commitment.y); +} +``` +> Source code: test_programs/execution_success/pedersen_commitment/src/main.nr#L1-L8 + + + + +## keccak256 + +Given an array of bytes (`u8`), returns the resulting keccak hash as an array of +32 bytes (`[u8; 32]`). Specify a message_size to hash only the first +`message_size` bytes of the input. + +```rust title="keccak256" showLineNumbers +pub fn keccak256(input: [u8; N], message_size: u32) -> [u8; 32] +``` +> Source code: noir_stdlib/src/hash/mod.nr#L128-L130 + + +example: + +```rust title="keccak256" showLineNumbers +fn main(x: Field, result: [u8; 32]) { + // We use the `as` keyword here to denote the fact that we want to take just the first byte from the x Field + // The padding is taken care of by the program + let digest = std::hash::keccak256([x as u8], 1); + assert(digest == result); + + //#1399: variable message size + let message_size = 4; + let hash_a = std::hash::keccak256([1, 2, 3, 4], message_size); + let hash_b = std::hash::keccak256([1, 2, 3, 4, 0, 0, 0, 0], message_size); + + assert(hash_a == hash_b); + + let message_size_big = 8; + let hash_c = std::hash::keccak256([1, 2, 3, 4, 0, 0, 0, 0], message_size_big); + + assert(hash_a != hash_c); +} +``` +> Source code: test_programs/execution_success/keccak256/src/main.nr#L1-L21 + + + + +## poseidon + +Given an array of Fields, returns a new Field with the Poseidon Hash. Mind that you need to specify +how many inputs are there to your Poseidon function. + +```rust +// example for hash_1, hash_2 accepts an array of length 2, etc +fn hash_1(input: [Field; 1]) -> Field +``` + +example: + +```rust title="poseidon" showLineNumbers +use std::hash::poseidon; + +fn main(x1: [Field; 2], y1: pub Field, x2: [Field; 4], y2: pub Field) { + let hash1 = poseidon::bn254::hash_2(x1); + assert(hash1 == y1); + + let hash2 = poseidon::bn254::hash_4(x2); + assert(hash2 == y2); +} +``` +> Source code: test_programs/execution_success/poseidon_bn254_hash/src/main.nr#L1-L11 + + +## poseidon 2 + +Given an array of Fields, returns a new Field with the Poseidon2 Hash. Contrary to the Poseidon +function, there is only one hash and you can specify a message_size to hash only the first +`message_size` bytes of the input, + +```rust +// example for hashing the first three elements of the input +Poseidon2::hash(input, 3); +``` + +example: + +```rust title="poseidon2" showLineNumbers +use std::hash::poseidon2; + +fn main(inputs: [Field; 4], expected_hash: Field) { + let hash = poseidon2::Poseidon2::hash(inputs, inputs.len()); + assert_eq(hash, expected_hash); +} +``` +> Source code: test_programs/execution_success/poseidon2/src/main.nr#L1-L8 + + +## mimc_bn254 and mimc + +`mimc_bn254` is `mimc`, but with hardcoded parameters for the BN254 curve. You can use it by +providing an array of Fields, and it returns a Field with the hash. You can use the `mimc` method if +you're willing to input your own constants: + +```rust +fn mimc(x: Field, k: Field, constants: [Field; N], exp : Field) -> Field +``` + +otherwise, use the `mimc_bn254` method: + +```rust +fn mimc_bn254(array: [Field; N]) -> Field +``` + +example: + +```rust + +fn main() { + let x = [163, 117, 178, 149]; // some random bytes + let hash = std::hash::mimc::mimc_bn254(x); +} +``` + +## hash_to_field + +```rust +fn hash_to_field(_input : [Field]) -> Field {} +``` + +Calculates the `blake2s` hash of the inputs and returns the hash modulo the field modulus to return +a value which can be represented as a `Field`. + diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/index.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/index.md new file mode 100644 index 000000000000..650f30165d56 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/index.md @@ -0,0 +1,14 @@ +--- +title: Cryptographic Primitives +description: + Learn about the cryptographic primitives ready to use for any Noir project +keywords: + [ + cryptographic primitives, + Noir project, + ] +--- + +The Noir team is progressively adding new cryptographic primitives to the standard library. Reach out for news or if you would be interested in adding more of these calculations in Noir. + +Some methods are available thanks to the Aztec backend, not being performed using Noir. When using other backends, these methods may or may not be supplied. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/schnorr.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/schnorr.mdx new file mode 100644 index 000000000000..a32138daaa67 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/schnorr.mdx @@ -0,0 +1,64 @@ +--- +title: Schnorr Signatures +description: Learn how you can verify Schnorr signatures using Noir +keywords: [cryptographic primitives, Noir project, schnorr, signatures] +sidebar_position: 2 +--- + +import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; + +## schnorr::verify_signature + +Verifier for Schnorr signatures over the embedded curve (for BN254 it is Grumpkin). +See schnorr::verify_signature_slice for a version that works directly on slices. + +```rust title="schnorr_verify" showLineNumbers +pub fn verify_signature( + public_key_x: Field, + public_key_y: Field, + signature: [u8; 64], + message: [u8; N] +) -> bool +``` +> Source code: noir_stdlib/src/schnorr.nr#L2-L9 + + +where `_signature` can be generated like so using the npm package +[@noir-lang/barretenberg](https://www.npmjs.com/package/@noir-lang/barretenberg) + +```js +const { BarretenbergWasm } = require('@noir-lang/barretenberg/dest/wasm'); +const { Schnorr } = require('@noir-lang/barretenberg/dest/crypto/schnorr'); + +... + +const barretenberg = await BarretenbergWasm.new(); +const schnorr = new Schnorr(barretenberg); +const pubKey = schnorr.computePublicKey(privateKey); +const message = ... +const signature = Array.from( + schnorr.constructSignature(hash, privateKey).toBuffer() +); + +... +``` + + + +## schnorr::verify_signature_slice + +Verifier for Schnorr signatures over the embedded curve (for BN254 it is Grumpkin) +where the message is a slice. + +```rust title="schnorr_verify_slice" showLineNumbers +pub fn verify_signature_slice( + public_key_x: Field, + public_key_y: Field, + signature: [u8; 64], + message: [u8] +) -> bool +``` +> Source code: noir_stdlib/src/schnorr.nr#L13-L20 + + + diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/is_unconstrained.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/is_unconstrained.md new file mode 100644 index 000000000000..51bb1bda8f1b --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/is_unconstrained.md @@ -0,0 +1,69 @@ +--- +title: Is Unconstrained Function +description: + The is_unconstrained function returns wether the context at that point of the program is unconstrained or not. +keywords: + [ + unconstrained + ] +--- + +It's very common for functions in circuits to take unconstrained hints of an expensive computation and then verify it. This is done by running the hint in an unconstrained context and then verifying the result in a constrained context. + +When a function is marked as unconstrained, any subsequent functions that it calls will also be run in an unconstrained context. However, if we are implementing a library function, other users might call it within an unconstrained context or a constrained one. Generally, in an unconstrained context we prefer just computing the result instead of taking a hint of it and verifying it, since that'd mean doing the same computation twice: + +```rust + +fn my_expensive_computation(){ + ... +} + +unconstrained fn my_expensive_computation_hint(){ + my_expensive_computation() +} + +pub fn external_interface(){ + my_expensive_computation_hint(); + // verify my_expensive_computation: If external_interface is called from unconstrained, this is redundant + ... +} + +``` + +In order to improve the performance in an unconstrained context you can use the function at `std::runtime::is_unconstrained() -> bool`: + + +```rust +use dep::std::runtime::is_unconstrained; + +fn my_expensive_computation(){ + ... +} + +unconstrained fn my_expensive_computation_hint(){ + my_expensive_computation() +} + +pub fn external_interface(){ + if is_unconstrained() { + my_expensive_computation(); + } else { + my_expensive_computation_hint(); + // verify my_expensive_computation + ... + } +} + +``` + +The is_unconstrained result is resolved at compile time, so in unconstrained contexts the compiler removes the else branch, and in constrained contexts the compiler removes the if branch, reducing the amount of compute necessary to run external_interface. + +Note that using `is_unconstrained` in a `comptime` context will also return `true`: + +``` +fn main() { + comptime { + assert(is_unconstrained()); + } +} +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/logging.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/logging.md new file mode 100644 index 000000000000..db75ef9f86fa --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/logging.md @@ -0,0 +1,78 @@ +--- +title: Logging +description: + Learn how to use the println statement for debugging in Noir with this tutorial. Understand the + basics of logging in Noir and how to implement it in your code. +keywords: + [ + noir logging, + println statement, + print statement, + debugging in noir, + noir std library, + logging tutorial, + basic logging in noir, + noir logging implementation, + noir debugging techniques, + rust, + ] +--- + +The standard library provides two familiar statements you can use: `println` and `print`. Despite being a limited implementation of rust's `println!` and `print!` macros, these constructs can be useful for debugging. + +You can print the output of both statements in your Noir code by using the `nargo execute` command or the `--show-output` flag when using `nargo test` (provided there are print statements in your tests). + +It is recommended to use `nargo execute` if you want to debug failing constraints with `println` or `print` statements. This is due to every input in a test being a constant rather than a witness, so we issue an error during compilation while we only print during execution (which comes after compilation). Neither `println`, nor `print` are callable for failed constraints caught at compile time. + +Both `print` and `println` are generic functions which can work on integers, fields, strings, and even structs or expressions. Note however, that slices are currently unsupported. For example: + +```rust +struct Person { + age: Field, + height: Field, +} + +fn main(age: Field, height: Field) { + let person = Person { + age: age, + height: height, + }; + println(person); + println(age + height); + println("Hello world!"); +} +``` + +You can print different types in the same statement (including strings) with a type called `fmtstr`. It can be specified in the same way as a normal string, just prepended with an "f" character: + +```rust + let fmt_str = f"i: {i}, j: {j}"; + println(fmt_str); + + let s = myStruct { y: x, x: y }; + println(s); + + println(f"i: {i}, s: {s}"); + + println(x); + println([x, y]); + + let foo = fooStruct { my_struct: s, foo: 15 }; + println(f"s: {s}, foo: {foo}"); + + println(15); // prints 0x0f, implicit Field + println(-1 as u8); // prints 255 + println(-1 as i8); // prints -1 +``` + +Examples shown above are interchangeable between the two `print` statements: + +```rust +let person = Person { age : age, height : height }; + +println(person); +print(person); + +println("Hello world!"); // Prints with a newline at the end of the input +print("Hello world!"); // Prints the input and keeps cursor on the same line +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/merkle_trees.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/merkle_trees.md new file mode 100644 index 000000000000..6a9ebf72ada0 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/merkle_trees.md @@ -0,0 +1,58 @@ +--- +title: Merkle Trees +description: Learn about Merkle Trees in Noir with this tutorial. Explore the basics of computing a merkle root using a proof, with examples. +keywords: + [ + Merkle trees in Noir, + Noir programming language, + check membership, + computing root from leaf, + Noir Merkle tree implementation, + Merkle tree tutorial, + Merkle tree code examples, + Noir libraries, + pedersen hash., + ] +--- + +## compute_merkle_root + +Returns the root of the tree from the provided leaf and its hash path, using a [Pedersen hash](./cryptographic_primitives/hashes.mdx#pedersen_hash). + +```rust +fn compute_merkle_root(leaf : Field, index : Field, hash_path: [Field]) -> Field +``` + +example: + +```rust +/** + // these values are for this example only + index = "0" + priv_key = "0x000000000000000000000000000000000000000000000000000000616c696365" + secret = "0x1929ea3ab8d9106a899386883d9428f8256cfedb3c4f6b66bf4aa4d28a79988f" + note_hash_path = [ + "0x1e61bdae0f027b1b2159e1f9d3f8d00fa668a952dddd822fda80dc745d6f65cc", + "0x0e4223f3925f98934393c74975142bd73079ab0621f4ee133cee050a3c194f1a", + "0x2fd7bb412155bf8693a3bd2a3e7581a679c95c68a052f835dddca85fa1569a40" + ] + */ +fn main(index: Field, priv_key: Field, secret: Field, note_hash_path: [Field; 3]) { + + let pubkey = std::scalar_mul::fixed_base_embedded_curve(priv_key); + let pubkey_x = pubkey[0]; + let pubkey_y = pubkey[1]; + let note_commitment = std::hash::pedersen(&[pubkey_x, pubkey_y, secret]); + + let root = std::merkle::compute_merkle_root(note_commitment[0], index, note_hash_path.as_slice()); + println(root); +} +``` + +To check merkle tree membership: + +1. Include a merkle root as a program input. +2. Compute the merkle root of a given leaf, index and hash path. +3. Assert the merkle roots are equal. + +For more info about merkle trees, see the Wikipedia [page](https://en.wikipedia.org/wiki/Merkle_tree). diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/options.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/options.md new file mode 100644 index 000000000000..a1bd4e1de5fd --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/options.md @@ -0,0 +1,101 @@ +--- +title: Option Type +--- + +The `Option` type is a way to express that a value might be present (`Some(T))` or absent (`None`). It's a safer way to handle potential absence of values, compared to using nulls in many other languages. + +```rust +struct Option { + None, + Some(T), +} +``` + +The `Option` type, already imported into your Noir program, can be used directly: + +```rust +fn main() { + let none = Option::none(); + let some = Option::some(3); +} +``` + +See [this test](https://github.com/noir-lang/noir/blob/5cbfb9c4a06c8865c98ff2b594464b037d821a5c/crates/nargo_cli/tests/test_data/option/src/main.nr) for a more comprehensive set of examples of each of the methods described below. + +## Methods + +### none + +Constructs a none value. + +### some + +Constructs a some wrapper around a given value. + +### is_none + +Returns true if the Option is None. + +### is_some + +Returns true of the Option is Some. + +### unwrap + +Asserts `self.is_some()` and returns the wrapped value. + +### unwrap_unchecked + +Returns the inner value without asserting `self.is_some()`. This method can be useful within an if condition when we already know that `option.is_some()`. If the option is None, there is no guarantee what value will be returned, only that it will be of type T for an `Option`. + +### unwrap_or + +Returns the wrapped value if `self.is_some()`. Otherwise, returns the given default value. + +### unwrap_or_else + +Returns the wrapped value if `self.is_some()`. Otherwise, calls the given function to return a default value. + +### expect + +Asserts `self.is_some()` with a provided custom message and returns the contained `Some` value. The custom message is expected to be a format string. + +### map + +If self is `Some(x)`, this returns `Some(f(x))`. Otherwise, this returns `None`. + +### map_or + +If self is `Some(x)`, this returns `f(x)`. Otherwise, this returns the given default value. + +### map_or_else + +If self is `Some(x)`, this returns `f(x)`. Otherwise, this returns `default()`. + +### and + +Returns None if self is None. Otherwise, this returns `other`. + +### and_then + +If self is None, this returns None. Otherwise, this calls the given function with the Some value contained within self, and returns the result of that call. In some languages this function is called `flat_map` or `bind`. + +### or + +If self is Some, return self. Otherwise, return `other`. + +### or_else + +If self is Some, return self. Otherwise, return `default()`. + +### xor + +If only one of the two Options is Some, return that option. Otherwise, if both options are Some or both are None, None is returned. + +### filter + +Returns `Some(x)` if self is `Some(x)` and `predicate(x)` is true. Otherwise, this returns `None`. + +### flatten + +Flattens an `Option>` into a `Option`. This returns `None` if the outer Option is None. Otherwise, this returns the inner Option. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/recursion.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/recursion.md new file mode 100644 index 000000000000..7f4dcebf0841 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/recursion.md @@ -0,0 +1,85 @@ +--- +title: Recursive Proofs +description: Learn about how to write recursive proofs in Noir. +keywords: [recursion, recursive proofs, verification_key, verify_proof] +--- + +import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; + +Noir supports recursively verifying proofs, meaning you verify the proof of a Noir program in another Noir program. This enables creating proofs of arbitrary size by doing step-wise verification of smaller components of a large proof. + +Read [the explainer on recursion](../../explainers/explainer-recursion.md) to know more about this function and the [guide on how to use it.](../../how_to/how-to-recursion.md) + +## The `#[recursive]` Attribute + +In Noir, the `#[recursive]` attribute is used to indicate that a circuit is designed for recursive proof generation. When applied, it informs the compiler and the tooling that the circuit should be compiled in a way that makes its proofs suitable for recursive verification. This attribute eliminates the need for manual flagging of recursion at the tooling level, streamlining the proof generation process for recursive circuits. + +### Example usage with `#[recursive]` + +```rust +#[recursive] +fn main(x: Field, y: pub Field) { + assert(x == y, "x and y are not equal"); +} + +// This marks the circuit as recursion-friendly and indicates that proofs generated from this circuit +// are intended for recursive verification. +``` + +By incorporating this attribute directly in the circuit's definition, tooling like Nargo and NoirJS can automatically execute recursive-specific duties for Noir programs (e.g. recursive-friendly proof artifact generation) without additional flags or configurations. + +## Verifying Recursive Proofs + +```rust +#[foreign(recursive_aggregation)] +pub fn verify_proof(verification_key: [Field], proof: [Field], public_inputs: [Field], key_hash: Field) {} +``` + + + +## Example usage + +```rust + +fn main( + verification_key : [Field; 114], + proof : [Field; 93], + public_inputs : [Field; 1], + key_hash : Field, + proof_b : [Field; 93], +) { + std::verify_proof( + verification_key, + proof, + public_inputs, + key_hash + ); + + std::verify_proof( + verification_key, + proof_b, + public_inputs, + key_hash + ); +} +``` + +You can see a full example of recursive proofs in [this example recursion demo repo](https://github.com/noir-lang/noir-examples/tree/master/recursion). + +## Parameters + +### `verification_key` + +The verification key for the zk program that is being verified. + +### `proof` + +The proof for the zk program that is being verified. + +### `public_inputs` + +These represent the public inputs of the proof we are verifying. + +### `key_hash` + +A key hash is used to check the validity of the verification key. The circuit implementing this opcode can use this hash to ensure that the key provided to the circuit matches the key produced by the circuit creator. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/traits.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/traits.md new file mode 100644 index 000000000000..850cc129e73a --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/traits.md @@ -0,0 +1,501 @@ +--- +title: Traits +description: Noir's stdlib provides a few commonly used traits. +keywords: [traits, trait, interface, protocol, default, add, eq] +--- + +## `std::default` + +### `std::default::Default` + +```rust title="default-trait" showLineNumbers +trait Default { + fn default() -> Self; +} +``` +> Source code: noir_stdlib/src/default.nr#L4-L8 + + +Constructs a default value of a type. + +Implementations: +```rust +impl Default for Field { .. } + +impl Default for i8 { .. } +impl Default for i16 { .. } +impl Default for i32 { .. } +impl Default for i64 { .. } + +impl Default for u8 { .. } +impl Default for u16 { .. } +impl Default for u32 { .. } +impl Default for u64 { .. } + +impl Default for () { .. } +impl Default for bool { .. } + +impl Default for [T; N] + where T: Default { .. } + +impl Default for [T] { .. } + +impl Default for (A, B) + where A: Default, B: Default { .. } + +impl Default for (A, B, C) + where A: Default, B: Default, C: Default { .. } + +impl Default for (A, B, C, D) + where A: Default, B: Default, C: Default, D: Default { .. } + +impl Default for (A, B, C, D, E) + where A: Default, B: Default, C: Default, D: Default, E: Default { .. } +``` + +For primitive integer types, the return value of `default` is `0`. Container +types such as arrays are filled with default values of their element type, +except slices whose length is unknown and thus defaulted to zero. + +--- + +## `std::convert` + +### `std::convert::From` + +```rust title="from-trait" showLineNumbers +trait From { + fn from(input: T) -> Self; +} +``` +> Source code: noir_stdlib/src/convert.nr#L1-L5 + + +The `From` trait defines how to convert from a given type `T` to the type on which the trait is implemented. + +The Noir standard library provides a number of implementations of `From` between primitive types. +```rust title="from-impls" showLineNumbers +// Unsigned integers + +impl From for u32 { fn from(value: u8) -> u32 { value as u32 } } + +impl From for u64 { fn from(value: u8) -> u64 { value as u64 } } +impl From for u64 { fn from(value: u32) -> u64 { value as u64 } } + +impl From for Field { fn from(value: u8) -> Field { value as Field } } +impl From for Field { fn from(value: u32) -> Field { value as Field } } +impl From for Field { fn from(value: u64) -> Field { value as Field } } + +// Signed integers + +impl From for i32 { fn from(value: i8) -> i32 { value as i32 } } + +impl From for i64 { fn from(value: i8) -> i64 { value as i64 } } +impl From for i64 { fn from(value: i32) -> i64 { value as i64 } } + +// Booleans +impl From for u8 { fn from(value: bool) -> u8 { value as u8 } } +impl From for u32 { fn from(value: bool) -> u32 { value as u32 } } +impl From for u64 { fn from(value: bool) -> u64 { value as u64 } } +impl From for i8 { fn from(value: bool) -> i8 { value as i8 } } +impl From for i32 { fn from(value: bool) -> i32 { value as i32 } } +impl From for i64 { fn from(value: bool) -> i64 { value as i64 } } +impl From for Field { fn from(value: bool) -> Field { value as Field } } +``` +> Source code: noir_stdlib/src/convert.nr#L25-L52 + + +#### When to implement `From` + +As a general rule of thumb, `From` may be implemented in the [situations where it would be suitable in Rust](https://doc.rust-lang.org/std/convert/trait.From.html#when-to-implement-from): + +- The conversion is *infallible*: Noir does not provide an equivalent to Rust's `TryFrom`, if the conversion can fail then provide a named method instead. +- The conversion is *lossless*: semantically, it should not lose or discard information. For example, `u32: From` can losslessly convert any `u16` into a valid `u32` such that the original `u16` can be recovered. On the other hand, `u16: From` should not be implemented as `2**16` is a `u32` which cannot be losslessly converted into a `u16`. +- The conversion is *value-preserving*: the conceptual kind and meaning of the resulting value is the same, even though the Noir type and technical representation might be different. While it's possible to infallibly and losslessly convert a `u8` into a `str<2>` hex representation, `4u8` and `"04"` are too different for `str<2>: From` to be implemented. +- The conversion is *obvious*: it's the only reasonable conversion between the two types. If there's ambiguity on how to convert between them such that the same input could potentially map to two different values then a named method should be used. For instance rather than implementing `U128: From<[u8; 16]>`, the methods `U128::from_le_bytes` and `U128::from_be_bytes` are used as otherwise the endianness of the array would be ambiguous, resulting in two potential values of `U128` from the same byte array. + +One additional recommendation specific to Noir is: +- The conversion is *efficient*: it's relatively cheap to convert between the two types. Due to being a ZK DSL, it's more important to avoid unnecessary computation compared to Rust. If the implementation of `From` would encourage users to perform unnecessary conversion, resulting in additional proving time, then it may be preferable to expose functionality such that this conversion may be avoided. + +### `std::convert::Into` + +The `Into` trait is defined as the reciprocal of `From`. It should be easy to convince yourself that if we can convert to type `A` from type `B`, then it's possible to convert type `B` into type `A`. + +For this reason, implementing `From` on a type will automatically generate a matching `Into` implementation. One should always prefer implementing `From` over `Into` as implementing `Into` will not generate a matching `From` implementation. + +```rust title="into-trait" showLineNumbers +trait Into { + fn into(self) -> T; +} + +impl Into for U where T: From { + fn into(self) -> T { + T::from(self) + } +} +``` +> Source code: noir_stdlib/src/convert.nr#L13-L23 + + +`Into` is most useful when passing function arguments where the types don't quite match up with what the function expects. In this case, the compiler has enough type information to perform the necessary conversion by just appending `.into()` onto the arguments in question. + +--- + +## `std::cmp` + +### `std::cmp::Eq` + +```rust title="eq-trait" showLineNumbers +trait Eq { + fn eq(self, other: Self) -> bool; +} +``` +> Source code: noir_stdlib/src/cmp.nr#L4-L8 + + +Returns `true` if `self` is equal to `other`. Implementing this trait on a type +allows the type to be used with `==` and `!=`. + +Implementations: +```rust +impl Eq for Field { .. } + +impl Eq for i8 { .. } +impl Eq for i16 { .. } +impl Eq for i32 { .. } +impl Eq for i64 { .. } + +impl Eq for u8 { .. } +impl Eq for u16 { .. } +impl Eq for u32 { .. } +impl Eq for u64 { .. } + +impl Eq for () { .. } +impl Eq for bool { .. } + +impl Eq for [T; N] + where T: Eq { .. } + +impl Eq for [T] + where T: Eq { .. } + +impl Eq for (A, B) + where A: Eq, B: Eq { .. } + +impl Eq for (A, B, C) + where A: Eq, B: Eq, C: Eq { .. } + +impl Eq for (A, B, C, D) + where A: Eq, B: Eq, C: Eq, D: Eq { .. } + +impl Eq for (A, B, C, D, E) + where A: Eq, B: Eq, C: Eq, D: Eq, E: Eq { .. } +``` + +### `std::cmp::Ord` + +```rust title="ord-trait" showLineNumbers +trait Ord { + fn cmp(self, other: Self) -> Ordering; +} +``` +> Source code: noir_stdlib/src/cmp.nr#L113-L117 + + +`a.cmp(b)` compares two values returning `Ordering::less()` if `a < b`, +`Ordering::equal()` if `a == b`, or `Ordering::greater()` if `a > b`. +Implementing this trait on a type allows `<`, `<=`, `>`, and `>=` to be +used on values of the type. + +`std::cmp` also provides `max` and `min` functions for any type which implements the `Ord` trait. + +Implementations: + +```rust +impl Ord for u8 { .. } +impl Ord for u16 { .. } +impl Ord for u32 { .. } +impl Ord for u64 { .. } + +impl Ord for i8 { .. } +impl Ord for i16 { .. } +impl Ord for i32 { .. } + +impl Ord for i64 { .. } + +impl Ord for () { .. } +impl Ord for bool { .. } + +impl Ord for [T; N] + where T: Ord { .. } + +impl Ord for [T] + where T: Ord { .. } + +impl Ord for (A, B) + where A: Ord, B: Ord { .. } + +impl Ord for (A, B, C) + where A: Ord, B: Ord, C: Ord { .. } + +impl Ord for (A, B, C, D) + where A: Ord, B: Ord, C: Ord, D: Ord { .. } + +impl Ord for (A, B, C, D, E) + where A: Ord, B: Ord, C: Ord, D: Ord, E: Ord { .. } +``` + +--- + +## `std::ops` + +### `std::ops::Add`, `std::ops::Sub`, `std::ops::Mul`, and `std::ops::Div` + +These traits abstract over addition, subtraction, multiplication, and division respectively. +Implementing these traits for a given type will also allow that type to be used with the corresponding operator +for that trait (`+` for Add, etc) in addition to the normal method names. + +```rust title="add-trait" showLineNumbers +trait Add { + fn add(self, other: Self) -> Self; +} +``` +> Source code: noir_stdlib/src/ops/arith.nr#L1-L5 + +```rust title="sub-trait" showLineNumbers +trait Sub { + fn sub(self, other: Self) -> Self; +} +``` +> Source code: noir_stdlib/src/ops/arith.nr#L19-L23 + +```rust title="mul-trait" showLineNumbers +trait Mul { + fn mul(self, other: Self) -> Self; +} +``` +> Source code: noir_stdlib/src/ops/arith.nr#L37-L41 + +```rust title="div-trait" showLineNumbers +trait Div { + fn div(self, other: Self) -> Self; +} +``` +> Source code: noir_stdlib/src/ops/arith.nr#L55-L59 + + +The implementations block below is given for the `Add` trait, but the same types that implement +`Add` also implement `Sub`, `Mul`, and `Div`. + +Implementations: +```rust +impl Add for Field { .. } + +impl Add for i8 { .. } +impl Add for i16 { .. } +impl Add for i32 { .. } +impl Add for i64 { .. } + +impl Add for u8 { .. } +impl Add for u16 { .. } +impl Add for u32 { .. } +impl Add for u64 { .. } +``` + +### `std::ops::Rem` + +```rust title="rem-trait" showLineNumbers +trait Rem{ + fn rem(self, other: Self) -> Self; +} +``` +> Source code: noir_stdlib/src/ops/arith.nr#L73-L77 + + +`Rem::rem(a, b)` is the remainder function returning the result of what is +left after dividing `a` and `b`. Implementing `Rem` allows the `%` operator +to be used with the implementation type. + +Unlike other numeric traits, `Rem` is not implemented for `Field`. + +Implementations: +```rust +impl Rem for u8 { fn rem(self, other: u8) -> u8 { self % other } } +impl Rem for u16 { fn rem(self, other: u16) -> u16 { self % other } } +impl Rem for u32 { fn rem(self, other: u32) -> u32 { self % other } } +impl Rem for u64 { fn rem(self, other: u64) -> u64 { self % other } } + +impl Rem for i8 { fn rem(self, other: i8) -> i8 { self % other } } +impl Rem for i16 { fn rem(self, other: i16) -> i16 { self % other } } +impl Rem for i32 { fn rem(self, other: i32) -> i32 { self % other } } +impl Rem for i64 { fn rem(self, other: i64) -> i64 { self % other } } +``` + +### `std::ops::Neg` + +```rust title="neg-trait" showLineNumbers +trait Neg { + fn neg(self) -> Self; +} +``` +> Source code: noir_stdlib/src/ops/arith.nr#L89-L93 + + +`Neg::neg` is equivalent to the unary negation operator `-`. + +Implementations: +```rust title="neg-trait-impls" showLineNumbers +impl Neg for Field { fn neg(self) -> Field { -self } } + +impl Neg for i8 { fn neg(self) -> i8 { -self } } +impl Neg for i16 { fn neg(self) -> i16 { -self } } +impl Neg for i32 { fn neg(self) -> i32 { -self } } +impl Neg for i64 { fn neg(self) -> i64 { -self } } +``` +> Source code: noir_stdlib/src/ops/arith.nr#L95-L102 + + +### `std::ops::Not` + +```rust title="not-trait" showLineNumbers +trait Not { + fn not(self: Self) -> Self; +} +``` +> Source code: noir_stdlib/src/ops/bit.nr#L1-L5 + + +`Not::not` is equivalent to the unary bitwise NOT operator `!`. + +Implementations: +```rust title="not-trait-impls" showLineNumbers +impl Not for bool { fn not(self) -> bool { !self } } + +impl Not for u64 { fn not(self) -> u64 { !self } } +impl Not for u32 { fn not(self) -> u32 { !self } } +impl Not for u16 { fn not(self) -> u16 { !self } } +impl Not for u8 { fn not(self) -> u8 { !self } } +impl Not for u1 { fn not(self) -> u1 { !self } } + +impl Not for i8 { fn not(self) -> i8 { !self } } +impl Not for i16 { fn not(self) -> i16 { !self } } +impl Not for i32 { fn not(self) -> i32 { !self } } +impl Not for i64 { fn not(self) -> i64 { !self } } +``` +> Source code: noir_stdlib/src/ops/bit.nr#L7-L20 + + +### `std::ops::{ BitOr, BitAnd, BitXor }` + +```rust title="bitor-trait" showLineNumbers +trait BitOr { + fn bitor(self, other: Self) -> Self; +} +``` +> Source code: noir_stdlib/src/ops/bit.nr#L22-L26 + +```rust title="bitand-trait" showLineNumbers +trait BitAnd { + fn bitand(self, other: Self) -> Self; +} +``` +> Source code: noir_stdlib/src/ops/bit.nr#L40-L44 + +```rust title="bitxor-trait" showLineNumbers +trait BitXor { + fn bitxor(self, other: Self) -> Self; +} +``` +> Source code: noir_stdlib/src/ops/bit.nr#L58-L62 + + +Traits for the bitwise operations `|`, `&`, and `^`. + +Implementing `BitOr`, `BitAnd` or `BitXor` for a type allows the `|`, `&`, or `^` operator respectively +to be used with the type. + +The implementations block below is given for the `BitOr` trait, but the same types that implement +`BitOr` also implement `BitAnd` and `BitXor`. + +Implementations: +```rust +impl BitOr for bool { fn bitor(self, other: bool) -> bool { self | other } } + +impl BitOr for u8 { fn bitor(self, other: u8) -> u8 { self | other } } +impl BitOr for u16 { fn bitor(self, other: u16) -> u16 { self | other } } +impl BitOr for u32 { fn bitor(self, other: u32) -> u32 { self | other } } +impl BitOr for u64 { fn bitor(self, other: u64) -> u64 { self | other } } + +impl BitOr for i8 { fn bitor(self, other: i8) -> i8 { self | other } } +impl BitOr for i16 { fn bitor(self, other: i16) -> i16 { self | other } } +impl BitOr for i32 { fn bitor(self, other: i32) -> i32 { self | other } } +impl BitOr for i64 { fn bitor(self, other: i64) -> i64 { self | other } } +``` + +### `std::ops::{ Shl, Shr }` + +```rust title="shl-trait" showLineNumbers +trait Shl { + fn shl(self, other: u8) -> Self; +} +``` +> Source code: noir_stdlib/src/ops/bit.nr#L76-L80 + +```rust title="shr-trait" showLineNumbers +trait Shr { + fn shr(self, other: u8) -> Self; +} +``` +> Source code: noir_stdlib/src/ops/bit.nr#L93-L97 + + +Traits for a bit shift left and bit shift right. + +Implementing `Shl` for a type allows the left shift operator (`<<`) to be used with the implementation type. +Similarly, implementing `Shr` allows the right shift operator (`>>`) to be used with the type. + +Note that bit shifting is not currently implemented for signed types. + +The implementations block below is given for the `Shl` trait, but the same types that implement +`Shl` also implement `Shr`. + +Implementations: +```rust +impl Shl for u8 { fn shl(self, other: u8) -> u8 { self << other } } +impl Shl for u16 { fn shl(self, other: u16) -> u16 { self << other } } +impl Shl for u32 { fn shl(self, other: u32) -> u32 { self << other } } +impl Shl for u64 { fn shl(self, other: u64) -> u64 { self << other } } +``` + +--- + +## `std::append` + +### `std::append::Append` + +`Append` can abstract over types that can be appended to - usually container types: + +```rust title="append-trait" showLineNumbers +trait Append { + fn empty() -> Self; + fn append(self, other: Self) -> Self; +} +``` +> Source code: noir_stdlib/src/append.nr#L9-L14 + + +`Append` requires two methods: + +- `empty`: Constructs an empty value of `Self`. +- `append`: Append two values together, returning the result. + +Additionally, it is expected that for any implementation: + +- `T::empty().append(x) == x` +- `x.append(T::empty()) == x` + +Implementations: +```rust +impl Append for [T] +impl Append for Quoted +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/zeroed.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/zeroed.md new file mode 100644 index 000000000000..f450fecdd364 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/zeroed.md @@ -0,0 +1,26 @@ +--- +title: Zeroed Function +description: + The zeroed function returns a zeroed value of any type. +keywords: + [ + zeroed + ] +--- + +Implements `fn zeroed() -> T` to return a zeroed value of any type. This function is generally unsafe to use as the zeroed bit pattern is not guaranteed to be valid for all types. It can however, be useful in cases when the value is guaranteed not to be used such as in a BoundedVec library implementing a growable vector, up to a certain length, backed by an array. The array can be initialized with zeroed values which are guaranteed to be inaccessible until the vector is pushed to. Similarly, enumerations in noir can be implemented using this method by providing zeroed values for the unused variants. + +You can access the function at `std::unsafe::zeroed`. + +This function currently supports the following types: + +- Field +- Bool +- Uint +- Array +- Slice +- String +- Tuple +- Function + +Using it on other types could result in unexpected behavior. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/backend_barretenberg/.nojekyll b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/backend_barretenberg/.nojekyll new file mode 100644 index 000000000000..e2ac6616addc --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/backend_barretenberg/.nojekyll @@ -0,0 +1 @@ +TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/backend_barretenberg/classes/BarretenbergBackend.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/backend_barretenberg/classes/BarretenbergBackend.md new file mode 100644 index 000000000000..42f065f4a4e7 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/backend_barretenberg/classes/BarretenbergBackend.md @@ -0,0 +1,141 @@ +# BarretenbergBackend + +## Implements + +- [`Backend`](../index.md#backend) +- [`Backend`](../index.md#backend) + +## Constructors + +### new BarretenbergBackend(acirCircuit, options) + +```ts +new BarretenbergBackend(acirCircuit, options): BarretenbergBackend +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `acirCircuit` | `CompiledCircuit` | +| `options` | [`BackendOptions`](../type-aliases/BackendOptions.md) | + +#### Returns + +[`BarretenbergBackend`](BarretenbergBackend.md) + +## Properties + +| Property | Type | Description | +| :------ | :------ | :------ | +| `acirComposer` | `any` | - | +| `acirUncompressedBytecode` | `Uint8Array` | - | +| `api` | `Barretenberg` | - | +| `options` | [`BackendOptions`](../type-aliases/BackendOptions.md) | - | + +## Methods + +### destroy() + +```ts +destroy(): Promise +``` + +#### Returns + +`Promise`\<`void`\> + +*** + +### generateProof() + +```ts +generateProof(compressedWitness): Promise +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `compressedWitness` | `Uint8Array` | + +#### Returns + +`Promise`\<`ProofData`\> + +#### Description + +Generates a proof + +*** + +### generateRecursiveProofArtifacts() + +```ts +generateRecursiveProofArtifacts(proofData, numOfPublicInputs): Promise +``` + +Generates artifacts that will be passed to a circuit that will verify this proof. + +Instead of passing the proof and verification key as a byte array, we pass them +as fields which makes it cheaper to verify in a circuit. + +The proof that is passed here will have been created using a circuit +that has the #[recursive] attribute on its `main` method. + +The number of public inputs denotes how many public inputs are in the inner proof. + +#### Parameters + +| Parameter | Type | Default value | +| :------ | :------ | :------ | +| `proofData` | `ProofData` | `undefined` | +| `numOfPublicInputs` | `number` | `0` | + +#### Returns + +`Promise`\<`object`\> + +#### Example + +```typescript +const artifacts = await backend.generateRecursiveProofArtifacts(proof, numOfPublicInputs); +``` + +*** + +### getVerificationKey() + +```ts +getVerificationKey(): Promise +``` + +#### Returns + +`Promise`\<`Uint8Array`\> + +*** + +### verifyProof() + +```ts +verifyProof(proofData): Promise +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `proofData` | `ProofData` | + +#### Returns + +`Promise`\<`boolean`\> + +#### Description + +Verifies a proof + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/backend_barretenberg/classes/BarretenbergVerifier.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/backend_barretenberg/classes/BarretenbergVerifier.md new file mode 100644 index 000000000000..500276ea7480 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/backend_barretenberg/classes/BarretenbergVerifier.md @@ -0,0 +1,58 @@ +# BarretenbergVerifier + +## Constructors + +### new BarretenbergVerifier(options) + +```ts +new BarretenbergVerifier(options): BarretenbergVerifier +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `options` | [`BackendOptions`](../type-aliases/BackendOptions.md) | + +#### Returns + +[`BarretenbergVerifier`](BarretenbergVerifier.md) + +## Methods + +### destroy() + +```ts +destroy(): Promise +``` + +#### Returns + +`Promise`\<`void`\> + +*** + +### verifyProof() + +```ts +verifyProof(proofData, verificationKey): Promise +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `proofData` | `ProofData` | +| `verificationKey` | `Uint8Array` | + +#### Returns + +`Promise`\<`boolean`\> + +#### Description + +Verifies a proof + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/backend_barretenberg/classes/UltraHonkBackend.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/backend_barretenberg/classes/UltraHonkBackend.md new file mode 100644 index 000000000000..be1cd9ad4653 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/backend_barretenberg/classes/UltraHonkBackend.md @@ -0,0 +1,116 @@ +# UltraHonkBackend + +## Implements + +- [`Backend`](../index.md#backend) +- [`Backend`](../index.md#backend) + +## Constructors + +### new UltraHonkBackend(acirCircuit, options) + +```ts +new UltraHonkBackend(acirCircuit, options): UltraHonkBackend +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `acirCircuit` | `CompiledCircuit` | +| `options` | [`BackendOptions`](../type-aliases/BackendOptions.md) | + +#### Returns + +[`UltraHonkBackend`](UltraHonkBackend.md) + +## Properties + +| Property | Type | Description | +| :------ | :------ | :------ | +| `acirUncompressedBytecode` | `Uint8Array` | - | +| `api` | `Barretenberg` | - | +| `options` | [`BackendOptions`](../type-aliases/BackendOptions.md) | - | + +## Methods + +### destroy() + +```ts +destroy(): Promise +``` + +#### Returns + +`Promise`\<`void`\> + +*** + +### generateProof() + +```ts +generateProof(decompressedWitness): Promise +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `decompressedWitness` | `Uint8Array` | + +#### Returns + +`Promise`\<`ProofData`\> + +*** + +### generateRecursiveProofArtifacts() + +```ts +generateRecursiveProofArtifacts(_proofData, _numOfPublicInputs): Promise +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `_proofData` | `ProofData` | +| `_numOfPublicInputs` | `number` | + +#### Returns + +`Promise`\<`object`\> + +*** + +### getVerificationKey() + +```ts +getVerificationKey(): Promise +``` + +#### Returns + +`Promise`\<`Uint8Array`\> + +*** + +### verifyProof() + +```ts +verifyProof(proofData): Promise +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `proofData` | `ProofData` | + +#### Returns + +`Promise`\<`boolean`\> + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/backend_barretenberg/classes/UltraHonkVerifier.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/backend_barretenberg/classes/UltraHonkVerifier.md new file mode 100644 index 000000000000..aee9460153f3 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/backend_barretenberg/classes/UltraHonkVerifier.md @@ -0,0 +1,58 @@ +# UltraHonkVerifier + +## Constructors + +### new UltraHonkVerifier(options) + +```ts +new UltraHonkVerifier(options): UltraHonkVerifier +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `options` | [`BackendOptions`](../type-aliases/BackendOptions.md) | + +#### Returns + +[`UltraHonkVerifier`](UltraHonkVerifier.md) + +## Methods + +### destroy() + +```ts +destroy(): Promise +``` + +#### Returns + +`Promise`\<`void`\> + +*** + +### verifyProof() + +```ts +verifyProof(proofData, verificationKey): Promise +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `proofData` | `ProofData` | +| `verificationKey` | `Uint8Array` | + +#### Returns + +`Promise`\<`boolean`\> + +#### Description + +Verifies a proof + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/backend_barretenberg/index.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/backend_barretenberg/index.md new file mode 100644 index 000000000000..4699e16dee67 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/backend_barretenberg/index.md @@ -0,0 +1,42 @@ +# backend_barretenberg + +## Exports + +### Classes + +| Class | Description | +| :------ | :------ | +| [BarretenbergBackend](classes/BarretenbergBackend.md) | - | +| [BarretenbergVerifier](classes/BarretenbergVerifier.md) | - | +| [UltraHonkBackend](classes/UltraHonkBackend.md) | - | +| [UltraHonkVerifier](classes/UltraHonkVerifier.md) | - | + +### Type Aliases + +| Type alias | Description | +| :------ | :------ | +| [BackendOptions](type-aliases/BackendOptions.md) | - | + +## References + +### CompiledCircuit + +Renames and re-exports [Backend](index.md#backend) + +*** + +### ProofData + +Renames and re-exports [Backend](index.md#backend) + +## Variables + +### Backend + +```ts +Backend: any; +``` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/backend_barretenberg/type-aliases/BackendOptions.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/backend_barretenberg/type-aliases/BackendOptions.md new file mode 100644 index 000000000000..b49a479f4f46 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/backend_barretenberg/type-aliases/BackendOptions.md @@ -0,0 +1,21 @@ +# BackendOptions + +```ts +type BackendOptions: object; +``` + +## Description + +An options object, currently only used to specify the number of threads to use. + +## Type declaration + +| Member | Type | Description | +| :------ | :------ | :------ | +| `memory` | `object` | - | +| `memory.maximum` | `number` | - | +| `threads` | `number` | **Description**

Number of threads | + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/backend_barretenberg/typedoc-sidebar.cjs b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/backend_barretenberg/typedoc-sidebar.cjs new file mode 100644 index 000000000000..8ecf05c0163b --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/backend_barretenberg/typedoc-sidebar.cjs @@ -0,0 +1,4 @@ +// @ts-check +/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ +const typedocSidebar = { items: [{"type":"category","label":"Classes","items":[{"type":"doc","id":"reference/NoirJS/backend_barretenberg/classes/BarretenbergBackend","label":"BarretenbergBackend"},{"type":"doc","id":"reference/NoirJS/backend_barretenberg/classes/BarretenbergVerifier","label":"BarretenbergVerifier"},{"type":"doc","id":"reference/NoirJS/backend_barretenberg/classes/UltraHonkBackend","label":"UltraHonkBackend"},{"type":"doc","id":"reference/NoirJS/backend_barretenberg/classes/UltraHonkVerifier","label":"UltraHonkVerifier"}]},{"type":"category","label":"Type Aliases","items":[{"type":"doc","id":"reference/NoirJS/backend_barretenberg/type-aliases/BackendOptions","label":"BackendOptions"}]}]}; +module.exports = typedocSidebar.items; \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/.nojekyll b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/.nojekyll new file mode 100644 index 000000000000..e2ac6616addc --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/.nojekyll @@ -0,0 +1 @@ +TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/classes/Noir.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/classes/Noir.md new file mode 100644 index 000000000000..ead255bc5047 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/classes/Noir.md @@ -0,0 +1,52 @@ +# Noir + +## Constructors + +### new Noir(circuit) + +```ts +new Noir(circuit): Noir +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `circuit` | `CompiledCircuit` | + +#### Returns + +[`Noir`](Noir.md) + +## Methods + +### execute() + +```ts +execute(inputs, foreignCallHandler?): Promise +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `inputs` | `InputMap` | +| `foreignCallHandler`? | [`ForeignCallHandler`](../type-aliases/ForeignCallHandler.md) | + +#### Returns + +`Promise`\<`object`\> + +#### Description + +Allows to execute a circuit to get its witness and return value. + +#### Example + +```typescript +async execute(inputs) +``` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/functions/and.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/functions/and.md new file mode 100644 index 000000000000..c783283e3965 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/functions/and.md @@ -0,0 +1,22 @@ +# and() + +```ts +and(lhs, rhs): string +``` + +Performs a bitwise AND operation between `lhs` and `rhs` + +## Parameters + +| Parameter | Type | Description | +| :------ | :------ | :------ | +| `lhs` | `string` | | +| `rhs` | `string` | | + +## Returns + +`string` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/functions/blake2s256.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/functions/blake2s256.md new file mode 100644 index 000000000000..7882d0da8d50 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/functions/blake2s256.md @@ -0,0 +1,21 @@ +# blake2s256() + +```ts +blake2s256(inputs): Uint8Array +``` + +Calculates the Blake2s256 hash of the input bytes + +## Parameters + +| Parameter | Type | Description | +| :------ | :------ | :------ | +| `inputs` | `Uint8Array` | | + +## Returns + +`Uint8Array` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/functions/ecdsa_secp256k1_verify.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/functions/ecdsa_secp256k1_verify.md new file mode 100644 index 000000000000..5e3cd53e9d36 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/functions/ecdsa_secp256k1_verify.md @@ -0,0 +1,28 @@ +# ecdsa\_secp256k1\_verify() + +```ts +ecdsa_secp256k1_verify( + hashed_msg, + public_key_x_bytes, + public_key_y_bytes, + signature): boolean +``` + +Verifies a ECDSA signature over the secp256k1 curve. + +## Parameters + +| Parameter | Type | Description | +| :------ | :------ | :------ | +| `hashed_msg` | `Uint8Array` | | +| `public_key_x_bytes` | `Uint8Array` | | +| `public_key_y_bytes` | `Uint8Array` | | +| `signature` | `Uint8Array` | | + +## Returns + +`boolean` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/functions/ecdsa_secp256r1_verify.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/functions/ecdsa_secp256r1_verify.md new file mode 100644 index 000000000000..0b20ff689575 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/functions/ecdsa_secp256r1_verify.md @@ -0,0 +1,28 @@ +# ecdsa\_secp256r1\_verify() + +```ts +ecdsa_secp256r1_verify( + hashed_msg, + public_key_x_bytes, + public_key_y_bytes, + signature): boolean +``` + +Verifies a ECDSA signature over the secp256r1 curve. + +## Parameters + +| Parameter | Type | Description | +| :------ | :------ | :------ | +| `hashed_msg` | `Uint8Array` | | +| `public_key_x_bytes` | `Uint8Array` | | +| `public_key_y_bytes` | `Uint8Array` | | +| `signature` | `Uint8Array` | | + +## Returns + +`boolean` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/functions/keccak256.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/functions/keccak256.md new file mode 100644 index 000000000000..d10f155ce86f --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/functions/keccak256.md @@ -0,0 +1,21 @@ +# keccak256() + +```ts +keccak256(inputs): Uint8Array +``` + +Calculates the Keccak256 hash of the input bytes + +## Parameters + +| Parameter | Type | Description | +| :------ | :------ | :------ | +| `inputs` | `Uint8Array` | | + +## Returns + +`Uint8Array` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/functions/sha256.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/functions/sha256.md new file mode 100644 index 000000000000..6ba4ecac0229 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/functions/sha256.md @@ -0,0 +1,21 @@ +# sha256() + +```ts +sha256(inputs): Uint8Array +``` + +Calculates the SHA256 hash of the input bytes + +## Parameters + +| Parameter | Type | Description | +| :------ | :------ | :------ | +| `inputs` | `Uint8Array` | | + +## Returns + +`Uint8Array` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/functions/xor.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/functions/xor.md new file mode 100644 index 000000000000..8d762b895d30 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/functions/xor.md @@ -0,0 +1,22 @@ +# xor() + +```ts +xor(lhs, rhs): string +``` + +Performs a bitwise XOR operation between `lhs` and `rhs` + +## Parameters + +| Parameter | Type | Description | +| :------ | :------ | :------ | +| `lhs` | `string` | | +| `rhs` | `string` | | + +## Returns + +`string` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/index.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/index.md new file mode 100644 index 000000000000..166508f71244 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/index.md @@ -0,0 +1,49 @@ +# noir_js + +## Exports + +### Classes + +| Class | Description | +| :------ | :------ | +| [Noir](classes/Noir.md) | - | + +### Type Aliases + +| Type alias | Description | +| :------ | :------ | +| [ErrorWithPayload](type-aliases/ErrorWithPayload.md) | - | +| [ForeignCallHandler](type-aliases/ForeignCallHandler.md) | A callback which performs an foreign call and returns the response. | +| [ForeignCallInput](type-aliases/ForeignCallInput.md) | - | +| [ForeignCallOutput](type-aliases/ForeignCallOutput.md) | - | +| [WitnessMap](type-aliases/WitnessMap.md) | - | + +### Functions + +| Function | Description | +| :------ | :------ | +| [and](functions/and.md) | Performs a bitwise AND operation between `lhs` and `rhs` | +| [blake2s256](functions/blake2s256.md) | Calculates the Blake2s256 hash of the input bytes | +| [ecdsa\_secp256k1\_verify](functions/ecdsa_secp256k1_verify.md) | Verifies a ECDSA signature over the secp256k1 curve. | +| [ecdsa\_secp256r1\_verify](functions/ecdsa_secp256r1_verify.md) | Verifies a ECDSA signature over the secp256r1 curve. | +| [keccak256](functions/keccak256.md) | Calculates the Keccak256 hash of the input bytes | +| [sha256](functions/sha256.md) | Calculates the SHA256 hash of the input bytes | +| [xor](functions/xor.md) | Performs a bitwise XOR operation between `lhs` and `rhs` | + +## References + +### CompiledCircuit + +Renames and re-exports [InputMap](index.md#inputmap) + +## Variables + +### InputMap + +```ts +InputMap: any; +``` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/type-aliases/ErrorWithPayload.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/type-aliases/ErrorWithPayload.md new file mode 100644 index 000000000000..e8c2f4aef3d5 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/type-aliases/ErrorWithPayload.md @@ -0,0 +1,15 @@ +# ErrorWithPayload + +```ts +type ErrorWithPayload: ExecutionError & object; +``` + +## Type declaration + +| Member | Type | Description | +| :------ | :------ | :------ | +| `decodedAssertionPayload` | `any` | - | + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/type-aliases/ForeignCallHandler.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/type-aliases/ForeignCallHandler.md new file mode 100644 index 000000000000..812b8b164818 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/type-aliases/ForeignCallHandler.md @@ -0,0 +1,24 @@ +# ForeignCallHandler + +```ts +type ForeignCallHandler: (name, inputs) => Promise; +``` + +A callback which performs an foreign call and returns the response. + +## Parameters + +| Parameter | Type | Description | +| :------ | :------ | :------ | +| `name` | `string` | The identifier for the type of foreign call being performed. | +| `inputs` | [`ForeignCallInput`](ForeignCallInput.md)[] | An array of hex encoded inputs to the foreign call. | + +## Returns + +`Promise`\<[`ForeignCallOutput`](ForeignCallOutput.md)[]\> + +outputs - An array of hex encoded outputs containing the results of the foreign call. + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/type-aliases/ForeignCallInput.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/type-aliases/ForeignCallInput.md new file mode 100644 index 000000000000..dd95809186a2 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/type-aliases/ForeignCallInput.md @@ -0,0 +1,9 @@ +# ForeignCallInput + +```ts +type ForeignCallInput: string[]; +``` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/type-aliases/ForeignCallOutput.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/type-aliases/ForeignCallOutput.md new file mode 100644 index 000000000000..b71fb78a9469 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/type-aliases/ForeignCallOutput.md @@ -0,0 +1,9 @@ +# ForeignCallOutput + +```ts +type ForeignCallOutput: string | string[]; +``` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/type-aliases/WitnessMap.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/type-aliases/WitnessMap.md new file mode 100644 index 000000000000..258c46f9d0c9 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/type-aliases/WitnessMap.md @@ -0,0 +1,9 @@ +# WitnessMap + +```ts +type WitnessMap: Map; +``` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/typedoc-sidebar.cjs b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/typedoc-sidebar.cjs new file mode 100644 index 000000000000..b3156097df61 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_js/typedoc-sidebar.cjs @@ -0,0 +1,4 @@ +// @ts-check +/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ +const typedocSidebar = { items: [{"type":"category","label":"Classes","items":[{"type":"doc","id":"reference/NoirJS/noir_js/classes/Noir","label":"Noir"}]},{"type":"category","label":"Type Aliases","items":[{"type":"doc","id":"reference/NoirJS/noir_js/type-aliases/ErrorWithPayload","label":"ErrorWithPayload"},{"type":"doc","id":"reference/NoirJS/noir_js/type-aliases/ForeignCallHandler","label":"ForeignCallHandler"},{"type":"doc","id":"reference/NoirJS/noir_js/type-aliases/ForeignCallInput","label":"ForeignCallInput"},{"type":"doc","id":"reference/NoirJS/noir_js/type-aliases/ForeignCallOutput","label":"ForeignCallOutput"},{"type":"doc","id":"reference/NoirJS/noir_js/type-aliases/WitnessMap","label":"WitnessMap"}]},{"type":"category","label":"Functions","items":[{"type":"doc","id":"reference/NoirJS/noir_js/functions/and","label":"and"},{"type":"doc","id":"reference/NoirJS/noir_js/functions/blake2s256","label":"blake2s256"},{"type":"doc","id":"reference/NoirJS/noir_js/functions/ecdsa_secp256k1_verify","label":"ecdsa_secp256k1_verify"},{"type":"doc","id":"reference/NoirJS/noir_js/functions/ecdsa_secp256r1_verify","label":"ecdsa_secp256r1_verify"},{"type":"doc","id":"reference/NoirJS/noir_js/functions/keccak256","label":"keccak256"},{"type":"doc","id":"reference/NoirJS/noir_js/functions/sha256","label":"sha256"},{"type":"doc","id":"reference/NoirJS/noir_js/functions/xor","label":"xor"}]}]}; +module.exports = typedocSidebar.items; \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_wasm/.nojekyll b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_wasm/.nojekyll new file mode 100644 index 000000000000..e2ac6616addc --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_wasm/.nojekyll @@ -0,0 +1 @@ +TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_wasm/functions/compile.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_wasm/functions/compile.md new file mode 100644 index 000000000000..6faf763b37f7 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_wasm/functions/compile.md @@ -0,0 +1,51 @@ +# compile() + +```ts +compile( + fileManager, + projectPath?, + logFn?, +debugLogFn?): Promise +``` + +Compiles a Noir project + +## Parameters + +| Parameter | Type | Description | +| :------ | :------ | :------ | +| `fileManager` | `FileManager` | The file manager to use | +| `projectPath`? | `string` | The path to the project inside the file manager. Defaults to the root of the file manager | +| `logFn`? | `LogFn` | A logging function. If not provided, console.log will be used | +| `debugLogFn`? | `LogFn` | A debug logging function. If not provided, logFn will be used | + +## Returns + +`Promise`\<[`ProgramCompilationArtifacts`](../index.md#programcompilationartifacts)\> + +## Example + +```typescript +// Node.js + +import { compile_program, createFileManager } from '@noir-lang/noir_wasm'; + +const fm = createFileManager(myProjectPath); +const myCompiledCode = await compile_program(fm); +``` + +```typescript +// Browser + +import { compile_program, createFileManager } from '@noir-lang/noir_wasm'; + +const fm = createFileManager('/'); +for (const path of files) { + await fm.writeFile(path, await getFileAsStream(path)); +} +const myCompiledCode = await compile_program(fm); +``` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_wasm/functions/compile_contract.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_wasm/functions/compile_contract.md new file mode 100644 index 000000000000..7d0b39a43ef8 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_wasm/functions/compile_contract.md @@ -0,0 +1,51 @@ +# compile\_contract() + +```ts +compile_contract( + fileManager, + projectPath?, + logFn?, +debugLogFn?): Promise +``` + +Compiles a Noir project + +## Parameters + +| Parameter | Type | Description | +| :------ | :------ | :------ | +| `fileManager` | `FileManager` | The file manager to use | +| `projectPath`? | `string` | The path to the project inside the file manager. Defaults to the root of the file manager | +| `logFn`? | `LogFn` | A logging function. If not provided, console.log will be used | +| `debugLogFn`? | `LogFn` | A debug logging function. If not provided, logFn will be used | + +## Returns + +`Promise`\<[`ContractCompilationArtifacts`](../index.md#contractcompilationartifacts)\> + +## Example + +```typescript +// Node.js + +import { compile_contract, createFileManager } from '@noir-lang/noir_wasm'; + +const fm = createFileManager(myProjectPath); +const myCompiledCode = await compile_contract(fm); +``` + +```typescript +// Browser + +import { compile_contract, createFileManager } from '@noir-lang/noir_wasm'; + +const fm = createFileManager('/'); +for (const path of files) { + await fm.writeFile(path, await getFileAsStream(path)); +} +const myCompiledCode = await compile_contract(fm); +``` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_wasm/functions/createFileManager.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_wasm/functions/createFileManager.md new file mode 100644 index 000000000000..7e65c1d69c7e --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_wasm/functions/createFileManager.md @@ -0,0 +1,21 @@ +# createFileManager() + +```ts +createFileManager(dataDir): FileManager +``` + +Creates a new FileManager instance based on fs in node and memfs in the browser (via webpack alias) + +## Parameters + +| Parameter | Type | Description | +| :------ | :------ | :------ | +| `dataDir` | `string` | root of the file system | + +## Returns + +`FileManager` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_wasm/functions/inflateDebugSymbols.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_wasm/functions/inflateDebugSymbols.md new file mode 100644 index 000000000000..fcea92753412 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_wasm/functions/inflateDebugSymbols.md @@ -0,0 +1,21 @@ +# inflateDebugSymbols() + +```ts +inflateDebugSymbols(debugSymbols): any +``` + +Decompresses and decodes the debug symbols + +## Parameters + +| Parameter | Type | Description | +| :------ | :------ | :------ | +| `debugSymbols` | `string` | The base64 encoded debug symbols | + +## Returns + +`any` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_wasm/index.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_wasm/index.md new file mode 100644 index 000000000000..b6e0f9d1bc0e --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_wasm/index.md @@ -0,0 +1,49 @@ +# noir_wasm + +## Exports + +### Functions + +| Function | Description | +| :------ | :------ | +| [compile](functions/compile.md) | Compiles a Noir project | +| [compile\_contract](functions/compile_contract.md) | Compiles a Noir project | +| [createFileManager](functions/createFileManager.md) | Creates a new FileManager instance based on fs in node and memfs in the browser (via webpack alias) | +| [inflateDebugSymbols](functions/inflateDebugSymbols.md) | Decompresses and decodes the debug symbols | + +## References + +### compile\_program + +Renames and re-exports [compile](functions/compile.md) + +## Interfaces + +### ContractCompilationArtifacts + +The compilation artifacts of a given contract. + +#### Properties + +| Property | Type | Description | +| :------ | :------ | :------ | +| `contract` | `ContractArtifact` | The compiled contract. | +| `warnings` | `unknown`[] | Compilation warnings. | + +*** + +### ProgramCompilationArtifacts + +The compilation artifacts of a given program. + +#### Properties + +| Property | Type | Description | +| :------ | :------ | :------ | +| `name` | `string` | not part of the compilation output, injected later | +| `program` | `ProgramArtifact` | The compiled contract. | +| `warnings` | `unknown`[] | Compilation warnings. | + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_wasm/typedoc-sidebar.cjs b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_wasm/typedoc-sidebar.cjs new file mode 100644 index 000000000000..e0870710349c --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/NoirJS/noir_wasm/typedoc-sidebar.cjs @@ -0,0 +1,4 @@ +// @ts-check +/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ +const typedocSidebar = { items: [{"type":"doc","id":"reference/NoirJS/noir_wasm/index","label":"API"},{"type":"category","label":"Functions","items":[{"type":"doc","id":"reference/NoirJS/noir_wasm/functions/compile","label":"compile"},{"type":"doc","id":"reference/NoirJS/noir_wasm/functions/compile_contract","label":"compile_contract"},{"type":"doc","id":"reference/NoirJS/noir_wasm/functions/createFileManager","label":"createFileManager"},{"type":"doc","id":"reference/NoirJS/noir_wasm/functions/inflateDebugSymbols","label":"inflateDebugSymbols"}]}]}; +module.exports = typedocSidebar.items; \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/_category_.json b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/_category_.json new file mode 100644 index 000000000000..5b6a20a609af --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/_category_.json @@ -0,0 +1,5 @@ +{ + "position": 4, + "collapsible": true, + "collapsed": true +} diff --git a/noir/noir-repo/docs/docs/getting_started/barretenberg/_category_.json b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/debugger/_category_.json similarity index 65% rename from noir/noir-repo/docs/docs/getting_started/barretenberg/_category_.json rename to noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/debugger/_category_.json index 27a8e89228de..27869205ad3c 100644 --- a/noir/noir-repo/docs/docs/getting_started/barretenberg/_category_.json +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/debugger/_category_.json @@ -1,6 +1,6 @@ { + "label": "Debugger", "position": 1, - "label": "Install Barretenberg", "collapsible": true, "collapsed": true } diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/debugger/debugger_known_limitations.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/debugger/debugger_known_limitations.md new file mode 100644 index 000000000000..936d416ac4bc --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/debugger/debugger_known_limitations.md @@ -0,0 +1,59 @@ +--- +title: Known limitations +description: + An overview of known limitations of the current version of the Noir debugger +keywords: + [ + Nargo, + Noir Debugger, + VS Code, + ] +sidebar_position: 2 +--- + +# Debugger Known Limitations + +There are currently some limits to what the debugger can observe. + +## Mutable references + +The debugger is currently blind to any state mutated via a mutable reference. For example, in: + +``` +let mut x = 1; +let y = &mut x; +*y = 2; +``` + +The update on `x` will not be observed by the debugger. That means, when running `vars` from the debugger REPL, or inspecting the _local variables_ pane in the VS Code debugger, `x` will appear with value 1 despite having executed `*y = 2;`. + +## Variables of type function or mutable references are opaque + +When inspecting variables, any variable of type `Function` or `MutableReference` will render its value as `<>` or `<>`. + +## Debugger instrumentation affects resulting ACIR + +In order to make the state of local variables observable, the debugger compiles Noir circuits interleaving foreign calls that track any mutations to them. While this works (except in the cases described above) and doesn't introduce any behavior changes, it does as a side effect produce bigger bytecode. In particular, when running the command `opcodes` on the REPL debugger, you will notice Unconstrained VM blocks that look like this: + +``` +... +5 BRILLIG inputs=[Single(Expression { mul_terms: [], linear_combinations: [], q_c: 2 }), Single(Expression { mul_terms: [], linear_combinations: [(1, Witness(2))], q_c: 0 })] + | outputs=[] + 5.0 | Mov { destination: RegisterIndex(2), source: RegisterIndex(0) } + 5.1 | Mov { destination: RegisterIndex(3), source: RegisterIndex(1) } + 5.2 | Const { destination: RegisterIndex(0), value: Value { inner: 0 } } + 5.3 | Const { destination: RegisterIndex(1), value: Value { inner: 0 } } + 5.4 | Mov { destination: RegisterIndex(2), source: RegisterIndex(2) } + 5.5 | Mov { destination: RegisterIndex(3), source: RegisterIndex(3) } + 5.6 | Call { location: 8 } + 5.7 | Stop + 5.8 | ForeignCall { function: "__debug_var_assign", destinations: [], inputs: [RegisterIndex(RegisterIndex(2)), RegisterIndex(RegisterIndex(3))] } +... +``` + +If you are interested in debugging/inspecting compiled ACIR without these synthetic changes, you can invoke the REPL debugger with the `--skip-instrumentation` flag or launch the VS Code debugger with the `skipConfiguration` property set to true in its launch configuration. You can find more details about those in the [Debugger REPL reference](debugger_repl.md) and the [VS Code Debugger reference](debugger_vscode.md). + +:::note +Skipping debugger instrumentation means you won't be able to inspect values of local variables. +::: + diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/debugger/debugger_repl.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/debugger/debugger_repl.md new file mode 100644 index 000000000000..46e2011304e5 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/debugger/debugger_repl.md @@ -0,0 +1,360 @@ +--- +title: REPL Debugger +description: + Noir Debugger REPL options and commands. +keywords: + [ + Nargo, + Noir CLI, + Noir Debugger, + REPL, + ] +sidebar_position: 1 +--- + +## Running the REPL debugger + +`nargo debug [OPTIONS] [WITNESS_NAME]` + +Runs the Noir REPL debugger. If a `WITNESS_NAME` is provided the debugger writes the resulting execution witness to a `WITNESS_NAME` file. + +### Options + +| Option | Description | +| --------------------- | ------------------------------------------------------------ | +| `-p, --prover-name ` | The name of the toml file which contains the inputs for the prover [default: Prover]| +| `--package ` | The name of the package to debug | +| `--print-acir` | Display the ACIR for compiled circuit | +| `--deny-warnings` | Treat all warnings as errors | +| `--silence-warnings` | Suppress warnings | +| `-h, --help` | Print help | + +None of these options are required. + +:::note +Since the debugger starts by compiling the target package, all Noir compiler options are also available. Check out the [compiler reference](../nargo_commands.md#nargo-compile) to learn more about the compiler options. +::: + +## REPL commands + +Once the debugger is running, it accepts the following commands. + +#### `help` (h) + +Displays the menu of available commands. + +``` +> help +Available commands: + + opcodes display ACIR opcodes + into step into to the next opcode + next step until a new source location is reached + out step until a new source location is reached + and the current stack frame is finished + break LOCATION:OpcodeLocation add a breakpoint at an opcode location + over step until a new source location is reached + without diving into function calls + restart restart the debugging session + delete LOCATION:OpcodeLocation delete breakpoint at an opcode location + witness show witness map + witness index:u32 display a single witness from the witness map + witness index:u32 value:String update a witness with the given value + memset index:usize value:String update a memory cell with the given + value + continue continue execution until the end of the + program + vars show variable values available at this point + in execution + stacktrace display the current stack trace + memory show memory (valid when executing unconstrained code) value + step step to the next ACIR opcode + +Other commands: + + help Show this help message + quit Quit repl + +``` + +### Stepping through programs + +#### `next` (n) + +Step until the next Noir source code location. While other commands, such as [`into`](#into-i) and [`step`](#step-s), allow for finer grained control of the program's execution at the opcode level, `next` is source code centric. For example: + +``` +3 ... +4 fn main(x: u32) { +5 assert(entry_point(x) == 2); +6 swap_entry_point(x, x + 1); +7 -> assert(deep_entry_point(x) == 4); +8 multiple_values_entry_point(x); +9 } +``` + + +Using `next` here would cause the debugger to jump to the definition of `deep_entry_point` (if available). + +If you want to step over `deep_entry_point` and go straight to line 8, use [the `over` command](#over) instead. + +#### `over` + +Step until the next source code location, without diving into function calls. For example: + +``` +3 ... +4 fn main(x: u32) { +5 assert(entry_point(x) == 2); +6 swap_entry_point(x, x + 1); +7 -> assert(deep_entry_point(x) == 4); +8 multiple_values_entry_point(x); +9 } +``` + + +Using `over` here would cause the debugger to execute until line 8 (`multiple_values_entry_point(x);`). + +If you want to step into `deep_entry_point` instead, use [the `next` command](#next-n). + +#### `out` + +Step until the end of the current function call. For example: + +``` + 3 ... + 4 fn main(x: u32) { + 5 assert(entry_point(x) == 2); + 6 swap_entry_point(x, x + 1); + 7 -> assert(deep_entry_point(x) == 4); + 8 multiple_values_entry_point(x); + 9 } + 10 + 11 unconstrained fn returns_multiple_values(x: u32) -> (u32, u32, u32, u32) { + 12 ... + ... + 55 + 56 unconstrained fn deep_entry_point(x: u32) -> u32 { + 57 -> level_1(x + 1) + 58 } + +``` + +Running `out` here will resume execution until line 8. + +#### `step` (s) + +Skips to the next ACIR code. A compiled Noir program is a sequence of ACIR opcodes. However, an unconstrained VM opcode denotes the start of an unconstrained code block, to be executed by the unconstrained VM. For example (redacted for brevity): + +``` +0 BLACKBOX::RANGE [(_0, num_bits: 32)] [ ] +1 -> BRILLIG inputs=[Single(Expression { mul_terms: [], linear_combinations: [(1, Witness(0))], q_c: 0 })] outputs=[Simple(Witness(1))] + 1.0 | Mov { destination: RegisterIndex(2), source: RegisterIndex(0) } + 1.1 | Const { destination: RegisterIndex(0), value: Value { inner: 0 } } + 1.2 | Const { destination: RegisterIndex(1), value: Value { inner: 0 } } + 1.3 | Mov { destination: RegisterIndex(2), source: RegisterIndex(2) } + 1.4 | Call { location: 7 } + ... + 1.43 | Return +2 EXPR [ (1, _1) -2 ] +``` + +The `->` here shows the debugger paused at an ACIR opcode: `BRILLIG`, at index 1, which denotes an unconstrained code block is about to start. + +Using the `step` command at this point would result in the debugger stopping at ACIR opcode 2, `EXPR`, skipping unconstrained computation steps. + +Use [the `into` command](#into-i) instead if you want to follow unconstrained computation step by step. + +#### `into` (i) + +Steps into the next opcode. A compiled Noir program is a sequence of ACIR opcodes. However, a BRILLIG opcode denotes the start of an unconstrained code block, to be executed by the unconstrained VM. For example (redacted for brevity): + +``` +0 BLACKBOX::RANGE [(_0, num_bits: 32)] [ ] +1 -> BRILLIG inputs=[Single(Expression { mul_terms: [], linear_combinations: [(1, Witness(0))], q_c: 0 })] outputs=[Simple(Witness(1))] + 1.0 | Mov { destination: RegisterIndex(2), source: RegisterIndex(0) } + 1.1 | Const { destination: RegisterIndex(0), value: Value { inner: 0 } } + 1.2 | Const { destination: RegisterIndex(1), value: Value { inner: 0 } } + 1.3 | Mov { destination: RegisterIndex(2), source: RegisterIndex(2) } + 1.4 | Call { location: 7 } + ... + 1.43 | Return +2 EXPR [ (1, _1) -2 ] +``` + +The `->` here shows the debugger paused at an ACIR opcode: `BRILLIG`, at index 1, which denotes an unconstrained code block is about to start. + +Using the `into` command at this point would result in the debugger stopping at opcode 1.0, `Mov ...`, allowing the debugger user to follow unconstrained computation step by step. + +Use [the `step` command](#step-s) instead if you want to skip to the next ACIR code directly. + +#### `continue` (c) + +Continues execution until the next breakpoint, or the end of the program. + +#### `restart` (res) + +Interrupts execution, and restarts a new debugging session from scratch. + +#### `opcodes` (o) + +Display the program's ACIR opcode sequence. For example: + +``` +0 BLACKBOX::RANGE [(_0, num_bits: 32)] [ ] +1 -> BRILLIG inputs=[Single(Expression { mul_terms: [], linear_combinations: [(1, Witness(0))], q_c: 0 })] outputs=[Simple(Witness(1))] + 1.0 | Mov { destination: RegisterIndex(2), source: RegisterIndex(0) } + 1.1 | Const { destination: RegisterIndex(0), value: Value { inner: 0 } } + 1.2 | Const { destination: RegisterIndex(1), value: Value { inner: 0 } } + 1.3 | Mov { destination: RegisterIndex(2), source: RegisterIndex(2) } + 1.4 | Call { location: 7 } + ... + 1.43 | Return +2 EXPR [ (1, _1) -2 ] +``` + +### Breakpoints + +#### `break [Opcode]` (or shorthand `b [Opcode]`) + +Sets a breakpoint on the specified opcode index. To get a list of the program opcode numbers, see [the `opcode` command](#opcodes-o). For example: + +``` +0 BLACKBOX::RANGE [(_0, num_bits: 32)] [ ] +1 -> BRILLIG inputs=[Single(Expression { mul_terms: [], linear_combinations: [(1, Witness(0))], q_c: 0 })] outputs=[Simple(Witness(1))] + 1.0 | Mov { destination: RegisterIndex(2), source: RegisterIndex(0) } + 1.1 | Const { destination: RegisterIndex(0), value: Value { inner: 0 } } + 1.2 | Const { destination: RegisterIndex(1), value: Value { inner: 0 } } + 1.3 | Mov { destination: RegisterIndex(2), source: RegisterIndex(2) } + 1.4 | Call { location: 7 } + ... + 1.43 | Return +2 EXPR [ (1, _1) -2 ] +``` + +In this example, issuing a `break 1.2` command adds break on opcode 1.2, as denoted by the `*` character: + +``` +0 BLACKBOX::RANGE [(_0, num_bits: 32)] [ ] +1 -> BRILLIG inputs=[Single(Expression { mul_terms: [], linear_combinations: [(1, Witness(0))], q_c: 0 })] outputs=[Simple(Witness(1))] + 1.0 | Mov { destination: RegisterIndex(2), source: RegisterIndex(0) } + 1.1 | Const { destination: RegisterIndex(0), value: Value { inner: 0 } } + 1.2 | * Const { destination: RegisterIndex(1), value: Value { inner: 0 } } + 1.3 | Mov { destination: RegisterIndex(2), source: RegisterIndex(2) } + 1.4 | Call { location: 7 } + ... + 1.43 | Return +2 EXPR [ (1, _1) -2 ] +``` + +Running [the `continue` command](#continue-c) at this point would cause the debugger to execute the program until opcode 1.2. + +#### `delete [Opcode]` (or shorthand `d [Opcode]`) + +Deletes a breakpoint at an opcode location. Usage is analogous to [the `break` command](#). + +### Variable inspection + +#### vars + +Show variable values available at this point in execution. + +:::note +The ability to inspect variable values from the debugger depends on compilation to be run in a special debug instrumentation mode. This instrumentation weaves variable tracing code with the original source code. + +So variable value inspection comes at the expense of making the resulting ACIR bytecode bigger and harder to understand and optimize. + +If you find this compromise unacceptable, you can run the debugger with the flag `--skip-debug-instrumentation`. This will compile your circuit without any additional debug information, so the resulting ACIR bytecode will be identical to the one produced by standard Noir compilation. However, if you opt for this, the `vars` command will not be available while debugging. +::: + + +### Stacktrace + +#### `stacktrace` + +Displays the current stack trace. + + +### Witness map + +#### `witness` (w) + +Show witness map. For example: + +``` +_0 = 0 +_1 = 2 +_2 = 1 +``` + +#### `witness [Witness Index]` + +Display a single witness from the witness map. For example: + +``` +> witness 1 +_1 = 2 +``` + +#### `witness [Witness Index] [New value]` + +Overwrite the given index with a new value. For example: + +``` +> witness 1 3 +_1 = 3 +``` + + +### Unconstrained VM memory + +#### `memory` + +Show unconstrained VM memory state. For example: + +``` +> memory +At opcode 1.13: Store { destination_pointer: RegisterIndex(0), source: RegisterIndex(3) } +... +> registers +0 = 0 +1 = 10 +2 = 0 +3 = 1 +4 = 1 +5 = 2³² +6 = 1 +> into +At opcode 1.14: Const { destination: RegisterIndex(5), value: Value { inner: 1 } } +... +> memory +0 = 1 +> +``` + +In the example above: we start with clean memory, then step through a `Store` opcode which stores the value of register 3 (1) into the memory address stored in register 0 (0). Thus now `memory` shows memory address 0 contains value 1. + +:::note +This command is only functional while the debugger is executing unconstrained code. +::: + +#### `memset [Memory address] [New value]` + +Update a memory cell with the given value. For example: + +``` +> memory +0 = 1 +> memset 0 2 +> memory +0 = 2 +> memset 1 4 +> memory +0 = 2 +1 = 4 +> +``` + +:::note +This command is only functional while the debugger is executing unconstrained code. +::: \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/debugger/debugger_vscode.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/debugger/debugger_vscode.md new file mode 100644 index 000000000000..c027332b3b04 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/debugger/debugger_vscode.md @@ -0,0 +1,82 @@ +--- +title: VS Code Debugger +description: + VS Code Debugger configuration and features. +keywords: + [ + Nargo, + Noir CLI, + Noir Debugger, + VS Code, + IDE, + ] +sidebar_position: 0 +--- + +# VS Code Noir Debugger Reference + +The Noir debugger enabled by the vscode-noir extension ships with default settings such that the most common scenario should run without any additional configuration steps. + +These defaults can nevertheless be overridden by defining a launch configuration file. This page provides a reference for the properties you can override via a launch configuration file, as well as documenting the Nargo `dap` command, which is a dependency of the VS Code Noir debugger. + + +## Creating and editing launch configuration files + +To create a launch configuration file from VS Code, open the _debug pane_, and click on _create a launch.json file_. + +![Creating a launch configuration file](@site/static/img/debugger/ref1-create-launch.png) + +A `launch.json` file will be created, populated with basic defaults. + +### Noir Debugger launch.json properties + +#### projectFolder + +_String, optional._ + +Absolute path to the Nargo project to debug. By default, it is dynamically determined by looking for the nearest `Nargo.toml` file to the active file at the moment of launching the debugger. + +#### proverName + +_String, optional._ + +Name of the prover input to use. Defaults to `Prover`, which looks for a file named `Prover.toml` at the `projectFolder`. + +#### generateAcir + +_Boolean, optional._ + +If true, generate ACIR opcodes instead of unconstrained opcodes which will be closer to release binaries but less convenient for debugging. Defaults to `false`. + +#### skipInstrumentation + +_Boolean, optional._ + +Skips variables debugging instrumentation of code, making debugging less convenient but the resulting binary smaller and closer to production. Defaults to `false`. + +:::note +Skipping instrumentation causes the debugger to be unable to inspect local variables. +::: + +## `nargo dap [OPTIONS]` + +When run without any option flags, it starts the Nargo Debug Adapter Protocol server, which acts as the debugging backend for the VS Code Noir Debugger. + +All option flags are related to preflight checks. The Debug Adapter Protocol specifies how errors are to be informed from a running DAP server, but it doesn't specify mechanisms to communicate server initialization errors between the DAP server and its client IDE. + +Thus `nargo dap` ships with a _preflight check_ mode. If flag `--preflight-check` and the rest of the `--preflight-*` flags are provided, Nargo will run the same initialization routine except it will not start the DAP server. + +`vscode-noir` will then run `nargo dap` in preflight check mode first before a debugging session starts. If the preflight check ends in error, vscode-noir will present stderr and stdout output from this process through its own Output pane in VS Code. This makes it possible for users to diagnose what pieces of configuration might be wrong or missing in case of initialization errors. + +If the preflight check succeeds, `vscode-noir` proceeds to start the DAP server normally but running `nargo dap` without any additional flags. + +### Options + +| Option | Description | +| --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | +| `--preflight-check` | If present, dap runs in preflight check mode. | +| `--preflight-project-folder ` | Absolute path to the project to debug for preflight check. | +| `--preflight-prover-name ` | Name of prover file to use for preflight check | +| `--preflight-generate-acir` | Optional. If present, compile in ACIR mode while running preflight check. | +| `--preflight-skip-instrumentation` | Optional. If present, compile without introducing debug instrumentation while running preflight check. | +| `-h, --help` | Print help. | diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/nargo_commands.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/nargo_commands.md new file mode 100644 index 000000000000..8384788fa814 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/nargo_commands.md @@ -0,0 +1,289 @@ +--- +title: Nargo +description: + Noir CLI Commands for Noir Prover and Verifier to create, execute, prove and verify programs, + generate Solidity verifier smart contract and compile into JSON file containing ACIR + representation and ABI of circuit. +keywords: + [ + Nargo, + Noir CLI, + Noir Prover, + Noir Verifier, + generate Solidity verifier, + compile JSON file, + ACIR representation, + ABI of circuit, + TypeScript, + ] +sidebar_position: 0 +--- + +# Command-Line Help for `nargo` + +This document contains the help content for the `nargo` command-line program. + +**Command Overview:** + +* [`nargo`↴](#nargo) +* [`nargo check`↴](#nargo-check) +* [`nargo fmt`↴](#nargo-fmt) +* [`nargo compile`↴](#nargo-compile) +* [`nargo new`↴](#nargo-new) +* [`nargo init`↴](#nargo-init) +* [`nargo execute`↴](#nargo-execute) +* [`nargo debug`↴](#nargo-debug) +* [`nargo test`↴](#nargo-test) +* [`nargo info`↴](#nargo-info) +* [`nargo lsp`↴](#nargo-lsp) + +## `nargo` + +Noir's package manager + +**Usage:** `nargo ` + +###### **Subcommands:** + +* `check` — Checks the constraint system for errors +* `fmt` — Format the Noir files in a workspace +* `compile` — Compile the program and its secret execution trace into ACIR format +* `new` — Create a Noir project in a new directory +* `init` — Create a Noir project in the current directory +* `execute` — Executes a circuit to calculate its return value +* `debug` — Executes a circuit in debug mode +* `test` — Run the tests for this program +* `info` — Provides detailed information on each of a program's function (represented by a single circuit) +* `lsp` — Starts the Noir LSP server + +###### **Options:** + + + + +## `nargo check` + +Checks the constraint system for errors + +**Usage:** `nargo check [OPTIONS]` + +###### **Options:** + +* `--package ` — The name of the package to check +* `--workspace` — Check all packages in the workspace +* `--overwrite` — Force overwrite of existing files +* `--expression-width ` — Specify the backend expression width that should be targeted +* `--bounded-codegen` — Generate ACIR with the target backend expression width. The default is to generate ACIR without a bound and split expressions after code generation. Activating this flag can sometimes provide optimizations for certain programs + + Default value: `false` +* `--force` — Force a full recompilation +* `--print-acir` — Display the ACIR for compiled circuit +* `--deny-warnings` — Treat all warnings as errors +* `--silence-warnings` — Suppress warnings +* `--debug-comptime-in-file ` — Enable printing results of comptime evaluation: provide a path suffix for the module to debug, e.g. "package_name/src/main.nr" + + + +## `nargo fmt` + +Format the Noir files in a workspace + +**Usage:** `nargo fmt [OPTIONS]` + +###### **Options:** + +* `--check` — Run noirfmt in check mode + + + +## `nargo compile` + +Compile the program and its secret execution trace into ACIR format + +**Usage:** `nargo compile [OPTIONS]` + +###### **Options:** + +* `--package ` — The name of the package to compile +* `--workspace` — Compile all packages in the workspace +* `--expression-width ` — Specify the backend expression width that should be targeted +* `--bounded-codegen` — Generate ACIR with the target backend expression width. The default is to generate ACIR without a bound and split expressions after code generation. Activating this flag can sometimes provide optimizations for certain programs + + Default value: `false` +* `--force` — Force a full recompilation +* `--print-acir` — Display the ACIR for compiled circuit +* `--deny-warnings` — Treat all warnings as errors +* `--silence-warnings` — Suppress warnings +* `--debug-comptime-in-file ` — Enable printing results of comptime evaluation: provide a path suffix for the module to debug, e.g. "package_name/src/main.nr" + + + +## `nargo new` + +Create a Noir project in a new directory + +**Usage:** `nargo new [OPTIONS] ` + +###### **Arguments:** + +* `` — The path to save the new project + +###### **Options:** + +* `--name ` — Name of the package [default: package directory name] +* `--lib` — Use a library template +* `--bin` — Use a binary template [default] +* `--contract` — Use a contract template + + + +## `nargo init` + +Create a Noir project in the current directory + +**Usage:** `nargo init [OPTIONS]` + +###### **Options:** + +* `--name ` — Name of the package [default: current directory name] +* `--lib` — Use a library template +* `--bin` — Use a binary template [default] +* `--contract` — Use a contract template + + + +## `nargo execute` + +Executes a circuit to calculate its return value + +**Usage:** `nargo execute [OPTIONS] [WITNESS_NAME]` + +###### **Arguments:** + +* `` — Write the execution witness to named file + +###### **Options:** + +* `-p`, `--prover-name ` — The name of the toml file which contains the inputs for the prover + + Default value: `Prover` +* `--package ` — The name of the package to execute +* `--workspace` — Execute all packages in the workspace +* `--expression-width ` — Specify the backend expression width that should be targeted +* `--bounded-codegen` — Generate ACIR with the target backend expression width. The default is to generate ACIR without a bound and split expressions after code generation. Activating this flag can sometimes provide optimizations for certain programs + + Default value: `false` +* `--force` — Force a full recompilation +* `--print-acir` — Display the ACIR for compiled circuit +* `--deny-warnings` — Treat all warnings as errors +* `--silence-warnings` — Suppress warnings +* `--debug-comptime-in-file ` — Enable printing results of comptime evaluation: provide a path suffix for the module to debug, e.g. "package_name/src/main.nr" +* `--oracle-resolver ` — JSON RPC url to solve oracle calls + + + +## `nargo debug` + +Executes a circuit in debug mode + +**Usage:** `nargo debug [OPTIONS] [WITNESS_NAME]` + +###### **Arguments:** + +* `` — Write the execution witness to named file + +###### **Options:** + +* `-p`, `--prover-name ` — The name of the toml file which contains the inputs for the prover + + Default value: `Prover` +* `--package ` — The name of the package to execute +* `--expression-width ` — Specify the backend expression width that should be targeted +* `--bounded-codegen` — Generate ACIR with the target backend expression width. The default is to generate ACIR without a bound and split expressions after code generation. Activating this flag can sometimes provide optimizations for certain programs + + Default value: `false` +* `--force` — Force a full recompilation +* `--print-acir` — Display the ACIR for compiled circuit +* `--deny-warnings` — Treat all warnings as errors +* `--silence-warnings` — Suppress warnings +* `--debug-comptime-in-file ` — Enable printing results of comptime evaluation: provide a path suffix for the module to debug, e.g. "package_name/src/main.nr" +* `--acir-mode` — Force ACIR output (disabling instrumentation) +* `--skip-instrumentation ` — Disable vars debug instrumentation (enabled by default) + + Possible values: `true`, `false` + + + + +## `nargo test` + +Run the tests for this program + +**Usage:** `nargo test [OPTIONS] [TEST_NAME]` + +###### **Arguments:** + +* `` — If given, only tests with names containing this string will be run + +###### **Options:** + +* `--show-output` — Display output of `println` statements +* `--exact` — Only run tests that match exactly +* `--package ` — The name of the package to test +* `--workspace` — Test all packages in the workspace +* `--expression-width ` — Specify the backend expression width that should be targeted +* `--bounded-codegen` — Generate ACIR with the target backend expression width. The default is to generate ACIR without a bound and split expressions after code generation. Activating this flag can sometimes provide optimizations for certain programs + + Default value: `false` +* `--force` — Force a full recompilation +* `--print-acir` — Display the ACIR for compiled circuit +* `--deny-warnings` — Treat all warnings as errors +* `--silence-warnings` — Suppress warnings +* `--debug-comptime-in-file ` — Enable printing results of comptime evaluation: provide a path suffix for the module to debug, e.g. "package_name/src/main.nr" +* `--oracle-resolver ` — JSON RPC url to solve oracle calls + + + +## `nargo info` + +Provides detailed information on each of a program's function (represented by a single circuit) + +Current information provided per circuit: 1. The number of ACIR opcodes 2. Counts the final number gates in the circuit used by a backend + +**Usage:** `nargo info [OPTIONS]` + +###### **Options:** + +* `--package ` — The name of the package to detail +* `--workspace` — Detail all packages in the workspace +* `--expression-width ` — Specify the backend expression width that should be targeted +* `--bounded-codegen` — Generate ACIR with the target backend expression width. The default is to generate ACIR without a bound and split expressions after code generation. Activating this flag can sometimes provide optimizations for certain programs + + Default value: `false` +* `--force` — Force a full recompilation +* `--print-acir` — Display the ACIR for compiled circuit +* `--deny-warnings` — Treat all warnings as errors +* `--silence-warnings` — Suppress warnings +* `--debug-comptime-in-file ` — Enable printing results of comptime evaluation: provide a path suffix for the module to debug, e.g. "package_name/src/main.nr" + + + +## `nargo lsp` + +Starts the Noir LSP server + +Starts an LSP server which allows IDEs such as VS Code to display diagnostics in Noir source. + +VS Code Noir Language Support: https://marketplace.visualstudio.com/items?itemName=noir-lang.vscode-noir + +**Usage:** `nargo lsp` + + + +
+ + + This document was generated automatically by + clap-markdown. + + diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/noir_codegen.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/noir_codegen.md new file mode 100644 index 000000000000..db8f07dc22e6 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/noir_codegen.md @@ -0,0 +1,114 @@ +--- +title: Noir Codegen for TypeScript +description: Learn how to use Noir codegen to generate TypeScript bindings +keywords: [Nargo, Noir, compile, TypeScript] +sidebar_position: 3 +--- + +When using TypeScript, it is extra work to interpret Noir program outputs in a type-safe way. Third party libraries may exist for popular Noir programs, but they are either hard to find or unmaintained. + +Now you can generate TypeScript bindings for your Noir programs in two steps: +1. Exporting Noir functions using `nargo export` +2. Using the TypeScript module `noir_codegen` to generate TypeScript binding + +**Note:** you can only export functions from a Noir *library* (not binary or contract program types). + +## Installation + +### Your TypeScript project + +If you don't already have a TypeScript project you can add the module with `yarn` (or `npm`), then initialize it: + +```bash +yarn add typescript -D +npx tsc --init +``` + +### Add TypeScript module - `noir_codegen` + +The following command will add the module to your project's devDependencies: + +```bash +yarn add @noir-lang/noir_codegen -D +``` + +### Nargo library +Make sure you have Nargo, v0.25.0 or greater, installed. If you don't, follow the [installation guide](../getting_started/installation/index.md). + +If you're in a new project, make a `circuits` folder and create a new Noir library: + +```bash +mkdir circuits && cd circuits +nargo new --lib myNoirLib +``` + +## Usage + +### Export ABI of specified functions + +First go to the `.nr` files in your Noir library, and add the `#[export]` macro to each function that you want to use in TypeScript. + +```rust +#[export] +fn your_function(... +``` + +From your Noir library (where `Nargo.toml` is), run the following command: + +```bash +nargo export +``` + +You will now have an `export` directory with a .json file per exported function. + +You can also specify the directory of Noir programs using `--program-dir`, for example: + +```bash +nargo export --program-dir=./circuits/myNoirLib +``` + +### Generate TypeScript bindings from exported functions + +To use the `noir-codegen` package we added to the TypeScript project: + +```bash +yarn noir-codegen ./export/your_function.json +``` + +This creates an `exports` directory with an `index.ts` file containing all exported functions. + +**Note:** adding `--out-dir` allows you to specify an output dir for your TypeScript bindings to go. Eg: + +```bash +yarn noir-codegen ./export/*.json --out-dir ./path/to/output/dir +``` + +## Example .nr function to .ts output + +Consider a Noir library with this function: + +```rust +#[export] +fn not_equal(x: Field, y: Field) -> bool { + x != y +} +``` + +After the export and codegen steps, you should have an `index.ts` like: + +```typescript +export type Field = string; + + +export const is_equal_circuit: CompiledCircuit = +{"abi":{"parameters":[{"name":"x","type":{"kind":"field"},"visibility":"private"},{"name":"y","type":{"kind":"field"},"visibility":"private"}],"return_type":{"abi_type":{"kind":"boolean"},"visibility":"private"}},"bytecode":"H4sIAAAAAAAA/7WUMQ7DIAxFQ0Krrr2JjSGYLVcpKrn/CaqqDQN12WK+hPBgmWd/wEyHbF1SS923uhOs3pfoChI+wKXMAXzIKyNj4PB0TFTYc0w5RUjoqeAeEu1wqK0F54RGkWvW44LPzExnlkbMEs4JNZmN8PxS42uHv82T8a3Jeyn2Ks+VLPcO558HmyLMCDOXAXXtpPt4R/Rt9T36ss6dS9HGPx/eG17nGegKBQAA"}; + +export async function is_equal(x: Field, y: Field, foreignCallHandler?: ForeignCallHandler): Promise { + const program = new Noir(is_equal_circuit); + const args: InputMap = { x, y }; + const { returnValue } = await program.execute(args, foreignCallHandler); + return returnValue as boolean; +} +``` + +Now the `is_equal()` function and relevant types are readily available for use in TypeScript. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/tooling/debugger.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/tooling/debugger.md new file mode 100644 index 000000000000..9b7565ba9ff2 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/tooling/debugger.md @@ -0,0 +1,26 @@ +--- +title: Debugger +description: Learn about the Noir Debugger, in its REPL or VS Code versions. +keywords: [Nargo, VSCode, Visual Studio Code, REPL, Debugger] +sidebar_position: 2 +--- + +# Noir Debugger + +There are currently two ways of debugging Noir programs: + +1. From VS Code, via the [vscode-noir](https://github.com/noir-lang/vscode-noir) extension. You can install it via the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=noir-lang.vscode-noir). +2. Via the REPL debugger, which ships with Nargo. + +In order to use either version of the debugger, you will need to install recent enough versions of Noir, [Nargo](../getting_started/installation/index.md) and vscode-noir: + +- Noir & Nargo ≥0.28.0 +- Noir's VS Code extension ≥0.0.11 + +:::info +At the moment, the debugger supports debugging binary projects, but not contracts. +::: + +We cover the VS Code Noir debugger more in depth in [its VS Code debugger how-to guide](../how_to/debugger/debugging_with_vs_code.md) and [the reference](../reference/debugger/debugger_vscode.md). + +The REPL debugger is discussed at length in [the REPL debugger how-to guide](../how_to/debugger/debugging_with_the_repl.md) and [the reference](../reference/debugger/debugger_repl.md). diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/tooling/language_server.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/tooling/language_server.md new file mode 100644 index 000000000000..81e0356ef8a1 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/tooling/language_server.md @@ -0,0 +1,43 @@ +--- +title: Language Server +description: Learn about the Noir Language Server, how to install the components, and configuration that may be required. +keywords: [Nargo, Language Server, LSP, VSCode, Visual Studio Code] +sidebar_position: 0 +--- + +This section helps you install and configure the Noir Language Server. + +The Language Server Protocol (LSP) has two components, the [Server](#language-server) and the [Client](#language-client). Below we describe each in the context of Noir. + +## Language Server + +The Server component is provided by the Nargo command line tool that you installed at the beginning of this guide. +As long as Nargo is installed and you've used it to run other commands in this guide, it should be good to go! + +If you'd like to verify that the `nargo lsp` command is available, you can run `nargo --help` and look for `lsp` in the list of commands. If you see it, you're using a version of Noir with LSP support. + +## Language Client + +The Client component is usually an editor plugin that launches the Server. It communicates LSP messages between the editor and the Server. For example, when you save a file, the Client will alert the Server, so it can try to compile the project and report any errors. + +Currently, Noir provides a Language Client for Visual Studio Code via the [vscode-noir](https://github.com/noir-lang/vscode-noir) extension. You can install it via the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=noir-lang.vscode-noir). + +> **Note:** Noir's Language Server Protocol support currently assumes users' VSCode workspace root to be the same as users' Noir project root (i.e. where Nargo.toml lies). +> +> If LSP features seem to be missing / malfunctioning, make sure you are opening your Noir project directly (instead of as a sub-folder) in your VSCode instance. + +When your language server is running correctly and the VSCode plugin is installed, you should see handy codelens buttons for compilation, measuring circuit size, execution, and tests: + +![Compile and Execute](@site/static/img/codelens_compile_execute.png) +![Run test](@site/static/img/codelens_run_test.png) + +You should also see your tests in the `testing` panel: + +![Testing panel](@site/static/img/codelens_testing_panel.png) + +### Configuration + +- **Noir: Enable LSP** - If checked, the extension will launch the Language Server via `nargo lsp` and communicate with it. +- **Noir: Nargo Flags** - Additional flags may be specified if you require them to be added when the extension calls `nargo lsp`. +- **Noir: Nargo Path** - An absolute path to a Nargo binary with the `lsp` command. This may be useful if Nargo is not within the `PATH` of your editor. +- **Noir > Trace: Server** - Setting this to `"messages"` or `"verbose"` will log LSP messages between the Client and Server. Useful for debugging. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/tooling/testing.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/tooling/testing.md new file mode 100644 index 000000000000..866677da5679 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/tooling/testing.md @@ -0,0 +1,79 @@ +--- +title: Testing in Noir +description: Learn how to use Nargo to test your Noir program in a quick and easy way +keywords: [Nargo, testing, Noir, compile, test] +sidebar_position: 1 +--- + +You can test your Noir programs using Noir circuits. + +Nargo will automatically compile and run any functions which have the decorator `#[test]` on them if +you run `nargo test`. + +For example if you have a program like: + +```rust +fn add(x: u64, y: u64) -> u64 { + x + y +} +#[test] +fn test_add() { + assert(add(2,2) == 4); + assert(add(0,1) == 1); + assert(add(1,0) == 1); +} +``` + +Running `nargo test` will test that the `test_add` function can be executed while satisfying all +the constraints which allows you to test that add returns the expected values. Test functions can't +have any arguments currently. + +### Test fail + +You can write tests that are expected to fail by using the decorator `#[test(should_fail)]`. For example: + +```rust +fn add(x: u64, y: u64) -> u64 { + x + y +} +#[test(should_fail)] +fn test_add() { + assert(add(2,2) == 5); +} +``` + +You can be more specific and make it fail with a specific reason by using `should_fail_with = ""`: + +```rust +fn main(african_swallow_avg_speed : Field) { + assert(african_swallow_avg_speed == 65, "What is the airspeed velocity of an unladen swallow"); +} + +#[test] +fn test_king_arthur() { + main(65); +} + +#[test(should_fail_with = "What is the airspeed velocity of an unladen swallow")] +fn test_bridgekeeper() { + main(32); +} +``` + +The string given to `should_fail_with` doesn't need to exactly match the failure reason, it just needs to be a substring of it: + +```rust +fn main(african_swallow_avg_speed : Field) { + assert(african_swallow_avg_speed == 65, "What is the airspeed velocity of an unladen swallow"); +} + +#[test] +fn test_king_arthur() { + main(65); +} + +#[test(should_fail_with = "airspeed velocity")] +fn test_bridgekeeper() { + main(32); +} +``` \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/tutorials/noirjs_app.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/tutorials/noirjs_app.md new file mode 100644 index 000000000000..eac28168445f --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/tutorials/noirjs_app.md @@ -0,0 +1,362 @@ +--- +title: Building a web app with NoirJS +description: Learn how to setup a new app that uses Noir to generate and verify zero-knowledge SNARK proofs in a typescript or javascript environment. +keywords: [how to, guide, javascript, typescript, noir, barretenberg, zero-knowledge, proofs, app] +sidebar_position: 0 +pagination_next: noir/concepts/data_types/index +--- + +NoirJS is a set of packages meant to work both in a browser and a server environment. In this tutorial, we will build a simple web app using them. From here, you should get an idea on how to proceed with your own Noir projects! + +You can find the complete app code for this guide [here](https://github.com/noir-lang/tiny-noirjs-app). + +## Setup + +:::note + +Feel free to use whatever versions, just keep in mind that Nargo and the NoirJS packages are meant to be in sync. For example, Nargo 0.31.x matches `noir_js@0.31.x`, etc. + +In this guide, we will be pinned to 0.31.0. + +::: + +Before we start, we want to make sure we have Node, Nargo and the Barretenberg proving system (`bb`) installed. + +We start by opening a terminal and executing `node --version`. If we don't get an output like `v20.10.0`, that means node is not installed. Let's do that by following the handy [nvm guide](https://github.com/nvm-sh/nvm?tab=readme-ov-file#install--update-script). + +As for `Nargo`, we can follow the [Nargo guide](../getting_started/installation/index.md) to install it. If you're lazy, just paste this on a terminal and run `noirup`: + +```sh +curl -L https://raw.githubusercontent.com/noir-lang/noirup/main/install | bash +``` + +Follow the instructions on [this page](https://github.com/AztecProtocol/aztec-packages/tree/master/barretenberg/cpp/src/barretenberg/bb#installation) to install `bb`. +Version 0.41.0 is compatible with `nargo` version 0.31.0, which you can install with `bbup -v 0.41.0` once `bbup` is installed. + +Easy enough. Onwards! + +## Our project + +ZK is a powerful technology. An app that doesn't reveal one of the inputs to _anyone_ is almost unbelievable, yet Noir makes it as easy as a single line of code. + +In fact, it's so simple that it comes nicely packaged in `nargo`. Let's do that! + +### Nargo + +Run: + +```bash +nargo new circuit +``` + +And... That's about it. Your program is ready to be compiled and run. + +To compile, let's `cd` into the `circuit` folder to enter our project, and call: + +```bash +nargo compile +``` + +This compiles our circuit into `json` format and add it to a new `target` folder. + +:::info + +At this point in the tutorial, your folder structure should look like this: + +```tree +. +└── circuit <---- our working directory + ├── Nargo.toml + ├── src + │ └── main.nr + └── target + └── circuit.json +``` + +::: + +### Node and Vite + +If you want to explore Nargo, feel free to go on a side-quest now and follow the steps in the +[getting started](../getting_started/hello_noir/index.md) guide. However, we want our app to run on the browser, so we need Vite. + +Vite is a powerful tool to generate static websites. While it provides all kinds of features, let's just go barebones with some good old vanilla JS. + +To do this this, go back to the previous folder (`cd ..`) and create a new vite project by running `npm create vite` and choosing "Vanilla" and "Javascript". + +A wild `vite-project` directory should now appear in your root folder! Let's not waste any time and dive right in: + +```bash +cd vite-project +``` + +### Setting Up Vite and Configuring the Project + +Before we proceed with any coding, let's get our environment tailored for Noir. We'll start by laying down the foundations with a `vite.config.js` file. This little piece of configuration is our secret sauce for making sure everything meshes well with the NoirJS libraries and other special setups we might need, like handling WebAssembly modules. Here’s how you get that going: + +#### Creating the vite.config.js + +In your freshly minted `vite-project` folder, create a new file named `vite.config.js` and open it in your code editor. Paste the following to set the stage: + +```javascript +import { defineConfig } from 'vite'; +import copy from 'rollup-plugin-copy'; +import fs from 'fs'; +import path from 'path'; + +const wasmContentTypePlugin = { + name: 'wasm-content-type-plugin', + configureServer(server) { + server.middlewares.use(async (req, res, next) => { + if (req.url.endsWith('.wasm')) { + res.setHeader('Content-Type', 'application/wasm'); + const newPath = req.url.replace('deps', 'dist'); + const targetPath = path.join(__dirname, newPath); + const wasmContent = fs.readFileSync(targetPath); + return res.end(wasmContent); + } + next(); + }); + }, +}; + +export default defineConfig(({ command }) => { + if (command === 'serve') { + return { + build: { + target: 'esnext', + rollupOptions: { + external: ['@aztec/bb.js'] + } + }, + optimizeDeps: { + esbuildOptions: { + target: 'esnext' + } + }, + plugins: [ + copy({ + targets: [{ src: 'node_modules/**/*.wasm', dest: 'node_modules/.vite/dist' }], + copySync: true, + hook: 'buildStart', + }), + command === 'serve' ? wasmContentTypePlugin : [], + ], + }; + } + + return {}; +}); +``` + +#### Install Dependencies + +Now that our stage is set, install the necessary NoirJS packages along with our other dependencies: + +```bash +npm install && npm install @noir-lang/backend_barretenberg@0.31.0 @noir-lang/noir_js@0.31.0 +npm install rollup-plugin-copy --save-dev +``` + +:::info + +At this point in the tutorial, your folder structure should look like this: + +```tree +. +└── circuit + └── ...etc... +└── vite-project <---- our working directory + └── ...etc... +``` + +::: + +#### Some cleanup + +`npx create vite` is amazing but it creates a bunch of files we don't really need for our simple example. Actually, let's just delete everything except for `vite.config.js`, `index.html`, `main.js` and `package.json`. I feel lighter already. + +![my heart is ready for you, noir.js](@site/static/img/memes/titanic.jpeg) + +## HTML + +Our app won't run like this, of course. We need some working HTML, at least. Let's open our broken-hearted `index.html` and replace everything with this code snippet: + +```html + + + + + + +

Noir app

+
+ + +
+
+

Logs

+

Proof

+
+ + +``` + +It _could_ be a beautiful UI... Depending on which universe you live in. + +## Some good old vanilla Javascript + +Our love for Noir needs undivided attention, so let's just open `main.js` and delete everything (this is where the romantic scenery becomes a bit creepy). + +Start by pasting in this boilerplate code: + +```js +function display(container, msg) { + const c = document.getElementById(container); + const p = document.createElement('p'); + p.textContent = msg; + c.appendChild(p); +} + +document.getElementById('submitGuess').addEventListener('click', async () => { + try { + // here's where love happens + } catch (err) { + display('logs', 'Oh 💔 Wrong guess'); + } +}); +``` + +The display function doesn't do much. We're simply manipulating our website to see stuff happening. For example, if the proof fails, it will simply log a broken heart 😢 + +:::info + +At this point in the tutorial, your folder structure should look like this: + +```tree +. +└── circuit + └── ...same as above +└── vite-project + ├── vite.config.js + ├── main.js + ├── package.json + └── index.html +``` + +You'll see other files and folders showing up (like `package-lock.json`, `node_modules`) but you shouldn't have to care about those. + +::: + +## Some NoirJS + +We're starting with the good stuff now. If you've compiled the circuit as described above, you should have a `json` file we want to import at the very top of our `main.js` file: + +```ts +import circuit from '../circuit/target/circuit.json'; +``` + +[Noir is backend-agnostic](../index.mdx#whats-new-about-noir). We write Noir, but we also need a proving backend. That's why we need to import and instantiate the two dependencies we installed above: `BarretenbergBackend` and `Noir`. Let's import them right below: + +```js +import { BarretenbergBackend, BarretenbergVerifier as Verifier } from '@noir-lang/backend_barretenberg'; +import { Noir } from '@noir-lang/noir_js'; +``` + +And instantiate them inside our try-catch block: + +```ts +// try { +const backend = new BarretenbergBackend(circuit); +const noir = new Noir(circuit); +// } +``` + +:::note + +For the remainder of the tutorial, everything will be happening inside the `try` block + +::: + +## Our app + +Now for the app itself. We're capturing whatever is in the input when people press the submit button. Just add this: + +```js +const x = parseInt(document.getElementById('guessInput').value); +const input = { x, y: 2 }; +``` + +Now we're ready to prove stuff! Let's feed some inputs to our circuit and calculate the proof: + +```js +await setup(); // let's squeeze our wasm inits here + +display('logs', 'Generating proof... ⌛'); +const { witness } = await noir.execute(input); +const proof = await backend.generateProof(witness); +display('logs', 'Generating proof... ✅'); +display('results', proof.proof); +``` + +You're probably eager to see stuff happening, so go and run your app now! + +From your terminal, run `npm run dev`. If it doesn't open a browser for you, just visit `localhost:5173`. You should now see the worst UI ever, with an ugly input. + +![Getting Started 0](@site/static/img/noir_getting_started_1.png) + +Now, our circuit says `fn main(x: Field, y: pub Field)`. This means only the `y` value is public, and it's hardcoded above: `input = { x, y: 2 }`. In other words, you won't need to send your secret`x` to the verifier! + +By inputting any number other than 2 in the input box and clicking "submit", you should get a valid proof. Otherwise the proof won't even generate correctly. By the way, if you're human, you shouldn't be able to understand anything on the "proof" box. That's OK. We like you, human ❤️. + +## Verifying + +Time to celebrate, yes! But we shouldn't trust machines so blindly. Let's add these lines to see our proof being verified: + +```js +display('logs', 'Verifying proof... ⌛'); +const isValid = await backend.verifyProof(proof); + +// or to cache and use the verification key: +// const verificationKey = await backend.getVerificationKey(); +// const verifier = new Verifier(); +// const isValid = await verifier.verifyProof(proof, verificationKey); + +if (isValid) display('logs', 'Verifying proof... ✅'); +``` + +You have successfully generated a client-side Noir web app! + +![coded app without math knowledge](@site/static/img/memes/flextape.jpeg) + +## Further Reading + +You can see how noirjs is used in a full stack Next.js hardhat application in the [noir-starter repo here](https://github.com/noir-lang/noir-starter/tree/main/vite-hardhat). The example shows how to calculate a proof in the browser and verify it with a deployed Solidity verifier contract from noirjs. + +You should also check out the more advanced examples in the [noir-examples repo](https://github.com/noir-lang/noir-examples), where you'll find reference usage for some cool apps. + +## UltraHonk Backend + +Barretenberg has recently exposed a new UltraHonk backend. We can use UltraHonk in NoirJS after version 0.33.0. Everything will be the same as the tutorial above, except that the class we need to import will change: +```js +import { UltraHonkBackend, UltraHonkVerifier as Verifier } from '@noir-lang/backend_barretenberg'; +``` +The backend will then be instantiated as such: +```js +const backend = new UltraHonkBackend(circuit); +``` +Then all the commands to prove and verify your circuit will be same. + +The only feature currently unsupported with UltraHonk are [recursive proofs](../explainers/explainer-recursion.md). \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_sidebars/version-v0.33.0-sidebars.json b/noir/noir-repo/docs/versioned_sidebars/version-v0.33.0-sidebars.json new file mode 100644 index 000000000000..b9ad026f69ff --- /dev/null +++ b/noir/noir-repo/docs/versioned_sidebars/version-v0.33.0-sidebars.json @@ -0,0 +1,93 @@ +{ + "sidebar": [ + { + "type": "doc", + "id": "index" + }, + { + "type": "category", + "label": "Getting Started", + "items": [ + { + "type": "autogenerated", + "dirName": "getting_started" + } + ] + }, + { + "type": "category", + "label": "The Noir Language", + "items": [ + { + "type": "autogenerated", + "dirName": "noir" + } + ] + }, + { + "type": "html", + "value": "
", + "defaultStyle": true + }, + { + "type": "category", + "label": "How To Guides", + "items": [ + { + "type": "autogenerated", + "dirName": "how_to" + } + ] + }, + { + "type": "category", + "label": "Explainers", + "items": [ + { + "type": "autogenerated", + "dirName": "explainers" + } + ] + }, + { + "type": "category", + "label": "Tutorials", + "items": [ + { + "type": "autogenerated", + "dirName": "tutorials" + } + ] + }, + { + "type": "category", + "label": "Reference", + "items": [ + { + "type": "autogenerated", + "dirName": "reference" + } + ] + }, + { + "type": "category", + "label": "Tooling", + "items": [ + { + "type": "autogenerated", + "dirName": "tooling" + } + ] + }, + { + "type": "html", + "value": "
", + "defaultStyle": true + }, + { + "type": "doc", + "id": "migration_notes", + "label": "Migration notes" + } + ] +} diff --git a/noir/noir-repo/examples/recursion/recurse_leaf/src/main.nr b/noir/noir-repo/examples/recursion/recurse_leaf/src/main.nr index 4859e84d49eb..1f111a1b5b02 100644 --- a/noir/noir-repo/examples/recursion/recurse_leaf/src/main.nr +++ b/noir/noir-repo/examples/recursion/recurse_leaf/src/main.nr @@ -7,12 +7,7 @@ fn main( num: u64 ) -> pub u64 { // verify sum so far was computed correctly - std::verify_proof( - verification_key.as_slice(), - proof.as_slice(), - public_inputs.as_slice(), - key_hash - ); + std::verify_proof(verification_key, proof, public_inputs, key_hash); // Take output of previous proof and add another number to it. public_inputs[2] as u64 + num } diff --git a/noir/noir-repo/examples/recursion/recurse_node/src/main.nr b/noir/noir-repo/examples/recursion/recurse_node/src/main.nr index 60192493b54f..05b717fc794d 100644 --- a/noir/noir-repo/examples/recursion/recurse_node/src/main.nr +++ b/noir/noir-repo/examples/recursion/recurse_node/src/main.nr @@ -5,11 +5,6 @@ fn main( proof: [Field; 109] ) -> pub u64 { // verify sum was computed correctly - std::verify_proof( - verification_key.as_slice(), - proof.as_slice(), - public_inputs.as_slice(), - key_hash - ); + std::verify_proof(verification_key, proof, public_inputs, key_hash); public_inputs[3] as u64 } diff --git a/noir/noir-repo/noir_stdlib/src/array.nr b/noir/noir-repo/noir_stdlib/src/array.nr index ad9c7093d070..af2bea12c60b 100644 --- a/noir/noir-repo/noir_stdlib/src/array.nr +++ b/noir/noir-repo/noir_stdlib/src/array.nr @@ -1,7 +1,7 @@ use crate::cmp::Ord; +use crate::option::Option; +use crate::convert::From; -// TODO: Once we fully move to the new SSA pass this module can be removed and replaced -// by the methods in the `slice` module impl [T; N] { #[builtin(array_len)] pub fn len(self) -> u32 {} @@ -108,6 +108,13 @@ impl [T; N] { } } +impl [u8; N] { + /// Convert a sequence of bytes as-is into a string. + /// This function performs no UTF-8 validation or similar. + #[builtin(array_as_str_unchecked)] + pub fn as_str_unchecked(self) -> str {} +} + // helper function used to look up the position of a value in an array of Field // Note that function returns 0 if the value is not found unconstrained fn find_index(a: [u32; N], find: u32) -> u32 { @@ -119,3 +126,9 @@ unconstrained fn find_index(a: [u32; N], find: u32) -> u32 { } result } + +impl From> for [u8; N] { + fn from(s: str) -> Self { + s.as_bytes() + } +} diff --git a/noir/noir-repo/noir_stdlib/src/cmp.nr b/noir/noir-repo/noir_stdlib/src/cmp.nr index bdd5e2bc5ec0..10182ca83b0d 100644 --- a/noir/noir-repo/noir_stdlib/src/cmp.nr +++ b/noir/noir-repo/noir_stdlib/src/cmp.nr @@ -1,9 +1,19 @@ +use crate::meta::derive_via; + +#[derive_via(derive_eq)] // docs:start:eq-trait trait Eq { fn eq(self, other: Self) -> bool; } // docs:end:eq-trait +comptime fn derive_eq(s: StructDefinition) -> Quoted { + let signature = quote { fn eq(_self: Self, _other: Self) -> bool }; + let for_each_field = |name| quote { (_self.$name == _other.$name) }; + let body = |fields| fields; + crate::meta::make_trait_impl(s, quote { Eq }, signature, for_each_field, quote { & }, body) +} + impl Eq for Field { fn eq(self, other: Field) -> bool { self == other } } impl Eq for u64 { fn eq(self, other: u64) -> bool { self == other } } @@ -99,12 +109,28 @@ impl Ordering { } } +#[derive_via(derive_ord)] // docs:start:ord-trait trait Ord { fn cmp(self, other: Self) -> Ordering; } // docs:end:ord-trait +comptime fn derive_ord(s: StructDefinition) -> 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() { + result = _self.$name.cmp(_other.$name); + } + }; + let body = |fields| quote { + let mut result = std::cmp::Ordering::equal(); + $fields + result + }; + crate::meta::make_trait_impl(s, quote { Ord }, signature, for_each_field, quote {}, body) +} + // Note: Field deliberately does not implement Ord impl Ord for u64 { diff --git a/noir/noir-repo/noir_stdlib/src/default.nr b/noir/noir-repo/noir_stdlib/src/default.nr index 0acb39660343..f9399bfb865c 100644 --- a/noir/noir-repo/noir_stdlib/src/default.nr +++ b/noir/noir-repo/noir_stdlib/src/default.nr @@ -1,9 +1,20 @@ +use crate::meta::derive_via; + +#[derive_via(derive_default)] // docs:start:default-trait trait Default { fn default() -> Self; } // docs:end:default-trait +comptime fn derive_default(s: StructDefinition) -> Quoted { + let name = quote { Default }; + let signature = quote { fn default() -> Self }; + let for_each_field = |name| quote { $name: Default::default() }; + let body = |fields| quote { Self { $fields } }; + crate::meta::make_trait_impl(s, name, signature, for_each_field, quote { , }, body) +} + impl Default for Field { fn default() -> Field { 0 } } impl Default for u8 { fn default() -> u8 { 0 } } diff --git a/noir/noir-repo/noir_stdlib/src/hash/mod.nr b/noir/noir-repo/noir_stdlib/src/hash/mod.nr index 320b89353d93..84ad7c22bb32 100644 --- a/noir/noir-repo/noir_stdlib/src/hash/mod.nr +++ b/noir/noir-repo/noir_stdlib/src/hash/mod.nr @@ -8,6 +8,7 @@ use crate::uint128::U128; use crate::sha256::{digest, sha256_var}; use crate::collections::vec::Vec; use crate::embedded_curve_ops::{EmbeddedCurvePoint, EmbeddedCurveScalar, multi_scalar_mul, multi_scalar_mul_slice}; +use crate::meta::derive_via; #[foreign(sha256)] // docs:start:sha256 @@ -46,6 +47,7 @@ fn pedersen_commitment_with_separator(input: [Field; N], separator: } } +#[no_predicates] fn pedersen_commitment_with_separator_noir(input: [Field; N], separator: u32) -> EmbeddedCurvePoint { let mut points = [EmbeddedCurveScalar { lo: 0, hi: 0 }; N]; for i in 0..N { @@ -56,15 +58,19 @@ fn pedersen_commitment_with_separator_noir(input: [Field; N], separa multi_scalar_mul(generators, points) } +#[no_predicates] fn pedersen_hash_with_separator_noir(input: [Field; N], separator: u32) -> Field { let mut scalars: Vec = Vec::from_slice([EmbeddedCurveScalar { lo: 0, hi: 0 }; N].as_slice()); //Vec::new(); for i in 0..N { scalars.set(i, from_field_unsafe(input[i])); } - scalars.push(EmbeddedCurveScalar { lo: N as Field, hi: 0 }); + scalars.push(EmbeddedCurveScalar { lo: N as Field, hi: 0 as Field }); let domain_generators :[EmbeddedCurvePoint; N]= derive_generators("DEFAULT_DOMAIN_SEPARATOR".as_bytes(), separator); - let mut vec_generators = Vec::from_slice(domain_generators.as_slice()); + let mut vec_generators = Vec::new(); + for i in 0..N { + vec_generators.push(domain_generators[i]); + } let length_generator : [EmbeddedCurvePoint; 1] = derive_generators("pedersen_hash_length".as_bytes(), 0); vec_generators.push(length_generator[0]); multi_scalar_mul_slice(vec_generators.slice, scalars.slice)[0] @@ -86,7 +92,7 @@ fn __pedersen_commitment_with_separator(input: [Field; N], separator #[field(bn254)] fn derive_generators(domain_separator_bytes: [u8; M], starting_index: u32) -> [EmbeddedCurvePoint; N] { crate::assert_constant(domain_separator_bytes); - crate::assert_constant(starting_index); + // TODO(https://github.com/noir-lang/noir/issues/5672): Add back assert_constant on starting_index __derive_generators(domain_separator_bytes, starting_index) } @@ -136,10 +142,18 @@ pub fn sha256_compression(_input: [u32; 16], _state: [u32; 8]) -> [u32; 8] {} // Partially ported and impacted by rust. // Hash trait shall be implemented per type. -trait Hash{ +#[derive_via(derive_hash)] +trait Hash { fn hash(self, state: &mut H) where H: Hasher; } +comptime fn derive_hash(s: StructDefinition) -> 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); }; + crate::meta::make_trait_impl(s, name, signature, for_each_field, quote {}, |fields| fields) +} + // Hasher trait shall be implemented by algorithms to provide hash-agnostic means. // TODO: consider making the types generic here ([u8], [Field], etc.) trait Hasher{ diff --git a/noir/noir-repo/noir_stdlib/src/hash/poseidon2.nr b/noir/noir-repo/noir_stdlib/src/hash/poseidon2.nr index 08cf68d1f827..9626da0cf97a 100644 --- a/noir/noir-repo/noir_stdlib/src/hash/poseidon2.nr +++ b/noir/noir-repo/noir_stdlib/src/hash/poseidon2.nr @@ -1,7 +1,7 @@ use crate::hash::Hasher; use crate::default::Default; -global RATE: u32 = 3; +comptime global RATE: u32 = 3; struct Poseidon2 { cache: [Field;3], diff --git a/noir/noir-repo/noir_stdlib/src/lib.nr b/noir/noir-repo/noir_stdlib/src/lib.nr index ac53941e7529..2d559c431629 100644 --- a/noir/noir-repo/noir_stdlib/src/lib.nr +++ b/noir/noir-repo/noir_stdlib/src/lib.nr @@ -43,7 +43,12 @@ unconstrained pub fn println(input: T) { } #[foreign(recursive_aggregation)] -pub fn verify_proof(verification_key: [Field], proof: [Field], public_inputs: [Field], key_hash: Field) {} +pub fn verify_proof( + verification_key: [Field; N], + proof: [Field; M], + public_inputs: [Field; K], + key_hash: Field +) {} // Asserts that the given value is known at compile-time. // Useful for debugging for-loop bounds. diff --git a/noir/noir-repo/noir_stdlib/src/meta/expr.nr b/noir/noir-repo/noir_stdlib/src/meta/expr.nr new file mode 100644 index 000000000000..54681632543c --- /dev/null +++ b/noir/noir-repo/noir_stdlib/src/meta/expr.nr @@ -0,0 +1,6 @@ +use crate::option::Option; + +impl Expr { + #[builtin(expr_as_function_call)] + fn as_function_call(self) -> Option<(Expr, [Expr])> {} +} diff --git a/noir/noir-repo/noir_stdlib/src/meta/function_def.nr b/noir/noir-repo/noir_stdlib/src/meta/function_def.nr new file mode 100644 index 000000000000..2b5ddd008ea5 --- /dev/null +++ b/noir/noir-repo/noir_stdlib/src/meta/function_def.nr @@ -0,0 +1,19 @@ +impl FunctionDefinition { + #[builtin(function_def_name)] + fn name(self) -> Quoted {} + + #[builtin(function_def_parameters)] + fn parameters(self) -> [(Quoted, Type)] {} + + #[builtin(function_def_return_type)] + fn return_type(self) -> Type {} + + #[builtin(function_def_set_body)] + fn set_body(self, body: Quoted) {} + + #[builtin(function_def_set_parameters)] + fn set_parameters(self, parameters: [(Quoted, Type)]) {} + + #[builtin(function_def_set_return_type)] + fn set_return_type(self, return_type: Type) {} +} diff --git a/noir/noir-repo/noir_stdlib/src/meta/mod.nr b/noir/noir-repo/noir_stdlib/src/meta/mod.nr index 395f09a453ec..2763685fd0d7 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/mod.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/mod.nr @@ -1,6 +1,14 @@ +use crate::collections::umap::UHashMap; +use crate::hash::BuildHasherDefault; +use crate::hash::poseidon2::Poseidon2Hasher; + +mod expr; +mod function_def; +mod module; +mod struct_def; mod trait_constraint; mod trait_def; -mod type_def; +mod typ; mod quoted; /// Calling unquote as a macro (via `unquote!(arg)`) will unquote @@ -9,3 +17,72 @@ mod quoted; pub comptime fn unquote(code: Quoted) -> Quoted { code } + +/// Returns the type of any value +#[builtin(type_of)] +pub comptime fn type_of(x: T) -> Type {} + +type DeriveFunction = fn(StructDefinition) -> Quoted; + +comptime mut global HANDLERS: UHashMap> = UHashMap::default(); + +#[varargs] +pub comptime fn derive(s: StructDefinition, traits: [TraitDefinition]) -> Quoted { + let mut result = quote {}; + + for trait_to_derive in traits { + let handler = HANDLERS.get(trait_to_derive); + assert(handler.is_some(), f"No derive function registered for `{trait_to_derive}`"); + + let trait_impl = handler.unwrap()(s); + result = quote { $result $trait_impl }; + } + + result +} + +unconstrained pub comptime fn derive_via(t: TraitDefinition, f: DeriveFunction) { + HANDLERS.insert(t, f); +} + +/// `make_impl` is a helper function to make a simple impl, usually while deriving a trait. +/// This impl has a couple assumptions: +/// 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. +/// 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. +/// +/// See `derive_eq` and `derive_default` for example usage. +pub comptime fn make_trait_impl( + s: StructDefinition, + trait_name: Quoted, + function_signature: Quoted, + for_each_field: fn[Env1](Quoted) -> Quoted, + join_fields_with: Quoted, + body: fn[Env2](Quoted) -> Quoted +) -> Quoted { + let typ = s.as_type(); + let impl_generics = s.generics().map(|g| quote { $g }).join(quote {,}); + let where_clause = s.generics().map(|name| quote { $name: $trait_name }).join(quote {,}); + + // `for_each_field(field1) $join_fields_with for_each_field(field2) $join_fields_with ...` + let fields = s.fields().map( + |f: (Quoted, Type)| { + let name = f.0; + for_each_field(name) + } + ); + let body = body(fields.join(join_fields_with)); + + quote { + impl<$impl_generics> $trait_name for $typ where $where_clause { + $function_signature { + $body + } + } + } +} diff --git a/noir/noir-repo/noir_stdlib/src/meta/module.nr b/noir/noir-repo/noir_stdlib/src/meta/module.nr new file mode 100644 index 000000000000..ee00f3608069 --- /dev/null +++ b/noir/noir-repo/noir_stdlib/src/meta/module.nr @@ -0,0 +1,10 @@ +impl Module { + #[builtin(module_is_contract)] + fn is_contract(self) -> bool {} + + #[builtin(module_functions)] + fn functions(self) -> [FunctionDefinition] {} + + #[builtin(module_name)] + fn name(self) -> Quoted {} +} diff --git a/noir/noir-repo/noir_stdlib/src/meta/quoted.nr b/noir/noir-repo/noir_stdlib/src/meta/quoted.nr index 6273d64b10c5..cccc3fe0f122 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/quoted.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/quoted.nr @@ -1,4 +1,26 @@ +use crate::cmp::Eq; +use crate::option::Option; + impl Quoted { + #[builtin(quoted_as_expr)] + fn as_expr(self) -> Option {} + + #[builtin(quoted_as_module)] + fn as_module(self) -> Option {} + #[builtin(quoted_as_trait_constraint)] fn as_trait_constraint(self) -> TraitConstraint {} + + #[builtin(quoted_as_type)] + fn as_type(self) -> Type {} } + +impl Eq for Quoted { + fn eq(self, other: Quoted) -> bool { + quoted_eq(self, other) + } +} + +#[builtin(quoted_eq)] +fn quoted_eq(_first: Quoted, _second: Quoted) -> bool {} + diff --git a/noir/noir-repo/noir_stdlib/src/meta/type_def.nr b/noir/noir-repo/noir_stdlib/src/meta/struct_def.nr similarity index 58% rename from noir/noir-repo/noir_stdlib/src/meta/type_def.nr rename to noir/noir-repo/noir_stdlib/src/meta/struct_def.nr index c01aab4b1413..8d3f9ceb8a5d 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/type_def.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/struct_def.nr @@ -2,15 +2,14 @@ impl StructDefinition { /// Return a syntactic version of this struct definition as a type. /// For example, `as_type(quote { type Foo { ... } })` would return `Foo` #[builtin(struct_def_as_type)] - fn as_type(self) -> Quoted {} + fn as_type(self) -> Type {} - /// Return each generic on this struct. The names of these generics are unchanged - /// so users may need to keep name collisions in mind if this is used directly in a macro. + /// Return each generic on this struct. #[builtin(struct_def_generics)] - fn generics(self) -> [Quoted] {} + fn generics(self) -> [Type] {} /// Returns (name, type) pairs of each field in this struct. Each type is as-is /// with any generic arguments unchanged. #[builtin(struct_def_fields)] - fn fields(self) -> [(Quoted, Quoted)] {} + fn fields(self) -> [(Quoted, Type)] {} } diff --git a/noir/noir-repo/noir_stdlib/src/meta/trait_def.nr b/noir/noir-repo/noir_stdlib/src/meta/trait_def.nr index 5de7631e34de..ca381cb8e163 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/trait_def.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/trait_def.nr @@ -1,4 +1,25 @@ +use crate::hash::{Hash, Hasher}; +use crate::cmp::Eq; + impl TraitDefinition { #[builtin(trait_def_as_trait_constraint)] fn as_trait_constraint(_self: Self) -> TraitConstraint {} } + +impl Eq for TraitDefinition { + fn eq(self, other: Self) -> bool { + trait_def_eq(self, other) + } +} + +impl Hash for TraitDefinition { + fn hash(self, state: &mut H) where H: Hasher { + state.write(trait_def_hash(self)); + } +} + +#[builtin(trait_def_eq)] +fn trait_def_eq(_first: TraitDefinition, _second: TraitDefinition) -> bool {} + +#[builtin(trait_def_hash)] +fn trait_def_hash(_def: TraitDefinition) -> Field {} diff --git a/noir/noir-repo/noir_stdlib/src/meta/typ.nr b/noir/noir-repo/noir_stdlib/src/meta/typ.nr new file mode 100644 index 000000000000..ad669e93c0a9 --- /dev/null +++ b/noir/noir-repo/noir_stdlib/src/meta/typ.nr @@ -0,0 +1,40 @@ +use crate::cmp::Eq; +use crate::option::Option; + +impl Type { + #[builtin(type_as_array)] + fn as_array(self) -> Option<(Type, Type)> {} + + #[builtin(type_as_constant)] + fn as_constant(self) -> Option {} + + #[builtin(type_as_integer)] + fn as_integer(self) -> Option<(bool, u8)> {} + + #[builtin(type_as_slice)] + fn as_slice(self) -> Option {} + + #[builtin(type_as_struct)] + fn as_struct(self) -> Option<(StructDefinition, [Type])> {} + + #[builtin(type_as_tuple)] + fn as_tuple(self) -> Option<[Type]> {} + + #[builtin(type_implements)] + fn implements(self, constraint: TraitConstraint) -> bool {} + + #[builtin(type_is_bool)] + fn is_bool(self) -> bool {} + + #[builtin(type_is_field)] + fn is_field(self) -> bool {} +} + +impl Eq for Type { + fn eq(self, other: Self) -> bool { + type_eq(self, other) + } +} + +#[builtin(type_eq)] +fn type_eq(_first: Type, _second: Type) -> bool {} diff --git a/noir/noir-repo/noir_stdlib/src/prelude.nr b/noir/noir-repo/noir_stdlib/src/prelude.nr index 3244329aa4b4..0d423e3556dc 100644 --- a/noir/noir-repo/noir_stdlib/src/prelude.nr +++ b/noir/noir-repo/noir_stdlib/src/prelude.nr @@ -6,3 +6,4 @@ use crate::uint128::U128; use crate::cmp::{Eq, Ord}; use crate::default::Default; use crate::convert::{From, Into}; +use crate::meta::{derive, derive_via}; diff --git a/noir/noir-repo/noir_stdlib/src/string.nr b/noir/noir-repo/noir_stdlib/src/string.nr index 5f8f3de775dc..18fb449626ab 100644 --- a/noir/noir-repo/noir_stdlib/src/string.nr +++ b/noir/noir-repo/noir_stdlib/src/string.nr @@ -1,4 +1,6 @@ use crate::collections::vec::Vec; +use crate::convert::From; + impl str { /// Converts the given string into a byte array #[builtin(str_as_bytes)] @@ -9,3 +11,9 @@ impl str { Vec::from_slice(self.as_bytes().as_slice()) } } + +impl From<[u8; N]> for str { + fn from(bytes: [u8; N]) -> Self { + bytes.as_str_unchecked() + } +} diff --git a/noir/noir-repo/noir_stdlib/src/uint128.nr b/noir/noir-repo/noir_stdlib/src/uint128.nr index e99818bafa0d..7b75cf4cae4f 100644 --- a/noir/noir-repo/noir_stdlib/src/uint128.nr +++ b/noir/noir-repo/noir_stdlib/src/uint128.nr @@ -100,14 +100,14 @@ impl U128 { } fn decode_ascii(ascii: u8) -> Field { - if ascii < 58 { + (if ascii < 58 { ascii - 48 } else { let ascii = ascii + 32 * (U128::uconstrained_check_is_upper_ascii(ascii) as u8); assert(ascii >= 97); // enforce >= 'a' assert(ascii <= 102); // enforce <= 'f' ascii - 87 - } as Field + }) as Field } // TODO: Replace with a faster version. diff --git a/noir/noir-repo/scripts/install_bb.sh b/noir/noir-repo/scripts/install_bb.sh index 95dcfdda8800..65a449be5433 100755 --- a/noir/noir-repo/scripts/install_bb.sh +++ b/noir/noir-repo/scripts/install_bb.sh @@ -1,6 +1,6 @@ #!/bin/bash -VERSION="0.46.1" +VERSION="0.47.1" BBUP_PATH=~/.bb/bbup diff --git a/noir/noir-repo/test_programs/compile_failure/non_comptime_local_fn_call/Nargo.toml b/noir/noir-repo/test_programs/compile_failure/non_comptime_local_fn_call/Nargo.toml deleted file mode 100644 index 8bdefbbbd218..000000000000 --- a/noir/noir-repo/test_programs/compile_failure/non_comptime_local_fn_call/Nargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "non_comptime_local_fn_call" -type = "bin" -authors = [""] -compiler_version = ">=0.23.0" - -[dependencies] diff --git a/noir/noir-repo/test_programs/compile_failure/non_comptime_local_fn_call/src/main.nr b/noir/noir-repo/test_programs/compile_failure/non_comptime_local_fn_call/src/main.nr deleted file mode 100644 index d75bb1a922af..000000000000 --- a/noir/noir-repo/test_programs/compile_failure/non_comptime_local_fn_call/src/main.nr +++ /dev/null @@ -1,9 +0,0 @@ -fn main() { - comptime { - let _a = id(3); - } -} - -fn id(x: Field) -> Field { - x -} diff --git a/noir/noir-repo/test_programs/compile_failure/type_annotation_needed_on_struct_constructor/Nargo.toml b/noir/noir-repo/test_programs/compile_failure/type_annotation_needed_on_struct_constructor/Nargo.toml new file mode 100644 index 000000000000..ac7933fa250c --- /dev/null +++ b/noir/noir-repo/test_programs/compile_failure/type_annotation_needed_on_struct_constructor/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "type_annotation_needed_on_struct_constructor" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/compile_failure/type_annotation_needed_on_struct_constructor/src/main.nr b/noir/noir-repo/test_programs/compile_failure/type_annotation_needed_on_struct_constructor/src/main.nr new file mode 100644 index 000000000000..5207210dfbf3 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_failure/type_annotation_needed_on_struct_constructor/src/main.nr @@ -0,0 +1,6 @@ +struct Foo { +} + +fn main() { + let foo = Foo {}; +} diff --git a/noir/noir-repo/test_programs/compile_failure/type_annotation_needed_on_struct_new/Nargo.toml b/noir/noir-repo/test_programs/compile_failure/type_annotation_needed_on_struct_new/Nargo.toml new file mode 100644 index 000000000000..cb53d2924f42 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_failure/type_annotation_needed_on_struct_new/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "type_annotation_needed_on_struct_new" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/compile_failure/type_annotation_needed_on_struct_new/src/main.nr b/noir/noir-repo/test_programs/compile_failure/type_annotation_needed_on_struct_new/src/main.nr new file mode 100644 index 000000000000..f740dfa6d378 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_failure/type_annotation_needed_on_struct_new/src/main.nr @@ -0,0 +1,12 @@ +struct Foo { +} + +impl Foo { + fn new() -> Foo { + Foo {} + } +} + +fn main() { + let foo = Foo::new(); +} diff --git a/noir/noir-repo/test_programs/compile_success_empty/arithmetic_generics/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/arithmetic_generics/Nargo.toml new file mode 100644 index 000000000000..2352ae0c5625 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/arithmetic_generics/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "arithmetic_generics" +type = "bin" +authors = [""] +compiler_version = ">=0.32.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/compile_success_empty/arithmetic_generics/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/arithmetic_generics/src/main.nr new file mode 100644 index 000000000000..d4f71d38413f --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/arithmetic_generics/src/main.nr @@ -0,0 +1,103 @@ +fn main() { + let (first, rest) = split_first([1, 2, 3, 4]); + assert_eq(first, 1); + assert_eq(rest, [2, 3, 4]); + + // Type inference works without the type constraints from assert_eq as well + let _ = split_first([1, 2, 3]); + + let _ = push_multiple([1, 2, 3]); +} + +fn split_first(array: [T; N]) -> (T, [T; N - 1]) { + std::static_assert(N != 0, "split_first called on empty array"); + let mut new_array: [T; N - 1] = std::unsafe::zeroed(); + + for i in 0..N - 1 { + new_array[i] = array[i + 1]; + } + + (array[0], new_array) +} + +fn push(array: [Field; N], element: Field) -> [Field; N + 1] { + let mut result: [_; N + 1] = std::unsafe::zeroed(); + result[array.len()] = element; + + for i in 0..array.len() { + result[i] = array[i]; + } + + result +} + +fn push_multiple(array: [Field; N]) -> [Field; N + 2] { + // : [Field; N + 1] + let array2 = push(array, 4); + + // : [Field; (N + 1) + 1] + let array3 = push(array2, 5); + + // [Field; (N + 1) + 1] = [Field; N + 2] + array3 +} + +// This signature fails because we can't match `_ + 1` to `3` at the call site +// fn push_multiple(array: [Field; 1 + N]) -> [Field; N + 3] { + +// ********************************************* +// The rest of this file is setup for demo_proof +// ********************************************* + +struct W { } + +struct Equiv { + // TODO(https://github.com/noir-lang/noir/issues/5644): + // Bug with struct_obj.field_thats_a_fn(x) + + to_: fn[TU](T) -> U, + fro_: fn[UT](U) -> T, + // .. other coherence conditions +} + +impl Equiv { + fn to(self, x: T) -> U { + (self.to_)(x) + } + + fn fro(self, x: U) -> T { + (self.fro_)(x) + } +} + +fn equiv_trans( + x: Equiv, + y: Equiv +) -> Equiv, Equiv), V, (Equiv, Equiv)> { + Equiv { to_: |z| { y.to(x.to(z)) }, fro_: |z| { x.fro(y.fro(z)) } } +} + +fn mul_one_r() -> Equiv, (), W, ()> { + Equiv { to_: |_x| { W {} }, fro_: |_x| { W {} } } +} + +fn add_equiv_r(_: Equiv, EN, W, EM>) -> Equiv, (), W, ()> { + Equiv { to_: |_x| { W {} }, fro_: |_x| { W {} } } +} + +fn mul_comm() -> Equiv, (), W, ()> { + Equiv { to_: |_x| { W {} }, fro_: |_x| { W {} } } +} + +fn mul_add() -> Equiv, (), W, ()> { + Equiv { to_: |_x| { W {} }, fro_: |_x| { W {} } } +} + +// (N + 1) * N == N * N + N +fn demo_proof() -> Equiv, (Equiv, (), W, ()>, Equiv, (Equiv, (), W, ()>, Equiv, (), W<(N * (N + 1))>, ()>), W, (Equiv, (), W<(N * (N + 1))>, ()>, Equiv, (), W, ()>)>), W, (Equiv, (Equiv, (), W, ()>, Equiv, (), W<(N * (N + 1))>, ()>), W, (Equiv, (), W<(N * (N + 1))>, ()>, Equiv, (), W, ()>)>, Equiv, (), W, ()>)> { + let p1: Equiv, (), W, ()> = mul_comm(); + let p2: Equiv, (), W, ()> = mul_add::(); + let p3_sub: Equiv, (), W, ()> = mul_one_r(); + let p3: Equiv, (), W, ()> = add_equiv_r::(p3_sub); + equiv_trans(equiv_trans(p1, p2), p3) +} 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 44b9c20460f9..6178df5e7495 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 @@ -1,9 +1,9 @@ -#[attr_with_args(a b, c d)] -#[varargs(one, two)] -#[varargs(one, two, three, four)] +#[attr_with_args(1, 2)] +#[varargs(1, 2)] +#[varargs(1, 2, 3, 4)] struct Foo {} -comptime fn attr_with_args(s: StructDefinition, a: Quoted, b: Quoted) { +comptime fn attr_with_args(s: StructDefinition, a: Field, b: Field) { // Ensure all variables are in scope. // We can't print them since that breaks the test runner. let _ = s; @@ -11,7 +11,8 @@ comptime fn attr_with_args(s: StructDefinition, a: Quoted, b: Quoted) { let _ = b; } -comptime fn varargs(s: StructDefinition, t: [Quoted]) { +#[varargs] +comptime fn varargs(s: StructDefinition, t: [Field]) { let _ = s; for _ in t {} assert(t.len() < 5); diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_closures/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/comptime_closures/Nargo.toml new file mode 100644 index 000000000000..50cde2ec2dda --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_closures/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "comptime_closures" +type = "bin" +authors = [""] +compiler_version = ">=0.32.0" + +[dependencies] diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_closures/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_closures/src/main.nr new file mode 100644 index 000000000000..95f602e04bfc --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_closures/src/main.nr @@ -0,0 +1,39 @@ +fn main() { + comptime + { + closure_test(0); + } +} + +fn closure_test(mut x: Field) { + let one = 1; + let add1 = |z| { + (|| { + *z += one; + })() + }; + + let two = 2; + let add2 = |z| { + *z = *z + two; + }; + + add1(&mut x); + assert(x == 1); + + add2(&mut x); + assert(x == 3); + + issue_2120(); +} + +fn issue_2120() { + let x1 = &mut 42; + let set_x1 = |y| { *x1 = y; }; + + assert(*x1 == 42); + set_x1(44); + assert(*x1 == 44); + set_x1(*x1); + assert(*x1 == 44); +} diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_exp/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/comptime_exp/Nargo.toml new file mode 100644 index 000000000000..df36e0e05b0c --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_exp/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "comptime_exp" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_exp/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_exp/src/main.nr new file mode 100644 index 000000000000..8b6f7b480c7c --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_exp/src/main.nr @@ -0,0 +1,8 @@ +fn main() { + comptime + { + let expr = quote { foo(bar) }.as_expr().unwrap(); + let (_function, args) = expr.as_function_call().unwrap(); + assert_eq(args.len(), 1); + } +} diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_fmt_strings/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/comptime_fmt_strings/Nargo.toml new file mode 100644 index 000000000000..84162d3c0935 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_fmt_strings/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "comptime_fmt_strings" +type = "bin" +authors = [""] +compiler_version = ">=0.32.0" + +[dependencies] diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_fmt_strings/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_fmt_strings/src/main.nr new file mode 100644 index 000000000000..19572fd15a14 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_fmt_strings/src/main.nr @@ -0,0 +1,15 @@ +fn main() { + // format strings are lowered as normal strings + let (s1, s2): (str<39>, str<4>) = comptime { + let x = 4; + let y = 5; + + // Can't print these at compile-time here since printing to stdout while + // compiling breaks the test runner. + let s1 = f"x is {x}, fake interpolation: \{y}, y is {y}"; + let s2 = std::unsafe::zeroed::>(); + (s1, s2) + }; + assert_eq(s1, "x is 4, fake interpolation: {y}, y is 5"); + assert_eq(s2, "\0\0\0\0"); +} diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_function_definition/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/comptime_function_definition/Nargo.toml new file mode 100644 index 000000000000..a5f1e75dcef4 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_function_definition/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "comptime_function_definition" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file 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 new file mode 100644 index 000000000000..ce09ba86e49f --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_function_definition/src/main.nr @@ -0,0 +1,57 @@ +use std::meta::type_of; + +struct Foo { x: Field, field: Field } + +#[function_attr] +fn foo(w: i32, y: Field, Foo { x, field: some_field }: Foo, mut a: bool, (b, c): (i32, i32)) -> i32 { + let _ = (w, y, x, some_field, a, b, c); + 1 +} + +comptime fn function_attr(f: FunctionDefinition) { + // Check FunctionDefinition::parameters + let parameters = f.parameters(); + assert_eq(parameters.len(), 5); + + assert_eq(parameters[0].0, quote { w }); + assert_eq(parameters[1].0, quote { y }); + assert_eq(parameters[2].0, quote { Foo { x, field: some_field } }); + assert_eq(parameters[3].0, quote { mut a }); + assert_eq(parameters[4].0, quote { (b, c) }); + + let an_i32: i32 = 0; + + assert_eq(parameters[0].1, type_of(an_i32)); + assert_eq(parameters[1].1, type_of(0)); + assert_eq(parameters[2].1, type_of(Foo { x: 0, field: 1 })); + assert_eq(parameters[3].1, type_of(true)); + assert_eq(parameters[4].1, type_of((an_i32, an_i32))); + + // Check FunctionDefinition::return_type + assert_eq(f.return_type(), type_of(an_i32)); + + // Check FunctionDefinition::name + assert_eq(f.name(), quote { foo }); +} + +#[mutate_add_one] +fn add_one() {} + +comptime fn mutate_add_one(f: FunctionDefinition) { + // fn add_one(x: Field) + assert_eq(f.parameters().len(), 0); + f.set_parameters(&[(quote { x }, type_of(0))]); + assert_eq(f.parameters().len(), 1); + + // fn add_one(x: Field) -> Field + assert_eq(f.return_type(), type_of(())); + f.set_return_type(type_of(0)); + assert_eq(f.return_type(), type_of(0)); + + // fn add_one(x: Field) -> Field { x + 1 } + f.set_body(quote { x + 1 }); +} + +fn main() { + assert_eq(add_one(41), 42); +} diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_module/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/comptime_module/Nargo.toml new file mode 100644 index 000000000000..cb295ca1e17e --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_module/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "comptime_module" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_module/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_module/src/main.nr new file mode 100644 index 000000000000..e9c9817cfd88 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_module/src/main.nr @@ -0,0 +1,26 @@ +mod foo { + fn x() {} + fn y() {} +} + +contract bar {} + +fn main() { + comptime + { + // Check Module::is_contract + let foo = quote { foo }.as_module().unwrap(); + assert(!foo.is_contract()); + + let bar = quote { bar }.as_module().unwrap(); + assert(bar.is_contract()); + + // Check Module::functions + assert_eq(foo.functions().len(), 2); + assert_eq(bar.functions().len(), 0); + + // Check Module::name + assert_eq(foo.name(), quote { foo }); + assert_eq(bar.name(), quote { bar }); + } +} diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_trait_constraint/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_trait_constraint/src/main.nr index 5c99f8c587eb..2f2ca89cfb55 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/comptime_trait_constraint/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_trait_constraint/src/main.nr @@ -24,16 +24,16 @@ fn main() { } } -comptime struct TestHasher { +struct TestHasher { result: Field, } -comptime impl Hasher for TestHasher { - comptime fn finish(self) -> Field { +impl Hasher for TestHasher { + fn finish(self) -> Field { self.result } - comptime fn write(&mut self, input: Field) { + fn write(&mut self, input: Field) { self.result += input; } } diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_traits/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_traits/src/main.nr index 8b1f81e65942..7d1e116dd0c2 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/comptime_traits/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_traits/src/main.nr @@ -20,8 +20,8 @@ struct MyType { value: i32, } -comptime impl Neg for MyType { - comptime fn neg(self) -> Self { +impl Neg for MyType { + fn neg(self) -> Self { self } } diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_type/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/comptime_type/Nargo.toml new file mode 100644 index 000000000000..c5b9ca892401 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_type/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "comptime_type" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file 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 new file mode 100644 index 000000000000..170292b0e375 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_type/src/main.nr @@ -0,0 +1,121 @@ +use std::meta::type_of; + +struct Foo { + x: T +} + +trait SomeTrait { + +} +struct StructImplementsSomeTrait { + +} + +impl SomeTrait for StructImplementsSomeTrait { + +} + +struct StructDoesNotImplementSomeTrait { + +} + +fn main() { + comptime + { + // Check type_of works correctly (relies on Eq for Type) + let a_field = 0; + let another_field = 1; + let an_i32: i32 = 0; + let field_type_1 = type_of(a_field); + let field_type_2 = type_of(another_field); + let i32_type = type_of(an_i32); + assert(field_type_1 == field_type_2); + assert(field_type_1 != i32_type); + + // Check Type::is_field + assert(field_type_1.is_field()); + assert(!i32_type.is_field()); + + // Check Type::as_integer + assert(field_type_1.as_integer().is_none()); + + let (signed, bits) = i32_type.as_integer().unwrap(); + assert(signed); + assert_eq(bits, 32); + + let a_u8: u8 = 0; + let u8_type = type_of(a_u8); + let (signed, bits) = u8_type.as_integer().unwrap(); + assert(!signed); + assert_eq(bits, 8); + + // Check Type::as_tuple + assert(u8_type.as_tuple().is_none()); + + let tuple = (an_i32, a_u8); + let tuple_type = type_of(tuple); + let tuple_types = tuple_type.as_tuple().unwrap(); + assert_eq(tuple_types.len(), 2); + assert_eq(tuple_types[0], i32_type); + assert_eq(tuple_types[1], u8_type); + + // Check Type::as_slice + assert(u8_type.as_slice().is_none()); + + let slice = &[1]; + let slice_type = type_of(slice); + let slice_type_element_type = slice_type.as_slice().unwrap(); + assert_eq(slice_type_element_type, field_type_1); + + // Check Type::as_array + assert(u8_type.as_array().is_none()); + + let array = [1, 2, 3]; + let array_type = type_of(array); + let (array_type_element_type , array_length) = array_type.as_array().unwrap(); + assert_eq(array_type_element_type, field_type_1); + + // Check Type::as_constant + assert(u8_type.as_constant().is_none()); + assert_eq(array_length.as_constant().unwrap(), 3); + + // Check Type::is_bool + assert(!u8_type.is_bool()); + + let yes = true; + let bool_type = type_of(yes); + assert(bool_type.is_bool()); + + // Check Type::as_struct + assert(u8_type.as_struct().is_none()); + + let foo = Foo { x: 0 }; + let foo_type = type_of(foo); + let (struct_definition, generics) = foo_type.as_struct().unwrap(); + let fields = struct_definition.fields(); + assert_eq(fields.len(), 1); + + assert_eq(generics.len(), 1); + assert_eq(generics[0], field_type_1); + + // Check Type::implements + let some_trait_i32 = quote { SomeTrait }.as_trait_constraint(); + let struct_implements_some_trait = quote { StructImplementsSomeTrait }.as_type(); + let struct_does_not_implement_some_trait = quote { StructDoesNotImplementSomeTrait }.as_type(); + assert(struct_implements_some_trait.implements(some_trait_i32)); + assert(!struct_does_not_implement_some_trait.implements(some_trait_i32)); + + let some_trait_field = quote { SomeTrait }.as_trait_constraint(); + assert(!struct_implements_some_trait.implements(some_trait_field)); + assert(!struct_does_not_implement_some_trait.implements(some_trait_field)); + } +} + +fn function_with_where(_x: T) where T: SomeTrait { + comptime + { + let t = quote { T }.as_type(); + let some_trait_i32 = quote { SomeTrait }.as_trait_constraint(); + assert(t.implements(some_trait_i32)); + } +} 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 5463a61d9698..69cb641e7c7a 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,5 +1,5 @@ comptime fn derive_default(typ: StructDefinition) -> Quoted { - let generics: [Quoted] = typ.generics(); + let generics = typ.generics(); assert_eq( generics.len(), 0, "derive_default: Deriving Default on generic types is currently unimplemented" ); @@ -27,7 +27,7 @@ struct Foo { #[derive_default] struct Bar {} -comptime fn make_field_exprs(fields: [(Quoted, Quoted)]) -> [Quoted] { +comptime fn make_field_exprs(fields: [(Quoted, Type)]) -> [Quoted] { let mut result = &[]; for my_field in fields { let name = my_field.0; diff --git a/noir/noir-repo/test_programs/compile_success_empty/quoted_as_type/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/quoted_as_type/Nargo.toml new file mode 100644 index 000000000000..7d669ead363c --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/quoted_as_type/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "quoted_as_type" +type = "bin" +authors = [""] +compiler_version = ">=0.32.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/compile_success_empty/quoted_as_type/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/quoted_as_type/src/main.nr new file mode 100644 index 000000000000..e06294592ca4 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/quoted_as_type/src/main.nr @@ -0,0 +1,21 @@ +fn main() { + macro!().do_nothing(); +} + +comptime fn macro() -> Quoted { + let typ = quote { Foo }.as_type(); + quote { let foo: $typ = Foo {}; foo } +} + +struct Foo {} + +// Ensure we call the Foo impl +impl Foo { + fn do_nothing(_self: Self) { + assert(false); + } +} + +impl Foo { + fn do_nothing(_self: Self) {} +} diff --git a/noir/noir-repo/test_programs/compile_success_empty/regression_5671/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/regression_5671/Nargo.toml new file mode 100644 index 000000000000..4ddf3413e5ea --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/regression_5671/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "regression_5671" +type = "bin" +authors = [""] +compiler_version = ">=0.32.0" + +[dependencies] 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 new file mode 100644 index 000000000000..2bac98ef7c42 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/regression_5671/src/main.nr @@ -0,0 +1,20 @@ +#[foo] +struct MyOtherStruct { + field1: A, + field2: B, +} + +comptime fn foo(_s: StructDefinition) -> Quoted { + quote { + impl Eq for MyOtherStruct where A: Eq, B: Eq { + fn eq(self, other: Self) -> bool { + (self.field1 == other.field1) & (self.field2 == other.field2) + } + } + } +} + +fn main() { + let x = MyOtherStruct { field1: 1, field2: 2 }; + assert_eq(x, x); +} diff --git a/noir/noir-repo/test_programs/execution_success/verify_honk_proof/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/trait_call_in_global/Nargo.toml similarity index 64% rename from noir/noir-repo/test_programs/execution_success/verify_honk_proof/Nargo.toml rename to noir/noir-repo/test_programs/compile_success_empty/trait_call_in_global/Nargo.toml index 8fce1bf44b6e..005fec5bf366 100644 --- a/noir/noir-repo/test_programs/execution_success/verify_honk_proof/Nargo.toml +++ b/noir/noir-repo/test_programs/compile_success_empty/trait_call_in_global/Nargo.toml @@ -1,5 +1,5 @@ [package] -name = "verify_honk_proof" +name = "trait_call_in_global" type = "bin" authors = [""] diff --git a/noir/noir-repo/test_programs/compile_success_empty/trait_call_in_global/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/trait_call_in_global/src/main.nr new file mode 100644 index 000000000000..775cb5f3b7d6 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/trait_call_in_global/src/main.nr @@ -0,0 +1,5 @@ +global s: BoundedVec = From::from([0]); + +fn main() { + let _ = s; +} diff --git a/noir/noir-repo/test_programs/compile_success_empty/zeroed_slice/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/zeroed_slice/Nargo.toml new file mode 100644 index 000000000000..650baead9e2b --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/zeroed_slice/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "zeroed_slice" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/compile_success_empty/zeroed_slice/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/zeroed_slice/src/main.nr new file mode 100644 index 000000000000..44ccb2bd5959 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/zeroed_slice/src/main.nr @@ -0,0 +1,3 @@ +fn main() { + let _: [u8] = std::unsafe::zeroed(); +} diff --git a/noir/noir-repo/test_programs/execution_success/databus/src/main.nr b/noir/noir-repo/test_programs/execution_success/databus/src/main.nr index 7e5c23d508de..1e4aa141eead 100644 --- a/noir/noir-repo/test_programs/execution_success/databus/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/databus/src/main.nr @@ -1,4 +1,4 @@ -fn main(mut x: u32, y: call_data u32, z: call_data [u32; 4]) -> return_data u32 { +fn main(mut x: u32, y: call_data(0) u32, z: call_data(0) [u32; 4]) -> return_data u32 { let a = z[x]; a + foo(y) } diff --git a/noir/noir-repo/test_programs/execution_success/derive/Nargo.toml b/noir/noir-repo/test_programs/execution_success/derive/Nargo.toml new file mode 100644 index 000000000000..f38465943050 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/derive/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "derive" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file 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 new file mode 100644 index 000000000000..5ec2fb32a799 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/derive/src/main.nr @@ -0,0 +1,70 @@ +use std::hash::Hash; + +#[derive_via(derive_do_nothing)] +trait DoNothing { + fn do_nothing(self); +} + +#[derive(DoNothing)] +struct MyStruct { my_field: u32 } + +comptime fn derive_do_nothing(s: StructDefinition) -> Quoted { + let typ = s.as_type(); + let generics = s.generics().map(|g| quote { $g }).join(quote {,}); + quote { + impl<$generics> DoNothing for $typ { + fn do_nothing(_self: Self) { + // Traits can't tell us what to do + println("something"); + } + } + } +} + +// Test stdlib derive fns & multiple traits +// - We can derive Ord and Hash even though std::cmp::Ordering and std::hash::Hasher aren't imported +#[derive(Eq, Default, Hash, Ord)] +struct MyOtherStruct { + field1: A, + field2: B, + field3: MyOtherOtherStruct, +} + +#[derive(Eq, Default, Hash, Ord)] +struct MyOtherOtherStruct { + x: T, +} + +fn main() { + let s = MyStruct { my_field: 1 }; + s.do_nothing(); + + let o: MyOtherStruct = MyOtherStruct::default(); + assert_eq(o, o); + + let o: MyOtherStruct]> = MyOtherStruct::default(); + assert_eq(o, o); + + // Field & str<2> above don't implement Ord + let o1 = MyOtherStruct { field1: 12 as u32, field2: 24 as i8, field3: MyOtherOtherStruct { x: 54 as i8 } }; + let o2 = MyOtherStruct { field1: 12 as u32, field2: 24 as i8, field3: MyOtherOtherStruct { x: 55 as i8 } }; + assert(o1 < o2); + + let mut hasher = TestHasher { result: 0 }; + o1.hash(&mut hasher); + assert_eq(hasher.finish(), 12 + 24 + 54); +} + +struct TestHasher { + result: Field, +} + +impl std::hash::Hasher for TestHasher { + fn finish(self) -> Field { + self.result + } + + fn write(&mut self, input: Field) { + self.result += input; + } +} diff --git a/noir/noir-repo/test_programs/execution_success/double_verify_nested_proof/src/main.nr b/noir/noir-repo/test_programs/execution_success/double_verify_nested_proof/src/main.nr index 5f0eb1a5b530..75a5fa9ebdab 100644 --- a/noir/noir-repo/test_programs/execution_success/double_verify_nested_proof/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/double_verify_nested_proof/src/main.nr @@ -19,17 +19,7 @@ fn main( key_hash: Field, proof_b: [Field; 109] ) { - std::verify_proof( - verification_key.as_slice(), - proof.as_slice(), - public_inputs.as_slice(), - key_hash - ); + std::verify_proof(verification_key, proof, public_inputs, key_hash); - std::verify_proof( - verification_key.as_slice(), - proof_b.as_slice(), - public_inputs.as_slice(), - key_hash - ); + std::verify_proof(verification_key, proof_b, public_inputs, key_hash); } diff --git a/noir/noir-repo/test_programs/execution_success/double_verify_proof/src/main.nr b/noir/noir-repo/test_programs/execution_success/double_verify_proof/src/main.nr index d3b909c3fa44..8d73bb09aa5f 100644 --- a/noir/noir-repo/test_programs/execution_success/double_verify_proof/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/double_verify_proof/src/main.nr @@ -12,17 +12,7 @@ fn main( key_hash: Field, proof_b: [Field; 93] ) { - std::verify_proof( - verification_key.as_slice(), - proof.as_slice(), - public_inputs.as_slice(), - key_hash - ); + std::verify_proof(verification_key, proof, public_inputs, key_hash); - std::verify_proof( - verification_key.as_slice(), - proof_b.as_slice(), - public_inputs.as_slice(), - key_hash - ); + std::verify_proof(verification_key, proof_b, public_inputs, key_hash); } diff --git a/noir/noir-repo/test_programs/execution_success/double_verify_proof_recursive/src/main.nr b/noir/noir-repo/test_programs/execution_success/double_verify_proof_recursive/src/main.nr index 2555bbc47587..5137a538e428 100644 --- a/noir/noir-repo/test_programs/execution_success/double_verify_proof_recursive/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/double_verify_proof_recursive/src/main.nr @@ -13,17 +13,7 @@ fn main( key_hash: Field, proof_b: [Field; 93] ) { - std::verify_proof( - verification_key.as_slice(), - proof.as_slice(), - public_inputs.as_slice(), - key_hash - ); + std::verify_proof(verification_key, proof, public_inputs, key_hash); - std::verify_proof( - verification_key.as_slice(), - proof_b.as_slice(), - public_inputs.as_slice(), - key_hash - ); + std::verify_proof(verification_key, proof_b, public_inputs, key_hash); } diff --git a/noir/noir-repo/test_programs/execution_success/regression_5615/Nargo.toml b/noir/noir-repo/test_programs/execution_success/regression_5615/Nargo.toml new file mode 100644 index 000000000000..738d99391a22 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/regression_5615/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "regression_5615" +type = "bin" +authors = [""] +compiler_version = ">=0.32.0" + +[dependencies] diff --git a/noir/noir-repo/test_programs/execution_success/regression_5615/src/main.nr b/noir/noir-repo/test_programs/execution_success/regression_5615/src/main.nr new file mode 100644 index 000000000000..afb641e510de --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/regression_5615/src/main.nr @@ -0,0 +1,12 @@ +use std::collections::umap::UHashMap; +use std::hash::BuildHasherDefault; +use std::hash::poseidon2::Poseidon2Hasher; + +unconstrained fn main() { + comptime + { + let mut map: UHashMap> = UHashMap::default(); + + map.insert(1, 2); + } +} diff --git a/noir/noir-repo/test_programs/execution_success/slice_regex/Nargo.toml b/noir/noir-repo/test_programs/execution_success/slice_regex/Nargo.toml new file mode 100644 index 000000000000..ac95636c74a7 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/slice_regex/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "slice_regex" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_success/slice_regex/src/main.nr b/noir/noir-repo/test_programs/execution_success/slice_regex/src/main.nr new file mode 100644 index 000000000000..43bd4433c694 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/slice_regex/src/main.nr @@ -0,0 +1,811 @@ +struct Match { + succeeded: bool, + match_ends: u32, + leftover: [u8], +} + +impl Match { + fn empty(leftover: [u8]) -> Self { + Match { succeeded: true, match_ends: 0, leftover } + } +} + +impl Eq for Match { + fn eq(self, other: Self) -> bool { + (self.succeeded == other.succeeded) & + (self.match_ends == other.match_ends) + // (self.leftover == other.leftover) + } +} + +// TODO: load match into str and assert that it's the correct length +// impl From for str + +trait Regex { + fn match(self, input: [u8]) -> Match; +} + +// Empty +impl Regex for () { + fn match(_self: Self, input: [u8]) -> Match { + Match::empty(input) + } +} + +// Exact +impl Regex for str { + fn match(self, input: [u8]) -> Match { + let mut leftover = input; + let mut matches_input = true; + let self_as_bytes = self.as_bytes(); + for c in self_as_bytes { + if leftover.len() != 0 { + let (first_elem, popped_slice) = leftover.pop_front(); + leftover = popped_slice; + matches_input &= first_elem == c; + } else { + matches_input = false; + } + } + if matches_input { + Match { + succeeded: true, + match_ends: self_as_bytes.len(), + leftover, + } + } else { + Match { + succeeded: false, + match_ends: 0, + leftover: input, + } + } + } +} + +// And +impl Regex for (T, U) where T: Regex, U: Regex { + fn match(self, input: [u8]) -> Match { + let lhs_result = self.0.match(input); + if lhs_result.succeeded { + let rhs_result = self.1.match(lhs_result.leftover); + if rhs_result.succeeded { + Match { + succeeded: true, + match_ends: lhs_result.match_ends + rhs_result.match_ends, + leftover: rhs_result.leftover, + } + } else { + Match { + succeeded: false, + match_ends: 0, + leftover: input, + } + } + } else { + Match { + succeeded: false, + match_ends: 0, + leftover: input, + } + } + } +} + +// N T's: (T, (T, (T, T))) +struct Repeated { + inner: T, +} + +impl Regex for Repeated where T: Regex { + fn match(self, input: [u8]) -> Match { + let mut result = Match::empty(input); + for _ in 0..N { + if result.succeeded { + let next_result = self.inner.match(result.leftover); + result = Match { + succeeded: next_result.succeeded, + match_ends: result.match_ends + next_result.match_ends, + leftover: next_result.leftover, + }; + } + } + result + } +} + +struct Or { + lhs: T, + rhs: U, +} + +impl Regex for Or where T: Regex, U: Regex { + fn match(self, input: [u8]) -> Match { + let lhs_result = self.lhs.match(input); + if lhs_result.succeeded { + lhs_result + } else { + self.rhs.match(input) + } + } +} + +struct Question { + inner: T, +} + +impl Regex for Question where T: Regex { + fn match(self, input: [u8]) -> Match { + Or { + lhs: self.inner, + rhs: (), + }.match(input) + } +} + +// 0 <= num_matches <= N +struct Star { + inner: T, +} + +impl Regex for Star where T: Regex { + fn match(self, input: [u8]) -> Match { + let regex: Repeated<_, N> = Repeated { + inner: Question { inner: self.inner }, + }; + regex.match(input) + } +} + +// 0 < num_matches <= N +struct Plus { + inner: T, +} + +impl Regex for Plus where T: Regex { + fn match(self, input: [u8]) -> Match { + std::static_assert(N_PRED + 1 == N, "N - 1 != N_PRED"); + let star: Star = Star { inner: self.inner }; + ( + self.inner, + star + ).match(input) + } +} + +fn main() { + // gr(a|e)y + let graey_regex = ("gr", (Or { lhs: "a", rhs: "e" }, "y")); + + // NOTE: leftover ignored in Eq: Match + let result = graey_regex.match("gray".as_bytes().as_slice()); + println(result); + assert_eq(result, Match { succeeded: true, match_ends: 4, leftover: &[] }); + + // NOTE: leftover ignored in Eq: Match + let result = graey_regex.match("grey".as_bytes().as_slice()); + println(result); + assert_eq(result, Match { succeeded: true, match_ends: 4, leftover: &[] }); + + // colou?r + let colour_regex = ("colo", (Question { inner: "u" }, "r")); + + let result = colour_regex.match("color".as_bytes().as_slice()); + println(result); + assert_eq(result, Match { succeeded: true, match_ends: 5, leftover: &[] }); + + let result = colour_regex.match("colour".as_bytes().as_slice()); + println(result); + assert_eq(result, Match { succeeded: true, match_ends: 6, leftover: &[] }); + + // parse the empty string three times + // EMPTY{3} + let three_empties_regex: Repeated<(), 3> = Repeated { inner: () }; + + let result = three_empties_regex.match("111".as_bytes().as_slice()); + println(result); + assert_eq(result, Match { succeeded: true, match_ends: 0, leftover: &[] }); + + // 1{0} + let zero_ones_regex: Repeated, 0> = Repeated { inner: "1" }; + + let result = zero_ones_regex.match("111".as_bytes().as_slice()); + println(result); + assert_eq(result, Match { succeeded: true, match_ends: 0, leftover: &[] }); + + // 1{1} + let one_ones_regex: Repeated, 1> = Repeated { inner: "1" }; + + let result = one_ones_regex.match("111".as_bytes().as_slice()); + println(result); + assert_eq(result, Match { succeeded: true, match_ends: 1, leftover: &[] }); + + // 1{2} + let two_ones_regex: Repeated, 2> = Repeated { inner: "1" }; + + let result = two_ones_regex.match("111".as_bytes().as_slice()); + println(result); + assert_eq(result, Match { succeeded: true, match_ends: 2, leftover: &[] }); + + // 1{3} + let three_ones_regex: Repeated, 3> = Repeated { inner: "1" }; + + let result = three_ones_regex.match("1111".as_bytes().as_slice()); + println(result); + assert_eq(result, Match { succeeded: true, match_ends: 3, leftover: &[] }); + // TODO(https://github.com/noir-lang/noir/issues/5462): re-enable these cases and complete the test using array_regex below + // + // // 1* + // let ones_regex: Star, 5> = Star { inner: "1" }; + // + // let result = ones_regex.match("11000".as_bytes().as_slice()); + // println(result); + // assert_eq(result, Match { succeeded: true, match_ends: 2, leftover: &[] }); + // + // let result = ones_regex.match("11".as_bytes().as_slice()); + // println(result); + // assert_eq(result, Match { succeeded: true, match_ends: 2, leftover: &[] }); + // + // let result = ones_regex.match("111111".as_bytes().as_slice()); + // println(result); + // assert_eq(result, Match { succeeded: true, match_ends: 5, leftover: &[] }); + // + // + // // 1+ + // let nonempty_ones_regex: Plus, 5, 4> = Plus { inner: "1" }; + // + // let result = nonempty_ones_regex.match("111111".as_bytes().as_slice()); + // println(result); + // assert_eq(result, Match { succeeded: true, match_ends: 5, leftover: &[] }); + // + // // 2^n-1 in binary: 1+0 + // let pred_pow_two_regex = (nonempty_ones_regex, "0"); + // + // let result = pred_pow_two_regex.match("1110".as_bytes().as_slice()); + // println(result); + // assert_eq(result, Match { succeeded: true, match_ends: 3, leftover: &[] }); + // + // // (0|1)* + // let binary_regex: Star, str<1>>, 5> = Star { inner: Or { lhs: "0", rhs: "1" } }; + // + // let result = binary_regex.match("110100".as_bytes().as_slice()); + // println(result); + // assert_eq(result, Match { succeeded: true, match_ends: 5, leftover: &[] }); + // + // // even numbers in binary: 1(0|1)*0 + // let even_binary_regex = ("1", (binary_regex, "0")); + // + // let result = even_binary_regex.match("1111110".as_bytes().as_slice()); + // println(result); + // assert_eq(result, Match { succeeded: true, match_ends: 6, leftover: &[] }); + // 2-letter capitalized words: [A-Z][a-z] + // numbers: \d+ + // [0-9]+ + // words: \w+ + // [a-Z]+ + // adapted URL parser: (https?:\/\/)?([\da-z.\-]+)\.([a-z.]+)([\/\w \.\-]*)*\/? + // // panics (at compile time) when input string is too short + // let foo_regex = ( + // "colo", + // ( + // Question { + // inner: "u", + // }, + // "r" + // ) + // ); + // + // let result = foo_regex.match("colo".as_bytes().as_slice()); + // println(result); + // assert_eq(result, Match { + // succeeded: true, + // match_ends: 4, + // leftover: &[], + // }); +} + +// array_regex: use to complete test once https://github.com/noir-lang/noir/issues/5462 is resolved +// +// // offset <= len <= N +// struct Bvec { +// inner: [T; N], +// +// // elements at indices < offset are zero +// offset: u32, +// +// // elements at indices >= len are zero +// len: u32, +// } +// +// impl Eq for Bvec where T: Eq { +// fn eq(self, other: Self) -> bool { +// (self.inner == other.inner) & +// (self.offset == other.offset) & +// (self.len == other.len) +// } +// } +// +// impl Bvec { +// fn empty() -> Self { +// Self { inner: [std::unsafe::zeroed(); N], offset: 0, len: 0 } +// } +// +// fn new(array: [T; N]) -> Self { +// let mut result = Bvec::empty(); +// for x in array { +// result = result.push(x); +// } +// result +// } +// +// // pushing when len == N is a no-op +// fn push(self, x: T) -> Self { +// let mut inner = self.inner; +// let mut len = self.len; +// if self.len < N { +// inner[self.len] = x; +// len += 1; +// } +// +// Self { inner, offset: self.offset, len } +// } +// +// fn pop_front(self) -> (T, Self) { +// assert(self.offset <= self.inner.len()); +// assert(self.len != 0); +// +// let first_elem = self.inner[self.offset]; +// let popped_slice = Self { inner: self.inner, offset: self.offset + 1, len: self.len - 1 }; +// +// (first_elem, popped_slice) +// } +// } +// +// struct Match { +// succeeded: bool, +// match_ends: u32, +// leftover: Bvec, +// } +// +// impl Match { +// fn empty(leftover: Bvec) -> Self { +// Match { succeeded: true, match_ends: 0, leftover } +// } +// +// fn failed(leftover: Bvec) -> Self { +// Match { succeeded: false, match_ends: 0, leftover } +// } +// } +// +// impl Eq for Match { +// fn eq(self, other: Self) -> bool { +// (self.succeeded == other.succeeded) & +// (self.match_ends == other.match_ends) & +// (self.leftover == other.leftover) +// } +// } +// +// // TODO: load match into str and assert that it's the correct length +// // impl From for str +// +// trait Regex { +// // Perform a match without backtracking +// fn match(self, input: Bvec) -> Match; +// } +// +// // Empty +// impl Regex for () { +// fn match(_self: Self, input: Bvec) -> Match { +// Match::empty(input) +// } +// } +// +// // Exact +// impl Regex for str { +// fn match(self, input: Bvec) -> Match { +// let mut leftover = input; +// let mut matches_input = true; +// let self_as_bytes = self.as_bytes(); +// for c in self_as_bytes { +// if leftover.len != 0 { +// let (first_elem, popped_slice) = leftover.pop_front(); +// leftover = popped_slice; +// matches_input &= first_elem == c; +// } else { +// matches_input = false; +// } +// } +// if matches_input { +// Match { +// succeeded: true, +// match_ends: self_as_bytes.len(), +// leftover, +// } +// } else { +// Match { +// succeeded: false, +// match_ends: 0, +// leftover: input, +// } +// } +// } +// } +// +// // And +// impl Regex for (T, U) where T: Regex, U: Regex { +// fn match(self, input: Bvec) -> Match { +// let lhs_result = self.0.match(input); +// if lhs_result.succeeded { +// let rhs_result = self.1.match(lhs_result.leftover); +// if rhs_result.succeeded { +// Match { +// succeeded: true, +// match_ends: lhs_result.match_ends + rhs_result.match_ends, +// leftover: rhs_result.leftover, +// } +// } else { +// Match { +// succeeded: false, +// match_ends: 0, +// leftover: input, +// } +// } +// } else { +// Match { +// succeeded: false, +// match_ends: 0, +// leftover: input, +// } +// } +// } +// } +// +// // N T's: (T, (T, (T, T))) +// struct Repeated { +// inner: T, +// } +// +// impl Regex for Repeated where T: Regex { +// fn match(self, input: Bvec) -> Match { +// let mut result = Match::empty(input); +// for _ in 0..M { +// if result.succeeded { +// let next_result = self.inner.match(result.leftover); +// result = Match { +// succeeded: next_result.succeeded, +// match_ends: result.match_ends + next_result.match_ends, +// leftover: next_result.leftover, +// }; +// } +// } +// result +// } +// } +// +// struct Or { +// lhs: T, +// rhs: U, +// } +// +// impl Regex for Or where T: Regex, U: Regex { +// fn match(self, input: Bvec) -> Match { +// let lhs_result = self.lhs.match(input); +// if lhs_result.succeeded { +// lhs_result +// } else { +// self.rhs.match(input) +// } +// } +// } +// +// struct Question { +// inner: T, +// } +// +// impl Regex for Question where T: Regex { +// fn match(self, input: Bvec) -> Match { +// Or { +// lhs: self.inner, +// rhs: (), +// }.match(input) +// } +// } +// +// // 0 <= num_matches <= N +// struct Star { +// inner: T, +// } +// +// impl Regex for Star where T: Regex { +// fn match(self, input: Bvec) -> Match { +// let regex: Repeated<_, M> = Repeated { +// inner: Question { inner: self.inner }, +// }; +// regex.match(input) +// } +// } +// +// // 0 < num_matches <= N +// struct Plus { +// inner: T, +// } +// +// impl Regex for Plus where T: Regex { +// fn match(self, input: Bvec) -> Match { +// std::static_assert(M_PRED + 1 == M, "M - 1 != M_PRED"); +// let star: Star = Star { inner: self.inner }; +// ( +// self.inner, +// star +// ).match(input) +// } +// } +// +// // Repeated is to (,) as AnyOf is to Or +// struct AnyOf { +// inner: [T; N], +// } +// +// impl Regex for AnyOf where T: Regex { +// fn match(self, input: Bvec) -> Match { +// let mut result = Match::failed(input); +// for i in 0..M { +// if !result.succeeded { +// result = self.inner[i].match(result.leftover); +// } +// } +// result +// } +// } +// +// fn reverse_array(input: [T; N]) -> [T; N] { +// let mut output = [std::unsafe::zeroed(); N]; +// for i in 0..N { +// output[i] = input[N - (i + 1)]; +// } +// output +// } +// +// fn main() { +// assert_eq(reverse_array([1, 2, 3, 4]), [4, 3, 2, 1]); +// +// let mut xs: Bvec = Bvec::empty(); +// +// xs = xs.push(0); +// assert_eq(xs, Bvec { inner: [0, 0, 0], offset: 0, len: 1 }); +// +// xs = xs.push(1); +// assert_eq(xs, Bvec { inner: [0, 1, 0], offset: 0, len: 2 }); +// +// xs = xs.push(2); +// assert_eq(xs, Bvec { inner: [0, 1, 2], offset: 0, len: 3 }); +// +// xs = xs.push(3); +// assert_eq(xs, Bvec { inner: [0, 1, 2], offset: 0, len: 3 }); +// +// let ys = Bvec::new([0, 1, 2]); +// assert_eq(xs, ys); +// +// // test that pop_front gives all contents, in order, +// // followed by std::unsafe::zeroed() +// println(xs); +// let (x, new_xs) = xs.pop_front(); +// assert_eq(x, 0); +// +// xs = new_xs; +// println(xs); +// let (x, new_xs) = xs.pop_front(); +// assert_eq(x, 1); +// +// xs = new_xs; +// println(xs); +// let (x, new_xs) = xs.pop_front(); +// assert_eq(x, 2); +// +// xs = new_xs; +// println(xs); +// if xs.len != 0 { +// let (x, _new_xs) = xs.pop_front(); +// assert_eq(x, std::unsafe::zeroed()); +// } +// +// assert_eq(new_xs, Bvec { inner: [0, 1, 2], offset: 3, len: 0 }); +// +// // gr(a|e)y +// let graey_regex = ("gr", (Or { lhs: "a", rhs: "e" }, "y")); +// +// let result = graey_regex.match(Bvec::new("gray".as_bytes())); +// println(result); +// assert(result.succeeded); +// assert_eq(result.match_ends, 4); +// assert_eq(result.leftover.len, 0); +// +// let result = graey_regex.match(Bvec::new("grey".as_bytes())); +// println(result); +// assert(result.succeeded); +// assert_eq(result.match_ends, 4); +// assert_eq(result.leftover.len, 0); +// +// // colou?r +// let colour_regex = ("colo", (Question { inner: "u" }, "r")); +// +// let result = colour_regex.match(Bvec::new("color".as_bytes())); +// println(result); +// assert(result.succeeded); +// assert_eq(result.match_ends, 5); +// assert_eq(result.leftover.len, 0); +// +// let result = colour_regex.match(Bvec::new("colour".as_bytes())); +// println(result); +// assert(result.succeeded); +// assert_eq(result.match_ends, 6); +// assert_eq(result.leftover.len, 0); +// +// // parse the empty string three times +// // EMPTY{3} +// let three_empties_regex: Repeated<(), 3> = Repeated { inner: () }; +// +// let result = three_empties_regex.match(Bvec::new("111".as_bytes())); +// println(result); +// assert(result.succeeded); +// assert_eq(result.match_ends, 0); +// assert_eq(result.leftover.len, 3); +// +// // 1{0} +// let zero_ones_regex: Repeated, 0> = Repeated { inner: "1" }; +// +// let result = zero_ones_regex.match(Bvec::new("111".as_bytes())); +// println(result); +// assert(result.succeeded); +// assert_eq(result.match_ends, 0); +// assert_eq(result.leftover.len, 3); +// +// // 1{1} +// let one_ones_regex: Repeated, 1> = Repeated { inner: "1" }; +// +// let result = one_ones_regex.match(Bvec::new("111".as_bytes())); +// println(result); +// assert(result.succeeded); +// assert_eq(result.match_ends, 1); +// assert_eq(result.leftover.len, 2); +// +// // 1{2} +// let two_ones_regex: Repeated, 2> = Repeated { inner: "1" }; +// +// let result = two_ones_regex.match(Bvec::new("111".as_bytes())); +// println(result); +// assert(result.succeeded); +// assert_eq(result.match_ends, 2); +// assert_eq(result.leftover.len, 1); +// +// // 1{3} +// let three_ones_regex: Repeated, 3> = Repeated { inner: "1" }; +// +// let result = three_ones_regex.match(Bvec::new("1111".as_bytes())); +// println(result); +// assert(result.succeeded); +// assert_eq(result.match_ends, 3); +// assert_eq(result.leftover.len, 1); +// +// // 1* +// let ones_regex: Star, 5> = Star { inner: "1" }; +// +// let result = ones_regex.match(Bvec::new("11000".as_bytes())); +// println(result); +// assert(result.succeeded); +// assert_eq(result.match_ends, 2); +// assert_eq(result.leftover.len, 3); +// +// let result = ones_regex.match(Bvec::new("11".as_bytes())); +// println(result); +// assert(result.succeeded); +// assert_eq(result.match_ends, 2); +// assert_eq(result.leftover.len, 0); +// +// let result = ones_regex.match(Bvec::new("111111".as_bytes())); +// println(result); +// assert(result.succeeded); +// assert_eq(result.match_ends, 5); +// assert_eq(result.leftover.len, 1); +// +// // 1+ +// let nonempty_ones_regex: Plus, 5, 4> = Plus { inner: "1" }; +// +// let result = nonempty_ones_regex.match(Bvec::new("111111".as_bytes())); +// println(result); +// assert(result.succeeded); +// assert_eq(result.match_ends, 5); +// assert_eq(result.leftover.len, 1); +// +// // 2^n-1 in binary: 1+0 +// let pred_pow_two_regex = (nonempty_ones_regex, "0"); +// +// let result = pred_pow_two_regex.match(Bvec::new("1110".as_bytes())); +// println(result); +// assert(result.succeeded); +// assert_eq(result.match_ends, 4); +// assert_eq(result.leftover.len, 0); +// +// // (0|1)* +// let binary_regex: Star, str<1>>, 5> = Star { inner: Or { lhs: "0", rhs: "1" } }; +// +// let result = binary_regex.match(Bvec::new("110100".as_bytes())); +// println(result); +// assert(result.succeeded); +// assert_eq(result.match_ends, 5); +// assert_eq(result.leftover.len, 1); +// +// // even numbers in binary: 1(0|1)*0 +// let even_binary_regex = ("1", (binary_regex, "0")); +// +// let result = even_binary_regex.match(Bvec::new("1111110".as_bytes())); +// println(result); +// assert(result.succeeded); +// assert_eq(result.match_ends, 7); +// assert_eq(result.leftover.len, 0); +// +// // digit: \d+ +// // [0-9] +// let digit_regex = AnyOf { +// inner: [ +// "0", +// "1", +// "2", +// "3", +// "4", +// "5", +// "6", +// "7", +// "8", +// "9" +// ] +// }; +// +// let result = digit_regex.match(Bvec::new("157196345823795".as_bytes())); +// println(result); +// assert(result.succeeded); +// assert_eq(result.match_ends, 1); +// assert_eq(result.leftover.len, 14); +// +// let result = digit_regex.match(Bvec::new("hi".as_bytes())); +// println(result); +// assert(!result.succeeded); +// assert_eq(result.match_ends, 0); +// assert_eq(result.leftover.len, 2); +// +// // digits: \d+ +// // [0-9]+ +// let digits_regex: Plus, 10>, 32, 31> = Plus { inner: digit_regex }; +// +// let result = digits_regex.match(Bvec::new("123456789012345".as_bytes())); +// println(result); +// assert(result.succeeded); +// assert_eq(result.match_ends, 15); +// assert_eq(result.leftover.len, 0); +// +// let result = digits_regex.match(Bvec::new("123456789012345 then words".as_bytes())); +// println(result); +// assert(result.succeeded); +// assert_eq(result.match_ends, 15); +// assert_eq(result.leftover.len, 11); +// +// // multiples of 10 +// // apply to a reversed input string (because there isn't backtracking) +// // 0\d+ +// let backwards_mult_of_10_regex = ("0", digits_regex); +// +// let result = backwards_mult_of_10_regex.match(Bvec::new(reverse_array("1230".as_bytes()))); +// println(result); +// assert(result.succeeded); +// assert_eq(result.match_ends, 4); +// assert_eq(result.leftover.len, 0); +// +// let ten_pow_16: str<17> = "10000000000000000"; +// let result = backwards_mult_of_10_regex.match(Bvec::new(reverse_array(ten_pow_16.as_bytes()))); +// println(result); +// assert(result.succeeded); +// assert_eq(result.match_ends, 17); +// assert_eq(result.leftover.len, 0); +// // adapted URL parser: (https?:\/\/)?([\da-c.\-]+)\.([a-c.]+)([\/\w \.\-]*)*\/? +// } + diff --git a/noir/noir-repo/test_programs/execution_success/verify_honk_proof/Prover.toml b/noir/noir-repo/test_programs/execution_success/verify_honk_proof/Prover.toml deleted file mode 100644 index fc5e6002dbfd..000000000000 --- a/noir/noir-repo/test_programs/execution_success/verify_honk_proof/Prover.toml +++ /dev/null @@ -1,4 +0,0 @@ -key_hash = "0x096129b1c6e108252fc5c829c4cc9b7e8f0d1fd9f29c2532b563d6396645e08f" -proof = ["0x0000000000000000000000000000000000000000000000000000000000000010","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000001","0x00000000000000000000000000000079ea57b3d7247e1b84fc1ab449de746345","0x000000000000000000000000000000000023fb17d477c91e0fb057233a66ef2a","0x000000000000000000000000000000146353d3faf24455819947aa0a25868174","0x00000000000000000000000000000000000093b1c637419c9f016bb0261cdfc6","0x000000000000000000000000000000325b128a84544d31fa1c577232c742b574","0x00000000000000000000000000000000002b3db93a2fca4c31308471d4f55fa2","0x00000000000000000000000000000054d9d87932eee6280c37d802ec8d47ca02","0x000000000000000000000000000000000000397167bb1e36d061487e93e4d97e","0x000000000000000000000000000000143b0960a1b9f19a44ad1cf2b7059832d6","0x0000000000000000000000000000000000158446576b2d43f78b48799ff7e760","0x000000000000000000000000000000cf640bad8ccc1890d738ab917d6caa957e","0x00000000000000000000000000000000001d6fd185d8771b864545438c6a1d68","0x000000000000000000000000000000a33cd928d0d4c7f244824b63b15f4c5423","0x00000000000000000000000000000000000433ccd872d2a302104048474e0bea","0x000000000000000000000000000000eaf7d13e5e9706e1b8a9343bd493a060af","0x00000000000000000000000000000000001a062842ba351b311ae52693f5114e","0x000000000000000000000000000000a33cd928d0d4c7f244824b63b15f4c5423","0x00000000000000000000000000000000000433ccd872d2a302104048474e0bea","0x000000000000000000000000000000eaf7d13e5e9706e1b8a9343bd493a060af","0x00000000000000000000000000000000001a062842ba351b311ae52693f5114e","0x000000000000000000000000000000160d90f214f524875c01cb9cf0f2d272b9","0x000000000000000000000000000000000015d5f906c4fe06017b0f9824434d09","0x0000000000000000000000000000007fc2db3cfe49b7666aeafd8cf6973c9fed","0x00000000000000000000000000000000000c7fc1e545a8ee19a7bc6ad6f2ea47","0x000000000000000000000000000000fc3c9df244afbba117cd897a4c929edb84","0x0000000000000000000000000000000000216f0c3a2e5e8683d9717ad40eadde","0x000000000000000000000000000000c381b45048aa5163e0129e4031e29058cb","0x00000000000000000000000000000000002f11022de88492201c28f87582684d","0x000000000000000000000000000000c98462e08c423124d92a41110c378db160","0x00000000000000000000000000000000000106dafb059575ec9b926aa90edfef","0x0000000000000000000000000000007d0cc0465628f6b0f3918aa9d7cf33ff38","0x00000000000000000000000000000000002cff01344fc7c6f81399b7ae660ad4","0x07eff01a06f356d255515e5f27cb51e8873277beb3f986c215181b475df4dd8e","0x28745e58da3e495762fee75759b60674a1017089c5bfe9cf2ec9da4c920b2273","0x1d5b7b751e509ac70caa253595be4523d1963cf7bd6751d2c146e2fc10d00196","0x26fe27f73b55be7d49b4c1c11f085f47f6a241ba5ea0d48b47964e7adf5e8e5a","0x239206c519de2576a554a70f387cdf5d525a599541be2ecd9260e52d572ae07c","0x04e35b29a57c31c89c72a6387bf89613b64c2827e0c2402b8dfb2c1cfea0c878","0x1e8398c5dd85d15154110c2480f2249030aecd7595242ae86bbdf7b2730ca070","0x2ba9986a038e85a4dd96badffb6a44950c37360fd6e8ec6c4b9647377bcb45f5","0x27ca7a06ceea23d329c52dac8c0715440238d37362ab0fb1e26544b18bb79a3b","0x23b768d51fa7922f8292309455adc5730b8964818c328a42dff60a57add32f50","0x24e8634d5381475abe5821450299d9d8d725a472610fe265e44c8360c4708c95","0x0cdbb73fe5c035427113e66a15b8c41e963ae215e491d855a3ce8c3ab200fb3b","0x0e8acd2ed6af85e4f71b96c51d2a57bceea5c50fb405b7888359f0635b415da7","0x2914cc0244acf5ac6d674d3c96d543ee2f3e95d0248ee66daf0cf2932107e300","0x00ff0384250d2c2e59cd1cf58cebd1d3b1ebab7989eb2eaa6b6bbce69f9e8ba0","0x253f7a5007d47d3d858fc0e172c971cb54f97cea5c63ca60efe61589913b2499","0x2d34704fc711dabe0f716dbebc5dfd0eaa5667006847d333dadc86e15bf672c0","0x0bdd67ff40c61242e46a234c0d438663a9ccae833d1e0b22833ffe41e2828bb4","0x04c7ba2edccfb340eba0c94a7a5d5d53b010939621053c7c0fd27f2ba4b08273","0x0c3f68e6de8042a10098596e80ea79882b37d22c6a6adaa64f5c668739932fa5","0x14bcb10845b45cb8fdcac13e41ad755f6d966756ee2f3d4ed8a5791d4b345ea8","0x0dd68c1e3d122d4d4b28a8ac7e6a592146afe70e3852906c27ccc7e345f745e2","0x06816aff04192007cb2b3ed2cee4b22e044ced0199b136942348ced61990c1a7","0x3013f13664687bc3cbe26314f17cf309486ef71ffb55ce2589075554fc31ee69","0x1941a602d47af0e52f06a272998b6a59313f316508c0778714a36d7bb4f5669b","0x268750f15f2ac995d1d59859b7d636ae814e751b84318218ac1ce75a14b00e18","0x2aaff14fd98aa13ffdf34e3f689e16b2e8cb7695db9a014dd270b021968e3bb2","0x090087ad0d688396823bbd90a8770c1101e5907efd1c4fbafff8a1e9f2f84d89","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x152deae3a77021b0201a74d98b30d842baea62c0d2531d69d5e866e59f48e052","0x084acb08dc53880864266b6dea02ec7a85ffab2ab590ba9a4adb32ad2c8ffe53","0x1b8ab1a2e47a839fdbf19d2cbea2abe79c57722270123cf96289a11e233cd175","0x03493f800f9abbe4e405f0f637f41f22dcc10e44e836a09115ed5821cd5856e6","0x24c358e686e47c512bbec4a1b9ac562c251e004ea142df44ea3b18cf214baa47","0x18296076ac89be1c4c24a04553be7bd07bba5a41d1c33de2bec14cfd1262ab9f","0x0e30341606dc2577a451251241394b3871e9db0e1758d250d36348bcbb8b6fdb","0x15f846978484540ac3c35eee38ccd980f01e8bda6050a645c4abca6f22b24619","0x2735dd2b603cde2937bf842002e8704ef1e3883d2d0a6e999dd7015496c10302","0x23c47d9891d04bdb88ca239119e423afdc6d2bd45fb92f5f19b8b0a9583fc863","0x1ce47f9088eecc7268d4558aa02a4902282bccaacbe882917cc57969af2236d0","0x2b5a6f937fcc921cced568de248e19fd3801e688505ee44af6499e14885c9879","0x2ae2f654890e7018bae8308b5a97230cdcd3b63b24334a05dc4fdc4107cff73d","0x06a87313997c2a5318a8ce0f75e26b9c4a2a83bd9c3578f10d1c1f3bfded8f29","0x0afe95fddb76f390d58e15b7e647e9ed083a66aa7829a18963125d865b64ef7f","0x1ff7ecaf04f4e8a9d57f79c85dd963099f6005f542df7c20505af69061473114","0x26ca489f39024294da78a601feda0a17c40d46e2c7d0787b47dc0afaf027a8c8","0x2da37034033c950b2f85c32be2b0f1102dae5ec01e13681ffc9a9a3033469a8d","0x22c35dc92f5bf1cb569ad756b45208ffa8a85d825ebacf8e7354e9162651d1fa","0x0e443f72c90fec92786098f7ec90cea01f6202db6998b34dbb1e7b0293f4bebd","0x049684508bb0af0f27bcaaf96aa53eac25a425e159eb33e031db157d63c22fb9","0x20d990716bfec57f52f603d50d0d81c4c851bfc231894eb573fa54f2ac70c9dd","0x1fd19e900621d01488be88d4a6d95c2583c19c6d1d49e8cd139bce76051b71bc","0x1679a31a104b20b301737b9214f12a0707727bd4510d5a53e5bec1321816cdfa","0x27b3d8000581372f35039477c28a268065b3717dbd9337c06a82162781e0d131","0x23b79b53bdb698ef8c7c01afaf3350deb78b5e841e09b13b6ef86fc68f97bcab","0x1d4abc42698589c40b05e187c12af268fffe64010756a8d08ea817105305a770","0x0f744ca06905efa1f604f387284979be483c00ee9298134e7337bd8bb4a88933","0x0be6790122704c6ed4c37fef0b524b413e63b88c2dadbe67a5ba039cf11cc628","0x19fa34479d41d734a17619048627633807d706b2b1035a326efada8f3e8eb183","0x1b208f5cc663a9560e8685c351cb17b8e5862eb16f1407cf654e8ffae331aa9b","0x1b140725b61fe2e1057d72525aecf1d319ecb509a392f68e4058d13cea209993","0x1b140725b61fe2e1057d72525aecf1d319ecb509a392f68e4058d13cea209993","0x0d1703eac9b276094d72a50322dd82033960a6f1f6176aa2b029e39a1375bb51","0x09ba2a48cfdcc27f6b6db2ca277c5016d4f5a177e65eec6f68e30a67d4b06c1b","0x0e243bf8b2d6b8e46ed75902fe60781b2b41cf45287f367df850ce50de7f86af","0x1be244289270e4c0dc8517edfe335954fa7b56c3bf6fe06bc2d268f7db7a68ee","0x116ef1bfcfbca0612c92872aa3d07d32cb0b9716b1ba735846888a56e03c6207","0x0de8a7471ceb058680d2e0afa73e3dd843b527db1c16ebfaf8612447ffbee858","0x16911fee4120f72d81b0dfb0eeeb7380611119ee990daec5669b711cb35e2756","0x1c278b26a16e1ee7e21a71b67a31cb0b9907dae80776aa1dc7094ea5b4e2c34e","0x0f5c67db668b1f1880c51f805ec3d40aa27d34b4c8833f755d4466c285264399","0x000000000000000000000000000000dc2546d68fbe5a4913dde8ed73f673bc5f","0x00000000000000000000000000000000001310657525d78319e5b15c92398dcf","0x0000000000000000000000000000000fde9a035776897ed560b4d9ae338b5f85","0x00000000000000000000000000000000000f84fecfb3ea28426f114d9de93cb3","0x000000000000000000000000000000d3ea685110f3ff69bf91cc32cc5170b62e","0x0000000000000000000000000000000000179205f5ebaf3eaf5d50be462f830d","0x00000000000000000000000000000024a7284c15d725d62b8f5c1090b08b58b7","0x00000000000000000000000000000000002b6fdb2139f7b9443cbd82e6423486","0x00000000000000000000000000000006489f49eed3370ee31c80590eed2d0c3a","0x000000000000000000000000000000000010c11c3a122e00a12e0cf7a58d81ae","0x000000000000000000000000000000eb2d1eef7e7c7c0c054859600d264176e9","0x000000000000000000000000000000000028ac3239a0917c7c3761e11fbf9541","0x0000000000000000000000000000006ecbe6a2ccf0c9e1b743a84e1540796b81","0x0000000000000000000000000000000000098a99a81cbc111660301a03f77d96","0x000000000000000000000000000000c4f256019891f39b00b1b00428b3a154a5","0x00000000000000000000000000000000001bc2f83790ff1d3086273e4560135c","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x000000000000000000000000000000868795ebcbf38bffa96f455a314c7b9310","0x00000000000000000000000000000000002e43e0a550d7cce874e869ed0ef545","0x0000000000000000000000000000001e5a780edfd01526758b69bfaf25803f67","0x00000000000000000000000000000000000f0991f4b5dc348354f019ecc66502","0x000000000000000000000000000000cb917b7819afd60fc86ea477594ffca008","0x000000000000000000000000000000000002beaa7c144fc6620870e72ee8064c","0x000000000000000000000000000000b7f4dfed23506dadd1726a896e226d7a34","0x00000000000000000000000000000000001bb28f2fcfb40843aa5f5e38d689e1"] -public_inputs = ["0x0000000000000000000000000000000000000000000000000000000000000003"] -verification_key = ["0x0000000000000000000000000000000000000000000000000000000000000010","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000008c068dccb0e55d4b56c7c32ebfafeb5b02","0x0000000000000000000000000000000000266c985199590a284abf77ba01c36e","0x00000000000000000000000000000044fb25551548bb4059099673aed8646b35","0x000000000000000000000000000000000023ab8c745db114ee56b97a8aa27934","0x000000000000000000000000000000a56563b42599f0e4ad5455a8fd988b9ff3","0x00000000000000000000000000000000000a94e3640063f3f4758fcfe457d298","0x0000000000000000000000000000008a51861ca043ceae044d6ab4f136331514","0x00000000000000000000000000000000001812e50744ac8ed3cc4a9594a701cc","0x000000000000000000000000000000b911c8cf179747b410c1cb9fd8a8bde095","0x00000000000000000000000000000000001826edb9faf624498fe82f5a61008d","0x000000000000000000000000000000ed158ea534a9c72ec9e614906fd7adff9a","0x000000000000000000000000000000000017cb9637e464dc2647b9b8688c5fa0","0x0000000000000000000000000000004b5064dd55e5ec8cd9bdd01c0e22eb7122","0x00000000000000000000000000000000002c7cff0caa8ba3fec7523dcbc934a8","0x000000000000000000000000000000f268df76bf0d78739ded43daba9c339499","0x00000000000000000000000000000000002e11974b75c78b276ae16219b99dc9","0x000000000000000000000000000000cfc293980c0ecf813f4f1436ff140740c3","0x000000000000000000000000000000000016ff2972a7eedf8ff27f494904fa47","0x00000000000000000000000000000085a92cc2b6efec726ea10710b20776ee70","0x0000000000000000000000000000000000278709e98b64a3553dc3e6e514e7ff","0x0000000000000000000000000000004391d81714b7d7ad40642b9308d02258b4","0x0000000000000000000000000000000000207710f769c857fbe624a2333097b2","0x0000000000000000000000000000002f767ee4790206ca5c193b742aa672d6d8","0x00000000000000000000000000000000001044cdbbd63806d10426ca4cb77cbc","0x000000000000000000000000000000314be7aecd2a710b8966befe7c0b08f574","0x00000000000000000000000000000000000558190b4fa7d726895b6d7d9c0bef","0x000000000000000000000000000000d64f3a11faf61b8776b0e778ab7a16c09c","0x00000000000000000000000000000000000d1c3d5e8fe0193b17834424ce605d","0x000000000000000000000000000000d8019ded441b9e454eb4045069cefee487","0x00000000000000000000000000000000002c066d46d386975a57df073e19403b","0x0000000000000000000000000000006bf779063abc501d4102fbfc99d4227c16","0x00000000000000000000000000000000001bbf8b9e8c4b2184984b994c744d21","0x0000000000000000000000000000003896ea793e6b3f6a14218d476534109610","0x00000000000000000000000000000000000e84090add56f2500ab518c655cae6","0x00000000000000000000000000000065df446fdddba972f3c4414ad3c901f4f9","0x00000000000000000000000000000000002b78a584bd6ae88cf4ec7c65c90e0b","0x00000000000000000000000000000094e611b5d59a27773f744710b476fbd30f","0x00000000000000000000000000000000001bd6129f9646aa21af0d77e7b1cc97","0x000000000000000000000000000000139a9d1593d56e65e710b2f344756b721e","0x00000000000000000000000000000000002f8d492d76a22b6834f0b88e2d4096","0x00000000000000000000000000000026c814cd7c5e1ba2094969bb1d74f1c66b","0x000000000000000000000000000000000013129f0714c3307644809495e01504","0x0000000000000000000000000000007d4549a4df958fe4825e7cb590563154ab","0x00000000000000000000000000000000000e7d5873232b1bdd0ce181513b47d1","0x000000000000000000000000000000a54541a8f32c0d9f8645edf17aac8fa230","0x00000000000000000000000000000000001e0677756494ded8010e8ef02518b2","0x0000000000000000000000000000008b101700e2d4f9116b01bfaaf3c458a423","0x0000000000000000000000000000000000021e43a3c385eba62bcc47aad7b9ea","0x00000000000000000000000000000099559d1c1ed6758494d18b9890bb5e3f97","0x00000000000000000000000000000000002e68b3c679543d2933bf9f7f77d422","0x000000000000000000000000000000c842dceb89f5cf4c130810f4802014a67f","0x00000000000000000000000000000000000d647daa6d2a8ac14f2da194b3a27e","0x000000000000000000000000000000af641be24f11d735581ad2e14787470194","0x00000000000000000000000000000000001e90f381ece8401026212fdbb26199","0x000000000000000000000000000000f601a4b716e755b0cf516d07e403265e27","0x00000000000000000000000000000000002d49d628876caa6993afe9fc30a764","0x0000000000000000000000000000008e9de4c6ce2e85105ec90ab63303b61502","0x00000000000000000000000000000000001b063563a7858b064132573e0aca86","0x00000000000000000000000000000021c200c8468139aa32fcf13fd1d8570828","0x0000000000000000000000000000000000023a4e744c62548c3b32986b3bc73a","0x0000000000000000000000000000000af941f79a4d93c6e9aad19c6049e1fa53","0x000000000000000000000000000000000003db2201f4b1b9a4d3646331e1f8e1","0x00000000000000000000000000000005d91fe16bd2b8dd3ce8b7d70ce6222b4f","0x0000000000000000000000000000000000102db0f3fd668e06f49d133d1bf994","0x0000000000000000000000000000009459915944c39a12b978a433efb6517d0f","0x00000000000000000000000000000000000b1c9fa9f4ce17e53f3acd13be4078","0x0000000000000000000000000000007c8d45be92476f8867dca4078fb7b6b2f8","0x00000000000000000000000000000000001f21afb9b7ccd5c404f0115253d2a6","0x0000000000000000000000000000004d78a34b40208c31be4fb8b39d23f1d1de","0x00000000000000000000000000000000000f3090488b19df76c4358537728d9a","0x00000000000000000000000000000060b0272756debcae50a25a3ee7d7095ea9","0x00000000000000000000000000000000002e84bca0d93b098853cca06147ec94","0x000000000000000000000000000000a0875603e0a017ce12ff79764af43e7421","0x0000000000000000000000000000000000245798a7b19502ba14b46eb68dc771","0x00000000000000000000000000000089b25e854077925674d0645ed1e784c929","0x000000000000000000000000000000000008b8347d14433adba1d9e9406eb1db","0x000000000000000000000000000000d0d3258758dfa9bae9e415f6d48d990e16","0x0000000000000000000000000000000000224948ddbcddb1e360efa2ac511aac","0x000000000000000000000000000000f6a101330e9f928dc80a3d3b9afefb373a","0x00000000000000000000000000000000001011627c159ab9f3ff0a0416a01df6","0x0000000000000000000000000000002ec420ad50087360c152c131400547bcc6","0x000000000000000000000000000000000018dab63316305864682bfe7b586e91","0x0000000000000000000000000000004bd9f352c132c7ae6bed5ea997693e6300","0x00000000000000000000000000000000001edb4d30542aa0ac4fe8eb31fc2ce0","0x0000000000000000000000000000008bcf42c24591e90cf41fc687829fe0b0aa","0x000000000000000000000000000000000027a49cd522a4fbbdfc8846331514de","0x000000000000000000000000000000bdfbf1d964fcfb887c3631ef202797fc2f","0x00000000000000000000000000000000001432caafa62e791082fd900fcb34a1","0x0000000000000000000000000000006f99a40f79f14ed78a291d53d0425ddc9d","0x000000000000000000000000000000000007ea92c2de0345ded1d25b237f0845","0x000000000000000000000000000000bc1328fa2c343da93cb98486d414f0a40a","0x0000000000000000000000000000000000255aeaa6894472e3cb6b0a790cf290","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000008775499e69e8bd2c39af33bd5fa0b4079a","0x0000000000000000000000000000000000024236bda126650fb5228cf424a087","0x000000000000000000000000000000b0eb1a867b06854066589b967455259b32","0x0000000000000000000000000000000000233cda9292be02cfa2da9d0fc7b0ea"] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_success/verify_honk_proof/src/main.nr b/noir/noir-repo/test_programs/execution_success/verify_honk_proof/src/main.nr deleted file mode 100644 index a18403eba718..000000000000 --- a/noir/noir-repo/test_programs/execution_success/verify_honk_proof/src/main.nr +++ /dev/null @@ -1,21 +0,0 @@ - -// This circuit aggregates a single Honk proof from `assert_statement_recursive`. -global SIZE_OF_PROOF_IF_LOGN_IS_28 : u32 = 393; -fn main( - verification_key: [Field; 103], - // This is the proof without public inputs attached. - // - // This means: the size of this does not change with the number of public inputs. - proof: [Field; SIZE_OF_PROOF_IF_LOGN_IS_28], - public_inputs: pub [Field; 1], - // This is currently not public. It is fine given that the vk is a part of the circuit definition. - // I believe we want to eventually make it public too though. - key_hash: Field -) { - std::verify_proof( - verification_key.as_slice(), - proof.as_slice(), - public_inputs.as_slice(), - key_hash - ); -} diff --git a/noir/noir-repo/tooling/debugger/src/context.rs b/noir/noir-repo/tooling/debugger/src/context.rs index 7cdbe5156493..6ec1aff83258 100644 --- a/noir/noir-repo/tooling/debugger/src/context.rs +++ b/noir/noir-repo/tooling/debugger/src/context.rs @@ -1,6 +1,6 @@ use crate::foreign_calls::DebugForeignCallExecutor; use acvm::acir::brillig::BitSize; -use acvm::acir::circuit::brillig::BrilligBytecode; +use acvm::acir::circuit::brillig::{BrilligBytecode, BrilligFunctionId}; use acvm::acir::circuit::{Circuit, Opcode, OpcodeLocation}; use acvm::acir::native_types::{Witness, WitnessMap, WitnessStack}; use acvm::brillig_vm::MemoryValue; @@ -57,8 +57,25 @@ use std::collections::{hash_set::Iter, HashSet}; pub struct AddressMap { addresses: Vec>, - // virtual address of the last opcode of the program + /// Virtual address of the last opcode of the program last_valid_address: usize, + + /// Maps the "holes" in the `addresses` nodes to the Brillig function ID + /// associated with that address space. + brillig_addresses: Vec, +} + +/// Associates a BrilligFunctionId with the address space. +/// A BrilligFunctionId is found by checking whether an address is between +/// the `start_address` and `end_address` +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +struct BrilligAddressSpace { + /// The start of the Brillig call address space + start_address: usize, + /// The end of the Brillig address space + end_address: usize, + /// The Brillig function id associated with this address space + brillig_function_id: BrilligFunctionId, } impl AddressMap { @@ -68,25 +85,35 @@ impl AddressMap { ) -> Self { let opcode_address_size = |opcode: &Opcode| { if let Opcode::BrilligCall { id, .. } = opcode { - unconstrained_functions[*id as usize].bytecode.len() + (unconstrained_functions[id.as_usize()].bytecode.len(), Some(*id)) } else { - 1 + (1, None) } }; let mut addresses = Vec::with_capacity(circuits.len()); let mut next_address = 0usize; + let mut brillig_addresses = Vec::new(); for circuit in circuits { let mut circuit_addresses = Vec::with_capacity(circuit.opcodes.len()); for opcode in &circuit.opcodes { circuit_addresses.push(next_address); - next_address += opcode_address_size(opcode); + let (address_size, brillig_function_id) = opcode_address_size(opcode); + if let Some(brillig_function_id) = brillig_function_id { + let brillig_address_space = BrilligAddressSpace { + start_address: next_address, + end_address: next_address + address_size, + brillig_function_id, + }; + brillig_addresses.push(brillig_address_space); + } + next_address += address_size; } addresses.push(circuit_addresses); } - Self { addresses, last_valid_address: next_address - 1 } + Self { addresses, last_valid_address: next_address - 1, brillig_addresses } } /// Returns the absolute address of the opcode at the given location. @@ -120,16 +147,26 @@ impl AddressMap { // We binary search among the selected `circuit_id`` list of opcodes // If Err(insert_index) this means that the given address // is a Brillig addresses that's contained in previous index ACIR opcode index - let opcode_location = match self.addresses[circuit_id].binary_search(&address) { - Ok(found_index) => OpcodeLocation::Acir(found_index), - Err(insert_index) => { - let acir_index = insert_index - 1; - let base_offset = self.addresses[circuit_id][acir_index]; - let brillig_index = address - base_offset; - OpcodeLocation::Brillig { acir_index, brillig_index } - } - }; - Some(DebugLocation { circuit_id: circuit_id as u32, opcode_location }) + let (opcode_location, brillig_function_id) = + match self.addresses[circuit_id].binary_search(&address) { + Ok(found_index) => (OpcodeLocation::Acir(found_index), None), + Err(insert_index) => { + let acir_index = insert_index - 1; + let base_offset = self.addresses[circuit_id][acir_index]; + let brillig_index = address - base_offset; + let brillig_function_id = self + .brillig_addresses + .iter() + .find(|brillig_address_space| { + address >= brillig_address_space.start_address + && address <= brillig_address_space.end_address + }) + .map(|brillig_address_space| brillig_address_space.brillig_function_id); + (OpcodeLocation::Brillig { acir_index, brillig_index }, brillig_function_id) + } + }; + + Some(DebugLocation { circuit_id: circuit_id as u32, opcode_location, brillig_function_id }) } } @@ -137,6 +174,7 @@ impl AddressMap { pub struct DebugLocation { pub circuit_id: u32, pub opcode_location: OpcodeLocation, + pub brillig_function_id: Option, } impl std::fmt::Display for DebugLocation { @@ -165,13 +203,13 @@ impl std::str::FromStr for DebugLocation { match parts.len() { 1 => OpcodeLocation::from_str(parts[0]).map_or(error, |opcode_location| { - Ok(DebugLocation { circuit_id: 0, opcode_location }) + Ok(DebugLocation { circuit_id: 0, opcode_location, brillig_function_id: None }) }), 2 => { let first_part = parts[0].parse().ok(); let second_part = OpcodeLocation::from_str(parts[1]).ok(); if let (Some(circuit_id), Some(opcode_location)) = (first_part, second_part) { - Ok(DebugLocation { circuit_id, opcode_location }) + Ok(DebugLocation { circuit_id, opcode_location, brillig_function_id: None }) } else { error } @@ -276,12 +314,24 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { if ip >= self.get_opcodes().len() { None } else { - let opcode_location = if let Some(ref solver) = self.brillig_solver { - OpcodeLocation::Brillig { acir_index: ip, brillig_index: solver.program_counter() } - } else { - OpcodeLocation::Acir(ip) - }; - Some(DebugLocation { circuit_id: self.current_circuit_id, opcode_location }) + let (opcode_location, brillig_function_id) = + if let Some(ref solver) = self.brillig_solver { + let function_id = solver.function_id; + ( + OpcodeLocation::Brillig { + acir_index: ip, + brillig_index: solver.program_counter(), + }, + Some(function_id), + ) + } else { + (OpcodeLocation::Acir(ip), None) + }; + Some(DebugLocation { + circuit_id: self.current_circuit_id, + brillig_function_id, + opcode_location, + }) } } @@ -293,6 +343,7 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { .map(|ExecutionFrame { circuit_id, acvm }| DebugLocation { circuit_id: *circuit_id, opcode_location: OpcodeLocation::Acir(acvm.instruction_pointer()), + brillig_function_id: None, }) .collect(); @@ -306,11 +357,13 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { acir_index: instruction_pointer, brillig_index: *program_counter, }, + brillig_function_id: Some(solver.function_id), })); } else if instruction_pointer < self.get_opcodes().len() { frames.push(DebugLocation { circuit_id, opcode_location: OpcodeLocation::Acir(instruction_pointer), + brillig_function_id: None, }); } frames @@ -388,15 +441,24 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { ) -> Vec { self.debug_artifact.debug_symbols[debug_location.circuit_id as usize] .opcode_location(&debug_location.opcode_location) - .map(|source_locations| { - source_locations - .into_iter() - .filter(|source_location| { - !self.is_source_location_in_debug_module(source_location) - }) - .collect() + .unwrap_or_else(|| { + if let Some(brillig_function_id) = debug_location.brillig_function_id { + let brillig_locations = self.debug_artifact.debug_symbols + [debug_location.circuit_id as usize] + .brillig_locations + .get(&brillig_function_id); + brillig_locations + .unwrap() + .get(&debug_location.opcode_location) + .cloned() + .unwrap_or_default() + } else { + vec![] + } }) - .unwrap_or_default() + .into_iter() + .filter(|source_location| !self.is_source_location_in_debug_module(source_location)) + .collect() } /// Returns the current call stack with expanded source locations. In @@ -431,7 +493,7 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { let opcode = &opcodes[*acir_index]; match opcode { Opcode::BrilligCall { id, .. } => { - let first_opcode = &self.unconstrained_functions[*id as usize].bytecode[0]; + let first_opcode = &self.unconstrained_functions[id.as_usize()].bytecode[0]; format!("BRILLIG {first_opcode:?}") } _ => format!("{opcode:?}"), @@ -439,7 +501,7 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { } OpcodeLocation::Brillig { acir_index, brillig_index } => match &opcodes[*acir_index] { Opcode::BrilligCall { id, .. } => { - let bytecode = &self.unconstrained_functions[*id as usize].bytecode; + let bytecode = &self.unconstrained_functions[id.as_usize()].bytecode; let opcode = &bytecode[*brillig_index]; format!(" | {opcode:?}") } @@ -630,6 +692,7 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { Some(DebugLocation { circuit_id, opcode_location: OpcodeLocation::Acir(acir_index), + .. }) => { matches!( self.get_opcodes_of_circuit(circuit_id)[acir_index], @@ -751,7 +814,7 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { if acir_index < opcodes.len() { match &opcodes[acir_index] { Opcode::BrilligCall { id, .. } => { - let bytecode = &self.unconstrained_functions[*id as usize].bytecode; + let bytecode = &self.unconstrained_functions[id.as_usize()].bytecode; brillig_index < bytecode.len() } _ => false, @@ -821,24 +884,22 @@ fn build_source_to_opcode_debug_mappings( let mut result: BTreeMap> = BTreeMap::new(); for (circuit_id, debug_symbols) in debug_artifact.debug_symbols.iter().enumerate() { - for (opcode_location, source_locations) in &debug_symbols.locations { - source_locations.iter().for_each(|source_location| { - let span = source_location.span; - let file_id = source_location.file; - let Some(file) = simple_files.get(&file_id) else { - return; - }; - let Ok(line_index) = file.line_index((), span.start() as usize) else { - return; - }; - let line_number = line_index + 1; + add_opcode_locations_map( + &debug_symbols.locations, + &mut result, + &simple_files, + circuit_id, + None, + ); - let debug_location = DebugLocation { - circuit_id: circuit_id as u32, - opcode_location: *opcode_location, - }; - result.entry(file_id).or_default().push((line_number, debug_location)); - }); + for (brillig_function_id, brillig_locations_map) in &debug_symbols.brillig_locations { + add_opcode_locations_map( + brillig_locations_map, + &mut result, + &simple_files, + circuit_id, + Some(*brillig_function_id), + ); } } result.iter_mut().for_each(|(_, file_locations)| file_locations.sort_by_key(|x| (x.0, x.1))); @@ -846,6 +907,35 @@ fn build_source_to_opcode_debug_mappings( result } +fn add_opcode_locations_map( + opcode_to_locations: &BTreeMap>, + source_to_locations: &mut BTreeMap>, + simple_files: &BTreeMap<&FileId, SimpleFile<&str, &str>>, + circuit_id: usize, + brillig_function_id: Option, +) { + for (opcode_location, source_locations) in opcode_to_locations { + source_locations.iter().for_each(|source_location| { + let span = source_location.span; + let file_id = source_location.file; + let Some(file) = simple_files.get(&file_id) else { + return; + }; + let Ok(line_index) = file.line_index((), span.start() as usize) else { + return; + }; + let line_number = line_index + 1; + + let debug_location = DebugLocation { + circuit_id: circuit_id as u32, + opcode_location: *opcode_location, + brillig_function_id, + }; + source_to_locations.entry(file_id).or_default().push((line_number, debug_location)); + }); + } +} + #[cfg(test)] mod tests { use super::*; @@ -855,7 +945,7 @@ mod tests { acir::{ brillig::IntegerBitSize, circuit::{ - brillig::{BrilligInputs, BrilligOutputs}, + brillig::{BrilligFunctionId, BrilligInputs, BrilligOutputs}, opcodes::{BlockId, BlockType}, }, native_types::Expression, @@ -896,7 +986,7 @@ mod tests { ], }; let opcodes = vec![Opcode::BrilligCall { - id: 0, + id: BrilligFunctionId(0), inputs: vec![BrilligInputs::Single(Expression { linear_combinations: vec![(fe_1, w_x)], ..Expression::default() @@ -928,7 +1018,11 @@ mod tests { assert_eq!( context.get_current_debug_location(), - Some(DebugLocation { circuit_id: 0, opcode_location: OpcodeLocation::Acir(0) }) + Some(DebugLocation { + circuit_id: 0, + opcode_location: OpcodeLocation::Acir(0), + brillig_function_id: None, + }) ); // Execute the first Brillig opcode (calldata copy) @@ -938,7 +1032,8 @@ mod tests { context.get_current_debug_location(), Some(DebugLocation { circuit_id: 0, - opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 1 } + opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 1 }, + brillig_function_id: Some(BrilligFunctionId(0)), }) ); @@ -949,7 +1044,8 @@ mod tests { context.get_current_debug_location(), Some(DebugLocation { circuit_id: 0, - opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 2 } + opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 2 }, + brillig_function_id: Some(BrilligFunctionId(0)), }) ); @@ -960,7 +1056,8 @@ mod tests { context.get_current_debug_location(), Some(DebugLocation { circuit_id: 0, - opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 2 } + opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 2 }, + brillig_function_id: Some(BrilligFunctionId(0)), }) ); @@ -971,7 +1068,8 @@ mod tests { context.get_current_debug_location(), Some(DebugLocation { circuit_id: 0, - opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 3 } + opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 3 }, + brillig_function_id: Some(BrilligFunctionId(0)), }) ); @@ -1009,7 +1107,7 @@ mod tests { let opcodes = vec![ // z = x + y Opcode::BrilligCall { - id: 0, + id: BrilligFunctionId(0), inputs: vec![ BrilligInputs::Single(Expression { linear_combinations: vec![(fe_1, w_x)], @@ -1056,6 +1154,7 @@ mod tests { let breakpoint_location = DebugLocation { circuit_id: 0, opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 1 }, + brillig_function_id: Some(BrilligFunctionId(0)), }; assert!(context.add_breakpoint(breakpoint_location)); @@ -1069,7 +1168,11 @@ mod tests { assert!(matches!(result, DebugCommandResult::Ok)); assert_eq!( context.get_current_debug_location(), - Some(DebugLocation { circuit_id: 0, opcode_location: OpcodeLocation::Acir(1) }) + Some(DebugLocation { + circuit_id: 0, + opcode_location: OpcodeLocation::Acir(1), + brillig_function_id: None + }) ); // last ACIR opcode @@ -1101,7 +1204,12 @@ mod tests { init: vec![], block_type: BlockType::Memory, }, - Opcode::BrilligCall { id: 0, inputs: vec![], outputs: vec![], predicate: None }, + Opcode::BrilligCall { + id: BrilligFunctionId(0), + inputs: vec![], + outputs: vec![], + predicate: None, + }, Opcode::Call { id: 1, inputs: vec![], outputs: vec![], predicate: None }, Opcode::AssertZero(Expression::default()), ], @@ -1109,7 +1217,12 @@ mod tests { }; let circuit_two = Circuit { opcodes: vec![ - Opcode::BrilligCall { id: 1, inputs: vec![], outputs: vec![], predicate: None }, + Opcode::BrilligCall { + id: BrilligFunctionId(1), + inputs: vec![], + outputs: vec![], + predicate: None, + }, Opcode::AssertZero(Expression::default()), ], ..Circuit::default() @@ -1134,24 +1247,51 @@ mod tests { assert_eq!( locations, vec![ - Some(DebugLocation { circuit_id: 0, opcode_location: OpcodeLocation::Acir(0) }), - Some(DebugLocation { circuit_id: 0, opcode_location: OpcodeLocation::Acir(1) }), Some(DebugLocation { circuit_id: 0, - opcode_location: OpcodeLocation::Brillig { acir_index: 1, brillig_index: 1 } + opcode_location: OpcodeLocation::Acir(0), + brillig_function_id: None + }), + Some(DebugLocation { + circuit_id: 0, + opcode_location: OpcodeLocation::Acir(1), + brillig_function_id: None + }), + Some(DebugLocation { + circuit_id: 0, + opcode_location: OpcodeLocation::Brillig { acir_index: 1, brillig_index: 1 }, + brillig_function_id: Some(BrilligFunctionId(0)), + }), + Some(DebugLocation { + circuit_id: 0, + opcode_location: OpcodeLocation::Acir(2), + brillig_function_id: None + }), + Some(DebugLocation { + circuit_id: 0, + opcode_location: OpcodeLocation::Acir(3), + brillig_function_id: None + }), + Some(DebugLocation { + circuit_id: 1, + opcode_location: OpcodeLocation::Acir(0), + brillig_function_id: None + }), + Some(DebugLocation { + circuit_id: 1, + opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 1 }, + brillig_function_id: Some(BrilligFunctionId(1)), }), - Some(DebugLocation { circuit_id: 0, opcode_location: OpcodeLocation::Acir(2) }), - Some(DebugLocation { circuit_id: 0, opcode_location: OpcodeLocation::Acir(3) }), - Some(DebugLocation { circuit_id: 1, opcode_location: OpcodeLocation::Acir(0) }), Some(DebugLocation { circuit_id: 1, - opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 1 } + opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 2 }, + brillig_function_id: Some(BrilligFunctionId(1)), }), Some(DebugLocation { circuit_id: 1, - opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 2 } + opcode_location: OpcodeLocation::Acir(1), + brillig_function_id: None }), - Some(DebugLocation { circuit_id: 1, opcode_location: OpcodeLocation::Acir(1) }), ] ); @@ -1170,14 +1310,16 @@ mod tests { 1, context.debug_location_to_address(&DebugLocation { circuit_id: 0, - opcode_location: OpcodeLocation::Brillig { acir_index: 1, brillig_index: 0 } + opcode_location: OpcodeLocation::Brillig { acir_index: 1, brillig_index: 0 }, + brillig_function_id: Some(BrilligFunctionId(0)), }) ); assert_eq!( 5, context.debug_location_to_address(&DebugLocation { circuit_id: 1, - opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 0 } + opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 0 }, + brillig_function_id: Some(BrilligFunctionId(1)), }) ); } diff --git a/noir/noir-repo/tooling/debugger/src/repl.rs b/noir/noir-repo/tooling/debugger/src/repl.rs index bd9b316331dd..1a7c2d6c7a8d 100644 --- a/noir/noir-repo/tooling/debugger/src/repl.rs +++ b/noir/noir-repo/tooling/debugger/src/repl.rs @@ -1,7 +1,7 @@ use crate::context::{DebugCommandResult, DebugContext, DebugLocation}; use acvm::acir::brillig::{BitSize, IntegerBitSize}; -use acvm::acir::circuit::brillig::BrilligBytecode; +use acvm::acir::circuit::brillig::{BrilligBytecode, BrilligFunctionId}; use acvm::acir::circuit::{Circuit, Opcode, OpcodeLocation}; use acvm::acir::native_types::{Witness, WitnessMap, WitnessStack}; use acvm::brillig_vm::brillig::Opcode as BrilligOpcode; @@ -83,7 +83,7 @@ impl<'a, B: BlackBoxFunctionSolver> ReplDebugger<'a, B> { OpcodeLocation::Brillig { acir_index, brillig_index } => { let brillig_bytecode = if let Opcode::BrilligCall { id, .. } = opcodes[*acir_index] { - &self.unconstrained_functions[id as usize].bytecode + &self.unconstrained_functions[id.as_usize()].bytecode } else { unreachable!("Brillig location does not contain Brillig opcodes"); }; @@ -111,7 +111,7 @@ impl<'a, B: BlackBoxFunctionSolver> ReplDebugger<'a, B> { OpcodeLocation::Brillig { acir_index, brillig_index } => { let brillig_bytecode = if let Opcode::BrilligCall { id, .. } = opcodes[*acir_index] { - &self.unconstrained_functions[id as usize].bytecode + &self.unconstrained_functions[id.as_usize()].bytecode } else { unreachable!("Brillig location does not contain Brillig opcodes"); }; @@ -168,36 +168,41 @@ impl<'a, B: BlackBoxFunctionSolver> ReplDebugger<'a, B> { } else if self.context.is_breakpoint_set(&DebugLocation { circuit_id, opcode_location: OpcodeLocation::Acir(acir_index), + brillig_function_id: None, }) { " *" } else { "" } }; - let brillig_marker = |acir_index, brillig_index| { + let brillig_marker = |acir_index, brillig_index, brillig_function_id| { if current_acir_index == Some(acir_index) && brillig_index == current_brillig_index { "->" } else if self.context.is_breakpoint_set(&DebugLocation { circuit_id, opcode_location: OpcodeLocation::Brillig { acir_index, brillig_index }, + brillig_function_id: Some(brillig_function_id), }) { " *" } else { "" } }; - let print_brillig_bytecode = |acir_index, bytecode: &[BrilligOpcode]| { - for (brillig_index, brillig_opcode) in bytecode.iter().enumerate() { - println!( - "{:>2}:{:>3}.{:<2} |{:2} {:?}", - circuit_id, - acir_index, - brillig_index, - brillig_marker(acir_index, brillig_index), - brillig_opcode - ); - } - }; + let print_brillig_bytecode = + |acir_index, + bytecode: &[BrilligOpcode], + brillig_function_id: BrilligFunctionId| { + for (brillig_index, brillig_opcode) in bytecode.iter().enumerate() { + println!( + "{:>2}:{:>3}.{:<2} |{:2} {:?}", + circuit_id, + acir_index, + brillig_index, + brillig_marker(acir_index, brillig_index, brillig_function_id), + brillig_opcode + ); + } + }; for (acir_index, opcode) in opcodes.iter().enumerate() { let marker = outer_marker(acir_index); match &opcode { @@ -207,8 +212,8 @@ impl<'a, B: BlackBoxFunctionSolver> ReplDebugger<'a, B> { circuit_id, acir_index, marker, id, inputs ); println!(" | outputs={:?}", outputs); - let bytecode = &self.unconstrained_functions[*id as usize].bytecode; - print_brillig_bytecode(acir_index, bytecode); + let bytecode = &self.unconstrained_functions[id.as_usize()].bytecode; + print_brillig_bytecode(acir_index, bytecode, *id); } _ => println!("{:>2}:{:>3} {:2} {:?}", circuit_id, acir_index, marker, opcode), } diff --git a/noir/noir-repo/tooling/debugger/src/source_code_printer.rs b/noir/noir-repo/tooling/debugger/src/source_code_printer.rs index e9586b786bd0..d1c82ad96bab 100644 --- a/noir/noir-repo/tooling/debugger/src/source_code_printer.rs +++ b/noir/noir-repo/tooling/debugger/src/source_code_printer.rs @@ -274,6 +274,7 @@ mod tests { BTreeMap::default(), BTreeMap::default(), BTreeMap::default(), + BTreeMap::default(), )]; let debug_artifact = DebugArtifact::new(debug_symbols, &fm); diff --git a/noir/noir-repo/tooling/fuzzer/Cargo.toml b/noir/noir-repo/tooling/fuzzer/Cargo.toml index ef49d707d6a5..106d8abead12 100644 --- a/noir/noir-repo/tooling/fuzzer/Cargo.toml +++ b/noir/noir-repo/tooling/fuzzer/Cargo.toml @@ -11,7 +11,6 @@ license.workspace = true [dependencies] acvm.workspace = true -nargo.workspace = true noirc_artifacts.workspace = true noirc_abi.workspace = true proptest.workspace = true diff --git a/noir/noir-repo/tooling/fuzzer/src/lib.rs b/noir/noir-repo/tooling/fuzzer/src/lib.rs index 28d7353f35a2..28a43279c953 100644 --- a/noir/noir-repo/tooling/fuzzer/src/lib.rs +++ b/noir/noir-repo/tooling/fuzzer/src/lib.rs @@ -3,7 +3,13 @@ //! //! Code is used under the MIT license. -use acvm::{blackbox_solver::StubbedBlackBoxSolver, FieldElement}; +use acvm::{ + acir::{ + circuit::Program, + native_types::{WitnessMap, WitnessStack}, + }, + FieldElement, +}; use dictionary::build_dictionary_from_program; use noirc_abi::InputMap; use proptest::test_runner::{TestCaseError, TestError, TestRunner}; @@ -16,25 +22,32 @@ use types::{CaseOutcome, CounterExampleOutcome, FuzzOutcome, FuzzTestResult}; use noirc_artifacts::program::ProgramArtifact; -use nargo::ops::{execute_program, DefaultForeignCallExecutor}; - /// An executor for Noir programs which which provides fuzzing support using [`proptest`]. /// /// After instantiation, calling `fuzz` will proceed to hammer the program with /// inputs, until it finds a counterexample. The provided [`TestRunner`] contains all the /// configuration which can be overridden via [environment variables](proptest::test_runner::Config) -pub struct FuzzedExecutor { +pub struct FuzzedExecutor { /// The program to be fuzzed program: ProgramArtifact, + /// A function which executes the programs with a given set of inputs + executor: E, + /// The fuzzer runner: TestRunner, } -impl FuzzedExecutor { +impl< + E: Fn( + &Program, + WitnessMap, + ) -> Result, String>, + > FuzzedExecutor +{ /// Instantiates a fuzzed executor given a testrunner - pub fn new(program: ProgramArtifact, runner: TestRunner) -> Self { - Self { program, runner } + pub fn new(program: ProgramArtifact, executor: E, runner: TestRunner) -> Self { + Self { program, executor, runner } } /// Fuzzes the provided program. @@ -76,19 +89,14 @@ impl FuzzedExecutor { /// or a `CounterExampleOutcome` pub fn single_fuzz(&self, input_map: InputMap) -> Result { let initial_witness = self.program.abi.encode(&input_map, None).unwrap(); - let result = execute_program( - &self.program.bytecode, - initial_witness, - &StubbedBlackBoxSolver, - &mut DefaultForeignCallExecutor::::new(false, None), - ); + let result = (self.executor)(&self.program.bytecode, initial_witness); // TODO: Add handling for `vm.assume` equivalent match result { Ok(_) => Ok(FuzzOutcome::Case(CaseOutcome { case: input_map })), Err(err) => Ok(FuzzOutcome::CounterExample(CounterExampleOutcome { - exit_reason: err.to_string(), + exit_reason: err, counterexample: input_map, })), } diff --git a/noir/noir-repo/tooling/lsp/Cargo.toml b/noir/noir-repo/tooling/lsp/Cargo.toml index ac3e3b1d30a0..03c6c9105bac 100644 --- a/noir/noir-repo/tooling/lsp/Cargo.toml +++ b/noir/noir-repo/tooling/lsp/Cargo.toml @@ -22,6 +22,7 @@ noirc_frontend.workspace = true noirc_artifacts.workspace = true serde.workspace = true serde_json.workspace = true +strum = "0.24" tower.workspace = true async-lsp = { workspace = true, features = ["omni-trait"] } serde_with = "3.2.0" diff --git a/noir/noir-repo/tooling/lsp/src/lib.rs b/noir/noir-repo/tooling/lsp/src/lib.rs index c7b70339e1d9..ca34d7686fd6 100644 --- a/noir/noir-repo/tooling/lsp/src/lib.rs +++ b/noir/noir-repo/tooling/lsp/src/lib.rs @@ -22,8 +22,8 @@ use fm::{codespan_files as files, FileManager}; use fxhash::FxHashSet; use lsp_types::{ request::{ - DocumentSymbolRequest, HoverRequest, InlayHintRequest, PrepareRenameRequest, References, - Rename, + Completion, DocumentSymbolRequest, HoverRequest, InlayHintRequest, PrepareRenameRequest, + References, Rename, }, CodeLens, }; @@ -36,7 +36,10 @@ use nargo_toml::{find_file_manifest, resolve_workspace_from_toml, PackageSelecti use noirc_driver::{file_manager_with_stdlib, prepare_crate, NOIR_ARTIFACT_VERSION_STRING}; use noirc_frontend::{ graph::{CrateId, CrateName}, - hir::{def_map::parse_file, Context, FunctionNameMatch, ParsedFiles}, + hir::{ + def_map::{parse_file, CrateDefMap}, + Context, FunctionNameMatch, ParsedFiles, + }, node_interner::NodeInterner, parser::ParserError, ParsedModule, @@ -48,11 +51,11 @@ use notifications::{ on_did_open_text_document, on_did_save_text_document, on_exit, on_initialized, }; use requests::{ - on_code_lens_request, on_document_symbol_request, on_formatting, on_goto_declaration_request, - on_goto_definition_request, on_goto_type_definition_request, on_hover_request, on_initialize, - on_inlay_hint_request, on_prepare_rename_request, on_profile_run_request, - on_references_request, on_rename_request, on_shutdown, on_test_run_request, on_tests_request, - LspInitializationOptions, + on_code_lens_request, on_completion_request, on_document_symbol_request, on_formatting, + on_goto_declaration_request, on_goto_definition_request, on_goto_type_definition_request, + on_hover_request, on_initialize, on_inlay_hint_request, on_prepare_rename_request, + on_profile_run_request, on_references_request, on_rename_request, on_shutdown, + on_test_run_request, on_tests_request, LspInitializationOptions, }; use serde_json::Value as JsonValue; use thiserror::Error; @@ -62,6 +65,7 @@ mod notifications; mod requests; mod solver; mod types; +mod utils; #[cfg(test)] mod test_utils; @@ -86,6 +90,7 @@ pub struct LspState { cached_lenses: HashMap>, cached_definitions: HashMap, cached_parsed_files: HashMap))>, + cached_def_maps: HashMap>, options: LspInitializationOptions, } @@ -103,6 +108,7 @@ impl LspState { cached_definitions: HashMap::new(), open_documents_count: 0, cached_parsed_files: HashMap::new(), + cached_def_maps: HashMap::new(), options: Default::default(), } } @@ -136,6 +142,7 @@ impl NargoLspService { .request::(on_rename_request) .request::(on_hover_request) .request::(on_inlay_hint_request) + .request::(on_completion_request) .notification::(on_initialized) .notification::(on_did_change_configuration) .notification::(on_did_open_text_document) @@ -236,38 +243,14 @@ fn byte_span_to_range<'a, F: files::Files<'a> + ?Sized>( } } -pub(crate) fn resolve_workspace_for_source_path( - file_path: &Path, - root_path: &Option, -) -> Result { - // If there's a LSP root path, starting from file_path go up the directory tree - // searching for Nargo.toml files. The last one we find is the one we'll use - // (we'll assume Noir workspaces aren't nested) - if let Some(root_path) = root_path { - let mut current_path = file_path; - let mut current_toml_path = None; - while current_path.starts_with(root_path) { - if let Some(toml_path) = find_file_manifest(current_path) { - current_toml_path = Some(toml_path); - - if let Some(next_path) = current_path.parent() { - current_path = next_path; - } else { - break; - } - } else { - break; - } - } - - if let Some(toml_path) = current_toml_path { - return resolve_workspace_from_toml( - &toml_path, - PackageSelection::All, - Some(NOIR_ARTIFACT_VERSION_STRING.to_string()), - ) - .map_err(|err| LspError::WorkspaceResolutionError(err.to_string())); - } +pub(crate) fn resolve_workspace_for_source_path(file_path: &Path) -> Result { + if let Some(toml_path) = find_file_manifest(file_path) { + return resolve_workspace_from_toml( + &toml_path, + PackageSelection::All, + Some(NOIR_ARTIFACT_VERSION_STRING.to_string()), + ) + .map_err(|err| LspError::WorkspaceResolutionError(err.to_string())); } let Some(parent_folder) = file_path @@ -313,7 +296,7 @@ pub(crate) fn prepare_package<'file_manager, 'parsed_files>( package: &Package, ) -> (Context<'file_manager, 'parsed_files>, CrateId) { let (mut context, crate_id) = nargo::prepare_package(file_manager, parsed_files, package); - context.track_references(); + context.activate_lsp_mode(); (context, crate_id) } @@ -334,7 +317,7 @@ fn prepare_source(source: String, state: &mut LspState) -> (Context<'static, 'st let parsed_files = parse_diff(&file_manager, state); let mut context = Context::new(file_manager, parsed_files); - context.track_references(); + context.activate_lsp_mode(); let root_crate_id = prepare_crate(&mut context, file_name); @@ -428,7 +411,7 @@ fn prepare_package_from_source_string() { let mut state = LspState::new(&client, acvm::blackbox_solver::StubbedBlackBoxSolver); let (mut context, crate_id) = crate::prepare_source(source.to_string(), &mut state); - let _check_result = noirc_driver::check_crate(&mut context, crate_id, false, false, None); + let _check_result = noirc_driver::check_crate(&mut context, crate_id, &Default::default()); let main_func_id = context.get_main_function(&crate_id); assert!(main_func_id.is_some()); } diff --git a/noir/noir-repo/tooling/lsp/src/notifications/mod.rs b/noir/noir-repo/tooling/lsp/src/notifications/mod.rs index 24409e85db80..3a60de15c4a0 100644 --- a/noir/noir-repo/tooling/lsp/src/notifications/mod.rs +++ b/noir/noir-repo/tooling/lsp/src/notifications/mod.rs @@ -30,22 +30,16 @@ pub(super) fn on_did_change_configuration( ControlFlow::Continue(()) } -pub(super) fn on_did_open_text_document( +pub(crate) fn on_did_open_text_document( state: &mut LspState, params: DidOpenTextDocumentParams, ) -> ControlFlow> { state.input_files.insert(params.text_document.uri.to_string(), params.text_document.text); let document_uri = params.text_document.uri; - let only_process_document_uri_package = false; let output_diagnostics = true; - match process_workspace_for_noir_document( - state, - document_uri, - only_process_document_uri_package, - output_diagnostics, - ) { + match process_workspace_for_noir_document(state, document_uri, output_diagnostics) { Ok(_) => { state.open_documents_count += 1; ControlFlow::Continue(()) @@ -62,15 +56,9 @@ pub(super) fn on_did_change_text_document( state.input_files.insert(params.text_document.uri.to_string(), text.clone()); let document_uri = params.text_document.uri; - let only_process_document_uri_package = true; - let output_diagnotics = false; + let output_diagnostics = false; - match process_workspace_for_noir_document( - state, - document_uri, - only_process_document_uri_package, - output_diagnotics, - ) { + match process_workspace_for_noir_document(state, document_uri, output_diagnostics) { Ok(_) => ControlFlow::Continue(()), Err(err) => ControlFlow::Break(Err(err)), } @@ -90,15 +78,9 @@ pub(super) fn on_did_close_text_document( } let document_uri = params.text_document.uri; - let only_process_document_uri_package = true; - let output_diagnotics = false; + let output_diagnostics = false; - match process_workspace_for_noir_document( - state, - document_uri, - only_process_document_uri_package, - output_diagnotics, - ) { + match process_workspace_for_noir_document(state, document_uri, output_diagnostics) { Ok(_) => ControlFlow::Continue(()), Err(err) => ControlFlow::Break(Err(err)), } @@ -109,15 +91,9 @@ pub(super) fn on_did_save_text_document( params: DidSaveTextDocumentParams, ) -> ControlFlow> { let document_uri = params.text_document.uri; - let only_process_document_uri_package = false; - let output_diagnotics = true; + let output_diagnostics = true; - match process_workspace_for_noir_document( - state, - document_uri, - only_process_document_uri_package, - output_diagnotics, - ) { + match process_workspace_for_noir_document(state, document_uri, output_diagnostics) { Ok(_) => ControlFlow::Continue(()), Err(err) => ControlFlow::Break(Err(err)), } @@ -129,17 +105,15 @@ pub(super) fn on_did_save_text_document( pub(crate) fn process_workspace_for_noir_document( state: &mut LspState, document_uri: lsp_types::Url, - only_process_document_uri_package: bool, output_diagnostics: bool, ) -> Result<(), async_lsp::Error> { let file_path = document_uri.to_file_path().map_err(|_| { ResponseError::new(ErrorCode::REQUEST_FAILED, "URI is not a valid file path") })?; - let workspace = - resolve_workspace_for_source_path(&file_path, &state.root_path).map_err(|lsp_error| { - ResponseError::new(ErrorCode::REQUEST_FAILED, lsp_error.to_string()) - })?; + let workspace = resolve_workspace_for_source_path(&file_path).map_err(|lsp_error| { + ResponseError::new(ErrorCode::REQUEST_FAILED, lsp_error.to_string()) + })?; let mut workspace_file_manager = file_manager_with_stdlib(&workspace.root_dir); insert_all_files_for_workspace_into_file_manager( @@ -155,14 +129,10 @@ pub(crate) fn process_workspace_for_noir_document( .flat_map(|package| -> Vec { let package_root_dir: String = package.root_dir.as_os_str().to_string_lossy().into(); - if only_process_document_uri_package && !file_path.starts_with(&package.root_dir) { - return vec![]; - } - let (mut context, crate_id) = crate::prepare_package(&workspace_file_manager, &parsed_files, package); - let file_diagnostics = match check_crate(&mut context, crate_id, false, false, None) { + let file_diagnostics = match check_crate(&mut context, crate_id, &Default::default()) { Ok(((), warnings)) => warnings, Err(errors_and_warnings) => errors_and_warnings, }; @@ -183,8 +153,8 @@ pub(crate) fn process_workspace_for_noir_document( Some(&file_path), ); state.cached_lenses.insert(document_uri.to_string(), collected_lenses); - - state.cached_definitions.insert(package_root_dir, context.def_interner); + state.cached_definitions.insert(package_root_dir.clone(), context.def_interner); + state.cached_def_maps.insert(package_root_dir.clone(), context.def_maps); let fm = &context.file_manager; let files = fm.as_file_map(); diff --git a/noir/noir-repo/tooling/lsp/src/requests/code_lens_request.rs b/noir/noir-repo/tooling/lsp/src/requests/code_lens_request.rs index 51336a324da4..9799cf875a9f 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/code_lens_request.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/code_lens_request.rs @@ -63,8 +63,7 @@ fn on_code_lens_request_inner( ResponseError::new(ErrorCode::REQUEST_FAILED, "Could not read file from disk") })?; - let workspace = - resolve_workspace_for_source_path(file_path.as_path(), &state.root_path).unwrap(); + let workspace = resolve_workspace_for_source_path(file_path.as_path()).unwrap(); let package = crate::workspace_package_for_file(&workspace, &file_path).ok_or_else(|| { ResponseError::new(ErrorCode::REQUEST_FAILED, "Could not find package for file") @@ -73,7 +72,7 @@ fn on_code_lens_request_inner( let (mut context, crate_id) = prepare_source(source_string, state); // We ignore the warnings and errors produced by compilation for producing code lenses // because we can still get the test functions even if compilation fails - let _ = check_crate(&mut context, crate_id, false, false, None); + let _ = check_crate(&mut context, crate_id, &Default::default()); let collected_lenses = collect_lenses_for_package(&context, crate_id, &workspace, package, None); diff --git a/noir/noir-repo/tooling/lsp/src/requests/completion.rs b/noir/noir-repo/tooling/lsp/src/requests/completion.rs new file mode 100644 index 000000000000..48616c0f52d5 --- /dev/null +++ b/noir/noir-repo/tooling/lsp/src/requests/completion.rs @@ -0,0 +1,2204 @@ +use std::{ + collections::{BTreeMap, HashMap, HashSet}, + future::{self, Future}, +}; + +use async_lsp::ResponseError; +use builtins::{builtin_integer_types, keyword_builtin_function, keyword_builtin_type}; +use fm::{FileId, PathString}; +use lsp_types::{ + CompletionItem, CompletionItemKind, CompletionItemLabelDetails, CompletionParams, + CompletionResponse, InsertTextFormat, +}; +use noirc_errors::{Location, Span}; +use noirc_frontend::{ + ast::{ + ArrayLiteral, AsTraitPath, BlockExpression, CallExpression, CastExpression, + ConstrainStatement, ConstructorExpression, Expression, ForLoopStatement, ForRange, + FunctionReturnType, Ident, IfExpression, IndexExpression, InfixExpression, LValue, Lambda, + LetStatement, Literal, MemberAccessExpression, MethodCallExpression, NoirFunction, + NoirStruct, NoirTrait, NoirTraitImpl, NoirTypeAlias, Path, PathKind, PathSegment, Pattern, + Statement, TraitImplItem, TraitItem, TypeImpl, UnresolvedGeneric, UnresolvedGenerics, + UnresolvedType, UseTree, UseTreeKind, + }, + graph::{CrateId, Dependency}, + hir::{ + def_map::{CrateDefMap, LocalModuleId, ModuleId}, + resolution::path_resolver::{PathResolver, StandardPathResolver}, + }, + hir_def::{function::FuncMeta, stmt::HirPattern}, + macros_api::{ModuleDefId, NodeInterner, StructId}, + node_interner::{FuncId, GlobalId, ReferenceId, TraitId, TypeAliasId}, + parser::{Item, ItemKind}, + token::Keyword, + ParsedModule, Type, +}; +use strum::IntoEnumIterator; + +use crate::{utils, LspState}; + +use super::process_request; + +mod builtins; + +/// When finding items in a module, whether to show only direct children or all visible items. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum ModuleCompletionKind { + // Only show a module's direct children. This is used when completing a use statement + // or a path after the first segment. + DirectChildren, + // Show all of a module's visible items. This is used when completing a path outside + // of a use statement (in regular code) when the path is just a single segment: + // we want to find items exposed in the current module. + AllVisibleItems, +} + +/// When suggest a function as a result of completion, whether to autocomplete its name or its name and parameters. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum FunctionCompleteKind { + // Only complete a function's name. This is used in use statement. + Name, + // Complete a function's name and parameters (as a snippet). This is used in regular code. + NameAndParameters, +} + +/// When requesting completions, whether to list all items or just types. +/// For example, when writing `let x: S` we only want to suggest types at this +/// point (modules too, because they might include types too). +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum RequestedItems { + // Suggest any items (types, functions, etc.). + AnyItems, + // Only suggest types. + OnlyTypes, +} + +pub(crate) fn on_completion_request( + state: &mut LspState, + params: CompletionParams, +) -> impl Future, ResponseError>> { + let uri = params.text_document_position.clone().text_document.uri; + + let result = process_request(state, params.text_document_position.clone(), |args| { + let path = PathString::from_path(uri.to_file_path().unwrap()); + args.files.get_file_id(&path).and_then(|file_id| { + utils::position_to_byte_index( + args.files, + file_id, + ¶ms.text_document_position.position, + ) + .and_then(|byte_index| { + let file = args.files.get_file(file_id).unwrap(); + let source = file.source(); + let byte = source.as_bytes().get(byte_index - 1).copied(); + let (parsed_module, _errors) = noirc_frontend::parse_program(source); + + let mut finder = NodeFinder::new( + file_id, + byte_index, + byte, + args.crate_id, + args.def_maps, + args.dependencies, + args.interner, + ); + finder.find(&parsed_module) + }) + }) + }); + future::ready(result) +} + +struct NodeFinder<'a> { + file: FileId, + byte_index: usize, + byte: Option, + /// The module ID of the current file. + root_module_id: ModuleId, + /// The module ID in scope. This might change as we traverse the AST + /// if we are analyzing something inside an inline module declaration. + module_id: ModuleId, + def_maps: &'a BTreeMap, + dependencies: &'a Vec, + interner: &'a NodeInterner, + /// Completion items we find along the way. + completion_items: Vec, + /// Local variables in the current scope, mapped to their locations. + /// As we traverse the AST, we collect local variables. + local_variables: HashMap, + /// Type parameters in the current scope. These are collected when entering + /// a struct, a function, etc., and cleared afterwards. + type_parameters: HashSet, +} + +impl<'a> NodeFinder<'a> { + fn new( + file: FileId, + byte_index: usize, + byte: Option, + krate: CrateId, + def_maps: &'a BTreeMap, + dependencies: &'a Vec, + interner: &'a NodeInterner, + ) -> Self { + // Find the module the current file belongs to + let def_map = &def_maps[&krate]; + let root_module_id = ModuleId { krate, local_id: def_map.root() }; + let local_id = if let Some((module_index, _)) = + def_map.modules().iter().find(|(_, module_data)| module_data.location.file == file) + { + LocalModuleId(module_index) + } else { + def_map.root() + }; + let module_id = ModuleId { krate, local_id }; + Self { + file, + byte_index, + byte, + root_module_id, + module_id, + def_maps, + dependencies, + interner, + completion_items: Vec::new(), + local_variables: HashMap::new(), + type_parameters: HashSet::new(), + } + } + + fn find(&mut self, parsed_module: &ParsedModule) -> Option { + self.find_in_parsed_module(parsed_module); + + if self.completion_items.is_empty() { + None + } else { + Some(CompletionResponse::Array(std::mem::take(&mut self.completion_items))) + } + } + + fn find_in_parsed_module(&mut self, parsed_module: &ParsedModule) { + for item in &parsed_module.items { + self.find_in_item(item); + } + } + + fn find_in_item(&mut self, item: &Item) { + if !self.includes_span(item.span) { + return; + } + + match &item.kind { + ItemKind::Import(use_tree) => { + let mut prefixes = Vec::new(); + self.find_in_use_tree(use_tree, &mut prefixes); + } + ItemKind::Submodules(parsed_sub_module) => { + // Switch `self.module_id` to the submodule + let previous_module_id = self.module_id; + + let def_map = &self.def_maps[&self.module_id.krate]; + let Some(module_data) = def_map.modules().get(self.module_id.local_id.0) else { + return; + }; + if let Some(child_module) = module_data.children.get(&parsed_sub_module.name) { + self.module_id = + ModuleId { krate: self.module_id.krate, local_id: *child_module }; + } + + self.find_in_parsed_module(&parsed_sub_module.contents); + + // Restore the old module before continuing + self.module_id = previous_module_id; + } + ItemKind::Function(noir_function) => self.find_in_noir_function(noir_function), + ItemKind::TraitImpl(noir_trait_impl) => self.find_in_noir_trait_impl(noir_trait_impl), + ItemKind::Impl(type_impl) => self.find_in_type_impl(type_impl), + ItemKind::Global(let_statement) => self.find_in_let_statement(let_statement, false), + ItemKind::TypeAlias(noir_type_alias) => self.find_in_noir_type_alias(noir_type_alias), + ItemKind::Struct(noir_struct) => self.find_in_noir_struct(noir_struct), + ItemKind::Trait(noir_trait) => self.find_in_noir_trait(noir_trait), + ItemKind::ModuleDecl(_) => (), + } + } + + fn find_in_noir_function(&mut self, noir_function: &NoirFunction) { + let old_type_parameters = self.type_parameters.clone(); + self.collect_type_parameters_in_generics(&noir_function.def.generics); + + for param in &noir_function.def.parameters { + self.find_in_unresolved_type(¶m.typ); + } + + self.find_in_function_return_type(&noir_function.def.return_type); + + self.local_variables.clear(); + for param in &noir_function.def.parameters { + self.collect_local_variables(¶m.pattern); + } + + self.find_in_block_expression(&noir_function.def.body); + + self.type_parameters = old_type_parameters; + } + + fn find_in_noir_trait_impl(&mut self, noir_trait_impl: &NoirTraitImpl) { + self.type_parameters.clear(); + self.collect_type_parameters_in_generics(&noir_trait_impl.impl_generics); + + for item in &noir_trait_impl.items { + self.find_in_trait_impl_item(item); + } + + self.type_parameters.clear(); + } + + fn find_in_trait_impl_item(&mut self, item: &TraitImplItem) { + match item { + TraitImplItem::Function(noir_function) => self.find_in_noir_function(noir_function), + TraitImplItem::Constant(_, _, _) => (), + TraitImplItem::Type { .. } => (), + } + } + + fn find_in_type_impl(&mut self, type_impl: &TypeImpl) { + self.type_parameters.clear(); + self.collect_type_parameters_in_generics(&type_impl.generics); + + for (method, span) in &type_impl.methods { + self.find_in_noir_function(method); + + // Optimization: stop looking in functions past the completion cursor + if span.end() as usize > self.byte_index { + break; + } + } + + self.type_parameters.clear(); + } + + fn find_in_noir_type_alias(&mut self, noir_type_alias: &NoirTypeAlias) { + self.find_in_unresolved_type(&noir_type_alias.typ); + } + + fn find_in_noir_struct(&mut self, noir_struct: &NoirStruct) { + self.type_parameters.clear(); + self.collect_type_parameters_in_generics(&noir_struct.generics); + + for (_name, unresolved_type) in &noir_struct.fields { + self.find_in_unresolved_type(unresolved_type); + } + + self.type_parameters.clear(); + } + + fn find_in_noir_trait(&mut self, noir_trait: &NoirTrait) { + for item in &noir_trait.items { + self.find_in_trait_item(item); + } + } + + fn find_in_trait_item(&mut self, trait_item: &TraitItem) { + match trait_item { + TraitItem::Function { + name: _, + generics, + parameters, + return_type, + where_clause, + body, + } => { + let old_type_parameters = self.type_parameters.clone(); + self.collect_type_parameters_in_generics(generics); + + for (_name, unresolved_type) in parameters { + self.find_in_unresolved_type(unresolved_type); + } + + self.find_in_function_return_type(return_type); + + for unresolved_trait_constraint in where_clause { + self.find_in_unresolved_type(&unresolved_trait_constraint.typ); + } + + if let Some(body) = body { + self.local_variables.clear(); + for (name, _) in parameters { + self.local_variables.insert(name.to_string(), name.span()); + } + self.find_in_block_expression(body); + }; + + self.type_parameters = old_type_parameters; + } + TraitItem::Constant { name: _, typ, default_value } => { + self.find_in_unresolved_type(typ); + + if let Some(default_value) = default_value { + self.find_in_expression(default_value); + } + } + TraitItem::Type { name: _ } => (), + } + } + + fn find_in_block_expression(&mut self, block_expression: &BlockExpression) { + let old_local_variables = self.local_variables.clone(); + for statement in &block_expression.statements { + self.find_in_statement(statement); + + // Optimization: stop looking in statements past the completion cursor + if statement.span.end() as usize > self.byte_index { + break; + } + } + self.local_variables = old_local_variables; + } + + fn find_in_statement(&mut self, statement: &Statement) { + match &statement.kind { + noirc_frontend::ast::StatementKind::Let(let_statement) => { + self.find_in_let_statement(let_statement, true); + } + noirc_frontend::ast::StatementKind::Constrain(constrain_statement) => { + self.find_in_constrain_statement(constrain_statement); + } + noirc_frontend::ast::StatementKind::Expression(expression) => { + self.find_in_expression(expression); + } + noirc_frontend::ast::StatementKind::Assign(assign_statement) => { + self.find_in_assign_statement(assign_statement); + } + noirc_frontend::ast::StatementKind::For(for_loop_statement) => { + self.find_in_for_loop_statement(for_loop_statement); + } + noirc_frontend::ast::StatementKind::Comptime(statement) => { + // When entering a comptime block, regular local variables shouldn't be offered anymore + let old_local_variables = self.local_variables.clone(); + self.local_variables.clear(); + + self.find_in_statement(statement); + + self.local_variables = old_local_variables; + } + noirc_frontend::ast::StatementKind::Semi(expression) => { + self.find_in_expression(expression); + } + noirc_frontend::ast::StatementKind::Break + | noirc_frontend::ast::StatementKind::Continue + | noirc_frontend::ast::StatementKind::Error => (), + } + } + + fn find_in_let_statement( + &mut self, + let_statement: &LetStatement, + collect_local_variables: bool, + ) { + self.find_in_unresolved_type(&let_statement.r#type); + self.find_in_expression(&let_statement.expression); + + if collect_local_variables { + self.collect_local_variables(&let_statement.pattern); + } + } + + fn find_in_constrain_statement(&mut self, constrain_statement: &ConstrainStatement) { + self.find_in_expression(&constrain_statement.0); + + if let Some(exp) = &constrain_statement.1 { + self.find_in_expression(exp); + } + } + + fn find_in_assign_statement( + &mut self, + assign_statement: &noirc_frontend::ast::AssignStatement, + ) { + self.find_in_lvalue(&assign_statement.lvalue); + self.find_in_expression(&assign_statement.expression); + } + + fn find_in_for_loop_statement(&mut self, for_loop_statement: &ForLoopStatement) { + let old_local_variables = self.local_variables.clone(); + let ident = &for_loop_statement.identifier; + self.local_variables.insert(ident.to_string(), ident.span()); + + self.find_in_for_range(&for_loop_statement.range); + self.find_in_expression(&for_loop_statement.block); + + self.local_variables = old_local_variables; + } + + fn find_in_lvalue(&mut self, lvalue: &LValue) { + match lvalue { + LValue::Ident(_) => (), + LValue::MemberAccess { object, field_name: _, span: _ } => self.find_in_lvalue(object), + LValue::Index { array, index, span: _ } => { + self.find_in_lvalue(array); + self.find_in_expression(index); + } + LValue::Dereference(lvalue, _) => self.find_in_lvalue(lvalue), + } + } + + fn find_in_for_range(&mut self, for_range: &ForRange) { + match for_range { + ForRange::Range(start, end) => { + self.find_in_expression(start); + self.find_in_expression(end); + } + ForRange::Array(expression) => self.find_in_expression(expression), + } + } + + fn find_in_expressions(&mut self, expressions: &[Expression]) { + for expression in expressions { + self.find_in_expression(expression); + } + } + + fn find_in_expression(&mut self, expression: &Expression) { + match &expression.kind { + noirc_frontend::ast::ExpressionKind::Literal(literal) => self.find_in_literal(literal), + noirc_frontend::ast::ExpressionKind::Block(block_expression) => { + self.find_in_block_expression(block_expression); + } + noirc_frontend::ast::ExpressionKind::Prefix(prefix_expression) => { + self.find_in_expression(&prefix_expression.rhs); + } + noirc_frontend::ast::ExpressionKind::Index(index_expression) => { + self.find_in_index_expression(index_expression); + } + noirc_frontend::ast::ExpressionKind::Call(call_expression) => { + self.find_in_call_expression(call_expression); + } + noirc_frontend::ast::ExpressionKind::MethodCall(method_call_expression) => { + self.find_in_method_call_expression(method_call_expression); + } + noirc_frontend::ast::ExpressionKind::Constructor(constructor_expression) => { + self.find_in_constructor_expression(constructor_expression); + } + noirc_frontend::ast::ExpressionKind::MemberAccess(member_access_expression) => { + self.find_in_member_access_expression(member_access_expression); + } + noirc_frontend::ast::ExpressionKind::Cast(cast_expression) => { + self.find_in_cast_expression(cast_expression); + } + noirc_frontend::ast::ExpressionKind::Infix(infix_expression) => { + self.find_in_infix_expression(infix_expression); + } + noirc_frontend::ast::ExpressionKind::If(if_expression) => { + self.find_in_if_expression(if_expression); + } + noirc_frontend::ast::ExpressionKind::Variable(path) => { + self.find_in_path(path, RequestedItems::AnyItems); + } + noirc_frontend::ast::ExpressionKind::Tuple(expressions) => { + self.find_in_expressions(expressions); + } + noirc_frontend::ast::ExpressionKind::Lambda(lambda) => self.find_in_lambda(lambda), + noirc_frontend::ast::ExpressionKind::Parenthesized(expression) => { + self.find_in_expression(expression); + } + noirc_frontend::ast::ExpressionKind::Unquote(expression) => { + self.find_in_expression(expression); + } + noirc_frontend::ast::ExpressionKind::Comptime(block_expression, _) => { + // When entering a comptime block, regular local variables shouldn't be offered anymore + let old_local_variables = self.local_variables.clone(); + self.local_variables.clear(); + + self.find_in_block_expression(block_expression); + + self.local_variables = old_local_variables; + } + noirc_frontend::ast::ExpressionKind::AsTraitPath(as_trait_path) => { + self.find_in_as_trait_path(as_trait_path); + } + noirc_frontend::ast::ExpressionKind::Quote(_) + | noirc_frontend::ast::ExpressionKind::Resolved(_) + | noirc_frontend::ast::ExpressionKind::Error => (), + } + } + + fn find_in_literal(&mut self, literal: &Literal) { + match literal { + Literal::Array(array_literal) => self.find_in_array_literal(array_literal), + Literal::Slice(array_literal) => self.find_in_array_literal(array_literal), + Literal::Bool(_) + | Literal::Integer(_, _) + | Literal::Str(_) + | Literal::RawStr(_, _) + | Literal::FmtStr(_) + | Literal::Unit => (), + } + } + + fn find_in_array_literal(&mut self, array_literal: &ArrayLiteral) { + match array_literal { + ArrayLiteral::Standard(expressions) => self.find_in_expressions(expressions), + ArrayLiteral::Repeated { repeated_element, length } => { + self.find_in_expression(repeated_element); + self.find_in_expression(length); + } + } + } + + fn find_in_index_expression(&mut self, index_expression: &IndexExpression) { + self.find_in_expression(&index_expression.collection); + self.find_in_expression(&index_expression.index); + } + + fn find_in_call_expression(&mut self, call_expression: &CallExpression) { + self.find_in_expression(&call_expression.func); + self.find_in_expressions(&call_expression.arguments); + } + + fn find_in_method_call_expression(&mut self, method_call_expression: &MethodCallExpression) { + self.find_in_expression(&method_call_expression.object); + self.find_in_expressions(&method_call_expression.arguments); + } + + fn find_in_constructor_expression(&mut self, constructor_expression: &ConstructorExpression) { + self.find_in_path(&constructor_expression.type_name, RequestedItems::OnlyTypes); + + for (_field_name, expression) in &constructor_expression.fields { + self.find_in_expression(expression); + } + } + + fn find_in_member_access_expression( + &mut self, + member_access_expression: &MemberAccessExpression, + ) { + self.find_in_expression(&member_access_expression.lhs); + } + + fn find_in_cast_expression(&mut self, cast_expression: &CastExpression) { + self.find_in_expression(&cast_expression.lhs); + } + + fn find_in_infix_expression(&mut self, infix_expression: &InfixExpression) { + self.find_in_expression(&infix_expression.lhs); + self.find_in_expression(&infix_expression.rhs); + } + + fn find_in_if_expression(&mut self, if_expression: &IfExpression) { + self.find_in_expression(&if_expression.condition); + + let old_local_variables = self.local_variables.clone(); + self.find_in_expression(&if_expression.consequence); + self.local_variables = old_local_variables; + + if let Some(alternative) = &if_expression.alternative { + let old_local_variables = self.local_variables.clone(); + self.find_in_expression(alternative); + self.local_variables = old_local_variables; + } + } + + fn find_in_lambda(&mut self, lambda: &Lambda) { + for (_, unresolved_type) in &lambda.parameters { + self.find_in_unresolved_type(unresolved_type); + } + + let old_local_variables = self.local_variables.clone(); + for (pattern, _) in &lambda.parameters { + self.collect_local_variables(pattern); + } + + self.find_in_expression(&lambda.body); + + self.local_variables = old_local_variables; + } + + fn find_in_as_trait_path(&mut self, as_trait_path: &AsTraitPath) { + self.find_in_path(&as_trait_path.trait_path, RequestedItems::OnlyTypes); + } + + fn find_in_function_return_type(&mut self, return_type: &FunctionReturnType) { + match return_type { + noirc_frontend::ast::FunctionReturnType::Default(_) => (), + noirc_frontend::ast::FunctionReturnType::Ty(unresolved_type) => { + self.find_in_unresolved_type(unresolved_type); + } + } + } + + fn find_in_unresolved_types(&mut self, unresolved_type: &[UnresolvedType]) { + for unresolved_type in unresolved_type { + self.find_in_unresolved_type(unresolved_type); + } + } + + fn find_in_unresolved_type(&mut self, unresolved_type: &UnresolvedType) { + if let Some(span) = unresolved_type.span { + if !self.includes_span(span) { + return; + } + } + + match &unresolved_type.typ { + noirc_frontend::ast::UnresolvedTypeData::Array(_, unresolved_type) => { + self.find_in_unresolved_type(unresolved_type); + } + noirc_frontend::ast::UnresolvedTypeData::Slice(unresolved_type) => { + self.find_in_unresolved_type(unresolved_type); + } + noirc_frontend::ast::UnresolvedTypeData::Parenthesized(unresolved_type) => { + self.find_in_unresolved_type(unresolved_type); + } + noirc_frontend::ast::UnresolvedTypeData::Named(path, unresolved_types, _) => { + self.find_in_path(path, RequestedItems::OnlyTypes); + self.find_in_unresolved_types(unresolved_types); + } + noirc_frontend::ast::UnresolvedTypeData::TraitAsType(path, unresolved_types) => { + self.find_in_path(path, RequestedItems::OnlyTypes); + self.find_in_unresolved_types(unresolved_types); + } + noirc_frontend::ast::UnresolvedTypeData::MutableReference(unresolved_type) => { + self.find_in_unresolved_type(unresolved_type); + } + noirc_frontend::ast::UnresolvedTypeData::Tuple(unresolved_types) => { + self.find_in_unresolved_types(unresolved_types); + } + noirc_frontend::ast::UnresolvedTypeData::Function(args, ret, env) => { + self.find_in_unresolved_types(args); + self.find_in_unresolved_type(ret); + self.find_in_unresolved_type(env); + } + noirc_frontend::ast::UnresolvedTypeData::AsTraitPath(as_trait_path) => { + self.find_in_as_trait_path(as_trait_path); + } + noirc_frontend::ast::UnresolvedTypeData::Expression(_) + | noirc_frontend::ast::UnresolvedTypeData::FormatString(_, _) + | noirc_frontend::ast::UnresolvedTypeData::String(_) + | noirc_frontend::ast::UnresolvedTypeData::Unspecified + | noirc_frontend::ast::UnresolvedTypeData::Quoted(_) + | noirc_frontend::ast::UnresolvedTypeData::FieldElement + | noirc_frontend::ast::UnresolvedTypeData::Integer(_, _) + | noirc_frontend::ast::UnresolvedTypeData::Bool + | noirc_frontend::ast::UnresolvedTypeData::Unit + | noirc_frontend::ast::UnresolvedTypeData::Resolved(_) + | noirc_frontend::ast::UnresolvedTypeData::Error => (), + } + } + + fn find_in_path(&mut self, path: &Path, requested_items: RequestedItems) { + // Only offer completions if we are right at the end of the path + if self.byte_index != path.span.end() as usize { + return; + } + + let after_colons = self.byte == Some(b':'); + + let mut idents: Vec = + path.segments.iter().map(|segment| segment.ident.clone()).collect(); + let prefix; + let at_root; + + if after_colons { + prefix = String::new(); + at_root = false; + } else { + prefix = idents.pop().unwrap().to_string(); + at_root = idents.is_empty(); + } + + let is_single_segment = !after_colons && idents.is_empty() && path.kind == PathKind::Plain; + + let module_id = + if idents.is_empty() { Some(self.module_id) } else { self.resolve_module(idents) }; + let Some(module_id) = module_id else { + return; + }; + + let module_completion_kind = if after_colons { + ModuleCompletionKind::DirectChildren + } else { + ModuleCompletionKind::AllVisibleItems + }; + let function_completion_kind = FunctionCompleteKind::NameAndParameters; + + self.complete_in_module( + module_id, + &prefix, + path.kind, + at_root, + module_completion_kind, + function_completion_kind, + requested_items, + ); + + if is_single_segment { + match requested_items { + RequestedItems::AnyItems => { + self.local_variables_completion(&prefix); + self.builtin_functions_completion(&prefix); + self.builtin_values_completion(&prefix); + } + RequestedItems::OnlyTypes => { + self.builtin_types_completion(&prefix); + self.type_parameters_completion(&prefix); + } + } + } + } + + fn local_variables_completion(&mut self, prefix: &str) { + for (name, span) in &self.local_variables { + if name_matches(name, prefix) { + let location = Location::new(*span, self.file); + let description = if let Some(ReferenceId::Local(definition_id)) = + self.interner.reference_at_location(location) + { + let typ = self.interner.definition_type(definition_id); + Some(typ.to_string()) + } else { + None + }; + + self.completion_items.push(simple_completion_item( + name, + CompletionItemKind::VARIABLE, + description, + )); + } + } + } + + fn type_parameters_completion(&mut self, prefix: &str) { + for name in &self.type_parameters { + if name_matches(name, prefix) { + self.completion_items.push(simple_completion_item( + name, + CompletionItemKind::TYPE_PARAMETER, + None, + )); + } + } + } + + fn find_in_use_tree(&mut self, use_tree: &UseTree, prefixes: &mut Vec) { + match &use_tree.kind { + UseTreeKind::Path(ident, alias) => { + prefixes.push(use_tree.prefix.clone()); + self.find_in_use_tree_path(prefixes, ident, alias); + prefixes.pop(); + } + UseTreeKind::List(use_trees) => { + prefixes.push(use_tree.prefix.clone()); + for use_tree in use_trees { + self.find_in_use_tree(use_tree, prefixes); + } + prefixes.pop(); + } + } + } + + fn find_in_use_tree_path( + &mut self, + prefixes: &Vec, + ident: &Ident, + alias: &Option, + ) { + if let Some(_alias) = alias { + // Won't handle completion if there's an alias (for now) + return; + } + + let after_colons = self.byte == Some(b':'); + let at_ident_end = self.byte_index == ident.span().end() as usize; + let at_ident_colons_end = + after_colons && self.byte_index - 2 == ident.span().end() as usize; + + if !(at_ident_end || at_ident_colons_end) { + return; + } + + let path_kind = prefixes[0].kind; + + let mut segments: Vec = Vec::new(); + for prefix in prefixes { + for segment in &prefix.segments { + segments.push(segment.ident.clone()); + } + } + + let module_completion_kind = ModuleCompletionKind::DirectChildren; + let function_completion_kind = FunctionCompleteKind::Name; + let requested_items = RequestedItems::AnyItems; + + if after_colons { + // We are right after "::" + segments.push(ident.clone()); + + if let Some(module_id) = self.resolve_module(segments) { + let prefix = String::new(); + let at_root = false; + self.complete_in_module( + module_id, + &prefix, + path_kind, + at_root, + module_completion_kind, + function_completion_kind, + requested_items, + ); + }; + } else { + // We are right after the last segment + let prefix = ident.to_string(); + if segments.is_empty() { + let at_root = true; + self.complete_in_module( + self.module_id, + &prefix, + path_kind, + at_root, + module_completion_kind, + function_completion_kind, + requested_items, + ); + } else if let Some(module_id) = self.resolve_module(segments) { + let at_root = false; + self.complete_in_module( + module_id, + &prefix, + path_kind, + at_root, + module_completion_kind, + function_completion_kind, + requested_items, + ); + } + } + } + + fn collect_local_variables(&mut self, pattern: &Pattern) { + match pattern { + Pattern::Identifier(ident) => { + self.local_variables.insert(ident.to_string(), ident.span()); + } + Pattern::Mutable(pattern, _, _) => self.collect_local_variables(pattern), + Pattern::Tuple(patterns, _) => { + for pattern in patterns { + self.collect_local_variables(pattern); + } + } + Pattern::Struct(_, patterns, _) => { + for (_, pattern) in patterns { + self.collect_local_variables(pattern); + } + } + } + } + + fn collect_type_parameters_in_generics(&mut self, generics: &UnresolvedGenerics) { + for generic in generics { + self.collect_type_parameters_in_generic(generic); + } + } + + fn collect_type_parameters_in_generic(&mut self, generic: &UnresolvedGeneric) { + match generic { + UnresolvedGeneric::Variable(ident) => { + self.type_parameters.insert(ident.to_string()); + } + UnresolvedGeneric::Numeric { ident, typ: _ } => { + self.type_parameters.insert(ident.to_string()); + } + UnresolvedGeneric::Resolved(..) => (), + }; + } + + #[allow(clippy::too_many_arguments)] + fn complete_in_module( + &mut self, + module_id: ModuleId, + prefix: &str, + path_kind: PathKind, + at_root: bool, + module_completion_kind: ModuleCompletionKind, + function_completion_kind: FunctionCompleteKind, + requested_items: RequestedItems, + ) { + let def_map = &self.def_maps[&module_id.krate]; + let Some(mut module_data) = def_map.modules().get(module_id.local_id.0) else { + return; + }; + + if at_root { + match path_kind { + PathKind::Crate => { + let Some(root_module_data) = def_map.modules().get(def_map.root().0) else { + return; + }; + module_data = root_module_data; + } + PathKind::Super => { + let Some(parent) = module_data.parent else { + return; + }; + let Some(parent_module_data) = def_map.modules().get(parent.0) else { + return; + }; + module_data = parent_module_data; + } + PathKind::Dep => (), + PathKind::Plain => (), + } + } + + let items = match module_completion_kind { + ModuleCompletionKind::DirectChildren => module_data.definitions(), + ModuleCompletionKind::AllVisibleItems => module_data.scope(), + }; + + for ident in items.names() { + let name = &ident.0.contents; + + if name_matches(name, prefix) { + let per_ns = module_data.find_name(ident); + if let Some((module_def_id, _, _)) = per_ns.types { + if let Some(completion_item) = self.module_def_id_completion_item( + module_def_id, + name.clone(), + function_completion_kind, + requested_items, + ) { + self.completion_items.push(completion_item); + } + } + + if let Some((module_def_id, _, _)) = per_ns.values { + if let Some(completion_item) = self.module_def_id_completion_item( + module_def_id, + name.clone(), + function_completion_kind, + requested_items, + ) { + self.completion_items.push(completion_item); + } + } + } + } + + if at_root && path_kind == PathKind::Plain { + for dependency in self.dependencies { + let dependency_name = dependency.as_name(); + if name_matches(&dependency_name, prefix) { + self.completion_items.push(crate_completion_item(dependency_name)); + } + } + + if name_matches("crate::", prefix) { + self.completion_items.push(simple_completion_item( + "crate::", + CompletionItemKind::KEYWORD, + None, + )); + } + + if module_data.parent.is_some() && name_matches("super::", prefix) { + self.completion_items.push(simple_completion_item( + "super::", + CompletionItemKind::KEYWORD, + None, + )); + } + } + } + + fn module_def_id_completion_item( + &self, + module_def_id: ModuleDefId, + name: String, + function_completion_kind: FunctionCompleteKind, + requested_items: RequestedItems, + ) -> Option { + match requested_items { + RequestedItems::OnlyTypes => match module_def_id { + ModuleDefId::FunctionId(_) | ModuleDefId::GlobalId(_) => return None, + ModuleDefId::ModuleId(_) + | ModuleDefId::TypeId(_) + | ModuleDefId::TypeAliasId(_) + | ModuleDefId::TraitId(_) => (), + }, + RequestedItems::AnyItems => (), + } + + let completion_item = match module_def_id { + ModuleDefId::ModuleId(_) => module_completion_item(name), + ModuleDefId::FunctionId(func_id) => { + self.function_completion_item(func_id, function_completion_kind) + } + ModuleDefId::TypeId(struct_id) => self.struct_completion_item(struct_id), + ModuleDefId::TypeAliasId(type_alias_id) => { + self.type_alias_completion_item(type_alias_id) + } + ModuleDefId::TraitId(trait_id) => self.trait_completion_item(trait_id), + ModuleDefId::GlobalId(global_id) => self.global_completion_item(global_id), + }; + Some(completion_item) + } + + fn function_completion_item( + &self, + func_id: FuncId, + function_completion_kind: FunctionCompleteKind, + ) -> CompletionItem { + let func_meta = self.interner.function_meta(&func_id); + let name = self.interner.function_name(&func_id).to_string(); + + let mut typ = &func_meta.typ; + if let Type::Forall(_, typ_) = typ { + typ = typ_; + } + let description = typ.to_string(); + let description = description.strip_suffix(" -> ()").unwrap_or(&description).to_string(); + + match function_completion_kind { + FunctionCompleteKind::Name => { + simple_completion_item(name, CompletionItemKind::FUNCTION, Some(description)) + } + FunctionCompleteKind::NameAndParameters => { + let label = format!("{}(…)", name); + let kind = CompletionItemKind::FUNCTION; + let insert_text = self.compute_function_insert_text(func_meta, &name); + + snippet_completion_item(label, kind, insert_text, Some(description)) + } + } + } + + fn compute_function_insert_text(&self, func_meta: &FuncMeta, name: &str) -> String { + let mut text = String::new(); + text.push_str(name); + text.push('('); + for (index, (pattern, _, _)) in func_meta.parameters.0.iter().enumerate() { + if index > 0 { + text.push_str(", "); + } + + text.push_str("${"); + text.push_str(&(index + 1).to_string()); + text.push(':'); + self.hir_pattern_to_argument(pattern, &mut text); + text.push('}'); + } + text.push(')'); + text + } + + fn hir_pattern_to_argument(&self, pattern: &HirPattern, text: &mut String) { + match pattern { + HirPattern::Identifier(hir_ident) => { + text.push_str(self.interner.definition_name(hir_ident.id)); + } + HirPattern::Mutable(pattern, _) => self.hir_pattern_to_argument(pattern, text), + HirPattern::Tuple(_, _) | HirPattern::Struct(_, _, _) => text.push('_'), + } + } + + fn struct_completion_item(&self, struct_id: StructId) -> CompletionItem { + let struct_type = self.interner.get_struct(struct_id); + let struct_type = struct_type.borrow(); + let name = struct_type.name.to_string(); + + simple_completion_item(name.clone(), CompletionItemKind::STRUCT, Some(name)) + } + + fn type_alias_completion_item(&self, type_alias_id: TypeAliasId) -> CompletionItem { + let type_alias = self.interner.get_type_alias(type_alias_id); + let type_alias = type_alias.borrow(); + let name = type_alias.name.to_string(); + + simple_completion_item(name.clone(), CompletionItemKind::STRUCT, Some(name)) + } + + fn trait_completion_item(&self, trait_id: TraitId) -> CompletionItem { + let trait_ = self.interner.get_trait(trait_id); + let name = trait_.name.to_string(); + + simple_completion_item(name.clone(), CompletionItemKind::INTERFACE, Some(name)) + } + + fn global_completion_item(&self, global_id: GlobalId) -> CompletionItem { + let global_definition = self.interner.get_global_definition(global_id); + let name = global_definition.name.clone(); + + let global = self.interner.get_global(global_id); + let typ = self.interner.definition_type(global.definition_id); + let description = typ.to_string(); + + simple_completion_item(name, CompletionItemKind::CONSTANT, Some(description)) + } + + fn resolve_module(&self, segments: Vec) -> Option { + if let Some(ModuleDefId::ModuleId(module_id)) = self.resolve_path(segments) { + Some(module_id) + } else { + None + } + } + + fn resolve_path(&self, segments: Vec) -> Option { + let path_segments = segments.into_iter().map(PathSegment::from).collect(); + let path = Path { segments: path_segments, kind: PathKind::Plain, span: Span::default() }; + + let path_resolver = StandardPathResolver::new(self.root_module_id); + match path_resolver.resolve(self.def_maps, path, &mut None) { + Ok(path_resolution) => Some(path_resolution.module_def_id), + Err(_) => None, + } + } + + fn builtin_functions_completion(&mut self, prefix: &str) { + for keyword in Keyword::iter() { + if let Some(func) = keyword_builtin_function(&keyword) { + if name_matches(func.name, prefix) { + self.completion_items.push(snippet_completion_item( + format!("{}(…)", func.name), + CompletionItemKind::FUNCTION, + format!("{}({})", func.name, func.parameters), + Some(func.description.to_string()), + )); + } + } + } + } + + fn builtin_values_completion(&mut self, prefix: &str) { + for keyword in ["false", "true"] { + if name_matches(keyword, prefix) { + self.completion_items.push(simple_completion_item( + keyword, + CompletionItemKind::KEYWORD, + Some("bool".to_string()), + )); + } + } + } + + fn builtin_types_completion(&mut self, prefix: &str) { + for keyword in Keyword::iter() { + if let Some(typ) = keyword_builtin_type(&keyword) { + if name_matches(typ, prefix) { + self.completion_items.push(simple_completion_item( + typ, + CompletionItemKind::STRUCT, + Some(typ.to_string()), + )); + } + } + } + + for typ in builtin_integer_types() { + if name_matches(typ, prefix) { + self.completion_items.push(simple_completion_item( + typ, + CompletionItemKind::STRUCT, + Some(typ.to_string()), + )); + } + } + } + + fn includes_span(&self, span: Span) -> bool { + span.start() as usize <= self.byte_index && self.byte_index <= span.end() as usize + } +} + +fn name_matches(name: &str, prefix: &str) -> bool { + name.starts_with(prefix) +} + +fn module_completion_item(name: impl Into) -> CompletionItem { + simple_completion_item(name, CompletionItemKind::MODULE, None) +} + +fn crate_completion_item(name: impl Into) -> CompletionItem { + simple_completion_item(name, CompletionItemKind::MODULE, None) +} + +fn simple_completion_item( + label: impl Into, + kind: CompletionItemKind, + description: Option, +) -> CompletionItem { + CompletionItem { + label: label.into(), + label_details: Some(CompletionItemLabelDetails { detail: None, description }), + kind: Some(kind), + detail: None, + documentation: None, + deprecated: None, + preselect: None, + sort_text: None, + filter_text: None, + insert_text: None, + insert_text_format: None, + insert_text_mode: None, + text_edit: None, + additional_text_edits: None, + command: None, + commit_characters: None, + data: None, + tags: None, + } +} + +fn snippet_completion_item( + label: impl Into, + kind: CompletionItemKind, + insert_text: impl Into, + description: Option, +) -> CompletionItem { + CompletionItem { + label: label.into(), + label_details: Some(CompletionItemLabelDetails { detail: None, description }), + kind: Some(kind), + insert_text_format: Some(InsertTextFormat::SNIPPET), + insert_text: Some(insert_text.into()), + detail: None, + documentation: None, + deprecated: None, + preselect: None, + sort_text: None, + filter_text: None, + insert_text_mode: None, + text_edit: None, + additional_text_edits: None, + command: None, + commit_characters: None, + data: None, + tags: None, + } +} + +#[cfg(test)] +mod completion_tests { + use crate::{notifications::on_did_open_text_document, test_utils}; + + use super::*; + use lsp_types::{ + DidOpenTextDocumentParams, PartialResultParams, Position, TextDocumentIdentifier, + TextDocumentItem, TextDocumentPositionParams, WorkDoneProgressParams, + }; + use tokio::test; + + async fn assert_completion(src: &str, expected: Vec) { + 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(">|<", ""); + + 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(), + }, + }, + ); + + // Get inlay hints. These should now be relative to the changed text, + // not the saved file's text. + let response = on_completion_request( + &mut state, + CompletionParams { + 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: None, + }, + ) + .await + .expect("Could not execute on_completion_request") + .unwrap(); + + let CompletionResponse::Array(items) = response else { + panic!("Expected response to be CompletionResponse::Array"); + }; + + let mut items = items.clone(); + items.sort_by_key(|item| item.label.clone()); + + let mut expected = expected.clone(); + expected.sort_by_key(|item| item.label.clone()); + + if items != expected { + println!( + "Items: {:?}", + items.iter().map(|item| item.label.clone()).collect::>() + ); + println!( + "Expected: {:?}", + expected.iter().map(|item| item.label.clone()).collect::>() + ); + } + + assert_eq!(items, expected); + } + + #[test] + async fn test_use_first_segment() { + let src = r#" + mod foo {} + mod foobar {} + use f>|< + "#; + + assert_completion( + src, + vec![module_completion_item("foo"), module_completion_item("foobar")], + ) + .await; + } + + #[test] + async fn test_use_second_segment() { + let src = r#" + mod foo { + mod bar {} + mod baz {} + } + use foo::>|< + "#; + + assert_completion(src, vec![module_completion_item("bar"), module_completion_item("baz")]) + .await; + } + + #[test] + async fn test_use_second_segment_after_typing() { + let src = r#" + mod foo { + mod bar {} + mod brave {} + } + use foo::ba>|< + "#; + + assert_completion(src, vec![module_completion_item("bar")]).await; + } + + #[test] + async fn test_use_struct() { + let src = r#" + mod foo { + struct Foo {} + } + use foo::>|< + "#; + + assert_completion( + src, + vec![simple_completion_item( + "Foo", + CompletionItemKind::STRUCT, + Some("Foo".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_use_function() { + let src = r#" + mod foo { + fn bar(x: i32) -> u64 { 0 } + } + use foo::>|< + "#; + + assert_completion( + src, + vec![simple_completion_item( + "bar", + CompletionItemKind::FUNCTION, + Some("fn(i32) -> u64".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_use_after_crate_and_letter() { + // Prove that "std" shows up + let src = r#" + use s>|< + "#; + assert_completion(src, vec![crate_completion_item("std")]).await; + + // "std" doesn't show up anymore because of the "crate::" prefix + let src = r#" + mod something {} + use crate::s>|< + "#; + assert_completion(src, vec![module_completion_item("something")]).await; + } + + #[test] + async fn test_use_suggests_hardcoded_crate() { + let src = r#" + use c>|< + "#; + + assert_completion( + src, + vec![simple_completion_item("crate::", CompletionItemKind::KEYWORD, None)], + ) + .await; + } + + #[test] + async fn test_use_in_tree_after_letter() { + let src = r#" + mod foo { + mod bar {} + } + use foo::{b>|<} + "#; + + assert_completion(src, vec![module_completion_item("bar")]).await; + } + + #[test] + async fn test_use_in_tree_after_colons() { + let src = r#" + mod foo { + mod bar { + mod baz {} + } + } + use foo::{bar::>|<} + "#; + + assert_completion(src, vec![module_completion_item("baz")]).await; + } + + #[test] + async fn test_use_in_tree_after_colons_after_another_segment() { + let src = r#" + mod foo { + mod bar {} + mod qux {} + } + use foo::{bar, q>|<} + "#; + + assert_completion(src, vec![module_completion_item("qux")]).await; + } + + #[test] + async fn test_use_in_nested_module() { + let src = r#" + mod foo { + mod something {} + + use s>|< + } + "#; + + assert_completion( + src, + vec![ + module_completion_item("something"), + crate_completion_item("std"), + simple_completion_item("super::", CompletionItemKind::KEYWORD, None), + ], + ) + .await; + } + + #[test] + async fn test_use_after_super() { + let src = r#" + mod foo {} + + mod bar { + mod something {} + + use super::f>|< + } + "#; + + assert_completion(src, vec![module_completion_item("foo")]).await; + } + + #[test] + async fn test_use_after_crate_and_letter_nested_in_module() { + let src = r#" + mod something { + mod something_else {} + use crate::s>|< + } + + "#; + assert_completion(src, vec![module_completion_item("something")]).await; + } + + #[test] + async fn test_use_after_crate_segment_and_letter_nested_in_module() { + let src = r#" + mod something { + mod something_else {} + use crate::something::s>|< + } + + "#; + assert_completion(src, vec![module_completion_item("something_else")]).await; + } + + #[test] + async fn test_complete_path_shows_module() { + let src = r#" + mod foobar {} + + fn main() { + fo>|< + } + "#; + assert_completion(src, vec![module_completion_item("foobar")]).await; + } + + #[test] + async fn test_complete_path_after_colons_shows_submodule() { + let src = r#" + mod foo { + mod bar {} + } + + fn main() { + foo::>|< + } + "#; + assert_completion(src, vec![module_completion_item("bar")]).await; + } + + #[test] + async fn test_complete_path_after_colons_and_letter_shows_submodule() { + let src = r#" + mod foo { + mod bar {} + } + + fn main() { + foo::b>|< + } + "#; + assert_completion(src, vec![module_completion_item("bar")]).await; + } + + #[test] + async fn test_complete_path_with_local_variable() { + let src = r#" + fn main() { + let local = 1; + l>|< + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "local", + CompletionItemKind::VARIABLE, + Some("Field".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_complete_path_with_shadowed_local_variable() { + let src = r#" + fn main() { + let local = 1; + let local = true; + l>|< + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "local", + CompletionItemKind::VARIABLE, + Some("bool".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_complete_path_with_function_argument() { + let src = r#" + fn main(local: Field) { + l>|< + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "local", + CompletionItemKind::VARIABLE, + Some("Field".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_complete_function() { + let src = r#" + fn hello(x: i32, y: Field) { } + + fn main() { + h>|< + } + "#; + assert_completion( + src, + vec![snippet_completion_item( + "hello(…)", + CompletionItemKind::FUNCTION, + "hello(${1:x}, ${2:y})", + Some("fn(i32, Field)".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_complete_builtin_functions() { + let src = r#" + fn main() { + a>|< + } + "#; + assert_completion( + src, + vec![ + snippet_completion_item( + "assert(…)", + CompletionItemKind::FUNCTION, + "assert(${1:predicate})", + Some("fn(T)".to_string()), + ), + snippet_completion_item( + "assert_constant(…)", + CompletionItemKind::FUNCTION, + "assert_constant(${1:x})", + Some("fn(T)".to_string()), + ), + snippet_completion_item( + "assert_eq(…)", + CompletionItemKind::FUNCTION, + "assert_eq(${1:lhs}, ${2:rhs})", + Some("fn(T, T)".to_string()), + ), + ], + ) + .await; + } + + #[test] + async fn test_complete_path_in_impl() { + let src = r#" + struct SomeStruct {} + + impl SomeStruct { + fn foo() { + S>|< + } + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "SomeStruct", + CompletionItemKind::STRUCT, + Some("SomeStruct".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_complete_path_in_trait_impl() { + let src = r#" + struct SomeStruct {} + trait Trait {} + + impl Trait for SomeStruct { + fn foo() { + S>|< + } + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "SomeStruct", + CompletionItemKind::STRUCT, + Some("SomeStruct".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_complete_path_with_for_argument() { + let src = r#" + fn main() { + for index in 0..10 { + i>|< + } + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "index", + CompletionItemKind::VARIABLE, + Some("u32".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_complete_path_with_lambda_argument() { + let src = r#" + fn lambda(f: fn(i32)) { } + + fn main() { + lambda(|var| v>|<) + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "var", + CompletionItemKind::VARIABLE, + Some("_".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_type_in_struct_field_type() { + let src = r#" + struct Something {} + + fn SomeFunction() {} + + struct Another { + some: So>|< + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "Something", + CompletionItemKind::STRUCT, + Some("Something".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_type_in_function_parameter() { + let src = r#" + struct Something {} + + fn foo(x: So>|<) {} + "#; + assert_completion( + src, + vec![simple_completion_item( + "Something", + CompletionItemKind::STRUCT, + Some("Something".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_type_in_function_return_type() { + let src = r#" + struct Something {} + + fn foo() -> So>|< {} + "#; + assert_completion( + src, + vec![simple_completion_item( + "Something", + CompletionItemKind::STRUCT, + Some("Something".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_type_in_type_alias() { + let src = r#" + struct Something {} + + type Foo = So>|< + "#; + assert_completion( + src, + vec![simple_completion_item( + "Something", + CompletionItemKind::STRUCT, + Some("Something".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_type_in_trait_function() { + let src = r#" + struct Something {} + + trait Trait { + fn foo(s: So>|<); + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "Something", + CompletionItemKind::STRUCT, + Some("Something".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_type_in_trait_function_return_type() { + let src = r#" + struct Something {} + + trait Trait { + fn foo() -> So>|<; + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "Something", + CompletionItemKind::STRUCT, + Some("Something".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_type_in_let_type() { + let src = r#" + struct Something {} + + fn main() { + let x: So>|< + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "Something", + CompletionItemKind::STRUCT, + Some("Something".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_type_in_lambda_parameter() { + let src = r#" + struct Something {} + + fn main() { + foo(|s: So>|<| s) + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "Something", + CompletionItemKind::STRUCT, + Some("Something".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_builtin_types() { + let src = r#" + fn foo(x: i>|<) {} + "#; + assert_completion( + src, + vec![ + simple_completion_item("i8", CompletionItemKind::STRUCT, Some("i8".to_string())), + simple_completion_item("i16", CompletionItemKind::STRUCT, Some("i16".to_string())), + simple_completion_item("i32", CompletionItemKind::STRUCT, Some("i32".to_string())), + simple_completion_item("i64", CompletionItemKind::STRUCT, Some("i64".to_string())), + ], + ) + .await; + } + + #[test] + async fn test_suggest_true() { + let src = r#" + fn main() { + let x = t>|< + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "true", + CompletionItemKind::KEYWORD, + Some("bool".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_regarding_if_scope() { + let src = r#" + fn main() { + let good = 1; + if true { + let great = 2; + g>|< + } else { + let greater = 3; + } + } + "#; + assert_completion( + src, + vec![ + simple_completion_item( + "good", + CompletionItemKind::VARIABLE, + Some("Field".to_string()), + ), + simple_completion_item( + "great", + CompletionItemKind::VARIABLE, + Some("Field".to_string()), + ), + ], + ) + .await; + + let src = r#" + fn main() { + let good = 1; + if true { + let great = 2; + } else { + let greater = 3; + g>|< + } + } + "#; + assert_completion( + src, + vec![ + simple_completion_item( + "good", + CompletionItemKind::VARIABLE, + Some("Field".to_string()), + ), + simple_completion_item( + "greater", + CompletionItemKind::VARIABLE, + Some("Field".to_string()), + ), + ], + ) + .await; + + let src = r#" + fn main() { + let good = 1; + if true { + let great = 2; + } else { + let greater = 3; + } + g>|< + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "good", + CompletionItemKind::VARIABLE, + Some("Field".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_regarding_block_scope() { + let src = r#" + fn main() { + let good = 1; + { + let great = 2; + g>|< + } + } + "#; + assert_completion( + src, + vec![ + simple_completion_item( + "good", + CompletionItemKind::VARIABLE, + Some("Field".to_string()), + ), + simple_completion_item( + "great", + CompletionItemKind::VARIABLE, + Some("Field".to_string()), + ), + ], + ) + .await; + + let src = r#" + fn main() { + let good = 1; + { + let great = 2; + } + g>|< + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "good", + CompletionItemKind::VARIABLE, + Some("Field".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_struct_type_parameter() { + let src = r#" + struct Foo { + context: C>|< + } + "#; + assert_completion( + src, + vec![simple_completion_item("Context", CompletionItemKind::TYPE_PARAMETER, None)], + ) + .await; + } + + #[test] + async fn test_suggest_impl_type_parameter() { + let src = r#" + struct Foo {} + + impl Foo { + fn foo() { + let x: TypeP>|< + } + } + "#; + assert_completion( + src, + vec![simple_completion_item("TypeParam", CompletionItemKind::TYPE_PARAMETER, None)], + ) + .await; + } + + #[test] + async fn test_suggest_trait_impl_type_parameter() { + let src = r#" + struct Foo {} + trait Trait {} + + impl Trait for Foo { + fn foo() { + let x: TypeP>|< + } + } + "#; + assert_completion( + src, + vec![simple_completion_item("TypeParam", CompletionItemKind::TYPE_PARAMETER, None)], + ) + .await; + } + + #[test] + async fn test_suggest_trait_function_type_parameter() { + let src = r#" + struct Foo {} + trait Trait { + fn foo() { + let x: TypeP>|< + } + } + "#; + assert_completion( + src, + vec![simple_completion_item("TypeParam", CompletionItemKind::TYPE_PARAMETER, None)], + ) + .await; + } + + #[test] + async fn test_suggest_function_type_parameters() { + let src = r#" + fn foo(x: C>|<) {} + "#; + assert_completion( + src, + vec![simple_completion_item("Context", CompletionItemKind::TYPE_PARAMETER, None)], + ) + .await; + } +} diff --git a/noir/noir-repo/tooling/lsp/src/requests/completion/builtins.rs b/noir/noir-repo/tooling/lsp/src/requests/completion/builtins.rs new file mode 100644 index 000000000000..070be109f137 --- /dev/null +++ b/noir/noir-repo/tooling/lsp/src/requests/completion/builtins.rs @@ -0,0 +1,127 @@ +use noirc_frontend::token::Keyword; + +pub(super) fn builtin_integer_types() -> [&'static str; 8] { + ["i8", "i16", "i32", "i64", "u8", "u16", "u32", "u64"] +} + +/// If a keyword corresponds to a built-in type, returns that type's name. +pub(super) fn keyword_builtin_type(keyword: &Keyword) -> Option<&'static str> { + match keyword { + Keyword::Bool => Some("bool"), + Keyword::Expr => Some("Expr"), + Keyword::Field => Some("Field"), + Keyword::FunctionDefinition => Some("FunctionDefinition"), + Keyword::StructDefinition => Some("StructDefinition"), + Keyword::TraitConstraint => Some("TraitConstraint"), + Keyword::TraitDefinition => Some("TraitDefinition"), + Keyword::TypeType => Some("Type"), + + Keyword::As + | Keyword::Assert + | Keyword::AssertEq + | Keyword::Break + | Keyword::CallData + | Keyword::Char + | Keyword::Comptime + | Keyword::Constrain + | Keyword::Continue + | Keyword::Contract + | Keyword::Crate + | Keyword::Dep + | Keyword::Else + | Keyword::Fn + | Keyword::For + | Keyword::FormatString + | Keyword::Global + | Keyword::If + | Keyword::Impl + | Keyword::In + | Keyword::Let + | Keyword::Mod + | Keyword::Module + | Keyword::Mut + | Keyword::Pub + | Keyword::Quoted + | Keyword::Return + | Keyword::ReturnData + | Keyword::String + | Keyword::Struct + | Keyword::Super + | Keyword::TopLevelItem + | Keyword::Trait + | Keyword::Type + | Keyword::Unchecked + | Keyword::Unconstrained + | Keyword::Use + | Keyword::Where + | Keyword::While => None, + } +} + +pub(super) struct BuiltInFunction { + pub(super) name: &'static str, + pub(super) parameters: &'static str, + pub(super) description: &'static str, +} + +/// If a keyword corresponds to a built-in function, returns info about it +pub(super) fn keyword_builtin_function(keyword: &Keyword) -> Option { + match keyword { + Keyword::Assert => Some(BuiltInFunction { + name: "assert", + parameters: "${1:predicate}", + description: "fn(T)", + }), + Keyword::AssertEq => Some(BuiltInFunction { + name: "assert_eq", + parameters: "${1:lhs}, ${2:rhs}", + description: "fn(T, T)", + }), + + Keyword::As + | Keyword::Bool + | Keyword::Break + | Keyword::CallData + | Keyword::Char + | Keyword::Comptime + | Keyword::Constrain + | Keyword::Continue + | Keyword::Contract + | Keyword::Crate + | Keyword::Dep + | Keyword::Else + | Keyword::Expr + | Keyword::Field + | Keyword::Fn + | Keyword::For + | Keyword::FormatString + | Keyword::FunctionDefinition + | Keyword::Global + | Keyword::If + | Keyword::Impl + | Keyword::In + | Keyword::Let + | Keyword::Mod + | Keyword::Module + | Keyword::Mut + | Keyword::Pub + | Keyword::Quoted + | Keyword::Return + | Keyword::ReturnData + | Keyword::String + | Keyword::Struct + | Keyword::StructDefinition + | Keyword::Super + | Keyword::TopLevelItem + | Keyword::Trait + | Keyword::TraitConstraint + | Keyword::TraitDefinition + | Keyword::Type + | Keyword::TypeType + | Keyword::Unchecked + | Keyword::Unconstrained + | Keyword::Use + | Keyword::Where + | Keyword::While => None, + } +} diff --git a/noir/noir-repo/tooling/lsp/src/requests/document_symbol.rs b/noir/noir-repo/tooling/lsp/src/requests/document_symbol.rs index 67e2505d8fd2..20fdfb6ece72 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/document_symbol.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/document_symbol.rs @@ -427,7 +427,7 @@ impl<'a> DocumentSymbolCollector<'a> { return; }; - let name = name_path.last_segment(); + let name = name_path.last_ident(); let Some(name_location) = self.to_lsp_location(name.span()) else { return; diff --git a/noir/noir-repo/tooling/lsp/src/requests/hover.rs b/noir/noir-repo/tooling/lsp/src/requests/hover.rs index 161fd20f5559..b6fdc6f7842c 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/hover.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/hover.rs @@ -1,17 +1,18 @@ use std::future::{self, Future}; use async_lsp::ResponseError; +use fm::FileMap; use lsp_types::{Hover, HoverContents, HoverParams, MarkupContent, MarkupKind}; use noirc_frontend::{ ast::Visibility, graph::CrateId, hir::def_map::ModuleId, - hir_def::stmt::HirPattern, + hir_def::{stmt::HirPattern, traits::Trait}, macros_api::{NodeInterner, StructId}, node_interner::{ DefinitionId, DefinitionKind, FuncId, GlobalId, ReferenceId, TraitId, TypeAliasId, }, - Generics, Type, + Generics, Shared, StructType, Type, TypeAlias, TypeBinding, TypeVariable, }; use crate::LspState; @@ -23,39 +24,45 @@ pub(crate) fn on_hover_request( params: HoverParams, ) -> impl Future, ResponseError>> { let result = process_request(state, params.text_document_position_params, |args| { - args.interner.reference_at_location(args.location).map(|reference| { + args.interner.reference_at_location(args.location).and_then(|reference| { let location = args.interner.reference_location(reference); let lsp_location = to_lsp_location(args.files, location.file, location.span); - Hover { + format_reference(reference, &args).map(|formatted| Hover { range: lsp_location.map(|location| location.range), contents: HoverContents::Markup(MarkupContent { kind: MarkupKind::Markdown, - value: format_reference(reference, &args), + value: formatted, }), - } + }) }) }); future::ready(result) } -fn format_reference(reference: ReferenceId, args: &ProcessRequestCallbackArgs) -> String { +fn format_reference(reference: ReferenceId, args: &ProcessRequestCallbackArgs) -> Option { match reference { ReferenceId::Module(id) => format_module(id, args), - ReferenceId::Struct(id) => format_struct(id, args), - ReferenceId::StructMember(id, field_index) => format_struct_member(id, field_index, args), - ReferenceId::Trait(id) => format_trait(id, args), - ReferenceId::Global(id) => format_global(id, args), - ReferenceId::Function(id) => format_function(id, args), - ReferenceId::Alias(id) => format_alias(id, args), - ReferenceId::Local(id) => format_local(id, args), + ReferenceId::Struct(id) => Some(format_struct(id, args)), + ReferenceId::StructMember(id, field_index) => { + Some(format_struct_member(id, field_index, args)) + } + ReferenceId::Trait(id) => Some(format_trait(id, args)), + ReferenceId::Global(id) => Some(format_global(id, args)), + ReferenceId::Function(id) => Some(format_function(id, args)), + ReferenceId::Alias(id) => Some(format_alias(id, args)), + ReferenceId::Local(id) => Some(format_local(id, args)), ReferenceId::Reference(location, _) => { format_reference(args.interner.find_referenced(location).unwrap(), args) } } } -fn format_module(id: ModuleId, args: &ProcessRequestCallbackArgs) -> String { - let module_attributes = args.interner.module_attributes(&id); +fn format_module(id: ModuleId, args: &ProcessRequestCallbackArgs) -> Option { + // Note: it's not clear why `try_module_attributes` might return None here, but it happens. + // This is a workaround to avoid panicking in that case (which brings the LSP server down). + // Cases where this happens are related to generated code, so once that stops happening + // this won't be an issue anymore. + let module_attributes = args.interner.try_module_attributes(&id)?; let mut string = String::new(); if format_parent_module_from_module_id( @@ -68,7 +75,7 @@ fn format_module(id: ModuleId, args: &ProcessRequestCallbackArgs) -> String { string.push_str(" "); string.push_str("mod "); string.push_str(&module_attributes.name); - string + Some(string) } fn format_struct(id: StructId, args: &ProcessRequestCallbackArgs) -> String { @@ -114,6 +121,7 @@ fn format_struct_member( string.push_str(&field_name.0.contents); string.push_str(": "); string.push_str(&format!("{}", field_type)); + string.push_str(&go_to_type_links(field_type, args.interner, args.files)); string } @@ -145,6 +153,7 @@ fn format_global(id: GlobalId, args: &ProcessRequestCallbackArgs) -> String { string.push_str(&global_info.ident.0.contents); string.push_str(": "); string.push_str(&format!("{}", typ)); + string.push_str(&go_to_type_links(&typ, args.interner, args.files)); string } @@ -200,6 +209,8 @@ fn format_function(id: FuncId, args: &ProcessRequestCallbackArgs) -> String { } } + string.push_str(&go_to_type_links(return_type, args.interner, args.files)); + string } @@ -244,6 +255,9 @@ fn format_local(id: DefinitionId, args: &ProcessRequestCallbackArgs) -> String { string.push_str(": "); string.push_str(&format!("{}", typ)); } + + string.push_str(&go_to_type_links(&typ, args.interner, args.files)); + string } @@ -307,9 +321,9 @@ fn format_parent_module_from_module_id( ) -> bool { let crate_id = module.krate; let crate_name = match crate_id { - CrateId::Root(_) => Some(args.root_crate_name.clone()), + CrateId::Root(_) => Some(args.crate_name.clone()), CrateId::Crate(_) => args - .root_crate_dependencies + .dependencies .iter() .find(|dep| dep.crate_id == crate_id) .map(|dep| format!("{}", dep.name)), @@ -355,6 +369,148 @@ fn format_parent_module_from_module_id( true } +fn go_to_type_links(typ: &Type, interner: &NodeInterner, files: &FileMap) -> String { + let mut gatherer = TypeLinksGatherer { interner, files, links: Vec::new() }; + gatherer.gather_type_links(typ); + + let links = gatherer.links; + if links.is_empty() { + "".to_string() + } else { + let mut string = String::new(); + string.push_str("\n\n"); + string.push_str("Go to "); + for (index, link) in links.iter().enumerate() { + if index > 0 { + string.push_str(" | "); + } + string.push_str(link); + } + string + } +} + +struct TypeLinksGatherer<'a> { + interner: &'a NodeInterner, + files: &'a FileMap, + links: Vec, +} + +impl<'a> TypeLinksGatherer<'a> { + fn gather_type_links(&mut self, typ: &Type) { + match typ { + Type::Array(typ, _) => self.gather_type_links(typ), + Type::Slice(typ) => self.gather_type_links(typ), + Type::Tuple(types) => { + for typ in types { + self.gather_type_links(typ); + } + } + Type::Struct(struct_type, generics) => { + self.gather_struct_type_links(struct_type); + for generic in generics { + self.gather_type_links(generic); + } + } + Type::Alias(type_alias, generics) => { + self.gather_type_alias_links(type_alias); + for generic in generics { + self.gather_type_links(generic); + } + } + Type::TypeVariable(var, _) => { + self.gather_type_variable_links(var); + } + Type::TraitAsType(trait_id, _, generics) => { + let some_trait = self.interner.get_trait(*trait_id); + self.gather_trait_links(some_trait); + for generic in generics { + self.gather_type_links(generic); + } + } + Type::NamedGeneric(var, _, _) => { + self.gather_type_variable_links(var); + } + Type::Function(args, return_type, env) => { + for arg in args { + self.gather_type_links(arg); + } + self.gather_type_links(return_type); + self.gather_type_links(env); + } + Type::MutableReference(typ) => self.gather_type_links(typ), + Type::InfixExpr(lhs, _, rhs) => { + self.gather_type_links(lhs); + self.gather_type_links(rhs); + } + Type::FieldElement + | Type::Integer(..) + | Type::Bool + | Type::String(_) + | Type::FmtString(_, _) + | Type::Unit + | Type::Forall(_, _) + | Type::Constant(_) + | Type::Quoted(_) + | Type::Error => (), + } + } + + fn gather_struct_type_links(&mut self, struct_type: &Shared) { + let struct_type = struct_type.borrow(); + if let Some(lsp_location) = + to_lsp_location(self.files, struct_type.location.file, struct_type.name.span()) + { + self.push_link(format_link(struct_type.name.to_string(), lsp_location)); + } + } + + fn gather_type_alias_links(&mut self, type_alias: &Shared) { + let type_alias = type_alias.borrow(); + if let Some(lsp_location) = + to_lsp_location(self.files, type_alias.location.file, type_alias.name.span()) + { + self.push_link(format_link(type_alias.name.to_string(), lsp_location)); + } + } + + fn gather_trait_links(&mut self, some_trait: &Trait) { + if let Some(lsp_location) = + to_lsp_location(self.files, some_trait.location.file, some_trait.name.span()) + { + self.push_link(format_link(some_trait.name.to_string(), lsp_location)); + } + } + + fn gather_type_variable_links(&mut self, var: &TypeVariable) { + let var = &*var.borrow(); + match var { + TypeBinding::Bound(typ) => { + self.gather_type_links(typ); + } + TypeBinding::Unbound(..) => (), + } + } + + fn push_link(&mut self, link: String) { + if !self.links.contains(&link) { + self.links.push(link); + } + } +} + +fn format_link(name: String, location: lsp_types::Location) -> String { + format!( + "[{}]({}#L{},{}-{},{})", + name, + location.uri, + location.range.start.line + 1, + location.range.start.character + 1, + location.range.end.line + 1, + location.range.end.character + 1 + ) +} + #[cfg(test)] mod hover_tests { use crate::test_utils; @@ -529,6 +685,24 @@ mod hover_tests { .await; } + #[test] + async fn hover_on_local_var_whose_type_you_can_navigate_to() { + let workspace_on_src_lib_path = std::env::current_dir() + .unwrap() + .join("test_programs/workspace/one/src/lib.nr") + .canonicalize() + .expect("Could not resolve root path"); + let workspace_on_src_lib_path = workspace_on_src_lib_path.to_string_lossy(); + + assert_hover( + "workspace", + "two/src/lib.nr", + Position { line: 51, character: 8 }, + &format!(" let x: BoundedVec\n\nGo to [SubOneStruct](file://{}#L4,12-4,24)", workspace_on_src_lib_path), + ) + .await; + } + #[test] async fn hover_on_parameter() { assert_hover( 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 2afa5fa44fdb..8c3d8a056525 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/inlay_hint.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/inlay_hint.rs @@ -1,11 +1,10 @@ -use fm::codespan_files::Files; use std::future::{self, Future}; use async_lsp::ResponseError; use fm::{FileId, FileMap, PathString}; use lsp_types::{ - InlayHint, InlayHintKind, InlayHintLabel, InlayHintLabelPart, InlayHintParams, Position, - TextDocumentPositionParams, + InlayHint, InlayHintKind, InlayHintLabel, InlayHintLabelPart, InlayHintParams, Position, Range, + TextDocumentPositionParams, TextEdit, }; use noirc_errors::{Location, Span}; use noirc_frontend::{ @@ -21,7 +20,7 @@ use noirc_frontend::{ ParsedModule, Type, TypeBinding, TypeVariable, TypeVariableKind, }; -use crate::LspState; +use crate::{utils, LspState}; use super::{process_request, to_lsp_location, InlayHintsOptions}; @@ -43,7 +42,7 @@ pub(crate) fn on_inlay_hint_request( let source = file.source(); let (parsed_moduled, _errors) = noirc_frontend::parse_program(source); - let span = range_to_byte_span(args.files, file_id, ¶ms.range) + let span = utils::range_to_byte_span(args.files, file_id, ¶ms.range) .map(|range| Span::from(range.start as u32..range.end as u32)); let mut collector = @@ -86,25 +85,46 @@ impl<'a> InlayHintCollector<'a> { } match &item.kind { - ItemKind::Function(noir_function) => self.collect_in_noir_function(noir_function), + ItemKind::Function(noir_function) => { + self.collect_in_noir_function(noir_function, item.span); + } ItemKind::Trait(noir_trait) => { for item in &noir_trait.items { self.collect_in_trait_item(item); } } ItemKind::TraitImpl(noir_trait_impl) => { - for item in &noir_trait_impl.items { - self.collect_in_trait_impl_item(item); + for trait_impl_item in &noir_trait_impl.items { + self.collect_in_trait_impl_item(trait_impl_item, item.span); } + + self.show_closing_brace_hint(item.span, || { + format!( + " impl {} for {}", + noir_trait_impl.trait_name, noir_trait_impl.object_type + ) + }); } ItemKind::Impl(type_impl) => { - for (noir_function, _) in &type_impl.methods { - self.collect_in_noir_function(noir_function); + for (noir_function, span) in &type_impl.methods { + self.collect_in_noir_function(noir_function, *span); } + + self.show_closing_brace_hint(item.span, || { + format!(" impl {}", type_impl.object_type) + }); } ItemKind::Global(let_statement) => self.collect_in_let_statement(let_statement), ItemKind::Submodules(parsed_submodule) => { self.collect_in_parsed_module(&parsed_submodule.contents); + + self.show_closing_brace_hint(item.span, || { + if parsed_submodule.is_contract { + format!(" contract {}", parsed_submodule.name) + } else { + format!(" mod {}", parsed_submodule.name) + } + }); } ItemKind::ModuleDecl(_) => (), ItemKind::Import(_) => (), @@ -129,9 +149,11 @@ impl<'a> InlayHintCollector<'a> { } } - fn collect_in_trait_impl_item(&mut self, item: &TraitImplItem) { + fn collect_in_trait_impl_item(&mut self, item: &TraitImplItem, span: Span) { match item { - TraitImplItem::Function(noir_function) => self.collect_in_noir_function(noir_function), + TraitImplItem::Function(noir_function) => { + self.collect_in_noir_function(noir_function, span); + } TraitImplItem::Constant(_name, _typ, default_value) => { self.collect_in_expression(default_value); } @@ -139,8 +161,10 @@ impl<'a> InlayHintCollector<'a> { } } - fn collect_in_noir_function(&mut self, noir_function: &NoirFunction) { + fn collect_in_noir_function(&mut self, noir_function: &NoirFunction, span: Span) { self.collect_in_block_expression(&noir_function.def.body); + + self.show_closing_brace_hint(span, || format!(" fn {}", noir_function.def.name)); } fn collect_in_let_statement(&mut self, let_statement: &LetStatement) { @@ -173,7 +197,7 @@ impl<'a> InlayHintCollector<'a> { self.collect_in_expression(&assign_statement.expression); } StatementKind::For(for_loop_statement) => { - self.collect_in_ident(&for_loop_statement.identifier); + self.collect_in_ident(&for_loop_statement.identifier, false); self.collect_in_expression(&for_loop_statement.block); } StatementKind::Comptime(statement) => self.collect_in_statement(statement), @@ -251,7 +275,15 @@ impl<'a> InlayHintCollector<'a> { self.collect_in_expression(expression); } } - ExpressionKind::Lambda(lambda) => self.collect_in_expression(&lambda.body), + ExpressionKind::Lambda(lambda) => { + for (pattern, typ) in &lambda.parameters { + if matches!(typ.typ, UnresolvedTypeData::Unspecified) { + self.collect_in_pattern(pattern); + } + } + + self.collect_in_expression(&lambda.body); + } ExpressionKind::Parenthesized(parenthesized) => { self.collect_in_expression(parenthesized); } @@ -261,6 +293,9 @@ impl<'a> InlayHintCollector<'a> { ExpressionKind::Comptime(block_expression, _span) => { self.collect_in_block_expression(block_expression); } + ExpressionKind::AsTraitPath(path) => { + self.collect_in_ident(&path.impl_item, true); + } ExpressionKind::Literal(..) | ExpressionKind::Variable(..) | ExpressionKind::Quote(..) @@ -276,7 +311,7 @@ impl<'a> InlayHintCollector<'a> { match pattern { Pattern::Identifier(ident) => { - self.collect_in_ident(ident); + self.collect_in_ident(ident, true); } Pattern::Mutable(pattern, _span, _is_synthesized) => { self.collect_in_pattern(pattern); @@ -294,7 +329,7 @@ impl<'a> InlayHintCollector<'a> { } } - fn collect_in_ident(&mut self, ident: &Ident) { + fn collect_in_ident(&mut self, ident: &Ident, editable: bool) { if !self.options.type_hints.enabled { return; } @@ -308,17 +343,17 @@ impl<'a> InlayHintCollector<'a> { let global_info = self.interner.get_global(global_id); let definition_id = global_info.definition_id; let typ = self.interner.definition_type(definition_id); - self.push_type_hint(lsp_location, &typ); + self.push_type_hint(lsp_location, &typ, editable); } ReferenceId::Local(definition_id) => { let typ = self.interner.definition_type(definition_id); - self.push_type_hint(lsp_location, &typ); + self.push_type_hint(lsp_location, &typ, editable); } ReferenceId::StructMember(struct_id, field_index) => { let struct_type = self.interner.get_struct(struct_id); let struct_type = struct_type.borrow(); let (_field_name, field_type) = struct_type.field_at(field_index); - self.push_type_hint(lsp_location, field_type); + self.push_type_hint(lsp_location, field_type, false); } ReferenceId::Module(_) | ReferenceId::Struct(_) @@ -331,7 +366,7 @@ impl<'a> InlayHintCollector<'a> { } } - fn push_type_hint(&mut self, location: lsp_types::Location, typ: &Type) { + fn push_type_hint(&mut self, location: lsp_types::Location, typ: &Type, editable: bool) { let position = location.range.end; let mut parts = Vec::new(); @@ -342,7 +377,14 @@ impl<'a> InlayHintCollector<'a> { position, label: InlayHintLabel::LabelParts(parts), kind: Some(InlayHintKind::TYPE), - text_edits: None, + text_edits: if editable { + Some(vec![TextEdit { + range: Range { start: location.range.end, end: location.range.end }, + new_text: format!(": {}", typ), + }]) + } else { + None + }, tooltip: None, padding_left: None, padding_right: None, @@ -464,6 +506,20 @@ impl<'a> InlayHintCollector<'a> { fn intersects_span(&self, other_span: Span) -> bool { self.span.map_or(true, |span| span.intersects(&other_span)) } + + fn show_closing_brace_hint(&mut self, span: Span, f: F) + where + F: FnOnce() -> String, + { + if self.options.closing_brace_hints.enabled { + if let Some(lsp_location) = to_lsp_location(self.files, self.file_id, span) { + let lines = lsp_location.range.end.line - lsp_location.range.start.line + 1; + if lines >= self.options.closing_brace_hints.min_lines { + self.push_text_hint(lsp_location.range.end, f()); + } + } + } + } } fn string_part(str: impl Into) -> InlayHintLabelPart { @@ -584,6 +640,7 @@ fn push_type_parts(typ: &Type, parts: &mut Vec, files: &File | Type::NamedGeneric(..) | Type::Forall(..) | Type::Constant(..) + | Type::InfixExpr(..) | Type::Quoted(..) | Type::Error => { parts.push(string_part(typ.to_string())); @@ -609,13 +666,14 @@ fn push_type_variable_parts( fn get_expression_name(expression: &Expression) -> Option { match &expression.kind { - ExpressionKind::Variable(path, _) => Some(path.last_segment().to_string()), + ExpressionKind::Variable(path) => Some(path.last_name().to_string()), ExpressionKind::Prefix(prefix) => get_expression_name(&prefix.rhs), ExpressionKind::MemberAccess(member_access) => Some(member_access.rhs.to_string()), ExpressionKind::Call(call) => get_expression_name(&call.func), ExpressionKind::MethodCall(method_call) => Some(method_call.method_name.to_string()), ExpressionKind::Cast(cast) => get_expression_name(&cast.lhs), ExpressionKind::Parenthesized(expr) => get_expression_name(expr), + ExpressionKind::AsTraitPath(path) => Some(path.impl_item.to_string()), ExpressionKind::Constructor(..) | ExpressionKind::Infix(..) | ExpressionKind::Index(..) @@ -632,67 +690,10 @@ fn get_expression_name(expression: &Expression) -> Option { } } -// These functions are copied from the codespan_lsp crate, except that they never panic -// (the library will sometimes panic, so functions returning Result are not always accurate) - -fn range_to_byte_span( - files: &FileMap, - file_id: FileId, - range: &lsp_types::Range, -) -> Option> { - Some( - position_to_byte_index(files, file_id, &range.start)? - ..position_to_byte_index(files, file_id, &range.end)?, - ) -} - -fn position_to_byte_index( - files: &FileMap, - file_id: FileId, - position: &lsp_types::Position, -) -> Option { - let Ok(source) = files.source(file_id) else { - return None; - }; - - let Ok(line_span) = files.line_range(file_id, position.line as usize) else { - return None; - }; - let line_str = source.get(line_span.clone())?; - - let byte_offset = character_to_line_offset(line_str, position.character)?; - - Some(line_span.start + byte_offset) -} - -fn character_to_line_offset(line: &str, character: u32) -> Option { - let line_len = line.len(); - let mut character_offset = 0; - - let mut chars = line.chars(); - while let Some(ch) = chars.next() { - if character_offset == character { - let chars_off = chars.as_str().len(); - let ch_off = ch.len_utf8(); - - return Some(line_len - chars_off - ch_off); - } - - character_offset += ch.len_utf16() as u32; - } - - // Handle positions after the last character on the line - if character_offset == character { - Some(line_len) - } else { - None - } -} - #[cfg(test)] mod inlay_hints_tests { use crate::{ - requests::{ParameterHintsOptions, TypeHintsOptions}, + requests::{ClosingBraceHintsOptions, ParameterHintsOptions, TypeHintsOptions}, test_utils, }; @@ -728,6 +729,7 @@ mod inlay_hints_tests { InlayHintsOptions { type_hints: TypeHintsOptions { enabled: false }, parameter_hints: ParameterHintsOptions { enabled: false }, + closing_brace_hints: ClosingBraceHintsOptions { enabled: false, min_lines: 25 }, } } @@ -735,6 +737,7 @@ mod inlay_hints_tests { InlayHintsOptions { type_hints: TypeHintsOptions { enabled: true }, parameter_hints: ParameterHintsOptions { enabled: false }, + closing_brace_hints: ClosingBraceHintsOptions { enabled: false, min_lines: 25 }, } } @@ -742,6 +745,15 @@ mod inlay_hints_tests { InlayHintsOptions { type_hints: TypeHintsOptions { enabled: false }, parameter_hints: ParameterHintsOptions { enabled: true }, + closing_brace_hints: ClosingBraceHintsOptions { enabled: false, min_lines: 25 }, + } + } + + fn closing_braces_hints(min_lines: u32) -> InlayHintsOptions { + InlayHintsOptions { + type_hints: TypeHintsOptions { enabled: false }, + parameter_hints: ParameterHintsOptions { enabled: false }, + closing_brace_hints: ClosingBraceHintsOptions { enabled: true, min_lines }, } } @@ -756,8 +768,10 @@ mod inlay_hints_tests { let inlay_hints = get_inlay_hints(0, 3, type_hints()).await; assert_eq!(inlay_hints.len(), 1); + let position = Position { line: 1, character: 11 }; + let inlay_hint = &inlay_hints[0]; - assert_eq!(inlay_hint.position, Position { line: 1, character: 11 }); + assert_eq!(inlay_hint.position, position); if let InlayHintLabel::LabelParts(labels) = &inlay_hint.label { assert_eq!(labels.len(), 2); @@ -770,6 +784,14 @@ mod inlay_hints_tests { } else { panic!("Expected InlayHintLabel::LabelParts, got {:?}", inlay_hint.label); } + + assert_eq!( + inlay_hint.text_edits, + Some(vec![TextEdit { + range: Range { start: position, end: position }, + new_text: ": Field".to_string(), + }]) + ); } #[test] @@ -777,8 +799,10 @@ mod inlay_hints_tests { let inlay_hints = get_inlay_hints(12, 15, type_hints()).await; assert_eq!(inlay_hints.len(), 1); + let position = Position { line: 13, character: 11 }; + let inlay_hint = &inlay_hints[0]; - assert_eq!(inlay_hint.position, Position { line: 13, character: 11 }); + assert_eq!(inlay_hint.position, position); if let InlayHintLabel::LabelParts(labels) = &inlay_hint.label { assert_eq!(labels.len(), 2); @@ -798,6 +822,34 @@ mod inlay_hints_tests { } else { panic!("Expected InlayHintLabel::LabelParts, got {:?}", inlay_hint.label); } + + assert_eq!( + inlay_hint.text_edits, + Some(vec![TextEdit { + range: Range { start: position, end: position }, + new_text: ": Foo".to_string(), + }]) + ); + } + + #[test] + async fn test_type_inlay_hints_in_struct_member_pattern() { + let inlay_hints = get_inlay_hints(94, 96, type_hints()).await; + assert_eq!(inlay_hints.len(), 1); + + let inlay_hint = &inlay_hints[0]; + assert_eq!(inlay_hint.position, Position { line: 95, character: 24 }); + + if let InlayHintLabel::LabelParts(labels) = &inlay_hint.label { + assert_eq!(labels.len(), 2); + assert_eq!(labels[0].value, ": "); + assert_eq!(labels[0].location, None); + assert_eq!(labels[1].value, "i32"); + } else { + panic!("Expected InlayHintLabel::LabelParts, got {:?}", inlay_hint.label); + } + + assert_eq!(inlay_hint.text_edits, None); } #[test] @@ -816,6 +868,8 @@ mod inlay_hints_tests { } else { panic!("Expected InlayHintLabel::LabelParts, got {:?}", inlay_hint.label); } + + assert_eq!(inlay_hint.text_edits, None); } #[test] @@ -823,8 +877,10 @@ mod inlay_hints_tests { let inlay_hints = get_inlay_hints(19, 21, type_hints()).await; assert_eq!(inlay_hints.len(), 1); + let position = Position { line: 20, character: 10 }; + let inlay_hint = &inlay_hints[0]; - assert_eq!(inlay_hint.position, Position { line: 20, character: 10 }); + assert_eq!(inlay_hint.position, position); if let InlayHintLabel::LabelParts(labels) = &inlay_hint.label { assert_eq!(labels.len(), 2); @@ -834,6 +890,42 @@ mod inlay_hints_tests { } else { panic!("Expected InlayHintLabel::LabelParts, got {:?}", inlay_hint.label); } + + assert_eq!( + inlay_hint.text_edits, + Some(vec![TextEdit { + range: Range { start: position, end: position }, + new_text: ": Field".to_string(), + }]) + ); + } + + #[test] + async fn test_type_inlay_hints_in_lambda() { + let inlay_hints = get_inlay_hints(102, 105, type_hints()).await; + assert_eq!(inlay_hints.len(), 1); + + let position = Position { line: 104, character: 35 }; + + let inlay_hint = &inlay_hints[0]; + assert_eq!(inlay_hint.position, position); + + if let InlayHintLabel::LabelParts(labels) = &inlay_hint.label { + assert_eq!(labels.len(), 2); + assert_eq!(labels[0].value, ": "); + assert_eq!(labels[0].location, None); + assert_eq!(labels[1].value, "i32"); + } else { + panic!("Expected InlayHintLabel::LabelParts, got {:?}", inlay_hint.label); + } + + assert_eq!( + inlay_hint.text_edits, + Some(vec![TextEdit { + range: Range { start: position, end: position }, + new_text: ": i32".to_string(), + }]) + ); } #[test] @@ -855,6 +947,7 @@ mod inlay_hints_tests { let inlay_hint = &inlay_hints[0]; assert_eq!(inlay_hint.position, Position { line: 25, character: 12 }); + assert_eq!(inlay_hint.text_edits, None); if let InlayHintLabel::String(label) = &inlay_hint.label { assert_eq!(label, "one: "); } else { @@ -863,6 +956,7 @@ mod inlay_hints_tests { let inlay_hint = &inlay_hints[1]; assert_eq!(inlay_hint.position, Position { line: 25, character: 15 }); + assert_eq!(inlay_hint.text_edits, None); if let InlayHintLabel::String(label) = &inlay_hint.label { assert_eq!(label, "two: "); } else { @@ -877,6 +971,7 @@ mod inlay_hints_tests { let inlay_hint = &inlay_hints[0]; assert_eq!(inlay_hint.position, Position { line: 38, character: 18 }); + assert_eq!(inlay_hint.text_edits, None); if let InlayHintLabel::String(label) = &inlay_hint.label { assert_eq!(label, "one: "); } else { @@ -926,4 +1021,91 @@ mod inlay_hints_tests { let inlay_hints = get_inlay_hints(89, 92, parameter_hints()).await; assert!(inlay_hints.is_empty()); } + + #[test] + async fn test_does_not_show_closing_brace_inlay_hints_if_disabled() { + let inlay_hints = get_inlay_hints(41, 46, no_hints()).await; + assert!(inlay_hints.is_empty()); + } + + #[test] + async fn test_does_not_show_closing_brace_inlay_hints_if_enabled_but_not_lines() { + let inlay_hints = get_inlay_hints(41, 46, closing_braces_hints(6)).await; + assert!(inlay_hints.is_empty()); + } + + #[test] + async fn test_shows_closing_brace_inlay_hints_for_a_function() { + let inlay_hints = get_inlay_hints(41, 46, closing_braces_hints(5)).await; + assert_eq!(inlay_hints.len(), 1); + + let inlay_hint = &inlay_hints[0]; + assert_eq!(inlay_hint.position, Position { line: 45, character: 1 }); + assert_eq!(inlay_hint.text_edits, None); + if let InlayHintLabel::String(label) = &inlay_hint.label { + assert_eq!(label, " fn call_where_name_matches"); + } else { + panic!("Expected InlayHintLabel::String, got {:?}", inlay_hint.label); + } + } + + #[test] + async fn test_shows_closing_brace_inlay_hints_for_impl() { + let inlay_hints = get_inlay_hints(32, 34, closing_braces_hints(2)).await; + assert_eq!(inlay_hints.len(), 1); + + let inlay_hint = &inlay_hints[0]; + assert_eq!(inlay_hint.position, Position { line: 34, character: 1 }); + assert_eq!(inlay_hint.text_edits, None); + if let InlayHintLabel::String(label) = &inlay_hint.label { + assert_eq!(label, " impl SomeStruct"); + } else { + panic!("Expected InlayHintLabel::String, got {:?}", inlay_hint.label); + } + } + + #[test] + async fn test_shows_closing_brace_inlay_hints_for_trait_impl() { + let inlay_hints = get_inlay_hints(111, 113, closing_braces_hints(2)).await; + assert_eq!(inlay_hints.len(), 1); + + let inlay_hint = &inlay_hints[0]; + assert_eq!(inlay_hint.position, Position { line: 113, character: 1 }); + assert_eq!(inlay_hint.text_edits, None); + if let InlayHintLabel::String(label) = &inlay_hint.label { + assert_eq!(label, " impl SomeTrait for SomeStruct"); + } else { + panic!("Expected InlayHintLabel::String, got {:?}", inlay_hint.label); + } + } + + #[test] + async fn test_shows_closing_brace_inlay_hints_for_module() { + let inlay_hints = get_inlay_hints(115, 117, closing_braces_hints(2)).await; + assert_eq!(inlay_hints.len(), 1); + + let inlay_hint = &inlay_hints[0]; + assert_eq!(inlay_hint.position, Position { line: 117, character: 1 }); + assert_eq!(inlay_hint.text_edits, None); + if let InlayHintLabel::String(label) = &inlay_hint.label { + assert_eq!(label, " mod some_module"); + } else { + panic!("Expected InlayHintLabel::String, got {:?}", inlay_hint.label); + } + } + + #[test] + async fn test_shows_closing_brace_inlay_hints_for_contract() { + let inlay_hints = get_inlay_hints(119, 121, closing_braces_hints(2)).await; + assert_eq!(inlay_hints.len(), 1); + + let inlay_hint = &inlay_hints[0]; + assert_eq!(inlay_hint.position, Position { line: 121, character: 1 }); + assert_eq!(inlay_hint.text_edits, None); + if let InlayHintLabel::String(label) = &inlay_hint.label { + assert_eq!(label, " contract some_contract"); + } else { + panic!("Expected InlayHintLabel::String, got {:?}", inlay_hint.label); + } + } } diff --git a/noir/noir-repo/tooling/lsp/src/requests/mod.rs b/noir/noir-repo/tooling/lsp/src/requests/mod.rs index 4d261c1b50a2..e138f8396001 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/mod.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/mod.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeMap; +use std::path::PathBuf; use std::{collections::HashMap, future::Future}; use crate::insert_all_files_for_workspace_into_file_manager; @@ -14,6 +16,8 @@ use lsp_types::{ }; use nargo_fmt::Config; use noirc_driver::file_manager_with_stdlib; +use noirc_frontend::graph::CrateId; +use noirc_frontend::hir::def_map::CrateDefMap; use noirc_frontend::{graph::Dependency, macros_api::NodeInterner}; use serde::{Deserialize, Serialize}; @@ -33,6 +37,7 @@ use crate::{ // and params passed in. mod code_lens_request; +mod completion; mod document_symbol; mod goto_declaration; mod goto_definition; @@ -46,12 +51,12 @@ mod tests; pub(crate) use { code_lens_request::collect_lenses_for_package, code_lens_request::on_code_lens_request, - document_symbol::on_document_symbol_request, goto_declaration::on_goto_declaration_request, - goto_definition::on_goto_definition_request, goto_definition::on_goto_type_definition_request, - hover::on_hover_request, inlay_hint::on_inlay_hint_request, - profile_run::on_profile_run_request, references::on_references_request, - rename::on_prepare_rename_request, rename::on_rename_request, test_run::on_test_run_request, - tests::on_tests_request, + completion::on_completion_request, document_symbol::on_document_symbol_request, + goto_declaration::on_goto_declaration_request, goto_definition::on_goto_definition_request, + goto_definition::on_goto_type_definition_request, hover::on_hover_request, + inlay_hint::on_inlay_hint_request, profile_run::on_profile_run_request, + references::on_references_request, rename::on_prepare_rename_request, + rename::on_rename_request, test_run::on_test_run_request, tests::on_tests_request, }; /// LSP client will send initialization request after the server has started. @@ -77,6 +82,9 @@ pub(crate) struct InlayHintsOptions { #[serde(rename = "parameterHints", default = "default_parameter_hints")] pub(crate) parameter_hints: ParameterHintsOptions, + + #[serde(rename = "closingBraceHints", default = "default_closing_brace_hints")] + pub(crate) closing_brace_hints: ClosingBraceHintsOptions, } #[derive(Debug, Deserialize, Serialize, Copy, Clone)] @@ -91,6 +99,15 @@ pub(crate) struct ParameterHintsOptions { pub(crate) enabled: bool, } +#[derive(Debug, Deserialize, Serialize, Copy, Clone)] +pub(crate) struct ClosingBraceHintsOptions { + #[serde(rename = "enabled", default = "default_closing_brace_hints_enabled")] + pub(crate) enabled: bool, + + #[serde(rename = "minLines", default = "default_closing_brace_min_lines")] + pub(crate) min_lines: u32, +} + fn default_enable_code_lens() -> bool { true } @@ -103,6 +120,7 @@ fn default_inlay_hints() -> InlayHintsOptions { InlayHintsOptions { type_hints: default_type_hints(), parameter_hints: default_parameter_hints(), + closing_brace_hints: default_closing_brace_hints(), } } @@ -122,6 +140,21 @@ fn default_parameter_hints_enabled() -> bool { true } +fn default_closing_brace_hints() -> ClosingBraceHintsOptions { + ClosingBraceHintsOptions { + enabled: default_closing_brace_hints_enabled(), + min_lines: default_closing_brace_min_lines(), + } +} + +fn default_closing_brace_hints_enabled() -> bool { + true +} + +fn default_closing_brace_min_lines() -> u32 { + 25 +} + impl Default for LspInitializationOptions { fn default() -> Self { Self { @@ -199,6 +232,15 @@ pub(crate) fn on_initialize( label: Some("Noir".to_string()), }, )), + completion_provider: Some(lsp_types::OneOf::Right(lsp_types::CompletionOptions { + resolve_provider: None, + trigger_characters: Some(vec![":".to_string()]), + all_commit_characters: None, + work_done_progress_options: WorkDoneProgressOptions { + work_done_progress: None, + }, + completion_item: None, + })), }, server_info: None, }) @@ -324,7 +366,12 @@ where let file_name = files.name(file_id).ok()?; let path = file_name.to_string(); - let uri = Url::from_file_path(path).ok()?; + + // `path` might be a relative path so we canonicalize it to get an absolute path + let path_buf = PathBuf::from(path); + let path_buf = path_buf.canonicalize().unwrap_or(path_buf); + + let uri = Url::from_file_path(path_buf.to_str()?).ok()?; Some(Location { uri, range }) } @@ -341,8 +388,10 @@ pub(crate) struct ProcessRequestCallbackArgs<'a> { files: &'a FileMap, interner: &'a NodeInterner, interners: &'a HashMap, - root_crate_name: String, - root_crate_dependencies: &'a Vec, + crate_id: CrateId, + crate_name: String, + dependencies: &'a Vec, + def_maps: &'a BTreeMap, } pub(crate) fn process_request( @@ -358,8 +407,7 @@ where ResponseError::new(ErrorCode::REQUEST_FAILED, "URI is not a valid file path") })?; - let workspace = - resolve_workspace_for_source_path(file_path.as_path(), &state.root_path).unwrap(); + let workspace = resolve_workspace_for_source_path(file_path.as_path()).unwrap(); let package = crate::workspace_package_for_file(&workspace, &file_path).ok_or_else(|| { ResponseError::new(ErrorCode::REQUEST_FAILED, "Could not find package for file") })?; @@ -378,12 +426,15 @@ where crate::prepare_package(&workspace_file_manager, &parsed_files, package); let interner; + let def_maps; if let Some(def_interner) = state.cached_definitions.get(&package_root_path) { interner = def_interner; + def_maps = state.cached_def_maps.get(&package_root_path).unwrap(); } else { // We ignore the warnings and errors produced by compilation while resolving the definition - let _ = noirc_driver::check_crate(&mut context, crate_id, false, false, None); + let _ = noirc_driver::check_crate(&mut context, crate_id, &Default::default()); interner = &context.def_interner; + def_maps = &context.def_maps; } let files = context.file_manager.as_file_map(); @@ -399,8 +450,10 @@ where files, interner, interners: &state.cached_definitions, - root_crate_name: package.name.to_string(), - root_crate_dependencies: &context.crate_graph[context.root_crate_id()].dependencies, + crate_id, + crate_name: package.name.to_string(), + dependencies: &context.crate_graph[context.root_crate_id()].dependencies, + def_maps, })) } pub(crate) fn find_all_references_in_workspace( diff --git a/noir/noir-repo/tooling/lsp/src/requests/references.rs b/noir/noir-repo/tooling/lsp/src/requests/references.rs index 375e0b69aedc..c720156659d0 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/references.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/references.rs @@ -94,6 +94,10 @@ mod references_tests { check_references_succeeds("rename_function", "another_function", 0, false).await; } + // Ignored because making this work slows down everything, so for now things will not work + // as ideally, but they'll be fast. + // See https://github.com/noir-lang/noir/issues/5460 + #[ignore] #[test] async fn test_on_references_request_works_accross_workspace_packages() { let (mut state, noir_text_document) = test_utils::init_lsp_server("workspace").await; @@ -108,13 +112,11 @@ mod references_tests { let two_lib = Url::from_file_path(workspace_dir.join("two/src/lib.nr")).unwrap(); // We call this to open the document, so that the entire workspace is analyzed - let only_process_document_uri_package = false; let output_diagnostics = true; notifications::process_workspace_for_noir_document( &mut state, one_lib.clone(), - only_process_document_uri_package, output_diagnostics, ) .unwrap(); diff --git a/noir/noir-repo/tooling/lsp/src/requests/test_run.rs b/noir/noir-repo/tooling/lsp/src/requests/test_run.rs index bf4d9763faf9..fc4054633e28 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/test_run.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/test_run.rs @@ -61,7 +61,7 @@ fn on_test_run_request_inner( Some(package) => { let (mut context, crate_id) = crate::prepare_package(&workspace_file_manager, &parsed_files, package); - if check_crate(&mut context, crate_id, false, false, None).is_err() { + if check_crate(&mut context, crate_id, &Default::default()).is_err() { let result = NargoTestRunResult { id: params.id.clone(), result: "error".to_string(), diff --git a/noir/noir-repo/tooling/lsp/src/requests/tests.rs b/noir/noir-repo/tooling/lsp/src/requests/tests.rs index 20b960296965..7203aca7f090 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/tests.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/tests.rs @@ -65,7 +65,7 @@ fn on_tests_request_inner( crate::prepare_package(&workspace_file_manager, &parsed_files, package); // We ignore the warnings and errors produced by compilation for producing tests // because we can still get the test functions even if compilation fails - let _ = check_crate(&mut context, crate_id, false, false, None); + let _ = check_crate(&mut context, crate_id, &Default::default()); // We don't add test headings for a package if it contains no `#[test]` functions get_package_tests_in_crate(&context, &crate_id, &package.name) diff --git a/noir/noir-repo/tooling/lsp/src/types.rs b/noir/noir-repo/tooling/lsp/src/types.rs index fa3234cf3bb2..5afda0d292ae 100644 --- a/noir/noir-repo/tooling/lsp/src/types.rs +++ b/noir/noir-repo/tooling/lsp/src/types.rs @@ -1,7 +1,8 @@ use fm::FileId; use lsp_types::{ - DeclarationCapability, DefinitionOptions, DocumentSymbolOptions, HoverOptions, - InlayHintOptions, OneOf, ReferencesOptions, RenameOptions, TypeDefinitionProviderCapability, + CompletionOptions, DeclarationCapability, DefinitionOptions, DocumentSymbolOptions, + HoverOptions, InlayHintOptions, OneOf, ReferencesOptions, RenameOptions, + TypeDefinitionProviderCapability, }; use noirc_driver::DebugFile; use noirc_errors::{debug_info::OpCodesCount, Location}; @@ -156,6 +157,10 @@ pub(crate) struct ServerCapabilities { /// The server provides document symbol support. #[serde(skip_serializing_if = "Option::is_none")] pub(crate) document_symbol_provider: Option>, + + /// The server provides completion support. + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) completion_provider: Option>, } #[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)] diff --git a/noir/noir-repo/tooling/lsp/src/utils.rs b/noir/noir-repo/tooling/lsp/src/utils.rs new file mode 100644 index 000000000000..96db1f7bfa23 --- /dev/null +++ b/noir/noir-repo/tooling/lsp/src/utils.rs @@ -0,0 +1,59 @@ +// These functions are copied from the codespan_lsp crate, except that they never panic +// (the library will sometimes panic, so functions returning Result are not always accurate) + +use fm::codespan_files::Files; +use fm::{FileId, FileMap}; + +pub(crate) fn range_to_byte_span( + files: &FileMap, + file_id: FileId, + range: &lsp_types::Range, +) -> Option> { + Some( + position_to_byte_index(files, file_id, &range.start)? + ..position_to_byte_index(files, file_id, &range.end)?, + ) +} + +pub(crate) fn position_to_byte_index( + files: &FileMap, + file_id: FileId, + position: &lsp_types::Position, +) -> Option { + let Ok(source) = files.source(file_id) else { + return None; + }; + + let Ok(line_span) = files.line_range(file_id, position.line as usize) else { + return None; + }; + let line_str = source.get(line_span.clone())?; + + let byte_offset = character_to_line_offset(line_str, position.character)?; + + Some(line_span.start + byte_offset) +} + +pub(crate) fn character_to_line_offset(line: &str, character: u32) -> Option { + let line_len = line.len(); + let mut character_offset = 0; + + let mut chars = line.chars(); + while let Some(ch) = chars.next() { + if character_offset == character { + let chars_off = chars.as_str().len(); + let ch_off = ch.len_utf8(); + + return Some(line_len - chars_off - ch_off); + } + + character_offset += ch.len_utf16() as u32; + } + + // Handle positions after the last character on the line + if character_offset == character { + Some(line_len) + } else { + None + } +} diff --git a/noir/noir-repo/tooling/lsp/test_programs/inlay_hints/src/main.nr b/noir/noir-repo/tooling/lsp/test_programs/inlay_hints/src/main.nr index 2b53f8de339b..46a6d3bc558e 100644 --- a/noir/noir-repo/tooling/lsp/test_programs/inlay_hints/src/main.nr +++ b/noir/noir-repo/tooling/lsp/test_programs/inlay_hints/src/main.nr @@ -92,3 +92,31 @@ fn call_yet_another_function() { yet_another_function(some_name) // Should not show parameter names ("name" is a suffix of "some_name") } +fn struct_member_hint() { + let SomeStruct { one } = SomeStruct { one: 1 }; +} + +fn some_map(x: T, f: fn(T) -> U) -> U { + f(x) +} + +fn hint_on_lambda_parameter() { + let value: i32 = 1; + let _: i32 = some_map(value, |x| x + 1); +} + +trait SomeTrait { + +} + +impl SomeTrait for SomeStruct { + +} + +mod some_module { + +} + +contract some_contract { + +} diff --git a/noir/noir-repo/tooling/nargo/Cargo.toml b/noir/noir-repo/tooling/nargo/Cargo.toml index b0cf1cfcbb17..56e88dacf2d6 100644 --- a/noir/noir-repo/tooling/nargo/Cargo.toml +++ b/noir/noir-repo/tooling/nargo/Cargo.toml @@ -25,6 +25,10 @@ jsonrpc.workspace = true rand.workspace = true serde.workspace = true +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +noir_fuzzer.workspace = true +proptest.workspace = true + [dev-dependencies] # TODO: This dependency is used to generate unit tests for `get_all_paths_in_dir` # TODO: once that method is moved to nargo_cli, we can move this dependency to nargo_cli diff --git a/noir/noir-repo/tooling/nargo/src/errors.rs b/noir/noir-repo/tooling/nargo/src/errors.rs index b2248605cb53..86bb767b9fca 100644 --- a/noir/noir-repo/tooling/nargo/src/errors.rs +++ b/noir/noir-repo/tooling/nargo/src/errors.rs @@ -149,13 +149,34 @@ fn extract_locations_from_error( } } + let brillig_function_id = match error { + ExecutionError::SolvingError( + OpcodeResolutionError::BrilligFunctionFailed { function_id, .. }, + _, + ) => Some(*function_id), + _ => None, + }; + Some( opcode_locations .iter() .flat_map(|resolved_location| { debug[resolved_location.acir_function_index] .opcode_location(&resolved_location.opcode_location) - .unwrap_or_default() + .unwrap_or_else(|| { + if let Some(brillig_function_id) = brillig_function_id { + let brillig_locations = debug[resolved_location.acir_function_index] + .brillig_locations + .get(&brillig_function_id); + brillig_locations + .unwrap() + .get(&resolved_location.opcode_location) + .cloned() + .unwrap_or_default() + } else { + vec![] + } + }) }) .collect(), ) diff --git a/noir/noir-repo/tooling/nargo/src/ops/compile.rs b/noir/noir-repo/tooling/nargo/src/ops/compile.rs index d7c7cc2c123d..cd9ccf67957d 100644 --- a/noir/noir-repo/tooling/nargo/src/ops/compile.rs +++ b/noir/noir-repo/tooling/nargo/src/ops/compile.rs @@ -31,7 +31,9 @@ pub fn compile_workspace( // Compile all of the packages in parallel. let program_results: Vec> = binary_packages .par_iter() - .map(|package| compile_program(file_manager, parsed_files, package, compile_options, None)) + .map(|package| { + compile_program(file_manager, parsed_files, workspace, package, compile_options, None) + }) .collect(); let contract_results: Vec> = contract_packages .par_iter() @@ -57,6 +59,7 @@ pub fn compile_workspace( pub fn compile_program( file_manager: &FileManager, parsed_files: &ParsedFiles, + workspace: &Workspace, package: &Package, compile_options: &CompileOptions, cached_program: Option, @@ -64,6 +67,7 @@ pub fn compile_program( compile_program_with_debug_instrumenter( file_manager, parsed_files, + workspace, package, compile_options, cached_program, @@ -74,6 +78,7 @@ pub fn compile_program( pub fn compile_program_with_debug_instrumenter( file_manager: &FileManager, parsed_files: &ParsedFiles, + workspace: &Workspace, package: &Package, compile_options: &CompileOptions, cached_program: Option, @@ -82,6 +87,7 @@ pub fn compile_program_with_debug_instrumenter( let (mut context, crate_id) = prepare_package(file_manager, parsed_files, package); link_to_debug_crate(&mut context, crate_id); context.debug_instrumenter = debug_instrumenter; + context.package_build_path = workspace.package_build_path(package); noirc_driver::compile_main(&mut context, crate_id, compile_options, cached_program) } diff --git a/noir/noir-repo/tooling/nargo/src/ops/test.rs b/noir/noir-repo/tooling/nargo/src/ops/test.rs index 18c6f2530b96..0faae5ff5063 100644 --- a/noir/noir-repo/tooling/nargo/src/ops/test.rs +++ b/noir/noir-repo/tooling/nargo/src/ops/test.rs @@ -31,23 +31,75 @@ pub fn run_test>( foreign_call_resolver_url: Option<&str>, config: &CompileOptions, ) -> TestStatus { - let compiled_program = compile_no_check(context, config, test_function.get_id(), None, false); - match compiled_program { + let test_function_has_no_arguments = context + .def_interner + .function_meta(&test_function.get_id()) + .function_signature() + .0 + .is_empty(); + + match compile_no_check(context, config, test_function.get_id(), None, false) { Ok(compiled_program) => { - // Run the backend to ensure the PWG evaluates functions like std::hash::pedersen, - // otherwise constraints involving these expressions will not error. - let circuit_execution = execute_program( - &compiled_program.program, - WitnessMap::new(), - blackbox_solver, - &mut DefaultForeignCallExecutor::new(show_output, foreign_call_resolver_url), - ); - test_status_program_compile_pass( - test_function, - compiled_program.abi, - compiled_program.debug, - circuit_execution, - ) + if test_function_has_no_arguments { + // Run the backend to ensure the PWG evaluates functions like std::hash::pedersen, + // otherwise constraints involving these expressions will not error. + let circuit_execution = execute_program( + &compiled_program.program, + WitnessMap::new(), + blackbox_solver, + &mut DefaultForeignCallExecutor::new(show_output, foreign_call_resolver_url), + ); + test_status_program_compile_pass( + test_function, + compiled_program.abi, + compiled_program.debug, + circuit_execution, + ) + } else { + #[cfg(target_arch = "wasm32")] + { + // We currently don't support fuzz testing on wasm32 as the u128 strategies do not exist on this platform. + TestStatus::Fail { + message: "Fuzz tests are not supported on wasm32".to_string(), + error_diagnostic: None, + } + } + + #[cfg(not(target_arch = "wasm32"))] + { + use acvm::acir::circuit::Program; + use noir_fuzzer::FuzzedExecutor; + use proptest::test_runner::TestRunner; + let runner = TestRunner::default(); + + let executor = + |program: &Program, + initial_witness: WitnessMap| + -> Result, String> { + execute_program( + program, + initial_witness, + blackbox_solver, + &mut DefaultForeignCallExecutor::::new( + false, + foreign_call_resolver_url, + ), + ) + .map_err(|err| err.to_string()) + }; + let fuzzer = FuzzedExecutor::new(compiled_program.into(), executor, runner); + + let result = fuzzer.fuzz(); + if result.success { + TestStatus::Pass + } else { + TestStatus::Fail { + message: result.reason.unwrap_or_default(), + error_diagnostic: None, + } + } + } + } } Err(err) => test_status_program_compile_fail(err, test_function), } diff --git a/noir/noir-repo/tooling/nargo_cli/Cargo.toml b/noir/noir-repo/tooling/nargo_cli/Cargo.toml index c6cf842a623c..dabb779ae8d2 100644 --- a/noir/noir-repo/tooling/nargo_cli/Cargo.toml +++ b/noir/noir-repo/tooling/nargo_cli/Cargo.toml @@ -32,7 +32,6 @@ noirc_driver.workspace = true noirc_frontend = { workspace = true, features = ["bn254"] } noirc_abi.workspace = true noirc_errors.workspace = true -noir_fuzzer.workspace = true noirc_artifacts.workspace = true acvm = { workspace = true, features = ["bn254"] } bn254_blackbox_solver.workspace = true @@ -51,7 +50,6 @@ color-eyre.workspace = true tokio = { version = "1.0", features = ["io-std", "rt"] } dap.workspace = true clap-markdown = { git = "https://github.com/noir-lang/clap-markdown", rev = "450d759532c88f0dba70891ceecdbc9ff8f25d2b", optional = true } -proptest.workspace = true notify = "6.1.1" notify-debouncer-full = "0.3.1" diff --git a/noir/noir-repo/tooling/nargo_cli/build.rs b/noir/noir-repo/tooling/nargo_cli/build.rs index 74e07efb5c1d..3f8cd055569a 100644 --- a/noir/noir-repo/tooling/nargo_cli/build.rs +++ b/noir/noir-repo/tooling/nargo_cli/build.rs @@ -218,7 +218,7 @@ fn generate_compile_success_empty_tests(test_file: &mut File, test_data_dir: &Pa &test_dir, &format!( r#" - nargo.arg("info").arg("--json").arg("--force"); + nargo.arg("info").arg("--arithmetic-generics").arg("--json").arg("--force"); {assert_zero_opcodes}"#, ), diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/check_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/check_cmd.rs index d40bae1ecfda..5239070b4d20 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/check_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/check_cmd.rs @@ -81,14 +81,7 @@ fn check_package( allow_overwrite: bool, ) -> Result { let (mut context, crate_id) = prepare_package(file_manager, parsed_files, package); - check_crate_and_report_errors( - &mut context, - crate_id, - compile_options.deny_warnings, - compile_options.disable_macros, - compile_options.silence_warnings, - compile_options.debug_comptime_in_file.as_deref(), - )?; + check_crate_and_report_errors(&mut context, crate_id, compile_options)?; if package.is_library() || package.is_contract() { // Libraries do not have ABIs while contracts have many, so we cannot generate a `Prover.toml` file. @@ -157,14 +150,10 @@ fn create_input_toml_template( pub(crate) fn check_crate_and_report_errors( context: &mut Context, crate_id: CrateId, - deny_warnings: bool, - disable_macros: bool, - silence_warnings: bool, - debug_comptime_in_file: Option<&str>, + options: &CompileOptions, ) -> Result<(), CompileError> { - let result = - check_crate(context, crate_id, deny_warnings, disable_macros, debug_comptime_in_file); - report_errors(result, &context.file_manager, deny_warnings, silence_warnings) + let result = check_crate(context, crate_id, options); + report_errors(result, &context.file_manager, options.deny_warnings, options.silence_warnings) } #[cfg(test)] 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 a2877ebdeac5..21cf9751a8b0 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 @@ -9,8 +9,8 @@ use nargo::package::Package; use nargo::workspace::Workspace; use nargo::{insert_all_files_for_workspace_into_file_manager, parse_all}; use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; -use noirc_driver::file_manager_with_stdlib; use noirc_driver::NOIR_ARTIFACT_VERSION_STRING; +use noirc_driver::{file_manager_with_stdlib, DEFAULT_EXPRESSION_WIDTH}; use noirc_driver::{CompilationResult, CompileOptions, CompiledContract}; use noirc_frontend::graph::CrateName; @@ -187,6 +187,7 @@ fn compile_programs( let (program, warnings) = compile_program( file_manager, parsed_files, + workspace, package, compile_options, load_cached_program(package), @@ -250,12 +251,6 @@ fn save_contract( } } -/// Default expression width used for Noir compilation. -/// The ACVM native type `ExpressionWidth` has its own default which should always be unbounded, -/// while we can sometimes expect the compilation target width to change. -/// Thus, we set it separately here rather than trying to alter the default derivation of the type. -const DEFAULT_EXPRESSION_WIDTH: ExpressionWidth = ExpressionWidth::Bounded { width: 4 }; - /// If a target width was not specified in the CLI we can safely override the default. pub(crate) fn get_target_width( package_default_width: Option, diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/debug_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/debug_cmd.rs index 311af9b9db01..0a593e09c176 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/debug_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/debug_cmd.rs @@ -113,13 +113,21 @@ pub(crate) fn compile_bin_package_for_debugging( compile_program_with_debug_instrumenter( &workspace_file_manager, &parsed_files, + workspace, package, &compile_options, None, debug_state, ) } else { - compile_program(&workspace_file_manager, &parsed_files, package, &compile_options, None) + compile_program( + &workspace_file_manager, + &parsed_files, + workspace, + package, + &compile_options, + None, + ) }; report_errors( diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/export_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/export_cmd.rs index 1b7ba97d68dd..19add7f30dc8 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/export_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/export_cmd.rs @@ -83,14 +83,7 @@ fn compile_exported_functions( compile_options: &CompileOptions, ) -> Result<(), CliError> { let (mut context, crate_id) = prepare_package(file_manager, parsed_files, package); - check_crate_and_report_errors( - &mut context, - crate_id, - compile_options.deny_warnings, - compile_options.disable_macros, - compile_options.silence_warnings, - compile_options.debug_comptime_in_file.as_deref(), - )?; + check_crate_and_report_errors(&mut context, crate_id, compile_options)?; let exported_functions = context.get_all_exported_functions_in_crate(&crate_id); diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/fs/program.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/fs/program.rs index caeaafd4ab3a..323cd2c6a066 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/fs/program.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/fs/program.rs @@ -31,7 +31,6 @@ fn save_build_artifact_to_file, T: ?Sized + serde::Serialize>( ) -> PathBuf { create_named_dir(circuit_dir.as_ref(), "target"); let circuit_path = circuit_dir.as_ref().join(artifact_name).with_extension("json"); - write_to_file(&serde_json::to_vec(build_artifact).unwrap(), &circuit_path); circuit_path diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd.rs index c8848e2e304f..399b8eb56351 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd.rs @@ -10,8 +10,7 @@ use nargo::{ }; use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; use noirc_driver::{ - check_crate, compile_no_check, file_manager_with_stdlib, CompileOptions, - NOIR_ARTIFACT_VERSION_STRING, + check_crate, file_manager_with_stdlib, CompileOptions, NOIR_ARTIFACT_VERSION_STRING, }; use noirc_frontend::{ graph::CrateName, @@ -171,14 +170,8 @@ fn run_test + Default>( // We then need to construct a separate copy for each test. let (mut context, crate_id) = prepare_package(file_manager, parsed_files, package); - check_crate( - &mut context, - crate_id, - compile_options.deny_warnings, - compile_options.disable_macros, - compile_options.debug_comptime_in_file.as_deref(), - ) - .expect("Any errors should have occurred when collecting test functions"); + check_crate(&mut context, crate_id, compile_options) + .expect("Any errors should have occurred when collecting test functions"); let test_functions = context .get_all_test_functions_in_crate_matching(&crate_id, FunctionNameMatch::Exact(fn_name)); @@ -186,47 +179,14 @@ fn run_test + Default>( let blackbox_solver = S::default(); - let test_function_has_no_arguments = context - .def_interner - .function_meta(&test_function.get_id()) - .function_signature() - .0 - .is_empty(); - - if test_function_has_no_arguments { - nargo::ops::run_test( - &blackbox_solver, - &mut context, - test_function, - show_output, - foreign_call_resolver_url, - compile_options, - ) - } else { - use noir_fuzzer::FuzzedExecutor; - use proptest::test_runner::TestRunner; - - let compiled_program = - compile_no_check(&mut context, compile_options, test_function.get_id(), None, false); - match compiled_program { - Ok(compiled_program) => { - let runner = TestRunner::default(); - - let fuzzer = FuzzedExecutor::new(compiled_program.into(), runner); - - let result = fuzzer.fuzz(); - if result.success { - TestStatus::Pass - } else { - TestStatus::Fail { - message: result.reason.unwrap_or_default(), - error_diagnostic: None, - } - } - } - Err(err) => TestStatus::CompileError(err.into()), - } - } + nargo::ops::run_test( + &blackbox_solver, + &mut context, + test_function, + show_output, + foreign_call_resolver_url, + compile_options, + ) } fn get_tests_in_package( @@ -237,14 +197,7 @@ fn get_tests_in_package( compile_options: &CompileOptions, ) -> Result, CliError> { let (mut context, crate_id) = prepare_package(file_manager, parsed_files, package); - check_crate_and_report_errors( - &mut context, - crate_id, - compile_options.deny_warnings, - compile_options.disable_macros, - compile_options.silence_warnings, - compile_options.debug_comptime_in_file.as_deref(), - )?; + check_crate_and_report_errors(&mut context, crate_id, compile_options)?; Ok(context .get_all_test_functions_in_crate_matching(&crate_id, fn_name) diff --git a/noir/noir-repo/tooling/nargo_cli/tests/stdlib-tests.rs b/noir/noir-repo/tooling/nargo_cli/tests/stdlib-tests.rs index c4cc792438ea..e29b4a68c157 100644 --- a/noir/noir-repo/tooling/nargo_cli/tests/stdlib-tests.rs +++ b/noir/noir-repo/tooling/nargo_cli/tests/stdlib-tests.rs @@ -2,7 +2,7 @@ use std::io::Write; use std::{collections::BTreeMap, path::PathBuf}; use fm::FileManager; -use noirc_driver::{check_crate, compile_no_check, file_manager_with_stdlib, CompileOptions}; +use noirc_driver::{check_crate, file_manager_with_stdlib, CompileOptions}; use noirc_frontend::hir::FunctionNameMatch; use nargo::{ @@ -33,7 +33,7 @@ fn run_stdlib_tests() { let (mut context, dummy_crate_id) = prepare_package(&file_manager, &parsed_files, &dummy_package); - let result = check_crate(&mut context, dummy_crate_id, false, false, None); + let result = check_crate(&mut context, dummy_crate_id, &Default::default()); report_errors(result, &context.file_manager, true, false) .expect("Error encountered while compiling standard library"); @@ -47,52 +47,14 @@ fn run_stdlib_tests() { let test_report: Vec<(String, TestStatus)> = test_functions .into_iter() .map(|(test_name, test_function)| { - let test_function_has_no_arguments = context - .def_interner - .function_meta(&test_function.get_id()) - .function_signature() - .0 - .is_empty(); - - let status = if test_function_has_no_arguments { - run_test( - &bn254_blackbox_solver::Bn254BlackBoxSolver, - &mut context, - &test_function, - false, - None, - &CompileOptions::default(), - ) - } else { - use noir_fuzzer::FuzzedExecutor; - use proptest::test_runner::TestRunner; - - let compiled_program = compile_no_check( - &mut context, - &CompileOptions::default(), - test_function.get_id(), - None, - false, - ); - match compiled_program { - Ok(compiled_program) => { - let runner = TestRunner::default(); - - let fuzzer = FuzzedExecutor::new(compiled_program.into(), runner); - - let result = fuzzer.fuzz(); - if result.success { - TestStatus::Pass - } else { - TestStatus::Fail { - message: result.reason.unwrap_or_default(), - error_diagnostic: None, - } - } - } - Err(err) => TestStatus::CompileError(err.into()), - } - }; + let status = run_test( + &bn254_blackbox_solver::Bn254BlackBoxSolver, + &mut context, + &test_function, + false, + None, + &CompileOptions::default(), + ); (test_name, status) }) .collect(); diff --git a/noir/noir-repo/tooling/nargo_fmt/src/items.rs b/noir/noir-repo/tooling/nargo_fmt/src/items.rs index 80b641fd830f..57757982e832 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/items.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/items.rs @@ -111,8 +111,4 @@ pub(crate) trait HasItem { fn start(&self) -> u32 { self.span().start() } - - fn end(&self) -> u32 { - self.span().end() - } } diff --git a/noir/noir-repo/tooling/nargo_fmt/src/rewrite/expr.rs b/noir/noir-repo/tooling/nargo_fmt/src/rewrite/expr.rs index 015644c15cb2..41b15069546b 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/rewrite/expr.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/rewrite/expr.rs @@ -1,5 +1,6 @@ use noirc_frontend::ast::{ - ArrayLiteral, BlockExpression, Expression, ExpressionKind, Literal, UnaryOp, UnresolvedType, + ArrayLiteral, BlockExpression, Expression, ExpressionKind, Literal, Path, PathKind, UnaryOp, + UnresolvedType, }; use noirc_frontend::{macros_api::Span, token::Token}; @@ -161,12 +162,7 @@ pub(crate) fn rewrite( visitor.format_if(*if_expr) } - ExpressionKind::Variable(path, generics) => { - let path_string = visitor.slice(path.span); - - let turbofish = rewrite_turbofish(visitor, shape, generics); - format!("{path_string}{turbofish}") - } + ExpressionKind::Variable(path) => rewrite_path(visitor, shape, path), ExpressionKind::Lambda(_) => visitor.slice(span).to_string(), ExpressionKind::Quote(_) => visitor.slice(span).to_string(), ExpressionKind::Comptime(block, block_span) => { @@ -183,6 +179,10 @@ pub(crate) fn rewrite( format!("$({})", rewrite_sub_expr(visitor, shape, *expr)) } } + ExpressionKind::AsTraitPath(path) => { + let trait_path = rewrite_path(visitor, shape, path.trait_path); + format!("<{} as {}>::{}", path.typ, trait_path, path.impl_item) + } } } @@ -192,6 +192,25 @@ fn rewrite_block(visitor: &FmtVisitor, block: BlockExpression, span: Span) -> St visitor.finish() } +fn rewrite_path(visitor: &FmtVisitor, shape: Shape, path: Path) -> String { + let mut string = String::new(); + + if path.kind != PathKind::Plain { + string.push_str(&path.kind.to_string()); + string.push_str("::"); + } + + for (index, segment) in path.segments.iter().enumerate() { + if index > 0 { + string.push_str("::"); + } + string.push_str(&segment.ident.to_string()); + string.push_str(&rewrite_turbofish(visitor, shape, segment.generics.clone())); + } + + string +} + fn rewrite_turbofish( visitor: &FmtVisitor, shape: Shape, diff --git a/noir/noir-repo/tooling/nargo_fmt/src/rewrite/typ.rs b/noir/noir-repo/tooling/nargo_fmt/src/rewrite/typ.rs index 3298ed8ae731..b586f32a6feb 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/rewrite/typ.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/rewrite/typ.rs @@ -58,6 +58,7 @@ pub(crate) fn rewrite(visitor: &FmtVisitor, _shape: Shape, typ: UnresolvedType) UnresolvedTypeData::Resolved(_) => { unreachable!("Unexpected macro expansion of a type in nargo fmt input") } + UnresolvedTypeData::AsTraitPath(path) => path.to_string(), UnresolvedTypeData::Unspecified => todo!(), UnresolvedTypeData::FieldElement diff --git a/noir/noir-repo/tooling/nargo_fmt/src/utils.rs b/noir/noir-repo/tooling/nargo_fmt/src/utils.rs index 020f411ae2fe..83634b718e27 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/utils.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/utils.rs @@ -146,9 +146,10 @@ impl HasItem for Param { fn format(self, visitor: &FmtVisitor, shape: Shape) -> String { let pattern = visitor.slice(self.pattern.span()); let visibility = match self.visibility { - Visibility::Public => "pub", - Visibility::Private => "", - Visibility::DataBus => "call_data", + Visibility::Public => "pub".to_string(), + Visibility::Private => "".to_string(), + Visibility::CallData(x) => format!("call_data({x})"), + Visibility::ReturnData => "return_data".to_string(), }; if self.pattern.is_synthesized() || self.typ.is_synthesized() { @@ -187,6 +188,9 @@ impl HasItem for UnresolvedGeneric { result.push_str(&typ); result } + UnresolvedGeneric::Resolved(..) => { + unreachable!("Found macro result UnresolvedGeneric::Resolved in formatter") + } } } } diff --git a/noir/noir-repo/tooling/nargo_fmt/src/visitor/item.rs b/noir/noir-repo/tooling/nargo_fmt/src/visitor/item.rs index 5aaaf20ff479..0c9f61a7d40b 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/visitor/item.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/visitor/item.rs @@ -120,8 +120,11 @@ impl super::FmtVisitor<'_> { let visibility = match func.def.return_visibility { Visibility::Public => "pub", - Visibility::DataBus => "return_data", + Visibility::ReturnData => "return_data", Visibility::Private => "", + Visibility::CallData(_) => { + unreachable!("call_data cannot be used for return value") + } }; result.push_str(&append_space_if_nonempty(visibility.into())); diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/expected/expr.nr b/noir/noir-repo/tooling/nargo_fmt/tests/expected/expr.nr index 03a26835ee3c..babaf5b356e5 100644 --- a/noir/noir-repo/tooling/nargo_fmt/tests/expected/expr.nr +++ b/noir/noir-repo/tooling/nargo_fmt/tests/expected/expr.nr @@ -129,9 +129,9 @@ fn return_if_expr() { } fn if_if() { - if cond { + (if cond { some(); } else { none(); - }.bar().baz(); + }).bar().baz(); } diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/input/expr.nr b/noir/noir-repo/tooling/nargo_fmt/tests/input/expr.nr index b4edcbbed5f9..9ecefad7dfd8 100644 --- a/noir/noir-repo/tooling/nargo_fmt/tests/input/expr.nr +++ b/noir/noir-repo/tooling/nargo_fmt/tests/input/expr.nr @@ -147,7 +147,7 @@ fn return_if_expr() { } fn if_if() { -if cond { some(); } else { none(); } +(if cond { some(); } else { none(); }) .bar() .baz(); } \ No newline at end of file diff --git a/noir/noir-repo/tooling/noir_codegen/package.json b/noir/noir-repo/tooling/noir_codegen/package.json index fab6b8466d9d..e7f03822633d 100644 --- a/noir/noir-repo/tooling/noir_codegen/package.json +++ b/noir/noir-repo/tooling/noir_codegen/package.json @@ -3,7 +3,7 @@ "contributors": [ "The Noir Team " ], - "version": "0.32.0", + "version": "0.33.0", "packageManager": "yarn@3.5.1", "license": "(MIT OR Apache-2.0)", "type": "module", diff --git a/noir/noir-repo/tooling/noir_js/package.json b/noir/noir-repo/tooling/noir_js/package.json index a1b2e175688c..8ae098778d99 100644 --- a/noir/noir-repo/tooling/noir_js/package.json +++ b/noir/noir-repo/tooling/noir_js/package.json @@ -3,7 +3,7 @@ "contributors": [ "The Noir Team " ], - "version": "0.32.0", + "version": "0.33.0", "packageManager": "yarn@3.5.1", "license": "(MIT OR Apache-2.0)", "type": "module", diff --git a/noir/noir-repo/tooling/noir_js_backend_barretenberg/package.json b/noir/noir-repo/tooling/noir_js_backend_barretenberg/package.json index 7087052602ca..c18f9fe2c713 100644 --- a/noir/noir-repo/tooling/noir_js_backend_barretenberg/package.json +++ b/noir/noir-repo/tooling/noir_js_backend_barretenberg/package.json @@ -3,7 +3,7 @@ "contributors": [ "The Noir Team " ], - "version": "0.32.0", + "version": "0.33.0", "packageManager": "yarn@3.5.1", "license": "(MIT OR Apache-2.0)", "type": "module", @@ -41,7 +41,7 @@ "lint": "NODE_NO_WARNINGS=1 eslint . --ext .ts --ignore-path ./.eslintignore --max-warnings 0" }, "dependencies": { - "@aztec/bb.js": "portal:../../../../barretenberg/ts", + "@aztec/bb.js": "0.47.1", "@noir-lang/types": "workspace:*", "fflate": "^0.8.0" }, diff --git a/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/backend.ts b/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/backend.ts index 8ede6a07b50f..4fd256a7a810 100644 --- a/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/backend.ts +++ b/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/backend.ts @@ -2,8 +2,8 @@ import { decompressSync as gunzip } from 'fflate'; import { acirToUint8Array } from './serialize.js'; import { Backend, CompiledCircuit, ProofData, VerifierBackend } from '@noir-lang/types'; import { BackendOptions } from './types.js'; -import { deflattenPublicInputs } from './public_inputs.js'; -import { reconstructProofWithPublicInputs } from './verifier.js'; +import { deflattenFields } from './public_inputs.js'; +import { reconstructProofWithPublicInputs, reconstructProofWithPublicInputsHonk } from './verifier.js'; import { type Barretenberg } from '@aztec/bb.js'; // This is the number of bytes in a UltraPlonk proof @@ -50,6 +50,7 @@ export class BarretenbergBackend implements Backend, VerifierBackend { this.acirUncompressedBytecode, honkRecursion, ); + const crs = await Crs.new(subgroupSize + 1); await api.commonInitSlabAllocator(subgroupSize); await api.srsInitSrs(new RawBuffer(crs.getG1Data()), crs.numPoints, new RawBuffer(crs.getG2Data())); @@ -73,7 +74,7 @@ export class BarretenbergBackend implements Backend, VerifierBackend { const publicInputsConcatenated = proofWithPublicInputs.slice(0, splitIndex); const proof = proofWithPublicInputs.slice(splitIndex); - const publicInputs = deflattenPublicInputs(publicInputsConcatenated); + const publicInputs = deflattenFields(publicInputsConcatenated); return { proof, publicInputs }; } @@ -143,3 +144,140 @@ export class BarretenbergBackend implements Backend, VerifierBackend { await this.api.destroy(); } } + +// Buffers are prepended with their size. The size takes 4 bytes. +const serializedBufferSize = 4; +const fieldByteSize = 32; +const publicInputOffset = 3; +const publicInputsOffsetBytes = publicInputOffset * fieldByteSize; + +export class UltraHonkBackend implements Backend, VerifierBackend { + // These type assertions are used so that we don't + // have to initialize `api` in the constructor. + // These are initialized asynchronously in the `init` function, + // constructors cannot be asynchronous which is why we do this. + + protected api!: Barretenberg; + protected acirUncompressedBytecode: Uint8Array; + + constructor( + acirCircuit: CompiledCircuit, + protected options: BackendOptions = { threads: 1 }, + ) { + const acirBytecodeBase64 = acirCircuit.bytecode; + this.acirUncompressedBytecode = acirToUint8Array(acirBytecodeBase64); + } + + /** @ignore */ + async instantiate(): Promise { + if (!this.api) { + if (typeof navigator !== 'undefined' && navigator.hardwareConcurrency) { + this.options.threads = navigator.hardwareConcurrency; + } else { + try { + const os = await import('os'); + this.options.threads = os.cpus().length; + } catch (e) { + console.log('Could not detect environment. Falling back to one thread.', e); + } + } + const { Barretenberg, RawBuffer, Crs } = await import('@aztec/bb.js'); + const api = await Barretenberg.new(this.options); + + const honkRecursion = true; + const [_exact, _total, subgroupSize] = await api.acirGetCircuitSizes( + this.acirUncompressedBytecode, + honkRecursion, + ); + const crs = await Crs.new(subgroupSize + 1); + await api.commonInitSlabAllocator(subgroupSize); + await api.srsInitSrs(new RawBuffer(crs.getG1Data()), crs.numPoints, new RawBuffer(crs.getG2Data())); + + // We don't init a proving key here in the Honk API + // await api.acirInitProvingKey(this.acirComposer, this.acirUncompressedBytecode); + this.api = api; + } + } + + async generateProof(decompressedWitness: Uint8Array): Promise { + await this.instantiate(); + const proofWithPublicInputs = await this.api.acirProveUltraHonk( + this.acirUncompressedBytecode, + gunzip(decompressedWitness), + ); + const proofAsStrings = deflattenFields(proofWithPublicInputs.slice(4)); + + const numPublicInputs = Number(proofAsStrings[1]); + + // Account for the serialized buffer size at start + const publicInputsOffset = publicInputsOffsetBytes + serializedBufferSize; + // Get the part before and after the public inputs + const proofStart = proofWithPublicInputs.slice(0, publicInputsOffset); + const publicInputsSplitIndex = numPublicInputs * fieldByteSize; + const proofEnd = proofWithPublicInputs.slice(publicInputsOffset + publicInputsSplitIndex); + // Construct the proof without the public inputs + const proof = new Uint8Array([...proofStart, ...proofEnd]); + + // Fetch the number of public inputs out of the proof string + const publicInputsConcatenated = proofWithPublicInputs.slice( + publicInputsOffset, + publicInputsOffset + publicInputsSplitIndex, + ); + const publicInputs = deflattenFields(publicInputsConcatenated); + + return { proof, publicInputs }; + } + + async verifyProof(proofData: ProofData): Promise { + const { RawBuffer } = await import('@aztec/bb.js'); + + const proof = reconstructProofWithPublicInputsHonk(proofData); + + await this.instantiate(); + const vkBuf = await this.api.acirWriteVkUltraHonk(this.acirUncompressedBytecode); + + return await this.api.acirVerifyUltraHonk(proof, new RawBuffer(vkBuf)); + } + + async getVerificationKey(): Promise { + await this.instantiate(); + return await this.api.acirWriteVkUltraHonk(this.acirUncompressedBytecode); + } + + // TODO(https://github.com/noir-lang/noir/issues/5661): Update this to handle Honk recursive aggregation in the browser once it is ready in the backend itself + async generateRecursiveProofArtifacts( + _proofData: ProofData, + _numOfPublicInputs: number, + ): Promise<{ proofAsFields: string[]; vkAsFields: string[]; vkHash: string }> { + await this.instantiate(); + // TODO(https://github.com/noir-lang/noir/issues/5661): This needs to be updated to handle recursive aggregation. + // There is still a proofAsFields method but we could consider getting rid of it as the proof itself + // is a list of field elements. + // UltraHonk also does not have public inputs directly prepended to the proof and they are still instead + // inserted at an offset. + // const proof = reconstructProofWithPublicInputs(proofData); + // const proofAsFields = (await this.api.acirProofAsFieldsUltraHonk(proof)).slice(numOfPublicInputs); + + // TODO: perhaps we should put this in the init function. Need to benchmark + // TODO how long it takes. + const vkBuf = await this.api.acirWriteVkUltraHonk(this.acirUncompressedBytecode); + const vk = await this.api.acirVkAsFieldsUltraHonk(vkBuf); + + return { + // TODO(https://github.com/noir-lang/noir/issues/5661) + proofAsFields: [], + vkAsFields: vk.map((vk) => vk.toString()), + // We use an empty string for the vk hash here as it is unneeded as part of the recursive artifacts + // The user can be expected to hash the vk inside their circuit to check whether the vk is the circuit + // they expect + vkHash: '', + }; + } + + async destroy(): Promise { + if (!this.api) { + return; + } + await this.api.destroy(); + } +} diff --git a/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/index.ts b/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/index.ts index cefef07520f0..6786c1eec487 100644 --- a/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/index.ts +++ b/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/index.ts @@ -1,5 +1,5 @@ -export { BarretenbergBackend } from './backend.js'; -export { BarretenbergVerifier } from './verifier.js'; +export { BarretenbergBackend, UltraHonkBackend } from './backend.js'; +export { BarretenbergVerifier, UltraHonkVerifier } from './verifier.js'; // typedoc exports export { Backend, CompiledCircuit, ProofData } from '@noir-lang/types'; diff --git a/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/public_inputs.ts b/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/public_inputs.ts index ed771ab0d348..10b4ee6ab326 100644 --- a/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/public_inputs.ts +++ b/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/public_inputs.ts @@ -1,16 +1,16 @@ import { WitnessMap } from '@noir-lang/types'; -export function flattenPublicInputsAsArray(publicInputs: string[]): Uint8Array { - const flattenedPublicInputs = publicInputs.map(hexToUint8Array); +export function flattenFieldsAsArray(fields: string[]): Uint8Array { + const flattenedPublicInputs = fields.map(hexToUint8Array); return flattenUint8Arrays(flattenedPublicInputs); } -export function deflattenPublicInputs(flattenedPublicInputs: Uint8Array): string[] { +export function deflattenFields(flattenedFields: Uint8Array): string[] { const publicInputSize = 32; const chunkedFlattenedPublicInputs: Uint8Array[] = []; - for (let i = 0; i < flattenedPublicInputs.length; i += publicInputSize) { - const publicInput = flattenedPublicInputs.slice(i, i + publicInputSize); + for (let i = 0; i < flattenedFields.length; i += publicInputSize) { + const publicInput = flattenedFields.slice(i, i + publicInputSize); chunkedFlattenedPublicInputs.push(publicInput); } diff --git a/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/verifier.ts b/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/verifier.ts index fe9fa9cfffdf..58612672b350 100644 --- a/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/verifier.ts +++ b/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/verifier.ts @@ -1,6 +1,6 @@ import { ProofData } from '@noir-lang/types'; import { BackendOptions } from './types.js'; -import { flattenPublicInputsAsArray } from './public_inputs.js'; +import { flattenFieldsAsArray } from './public_inputs.js'; import { type Barretenberg } from '@aztec/bb.js'; export class BarretenbergVerifier { @@ -69,10 +69,86 @@ export class BarretenbergVerifier { export function reconstructProofWithPublicInputs(proofData: ProofData): Uint8Array { // Flatten publicInputs - const publicInputsConcatenated = flattenPublicInputsAsArray(proofData.publicInputs); + const publicInputsConcatenated = flattenFieldsAsArray(proofData.publicInputs); // Concatenate publicInputs and proof const proofWithPublicInputs = Uint8Array.from([...publicInputsConcatenated, ...proofData.proof]); return proofWithPublicInputs; } + +export class UltraHonkVerifier { + // These type assertions are used so that we don't + // have to initialize `api` in the constructor. + // These are initialized asynchronously in the `init` function, + // constructors cannot be asynchronous which is why we do this. + + private api!: Barretenberg; + + constructor(private options: BackendOptions = { threads: 1 }) {} + + /** @ignore */ + async instantiate(): Promise { + if (!this.api) { + if (typeof navigator !== 'undefined' && navigator.hardwareConcurrency) { + this.options.threads = navigator.hardwareConcurrency; + } else { + try { + const os = await import('os'); + this.options.threads = os.cpus().length; + } catch (e) { + console.log('Could not detect environment. Falling back to one thread.', e); + } + } + const { Barretenberg, RawBuffer, Crs } = await import('@aztec/bb.js'); + + // This is the number of CRS points necessary to verify a Barretenberg proof. + const NUM_CRS_POINTS_FOR_VERIFICATION: number = 0; + const [api, crs] = await Promise.all([Barretenberg.new(this.options), Crs.new(NUM_CRS_POINTS_FOR_VERIFICATION)]); + + await api.commonInitSlabAllocator(NUM_CRS_POINTS_FOR_VERIFICATION); + await api.srsInitSrs( + new RawBuffer([] /* crs.getG1Data() */), + NUM_CRS_POINTS_FOR_VERIFICATION, + new RawBuffer(crs.getG2Data()), + ); + + this.api = api; + } + } + + /** @description Verifies a proof */ + async verifyProof(proofData: ProofData, verificationKey: Uint8Array): Promise { + const { RawBuffer } = await import('@aztec/bb.js'); + + await this.instantiate(); + + const proof = reconstructProofWithPublicInputsHonk(proofData); + return await this.api.acirVerifyUltraHonk(proof, new RawBuffer(verificationKey)); + } + + async destroy(): Promise { + if (!this.api) { + return; + } + await this.api.destroy(); + } +} + +const serializedBufferSize = 4; +const fieldByteSize = 32; +const publicInputOffset = 3; +const publicInputsOffsetBytes = publicInputOffset * fieldByteSize; + +export function reconstructProofWithPublicInputsHonk(proofData: ProofData): Uint8Array { + // Flatten publicInputs + const publicInputsConcatenated = flattenFieldsAsArray(proofData.publicInputs); + + const proofStart = proofData.proof.slice(0, publicInputsOffsetBytes + serializedBufferSize); + const proofEnd = proofData.proof.slice(publicInputsOffsetBytes + serializedBufferSize); + + // Concatenate publicInputs and proof + const proofWithPublicInputs = Uint8Array.from([...proofStart, ...publicInputsConcatenated, ...proofEnd]); + + return proofWithPublicInputs; +} diff --git a/noir/noir-repo/tooling/noir_js_types/package.json b/noir/noir-repo/tooling/noir_js_types/package.json index 8548486d58c4..55aa85ae9073 100644 --- a/noir/noir-repo/tooling/noir_js_types/package.json +++ b/noir/noir-repo/tooling/noir_js_types/package.json @@ -4,7 +4,7 @@ "The Noir Team " ], "packageManager": "yarn@3.5.1", - "version": "0.32.0", + "version": "0.33.0", "license": "(MIT OR Apache-2.0)", "homepage": "https://noir-lang.org/", "repository": { diff --git a/noir/noir-repo/tooling/noirc_abi_wasm/build.sh b/noir/noir-repo/tooling/noirc_abi_wasm/build.sh index c07d2d8a4c1d..16fb26e55db1 100755 --- a/noir/noir-repo/tooling/noirc_abi_wasm/build.sh +++ b/noir/noir-repo/tooling/noirc_abi_wasm/build.sh @@ -25,7 +25,7 @@ function run_if_available { require_command jq require_command cargo require_command wasm-bindgen -#require_command wasm-opt +require_command wasm-opt self_path=$(dirname "$(readlink -f "$0")") pname=$(cargo read-manifest | jq -r '.name') diff --git a/noir/noir-repo/tooling/noirc_abi_wasm/package.json b/noir/noir-repo/tooling/noirc_abi_wasm/package.json index 05588da37a41..f3f4b9dacec4 100644 --- a/noir/noir-repo/tooling/noirc_abi_wasm/package.json +++ b/noir/noir-repo/tooling/noirc_abi_wasm/package.json @@ -3,7 +3,7 @@ "contributors": [ "The Noir Team " ], - "version": "0.32.0", + "version": "0.33.0", "license": "(MIT OR Apache-2.0)", "homepage": "https://noir-lang.org/", "repository": { diff --git a/noir/noir-repo/tooling/noirc_artifacts/src/debug.rs b/noir/noir-repo/tooling/noirc_artifacts/src/debug.rs index 11a3e1c4dd71..8e2add70ae7b 100644 --- a/noir/noir-repo/tooling/noirc_artifacts/src/debug.rs +++ b/noir/noir-repo/tooling/noirc_artifacts/src/debug.rs @@ -23,7 +23,7 @@ impl DebugArtifact { pub fn new(debug_symbols: Vec, file_manager: &FileManager) -> Self { let mut file_map = BTreeMap::new(); - let files_with_debug_symbols: BTreeSet = debug_symbols + let mut files_with_debug_symbols: BTreeSet = debug_symbols .iter() .flat_map(|function_symbols| { function_symbols @@ -33,6 +33,21 @@ impl DebugArtifact { }) .collect(); + let files_with_brillig_debug_symbols: BTreeSet = debug_symbols + .iter() + .flat_map(|function_symbols| { + let brillig_location_maps = + function_symbols.brillig_locations.values().flat_map(|brillig_location_map| { + brillig_location_map + .values() + .flat_map(|call_stack| call_stack.iter().map(|location| location.file)) + }); + brillig_location_maps + }) + .collect(); + + files_with_debug_symbols.extend(files_with_brillig_debug_symbols); + for file_id in files_with_debug_symbols { let file_path = file_manager.path(file_id).expect("file should exist"); let file_source = file_manager.fetch_file(file_id).expect("file should exist"); @@ -248,6 +263,7 @@ mod tests { BTreeMap::default(), BTreeMap::default(), BTreeMap::default(), + BTreeMap::default(), )]; let debug_artifact = DebugArtifact::new(debug_symbols, &fm); diff --git a/noir/noir-repo/tooling/profiler/src/flamegraph.rs b/noir/noir-repo/tooling/profiler/src/flamegraph.rs index 6b5a06405b37..0fdc65d89200 100644 --- a/noir/noir-repo/tooling/profiler/src/flamegraph.rs +++ b/noir/noir-repo/tooling/profiler/src/flamegraph.rs @@ -269,6 +269,7 @@ mod tests { BTreeMap::default(), BTreeMap::default(), BTreeMap::default(), + BTreeMap::default(), ); let samples_per_opcode = vec![10, 20, 30]; diff --git a/noir/noir-repo/yarn.lock b/noir/noir-repo/yarn.lock index f77e9f7e72ef..40d6ccc55e6e 100644 --- a/noir/noir-repo/yarn.lock +++ b/noir/noir-repo/yarn.lock @@ -221,18 +221,19 @@ __metadata: languageName: node linkType: hard -"@aztec/bb.js@portal:../../../../barretenberg/ts::locator=%40noir-lang%2Fbackend_barretenberg%40workspace%3Atooling%2Fnoir_js_backend_barretenberg": - version: 0.0.0-use.local - resolution: "@aztec/bb.js@portal:../../../../barretenberg/ts::locator=%40noir-lang%2Fbackend_barretenberg%40workspace%3Atooling%2Fnoir_js_backend_barretenberg" +"@aztec/bb.js@npm:0.47.1": + version: 0.47.1 + resolution: "@aztec/bb.js@npm:0.47.1" dependencies: comlink: ^4.4.1 commander: ^10.0.1 debug: ^4.3.4 tslib: ^2.4.0 bin: - bb.js: ./dest/node/main.js + bb.js: dest/node/main.js + checksum: fa06d2ab58b2a23bacc578df7654f5c7eb90553229fc9730aaaf7479bc96b39f10f24a4f3a7eae8f73df3cdd8a3ffb07627cad61dff9896cabdb275ce5b6f09b languageName: node - linkType: soft + linkType: hard "@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.11, @babel/code-frame@npm:^7.16.0, @babel/code-frame@npm:^7.22.13, @babel/code-frame@npm:^7.23.5, @babel/code-frame@npm:^7.8.3": version: 7.23.5 @@ -4160,7 +4161,7 @@ __metadata: version: 0.0.0-use.local resolution: "@noir-lang/backend_barretenberg@workspace:tooling/noir_js_backend_barretenberg" dependencies: - "@aztec/bb.js": "portal:../../../../barretenberg/ts" + "@aztec/bb.js": 0.47.1 "@noir-lang/types": "workspace:*" "@types/node": ^20.6.2 "@types/prettier": ^3