From 3948c2baf072ee753f86ed31d0d96212067a2e0e Mon Sep 17 00:00:00 2001 From: AztecBot Date: Tue, 20 Aug 2024 08:02:21 +0000 Subject: [PATCH] [1 changes] feat(perf): mem2reg function state for value loads to optimize across blocks (https://github.com/noir-lang/noir/pull/5757) feat: add `Expr::as_array`, `Expr::as_repeated_element_array` and same for slice (https://github.com/noir-lang/noir/pull/5750) chore: Add Brillig loop bytecode size regression and update noir-gates-diff report (https://github.com/noir-lang/noir/pull/5747) feat: fault-tolerant parsing of `fn` and `impl` (https://github.com/noir-lang/noir/pull/5753) fix: add missing trait impls for integer types to stdlib (https://github.com/noir-lang/noir/pull/5738) chore: refactor ACIR function IDs from raw integers to struct (https://github.com/noir-lang/noir/pull/5748) feat: (LSP) suggest names that match any part of the current prefix (https://github.com/noir-lang/noir/pull/5752) feat: LSP auto-import completion (https://github.com/noir-lang/noir/pull/5741) fix: Allow comptime code to use break without also being `unconstrained` (https://github.com/noir-lang/noir/pull/5744) feat: add `Expr::as_any_integer` and `Expr::as_member_access` (https://github.com/noir-lang/noir/pull/5742) chore: clarify Field use (https://github.com/noir-lang/noir/pull/5740) feat: add `Expr::as_binary_op` (https://github.com/noir-lang/noir/pull/5734) chore(docs): expanding solidity verifier chain list (https://github.com/noir-lang/noir/pull/5587) chore: apply some new lints across workspace (https://github.com/noir-lang/noir/pull/5736) feat: suggest trait methods in LSP completion (https://github.com/noir-lang/noir/pull/5735) feat: LSP autocomplete constructor fields (https://github.com/noir-lang/noir/pull/5732) feat: add `Expr::as_unary` (https://github.com/noir-lang/noir/pull/5731) chore: count brillig opcodes in nargo info (https://github.com/noir-lang/noir/pull/5189) feat: suggest tuple fields in LSP completion (https://github.com/noir-lang/noir/pull/5730) feat: add `Expr::as_bool` (https://github.com/noir-lang/noir/pull/5729) feat: add `Expr` methods: `as_tuple`, `as_parenthesized`, `as_index`, `as_if` (https://github.com/noir-lang/noir/pull/5726) feat: LSP signature help (https://github.com/noir-lang/noir/pull/5725) chore: split LSP completion.rs into several files (https://github.com/noir-lang/noir/pull/5723) feat: add `TraitImpl::trait_generic_args` and `TraitImpl::methods` (https://github.com/noir-lang/noir/pull/5722) fix: let LSP autocompletion work in more contexts (https://github.com/noir-lang/noir/pull/5719) fix(frontend): Continue type check if we are missing an unsafe block (https://github.com/noir-lang/noir/pull/5720) feat: add `unsafe` blocks for calling unconstrained code from constrained functions (https://github.com/noir-lang/noir/pull/4429) --- .noir-sync-commit | 2 +- .../.github/workflows/gates_report.yml | 2 +- .../workflows/gates_report_brillig.yml | 92 + noir/noir-repo/.gitignore | 1 + noir/noir-repo/Cargo.lock | 1 + noir/noir-repo/Cargo.toml | 6 + noir/noir-repo/acvm-repo/acir/Cargo.toml | 3 + .../acvm-repo/acir/src/circuit/mod.rs | 8 +- .../acvm-repo/acir/src/circuit/opcodes.rs | 6 +- .../acir/src/circuit/opcodes/function_id.rs | 17 + .../acir/tests/test_program_serialization.rs | 8 +- .../noir-repo/acvm-repo/acir_field/Cargo.toml | 3 + .../acvm-repo/acir_field/src/field_element.rs | 4 +- .../acvm-repo/acir_field/src/generic_ark.rs | 4 +- noir/noir-repo/acvm-repo/acvm/Cargo.toml | 3 + noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs | 6 +- noir/noir-repo/acvm-repo/acvm_js/Cargo.toml | 3 + noir/noir-repo/acvm-repo/acvm_js/build.sh | 2 +- .../acvm-repo/acvm_js/src/execute.rs | 4 +- .../acvm_js/src/js_execution_error.rs | 8 +- .../acvm-repo/blackbox_solver/Cargo.toml | 3 + .../bn254_blackbox_solver/Cargo.toml | 3 + .../src/generator/generators.rs | 2 +- noir/noir-repo/acvm-repo/brillig/Cargo.toml | 3 + .../noir-repo/acvm-repo/brillig_vm/Cargo.toml | 3 + noir/noir-repo/aztec_macros/Cargo.toml | 3 + .../src/transforms/contract_interface.rs | 176 +- .../aztec_macros/src/utils/parse_utils.rs | 5 +- noir/noir-repo/compiler/fm/Cargo.toml | 3 + .../noir-repo/compiler/noirc_arena/Cargo.toml | 3 + .../compiler/noirc_driver/Cargo.toml | 3 + .../compiler/noirc_driver/src/abi_gen.rs | 2 +- .../compiler/noirc_driver/src/lib.rs | 11 +- .../compiler/noirc_driver/src/program.rs | 2 + .../compiler/noirc_errors/Cargo.toml | 3 + .../compiler/noirc_errors/src/position.rs | 2 +- .../compiler/noirc_evaluator/Cargo.toml | 3 + .../src/brillig/brillig_gen.rs | 4 +- .../brillig/brillig_gen/brillig_directive.rs | 2 + .../src/brillig/brillig_ir/artifact.rs | 4 + .../compiler/noirc_evaluator/src/ssa.rs | 9 +- .../src/ssa/acir_gen/acir_ir/acir_variable.rs | 6 +- .../noirc_evaluator/src/ssa/acir_gen/mod.rs | 87 +- .../noirc_evaluator/src/ssa/ir/dfg.rs | 9 +- .../noirc_evaluator/src/ssa/ir/map.rs | 2 +- .../noirc_evaluator/src/ssa/opt/die.rs | 356 +++- .../noirc_evaluator/src/ssa/opt/mem2reg.rs | 47 +- .../src/ssa/ssa_gen/context.rs | 8 +- .../noirc_evaluator/src/ssa/ssa_gen/mod.rs | 12 +- .../noirc_frontend/src/ast/expression.rs | 18 +- .../compiler/noirc_frontend/src/ast/mod.rs | 7 +- .../noirc_frontend/src/ast/statement.rs | 10 +- .../compiler/noirc_frontend/src/debug/mod.rs | 24 +- .../noirc_frontend/src/elaborator/comptime.rs | 8 + .../src/elaborator/expressions.rs | 21 +- .../noirc_frontend/src/elaborator/mod.rs | 31 +- .../noirc_frontend/src/elaborator/traits.rs | 13 +- .../noirc_frontend/src/elaborator/types.rs | 60 +- .../src/hir/comptime/hir_to_display_ast.rs | 7 +- .../src/hir/comptime/interpreter.rs | 3 + .../src/hir/comptime/interpreter/builtin.rs | 349 +++- .../interpreter/builtin/builtin_helpers.rs | 86 +- .../noirc_frontend/src/hir/comptime/value.rs | 70 +- .../src/hir/def_collector/dc_mod.rs | 48 +- .../src/hir/def_map/module_def.rs | 2 +- .../src/hir/type_check/errors.rs | 5 + .../noirc_frontend/src/hir_def/expr.rs | 1 + .../noirc_frontend/src/hir_def/function.rs | 4 +- .../noirc_frontend/src/hir_def/traits.rs | 8 +- .../noirc_frontend/src/hir_def/types.rs | 121 +- .../noirc_frontend/src/lexer/token.rs | 6 + .../compiler/noirc_frontend/src/locations.rs | 76 +- .../src/monomorphization/ast.rs | 13 +- .../src/monomorphization/mod.rs | 45 +- .../noirc_frontend/src/node_interner.rs | 107 +- .../noirc_frontend/src/parser/errors.rs | 12 + .../noirc_frontend/src/parser/parser.rs | 136 +- .../src/parser/parser/function.rs | 35 +- .../src/parser/parser/traits.rs | 108 +- .../noirc_frontend/src/parser/parser/types.rs | 18 +- .../noirc_frontend/src/resolve_locations.rs | 6 + .../compiler/noirc_frontend/src/tests.rs | 203 ++ .../compiler/noirc_printable_type/Cargo.toml | 3 + .../compiler/noirc_printable_type/src/lib.rs | 1 + noir/noir-repo/compiler/wasm/Cargo.toml | 4 +- noir/noir-repo/cspell.json | 3 +- .../docs/explainers/explainer-writing-noir.md | 8 +- .../docs/how_to/how-to-solidity-verifier.md | 8 +- .../docs/docs/noir/concepts/unconstrained.md | 9 +- .../how_to/how-to-solidity-verifier.md | 8 +- .../reference/debugger/_category_.json | 2 +- noir/noir-repo/noir_stdlib/src/array.nr | 20 +- noir/noir-repo/noir_stdlib/src/cmp.nr | 26 + .../src/collections/bounded_vec.nr | 4 +- .../noir_stdlib/src/collections/map.nr | 4 +- .../noir_stdlib/src/collections/umap.nr | 6 +- noir/noir-repo/noir_stdlib/src/default.nr | 3 + noir/noir-repo/noir_stdlib/src/field/bn254.nr | 50 +- noir/noir-repo/noir_stdlib/src/hash/mod.nr | 22 +- noir/noir-repo/noir_stdlib/src/lib.nr | 18 +- .../noir_stdlib/src/{unsafe.nr => mem.nr} | 1 + noir/noir-repo/noir_stdlib/src/meta/expr.nr | 222 ++ noir/noir-repo/noir_stdlib/src/meta/mod.nr | 8 +- noir/noir-repo/noir_stdlib/src/meta/op.nr | 92 + .../noir_stdlib/src/meta/trait_impl.nr | 7 + noir/noir-repo/noir_stdlib/src/meta/typ.nr | 3 + noir/noir-repo/noir_stdlib/src/ops/arith.nr | 5 + noir/noir-repo/noir_stdlib/src/ops/bit.nr | 3 + noir/noir-repo/noir_stdlib/src/option.nr | 2 +- noir/noir-repo/noir_stdlib/src/slice.nr | 2 +- noir/noir-repo/noir_stdlib/src/uint128.nr | 62 +- noir/noir-repo/scripts/install_bb.sh | 2 +- .../array_length_defaulting/src/main.nr | 2 +- .../brillig_mut_ref_from_acir/src/main.nr | 4 +- .../regression_5008/src/main.nr | 4 +- .../turbofish_generic_count/src/main.nr | 2 +- .../unconstrained_ref/src/main.nr | 4 +- .../arithmetic_generics/src/main.nr | 4 +- .../brillig_cast/src/main.nr | 12 +- .../src/main.nr | 14 +- .../src/main.nr | 34 +- .../brillig_modulo/src/main.nr | 28 +- .../brillig_slice_input/src/main.nr | 12 +- .../comptime_exp/src/main.nr | 8 - .../comptime_fmt_strings/src/main.nr | 2 +- .../Nargo.toml | 2 +- .../comptime_trait_impl/src/main.nr | 32 + .../comptime_type/src/main.nr | 4 + .../macros_in_comptime/src/main.nr | 6 +- .../zeroed_slice/src/main.nr | 2 +- .../check_uncostrained_regression/src/main.nr | 4 +- .../brillig_assert_fail/src/main.nr | 6 +- .../brillig_assert_msg_runtime/src/main.nr | 6 +- .../src/main.nr | 4 +- .../regression_5202/src/main.nr | 8 +- .../Nargo.toml | 7 + .../Prover.toml | 0 .../src/main.nr | 4 + .../Nargo.toml | 7 + .../Prover.toml | 1 + .../src/main.nr | 4 + .../Nargo.toml | 7 + .../Prover.toml | 0 .../src/main.nr | 4 + .../Nargo.toml | 7 + .../Prover.toml | 1 + .../src/main.nr | 4 + .../Nargo.toml | 7 + .../Prover.toml | 0 .../src/main.nr | 4 + .../Nargo.toml | 7 + .../Prover.toml | 1 + .../src/main.nr | 4 + .../acir_inside_brillig_recursion/src/main.nr | 4 +- .../aes128_encrypt/src/main.nr | 21 +- .../src/main.nr | 6 +- .../execution_success/bigint/src/main.nr | 4 +- .../brillig_acir_as_brillig/src/main.nr | 8 +- .../brillig_array_to_slice/src/main.nr | 10 +- .../brillig_arrays/src/main.nr | 6 +- .../brillig_assert/src/main.nr | 4 +- .../brillig_blake2s/src/main.nr | 4 +- .../src/main.nr | 4 +- .../brillig_calls/src/main.nr | 10 +- .../brillig_calls_array/src/main.nr | 6 +- .../brillig_calls_conditionals/src/main.nr | 10 +- .../brillig_conditional/src/main.nr | 4 +- .../brillig_ecdsa_secp256k1/src/main.nr | 4 +- .../brillig_ecdsa_secp256r1/src/main.nr | 4 +- .../brillig_fns_as_values/src/main.nr | 20 +- .../brillig_hash_to_field/src/main.nr | 4 +- .../brillig_identity_function/src/main.nr | 22 +- .../brillig_keccak/src/main.nr | 26 +- .../brillig_loop/src/main.nr | 6 +- .../brillig_loop_size_regression/Nargo.toml | 7 + .../brillig_loop_size_regression/Prover.toml | 0 .../brillig_loop_size_regression/src/main.nr | 16 + .../brillig_nested_arrays/src/main.nr | 16 +- .../execution_success/brillig_not/src/main.nr | 6 +- .../brillig_oracle/src/main.nr | 30 +- .../brillig_recursion/src/main.nr | 4 +- .../brillig_sha256/src/main.nr | 4 +- .../brillig_unitialised_arrays/src/main.nr | 6 +- .../execution_success/databus/src/main.nr | 4 +- .../execution_success/generics/src/main.nr | 2 +- .../global_consts/src/main.nr | 2 +- .../is_unconstrained/src/main.nr | 4 +- .../nested_arrays_from_brillig/src/main.nr | 4 +- .../regression_4124/src/main.nr | 2 +- .../regression_5435/src/main.nr | 4 +- .../execution_success/slice_regex/src/main.nr | 8 +- .../execution_success/u16_support/src/main.nr | 4 +- .../execution_success/uhashmap/src/main.nr | 8 +- .../execution_success/unit_value/src/main.nr | 2 +- .../verify_honk_proof/Nargo.toml | 6 - .../verify_honk_proof/Prover.toml | 537 ----- .../verify_honk_proof/src/main.nr | 16 - .../test_programs/gates_report_brillig.sh | 33 + .../noir_test_success/mock_oracle/src/main.nr | 112 +- .../out_of_bounds_alignment/src/main.nr | 4 +- .../regression_4561/src/main.nr | 8 +- noir/noir-repo/tooling/acvm_cli/Cargo.toml | 3 + noir/noir-repo/tooling/debugger/Cargo.toml | 3 + .../noir-repo/tooling/debugger/src/context.rs | 15 +- noir/noir-repo/tooling/fuzzer/Cargo.toml | 3 + noir/noir-repo/tooling/lsp/Cargo.toml | 4 + noir/noir-repo/tooling/lsp/src/lib.rs | 11 +- .../tooling/lsp/src/notifications/mod.rs | 8 +- .../tooling/lsp/src/requests/completion.rs | 1789 ++++------------- .../src/requests/completion/auto_import.rs | 195 ++ .../lsp/src/requests/completion/builtins.rs | 64 + .../requests/completion/completion_items.rs | 391 ++++ .../lsp/src/requests/completion/kinds.rs | 42 + .../lsp/src/requests/completion/sort_text.rs | 39 + .../lsp/src/requests/completion/tests.rs | 1616 +++++++++++++++ .../lsp/src/requests/completion/traversal.rs | 132 ++ .../tooling/lsp/src/requests/hover.rs | 2 +- .../tooling/lsp/src/requests/inlay_hint.rs | 16 +- .../noir-repo/tooling/lsp/src/requests/mod.rs | 15 +- .../lsp/src/requests/signature_help.rs | 291 +++ .../lsp/src/requests/signature_help/tests.rs | 196 ++ .../src/requests/signature_help/traversal.rs | 304 +++ noir/noir-repo/tooling/lsp/src/types.rs | 6 +- noir/noir-repo/tooling/nargo/Cargo.toml | 3 + .../tooling/nargo/src/ops/execute.rs | 6 +- noir/noir-repo/tooling/nargo_cli/Cargo.toml | 3 + noir/noir-repo/tooling/nargo_cli/build.rs | 2 +- .../tooling/nargo_cli/src/cli/info_cmd.rs | 57 +- noir/noir-repo/tooling/nargo_fmt/Cargo.toml | 3 + .../tooling/nargo_fmt/src/rewrite/expr.rs | 3 + .../tooling/nargo_fmt/src/rewrite/typ.rs | 6 +- .../tooling/nargo_fmt/tests/expected/fn.nr | 2 + .../nargo_fmt/tests/expected/unsafe.nr | 8 + .../tooling/nargo_fmt/tests/input/fn.nr | 2 + .../tooling/nargo_fmt/tests/input/unsafe.nr | 8 + noir/noir-repo/tooling/nargo_toml/Cargo.toml | 3 + .../noir_js_backend_barretenberg/package.json | 2 +- noir/noir-repo/tooling/noirc_abi/Cargo.toml | 3 + .../tooling/noirc_abi/src/arbitrary.rs | 8 +- .../noir-repo/tooling/noirc_abi_wasm/build.sh | 2 +- .../tooling/noirc_abi_wasm/src/lib.rs | 5 +- .../tooling/noirc_artifacts/Cargo.toml | 3 + .../tooling/noirc_artifacts/src/program.rs | 4 + noir/noir-repo/tooling/profiler/Cargo.toml | 3 + .../profiler/src/cli/gates_flamegraph_cmd.rs | 3 +- .../src/cli/opcodes_flamegraph_cmd.rs | 2 + .../tooling/profiler/src/opcode_formatter.rs | 2 +- noir/noir-repo/yarn.lock | 13 +- 248 files changed, 7070 insertions(+), 2768 deletions(-) create mode 100644 noir/noir-repo/.github/workflows/gates_report_brillig.yml create mode 100644 noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/function_id.rs rename noir/noir-repo/noir_stdlib/src/{unsafe.nr => mem.nr} (99%) create mode 100644 noir/noir-repo/noir_stdlib/src/meta/op.nr create mode 100644 noir/noir-repo/noir_stdlib/src/meta/trait_impl.nr delete mode 100644 noir/noir-repo/test_programs/compile_success_empty/comptime_exp/src/main.nr rename noir/noir-repo/test_programs/compile_success_empty/{comptime_exp => comptime_trait_impl}/Nargo.toml (74%) create mode 100644 noir/noir-repo/test_programs/compile_success_empty/comptime_trait_impl/src/main.nr create mode 100644 noir/noir-repo/test_programs/execution_failure/unused_array_get_known_index_out_of_bounds/Nargo.toml create mode 100644 noir/noir-repo/test_programs/execution_failure/unused_array_get_known_index_out_of_bounds/Prover.toml create mode 100644 noir/noir-repo/test_programs/execution_failure/unused_array_get_known_index_out_of_bounds/src/main.nr create mode 100644 noir/noir-repo/test_programs/execution_failure/unused_array_get_unknown_index_out_of_bounds/Nargo.toml create mode 100644 noir/noir-repo/test_programs/execution_failure/unused_array_get_unknown_index_out_of_bounds/Prover.toml create mode 100644 noir/noir-repo/test_programs/execution_failure/unused_array_get_unknown_index_out_of_bounds/src/main.nr create mode 100644 noir/noir-repo/test_programs/execution_failure/unused_array_set_known_index_out_of_bounds/Nargo.toml create mode 100644 noir/noir-repo/test_programs/execution_failure/unused_array_set_known_index_out_of_bounds/Prover.toml create mode 100644 noir/noir-repo/test_programs/execution_failure/unused_array_set_known_index_out_of_bounds/src/main.nr create mode 100644 noir/noir-repo/test_programs/execution_failure/unused_array_set_unknown_index_out_of_bounds/Nargo.toml create mode 100644 noir/noir-repo/test_programs/execution_failure/unused_array_set_unknown_index_out_of_bounds/Prover.toml create mode 100644 noir/noir-repo/test_programs/execution_failure/unused_array_set_unknown_index_out_of_bounds/src/main.nr create mode 100644 noir/noir-repo/test_programs/execution_failure/unused_slice_get_known_index_out_of_bounds/Nargo.toml create mode 100644 noir/noir-repo/test_programs/execution_failure/unused_slice_get_known_index_out_of_bounds/Prover.toml create mode 100644 noir/noir-repo/test_programs/execution_failure/unused_slice_get_known_index_out_of_bounds/src/main.nr create mode 100644 noir/noir-repo/test_programs/execution_failure/unused_slice_get_unknown_index_out_of_bounds/Nargo.toml create mode 100644 noir/noir-repo/test_programs/execution_failure/unused_slice_get_unknown_index_out_of_bounds/Prover.toml create mode 100644 noir/noir-repo/test_programs/execution_failure/unused_slice_get_unknown_index_out_of_bounds/src/main.nr create mode 100644 noir/noir-repo/test_programs/execution_success/brillig_loop_size_regression/Nargo.toml create mode 100644 noir/noir-repo/test_programs/execution_success/brillig_loop_size_regression/Prover.toml create mode 100644 noir/noir-repo/test_programs/execution_success/brillig_loop_size_regression/src/main.nr delete mode 100644 noir/noir-repo/test_programs/execution_success/verify_honk_proof/Nargo.toml 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/test_programs/gates_report_brillig.sh create mode 100644 noir/noir-repo/tooling/lsp/src/requests/completion/auto_import.rs create mode 100644 noir/noir-repo/tooling/lsp/src/requests/completion/completion_items.rs create mode 100644 noir/noir-repo/tooling/lsp/src/requests/completion/kinds.rs create mode 100644 noir/noir-repo/tooling/lsp/src/requests/completion/sort_text.rs create mode 100644 noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs create mode 100644 noir/noir-repo/tooling/lsp/src/requests/completion/traversal.rs create mode 100644 noir/noir-repo/tooling/lsp/src/requests/signature_help.rs create mode 100644 noir/noir-repo/tooling/lsp/src/requests/signature_help/tests.rs create mode 100644 noir/noir-repo/tooling/lsp/src/requests/signature_help/traversal.rs create mode 100644 noir/noir-repo/tooling/nargo_fmt/tests/expected/unsafe.nr create mode 100644 noir/noir-repo/tooling/nargo_fmt/tests/input/unsafe.nr diff --git a/.noir-sync-commit b/.noir-sync-commit index d575ffe3e8c9..540b561247f5 100644 --- a/.noir-sync-commit +++ b/.noir-sync-commit @@ -1 +1 @@ -0ebf1fee471641db0bffcc8307d20327613c78c1 +0b297b3830ac26551bfb39fad01d74cd8ab341c3 diff --git a/noir/noir-repo/.github/workflows/gates_report.yml b/noir/noir-repo/.github/workflows/gates_report.yml index 0cc94a1a04de..0b0a527b69eb 100644 --- a/noir/noir-repo/.github/workflows/gates_report.yml +++ b/noir/noir-repo/.github/workflows/gates_report.yml @@ -80,7 +80,7 @@ jobs: - name: Compare gates reports id: gates_diff - uses: vezenovm/noir-gates-diff@acf12797860f237117e15c0d6e08d64253af52b6 + uses: noir-lang/noir-gates-diff@1931aaaa848a1a009363d6115293f7b7fc72bb87 with: report: gates_report.json summaryQuantile: 0.9 # only display the 10% most significant circuit size diffs in the summary (defaults to 20%) diff --git a/noir/noir-repo/.github/workflows/gates_report_brillig.yml b/noir/noir-repo/.github/workflows/gates_report_brillig.yml new file mode 100644 index 000000000000..4e12a6fcbca8 --- /dev/null +++ b/noir/noir-repo/.github/workflows/gates_report_brillig.yml @@ -0,0 +1,92 @@ +name: Report Brillig bytecode size diff + +on: + push: + branches: + - master + pull_request: + +jobs: + build-nargo: + runs-on: ubuntu-latest + strategy: + matrix: + target: [x86_64-unknown-linux-gnu] + + steps: + - name: Checkout Noir repo + uses: actions/checkout@v4 + + - name: Setup toolchain + uses: dtolnay/rust-toolchain@1.74.1 + + - uses: Swatinem/rust-cache@v2 + with: + key: ${{ matrix.target }} + cache-on-failure: true + save-if: ${{ github.event_name != 'merge_group' }} + + - name: Build Nargo + run: cargo build --package nargo_cli --release + + - name: Package artifacts + run: | + mkdir dist + cp ./target/release/nargo ./dist/nargo + 7z a -ttar -so -an ./dist/* | 7z a -si ./nargo-x86_64-unknown-linux-gnu.tar.gz + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: nargo + path: ./dist/* + retention-days: 3 + + compare_brillig_bytecode_size_reports: + needs: [build-nargo] + runs-on: ubuntu-latest + permissions: + pull-requests: write + + steps: + - uses: actions/checkout@v4 + + - name: Download nargo binary + uses: actions/download-artifact@v4 + with: + name: nargo + path: ./nargo + + - name: Set nargo on PATH + run: | + nargo_binary="${{ github.workspace }}/nargo/nargo" + chmod +x $nargo_binary + echo "$(dirname $nargo_binary)" >> $GITHUB_PATH + export PATH="$PATH:$(dirname $nargo_binary)" + nargo -V + + - name: Generate Brillig bytecode size report + working-directory: ./test_programs + run: | + chmod +x gates_report_brillig.sh + ./gates_report_brillig.sh + mv gates_report_brillig.json ../gates_report_brillig.json + + - name: Compare Brillig bytecode size reports + id: brillig_bytecode_diff + uses: noir-lang/noir-gates-diff@3fb844067b25d1b59727ea600b614503b33503f4 + with: + report: gates_report_brillig.json + header: | + # Changes to Brillig bytecode sizes + brillig_report: true + summaryQuantile: 0.9 # only display the 10% most significant bytecode size diffs in the summary (defaults to 20%) + + - name: Add bytecode size diff to sticky comment + if: github.event_name == 'pull_request' || github.event_name == 'pull_request_target' + uses: marocchino/sticky-pull-request-comment@v2 + with: + header: brillig + # delete the comment in case changes no longer impact brillig bytecode sizes + delete: ${{ !steps.brillig_bytecode_diff.outputs.markdown }} + message: ${{ steps.brillig_bytecode_diff.outputs.markdown }} \ No newline at end of file diff --git a/noir/noir-repo/.gitignore b/noir/noir-repo/.gitignore index 2c877a4d02c4..aeb7d8757c4a 100644 --- a/noir/noir-repo/.gitignore +++ b/noir/noir-repo/.gitignore @@ -33,6 +33,7 @@ tooling/noir_js/lib !compiler/wasm/noir-script/target gates_report.json +gates_report_brillig.json # Github Actions scratch space # This gives a location to download artifacts into the repository in CI without making git dirty. diff --git a/noir/noir-repo/Cargo.lock b/noir/noir-repo/Cargo.lock index bacbf939786b..e1159c712019 100644 --- a/noir/noir-repo/Cargo.lock +++ b/noir/noir-repo/Cargo.lock @@ -2722,6 +2722,7 @@ dependencies = [ "acvm", "async-lsp", "codespan-lsp", + "convert_case 0.6.0", "fm", "fxhash", "lsp-types 0.94.1", diff --git a/noir/noir-repo/Cargo.toml b/noir/noir-repo/Cargo.toml index 635091700572..7d9b3254c53c 100644 --- a/noir/noir-repo/Cargo.toml +++ b/noir/noir-repo/Cargo.toml @@ -50,6 +50,12 @@ rust-version = "1.74.1" license = "MIT OR Apache-2.0" repository = "https://github.com/noir-lang/noir/" +[workspace.lints.rust] +trivial_casts = "warn" +trivial_numeric_casts = "warn" +unused_import_braces = "warn" +unused_qualifications = "warn" + [workspace.dependencies] # ACVM workspace dependencies diff --git a/noir/noir-repo/acvm-repo/acir/Cargo.toml b/noir/noir-repo/acvm-repo/acir/Cargo.toml index 88616ccffb59..860b565544bf 100644 --- a/noir/noir-repo/acvm-repo/acir/Cargo.toml +++ b/noir/noir-repo/acvm-repo/acir/Cargo.toml @@ -10,6 +10,9 @@ license.workspace = true rust-version.workspace = true repository.workspace = true +[lints] +workspace = true + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] 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 00d0933a3aaa..43984e4a922c 100644 --- a/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs @@ -103,7 +103,7 @@ impl ErrorSelector { impl Serialize for ErrorSelector { fn serialize(&self, serializer: S) -> Result where - S: serde::Serializer, + S: Serializer, { self.0.to_string().serialize(serializer) } @@ -112,7 +112,7 @@ impl Serialize for ErrorSelector { impl<'de> Deserialize<'de> for ErrorSelector { fn deserialize(deserializer: D) -> Result where - D: serde::Deserializer<'de>, + D: Deserializer<'de>, { let s: String = Deserialize::deserialize(deserializer)?; let as_u64 = s.parse().map_err(serde::de::Error::custom)?; @@ -224,7 +224,7 @@ impl Circuit { } impl Program { - fn write(&self, writer: W) -> std::io::Result<()> { + fn write(&self, writer: W) -> std::io::Result<()> { let buf = bincode::serialize(self).unwrap(); let mut encoder = flate2::write::GzEncoder::new(writer, Compression::default()); encoder.write_all(&buf)?; @@ -250,7 +250,7 @@ impl Program { } impl Deserialize<'a>> Program { - fn read(reader: R) -> std::io::Result { + fn read(reader: R) -> std::io::Result { let mut gz_decoder = flate2::read::GzDecoder::new(reader); let mut buf_d = Vec::new(); gz_decoder.read_to_end(&mut buf_d)?; 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 b1fdc5e0080b..d7f0f5f6f1f9 100644 --- a/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes.rs +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes.rs @@ -2,6 +2,10 @@ use super::{ brillig::{BrilligFunctionId, BrilligInputs, BrilligOutputs}, directives::Directive, }; + +pub mod function_id; +pub use function_id::AcirFunctionId; + use crate::native_types::{Expression, Witness}; use acir_field::AcirField; use serde::{Deserialize, Serialize}; @@ -125,7 +129,7 @@ pub enum Opcode { Call { /// Id for the function being called. It is the responsibility of the executor /// to fetch the appropriate circuit from this id. - id: u32, + id: AcirFunctionId, /// Inputs to the function call inputs: Vec, /// Outputs of the function call diff --git a/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/function_id.rs b/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/function_id.rs new file mode 100644 index 000000000000..b5abb1b3942a --- /dev/null +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/function_id.rs @@ -0,0 +1,17 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize, Hash)] +#[serde(transparent)] +pub struct AcirFunctionId(pub u32); + +impl AcirFunctionId { + pub fn as_usize(&self) -> usize { + self.0 as usize + } +} + +impl std::fmt::Display for AcirFunctionId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} 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 1a634eeea9cd..ce28d47021cc 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 @@ -14,7 +14,7 @@ use std::collections::BTreeSet; use acir::{ circuit::{ brillig::{BrilligBytecode, BrilligFunctionId, BrilligInputs, BrilligOutputs}, - opcodes::{BlackBoxFuncCall, BlockId, FunctionInput, MemOp}, + opcodes::{AcirFunctionId, BlackBoxFuncCall, BlockId, FunctionInput, MemOp}, Circuit, Opcode, Program, PublicInputs, }, native_types::{Expression, Witness}, @@ -381,13 +381,13 @@ fn nested_acir_call_circuit() { // x // } let nested_call = Opcode::Call { - id: 1, + id: AcirFunctionId(1), inputs: vec![Witness(0), Witness(1)], outputs: vec![Witness(2)], predicate: None, }; let nested_call_two = Opcode::Call { - id: 1, + id: AcirFunctionId(1), inputs: vec![Witness(0), Witness(1)], outputs: vec![Witness(3)], predicate: None, @@ -419,7 +419,7 @@ fn nested_acir_call_circuit() { q_c: FieldElement::one() + FieldElement::one(), }); let call = Opcode::Call { - id: 2, + id: AcirFunctionId(2), inputs: vec![Witness(2), Witness(1)], outputs: vec![Witness(3)], predicate: None, diff --git a/noir/noir-repo/acvm-repo/acir_field/Cargo.toml b/noir/noir-repo/acvm-repo/acir_field/Cargo.toml index a037a453348e..c1cffc1334e9 100644 --- a/noir/noir-repo/acvm-repo/acir_field/Cargo.toml +++ b/noir/noir-repo/acvm-repo/acir_field/Cargo.toml @@ -10,6 +10,9 @@ license.workspace = true rust-version.workspace = true repository.workspace = true +[lints] +workspace = true + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] diff --git a/noir/noir-repo/acvm-repo/acir_field/src/field_element.rs b/noir/noir-repo/acvm-repo/acir_field/src/field_element.rs index 92ccbc4e5f63..2323f008dbeb 100644 --- a/noir/noir-repo/acvm-repo/acir_field/src/field_element.rs +++ b/noir/noir-repo/acvm-repo/acir_field/src/field_element.rs @@ -115,7 +115,7 @@ impl From for FieldElement { } } -impl Serialize for FieldElement { +impl Serialize for FieldElement { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -124,7 +124,7 @@ impl Serialize for FieldElement { } } -impl<'de, T: ark_ff::PrimeField> Deserialize<'de> for FieldElement { +impl<'de, T: PrimeField> Deserialize<'de> for FieldElement { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, diff --git a/noir/noir-repo/acvm-repo/acir_field/src/generic_ark.rs b/noir/noir-repo/acvm-repo/acir_field/src/generic_ark.rs index f7228e663147..74927c07a363 100644 --- a/noir/noir-repo/acvm-repo/acir_field/src/generic_ark.rs +++ b/noir/noir-repo/acvm-repo/acir_field/src/generic_ark.rs @@ -2,7 +2,7 @@ use num_bigint::BigUint; /// This trait is extremely unstable and WILL have breaking changes. pub trait AcirField: - std::marker::Sized + Sized + std::fmt::Display + std::fmt::Debug + Default @@ -24,7 +24,7 @@ pub trait AcirField: // + From + From + std::hash::Hash - + std::cmp::Eq + + Eq { fn one() -> Self; fn zero() -> Self; diff --git a/noir/noir-repo/acvm-repo/acvm/Cargo.toml b/noir/noir-repo/acvm-repo/acvm/Cargo.toml index 2ee4d529e5a5..bf1170ce0732 100644 --- a/noir/noir-repo/acvm-repo/acvm/Cargo.toml +++ b/noir/noir-repo/acvm-repo/acvm/Cargo.toml @@ -10,6 +10,9 @@ license.workspace = true rust-version.workspace = true repository.workspace = true +[lints] +workspace = true + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] 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 83c5aeb62960..647c11bd3c38 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs @@ -6,7 +6,7 @@ use acir::{ brillig::ForeignCallResult, circuit::{ brillig::{BrilligBytecode, BrilligFunctionId}, - opcodes::{BlockId, ConstantOrWitnessEnum, FunctionInput}, + opcodes::{AcirFunctionId, BlockId, ConstantOrWitnessEnum, FunctionInput}, AssertionPayload, ErrorSelector, ExpressionOrMemory, Opcode, OpcodeLocation, RawAssertionPayload, ResolvedAssertionPayload, STRING_ERROR_SELECTOR, }, @@ -575,7 +575,7 @@ impl<'a, F: AcirField, B: BlackBoxFunctionSolver> ACVM<'a, F, B> { else { unreachable!("Not executing a Call opcode"); }; - if *id == 0 { + if *id == AcirFunctionId(0) { return Err(OpcodeResolutionError::AcirMainCallAttempted { opcode_location: ErrorLocation::Resolved(OpcodeLocation::Acir( self.instruction_pointer(), @@ -716,7 +716,7 @@ pub(crate) fn is_predicate_false( #[derive(Debug, Clone, PartialEq)] pub struct AcirCallWaitInfo { /// Index in the list of ACIR function's that should be called - pub id: u32, + pub id: AcirFunctionId, /// Initial witness for the given circuit to be called pub initial_witness: WitnessMap, } diff --git a/noir/noir-repo/acvm-repo/acvm_js/Cargo.toml b/noir/noir-repo/acvm-repo/acvm_js/Cargo.toml index 0d90b9ba54f9..6b457a8f5f8c 100644 --- a/noir/noir-repo/acvm-repo/acvm_js/Cargo.toml +++ b/noir/noir-repo/acvm-repo/acvm_js/Cargo.toml @@ -10,6 +10,9 @@ license.workspace = true rust-version.workspace = true repository.workspace = true +[lints] +workspace = true + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] 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/src/execute.rs b/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs index c596dcf96145..98a0c4c3abe1 100644 --- a/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs +++ b/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs @@ -250,7 +250,7 @@ impl<'a, B: BlackBoxFunctionSolver> ProgramExecutor<'a, B> { acvm.resolve_pending_foreign_call(result); } ACVMStatus::RequiresAcirCall(call_info) => { - let acir_to_call = &self.functions[call_info.id as usize]; + let acir_to_call = &self.functions[call_info.id.as_usize()]; let initial_witness = call_info.initial_witness; let call_solved_witness = self .execute_circuit(acir_to_call, initial_witness, witness_stack) @@ -267,7 +267,7 @@ impl<'a, B: BlackBoxFunctionSolver> ProgramExecutor<'a, B> { } } acvm.resolve_pending_acir_call(call_resolved_outputs); - witness_stack.push(call_info.id, call_solved_witness.clone()); + witness_stack.push(call_info.id.0, call_solved_witness.clone()); } } } diff --git a/noir/noir-repo/acvm-repo/acvm_js/src/js_execution_error.rs b/noir/noir-repo/acvm-repo/acvm_js/src/js_execution_error.rs index 23da88247fc7..f6a00af79422 100644 --- a/noir/noir-repo/acvm-repo/acvm_js/src/js_execution_error.rs +++ b/noir/noir-repo/acvm-repo/acvm_js/src/js_execution_error.rs @@ -54,16 +54,14 @@ impl JsExecutionError { None => JsValue::UNDEFINED, }; let assertion_payload = match assertion_payload { - Some(raw) => ::from_serde(&raw) + Some(raw) => ::from_serde(&raw) .expect("Cannot serialize assertion payload"), None => JsValue::UNDEFINED, }; let brillig_function_id = match brillig_function_id { - Some(function_id) => { - ::from_serde(&function_id) - .expect("Cannot serialize Brillig function id") - } + Some(function_id) => ::from_serde(&function_id) + .expect("Cannot serialize Brillig function id"), None => JsValue::UNDEFINED, }; diff --git a/noir/noir-repo/acvm-repo/blackbox_solver/Cargo.toml b/noir/noir-repo/acvm-repo/blackbox_solver/Cargo.toml index 10b491c7f676..d0473857e1c2 100644 --- a/noir/noir-repo/acvm-repo/blackbox_solver/Cargo.toml +++ b/noir/noir-repo/acvm-repo/blackbox_solver/Cargo.toml @@ -10,6 +10,9 @@ license.workspace = true rust-version.workspace = true repository.workspace = true +[lints] +workspace = true + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] 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 ab677396c220..9a23f503a06e 100644 --- a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/Cargo.toml +++ b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/Cargo.toml @@ -10,6 +10,9 @@ license.workspace = true rust-version.workspace = true repository.workspace = true +[lints] +workspace = true + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] diff --git a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/generator/generators.rs b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/generator/generators.rs index bb51426b33b5..a4125014d565 100644 --- a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/generator/generators.rs +++ b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/generator/generators.rs @@ -91,7 +91,7 @@ mod test { fn test_derive_generators() { let res = derive_generators("test domain".as_bytes(), 128, 0); - let is_unique = |y: Affine, j: usize| -> bool { + let is_unique = |y: Affine, j: usize| -> bool { for (i, res) in res.iter().enumerate() { if i != j && *res == y { return false; diff --git a/noir/noir-repo/acvm-repo/brillig/Cargo.toml b/noir/noir-repo/acvm-repo/brillig/Cargo.toml index 631acbd55d87..9f5a5ce0ee23 100644 --- a/noir/noir-repo/acvm-repo/brillig/Cargo.toml +++ b/noir/noir-repo/acvm-repo/brillig/Cargo.toml @@ -10,6 +10,9 @@ license.workspace = true rust-version.workspace = true repository.workspace = true +[lints] +workspace = true + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] diff --git a/noir/noir-repo/acvm-repo/brillig_vm/Cargo.toml b/noir/noir-repo/acvm-repo/brillig_vm/Cargo.toml index ebd35f1579fe..cd9f754ebc0d 100644 --- a/noir/noir-repo/acvm-repo/brillig_vm/Cargo.toml +++ b/noir/noir-repo/acvm-repo/brillig_vm/Cargo.toml @@ -10,6 +10,9 @@ license.workspace = true rust-version.workspace = true repository.workspace = true +[lints] +workspace = true + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] diff --git a/noir/noir-repo/aztec_macros/Cargo.toml b/noir/noir-repo/aztec_macros/Cargo.toml index a99a654aeed9..c9d88e36e282 100644 --- a/noir/noir-repo/aztec_macros/Cargo.toml +++ b/noir/noir-repo/aztec_macros/Cargo.toml @@ -7,6 +7,9 @@ rust-version.workspace = true license.workspace = true repository.workspace = true +[lints] +workspace = true + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] 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 dd3ec7f6a757..7a8a71878577 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs @@ -324,101 +324,105 @@ pub fn update_fn_signatures_in_contract_interface( }); if let Some(interface_struct) = maybe_interface_struct { - let methods = context.def_interner.get_struct_methods(interface_struct.borrow().id); - - for func_id in methods.iter().flat_map(|methods| methods.direct.iter()) { - let name = context.def_interner.function_name(func_id); - let fn_parameters = &context.def_interner.function_meta(func_id).parameters.clone(); - - if name == "at" || name == "interface" || name == "storage" { - continue; - } - - let fn_signature_hash = compute_fn_signature_hash( - name, - &fn_parameters - .iter() - .skip(1) - .map(|(_, typ, _)| typ.clone()) - .collect::>(), - ); - let hir_func = context.def_interner.function(func_id).block(&context.def_interner); - - let function_selector_statement = context.def_interner.statement( - hir_func.statements().get(hir_func.statements().len() - 2).ok_or(( - AztecMacroError::CouldNotGenerateContractInterface { - secondary_message: Some( - "Function signature statement not found, invalid body length" - .to_string(), - ), - }, - file_id, - ))?, - ); - let function_selector_expression_id = match function_selector_statement { - HirStatement::Let(let_statement) => Ok(let_statement.expression), - _ => Err(( - AztecMacroError::CouldNotGenerateContractInterface { - secondary_message: Some( - "Function selector statement must be an expression".to_string(), - ), - }, - file_id, - )), - }?; - let function_selector_expression = - context.def_interner.expression(&function_selector_expression_id); - - let current_fn_signature_expression_id = match function_selector_expression { - HirExpression::Call(call_expr) => Ok(call_expr.arguments[0]), - _ => Err(( - AztecMacroError::CouldNotGenerateContractInterface { - secondary_message: Some( - "Function selector argument expression must be call expression" - .to_string(), - ), - }, - file_id, - )), - }?; - - let current_fn_signature_expression = - context.def_interner.expression(¤t_fn_signature_expression_id); + if let Some(methods) = + context.def_interner.get_struct_methods(interface_struct.borrow().id).cloned() + { + for func_id in methods.iter().flat_map(|(_name, methods)| methods.direct.iter()) { + let name = context.def_interner.function_name(func_id); + let fn_parameters = + &context.def_interner.function_meta(func_id).parameters.clone(); + + if name == "at" || name == "interface" || name == "storage" { + continue; + } - match current_fn_signature_expression { - HirExpression::Literal(HirLiteral::Integer(value, _)) => { - if !value.is_zero() { - Err(( + let fn_signature_hash = compute_fn_signature_hash( + name, + &fn_parameters + .iter() + .skip(1) + .map(|(_, typ, _)| typ.clone()) + .collect::>(), + ); + let hir_func = + context.def_interner.function(func_id).block(&context.def_interner); + + let function_selector_statement = context.def_interner.statement( + hir_func.statements().get(hir_func.statements().len() - 2).ok_or(( + AztecMacroError::CouldNotGenerateContractInterface { + secondary_message: Some( + "Function signature statement not found, invalid body length" + .to_string(), + ), + }, + file_id, + ))?, + ); + let function_selector_expression_id = match function_selector_statement { + HirStatement::Let(let_statement) => Ok(let_statement.expression), + _ => Err(( + AztecMacroError::CouldNotGenerateContractInterface { + secondary_message: Some( + "Function selector statement must be an expression".to_string(), + ), + }, + file_id, + )), + }?; + let function_selector_expression = + context.def_interner.expression(&function_selector_expression_id); + + let current_fn_signature_expression_id = match function_selector_expression { + HirExpression::Call(call_expr) => Ok(call_expr.arguments[0]), + _ => Err(( + AztecMacroError::CouldNotGenerateContractInterface { + secondary_message: Some( + "Function selector argument expression must be call expression" + .to_string(), + ), + }, + file_id, + )), + }?; + + let current_fn_signature_expression = + context.def_interner.expression(¤t_fn_signature_expression_id); + + match current_fn_signature_expression { + HirExpression::Literal(HirLiteral::Integer(value, _)) => { + if !value.is_zero() { + Err(( AztecMacroError::CouldNotGenerateContractInterface { secondary_message: Some( "Function signature argument must be a placeholder with value 0".to_string()), }, file_id, )) - } else { - Ok(()) + } else { + Ok(()) + } } - } - _ => Err(( - AztecMacroError::CouldNotGenerateContractInterface { - secondary_message: Some( - "Function signature argument must be a literal field element" - .to_string(), - ), + _ => Err(( + AztecMacroError::CouldNotGenerateContractInterface { + secondary_message: Some( + "Function signature argument must be a literal field element" + .to_string(), + ), + }, + file_id, + )), + }?; + + context.def_interner.update_expression( + current_fn_signature_expression_id, + |expr| { + *expr = HirExpression::Literal(HirLiteral::Integer( + FieldElement::from(fn_signature_hash as u128), + false, + )) }, - file_id, - )), - }?; - - context.def_interner.update_expression( - current_fn_signature_expression_id, - |expr| { - *expr = HirExpression::Literal(HirLiteral::Integer( - FieldElement::from(fn_signature_hash as u128), - false, - )) - }, - ); + ); + } } } } diff --git a/noir/noir-repo/aztec_macros/src/utils/parse_utils.rs b/noir/noir-repo/aztec_macros/src/utils/parse_utils.rs index 3b3813da6ee3..6b5db103c0b1 100644 --- a/noir/noir-repo/aztec_macros/src/utils/parse_utils.rs +++ b/noir/noir-repo/aztec_macros/src/utils/parse_utils.rs @@ -267,6 +267,9 @@ fn empty_expression(expression: &mut Expression) { ExpressionKind::Comptime(block_expression, _span) => { empty_block_expression(block_expression); } + ExpressionKind::Unsafe(block_expression, _span) => { + empty_block_expression(block_expression); + } ExpressionKind::Quote(..) | ExpressionKind::Resolved(_) | ExpressionKind::Error => (), ExpressionKind::AsTraitPath(path) => { empty_unresolved_type(&mut path.typ); @@ -325,7 +328,7 @@ fn empty_unresolved_type(unresolved_type: &mut UnresolvedType) { empty_unresolved_type(unresolved_type) } UnresolvedTypeData::Tuple(unresolved_types) => empty_unresolved_types(unresolved_types), - UnresolvedTypeData::Function(args, ret, _env) => { + UnresolvedTypeData::Function(args, ret, _env, _) => { empty_unresolved_types(args); empty_unresolved_type(ret); } diff --git a/noir/noir-repo/compiler/fm/Cargo.toml b/noir/noir-repo/compiler/fm/Cargo.toml index b48f445be367..c367333a6f48 100644 --- a/noir/noir-repo/compiler/fm/Cargo.toml +++ b/noir/noir-repo/compiler/fm/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true rust-version.workspace = true license.workspace = true +[lints] +workspace = true + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] diff --git a/noir/noir-repo/compiler/noirc_arena/Cargo.toml b/noir/noir-repo/compiler/noirc_arena/Cargo.toml index b94f997b7b9d..68fc4d1c920f 100644 --- a/noir/noir-repo/compiler/noirc_arena/Cargo.toml +++ b/noir/noir-repo/compiler/noirc_arena/Cargo.toml @@ -5,3 +5,6 @@ authors.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true + +[lints] +workspace = true \ No newline at end of file diff --git a/noir/noir-repo/compiler/noirc_driver/Cargo.toml b/noir/noir-repo/compiler/noirc_driver/Cargo.toml index a9949f5093a9..b244018cc711 100644 --- a/noir/noir-repo/compiler/noirc_driver/Cargo.toml +++ b/noir/noir-repo/compiler/noirc_driver/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true rust-version.workspace = true license.workspace = true +[lints] +workspace = true + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [build-dependencies] 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 87181b285de8..e2692349baa3 100644 --- a/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs +++ b/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs @@ -107,7 +107,7 @@ pub(super) fn abi_type_from_hir_type(context: &Context, typ: &Type) -> AbiType { | Type::Forall(..) | Type::Quoted(_) | Type::Slice(_) - | Type::Function(_, _, _) => unreachable!("{typ} cannot be used in the abi"), + | Type::Function(_, _, _, _) => unreachable!("{typ} cannot be used in the abi"), Type::FmtString(_, _) => unreachable!("format strings cannot be used in the abi"), Type::MutableReference(_) => unreachable!("&mut cannot be used in the abi"), } diff --git a/noir/noir-repo/compiler/noirc_driver/src/lib.rs b/noir/noir-repo/compiler/noirc_driver/src/lib.rs index 72c95823553f..467bda2ca88c 100644 --- a/noir/noir-repo/compiler/noirc_driver/src/lib.rs +++ b/noir/noir-repo/compiler/noirc_driver/src/lib.rs @@ -274,11 +274,8 @@ pub fn check_crate( crate_id: CrateId, options: &CompileOptions, ) -> CompilationResult<()> { - let macros: &[&dyn MacroProcessor] = if options.disable_macros { - &[] - } else { - &[&aztec_macros::AztecMacro as &dyn MacroProcessor] - }; + let macros: &[&dyn MacroProcessor] = + if options.disable_macros { &[] } else { &[&aztec_macros::AztecMacro] }; let mut errors = vec![]; let diagnostics = CrateDefMap::collect_defs( @@ -557,6 +554,7 @@ pub fn compile_no_check( let force_compile = force_compile || options.print_acir || options.show_brillig + || options.force_brillig || options.show_ssa || options.emit_ssa; @@ -578,7 +576,7 @@ pub fn compile_no_check( emit_ssa: if options.emit_ssa { Some(context.package_build_path.clone()) } else { None }, }; - let SsaProgramArtifact { program, debug, warnings, names, error_types, .. } = + let SsaProgramArtifact { program, debug, warnings, names, brillig_names, error_types, .. } = create_program(program, &ssa_evaluator_options)?; let abi = abi_gen::gen_abi(context, &main_function, return_visibility, error_types); @@ -593,5 +591,6 @@ pub fn compile_no_check( noir_version: NOIR_ARTIFACT_VERSION_STRING.to_string(), warnings, names, + brillig_names, }) } diff --git a/noir/noir-repo/compiler/noirc_driver/src/program.rs b/noir/noir-repo/compiler/noirc_driver/src/program.rs index 8e02de0b8b32..884604829285 100644 --- a/noir/noir-repo/compiler/noirc_driver/src/program.rs +++ b/noir/noir-repo/compiler/noirc_driver/src/program.rs @@ -29,4 +29,6 @@ pub struct CompiledProgram { pub warnings: Vec, /// Names of the functions in the program. These are used for more informative debugging and benchmarking. pub names: Vec, + /// Names of the unconstrained functions in the program. + pub brillig_names: Vec, } diff --git a/noir/noir-repo/compiler/noirc_errors/Cargo.toml b/noir/noir-repo/compiler/noirc_errors/Cargo.toml index 41b1cd0ff580..61b274c605f0 100644 --- a/noir/noir-repo/compiler/noirc_errors/Cargo.toml +++ b/noir/noir-repo/compiler/noirc_errors/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true rust-version.workspace = true license.workspace = true +[lints] +workspace = true + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] diff --git a/noir/noir-repo/compiler/noirc_errors/src/position.rs b/noir/noir-repo/compiler/noirc_errors/src/position.rs index 02b242e8b4da..1792197eab72 100644 --- a/noir/noir-repo/compiler/noirc_errors/src/position.rs +++ b/noir/noir-repo/compiler/noirc_errors/src/position.rs @@ -16,7 +16,7 @@ pub struct Spanned { /// This is important for tests. Two Spanned objects are equal if their content is equal /// They may not have the same span. Use into_span to test for Span being equal specifically -impl PartialEq> for Spanned { +impl PartialEq> for Spanned { fn eq(&self, other: &Spanned) -> bool { self.contents == other.contents } diff --git a/noir/noir-repo/compiler/noirc_evaluator/Cargo.toml b/noir/noir-repo/compiler/noirc_evaluator/Cargo.toml index 66c770b50648..81feb0b71547 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/Cargo.toml +++ b/noir/noir-repo/compiler/noirc_evaluator/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true rust-version.workspace = true license.workspace = true +[lints] +workspace = true + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen.rs index ee61a9d13d3c..d6364cefc9a3 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen.rs @@ -27,5 +27,7 @@ pub(crate) fn convert_ssa_function( BrilligBlock::compile(&mut function_context, &mut brillig_context, block, &func.dfg); } - brillig_context.artifact() + let mut artifact = brillig_context.artifact(); + artifact.name = func.name().to_string(); + artifact } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs index dff4da56c1ec..fca1f60544dc 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs @@ -55,6 +55,7 @@ pub(crate) fn directive_invert() -> GeneratedBrillig { ], assert_messages: Default::default(), locations: Default::default(), + name: "directive_invert".to_string(), } } @@ -109,5 +110,6 @@ pub(crate) fn directive_quotient() -> GeneratedBrillig { ], assert_messages: Default::default(), locations: Default::default(), + name: "directive_integer_quotient".to_string(), } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs index 2d0bdb5955c0..53964f641707 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs @@ -22,6 +22,7 @@ pub(crate) struct GeneratedBrillig { pub(crate) byte_code: Vec>, pub(crate) locations: BTreeMap, pub(crate) assert_messages: BTreeMap, + pub(crate) name: String, } #[derive(Default, Debug, Clone)] @@ -49,6 +50,8 @@ pub(crate) struct BrilligArtifact { locations: BTreeMap, /// The current call stack. All opcodes that are pushed will be associated with this call stack. call_stack: CallStack, + /// Name of the function, only used for debugging purposes. + pub(crate) name: String, } /// A pointer to a location in the opcode. @@ -81,6 +84,7 @@ impl BrilligArtifact { byte_code: self.byte_code, locations: self.locations, assert_messages: self.assert_messages, + name: self.name, } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs index b632958f37f1..9daf98e606ba 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs @@ -151,6 +151,7 @@ pub struct SsaProgramArtifact { pub main_input_witnesses: Vec, pub main_return_witnesses: Vec, pub names: Vec, + pub brillig_names: Vec, pub error_types: BTreeMap, } @@ -167,6 +168,7 @@ impl SsaProgramArtifact { main_input_witnesses: Vec::default(), main_return_witnesses: Vec::default(), names: Vec::default(), + brillig_names: Vec::default(), error_types, } } @@ -202,8 +204,10 @@ pub fn create_program( let func_sigs = program.function_signatures.clone(); let recursive = program.recursive; - let ArtifactsAndWarnings((generated_acirs, generated_brillig, error_types), ssa_level_warnings) = - optimize_into_acir(program, options)?; + let ArtifactsAndWarnings( + (generated_acirs, generated_brillig, brillig_function_names, error_types), + ssa_level_warnings, + ) = optimize_into_acir(program, options)?; if options.force_brillig_output { assert_eq!( generated_acirs.len(), @@ -236,6 +240,7 @@ pub fn create_program( program_artifact.add_circuit(circuit_artifact, is_main); is_main = false; } + program_artifact.brillig_names = brillig_function_names; Ok(program_artifact) } 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 b2a731064687..a8324c11592e 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 @@ -8,7 +8,7 @@ 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::{BrilligFunctionId, BrilligInputs, BrilligOutputs}; -use acvm::acir::circuit::opcodes::{BlockId, BlockType, MemOp}; +use acvm::acir::circuit::opcodes::{AcirFunctionId, BlockId, BlockType, MemOp}; use acvm::acir::circuit::{AssertionPayload, ExpressionOrMemory, ExpressionWidth, Opcode}; use acvm::blackbox_solver; use acvm::brillig_vm::{MemoryValue, VMStatus, VM}; @@ -1959,7 +1959,7 @@ impl AcirContext { pub(crate) fn call_acir_function( &mut self, - id: u32, + id: AcirFunctionId, inputs: Vec, output_count: usize, predicate: AcirVar, @@ -2005,7 +2005,7 @@ impl PartialEq for AcirVarData { } // TODO: check/test this hash impl -impl std::hash::Hash for AcirVarData { +impl Hash for AcirVarData { fn hash(&self, state: &mut H) { core::mem::discriminant(self).hash(state); } 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 22b13aeae797..3e36459a0d67 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 @@ -29,7 +29,7 @@ use crate::brillig::brillig_ir::BrilligContext; use crate::brillig::{brillig_gen::brillig_fn::FunctionContext as BrilligFunctionContext, Brillig}; use crate::errors::{InternalError, InternalWarning, RuntimeError, SsaReport}; pub(crate) use acir_ir::generated_acir::GeneratedAcir; -use acvm::acir::circuit::opcodes::BlockType; +use acvm::acir::circuit::opcodes::{AcirFunctionId, BlockType}; use noirc_frontend::monomorphization::ast::InlineType; use acvm::acir::circuit::brillig::{BrilligBytecode, BrilligFunctionId}; @@ -235,7 +235,7 @@ impl Debug for AcirDynamicArray { #[derive(Debug, Clone)] pub(crate) enum AcirValue { Var(AcirVar, AcirType), - Array(im::Vector), + Array(Vector), DynamicArray(AcirDynamicArray), } @@ -282,6 +282,7 @@ impl AcirValue { pub(crate) type Artifacts = ( Vec>, Vec>, + Vec, BTreeMap, ); @@ -334,11 +335,13 @@ impl Ssa { } } - let brillig = vecmap(shared_context.generated_brillig, |brillig| BrilligBytecode { - bytecode: brillig.byte_code, - }); + let (brillig_bytecode, brillig_names) = shared_context + .generated_brillig + .into_iter() + .map(|brillig| (BrilligBytecode { bytecode: brillig.byte_code }, brillig.name)) + .unzip(); - Ok((acirs, brillig, self.error_selector_to_type)) + Ok((acirs, brillig_bytecode, brillig_names, self.error_selector_to_type)) } } @@ -772,7 +775,7 @@ impl<'a> Context<'a> { .get(id) .expect("ICE: should have an associated final index"); let output_vars = self.acir_context.call_acir_function( - *acir_function_id, + AcirFunctionId(*acir_function_id), inputs, output_count, self.current_side_effects_enabled_var, @@ -955,6 +958,8 @@ impl<'a> Context<'a> { BrilligFunctionContext::return_values(func), BrilligFunctionContext::function_id_to_function_label(func.id()), ); + entry_point.name = func.name().to_string(); + // Link the entry point with all dependencies while let Some(unresolved_fn_label) = entry_point.first_unresolved_function_call() { let artifact = &brillig.find_by_function_label(unresolved_fn_label.clone()); @@ -1645,7 +1650,7 @@ impl<'a> Context<'a> { let read = self.acir_context.read_from_memory(source, &index_var)?; Ok::(AcirValue::Var(read, AcirType::field())) })?; - let array: im::Vector = init_values.into(); + let array: Vector = init_values.into(); self.initialize_array(destination, array_len, Some(AcirValue::Array(array)))?; Ok(()) } @@ -2862,9 +2867,13 @@ fn can_omit_element_sizes_array(array_typ: &Type) -> bool { #[cfg(test)] mod test { + use acvm::{ acir::{ - circuit::{brillig::BrilligFunctionId, ExpressionWidth, Opcode, OpcodeLocation}, + circuit::{ + brillig::BrilligFunctionId, opcodes::AcirFunctionId, ExpressionWidth, Opcode, + OpcodeLocation, + }, native_types::Witness, }, FieldElement, @@ -2966,7 +2975,7 @@ mod test { let ssa = builder.finish(); - let (acir_functions, _, _) = ssa + let (acir_functions, _, _, _) = ssa .into_acir(&Brillig::default(), ExpressionWidth::default()) .expect("Should compile manually written SSA into ACIR"); // Expected result: @@ -3015,8 +3024,18 @@ mod test { let main_opcodes = main_acir.opcodes(); assert_eq!(main_opcodes.len(), 3, "Should have two calls to `foo`"); - check_call_opcode(&main_opcodes[0], 1, vec![Witness(0), Witness(1)], vec![Witness(2)]); - check_call_opcode(&main_opcodes[1], 1, vec![Witness(0), Witness(1)], vec![Witness(3)]); + check_call_opcode( + &main_opcodes[0], + AcirFunctionId(1), + vec![Witness(0), Witness(1)], + vec![Witness(2)], + ); + check_call_opcode( + &main_opcodes[1], + AcirFunctionId(1), + vec![Witness(0), Witness(1)], + vec![Witness(3)], + ); if let Opcode::AssertZero(expr) = &main_opcodes[2] { assert_eq!(expr.linear_combinations[0].0, FieldElement::from(1u128)); @@ -3061,7 +3080,7 @@ mod test { let ssa = builder.finish(); - let (acir_functions, _, _) = ssa + let (acir_functions, _, _, _) = ssa .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` @@ -3071,9 +3090,19 @@ mod test { let main_opcodes = main_acir.opcodes(); assert_eq!(main_opcodes.len(), 3, "Should have two calls to `foo` and an assert"); - check_call_opcode(&main_opcodes[0], 1, vec![Witness(0), Witness(1)], vec![Witness(2)]); + check_call_opcode( + &main_opcodes[0], + AcirFunctionId(1), + vec![Witness(0), Witness(1)], + vec![Witness(2)], + ); // The output of the first call should be the input of the second call - check_call_opcode(&main_opcodes[1], 1, vec![Witness(2), Witness(1)], vec![Witness(3)]); + check_call_opcode( + &main_opcodes[1], + AcirFunctionId(1), + vec![Witness(2), Witness(1)], + vec![Witness(3)], + ); } fn basic_nested_call(inline_type: InlineType) { @@ -3151,7 +3180,7 @@ mod test { let ssa = builder.finish(); - let (acir_functions, _, _) = ssa + let (acir_functions, _, _, _) = ssa .into_acir(&Brillig::default(), ExpressionWidth::default()) .expect("Should compile manually written SSA into ACIR"); @@ -3162,9 +3191,19 @@ mod test { assert_eq!(main_opcodes.len(), 3, "Should have two calls to `foo` and an assert"); // Both of these should call func_with_nested_foo_call f1 - check_call_opcode(&main_opcodes[0], 1, vec![Witness(0), Witness(1)], vec![Witness(2)]); + check_call_opcode( + &main_opcodes[0], + AcirFunctionId(1), + vec![Witness(0), Witness(1)], + vec![Witness(2)], + ); // The output of the first call should be the input of the second call - check_call_opcode(&main_opcodes[1], 1, vec![Witness(0), Witness(1)], vec![Witness(3)]); + check_call_opcode( + &main_opcodes[1], + AcirFunctionId(1), + vec![Witness(0), Witness(1)], + vec![Witness(3)], + ); let func_with_nested_call_acir = &acir_functions[1]; let func_with_nested_call_opcodes = func_with_nested_call_acir.opcodes(); @@ -3177,7 +3216,7 @@ mod test { // Should call foo f2 check_call_opcode( &func_with_nested_call_opcodes[1], - 2, + AcirFunctionId(2), vec![Witness(3), Witness(1)], vec![Witness(4)], ); @@ -3185,7 +3224,7 @@ mod test { fn check_call_opcode( opcode: &Opcode, - expected_id: u32, + expected_id: AcirFunctionId, expected_inputs: Vec, expected_outputs: Vec, ) { @@ -3265,7 +3304,7 @@ mod test { let ssa = builder.finish(); let brillig = ssa.to_brillig(false); - let (acir_functions, brillig_functions, _) = ssa + let (acir_functions, brillig_functions, _, _) = ssa .into_acir(&brillig, ExpressionWidth::default()) .expect("Should compile manually written SSA into ACIR"); @@ -3329,7 +3368,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 + let (acir_functions, brillig_functions, _, _) = ssa .into_acir(&Brillig::default(), ExpressionWidth::default()) .expect("Should compile manually written SSA into ACIR"); @@ -3403,7 +3442,7 @@ mod test { let brillig = ssa.to_brillig(false); println!("{}", ssa); - let (acir_functions, brillig_functions, _) = ssa + let (acir_functions, brillig_functions, _, _) = ssa .into_acir(&brillig, ExpressionWidth::default()) .expect("Should compile manually written SSA into ACIR"); @@ -3491,7 +3530,7 @@ mod test { let brillig = ssa.to_brillig(false); println!("{}", ssa); - let (acir_functions, brillig_functions, _) = ssa + let (acir_functions, brillig_functions, _, _) = ssa .into_acir(&brillig, ExpressionWidth::default()) .expect("Should compile manually written SSA into ACIR"); 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 34d7d595eb93..f06f46d7af8f 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 @@ -171,13 +171,14 @@ impl DataFlowGraph { ctrl_typevars: Option>, call_stack: CallStack, ) -> InsertInstructionResult { - use InsertInstructionResult::*; match instruction.simplify(self, block, ctrl_typevars.clone(), &call_stack) { - SimplifyResult::SimplifiedTo(simplification) => SimplifiedTo(simplification), + SimplifyResult::SimplifiedTo(simplification) => { + InsertInstructionResult::SimplifiedTo(simplification) + } SimplifyResult::SimplifiedToMultiple(simplification) => { - SimplifiedToMultiple(simplification) + InsertInstructionResult::SimplifiedToMultiple(simplification) } - SimplifyResult::Remove => InstructionRemoved, + SimplifyResult::Remove => InsertInstructionResult::InstructionRemoved, result @ (SimplifyResult::SimplifiedToInstruction(_) | SimplifyResult::SimplifiedToInstructionMultiple(_) | SimplifyResult::None) => { 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 1c9a31f0c99d..f1265553b83a 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 @@ -50,7 +50,7 @@ impl Id { // Need to manually implement most impls on Id. // Otherwise rust assumes that Id: Hash only if T: Hash, // which isn't true since the T is not used internally. -impl std::hash::Hash for Id { +impl Hash for Id { fn hash(&self, state: &mut H) { self.index.hash(state); } 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 24519d530ee8..1aa0c2efbd08 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 @@ -2,16 +2,20 @@ //! which the results are unused. use std::collections::HashSet; +use im::Vector; +use noirc_errors::Location; + use crate::ssa::{ ir::{ basic_block::{BasicBlock, BasicBlockId}, dfg::DataFlowGraph, function::Function, - instruction::{Instruction, InstructionId, Intrinsic}, + instruction::{BinaryOp, Instruction, InstructionId, Intrinsic}, post_order::PostOrder, + types::Type, value::{Value, ValueId}, }, - ssa_gen::Ssa, + ssa_gen::{Ssa, SSA_WORD_SIZE}, }; impl Ssa { @@ -20,7 +24,7 @@ impl Ssa { #[tracing::instrument(level = "trace", skip(self))] pub(crate) fn dead_instruction_elimination(mut self) -> Ssa { for function in self.functions.values_mut() { - dead_instruction_elimination(function); + dead_instruction_elimination(function, true); } self } @@ -32,16 +36,29 @@ impl Ssa { /// instructions that reference results from an instruction in another block are evaluated first. /// If we did not iterate blocks in this order we could not safely say whether or not the results /// of its instructions are needed elsewhere. -fn dead_instruction_elimination(function: &mut Function) { +fn dead_instruction_elimination(function: &mut Function, insert_out_of_bounds_checks: bool) { let mut context = Context::default(); 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); + let mut inserted_out_of_bounds_checks = false; + let blocks = PostOrder::with_function(function); for block in blocks.as_slice() { - context.remove_unused_instructions_in_block(function, *block); + inserted_out_of_bounds_checks |= context.remove_unused_instructions_in_block( + function, + *block, + insert_out_of_bounds_checks, + ); + } + + // If we inserted out of bounds check, let's run the pass again with those new + // instructions (we don't want to remove those checks, or instructions that are + // dependencies of those checks) + if inserted_out_of_bounds_checks { + dead_instruction_elimination(function, false); + return; } context.remove_rc_instructions(&mut function.dfg); @@ -71,20 +88,40 @@ impl Context { /// values set. This allows DIE to identify whole chains of unused instructions. (If the /// values referenced by an unused instruction were considered to be used, only the head of /// such chains would be removed.) + /// + /// If `insert_out_of_bounds_checks` is true and there are unused ArrayGet/ArraySet that + /// might be out of bounds, this method will insert out of bounds checks instead of + /// removing unused instructions and return `true`. The idea then is to later call this + /// function again with `insert_out_of_bounds_checks` set to false to effectively remove + /// unused instructions but leave the out of bounds checks. fn remove_unused_instructions_in_block( &mut self, function: &mut Function, block_id: BasicBlockId, - ) { + insert_out_of_bounds_checks: bool, + ) -> bool { let block = &function.dfg[block_id]; self.mark_terminator_values_as_used(function, block); - for instruction_id in block.instructions().iter().rev() { + let instructions_len = block.instructions().len(); + + // Indexes of instructions that might be out of bounds. + // We'll remove those, but before that we'll insert bounds checks for them. + let mut possible_index_out_of_bounds_indexes = Vec::new(); + + for (instruction_index, instruction_id) in block.instructions().iter().rev().enumerate() { + let instruction = &function.dfg[*instruction_id]; + if self.is_unused(*instruction_id, function) { self.instructions_to_remove.insert(*instruction_id); - } else { - let instruction = &function.dfg[*instruction_id]; + if insert_out_of_bounds_checks + && instruction_might_result_in_out_of_bounds(function, instruction) + { + possible_index_out_of_bounds_indexes + .push(instructions_len - instruction_index - 1); + } + } else { use Instruction::*; if matches!(instruction, IncrementRc { .. } | DecrementRc { .. }) { self.rc_instructions.push((*instruction_id, block_id)); @@ -96,9 +133,26 @@ impl Context { } } + // If there are some instructions that might trigger an out of bounds error, + // first add constrain checks. Then run the DIE pass again, which will remove those + // but leave the constrains (any any value needed by those constrains) + if !possible_index_out_of_bounds_indexes.is_empty() { + let inserted_check = self.replace_array_instructions_with_out_of_bounds_checks( + function, + block_id, + &mut possible_index_out_of_bounds_indexes, + ); + // There's a slight chance we didn't insert any checks, so we could proceed with DIE. + if inserted_check { + return true; + } + } + function.dfg[block_id] .instructions_mut() .retain(|instruction| !self.instructions_to_remove.contains(instruction)); + + false } /// Returns true if an instruction can be removed. @@ -166,6 +220,288 @@ impl Context { } } } + + /// Replaces unused ArrayGet/ArraySet instructions with out of bounds checks. + /// Returns `true` if at least one check was inserted. + /// Because some ArrayGet might happen in groups (for composite types), if just + /// some of the instructions in a group are used but not all of them, no check + /// is inserted, so this method might return `false`. + fn replace_array_instructions_with_out_of_bounds_checks( + &mut self, + function: &mut Function, + block_id: BasicBlockId, + possible_index_out_of_bounds_indexes: &mut Vec, + ) -> bool { + let mut inserted_check = false; + + // Keep track of the current side effects condition + let mut side_effects_condition = None; + + // Keep track of the next index we need to handle + let mut next_out_of_bounds_index = possible_index_out_of_bounds_indexes.pop(); + + let instructions = function.dfg[block_id].take_instructions(); + for (index, instruction_id) in instructions.iter().enumerate() { + let instruction_id = *instruction_id; + let instruction = &function.dfg[instruction_id]; + + if let Instruction::EnableSideEffects { condition } = instruction { + side_effects_condition = Some(*condition); + + // We still need to keep the EnableSideEffects instruction + function.dfg[block_id].instructions_mut().push(instruction_id); + continue; + }; + + // If it's an ArrayGet we'll deal with groups of it in case the array type is a composite type, + // and adjust `next_out_of_bounds_index` and `possible_index_out_of_bounds_indexes` accordingly + if let Instruction::ArrayGet { array, .. } = instruction { + handle_array_get_group( + function, + array, + index, + &mut next_out_of_bounds_index, + possible_index_out_of_bounds_indexes, + ); + } + + let Some(out_of_bounds_index) = next_out_of_bounds_index else { + // No more out of bounds instructions to insert, just push the current instruction + function.dfg[block_id].instructions_mut().push(instruction_id); + continue; + }; + + if index != out_of_bounds_index { + // This instruction is not out of bounds: let's just push it + function.dfg[block_id].instructions_mut().push(instruction_id); + continue; + } + + // This is an instruction that might be out of bounds: let's add a constrain. + let (array, index) = match instruction { + Instruction::ArrayGet { array, index } + | Instruction::ArraySet { array, index, .. } => (array, index), + _ => panic!("Expected an ArrayGet or ArraySet instruction here"), + }; + + let call_stack = function.dfg.get_call_stack(instruction_id); + + let (lhs, rhs) = if function.dfg.get_numeric_constant(*index).is_some() { + // If we are here it means the index is known but out of bounds. That's always an error! + let false_const = function.dfg.make_constant(false.into(), Type::bool()); + let true_const = function.dfg.make_constant(true.into(), Type::bool()); + (false_const, true_const) + } else { + // `index` will be relative to the flattened array length, so we need to take that into account + let array_length = function.dfg.type_of_value(*array).flattened_size(); + + // If we are here it means the index is dynamic, so let's add a check that it's less than length + let index = function.dfg.insert_instruction_and_results( + Instruction::Cast(*index, Type::unsigned(SSA_WORD_SIZE)), + block_id, + None, + call_stack.clone(), + ); + let index = index.first(); + + let array_typ = Type::unsigned(SSA_WORD_SIZE); + let array_length = + function.dfg.make_constant((array_length as u128).into(), array_typ); + let is_index_out_of_bounds = function.dfg.insert_instruction_and_results( + Instruction::binary(BinaryOp::Lt, index, array_length), + block_id, + None, + call_stack.clone(), + ); + let is_index_out_of_bounds = is_index_out_of_bounds.first(); + let true_const = function.dfg.make_constant(true.into(), Type::bool()); + (is_index_out_of_bounds, true_const) + }; + + let (lhs, rhs) = apply_side_effects( + side_effects_condition, + lhs, + rhs, + function, + block_id, + call_stack.clone(), + ); + + let message = Some("Index out of bounds".to_owned().into()); + function.dfg.insert_instruction_and_results( + Instruction::Constrain(lhs, rhs, message), + block_id, + None, + call_stack, + ); + inserted_check = true; + + next_out_of_bounds_index = possible_index_out_of_bounds_indexes.pop(); + } + + inserted_check + } +} + +fn instruction_might_result_in_out_of_bounds( + function: &Function, + instruction: &Instruction, +) -> bool { + use Instruction::*; + match instruction { + ArrayGet { array, index } | ArraySet { array, index, .. } => { + if function.dfg.try_get_array_length(*array).is_some() { + if let Some(known_index) = function.dfg.get_numeric_constant(*index) { + // `index` will be relative to the flattened array length, so we need to take that into account + let typ = function.dfg.type_of_value(*array); + let array_length = typ.flattened_size(); + known_index >= array_length.into() + } else { + // A dynamic index might always be out of bounds + true + } + } else { + // Slice operations might be out of bounds, but there's no way we + // can insert a check because we don't know a slice's length + false + } + } + _ => false, + } +} + +fn handle_array_get_group( + function: &Function, + array: &ValueId, + index: usize, + next_out_of_bounds_index: &mut Option, + possible_index_out_of_bounds_indexes: &mut Vec, +) { + let Some(array_length) = function.dfg.try_get_array_length(*array) else { + // Nothing to do for slices + return; + }; + + let flattened_size = function.dfg.type_of_value(*array).flattened_size(); + let element_size = flattened_size / array_length; + if element_size <= 1 { + // Not a composite type + return; + }; + + // It's a composite type. + // When doing ArrayGet on a composite type, this **always** results in instructions like these + // (assuming element_size == 3): + // + // 1. v27 = array_get v1, index v26 + // 2. v28 = add v26, u32 1 + // 3. v29 = array_get v1, index v28 + // 4. v30 = add v26, u32 2 + // 5. v31 = array_get v1, index v30 + // + // That means that after this instructions, (element_size - 1) instructions will be + // part of this composite array get, and they'll be two instructions apart. + // + // Now three things can happen: + // a) none of the array_get instructions are unused: in this case they won't be in + // `possible_index_out_of_bounds_indexes` and they won't be removed, nothing to do here + // b) all of the array_get instructions are unused: in this case we can replace **all** + // of them with just one constrain: no need to do one per array_get + // c) some of the array_get instructions are unused, but not all: in this case + // we don't need to insert any constrain, because on a later stage array bound checks + // will be performed anyway. We'll let DIE remove the unused ones, without replacing + // them with bounds checks, and leave the used ones. + // + // To check in which scenario we are we can get from `possible_index_out_of_bounds_indexes` + // (starting from `next_out_of_bounds_index`) while we are in the group ranges + // (1..=5 in the example above) + + let Some(out_of_bounds_index) = *next_out_of_bounds_index else { + // No next unused instruction, so this is case a) and nothing needs to be done here + return; + }; + + if index != out_of_bounds_index { + // The next index is not the one for the current instructions, + // so we are in case a), and nothing needs to be done here + return; + } + + // What's the last instruction that's part of the group? (5 in the example above) + let last_instruction_index = index + 2 * (element_size - 1); + // How many unused instructions are in this group? + let mut unused_count = 1; + loop { + *next_out_of_bounds_index = possible_index_out_of_bounds_indexes.pop(); + if let Some(out_of_bounds_index) = *next_out_of_bounds_index { + if out_of_bounds_index <= last_instruction_index { + unused_count += 1; + if unused_count == element_size { + // We are in case b): we need to insert just one constrain. + // Since we popped all of the group indexes, and given that we + // are analyzing the first instruction in the group, we can + // set `next_out_of_bounds_index` to the current index: + // then a check will be inserted, and no other check will be + // inserted for the rest of the group. + *next_out_of_bounds_index = Some(index); + break; + } else { + continue; + } + } + } + + // We are in case c): some of the instructions are unused. + // We don't need to insert any checks, and given that we already popped + // all of the indexes in the group, there's nothing else to do here. + break; + } +} + +// Given `lhs` and `rhs` values, if there's a side effects condition this will +// return (`lhs * condition`, `rhs * condition`), otherwise just (`lhs`, `rhs`) +fn apply_side_effects( + side_effects_condition: Option, + lhs: ValueId, + rhs: ValueId, + function: &mut Function, + block_id: BasicBlockId, + call_stack: Vector, +) -> (ValueId, ValueId) { + // See if there's an active "enable side effects" condition + let Some(condition) = side_effects_condition else { + return (lhs, rhs); + }; + + let dfg = &mut function.dfg; + + // Condition needs to be cast to argument type in order to multiply them together. + // In our case, lhs is always a boolean. + let casted_condition = dfg.insert_instruction_and_results( + Instruction::Cast(condition, Type::bool()), + block_id, + None, + call_stack.clone(), + ); + let casted_condition = casted_condition.first(); + + let lhs = dfg.insert_instruction_and_results( + Instruction::binary(BinaryOp::Mul, lhs, casted_condition), + block_id, + None, + call_stack.clone(), + ); + let lhs = lhs.first(); + + let rhs = dfg.insert_instruction_and_results( + Instruction::binary(BinaryOp::Mul, rhs, casted_condition), + block_id, + None, + call_stack, + ); + let rhs = rhs.first(); + + (lhs, rhs) } #[cfg(test)] diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs index 5b1139e5b9c6..e5a25dcfef1f 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs @@ -66,6 +66,8 @@ mod block; use std::collections::{BTreeMap, BTreeSet}; +use fxhash::FxHashMap as HashMap; + use crate::ssa::{ ir::{ basic_block::BasicBlockId, @@ -111,6 +113,10 @@ struct PerFunctionContext<'f> { /// We avoid removing individual instructions as we go since removing elements /// from the middle of Vecs many times will be slower than a single call to `retain`. instructions_to_remove: BTreeSet, + + /// Track a value's last load across all blocks. + /// If a value is not used in anymore loads we can remove the last store to that value. + last_loads: HashMap, } impl<'f> PerFunctionContext<'f> { @@ -124,6 +130,7 @@ impl<'f> PerFunctionContext<'f> { inserter: FunctionInserter::new(function), blocks: BTreeMap::new(), instructions_to_remove: BTreeSet::new(), + last_loads: HashMap::default(), } } @@ -140,6 +147,18 @@ impl<'f> PerFunctionContext<'f> { let references = self.find_starting_references(block); self.analyze_block(block, references); } + + // If we never load from an address within a function we can remove all stores to that address. + // This rule does not apply to reference parameters, which we must also check for before removing these stores. + for (block_id, block) in self.blocks.iter() { + let block_params = self.inserter.function.dfg.block_parameters(*block_id); + for (value, store_instruction) in block.last_stores.iter() { + let is_reference_param = block_params.contains(value); + if self.last_loads.get(value).is_none() && !is_reference_param { + self.instructions_to_remove.insert(*store_instruction); + } + } + } } /// The value of each reference at the start of the given block is the unification @@ -239,6 +258,8 @@ impl<'f> PerFunctionContext<'f> { self.instructions_to_remove.insert(instruction); } else { references.mark_value_used(address, self.inserter.function); + + self.last_loads.insert(address, instruction); } } Instruction::Store { address, value } => { @@ -594,10 +615,8 @@ mod tests { // acir fn main f0 { // b0(): // v7 = allocate - // store Field 5 at v7 // jmp b1(Field 5) // b1(v3: Field): - // store Field 6 at v7 // return v3, Field 5, Field 6 // } let ssa = ssa.mem2reg(); @@ -609,9 +628,9 @@ mod tests { assert_eq!(count_loads(main.entry_block(), &main.dfg), 0); assert_eq!(count_loads(b1, &main.dfg), 0); - // Neither store is removed since they are each the last in the block and there are multiple blocks - assert_eq!(count_stores(main.entry_block(), &main.dfg), 1); - assert_eq!(count_stores(b1, &main.dfg), 1); + // All stores are removed as there are no loads to the values being stored anywhere in the function. + assert_eq!(count_stores(main.entry_block(), &main.dfg), 0); + assert_eq!(count_stores(b1, &main.dfg), 0); // The jmp to b1 should also be a constant 5 now match main.dfg[main.entry_block()].terminator() { @@ -641,8 +660,8 @@ mod tests { // b1(): // store Field 1 at v3 // store Field 2 at v4 - // v8 = load v3 - // v9 = eq v8, Field 2 + // v7 = load v3 + // v8 = eq v7, Field 2 // return // } let main_id = Id::test_new(0); @@ -681,12 +700,9 @@ mod tests { // acir fn main f0 { // b0(): // v9 = allocate - // store Field 0 at v9 // v10 = allocate - // store v9 at v10 // jmp b1() // b1(): - // store Field 2 at v9 // return // } let ssa = ssa.mem2reg(); @@ -698,14 +714,17 @@ mod tests { assert_eq!(count_loads(main.entry_block(), &main.dfg), 0); assert_eq!(count_loads(b1, &main.dfg), 0); - // Only the first store in b1 is removed since there is another store to the same reference + // All stores should be removed. + // The first store in b1 is removed since there is another store to the same reference // in the same block, and the store is not needed before the later store. - assert_eq!(count_stores(main.entry_block(), &main.dfg), 2); - assert_eq!(count_stores(b1, &main.dfg), 1); + // The rest of the stores are also removed as no loads are done within any blocks + // to the stored values. + assert_eq!(count_stores(main.entry_block(), &main.dfg), 0); + assert_eq!(count_stores(b1, &main.dfg), 0); let b1_instructions = main.dfg[b1].instructions(); // We expect the last eq to be optimized out - assert_eq!(b1_instructions.len(), 1); + assert_eq!(b1_instructions.len(), 0); } } 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 e16f6697c70e..13e5c2445ad0 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 @@ -85,7 +85,7 @@ pub(super) struct Loop { } /// The queue of functions remaining to compile -type FunctionQueue = Vec<(ast::FuncId, IrFunctionId)>; +type FunctionQueue = Vec<(FuncId, IrFunctionId)>; impl<'a> FunctionContext<'a> { /// Create a new FunctionContext to compile the first function in the shared_context's @@ -248,7 +248,7 @@ impl<'a> FunctionContext<'a> { } ast::Type::Unit => panic!("convert_non_tuple_type called on a unit type"), ast::Type::Tuple(_) => panic!("convert_non_tuple_type called on a tuple: {typ}"), - ast::Type::Function(_, _, _) => Type::Function, + ast::Type::Function(_, _, _, _) => Type::Function, ast::Type::Slice(_) => panic!("convert_non_tuple_type called on a slice: {typ}"), ast::Type::MutableReference(element) => { // Recursive call to panic if element is a tuple @@ -1005,14 +1005,14 @@ impl SharedContext { } /// Pops the next function from the shared function queue, returning None if the queue is empty. - pub(super) fn pop_next_function_in_queue(&self) -> Option<(ast::FuncId, IrFunctionId)> { + pub(super) fn pop_next_function_in_queue(&self) -> Option<(FuncId, IrFunctionId)> { self.function_queue.lock().expect("Failed to lock function_queue").pop() } /// Return the matching id for the given function if known. If it is not known this /// will add the function to the queue of functions to compile, assign it a new id, /// and return this new id. - pub(super) fn get_or_queue_function(&self, id: ast::FuncId) -> IrFunctionId { + pub(super) fn get_or_queue_function(&self, id: FuncId) -> IrFunctionId { // Start a new block to guarantee the destructor for the map lock is released // before map needs to be acquired again in self.functions.write() below { 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 1373d1bc46e1..6b19aff26748 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 @@ -396,11 +396,11 @@ impl<'a> FunctionContext<'a> { /// return a reference to each element, for use with the store instruction. fn codegen_array_index( &mut self, - array: super::ir::value::ValueId, - index: super::ir::value::ValueId, + array: ValueId, + index: ValueId, element_type: &ast::Type, location: Location, - length: Option, + length: Option, ) -> Result { // base_index = index * type_size let index = self.make_array_index(index); @@ -438,11 +438,7 @@ impl<'a> FunctionContext<'a> { /// Prepare a slice access. /// Check that the index being used to access a slice element /// is less than the dynamic slice length. - fn codegen_slice_access_check( - &mut self, - index: super::ir::value::ValueId, - length: Option, - ) { + fn codegen_slice_access_check(&mut self, index: ValueId, length: Option) { let index = self.make_array_index(index); // We convert the length as an array index type for comparison let array_len = self 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 aab995c49a15..1fd1e313c442 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs @@ -8,7 +8,7 @@ use crate::ast::{ use crate::hir::def_collector::errors::DefCollectorErrorKind; use crate::macros_api::StructId; use crate::node_interner::{ExprId, QuotedTypeId}; -use crate::token::{Attributes, Token, Tokens}; +use crate::token::{Attributes, FunctionAttribute, Token, Tokens}; use crate::{Kind, Type}; use acvm::{acir::AcirField, FieldElement}; use iter_extended::vecmap; @@ -36,6 +36,7 @@ pub enum ExpressionKind { Quote(Tokens), Unquote(Box), Comptime(BlockExpression, Span), + Unsafe(BlockExpression, Span), AsTraitPath(AsTraitPath), // This variant is only emitted when inlining the result of comptime @@ -477,6 +478,20 @@ pub struct FunctionDefinition { pub return_visibility: Visibility, } +impl FunctionDefinition { + pub fn is_private(&self) -> bool { + self.visibility == ItemVisibility::Private + } + + pub fn is_test(&self) -> bool { + if let Some(attribute) = &self.attributes.function { + matches!(attribute, FunctionAttribute::Test(..)) + } else { + false + } + } +} + #[derive(Debug, PartialEq, Eq, Clone)] pub struct Param { pub visibility: Visibility, @@ -587,6 +602,7 @@ impl Display for ExpressionKind { Lambda(lambda) => lambda.fmt(f), Parenthesized(sub_expr) => write!(f, "({sub_expr})"), Comptime(block, _) => write!(f, "comptime {block}"), + Unsafe(block, _) => write!(f, "unsafe {block}"), Error => write!(f, "Error"), Resolved(_) => write!(f, "?Resolved"), Unquote(expr) => write!(f, "$({expr})"), 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 8e27f0bdda90..7b5b64e7089a 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/mod.rs @@ -127,6 +127,7 @@ pub enum UnresolvedTypeData { /*args:*/ Vec, /*ret:*/ Box, /*env:*/ Box, + /*unconstrained:*/ bool, ), /// The type of quoted code for metaprogramming @@ -222,7 +223,11 @@ impl std::fmt::Display for UnresolvedTypeData { Bool => write!(f, "bool"), String(len) => write!(f, "str<{len}>"), FormatString(len, elements) => write!(f, "fmt<{len}, {elements}"), - Function(args, ret, env) => { + Function(args, ret, env, unconstrained) => { + if *unconstrained { + write!(f, "unconstrained ")?; + } + let args = vecmap(args, ToString::to_string).join(", "); match &env.as_ref().typ { 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 5d9a97fa6cf3..10ae7a1e62f0 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs @@ -100,7 +100,9 @@ impl StatementKind { StatementKind::Expression(expr) => { match (&expr.kind, semi, last_statement_in_block) { // Semicolons are optional for these expressions - (ExpressionKind::Block(_), semi, _) | (ExpressionKind::If(_), semi, _) => { + (ExpressionKind::Block(_), semi, _) + | (ExpressionKind::Unsafe(..), semi, _) + | (ExpressionKind::If(_), semi, _) => { if semi.is_some() { StatementKind::Semi(expr) } else { @@ -745,8 +747,10 @@ impl ForRange { let block = ExpressionKind::Block(BlockExpression { statements: vec![let_array, for_loop], }); - let kind = StatementKind::Expression(Expression::new(block, for_loop_span)); - Statement { kind, span: for_loop_span } + Statement { + kind: StatementKind::Expression(Expression::new(block, for_loop_span)), + span: for_loop_span, + } } } } 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 598ffed14335..2856bb552764 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/debug/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/debug/mod.rs @@ -493,7 +493,9 @@ pub fn build_debug_crate_file() -> String { __debug_var_assign_oracle(var_id, value); } pub fn __debug_var_assign(var_id: u32, value: T) { - __debug_var_assign_inner(var_id, value); + unsafe {{ + __debug_var_assign_inner(var_id, value); + }} } #[oracle(__debug_var_drop)] @@ -502,7 +504,9 @@ pub fn build_debug_crate_file() -> String { __debug_var_drop_oracle(var_id); } pub fn __debug_var_drop(var_id: u32) { - __debug_var_drop_inner(var_id); + unsafe {{ + __debug_var_drop_inner(var_id); + }} } #[oracle(__debug_fn_enter)] @@ -511,7 +515,9 @@ pub fn build_debug_crate_file() -> String { __debug_fn_enter_oracle(fn_id); } pub fn __debug_fn_enter(fn_id: u32) { - __debug_fn_enter_inner(fn_id); + unsafe {{ + __debug_fn_enter_inner(fn_id); + }} } #[oracle(__debug_fn_exit)] @@ -520,7 +526,9 @@ pub fn build_debug_crate_file() -> String { __debug_fn_exit_oracle(fn_id); } pub fn __debug_fn_exit(fn_id: u32) { - __debug_fn_exit_inner(fn_id); + unsafe {{ + __debug_fn_exit_inner(fn_id); + }} } #[oracle(__debug_dereference_assign)] @@ -529,7 +537,9 @@ pub fn build_debug_crate_file() -> String { __debug_dereference_assign_oracle(var_id, value); } pub fn __debug_dereference_assign(var_id: u32, value: T) { - __debug_dereference_assign_inner(var_id, value); + unsafe {{ + __debug_dereference_assign_inner(var_id, value); + }} } "# .to_string(), @@ -553,7 +563,9 @@ pub fn build_debug_crate_file() -> String { __debug_oracle_member_assign_{n}(var_id, value, {vars}); }} pub fn __debug_member_assign_{n}(var_id: u32, value: T, {var_sig}) {{ - __debug_inner_member_assign_{n}(var_id, value, {vars}); + unsafe {{ + __debug_inner_member_assign_{n}(var_id, value, {vars}); + }} }} "# 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 afa2e7fa7a8f..2b78c02e53c1 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/comptime.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/comptime.rs @@ -267,6 +267,14 @@ impl<'context> Elaborator<'context> { let id = self.interner.push_empty_fn(); let module = self.module_id(); self.interner.push_function(id, &function.def, module, location); + + if self.interner.is_in_lsp_mode() + && !function.def.is_test() + && !function.def.is_private() + { + self.interner.register_function(id, &function.def); + } + let functions = vec![(self.local_module, id, function)]; generated_items.functions.push(UnresolvedFunctions { file_id: self.file, 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 5ba448f890ea..b31b8f360319 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -58,6 +58,9 @@ impl<'context> Elaborator<'context> { ExpressionKind::Comptime(comptime, _) => { return self.elaborate_comptime_block(comptime, expr.span) } + ExpressionKind::Unsafe(block_expression, _) => { + self.elaborate_unsafe_block(block_expression) + } ExpressionKind::Resolved(id) => return (id, self.interner.id_type(id)), ExpressionKind::Error => (HirExpression::Error, Type::Error), ExpressionKind::Unquote(_) => { @@ -105,6 +108,19 @@ impl<'context> Elaborator<'context> { (HirBlockExpression { statements }, block_type) } + fn elaborate_unsafe_block(&mut self, block: BlockExpression) -> (HirExpression, Type) { + // Before entering the block we cache the old value of `in_unsafe_block` so it can be restored. + let old_in_unsafe_block = self.in_unsafe_block; + self.in_unsafe_block = true; + + let (hir_block_expression, typ) = self.elaborate_block_expression(block); + + // Finally, we restore the original value of `self.in_unsafe_block`. + self.in_unsafe_block = old_in_unsafe_block; + + (HirExpression::Unsafe(hir_block_expression), typ) + } + fn elaborate_literal(&mut self, literal: Literal, span: Span) -> (HirExpression, Type) { use HirExpression::Literal as Lit; match literal { @@ -369,7 +385,8 @@ impl<'context> Elaborator<'context> { function_args.push((typ, arg, span)); } - let location = Location::new(span, self.file); + let call_span = Span::from(object_span.start()..method_name_span.end()); + let location = Location::new(call_span, self.file); let method = method_call.method_name; let turbofish_generics = generics.clone(); let is_macro_call = method_call.is_macro_call; @@ -710,7 +727,7 @@ impl<'context> Elaborator<'context> { let captures = lambda_context.captures; let expr = HirExpression::Lambda(HirLambda { parameters, return_type, body, captures }); - (expr, Type::Function(arg_types, Box::new(body_type), Box::new(env_type))) + (expr, Type::Function(arg_types, Box::new(body_type), Box::new(env_type), false)) } fn elaborate_quote(&mut self, mut tokens: Tokens) -> (HirExpression, Type) { 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 e60308aaddd9..2e4a69c067f4 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs @@ -97,6 +97,7 @@ pub struct Elaborator<'context> { file: FileId, + in_unsafe_block: bool, nested_loops: usize, /// Contains a mapping of the current struct or functions's generics to @@ -194,6 +195,7 @@ impl<'context> Elaborator<'context> { interner, def_maps, file: FileId::dummy(), + in_unsafe_block: false, nested_loops: 0, generics: Vec::new(), lambda_stack: Vec::new(), @@ -802,7 +804,12 @@ impl<'context> Elaborator<'context> { let return_type = Box::new(self.resolve_type(func.return_type())); - let mut typ = Type::Function(parameter_types, return_type, Box::new(Type::Unit)); + let mut typ = Type::Function( + parameter_types, + return_type, + Box::new(Type::Unit), + func.def.is_unconstrained, + ); if !generics.is_empty() { typ = Type::Forall(generics, Box::new(typ)); @@ -1290,6 +1297,12 @@ impl<'context> Elaborator<'context> { self.current_item = Some(DependencyId::Global(global_id)); let let_stmt = global.stmt_def; + let name = if self.interner.is_in_lsp_mode() { + Some(let_stmt.pattern.name_ident().to_string()) + } else { + None + }; + if !self.in_contract() && let_stmt.attributes.iter().any(|attr| matches!(attr, SecondaryAttribute::Abi(_))) { @@ -1312,8 +1325,9 @@ impl<'context> Elaborator<'context> { self.elaborate_comptime_global(global_id); } - self.interner - .add_definition_location(ReferenceId::Global(global_id), Some(self.module_id())); + if let Some(name) = name { + self.interner.register_global(global_id, name, self.module_id()); + } self.local_module = old_module; self.file = old_file; @@ -1451,9 +1465,12 @@ impl<'context> Elaborator<'context> { /// True if we're currently within a constrained function. /// Defaults to `true` if the current function is unknown. fn in_constrained_function(&self) -> bool { - self.current_item.map_or(true, |id| match id { - DependencyId::Function(id) => !self.interner.function_modifiers(&id).is_unconstrained, - _ => true, - }) + !self.in_comptime_context() + && self.current_item.map_or(true, |id| match id { + DependencyId::Function(id) => { + !self.interner.function_modifiers(&id).is_unconstrained + } + _ => true, + }) } } 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 1e48fdd07e7c..52407746258f 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/traits.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/traits.rs @@ -145,8 +145,9 @@ impl<'context> Elaborator<'context> { }; let no_environment = Box::new(Type::Unit); + // TODO: unconstrained let function_type = - Type::Function(arguments, Box::new(return_type), no_environment); + Type::Function(arguments, Box::new(return_type), no_environment, false); functions.push(TraitFunction { name: name.clone(), @@ -345,9 +346,15 @@ fn check_function_type_matches_expected_type( ) { let mut bindings = TypeBindings::new(); // Shouldn't need to unify envs, they should always be equal since they're both free functions - if let (Type::Function(params_a, ret_a, _env_a), Type::Function(params_b, ret_b, _env_b)) = - (expected, actual) + if let ( + Type::Function(params_a, ret_a, _env_a, _unconstrained_a), + Type::Function(params_b, ret_b, _env_b, _unconstrained_b), + ) = (expected, actual) { + // TODO: we don't yet allow marking a trait function or a trait impl function as unconstrained, + // so both values will always be false here. Once we support that, we should check that both + // match (adding a test for it). + if params_a.len() == params_b.len() { for (i, (a, b)) in params_a.iter().zip(params_b.iter()).enumerate() { if a.try_unify(b, &mut bindings).is_err() { 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 f8ba994f66b7..58d019c86aae 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs @@ -125,7 +125,7 @@ impl<'context> Elaborator<'context> { Tuple(fields) => { Type::Tuple(vecmap(fields, |field| self.resolve_type_inner(field, kind))) } - Function(args, ret, env) => { + Function(args, ret, env, unconstrained) => { let args = vecmap(args, |arg| self.resolve_type_inner(arg, kind)); let ret = Box::new(self.resolve_type_inner(*ret, kind)); @@ -139,7 +139,7 @@ impl<'context> Elaborator<'context> { match *env { Type::Unit | Type::Tuple(_) | Type::NamedGeneric(_, _, _) => { - Type::Function(args, ret, env) + Type::Function(args, ret, env, unconstrained) } _ => { self.push_err(ResolverError::InvalidClosureEnvironment { @@ -735,8 +735,8 @@ impl<'context> Elaborator<'context> { return Type::Error; } - for (param, (arg, _, arg_span)) in fn_params.iter().zip(callsite_args) { - self.unify(arg, param, || TypeCheckError::TypeMismatch { + for (param, (arg, arg_expr_id, arg_span)) in fn_params.iter().zip(callsite_args) { + self.unify_with_coercions(arg, param, *arg_expr_id, || TypeCheckError::TypeMismatch { expected_typ: param.to_string(), expr_typ: arg.to_string(), expr_span: *arg_span, @@ -763,7 +763,8 @@ impl<'context> Elaborator<'context> { let ret = self.interner.next_type_variable(); let args = vecmap(args, |(arg, _, _)| arg); let env_type = self.interner.next_type_variable(); - let expected = Type::Function(args, Box::new(ret.clone()), Box::new(env_type)); + let expected = + Type::Function(args, Box::new(ret.clone()), Box::new(env_type), false); if let Err(error) = binding.try_bind(expected, span) { self.push_err(error); @@ -772,7 +773,7 @@ impl<'context> Elaborator<'context> { } // The closure env is ignored on purpose: call arguments never place // constraints on closure environments. - Type::Function(parameters, ret, _env) => { + Type::Function(parameters, ret, _env, _unconstrained) => { self.bind_function_type_impl(¶meters, &ret, &args, span) } Type::Error => Type::Error, @@ -1128,7 +1129,7 @@ impl<'context> Elaborator<'context> { let (method_type, mut bindings) = method.typ.clone().instantiate(self.interner); match method_type { - Type::Function(args, _, _) => { + Type::Function(args, _, _, _) => { // We can cheat a bit and match against only the object type here since no operator // overload uses other generic parameters or return types aside from the object type. let expected_object_type = &args[0]; @@ -1314,22 +1315,33 @@ impl<'context> Elaborator<'context> { let is_current_func_constrained = self.in_constrained_function(); - let is_unconstrained_call = self.is_unconstrained_call(call.func); + let func_type_is_unconstrained = + if let Type::Function(_args, _ret, _env, unconstrained) = &func_type { + *unconstrained + } else { + false + }; + + let is_unconstrained_call = + func_type_is_unconstrained || self.is_unconstrained_call(call.func); let crossing_runtime_boundary = is_current_func_constrained && is_unconstrained_call; if crossing_runtime_boundary { - let called_func_id = self - .interner - .lookup_function_from_expr(&call.func) - .expect("Called function should exist"); - self.run_lint(|elaborator| { - lints::oracle_called_from_constrained_function( - elaborator.interner, - &called_func_id, - is_current_func_constrained, - span, - ) - .map(Into::into) - }); + if !self.in_unsafe_block { + self.push_err(TypeCheckError::Unsafe { span }); + } + + if let Some(called_func_id) = self.interner.lookup_function_from_expr(&call.func) { + self.run_lint(|elaborator| { + lints::oracle_called_from_constrained_function( + elaborator.interner, + &called_func_id, + is_current_func_constrained, + span, + ) + .map(Into::into) + }); + } + let errors = lints::unconstrained_function_args(&args); for error in errors { self.push_err(error); @@ -1373,9 +1385,9 @@ impl<'context> Elaborator<'context> { object: &mut ExprId, ) { let expected_object_type = match function_type { - Type::Function(args, _, _) => args.first(), + Type::Function(args, _, _, _) => args.first(), Type::Forall(_, typ) => match typ.as_ref() { - Type::Function(args, _, _) => args.first(), + Type::Function(args, _, _, _) => args.first(), typ => unreachable!("Unexpected type for function: {typ}"), }, typ => unreachable!("Unexpected type for function: {typ}"), @@ -1577,7 +1589,7 @@ impl<'context> Elaborator<'context> { } } - Type::Function(parameters, return_type, _env) => { + Type::Function(parameters, return_type, _env, _unconstrained) => { for parameter in parameters { Self::find_numeric_generics_in_type(parameter, found); } 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 bc48b2875c8b..853697220b3e 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 @@ -202,6 +202,9 @@ impl HirExpression { HirExpression::Comptime(block) => { ExpressionKind::Comptime(block.to_display_ast(interner), span) } + HirExpression::Unsafe(block) => { + ExpressionKind::Unsafe(block.to_display_ast(interner), span) + } HirExpression::Quote(block) => ExpressionKind::Quote(block.clone()), // A macro was evaluated here: return the quoted result @@ -340,11 +343,11 @@ impl Type { let name = Path::from_single(name.as_ref().clone(), Span::default()); UnresolvedTypeData::TraitAsType(name, Vec::new()) } - Type::Function(args, ret, env) => { + Type::Function(args, ret, env, unconstrained) => { let args = vecmap(args, |arg| arg.to_display_ast()); let ret = Box::new(ret.to_display_ast()); let env = Box::new(env.to_display_ast()); - UnresolvedTypeData::Function(args, ret, env) + UnresolvedTypeData::Function(args, ret, env, *unconstrained) } Type::MutableReference(element) => { let element = Box::new(element.to_display_ast()); 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 72b92e288c7f..33f8c9d8332d 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 @@ -472,6 +472,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { HirExpression::Lambda(lambda) => self.evaluate_lambda(lambda, id), HirExpression::Quote(tokens) => self.evaluate_quote(tokens, id), HirExpression::Comptime(block) => self.evaluate_block(block), + HirExpression::Unsafe(block) => self.evaluate_block(block), HirExpression::Unquote(tokens) => { // An Unquote expression being found is indicative of a macro being // expanded within another comptime fn which we don't currently support. @@ -896,6 +897,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)), + (Value::Bool(lhs), Value::Bool(rhs)) => Ok(Value::Bool(lhs == rhs)), (lhs, rhs) => make_error(self, lhs, rhs, "=="), }, BinaryOpKind::NotEqual => match (lhs, rhs) { @@ -908,6 +910,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)), + (Value::Bool(lhs), Value::Bool(rhs)) => Ok(Value::Bool(lhs != rhs)), (lhs, rhs) => make_error(self, lhs, rhs, "!="), }, BinaryOpKind::Less => match (lhs, rhs) { 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 ef7b9f2be550..f65c5ae8cdff 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 @@ -7,8 +7,8 @@ use acvm::{AcirField, FieldElement}; 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, + get_slice, get_struct, get_trait_constraint, get_trait_def, get_trait_impl, 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}; @@ -17,13 +17,13 @@ use rustc_hash::FxHashMap as HashMap; use crate::{ ast::{ - ExpressionKind, FunctionKind, FunctionReturnType, IntegerBitSize, UnresolvedType, - UnresolvedTypeData, Visibility, + ArrayLiteral, ExpressionKind, FunctionKind, FunctionReturnType, IntegerBitSize, Literal, + UnaryOp, 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, + node_interner::{DefinitionKind, TraitImplKind}, parser::{self}, token::{SpannedToken, Token}, QuotedType, Shared, Type, @@ -47,7 +47,23 @@ impl<'local, 'context> Interpreter<'local, 'context> { "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_array" => expr_as_array(arguments, return_type, location), + "expr_as_binary_op" => expr_as_binary_op(arguments, return_type, location), + "expr_as_bool" => expr_as_bool(arguments, return_type, location), "expr_as_function_call" => expr_as_function_call(arguments, return_type, location), + "expr_as_if" => expr_as_if(arguments, return_type, location), + "expr_as_index" => expr_as_index(arguments, return_type, location), + "expr_as_integer" => expr_as_integer(arguments, return_type, location), + "expr_as_member_access" => expr_as_member_access(arguments, return_type, location), + "expr_as_repeated_element_array" => { + expr_as_repeated_element_array(arguments, return_type, location) + } + "expr_as_repeated_element_slice" => { + expr_as_repeated_element_slice(arguments, return_type, location) + } + "expr_as_slice" => expr_as_slice(arguments, return_type, location), + "expr_as_tuple" => expr_as_tuple(arguments, return_type, location), + "expr_as_unary_op" => expr_as_unary_op(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), @@ -86,6 +102,10 @@ impl<'local, 'context> Interpreter<'local, 'context> { } "trait_def_eq" => trait_def_eq(interner, arguments, location), "trait_def_hash" => trait_def_hash(interner, arguments, location), + "trait_impl_methods" => trait_impl_methods(interner, arguments, location), + "trait_impl_trait_generic_args" => { + trait_impl_trait_generic_args(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), @@ -93,6 +113,9 @@ impl<'local, 'context> Interpreter<'local, 'context> { "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_get_trait_impl" => { + type_get_trait_impl(interner, arguments, return_type, location) + } "type_implements" => type_implements(interner, arguments, location), "type_is_bool" => type_is_bool(arguments, location), "type_is_field" => type_is_field(arguments, location), @@ -507,6 +530,26 @@ fn type_eq(arguments: Vec<(Value, Location)>, location: Location) -> IResult Option +fn type_get_trait_impl( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + let (typ, constraint) = check_two_arguments(arguments, location)?; + + let typ = get_type(typ)?; + let (trait_id, generics) = get_trait_constraint(constraint)?; + + let option_value = match interner.try_lookup_trait_implementation(&typ, trait_id, &generics) { + Ok((TraitImplKind::Normal(trait_impl_id), _)) => Some(Value::TraitImpl(trait_impl_id)), + _ => None, + }; + + option(return_type, option_value) +} + // fn implements(self, constraint: TraitConstraint) -> bool fn type_implements( interner: &NodeInterner, @@ -607,6 +650,41 @@ fn trait_def_eq( Ok(Value::Bool(id_a == id_b)) } +// fn methods(self) -> [FunctionDefinition] +fn trait_impl_methods( + interner: &mut NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let argument = check_one_argument(arguments, location)?; + + let trait_impl_id = get_trait_impl(argument)?; + let trait_impl = interner.get_trait_implementation(trait_impl_id); + let trait_impl = trait_impl.borrow(); + let methods = + trait_impl.methods.iter().map(|func_id| Value::FunctionDefinition(*func_id)).collect(); + let slice_type = Type::Slice(Box::new(Type::Quoted(QuotedType::FunctionDefinition))); + + Ok(Value::Slice(methods, slice_type)) +} + +// fn trait_generic_args(self) -> [Type] +fn trait_impl_trait_generic_args( + interner: &mut NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let argument = check_one_argument(arguments, location)?; + + let trait_impl_id = get_trait_impl(argument)?; + let trait_impl = interner.get_trait_implementation(trait_impl_id); + let trait_impl = trait_impl.borrow(); + let trait_generics = trait_impl.trait_generics.iter().map(|t| Value::Type(t.clone())).collect(); + let slice_type = Type::Slice(Box::new(Type::Quoted(QuotedType::Type))); + + Ok(Value::Slice(trait_generics, slice_type)) +} + // fn zeroed() -> T fn zeroed(return_type: Type) -> IResult { match return_type { @@ -688,6 +766,71 @@ fn zeroed(return_type: Type) -> IResult { } } +// fn as_array(self) -> Option<[Expr]> +fn expr_as_array( + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(arguments, return_type, location, |expr| { + if let ExpressionKind::Literal(Literal::Array(ArrayLiteral::Standard(exprs))) = expr { + let exprs = exprs.into_iter().map(|expr| Value::Expr(expr.kind)).collect(); + let typ = Type::Slice(Box::new(Type::Quoted(QuotedType::Expr))); + Some(Value::Slice(exprs, typ)) + } else { + None + } + }) +} + +// fn as_binary_op(self) -> Option<(Expr, BinaryOp, Expr)> +fn expr_as_binary_op( + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(arguments, return_type.clone(), location, |expr| { + if let ExpressionKind::Infix(infix_expr) = expr { + let option_type = extract_option_generic_type(return_type); + let Type::Tuple(mut tuple_types) = option_type else { + panic!("Expected the return type option generic arg to be a tuple"); + }; + assert_eq!(tuple_types.len(), 3); + + tuple_types.pop().unwrap(); + let binary_op_type = tuple_types.pop().unwrap(); + + // For the op value we use the enum member index, which should match noir_stdlib/src/meta/op.nr + let binary_op_value = infix_expr.operator.contents as u128; + + let mut fields = HashMap::default(); + fields.insert(Rc::new("op".to_string()), Value::Field(binary_op_value.into())); + + let unary_op = Value::Struct(fields, binary_op_type); + let lhs = Value::Expr(infix_expr.lhs.kind); + let rhs = Value::Expr(infix_expr.rhs.kind); + Some(Value::Tuple(vec![lhs, unary_op, rhs])) + } else { + None + } + }) +} + +// fn as_bool(self) -> Option +fn expr_as_bool( + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(arguments, return_type, location, |expr| { + if let ExpressionKind::Literal(Literal::Bool(bool)) = expr { + Some(Value::Bool(bool)) + } else { + None + } + }) +} + // fn as_function_call(self) -> Option<(Expr, [Expr])> fn expr_as_function_call( arguments: Vec<(Value, Location)>, @@ -708,6 +851,194 @@ fn expr_as_function_call( }) } +// fn as_if(self) -> Option<(Expr, Expr, Option)> +fn expr_as_if( + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(arguments, return_type.clone(), location, |expr| { + if let ExpressionKind::If(if_expr) = expr { + // Get the type of `Option` + let option_type = extract_option_generic_type(return_type.clone()); + let Type::Tuple(option_types) = option_type else { + panic!("Expected the return type option generic arg to be a tuple"); + }; + assert_eq!(option_types.len(), 3); + let alternative_option_type = option_types[2].clone(); + + let alternative = + option(alternative_option_type, if_expr.alternative.map(|e| Value::Expr(e.kind))); + + Some(Value::Tuple(vec![ + Value::Expr(if_expr.condition.kind), + Value::Expr(if_expr.consequence.kind), + alternative.ok()?, + ])) + } else { + None + } + }) +} + +// fn as_index(self) -> Option +fn expr_as_index( + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(arguments, return_type, location, |expr| { + if let ExpressionKind::Index(index_expr) = expr { + Some(Value::Tuple(vec![ + Value::Expr(index_expr.collection.kind), + Value::Expr(index_expr.index.kind), + ])) + } else { + None + } + }) +} + +// fn as_integer(self) -> Option<(Field, bool)> +fn expr_as_integer( + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(arguments, return_type.clone(), location, |expr| { + if let ExpressionKind::Literal(Literal::Integer(field, sign)) = expr { + Some(Value::Tuple(vec![Value::Field(field), Value::Bool(sign)])) + } else { + None + } + }) +} + +// fn as_member_access(self) -> Option<(Expr, Quoted)> +fn expr_as_member_access( + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(arguments, return_type, location, |expr| { + if let ExpressionKind::MemberAccess(member_access) = expr { + let tokens = Rc::new(vec![Token::Ident(member_access.rhs.0.contents.clone())]); + Some(Value::Tuple(vec![Value::Expr(member_access.lhs.kind), Value::Quoted(tokens)])) + } else { + None + } + }) +} + +// fn as_repeated_element_array(self) -> Option<(Expr, Expr)> +fn expr_as_repeated_element_array( + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(arguments, return_type, location, |expr| { + if let ExpressionKind::Literal(Literal::Array(ArrayLiteral::Repeated { + repeated_element, + length, + })) = expr + { + Some(Value::Tuple(vec![Value::Expr(repeated_element.kind), Value::Expr(length.kind)])) + } else { + None + } + }) +} + +// fn as_repeated_element_slice(self) -> Option<(Expr, Expr)> +fn expr_as_repeated_element_slice( + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(arguments, return_type, location, |expr| { + if let ExpressionKind::Literal(Literal::Slice(ArrayLiteral::Repeated { + repeated_element, + length, + })) = expr + { + Some(Value::Tuple(vec![Value::Expr(repeated_element.kind), Value::Expr(length.kind)])) + } else { + None + } + }) +} + +// fn as_slice(self) -> Option<[Expr]> +fn expr_as_slice( + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(arguments, return_type, location, |expr| { + if let ExpressionKind::Literal(Literal::Slice(ArrayLiteral::Standard(exprs))) = expr { + let exprs = exprs.into_iter().map(|expr| Value::Expr(expr.kind)).collect(); + let typ = Type::Slice(Box::new(Type::Quoted(QuotedType::Expr))); + Some(Value::Slice(exprs, typ)) + } else { + None + } + }) +} + +// fn as_tuple(self) -> Option<[Expr]> +fn expr_as_tuple( + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(arguments, return_type, location, |expr| { + if let ExpressionKind::Tuple(expressions) = expr { + let expressions = expressions.into_iter().map(|expr| Value::Expr(expr.kind)).collect(); + let typ = Type::Slice(Box::new(Type::Quoted(QuotedType::Expr))); + Some(Value::Slice(expressions, typ)) + } else { + None + } + }) +} + +// fn as_unary_op(self) -> Option<(UnaryOp, Expr)> +fn expr_as_unary_op( + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(arguments, return_type.clone(), location, |expr| { + if let ExpressionKind::Prefix(prefix_expr) = expr { + let option_type = extract_option_generic_type(return_type); + let Type::Tuple(mut tuple_types) = option_type else { + panic!("Expected the return type option generic arg to be a tuple"); + }; + assert_eq!(tuple_types.len(), 2); + + tuple_types.pop().unwrap(); + let unary_op_type = tuple_types.pop().unwrap(); + + // These values should match the values used in noir_stdlib/src/meta/op.nr + let unary_op_value: u128 = match prefix_expr.operator { + UnaryOp::Minus => 0, + UnaryOp::Not => 1, + UnaryOp::MutableReference => 2, + UnaryOp::Dereference { .. } => 3, + }; + + let mut fields = HashMap::default(); + fields.insert(Rc::new("op".to_string()), Value::Field(unary_op_value.into())); + + let unary_op = Value::Struct(fields, unary_op_type); + let rhs = Value::Expr(prefix_expr.rhs.kind); + Some(Value::Tuple(vec![unary_op, rhs])) + } else { + None + } + }) +} + // Helper function for implementing the `expr_as_...` functions. fn expr_as( arguments: Vec<(Value, Location)>, @@ -719,8 +1050,12 @@ where F: FnOnce(ExpressionKind) -> Option, { let self_argument = check_one_argument(arguments, location)?; - let expr = get_expr(self_argument)?; - let option_value = f(expr); + let mut expression_kind = get_expr(self_argument)?; + while let ExpressionKind::Parenthesized(expression) = expression_kind { + expression_kind = expression.kind; + } + + let option_value = f(expression_kind); option(return_type, option_value) } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs index 56f6c11974f3..8d2f55b9c20b 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs @@ -14,7 +14,7 @@ use crate::{ stmt::HirPattern, }, macros_api::{NodeInterner, StructId}, - node_interner::{FuncId, TraitId}, + node_interner::{FuncId, TraitId, TraitImplId}, parser::NoirParser, token::{Token, Tokens}, QuotedType, Type, @@ -77,8 +77,7 @@ pub(crate) fn get_array( 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 }) + type_mismatch(value, expected, location) } } } @@ -92,8 +91,7 @@ pub(crate) fn get_slice( 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 }) + type_mismatch(value, expected, location) } } } @@ -107,8 +105,7 @@ pub(crate) fn get_tuple( 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 }) + type_mismatch(value, expected, location) } } } @@ -116,10 +113,7 @@ pub(crate) fn get_tuple( 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 }) - } + value => type_mismatch(value, Type::FieldElement, location), } } @@ -128,8 +122,7 @@ pub(crate) fn get_u8((value, location): (Value, Location)) -> IResult { 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 }) + type_mismatch(value, expected, location) } } } @@ -139,8 +132,7 @@ pub(crate) fn get_u32((value, location): (Value, Location)) -> IResult { 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 }) + type_mismatch(value, expected, location) } } } @@ -148,44 +140,28 @@ pub(crate) fn get_u32((value, location): (Value, Location)) -> IResult { 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 }) - } + value => type_mismatch(value, Type::Quoted(QuotedType::Expr), 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 }) - } + value => type_mismatch(value, Type::Quoted(QuotedType::FunctionDefinition), 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 }) - } + value => type_mismatch(value, Type::Quoted(QuotedType::Module), 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 }) - } + _ => type_mismatch(value, Type::Quoted(QuotedType::StructDefinition), location), } } @@ -194,47 +170,43 @@ pub(crate) fn get_trait_constraint( ) -> 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 }) - } + value => type_mismatch(value, Type::Quoted(QuotedType::TraitConstraint), 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 }) - } + value => type_mismatch(value, Type::Quoted(QuotedType::TraitDefinition), location), + } +} + +pub(crate) fn get_trait_impl((value, location): (Value, Location)) -> IResult { + match value { + Value::TraitImpl(id) => Ok(id), + value => type_mismatch(value, Type::Quoted(QuotedType::TraitImpl), 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 }) - } + value => type_mismatch(value, Type::Quoted(QuotedType::Type), 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 }) - } + value => type_mismatch(value, Type::Quoted(QuotedType::Quoted), location), } } +fn type_mismatch(value: Value, expected: Type, location: Location) -> IResult { + 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, @@ -357,7 +329,7 @@ where pub(super) fn replace_func_meta_parameters(typ: &mut Type, parameter_types: Vec) { match typ { - Type::Function(parameters, _, _) => { + Type::Function(parameters, _, _, _) => { *parameters = parameter_types; } Type::Forall(_, typ) => replace_func_meta_parameters(typ, parameter_types), @@ -367,7 +339,7 @@ pub(super) fn replace_func_meta_parameters(typ: &mut Type, parameter_types: Vec< pub(super) fn replace_func_meta_return_type(typ: &mut Type, return_type: Type) { match typ { - Type::Function(_, ret, _) => { + 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/value.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/value.rs index d5408309e554..d65c2bb7dcc2 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 @@ -9,12 +9,15 @@ use noirc_errors::{Location, Span}; use crate::{ ast::{ArrayLiteral, ConstructorExpression, Ident, IntegerBitSize, Signedness}, hir::def_map::ModuleId, - hir_def::expr::{HirArrayLiteral, HirConstructorExpression, HirIdent, HirLambda, ImplKind}, + hir_def::{ + expr::{HirArrayLiteral, HirConstructorExpression, HirIdent, HirLambda, ImplKind}, + traits::TraitConstraint, + }, macros_api::{ Expression, ExpressionKind, HirExpression, HirLiteral, Literal, NodeInterner, Path, StructId, }, - node_interner::{ExprId, FuncId, TraitId}, + node_interner::{ExprId, FuncId, TraitId, TraitImplId}, parser::{self, NoirParser, TopLevelStatement}, token::{SpannedToken, Token, Tokens}, QuotedType, Shared, Type, TypeBindings, @@ -53,6 +56,7 @@ pub enum Value { StructDefinition(StructId), TraitConstraint(TraitId, /* trait generics */ Vec), TraitDefinition(TraitId), + TraitImpl(TraitImplId), FunctionDefinition(FuncId), ModuleDefinition(ModuleId), Type(Type), @@ -100,6 +104,7 @@ impl Value { } Value::TraitConstraint { .. } => Type::Quoted(QuotedType::TraitConstraint), Value::TraitDefinition(_) => Type::Quoted(QuotedType::TraitDefinition), + Value::TraitImpl(_) => Type::Quoted(QuotedType::TraitImpl), Value::FunctionDefinition(_) => Type::Quoted(QuotedType::FunctionDefinition), Value::ModuleDefinition(_) => Type::Quoted(QuotedType::Module), Value::Type(_) => Type::Quoted(QuotedType::Type), @@ -230,6 +235,7 @@ impl Value { | Value::StructDefinition(_) | Value::TraitConstraint(..) | Value::TraitDefinition(_) + | Value::TraitImpl(_) | Value::FunctionDefinition(_) | Value::Zeroed(_) | Value::Type(_) @@ -353,6 +359,7 @@ impl Value { | Value::StructDefinition(_) | Value::TraitConstraint(..) | Value::TraitDefinition(_) + | Value::TraitImpl(_) | Value::FunctionDefinition(_) | Value::Zeroed(_) | Value::Type(_) @@ -516,18 +523,40 @@ impl<'value, 'interner> Display for ValuePrinter<'value, 'interner> { 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) - } + write!(f, "{}", display_trait_id_and_generics(self.interner, trait_id, generics)) } Value::TraitDefinition(trait_id) => { let trait_ = self.interner.get_trait(*trait_id); write!(f, "{}", trait_.name) } + Value::TraitImpl(trait_impl_id) => { + let trait_impl = self.interner.get_trait_implementation(*trait_impl_id); + let trait_impl = trait_impl.borrow(); + + let generic_string = + vecmap(&trait_impl.trait_generics, ToString::to_string).join(", "); + let generic_string = if generic_string.is_empty() { + generic_string + } else { + format!("<{}>", generic_string) + }; + + let where_clause = vecmap(&trait_impl.where_clause, |trait_constraint| { + display_trait_constraint(self.interner, trait_constraint) + }); + let where_clause = where_clause.join(", "); + let where_clause = if where_clause.is_empty() { + where_clause + } else { + format!(" where {}", where_clause) + }; + + write!( + f, + "impl {}{} for {}{}", + trait_impl.ident, generic_string, trait_impl.typ, where_clause + ) + } Value::FunctionDefinition(function_id) => { write!(f, "{}", self.interner.function_name(function_id)) } @@ -538,3 +567,26 @@ impl<'value, 'interner> Display for ValuePrinter<'value, 'interner> { } } } + +fn display_trait_id_and_generics( + interner: &NodeInterner, + trait_id: &TraitId, + generics: &Vec, +) -> String { + let trait_ = interner.get_trait(*trait_id); + let generic_string = vecmap(generics, ToString::to_string).join(", "); + if generics.is_empty() { + format!("{}", trait_.name) + } else { + format!("{}<{generic_string}>", trait_.name) + } +} + +fn display_trait_constraint(interner: &NodeInterner, trait_constraint: &TraitConstraint) -> String { + let trait_constraint_string = display_trait_id_and_generics( + interner, + &trait_constraint.trait_id, + &trait_constraint.trait_generics, + ); + format!("{}: {}", trait_constraint.typ, trait_constraint_string) +} 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 be2afd13507e..6436c2562bcf 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 @@ -14,7 +14,7 @@ use crate::ast::{ TypeImpl, }; use crate::macros_api::NodeInterner; -use crate::node_interner::{ModuleAttributes, ReferenceId}; +use crate::node_interner::ModuleAttributes; use crate::{ graph::CrateId, hir::def_collector::dc_crate::{UnresolvedStruct, UnresolvedTrait}, @@ -232,6 +232,13 @@ impl<'a> ModCollector<'a> { let location = Location::new(function.span(), self.file_id); context.def_interner.push_function(func_id, &function.def, module, location); + if context.def_interner.is_in_lsp_mode() + && !function.def.is_test() + && !function.def.is_private() + { + context.def_interner.register_function(func_id, &function.def); + } + // Now link this func_id to a crate level map with the noir function and the module id // Encountering a NoirFunction, we retrieve it's module_data to get the namespace // Once we have lowered it to a HirFunction, we retrieve it's Id from the DefInterner @@ -307,8 +314,8 @@ impl<'a> ModCollector<'a> { }; // Add the struct to scope so its path can be looked up later - let result = - self.def_collector.def_map.modules[self.module_id.0].declare_struct(name, id); + let result = self.def_collector.def_map.modules[self.module_id.0] + .declare_struct(name.clone(), id); if let Err((first_def, second_def)) = result { let error = DefCollectorErrorKind::Duplicate { @@ -322,10 +329,10 @@ impl<'a> ModCollector<'a> { // And store the TypeId -> StructType mapping somewhere it is reachable self.def_collector.items.types.insert(id, unresolved); - context.def_interner.add_definition_location( - ReferenceId::Struct(id), - Some(ModuleId { krate, local_id: self.module_id }), - ); + if context.def_interner.is_in_lsp_mode() { + let parent_module_id = ModuleId { krate, local_id: self.module_id }; + context.def_interner.register_struct(id, name.to_string(), parent_module_id); + } } definition_errors } @@ -383,7 +390,7 @@ impl<'a> ModCollector<'a> { // Add the type alias to scope so its path can be looked up later let result = self.def_collector.def_map.modules[self.module_id.0] - .declare_type_alias(name, type_alias_id); + .declare_type_alias(name.clone(), type_alias_id); if let Err((first_def, second_def)) = result { let err = DefCollectorErrorKind::Duplicate { @@ -396,10 +403,11 @@ impl<'a> ModCollector<'a> { self.def_collector.items.type_aliases.insert(type_alias_id, unresolved); - context.def_interner.add_definition_location( - ReferenceId::Alias(type_alias_id), - Some(ModuleId { krate, local_id: self.module_id }), - ); + if context.def_interner.is_in_lsp_mode() { + let parent_module_id = ModuleId { krate, local_id: self.module_id }; + let name = name.to_string(); + context.def_interner.register_type_alias(type_alias_id, name, parent_module_id); + } } errors } @@ -432,8 +440,8 @@ impl<'a> ModCollector<'a> { }; // Add the trait to scope so its path can be looked up later - let result = - self.def_collector.def_map.modules[self.module_id.0].declare_trait(name, trait_id); + let result = self.def_collector.def_map.modules[self.module_id.0] + .declare_trait(name.clone(), trait_id); if let Err((first_def, second_def)) = result { let error = DefCollectorErrorKind::Duplicate { @@ -567,10 +575,10 @@ impl<'a> ModCollector<'a> { }; context.def_interner.push_empty_trait(trait_id, &unresolved, resolved_generics); - context.def_interner.add_definition_location( - ReferenceId::Trait(trait_id), - Some(ModuleId { krate, local_id: self.module_id }), - ); + if context.def_interner.is_in_lsp_mode() { + let parent_module_id = ModuleId { krate, local_id: self.module_id }; + context.def_interner.register_trait(trait_id, name.to_string(), parent_module_id); + } self.def_collector.items.traits.insert(trait_id, unresolved); } @@ -766,6 +774,10 @@ impl<'a> ModCollector<'a> { parent: self.module_id, }, ); + + if context.def_interner.is_in_lsp_mode() { + context.def_interner.register_module(mod_id, mod_name.0.contents.clone()); + } } Ok(mod_id) diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/module_def.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/module_def.rs index 54d092f95151..a487bda81b3a 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/module_def.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/module_def.rs @@ -3,7 +3,7 @@ use crate::node_interner::{FuncId, GlobalId, StructId, TraitId, TypeAliasId}; use super::ModuleId; /// A generic ID that references either a module, function, type, interface or global -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum ModuleDefId { ModuleId(ModuleId), FunctionId(FuncId), 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 8eba8215f84e..380753d81989 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 @@ -136,6 +136,8 @@ pub enum TypeCheckError { UnconstrainedReferenceToConstrained { span: Span }, #[error("Slices cannot be returned from an unconstrained runtime to a constrained runtime")] UnconstrainedSliceReturnToConstrained { span: Span }, + #[error("Call to unconstrained function is unsafe and must be in an unconstrained function or unsafe block")] + Unsafe { span: Span }, #[error("Slices must have constant length")] NonConstantSliceLength { span: Span }, #[error("Only sized types may be used in the entry point to a program")] @@ -356,6 +358,9 @@ impl<'a> From<&'a TypeCheckError> for Diagnostic { let msg = "turbofish (`::<_>`) usage at this position isn't supported yet"; Diagnostic::simple_error(msg.to_string(), "".to_string(), *span) }, + TypeCheckError::Unsafe { span } => { + Diagnostic::simple_warning(error.to_string(), String::new(), *span) + } } } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/expr.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/expr.rs index e85d30f0c321..8137e74da30c 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/expr.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/expr.rs @@ -37,6 +37,7 @@ pub enum HirExpression { Quote(Tokens), Unquote(Tokens), Comptime(HirBlockExpression), + Unsafe(HirBlockExpression), Error, } 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 6b66cf1ab4a3..29b0cb7b8af6 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 @@ -194,9 +194,9 @@ impl FuncMeta { /// Gives the (uninstantiated) return type of this function. pub fn return_type(&self) -> &Type { match &self.typ { - Type::Function(_, ret, _env) => ret, + Type::Function(_, ret, _env, _unconstrained) => ret, Type::Forall(_, typ) => match typ.as_ref() { - Type::Function(_, ret, _env) => ret, + Type::Function(_, ret, _env, _unconstrained) => ret, _ => unreachable!(), }, _ => unreachable!(), diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/traits.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/traits.rs index 099c9ea78f7c..df6ccc0492c6 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/traits.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/traits.rs @@ -142,9 +142,9 @@ impl std::fmt::Display for Trait { impl TraitFunction { pub fn arguments(&self) -> &[Type] { match &self.typ { - Type::Function(args, _, _) => args, + Type::Function(args, _, _, _) => args, Type::Forall(_, typ) => match typ.as_ref() { - Type::Function(args, _, _) => args, + Type::Function(args, _, _, _) => args, _ => unreachable!("Trait function does not have a function type"), }, _ => unreachable!("Trait function does not have a function type"), @@ -161,9 +161,9 @@ impl TraitFunction { pub fn return_type(&self) -> &Type { match &self.typ { - Type::Function(_, return_type, _) => return_type, + Type::Function(_, return_type, _, _) => return_type, Type::Forall(_, typ) => match typ.as_ref() { - Type::Function(_, return_type, _) => return_type, + Type::Function(_, return_type, _, _) => return_type, _ => unreachable!("Trait function does not have a function type"), }, _ => unreachable!("Trait function does not have a function type"), 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 177d23c74dde..d2f499950f15 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 @@ -88,7 +88,12 @@ pub enum Type { /// the environment should be `Unit` by default, /// for closures it should contain a `Tuple` type with the captured /// variable types. - Function(Vec, /*return_type:*/ Box, /*environment:*/ Box), + Function( + Vec, + /*return_type:*/ Box, + /*environment:*/ Box, + /*unconstrained*/ bool, + ), /// &mut T MutableReference(Box), @@ -146,6 +151,7 @@ pub enum QuotedType { StructDefinition, TraitConstraint, TraitDefinition, + TraitImpl, FunctionDefinition, Module, } @@ -662,15 +668,19 @@ impl std::fmt::Display for Type { let typevars = vecmap(typevars, |var| var.id().to_string()); write!(f, "forall {}. {}", typevars.join(" "), typ) } - Type::Function(args, ret, env) => { + Type::Function(args, ret, env, unconstrained) => { + if *unconstrained { + write!(f, "unconstrained ")?; + } + let closure_env_text = match **env { Type::Unit => "".to_string(), - _ => format!(" with env {env}"), + _ => format!("[{env}]"), }; let args = vecmap(args.iter(), ToString::to_string); - write!(f, "fn({}) -> {ret}{closure_env_text}", args.join(", ")) + write!(f, "fn{closure_env_text}({}) -> {ret}", args.join(", ")) } Type::MutableReference(element) => { write!(f, "&mut {element}") @@ -727,6 +737,7 @@ impl std::fmt::Display for QuotedType { QuotedType::StructDefinition => write!(f, "StructDefinition"), QuotedType::TraitDefinition => write!(f, "TraitDefinition"), QuotedType::TraitConstraint => write!(f, "TraitConstraint"), + QuotedType::TraitImpl => write!(f, "TraitImpl"), QuotedType::FunctionDefinition => write!(f, "FunctionDefinition"), QuotedType::Module => write!(f, "Module"), } @@ -853,7 +864,7 @@ impl Type { Type::Tuple(fields) => { fields.iter().any(|field| field.contains_numeric_typevar(target_id)) } - Type::Function(parameters, return_type, env) => { + Type::Function(parameters, return_type, env, _unconstrained) => { parameters.iter().any(|parameter| parameter.contains_numeric_typevar(target_id)) || return_type.contains_numeric_typevar(target_id) || env.contains_numeric_typevar(target_id) @@ -933,7 +944,7 @@ impl Type { field.find_numeric_type_vars(found_names); } } - Type::Function(parameters, return_type, env) => { + Type::Function(parameters, return_type, env, _unconstrained) => { for parameter in parameters.iter() { parameter.find_numeric_type_vars(found_names); } @@ -990,7 +1001,7 @@ impl Type { Type::FmtString(_, _) | Type::TypeVariable(_, _) | Type::NamedGeneric(_, _, _) - | Type::Function(_, _, _) + | Type::Function(_, _, _, _) | Type::MutableReference(_) | Type::Forall(_, _) | Type::Quoted(_) @@ -1039,7 +1050,7 @@ impl Type { Type::FmtString(_, _) // To enable this we would need to determine the size of the closure outputs at compile-time. // This is possible as long as the output size is not dependent upon a witness condition. - | Type::Function(_, _, _) + | Type::Function(_, _, _, _) | Type::Slice(_) | Type::MutableReference(_) | Type::Forall(_, _) @@ -1077,7 +1088,7 @@ impl Type { | Type::Slice(_) | Type::TypeVariable(_, _) | Type::NamedGeneric(_, _, _) - | Type::Function(_, _, _) + | Type::Function(_, _, _, _) | Type::FmtString(_, _) | Type::InfixExpr(..) | Type::Error => true, @@ -1208,7 +1219,7 @@ impl Type { | Type::TypeVariable(_, _) | Type::TraitAsType(..) | Type::NamedGeneric(_, _, _) - | Type::Function(_, _, _) + | Type::Function(_, _, _, _) | Type::MutableReference(_) | Type::Forall(_, _) | Type::Constant(_) @@ -1576,8 +1587,11 @@ impl Type { } } - (Function(params_a, ret_a, env_a), Function(params_b, ret_b, env_b)) => { - if params_a.len() == params_b.len() { + ( + Function(params_a, ret_a, env_a, unconstrained_a), + Function(params_b, ret_b, env_b, unconstrained_b), + ) => { + if unconstrained_a == unconstrained_b && params_a.len() == params_b.len() { for (a, b) in params_a.iter().zip(params_b.iter()) { a.try_unify(b, bindings)?; } @@ -1768,12 +1782,38 @@ impl Type { ) { let mut bindings = TypeBindings::new(); - if let Err(UnificationError) = self.try_unify(expected, &mut bindings) { - if !self.try_array_to_slice_coercion(expected, expression, interner) { - errors.push(make_error()); - } - } else { + if let Ok(()) = self.try_unify(expected, &mut bindings) { Type::apply_type_bindings(bindings); + return; + } + + if self.try_array_to_slice_coercion(expected, expression, interner) { + return; + } + + // Try to coerce `fn (..) -> T` to `unconstrained fn (..) -> T` + if let Some(coerced_self) = self.try_fn_to_unconstrained_fn_coercion(expected) { + coerced_self.unify_with_coercions(expected, expression, interner, errors, make_error); + return; + } + + errors.push(make_error()); + } + + // If `self` and `expected` are function types, tries to coerce `self` to `expected`. + // Returns None if no coercion can be applied, otherwise returns `self` coerced to `expected`. + fn try_fn_to_unconstrained_fn_coercion(&self, expected: &Type) -> Option { + // If `self` and `expected` are function types, `self` can be coerced to `expected` + // if `self` is unconstrained and `expected` is not. The other way around is an error, though. + if let ( + Type::Function(params, ret, env, unconstrained_self), + Type::Function(_, _, _, unconstrained_expected), + ) = (self.follow_bindings(), expected.follow_bindings()) + { + (!unconstrained_self && unconstrained_expected) + .then(|| Type::Function(params, ret, env, unconstrained_expected)) + } else { + None } } @@ -1789,13 +1829,17 @@ impl Type { let target = target.follow_bindings(); if let (Type::Array(_size, element1), Type::Slice(element2)) = (&this, &target) { - // Still have to ensure the element types match. - // Don't need to issue an error here if not, it will be done in unify_with_coercions - let mut bindings = TypeBindings::new(); - if element1.try_unify(element2, &mut bindings).is_ok() { - convert_array_expression_to_slice(expression, this, target, interner); - Self::apply_type_bindings(bindings); - return true; + // We can only do the coercion if the `as_slice` method exists. + // This is usually true, but some tests don't have access to the standard library. + if let Some(as_slice) = interner.lookup_primitive_method(&this, "as_slice") { + // Still have to ensure the element types match. + // Don't need to issue an error here if not, it will be done in unify_with_coercions + let mut bindings = TypeBindings::new(); + if element1.try_unify(element2, &mut bindings).is_ok() { + convert_array_expression_to_slice(expression, this, target, as_slice, interner); + Self::apply_type_bindings(bindings); + return true; + } } } false @@ -2070,13 +2114,13 @@ impl Type { let typ = Box::new(typ.substitute_helper(type_bindings, substitute_bound_typevars)); Type::Forall(typevars.clone(), typ) } - Type::Function(args, ret, env) => { + Type::Function(args, ret, env, unconstrained) => { let args = vecmap(args, |arg| { arg.substitute_helper(type_bindings, substitute_bound_typevars) }); let ret = Box::new(ret.substitute_helper(type_bindings, substitute_bound_typevars)); let env = Box::new(env.substitute_helper(type_bindings, substitute_bound_typevars)); - Type::Function(args, ret, env) + Type::Function(args, ret, env, *unconstrained) } Type::MutableReference(element) => Type::MutableReference(Box::new( element.substitute_helper(type_bindings, substitute_bound_typevars), @@ -2132,7 +2176,7 @@ impl Type { Type::Forall(typevars, typ) => { !typevars.iter().any(|var| var.id() == target_id) && typ.occurs(target_id) } - Type::Function(args, ret, env) => { + Type::Function(args, ret, env, _unconstrained) => { args.iter().any(|arg| arg.occurs(target_id)) || ret.occurs(target_id) || env.occurs(target_id) @@ -2186,11 +2230,11 @@ impl Type { self.clone() } - Function(args, ret, env) => { + Function(args, ret, env, unconstrained) => { let args = vecmap(args, |arg| arg.follow_bindings()); let ret = Box::new(ret.follow_bindings()); let env = Box::new(env.follow_bindings()); - Function(args, ret, env) + Function(args, ret, env, *unconstrained) } MutableReference(element) => MutableReference(Box::new(element.follow_bindings())), @@ -2282,7 +2326,7 @@ impl Type { *self = Type::TypeVariable(var.clone(), TypeVariableKind::Normal); } } - Type::Function(args, ret, env) => { + Type::Function(args, ret, env, _unconstrained) => { for arg in args { arg.replace_named_generics_with_type_variables(); } @@ -2311,12 +2355,9 @@ fn convert_array_expression_to_slice( expression: ExprId, array_type: Type, target_type: Type, + as_slice_method: crate::node_interner::FuncId, interner: &mut NodeInterner, ) { - let as_slice_method = interner - .lookup_primitive_method(&array_type, "as_slice") - .expect("Expected 'as_slice' method to be present in Noir's stdlib"); - let as_slice_id = interner.function_definition_id(as_slice_method); let location = interner.expr_location(&expression); let as_slice = HirExpression::Ident(HirIdent::non_trait_method(as_slice_id, location), None); @@ -2337,7 +2378,8 @@ fn convert_array_expression_to_slice( interner.push_expr_location(func, location.span, location.file); interner.push_expr_type(expression, target_type.clone()); - let func_type = Type::Function(vec![array_type], Box::new(target_type), Box::new(Type::Unit)); + let func_type = + Type::Function(vec![array_type], Box::new(target_type), Box::new(Type::Unit), false); interner.push_expr_type(func, func_type); } @@ -2429,10 +2471,11 @@ impl From<&Type> for PrintableType { Type::TypeVariable(_, _) => unreachable!(), Type::NamedGeneric(..) => unreachable!(), Type::Forall(..) => unreachable!(), - Type::Function(arguments, return_type, env) => PrintableType::Function { + Type::Function(arguments, return_type, env, unconstrained) => PrintableType::Function { arguments: arguments.iter().map(|arg| arg.into()).collect(), return_type: Box::new(return_type.as_ref().into()), env: Box::new(env.as_ref().into()), + unconstrained: *unconstrained, }, Type::MutableReference(typ) => { PrintableType::MutableReference { typ: Box::new(typ.as_ref().into()) } @@ -2517,7 +2560,11 @@ impl std::fmt::Debug for Type { let typevars = vecmap(typevars, |var| format!("{:?}", var)); write!(f, "forall {}. {:?}", typevars.join(" "), typ) } - Type::Function(args, ret, env) => { + Type::Function(args, ret, env, unconstrained) => { + if *unconstrained { + write!(f, "unconstrained ")?; + } + let closure_env_text = match **env { Type::Unit => "".to_string(), _ => format!(" with env {env:?}"), 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 4222d2b585fe..b9b4bdef9aa5 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs @@ -925,10 +925,12 @@ pub enum Keyword { Trait, TraitConstraint, TraitDefinition, + TraitImpl, Type, TypeType, Unchecked, Unconstrained, + Unsafe, Use, Where, While, @@ -977,10 +979,12 @@ impl fmt::Display for Keyword { Keyword::Trait => write!(f, "trait"), Keyword::TraitConstraint => write!(f, "TraitConstraint"), Keyword::TraitDefinition => write!(f, "TraitDefinition"), + Keyword::TraitImpl => write!(f, "TraitImpl"), Keyword::Type => write!(f, "type"), Keyword::TypeType => write!(f, "Type"), Keyword::Unchecked => write!(f, "unchecked"), Keyword::Unconstrained => write!(f, "unconstrained"), + Keyword::Unsafe => write!(f, "unsafe"), Keyword::Use => write!(f, "use"), Keyword::Where => write!(f, "where"), Keyword::While => write!(f, "while"), @@ -1031,11 +1035,13 @@ impl Keyword { "trait" => Keyword::Trait, "TraitConstraint" => Keyword::TraitConstraint, "TraitDefinition" => Keyword::TraitDefinition, + "TraitImpl" => Keyword::TraitImpl, "type" => Keyword::Type, "Type" => Keyword::TypeType, "StructDefinition" => Keyword::StructDefinition, "unchecked" => Keyword::Unchecked, "unconstrained" => Keyword::Unconstrained, + "unsafe" => Keyword::Unsafe, "use" => Keyword::Use, "where" => Keyword::Where, "while" => Keyword::While, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/locations.rs b/noir/noir-repo/compiler/noirc_frontend/src/locations.rs index c437676b605d..0ac13a58ecf0 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/locations.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/locations.rs @@ -1,9 +1,10 @@ use fm::FileId; use noirc_errors::Location; use rangemap::RangeMap; -use rustc_hash::FxHashMap; +use rustc_hash::FxHashMap as HashMap; use crate::{ + ast::{FunctionDefinition, ItemVisibility}, hir::def_map::{ModuleDefId, ModuleId}, macros_api::{NodeInterner, StructId}, node_interner::{DefinitionId, FuncId, GlobalId, ReferenceId, TraitId, TypeAliasId}, @@ -12,7 +13,7 @@ use petgraph::prelude::NodeIndex as PetGraphIndex; #[derive(Debug, Default)] pub(crate) struct LocationIndices { - map_file_to_range: FxHashMap>, + map_file_to_range: HashMap>, } impl LocationIndices { @@ -275,4 +276,75 @@ impl NodeInterner { .neighbors_directed(reference_index, petgraph::Direction::Outgoing) .next() } + + pub(crate) fn register_module(&mut self, id: ModuleId, name: String) { + self.register_name_for_auto_import(name, ModuleDefId::ModuleId(id), ItemVisibility::Public); + } + + pub(crate) fn register_global( + &mut self, + id: GlobalId, + name: String, + parent_module_id: ModuleId, + ) { + self.add_definition_location(ReferenceId::Global(id), Some(parent_module_id)); + + let visibility = ItemVisibility::Public; + self.register_name_for_auto_import(name, ModuleDefId::GlobalId(id), visibility); + } + + pub(crate) fn register_struct( + &mut self, + id: StructId, + name: String, + parent_module_id: ModuleId, + ) { + self.add_definition_location(ReferenceId::Struct(id), Some(parent_module_id)); + + let visibility = ItemVisibility::Public; + self.register_name_for_auto_import(name, ModuleDefId::TypeId(id), visibility); + } + + pub(crate) fn register_trait(&mut self, id: TraitId, name: String, parent_module_id: ModuleId) { + self.add_definition_location(ReferenceId::Trait(id), Some(parent_module_id)); + + self.register_name_for_auto_import(name, ModuleDefId::TraitId(id), ItemVisibility::Public); + } + + pub(crate) fn register_type_alias( + &mut self, + id: TypeAliasId, + name: String, + parent_module_id: ModuleId, + ) { + self.add_definition_location(ReferenceId::Alias(id), Some(parent_module_id)); + + let visibility = ItemVisibility::Public; + self.register_name_for_auto_import(name, ModuleDefId::TypeAliasId(id), visibility); + } + + pub(crate) fn register_function(&mut self, id: FuncId, func_def: &FunctionDefinition) { + self.register_name_for_auto_import( + func_def.name.0.contents.clone(), + ModuleDefId::FunctionId(id), + func_def.visibility, + ); + } + + fn register_name_for_auto_import( + &mut self, + name: String, + module_def_id: ModuleDefId, + visibility: ItemVisibility, + ) { + if !self.lsp_mode { + return; + } + + self.auto_import_names.entry(name).or_default().push((module_def_id, visibility)); + } + + pub fn get_auto_import_names(&self) -> &HashMap> { + &self.auto_import_names + } } 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 f2ed9433e615..f7bcfd58b72e 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/ast.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/ast.rs @@ -288,7 +288,12 @@ pub enum Type { Tuple(Vec), Slice(Box), MutableReference(Box), - Function(/*args:*/ Vec, /*ret:*/ Box, /*env:*/ Box), + Function( + /*args:*/ Vec, + /*ret:*/ Box, + /*env:*/ Box, + /*unconstrained:*/ bool, + ), } impl Type { @@ -419,7 +424,11 @@ impl std::fmt::Display for Type { let elements = vecmap(elements, ToString::to_string); write!(f, "({})", elements.join(", ")) } - Type::Function(args, ret, env) => { + Type::Function(args, ret, env, unconstrained) => { + if *unconstrained { + write!(f, "unconstrained ")?; + } + let args = vecmap(args, ToString::to_string); let closure_env_text = match **env { Type::Unit => "".to_string(), 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 5ac730db4006..510b81d9acb8 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -442,6 +442,7 @@ impl<'interner> Monomorphizer<'interner> { }, HirExpression::Literal(HirLiteral::Unit) => ast::Expression::Block(vec![]), HirExpression::Block(block) => self.block(block.statements)?, + HirExpression::Unsafe(block) => self.block(block.statements)?, HirExpression::Prefix(prefix) => { let rhs = self.expr(prefix.rhs)?; @@ -1003,15 +1004,17 @@ impl<'interner> Monomorphizer<'interner> { ast::Type::Tuple(fields) } - HirType::Function(args, ret, env) => { + HirType::Function(args, ret, env, unconstrained) => { let args = try_vecmap(args, |x| Self::convert_type(x, location))?; let ret = Box::new(Self::convert_type(ret, location)?); let env = Self::convert_type(env, location)?; match &env { - ast::Type::Unit => ast::Type::Function(args, ret, Box::new(env)), + ast::Type::Unit => { + ast::Type::Function(args, ret, Box::new(env), *unconstrained) + } ast::Type::Tuple(_elements) => ast::Type::Tuple(vec![ env.clone(), - ast::Type::Function(args, ret, Box::new(env)), + ast::Type::Function(args, ret, Box::new(env), *unconstrained), ]), _ => { unreachable!( @@ -1100,7 +1103,7 @@ impl<'interner> Monomorphizer<'interner> { Ok(()) } - HirType::Function(args, ret, env) => { + HirType::Function(args, ret, env, _) => { for arg in args { Self::check_type(arg, location)?; } @@ -1122,7 +1125,7 @@ impl<'interner> Monomorphizer<'interner> { true } else if let ast::Type::Tuple(elements) = t { if elements.len() == 2 { - matches!(elements[1], ast::Type::Function(_, _, _)) + matches!(elements[1], ast::Type::Function(_, _, _, _)) } else { false } @@ -1132,7 +1135,7 @@ impl<'interner> Monomorphizer<'interner> { } fn is_function_closure_type(&self, t: &ast::Type) -> bool { - if let ast::Type::Function(_, _, env) = t { + if let ast::Type::Function(_, _, env, _) = t { let e = (*env).clone(); matches!(*e, ast::Type::Tuple(_captures)) } else { @@ -1499,8 +1502,12 @@ impl<'interner> Monomorphizer<'interner> { }; self.push_function(id, function); - let typ = - ast::Type::Function(parameter_types, Box::new(ret_type), Box::new(ast::Type::Unit)); + let typ = ast::Type::Function( + parameter_types, + Box::new(ret_type), + Box::new(ast::Type::Unit), + false, + ); let name = lambda_name.to_owned(); Ok(ast::Expression::Ident(ast::Ident { @@ -1568,7 +1575,7 @@ impl<'interner> Monomorphizer<'interner> { })?); let expr_type = self.interner.id_type(expr); - let env_typ = if let types::Type::Function(_, _, function_env_type) = expr_type { + let env_typ = if let types::Type::Function(_, _, function_env_type, _) = expr_type { Self::convert_type(&function_env_type, location)? } else { unreachable!("expected a Function type for a Lambda node") @@ -1598,8 +1605,12 @@ impl<'interner> Monomorphizer<'interner> { let body = self.expr(lambda.body)?; self.lambda_envs_stack.pop(); - let lambda_fn_typ: ast::Type = - ast::Type::Function(parameter_types, Box::new(ret_type), Box::new(env_typ.clone())); + let lambda_fn_typ: ast::Type = ast::Type::Function( + parameter_types, + Box::new(ret_type), + Box::new(env_typ.clone()), + false, + ); let lambda_fn = ast::Expression::Ident(ast::Ident { definition: Definition::Function(id), mutable: false, @@ -1649,7 +1660,7 @@ impl<'interner> Monomorphizer<'interner> { Ok((block_let_stmt, closure_ident)) } - /// Implements std::unsafe::zeroed by returning an appropriate zeroed + /// Implements std::unsafe_func::zeroed by returning an appropriate zeroed /// ast literal or collection node for the given type. Note that for functions /// there is no obvious zeroed value so this should be considered unsafe to use. fn zeroed_value_of_type( @@ -1689,9 +1700,8 @@ impl<'interner> Monomorphizer<'interner> { ast::Type::Tuple(fields) => ast::Expression::Tuple(vecmap(fields, |field| { self.zeroed_value_of_type(field, location) })), - ast::Type::Function(parameter_types, ret_type, env) => { - self.create_zeroed_function(parameter_types, ret_type, env, location) - } + ast::Type::Function(parameter_types, ret_type, env, unconstrained) => self + .create_zeroed_function(parameter_types, ret_type, env, *unconstrained, location), ast::Type::Slice(element_type) => { ast::Expression::Literal(ast::Literal::Slice(ast::ArrayLiteral { contents: vec![], @@ -1713,7 +1723,7 @@ impl<'interner> Monomorphizer<'interner> { } // Creating a zeroed function value is almost always an error if it is used later, - // Hence why std::unsafe::zeroed is unsafe. + // Hence why std::unsafe_func::zeroed is unsafe. // // To avoid confusing later passes, we arbitrarily choose to construct a function // that satisfies the input type by discarding all its parameters and returning a @@ -1723,6 +1733,7 @@ impl<'interner> Monomorphizer<'interner> { parameter_types: &[ast::Type], ret_type: &ast::Type, env_type: &ast::Type, + unconstrained: bool, location: noirc_errors::Location, ) -> ast::Expression { let lambda_name = "zeroed_lambda"; @@ -1737,7 +1748,6 @@ impl<'interner> Monomorphizer<'interner> { let return_type = ret_type.clone(); let name = lambda_name.to_owned(); - let unconstrained = false; let function = ast::Function { id, name, @@ -1759,6 +1769,7 @@ impl<'interner> Monomorphizer<'interner> { parameter_types.to_owned(), Box::new(ret_type.clone()), Box::new(env_type.clone()), + unconstrained, ), }) } 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 43e742b940e7..46ec2676930d 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs @@ -19,6 +19,7 @@ use crate::hir::comptime; use crate::hir::def_collector::dc_crate::CompilationError; use crate::hir::def_collector::dc_crate::{UnresolvedStruct, UnresolvedTrait, UnresolvedTypeAlias}; use crate::hir::def_map::{LocalModuleId, ModuleId}; +use crate::macros_api::ModuleDefId; use crate::macros_api::UnaryOp; use crate::QuotedType; @@ -179,10 +180,10 @@ pub struct NodeInterner { /// may have both `impl Struct { fn foo(){} }` and `impl Struct { fn foo(){} }`. /// If this happens, the returned Vec will have 2 entries and we'll need to further /// disambiguate them by checking the type of each function. - struct_methods: HashMap<(StructId, String), Methods>, + struct_methods: HashMap>, /// Methods on primitive types defined in the stdlib. - primitive_methods: HashMap<(TypeMethodKey, String), Methods>, + primitive_methods: HashMap>, // For trait implementation functions, this is their self type and trait they belong to func_id_to_trait: HashMap, @@ -231,6 +232,11 @@ pub struct NodeInterner { // (ReferenceId::Reference and ReferenceId::Local aren't included here) pub(crate) reference_modules: HashMap, + // All names (and their definitions) that can be offered for auto_import. + // These include top-level functions, global variables and types, but excludes + // impl and trait-impl methods. + pub(crate) auto_import_names: 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. @@ -602,6 +608,7 @@ impl Default for NodeInterner { reference_graph: petgraph::graph::DiGraph::new(), reference_graph_indices: HashMap::default(), reference_modules: HashMap::default(), + auto_import_names: HashMap::default(), comptime_scopes: vec![HashMap::default()], } } @@ -910,6 +917,7 @@ impl NodeInterner { // This needs to be done after pushing the definition since it will reference the // location that was stored self.add_definition_location(ReferenceId::Function(id), Some(module)); + definition_id } @@ -951,12 +959,12 @@ impl NodeInterner { /// Returns the [`FuncId`] corresponding to the function referred to by `expr_id` pub fn lookup_function_from_expr(&self, expr: &ExprId) -> Option { if let HirExpression::Ident(HirIdent { id, .. }, _) = self.expression(expr) { - if let Some(DefinitionKind::Function(func_id)) = - self.try_definition(id).map(|def| &def.kind) - { - Some(*func_id) - } else { - None + match self.try_definition(id).map(|def| &def.kind) { + Some(DefinitionKind::Function(func_id)) => Some(*func_id), + Some(DefinitionKind::Local(Some(expr_id))) => { + self.lookup_function_from_expr(expr_id) + } + _ => None, } } else { None @@ -1131,22 +1139,27 @@ impl NodeInterner { self.structs[&id].clone() } - pub fn get_struct_methods(&self, id: StructId) -> Vec { - self.struct_methods - .keys() - .filter_map(|(key_id, name)| { - if key_id == &id { - Some( - self.struct_methods - .get(&(*key_id, name.clone())) - .expect("get_struct_methods given invalid StructId") - .clone(), - ) - } else { - None - } - }) - .collect() + pub fn get_struct_methods(&self, id: StructId) -> Option<&HashMap> { + self.struct_methods.get(&id) + } + + fn get_primitive_methods(&self, key: TypeMethodKey) -> Option<&HashMap> { + self.primitive_methods.get(&key) + } + + pub fn get_type_methods(&self, typ: &Type) -> Option<&HashMap> { + match typ { + Type::Struct(struct_type, _) => { + let struct_type = struct_type.borrow(); + self.get_struct_methods(struct_type.id) + } + Type::Alias(type_alias, generics) => { + let type_alias = type_alias.borrow(); + let typ = type_alias.get_type(generics); + self.get_type_methods(&typ) + } + _ => get_type_method_key(typ).and_then(|key| self.get_primitive_methods(key)), + } } pub fn get_trait(&self, id: TraitId) -> &Trait { @@ -1199,14 +1212,19 @@ impl NodeInterner { pub fn id_type_substitute_trait_as_type(&self, def_id: DefinitionId) -> Type { let typ = self.definition_type(def_id); - if let Type::Function(args, ret, env) = &typ { + if let Type::Function(args, ret, env, unconstrained) = &typ { let def = self.definition(def_id); if let Type::TraitAsType(..) = ret.as_ref() { if let DefinitionKind::Function(func_id) = def.kind { let f = self.function(&func_id); let func_body = f.as_expr(); let ret_type = self.id_type(func_body); - let new_type = Type::Function(args.clone(), Box::new(ret_type), env.clone()); + let new_type = Type::Function( + args.clone(), + Box::new(ret_type), + env.clone(), + *unconstrained, + ); return new_type; } } @@ -1300,8 +1318,12 @@ impl NodeInterner { return Some(existing); } - let key = (id, method_name); - self.struct_methods.entry(key).or_default().add_method(method_id, is_trait_method); + self.struct_methods + .entry(id) + .or_default() + .entry(method_name) + .or_default() + .add_method(method_id, is_trait_method); None } Type::Error => None, @@ -1314,7 +1336,9 @@ impl NodeInterner { unreachable!("Cannot add a method to the unsupported type '{}'", other) }); self.primitive_methods - .entry((key, method_name)) + .entry(key) + .or_default() + .entry(method_name) .or_default() .add_method(method_id, is_trait_method); None @@ -1648,7 +1672,7 @@ impl NodeInterner { method_name: &str, force_type_check: bool, ) -> Option { - let methods = self.struct_methods.get(&(id, method_name.to_owned())); + let methods = self.struct_methods.get(&id).and_then(|h| h.get(method_name)); // If there is only one method, just return it immediately. // It will still be typechecked later. @@ -1673,8 +1697,8 @@ impl NodeInterner { } else { // Failed to find a match for the type in question, switch to looking at impls // for all types `T`, e.g. `impl Foo for T` - let key = &(TypeMethodKey::Generic, method_name.to_owned()); - let global_methods = self.primitive_methods.get(key)?; + let global_methods = + self.primitive_methods.get(&TypeMethodKey::Generic)?.get(method_name)?; global_methods.find_matching_method(typ, self) } } @@ -1682,7 +1706,7 @@ impl NodeInterner { /// Looks up a given method name on the given primitive type. pub fn lookup_primitive_method(&self, typ: &Type, method_name: &str) -> Option { let key = get_type_method_key(typ)?; - let methods = self.primitive_methods.get(&(key, method_name.to_owned()))?; + let methods = self.primitive_methods.get(&key)?.get(method_name)?; self.find_matching_method(typ, Some(methods), method_name) } @@ -1779,7 +1803,7 @@ impl NodeInterner { let the_trait = self.get_trait(trait_id); self.ordering_type = match &the_trait.methods[0].typ { Type::Forall(_, typ) => match typ.as_ref() { - Type::Function(_, return_type, _) => Some(return_type.as_ref().clone()), + Type::Function(_, return_type, _, _) => Some(return_type.as_ref().clone()), other => unreachable!("Expected function type for `cmp`, found {}", other), }, other => unreachable!("Expected Forall type for `cmp`, found {}", other), @@ -1803,6 +1827,11 @@ impl NodeInterner { self.prefix_operator_traits.insert(operator, trait_id); } + pub fn is_operator_trait(&self, trait_id: TraitId) -> bool { + self.infix_operator_traits.values().any(|id| *id == trait_id) + || self.prefix_operator_traits.values().any(|id| *id == trait_id) + } + /// This function is needed when creating a NodeInterner for testing so that calls /// to `get_operator_trait` do not panic when the stdlib isn't present. #[cfg(test)] @@ -1977,7 +2006,7 @@ impl NodeInterner { }; let env = Box::new(Type::Unit); - (Type::Function(args, Box::new(ret.clone()), env), ret) + (Type::Function(args, Box::new(ret.clone()), env, false), ret) } /// Returns the type of a prefix operator (which is always a function), along with its return type. @@ -1986,7 +2015,7 @@ impl NodeInterner { let args = vec![rhs_type]; let ret = self.id_type(operator_expr); let env = Box::new(Type::Unit); - (Type::Function(args, Box::new(ret.clone()), env), ret) + (Type::Function(args, Box::new(ret.clone()), env, false), ret) } pub fn is_in_lsp_mode(&self) -> bool { @@ -2017,7 +2046,7 @@ impl Methods { } /// Iterate through each method, starting with the direct methods - fn iter(&self) -> impl Iterator + '_ { + pub fn iter(&self) -> impl Iterator + '_ { self.direct.iter().copied().chain(self.trait_impl_methods.iter().copied()) } @@ -2027,7 +2056,7 @@ impl Methods { // at most 1 matching method in this list. for method in self.iter() { match interner.function_meta(&method).typ.instantiate(interner).0 { - Type::Function(args, _, _) => { + Type::Function(args, _, _, _) => { if let Some(object) = args.first() { let mut bindings = TypeBindings::new(); @@ -2078,7 +2107,7 @@ fn get_type_method_key(typ: &Type) -> Option { Type::FmtString(_, _) => Some(FmtString), Type::Unit => Some(Unit), Type::Tuple(_) => Some(Tuple), - Type::Function(_, _, _) => Some(Function), + Type::Function(_, _, _, _) => Some(Function), Type::NamedGeneric(_, _, _) => Some(Generic), Type::Quoted(quoted) => Some(Quoted(*quoted)), Type::MutableReference(element) => get_type_method_key(element), 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 36d3ce5898cc..ebb58ddc2241 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/errors.rs @@ -16,8 +16,20 @@ pub enum ParserErrorReason { ExpectedFieldName(Token), #[error("expected a pattern but found a type - {0}")] ExpectedPatternButFoundType(Token), + #[error("expected an identifier after .")] + ExpectedIdentifierAfterDot, #[error("expected an identifier after ::")] ExpectedIdentifierAfterColons, + #[error("expected {{ or -> after function parameters")] + ExpectedLeftBraceOrArrowAfterFunctionParameters, + #[error("expected {{ after if condition")] + ExpectedLeftBraceAfterIfCondition, + #[error("expected <, where or {{ after trait name")] + ExpectedLeftBracketOrWhereOrLeftBraceOrArrowAfterTraitName, + #[error("expected <, where or {{ after impl type")] + ExpectedLeftBracketOrWhereOrLeftBraceOrArrowAfterImplType, + #[error("expected <, where or {{ after trait impl for type")] + ExpectedLeftBracketOrWhereOrLeftBraceOrArrowAfterTraitImplForType, #[error("Expected a ; separating these two statements")] MissingSeparatingSemi, #[error("constrain keyword is deprecated")] 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 5a97d66df9a8..eddf85becfee 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs @@ -219,13 +219,27 @@ fn top_level_statement<'a>( /// /// implementation: 'impl' generics type '{' function_definition ... '}' fn implementation() -> impl NoirParser { + let methods_or_error = just(Token::LeftBrace) + .ignore_then(spanned(function::function_definition(true)).repeated()) + .then_ignore(just(Token::RightBrace)) + .or_not() + .validate(|methods, span, emit| { + if let Some(methods) = methods { + methods + } else { + emit(ParserError::with_reason( + ParserErrorReason::ExpectedLeftBracketOrWhereOrLeftBraceOrArrowAfterImplType, + span, + )); + vec![] + } + }); + keyword(Keyword::Impl) .ignore_then(function::generics()) .then(parse_type().map_with_span(|typ, span| (typ, span))) .then(where_clause()) - .then_ignore(just(Token::LeftBrace)) - .then(spanned(function::function_definition(true)).repeated()) - .then_ignore(just(Token::RightBrace)) + .then(methods_or_error) .map(|args| { let ((other_args, where_clause), methods) = args; let (generics, (object_type, type_span)) = other_args; @@ -377,6 +391,7 @@ pub fn block<'a>( statement: impl NoirParser + 'a, ) -> impl NoirParser + 'a { use Token::*; + statement .recover_via(statement_recovery()) .then(just(Semicolon).or_not().map_with_span(|s, span| (s, span))) @@ -519,6 +534,15 @@ where .map(|(block, span)| ExpressionKind::Comptime(block, span)) } +fn unsafe_expr<'a, S>(statement: S) -> impl NoirParser + 'a +where + S: NoirParser + 'a, +{ + keyword(Keyword::Unsafe) + .ignore_then(spanned(block(statement))) + .map(|(block, span)| ExpressionKind::Unsafe(block, span)) +} + fn let_statement<'a, P>( expr_parser: P, ) -> impl NoirParser<((Pattern, UnresolvedType), Expression)> + 'a @@ -847,6 +871,9 @@ where ArrayIndex(Expression), Cast(UnresolvedType), MemberAccess(UnaryRhsMemberAccess), + /// This is to allow `foo.` (no identifier afterwards) to be parsed as `foo` + /// and produce an error, rather than just erroring (for LSP). + JustADot, } // `(arg1, ..., argN)` in `my_func(arg1, ..., argN)` @@ -890,7 +917,16 @@ where }) .labelled(ParsingRuleLabel::FieldAccess); - let rhs = choice((call_rhs, array_rhs, cast_rhs, member_rhs)); + let just_a_dot = + just(Token::Dot).map(|_| UnaryRhs::JustADot).validate(|value, span, emit_error| { + emit_error(ParserError::with_reason( + ParserErrorReason::ExpectedIdentifierAfterDot, + span, + )); + value + }); + + let rhs = choice((call_rhs, array_rhs, cast_rhs, member_rhs, just_a_dot)); foldl_with_span( atom(expr_parser, expr_no_constructors, statement, allow_constructors), @@ -904,6 +940,7 @@ where UnaryRhs::MemberAccess(field) => { Expression::member_access_or_method_call(lhs, field, span) } + UnaryRhs::JustADot => lhs, }, ) } @@ -931,10 +968,32 @@ where keyword(Keyword::If) .ignore_then(expr_no_constructors) - .then(if_block) - .then(keyword(Keyword::Else).ignore_then(else_block).or_not()) - .map(|((condition, consequence), alternative)| { - ExpressionKind::If(Box::new(IfExpression { condition, consequence, alternative })) + .then(if_block.then(keyword(Keyword::Else).ignore_then(else_block).or_not()).or_not()) + .validate(|(condition, consequence_and_alternative), span, emit_error| { + if let Some((consequence, alternative)) = consequence_and_alternative { + ExpressionKind::If(Box::new(IfExpression { + condition, + consequence, + alternative, + })) + } else { + // We allow `if cond` without a block mainly for LSP, so that it parses right + // and autocompletion works there. + emit_error(ParserError::with_reason( + ParserErrorReason::ExpectedLeftBraceAfterIfCondition, + span, + )); + + let span_end = condition.span.end(); + ExpressionKind::If(Box::new(IfExpression { + condition, + consequence: Expression::new( + ExpressionKind::Error, + Span::from(span_end..span_end), + ), + alternative: None, + })) + } }) }) } @@ -1080,7 +1139,8 @@ where }, lambdas::lambda(expr_parser.clone()), block(statement.clone()).map(ExpressionKind::Block), - comptime_expr(statement), + comptime_expr(statement.clone()), + unsafe_expr(statement), quote(), unquote(expr_parser.clone()), variable(), @@ -1407,6 +1467,24 @@ mod test { ); } + #[test] + fn parse_if_without_block() { + let src = "if foo"; + let parser = if_expr(expression_no_constructors(expression()), fresh_statement()); + let (expression_kind, errors) = parse_recover(parser, src); + + let expression_kind = expression_kind.unwrap(); + let ExpressionKind::If(if_expression) = expression_kind else { + panic!("Expected an if expression, got {:?}", expression_kind); + }; + + assert_eq!(if_expression.consequence.kind, ExpressionKind::Error); + assert_eq!(if_expression.alternative, None); + + assert_eq!(errors.len(), 1); + assert_eq!(errors[0].message, "expected { after if condition"); + } + #[test] fn parse_module_declaration() { parse_with(module_declaration(), "mod foo").unwrap(); @@ -1673,4 +1751,44 @@ mod test { let block_expr = block_expr.expect("Failed to parse module"); assert_eq!(block_expr.statements.len(), 2); } + + #[test] + fn test_parses_member_access_without_member_name() { + let src = "{ foo. }"; + + let (Some(block_expression), errors) = parse_recover(block(fresh_statement()), src) else { + panic!("Expected to be able to parse a block expression"); + }; + + assert_eq!(errors.len(), 1); + assert_eq!(errors[0].message, "expected an identifier after ."); + + let statement = &block_expression.statements[0]; + let StatementKind::Expression(expr) = &statement.kind else { + panic!("Expected an expression statement"); + }; + + let ExpressionKind::Variable(var) = &expr.kind else { + panic!("Expected a variable expression"); + }; + + assert_eq!(var.to_string(), "foo"); + } + + #[test] + fn parse_recover_impl_without_body() { + let src = "impl Foo"; + + let (top_level_statement, errors) = parse_recover(implementation(), src); + assert_eq!(errors.len(), 1); + assert_eq!(errors[0].message, "expected <, where or { after impl type"); + + let top_level_statement = top_level_statement.unwrap(); + let TopLevelStatement::Impl(impl_) = top_level_statement else { + panic!("Expected to parse an impl"); + }; + + assert_eq!(impl_.object_type.to_string(), "Foo"); + assert!(impl_.methods.is_empty()); + } } 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 3de48d2e02a1..567608983741 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 @@ -6,7 +6,10 @@ use super::{ self_parameter, where_clause, NoirParser, }; use crate::token::{Keyword, Token, TokenKind}; -use crate::{ast::IntegerBitSize, parser::spanned}; +use crate::{ + ast::{BlockExpression, IntegerBitSize}, + parser::spanned, +}; use crate::{ ast::{ FunctionDefinition, FunctionReturnType, ItemVisibility, NoirFunction, Param, Visibility, @@ -20,10 +23,24 @@ use crate::{ }; use chumsky::prelude::*; +use noirc_errors::Span; /// function_definition: attribute function_modifiers 'fn' ident generics '(' function_parameters ')' function_return_type block /// function_modifiers 'fn' ident generics '(' function_parameters ')' function_return_type block pub(super) fn function_definition(allow_self: bool) -> impl NoirParser { + let body_or_error = + spanned(block(fresh_statement()).or_not()).validate(|(body, body_span), span, emit| { + if let Some(body) = body { + (body, body_span) + } else { + emit(ParserError::with_reason( + ParserErrorReason::ExpectedLeftBraceOrArrowAfterFunctionParameters, + span, + )); + (BlockExpression { statements: vec![] }, Span::from(span.end()..span.end())) + } + }); + attributes() .then(function_modifiers()) .then_ignore(keyword(Keyword::Fn)) @@ -32,7 +49,7 @@ pub(super) fn function_definition(allow_self: bool) -> impl NoirParser after function parameters"); + + let noir_function = noir_function.unwrap(); + assert_eq!(noir_function.name(), "foo"); + assert_eq!(noir_function.parameters().len(), 1); + assert!(noir_function.def.body.statements.is_empty()); + } } 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 0874cadd34e3..0cf5e63f5f35 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 @@ -23,14 +23,28 @@ use crate::{ use super::{generic_type_args, parse_type, primitives::ident}; pub(super) fn trait_definition() -> impl NoirParser { + let trait_body_or_error = just(Token::LeftBrace) + .ignore_then(trait_body()) + .then_ignore(just(Token::RightBrace)) + .or_not() + .validate(|items, span, emit| { + if let Some(items) = items { + items + } else { + emit(ParserError::with_reason( + ParserErrorReason::ExpectedLeftBracketOrWhereOrLeftBraceOrArrowAfterTraitName, + span, + )); + vec![] + } + }); + attributes() .then_ignore(keyword(Keyword::Trait)) .then(ident()) .then(function::generics()) .then(where_clause()) - .then_ignore(just(Token::LeftBrace)) - .then(trait_body()) - .then_ignore(just(Token::RightBrace)) + .then(trait_body_or_error) .validate(|((((attributes, name), generics), where_clause), items), span, emit| { let attributes = validate_secondary_attributes(attributes, span, emit); TopLevelStatement::Trait(NoirTrait { @@ -70,13 +84,26 @@ fn trait_function_declaration() -> impl NoirParser { let trait_function_body_or_semicolon = block(fresh_statement()).map(Option::from).or(just(Token::Semicolon).to(Option::None)); + let trait_function_body_or_semicolon_or_error = + trait_function_body_or_semicolon.or_not().validate(|body, span, emit| { + if let Some(body) = body { + body + } else { + emit(ParserError::with_reason( + ParserErrorReason::ExpectedLeftBraceOrArrowAfterFunctionParameters, + span, + )); + None + } + }); + keyword(Keyword::Fn) .ignore_then(ident()) .then(function::generics()) .then(parenthesized(function_declaration_parameters())) .then(function_return_type().map(|(_, typ)| typ)) .then(where_clause()) - .then(trait_function_body_or_semicolon) + .then(trait_function_body_or_semicolon_or_error) .map(|(((((name, generics), parameters), return_type), where_clause), body)| { TraitItem::Function { name, generics, parameters, return_type, where_clause, body } }) @@ -101,6 +128,24 @@ 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 { + let body_or_error = + just(Token::LeftBrace) + .ignore_then(trait_implementation_body()) + .then_ignore(just(Token::RightBrace)) + .or_not() + .validate(|items, span, emit| { + if let Some(items) = items { + items + } else { + emit(ParserError::with_reason( + ParserErrorReason::ExpectedLeftBracketOrWhereOrLeftBraceOrArrowAfterTraitImplForType, + span, + )); + + vec![] + } + }); + keyword(Keyword::Impl) .ignore_then(function::generics()) .then(path_no_turbofish()) @@ -108,13 +153,10 @@ pub(super) fn trait_implementation() -> impl NoirParser { .then_ignore(keyword(Keyword::For)) .then(parse_type()) .then(where_clause()) - .then_ignore(just(Token::LeftBrace)) - .then(trait_implementation_body()) - .then_ignore(just(Token::RightBrace)) + .then(body_or_error) .map(|args| { let (((other_args, object_type), where_clause), items) = args; let ((impl_generics, trait_name), trait_generics) = other_args; - TopLevelStatement::TraitImpl(NoirTraitImpl { impl_generics, trait_name, @@ -227,4 +269,54 @@ mod test { vec!["trait MissingBody", "trait WrongDelimiter { fn foo() -> u8, fn bar() -> u8 }"], ); } + + #[test] + fn parse_recover_function_without_left_brace_or_semicolon() { + let src = "fn foo(x: i32)"; + + let (trait_item, errors) = parse_recover(trait_function_declaration(), src); + assert_eq!(errors.len(), 1); + assert_eq!(errors[0].message, "expected { or -> after function parameters"); + + let Some(TraitItem::Function { name, parameters, body, .. }) = trait_item else { + panic!("Expected to parser trait item as function"); + }; + + assert_eq!(name.to_string(), "foo"); + assert_eq!(parameters.len(), 1); + assert!(body.is_none()); + } + + #[test] + fn parse_recover_trait_without_body() { + let src = "trait Foo"; + + let (top_level_statement, errors) = parse_recover(trait_definition(), src); + assert_eq!(errors.len(), 1); + assert_eq!(errors[0].message, "expected <, where or { after trait name"); + + let top_level_statement = top_level_statement.unwrap(); + let TopLevelStatement::Trait(trait_) = top_level_statement else { + panic!("Expected to parse a trait"); + }; + + assert_eq!(trait_.name.to_string(), "Foo"); + assert!(trait_.items.is_empty()); + } + + #[test] + fn parse_recover_trait_impl_without_body() { + let src = "impl Foo for Bar"; + + let (top_level_statement, errors) = parse_recover(trait_implementation(), src); + assert_eq!(errors.len(), 1); + assert_eq!(errors[0].message, "expected <, where or { after trait impl for type"); + + let top_level_statement = top_level_statement.unwrap(); + let TopLevelStatement::TraitImpl(trait_impl) = top_level_statement else { + panic!("Expected to parse a trait impl"); + }; + + assert!(trait_impl.items.is_empty()); + } } 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 7c551ca96d19..3db2f24aa580 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 @@ -31,6 +31,7 @@ pub(super) fn parse_type_inner<'a>( struct_definition_type(), trait_constraint_type(), trait_definition_type(), + trait_impl_type(), function_definition_type(), module_type(), top_level_item_type(), @@ -107,6 +108,11 @@ pub(super) fn trait_definition_type() -> impl NoirParser { }) } +pub(super) fn trait_impl_type() -> impl NoirParser { + keyword(Keyword::TraitImpl) + .map_with_span(|_, span| UnresolvedTypeData::Quoted(QuotedType::TraitImpl).with_span(span)) +} + pub(super) fn function_definition_type() -> impl NoirParser { keyword(Keyword::FunctionDefinition).map_with_span(|_, span| { UnresolvedTypeData::Quoted(QuotedType::FunctionDefinition).with_span(span) @@ -312,13 +318,17 @@ where t.unwrap_or_else(|| UnresolvedTypeData::Unit.with_span(Span::empty(span.end()))) }); - keyword(Keyword::Fn) - .ignore_then(env) + keyword(Keyword::Unconstrained) + .or_not() + .then(keyword(Keyword::Fn)) + .map(|(unconstrained_token, _fn_token)| unconstrained_token.is_some()) + .then(env) .then(args) .then_ignore(just(Token::Arrow)) .then(type_parser) - .map_with_span(|((env, args), ret), span| { - UnresolvedTypeData::Function(args, Box::new(ret), Box::new(env)).with_span(span) + .map_with_span(|(((unconstrained, env), args), ret), span| { + UnresolvedTypeData::Function(args, Box::new(ret), Box::new(env), unconstrained) + .with_span(span) }) } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/resolve_locations.rs b/noir/noir-repo/compiler/noirc_frontend/src/resolve_locations.rs index efb430b75ebd..61ff6baf919b 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/resolve_locations.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/resolve_locations.rs @@ -31,6 +31,12 @@ impl NodeInterner { location_candidate.map(|(index, _location)| *index) } + /// Returns the Type of the expression that exists at the given location. + pub fn type_at_location(&self, location: Location) -> Option { + let index = self.find_location_index(location)?; + Some(self.id_type(index)) + } + /// Returns the [Location] of the definition of the given Ident found at [Span] of the given [FileId]. /// Returns [None] when definition is not found. pub fn get_definition_location_from( diff --git a/noir/noir-repo/compiler/noirc_frontend/src/tests.rs b/noir/noir-repo/compiler/noirc_frontend/src/tests.rs index 9124567b4e50..26a82216eeed 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/tests.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/tests.rs @@ -2460,6 +2460,209 @@ fn no_super() { assert_eq!(span.end(), 9); } +#[test] +fn cannot_call_unconstrained_function_outside_of_unsafe() { + let src = r#" + fn main() { + foo(); + } + + unconstrained fn foo() {} + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::TypeError(TypeCheckError::Unsafe { .. }) = &errors[0].0 else { + panic!("Expected an 'unsafe' error, got {:?}", errors[0].0); + }; +} + +#[test] +fn cannot_call_unconstrained_first_class_function_outside_of_unsafe() { + let src = r#" + fn main() { + let func = foo; + // Warning should trigger here + func(); + inner(func); + } + + fn inner(x: unconstrained fn() -> ()) { + // Warning should trigger here + x(); + } + + unconstrained fn foo() {} + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 2); + + for error in &errors { + let CompilationError::TypeError(TypeCheckError::Unsafe { .. }) = &error.0 else { + panic!("Expected an 'unsafe' error, got {:?}", errors[0].0); + }; + } +} + +#[test] +fn missing_unsafe_block_when_needing_type_annotations() { + // This test is a regression check that even when an unsafe block is missing + // that we still appropriately continue type checking and infer type annotations. + let src = r#" + fn main() { + let z = BigNum { limbs: [2, 0, 0] }; + assert(z.__is_zero() == false); + } + + struct BigNum { + limbs: [u64; N], + } + + impl BigNum { + unconstrained fn __is_zero_impl(self) -> bool { + let mut result: bool = true; + for i in 0..N { + result = result & (self.limbs[i] == 0); + } + result + } + } + + trait BigNumTrait { + fn __is_zero(self) -> bool; + } + + impl BigNumTrait for BigNum { + fn __is_zero(self) -> bool { + self.__is_zero_impl() + } + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::TypeError(TypeCheckError::Unsafe { .. }) = &errors[0].0 else { + panic!("Expected an 'unsafe' error, got {:?}", errors[0].0); + }; +} + +#[test] +fn cannot_pass_unconstrained_function_to_regular_function() { + let src = r#" + fn main() { + let func = foo; + expect_regular(func); + } + + unconstrained fn foo() {} + + fn expect_regular(_func: fn() -> ()) { + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + if let CompilationError::TypeError(TypeCheckError::TypeMismatch { + expected_typ, + expr_typ, + .. + }) = &errors[0].0 + { + assert_eq!(expected_typ, "fn() -> ()"); + assert_eq!(expr_typ, "unconstrained fn() -> ()"); + } else { + panic!("Expected a type mismatch error, got {:?}", errors[0].0); + }; +} + +#[test] +fn cannot_assign_unconstrained_and_regular_fn_to_variable() { + let src = r#" + fn main() { + let _func = if true { foo } else { bar }; + } + + fn foo() {} + unconstrained fn bar() {} + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::TypeError(TypeCheckError::Context { err, .. }) = &errors[0].0 else { + panic!("Expected a context error, got {:?}", errors[0].0); + }; + + if let TypeCheckError::TypeMismatch { expected_typ, expr_typ, .. } = err.as_ref() { + assert_eq!(expected_typ, "fn() -> ()"); + assert_eq!(expr_typ, "unconstrained fn() -> ()"); + } else { + panic!("Expected a type mismatch error, got {:?}", errors[0].0); + }; +} + +#[test] +fn can_pass_regular_function_to_unconstrained_function() { + let src = r#" + fn main() { + let func = foo; + expect_unconstrained(func); + } + + fn foo() {} + + fn expect_unconstrained(_func: unconstrained fn() -> ()) {} + "#; + assert_no_errors(src); +} + +#[test] +fn cannot_pass_unconstrained_function_to_constrained_function() { + let src = r#" + fn main() { + let func = foo; + expect_regular(func); + } + + unconstrained fn foo() {} + + fn expect_regular(_func: fn() -> ()) {} + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::TypeError(TypeCheckError::TypeMismatch { .. }) = &errors[0].0 else { + panic!("Expected a type mismatch error, got {:?}", errors[0].0); + }; +} + +#[test] +fn can_assign_regular_function_to_unconstrained_function_in_explicitly_typed_var() { + let src = r#" + fn main() { + let _func: unconstrained fn() -> () = foo; + } + + fn foo() {} + "#; + assert_no_errors(src); +} + +#[test] +fn can_assign_regular_function_to_unconstrained_function_in_struct_member() { + let src = r#" + fn main() { + let _ = Foo { func: foo }; + } + + fn foo() {} + + struct Foo { + func: unconstrained fn() -> (), + } + "#; + assert_no_errors(src); +} + #[test] fn trait_impl_generics_count_mismatch() { let src = r#" diff --git a/noir/noir-repo/compiler/noirc_printable_type/Cargo.toml b/noir/noir-repo/compiler/noirc_printable_type/Cargo.toml index 5140f5a5a8c4..8bb56703e8ae 100644 --- a/noir/noir-repo/compiler/noirc_printable_type/Cargo.toml +++ b/noir/noir-repo/compiler/noirc_printable_type/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true rust-version.workspace = true license.workspace = true +[lints] +workspace = true + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] diff --git a/noir/noir-repo/compiler/noirc_printable_type/src/lib.rs b/noir/noir-repo/compiler/noirc_printable_type/src/lib.rs index dfecd301b351..5ab04c6f5769 100644 --- a/noir/noir-repo/compiler/noirc_printable_type/src/lib.rs +++ b/noir/noir-repo/compiler/noirc_printable_type/src/lib.rs @@ -40,6 +40,7 @@ pub enum PrintableType { arguments: Vec, return_type: Box, env: Box, + unconstrained: bool, }, MutableReference { typ: Box, diff --git a/noir/noir-repo/compiler/wasm/Cargo.toml b/noir/noir-repo/compiler/wasm/Cargo.toml index 20272dfeecb7..c8b8c3bb06e9 100644 --- a/noir/noir-repo/compiler/wasm/Cargo.toml +++ b/noir/noir-repo/compiler/wasm/Cargo.toml @@ -6,8 +6,10 @@ edition.workspace = true rust-version.workspace = true license.workspace = true -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lints] +workspace = true +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] crate-type = ["cdylib"] diff --git a/noir/noir-repo/cspell.json b/noir/noir-repo/cspell.json index e939cd583e85..18417b376e59 100644 --- a/noir/noir-repo/cspell.json +++ b/noir/noir-repo/cspell.json @@ -216,7 +216,8 @@ "wasi", "wasmer", "Weierstraß", - "zshell" + "zshell", + "Linea" ], "ignorePaths": [ "./**/node_modules/**", diff --git a/noir/noir-repo/docs/docs/explainers/explainer-writing-noir.md b/noir/noir-repo/docs/docs/explainers/explainer-writing-noir.md index c8a42c379e66..3ef6e014a2fa 100644 --- a/noir/noir-repo/docs/docs/explainers/explainer-writing-noir.md +++ b/noir/noir-repo/docs/docs/explainers/explainer-writing-noir.md @@ -86,7 +86,7 @@ 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 + - Conceptually 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) @@ -99,12 +99,16 @@ You can dig deeper and use the `--print-acir` param to take a closer look at ind ### 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. +Some things to be mindful of when using a Field type for a regular integer value: +- A variable of type `Field` can be cast `as` an integer type (eg `u8`, `u64`) + - Note: this retains only the bits of the integer type. Eg a Field value of 260 as a `u8` becomes 4 +- For Field types arithmetic operations meaningfully overflow/underflow, yet for integer types they are checked according to their size +- Comparisons and bitwise operations do not exist for `Field`s, cast to an appropriately sized integer type when you need to :::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 diff --git a/noir/noir-repo/docs/docs/how_to/how-to-solidity-verifier.md b/noir/noir-repo/docs/docs/how_to/how-to-solidity-verifier.md index c800d91ac695..3bb96c66795d 100644 --- a/noir/noir-repo/docs/docs/how_to/how-to-solidity-verifier.md +++ b/noir/noir-repo/docs/docs/how_to/how-to-solidity-verifier.md @@ -133,7 +133,7 @@ function verify(bytes calldata _proof, bytes32[] calldata _publicInputs) externa 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 +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. @@ -239,6 +239,12 @@ For example, chains like `zkSync ERA` and `Polygon zkEVM` do not currently suppo - Polygon PoS - Scroll - Celo +- BSC +- Blast L2 +- Avalanche C-Chain +- Mode +- Linea +- Moonbeam 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. diff --git a/noir/noir-repo/docs/docs/noir/concepts/unconstrained.md b/noir/noir-repo/docs/docs/noir/concepts/unconstrained.md index 96f824c5e425..b5221b8d2ddf 100644 --- a/noir/noir-repo/docs/docs/noir/concepts/unconstrained.md +++ b/noir/noir-repo/docs/docs/noir/concepts/unconstrained.md @@ -62,11 +62,13 @@ Those are some nice savings already but we can do better. This code is all const 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: +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 out = unsafe { + u72_to_u8(num) + }; let mut reconstructed_num: u72 = 0; for i in 0..8 { @@ -92,6 +94,9 @@ 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). +Note that in order to invoke unconstrained functions we need to wrap them in an `unsafe` block, +to make it clear that the call is unconstrained. + 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 diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/how_to/how-to-solidity-verifier.md b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/how_to/how-to-solidity-verifier.md index c800d91ac695..3bb96c66795d 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/how_to/how-to-solidity-verifier.md +++ b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/how_to/how-to-solidity-verifier.md @@ -133,7 +133,7 @@ function verify(bytes calldata _proof, bytes32[] calldata _publicInputs) externa 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 +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. @@ -239,6 +239,12 @@ For example, chains like `zkSync ERA` and `Polygon zkEVM` do not currently suppo - Polygon PoS - Scroll - Celo +- BSC +- Blast L2 +- Avalanche C-Chain +- Mode +- Linea +- Moonbeam 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. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/debugger/_category_.json b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/debugger/_category_.json index f0768339ea9f..27869205ad3c 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/debugger/_category_.json +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/reference/debugger/_category_.json @@ -3,4 +3,4 @@ "position": 1, "collapsible": true, "collapsed": true -} \ No newline at end of file +} diff --git a/noir/noir-repo/noir_stdlib/src/array.nr b/noir/noir-repo/noir_stdlib/src/array.nr index af2bea12c60b..cef79e7c7f67 100644 --- a/noir/noir-repo/noir_stdlib/src/array.nr +++ b/noir/noir-repo/noir_stdlib/src/array.nr @@ -11,14 +11,20 @@ impl [T; N] { } pub fn sort_via(self, ordering: fn[Env](T, T) -> bool) -> Self { - let sorted_index = self.get_sorting_index(ordering); - let mut result = self; - // Ensure the indexes are correct - for i in 0..N { - let pos = find_index(sorted_index, i); - assert(sorted_index[pos] == i); - } + let sorted_index = unsafe { + // Safety: These indices are asserted to be the sorted element indices via `find_index` + let sorted_index: [u32; N] = self.get_sorting_index(ordering); + + for i in 0..N { + let pos = find_index(sorted_index, i); + assert(sorted_index[pos] == i); + } + + sorted_index + }; + // Sort the array using the indexes + let mut result = self; for i in 0..N { result[i] = self[sorted_index[i]]; } diff --git a/noir/noir-repo/noir_stdlib/src/cmp.nr b/noir/noir-repo/noir_stdlib/src/cmp.nr index 10182ca83b0d..ec979d607537 100644 --- a/noir/noir-repo/noir_stdlib/src/cmp.nr +++ b/noir/noir-repo/noir_stdlib/src/cmp.nr @@ -18,10 +18,12 @@ impl Eq for Field { fn eq(self, other: Field) -> bool { self == other } } impl Eq for u64 { fn eq(self, other: u64) -> bool { self == other } } impl Eq for u32 { fn eq(self, other: u32) -> bool { self == other } } +impl Eq for u16 { fn eq(self, other: u16) -> bool { self == other } } impl Eq for u8 { fn eq(self, other: u8) -> bool { self == other } } impl Eq for u1 { fn eq(self, other: u1) -> bool { self == other } } impl Eq for i8 { fn eq(self, other: i8) -> bool { self == other } } +impl Eq for i16 { fn eq(self, other: i16) -> bool { self == other } } impl Eq for i32 { fn eq(self, other: i32) -> bool { self == other } } impl Eq for i64 { fn eq(self, other: i64) -> bool { self == other } } @@ -157,6 +159,18 @@ impl Ord for u32 { } } +impl Ord for u16 { + fn cmp(self, other: u16) -> Ordering { + if self < other { + Ordering::less() + } else if self > other { + Ordering::greater() + } else { + Ordering::equal() + } + } +} + impl Ord for u8 { fn cmp(self, other: u8) -> Ordering { if self < other { @@ -181,6 +195,18 @@ impl Ord for i8 { } } +impl Ord for i16 { + fn cmp(self, other: i16) -> Ordering { + if self < other { + Ordering::less() + } else if self > other { + Ordering::greater() + } else { + Ordering::equal() + } + } +} + impl Ord for i32 { fn cmp(self, other: i32) -> Ordering { if self < other { diff --git a/noir/noir-repo/noir_stdlib/src/collections/bounded_vec.nr b/noir/noir-repo/noir_stdlib/src/collections/bounded_vec.nr index a56d6f603902..a4c0f642a828 100644 --- a/noir/noir-repo/noir_stdlib/src/collections/bounded_vec.nr +++ b/noir/noir-repo/noir_stdlib/src/collections/bounded_vec.nr @@ -7,7 +7,7 @@ struct BoundedVec { impl BoundedVec { pub fn new() -> Self { - let zeroed = crate::unsafe::zeroed(); + let zeroed = crate::mem::zeroed(); BoundedVec { storage: [zeroed; MaxLen], len: 0 } } @@ -106,7 +106,7 @@ impl BoundedVec { self.len -= 1; let elem = self.storage[self.len]; - self.storage[self.len] = crate::unsafe::zeroed(); + self.storage[self.len] = crate::mem::zeroed(); elem } diff --git a/noir/noir-repo/noir_stdlib/src/collections/map.nr b/noir/noir-repo/noir_stdlib/src/collections/map.nr index 8324583632f8..bd50f345356c 100644 --- a/noir/noir-repo/noir_stdlib/src/collections/map.nr +++ b/noir/noir-repo/noir_stdlib/src/collections/map.nr @@ -168,7 +168,9 @@ impl HashMap { for slot in self._table { if slot.is_valid() { - let (_, value) = slot.key_value_unchecked(); + let (_, value) = unsafe { + slot.key_value_unchecked() + }; values.push(value); } } diff --git a/noir/noir-repo/noir_stdlib/src/collections/umap.nr b/noir/noir-repo/noir_stdlib/src/collections/umap.nr index fe16ef6bca26..86ae79ea6446 100644 --- a/noir/noir-repo/noir_stdlib/src/collections/umap.nr +++ b/noir/noir-repo/noir_stdlib/src/collections/umap.nr @@ -119,7 +119,9 @@ impl UHashMap { B: BuildHasher, H: Hasher { // docs:end:contains_key - self.get(key).is_some() + unsafe { + self.get(key) + }.is_some() } // Returns true if the map contains no elements. @@ -438,7 +440,7 @@ where // Not marked as deleted and has key-value. if equal & slot.is_valid(){ let (key, value) = slot.key_value_unchecked(); - let other_value = other.get(key); + let other_value = unsafe { other.get(key) }; if other_value.is_none(){ equal = false; diff --git a/noir/noir-repo/noir_stdlib/src/default.nr b/noir/noir-repo/noir_stdlib/src/default.nr index f9399bfb865c..3ac5fbb394ef 100644 --- a/noir/noir-repo/noir_stdlib/src/default.nr +++ b/noir/noir-repo/noir_stdlib/src/default.nr @@ -17,11 +17,14 @@ comptime fn derive_default(s: StructDefinition) -> Quoted { impl Default for Field { fn default() -> Field { 0 } } +impl Default for u1 { fn default() -> u1 { 0 } } impl Default for u8 { fn default() -> u8 { 0 } } +impl Default for u16 { fn default() -> u16 { 0 } } impl Default for u32 { fn default() -> u32 { 0 } } impl Default for u64 { fn default() -> u64 { 0 } } impl Default for i8 { fn default() -> i8 { 0 } } +impl Default for i16 { fn default() -> i16 { 0 } } impl Default for i32 { fn default() -> i32 { 0 } } impl Default for i64 { fn default() -> i64 { 0 } } diff --git a/noir/noir-repo/noir_stdlib/src/field/bn254.nr b/noir/noir-repo/noir_stdlib/src/field/bn254.nr index e8db0a30c382..ed0053694c73 100644 --- a/noir/noir-repo/noir_stdlib/src/field/bn254.nr +++ b/noir/noir-repo/noir_stdlib/src/field/bn254.nr @@ -66,13 +66,15 @@ unconstrained fn lte_16_hint(x: Field, y: Field) -> bool { fn assert_gt_limbs(a: (Field, Field), b: (Field, Field)) { let (alo, ahi) = a; let (blo, bhi) = b; - let borrow = lte_16_hint(alo, blo); + unsafe { + let borrow = lte_16_hint(alo, blo); - let rlo = alo - blo - 1 + (borrow as Field) * TWO_POW_128; - let rhi = ahi - bhi - (borrow as Field); + let rlo = alo - blo - 1 + (borrow as Field) * TWO_POW_128; + let rhi = ahi - bhi - (borrow as Field); - rlo.assert_max_bit_size(128); - rhi.assert_max_bit_size(128); + rlo.assert_max_bit_size(128); + rhi.assert_max_bit_size(128); + } } /// Decompose a single field into two 16 byte fields. @@ -80,19 +82,21 @@ pub fn decompose(x: Field) -> (Field, Field) { if is_unconstrained() { compute_decomposition(x) } else { - // Take hints of the decomposition - let (xlo, xhi) = decompose_hint(x); + unsafe { + // Take hints of the decomposition + let (xlo, xhi) = decompose_hint(x); - // Range check the limbs - xlo.assert_max_bit_size(128); - xhi.assert_max_bit_size(128); + // Range check the limbs + xlo.assert_max_bit_size(128); + xhi.assert_max_bit_size(128); - // Check that the decomposition is correct - assert_eq(x, xlo + TWO_POW_128 * xhi); + // Check that the decomposition is correct + assert_eq(x, xlo + TWO_POW_128 * xhi); - // Assert that the decomposition of P is greater than the decomposition of x - assert_gt_limbs((PLO, PHI), (xlo, xhi)); - (xlo, xhi) + // Assert that the decomposition of P is greater than the decomposition of x + assert_gt_limbs((PLO, PHI), (xlo, xhi)); + (xlo, xhi) + } } } @@ -118,14 +122,16 @@ pub fn gt(a: Field, b: Field) -> bool { compute_lt(b, a, 32) } else if a == b { false - } else { + } else { // Take a hint of the comparison and verify it - if lt_32_hint(a, b) { - assert_gt(b, a); - false - } else { - assert_gt(a, b); - true + unsafe { + if lt_32_hint(a, b) { + assert_gt(b, a); + false + } else { + assert_gt(a, b); + true + } } } } diff --git a/noir/noir-repo/noir_stdlib/src/hash/mod.nr b/noir/noir-repo/noir_stdlib/src/hash/mod.nr index 88dbf83e5bd9..3abc630ab10d 100644 --- a/noir/noir-repo/noir_stdlib/src/hash/mod.nr +++ b/noir/noir-repo/noir_stdlib/src/hash/mod.nr @@ -108,7 +108,9 @@ fn __derive_generators( // does not assert the limbs are 128 bits // does not assert the decomposition does not overflow the EmbeddedCurveScalar fn from_field_unsafe(scalar: Field) -> EmbeddedCurveScalar { - let (xlo, xhi) = crate::field::bn254::decompose_hint(scalar); + let (xlo, xhi) = unsafe { + crate::field::bn254::decompose_hint(scalar) + }; // Check that the decomposition is correct assert_eq(scalar, xlo + crate::field::bn254::TWO_POW_128 * xhi); EmbeddedCurveScalar { lo: xlo, hi: xhi } @@ -193,12 +195,24 @@ impl Hash for Field { } } +impl Hash for u1 { + fn hash(self, state: &mut H) where H: Hasher{ + H::write(state, self as Field); + } +} + impl Hash for u8 { fn hash(self, state: &mut H) where H: Hasher{ H::write(state, self as Field); } } +impl Hash for u16 { + fn hash(self, state: &mut H) where H: Hasher{ + H::write(state, self as Field); + } +} + impl Hash for u32 { fn hash(self, state: &mut H) where H: Hasher{ H::write(state, self as Field); @@ -217,6 +231,12 @@ impl Hash for i8 { } } +impl Hash for i16 { + fn hash(self, state: &mut H) where H: Hasher{ + H::write(state, self as Field); + } +} + impl Hash for i32 { fn hash(self, state: &mut H) where H: Hasher{ H::write(state, self as Field); diff --git a/noir/noir-repo/noir_stdlib/src/lib.nr b/noir/noir-repo/noir_stdlib/src/lib.nr index 2d559c431629..5c4587b69aca 100644 --- a/noir/noir-repo/noir_stdlib/src/lib.nr +++ b/noir/noir-repo/noir_stdlib/src/lib.nr @@ -12,7 +12,6 @@ mod sha256; mod sha512; mod field; mod ec; -mod unsafe; mod collections; mod compat; mod convert; @@ -28,18 +27,27 @@ mod bigint; mod runtime; mod meta; mod append; +mod mem; // Oracle calls are required to be wrapped in an unconstrained function // Thus, the only argument to the `println` oracle is expected to always be an ident #[oracle(print)] unconstrained fn print_oracle(with_newline: bool, input: T) {} -unconstrained pub fn print(input: T) { - print_oracle(false, input); +unconstrained fn print_unconstrained(with_newline: bool, input: T) { + print_oracle(with_newline, input); } -unconstrained pub fn println(input: T) { - print_oracle(true, input); +pub fn println(input: T) { + unsafe { + print_unconstrained(true, input); + } +} + +pub fn print(input: T) { + unsafe { + print_unconstrained(false, input); + } } #[foreign(recursive_aggregation)] diff --git a/noir/noir-repo/noir_stdlib/src/unsafe.nr b/noir/noir-repo/noir_stdlib/src/mem.nr similarity index 99% rename from noir/noir-repo/noir_stdlib/src/unsafe.nr rename to noir/noir-repo/noir_stdlib/src/mem.nr index 542bd31fa84c..88d17e20ee3d 100644 --- a/noir/noir-repo/noir_stdlib/src/unsafe.nr +++ b/noir/noir-repo/noir_stdlib/src/mem.nr @@ -3,3 +3,4 @@ /// is no guarantee that all zeroes is a valid bit pattern for every type. #[builtin(zeroed)] pub fn zeroed() -> T {} + diff --git a/noir/noir-repo/noir_stdlib/src/meta/expr.nr b/noir/noir-repo/noir_stdlib/src/meta/expr.nr index 54681632543c..94889b7c3dd4 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/expr.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/expr.nr @@ -1,6 +1,228 @@ use crate::option::Option; +use crate::meta::op::UnaryOp; +use crate::meta::op::BinaryOp; impl Expr { + #[builtin(expr_as_array)] + fn as_array(self) -> Option<[Expr]> {} + + #[builtin(expr_as_integer)] + fn as_integer(self) -> Option<(Field, bool)> {} + + #[builtin(expr_as_binary_op)] + fn as_binary_op(self) -> Option<(Expr, BinaryOp, Expr)> {} + + #[builtin(expr_as_bool)] + fn as_bool(self) -> Option {} + #[builtin(expr_as_function_call)] fn as_function_call(self) -> Option<(Expr, [Expr])> {} + + #[builtin(expr_as_if)] + fn as_if(self) -> Option<(Expr, Expr, Option)> {} + + #[builtin(expr_as_index)] + fn as_index(self) -> Option<(Expr, Expr)> {} + + #[builtin(expr_as_member_access)] + fn as_member_access(self) -> Option<(Expr, Quoted)> {} + + #[builtin(expr_as_repeated_element_array)] + fn as_repeated_element_array(self) -> Option<(Expr, Expr)> {} + + #[builtin(expr_as_repeated_element_slice)] + fn as_repeated_element_slice(self) -> Option<(Expr, Expr)> {} + + #[builtin(expr_as_slice)] + fn as_slice(self) -> Option<[Expr]> {} + + #[builtin(expr_as_tuple)] + fn as_tuple(self) -> Option<[Expr]> {} + + #[builtin(expr_as_unary_op)] + fn as_unary_op(self) -> Option<(UnaryOp, Expr)> {} +} + +mod tests { + use crate::meta::op::UnaryOp; + use crate::meta::op::BinaryOp; + + #[test] + fn test_expr_as_array() { + comptime + { + let expr = quote { [1, 2, 4] }.as_expr().unwrap(); + let elems = expr.as_array().unwrap(); + assert_eq(elems.len(), 3); + assert_eq(elems[0].as_integer().unwrap(), (1, false)); + assert_eq(elems[1].as_integer().unwrap(), (2, false)); + assert_eq(elems[2].as_integer().unwrap(), (4, false)); + } + } + + #[test] + fn test_expr_as_integer() { + comptime + { + let expr = quote { 1 }.as_expr().unwrap(); + assert_eq((1, false), expr.as_integer().unwrap()); + + let expr = quote { -2 }.as_expr().unwrap(); + assert_eq((2, true), expr.as_integer().unwrap()); + } + } + + #[test] + fn test_expr_as_binary_op() { + comptime + { + assert(get_binary_op(quote { x + y }).is_add()); + assert(get_binary_op(quote { x - y }).is_subtract()); + assert(get_binary_op(quote { x * y }).is_multiply()); + assert(get_binary_op(quote { x / y }).is_divide()); + assert(get_binary_op(quote { x == y }).is_equal()); + assert(get_binary_op(quote { x != y }).is_not_equal()); + assert(get_binary_op(quote { x > y }).is_greater()); + assert(get_binary_op(quote { x >= y }).is_greater_or_equal()); + assert(get_binary_op(quote { x & y }).is_and()); + assert(get_binary_op(quote { x | y }).is_or()); + assert(get_binary_op(quote { x ^ y }).is_xor()); + assert(get_binary_op(quote { x >> y }).is_shift_right()); + assert(get_binary_op(quote { x << y }).is_shift_left()); + assert(get_binary_op(quote { x % y }).is_modulo()); + } + } + + #[test] + fn test_expr_as_bool() { + comptime + { + let expr = quote { false }.as_expr().unwrap(); + assert(expr.as_bool().unwrap() == false); + + let expr = quote { true }.as_expr().unwrap(); + assert_eq(expr.as_bool().unwrap(), true); + } + } + + #[test] + fn test_expr_as_function_call() { + comptime + { + let expr = quote { foo(42) }.as_expr().unwrap(); + let (_function, args) = expr.as_function_call().unwrap(); + assert_eq(args.len(), 1); + assert_eq(args[0].as_integer().unwrap(), (42, false)); + } + } + + #[test] + fn test_expr_as_if() { + comptime + { + let expr = quote { if 1 { 2 } }.as_expr().unwrap(); + let (_condition, _consequence, alternative) = expr.as_if().unwrap(); + assert(alternative.is_none()); + + let expr = quote { if 1 { 2 } else { 3 } }.as_expr().unwrap(); + let (_condition, _consequence, alternative) = expr.as_if().unwrap(); + assert(alternative.is_some()); + } + } + + #[test] + fn test_expr_as_index() { + comptime + { + let expr = quote { foo[bar] }.as_expr().unwrap(); + assert(expr.as_index().is_some()); + } + } + + #[test] + fn test_expr_as_member_access() { + comptime + { + let expr = quote { foo.bar }.as_expr().unwrap(); + let (_, name) = expr.as_member_access().unwrap(); + assert_eq(name, quote { bar }); + } + } + + #[test] + fn test_expr_as_repeated_element_array() { + comptime + { + let expr = quote { [1; 3] }.as_expr().unwrap(); + let (expr, length) = expr.as_repeated_element_array().unwrap(); + assert_eq(expr.as_integer().unwrap(), (1, false)); + assert_eq(length.as_integer().unwrap(), (3, false)); + } + } + + #[test] + fn test_expr_as_repeated_element_slice() { + comptime + { + let expr = quote { &[1; 3] }.as_expr().unwrap(); + let (expr, length) = expr.as_repeated_element_slice().unwrap(); + assert_eq(expr.as_integer().unwrap(), (1, false)); + assert_eq(length.as_integer().unwrap(), (3, false)); + } + } + + #[test] + fn test_expr_as_slice() { + comptime + { + let expr = quote { &[1, 3, 5] }.as_expr().unwrap(); + let elems = expr.as_slice().unwrap(); + assert_eq(elems.len(), 3); + assert_eq(elems[0].as_integer().unwrap(), (1, false)); + assert_eq(elems[1].as_integer().unwrap(), (3, false)); + assert_eq(elems[2].as_integer().unwrap(), (5, false)); + } + } + + #[test] + fn test_expr_as_tuple() { + comptime + { + let expr = quote { (1, 2) }.as_expr().unwrap(); + let tuple_exprs = expr.as_tuple().unwrap(); + assert_eq(tuple_exprs.len(), 2); + } + } + + #[test] + fn test_expr_as_unary_op() { + comptime + { + assert(get_unary_op(quote { -x }).is_minus()); + assert(get_unary_op(quote { !x }).is_not()); + assert(get_unary_op(quote { &mut x }).is_mutable_reference()); + assert(get_unary_op(quote { *x }).is_dereference()); + } + } + + #[test] + fn test_automatically_unwraps_parenthesized_expression() { + comptime + { + let expr = quote { ((if 1 { 2 })) }.as_expr().unwrap(); + assert(expr.as_if().is_some()); + } + } + + comptime fn get_unary_op(quoted: Quoted) -> UnaryOp { + let expr = quoted.as_expr().unwrap(); + let (op, _) = expr.as_unary_op().unwrap(); + op + } + + comptime fn get_binary_op(quoted: Quoted) -> BinaryOp { + let expr = quoted.as_expr().unwrap(); + let (_, op, _) = expr.as_binary_op().unwrap(); + op + } } diff --git a/noir/noir-repo/noir_stdlib/src/meta/mod.nr b/noir/noir-repo/noir_stdlib/src/meta/mod.nr index 2763685fd0d7..d16a8648bc2e 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/mod.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/mod.nr @@ -5,9 +5,11 @@ use crate::hash::poseidon2::Poseidon2Hasher; mod expr; mod function_def; mod module; +mod op; mod struct_def; mod trait_constraint; mod trait_def; +mod trait_impl; mod typ; mod quoted; @@ -31,7 +33,9 @@ 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); + let handler = unsafe { + HANDLERS.get(trait_to_derive) + }; assert(handler.is_some(), f"No derive function registered for `{trait_to_derive}`"); let trait_impl = handler.unwrap()(s); @@ -41,7 +45,7 @@ pub comptime fn derive(s: StructDefinition, traits: [TraitDefinition]) -> Quoted result } -unconstrained pub comptime fn derive_via(t: TraitDefinition, f: DeriveFunction) { +pub comptime fn derive_via(t: TraitDefinition, f: DeriveFunction) { HANDLERS.insert(t, f); } diff --git a/noir/noir-repo/noir_stdlib/src/meta/op.nr b/noir/noir-repo/noir_stdlib/src/meta/op.nr new file mode 100644 index 000000000000..ebd89677c504 --- /dev/null +++ b/noir/noir-repo/noir_stdlib/src/meta/op.nr @@ -0,0 +1,92 @@ +struct UnaryOp { + op: Field +} + +impl UnaryOp { + fn is_minus(self) -> bool { + self.op == 0 + } + + fn is_not(self) -> bool { + self.op == 1 + } + + fn is_mutable_reference(self) -> bool { + self.op == 2 + } + + fn is_dereference(self) -> bool { + self.op == 3 + } +} + +struct BinaryOp { + op: Field +} + +impl BinaryOp { + fn is_add(self) -> bool { + self.op == 0 + } + + fn is_subtract(self) -> bool { + self.op == 1 + } + + fn is_multiply(self) -> bool { + self.op == 2 + } + + fn is_divide(self) -> bool { + self.op == 3 + } + + fn is_equal(self) -> bool { + self.op == 4 + } + + fn is_not_equal(self) -> bool { + self.op == 5 + } + + fn is_less(self) -> bool { + self.op == 6 + } + + fn is_less_equal(self) -> bool { + self.op == 7 + } + + fn is_greater(self) -> bool { + self.op == 8 + } + + fn is_greater_or_equal(self) -> bool { + self.op == 9 + } + + fn is_and(self) -> bool { + self.op == 10 + } + + fn is_or(self) -> bool { + self.op == 11 + } + + fn is_xor(self) -> bool { + self.op == 12 + } + + fn is_shift_right(self) -> bool { + self.op == 13 + } + + fn is_shift_left(self) -> bool { + self.op == 14 + } + + fn is_modulo(self) -> bool { + self.op == 15 + } +} + diff --git a/noir/noir-repo/noir_stdlib/src/meta/trait_impl.nr b/noir/noir-repo/noir_stdlib/src/meta/trait_impl.nr new file mode 100644 index 000000000000..2f82ee5f4345 --- /dev/null +++ b/noir/noir-repo/noir_stdlib/src/meta/trait_impl.nr @@ -0,0 +1,7 @@ +impl TraitImpl { + #[builtin(trait_impl_trait_generic_args)] + fn trait_generic_args(self) -> [Type] {} + + #[builtin(trait_impl_methods)] + fn methods(self) -> [FunctionDefinition] {} +} diff --git a/noir/noir-repo/noir_stdlib/src/meta/typ.nr b/noir/noir-repo/noir_stdlib/src/meta/typ.nr index ad669e93c0a9..67ad2a967398 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/typ.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/typ.nr @@ -20,6 +20,9 @@ impl Type { #[builtin(type_as_tuple)] fn as_tuple(self) -> Option<[Type]> {} + #[builtin(type_get_trait_impl)] + fn get_trait_impl(self, constraint: TraitConstraint) -> Option {} + #[builtin(type_implements)] fn implements(self, constraint: TraitConstraint) -> bool {} diff --git a/noir/noir-repo/noir_stdlib/src/ops/arith.nr b/noir/noir-repo/noir_stdlib/src/ops/arith.nr index df0ff978a7cf..918c8e9bc283 100644 --- a/noir/noir-repo/noir_stdlib/src/ops/arith.nr +++ b/noir/noir-repo/noir_stdlib/src/ops/arith.nr @@ -10,6 +10,7 @@ impl Add for u64 { fn add(self, other: u64) -> u64 { self + other } } impl Add for u32 { fn add(self, other: u32) -> u32 { self + other } } impl Add for u16 { fn add(self, other: u16) -> u16 { self + other } } impl Add for u8 { fn add(self, other: u8) -> u8 { self + other } } +impl Add for u1 { fn add(self, other: u1) -> u1 { self + other } } impl Add for i8 { fn add(self, other: i8) -> i8 { self + other } } impl Add for i16 { fn add(self, other: i16) -> i16 { self + other } } @@ -28,6 +29,7 @@ impl Sub for u64 { fn sub(self, other: u64) -> u64 { self - other } } impl Sub for u32 { fn sub(self, other: u32) -> u32 { self - other } } impl Sub for u16 { fn sub(self, other: u16) -> u16 { self - other } } impl Sub for u8 { fn sub(self, other: u8) -> u8 { self - other } } +impl Sub for u1 { fn sub(self, other: u1) -> u1 { self - other } } impl Sub for i8 { fn sub(self, other: i8) -> i8 { self - other } } impl Sub for i16 { fn sub(self, other: i16) -> i16 { self - other } } @@ -46,6 +48,7 @@ impl Mul for u64 { fn mul(self, other: u64) -> u64 { self * other } } impl Mul for u32 { fn mul(self, other: u32) -> u32 { self * other } } impl Mul for u16 { fn mul(self, other: u16) -> u16 { self * other } } impl Mul for u8 { fn mul(self, other: u8) -> u8 { self * other } } +impl Mul for u1 { fn mul(self, other: u1) -> u1 { self * other } } impl Mul for i8 { fn mul(self, other: i8) -> i8 { self * other } } impl Mul for i16 { fn mul(self, other: i16) -> i16 { self * other } } @@ -64,6 +67,7 @@ impl Div for u64 { fn div(self, other: u64) -> u64 { self / other } } impl Div for u32 { fn div(self, other: u32) -> u32 { self / other } } impl Div for u16 { fn div(self, other: u16) -> u16 { self / other } } impl Div for u8 { fn div(self, other: u8) -> u8 { self / other } } +impl Div for u1 { fn div(self, other: u1) -> u1 { self / other } } impl Div for i8 { fn div(self, other: i8) -> i8 { self / other } } impl Div for i16 { fn div(self, other: i16) -> i16 { self / other } } @@ -80,6 +84,7 @@ impl Rem for u64 { fn rem(self, other: u64) -> u64 { self % other } } impl Rem for u32 { fn rem(self, other: u32) -> u32 { self % other } } impl Rem for u16 { fn rem(self, other: u16) -> u16 { self % other } } impl Rem for u8 { fn rem(self, other: u8) -> u8 { self % other } } +impl Rem for u1 { fn rem(self, other: u1) -> u1 { 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 } } diff --git a/noir/noir-repo/noir_stdlib/src/ops/bit.nr b/noir/noir-repo/noir_stdlib/src/ops/bit.nr index a31cfee878cc..015d0008e7a8 100644 --- a/noir/noir-repo/noir_stdlib/src/ops/bit.nr +++ b/noir/noir-repo/noir_stdlib/src/ops/bit.nr @@ -31,6 +31,7 @@ impl BitOr for u64 { fn bitor(self, other: u64) -> u64 { self | other } } impl BitOr for u32 { fn bitor(self, other: u32) -> u32 { self | other } } impl BitOr for u16 { fn bitor(self, other: u16) -> u16 { self | other } } impl BitOr for u8 { fn bitor(self, other: u8) -> u8 { self | other } } +impl BitOr for u1 { fn bitor(self, other: u1) -> u1 { 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 } } @@ -49,6 +50,7 @@ impl BitAnd for u64 { fn bitand(self, other: u64) -> u64 { self & other } } impl BitAnd for u32 { fn bitand(self, other: u32) -> u32 { self & other } } impl BitAnd for u16 { fn bitand(self, other: u16) -> u16 { self & other } } impl BitAnd for u8 { fn bitand(self, other: u8) -> u8 { self & other } } +impl BitAnd for u1 { fn bitand(self, other: u1) -> u1 { self & other } } impl BitAnd for i8 { fn bitand(self, other: i8) -> i8 { self & other } } impl BitAnd for i16 { fn bitand(self, other: i16) -> i16 { self & other } } @@ -67,6 +69,7 @@ impl BitXor for u64 { fn bitxor(self, other: u64) -> u64 { self ^ other } } impl BitXor for u32 { fn bitxor(self, other: u32) -> u32 { self ^ other } } impl BitXor for u16 { fn bitxor(self, other: u16) -> u16 { self ^ other } } impl BitXor for u8 { fn bitxor(self, other: u8) -> u8 { self ^ other } } +impl BitXor for u1 { fn bitxor(self, other: u1) -> u1 { self ^ other } } impl BitXor for i8 { fn bitxor(self, other: i8) -> i8 { self ^ other } } impl BitXor for i16 { fn bitxor(self, other: i16) -> i16 { self ^ other } } diff --git a/noir/noir-repo/noir_stdlib/src/option.nr b/noir/noir-repo/noir_stdlib/src/option.nr index df020e756150..8d6d9ef970d6 100644 --- a/noir/noir-repo/noir_stdlib/src/option.nr +++ b/noir/noir-repo/noir_stdlib/src/option.nr @@ -10,7 +10,7 @@ struct Option { impl Option { /// Constructs a None value pub fn none() -> Self { - Self { _is_some: false, _value: crate::unsafe::zeroed() } + Self { _is_some: false, _value: crate::mem::zeroed() } } /// Constructs a Some wrapper around the given value diff --git a/noir/noir-repo/noir_stdlib/src/slice.nr b/noir/noir-repo/noir_stdlib/src/slice.nr index 8d3f395f0807..f9aa98a9ecd3 100644 --- a/noir/noir-repo/noir_stdlib/src/slice.nr +++ b/noir/noir-repo/noir_stdlib/src/slice.nr @@ -49,7 +49,7 @@ impl [T] { pub fn as_array(self) -> [T; N] { assert(self.len() == N); - let mut array = [crate::unsafe::zeroed(); N]; + let mut array = [crate::mem::zeroed(); N]; for i in 0..N { array[i] = self[i]; } diff --git a/noir/noir-repo/noir_stdlib/src/uint128.nr b/noir/noir-repo/noir_stdlib/src/uint128.nr index 7b75cf4cae4f..a6d980c7f33b 100644 --- a/noir/noir-repo/noir_stdlib/src/uint128.nr +++ b/noir/noir-repo/noir_stdlib/src/uint128.nr @@ -103,7 +103,9 @@ impl U128 { (if ascii < 58 { ascii - 48 } else { - let ascii = ascii + 32 * (U128::uconstrained_check_is_upper_ascii(ascii) as u8); + let ascii = ascii + 32 * (unsafe { + U128::uconstrained_check_is_upper_ascii(ascii) as u8 + }); assert(ascii >= 97); // enforce >= 'a' assert(ascii <= 102); // enforce <= 'f' ascii - 87 @@ -212,21 +214,26 @@ impl Mul for U128 { impl Div for U128 { fn div(self: Self, b: U128) -> U128 { - let (q,r) = self.unconstrained_div(b); - let a = b * q + r; - assert_eq(self, a); - assert(r < b); - q + unsafe { + let (q,r) = self.unconstrained_div(b); + let a = b * q + r; + assert_eq(self, a); + assert(r < b); + q + } } } impl Rem for U128 { fn rem(self: Self, b: U128) -> U128 { - let (q,r) = self.unconstrained_div(b); - let a = b * q + r; - assert_eq(self, a); - assert(r < b); - r + unsafe { + let (q,r) = self.unconstrained_div(b); + let a = b * q + r; + assert_eq(self, a); + assert(r < b); + + r + } } } @@ -439,31 +446,40 @@ mod tests { let b= U128::from_u64s_le(0x0, 0xfffffffffffffffe); let c= U128::one(); let d= U128::from_u64s_le(0x0, 0x1); - let (q,r) = a.unconstrained_div(b); - assert_eq(q, c); - assert_eq(r, d); + unsafe { + let (q,r) = a.unconstrained_div(b); + assert_eq(q, c); + assert_eq(r, d); + } let a = U128::from_u64s_le(2, 0); let b = U128::one(); // Check the case where a is a multiple of b - let (c,d ) = a.unconstrained_div(b); - assert_eq((c, d), (a, U128::zero())); + unsafe { + let (c, d) = a.unconstrained_div(b); + assert_eq((c, d), (a, U128::zero())); + } // Check where b is a multiple of a - let (c,d) = b.unconstrained_div(a); - assert_eq((c, d), (U128::zero(), b)); + unsafe { + let (c,d) = b.unconstrained_div(a); + assert_eq((c, d), (U128::zero(), b)); + } // Dividing by zero returns 0,0 let a = U128::from_u64s_le(0x1, 0x0); let b = U128::zero(); - let (c,d)= a.unconstrained_div(b); - assert_eq((c, d), (U128::zero(), U128::zero())); - + unsafe { + let (c, d)= a.unconstrained_div(b); + assert_eq((c, d), (U128::zero(), U128::zero())); + } // Dividing 1<<127 by 1<<127 (special case) let a = U128::from_u64s_le(0x0, pow63 as u64); let b = U128::from_u64s_le(0x0, pow63 as u64); - let (c,d )= a.unconstrained_div(b); - assert_eq((c, d), (U128::one(), U128::zero())); + unsafe { + let (c, d) = a.unconstrained_div(b); + assert_eq((c, d), (U128::one(), U128::zero())); + } } #[test] diff --git a/noir/noir-repo/scripts/install_bb.sh b/noir/noir-repo/scripts/install_bb.sh index 65a449be5433..91b9180f138b 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.47.1" +VERSION="0.48.0" BBUP_PATH=~/.bb/bbup diff --git a/noir/noir-repo/test_programs/compile_failure/array_length_defaulting/src/main.nr b/noir/noir-repo/test_programs/compile_failure/array_length_defaulting/src/main.nr index 40543db28705..16902860a16f 100644 --- a/noir/noir-repo/test_programs/compile_failure/array_length_defaulting/src/main.nr +++ b/noir/noir-repo/test_programs/compile_failure/array_length_defaulting/src/main.nr @@ -1,5 +1,5 @@ fn main() { - let x = std::unsafe::zeroed(); + let x = std::mem::zeroed(); foo(x); } diff --git a/noir/noir-repo/test_programs/compile_failure/brillig_mut_ref_from_acir/src/main.nr b/noir/noir-repo/test_programs/compile_failure/brillig_mut_ref_from_acir/src/main.nr index 473ad8e8d6af..f262635e5089 100644 --- a/noir/noir-repo/test_programs/compile_failure/brillig_mut_ref_from_acir/src/main.nr +++ b/noir/noir-repo/test_programs/compile_failure/brillig_mut_ref_from_acir/src/main.nr @@ -3,6 +3,8 @@ unconstrained fn mut_ref_identity(value: &mut Field) -> Field { } fn main(mut x: Field, y: pub Field) { - let returned_x = mut_ref_identity(&mut x); + let returned_x = unsafe { + mut_ref_identity(&mut x) + }; assert(returned_x == x); } diff --git a/noir/noir-repo/test_programs/compile_failure/regression_5008/src/main.nr b/noir/noir-repo/test_programs/compile_failure/regression_5008/src/main.nr index 6d9645ee6eb5..cf79c22683f8 100644 --- a/noir/noir-repo/test_programs/compile_failure/regression_5008/src/main.nr +++ b/noir/noir-repo/test_programs/compile_failure/regression_5008/src/main.nr @@ -13,5 +13,7 @@ impl Foo { fn main() { let foo = Foo { bar: &mut Bar { value: 0 } }; - foo.crash_fn(); + unsafe { + foo.crash_fn() + }; } diff --git a/noir/noir-repo/test_programs/compile_failure/turbofish_generic_count/src/main.nr b/noir/noir-repo/test_programs/compile_failure/turbofish_generic_count/src/main.nr index 4091b2f05810..5dc05a3a5c34 100644 --- a/noir/noir-repo/test_programs/compile_failure/turbofish_generic_count/src/main.nr +++ b/noir/noir-repo/test_programs/compile_failure/turbofish_generic_count/src/main.nr @@ -6,7 +6,7 @@ struct Bar { impl Bar { fn zeroed(_self: Self) -> A { - std::unsafe::zeroed() + std::mem::zeroed() } } diff --git a/noir/noir-repo/test_programs/compile_failure/unconstrained_ref/src/main.nr b/noir/noir-repo/test_programs/compile_failure/unconstrained_ref/src/main.nr index 1491badaa49c..9a4f42469b52 100644 --- a/noir/noir-repo/test_programs/compile_failure/unconstrained_ref/src/main.nr +++ b/noir/noir-repo/test_programs/compile_failure/unconstrained_ref/src/main.nr @@ -4,5 +4,7 @@ unconstrained fn uncon_ref() -> &mut Field { } fn main() { - let e = uncon_ref(); + let e = unsafe { + uncon_ref() + }; } 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 index d4f71d38413f..6cd13ab0e2f1 100644 --- 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 @@ -11,7 +11,7 @@ fn main() { 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(); + let mut new_array: [T; N - 1] = std::mem::zeroed(); for i in 0..N - 1 { new_array[i] = array[i + 1]; @@ -21,7 +21,7 @@ fn split_first(array: [T; N]) -> (T, [T; N - 1]) { } fn push(array: [Field; N], element: Field) -> [Field; N + 1] { - let mut result: [_; N + 1] = std::unsafe::zeroed(); + let mut result: [_; N + 1] = std::mem::zeroed(); result[array.len()] = element; for i in 0..array.len() { diff --git a/noir/noir-repo/test_programs/compile_success_empty/brillig_cast/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/brillig_cast/src/main.nr index ecb832468ba5..f2bede99d4ca 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/brillig_cast/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/brillig_cast/src/main.nr @@ -2,11 +2,13 @@ // // The features being tested are cast operations on brillig fn main() { - bool_casts(); - field_casts(); - uint_casts(); - int_casts(); - mixed_casts(); + unsafe { + bool_casts(); + field_casts(); + uint_casts(); + int_casts(); + mixed_casts(); + } } unconstrained fn bool_casts() { diff --git a/noir/noir-repo/test_programs/compile_success_empty/brillig_field_binary_operations/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/brillig_field_binary_operations/src/main.nr index 54f068588460..fedfe48cb0dc 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/brillig_field_binary_operations/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/brillig_field_binary_operations/src/main.nr @@ -1,11 +1,13 @@ // Tests arithmetic operations on fields fn main() { - let x = 4; - let y = 2; - assert((x + y) == add(x, y)); - assert((x - y) == sub(x, y)); - assert((x * y) == mul(x, y)); - assert((x / y) == div(x, y)); + unsafe { + let x = 4; + let y = 2; + assert((x + y) == add(x, y)); + assert((x - y) == sub(x, y)); + assert((x * y) == mul(x, y)); + assert((x / y) == div(x, y)); + } } unconstrained fn add(x: Field, y: Field) -> Field { diff --git a/noir/noir-repo/test_programs/compile_success_empty/brillig_integer_binary_operations/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/brillig_integer_binary_operations/src/main.nr index 7d3f9459598e..7ecc21dbd2f1 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/brillig_integer_binary_operations/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/brillig_integer_binary_operations/src/main.nr @@ -3,28 +3,30 @@ fn main() { let x: u32 = 6; let y: u32 = 2; - assert((x + y) == add(x, y)); + unsafe { + assert((x + y) == add(x, y)); - assert((x - y) == sub(x, y)); + assert((x - y) == sub(x, y)); - assert((x * y) == mul(x, y)); + assert((x * y) == mul(x, y)); - assert((x / y) == div(x, y)); - // TODO SSA => ACIR has some issues with i32 ops - assert(check_signed_div(6, 2, 3)); + assert((x / y) == div(x, y)); + // TODO SSA => ACIR has some issues with i32 ops + assert(check_signed_div(6, 2, 3)); - assert(eq(1, 2) == false); - assert(eq(1, 1)); + assert(eq(1, 2) == false); + assert(eq(1, 1)); - assert(lt(x, y) == false); - assert(lt(y, x)); + assert(lt(x, y) == false); + assert(lt(y, x)); - assert((x & y) == and(x, y)); - assert((x | y) == or(x, y)); - // TODO SSA => ACIR has some issues with xor ops - assert(check_xor(x, y, 4)); - assert((x >> y as u8) == shr(x, y as u8)); - assert((x << y as u8) == shl(x, y as u8)); + assert((x & y) == and(x, y)); + assert((x | y) == or(x, y)); + // TODO SSA => ACIR has some issues with xor ops + assert(check_xor(x, y, 4)); + assert((x >> y as u8) == shr(x, y as u8)); + assert((x << y as u8) == shl(x, y as u8)); + } } unconstrained fn add(x: u32, y: u32) -> u32 { diff --git a/noir/noir-repo/test_programs/compile_success_empty/brillig_modulo/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/brillig_modulo/src/main.nr index 195ed31fb08d..dff5eadcb552 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/brillig_modulo/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/brillig_modulo/src/main.nr @@ -2,20 +2,22 @@ // // The features being tested is modulo operations on brillig fn main() { - assert(modulo(47, 3) == 2); - assert(modulo(2, 3) == 2); - assert(signed_modulo(5, 3) == 2); - assert(signed_modulo(2, 3) == 2); + unsafe { + assert(modulo(47, 3) == 2); + assert(modulo(2, 3) == 2); + assert(signed_modulo(5, 3) == 2); + assert(signed_modulo(2, 3) == 2); - let minus_two: i8 = -2; // 254 - let minus_three: i8 = -3; // 253 - let minus_five: i8 = -5; // 251 - // (5 / -3) * -3 + 2 = -1 * -3 + 2 = 3 + 2 = 5 - assert(signed_modulo(5, minus_three) == 2); - // (-5 / 3) * 3 - 2 = -1 * 3 - 2 = -3 - 2 = -5 - assert(signed_modulo(minus_five, 3) == minus_two); - // (-5 / -3) * -3 - 2 = 1 * -3 - 2 = -3 - 2 = -5 - assert(signed_modulo(minus_five, minus_three) == minus_two); + let minus_two: i8 = -2; // 254 + let minus_three: i8 = -3; // 253 + let minus_five: i8 = -5; // 251 + // (5 / -3) * -3 + 2 = -1 * -3 + 2 = 3 + 2 = 5 + assert(signed_modulo(5, minus_three) == 2); + // (-5 / 3) * 3 - 2 = -1 * 3 - 2 = -3 - 2 = -5 + assert(signed_modulo(minus_five, 3) == minus_two); + // (-5 / -3) * -3 - 2 = 1 * -3 - 2 = -3 - 2 = -5 + assert(signed_modulo(minus_five, minus_three) == minus_two); + } } unconstrained fn modulo(x: u32, y: u32) -> u32 { diff --git a/noir/noir-repo/test_programs/compile_success_empty/brillig_slice_input/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/brillig_slice_input/src/main.nr index 8403cb7d4a06..596f364b49f5 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/brillig_slice_input/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/brillig_slice_input/src/main.nr @@ -25,8 +25,10 @@ fn main() { y: 8, } ]); - let brillig_sum = sum_slice(slice); - assert_eq(brillig_sum, 55); + unsafe { + let brillig_sum = sum_slice(slice); + assert_eq(brillig_sum, 55); + } slice = slice.push_back([ Point { @@ -38,6 +40,8 @@ fn main() { y: 13, } ]); - let brillig_sum = sum_slice(slice); - assert_eq(brillig_sum, 100); + unsafe { + let brillig_sum = sum_slice(slice); + assert_eq(brillig_sum, 100); + } } 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 deleted file mode 100644 index 8b6f7b480c7c..000000000000 --- a/noir/noir-repo/test_programs/compile_success_empty/comptime_exp/src/main.nr +++ /dev/null @@ -1,8 +0,0 @@ -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/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_fmt_strings/src/main.nr index 19572fd15a14..705a1b2ab4ec 100644 --- 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 @@ -7,7 +7,7 @@ fn main() { // 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::>(); + let s2 = std::mem::zeroed::>(); (s1, s2) }; assert_eq(s1, "x is 4, fake interpolation: {y}, y is 5"); 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_trait_impl/Nargo.toml similarity index 74% rename from noir/noir-repo/test_programs/compile_success_empty/comptime_exp/Nargo.toml rename to noir/noir-repo/test_programs/compile_success_empty/comptime_trait_impl/Nargo.toml index df36e0e05b0c..c9a6f1e150e2 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/comptime_exp/Nargo.toml +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_trait_impl/Nargo.toml @@ -1,5 +1,5 @@ [package] -name = "comptime_exp" +name = "comptime_trait_impl" type = "bin" authors = [""] compiler_version = ">=0.31.0" diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_trait_impl/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_trait_impl/src/main.nr new file mode 100644 index 000000000000..87b48e7a357e --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_trait_impl/src/main.nr @@ -0,0 +1,32 @@ +use std::meta::type_of; + +trait SomeTrait { + fn foo(); +} +struct SomeStruct { + +} + +impl SomeTrait for SomeStruct { + fn foo() {} +} + +fn main() { + comptime + { + let some_struct = quote { SomeStruct }.as_type(); + let some_trait = quote { SomeTrait }.as_trait_constraint(); + let trait_impl = some_struct.get_trait_impl(some_trait).unwrap(); + + // Check TraitImpl::trait_generic_args + let trait_generic_args = trait_impl.trait_generic_args(); + assert_eq(trait_generic_args.len(), 2); + assert_eq(trait_generic_args[0], quote { i32 }.as_type()); + assert_eq(trait_generic_args[1], quote { i64 }.as_type()); + + // Check TraitImpl::methods + let methods = trait_impl.methods(); + assert_eq(methods.len(), 1); + assert_eq(methods[0].name(), quote { foo }); + } +} diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_type/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_type/src/main.nr index 170292b0e375..f0b53a392ed0 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/comptime_type/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_type/src/main.nr @@ -108,6 +108,8 @@ fn main() { 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)); + + let _trait_impl = struct_implements_some_trait.get_trait_impl(some_trait_i32).unwrap(); } } @@ -117,5 +119,7 @@ fn function_with_where(_x: T) where T: SomeTrait { let t = quote { T }.as_type(); let some_trait_i32 = quote { SomeTrait }.as_trait_constraint(); assert(t.implements(some_trait_i32)); + + assert(t.get_trait_impl(some_trait_i32).is_none()); } } diff --git a/noir/noir-repo/test_programs/compile_success_empty/macros_in_comptime/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/macros_in_comptime/src/main.nr index 52567025e23e..c5cc78801124 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/macros_in_comptime/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/macros_in_comptime/src/main.nr @@ -4,14 +4,16 @@ use std::meta::unquote; fn main() { comptime { - foo::<3>(5); + unsafe { + foo::<3>(5) + }; submodule::bar(); } } // Call a different function from the interpreter, then have the // elaborator switch to the middle of foo from its previous scope in main -unconstrained comptime fn foo(x: Field) { +comptime fn foo(x: Field) { assert(modulus_num_bits() != 0); let cond = quote { modulus_num_bits() != 0 }; 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 index 44ccb2bd5959..b35afdbc6c68 100644 --- 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 @@ -1,3 +1,3 @@ fn main() { - let _: [u8] = std::unsafe::zeroed(); + let _: [u8] = std::mem::zeroed(); } diff --git a/noir/noir-repo/test_programs/compile_success_no_bug/check_uncostrained_regression/src/main.nr b/noir/noir-repo/test_programs/compile_success_no_bug/check_uncostrained_regression/src/main.nr index e93e068f4327..33b84c2b702f 100644 --- a/noir/noir-repo/test_programs/compile_success_no_bug/check_uncostrained_regression/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_no_bug/check_uncostrained_regression/src/main.nr @@ -15,7 +15,9 @@ unconstrained fn convert(trigger: Trigger) -> ResultType { } impl Trigger { fn execute(self) -> ResultType { - let result = convert(self); + let result = unsafe { + convert(self) + }; assert(result.a == self.x + 1); assert(result.b == self.y - 1 + self.z[2]); assert(result.c[1] == 0); diff --git a/noir/noir-repo/test_programs/execution_failure/brillig_assert_fail/src/main.nr b/noir/noir-repo/test_programs/execution_failure/brillig_assert_fail/src/main.nr index da9d4ec1ac89..07256f0c398c 100644 --- a/noir/noir-repo/test_programs/execution_failure/brillig_assert_fail/src/main.nr +++ b/noir/noir-repo/test_programs/execution_failure/brillig_assert_fail/src/main.nr @@ -2,7 +2,11 @@ // // The features being tested is using assert on brillig fn main(x: Field) { - assert(1 == conditional(x as bool)); + assert( + 1 == unsafe { + conditional(x as bool) + } + ); } unconstrained fn conditional(x: bool) -> Field { diff --git a/noir/noir-repo/test_programs/execution_failure/brillig_assert_msg_runtime/src/main.nr b/noir/noir-repo/test_programs/execution_failure/brillig_assert_msg_runtime/src/main.nr index bd77551e3041..4dd06ceb7430 100644 --- a/noir/noir-repo/test_programs/execution_failure/brillig_assert_msg_runtime/src/main.nr +++ b/noir/noir-repo/test_programs/execution_failure/brillig_assert_msg_runtime/src/main.nr @@ -1,5 +1,9 @@ fn main(x: Field) { - assert(1 == conditional(x)); + assert( + 1 == unsafe { + conditional(x) + } + ); } unconstrained fn conditional(x: Field) -> Field { diff --git a/noir/noir-repo/test_programs/execution_failure/fold_nested_brillig_assert_fail/src/main.nr b/noir/noir-repo/test_programs/execution_failure/fold_nested_brillig_assert_fail/src/main.nr index 0a5038c179bb..21fd4006c2a6 100644 --- a/noir/noir-repo/test_programs/execution_failure/fold_nested_brillig_assert_fail/src/main.nr +++ b/noir/noir-repo/test_programs/execution_failure/fold_nested_brillig_assert_fail/src/main.nr @@ -13,7 +13,9 @@ fn fold_conditional_wrapper(x: bool) -> Field { #[fold] fn fold_conditional(x: bool) -> Field { - conditional_wrapper(x) + unsafe { + conditional_wrapper(x) + } } unconstrained fn conditional_wrapper(x: bool) -> Field { diff --git a/noir/noir-repo/test_programs/execution_failure/regression_5202/src/main.nr b/noir/noir-repo/test_programs/execution_failure/regression_5202/src/main.nr index 45ffafca4c75..fbcdba433554 100644 --- a/noir/noir-repo/test_programs/execution_failure/regression_5202/src/main.nr +++ b/noir/noir-repo/test_programs/execution_failure/regression_5202/src/main.nr @@ -13,11 +13,15 @@ unconstrained fn should_i_assert() -> bool { } fn get_magical_boolean() -> bool { - let option = get_unconstrained_option(); + let option = unsafe { + get_unconstrained_option() + }; let pre_assert = option.is_some().to_field(); - if should_i_assert() { + if unsafe { + should_i_assert() + } { // Note that `should_i_assert` is unconstrained, so Noir should not be able to infer // any behavior from the contents of this block. In this case it is actually false, so the // assertion below is not even executed (if it did it'd fail since the values are not equal). diff --git a/noir/noir-repo/test_programs/execution_failure/unused_array_get_known_index_out_of_bounds/Nargo.toml b/noir/noir-repo/test_programs/execution_failure/unused_array_get_known_index_out_of_bounds/Nargo.toml new file mode 100644 index 000000000000..4123215e2b6c --- /dev/null +++ b/noir/noir-repo/test_programs/execution_failure/unused_array_get_known_index_out_of_bounds/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "unused_array_get_known_index_out_of_bounds" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_failure/unused_array_get_known_index_out_of_bounds/Prover.toml b/noir/noir-repo/test_programs/execution_failure/unused_array_get_known_index_out_of_bounds/Prover.toml new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/noir/noir-repo/test_programs/execution_failure/unused_array_get_known_index_out_of_bounds/src/main.nr b/noir/noir-repo/test_programs/execution_failure/unused_array_get_known_index_out_of_bounds/src/main.nr new file mode 100644 index 000000000000..bdc645ec483d --- /dev/null +++ b/noir/noir-repo/test_programs/execution_failure/unused_array_get_known_index_out_of_bounds/src/main.nr @@ -0,0 +1,4 @@ +fn main() { + let array = [1, 2, 3]; + let _ = array[10]; // Index out of bounds +} diff --git a/noir/noir-repo/test_programs/execution_failure/unused_array_get_unknown_index_out_of_bounds/Nargo.toml b/noir/noir-repo/test_programs/execution_failure/unused_array_get_unknown_index_out_of_bounds/Nargo.toml new file mode 100644 index 000000000000..04d9146b8812 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_failure/unused_array_get_unknown_index_out_of_bounds/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "unused_array_get_unknown_index_out_of_bounds" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_failure/unused_array_get_unknown_index_out_of_bounds/Prover.toml b/noir/noir-repo/test_programs/execution_failure/unused_array_get_unknown_index_out_of_bounds/Prover.toml new file mode 100644 index 000000000000..1ec81884d618 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_failure/unused_array_get_unknown_index_out_of_bounds/Prover.toml @@ -0,0 +1 @@ +x = "10" \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_failure/unused_array_get_unknown_index_out_of_bounds/src/main.nr b/noir/noir-repo/test_programs/execution_failure/unused_array_get_unknown_index_out_of_bounds/src/main.nr new file mode 100644 index 000000000000..15c2d1f1f23e --- /dev/null +++ b/noir/noir-repo/test_programs/execution_failure/unused_array_get_unknown_index_out_of_bounds/src/main.nr @@ -0,0 +1,4 @@ +fn main(x: Field) { + let array = [1, 2, 3]; + let _ = array[x]; // Index out of bounds +} diff --git a/noir/noir-repo/test_programs/execution_failure/unused_array_set_known_index_out_of_bounds/Nargo.toml b/noir/noir-repo/test_programs/execution_failure/unused_array_set_known_index_out_of_bounds/Nargo.toml new file mode 100644 index 000000000000..b8fe7e955a19 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_failure/unused_array_set_known_index_out_of_bounds/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "unused_array_set_known_index_out_of_bounds" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_failure/unused_array_set_known_index_out_of_bounds/Prover.toml b/noir/noir-repo/test_programs/execution_failure/unused_array_set_known_index_out_of_bounds/Prover.toml new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/noir/noir-repo/test_programs/execution_failure/unused_array_set_known_index_out_of_bounds/src/main.nr b/noir/noir-repo/test_programs/execution_failure/unused_array_set_known_index_out_of_bounds/src/main.nr new file mode 100644 index 000000000000..9c447aee08f7 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_failure/unused_array_set_known_index_out_of_bounds/src/main.nr @@ -0,0 +1,4 @@ +fn main() { + let mut array = [1, 2, 3]; + array[10] = 1; // Index out of bounds +} diff --git a/noir/noir-repo/test_programs/execution_failure/unused_array_set_unknown_index_out_of_bounds/Nargo.toml b/noir/noir-repo/test_programs/execution_failure/unused_array_set_unknown_index_out_of_bounds/Nargo.toml new file mode 100644 index 000000000000..ccc00956e801 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_failure/unused_array_set_unknown_index_out_of_bounds/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "unused_array_set_unknown_index_out_of_bounds" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_failure/unused_array_set_unknown_index_out_of_bounds/Prover.toml b/noir/noir-repo/test_programs/execution_failure/unused_array_set_unknown_index_out_of_bounds/Prover.toml new file mode 100644 index 000000000000..1ec81884d618 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_failure/unused_array_set_unknown_index_out_of_bounds/Prover.toml @@ -0,0 +1 @@ +x = "10" \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_failure/unused_array_set_unknown_index_out_of_bounds/src/main.nr b/noir/noir-repo/test_programs/execution_failure/unused_array_set_unknown_index_out_of_bounds/src/main.nr new file mode 100644 index 000000000000..dbde898f7a9a --- /dev/null +++ b/noir/noir-repo/test_programs/execution_failure/unused_array_set_unknown_index_out_of_bounds/src/main.nr @@ -0,0 +1,4 @@ +fn main(x: Field) { + let mut array = [1, 2, 3]; + array[x] = 1; // Index out of bounds +} diff --git a/noir/noir-repo/test_programs/execution_failure/unused_slice_get_known_index_out_of_bounds/Nargo.toml b/noir/noir-repo/test_programs/execution_failure/unused_slice_get_known_index_out_of_bounds/Nargo.toml new file mode 100644 index 000000000000..f2acfa4d4cf9 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_failure/unused_slice_get_known_index_out_of_bounds/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "unused_slice_get_known_index_out_of_bounds" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_failure/unused_slice_get_known_index_out_of_bounds/Prover.toml b/noir/noir-repo/test_programs/execution_failure/unused_slice_get_known_index_out_of_bounds/Prover.toml new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/noir/noir-repo/test_programs/execution_failure/unused_slice_get_known_index_out_of_bounds/src/main.nr b/noir/noir-repo/test_programs/execution_failure/unused_slice_get_known_index_out_of_bounds/src/main.nr new file mode 100644 index 000000000000..59e57664bbe9 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_failure/unused_slice_get_known_index_out_of_bounds/src/main.nr @@ -0,0 +1,4 @@ +fn main() { + let slice = &[1, 2, 3]; + let _ = slice[10]; // Index out of bounds +} diff --git a/noir/noir-repo/test_programs/execution_failure/unused_slice_get_unknown_index_out_of_bounds/Nargo.toml b/noir/noir-repo/test_programs/execution_failure/unused_slice_get_unknown_index_out_of_bounds/Nargo.toml new file mode 100644 index 000000000000..3c8ae8fe07a6 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_failure/unused_slice_get_unknown_index_out_of_bounds/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "unused_slice_get_unknown_index_out_of_bounds" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_failure/unused_slice_get_unknown_index_out_of_bounds/Prover.toml b/noir/noir-repo/test_programs/execution_failure/unused_slice_get_unknown_index_out_of_bounds/Prover.toml new file mode 100644 index 000000000000..1ec81884d618 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_failure/unused_slice_get_unknown_index_out_of_bounds/Prover.toml @@ -0,0 +1 @@ +x = "10" \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_failure/unused_slice_get_unknown_index_out_of_bounds/src/main.nr b/noir/noir-repo/test_programs/execution_failure/unused_slice_get_unknown_index_out_of_bounds/src/main.nr new file mode 100644 index 000000000000..5a62e0e98432 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_failure/unused_slice_get_unknown_index_out_of_bounds/src/main.nr @@ -0,0 +1,4 @@ +fn main(x: Field) { + let slice = &[1, 2, 3]; + let _ = slice[x]; // Index out of bounds +} diff --git a/noir/noir-repo/test_programs/execution_success/acir_inside_brillig_recursion/src/main.nr b/noir/noir-repo/test_programs/execution_success/acir_inside_brillig_recursion/src/main.nr index 92f8524a7711..49b7c00b6b97 100644 --- a/noir/noir-repo/test_programs/execution_success/acir_inside_brillig_recursion/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/acir_inside_brillig_recursion/src/main.nr @@ -1,5 +1,7 @@ fn main() { - assert_eq(fibonacci(3), fibonacci_hint(3)); + unsafe { + assert_eq(fibonacci(3), fibonacci_hint(3)); + } } unconstrained fn fibonacci_hint(x: u32) -> u32 { diff --git a/noir/noir-repo/test_programs/execution_success/aes128_encrypt/src/main.nr b/noir/noir-repo/test_programs/execution_success/aes128_encrypt/src/main.nr index cd7fb4772e23..9cf07841b9e6 100644 --- a/noir/noir-repo/test_programs/execution_success/aes128_encrypt/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/aes128_encrypt/src/main.nr @@ -31,12 +31,19 @@ unconstrained fn cipher(plaintext: [u8; 12], iv: [u8; 16], key: [u8; 16]) -> [u8 fn main(inputs: str<12>, iv: str<16>, key: str<16>, output: str<32>) { let result = std::aes128::aes128_encrypt(inputs.as_bytes(), iv.as_bytes(), key.as_bytes()); - let output_bytes: [u8; 16] = decode_hex(output); - for i in 0..16 { - assert(result[i] == output_bytes[i]); - } - let unconstrained_result = cipher(inputs.as_bytes(), iv.as_bytes(), key.as_bytes()); - for i in 0..16 { - assert(unconstrained_result[i] == output_bytes[i]); + + let output_bytes: [u8; 16] = unsafe { + let output_bytes: [u8; 16] = decode_hex(output); + for i in 0..16 { + assert(result[i] == output_bytes[i]); + } + output_bytes + }; + + unsafe { + let unconstrained_result = cipher(inputs.as_bytes(), iv.as_bytes(), key.as_bytes()); + for i in 0..16 { + assert(unconstrained_result[i] == output_bytes[i]); + } } } diff --git a/noir/noir-repo/test_programs/execution_success/array_to_slice_constant_length/src/main.nr b/noir/noir-repo/test_programs/execution_success/array_to_slice_constant_length/src/main.nr index e81dd4a0c5f9..5668a9ff388c 100644 --- a/noir/noir-repo/test_programs/execution_success/array_to_slice_constant_length/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/array_to_slice_constant_length/src/main.nr @@ -5,6 +5,8 @@ unconstrained fn return_array(val: Field) -> [Field; 1] { } fn main(val: Field) { - let array = return_array(val); - assert_constant(array.as_slice().len()); + unsafe { + let array = return_array(val); + assert_constant(array.as_slice().len()); + } } diff --git a/noir/noir-repo/test_programs/execution_success/bigint/src/main.nr b/noir/noir-repo/test_programs/execution_success/bigint/src/main.nr index 9385b39e8476..188ccd01131e 100644 --- a/noir/noir-repo/test_programs/execution_success/bigint/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/bigint/src/main.nr @@ -17,7 +17,9 @@ fn main(mut x: [u8; 5], y: [u8; 5]) { let c = if x[0] != 0 { test_unconstrained1(a, b) } else { - test_unconstrained2(a, b) + unsafe { + test_unconstrained2(a, b) + } }; assert(c.array[0] == std::wrapping_mul(x[0], y[0])); diff --git a/noir/noir-repo/test_programs/execution_success/brillig_acir_as_brillig/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_acir_as_brillig/src/main.nr index 5bd6ce0adb2b..1a595ecfb383 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_acir_as_brillig/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_acir_as_brillig/src/main.nr @@ -1,7 +1,9 @@ fn main(x: u32) { - assert(entry_point(x) == 2); - swap_entry_point(x, x + 1); - assert(deep_entry_point(x) == 4); + unsafe { + assert(entry_point(x) == 2); + swap_entry_point(x, x + 1); + assert(deep_entry_point(x) == 4); + } } fn inner(x: u32) -> u32 { diff --git a/noir/noir-repo/test_programs/execution_success/brillig_array_to_slice/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_array_to_slice/src/main.nr index 262d3b5d86a4..f54adb399639 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_array_to_slice/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_array_to_slice/src/main.nr @@ -10,9 +10,11 @@ unconstrained fn brillig_as_slice(x: Field) -> (u32, Field, Field) { } fn main(x: Field) { - let (slice_len, dynamic_0, slice_0) = brillig_as_slice(x); - assert(slice_len == 1); - assert(dynamic_0 == 2); - assert(slice_0 == 2); + unsafe { + let (slice_len, dynamic_0, slice_0) = brillig_as_slice(x); + assert(slice_len == 1); + assert(dynamic_0 == 2); + assert(slice_0 == 2); + } } diff --git a/noir/noir-repo/test_programs/execution_success/brillig_arrays/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_arrays/src/main.nr index e535b6001a43..c7f0757f31eb 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_arrays/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_arrays/src/main.nr @@ -2,8 +2,10 @@ // // The features being tested are array reads and writes fn main(x: [Field; 3]) { - read_array(x); - read_write_array(x); + unsafe { + read_array(x); + read_write_array(x); + } } unconstrained fn read_array(x: [Field; 3]) { diff --git a/noir/noir-repo/test_programs/execution_success/brillig_assert/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_assert/src/main.nr index 16fe7b29061b..c6c39b61bc9e 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_assert/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_assert/src/main.nr @@ -2,7 +2,9 @@ // // The features being tested is using assert on brillig fn main(x: Field) { - assert(1 == conditional(x as bool)); + unsafe { + assert(1 == conditional(x as bool)); + } } unconstrained fn conditional(x: bool) -> Field { diff --git a/noir/noir-repo/test_programs/execution_success/brillig_blake2s/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_blake2s/src/main.nr index 2743e02e920c..122142a9c80a 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_blake2s/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_blake2s/src/main.nr @@ -2,7 +2,9 @@ // // The features being tested is blake2s in brillig fn main(x: [u8; 5], result: [u8; 32]) { - assert(blake2s(x) == result); + unsafe { + assert(blake2s(x) == result); + } } unconstrained fn blake2s(x: [u8; 5]) -> [u8; 32] { diff --git a/noir/noir-repo/test_programs/execution_success/brillig_block_parameter_liveness/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_block_parameter_liveness/src/main.nr index 2809668c574d..6ddfc03622a2 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_block_parameter_liveness/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_block_parameter_liveness/src/main.nr @@ -38,11 +38,11 @@ struct Outer { // If we don't take into account block parameter liveness, this function will need 5*500=2500 stack items unconstrained fn main(conditions: [bool; 5]) -> pub Outer { let out0 = if conditions[0] { - let mut outer: Outer = std::unsafe::zeroed(); + let mut outer: Outer = std::mem::zeroed(); outer.middle_a.inner_a.a = 1; outer } else { - let mut outer: Outer= std::unsafe::zeroed(); + let mut outer: Outer = std::mem::zeroed(); outer.middle_f.inner_c.d = 2; outer }; diff --git a/noir/noir-repo/test_programs/execution_success/brillig_calls/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_calls/src/main.nr index 5c39713f5bbc..3e23da53b18f 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_calls/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_calls/src/main.nr @@ -2,10 +2,12 @@ // // The features being tested is brillig calls fn main(x: u32) { - assert(entry_point(x) == 2); - swap_entry_point(x, x + 1); - assert(deep_entry_point(x) == 4); - multiple_values_entry_point(x); + unsafe { + assert(entry_point(x) == 2); + swap_entry_point(x, x + 1); + assert(deep_entry_point(x) == 4); + multiple_values_entry_point(x); + } } unconstrained fn returns_multiple_values(x: u32) -> (u32, u32, u32, u32) { diff --git a/noir/noir-repo/test_programs/execution_success/brillig_calls_array/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_calls_array/src/main.nr index 1b1d89f6366f..8b27a9bb202e 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_calls_array/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_calls_array/src/main.nr @@ -2,8 +2,10 @@ // // The features being tested is brillig calls passing arrays around fn main(x: [u32; 3]) { - assert(entry_point(x) == 9); - another_entry_point(x); + unsafe { + assert(entry_point(x) == 9); + another_entry_point(x); + } } unconstrained fn inner(x: [u32; 3]) -> [u32; 3] { diff --git a/noir/noir-repo/test_programs/execution_success/brillig_calls_conditionals/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_calls_conditionals/src/main.nr index 0a1718d0171b..318da4caf722 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_calls_conditionals/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_calls_conditionals/src/main.nr @@ -2,10 +2,12 @@ // // The features being tested is brillig calls with conditionals fn main(x: [u32; 3]) { - assert(entry_point(x[0]) == 7); - assert(entry_point(x[1]) == 8); - assert(entry_point(x[2]) == 9); - assert(entry_point(42) == 0); + unsafe { + assert(entry_point(x[0]) == 7); + assert(entry_point(x[1]) == 8); + assert(entry_point(x[2]) == 9); + assert(entry_point(42) == 0); + } } unconstrained fn inner_1() -> u32 { diff --git a/noir/noir-repo/test_programs/execution_success/brillig_conditional/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_conditional/src/main.nr index a59336a877bd..8ababf823196 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_conditional/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_conditional/src/main.nr @@ -2,7 +2,9 @@ // // The features being tested is basic conditonal on brillig fn main(x: Field) { - assert(4 == conditional(x == 1)); + unsafe { + assert(4 == conditional(x == 1)); + } } unconstrained fn conditional(x: bool) -> Field { diff --git a/noir/noir-repo/test_programs/execution_success/brillig_ecdsa_secp256k1/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_ecdsa_secp256k1/src/main.nr index 78343dcd26c2..45f13c79637f 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_ecdsa_secp256k1/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_ecdsa_secp256k1/src/main.nr @@ -2,7 +2,9 @@ // // The features being tested is ecdsa in brillig fn main(hashed_message: [u8; 32], pub_key_x: [u8; 32], pub_key_y: [u8; 32], signature: [u8; 64]) { - assert(ecdsa(hashed_message, pub_key_x, pub_key_y, signature)); + unsafe { + assert(ecdsa(hashed_message, pub_key_x, pub_key_y, signature)); + } } unconstrained fn ecdsa( diff --git a/noir/noir-repo/test_programs/execution_success/brillig_ecdsa_secp256r1/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_ecdsa_secp256r1/src/main.nr index 48debadb0127..32b562ec50c2 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_ecdsa_secp256r1/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_ecdsa_secp256r1/src/main.nr @@ -2,7 +2,9 @@ // // The features being tested is ecdsa in brillig fn main(hashed_message: [u8; 32], pub_key_x: [u8; 32], pub_key_y: [u8; 32], signature: [u8; 64]) { - assert(ecdsa(hashed_message, pub_key_x, pub_key_y, signature)); + unsafe { + assert(ecdsa(hashed_message, pub_key_x, pub_key_y, signature)); + } } unconstrained fn ecdsa( diff --git a/noir/noir-repo/test_programs/execution_success/brillig_fns_as_values/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_fns_as_values/src/main.nr index 1476c4474318..55b9d3079051 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_fns_as_values/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_fns_as_values/src/main.nr @@ -1,18 +1,20 @@ struct MyStruct { - operation: fn (u32) -> u32, + operation: unconstrained fn (u32) -> u32, } fn main(x: u32) { - assert(wrapper(increment, x) == x + 1); - assert(wrapper(increment_acir, x) == x + 1); - assert(wrapper(decrement, x) == x - 1); - assert(wrapper_with_struct(MyStruct { operation: increment }, x) == x + 1); - assert(wrapper_with_struct(MyStruct { operation: decrement }, x) == x - 1); - // https://github.com/noir-lang/noir/issues/1975 - assert(increment(x) == x + 1); + unsafe { + assert(wrapper(increment, x) == x + 1); + assert(wrapper(increment_acir, x) == x + 1); + assert(wrapper(decrement, x) == x - 1); + assert(wrapper_with_struct(MyStruct { operation: increment }, x) == x + 1); + assert(wrapper_with_struct(MyStruct { operation: decrement }, x) == x - 1); + // https://github.com/noir-lang/noir/issues/1975 + assert(increment(x) == x + 1); + } } -unconstrained fn wrapper(func: fn(u32) -> u32, param: u32) -> u32 { +unconstrained fn wrapper(func: unconstrained fn(u32) -> u32, param: u32) -> u32 { func(param) } diff --git a/noir/noir-repo/test_programs/execution_success/brillig_hash_to_field/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_hash_to_field/src/main.nr index 78759bd84c67..d1ea635d49ad 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_hash_to_field/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_hash_to_field/src/main.nr @@ -2,7 +2,9 @@ // // The features being tested is hash_to_field in brillig fn main(input: Field) -> pub Field { - hash_to_field(input) + unsafe { + hash_to_field(input) + } } unconstrained fn hash_to_field(input: Field) -> Field { diff --git a/noir/noir-repo/test_programs/execution_success/brillig_identity_function/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_identity_function/src/main.nr index f41188b1f0d8..c2759fe054f9 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_identity_function/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_identity_function/src/main.nr @@ -6,17 +6,19 @@ struct myStruct { // // The features being tested is the identity function in Brillig fn main(x: Field) { - assert(x == identity(x)); - // TODO: add support for array comparison - let arr = identity_array([x, x]); - assert(x == arr[0]); - assert(x == arr[1]); + unsafe { + assert(x == identity(x)); + // TODO: add support for array comparison + let arr = identity_array([x, x]); + assert(x == arr[0]); + assert(x == arr[1]); - let s = myStruct { foo: x, foo_arr: [x, x] }; - let identity_struct = identity_struct(s); - assert(x == identity_struct.foo); - assert(x == identity_struct.foo_arr[0]); - assert(x == identity_struct.foo_arr[1]); + let s = myStruct { foo: x, foo_arr: [x, x] }; + let identity_struct = identity_struct(s); + assert(x == identity_struct.foo); + assert(x == identity_struct.foo_arr[0]); + assert(x == identity_struct.foo_arr[1]); + } } unconstrained fn identity(x: Field) -> Field { diff --git a/noir/noir-repo/test_programs/execution_success/brillig_keccak/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_keccak/src/main.nr index 9150e38f2081..dd3392086598 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_keccak/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_keccak/src/main.nr @@ -2,21 +2,23 @@ // // The features being tested is keccak256 in brillig 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 = keccak256([x as u8], 1); - assert(digest == result); - //#1399: variable message size - let message_size = 4; - let hash_a = keccak256([1, 2, 3, 4], message_size); - let hash_b = keccak256([1, 2, 3, 4, 0, 0, 0, 0], message_size); + unsafe { + // 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 = keccak256([x as u8], 1); + assert(digest == result); + //#1399: variable message size + let message_size = 4; + let hash_a = keccak256([1, 2, 3, 4], message_size); + let hash_b = keccak256([1, 2, 3, 4, 0, 0, 0, 0], message_size); - assert(hash_a == hash_b); + assert(hash_a == hash_b); - let message_size_big = 8; - let hash_c = keccak256([1, 2, 3, 4, 0, 0, 0, 0], message_size_big); + let message_size_big = 8; + let hash_c = keccak256([1, 2, 3, 4, 0, 0, 0, 0], message_size_big); - assert(hash_a != hash_c); + assert(hash_a != hash_c); + } } unconstrained fn keccak256(data: [u8; N], msg_len: u32) -> [u8; 32] { diff --git a/noir/noir-repo/test_programs/execution_success/brillig_loop/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_loop/src/main.nr index 05d354693423..770660bb2a1e 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_loop/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_loop/src/main.nr @@ -2,8 +2,10 @@ // // The features being tested is basic looping on brillig fn main(sum: u32) { - assert(loop(4) == sum); - assert(plain_loop() == sum); + unsafe { + assert(loop(4) == sum); + assert(plain_loop() == sum); + } } unconstrained fn loop(x: u32) -> u32 { diff --git a/noir/noir-repo/test_programs/execution_success/brillig_loop_size_regression/Nargo.toml b/noir/noir-repo/test_programs/execution_success/brillig_loop_size_regression/Nargo.toml new file mode 100644 index 000000000000..d2a98e19742d --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/brillig_loop_size_regression/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "brillig_loop_size_regression" +type = "bin" +authors = [""] +compiler_version = ">=0.33.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_success/brillig_loop_size_regression/Prover.toml b/noir/noir-repo/test_programs/execution_success/brillig_loop_size_regression/Prover.toml new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/noir/noir-repo/test_programs/execution_success/brillig_loop_size_regression/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_loop_size_regression/src/main.nr new file mode 100644 index 000000000000..488304114b97 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/brillig_loop_size_regression/src/main.nr @@ -0,0 +1,16 @@ +struct EnumEmulation { + a: Option, + b: Option, + c: Option, +} + +unconstrained fn main() -> pub Field { + let mut emulated_enum = EnumEmulation { a: Option::some(1), b: Option::none(), c: Option::none() }; + + for _ in 0..1 { + assert_eq(emulated_enum.a.unwrap(), 1); + } + + emulated_enum.a = Option::some(2); + emulated_enum.a.unwrap() +} diff --git a/noir/noir-repo/test_programs/execution_success/brillig_nested_arrays/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_nested_arrays/src/main.nr index 5a5657246a82..77ab4ea19a6e 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_nested_arrays/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_nested_arrays/src/main.nr @@ -28,14 +28,16 @@ unconstrained fn create_and_assert_inside_brillig(x: Field, y: Field) { } fn main(x: Field, y: Field) { - let header = Header { params: [1, 2, 3] }; - let note0 = MyNote { array: [1, 2], plain: 3, header }; - let note1 = MyNote { array: [4, 5], plain: 6, header }; + unsafe { + let header = Header { params: [1, 2, 3] }; + let note0 = MyNote { array: [1, 2], plain: 3, header }; + let note1 = MyNote { array: [4, 5], plain: 6, header }; - assert(access_nested([note0, note1], x, y) == (2 + 4 + 3 + 1)); + assert(access_nested([note0, note1], x, y) == (2 + 4 + 3 + 1)); - let notes = create_inside_brillig(); - assert_inside_brillig(notes, x, y); - create_and_assert_inside_brillig(x, y); + let notes = create_inside_brillig(); + assert_inside_brillig(notes, x, y); + create_and_assert_inside_brillig(x, y); + } } diff --git a/noir/noir-repo/test_programs/execution_success/brillig_not/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_not/src/main.nr index d34b3edb4b65..557d1e2e31fc 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_not/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_not/src/main.nr @@ -2,8 +2,10 @@ // // The features being tested is not instruction on brillig fn main(x: Field, y: Field) { - assert(false == not_operator(x as bool)); - assert(true == not_operator(y as bool)); + unsafe { + assert(false == not_operator(x as bool)); + assert(true == not_operator(y as bool)); + } } unconstrained fn not_operator(x: bool) -> bool { diff --git a/noir/noir-repo/test_programs/execution_success/brillig_oracle/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_oracle/src/main.nr index 0305cb06978c..93465b873893 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_oracle/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_oracle/src/main.nr @@ -3,21 +3,23 @@ use std::test::OracleMock; // Tests oracle usage in brillig/unconstrained functions fn main(_x: Field) { - let size = 20; - // TODO: Add a method along the lines of `(0..size).to_array()`. - let mut mock_oracle_response = [0; 20]; - // TODO: Add an `array.reverse()` method. - let mut reversed_mock_oracle_response = [0; 20]; - for i in 0..size { - mock_oracle_response[i] = i; - reversed_mock_oracle_response[19 - i] = i; + unsafe { + let size = 20; + // TODO: Add a method along the lines of `(0..size).to_array()`. + let mut mock_oracle_response = [0; 20]; + // TODO: Add an `array.reverse()` method. + let mut reversed_mock_oracle_response = [0; 20]; + for i in 0..size { + mock_oracle_response[i] = i; + reversed_mock_oracle_response[19 - i] = i; + } + + // TODO: this method of returning a slice feels hacky. + let _ = OracleMock::mock("get_number_sequence").with_params(size).returns((20, mock_oracle_response)); + let _ = OracleMock::mock("get_reverse_number_sequence").with_params(size).returns((20, reversed_mock_oracle_response)); + + get_number_sequence_wrapper(size as Field); } - - // TODO: this method of returning a slice feels hacky. - let _ = OracleMock::mock("get_number_sequence").with_params(size).returns((20, mock_oracle_response)); - let _ = OracleMock::mock("get_reverse_number_sequence").with_params(size).returns((20, reversed_mock_oracle_response)); - - get_number_sequence_wrapper(size as Field); } // Define oracle functions which we have mocked above diff --git a/noir/noir-repo/test_programs/execution_success/brillig_recursion/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_recursion/src/main.nr index a87ef28bc561..c69468013b1f 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_recursion/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_recursion/src/main.nr @@ -2,7 +2,9 @@ // // The feature being tested is brillig recursion fn main(x: u32) { - assert(fibonacci(x) == 55); + unsafe { + assert(fibonacci(x) == 55); + } } unconstrained fn fibonacci(x: u32) -> u32 { diff --git a/noir/noir-repo/test_programs/execution_success/brillig_sha256/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_sha256/src/main.nr index fcc01978a0a0..5519fb2da643 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_sha256/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_sha256/src/main.nr @@ -2,7 +2,9 @@ // // The features being tested is sha256 in brillig fn main(x: Field, result: [u8; 32]) { - assert(result == sha256(x)); + unsafe { + assert(result == sha256(x)); + } } unconstrained fn sha256(x: Field) -> [u8; 32] { diff --git a/noir/noir-repo/test_programs/execution_success/brillig_unitialised_arrays/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_unitialised_arrays/src/main.nr index 5ec657b0d350..d4b74162cfb7 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_unitialised_arrays/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_unitialised_arrays/src/main.nr @@ -1,6 +1,8 @@ fn main(x: Field, y: Field) -> pub Field { - let notes = create_notes(x, y); - sum_x(notes, x, y) + unsafe { + let notes = create_notes(x, y); + sum_x(notes, x, y) + } } fn sum_x(notes: [Field; 2], x: Field, y: Field) -> Field { 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 1e4aa141eead..ecc7794cf9ea 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,6 +1,8 @@ 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) + unsafe { + a + foo(y) + } } // Use an unconstrained function to force the compiler to avoid inlining diff --git a/noir/noir-repo/test_programs/execution_success/generics/src/main.nr b/noir/noir-repo/test_programs/execution_success/generics/src/main.nr index f754fb96292f..75a7f8a3154c 100644 --- a/noir/noir-repo/test_programs/execution_success/generics/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/generics/src/main.nr @@ -34,7 +34,7 @@ impl Bar { impl Bar { // This is to test that we can use turbofish on methods as well fn zeroed(_self: Self) -> A { - std::unsafe::zeroed() + std::mem::zeroed() } } diff --git a/noir/noir-repo/test_programs/execution_success/global_consts/src/main.nr b/noir/noir-repo/test_programs/execution_success/global_consts/src/main.nr index 52ffe3e823bd..966be2741d6d 100644 --- a/noir/noir-repo/test_programs/execution_success/global_consts/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/global_consts/src/main.nr @@ -25,7 +25,7 @@ unconstrained fn calculate_global_value() -> Field { } // Regression test for https://github.com/noir-lang/noir/issues/4318 -global CALCULATED_GLOBAL: Field = calculate_global_value(); +global CALCULATED_GLOBAL: Field = unsafe { calculate_global_value() }; fn main( a: [Field; M + N - N], diff --git a/noir/noir-repo/test_programs/execution_success/is_unconstrained/src/main.nr b/noir/noir-repo/test_programs/execution_success/is_unconstrained/src/main.nr index ddafc73c5981..d06366cf6420 100644 --- a/noir/noir-repo/test_programs/execution_success/is_unconstrained/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/is_unconstrained/src/main.nr @@ -9,6 +9,8 @@ unconstrained fn unconstrained_intermediate() { } fn main() { - unconstrained_intermediate(); + unsafe { + unconstrained_intermediate(); + } check(false); } diff --git a/noir/noir-repo/test_programs/execution_success/nested_arrays_from_brillig/src/main.nr b/noir/noir-repo/test_programs/execution_success/nested_arrays_from_brillig/src/main.nr index 1bcbd7d54212..9664b4d1ce61 100644 --- a/noir/noir-repo/test_programs/execution_success/nested_arrays_from_brillig/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/nested_arrays_from_brillig/src/main.nr @@ -20,7 +20,9 @@ unconstrained fn create_inside_brillig(values: [Field; 6]) -> [MyNote; 2] { } fn main(values: [Field; 6]) { - let notes = create_inside_brillig(values); + let notes = unsafe { + create_inside_brillig(values) + }; assert(access_nested(notes) == (2 + 4 + 3 + 1)); } diff --git a/noir/noir-repo/test_programs/execution_success/regression_4124/src/main.nr b/noir/noir-repo/test_programs/execution_success/regression_4124/src/main.nr index 6caea017798b..7b5060062da6 100644 --- a/noir/noir-repo/test_programs/execution_success/regression_4124/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/regression_4124/src/main.nr @@ -11,7 +11,7 @@ impl MyDeserialize<1> for Field { } pub fn storage_read() -> [Field; N] { - std::unsafe::zeroed() + std::mem::zeroed() } struct PublicMutable { diff --git a/noir/noir-repo/test_programs/execution_success/regression_5435/src/main.nr b/noir/noir-repo/test_programs/execution_success/regression_5435/src/main.nr index 65f13c5b2011..d8aeb76356b5 100644 --- a/noir/noir-repo/test_programs/execution_success/regression_5435/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/regression_5435/src/main.nr @@ -3,7 +3,9 @@ fn main(input: Field, enable: bool) { let hash = no_predicate_function(input); // `EnableSideEffects` instruction from above instruction leaks out and removes the predicate from this call, // resulting in execution failure. - fail(hash); + unsafe { + fail(hash) + }; } } 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 index 43bd4433c694..2382f5b0f2f8 100644 --- 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 @@ -327,7 +327,7 @@ fn main() { // // impl Bvec { // fn empty() -> Self { -// Self { inner: [std::unsafe::zeroed(); N], offset: 0, len: 0 } +// Self { inner: [std::mem::zeroed(); N], offset: 0, len: 0 } // } // // fn new(array: [T; N]) -> Self { @@ -559,7 +559,7 @@ fn main() { // } // // fn reverse_array(input: [T; N]) -> [T; N] { -// let mut output = [std::unsafe::zeroed(); N]; +// let mut output = [std::mem::zeroed(); N]; // for i in 0..N { // output[i] = input[N - (i + 1)]; // } @@ -587,7 +587,7 @@ fn main() { // assert_eq(xs, ys); // // // test that pop_front gives all contents, in order, -// // followed by std::unsafe::zeroed() +// // followed by std::mem::zeroed() // println(xs); // let (x, new_xs) = xs.pop_front(); // assert_eq(x, 0); @@ -606,7 +606,7 @@ fn main() { // println(xs); // if xs.len != 0 { // let (x, _new_xs) = xs.pop_front(); -// assert_eq(x, std::unsafe::zeroed()); +// assert_eq(x, std::mem::zeroed()); // } // // assert_eq(new_xs, Bvec { inner: [0, 1, 2], offset: 3, len: 0 }); diff --git a/noir/noir-repo/test_programs/execution_success/u16_support/src/main.nr b/noir/noir-repo/test_programs/execution_success/u16_support/src/main.nr index e8b418f16da4..ca41c7080771 100644 --- a/noir/noir-repo/test_programs/execution_success/u16_support/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/u16_support/src/main.nr @@ -1,6 +1,8 @@ fn main(x: u16) { test_u16(x); - test_u16_unconstrained(x); + unsafe { + test_u16_unconstrained(x); + } } unconstrained fn test_u16_unconstrained(x: u16) { diff --git a/noir/noir-repo/test_programs/execution_success/uhashmap/src/main.nr b/noir/noir-repo/test_programs/execution_success/uhashmap/src/main.nr index 395ed21b6b09..f2ca68137130 100644 --- a/noir/noir-repo/test_programs/execution_success/uhashmap/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/uhashmap/src/main.nr @@ -294,7 +294,9 @@ unconstrained fn doc_tests() { // docs:start:get_example fn get_example(map: UHashMap>) { - let x = map.get(12); + let x = unsafe { + map.get(12) + }; if x.is_some() { assert(x.unwrap() == 42); @@ -320,7 +322,9 @@ fn entries_examples(map: UHashMap {value}"); } // docs:end:keys_example diff --git a/noir/noir-repo/test_programs/execution_success/unit_value/src/main.nr b/noir/noir-repo/test_programs/execution_success/unit_value/src/main.nr index a488f267b4c0..18c7e5f4f7da 100644 --- a/noir/noir-repo/test_programs/execution_success/unit_value/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/unit_value/src/main.nr @@ -1,5 +1,5 @@ fn get_transaction() { - std::unsafe::zeroed() + std::mem::zeroed() } fn main() { diff --git a/noir/noir-repo/test_programs/execution_success/verify_honk_proof/Nargo.toml b/noir/noir-repo/test_programs/execution_success/verify_honk_proof/Nargo.toml deleted file mode 100644 index 8fce1bf44b6e..000000000000 --- a/noir/noir-repo/test_programs/execution_success/verify_honk_proof/Nargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "verify_honk_proof" -type = "bin" -authors = [""] - -[dependencies] 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 f2e6bbed8ef2..000000000000 --- a/noir/noir-repo/test_programs/execution_success/verify_honk_proof/Prover.toml +++ /dev/null @@ -1,537 +0,0 @@ -key_hash = "0x096129b1c6e108252fc5c829c4cc9b7e8f0d1fd9f29c2532b563d6396645e08f" -proof = [ - "0x0000000000000000000000000000000000000000000000000000000000000020", - "0x0000000000000000000000000000000000000000000000000000000000000011", - "0x0000000000000000000000000000000000000000000000000000000000000001", - "0x0000000000000000000000000000000000000000000000042ab5d6d1986846cf", - "0x00000000000000000000000000000000000000000000000b75c020998797da78", - "0x0000000000000000000000000000000000000000000000005a107acb64952eca", - "0x000000000000000000000000000000000000000000000000000031e97a575e9d", - "0x00000000000000000000000000000000000000000000000b5666547acf8bd5a4", - "0x00000000000000000000000000000000000000000000000c410db10a01750aeb", - "0x00000000000000000000000000000000000000000000000d722669117f9758a4", - "0x000000000000000000000000000000000000000000000000000178cbf4206471", - "0x000000000000000000000000000000000000000000000000e91b8a11e7842c38", - "0x000000000000000000000000000000000000000000000007fd51009034b3357f", - "0x000000000000000000000000000000000000000000000009889939f81e9c7402", - "0x0000000000000000000000000000000000000000000000000000f94656a2ca48", - "0x000000000000000000000000000000000000000000000006fb128b46c1ddb67f", - "0x0000000000000000000000000000000000000000000000093fe27776f50224bd", - "0x000000000000000000000000000000000000000000000004a0c80c0da527a081", - "0x0000000000000000000000000000000000000000000000000001b52c2020d746", - "0x0000000000000000000000000000005a9bae947e1e91af9e4033d8d6aa6ed632", - "0x000000000000000000000000000000000025e485e013446d4ac7981c88ba6ecc", - "0x000000000000000000000000000000ff1e0496e30ab24a63b32b2d1120b76e62", - "0x00000000000000000000000000000000001afe0a8a685d7cd85d1010e55d9d7c", - "0x000000000000000000000000000000b0804efd6573805f991458295f510a2004", - "0x00000000000000000000000000000000000c81a178016e2fe18605022d5a8b0e", - "0x000000000000000000000000000000eba51e76eb1cfff60a53a0092a3c3dea47", - "0x000000000000000000000000000000000022e7466247b533282f5936ac4e6c15", - "0x00000000000000000000000000000071b1d76edf770edff98f00ff4deec264cd", - "0x00000000000000000000000000000000001e48128e68794d8861fcbb2986a383", - "0x000000000000000000000000000000d3a2af4915ae6d86b097adc377fafda2d4", - "0x000000000000000000000000000000000006359de9ca452dab3a4f1f8d9c9d98", - "0x0000000000000000000000000000000d9d719a8b9f020ad3642d60fe704e696f", - "0x00000000000000000000000000000000000ddfdbbdefc4ac1580ed38e12cfa49", - "0x0000000000000000000000000000008289fe9754ce48cd01b7be96a861b5e157", - "0x00000000000000000000000000000000000ff3e0896bdea021253b3d360fa678", - "0x0000000000000000000000000000000d9d719a8b9f020ad3642d60fe704e696f", - "0x00000000000000000000000000000000000ddfdbbdefc4ac1580ed38e12cfa49", - "0x0000000000000000000000000000008289fe9754ce48cd01b7be96a861b5e157", - "0x00000000000000000000000000000000000ff3e0896bdea021253b3d360fa678", - "0x000000000000000000000000000000f968b227a358a305607f3efc933823d288", - "0x00000000000000000000000000000000000eaf8adb390375a76d95e918b65e08", - "0x000000000000000000000000000000bb34b4b447aae56f5e24f81c3acd6d547f", - "0x00000000000000000000000000000000002175d012746260ebcfe339a91a81e1", - "0x0000000000000000000000000000005b739ed2075f2b046062b8fc6a2d1e9863", - "0x00000000000000000000000000000000001285cd1030d338c0e1603b4da2c838", - "0x00000000000000000000000000000027447d6c281eb38b2b937af4a516d60c04", - "0x000000000000000000000000000000000019bc3d980465fbb4a656a74296fc58", - "0x000000000000000000000000000000b484788ace8f7df86dd5e325d2e9b12599", - "0x00000000000000000000000000000000000a2ca0d10eb7b767114ae230b728d3", - "0x000000000000000000000000000000c6dfc7092f16f95795e437664498b88d53", - "0x0000000000000000000000000000000000131067b4e4d95a4f6f8cf5c9b5450a", - "0x0f413f22eec51f2a02800e0cafaeec1d92d744fbbaef213c687b9edabd6985f5", - "0x21230f4ff26c80ffb5d037a9d1d26c3f955ca34cbeca4f54db6656b932967a0c", - "0x0521f877fe35535767f99597cc50effbd283dcae6812ee0a7620d796ccbfd642", - "0x202b01350a9cc5c20ec0f3eaada338c0a3b793811bd539418ffa3cc4302615e2", - "0x2d1214d9b0d41058ad4a172d9c0aecc5bdabe95e687c3465050c6b5396509be4", - "0x1113b344a151b0af091cb28d728b752ebb4865da6cd7ee68471b961ca5cf69b9", - "0x2aa66d0954bb83e17bd5c9928d3aa7a7df75d741d409f7c15ba596804ba643fb", - "0x2e26bc7a530771ef7a95d5360d537e41cf94d8a0942764ff09881c107f91a106", - "0x0f14f32b921bb63ad1df00adab7c82af58ea8aa7f353f14b281208d8c5fab504", - "0x13429515c0c53b6502bbcdf545defb3cb69a986c9263e070fcbb397391aae1a3", - "0x1f21cac5e2f262afc1006a21454cc6bcb018c44e53ad8ab61cebbac99e539176", - "0x2a9886a6ddc8a61b097c668cd362fc8acdee8dde74f7b1af192c3e060bb2948f", - "0x2d718181e408ead2e9bcd30a84ad1fccbaf8d48ab6d1820bad4933d284b503c4", - "0x2634c1aafc902f14508f34d3d7e9d485f42d1a4c95b5a1ef73711ed0d3c68d77", - "0x092ede9777e6472ce5ffd8c963d466006189e960e2c591d338dc8d4af1a057fb", - "0x1cba45b17fd24f1cb1b4ab7b83eee741f6c77ba70a497dc4de259eceb7d5ea26", - "0x246e887c7bf2e17f919b2393b6e9b00b33e8822d862544a775aac05cb7bff710", - "0x04c3f539fe8689971948afcb437f1ecbd444a5bddaca1c8a450348dcd8480047", - "0x20c6a423ae4fd58e8951aa378d02d77baf90508ceb48856db2319d70938b186e", - "0x1bcf8786b554b3316d8ebdbc9d006a4e5d4865aad512ffd404b7f83550d3d030", - "0x09ab038260518f0970564afcd6bf22e2abf6b1fa5e12a327bbf195b6ca5edd78", - "0x1024e32554746f89c195286ba6ccfc9765e5d14bbe8064bc6fdf22d16ec6b495", - "0x17706656f8dbd7e47bb257a6428f0cb7278ea02fa9e6ce431d7bcc9133fba9c7", - "0x25a3e8a33c15ef2a4dd16313a6049bf1d468b4cdc141f238f2d51a1e8e1c22b3", - "0x1198863f08006edb27aee23164fb117a4ddec1bf1ed89807aa907e5cd24bf068", - "0x1862b4856b5b4d4a064f873e221703e4e2cd1ebfca1337dedca56485c38ed5a0", - "0x062214af1ea6dd6bf8895b92d394571c43970b6f967e1c794624d96071b25ad3", - "0x1e5be9428ddcf1f9b0cbafc28101e792ec5cf73852b0cd0b84fbff71b4490e09", - "0x2d4189bea5b1e30f63c64bd26df82f18bcaf885ec8887b54634b2557869ce87f", - "0x0f2e5d9a908850e9d44925e17d8b12d1adb1ed029799c9b5858598504242bbc0", - "0x3050dc85746a57931d99f3f35e77c2ba561fba0baa018b79ff1fd544026833ae", - "0x2a591a32437e5e0b875a137fd868bd1b6dbc003ff1b661f26e00627cc7c5cf47", - "0x27946841e1670ad9c65717016d0cedf524724217236e81b9fd0a264a36ebfb0e", - "0x0fc396e9d19d6e68e289602e292ee345542d0d28bf6de34fa62cc577cbdfb1df", - "0x08e7433a07a44c0c9c4dd4b273a2685bbd1a91fd5cf2b43409458fab42a23e1b", - "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", - "0x12bd9bfb029c3503a5c6deea87b0a0f11bb9f7ea584af2d48f3e48d7e09247ae", - "0x2ccc4810748c0a82dfc0f063d0b8c7999ffe9474653080e6ef92b3cb7a428784", - "0x08eb574d7fecadadb508c8bd35fdad06b99110609d679763c2e3645229b1b95a", - "0x0f1a65e747c8021ed7c454a4be1e89b1bce66ead9ed980fa98a7a050eafe98a1", - "0x1c8ff9e36684ec71614dee4c17859b06c742089f6029d3694a16e00dac9b57f1", - "0x0303101a8ba712aeca4da85b767ab8d3ecf489ec7d746f8ee20041717cc000e9", - "0x0aaf64c65e7088e5596108c9601467911fea809ca6540d79af77e6e66e36cd99", - "0x17caf164ce74ea7edfb1390e07763d2197797ec26661b92cde18a98d61d2fddc", - "0x18cb055c7ad6d01437725bb457681d81f3ecadc4f35d838a3c13daf25a44456a", - "0x2d78602b8bbcd32b36a99a6e2d248e7fe044ef1b50813133370412f9ef5299f0", - "0x2b139276ea86d426a115479e4154f72a6bd83a6253bf13e9670dc6b4664378f0", - "0x127c7837b384902c39a104036c09546728571c46c8166b1b9b13b3a615ebb781", - "0x05faa4816f83cf0189a482ad943c94b9ec6474002f2b327f8698763ad0ea0985", - "0x2f90359cc30ee693fb3aced96523cf7aebd152c22329eee56a398d9a4ac0628e", - "0x0a71beaf17a59c5a238f04c1f203848d87502c5057a78c13f0cfb0f9876e7714", - "0x2696c1e6d089556adaeb95c8a5e3065b00a393a38c2d69e9bd6ce8cdc49d87da", - "0x1f3d165a7dc6564a036e451eb9cb7f1e1cb1e6d29daa75e3f135ea3e58a79ccd", - "0x1473a660819bdd838d56122b72b32b267211e9f1103239480ec50fa85c9e1035", - "0x0a8ccaeb22451f391b3fc3467c8e6e900270a7afb7b510e8acf5a4f06f1c0888", - "0x03b3080afc0658cc87e307758cebc171921f43eca159b9dedf7f72aa8dd926bd", - "0x2dd7d6663fa0e1755dfafac352c361fcd64c7f4d53627e3646870ac169cc4a07", - "0x1ec54b883f5f35ccad0e75695af20790d9860104095bab34c9bf01628dd40cb9", - "0x193dff50f83c241f7a9e087a29ce72ecf3f6d8563593f786dcd04c32bcfd4ced", - "0x135122c0dae26cda8ca1c09de8225064ad86d10423ab0aaa53b481aa4626e1d6", - "0x08d5a56cbfab5aeed56d3cdd7fb6b30fc26b0c1a5b63fccd7fa44c53ba6fd35a", - "0x0d12f126dfa2daad3726d00ca339284cc22e36c6d81bb7a4b95c6f9598b60e7c", - "0x2e8b24bbdf2fd839d3c7cae1f0eeb96bfcfaeef30b27476f2fafcb17da78cd5e", - "0x2364acfe0cea39b7f749c5f303b99504977357925f810f684c60f35d16315211", - "0x06ca062eb70b8c51cfac35345e7b6b51f33a8ec9ebe204fb9b4911200bf508b7", - "0x266c0aa1ccb97186815bf69084f600d06ddd934e59a38dfe602ee5d6b9487f22", - "0x1d817537a49c6d0e3b4b65c6665334b91d7593142e60065048be9e55ceb5e7ab", - "0x05e9b7256a368df053c691952b59e9327a7c12ed322bbd6f72c669b9b9c26d49", - "0x05e9b7256a368df053c691952b59e9327a7c12ed322bbd6f72c669b9b9c26d49", - "0x25b77026673a1e613e50df0e88fb510973739d5f9064bd364079a9f884209632", - "0x25c9bc7a3f6aae3d43ff68b5614b34b5eaceff37157b37347995d231784ac1fd", - "0x085f69baef22680ae15f4801ef4361ebe9c7fc24a94b5bc2527dce8fb705439e", - "0x0d7c6b9ce31bfc32238a205455baf5ffe99cd30eb0f7bb5b504e1d4501e01382", - "0x1001a8cc4bc1221c814fba0eddcf3c40619b133373640c600de5bed0a0a05b10", - "0x20f5894be90e52977cb70f4f4cbd5101693db0360848939750db7e91109d54b6", - "0x22c09cb26db43f0599408b4daed0f4f496c66424e6affa41c14387d8e0af851b", - "0x24e5f41357798432426a9549d71e8cc681eaebacbe87f6e3bf38e85de5aa2f3d", - "0x06eb90100c736fbf2b87432d7821ecdc0b365024739bc36363d48b905973f5b9", - "0x000000000000000000000000000000ece6d09ed58e9f5661c01140b10558a8c2", - "0x000000000000000000000000000000000012b6e4f37adcb34b8e88ff8b6eebce", - "0x000000000000000000000000000000b226a2bb93593fa1fab19a44767828a3f5", - "0x00000000000000000000000000000000002b5b518342030543092e1428a7e33c", - "0x00000000000000000000000000000022ba33857034a0574c216eb3c1ddff3025", - "0x00000000000000000000000000000000001918e58df857985a7cf9eae7802165", - "0x00000000000000000000000000000045c2d840b96fb6106cc14dcad89dd5f675", - "0x00000000000000000000000000000000000afdfac1e3a1febdd0208867d44f98", - "0x00000000000000000000000000000042ebed6c5ec45d794f119aef24c192af0f", - "0x00000000000000000000000000000000002d05ef250900bbcc5751bbeb210d6a", - "0x00000000000000000000000000000060d604bdda48eecc90ed065bd9770e1323", - "0x00000000000000000000000000000000001fed91c63d0041660c1cbc84c2ffbb", - "0x00000000000000000000000000000054196b549cde36092e8184c7f4f7d878de", - "0x00000000000000000000000000000000000153f26a01294329922b492485cc31", - "0x00000000000000000000000000000056ebea579d10dbb440f0222931df2c0059", - "0x00000000000000000000000000000000000d2cbc61ce5b7cdd7fce398da4637b", - "0x000000000000000000000000000000e2b9512360b9797d96675d8a2fd2f7aa5d", - "0x000000000000000000000000000000000025742905f105ff895f74e7c3daa34a", - "0x000000000000000000000000000000a2dd7df55db59bd41b83518d4403fbc382", - "0x00000000000000000000000000000000002c1d9c3cbb9371d4cc4e9f900b9a46", - "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", - "0x000000000000000000000000000000bcf12ae40c9425c3e67654b84181f90502", - "0x00000000000000000000000000000000000b6d3faa8a71ff6ef1aa887b7307cf", - "0x0000000000000000000000000000001f6f719acc23b8f84808c0275d61cfb456", - "0x0000000000000000000000000000000000296030933ed0c134457ae71c393dfe", - "0x000000000000000000000000000000ebe1a57cdd7d3d763289b40ef5ed9a7ae0", - "0x000000000000000000000000000000000010f30483e7df51fca2316d3367603c", - "0x0000000000000000000000000000000149b7b283ab18060618c8e051864c03cd", - "0x00000000000000000000000000000000001ef7763235a3a25e241a5f06704dc3", -] -public_inputs = [ - "0x0000000000000000000000000000000000000000000000000000000000000003", -] -verification_key = [ - "0x0000000000000000000000000000000000000000000000000000000000000020", - "0x0000000000000000000000000000000000000000000000000000000000000011", - "0x0000000000000000000000000000000000000000000000000000000000000001", - "0x0000000000000000000000000000000000000000000000000000000000000001", - "0x0000000000000000000000000000000000000000000000000000000000000001", - "0x0000000000000000000000000000000000000000000000000000000000000002", - "0x0000000000000000000000000000000000000000000000000000000000000003", - "0x0000000000000000000000000000000000000000000000000000000000000004", - "0x0000000000000000000000000000000000000000000000000000000000000005", - "0x0000000000000000000000000000000000000000000000000000000000000006", - "0x0000000000000000000000000000000000000000000000000000000000000007", - "0x0000000000000000000000000000000000000000000000000000000000000008", - "0x0000000000000000000000000000000000000000000000000000000000000009", - "0x000000000000000000000000000000000000000000000000000000000000000a", - "0x000000000000000000000000000000000000000000000000000000000000000b", - "0x000000000000000000000000000000000000000000000000000000000000000c", - "0x000000000000000000000000000000000000000000000000000000000000000d", - "0x000000000000000000000000000000000000000000000000000000000000000e", - "0x000000000000000000000000000000000000000000000000000000000000000f", - "0x0000000000000000000000000000000000000000000000000000000000000010", - "0x00000000000000000000000000000060e430ad1c23bfcf3514323aae3f206e84", - "0x00000000000000000000000000000000001b5c3ff4c2458d8f481b1c068f27ae", - "0x000000000000000000000000000000bb510ab2112def34980e4fc6998ad9dd16", - "0x00000000000000000000000000000000000576e7c105b43e061e13cb877fefe1", - "0x000000000000000000000000000000ced074785d11857b065d8199e6669a601c", - "0x00000000000000000000000000000000000053b48a4098c1c0ae268f273952f7", - "0x000000000000000000000000000000d1d4b26e941db8168cee8f6de548ae0fd8", - "0x00000000000000000000000000000000001a9adf5a6dadc3d948bb61dfd63f4c", - "0x0000000000000000000000000000009ce1faac6f8de6ebb18f1db17372c82ad5", - "0x00000000000000000000000000000000002002681bb417184b2df070a16a3858", - "0x000000000000000000000000000000161baa651a8092e0e84725594de5aba511", - "0x00000000000000000000000000000000000be0064399c2a1efff9eb0cdcb2223", - "0x0000000000000000000000000000008673be6fd1bdbe980a29d8c1ded54381e7", - "0x000000000000000000000000000000000008a5158a7d9648cf1d234524c9fa0c", - "0x0000000000000000000000000000002b4fce6e4b1c72062b296d49bca2aa4130", - "0x00000000000000000000000000000000002e45a9eff4b6769e55fb710cded44f", - "0x00000000000000000000000000000072b85bf733758b76bcf97333efb85a23e3", - "0x000000000000000000000000000000000017da0ea508994fc82862715e4b5592", - "0x00000000000000000000000000000094fa74695cf058dba8ff35aec95456c6c3", - "0x0000000000000000000000000000000000211acddb851061c24b8f159e832bd1", - "0x000000000000000000000000000000303b5e5c531384b9a792e11702ad3bcab0", - "0x00000000000000000000000000000000000d336dff51a60b8833d5d7f6d4314c", - "0x0000000000000000000000000000009f825dde88092070747180d581c342444a", - "0x0000000000000000000000000000000000237fbd6511a03cca8cac01b555fe01", - "0x0000000000000000000000000000007c313205159495df6d8de292079a4844ff", - "0x000000000000000000000000000000000018facdfc468530dd45e8f7a1d38ce9", - "0x0000000000000000000000000000000d1ce33446fc3dc4ab40ca38d92dac74e1", - "0x00000000000000000000000000000000000852d8e3e0e8f4435af3e94222688b", - "0x0000000000000000000000000000006c04ee19ec1dfec87ed47d6d04aa158de2", - "0x000000000000000000000000000000000013240f97a584b45184c8ec31319b5f", - "0x000000000000000000000000000000cefb5d240b07ceb4be26ea429b6dc9d9e0", - "0x00000000000000000000000000000000002dad22022121d689f57fb38ca21349", - "0x000000000000000000000000000000c9f189f2a91aeb664ce376d8b157ba98f8", - "0x00000000000000000000000000000000002531a51ad54f124d58094b219818d2", - "0x000000000000000000000000000000ef1e6db71809307f677677e62b4163f556", - "0x0000000000000000000000000000000000272da4396fb2a7ee0638b9140e523d", - "0x0000000000000000000000000000002e54c0244a7732c87bc4712a76dd8c83fb", - "0x000000000000000000000000000000000007db77b3e04b7eba9643da57cbbe4d", - "0x000000000000000000000000000000e0dfe1ddd7f74ae0d636c910c3e85830d8", - "0x00000000000000000000000000000000000466fa9b57ec4664abd1505b490862", - "0x0000000000000000000000000000009ee55ae8a32fe5384c79907067cc27192e", - "0x00000000000000000000000000000000000799d0e465cec07ecb5238c854e830", - "0x0000000000000000000000000000001d5910ad361e76e1c241247a823733c39f", - "0x00000000000000000000000000000000002b03f2ccf7507564da2e6678bef8fe", - "0x000000000000000000000000000000231147211b3c75e1f47d150e4bbd2fb22e", - "0x00000000000000000000000000000000000d19ee104a10d3c701cfd87473cbbe", - "0x0000000000000000000000000000006705f3f382637d00f698e2c5c94ed05ae9", - "0x00000000000000000000000000000000000b9c792da28bb60601dd7ce4b74e68", - "0x000000000000000000000000000000ac5acc8cc21e4ddb225c510670f80c80b3", - "0x00000000000000000000000000000000002da9d3fa57343e6998aba19429b9fa", - "0x0000000000000000000000000000004bacbf54b7c17a560df0af18b6d0d527be", - "0x00000000000000000000000000000000000faea33aeca2025b22c288964b21eb", - "0x000000000000000000000000000000492e756298d68d6e95de096055cc0336c3", - "0x00000000000000000000000000000000001a12a12f004859e5a3675c7315121b", - "0x000000000000000000000000000000893d521d512f30e6d32afbbc0cecd8ee00", - "0x00000000000000000000000000000000001674b3c1ef12c6da690631e0d86c04", - "0x000000000000000000000000000000aa6cb02a52e7a613873d4ac9b411349945", - "0x00000000000000000000000000000000001ecb1fe9c493add46751f9940f73e1", - "0x00000000000000000000000000000045b3d362ca82cba69fb2b9c733a5b8c351", - "0x000000000000000000000000000000000019a683586af466e331945b732d2f8c", - "0x000000000000000000000000000000fc79b052dfdfe67c0ecfc06b4267ffd694", - "0x00000000000000000000000000000000001336a70c396393038d5e9913744ac2", - "0x0000000000000000000000000000005450d29af1e9438e91cd33ddeb2548226e", - "0x000000000000000000000000000000000000993a602891cfd0e6f6ecf7404933", - "0x000000000000000000000000000000498efddab90a32e9b2db729ed6e9b40192", - "0x00000000000000000000000000000000002425efebe9628c63ca6fc28bdb5901", - "0x000000000000000000000000000000d8488157f875a21ab5f93f1c2b641f3de9", - "0x0000000000000000000000000000000000290f95ada3936604dc4b14df7504e3", - "0x0000000000000000000000000000005d6902187f3ed60dcce06fca211b40329a", - "0x00000000000000000000000000000000002b5870a6ba0b20aaa0178e5adfbc36", - "0x000000000000000000000000000000e5c2519171fa0e548fc3c4966ffc1ce570", - "0x00000000000000000000000000000000001cb8d8f4793b7debbdc429389dbf2d", - "0x000000000000000000000000000000a3ee22dd60456277b86c32a18982dcb185", - "0x00000000000000000000000000000000002493c99a3d068b03f8f2b8d28b57ce", - "0x000000000000000000000000000000f6c3731486320082c20ec71bbdc92196c1", - "0x00000000000000000000000000000000001ded39c4c8366469843cd63f09ecac", - "0x000000000000000000000000000000494997477ab161763e46601d95844837ef", - "0x00000000000000000000000000000000002e0cddbc5712d79b59cb3b41ebbcdd", - "0x000000000000000000000000000000426db4c64531d350750df62dbbc41a1bd9", - "0x0000000000000000000000000000000000303126892f664d8d505964d14315ec", - "0x00000000000000000000000000000076a6b2c6040c0c62bd59acfe3e3e125672", - "0x000000000000000000000000000000000000874a5ad262eecc6b565e0b085074", - "0x000000000000000000000000000000ef082fb517183c9c6841c2b8ef2ca1df04", - "0x0000000000000000000000000000000000127b2a745a1b74968c3edc18982b9b", - "0x000000000000000000000000000000c9efd4f8c3d56e1eb23d789a8f710d5be6", - "0x000000000000000000000000000000000015a18748490ff4c2b1871081954e86", - "0x000000000000000000000000000000a0011ef987dc016ab110eacd554a1d8bbf", - "0x00000000000000000000000000000000002097c84955059442a95df075833071", - "0x000000000000000000000000000000d38e9426ad3085b68b00a93c17897c2877", - "0x00000000000000000000000000000000002aecd48089890ea0798eb952c66824", - "0x00000000000000000000000000000078d8a9ce405ce559f441f2e71477ff3ddb", - "0x00000000000000000000000000000000001216bdb2f0d961bb8a7a23331d2150", - "0x0000000000000000000000000000000000000000000000000000000000000001", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000002", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x000000000000000000000000000000ee40d90bea71fba7a412dd61fcf34e8ceb", - "0x0000000000000000000000000000000000140b0936c323fd2471155617b6af56", - "0x0000000000000000000000000000002b90071823185c5ff8e440fd3d73b6fefc", - "0x00000000000000000000000000000000002b6c10790a5f6631c87d652e059df4", -] 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 17adc68c056e..000000000000 --- a/noir/noir-repo/test_programs/execution_success/verify_honk_proof/src/main.nr +++ /dev/null @@ -1,16 +0,0 @@ - -// This circuit aggregates a single Honk proof from `assert_statement_recursive`. -global SIZE_OF_PROOF_IF_LOGN_IS_28 : u32 = 409; -fn main( - verification_key: [Field; 120], - // 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, proof, public_inputs, key_hash); -} diff --git a/noir/noir-repo/test_programs/gates_report_brillig.sh b/noir/noir-repo/test_programs/gates_report_brillig.sh new file mode 100644 index 000000000000..d3f6344dbf47 --- /dev/null +++ b/noir/noir-repo/test_programs/gates_report_brillig.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -e + +# These tests are incompatible with gas reporting +excluded_dirs=("workspace" "workspace_default_member" "double_verify_nested_proof" "overlapping_dep_and_mod" "comptime_println") + +current_dir=$(pwd) +base_path="$current_dir/execution_success" +test_dirs=$(ls $base_path) + +# We generate a Noir workspace which contains all of the test cases +# This allows us to generate a gates report using `nargo info` for all of them at once. + +echo "[workspace]" > Nargo.toml +echo "members = [" >> Nargo.toml + +for dir in $test_dirs; do + if [[ " ${excluded_dirs[@]} " =~ " ${dir} " ]]; then + continue + fi + + if [[ ${CI-false} = "true" ]] && [[ " ${ci_excluded_dirs[@]} " =~ " ${dir} " ]]; then + continue + fi + + echo " \"execution_success/$dir\"," >> Nargo.toml +done + +echo "]" >> Nargo.toml + +nargo info --force-brillig --json > gates_report_brillig.json + +rm Nargo.toml diff --git a/noir/noir-repo/test_programs/noir_test_success/mock_oracle/src/main.nr b/noir/noir-repo/test_programs/noir_test_success/mock_oracle/src/main.nr index 4d3dd8d030b1..1b427043e917 100644 --- a/noir/noir-repo/test_programs/noir_test_success/mock_oracle/src/main.nr +++ b/noir/noir-repo/test_programs/noir_test_success/mock_oracle/src/main.nr @@ -34,68 +34,84 @@ unconstrained fn struct_field(point: Point, array: [Field; 4]) -> Field { #[test(should_fail)] fn test_mock_no_returns() { - OracleMock::mock("void_field"); - void_field(); // Some return value must be set + unsafe { + OracleMock::mock("void_field"); + void_field(); // Some return value must be set + } } #[test] fn test_mock() { - OracleMock::mock("void_field").returns(10); - assert_eq(void_field(), 10); + unsafe { + OracleMock::mock("void_field").returns(10); + assert_eq(void_field(), 10); + } } #[test] fn test_multiple_mock() { - let first_mock = OracleMock::mock("void_field").returns(10); - OracleMock::mock("void_field").returns(42); + unsafe { + let first_mock = OracleMock::mock("void_field").returns(10); + OracleMock::mock("void_field").returns(42); - // The mocks are searched for in creation order, so the first one prevents the second from being called. - assert_eq(void_field(), 10); + // The mocks are searched for in creation order, so the first one prevents the second from being called. + assert_eq(void_field(), 10); - first_mock.clear(); - assert_eq(void_field(), 42); + first_mock.clear(); + assert_eq(void_field(), 42); + } } #[test] fn test_multiple_mock_times() { - OracleMock::mock("void_field").returns(10).times(2); - OracleMock::mock("void_field").returns(42); + unsafe { + OracleMock::mock("void_field").returns(10).times(2); + OracleMock::mock("void_field").returns(42); - assert_eq(void_field(), 10); - assert_eq(void_field(), 10); - assert_eq(void_field(), 42); + assert_eq(void_field(), 10); + assert_eq(void_field(), 10); + assert_eq(void_field(), 42); + } } #[test] fn test_mock_with_params() { - OracleMock::mock("field_field").with_params((5,)).returns(10); - assert_eq(field_field(5), 10); + unsafe { + OracleMock::mock("field_field").with_params((5,)).returns(10); + assert_eq(field_field(5), 10); + } } #[test] fn test_multiple_mock_with_params() { - OracleMock::mock("field_field").with_params((5,)).returns(10); - OracleMock::mock("field_field").with_params((7,)).returns(14); + unsafe { + OracleMock::mock("field_field").with_params((5,)).returns(10); + OracleMock::mock("field_field").with_params((7,)).returns(14); - assert_eq(field_field(5), 10); - assert_eq(field_field(7), 14); + assert_eq(field_field(5), 10); + assert_eq(field_field(7), 14); + } } #[test] fn test_mock_last_params() { - let mock = OracleMock::mock("field_field").returns(10); - assert_eq(field_field(5), 10); + unsafe { + let mock = OracleMock::mock("field_field").returns(10); + assert_eq(field_field(5), 10); - assert_eq(mock.get_last_params(), 5); + assert_eq(mock.get_last_params(), 5); + } } #[test] fn test_mock_last_params_many_calls() { - let mock = OracleMock::mock("field_field").returns(10); - assert_eq(field_field(5), 10); - assert_eq(field_field(7), 10); + unsafe { + let mock = OracleMock::mock("field_field").returns(10); + assert_eq(field_field(5), 10); + assert_eq(field_field(7), 10); - assert_eq(mock.get_last_params(), 7); + assert_eq(mock.get_last_params(), 7); + } } #[test] @@ -106,25 +122,25 @@ fn test_mock_struct_field() { let another_array = [4, 3, 2, 1]; let point = Point { x: 14, y: 27 }; - OracleMock::mock("struct_field").returns(42).times(2); - let timeless_mock = OracleMock::mock("struct_field").returns(0); - - assert_eq(42, struct_field(point, array)); - assert_eq(42, struct_field(point, array)); - // The times(2) mock is now cleared - - assert_eq(0, struct_field(point, array)); - - let last_params: (Point, [Field; 4]) = timeless_mock.get_last_params(); - assert_eq(last_params.0, point); - assert_eq(last_params.1, array); - - // We clear the mock with no times() to allow other mocks to be callable - timeless_mock.clear(); - - OracleMock::mock("struct_field").with_params((point, array)).returns(10); - OracleMock::mock("struct_field").with_params((point, another_array)).returns(20); - assert_eq(10, struct_field(point, array)); - assert_eq(20, struct_field(point, another_array)); + unsafe { + OracleMock::mock("struct_field").returns(42).times(2); + let timeless_mock = OracleMock::mock("struct_field").returns(0); + assert_eq(42, struct_field(point, array)); + assert_eq(42, struct_field(point, array)); + // The times(2) mock is now cleared + + assert_eq(0, struct_field(point, array)); + let last_params: (Point, [Field; 4]) = timeless_mock.get_last_params(); + assert_eq(last_params.0, point); + assert_eq(last_params.1, array); + + // We clear the mock with no times() to allow other mocks to be callable + timeless_mock.clear(); + + OracleMock::mock("struct_field").with_params((point, array)).returns(10); + OracleMock::mock("struct_field").with_params((point, another_array)).returns(20); + assert_eq(10, struct_field(point, array)); + assert_eq(20, struct_field(point, another_array)); + } } diff --git a/noir/noir-repo/test_programs/noir_test_success/out_of_bounds_alignment/src/main.nr b/noir/noir-repo/test_programs/noir_test_success/out_of_bounds_alignment/src/main.nr index a47ab37eb312..fb90e3d96c5a 100644 --- a/noir/noir-repo/test_programs/noir_test_success/out_of_bounds_alignment/src/main.nr +++ b/noir/noir-repo/test_programs/noir_test_success/out_of_bounds_alignment/src/main.nr @@ -13,5 +13,7 @@ fn test_acir() { #[test(should_fail)] fn test_brillig() { - assert_eq(out_of_bounds_unconstrained_wrapper([0; 50], [0; 50]), 0); + unsafe { + assert_eq(out_of_bounds_unconstrained_wrapper([0; 50], [0; 50]), 0); + } } diff --git a/noir/noir-repo/test_programs/noir_test_success/regression_4561/src/main.nr b/noir/noir-repo/test_programs/noir_test_success/regression_4561/src/main.nr index ad40941ff51c..6a8b4bd61fb0 100644 --- a/noir/noir-repo/test_programs/noir_test_success/regression_4561/src/main.nr +++ b/noir/noir-repo/test_programs/noir_test_success/regression_4561/src/main.nr @@ -12,7 +12,7 @@ unconstrained fn simple_nested_return_unconstrained() -> TReturn { } #[test] -fn test_simple_nested_return() { +unconstrained fn test_simple_nested_return() { OracleMock::mock("simple_nested_return").returns([1, 2, 3, 4, 5, 6]); assert_eq(simple_nested_return_unconstrained(), [[1, 2, 3], [4, 5, 6]]); } @@ -25,7 +25,7 @@ unconstrained fn nested_with_fields_return_unconstrained() -> (Field, TReturn, F } #[test] -fn test_nested_with_fields_return() { +unconstrained fn test_nested_with_fields_return() { OracleMock::mock("nested_with_fields_return").returns((0, [1, 2, 3, 4, 5, 6], 7)); assert_eq(nested_with_fields_return_unconstrained(), (0, [[1, 2, 3], [4, 5, 6]], 7)); } @@ -38,7 +38,7 @@ unconstrained fn two_nested_return_unconstrained() -> (Field, TReturn, Field, TR } #[test] -fn two_nested_return() { +unconstrained fn two_nested_return() { OracleMock::mock("two_nested_return").returns((0, [1, 2, 3, 4, 5, 6], 7, [1, 2, 3, 4, 5, 6])); assert_eq(two_nested_return_unconstrained(), (0, [[1, 2, 3], [4, 5, 6]], 7, [[1, 2, 3], [4, 5, 6]])); } @@ -57,7 +57,7 @@ struct TestTypeFoo { } #[test] -fn complexe_struct_return() { +unconstrained fn complexe_struct_return() { OracleMock::mock("foo_return").returns( ( 0, [1, 2, 3, 4, 5, 6], 7, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], [1, 2, 3, 4, 5, 6] diff --git a/noir/noir-repo/tooling/acvm_cli/Cargo.toml b/noir/noir-repo/tooling/acvm_cli/Cargo.toml index a592f2d65f3f..06dd5e676bd1 100644 --- a/noir/noir-repo/tooling/acvm_cli/Cargo.toml +++ b/noir/noir-repo/tooling/acvm_cli/Cargo.toml @@ -10,6 +10,9 @@ license.workspace = true rust-version.workspace = true repository.workspace = true +[lints] +workspace = true + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # Rename binary from `acvm_cli` to `acvm` diff --git a/noir/noir-repo/tooling/debugger/Cargo.toml b/noir/noir-repo/tooling/debugger/Cargo.toml index 540d6d11bc04..b9b83d86836b 100644 --- a/noir/noir-repo/tooling/debugger/Cargo.toml +++ b/noir/noir-repo/tooling/debugger/Cargo.toml @@ -7,6 +7,9 @@ edition.workspace = true rust-version.workspace = true license.workspace = true +[lints] +workspace = true + [build-dependencies] build-data.workspace = true diff --git a/noir/noir-repo/tooling/debugger/src/context.rs b/noir/noir-repo/tooling/debugger/src/context.rs index 6ec1aff83258..890732b579cb 100644 --- a/noir/noir-repo/tooling/debugger/src/context.rs +++ b/noir/noir-repo/tooling/debugger/src/context.rs @@ -566,7 +566,7 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { &mut self, call_info: AcirCallWaitInfo, ) -> DebugCommandResult { - let callee_circuit = &self.circuits[call_info.id as usize]; + let callee_circuit = &self.circuits[call_info.id.as_usize()]; let callee_witness_map = call_info.initial_witness; let callee_acvm = ACVM::new( self.backend, @@ -578,7 +578,7 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { let caller_acvm = std::mem::replace(&mut self.acvm, callee_acvm); self.acvm_stack .push(ExecutionFrame { circuit_id: self.current_circuit_id, acvm: caller_acvm }); - self.current_circuit_id = call_info.id; + self.current_circuit_id = call_info.id.0; // Explicitly handling the new ACVM status here handles two edge cases: // 1. there is a breakpoint set at the beginning of a circuit @@ -596,7 +596,7 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { let ACVMStatus::RequiresAcirCall(call_info) = self.acvm.get_status() else { unreachable!("Resolving an ACIR call, the caller is in an invalid state"); }; - let acir_to_call = &self.circuits[call_info.id as usize]; + let acir_to_call = &self.circuits[call_info.id.as_usize()]; let mut call_resolved_outputs = Vec::new(); for return_witness_index in acir_to_call.return_values.indices() { @@ -946,7 +946,7 @@ mod tests { brillig::IntegerBitSize, circuit::{ brillig::{BrilligFunctionId, BrilligInputs, BrilligOutputs}, - opcodes::{BlockId, BlockType}, + opcodes::{AcirFunctionId, BlockId, BlockType}, }, native_types::Expression, AcirField, @@ -1210,7 +1210,12 @@ mod tests { outputs: vec![], predicate: None, }, - Opcode::Call { id: 1, inputs: vec![], outputs: vec![], predicate: None }, + Opcode::Call { + id: AcirFunctionId(1), + inputs: vec![], + outputs: vec![], + predicate: None, + }, Opcode::AssertZero(Expression::default()), ], ..Circuit::default() diff --git a/noir/noir-repo/tooling/fuzzer/Cargo.toml b/noir/noir-repo/tooling/fuzzer/Cargo.toml index 106d8abead12..317019918443 100644 --- a/noir/noir-repo/tooling/fuzzer/Cargo.toml +++ b/noir/noir-repo/tooling/fuzzer/Cargo.toml @@ -7,6 +7,9 @@ edition.workspace = true rust-version.workspace = true license.workspace = true +[lints] +workspace = true + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] diff --git a/noir/noir-repo/tooling/lsp/Cargo.toml b/noir/noir-repo/tooling/lsp/Cargo.toml index 03c6c9105bac..353a6ade904c 100644 --- a/noir/noir-repo/tooling/lsp/Cargo.toml +++ b/noir/noir-repo/tooling/lsp/Cargo.toml @@ -7,6 +7,9 @@ edition.workspace = true# rust-version.workspace = true license.workspace = true +[lints] +workspace = true + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] @@ -30,6 +33,7 @@ thiserror.workspace = true fm.workspace = true rayon = "1.8.0" fxhash.workspace = true +convert_case = "0.6.0" [target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dependencies] wasm-bindgen.workspace = true diff --git a/noir/noir-repo/tooling/lsp/src/lib.rs b/noir/noir-repo/tooling/lsp/src/lib.rs index ca34d7686fd6..366b158ad89b 100644 --- a/noir/noir-repo/tooling/lsp/src/lib.rs +++ b/noir/noir-repo/tooling/lsp/src/lib.rs @@ -23,14 +23,14 @@ use fxhash::FxHashSet; use lsp_types::{ request::{ Completion, DocumentSymbolRequest, HoverRequest, InlayHintRequest, PrepareRenameRequest, - References, Rename, + References, Rename, SignatureHelpRequest, }, CodeLens, }; use nargo::{ package::{Package, PackageType}, parse_all, - workspace::{self, Workspace}, + workspace::Workspace, }; use nargo_toml::{find_file_manifest, resolve_workspace_from_toml, PackageSelection}; use noirc_driver::{file_manager_with_stdlib, prepare_crate, NOIR_ARTIFACT_VERSION_STRING}; @@ -55,7 +55,7 @@ use requests::{ 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_signature_help_request, on_test_run_request, on_tests_request, LspInitializationOptions, }; use serde_json::Value as JsonValue; use thiserror::Error; @@ -143,6 +143,7 @@ impl NargoLspService { .request::(on_hover_request) .request::(on_inlay_hint_request) .request::(on_completion_request) + .request::(on_signature_help_request) .notification::(on_initialized) .notification::(on_did_change_configuration) .notification::(on_did_open_text_document) @@ -383,7 +384,7 @@ fn parse_diff(file_manager: &FileManager, state: &mut LspState) -> ParsedFiles { pub fn insert_all_files_for_workspace_into_file_manager( state: &LspState, - workspace: &workspace::Workspace, + workspace: &Workspace, file_manager: &mut FileManager, ) { // First add files we cached: these have the source code of files that are modified @@ -410,7 +411,7 @@ fn prepare_package_from_source_string() { let client = ClientSocket::new_closed(); let mut state = LspState::new(&client, acvm::blackbox_solver::StubbedBlackBoxSolver); - let (mut context, crate_id) = crate::prepare_source(source.to_string(), &mut state); + let (mut context, crate_id) = prepare_source(source.to_string(), &mut state); 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 3a60de15c4a0..4d2186badc3c 100644 --- a/noir/noir-repo/tooling/lsp/src/notifications/mod.rs +++ b/noir/noir-repo/tooling/lsp/src/notifications/mod.rs @@ -223,7 +223,7 @@ mod notification_tests { use super::*; use lsp_types::{ - InlayHintLabel, InlayHintParams, Position, TextDocumentContentChangeEvent, + InlayHintLabel, InlayHintParams, Position, Range, TextDocumentContentChangeEvent, TextDocumentIdentifier, TextDocumentItem, VersionedTextDocumentIdentifier, WorkDoneProgressParams, }; @@ -270,9 +270,9 @@ mod notification_tests { InlayHintParams { work_done_progress_params: WorkDoneProgressParams { work_done_token: None }, text_document: TextDocumentIdentifier { uri: noir_text_document }, - range: lsp_types::Range { - start: lsp_types::Position { line: 0, character: 0 }, - end: lsp_types::Position { line: 1, character: 0 }, + range: Range { + start: Position { line: 0, character: 0 }, + end: Position { line: 1, character: 0 }, }, }, ) diff --git a/noir/noir-repo/tooling/lsp/src/requests/completion.rs b/noir/noir-repo/tooling/lsp/src/requests/completion.rs index 48616c0f52d5..387006f7daec 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/completion.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/completion.rs @@ -4,74 +4,47 @@ use std::{ }; 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 completion_items::{ + crate_completion_item, field_completion_item, simple_completion_item, + struct_field_completion_item, }; +use convert_case::{Case, Casing}; +use fm::{FileId, FileMap, PathString}; +use kinds::{FunctionCompletionKind, FunctionKind, ModuleCompletionKind, RequestedItems}; +use lsp_types::{CompletionItem, CompletionItemKind, CompletionParams, CompletionResponse}; 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, + AsTraitPath, BlockExpression, ConstructorExpression, Expression, ExpressionKind, + ForLoopStatement, Ident, IfExpression, LValue, Lambda, LetStatement, + MemberAccessExpression, NoirFunction, NoirStruct, NoirTraitImpl, Path, PathKind, + PathSegment, Pattern, Statement, StatementKind, TraitItem, TypeImpl, UnresolvedGeneric, + UnresolvedGenerics, UnresolvedType, UnresolvedTypeData, 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}, + hir_def::traits::Trait, + macros_api::{ModuleDefId, NodeInterner}, + node_interner::ReferenceId, parser::{Item, ItemKind}, - token::Keyword, - ParsedModule, Type, + ParsedModule, StructType, Type, }; -use strum::IntoEnumIterator; +use sort_text::underscore_sort_text; -use crate::{utils, LspState}; +use crate::{requests::to_lsp_location, utils, LspState}; use super::process_request; +mod auto_import; 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, -} +mod completion_items; +mod kinds; +mod sort_text; +mod tests; +mod traversal; pub(crate) fn on_completion_request( state: &mut LspState, @@ -94,7 +67,9 @@ pub(crate) fn on_completion_request( let (parsed_module, _errors) = noirc_frontend::parse_program(source); let mut finder = NodeFinder::new( + args.files, file_id, + source, byte_index, byte, args.crate_id, @@ -110,7 +85,9 @@ pub(crate) fn on_completion_request( } struct NodeFinder<'a> { + files: &'a FileMap, file: FileId, + lines: Vec<&'a str>, byte_index: usize, byte: Option, /// The module ID of the current file. @@ -129,11 +106,20 @@ struct NodeFinder<'a> { /// Type parameters in the current scope. These are collected when entering /// a struct, a function, etc., and cleared afterwards. type_parameters: HashSet, + /// ModuleDefIds we already suggested, so we don't offer these for auto-import. + suggested_module_def_ids: HashSet, + /// How many nested `mod` we are in deep + nesting: usize, + /// The line where an auto_import must be inserted + auto_import_line: usize, } impl<'a> NodeFinder<'a> { + #[allow(clippy::too_many_arguments)] fn new( + files: &'a FileMap, file: FileId, + source: &'a str, byte_index: usize, byte: Option, krate: CrateId, @@ -153,7 +139,9 @@ impl<'a> NodeFinder<'a> { }; let module_id = ModuleId { krate, local_id }; Self { + files, file, + lines: source.lines().collect(), byte_index, byte, root_module_id, @@ -164,6 +152,9 @@ impl<'a> NodeFinder<'a> { completion_items: Vec::new(), local_variables: HashMap::new(), type_parameters: HashSet::new(), + suggested_module_def_ids: HashSet::new(), + nesting: 0, + auto_import_line: 0, } } @@ -173,17 +164,26 @@ impl<'a> NodeFinder<'a> { if self.completion_items.is_empty() { None } else { - Some(CompletionResponse::Array(std::mem::take(&mut self.completion_items))) - } - } + let mut items = std::mem::take(&mut self.completion_items); + + // Show items that start with underscore last in the list + for item in items.iter_mut() { + if item.label.starts_with('_') { + item.sort_text = Some(underscore_sort_text()); + } + } - fn find_in_parsed_module(&mut self, parsed_module: &ParsedModule) { - for item in &parsed_module.items { - self.find_in_item(item); + Some(CompletionResponse::Array(items)) } } fn find_in_item(&mut self, item: &Item) { + if let ItemKind::Import(..) = &item.kind { + if let Some(lsp_location) = to_lsp_location(self.files, self.file, item.span) { + self.auto_import_line = (lsp_location.range.end.line + 1) as usize; + } + } + if !self.includes_span(item.span) { return; } @@ -206,10 +206,19 @@ impl<'a> NodeFinder<'a> { ModuleId { krate: self.module_id.krate, local_id: *child_module }; } + let old_auto_import_line = self.auto_import_line; + self.nesting += 1; + + if let Some(lsp_location) = to_lsp_location(self.files, self.file, item.span) { + self.auto_import_line = (lsp_location.range.start.line + 1) as usize; + } + self.find_in_parsed_module(&parsed_sub_module.contents); // Restore the old module before continuing self.module_id = previous_module_id; + self.nesting -= 1; + self.auto_import_line = old_auto_import_line; } 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), @@ -243,6 +252,9 @@ impl<'a> NodeFinder<'a> { } fn find_in_noir_trait_impl(&mut self, noir_trait_impl: &NoirTraitImpl) { + self.find_in_path(&noir_trait_impl.trait_name, RequestedItems::OnlyTypes); + self.find_in_unresolved_type(&noir_trait_impl.object_type); + self.type_parameters.clear(); self.collect_type_parameters_in_generics(&noir_trait_impl.impl_generics); @@ -253,15 +265,9 @@ impl<'a> NodeFinder<'a> { 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.find_in_unresolved_type(&type_impl.object_type); + self.type_parameters.clear(); self.collect_type_parameters_in_generics(&type_impl.generics); @@ -277,10 +283,6 @@ impl<'a> NodeFinder<'a> { 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); @@ -292,12 +294,6 @@ impl<'a> NodeFinder<'a> { 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 { @@ -357,22 +353,22 @@ impl<'a> NodeFinder<'a> { fn find_in_statement(&mut self, statement: &Statement) { match &statement.kind { - noirc_frontend::ast::StatementKind::Let(let_statement) => { + StatementKind::Let(let_statement) => { self.find_in_let_statement(let_statement, true); } - noirc_frontend::ast::StatementKind::Constrain(constrain_statement) => { + StatementKind::Constrain(constrain_statement) => { self.find_in_constrain_statement(constrain_statement); } - noirc_frontend::ast::StatementKind::Expression(expression) => { + StatementKind::Expression(expression) => { self.find_in_expression(expression); } - noirc_frontend::ast::StatementKind::Assign(assign_statement) => { + StatementKind::Assign(assign_statement) => { self.find_in_assign_statement(assign_statement); } - noirc_frontend::ast::StatementKind::For(for_loop_statement) => { + StatementKind::For(for_loop_statement) => { self.find_in_for_loop_statement(for_loop_statement); } - noirc_frontend::ast::StatementKind::Comptime(statement) => { + 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(); @@ -381,12 +377,10 @@ impl<'a> NodeFinder<'a> { self.local_variables = old_local_variables; } - noirc_frontend::ast::StatementKind::Semi(expression) => { + StatementKind::Semi(expression) => { self.find_in_expression(expression); } - noirc_frontend::ast::StatementKind::Break - | noirc_frontend::ast::StatementKind::Continue - | noirc_frontend::ast::StatementKind::Error => (), + StatementKind::Break | StatementKind::Continue | StatementKind::Error => (), } } @@ -403,22 +397,6 @@ impl<'a> NodeFinder<'a> { } } - 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; @@ -432,7 +410,18 @@ impl<'a> NodeFinder<'a> { fn find_in_lvalue(&mut self, lvalue: &LValue) { match lvalue { - LValue::Ident(_) => (), + LValue::Ident(ident) => { + if self.byte == Some(b'.') && ident.span().end() as usize == self.byte_index - 1 { + let location = Location::new(ident.span(), self.file); + if let Some(ReferenceId::Local(definition_id)) = + self.interner.find_referenced(location) + { + let typ = self.interner.definition_type(definition_id); + let prefix = ""; + self.complete_type_fields_and_methods(&typ, prefix); + } + } + } LValue::MemberAccess { object, field_name: _, span: _ } => self.find_in_lvalue(object), LValue::Index { array, index, span: _ } => { self.find_in_lvalue(array); @@ -442,69 +431,53 @@ impl<'a> NodeFinder<'a> { } } - 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) => { + ExpressionKind::Literal(literal) => self.find_in_literal(literal), + ExpressionKind::Block(block_expression) => { self.find_in_block_expression(block_expression); } - noirc_frontend::ast::ExpressionKind::Prefix(prefix_expression) => { + ExpressionKind::Prefix(prefix_expression) => { self.find_in_expression(&prefix_expression.rhs); } - noirc_frontend::ast::ExpressionKind::Index(index_expression) => { + ExpressionKind::Index(index_expression) => { self.find_in_index_expression(index_expression); } - noirc_frontend::ast::ExpressionKind::Call(call_expression) => { + ExpressionKind::Call(call_expression) => { self.find_in_call_expression(call_expression); } - noirc_frontend::ast::ExpressionKind::MethodCall(method_call_expression) => { + ExpressionKind::MethodCall(method_call_expression) => { self.find_in_method_call_expression(method_call_expression); } - noirc_frontend::ast::ExpressionKind::Constructor(constructor_expression) => { + ExpressionKind::Constructor(constructor_expression) => { self.find_in_constructor_expression(constructor_expression); } - noirc_frontend::ast::ExpressionKind::MemberAccess(member_access_expression) => { + ExpressionKind::MemberAccess(member_access_expression) => { self.find_in_member_access_expression(member_access_expression); } - noirc_frontend::ast::ExpressionKind::Cast(cast_expression) => { + ExpressionKind::Cast(cast_expression) => { self.find_in_cast_expression(cast_expression); } - noirc_frontend::ast::ExpressionKind::Infix(infix_expression) => { + ExpressionKind::Infix(infix_expression) => { self.find_in_infix_expression(infix_expression); } - noirc_frontend::ast::ExpressionKind::If(if_expression) => { + ExpressionKind::If(if_expression) => { self.find_in_if_expression(if_expression); } - noirc_frontend::ast::ExpressionKind::Variable(path) => { + ExpressionKind::Variable(path) => { self.find_in_path(path, RequestedItems::AnyItems); } - noirc_frontend::ast::ExpressionKind::Tuple(expressions) => { + 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) => { + ExpressionKind::Lambda(lambda) => self.find_in_lambda(lambda), + ExpressionKind::Parenthesized(expression) => { self.find_in_expression(expression); } - noirc_frontend::ast::ExpressionKind::Unquote(expression) => { + ExpressionKind::Unquote(expression) => { self.find_in_expression(expression); } - noirc_frontend::ast::ExpressionKind::Comptime(block_expression, _) => { + 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(); @@ -513,58 +486,75 @@ impl<'a> NodeFinder<'a> { self.local_variables = old_local_variables; } - noirc_frontend::ast::ExpressionKind::AsTraitPath(as_trait_path) => { + ExpressionKind::Unsafe(block_expression, _) => { + self.find_in_block_expression(block_expression); + } + 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 => (), + ExpressionKind::Quote(_) | ExpressionKind::Resolved(_) | 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 => (), + // "foo." (no identifier afterwards) is parsed as the expression on the left hand-side of the dot. + // Here we check if there's a dot at the completion position, and if the expression + // ends right before the dot. If so, it means we want to complete the expression's type fields and methods. + // We only do this after visiting nested expressions, because in an expression like `foo & bar.` we want + // to complete for `bar`, not for `foo & bar`. + if self.completion_items.is_empty() + && self.byte == Some(b'.') + && expression.span.end() as usize == self.byte_index - 1 + { + let location = Location::new(expression.span, self.file); + if let Some(typ) = self.interner.type_at_location(location) { + let typ = typ.follow_bindings(); + let prefix = ""; + self.complete_type_fields_and_methods(&typ, prefix); + } } } - 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_constructor_expression(&mut self, constructor_expression: &ConstructorExpression) { + self.find_in_path(&constructor_expression.type_name, RequestedItems::OnlyTypes); + + // Check if we need to autocomplete the field name + if constructor_expression + .fields + .iter() + .any(|(field_name, _)| field_name.span().end() as usize == self.byte_index) + { + self.complete_constructor_field_name(constructor_expression); + return; } - } - fn find_in_index_expression(&mut self, index_expression: &IndexExpression) { - self.find_in_expression(&index_expression.collection); - self.find_in_expression(&index_expression.index); + for (_field_name, expression) in &constructor_expression.fields { + self.find_in_expression(expression); + } } - 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 complete_constructor_field_name(&mut self, constructor_expression: &ConstructorExpression) { + let location = + Location::new(constructor_expression.type_name.last_ident().span(), self.file); + let Some(ReferenceId::Struct(struct_id)) = self.interner.find_referenced(location) else { + return; + }; - 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); - } + let struct_type = self.interner.get_struct(struct_id); + let struct_type = struct_type.borrow(); - fn find_in_constructor_expression(&mut self, constructor_expression: &ConstructorExpression) { - self.find_in_path(&constructor_expression.type_name, RequestedItems::OnlyTypes); + // First get all of the struct's fields + let mut fields = HashMap::new(); + let fields_as_written = struct_type.get_fields_as_written(); + for (field, typ) in &fields_as_written { + fields.insert(field, typ); + } - for (_field_name, expression) in &constructor_expression.fields { - self.find_in_expression(expression); + // Remove the ones that already exists in the constructor + for (field, _) in &constructor_expression.fields { + fields.remove(&field.0.contents); + } + + for (field, typ) in fields { + self.completion_items.push(struct_field_completion_item(field, typ)); } } @@ -572,16 +562,20 @@ impl<'a> NodeFinder<'a> { &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); - } + let ident = &member_access_expression.rhs; + + if self.byte_index == ident.span().end() as usize { + // Assuming member_access_expression is of the form `foo.bar`, we are right after `bar` + let location = Location::new(member_access_expression.lhs.span, self.file); + if let Some(typ) = self.interner.type_at_location(location) { + let typ = typ.follow_bindings(); + let prefix = ident.to_string().to_case(Case::Snake); + self.complete_type_fields_and_methods(&typ, &prefix); + return; + } + } - fn find_in_infix_expression(&mut self, infix_expression: &InfixExpression) { - self.find_in_expression(&infix_expression.lhs); - self.find_in_expression(&infix_expression.rhs); + self.find_in_expression(&member_access_expression.lhs); } fn find_in_if_expression(&mut self, if_expression: &IfExpression) { @@ -617,21 +611,6 @@ impl<'a> NodeFinder<'a> { 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) { @@ -640,48 +619,48 @@ impl<'a> NodeFinder<'a> { } match &unresolved_type.typ { - noirc_frontend::ast::UnresolvedTypeData::Array(_, unresolved_type) => { + UnresolvedTypeData::Array(_, unresolved_type) => { self.find_in_unresolved_type(unresolved_type); } - noirc_frontend::ast::UnresolvedTypeData::Slice(unresolved_type) => { + UnresolvedTypeData::Slice(unresolved_type) => { self.find_in_unresolved_type(unresolved_type); } - noirc_frontend::ast::UnresolvedTypeData::Parenthesized(unresolved_type) => { + UnresolvedTypeData::Parenthesized(unresolved_type) => { self.find_in_unresolved_type(unresolved_type); } - noirc_frontend::ast::UnresolvedTypeData::Named(path, unresolved_types, _) => { + 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) => { + 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) => { + UnresolvedTypeData::MutableReference(unresolved_type) => { self.find_in_unresolved_type(unresolved_type); } - noirc_frontend::ast::UnresolvedTypeData::Tuple(unresolved_types) => { + UnresolvedTypeData::Tuple(unresolved_types) => { self.find_in_unresolved_types(unresolved_types); } - noirc_frontend::ast::UnresolvedTypeData::Function(args, ret, env) => { + 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) => { + 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 => (), + UnresolvedTypeData::Expression(_) + | UnresolvedTypeData::FormatString(_, _) + | UnresolvedTypeData::String(_) + | UnresolvedTypeData::Unspecified + | UnresolvedTypeData::Quoted(_) + | UnresolvedTypeData::FieldElement + | UnresolvedTypeData::Integer(_, _) + | UnresolvedTypeData::Bool + | UnresolvedTypeData::Unit + | UnresolvedTypeData::Resolved(_) + | UnresolvedTypeData::Error => (), } } @@ -706,20 +685,54 @@ impl<'a> NodeFinder<'a> { at_root = idents.is_empty(); } + let prefix = prefix.to_case(Case::Snake); + let is_single_segment = !after_colons && idents.is_empty() && path.kind == PathKind::Plain; + let module_id; - let module_id = - if idents.is_empty() { Some(self.module_id) } else { self.resolve_module(idents) }; - let Some(module_id) = module_id else { - return; - }; + if idents.is_empty() { + module_id = self.module_id; + } else { + let Some(module_def_id) = self.resolve_path(idents) else { + return; + }; + + match module_def_id { + ModuleDefId::ModuleId(id) => module_id = id, + ModuleDefId::TypeId(struct_id) => { + let struct_type = self.interner.get_struct(struct_id); + self.complete_type_methods( + &Type::Struct(struct_type, vec![]), + &prefix, + FunctionKind::Any, + ); + return; + } + ModuleDefId::FunctionId(_) => { + // There's nothing inside a function + return; + } + ModuleDefId::TypeAliasId(type_alias_id) => { + let type_alias = self.interner.get_type_alias(type_alias_id); + let type_alias = type_alias.borrow(); + self.complete_type_methods(&type_alias.typ, &prefix, FunctionKind::Any); + return; + } + ModuleDefId::TraitId(trait_id) => { + let trait_ = self.interner.get_trait(trait_id); + self.complete_trait_methods(trait_, &prefix, FunctionKind::Any); + return; + } + ModuleDefId::GlobalId(_) => return, + } + } let module_completion_kind = if after_colons { ModuleCompletionKind::DirectChildren } else { ModuleCompletionKind::AllVisibleItems }; - let function_completion_kind = FunctionCompleteKind::NameAndParameters; + let function_completion_kind = FunctionCompletionKind::NameAndParameters; self.complete_in_module( module_id, @@ -743,6 +756,7 @@ impl<'a> NodeFinder<'a> { self.type_parameters_completion(&prefix); } } + self.complete_auto_imports(&prefix, requested_items); } } @@ -827,7 +841,7 @@ impl<'a> NodeFinder<'a> { } let module_completion_kind = ModuleCompletionKind::DirectChildren; - let function_completion_kind = FunctionCompleteKind::Name; + let function_completion_kind = FunctionCompletionKind::Name; let requested_items = RequestedItems::AnyItems; if after_colons { @@ -835,11 +849,11 @@ impl<'a> NodeFinder<'a> { segments.push(ident.clone()); if let Some(module_id) = self.resolve_module(segments) { - let prefix = String::new(); + let prefix = ""; let at_root = false; self.complete_in_module( module_id, - &prefix, + prefix, path_kind, at_root, module_completion_kind, @@ -849,7 +863,7 @@ impl<'a> NodeFinder<'a> { }; } else { // We are right after the last segment - let prefix = ident.to_string(); + let prefix = ident.to_string().to_case(Case::Snake); if segments.is_empty() { let at_root = true; self.complete_in_module( @@ -913,6 +927,103 @@ impl<'a> NodeFinder<'a> { }; } + fn complete_type_fields_and_methods(&mut self, typ: &Type, prefix: &str) { + match typ { + Type::Struct(struct_type, generics) => { + self.complete_struct_fields(&struct_type.borrow(), generics, prefix); + } + Type::MutableReference(typ) => { + return self.complete_type_fields_and_methods(typ, prefix); + } + Type::Alias(type_alias, _) => { + let type_alias = type_alias.borrow(); + return self.complete_type_fields_and_methods(&type_alias.typ, prefix); + } + Type::Tuple(types) => { + self.complete_tuple_fields(types); + } + Type::FieldElement + | Type::Array(_, _) + | Type::Slice(_) + | Type::Integer(_, _) + | Type::Bool + | Type::String(_) + | Type::FmtString(_, _) + | Type::Unit + | Type::TypeVariable(_, _) + | Type::TraitAsType(_, _, _) + | Type::NamedGeneric(_, _, _) + | Type::Function(..) + | Type::Forall(_, _) + | Type::Constant(_) + | Type::Quoted(_) + | Type::InfixExpr(_, _, _) + | Type::Error => (), + } + + self.complete_type_methods(typ, prefix, FunctionKind::SelfType(typ)); + } + + fn complete_type_methods(&mut self, typ: &Type, prefix: &str, function_kind: FunctionKind) { + let Some(methods_by_name) = self.interner.get_type_methods(typ) else { + return; + }; + + for (name, methods) in methods_by_name { + for func_id in methods.iter() { + if name_matches(name, prefix) { + if let Some(completion_item) = self.function_completion_item( + func_id, + FunctionCompletionKind::NameAndParameters, + function_kind, + ) { + self.completion_items.push(completion_item); + self.suggested_module_def_ids.insert(ModuleDefId::FunctionId(func_id)); + } + } + } + } + } + + fn complete_trait_methods( + &mut self, + trait_: &Trait, + prefix: &str, + function_kind: FunctionKind, + ) { + for (name, func_id) in &trait_.method_ids { + if name_matches(name, prefix) { + if let Some(completion_item) = self.function_completion_item( + *func_id, + FunctionCompletionKind::NameAndParameters, + function_kind, + ) { + self.completion_items.push(completion_item); + self.suggested_module_def_ids.insert(ModuleDefId::FunctionId(*func_id)); + } + } + } + } + + fn complete_struct_fields( + &mut self, + struct_type: &StructType, + generics: &[Type], + prefix: &str, + ) { + for (name, typ) in &struct_type.get_fields(generics) { + if name_matches(name, prefix) { + self.completion_items.push(struct_field_completion_item(name, typ)); + } + } + } + + fn complete_tuple_fields(&mut self, types: &[Type]) { + for (index, typ) in types.iter().enumerate() { + self.completion_items.push(field_completion_item(&index.to_string(), typ.to_string())); + } + } + #[allow(clippy::too_many_arguments)] fn complete_in_module( &mut self, @@ -921,7 +1032,7 @@ impl<'a> NodeFinder<'a> { path_kind: PathKind, at_root: bool, module_completion_kind: ModuleCompletionKind, - function_completion_kind: FunctionCompleteKind, + function_completion_kind: FunctionCompletionKind, requested_items: RequestedItems, ) { let def_map = &self.def_maps[&module_id.krate]; @@ -951,6 +1062,8 @@ impl<'a> NodeFinder<'a> { } } + let function_kind = FunctionKind::Any; + let items = match module_completion_kind { ModuleCompletionKind::DirectChildren => module_data.definitions(), ModuleCompletionKind::AllVisibleItems => module_data.scope(), @@ -966,9 +1079,11 @@ impl<'a> NodeFinder<'a> { module_def_id, name.clone(), function_completion_kind, + function_kind, requested_items, ) { self.completion_items.push(completion_item); + self.suggested_module_def_ids.insert(module_def_id); } } @@ -977,9 +1092,11 @@ impl<'a> NodeFinder<'a> { module_def_id, name.clone(), function_completion_kind, + function_kind, requested_items, ) { self.completion_items.push(completion_item); + self.suggested_module_def_ids.insert(module_def_id); } } } @@ -1011,131 +1128,6 @@ impl<'a> NodeFinder<'a> { } } - 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) @@ -1145,65 +1137,25 @@ impl<'a> NodeFinder<'a> { } fn resolve_path(&self, segments: Vec) -> Option { + let last_segment = segments.last().unwrap().clone(); + 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, + if let Ok(path_resolution) = path_resolver.resolve(self.def_maps, path, &mut None) { + return Some(path_resolution.module_def_id); } - } - 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()), - )); - } + // If we can't resolve a path trough lookup, let's see if the last segment is bound to a type + let location = Location::new(last_segment.span(), self.file); + if let Some(reference_id) = self.interner.find_referenced(location) { + if let Some(id) = module_def_id_from_reference_id(reference_id) { + return Some(id); } } - } - 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()), - )); - } - } + None } fn includes_span(&self, span: Span) -> bool { @@ -1211,994 +1163,69 @@ impl<'a> NodeFinder<'a> { } } +/// Returns true if name matches a prefix written in code. +/// `prefix` must already be in snake case. +/// This method splits both name and prefix by underscore, +/// then checks that every part of name starts with a part of +/// prefix, in order. +/// +/// For example: +/// +/// // "merk" and "ro" match "merkle" and "root" and are in order +/// name_matches("compute_merkle_root", "merk_ro") == true +/// +/// // "ro" matches "root", but "merkle" comes before it, so no match +/// name_matches("compute_merkle_root", "ro_mer") == false +/// +/// // neither "compute" nor "merkle" nor "root" start with "oot" +/// name_matches("compute_merkle_root", "oot") == false 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) -} + let name = name.to_case(Case::Snake); + let name_parts: Vec<&str> = name.split('_').collect(); -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, + let mut last_index: i32 = -1; + for prefix_part in prefix.split('_') { + if let Some(name_part_index) = + name_parts.iter().position(|name_part| name_part.starts_with(prefix_part)) + { + if last_index >= name_part_index as i32 { + return false; + } + last_index = name_part_index as i32; + } else { + return false; + } } + + true } -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, +fn module_def_id_from_reference_id(reference_id: ReferenceId) -> Option { + match reference_id { + ReferenceId::Module(module_id) => Some(ModuleDefId::ModuleId(module_id)), + ReferenceId::Struct(struct_id) => Some(ModuleDefId::TypeId(struct_id)), + ReferenceId::Trait(trait_id) => Some(ModuleDefId::TraitId(trait_id)), + ReferenceId::Function(func_id) => Some(ModuleDefId::FunctionId(func_id)), + ReferenceId::Alias(type_alias_id) => Some(ModuleDefId::TypeAliasId(type_alias_id)), + ReferenceId::StructMember(_, _) + | ReferenceId::Global(_) + | ReferenceId::Local(_) + | ReferenceId::Reference(_, _) => 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; - } +mod completion_name_matches_tests { + use crate::requests::completion::name_matches; #[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; + fn test_name_matches() { + assert!(name_matches("foo", "foo")); + assert!(name_matches("foo_bar", "bar")); + assert!(name_matches("FooBar", "foo")); + assert!(name_matches("FooBar", "bar")); + assert!(name_matches("FooBar", "foo_bar")); + + assert!(!name_matches("foo_bar", "o_b")); } } diff --git a/noir/noir-repo/tooling/lsp/src/requests/completion/auto_import.rs b/noir/noir-repo/tooling/lsp/src/requests/completion/auto_import.rs new file mode 100644 index 000000000000..c5710d30b5cb --- /dev/null +++ b/noir/noir-repo/tooling/lsp/src/requests/completion/auto_import.rs @@ -0,0 +1,195 @@ +use lsp_types::{Position, Range, TextEdit}; +use noirc_frontend::{ + ast::ItemVisibility, + graph::{CrateId, Dependency}, + hir::def_map::ModuleId, + macros_api::{ModuleDefId, NodeInterner}, + node_interner::ReferenceId, +}; + +use super::{ + kinds::{FunctionCompletionKind, FunctionKind, RequestedItems}, + name_matches, + sort_text::auto_import_sort_text, + NodeFinder, +}; + +impl<'a> NodeFinder<'a> { + pub(super) fn complete_auto_imports(&mut self, prefix: &str, requested_items: RequestedItems) { + for (name, entries) in self.interner.get_auto_import_names() { + if !name_matches(name, prefix) { + continue; + } + + for (module_def_id, visibility) in entries { + if self.suggested_module_def_ids.contains(module_def_id) { + continue; + } + + let Some(mut completion_item) = self.module_def_id_completion_item( + *module_def_id, + name.clone(), + FunctionCompletionKind::NameAndParameters, + FunctionKind::Any, + requested_items, + ) else { + continue; + }; + + let module_full_path; + if let ModuleDefId::ModuleId(module_id) = module_def_id { + module_full_path = module_id_path( + module_id, + &self.module_id, + self.interner, + self.dependencies, + ); + } else { + let Some(parent_module) = get_parent_module(self.interner, *module_def_id) + else { + continue; + }; + + match *visibility { + ItemVisibility::Public => (), + ItemVisibility::Private => { + // Technically this can't be reached because we don't record private items for auto-import, + // but this is here for completeness. + continue; + } + ItemVisibility::PublicCrate => { + if self.module_id.krate != parent_module.krate { + continue; + } + } + } + + module_full_path = module_id_path( + parent_module, + &self.module_id, + self.interner, + self.dependencies, + ); + } + + let full_path = if let ModuleDefId::ModuleId(..) = module_def_id { + module_full_path + } else { + format!("{}::{}", module_full_path, name) + }; + + let mut label_details = completion_item.label_details.unwrap(); + label_details.detail = Some(format!("(use {})", full_path)); + completion_item.label_details = Some(label_details); + + let line = self.auto_import_line as u32; + let character = (self.nesting * 4) as u32; + let indent = " ".repeat(self.nesting * 4); + let mut newlines = "\n"; + + // If the line we are inserting into is not an empty line, insert an extra line to make some room + if let Some(line_text) = self.lines.get(line as usize) { + if !line_text.trim().is_empty() { + newlines = "\n\n"; + } + } + + completion_item.additional_text_edits = Some(vec![TextEdit { + range: Range { + start: Position { line, character }, + end: Position { line, character }, + }, + new_text: format!("use {};{}{}", full_path, newlines, indent), + }]); + + completion_item.sort_text = Some(auto_import_sort_text()); + + self.completion_items.push(completion_item); + self.suggested_module_def_ids.insert(*module_def_id); + } + } + } +} + +fn get_parent_module(interner: &NodeInterner, module_def_id: ModuleDefId) -> Option<&ModuleId> { + let reference_id = module_def_id_to_reference_id(module_def_id); + interner.reference_module(reference_id) +} + +fn module_def_id_to_reference_id(module_def_id: ModuleDefId) -> ReferenceId { + match module_def_id { + ModuleDefId::ModuleId(id) => ReferenceId::Module(id), + ModuleDefId::FunctionId(id) => ReferenceId::Function(id), + ModuleDefId::TypeId(id) => ReferenceId::Struct(id), + ModuleDefId::TypeAliasId(id) => ReferenceId::Alias(id), + ModuleDefId::TraitId(id) => ReferenceId::Trait(id), + ModuleDefId::GlobalId(id) => ReferenceId::Global(id), + } +} + +/// Computes the path of `module_id` relative to `current_module_id`. +/// If it's not relative, the full path is returned. +fn module_id_path( + module_id: &ModuleId, + current_module_id: &ModuleId, + interner: &NodeInterner, + dependencies: &[Dependency], +) -> String { + let mut string = String::new(); + + let crate_id = module_id.krate; + let crate_name = match crate_id { + CrateId::Root(_) => Some("crate".to_string()), + CrateId::Crate(_) => dependencies + .iter() + .find(|dep| dep.crate_id == crate_id) + .map(|dep| format!("{}", dep.name)), + CrateId::Stdlib(_) => Some("std".to_string()), + CrateId::Dummy => None, + }; + + let wrote_crate = if let Some(crate_name) = crate_name { + string.push_str(&crate_name); + true + } else { + false + }; + + let Some(module_attributes) = interner.try_module_attributes(module_id) else { + return string; + }; + + if wrote_crate { + string.push_str("::"); + } + + let mut segments = Vec::new(); + let mut current_attributes = module_attributes; + loop { + let parent_module_id = + &ModuleId { krate: module_id.krate, local_id: current_attributes.parent }; + + // If the parent module is the current module we stop because we want a relative path to the module + if current_module_id == parent_module_id { + // When the path is relative we don't want the "crate::" prefix anymore + string = string.strip_prefix("crate::").unwrap_or(&string).to_string(); + break; + } + + let Some(parent_attributes) = interner.try_module_attributes(parent_module_id) else { + break; + }; + + segments.push(&parent_attributes.name); + current_attributes = parent_attributes; + } + + for segment in segments.iter().rev() { + string.push_str(segment); + string.push_str("::"); + } + + string.push_str(&module_attributes.name); + + string +} diff --git a/noir/noir-repo/tooling/lsp/src/requests/completion/builtins.rs b/noir/noir-repo/tooling/lsp/src/requests/completion/builtins.rs index 070be109f137..75eba7fb3c7e 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/completion/builtins.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/completion/builtins.rs @@ -1,4 +1,64 @@ +use lsp_types::CompletionItemKind; use noirc_frontend::token::Keyword; +use strum::IntoEnumIterator; + +use super::{ + completion_items::{simple_completion_item, snippet_completion_item}, + name_matches, NodeFinder, +}; + +impl<'a> NodeFinder<'a> { + pub(super) 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()), + )); + } + } + } + } + + pub(super) 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()), + )); + } + } + } + + pub(super) 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()), + )); + } + } + } +} pub(super) fn builtin_integer_types() -> [&'static str; 8] { ["i8", "i16", "i32", "i64", "u8", "u16", "u32", "u64"] @@ -14,6 +74,7 @@ pub(super) fn keyword_builtin_type(keyword: &Keyword) -> Option<&'static str> { Keyword::StructDefinition => Some("StructDefinition"), Keyword::TraitConstraint => Some("TraitConstraint"), Keyword::TraitDefinition => Some("TraitDefinition"), + Keyword::TraitImpl => Some("TraitImpl"), Keyword::TypeType => Some("Type"), Keyword::As @@ -52,6 +113,7 @@ pub(super) fn keyword_builtin_type(keyword: &Keyword) -> Option<&'static str> { | Keyword::Type | Keyword::Unchecked | Keyword::Unconstrained + | Keyword::Unsafe | Keyword::Use | Keyword::Where | Keyword::While => None, @@ -116,10 +178,12 @@ pub(super) fn keyword_builtin_function(keyword: &Keyword) -> Option None, diff --git a/noir/noir-repo/tooling/lsp/src/requests/completion/completion_items.rs b/noir/noir-repo/tooling/lsp/src/requests/completion/completion_items.rs new file mode 100644 index 000000000000..d4190f5241ce --- /dev/null +++ b/noir/noir-repo/tooling/lsp/src/requests/completion/completion_items.rs @@ -0,0 +1,391 @@ +use lsp_types::{ + Command, CompletionItem, CompletionItemKind, CompletionItemLabelDetails, InsertTextFormat, +}; +use noirc_frontend::{ + hir_def::{function::FuncMeta, stmt::HirPattern}, + macros_api::{ModuleDefId, StructId}, + node_interner::{FuncId, GlobalId, TraitId, TypeAliasId}, + Type, +}; + +use super::{ + sort_text::{ + crate_or_module_sort_text, default_sort_text, new_sort_text, operator_sort_text, + self_mismatch_sort_text, + }, + FunctionCompletionKind, FunctionKind, NodeFinder, RequestedItems, +}; + +impl<'a> NodeFinder<'a> { + pub(super) fn module_def_id_completion_item( + &self, + module_def_id: ModuleDefId, + name: String, + function_completion_kind: FunctionCompletionKind, + function_kind: FunctionKind, + 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 => (), + } + + match module_def_id { + ModuleDefId::ModuleId(_) => Some(module_completion_item(name)), + ModuleDefId::FunctionId(func_id) => { + self.function_completion_item(func_id, function_completion_kind, function_kind) + } + ModuleDefId::TypeId(struct_id) => Some(self.struct_completion_item(struct_id)), + ModuleDefId::TypeAliasId(type_alias_id) => { + Some(self.type_alias_completion_item(type_alias_id)) + } + ModuleDefId::TraitId(trait_id) => Some(self.trait_completion_item(trait_id)), + ModuleDefId::GlobalId(global_id) => Some(self.global_completion_item(global_id)), + } + } + + 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)) + } + + pub(super) fn function_completion_item( + &self, + func_id: FuncId, + function_completion_kind: FunctionCompletionKind, + function_kind: FunctionKind, + ) -> Option { + let func_meta = self.interner.function_meta(&func_id); + let name = &self.interner.function_name(&func_id).to_string(); + + let func_self_type = if let Some((pattern, typ, _)) = func_meta.parameters.0.first() { + if self.hir_pattern_is_self_type(pattern) { + if let Type::MutableReference(mut_typ) = typ { + let typ: &Type = mut_typ; + Some(typ) + } else { + Some(typ) + } + } else { + None + } + } else { + None + }; + + match function_kind { + FunctionKind::Any => (), + FunctionKind::SelfType(mut self_type) => { + if let Some(func_self_type) = func_self_type { + if matches!(self_type, Type::Integer(..)) + || matches!(self_type, Type::FieldElement) + { + // Check that the pattern type is the same as self type. + // We do this because some types (only Field and integer types) + // have their methods in the same HashMap. + + if let Type::MutableReference(mut_typ) = self_type { + self_type = mut_typ; + } + + if self_type != func_self_type { + return None; + } + } else if let Type::Tuple(self_tuple_types) = self_type { + // Tuple types of different lengths seem to also have methods defined on all of them, + // so here we reject methods for tuples where the length doesn't match. + if let Type::Tuple(func_self_tuple_types) = func_self_type { + if self_tuple_types.len() != func_self_tuple_types.len() { + return None; + } + } + } + } else { + return None; + } + } + } + + let is_operator = if let Some(trait_impl_id) = &func_meta.trait_impl { + let trait_impl = self.interner.get_trait_implementation(*trait_impl_id); + let trait_impl = trait_impl.borrow(); + self.interner.is_operator_trait(trait_impl.trait_id) + } else { + false + }; + let description = func_meta_type_to_string(func_meta, func_self_type.is_some()); + + let completion_item = match function_completion_kind { + FunctionCompletionKind::Name => { + simple_completion_item(name, CompletionItemKind::FUNCTION, Some(description)) + } + FunctionCompletionKind::NameAndParameters => { + let kind = CompletionItemKind::FUNCTION; + let insert_text = self.compute_function_insert_text(func_meta, name, function_kind); + let label = if insert_text.ends_with("()") { + format!("{}()", name) + } else { + format!("{}(…)", name) + }; + + snippet_completion_item(label, kind, insert_text, Some(description)) + } + }; + + let completion_item = if is_operator { + completion_item_with_sort_text(completion_item, operator_sort_text()) + } else if function_kind == FunctionKind::Any && name == "new" { + completion_item_with_sort_text(completion_item, new_sort_text()) + } else if function_kind == FunctionKind::Any && func_self_type.is_some() { + completion_item_with_sort_text(completion_item, self_mismatch_sort_text()) + } else { + completion_item + }; + + let completion_item = match function_completion_kind { + FunctionCompletionKind::Name => completion_item, + FunctionCompletionKind::NameAndParameters => { + completion_item_with_trigger_parameter_hints_command(completion_item) + } + }; + + Some(completion_item) + } + + fn compute_function_insert_text( + &self, + func_meta: &FuncMeta, + name: &str, + function_kind: FunctionKind, + ) -> String { + let mut text = String::new(); + text.push_str(name); + text.push('('); + + let mut index = 1; + for (pattern, _, _) in &func_meta.parameters.0 { + if index == 1 { + match function_kind { + FunctionKind::SelfType(_) => { + if self.hir_pattern_is_self_type(pattern) { + continue; + } + } + FunctionKind::Any => (), + } + } + + if index > 1 { + text.push_str(", "); + } + + text.push_str("${"); + text.push_str(&index.to_string()); + text.push(':'); + self.hir_pattern_to_argument(pattern, &mut text); + text.push('}'); + + index += 1; + } + 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 hir_pattern_is_self_type(&self, pattern: &HirPattern) -> bool { + match pattern { + HirPattern::Identifier(hir_ident) => { + let name = self.interner.definition_name(hir_ident.id); + name == "self" || name == "_self" + } + HirPattern::Mutable(pattern, _) => self.hir_pattern_is_self_type(pattern), + HirPattern::Tuple(_, _) | HirPattern::Struct(_, _, _) => false, + } + } +} + +pub(super) fn module_completion_item(name: impl Into) -> CompletionItem { + completion_item_with_sort_text( + simple_completion_item(name, CompletionItemKind::MODULE, None), + crate_or_module_sort_text(), + ) +} + +pub(super) fn crate_completion_item(name: impl Into) -> CompletionItem { + completion_item_with_sort_text( + simple_completion_item(name, CompletionItemKind::MODULE, None), + crate_or_module_sort_text(), + ) +} + +fn func_meta_type_to_string(func_meta: &FuncMeta, has_self_type: bool) -> String { + let mut typ = &func_meta.typ; + if let Type::Forall(_, typ_) = typ { + typ = typ_; + } + + if let Type::Function(args, ret, _env, unconstrained) = typ { + let mut string = String::new(); + if *unconstrained { + string.push_str("unconstrained "); + } + string.push_str("fn("); + for (index, arg) in args.iter().enumerate() { + if index > 0 { + string.push_str(", "); + } + if index == 0 && has_self_type { + type_to_self_string(arg, &mut string); + } else { + string.push_str(&arg.to_string()); + } + } + string.push(')'); + + let ret: &Type = ret; + if let Type::Unit = ret { + // Nothing + } else { + string.push_str(" -> "); + string.push_str(&ret.to_string()); + } + string + } else { + typ.to_string() + } +} + +fn type_to_self_string(typ: &Type, string: &mut String) { + if let Type::MutableReference(..) = typ { + string.push_str("&mut self"); + } else { + string.push_str("self"); + } +} + +pub(super) fn struct_field_completion_item(field: &str, typ: &Type) -> CompletionItem { + field_completion_item(field, typ.to_string()) +} + +pub(super) fn field_completion_item(field: &str, typ: impl Into) -> CompletionItem { + simple_completion_item(field, CompletionItemKind::FIELD, Some(typ.into())) +} + +pub(super) 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: Some(default_sort_text()), + 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, + } +} + +pub(super) 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: Some(default_sort_text()), + filter_text: None, + insert_text_mode: None, + text_edit: None, + additional_text_edits: None, + command: None, + commit_characters: None, + data: None, + tags: None, + } +} + +pub(super) fn completion_item_with_sort_text( + completion_item: CompletionItem, + sort_text: String, +) -> CompletionItem { + CompletionItem { sort_text: Some(sort_text), ..completion_item } +} + +pub(super) fn completion_item_with_trigger_parameter_hints_command( + completion_item: CompletionItem, +) -> CompletionItem { + CompletionItem { + command: Some(Command { + title: "Trigger parameter hints".to_string(), + command: "editor.action.triggerParameterHints".to_string(), + arguments: None, + }), + ..completion_item + } +} diff --git a/noir/noir-repo/tooling/lsp/src/requests/completion/kinds.rs b/noir/noir-repo/tooling/lsp/src/requests/completion/kinds.rs new file mode 100644 index 000000000000..e01fcfc8c567 --- /dev/null +++ b/noir/noir-repo/tooling/lsp/src/requests/completion/kinds.rs @@ -0,0 +1,42 @@ +use noirc_frontend::Type; + +/// When finding items in a module, whether to show only direct children or all visible items. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub(super) 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)] +pub(super) enum FunctionCompletionKind { + // 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, +} + +/// Is there a requirement for suggesting functions? +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub(super) enum FunctionKind<'a> { + /// No requirement: any function is okay to suggest. + Any, + /// Only show functions that have the given self type. + SelfType(&'a Type), +} + +/// 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)] +pub(super) enum RequestedItems { + // Suggest any items (types, functions, etc.). + AnyItems, + // Only suggest types. + OnlyTypes, +} diff --git a/noir/noir-repo/tooling/lsp/src/requests/completion/sort_text.rs b/noir/noir-repo/tooling/lsp/src/requests/completion/sort_text.rs new file mode 100644 index 000000000000..9bdc603660fe --- /dev/null +++ b/noir/noir-repo/tooling/lsp/src/requests/completion/sort_text.rs @@ -0,0 +1,39 @@ +/// Sort text for "new" methods: we want these to show up before anything else, +/// if we are completing at something like `Foo::` +pub(super) fn new_sort_text() -> String { + "a".to_string() +} + +/// This is the default sort text. +pub(super) fn default_sort_text() -> String { + "b".to_string() +} + +/// We want crates and modules to show up after other things (for example +/// local variables, functions or types) +pub(super) fn crate_or_module_sort_text() -> String { + "c".to_string() +} + +/// Sort text for auto-import items. We want these to show up after local definitions. +pub(super) fn auto_import_sort_text() -> String { + "d".to_string() +} + +/// When completing something like `Foo::`, we want to show methods that take +/// self after the other ones. +pub(super) fn self_mismatch_sort_text() -> String { + "e".to_string() +} + +/// We want to show operator methods last. +pub(super) fn operator_sort_text() -> String { + "f".to_string() +} + +/// If a name begins with underscore it's likely something that's meant to +/// be private (but visibility doesn't exist everywhere yet, so for now +/// we assume that) +pub(super) fn underscore_sort_text() -> String { + "g".to_string() +} diff --git a/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs b/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs new file mode 100644 index 000000000000..ee2014ef8b8b --- /dev/null +++ b/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs @@ -0,0 +1,1616 @@ +#[cfg(test)] +mod completion_tests { + use crate::{ + notifications::on_did_open_text_document, + requests::{ + completion::{ + completion_items::{ + completion_item_with_sort_text, + completion_item_with_trigger_parameter_hints_command, crate_completion_item, + field_completion_item, module_completion_item, simple_completion_item, + snippet_completion_item, + }, + sort_text::{auto_import_sort_text, self_mismatch_sort_text}, + }, + on_completion_request, + }, + test_utils, + }; + + use lsp_types::{ + CompletionItem, CompletionItemKind, CompletionItemLabelDetails, CompletionParams, + CompletionResponse, DidOpenTextDocumentParams, PartialResultParams, Position, Range, + TextDocumentIdentifier, TextDocumentItem, TextDocumentPositionParams, TextEdit, + WorkDoneProgressParams, + }; + use tokio::test; + + async fn get_completions(src: &str) -> 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(), + }, + }, + ); + + 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"); + + if let Some(CompletionResponse::Array(items)) = response { + items + } else { + vec![] + } + } + + fn assert_items_match(mut items: Vec, mut expected: Vec) { + items.sort_by_key(|item| item.label.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); + } + + async fn assert_completion(src: &str, expected: Vec) { + let items = get_completions(src).await; + assert_items_match(items, expected); + } + + async fn assert_completion_excluding_auto_import(src: &str, expected: Vec) { + let items = get_completions(src).await; + let items = items.into_iter().filter(|item| item.additional_text_edits.is_none()).collect(); + assert_items_match(items, expected); + } + + pub(super) fn function_completion_item( + label: impl Into, + insert_text: impl Into, + description: impl Into, + ) -> CompletionItem { + completion_item_with_trigger_parameter_hints_command(snippet_completion_item( + label, + CompletionItemKind::FUNCTION, + insert_text, + Some(description.into()), + )) + } + + #[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 qux {} + } + + fn main() { + foo::q>|< + } + "#; + assert_completion(src, vec![module_completion_item("qux")]).await; + } + + #[test] + async fn test_complete_path_with_local_variable() { + let src = r#" + fn main() { + let local = 1; + l>|< + } + "#; + assert_completion_excluding_auto_import( + 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_excluding_auto_import( + 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_excluding_auto_import( + src, + vec![simple_completion_item( + "local", + CompletionItemKind::VARIABLE, + Some("Field".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_complete_function_without_arguments() { + let src = r#" + fn hello() { } + + fn main() { + h>|< + } + "#; + assert_completion_excluding_auto_import( + src, + vec![function_completion_item("hello()", "hello()", "fn()")], + ) + .await; + } + + #[test] + async fn test_complete_function() { + let src = r#" + fn hello(x: i32, y: Field) { } + + fn main() { + h>|< + } + "#; + assert_completion_excluding_auto_import( + src, + vec![function_completion_item( + "hello(…)", + "hello(${1:x}, ${2:y})", + "fn(i32, Field)".to_string(), + )], + ) + .await; + } + + #[test] + async fn test_complete_builtin_functions() { + let src = r#" + fn main() { + a>|< + } + "#; + assert_completion_excluding_auto_import( + src, + vec![ + snippet_completion_item( + "assert(…)", + CompletionItemKind::FUNCTION, + "assert(${1:predicate})", + Some("fn(T)".to_string()), + ), + function_completion_item("assert_constant(…)", "assert_constant(${1:x})", "fn(T)"), + 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() { + So>|< + } + } + "#; + assert_completion_excluding_auto_import( + 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() { + So>|< + } + } + "#; + assert_completion_excluding_auto_import( + 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 { + ind>|< + } + } + "#; + assert_completion_excluding_auto_import( + 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(|lambda_var| lambda_v>|<) + } + "#; + assert_completion_excluding_auto_import( + src, + vec![simple_completion_item( + "lambda_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>|<) {} + "#; + + let items = get_completions(src).await; + let items = items.into_iter().filter(|item| item.label.starts_with('i')).collect(); + + assert_items_match( + items, + 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())), + ], + ); + } + + #[test] + async fn test_suggest_true() { + let src = r#" + fn main() { + let x = t>|< + } + "#; + assert_completion_excluding_auto_import( + 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_excluding_auto_import( + 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_excluding_auto_import( + 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_excluding_auto_import( + 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_excluding_auto_import( + 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_excluding_auto_import( + 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: Cont>|< + } + "#; + assert_completion_excluding_auto_import( + 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: Cont>|<) {} + "#; + assert_completion_excluding_auto_import( + src, + vec![simple_completion_item("Context", CompletionItemKind::TYPE_PARAMETER, None)], + ) + .await; + } + + #[test] + async fn test_suggests_struct_field_after_dot_and_letter() { + let src = r#" + struct Some { + property: i32, + } + + fn foo(s: Some) { + s.p>|< + } + "#; + assert_completion(src, vec![field_completion_item("property", "i32")]).await; + } + + #[test] + async fn test_suggests_struct_field_after_dot_and_letter_for_generic_type() { + let src = r#" + struct Some { + property: T, + } + + fn foo(s: Some) { + s.p>|< + } + "#; + assert_completion(src, vec![field_completion_item("property", "i32")]).await; + } + + #[test] + async fn test_suggests_struct_field_after_dot_followed_by_brace() { + let src = r#" + struct Some { + property: i32, + } + + fn foo(s: Some) { + s.>|< + } + "#; + assert_completion(src, vec![field_completion_item("property", "i32")]).await; + } + + #[test] + async fn test_suggests_struct_field_after_dot_chain() { + let src = r#" + struct Some { + property: Other, + } + + struct Other { + bar: i32, + } + + fn foo(some: Some) { + some.property.>|< + } + "#; + assert_completion(src, vec![field_completion_item("bar", "i32")]).await; + } + + #[test] + async fn test_suggests_struct_impl_method() { + let src = r#" + struct Some { + } + + impl Some { + fn foobar(self, x: i32) {} + fn foobar2(&mut self, x: i32) {} + fn foobar3(y: i32) {} + } + + fn foo(some: Some) { + some.f>|< + } + "#; + assert_completion( + src, + vec![ + function_completion_item("foobar(…)", "foobar(${1:x})", "fn(self, i32)"), + function_completion_item("foobar2(…)", "foobar2(${1:x})", "fn(&mut self, i32)"), + ], + ) + .await; + } + + #[test] + async fn test_suggests_struct_trait_impl_method() { + let src = r#" + struct Some { + } + + trait SomeTrait { + fn foobar(self, x: i32); + fn foobar2(y: i32); + } + + impl SomeTrait for Some { + fn foobar(self, x: i32) {} + fn foobar2(y: i32) {} + } + + fn foo(some: Some) { + some.f>|< + } + "#; + assert_completion( + src, + vec![function_completion_item("foobar(…)", "foobar(${1:x})", "fn(self, i32)")], + ) + .await; + } + + #[test] + async fn test_suggests_primitive_trait_impl_method() { + let src = r#" + trait SomeTrait { + fn foobar(self, x: i32); + fn foobar2(y: i32); + } + + impl SomeTrait for Field { + fn foobar(self, x: i32) {} + fn foobar2(y: i32) {} + } + + fn foo(field: Field) { + field.f>|< + } + "#; + assert_completion( + src, + vec![function_completion_item("foobar(…)", "foobar(${1:x})", "fn(self, i32)")], + ) + .await; + } + + #[test] + async fn test_suggests_struct_methods_after_colons() { + let src = r#" + struct Some { + } + + impl Some { + fn foobar(self, x: i32) {} + fn foobar2(&mut self, x: i32) {} + fn foobar3(y: i32) {} + } + + fn foo() { + Some::>|< + } + "#; + assert_completion( + src, + vec![ + completion_item_with_sort_text( + function_completion_item( + "foobar(…)", + "foobar(${1:self}, ${2:x})", + "fn(self, i32)", + ), + self_mismatch_sort_text(), + ), + completion_item_with_sort_text( + function_completion_item( + "foobar2(…)", + "foobar2(${1:self}, ${2:x})", + "fn(&mut self, i32)", + ), + self_mismatch_sort_text(), + ), + function_completion_item("foobar3(…)", "foobar3(${1:y})", "fn(i32)"), + ], + ) + .await; + } + + #[test] + async fn test_suggests_struct_behind_alias_methods_after_dot() { + let src = r#" + struct Some { + } + + type Alias = Some; + + impl Some { + fn foobar(self, x: i32) {} + } + + fn foo(some: Alias) { + some.>|< + } + "#; + assert_completion( + src, + vec![function_completion_item("foobar(…)", "foobar(${1:x})", "fn(self, i32)")], + ) + .await; + } + + #[test] + async fn test_suggests_struct_behind_alias_methods_after_colons() { + let src = r#" + struct Some { + } + + type Alias = Some; + + impl Some { + fn foobar(self, x: i32) {} + fn foobar2(&mut self, x: i32) {} + fn foobar3(y: i32) {} + } + + fn foo() { + Alias::>|< + } + "#; + assert_completion( + src, + vec![ + completion_item_with_sort_text( + function_completion_item( + "foobar(…)", + "foobar(${1:self}, ${2:x})", + "fn(self, i32)", + ), + self_mismatch_sort_text(), + ), + completion_item_with_sort_text( + function_completion_item( + "foobar2(…)", + "foobar2(${1:self}, ${2:x})", + "fn(&mut self, i32)", + ), + self_mismatch_sort_text(), + ), + function_completion_item("foobar3(…)", "foobar3(${1:y})", "fn(i32)"), + ], + ) + .await; + } + + #[test] + async fn test_completes_in_broken_if_after_dot() { + let src = r#" + struct Some { + foo: i32, + } + + fn foo(s: Some) { + if s.>|< + } + "#; + assert_completion(src, vec![field_completion_item("foo", "i32")]).await; + } + + #[test] + async fn test_completes_in_nested_expression() { + let src = r#" + struct Foo { bar: Bar } + struct Bar { baz: i32 } + + fn foo(f: Foo) { + f.bar & f.>|< + } + "#; + assert_completion(src, vec![field_completion_item("bar", "Bar")]).await; + } + + #[test] + async fn test_completes_in_call_chain() { + let src = r#" + struct Foo {} + + impl Foo { + fn foo(self) -> Foo { self } + } + + fn foo(f: Foo) { + f.foo().>|< + } + "#; + assert_completion(src, vec![function_completion_item("foo()", "foo()", "fn(self) -> Foo")]) + .await; + } + + #[test] + async fn test_completes_when_assignment_follows() { + let src = r#" + struct Foo { + bar: i32, + } + + fn foo(f: Foo) { + let mut x = 1; + + f.>|< + + x = 2; + } + "#; + assert_completion(src, vec![field_completion_item("bar", "i32")]).await; + } + + #[test] + async fn test_completes_tuple_fields() { + let src = r#" + fn main() { + let tuple = (1, true); + tuple.>|< + } + "#; + + let items = get_completions(src).await; + let items = items.into_iter().filter(|item| item.kind == Some(CompletionItemKind::FIELD)); + let items = items.collect(); + + assert_items_match( + items, + vec![field_completion_item("0", "Field"), field_completion_item("1", "bool")], + ); + } + + #[test] + async fn test_completes_constructor_fields() { + let src = r#" + mod foobar { + struct Foo { + bb: i32, + bbb: Field, + bbbb: bool, + bbbbb: str<6>, + } + } + + fn main() { + foobar::Foo { bbb: 1, b>|<, bbbbb } + } + "#; + assert_completion( + src, + vec![field_completion_item("bb", "i32"), field_completion_item("bbbb", "bool")], + ) + .await; + } + + #[test] + async fn test_completes_trait_methods() { + let src = r#" + trait One { + fn one() -> Self; + } + + fn main() { + One::>|< + } + "#; + assert_completion(src, vec![function_completion_item("one()", "one()", "fn() -> Self")]) + .await; + } + + #[test] + async fn test_auto_imports() { + let src = r#" + mod foo { + mod bar { + pub fn hello_world() {} + + struct Foo { + } + + impl Foo { + // This is here to make sure it's not offered for completion + fn hello_world() {} + } + } + } + + fn main() { + hel>|< + } + "#; + let items = get_completions(src).await; + assert_eq!(items.len(), 1); + + let item = &items[0]; + assert_eq!(item.label, "hello_world()"); + assert_eq!( + item.label_details, + Some(CompletionItemLabelDetails { + detail: Some("(use foo::bar::hello_world)".to_string()), + description: Some("fn()".to_string()) + }) + ); + + assert_eq!( + item.additional_text_edits, + Some(vec![TextEdit { + range: Range { + start: Position { line: 0, character: 0 }, + end: Position { line: 0, character: 0 }, + }, + new_text: "use foo::bar::hello_world;\n".to_string(), + }]) + ); + + assert_eq!(item.sort_text, Some(auto_import_sort_text())); + } + + #[test] + async fn test_auto_imports_when_in_nested_module_and_item_is_further_nested() { + let src = r#" + mod foo { + mod bar { + pub fn hello_world() {} + } + + fn foo() { + hel>|< + } + } + "#; + let items = get_completions(src).await; + assert_eq!(items.len(), 1); + + let item = &items[0]; + assert_eq!(item.label, "hello_world()"); + assert_eq!( + item.label_details, + Some(CompletionItemLabelDetails { + detail: Some("(use bar::hello_world)".to_string()), + description: Some("fn()".to_string()) + }) + ); + + assert_eq!( + item.additional_text_edits, + Some(vec![TextEdit { + range: Range { + start: Position { line: 2, character: 4 }, + end: Position { line: 2, character: 4 }, + }, + new_text: "use bar::hello_world;\n\n ".to_string(), + }]) + ); + } + + #[test] + async fn test_auto_imports_when_in_nested_module_and_item_is_not_further_nested() { + let src = r#" + mod foo { + mod bar { + pub fn hello_world() {} + } + + mod baz { + fn foo() { + hel>|< + } + } + } + "#; + let items = get_completions(src).await; + assert_eq!(items.len(), 1); + + let item = &items[0]; + assert_eq!(item.label, "hello_world()"); + assert_eq!( + item.label_details, + Some(CompletionItemLabelDetails { + detail: Some("(use crate::foo::bar::hello_world)".to_string()), + description: Some("fn()".to_string()) + }) + ); + + assert_eq!( + item.additional_text_edits, + Some(vec![TextEdit { + range: Range { + start: Position { line: 7, character: 8 }, + end: Position { line: 7, character: 8 }, + }, + new_text: "use crate::foo::bar::hello_world;\n\n ".to_string(), + }]) + ); + } + + #[test] + async fn test_auto_import_inserts_after_last_use() { + let src = r#" + mod foo { + mod bar { + pub fn hello_world() {} + } + } + + use foo::bar; + + fn main() { + hel>|< + } + "#; + let items = get_completions(src).await; + assert_eq!(items.len(), 1); + + let item = &items[0]; + assert_eq!( + item.additional_text_edits, + Some(vec![TextEdit { + range: Range { + start: Position { line: 8, character: 0 }, + end: Position { line: 8, character: 0 }, + }, + new_text: "use foo::bar::hello_world;\n".to_string(), + }]) + ); + } + + #[test] + async fn test_does_not_auto_import_test_functions() { + let src = r#" + mod foo { + mod bar { + #[test] + pub fn hello_world() {} + } + } + + use foo::bar; + + fn main() { + hel>|< + } + "#; + let items = get_completions(src).await; + assert!(items.is_empty()); + } + + #[test] + async fn test_does_not_auto_import_private_functions() { + let src = r#" + mod foo { + mod bar { + fn hello_world() {} + } + } + + use foo::bar; + + fn main() { + hel>|< + } + "#; + let items = get_completions(src).await; + assert!(items.is_empty()); + } + + #[test] + async fn test_auto_import_suggests_modules_too() { + let src = r#" + mod foo { + mod barbaz { + fn hello_world() {} + } + } + + fn main() { + barb>|< + } + "#; + let items = get_completions(src).await; + assert_eq!(items.len(), 1); + + let item = &items[0]; + assert_eq!(item.label, "barbaz"); + assert_eq!( + item.label_details, + Some(CompletionItemLabelDetails { + detail: Some("(use foo::barbaz)".to_string()), + description: None + }) + ); + } + + #[test] + async fn test_completes_matching_any_part_of_an_identifier_by_underscore() { + let src = r#" + struct Foo { + some_property: i32, + } + + fn foo(f: Foo) { + f.prop>|< + } + "#; + assert_completion(src, vec![field_completion_item("some_property", "i32")]).await; + } + + #[test] + async fn test_completes_in_impl_type() { + let src = r#" + struct FooBar { + } + + impl FooB>|< + "#; + + assert_completion( + src, + vec![simple_completion_item( + "FooBar", + CompletionItemKind::STRUCT, + Some("FooBar".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_completes_in_impl_for_type() { + let src = r#" + struct FooBar { + } + + impl Default for FooB>|< + "#; + + assert_completion( + src, + vec![simple_completion_item( + "FooBar", + CompletionItemKind::STRUCT, + Some("FooBar".to_string()), + )], + ) + .await; + } +} diff --git a/noir/noir-repo/tooling/lsp/src/requests/completion/traversal.rs b/noir/noir-repo/tooling/lsp/src/requests/completion/traversal.rs new file mode 100644 index 000000000000..b487c8baf361 --- /dev/null +++ b/noir/noir-repo/tooling/lsp/src/requests/completion/traversal.rs @@ -0,0 +1,132 @@ +/// This file includes the completion logic that's just about +/// traversing the AST without any additional logic. +use noirc_frontend::{ + ast::{ + ArrayLiteral, AssignStatement, CallExpression, CastExpression, ConstrainStatement, + Expression, ForRange, FunctionReturnType, IndexExpression, InfixExpression, Literal, + MethodCallExpression, NoirTrait, NoirTypeAlias, TraitImplItem, UnresolvedType, + }, + ParsedModule, +}; + +use super::NodeFinder; + +impl<'a> NodeFinder<'a> { + pub(super) fn find_in_parsed_module(&mut self, parsed_module: &ParsedModule) { + for item in &parsed_module.items { + self.find_in_item(item); + } + } + + pub(super) 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 { .. } => (), + } + } + + pub(super) fn find_in_noir_trait(&mut self, noir_trait: &NoirTrait) { + for item in &noir_trait.items { + self.find_in_trait_item(item); + } + } + + pub(super) 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); + } + } + + pub(super) fn find_in_assign_statement(&mut self, assign_statement: &AssignStatement) { + self.find_in_lvalue(&assign_statement.lvalue); + self.find_in_expression(&assign_statement.expression); + } + + pub(super) 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), + } + } + + pub(super) fn find_in_expressions(&mut self, expressions: &[Expression]) { + for expression in expressions { + self.find_in_expression(expression); + } + } + + pub(super) 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 => (), + } + } + + pub(super) 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); + } + } + } + + pub(super) fn find_in_index_expression(&mut self, index_expression: &IndexExpression) { + self.find_in_expression(&index_expression.collection); + self.find_in_expression(&index_expression.index); + } + + pub(super) fn find_in_call_expression(&mut self, call_expression: &CallExpression) { + self.find_in_expression(&call_expression.func); + self.find_in_expressions(&call_expression.arguments); + } + + pub(super) 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); + } + + pub(super) fn find_in_cast_expression(&mut self, cast_expression: &CastExpression) { + self.find_in_expression(&cast_expression.lhs); + } + + pub(super) fn find_in_infix_expression(&mut self, infix_expression: &InfixExpression) { + self.find_in_expression(&infix_expression.lhs); + self.find_in_expression(&infix_expression.rhs); + } + + pub(super) fn find_in_unresolved_types(&mut self, unresolved_type: &[UnresolvedType]) { + for unresolved_type in unresolved_type { + self.find_in_unresolved_type(unresolved_type); + } + } + + pub(super) 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); + } + } + } + + pub(super) fn find_in_noir_type_alias(&mut self, noir_type_alias: &NoirTypeAlias) { + self.find_in_unresolved_type(&noir_type_alias.typ); + } +} diff --git a/noir/noir-repo/tooling/lsp/src/requests/hover.rs b/noir/noir-repo/tooling/lsp/src/requests/hover.rs index b6fdc6f7842c..aa97def82749 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/hover.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/hover.rs @@ -431,7 +431,7 @@ impl<'a> TypeLinksGatherer<'a> { Type::NamedGeneric(var, _, _) => { self.gather_type_variable_links(var); } - Type::Function(args, return_type, env) => { + Type::Function(args, return_type, env, _) => { for arg in args { self.gather_type_links(arg); } 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 8c3d8a056525..a1e083187d39 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/inlay_hint.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/inlay_hint.rs @@ -293,6 +293,9 @@ impl<'a> InlayHintCollector<'a> { ExpressionKind::Comptime(block_expression, _span) => { self.collect_in_block_expression(block_expression); } + ExpressionKind::Unsafe(block_expression, _span) => { + self.collect_in_block_expression(block_expression); + } ExpressionKind::AsTraitPath(path) => { self.collect_in_ident(&path.impl_item, true); } @@ -589,7 +592,11 @@ fn push_type_parts(typ: &Type, parts: &mut Vec, files: &File parts.push(string_part(">")); } } - Type::Function(args, return_type, _env) => { + Type::Function(args, return_type, _env, unconstrained) => { + if *unconstrained { + parts.push(string_part("unconstrained ")); + } + parts.push(string_part("fn(")); for (index, arg) in args.iter().enumerate() { push_type_parts(arg, parts, files); @@ -686,6 +693,7 @@ fn get_expression_name(expression: &Expression) -> Option { | ExpressionKind::Comptime(..) | ExpressionKind::Resolved(..) | ExpressionKind::Literal(..) + | ExpressionKind::Unsafe(..) | ExpressionKind::Error => None, } } @@ -714,9 +722,9 @@ mod inlay_hints_tests { InlayHintParams { work_done_progress_params: WorkDoneProgressParams { work_done_token: None }, text_document: TextDocumentIdentifier { uri: noir_text_document }, - range: lsp_types::Range { - start: lsp_types::Position { line: start_line, character: 0 }, - end: lsp_types::Position { line: end_line, character: 0 }, + range: Range { + start: Position { line: start_line, character: 0 }, + end: Position { line: end_line, character: 0 }, }, }, ) diff --git a/noir/noir-repo/tooling/lsp/src/requests/mod.rs b/noir/noir-repo/tooling/lsp/src/requests/mod.rs index e138f8396001..e88c7f114659 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/mod.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/mod.rs @@ -46,6 +46,7 @@ mod inlay_hint; mod profile_run; mod references; mod rename; +mod signature_help; mod test_run; mod tests; @@ -56,7 +57,8 @@ pub(crate) use { 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, + rename::on_rename_request, signature_help::on_signature_help_request, + test_run::on_test_run_request, tests::on_tests_request, }; /// LSP client will send initialization request after the server has started. @@ -234,13 +236,22 @@ pub(crate) fn on_initialize( )), completion_provider: Some(lsp_types::OneOf::Right(lsp_types::CompletionOptions { resolve_provider: None, - trigger_characters: Some(vec![":".to_string()]), + trigger_characters: Some(vec![".".to_string(), ":".to_string()]), all_commit_characters: None, work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None, }, completion_item: None, })), + signature_help_provider: Some(lsp_types::OneOf::Right( + lsp_types::SignatureHelpOptions { + trigger_characters: Some(vec!["(".to_string(), ",".to_string()]), + retrigger_characters: None, + work_done_progress_options: WorkDoneProgressOptions { + work_done_progress: None, + }, + }, + )), }, server_info: None, }) diff --git a/noir/noir-repo/tooling/lsp/src/requests/signature_help.rs b/noir/noir-repo/tooling/lsp/src/requests/signature_help.rs new file mode 100644 index 000000000000..c2c691855479 --- /dev/null +++ b/noir/noir-repo/tooling/lsp/src/requests/signature_help.rs @@ -0,0 +1,291 @@ +use std::future::{self, Future}; + +use async_lsp::ResponseError; +use fm::{FileId, PathString}; +use lsp_types::{ + ParameterInformation, ParameterLabel, SignatureHelp, SignatureHelpParams, SignatureInformation, +}; +use noirc_errors::{Location, Span}; +use noirc_frontend::{ + ast::{CallExpression, Expression, FunctionReturnType, MethodCallExpression}, + hir_def::{function::FuncMeta, stmt::HirPattern}, + macros_api::NodeInterner, + node_interner::ReferenceId, + ParsedModule, Type, +}; + +use crate::{utils, LspState}; + +use super::process_request; + +mod tests; +mod traversal; + +pub(crate) fn on_signature_help_request( + state: &mut LspState, + params: SignatureHelpParams, +) -> impl Future, ResponseError>> { + let uri = params.text_document_position_params.clone().text_document.uri; + + let result = process_request(state, params.text_document_position_params.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_params.position, + ) + .and_then(|byte_index| { + let file = args.files.get_file(file_id).unwrap(); + let source = file.source(); + let (parsed_module, _errors) = noirc_frontend::parse_program(source); + + let mut finder = SignatureFinder::new(file_id, byte_index, args.interner); + finder.find(&parsed_module) + }) + }) + }); + future::ready(result) +} + +struct SignatureFinder<'a> { + file: FileId, + byte_index: usize, + interner: &'a NodeInterner, + signature_help: Option, +} + +impl<'a> SignatureFinder<'a> { + fn new(file: FileId, byte_index: usize, interner: &'a NodeInterner) -> Self { + Self { file, byte_index, interner, signature_help: None } + } + + fn find(&mut self, parsed_module: &ParsedModule) -> Option { + self.find_in_parsed_module(parsed_module); + + self.signature_help.clone() + } + + fn find_in_call_expression(&mut self, call_expression: &CallExpression, span: Span) { + self.find_in_expression(&call_expression.func); + self.find_in_expressions(&call_expression.arguments); + + let arguments_span = Span::from(call_expression.func.span.end() + 1..span.end() - 1); + let span = call_expression.func.span; + let name_span = Span::from(span.end() - 1..span.end()); + let has_self = false; + + self.try_compute_signature_help( + &call_expression.arguments, + arguments_span, + name_span, + has_self, + ); + } + + fn find_in_method_call_expression( + &mut self, + method_call_expression: &MethodCallExpression, + span: Span, + ) { + self.find_in_expression(&method_call_expression.object); + self.find_in_expressions(&method_call_expression.arguments); + + let arguments_span = + Span::from(method_call_expression.method_name.span().end() + 1..span.end() - 1); + let name_span = method_call_expression.method_name.span(); + let has_self = true; + + self.try_compute_signature_help( + &method_call_expression.arguments, + arguments_span, + name_span, + has_self, + ); + } + + fn try_compute_signature_help( + &mut self, + arguments: &[Expression], + arguments_span: Span, + name_span: Span, + has_self: bool, + ) { + if self.signature_help.is_some() { + return; + } + + if !self.includes_span(arguments_span) { + return; + } + + let mut active_parameter = None; + for (index, arg) in arguments.iter().enumerate() { + if self.includes_span(arg.span) || arg.span.start() as usize >= self.byte_index { + active_parameter = Some(index as u32); + break; + } + } + + if active_parameter.is_none() { + active_parameter = Some(arguments.len() as u32); + } + + let location = Location::new(name_span, self.file); + + // Check if the call references a named function + if let Some(ReferenceId::Function(func_id)) = self.interner.find_referenced(location) { + let name = self.interner.function_name(&func_id); + let func_meta = self.interner.function_meta(&func_id); + + let signature_information = + self.func_meta_signature_information(func_meta, name, active_parameter, has_self); + self.set_signature_help(signature_information); + return; + } + + // Otherwise, the call must be a reference to an fn type + if let Some(mut typ) = self.interner.type_at_location(location) { + if let Type::Forall(_, forall_typ) = typ { + typ = *forall_typ; + } + if let Type::Function(args, return_type, _, unconstrained) = typ { + let signature_information = self.function_type_signature_information( + &args, + &return_type, + unconstrained, + active_parameter, + ); + self.set_signature_help(signature_information); + } + } + } + + fn func_meta_signature_information( + &self, + func_meta: &FuncMeta, + name: &str, + active_parameter: Option, + has_self: bool, + ) -> SignatureInformation { + let mut label = String::new(); + let mut parameters = Vec::new(); + + label.push_str("fn "); + label.push_str(name); + label.push('('); + for (index, (pattern, typ, _)) in func_meta.parameters.0.iter().enumerate() { + if index > 0 { + label.push_str(", "); + } + + if has_self && index == 0 { + if let Type::MutableReference(..) = typ { + label.push_str("&mut self"); + } else { + label.push_str("self"); + } + } else { + let parameter_start = label.chars().count(); + + self.hir_pattern_to_argument(pattern, &mut label); + label.push_str(": "); + label.push_str(&typ.to_string()); + + let parameter_end = label.chars().count(); + + parameters.push(ParameterInformation { + label: ParameterLabel::LabelOffsets([ + parameter_start as u32, + parameter_end as u32, + ]), + documentation: None, + }); + } + } + label.push(')'); + + match &func_meta.return_type { + FunctionReturnType::Default(_) => (), + FunctionReturnType::Ty(typ) => { + label.push_str(" -> "); + label.push_str(&typ.to_string()); + } + } + + SignatureInformation { + label, + documentation: None, + parameters: Some(parameters), + active_parameter, + } + } + + fn function_type_signature_information( + &self, + args: &[Type], + return_type: &Type, + unconstrained: bool, + active_parameter: Option, + ) -> SignatureInformation { + let mut label = String::new(); + let mut parameters = Vec::new(); + + if unconstrained { + label.push_str("unconstrained "); + } + label.push_str("fn("); + for (index, typ) in args.iter().enumerate() { + if index > 0 { + label.push_str(", "); + } + + let parameter_start = label.chars().count(); + label.push_str(&typ.to_string()); + let parameter_end = label.chars().count(); + + parameters.push(ParameterInformation { + label: ParameterLabel::LabelOffsets([parameter_start as u32, parameter_end as u32]), + documentation: None, + }); + } + label.push(')'); + + if let Type::Unit = return_type { + // Nothing + } else { + label.push_str(" -> "); + label.push_str(&return_type.to_string()); + } + + SignatureInformation { + label, + documentation: None, + parameters: Some(parameters), + active_parameter, + } + } + + 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 set_signature_help(&mut self, signature_information: SignatureInformation) { + let signature_help = SignatureHelp { + active_parameter: signature_information.active_parameter, + signatures: vec![signature_information], + active_signature: Some(0), + }; + self.signature_help = Some(signature_help); + } + + fn includes_span(&self, span: Span) -> bool { + span.start() as usize <= self.byte_index && self.byte_index <= span.end() as usize + } +} diff --git a/noir/noir-repo/tooling/lsp/src/requests/signature_help/tests.rs b/noir/noir-repo/tooling/lsp/src/requests/signature_help/tests.rs new file mode 100644 index 000000000000..c48ee1590847 --- /dev/null +++ b/noir/noir-repo/tooling/lsp/src/requests/signature_help/tests.rs @@ -0,0 +1,196 @@ +#[cfg(test)] +mod signature_help_tests { + use crate::{ + notifications::on_did_open_text_document, requests::on_signature_help_request, test_utils, + }; + + use lsp_types::{ + DidOpenTextDocumentParams, ParameterLabel, Position, SignatureHelp, SignatureHelpParams, + TextDocumentIdentifier, TextDocumentItem, TextDocumentPositionParams, + WorkDoneProgressParams, + }; + use tokio::test; + + async fn get_signature_help(src: &str) -> SignatureHelp { + let (mut state, noir_text_document) = test_utils::init_lsp_server("document_symbol").await; + + let (line, column) = src + .lines() + .enumerate() + .find_map(|(line_index, line)| { + line.find(">|<").map(|char_index| (line_index, char_index)) + }) + .expect("Expected to find one >|< in the source code"); + + let src = src.replace(">|<", ""); + + 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(), + }, + }, + ); + + on_signature_help_request( + &mut state, + SignatureHelpParams { + context: None, + text_document_position_params: 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 }, + }, + ) + .await + .expect("Could not execute on_signature_help_request") + .unwrap() + } + + fn check_label(signature_label: &str, parameter_label: &ParameterLabel, expected_string: &str) { + let ParameterLabel::LabelOffsets(offsets) = parameter_label else { + panic!("Expected label to be LabelOffsets, got {:?}", parameter_label); + }; + + assert_eq!(&signature_label[offsets[0] as usize..offsets[1] as usize], expected_string); + } + + #[test] + async fn test_signature_help_for_call_at_first_argument() { + let src = r#" + fn foo(x: i32, y: Field) -> u32 { 0 } + fn wrapper(x: u32) {} + + fn bar() { + wrapper(foo(>|<1, 2)); + } + "#; + + let signature_help = get_signature_help(src).await; + assert_eq!(signature_help.signatures.len(), 1); + + let signature = &signature_help.signatures[0]; + assert_eq!(signature.label, "fn foo(x: i32, y: Field) -> u32"); + + let params = signature.parameters.as_ref().unwrap(); + assert_eq!(params.len(), 2); + + check_label(&signature.label, ¶ms[0].label, "x: i32"); + check_label(&signature.label, ¶ms[1].label, "y: Field"); + + assert_eq!(signature.active_parameter, Some(0)); + } + + #[test] + async fn test_signature_help_for_call_between_arguments() { + let src = r#" + fn foo(x: i32, y: Field) -> u32 { 0 } + + fn bar() { + foo(1,>|< 2); + } + "#; + + let signature_help = get_signature_help(src).await; + assert_eq!(signature_help.signatures.len(), 1); + + let signature = &signature_help.signatures[0]; + assert_eq!(signature.active_parameter, Some(1)); + } + + #[test] + async fn test_signature_help_for_call_at_second_argument() { + let src = r#" + fn foo(x: i32, y: Field) -> u32 { 0 } + + fn bar() { + foo(1, >|<2); + } + "#; + + let signature_help = get_signature_help(src).await; + assert_eq!(signature_help.signatures.len(), 1); + + let signature = &signature_help.signatures[0]; + assert_eq!(signature.active_parameter, Some(1)); + } + + #[test] + async fn test_signature_help_for_call_past_last_argument() { + let src = r#" + fn foo(x: i32, y: Field) -> u32 { 0 } + + fn bar() { + foo(1, 2, >|<); + } + "#; + + let signature_help = get_signature_help(src).await; + assert_eq!(signature_help.signatures.len(), 1); + + let signature = &signature_help.signatures[0]; + assert_eq!(signature.active_parameter, Some(2)); + } + + #[test] + async fn test_signature_help_for_method_call() { + let src = r#" + struct Foo {} + + impl Foo { + fn foo(self, x: i32, y: Field) -> u32 { 0 } + } + + fn wrapper(x: u32) {} + + fn bar(f: Foo) { + wrapper(f.foo(>|<1, 2)); + } + "#; + + let signature_help = get_signature_help(src).await; + assert_eq!(signature_help.signatures.len(), 1); + + let signature = &signature_help.signatures[0]; + assert_eq!(signature.label, "fn foo(self, x: i32, y: Field) -> u32"); + + let params = signature.parameters.as_ref().unwrap(); + assert_eq!(params.len(), 2); + + check_label(&signature.label, ¶ms[0].label, "x: i32"); + check_label(&signature.label, ¶ms[1].label, "y: Field"); + + assert_eq!(signature.active_parameter, Some(0)); + } + + #[test] + async fn test_signature_help_for_fn_call() { + let src = r#" + fn foo(x: i32, y: Field) -> u32 { 0 } + + fn bar() { + let f = foo; + f(>|<1, 2); + } + "#; + + let signature_help = get_signature_help(src).await; + assert_eq!(signature_help.signatures.len(), 1); + + let signature = &signature_help.signatures[0]; + assert_eq!(signature.label, "fn(i32, Field) -> u32"); + + let params = signature.parameters.as_ref().unwrap(); + assert_eq!(params.len(), 2); + + check_label(&signature.label, ¶ms[0].label, "i32"); + check_label(&signature.label, ¶ms[1].label, "Field"); + + assert_eq!(signature.active_parameter, Some(0)); + } +} diff --git a/noir/noir-repo/tooling/lsp/src/requests/signature_help/traversal.rs b/noir/noir-repo/tooling/lsp/src/requests/signature_help/traversal.rs new file mode 100644 index 000000000000..22f92a86124a --- /dev/null +++ b/noir/noir-repo/tooling/lsp/src/requests/signature_help/traversal.rs @@ -0,0 +1,304 @@ +/// This file includes the signature help logic that's just about +/// traversing the AST without any additional logic. +use super::SignatureFinder; + +use noirc_frontend::{ + ast::{ + ArrayLiteral, AssignStatement, BlockExpression, CastExpression, ConstrainStatement, + ConstructorExpression, Expression, ExpressionKind, ForLoopStatement, ForRange, + IfExpression, IndexExpression, InfixExpression, LValue, Lambda, LetStatement, Literal, + MemberAccessExpression, NoirFunction, NoirTrait, NoirTraitImpl, Statement, StatementKind, + TraitImplItem, TraitItem, TypeImpl, + }, + parser::{Item, ItemKind}, + ParsedModule, +}; + +impl<'a> SignatureFinder<'a> { + pub(super) fn find_in_parsed_module(&mut self, parsed_module: &ParsedModule) { + for item in &parsed_module.items { + self.find_in_item(item); + } + } + + pub(super) fn find_in_item(&mut self, item: &Item) { + if !self.includes_span(item.span) { + return; + } + + match &item.kind { + ItemKind::Submodules(parsed_sub_module) => { + self.find_in_parsed_module(&parsed_sub_module.contents); + } + 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), + ItemKind::Trait(noir_trait) => self.find_in_noir_trait(noir_trait), + ItemKind::Import(..) + | ItemKind::TypeAlias(_) + | ItemKind::Struct(_) + | ItemKind::ModuleDecl(_) => (), + } + } + + pub(super) fn find_in_noir_function(&mut self, noir_function: &NoirFunction) { + self.find_in_block_expression(&noir_function.def.body); + } + + pub(super) fn find_in_noir_trait_impl(&mut self, noir_trait_impl: &NoirTraitImpl) { + for item in &noir_trait_impl.items { + self.find_in_trait_impl_item(item); + } + } + + pub(super) 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 { .. } => (), + } + } + + pub(super) fn find_in_type_impl(&mut self, type_impl: &TypeImpl) { + for (method, span) in &type_impl.methods { + if self.includes_span(*span) { + self.find_in_noir_function(method); + } + } + } + + pub(super) fn find_in_noir_trait(&mut self, noir_trait: &NoirTrait) { + for item in &noir_trait.items { + self.find_in_trait_item(item); + } + } + + pub(super) fn find_in_trait_item(&mut self, trait_item: &TraitItem) { + match trait_item { + TraitItem::Function { body, .. } => { + if let Some(body) = body { + self.find_in_block_expression(body); + }; + } + TraitItem::Constant { default_value, .. } => { + if let Some(default_value) = default_value { + self.find_in_expression(default_value); + } + } + TraitItem::Type { .. } => (), + } + } + + pub(super) fn find_in_block_expression(&mut self, block_expression: &BlockExpression) { + for statement in &block_expression.statements { + if self.includes_span(statement.span) { + self.find_in_statement(statement); + } + } + } + + pub(super) fn find_in_statement(&mut self, statement: &Statement) { + if !self.includes_span(statement.span) { + return; + } + + match &statement.kind { + StatementKind::Let(let_statement) => { + self.find_in_let_statement(let_statement); + } + StatementKind::Constrain(constrain_statement) => { + self.find_in_constrain_statement(constrain_statement); + } + StatementKind::Expression(expression) => { + self.find_in_expression(expression); + } + StatementKind::Assign(assign_statement) => { + self.find_in_assign_statement(assign_statement); + } + StatementKind::For(for_loop_statement) => { + self.find_in_for_loop_statement(for_loop_statement); + } + StatementKind::Comptime(statement) => { + self.find_in_statement(statement); + } + StatementKind::Semi(expression) => { + self.find_in_expression(expression); + } + StatementKind::Break | StatementKind::Continue | StatementKind::Error => (), + } + } + + pub(super) fn find_in_let_statement(&mut self, let_statement: &LetStatement) { + self.find_in_expression(&let_statement.expression); + } + + pub(super) 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); + } + } + + pub(super) fn find_in_assign_statement(&mut self, assign_statement: &AssignStatement) { + self.find_in_lvalue(&assign_statement.lvalue); + self.find_in_expression(&assign_statement.expression); + } + + pub(super) fn find_in_for_loop_statement(&mut self, for_loop_statement: &ForLoopStatement) { + self.find_in_for_range(&for_loop_statement.range); + self.find_in_expression(&for_loop_statement.block); + } + + pub(super) 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), + } + } + + pub(super) 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), + } + } + + pub(super) fn find_in_expressions(&mut self, expressions: &[Expression]) { + for expression in expressions { + self.find_in_expression(expression); + } + } + + pub(super) fn find_in_expression(&mut self, expression: &Expression) { + match &expression.kind { + ExpressionKind::Literal(literal) => self.find_in_literal(literal), + ExpressionKind::Block(block_expression) => { + self.find_in_block_expression(block_expression); + } + ExpressionKind::Prefix(prefix_expression) => { + self.find_in_expression(&prefix_expression.rhs); + } + ExpressionKind::Index(index_expression) => { + self.find_in_index_expression(index_expression); + } + ExpressionKind::Call(call_expression) => { + self.find_in_call_expression(call_expression, expression.span); + } + ExpressionKind::MethodCall(method_call_expression) => { + self.find_in_method_call_expression(method_call_expression, expression.span); + } + ExpressionKind::Constructor(constructor_expression) => { + self.find_in_constructor_expression(constructor_expression); + } + ExpressionKind::MemberAccess(member_access_expression) => { + self.find_in_member_access_expression(member_access_expression); + } + ExpressionKind::Cast(cast_expression) => { + self.find_in_cast_expression(cast_expression); + } + ExpressionKind::Infix(infix_expression) => { + self.find_in_infix_expression(infix_expression); + } + ExpressionKind::If(if_expression) => { + self.find_in_if_expression(if_expression); + } + ExpressionKind::Tuple(expressions) => { + self.find_in_expressions(expressions); + } + ExpressionKind::Lambda(lambda) => self.find_in_lambda(lambda), + ExpressionKind::Parenthesized(expression) => { + self.find_in_expression(expression); + } + ExpressionKind::Unquote(expression) => { + self.find_in_expression(expression); + } + ExpressionKind::Comptime(block_expression, _) => { + self.find_in_block_expression(block_expression); + } + ExpressionKind::Unsafe(block_expression, _) => { + self.find_in_block_expression(block_expression); + } + ExpressionKind::Variable(_) + | ExpressionKind::AsTraitPath(_) + | ExpressionKind::Quote(_) + | ExpressionKind::Resolved(_) + | ExpressionKind::Error => (), + } + } + + pub(super) 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 => (), + } + } + + pub(super) 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); + } + } + } + + pub(super) fn find_in_index_expression(&mut self, index_expression: &IndexExpression) { + self.find_in_expression(&index_expression.collection); + self.find_in_expression(&index_expression.index); + } + + pub(super) fn find_in_constructor_expression( + &mut self, + constructor_expression: &ConstructorExpression, + ) { + for (_field_name, expression) in &constructor_expression.fields { + self.find_in_expression(expression); + } + } + + pub(super) fn find_in_member_access_expression( + &mut self, + member_access_expression: &MemberAccessExpression, + ) { + self.find_in_expression(&member_access_expression.lhs); + } + + pub(super) fn find_in_cast_expression(&mut self, cast_expression: &CastExpression) { + self.find_in_expression(&cast_expression.lhs); + } + + pub(super) fn find_in_infix_expression(&mut self, infix_expression: &InfixExpression) { + self.find_in_expression(&infix_expression.lhs); + self.find_in_expression(&infix_expression.rhs); + } + + pub(super) fn find_in_if_expression(&mut self, if_expression: &IfExpression) { + self.find_in_expression(&if_expression.condition); + self.find_in_expression(&if_expression.consequence); + + if let Some(alternative) = &if_expression.alternative { + self.find_in_expression(alternative); + } + } + + pub(super) fn find_in_lambda(&mut self, lambda: &Lambda) { + self.find_in_expression(&lambda.body); + } +} diff --git a/noir/noir-repo/tooling/lsp/src/types.rs b/noir/noir-repo/tooling/lsp/src/types.rs index 5afda0d292ae..3ac1f35e18e7 100644 --- a/noir/noir-repo/tooling/lsp/src/types.rs +++ b/noir/noir-repo/tooling/lsp/src/types.rs @@ -1,7 +1,7 @@ use fm::FileId; use lsp_types::{ CompletionOptions, DeclarationCapability, DefinitionOptions, DocumentSymbolOptions, - HoverOptions, InlayHintOptions, OneOf, ReferencesOptions, RenameOptions, + HoverOptions, InlayHintOptions, OneOf, ReferencesOptions, RenameOptions, SignatureHelpOptions, TypeDefinitionProviderCapability, }; use noirc_driver::DebugFile; @@ -161,6 +161,10 @@ pub(crate) struct ServerCapabilities { /// The server provides completion support. #[serde(skip_serializing_if = "Option::is_none")] pub(crate) completion_provider: Option>, + + /// The server provides signature help support. + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) signature_help_provider: Option>, } #[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)] diff --git a/noir/noir-repo/tooling/nargo/Cargo.toml b/noir/noir-repo/tooling/nargo/Cargo.toml index 56e88dacf2d6..046eca88099b 100644 --- a/noir/noir-repo/tooling/nargo/Cargo.toml +++ b/noir/noir-repo/tooling/nargo/Cargo.toml @@ -7,6 +7,9 @@ edition.workspace = true rust-version.workspace = true license.workspace = true +[lints] +workspace = true + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] diff --git a/noir/noir-repo/tooling/nargo/src/ops/execute.rs b/noir/noir-repo/tooling/nargo/src/ops/execute.rs index c9cc60d03d93..2e214c4e4259 100644 --- a/noir/noir-repo/tooling/nargo/src/ops/execute.rs +++ b/noir/noir-repo/tooling/nargo/src/ops/execute.rs @@ -139,9 +139,9 @@ impl<'a, F: AcirField, B: BlackBoxFunctionSolver, E: ForeignCallExecutor> }); // Set current function to the circuit we are about to execute - self.current_function_index = call_info.id as usize; + self.current_function_index = call_info.id.as_usize(); // Execute the ACIR call - let acir_to_call = &self.functions[call_info.id as usize]; + let acir_to_call = &self.functions[call_info.id.as_usize()]; let initial_witness = call_info.initial_witness; let call_solved_witness = self.execute_circuit(initial_witness)?; @@ -163,7 +163,7 @@ impl<'a, F: AcirField, B: BlackBoxFunctionSolver, E: ForeignCallExecutor> } } acvm.resolve_pending_acir_call(call_resolved_outputs); - self.witness_stack.push(call_info.id, call_solved_witness); + self.witness_stack.push(call_info.id.0, call_solved_witness); } } } diff --git a/noir/noir-repo/tooling/nargo_cli/Cargo.toml b/noir/noir-repo/tooling/nargo_cli/Cargo.toml index dabb779ae8d2..f3d9f92caaaa 100644 --- a/noir/noir-repo/tooling/nargo_cli/Cargo.toml +++ b/noir/noir-repo/tooling/nargo_cli/Cargo.toml @@ -8,6 +8,9 @@ edition.workspace = true rust-version.workspace = true license.workspace = true +[lints] +workspace = true + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # Rename binary from `nargo_cli` to `nargo` diff --git a/noir/noir-repo/tooling/nargo_cli/build.rs b/noir/noir-repo/tooling/nargo_cli/build.rs index 3f8cd055569a..4dcfccdf0857 100644 --- a/noir/noir-repo/tooling/nargo_cli/build.rs +++ b/noir/noir-repo/tooling/nargo_cli/build.rs @@ -207,7 +207,7 @@ fn generate_compile_success_empty_tests(test_file: &mut File, test_data_dir: &Pa let json: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap_or_else(|e| {{ panic!("JSON was not well-formatted {:?}\n\n{:?}", e, std::str::from_utf8(&output.stdout)) }}); - let num_opcodes = &json["programs"][0]["functions"][0]["acir_opcodes"]; + let num_opcodes = &json["programs"][0]["functions"][0]["opcodes"]; assert_eq!(num_opcodes.as_u64().expect("number of opcodes should fit in a u64"), 0); "#; diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/info_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/info_cmd.rs index a6395d1c8c95..7e4a8b4117dd 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/info_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/info_cmd.rs @@ -100,8 +100,7 @@ pub(crate) fn run(args: InfoCommand, config: NargoConfig) -> Result<(), CliError } else { // Otherwise print human-readable table. if !info_report.programs.is_empty() { - let mut program_table = - table!([Fm->"Package", Fm->"Function", Fm->"Expression Width", Fm->"ACIR Opcodes"]); + let mut program_table = table!([Fm->"Package", Fm->"Function", Fm->"Expression Width", Fm->"ACIR Opcodes", Fm->"Brillig Opcodes"]); for program_info in info_report.programs { let program_rows: Vec = program_info.into(); @@ -176,18 +175,32 @@ struct ProgramInfo { #[serde(skip)] expression_width: ExpressionWidth, functions: Vec, + #[serde(skip)] + unconstrained_functions_opcodes: usize, + unconstrained_functions: Vec, } impl From for Vec { fn from(program_info: ProgramInfo) -> Self { - vecmap(program_info.functions, |function| { + let mut main = vecmap(program_info.functions, |function| { row![ Fm->format!("{}", program_info.package_name), Fc->format!("{}", function.name), format!("{:?}", program_info.expression_width), - Fc->format!("{}", function.acir_opcodes), + Fc->format!("{}", function.opcodes), + Fc->format!("{}", program_info.unconstrained_functions_opcodes), ] - }) + }); + main.extend(vecmap(program_info.unconstrained_functions, |function| { + row![ + Fm->format!("{}", program_info.package_name), + Fc->format!("{}", function.name), + format!("N/A", ), + Fc->format!("N/A"), + Fc->format!("{}", function.opcodes), + ] + })); + main } } @@ -203,7 +216,7 @@ struct ContractInfo { #[derive(Debug, Serialize)] struct FunctionInfo { name: String, - acir_opcodes: usize, + opcodes: usize, } impl From for Vec { @@ -213,7 +226,7 @@ impl From for Vec { Fm->format!("{}", contract_info.name), Fc->format!("{}", function.name), format!("{:?}", contract_info.expression_width), - Fc->format!("{}", function.acir_opcodes), + Fc->format!("{}", function.opcodes), ] }) } @@ -231,9 +244,35 @@ fn count_opcodes_and_gates_in_program( .enumerate() .map(|(i, function)| FunctionInfo { name: compiled_program.names[i].clone(), - acir_opcodes: function.opcodes.len(), + opcodes: function.opcodes.len(), }) .collect(); - ProgramInfo { package_name: package.name.to_string(), expression_width, functions } + let opcodes_len: Vec = compiled_program + .bytecode + .unconstrained_functions + .iter() + .map(|func| func.bytecode.len()) + .collect(); + let unconstrained_functions_opcodes = compiled_program + .bytecode + .unconstrained_functions + .into_par_iter() + .map(|function| function.bytecode.len()) + .sum(); + let unconstrained_info: Vec = compiled_program + .brillig_names + .clone() + .iter() + .zip(opcodes_len) + .map(|(name, len)| FunctionInfo { name: name.clone(), opcodes: len }) + .collect(); + + ProgramInfo { + package_name: package.name.to_string(), + expression_width, + functions, + unconstrained_functions_opcodes, + unconstrained_functions: unconstrained_info, + } } diff --git a/noir/noir-repo/tooling/nargo_fmt/Cargo.toml b/noir/noir-repo/tooling/nargo_fmt/Cargo.toml index 05b2fdb7d526..9868f2590975 100644 --- a/noir/noir-repo/tooling/nargo_fmt/Cargo.toml +++ b/noir/noir-repo/tooling/nargo_fmt/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true rust-version.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] bytecount = "0.6.3" noirc_frontend.workspace = true 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 41b15069546b..4fee7d3e197e 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/rewrite/expr.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/rewrite/expr.rs @@ -168,6 +168,9 @@ pub(crate) fn rewrite( ExpressionKind::Comptime(block, block_span) => { format!("comptime {}", rewrite_block(visitor, block, block_span)) } + ExpressionKind::Unsafe(block, block_span) => { + format!("unsafe {}", rewrite_block(visitor, block, block_span)) + } ExpressionKind::Error => unreachable!(), ExpressionKind::Resolved(_) => { unreachable!("ExpressionKind::Resolved should only emitted by the comptime interpreter") 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 b586f32a6feb..86cc618eb33d 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/rewrite/typ.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/rewrite/typ.rs @@ -37,7 +37,9 @@ pub(crate) fn rewrite(visitor: &FmtVisitor, _shape: Shape, typ: UnresolvedType) format!("({types})") } } - UnresolvedTypeData::Function(args, return_type, env) => { + UnresolvedTypeData::Function(args, return_type, env, unconstrained) => { + let unconstrained = if unconstrained { "unconstrained " } else { "" }; + let env = if span_is_empty(env.span.unwrap()) { "".into() } else { @@ -53,7 +55,7 @@ pub(crate) fn rewrite(visitor: &FmtVisitor, _shape: Shape, typ: UnresolvedType) let return_type = rewrite(visitor, _shape, *return_type); - format!("fn{env}({args}) -> {return_type}") + format!("{unconstrained}fn{env}({args}) -> {return_type}") } UnresolvedTypeData::Resolved(_) => { unreachable!("Unexpected macro expansion of a type in nargo fmt input") diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/expected/fn.nr b/noir/noir-repo/tooling/nargo_fmt/tests/expected/fn.nr index 4dde9a1b3ece..73248d0c04ff 100644 --- a/noir/noir-repo/tooling/nargo_fmt/tests/expected/fn.nr +++ b/noir/noir-repo/tooling/nargo_fmt/tests/expected/fn.nr @@ -69,3 +69,5 @@ fn id_two(x: [Field; I]) -> [Field; I] {} fn whitespace_before_generics(foo: T) {} fn more_whitespace_before_generics(foo: T) {} + +fn with_unconstrained(x: unconstrained fn() -> ()) {} diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/expected/unsafe.nr b/noir/noir-repo/tooling/nargo_fmt/tests/expected/unsafe.nr new file mode 100644 index 000000000000..7d733c203de0 --- /dev/null +++ b/noir/noir-repo/tooling/nargo_fmt/tests/expected/unsafe.nr @@ -0,0 +1,8 @@ +fn main(x: pub u8, y: u8) { + unsafe {} + + unsafe { + assert_eq(x, y); + } +} + diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/input/fn.nr b/noir/noir-repo/tooling/nargo_fmt/tests/input/fn.nr index 16ed95a540d6..8db6022808f2 100644 --- a/noir/noir-repo/tooling/nargo_fmt/tests/input/fn.nr +++ b/noir/noir-repo/tooling/nargo_fmt/tests/input/fn.nr @@ -54,3 +54,5 @@ fn whitespace_before_generics < T > (foo: T) {} fn more_whitespace_before_generics < T > (foo: T) {} + +fn with_unconstrained(x: unconstrained fn() -> ()) {} diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/input/unsafe.nr b/noir/noir-repo/tooling/nargo_fmt/tests/input/unsafe.nr new file mode 100644 index 000000000000..6e12ef975eef --- /dev/null +++ b/noir/noir-repo/tooling/nargo_fmt/tests/input/unsafe.nr @@ -0,0 +1,8 @@ +fn main(x: pub u8, y: u8) { + unsafe { } + + unsafe { + assert_eq(x, y); + } +} + diff --git a/noir/noir-repo/tooling/nargo_toml/Cargo.toml b/noir/noir-repo/tooling/nargo_toml/Cargo.toml index 7c9faa4562a9..e4766e448595 100644 --- a/noir/noir-repo/tooling/nargo_toml/Cargo.toml +++ b/noir/noir-repo/tooling/nargo_toml/Cargo.toml @@ -7,6 +7,9 @@ edition.workspace = true rust-version.workspace = true license.workspace = true +[lints] +workspace = true + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] 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 a5593cc284c7..661d71fb9c34 100644 --- a/noir/noir-repo/tooling/noir_js_backend_barretenberg/package.json +++ b/noir/noir-repo/tooling/noir_js_backend_barretenberg/package.json @@ -41,7 +41,7 @@ "lint": "NODE_NO_WARNINGS=1 eslint . --ext .ts --ignore-path ./.eslintignore --max-warnings 0" }, "dependencies": { - "@aztec/bb.js": "portal:../../../../barretenberg/ts", + "@aztec/bb.js": "0.48.0", "@noir-lang/types": "workspace:*", "fflate": "^0.8.0" }, diff --git a/noir/noir-repo/tooling/noirc_abi/Cargo.toml b/noir/noir-repo/tooling/noirc_abi/Cargo.toml index 4c0c1f75e420..a7baf334bff3 100644 --- a/noir/noir-repo/tooling/noirc_abi/Cargo.toml +++ b/noir/noir-repo/tooling/noirc_abi/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true rust-version.workspace = true license.workspace = true +[lints] +workspace = true + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] diff --git a/noir/noir-repo/tooling/noirc_abi/src/arbitrary.rs b/noir/noir-repo/tooling/noirc_abi/src/arbitrary.rs index aecb620b79d0..b79f232d9e8b 100644 --- a/noir/noir-repo/tooling/noirc_abi/src/arbitrary.rs +++ b/noir/noir-repo/tooling/noirc_abi/src/arbitrary.rs @@ -107,10 +107,8 @@ pub(super) fn arb_abi_type() -> BoxedStrategy { (1..10u32, inner.clone()) .prop_map(|(length, typ)| { AbiType::Array { length, typ: Box::new(typ) } }) .boxed(), - prop::collection::vec(inner.clone(), 1..10) - .prop_map(|fields| { AbiType::Tuple { fields } }) - .boxed(), - (".*", prop::collection::vec((".+", inner), 1..10)) + vec(inner.clone(), 1..10).prop_map(|fields| { AbiType::Tuple { fields } }).boxed(), + (".*", vec((".+", inner), 1..10)) .prop_map(|(path, mut fields)| { // Require that all field names are unique. ensure_unique_strings(fields.iter_mut().map(|(field_name, _)| field_name)); @@ -141,7 +139,7 @@ fn arb_abi_param(typ: AbiType) -> SBoxedStrategy { prop_compose! { pub(super) fn arb_abi_and_input_map() - (mut parameters_with_values in proptest::collection::vec(arb_abi_param_and_value(), 0..100), return_type: Option) + (mut parameters_with_values in vec(arb_abi_param_and_value(), 0..100), return_type: Option) -> (Abi, InputMap) { // Require that all parameter names are unique. ensure_unique_strings(parameters_with_values.iter_mut().map(|(param_name,_)| &mut param_name.name)); 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/src/lib.rs b/noir/noir-repo/tooling/noirc_abi_wasm/src/lib.rs index ef4a468b6616..b0c11979d176 100644 --- a/noir/noir-repo/tooling/noirc_abi_wasm/src/lib.rs +++ b/noir/noir-repo/tooling/noirc_abi_wasm/src/lib.rs @@ -121,8 +121,7 @@ pub fn abi_decode(abi: JsAbi, witness_map: JsWitnessMap) -> Result::from_serde(&return_struct) - .map_err(|err| err.to_string().into()) + ::from_serde(&return_struct).map_err(|err| err.to_string().into()) } #[wasm_bindgen(js_name = serializeWitness)] @@ -155,7 +154,7 @@ pub fn abi_decode_error( AbiErrorType::Custom(typ) => { let input_value = decode_value(&mut raw_error.data.into_iter(), &typ)?; let json_types = JsonTypes::try_from_input_value(&input_value, &typ)?; - ::from_serde(&json_types) + ::from_serde(&json_types) .map_err(|err| err.to_string().into()) } } diff --git a/noir/noir-repo/tooling/noirc_artifacts/Cargo.toml b/noir/noir-repo/tooling/noirc_artifacts/Cargo.toml index 4249604f9492..13ff68e423a7 100644 --- a/noir/noir-repo/tooling/noirc_artifacts/Cargo.toml +++ b/noir/noir-repo/tooling/noirc_artifacts/Cargo.toml @@ -7,6 +7,9 @@ edition.workspace = true rust-version.workspace = true license.workspace = true +[lints] +workspace = true + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] diff --git a/noir/noir-repo/tooling/noirc_artifacts/src/program.rs b/noir/noir-repo/tooling/noirc_artifacts/src/program.rs index 91f021574149..ffffc6345d58 100644 --- a/noir/noir-repo/tooling/noirc_artifacts/src/program.rs +++ b/noir/noir-repo/tooling/noirc_artifacts/src/program.rs @@ -37,6 +37,8 @@ pub struct ProgramArtifact { pub file_map: BTreeMap, pub names: Vec, + /// Names of the unconstrained functions in the program. + pub brillig_names: Vec, } impl From for ProgramArtifact { @@ -49,6 +51,7 @@ impl From for ProgramArtifact { debug_symbols: ProgramDebugInfo { debug_infos: compiled_program.debug }, file_map: compiled_program.file_map, names: compiled_program.names, + brillig_names: compiled_program.brillig_names, } } } @@ -64,6 +67,7 @@ impl From for CompiledProgram { file_map: program.file_map, warnings: vec![], names: program.names, + brillig_names: program.brillig_names, } } } diff --git a/noir/noir-repo/tooling/profiler/Cargo.toml b/noir/noir-repo/tooling/profiler/Cargo.toml index 0ccd56b791f4..136775d58317 100644 --- a/noir/noir-repo/tooling/profiler/Cargo.toml +++ b/noir/noir-repo/tooling/profiler/Cargo.toml @@ -8,6 +8,9 @@ license.workspace = true rust-version.workspace = true repository.workspace = true +[lints] +workspace = true + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [[bin]] diff --git a/noir/noir-repo/tooling/profiler/src/cli/gates_flamegraph_cmd.rs b/noir/noir-repo/tooling/profiler/src/cli/gates_flamegraph_cmd.rs index e072c63f2749..0fa12239d050 100644 --- a/noir/noir-repo/tooling/profiler/src/cli/gates_flamegraph_cmd.rs +++ b/noir/noir-repo/tooling/profiler/src/cli/gates_flamegraph_cmd.rs @@ -128,7 +128,7 @@ mod tests { } impl GatesProvider for TestGateProvider { - fn get_gates(&self, artifact_path: &std::path::Path) -> eyre::Result { + fn get_gates(&self, artifact_path: &Path) -> eyre::Result { let response = self .mock_responses .get(artifact_path) @@ -173,6 +173,7 @@ mod tests { debug_symbols: ProgramDebugInfo { debug_infos: vec![DebugInfo::default()] }, file_map: BTreeMap::default(), names: vec!["main".to_string()], + brillig_names: Vec::new(), }; // Write the artifact to a file diff --git a/noir/noir-repo/tooling/profiler/src/cli/opcodes_flamegraph_cmd.rs b/noir/noir-repo/tooling/profiler/src/cli/opcodes_flamegraph_cmd.rs index f91e50d87169..d7f3cbb9b85d 100644 --- a/noir/noir-repo/tooling/profiler/src/cli/opcodes_flamegraph_cmd.rs +++ b/noir/noir-repo/tooling/profiler/src/cli/opcodes_flamegraph_cmd.rs @@ -188,6 +188,7 @@ mod tests { debug_symbols: ProgramDebugInfo { debug_infos: vec![DebugInfo::default()] }, file_map: BTreeMap::default(), names: vec!["main".to_string()], + brillig_names: Vec::new(), }; // Write the artifact to a file @@ -228,6 +229,7 @@ mod tests { debug_symbols: ProgramDebugInfo { debug_infos: vec![DebugInfo::default()] }, file_map: BTreeMap::default(), names: vec!["main".to_string()], + brillig_names: vec!["main".to_string()], }; // Write the artifact to a file diff --git a/noir/noir-repo/tooling/profiler/src/opcode_formatter.rs b/noir/noir-repo/tooling/profiler/src/opcode_formatter.rs index 772c87ad1cb0..edb3e1b0f08b 100644 --- a/noir/noir-repo/tooling/profiler/src/opcode_formatter.rs +++ b/noir/noir-repo/tooling/profiler/src/opcode_formatter.rs @@ -100,7 +100,7 @@ fn format_binary_field_op(op: &BinaryFieldOp) -> String { } } -fn format_binary_int(op: &acir::brillig::BinaryIntOp) -> String { +fn format_binary_int(op: &BinaryIntOp) -> String { match op { BinaryIntOp::Add => "add".to_string(), BinaryIntOp::Sub => "sub".to_string(), diff --git a/noir/noir-repo/yarn.lock b/noir/noir-repo/yarn.lock index f77e9f7e72ef..f10b5b2cb671 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.48.0": + version: 0.48.0 + resolution: "@aztec/bb.js@npm:0.48.0" 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: e06b864a5acea4299dfa350f732dd05a807968678fd3bc3b9c699f9bc50aef1525e2492dfacf9965270082c23b04653f55c40a34f75ead11a52e3fc5512ddce7 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.48.0 "@noir-lang/types": "workspace:*" "@types/node": ^20.6.2 "@types/prettier": ^3