From 7475871a99a051b1c1ca2ade40ee00dedea5d92a Mon Sep 17 00:00:00 2001 From: AztecBot Date: Wed, 28 Aug 2024 22:57:22 +0000 Subject: [PATCH] [1 changes] feat(meta): Comptime keccak (https://github.com/noir-lang/noir/pull/5854) feat(optimization): Avoid merging identical (by ID) arrays (https://github.com/noir-lang/noir/pull/5853) feat: add `FunctionDef::body` (https://github.com/noir-lang/noir/pull/5825) fix(sha256): Fix upper bound when building msg block and delay final block compression under certain cases (https://github.com/noir-lang/noir/pull/5838) feat: remove unnecessary copying of vector size during reversal (https://github.com/noir-lang/noir/pull/5852) chore: Add missing cases to arithmetic generics (https://github.com/noir-lang/noir/pull/5841) feat: warn on unused imports (https://github.com/noir-lang/noir/pull/5847) chore: add documentation to `to_be_bytes`, etc. (https://github.com/noir-lang/noir/pull/5843) feat: simplify constant calls to `poseidon2_permutation`, `schnorr_verify` and `embedded_curve_add` (https://github.com/noir-lang/noir/pull/5140) chore: don't require empty `Prover.toml` for programs with zero arguments but a return value (https://github.com/noir-lang/noir/pull/5845) fix!: Check unused generics are bound (https://github.com/noir-lang/noir/pull/5840) chore(perf): Simplify poseidon2 algorithm (https://github.com/noir-lang/noir/pull/5811) chore: redo typo PR by nnsW3 (https://github.com/noir-lang/noir/pull/5834) fix(sha256): Perform compression per block and utilize ROM instead of RAM when setting up the message block (https://github.com/noir-lang/noir/pull/5760) chore(perf): Update to stdlib keccak for reduced Brillig code size (https://github.com/noir-lang/noir/pull/5827) --- .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 | 222 +- noir/noir-repo/Cargo.toml | 16 +- noir/noir-repo/acvm-repo/acir/Cargo.toml | 3 + .../acvm-repo/acir/src/circuit/mod.rs | 34 +- .../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 | 5 +- .../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 | 6 +- noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs | 6 +- .../acvm/tests/solver.proptest-regressions | 6 + noir/noir-repo/acvm-repo/acvm/tests/solver.rs | 995 +++++++- 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 +- .../bn254_blackbox_solver/src/lib.rs | 2 +- .../bn254_blackbox_solver/src/poseidon2.rs | 20 +- 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/transforms/functions.rs | 6 +- .../src/transforms/note_interface.rs | 62 +- .../aztec_macros/src/transforms/storage.rs | 34 +- .../aztec_macros/src/utils/ast_utils.rs | 9 +- .../aztec_macros/src/utils/parse_utils.rs | 47 +- noir/noir-repo/compiler/fm/Cargo.toml | 3 + .../noir-repo/compiler/noirc_arena/Cargo.toml | 3 + .../compiler/noirc_driver/Cargo.toml | 7 + .../compiler/noirc_driver/src/abi_gen.rs | 2 +- .../compiler/noirc_driver/src/lib.rs | 44 +- .../compiler/noirc_driver/src/program.rs | 2 + .../compiler/noirc_errors/Cargo.toml | 3 + .../compiler/noirc_errors/src/debug_info.rs | 10 +- .../compiler/noirc_errors/src/position.rs | 2 +- .../compiler/noirc_evaluator/Cargo.toml | 10 +- .../src/brillig/brillig_gen.rs | 4 +- .../src/brillig/brillig_gen/brillig_block.rs | 2 +- .../brillig/brillig_gen/brillig_directive.rs | 4 +- .../src/brillig/brillig_ir/artifact.rs | 4 + .../src/brillig/brillig_ir/codegen_memory.rs | 14 +- .../compiler/noirc_evaluator/src/ssa.rs | 22 +- .../src/ssa/acir_gen/acir_ir/acir_variable.rs | 45 +- .../ssa/acir_gen/acir_ir/generated_acir.rs | 21 +- .../noirc_evaluator/src/ssa/acir_gen/mod.rs | 103 +- .../check_for_underconstrained_values.rs | 2 +- .../src/ssa/function_builder/mod.rs | 2 +- .../noirc_evaluator/src/ssa/ir/dfg.rs | 17 +- .../noirc_evaluator/src/ssa/ir/instruction.rs | 133 +- .../src/ssa/ir/instruction/call.rs | 45 +- .../src/ssa/ir/instruction/call/blackbox.rs | 190 ++ .../noirc_evaluator/src/ssa/ir/map.rs | 2 +- .../noirc_evaluator/src/ssa/ir/printer.rs | 2 +- .../src/ssa/opt/constant_folding.rs | 4 +- .../noirc_evaluator/src/ssa/opt/die.rs | 356 ++- .../src/ssa/opt/flatten_cfg.rs | 6 +- .../src/ssa/opt/flatten_cfg/value_merger.rs | 11 +- .../noirc_evaluator/src/ssa/opt/inlining.rs | 2 +- .../noirc_evaluator/src/ssa/opt/mem2reg.rs | 47 +- .../src/ssa/opt/remove_enable_side_effects.rs | 20 +- .../src/ssa/opt/remove_if_else.rs | 2 +- .../src/ssa/ssa_gen/context.rs | 8 +- .../noirc_evaluator/src/ssa/ssa_gen/mod.rs | 12 +- .../compiler/noirc_frontend/Cargo.toml | 2 +- .../noirc_frontend/src/ast/expression.rs | 34 +- .../noirc_frontend/src/ast/function.rs | 4 +- .../compiler/noirc_frontend/src/ast/mod.rs | 126 +- .../noirc_frontend/src/ast/statement.rs | 69 +- .../compiler/noirc_frontend/src/ast/traits.rs | 19 +- .../compiler/noirc_frontend/src/debug/mod.rs | 33 +- .../noirc_frontend/src/elaborator/comptime.rs | 25 +- .../src/elaborator/expressions.rs | 34 +- .../noirc_frontend/src/elaborator/lints.rs | 4 +- .../noirc_frontend/src/elaborator/mod.rs | 141 +- .../noirc_frontend/src/elaborator/patterns.rs | 28 +- .../noirc_frontend/src/elaborator/scope.rs | 46 +- .../src/elaborator/statements.rs | 52 +- .../src/elaborator/trait_impls.rs | 70 +- .../noirc_frontend/src/elaborator/traits.rs | 93 +- .../noirc_frontend/src/elaborator/types.rs | 535 +++-- .../noirc_frontend/src/hir/comptime/errors.rs | 34 +- .../src/hir/comptime/hir_to_display_ast.rs | 27 +- .../src/hir/comptime/interpreter.rs | 3 + .../src/hir/comptime/interpreter/builtin.rs | 739 +++++- .../interpreter/builtin/builtin_helpers.rs | 147 +- .../src/hir/comptime/interpreter/foreign.rs | 31 +- .../noirc_frontend/src/hir/comptime/value.rs | 353 ++- .../src/hir/def_collector/dc_crate.rs | 53 +- .../src/hir/def_collector/dc_mod.rs | 151 +- .../src/hir/def_collector/errors.rs | 14 +- .../noirc_frontend/src/hir/def_map/mod.rs | 3 + .../src/hir/def_map/module_data.rs | 22 +- .../src/hir/def_map/module_def.rs | 2 +- .../src/hir/resolution/errors.rs | 57 +- .../src/hir/resolution/import.rs | 25 +- .../src/hir/resolution/path_resolver.rs | 6 +- .../src/hir/type_check/errors.rs | 92 +- .../src/hir/type_check/generics.rs | 166 ++ .../noirc_frontend/src/hir/type_check/mod.rs | 11 +- .../noirc_frontend/src/hir_def/expr.rs | 16 +- .../noirc_frontend/src/hir_def/function.rs | 4 +- .../noirc_frontend/src/hir_def/traits.rs | 81 +- .../noirc_frontend/src/hir_def/types.rs | 512 +++-- .../src/hir_def/types/arithmetic.rs | 215 ++ .../noirc_frontend/src/lexer/token.rs | 48 +- .../compiler/noirc_frontend/src/locations.rs | 76 +- .../src/monomorphization/ast.rs | 31 +- .../src/monomorphization/errors.rs | 28 +- .../src/monomorphization/mod.rs | 98 +- .../src/monomorphization/printer.rs | 4 +- .../noirc_frontend/src/node_interner.rs | 422 +++- .../noirc_frontend/src/parser/errors.rs | 16 + .../compiler/noirc_frontend/src/parser/mod.rs | 2 +- .../noirc_frontend/src/parser/parser.rs | 192 +- .../src/parser/parser/function.rs | 35 +- .../src/parser/parser/lambdas.rs | 10 +- .../noirc_frontend/src/parser/parser/path.rs | 13 +- .../src/parser/parser/primitives.rs | 24 +- .../src/parser/parser/traits.rs | 121 +- .../noirc_frontend/src/parser/parser/types.rs | 88 +- .../noirc_frontend/src/resolve_locations.rs | 6 + .../compiler/noirc_frontend/src/tests.rs | 340 ++- .../compiler/noirc_printable_type/Cargo.toml | 3 + .../compiler/noirc_printable_type/src/lib.rs | 1 + noir/noir-repo/compiler/wasm/Cargo.toml | 4 +- .../github-dependency-resolver.ts | 3 +- .../github-dependency-resolver.test.ts | 2 + .../wasm/test/fixtures/with-deps/src/main.nr | 2 +- noir/noir-repo/cspell.json | 10 +- .../docs/explainers/explainer-writing-noir.md | 8 +- .../docs/how_to/how-to-solidity-verifier.md | 8 +- .../docs/docs/noir/concepts/comptime.md | 262 +++ .../docs/noir/concepts/data_types/slices.mdx | 2 +- .../docs/docs/noir/concepts/traits.md | 60 + .../docs/docs/noir/concepts/unconstrained.md | 9 +- .../docs/docs/noir/standard_library/bigint.md | 5 + .../cryptographic_primitives/hashes.mdx | 2 +- .../docs/noir/standard_library/meta/expr.md | 181 ++ .../standard_library/meta/function_def.md | 61 + .../docs/noir/standard_library/meta/index.md | 141 ++ .../docs/noir/standard_library/meta/module.md | 27 + .../docs/noir/standard_library/meta/op.md | 146 ++ .../docs/noir/standard_library/meta/quoted.md | 57 + .../noir/standard_library/meta/struct_def.md | 45 + .../standard_library/meta/trait_constraint.md | 17 + .../noir/standard_library/meta/trait_def.md | 22 + .../noir/standard_library/meta/trait_impl.md | 52 + .../docs/noir/standard_library/meta/typ.md | 126 + .../standard_library/meta/unresolved_type.md | 13 + .../how_to/how-to-solidity-verifier.md | 8 +- .../reference/debugger/_category_.json | 2 +- noir/noir-repo/noir_stdlib/src/array.nr | 21 +- noir/noir-repo/noir_stdlib/src/cmp.nr | 30 + .../src/collections/bounded_vec.nr | 4 +- .../noir_stdlib/src/collections/map.nr | 22 +- .../noir_stdlib/src/collections/umap.nr | 28 +- 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/field/mod.nr | 69 +- noir/noir-repo/noir_stdlib/src/hash/keccak.nr | 81 +- noir/noir-repo/noir_stdlib/src/hash/mod.nr | 37 +- .../noir_stdlib/src/hash/poseidon2.nr | 55 +- noir/noir-repo/noir_stdlib/src/hash/sha256.nr | 248 ++ noir/noir-repo/noir_stdlib/src/hash/sha512.nr | 165 ++ 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 | 413 ++++ .../noir_stdlib/src/meta/function_def.nr | 19 +- noir/noir-repo/noir_stdlib/src/meta/mod.nr | 160 +- noir/noir-repo/noir_stdlib/src/meta/module.nr | 6 + noir/noir-repo/noir_stdlib/src/meta/op.nr | 188 ++ noir/noir-repo/noir_stdlib/src/meta/quoted.nr | 8 + .../noir_stdlib/src/meta/struct_def.nr | 6 + .../noir_stdlib/src/meta/trait_def.nr | 2 + .../noir_stdlib/src/meta/trait_impl.nr | 11 + noir/noir-repo/noir_stdlib/src/meta/typ.nr | 23 + .../noir_stdlib/src/meta/unresolved_type.nr | 6 + 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 | 4 +- noir/noir-repo/noir_stdlib/src/sha256.nr | 98 +- noir/noir-repo/noir_stdlib/src/sha512.nr | 167 +- noir/noir-repo/noir_stdlib/src/slice.nr | 7 +- 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 +- .../unspecified_generic/Nargo.toml | 7 + .../unspecified_generic/src/main.nr | 5 + .../arithmetic_generics/src/main.nr | 35 +- .../associated_types/Nargo.toml | 7 + .../associated_types/src/main.nr | 28 + .../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 +- .../comptime_function_definition/src/main.nr | 2 +- .../comptime_keccak/Nargo.toml | 7 + .../comptime_keccak/src/main.nr | 31 + .../comptime_module/src/main.nr | 15 + .../comptime_trait_impl/Nargo.toml | 7 + .../comptime_trait_impl/src/main.nr | 32 + .../comptime_type/src/main.nr | 6 + .../Nargo.toml | 7 + .../src/main.nr | 11 + .../inject_context_attribute/Nargo.toml | 6 + .../inject_context_attribute/src/main.nr | 53 + .../macros_in_comptime/src/main.nr | 6 +- .../method_call_regression/src/main.nr | 10 +- .../compile_success_empty/option/src/main.nr | 2 +- .../poseidon2_simplification/Nargo.toml | 7 + .../poseidon2_simplification/src/main.nr | 7 + .../regression_5823/Nargo.toml | 7 + .../regression_5823/src/main.nr | 5 + .../schnorr_simplification}/Nargo.toml | 2 +- .../schnorr_simplification/src/main.nr | 78 + .../serialize/Nargo.toml | 7 + .../serialize/src/main.nr | 59 + .../zeroed_slice/src/main.nr | 2 +- .../check_uncostrained_regression/src/main.nr | 4 +- .../bigint_from_too_many_le_bytes/Nargo.toml | 7 + .../bigint_from_too_many_le_bytes/src/main.nr | 22 + .../brillig_assert_fail/src/main.nr | 6 +- .../brillig_assert_msg_runtime/src/main.nr | 6 +- .../src/main.nr | 5 +- .../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 +- .../Nargo.toml | 7 + .../Prover.toml | 2 + .../src/main.nr | 13 + .../regression_4124/src/main.nr | 2 +- .../regression_5435/src/main.nr | 4 +- .../execution_success/sha256/Prover.toml | 2 + .../execution_success/sha256/src/main.nr | 7 +- .../sha256_regression/Nargo.toml | 7 + .../sha256_regression/Prover.toml | 9 + .../sha256_regression/src/main.nr | 26 + .../sha256_var_size_regression/Nargo.toml | 7 + .../sha256_var_size_regression/Prover.toml | 3 + .../sha256_var_size_regression/src/main.nr | 17 + .../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/Prover.toml | 537 ----- .../verify_honk_proof/src/main.nr | 16 - .../test_programs/gates_report_brillig.sh | 33 + .../comptime_expr}/Nargo.toml | 2 +- .../comptime_expr/src/main.nr | 569 +++++ .../embedded_curve_ops/src/main.nr | 1 - .../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/test_programs/rebuild.sh | 2 +- noir/noir-repo/tooling/acvm_cli/Cargo.toml | 3 + noir/noir-repo/tooling/debugger/Cargo.toml | 3 + .../noir-repo/tooling/debugger/src/context.rs | 44 +- noir/noir-repo/tooling/fuzzer/Cargo.toml | 3 + noir/noir-repo/tooling/lsp/Cargo.toml | 4 + noir/noir-repo/tooling/lsp/src/lib.rs | 24 +- .../tooling/lsp/src/notifications/mod.rs | 16 +- .../tooling/lsp/src/requests/completion.rs | 2046 +++++------------ .../src/requests/completion/auto_import.rs | 205 ++ .../lsp/src/requests/completion/builtins.rs | 87 +- .../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 | 1794 +++++++++++++++ .../lsp/src/requests/completion/traversal.rs | 126 + .../lsp/src/requests/document_symbol.rs | 30 +- .../lsp/src/requests/goto_definition.rs | 14 + .../tooling/lsp/src/requests/hover.rs | 111 +- .../tooling/lsp/src/requests/inlay_hint.rs | 25 +- .../noir-repo/tooling/lsp/src/requests/mod.rs | 15 +- .../lsp/src/requests/signature_help.rs | 292 +++ .../lsp/src/requests/signature_help/tests.rs | 196 ++ .../src/requests/signature_help/traversal.rs | 309 +++ noir/noir-repo/tooling/lsp/src/types.rs | 6 +- .../test_programs/go_to_definition/Nargo.toml | 1 + .../go_to_definition/dependency/Nargo.toml | 6 + .../go_to_definition/dependency/src/lib.nr | 1 + .../go_to_definition/src/main.nr | 1 + noir/noir-repo/tooling/nargo/Cargo.toml | 3 + noir/noir-repo/tooling/nargo/src/errors.rs | 7 +- noir/noir-repo/tooling/nargo/src/lib.rs | 31 +- .../tooling/nargo/src/ops/execute.rs | 10 +- noir/noir-repo/tooling/nargo/src/package.rs | 7 + noir/noir-repo/tooling/nargo_cli/Cargo.toml | 5 +- noir/noir-repo/tooling/nargo_cli/build.rs | 2 +- .../tooling/nargo_cli/src/cli/check_cmd.rs | 11 +- .../tooling/nargo_cli/src/cli/export_cmd.rs | 6 +- .../tooling/nargo_cli/src/cli/fs/inputs.rs | 8 +- .../tooling/nargo_cli/src/cli/info_cmd.rs | 57 +- .../tooling/nargo_cli/src/cli/test_cmd.rs | 13 +- noir/noir-repo/tooling/nargo_fmt/Cargo.toml | 3 + .../tooling/nargo_fmt/src/rewrite/expr.rs | 6 + .../tooling/nargo_fmt/src/rewrite/typ.rs | 12 +- .../tooling/nargo_fmt/src/visitor/item.rs | 13 +- .../tooling/nargo_fmt/src/visitor/stmt.rs | 3 + .../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 | 6 +- .../src/cli/opcodes_flamegraph_cmd.rs | 7 +- .../tooling/profiler/src/flamegraph.rs | 26 +- .../tooling/profiler/src/opcode_formatter.rs | 2 +- noir/noir-repo/yarn.lock | 13 +- 385 files changed, 15996 insertions(+), 4469 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 create mode 100644 noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call/blackbox.rs create mode 100644 noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/generics.rs create mode 100644 noir/noir-repo/compiler/noirc_frontend/src/hir_def/types/arithmetic.rs create mode 100644 noir/noir-repo/docs/docs/noir/concepts/comptime.md create mode 100644 noir/noir-repo/docs/docs/noir/standard_library/meta/expr.md create mode 100644 noir/noir-repo/docs/docs/noir/standard_library/meta/function_def.md create mode 100644 noir/noir-repo/docs/docs/noir/standard_library/meta/index.md create mode 100644 noir/noir-repo/docs/docs/noir/standard_library/meta/module.md create mode 100644 noir/noir-repo/docs/docs/noir/standard_library/meta/op.md create mode 100644 noir/noir-repo/docs/docs/noir/standard_library/meta/quoted.md create mode 100644 noir/noir-repo/docs/docs/noir/standard_library/meta/struct_def.md create mode 100644 noir/noir-repo/docs/docs/noir/standard_library/meta/trait_constraint.md create mode 100644 noir/noir-repo/docs/docs/noir/standard_library/meta/trait_def.md create mode 100644 noir/noir-repo/docs/docs/noir/standard_library/meta/trait_impl.md create mode 100644 noir/noir-repo/docs/docs/noir/standard_library/meta/typ.md create mode 100644 noir/noir-repo/docs/docs/noir/standard_library/meta/unresolved_type.md create mode 100644 noir/noir-repo/noir_stdlib/src/hash/sha256.nr create mode 100644 noir/noir-repo/noir_stdlib/src/hash/sha512.nr 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 create mode 100644 noir/noir-repo/noir_stdlib/src/meta/unresolved_type.nr create mode 100644 noir/noir-repo/test_programs/compile_failure/unspecified_generic/Nargo.toml create mode 100644 noir/noir-repo/test_programs/compile_failure/unspecified_generic/src/main.nr create mode 100644 noir/noir-repo/test_programs/compile_success_empty/associated_types/Nargo.toml create mode 100644 noir/noir-repo/test_programs/compile_success_empty/associated_types/src/main.nr delete mode 100644 noir/noir-repo/test_programs/compile_success_empty/comptime_exp/src/main.nr create mode 100644 noir/noir-repo/test_programs/compile_success_empty/comptime_keccak/Nargo.toml create mode 100644 noir/noir-repo/test_programs/compile_success_empty/comptime_keccak/src/main.nr create mode 100644 noir/noir-repo/test_programs/compile_success_empty/comptime_trait_impl/Nargo.toml 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/compile_success_empty/embedded_curve_add_simplification/Nargo.toml create mode 100644 noir/noir-repo/test_programs/compile_success_empty/embedded_curve_add_simplification/src/main.nr create mode 100644 noir/noir-repo/test_programs/compile_success_empty/inject_context_attribute/Nargo.toml create mode 100644 noir/noir-repo/test_programs/compile_success_empty/inject_context_attribute/src/main.nr create mode 100644 noir/noir-repo/test_programs/compile_success_empty/poseidon2_simplification/Nargo.toml create mode 100644 noir/noir-repo/test_programs/compile_success_empty/poseidon2_simplification/src/main.nr create mode 100644 noir/noir-repo/test_programs/compile_success_empty/regression_5823/Nargo.toml create mode 100644 noir/noir-repo/test_programs/compile_success_empty/regression_5823/src/main.nr rename noir/noir-repo/test_programs/{execution_success/verify_honk_proof => compile_success_empty/schnorr_simplification}/Nargo.toml (62%) create mode 100644 noir/noir-repo/test_programs/compile_success_empty/schnorr_simplification/src/main.nr create mode 100644 noir/noir-repo/test_programs/compile_success_empty/serialize/Nargo.toml create mode 100644 noir/noir-repo/test_programs/compile_success_empty/serialize/src/main.nr create mode 100644 noir/noir-repo/test_programs/execution_failure/bigint_from_too_many_le_bytes/Nargo.toml create mode 100644 noir/noir-repo/test_programs/execution_failure/bigint_from_too_many_le_bytes/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 create mode 100644 noir/noir-repo/test_programs/execution_success/nested_dyn_array_regression_5782/Nargo.toml create mode 100644 noir/noir-repo/test_programs/execution_success/nested_dyn_array_regression_5782/Prover.toml create mode 100644 noir/noir-repo/test_programs/execution_success/nested_dyn_array_regression_5782/src/main.nr create mode 100644 noir/noir-repo/test_programs/execution_success/sha256_regression/Nargo.toml create mode 100644 noir/noir-repo/test_programs/execution_success/sha256_regression/Prover.toml create mode 100644 noir/noir-repo/test_programs/execution_success/sha256_regression/src/main.nr create mode 100644 noir/noir-repo/test_programs/execution_success/sha256_var_size_regression/Nargo.toml create mode 100644 noir/noir-repo/test_programs/execution_success/sha256_var_size_regression/Prover.toml create mode 100644 noir/noir-repo/test_programs/execution_success/sha256_var_size_regression/src/main.nr delete mode 100644 noir/noir-repo/test_programs/execution_success/verify_honk_proof/Prover.toml delete mode 100644 noir/noir-repo/test_programs/execution_success/verify_honk_proof/src/main.nr create mode 100644 noir/noir-repo/test_programs/gates_report_brillig.sh rename noir/noir-repo/test_programs/{compile_success_empty/comptime_exp => noir_test_success/comptime_expr}/Nargo.toml (78%) create mode 100644 noir/noir-repo/test_programs/noir_test_success/comptime_expr/src/main.nr 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/lsp/test_programs/go_to_definition/dependency/Nargo.toml create mode 100644 noir/noir-repo/tooling/lsp/test_programs/go_to_definition/dependency/src/lib.nr 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 d575ffe3e8c..12778b685c6 100644 --- a/.noir-sync-commit +++ b/.noir-sync-commit @@ -1 +1 @@ -0ebf1fee471641db0bffcc8307d20327613c78c1 +0e8becc7bccee2ae4e4e3ef373df08c3e9ef88c9 diff --git a/noir/noir-repo/.github/workflows/gates_report.yml b/noir/noir-repo/.github/workflows/gates_report.yml index 0cc94a1a04d..0b0a527b69e 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 00000000000..4e12a6fcbca --- /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 2c877a4d02c..aeb7d8757c4 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 bacbf939786..2cf79c40303 100644 --- a/noir/noir-repo/Cargo.lock +++ b/noir/noir-repo/Cargo.lock @@ -45,6 +45,8 @@ dependencies = [ "acir", "acvm_blackbox_solver", "ark-bls12-381", + "ark-bn254", + "bn254_blackbox_solver", "brillig_vm", "indexmap 1.9.3", "num-bigint", @@ -52,6 +54,7 @@ dependencies = [ "serde", "thiserror", "tracing", + "zkhash", ] [[package]] @@ -539,6 +542,18 @@ dependencies = [ "typenum", ] +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake2" version = "0.10.6" @@ -548,6 +563,17 @@ dependencies = [ "digest", ] +[[package]] +name = "blake2b_simd" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + [[package]] name = "blake3" version = "1.5.0" @@ -570,6 +596,19 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bls12_381" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3c196a77437e7cc2fb515ce413a6401291578b5afc8ecb29a3c7ab957f05941" +dependencies = [ + "ff 0.12.1", + "group 0.12.1", + "pairing", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "bn254_blackbox_solver" version = "0.49.0" @@ -1360,9 +1399,9 @@ dependencies = [ "crypto-bigint", "der", "digest", - "ff", + "ff 0.12.1", "generic-array", - "group", + "group 0.12.1", "pkcs8", "rand_core 0.6.4", "sec1", @@ -1465,6 +1504,18 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" dependencies = [ + "bitvec", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "bitvec", "rand_core 0.6.4", "subtle", ] @@ -1569,6 +1620,12 @@ dependencies = [ "libc", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.1.31" @@ -1764,7 +1821,19 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" dependencies = [ - "ff", + "ff 0.12.1", + "memuse", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff 0.13.0", "rand_core 0.6.4", "subtle", ] @@ -1775,6 +1844,29 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +[[package]] +name = "halo2" +version = "0.1.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a23c779b38253fe1538102da44ad5bd5378495a61d2c4ee18d64eaa61ae5995" +dependencies = [ + "halo2_proofs", +] + +[[package]] +name = "halo2_proofs" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e925780549adee8364c7f2b685c753f6f3df23bde520c67416e93bf615933760" +dependencies = [ + "blake2b_simd", + "ff 0.12.1", + "group 0.12.1", + "pasta_curves 0.4.1", + "rand_core 0.6.4", + "rayon", +] + [[package]] name = "hashbrown" version = "0.11.2" @@ -2252,6 +2344,20 @@ dependencies = [ "unicase", ] +[[package]] +name = "jubjub" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a575df5f985fe1cd5b2b05664ff6accfc46559032b954529fd225a2168d27b0f" +dependencies = [ + "bitvec", + "bls12_381", + "ff 0.12.1", + "group 0.12.1", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "k256" version = "0.11.6" @@ -2329,6 +2435,9 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] [[package]] name = "libaes" @@ -2464,6 +2573,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memuse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2145869435ace5ea6ea3d35f59be559317ec9a0d04e1812d5f185a87b6d36f1a" + [[package]] name = "miniz_oxide" version = "0.7.1" @@ -2722,6 +2837,7 @@ dependencies = [ "acvm", "async-lsp", "codespan-lsp", + "convert_case 0.6.0", "fm", "fxhash", "lsp-types 0.94.1", @@ -2888,6 +3004,7 @@ version = "0.33.0" dependencies = [ "acvm", "bn254_blackbox_solver", + "cfg-if 1.0.0", "chrono", "fxhash", "im", @@ -3099,6 +3216,15 @@ dependencies = [ "sha2", ] +[[package]] +name = "pairing" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135590d8bdba2b31346f9cd1fb2a912329f5135e832a4f422942eb6ead8b6b3b" +dependencies = [ + "group 0.12.1", +] + [[package]] name = "parking_lot" version = "0.11.2" @@ -3147,6 +3273,36 @@ dependencies = [ "windows-targets 0.48.1", ] +[[package]] +name = "pasta_curves" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc65faf8e7313b4b1fbaa9f7ca917a0eed499a9663be71477f87993604341d8" +dependencies = [ + "blake2b_simd", + "ff 0.12.1", + "group 0.12.1", + "lazy_static", + "rand 0.8.5", + "static_assertions", + "subtle", +] + +[[package]] +name = "pasta_curves" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e57598f73cc7e1b2ac63c79c517b31a0877cd7c402cdcaa311b5208de7a095" +dependencies = [ + "blake2b_simd", + "ff 0.13.0", + "group 0.13.0", + "lazy_static", + "rand 0.8.5", + "static_assertions", + "subtle", +] + [[package]] name = "paste" version = "1.0.14" @@ -3472,6 +3628,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "radix_trie" version = "0.2.1" @@ -4192,6 +4354,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "spki" version = "0.6.0" @@ -4208,6 +4376,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "str-buf" version = "1.0.6" @@ -4309,6 +4483,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tempfile" version = "3.8.0" @@ -5177,6 +5357,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "zerocopy" version = "0.7.32" @@ -5216,3 +5405,30 @@ dependencies = [ "quote", "syn 2.0.64", ] + +[[package]] +name = "zkhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4352d1081da6922701401cdd4cbf29a2723feb4cfabb5771f6fee8e9276da1c7" +dependencies = [ + "ark-ff", + "ark-std", + "bitvec", + "blake2", + "bls12_381", + "byteorder", + "cfg-if 1.0.0", + "group 0.12.1", + "group 0.13.0", + "halo2", + "hex", + "jubjub", + "lazy_static", + "pasta_curves 0.5.1", + "rand 0.8.5", + "serde", + "sha2", + "sha3", + "subtle", +] diff --git a/noir/noir-repo/Cargo.toml b/noir/noir-repo/Cargo.toml index 63509170057..bf5739ebbe8 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 @@ -124,7 +130,7 @@ criterion = "0.5.0" # https://github.com/tikv/pprof-rs/pull/172 pprof = { version = "0.13", features = ["flamegraph", "criterion"] } - +cfg-if = "1.0.0" dirs = "4" serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0" @@ -160,6 +166,14 @@ rust-embed = "6.6.0" # See https://ritik-mishra.medium.com/resolving-the-wasm-pack-error-locals-exceed-maximum-ec3a9d96685b opt-level = 1 +# release mode with extra checks, e.g. overflow checks +[profile.release-pedantic] +inherits = "release" +overflow-checks = true + +[profile.test] +inherits = "dev" +overflow-checks = true [profile.size] inherits = "release" diff --git a/noir/noir-repo/acvm-repo/acir/Cargo.toml b/noir/noir-repo/acvm-repo/acir/Cargo.toml index 88616ccffb5..860b565544b 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 00d0933a3aa..f700fefe0cd 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)?; @@ -153,9 +153,28 @@ pub struct ResolvedOpcodeLocation { /// map opcodes to debug information related to their context. pub enum OpcodeLocation { Acir(usize), + // TODO(https://github.com/noir-lang/noir/issues/5792): We can not get rid of this enum field entirely just yet as this format is still + // used for resolving assert messages which is a breaking serialization change. Brillig { acir_index: usize, brillig_index: usize }, } +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] +pub struct BrilligOpcodeLocation(pub usize); + +impl OpcodeLocation { + // Utility method to allow easily comparing a resolved Brillig location and a debug Brillig location. + // This method is useful when fetching Brillig debug locations as this does not need an ACIR index, + // and just need the Brillig index. + pub fn to_brillig_location(self) -> Option { + match self { + OpcodeLocation::Brillig { brillig_index, .. } => { + Some(BrilligOpcodeLocation(brillig_index)) + } + _ => None, + } + } +} + impl std::fmt::Display for OpcodeLocation { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -204,6 +223,13 @@ impl FromStr for OpcodeLocation { } } +impl std::fmt::Display for BrilligOpcodeLocation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let index = self.0; + write!(f, "{index}") + } +} + impl Circuit { pub fn num_vars(&self) -> u32 { self.current_witness_index + 1 @@ -224,7 +250,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 +276,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 b1fdc5e0080..d7f0f5f6f1f 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 00000000000..b5abb1b3942 --- /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 1a634eeea9c..ce28d47021c 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 a037a453348..acc34457bc9 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] @@ -21,7 +24,7 @@ ark-bn254.workspace = true ark-bls12-381 = { workspace = true, optional = true } ark-ff.workspace = true -cfg-if = "1.0.0" +cfg-if.workspace = true [dev-dependencies] proptest.workspace = true 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 92ccbc4e5f6..2323f008dbe 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 f7228e66314..74927c07a36 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 2ee4d529e5a..ea80dbeedbb 100644 --- a/noir/noir-repo/acvm-repo/acvm/Cargo.toml +++ b/noir/noir-repo/acvm-repo/acvm/Cargo.toml @@ -10,7 +10,8 @@ license.workspace = true rust-version.workspace = true repository.workspace = true -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lints] +workspace = true [dependencies] num-bigint.workspace = true @@ -38,4 +39,7 @@ bls12_381 = [ [dev-dependencies] ark-bls12-381 = { version = "^0.4.0", default-features = false, features = ["curve"] } +ark-bn254.workspace = true +bn254_blackbox_solver.workspace = true proptest.workspace = true +zkhash = { version = "^0.2.0", default-features = false } 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 83c5aeb6296..647c11bd3c3 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/tests/solver.proptest-regressions b/noir/noir-repo/acvm-repo/acvm/tests/solver.proptest-regressions index 35627c1fbae..d5b09c8c009 100644 --- a/noir/noir-repo/acvm-repo/acvm/tests/solver.proptest-regressions +++ b/noir/noir-repo/acvm-repo/acvm/tests/solver.proptest-regressions @@ -4,6 +4,7 @@ # # It is recommended to check this file in to source control so that # everyone who runs the test benefits from these saved cases. +cc 960460afabe9cd5f293b310d0aa3cc55f79163d4445fb4fd24f148c0c70ef421 # shrinks to inputs = [(7006800039331019243688393824145158009389575060470190384886350246083750305789, false), (2¹⁶×69288181877743077422831840787272084000814053518824660036579689151244970, false), (300437348917483825612039528102296693334006129135490594023152856084953868810, false), (4823492773360499854802554302585328525608528551020562050189864554765904117770, false), (4824670368033820130439612125278697378018346703067216363823090518963541115050, false), (4541974568008122685711264049174891850838557765495784399449231366824048721930, false), (300437350040540685984720241501165014133014058360545849645894867666537650617, true), (163192779580560125232076252958207681919, true), (-2542062050439421159413867956702051849160671674920746658341296409298182239337, true), (175503121977504223703263610086399928344, true), (260495679943662138441041500775727067764, true), (256267345976523334436046522042864460684, false), (-3293060738351338103745720847983311414454700585429643550776950744744641792278, true), (-2738558284890938529700946417088255944936801663233427893951742481234449937863, true), (-9408903287898353981993159772236368632269873213738312580426228276387594806467, false), (-4094435362802425278873069427818373025789998862201415928058861770747528163349, false), (112103815807797910215405155129290739992, true), (652959048122537987900699228406811595117461985442100769568930484681117184927, true), (25648069884835098934129943086492583018, true), (182381658164976581567218989771782914043, true), (276027589667111318145641746681175535935, true), (228979537701935045218138086489994361738, true), (-7189629140037494234317011123662291175105203248014412116387794444668331938972, true), (2⁴×21045079095550098765213744333690206045, true), (2⁴×272957279228827760019939917744285068022450150396463247147001692315953383164, true), (278169782777797975865517348961398261204, true), (-9350891260062047521505428797610570271715889226434172923277698254137606657637, true), (31753891497170711449574490616043022555, true), (5826530521679896062199745042089932193960338071930275815357730199930663038971, false), (6333148601890872882199727702821458893692619379008420418245130578951125712813, true), (11591994241860040248819868426028245994970593822060591090353134114607771350840, false), (285019902865519749239467179980780356895, true), (-1841690738801312317622577110475383143336981923160464720298620703157843518801, false), (5677035692440022901187907185898133134673347474144834237621131124787028412481, false), (3817657503746267247293557834223189311, false), (3524986425011502884950940801392664786470720156074841155252661063475974853061, true), (3804027911019145431046523949797604749769206523757137367319384533460898442710, false), (66232570440565238307407257297826182007, true), (10950346695884721979708694328592923581621839140812284359518482850256345903994, true), (-2555270735135738372913055831827559435131548066765611995534606037449334265987, false), (254175244691305364094581273598120793718, true), (290269702902160220362408941750532374247, false), (123377132611149223348638391192338552487, true), (11181151842165891780257230406582701746739259352010643125205313477915192331122, true), (-2⁴×409367997994623731443501907254201822154034077116501532446678462812476345374, true), (2⁴×544614809509366338085123708986493598, false), (2⁴×17078170458432298388079487507615195387, true), (149630116761755329336202584260592884865, false), (2⁴×10091764197139943452626559964081430454, true), (2534364375519095349027150248721158492583764392404691624469146116049080450061, true), (603871911530068460120598744369974193816624318623555065050680487659787773114, true), (160084452435578937990759565211574469929, false), (218093312507148512669194680815491642329, true), (300736043265743439077982281106014100410, true), (18523147759780982956728003842757122081, true), (306248530673006182475517947158145534649, true), (102222390890150910453739963495986974375, false), (257196139219119037476292875587496141911, true), (331081228043791906616479284936784004527, false), (3016805042897548992579828613322067728372560923112623438800394966350526781639, false), (-572714658027596419754534645583807426372197275429130874987048126395899381912, true), (-729547819819366432764794677355722188538208598242889613088699865869727118200, true), (821714036325498522863927403152236109, false), (4787751092911790208417000875909331970360784454334971842120679656099485179967, false), (1782142348917496268507120173995265725105472252018261170186756542207304517779, true), (12386003977677513084491398210232899807, false), (-2⁴×618559586542445675091456136080909891843100214761559602644497715652929490680, true), (4825546553395317448131256122977630559401382302668933235792589235215581675013, true), (140081105962524159719688476469460050188, true), (10859915619506763272622767997975661267764177865434405229649588030297621763336, false), (5224965182422450783905844900991128046, false), (71305303954419714098999884166539099370, false), (1774498925999897022672533369155675274519638992774354242677460742588824167893, true), (7303027640230419310139844673790234369436236422573534223908300465886393464159, false), (-6886271862152701865078616271612468719595307788386883226924940208040368361967, false), (10731912210227615778772646231985571152124249989191718989152918741848181957943, true), (198890466402904980166209748345696486119, true), (-5302367762468092462990244835112194335091450476355385352485920702032880189295, true), (2⁸×10099407372054304360516381277909507981224443414132090997901220204171639546, false), (168440361528147182399972323599534901404, true), (-6814422589767867668898643051011344807889685848964308955561956885501315175751, false), (316318615743355850862007524903255359480, true), (2712091589809687549346821393688518190583490946219058843320554352096717761470, true), (271066251172692433277334139422005324431, true), (5518750645785029524081697870015943313782873802072211951410725469084236770902, true), (1598180654131654153665855630440993899751870982081743673174495880400279439835, true), (11087487398111971618312866830968963281800717504531211030365913622073456199882, true), (111117995977996342335283103148096717294, true), (-1295976581376625868027817481511298593289394037483836095148092178778026996567, true), (83596219152712990240747906837305397145, true), (1228889622953345470420372628777807948517612731014485410422479520987606375826, true), (-8913468232804310348258666527777442833035058410912856005485334678693975514181, false), (-2⁴×278169562875273203973590188765589611858625386111898684152446666758264013985, true), (119076628552542975308949066183764445758, false), (2⁴×14050391655614718300501271078314774634, false), (194148774408232082873511641818616649590, false), (8194443279687593339348520601136879720103397639149864695196588804167374140678, false), (-354687261929126904957780450034290767854037900960107070732201978247851494917, false), (-4179720645035540348033514199762031592916784883648049359344050592582034531797, false), (252905126534820884873153821542654918918, false), (1623505226178950044083922559187169870608127387658796034641797863725138722258, false), (4666866662074690176801634318736786643725641691879698870430777699162065406876, true), (2⁸×435424843840757189423437488726199338, true), (-4965389845430595378911449436549917702549453083792442397843742095714939156996, true), (14382174601370788118680121014383071020, true), (18435597849872688842745199082065115774, true), (62402934393754095161223999760955177676, true), (3342319610924388381676805660071709048562626908839360397545889815909430346935, false), (320934966917535193944554370897413442767, false), (-7042080042894669842698869952298956796788859350146750227466425877912934318731, false), (730331371245847611804891620969657153593889954959738225897355098167792980949, false), (4302619576720736253790944194743764744133983784456893791762467136654139077499, true), (8873437849856151332967264853182096823478282147593198413978830303785491549165, true), (90112803136689598890112508644364307477, false), (5515586277356922699974022986439856116085865722104320017169902845027519269630, true), (193123430947542232681959839788943833622, false), (313518414676988815146392681741405591399, false), (1744503198465993468536485478138286257957976156358242481202409192902804688819, true), (232270086930796904432134012473980400372, false), (54252678944965710450270323121364517361, false), (227966767610810610640465794393680628548, true), (195275454883093736141594026466958950870, false), (41489184570808717515029072032722768645, false), (-1376299006702054572367600313792943879784167154440024183554470146184948865317, false), (-4342520612104393712790888025887841267334261124780864718056663459023881749401, false), (-2195492958636161600210780697531407029867553813415393642723791298864497552233, true), (332196823653718041971704314790182081950, true), (6692397022125437472229833939879677974689961856437724474341917567424805187801, true), (-2383455130457463032029147766306601662937360882870225696738568438241981024563, true), (6717167117084866258340101046663161160224757566359450082951177418440696737772, true), (-1642389908034348896239060205015333283837806723968213066158348166269363356454, true), (6906326300271352814414395025112363945375522026620021146077167231059213695969, false), (62420300707619024606657497955183365269, false), (310840635777201053115164797040466639857, true)] cc e4dd0e141df173f5dfdfb186bba4154247ec284b71d8f294fa3282da953a0e92 # shrinks to x = 0, y = 1 cc 419ed6fdf1bf1f2513889c42ec86c665c9d0500ceb075cbbd07f72444dbd78c6 # shrinks to x = 266672725 cc 0810fc9e126b56cf0a0ddb25e0dc498fa3b2f1980951550403479fc01c209833 # shrinks to modulus = [71, 253, 124, 216, 22, 140, 32, 60, 141, 202, 113, 104, 145, 106, 129, 151, 93, 88, 129, 129, 182, 69, 80, 184, 41, 160, 49, 225, 114, 78, 100, 48], zero_or_ones_constant = false, use_constant = false @@ -11,3 +12,8 @@ cc 735ee9beb1a1dbb82ded6f30e544d7dfde149957e5d45a8c96fc65a690b6b71c # shrinks to cc ca81bc11114a2a2b34021f44ecc1e10cb018e35021ef4d728e07a6791dad38d6 # shrinks to (xs, modulus) = ([(0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (49, false)], [71, 253, 124, 216, 22, 140, 32, 60, 141, 202, 113, 104, 145, 106, 129, 151, 93, 88, 129, 129, 182, 69, 80, 184, 41, 160, 49, 225, 114, 78, 100, 48]) cc 6c1d571a0111e6b4c244dc16da122ebab361e77b71db7770d638076ab21a717b # shrinks to (xs, modulus) = ([(0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (49, false)], [71, 253, 124, 216, 22, 140, 32, 60, 141, 202, 113, 104, 145, 106, 129, 151, 93, 88, 129, 129, 182, 69, 80, 184, 41, 160, 49, 225, 114, 78, 100, 48]) cc ccb7061ab6b85e2554d00bf03d74204977ed7a4109d7e2d5c6b5aaa2179cfaf9 # shrinks to (xs, modulus) = ([(0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (0, false), (49, false)], [71, 253, 124, 216, 22, 140, 32, 60, 141, 202, 113, 104, 145, 106, 129, 151, 93, 88, 129, 129, 182, 69, 80, 184, 41, 160, 49, 225, 114, 78, 100, 48]) +cc 853d774f6d69809a63e3121ccdbbb780db42acb861cb6cada63247446932a321 # shrinks to inputs_distinct_inputs = ([(0, false)], [(9374390252263900826, false)]) +cc 4fc0bd347d9f4967801e2e30c746d2f6c012882911f72e7e816d350a742ced28 # shrinks to inputs_distinct_inputs = ([(2⁸×61916068613087029720904767285796661, false)], [(2⁸×220343640628484768581538005104492351, false)]) +cc 04d8571793600c2023d7aba2d1dd8f0e2c82b6010130d95a193df02b07977712 # shrinks to inputs_distinct_inputs = ([], [(0, true)]) +cc dbc57772b9450371db70f8aa06d10502bb1aef030448c6df467465937bc8916a # shrinks to inputs_distinct_inputs = ([(295, false), (0, false), (0, false), (0, false), (0, false), (0, false)], [(295, false), (0, false), (328, false), (237, true), (484, true), (69, false)]) +cc ef68d2dc6f0d366dd69edf8eec02a7b9cd7d6888983cea45496516b6effca813 # shrinks to inputs_distinct_inputs = ([(40, false), (471, false), (56, false), (35, false), (104, false), (232, false), (252, false), (131, false), (437, true), (354, false), (235, false), (316, true), (364, true), (242, false), (436, true), (298, true), (360, true), (174, true), (295, false), (250, true), (178, true), (426, false), (78, false), (217, true), (296, true), (371, false), (349, true), (445, false), (221, false), (409, false), (59, false), (511, true), (482, false)], [(136, true), (228, true), (193, true), (190, true), (15, false), (399, false), (54, false), (195, true), (258, true), (99, false), (83, false), (383, true), (456, true), (409, true), (347, false), (183, false), (371, true), (410, true), (439, true), (175, true), (445, false), (165, false), (70, false), (2⁴×22, true), (339, true), (161, true), (313, false), (2⁴×23, true), (275, true), (278, true), (294, true), (284, true), (262, false)]) diff --git a/noir/noir-repo/acvm-repo/acvm/tests/solver.rs b/noir/noir-repo/acvm-repo/acvm/tests/solver.rs index a1b8b62f8bf..2a06e07f092 100644 --- a/noir/noir-repo/acvm-repo/acvm/tests/solver.rs +++ b/noir/noir-repo/acvm-repo/acvm/tests/solver.rs @@ -1,4 +1,5 @@ -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashSet}; +use std::sync::Arc; use acir::{ acir_field::GenericFieldElement, @@ -14,13 +15,14 @@ use acir::{ use acvm::pwg::{ACVMStatus, ErrorLocation, ForeignCallWaitInfo, OpcodeResolutionError, ACVM}; use acvm_blackbox_solver::StubbedBlackBoxSolver; +use bn254_blackbox_solver::{field_from_hex, Bn254BlackBoxSolver, POSEIDON2_CONFIG}; use brillig_vm::brillig::HeapValueType; use proptest::arbitrary::any; use proptest::prelude::*; use proptest::result::maybe_ok; - -// Reenable these test cases once we move the brillig implementation of inversion down into the acvm stdlib. +use proptest::sample::select; +use zkhash::poseidon2::poseidon2_params::Poseidon2Params; #[test] fn bls12_381_circuit() { @@ -728,6 +730,248 @@ fn memory_operations() { assert_eq!(witness_map[&Witness(8)], FieldElement::from(6u128)); } +fn allowed_bigint_moduli() -> Vec> { + let bn254_fq: Vec = vec![ + 0x47, 0xFD, 0x7C, 0xD8, 0x16, 0x8C, 0x20, 0x3C, 0x8d, 0xca, 0x71, 0x68, 0x91, 0x6a, 0x81, + 0x97, 0x5d, 0x58, 0x81, 0x81, 0xb6, 0x45, 0x50, 0xb8, 0x29, 0xa0, 0x31, 0xe1, 0x72, 0x4e, + 0x64, 0x30, + ]; + let bn254_fr: Vec = vec![ + 1, 0, 0, 240, 147, 245, 225, 67, 145, 112, 185, 121, 72, 232, 51, 40, 93, 88, 129, 129, + 182, 69, 80, 184, 41, 160, 49, 225, 114, 78, 100, 48, + ]; + let secpk1_fr: Vec = vec![ + 0x41, 0x41, 0x36, 0xD0, 0x8C, 0x5E, 0xD2, 0xBF, 0x3B, 0xA0, 0x48, 0xAF, 0xE6, 0xDC, 0xAE, + 0xBA, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, + ]; + let secpk1_fq: Vec = vec![ + 0x2F, 0xFC, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, + ]; + let secpr1_fq: Vec = vec![ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, + ]; + let secpr1_fr: Vec = vec![ + 81, 37, 99, 252, 194, 202, 185, 243, 132, 158, 23, 167, 173, 250, 230, 188, 255, 255, 255, + 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, + ]; + + vec![bn254_fq, bn254_fr, secpk1_fr, secpk1_fq, secpr1_fq, secpr1_fr] +} + +/// Whether to use a FunctionInput::constant or FunctionInput::witness: +/// +/// (value, use_constant) +type ConstantOrWitness = (FieldElement, bool); + +// For each ConstantOrWitness, +// - If use_constant, then convert to a FunctionInput::constant +// - Otherwise, convert to FunctionInput::witness +// + With the Witness index as (input_index + offset) +fn constant_or_witness_to_function_inputs( + xs: Vec, + offset: usize, + num_bits: Option, +) -> Vec> { + let num_bits = num_bits.unwrap_or(FieldElement::max_num_bits()); + xs.into_iter() + .enumerate() + .map(|(i, (x, use_constant))| { + if use_constant { + FunctionInput::constant(x, num_bits) + } else { + FunctionInput::witness(Witness((i + offset) as u32), num_bits) + } + }) + .collect() +} + +// Convert ConstantOrWitness's back to FieldElement's by dropping the bool's +fn drop_use_constant(input: &[ConstantOrWitness]) -> Vec { + input.iter().map(|x| x.0).collect() +} + +// equivalent values (ignoring use_constant) +fn drop_use_constant_eq(x: &[ConstantOrWitness], y: &[ConstantOrWitness]) -> bool { + drop_use_constant(x) == drop_use_constant(y) +} + +// Convert FieldElement's to ConstantOrWitness's by making all of them witnesses +fn use_witnesses(inputs: Vec) -> Vec { + inputs.into_iter().map(|input| (input, false)).collect() +} + +fn solve_array_input_blackbox_call( + inputs: Vec, + num_outputs: usize, + num_bits: Option, + f: F, +) -> Vec +where + F: FnOnce((Vec>, Vec)) -> BlackBoxFuncCall, +{ + let initial_witness_vec: Vec<_> = + inputs.iter().enumerate().map(|(i, (x, _))| (Witness(i as u32), *x)).collect(); + let outputs: Vec<_> = (0..num_outputs) + .map(|i| Witness((i + inputs.len()) as u32)) // offset past the indices of inputs + .collect(); + let initial_witness = WitnessMap::from(BTreeMap::from_iter(initial_witness_vec)); + + let inputs = constant_or_witness_to_function_inputs(inputs, 0, num_bits); + let op = Opcode::BlackBoxFuncCall(f((inputs.clone(), outputs.clone()))); + let opcodes = vec![op]; + let unconstrained_functions = vec![]; + let mut acvm = + ACVM::new(&Bn254BlackBoxSolver, &opcodes, initial_witness, &unconstrained_functions, &[]); + let solver_status = acvm.solve(); + assert_eq!(solver_status, ACVMStatus::Solved); + let witness_map = acvm.finalize(); + + outputs + .iter() + .map(|witness| *witness_map.get(witness).expect("all witnesses to be set")) + .collect() +} + +prop_compose! { + fn bigint_with_modulus()(modulus in select(allowed_bigint_moduli())) + (inputs in proptest::collection::vec(any::<(u8, bool)>(), modulus.len()), modulus in Just(modulus)) + -> (Vec, Vec) { + let inputs = inputs.into_iter().zip(modulus.iter()).map(|((input, use_constant), modulus_byte)| { + (FieldElement::from(input.clamp(0, *modulus_byte) as u128), use_constant) + }).collect(); + (inputs, modulus) + } +} + +prop_compose! { + fn bigint_pair_with_modulus()(inputs_modulus in bigint_with_modulus()) + (second_inputs in proptest::collection::vec(any::<(u8, bool)>(), inputs_modulus.1.len()), inputs_modulus in Just(inputs_modulus)) + -> (Vec, Vec, Vec) { + let (inputs, modulus) = inputs_modulus; + let second_inputs = second_inputs.into_iter().zip(modulus.iter()).map(|((input, use_constant), modulus_byte)| { + (FieldElement::from(input.clamp(0, *modulus_byte) as u128), use_constant) + }).collect(); + (inputs, second_inputs, modulus) + } +} + +prop_compose! { + fn bigint_triple_with_modulus()(inputs_pair_modulus in bigint_pair_with_modulus()) + (third_inputs in proptest::collection::vec(any::<(u8, bool)>(), inputs_pair_modulus.2.len()), inputs_pair_modulus in Just(inputs_pair_modulus)) + -> (Vec, Vec, Vec, Vec) { + let (inputs, second_inputs, modulus) = inputs_pair_modulus; + let third_inputs = third_inputs.into_iter().zip(modulus.iter()).map(|((input, use_constant), modulus_byte)| { + (FieldElement::from(input.clamp(0, *modulus_byte) as u128), use_constant) + }).collect(); + (inputs, second_inputs, third_inputs, modulus) + } +} + +fn bigint_add_op() -> BlackBoxFuncCall { + BlackBoxFuncCall::BigIntAdd { lhs: 0, rhs: 1, output: 2 } +} + +fn bigint_mul_op() -> BlackBoxFuncCall { + BlackBoxFuncCall::BigIntMul { lhs: 0, rhs: 1, output: 2 } +} + +fn bigint_sub_op() -> BlackBoxFuncCall { + BlackBoxFuncCall::BigIntSub { lhs: 0, rhs: 1, output: 2 } +} + +fn bigint_div_op() -> BlackBoxFuncCall { + BlackBoxFuncCall::BigIntDiv { lhs: 0, rhs: 1, output: 2 } +} + +// Input is a BigInt, represented as a LE Vec of u8-range FieldElement's along +// with their use_constant values. +// +// Output is a zeroed BigInt that matches the input BigInt's +// - Byte length +// - use_constant values +fn bigint_zeroed(inputs: &[ConstantOrWitness]) -> Vec { + inputs.iter().map(|(_, use_constant)| (FieldElement::zero(), *use_constant)).collect() +} + +// bigint_zeroed, but returns one +fn bigint_to_one(inputs: &[ConstantOrWitness]) -> Vec { + let mut one = bigint_zeroed(inputs); + // little-endian + one[0] = (FieldElement::one(), one[0].1); + one +} + +// Using the given BigInt modulus, solve the following circuit: +// - Convert xs, ys to BigInt's with ID's 0, 1, resp. +// - If the middle_op is present, run it +// + Input BigInt ID's: 0, 1 +// + Output BigInt ID: 2 +// - If the middle_op is missing, the output BigInt ID is 0 +// - Run BigIntToLeBytes on the output BigInt ID +// - Output the resulting Vec of LE bytes +fn bigint_solve_binary_op_opt( + middle_op: Option>, + modulus: Vec, + lhs: Vec, + rhs: Vec, +) -> Vec { + let initial_witness_vec: Vec<_> = lhs + .iter() + .chain(rhs.iter()) + .enumerate() + .map(|(i, (x, _))| (Witness(i as u32), *x)) + .collect(); + let output_witnesses: Vec<_> = initial_witness_vec + .iter() + .take(lhs.len()) + .enumerate() + .map(|(index, _)| Witness((index + 2 * lhs.len()) as u32)) // offset past the indices of lhs, rhs + .collect(); + let initial_witness = WitnessMap::from(BTreeMap::from_iter(initial_witness_vec)); + + let lhs = constant_or_witness_to_function_inputs(lhs, 0, None); + let rhs = constant_or_witness_to_function_inputs(rhs, lhs.len(), None); + + let to_op_input = if middle_op.is_some() { 2 } else { 0 }; + + let bigint_from_lhs_op = Opcode::BlackBoxFuncCall(BlackBoxFuncCall::BigIntFromLeBytes { + inputs: lhs, + modulus: modulus.clone(), + output: 0, + }); + let bigint_from_rhs_op = Opcode::BlackBoxFuncCall(BlackBoxFuncCall::BigIntFromLeBytes { + inputs: rhs, + modulus: modulus.clone(), + output: 1, + }); + let bigint_to_op = Opcode::BlackBoxFuncCall(BlackBoxFuncCall::BigIntToLeBytes { + input: to_op_input, + outputs: output_witnesses.clone(), + }); + + let mut opcodes = vec![bigint_from_lhs_op, bigint_from_rhs_op]; + if let Some(middle_op) = middle_op { + opcodes.push(Opcode::BlackBoxFuncCall(middle_op)); + } + opcodes.push(bigint_to_op); + + let unconstrained_functions = vec![]; + let mut acvm = + ACVM::new(&StubbedBlackBoxSolver, &opcodes, initial_witness, &unconstrained_functions, &[]); + let solver_status = acvm.solve(); + assert_eq!(solver_status, ACVMStatus::Solved); + let witness_map = acvm.finalize(); + output_witnesses + .iter() + .map(|witness| *witness_map.get(witness).expect("all witnesses to be set")) + .collect() +} + // Solve the given BlackBoxFuncCall with witnesses: 1, 2 as x, y, resp. #[cfg(test)] fn solve_blackbox_func_call( @@ -735,25 +979,26 @@ fn solve_blackbox_func_call( Option, Option, ) -> BlackBoxFuncCall, - x: (FieldElement, bool), // if false, use a Witness - y: (FieldElement, bool), // if false, use a Witness + lhs: (FieldElement, bool), // if false, use a Witness + rhs: (FieldElement, bool), // if false, use a Witness ) -> FieldElement { - let (x, x_constant) = x; - let (y, y_constant) = y; + let (lhs, lhs_constant) = lhs; + let (rhs, rhs_constant) = rhs; - let initial_witness = WitnessMap::from(BTreeMap::from_iter([(Witness(1), x), (Witness(2), y)])); + let initial_witness = + WitnessMap::from(BTreeMap::from_iter([(Witness(1), lhs), (Witness(2), rhs)])); - let mut lhs = None; - if x_constant { - lhs = Some(x); + let mut lhs_opt = None; + if lhs_constant { + lhs_opt = Some(lhs); } - let mut rhs = None; - if y_constant { - rhs = Some(y); + let mut rhs_opt = None; + if rhs_constant { + rhs_opt = Some(rhs); } - let op = Opcode::BlackBoxFuncCall(blackbox_func_call(lhs, rhs)); + let op = Opcode::BlackBoxFuncCall(blackbox_func_call(lhs_opt, rhs_opt)); let opcodes = vec![op]; let unconstrained_functions = vec![]; let mut acvm = @@ -765,6 +1010,205 @@ fn solve_blackbox_func_call( witness_map[&Witness(3)] } +// N inputs +// 32 outputs +fn sha256_op( + function_inputs_and_outputs: (Vec>, Vec), +) -> BlackBoxFuncCall { + let (function_inputs, outputs) = function_inputs_and_outputs; + BlackBoxFuncCall::SHA256 { + inputs: function_inputs, + outputs: outputs.try_into().expect("SHA256 returns 32 outputs"), + } +} + +// N inputs +// 32 outputs +fn blake2s_op( + function_inputs_and_outputs: (Vec>, Vec), +) -> BlackBoxFuncCall { + let (function_inputs, outputs) = function_inputs_and_outputs; + BlackBoxFuncCall::Blake2s { + inputs: function_inputs, + outputs: outputs.try_into().expect("Blake2s returns 32 outputs"), + } +} + +// N inputs +// 32 outputs +fn blake3_op( + function_inputs_and_outputs: (Vec>, Vec), +) -> BlackBoxFuncCall { + let (function_inputs, outputs) = function_inputs_and_outputs; + BlackBoxFuncCall::Blake3 { + inputs: function_inputs, + outputs: outputs.try_into().expect("Blake3 returns 32 outputs"), + } +} + +// variable inputs +// 32 outputs +fn keccak256_op( + function_inputs_and_outputs: (Vec>, Vec), +) -> BlackBoxFuncCall { + let (function_inputs, outputs) = function_inputs_and_outputs; + let function_inputs_len = function_inputs.len(); + BlackBoxFuncCall::Keccak256 { + inputs: function_inputs, + var_message_size: FunctionInput::constant( + function_inputs_len.into(), + FieldElement::max_num_bits(), + ), + outputs: outputs.try_into().expect("Keccak256 returns 32 outputs"), + } +} + +// var_message_size is the number of bytes to take +// from the input. Note: if `var_message_size` +// is more than the number of bytes in the input, +// then an error is returned. +// +// variable inputs +// 32 outputs +fn keccak256_invalid_message_size_op( + function_inputs_and_outputs: (Vec>, Vec), +) -> BlackBoxFuncCall { + let (function_inputs, outputs) = function_inputs_and_outputs; + let function_inputs_len = function_inputs.len(); + BlackBoxFuncCall::Keccak256 { + inputs: function_inputs, + var_message_size: FunctionInput::constant( + (function_inputs_len - 1).into(), + FieldElement::max_num_bits(), + ), + outputs: outputs.try_into().expect("Keccak256 returns 32 outputs"), + } +} + +// 25 inputs +// 25 outputs +fn keccakf1600_op( + function_inputs_and_outputs: (Vec>, Vec), +) -> BlackBoxFuncCall { + let (function_inputs, outputs) = function_inputs_and_outputs; + BlackBoxFuncCall::Keccakf1600 { + inputs: function_inputs.try_into().expect("Keccakf1600 expects 25 inputs"), + outputs: outputs.try_into().expect("Keccakf1600 returns 25 outputs"), + } +} + +// N inputs +// N outputs +fn poseidon2_permutation_op( + function_inputs_and_outputs: (Vec>, Vec), +) -> BlackBoxFuncCall { + let (inputs, outputs) = function_inputs_and_outputs; + let len = inputs.len() as u32; + BlackBoxFuncCall::Poseidon2Permutation { inputs, outputs, len } +} + +// N inputs +// N outputs +fn poseidon2_permutation_invalid_len_op( + function_inputs_and_outputs: (Vec>, Vec), +) -> BlackBoxFuncCall { + let (inputs, outputs) = function_inputs_and_outputs; + let len = (inputs.len() as u32) + 1; + BlackBoxFuncCall::Poseidon2Permutation { inputs, outputs, len } +} + +// 24 inputs (16 + 8) +// 8 outputs +fn sha256_compression_op( + function_inputs_and_outputs: (Vec>, Vec), +) -> BlackBoxFuncCall { + let (function_inputs, outputs) = function_inputs_and_outputs; + let mut function_inputs = function_inputs.into_iter(); + let inputs = core::array::from_fn(|_| function_inputs.next().unwrap()); + let hash_values = core::array::from_fn(|_| function_inputs.next().unwrap()); + BlackBoxFuncCall::Sha256Compression { + inputs: Box::new(inputs), + hash_values: Box::new(hash_values), + outputs: outputs.try_into().unwrap(), + } +} + +fn into_repr_vec(fields: T) -> Vec +where + T: IntoIterator, +{ + fields.into_iter().map(|field| field.into_repr()).collect() +} + +fn into_repr_mat(fields: T) -> Vec> +where + T: IntoIterator, + U: IntoIterator, +{ + fields.into_iter().map(|field| into_repr_vec(field)).collect() +} + +fn run_both_poseidon2_permutations( + inputs: Vec, +) -> (Vec, Vec) { + let result = solve_array_input_blackbox_call( + inputs.clone(), + inputs.len(), + None, + poseidon2_permutation_op, + ); + + let poseidon2_t = POSEIDON2_CONFIG.t as usize; + let poseidon2_d = 5; + let rounds_f = POSEIDON2_CONFIG.rounds_f as usize; + let rounds_p = POSEIDON2_CONFIG.rounds_p as usize; + let mat_internal_diag_m_1 = into_repr_vec(POSEIDON2_CONFIG.internal_matrix_diagonal); + let mat_internal = vec![]; + let round_constants = into_repr_mat(POSEIDON2_CONFIG.round_constant); + + let external_poseidon2 = + zkhash::poseidon2::poseidon2::Poseidon2::new(&Arc::new(Poseidon2Params::new( + poseidon2_t, + poseidon2_d, + rounds_f, + rounds_p, + &mat_internal_diag_m_1, + &mat_internal, + &round_constants, + ))); + + let expected_result = + external_poseidon2.permutation(&into_repr_vec(drop_use_constant(&inputs))); + (into_repr_vec(result), expected_result) +} + +// Using the given BigInt modulus, solve the following circuit: +// - Convert xs, ys to BigInt's with ID's 0, 1, resp. +// - Run the middle_op: +// + Input BigInt ID's: 0, 1 +// + Output BigInt ID: 2 +// - Run BigIntToLeBytes on the output BigInt ID +// - Output the resulting Vec of LE bytes +fn bigint_solve_binary_op( + middle_op: BlackBoxFuncCall, + modulus: Vec, + lhs: Vec, + rhs: Vec, +) -> Vec { + bigint_solve_binary_op_opt(Some(middle_op), modulus, lhs, rhs) +} + +// Using the given BigInt modulus, solve the following circuit: +// - Convert the input to a BigInt with ID 0 +// - Run BigIntToLeBytes on BigInt ID 0 +// - Output the resulting Vec of LE bytes +fn bigint_solve_from_to_le_bytes( + modulus: Vec, + inputs: Vec, +) -> Vec { + bigint_solve_binary_op_opt(None, modulus, inputs, vec![]) +} + fn function_input_from_option( witness: Witness, opt_constant: Option, @@ -827,6 +1271,33 @@ fn prop_assert_zero_l( (solve_blackbox_func_call(op, op_zero, x), FieldElement::zero()) } +// Test that varying one of the inputs produces a different result +// +// (is the op injective for the given inputs?, failure string) +fn prop_assert_injective( + inputs: Vec, + distinct_inputs: Vec, + num_outputs: usize, + num_bits: Option, + op: F, +) -> (bool, String) +where + F: FnOnce((Vec>, Vec)) -> BlackBoxFuncCall + + Clone, +{ + let equal_inputs = drop_use_constant_eq(&inputs, &distinct_inputs); + let message = format!("not injective:\n{:?}\n{:?}", &inputs, &distinct_inputs); + let outputs_not_equal = + solve_array_input_blackbox_call(inputs, num_outputs, num_bits, op.clone()) + != solve_array_input_blackbox_call(distinct_inputs, num_outputs, num_bits, op); + (equal_inputs || outputs_not_equal, message) +} + +fn field_element_ones() -> FieldElement { + let exponent: FieldElement = (253_u128).into(); + FieldElement::from(2u128).pow(&exponent) - FieldElement::one() +} + prop_compose! { // Use both `u128` and hex proptest strategies fn field_element() @@ -841,11 +1312,174 @@ prop_compose! { } } -fn field_element_ones() -> FieldElement { - let exponent: FieldElement = (253_u128).into(); - FieldElement::from(2u128).pow(&exponent) - FieldElement::one() +prop_compose! { + fn any_distinct_inputs(max_input_bits: Option, min_size: usize, max_size: usize) + (size_and_patch in any::<(usize, usize, usize)>()) // NOTE: macro ambiguity when using (x: T) + (inputs_distinct_inputs in + (proptest::collection::vec(any::<(u128, bool)>(), std::cmp::max(min_size, size_and_patch.0 % max_size)), + proptest::collection::vec(any::<(u128, bool)>(), std::cmp::max(min_size, size_and_patch.0 % max_size))), + size_and_patch in Just(size_and_patch)) + -> (Vec, Vec) { + let (_size, patch_location, patch_value) = size_and_patch; + let (inputs, distinct_inputs) = inputs_distinct_inputs; + let to_input = |(x, use_constant)| { + let modulus = if let Some(max_input_bits) = max_input_bits { + 2u128 << max_input_bits + } else { + 1 + }; + (FieldElement::from(x % modulus), use_constant) + }; + let inputs: Vec<_> = inputs.into_iter().map(to_input).collect(); + let mut distinct_inputs: Vec<_> = distinct_inputs.into_iter().map(to_input).collect(); + + // if equivalent w/o use_constant, patch with the patch_value + if drop_use_constant_eq(&inputs, &distinct_inputs) { + let distinct_inputs_len = distinct_inputs.len(); + let positive_patch_value = std::cmp::max(patch_value, 1); + if distinct_inputs_len != 0 { + distinct_inputs[patch_location % distinct_inputs_len].0 += FieldElement::from(positive_patch_value) + } else { + distinct_inputs.push((FieldElement::zero(), true)) + } + } + + (inputs, distinct_inputs) + } +} + +#[test] +fn poseidon2_permutation_zeroes() { + let use_constants: [bool; 4] = [false; 4]; + let inputs: Vec<_> = [FieldElement::zero(); 4].into_iter().zip(use_constants).collect(); + let (result, expected_result) = run_both_poseidon2_permutations(inputs); + + let internal_expected_result = vec![ + field_from_hex("18DFB8DC9B82229CFF974EFEFC8DF78B1CE96D9D844236B496785C698BC6732E"), + field_from_hex("095C230D1D37A246E8D2D5A63B165FE0FADE040D442F61E25F0590E5FB76F839"), + field_from_hex("0BB9545846E1AFA4FA3C97414A60A20FC4949F537A68CCECA34C5CE71E28AA59"), + field_from_hex("18A4F34C9C6F99335FF7638B82AEED9018026618358873C982BBDDE265B2ED6D"), + ]; + + assert_eq!(expected_result, into_repr_vec(internal_expected_result)); + assert_eq!(result, expected_result); +} + +#[test] +fn sha256_zeros() { + let results = solve_array_input_blackbox_call(vec![], 32, None, sha256_op); + let expected_results: Vec<_> = vec![ + 227, 176, 196, 66, 152, 252, 28, 20, 154, 251, 244, 200, 153, 111, 185, 36, 39, 174, 65, + 228, 100, 155, 147, 76, 164, 149, 153, 27, 120, 82, 184, 85, + ] + .into_iter() + .map(|x: u128| FieldElement::from(x)) + .collect(); + assert_eq!(results, expected_results); +} + +#[test] +fn sha256_compression_zeros() { + let results = solve_array_input_blackbox_call( + [(FieldElement::zero(), false); 24].try_into().unwrap(), + 8, + None, + sha256_compression_op, + ); + let expected_results: Vec<_> = vec![ + 2091193876, 1113340840, 3461668143, 3254913767, 3068490961, 2551409935, 2927503052, + 3205228454, + ] + .into_iter() + .map(|x: u128| FieldElement::from(x)) + .collect(); + assert_eq!(results, expected_results); +} + +#[test] +fn blake2s_zeros() { + let results = solve_array_input_blackbox_call(vec![], 32, None, blake2s_op); + let expected_results: Vec<_> = vec![ + 105, 33, 122, 48, 121, 144, 128, 148, 225, 17, 33, 208, 66, 53, 74, 124, 31, 85, 182, 72, + 44, 161, 165, 30, 27, 37, 13, 253, 30, 208, 238, 249, + ] + .into_iter() + .map(|x: u128| FieldElement::from(x)) + .collect(); + assert_eq!(results, expected_results); +} + +#[test] +fn blake3_zeros() { + let results = solve_array_input_blackbox_call(vec![], 32, None, blake3_op); + let expected_results: Vec<_> = vec![ + 175, 19, 73, 185, 245, 249, 161, 166, 160, 64, 77, 234, 54, 220, 201, 73, 155, 203, 37, + 201, 173, 193, 18, 183, 204, 154, 147, 202, 228, 31, 50, 98, + ] + .into_iter() + .map(|x: u128| FieldElement::from(x)) + .collect(); + assert_eq!(results, expected_results); +} + +#[test] +fn keccak256_zeros() { + let results = solve_array_input_blackbox_call(vec![], 32, None, keccak256_op); + let expected_results: Vec<_> = vec![ + 197, 210, 70, 1, 134, 247, 35, 60, 146, 126, 125, 178, 220, 199, 3, 192, 229, 0, 182, 83, + 202, 130, 39, 59, 123, 250, 216, 4, 93, 133, 164, 112, + ] + .into_iter() + .map(|x: u128| FieldElement::from(x)) + .collect(); + assert_eq!(results, expected_results); +} + +#[test] +fn keccakf1600_zeros() { + let results = solve_array_input_blackbox_call( + [(FieldElement::zero(), false); 25].into(), + 25, + Some(64), + keccakf1600_op, + ); + let expected_results: Vec<_> = vec![ + 17376452488221285863, + 9571781953733019530, + 15391093639620504046, + 13624874521033984333, + 10027350355371872343, + 18417369716475457492, + 10448040663659726788, + 10113917136857017974, + 12479658147685402012, + 3500241080921619556, + 16959053435453822517, + 12224711289652453635, + 9342009439668884831, + 4879704952849025062, + 140226327413610143, + 424854978622500449, + 7259519967065370866, + 7004910057750291985, + 13293599522548616907, + 10105770293752443592, + 10668034807192757780, + 1747952066141424100, + 1654286879329379778, + 8500057116360352059, + 16929593379567477321, + ] + .into_iter() + .map(|x: u128| FieldElement::from(x)) + .collect(); + + assert_eq!(results, expected_results); } +// NOTE: an "average" bigint is large, so consider increasing the number of proptest shrinking +// iterations (from the default 1024) to reach a simplified case, e.g. +// PROPTEST_MAX_SHRINK_ITERS=1024000 proptest! { #[test] @@ -910,4 +1544,329 @@ proptest! { let (lhs, rhs) = prop_assert_zero_l(and_op, zero, x); prop_assert_eq!(lhs, rhs); } + + #[test] + fn poseidon2_permutation_matches_external_impl(inputs in proptest::collection::vec(field_element(), 4)) { + let (result, expected_result) = run_both_poseidon2_permutations(inputs); + prop_assert_eq!(result, expected_result) + } + + #[test] + fn sha256_injective(inputs_distinct_inputs in any_distinct_inputs(None, 0, 32)) { + let (inputs, distinct_inputs) = inputs_distinct_inputs; + let (result, message) = prop_assert_injective(inputs, distinct_inputs, 32, None, sha256_op); + prop_assert!(result, "{}", message); + } + + #[test] + fn sha256_compression_injective(inputs_distinct_inputs in any_distinct_inputs(None, 24, 24)) { + let (inputs, distinct_inputs) = inputs_distinct_inputs; + if inputs.len() == 24 && distinct_inputs.len() == 24 { + let (result, message) = prop_assert_injective(inputs, distinct_inputs, 8, None, sha256_compression_op); + prop_assert!(result, "{}", message); + } + } + + #[test] + fn blake2s_injective(inputs_distinct_inputs in any_distinct_inputs(None, 0, 32)) { + let (inputs, distinct_inputs) = inputs_distinct_inputs; + let (result, message) = prop_assert_injective(inputs, distinct_inputs, 32, None, blake2s_op); + prop_assert!(result, "{}", message); + } + + #[test] + fn blake3_injective(inputs_distinct_inputs in any_distinct_inputs(None, 0, 32)) { + let (inputs, distinct_inputs) = inputs_distinct_inputs; + let (result, message) = prop_assert_injective(inputs, distinct_inputs, 32, None, blake3_op); + prop_assert!(result, "{}", message); + } + + #[test] + fn keccak256_injective(inputs_distinct_inputs in any_distinct_inputs(Some(8), 0, 32)) { + let (inputs, distinct_inputs) = inputs_distinct_inputs; + let (result, message) = prop_assert_injective(inputs, distinct_inputs, 32, Some(32), keccak256_op); + prop_assert!(result, "{}", message); + } + + // TODO(https://github.com/noir-lang/noir/issues/5689): doesn't fail with a user error + // The test failing with "not injective" demonstrates that it returns constant output instead + // of failing with a user error. + #[test] + #[should_panic(expected = "Test failed: not injective")] + fn keccak256_invalid_message_size_fails(inputs_distinct_inputs in any_distinct_inputs(Some(8), 0, 32)) { + let (inputs, distinct_inputs) = inputs_distinct_inputs; + let (result, message) = prop_assert_injective(inputs, distinct_inputs, 32, Some(8), keccak256_invalid_message_size_op); + prop_assert!(result, "{}", message); + } + + #[test] + fn keccakf1600_injective(inputs_distinct_inputs in any_distinct_inputs(Some(8), 25, 25)) { + let (inputs, distinct_inputs) = inputs_distinct_inputs; + assert_eq!(inputs.len(), 25); + assert_eq!(distinct_inputs.len(), 25); + let (result, message) = prop_assert_injective(inputs, distinct_inputs, 25, Some(64), keccakf1600_op); + prop_assert!(result, "{}", message); + } + + // TODO(https://github.com/noir-lang/noir/issues/5699): wrong failure message + #[test] + #[should_panic(expected = "Failure(BlackBoxFunctionFailed(Poseidon2Permutation, \"the number of inputs does not match specified length. 6 != 7\"))")] + fn poseidon2_permutation_invalid_size_fails(inputs_distinct_inputs in any_distinct_inputs(None, 6, 6)) { + let (inputs, distinct_inputs) = inputs_distinct_inputs; + let (result, message) = prop_assert_injective(inputs, distinct_inputs, 1, None, poseidon2_permutation_invalid_len_op); + prop_assert!(result, "{}", message); + } + + #[test] + fn bigint_from_to_le_bytes_zero_one(modulus in select(allowed_bigint_moduli()), zero_or_ones_constant: bool, use_constant: bool) { + let zero_function_input = if zero_or_ones_constant { + FieldElement::one() + } else { + FieldElement::zero() + }; + let zero_or_ones: Vec<_> = modulus.iter().map(|_| (zero_function_input, use_constant)).collect(); + let expected_results = drop_use_constant(&zero_or_ones); + let results = bigint_solve_from_to_le_bytes(modulus.clone(), zero_or_ones); + prop_assert_eq!(results, expected_results) + } + + #[test] + fn bigint_from_to_le_bytes((input, modulus) in bigint_with_modulus()) { + let expected_results: Vec<_> = drop_use_constant(&input); + let results = bigint_solve_from_to_le_bytes(modulus.clone(), input); + prop_assert_eq!(results, expected_results) + } + + #[test] + // TODO(https://github.com/noir-lang/noir/issues/5580): desired behavior? + fn bigint_from_to_le_bytes_extra_input_bytes((input, modulus) in bigint_with_modulus(), extra_bytes_len: u8, extra_bytes in proptest::collection::vec(any::<(u8, bool)>(), u8::MAX as usize)) { + let mut input = input; + let mut extra_bytes: Vec<_> = extra_bytes.into_iter().take(extra_bytes_len as usize).map(|(x, use_constant)| (FieldElement::from(x as u128), use_constant)).collect(); + input.append(&mut extra_bytes); + let expected_results: Vec<_> = drop_use_constant(&input); + let results = bigint_solve_from_to_le_bytes(modulus.clone(), input); + prop_assert_eq!(results, expected_results) + } + + #[test] + // TODO(https://github.com/noir-lang/noir/issues/5580): desired behavior? + #[should_panic(expected = "Test failed: assertion failed: `(left == right)`")] + fn bigint_from_to_le_bytes_bigger_than_u8((input, modulus) in bigint_with_modulus(), patch_location: usize, larger_value: u16, use_constant: bool) { + let mut input = input; + let patch_location = patch_location % input.len(); + let larger_value = FieldElement::from(std::cmp::max((u8::MAX as u16) + 1, larger_value) as u128); + input[patch_location] = (larger_value, use_constant); + let expected_results: Vec<_> = drop_use_constant(&input); + let results = bigint_solve_from_to_le_bytes(modulus.clone(), input); + prop_assert_eq!(results, expected_results) + } + + #[test] + // TODO(https://github.com/noir-lang/noir/issues/5578): this test attempts to use a guaranteed-invalid BigInt modulus + // #[should_panic(expected = "attempt to add with overflow")] + fn bigint_from_to_le_bytes_disallowed_modulus(mut modulus in select(allowed_bigint_moduli()), patch_location: usize, patch_amount: u8, zero_or_ones_constant: bool, use_constant: bool) { + let allowed_moduli: HashSet> = allowed_bigint_moduli().into_iter().collect(); + let mut patch_location = patch_location % modulus.len(); + let patch_amount = patch_amount.clamp(1, u8::MAX); + while allowed_moduli.contains(&modulus) { + modulus[patch_location] = patch_amount.wrapping_add(modulus[patch_location]); + patch_location += 1; + patch_location %= modulus.len(); + } + + let zero_function_input = if zero_or_ones_constant { + FieldElement::zero() + } else { + FieldElement::one() + }; + let zero: Vec<_> = modulus.iter().map(|_| (zero_function_input, use_constant)).collect(); + let expected_results: Vec<_> = drop_use_constant(&zero); + let results = bigint_solve_from_to_le_bytes(modulus.clone(), zero); + + prop_assert_eq!(results, expected_results) + } + + #[test] + fn bigint_add_commutative((xs, ys, modulus) in bigint_pair_with_modulus()) { + let lhs_results = bigint_solve_binary_op(bigint_add_op(), modulus.clone(), xs.clone(), ys.clone()); + let rhs_results = bigint_solve_binary_op(bigint_add_op(), modulus, ys, xs); + + prop_assert_eq!(lhs_results, rhs_results) + } + + #[test] + fn bigint_mul_commutative((xs, ys, modulus) in bigint_pair_with_modulus()) { + let lhs_results = bigint_solve_binary_op(bigint_mul_op(), modulus.clone(), xs.clone(), ys.clone()); + let rhs_results = bigint_solve_binary_op(bigint_mul_op(), modulus, ys, xs); + + prop_assert_eq!(lhs_results, rhs_results) + } + + #[test] + fn bigint_add_associative((xs, ys, zs, modulus) in bigint_triple_with_modulus()) { + // f(f(xs, ys), zs) == + let op_xs_ys = bigint_solve_binary_op(bigint_add_op(), modulus.clone(), xs.clone(), ys.clone()); + let xs_ys = use_witnesses(op_xs_ys); + let op_xs_ys_op_zs = bigint_solve_binary_op(bigint_add_op(), modulus.clone(), xs_ys, zs.clone()); + + // f(xs, f(ys, zs)) + let op_ys_zs = bigint_solve_binary_op(bigint_add_op(), modulus.clone(), ys.clone(), zs.clone()); + let ys_zs = use_witnesses(op_ys_zs); + let op_xs_op_ys_zs = bigint_solve_binary_op(bigint_add_op(), modulus, xs, ys_zs); + + prop_assert_eq!(op_xs_ys_op_zs, op_xs_op_ys_zs) + } + + #[test] + fn bigint_mul_associative((xs, ys, zs, modulus) in bigint_triple_with_modulus()) { + // f(f(xs, ys), zs) == + let op_xs_ys = bigint_solve_binary_op(bigint_mul_op(), modulus.clone(), xs.clone(), ys.clone()); + let xs_ys = use_witnesses(op_xs_ys); + let op_xs_ys_op_zs = bigint_solve_binary_op(bigint_mul_op(), modulus.clone(), xs_ys, zs.clone()); + + // f(xs, f(ys, zs)) + let op_ys_zs = bigint_solve_binary_op(bigint_mul_op(), modulus.clone(), ys.clone(), zs.clone()); + let ys_zs = use_witnesses(op_ys_zs); + let op_xs_op_ys_zs = bigint_solve_binary_op(bigint_mul_op(), modulus, xs, ys_zs); + + prop_assert_eq!(op_xs_ys_op_zs, op_xs_op_ys_zs) + } + + #[test] + fn bigint_mul_add_distributive((xs, ys, zs, modulus) in bigint_triple_with_modulus()) { + // xs * (ys + zs) == + let add_ys_zs = bigint_solve_binary_op(bigint_add_op(), modulus.clone(), ys.clone(), zs.clone()); + let add_ys_zs = use_witnesses(add_ys_zs); + let mul_xs_add_ys_zs = bigint_solve_binary_op(bigint_mul_op(), modulus.clone(), xs.clone(), add_ys_zs); + + // xs * ys + xs * zs + let mul_xs_ys = bigint_solve_binary_op(bigint_mul_op(), modulus.clone(), xs.clone(), ys); + let mul_xs_ys = use_witnesses(mul_xs_ys); + let mul_xs_zs = bigint_solve_binary_op(bigint_mul_op(), modulus.clone(), xs, zs); + let mul_xs_zs = use_witnesses(mul_xs_zs); + let add_mul_xs_ys_mul_xs_zs = bigint_solve_binary_op(bigint_add_op(), modulus, mul_xs_ys, mul_xs_zs); + + prop_assert_eq!(mul_xs_add_ys_zs, add_mul_xs_ys_mul_xs_zs) + } + + + #[test] + fn bigint_add_zero_l((xs, modulus) in bigint_with_modulus()) { + let zero = bigint_zeroed(&xs); + let expected_results = drop_use_constant(&xs); + let results = bigint_solve_binary_op(bigint_add_op(), modulus, zero, xs); + + prop_assert_eq!(results, expected_results) + } + + #[test] + fn bigint_mul_zero_l((xs, modulus) in bigint_with_modulus()) { + let zero = bigint_zeroed(&xs); + let expected_results = drop_use_constant(&zero); + let results = bigint_solve_binary_op(bigint_mul_op(), modulus, zero, xs); + prop_assert_eq!(results, expected_results) + } + + #[test] + fn bigint_mul_one_l((xs, modulus) in bigint_with_modulus()) { + let one = bigint_to_one(&xs); + let expected_results: Vec<_> = drop_use_constant(&xs); + let results = bigint_solve_binary_op(bigint_mul_op(), modulus, one, xs); + prop_assert_eq!(results, expected_results) + } + + #[test] + fn bigint_sub_self((xs, modulus) in bigint_with_modulus()) { + let expected_results = drop_use_constant(&bigint_zeroed(&xs)); + let results = bigint_solve_binary_op(bigint_sub_op(), modulus, xs.clone(), xs); + prop_assert_eq!(results, expected_results) + } + + #[test] + fn bigint_sub_zero((xs, modulus) in bigint_with_modulus()) { + let zero = bigint_zeroed(&xs); + let expected_results: Vec<_> = drop_use_constant(&xs); + let results = bigint_solve_binary_op(bigint_sub_op(), modulus, xs, zero); + prop_assert_eq!(results, expected_results) + } + + #[test] + fn bigint_sub_one((xs, modulus) in bigint_with_modulus()) { + let one = bigint_to_one(&xs); + let expected_results: Vec<_> = drop_use_constant(&xs); + let results = bigint_solve_binary_op(bigint_sub_op(), modulus, xs, one); + prop_assert!(results != expected_results, "{:?} == {:?}", results, expected_results) + } + + #[test] + fn bigint_div_self((xs, modulus) in bigint_with_modulus()) { + let one = drop_use_constant(&bigint_to_one(&xs)); + let results = bigint_solve_binary_op(bigint_div_op(), modulus, xs.clone(), xs); + prop_assert_eq!(results, one) + } + + #[test] + // TODO(https://github.com/noir-lang/noir/issues/5645) + fn bigint_div_by_zero((xs, modulus) in bigint_with_modulus()) { + let zero = bigint_zeroed(&xs); + let expected_results = drop_use_constant(&zero); + let results = bigint_solve_binary_op(bigint_div_op(), modulus, xs, zero); + prop_assert_eq!(results, expected_results) + } + + #[test] + fn bigint_div_one((xs, modulus) in bigint_with_modulus()) { + let one = bigint_to_one(&xs); + let expected_results = drop_use_constant(&xs); + let results = bigint_solve_binary_op(bigint_div_op(), modulus, xs, one); + prop_assert_eq!(results, expected_results) + } + + #[test] + fn bigint_div_zero((xs, modulus) in bigint_with_modulus()) { + let zero = bigint_zeroed(&xs); + let expected_results = drop_use_constant(&zero); + let results = bigint_solve_binary_op(bigint_div_op(), modulus, zero, xs); + prop_assert_eq!(results, expected_results) + } + + #[test] + fn bigint_add_sub((xs, ys, modulus) in bigint_pair_with_modulus()) { + let expected_results = drop_use_constant(&xs); + let add_results = bigint_solve_binary_op(bigint_add_op(), modulus.clone(), xs, ys.clone()); + let add_bigint = use_witnesses(add_results); + let results = bigint_solve_binary_op(bigint_sub_op(), modulus, add_bigint, ys); + + prop_assert_eq!(results, expected_results) + } + + #[test] + fn bigint_sub_add((xs, ys, modulus) in bigint_pair_with_modulus()) { + let expected_results = drop_use_constant(&xs); + let sub_results = bigint_solve_binary_op(bigint_sub_op(), modulus.clone(), xs, ys.clone()); + let add_bigint = use_witnesses(sub_results); + let results = bigint_solve_binary_op(bigint_add_op(), modulus, add_bigint, ys); + + prop_assert_eq!(results, expected_results) + } + + #[test] + fn bigint_div_mul((xs, ys, modulus) in bigint_pair_with_modulus()) { + let expected_results = drop_use_constant(&xs); + let div_results = bigint_solve_binary_op(bigint_div_op(), modulus.clone(), xs, ys.clone()); + let div_bigint = use_witnesses(div_results); + let results = bigint_solve_binary_op(bigint_mul_op(), modulus, div_bigint, ys); + + prop_assert_eq!(results, expected_results) + } + + #[test] + fn bigint_mul_div((xs, ys, modulus) in bigint_pair_with_modulus()) { + let expected_results = drop_use_constant(&xs); + let mul_results = bigint_solve_binary_op(bigint_mul_op(), modulus.clone(), xs, ys.clone()); + let mul_bigint = use_witnesses(mul_results); + let results = bigint_solve_binary_op(bigint_div_op(), modulus, mul_bigint, ys); + + prop_assert_eq!(results, expected_results) + } } diff --git a/noir/noir-repo/acvm-repo/acvm_js/Cargo.toml b/noir/noir-repo/acvm-repo/acvm_js/Cargo.toml index 0d90b9ba54f..6b457a8f5f8 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 c07d2d8a4c1..16fb26e55db 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 c596dcf9614..98a0c4c3abe 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 23da88247fc..f6a00af7942 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 10b491c7f67..d0473857e1c 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 ab677396c22..9a23f503a06 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 bb51426b33b..a4125014d56 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/bn254_blackbox_solver/src/lib.rs b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/lib.rs index 6897116e90e..43ee6a9ddd2 100644 --- a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/lib.rs +++ b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/lib.rs @@ -13,7 +13,7 @@ mod schnorr; use ark_ec::AffineRepr; pub use embedded_curve_ops::{embedded_curve_add, multi_scalar_mul}; pub use generator::generators::derive_generators; -pub use poseidon2::poseidon2_permutation; +pub use poseidon2::{field_from_hex, poseidon2_permutation, Poseidon2Config, POSEIDON2_CONFIG}; // Temporary hack, this ensure that we always use a bn254 field here // without polluting the feature flags of the `acir_field` crate. diff --git a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/poseidon2.rs b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/poseidon2.rs index 18ed0b1d8ab..dd3e8b725c2 100644 --- a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/poseidon2.rs +++ b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/poseidon2.rs @@ -16,26 +16,26 @@ pub(crate) struct Poseidon2<'a> { config: &'a Poseidon2Config, } -struct Poseidon2Config { - t: u32, - rounds_f: u32, - rounds_p: u32, - internal_matrix_diagonal: [FieldElement; 4], - round_constant: [[FieldElement; 4]; 64], +pub struct Poseidon2Config { + pub t: u32, + pub rounds_f: u32, + pub rounds_p: u32, + pub internal_matrix_diagonal: [FieldElement; 4], + pub round_constant: [[FieldElement; 4]; 64], } -fn field_from_hex(hex: &str) -> FieldElement { +pub fn field_from_hex(hex: &str) -> FieldElement { FieldElement::from_be_bytes_reduce(&hex::decode(hex).expect("Should be passed only valid hex")) } lazy_static! { - static ref INTERNAL_MATRIX_DIAGONAL: [FieldElement; 4] = [ + pub static ref INTERNAL_MATRIX_DIAGONAL: [FieldElement; 4] = [ field_from_hex("10dc6e9c006ea38b04b1e03b4bd9490c0d03f98929ca1d7fb56821fd19d3b6e7"), field_from_hex("0c28145b6a44df3e0149b3d0a30b3bb599df9756d4dd9b84a86b38cfb45a740b"), field_from_hex("00544b8338791518b2c7645a50392798b21f75bb60e3596170067d00141cac15"), field_from_hex("222c01175718386f2e2e82eb122789e352e105a3b8fa852613bc534433ee428b"), ]; - static ref ROUND_CONSTANT: [[FieldElement; 4]; 64] = [ + pub static ref ROUND_CONSTANT: [[FieldElement; 4]; 64] = [ [ field_from_hex("19b849f69450b06848da1d39bd5e4a4302bb86744edc26238b0878e269ed23e5"), field_from_hex("265ddfe127dd51bd7239347b758f0a1320eb2cc7450acc1dad47f80c8dcf34d6"), @@ -421,7 +421,7 @@ lazy_static! { field_from_hex("176563472456aaa746b694c60e1823611ef39039b2edc7ff391e6f2293d2c404"), ], ]; - static ref POSEIDON2_CONFIG: Poseidon2Config = Poseidon2Config { + pub static ref POSEIDON2_CONFIG: Poseidon2Config = Poseidon2Config { t: 4, rounds_f: 8, rounds_p: 56, diff --git a/noir/noir-repo/acvm-repo/brillig/Cargo.toml b/noir/noir-repo/acvm-repo/brillig/Cargo.toml index 631acbd55d8..9f5a5ce0ee2 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 ebd35f1579f..cd9f754ebc0 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 a99a654aeed..c9d88e36e28 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 dd3ec7f6a75..7a8a7187857 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/transforms/functions.rs b/noir/noir-repo/aztec_macros/src/transforms/functions.rs index cd3fdd1fc62..dcd4fdc76f6 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/functions.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/functions.rs @@ -267,7 +267,7 @@ fn create_inputs(ty: &str) -> Param { let path_snippet = ty.to_case(Case::Snake); // e.g. private_context_inputs let type_path = chained_dep!("aztec", "context", "inputs", &path_snippet, ty); - let context_type = make_type(UnresolvedTypeData::Named(type_path, vec![], true)); + let context_type = make_type(UnresolvedTypeData::Named(type_path, Default::default(), true)); let visibility = Visibility::Private; Param { pattern: context_pattern, typ: context_type, visibility, span: Span::default() } @@ -396,7 +396,7 @@ fn serialize_to_hasher( Signedness::Unsigned, ast::IntegerBitSize::ThirtyTwo, ), - span: None, + span: Span::default(), }, hasher_name, )) @@ -595,7 +595,7 @@ fn abstract_return_values(func: &NoirFunction) -> Result>, serialize_to_hasher(&ident(return_value_name), ¤t_return_type, hasher_name) .ok_or_else(|| AztecMacroError::UnsupportedFunctionReturnType { typ: current_return_type.clone(), - span: func.return_type().span.unwrap_or_default(), + span: func.return_type().span, })?; replacement_statements.extend(serialization_statements); diff --git a/noir/noir-repo/aztec_macros/src/transforms/note_interface.rs b/noir/noir-repo/aztec_macros/src/transforms/note_interface.rs index 7f5c0e8b48b..8df1d128c6f 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/note_interface.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/note_interface.rs @@ -62,8 +62,9 @@ pub fn generate_note_interface_impl( note_struct.name.0.contents )), })?; - let note_interface_impl_span: Option = - if empty_spans { None } else { trait_impl.object_type.span }; + let note_interface_impl_span = + if empty_spans { Span::default() } else { trait_impl.object_type.span }; + // Look for the note struct implementation, generate a default one if it doesn't exist (in order to append methods to it) let existing_impl = module.impls.iter_mut().find(|r#impl| match &r#impl.object_type.typ { UnresolvedTypeData::Named(path, _, _) => path.last_ident().eq(¬e_struct.name), @@ -87,6 +88,7 @@ pub fn generate_note_interface_impl( let mut note_fields = vec![]; let note_interface_generics = trait_impl .trait_generics + .ordered_args .iter() .map(|gen| match gen.typ.clone() { UnresolvedTypeData::Named(path, _, _) => Ok(path.last_name().to_string()), @@ -94,7 +96,7 @@ pub fn generate_note_interface_impl( Ok(val.to_string()) } _ => Err(AztecMacroError::CouldNotImplementNoteInterface { - span: trait_impl.object_type.span, + span: Some(trait_impl.object_type.span), secondary_message: Some(format!( "NoteInterface must be generic over NOTE_LEN and NOTE_BYTES_LEN: {}", note_type @@ -119,7 +121,7 @@ pub fn generate_note_interface_impl( ident("header"), make_type(UnresolvedTypeData::Named( chained_dep!("aztec", "note", "note_header", "NoteHeader"), - vec![], + Default::default(), false, )), ); @@ -231,7 +233,7 @@ fn generate_note_to_be_bytes( note_type: &String, byte_length: &str, serialized_length: &str, - impl_span: Option, + impl_span: Span, empty_spans: bool, ) -> Result { let function_source = format!( @@ -268,13 +270,13 @@ fn generate_note_to_be_bytes( dbg!(errors); return Err(AztecMacroError::CouldNotImplementNoteInterface { secondary_message: Some("Failed to parse Noir macro code (fn to_be_bytes). This is either a bug in the compiler or the Noir macro code".to_string()), - span: impl_span + span: Some(impl_span) }); } let mut function_ast = function_ast.into_sorted(); let mut noir_fn = function_ast.functions.remove(0); - noir_fn.def.span = impl_span.unwrap_or_default(); + noir_fn.def.span = impl_span; noir_fn.def.visibility = ItemVisibility::Public; Ok(noir_fn) } @@ -282,7 +284,7 @@ fn generate_note_to_be_bytes( fn generate_note_get_header( note_type: &String, note_header_field_name: &String, - impl_span: Option, + impl_span: Span, empty_spans: bool, ) -> Result { let function_source = format!( @@ -300,13 +302,13 @@ fn generate_note_get_header( dbg!(errors); return Err(AztecMacroError::CouldNotImplementNoteInterface { secondary_message: Some("Failed to parse Noir macro code (fn get_header). This is either a bug in the compiler or the Noir macro code".to_string()), - span: impl_span + span: Some(impl_span) }); } let mut function_ast = function_ast.into_sorted(); let mut noir_fn = function_ast.functions.remove(0); - noir_fn.def.span = impl_span.unwrap_or_default(); + noir_fn.def.span = impl_span; noir_fn.def.visibility = ItemVisibility::Public; Ok(noir_fn) } @@ -314,7 +316,7 @@ fn generate_note_get_header( fn generate_note_set_header( note_type: &String, note_header_field_name: &String, - impl_span: Option, + impl_span: Span, empty_spans: bool, ) -> Result { let function_source = format!( @@ -331,13 +333,13 @@ fn generate_note_set_header( dbg!(errors); return Err(AztecMacroError::CouldNotImplementNoteInterface { secondary_message: Some("Failed to parse Noir macro code (fn set_header). This is either a bug in the compiler or the Noir macro code".to_string()), - span: impl_span + span: Some(impl_span) }); } let mut function_ast = function_ast.into_sorted(); let mut noir_fn = function_ast.functions.remove(0); - noir_fn.def.span = impl_span.unwrap_or_default(); + noir_fn.def.span = impl_span; noir_fn.def.visibility = ItemVisibility::Public; Ok(noir_fn) } @@ -346,7 +348,7 @@ fn generate_note_set_header( // of the conversion of the characters in the note's struct name to unsigned integers. fn generate_get_note_type_id( note_type_id: u32, - impl_span: Option, + impl_span: Span, empty_spans: bool, ) -> Result { // TODO(#7165): replace {} with dep::aztec::protocol_types::abis::note_selector::compute_note_selector(\"{}\") in the function source below @@ -365,13 +367,13 @@ fn generate_get_note_type_id( dbg!(errors); return Err(AztecMacroError::CouldNotImplementNoteInterface { secondary_message: Some("Failed to parse Noir macro code (fn get_note_type_id). This is either a bug in the compiler or the Noir macro code".to_string()), - span: impl_span + span: Some(impl_span) }); } let mut function_ast = function_ast.into_sorted(); let mut noir_fn = function_ast.functions.remove(0); - noir_fn.def.span = impl_span.unwrap_or_default(); + noir_fn.def.span = impl_span; noir_fn.def.visibility = ItemVisibility::Public; Ok(noir_fn) } @@ -389,7 +391,7 @@ fn generate_note_properties_struct( note_type: &str, note_fields: &[(String, String)], note_header_field_name: &String, - impl_span: Option, + impl_span: Span, empty_spans: bool, ) -> Result { let struct_source = @@ -400,7 +402,7 @@ fn generate_note_properties_struct( dbg!(errors); return Err(AztecMacroError::CouldNotImplementNoteInterface { secondary_message: Some(format!("Failed to parse Noir macro code (struct {}Properties). This is either a bug in the compiler or the Noir macro code", note_type)), - span: impl_span + span: Some(impl_span) }); } @@ -423,7 +425,7 @@ fn generate_note_deserialize_content( note_fields: &[(String, String)], note_serialize_len: &String, note_header_field_name: &String, - impl_span: Option, + impl_span: Span, empty_spans: bool, ) -> Result { let function_source = generate_note_deserialize_content_source( @@ -438,13 +440,13 @@ fn generate_note_deserialize_content( dbg!(errors); return Err(AztecMacroError::CouldNotImplementNoteInterface { secondary_message: Some("Failed to parse Noir macro code (fn deserialize_content). This is either a bug in the compiler or the Noir macro code".to_string()), - span: impl_span + span: Some(impl_span) }); } let mut function_ast = function_ast.into_sorted(); let mut noir_fn = function_ast.functions.remove(0); - noir_fn.def.span = impl_span.unwrap_or_default(); + noir_fn.def.span = impl_span; noir_fn.def.visibility = ItemVisibility::Public; Ok(noir_fn) } @@ -461,7 +463,7 @@ fn generate_note_serialize_content( note_fields: &[(String, String)], note_serialize_len: &String, note_header_field_name: &String, - impl_span: Option, + impl_span: Span, empty_spans: bool, ) -> Result { let function_source = generate_note_serialize_content_source( @@ -476,13 +478,13 @@ fn generate_note_serialize_content( dbg!(errors); return Err(AztecMacroError::CouldNotImplementNoteInterface { secondary_message: Some("Failed to parse Noir macro code (fn serialize_content). This is either a bug in the compiler or the Noir macro code".to_string()), - span: impl_span + span: Some(impl_span) }); } let mut function_ast = function_ast.into_sorted(); let mut noir_fn = function_ast.functions.remove(0); - noir_fn.def.span = impl_span.unwrap_or_default(); + noir_fn.def.span = impl_span; noir_fn.def.visibility = ItemVisibility::Public; Ok(noir_fn) } @@ -492,7 +494,7 @@ fn generate_note_properties_fn( note_type: &str, note_fields: &[(String, String)], note_header_field_name: &String, - impl_span: Option, + impl_span: Span, empty_spans: bool, ) -> Result { let function_source = @@ -502,12 +504,12 @@ fn generate_note_properties_fn( dbg!(errors); return Err(AztecMacroError::CouldNotImplementNoteInterface { secondary_message: Some("Failed to parse Noir macro code (fn properties). This is either a bug in the compiler or the Noir macro code".to_string()), - span: impl_span + span: Some(impl_span) }); } let mut function_ast = function_ast.into_sorted(); let mut noir_fn = function_ast.functions.remove(0); - noir_fn.def.span = impl_span.unwrap_or_default(); + noir_fn.def.span = impl_span; noir_fn.def.visibility = ItemVisibility::Public; Ok(noir_fn) } @@ -519,7 +521,7 @@ fn generate_note_properties_fn( // fn generate_compute_note_hiding_point( note_type: &String, - impl_span: Option, + impl_span: Span, empty_spans: bool, ) -> Result { // TODO(#7771): update this to do only 1 MSM call @@ -541,12 +543,12 @@ fn generate_compute_note_hiding_point( dbg!(errors); return Err(AztecMacroError::CouldNotImplementNoteInterface { secondary_message: Some("Failed to parse Noir macro code (fn compute_note_hiding_point). This is either a bug in the compiler or the Noir macro code".to_string()), - span: impl_span + span: Some(impl_span) }); } let mut function_ast = function_ast.into_sorted(); let mut noir_fn = function_ast.functions.remove(0); - noir_fn.def.span = impl_span.unwrap_or_default(); + noir_fn.def.span = impl_span; noir_fn.def.visibility = ItemVisibility::Public; Ok(noir_fn) } diff --git a/noir/noir-repo/aztec_macros/src/transforms/storage.rs b/noir/noir-repo/aztec_macros/src/transforms/storage.rs index dacea1a95e3..7dd21f1a8ac 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/storage.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/storage.rs @@ -1,8 +1,9 @@ use acvm::acir::AcirField; use noirc_errors::Span; use noirc_frontend::ast::{ - BlockExpression, Expression, ExpressionKind, FunctionDefinition, Ident, Literal, NoirFunction, - NoirStruct, Pattern, StatementKind, TypeImpl, UnresolvedType, UnresolvedTypeData, + BlockExpression, Expression, ExpressionKind, FunctionDefinition, GenericTypeArgs, Ident, + Literal, NoirFunction, NoirStruct, Pattern, StatementKind, TypeImpl, UnresolvedType, + UnresolvedTypeData, }; use noirc_frontend::{ graph::CrateId, @@ -54,13 +55,13 @@ pub fn check_for_storage_definition( fn inject_context_in_storage_field(field: &mut UnresolvedType) -> Result<(), AztecMacroError> { match &mut field.typ { UnresolvedTypeData::Named(path, generics, _) => { - generics.push(make_type(UnresolvedTypeData::Named( + generics.ordered_args.push(make_type(UnresolvedTypeData::Named( ident_path("Context"), - vec![], + GenericTypeArgs::default(), false, ))); match path.last_name() { - "Map" => inject_context_in_storage_field(&mut generics[1]), + "Map" => inject_context_in_storage_field(&mut generics.ordered_args[1]), _ => Ok(()), } } @@ -144,7 +145,10 @@ pub fn generate_storage_field_constructor( generate_storage_field_constructor( // Map is expected to have three generic parameters: key, value and context (i.e. // Map. Here `get(1)` fetches the value type. - &(type_ident.clone(), generics.get(1).unwrap().clone()), + &( + type_ident.clone(), + generics.ordered_args.get(1).unwrap().clone(), + ), variable("slot"), )?, ), @@ -219,8 +223,11 @@ pub fn generate_storage_implementation( // This is the type over which the impl is generic. let generic_context_ident = ident("Context"); - let generic_context_type = - make_type(UnresolvedTypeData::Named(ident_path("Context"), vec![], true)); + let generic_context_type = make_type(UnresolvedTypeData::Named( + ident_path("Context"), + GenericTypeArgs::default(), + true, + )); let init = NoirFunction::normal(FunctionDefinition::normal( &ident("init"), @@ -231,14 +238,13 @@ pub fn generate_storage_implementation( &return_type(chained_path!("Self")), )); + let ordered_args = vec![generic_context_type.clone()]; + let generics = GenericTypeArgs { ordered_args, named_args: Vec::new() }; + let storage_impl = TypeImpl { object_type: UnresolvedType { - typ: UnresolvedTypeData::Named( - chained_path!(storage_struct_name), - vec![generic_context_type.clone()], - true, - ), - span: Some(Span::default()), + typ: UnresolvedTypeData::Named(chained_path!(storage_struct_name), generics, true), + span: Span::default(), }, type_span: Span::default(), generics: vec![generic_context_ident.into()], diff --git a/noir/noir-repo/aztec_macros/src/utils/ast_utils.rs b/noir/noir-repo/aztec_macros/src/utils/ast_utils.rs index a74ec5b777a..955e4111bb3 100644 --- a/noir/noir-repo/aztec_macros/src/utils/ast_utils.rs +++ b/noir/noir-repo/aztec_macros/src/utils/ast_utils.rs @@ -108,17 +108,14 @@ pub fn assignment_with_type( } pub fn return_type(path: Path) -> FunctionReturnType { - let ty = make_type(UnresolvedTypeData::Named(path, vec![], true)); + let ty = make_type(UnresolvedTypeData::Named(path, Default::default(), true)); FunctionReturnType::Ty(ty) } pub fn lambda(parameters: Vec<(Pattern, UnresolvedType)>, body: Expression) -> Expression { expression(ExpressionKind::Lambda(Box::new(Lambda { parameters, - return_type: UnresolvedType { - typ: UnresolvedTypeData::Unspecified, - span: Some(Span::default()), - }, + return_type: UnresolvedType { typ: UnresolvedTypeData::Unspecified, span: Span::default() }, body, }))) } @@ -179,7 +176,7 @@ pub fn cast(lhs: Expression, ty: UnresolvedTypeData) -> Expression { } pub fn make_type(typ: UnresolvedTypeData) -> UnresolvedType { - UnresolvedType { typ, span: Some(Span::default()) } + UnresolvedType { typ, span: Span::default() } } pub fn index_array(array: Ident, index: &str) -> Expression { 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 3b3813da6ee..f2998fbaafc 100644 --- a/noir/noir-repo/aztec_macros/src/utils/parse_utils.rs +++ b/noir/noir-repo/aztec_macros/src/utils/parse_utils.rs @@ -2,12 +2,13 @@ use noirc_frontend::{ ast::{ ArrayLiteral, AssignStatement, BlockExpression, CallExpression, CastExpression, ConstrainStatement, ConstructorExpression, Expression, ExpressionKind, ForLoopStatement, - ForRange, FunctionReturnType, Ident, IfExpression, IndexExpression, InfixExpression, - LValue, Lambda, LetStatement, Literal, MemberAccessExpression, MethodCallExpression, - ModuleDeclaration, NoirFunction, NoirStruct, NoirTrait, NoirTraitImpl, NoirTypeAlias, Path, - PathSegment, Pattern, PrefixExpression, Statement, StatementKind, TraitImplItem, TraitItem, - TypeImpl, UnresolvedGeneric, UnresolvedGenerics, UnresolvedTraitConstraint, UnresolvedType, - UnresolvedTypeData, UnresolvedTypeExpression, UseTree, UseTreeKind, + ForRange, FunctionReturnType, GenericTypeArgs, Ident, IfExpression, IndexExpression, + InfixExpression, LValue, Lambda, LetStatement, Literal, MemberAccessExpression, + MethodCallExpression, ModuleDeclaration, NoirFunction, NoirStruct, NoirTrait, + NoirTraitImpl, NoirTypeAlias, Path, PathSegment, Pattern, PrefixExpression, Statement, + StatementKind, TraitImplItem, TraitItem, TypeImpl, UnresolvedGeneric, UnresolvedGenerics, + UnresolvedTraitConstraint, UnresolvedType, UnresolvedTypeData, UnresolvedTypeExpression, + UseTree, UseTreeKind, }, parser::{Item, ItemKind, ParsedSubModule, ParserError}, ParsedModule, @@ -217,7 +218,10 @@ fn empty_statement(statement: &mut Statement) { StatementKind::For(for_loop_statement) => empty_for_loop_statement(for_loop_statement), StatementKind::Comptime(statement) => empty_statement(statement), StatementKind::Semi(expression) => empty_expression(expression), - StatementKind::Break | StatementKind::Continue | StatementKind::Error => (), + StatementKind::Break + | StatementKind::Continue + | StatementKind::Interned(_) + | StatementKind::Error => (), } } @@ -267,12 +271,18 @@ fn empty_expression(expression: &mut Expression) { ExpressionKind::Comptime(block_expression, _span) => { empty_block_expression(block_expression); } - ExpressionKind::Quote(..) | ExpressionKind::Resolved(_) | ExpressionKind::Error => (), + ExpressionKind::Unsafe(block_expression, _span) => { + empty_block_expression(block_expression); + } ExpressionKind::AsTraitPath(path) => { empty_unresolved_type(&mut path.typ); empty_path(&mut path.trait_path); empty_ident(&mut path.impl_item); } + ExpressionKind::Quote(..) + | ExpressionKind::Resolved(_) + | ExpressionKind::Interned(_) + | ExpressionKind::Error => (), } } @@ -294,6 +304,14 @@ fn empty_unresolved_types(unresolved_types: &mut [UnresolvedType]) { } } +fn empty_type_args(generics: &mut GenericTypeArgs) { + empty_unresolved_types(&mut generics.ordered_args); + for (name, typ) in &mut generics.named_args { + empty_ident(name); + empty_unresolved_type(typ); + } +} + fn empty_unresolved_type(unresolved_type: &mut UnresolvedType) { unresolved_type.span = Default::default(); @@ -315,17 +333,17 @@ fn empty_unresolved_type(unresolved_type: &mut UnresolvedType) { } UnresolvedTypeData::Named(path, unresolved_types, _) => { empty_path(path); - empty_unresolved_types(unresolved_types); + empty_type_args(unresolved_types); } UnresolvedTypeData::TraitAsType(path, unresolved_types) => { empty_path(path); - empty_unresolved_types(unresolved_types); + empty_type_args(unresolved_types); } UnresolvedTypeData::MutableReference(unresolved_type) => { empty_unresolved_type(unresolved_type) } UnresolvedTypeData::Tuple(unresolved_types) => empty_unresolved_types(unresolved_types), - UnresolvedTypeData::Function(args, ret, _env) => { + UnresolvedTypeData::Function(args, ret, _env, _) => { empty_unresolved_types(args); empty_unresolved_type(ret); } @@ -341,6 +359,7 @@ fn empty_unresolved_type(unresolved_type: &mut UnresolvedType) { | UnresolvedTypeData::Unit | UnresolvedTypeData::Quoted(_) | UnresolvedTypeData::Resolved(_) + | UnresolvedTypeData::Interned(_) | UnresolvedTypeData::Unspecified | UnresolvedTypeData::Error => (), } @@ -519,6 +538,7 @@ fn empty_lvalue(lvalue: &mut LValue) { empty_expression(index); } LValue::Dereference(lvalue, _) => empty_lvalue(lvalue), + LValue::Interned(..) => (), } } @@ -540,5 +560,10 @@ fn empty_unresolved_type_expression(unresolved_type_expression: &mut UnresolvedT empty_unresolved_type_expression(rhs); } UnresolvedTypeExpression::Constant(_, _) => (), + UnresolvedTypeExpression::AsTraitPath(path) => { + empty_unresolved_type(&mut path.typ); + empty_path(&mut path.trait_path); + empty_ident(&mut path.impl_item); + } } } diff --git a/noir/noir-repo/compiler/fm/Cargo.toml b/noir/noir-repo/compiler/fm/Cargo.toml index b48f445be36..c367333a6f4 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 b94f997b7b9..68fc4d1c920 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 a9949f5093a..6b200e79b89 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] @@ -26,3 +29,7 @@ rust-embed.workspace = true tracing.workspace = true aztec_macros = { path = "../../aztec_macros" } + +[features] +bn254 = ["noirc_frontend/bn254", "noirc_evaluator/bn254"] +bls12_381 = ["noirc_frontend/bls12_381", "noirc_evaluator/bls12_381"] 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 87181b285de..e2692349baa 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 72c95823553..f95c9de7c2c 100644 --- a/noir/noir-repo/compiler/noirc_driver/src/lib.rs +++ b/noir/noir-repo/compiler/noirc_driver/src/lib.rs @@ -123,6 +123,24 @@ pub struct CompileOptions { /// Temporary flag to enable the experimental arithmetic generics feature #[arg(long, hide = true)] pub arithmetic_generics: bool, + + /// Flag to turn off the compiler check for under constrained values. + /// Warning: This can improve compilation speed but can also lead to correctness errors. + /// This check should always be run on production code. + #[arg(long)] + pub skip_underconstrained_check: bool, +} + +#[derive(Clone, Debug, Default)] +pub struct CheckOptions { + pub compile_options: CompileOptions, + pub error_on_unused_imports: bool, +} + +impl CheckOptions { + pub fn new(compile_options: &CompileOptions, error_on_unused_imports: bool) -> Self { + Self { compile_options: compile_options.clone(), error_on_unused_imports } + } } pub fn parse_expression_width(input: &str) -> Result { @@ -272,13 +290,12 @@ pub fn add_dep( pub fn check_crate( context: &mut Context, crate_id: CrateId, - options: &CompileOptions, + check_options: &CheckOptions, ) -> CompilationResult<()> { - let macros: &[&dyn MacroProcessor] = if options.disable_macros { - &[] - } else { - &[&aztec_macros::AztecMacro as &dyn MacroProcessor] - }; + let options = &check_options.compile_options; + + let macros: &[&dyn MacroProcessor] = + if options.disable_macros { &[] } else { &[&aztec_macros::AztecMacro] }; let mut errors = vec![]; let diagnostics = CrateDefMap::collect_defs( @@ -286,6 +303,7 @@ pub fn check_crate( context, options.debug_comptime_in_file.as_deref(), options.arithmetic_generics, + check_options.error_on_unused_imports, macros, ); errors.extend(diagnostics.into_iter().map(|(error, file_id)| { @@ -319,7 +337,10 @@ pub fn compile_main( options: &CompileOptions, cached_program: Option, ) -> CompilationResult { - let (_, mut warnings) = check_crate(context, crate_id, options)?; + let error_on_unused_imports = true; + let check_options = CheckOptions::new(options, error_on_unused_imports); + + let (_, mut warnings) = check_crate(context, crate_id, &check_options)?; let main = context.get_main_function(&crate_id).ok_or_else(|| { // TODO(#2155): This error might be a better to exist in Nargo @@ -354,7 +375,9 @@ pub fn compile_contract( crate_id: CrateId, options: &CompileOptions, ) -> CompilationResult { - let (_, warnings) = check_crate(context, crate_id, options)?; + let error_on_unused_imports = true; + let check_options = CheckOptions::new(options, error_on_unused_imports); + let (_, warnings) = check_crate(context, crate_id, &check_options)?; // TODO: We probably want to error if contracts is empty let contracts = context.get_all_contracts(&crate_id); @@ -557,6 +580,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; @@ -576,9 +600,10 @@ pub fn compile_no_check( ExpressionWidth::default() }, emit_ssa: if options.emit_ssa { Some(context.package_build_path.clone()) } else { None }, + skip_underconstrained_check: options.skip_underconstrained_check, }; - 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 +618,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 8e02de0b8b3..88460482928 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 41b1cd0ff58..61b274c605f 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/debug_info.rs b/noir/noir-repo/compiler/noirc_errors/src/debug_info.rs index 1a254175c0a..b480d20fde4 100644 --- a/noir/noir-repo/compiler/noirc_errors/src/debug_info.rs +++ b/noir/noir-repo/compiler/noirc_errors/src/debug_info.rs @@ -1,4 +1,5 @@ use acvm::acir::circuit::brillig::BrilligFunctionId; +use acvm::acir::circuit::BrilligOpcodeLocation; use acvm::acir::circuit::OpcodeLocation; use acvm::compiler::AcirTransformationMap; @@ -98,8 +99,8 @@ pub struct DebugInfo { /// that they should be serialized to/from strings. #[serde_as(as = "BTreeMap")] pub locations: BTreeMap>, - #[serde_as(as = "BTreeMap<_, BTreeMap>")] - pub brillig_locations: BTreeMap>>, + pub brillig_locations: + BTreeMap>>, pub variables: DebugVariables, pub functions: DebugFunctions, pub types: DebugTypes, @@ -116,7 +117,10 @@ pub struct OpCodesCount { impl DebugInfo { pub fn new( locations: BTreeMap>, - brillig_locations: BTreeMap>>, + brillig_locations: BTreeMap< + BrilligFunctionId, + BTreeMap>, + >, variables: DebugVariables, functions: DebugFunctions, types: DebugTypes, diff --git a/noir/noir-repo/compiler/noirc_errors/src/position.rs b/noir/noir-repo/compiler/noirc_errors/src/position.rs index 02b242e8b4d..1792197eab7 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 66c770b5064..3bc7f544170 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] @@ -23,6 +26,11 @@ serde_json.workspace = true serde_with = "3.2.0" tracing.workspace = true chrono = "0.4.37" +cfg-if.workspace = true [dev-dependencies] -proptest.workspace = true \ No newline at end of file +proptest.workspace = true + +[features] +bn254 = ["noirc_frontend/bn254"] +bls12_381= [] 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 ee61a9d13d3..d6364cefc9a 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_block.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs index 76ab613ed1f..a9801c8904e 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs @@ -725,7 +725,7 @@ impl<'block> BrilligBlock<'block> { 1, ); } - Instruction::EnableSideEffects { .. } => { + Instruction::EnableSideEffectsIf { .. } => { todo!("enable_side_effects not supported by brillig") } Instruction::IfElse { .. } => { 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 dff4da56c1e..c17088a5d8c 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(), } } @@ -62,7 +63,7 @@ pub(crate) fn directive_invert() -> GeneratedBrillig { /// /// This is equivalent to the Noir (pseudo)code /// -/// ```ignore +/// ```text /// fn quotient(a: T, b: T) -> (T,T) { /// (a/b, a-a/b*b) /// } @@ -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 2d0bdb5955c..53964f64170 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/brillig/brillig_ir/codegen_memory.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_memory.rs index d20f736ee6d..ec3b080895b 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_memory.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_memory.rs @@ -263,20 +263,14 @@ impl BrilligContext { let index_at_end_of_array = self.allocate_register(); let end_value_register = self.allocate_register(); - self.codegen_loop(iteration_count, |ctx, iterator_register| { - // Load both values - ctx.codegen_array_get(vector.pointer, iterator_register, start_value_register); + self.mov_instruction(index_at_end_of_array, vector.size); + self.codegen_loop(iteration_count, |ctx, iterator_register| { // The index at the end of array is size - 1 - iterator - ctx.mov_instruction(index_at_end_of_array, vector.size); ctx.codegen_usize_op_in_place(index_at_end_of_array, BrilligBinaryOp::Sub, 1); - ctx.memory_op_instruction( - index_at_end_of_array, - iterator_register.address, - index_at_end_of_array, - BrilligBinaryOp::Sub, - ); + // Load both values + ctx.codegen_array_get(vector.pointer, iterator_register, start_value_register); ctx.codegen_array_get( vector.pointer, SingleAddrVariable::new_usize(index_at_end_of_array), diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs index b632958f37f..2d138c13f7f 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs @@ -64,6 +64,9 @@ pub struct SsaEvaluatorOptions { /// Dump the unoptimized SSA to the supplied path if it exists pub emit_ssa: Option, + + /// Skip the check for under constrained values + pub skip_underconstrained_check: bool, } pub(crate) struct ArtifactsAndWarnings(Artifacts, Vec); @@ -111,13 +114,19 @@ pub(crate) fn optimize_into_acir( .run_pass(Ssa::inline_functions_with_no_predicates, "After Inlining:") .run_pass(Ssa::remove_if_else, "After Remove IfElse:") .run_pass(Ssa::fold_constants, "After Constant Folding:") - .run_pass(Ssa::remove_enable_side_effects, "After EnableSideEffects removal:") + .run_pass(Ssa::remove_enable_side_effects, "After EnableSideEffectsIf removal:") .run_pass(Ssa::fold_constants_using_constraints, "After Constraint Folding:") .run_pass(Ssa::dead_instruction_elimination, "After Dead Instruction Elimination:") .run_pass(Ssa::array_set_optimization, "After Array Set Optimizations:") .finish(); - let ssa_level_warnings = ssa.check_for_underconstrained_values(); + let ssa_level_warnings = if options.skip_underconstrained_check { + vec![] + } else { + time("After Check for Underconstrained Values", options.print_codegen_timings, || { + ssa.check_for_underconstrained_values() + }) + }; let brillig = time("SSA to Brillig", options.print_codegen_timings, || { ssa.to_brillig(options.enable_brillig_logging) }); @@ -151,6 +160,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 +177,7 @@ impl SsaProgramArtifact { main_input_witnesses: Vec::default(), main_return_witnesses: Vec::default(), names: Vec::default(), + brillig_names: Vec::default(), error_types, } } @@ -202,8 +213,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 +249,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 b2a73106468..317cf43669c 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,9 +8,8 @@ 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}; use acvm::{ acir::AcirField, @@ -492,7 +491,7 @@ impl AcirContext { self.sub_var(sum, mul) } else { // Implement OR in terms of AND - // (NOT a) NAND (NOT b) => a OR b + // (NOT a) AND (NOT b) => NOT (a OR b) let a = self.not_var(lhs, typ.clone())?; let b = self.not_var(rhs, typ.clone())?; let a_and_b = self.and_var(a, b, typ.clone())?; @@ -1428,8 +1427,19 @@ impl AcirContext { } _ => (vec![], vec![]), }; - // Allow constant inputs only for MSM for now - let allow_constant_inputs = name.eq(&BlackBoxFunc::MultiScalarMul); + // Allow constant inputs for most blackbox + // EmbeddedCurveAdd needs to be fixed first in bb + // Poseidon2Permutation requires witness input + let allow_constant_inputs = matches!( + name, + BlackBoxFunc::MultiScalarMul + | BlackBoxFunc::Keccakf1600 + | BlackBoxFunc::Blake2s + | BlackBoxFunc::Blake3 + | BlackBoxFunc::AND + | BlackBoxFunc::XOR + | BlackBoxFunc::AES128Encrypt + ); // Convert `AcirVar` to `FunctionInput` let inputs = self.prepare_inputs_for_black_box_func_call(inputs, allow_constant_inputs)?; // Call Black box with `FunctionInput` @@ -1921,6 +1931,9 @@ impl AcirContext { } Some(optional_value) => { let mut values = Vec::new(); + if let AcirValue::DynamicArray(_) = optional_value { + unreachable!("Dynamic array should already be initialized"); + } self.initialize_array_inner(&mut values, optional_value)?; values } @@ -1950,8 +1963,16 @@ impl AcirContext { self.initialize_array_inner(witnesses, value)?; } } - AcirValue::DynamicArray(_) => { - unreachable!("Dynamic array should already be initialized"); + AcirValue::DynamicArray(AcirDynamicArray { block_id, len, .. }) => { + let dynamic_array_values = try_vecmap(0..len, |i| { + let index_var = self.add_constant(i); + + let read = self.read_from_memory(block_id, &index_var)?; + Ok::(AcirValue::Var(read, AcirType::field())) + })?; + for value in dynamic_array_values { + self.initialize_array_inner(witnesses, value)?; + } } } Ok(()) @@ -1959,7 +1980,7 @@ impl AcirContext { pub(crate) fn call_acir_function( &mut self, - id: u32, + id: AcirFunctionId, inputs: Vec, output_count: usize, predicate: AcirVar, @@ -2005,7 +2026,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); } @@ -2105,7 +2126,11 @@ fn execute_brillig( } // Instantiate a Brillig VM given the solved input registers and memory, along with the Brillig bytecode. - let mut vm = VM::new(calldata, code, Vec::new(), &blackbox_solver::StubbedBlackBoxSolver); + // + // We pass a stubbed solver here as a concrete solver implies a field choice which conflicts with this function + // being generic. + let solver = acvm::blackbox_solver::StubbedBlackBoxSolver; + let mut vm = VM::new(calldata, code, Vec::new(), &solver); // Run the Brillig VM on these inputs, bytecode, etc! let vm_status = vm.process_opcodes(); diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs index 661371c5de6..004979ce84e 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs @@ -1,6 +1,6 @@ //! `GeneratedAcir` is constructed as part of the `acir_gen` pass to accumulate all of the ACIR //! program as it is being converted from SSA form. -use std::collections::BTreeMap; +use std::{collections::BTreeMap, u32}; use crate::{ brillig::{brillig_gen::brillig_directive, brillig_ir::artifact::GeneratedBrillig}, @@ -11,7 +11,7 @@ use acvm::acir::{ circuit::{ brillig::{BrilligFunctionId, BrilligInputs, BrilligOutputs}, opcodes::{BlackBoxFuncCall, FunctionInput, Opcode as AcirOpcode}, - AssertionPayload, OpcodeLocation, + AssertionPayload, BrilligOpcodeLocation, OpcodeLocation, }, native_types::Witness, BlackBoxFunc, @@ -53,7 +53,7 @@ pub(crate) struct GeneratedAcir { /// Brillig function id -> Opcodes locations map /// This map is used to prevent redundant locations being stored for the same Brillig entry point. - pub(crate) brillig_locations: BTreeMap, + pub(crate) brillig_locations: BTreeMap, /// Source code location of the current instruction being processed /// None if we do not know the location @@ -77,6 +77,8 @@ pub(crate) struct GeneratedAcir { /// Correspondence between an opcode index (in opcodes) and the source code call stack which generated it pub(crate) type OpcodeToLocationsMap = BTreeMap; +pub(crate) type BrilligOpcodeToLocationsMap = BTreeMap; + #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] pub(crate) enum BrilligStdlibFunc { Inverse, @@ -590,6 +592,7 @@ impl GeneratedAcir { return; } + // TODO(https://github.com/noir-lang/noir/issues/5792) for (brillig_index, message) in generated_brillig.assert_messages.iter() { self.assertion_payloads.insert( OpcodeLocation::Brillig { @@ -605,13 +608,10 @@ impl GeneratedAcir { } for (brillig_index, call_stack) in generated_brillig.locations.iter() { - self.brillig_locations.entry(brillig_function_index).or_default().insert( - OpcodeLocation::Brillig { - acir_index: self.opcodes.len() - 1, - brillig_index: *brillig_index, - }, - call_stack.clone(), - ); + self.brillig_locations + .entry(brillig_function_index) + .or_default() + .insert(BrilligOpcodeLocation(*brillig_index), call_stack.clone()); } } @@ -625,6 +625,7 @@ impl GeneratedAcir { OpcodeLocation::Acir(index) => index, _ => panic!("should not have brillig index"), }; + match &mut self.opcodes[acir_index] { AcirOpcode::BrilligCall { id, .. } => *id = brillig_function_index, _ => panic!("expected brillig call opcode"), 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 22b13aeae79..0a81990ab10 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)) } } @@ -703,7 +706,7 @@ impl<'a> Context<'a> { self.convert_ssa_truncate(*value, *bit_size, *max_bit_size, dfg)?; self.define_result_var(dfg, instruction_id, result_acir_var); } - Instruction::EnableSideEffects { condition } => { + Instruction::EnableSideEffectsIf { condition } => { let acir_var = self.convert_numeric_value(*condition, dfg)?; self.current_side_effects_enabled_var = acir_var; } @@ -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()); @@ -1160,11 +1165,15 @@ impl<'a> Context<'a> { let index_var = self.convert_numeric_value(index, dfg)?; let index_var = self.get_flattened_index(&array_typ, array_id, index_var, dfg)?; - // predicate_index = index*predicate + (1-predicate)*offset - let offset = self.acir_context.add_constant(offset); - let sub = self.acir_context.sub_var(index_var, offset)?; - let pred = self.acir_context.mul_var(sub, self.current_side_effects_enabled_var)?; - let predicate_index = self.acir_context.add_var(pred, offset)?; + let predicate_index = if dfg.is_safe_index(index, array_id) { + index_var + } else { + // index*predicate + (1-predicate)*offset + let offset = self.acir_context.add_constant(offset); + let sub = self.acir_context.sub_var(index_var, offset)?; + let pred = self.acir_context.mul_var(sub, self.current_side_effects_enabled_var)?; + self.acir_context.add_var(pred, offset)? + }; let new_value = if let Some(store) = store_value { let store_value = self.convert_value(store, dfg); @@ -1645,7 +1654,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 +2871,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 +2979,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 +3028,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 +3084,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 +3094,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 +3184,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 +3195,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 +3220,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 +3228,7 @@ mod test { fn check_call_opcode( opcode: &Opcode, - expected_id: u32, + expected_id: AcirFunctionId, expected_inputs: Vec, expected_outputs: Vec, ) { @@ -3265,7 +3308,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 +3372,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 +3446,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 +3534,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/checks/check_for_underconstrained_values.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs index 24fcb8f61df..79db4e645ee 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs @@ -255,7 +255,7 @@ impl Context { } Instruction::Allocate { .. } | Instruction::DecrementRc { .. } - | Instruction::EnableSideEffects { .. } + | Instruction::EnableSideEffectsIf { .. } | Instruction::IncrementRc { .. } | Instruction::RangeCheck { .. } => {} } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs index 49184bf4c63..8cc42241d92 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs @@ -337,7 +337,7 @@ impl FunctionBuilder { /// Insert an enable_side_effects_if instruction. These are normally only automatically /// inserted during the flattening pass when branching is removed. pub(crate) fn insert_enable_side_effects_if(&mut self, condition: ValueId) { - self.insert_instruction(Instruction::EnableSideEffects { condition }, None); + self.insert_instruction(Instruction::EnableSideEffectsIf { condition }, None); } /// Terminates the current block with the given terminator instruction 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 34d7d595eb9..d79916a9e11 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) => { @@ -471,6 +472,14 @@ impl DataFlowGraph { } } + /// A constant index less than the array length is safe + pub(crate) fn is_safe_index(&self, index: ValueId, array: ValueId) -> bool { + #[allow(clippy::match_like_matches_macro)] + match (self.type_of_value(array), self.get_numeric_constant(index)) { + (Type::Array(_, len), Some(index)) if index.to_u128() < (len as u128) => true, + _ => false, + } + } /// Sets the terminator instruction for the given basic block pub(crate) fn set_block_terminator( &mut self, diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs index 7dcb50762f5..36069f17933 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs @@ -218,13 +218,23 @@ pub(crate) enum Instruction { Store { address: ValueId, value: ValueId }, /// Provides a context for all instructions that follow up until the next - /// `EnableSideEffects` is encountered, for stating a condition that determines whether + /// `EnableSideEffectsIf` is encountered, for stating a condition that determines whether /// such instructions are allowed to have side-effects. /// + /// For example, + /// ```text + /// EnableSideEffectsIf condition0; + /// code0; + /// EnableSideEffectsIf condition1; + /// code1; + /// ``` + /// - `code0` will have side effects iff `condition0` evaluates to `true` + /// - `code1` will have side effects iff `condition1` evaluates to `true` + /// /// This instruction is only emitted after the cfg flattening pass, and is used to annotate /// instruction regions with an condition that corresponds to their position in the CFG's /// if-branching structure. - EnableSideEffects { condition: ValueId }, + EnableSideEffectsIf { condition: ValueId }, /// Retrieve a value from an array at the given index ArrayGet { array: ValueId, index: ValueId }, @@ -249,6 +259,17 @@ pub(crate) enum Instruction { DecrementRc { value: ValueId }, /// Merge two values returned from opposite branches of a conditional into one. + /// + /// ```text + /// if then_condition { + /// then_value + /// } else { // else_condition = !then_condition + /// else_value + /// } + /// ``` + /// + /// Where we save the result of !then_condition so that we have the same + /// ValueId for it each time. IfElse { then_condition: ValueId, then_value: ValueId, @@ -279,7 +300,7 @@ impl Instruction { | Instruction::IncrementRc { .. } | Instruction::DecrementRc { .. } | Instruction::RangeCheck { .. } - | Instruction::EnableSideEffects { .. } => InstructionResultType::None, + | Instruction::EnableSideEffectsIf { .. } => InstructionResultType::None, Instruction::Allocate { .. } | Instruction::Load { .. } | Instruction::ArrayGet { .. } @@ -306,7 +327,7 @@ impl Instruction { match self { // These either have side-effects or interact with memory - EnableSideEffects { .. } + EnableSideEffectsIf { .. } | Allocate | Load { .. } | Store { .. } @@ -362,7 +383,7 @@ impl Instruction { Constrain(..) | Store { .. } - | EnableSideEffects { .. } + | EnableSideEffectsIf { .. } | IncrementRc { .. } | DecrementRc { .. } | RangeCheck { .. } => false, @@ -396,16 +417,12 @@ impl Instruction { true } - // `ArrayGet`s which read from "known good" indices from an array don't need a predicate. Instruction::ArrayGet { array, index } => { - #[allow(clippy::match_like_matches_macro)] - match (dfg.type_of_value(*array), dfg.get_numeric_constant(*index)) { - (Type::Array(_, len), Some(index)) if index.to_u128() < (len as u128) => false, - _ => true, - } + // `ArrayGet`s which read from "known good" indices from an array should not need a predicate. + !dfg.is_safe_index(*index, *array) } - Instruction::EnableSideEffects { .. } | Instruction::ArraySet { .. } => true, + Instruction::EnableSideEffectsIf { .. } | Instruction::ArraySet { .. } => true, Instruction::Call { func, .. } => match dfg[*func] { Value::Function(_) => true, @@ -470,8 +487,8 @@ impl Instruction { Instruction::Store { address, value } => { Instruction::Store { address: f(*address), value: f(*value) } } - Instruction::EnableSideEffects { condition } => { - Instruction::EnableSideEffects { condition: f(*condition) } + Instruction::EnableSideEffectsIf { condition } => { + Instruction::EnableSideEffectsIf { condition: f(*condition) } } Instruction::ArrayGet { array, index } => { Instruction::ArrayGet { array: f(*array), index: f(*index) } @@ -545,7 +562,7 @@ impl Instruction { f(*index); f(*value); } - Instruction::EnableSideEffects { condition } => { + Instruction::EnableSideEffectsIf { condition } => { f(*condition); } Instruction::IncrementRc { value } @@ -608,16 +625,11 @@ impl Instruction { } } Instruction::ArrayGet { array, index } => { - let array = dfg.get_array_constant(*array); - let index = dfg.get_numeric_constant(*index); - if let (Some((array, _)), Some(index)) = (array, index) { - let index = - index.try_to_u32().expect("Expected array index to fit in u32") as usize; - if index < array.len() { - return SimplifiedTo(array[index]); - } + if let Some(index) = dfg.get_numeric_constant(*index) { + try_optimize_array_get_from_previous_set(dfg, *array, index) + } else { + None } - None } Instruction::ArraySet { array, index, value, .. } => { let array = dfg.get_array_constant(*array); @@ -687,11 +699,11 @@ impl Instruction { Instruction::Call { func, arguments } => { simplify_call(*func, arguments, dfg, block, ctrl_typevars, call_stack) } - Instruction::EnableSideEffects { condition } => { + Instruction::EnableSideEffectsIf { condition } => { if let Some(last) = dfg[block].instructions().last().copied() { let last = &mut dfg[last]; - if matches!(last, Instruction::EnableSideEffects { .. }) { - *last = Instruction::EnableSideEffects { condition: *condition }; + if matches!(last, Instruction::EnableSideEffectsIf { .. }) { + *last = Instruction::EnableSideEffectsIf { condition: *condition }; return Remove; } } @@ -721,11 +733,15 @@ impl Instruction { } } + let then_value = dfg.resolve(*then_value); + let else_value = dfg.resolve(*else_value); + if then_value == else_value { + return SimplifiedTo(then_value); + } + if matches!(&typ, Type::Numeric(_)) { let then_condition = *then_condition; - let then_value = *then_value; let else_condition = *else_condition; - let else_value = *else_value; let result = ValueMerger::merge_numeric_values( dfg, @@ -744,6 +760,65 @@ impl Instruction { } } +/// Given a chain of operations like: +/// v1 = array_set [10, 11, 12], index 1, value: 5 +/// v2 = array_set v1, index 2, value: 6 +/// v3 = array_set v2, index 2, value: 7 +/// v4 = array_get v3, index 1 +/// +/// We want to optimize `v4` to `10`. To do this we need to follow the array value +/// through several array sets. For each array set: +/// - If the index is non-constant we fail the optimization since any index may be changed +/// - If the index is constant and is our target index, we conservatively fail the optimization +/// in case the array_set is disabled from a previous `enable_side_effects_if` and the array get +/// was not. +/// - Otherwise, we check the array value of the array set. +/// - If the array value is constant, we use that array. +/// - If the array value is from a previous array-set, we recur. +fn try_optimize_array_get_from_previous_set( + dfg: &DataFlowGraph, + mut array_id: Id, + target_index: FieldElement, +) -> SimplifyResult { + let mut elements = None; + + // Arbitrary number of maximum tries just to prevent this optimization from taking too long. + let max_tries = 5; + for _ in 0..max_tries { + match &dfg[array_id] { + Value::Instruction { instruction, .. } => { + match &dfg[*instruction] { + Instruction::ArraySet { array, index, value, .. } => { + if let Some(constant) = dfg.get_numeric_constant(*index) { + if constant == target_index { + return SimplifyResult::SimplifiedTo(*value); + } + + array_id = *array; // recur + } else { + return SimplifyResult::None; + } + } + _ => return SimplifyResult::None, + } + } + Value::Array { array, typ: _ } => { + elements = Some(array.clone()); + break; + } + _ => return SimplifyResult::None, + } + } + + if let (Some(array), Some(index)) = (elements, target_index.try_to_u64()) { + let index = index as usize; + if index < array.len() { + return SimplifyResult::SimplifiedTo(array[index]); + } + } + SimplifyResult::None +} + pub(crate) type ErrorType = HirType; pub(crate) fn error_selector_from_type(typ: &ErrorType) -> ErrorSelector { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs index ea2523e873e..de7ab6e532d 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs @@ -1,7 +1,10 @@ use fxhash::FxHashMap as HashMap; use std::{collections::VecDeque, rc::Rc}; -use acvm::{acir::AcirField, acir::BlackBoxFunc, BlackBoxResolutionError, FieldElement}; +use acvm::{ + acir::{AcirField, BlackBoxFunc}, + BlackBoxResolutionError, FieldElement, +}; use bn254_blackbox_solver::derive_generators; use iter_extended::vecmap; use num_bigint::BigUint; @@ -20,6 +23,8 @@ use crate::ssa::{ use super::{Binary, BinaryOp, Endian, Instruction, SimplifyResult}; +mod blackbox; + /// Try to simplify this call instruction. If the instruction can be simplified to a known value, /// that value is returned. Otherwise None is returned. /// @@ -468,11 +473,17 @@ fn simplify_black_box_func( arguments: &[ValueId], dfg: &mut DataFlowGraph, ) -> SimplifyResult { + cfg_if::cfg_if! { + if #[cfg(feature = "bn254")] { + let solver = bn254_blackbox_solver::Bn254BlackBoxSolver; + } else { + let solver = acvm::blackbox_solver::StubbedBlackBoxSolver; + } + }; match bb_func { BlackBoxFunc::SHA256 => simplify_hash(dfg, arguments, acvm::blackbox_solver::sha256), BlackBoxFunc::Blake2s => simplify_hash(dfg, arguments, acvm::blackbox_solver::blake2s), BlackBoxFunc::Blake3 => simplify_hash(dfg, arguments, acvm::blackbox_solver::blake3), - BlackBoxFunc::PedersenCommitment | BlackBoxFunc::PedersenHash => SimplifyResult::None, BlackBoxFunc::Keccakf1600 => { if let Some((array_input, _)) = dfg.get_array_constant(arguments[0]) { if array_is_constant(dfg, &array_input) { @@ -503,20 +514,26 @@ fn simplify_black_box_func( BlackBoxFunc::Keccak256 => { unreachable!("Keccak256 should have been replaced by calls to Keccakf1600") } - BlackBoxFunc::Poseidon2Permutation => SimplifyResult::None, //TODO(Guillaume) - BlackBoxFunc::EcdsaSecp256k1 => { - simplify_signature(dfg, arguments, acvm::blackbox_solver::ecdsa_secp256k1_verify) - } - BlackBoxFunc::EcdsaSecp256r1 => { - simplify_signature(dfg, arguments, acvm::blackbox_solver::ecdsa_secp256r1_verify) + BlackBoxFunc::Poseidon2Permutation => { + blackbox::simplify_poseidon2_permutation(dfg, solver, arguments) } + BlackBoxFunc::EcdsaSecp256k1 => blackbox::simplify_signature( + dfg, + arguments, + acvm::blackbox_solver::ecdsa_secp256k1_verify, + ), + BlackBoxFunc::EcdsaSecp256r1 => blackbox::simplify_signature( + dfg, + arguments, + acvm::blackbox_solver::ecdsa_secp256r1_verify, + ), + + BlackBoxFunc::PedersenCommitment + | BlackBoxFunc::PedersenHash + | BlackBoxFunc::MultiScalarMul => SimplifyResult::None, + BlackBoxFunc::EmbeddedCurveAdd => blackbox::simplify_ec_add(dfg, solver, arguments), + BlackBoxFunc::SchnorrVerify => blackbox::simplify_schnorr_verify(dfg, solver, arguments), - BlackBoxFunc::MultiScalarMul - | BlackBoxFunc::SchnorrVerify - | BlackBoxFunc::EmbeddedCurveAdd => { - // Currently unsolvable here as we rely on an implementation in the backend. - SimplifyResult::None - } BlackBoxFunc::BigIntAdd | BlackBoxFunc::BigIntSub | BlackBoxFunc::BigIntMul diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call/blackbox.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call/blackbox.rs new file mode 100644 index 00000000000..706e8891cde --- /dev/null +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call/blackbox.rs @@ -0,0 +1,190 @@ +use std::rc::Rc; + +use acvm::{acir::AcirField, BlackBoxFunctionSolver, BlackBoxResolutionError, FieldElement}; +use iter_extended::vecmap; + +use crate::ssa::ir::{ + dfg::DataFlowGraph, instruction::SimplifyResult, types::Type, value::ValueId, +}; + +use super::{array_is_constant, make_constant_array, to_u8_vec}; + +pub(super) fn simplify_ec_add( + dfg: &mut DataFlowGraph, + solver: impl BlackBoxFunctionSolver, + arguments: &[ValueId], +) -> SimplifyResult { + match ( + dfg.get_numeric_constant(arguments[0]), + dfg.get_numeric_constant(arguments[1]), + dfg.get_numeric_constant(arguments[2]), + dfg.get_numeric_constant(arguments[3]), + dfg.get_numeric_constant(arguments[4]), + dfg.get_numeric_constant(arguments[5]), + ) { + ( + Some(point1_x), + Some(point1_y), + Some(point1_is_infinity), + Some(point2_x), + Some(point2_y), + Some(point2_is_infinity), + ) => { + let Ok((result_x, result_y, result_is_infinity)) = solver.ec_add( + &point1_x, + &point1_y, + &point1_is_infinity, + &point2_x, + &point2_y, + &point2_is_infinity, + ) else { + return SimplifyResult::None; + }; + + let result_x = dfg.make_constant(result_x, Type::field()); + let result_y = dfg.make_constant(result_y, Type::field()); + let result_is_infinity = dfg.make_constant(result_is_infinity, Type::bool()); + + let typ = Type::Array(Rc::new(vec![Type::field()]), 3); + let result_array = + dfg.make_array(im::vector![result_x, result_y, result_is_infinity], typ); + + SimplifyResult::SimplifiedTo(result_array) + } + _ => SimplifyResult::None, + } +} + +pub(super) fn simplify_poseidon2_permutation( + dfg: &mut DataFlowGraph, + solver: impl BlackBoxFunctionSolver, + arguments: &[ValueId], +) -> SimplifyResult { + match (dfg.get_array_constant(arguments[0]), dfg.get_numeric_constant(arguments[1])) { + (Some((state, _)), Some(state_length)) if array_is_constant(dfg, &state) => { + let state: Vec = state + .iter() + .map(|id| { + dfg.get_numeric_constant(*id) + .expect("value id from array should point at constant") + }) + .collect(); + + let Some(state_length) = state_length.try_to_u32() else { + return SimplifyResult::None; + }; + + let Ok(new_state) = solver.poseidon2_permutation(&state, state_length) else { + return SimplifyResult::None; + }; + + let result_array = make_constant_array(dfg, new_state, Type::field()); + + SimplifyResult::SimplifiedTo(result_array) + } + _ => SimplifyResult::None, + } +} + +pub(super) fn simplify_schnorr_verify( + dfg: &mut DataFlowGraph, + solver: impl BlackBoxFunctionSolver, + arguments: &[ValueId], +) -> SimplifyResult { + match ( + dfg.get_numeric_constant(arguments[0]), + dfg.get_numeric_constant(arguments[1]), + dfg.get_array_constant(arguments[2]), + dfg.get_array_constant(arguments[3]), + ) { + (Some(public_key_x), Some(public_key_y), Some((signature, _)), Some((message, _))) + if array_is_constant(dfg, &signature) && array_is_constant(dfg, &message) => + { + let signature = to_u8_vec(dfg, signature); + let signature: [u8; 64] = + signature.try_into().expect("Compiler should produce correctly sized signature"); + + let message = to_u8_vec(dfg, message); + + let Ok(valid_signature) = + solver.schnorr_verify(&public_key_x, &public_key_y, &signature, &message) + else { + return SimplifyResult::None; + }; + + let valid_signature = dfg.make_constant(valid_signature.into(), Type::bool()); + SimplifyResult::SimplifiedTo(valid_signature) + } + _ => SimplifyResult::None, + } +} + +pub(super) fn simplify_hash( + dfg: &mut DataFlowGraph, + arguments: &[ValueId], + hash_function: fn(&[u8]) -> Result<[u8; 32], BlackBoxResolutionError>, +) -> SimplifyResult { + match dfg.get_array_constant(arguments[0]) { + Some((input, _)) if array_is_constant(dfg, &input) => { + let input_bytes: Vec = to_u8_vec(dfg, input); + + let hash = hash_function(&input_bytes) + .expect("Rust solvable black box function should not fail"); + + let hash_values = vecmap(hash, |byte| FieldElement::from_be_bytes_reduce(&[byte])); + + let result_array = make_constant_array(dfg, hash_values, Type::unsigned(8)); + SimplifyResult::SimplifiedTo(result_array) + } + _ => SimplifyResult::None, + } +} + +type ECDSASignatureVerifier = fn( + hashed_msg: &[u8], + public_key_x: &[u8; 32], + public_key_y: &[u8; 32], + signature: &[u8; 64], +) -> Result; + +pub(super) fn simplify_signature( + dfg: &mut DataFlowGraph, + arguments: &[ValueId], + signature_verifier: ECDSASignatureVerifier, +) -> SimplifyResult { + match ( + dfg.get_array_constant(arguments[0]), + dfg.get_array_constant(arguments[1]), + dfg.get_array_constant(arguments[2]), + dfg.get_array_constant(arguments[3]), + ) { + ( + Some((public_key_x, _)), + Some((public_key_y, _)), + Some((signature, _)), + Some((hashed_message, _)), + ) if array_is_constant(dfg, &public_key_x) + && array_is_constant(dfg, &public_key_y) + && array_is_constant(dfg, &signature) + && array_is_constant(dfg, &hashed_message) => + { + let public_key_x: [u8; 32] = to_u8_vec(dfg, public_key_x) + .try_into() + .expect("ECDSA public key fields are 32 bytes"); + let public_key_y: [u8; 32] = to_u8_vec(dfg, public_key_y) + .try_into() + .expect("ECDSA public key fields are 32 bytes"); + let signature: [u8; 64] = + to_u8_vec(dfg, signature).try_into().expect("ECDSA signatures are 64 bytes"); + let hashed_message: Vec = to_u8_vec(dfg, hashed_message); + + let valid_signature = + signature_verifier(&hashed_message, &public_key_x, &public_key_y, &signature) + .expect("Rust solvable black box function should not fail"); + + let valid_signature = dfg.make_constant(valid_signature.into(), Type::bool()); + SimplifyResult::SimplifiedTo(valid_signature) + } + _ => 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 1c9a31f0c99..f1265553b83 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/ir/printer.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs index 656bd26620e..e8c9d01988e 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs @@ -176,7 +176,7 @@ fn display_instruction_inner( Instruction::Store { address, value } => { writeln!(f, "store {} at {}", show(*value), show(*address)) } - Instruction::EnableSideEffects { condition } => { + Instruction::EnableSideEffectsIf { condition } => { writeln!(f, "enable_side_effects {}", show(*condition)) } Instruction::ArrayGet { array, index } => { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs index 160105d27e6..c8f6d201d86 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs @@ -159,9 +159,9 @@ impl Context { *side_effects_enabled_var, ); - // If we just inserted an `Instruction::EnableSideEffects`, we need to update `side_effects_enabled_var` + // If we just inserted an `Instruction::EnableSideEffectsIf`, we need to update `side_effects_enabled_var` // so that we use the correct set of constrained values in future. - if let Instruction::EnableSideEffects { condition } = instruction { + if let Instruction::EnableSideEffectsIf { condition } = instruction { *side_effects_enabled_var = condition; }; } 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 24519d530ee..b9804062118 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::EnableSideEffectsIf { 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/flatten_cfg.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs index 288e41cb994..72ed02b00a8 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs @@ -11,7 +11,7 @@ //! elimination (DIE) pass. //! //! Though CFG information is lost during this pass, some key information is retained in the form -//! of `EnableSideEffect` instructions. Each time the flattening pass enters and exits a branch of +//! of `EnableSideEffectsIf` instructions. Each time the flattening pass enters and exits a branch of //! a jmpif, an instruction is inserted to capture a condition that is analogous to the activeness //! of the program point. For example: //! @@ -573,7 +573,7 @@ impl<'f> Context<'f> { } /// Checks the branch condition on the top of the stack and uses it to build and insert an - /// `EnableSideEffects` instruction into the entry block. + /// `EnableSideEffectsIf` instruction into the entry block. /// /// If the stack is empty, a "true" u1 constant is taken to be the active condition. This is /// necessary for re-enabling side-effects when re-emerging to a branch depth of 0. @@ -584,7 +584,7 @@ impl<'f> Context<'f> { self.inserter.function.dfg.make_constant(FieldElement::one(), Type::unsigned(1)) } }; - let enable_side_effects = Instruction::EnableSideEffects { condition }; + let enable_side_effects = Instruction::EnableSideEffectsIf { condition }; let call_stack = self.inserter.function.dfg.get_value_call_stack(condition); self.insert_instruction_with_typevars(enable_side_effects, None, call_stack); } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/value_merger.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/value_merger.rs index 90e24a1d5e3..75ee57dd4fa 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/value_merger.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/value_merger.rs @@ -58,6 +58,13 @@ impl<'a> ValueMerger<'a> { then_value: ValueId, else_value: ValueId, ) -> ValueId { + let then_value = self.dfg.resolve(then_value); + let else_value = self.dfg.resolve(else_value); + + if then_value == else_value { + return then_value; + } + match self.dfg.type_of_value(then_value) { Type::Numeric(_) => Self::merge_numeric_values( self.dfg, @@ -374,7 +381,7 @@ impl<'a> ValueMerger<'a> { for (index, element_type, condition) in changed_indices { let typevars = Some(vec![element_type.clone()]); - let instruction = Instruction::EnableSideEffects { condition }; + let instruction = Instruction::EnableSideEffectsIf { condition }; self.insert_instruction(instruction); let mut get_element = |array, typevars| { @@ -398,7 +405,7 @@ impl<'a> ValueMerger<'a> { array = self.insert_array_set(array, index, value, Some(condition)).first(); } - let instruction = Instruction::EnableSideEffects { condition: current_condition }; + let instruction = Instruction::EnableSideEffectsIf { condition: current_condition }; self.insert_instruction(instruction); Some(array) } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/inlining.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/inlining.rs index d78399a3e6b..1ff593a1531 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/inlining.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/inlining.rs @@ -502,7 +502,7 @@ impl<'function> PerFunctionContext<'function> { } None => self.push_instruction(*id), }, - Instruction::EnableSideEffects { condition } => { + Instruction::EnableSideEffectsIf { condition } => { side_effects_enabled = Some(self.translate_value(*condition)); self.push_instruction(*id); } 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 5b1139e5b9c..e5a25dcfef1 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/opt/remove_enable_side_effects.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs index 224060e131f..a56786b2603 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs @@ -1,13 +1,13 @@ -//! The goal of the "remove enable side effects" optimization pass is to delay any [Instruction::EnableSideEffects] +//! The goal of the "remove enable side effects" optimization pass is to delay any [Instruction::EnableSideEffectsIf] //! instructions such that they cover the minimum number of instructions possible. //! //! The pass works as follows: -//! - Insert instructions until an [Instruction::EnableSideEffects] is encountered, save this [InstructionId]. +//! - Insert instructions until an [Instruction::EnableSideEffectsIf] is encountered, save this [InstructionId]. //! - Continue inserting instructions until either -//! - Another [Instruction::EnableSideEffects] is encountered, if so then drop the previous [InstructionId] in favour +//! - Another [Instruction::EnableSideEffectsIf] is encountered, if so then drop the previous [InstructionId] in favour //! of this one. -//! - An [Instruction] with side-effects is encountered, if so then insert the currently saved [Instruction::EnableSideEffects] -//! before the [Instruction]. Continue inserting instructions until the next [Instruction::EnableSideEffects] is encountered. +//! - An [Instruction] with side-effects is encountered, if so then insert the currently saved [Instruction::EnableSideEffectsIf] +//! before the [Instruction]. Continue inserting instructions until the next [Instruction::EnableSideEffectsIf] is encountered. use std::collections::HashSet; use acvm::{acir::AcirField, FieldElement}; @@ -70,10 +70,10 @@ impl Context { for instruction_id in instructions { let instruction = &function.dfg[instruction_id]; - // If we run into another `Instruction::EnableSideEffects` before encountering any + // If we run into another `Instruction::EnableSideEffectsIf` before encountering any // instructions with side effects then we can drop the instruction we're holding and - // continue with the new `Instruction::EnableSideEffects`. - if let Instruction::EnableSideEffects { condition } = instruction { + // continue with the new `Instruction::EnableSideEffectsIf`. + if let Instruction::EnableSideEffectsIf { condition } = instruction { // If this instruction isn't changing the currently active condition then we can ignore it. if active_condition == *condition { continue; @@ -98,7 +98,7 @@ impl Context { } // If we hit an instruction which is affected by the side effects var then we must insert the - // `Instruction::EnableSideEffects` before we insert this new instruction. + // `Instruction::EnableSideEffectsIf` before we insert this new instruction. if Self::responds_to_side_effects_var(&function.dfg, instruction) { if let Some(enable_side_effects_instruction_id) = last_side_effects_enabled_instruction.take() @@ -140,7 +140,7 @@ impl Context { | IncrementRc { .. } | DecrementRc { .. } => false, - EnableSideEffects { .. } + EnableSideEffectsIf { .. } | ArrayGet { .. } | ArraySet { .. } | Allocate diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs index b1ca5fa25a0..cc02faeb3df 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs @@ -128,7 +128,7 @@ impl Context { self.slice_sizes.insert(result, old_capacity); function.dfg[block].instructions_mut().push(instruction); } - Instruction::EnableSideEffects { condition } => { + Instruction::EnableSideEffectsIf { condition } => { current_conditional = *condition; function.dfg[block].instructions_mut().push(instruction); } 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 e16f6697c70..13e5c2445ad 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 1373d1bc46e..6b19aff2674 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/Cargo.toml b/noir/noir-repo/compiler/noirc_frontend/Cargo.toml index 7ef8870eaa8..c0f6c8965fb 100644 --- a/noir/noir-repo/compiler/noirc_frontend/Cargo.toml +++ b/noir/noir-repo/compiler/noirc_frontend/Cargo.toml @@ -27,7 +27,7 @@ num-traits.workspace = true rustc-hash = "1.1.0" small-ord-set = "0.1.3" regex = "1.9.1" -cfg-if = "1.0.0" +cfg-if.workspace = true tracing.workspace = true petgraph = "0.6" rangemap = "1.4.0" 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 aab995c49a1..f242180134d 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs @@ -7,8 +7,8 @@ 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::node_interner::{ExprId, InternedExpressionKind, QuotedTypeId}; +use crate::token::{Attributes, FunctionAttribute, Token, Tokens}; use crate::{Kind, Type}; use acvm::{acir::AcirField, FieldElement}; use iter_extended::vecmap; @@ -36,12 +36,18 @@ 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 // code. It is used to translate function values back into the AST while // guaranteeing they have the same instantiated type and definition id without resolving again. Resolved(ExprId), + + // This is an interned ExpressionKind during comptime code. + // The actual ExpressionKind can be retrieved with a NodeInterner. + Interned(InternedExpressionKind), + Error, } @@ -68,9 +74,7 @@ impl UnresolvedGeneric { pub fn span(&self) -> Span { match self { UnresolvedGeneric::Variable(ident) => ident.0.span(), - UnresolvedGeneric::Numeric { ident, typ } => { - ident.0.span().merge(typ.span.unwrap_or_default()) - } + UnresolvedGeneric::Numeric { ident, typ } => ident.0.span().merge(typ.span), UnresolvedGeneric::Resolved(_, span) => *span, } } @@ -477,6 +481,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,8 +605,10 @@ 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"), + Interned(_) => write!(f, "?Interned"), Unquote(expr) => write!(f, "$({expr})"), Quote(tokens) => { let tokens = vecmap(&tokens.0, ToString::to_string); @@ -775,7 +795,7 @@ impl FunctionDefinition { visibility: Visibility::Private, pattern: Pattern::Identifier(ident.clone()), typ: unresolved_type.clone(), - span: ident.span().merge(unresolved_type.span.unwrap()), + span: ident.span().merge(unresolved_type.span), }) .collect(); @@ -832,7 +852,7 @@ impl FunctionReturnType { pub fn get_type(&self) -> Cow { match self { FunctionReturnType::Default(span) => { - Cow::Owned(UnresolvedType { typ: UnresolvedTypeData::Unit, span: Some(*span) }) + Cow::Owned(UnresolvedType { typ: UnresolvedTypeData::Unit, span: *span }) } FunctionReturnType::Ty(typ) => Cow::Borrowed(typ), } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/function.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/function.rs index 8acc068d86a..4f55e4c2c76 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/function.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/function.rs @@ -61,9 +61,7 @@ impl NoirFunction { pub fn return_type(&self) -> UnresolvedType { match &self.def.return_type { - FunctionReturnType::Default(_) => { - UnresolvedType::without_span(UnresolvedTypeData::Unit) - } + FunctionReturnType::Default(span) => UnresolvedTypeData::Unit.with_span(*span), FunctionReturnType::Ty(ty) => ty.clone(), } } 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 8e27f0bdda9..b10e58aac0c 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/mod.rs @@ -22,7 +22,7 @@ pub use traits::*; pub use type_alias::*; use crate::{ - node_interner::QuotedTypeId, + node_interner::{InternedUnresolvedTypeData, QuotedTypeId}, parser::{ParserError, ParserErrorReason}, token::IntType, BinaryTypeOperator, @@ -112,10 +112,10 @@ pub enum UnresolvedTypeData { Parenthesized(Box), /// A Named UnresolvedType can be a struct type or a type variable - Named(Path, Vec, /*is_synthesized*/ bool), + Named(Path, GenericTypeArgs, /*is_synthesized*/ bool), /// A Trait as return type or parameter of function, including its generics - TraitAsType(Path, Vec), + TraitAsType(Path, GenericTypeArgs), /// &mut T MutableReference(Box), @@ -127,6 +127,7 @@ pub enum UnresolvedTypeData { /*args:*/ Vec, /*ret:*/ Box, /*env:*/ Box, + /*unconstrained:*/ bool, ), /// The type of quoted code for metaprogramming @@ -140,6 +141,10 @@ pub enum UnresolvedTypeData { /// as a result of being spliced into a macro's token stream input. Resolved(QuotedTypeId), + // This is an interned UnresolvedTypeData during comptime code. + // The actual UnresolvedTypeData can be retrieved with a NodeInterner. + Interned(InternedUnresolvedTypeData), + Unspecified, // This is for when the user declares a variable without specifying it's type Error, } @@ -147,11 +152,47 @@ pub enum UnresolvedTypeData { #[derive(Debug, PartialEq, Eq, Clone, Hash)] pub struct UnresolvedType { pub typ: UnresolvedTypeData, + pub span: Span, +} + +/// An argument to a generic type or trait. +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +pub enum GenericTypeArg { + /// An ordered argument, e.g. `` + Ordered(UnresolvedType), - // The span is None in the cases where the User omitted a type: - // fn Foo() {} --- return type is UnresolvedType::Unit without a span - // let x = 100; --- type is UnresolvedType::Unspecified without a span - pub span: Option, + /// A named argument, e.g. ``. + /// Used for associated types. + Named(Ident, UnresolvedType), +} + +#[derive(Debug, Default, PartialEq, Eq, Clone, Hash)] +pub struct GenericTypeArgs { + /// Each ordered argument, e.g. `` + pub ordered_args: Vec, + + /// All named arguments, e.g. ``. + /// Used for associated types. + pub named_args: Vec<(Ident, UnresolvedType)>, +} + +impl GenericTypeArgs { + pub fn is_empty(&self) -> bool { + self.ordered_args.is_empty() && self.named_args.is_empty() + } +} + +impl From> for GenericTypeArgs { + fn from(args: Vec) -> Self { + let mut this = GenericTypeArgs::default(); + for arg in args { + match arg { + GenericTypeArg::Ordered(typ) => this.ordered_args.push(typ), + GenericTypeArg::Named(name, typ) => this.named_args.push((name, typ)), + } + } + this + } } /// Type wrapper for a member access @@ -179,11 +220,38 @@ pub enum UnresolvedTypeExpression { Box, Span, ), + AsTraitPath(Box), } impl Recoverable for UnresolvedType { fn error(span: Span) -> Self { - UnresolvedType { typ: UnresolvedTypeData::Error, span: Some(span) } + UnresolvedType { typ: UnresolvedTypeData::Error, span } + } +} + +impl std::fmt::Display for GenericTypeArg { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + GenericTypeArg::Ordered(typ) => typ.fmt(f), + GenericTypeArg::Named(name, typ) => write!(f, "{name} = {typ}"), + } + } +} + +impl std::fmt::Display for GenericTypeArgs { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.is_empty() { + Ok(()) + } else { + let mut args = vecmap(&self.ordered_args, ToString::to_string).join(", "); + + if !self.ordered_args.is_empty() && !self.named_args.is_empty() { + args += ", "; + } + + args += &vecmap(&self.named_args, |(name, typ)| format!("{name} = {typ}")).join(", "); + write!(f, "<{args}>") + } } } @@ -198,22 +266,8 @@ impl std::fmt::Display for UnresolvedTypeData { Signedness::Signed => write!(f, "i{num_bits}"), Signedness::Unsigned => write!(f, "u{num_bits}"), }, - Named(s, args, _) => { - let args = vecmap(args, |arg| ToString::to_string(&arg.typ)); - if args.is_empty() { - write!(f, "{s}") - } else { - write!(f, "{}<{}>", s, args.join(", ")) - } - } - TraitAsType(s, args) => { - let args = vecmap(args, |arg| ToString::to_string(&arg.typ)); - if args.is_empty() { - write!(f, "impl {s}") - } else { - write!(f, "impl {}<{}>", s, args.join(", ")) - } - } + Named(s, args, _) => write!(f, "{s}{args}"), + TraitAsType(s, args) => write!(f, "impl {s}{args}"), Tuple(elements) => { let elements = vecmap(elements, ToString::to_string); write!(f, "({})", elements.join(", ")) @@ -222,7 +276,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 { @@ -243,6 +301,7 @@ impl std::fmt::Display for UnresolvedTypeData { Unspecified => write!(f, "unspecified"), Parenthesized(typ) => write!(f, "({typ})"), Resolved(_) => write!(f, "(resolved type)"), + Interned(_) => write!(f, "?Interned"), AsTraitPath(path) => write!(f, "{path}"), } } @@ -262,6 +321,7 @@ impl std::fmt::Display for UnresolvedTypeExpression { UnresolvedTypeExpression::BinaryOperation(lhs, op, rhs, _) => { write!(f, "({lhs} {op} {rhs})") } + UnresolvedTypeExpression::AsTraitPath(path) => write!(f, "{path}"), } } } @@ -275,14 +335,6 @@ impl UnresolvedType { } } - pub fn without_span(typ: UnresolvedTypeData) -> UnresolvedType { - UnresolvedType { typ, span: None } - } - - pub fn unspecified() -> UnresolvedType { - UnresolvedType { typ: UnresolvedTypeData::Unspecified, span: None } - } - pub(crate) fn is_type_expression(&self) -> bool { matches!(&self.typ, UnresolvedTypeData::Expression(_)) } @@ -304,7 +356,7 @@ impl UnresolvedTypeData { } pub fn with_span(&self, span: Span) -> UnresolvedType { - UnresolvedType { typ: self.clone(), span: Some(span) } + UnresolvedType { typ: self.clone(), span } } } @@ -341,6 +393,9 @@ impl UnresolvedTypeExpression { UnresolvedTypeExpression::Variable(path) => path.span(), UnresolvedTypeExpression::Constant(_, span) => *span, UnresolvedTypeExpression::BinaryOperation(_, _, _, span) => *span, + UnresolvedTypeExpression::AsTraitPath(path) => { + path.trait_path.span.merge(path.impl_item.span()) + } } } @@ -383,6 +438,9 @@ impl UnresolvedTypeExpression { }; Ok(UnresolvedTypeExpression::BinaryOperation(lhs, op, rhs, expr.span)) } + ExpressionKind::AsTraitPath(path) => { + Ok(UnresolvedTypeExpression::AsTraitPath(Box::new(path))) + } _ => Err(expr), } } 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 5d9a97fa6cf..c88fcba749b 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs @@ -7,12 +7,13 @@ use iter_extended::vecmap; use noirc_errors::{Span, Spanned}; use super::{ - BlockExpression, Expression, ExpressionKind, IndexExpression, MemberAccessExpression, - MethodCallExpression, UnresolvedType, + BlockExpression, Expression, ExpressionKind, GenericTypeArgs, IndexExpression, + MemberAccessExpression, MethodCallExpression, UnresolvedType, }; use crate::elaborator::types::SELF_TYPE_NAME; use crate::lexer::token::SpannedToken; -use crate::macros_api::SecondaryAttribute; +use crate::macros_api::{SecondaryAttribute, UnresolvedTypeData}; +use crate::node_interner::{InternedExpressionKind, InternedStatementKind}; use crate::parser::{ParserError, ParserErrorReason}; use crate::token::Token; @@ -45,6 +46,9 @@ pub enum StatementKind { Comptime(Box), // This is an expression with a trailing semi-colon Semi(Expression), + // This is an interned StatementKind during comptime code. + // The actual StatementKind can be retrieved with a NodeInterner. + Interned(InternedStatementKind), // This statement is the result of a recovered parse error. // To avoid issuing multiple errors in later steps, it should // be skipped in any future analysis if possible. @@ -97,10 +101,15 @@ impl StatementKind { // A semicolon on a for loop is optional and does nothing StatementKind::For(_) => self, + // No semicolon needed for a resolved statement + StatementKind::Interned(_) => self, + 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 { @@ -369,6 +378,7 @@ impl UseTree { pub struct AsTraitPath { pub typ: UnresolvedType, pub trait_path: Path, + pub trait_generics: GenericTypeArgs, pub impl_item: Ident, } @@ -531,6 +541,7 @@ pub enum LValue { MemberAccess { object: Box, field_name: Ident, span: Span }, Index { array: Box, index: Expression, span: Span }, Dereference(Box, Span), + Interned(InternedExpressionKind, Span), } #[derive(Debug, PartialEq, Eq, Clone)] @@ -588,7 +599,7 @@ impl Recoverable for Pattern { } impl LValue { - fn as_expression(&self) -> Expression { + pub fn as_expression(&self) -> Expression { let kind = match self { LValue::Ident(ident) => ExpressionKind::Variable(Path::from_ident(ident.clone())), LValue::MemberAccess { object, field_name, span: _ } => { @@ -609,17 +620,53 @@ impl LValue { rhs: lvalue.as_expression(), })) } + LValue::Interned(id, _) => ExpressionKind::Interned(*id), }; let span = self.span(); Expression::new(kind, span) } + pub fn from_expression(expr: Expression) -> LValue { + LValue::from_expression_kind(expr.kind, expr.span) + } + + pub fn from_expression_kind(expr: ExpressionKind, span: Span) -> LValue { + match expr { + ExpressionKind::Variable(path) => LValue::Ident(path.as_ident().unwrap().clone()), + ExpressionKind::MemberAccess(member_access) => LValue::MemberAccess { + object: Box::new(LValue::from_expression(member_access.lhs)), + field_name: member_access.rhs, + span, + }, + ExpressionKind::Index(index) => LValue::Index { + array: Box::new(LValue::from_expression(index.collection)), + index: index.index, + span, + }, + ExpressionKind::Prefix(prefix) => { + if matches!( + prefix.operator, + crate::ast::UnaryOp::Dereference { implicitly_added: false } + ) { + LValue::Dereference(Box::new(LValue::from_expression(prefix.rhs)), span) + } else { + panic!("Called LValue::from_expression with an invalid prefix operator") + } + } + ExpressionKind::Interned(id) => LValue::Interned(id, span), + _ => { + panic!("Called LValue::from_expression with an invalid expression") + } + } + } + pub fn span(&self) -> Span { match self { LValue::Ident(ident) => ident.span(), LValue::MemberAccess { span, .. } | LValue::Index { span, .. } | LValue::Dereference(_, span) => *span, + LValue::Interned(_, span) => *span, } } } @@ -670,7 +717,7 @@ impl ForRange { let let_array = Statement { kind: StatementKind::Let(LetStatement { pattern: Pattern::Identifier(array_ident.clone()), - r#type: UnresolvedType::unspecified(), + r#type: UnresolvedTypeData::Unspecified.with_span(Default::default()), expression: array, comptime: false, attributes: vec![], @@ -716,7 +763,7 @@ impl ForRange { let let_elem = Statement { kind: StatementKind::Let(LetStatement { pattern: Pattern::Identifier(identifier), - r#type: UnresolvedType::unspecified(), + r#type: UnresolvedTypeData::Unspecified.with_span(Default::default()), expression: Expression::new(loop_element, array_span), comptime: false, attributes: vec![], @@ -745,8 +792,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, + } } } } @@ -772,6 +821,7 @@ impl Display for StatementKind { StatementKind::Continue => write!(f, "continue"), StatementKind::Comptime(statement) => write!(f, "comptime {}", statement.kind), StatementKind::Semi(semi) => write!(f, "{semi};"), + StatementKind::Interned(_) => write!(f, "(resolved);"), StatementKind::Error => write!(f, "Error"), } } @@ -804,6 +854,7 @@ impl Display for LValue { } LValue::Index { array, index, span: _ } => write!(f, "{array}[{index}]"), LValue::Dereference(lvalue, _span) => write!(f, "*{lvalue}"), + LValue::Interned(_, _) => write!(f, "?Interned"), } } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/traits.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/traits.rs index f8f8ef667b4..e3221f287d3 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/traits.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/traits.rs @@ -10,6 +10,8 @@ use crate::ast::{ use crate::macros_api::SecondaryAttribute; use crate::node_interner::TraitId; +use super::GenericTypeArgs; + /// AST node for trait definitions: /// `trait name { ... items ... }` #[derive(Clone, Debug)] @@ -62,7 +64,8 @@ pub struct NoirTraitImpl { pub impl_generics: UnresolvedGenerics, pub trait_name: Path, - pub trait_generics: Vec, + + pub trait_generics: GenericTypeArgs, pub object_type: UnresolvedType, @@ -88,7 +91,7 @@ pub struct UnresolvedTraitConstraint { pub struct TraitBound { pub trait_path: Path, pub trait_id: Option, // initially None, gets assigned during DC - pub trait_generics: Vec, + pub trait_generics: GenericTypeArgs, } #[derive(Clone, Debug)] @@ -179,21 +182,13 @@ impl Display for UnresolvedTraitConstraint { impl Display for TraitBound { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let generics = vecmap(&self.trait_generics, |generic| generic.to_string()); - if !generics.is_empty() { - write!(f, "{}<{}>", self.trait_path, generics.join(", ")) - } else { - write!(f, "{}", self.trait_path) - } + write!(f, "{}{}", self.trait_path, self.trait_generics) } } impl Display for NoirTraitImpl { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let generics = vecmap(&self.trait_generics, |generic| generic.to_string()); - let generics = generics.join(", "); - - writeln!(f, "impl {}<{}> for {} {{", self.trait_name, generics, self.object_type)?; + writeln!(f, "impl {}{} for {} {{", self.trait_name, self.trait_generics, self.object_type)?; for item in self.items.iter() { let item = item.to_string(); 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 598ffed1433..fe027969473 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/debug/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/debug/mod.rs @@ -144,7 +144,7 @@ impl DebugInstrumenter { let save_ret_expr = ast::Statement { kind: ast::StatementKind::Let(ast::LetStatement { pattern: ast::Pattern::Identifier(ident("__debug_expr", ret_expr.span)), - r#type: ast::UnresolvedType::unspecified(), + r#type: ast::UnresolvedTypeData::Unspecified.with_span(Default::default()), expression: ret_expr.clone(), comptime: false, attributes: vec![], @@ -244,7 +244,7 @@ impl DebugInstrumenter { ast::Statement { kind: ast::StatementKind::Let(ast::LetStatement { pattern: ast::Pattern::Tuple(vars_pattern, let_stmt.pattern.span()), - r#type: ast::UnresolvedType::unspecified(), + r#type: ast::UnresolvedTypeData::Unspecified.with_span(Default::default()), comptime: false, expression: ast::Expression { kind: ast::ExpressionKind::Block(ast::BlockExpression { @@ -276,7 +276,7 @@ impl DebugInstrumenter { let let_kind = ast::StatementKind::Let(ast::LetStatement { pattern: ast::Pattern::Identifier(ident("__debug_expr", assign_stmt.expression.span)), - r#type: ast::UnresolvedType::unspecified(), + r#type: ast::UnresolvedTypeData::Unspecified.with_span(Default::default()), expression: assign_stmt.expression.clone(), comptime: false, attributes: vec![], @@ -322,6 +322,9 @@ impl DebugInstrumenter { ast::LValue::Dereference(_ref, _span) => { unimplemented![] } + ast::LValue::Interned(..) => { + unimplemented![] + } } } build_assign_member_stmt( @@ -493,7 +496,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 +507,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 +518,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 +529,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 +540,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 +566,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 afa2e7fa7a8..01b4585640f 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, @@ -276,13 +284,14 @@ impl<'context> Elaborator<'context> { }); } TopLevelStatement::TraitImpl(mut trait_impl) => { - let methods = dc_mod::collect_trait_impl_functions( - self.interner, - &mut trait_impl, - self.crate_id, - self.file, - self.local_module, - ); + let (methods, associated_types, associated_constants) = + dc_mod::collect_trait_impl_items( + self.interner, + &mut trait_impl, + self.crate_id, + self.file, + self.local_module, + ); generated_items.trait_impls.push(UnresolvedTraitImpl { file_id: self.file, @@ -293,6 +302,8 @@ impl<'context> Elaborator<'context> { methods, generics: trait_impl.impl_generics, where_clause: trait_impl.where_clause, + associated_types, + associated_constants, // These last fields are filled in later trait_id: None, 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 5ba448f890e..beede7a3a30 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -11,7 +11,7 @@ use crate::{ hir::{ comptime::{self, InterpreterError}, resolution::errors::ResolverError, - type_check::TypeCheckError, + type_check::{generics::TraitGenerics, TypeCheckError}, }, hir_def::{ expr::{ @@ -58,7 +58,15 @@ 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::Interned(id) => { + let expr_kind = self.interner.get_expression_kind(id); + let expr = Expression::new(expr_kind.clone(), expr.span); + return self.elaborate_expression(expr); + } ExpressionKind::Error => (HirExpression::Error, Type::Error), ExpressionKind::Unquote(_) => { self.push_err(ResolverError::UnquoteUsedOutsideQuote { span: expr.span }); @@ -105,6 +113,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 +390,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; @@ -380,7 +402,7 @@ impl<'context> Elaborator<'context> { // so that the backend doesn't need to worry about methods // TODO: update object_type here? let ((function_id, function_name), function_call) = method_call.into_function_call( - &method_ref, + method_ref, object_type, is_macro_call, location, @@ -489,7 +511,7 @@ impl<'context> Elaborator<'context> { unseen_fields.remove(&field_name); seen_fields.insert(field_name.clone()); - self.unify_with_coercions(&field_type, expected_type, resolved, || { + self.unify_with_coercions(&field_type, expected_type, resolved, field_span, || { TypeCheckError::TypeMismatch { expected_typ: expected_type.to_string(), expr_typ: field_type.to_string(), @@ -603,7 +625,7 @@ impl<'context> Elaborator<'context> { let constraint = TraitConstraint { typ: operand_type.clone(), trait_id: trait_id.trait_id, - trait_generics: Vec::new(), + trait_generics: TraitGenerics::default(), span, }; self.push_trait_constraint(constraint, expr_id); @@ -710,7 +732,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/lints.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/lints.rs index a4140043ac1..78df10fa94c 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/lints.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/lints.rs @@ -236,9 +236,9 @@ pub(crate) fn overflowing_int( }, HirExpression::Prefix(expr) => { overflowing_int(interner, &expr.rhs, annotated_type); - if expr.operator == UnaryOp::Minus { + if expr.operator == UnaryOp::Minus && annotated_type.is_unsigned() { errors.push(TypeCheckError::InvalidUnaryOp { - kind: "annotated_type".to_string(), + kind: annotated_type.to_string(), span, }); } 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 e60308aaddd..e8b38193223 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs @@ -4,16 +4,16 @@ use std::{ }; use crate::{ - ast::{FunctionKind, UnresolvedTraitConstraint}, + ast::{FunctionKind, GenericTypeArgs, UnresolvedTraitConstraint}, hir::{ def_collector::dc_crate::{ filter_literal_globals, CompilationError, ImplMap, UnresolvedGlobal, UnresolvedStruct, UnresolvedTypeAlias, }, def_map::DefMaps, - resolution::{errors::ResolverError, path_resolver::PathResolver}, + resolution::errors::ResolverError, scope::ScopeForest as GenericScopeForest, - type_check::TypeCheckError, + type_check::{generics::TraitGenerics, TypeCheckError}, }, hir_def::{ expr::{HirCapturedVar, HirIdent}, @@ -36,11 +36,11 @@ use crate::{ hir::{ def_collector::{dc_crate::CollectedItems, errors::DefCollectorErrorKind}, def_map::{LocalModuleId, ModuleDefId, ModuleId, MAIN_FUNCTION}, - resolution::{import::PathResolution, path_resolver::StandardPathResolver}, + resolution::import::PathResolution, Context, }, hir_def::function::{FuncMeta, HirFunction}, - macros_api::{Param, Path, UnresolvedType, UnresolvedTypeData}, + macros_api::{Param, Path, UnresolvedTypeData}, node_interner::TraitImplId, }; use crate::{ @@ -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 @@ -121,6 +122,9 @@ pub struct Elaborator<'context> { /// to the corresponding trait impl ID. current_trait_impl: Option, + /// The trait we're currently resolving, if we are resolving one. + current_trait: Option, + /// In-resolution names /// /// This needs to be a set because we can have multiple in-resolution @@ -194,6 +198,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(), @@ -208,6 +213,7 @@ impl<'context> Elaborator<'context> { debug_comptime_in_file, unresolved_globals: BTreeMap::new(), enable_arithmetic_generics, + current_trait: None, } } @@ -484,7 +490,8 @@ impl<'context> Elaborator<'context> { self.verify_trait_constraint( &constraint.typ, constraint.trait_id, - &constraint.trait_generics, + &constraint.trait_generics.ordered, + &constraint.trait_generics.named, expr_id, span, ); @@ -500,7 +507,7 @@ impl<'context> Elaborator<'context> { fn desugar_impl_trait_arg( &mut self, trait_path: Path, - trait_generics: Vec, + trait_generics: GenericTypeArgs, generics: &mut Vec, trait_constraints: &mut Vec, ) -> Type { @@ -623,10 +630,8 @@ impl<'context> Elaborator<'context> { } } - pub fn resolve_module_by_path(&self, path: Path) -> Option { - let path_resolver = StandardPathResolver::new(self.module_id()); - - match path_resolver.resolve(self.def_maps, path.clone(), &mut None) { + pub fn resolve_module_by_path(&mut self, path: Path) -> Option { + match self.resolve_path(path.clone()) { Ok(PathResolution { module_def_id: ModuleDefId::ModuleId(module_id), error }) => { if error.is_some() { None @@ -639,9 +644,7 @@ impl<'context> Elaborator<'context> { } fn resolve_trait_by_path(&mut self, path: Path) -> Option { - let path_resolver = StandardPathResolver::new(self.module_id()); - - let error = match path_resolver.resolve(self.def_maps, path.clone(), &mut None) { + let error = match self.resolve_path(path.clone()) { Ok(PathResolution { module_def_id: ModuleDefId::TraitId(trait_id), error }) => { if let Some(error) = error { self.push_err(error); @@ -680,33 +683,13 @@ impl<'context> Elaborator<'context> { bound: &TraitBound, typ: Type, ) -> Option { - let the_trait = self.lookup_trait_or_error(bound.trait_path.clone())?; - - let resolved_generics = &the_trait.generics.clone(); - assert_eq!(resolved_generics.len(), bound.trait_generics.len()); - let generics_with_types = resolved_generics.iter().zip(&bound.trait_generics); - let trait_generics = vecmap(generics_with_types, |(generic, typ)| { - self.resolve_type_inner(typ.clone(), &generic.kind) - }); - let the_trait = self.lookup_trait_or_error(bound.trait_path.clone())?; let trait_id = the_trait.id; + let span = bound.trait_path.span; - let span = bound.trait_path.span(); - - let expected_generics = the_trait.generics.len(); - let actual_generics = trait_generics.len(); - - if actual_generics != expected_generics { - let item_name = the_trait.name.to_string(); - self.push_err(ResolverError::IncorrectGenericCount { - span, - item_name, - actual: actual_generics, - expected: expected_generics, - }); - } + let (ordered, named) = self.resolve_type_args(bound.trait_generics.clone(), trait_id, span); + let trait_generics = TraitGenerics { ordered, named }; Some(TraitConstraint { typ, trait_id, trait_generics, span }) } @@ -772,8 +755,7 @@ impl<'context> Elaborator<'context> { lints::unnecessary_pub_argument(func, visibility, is_pub_allowed).map(Into::into) }); - let type_span = typ.span.unwrap_or_else(|| pattern.span()); - + let type_span = typ.span; let typ = match typ.typ { UnresolvedTypeData::TraitAsType(path, args) => { self.desugar_impl_trait_arg(path, args, &mut generics, &mut trait_constraints) @@ -802,7 +784,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)); @@ -1015,31 +1002,18 @@ impl<'context> Elaborator<'context> { let self_type_span = trait_impl.object_type.span; if matches!(self_type, Type::MutableReference(_)) { - let span = self_type_span.unwrap_or_else(|| trait_impl.trait_path.span()); + let span = self_type_span; self.push_err(DefCollectorErrorKind::MutableReferenceInTraitImpl { span }); } if let Some(trait_id) = trait_impl.trait_id { self.generics = trait_impl.resolved_generics.clone(); - let where_clause = trait_impl - .where_clause - .iter() - .flat_map(|item| self.resolve_trait_constraint(item)) - .collect::>(); + let where_clause = self.resolve_trait_constraints(&trait_impl.where_clause); self.collect_trait_impl_methods(trait_id, trait_impl, &where_clause); let span = trait_impl.object_type.span; - - let span = if let Some(span) = span { - span - } else if self.interner.is_in_lsp_mode() { - // A span might not be set if this was generated by a macro - Default::default() - } else { - span.expect("All trait self types should have spans") - }; self.declare_methods_on_struct(true, &mut trait_impl.methods, span); let methods = trait_impl.methods.function_ids(); @@ -1053,9 +1027,9 @@ impl<'context> Elaborator<'context> { ident: trait_impl.trait_path.last_ident(), typ: self_type.clone(), trait_id, - trait_generics: trait_generics.clone(), + trait_generics, file: trait_impl.file_id, - where_clause, + where_clause: where_clause.clone(), methods, }); @@ -1064,14 +1038,13 @@ impl<'context> Elaborator<'context> { if let Err((prev_span, prev_file)) = self.interner.add_trait_implementation( self_type.clone(), trait_id, - trait_generics, trait_impl.impl_id.expect("impl_id should be set in define_function_metas"), generics, resolved_trait_impl, ) { self.push_err(DefCollectorErrorKind::OverlappingImpl { typ: self_type.clone(), - span: self_type_span.unwrap_or_else(|| trait_impl.trait_path.span()), + span: self_type_span, }); // The 'previous impl defined here' note must be a separate error currently @@ -1290,6 +1263,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 +1291,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; @@ -1377,7 +1357,7 @@ impl<'context> Elaborator<'context> { let trait_id = self.resolve_trait_by_path(trait_impl.trait_path.clone()); trait_impl.trait_id = trait_id; - let unresolved_type = &trait_impl.object_type; + let unresolved_type = trait_impl.object_type.clone(); self.add_generics(&trait_impl.generics); trait_impl.resolved_generics = self.generics.clone(); @@ -1387,24 +1367,28 @@ impl<'context> Elaborator<'context> { method.def.where_clause.append(&mut trait_impl.where_clause.clone()); } + // Add each associated type to the list of named type arguments + let mut trait_generics = trait_impl.trait_generics.clone(); + trait_generics.named_args.extend(self.take_unresolved_associated_types(trait_impl)); + + let impl_id = self.interner.next_trait_impl_id(); + self.current_trait_impl = Some(impl_id); + // Fetch trait constraints here - let trait_generics = trait_impl + let (ordered_generics, named_generics) = trait_impl .trait_id - .and_then(|trait_id| self.resolve_trait_impl_generics(trait_impl, trait_id)) - .unwrap_or_else(|| { - // We still resolve as to continue type checking - vecmap(&trait_impl.trait_generics, |generic| self.resolve_type(generic.clone())) - }); + .map(|trait_id| { + self.resolve_type_args(trait_generics, trait_id, trait_impl.trait_path.span) + }) + .unwrap_or_default(); - trait_impl.resolved_trait_generics = trait_generics; + trait_impl.resolved_trait_generics = ordered_generics; + self.interner.set_associated_types_for_impl(impl_id, named_generics); - let self_type = self.resolve_type(unresolved_type.clone()); + let self_type = self.resolve_type(unresolved_type); self.self_type = Some(self_type.clone()); trait_impl.methods.self_type = Some(self_type); - let impl_id = self.interner.next_trait_impl_id(); - self.current_trait_impl = Some(impl_id); - self.define_function_metas_for_functions(&mut trait_impl.methods); trait_impl.resolved_object_type = self.self_type.take(); @@ -1451,9 +1435,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/patterns.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/patterns.rs index bd44e087e70..06c153d4c10 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/patterns.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/patterns.rs @@ -585,25 +585,7 @@ impl<'context> Elaborator<'context> { // will replace each trait generic with a fresh type variable, rather than // the type used in the trait constraint (if it exists). See #4088. if let ImplKind::TraitMethod(_, constraint, assumed) = &ident.impl_kind { - let the_trait = self.interner.get_trait(constraint.trait_id); - assert_eq!(the_trait.generics.len(), constraint.trait_generics.len()); - - for (param, arg) in the_trait.generics.iter().zip(&constraint.trait_generics) { - // Avoid binding t = t - if !arg.occurs(param.type_var.id()) { - bindings.insert(param.type_var.id(), (param.type_var.clone(), arg.clone())); - } - } - - // If the trait impl is already assumed to exist we should add any type bindings for `Self`. - // Otherwise `self` will be replaced with a fresh type variable, which will require the user - // to specify a redundant type annotation. - if *assumed { - bindings.insert( - the_trait.self_type_typevar_id, - (the_trait.self_type_typevar.clone(), constraint.typ.clone()), - ); - } + self.bind_generics_from_trait_constraint(constraint, *assumed, &mut bindings); } // An identifiers type may be forall-quantified in the case of generic functions. @@ -622,6 +604,7 @@ impl<'context> Elaborator<'context> { let span = self.interner.expr_span(&expr_id); let location = self.interner.expr_location(&expr_id); + // This instantiates a trait's generics as well which need to be set // when the constraint below is later solved for when the function is // finished. How to link the two? @@ -643,10 +626,9 @@ impl<'context> Elaborator<'context> { if let ImplKind::TraitMethod(_, mut constraint, assumed) = ident.impl_kind { constraint.apply_bindings(&bindings); if assumed { - let trait_impl = TraitImplKind::Assumed { - object_type: constraint.typ, - trait_generics: constraint.trait_generics, - }; + let trait_generics = constraint.trait_generics.clone(); + let object_type = constraint.typ; + let trait_impl = TraitImplKind::Assumed { object_type, trait_generics }; self.interner.select_impl_for_expression(expr_id, trait_impl); } else { // Currently only one impl can be selected per expr_id, so this diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/scope.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/scope.rs index b2367e0cf0e..a51fd737f74 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/scope.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/scope.rs @@ -2,6 +2,7 @@ use noirc_errors::{Location, Spanned}; use crate::ast::{PathKind, ERROR_IDENT}; use crate::hir::def_map::{LocalModuleId, ModuleId}; +use crate::hir::resolution::import::{PathResolution, PathResolutionResult}; use crate::hir::resolution::path_resolver::{PathResolver, StandardPathResolver}; use crate::hir::scope::{Scope as GenericScope, ScopeTree as GenericScopeTree}; use crate::macros_api::Ident; @@ -29,7 +30,7 @@ type ScopeTree = GenericScopeTree; impl<'context> Elaborator<'context> { pub(super) fn lookup(&mut self, path: Path) -> Result { let span = path.span(); - let id = self.resolve_path(path)?; + let id = self.resolve_path_or_error(path)?; T::try_from(id).ok_or_else(|| ResolverError::Expected { expected: T::description(), got: id.as_str().to_owned(), @@ -42,15 +43,37 @@ impl<'context> Elaborator<'context> { ModuleId { krate: self.crate_id, local_id: self.local_module } } - pub(super) fn resolve_path(&mut self, path: Path) -> Result { + pub(super) fn resolve_path_or_error( + &mut self, + path: Path, + ) -> Result { + let path_resolution = self.resolve_path(path)?; + + if let Some(error) = path_resolution.error { + self.push_err(error); + } + + Ok(path_resolution.module_def_id) + } + + pub(super) fn resolve_path(&mut self, path: Path) -> PathResolutionResult { let mut module_id = self.module_id(); let mut path = path; + if path.kind == PathKind::Plain { + let def_map = self.def_maps.get_mut(&self.crate_id).unwrap(); + let module_data = &mut def_map.modules[module_id.local_id.0]; + module_data.use_import(&path.segments[0].ident); + } + if path.kind == PathKind::Plain && path.first_name() == SELF_TYPE_NAME { if let Some(Type::Struct(struct_type, _)) = &self.self_type { let struct_type = struct_type.borrow(); if path.segments.len() == 1 { - return Ok(ModuleDefId::TypeId(struct_type.id)); + return Ok(PathResolution { + module_def_id: ModuleDefId::TypeId(struct_type.id), + error: None, + }); } module_id = struct_type.id.module_id(); @@ -65,11 +88,7 @@ impl<'context> Elaborator<'context> { self.resolve_path_in_module(path, module_id) } - fn resolve_path_in_module( - &mut self, - path: Path, - module_id: ModuleId, - ) -> Result { + fn resolve_path_in_module(&mut self, path: Path, module_id: ModuleId) -> PathResolutionResult { let resolver = StandardPathResolver::new(module_id); let path_resolution; @@ -83,9 +102,6 @@ impl<'context> Elaborator<'context> { resolver.resolve(self.def_maps, path.clone(), &mut Some(&mut references))?; for (referenced, segment) in references.iter().zip(path.segments) { - let Some(referenced) = referenced else { - continue; - }; self.interner.add_reference( *referenced, Location::new(segment.ident.span(), self.file), @@ -102,11 +118,7 @@ impl<'context> Elaborator<'context> { path_resolution = resolver.resolve(self.def_maps, path, &mut None)?; } - if let Some(error) = path_resolution.error { - self.push_err(error); - } - - Ok(path_resolution.module_def_id) + Ok(path_resolution) } pub(super) fn get_struct(&self, type_id: StructId) -> Shared { @@ -153,7 +165,7 @@ impl<'context> Elaborator<'context> { pub(super) fn lookup_global(&mut self, path: Path) -> Result { let span = path.span(); - let id = self.resolve_path(path)?; + let id = self.resolve_path_or_error(path)?; if let Some(function) = TryFromModuleDefId::try_from(id) { return Ok(self.interner.function_definition_id(function)); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/statements.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/statements.rs index 48380383eb0..dcbdf89391e 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/statements.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/statements.rs @@ -39,6 +39,11 @@ impl<'context> Elaborator<'context> { let (expr, _typ) = self.elaborate_expression(expr); (HirStatement::Semi(expr), Type::Unit) } + StatementKind::Interned(id) => { + let kind = self.interner.get_statement_kind(id); + let statement = Statement { kind: kind.clone(), span: statement.span }; + self.elaborate_statement_value(statement) + } StatementKind::Error => (HirStatement::Error, Type::Error), } } @@ -66,36 +71,31 @@ impl<'context> Elaborator<'context> { ) -> (HirStatement, Type) { let expr_span = let_stmt.expression.span; let (expression, expr_type) = self.elaborate_expression(let_stmt.expression); - let annotated_type = self.resolve_type(let_stmt.r#type); + let annotated_type = self.resolve_inferred_type(let_stmt.r#type); let definition = match global_id { None => DefinitionKind::Local(Some(expression)), Some(id) => DefinitionKind::Global(id), }; - // First check if the LHS is unspecified - // If so, then we give it the same type as the expression - let r#type = if annotated_type != Type::Error { - // Now check if LHS is the same type as the RHS - // Importantly, we do not coerce any types implicitly - self.unify_with_coercions(&expr_type, &annotated_type, expression, || { - TypeCheckError::TypeMismatch { - expected_typ: annotated_type.to_string(), - expr_typ: expr_type.to_string(), - expr_span, - } - }); - if annotated_type.is_integer() { - let errors = lints::overflowing_int(self.interner, &expression, &annotated_type); - for error in errors { - self.push_err(error); - } + // Now check if LHS is the same type as the RHS + // Importantly, we do not coerce any types implicitly + self.unify_with_coercions(&expr_type, &annotated_type, expression, expr_span, || { + TypeCheckError::TypeMismatch { + expected_typ: annotated_type.to_string(), + expr_typ: expr_type.to_string(), + expr_span, } - annotated_type - } else { - expr_type - }; + }); + if annotated_type.is_integer() { + let errors = lints::overflowing_int(self.interner, &expression, &annotated_type); + for error in errors { + self.push_err(error); + } + } + + let r#type = annotated_type; let pattern = self.elaborate_pattern_and_store_ids( let_stmt.pattern, r#type.clone(), @@ -136,7 +136,7 @@ impl<'context> Elaborator<'context> { self.push_err(TypeCheckError::VariableMustBeMutable { name, span }); } - self.unify_with_coercions(&expr_type, &lvalue_type, expression, || { + self.unify_with_coercions(&expr_type, &lvalue_type, expression, span, || { TypeCheckError::TypeMismatchWithSource { actual: expr_type.clone(), expected: lvalue_type.clone(), @@ -362,6 +362,10 @@ impl<'context> Elaborator<'context> { let lvalue = HirLValue::Dereference { lvalue, element_type, location }; (lvalue, typ, true) } + LValue::Interned(id, span) => { + let lvalue = self.interner.get_lvalue(id, span).clone(); + self.elaborate_lvalue(lvalue, assign_span) + } } } @@ -424,7 +428,7 @@ impl<'context> Elaborator<'context> { // If we get here the type has no field named 'access.rhs'. // Now we specialize the error message based on whether we know the object type in question yet. if let Type::TypeVariable(..) = &lhs_type { - self.push_err(TypeCheckError::TypeAnnotationsNeeded { span }); + self.push_err(TypeCheckError::TypeAnnotationsNeededForFieldAccess { span }); } else if lhs_type != Type::Error { self.push_err(TypeCheckError::AccessUnknownMember { lhs_type, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/trait_impls.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/trait_impls.rs index 853ee6389fd..aa7e1cb89c5 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/trait_impls.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/trait_impls.rs @@ -1,16 +1,16 @@ use crate::{ + ast::UnresolvedTypeExpression, graph::CrateId, hir::def_collector::{dc_crate::UnresolvedTraitImpl, errors::DefCollectorErrorKind}, + macros_api::{Ident, UnresolvedType, UnresolvedTypeData}, + node_interner::TraitImplId, ResolvedGeneric, }; use crate::{ hir::def_collector::errors::DuplicateType, - hir_def::{ - traits::{TraitConstraint, TraitFunction}, - types::Generics, - }, + hir_def::traits::{TraitConstraint, TraitFunction}, node_interner::{FuncId, TraitId}, - Type, TypeBindings, + Type, }; use noirc_errors::Location; @@ -28,6 +28,8 @@ impl<'context> Elaborator<'context> { self.local_module = trait_impl.module_id; self.file = trait_impl.file_id; + let impl_id = trait_impl.impl_id.expect("impl_id should be set in define_function_metas"); + // In this Vec methods[i] corresponds to trait.methods[i]. If the impl has no implementation // for a particular method, the default implementation will be added at that slot. let mut ordered_methods = Vec::new(); @@ -38,7 +40,6 @@ impl<'context> Elaborator<'context> { // set of function ids that have a corresponding method in the trait let mut func_ids_in_trait = HashSet::default(); - let trait_generics = &self.interner.get_trait(trait_id).generics.clone(); // Temporarily take ownership of the trait's methods so we can iterate over them // while also mutating the interner let the_trait = self.interner.get_trait_mut(trait_id); @@ -72,10 +73,7 @@ impl<'context> Elaborator<'context> { self.push_err(DefCollectorErrorKind::TraitMissingMethod { trait_name: self.interner.get_trait(trait_id).name.clone(), method_name: method.name.clone(), - trait_impl_span: trait_impl - .object_type - .span - .expect("type must have a span"), + trait_impl_span: trait_impl.object_type.span, }); } } else { @@ -85,7 +83,8 @@ impl<'context> Elaborator<'context> { method, trait_impl_where_clause, &trait_impl.resolved_trait_generics, - trait_generics, + trait_id, + impl_id, ); func_ids_in_trait.insert(*func_id); @@ -141,16 +140,15 @@ impl<'context> Elaborator<'context> { func_id: &FuncId, method: &TraitFunction, trait_impl_where_clause: &[TraitConstraint], - impl_trait_generics: &[Type], - trait_generics: &Generics, + trait_impl_generics: &[Type], + trait_id: TraitId, + impl_id: TraitImplId, ) { - let mut bindings = TypeBindings::new(); - for (trait_generic, impl_trait_generic) in trait_generics.iter().zip(impl_trait_generics) { - bindings.insert( - trait_generic.type_var.id(), - (trait_generic.type_var.clone(), impl_trait_generic.clone()), - ); - } + // First get the general trait to impl bindings. + // Then we'll need to add the bindings for this specific method. + let self_type = self.self_type.as_ref().unwrap().clone(); + let mut bindings = + self.interner.trait_to_impl_bindings(trait_id, impl_id, trait_impl_generics, self_type); let override_meta = self.interner.function_meta(func_id); // Substitute each generic on the trait function with the corresponding generic on the impl function @@ -166,11 +164,9 @@ impl<'context> Elaborator<'context> { let mut substituted_method_ids = HashSet::default(); for method_constraint in method.trait_constraints.iter() { let substituted_constraint_type = method_constraint.typ.substitute(&bindings); - let substituted_trait_generics = method_constraint - .trait_generics - .iter() - .map(|generic| generic.substitute(&bindings)) - .collect::>(); + let substituted_trait_generics = + method_constraint.trait_generics.map(|generic| generic.substitute(&bindings)); + substituted_method_ids.insert(( substituted_constraint_type, method_constraint.trait_id, @@ -221,8 +217,30 @@ impl<'context> Elaborator<'context> { let the_trait = self.interner.get_trait(trait_id); if self.crate_id != the_trait.crate_id && self.crate_id != object_crate { self.push_err(DefCollectorErrorKind::TraitImplOrphaned { - span: trait_impl.object_type.span.expect("object type must have a span"), + span: trait_impl.object_type.span, }); } } + + pub(super) fn take_unresolved_associated_types( + &mut self, + trait_impl: &mut UnresolvedTraitImpl, + ) -> Vec<(Ident, UnresolvedType)> { + let mut associated_types = Vec::new(); + for (name, _, expr) in trait_impl.associated_constants.drain(..) { + let span = expr.span; + let typ = match UnresolvedTypeExpression::from_expr(expr, span) { + Ok(expr) => UnresolvedTypeData::Expression(expr).with_span(span), + Err(error) => { + self.push_err(error); + UnresolvedTypeData::Error.with_span(span) + } + }; + associated_types.push((name, typ)); + } + for (name, typ) in trait_impl.associated_types.drain(..) { + associated_types.push((name, typ)); + } + associated_types + } } 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 1e48fdd07e7..f651630baa2 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/traits.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/traits.rs @@ -7,14 +7,8 @@ use crate::{ ast::{ FunctionKind, TraitItem, UnresolvedGeneric, UnresolvedGenerics, UnresolvedTraitConstraint, }, - hir::{ - def_collector::dc_crate::{CompilationError, UnresolvedTrait, UnresolvedTraitImpl}, - type_check::TypeCheckError, - }, - hir_def::{ - function::Parameters, - traits::{TraitConstant, TraitFunction, TraitType}, - }, + hir::{def_collector::dc_crate::UnresolvedTrait, type_check::TypeCheckError}, + hir_def::{function::Parameters, traits::TraitFunction}, macros_api::{ BlockExpression, FunctionDefinition, FunctionReturnType, Ident, ItemVisibility, NodeInterner, NoirFunction, Param, Pattern, UnresolvedType, Visibility, @@ -30,18 +24,19 @@ impl<'context> Elaborator<'context> { pub fn collect_traits(&mut self, traits: &BTreeMap) { for (trait_id, unresolved_trait) in traits { self.recover_generics(|this| { + this.current_trait = Some(*trait_id); + let resolved_generics = this.interner.get_trait(*trait_id).generics.clone(); this.add_existing_generics( &unresolved_trait.trait_def.generics, &resolved_generics, ); - // Resolve order - // 1. Trait Types ( Trait constants can have a trait type, therefore types before constants) - let _ = this.resolve_trait_types(unresolved_trait); - // 2. Trait Constants ( Trait's methods can use trait types & constants, therefore they should be after) - let _ = this.resolve_trait_constants(unresolved_trait); - // 3. Trait Methods + // Each associated type in this trait is also an implicit generic + for associated_type in &this.interner.get_trait(*trait_id).associated_types { + this.generics.push(associated_type.clone()); + } + let methods = this.resolve_trait_methods(*trait_id, unresolved_trait); this.interner.update_trait(*trait_id, |trait_def| { @@ -57,19 +52,8 @@ impl<'context> Elaborator<'context> { self.interner.try_add_prefix_operator_trait(*trait_id); } } - } - fn resolve_trait_types(&mut self, _unresolved_trait: &UnresolvedTrait) -> Vec { - // TODO - vec![] - } - - fn resolve_trait_constants( - &mut self, - _unresolved_trait: &UnresolvedTrait, - ) -> Vec { - // TODO - vec![] + self.current_trait = None; } fn resolve_trait_methods( @@ -145,8 +129,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(), @@ -206,31 +191,6 @@ impl<'context> Elaborator<'context> { // Don't check the scope tree for unused variables, they can't be used in a declaration anyway. self.generics.truncate(old_generic_count); } - - pub fn resolve_trait_impl_generics( - &mut self, - trait_impl: &UnresolvedTraitImpl, - trait_id: TraitId, - ) -> Option> { - let trait_def = self.interner.get_trait(trait_id); - let resolved_generics = trait_def.generics.clone(); - if resolved_generics.len() != trait_impl.trait_generics.len() { - self.push_err(CompilationError::TypeError(TypeCheckError::GenericCountMismatch { - item: trait_def.name.to_string(), - expected: resolved_generics.len(), - found: trait_impl.trait_generics.len(), - span: trait_impl.trait_path.span(), - })); - - return None; - } - - let generics = trait_impl.trait_generics.iter().zip(resolved_generics.iter()); - let mapped = generics.map(|(generic, resolved_generic)| { - self.resolve_type_inner(generic.clone(), &resolved_generic.kind) - }); - Some(mapped.collect()) - } } /// Checks that the type of a function in a trait impl matches the type @@ -263,24 +223,18 @@ pub(crate) fn check_trait_impl_method_matches_declaration( let definition_type = meta.typ.as_monotype(); - let impl_ = + let impl_id = meta.trait_impl.expect("Trait impl function should have a corresponding trait impl"); // If the trait implementation is not defined in the interner then there was a previous // error in resolving the trait path and there is likely no trait for this impl. - let Some(impl_) = interner.try_get_trait_implementation(impl_) else { + let Some(impl_) = interner.try_get_trait_implementation(impl_id) else { return errors; }; let impl_ = impl_.borrow(); let trait_info = interner.get_trait(impl_.trait_id); - let mut bindings = TypeBindings::new(); - bindings.insert( - trait_info.self_type_typevar_id, - (trait_info.self_type_typevar.clone(), impl_.typ.clone()), - ); - if trait_info.generics.len() != impl_.trait_generics.len() { let expected = trait_info.generics.len(); let found = impl_.trait_generics.len(); @@ -290,9 +244,12 @@ pub(crate) fn check_trait_impl_method_matches_declaration( } // Substitute each generic on the trait with the corresponding generic on the impl - for (generic, arg) in trait_info.generics.iter().zip(&impl_.trait_generics) { - bindings.insert(generic.type_var.id(), (generic.type_var.clone(), arg.clone())); - } + let mut bindings = interner.trait_to_impl_bindings( + impl_.trait_id, + impl_id, + &impl_.trait_generics, + impl_.typ.clone(), + ); // If this is None, the trait does not have the corresponding function. // This error should have been caught in name resolution already so we don't @@ -345,9 +302,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 f8ba994f66b..e41234a5be5 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs @@ -1,19 +1,23 @@ -use std::{collections::BTreeMap, rc::Rc}; +use std::{borrow::Cow, collections::BTreeMap, rc::Rc}; use acvm::acir::AcirField; use iter_extended::vecmap; use noirc_errors::{Location, Span}; +use rustc_hash::FxHashMap as HashMap; use crate::{ ast::{ - BinaryOpKind, IntegerBitSize, UnresolvedGeneric, UnresolvedGenerics, - UnresolvedTypeExpression, + AsTraitPath, BinaryOpKind, GenericTypeArgs, IntegerBitSize, UnresolvedGeneric, + UnresolvedGenerics, UnresolvedTypeExpression, }, hir::{ comptime::{Interpreter, Value}, def_map::ModuleDefId, resolution::errors::ResolverError, - type_check::{NoMatchingImplFoundError, Source, TypeCheckError}, + type_check::{ + generics::{Generic, TraitGenerics}, + NoMatchingImplFoundError, Source, TypeCheckError, + }, }, hir_def::{ expr::{ @@ -21,17 +25,18 @@ use crate::{ HirPrefixExpression, }, function::{FuncMeta, Parameters}, - traits::TraitConstraint, + traits::{NamedType, TraitConstraint}, }, macros_api::{ - HirExpression, HirLiteral, HirStatement, NodeInterner, Path, PathKind, SecondaryAttribute, - Signedness, UnaryOp, UnresolvedType, UnresolvedTypeData, + HirExpression, HirLiteral, HirStatement, Ident, NodeInterner, Path, PathKind, + SecondaryAttribute, Signedness, UnaryOp, UnresolvedType, UnresolvedTypeData, }, node_interner::{ - DefinitionKind, DependencyId, ExprId, FuncId, GlobalId, TraitId, TraitImplKind, - TraitMethodId, + DefinitionKind, DependencyId, ExprId, FuncId, GlobalId, ImplSearchErrorKind, TraitId, + TraitImplKind, TraitMethodId, }, - Generics, Kind, ResolvedGeneric, Type, TypeBinding, TypeVariable, TypeVariableKind, + Generics, Kind, ResolvedGeneric, Type, TypeBinding, TypeBindings, TypeVariable, + TypeVariableKind, }; use super::{lints, Elaborator}; @@ -45,9 +50,7 @@ impl<'context> Elaborator<'context> { let span = typ.span; let resolved_type = self.resolve_type_inner(typ, &Kind::Normal); if resolved_type.is_nested_slice() { - self.push_err(ResolverError::NestedSlices { - span: span.expect("Type should have span"), - }); + self.push_err(ResolverError::NestedSlices { span }); } resolved_type } @@ -117,7 +120,11 @@ impl<'context> Elaborator<'context> { } Quoted(quoted) => Type::Quoted(quoted), Unit => Type::Unit, - Unspecified => Type::Error, + Unspecified => { + let span = typ.span; + self.push_err(TypeCheckError::UnspecifiedType { span }); + Type::Error + } Error => Type::Error, Named(path, args, _) => self.resolve_named_type(path, args), TraitAsType(path, args) => self.resolve_trait_as_type(path, args), @@ -125,21 +132,16 @@ 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)); - - // expect() here is valid, because the only places we don't have a span are omitted types - // e.g. a function without return type implicitly has a spanless UnresolvedType::Unit return type - // To get an invalid env type, the user must explicitly specify the type, which will have a span - let env_span = - env.span.expect("Unexpected missing span for closure environment type"); + let env_span = env.span; let env = Box::new(self.resolve_type_inner(*env, kind)); match *env { Type::Unit | Type::Tuple(_) | Type::NamedGeneric(_, _, _) => { - Type::Function(args, ret, env) + Type::Function(args, ret, env, unconstrained) } _ => { self.push_err(ResolverError::InvalidClosureEnvironment { @@ -155,30 +157,30 @@ impl<'context> Elaborator<'context> { } Parenthesized(typ) => self.resolve_type_inner(*typ, kind), Resolved(id) => self.interner.get_quoted_type(id).clone(), - AsTraitPath(_) => todo!("Resolve AsTraitPath"), + AsTraitPath(path) => self.resolve_as_trait_path(*path), + Interned(id) => { + let typ = self.interner.get_unresolved_type_data(id).clone(); + return self.resolve_type_inner(UnresolvedType { typ, span }, kind); + } }; - if let Some(unresolved_span) = typ.span { - let location = Location::new(named_path_span.unwrap_or(unresolved_span), self.file); - - match resolved_type { - Type::Struct(ref struct_type, _) => { - // Record the location of the type reference - self.interner.push_type_ref_location(resolved_type.clone(), location); - - if !is_synthetic { - self.interner.add_struct_reference( - struct_type.borrow().id, - location, - is_self_type_name, - ); - } - } - Type::Alias(ref alias_type, _) => { - self.interner.add_alias_reference(alias_type.borrow().id, location); + let location = Location::new(named_path_span.unwrap_or(typ.span), self.file); + match resolved_type { + Type::Struct(ref struct_type, _) => { + // Record the location of the type reference + self.interner.push_type_ref_location(resolved_type.clone(), location); + if !is_synthetic { + self.interner.add_struct_reference( + struct_type.borrow().id, + location, + is_self_type_name, + ); } - _ => (), } + Type::Alias(ref alias_type, _) => { + self.interner.add_alias_reference(alias_type.borrow().id, location); + } + _ => (), } // Check that any types with a type kind match the expected type kind supplied to this function @@ -196,10 +198,8 @@ impl<'context> Elaborator<'context> { // } if let Type::NamedGeneric(_, name, resolved_kind) = &resolved_type { if matches!(resolved_kind, Kind::Numeric { .. }) && matches!(kind, Kind::Normal) { - let expected_typ_err = ResolverError::NumericGenericUsedForType { - name: name.to_string(), - span: span.expect("Type should have span"), - }; + let expected_typ_err = + ResolverError::NumericGenericUsedForType { name: name.to_string(), span }; self.push_err(expected_typ_err); return Type::Error; } @@ -212,7 +212,27 @@ impl<'context> Elaborator<'context> { self.generics.iter().find(|generic| generic.name.as_ref() == target_name) } - fn resolve_named_type(&mut self, path: Path, args: Vec) -> Type { + // Resolve Self::Foo to an associated type on the current trait or trait impl + fn lookup_associated_type_on_self(&self, path: &Path) -> Option { + if path.segments.len() == 2 && path.first_name() == SELF_TYPE_NAME { + if let Some(trait_id) = self.current_trait { + let the_trait = self.interner.get_trait(trait_id); + if let Some(typ) = the_trait.get_associated_type(path.last_name()) { + return Some(typ.clone().as_named_generic()); + } + } + + if let Some(impl_id) = self.current_trait_impl { + let name = path.last_name(); + if let Some(typ) = self.interner.find_associated_type_for_impl(impl_id, name) { + return Some(typ.clone()); + } + } + } + None + } + + fn resolve_named_type(&mut self, path: Path, args: GenericTypeArgs) -> Type { if args.is_empty() { if let Some(typ) = self.lookup_generic_or_global_type(&path) { return typ; @@ -234,28 +254,18 @@ impl<'context> Elaborator<'context> { } else if name == WILDCARD_TYPE { return self.interner.next_type_variable(); } + } else if let Some(typ) = self.lookup_associated_type_on_self(&path) { + if !args.is_empty() { + self.push_err(ResolverError::GenericsOnAssociatedType { span: path.span() }); + } + return typ; } let span = path.span(); if let Some(type_alias) = self.lookup_type_alias(path.clone()) { - let type_alias = type_alias.borrow(); - let actual_generic_count = args.len(); - let expected_generic_count = type_alias.generics.len(); - let type_alias_string = type_alias.to_string(); - let id = type_alias.id; - - let mut args = vecmap(type_alias.generics.iter().zip(args), |(generic, arg)| { - self.resolve_type_inner(arg, &generic.kind) - }); - - self.verify_generics_count( - expected_generic_count, - actual_generic_count, - &mut args, - span, - || type_alias_string, - ); + let id = type_alias.borrow().id; + let (args, _) = self.resolve_type_args(args, id, path.span()); if let Some(item) = self.current_item { self.interner.add_type_alias_dependency(item, id); @@ -270,8 +280,7 @@ impl<'context> Elaborator<'context> { // equal to another type alias. Fixing this fully requires an analysis to create a DFG // of definition ordering, but for now we have an explicit check here so that we at // least issue an error that the type was not found instead of silently passing. - let alias = self.interner.get_type_alias(id); - return Type::Alias(alias, args); + return Type::Alias(type_alias, args); } match self.lookup_struct_or_error(path) { @@ -284,9 +293,6 @@ impl<'context> Elaborator<'context> { return Type::Error; } - let expected_generic_count = struct_type.borrow().generics.len(); - let actual_generic_count = args.len(); - if !self.in_contract() && self .interner @@ -299,18 +305,7 @@ impl<'context> Elaborator<'context> { }); } - let mut args = - vecmap(struct_type.borrow().generics.iter().zip(args), |(generic, arg)| { - self.resolve_type_inner(arg, &generic.kind) - }); - - self.verify_generics_count( - expected_generic_count, - actual_generic_count, - &mut args, - span, - || struct_type.borrow().to_string(), - ); + let (args, _) = self.resolve_type_args(args, struct_type.borrow(), span); if let Some(current_item) = self.current_item { let dependency_id = struct_type.borrow().id; @@ -323,44 +318,99 @@ impl<'context> Elaborator<'context> { } } - fn resolve_trait_as_type(&mut self, path: Path, args: Vec) -> Type { + fn resolve_trait_as_type(&mut self, path: Path, args: GenericTypeArgs) -> Type { // Fetch information needed from the trait as the closure for resolving all the `args` // requires exclusive access to `self` - let trait_as_type_info = self - .lookup_trait_or_error(path) - .map(|t| (t.id, Rc::new(t.name.to_string()), t.generics.clone())); - - if let Some((id, name, resolved_generics)) = trait_as_type_info { - assert_eq!(resolved_generics.len(), args.len()); - let generics_with_types = resolved_generics.iter().zip(args); - let args = vecmap(generics_with_types, |(generic, typ)| { - self.resolve_type_inner(typ, &generic.kind) - }); - Type::TraitAsType(id, Rc::new(name.to_string()), args) + let span = path.span; + let trait_as_type_info = self.lookup_trait_or_error(path).map(|t| t.id); + + if let Some(id) = trait_as_type_info { + let (ordered, named) = self.resolve_type_args(args, id, span); + let name = self.interner.get_trait(id).name.to_string(); + let generics = TraitGenerics { ordered, named }; + Type::TraitAsType(id, Rc::new(name), generics) } else { Type::Error } } - fn verify_generics_count( + pub(super) fn resolve_type_args( &mut self, - expected_count: usize, - actual_count: usize, - args: &mut Vec, + mut args: GenericTypeArgs, + item: impl Generic, span: Span, - type_name: impl FnOnce() -> String, - ) { - if actual_count != expected_count { - self.push_err(ResolverError::IncorrectGenericCount { + ) -> (Vec, Vec) { + let expected_kinds = item.generics(self.interner); + + if args.ordered_args.len() != expected_kinds.len() { + self.push_err(TypeCheckError::GenericCountMismatch { + item: item.item_name(self.interner), + expected: expected_kinds.len(), + found: args.ordered_args.len(), span, - item_name: type_name(), - actual: actual_count, - expected: expected_count, }); + let error_type = UnresolvedTypeData::Error.with_span(span); + args.ordered_args.resize(expected_kinds.len(), error_type); + } + + let ordered_args = expected_kinds.iter().zip(args.ordered_args); + let ordered = + vecmap(ordered_args, |(generic, typ)| self.resolve_type_inner(typ, &generic.kind)); - // Fix the generic count so we can continue typechecking - args.resize_with(expected_count, || Type::Error); + let mut associated = Vec::new(); + + if item.accepts_named_type_args() { + associated = self.resolve_associated_type_args(args.named_args, item, span); + } else if !args.named_args.is_empty() { + let item_kind = item.item_kind(); + self.push_err(ResolverError::NamedTypeArgs { span, item_kind }); } + + (ordered, associated) + } + + fn resolve_associated_type_args( + &mut self, + args: Vec<(Ident, UnresolvedType)>, + item: impl Generic, + span: Span, + ) -> Vec { + let mut seen_args = HashMap::default(); + let mut required_args = item.named_generics(self.interner); + let mut resolved = Vec::with_capacity(required_args.len()); + + // Go through each argument to check if it is in our required_args list. + // If it is remove it from the list, otherwise issue an error. + for (name, typ) in args { + let index = + required_args.iter().position(|item| item.name.as_ref() == &name.0.contents); + + let Some(index) = index else { + if let Some(prev_span) = seen_args.get(&name.0.contents).copied() { + self.push_err(TypeCheckError::DuplicateNamedTypeArg { name, prev_span }); + } else { + let item = item.item_name(self.interner); + self.push_err(TypeCheckError::NoSuchNamedTypeArg { name, item }); + } + continue; + }; + + // Remove the argument from the required list so we remember that we already have it + let expected = required_args.remove(index); + seen_args.insert(name.0.contents.clone(), name.span()); + + let typ = self.resolve_type_inner(typ, &expected.kind); + resolved.push(NamedType { name, typ }); + } + + // Anything that hasn't been removed yet is missing + for generic in required_args { + let item = item.item_name(self.interner); + let name = generic.name.clone(); + self.push_err(TypeCheckError::MissingNamedTypeArg { item, span, name }); + } + + resolved } pub fn lookup_generic_or_global_type(&mut self, path: &Path) -> Option { @@ -370,10 +420,12 @@ impl<'context> Elaborator<'context> { let generic = generic.clone(); return Some(Type::NamedGeneric(generic.type_var, generic.name, generic.kind)); } + } else if let Some(typ) = self.lookup_associated_type_on_self(path) { + return Some(typ); } // If we cannot find a local generic of the same name, try to look up a global - match self.resolve_path(path.clone()) { + match self.resolve_path_or_error(path.clone()) { Ok(ModuleDefId::GlobalId(id)) => { if let Some(current_item) = self.current_item { self.interner.add_global_dependency(current_item, id); @@ -397,14 +449,19 @@ impl<'context> Elaborator<'context> { }) } UnresolvedTypeExpression::Constant(int, _) => Type::Constant(int), - UnresolvedTypeExpression::BinaryOperation(lhs, op, rhs, _) => { + UnresolvedTypeExpression::BinaryOperation(lhs, op, rhs, span) => { let (lhs_span, rhs_span) = (lhs.span(), rhs.span()); let lhs = self.convert_expression_type(*lhs); let rhs = self.convert_expression_type(*rhs); match (lhs, rhs) { (Type::Constant(lhs), Type::Constant(rhs)) => { - Type::Constant(op.function(lhs, rhs)) + if let Some(result) = op.function(lhs, rhs) { + Type::Constant(result) + } else { + self.push_err(ResolverError::OverflowInType { lhs, op, rhs, span }); + Type::Error + } } (lhs, rhs) => { if !self.enable_arithmetic_generics { @@ -417,6 +474,49 @@ impl<'context> Elaborator<'context> { } } } + UnresolvedTypeExpression::AsTraitPath(path) => self.resolve_as_trait_path(*path), + } + } + + fn resolve_as_trait_path(&mut self, path: AsTraitPath) -> Type { + let span = path.trait_path.span; + let Some(trait_id) = self.resolve_trait_by_path(path.trait_path.clone()) else { + // Error should already be pushed in the None case + return Type::Error; + }; + + let (ordered, named) = self.resolve_type_args(path.trait_generics.clone(), trait_id, span); + let object_type = self.resolve_type(path.typ.clone()); + + match self.interner.lookup_trait_implementation(&object_type, trait_id, &ordered, &named) { + Ok(impl_kind) => self.get_associated_type_from_trait_impl(path, impl_kind), + Err(constraints) => { + self.push_trait_constraint_error(&object_type, constraints, span); + Type::Error + } + } + } + + fn get_associated_type_from_trait_impl( + &mut self, + path: AsTraitPath, + impl_kind: TraitImplKind, + ) -> Type { + let associated_types = match impl_kind { + TraitImplKind::Assumed { trait_generics, .. } => Cow::Owned(trait_generics.named), + TraitImplKind::Normal(impl_id) => { + Cow::Borrowed(self.interner.get_associated_types_for_impl(impl_id)) + } + }; + + match associated_types.iter().find(|named| named.name == path.impl_item) { + Some(generic) => generic.typ.clone(), + None => { + let name = path.impl_item.clone(); + let item = format!("<{} as {}>", path.typ, path.trait_path); + self.push_err(TypeCheckError::NoSuchNamedTypeArg { name, item }); + Type::Error + } } } @@ -438,17 +538,8 @@ impl<'context> Elaborator<'context> { if name == SELF_TYPE_NAME { let the_trait = self.interner.get_trait(trait_id); let method = the_trait.find_method(method.0.contents.as_str())?; - - let constraint = TraitConstraint { - typ: self.self_type.clone()?, - trait_generics: Type::from_generics(&vecmap(&the_trait.generics, |generic| { - generic.type_var.clone() - })), - trait_id, - span: path.span(), - }; - - return Some((method, constraint, false)); + let constraint = the_trait.as_constraint(path.span); + return Some((method, constraint, true)); } } None @@ -464,17 +555,9 @@ impl<'context> Elaborator<'context> { ) -> Option<(TraitMethodId, TraitConstraint, bool)> { let func_id: FuncId = self.lookup(path.clone()).ok()?; let meta = self.interner.function_meta(&func_id); - let trait_id = meta.trait_id?; - let the_trait = self.interner.get_trait(trait_id); + let the_trait = self.interner.get_trait(meta.trait_id?); let method = the_trait.find_method(path.last_name())?; - let constraint = TraitConstraint { - typ: Type::TypeVariable(the_trait.self_type_typevar.clone(), TypeVariableKind::Normal), - trait_generics: Type::from_generics(&vecmap(&the_trait.generics, |generic| { - generic.type_var.clone() - })), - trait_id, - span: path.span(), - }; + let constraint = the_trait.as_constraint(path.span); Some((method, constraint, false)) } @@ -641,10 +724,18 @@ impl<'context> Elaborator<'context> { actual: &Type, expected: &Type, expression: ExprId, + span: Span, make_error: impl FnOnce() -> TypeCheckError, ) { let mut errors = Vec::new(); - actual.unify_with_coercions(expected, expression, self.interner, &mut errors, make_error); + actual.unify_with_coercions( + expected, + expression, + span, + self.interner, + &mut errors, + make_error, + ); self.errors.extend(errors.into_iter().map(|error| (error.into(), self.file))); } @@ -676,7 +767,7 @@ impl<'context> Elaborator<'context> { /// Insert as many dereference operations as necessary to automatically dereference a method /// call object to its base value type T. pub(super) fn insert_auto_dereferences(&mut self, object: ExprId, typ: Type) -> (ExprId, Type) { - if let Type::MutableReference(element) = typ { + if let Type::MutableReference(element) = typ.follow_bindings() { let location = self.interner.id_location(object); let object = self.interner.push_expr(HirExpression::Prefix(HirPrefixExpression { @@ -735,11 +826,13 @@ 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 { - expected_typ: param.to_string(), - expr_typ: arg.to_string(), - expr_span: *arg_span, + for (param, (arg, arg_expr_id, arg_span)) in fn_params.iter().zip(callsite_args) { + self.unify_with_coercions(arg, param, *arg_expr_id, *arg_span, || { + TypeCheckError::TypeMismatch { + expected_typ: param.to_string(), + expr_typ: arg.to_string(), + expr_span: *arg_span, + } }); } @@ -763,7 +856,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 +866,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, @@ -956,9 +1050,6 @@ impl<'context> Elaborator<'context> { // Matches on TypeVariable must be first so that we follow any type // bindings. (TypeVariable(int, _), other) | (other, TypeVariable(int, _)) => { - if let TypeBinding::Bound(binding) = &*int.borrow() { - return self.infix_operand_type_rules(binding, op, other, span); - } if op.kind == BinaryOpKind::ShiftLeft || op.kind == BinaryOpKind::ShiftRight { self.unify( rhs_type, @@ -973,6 +1064,9 @@ impl<'context> Elaborator<'context> { }; return Ok((lhs_type.clone(), use_impl)); } + if let TypeBinding::Bound(binding) = &*int.borrow() { + return self.infix_operand_type_rules(binding, op, other, span); + } let use_impl = self.bind_type_variables_for_infix(lhs_type, op, rhs_type, span); Ok((other.clone(), use_impl)) } @@ -1128,7 +1222,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]; @@ -1150,7 +1244,7 @@ impl<'context> Elaborator<'context> { let the_trait = self.interner.get_trait(trait_method_id.trait_id); let object_type = object_type.substitute(&bindings); bindings.insert( - the_trait.self_type_typevar_id, + the_trait.self_type_typevar.id(), (the_trait.self_type_typevar.clone(), object_type.clone()), ); self.interner.select_impl_for_expression( @@ -1247,7 +1341,7 @@ impl<'context> Elaborator<'context> { // The type variable must be unbound at this point since follow_bindings was called Type::TypeVariable(_, TypeVariableKind::Normal) => { - self.push_err(TypeCheckError::TypeAnnotationsNeeded { span }); + self.push_err(TypeCheckError::TypeAnnotationsNeededForMethodCall { span }); None } @@ -1282,10 +1376,9 @@ impl<'context> Elaborator<'context> { if method.name.0.contents == method_name { let trait_method = TraitMethodId { trait_id: constraint.trait_id, method_index }; - return Some(HirMethodReference::TraitMethodId( - trait_method, - constraint.trait_generics.clone(), - )); + + let generics = constraint.trait_generics.clone(); + return Some(HirMethodReference::TraitMethodId(trait_method, generics)); } } } @@ -1314,22 +1407,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 +1477,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}"), @@ -1428,7 +1532,16 @@ impl<'context> Elaborator<'context> { let func_span = self.interner.expr_span(&body_id); // XXX: We could be more specific and return the span of the last stmt, however stmts do not have spans yet if let Type::TraitAsType(trait_id, _, generics) = declared_return_type { - if self.interner.lookup_trait_implementation(&body_type, *trait_id, generics).is_err() { + if self + .interner + .lookup_trait_implementation( + &body_type, + *trait_id, + &generics.ordered, + &generics.named, + ) + .is_err() + { self.push_err(TypeCheckError::TypeMismatchWithSource { expected: declared_return_type.clone(), actual: body_type, @@ -1437,7 +1550,7 @@ impl<'context> Elaborator<'context> { }); } } else { - self.unify_with_coercions(&body_type, declared_return_type, body_id, || { + self.unify_with_coercions(&body_type, declared_return_type, body_id, func_span, || { let mut error = TypeCheckError::TypeMismatchWithSource { expected: declared_return_type.clone(), actual: body_type.clone(), @@ -1479,22 +1592,47 @@ impl<'context> Elaborator<'context> { object_type: &Type, trait_id: TraitId, trait_generics: &[Type], + associated_types: &[NamedType], function_ident_id: ExprId, span: Span, ) { - match self.interner.lookup_trait_implementation(object_type, trait_id, trait_generics) { + match self.interner.lookup_trait_implementation( + object_type, + trait_id, + trait_generics, + associated_types, + ) { Ok(impl_kind) => { self.interner.select_impl_for_expression(function_ident_id, impl_kind); } - Err(erroring_constraints) => { - if erroring_constraints.is_empty() { - self.push_err(TypeCheckError::TypeAnnotationsNeeded { span }); - } else if let Some(error) = - NoMatchingImplFoundError::new(self.interner, erroring_constraints, span) + Err(error) => self.push_trait_constraint_error(object_type, error, span), + } + } + + fn push_trait_constraint_error( + &mut self, + object_type: &Type, + error: ImplSearchErrorKind, + span: Span, + ) { + match error { + ImplSearchErrorKind::TypeAnnotationsNeededOnObjectType => { + self.push_err(TypeCheckError::TypeAnnotationsNeededForMethodCall { span }); + } + ImplSearchErrorKind::Nested(constraints) => { + if let Some(error) = NoMatchingImplFoundError::new(self.interner, constraints, span) { self.push_err(TypeCheckError::NoMatchingImplFound(error)); } } + ImplSearchErrorKind::MultipleMatching(candidates) => { + let object_type = object_type.clone(); + self.push_err(TypeCheckError::MultipleMatchingImpls { + object_type, + span, + candidates, + }); + } } } @@ -1555,9 +1693,12 @@ impl<'context> Elaborator<'context> { | Type::Forall(_, _) => (), Type::TraitAsType(_, _, args) => { - for arg in args { + for arg in &args.ordered { Self::find_numeric_generics_in_type(arg, found); } + for arg in &args.named { + Self::find_numeric_generics_in_type(&arg.typ, found); + } } Type::Array(length, element_type) => { @@ -1577,7 +1718,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); } @@ -1653,6 +1794,50 @@ impl<'context> Elaborator<'context> { } } } + + pub fn bind_generics_from_trait_constraint( + &mut self, + constraint: &TraitConstraint, + assumed: bool, + bindings: &mut TypeBindings, + ) { + let the_trait = self.interner.get_trait(constraint.trait_id); + assert_eq!(the_trait.generics.len(), constraint.trait_generics.ordered.len()); + + for (param, arg) in the_trait.generics.iter().zip(&constraint.trait_generics.ordered) { + // Avoid binding t = t + if !arg.occurs(param.type_var.id()) { + bindings.insert(param.type_var.id(), (param.type_var.clone(), arg.clone())); + } + } + + let mut associated_types = the_trait.associated_types.clone(); + assert_eq!(associated_types.len(), constraint.trait_generics.named.len()); + + for arg in &constraint.trait_generics.named { + let i = associated_types + .iter() + .position(|typ| *typ.name == arg.name.0.contents) + .unwrap_or_else(|| { + unreachable!("Expected to find associated type named {}", arg.name) + }); + + let param = associated_types.swap_remove(i); + + // Avoid binding t = t + if !arg.typ.occurs(param.type_var.id()) { + bindings.insert(param.type_var.id(), (param.type_var.clone(), arg.typ.clone())); + } + } + + // If the trait impl is already assumed to exist we should add any type bindings for `Self`. + // Otherwise `self` will be replaced with a fresh type variable, which will require the user + // to specify a redundant type annotation. + if assumed { + let self_type = the_trait.self_type_typevar.clone(); + bindings.insert(self_type.id(), (self_type, constraint.typ.clone())); + } + } } /// Gives an error if a user tries to create a mutable reference diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/errors.rs index b7b49090232..fd916485eaf 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/errors.rs @@ -188,11 +188,19 @@ pub enum InterpreterError { FunctionAlreadyResolved { location: Location, }, + MultipleMatchingImpls { + object_type: Type, + candidates: Vec, + location: Location, + }, Unimplemented { item: String, location: Location, }, + TypeAnnotationsNeededForMethodCall { + location: Location, + }, // These cases are not errors, they are just used to prevent us from running more code // until the loop can be resumed properly. These cases will never be displayed to users. @@ -257,8 +265,10 @@ impl InterpreterError { | InterpreterError::ContinueNotInLoop { location, .. } | InterpreterError::TraitDefinitionMustBeAPath { location } | InterpreterError::FailedToResolveTraitDefinition { location } - | InterpreterError::FailedToResolveTraitBound { location, .. } => *location, - InterpreterError::FunctionAlreadyResolved { location, .. } => *location, + | InterpreterError::FailedToResolveTraitBound { location, .. } + | InterpreterError::FunctionAlreadyResolved { location, .. } + | InterpreterError::MultipleMatchingImpls { location, .. } + | InterpreterError::TypeAnnotationsNeededForMethodCall { location } => *location, InterpreterError::FailedToParseMacro { error, file, .. } => { Location::new(error.span(), *file) @@ -527,6 +537,26 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { .to_string(); CustomDiagnostic::simple_error(msg, secondary, location.span) } + InterpreterError::MultipleMatchingImpls { object_type, candidates, location } => { + let message = format!("Multiple trait impls match the object type `{object_type}`"); + let secondary = "Ambiguous impl".to_string(); + let mut error = CustomDiagnostic::simple_error(message, secondary, location.span); + for (i, candidate) in candidates.iter().enumerate() { + error.add_note(format!("Candidate {}: `{candidate}`", i + 1)); + } + error + } + InterpreterError::TypeAnnotationsNeededForMethodCall { location } => { + let mut error = CustomDiagnostic::simple_error( + "Object type is unknown in method call".to_string(), + "Type must be known by this point to know which method to call".to_string(), + location.span, + ); + let message = + "Try adding a type annotation for the object type before this method call"; + error.add_note(message.to_string()); + error + } } } } 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 bc48b2875c8..1c03184a8f5 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 @@ -3,8 +3,8 @@ use noirc_errors::{Span, Spanned}; use crate::ast::{ ArrayLiteral, AssignStatement, BlockExpression, CallExpression, CastExpression, ConstrainKind, - ConstructorExpression, ExpressionKind, ForLoopStatement, ForRange, Ident, IfExpression, - IndexExpression, InfixExpression, LValue, Lambda, LetStatement, Literal, + ConstructorExpression, ExpressionKind, ForLoopStatement, ForRange, GenericTypeArgs, Ident, + IfExpression, IndexExpression, InfixExpression, LValue, Lambda, LetStatement, Literal, MemberAccessExpression, MethodCallExpression, Path, PathSegment, Pattern, PrefixExpression, UnresolvedType, UnresolvedTypeData, UnresolvedTypeExpression, }; @@ -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 @@ -297,7 +300,8 @@ impl Type { } Type::Struct(def, generics) => { let struct_def = def.borrow(); - let generics = vecmap(generics, |generic| generic.to_display_ast()); + let ordered_args = vecmap(generics, |generic| generic.to_display_ast()); + let generics = GenericTypeArgs { ordered_args, named_args: Vec::new() }; let name = Path::from_ident(struct_def.name.clone()); UnresolvedTypeData::Named(name, generics, false) } @@ -305,7 +309,8 @@ impl Type { // Keep the alias name instead of expanding this in case the // alias' definition was changed let type_def = type_def.borrow(); - let generics = vecmap(generics, |generic| generic.to_display_ast()); + let ordered_args = vecmap(generics, |generic| generic.to_display_ast()); + let generics = GenericTypeArgs { ordered_args, named_args: Vec::new() }; let name = Path::from_ident(type_def.name.clone()); UnresolvedTypeData::Named(name, generics, false) } @@ -332,19 +337,23 @@ impl Type { } } Type::TraitAsType(_, name, generics) => { - let generics = vecmap(generics, |generic| generic.to_display_ast()); + let ordered_args = vecmap(&generics.ordered, |generic| generic.to_display_ast()); + let named_args = vecmap(&generics.named, |named_type| { + (named_type.name.clone(), named_type.typ.to_display_ast()) + }); + let generics = GenericTypeArgs { ordered_args, named_args }; let name = Path::from_single(name.as_ref().clone(), Span::default()); UnresolvedTypeData::TraitAsType(name, generics) } Type::NamedGeneric(_var, name, _kind) => { let name = Path::from_single(name.as_ref().clone(), Span::default()); - UnresolvedTypeData::TraitAsType(name, Vec::new()) + UnresolvedTypeData::Named(name, GenericTypeArgs::default(), true) } - 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()); @@ -367,7 +376,7 @@ impl Type { } }; - UnresolvedType { typ, span: None } + UnresolvedType { typ, span: Span::default() } } /// Convert to AST for display (some details lost) 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 72b92e288c7..33f8c9d8332 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 ef7b9f2be55..852733b6ca8 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -5,27 +5,32 @@ use std::{ 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, - replace_func_meta_parameters, replace_func_meta_return_type, + block_expression_to_value, check_argument_count, check_function_not_yet_resolved, + check_one_argument, check_three_arguments, check_two_arguments, get_expr, get_field, + get_function_def, get_module, get_quoted, get_slice, get_struct, get_trait_constraint, + get_trait_def, get_trait_impl, get_tuple, get_type, get_u32, get_unresolved_type, + hir_pattern_to_tokens, mutate_func_meta_type, parse, replace_func_meta_parameters, + replace_func_meta_return_type, }; +use chumsky::{prelude::choice, Parser}; +use im::Vector; use iter_extended::{try_vecmap, vecmap}; use noirc_errors::Location; +use num_bigint::BigUint; use rustc_hash::FxHashMap as HashMap; use crate::{ ast::{ - ExpressionKind, FunctionKind, FunctionReturnType, IntegerBitSize, UnresolvedType, - UnresolvedTypeData, Visibility, + ArrayLiteral, BlockExpression, Expression, ExpressionKind, FunctionKind, + FunctionReturnType, IntegerBitSize, LValue, Literal, Statement, StatementKind, UnaryOp, + UnresolvedType, UnresolvedTypeData, Visibility, }, - hir::comptime::{errors::IResult, value::add_token_spans, InterpreterError, Value}, + hir::comptime::{errors::IResult, value::ExprValue, InterpreterError, Value}, hir_def::function::FunctionBody, - macros_api::{ModuleDefId, NodeInterner, Signedness}, - node_interner::DefinitionKind, + macros_api::{HirExpression, HirLiteral, ModuleDefId, NodeInterner, Signedness}, + node_interner::{DefinitionKind, TraitImplKind}, parser::{self}, - token::{SpannedToken, Token}, + token::Token, QuotedType, Shared, Type, }; @@ -46,9 +51,42 @@ impl<'local, 'context> Interpreter<'local, 'context> { match name { "array_as_str_unchecked" => array_as_str_unchecked(interner, arguments, location), "array_len" => array_len(interner, arguments, location), + "assert_constant" => Ok(Value::Bool(true)), "as_slice" => as_slice(interner, arguments, location), - "expr_as_function_call" => expr_as_function_call(arguments, return_type, location), + "expr_as_array" => expr_as_array(interner, arguments, return_type, location), + "expr_as_assign" => expr_as_assign(interner, arguments, return_type, location), + "expr_as_binary_op" => expr_as_binary_op(interner, arguments, return_type, location), + "expr_as_block" => expr_as_block(interner, arguments, return_type, location), + "expr_as_bool" => expr_as_bool(interner, arguments, return_type, location), + "expr_as_cast" => expr_as_cast(interner, arguments, return_type, location), + "expr_as_comptime" => expr_as_comptime(interner, arguments, return_type, location), + "expr_as_function_call" => { + expr_as_function_call(interner, arguments, return_type, location) + } + "expr_as_if" => expr_as_if(interner, arguments, return_type, location), + "expr_as_index" => expr_as_index(interner, arguments, return_type, location), + "expr_as_integer" => expr_as_integer(interner, arguments, return_type, location), + "expr_as_member_access" => { + expr_as_member_access(interner, arguments, return_type, location) + } + "expr_as_method_call" => { + expr_as_method_call(interner, arguments, return_type, location) + } + "expr_as_repeated_element_array" => { + expr_as_repeated_element_array(interner, arguments, return_type, location) + } + "expr_as_repeated_element_slice" => { + expr_as_repeated_element_slice(interner, arguments, return_type, location) + } + "expr_as_slice" => expr_as_slice(interner, arguments, return_type, location), + "expr_as_tuple" => expr_as_tuple(interner, arguments, return_type, location), + "expr_as_unary_op" => expr_as_unary_op(interner, arguments, return_type, location), + "expr_as_unsafe" => expr_as_unsafe(interner, arguments, return_type, location), + "expr_has_semicolon" => expr_has_semicolon(interner, arguments, location), + "expr_is_break" => expr_is_break(interner, arguments, location), + "expr_is_continue" => expr_is_continue(interner, arguments, location), "is_unconstrained" => Ok(Value::Bool(true)), + "function_def_body" => function_def_body(interner, arguments, location), "function_def_name" => function_def_name(interner, arguments, location), "function_def_parameters" => function_def_parameters(interner, arguments, location), "function_def_return_type" => function_def_return_type(interner, arguments, location), @@ -79,6 +117,7 @@ impl<'local, 'context> Interpreter<'local, 'context> { "struct_def_as_type" => struct_def_as_type(interner, arguments, location), "struct_def_fields" => struct_def_fields(interner, arguments, location), "struct_def_generics" => struct_def_generics(interner, arguments, location), + "to_le_radix" => to_le_radix(arguments, location), "trait_constraint_eq" => trait_constraint_eq(interner, arguments, location), "trait_constraint_hash" => trait_constraint_hash(interner, arguments, location), "trait_def_as_trait_constraint" => { @@ -86,6 +125,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,10 +136,14 @@ 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), "type_of" => type_of(arguments, location), + "unresolved_type_is_field" => unresolved_type_is_field(interner, arguments, location), "zeroed" => zeroed(return_type), _ => { let item = format!("Comptime evaluation for builtin function {name}"); @@ -322,10 +369,14 @@ fn quoted_as_expr( ) -> IResult { let argument = check_one_argument(arguments, location)?; - let expr = parse(argument, parser::expression(), "an expression").ok(); - let value = expr.map(|expr| Value::Expr(expr.kind)); + let expr_parser = parser::expression().map(|expr| Value::expression(expr.kind)); + let statement_parser = parser::fresh_statement().map(Value::statement); + let lvalue_parser = parser::lvalue(parser::expression()).map(Value::lvalue); + let parser = choice((expr_parser, statement_parser, lvalue_parser)); + + let expr = parse(argument, parser, "an expression").ok(); - option(return_type, value) + option(return_type, expr) } // fn as_module(quoted: Quoted) -> Option @@ -361,6 +412,7 @@ fn quoted_as_trait_constraint( elaborator.resolve_trait_bound(&trait_bound, Type::Unit) }) .ok_or(InterpreterError::FailedToResolveTraitBound { trait_bound, location })?; + Ok(Value::TraitConstraint(bound.trait_id, bound.trait_generics)) } @@ -377,6 +429,35 @@ fn quoted_as_type( Ok(Value::Type(typ)) } +fn to_le_radix(arguments: Vec<(Value, Location)>, location: Location) -> IResult { + let (value, radix, limb_count) = check_three_arguments(arguments, location)?; + + let value = get_field(value)?; + let radix = get_u32(radix)?; + let limb_count = get_u32(limb_count)?; + + // Decompose the integer into its radix digits in little endian form. + let decomposed_integer = compute_to_radix(value, radix); + let decomposed_integer = vecmap(0..limb_count as usize, |i| match decomposed_integer.get(i) { + Some(digit) => Value::U8(*digit), + None => Value::U8(0), + }); + Ok(Value::Array( + decomposed_integer.into(), + Type::Integer(Signedness::Unsigned, IntegerBitSize::Eight), + )) +} + +fn compute_to_radix(field: FieldElement, radix: u32) -> Vec { + let bit_size = u32::BITS - (radix - 1).leading_zeros(); + let radix_big = BigUint::from(radix); + assert_eq!(BigUint::from(2u128).pow(bit_size), radix_big, "ICE: Radix must be a power of 2"); + let big_integer = BigUint::from_bytes_be(&field.to_be_bytes()); + + // Decompose the integer into its radix digits in little endian form. + big_integer.to_radix_le(radix) +} + // fn as_array(self) -> Option<(Type, Type)> fn type_as_array( arguments: Vec<(Value, Location)>, @@ -507,6 +588,31 @@ 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.ordered, + &generics.named, + ) { + 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, @@ -518,7 +624,9 @@ fn type_implements( let typ = get_type(typ)?; let (trait_id, generics) = get_trait_constraint(constraint)?; - let implements = interner.try_lookup_trait_implementation(&typ, trait_id, &generics).is_ok(); + let implements = interner + .try_lookup_trait_implementation(&typ, trait_id, &generics.ordered, &generics.named) + .is_ok(); Ok(Value::Bool(implements)) } @@ -607,6 +715,52 @@ 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 is_field(self) -> bool +fn unresolved_type_is_field( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let self_argument = check_one_argument(arguments, location)?; + let typ = get_unresolved_type(interner, self_argument)?; + Ok(Value::Bool(matches!(typ, UnresolvedTypeData::FieldElement))) +} + // fn zeroed() -> T fn zeroed(return_type: Type) -> IResult { match return_type { @@ -683,22 +837,179 @@ fn zeroed(return_type: Type) -> IResult { | Type::InfixExpr(..) | Type::Quoted(_) | Type::Error - | Type::TraitAsType(_, _, _) + | Type::TraitAsType(..) | Type::NamedGeneric(_, _, _) => Ok(Value::Zeroed(return_type)), } } +// fn as_array(self) -> Option<[Expr]> +fn expr_as_array( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(interner, arguments, return_type, location, |expr| { + if let ExprValue::Expression(ExpressionKind::Literal(Literal::Array( + ArrayLiteral::Standard(exprs), + ))) = expr + { + let exprs = exprs.into_iter().map(|expr| Value::expression(expr.kind)).collect(); + let typ = Type::Slice(Box::new(Type::Quoted(QuotedType::Expr))); + Some(Value::Slice(exprs, typ)) + } else { + None + } + }) +} + +// fn as_assign(self) -> Option<(Expr, Expr)> +fn expr_as_assign( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(interner, arguments, return_type, location, |expr| { + if let ExprValue::Statement(StatementKind::Assign(assign)) = expr { + let lhs = Value::lvalue(assign.lvalue); + let rhs = Value::expression(assign.expression.kind); + Some(Value::Tuple(vec![lhs, rhs])) + } else { + None + } + }) +} + +// fn as_binary_op(self) -> Option<(Expr, BinaryOp, Expr)> +fn expr_as_binary_op( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(interner, arguments, return_type.clone(), location, |expr| { + if let ExprValue::Expression(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::expression(infix_expr.lhs.kind); + let rhs = Value::expression(infix_expr.rhs.kind); + Some(Value::Tuple(vec![lhs, unary_op, rhs])) + } else { + None + } + }) +} + +// fn as_block(self) -> Option<[Expr]> +fn expr_as_block( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(interner, arguments, return_type, location, |expr| { + if let ExprValue::Expression(ExpressionKind::Block(block_expr)) = expr { + Some(block_expression_to_value(block_expr)) + } else { + None + } + }) +} + +// fn as_bool(self) -> Option +fn expr_as_bool( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(interner, arguments, return_type, location, |expr| { + if let ExprValue::Expression(ExpressionKind::Literal(Literal::Bool(bool))) = expr { + Some(Value::Bool(bool)) + } else { + None + } + }) +} + +// fn as_cast(self) -> Option<(Expr, UnresolvedType)> +fn expr_as_cast( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(interner, arguments, return_type, location, |expr| { + if let ExprValue::Expression(ExpressionKind::Cast(cast)) = expr { + let lhs = Value::expression(cast.lhs.kind); + let typ = Value::UnresolvedType(cast.r#type.typ); + Some(Value::Tuple(vec![lhs, typ])) + } else { + None + } + }) +} + +// fn as_comptime(self) -> Option<[Expr]> +fn expr_as_comptime( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + use ExpressionKind::Block; + + expr_as(interner, arguments, return_type, location, |expr| { + if let ExprValue::Expression(ExpressionKind::Comptime(block_expr, _)) = expr { + Some(block_expression_to_value(block_expr)) + } else if let ExprValue::Statement(StatementKind::Comptime(statement)) = expr { + let typ = Type::Slice(Box::new(Type::Quoted(QuotedType::Expr))); + + // comptime { ... } as a statement wraps a block expression, + // and in that case we return the block expression statements + // (comptime as a statement can also be comptime for, but in that case we'll + // return the for statement as a single expression) + if let StatementKind::Expression(Expression { kind: Block(block), .. }) = statement.kind + { + Some(block_expression_to_value(block)) + } else { + let mut elements = Vector::new(); + elements.push_back(Value::statement(statement.kind)); + Some(Value::Slice(elements, typ)) + } + } else { + None + } + }) +} + // fn as_function_call(self) -> Option<(Expr, [Expr])> fn expr_as_function_call( + interner: &NodeInterner, arguments: Vec<(Value, Location)>, return_type: Type, location: Location, ) -> IResult { - expr_as(arguments, return_type, location, |expr| { - if let ExpressionKind::Call(call_expression) = expr { - let function = Value::Expr(call_expression.func.kind); + expr_as(interner, arguments, return_type, location, |expr| { + if let ExprValue::Expression(ExpressionKind::Call(call_expression)) = expr { + let function = Value::expression(call_expression.func.kind); let arguments = call_expression.arguments.into_iter(); - let arguments = arguments.map(|argument| Value::Expr(argument.kind)).collect(); + let arguments = arguments.map(|argument| Value::expression(argument.kind)).collect(); let arguments = Value::Slice(arguments, Type::Slice(Box::new(Type::Quoted(QuotedType::Expr)))); Some(Value::Tuple(vec![function, arguments])) @@ -708,22 +1019,365 @@ fn expr_as_function_call( }) } +// fn as_if(self) -> Option<(Expr, Expr, Option)> +fn expr_as_if( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(interner, arguments, return_type.clone(), location, |expr| { + if let ExprValue::Expression(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::expression(e.kind)), + ); + + Some(Value::Tuple(vec![ + Value::expression(if_expr.condition.kind), + Value::expression(if_expr.consequence.kind), + alternative.ok()?, + ])) + } else { + None + } + }) +} + +// fn as_index(self) -> Option +fn expr_as_index( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(interner, arguments, return_type, location, |expr| { + if let ExprValue::Expression(ExpressionKind::Index(index_expr)) = expr { + Some(Value::Tuple(vec![ + Value::expression(index_expr.collection.kind), + Value::expression(index_expr.index.kind), + ])) + } else { + None + } + }) +} + +// fn as_integer(self) -> Option<(Field, bool)> +fn expr_as_integer( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(interner, arguments, return_type.clone(), location, |expr| match expr { + ExprValue::Expression(ExpressionKind::Literal(Literal::Integer(field, sign))) => { + Some(Value::Tuple(vec![Value::Field(field), Value::Bool(sign)])) + } + ExprValue::Expression(ExpressionKind::Resolved(id)) => { + if let HirExpression::Literal(HirLiteral::Integer(field, sign)) = + interner.expression(&id) + { + Some(Value::Tuple(vec![Value::Field(field), Value::Bool(sign)])) + } else { + None + } + } + _ => None, + }) +} + +// fn as_member_access(self) -> Option<(Expr, Quoted)> +fn expr_as_member_access( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(interner, arguments, return_type, location, |expr| match expr { + ExprValue::Expression(ExpressionKind::MemberAccess(member_access)) => { + let tokens = Rc::new(vec![Token::Ident(member_access.rhs.0.contents.clone())]); + Some(Value::Tuple(vec![ + Value::expression(member_access.lhs.kind), + Value::Quoted(tokens), + ])) + } + ExprValue::LValue(crate::ast::LValue::MemberAccess { object, field_name, span: _ }) => { + let tokens = Rc::new(vec![Token::Ident(field_name.0.contents.clone())]); + Some(Value::Tuple(vec![Value::lvalue(*object), Value::Quoted(tokens)])) + } + _ => None, + }) +} + +// fn as_method_call(self) -> Option<(Expr, Quoted, [UnresolvedType], [Expr])> +fn expr_as_method_call( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(interner, arguments, return_type, location, |expr| { + if let ExprValue::Expression(ExpressionKind::MethodCall(method_call)) = expr { + let object = Value::expression(method_call.object.kind); + + let name_tokens = + Rc::new(vec![Token::Ident(method_call.method_name.0.contents.clone())]); + let name = Value::Quoted(name_tokens); + + let generics = method_call.generics.unwrap_or_default().into_iter(); + let generics = generics.map(|generic| Value::UnresolvedType(generic.typ)).collect(); + let generics = Value::Slice( + generics, + Type::Slice(Box::new(Type::Quoted(QuotedType::UnresolvedType))), + ); + + let arguments = method_call.arguments.into_iter(); + let arguments = arguments.map(|argument| Value::expression(argument.kind)).collect(); + let arguments = + Value::Slice(arguments, Type::Slice(Box::new(Type::Quoted(QuotedType::Expr)))); + + Some(Value::Tuple(vec![object, name, generics, arguments])) + } else { + None + } + }) +} + +// fn as_repeated_element_array(self) -> Option<(Expr, Expr)> +fn expr_as_repeated_element_array( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(interner, arguments, return_type, location, |expr| { + if let ExprValue::Expression(ExpressionKind::Literal(Literal::Array( + ArrayLiteral::Repeated { repeated_element, length }, + ))) = expr + { + Some(Value::Tuple(vec![ + Value::expression(repeated_element.kind), + Value::expression(length.kind), + ])) + } else { + None + } + }) +} + +// fn as_repeated_element_slice(self) -> Option<(Expr, Expr)> +fn expr_as_repeated_element_slice( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(interner, arguments, return_type, location, |expr| { + if let ExprValue::Expression(ExpressionKind::Literal(Literal::Slice( + ArrayLiteral::Repeated { repeated_element, length }, + ))) = expr + { + Some(Value::Tuple(vec![ + Value::expression(repeated_element.kind), + Value::expression(length.kind), + ])) + } else { + None + } + }) +} + +// fn as_slice(self) -> Option<[Expr]> +fn expr_as_slice( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(interner, arguments, return_type, location, |expr| { + if let ExprValue::Expression(ExpressionKind::Literal(Literal::Slice( + ArrayLiteral::Standard(exprs), + ))) = expr + { + let exprs = exprs.into_iter().map(|expr| Value::expression(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( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(interner, arguments, return_type, location, |expr| { + if let ExprValue::Expression(ExpressionKind::Tuple(expressions)) = expr { + let expressions = + expressions.into_iter().map(|expr| Value::expression(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( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(interner, arguments, return_type.clone(), location, |expr| { + if let ExprValue::Expression(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::expression(prefix_expr.rhs.kind); + Some(Value::Tuple(vec![unary_op, rhs])) + } else { + None + } + }) +} + +// fn as_unsafe(self) -> Option<[Expr]> +fn expr_as_unsafe( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(interner, arguments, return_type, location, |expr| { + if let ExprValue::Expression(ExpressionKind::Unsafe(block_expr, _)) = expr { + Some(block_expression_to_value(block_expr)) + } else { + None + } + }) +} + +// fn as_has_semicolon(self) -> bool +fn expr_has_semicolon( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let self_argument = check_one_argument(arguments, location)?; + let expr_value = get_expr(interner, self_argument)?; + Ok(Value::Bool(matches!(expr_value, ExprValue::Statement(StatementKind::Semi(..))))) +} + +// fn is_break(self) -> bool +fn expr_is_break( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let self_argument = check_one_argument(arguments, location)?; + let expr_value = get_expr(interner, self_argument)?; + Ok(Value::Bool(matches!(expr_value, ExprValue::Statement(StatementKind::Break)))) +} + +// fn is_continue(self) -> bool +fn expr_is_continue( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let self_argument = check_one_argument(arguments, location)?; + let expr_value = get_expr(interner, self_argument)?; + Ok(Value::Bool(matches!(expr_value, ExprValue::Statement(StatementKind::Continue)))) +} + // Helper function for implementing the `expr_as_...` functions. fn expr_as( + interner: &NodeInterner, arguments: Vec<(Value, Location)>, return_type: Type, location: Location, f: F, ) -> IResult where - F: FnOnce(ExpressionKind) -> Option, + F: FnOnce(ExprValue) -> Option, { let self_argument = check_one_argument(arguments, location)?; - let expr = get_expr(self_argument)?; - let option_value = f(expr); + let mut expr_value = get_expr(interner, self_argument)?; + loop { + match expr_value { + ExprValue::Expression(ExpressionKind::Parenthesized(expression)) => { + expr_value = ExprValue::Expression(expression.kind); + } + ExprValue::Statement(StatementKind::Expression(expression)) + | ExprValue::Statement(StatementKind::Semi(expression)) => { + expr_value = ExprValue::Expression(expression.kind); + } + ExprValue::Expression(ExpressionKind::Interned(id)) => { + expr_value = ExprValue::Expression(interner.get_expression_kind(id).clone()); + } + ExprValue::Statement(StatementKind::Interned(id)) => { + expr_value = ExprValue::Statement(interner.get_statement_kind(id).clone()); + } + ExprValue::LValue(LValue::Interned(id, span)) => { + expr_value = ExprValue::LValue(interner.get_lvalue(id, span).clone()); + } + _ => break, + } + } + + let option_value = f(expr_value); option(return_type, option_value) } +// fn body(self) -> Expr +fn function_def_body( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let self_argument = check_one_argument(arguments, location)?; + let func_id = get_function_def(self_argument)?; + let func_meta = interner.function_meta(&func_id); + if let FunctionBody::Unresolved(_, block_expr, _) = &func_meta.function_body { + Ok(Value::expression(ExpressionKind::Block(block_expr.clone()))) + } else { + Err(InterpreterError::FunctionAlreadyResolved { location }) + } +} + // fn name(self) -> Quoted fn function_def_name( interner: &NodeInterner, @@ -778,32 +1432,30 @@ fn function_def_return_type( Ok(Value::Type(func_meta.return_type().follow_bindings())) } -// fn set_body(self, body: Quoted) +// fn set_body(self, body: Expr) fn function_def_set_body( interpreter: &mut Interpreter, arguments: Vec<(Value, Location)>, location: Location, ) -> IResult { let (self_argument, body_argument) = check_two_arguments(arguments, location)?; - let body_argument_location = body_argument.1; + let body_location = body_argument.1; let func_id = get_function_def(self_argument)?; check_function_not_yet_resolved(interpreter, func_id, location)?; - let body_tokens = get_quoted(body_argument)?; - let mut body_quoted = add_token_spans(body_tokens.clone(), body_argument_location.span); - - // Surround the body in `{ ... }` so we can parse it as a block - body_quoted.0.insert(0, SpannedToken::new(Token::LeftBrace, location.span)); - body_quoted.0.push(SpannedToken::new(Token::RightBrace, location.span)); + let body_argument = get_expr(interpreter.elaborator.interner, body_argument)?; + let statement_kind = match body_argument { + ExprValue::Expression(expression_kind) => StatementKind::Expression(Expression { + kind: expression_kind, + span: body_location.span, + }), + ExprValue::Statement(statement_kind) => statement_kind, + ExprValue::LValue(lvalue) => StatementKind::Expression(lvalue.as_expression()), + }; - let body = parse_tokens( - body_tokens, - body_quoted, - body_argument_location, - parser::block(parser::fresh_statement()), - "a block", - )?; + let statement = Statement { kind: statement_kind, span: body_location.span }; + let body = BlockExpression { statements: vec![statement] }; let func_meta = interpreter.elaborator.interner.function_meta_mut(&func_id); func_meta.has_body = true; @@ -884,7 +1536,7 @@ fn function_def_set_return_type( mutate_func_meta_type(interpreter.elaborator.interner, func_id, |func_meta| { func_meta.return_type = FunctionReturnType::Ty(UnresolvedType { typ: UnresolvedTypeData::Resolved(quoted_type_id), - span: Some(location.span), + span: location.span, }); replace_func_meta_return_type(&mut func_meta.typ, return_type); }); @@ -1022,12 +1674,9 @@ fn trait_def_as_trait_constraint( let argument = check_one_argument(arguments, location)?; let trait_id = get_trait_def(argument)?; - let the_trait = interner.get_trait(trait_id); - let trait_generics = vecmap(&the_trait.generics, |generic| { - Type::NamedGeneric(generic.type_var.clone(), generic.name.clone(), generic.kind.clone()) - }); + let constraint = interner.get_trait(trait_id).as_constraint(location.span); - Ok(Value::TraitConstraint(trait_id, trait_generics)) + Ok(Value::TraitConstraint(trait_id, constraint.trait_generics)) } /// Creates a value that holds an `Option`. 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 56f6c11974f..2e06240e995 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 @@ -4,17 +4,25 @@ use acvm::FieldElement; use noirc_errors::Location; use crate::{ - ast::{ExpressionKind, IntegerBitSize, Signedness}, + ast::{ + BlockExpression, ExpressionKind, IntegerBitSize, LValue, Signedness, StatementKind, + UnresolvedTypeData, + }, hir::{ - comptime::{errors::IResult, value::add_token_spans, Interpreter, InterpreterError, Value}, + comptime::{ + errors::IResult, + value::{add_token_spans, ExprValue}, + Interpreter, InterpreterError, Value, + }, def_map::ModuleId, + type_check::generics::TraitGenerics, }, hir_def::{ function::{FuncMeta, FunctionBody}, stmt::HirPattern, }, macros_api::{NodeInterner, StructId}, - node_interner::{FuncId, TraitId}, + node_interner::{FuncId, TraitId, TraitImplId}, parser::NoirParser, token::{Token, Tokens}, QuotedType, Type, @@ -77,8 +85,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 +99,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 +113,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 +121,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 +130,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,102 +140,122 @@ 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) } } } -pub(crate) fn get_expr((value, location): (Value, Location)) -> IResult { +pub(crate) fn get_u64((value, location): (Value, Location)) -> IResult { match value { - Value::Expr(expr) => Ok(expr), + Value::U64(value) => Ok(value), value => { - let expected = Type::Quoted(QuotedType::Expr); - let actual = value.get_type().into_owned(); - Err(InterpreterError::TypeMismatch { expected, actual, location }) + let expected = Type::Integer(Signedness::Unsigned, IntegerBitSize::SixtyFour); + type_mismatch(value, expected, location) } } } +pub(crate) fn get_expr( + interner: &NodeInterner, + (value, location): (Value, Location), +) -> IResult { + match value { + Value::Expr(expr) => match expr { + ExprValue::Expression(ExpressionKind::Interned(id)) => { + Ok(ExprValue::Expression(interner.get_expression_kind(id).clone())) + } + ExprValue::Statement(StatementKind::Interned(id)) => { + Ok(ExprValue::Statement(interner.get_statement_kind(id).clone())) + } + ExprValue::LValue(LValue::Interned(id, _)) => { + Ok(ExprValue::LValue(interner.get_lvalue(id, location.span).clone())) + } + _ => Ok(expr), + }, + 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), } } pub(crate) fn get_trait_constraint( (value, location): (Value, Location), -) -> IResult<(TraitId, Vec)> { +) -> IResult<(TraitId, TraitGenerics)> { 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), + } +} + +pub(crate) fn get_unresolved_type( + interner: &NodeInterner, + (value, location): (Value, Location), +) -> IResult { + match value { + Value::UnresolvedType(typ) => { + if let UnresolvedTypeData::Interned(id) = typ { + let typ = interner.get_unresolved_type_data(id).clone(); + Ok(typ) + } else { + Ok(typ) + } } + value => type_mismatch(value, Type::Quoted(QuotedType::UnresolvedType), 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 +378,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,10 +388,18 @@ 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), _ => {} } } + +pub(super) fn block_expression_to_value(block_expr: BlockExpression) -> Value { + let typ = Type::Slice(Box::new(Type::Quoted(QuotedType::Expr))); + let statements = block_expr.statements.into_iter(); + let statements = statements.map(|statement| Value::statement(statement.kind)).collect(); + + Value::Slice(statements, typ) +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/foreign.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/foreign.rs index f7caf84ec42..5ae60bb4d00 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/foreign.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/foreign.rs @@ -1,5 +1,6 @@ -use acvm::BlackBoxFunctionSolver; +use acvm::blackbox_solver::BlackBoxFunctionSolver; use bn254_blackbox_solver::Bn254BlackBoxSolver; +use im::Vector; use iter_extended::try_vecmap; use noirc_errors::Location; @@ -8,7 +9,9 @@ use crate::{ macros_api::NodeInterner, }; -use super::builtin::builtin_helpers::{check_two_arguments, get_array, get_field, get_u32}; +use super::builtin::builtin_helpers::{ + check_one_argument, check_two_arguments, get_array, get_field, get_u32, get_u64, +}; pub(super) fn call_foreign( interner: &mut NodeInterner, @@ -18,6 +21,7 @@ pub(super) fn call_foreign( ) -> IResult { match name { "poseidon2_permutation" => poseidon2_permutation(interner, arguments, location), + "keccakf1600" => keccakf1600(interner, arguments, location), _ => { let item = format!("Comptime evaluation for builtin function {name}"); Err(InterpreterError::Unimplemented { item, location }) @@ -47,3 +51,26 @@ fn poseidon2_permutation( let array = fields.into_iter().map(Value::Field).collect(); Ok(Value::Array(array, typ)) } + +fn keccakf1600( + interner: &mut NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let input = check_one_argument(arguments, location)?; + let input_location = input.1; + + let (input, typ) = get_array(interner, input)?; + + let input = try_vecmap(input, |integer| get_u64((integer, input_location)))?; + + let mut state = [0u64; 25]; + for (it, input_value) in state.iter_mut().zip(input.iter()) { + *it = *input_value; + } + let result_lanes = acvm::blackbox_solver::keccakf1600(state) + .map_err(|error| InterpreterError::BlackBoxError(error, location))?; + + let array: Vector = result_lanes.into_iter().map(Value::U64).collect(); + Ok(Value::Array(array, typ)) +} 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 d5408309e55..5b4875c8c41 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 @@ -5,16 +5,26 @@ use chumsky::Parser; use im::Vector; use iter_extended::{try_vecmap, vecmap}; use noirc_errors::{Location, Span}; +use strum_macros::Display; use crate::{ - ast::{ArrayLiteral, ConstructorExpression, Ident, IntegerBitSize, Signedness}, - hir::def_map::ModuleId, - hir_def::expr::{HirArrayLiteral, HirConstructorExpression, HirIdent, HirLambda, ImplKind}, + ast::{ + ArrayLiteral, AssignStatement, BlockExpression, CallExpression, CastExpression, + ConstrainStatement, ConstructorExpression, ForLoopStatement, ForRange, Ident, IfExpression, + IndexExpression, InfixExpression, IntegerBitSize, LValue, Lambda, LetStatement, + MemberAccessExpression, MethodCallExpression, PrefixExpression, Signedness, Statement, + StatementKind, UnresolvedTypeData, + }, + hir::{def_map::ModuleId, type_check::generics::TraitGenerics}, + 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, @@ -51,16 +61,37 @@ pub enum Value { /// be inserted into separate files entirely. Quoted(Rc>), StructDefinition(StructId), - TraitConstraint(TraitId, /* trait generics */ Vec), + TraitConstraint(TraitId, TraitGenerics), TraitDefinition(TraitId), + TraitImpl(TraitImplId), FunctionDefinition(FuncId), ModuleDefinition(ModuleId), Type(Type), Zeroed(Type), - Expr(ExpressionKind), + Expr(ExprValue), + UnresolvedType(UnresolvedTypeData), +} + +#[derive(Debug, Clone, PartialEq, Eq, Display)] +pub enum ExprValue { + Expression(ExpressionKind), + Statement(StatementKind), + LValue(LValue), } impl Value { + pub(crate) fn expression(expr: ExpressionKind) -> Self { + Value::Expr(ExprValue::Expression(expr)) + } + + pub(crate) fn statement(statement: StatementKind) -> Self { + Value::Expr(ExprValue::Statement(statement)) + } + + pub(crate) fn lvalue(lvaue: LValue) -> Self { + Value::Expr(ExprValue::LValue(lvaue)) + } + pub(crate) fn get_type(&self) -> Cow { Cow::Owned(match self { Value::Unit => Type::Unit, @@ -100,11 +131,13 @@ 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), Value::Zeroed(typ) => return Cow::Borrowed(typ), Value::Expr(_) => Type::Quoted(QuotedType::Expr), + Value::UnresolvedType(_) => Type::Quoted(QuotedType::UnresolvedType), }) } @@ -225,14 +258,22 @@ impl Value { } }; } - Value::Expr(expr) => expr, - Value::Pointer(..) + Value::Expr(ExprValue::Expression(expr)) => expr, + Value::Expr(ExprValue::Statement(statement)) => { + ExpressionKind::Block(BlockExpression { + statements: vec![Statement { kind: statement, span: location.span }], + }) + } + Value::Expr(ExprValue::LValue(_)) + | Value::Pointer(..) | Value::StructDefinition(_) | Value::TraitConstraint(..) | Value::TraitDefinition(_) + | Value::TraitImpl(_) | Value::FunctionDefinition(_) | Value::Zeroed(_) | Value::Type(_) + | Value::UnresolvedType(_) | Value::ModuleDefinition(_) => { let typ = self.get_type().into_owned(); let value = self.display(interner).to_string(); @@ -353,9 +394,11 @@ impl Value { | Value::StructDefinition(_) | Value::TraitConstraint(..) | Value::TraitDefinition(_) + | Value::TraitImpl(_) | Value::FunctionDefinition(_) | Value::Zeroed(_) | Value::Type(_) + | Value::UnresolvedType(_) | Value::ModuleDefinition(_) => { let typ = self.get_type().into_owned(); let value = self.display(interner).to_string(); @@ -377,6 +420,18 @@ impl Value { let token = match self { Value::Quoted(tokens) => return Ok(unwrap_rc(tokens)), Value::Type(typ) => Token::QuotedType(interner.push_quoted_type(typ)), + Value::Expr(ExprValue::Expression(expr)) => { + Token::InternedExpr(interner.push_expression_kind(expr)) + } + Value::Expr(ExprValue::Statement(statement)) => { + Token::InternedStatement(interner.push_statement_kind(statement)) + } + Value::Expr(ExprValue::LValue(lvalue)) => { + Token::InternedLValue(interner.push_lvalue(lvalue)) + } + Value::UnresolvedType(typ) => { + Token::InternedUnresolvedTypeData(interner.push_unresolved_type_data(typ)) + } other => Token::UnquoteMarker(other.into_hir_expression(interner, location)?), }; Ok(vec![token]) @@ -517,24 +572,292 @@ impl<'value, 'interner> Display for ValuePrinter<'value, 'interner> { } 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, "{}{generics}", trait_.name) } 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)) } Value::ModuleDefinition(_) => write!(f, "(module)"), Value::Zeroed(typ) => write!(f, "(zeroed {typ})"), Value::Type(typ) => write!(f, "{}", typ), - Value::Expr(expr) => write!(f, "{}", expr), + Value::Expr(ExprValue::Expression(expr)) => { + write!(f, "{}", remove_interned_in_expression_kind(self.interner, expr.clone())) + } + Value::Expr(ExprValue::Statement(statement)) => { + write!(f, "{}", remove_interned_in_statement_kind(self.interner, statement.clone())) + } + Value::Expr(ExprValue::LValue(lvalue)) => { + write!(f, "{}", remove_interned_in_lvalue(self.interner, lvalue.clone())) + } + Value::UnresolvedType(typ) => { + if let UnresolvedTypeData::Interned(id) = typ { + let typ = self.interner.get_unresolved_type_data(*id); + write!(f, "{}", typ) + } else { + write!(f, "{}", typ) + } + } + } + } +} + +fn display_trait_constraint(interner: &NodeInterner, trait_constraint: &TraitConstraint) -> String { + let trait_ = interner.get_trait(trait_constraint.trait_id); + format!("{}: {}{}", trait_constraint.typ, trait_.name, trait_constraint.trait_generics) +} + +// Returns a new Expression where all Interned and Resolved expressions have been turned into non-interned ExpressionKind. +fn remove_interned_in_expression(interner: &NodeInterner, expr: Expression) -> Expression { + Expression { kind: remove_interned_in_expression_kind(interner, expr.kind), span: expr.span } +} + +// Returns a new ExpressionKind where all Interned and Resolved expressions have been turned into non-interned ExpressionKind. +fn remove_interned_in_expression_kind( + interner: &NodeInterner, + expr: ExpressionKind, +) -> ExpressionKind { + match expr { + ExpressionKind::Literal(literal) => { + ExpressionKind::Literal(remove_interned_in_literal(interner, literal)) + } + ExpressionKind::Block(block) => { + let statements = + vecmap(block.statements, |stmt| remove_interned_in_statement(interner, stmt)); + ExpressionKind::Block(BlockExpression { statements }) + } + ExpressionKind::Prefix(prefix) => ExpressionKind::Prefix(Box::new(PrefixExpression { + rhs: remove_interned_in_expression(interner, prefix.rhs), + ..*prefix + })), + ExpressionKind::Index(index) => ExpressionKind::Index(Box::new(IndexExpression { + collection: remove_interned_in_expression(interner, index.collection), + index: remove_interned_in_expression(interner, index.index), + })), + ExpressionKind::Call(call) => ExpressionKind::Call(Box::new(CallExpression { + func: Box::new(remove_interned_in_expression(interner, *call.func)), + arguments: vecmap(call.arguments, |arg| remove_interned_in_expression(interner, arg)), + ..*call + })), + ExpressionKind::MethodCall(call) => { + ExpressionKind::MethodCall(Box::new(MethodCallExpression { + object: remove_interned_in_expression(interner, call.object), + arguments: vecmap(call.arguments, |arg| { + remove_interned_in_expression(interner, arg) + }), + ..*call + })) + } + ExpressionKind::Constructor(constructor) => { + ExpressionKind::Constructor(Box::new(ConstructorExpression { + fields: vecmap(constructor.fields, |(name, expr)| { + (name, remove_interned_in_expression(interner, expr)) + }), + ..*constructor + })) + } + ExpressionKind::MemberAccess(member_access) => { + ExpressionKind::MemberAccess(Box::new(MemberAccessExpression { + lhs: remove_interned_in_expression(interner, member_access.lhs), + ..*member_access + })) + } + ExpressionKind::Cast(cast) => ExpressionKind::Cast(Box::new(CastExpression { + lhs: remove_interned_in_expression(interner, cast.lhs), + ..*cast + })), + ExpressionKind::Infix(infix) => ExpressionKind::Infix(Box::new(InfixExpression { + lhs: remove_interned_in_expression(interner, infix.lhs), + rhs: remove_interned_in_expression(interner, infix.rhs), + ..*infix + })), + ExpressionKind::If(if_expr) => ExpressionKind::If(Box::new(IfExpression { + condition: remove_interned_in_expression(interner, if_expr.condition), + consequence: remove_interned_in_expression(interner, if_expr.consequence), + alternative: if_expr + .alternative + .map(|alternative| remove_interned_in_expression(interner, alternative)), + })), + ExpressionKind::Variable(_) => expr, + ExpressionKind::Tuple(expressions) => ExpressionKind::Tuple(vecmap(expressions, |expr| { + remove_interned_in_expression(interner, expr) + })), + ExpressionKind::Lambda(lambda) => ExpressionKind::Lambda(Box::new(Lambda { + body: remove_interned_in_expression(interner, lambda.body), + ..*lambda + })), + ExpressionKind::Parenthesized(expr) => { + ExpressionKind::Parenthesized(Box::new(remove_interned_in_expression(interner, *expr))) + } + ExpressionKind::Quote(_) => expr, + ExpressionKind::Unquote(expr) => { + ExpressionKind::Unquote(Box::new(remove_interned_in_expression(interner, *expr))) + } + ExpressionKind::Comptime(block, span) => { + let statements = + vecmap(block.statements, |stmt| remove_interned_in_statement(interner, stmt)); + ExpressionKind::Comptime(BlockExpression { statements }, span) + } + ExpressionKind::Unsafe(block, span) => { + let statements = + vecmap(block.statements, |stmt| remove_interned_in_statement(interner, stmt)); + ExpressionKind::Unsafe(BlockExpression { statements }, span) + } + ExpressionKind::AsTraitPath(_) => expr, + ExpressionKind::Resolved(id) => { + let expr = interner.expression(&id); + expr.to_display_ast(interner, Span::default()).kind + } + ExpressionKind::Interned(id) => { + let expr = interner.get_expression_kind(id).clone(); + remove_interned_in_expression_kind(interner, expr) + } + ExpressionKind::Error => expr, + } +} + +fn remove_interned_in_literal(interner: &NodeInterner, literal: Literal) -> Literal { + match literal { + Literal::Array(array_literal) => { + Literal::Array(remove_interned_in_array_literal(interner, array_literal)) + } + Literal::Slice(array_literal) => { + Literal::Array(remove_interned_in_array_literal(interner, array_literal)) + } + Literal::Bool(_) + | Literal::Integer(_, _) + | Literal::Str(_) + | Literal::RawStr(_, _) + | Literal::FmtStr(_) + | Literal::Unit => literal, + } +} + +fn remove_interned_in_array_literal( + interner: &NodeInterner, + literal: ArrayLiteral, +) -> ArrayLiteral { + match literal { + ArrayLiteral::Standard(expressions) => { + ArrayLiteral::Standard(vecmap(expressions, |expr| { + remove_interned_in_expression(interner, expr) + })) + } + ArrayLiteral::Repeated { repeated_element, length } => ArrayLiteral::Repeated { + repeated_element: Box::new(remove_interned_in_expression(interner, *repeated_element)), + length: Box::new(remove_interned_in_expression(interner, *length)), + }, + } +} + +// Returns a new Statement where all Interned statements have been turned into non-interned StatementKind. +fn remove_interned_in_statement(interner: &NodeInterner, statement: Statement) -> Statement { + Statement { + kind: remove_interned_in_statement_kind(interner, statement.kind), + span: statement.span, + } +} + +// Returns a new StatementKind where all Interned statements have been turned into non-interned StatementKind. +fn remove_interned_in_statement_kind( + interner: &NodeInterner, + statement: StatementKind, +) -> StatementKind { + match statement { + StatementKind::Let(let_statement) => StatementKind::Let(LetStatement { + expression: remove_interned_in_expression(interner, let_statement.expression), + ..let_statement + }), + StatementKind::Constrain(constrain) => StatementKind::Constrain(ConstrainStatement( + remove_interned_in_expression(interner, constrain.0), + constrain.1.map(|expr| remove_interned_in_expression(interner, expr)), + constrain.2, + )), + StatementKind::Expression(expr) => { + StatementKind::Expression(remove_interned_in_expression(interner, expr)) + } + StatementKind::Assign(assign) => StatementKind::Assign(AssignStatement { + lvalue: assign.lvalue, + expression: remove_interned_in_expression(interner, assign.expression), + }), + StatementKind::For(for_loop) => StatementKind::For(ForLoopStatement { + range: match for_loop.range { + ForRange::Range(from, to) => ForRange::Range( + remove_interned_in_expression(interner, from), + remove_interned_in_expression(interner, to), + ), + ForRange::Array(expr) => { + ForRange::Array(remove_interned_in_expression(interner, expr)) + } + }, + block: remove_interned_in_expression(interner, for_loop.block), + ..for_loop + }), + StatementKind::Comptime(statement) => { + StatementKind::Comptime(Box::new(remove_interned_in_statement(interner, *statement))) + } + StatementKind::Semi(expr) => { + StatementKind::Semi(remove_interned_in_expression(interner, expr)) + } + StatementKind::Interned(id) => { + let statement = interner.get_statement_kind(id).clone(); + remove_interned_in_statement_kind(interner, statement) + } + StatementKind::Break | StatementKind::Continue | StatementKind::Error => statement, + } +} + +// Returns a new LValue where all Interned LValues have been turned into LValue. +fn remove_interned_in_lvalue(interner: &NodeInterner, lvalue: LValue) -> LValue { + match lvalue { + LValue::Ident(_) => lvalue, + LValue::MemberAccess { object, field_name, span } => LValue::MemberAccess { + object: Box::new(remove_interned_in_lvalue(interner, *object)), + field_name, + span, + }, + LValue::Index { array, index, span } => LValue::Index { + array: Box::new(remove_interned_in_lvalue(interner, *array)), + index: remove_interned_in_expression(interner, index), + span, + }, + LValue::Dereference(lvalue, span) => { + LValue::Dereference(Box::new(remove_interned_in_lvalue(interner, *lvalue)), span) + } + LValue::Interned(id, span) => { + let lvalue = interner.get_lvalue(id, span); + remove_interned_in_lvalue(interner, lvalue) } } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index fabd76a2818..30c91b42b2e 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -12,15 +12,16 @@ use crate::{Generics, Type}; use crate::hir::resolution::import::{resolve_import, ImportDirective, PathResolution}; use crate::hir::Context; -use crate::macros_api::{MacroError, MacroProcessor}; +use crate::macros_api::{Expression, MacroError, MacroProcessor}; use crate::node_interner::{ - FuncId, GlobalId, NodeInterner, ReferenceId, StructId, TraitId, TraitImplId, TypeAliasId, + FuncId, GlobalId, ModuleAttributes, NodeInterner, ReferenceId, StructId, TraitId, TraitImplId, + TypeAliasId, }; use crate::ast::{ - ExpressionKind, Ident, LetStatement, Literal, NoirFunction, NoirStruct, NoirTrait, - NoirTypeAlias, Path, PathKind, PathSegment, UnresolvedGenerics, UnresolvedTraitConstraint, - UnresolvedType, + ExpressionKind, GenericTypeArgs, Ident, LetStatement, Literal, NoirFunction, NoirStruct, + NoirTrait, NoirTypeAlias, Path, PathKind, PathSegment, UnresolvedGenerics, + UnresolvedTraitConstraint, UnresolvedType, }; use crate::parser::{ParserError, SortedModule}; @@ -74,13 +75,16 @@ pub struct UnresolvedTrait { pub struct UnresolvedTraitImpl { pub file_id: FileId, pub module_id: LocalModuleId, - pub trait_generics: Vec, + pub trait_generics: GenericTypeArgs, pub trait_path: Path, pub object_type: UnresolvedType, pub methods: UnresolvedFunctions, pub generics: UnresolvedGenerics, pub where_clause: Vec, + pub associated_types: Vec<(Ident, UnresolvedType)>, + pub associated_constants: Vec<(Ident, UnresolvedType, Expression)>, + // Every field after this line is filled in later in the elaborator pub trait_id: Option, pub impl_id: Option, @@ -241,6 +245,7 @@ impl DefCollector { /// Collect all of the definitions in a given crate into a CrateDefMap /// Modules which are not a part of the module hierarchy starting with /// the root module, will be ignored. + #[allow(clippy::too_many_arguments)] pub fn collect_crate_and_dependencies( mut def_map: CrateDefMap, context: &mut Context, @@ -248,6 +253,7 @@ impl DefCollector { root_file_id: FileId, debug_comptime_in_file: Option<&str>, enable_arithmetic_generics: bool, + error_on_unused_imports: bool, macro_processors: &[&dyn MacroProcessor], ) -> Vec<(CompilationError, FileId)> { let mut errors: Vec<(CompilationError, FileId)> = vec![]; @@ -261,19 +267,27 @@ impl DefCollector { let crate_graph = &context.crate_graph[crate_id]; for dep in crate_graph.dependencies.clone() { + let error_on_unused_imports = false; errors.extend(CrateDefMap::collect_defs( dep.crate_id, context, debug_comptime_in_file, enable_arithmetic_generics, + error_on_unused_imports, macro_processors, )); - let dep_def_root = - context.def_map(&dep.crate_id).expect("ice: def map was just created").root; + let dep_def_map = + context.def_map(&dep.crate_id).expect("ice: def map was just created"); + + let dep_def_root = dep_def_map.root; let module_id = ModuleId { krate: dep.crate_id, local_id: dep_def_root }; // Add this crate as a dependency by linking it's root module def_map.extern_prelude.insert(dep.as_name(), module_id); + + let location = dep_def_map[dep_def_root].location; + let attriutes = ModuleAttributes { name: dep.as_name(), location, parent: None }; + context.def_interner.add_module_attributes(module_id, attriutes); } // At this point, all dependencies are resolved and type checked. @@ -309,7 +323,7 @@ impl DefCollector { for collected_import in std::mem::take(&mut def_collector.imports) { let module_id = collected_import.module_id; let resolved_import = if context.def_interner.lsp_mode { - let mut references: Vec> = Vec::new(); + let mut references: Vec = Vec::new(); let resolved_import = resolve_import( crate_id, &collected_import, @@ -322,9 +336,6 @@ impl DefCollector { for (referenced, segment) in references.iter().zip(&collected_import.path.segments) { - let Some(referenced) = referenced else { - continue; - }; context.def_interner.add_reference( *referenced, Location::new(segment.ident.span(), file_id), @@ -406,8 +417,26 @@ impl DefCollector { ); } + if error_on_unused_imports { + Self::check_unused_imports(context, crate_id, &mut errors); + } + errors } + + fn check_unused_imports( + context: &Context, + crate_id: CrateId, + errors: &mut Vec<(CompilationError, FileId)>, + ) { + errors.extend(context.def_maps[&crate_id].modules().iter().flat_map(|(_, module)| { + module.unused_imports().iter().map(|ident| { + let ident = ident.clone(); + let error = CompilationError::ResolverError(ResolverError::UnusedImport { ident }); + (error, module.location.file) + }) + })); + } } fn add_import_reference( 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 be2afd13507..459c4869379 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 @@ -1,4 +1,5 @@ use std::path::Path; +use std::rc::Rc; use std::vec; use acvm::{AcirField, FieldElement}; @@ -13,8 +14,9 @@ use crate::ast::{ NoirStruct, NoirTrait, NoirTraitImpl, NoirTypeAlias, Pattern, TraitImplItem, TraitItem, TypeImpl, }; -use crate::macros_api::NodeInterner; -use crate::node_interner::{ModuleAttributes, ReferenceId}; +use crate::hir::resolution::errors::ResolverError; +use crate::macros_api::{Expression, NodeInterner, UnresolvedType, UnresolvedTypeData}; +use crate::node_interner::ModuleAttributes; use crate::{ graph::CrateId, hir::def_collector::dc_crate::{UnresolvedStruct, UnresolvedTrait}, @@ -22,6 +24,7 @@ use crate::{ node_interner::{FunctionModifiers, TraitId, TypeAliasId}, parser::{SortedModule, SortedSubModule}, }; +use crate::{Generics, Kind, ResolvedGeneric, Type, TypeVariable}; use super::{ dc_crate::{ @@ -162,13 +165,14 @@ impl<'a> ModCollector<'a> { for mut trait_impl in impls { let trait_name = trait_impl.trait_name.clone(); - let mut unresolved_functions = collect_trait_impl_functions( - &mut context.def_interner, - &mut trait_impl, - krate, - self.file_id, - self.module_id, - ); + let (mut unresolved_functions, associated_types, associated_constants) = + collect_trait_impl_items( + &mut context.def_interner, + &mut trait_impl, + krate, + self.file_id, + self.module_id, + ); let module = ModuleId { krate, local_id: self.module_id }; @@ -186,6 +190,8 @@ impl<'a> ModCollector<'a> { generics: trait_impl.impl_generics, where_clause: trait_impl.where_clause, trait_generics: trait_impl.trait_generics, + associated_constants, + associated_types, // These last fields are filled later on trait_id: None, @@ -232,6 +238,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 +320,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 +335,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 +396,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 +409,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 +446,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 { @@ -453,6 +467,8 @@ impl<'a> ModCollector<'a> { }; let mut method_ids = HashMap::default(); + let mut associated_types = Generics::new(); + for trait_item in &trait_definition.items { match trait_item { TraitItem::Function { @@ -513,7 +529,7 @@ impl<'a> ModCollector<'a> { } } } - TraitItem::Constant { name, .. } => { + TraitItem::Constant { name, typ, default_value: _ } => { let global_id = context.def_interner.push_empty_global( name.clone(), trait_id.0.local_id, @@ -534,10 +550,19 @@ impl<'a> ModCollector<'a> { second_def, }; errors.push((error.into(), self.file_id)); + } else { + let type_variable_id = context.def_interner.next_type_variable_id(); + let typ = self.resolve_associated_constant_type(typ, &mut errors); + + associated_types.push(ResolvedGeneric { + name: Rc::new(name.to_string()), + type_var: TypeVariable::unbound(type_variable_id), + kind: Kind::Numeric(Box::new(typ)), + span: name.span(), + }); } } TraitItem::Type { name } => { - // TODO(nickysn or alexvitkov): implement context.def_interner.push_empty_type_alias and get an id, instead of using TypeAliasId::dummy_id() if let Err((first_def, second_def)) = self.def_collector.def_map.modules [trait_id.0.local_id.0] .declare_type_alias(name.clone(), TypeAliasId::dummy_id()) @@ -548,6 +573,14 @@ impl<'a> ModCollector<'a> { second_def, }; errors.push((error.into(), self.file_id)); + } else { + let type_variable_id = context.def_interner.next_type_variable_id(); + associated_types.push(ResolvedGeneric { + name: Rc::new(name.to_string()), + type_var: TypeVariable::unbound(type_variable_id), + kind: Kind::Normal, + span: name.span(), + }); } } } @@ -556,7 +589,6 @@ impl<'a> ModCollector<'a> { let resolved_generics = context.resolve_generics(&trait_definition.generics, &mut errors, self.file_id); - // And store the TraitId -> TraitType mapping somewhere it is reachable let unresolved = UnresolvedTrait { file_id: self.file_id, module_id: self.module_id, @@ -565,13 +597,18 @@ impl<'a> ModCollector<'a> { method_ids, fns_with_default_impl: unresolved_functions, }; - 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 }), + context.def_interner.push_empty_trait( + trait_id, + &unresolved, + resolved_generics, + associated_types, ); + 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); } errors @@ -763,13 +800,34 @@ impl<'a> ModCollector<'a> { ModuleAttributes { name: mod_name.0.contents.clone(), location: mod_location, - parent: self.module_id, + parent: Some(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) } + + fn resolve_associated_constant_type( + &self, + typ: &UnresolvedType, + errors: &mut Vec<(CompilationError, FileId)>, + ) -> Type { + match &typ.typ { + UnresolvedTypeData::FieldElement => Type::FieldElement, + UnresolvedTypeData::Integer(sign, bits) => Type::Integer(*sign, *bits), + _ => { + let span = typ.span; + let error = ResolverError::AssociatedConstantsMustBeNumeric { span }; + errors.push((error.into(), self.file_id)); + Type::Error + } + } + } } fn find_module( @@ -858,28 +916,43 @@ fn is_native_field(str: &str) -> bool { } } -pub(crate) fn collect_trait_impl_functions( +type AssociatedTypes = Vec<(Ident, UnresolvedType)>; +type AssociatedConstants = Vec<(Ident, UnresolvedType, Expression)>; + +/// Returns a tuple of (methods, associated types, associated constants) +pub(crate) fn collect_trait_impl_items( interner: &mut NodeInterner, trait_impl: &mut NoirTraitImpl, krate: CrateId, file_id: FileId, local_id: LocalModuleId, -) -> UnresolvedFunctions { +) -> (UnresolvedFunctions, AssociatedTypes, AssociatedConstants) { let mut unresolved_functions = UnresolvedFunctions { file_id, functions: Vec::new(), trait_id: None, self_type: None }; + let mut associated_types = Vec::new(); + let mut associated_constants = Vec::new(); + let module = ModuleId { krate, local_id }; for item in std::mem::take(&mut trait_impl.items) { - if let TraitImplItem::Function(impl_method) = item { - let func_id = interner.push_empty_fn(); - let location = Location::new(impl_method.span(), file_id); - interner.push_function(func_id, &impl_method.def, module, location); - unresolved_functions.push_fn(local_id, func_id, impl_method); + match item { + TraitImplItem::Function(impl_method) => { + let func_id = interner.push_empty_fn(); + let location = Location::new(impl_method.span(), file_id); + interner.push_function(func_id, &impl_method.def, module, location); + unresolved_functions.push_fn(local_id, func_id, impl_method); + } + TraitImplItem::Constant(name, typ, expr) => { + associated_constants.push((name, typ, expr)); + } + TraitImplItem::Type { name, alias } => { + associated_types.push((name, alias)); + } } } - unresolved_functions + (unresolved_functions, associated_types, associated_constants) } pub(crate) fn collect_global( diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/errors.rs index 9e9471c0cb3..e705d7b6fad 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/errors.rs @@ -1,5 +1,6 @@ use crate::ast::{Ident, Path, UnresolvedTypeData}; use crate::hir::resolution::import::PathResolutionError; +use crate::hir::type_check::generics::TraitGenerics; use noirc_errors::CustomDiagnostic as Diagnostic; use noirc_errors::FileDiagnostic; @@ -76,7 +77,7 @@ pub enum DefCollectorErrorKind { ImplIsStricterThanTrait { constraint_typ: crate::Type, constraint_name: String, - constraint_generics: Vec, + constraint_generics: TraitGenerics, constraint_span: Span, trait_method_name: String, trait_method_span: Span, @@ -280,18 +281,11 @@ impl<'a> From<&'a DefCollectorErrorKind> for Diagnostic { ) } DefCollectorErrorKind::ImplIsStricterThanTrait { constraint_typ, constraint_name, constraint_generics, constraint_span, trait_method_name, trait_method_span } => { - let mut constraint_name_with_generics = constraint_name.to_owned(); - if !constraint_generics.is_empty() { - constraint_name_with_generics.push('<'); - for generic in constraint_generics.iter() { - constraint_name_with_generics.push_str(generic.to_string().as_str()); - } - constraint_name_with_generics.push('>'); - } + let constraint = format!("{}{}", constraint_name, constraint_generics); let mut diag = Diagnostic::simple_error( "impl has stricter requirements than trait".to_string(), - format!("impl has extra requirement `{constraint_typ}: {constraint_name_with_generics}`"), + format!("impl has extra requirement `{constraint_typ}: {constraint}`"), *constraint_span, ); diag.add_secondary(format!("definition of `{trait_method_name}` from trait"), *trait_method_span); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/mod.rs index e607de52ff1..758b4cf6e5c 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/mod.rs @@ -77,6 +77,7 @@ impl CrateDefMap { context: &mut Context, debug_comptime_in_file: Option<&str>, enable_arithmetic_generics: bool, + error_on_unused_imports: bool, macro_processors: &[&dyn MacroProcessor], ) -> Vec<(CompilationError, FileId)> { // Check if this Crate has already been compiled @@ -127,12 +128,14 @@ impl CrateDefMap { root_file_id, debug_comptime_in_file, enable_arithmetic_generics, + error_on_unused_imports, macro_processors, )); errors.extend( parsing_errors.iter().map(|e| (e.clone().into(), root_file_id)).collect::>(), ); + errors } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/module_data.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/module_data.rs index 8a0125cfe95..7b14db8be77 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/module_data.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/module_data.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use noirc_errors::Location; @@ -24,6 +24,10 @@ pub struct ModuleData { /// True if this module is a `contract Foo { ... }` module containing contract functions pub is_contract: bool, + + /// List of all unused imports. Each time something is imported into this module it's added + /// to this set. When it's used, it's removed. At the end of the program only unused imports remain. + unused_imports: HashSet, } impl ModuleData { @@ -35,6 +39,7 @@ impl ModuleData { definitions: ItemScope::default(), location, is_contract, + unused_imports: HashSet::new(), } } @@ -121,6 +126,11 @@ impl ModuleData { id: ModuleDefId, is_prelude: bool, ) -> Result<(), (Ident, Ident)> { + // Empty spans could come from implicitly injected imports, and we don't want to track those + if name.span().start() < name.span().end() { + self.unused_imports.insert(name.clone()); + } + self.scope.add_item_to_namespace(name, ItemVisibility::Public, id, None, is_prelude) } @@ -137,4 +147,14 @@ impl ModuleData { pub fn value_definitions(&self) -> impl Iterator + '_ { self.definitions.values().values().flat_map(|a| a.values().map(|(id, _, _)| *id)) } + + /// Marks an ident as being used by an import. + pub fn use_import(&mut self, ident: &Ident) { + self.unused_imports.remove(ident); + } + + /// Returns the list of all unused imports at this moment. + pub fn unused_imports(&self) -> &HashSet { + &self.unused_imports + } } 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 54d092f9515..a487bda81b3 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/resolution/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/errors.rs index cfaa2063c40..0b0d8d735eb 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/errors.rs @@ -20,6 +20,8 @@ pub enum ResolverError { DuplicateDefinition { name: String, first_span: Span, second_span: Span }, #[error("Unused variable")] UnusedVariable { ident: Ident }, + #[error("Unused import")] + UnusedImport { ident: Ident }, #[error("Could not find variable in this scope")] VariableNotDeclared { name: String, span: Span }, #[error("path is not an identifier")] @@ -58,8 +60,8 @@ pub enum ResolverError { NonStructWithGenerics { span: Span }, #[error("Cannot apply generics on Self type")] GenericsOnSelfType { span: Span }, - #[error("Incorrect amount of arguments to {item_name}")] - IncorrectGenericCount { span: Span, item_name: String, actual: usize, expected: usize }, + #[error("Cannot apply generics on an associated type")] + GenericsOnAssociatedType { span: Span }, #[error("{0}")] ParserError(Box), #[error("Cannot create a mutable reference to {variable}, it was declared to be immutable")] @@ -116,6 +118,12 @@ pub enum ResolverError { NonFunctionInAnnotation { span: Span }, #[error("Type `{typ}` was inserted into the generics list from a macro, but is not a generic")] MacroResultInGenericsListNotAGeneric { span: Span, typ: Type }, + #[error("Named type arguments aren't allowed in a {item_kind}")] + NamedTypeArgs { span: Span, item_kind: &'static str }, + #[error("Associated constants may only be a field or integer type")] + AssociatedConstantsMustBeNumeric { span: Span }, + #[error("Overflow in `{lhs} {op} {rhs}`")] + OverflowInType { lhs: u32, op: crate::BinaryTypeOperator, rhs: u32, span: Span }, } impl ResolverError { @@ -148,6 +156,15 @@ impl<'a> From<&'a ResolverError> for Diagnostic { ident.span(), ) } + ResolverError::UnusedImport { ident } => { + let name = &ident.0.contents; + + Diagnostic::simple_warning( + format!("unused import {name}"), + "unused import ".to_string(), + ident.span(), + ) + } ResolverError::VariableNotDeclared { name, span } => Diagnostic::simple_error( format!("cannot find `{name}` in this scope "), "not found in this scope".to_string(), @@ -281,16 +298,11 @@ impl<'a> From<&'a ResolverError> for Diagnostic { "Use an explicit type name or apply the generics at the start of the impl instead".into(), *span, ), - ResolverError::IncorrectGenericCount { span, item_name, actual, expected } => { - let expected_plural = if *expected == 1 { "" } else { "s" }; - let actual_plural = if *actual == 1 { "is" } else { "are" }; - - Diagnostic::simple_error( - format!("`{item_name}` has {expected} generic argument{expected_plural} but {actual} {actual_plural} given here"), - "Incorrect number of generic arguments".into(), - *span, - ) - } + ResolverError::GenericsOnAssociatedType { span } => Diagnostic::simple_error( + "Generic Associated Types (GATs) are currently unsupported in Noir".into(), + "Cannot apply generics to an associated type".into(), + *span, + ), ResolverError::ParserError(error) => error.as_ref().into(), ResolverError::MutableReferenceToImmutableVariable { variable, span } => { Diagnostic::simple_error(format!("Cannot mutably reference the immutable variable {variable}"), format!("{variable} is immutable"), *span) @@ -467,6 +479,27 @@ impl<'a> From<&'a ResolverError> for Diagnostic { *span, ) } + ResolverError::NamedTypeArgs { span, item_kind } => { + Diagnostic::simple_error( + format!("Named type arguments aren't allowed on a {item_kind}"), + "Named type arguments are only allowed for associated types on traits".to_string(), + *span, + ) + } + ResolverError::AssociatedConstantsMustBeNumeric { span } => { + Diagnostic::simple_error( + "Associated constants may only be a field or integer type".to_string(), + "Only numeric constants are allowed".to_string(), + *span, + ) + } + ResolverError::OverflowInType { lhs, op, rhs, span } => { + Diagnostic::simple_error( + format!("Overflow in `{lhs} {op} {rhs}`"), + "Overflow here".to_string(), + *span, + ) + } } } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/import.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/import.rs index 4693d3826a8..b820e4664e3 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/import.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/import.rs @@ -86,7 +86,7 @@ pub fn resolve_import( crate_id: CrateId, import_directive: &ImportDirective, def_maps: &BTreeMap, - path_references: &mut Option<&mut Vec>>, + path_references: &mut Option<&mut Vec>, ) -> Result { let module_scope = import_directive.module_id; let NamespaceResolution { @@ -131,7 +131,7 @@ fn resolve_path_to_ns( crate_id: CrateId, importing_crate: CrateId, def_maps: &BTreeMap, - path_references: &mut Option<&mut Vec>>, + path_references: &mut Option<&mut Vec>, ) -> NamespaceResolutionResult { let import_path = &import_directive.path.segments; let def_map = &def_maps[&crate_id]; @@ -221,7 +221,7 @@ fn resolve_path_from_crate_root( import_path: &[PathSegment], def_maps: &BTreeMap, - path_references: &mut Option<&mut Vec>>, + path_references: &mut Option<&mut Vec>, ) -> NamespaceResolutionResult { resolve_name_in_module( crate_id, @@ -239,7 +239,7 @@ fn resolve_name_in_module( import_path: &[PathSegment], starting_mod: LocalModuleId, def_maps: &BTreeMap, - path_references: &mut Option<&mut Vec>>, + path_references: &mut Option<&mut Vec>, ) -> NamespaceResolutionResult { let def_map = &def_maps[&krate]; let mut current_mod_id = ModuleId { krate, local_id: starting_mod }; @@ -275,7 +275,7 @@ fn resolve_name_in_module( current_mod_id = match typ { ModuleDefId::ModuleId(id) => { if let Some(path_references) = path_references { - path_references.push(Some(ReferenceId::Module(id))); + path_references.push(ReferenceId::Module(id)); } id } @@ -283,14 +283,14 @@ fn resolve_name_in_module( // TODO: If impls are ever implemented, types can be used in a path ModuleDefId::TypeId(id) => { if let Some(path_references) = path_references { - path_references.push(Some(ReferenceId::Struct(id))); + path_references.push(ReferenceId::Struct(id)); } id.module_id() } ModuleDefId::TypeAliasId(_) => panic!("type aliases cannot be used in type namespace"), ModuleDefId::TraitId(id) => { if let Some(path_references) = path_references { - path_references.push(Some(ReferenceId::Trait(id))); + path_references.push(ReferenceId::Trait(id)); } id.0 } @@ -337,7 +337,7 @@ fn resolve_external_dep( current_def_map: &CrateDefMap, directive: &ImportDirective, def_maps: &BTreeMap, - path_references: &mut Option<&mut Vec>>, + path_references: &mut Option<&mut Vec>, importing_crate: CrateId, ) -> NamespaceResolutionResult { // Use extern_prelude to get the dep @@ -355,9 +355,8 @@ fn resolve_external_dep( // See `singleton_import.nr` test case for a check that such cases are handled elsewhere. let path_without_crate_name = &path[1..]; - // Given that we skipped the first segment, record that it doesn't refer to any module or type. if let Some(path_references) = path_references { - path_references.push(None); + path_references.push(ReferenceId::Module(*dep_module)); } let path = Path { @@ -375,9 +374,9 @@ fn resolve_external_dep( resolve_path_to_ns(&dep_directive, dep_module.krate, importing_crate, def_maps, path_references) } -// Issue an error if the given private function is being called from a non-child module, or -// if the given pub(crate) function is being called from another crate -fn can_reference_module_id( +// Returns false if the given private function is being called from a non-child module, or +// if the given pub(crate) function is being called from another crate. Otherwise returns true. +pub fn can_reference_module_id( def_maps: &BTreeMap, importing_crate: CrateId, current_module: LocalModuleId, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/path_resolver.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/path_resolver.rs index 7cd44a84018..712951ad6cb 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/path_resolver.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/path_resolver.rs @@ -15,7 +15,7 @@ pub trait PathResolver { &self, def_maps: &BTreeMap, path: Path, - path_references: &mut Option<&mut Vec>>, + path_references: &mut Option<&mut Vec>, ) -> PathResolutionResult; fn local_module_id(&self) -> LocalModuleId; @@ -39,7 +39,7 @@ impl PathResolver for StandardPathResolver { &self, def_maps: &BTreeMap, path: Path, - path_references: &mut Option<&mut Vec>>, + path_references: &mut Option<&mut Vec>, ) -> PathResolutionResult { resolve_path(def_maps, self.module_id, path, path_references) } @@ -59,7 +59,7 @@ pub fn resolve_path( def_maps: &BTreeMap, module_id: ModuleId, path: Path, - path_references: &mut Option<&mut Vec>>, + path_references: &mut Option<&mut Vec>, ) -> PathResolutionResult { // lets package up the path into an ImportDirective and resolve it using that let import = 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 8eba8215f84..17642843757 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 @@ -1,5 +1,6 @@ +use std::rc::Rc; + use acvm::FieldElement; -use iter_extended::vecmap; use noirc_errors::CustomDiagnostic as Diagnostic; use noirc_errors::Span; use thiserror::Error; @@ -9,6 +10,7 @@ use crate::hir::resolution::errors::ResolverError; use crate::hir_def::expr::HirBinaryOp; use crate::hir_def::traits::TraitConstraint; use crate::hir_def::types::Type; +use crate::macros_api::Ident; use crate::macros_api::NodeInterner; #[derive(Error, Debug, Clone, PartialEq, Eq)] @@ -102,8 +104,12 @@ pub enum TypeCheckError { second_type: String, second_index: usize, }, - #[error("Cannot infer type of expression, type annotations needed before this point")] - TypeAnnotationsNeeded { span: Span }, + #[error("Object type is unknown in method call")] + TypeAnnotationsNeededForMethodCall { span: Span }, + #[error("Object type is unknown in field access")] + TypeAnnotationsNeededForFieldAccess { span: Span }, + #[error("Multiple trait impls may apply to this object type")] + MultipleMatchingImpls { object_type: Type, candidates: Vec, span: Span }, #[error("use of deprecated function {name}")] CallDeprecated { name: String, note: Option, span: Span }, #[error("{0}")] @@ -136,6 +142,10 @@ 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("Converting an unconstrained fn to a non-unconstrained fn is unsafe")] + UnsafeFn { 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")] @@ -154,6 +164,16 @@ pub enum TypeCheckError { MacroReturningNonExpr { typ: Type, span: Span }, #[error("turbofish (`::<_>`) usage at this position isn't supported yet")] UnsupportedTurbofishUsage { span: Span }, + #[error("`{name}` has already been specified")] + DuplicateNamedTypeArg { name: Ident, prev_span: Span }, + #[error("`{item}` has no associated type named `{name}`")] + NoSuchNamedTypeArg { name: Ident, item: String }, + #[error("`{item}` is missing the associated type `{name}`")] + MissingNamedTypeArg { name: Rc, item: String, span: Span }, + #[error("Internal compiler error: type unspecified for value")] + UnspecifiedType { span: Span }, + #[error("Binding `{typ}` here to the `_` inside would create a cyclic type")] + CyclicType { typ: Type, span: Span }, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -274,11 +294,33 @@ impl<'a> From<&'a TypeCheckError> for Diagnostic { format!("return type is {typ}"), *span, ), - TypeCheckError::TypeAnnotationsNeeded { span } => Diagnostic::simple_error( - "Expression type is ambiguous".to_string(), - "Type must be known at this point".to_string(), - *span, - ), + TypeCheckError::TypeAnnotationsNeededForMethodCall { span } => { + let mut error = Diagnostic::simple_error( + "Object type is unknown in method call".to_string(), + "Type must be known by this point to know which method to call".to_string(), + *span, + ); + error.add_note("Try adding a type annotation for the object type before this method call".to_string()); + error + }, + TypeCheckError::TypeAnnotationsNeededForFieldAccess { span } => { + let mut error = Diagnostic::simple_error( + "Object type is unknown in field access".to_string(), + "Type must be known by this point".to_string(), + *span, + ); + error.add_note("Try adding a type annotation for the object type before this expression".to_string()); + error + }, + TypeCheckError::MultipleMatchingImpls { object_type, candidates, span } => { + let message = format!("Multiple trait impls match the object type `{object_type}`"); + let secondary = "Ambiguous impl".to_string(); + let mut error = Diagnostic::simple_error(message, secondary, *span); + for (i, candidate) in candidates.iter().enumerate() { + error.add_note(format!("Candidate {}: `{candidate}`", i + 1)); + } + error + }, TypeCheckError::ResolverError(error) => error.into(), TypeCheckError::TypeMismatchWithSource { expected, actual, span, source } => { let message = match source { @@ -294,7 +336,7 @@ impl<'a> From<&'a TypeCheckError> for Diagnostic { Source::Return(ret_ty, expr_span) => { let ret_ty_span = match ret_ty.clone() { FunctionReturnType::Default(span) => span, - FunctionReturnType::Ty(ty) => ty.span.unwrap(), + FunctionReturnType::Ty(ty) => ty.span, }; let mut diagnostic = Diagnostic::simple_error(format!("expected type {expected}, found type {actual}"), format!("expected {expected} because of return type"), ret_ty_span); @@ -356,6 +398,32 @@ 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::DuplicateNamedTypeArg { name, prev_span } => { + let msg = format!("`{name}` has already been specified"); + let mut error = Diagnostic::simple_error(msg.to_string(), "".to_string(), name.span()); + error.add_secondary(format!("`{name}` previously specified here"), *prev_span); + error + }, + TypeCheckError::NoSuchNamedTypeArg { name, item } => { + let msg = format!("`{item}` has no associated type named `{name}`"); + Diagnostic::simple_error(msg.to_string(), "".to_string(), name.span()) + }, + TypeCheckError::MissingNamedTypeArg { name, item, span } => { + let msg = format!("`{item}` is missing the associated type `{name}`"); + Diagnostic::simple_error(msg.to_string(), "".to_string(), *span) + }, + TypeCheckError::Unsafe { span } => { + Diagnostic::simple_warning(error.to_string(), String::new(), *span) + } + TypeCheckError::UnsafeFn { span } => { + Diagnostic::simple_warning(error.to_string(), String::new(), *span) + } + TypeCheckError::UnspecifiedType { span } => { + Diagnostic::simple_error(error.to_string(), String::new(), *span) + } + TypeCheckError::CyclicType { typ: _, span } => { + Diagnostic::simple_error(error.to_string(), "Cyclic types have unlimited size and are prohibited in Noir".into(), *span) + } } } } @@ -394,11 +462,7 @@ impl NoMatchingImplFoundError { .into_iter() .map(|constraint| { let r#trait = interner.try_get_trait(constraint.trait_id)?; - let mut name = r#trait.name.to_string(); - if !constraint.trait_generics.is_empty() { - let generics = vecmap(&constraint.trait_generics, ToString::to_string); - name += &format!("<{}>", generics.join(", ")); - } + let name = format!("{}{}", r#trait.name, constraint.trait_generics); Some((constraint.typ, name)) }) .collect::>>()?; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/generics.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/generics.rs new file mode 100644 index 00000000000..697c78745f9 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/generics.rs @@ -0,0 +1,166 @@ +use std::cell::Ref; + +use iter_extended::vecmap; + +use crate::{ + hir_def::traits::NamedType, + macros_api::NodeInterner, + node_interner::{TraitId, TypeAliasId}, + ResolvedGeneric, StructType, Type, +}; + +/// Represents something that can be generic over type variables +/// such as a trait, struct type, or type alias. +/// +/// Used primarily by `Elaborator::resolve_type_args` so that we can +/// have one function to do this for struct types, type aliases, traits, etc. +pub trait Generic { + /// The name of this kind of item, for error messages. E.g. "trait", "struct type". + fn item_kind(&self) -> &'static str; + + /// The name of this item, usually named by a user. E.g. "Foo" for "struct Foo {}" + fn item_name(&self, interner: &NodeInterner) -> String; + + /// Each ordered generic on this type, excluding any named generics. + fn generics(&self, interner: &NodeInterner) -> Vec; + + /// True if this item kind can ever accept named type arguments. + /// Currently, this is only true for traits. Structs & aliases can never have named args. + fn accepts_named_type_args(&self) -> bool; + + fn named_generics(&self, interner: &NodeInterner) -> Vec; +} + +impl Generic for TraitId { + fn item_kind(&self) -> &'static str { + "trait" + } + + fn item_name(&self, interner: &NodeInterner) -> String { + interner.get_trait(*self).name.to_string() + } + + fn generics(&self, interner: &NodeInterner) -> Vec { + interner.get_trait(*self).generics.clone() + } + + fn accepts_named_type_args(&self) -> bool { + true + } + + fn named_generics(&self, interner: &NodeInterner) -> Vec { + interner.get_trait(*self).associated_types.clone() + } +} + +impl Generic for TypeAliasId { + fn item_kind(&self) -> &'static str { + "type alias" + } + + fn item_name(&self, interner: &NodeInterner) -> String { + interner.get_type_alias(*self).borrow().name.to_string() + } + + fn generics(&self, interner: &NodeInterner) -> Vec { + interner.get_type_alias(*self).borrow().generics.clone() + } + + fn accepts_named_type_args(&self) -> bool { + false + } + + fn named_generics(&self, _interner: &NodeInterner) -> Vec { + Vec::new() + } +} + +impl Generic for Ref<'_, StructType> { + fn item_kind(&self) -> &'static str { + "struct" + } + + fn item_name(&self, _interner: &NodeInterner) -> String { + self.name.to_string() + } + + fn generics(&self, _interner: &NodeInterner) -> Vec { + self.generics.clone() + } + + fn accepts_named_type_args(&self) -> bool { + false + } + + fn named_generics(&self, _interner: &NodeInterner) -> Vec { + Vec::new() + } +} + +/// TraitGenerics are different from regular generics in that they can +/// also contain associated type arguments. +#[derive(Default, PartialEq, Eq, Clone, Hash, Ord, PartialOrd)] +pub struct TraitGenerics { + pub ordered: Vec, + pub named: Vec, +} + +impl TraitGenerics { + pub fn map(&self, mut f: impl FnMut(&Type) -> Type) -> TraitGenerics { + let ordered = vecmap(&self.ordered, &mut f); + let named = + vecmap(&self.named, |named| NamedType { name: named.name.clone(), typ: f(&named.typ) }); + TraitGenerics { ordered, named } + } +} + +impl std::fmt::Display for TraitGenerics { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fmt_trait_generics(self, f, false) + } +} + +impl std::fmt::Debug for TraitGenerics { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fmt_trait_generics(self, f, true) + } +} + +fn fmt_trait_generics( + generics: &TraitGenerics, + f: &mut std::fmt::Formatter<'_>, + debug: bool, +) -> std::fmt::Result { + if !generics.ordered.is_empty() || !generics.named.is_empty() { + write!(f, "<")?; + for (i, typ) in generics.ordered.iter().enumerate() { + if i != 0 { + write!(f, ", ")?; + } + + if debug { + write!(f, "{typ:?}")?; + } else { + write!(f, "{typ}")?; + } + } + + if !generics.ordered.is_empty() && !generics.named.is_empty() { + write!(f, ", ")?; + } + + for (i, named) in generics.named.iter().enumerate() { + if i != 0 { + write!(f, ", ")?; + } + + if debug { + write!(f, "{} = {:?}", named.name, named.typ)?; + } else { + write!(f, "{} = {}", named.name, named.typ)?; + } + } + write!(f, ">")?; + } + Ok(()) +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/mod.rs index b6efa17a529..f45b68dd818 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/mod.rs @@ -1,12 +1,5 @@ -//! This file contains type_check_func, the entry point to the type checking pass (for each function). -//! -//! The pass structure of type checking is relatively straightforward. It is a single pass through -//! the HIR of each function and outputs the inferred type of each HIR node into the NodeInterner, -//! keyed by the ID of the node. -//! -//! Although this algorithm features inference via TypeVariables, there is no generalization step -//! as all functions are required to give their full signatures. Closures are inferred but are -//! never generalized and thus cannot be used polymorphically. mod errors; +pub mod generics; + pub use self::errors::Source; pub use errors::{NoMatchingImplFoundError, TypeCheckError}; 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 e85d30f0c32..40c16d00356 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 @@ -3,6 +3,7 @@ use fm::FileId; use noirc_errors::Location; use crate::ast::{BinaryOp, BinaryOpKind, Ident, UnaryOp}; +use crate::hir::type_check::generics::TraitGenerics; use crate::node_interner::{DefinitionId, ExprId, FuncId, NodeInterner, StmtId, TraitMethodId}; use crate::token::Tokens; use crate::Shared; @@ -37,6 +38,7 @@ pub enum HirExpression { Quote(Tokens), Unquote(Tokens), Comptime(HirBlockExpression), + Unsafe(HirBlockExpression), Error, } @@ -198,7 +200,7 @@ pub enum HirMethodReference { /// Or a method can come from a Trait impl block, in which case /// the actual function called will depend on the instantiated type, /// which can be only known during monomorphization. - TraitMethodId(TraitMethodId, /*trait generics:*/ Vec), + TraitMethodId(TraitMethodId, TraitGenerics), } impl HirMethodCallExpression { @@ -207,7 +209,7 @@ impl HirMethodCallExpression { /// Returns ((func_var_id, func_var), call_expr) pub fn into_function_call( mut self, - method: &HirMethodReference, + method: HirMethodReference, object_type: Type, is_macro_call: bool, location: Location, @@ -218,17 +220,17 @@ impl HirMethodCallExpression { let (id, impl_kind) = match method { HirMethodReference::FuncId(func_id) => { - (interner.function_definition_id(*func_id), ImplKind::NotATraitMethod) + (interner.function_definition_id(func_id), ImplKind::NotATraitMethod) } - HirMethodReference::TraitMethodId(method_id, generics) => { - let id = interner.trait_method_id(*method_id); + HirMethodReference::TraitMethodId(method_id, trait_generics) => { + let id = interner.trait_method_id(method_id); let constraint = TraitConstraint { typ: object_type, trait_id: method_id.trait_id, - trait_generics: generics.clone(), + trait_generics, span: location.span, }; - (id, ImplKind::TraitMethod(*method_id, constraint, false)) + (id, ImplKind::TraitMethod(method_id, constraint, false)) } }; let func_var = HirIdent { location, id, impl_kind }; 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 6b66cf1ab4a..29b0cb7b8af 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 099c9ea78f7..0572ba403a1 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 @@ -1,11 +1,14 @@ +use iter_extended::vecmap; use rustc_hash::FxHashMap as HashMap; use crate::ast::{Ident, NoirFunction}; +use crate::hir::type_check::generics::TraitGenerics; use crate::{ graph::CrateId, node_interner::{FuncId, TraitId, TraitMethodId}, - Generics, Type, TypeBindings, TypeVariable, TypeVariableId, + Generics, Type, TypeBindings, TypeVariable, }; +use crate::{ResolvedGeneric, TypeVariableKind}; use fm::FileId; use noirc_errors::{Location, Span}; @@ -23,15 +26,20 @@ pub struct TraitFunction { #[derive(Clone, Debug, PartialEq, Eq)] pub struct TraitConstant { pub name: Ident, - pub ty: Type, + pub typ: Type, pub span: Span, } -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct TraitType { +#[derive(Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)] +pub struct NamedType { pub name: Ident, - pub ty: Type, - pub span: Span, + pub typ: Type, +} + +impl std::fmt::Display for NamedType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{} = {}", self.name, self.typ) + } } /// Represents a trait in the type system. Each instance of this struct @@ -53,8 +61,7 @@ pub struct Trait { /// the information needed to create the full TraitFunction. pub method_ids: HashMap, - pub constants: Vec, - pub types: Vec, + pub associated_types: Generics, pub name: Ident, pub generics: Generics, @@ -64,7 +71,6 @@ pub struct Trait { /// to this TypeVariable. Then when we check if the types of trait impl elements /// match the definition in the trait, we bind this TypeVariable to whatever /// the correct Self type is for that particular impl block. - pub self_type_typevar_id: TypeVariableId, pub self_type_typevar: TypeVariable, } @@ -73,7 +79,15 @@ pub struct TraitImpl { pub ident: Ident, pub typ: Type, pub trait_id: TraitId, + + /// Any ordered type arguments on the trait this impl is for. + /// E.g. `A, B` in `impl Foo for Bar` + /// + /// Note that named arguments (associated types) are stored separately + /// in the NodeInterner. This is because they're required to resolve types + /// before the impl as a whole is finished resolving. pub trait_generics: Vec, + pub file: FileId, pub methods: Vec, // methods[i] is the implementation of trait.methods[i] for Type typ @@ -88,21 +102,21 @@ pub struct TraitImpl { pub struct TraitConstraint { pub typ: Type, pub trait_id: TraitId, - pub trait_generics: Vec, + pub trait_generics: TraitGenerics, pub span: Span, } impl TraitConstraint { - pub fn new(typ: Type, trait_id: TraitId, trait_generics: Vec, span: Span) -> Self { - Self { typ, trait_id, trait_generics, span } - } - pub fn apply_bindings(&mut self, type_bindings: &TypeBindings) { self.typ = self.typ.substitute(type_bindings); - for typ in &mut self.trait_generics { + for typ in &mut self.trait_generics.ordered { *typ = typ.substitute(type_bindings); } + + for named in &mut self.trait_generics.named { + named.typ = named.typ.substitute(type_bindings); + } } } @@ -131,6 +145,35 @@ impl Trait { } None } + + pub fn get_associated_type(&self, last_name: &str) -> Option<&ResolvedGeneric> { + self.associated_types.iter().find(|typ| typ.name.as_ref() == last_name) + } + + /// Returns both the ordered generics of this type, and its named, associated types. + /// These types are all as-is and are not instantiated. + pub fn get_generics(&self) -> (Vec, Vec) { + let ordered = vecmap(&self.generics, |generic| generic.clone().as_named_generic()); + let named = vecmap(&self.associated_types, |generic| generic.clone().as_named_generic()); + (ordered, named) + } + + /// Returns a TraitConstraint for this trait using Self as the object + /// type and the uninstantiated generics for any trait generics. + pub fn as_constraint(&self, span: Span) -> TraitConstraint { + let ordered = vecmap(&self.generics, |generic| generic.clone().as_named_generic()); + let named = vecmap(&self.associated_types, |generic| { + let name = Ident::new(generic.name.to_string(), span); + NamedType { name, typ: generic.clone().as_named_generic() } + }); + + TraitConstraint { + typ: Type::TypeVariable(self.self_type_typevar.clone(), TypeVariableKind::Normal), + trait_generics: TraitGenerics { ordered, named }, + trait_id: self.id, + span, + } + } } impl std::fmt::Display for Trait { @@ -142,9 +185,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 +204,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 177d23c74dd..c59c86b9616 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 @@ -7,7 +7,7 @@ use std::{ use crate::{ ast::IntegerBitSize, - hir::type_check::TypeCheckError, + hir::type_check::{generics::TraitGenerics, TypeCheckError}, node_interner::{ExprId, NodeInterner, TraitId, TypeAliasId}, }; use iter_extended::vecmap; @@ -19,9 +19,14 @@ use crate::{ node_interner::StructId, }; -use super::expr::{HirCallExpression, HirExpression, HirIdent}; +use super::{ + expr::{HirCallExpression, HirExpression, HirIdent}, + traits::NamedType, +}; + +mod arithmetic; -#[derive(PartialEq, Eq, Clone, Hash, Ord, PartialOrd)] +#[derive(Eq, Clone, Ord, PartialOrd)] pub enum Type { /// A primitive Field type FieldElement, @@ -78,7 +83,7 @@ pub enum Type { /// `impl Trait` when used in a type position. /// These are only matched based on the TraitId. The trait name parameter is only /// used for displaying error messages using the name of the trait. - TraitAsType(TraitId, /*name:*/ Rc, /*generics:*/ Vec), + TraitAsType(TraitId, Rc, TraitGenerics), /// NamedGenerics are the 'T' or 'U' in a user-defined generic function /// like `fn foo(...) {}`. Unlike TypeVariables, they cannot be bound over. @@ -88,7 +93,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 +156,8 @@ pub enum QuotedType { StructDefinition, TraitConstraint, TraitDefinition, + TraitImpl, + UnresolvedType, FunctionDefinition, Module, } @@ -199,6 +211,12 @@ impl ResolvedGeneric { } } +enum FunctionCoercionResult { + NoCoercion, + Coerced(Type), + UnconstrainedMismatch(Type), +} + impl std::hash::Hash for StructType { fn hash(&self, state: &mut H) { self.id.hash(state); @@ -528,7 +546,7 @@ impl TypeVariable { }; if binding.occurs(id) { - Err(TypeCheckError::TypeAnnotationsNeeded { span }) + Err(TypeCheckError::CyclicType { span, typ: binding }) } else { *self.1.borrow_mut() = TypeBinding::Bound(binding); Ok(()) @@ -634,12 +652,7 @@ impl std::fmt::Display for Type { } } Type::TraitAsType(_id, name, generics) => { - write!(f, "impl {}", name)?; - if !generics.is_empty() { - let generics = vecmap(generics, ToString::to_string).join(", "); - write!(f, "<{generics}>")?; - } - Ok(()) + write!(f, "impl {}{}", name, generics) } Type::Tuple(elements) => { let elements = vecmap(elements, ToString::to_string); @@ -662,15 +675,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 +744,8 @@ 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::UnresolvedType => write!(f, "UnresolvedType"), QuotedType::FunctionDefinition => write!(f, "FunctionDefinition"), QuotedType::Module => write!(f, "Module"), } @@ -843,8 +862,9 @@ impl Type { | Type::Forall(_, _) | Type::Quoted(_) => false, - Type::TraitAsType(_, _, args) => { - args.iter().any(|generic| generic.contains_numeric_typevar(target_id)) + Type::TraitAsType(_, _, generics) => { + generics.ordered.iter().any(|generic| generic.contains_numeric_typevar(target_id)) + || generics.named.iter().any(|typ| typ.typ.contains_numeric_typevar(target_id)) } Type::Array(length, elem) => { elem.contains_numeric_typevar(target_id) || named_generic_id_matches_target(length) @@ -853,7 +873,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) @@ -919,9 +939,12 @@ impl Type { } Type::TraitAsType(_, _, args) => { - for arg in args.iter() { + for arg in args.ordered.iter() { arg.find_numeric_type_vars(found_names); } + for arg in args.named.iter() { + arg.typ.find_numeric_type_vars(found_names); + } } Type::Array(length, elem) => { elem.find_numeric_type_vars(found_names); @@ -933,7 +956,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 +1013,7 @@ impl Type { Type::FmtString(_, _) | Type::TypeVariable(_, _) | Type::NamedGeneric(_, _, _) - | Type::Function(_, _, _) + | Type::Function(_, _, _, _) | Type::MutableReference(_) | Type::Forall(_, _) | Type::Quoted(_) @@ -1039,7 +1062,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 +1100,7 @@ impl Type { | Type::Slice(_) | Type::TypeVariable(_, _) | Type::NamedGeneric(_, _, _) - | Type::Function(_, _, _) + | Type::Function(_, _, _, _) | Type::FmtString(_, _) | Type::InfixExpr(..) | Type::Error => true, @@ -1208,7 +1231,7 @@ impl Type { | Type::TypeVariable(_, _) | Type::TraitAsType(..) | Type::NamedGeneric(_, _, _) - | Type::Function(_, _, _) + | Type::Function(_, _, _, _) | Type::MutableReference(_) | Type::Forall(_, _) | Type::Constant(_) @@ -1576,8 +1599,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)?; } @@ -1609,6 +1635,15 @@ impl Type { } else { Err(UnificationError) } + } else if let InfixExpr(lhs, op, rhs) = other { + if let Some(inverse) = op.inverse() { + // Handle cases like `4 = a + b` by trying to solve to `a = 4 - b` + let new_type = InfixExpr(Box::new(Constant(*value)), inverse, rhs.clone()); + new_type.try_unify(lhs, bindings)?; + Ok(()) + } else { + Err(UnificationError) + } } else { Err(UnificationError) } @@ -1624,107 +1659,6 @@ impl Type { } } - /// Try to canonicalize the representation of this type. - /// Currently the only type with a canonical representation is - /// `Type::Infix` where for each consecutive commutative operator - /// we sort the non-constant operands by `Type: Ord` and place all constant - /// operands at the end, constant folded. - /// - /// For example: - /// - `canonicalize[((1 + N) + M) + 2] = (M + N) + 3` - /// - `canonicalize[A + 2 * B + 3 - 2] = A + (B * 2) + 3 - 2` - pub fn canonicalize(&self) -> Type { - match self.follow_bindings() { - Type::InfixExpr(lhs, op, rhs) => { - if let Some(value) = self.evaluate_to_u32() { - return Type::Constant(value); - } - - let lhs = lhs.canonicalize(); - let rhs = rhs.canonicalize(); - - if let Some(result) = Self::try_simplify_subtraction(&lhs, op, &rhs) { - return result; - } - - if op.is_commutative() { - return Self::sort_commutative(&lhs, op, &rhs); - } - - Type::InfixExpr(Box::new(lhs), op, Box::new(rhs)) - } - other => other, - } - } - - fn sort_commutative(lhs: &Type, op: BinaryTypeOperator, rhs: &Type) -> Type { - let mut queue = vec![lhs.clone(), rhs.clone()]; - - let mut sorted = BTreeSet::new(); - - let zero_value = if op == BinaryTypeOperator::Addition { 0 } else { 1 }; - let mut constant = zero_value; - - // Push each non-constant term to `sorted` to sort them. Recur on InfixExprs with the same operator. - while let Some(item) = queue.pop() { - match item.canonicalize() { - Type::InfixExpr(lhs, new_op, rhs) if new_op == op => { - queue.push(*lhs); - queue.push(*rhs); - } - Type::Constant(new_constant) => { - constant = op.function(constant, new_constant); - } - other => { - sorted.insert(other); - } - } - } - - if let Some(first) = sorted.pop_first() { - let mut typ = first.clone(); - - for rhs in sorted { - typ = Type::InfixExpr(Box::new(typ), op, Box::new(rhs.clone())); - } - - if constant != zero_value { - typ = Type::InfixExpr(Box::new(typ), op, Box::new(Type::Constant(constant))); - } - - typ - } else { - // Every type must have been a constant - Type::Constant(constant) - } - } - - /// Try to simplify a subtraction expression of `lhs - rhs`. - /// - /// - Simplifies `(a + C1) - C2` to `a + (C1 - C2)` if C1 and C2 are constants. - fn try_simplify_subtraction(lhs: &Type, op: BinaryTypeOperator, rhs: &Type) -> Option { - use BinaryTypeOperator::*; - match lhs { - Type::InfixExpr(l_lhs, l_op, l_rhs) => { - // Simplify `(N + 2) - 1` - if op == Subtraction && *l_op == Addition { - if let (Some(lhs_const), Some(rhs_const)) = - (l_rhs.evaluate_to_u32(), rhs.evaluate_to_u32()) - { - if lhs_const > rhs_const { - let constant = Box::new(Type::Constant(lhs_const - rhs_const)); - return Some( - Type::InfixExpr(l_lhs.clone(), *l_op, constant).canonicalize(), - ); - } - } - } - None - } - _ => None, - } - } - /// Try to unify a type variable to `self`. /// This is a helper function factored out from try_unify. fn try_unify_to_type_variable( @@ -1762,18 +1696,57 @@ impl Type { &self, expected: &Type, expression: ExprId, + span: Span, interner: &mut NodeInterner, errors: &mut Vec, make_error: impl FnOnce() -> TypeCheckError, ) { 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()); + 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` + match self.try_fn_to_unconstrained_fn_coercion(expected) { + FunctionCoercionResult::NoCoercion => errors.push(make_error()), + FunctionCoercionResult::Coerced(coerced_self) => { + coerced_self + .unify_with_coercions(expected, expression, span, interner, errors, make_error); + } + FunctionCoercionResult::UnconstrainedMismatch(coerced_self) => { + errors.push(TypeCheckError::UnsafeFn { span }); + + coerced_self + .unify_with_coercions(expected, expression, span, interner, errors, 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) -> FunctionCoercionResult { + // 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()) + { + let coerced_type = Type::Function(params, ret, env, unconstrained_expected); + + match (unconstrained_self, unconstrained_expected) { + (true, true) | (false, false) => FunctionCoercionResult::NoCoercion, + (false, true) => FunctionCoercionResult::Coerced(coerced_type), + (true, false) => FunctionCoercionResult::UnconstrainedMismatch(coerced_type), } } else { - Type::apply_type_bindings(bindings); + FunctionCoercionResult::NoCoercion } } @@ -1789,13 +1762,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 @@ -1818,14 +1795,14 @@ impl Type { } } - match self { - Type::TypeVariable(_, TypeVariableKind::Constant(size)) => Some(*size), + match self.canonicalize() { + Type::TypeVariable(_, TypeVariableKind::Constant(size)) => Some(size), Type::Array(len, _elem) => len.evaluate_to_u32(), - Type::Constant(x) => Some(*x), + Type::Constant(x) => Some(x), Type::InfixExpr(lhs, op, rhs) => { let lhs = lhs.evaluate_to_u32()?; let rhs = rhs.evaluate_to_u32()?; - Some(op.function(lhs, rhs)) + op.function(lhs, rhs) } _ => None, } @@ -1856,11 +1833,13 @@ impl Type { /// Retrieves the type of the given field name /// Panics if the type is not a struct or tuple. pub fn get_field_type(&self, field_name: &str) -> Option { - match self { - Type::Struct(def, args) => def.borrow().get_field(field_name, args).map(|(typ, _)| typ), + match self.follow_bindings() { + Type::Struct(def, args) => { + def.borrow().get_field(field_name, &args).map(|(typ, _)| typ) + } Type::Tuple(fields) => { - let mut fields = fields.iter().enumerate(); - fields.find(|(i, _)| i.to_string() == *field_name).map(|(_, typ)| typ).cloned() + let mut fields = fields.into_iter().enumerate(); + fields.find(|(i, _)| i.to_string() == *field_name).map(|(_, typ)| typ) } _ => None, } @@ -1927,17 +1906,13 @@ impl Type { Type::Forall(typevars, typ) => { assert_eq!(types.len() + implicit_generic_count, typevars.len(), "Turbofish operator used with incorrect generic count which was not caught by name resolution"); + let bindings = + (0..implicit_generic_count).map(|_| interner.next_type_variable()).chain(types); + let replacements = typevars .iter() - .enumerate() - .map(|(i, var)| { - let binding = if i < implicit_generic_count { - interner.next_type_variable() - } else { - types[i - implicit_generic_count].clone() - }; - (var.id(), (var.clone(), binding)) - }) + .zip(bindings) + .map(|(var, binding)| (var.id(), (var.clone(), binding))) .collect(); let instantiated = typ.substitute(&replacements); @@ -2070,23 +2045,27 @@ 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), )), - Type::TraitAsType(s, name, args) => { - let args = vecmap(args, |arg| { + Type::TraitAsType(s, name, generics) => { + let ordered = vecmap(&generics.ordered, |arg| { arg.substitute_helper(type_bindings, substitute_bound_typevars) }); - Type::TraitAsType(*s, name.clone(), args) + let named = vecmap(&generics.named, |arg| { + let typ = arg.typ.substitute_helper(type_bindings, substitute_bound_typevars); + NamedType { name: arg.name.clone(), typ } + }); + Type::TraitAsType(*s, name.clone(), TraitGenerics { ordered, named }) } Type::InfixExpr(lhs, op, rhs) => { let lhs = lhs.substitute_helper(type_bindings, substitute_bound_typevars); @@ -2115,11 +2094,13 @@ impl Type { let field_occurs = fields.occurs(target_id); len_occurs || field_occurs } - Type::Struct(_, generic_args) - | Type::Alias(_, generic_args) - | Type::TraitAsType(_, _, generic_args) => { + Type::Struct(_, generic_args) | Type::Alias(_, generic_args) => { generic_args.iter().any(|arg| arg.occurs(target_id)) } + Type::TraitAsType(_, _, args) => { + args.ordered.iter().any(|arg| arg.occurs(target_id)) + || args.named.iter().any(|arg| arg.typ.occurs(target_id)) + } Type::Tuple(fields) => fields.iter().any(|field| field.occurs(target_id)), Type::NamedGeneric(type_var, _, _) | Type::TypeVariable(type_var, _) => { match &*type_var.borrow() { @@ -2132,7 +2113,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,18 +2167,22 @@ 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())), TraitAsType(s, name, args) => { - let args = vecmap(args, |arg| arg.follow_bindings()); - TraitAsType(*s, name.clone(), args) + let ordered = vecmap(&args.ordered, |arg| arg.follow_bindings()); + let named = vecmap(&args.named, |arg| NamedType { + name: arg.name.clone(), + typ: arg.typ.follow_bindings(), + }); + TraitAsType(*s, name.clone(), TraitGenerics { ordered, named }) } InfixExpr(lhs, op, rhs) => { let lhs = lhs.follow_bindings(); @@ -2266,9 +2251,12 @@ impl Type { } } Type::TraitAsType(_, _, generics) => { - for generic in generics { + for generic in &mut generics.ordered { generic.replace_named_generics_with_type_variables(); } + for generic in &mut generics.named { + generic.typ.replace_named_generics_with_type_variables(); + } } Type::NamedGeneric(var, _, _) => { let type_binding = var.borrow(); @@ -2282,7 +2270,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 +2299,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,25 +2322,37 @@ 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); } impl BinaryTypeOperator { /// Perform the actual rust numeric operation associated with this operator - pub fn function(self, a: u32, b: u32) -> u32 { + pub fn function(self, a: u32, b: u32) -> Option { match self { - BinaryTypeOperator::Addition => a.wrapping_add(b), - BinaryTypeOperator::Subtraction => a.wrapping_sub(b), - BinaryTypeOperator::Multiplication => a.wrapping_mul(b), - BinaryTypeOperator::Division => a.wrapping_div(b), - BinaryTypeOperator::Modulo => a.wrapping_rem(b), + BinaryTypeOperator::Addition => a.checked_add(b), + BinaryTypeOperator::Subtraction => a.checked_sub(b), + BinaryTypeOperator::Multiplication => a.checked_mul(b), + BinaryTypeOperator::Division => a.checked_div(b), + BinaryTypeOperator::Modulo => a.checked_rem(b), } } fn is_commutative(self) -> bool { matches!(self, BinaryTypeOperator::Addition | BinaryTypeOperator::Multiplication) } + + /// Return the operator that will "undo" this operation if applied to the rhs + fn inverse(self) -> Option { + match self { + BinaryTypeOperator::Addition => Some(BinaryTypeOperator::Subtraction), + BinaryTypeOperator::Subtraction => Some(BinaryTypeOperator::Addition), + BinaryTypeOperator::Multiplication => Some(BinaryTypeOperator::Division), + BinaryTypeOperator::Division => Some(BinaryTypeOperator::Multiplication), + BinaryTypeOperator::Modulo => None, + } + } } impl TypeVariableKind { @@ -2424,15 +2421,16 @@ impl From<&Type> for PrintableType { PrintableType::Struct { fields, name: struct_type.name.to_string() } } Type::Alias(alias, args) => alias.borrow().get_type(args).into(), - Type::TraitAsType(_, _, _) => unreachable!(), + Type::TraitAsType(..) => unreachable!(), Type::Tuple(types) => PrintableType::Tuple { types: vecmap(types, |typ| typ.into()) }, 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()) } @@ -2485,14 +2483,7 @@ impl std::fmt::Debug for Type { write!(f, "{}<{}>", alias.borrow(), args.join(", ")) } } - Type::TraitAsType(_id, name, generics) => { - write!(f, "impl {}", name)?; - if !generics.is_empty() { - let generics = vecmap(generics, |arg| format!("{:?}", arg)).join(", "); - write!(f, "<{generics}>")?; - } - Ok(()) - } + Type::TraitAsType(_id, name, generics) => write!(f, "impl {}{:?}", name, generics), Type::Tuple(elements) => { let elements = vecmap(elements, |arg| format!("{:?}", arg)); write!(f, "({})", elements.join(", ")) @@ -2517,7 +2508,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:?}"), @@ -2558,3 +2553,136 @@ impl std::fmt::Debug for StructType { write!(f, "{}", self.name) } } + +impl std::hash::Hash for Type { + fn hash(&self, state: &mut H) { + if let Some(variable) = self.get_inner_type_variable() { + if let TypeBinding::Bound(typ) = &*variable.borrow() { + typ.hash(state); + return; + } + } + + if !matches!(self, Type::TypeVariable(..) | Type::NamedGeneric(..)) { + std::mem::discriminant(self).hash(state); + } + + match self { + Type::FieldElement | Type::Bool | Type::Unit | Type::Error => (), + Type::Array(len, elem) => { + len.hash(state); + elem.hash(state); + } + Type::Slice(elem) => elem.hash(state), + Type::Integer(sign, bits) => { + sign.hash(state); + bits.hash(state); + } + Type::String(len) => len.hash(state), + Type::FmtString(len, env) => { + len.hash(state); + env.hash(state); + } + Type::Tuple(elems) => elems.hash(state), + Type::Struct(def, args) => { + def.hash(state); + args.hash(state); + } + Type::Alias(alias, args) => { + alias.hash(state); + args.hash(state); + } + Type::TypeVariable(var, _) | Type::NamedGeneric(var, ..) => var.hash(state), + Type::TraitAsType(trait_id, _, args) => { + trait_id.hash(state); + args.hash(state); + } + Type::Function(args, ret, env, is_unconstrained) => { + args.hash(state); + ret.hash(state); + env.hash(state); + is_unconstrained.hash(state); + } + Type::MutableReference(elem) => elem.hash(state), + Type::Forall(vars, typ) => { + vars.hash(state); + typ.hash(state); + } + Type::Constant(value) => value.hash(state), + Type::Quoted(typ) => typ.hash(state), + Type::InfixExpr(lhs, op, rhs) => { + lhs.hash(state); + op.hash(state); + rhs.hash(state); + } + } + } +} + +impl PartialEq for Type { + fn eq(&self, other: &Self) -> bool { + if let Some(variable) = self.get_inner_type_variable() { + if let TypeBinding::Bound(typ) = &*variable.borrow() { + return typ == other; + } + } + + if let Some(variable) = other.get_inner_type_variable() { + if let TypeBinding::Bound(typ) = &*variable.borrow() { + return self == typ; + } + } + + use Type::*; + match (self, other) { + (FieldElement, FieldElement) | (Bool, Bool) | (Unit, Unit) | (Error, Error) => true, + (Array(lhs_len, lhs_elem), Array(rhs_len, rhs_elem)) => { + lhs_len == rhs_len && lhs_elem == rhs_elem + } + (Slice(lhs_elem), Slice(rhs_elem)) => lhs_elem == rhs_elem, + (Integer(lhs_sign, lhs_bits), Integer(rhs_sign, rhs_bits)) => { + lhs_sign == rhs_sign && lhs_bits == rhs_bits + } + (String(lhs_len), String(rhs_len)) => lhs_len == rhs_len, + (FmtString(lhs_len, lhs_env), FmtString(rhs_len, rhs_env)) => { + lhs_len == rhs_len && lhs_env == rhs_env + } + (Tuple(lhs_types), Tuple(rhs_types)) => lhs_types == rhs_types, + (Struct(lhs_struct, lhs_generics), Struct(rhs_struct, rhs_generics)) => { + lhs_struct == rhs_struct && lhs_generics == rhs_generics + } + (Alias(lhs_alias, lhs_generics), Alias(rhs_alias, rhs_generics)) => { + lhs_alias == rhs_alias && lhs_generics == rhs_generics + } + (TraitAsType(lhs_trait, _, lhs_generics), TraitAsType(rhs_trait, _, rhs_generics)) => { + lhs_trait == rhs_trait && lhs_generics == rhs_generics + } + ( + Function(lhs_args, lhs_ret, lhs_env, lhs_unconstrained), + Function(rhs_args, rhs_ret, rhs_env, rhs_unconstrained), + ) => { + let args_and_ret_eq = lhs_args == rhs_args && lhs_ret == rhs_ret; + args_and_ret_eq && lhs_env == rhs_env && lhs_unconstrained == rhs_unconstrained + } + (MutableReference(lhs_elem), MutableReference(rhs_elem)) => lhs_elem == rhs_elem, + (Forall(lhs_vars, lhs_type), Forall(rhs_vars, rhs_type)) => { + lhs_vars == rhs_vars && lhs_type == rhs_type + } + (Constant(lhs), Constant(rhs)) => lhs == rhs, + (Quoted(lhs), Quoted(rhs)) => lhs == rhs, + (InfixExpr(l_lhs, l_op, l_rhs), InfixExpr(r_lhs, r_op, r_rhs)) => { + l_lhs == r_lhs && l_op == r_op && l_rhs == r_rhs + } + // Special case: we consider unbound named generics and type variables to be equal to each + // other if their type variable ids match. This is important for some corner cases in + // monomorphization where we call `replace_named_generics_with_type_variables` but + // still want them to be equal for canonicalization checks in arithmetic generics. + // Without this we'd fail the `serialize` test. + ( + NamedGeneric(lhs_var, _, _) | TypeVariable(lhs_var, _), + NamedGeneric(rhs_var, _, _) | TypeVariable(rhs_var, _), + ) => lhs_var.id() == rhs_var.id(), + _ => false, + } + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types/arithmetic.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types/arithmetic.rs new file mode 100644 index 00000000000..ad07185dff1 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types/arithmetic.rs @@ -0,0 +1,215 @@ +use std::collections::BTreeSet; + +use crate::{BinaryTypeOperator, Type}; + +impl Type { + /// Try to canonicalize the representation of this type. + /// Currently the only type with a canonical representation is + /// `Type::Infix` where for each consecutive commutative operator + /// we sort the non-constant operands by `Type: Ord` and place all constant + /// operands at the end, constant folded. + /// + /// For example: + /// - `canonicalize[((1 + N) + M) + 2] = (M + N) + 3` + /// - `canonicalize[A + 2 * B + 3 - 2] = A + (B * 2) + 3 - 2` + pub fn canonicalize(&self) -> Type { + match self.follow_bindings() { + Type::InfixExpr(lhs, op, rhs) => { + // evaluate_to_u32 also calls canonicalize so if we just called + // `self.evaluate_to_u32()` we'd get infinite recursion. + if let (Some(lhs), Some(rhs)) = (lhs.evaluate_to_u32(), rhs.evaluate_to_u32()) { + if let Some(result) = op.function(lhs, rhs) { + return Type::Constant(result); + } + } + + let lhs = lhs.canonicalize(); + let rhs = rhs.canonicalize(); + if let Some(result) = Self::try_simplify_non_constants_in_lhs(&lhs, op, &rhs) { + return result.canonicalize(); + } + + if let Some(result) = Self::try_simplify_non_constants_in_rhs(&lhs, op, &rhs) { + return result.canonicalize(); + } + + // Try to simplify partially constant expressions in the form `(N op1 C1) op2 C2` + // where C1 and C2 are constants that can be combined (e.g. N + 5 - 3 = N + 2) + if let Some(result) = Self::try_simplify_partial_constants(&lhs, op, &rhs) { + return result.canonicalize(); + } + + if op.is_commutative() { + return Self::sort_commutative(&lhs, op, &rhs); + } + + Type::InfixExpr(Box::new(lhs), op, Box::new(rhs)) + } + other => other, + } + } + + fn sort_commutative(lhs: &Type, op: BinaryTypeOperator, rhs: &Type) -> Type { + let mut queue = vec![lhs.clone(), rhs.clone()]; + + let mut sorted = BTreeSet::new(); + + let zero_value = if op == BinaryTypeOperator::Addition { 0 } else { 1 }; + let mut constant = zero_value; + + // Push each non-constant term to `sorted` to sort them. Recur on InfixExprs with the same operator. + while let Some(item) = queue.pop() { + match item.canonicalize() { + Type::InfixExpr(lhs, new_op, rhs) if new_op == op => { + queue.push(*lhs); + queue.push(*rhs); + } + Type::Constant(new_constant) => { + if let Some(result) = op.function(constant, new_constant) { + constant = result; + } else { + sorted.insert(Type::Constant(new_constant)); + } + } + other => { + sorted.insert(other); + } + } + } + + if let Some(first) = sorted.pop_first() { + let mut typ = first.clone(); + + for rhs in sorted { + typ = Type::InfixExpr(Box::new(typ), op, Box::new(rhs.clone())); + } + + if constant != zero_value { + typ = Type::InfixExpr(Box::new(typ), op, Box::new(Type::Constant(constant))); + } + + typ + } else { + // Every type must have been a constant + Type::Constant(constant) + } + } + + /// Try to simplify non-constant expressions in the form `(N op1 M) op2 M` + /// where the two `M` terms are expected to cancel out. + /// Precondition: `lhs & rhs are in canonical form` + /// + /// - Simplifies `(N +/- M) -/+ M` to `N` + /// - Simplifies `(N */÷ M) ÷/* M` to `N` + fn try_simplify_non_constants_in_lhs( + lhs: &Type, + op: BinaryTypeOperator, + rhs: &Type, + ) -> Option { + let Type::InfixExpr(l_lhs, l_op, l_rhs) = lhs.follow_bindings() else { + return None; + }; + + // Note that this is exact, syntactic equality, not unification. + // `rhs` is expected to already be in canonical form. + if l_op.inverse() != Some(op) || l_rhs.canonicalize() != *rhs { + return None; + } + + Some(*l_lhs) + } + + /// Try to simplify non-constant expressions in the form `N op1 (M op1 N)` + /// where the two `M` terms are expected to cancel out. + /// Precondition: `lhs & rhs are in canonical form` + /// + /// Unlike `try_simplify_non_constants_in_lhs` we can't simplify `N / (M * N)` + /// Since that should simplify to `1 / M` instead of `M`. + /// + /// - Simplifies `N +/- (M -/+ N)` to `M` + /// - Simplifies `N * (M ÷ N)` to `M` + fn try_simplify_non_constants_in_rhs( + lhs: &Type, + op: BinaryTypeOperator, + rhs: &Type, + ) -> Option { + let Type::InfixExpr(r_lhs, r_op, r_rhs) = rhs.follow_bindings() else { + return None; + }; + + // `N / (M * N)` should be simplified to `1 / M`, but we only handle + // simplifying to `M` in this function. + if op == BinaryTypeOperator::Division && r_op == BinaryTypeOperator::Multiplication { + return None; + } + + // Note that this is exact, syntactic equality, not unification. + // `lhs` is expected to already be in canonical form. + if r_op.inverse() != Some(op) || *lhs != r_rhs.canonicalize() { + return None; + } + + Some(*r_lhs) + } + + /// Given: + /// lhs = `N op C1` + /// rhs = C2 + /// Returns: `(N, op, C1, C2)` if C1 and C2 are constants. + /// Note that the operator here is within the `lhs` term, the operator + /// separating lhs and rhs is not needed. + /// Precondition: `lhs & rhs are in canonical form` + fn parse_partial_constant_expr( + lhs: &Type, + rhs: &Type, + ) -> Option<(Box, BinaryTypeOperator, u32, u32)> { + let rhs = rhs.evaluate_to_u32()?; + + let Type::InfixExpr(l_type, l_op, l_rhs) = lhs.follow_bindings() else { + return None; + }; + + let l_rhs = l_rhs.evaluate_to_u32()?; + Some((l_type, l_op, l_rhs, rhs)) + } + + /// Try to simplify partially constant expressions in the form `(N op1 C1) op2 C2` + /// where C1 and C2 are constants that can be combined (e.g. N + 5 - 3 = N + 2) + /// Precondition: `lhs & rhs are in canonical form` + /// + /// - Simplifies `(N +/- C1) +/- C2` to `N +/- (C1 +/- C2)` if C1 and C2 are constants. + /// - Simplifies `(N */÷ C1) */÷ C2` to `N */÷ (C1 */÷ C2)` if C1 and C2 are constants. + fn try_simplify_partial_constants( + lhs: &Type, + mut op: BinaryTypeOperator, + rhs: &Type, + ) -> Option { + use BinaryTypeOperator::*; + let (l_type, l_op, l_const, r_const) = Type::parse_partial_constant_expr(lhs, rhs)?; + + match (l_op, op) { + (Addition | Subtraction, Addition | Subtraction) => { + // If l_op is a subtraction we want to inverse the rhs operator. + if l_op == Subtraction { + op = op.inverse()?; + } + let result = op.function(l_const, r_const)?; + Some(Type::InfixExpr(l_type, l_op, Box::new(Type::Constant(result)))) + } + (Multiplication | Division, Multiplication | Division) => { + // If l_op is a division we want to inverse the rhs operator. + if l_op == Division { + op = op.inverse()?; + } + // If op is a division we need to ensure it divides evenly + if op == Division && (r_const == 0 || l_const % r_const != 0) { + None + } else { + let result = op.function(l_const, r_const)?; + Some(Type::InfixExpr(l_type, l_op, Box::new(Type::Constant(result)))) + } + } + _ => None, + } + } +} 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 4222d2b585f..b3b6d25480f 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs @@ -4,7 +4,10 @@ use std::{fmt, iter::Map, vec::IntoIter}; use crate::{ lexer::errors::LexerErrorKind, - node_interner::{ExprId, QuotedTypeId}, + node_interner::{ + ExprId, InternedExpressionKind, InternedStatementKind, InternedUnresolvedTypeData, + QuotedTypeId, + }, }; /// Represents a token in noir's grammar - a word, number, @@ -28,6 +31,10 @@ pub enum BorrowedToken<'input> { BlockComment(&'input str, Option), Quote(&'input Tokens), QuotedType(QuotedTypeId), + InternedExpression(InternedExpressionKind), + InternedStatement(InternedStatementKind), + InternedLValue(InternedExpressionKind), + InternedUnresolvedTypeData(InternedUnresolvedTypeData), /// < Less, /// <= @@ -134,6 +141,14 @@ pub enum Token { /// to avoid having to tokenize it, re-parse it, and re-resolve it which /// may change the underlying type. QuotedType(QuotedTypeId), + /// A reference to an interned `ExpressionKind`. + InternedExpr(InternedExpressionKind), + /// A reference to an interned `StatementKind`. + InternedStatement(InternedStatementKind), + /// A reference to an interned `LValue`. + InternedLValue(InternedExpressionKind), + /// A reference to an interned `UnresolvedTypeData`. + InternedUnresolvedTypeData(InternedUnresolvedTypeData), /// < Less, /// <= @@ -233,6 +248,10 @@ pub fn token_to_borrowed_token(token: &Token) -> BorrowedToken<'_> { Token::BlockComment(ref s, _style) => BorrowedToken::BlockComment(s, *_style), Token::Quote(stream) => BorrowedToken::Quote(stream), Token::QuotedType(id) => BorrowedToken::QuotedType(*id), + Token::InternedExpr(id) => BorrowedToken::InternedExpression(*id), + Token::InternedStatement(id) => BorrowedToken::InternedStatement(*id), + Token::InternedLValue(id) => BorrowedToken::InternedLValue(*id), + Token::InternedUnresolvedTypeData(id) => BorrowedToken::InternedUnresolvedTypeData(*id), Token::IntType(ref i) => BorrowedToken::IntType(i.clone()), Token::Less => BorrowedToken::Less, Token::LessEqual => BorrowedToken::LessEqual, @@ -353,8 +372,12 @@ impl fmt::Display for Token { } write!(f, "}}") } - // Quoted types only have an ID so there is nothing to display + // Quoted types and exprs only have an ID so there is nothing to display Token::QuotedType(_) => write!(f, "(type)"), + Token::InternedExpr(_) | Token::InternedStatement(_) | Token::InternedLValue(_) => { + write!(f, "(expr)") + } + Token::InternedUnresolvedTypeData(_) => write!(f, "(type)"), Token::IntType(ref i) => write!(f, "{i}"), Token::Less => write!(f, "<"), Token::LessEqual => write!(f, "<="), @@ -407,6 +430,10 @@ pub enum TokenKind { Attribute, Quote, QuotedType, + InternedExpr, + InternedStatement, + InternedLValue, + InternedUnresolvedTypeData, UnquoteMarker, } @@ -420,6 +447,10 @@ impl fmt::Display for TokenKind { TokenKind::Attribute => write!(f, "attribute"), TokenKind::Quote => write!(f, "quote"), TokenKind::QuotedType => write!(f, "quoted type"), + TokenKind::InternedExpr => write!(f, "interned expr"), + TokenKind::InternedStatement => write!(f, "interned statement"), + TokenKind::InternedLValue => write!(f, "interned lvalue"), + TokenKind::InternedUnresolvedTypeData => write!(f, "interned unresolved type"), TokenKind::UnquoteMarker => write!(f, "macro result"), } } @@ -439,6 +470,10 @@ impl Token { Token::UnquoteMarker(_) => TokenKind::UnquoteMarker, Token::Quote(_) => TokenKind::Quote, Token::QuotedType(_) => TokenKind::QuotedType, + Token::InternedExpr(_) => TokenKind::InternedExpr, + Token::InternedStatement(_) => TokenKind::InternedStatement, + Token::InternedLValue(_) => TokenKind::InternedLValue, + Token::InternedUnresolvedTypeData(_) => TokenKind::InternedUnresolvedTypeData, tok => TokenKind::Token(tok.clone()), } } @@ -925,10 +960,13 @@ pub enum Keyword { Trait, TraitConstraint, TraitDefinition, + TraitImpl, Type, TypeType, Unchecked, Unconstrained, + UnresolvedType, + Unsafe, Use, Where, While, @@ -977,10 +1015,13 @@ 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::UnresolvedType => write!(f, "UnresolvedType"), + Keyword::Unsafe => write!(f, "unsafe"), Keyword::Use => write!(f, "use"), Keyword::Where => write!(f, "where"), Keyword::While => write!(f, "while"), @@ -1031,11 +1072,14 @@ 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, + "UnresolvedType" => Keyword::UnresolvedType, + "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 c437676b605..0ac13a58ecf 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 f2ed9433e61..eb6b4bf7bd4 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/ast.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/ast.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use acvm::FieldElement; use iter_extended::vecmap; use noirc_errors::{ @@ -67,6 +69,12 @@ pub struct LocalId(pub u32); #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct FuncId(pub u32); +impl Display for FuncId { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + #[derive(Debug, Clone, Hash)] pub struct Ident { pub location: Option, @@ -288,7 +296,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 { @@ -352,16 +365,12 @@ impl Program { FuncId(0) } - pub fn take_main_body(&mut self) -> Expression { - self.take_function_body(FuncId(0)) - } - /// Takes a function body by replacing it with `false` and /// returning the previous value pub fn take_function_body(&mut self, function: FuncId) -> Expression { - let main = &mut self.functions[function.0 as usize]; - let replacement = Expression::Literal(Literal::Bool(false)); - std::mem::replace(&mut main.body, replacement) + let function_definition = &mut self[function]; + let replacement = Expression::Block(vec![]); + std::mem::replace(&mut function_definition.body, replacement) } } @@ -419,7 +428,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/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/errors.rs index df61c138c02..ce8ef3572e6 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/errors.rs @@ -1,11 +1,11 @@ use noirc_errors::{CustomDiagnostic, FileDiagnostic, Location}; -use crate::hir::comptime::InterpreterError; +use crate::{hir::comptime::InterpreterError, Type}; #[derive(Debug)] pub enum MonomorphizationError { - UnknownArrayLength { location: Location }, - TypeAnnotationsNeeded { location: Location }, + UnknownArrayLength { length: Type, location: Location }, + NoDefaultType { location: Location }, InternalError { message: &'static str, location: Location }, InterpreterError(InterpreterError), } @@ -13,9 +13,9 @@ pub enum MonomorphizationError { impl MonomorphizationError { fn location(&self) -> Location { match self { - MonomorphizationError::UnknownArrayLength { location } + MonomorphizationError::UnknownArrayLength { location, .. } | MonomorphizationError::InternalError { location, .. } - | MonomorphizationError::TypeAnnotationsNeeded { location } => *location, + | MonomorphizationError::NoDefaultType { location, .. } => *location, MonomorphizationError::InterpreterError(error) => error.get_location(), } } @@ -32,16 +32,20 @@ impl From for FileDiagnostic { impl MonomorphizationError { fn into_diagnostic(self) -> CustomDiagnostic { - let message = match self { - MonomorphizationError::UnknownArrayLength { .. } => { - "Length of generic array could not be determined." + let message = match &self { + MonomorphizationError::UnknownArrayLength { length, .. } => { + format!("Could not determine array length `{length}`") } - MonomorphizationError::TypeAnnotationsNeeded { .. } => "Type annotations needed", - MonomorphizationError::InterpreterError(error) => return (&error).into(), - MonomorphizationError::InternalError { message, .. } => message, + MonomorphizationError::NoDefaultType { location } => { + let message = "Type annotation needed".into(); + let secondary = "Could not determine type of generic argument".into(); + return CustomDiagnostic::simple_error(message, secondary, location.span); + } + MonomorphizationError::InterpreterError(error) => return error.into(), + MonomorphizationError::InternalError { message, .. } => message.to_string(), }; let location = self.location(); - CustomDiagnostic::simple_error(message.into(), String::new(), location.span) + CustomDiagnostic::simple_error(message, String::new(), location.span) } } 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 5ac730db400..87b55540bbd 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -11,7 +11,7 @@ use crate::ast::{FunctionKind, IntegerBitSize, Signedness, UnaryOp, Visibility}; use crate::hir::comptime::InterpreterError; use crate::hir::type_check::NoMatchingImplFoundError; -use crate::node_interner::ExprId; +use crate::node_interner::{ExprId, ImplSearchErrorKind}; use crate::{ debug::DebugInstrumenter, hir_def::{ @@ -301,6 +301,7 @@ impl<'interner> Monomorphizer<'interner> { } let meta = self.interner.function_meta(&f).clone(); + let mut func_sig = meta.function_signature(); // Follow the bindings of the function signature for entry points // which are not `main` such as foldable functions. @@ -442,6 +443,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)?; @@ -568,7 +570,7 @@ impl<'interner> Monomorphizer<'interner> { let length = length.evaluate_to_u32().ok_or_else(|| { let location = self.interner.expr_location(&array); - MonomorphizationError::UnknownArrayLength { location } + MonomorphizationError::UnknownArrayLength { location, length } })?; let contents = try_vecmap(0..length, |_| self.expr(repeated_element))?; @@ -844,6 +846,14 @@ impl<'interner> Monomorphizer<'interner> { return self.resolve_trait_method_expr(expr_id, typ, method); } + // Ensure all instantiation bindings are bound. + // This ensures even unused type variables like `fn foo() {}` have concrete types + if let Some(bindings) = self.interner.try_get_instantiation_bindings(expr_id) { + for (_, binding) in bindings.values() { + Self::check_type(binding, ident.location)?; + } + } + let definition = self.interner.definition(ident.id); let ident = match &definition.kind { DefinitionKind::Function(func_id) => { @@ -935,7 +945,10 @@ impl<'interner> Monomorphizer<'interner> { let element = Box::new(Self::convert_type(element.as_ref(), location)?); let length = match length.evaluate_to_u32() { Some(length) => length, - None => return Err(MonomorphizationError::TypeAnnotationsNeeded { location }), + None => { + let length = length.as_ref().clone(); + return Err(MonomorphizationError::UnknownArrayLength { location, length }); + } }; ast::Type::Array(length, element) } @@ -968,7 +981,7 @@ impl<'interner> Monomorphizer<'interner> { // and within a larger generic type. let default = match kind.default_type() { Some(typ) => typ, - None => return Err(MonomorphizationError::TypeAnnotationsNeeded { location }), + None => return Err(MonomorphizationError::NoDefaultType { location }), }; let monomorphized_default = Self::convert_type(&default, location)?; @@ -1003,15 +1016,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!( @@ -1026,11 +1041,12 @@ impl<'interner> Monomorphizer<'interner> { ast::Type::MutableReference(Box::new(element)) } - HirType::Forall(_, _) - | HirType::Constant(_) - | HirType::InfixExpr(..) - | HirType::Error => { - unreachable!("Unexpected type {} found", typ) + HirType::Forall(_, _) | HirType::Constant(_) | HirType::InfixExpr(..) => { + unreachable!("Unexpected type {typ} found") + } + HirType::Error => { + let message = "Unexpected Type::Error found during monomorphization"; + return Err(MonomorphizationError::InternalError { message, location }); } HirType::Quoted(_) => unreachable!("Tried to translate Code type into runtime code"), }) @@ -1070,7 +1086,7 @@ impl<'interner> Monomorphizer<'interner> { // and within a larger generic type. let default = match kind.default_type() { Some(typ) => typ, - None => return Err(MonomorphizationError::TypeAnnotationsNeeded { location }), + None => return Err(MonomorphizationError::NoDefaultType { location }), }; Self::check_type(&default, location) @@ -1100,7 +1116,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 +1138,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 +1148,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 +1515,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 +1588,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 +1618,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 +1673,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 +1713,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 +1736,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 +1746,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 +1761,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 +1782,7 @@ impl<'interner> Monomorphizer<'interner> { parameter_types.to_owned(), Box::new(ret_type.clone()), Box::new(env_type.clone()), + unconstrained, ), }) } @@ -1934,28 +1958,38 @@ pub fn resolve_trait_method( let impl_id = match trait_impl { TraitImplKind::Normal(impl_id) => impl_id, TraitImplKind::Assumed { object_type, trait_generics } => { + let location = interner.expr_location(&expr_id); + match interner.lookup_trait_implementation( &object_type, method.trait_id, - &trait_generics, + &trait_generics.ordered, + &trait_generics.named, ) { Ok(TraitImplKind::Normal(impl_id)) => impl_id, Ok(TraitImplKind::Assumed { .. }) => { - let location = interner.expr_location(&expr_id); return Err(InterpreterError::NoImpl { location }); } - Err(constraints) => { - let location = interner.expr_location(&expr_id); + Err(ImplSearchErrorKind::TypeAnnotationsNeededOnObjectType) => { + return Err(InterpreterError::TypeAnnotationsNeededForMethodCall { location }); + } + Err(ImplSearchErrorKind::Nested(constraints)) => { if let Some(error) = NoMatchingImplFoundError::new(interner, constraints, location.span) { let file = location.file; return Err(InterpreterError::NoMatchingImplFound { error, file }); } else { - let location = interner.expr_location(&expr_id); return Err(InterpreterError::NoImpl { location }); } } + Err(ImplSearchErrorKind::MultipleMatching(candidates)) => { + return Err(InterpreterError::MultipleMatchingImpls { + object_type, + location, + candidates, + }); + } } } }; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/printer.rs b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/printer.rs index 9b1eeecdc1a..b6421b26a03 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/printer.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/printer.rs @@ -19,7 +19,7 @@ impl AstPrinter { write!( f, "fn {}$f{}({}) -> {} {{", - function.name, function.id.0, params, function.return_type + function.name, function.id, params, function.return_type )?; self.indent_level += 1; self.print_expr_expect_block(&function.body, f)?; @@ -291,7 +291,7 @@ impl Display for Definition { fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { match self { Definition::Local(id) => write!(f, "l{}", id.0), - Definition::Function(id) => write!(f, "f{}", id.0), + Definition::Function(id) => write!(f, "f{}", id), Definition::Builtin(name) => write!(f, "{name}"), Definition::LowLevel(name) => write!(f, "{name}"), Definition::Oracle(name) => write!(f, "{name}"), 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 43e742b940e..32f25790e12 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs @@ -13,12 +13,19 @@ use petgraph::prelude::DiGraph; use petgraph::prelude::NodeIndex as PetGraphIndex; use rustc_hash::FxHashMap as HashMap; +use crate::ast::ExpressionKind; use crate::ast::Ident; +use crate::ast::LValue; +use crate::ast::StatementKind; +use crate::ast::UnresolvedTypeData; use crate::graph::CrateId; 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::hir::type_check::generics::TraitGenerics; +use crate::hir_def::traits::NamedType; +use crate::macros_api::ModuleDefId; use crate::macros_api::UnaryOp; use crate::QuotedType; @@ -48,7 +55,7 @@ const IMPL_SEARCH_RECURSION_LIMIT: u32 = 10; pub struct ModuleAttributes { pub name: String, pub location: Location, - pub parent: LocalModuleId, + pub parent: Option, } type StructAttributes = Vec; @@ -132,6 +139,11 @@ pub struct NodeInterner { next_trait_implementation_id: usize, + /// The associated types for each trait impl. + /// This is stored outside of the TraitImpl object since it is required before that object is + /// created, when resolving the type signature of each method in the impl. + trait_impl_associated_types: HashMap>, + /// Trait implementations on each type. This is expected to always have the same length as /// `self.trait_implementations`. /// @@ -179,10 +191,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, @@ -200,6 +212,15 @@ pub struct NodeInterner { /// the actual type since types do not implement Send or Sync. quoted_types: noirc_arena::Arena, + // Interned `ExpressionKind`s during comptime code. + interned_expression_kinds: noirc_arena::Arena, + + // Interned `StatementKind`s during comptime code. + interned_statement_kinds: noirc_arena::Arena, + + // Interned `UnresolvedTypeData`s during comptime code. + interned_unresolved_type_datas: noirc_arena::Arena, + /// Determins whether to run in LSP mode. In LSP mode references are tracked. pub(crate) lsp_mode: bool, @@ -231,6 +252,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. @@ -301,10 +327,17 @@ pub enum TraitImplKind { /// /// The reference `Into::into(x)` would have inferred generics, but /// `x.into()` with a `X: Into` in scope would not. - trait_generics: Vec, + trait_generics: TraitGenerics, }, } +/// When searching for a trait impl, these are the types of errors we can expect +pub enum ImplSearchErrorKind { + TypeAnnotationsNeededOnObjectType, + Nested(Vec), + MultipleMatching(Vec), +} + /// Represents the methods on a given type that each share the same name. /// /// Methods are split into inherent methods and trait methods. If there is @@ -560,6 +593,15 @@ pub struct GlobalInfo { #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct QuotedTypeId(noirc_arena::Index); +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct InternedExpressionKind(noirc_arena::Index); + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct InternedStatementKind(noirc_arena::Index); + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct InternedUnresolvedTypeData(noirc_arena::Index); + impl Default for NodeInterner { fn default() -> Self { NodeInterner { @@ -597,12 +639,17 @@ impl Default for NodeInterner { type_alias_ref: Vec::new(), type_ref_locations: Vec::new(), quoted_types: Default::default(), + interned_expression_kinds: Default::default(), + interned_statement_kinds: Default::default(), + interned_unresolved_type_datas: Default::default(), lsp_mode: false, location_indices: LocationIndices::default(), 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()], + trait_impl_associated_types: HashMap::default(), } } } @@ -644,21 +691,18 @@ impl NodeInterner { type_id: TraitId, unresolved_trait: &UnresolvedTrait, generics: Generics, + associated_types: Generics, ) { - let self_type_typevar_id = self.next_type_variable_id(); - let new_trait = Trait { id: type_id, name: unresolved_trait.trait_def.name.clone(), crate_id: unresolved_trait.crate_id, location: Location::new(unresolved_trait.trait_def.span, unresolved_trait.file_id), generics, - self_type_typevar_id, - self_type_typevar: TypeVariable::unbound(self_type_typevar_id), + self_type_typevar: TypeVariable::unbound(self.next_type_variable_id()), methods: Vec::new(), method_ids: unresolved_trait.method_ids.clone(), - constants: Vec::new(), - types: Vec::new(), + associated_types, }; self.traits.insert(type_id, new_trait); @@ -910,6 +954,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 +996,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 @@ -1027,7 +1072,7 @@ impl NodeInterner { } pub fn try_module_parent(&self, module_id: &ModuleId) -> Option { - self.try_module_attributes(module_id).map(|attrs| attrs.parent) + self.try_module_attributes(module_id).and_then(|attrs| attrs.parent) } pub fn global_attributes(&self, global_id: &GlobalId) -> &[SecondaryAttribute] { @@ -1131,22 +1176,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 +1249,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; } } @@ -1259,6 +1314,10 @@ impl NodeInterner { &self.instantiation_bindings[&expr_id] } + pub fn try_get_instantiation_bindings(&self, expr_id: ExprId) -> Option<&TypeBindings> { + self.instantiation_bindings.get(&expr_id) + } + pub fn get_field_index(&self, expr_id: ExprId) -> usize { self.field_indices[&expr_id] } @@ -1300,8 +1359,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 +1377,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 @@ -1353,9 +1418,14 @@ impl NodeInterner { object_type: &Type, trait_id: TraitId, trait_generics: &[Type], - ) -> Result> { - let (impl_kind, bindings) = - self.try_lookup_trait_implementation(object_type, trait_id, trait_generics)?; + trait_associated_types: &[NamedType], + ) -> Result { + let (impl_kind, bindings) = self.try_lookup_trait_implementation( + object_type, + trait_id, + trait_generics, + trait_associated_types, + )?; Type::apply_type_bindings(bindings); Ok(impl_kind) @@ -1370,23 +1440,14 @@ impl NodeInterner { ) -> Vec<&TraitImplKind> { let trait_impl = self.trait_implementation_map.get(&trait_id); - trait_impl - .map(|trait_impl| { - trait_impl - .iter() - .filter_map(|(typ, impl_kind)| match &typ { - Type::Forall(_, typ) => { - if typ.deref() == object_type { - Some(impl_kind) - } else { - None - } - } - _ => None, - }) - .collect() - }) - .unwrap_or_default() + let trait_impl = trait_impl.map(|trait_impl| { + let impls = trait_impl.iter().filter_map(|(typ, impl_kind)| match &typ { + Type::Forall(_, typ) => (typ.deref() == object_type).then_some(impl_kind), + _ => None, + }); + impls.collect() + }); + trait_impl.unwrap_or_default() } /// Similar to `lookup_trait_implementation` but does not apply any type bindings on success. @@ -1399,12 +1460,14 @@ impl NodeInterner { object_type: &Type, trait_id: TraitId, trait_generics: &[Type], - ) -> Result<(TraitImplKind, TypeBindings), Vec> { + trait_associated_types: &[NamedType], + ) -> Result<(TraitImplKind, TypeBindings), ImplSearchErrorKind> { let mut bindings = TypeBindings::new(); let impl_kind = self.lookup_trait_implementation_helper( object_type, trait_id, trait_generics, + trait_associated_types, &mut bindings, IMPL_SEARCH_RECURSION_LIMIT, )?; @@ -1421,63 +1484,73 @@ impl NodeInterner { object_type: &Type, trait_id: TraitId, trait_generics: &[Type], + trait_associated_types: &[NamedType], type_bindings: &mut TypeBindings, recursion_limit: u32, - ) -> Result> { + ) -> Result { let make_constraint = || { - TraitConstraint::new( - object_type.clone(), + let ordered = trait_generics.to_vec(); + let named = trait_associated_types.to_vec(); + TraitConstraint { + typ: object_type.clone(), trait_id, - trait_generics.to_vec(), - Span::default(), - ) + trait_generics: TraitGenerics { ordered, named }, + span: Span::default(), + } }; + let nested_error = || ImplSearchErrorKind::Nested(vec![make_constraint()]); + // Prevent infinite recursion when looking for impls if recursion_limit == 0 { - return Err(vec![make_constraint()]); + return Err(nested_error()); } let object_type = object_type.substitute(type_bindings); // If the object type isn't known, just return an error saying type annotations are needed. if object_type.is_bindable() { - return Err(Vec::new()); + return Err(ImplSearchErrorKind::TypeAnnotationsNeededOnObjectType); } - let impls = - self.trait_implementation_map.get(&trait_id).ok_or_else(|| vec![make_constraint()])?; + let impls = self.trait_implementation_map.get(&trait_id).ok_or_else(nested_error)?; let mut matching_impls = Vec::new(); + let mut where_clause_error = None; - let mut where_clause_errors = Vec::new(); - - for (existing_object_type2, impl_kind) in impls { + for (existing_object_type, impl_kind) in impls { // Bug: We're instantiating only the object type's generics here, not all of the trait's generics like we need to let (existing_object_type, instantiation_bindings) = - existing_object_type2.instantiate(self); + existing_object_type.instantiate(self); let mut fresh_bindings = type_bindings.clone(); - let mut check_trait_generics = |impl_generics: &[Type]| { - trait_generics.iter().zip(impl_generics).all(|(trait_generic, impl_generic2)| { - let impl_generic = impl_generic2.substitute(&instantiation_bindings); - trait_generic.try_unify(&impl_generic, &mut fresh_bindings).is_ok() - }) - }; + let mut check_trait_generics = + |impl_generics: &[Type], impl_associated_types: &[NamedType]| { + trait_generics.iter().zip(impl_generics).all(|(trait_generic, impl_generic)| { + let impl_generic = impl_generic.force_substitute(&instantiation_bindings); + trait_generic.try_unify(&impl_generic, &mut fresh_bindings).is_ok() + }) && trait_associated_types.iter().zip(impl_associated_types).all( + |(trait_generic, impl_generic)| { + let impl_generic2 = + impl_generic.typ.force_substitute(&instantiation_bindings); + trait_generic.typ.try_unify(&impl_generic2, &mut fresh_bindings).is_ok() + }, + ) + }; - let generics_match = match impl_kind { + let trait_generics = match impl_kind { TraitImplKind::Normal(id) => { let shared_impl = self.get_trait_implementation(*id); let shared_impl = shared_impl.borrow(); - check_trait_generics(&shared_impl.trait_generics) - } - TraitImplKind::Assumed { trait_generics, .. } => { - check_trait_generics(trait_generics) + let named = self.get_associated_types_for_impl(*id).to_vec(); + let ordered = shared_impl.trait_generics.clone(); + TraitGenerics { named, ordered } } + TraitImplKind::Assumed { trait_generics, .. } => trait_generics.clone(), }; - if !generics_match { + if !check_trait_generics(&trait_generics.ordered, &trait_generics.named) { continue; } @@ -1486,34 +1559,48 @@ impl NodeInterner { let trait_impl = self.get_trait_implementation(*impl_id); let trait_impl = trait_impl.borrow(); - if let Err(errors) = self.validate_where_clause( + if let Err(error) = self.validate_where_clause( &trait_impl.where_clause, &mut fresh_bindings, &instantiation_bindings, recursion_limit, ) { // Only keep the first errors we get from a failing where clause - if where_clause_errors.is_empty() { - where_clause_errors.extend(errors); + if where_clause_error.is_none() { + where_clause_error = Some(error); } continue; } } - matching_impls.push((impl_kind.clone(), fresh_bindings)); + let constraint = TraitConstraint { + typ: existing_object_type, + trait_id, + trait_generics, + span: Span::default(), + }; + matching_impls.push((impl_kind.clone(), fresh_bindings, constraint)); } } if matching_impls.len() == 1 { - let (impl_, fresh_bindings) = matching_impls.pop().unwrap(); + let (impl_, fresh_bindings, _) = matching_impls.pop().unwrap(); *type_bindings = fresh_bindings; Ok(impl_) } else if matching_impls.is_empty() { - where_clause_errors.push(make_constraint()); - Err(where_clause_errors) + let mut errors = match where_clause_error { + Some((_, ImplSearchErrorKind::Nested(errors))) => errors, + Some((constraint, _other)) => vec![constraint], + None => vec![], + }; + errors.push(make_constraint()); + Err(ImplSearchErrorKind::Nested(errors)) } else { - // multiple matching impls, type annotations needed - Err(vec![]) + let impls = vecmap(matching_impls, |(_, _, constraint)| { + let name = &self.get_trait(constraint.trait_id).name; + format!("{}: {name}{}", constraint.typ, constraint.trait_generics) + }); + Err(ImplSearchErrorKind::MultipleMatching(impls)) } } @@ -1525,27 +1612,36 @@ impl NodeInterner { type_bindings: &mut TypeBindings, instantiation_bindings: &TypeBindings, recursion_limit: u32, - ) -> Result<(), Vec> { + ) -> Result<(), (TraitConstraint, ImplSearchErrorKind)> { for constraint in where_clause { // Instantiation bindings are generally safe to force substitute into the same type. // This is needed here to undo any bindings done to trait methods by monomorphization. - // Otherwise, an impl for (A, B) could get narrowed to only an impl for e.g. (u8, u16). + // Otherwise, an impl for any (A, B) could get narrowed to only an impl for e.g. (u8, u16). let constraint_type = constraint.typ.force_substitute(instantiation_bindings).substitute(type_bindings); - let trait_generics = vecmap(&constraint.trait_generics, |generic| { + let trait_generics = vecmap(&constraint.trait_generics.ordered, |generic| { generic.force_substitute(instantiation_bindings).substitute(type_bindings) }); + let trait_associated_types = vecmap(&constraint.trait_generics.named, |generic| { + let typ = generic.typ.force_substitute(instantiation_bindings); + NamedType { name: generic.name.clone(), typ: typ.substitute(type_bindings) } + }); + + // We can ignore any associated types on the constraint since those should not affect + // which impl we choose. self.lookup_trait_implementation_helper( &constraint_type, constraint.trait_id, &trait_generics, + &trait_associated_types, // Use a fresh set of type bindings here since the constraint_type originates from // our impl list, which we don't want to bind to. type_bindings, recursion_limit - 1, - )?; + ) + .map_err(|error| (constraint.clone(), error))?; } Ok(()) @@ -1563,10 +1659,16 @@ impl NodeInterner { &mut self, object_type: Type, trait_id: TraitId, - trait_generics: Vec, + trait_generics: TraitGenerics, ) -> bool { // Make sure there are no overlapping impls - if self.try_lookup_trait_implementation(&object_type, trait_id, &trait_generics).is_ok() { + let existing = self.try_lookup_trait_implementation( + &object_type, + trait_id, + &trait_generics.ordered, + &trait_generics.named, + ); + if existing.is_ok() { return false; } @@ -1580,7 +1682,6 @@ impl NodeInterner { &mut self, object_type: Type, trait_id: TraitId, - trait_generics: Vec, impl_id: TraitImplId, impl_generics: GenericTypeVars, trait_impl: Shared, @@ -1602,6 +1703,9 @@ impl NodeInterner { let instantiated_object_type = object_type.substitute(&substitutions); + let trait_generics = &trait_impl.borrow().trait_generics; + let associated_types = self.get_associated_types_for_impl(impl_id); + // Ignoring overlapping `TraitImplKind::Assumed` impls here is perfectly fine. // It should never happen since impls are defined at global scope, but even // if they were, we should never prevent defining a new impl because a 'where' @@ -1609,7 +1713,8 @@ impl NodeInterner { if let Ok((TraitImplKind::Normal(existing), _)) = self.try_lookup_trait_implementation( &instantiated_object_type, trait_id, - &trait_generics, + trait_generics, + associated_types, ) { let existing_impl = self.get_trait_implementation(existing); let existing_impl = existing_impl.borrow(); @@ -1648,7 +1753,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 +1778,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 +1787,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 +1884,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 +1908,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)] @@ -1957,6 +2067,41 @@ impl NodeInterner { &self.quoted_types[id.0] } + pub fn push_expression_kind(&mut self, expr: ExpressionKind) -> InternedExpressionKind { + InternedExpressionKind(self.interned_expression_kinds.insert(expr)) + } + + pub fn get_expression_kind(&self, id: InternedExpressionKind) -> &ExpressionKind { + &self.interned_expression_kinds[id.0] + } + + pub fn push_statement_kind(&mut self, statement: StatementKind) -> InternedStatementKind { + InternedStatementKind(self.interned_statement_kinds.insert(statement)) + } + + pub fn get_statement_kind(&self, id: InternedStatementKind) -> &StatementKind { + &self.interned_statement_kinds[id.0] + } + + pub fn push_lvalue(&mut self, lvalue: LValue) -> InternedExpressionKind { + self.push_expression_kind(lvalue.as_expression().kind) + } + + pub fn get_lvalue(&self, id: InternedExpressionKind, span: Span) -> LValue { + LValue::from_expression_kind(self.get_expression_kind(id).clone(), span) + } + + pub fn push_unresolved_type_data( + &mut self, + typ: UnresolvedTypeData, + ) -> InternedUnresolvedTypeData { + InternedUnresolvedTypeData(self.interned_unresolved_type_datas.insert(typ)) + } + + pub fn get_unresolved_type_data(&self, id: InternedUnresolvedTypeData) -> &UnresolvedTypeData { + &self.interned_unresolved_type_datas[id.0] + } + /// Returns the type of an operator (which is always a function), along with its return type. pub fn get_infix_operator_type( &self, @@ -1977,7 +2122,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,12 +2131,65 @@ 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 { self.lsp_mode } + + pub fn set_associated_types_for_impl( + &mut self, + impl_id: TraitImplId, + associated_types: Vec, + ) { + self.trait_impl_associated_types.insert(impl_id, associated_types); + } + + pub fn get_associated_types_for_impl(&self, impl_id: TraitImplId) -> &[NamedType] { + &self.trait_impl_associated_types[&impl_id] + } + + pub fn find_associated_type_for_impl( + &self, + impl_id: TraitImplId, + type_name: &str, + ) -> Option<&Type> { + let types = self.trait_impl_associated_types.get(&impl_id)?; + types.iter().find(|typ| typ.name.0.contents == type_name).map(|typ| &typ.typ) + } + + /// Return a set of TypeBindings to bind types from the parent trait to those from the trait impl. + pub fn trait_to_impl_bindings( + &self, + trait_id: TraitId, + impl_id: TraitImplId, + trait_impl_generics: &[Type], + impl_self_type: Type, + ) -> TypeBindings { + let mut bindings = TypeBindings::new(); + let the_trait = self.get_trait(trait_id); + let trait_generics = the_trait.generics.clone(); + + let self_type_var = the_trait.self_type_typevar.clone(); + bindings.insert(self_type_var.id(), (self_type_var, impl_self_type)); + + for (trait_generic, trait_impl_generic) in trait_generics.iter().zip(trait_impl_generics) { + let type_var = trait_generic.type_var.clone(); + bindings.insert(type_var.id(), (type_var, trait_impl_generic.clone())); + } + + // Now that the normal bindings are added, we still need to bind the associated types + let impl_associated_types = self.get_associated_types_for_impl(impl_id); + let trait_associated_types = &the_trait.associated_types; + + for (trait_type, impl_type) in trait_associated_types.iter().zip(impl_associated_types) { + let type_variable = trait_type.type_var.clone(); + bindings.insert(type_variable.id(), (type_variable, impl_type.typ.clone())); + } + + bindings + } } impl Methods { @@ -2017,7 +2215,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 +2225,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 +2276,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 36d3ce5898c..2e38d7ae83b 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")] @@ -52,6 +64,10 @@ pub enum ParserErrorReason { ForbiddenNumericGenericType, #[error("Invalid call data identifier, must be a number. E.g `call_data(0)`")] InvalidCallDataIdentifier, + #[error("Associated types are not allowed in paths")] + AssociatedTypesNotAllowedInPaths, + #[error("Associated types are not allowed on a method call")] + AssociatedTypesNotAllowedInMethodCalls, } /// Represents a parsing error, or a parsing error in the making. diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs index f1972bcb9b5..11944cd3304 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs @@ -25,7 +25,7 @@ use noirc_errors::Span; pub use parser::path::path_no_turbofish; pub use parser::traits::trait_bound; pub use parser::{ - block, expression, fresh_statement, parse_program, parse_type, pattern, top_level_items, + block, expression, fresh_statement, lvalue, parse_program, parse_type, pattern, top_level_items, }; #[derive(Debug, Clone)] 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 5a97d66df9a..8a894ec2b83 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs @@ -36,9 +36,9 @@ use super::{ }; use super::{spanned, Item, ItemKind}; use crate::ast::{ - BinaryOp, BinaryOpKind, BlockExpression, ForLoopStatement, ForRange, Ident, IfExpression, - InfixExpression, LValue, Literal, ModuleDeclaration, NoirTypeAlias, Param, Path, Pattern, - Recoverable, Statement, TypeImpl, UnaryRhsMemberAccess, UnaryRhsMethodCall, UseTree, + BinaryOp, BinaryOpKind, BlockExpression, ForLoopStatement, ForRange, GenericTypeArgs, Ident, + IfExpression, InfixExpression, LValue, Literal, ModuleDeclaration, NoirTypeAlias, Param, Path, + Pattern, Recoverable, Statement, TypeImpl, UnaryRhsMemberAccess, UnaryRhsMethodCall, UseTree, UseTreeKind, Visibility, }; use crate::ast::{ @@ -73,7 +73,9 @@ mod test_helpers; use literals::literal; use path::{maybe_empty_path, path}; -use primitives::{dereference, ident, negation, not, nothing, right_shift_operator, token_kind}; +use primitives::{ + dereference, ident, interned_expr, negation, not, nothing, right_shift_operator, token_kind, +}; use traits::where_clause; /// Entry function for the parser - also handles lexing internally. @@ -219,13 +221,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; @@ -319,7 +335,9 @@ fn self_parameter() -> impl NoirParser { .map(|(pattern_keyword, ident_span)| { let ident = Ident::new("self".to_string(), ident_span); let path = Path::from_single("Self".to_owned(), ident_span); - let mut self_type = UnresolvedTypeData::Named(path, vec![], true).with_span(ident_span); + let no_args = GenericTypeArgs::default(); + let mut self_type = + UnresolvedTypeData::Named(path, no_args, true).with_span(ident_span); let mut pattern = Pattern::Identifier(ident); match pattern_keyword { @@ -377,6 +395,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))) @@ -407,9 +426,9 @@ fn check_statements_require_semicolon( /// Parse an optional ': type' fn optional_type_annotation<'a>() -> impl NoirParser + 'a { - ignore_then_commit(just(Token::Colon), parse_type()) - .or_not() - .map(|r#type| r#type.unwrap_or_else(UnresolvedType::unspecified)) + ignore_then_commit(just(Token::Colon), parse_type()).or_not().map_with_span(|r#type, span| { + r#type.unwrap_or(UnresolvedTypeData::Unspecified.with_span(span)) + }) } fn module_declaration() -> impl NoirParser { @@ -470,6 +489,7 @@ where continue_statement(), return_statement(expr_parser.clone()), comptime_statement(expr_parser.clone(), expr_no_constructors, statement), + interned_statement(), expr_parser.map(StatementKind::Expression), )) }) @@ -509,6 +529,15 @@ where keyword(Keyword::Comptime).ignore_then(comptime_statement).map(StatementKind::Comptime) } +pub(super) fn interned_statement() -> impl NoirParser { + token_kind(TokenKind::InternedStatement).map(|token| match token { + Token::InternedStatement(id) => StatementKind::Interned(id), + _ => { + unreachable!("token_kind(InternedStatement) guarantees we parse an interned statement") + } + }) +} + /// Comptime in an expression position only accepts entire blocks fn comptime_expr<'a, S>(statement: S) -> impl NoirParser + 'a where @@ -519,6 +548,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 @@ -616,7 +654,7 @@ enum LValueRhs { Index(Expression, Span), } -fn lvalue<'a, P>(expr_parser: P) -> impl NoirParser + 'a +pub fn lvalue<'a, P>(expr_parser: P) -> impl NoirParser + 'a where P: ExprParser + 'a, { @@ -629,7 +667,15 @@ where let parenthesized = lvalue.delimited_by(just(Token::LeftParen), just(Token::RightParen)); - let term = choice((parenthesized, dereferences, l_ident)); + let interned = + token_kind(TokenKind::InternedLValue).map_with_span(|token, span| match token { + Token::InternedLValue(id) => LValue::Interned(id, span), + _ => unreachable!( + "token_kind(InternedLValue) guarantees we parse an interned lvalue" + ), + }); + + let term = choice((parenthesized, dereferences, l_ident, interned)); let l_member_rhs = just(Token::Dot).ignore_then(field_name()).map_with_span(LValueRhs::MemberAccess); @@ -847,6 +893,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)` @@ -875,10 +924,15 @@ where let method_call_rhs = turbofish .then(just(Token::Bang).or_not()) .then(parenthesized(expression_list(expr_parser.clone()))) - .map(|((turbofish, macro_call), args)| UnaryRhsMethodCall { - turbofish, - macro_call: macro_call.is_some(), - args, + .validate(|((turbofish, macro_call), args), span, emit| { + if turbofish.as_ref().map_or(false, |generics| !generics.named_args.is_empty()) { + let reason = ParserErrorReason::AssociatedTypesNotAllowedInMethodCalls; + emit(ParserError::with_reason(reason, span)); + } + + let macro_call = macro_call.is_some(); + let turbofish = turbofish.map(|generics| generics.ordered_args); + UnaryRhsMethodCall { turbofish, macro_call, args } }); // `.foo` or `.foo(args)` in `atom.foo` or `atom.foo(args)` @@ -890,7 +944,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 +967,7 @@ where UnaryRhs::MemberAccess(field) => { Expression::member_access_or_method_call(lhs, field, span) } + UnaryRhs::JustADot => lhs, }, ) } @@ -931,10 +995,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,13 +1166,15 @@ 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(), literal(), as_trait_path(parse_type()).map(ExpressionKind::AsTraitPath), macro_quote_marker(), + interned_expr(), )) .map_with_span(Expression::new) .or(parenthesized(expr_parser.clone()).map_with_span(|sub_expr, span| { @@ -1407,6 +1495,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 +1779,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 3de48d2e02a..56760898374 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/lambdas.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/lambdas.rs index 2b4a1d547c0..5ef0b918375 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/lambdas.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/lambdas.rs @@ -2,6 +2,7 @@ use chumsky::{primitive::just, Parser}; use super::{parse_type, pattern}; use crate::ast::{Expression, ExpressionKind, Lambda, Pattern, UnresolvedType}; +use crate::macros_api::UnresolvedTypeData; use crate::{ parser::{labels::ParsingRuleLabel, parameter_name_recovery, parameter_recovery, NoirParser}, token::Token, @@ -23,9 +24,10 @@ fn lambda_parameters() -> impl NoirParser> { let typ = parse_type().recover_via(parameter_recovery()); let typ = just(Token::Colon).ignore_then(typ); - let parameter = pattern() - .recover_via(parameter_name_recovery()) - .then(typ.or_not().map(|typ| typ.unwrap_or_else(UnresolvedType::unspecified))); + let parameter = + pattern().recover_via(parameter_name_recovery()).then(typ.or_not().map_with_span( + |typ, span| typ.unwrap_or(UnresolvedTypeData::Unspecified.with_span(span)), + )); parameter .separated_by(just(Token::Comma)) @@ -37,5 +39,5 @@ fn lambda_return_type() -> impl NoirParser { just(Token::Arrow) .ignore_then(parse_type()) .or_not() - .map(|ret| ret.unwrap_or_else(UnresolvedType::unspecified)) + .map_with_span(|ret, span| ret.unwrap_or(UnresolvedTypeData::Unspecified.with_span(span))) } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/path.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/path.rs index ae3a1bc0b93..ea121c6f6db 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/path.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/path.rs @@ -7,6 +7,7 @@ use chumsky::prelude::*; use super::keyword; use super::primitives::{ident, path_segment, path_segment_no_turbofish}; +use super::types::generic_type_args; pub(super) fn path<'a>( type_parser: impl NoirParser + 'a, @@ -54,14 +55,16 @@ pub(super) fn as_trait_path<'a>( just(Token::Less) .ignore_then(type_parser.clone()) .then_ignore(keyword(Keyword::As)) - .then(path(type_parser)) + .then(path(type_parser.clone())) + .then(generic_type_args(type_parser)) .then_ignore(just(Token::Greater)) .then_ignore(just(Token::DoubleColon)) .then(ident()) - .validate(|((typ, trait_path), impl_item), span, emit| { - let reason = ParserErrorReason::ExperimentalFeature("Fully qualified trait impl paths"); - emit(ParserError::with_reason(reason, span)); - AsTraitPath { typ, trait_path, impl_item } + .map(|(((typ, trait_path), trait_generics), impl_item)| AsTraitPath { + typ, + trait_path, + trait_generics, + impl_item, }) } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/primitives.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/primitives.rs index 25f693bf504..c1516e2c927 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/primitives.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/primitives.rs @@ -1,7 +1,8 @@ use chumsky::prelude::*; -use crate::ast::{ExpressionKind, Ident, PathSegment, UnaryOp}; +use crate::ast::{ExpressionKind, GenericTypeArgs, Ident, PathSegment, UnaryOp}; use crate::macros_api::UnresolvedType; +use crate::parser::ParserErrorReason; use crate::{ parser::{labels::ParsingRuleLabel, ExprParser, NoirParser, ParserError}, token::{Keyword, Token, TokenKind}, @@ -36,10 +37,14 @@ pub(super) fn token_kind(token_kind: TokenKind) -> impl NoirParser { pub(super) fn path_segment<'a>( type_parser: impl NoirParser + 'a, ) -> impl NoirParser + 'a { - ident().then(turbofish(type_parser)).map_with_span(|(ident, generics), span| PathSegment { - ident, - generics, - span, + ident().then(turbofish(type_parser)).validate(|(ident, generics), span, emit| { + if generics.as_ref().map_or(false, |generics| !generics.named_args.is_empty()) { + let reason = ParserErrorReason::AssociatedTypesNotAllowedInPaths; + emit(ParserError::with_reason(reason, span)); + } + + let generics = generics.map(|generics| generics.ordered_args); + PathSegment { ident, generics, span } }) } @@ -95,7 +100,7 @@ where pub(super) fn turbofish<'a>( type_parser: impl NoirParser + 'a, -) -> impl NoirParser>> + 'a { +) -> impl NoirParser> + 'a { just(Token::DoubleColon).ignore_then(required_generic_type_args(type_parser)).or_not() } @@ -114,6 +119,13 @@ pub(super) fn macro_quote_marker() -> impl NoirParser { }) } +pub(super) fn interned_expr() -> impl NoirParser { + token_kind(TokenKind::InternedExpr).map(|token| match token { + Token::InternedExpr(id) => ExpressionKind::Interned(id), + _ => unreachable!("token_kind(InternedExpr) guarantees we parse an interned expr"), + }) +} + #[cfg(test)] mod test { use crate::parser::parser::{ 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 0874cadd34e..bf5a4b4d0b4 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 } }) @@ -84,15 +111,10 @@ fn trait_function_declaration() -> impl NoirParser { /// trait_type_declaration: 'type' ident generics fn trait_type_declaration() -> impl NoirParser { - keyword(Keyword::Type).ignore_then(ident()).then_ignore(just(Token::Semicolon)).validate( - |name, span, emit| { - emit(ParserError::with_reason( - ParserErrorReason::ExperimentalFeature("Associated types"), - span, - )); - TraitItem::Type { name } - }, - ) + keyword(Keyword::Type) + .ignore_then(ident()) + .then_ignore(just(Token::Semicolon)) + .map(|name| TraitItem::Type { name }) } /// Parses a trait implementation, implementing a particular trait for a type. @@ -101,6 +123,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 +148,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 +264,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 7c551ca96d1..f83303151eb 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/types.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/types.rs @@ -1,11 +1,12 @@ use super::path::{as_trait_path, path_no_turbofish}; -use super::primitives::token_kind; +use super::primitives::{ident, token_kind}; use super::{ expression_with_precedence, keyword, nothing, parenthesized, NoirParser, ParserError, ParserErrorReason, Precedence, }; use crate::ast::{ - Expression, Recoverable, UnresolvedType, UnresolvedTypeData, UnresolvedTypeExpression, + Expression, GenericTypeArg, GenericTypeArgs, Recoverable, UnresolvedType, UnresolvedTypeData, + UnresolvedTypeExpression, }; use crate::QuotedType; @@ -27,15 +28,7 @@ pub(super) fn parse_type_inner<'a>( int_type(), bool_type(), string_type(), - expr_type(), - struct_definition_type(), - trait_constraint_type(), - trait_definition_type(), - function_definition_type(), - module_type(), - top_level_item_type(), - type_of_quoted_types(), - quoted_type(), + comptime_type(), resolved_type(), format_string_type(recursive_type_parser.clone()), named_type(recursive_type_parser.clone()), @@ -47,6 +40,7 @@ pub(super) fn parse_type_inner<'a>( function_type(recursive_type_parser.clone()), mutable_reference_type(recursive_type_parser.clone()), as_trait_path_type(recursive_type_parser), + interned_unresolved_type(), )) } @@ -64,7 +58,7 @@ pub(super) fn parenthesized_type( .delimited_by(just(Token::LeftParen), just(Token::RightParen)) .map_with_span(|typ, span| UnresolvedType { typ: UnresolvedTypeData::Parenthesized(Box::new(typ)), - span: span.into(), + span, }) } @@ -81,6 +75,22 @@ pub(super) fn bool_type() -> impl NoirParser { keyword(Keyword::Bool).map_with_span(|_, span| UnresolvedTypeData::Bool.with_span(span)) } +pub(super) fn comptime_type() -> impl NoirParser { + choice(( + expr_type(), + struct_definition_type(), + trait_constraint_type(), + trait_definition_type(), + trait_impl_type(), + unresolved_type_type(), + function_definition_type(), + module_type(), + type_of_quoted_types(), + top_level_item_type(), + quoted_type(), + )) +} + /// This is the type `Expr` - the type of a quoted, untyped expression object used for macros pub(super) fn expr_type() -> impl NoirParser { keyword(Keyword::Expr) @@ -107,6 +117,17 @@ 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 unresolved_type_type() -> impl NoirParser { + keyword(Keyword::UnresolvedType).map_with_span(|_, span| { + UnresolvedTypeData::Quoted(QuotedType::UnresolvedType).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) @@ -148,6 +169,15 @@ pub(super) fn resolved_type() -> impl NoirParser { }) } +pub(super) fn interned_unresolved_type() -> impl NoirParser { + token_kind(TokenKind::InternedUnresolvedTypeData).map_with_span(|token, span| match token { + Token::InternedUnresolvedTypeData(id) => UnresolvedTypeData::Interned(id).with_span(span), + _ => unreachable!( + "token_kind(InternedUnresolvedTypeData) guarantees we parse an interned unresolved type" + ), + }) +} + pub(super) fn string_type() -> impl NoirParser { keyword(Keyword::String) .ignore_then(type_expression().delimited_by(just(Token::Less), just(Token::Greater))) @@ -207,25 +237,37 @@ pub(super) fn named_trait<'a>( pub(super) fn generic_type_args<'a>( type_parser: impl NoirParser + 'a, -) -> impl NoirParser> + 'a { +) -> impl NoirParser + 'a { required_generic_type_args(type_parser).or_not().map(Option::unwrap_or_default) } pub(super) fn required_generic_type_args<'a>( type_parser: impl NoirParser + 'a, -) -> impl NoirParser> + 'a { - type_parser +) -> impl NoirParser + 'a { + let generic_type_arg = type_parser .clone() + .then_ignore(one_of([Token::Comma, Token::Greater]).rewind()) + .or(type_expression_validated()); + + let named_arg = ident() + .then_ignore(just(Token::Assign)) + .then(generic_type_arg.clone()) + .map(|(name, typ)| GenericTypeArg::Named(name, typ)); + + // We need to parse named arguments first since otherwise when we see + // `Foo = Bar`, just `Foo` is a valid type, and we'd parse an ordered + // generic before erroring that an `=` is invalid after an ordered generic. + choice((named_arg, generic_type_arg.map(GenericTypeArg::Ordered))) + .boxed() // Without checking for a terminating ',' or '>' here we may incorrectly // parse a generic `N * 2` as just the type `N` then fail when there is no // separator afterward. Failing early here ensures we try the `type_expression` // parser afterward. - .then_ignore(one_of([Token::Comma, Token::Greater]).rewind()) - .or(type_expression_validated()) .separated_by(just(Token::Comma)) .allow_trailing() .at_least(1) .delimited_by(just(Token::Less), just(Token::Greater)) + .map(GenericTypeArgs::from) } pub(super) fn array_type<'a>( @@ -312,13 +354,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 efb430b75eb..61ff6baf919 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 9124567b4e5..870c781b89d 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/tests.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/tests.rs @@ -76,15 +76,21 @@ pub(crate) fn get_program(src: &str) -> (ParsedModule, Context, Vec<(Compilation extern_prelude: BTreeMap::new(), }; + let debug_comptime_in_file = None; + let enable_arithmetic_generics = false; + let error_on_unused_imports = true; + let macro_processors = &[]; + // Now we want to populate the CrateDefMap using the DefCollector errors.extend(DefCollector::collect_crate_and_dependencies( def_map, &mut context, program.clone().into_sorted(), root_file_id, - None, // No debug_comptime_in_file - false, // Disallow arithmetic generics - &[], // No macro processors + debug_comptime_in_file, + enable_arithmetic_generics, + error_on_unused_imports, + macro_processors, )); } (program, context, errors) @@ -2227,7 +2233,7 @@ fn impl_stricter_than_trait_different_trait_generics() { { assert!(matches!(constraint_typ.to_string().as_str(), "A")); assert!(matches!(constraint_name.as_str(), "T2")); - assert!(matches!(constraint_generics[0].to_string().as_str(), "B")); + assert!(matches!(constraint_generics.ordered[0].to_string().as_str(), "B")); } else { panic!("Expected DefCollectorErrorKind::ImplIsStricterThanTrait but got {:?}", errors[0].0); } @@ -2424,6 +2430,10 @@ fn use_super() { mod foo { use super::some_func; + + fn bar() { + some_func(); + } } "#; assert_no_errors(src); @@ -2460,6 +2470,201 @@ 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); + + let CompilationError::TypeError(TypeCheckError::UnsafeFn { .. }) = &errors[0].0 else { + panic!("Expected an UnsafeFn 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::UnsafeFn { .. }) = &errors[0].0 else { + panic!("Expected an UnsafeFn 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#" @@ -2694,16 +2899,14 @@ fn incorrect_generic_count_on_struct_impl() { let errors = get_program_errors(src); assert_eq!(errors.len(), 1); - let CompilationError::ResolverError(ResolverError::IncorrectGenericCount { - actual, - expected, - .. + let CompilationError::TypeError(TypeCheckError::GenericCountMismatch { + found, expected, .. }) = errors[0].0 else { panic!("Expected an incorrect generic count mismatch error, got {:?}", errors[0].0); }; - assert_eq!(actual, 1); + assert_eq!(found, 1); assert_eq!(expected, 0); } @@ -2718,16 +2921,14 @@ fn incorrect_generic_count_on_type_alias() { let errors = get_program_errors(src); assert_eq!(errors.len(), 1); - let CompilationError::ResolverError(ResolverError::IncorrectGenericCount { - actual, - expected, - .. + let CompilationError::TypeError(TypeCheckError::GenericCountMismatch { + found, expected, .. }) = errors[0].0 else { panic!("Expected an incorrect generic count mismatch error, got {:?}", errors[0].0); }; - assert_eq!(actual, 1); + assert_eq!(found, 1); assert_eq!(expected, 0); } @@ -2919,3 +3120,114 @@ fn trait_impl_for_a_type_that_implements_another_trait_with_another_impl_used() "#; assert_no_errors(src); } + +#[test] +fn impl_missing_associated_type() { + let src = r#" + trait Foo { + type Assoc; + } + + impl Foo for () {} + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + assert!(matches!( + &errors[0].0, + CompilationError::TypeError(TypeCheckError::MissingNamedTypeArg { .. }) + )); +} + +#[test] +fn as_trait_path_syntax_resolves_outside_impl() { + let src = r#" + trait Foo { + type Assoc; + } + + struct Bar {} + + impl Foo for Bar { + type Assoc = i32; + } + + fn main() { + // AsTraitPath syntax is a bit silly when associated types + // are explicitly specified + let _: i64 = 1 as >::Assoc; + } + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + use CompilationError::TypeError; + use TypeCheckError::TypeMismatch; + let TypeError(TypeMismatch { expected_typ, expr_typ, .. }) = errors[0].0.clone() else { + panic!("Expected TypeMismatch error, found {:?}", errors[0].0); + }; + + assert_eq!(expected_typ, "i64".to_string()); + assert_eq!(expr_typ, "i32".to_string()); +} + +#[test] +fn as_trait_path_syntax_no_impl() { + let src = r#" + trait Foo { + type Assoc; + } + + struct Bar {} + + impl Foo for Bar { + type Assoc = i32; + } + + fn main() { + let _: i64 = 1 as >::Assoc; + } + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + use CompilationError::TypeError; + assert!(matches!(&errors[0].0, TypeError(TypeCheckError::NoMatchingImplFound { .. }))); +} + +#[test] +fn errors_on_unused_import() { + let src = r#" + mod foo { + pub fn bar() {} + pub fn baz() {} + + trait Foo { + } + } + + use foo::bar; + use foo::baz; + use foo::Foo; + + impl Foo for Field { + } + + fn main() { + baz(); + } + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::ResolverError(ResolverError::UnusedImport { ident }) = &errors[0].0 + else { + panic!("Expected an unused import error"); + }; + + assert_eq!(ident.to_string(), "bar"); +} diff --git a/noir/noir-repo/compiler/noirc_printable_type/Cargo.toml b/noir/noir-repo/compiler/noirc_printable_type/Cargo.toml index 5140f5a5a8c..8bb56703e8a 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 dfecd301b35..5ab04c6f576 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 20272dfeecb..c8b8c3bb06e 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/compiler/wasm/src/noir/dependencies/github-dependency-resolver.ts b/noir/noir-repo/compiler/wasm/src/noir/dependencies/github-dependency-resolver.ts index 39ad0d802fb..1fd37248e78 100644 --- a/noir/noir-repo/compiler/wasm/src/noir/dependencies/github-dependency-resolver.ts +++ b/noir/noir-repo/compiler/wasm/src/noir/dependencies/github-dependency-resolver.ts @@ -45,7 +45,8 @@ export class GithubDependencyResolver implements DependencyResolver { } async #fetchZipFromGithub(dependency: Pick): Promise { - if (!dependency.git.startsWith('https://github.com')) { + const git_host = new URL(dependency.git); + if (git_host !== null && git_host.host != 'github.com') { throw new Error('Only github dependencies are supported'); } diff --git a/noir/noir-repo/compiler/wasm/test/dependencies/github-dependency-resolver.test.ts b/noir/noir-repo/compiler/wasm/test/dependencies/github-dependency-resolver.test.ts index 505b2269cd2..684a19beb24 100644 --- a/noir/noir-repo/compiler/wasm/test/dependencies/github-dependency-resolver.test.ts +++ b/noir/noir-repo/compiler/wasm/test/dependencies/github-dependency-resolver.test.ts @@ -124,6 +124,8 @@ describe('GithubDependencyResolver', () => { { git: 'https://github.com/', tag: 'v1' }, { git: 'https://github.com/foo', tag: 'v1' }, { git: 'https://example.com', tag: 'v1' }, + { git: 'https://github.com.otherdomain.com', tag: 'v1' }, + { git: 'https://github.com.otherdomain.com/example/repo', tag: 'v1' }, ]).it('throws if the Github URL is invalid %j', (dep) => { expect(() => resolveGithubCodeArchive(dep, 'zip')).to.throw(); }); diff --git a/noir/noir-repo/compiler/wasm/test/fixtures/with-deps/src/main.nr b/noir/noir-repo/compiler/wasm/test/fixtures/with-deps/src/main.nr index fe9e7f9ca77..c66f302365a 100644 --- a/noir/noir-repo/compiler/wasm/test/fixtures/with-deps/src/main.nr +++ b/noir/noir-repo/compiler/wasm/test/fixtures/with-deps/src/main.nr @@ -1,4 +1,4 @@ use lib_a::divide; fn main(x: u64, y: pub u64) { - divide(x, y); + let _ = divide(x, y); } diff --git a/noir/noir-repo/cspell.json b/noir/noir-repo/cspell.json index e939cd583e8..c34d14b6153 100644 --- a/noir/noir-repo/cspell.json +++ b/noir/noir-repo/cspell.json @@ -20,6 +20,7 @@ "barretenberg", "barustenberg", "bbup", + "bignum", "bincode", "bindgen", "bitand", @@ -119,6 +120,7 @@ "keccakf", "krate", "libc", + "Linea", "losslessly", "lvalue", "Maddiaa", @@ -126,6 +128,7 @@ "memfs", "memset", "merkle", + "metaprogramming", "metas", "microcontroller", "minreq", @@ -166,6 +169,7 @@ "pseudocode", "pubkey", "quantile", + "quasiquote", "rangemap", "repr", "reqwest", @@ -204,6 +208,8 @@ "typevar", "typevars", "udiv", + "umap", + "underconstrained", "uninstantiated", "unnormalized", "unoptimized", @@ -216,7 +222,9 @@ "wasi", "wasmer", "Weierstraß", - "zshell" + "zkhash", + "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 c8a42c379e6..3ef6e014a2f 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 c800d91ac69..3bb96c66795 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/comptime.md b/noir/noir-repo/docs/docs/noir/concepts/comptime.md new file mode 100644 index 00000000000..2b5c29538b9 --- /dev/null +++ b/noir/noir-repo/docs/docs/noir/concepts/comptime.md @@ -0,0 +1,262 @@ +--- +title: Compile-time Code & Metaprogramming +description: Learn how to use metaprogramming in Noir to create macros or derive your own traits +keywords: [Noir, comptime, compile-time, metaprogramming, macros, quote, unquote] +sidebar_position: 15 +--- + +# Overview + +Metaprogramming in Noir is comprised of three parts: +1. `comptime` code +2. Quoting and unquoting +3. The metaprogramming API in `std::meta` + +Each of these are explained in more detail in the next sections but the wide picture is that +`comptime` allows us to write code which runs at compile-time. In this `comptime` code we +can quote and unquote snippets of the program, manipulate them, and insert them in other +parts of the program. Comptime functions which do this are said to be macros. Additionally, +there's a compile-time API of built-in types and functions provided by the compiler which allows +for greater analysis and modification of programs. + +--- + +# Comptime + +`comptime` is a new keyword in Noir which marks an item as executing or existing at compile-time. It can be used in several ways: + +- `comptime fn` to define functions which execute exclusively during compile-time. +- `comptime global` to define a global variable which is evaluated at compile-time. + - Unlike runtime globals, `comptime global`s can be mutable. +- `comptime { ... }` to execute a block of statements during compile-time. +- `comptime let` to define a variable whose value is evaluated at compile-time. +- `comptime for` to run a for loop at compile-time. Syntax sugar for `comptime { for .. }`. + +## Scoping + +Note that while in a `comptime` context, any runtime variables _local to the current function_ are never visible. + +## Evaluating + +Evaluation rules of `comptime` follows the normal unconstrained evaluation rules for other Noir code. There are a few things to note though: + +- Certain built-in functions may not be available, although more may be added over time. +- Evaluation order of global items is currently unspecified. For example, given the following two functions we can't guarantee +which `println` will execute first. The ordering of the two printouts will be arbitrary, but should be stable across multiple compilations with the same `nargo` version as long as the program is also unchanged. + +```rust +fn one() { + comptime { println("one"); } +} + +fn two() { + comptime { println("two"); } +} +``` + +- Since evaluation order is unspecified, care should be taken when using mutable globals so that they do not rely on a particular ordering. +For example, using globals to generate unique ids should be fine but relying on certain ids always being produced (especially after edits to the program) should be avoided. +- Although most ordering of globals is unspecified, two are: + - Dependencies of a crate will always be evaluated before the dependent crate. + - Any annotations on a function will be run before the function itself is resolved. This is to allow the annotation to modify the function if necessary. Note that if the + function itself was called at compile-time previously, it will already be resolved and cannot be modified. To prevent accidentally calling functions you wish to modify + at compile-time, it may be helpful to sort your `comptime` annotation functions into a different crate along with any dependencies they require. + +## Lowering + +When a `comptime` value is used in runtime code it must be lowered into a runtime value. This means replacing the expression with the literal that it evaluated to. For example, the code: + +```rust +struct Foo { array: [Field; 2], len: u32 } + +fn main() { + println(comptime { + let mut foo = std::mem::zeroed::(); + foo.array[0] = 4; + foo.len = 1; + foo + }); +} +``` + +will be converted to the following after `comptime` expressions are evaluated: + +```rust +struct Foo { array: [Field; 2], len: u32 } + +fn main() { + println(Foo { array: [4, 0], len: 1 }); +} +``` + +Not all types of values can be lowered. For example, `Type`s and `TypeDefinition`s (among other types) cannot be lowered at all. + +```rust +fn main() { + // There's nothing we could inline here to create a Type value at runtime + // let _ = get_type!(); +} + +comptime fn get_type() -> Type { ... } +``` + +--- + +# (Quasi) Quote + +Macros in Noir are `comptime` functions which return code as a value which is inserted into the call site when it is lowered there. +A code value in this case is of type `Quoted` and can be created by a `quote { ... }` expression. +More specifically, the code value `quote` creates is a token stream - a representation of source code as a series of words, numbers, string literals, or operators. +For example, the expression `quote { Hi "there reader"! }` would quote three tokens: the word "hi", the string "there reader", and an exclamation mark. +You'll note that snippets that would otherwise be invalid syntax can still be quoted. + +When a `Quoted` value is used in runtime code, it is lowered into a `quote { ... }` expression. Since this expression is only valid +in compile-time code however, we'd get an error if we tried this. Instead, we can use macro insertion to insert each token into the +program at that point, and parse it as an expression. To do this, we have to add a `!` after the function name returning the `Quoted` value. +If the value was created locally and there is no function returning it, `std::meta::unquote!(_)` can be used instead. +Calling such a function at compile-time without `!` will just return the `Quoted` value to be further manipulated. For example: + +#include_code quote-example noir_stdlib/src/meta/mod.nr rust + +For those familiar with quoting from other languages (primarily lisps), Noir's `quote` is actually a _quasiquote_. +This means we can escape the quoting by using the unquote operator to splice values in the middle of quoted code. + +# Unquote + +The unquote operator `$` is usable within a `quote` expression. +It takes a variable as an argument, evaluates the variable, and splices the resulting value into the quoted token stream at that point. For example, + +```rust +comptime { + let x = 1 + 2; + let y = quote { $x + 4 }; +} +``` + +The value of `y` above will be the token stream containing `3`, `+`, and `4`. We can also use this to combine `Quoted` values into larger token streams: + +```rust +comptime { + let x = quote { 1 + 2 }; + let y = quote { $x + 4 }; +} +``` + +The value of `y` above is now the token stream containing five tokens: `1 + 2 + 4`. + +Note that to unquote something, a variable name _must_ follow the `$` operator in a token stream. +If it is an expression (even a parenthesized one), it will do nothing. Most likely a parse error will be given when the macro is later unquoted. + +Unquoting can also be avoided by escaping the `$` with a backslash: + +``` +comptime { + let x = quote { 1 + 2 }; + + // y contains the four tokens: `$x + 4` + let y = quote { \$x + 4 }; +} +``` + +--- + +# Annotations + +Annotations provide a way to run a `comptime` function on an item in the program. +When you use an annotation, the function with the same name will be called with that item as an argument: + +```rust +#[my_struct_annotation] +struct Foo {} + +comptime fn my_struct_annotation(s: StructDefinition) { + println("Called my_struct_annotation!"); +} + +#[my_function_annotation] +fn foo() {} + +comptime fn my_function_annotation(f: FunctionDefinition) { + println("Called my_function_annotation!"); +} +``` + +Anything returned from one of these functions will be inserted at top-level along with the original item. +Note that expressions are not valid at top-level so you'll get an error trying to return `3` or similar just as if you tried to write a program containing `3; struct Foo {}`. +You can insert other top-level items such as traits, structs, or functions this way though. +For example, this is the mechanism used to insert additional trait implementations into the program when deriving a trait impl from a struct: + +#include_code derive-field-count-example noir_stdlib/src/meta/mod.nr rust + +## Calling annotations with additional arguments + +Arguments may optionally be given to annotations. +When this is done, these additional arguments are passed to the annotation function after the item argument. + +#include_code annotation-arguments-example noir_stdlib/src/meta/mod.nr rust + +We can also take any number of arguments by adding the `varargs` annotation: + +#include_code annotation-varargs-example noir_stdlib/src/meta/mod.nr rust + +--- + +# Comptime API + +Although `comptime`, `quote`, and unquoting provide a flexible base for writing macros, +Noir's true metaprogramming ability comes from being able to interact with the compiler through a compile-time API. +This API can be accessed through built-in functions in `std::meta` as well as on methods of several `comptime` types. + +The following is an incomplete list of some `comptime` types along with some useful methods on them. + +- `Quoted`: A token stream +- `Type`: The type of a Noir type + - `fn implements(self, constraint: TraitConstraint) -> bool` + - Returns true if `self` implements the given trait constraint +- `Expr`: A syntactically valid expression. Can be used to recur on a program's parse tree to inspect how it is structured. + - Methods: + - `fn as_function_call(self) -> Option<(Expr, [Expr])>` + - If this is a function call expression, return `(function, arguments)` + - `fn as_block(self) -> Option<[Expr]>` + - If this is a block, return each statement in the block +- `FunctionDefinition`: A function definition + - Methods: + - `fn parameters(self) -> [(Quoted, Type)]` + - Returns a slice of `(name, type)` pairs for each parameter +- `StructDefinition`: A struct definition + - Methods: + - `fn as_type(self) -> Type` + - Returns this `StructDefinition` as a `Type`. Any generics are kept as-is + - `fn generics(self) -> [Quoted]` + - Return the name of each generic on this struct + - `fn fields(self) -> [(Quoted, Type)]` + - Return the name and type of each field +- `TraitConstraint`: A trait constraint such as `From` +- `UnresolvedType`: A syntactic notation that refers to a Noir type that hasn't been resolved yet + +There are many more functions available by exploring the `std::meta` module and its submodules. +Using these methods is the key to writing powerful metaprogramming libraries. + +--- + +# Example: Derive + +Using all of the above, we can write a `derive` macro that behaves similarly to Rust's but is not built into the language. +From the user's perspective it will look like this: + +```rust +// Example usage +#[derive(Default, Eq, Ord)] +struct MyStruct { my_field: u32 } +``` + +To implement `derive` we'll have to create a `comptime` function that accepts +a variable amount of traits. + +#include_code derive_example noir_stdlib/src/meta/mod.nr rust + +Registering a derive function could be done as follows: + +#include_code derive_via noir_stdlib/src/meta/mod.nr rust + +#include_code big-derive-usage-example noir_stdlib/src/meta/mod.nr rust diff --git a/noir/noir-repo/docs/docs/noir/concepts/data_types/slices.mdx b/noir/noir-repo/docs/docs/noir/concepts/data_types/slices.mdx index 95da2030843..a0c87c29259 100644 --- a/noir/noir-repo/docs/docs/noir/concepts/data_types/slices.mdx +++ b/noir/noir-repo/docs/docs/noir/concepts/data_types/slices.mdx @@ -20,7 +20,7 @@ fn main() -> pub u32 { } ``` -To write a slice literal, use a preceeding ampersand as in: `&[0; 2]` or +To write a slice literal, use a preceding ampersand as in: `&[0; 2]` or `&[1, 2, 3]`. It is important to note that slices are not references to arrays. In Noir, diff --git a/noir/noir-repo/docs/docs/noir/concepts/traits.md b/noir/noir-repo/docs/docs/noir/concepts/traits.md index 51305b38c16..597c62c737c 100644 --- a/noir/noir-repo/docs/docs/noir/concepts/traits.md +++ b/noir/noir-repo/docs/docs/noir/concepts/traits.md @@ -225,6 +225,66 @@ fn main() { } ``` +### Associated Types and Constants + +Traits also support associated types and constraints which can be thought of as additional generics that are referred to by name. + +Here's an example of a trait with an associated type `Foo` and a constant `Bar`: + +```rust +trait MyTrait { + type Foo; + + let Bar: u32; +} +``` + +Now when we're implementing `MyTrait` we also have to provide values for `Foo` and `Bar`: + +```rust +impl MyTrait for Field { + type Foo = i32; + + let Bar: u32 = 11; +} +``` + +Since associated constants can also be used in a type position, its values are limited to only other +expression kinds allowed in numeric generics. + +Note that currently all associated types and constants must be explicitly specified in a trait constraint. +If we leave out any, we'll get an error that we're missing one: + +```rust +// Error! Constraint is missing associated constant for `Bar` +fn foo(x: T) where T: MyTrait { + ... +} +``` + +Because all associated types and constants must be explicitly specified, they are essentially named generics, +although this is set to change in the future. Future versions of Noir will allow users to elide associated types +in trait constraints similar to Rust. When this is done, you may still refer to their value with the `::AssociatedType` +syntax: + +```rust +// Only valid in future versions of Noir: +fn foo(x: T) where T: MyTrait { + let _: ::Foo = ...; +} +``` + +The type as trait syntax is possible in Noir today but is less useful when each type must be explicitly specified anyway: + +```rust +fn foo(x: T) where T: MyTrait { + // Works, but could just use F directly + let _: >::Foo = ...; + + let _: F = ...; +} +``` + ## Trait Methods With No `self` A trait can contain any number of methods, each of which have access to the `Self` type which represents each type diff --git a/noir/noir-repo/docs/docs/noir/concepts/unconstrained.md b/noir/noir-repo/docs/docs/noir/concepts/unconstrained.md index 96f824c5e42..b5221b8d2dd 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/docs/noir/standard_library/bigint.md b/noir/noir-repo/docs/docs/noir/standard_library/bigint.md index 54d791b82d3..cc7d6e1c8de 100644 --- a/noir/noir-repo/docs/docs/noir/standard_library/bigint.md +++ b/noir/noir-repo/docs/docs/noir/standard_library/bigint.md @@ -15,6 +15,11 @@ The BigInt module in the standard library exposes some class of integers which d The module can currently be considered as `Field`s with fixed modulo sizes used by a set of elliptic curves, in addition to just the native curve. [More work](https://github.com/noir-lang/noir/issues/510) is needed to achieve arbitrarily sized big integers. +:::note + +`nargo` can be built with `--profile release-pedantic` to enable extra overflow checks which may affect `BigInt` results in some cases. +Consider the [`noir-bignum`](https://github.com/noir-lang/noir-bignum) library for an optimized alternative approach. + ::: Currently 6 classes of integers (i.e 'big' prime numbers) are available in the module, namely: diff --git a/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/hashes.mdx b/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/hashes.mdx index 0abd7a12a22..6ff47a77df9 100644 --- a/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/hashes.mdx +++ b/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/hashes.mdx @@ -15,7 +15,7 @@ import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; Given an array of bytes, returns the resulting sha256 hash. Specify a message_size to hash only the first `message_size` bytes of the input. -#include_code sha256 noir_stdlib/src/hash/mod.nr rust +#include_code sha256 noir_stdlib/src/hash/sha256.nr rust example: #include_code sha256_var test_programs/execution_success/sha256/src/main.nr rust diff --git a/noir/noir-repo/docs/docs/noir/standard_library/meta/expr.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/expr.md new file mode 100644 index 00000000000..d421e8b56a3 --- /dev/null +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/expr.md @@ -0,0 +1,181 @@ +--- +title: Expr +--- + +`std::meta::expr` contains methods on the built-in `Expr` type for quoted, syntactically valid expressions. + +## Methods + +### as_array + +#include_code as_array noir_stdlib/src/meta/expr.nr rust + +If this expression is an array, this returns a slice of each element in the array. + +### as_assign + +#include_code as_assign noir_stdlib/src/meta/expr.nr rust + +If this expression is an assignment, this returns a tuple with the left hand side +and right hand side in order. + +### as_binary_op + +#include_code as_binary_op noir_stdlib/src/meta/expr.nr rust + +If this expression is a binary operator operation ` `, +return the left-hand side, operator, and the right-hand side of the operation. + +### as_block + +#include_code as_block noir_stdlib/src/meta/expr.nr rust + +If this expression is a block `{ stmt1; stmt2; ...; stmtN }`, return +a slice containing each statement. + +### as_bool + +#include_code as_bool noir_stdlib/src/meta/expr.nr rust + +If this expression is a boolean literal, return that literal. + +### as_comptime + +#include_code as_comptime noir_stdlib/src/meta/expr.nr rust + +If this expression is a `comptime { stmt1; stmt2; ...; stmtN }` block, +return each statement in the block. + +### as_function_call + +#include_code as_function_call noir_stdlib/src/meta/expr.nr rust + +If this expression is a function call `foo(arg1, ..., argN)`, return +the function and a slice of each argument. + +### as_if + +#include_code as_if noir_stdlib/src/meta/expr.nr rust + +If this expression is an `if condition { then_branch } else { else_branch }`, +return the condition, then branch, and else branch. If there is no else branch, +`None` is returned for that branch instead. + +### as_index + +#include_code as_index noir_stdlib/src/meta/expr.nr rust + +If this expression is an index into an array `array[index]`, return the +array and the index. + +### as_integer + +#include_code as_integer noir_stdlib/src/meta/expr.nr rust + +If this element is an integer literal, return the integer as a field +as well as whether the integer is negative (true) or not (false). + +### as_member_access + +#include_code as_member_access noir_stdlib/src/meta/expr.nr rust + +If this expression is a member access `foo.bar`, return the struct/tuple +expression and the field. The field will be represented as a quoted value. + +### as_method_call + +#include_code as_method_call noir_stdlib/src/meta/expr.nr rust + +If this expression is a method call `foo.bar::(arg1, ..., argN)`, return +the receiver, method name, a slice of each generic argument, and a slice of each argument. + +### as_repeated_element_array + +#include_code as_repeated_element_array noir_stdlib/src/meta/expr.nr rust + +If this expression is a repeated element array `[elem; length]`, return +the repeated element and the length expressions. + +### as_repeated_element_slice + +#include_code as_repeated_element_slice noir_stdlib/src/meta/expr.nr rust + +If this expression is a repeated element slice `[elem; length]`, return +the repeated element and the length expressions. + +### as_slice + +#include_code as_slice noir_stdlib/src/meta/expr.nr rust + +If this expression is a slice literal `&[elem1, ..., elemN]`, +return each element of the slice. + +### as_tuple + +#include_code as_tuple noir_stdlib/src/meta/expr.nr rust + +If this expression is a tuple `(field1, ..., fieldN)`, +return each element of the tuple. + +### as_unary_op + +#include_code as_unary_op noir_stdlib/src/meta/expr.nr rust + +If this expression is a unary operation ` `, +return the unary operator as well as the right-hand side expression. + +### as_unsafe + +#include_code as_unsafe noir_stdlib/src/meta/expr.nr rust + +If this expression is an `unsafe { stmt1; ...; stmtN }` block, +return each statement inside in a slice. + +### has_semicolon + +#include_code has_semicolon noir_stdlib/src/meta/expr.nr rust + +`true` if this expression is trailed by a semicolon. E.g. + +``` +comptime { + let expr1 = quote { 1 + 2 }.as_expr().unwrap(); + let expr2 = quote { 1 + 2; }.as_expr().unwrap(); + + assert(expr1.as_binary_op().is_some()); + assert(expr2.as_binary_op().is_some()); + + assert(!expr1.has_semicolon()); + assert(expr2.has_semicolon()); +} +``` + +### is_break + +#include_code is_break noir_stdlib/src/meta/expr.nr rust + +`true` if this expression is `break`. + +### is_continue + +#include_code is_continue noir_stdlib/src/meta/expr.nr rust + +`true` if this expression is `continue`. + +### mutate + +#include_code mutate noir_stdlib/src/meta/expr.nr rust + +Applies a mapping function to this expression and to all of its sub-expressions. +`f` will be applied to each sub-expression first, then applied to the expression itself. + +This happens recursively for every expression within `self`. + +For example, calling `mutate` on `(&[1], &[2, 3])` with an `f` that returns `Option::some` +for expressions that are integers, doubling them, would return `(&[2], &[4, 6])`. + +### quoted + +#include_code quoted noir_stdlib/src/meta/expr.nr rust + +Returns this expression as a `Quoted` value. It's the same as `quote { $self }`. \ No newline at end of file diff --git a/noir/noir-repo/docs/docs/noir/standard_library/meta/function_def.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/function_def.md new file mode 100644 index 00000000000..8a4e8c84958 --- /dev/null +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/function_def.md @@ -0,0 +1,61 @@ +--- +title: FunctionDefinition +--- + +`std::meta::function_def` contains methods on the built-in `FunctionDefinition` type representing +a function definition in the source program. + +## Methods + +### body + +#include_code body noir_stdlib/src/meta/function_def.nr rust + +Returns the body of the function as an expression. This is only valid +on functions in the current crate which have not yet been resolved. +This means any functions called at compile-time are invalid targets for this method. + +### name + +#include_code name noir_stdlib/src/meta/function_def.nr rust + +Returns the name of the function. + +### parameters + +#include_code parameters noir_stdlib/src/meta/function_def.nr rust + +Returns each parameter of the function as a tuple of (parameter pattern, parameter type). + +### return_type + +#include_code return_type noir_stdlib/src/meta/function_def.nr rust + +The return type of the function. + +### set_body + +#include_code set_body noir_stdlib/src/meta/function_def.nr rust + +Mutate the function body to a new expression. This is only valid +on functions in the current crate which have not yet been resolved. +This means any functions called at compile-time are invalid targets for this method. + +### set_parameters + +#include_code set_parameters noir_stdlib/src/meta/function_def.nr rust + +Mutates the function's parameters to a new set of parameters. This is only valid +on functions in the current crate which have not yet been resolved. +This means any functions called at compile-time are invalid targets for this method. + +Expects a slice of (parameter pattern, parameter type) for each parameter. Requires +each parameter pattern to be a syntactically valid parameter. + +### set_return_type + +#include_code set_return_type noir_stdlib/src/meta/function_def.nr rust + +Mutates the function's return type to a new type. This is only valid +on functions in the current crate which have not yet been resolved. +This means any functions called at compile-time are invalid targets for this method. diff --git a/noir/noir-repo/docs/docs/noir/standard_library/meta/index.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/index.md new file mode 100644 index 00000000000..db0e5d0e411 --- /dev/null +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/index.md @@ -0,0 +1,141 @@ +--- +title: Metaprogramming +description: Noir's Metaprogramming API +keywords: [metaprogramming, comptime, macros, macro, quote, unquote] +--- + +`std::meta` is the entry point for Noir's metaprogramming API. This consists of `comptime` functions +and types used for inspecting and modifying Noir programs. + +## Functions + +### type_of + +#include_code type_of noir_stdlib/src/meta/mod.nr rust + +Returns the type of a variable at compile-time. + +Example: +```rust +comptime { + let x: i32 = 1; + let x_type: Type = std::meta::type_of(x); + + assert_eq(x_type, quote { i32 }.as_type()); +} +``` + +### unquote + +#include_code unquote noir_stdlib/src/meta/mod.nr rust + +Unquotes the passed-in token stream where this function was called. + +Example: +```rust +comptime { + let code = quote { 1 + 2 }; + + // let x = 1 + 2; + let x = unquote!(code); +} +``` + +### derive + +#include_code derive noir_stdlib/src/meta/mod.nr rust + +Attribute placed on struct definitions. + +Creates a trait impl for each trait passed in as an argument. +To do this, the trait must have a derive handler registered +with `derive_via` beforehand. The traits in the stdlib that +can be derived this way are `Eq`, `Ord`, `Default`, and `Hash`. + +Example: +```rust +#[derive(Eq, Default)] +struct Foo { + x: i32, + y: T, +} + +fn main() { + let foo1 = Foo::default(); + let foo2 = Foo { x: 0, y: &[0] }; + assert_eq(foo1, foo2); +} +``` + +### derive_via + +#include_code derive_via_signature noir_stdlib/src/meta/mod.nr rust + +Attribute placed on trait definitions. + +Registers a function to create impls for the given trait +when the trait is used in a `derive` call. Users may use +this to register their own functions to enable their traits +to be derived by `derive`. + +Because this function requires a function as an argument which +should produce a trait impl for any given struct, users may find +it helpful to use a function like `std::meta::make_trait_impl` to +help creating these impls. + +Example: +```rust +#[derive_via(derive_do_nothing)] +trait DoNothing { + fn do_nothing(self); +} + +comptime fn derive_do_nothing(s: StructDefinition) -> Quoted { + let typ = s.as_type(); + quote { + impl DoNothing for $typ { + fn do_nothing(self) { + println("Nothing"); + } + } + } +} +``` + +As another example, `derive_eq` in the stdlib is used to derive the `Eq` +trait for any struct. It makes use of `make_trait_impl` to do this: + +#include_code derive_eq noir_stdlib/src/cmp.nr rust + +### make_trait_impl + +#include_code make_trait_impl noir_stdlib/src/meta/mod.nr rust + +A helper function to more easily create trait impls while deriving traits. + +Note that this function only works for traits which: +1. Have only one method +2. Have no generics on the trait itself. + - E.g. Using this on a trait such as `trait Foo { ... }` will result in the + generated impl incorrectly missing the `T` generic. + +If your trait fits these criteria then `make_trait_impl` is likely the easiest +way to write your derive handler. The arguments are as follows: + +- `s`: The struct to make the impl for +- `trait_name`: The name of the trait to derive. E.g. `quote { Eq }`. +- `function_signature`: The signature of the trait method to derive. E.g. `fn eq(self, other: Self) -> bool`. +- `for_each_field`: An operation to be performed on each field. E.g. `|name| quote { (self.$name == other.$name) }`. +- `join_fields_with`: A separator to join each result of `for_each_field` with. + E.g. `quote { & }`. You can also use an empty `quote {}` for no separator. +- `body`: The result of the field operations are passed into this function for any final processing. + This is the place to insert any setup/teardown code the trait requires. If the trait doesn't require + any such code, you can return the body as-is: `|body| body`. + +Example deriving `Hash`: + +#include_code derive_hash noir_stdlib/src/hash/mod.nr rust + +Example deriving `Ord`: + +#include_code derive_ord noir_stdlib/src/cmp.nr rust diff --git a/noir/noir-repo/docs/docs/noir/standard_library/meta/module.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/module.md new file mode 100644 index 00000000000..d283f2da8b2 --- /dev/null +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/module.md @@ -0,0 +1,27 @@ +--- +title: Module +--- + +`std::meta::module` contains methods on the built-in `Module` type which represents a module in the source program. +Note that this type represents a module generally, it isn't limited to only `mod my_submodule { ... }` +declarations in the source program. + +## Methods + +### name + +#include_code name noir_stdlib/src/meta/module.nr rust + +Returns the name of the module. + +### functions + +#include_code functions noir_stdlib/src/meta/module.nr rust + +Returns each function in the module. + +### is_contract + +#include_code is_contract noir_stdlib/src/meta/module.nr rust + +`true` if this module is a contract module (was declared via `contract foo { ... }`). diff --git a/noir/noir-repo/docs/docs/noir/standard_library/meta/op.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/op.md new file mode 100644 index 00000000000..d8b154edc02 --- /dev/null +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/op.md @@ -0,0 +1,146 @@ +--- +title: UnaryOp and BinaryOp +--- + +`std::meta::op` contains the `UnaryOp` and `BinaryOp` types as well as methods on them. +These types are used to represent a unary or binary operator respectively in Noir source code. + +## Types + +### UnaryOp + +Represents a unary operator. One of `-`, `!`, `&mut`, or `*`. + +### Methods + +#### is_minus + +#include_code is_minus noir_stdlib/src/meta/op.nr rust + +Returns `true` if this operator is `-`. + +#### is_not + +#include_code is_not noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `!` + +#### is_mutable_reference + +#include_code is_mutable_reference noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `&mut` + +#### is_dereference + +#include_code is_dereference noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `*` + +#### quoted + +#include_code unary_quoted noir_stdlib/src/meta/op.nr rust + +Returns this operator as a `Quoted` value. + +### BinaryOp + +Represents a binary operator. One of `+`, `-`, `*`, `/`, `%`, `==`, `!=`, `<`, `<=`, `>`, `>=`, `&`, `|`, `^`, `>>`, or `<<`. + +### Methods + +#### is_add + +#include_code is_add noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `+` + +#### is_subtract + +#include_code is_subtract noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `-` + +#### is_multiply + +#include_code is_multiply noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `*` + +#### is_divide + +#include_code is_divide noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `/` + +#### is_modulo + +#include_code is_modulo noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `%` + +#### is_equal + +#include_code is_equal noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `==` + +#### is_not_equal + +#include_code is_not_equal noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `!=` + +#### is_less_than + +#include_code is_less_than noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `<` + +#### is_less_than_or_equal + +#include_code is_less_than_or_equal noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `<=` + +#### is_greater_than + +#include_code is_greater_than noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `>` + +#### is_greater_than_or_equal + +#include_code is_greater_than_or_equal noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `>=` + +#### is_and + +#include_code is_and noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `&` + +#### is_or + +#include_code is_or noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `|` + +#### is_shift_right + +#include_code is_shift_right noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `>>` + +#### is_shift_left + +#include_code is_shift_right noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `<<` + +#### quoted + +#include_code binary_quoted noir_stdlib/src/meta/op.nr rust + +Returns this operator as a `Quoted` value. \ No newline at end of file diff --git a/noir/noir-repo/docs/docs/noir/standard_library/meta/quoted.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/quoted.md new file mode 100644 index 00000000000..bf79f2e5d9f --- /dev/null +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/quoted.md @@ -0,0 +1,57 @@ +--- +title: Quoted +--- + +`std::meta::quoted` contains methods on the built-in `Quoted` type which represents +quoted token streams and is the result of the `quote { ... }` expression. + +## Methods + +### as_expr + +#include_code as_expr noir_stdlib/src/meta/quoted.nr rust + +Parses the quoted token stream as an expression. Returns `Option::none()` if +the expression failed to parse. + +Example: + +#include_code as_expr_example test_programs/noir_test_success/comptime_expr/src/main.nr rust + +### as_module + +#include_code as_module noir_stdlib/src/meta/quoted.nr rust + +Interprets this token stream as a module path leading to the name of a module. +Returns `Option::none()` if the module isn't found or this token stream cannot be parsed as a path. + +Example: + +#include_code as_module_example test_programs/compile_success_empty/comptime_module/src/main.nr rust + +### as_trait_constraint + +#include_code as_trait_constraint noir_stdlib/src/meta/quoted.nr rust + +Interprets this token stream as a trait constraint (without an object type). +Note that this function panics instead of returning `Option::none()` if the token +stream does not parse and resolve to a valid trait constraint. + +Example: + +#include_code implements_example test_programs/compile_success_empty/comptime_type/src/main.nr rust + +### as_type + +#include_code as_type noir_stdlib/src/meta/quoted.nr rust + +Interprets this token stream as a resolved type. Panics if the token +stream doesn't parse to a type or if the type isn't a valid type in scope. + +#include_code implements_example test_programs/compile_success_empty/comptime_type/src/main.nr rust + +## Trait Implementations + +```rust +impl Eq for Quoted +``` diff --git a/noir/noir-repo/docs/docs/noir/standard_library/meta/struct_def.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/struct_def.md new file mode 100644 index 00000000000..ab3ea4e0698 --- /dev/null +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/struct_def.md @@ -0,0 +1,45 @@ +--- +title: StructDefinition +--- + +`std::meta::struct_def` contains methods on the built-in `StructDefinition` type. +This type corresponds to `struct Name { field1: Type1, ... }` items in the source program. + +## Methods + +### as_type + +#include_code as_type noir_stdlib/src/meta/struct_def.nr rust + +Returns this struct as a type in the source program. If this struct has +any generics, the generics are also included as-is. + +### generics + +#include_code generics noir_stdlib/src/meta/struct_def.nr rust + +Returns each generic on this struct. + +Example: + +``` +#[example] +struct Foo { + bar: [T; 2], + baz: Baz, +} + +comptime fn example(foo: StructDefinition) { + assert_eq(foo.generics().len(), 2); + + // Fails because `T` isn't in scope + // let t = quote { T }.as_type(); + // assert_eq(foo.generics()[0], t); +} +``` + +### fields + +#include_code fields noir_stdlib/src/meta/struct_def.nr rust + +Returns each field of this struct as a pair of (field name, field type). diff --git a/noir/noir-repo/docs/docs/noir/standard_library/meta/trait_constraint.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/trait_constraint.md new file mode 100644 index 00000000000..3106f732b5a --- /dev/null +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/trait_constraint.md @@ -0,0 +1,17 @@ +--- +title: TraitConstraint +--- + +`std::meta::trait_constraint` contains methods on the built-in `TraitConstraint` type which represents +a trait constraint that can be used to search for a trait implementation. This is similar +syntactically to just the trait itself, but can also contain generic arguments. E.g. `Eq`, `Default`, +`BuildHasher`. + +This type currently has no public methods but it can be used alongside `Type` in `implements` or `get_trait_impl`. + +## Trait Implementations + +```rust +impl Eq for TraitConstraint +impl Hash for TraitConstraint +``` diff --git a/noir/noir-repo/docs/docs/noir/standard_library/meta/trait_def.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/trait_def.md new file mode 100644 index 00000000000..b6e8bf4ff76 --- /dev/null +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/trait_def.md @@ -0,0 +1,22 @@ +--- +title: TraitDefinition +--- + +`std::meta::trait_def` contains methods on the built-in `TraitDefinition` type. This type +represents trait definitions such as `trait Foo { .. }` at the top-level of a program. + +## Methods + +### as_trait_constraint + +#include_code as_trait_constraint noir_stdlib/src/meta/trait_def.nr rust + +Converts this trait into a trait constraint. If there are any generics on this +trait, they will be kept as-is without instantiating or replacing them. + +## Trait Implementations + +```rust +impl Eq for TraitDefinition +impl Hash for TraitDefinition +``` diff --git a/noir/noir-repo/docs/docs/noir/standard_library/meta/trait_impl.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/trait_impl.md new file mode 100644 index 00000000000..659c6aad719 --- /dev/null +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/trait_impl.md @@ -0,0 +1,52 @@ +--- +title: TraitImpl +--- + +`std::meta::trait_impl` contains methods on the built-in `TraitImpl` type which represents a trait +implementation such as `impl Foo for Bar { ... }`. + +## Methods + +### trait_generic_args + +#include_code trait_generic_args noir_stdlib/src/meta/trait_impl.nr rust + +Returns any generic arguments on the trait of this trait implementation, if any. + +```rs +impl Foo for Bar { ... } + +comptime { + let bar_type = quote { Bar }.as_type(); + let foo = quote { Foo }.as_trait_constraint(); + + let my_impl: TraitImpl = bar_type.get_trait_impl(foo).unwrap(); + + let generics = my_impl.trait_generic_args(); + assert_eq(generics.len(), 2); + + assert_eq(generics[0], quote { i32 }.as_type()); + assert_eq(generics[1], quote { Field }.as_type()); +} +``` + +### methods + +#include_code methods noir_stdlib/src/meta/trait_impl.nr rust + +Returns each method in this trait impl. + +Example: + +```rs +comptime { + let i32_type = quote { i32 }.as_type(); + let eq = quote { Eq }.as_trait_constraint(); + + let impl_eq_for_i32: TraitImpl = i32_type.get_trait_impl(eq).unwrap(); + let methods = impl_eq_for_i32.methods(); + + assert_eq(methods.len(), 1); + assert_eq(methods[0].name(), quote { eq }); +} +``` diff --git a/noir/noir-repo/docs/docs/noir/standard_library/meta/typ.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/typ.md new file mode 100644 index 00000000000..bad6435e94a --- /dev/null +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/typ.md @@ -0,0 +1,126 @@ +--- +title: Type +--- + +`std::meta::typ` contains methods on the built-in `Type` type used for representing +a type in the source program. + +## Methods + +### as_array + +#include_code as_array noir_stdlib/src/meta/typ.nr rust + +If this type is an array, return a pair of (element type, size type). + +Example: + +```rust +comptime { + let array_type = quote { [Field; 3] }.as_type(); + let (field_type, three_type) = array_type.as_array().unwrap(); + + assert(field_type.is_field()); + assert_eq(three_type.as_constant().unwrap(), 3); +} +``` + +### as_constant + +#include_code as_constant noir_stdlib/src/meta/typ.nr rust + +If this type is a constant integer (such as the `3` in the array type `[Field; 3]`), +return the numeric constant. + +### as_integer + +#include_code as_integer noir_stdlib/src/meta/typ.nr rust + +If this is an integer type, return a boolean which is `true` +if the type is signed, as well as the number of bits of this integer type. + +### as_slice + +#include_code as_slice noir_stdlib/src/meta/typ.nr rust + +If this is a slice type, return the element type of the slice. + +### as_struct + +#include_code as_struct noir_stdlib/src/meta/typ.nr rust + +If this is a struct type, returns the struct in addition to +any generic arguments on this type. + +### as_tuple + +#include_code as_tuple noir_stdlib/src/meta/typ.nr rust + +If this is a tuple type, returns each element type of the tuple. + +### get_trait_impl + +#include_code get_trait_impl noir_stdlib/src/meta/typ.nr rust + +Retrieves the trait implementation that implements the given +trait constraint for this type. If the trait constraint is not +found, `None` is returned. Note that since the concrete trait implementation +for a trait constraint specified from a `where` clause is unknown, +this function will return `None` in these cases. If you only want to know +whether a type implements a trait, use `implements` instead. + +Example: + +```rust +comptime { + let field_type = quote { Field }.as_type(); + let default = quote { Default }.as_trait_constraint(); + + let the_impl: TraitImpl = field_type.get_trait_impl(default).unwrap(); + assert(the_impl.methods().len(), 1); +} +``` + +### implements + +#include_code implements noir_stdlib/src/meta/typ.nr rust + +`true` if this type implements the given trait. Note that unlike +`get_trait_impl` this will also return true for any `where` constraints +in scope. + +Example: + +```rust +fn foo() where T: Default { + comptime { + let field_type = quote { Field }.as_type(); + let default = quote { Default }.as_trait_constraint(); + assert(field_type.implements(default)); + + let t = quote { T }.as_type(); + assert(t.implements(default)); + } +} +``` + +### is_bool + +#include_code is_bool noir_stdlib/src/meta/typ.nr rust + +`true` if this type is `bool`. + +### is_field + +#include_code is_field noir_stdlib/src/meta/typ.nr rust + +`true` if this type is `Field`. + +## Trait Implementations + +```rust +impl Eq for Type +``` +Note that this is syntactic equality, this is not the same as whether two types will type check +to be the same type. Unless type inference or generics are being used however, users should not +typically have to worry about this distinction. diff --git a/noir/noir-repo/docs/docs/noir/standard_library/meta/unresolved_type.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/unresolved_type.md new file mode 100644 index 00000000000..9c61f91dee2 --- /dev/null +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/unresolved_type.md @@ -0,0 +1,13 @@ +--- +title: UnresolvedType +--- + +`std::meta::unresolved_type` contains methods on the built-in `UnresolvedType` type for the syntax of types. + +## Methods + +### is_field + +#include_code is_field noir_stdlib/src/meta/unresolved_type.nr rust + +Returns true if this type refers to the Field type. 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 c800d91ac69..3bb96c66795 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 f0768339ea9..27869205ad3 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 af2bea12c60..23683a54e45 100644 --- a/noir/noir-repo/noir_stdlib/src/array.nr +++ b/noir/noir-repo/noir_stdlib/src/array.nr @@ -3,6 +3,7 @@ use crate::option::Option; use crate::convert::From; impl [T; N] { + /// Returns the length of the slice. #[builtin(array_len)] pub fn len(self) -> u32 {} @@ -11,14 +12,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 10182ca83b0..b7f473429a7 100644 --- a/noir/noir-repo/noir_stdlib/src/cmp.nr +++ b/noir/noir-repo/noir_stdlib/src/cmp.nr @@ -7,21 +7,25 @@ trait Eq { } // docs:end:eq-trait +// docs:start:derive_eq comptime fn derive_eq(s: StructDefinition) -> Quoted { let signature = quote { fn eq(_self: Self, _other: Self) -> bool }; let for_each_field = |name| quote { (_self.$name == _other.$name) }; let body = |fields| fields; crate::meta::make_trait_impl(s, quote { Eq }, signature, for_each_field, quote { & }, body) } +// docs:end:derive_eq 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 } } @@ -116,6 +120,7 @@ trait Ord { } // docs:end:ord-trait +// docs:start:derive_ord comptime fn derive_ord(s: StructDefinition) -> Quoted { let signature = quote { fn cmp(_self: Self, _other: Self) -> std::cmp::Ordering }; let for_each_field = |name| quote { @@ -130,6 +135,7 @@ comptime fn derive_ord(s: StructDefinition) -> Quoted { }; crate::meta::make_trait_impl(s, quote { Ord }, signature, for_each_field, quote {}, body) } +// docs:end:derive_ord // Note: Field deliberately does not implement Ord @@ -157,6 +163,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 +199,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 a56d6f60390..a4c0f642a82 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 8324583632f..4607b06d667 100644 --- a/noir/noir-repo/noir_stdlib/src/collections/map.nr +++ b/noir/noir-repo/noir_stdlib/src/collections/map.nr @@ -77,10 +77,10 @@ impl Slot { // While conducting lookup, we iterate attempt from 0 to N - 1 due to heuristic, // that if we have went that far without finding desired, // it is very unlikely to be after - performance will be heavily degraded. -impl HashMap { +impl HashMap { // Creates a new instance of HashMap with specified BuildHasher. // docs:start:with_hasher - pub fn with_hasher(_build_hasher: B) -> Self + pub fn with_hasher(_build_hasher: B) -> Self where B: BuildHasher { // docs:end:with_hasher @@ -99,7 +99,7 @@ impl HashMap { // Returns true if the map contains a value for the specified key. // docs:start:contains_key - pub fn contains_key( + pub fn contains_key( self, key: K ) -> bool @@ -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); } } @@ -181,7 +183,7 @@ impl HashMap { // For each key-value entry applies mutator function. // docs:start:iter_mut - pub fn iter_mut( + pub fn iter_mut( &mut self, f: fn(K, V) -> (K, V) ) @@ -206,7 +208,7 @@ impl HashMap { // For each key applies mutator function. // docs:start:iter_keys_mut - pub fn iter_keys_mut( + pub fn iter_keys_mut( &mut self, f: fn(K) -> K ) @@ -276,7 +278,7 @@ impl HashMap { // Get the value by key. If it does not exist, returns none(). // docs:start:get - pub fn get( + pub fn get( self, key: K ) -> Option @@ -311,7 +313,7 @@ impl HashMap { // Insert key-value entry. In case key was already present, value is overridden. // docs:start:insert - pub fn insert( + pub fn insert( &mut self, key: K, value: V @@ -354,7 +356,7 @@ impl HashMap { // Removes a key-value entry. If key is not present, HashMap remains unchanged. // docs:start:remove - pub fn remove( + pub fn remove( &mut self, key: K ) @@ -386,7 +388,7 @@ impl HashMap { } // Apply HashMap's hasher onto key to obtain pre-hash for probing. - fn hash( + fn hash( self, key: K ) -> u32 diff --git a/noir/noir-repo/noir_stdlib/src/collections/umap.nr b/noir/noir-repo/noir_stdlib/src/collections/umap.nr index fe16ef6bca2..c552c053a92 100644 --- a/noir/noir-repo/noir_stdlib/src/collections/umap.nr +++ b/noir/noir-repo/noir_stdlib/src/collections/umap.nr @@ -76,10 +76,10 @@ impl Slot { // While conducting lookup, we iterate attempt from 0 to N - 1 due to heuristic, // that if we have went that far without finding desired, // it is very unlikely to be after - performance will be heavily degraded. -impl UHashMap { +impl UHashMap { // Creates a new instance of UHashMap with specified BuildHasher. // docs:start:with_hasher - pub fn with_hasher(_build_hasher: B) -> Self + pub fn with_hasher(_build_hasher: B) -> Self where B: BuildHasher { // docs:end:with_hasher @@ -88,7 +88,7 @@ impl UHashMap { Self { _table, _len, _build_hasher } } - pub fn with_hasher_and_capacity(_build_hasher: B, capacity: u32) -> Self + pub fn with_hasher_and_capacity(_build_hasher: B, capacity: u32) -> Self where B: BuildHasher { // docs:end:with_hasher @@ -110,7 +110,7 @@ impl UHashMap { // Returns true if the map contains a value for the specified key. // docs:start:contains_key - pub fn contains_key( + pub fn contains_key( self, key: K ) -> bool @@ -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. @@ -192,7 +194,7 @@ impl UHashMap { // For each key-value entry applies mutator function. // docs:start:iter_mut - unconstrained pub fn iter_mut( + unconstrained pub fn iter_mut( &mut self, f: fn(K, V) -> (K, V) ) @@ -214,7 +216,7 @@ impl UHashMap { // For each key applies mutator function. // docs:start:iter_keys_mut - unconstrained pub fn iter_keys_mut( + unconstrained pub fn iter_keys_mut( &mut self, f: fn(K) -> K ) @@ -281,7 +283,7 @@ impl UHashMap { // Get the value by key. If it does not exist, returns none(). // docs:start:get - unconstrained pub fn get( + unconstrained pub fn get( self, key: K ) -> Option @@ -313,7 +315,7 @@ impl UHashMap { // Insert key-value entry. In case key was already present, value is overridden. // docs:start:insert - unconstrained pub fn insert( + unconstrained pub fn insert( &mut self, key: K, value: V @@ -351,7 +353,7 @@ impl UHashMap { } } - unconstrained fn try_resize(&mut self) + unconstrained fn try_resize(&mut self) where B: BuildHasher, K: Eq + Hash, H: Hasher { if self.len() + 1 >= self.capacity() / 2 { let capacity = self.capacity() * 2; @@ -366,7 +368,7 @@ impl UHashMap { // Removes a key-value entry. If key is not present, UHashMap remains unchanged. // docs:start:remove - unconstrained pub fn remove( + unconstrained pub fn remove( &mut self, key: K ) @@ -395,7 +397,7 @@ impl UHashMap { } // Apply UHashMap's hasher onto key to obtain pre-hash for probing. - fn hash( + fn hash( self, key: K ) -> u32 @@ -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 f9399bfb865..3ac5fbb394e 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 e8db0a30c38..ed0053694c7 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/field/mod.nr b/noir/noir-repo/noir_stdlib/src/field/mod.nr index 4b6deaa1106..534ac012beb 100644 --- a/noir/noir-repo/noir_stdlib/src/field/mod.nr +++ b/noir/noir-repo/noir_stdlib/src/field/mod.nr @@ -2,35 +2,85 @@ mod bn254; use bn254::lt as bn254_lt; impl Field { + /// Asserts that `self` can be represented in `bit_size` bits. + /// + /// # Failures + /// Causes a constraint failure for `Field` values exceeding `2^{bit_size}`. + pub fn assert_max_bit_size(self, bit_size: u32) { + crate::assert_constant(bit_size); + assert(bit_size < modulus_num_bits() as u32); + self.__assert_max_bit_size(bit_size); + } + + #[builtin(apply_range_constraint)] + fn __assert_max_bit_size(self, bit_size: u32) {} + + /// Decomposes `self` into its little endian bit decomposition as a `[u1]` slice of length `bit_size`. + /// This slice will be zero padded should not all bits be necessary to represent `self`. + /// + /// # Failures + /// Causes a constraint failure for `Field` values exceeding `2^{bit_size}` as the resulting slice will not + /// be able to represent the original `Field`. + /// + /// # Safety + /// Values of `bit_size` equal to or greater than the number of bits necessary to represent the `Field` modulus + /// (e.g. 254 for the BN254 field) allow for multiple bit decompositions. This is due to how the `Field` will + /// wrap around due to overflow when verifying the decomposition. pub fn to_le_bits(self: Self, bit_size: u32) -> [u1] { crate::assert_constant(bit_size); self.__to_le_bits(bit_size) } + /// Decomposes `self` into its big endian bit decomposition as a `[u1]` slice of length `bit_size`. + /// This slice will be zero padded should not all bits be necessary to represent `self`. + /// + /// # Failures + /// Causes a constraint failure for `Field` values exceeding `2^{bit_size}` as the resulting slice will not + /// be able to represent the original `Field`. + /// + /// # Safety + /// Values of `bit_size` equal to or greater than the number of bits necessary to represent the `Field` modulus + /// (e.g. 254 for the BN254 field) allow for multiple bit decompositions. This is due to how the `Field` will + /// wrap around due to overflow when verifying the decomposition. pub fn to_be_bits(self: Self, bit_size: u32) -> [u1] { crate::assert_constant(bit_size); self.__to_be_bits(bit_size) } + /// See `Field.to_be_bits` #[builtin(to_le_bits)] fn __to_le_bits(self, _bit_size: u32) -> [u1] {} + /// See `Field.to_le_bits` #[builtin(to_be_bits)] fn __to_be_bits(self, bit_size: u32) -> [u1] {} - #[builtin(apply_range_constraint)] - fn __assert_max_bit_size(self, bit_size: u32) {} - - pub fn assert_max_bit_size(self: Self, bit_size: u32) { - crate::assert_constant(bit_size); - assert(bit_size < modulus_num_bits() as u32); - self.__assert_max_bit_size(bit_size); - } - + /// Decomposes `self` into its little endian byte decomposition as a `[u8]` slice of length `byte_size`. + /// This slice will be zero padded should not all bytes be necessary to represent `self`. + /// + /// # Failures + /// Causes a constraint failure for `Field` values exceeding `2^{8*byte_size}` as the resulting slice will not + /// be able to represent the original `Field`. + /// + /// # Safety + /// Values of `byte_size` equal to or greater than the number of bytes necessary to represent the `Field` modulus + /// (e.g. 32 for the BN254 field) allow for multiple byte decompositions. This is due to how the `Field` will + /// wrap around due to overflow when verifying the decomposition. pub fn to_le_bytes(self: Self, byte_size: u32) -> [u8] { self.to_le_radix(256, byte_size) } + /// Decomposes `self` into its big endian byte decomposition as a `[u8]` slice of length `byte_size`. + /// This slice will be zero padded should not all bytes be necessary to represent `self`. + /// + /// # Failures + /// Causes a constraint failure for `Field` values exceeding `2^{8*byte_size}` as the resulting slice will not + /// be able to represent the original `Field`. + /// + /// # Safety + /// Values of `byte_size` equal to or greater than the number of bytes necessary to represent the `Field` modulus + /// (e.g. 32 for the BN254 field) allow for multiple byte decompositions. This is due to how the `Field` will + /// wrap around due to overflow when verifying the decomposition. pub fn to_be_bytes(self: Self, byte_size: u32) -> [u8] { self.to_be_radix(256, byte_size) } @@ -47,7 +97,6 @@ impl Field { self.__to_be_radix(radix, result_len) } - // decompose `_self` into a `_result_len` vector over the `_radix` basis // `_radix` must be less than 256 #[builtin(to_le_radix)] fn __to_le_radix(self, radix: u32, result_len: u32) -> [u8] {} diff --git a/noir/noir-repo/noir_stdlib/src/hash/keccak.nr b/noir/noir-repo/noir_stdlib/src/hash/keccak.nr index bb8a9cc2ce2..0c31d238f66 100644 --- a/noir/noir-repo/noir_stdlib/src/hash/keccak.nr +++ b/noir/noir-repo/noir_stdlib/src/hash/keccak.nr @@ -1,19 +1,27 @@ +use crate::collections::vec::Vec; +use crate::runtime::is_unconstrained; + global LIMBS_PER_BLOCK = 17; //BLOCK_SIZE / 8; global NUM_KECCAK_LANES = 25; global BLOCK_SIZE = 136; //(1600 - BITS * 2) / WORD_SIZE; global WORD_SIZE = 8; -use crate::collections::vec::Vec; - #[foreign(keccakf1600)] fn keccakf1600(input: [u64; 25]) -> [u64; 25] {} #[no_predicates] -pub(crate) fn keccak256(mut input: [u8; N], message_size: u32) -> [u8; 32] { +pub(crate) fn keccak256(input: [u8; N], message_size: u32) -> [u8; 32] { assert(N >= message_size); - for i in 0..N { - if i >= message_size { - input[i] = 0; + let mut block_bytes = [0; BLOCK_SIZE]; + if is_unconstrained() { + for i in 0..message_size { + block_bytes[i] = input[i]; + } + } else { + for i in 0..N { + if i < message_size { + block_bytes[i] = input[i]; + } } } @@ -24,11 +32,6 @@ pub(crate) fn keccak256(mut input: [u8; N], message_size: u32) -> [u let real_max_blocks = (message_size + BLOCK_SIZE) / BLOCK_SIZE; let real_blocks_bytes = real_max_blocks * BLOCK_SIZE; - let mut block_bytes = [0; BLOCK_SIZE]; - for i in 0..N { - block_bytes[i] = input[i]; - } - block_bytes[message_size] = 1; block_bytes[real_blocks_bytes - 1] = 0x80; @@ -36,28 +39,28 @@ pub(crate) fn keccak256(mut input: [u8; N], message_size: u32) -> [u // means we need to swap our byte ordering let num_limbs = max_blocks * LIMBS_PER_BLOCK; //max_blocks_length / WORD_SIZE; for i in 0..num_limbs { - let mut temp = [0; 8]; - for j in 0..8 { - temp[j] = block_bytes[8*i+j]; + let mut temp = [0; WORD_SIZE]; + let word_size_times_i = WORD_SIZE * i; + for j in 0..WORD_SIZE { + temp[j] = block_bytes[word_size_times_i+j]; } - for j in 0..8 { - block_bytes[8 * i + j] = temp[7 - j]; + for j in 0..WORD_SIZE { + block_bytes[word_size_times_i + j] = temp[7 - j]; } } - let byte_size = max_blocks_length; + let mut sliced_buffer = Vec::new(); - for _i in 0..num_limbs { - sliced_buffer.push(0); - } // populate a vector of 64-bit limbs from our byte array for i in 0..num_limbs { + let word_size_times_i = i * WORD_SIZE; + let ws_times_i_plus_7 = word_size_times_i + 7; let mut sliced = 0; - if (i * WORD_SIZE + WORD_SIZE > byte_size) { - let slice_size = byte_size - (i * WORD_SIZE); + if (word_size_times_i + WORD_SIZE > max_blocks_length) { + let slice_size = max_blocks_length - word_size_times_i; let byte_shift = (WORD_SIZE - slice_size) * 8; let mut v = 1; for k in 0..slice_size { - sliced += v * (block_bytes[i * WORD_SIZE+7-k] as Field); + sliced += v * (block_bytes[ws_times_i_plus_7-k] as Field); v *= 256; } let w = 1 << (byte_shift as u8); @@ -65,22 +68,20 @@ pub(crate) fn keccak256(mut input: [u8; N], message_size: u32) -> [u } else { let mut v = 1; for k in 0..WORD_SIZE { - sliced += v * (block_bytes[i * WORD_SIZE+7-k] as Field); + sliced += v * (block_bytes[ws_times_i_plus_7-k] as Field); v *= 256; } } - sliced_buffer.set(i, sliced as u64); + + sliced_buffer.push(sliced as u64); } //2. sponge_absorb - let num_blocks = max_blocks; let mut state : [u64;NUM_KECCAK_LANES]= [0; NUM_KECCAK_LANES]; - let mut under_block = true; - for i in 0..num_blocks { - if i == real_max_blocks { - under_block = false; - } - if under_block { + // When in an unconstrained runtime we can take advantage of runtime loop bounds, + // thus allowing us to simplify the loop body. + if is_unconstrained() { + for i in 0..real_max_blocks { if (i == 0) { for j in 0..LIMBS_PER_BLOCK { state[j] = sliced_buffer.get(j); @@ -92,6 +93,22 @@ pub(crate) fn keccak256(mut input: [u8; N], message_size: u32) -> [u } state = keccakf1600(state); } + } else { + // `real_max_blocks` is guaranteed to at least be `1` + // We peel out the first block as to avoid a conditional inside of the loop. + // Otherwise, a dynamic predicate can cause a blowup in a constrained runtime. + for j in 0..LIMBS_PER_BLOCK { + state[j] = sliced_buffer.get(j); + } + state = keccakf1600(state); + for i in 1..max_blocks { + if i < real_max_blocks { + for j in 0..LIMBS_PER_BLOCK { + state[j] = state[j] ^ sliced_buffer.get(i * LIMBS_PER_BLOCK + j); + } + state = keccakf1600(state); + } + } } //3. sponge_squeeze diff --git a/noir/noir-repo/noir_stdlib/src/hash/mod.nr b/noir/noir-repo/noir_stdlib/src/hash/mod.nr index 88dbf83e5bd..657e1cd8309 100644 --- a/noir/noir-repo/noir_stdlib/src/hash/mod.nr +++ b/noir/noir-repo/noir_stdlib/src/hash/mod.nr @@ -2,19 +2,17 @@ mod poseidon; mod mimc; mod poseidon2; mod keccak; +mod sha256; +mod sha512; use crate::default::Default; use crate::uint128::U128; -use crate::sha256::{digest, sha256_var}; use crate::collections::vec::Vec; use crate::embedded_curve_ops::{EmbeddedCurvePoint, EmbeddedCurveScalar, multi_scalar_mul, multi_scalar_mul_slice}; use crate::meta::derive_via; -#[foreign(sha256)] -// docs:start:sha256 -pub fn sha256(input: [u8; N]) -> [u8; 32] -// docs:end:sha256 -{} +// Kept for backwards compatibility +use sha256::{digest, sha256, sha256_compression, sha256_var}; #[foreign(blake2s)] // docs:start:blake2s @@ -108,7 +106,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 } @@ -135,9 +135,6 @@ pub fn keccak256(input: [u8; N], message_size: u32) -> [u8; 32] #[foreign(poseidon2_permutation)] pub fn poseidon2_permutation(_input: [Field; N], _state_length: u32) -> [Field; N] {} -#[foreign(sha256_compression)] -pub fn sha256_compression(_input: [u32; 16], _state: [u32; 8]) -> [u32; 8] {} - // Generic hashing support. // Partially ported and impacted by rust. @@ -147,12 +144,14 @@ trait Hash { fn hash(self, state: &mut H) where H: Hasher; } +// docs:start:derive_hash comptime fn derive_hash(s: StructDefinition) -> Quoted { let name = quote { Hash }; let signature = quote { fn hash(_self: Self, _state: &mut H) where H: std::hash::Hasher }; let for_each_field = |name| quote { _self.$name.hash(_state); }; crate::meta::make_trait_impl(s, name, signature, for_each_field, quote {}, |fields| fields) } +// docs:end:derive_hash // Hasher trait shall be implemented by algorithms to provide hash-agnostic means. // TODO: consider making the types generic here ([u8], [Field], etc.) @@ -193,12 +192,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 +228,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/hash/poseidon2.nr b/noir/noir-repo/noir_stdlib/src/hash/poseidon2.nr index 9626da0cf97..cf820f86370 100644 --- a/noir/noir-repo/noir_stdlib/src/hash/poseidon2.nr +++ b/noir/noir-repo/noir_stdlib/src/hash/poseidon2.nr @@ -26,7 +26,7 @@ impl Poseidon2 { result } - fn perform_duplex(&mut self) -> [Field; RATE] { + fn perform_duplex(&mut self) { // zero-pad the cache for i in 0..RATE { if i >= self.cache_size { @@ -38,61 +38,30 @@ impl Poseidon2 { self.state[i] += self.cache[i]; } self.state = crate::hash::poseidon2_permutation(self.state, 4); - // return `RATE` number of field elements from the sponge state. - let mut result = [0; RATE]; - for i in 0..RATE { - result[i] = self.state[i]; - } - result } fn absorb(&mut self, input: Field) { - if (!self.squeeze_mode) & (self.cache_size == RATE) { + assert(!self.squeeze_mode); + if self.cache_size == RATE { // If we're absorbing, and the cache is full, apply the sponge permutation to compress the cache - let _ = self.perform_duplex(); + self.perform_duplex(); self.cache[0] = input; self.cache_size = 1; - } else if (!self.squeeze_mode) & (self.cache_size != RATE) { + } else { // If we're absorbing, and the cache is not full, add the input into the cache self.cache[self.cache_size] = input; self.cache_size += 1; - } else if self.squeeze_mode { - // If we're in squeeze mode, switch to absorb mode and add the input into the cache. - // N.B. I don't think this code path can be reached?! - self.cache[0] = input; - self.cache_size = 1; - self.squeeze_mode = false; } } fn squeeze(&mut self) -> Field { - if self.squeeze_mode & (self.cache_size == 0) { - // If we're in squeze mode and the cache is empty, there is nothing left to squeeze out of the sponge! - // Switch to absorb mode. - self.squeeze_mode = false; - self.cache_size = 0; - } - if !self.squeeze_mode { - // If we're in absorb mode, apply sponge permutation to compress the cache, populate cache with compressed - // state and switch to squeeze mode. Note: this code block will execute if the previous `if` condition was - // matched - let new_output_elements = self.perform_duplex(); - self.squeeze_mode = true; - for i in 0..RATE { - self.cache[i] = new_output_elements[i]; - } - self.cache_size = RATE; - } - // By this point, we should have a non-empty cache. Pop one item off the top of the cache and return it. - let result = self.cache[0]; - for i in 1..RATE { - if i < self.cache_size { - self.cache[i - 1] = self.cache[i]; - } - } - self.cache_size -= 1; - self.cache[self.cache_size] = 0; - result + assert(!self.squeeze_mode); + // If we're in absorb mode, apply sponge permutation to compress the cache. + self.perform_duplex(); + self.squeeze_mode = true; + + // Pop one item off the top of the permutation and return it. + self.state[0] } fn hash_internal(input: [Field; N], in_len: u32, is_variable_length: bool) -> Field { diff --git a/noir/noir-repo/noir_stdlib/src/hash/sha256.nr b/noir/noir-repo/noir_stdlib/src/hash/sha256.nr new file mode 100644 index 00000000000..d0e3d5e88c5 --- /dev/null +++ b/noir/noir-repo/noir_stdlib/src/hash/sha256.nr @@ -0,0 +1,248 @@ +use crate::runtime::is_unconstrained; + +// Implementation of SHA-256 mapping a byte array of variable length to +// 32 bytes. + +// Deprecated in favour of `sha256_var` +#[foreign(sha256)] +// docs:start:sha256 +pub fn sha256(input: [u8; N]) -> [u8; 32] +// docs:end:sha256 +{} + +#[foreign(sha256_compression)] +pub fn sha256_compression(_input: [u32; 16], _state: [u32; 8]) -> [u32; 8] {} + +// SHA-256 hash function +#[no_predicates] +pub fn digest(msg: [u8; N]) -> [u8; 32] { + sha256_var(msg, N as u64) +} + +// Convert 64-byte array to array of 16 u32s +fn msg_u8_to_u32(msg: [u8; 64]) -> [u32; 16] { + let mut msg32: [u32; 16] = [0; 16]; + + for i in 0..16 { + let mut msg_field: Field = 0; + for j in 0..4 { + msg_field = msg_field * 256 + msg[64 - 4*(i + 1) + j] as Field; + } + msg32[15 - i] = msg_field as u32; + } + + msg32 +} + +unconstrained fn build_msg_block_iter(msg: [u8; N], message_size: u64, msg_start: u32) -> ([u8; 64], u64) { + let mut msg_block: [u8; BLOCK_SIZE] = [0; BLOCK_SIZE]; + let mut msg_byte_ptr: u64 = 0; // Message byte pointer + let mut msg_end = msg_start + BLOCK_SIZE; + if msg_end > N { + msg_end = N; + } + for k in msg_start..msg_end { + if k as u64 < message_size { + msg_block[msg_byte_ptr] = msg[k]; + msg_byte_ptr = msg_byte_ptr + 1; + } + } + (msg_block, msg_byte_ptr) +} + +// Verify the block we are compressing was appropriately constructed +fn verify_msg_block( + msg: [u8; N], + message_size: u64, + msg_block: [u8; 64], + msg_start: u32 +) -> u64 { + let mut msg_byte_ptr: u64 = 0; // Message byte pointer + let mut msg_end = msg_start + BLOCK_SIZE; + let mut extra_bytes = 0; + if msg_end > N { + msg_end = N; + extra_bytes = msg_end - N; + } + + for k in msg_start..msg_end { + if k as u64 < message_size { + msg_byte_ptr = msg_byte_ptr + 1; + } + } + + for i in 0..BLOCK_SIZE { + if i as u64 >= msg_byte_ptr { + assert_eq(msg_block[i], 0); + } else { + assert_eq(msg_block[i], msg[msg_start + i - extra_bytes]); + } + } + + msg_byte_ptr +} + +global BLOCK_SIZE = 64; +global ZERO = 0; + +// Variable size SHA-256 hash +pub fn sha256_var(msg: [u8; N], message_size: u64) -> [u8; 32] { + let num_blocks = N / BLOCK_SIZE; + let mut msg_block: [u8; BLOCK_SIZE] = [0; BLOCK_SIZE]; + let mut h: [u32; 8] = [1779033703, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225]; // Intermediate hash, starting with the canonical initial value + let mut msg_byte_ptr = 0; // Pointer into msg_block + + for i in 0..num_blocks { + let (new_msg_block, new_msg_byte_ptr) = unsafe { + build_msg_block_iter(msg, message_size, BLOCK_SIZE * i) + }; + msg_block = new_msg_block; + + if !is_unconstrained() { + // Verify the block we are compressing was appropriately constructed + msg_byte_ptr = verify_msg_block(msg, message_size, msg_block, BLOCK_SIZE * i); + } else { + msg_byte_ptr = new_msg_byte_ptr; + } + + // Compress the block + h = sha256_compression(msg_u8_to_u32(msg_block), h); + } + + let modulo = N % BLOCK_SIZE; + // Handle setup of the final msg block. + // This case is only hit if the msg is less than the block size, + // or our message cannot be evenly split into blocks. + if modulo != 0 { + let (new_msg_block, new_msg_byte_ptr) = unsafe { + build_msg_block_iter(msg, message_size, BLOCK_SIZE * num_blocks) + }; + msg_block = new_msg_block; + + if !is_unconstrained() { + msg_byte_ptr = verify_msg_block(msg, message_size, msg_block, BLOCK_SIZE * num_blocks); + } else { + msg_byte_ptr = new_msg_byte_ptr; + } + } + + if msg_byte_ptr == BLOCK_SIZE as u64 { + msg_byte_ptr = 0; + } + + // This variable is used to get around the compiler under-constrained check giving a warning. + // We want to check against a constant zero, but if it does not come from the circuit inputs + // or return values the compiler check will issue a warning. + let zero = msg_block[0] - msg_block[0]; + + // Pad the rest such that we have a [u32; 2] block at the end representing the length + // of the message, and a block of 1 0 ... 0 following the message (i.e. [1 << 7, 0, ..., 0]). + msg_block[msg_byte_ptr] = 1 << 7; + let last_block = msg_block; + msg_byte_ptr = msg_byte_ptr + 1; + + unsafe { + let (new_msg_block, new_msg_byte_ptr) = pad_msg_block(msg_block, msg_byte_ptr); + msg_block = new_msg_block; + if crate::runtime::is_unconstrained() { + msg_byte_ptr = new_msg_byte_ptr; + } + } + + if !crate::runtime::is_unconstrained() { + for i in 0..64 { + assert_eq(msg_block[i], last_block[i]); + } + + // If i >= 57, there aren't enough bits in the current message block to accomplish this, so + // the 1 and 0s fill up the current block, which we then compress accordingly. + // Not enough bits (64) to store length. Fill up with zeros. + for _i in 57..64 { + if msg_byte_ptr <= 63 & msg_byte_ptr >= 57 { + assert_eq(msg_block[msg_byte_ptr], zero); + msg_byte_ptr += 1; + } + } + } + + if msg_byte_ptr >= 57 { + h = sha256_compression(msg_u8_to_u32(msg_block), h); + + msg_byte_ptr = 0; + } + + msg_block = unsafe { + attach_len_to_msg_block(msg_block, msg_byte_ptr, message_size) + }; + + if !crate::runtime::is_unconstrained() { + for i in 0..56 { + if i < msg_byte_ptr { + assert_eq(msg_block[i], last_block[i]); + } else { + assert_eq(msg_block[i], zero); + } + } + + let len = 8 * message_size; + let len_bytes = (len as Field).to_be_bytes(8); + for i in 56..64 { + assert_eq(msg_block[i], len_bytes[i - 56]); + } + } + + hash_final_block(msg_block, h) +} + +unconstrained fn pad_msg_block(mut msg_block: [u8; 64], mut msg_byte_ptr: u64) -> ([u8; 64], u64) { + // If i >= 57, there aren't enough bits in the current message block to accomplish this, so + // the 1 and 0s fill up the current block, which we then compress accordingly. + if msg_byte_ptr >= 57 { + // Not enough bits (64) to store length. Fill up with zeros. + if msg_byte_ptr < 64 { + for _ in 57..64 { + if msg_byte_ptr <= 63 { + msg_block[msg_byte_ptr] = 0; + msg_byte_ptr += 1; + } + } + } + } + (msg_block, msg_byte_ptr) +} + +unconstrained fn attach_len_to_msg_block(mut msg_block: [u8; 64], mut msg_byte_ptr: u64, message_size: u64) -> [u8; 64] { + let len = 8 * message_size; + let len_bytes = (len as Field).to_be_bytes(8); + for _i in 0..64 { + // In any case, fill blocks up with zeros until the last 64 (i.e. until msg_byte_ptr = 56). + if msg_byte_ptr < 56 { + msg_block[msg_byte_ptr] = 0; + msg_byte_ptr = msg_byte_ptr + 1; + } else if msg_byte_ptr < 64 { + for j in 0..8 { + msg_block[msg_byte_ptr + j] = len_bytes[j]; + } + msg_byte_ptr += 8; + } + } + msg_block +} + +fn hash_final_block(msg_block: [u8; 64], mut state: [u32; 8]) -> [u8; 32] { + let mut out_h: [u8; 32] = [0; 32]; // Digest as sequence of bytes + + // Hash final padded block + state = sha256_compression(msg_u8_to_u32(msg_block), state); + + // Return final hash as byte array + for j in 0..8 { + let h_bytes = (state[7 - j] as Field).to_le_bytes(4); + for k in 0..4 { + out_h[31 - 4*j - k] = h_bytes[k]; + } + } + + out_h +} + diff --git a/noir/noir-repo/noir_stdlib/src/hash/sha512.nr b/noir/noir-repo/noir_stdlib/src/hash/sha512.nr new file mode 100644 index 00000000000..be255a594af --- /dev/null +++ b/noir/noir-repo/noir_stdlib/src/hash/sha512.nr @@ -0,0 +1,165 @@ +// Implementation of SHA-512 mapping a byte array of variable length to +// 64 bytes. +// Internal functions act on 64-bit unsigned integers for simplicity. +// Auxiliary mappings; names as in FIPS PUB 180-4 +fn rotr64(a: u64, b: u8) -> u64 // 64-bit right rotation +{ + // None of the bits overlap between `(a >> b)` and `(a << (64 - b))` + // Addition is then equivalent to OR, with fewer constraints. + (a >> b) + (a << (64 - b)) +} + +fn sha_ch(x: u64, y: u64, z: u64) -> u64 { + (x & y) ^ (!x & z) +} + +fn sha_maj(x: u64, y: u64, z: u64) -> u64 { + (x & y) ^ (x & z) ^ (y & z) +} + +fn sha_bigma0(x: u64) -> u64 { + rotr64(x, 28) ^ rotr64(x, 34) ^ rotr64(x, 39) +} + +fn sha_bigma1(x: u64) -> u64 { + rotr64(x, 14) ^ rotr64(x, 18) ^ rotr64(x, 41) +} + +fn sha_sigma0(x: u64) -> u64 { + rotr64(x, 1) ^ rotr64(x, 8) ^ (x >> 7) +} + +fn sha_sigma1(x: u64) -> u64 { + rotr64(x, 19) ^ rotr64(x, 61) ^ (x >> 6) +} + +fn sha_w(msg: [u64; 16]) -> [u64; 80] // Expanded message blocks +{ + let mut w: [u64;80] = [0; 80]; + + for j in 0..16 { + w[j] = msg[j]; + } + + for j in 16..80 { + w[j] = crate::wrapping_add( + crate::wrapping_add(sha_sigma1(w[j-2]), w[j-7]), + crate::wrapping_add(sha_sigma0(w[j-15]), w[j-16]), + ); + } + w +} + +// SHA-512 compression function +#[no_predicates] +fn sha_c(msg: [u64; 16], hash: [u64; 8]) -> [u64; 8] { + // noir-fmt:ignore + let K: [u64; 80] = [4794697086780616226, 8158064640168781261, 13096744586834688815, 16840607885511220156, 4131703408338449720, 6480981068601479193, 10538285296894168987, 12329834152419229976, 15566598209576043074, 1334009975649890238, 2608012711638119052, 6128411473006802146, 8268148722764581231, 9286055187155687089, 11230858885718282805, 13951009754708518548, 16472876342353939154, 17275323862435702243, 1135362057144423861, 2597628984639134821, 3308224258029322869, 5365058923640841347, 6679025012923562964, 8573033837759648693, 10970295158949994411, 12119686244451234320, 12683024718118986047, 13788192230050041572, 14330467153632333762, 15395433587784984357, 489312712824947311, 1452737877330783856, 2861767655752347644, 3322285676063803686, 5560940570517711597, 5996557281743188959, 7280758554555802590, 8532644243296465576, 9350256976987008742, 10552545826968843579, 11727347734174303076, 12113106623233404929, 14000437183269869457, 14369950271660146224, 15101387698204529176, 15463397548674623760, 17586052441742319658, 1182934255886127544, 1847814050463011016, 2177327727835720531, 2830643537854262169, 3796741975233480872, 4115178125766777443, 5681478168544905931, 6601373596472566643, 7507060721942968483, 8399075790359081724, 8693463985226723168, 9568029438360202098, 10144078919501101548, 10430055236837252648, 11840083180663258601, 13761210420658862357, 14299343276471374635, 14566680578165727644, 15097957966210449927, 16922976911328602910, 17689382322260857208, 500013540394364858, 748580250866718886, 1242879168328830382, 1977374033974150939, 2944078676154940804, 3659926193048069267, 4368137639120453308, 4836135668995329356, 5532061633213252278, 6448918945643986474, 6902733635092675308, 7801388544844847127]; // first 64 bits of fractional parts of cube roots of first 80 primes + let mut out_h: [u64; 8] = hash; + let w = sha_w(msg); + for j in 0..80 { + let out1 = crate::wrapping_add(out_h[7], sha_bigma1(out_h[4])); + let out2 = crate::wrapping_add(out1, sha_ch(out_h[4], out_h[5], out_h[6])); + let t1 = crate::wrapping_add(crate::wrapping_add(out2, K[j]), w[j]); + let t2 = crate::wrapping_add(sha_bigma0(out_h[0]), sha_maj(out_h[0], out_h[1], out_h[2])); + out_h[7] = out_h[6]; + out_h[6] = out_h[5]; + out_h[5] = out_h[4]; + out_h[4] = crate::wrapping_add(out_h[3] , t1); + out_h[3] = out_h[2]; + out_h[2] = out_h[1]; + out_h[1] = out_h[0]; + out_h[0] = crate::wrapping_add(t1, t2); + } + + out_h +} +// Convert 128-byte array to array of 16 u64s +fn msg_u8_to_u64(msg: [u8; 128]) -> [u64; 16] { + let mut msg64: [u64; 16] = [0; 16]; + + for i in 0..16 { + let mut msg_field: Field = 0; + for j in 0..8 { + msg_field = msg_field * 256 + msg[128 - 8*(i + 1) + j] as Field; + } + msg64[15 - i] = msg_field as u64; + } + + msg64 +} +// SHA-512 hash function +pub fn digest(msg: [u8; N]) -> [u8; 64] { + let mut msg_block: [u8; 128] = [0; 128]; + // noir-fmt:ignore + let mut h: [u64; 8] = [7640891576956012808, 13503953896175478587, 4354685564936845355, 11912009170470909681, 5840696475078001361, 11170449401992604703, 2270897969802886507, 6620516959819538809]; // Intermediate hash, starting with the canonical initial value + let mut c: [u64; 8] = [0; 8]; // Compression of current message block as sequence of u64 + let mut out_h: [u8; 64] = [0; 64]; // Digest as sequence of bytes + let mut i: u64 = 0; // Message byte pointer + for k in 0..msg.len() { + // Populate msg_block + msg_block[i] = msg[k]; + i = i + 1; + if i == 128 { + // Enough to hash block + c = sha_c(msg_u8_to_u64(msg_block), h); + for j in 0..8 { + h[j] = crate::wrapping_add(h[j], c[j]); + } + + i = 0; + } + } + // Pad the rest such that we have a [u64; 2] block at the end representing the length + // of the message, and a block of 1 0 ... 0 following the message (i.e. [1 << 7, 0, ..., 0]). + msg_block[i] = 1 << 7; + i += 1; + // If i >= 113, there aren't enough bits in the current message block to accomplish this, so + // the 1 and 0s fill up the current block, which we then compress accordingly. + if i >= 113 { + // Not enough bits (128) to store length. Fill up with zeros. + if i < 128 { + for _i in 113..128 { + if i <= 127 { + msg_block[i] = 0; + i += 1; + } + } + } + c = sha_c(msg_u8_to_u64(msg_block), h); + for j in 0..8 { + h[j] = crate::wrapping_add(h[j], c[j]); + } + + i = 0; + } + + let len = 8 * msg.len(); + let len_bytes = (len as Field).to_le_bytes(16); + for _i in 0..128 { + // In any case, fill blocks up with zeros until the last 128 (i.e. until i = 112). + if i < 112 { + msg_block[i] = 0; + i += 1; + } else if i < 128 { + for j in 0..16 { + msg_block[127 - j] = len_bytes[j]; + } + i += 16; // Done. + } + } + // Hash final padded block + c = sha_c(msg_u8_to_u64(msg_block), h); + for j in 0..8 { + h[j] = crate::wrapping_add(h[j], c[j]); + } + // Return final hash as byte array + for j in 0..8 { + let h_bytes = (h[7 - j] as Field).to_le_bytes(8); + for k in 0..8 { + out_h[63 - 8*j - k] = h_bytes[k]; + } + } + + out_h +} diff --git a/noir/noir-repo/noir_stdlib/src/lib.nr b/noir/noir-repo/noir_stdlib/src/lib.nr index 2d559c43162..5c4587b69ac 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 542bd31fa84..88d17e20ee3 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 54681632543..c09d9b92c9b 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/expr.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/expr.nr @@ -1,6 +1,419 @@ use crate::option::Option; +use crate::meta::op::UnaryOp; +use crate::meta::op::BinaryOp; impl Expr { + #[builtin(expr_as_array)] + // docs:start:as_array + fn as_array(self) -> Option<[Expr]> {} + // docs:end:as_array + + #[builtin(expr_as_assign)] + // docs:start:as_assign + fn as_assign(self) -> Option<(Expr, Expr)> {} + // docs:end:as_assign + + #[builtin(expr_as_integer)] + // docs:start:as_integer + fn as_integer(self) -> Option<(Field, bool)> {} + // docs:end:as_integer + + #[builtin(expr_as_binary_op)] + // docs:start:as_binary_op + fn as_binary_op(self) -> Option<(Expr, BinaryOp, Expr)> {} + // docs:end:as_binary_op + + #[builtin(expr_as_block)] + // docs:start:as_block + fn as_block(self) -> Option<[Expr]> {} + // docs:end:as_block + + #[builtin(expr_as_bool)] + // docs:start:as_bool + fn as_bool(self) -> Option {} + // docs:end:as_bool + + #[builtin(expr_as_cast)] + fn as_cast(self) -> Option<(Expr, UnresolvedType)> {} + + #[builtin(expr_as_comptime)] + // docs:start:as_comptime + fn as_comptime(self) -> Option<[Expr]> {} + // docs:end:as_comptime + #[builtin(expr_as_function_call)] + // docs:start:as_function_call fn as_function_call(self) -> Option<(Expr, [Expr])> {} + // docs:end:as_function_call + + #[builtin(expr_as_if)] + // docs:start:as_if + fn as_if(self) -> Option<(Expr, Expr, Option)> {} + // docs:end:as_if + + #[builtin(expr_as_index)] + // docs:start:as_index + fn as_index(self) -> Option<(Expr, Expr)> {} + // docs:end:as_index + + #[builtin(expr_as_member_access)] + // docs:start:as_member_access + fn as_member_access(self) -> Option<(Expr, Quoted)> {} + // docs:end:as_member_access + + #[builtin(expr_as_method_call)] + // docs:start:as_method_call + fn as_method_call(self) -> Option<(Expr, Quoted, [UnresolvedType], [Expr])> {} + // docs:end:as_method_call + + #[builtin(expr_as_repeated_element_array)] + // docs:start:as_repeated_element_array + fn as_repeated_element_array(self) -> Option<(Expr, Expr)> {} + // docs:end:as_repeated_element_array + + #[builtin(expr_as_repeated_element_slice)] + // docs:start:as_repeated_element_slice + fn as_repeated_element_slice(self) -> Option<(Expr, Expr)> {} + // docs:end:as_repeated_element_slice + + #[builtin(expr_as_slice)] + // docs:start:as_slice + fn as_slice(self) -> Option<[Expr]> {} + // docs:end:as_slice + + #[builtin(expr_as_tuple)] + // docs:start:as_tuple + fn as_tuple(self) -> Option<[Expr]> {} + // docs:end:as_tuple + + #[builtin(expr_as_unary_op)] + // docs:start:as_unary_op + fn as_unary_op(self) -> Option<(UnaryOp, Expr)> {} + // docs:end:as_unary_op + + #[builtin(expr_as_unsafe)] + // docs:start:as_unsafe + fn as_unsafe(self) -> Option<[Expr]> {} + // docs:end:as_unsafe + + #[builtin(expr_has_semicolon)] + // docs:start:has_semicolon + fn has_semicolon(self) -> bool {} + // docs:end:has_semicolon + + #[builtin(expr_is_break)] + // docs:start:is_break + fn is_break(self) -> bool {} + // docs:end:is_break + + #[builtin(expr_is_continue)] + // docs:start:is_continue + fn is_continue(self) -> bool {} + // docs:end:is_continue + + // docs:start:mutate + fn mutate(self, f: fn[Env](Expr) -> Option) -> Expr { + // docs:end:mutate + let result = mutate_array(self, f); + let result = result.or_else(|| mutate_assign(self, f)); + let result = result.or_else(|| mutate_binary_op(self, f)); + let result = result.or_else(|| mutate_block(self, f)); + let result = result.or_else(|| mutate_cast(self, f)); + let result = result.or_else(|| mutate_comptime(self, f)); + let result = result.or_else(|| mutate_if(self, f)); + let result = result.or_else(|| mutate_index(self, f)); + let result = result.or_else(|| mutate_function_call(self, f)); + let result = result.or_else(|| mutate_member_access(self, f)); + let result = result.or_else(|| mutate_method_call(self, f)); + let result = result.or_else(|| mutate_repeated_element_array(self, f)); + let result = result.or_else(|| mutate_repeated_element_slice(self, f)); + let result = result.or_else(|| mutate_slice(self, f)); + let result = result.or_else(|| mutate_tuple(self, f)); + let result = result.or_else(|| mutate_unary_op(self, f)); + let result = result.or_else(|| mutate_unsafe(self, f)); + if result.is_some() { + let result = result.unwrap_unchecked(); + let modified = f(result); + modified.unwrap_or(result) + } else { + f(self).unwrap_or(self) + } + } + + // docs:start:quoted + fn quoted(self) -> Quoted { + // docs:end:quoted + quote { $self } + } +} + +fn mutate_array(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { + expr.as_array().map( + |exprs: [Expr]| { + let exprs = mutate_expressions(exprs, f); + new_array(exprs) + } + ) +} + +fn mutate_assign(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { + expr.as_assign().map( + |expr: (Expr, Expr)| { + let (lhs, rhs) = expr; + let lhs = lhs.mutate(f); + let rhs = rhs.mutate(f); + new_assign(lhs, rhs) + } + ) +} + +fn mutate_binary_op(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { + expr.as_binary_op().map( + |expr: (Expr, BinaryOp, Expr)| { + let (lhs, op, rhs) = expr; + let lhs = lhs.mutate(f); + let rhs = rhs.mutate(f); + new_binary_op(lhs, op, rhs) + } + ) +} + +fn mutate_block(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { + expr.as_block().map( + |exprs: [Expr]| { + let exprs = mutate_expressions(exprs, f); + new_block(exprs) + } + ) +} + +fn mutate_cast(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { + expr.as_cast().map( + |expr: (Expr, UnresolvedType)| { + let (expr, typ) = expr; + let expr = expr.mutate(f); + new_cast(expr, typ) + } + ) +} + +fn mutate_comptime(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { + expr.as_comptime().map( + |exprs: [Expr]| { + let exprs = exprs.map(|expr: Expr| expr.mutate(f)); + new_comptime(exprs) + } + ) +} + +fn mutate_function_call(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { + expr.as_function_call().map( + |expr: (Expr, [Expr])| { + let (function, arguments) = expr; + let function = function.mutate(f); + let arguments = arguments.map(|arg: Expr| arg.mutate(f)); + new_function_call(function, arguments) + } + ) +} + +fn mutate_if(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { + expr.as_if().map( + |expr: (Expr, Expr, Option)| { + let (condition, consequence, alternative) = expr; + let condition = condition.mutate(f); + let consequence = consequence.mutate(f); + let alternative = alternative.map(|alternative: Expr| alternative.mutate(f)); + new_if(condition, consequence, alternative) + } + ) +} + +fn mutate_index(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { + expr.as_index().map( + |expr: (Expr, Expr)| { + let (object, index) = expr; + let object = object.mutate(f); + let index = index.mutate(f); + new_index(object, index) + } + ) +} + +fn mutate_member_access(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { + expr.as_member_access().map( + |expr: (Expr, Quoted)| { + let (object, name) = expr; + let object = object.mutate(f); + new_member_access(object, name) + } + ) +} + +fn mutate_method_call(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { + expr.as_method_call().map( + |expr: (Expr, Quoted, [UnresolvedType], [Expr])| { + let (object, name, generics, arguments) = expr; + let object = object.mutate(f); + let arguments = arguments.map(|arg: Expr| arg.mutate(f)); + new_method_call(object, name, generics, arguments) + } + ) +} + +fn mutate_repeated_element_array(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { + expr.as_repeated_element_array().map( + |expr: (Expr, Expr)| { + let (expr, length) = expr; + let expr = expr.mutate(f); + let length = length.mutate(f); + new_repeated_element_array(expr, length) + } + ) +} + +fn mutate_repeated_element_slice(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { + expr.as_repeated_element_slice().map( + |expr: (Expr, Expr)| { + let (expr, length) = expr; + let expr = expr.mutate(f); + let length = length.mutate(f); + new_repeated_element_slice(expr, length) + } + ) +} + +fn mutate_slice(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { + expr.as_slice().map( + |exprs: [Expr]| { + let exprs = mutate_expressions(exprs, f); + new_slice(exprs) + } + ) +} + +fn mutate_tuple(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { + expr.as_tuple().map( + |exprs: [Expr]| { + let exprs = mutate_expressions(exprs, f); + new_tuple(exprs) + } + ) +} + +fn mutate_unary_op(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { + expr.as_unary_op().map( + |expr: (UnaryOp, Expr)| { + let (op, rhs) = expr; + let rhs = rhs.mutate(f); + new_unary_op(op, rhs) + } + ) +} + +fn mutate_unsafe(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { + expr.as_unsafe().map( + |exprs: [Expr]| { + let exprs = exprs.map(|expr: Expr| expr.mutate(f)); + new_unsafe(exprs) + } + ) +} + +fn mutate_expressions(exprs: [Expr], f: fn[Env](Expr) -> Option) -> [Expr] { + exprs.map(|expr: Expr| expr.mutate(f)) +} + +fn new_array(exprs: [Expr]) -> Expr { + let exprs = join_expressions(exprs, quote { , }); + quote { [$exprs]}.as_expr().unwrap() +} + +fn new_assign(lhs: Expr, rhs: Expr) -> Expr { + quote { $lhs = $rhs }.as_expr().unwrap() +} + +fn new_binary_op(lhs: Expr, op: BinaryOp, rhs: Expr) -> Expr { + let op = op.quoted(); + quote { ($lhs) $op ($rhs) }.as_expr().unwrap() +} + +fn new_block(exprs: [Expr]) -> Expr { + let exprs = join_expressions(exprs, quote { ; }); + quote { { $exprs }}.as_expr().unwrap() +} + +fn new_cast(expr: Expr, typ: UnresolvedType) -> Expr { + quote { ($expr) as $typ }.as_expr().unwrap() +} + +fn new_comptime(exprs: [Expr]) -> Expr { + let exprs = join_expressions(exprs, quote { ; }); + quote { comptime { $exprs }}.as_expr().unwrap() +} + +fn new_if(condition: Expr, consequence: Expr, alternative: Option) -> Expr { + if alternative.is_some() { + let alternative = alternative.unwrap(); + quote { if $condition { $consequence } else { $alternative }}.as_expr().unwrap() + } else { + quote { if $condition { $consequence } }.as_expr().unwrap() + } +} + +fn new_index(object: Expr, index: Expr) -> Expr { + quote { $object[$index] }.as_expr().unwrap() +} + +fn new_member_access(object: Expr, name: Quoted) -> Expr { + quote { $object.$name }.as_expr().unwrap() +} + +fn new_function_call(function: Expr, arguments: [Expr]) -> Expr { + let arguments = join_expressions(arguments, quote { , }); + + quote { $function($arguments) }.as_expr().unwrap() +} + +fn new_method_call(object: Expr, name: Quoted, generics: [UnresolvedType], arguments: [Expr]) -> Expr { + let arguments = join_expressions(arguments, quote { , }); + + if generics.len() == 0 { + quote { $object.$name($arguments) }.as_expr().unwrap() + } else { + let generics = generics.map(|generic| quote { $generic }).join(quote { , }); + quote { $object.$name::<$generics>($arguments) }.as_expr().unwrap() + } +} + +fn new_repeated_element_array(expr: Expr, length: Expr) -> Expr { + quote { [$expr; $length] }.as_expr().unwrap() +} + +fn new_repeated_element_slice(expr: Expr, length: Expr) -> Expr { + quote { &[$expr; $length] }.as_expr().unwrap() +} + +fn new_slice(exprs: [Expr]) -> Expr { + let exprs = join_expressions(exprs, quote { , }); + quote { &[$exprs]}.as_expr().unwrap() +} + +fn new_tuple(exprs: [Expr]) -> Expr { + let exprs = join_expressions(exprs, quote { , }); + quote { ($exprs) }.as_expr().unwrap() +} + +fn new_unary_op(op: UnaryOp, rhs: Expr) -> Expr { + let op = op.quoted(); + quote { $op($rhs) }.as_expr().unwrap() +} + +fn new_unsafe(exprs: [Expr]) -> Expr { + let exprs = join_expressions(exprs, quote { ; }); + quote { unsafe { $exprs }}.as_expr().unwrap() +} + +fn join_expressions(exprs: [Expr], separator: Quoted) -> Quoted { + exprs.map(|expr: Expr| expr.quoted()).join(separator) } diff --git a/noir/noir-repo/noir_stdlib/src/meta/function_def.nr b/noir/noir-repo/noir_stdlib/src/meta/function_def.nr index 2b5ddd008ea..84f9c60b304 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/function_def.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/function_def.nr @@ -1,19 +1,36 @@ impl FunctionDefinition { + #[builtin(function_def_body)] + // docs:start:body + fn body(self) -> Expr {} + // docs:end:body + #[builtin(function_def_name)] + // docs:start:name fn name(self) -> Quoted {} + // docs:end:name #[builtin(function_def_parameters)] + // docs:start:parameters fn parameters(self) -> [(Quoted, Type)] {} + // docs:end:parameters #[builtin(function_def_return_type)] + // docs:start:return_type fn return_type(self) -> Type {} + // docs:end:return_type #[builtin(function_def_set_body)] - fn set_body(self, body: Quoted) {} + // docs:start:set_body + fn set_body(self, body: Expr) {} + // docs:end:set_body #[builtin(function_def_set_parameters)] + // docs:start:set_parameters fn set_parameters(self, parameters: [(Quoted, Type)]) {} + // docs:end:set_parameters #[builtin(function_def_set_return_type)] + // docs:start:set_return_type fn set_return_type(self, return_type: Type) {} + // docs:end:set_return_type } diff --git a/noir/noir-repo/noir_stdlib/src/meta/mod.nr b/noir/noir-repo/noir_stdlib/src/meta/mod.nr index 2763685fd0d..be1b12540c9 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/mod.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/mod.nr @@ -1,37 +1,56 @@ -use crate::collections::umap::UHashMap; -use crate::hash::BuildHasherDefault; -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; +mod unresolved_type; /// Calling unquote as a macro (via `unquote!(arg)`) will unquote /// its argument. Since this is the effect `!` already does, `unquote` /// itself does not need to do anything besides return its argument. +// docs:start:unquote pub comptime fn unquote(code: Quoted) -> Quoted { + // docs:end:unquote code } /// Returns the type of any value #[builtin(type_of)] +// docs:start:type_of pub comptime fn type_of(x: T) -> Type {} +// docs:end:type_of + +// docs:start:derive_example +// These are needed for the unconstrained hashmap we're using to store derive functions +use crate::collections::umap::UHashMap; +use crate::hash::BuildHasherDefault; +use crate::hash::poseidon2::Poseidon2Hasher; +// A derive function is one that given a struct definition can +// create us a quoted trait impl from it. type DeriveFunction = fn(StructDefinition) -> Quoted; +// We'll keep a global HANDLERS map to keep track of the derive handler for each trait comptime mut global HANDLERS: UHashMap> = UHashMap::default(); +// Given a struct and a slice of traits to derive, create trait impls for each. +// This function is as simple as iterating over the slice, checking if we have a trait +// handler registered for the given trait, calling it, and appending the result. +// docs:start:derive #[varargs] pub comptime fn derive(s: StructDefinition, traits: [TraitDefinition]) -> Quoted { + // docs:end:derive 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); @@ -40,10 +59,16 @@ pub comptime fn derive(s: StructDefinition, traits: [TraitDefinition]) -> Quoted result } +// docs:end:derive_example -unconstrained pub comptime fn derive_via(t: TraitDefinition, f: DeriveFunction) { +// docs:start:derive_via +// To register a handler for a trait, just add it to our handlers map +// docs:start:derive_via_signature +pub comptime fn derive_via(t: TraitDefinition, f: DeriveFunction) { + // docs:end:derive_via_signature HANDLERS.insert(t, f); } +// docs:end:derive_via /// `make_impl` is a helper function to make a simple impl, usually while deriving a trait. /// This impl has a couple assumptions: @@ -57,6 +82,7 @@ unconstrained pub comptime fn derive_via(t: TraitDefinition, f: DeriveFunction) /// any final processing - e.g. wrapping each field in a `StructConstructor { .. }` expression. /// /// See `derive_eq` and `derive_default` for example usage. +// docs:start:make_trait_impl pub comptime fn make_trait_impl( s: StructDefinition, trait_name: Quoted, @@ -65,6 +91,7 @@ pub comptime fn make_trait_impl( join_fields_with: Quoted, body: fn[Env2](Quoted) -> Quoted ) -> Quoted { + // docs:end:make_trait_impl let typ = s.as_type(); let impl_generics = s.generics().map(|g| quote { $g }).join(quote {,}); let where_clause = s.generics().map(|name| quote { $name: $trait_name }).join(quote {,}); @@ -86,3 +113,124 @@ pub comptime fn make_trait_impl( } } } + +mod tests { + // docs:start:quote-example + comptime fn quote_one() -> Quoted { + quote { 1 } + } + + fn returning_versus_macro_insertion() { + comptime + { + // let _a: Quoted = quote { 1 }; + let _a: Quoted = quote_one(); + + // let _b: i32 = 1; + let _b: i32 = quote_one!(); + } + } + // docs:end:quote-example + + // docs:start:derive-field-count-example + trait FieldCount { + fn field_count() -> u32; + } + + #[derive_field_count] + struct Bar { x: Field, y: [Field; 2] } + + comptime fn derive_field_count(s: StructDefinition) -> Quoted { + let typ = s.as_type(); + let field_count = s.fields().len(); + quote { + impl FieldCount for $typ { + fn field_count() -> u32 { + $field_count + } + } + } + } + // docs:end:derive-field-count-example + + // docs:start:annotation-arguments-example + #[assert_field_is_type(quote { i32 }.as_type())] + struct MyStruct { my_field: i32 } + + comptime fn assert_field_is_type(s: StructDefinition, typ: Type) { + // Assert the first field in `s` has type `typ` + let fields = s.fields(); + assert_eq(fields[0].1, typ); + } + // docs:end:annotation-arguments-example + + // docs:start:annotation-varargs-example + #[assert_three_args(1, 2, 3)] + struct MyOtherStruct { my_other_field: u32 } + + #[varargs] + comptime fn assert_three_args(_s: StructDefinition, args: [Field]) { + assert_eq(args.len(), 3); + } + // docs:end:annotation-varargs-example + + // docs:start:big-derive-usage-example + // Finally, to register a handler we call the above function as an annotation + // with our handler function. + #[derive_via(derive_do_nothing)] + trait DoNothing { + fn do_nothing(self); + } + + comptime fn derive_do_nothing(s: StructDefinition) -> Quoted { + // This is simplified since we don't handle generics or where clauses! + // In a real example we'd likely also need to introduce each of + // `s.generics()` as well as a trait constraint for each generic + // to ensure they also implement the trait. + let typ = s.as_type(); + quote { + impl DoNothing for $typ { + fn do_nothing(self) { + // Traits can't tell us what to do + println("something"); + } + } + } + } + + // Since `DoNothing` is a simple trait which: + // 1. Only has one method + // 2. Does not have any generics on the trait itself + // We can use `std::meta::make_trait_impl` to help us out. + // This helper function will generate our impl for us along with any + // necessary where clauses and still provides a flexible interface + // for us to work on each field on the struct. + comptime fn derive_do_nothing_alt(s: StructDefinition) -> Quoted { + let trait_name = quote { DoNothing }; + let method_signature = quote { fn do_nothing(self) }; + + // Call `do_nothing` recursively on each field in the struct + let for_each_field = |field_name| quote { self.$field_name.do_nothing(); }; + + // Some traits like Eq want to join each field expression with something like `&`. + // We don't need that here + let join_fields_with = quote {}; + + // The body function is a spot to insert any extra setup/teardown needed. + // We'll insert our println here. Since we recur on each field, we should see + // one println for the struct itself, followed by a println for every field (recursively). + let body = |body| quote { + println("something"); + $body + }; + crate::meta::make_trait_impl( + s, + trait_name, + method_signature, + for_each_field, + join_fields_with, + body + ) + } + // docs:end:big-derive-usage-example +} diff --git a/noir/noir-repo/noir_stdlib/src/meta/module.nr b/noir/noir-repo/noir_stdlib/src/meta/module.nr index ee00f360806..6ea3ca55fb1 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/module.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/module.nr @@ -1,10 +1,16 @@ impl Module { #[builtin(module_is_contract)] +// docs:start:is_contract fn is_contract(self) -> bool {} + // docs:end:is_contract #[builtin(module_functions)] +// docs:start:functions fn functions(self) -> [FunctionDefinition] {} + // docs:end:functions #[builtin(module_name)] +// docs:start:name fn name(self) -> Quoted {} + // docs:end:name } 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 00000000000..f3060a1648b --- /dev/null +++ b/noir/noir-repo/noir_stdlib/src/meta/op.nr @@ -0,0 +1,188 @@ +struct UnaryOp { + op: Field +} + +impl UnaryOp { + // docs:start:is_minus + pub fn is_minus(self) -> bool { + // docs:end:is_minus + self.op == 0 + } + + // docs:start:is_not + pub fn is_not(self) -> bool { + // docs:end:is_not + self.op == 1 + } + + // docs:start:is_mutable_reference + pub fn is_mutable_reference(self) -> bool { + // docs:end:is_mutable_reference + self.op == 2 + } + + // docs:start:is_dereference + pub fn is_dereference(self) -> bool { + // docs:end:is_dereference + self.op == 3 + } + + // docs:start:unary_quoted + pub fn quoted(self) -> Quoted { + // docs:end:unary_quoted + if self.is_minus() { + quote { - } + } else if self.is_not() { + quote { ! } + } else if self.is_mutable_reference() { + quote { &mut } + } else if self.is_dereference() { + quote { * } + } else { + crate::mem::zeroed() + } + } +} + +struct BinaryOp { + op: Field +} + +impl BinaryOp { + // docs:start:is_add + pub fn is_add(self) -> bool { + // docs:end:is_add + self.op == 0 + } + + // docs:start:is_subtract + pub fn is_subtract(self) -> bool { + // docs:end:is_subtract + self.op == 1 + } + + // docs:start:is_multiply + pub fn is_multiply(self) -> bool { + // docs:end:is_multiply + self.op == 2 + } + + // docs:start:is_divide + pub fn is_divide(self) -> bool { + // docs:end:is_divide + self.op == 3 + } + + // docs:start:is_equal + pub fn is_equal(self) -> bool { + // docs:end:is_equal + self.op == 4 + } + + // docs:start:is_not_equal + pub fn is_not_equal(self) -> bool { + // docs:end:is_not_equal + self.op == 5 + } + + // docs:start:is_less_than + pub fn is_less_than(self) -> bool { + // docs:end:is_less_than + self.op == 6 + } + + // docs:start:is_less_than_or_equal + pub fn is_less_than_or_equal(self) -> bool { + // docs:end:is_less_than_or_equal + self.op == 7 + } + + // docs:start:is_greater_than + pub fn is_greater_than(self) -> bool { + // docs:end:is_greater_than + self.op == 8 + } + + // docs:start:is_greater_than_or_equal + pub fn is_greater_than_or_equal(self) -> bool { + // docs:end:is_greater_than_or_equal + self.op == 9 + } + + // docs:start:is_and + pub fn is_and(self) -> bool { + // docs:end:is_and + self.op == 10 + } + + // docs:start:is_or + pub fn is_or(self) -> bool { + // docs:end:is_or + self.op == 11 + } + + // docs:start:is_xor + pub fn is_xor(self) -> bool { + // docs:end:is_xor + self.op == 12 + } + + // docs:start:is_shift_right + pub fn is_shift_right(self) -> bool { + // docs:end:is_shift_right + self.op == 13 + } + + // docs:start:is_shift_left + pub fn is_shift_left(self) -> bool { + // docs:end:is_shift_left + self.op == 14 + } + + // docs:start:is_modulo + pub fn is_modulo(self) -> bool { + // docs:end:is_modulo + self.op == 15 + } + + // docs:start:binary_quoted + pub fn quoted(self) -> Quoted { + // docs:end:binary_quoted + if self.is_add() { + quote { + } + } else if self.is_subtract() { + quote { - } + } else if self.is_multiply() { + quote { * } + } else if self.is_divide() { + quote { / } + } else if self.is_equal() { + quote { == } + } else if self.is_not_equal() { + quote { != } + } else if self.is_less_than() { + quote { < } + } else if self.is_less_than_or_equal() { + quote { <= } + } else if self.is_greater_than() { + quote { > } + } else if self.is_greater_than_or_equal() { + quote { >= } + } else if self.is_and() { + quote { & } + } else if self.is_or() { + quote { | } + } else if self.is_xor() { + quote { ^ } + } else if self.is_shift_right() { + quote { >> } + } else if self.is_shift_left() { + quote { << } + } else if self.is_modulo() { + quote { % } + } else { + crate::mem::zeroed() + } + } +} + diff --git a/noir/noir-repo/noir_stdlib/src/meta/quoted.nr b/noir/noir-repo/noir_stdlib/src/meta/quoted.nr index cccc3fe0f12..9fd1e9026ba 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/quoted.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/quoted.nr @@ -3,16 +3,24 @@ use crate::option::Option; impl Quoted { #[builtin(quoted_as_expr)] +// docs:start:as_expr fn as_expr(self) -> Option {} + // docs:end:as_expr #[builtin(quoted_as_module)] +// docs:start:as_module fn as_module(self) -> Option {} + // docs:end:as_module #[builtin(quoted_as_trait_constraint)] +// docs:start:as_trait_constraint fn as_trait_constraint(self) -> TraitConstraint {} + // docs:end:as_trait_constraint #[builtin(quoted_as_type)] +// docs:start:as_type fn as_type(self) -> Type {} + // docs:end:as_type } impl Eq for Quoted { diff --git a/noir/noir-repo/noir_stdlib/src/meta/struct_def.nr b/noir/noir-repo/noir_stdlib/src/meta/struct_def.nr index 8d3f9ceb8a5..60fdeba21aa 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/struct_def.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/struct_def.nr @@ -2,14 +2,20 @@ impl StructDefinition { /// Return a syntactic version of this struct definition as a type. /// For example, `as_type(quote { type Foo { ... } })` would return `Foo` #[builtin(struct_def_as_type)] +// docs:start:as_type fn as_type(self) -> Type {} + // docs:end:as_type /// Return each generic on this struct. #[builtin(struct_def_generics)] +// docs:start:generics fn generics(self) -> [Type] {} + // docs:end:generics /// Returns (name, type) pairs of each field in this struct. Each type is as-is /// with any generic arguments unchanged. #[builtin(struct_def_fields)] +// docs:start:fields fn fields(self) -> [(Quoted, Type)] {} + // docs:end:fields } diff --git a/noir/noir-repo/noir_stdlib/src/meta/trait_def.nr b/noir/noir-repo/noir_stdlib/src/meta/trait_def.nr index ca381cb8e16..c26b571240b 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/trait_def.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/trait_def.nr @@ -3,7 +3,9 @@ use crate::cmp::Eq; impl TraitDefinition { #[builtin(trait_def_as_trait_constraint)] +// docs:start:as_trait_constraint fn as_trait_constraint(_self: Self) -> TraitConstraint {} + // docs:end:as_trait_constraint } impl Eq for TraitDefinition { 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 00000000000..15b02eac6bd --- /dev/null +++ b/noir/noir-repo/noir_stdlib/src/meta/trait_impl.nr @@ -0,0 +1,11 @@ +impl TraitImpl { + #[builtin(trait_impl_trait_generic_args)] +// docs:start:trait_generic_args + fn trait_generic_args(self) -> [Type] {} + // docs:end:trait_generic_args + + #[builtin(trait_impl_methods)] +// docs:start:methods + fn methods(self) -> [FunctionDefinition] {} + // docs:end:methods +} diff --git a/noir/noir-repo/noir_stdlib/src/meta/typ.nr b/noir/noir-repo/noir_stdlib/src/meta/typ.nr index ad669e93c0a..a3f35b28e43 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/typ.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/typ.nr @@ -3,31 +3,54 @@ use crate::option::Option; impl Type { #[builtin(type_as_array)] +// docs:start:as_array fn as_array(self) -> Option<(Type, Type)> {} + // docs:end:as_array #[builtin(type_as_constant)] +// docs:start:as_constant fn as_constant(self) -> Option {} + // docs:end:as_constant #[builtin(type_as_integer)] +// docs:start:as_integer fn as_integer(self) -> Option<(bool, u8)> {} + // docs:end:as_integer #[builtin(type_as_slice)] +// docs:start:as_slice fn as_slice(self) -> Option {} + // docs:end:as_slice #[builtin(type_as_struct)] +// docs:start:as_struct fn as_struct(self) -> Option<(StructDefinition, [Type])> {} + // docs:end:as_struct #[builtin(type_as_tuple)] +// docs:start:as_tuple fn as_tuple(self) -> Option<[Type]> {} + // docs:end:as_tuple + + #[builtin(type_get_trait_impl)] +// docs:start:get_trait_impl + fn get_trait_impl(self, constraint: TraitConstraint) -> Option {} + // docs:end:get_trait_impl #[builtin(type_implements)] +// docs:start:implements fn implements(self, constraint: TraitConstraint) -> bool {} + // docs:end:implements #[builtin(type_is_bool)] +// docs:start:is_bool fn is_bool(self) -> bool {} + // docs:end:is_bool #[builtin(type_is_field)] +// docs:start:is_field fn is_field(self) -> bool {} + // docs:end:is_field } impl Eq for Type { diff --git a/noir/noir-repo/noir_stdlib/src/meta/unresolved_type.nr b/noir/noir-repo/noir_stdlib/src/meta/unresolved_type.nr new file mode 100644 index 00000000000..2589174ed64 --- /dev/null +++ b/noir/noir-repo/noir_stdlib/src/meta/unresolved_type.nr @@ -0,0 +1,6 @@ +impl UnresolvedType { + #[builtin(unresolved_type_is_field)] + // docs:start:is_field + fn is_field(self) -> bool {} + // docs:end:is_field +} diff --git a/noir/noir-repo/noir_stdlib/src/ops/arith.nr b/noir/noir-repo/noir_stdlib/src/ops/arith.nr index df0ff978a7c..918c8e9bc28 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 a31cfee878c..015d0008e7a 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 df020e75615..5b6b36679f8 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 @@ -116,7 +116,7 @@ impl Option { } /// If self is Some, return self. Otherwise, return `default()`. - pub fn or_else(self, default: fn[Env]() -> Self) -> Self { + pub fn or_else(self, default: fn[Env]() -> Self) -> Self { if self._is_some { self } else { default() } } diff --git a/noir/noir-repo/noir_stdlib/src/sha256.nr b/noir/noir-repo/noir_stdlib/src/sha256.nr index b5dc958e9d1..c3e18b13e91 100644 --- a/noir/noir-repo/noir_stdlib/src/sha256.nr +++ b/noir/noir-repo/noir_stdlib/src/sha256.nr @@ -1,96 +1,2 @@ -// Implementation of SHA-256 mapping a byte array of variable length to -// 32 bytes. - -// Convert 64-byte array to array of 16 u32s -fn msg_u8_to_u32(msg: [u8; 64]) -> [u32; 16] { - let mut msg32: [u32; 16] = [0; 16]; - - for i in 0..16 { - let mut msg_field: Field = 0; - for j in 0..4 { - msg_field = msg_field * 256 + msg[64 - 4*(i + 1) + j] as Field; - } - msg32[15 - i] = msg_field as u32; - } - - msg32 -} - -// SHA-256 hash function -#[no_predicates] -pub fn digest(msg: [u8; N]) -> [u8; 32] { - sha256_var(msg, N as u64) -} - -fn hash_final_block(msg_block: [u8; 64], mut state: [u32; 8]) -> [u8; 32] { - let mut out_h: [u8; 32] = [0; 32]; // Digest as sequence of bytes - - // Hash final padded block - state = crate::hash::sha256_compression(msg_u8_to_u32(msg_block), state); - - // Return final hash as byte array - for j in 0..8 { - let h_bytes = (state[7 - j] as Field).to_le_bytes(4); - for k in 0..4 { - out_h[31 - 4*j - k] = h_bytes[k]; - } - } - - out_h -} - -// Variable size SHA-256 hash -pub fn sha256_var(msg: [u8; N], message_size: u64) -> [u8; 32] { - let mut msg_block: [u8; 64] = [0; 64]; - let mut h: [u32; 8] = [1779033703, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225]; // Intermediate hash, starting with the canonical initial value - let mut i: u64 = 0; // Message byte pointer - for k in 0..N { - if k as u64 < message_size { - // Populate msg_block - msg_block[i] = msg[k]; - i = i + 1; - if i == 64 { - // Enough to hash block - h = crate::hash::sha256_compression(msg_u8_to_u32(msg_block), h); - - i = 0; - } - } - } - // Pad the rest such that we have a [u32; 2] block at the end representing the length - // of the message, and a block of 1 0 ... 0 following the message (i.e. [1 << 7, 0, ..., 0]). - msg_block[i] = 1 << 7; - i = i + 1; - // If i >= 57, there aren't enough bits in the current message block to accomplish this, so - // the 1 and 0s fill up the current block, which we then compress accordingly. - if i >= 57 { - // Not enough bits (64) to store length. Fill up with zeros. - if i < 64 { - for _i in 57..64 { - if i <= 63 { - msg_block[i] = 0; - i += 1; - } - } - } - h = crate::hash::sha256_compression(msg_u8_to_u32(msg_block), h); - - i = 0; - } - - let len = 8 * message_size; - let len_bytes = (len as Field).to_le_bytes(8); - for _i in 0..64 { - // In any case, fill blocks up with zeros until the last 64 (i.e. until i = 56). - if i < 56 { - msg_block[i] = 0; - i = i + 1; - } else if i < 64 { - for j in 0..8 { - msg_block[63 - j] = len_bytes[j]; - } - i += 8; - } - } - hash_final_block(msg_block, h) -} +// This file is kept for backwards compatibility. +use crate::hash::sha256::{digest, sha256_var}; diff --git a/noir/noir-repo/noir_stdlib/src/sha512.nr b/noir/noir-repo/noir_stdlib/src/sha512.nr index be255a594af..1ddbf6e0ae1 100644 --- a/noir/noir-repo/noir_stdlib/src/sha512.nr +++ b/noir/noir-repo/noir_stdlib/src/sha512.nr @@ -1,165 +1,2 @@ -// Implementation of SHA-512 mapping a byte array of variable length to -// 64 bytes. -// Internal functions act on 64-bit unsigned integers for simplicity. -// Auxiliary mappings; names as in FIPS PUB 180-4 -fn rotr64(a: u64, b: u8) -> u64 // 64-bit right rotation -{ - // None of the bits overlap between `(a >> b)` and `(a << (64 - b))` - // Addition is then equivalent to OR, with fewer constraints. - (a >> b) + (a << (64 - b)) -} - -fn sha_ch(x: u64, y: u64, z: u64) -> u64 { - (x & y) ^ (!x & z) -} - -fn sha_maj(x: u64, y: u64, z: u64) -> u64 { - (x & y) ^ (x & z) ^ (y & z) -} - -fn sha_bigma0(x: u64) -> u64 { - rotr64(x, 28) ^ rotr64(x, 34) ^ rotr64(x, 39) -} - -fn sha_bigma1(x: u64) -> u64 { - rotr64(x, 14) ^ rotr64(x, 18) ^ rotr64(x, 41) -} - -fn sha_sigma0(x: u64) -> u64 { - rotr64(x, 1) ^ rotr64(x, 8) ^ (x >> 7) -} - -fn sha_sigma1(x: u64) -> u64 { - rotr64(x, 19) ^ rotr64(x, 61) ^ (x >> 6) -} - -fn sha_w(msg: [u64; 16]) -> [u64; 80] // Expanded message blocks -{ - let mut w: [u64;80] = [0; 80]; - - for j in 0..16 { - w[j] = msg[j]; - } - - for j in 16..80 { - w[j] = crate::wrapping_add( - crate::wrapping_add(sha_sigma1(w[j-2]), w[j-7]), - crate::wrapping_add(sha_sigma0(w[j-15]), w[j-16]), - ); - } - w -} - -// SHA-512 compression function -#[no_predicates] -fn sha_c(msg: [u64; 16], hash: [u64; 8]) -> [u64; 8] { - // noir-fmt:ignore - let K: [u64; 80] = [4794697086780616226, 8158064640168781261, 13096744586834688815, 16840607885511220156, 4131703408338449720, 6480981068601479193, 10538285296894168987, 12329834152419229976, 15566598209576043074, 1334009975649890238, 2608012711638119052, 6128411473006802146, 8268148722764581231, 9286055187155687089, 11230858885718282805, 13951009754708518548, 16472876342353939154, 17275323862435702243, 1135362057144423861, 2597628984639134821, 3308224258029322869, 5365058923640841347, 6679025012923562964, 8573033837759648693, 10970295158949994411, 12119686244451234320, 12683024718118986047, 13788192230050041572, 14330467153632333762, 15395433587784984357, 489312712824947311, 1452737877330783856, 2861767655752347644, 3322285676063803686, 5560940570517711597, 5996557281743188959, 7280758554555802590, 8532644243296465576, 9350256976987008742, 10552545826968843579, 11727347734174303076, 12113106623233404929, 14000437183269869457, 14369950271660146224, 15101387698204529176, 15463397548674623760, 17586052441742319658, 1182934255886127544, 1847814050463011016, 2177327727835720531, 2830643537854262169, 3796741975233480872, 4115178125766777443, 5681478168544905931, 6601373596472566643, 7507060721942968483, 8399075790359081724, 8693463985226723168, 9568029438360202098, 10144078919501101548, 10430055236837252648, 11840083180663258601, 13761210420658862357, 14299343276471374635, 14566680578165727644, 15097957966210449927, 16922976911328602910, 17689382322260857208, 500013540394364858, 748580250866718886, 1242879168328830382, 1977374033974150939, 2944078676154940804, 3659926193048069267, 4368137639120453308, 4836135668995329356, 5532061633213252278, 6448918945643986474, 6902733635092675308, 7801388544844847127]; // first 64 bits of fractional parts of cube roots of first 80 primes - let mut out_h: [u64; 8] = hash; - let w = sha_w(msg); - for j in 0..80 { - let out1 = crate::wrapping_add(out_h[7], sha_bigma1(out_h[4])); - let out2 = crate::wrapping_add(out1, sha_ch(out_h[4], out_h[5], out_h[6])); - let t1 = crate::wrapping_add(crate::wrapping_add(out2, K[j]), w[j]); - let t2 = crate::wrapping_add(sha_bigma0(out_h[0]), sha_maj(out_h[0], out_h[1], out_h[2])); - out_h[7] = out_h[6]; - out_h[6] = out_h[5]; - out_h[5] = out_h[4]; - out_h[4] = crate::wrapping_add(out_h[3] , t1); - out_h[3] = out_h[2]; - out_h[2] = out_h[1]; - out_h[1] = out_h[0]; - out_h[0] = crate::wrapping_add(t1, t2); - } - - out_h -} -// Convert 128-byte array to array of 16 u64s -fn msg_u8_to_u64(msg: [u8; 128]) -> [u64; 16] { - let mut msg64: [u64; 16] = [0; 16]; - - for i in 0..16 { - let mut msg_field: Field = 0; - for j in 0..8 { - msg_field = msg_field * 256 + msg[128 - 8*(i + 1) + j] as Field; - } - msg64[15 - i] = msg_field as u64; - } - - msg64 -} -// SHA-512 hash function -pub fn digest(msg: [u8; N]) -> [u8; 64] { - let mut msg_block: [u8; 128] = [0; 128]; - // noir-fmt:ignore - let mut h: [u64; 8] = [7640891576956012808, 13503953896175478587, 4354685564936845355, 11912009170470909681, 5840696475078001361, 11170449401992604703, 2270897969802886507, 6620516959819538809]; // Intermediate hash, starting with the canonical initial value - let mut c: [u64; 8] = [0; 8]; // Compression of current message block as sequence of u64 - let mut out_h: [u8; 64] = [0; 64]; // Digest as sequence of bytes - let mut i: u64 = 0; // Message byte pointer - for k in 0..msg.len() { - // Populate msg_block - msg_block[i] = msg[k]; - i = i + 1; - if i == 128 { - // Enough to hash block - c = sha_c(msg_u8_to_u64(msg_block), h); - for j in 0..8 { - h[j] = crate::wrapping_add(h[j], c[j]); - } - - i = 0; - } - } - // Pad the rest such that we have a [u64; 2] block at the end representing the length - // of the message, and a block of 1 0 ... 0 following the message (i.e. [1 << 7, 0, ..., 0]). - msg_block[i] = 1 << 7; - i += 1; - // If i >= 113, there aren't enough bits in the current message block to accomplish this, so - // the 1 and 0s fill up the current block, which we then compress accordingly. - if i >= 113 { - // Not enough bits (128) to store length. Fill up with zeros. - if i < 128 { - for _i in 113..128 { - if i <= 127 { - msg_block[i] = 0; - i += 1; - } - } - } - c = sha_c(msg_u8_to_u64(msg_block), h); - for j in 0..8 { - h[j] = crate::wrapping_add(h[j], c[j]); - } - - i = 0; - } - - let len = 8 * msg.len(); - let len_bytes = (len as Field).to_le_bytes(16); - for _i in 0..128 { - // In any case, fill blocks up with zeros until the last 128 (i.e. until i = 112). - if i < 112 { - msg_block[i] = 0; - i += 1; - } else if i < 128 { - for j in 0..16 { - msg_block[127 - j] = len_bytes[j]; - } - i += 16; // Done. - } - } - // Hash final padded block - c = sha_c(msg_u8_to_u64(msg_block), h); - for j in 0..8 { - h[j] = crate::wrapping_add(h[j], c[j]); - } - // Return final hash as byte array - for j in 0..8 { - let h_bytes = (h[7 - j] as Field).to_le_bytes(8); - for k in 0..8 { - out_h[63 - 8*j - k] = h_bytes[k]; - } - } - - out_h -} +// This file is kept for backwards compatibility. +use crate::hash::sha512::digest; diff --git a/noir/noir-repo/noir_stdlib/src/slice.nr b/noir/noir-repo/noir_stdlib/src/slice.nr index 8d3f395f080..66c69db65f0 100644 --- a/noir/noir-repo/noir_stdlib/src/slice.nr +++ b/noir/noir-repo/noir_stdlib/src/slice.nr @@ -1,6 +1,7 @@ use crate::append::Append; impl [T] { + /// Returns the length of the slice. #[builtin(array_len)] pub fn len(self) -> u32 {} @@ -37,8 +38,8 @@ impl [T] { #[builtin(slice_remove)] pub fn remove(self, index: u32) -> (Self, T) {} - // Append each element of the `other` slice to the end of `self`. - // This returns a new slice and leaves both input slices unchanged. + /// Append each element of the `other` slice to the end of `self`. + /// This returns a new slice and leaves both input slices unchanged. pub fn append(mut self, other: Self) -> Self { for elem in other { self = self.push_back(elem); @@ -49,7 +50,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 7b75cf4cae4..a6d980c7f33 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 65a449be543..91b9180f138 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 40543db2870..16902860a16 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 473ad8e8d6a..f262635e508 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 6d9645ee6eb..cf79c22683f 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 4091b2f0581..5dc05a3a5c3 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 1491badaa49..9a4f42469b5 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_failure/unspecified_generic/Nargo.toml b/noir/noir-repo/test_programs/compile_failure/unspecified_generic/Nargo.toml new file mode 100644 index 00000000000..15b97018f2d --- /dev/null +++ b/noir/noir-repo/test_programs/compile_failure/unspecified_generic/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "unspecified_generic" +type = "bin" +authors = [""] +compiler_version = ">=0.33.0" + +[dependencies] diff --git a/noir/noir-repo/test_programs/compile_failure/unspecified_generic/src/main.nr b/noir/noir-repo/test_programs/compile_failure/unspecified_generic/src/main.nr new file mode 100644 index 00000000000..f26d794d567 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_failure/unspecified_generic/src/main.nr @@ -0,0 +1,5 @@ +fn foo() {} + +fn main() { + foo(); +} 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 d4f71d38413..ad8dff6c7b9 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 @@ -7,11 +7,14 @@ fn main() { let _ = split_first([1, 2, 3]); let _ = push_multiple([1, 2, 3]); + + test_constant_folding::<10>(); + test_non_constant_folding::<10, 20>(); } 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 +24,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() { @@ -101,3 +104,31 @@ fn demo_proof() -> Equiv, (Equiv, (), W, () let p3: Equiv, (), W, ()> = add_equiv_r::(p3_sub); equiv_trans(equiv_trans(p1, p2), p3) } + +fn test_constant_folding() { + // N + C1 - C2 = N + (C1 - C2) + let _: W = W:: {}; + + // N - C1 + C2 = N - (C1 - C2) + let _: W = W:: {}; + + // N * C1 / C2 = N * (C1 / C2) + let _: W = W:: {}; + + // N / C1 * C2 = N / (C1 / C2) + let _: W = W:: {}; +} + +fn test_non_constant_folding() { + // N + M - M = N + let _: W = W:: {}; + + // N - M + M = N + let _: W = W:: {}; + + // N * M / M = N + let _: W = W:: {}; + + // N / M * M = N + let _: W = W:: {}; +} diff --git a/noir/noir-repo/test_programs/compile_success_empty/associated_types/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/associated_types/Nargo.toml new file mode 100644 index 00000000000..99b8e1b2d47 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/associated_types/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "associated_types" +type = "bin" +authors = [""] +compiler_version = ">=0.33.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/compile_success_empty/associated_types/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/associated_types/src/main.nr new file mode 100644 index 00000000000..dbc6a393ec9 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/associated_types/src/main.nr @@ -0,0 +1,28 @@ +trait Collection { + type Elem; + + fn cget(self, index: u32) -> Option; + + fn ctake(self, index: u32) -> Self::Elem { + self.cget(index).unwrap() + } +} + +impl Collection for [T; N] { + type Elem = T; + + fn cget(self, index: u32) -> Option { + if index < self.len() { + Option::some(self[index]) + } else { + Option::none() + } + } +} + +fn main() { + // Use zeroed here so that we don't help by adding another type constraint. + // We should know Elem = Field from the associated type alone. + let array = [1, 2, 3, 0, 5]; + assert_eq(array.ctake(3), std::mem::zeroed()); +} 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 ecb832468ba..f2bede99d4c 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 54f06858846..fedfe48cb0d 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 7d3f9459598..7ecc21dbd2f 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 195ed31fb08..dff5eadcb55 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 8403cb7d4a0..596f364b49f 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 8b6f7b480c7..00000000000 --- 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 19572fd15a1..705a1b2ab4e 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_function_definition/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_function_definition/src/main.nr index ce09ba86e49..06da5a1dde5 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/comptime_function_definition/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_function_definition/src/main.nr @@ -49,7 +49,7 @@ comptime fn mutate_add_one(f: FunctionDefinition) { assert_eq(f.return_type(), type_of(0)); // fn add_one(x: Field) -> Field { x + 1 } - f.set_body(quote { x + 1 }); + f.set_body(quote { x + 1 }.as_expr().unwrap()); } fn main() { diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_keccak/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/comptime_keccak/Nargo.toml new file mode 100644 index 00000000000..47c8654804d --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_keccak/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "comptime_keccak" +type = "bin" +authors = [""] +compiler_version = ">=0.33.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_keccak/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_keccak/src/main.nr new file mode 100644 index 00000000000..3cde32b6ba9 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_keccak/src/main.nr @@ -0,0 +1,31 @@ +// Tests a very simple program. +// +// The features being tested is keccak256 in brillig +fn main() { + comptime + { + let x = 0xbd; + let result = [ + 0x5a, 0x50, 0x2f, 0x9f, 0xca, 0x46, 0x7b, 0x26, 0x6d, 0x5b, 0x78, 0x33, 0x65, 0x19, 0x37, 0xe8, 0x05, 0x27, 0x0c, 0xa3, 0xf3, 0xaf, 0x1c, 0x0d, 0xd2, 0x46, 0x2d, 0xca, 0x4b, 0x3b, 0x1a, 0xbf + ]; + // 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); + + 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); + } +} + +comptime fn keccak256(data: [u8; N], msg_len: u32) -> [u8; 32] { + std::hash::keccak256(data, msg_len) +} diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_module/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_module/src/main.nr index e9c9817cfd8..8d834381fea 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/comptime_module/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_module/src/main.nr @@ -24,3 +24,18 @@ fn main() { assert_eq(bar.name(), quote { bar }); } } + +// docs:start:as_module_example +mod baz { + mod qux {} +} + +#[test] +fn as_module_test() { + comptime + { + let my_mod = quote { baz::qux }.as_module().unwrap(); + assert_eq(my_mod.name(), quote { qux }); + } +} +// docs:end:as_module_example diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_trait_impl/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/comptime_trait_impl/Nargo.toml new file mode 100644 index 00000000000..c9a6f1e150e --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_trait_impl/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "comptime_trait_impl" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_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 00000000000..87b48e7a357 --- /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 170292b0e37..6d98d1d173b 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,14 +108,20 @@ 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(); } } +// docs:start:implements_example fn function_with_where(_x: T) where T: SomeTrait { comptime { let t = quote { T }.as_type(); let some_trait_i32 = quote { SomeTrait }.as_trait_constraint(); assert(t.implements(some_trait_i32)); + + assert(t.get_trait_impl(some_trait_i32).is_none()); } } +// docs:end:implements_example diff --git a/noir/noir-repo/test_programs/compile_success_empty/embedded_curve_add_simplification/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/embedded_curve_add_simplification/Nargo.toml new file mode 100644 index 00000000000..02586f5e926 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/embedded_curve_add_simplification/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "embedded_curve_add_simplification" +type = "bin" +authors = [""] +compiler_version = ">=0.23.0" + +[dependencies] diff --git a/noir/noir-repo/test_programs/compile_success_empty/embedded_curve_add_simplification/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/embedded_curve_add_simplification/src/main.nr new file mode 100644 index 00000000000..39992a6454b --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/embedded_curve_add_simplification/src/main.nr @@ -0,0 +1,11 @@ +use std::embedded_curve_ops::{EmbeddedCurvePoint, EmbeddedCurveScalar, multi_scalar_mul}; + +fn main() { + let zero = EmbeddedCurvePoint::point_at_infinity(); + let g1 = EmbeddedCurvePoint { x: 1, y: 17631683881184975370165255887551781615748388533673675138860, is_infinite: false }; + + assert(g1 + zero == g1); + assert(g1 - g1 == zero); + assert(g1 - zero == g1); + assert(zero + zero == zero); +} diff --git a/noir/noir-repo/test_programs/compile_success_empty/inject_context_attribute/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/inject_context_attribute/Nargo.toml new file mode 100644 index 00000000000..10f9cb1f9e2 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/inject_context_attribute/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "inject_context_attribute" +type = "bin" +authors = [""] + +[dependencies] diff --git a/noir/noir-repo/test_programs/compile_success_empty/inject_context_attribute/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/inject_context_attribute/src/main.nr new file mode 100644 index 00000000000..65003ed837b --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/inject_context_attribute/src/main.nr @@ -0,0 +1,53 @@ +struct Context { + value: Field, +} + +#[inject_context] +fn foo(x: Field) { + if true { + // 20 + 1 => 21 + bar(qux(x + 1)); + } else { + assert(false); + } +} + +#[inject_context] +fn bar(x: Field) { + let expected = _context.value; + assert_eq(x, expected); +} + +#[inject_context] +fn qux(x: Field) -> Field { + // 21 * 2 => 42 + x * 2 +} + +fn inject_context(f: FunctionDefinition) { + // Add a `_context: Context` parameter to the function + let parameters = f.parameters(); + let parameters = parameters.push_front((quote { _context }, quote { Context }.as_type())); + f.set_parameters(parameters); + + // Create a new body where every function call has `_context` added to the list of arguments. + let body = f.body().mutate(mapping_function); + f.set_body(body); +} + +fn mapping_function(expr: Expr) -> Option { + expr.as_function_call().map( + |func_call: (Expr, [Expr])| { + let (name, arguments) = func_call; + let arguments = arguments.push_front(quote { _context }.as_expr().unwrap()); + let arguments = arguments.map(|arg: Expr| arg.quoted()).join(quote { , }); + quote { $name($arguments) }.as_expr().unwrap() + } + ) +} + +fn main() { + let context = Context { value: 42 }; + foo(context, 20); +} + 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 52567025e23..c5cc7880112 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/method_call_regression/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/method_call_regression/src/main.nr index 88b8dc57196..de58271cae6 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/method_call_regression/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/method_call_regression/src/main.nr @@ -1,14 +1,14 @@ fn main() { - // s: Struct - let s = Struct { b: () }; + // s: Struct + let s = Struct { a: 0, b: () }; // Regression for #3089 s.foo(); } -struct Struct { b: B } +struct Struct { a: A, b: B } // Before the fix, this candidate is searched first, binding ? to `u8` permanently. -impl Struct { +impl Struct { fn foo(self) {} } @@ -18,6 +18,6 @@ impl Struct { // With the fix, the type of `s` correctly no longer changes until a // method is actually selected. So this candidate is now valid since // `Struct` unifies with `Struct` with `? = u32`. -impl Struct { +impl Struct { fn foo(self) {} } diff --git a/noir/noir-repo/test_programs/compile_success_empty/option/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/option/src/main.nr index c5f321256b1..d135b2d88b8 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/option/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/option/src/main.nr @@ -1,6 +1,6 @@ fn main() { let ten = 10; // giving this a name, to ensure that the Option functions work with closures - let none = Option::none(); + let none: Option = Option::none(); let some = Option::some(3); assert(none.is_none()); diff --git a/noir/noir-repo/test_programs/compile_success_empty/poseidon2_simplification/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/poseidon2_simplification/Nargo.toml new file mode 100644 index 00000000000..fbf2c11b220 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/poseidon2_simplification/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "poseidon2_simplification" +type = "bin" +authors = [""] +compiler_version = ">=0.33.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/compile_success_empty/poseidon2_simplification/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/poseidon2_simplification/src/main.nr new file mode 100644 index 00000000000..423dfcc4d3b --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/poseidon2_simplification/src/main.nr @@ -0,0 +1,7 @@ +use std::hash::poseidon2; + +fn main() { + let digest = poseidon2::Poseidon2::hash([0], 1); + let expected_digest = 0x2710144414c3a5f2354f4c08d52ed655b9fe253b4bf12cb9ad3de693d9b1db11; + assert_eq(digest, expected_digest); +} diff --git a/noir/noir-repo/test_programs/compile_success_empty/regression_5823/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/regression_5823/Nargo.toml new file mode 100644 index 00000000000..a2de5c954b9 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/regression_5823/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "regression_5823" +type = "bin" +authors = [""] +compiler_version = ">=0.33.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/compile_success_empty/regression_5823/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/regression_5823/src/main.nr new file mode 100644 index 00000000000..f615564fae2 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/regression_5823/src/main.nr @@ -0,0 +1,5 @@ +fn main() { + let x = 1 as u64; + let y = 2 as u8; + assert_eq(x << y, 4); +} diff --git a/noir/noir-repo/test_programs/execution_success/verify_honk_proof/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/schnorr_simplification/Nargo.toml similarity index 62% rename from noir/noir-repo/test_programs/execution_success/verify_honk_proof/Nargo.toml rename to noir/noir-repo/test_programs/compile_success_empty/schnorr_simplification/Nargo.toml index 8fce1bf44b6..599f06ac3d2 100644 --- a/noir/noir-repo/test_programs/execution_success/verify_honk_proof/Nargo.toml +++ b/noir/noir-repo/test_programs/compile_success_empty/schnorr_simplification/Nargo.toml @@ -1,5 +1,5 @@ [package] -name = "verify_honk_proof" +name = "schnorr_simplification" type = "bin" authors = [""] diff --git a/noir/noir-repo/test_programs/compile_success_empty/schnorr_simplification/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/schnorr_simplification/src/main.nr new file mode 100644 index 00000000000..e1095cd7fe2 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/schnorr_simplification/src/main.nr @@ -0,0 +1,78 @@ +use std::embedded_curve_ops; + +// Note: If main has any unsized types, then the verifier will never be able +// to figure out the circuit instance +fn main() { + let message = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + let pub_key_x = 0x04b260954662e97f00cab9adb773a259097f7a274b83b113532bce27fa3fb96a; + let pub_key_y = 0x2fd51571db6c08666b0edfbfbc57d432068bccd0110a39b166ab243da0037197; + let signature = [ + 1, + 13, + 119, + 112, + 212, + 39, + 233, + 41, + 84, + 235, + 255, + 93, + 245, + 172, + 186, + 83, + 157, + 253, + 76, + 77, + 33, + 128, + 178, + 15, + 214, + 67, + 105, + 107, + 177, + 234, + 77, + 48, + 27, + 237, + 155, + 84, + 39, + 84, + 247, + 27, + 22, + 8, + 176, + 230, + 24, + 115, + 145, + 220, + 254, + 122, + 135, + 179, + 171, + 4, + 214, + 202, + 64, + 199, + 19, + 84, + 239, + 138, + 124, + 12 + ]; + + let valid_signature = std::schnorr::verify_signature(pub_key_x, pub_key_y, signature, message); + assert(valid_signature); +} diff --git a/noir/noir-repo/test_programs/compile_success_empty/serialize/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/serialize/Nargo.toml new file mode 100644 index 00000000000..2cf87765b8a --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/serialize/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "serialize" +type = "bin" +authors = [""] +compiler_version = ">=0.32.0" + +[dependencies] diff --git a/noir/noir-repo/test_programs/compile_success_empty/serialize/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/serialize/src/main.nr new file mode 100644 index 00000000000..79114c5b567 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/serialize/src/main.nr @@ -0,0 +1,59 @@ +trait Serialize { + let Size: u32; + + // Note that Rust disallows referencing constants here! + fn serialize(self) -> [Field; Self::Size]; +} + +impl Serialize for (A, B) where A: Serialize, B: Serialize { + // let Size = ::Size + ::Size; + let Size = AS + BS; + + fn serialize(self: Self) -> [Field; Self::Size] { + let mut array: [Field; Self::Size] = std::mem::zeroed(); + let a = self.0.serialize(); + let b = self.1.serialize(); + + for i in 0 .. a.len() { + array[i] = a[i]; + } + for i in 0 .. b.len() { + array[i + a.len()] = b[i]; + } + array + } +} + +impl Serialize for [T; N] where T: Serialize { + // let Size = ::Size * N; + let Size = TS * N; + + fn serialize(self: Self) -> [Field; Self::Size] { + let mut array: [Field; Self::Size] = std::mem::zeroed(); + let mut array_i = 0; + + for elem in self { + let elem_fields = elem.serialize(); + + for i in 0 .. elem_fields.len() { + array[array_i] = elem_fields[i]; + array_i += 1; + } + } + + array + } +} + +impl Serialize for Field { + let Size = 1; + + fn serialize(self) -> [Field; Self::Size] { + [self] + } +} + +fn main() { + let x = ((1, [2, 3, 4]), [5, 6, 7, 8]); + assert_eq(x.serialize().len(), 8); +} 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 44ccb2bd595..b35afdbc6c6 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 e93e068f432..33b84c2b702 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/bigint_from_too_many_le_bytes/Nargo.toml b/noir/noir-repo/test_programs/execution_failure/bigint_from_too_many_le_bytes/Nargo.toml new file mode 100644 index 00000000000..cbdfc2d83d9 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_failure/bigint_from_too_many_le_bytes/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "bigint_from_too_many_le_bytes" +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/bigint_from_too_many_le_bytes/src/main.nr b/noir/noir-repo/test_programs/execution_failure/bigint_from_too_many_le_bytes/src/main.nr new file mode 100644 index 00000000000..2d4587ee3d9 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_failure/bigint_from_too_many_le_bytes/src/main.nr @@ -0,0 +1,22 @@ +use std::bigint::{bn254_fq, BigInt}; + +// TODO(https://github.com/noir-lang/noir/issues/5580): decide whether this is desired behavior +// +// Fails at execution time: +// +// error: Assertion failed: 'Index out of bounds' +// ┌─ std/cmp.nr:35:34 +// │ +// 35 │ result &= self[i].eq(other[i]); +// │ -------- +// │ +// = Call stack: +// 1. /Users/michaelklein/Coding/rust/noir/test_programs/compile_failure/bigint_from_too_many_le_bytes/src/main.nr:7:12 +// 2. std/cmp.nr:35:34 +// Failed assertion +fn main() { + let bytes: [u8] = bn254_fq.push_front(0x00); + let bigint = BigInt::from_le_bytes(bytes, bn254_fq); + let result_bytes = bigint.to_le_bytes(); + assert(bytes == result_bytes.as_slice()); +} 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 da9d4ec1ac8..07256f0c398 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 bd77551e304..4dd06ceb743 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 0a5038c179b..1c1563ffe1d 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 @@ -3,6 +3,7 @@ // The features being tested is using assert on brillig that is triggered through nested ACIR calls. // We want to make sure we get a call stack from the original call in main to the failed assert. fn main(x: Field) { + assert(1 == fold_conditional_wrapper(!x as bool)); assert(1 == fold_conditional_wrapper(x as bool)); } @@ -13,7 +14,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 45ffafca4c7..fbcdba43355 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 00000000000..4123215e2b6 --- /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 00000000000..e69de29bb2d 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 00000000000..bdc645ec483 --- /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 00000000000..04d9146b881 --- /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 00000000000..1ec81884d61 --- /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 00000000000..15c2d1f1f23 --- /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 00000000000..b8fe7e955a1 --- /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 00000000000..e69de29bb2d 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 00000000000..9c447aee08f --- /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 00000000000..ccc00956e80 --- /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 00000000000..1ec81884d61 --- /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 00000000000..dbde898f7a9 --- /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 00000000000..f2acfa4d4cf --- /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 00000000000..e69de29bb2d 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 00000000000..59e57664bbe --- /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 00000000000..3c8ae8fe07a --- /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 00000000000..1ec81884d61 --- /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 00000000000..5a62e0e9843 --- /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 92f8524a771..49b7c00b6b9 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 cd7fb4772e2..9cf07841b9e 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 e81dd4a0c5f..5668a9ff388 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 9385b39e847..188ccd01131 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 5bd6ce0adb2..1a595ecfb38 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 262d3b5d86a..f54adb39963 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 e535b6001a4..c7f0757f31e 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 16fe7b29061..c6c39b61bc9 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 2743e02e920..122142a9c80 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 2809668c574..6ddfc03622a 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 5c39713f5bb..3e23da53b18 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 1b1d89f6366..8b27a9bb202 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 0a1718d0171..318da4caf72 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 a59336a877b..8ababf82319 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 78343dcd26c..45f13c79637 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 48debadb012..32b562ec50c 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 1476c447431..55b9d307905 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 78759bd84c6..d1ea635d49a 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 f41188b1f0d..c2759fe054f 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 9150e38f208..dd339208659 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 05d35469342..770660bb2a1 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 00000000000..d2a98e19742 --- /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 00000000000..e69de29bb2d 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 00000000000..488304114b9 --- /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 5a5657246a8..77ab4ea19a6 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 d34b3edb4b6..557d1e2e31f 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 0305cb06978..93465b87389 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 a87ef28bc56..c69468013b1 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 fcc01978a0a..5519fb2da64 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 5ec657b0d35..d4b74162cfb 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 1e4aa141eea..ecc7794cf9e 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 f754fb96292..75a7f8a3154 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 52ffe3e823b..966be2741d6 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 ddafc73c598..d06366cf642 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 1bcbd7d5421..9664b4d1ce6 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/nested_dyn_array_regression_5782/Nargo.toml b/noir/noir-repo/test_programs/execution_success/nested_dyn_array_regression_5782/Nargo.toml new file mode 100644 index 00000000000..b5cdd19e186 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/nested_dyn_array_regression_5782/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "nested_dyn_array_regression_5782" +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/nested_dyn_array_regression_5782/Prover.toml b/noir/noir-repo/test_programs/execution_success/nested_dyn_array_regression_5782/Prover.toml new file mode 100644 index 00000000000..de2960def06 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/nested_dyn_array_regression_5782/Prover.toml @@ -0,0 +1,2 @@ +array = [5, 10] +i = 1 diff --git a/noir/noir-repo/test_programs/execution_success/nested_dyn_array_regression_5782/src/main.nr b/noir/noir-repo/test_programs/execution_success/nested_dyn_array_regression_5782/src/main.nr new file mode 100644 index 00000000000..b6a1238a9de --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/nested_dyn_array_regression_5782/src/main.nr @@ -0,0 +1,13 @@ +fn main(mut array: [Field; 2], i: u32) { + assert_eq(array[i - 1], 5); + assert_eq(array[i], 10); + + array[i] = 2; + + let array2 = [array, array]; + + assert_eq(array2[0][0], 5); + assert_eq(array2[0][i], 2); + assert_eq(array2[i][0], 5); + assert_eq(array2[i][i], 2); +} 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 6caea017798..7b5060062da 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 65f13c5b201..d8aeb76356b 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/sha256/Prover.toml b/noir/noir-repo/test_programs/execution_success/sha256/Prover.toml index c4df1b749bb..b4bf4162370 100644 --- a/noir/noir-repo/test_programs/execution_success/sha256/Prover.toml +++ b/noir/noir-repo/test_programs/execution_success/sha256/Prover.toml @@ -34,3 +34,5 @@ result = [ 0x73, 0x2b, ] +input = [0, 0] +toggle = false \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_success/sha256/src/main.nr b/noir/noir-repo/test_programs/execution_success/sha256/src/main.nr index 4f999d349f0..29bc9ac371a 100644 --- a/noir/noir-repo/test_programs/execution_success/sha256/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/sha256/src/main.nr @@ -10,11 +10,16 @@ // Not yet here: For R1CS, it is more about manipulating arithmetic gates to get performance // This can be done in ACIR! -fn main(x: Field, result: [u8; 32]) { +fn main(x: Field, result: [u8; 32], input: [u8; 2], toggle: bool) { // 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 // docs:start:sha256_var let digest = std::hash::sha256_var([x as u8], 1); // docs:end:sha256_var assert(digest == result); + + // variable size + let size: Field = 1 + toggle as Field; + let var_sha = std::hash::sha256_var(input, size as u64); + assert(var_sha == std::hash::sha256_var(input, 1)); } diff --git a/noir/noir-repo/test_programs/execution_success/sha256_regression/Nargo.toml b/noir/noir-repo/test_programs/execution_success/sha256_regression/Nargo.toml new file mode 100644 index 00000000000..ce98d000bcb --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/sha256_regression/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "sha256_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/sha256_regression/Prover.toml b/noir/noir-repo/test_programs/execution_success/sha256_regression/Prover.toml new file mode 100644 index 00000000000..ba0aadd1b75 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/sha256_regression/Prover.toml @@ -0,0 +1,9 @@ +msg_just_over_block = [102, 114, 111, 109, 58, 114, 117, 110, 110, 105, 101, 114, 46, 108, 101, 97, 103, 117, 101, 115, 46, 48, 106, 64, 105, 99, 108, 111, 117, 100, 46, 99, 111, 109, 13, 10, 99, 111, 110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 58, 116, 101, 120, 116, 47, 112, 108, 97, 105, 110, 59, 32, 99, 104, 97, 114, 115, 101, 116] +msg_multiple_of_block = [102, 114, 111, 109, 58, 114, 117, 110, 110, 105, 101, 114, 46, 108, 101, 97, 103, 117, 101, 115, 46, 48, 106, 64, 105, 99, 108, 111, 117, 100, 46, 99, 111, 109, 13, 10, 99, 111, 110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 58, 116, 101, 120, 116, 47, 112, 108, 97, 105, 110, 59, 32, 99, 104, 97, 114, 115, 101, 116, 61, 117, 115, 45, 97, 115, 99, 105, 105, 13, 10, 109, 105, 109, 101, 45, 118, 101, 114, 115, 105, 111, 110, 58, 49, 46, 48, 32, 40, 77, 97, 99, 32, 79, 83, 32, 88, 32, 77, 97, 105, 108, 32, 49, 54, 46, 48, 32, 92, 40, 51, 55, 51, 49, 46, 53, 48, 48, 46, 50, 51, 49, 92, 41, 41, 13, 10, 115, 117, 98, 106, 101, 99, 116, 58, 72, 101, 108, 108, 111, 13, 10, 109, 101, 115, 115, 97, 103, 101, 45, 105, 100, 58, 60, 56, 70, 56, 49, 57, 68, 51, 50, 45, 66, 54, 65, 67, 45, 52, 56, 57, 68, 45, 57, 55, 55, 70, 45, 52, 51, 56, 66, 66, 67, 52, 67, 65, 66, 50, 55, 64, 109, 101, 46, 99, 111, 109, 62, 13, 10, 100, 97, 116, 101, 58, 83, 97, 116, 44, 32, 50, 54, 32, 65, 117, 103, 32, 50, 48, 50, 51, 32, 49, 50, 58, 50, 53, 58, 50, 50, 32, 43, 48, 52, 48, 48, 13, 10, 116, 111, 58, 122, 107, 101, 119, 116, 101, 115, 116, 64, 103, 109, 97, 105, 108, 46, 99, 111, 109, 13, 10, 100, 107, 105, 109, 45, 115, 105, 103, 110, 97, 116, 117, 114, 101, 58, 118, 61, 49, 59, 32, 97, 61, 114, 115, 97, 45, 115, 104, 97, 50, 53, 54, 59, 32, 99, 61, 114, 101, 108, 97, 120, 101, 100, 47, 114, 101, 108, 97, 120, 101, 100, 59, 32, 100, 61, 105, 99, 108, 111, 117, 100, 46, 99, 111, 109, 59, 32, 115, 61, 49, 97, 49, 104, 97, 105, 59, 32, 116, 61, 49, 54, 57, 51, 48, 51, 56, 51, 51, 55, 59, 32, 98, 104, 61, 55, 120, 81, 77, 68, 117, 111, 86, 86, 85, 52, 109, 48, 87, 48, 87, 82, 86, 83, 114, 86, 88, 77, 101, 71, 83, 73, 65, 83, 115, 110, 117, 99, 75, 57, 100, 74, 115, 114, 99, 43, 118, 85, 61, 59, 32, 104, 61, 102, 114, 111, 109, 58, 67, 111, 110, 116, 101, 110, 116, 45, 84, 121, 112, 101, 58, 77, 105, 109, 101, 45, 86, 101, 114, 115, 105, 111, 110, 58, 83, 117, 98, 106, 101, 99] +msg_just_under_block = [102, 114, 111, 109, 58, 114, 117, 110, 110, 105, 101, 114, 46, 108, 101, 97, 103, 117, 101, 115, 46, 48, 106, 64, 105, 99, 108, 111, 117, 100, 46, 99, 111, 109, 13, 10, 99, 111, 110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 58, 116, 101, 120, 116, 47, 112, 108, 97, 105, 110, 59] +msg_big_not_block_multiple = [102, 114, 111, 109, 58, 114, 117, 110, 110, 105, 101, 114, 46, 108, 101, 97, 103, 117, 101, 115, 46, 48, 106, 64, 105, 99, 108, 111, 117, 100, 46, 99, 111, 109, 13, 10, 99, 111, 110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 58, 116, 101, 120, 116, 47, 112, 108, 97, 105, 110, 59, 32, 99, 104, 97, 114, 115, 101, 116, 61, 117, 115, 45, 97, 115, 99, 105, 105, 13, 10, 109, 105, 109, 101, 45, 118, 101, 114, 115, 105, 111, 110, 58, 49, 46, 48, 32, 40, 77, 97, 99, 32, 79, 83, 32, 88, 32, 77, 97, 105, 108, 32, 49, 54, 46, 48, 32, 92, 40, 51, 55, 51, 49, 46, 53, 48, 48, 46, 50, 51, 49, 92, 41, 41, 13, 10, 115, 117, 98, 106, 101, 99, 116, 58, 72, 101, 108, 108, 111, 13, 10, 109, 101, 115, 115, 97, 103, 101, 45, 105, 100, 58, 60, 56, 70, 56, 49, 57, 68, 51, 50, 45, 66, 54, 65, 67, 45, 52, 56, 57, 68, 45, 57, 55, 55, 70, 45, 52, 51, 56, 66, 66, 67, 52, 67, 65, 66, 50, 55, 64, 109, 101, 46, 99, 111, 109, 62, 13, 10, 100, 97, 116, 101, 58, 83, 97, 116, 44, 32, 50, 54, 32, 65, 117, 103, 32, 50, 48, 50, 51, 32, 49, 50, 58, 50, 53, 58, 50, 50, 32, 43, 48, 52, 48, 48, 13, 10, 116, 111, 58, 122, 107, 101, 119, 116, 101, 115, 116, 64, 103, 109, 97, 105, 108, 46, 99, 111, 109, 13, 10, 100, 107, 105, 109, 45, 115, 105, 103, 110, 97, 116, 117, 114, 101, 58, 118, 61, 49, 59, 32, 97, 61, 114, 115, 97, 45, 115, 104, 97, 50, 53, 54, 59, 32, 99, 61, 114, 101, 108, 97, 120, 101, 100, 47, 114, 101, 108, 97, 120, 101, 100, 59, 32, 100, 61, 105, 99, 108, 111, 117, 100, 46, 99, 111, 109, 59, 32, 115, 61, 49, 97, 49, 104, 97, 105, 59, 32, 116, 61, 49, 54, 57, 51, 48, 51, 56, 51, 51, 55, 59, 32, 98, 104, 61, 55, 120, 81, 77, 68, 117, 111, 86, 86, 85, 52, 109, 48, 87, 48, 87, 82, 86, 83, 114, 86, 88, 77, 101, 71, 83, 73, 65, 83, 115, 110, 117, 99, 75, 57, 100, 74, 115, 114, 99, 43, 118, 85, 61, 59, 32, 104, 61, 102, 114, 111, 109, 58, 67, 111, 110, 116, 101, 110, 116, 45, 84, 121, 112, 101, 58, 77, 105, 109, 101, 45, 86, 101, 114, 115, 105, 111, 110, 58, 83, 117, 98, 106, 101, 99, 116, 58, 77, 101, 115, 115, 97, 103, 101, 45, 73, 100, 58, 68, 97, 116, 101, 58, 116, 111, 59, 32, 98, 61] +# Results matched against ethers library +result_just_over_block = [91, 122, 146, 93, 52, 109, 133, 148, 171, 61, 156, 70, 189, 238, 153, 7, 222, 184, 94, 24, 65, 114, 192, 244, 207, 199, 87, 232, 192, 224, 171, 207] +result_multiple_of_block = [116, 90, 151, 31, 78, 22, 138, 180, 211, 189, 69, 76, 227, 200, 155, 29, 59, 123, 154, 60, 47, 153, 203, 129, 157, 251, 48, 2, 79, 11, 65, 47] +result_just_under_block = [143, 140, 76, 173, 222, 123, 102, 68, 70, 149, 207, 43, 39, 61, 34, 79, 216, 252, 213, 165, 74, 16, 110, 74, 29, 64, 138, 167, 30, 1, 9, 119] +result_big = [112, 144, 73, 182, 208, 98, 9, 238, 54, 229, 61, 145, 222, 17, 72, 62, 148, 222, 186, 55, 192, 82, 220, 35, 66, 47, 193, 200, 22, 38, 26, 186] diff --git a/noir/noir-repo/test_programs/execution_success/sha256_regression/src/main.nr b/noir/noir-repo/test_programs/execution_success/sha256_regression/src/main.nr new file mode 100644 index 00000000000..855931b4300 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/sha256_regression/src/main.nr @@ -0,0 +1,26 @@ +fn main( + msg_just_over_block: [u8; 68], + result_just_over_block: pub [u8; 32], + msg_multiple_of_block: [u8; 448], + result_multiple_of_block: pub [u8; 32], + // We want to make sure we are testing a message with a size >= 57 but < 64 + msg_just_under_block: [u8; 60], + result_just_under_block: pub [u8; 32], + msg_big_not_block_multiple: [u8; 472], + result_big: pub [u8; 32] +) { + let hash = std::hash::sha256_var(msg_just_over_block, msg_just_over_block.len() as u64); + assert_eq(hash, result_just_over_block); + + let hash = std::hash::sha256_var(msg_multiple_of_block, msg_multiple_of_block.len() as u64); + assert_eq(hash, result_multiple_of_block); + + let hash = std::hash::sha256_var(msg_just_under_block, msg_just_under_block.len() as u64); + assert_eq(hash, result_just_under_block); + + let hash = std::hash::sha256_var( + msg_big_not_block_multiple, + msg_big_not_block_multiple.len() as u64 + ); + assert_eq(hash, result_big); +} diff --git a/noir/noir-repo/test_programs/execution_success/sha256_var_size_regression/Nargo.toml b/noir/noir-repo/test_programs/execution_success/sha256_var_size_regression/Nargo.toml new file mode 100644 index 00000000000..3e141ee5d5f --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/sha256_var_size_regression/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "sha256_var_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/sha256_var_size_regression/Prover.toml b/noir/noir-repo/test_programs/execution_success/sha256_var_size_regression/Prover.toml new file mode 100644 index 00000000000..df632a42858 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/sha256_var_size_regression/Prover.toml @@ -0,0 +1,3 @@ +enable = [true, false] +foo = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] +toggle = false diff --git a/noir/noir-repo/test_programs/execution_success/sha256_var_size_regression/src/main.nr b/noir/noir-repo/test_programs/execution_success/sha256_var_size_regression/src/main.nr new file mode 100644 index 00000000000..de1c2b23c5f --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/sha256_var_size_regression/src/main.nr @@ -0,0 +1,17 @@ +global NUM_HASHES = 2; + +fn main(foo: [u8; 95], toggle: bool, enable: [bool; NUM_HASHES]) { + let mut result = [[0; 32]; NUM_HASHES]; + let mut const_result = [[0; 32]; NUM_HASHES]; + let size: Field = 93 + toggle as Field * 2; + for i in 0..NUM_HASHES { + if enable[i] { + result[i] = std::sha256::sha256_var(foo, size as u64); + const_result[i] = std::sha256::sha256_var(foo, 93); + } + } + + for i in 0..NUM_HASHES { + assert_eq(result[i], const_result[i]); + } +} 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 43bd4433c69..2382f5b0f2f 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 e8b418f16da..ca41c708077 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 395ed21b6b0..f2ca6813713 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 a488f267b4c..18c7e5f4f7d 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/Prover.toml b/noir/noir-repo/test_programs/execution_success/verify_honk_proof/Prover.toml deleted file mode 100644 index f2e6bbed8ef..00000000000 --- 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 17adc68c056..00000000000 --- 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 00000000000..d3f6344dbf4 --- /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/compile_success_empty/comptime_exp/Nargo.toml b/noir/noir-repo/test_programs/noir_test_success/comptime_expr/Nargo.toml similarity index 78% rename from noir/noir-repo/test_programs/compile_success_empty/comptime_exp/Nargo.toml rename to noir/noir-repo/test_programs/noir_test_success/comptime_expr/Nargo.toml index df36e0e05b0..a40da9c5f28 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/comptime_exp/Nargo.toml +++ b/noir/noir-repo/test_programs/noir_test_success/comptime_expr/Nargo.toml @@ -1,5 +1,5 @@ [package] -name = "comptime_exp" +name = "comptime_expr" type = "bin" authors = [""] compiler_version = ">=0.31.0" diff --git a/noir/noir-repo/test_programs/noir_test_success/comptime_expr/src/main.nr b/noir/noir-repo/test_programs/noir_test_success/comptime_expr/src/main.nr new file mode 100644 index 00000000000..abc7a793fd1 --- /dev/null +++ b/noir/noir-repo/test_programs/noir_test_success/comptime_expr/src/main.nr @@ -0,0 +1,569 @@ +mod tests { + use std::meta::op::UnaryOp; + use std::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_mutate_for_array() { + comptime + { + let expr = quote { [1, 2, 4] }.as_expr().unwrap(); + let expr = expr.mutate(times_two); + let elems = expr.as_array().unwrap(); + assert_eq(elems.len(), 3); + assert_eq(elems[0].as_integer().unwrap(), (2, false)); + assert_eq(elems[1].as_integer().unwrap(), (4, false)); + assert_eq(elems[2].as_integer().unwrap(), (8, false)); + } + } + + #[test] + fn test_expr_as_assign() { + comptime + { + let expr = quote { { a = 1; } }.as_expr().unwrap(); + let exprs = expr.as_block().unwrap(); + let (_lhs, rhs) = exprs[0].as_assign().unwrap(); + assert_eq(rhs.as_integer().unwrap(), (1, false)); + } + } + + #[test] + fn test_expr_mutate_for_assign() { + comptime + { + let expr = quote { { a = 1; } }.as_expr().unwrap(); + let expr = expr.mutate(times_two); + let exprs = expr.as_block().unwrap(); + let (_lhs, rhs) = exprs[0].as_assign().unwrap(); + assert_eq(rhs.as_integer().unwrap(), (2, false)); + } + } + + #[test] + fn test_expr_as_block() { + comptime + { + let expr = quote { { 1; 4; 23 } }.as_expr().unwrap(); + let exprs = expr.as_block().unwrap(); + assert_eq(exprs.len(), 3); + assert_eq(exprs[0].as_integer().unwrap(), (1, false)); + assert_eq(exprs[1].as_integer().unwrap(), (4, false)); + assert_eq(exprs[2].as_integer().unwrap(), (23, false)); + + assert(exprs[0].has_semicolon()); + assert(exprs[1].has_semicolon()); + assert(!exprs[2].has_semicolon()); + } + } + + #[test] + fn test_expr_mutate_for_block() { + comptime + { + let expr = quote { { 1; 4; 23 } }.as_expr().unwrap(); + let expr = expr.mutate(times_two); + let exprs = expr.as_block().unwrap(); + assert_eq(exprs.len(), 3); + assert_eq(exprs[0].as_integer().unwrap(), (2, false)); + assert_eq(exprs[1].as_integer().unwrap(), (8, false)); + assert_eq(exprs[2].as_integer().unwrap(), (46, false)); + + assert(exprs[0].has_semicolon()); + assert(exprs[1].has_semicolon()); + assert(!exprs[2].has_semicolon()); + } + } + + #[test] + fn test_expr_as_method_call() { + comptime + { + let expr = quote { foo.bar::(3, 4) }.as_expr().unwrap(); + let (_object, name, generics, arguments) = expr.as_method_call().unwrap(); + + assert_eq(name, quote { bar }); + + assert_eq(generics.len(), 1); + assert(generics[0].is_field()); + + assert_eq(arguments.len(), 2); + assert_eq(arguments[0].as_integer().unwrap(), (3, false)); + assert_eq(arguments[1].as_integer().unwrap(), (4, false)); + } + } + + #[test] + fn test_expr_mutate_for_method_call() { + comptime + { + let expr = quote { foo.bar(3, 4) }.as_expr().unwrap(); + let expr = expr.mutate(times_two); + + let (_object, name, generics, arguments) = expr.as_method_call().unwrap(); + + assert_eq(name, quote { bar }); + + assert_eq(generics.len(), 0); + + assert_eq(arguments.len(), 2); + assert_eq(arguments[0].as_integer().unwrap(), (6, false)); + assert_eq(arguments[1].as_integer().unwrap(), (8, 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_mutate_for_integer() { + comptime + { + let expr = quote { 1 }.as_expr().unwrap(); + let expr = expr.mutate(times_two); + + assert_eq((2, false), 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_less_than()); + assert(get_binary_op(quote { x <= y }).is_less_than_or_equal()); + assert(get_binary_op(quote { x > y }).is_greater_than()); + assert(get_binary_op(quote { x >= y }).is_greater_than_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_mutate_for_binary_op() { + comptime + { + let expr = quote { 3 + 4 }.as_expr().unwrap(); + let expr = expr.mutate(times_two); + + let (lhs, op, rhs) = expr.as_binary_op().unwrap(); + assert_eq(lhs.as_integer().unwrap(), (6, false)); + assert(op.is_add()); + assert_eq(rhs.as_integer().unwrap(), (8, false)); + } + } + + #[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_cast() { + comptime + { + let expr = quote { 1 as Field }.as_expr().unwrap(); + let (expr, typ) = expr.as_cast().unwrap(); + assert_eq(expr.as_integer().unwrap(), (1, false)); + assert(typ.is_field()); + } + } + + #[test] + fn test_expr_mutate_for_cast() { + comptime + { + let expr = quote { 1 as Field }.as_expr().unwrap(); + let expr = expr.mutate(times_two); + let (expr, typ) = expr.as_cast().unwrap(); + assert_eq(expr.as_integer().unwrap(), (2, false)); + assert(typ.is_field()); + } + } + + #[test] + fn test_expr_as_comptime() { + comptime + { + let expr = quote { comptime { 1; 4; 23 } }.as_expr().unwrap(); + let exprs = expr.as_comptime().unwrap(); + assert_eq(exprs.len(), 3); + } + } + + #[test] + fn test_expr_mutate_for_comptime() { + comptime + { + let expr = quote { comptime { 1; 4; 23 } }.as_expr().unwrap(); + let expr = expr.mutate(times_two); + let exprs = expr.as_comptime().unwrap(); + assert_eq(exprs.len(), 3); + assert_eq(exprs[0].as_integer().unwrap(), (2, false)); + } + } + + #[test] + fn test_expr_as_comptime_as_statement() { + comptime + { + let expr = quote { { comptime { 1; 4; 23 } } }.as_expr().unwrap(); + let exprs = expr.as_block().unwrap(); + assert_eq(exprs.len(), 1); + + let exprs = exprs[0].as_comptime().unwrap(); + assert_eq(exprs.len(), 3); + } + } + + // This test can't only be around the comptime block since that will cause + // `nargo fmt` to remove the comptime keyword. + // docs:start:as_expr_example + #[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)); + } + } + // docs:end:as_expr_example + + #[test] + fn test_expr_mutate_for_function_call() { + comptime + { + let expr = quote { foo(42) }.as_expr().unwrap(); + let expr = expr.mutate(times_two); + let (_function, args) = expr.as_function_call().unwrap(); + assert_eq(args.len(), 1); + assert_eq(args[0].as_integer().unwrap(), (84, 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_mutate_for_if() { + comptime + { + let expr = quote { if 1 { 2 } }.as_expr().unwrap(); + let expr = expr.mutate(times_two); + let (condition, consequence, alternative) = expr.as_if().unwrap(); + assert_eq(condition.as_integer().unwrap(), (2, false)); + let consequence = consequence.as_block().unwrap()[0].as_block().unwrap()[0]; + assert_eq(consequence.as_integer().unwrap(), (4, false)); + assert(alternative.is_none()); + + let expr = quote { if 1 { 2 } else { 3 } }.as_expr().unwrap(); + let expr = expr.mutate(times_two); + let (condition, consequence, alternative) = expr.as_if().unwrap(); + assert_eq(condition.as_integer().unwrap(), (2, false)); + let consequence = consequence.as_block().unwrap()[0].as_block().unwrap()[0]; + assert_eq(consequence.as_integer().unwrap(), (4, false)); + let alternative = alternative.unwrap().as_block().unwrap()[0].as_block().unwrap()[0]; + assert_eq(alternative.as_integer().unwrap(), (6, false)); + } + } + + #[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_mutate_for_index() { + comptime + { + let expr = quote { 1[2] }.as_expr().unwrap(); + let expr = expr.mutate(times_two); + let (object, index) = expr.as_index().unwrap(); + assert_eq(object.as_integer().unwrap(), (2, false)); + assert_eq(index.as_integer().unwrap(), (4, false)); + } + } + + #[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_mutate_for_member_access() { + comptime + { + let expr = quote { 1.bar }.as_expr().unwrap(); + let expr = expr.mutate(times_two); + let (expr, name) = expr.as_member_access().unwrap(); + assert_eq(name, quote { bar }); + assert_eq(expr.as_integer().unwrap(), (2, false)); + } + } + + #[test] + fn test_expr_as_member_access_with_an_lvalue() { + comptime + { + let expr = quote { { foo.bar = 1; } }.as_expr().unwrap(); + let exprs = expr.as_block().unwrap(); + let (lhs, _rhs) = exprs[0].as_assign().unwrap(); + let (_, name) = lhs.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_mutate_for_repeated_element_array() { + comptime + { + let expr = quote { [1; 3] }.as_expr().unwrap(); + let expr = expr.mutate(times_two); + let (expr, length) = expr.as_repeated_element_array().unwrap(); + assert_eq(expr.as_integer().unwrap(), (2, false)); + assert_eq(length.as_integer().unwrap(), (6, 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_mutate_for_repeated_element_slice() { + comptime + { + let expr = quote { &[1; 3] }.as_expr().unwrap(); + let expr = expr.mutate(times_two); + let (expr, length) = expr.as_repeated_element_slice().unwrap(); + assert_eq(expr.as_integer().unwrap(), (2, false)); + assert_eq(length.as_integer().unwrap(), (6, 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_mutate_for_slice() { + comptime + { + let expr = quote { &[1, 3, 5] }.as_expr().unwrap(); + let expr = expr.mutate(times_two); + let elems = expr.as_slice().unwrap(); + assert_eq(elems.len(), 3); + assert_eq(elems[0].as_integer().unwrap(), (2, false)); + assert_eq(elems[1].as_integer().unwrap(), (6, false)); + assert_eq(elems[2].as_integer().unwrap(), (10, 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_mutate_for_tuple() { + comptime + { + let expr = quote { (1, 2) }.as_expr().unwrap(); + let expr = expr.mutate(times_two); + let tuple_exprs = expr.as_tuple().unwrap(); + assert_eq(tuple_exprs.len(), 2); + assert_eq(tuple_exprs[0].as_integer().unwrap(), (2, false)); + assert_eq(tuple_exprs[1].as_integer().unwrap(), (4, false)); + } + } + + #[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_expr_mutate_for_unary_op() { + comptime + { + let expr = quote { -(1) }.as_expr().unwrap(); + let expr = expr.mutate(times_two); + let (op, expr) = expr.as_unary_op().unwrap(); + assert(op.is_minus()); + assert_eq(expr.as_integer().unwrap(), (2, false)); + } + } + + #[test] + fn test_expr_as_unsafe() { + comptime + { + let expr = quote { unsafe { 1; 4; 23 } }.as_expr().unwrap(); + let exprs = expr.as_unsafe().unwrap(); + assert_eq(exprs.len(), 3); + } + } + + #[test] + fn test_expr_mutate_for_unsafe() { + comptime + { + let expr = quote { unsafe { 1; 4; 23 } }.as_expr().unwrap(); + let expr = expr.mutate(times_two); + let exprs = expr.as_unsafe().unwrap(); + assert_eq(exprs.len(), 3); + assert_eq(exprs[0].as_integer().unwrap(), (2, false)); + } + } + + #[test] + fn test_expr_is_break() { + comptime + { + let expr = quote { { break; } }.as_expr().unwrap(); + let exprs = expr.as_block().unwrap(); + assert(exprs[0].is_break()); + } + } + + #[test] + fn test_expr_is_continue() { + comptime + { + let expr = quote { { continue; } }.as_expr().unwrap(); + let exprs = expr.as_block().unwrap(); + assert(exprs[0].is_continue()); + } + } + + #[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 + } + + comptime fn times_two(expr: Expr) -> Option { + expr.as_integer().and_then( + |integer: (Field, bool)| { + let (value, _) = integer; + let value = value * 2; + quote { $value }.as_expr() + } + ) + } +} + +fn main() {} diff --git a/noir/noir-repo/test_programs/noir_test_success/embedded_curve_ops/src/main.nr b/noir/noir-repo/test_programs/noir_test_success/embedded_curve_ops/src/main.nr index 0c2c333fa62..760df58c34a 100644 --- a/noir/noir-repo/test_programs/noir_test_success/embedded_curve_ops/src/main.nr +++ b/noir/noir-repo/test_programs/noir_test_success/embedded_curve_ops/src/main.nr @@ -4,7 +4,6 @@ use std::embedded_curve_ops::{EmbeddedCurvePoint, EmbeddedCurveScalar, multi_sca fn test_infinite_point() { let zero = EmbeddedCurvePoint::point_at_infinity(); - let zero = EmbeddedCurvePoint { x: 0, y: 0, is_infinite: true }; let g1 = EmbeddedCurvePoint { x: 1, y: 17631683881184975370165255887551781615748388533673675138860, is_infinite: false }; let g2 = g1 + g1; 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 4d3dd8d030b..1b427043e91 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 a47ab37eb31..fb90e3d96c5 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 ad40941ff51..6a8b4bd61fb 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/test_programs/rebuild.sh b/noir/noir-repo/test_programs/rebuild.sh index a70f69d531d..79adfc2d0df 100755 --- a/noir/noir-repo/test_programs/rebuild.sh +++ b/noir/noir-repo/test_programs/rebuild.sh @@ -60,6 +60,6 @@ for dir in $current_dir/benchmarks/*; do dirs_to_process+=("$dir") done -parallel -j0 process_dir {} "$current_dir" ::: ${dirs_to_process[@]} +parallel -j7 process_dir {} "$current_dir" ::: ${dirs_to_process[@]} echo "Rebuild Succeeded!" diff --git a/noir/noir-repo/tooling/acvm_cli/Cargo.toml b/noir/noir-repo/tooling/acvm_cli/Cargo.toml index a592f2d65f3..06dd5e676bd 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 540d6d11bc0..b9b83d86836 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 6ec1aff8325..0d348cf172d 100644 --- a/noir/noir-repo/tooling/debugger/src/context.rs +++ b/noir/noir-repo/tooling/debugger/src/context.rs @@ -442,16 +442,15 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { self.debug_artifact.debug_symbols[debug_location.circuit_id as usize] .opcode_location(&debug_location.opcode_location) .unwrap_or_else(|| { - if let Some(brillig_function_id) = debug_location.brillig_function_id { + if let (Some(brillig_function_id), Some(brillig_location)) = ( + debug_location.brillig_function_id, + debug_location.opcode_location.to_brillig_location(), + ) { let brillig_locations = self.debug_artifact.debug_symbols [debug_location.circuit_id as usize] .brillig_locations .get(&brillig_function_id); - brillig_locations - .unwrap() - .get(&debug_location.opcode_location) - .cloned() - .unwrap_or_default() + brillig_locations.unwrap().get(&brillig_location).cloned().unwrap_or_default() } else { vec![] } @@ -566,7 +565,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 +577,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 +595,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() { @@ -660,8 +659,9 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { fn get_current_acir_index(&self) -> Option { self.get_current_debug_location().map(|debug_location| { match debug_location.opcode_location { - OpcodeLocation::Acir(acir_index) => acir_index, - OpcodeLocation::Brillig { acir_index, .. } => acir_index, + OpcodeLocation::Acir(acir_index) | OpcodeLocation::Brillig { acir_index, .. } => { + acir_index + } } }) } @@ -893,8 +893,19 @@ fn build_source_to_opcode_debug_mappings( ); for (brillig_function_id, brillig_locations_map) in &debug_symbols.brillig_locations { + let brillig_locations_map = brillig_locations_map + .iter() + .map(|(key, val)| { + ( + // TODO: this is a temporary placeholder until the debugger is updated to handle the new brillig debug locations. + OpcodeLocation::Brillig { acir_index: 0, brillig_index: key.0 }, + val.clone(), + ) + }) + .collect(); + add_opcode_locations_map( - brillig_locations_map, + &brillig_locations_map, &mut result, &simple_files, circuit_id, @@ -946,7 +957,7 @@ mod tests { brillig::IntegerBitSize, circuit::{ brillig::{BrilligFunctionId, BrilligInputs, BrilligOutputs}, - opcodes::{BlockId, BlockType}, + opcodes::{AcirFunctionId, BlockId, BlockType}, }, native_types::Expression, AcirField, @@ -1210,7 +1221,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 106d8abead1..31701991844 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 03c6c9105ba..353a6ade904 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 ca34d7686fd..c2885543844 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,18 +384,21 @@ 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 - // but not saved to disk yet, and we want to make sure all LSP features work well - // according to these unsaved buffers, not what's saved on disk. + // Source code for files we cached override those that are read from disk. + let mut overrides: HashMap<&Path, &str> = HashMap::new(); for (path, source) in &state.input_files { let path = path.strip_prefix("file://").unwrap(); - file_manager.add_file_with_source_canonical_path(Path::new(path), source.clone()); + overrides.insert(Path::new(path), source); } - nargo::insert_all_files_for_workspace_into_file_manager(workspace, file_manager); + nargo::insert_all_files_for_workspace_into_file_manager_with_overrides( + workspace, + file_manager, + &overrides, + ); } #[test] @@ -410,7 +414,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 3a60de15c4a..8b030c9e0aa 100644 --- a/noir/noir-repo/tooling/lsp/src/notifications/mod.rs +++ b/noir/noir-repo/tooling/lsp/src/notifications/mod.rs @@ -2,7 +2,7 @@ use std::ops::ControlFlow; use crate::insert_all_files_for_workspace_into_file_manager; use async_lsp::{ErrorCode, LanguageClient, ResponseError}; -use noirc_driver::{check_crate, file_manager_with_stdlib}; +use noirc_driver::{check_crate, file_manager_with_stdlib, CheckOptions}; use noirc_errors::{DiagnosticKind, FileDiagnostic}; use crate::types::{ @@ -132,7 +132,11 @@ pub(crate) fn process_workspace_for_noir_document( let (mut context, crate_id) = crate::prepare_package(&workspace_file_manager, &parsed_files, package); - let file_diagnostics = match check_crate(&mut context, crate_id, &Default::default()) { + let options = CheckOptions { + error_on_unused_imports: package.error_on_unused_imports(), + ..Default::default() + }; + let file_diagnostics = match check_crate(&mut context, crate_id, &options) { Ok(((), warnings)) => warnings, Err(errors_and_warnings) => errors_and_warnings, }; @@ -223,7 +227,7 @@ mod notification_tests { use super::*; use lsp_types::{ - InlayHintLabel, InlayHintParams, Position, TextDocumentContentChangeEvent, + InlayHintLabel, InlayHintParams, Position, Range, TextDocumentContentChangeEvent, TextDocumentIdentifier, TextDocumentItem, VersionedTextDocumentIdentifier, WorkDoneProgressParams, }; @@ -270,9 +274,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 48616c0f52d..f339ed19622 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/completion.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/completion.rs @@ -4,74 +4,51 @@ 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, CallExpression, ConstructorExpression, Expression, + ExpressionKind, ForLoopStatement, Ident, IfExpression, ItemVisibility, LValue, Lambda, + LetStatement, MemberAccessExpression, MethodCallExpression, 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}, + resolution::{ + import::can_reference_module_id, + 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 +71,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 +89,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 +110,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 +143,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 +156,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 +168,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 +210,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 +256,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 +269,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 +287,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 +298,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 { @@ -342,6 +342,71 @@ impl<'a> NodeFinder<'a> { } } + pub(super) fn find_in_call_expression(&mut self, call_expression: &CallExpression) { + // Check if it's this case: + // + // foo::b>|<(...) + // + // In this case we want to suggest items in foo but if they are functions + // we don't want to insert arguments, because they are already there (even if + // they could be wrong) just because inserting them would lead to broken code. + if let ExpressionKind::Variable(path) = &call_expression.func.kind { + if self.includes_span(path.span) { + self.find_in_path_impl(path, RequestedItems::AnyItems, true); + return; + } + } + + // Check if it's this case: + // + // foo.>|<(...) + // + // "foo." is actually broken, but it's parsed as "foo", so this is seen + // as "foo(...)" but if we are at a dot right after "foo" it means it's + // the above case and we want to suggest methods of foo's type. + let after_dot = self.byte == Some(b'.'); + if after_dot && call_expression.func.span.end() as usize == self.byte_index - 1 { + let location = Location::new(call_expression.func.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, FunctionCompletionKind::Name); + return; + } + } + + 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, + ) { + // Check if it's this case: + // + // foo.b>|<(...) + // + // In this case we want to suggest items in foo but if they are functions + // we don't want to insert arguments, because they are already there (even if + // they could be wrong) just because inserting them would lead to broken code. + if self.includes_span(method_call_expression.method_name.span()) { + let location = Location::new(method_call_expression.object.span, self.file); + if let Some(typ) = self.interner.type_at_location(location) { + let typ = typ.follow_bindings(); + let prefix = method_call_expression.method_name.to_string(); + let offset = + self.byte_index - method_call_expression.method_name.span().start() as usize; + let prefix = prefix[0..offset].to_string(); + self.complete_type_fields_and_methods(&typ, &prefix, FunctionCompletionKind::Name); + return; + } + } + + self.find_in_expression(&method_call_expression.object); + self.find_in_expressions(&method_call_expression.arguments); + } + fn find_in_block_expression(&mut self, block_expression: &BlockExpression) { let old_local_variables = self.local_variables.clone(); for statement in &block_expression.statements { @@ -357,22 +422,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 +446,13 @@ 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::Interned(_) + | StatementKind::Error => (), } } @@ -403,22 +469,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,79 +482,79 @@ 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, + FunctionCompletionKind::NameAndParameters, + ); + } + } + } LValue::MemberAccess { object, field_name: _, span: _ } => self.find_in_lvalue(object), LValue::Index { array, index, span: _ } => { self.find_in_lvalue(array); self.find_in_expression(index); } LValue::Dereference(lvalue, _) => self.find_in_lvalue(lvalue), - } - } - - fn find_in_for_range(&mut self, for_range: &ForRange) { - match for_range { - ForRange::Range(start, end) => { - self.find_in_expression(start); - self.find_in_expression(end); - } - ForRange::Array(expression) => self.find_in_expression(expression), - } - } - - fn find_in_expressions(&mut self, expressions: &[Expression]) { - for expression in expressions { - self.find_in_expression(expression); + LValue::Interned(..) => (), } } 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 +563,82 @@ 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::Interned(_) + | 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, + FunctionCompletionKind::NameAndParameters, + ); + } } } - 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 +646,24 @@ 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, + FunctionCompletionKind::NameAndParameters, + ); + 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,84 +699,114 @@ 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) { - return; - } + if !self.includes_span(unresolved_type.span) { + return; } 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); + self.find_in_type_args(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); + self.find_in_type_args(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::Interned(_) + | UnresolvedTypeData::Error => (), } } fn find_in_path(&mut self, path: &Path, requested_items: RequestedItems) { - // Only offer completions if we are right at the end of the path - if self.byte_index != path.span.end() as usize { + self.find_in_path_impl(path, requested_items, false); + } + + fn find_in_path_impl( + &mut self, + path: &Path, + requested_items: RequestedItems, + mut in_the_middle: bool, + ) { + if !self.includes_span(path.span) { return; } let after_colons = self.byte == Some(b':'); - let mut idents: Vec = - path.segments.iter().map(|segment| segment.ident.clone()).collect(); + let mut idents: Vec = Vec::new(); + + // Find in which ident we are in, and in which part of it + // (it could be that we are completting in the middle of an ident) + for segment in &path.segments { + let ident = &segment.ident; + + // Check if we are at the end of the ident + if self.byte_index == ident.span().end() as usize { + idents.push(ident.clone()); + break; + } + + // Check if we are in the middle of an ident + if self.includes_span(ident.span()) { + // If so, take the substring and push that as the list of idents + // we'll do autocompletion for + let offset = self.byte_index - ident.span().start() as usize; + let substring = ident.0.contents[0..offset].to_string(); + let ident = Ident::new( + substring, + Span::from(ident.span().start()..ident.span().start() + offset as u32), + ); + idents.push(ident); + in_the_middle = true; + break; + } + + idents.push(ident.clone()); + + // Stop if the cursor is right after this ident and '::' + if after_colons && self.byte_index == ident.span().end() as usize + 2 { + break; + } + } + + if idents.len() < path.segments.len() { + in_the_middle = true; + } + let prefix; let at_root; @@ -706,20 +818,73 @@ impl<'a> NodeFinder<'a> { at_root = idents.is_empty(); } - let is_single_segment = !after_colons && idents.is_empty() && path.kind == PathKind::Plain; + let prefix = prefix.to_case(Case::Snake); - let module_id = - if idents.is_empty() { Some(self.module_id) } else { self.resolve_module(idents) }; - let Some(module_id) = module_id else { - return; - }; + let is_single_segment = !after_colons && idents.is_empty() && path.kind == PathKind::Plain; + let module_id; - let module_completion_kind = if after_colons { + let module_completion_kind = if after_colons || !idents.is_empty() { ModuleCompletionKind::DirectChildren } else { ModuleCompletionKind::AllVisibleItems }; - let function_completion_kind = FunctionCompleteKind::NameAndParameters; + + // When completing in the middle of an ident, we don't want to complete + // with function parameters because there might already be function parameters, + // and in the middle of a path it leads to code that won't compile + let function_completion_kind = if in_the_middle { + FunctionCompletionKind::Name + } else { + FunctionCompletionKind::NameAndParameters + }; + + 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, + function_completion_kind, + ); + 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, + function_completion_kind, + ); + return; + } + ModuleDefId::TraitId(trait_id) => { + let trait_ = self.interner.get_trait(trait_id); + self.complete_trait_methods( + trait_, + &prefix, + FunctionKind::Any, + function_completion_kind, + ); + return; + } + ModuleDefId::GlobalId(_) => return, + } + } self.complete_in_module( module_id, @@ -735,7 +900,7 @@ impl<'a> NodeFinder<'a> { match requested_items { RequestedItems::AnyItems => { self.local_variables_completion(&prefix); - self.builtin_functions_completion(&prefix); + self.builtin_functions_completion(&prefix, function_completion_kind); self.builtin_values_completion(&prefix); } RequestedItems::OnlyTypes => { @@ -743,6 +908,7 @@ impl<'a> NodeFinder<'a> { self.type_parameters_completion(&prefix); } } + self.complete_auto_imports(&prefix, requested_items, function_completion_kind); } } @@ -827,7 +993,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 +1001,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 +1015,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 +1079,126 @@ impl<'a> NodeFinder<'a> { }; } + fn complete_type_fields_and_methods( + &mut self, + typ: &Type, + prefix: &str, + function_completion_kind: FunctionCompletionKind, + ) { + 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, + function_completion_kind, + ); + } + Type::Alias(type_alias, _) => { + let type_alias = type_alias.borrow(); + return self.complete_type_fields_and_methods( + &type_alias.typ, + prefix, + function_completion_kind, + ); + } + 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), + function_completion_kind, + ); + } + + fn complete_type_methods( + &mut self, + typ: &Type, + prefix: &str, + function_kind: FunctionKind, + function_completion_kind: FunctionCompletionKind, + ) { + 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, + function_completion_kind, + 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, + function_completion_kind: FunctionCompletionKind, + ) { + for (name, func_id) in &trait_.method_ids { + if name_matches(name, prefix) { + if let Some(completion_item) = + self.function_completion_item(*func_id, function_completion_kind, 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 +1207,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 +1237,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(), @@ -961,25 +1249,33 @@ impl<'a> NodeFinder<'a> { if name_matches(name, prefix) { let per_ns = module_data.find_name(ident); - if let Some((module_def_id, _, _)) = per_ns.types { - if let Some(completion_item) = self.module_def_id_completion_item( - module_def_id, - name.clone(), - function_completion_kind, - requested_items, - ) { - self.completion_items.push(completion_item); + if let Some((module_def_id, visibility, _)) = per_ns.types { + if is_visible(module_id, self.module_id, visibility, self.def_maps) { + if let Some(completion_item) = self.module_def_id_completion_item( + 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); + } } } - if let Some((module_def_id, _, _)) = per_ns.values { - if let Some(completion_item) = self.module_def_id_completion_item( - module_def_id, - name.clone(), - function_completion_kind, - requested_items, - ) { - self.completion_items.push(completion_item); + if let Some((module_def_id, visibility, _)) = per_ns.values { + if is_visible(module_id, self.module_id, visibility, self.def_maps) { + if let Some(completion_item) = self.module_def_id_completion_item( + 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 +1307,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 +1316,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, - } - } - - fn builtin_functions_completion(&mut self, prefix: &str) { - for keyword in Keyword::iter() { - if let Some(func) = keyword_builtin_function(&keyword) { - if name_matches(func.name, prefix) { - self.completion_items.push(snippet_completion_item( - format!("{}(…)", func.name), - CompletionItemKind::FUNCTION, - format!("{}({})", func.name, func.parameters), - Some(func.description.to_string()), - )); - } - } - } - } - - fn builtin_values_completion(&mut self, prefix: &str) { - for keyword in ["false", "true"] { - if name_matches(keyword, prefix) { - self.completion_items.push(simple_completion_item( - keyword, - CompletionItemKind::KEYWORD, - Some("bool".to_string()), - )); - } + if let Ok(path_resolution) = path_resolver.resolve(self.def_maps, path, &mut None) { + return Some(path_resolution.module_def_id); } - } - 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()), - )); - } + // 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); } } - 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 +1342,91 @@ 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) -} - -fn crate_completion_item(name: impl Into) -> CompletionItem { - simple_completion_item(name, CompletionItemKind::MODULE, None) -} - -fn simple_completion_item( - label: impl Into, - kind: CompletionItemKind, - description: Option, -) -> CompletionItem { - CompletionItem { - label: label.into(), - label_details: Some(CompletionItemLabelDetails { detail: None, description }), - kind: Some(kind), - detail: None, - documentation: None, - deprecated: None, - preselect: None, - sort_text: None, - filter_text: None, - insert_text: None, - insert_text_format: None, - insert_text_mode: None, - text_edit: None, - additional_text_edits: None, - command: None, - commit_characters: None, - data: None, - tags: None, - } -} - -fn snippet_completion_item( - label: impl Into, - kind: CompletionItemKind, - insert_text: impl Into, - description: Option, -) -> CompletionItem { - CompletionItem { - label: label.into(), - label_details: Some(CompletionItemLabelDetails { detail: None, description }), - kind: Some(kind), - insert_text_format: Some(InsertTextFormat::SNIPPET), - insert_text: Some(insert_text.into()), - detail: None, - documentation: None, - deprecated: None, - preselect: None, - sort_text: None, - filter_text: None, - insert_text_mode: None, - text_edit: None, - additional_text_edits: None, - command: None, - commit_characters: None, - data: None, - tags: None, - } -} - -#[cfg(test)] -mod completion_tests { - use crate::{notifications::on_did_open_text_document, test_utils}; - - use super::*; - use lsp_types::{ - DidOpenTextDocumentParams, PartialResultParams, Position, TextDocumentIdentifier, - TextDocumentItem, TextDocumentPositionParams, WorkDoneProgressParams, - }; - use tokio::test; - - async fn assert_completion(src: &str, expected: Vec) { - let (mut state, noir_text_document) = test_utils::init_lsp_server("document_symbol").await; - - let (line, column) = src - .lines() - .enumerate() - .filter_map(|(line_index, line)| { - line.find(">|<").map(|char_index| (line_index, char_index)) - }) - .next() - .expect("Expected to find one >|< in the source code"); - - let src = src.replace(">|<", ""); - - on_did_open_text_document( - &mut state, - DidOpenTextDocumentParams { - text_document: TextDocumentItem { - uri: noir_text_document.clone(), - language_id: "noir".to_string(), - version: 0, - text: src.to_string(), - }, - }, - ); - - // Get inlay hints. These should now be relative to the changed text, - // not the saved file's text. - let response = on_completion_request( - &mut state, - CompletionParams { - text_document_position: TextDocumentPositionParams { - text_document: TextDocumentIdentifier { uri: noir_text_document }, - position: Position { line: line as u32, character: column as u32 }, - }, - work_done_progress_params: WorkDoneProgressParams { work_done_token: None }, - partial_result_params: PartialResultParams { partial_result_token: None }, - context: None, - }, - ) - .await - .expect("Could not execute on_completion_request") - .unwrap(); - - let CompletionResponse::Array(items) = response else { - panic!("Expected response to be CompletionResponse::Array"); - }; - - let mut items = items.clone(); - items.sort_by_key(|item| item.label.clone()); - - let mut expected = expected.clone(); - expected.sort_by_key(|item| item.label.clone()); - - if items != expected { - println!( - "Items: {:?}", - items.iter().map(|item| item.label.clone()).collect::>() - ); - println!( - "Expected: {:?}", - expected.iter().map(|item| item.label.clone()).collect::>() - ); - } - - assert_eq!(items, expected); - } - - #[test] - async fn test_use_first_segment() { - let src = r#" - mod foo {} - mod foobar {} - use f>|< - "#; - - assert_completion( - src, - vec![module_completion_item("foo"), module_completion_item("foobar")], - ) - .await; - } - - #[test] - async fn test_use_second_segment() { - let src = r#" - mod foo { - mod bar {} - mod baz {} - } - use foo::>|< - "#; - - assert_completion(src, vec![module_completion_item("bar"), module_completion_item("baz")]) - .await; - } - - #[test] - async fn test_use_second_segment_after_typing() { - let src = r#" - mod foo { - mod bar {} - mod brave {} - } - use foo::ba>|< - "#; - - assert_completion(src, vec![module_completion_item("bar")]).await; - } - - #[test] - async fn test_use_struct() { - let src = r#" - mod foo { - struct Foo {} - } - use foo::>|< - "#; - - assert_completion( - src, - vec![simple_completion_item( - "Foo", - CompletionItemKind::STRUCT, - Some("Foo".to_string()), - )], - ) - .await; - } - - #[test] - async fn test_use_function() { - let src = r#" - mod foo { - fn bar(x: i32) -> u64 { 0 } - } - use foo::>|< - "#; - - assert_completion( - src, - vec![simple_completion_item( - "bar", - CompletionItemKind::FUNCTION, - Some("fn(i32) -> u64".to_string()), - )], - ) - .await; - } - - #[test] - async fn test_use_after_crate_and_letter() { - // Prove that "std" shows up - let src = r#" - use s>|< - "#; - assert_completion(src, vec![crate_completion_item("std")]).await; - - // "std" doesn't show up anymore because of the "crate::" prefix - let src = r#" - mod something {} - use crate::s>|< - "#; - assert_completion(src, vec![module_completion_item("something")]).await; - } - - #[test] - async fn test_use_suggests_hardcoded_crate() { - let src = r#" - use c>|< - "#; - - assert_completion( - src, - vec![simple_completion_item("crate::", CompletionItemKind::KEYWORD, None)], - ) - .await; - } - - #[test] - async fn test_use_in_tree_after_letter() { - let src = r#" - mod foo { - mod bar {} - } - use foo::{b>|<} - "#; - - assert_completion(src, vec![module_completion_item("bar")]).await; - } + let name = name.to_case(Case::Snake); + let name_parts: Vec<&str> = name.split('_').collect(); - #[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 {} + let mut last_index: i32 = -1; + for prefix_part in prefix.split('_') { + // Look past parts we already matched + let offset = if last_index >= 0 { last_index as usize + 1 } else { 0 }; - 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; - } + if let Some(mut name_part_index) = + name_parts.iter().skip(offset).position(|name_part| name_part.starts_with(prefix_part)) + { + // Need to adjust the index if we skipped some segments + name_part_index += offset; - #[test] - async fn test_suggest_regarding_block_scope() { - let src = r#" - fn main() { - let good = 1; - { - let great = 2; - g>|< - } + if last_index >= name_part_index as i32 { + return false; } - "#; - 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; + last_index = name_part_index as i32; + } else { + return false; + } } - #[test] - async fn test_suggest_impl_type_parameter() { - let src = r#" - struct Foo {} + true +} - impl Foo { - fn foo() { - let x: TypeP>|< - } - } - "#; - assert_completion( - src, - vec![simple_completion_item("TypeParam", CompletionItemKind::TYPE_PARAMETER, None)], - ) - .await; +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, } +} - #[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; - } +fn is_visible( + target_module_id: ModuleId, + current_module_id: ModuleId, + visibility: ItemVisibility, + def_maps: &BTreeMap, +) -> bool { + can_reference_module_id( + def_maps, + current_module_id.krate, + current_module_id.local_id, + target_module_id, + visibility, + ) +} - #[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; - } +#[cfg(test)] +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("bar_baz", "bar_b")); + + 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 00000000000..162e1616832 --- /dev/null +++ b/noir/noir-repo/tooling/lsp/src/requests/completion/auto_import.rs @@ -0,0 +1,205 @@ +use std::collections::BTreeMap; + +use lsp_types::{Position, Range, TextEdit}; +use noirc_frontend::{ + ast::ItemVisibility, + graph::CrateId, + hir::def_map::{CrateDefMap, 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, + function_completion_kind: FunctionCompletionKind, + ) { + let current_module_parent_id = get_parent_module_id(self.def_maps, self.module_id); + + 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(), + function_completion_kind, + 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, + current_module_parent_id, + self.interner, + ); + } 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, + current_module_parent_id, + self.interner, + ); + } + + 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 { + let reference_id = module_def_id_to_reference_id(module_def_id); + interner.reference_module(reference_id).copied() +} + +fn get_parent_module_id( + def_maps: &BTreeMap, + module_id: ModuleId, +) -> Option { + let crate_def_map = &def_maps[&module_id.krate]; + let module_data = &crate_def_map.modules()[module_id.local_id.0]; + module_data.parent.map(|parent| ModuleId { krate: module_id.krate, local_id: parent }) +} + +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), + } +} + +/// Returns the path to reach an item inside `target_module_id` from inside `current_module_id`. +/// Returns a relative path if possible. +fn module_id_path( + target_module_id: ModuleId, + current_module_id: &ModuleId, + current_module_parent_id: Option, + interner: &NodeInterner, +) -> String { + if Some(target_module_id) == current_module_parent_id { + return "super".to_string(); + } + + let mut segments: Vec<&str> = Vec::new(); + let mut is_relative = false; + + if let Some(module_attributes) = interner.try_module_attributes(&target_module_id) { + segments.push(&module_attributes.name); + + let mut current_attributes = module_attributes; + loop { + let Some(parent_local_id) = current_attributes.parent else { + break; + }; + + let parent_module_id = + &ModuleId { krate: target_module_id.krate, local_id: parent_local_id }; + + if current_module_id == parent_module_id { + is_relative = true; + break; + } + + if current_module_parent_id == Some(*parent_module_id) { + segments.push("super"); + is_relative = true; + break; + } + + let Some(parent_attributes) = interner.try_module_attributes(parent_module_id) else { + break; + }; + + segments.push(&parent_attributes.name); + current_attributes = parent_attributes; + } + } + + if !is_relative { + // We don't record module attriubtes for the root module, + // so we handle that case separately + if let CrateId::Root(_) = target_module_id.krate { + segments.push("crate"); + } + } + + segments.reverse(); + segments.join("::") +} 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 070be109f13..430e04aedfd 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,83 @@ +use lsp_types::CompletionItemKind; use noirc_frontend::token::Keyword; +use strum::IntoEnumIterator; + +use super::{ + completion_items::{simple_completion_item, snippet_completion_item}, + kinds::FunctionCompletionKind, + name_matches, NodeFinder, +}; + +impl<'a> NodeFinder<'a> { + pub(super) fn builtin_functions_completion( + &mut self, + prefix: &str, + function_completion_kind: FunctionCompletionKind, + ) { + for keyword in Keyword::iter() { + if let Some(func) = keyword_builtin_function(&keyword) { + if name_matches(func.name, prefix) { + let description = Some(func.description.to_string()); + let label; + let insert_text; + match function_completion_kind { + FunctionCompletionKind::Name => { + label = func.name.to_string(); + insert_text = func.name.to_string(); + } + FunctionCompletionKind::NameAndParameters => { + label = format!("{}(…)", func.name); + insert_text = format!("{}({})", func.name, func.parameters); + } + } + + self.completion_items.push(snippet_completion_item( + label, + CompletionItemKind::FUNCTION, + insert_text, + description, + )); + } + } + } + } + + 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"] @@ -11,10 +90,13 @@ pub(super) fn keyword_builtin_type(keyword: &Keyword) -> Option<&'static str> { Keyword::Expr => Some("Expr"), Keyword::Field => Some("Field"), Keyword::FunctionDefinition => Some("FunctionDefinition"), + Keyword::Quoted => Some("Quoted"), Keyword::StructDefinition => Some("StructDefinition"), Keyword::TraitConstraint => Some("TraitConstraint"), Keyword::TraitDefinition => Some("TraitDefinition"), + Keyword::TraitImpl => Some("TraitImpl"), Keyword::TypeType => Some("Type"), + Keyword::UnresolvedType => Some("UnresolvedType"), Keyword::As | Keyword::Assert @@ -41,7 +123,6 @@ pub(super) fn keyword_builtin_type(keyword: &Keyword) -> Option<&'static str> { | Keyword::Module | Keyword::Mut | Keyword::Pub - | Keyword::Quoted | Keyword::Return | Keyword::ReturnData | Keyword::String @@ -52,6 +133,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 +198,13 @@ 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 00000000000..d4190f5241c --- /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 00000000000..e01fcfc8c56 --- /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 00000000000..9bdc603660f --- /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 00000000000..59e007c5a70 --- /dev/null +++ b/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs @@ -0,0 +1,1794 @@ +#[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 { + pub fn bar(x: i32) -> u64 { 0 } + fn bar_is_private(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 super::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 super::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; + } + + #[test] + async fn test_auto_import_with_super() { + let src = r#" + pub fn bar_baz() {} + + mod tests { + fn foo() { + bar_b>|< + } + } + "#; + let items = get_completions(src).await; + assert_eq!(items.len(), 1); + + let item = &items[0]; + assert_eq!(item.label, "bar_baz()"); + assert_eq!( + item.label_details, + Some(CompletionItemLabelDetails { + detail: Some("(use super::bar_baz)".to_string()), + description: Some("fn()".to_string()) + }) + ); + } + + #[test] + async fn test_auto_import_from_std() { + let src = r#" + fn main() { + compute_merkle_roo>|< + } + "#; + let items = get_completions(src).await; + assert_eq!(items.len(), 1); + + let item = &items[0]; + assert_eq!(item.label, "compute_merkle_root(…)"); + assert_eq!( + item.label_details.as_ref().unwrap().detail, + Some("(use std::merkle::compute_merkle_root)".to_string()), + ); + } + + #[test] + async fn test_completes_after_first_letter_of_path() { + let src = r#" + fn main() { + h>||||||<() + } + "#; + assert_completion_excluding_auto_import( + src, + vec![simple_completion_item( + "bar", + CompletionItemKind::FUNCTION, + Some("fn(self)".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 00000000000..e3bd8ffadf7 --- /dev/null +++ b/noir/noir-repo/tooling/lsp/src/requests/completion/traversal.rs @@ -0,0 +1,126 @@ +/// This file includes the completion logic that's just about +/// traversing the AST without any additional logic. +use noirc_frontend::{ + ast::{ + ArrayLiteral, AssignStatement, CastExpression, ConstrainStatement, Expression, ForRange, + FunctionReturnType, GenericTypeArgs, IndexExpression, InfixExpression, Literal, 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_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_type_args(&mut self, generics: &GenericTypeArgs) { + self.find_in_unresolved_types(&generics.ordered_args); + for (_name, typ) in &generics.named_args { + self.find_in_unresolved_type(typ); + } + } + + 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/document_symbol.rs b/noir/noir-repo/tooling/lsp/src/requests/document_symbol.rs index 20fdfb6ece7..bda246f7c98 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/document_symbol.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/document_symbol.rs @@ -140,11 +140,7 @@ impl<'a> DocumentSymbolCollector<'a> { let mut children = Vec::new(); for (field_name, typ) in &noir_struct.fields { - let span = if let Some(typ) = typ.span { - Span::from(field_name.span().start()..typ.end()) - } else { - field_name.span() - }; + let span = Span::from(field_name.span().start()..typ.span.end()); let Some(field_location) = self.to_lsp_location(span) else { continue; @@ -238,9 +234,7 @@ impl<'a> DocumentSymbolCollector<'a> { span = Span::from(span.start()..return_type_span.end()); } FunctionReturnType::Ty(typ) => { - if let Some(type_span) = typ.span { - span = Span::from(span.start()..type_span.end()); - } + span = Span::from(span.start()..typ.span.end()); } } @@ -290,9 +284,7 @@ impl<'a> DocumentSymbolCollector<'a> { let mut span = name.span(); // If there's a type span, extend the span to include it - if let Some(type_span) = typ.span { - span = Span::from(span.start()..type_span.end()); - } + span = Span::from(span.start()..typ.span.end()); // If there's a default value, extend the span to include it if let Some(default_value) = default_value { @@ -326,8 +318,8 @@ impl<'a> DocumentSymbolCollector<'a> { return; }; - let span = if let Some(type_span) = typ.and_then(|typ| typ.span) { - Span::from(name.span().start()..type_span.end()) + let span = if let Some(typ) = typ { + Span::from(name.span().start()..typ.span.end()) } else { name.span() }; @@ -367,10 +359,20 @@ impl<'a> DocumentSymbolCollector<'a> { trait_name.push_str(&noir_trait_impl.trait_name.to_string()); if !noir_trait_impl.trait_generics.is_empty() { trait_name.push('<'); - for (index, generic) in noir_trait_impl.trait_generics.iter().enumerate() { + for (index, generic) in noir_trait_impl.trait_generics.ordered_args.iter().enumerate() { + if index > 0 { + trait_name.push_str(", "); + } + trait_name.push_str(&generic.to_string()); + } + for (index, (name, generic)) in + noir_trait_impl.trait_generics.named_args.iter().enumerate() + { if index > 0 { trait_name.push_str(", "); } + trait_name.push_str(&name.0.contents); + trait_name.push_str(" = "); trait_name.push_str(&generic.to_string()); } trait_name.push('>'); diff --git a/noir/noir-repo/tooling/lsp/src/requests/goto_definition.rs b/noir/noir-repo/tooling/lsp/src/requests/goto_definition.rs index 5e655766024..6538e64dc90 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/goto_definition.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/goto_definition.rs @@ -235,4 +235,18 @@ mod goto_definition_tests { ) .await; } + + #[test] + async fn goto_crate() { + expect_goto( + "go_to_definition", + Position { line: 29, character: 6 }, // "dependency" in "use dependency::something" + "dependency/src/lib.nr", + Range { + start: Position { line: 0, character: 0 }, + end: Position { line: 0, character: 0 }, + }, + ) + .await; + } } diff --git a/noir/noir-repo/tooling/lsp/src/requests/hover.rs b/noir/noir-repo/tooling/lsp/src/requests/hover.rs index b6fdc6f7842..ae1e57f5ecc 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/hover.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/hover.rs @@ -58,6 +58,13 @@ fn format_reference(reference: ReferenceId, args: &ProcessRequestCallbackArgs) - } } fn format_module(id: ModuleId, args: &ProcessRequestCallbackArgs) -> Option { + let crate_root = args.def_maps[&id.krate].root(); + + if id.local_id == crate_root { + let dep = args.dependencies.iter().find(|dep| dep.crate_id == id.krate); + return dep.map(|dep| format!(" crate {}", dep.name)); + } + // Note: it's not clear why `try_module_attributes` might return None here, but it happens. // This is a workaround to avoid panicking in that case (which brings the LSP server down). // Cases where this happens are related to generated code, so once that stops happening @@ -65,12 +72,14 @@ fn format_module(id: ModuleId, args: &ProcessRequestCallbackArgs) -> Option bool { - let crate_id = module.krate; - let crate_name = match crate_id { - CrateId::Root(_) => Some(args.crate_name.clone()), - CrateId::Crate(_) => args - .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(" "); - string.push_str(&crate_name); - true - } else { - false - }; - - let Some(module_attributes) = args.interner.try_module_attributes(module) else { - return wrote_crate; - }; - - if wrote_crate { - string.push_str("::"); - } else { - string.push_str(" "); + let mut segments: Vec<&str> = Vec::new(); + + if let Some(module_attributes) = args.interner.try_module_attributes(module) { + segments.push(&module_attributes.name); + + let mut current_attributes = module_attributes; + loop { + let Some(parent_local_id) = current_attributes.parent else { + break; + }; + + let Some(parent_attributes) = args.interner.try_module_attributes(&ModuleId { + krate: module.krate, + local_id: parent_local_id, + }) else { + break; + }; + + segments.push(&parent_attributes.name); + current_attributes = parent_attributes; + } } - let mut segments = Vec::new(); - let mut current_attributes = module_attributes; - while let Some(parent_attributes) = args.interner.try_module_attributes(&ModuleId { - krate: module.krate, - local_id: current_attributes.parent, - }) { - segments.push(&parent_attributes.name); - current_attributes = parent_attributes; - } + // We don't record module attriubtes for the root module, + // so we handle that case separately + if let CrateId::Root(_) = module.krate { + segments.push(&args.crate_name); + }; - for segment in segments.iter().rev() { - string.push_str(segment); - string.push_str("::"); + if segments.is_empty() { + return false; } - string.push_str(&module_attributes.name); + segments.reverse(); + string.push_str(" "); + string.push_str(&segments.join("::")); true } @@ -424,14 +423,17 @@ impl<'a> TypeLinksGatherer<'a> { Type::TraitAsType(trait_id, _, generics) => { let some_trait = self.interner.get_trait(*trait_id); self.gather_trait_links(some_trait); - for generic in generics { + for generic in &generics.ordered { self.gather_type_links(generic); } + for named_type in &generics.named { + self.gather_type_links(&named_type.typ); + } } 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); } @@ -812,4 +814,15 @@ mod hover_tests { ) .await; } + + #[test] + async fn hover_on_crate_segment() { + assert_hover( + "workspace", + "two/src/lib.nr", + Position { line: 0, character: 5 }, + " crate one", + ) + .await; + } } 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 8c3d8a05652..2f6e7dede5d 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/inlay_hint.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/inlay_hint.rs @@ -202,9 +202,10 @@ impl<'a> InlayHintCollector<'a> { } StatementKind::Comptime(statement) => self.collect_in_statement(statement), StatementKind::Semi(expression) => self.collect_in_expression(expression), - StatementKind::Break => (), - StatementKind::Continue => (), - StatementKind::Error => (), + StatementKind::Break + | StatementKind::Continue + | StatementKind::Interned(_) + | StatementKind::Error => (), } } @@ -293,6 +294,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); } @@ -300,6 +304,7 @@ impl<'a> InlayHintCollector<'a> { | ExpressionKind::Variable(..) | ExpressionKind::Quote(..) | ExpressionKind::Resolved(..) + | ExpressionKind::Interned(..) | ExpressionKind::Error => (), } } @@ -589,7 +594,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); @@ -685,7 +694,9 @@ fn get_expression_name(expression: &Expression) -> Option { | ExpressionKind::Unquote(..) | ExpressionKind::Comptime(..) | ExpressionKind::Resolved(..) + | ExpressionKind::Interned(..) | ExpressionKind::Literal(..) + | ExpressionKind::Unsafe(..) | ExpressionKind::Error => None, } } @@ -714,9 +725,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 e138f839600..e88c7f11465 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 00000000000..8aa74fe9900 --- /dev/null +++ b/noir/noir-repo/tooling/lsp/src/requests/signature_help.rs @@ -0,0 +1,292 @@ +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) { + typ = typ.follow_bindings(); + 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 00000000000..c48ee159084 --- /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 00000000000..6a31a22d63a --- /dev/null +++ b/noir/noir-repo/tooling/lsp/src/requests/signature_help/traversal.rs @@ -0,0 +1,309 @@ +/// 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::Interned(_) + | 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), + LValue::Interned(..) => (), + } + } + + 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::Interned(_) + | 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 5afda0d292a..3ac1f35e18e 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/lsp/test_programs/go_to_definition/Nargo.toml b/noir/noir-repo/tooling/lsp/test_programs/go_to_definition/Nargo.toml index c894a050c40..96fc9cab39a 100644 --- a/noir/noir-repo/tooling/lsp/test_programs/go_to_definition/Nargo.toml +++ b/noir/noir-repo/tooling/lsp/test_programs/go_to_definition/Nargo.toml @@ -4,3 +4,4 @@ type = "bin" authors = [""] [dependencies] +dependency = { path = "dependency" } \ No newline at end of file diff --git a/noir/noir-repo/tooling/lsp/test_programs/go_to_definition/dependency/Nargo.toml b/noir/noir-repo/tooling/lsp/test_programs/go_to_definition/dependency/Nargo.toml new file mode 100644 index 00000000000..2e471678a44 --- /dev/null +++ b/noir/noir-repo/tooling/lsp/test_programs/go_to_definition/dependency/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "dependency" +type = "lib" +authors = [""] + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/tooling/lsp/test_programs/go_to_definition/dependency/src/lib.nr b/noir/noir-repo/tooling/lsp/test_programs/go_to_definition/dependency/src/lib.nr new file mode 100644 index 00000000000..6833f391999 --- /dev/null +++ b/noir/noir-repo/tooling/lsp/test_programs/go_to_definition/dependency/src/lib.nr @@ -0,0 +1 @@ +pub fn something() {} diff --git a/noir/noir-repo/tooling/lsp/test_programs/go_to_definition/src/main.nr b/noir/noir-repo/tooling/lsp/test_programs/go_to_definition/src/main.nr index 9223fdc0bd3..4550324c182 100644 --- a/noir/noir-repo/tooling/lsp/test_programs/go_to_definition/src/main.nr +++ b/noir/noir-repo/tooling/lsp/test_programs/go_to_definition/src/main.nr @@ -27,3 +27,4 @@ trait Trait { } +use dependency::something; diff --git a/noir/noir-repo/tooling/nargo/Cargo.toml b/noir/noir-repo/tooling/nargo/Cargo.toml index 56e88dacf2d..046eca88099 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/errors.rs b/noir/noir-repo/tooling/nargo/src/errors.rs index f9668653d0b..b5571ff7758 100644 --- a/noir/noir-repo/tooling/nargo/src/errors.rs +++ b/noir/noir-repo/tooling/nargo/src/errors.rs @@ -158,13 +158,16 @@ fn extract_locations_from_error( debug[resolved_location.acir_function_index] .opcode_location(&resolved_location.opcode_location) .unwrap_or_else(|| { - if let Some(brillig_function_id) = brillig_function_id { + if let (Some(brillig_function_id), Some(brillig_location)) = ( + brillig_function_id, + &resolved_location.opcode_location.to_brillig_location(), + ) { let brillig_locations = debug[resolved_location.acir_function_index] .brillig_locations .get(&brillig_function_id); brillig_locations .unwrap() - .get(&resolved_location.opcode_location) + .get(brillig_location) .cloned() .unwrap_or_default() } else { diff --git a/noir/noir-repo/tooling/nargo/src/lib.rs b/noir/noir-repo/tooling/nargo/src/lib.rs index c0c7602d14d..0118e83d2a3 100644 --- a/noir/noir-repo/tooling/nargo/src/lib.rs +++ b/noir/noir-repo/tooling/nargo/src/lib.rs @@ -13,7 +13,7 @@ pub mod ops; pub mod package; pub mod workspace; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashMap}; use fm::{FileManager, FILE_EXTENSION}; use noirc_driver::{add_dep, prepare_crate, prepare_dependency}; @@ -45,9 +45,21 @@ pub fn prepare_dependencies( pub fn insert_all_files_for_workspace_into_file_manager( workspace: &workspace::Workspace, file_manager: &mut FileManager, +) { + insert_all_files_for_workspace_into_file_manager_with_overrides( + workspace, + file_manager, + &HashMap::new(), + ); +} + +pub fn insert_all_files_for_workspace_into_file_manager_with_overrides( + workspace: &workspace::Workspace, + file_manager: &mut FileManager, + overrides: &HashMap<&std::path::Path, &str>, ) { for package in workspace.clone().into_iter() { - insert_all_files_for_package_into_file_manager(package, file_manager); + insert_all_files_for_package_into_file_manager(package, file_manager, overrides); } } // We will pre-populate the file manager with all the files in the package @@ -59,6 +71,7 @@ pub fn insert_all_files_for_workspace_into_file_manager( fn insert_all_files_for_package_into_file_manager( package: &Package, file_manager: &mut FileManager, + overrides: &HashMap<&std::path::Path, &str>, ) { // Start off at the entry path and read all files in the parent directory. let entry_path_parent = package @@ -70,8 +83,12 @@ fn insert_all_files_for_package_into_file_manager( let paths = get_all_noir_source_in_dir(entry_path_parent) .expect("could not get all paths in the package"); for path in paths { - let source = std::fs::read_to_string(path.as_path()) - .unwrap_or_else(|_| panic!("could not read file {:?} into string", path)); + let source = if let Some(src) = overrides.get(path.as_path()) { + src.to_string() + } else { + std::fs::read_to_string(path.as_path()) + .unwrap_or_else(|_| panic!("could not read file {:?} into string", path)) + }; file_manager.add_file_with_source(path.as_path(), source); } @@ -87,7 +104,11 @@ fn insert_all_files_for_packages_dependencies_into_file_manager( for (_, dep) in package.dependencies.iter() { match dep { Dependency::Local { package } | Dependency::Remote { package } => { - insert_all_files_for_package_into_file_manager(package, file_manager); + insert_all_files_for_package_into_file_manager( + package, + file_manager, + &HashMap::new(), + ); insert_all_files_for_packages_dependencies_into_file_manager(package, file_manager); } } diff --git a/noir/noir-repo/tooling/nargo/src/ops/execute.rs b/noir/noir-repo/tooling/nargo/src/ops/execute.rs index c9cc60d03d9..5a43b1c2f9c 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,10 +163,14 @@ 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); } } } + // Clear the call stack if we have succeeded in executing the circuit. + // This needs to be done or else all successful ACIR call stacks will also be + // included in a failure case. + self.call_stack.clear(); Ok(acvm.finalize()) } diff --git a/noir/noir-repo/tooling/nargo/src/package.rs b/noir/noir-repo/tooling/nargo/src/package.rs index f55ca5550a3..cde616a9e32 100644 --- a/noir/noir-repo/tooling/nargo/src/package.rs +++ b/noir/noir-repo/tooling/nargo/src/package.rs @@ -73,4 +73,11 @@ impl Package { pub fn is_library(&self) -> bool { self.package_type == PackageType::Library } + + pub fn error_on_unused_imports(&self) -> bool { + match self.package_type { + PackageType::Library => false, + PackageType::Binary | PackageType::Contract => true, + } + } } diff --git a/noir/noir-repo/tooling/nargo_cli/Cargo.toml b/noir/noir-repo/tooling/nargo_cli/Cargo.toml index dabb779ae8d..4e3f3a57e87 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` @@ -28,7 +31,7 @@ nargo_fmt.workspace = true nargo_toml.workspace = true noir_lsp.workspace = true noir_debugger.workspace = true -noirc_driver.workspace = true +noirc_driver = { workspace = true, features = ["bn254"] } noirc_frontend = { workspace = true, features = ["bn254"] } noirc_abi.workspace = true noirc_errors.workspace = true diff --git a/noir/noir-repo/tooling/nargo_cli/build.rs b/noir/noir-repo/tooling/nargo_cli/build.rs index 3f8cd055569..4dcfccdf085 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/check_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/check_cmd.rs index 5239070b4d2..1130a82fdfc 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/check_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/check_cmd.rs @@ -10,7 +10,7 @@ use nargo::{ use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; use noirc_abi::{AbiParameter, AbiType, MAIN_RETURN_NAME}; use noirc_driver::{ - check_crate, compute_function_abi, file_manager_with_stdlib, CompileOptions, + check_crate, compute_function_abi, file_manager_with_stdlib, CheckOptions, CompileOptions, NOIR_ARTIFACT_VERSION_STRING, }; use noirc_frontend::{ @@ -81,7 +81,9 @@ fn check_package( allow_overwrite: bool, ) -> Result { let (mut context, crate_id) = prepare_package(file_manager, parsed_files, package); - check_crate_and_report_errors(&mut context, crate_id, compile_options)?; + let error_on_unused_imports = package.error_on_unused_imports(); + let check_options = CheckOptions::new(compile_options, error_on_unused_imports); + check_crate_and_report_errors(&mut context, crate_id, &check_options)?; if package.is_library() || package.is_contract() { // Libraries do not have ABIs while contracts have many, so we cannot generate a `Prover.toml` file. @@ -150,9 +152,10 @@ fn create_input_toml_template( pub(crate) fn check_crate_and_report_errors( context: &mut Context, crate_id: CrateId, - options: &CompileOptions, + check_options: &CheckOptions, ) -> Result<(), CompileError> { - let result = check_crate(context, crate_id, options); + let options = &check_options.compile_options; + let result = check_crate(context, crate_id, check_options); report_errors(result, &context.file_manager, options.deny_warnings, options.silence_warnings) } diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/export_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/export_cmd.rs index 19add7f30dc..5721dd33e27 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/export_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/export_cmd.rs @@ -12,7 +12,7 @@ use nargo::workspace::Workspace; use nargo::{insert_all_files_for_workspace_into_file_manager, parse_all}; use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; use noirc_driver::{ - compile_no_check, file_manager_with_stdlib, CompileOptions, CompiledProgram, + compile_no_check, file_manager_with_stdlib, CheckOptions, CompileOptions, CompiledProgram, NOIR_ARTIFACT_VERSION_STRING, }; @@ -83,7 +83,9 @@ fn compile_exported_functions( compile_options: &CompileOptions, ) -> Result<(), CliError> { let (mut context, crate_id) = prepare_package(file_manager, parsed_files, package); - check_crate_and_report_errors(&mut context, crate_id, compile_options)?; + let error_on_unused_imports = package.error_on_unused_imports(); + let check_options = CheckOptions::new(compile_options, error_on_unused_imports); + check_crate_and_report_errors(&mut context, crate_id, &check_options)?; let exported_functions = context.get_all_exported_functions_in_crate(&crate_id); diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/fs/inputs.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/fs/inputs.rs index dee9a00507c..4a7a81431bb 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/fs/inputs.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/fs/inputs.rs @@ -25,7 +25,13 @@ pub(crate) fn read_inputs_from_file>( let file_path = path.as_ref().join(file_name).with_extension(format.ext()); if !file_path.exists() { - return Err(FilesystemError::MissingTomlFile(file_name.to_owned(), file_path)); + if abi.parameters.is_empty() { + // Reading a return value from the `Prover.toml` is optional, + // so if the ABI has no parameters we can skip reading the file if it doesn't exist. + return Ok((BTreeMap::new(), None)); + } else { + return Err(FilesystemError::MissingTomlFile(file_name.to_owned(), file_path)); + } } let input_string = std::fs::read_to_string(file_path).unwrap(); 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 a6395d1c8c9..7e4a8b4117d 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_cli/src/cli/test_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd.rs index 0d7c8fc8bf7..2b0c0fd58db 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd.rs @@ -10,7 +10,8 @@ use nargo::{ }; use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; use noirc_driver::{ - check_crate, file_manager_with_stdlib, CompileOptions, NOIR_ARTIFACT_VERSION_STRING, + check_crate, file_manager_with_stdlib, CheckOptions, CompileOptions, + NOIR_ARTIFACT_VERSION_STRING, }; use noirc_frontend::{ graph::CrateName, @@ -180,7 +181,9 @@ fn run_test + Default>( // We then need to construct a separate copy for each test. let (mut context, crate_id) = prepare_package(file_manager, parsed_files, package); - check_crate(&mut context, crate_id, compile_options) + let error_on_unused_imports = package.error_on_unused_imports(); + let check_options = CheckOptions::new(compile_options, error_on_unused_imports); + check_crate(&mut context, crate_id, &check_options) .expect("Any errors should have occurred when collecting test functions"); let test_functions = context @@ -206,10 +209,12 @@ fn get_tests_in_package( parsed_files: &ParsedFiles, package: &Package, fn_name: FunctionNameMatch, - compile_options: &CompileOptions, + options: &CompileOptions, ) -> Result, CliError> { let (mut context, crate_id) = prepare_package(file_manager, parsed_files, package); - check_crate_and_report_errors(&mut context, crate_id, compile_options)?; + let error_on_unused_imports = package.error_on_unused_imports(); + let check_options = CheckOptions::new(options, error_on_unused_imports); + check_crate_and_report_errors(&mut context, crate_id, &check_options)?; Ok(context .get_all_test_functions_in_crate_matching(&crate_id, fn_name) diff --git a/noir/noir-repo/tooling/nargo_fmt/Cargo.toml b/noir/noir-repo/tooling/nargo_fmt/Cargo.toml index 05b2fdb7d52..9868f259097 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 41b15069546..caa60b17cc2 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/rewrite/expr.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/rewrite/expr.rs @@ -168,10 +168,16 @@ 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") } + ExpressionKind::Interned(_) => { + unreachable!("ExpressionKind::Interned should only emitted by the comptime interpreter") + } ExpressionKind::Unquote(expr) => { if matches!(&expr.kind, ExpressionKind::Variable(..)) { format!("${expr}") 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 b586f32a6fe..6121f8debf6 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/rewrite/typ.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/rewrite/typ.rs @@ -37,8 +37,10 @@ pub(crate) fn rewrite(visitor: &FmtVisitor, _shape: Shape, typ: UnresolvedType) format!("({types})") } } - UnresolvedTypeData::Function(args, return_type, env) => { - let env = if span_is_empty(env.span.unwrap()) { + UnresolvedTypeData::Function(args, return_type, env, unconstrained) => { + let unconstrained = if unconstrained { "unconstrained " } else { "" }; + + let env = if span_is_empty(env.span) { "".into() } else { let ty = rewrite(visitor, _shape, *env); @@ -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") @@ -70,7 +72,7 @@ pub(crate) fn rewrite(visitor: &FmtVisitor, _shape: Shape, typ: UnresolvedType) | UnresolvedTypeData::String(_) | UnresolvedTypeData::FormatString(_, _) | UnresolvedTypeData::Quoted(_) - | UnresolvedTypeData::TraitAsType(_, _) => visitor.slice(typ.span.unwrap()).into(), - UnresolvedTypeData::Error => unreachable!(), + | UnresolvedTypeData::TraitAsType(_, _) => visitor.slice(typ.span).into(), + UnresolvedTypeData::Interned(_) | UnresolvedTypeData::Error => unreachable!(), } } diff --git a/noir/noir-repo/tooling/nargo_fmt/src/visitor/item.rs b/noir/noir-repo/tooling/nargo_fmt/src/visitor/item.rs index 0c9f61a7d40..94a32449ebe 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/visitor/item.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/visitor/item.rs @@ -6,7 +6,10 @@ use crate::{ }, visitor::expr::{format_seq, NewlineMode}, }; -use noirc_frontend::ast::{NoirFunction, Visibility}; +use noirc_frontend::{ + ast::{NoirFunction, Visibility}, + macros_api::UnresolvedTypeData, +}; use noirc_frontend::{ hir::resolution::errors::Span, parser::{Item, ItemKind}, @@ -108,14 +111,16 @@ impl super::FmtVisitor<'_> { fn format_return_type( &self, - return_type_span: Option, + span: Span, func: &NoirFunction, func_span: Span, params_end: u32, ) -> String { let mut result = String::new(); - if let Some(span) = return_type_span { + if func.return_type().typ == UnresolvedTypeData::Unit { + result.push_str(self.slice(params_end..func_span.start())); + } else { result.push_str(" -> "); let visibility = match func.def.return_visibility { @@ -135,8 +140,6 @@ impl super::FmtVisitor<'_> { if !slice.trim().is_empty() { result.push_str(slice); } - } else { - result.push_str(self.slice(params_end..func_span.start())); } result diff --git a/noir/noir-repo/tooling/nargo_fmt/src/visitor/stmt.rs b/noir/noir-repo/tooling/nargo_fmt/src/visitor/stmt.rs index 8e05fe3f5c5..b5ac14a33b3 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/visitor/stmt.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/visitor/stmt.rs @@ -104,6 +104,9 @@ impl super::FmtVisitor<'_> { StatementKind::Break => self.push_rewrite("break;".into(), span), StatementKind::Continue => self.push_rewrite("continue;".into(), span), StatementKind::Comptime(statement) => self.visit_stmt(statement.kind, span, is_last), + StatementKind::Interned(_) => unreachable!( + "StatementKind::Resolved should only emitted by the comptime interpreter" + ), } } } 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 4dde9a1b3ec..73248d0c04f 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 00000000000..7d733c203de --- /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 16ed95a540d..8db6022808f 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 00000000000..6e12ef975ee --- /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 7c9faa4562a..e4766e44859 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 a5593cc284c..661d71fb9c3 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 4c0c1f75e42..a7baf334bff 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 aecb620b79d..b79f232d9e8 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 c07d2d8a4c1..16fb26e55db 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 ef4a468b661..b0c11979d17 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 4249604f949..13ff68e423a 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 91f02157414..ffffc6345d5 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 0ccd56b791f..136775d5831 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 e072c63f274..d5fefc4ecda 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 @@ -22,7 +22,7 @@ pub(crate) struct GatesFlamegraphCommand { backend_path: String, /// Command to get a gates report from the backend. Defaults to "gates" - #[clap(long, short, default_value = "gates")] + #[clap(long, short = 'g', default_value = "gates")] backend_gates_command: String, #[arg(trailing_var_arg = true, allow_hyphen_values = true)] @@ -87,6 +87,7 @@ fn run_with_provider( opcode: AcirOrBrilligOpcode::Acir(opcode), call_stack: vec![OpcodeLocation::Acir(index)], count: gates, + brillig_function_id: None, }) .collect(); @@ -128,7 +129,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 +174,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 f91e50d8716..863d45b96d1 100644 --- a/noir/noir-repo/tooling/profiler/src/cli/opcodes_flamegraph_cmd.rs +++ b/noir/noir-repo/tooling/profiler/src/cli/opcodes_flamegraph_cmd.rs @@ -1,5 +1,6 @@ use std::path::{Path, PathBuf}; +use acir::circuit::brillig::BrilligFunctionId; use acir::circuit::{Circuit, Opcode, OpcodeLocation}; use clap::Args; use color_eyre::eyre::{self, Context}; @@ -20,7 +21,7 @@ pub(crate) struct OpcodesFlamegraphCommand { #[clap(long, short)] output: String, - /// Wether to skip brillig functions + /// Whether to skip brillig functions #[clap(long, short, action)] skip_brillig: bool, } @@ -62,6 +63,7 @@ fn run_with_generator( opcode: AcirOrBrilligOpcode::Acir(opcode.clone()), call_stack: vec![OpcodeLocation::Acir(index)], count: 1, + brillig_function_id: None, }) .collect(); @@ -101,6 +103,7 @@ fn run_with_generator( brillig_index, }], count: 1, + brillig_function_id: Some(BrilligFunctionId(brillig_fn_index as u32)), }) .collect(); @@ -188,6 +191,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 +232,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/flamegraph.rs b/noir/noir-repo/tooling/profiler/src/flamegraph.rs index da76f9b9938..488079de50a 100644 --- a/noir/noir-repo/tooling/profiler/src/flamegraph.rs +++ b/noir/noir-repo/tooling/profiler/src/flamegraph.rs @@ -1,6 +1,7 @@ use std::path::Path; use std::{collections::BTreeMap, io::BufWriter}; +use acir::circuit::brillig::BrilligFunctionId; use acir::circuit::OpcodeLocation; use acir::AcirField; use color_eyre::eyre::{self}; @@ -19,6 +20,7 @@ pub(crate) struct Sample { pub(crate) opcode: AcirOrBrilligOpcode, pub(crate) call_stack: Vec, pub(crate) count: usize, + pub(crate) brillig_function_id: Option, } #[derive(Debug, Default)] @@ -90,9 +92,24 @@ fn generate_folded_sorted_lines<'files, F: AcirField>( let mut location_names: Vec = sample .call_stack .into_iter() - .flat_map(|opcode_location| debug_symbols.locations.get(&opcode_location)) - .flatten() - .map(|location| location_to_callsite_label(*location, files)) + .flat_map(|opcode_location| { + debug_symbols.opcode_location(&opcode_location).unwrap_or_else(|| { + if let (Some(brillig_function_id), Some(brillig_location)) = + (sample.brillig_function_id, opcode_location.to_brillig_location()) + { + let brillig_locations = + debug_symbols.brillig_locations.get(&brillig_function_id); + if let Some(brillig_locations) = brillig_locations { + brillig_locations.get(&brillig_location).cloned().unwrap_or_default() + } else { + vec![] + } + } else { + vec![] + } + }) + }) + .map(|location| location_to_callsite_label(location, files)) .collect(); if location_names.is_empty() { @@ -286,11 +303,13 @@ mod tests { opcode: AcirOrBrilligOpcode::Acir(AcirOpcode::AssertZero(Expression::default())), call_stack: vec![OpcodeLocation::Acir(0)], count: 10, + brillig_function_id: None, }, Sample { opcode: AcirOrBrilligOpcode::Acir(AcirOpcode::AssertZero(Expression::default())), call_stack: vec![OpcodeLocation::Acir(1)], count: 20, + brillig_function_id: None, }, Sample { opcode: AcirOrBrilligOpcode::Acir(AcirOpcode::MemoryInit { @@ -300,6 +319,7 @@ mod tests { }), call_stack: vec![OpcodeLocation::Acir(2)], count: 30, + brillig_function_id: None, }, ]; diff --git a/noir/noir-repo/tooling/profiler/src/opcode_formatter.rs b/noir/noir-repo/tooling/profiler/src/opcode_formatter.rs index 772c87ad1cb..edb3e1b0f08 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 f77e9f7e72e..f10b5b2cb67 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