From 772b444eb3e703ac6c0e219596d4f11830e90123 Mon Sep 17 00:00:00 2001 From: AztecBot Date: Sun, 1 Sep 2024 08:02:01 +0000 Subject: [PATCH] [1 changes] feat: add `Type::as_string` (https://github.com/noir-lang/noir/pull/5871) feat: add `FunctionDef::has_named_attribute` (https://github.com/noir-lang/noir/pull/5870) chore: introduce the Visitor pattern (https://github.com/noir-lang/noir/pull/5868) fix(nargo): Resolve Brillig assertion payloads (https://github.com/noir-lang/noir/pull/5872) feat(perf): Simplify poseidon2 cache zero-pad (https://github.com/noir-lang/noir/pull/5869) feat: Sync from aztec-packages (https://github.com/noir-lang/noir/pull/5790) feat: add `Expr::resolve` and `TypedExpr::as_function_definition` (https://github.com/noir-lang/noir/pull/5859) feat: LSP signature help for assert and assert_eq (https://github.com/noir-lang/noir/pull/5862) fix(sha256): Add extra checks against message size when constructing msg blocks (https://github.com/noir-lang/noir/pull/5861) feat: show backtrace on comptime assertion failures (https://github.com/noir-lang/noir/pull/5842) feat: add `Expr::as_assert` (https://github.com/noir-lang/noir/pull/5857) chore: underconstrained check in parallel (https://github.com/noir-lang/noir/pull/5848) 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 +- noir/noir-repo/Cargo.lock | 223 ++- noir/noir-repo/Cargo.toml | 11 +- .../acvm-repo/acir/src/circuit/mod.rs | 26 + .../noir-repo/acvm-repo/acir_field/Cargo.toml | 2 +- noir/noir-repo/acvm-repo/acvm/Cargo.toml | 5 +- .../acvm/tests/solver.proptest-regressions | 6 + noir/noir-repo/acvm-repo/acvm/tests/solver.rs | 995 +++++++++++- noir/noir-repo/acvm-repo/acvm_js/build.sh | 2 +- .../bn254_blackbox_solver/src/lib.rs | 2 +- .../bn254_blackbox_solver/src/poseidon2.rs | 20 +- noir/noir-repo/aztec_macros/Cargo.toml | 1 + .../src/transforms/note_interface.rs | 3 +- .../aztec_macros/src/transforms/storage.rs | 32 +- .../aztec_macros/src/utils/parse_utils.rs | 42 +- .../compiler/noirc_driver/Cargo.toml | 4 + .../compiler/noirc_driver/src/lib.rs | 33 +- .../compiler/noirc_errors/src/debug_info.rs | 10 +- .../compiler/noirc_errors/src/reporter.rs | 18 +- .../compiler/noirc_evaluator/Cargo.toml | 8 +- .../src/brillig/brillig_gen/brillig_block.rs | 6 +- .../brillig/brillig_gen/brillig_directive.rs | 2 +- .../src/brillig/brillig_ir/codegen_memory.rs | 14 +- .../compiler/noirc_evaluator/src/ssa.rs | 13 +- .../src/ssa/acir_gen/acir_ir/acir_variable.rs | 39 +- .../ssa/acir_gen/acir_ir/generated_acir.rs | 21 +- .../noirc_evaluator/src/ssa/acir_gen/mod.rs | 16 +- .../check_for_underconstrained_values.rs | 59 +- .../src/ssa/function_builder/data_bus.rs | 7 +- .../src/ssa/function_builder/mod.rs | 10 +- .../noirc_evaluator/src/ssa/ir/dfg.rs | 8 + .../noirc_evaluator/src/ssa/ir/instruction.rs | 61 +- .../src/ssa/ir/instruction/call.rs | 51 +- .../src/ssa/ir/instruction/call/blackbox.rs | 190 +++ .../noirc_evaluator/src/ssa/ir/map.rs | 2 +- .../noirc_evaluator/src/ssa/ir/printer.rs | 2 +- .../noirc_evaluator/src/ssa/ir/types.rs | 12 +- .../src/ssa/opt/constant_folding.rs | 14 +- .../noirc_evaluator/src/ssa/opt/die.rs | 2 +- .../src/ssa/opt/flatten_cfg.rs | 14 +- .../src/ssa/opt/flatten_cfg/value_merger.rs | 11 +- .../noirc_evaluator/src/ssa/opt/inlining.rs | 2 +- .../noirc_evaluator/src/ssa/opt/mem2reg.rs | 8 +- .../noirc_evaluator/src/ssa/opt/rc.rs | 12 +- .../src/ssa/opt/remove_bit_shifts.rs | 4 +- .../src/ssa/opt/remove_enable_side_effects.rs | 20 +- .../src/ssa/opt/remove_if_else.rs | 2 +- .../src/ssa/ssa_gen/context.rs | 11 +- .../compiler/noirc_frontend/Cargo.toml | 2 +- .../noirc_frontend/src/ast/expression.rs | 8 +- .../compiler/noirc_frontend/src/ast/mod.rs | 106 +- .../noirc_frontend/src/ast/statement.rs | 53 +- .../compiler/noirc_frontend/src/ast/traits.rs | 19 +- .../noirc_frontend/src/ast/visitor.rs | 1301 ++++++++++++++++ .../compiler/noirc_frontend/src/debug/mod.rs | 3 + .../noirc_frontend/src/elaborator/comptime.rs | 20 +- .../src/elaborator/expressions.rs | 11 +- .../noirc_frontend/src/elaborator/lints.rs | 4 +- .../noirc_frontend/src/elaborator/mod.rs | 105 +- .../noirc_frontend/src/elaborator/patterns.rs | 28 +- .../noirc_frontend/src/elaborator/scope.rs | 46 +- .../src/elaborator/statements.rs | 52 +- .../src/elaborator/trait_impls.rs | 63 +- .../noirc_frontend/src/elaborator/traits.rs | 80 +- .../noirc_frontend/src/elaborator/types.rs | 409 +++-- .../noirc_frontend/src/hir/comptime/errors.rs | 47 +- .../src/hir/comptime/hir_to_display_ast.rs | 18 +- .../src/hir/comptime/interpreter.rs | 9 +- .../src/hir/comptime/interpreter/builtin.rs | 723 +++++++-- .../interpreter/builtin/builtin_helpers.rs | 74 +- .../src/hir/comptime/interpreter/foreign.rs | 31 +- .../noirc_frontend/src/hir/comptime/value.rs | 337 +++- .../src/hir/def_collector/dc_crate.rs | 53 +- .../src/hir/def_collector/dc_mod.rs | 103 +- .../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/resolution/errors.rs | 57 +- .../src/hir/resolution/import.rs | 25 +- .../src/hir/resolution/path_resolver.rs | 6 +- .../src/hir/type_check/errors.rs | 80 +- .../src/hir/type_check/generics.rs | 166 ++ .../noirc_frontend/src/hir/type_check/mod.rs | 11 +- .../noirc_frontend/src/hir_def/expr.rs | 15 +- .../noirc_frontend/src/hir_def/function.rs | 3 + .../noirc_frontend/src/hir_def/traits.rs | 72 +- .../noirc_frontend/src/hir_def/types.rs | 376 +++-- .../src/hir_def/types/arithmetic.rs | 215 +++ .../noirc_frontend/src/lexer/token.rs | 55 +- .../src/monomorphization/ast.rs | 18 +- .../src/monomorphization/errors.rs | 28 +- .../src/monomorphization/mod.rs | 53 +- .../src/monomorphization/printer.rs | 4 +- .../noirc_frontend/src/node_interner.rs | 315 +++- .../noirc_frontend/src/parser/errors.rs | 4 + .../compiler/noirc_frontend/src/parser/mod.rs | 2 +- .../noirc_frontend/src/parser/parser.rs | 50 +- .../noirc_frontend/src/parser/parser/path.rs | 13 +- .../src/parser/parser/primitives.rs | 24 +- .../src/parser/parser/traits.rs | 13 +- .../noirc_frontend/src/parser/parser/types.rs | 77 +- .../compiler/noirc_frontend/src/tests.rs | 145 +- .../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 | 8 + .../docs/docs/noir/concepts/comptime.md | 263 ++++ .../docs/noir/concepts/data_types/slices.mdx | 2 +- .../docs/docs/noir/concepts/traits.md | 60 + .../docs/docs/noir/standard_library/bigint.md | 5 + .../cryptographic_primitives/hashes.mdx | 2 +- .../docs/noir/standard_library/meta/expr.md | 201 +++ .../standard_library/meta/function_def.md | 67 + .../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 | 132 ++ .../noir/standard_library/meta/typed_expr.md | 13 + .../standard_library/meta/unresolved_type.md | 13 + noir/noir-repo/noir_stdlib/src/array.nr | 1 + noir/noir-repo/noir_stdlib/src/cmp.nr | 4 + .../noir_stdlib/src/collections/map.nr | 18 +- .../noir_stdlib/src/collections/umap.nr | 22 +- 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 | 15 +- .../noir_stdlib/src/hash/poseidon2.nr | 67 +- noir/noir-repo/noir_stdlib/src/hash/sha256.nr | 257 ++++ noir/noir-repo/noir_stdlib/src/hash/sha512.nr | 165 ++ noir/noir-repo/noir_stdlib/src/meta/expr.nr | 412 ++++- .../noir_stdlib/src/meta/function_def.nr | 24 +- noir/noir-repo/noir_stdlib/src/meta/mod.nr | 153 +- noir/noir-repo/noir_stdlib/src/meta/module.nr | 6 + noir/noir-repo/noir_stdlib/src/meta/op.nr | 136 +- 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 | 4 + noir/noir-repo/noir_stdlib/src/meta/typ.nr | 25 + .../noir_stdlib/src/meta/typed_expr.nr | 8 + .../noir_stdlib/src/meta/unresolved_type.nr | 6 + noir/noir-repo/noir_stdlib/src/option.nr | 2 +- 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 | 5 +- noir/noir-repo/scripts/install_bb.sh | 2 +- .../unspecified_generic/Nargo.toml | 7 + .../unspecified_generic/src/main.nr | 5 + .../arithmetic_generics/src/main.nr | 31 + .../associated_types/Nargo.toml | 7 + .../associated_types/src/main.nr | 28 + .../comptime_function_definition/src/main.nr | 4 +- .../comptime_keccak/Nargo.toml | 7 + .../comptime_keccak/src/main.nr | 31 + .../comptime_module/src/main.nr | 15 + .../comptime_type/src/main.nr | 7 + .../Nargo.toml | 7 + .../src/main.nr | 11 + .../inject_context_attribute/Nargo.toml | 6 + .../inject_context_attribute/src/main.nr | 63 + .../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 + .../bigint_from_too_many_le_bytes/Nargo.toml | 7 + .../bigint_from_too_many_le_bytes/src/main.nr | 22 + .../src/main.nr | 1 + .../Nargo.toml | 7 + .../Prover.toml | 2 + .../src/main.nr | 13 + .../execution_success/sha256/Prover.toml | 2 + .../execution_success/sha256/src/main.nr | 7 +- .../sha256_regression/Nargo.toml | 7 + .../sha256_regression/Prover.toml | 14 + .../sha256_regression/src/main.nr | 39 + .../sha256_var_size_regression/Nargo.toml | 7 + .../sha256_var_size_regression/Prover.toml | 3 + .../sha256_var_size_regression/src/main.nr | 17 + .../verify_honk_proof/Prover.toml | 4 - .../verify_honk_proof/src/main.nr | 17 - .../comptime_expr/Nargo.toml | 7 + .../comptime_expr/src/main.nr | 614 ++++++++ .../embedded_curve_ops/src/main.nr | 1 - noir/noir-repo/test_programs/rebuild.sh | 2 +- .../noir-repo/tooling/debugger/src/context.rs | 29 +- noir/noir-repo/tooling/lsp/Cargo.toml | 2 +- .../tooling/lsp/src/notifications/mod.rs | 8 +- .../tooling/lsp/src/requests/completion.rs | 1354 +++++++++-------- .../src/requests/completion/auto_import.rs | 44 +- .../lsp/src/requests/completion/builtins.rs | 48 +- .../lsp/src/requests/completion/tests.rs | 163 +- .../lsp/src/requests/completion/traversal.rs | 132 -- .../lsp/src/requests/document_symbol.rs | 438 +++--- .../lsp/src/requests/goto_definition.rs | 14 + .../tooling/lsp/src/requests/hover.rs | 71 +- .../tooling/lsp/src/requests/inlay_hint.rs | 376 ++--- .../lsp/src/requests/signature_help.rs | 232 ++- .../lsp/src/requests/signature_help/tests.rs | 47 + .../src/requests/signature_help/traversal.rs | 304 ---- .../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 | 2 +- noir/noir-repo/tooling/nargo/src/errors.rs | 23 +- .../tooling/nargo/src/ops/execute.rs | 12 + noir/noir-repo/tooling/nargo/src/package.rs | 7 + noir/noir-repo/tooling/nargo_cli/Cargo.toml | 4 +- .../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/test_cmd.rs | 13 +- .../tooling/nargo_fmt/src/rewrite/expr.rs | 3 + .../tooling/nargo_fmt/src/rewrite/typ.rs | 2 +- .../tooling/nargo_fmt/src/visitor/stmt.rs | 3 + .../noir_js_backend_barretenberg/package.json | 2 +- .../noir-repo/tooling/noirc_abi_wasm/build.sh | 2 +- .../profiler/src/cli/gates_flamegraph_cmd.rs | 3 +- .../src/cli/opcodes_flamegraph_cmd.rs | 5 +- .../tooling/profiler/src/flamegraph.rs | 26 +- noir/noir-repo/yarn.lock | 13 +- 232 files changed, 11908 insertions(+), 3386 deletions(-) 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/ast/visitor.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/typed_expr.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 create mode 100644 noir/noir-repo/noir_stdlib/src/meta/typed_expr.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 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/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_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/noir_test_success/comptime_expr/Nargo.toml create mode 100644 noir/noir-repo/test_programs/noir_test_success/comptime_expr/src/main.nr delete mode 100644 noir/noir-repo/tooling/lsp/src/requests/completion/traversal.rs delete 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 diff --git a/.noir-sync-commit b/.noir-sync-commit index c1224615e82..c303f36d632 100644 --- a/.noir-sync-commit +++ b/.noir-sync-commit @@ -1 +1 @@ -1958a7932642e2fa556a903a3186b142a70e3e48 +e29d4b3646f0527fc01bc4584ee33616db922c72 diff --git a/noir/noir-repo/Cargo.lock b/noir/noir-repo/Cargo.lock index e1159c71201..cd936e4bca2 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]] @@ -448,6 +451,7 @@ version = "0.33.0" dependencies = [ "acvm", "convert_case 0.6.0", + "im", "iter-extended", "noirc_errors", "noirc_frontend", @@ -539,6 +543,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 +564,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 +597,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 +1400,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 +1505,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 +1621,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 +1822,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 +1845,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 +2345,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 +2436,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 +2574,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" @@ -2889,6 +3005,7 @@ version = "0.33.0" dependencies = [ "acvm", "bn254_blackbox_solver", + "cfg-if 1.0.0", "chrono", "fxhash", "im", @@ -2897,6 +3014,7 @@ dependencies = [ "noirc_frontend", "num-bigint", "proptest", + "rayon", "serde", "serde_json", "serde_with", @@ -3100,6 +3218,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" @@ -3148,6 +3275,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" @@ -3473,6 +3630,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" @@ -4193,6 +4356,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" @@ -4209,6 +4378,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" @@ -4310,6 +4485,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" @@ -5178,6 +5359,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" @@ -5217,3 +5407,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 7d9b3254c53..a903ef6fec9 100644 --- a/noir/noir-repo/Cargo.toml +++ b/noir/noir-repo/Cargo.toml @@ -130,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" @@ -154,6 +154,7 @@ color-eyre = "0.6.2" rand = "0.8.5" proptest = "1.2.0" proptest-derive = "0.4.0" +rayon = "1.8.0" im = { version = "15.1", features = ["serde"] } tracing = "0.1.40" @@ -166,6 +167,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/src/circuit/mod.rs b/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs index 43984e4a922..f700fefe0cd 100644 --- a/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs @@ -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 diff --git a/noir/noir-repo/acvm-repo/acir_field/Cargo.toml b/noir/noir-repo/acvm-repo/acir_field/Cargo.toml index c1cffc1334e..acc34457bc9 100644 --- a/noir/noir-repo/acvm-repo/acir_field/Cargo.toml +++ b/noir/noir-repo/acvm-repo/acir_field/Cargo.toml @@ -24,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/acvm/Cargo.toml b/noir/noir-repo/acvm-repo/acvm/Cargo.toml index bf1170ce073..ea80dbeedbb 100644 --- a/noir/noir-repo/acvm-repo/acvm/Cargo.toml +++ b/noir/noir-repo/acvm-repo/acvm/Cargo.toml @@ -13,8 +13,6 @@ repository.workspace = true [lints] workspace = true -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] num-bigint.workspace = true thiserror.workspace = true @@ -41,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/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/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/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/aztec_macros/Cargo.toml b/noir/noir-repo/aztec_macros/Cargo.toml index c9d88e36e28..258379cd7b8 100644 --- a/noir/noir-repo/aztec_macros/Cargo.toml +++ b/noir/noir-repo/aztec_macros/Cargo.toml @@ -18,5 +18,6 @@ noirc_frontend.workspace = true noirc_errors.workspace = true iter-extended.workspace = true convert_case = "0.6.0" +im.workspace = true regex = "1.10" tiny-keccak = { version = "2.0.0", features = ["keccak"] } 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 46ed75620a7..8df1d128c6f 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/note_interface.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/note_interface.rs @@ -88,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()), @@ -120,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, )), ); diff --git a/noir/noir-repo/aztec_macros/src/transforms/storage.rs b/noir/noir-repo/aztec_macros/src/transforms/storage.rs index ce82b4d4b6d..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,13 +238,12 @@ 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, - ), + typ: UnresolvedTypeData::Named(chained_path!(storage_struct_name), generics, true), span: Span::default(), }, type_span: Span::default(), 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 6b5db103c0b..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 => (), } } @@ -270,12 +274,15 @@ fn empty_expression(expression: &mut Expression) { ExpressionKind::Unsafe(block_expression, _span) => { empty_block_expression(block_expression); } - ExpressionKind::Quote(..) | ExpressionKind::Resolved(_) | ExpressionKind::Error => (), ExpressionKind::AsTraitPath(path) => { empty_unresolved_type(&mut path.typ); empty_path(&mut path.trait_path); empty_ident(&mut path.impl_item); } + ExpressionKind::Quote(..) + | ExpressionKind::Resolved(_) + | ExpressionKind::Interned(_) + | ExpressionKind::Error => (), } } @@ -297,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(); @@ -318,11 +333,11 @@ 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) @@ -344,6 +359,7 @@ fn empty_unresolved_type(unresolved_type: &mut UnresolvedType) { | UnresolvedTypeData::Unit | UnresolvedTypeData::Quoted(_) | UnresolvedTypeData::Resolved(_) + | UnresolvedTypeData::Interned(_) | UnresolvedTypeData::Unspecified | UnresolvedTypeData::Error => (), } @@ -522,6 +538,7 @@ fn empty_lvalue(lvalue: &mut LValue) { empty_expression(index); } LValue::Dereference(lvalue, _) => empty_lvalue(lvalue), + LValue::Interned(..) => (), } } @@ -543,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/noirc_driver/Cargo.toml b/noir/noir-repo/compiler/noirc_driver/Cargo.toml index b244018cc71..6b200e79b89 100644 --- a/noir/noir-repo/compiler/noirc_driver/Cargo.toml +++ b/noir/noir-repo/compiler/noirc_driver/Cargo.toml @@ -29,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/lib.rs b/noir/noir-repo/compiler/noirc_driver/src/lib.rs index 467bda2ca88..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,8 +290,10 @@ pub fn add_dep( pub fn check_crate( context: &mut Context, crate_id: CrateId, - options: &CompileOptions, + check_options: &CheckOptions, ) -> CompilationResult<()> { + let options = &check_options.compile_options; + let macros: &[&dyn MacroProcessor] = if options.disable_macros { &[] } else { &[&aztec_macros::AztecMacro] }; @@ -283,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)| { @@ -316,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 @@ -351,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); @@ -574,6 +600,7 @@ 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, brillig_names, error_types, .. } = 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/reporter.rs b/noir/noir-repo/compiler/noirc_errors/src/reporter.rs index 3ce0f268715..b21dc759f14 100644 --- a/noir/noir-repo/compiler/noirc_errors/src/reporter.rs +++ b/noir/noir-repo/compiler/noirc_errors/src/reporter.rs @@ -46,7 +46,7 @@ impl CustomDiagnostic { ) -> CustomDiagnostic { CustomDiagnostic { message: primary_message, - secondaries: vec![CustomLabel::new(secondary_message, secondary_span)], + secondaries: vec![CustomLabel::new(secondary_message, secondary_span, None)], notes: Vec::new(), kind, } @@ -98,7 +98,7 @@ impl CustomDiagnostic { ) -> CustomDiagnostic { CustomDiagnostic { message: primary_message, - secondaries: vec![CustomLabel::new(secondary_message, secondary_span)], + secondaries: vec![CustomLabel::new(secondary_message, secondary_span, None)], notes: Vec::new(), kind: DiagnosticKind::Bug, } @@ -113,7 +113,11 @@ impl CustomDiagnostic { } pub fn add_secondary(&mut self, message: String, span: Span) { - self.secondaries.push(CustomLabel::new(message, span)); + self.secondaries.push(CustomLabel::new(message, span, None)); + } + + pub fn add_secondary_with_file(&mut self, message: String, span: Span, file: fm::FileId) { + self.secondaries.push(CustomLabel::new(message, span, Some(file))); } pub fn is_error(&self) -> bool { @@ -153,11 +157,12 @@ impl std::fmt::Display for CustomDiagnostic { pub struct CustomLabel { pub message: String, pub span: Span, + pub file: Option, } impl CustomLabel { - fn new(message: String, span: Span) -> CustomLabel { - CustomLabel { message, span } + fn new(message: String, span: Span, file: Option) -> CustomLabel { + CustomLabel { message, span, file } } } @@ -234,7 +239,8 @@ fn convert_diagnostic( .map(|sl| { let start_span = sl.span.start() as usize; let end_span = sl.span.end() as usize; - Label::secondary(file_id, start_span..end_span).with_message(&sl.message) + let file = sl.file.unwrap_or(file_id); + Label::secondary(file, start_span..end_span).with_message(&sl.message) }) .collect() } else { diff --git a/noir/noir-repo/compiler/noirc_evaluator/Cargo.toml b/noir/noir-repo/compiler/noirc_evaluator/Cargo.toml index 81feb0b7154..1db6af2ae85 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/Cargo.toml +++ b/noir/noir-repo/compiler/noirc_evaluator/Cargo.toml @@ -26,6 +26,12 @@ serde_json.workspace = true serde_with = "3.2.0" tracing.workspace = true chrono = "0.4.37" +rayon.workspace = true +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/brillig_block.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs index b4e55a52a36..eeaa60b4323 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 @@ -24,7 +24,7 @@ use acvm::{acir::AcirField, FieldElement}; use fxhash::{FxHashMap as HashMap, FxHashSet as HashSet}; use iter_extended::vecmap; use num_bigint::BigUint; -use std::rc::Rc; +use std::sync::Arc; use super::brillig_black_box::convert_black_box_call; use super::brillig_block_variables::BlockVariables; @@ -735,7 +735,7 @@ impl<'block> BrilligBlock<'block> { 1, ); } - Instruction::EnableSideEffects { .. } => { + Instruction::EnableSideEffectsIf { .. } => { todo!("enable_side_effects not supported by brillig") } Instruction::IfElse { .. } => { @@ -1643,7 +1643,7 @@ impl<'block> BrilligBlock<'block> { fn initialize_constant_array_runtime( &mut self, - item_types: Rc>, + item_types: Arc>, item_to_repeat: Vec, item_count: usize, pointer: MemoryAddress, 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 fca1f60544d..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 @@ -63,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) /// } 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 88f7e35865e..69e7ff5383d 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 @@ -276,20 +276,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(pointer, iterator_register, start_value_register); + self.mov_instruction(index_at_end_of_array, 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, 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(pointer, iterator_register, start_value_register); ctx.codegen_array_get( 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 9daf98e606b..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) }); 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 166b4ec2d49..d3d936385b0 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 @@ -10,7 +10,6 @@ use crate::ssa::ir::{instruction::Endian, types::NumericType}; use acvm::acir::circuit::brillig::{BrilligFunctionId, BrilligInputs, BrilligOutputs}; 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())?; @@ -1452,8 +1451,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` @@ -1945,6 +1955,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 } @@ -1974,8 +1987,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(()) @@ -2129,7 +2150,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 2e61a82d5b8..0cad7b9c978 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, @@ -591,6 +593,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 { @@ -606,13 +609,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()); } } @@ -626,6 +626,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 346d6fcd921..37ec43fb13b 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 @@ -706,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; } @@ -1165,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); 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..26eab290d4b 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 @@ -1,8 +1,6 @@ //! This module defines an SSA pass that detects if the final function has any subgraphs independent from inputs and outputs. //! If this is the case, then part of the final circuit can be completely replaced by any other passing circuit, since there are no constraints ensuring connections. //! So the compiler informs the developer of this as a bug -use im::HashMap; - use crate::errors::{InternalBug, SsaReport}; use crate::ssa::ir::basic_block::BasicBlockId; use crate::ssa::ir::function::RuntimeType; @@ -10,25 +8,29 @@ use crate::ssa::ir::function::{Function, FunctionId}; use crate::ssa::ir::instruction::{Instruction, InstructionId, Intrinsic}; use crate::ssa::ir::value::{Value, ValueId}; use crate::ssa::ssa_gen::Ssa; +use im::HashMap; +use rayon::prelude::*; use std::collections::{BTreeMap, HashSet}; impl Ssa { /// Go through each top-level non-brillig function and detect if it has independent subgraphs #[tracing::instrument(level = "trace", skip(self))] pub(crate) fn check_for_underconstrained_values(&mut self) -> Vec { - let mut warnings: Vec = Vec::new(); - for function in self.functions.values() { - match function.runtime() { - RuntimeType::Acir { .. } => { - warnings.extend(check_for_underconstrained_values_within_function( - function, + let functions_id = self.functions.values().map(|f| f.id().to_usize()).collect::>(); + functions_id + .iter() + .par_bridge() + .flat_map(|fid| { + let function_to_process = &self.functions[&FunctionId::new(*fid)]; + match function_to_process.runtime() { + RuntimeType::Acir { .. } => check_for_underconstrained_values_within_function( + function_to_process, &self.functions, - )); + ), + RuntimeType::Brillig => Vec::new(), } - RuntimeType::Brillig => (), - } - } - warnings + }) + .collect() } } @@ -88,9 +90,8 @@ impl Context { self.visited_blocks.insert(block); self.connect_value_ids_in_block(function, block, all_functions); } - // Merge ValueIds into sets, where each original small set of ValueIds is merged with another set if they intersect - self.merge_sets(); + self.value_sets = Self::merge_sets_par(&self.value_sets); } /// Find sets that contain input or output value of the function @@ -255,7 +256,7 @@ impl Context { } Instruction::Allocate { .. } | Instruction::DecrementRc { .. } - | Instruction::EnableSideEffects { .. } + | Instruction::EnableSideEffectsIf { .. } | Instruction::IncrementRc { .. } | Instruction::RangeCheck { .. } => {} } @@ -267,14 +268,13 @@ impl Context { /// Merge all small sets into larger ones based on whether the sets intersect or not /// /// If two small sets have a common ValueId, we merge them into one - fn merge_sets(&mut self) { + fn merge_sets(current: &[HashSet]) -> Vec> { let mut new_set_id: usize = 0; let mut updated_sets: HashMap> = HashMap::new(); let mut value_dictionary: HashMap = HashMap::new(); let mut parsed_value_set: HashSet = HashSet::new(); - // Go through each set - for set in self.value_sets.iter() { + for set in current.iter() { // Check if the set has any of the ValueIds we've encountered at previous iterations let intersection: HashSet = set.intersection(&parsed_value_set).copied().collect(); @@ -327,7 +327,26 @@ impl Context { } updated_sets.insert(largest_set_index, largest_set); } - self.value_sets = updated_sets.values().cloned().collect(); + updated_sets.values().cloned().collect() + } + + /// Parallel version of merge_sets + /// The sets are merged by chunks, and then the chunks are merged together + fn merge_sets_par(sets: &[HashSet]) -> Vec> { + let mut sets = sets.to_owned(); + let mut len = sets.len(); + let mut prev_len = len + 1; + + while len > 1000 && len < prev_len { + sets = sets.par_chunks(1000).flat_map(Self::merge_sets).collect(); + + prev_len = len; + len = sets.len(); + } + // TODO: if prev_len >= len, this means we cannot effectively merge the sets anymore + // We should instead partition the sets into disjoint chunks and work on those chunks, + // but for now we fallback to the non-parallel implementation + Self::merge_sets(&sets) } } #[cfg(test)] diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/data_bus.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/data_bus.rs index 9f964cf048d..38895bb977e 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/data_bus.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/data_bus.rs @@ -1,5 +1,4 @@ -use std::collections::BTreeMap; -use std::rc::Rc; +use std::{collections::BTreeMap, sync::Arc}; use crate::ssa::ir::{types::Type, value::ValueId}; use acvm::FieldElement; @@ -155,8 +154,8 @@ impl FunctionBuilder { let len = databus.values.len(); let array = if len > 0 { - let array = - self.array_constant(databus.values, Type::Array(Rc::new(vec![Type::field()]), len)); + let array = self + .array_constant(databus.values, Type::Array(Arc::new(vec![Type::field()]), len)); Some(array) } else { None 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..bf6430c36d7 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 @@ -1,6 +1,6 @@ pub(crate) mod data_bus; -use std::{borrow::Cow, collections::BTreeMap, rc::Rc}; +use std::{borrow::Cow, collections::BTreeMap, sync::Arc}; use acvm::{acir::circuit::ErrorSelector, FieldElement}; use noirc_errors::Location; @@ -189,7 +189,7 @@ impl FunctionBuilder { /// given amount of field elements. Returns the result of the allocate instruction, /// which is always a Reference to the allocated data. pub(crate) fn insert_allocate(&mut self, element_type: Type) -> ValueId { - let reference_type = Type::Reference(Rc::new(element_type)); + let reference_type = Type::Reference(Arc::new(element_type)); self.insert_instruction(Instruction::Allocate, Some(vec![reference_type])).first() } @@ -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 @@ -516,7 +516,7 @@ impl std::ops::Index for FunctionBuilder { #[cfg(test)] mod tests { - use std::rc::Rc; + use std::sync::Arc; use acvm::{acir::AcirField, FieldElement}; @@ -542,7 +542,7 @@ mod tests { let to_bits_id = builder.import_intrinsic_id(Intrinsic::ToBits(Endian::Little)); let input = builder.numeric_constant(FieldElement::from(7_u128), Type::field()); let length = builder.numeric_constant(FieldElement::from(8_u128), Type::field()); - let result_types = vec![Type::Array(Rc::new(vec![Type::bool()]), 8)]; + let result_types = vec![Type::Array(Arc::new(vec![Type::bool()]), 8)]; let call_results = builder.insert_call(to_bits_id, vec![input, length], result_types).into_owned(); 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 f06f46d7af8..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 @@ -472,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 1b3466c76fa..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 } @@ -682,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; } } @@ -716,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, 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..2c6aedeca35 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 std::{collections::VecDeque, sync::Arc}; -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 @@ -544,7 +561,7 @@ fn simplify_black_box_func( fn make_constant_array(dfg: &mut DataFlowGraph, results: Vec, typ: Type) -> ValueId { let result_constants = vecmap(results, |element| dfg.make_constant(element, typ.clone())); - let typ = Type::Array(Rc::new(vec![typ]), result_constants.len()); + let typ = Type::Array(Arc::new(vec![typ]), result_constants.len()); dfg.make_array(result_constants.into(), typ) } @@ -555,7 +572,7 @@ fn make_constant_slice( ) -> (ValueId, ValueId) { let result_constants = vecmap(results, |element| dfg.make_constant(element, typ.clone())); - let typ = Type::Slice(Rc::new(vec![typ])); + let typ = Type::Slice(Arc::new(vec![typ])); let length = FieldElement::from(result_constants.len() as u128); (dfg.make_constant(length, Type::length_type()), dfg.make_array(result_constants.into(), typ)) } 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..7789b212e58 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call/blackbox.rs @@ -0,0 +1,190 @@ +use std::sync::Arc; + +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(Arc::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 f1265553b83..769d52e6e65 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 @@ -27,7 +27,7 @@ impl Id { /// Constructs a new Id for the given index. /// This constructor is deliberately private to prevent /// constructing invalid IDs. - fn new(index: usize) -> Self { + pub(crate) fn new(index: usize) -> Self { Self { index, _marker: std::marker::PhantomData } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs index 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/ir/types.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/types.rs index e467fa5400d..b7ee37ba17a 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/types.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/types.rs @@ -1,5 +1,5 @@ use serde::{Deserialize, Serialize}; -use std::rc::Rc; +use std::sync::Arc; use acvm::{acir::AcirField, FieldElement}; use iter_extended::vecmap; @@ -72,13 +72,13 @@ pub(crate) enum Type { Numeric(NumericType), /// A reference to some value, such as an array - Reference(Rc), + Reference(Arc), /// An immutable array value with the given element type and length - Array(Rc, usize), + Array(Arc, usize), /// An immutable slice value with a given element type - Slice(Rc), + Slice(Arc), /// A function that may be called directly Function, @@ -112,7 +112,7 @@ impl Type { /// Creates the str type, of the given length N pub(crate) fn str(length: usize) -> Type { - Type::Array(Rc::new(vec![Type::char()]), length) + Type::Array(Arc::new(vec![Type::char()]), length) } /// Creates the native field type. @@ -190,7 +190,7 @@ impl Type { } } - pub(crate) fn element_types(self) -> Rc> { + pub(crate) fn element_types(self) -> Arc> { match self { Type::Array(element_types, _) | Type::Slice(element_types) => element_types, other => panic!("element_types: Expected array or slice, found {other}"), 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..ff9a63c8d79 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; }; } @@ -311,7 +311,7 @@ impl Context { #[cfg(test)] mod test { - use std::rc::Rc; + use std::sync::Arc; use crate::ssa::{ function_builder::FunctionBuilder, @@ -509,7 +509,7 @@ mod test { let one = builder.field_constant(1u128); let v1 = builder.insert_binary(v0, BinaryOp::Add, one); - let array_type = Type::Array(Rc::new(vec![Type::field()]), 1); + let array_type = Type::Array(Arc::new(vec![Type::field()]), 1); let arr = builder.current_function.dfg.make_array(vec![v1].into(), array_type); builder.terminate_with_return(vec![arr]); @@ -601,7 +601,7 @@ mod test { // Compiling main let mut builder = FunctionBuilder::new("main".into(), main_id); - let v0 = builder.add_parameter(Type::Array(Rc::new(vec![Type::field()]), 4)); + let v0 = builder.add_parameter(Type::Array(Arc::new(vec![Type::field()]), 4)); let v1 = builder.add_parameter(Type::unsigned(32)); let v2 = builder.add_parameter(Type::unsigned(1)); let v3 = builder.add_parameter(Type::unsigned(1)); @@ -737,7 +737,7 @@ mod test { let zero = builder.field_constant(0u128); let one = builder.field_constant(1u128); - let typ = Type::Array(Rc::new(vec![Type::field()]), 2); + let typ = Type::Array(Arc::new(vec![Type::field()]), 2); let array = builder.array_constant(vec![zero, one].into(), typ); let _v2 = builder.insert_array_get(array, v1, Type::field()); @@ -787,7 +787,7 @@ mod test { let v0 = builder.add_parameter(Type::bool()); let v1 = builder.add_parameter(Type::bool()); - let v2 = builder.add_parameter(Type::Array(Rc::new(vec![Type::field()]), 2)); + let v2 = builder.add_parameter(Type::Array(Arc::new(vec![Type::field()]), 2)); let zero = builder.numeric_constant(0u128, Type::length_type()); let one = builder.numeric_constant(1u128, Type::length_type()); 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 1aa0c2efbd0..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 @@ -245,7 +245,7 @@ impl Context { let instruction_id = *instruction_id; let instruction = &function.dfg[instruction_id]; - if let Instruction::EnableSideEffects { condition } = instruction { + if let Instruction::EnableSideEffectsIf { condition } = instruction { side_effects_condition = Some(*condition); // We still need to keep the EnableSideEffects instruction 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..d5fb98c7adc 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); } @@ -878,7 +878,7 @@ impl<'f> Context<'f> { #[cfg(test)] mod test { - use std::rc::Rc; + use std::sync::Arc; use acvm::acir::AcirField; @@ -1016,7 +1016,7 @@ mod test { let b2 = builder.insert_block(); let v0 = builder.add_parameter(Type::bool()); - let v1 = builder.add_parameter(Type::Reference(Rc::new(Type::field()))); + let v1 = builder.add_parameter(Type::Reference(Arc::new(Type::field()))); builder.terminate_with_jmpif(v0, b1, b2); @@ -1078,7 +1078,7 @@ mod test { let b3 = builder.insert_block(); let v0 = builder.add_parameter(Type::bool()); - let v1 = builder.add_parameter(Type::Reference(Rc::new(Type::field()))); + let v1 = builder.add_parameter(Type::Reference(Arc::new(Type::field()))); builder.terminate_with_jmpif(v0, b1, b2); @@ -1477,7 +1477,7 @@ mod test { let b2 = builder.insert_block(); let b3 = builder.insert_block(); - let element_type = Rc::new(vec![Type::unsigned(8)]); + let element_type = Arc::new(vec![Type::unsigned(8)]); let array_type = Type::Array(element_type.clone(), 2); let array = builder.add_parameter(array_type); 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 e5a25dcfef1..9d6582c0db7 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 @@ -425,7 +425,7 @@ impl<'f> PerFunctionContext<'f> { #[cfg(test)] mod tests { - use std::rc::Rc; + use std::sync::Arc; use acvm::{acir::AcirField, FieldElement}; use im::vector; @@ -454,11 +454,11 @@ mod tests { let func_id = Id::test_new(0); let mut builder = FunctionBuilder::new("func".into(), func_id); - let v0 = builder.insert_allocate(Type::Array(Rc::new(vec![Type::field()]), 2)); + let v0 = builder.insert_allocate(Type::Array(Arc::new(vec![Type::field()]), 2)); let one = builder.field_constant(FieldElement::one()); let two = builder.field_constant(FieldElement::one()); - let element_type = Rc::new(vec![Type::field()]); + let element_type = Arc::new(vec![Type::field()]); let array_type = Type::Array(element_type, 2); let array = builder.array_constant(vector![one, two], array_type.clone()); @@ -672,7 +672,7 @@ mod tests { let zero = builder.field_constant(0u128); builder.insert_store(v0, zero); - let v2 = builder.insert_allocate(Type::Reference(Rc::new(Type::field()))); + let v2 = builder.insert_allocate(Type::Reference(Arc::new(Type::field()))); builder.insert_store(v2, v0); let v3 = builder.insert_load(v2, Type::field()); diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/rc.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/rc.rs index 1561547e32e..4f109a27874 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/rc.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/rc.rs @@ -161,7 +161,7 @@ fn remove_instructions(to_remove: HashSet, function: &mut Functio #[cfg(test)] mod test { - use std::rc::Rc; + use std::sync::Arc; use crate::ssa::{ function_builder::FunctionBuilder, @@ -209,14 +209,14 @@ mod test { let mut builder = FunctionBuilder::new("foo".into(), main_id); builder.set_runtime(RuntimeType::Brillig); - let inner_array_type = Type::Array(Rc::new(vec![Type::field()]), 2); + let inner_array_type = Type::Array(Arc::new(vec![Type::field()]), 2); let v0 = builder.add_parameter(inner_array_type.clone()); builder.insert_inc_rc(v0); builder.insert_inc_rc(v0); builder.insert_dec_rc(v0); - let outer_array_type = Type::Array(Rc::new(vec![inner_array_type]), 1); + let outer_array_type = Type::Array(Arc::new(vec![inner_array_type]), 1); let array = builder.array_constant(vec![v0].into(), outer_array_type); builder.terminate_with_return(vec![array]); @@ -248,7 +248,7 @@ mod test { let main_id = Id::test_new(0); let mut builder = FunctionBuilder::new("mutator".into(), main_id); - let array_type = Type::Array(Rc::new(vec![Type::field()]), 2); + let array_type = Type::Array(Arc::new(vec![Type::field()]), 2); let v0 = builder.add_parameter(array_type.clone()); let v1 = builder.insert_allocate(array_type.clone()); @@ -297,8 +297,8 @@ mod test { let main_id = Id::test_new(0); let mut builder = FunctionBuilder::new("mutator2".into(), main_id); - let array_type = Type::Array(Rc::new(vec![Type::field()]), 2); - let reference_type = Type::Reference(Rc::new(array_type.clone())); + let array_type = Type::Array(Arc::new(vec![Type::field()]), 2); + let reference_type = Type::Reference(Arc::new(array_type.clone())); let v0 = builder.add_parameter(reference_type); diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs index 628e1bd7410..6ca7a76d740 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, rc::Rc}; +use std::{borrow::Cow, sync::Arc}; use acvm::{acir::AcirField, FieldElement}; @@ -174,7 +174,7 @@ impl Context<'_> { let to_bits = self.function.dfg.import_intrinsic(Intrinsic::ToBits(Endian::Little)); let length = self.field_constant(FieldElement::from(bit_size as i128)); let result_types = - vec![Type::field(), Type::Array(Rc::new(vec![Type::bool()]), bit_size as usize)]; + vec![Type::field(), Type::Array(Arc::new(vec![Type::bool()]), bit_size as usize)]; let rhs_bits = self.insert_call(to_bits, vec![rhs, length], result_types); let rhs_bits = rhs_bits[1]; 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 13e5c2445ad..fb7091a8854 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 @@ -1,5 +1,4 @@ -use std::rc::Rc; -use std::sync::{Mutex, RwLock}; +use std::sync::{Arc, Mutex, RwLock}; use acvm::{acir::AcirField, FieldElement}; use iter_extended::vecmap; @@ -198,7 +197,7 @@ impl<'a> FunctionContext<'a> { // A mutable reference wraps each element into a reference. // This can be multiple values if the element type is a tuple. ast::Type::MutableReference(element) => { - Self::map_type_helper(element, &mut |typ| f(Type::Reference(Rc::new(typ)))) + Self::map_type_helper(element, &mut |typ| f(Type::Reference(Arc::new(typ)))) } ast::Type::FmtString(len, fields) => { // A format string is represented by multiple values @@ -213,7 +212,7 @@ impl<'a> FunctionContext<'a> { let element_types = Self::convert_type(elements).flatten(); Tree::Branch(vec![ Tree::Leaf(f(Type::length_type())), - Tree::Leaf(f(Type::Slice(Rc::new(element_types)))), + Tree::Leaf(f(Type::Slice(Arc::new(element_types)))), ]) } other => Tree::Leaf(f(Self::convert_non_tuple_type(other))), @@ -237,7 +236,7 @@ impl<'a> FunctionContext<'a> { ast::Type::Field => Type::field(), ast::Type::Array(len, element) => { let element_types = Self::convert_type(element).flatten(); - Type::Array(Rc::new(element_types), *len as usize) + Type::Array(Arc::new(element_types), *len as usize) } ast::Type::Integer(Signedness::Signed, bits) => Type::signed((*bits).into()), ast::Type::Integer(Signedness::Unsigned, bits) => Type::unsigned((*bits).into()), @@ -253,7 +252,7 @@ impl<'a> FunctionContext<'a> { ast::Type::MutableReference(element) => { // Recursive call to panic if element is a tuple let element = Self::convert_non_tuple_type(element); - Type::Reference(Rc::new(element)) + Type::Reference(Arc::new(element)) } } } 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 dc07f55ee33..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,7 +7,7 @@ use crate::ast::{ }; use crate::hir::def_collector::errors::DefCollectorErrorKind; use crate::macros_api::StructId; -use crate::node_interner::{ExprId, QuotedTypeId}; +use crate::node_interner::{ExprId, InternedExpressionKind, QuotedTypeId}; use crate::token::{Attributes, FunctionAttribute, Token, Tokens}; use crate::{Kind, Type}; use acvm::{acir::AcirField, FieldElement}; @@ -43,6 +43,11 @@ pub enum ExpressionKind { // 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, } @@ -603,6 +608,7 @@ impl Display for ExpressionKind { 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); 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 781630571ef..e63222bfc87 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/mod.rs @@ -10,6 +10,9 @@ mod statement; mod structure; mod traits; mod type_alias; +mod visitor; + +pub use visitor::Visitor; pub use expression::*; pub use function::*; @@ -22,7 +25,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 +115,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), @@ -141,6 +144,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, } @@ -151,6 +158,46 @@ pub struct UnresolvedType { 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), + + /// 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 pub struct UnaryRhsMemberAccess { pub method_or_field: Ident, @@ -176,6 +223,7 @@ pub enum UnresolvedTypeExpression { Box, Span, ), + AsTraitPath(Box), } impl Recoverable for UnresolvedType { @@ -184,6 +232,32 @@ impl Recoverable for UnresolvedType { } } +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}>") + } + } +} + impl std::fmt::Display for UnresolvedTypeData { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { use UnresolvedTypeData::*; @@ -195,22 +269,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(", ")) @@ -244,6 +304,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}"), } } @@ -263,6 +324,7 @@ impl std::fmt::Display for UnresolvedTypeExpression { UnresolvedTypeExpression::BinaryOperation(lhs, op, rhs, _) => { write!(f, "({lhs} {op} {rhs})") } + UnresolvedTypeExpression::AsTraitPath(path) => write!(f, "{path}"), } } } @@ -334,6 +396,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()) + } } } @@ -376,6 +441,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 18033197079..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, 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,6 +101,9 @@ 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 @@ -371,6 +378,7 @@ impl UseTree { pub struct AsTraitPath { pub typ: UnresolvedType, pub trait_path: Path, + pub trait_generics: GenericTypeArgs, pub impl_item: Ident, } @@ -533,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)] @@ -590,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: _ } => { @@ -611,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, } } } @@ -776,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"), } } @@ -808,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/ast/visitor.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/visitor.rs new file mode 100644 index 00000000000..96183d3322f --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/visitor.rs @@ -0,0 +1,1301 @@ +use acvm::FieldElement; +use noirc_errors::Span; + +use crate::{ + ast::{ + ArrayLiteral, AsTraitPath, AssignStatement, BlockExpression, CallExpression, + CastExpression, ConstrainStatement, ConstructorExpression, Expression, ExpressionKind, + ForLoopStatement, ForRange, Ident, IfExpression, IndexExpression, InfixExpression, LValue, + Lambda, LetStatement, Literal, MemberAccessExpression, MethodCallExpression, + ModuleDeclaration, NoirFunction, NoirStruct, NoirTrait, NoirTraitImpl, NoirTypeAlias, Path, + PrefixExpression, Statement, StatementKind, TraitImplItem, TraitItem, TypeImpl, UseTree, + UseTreeKind, + }, + node_interner::{ + ExprId, InternedExpressionKind, InternedStatementKind, InternedUnresolvedTypeData, + QuotedTypeId, + }, + parser::{Item, ItemKind, ParsedSubModule}, + token::Tokens, + ParsedModule, QuotedType, +}; + +use super::{ + FunctionReturnType, GenericTypeArgs, IntegerBitSize, Pattern, Signedness, UnresolvedGenerics, + UnresolvedTraitConstraint, UnresolvedType, UnresolvedTypeData, UnresolvedTypeExpression, +}; + +/// Implements the [Visitor pattern](https://en.wikipedia.org/wiki/Visitor_pattern) for Noir's AST. +/// +/// In this implementation, methods must return a bool: +/// - true means children must be visited +/// - false means children must not be visited, either because the visitor implementation +/// will visit children of interest manually, or because no children are of interest +pub trait Visitor { + fn visit_parsed_module(&mut self, _: &ParsedModule) -> bool { + true + } + + fn visit_item(&mut self, _: &Item) -> bool { + true + } + + fn visit_parsed_submodule(&mut self, _: &ParsedSubModule, _: Span) -> bool { + true + } + + fn visit_noir_function(&mut self, _: &NoirFunction, _: Span) -> bool { + true + } + + fn visit_noir_trait_impl(&mut self, _: &NoirTraitImpl, _: Span) -> bool { + true + } + + fn visit_type_impl(&mut self, _: &TypeImpl, _: Span) -> bool { + true + } + + fn visit_trait_impl_item(&mut self, _: &TraitImplItem) -> bool { + true + } + + fn visit_trait_impl_item_function(&mut self, _: &NoirFunction, _span: Span) -> bool { + true + } + + fn visit_trait_impl_item_constant( + &mut self, + _name: &Ident, + _typ: &UnresolvedType, + _expression: &Expression, + ) -> bool { + true + } + + fn visit_trait_impl_item_type(&mut self, _name: &Ident, _alias: &UnresolvedType) -> bool { + true + } + + fn visit_noir_trait(&mut self, _: &NoirTrait, _: Span) -> bool { + true + } + + fn visit_trait_item(&mut self, _: &TraitItem) -> bool { + true + } + + fn visit_trait_item_function( + &mut self, + _name: &Ident, + _generics: &UnresolvedGenerics, + _parameters: &[(Ident, UnresolvedType)], + _return_type: &FunctionReturnType, + _where_clause: &[UnresolvedTraitConstraint], + _body: &Option, + ) -> bool { + true + } + + fn visit_trait_item_constant( + &mut self, + _name: &Ident, + _typ: &UnresolvedType, + _default_value: &Option, + ) -> bool { + true + } + + fn visit_trait_item_type(&mut self, _: &Ident) {} + + fn visit_use_tree(&mut self, _: &UseTree) -> bool { + true + } + + fn visit_use_tree_path(&mut self, _: &UseTree, _ident: &Ident, _alias: &Option) {} + + fn visit_use_tree_list(&mut self, _: &UseTree, _: &[UseTree]) -> bool { + true + } + + fn visit_noir_struct(&mut self, _: &NoirStruct, _: Span) -> bool { + true + } + + fn visit_noir_type_alias(&mut self, _: &NoirTypeAlias, _: Span) -> bool { + true + } + + fn visit_module_declaration(&mut self, _: &ModuleDeclaration, _: Span) {} + + fn visit_expression(&mut self, _: &Expression) -> bool { + true + } + + fn visit_literal(&mut self, _: &Literal, _: Span) -> bool { + true + } + + fn visit_literal_array(&mut self, _: &ArrayLiteral) -> bool { + true + } + + fn visit_literal_slice(&mut self, _: &ArrayLiteral) -> bool { + true + } + + fn visit_literal_bool(&mut self, _: bool) {} + + fn visit_literal_integer(&mut self, _value: FieldElement, _negative: bool) {} + + fn visit_literal_str(&mut self, _: &str) {} + + fn visit_literal_raw_str(&mut self, _: &str, _: u8) {} + + fn visit_literal_fmt_str(&mut self, _: &str) {} + + fn visit_literal_unit(&mut self) {} + + fn visit_block_expression(&mut self, _: &BlockExpression, _: Option) -> bool { + true + } + + fn visit_prefix_expression(&mut self, _: &PrefixExpression, _: Span) -> bool { + true + } + + fn visit_index_expression(&mut self, _: &IndexExpression, _: Span) -> bool { + true + } + + fn visit_call_expression(&mut self, _: &CallExpression, _: Span) -> bool { + true + } + + fn visit_method_call_expression(&mut self, _: &MethodCallExpression, _: Span) -> bool { + true + } + + fn visit_constructor_expression(&mut self, _: &ConstructorExpression, _: Span) -> bool { + true + } + + fn visit_member_access_expression(&mut self, _: &MemberAccessExpression, _: Span) -> bool { + true + } + + fn visit_cast_expression(&mut self, _: &CastExpression, _: Span) -> bool { + true + } + + fn visit_infix_expression(&mut self, _: &InfixExpression, _: Span) -> bool { + true + } + + fn visit_if_expression(&mut self, _: &IfExpression, _: Span) -> bool { + true + } + + fn visit_tuple(&mut self, _: &[Expression], _: Span) -> bool { + true + } + + fn visit_parenthesized(&mut self, _: &Expression, _: Span) -> bool { + true + } + + fn visit_unquote(&mut self, _: &Expression, _: Span) -> bool { + true + } + + fn visit_comptime_expression(&mut self, _: &BlockExpression, _: Span) -> bool { + true + } + + fn visit_unsafe(&mut self, _: &BlockExpression, _: Span) -> bool { + true + } + + fn visit_variable(&mut self, _: &Path, _: Span) -> bool { + true + } + + fn visit_quote(&mut self, _: &Tokens) {} + + fn visit_resolved_expression(&mut self, _expr_id: ExprId) {} + + fn visit_interned_expression(&mut self, _id: InternedExpressionKind) {} + + fn visit_error_expression(&mut self) {} + + fn visit_lambda(&mut self, _: &Lambda, _: Span) -> bool { + true + } + + fn visit_array_literal(&mut self, _: &ArrayLiteral) -> bool { + true + } + + fn visit_array_literal_standard(&mut self, _: &[Expression]) -> bool { + true + } + + fn visit_array_literal_repeated( + &mut self, + _repeated_element: &Expression, + _length: &Expression, + ) -> bool { + true + } + + fn visit_statement(&mut self, _: &Statement) -> bool { + true + } + + fn visit_import(&mut self, _: &UseTree) -> bool { + true + } + + fn visit_global(&mut self, _: &LetStatement, _: Span) -> bool { + true + } + + fn visit_let_statement(&mut self, _: &LetStatement) -> bool { + true + } + + fn visit_constrain_statement(&mut self, _: &ConstrainStatement) -> bool { + true + } + + fn visit_assign_statement(&mut self, _: &AssignStatement) -> bool { + true + } + + fn visit_for_loop_statement(&mut self, _: &ForLoopStatement) -> bool { + true + } + + fn visit_comptime_statement(&mut self, _: &Statement) -> bool { + true + } + + fn visit_break(&mut self) {} + + fn visit_continue(&mut self) {} + + fn visit_interned_statement(&mut self, _: InternedStatementKind) {} + + fn visit_error_statement(&mut self) {} + + fn visit_lvalue(&mut self, _: &LValue) -> bool { + true + } + + fn visit_lvalue_ident(&mut self, _: &Ident) {} + + fn visit_lvalue_member_access( + &mut self, + _object: &LValue, + _field_name: &Ident, + _span: Span, + ) -> bool { + true + } + + fn visit_lvalue_index(&mut self, _array: &LValue, _index: &Expression, _span: Span) -> bool { + true + } + + fn visit_lvalue_dereference(&mut self, _lvalue: &LValue, _span: Span) -> bool { + true + } + + fn visit_lvalue_interned(&mut self, _id: InternedExpressionKind, _span: Span) {} + + fn visit_for_range(&mut self, _: &ForRange) -> bool { + true + } + + fn visit_as_trait_path(&mut self, _: &AsTraitPath, _: Span) -> bool { + true + } + + fn visit_unresolved_type(&mut self, _: &UnresolvedType) -> bool { + true + } + + fn visit_array_type( + &mut self, + _: &UnresolvedTypeExpression, + _: &UnresolvedType, + _: Span, + ) -> bool { + true + } + + fn visit_slice_type(&mut self, _: &UnresolvedType, _: Span) -> bool { + true + } + + fn visit_parenthesized_type(&mut self, _: &UnresolvedType, _: Span) -> bool { + true + } + + fn visit_named_type(&mut self, _: &Path, _: &GenericTypeArgs, _: Span) -> bool { + true + } + + fn visit_trait_as_type(&mut self, _: &Path, _: &GenericTypeArgs, _: Span) -> bool { + true + } + + fn visit_mutable_reference_type(&mut self, _: &UnresolvedType, _: Span) -> bool { + true + } + + fn visit_tuple_type(&mut self, _: &[UnresolvedType], _: Span) -> bool { + true + } + + fn visit_function_type( + &mut self, + _args: &[UnresolvedType], + _ret: &UnresolvedType, + _env: &UnresolvedType, + _unconstrained: bool, + _span: Span, + ) -> bool { + true + } + + fn visit_as_trait_path_type(&mut self, _: &AsTraitPath, _: Span) -> bool { + true + } + + fn visit_expression_type(&mut self, _: &UnresolvedTypeExpression, _: Span) {} + + fn visit_format_string_type( + &mut self, + _: &UnresolvedTypeExpression, + _: &UnresolvedType, + _: Span, + ) -> bool { + true + } + + fn visit_string_type(&mut self, _: &UnresolvedTypeExpression, _: Span) {} + + fn visit_unspecified_type(&mut self, _: Span) {} + + fn visit_quoted_type(&mut self, _: &QuotedType, _: Span) {} + + fn visit_field_element_type(&mut self, _: Span) {} + + fn visit_integer_type(&mut self, _: Signedness, _: IntegerBitSize, _: Span) {} + + fn visit_bool_type(&mut self, _: Span) {} + + fn visit_unit_type(&mut self, _: Span) {} + + fn visit_resolved_type(&mut self, _: QuotedTypeId, _: Span) {} + + fn visit_interned_type(&mut self, _: InternedUnresolvedTypeData, _: Span) {} + + fn visit_error_type(&mut self, _: Span) {} + + fn visit_path(&mut self, _: &Path) {} + + fn visit_generic_type_args(&mut self, _: &GenericTypeArgs) -> bool { + true + } + + fn visit_function_return_type(&mut self, _: &FunctionReturnType) -> bool { + true + } + + fn visit_pattern(&mut self, _: &Pattern) -> bool { + true + } + + fn visit_identifier_pattern(&mut self, _: &Ident) {} + + fn visit_mutable_pattern(&mut self, _: &Pattern, _: Span, _is_synthesized: bool) -> bool { + true + } + + fn visit_tuple_pattern(&mut self, _: &[Pattern], _: Span) -> bool { + true + } + + fn visit_struct_pattern(&mut self, _: &Path, _: &[(Ident, Pattern)], _: Span) -> bool { + true + } +} + +impl ParsedModule { + pub fn accept(&self, visitor: &mut impl Visitor) { + if visitor.visit_parsed_module(self) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + for item in &self.items { + item.accept(visitor); + } + } +} + +impl Item { + pub fn accept(&self, visitor: &mut impl Visitor) { + if visitor.visit_item(self) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + match &self.kind { + ItemKind::Submodules(parsed_sub_module) => { + parsed_sub_module.accept(self.span, visitor); + } + ItemKind::Function(noir_function) => noir_function.accept(self.span, visitor), + ItemKind::TraitImpl(noir_trait_impl) => { + noir_trait_impl.accept(self.span, visitor); + } + ItemKind::Impl(type_impl) => type_impl.accept(self.span, visitor), + ItemKind::Global(let_statement) => { + if visitor.visit_global(let_statement, self.span) { + let_statement.accept(visitor); + } + } + ItemKind::Trait(noir_trait) => noir_trait.accept(self.span, visitor), + ItemKind::Import(use_tree) => { + if visitor.visit_import(use_tree) { + use_tree.accept(visitor); + } + } + ItemKind::TypeAlias(noir_type_alias) => noir_type_alias.accept(self.span, visitor), + ItemKind::Struct(noir_struct) => noir_struct.accept(self.span, visitor), + ItemKind::ModuleDecl(module_declaration) => { + module_declaration.accept(self.span, visitor); + } + } + } +} + +impl ParsedSubModule { + pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { + if visitor.visit_parsed_submodule(self, span) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + self.contents.accept(visitor); + } +} + +impl NoirFunction { + pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { + if visitor.visit_noir_function(self, span) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + for param in &self.def.parameters { + param.typ.accept(visitor); + } + + self.def.body.accept(None, visitor); + } +} + +impl NoirTraitImpl { + pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { + if visitor.visit_noir_trait_impl(self, span) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + self.trait_name.accept(visitor); + self.object_type.accept(visitor); + + for item in &self.items { + item.accept(visitor); + } + } +} + +impl TraitImplItem { + pub fn accept(&self, visitor: &mut impl Visitor) { + if visitor.visit_trait_impl_item(self) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + match self { + TraitImplItem::Function(noir_function) => { + let span = Span::from( + noir_function.name_ident().span().start()..noir_function.span().end(), + ); + + if visitor.visit_trait_impl_item_function(noir_function, span) { + noir_function.accept(span, visitor); + } + } + TraitImplItem::Constant(name, unresolved_type, expression) => { + if visitor.visit_trait_impl_item_constant(name, unresolved_type, expression) { + unresolved_type.accept(visitor); + expression.accept(visitor); + } + } + TraitImplItem::Type { name, alias } => { + if visitor.visit_trait_impl_item_type(name, alias) { + alias.accept(visitor); + } + } + } + } +} + +impl TypeImpl { + pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { + if visitor.visit_type_impl(self, span) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + self.object_type.accept(visitor); + + for (method, span) in &self.methods { + method.accept(*span, visitor); + } + } +} + +impl NoirTrait { + pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { + if visitor.visit_noir_trait(self, span) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + for item in &self.items { + item.accept(visitor); + } + } +} + +impl TraitItem { + pub fn accept(&self, visitor: &mut impl Visitor) { + if visitor.visit_trait_item(self) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + match self { + TraitItem::Function { name, generics, parameters, return_type, where_clause, body } => { + if visitor.visit_trait_item_function( + name, + generics, + parameters, + return_type, + where_clause, + body, + ) { + for (_name, unresolved_type) in parameters { + unresolved_type.accept(visitor); + } + + return_type.accept(visitor); + + for unresolved_trait_constraint in where_clause { + unresolved_trait_constraint.typ.accept(visitor); + } + + if let Some(body) = body { + body.accept(None, visitor); + } + } + } + TraitItem::Constant { name, typ, default_value } => { + if visitor.visit_trait_item_constant(name, typ, default_value) { + typ.accept(visitor); + + if let Some(default_value) = default_value { + default_value.accept(visitor); + } + } + } + TraitItem::Type { name } => visitor.visit_trait_item_type(name), + } + } +} + +impl UseTree { + pub fn accept(&self, visitor: &mut impl Visitor) { + if visitor.visit_use_tree(self) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + match &self.kind { + UseTreeKind::Path(ident, alias) => visitor.visit_use_tree_path(self, ident, alias), + UseTreeKind::List(use_trees) => { + if visitor.visit_use_tree_list(self, use_trees) { + for use_tree in use_trees { + use_tree.accept(visitor); + } + } + } + } + } +} + +impl NoirStruct { + pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { + if visitor.visit_noir_struct(self, span) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + for (_name, unresolved_type) in &self.fields { + unresolved_type.accept(visitor); + } + } +} + +impl NoirTypeAlias { + pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { + if visitor.visit_noir_type_alias(self, span) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + self.typ.accept(visitor); + } +} + +impl ModuleDeclaration { + pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { + visitor.visit_module_declaration(self, span); + } +} + +impl Expression { + pub fn accept(&self, visitor: &mut impl Visitor) { + if visitor.visit_expression(self) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + match &self.kind { + ExpressionKind::Literal(literal) => literal.accept(self.span, visitor), + ExpressionKind::Block(block_expression) => { + block_expression.accept(Some(self.span), visitor); + } + ExpressionKind::Prefix(prefix_expression) => { + prefix_expression.accept(self.span, visitor); + } + ExpressionKind::Index(index_expression) => { + index_expression.accept(self.span, visitor); + } + ExpressionKind::Call(call_expression) => { + call_expression.accept(self.span, visitor); + } + ExpressionKind::MethodCall(method_call_expression) => { + method_call_expression.accept(self.span, visitor); + } + ExpressionKind::Constructor(constructor_expression) => { + constructor_expression.accept(self.span, visitor); + } + ExpressionKind::MemberAccess(member_access_expression) => { + member_access_expression.accept(self.span, visitor); + } + ExpressionKind::Cast(cast_expression) => { + cast_expression.accept(self.span, visitor); + } + ExpressionKind::Infix(infix_expression) => { + infix_expression.accept(self.span, visitor); + } + ExpressionKind::If(if_expression) => { + if_expression.accept(self.span, visitor); + } + ExpressionKind::Tuple(expressions) => { + if visitor.visit_tuple(expressions, self.span) { + visit_expressions(expressions, visitor); + } + } + ExpressionKind::Lambda(lambda) => lambda.accept(self.span, visitor), + ExpressionKind::Parenthesized(expression) => { + if visitor.visit_parenthesized(expression, self.span) { + expression.accept(visitor); + } + } + ExpressionKind::Unquote(expression) => { + if visitor.visit_unquote(expression, self.span) { + expression.accept(visitor); + } + } + ExpressionKind::Comptime(block_expression, _) => { + if visitor.visit_comptime_expression(block_expression, self.span) { + block_expression.accept(None, visitor); + } + } + ExpressionKind::Unsafe(block_expression, _) => { + if visitor.visit_unsafe(block_expression, self.span) { + block_expression.accept(None, visitor); + } + } + ExpressionKind::Variable(path) => { + if visitor.visit_variable(path, self.span) { + path.accept(visitor); + } + } + ExpressionKind::AsTraitPath(as_trait_path) => { + as_trait_path.accept(self.span, visitor); + } + ExpressionKind::Quote(tokens) => visitor.visit_quote(tokens), + ExpressionKind::Resolved(expr_id) => visitor.visit_resolved_expression(*expr_id), + ExpressionKind::Interned(id) => visitor.visit_interned_expression(*id), + ExpressionKind::Error => visitor.visit_error_expression(), + } + } +} + +impl Literal { + pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { + if visitor.visit_literal(self, span) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + match self { + Literal::Array(array_literal) => { + if visitor.visit_literal_array(array_literal) { + array_literal.accept(visitor); + } + } + Literal::Slice(array_literal) => { + if visitor.visit_literal_slice(array_literal) { + array_literal.accept(visitor); + } + } + Literal::Bool(value) => visitor.visit_literal_bool(*value), + Literal::Integer(value, negative) => visitor.visit_literal_integer(*value, *negative), + Literal::Str(str) => visitor.visit_literal_str(str), + Literal::RawStr(str, length) => visitor.visit_literal_raw_str(str, *length), + Literal::FmtStr(str) => visitor.visit_literal_fmt_str(str), + Literal::Unit => visitor.visit_literal_unit(), + } + } +} + +impl BlockExpression { + pub fn accept(&self, span: Option, visitor: &mut impl Visitor) { + if visitor.visit_block_expression(self, span) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + for statement in &self.statements { + statement.accept(visitor); + } + } +} + +impl PrefixExpression { + pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { + if visitor.visit_prefix_expression(self, span) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + self.rhs.accept(visitor); + } +} + +impl IndexExpression { + pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { + if visitor.visit_index_expression(self, span) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + self.collection.accept(visitor); + self.index.accept(visitor); + } +} + +impl CallExpression { + pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { + if visitor.visit_call_expression(self, span) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + self.func.accept(visitor); + visit_expressions(&self.arguments, visitor); + } +} + +impl MethodCallExpression { + pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { + if visitor.visit_method_call_expression(self, span) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + self.object.accept(visitor); + visit_expressions(&self.arguments, visitor); + } +} + +impl ConstructorExpression { + pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { + if visitor.visit_constructor_expression(self, span) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + self.type_name.accept(visitor); + + for (_field_name, expression) in &self.fields { + expression.accept(visitor); + } + } +} + +impl MemberAccessExpression { + pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { + if visitor.visit_member_access_expression(self, span) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + self.lhs.accept(visitor); + } +} + +impl CastExpression { + pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { + if visitor.visit_cast_expression(self, span) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + self.lhs.accept(visitor); + } +} + +impl InfixExpression { + pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { + if visitor.visit_infix_expression(self, span) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + self.lhs.accept(visitor); + self.rhs.accept(visitor); + } +} + +impl IfExpression { + pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { + if visitor.visit_if_expression(self, span) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + self.condition.accept(visitor); + self.consequence.accept(visitor); + if let Some(alternative) = &self.alternative { + alternative.accept(visitor); + } + } +} + +impl Lambda { + pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { + if visitor.visit_lambda(self, span) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + for (_, unresolved_type) in &self.parameters { + unresolved_type.accept(visitor); + } + + self.body.accept(visitor); + } +} + +impl ArrayLiteral { + pub fn accept(&self, visitor: &mut impl Visitor) { + if visitor.visit_array_literal(self) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + match self { + ArrayLiteral::Standard(expressions) => { + if visitor.visit_array_literal_standard(expressions) { + visit_expressions(expressions, visitor); + } + } + ArrayLiteral::Repeated { repeated_element, length } => { + if visitor.visit_array_literal_repeated(repeated_element, length) { + repeated_element.accept(visitor); + length.accept(visitor); + } + } + } + } +} + +impl Statement { + pub fn accept(&self, visitor: &mut impl Visitor) { + if visitor.visit_statement(self) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + match &self.kind { + StatementKind::Let(let_statement) => { + let_statement.accept(visitor); + } + StatementKind::Constrain(constrain_statement) => { + constrain_statement.accept(visitor); + } + StatementKind::Expression(expression) => { + expression.accept(visitor); + } + StatementKind::Assign(assign_statement) => { + assign_statement.accept(visitor); + } + StatementKind::For(for_loop_statement) => { + for_loop_statement.accept(visitor); + } + StatementKind::Comptime(statement) => { + if visitor.visit_comptime_statement(statement) { + statement.accept(visitor); + } + } + StatementKind::Semi(expression) => { + expression.accept(visitor); + } + StatementKind::Break => visitor.visit_break(), + StatementKind::Continue => visitor.visit_continue(), + StatementKind::Interned(id) => visitor.visit_interned_statement(*id), + StatementKind::Error => visitor.visit_error_statement(), + } + } +} + +impl LetStatement { + pub fn accept(&self, visitor: &mut impl Visitor) { + if visitor.visit_let_statement(self) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + self.pattern.accept(visitor); + self.r#type.accept(visitor); + self.expression.accept(visitor); + } +} + +impl ConstrainStatement { + pub fn accept(&self, visitor: &mut impl Visitor) { + if visitor.visit_constrain_statement(self) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + self.0.accept(visitor); + + if let Some(exp) = &self.1 { + exp.accept(visitor); + } + } +} + +impl AssignStatement { + pub fn accept(&self, visitor: &mut impl Visitor) { + if visitor.visit_assign_statement(self) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + self.lvalue.accept(visitor); + self.expression.accept(visitor); + } +} + +impl ForLoopStatement { + pub fn accept(&self, visitor: &mut impl Visitor) { + if visitor.visit_for_loop_statement(self) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + self.range.accept(visitor); + self.block.accept(visitor); + } +} + +impl LValue { + pub fn accept(&self, visitor: &mut impl Visitor) { + if visitor.visit_lvalue(self) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + match self { + LValue::Ident(ident) => visitor.visit_lvalue_ident(ident), + LValue::MemberAccess { object, field_name, span } => { + if visitor.visit_lvalue_member_access(object, field_name, *span) { + object.accept(visitor); + } + } + LValue::Index { array, index, span } => { + if visitor.visit_lvalue_index(array, index, *span) { + array.accept(visitor); + index.accept(visitor); + } + } + LValue::Dereference(lvalue, span) => { + if visitor.visit_lvalue_dereference(lvalue, *span) { + lvalue.accept(visitor); + } + } + LValue::Interned(id, span) => visitor.visit_lvalue_interned(*id, *span), + } + } +} + +impl ForRange { + pub fn accept(&self, visitor: &mut impl Visitor) { + if visitor.visit_for_range(self) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + match self { + ForRange::Range(start, end) => { + start.accept(visitor); + end.accept(visitor); + } + ForRange::Array(expression) => expression.accept(visitor), + } + } +} + +impl AsTraitPath { + pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { + if visitor.visit_as_trait_path(self, span) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + self.trait_path.accept(visitor); + self.trait_generics.accept(visitor); + } +} + +impl UnresolvedType { + pub fn accept(&self, visitor: &mut impl Visitor) { + if visitor.visit_unresolved_type(self) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + match &self.typ { + UnresolvedTypeData::Array(unresolved_type_expression, unresolved_type) => { + if visitor.visit_array_type(unresolved_type_expression, unresolved_type, self.span) + { + unresolved_type.accept(visitor); + } + } + UnresolvedTypeData::Slice(unresolved_type) => { + if visitor.visit_slice_type(unresolved_type, self.span) { + unresolved_type.accept(visitor); + } + } + UnresolvedTypeData::Parenthesized(unresolved_type) => { + if visitor.visit_parenthesized_type(unresolved_type, self.span) { + unresolved_type.accept(visitor); + } + } + UnresolvedTypeData::Named(path, generic_type_args, _) => { + if visitor.visit_named_type(path, generic_type_args, self.span) { + path.accept(visitor); + generic_type_args.accept(visitor); + } + } + UnresolvedTypeData::TraitAsType(path, generic_type_args) => { + if visitor.visit_trait_as_type(path, generic_type_args, self.span) { + path.accept(visitor); + generic_type_args.accept(visitor); + } + } + UnresolvedTypeData::MutableReference(unresolved_type) => { + if visitor.visit_mutable_reference_type(unresolved_type, self.span) { + unresolved_type.accept(visitor); + } + } + UnresolvedTypeData::Tuple(unresolved_types) => { + if visitor.visit_tuple_type(unresolved_types, self.span) { + visit_unresolved_types(unresolved_types, visitor); + } + } + UnresolvedTypeData::Function(args, ret, env, unconstrained) => { + if visitor.visit_function_type(args, ret, env, *unconstrained, self.span) { + visit_unresolved_types(args, visitor); + ret.accept(visitor); + env.accept(visitor); + } + } + UnresolvedTypeData::AsTraitPath(as_trait_path) => { + if visitor.visit_as_trait_path_type(as_trait_path, self.span) { + as_trait_path.accept(self.span, visitor); + } + } + UnresolvedTypeData::Expression(expr) => visitor.visit_expression_type(expr, self.span), + UnresolvedTypeData::FormatString(expr, typ) => { + if visitor.visit_format_string_type(expr, typ, self.span) { + typ.accept(visitor); + } + } + UnresolvedTypeData::String(expr) => visitor.visit_string_type(expr, self.span), + UnresolvedTypeData::Unspecified => visitor.visit_unspecified_type(self.span), + UnresolvedTypeData::Quoted(typ) => visitor.visit_quoted_type(typ, self.span), + UnresolvedTypeData::FieldElement => visitor.visit_field_element_type(self.span), + UnresolvedTypeData::Integer(signdness, size) => { + visitor.visit_integer_type(*signdness, *size, self.span); + } + UnresolvedTypeData::Bool => visitor.visit_bool_type(self.span), + UnresolvedTypeData::Unit => visitor.visit_unit_type(self.span), + UnresolvedTypeData::Resolved(id) => visitor.visit_resolved_type(*id, self.span), + UnresolvedTypeData::Interned(id) => visitor.visit_interned_type(*id, self.span), + UnresolvedTypeData::Error => visitor.visit_error_type(self.span), + } + } +} + +impl Path { + pub fn accept(&self, visitor: &mut impl Visitor) { + visitor.visit_path(self); + } +} + +impl GenericTypeArgs { + pub fn accept(&self, visitor: &mut impl Visitor) { + if visitor.visit_generic_type_args(self) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + visit_unresolved_types(&self.ordered_args, visitor); + for (_name, typ) in &self.named_args { + typ.accept(visitor); + } + } +} + +impl FunctionReturnType { + pub fn accept(&self, visitor: &mut impl Visitor) { + if visitor.visit_function_return_type(self) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + match self { + FunctionReturnType::Default(_) => (), + FunctionReturnType::Ty(unresolved_type) => { + unresolved_type.accept(visitor); + } + } + } +} + +impl Pattern { + pub fn accept(&self, visitor: &mut impl Visitor) { + if visitor.visit_pattern(self) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + match self { + Pattern::Identifier(ident) => visitor.visit_identifier_pattern(ident), + Pattern::Mutable(pattern, span, is_synthesized) => { + if visitor.visit_mutable_pattern(pattern, *span, *is_synthesized) { + pattern.accept(visitor); + } + } + Pattern::Tuple(patterns, span) => { + if visitor.visit_tuple_pattern(patterns, *span) { + for pattern in patterns { + pattern.accept(visitor); + } + } + } + Pattern::Struct(path, fields, span) => { + if visitor.visit_struct_pattern(path, fields, *span) { + path.accept(visitor); + for (_, pattern) in fields { + pattern.accept(visitor); + } + } + } + } + } +} + +fn visit_expressions(expressions: &[Expression], visitor: &mut impl Visitor) { + for expression in expressions { + expression.accept(visitor); + } +} + +fn visit_unresolved_types(unresolved_type: &[UnresolvedType], visitor: &mut impl Visitor) { + for unresolved_type in unresolved_type { + unresolved_type.accept(visitor); + } +} 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 935acc4e6d0..fe027969473 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/debug/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/debug/mod.rs @@ -322,6 +322,9 @@ impl DebugInstrumenter { ast::LValue::Dereference(_ref, _span) => { unimplemented![] } + ast::LValue::Interned(..) => { + unimplemented![] + } } } build_assign_member_stmt( 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 2b78c02e53c..3e71f167802 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/comptime.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/comptime.rs @@ -46,6 +46,7 @@ impl<'context> Elaborator<'context> { self.crate_id, self.debug_comptime_in_file, self.enable_arithmetic_generics, + self.interpreter_call_stack.clone(), ); elaborator.function_context.push(FunctionContext::default()); @@ -167,7 +168,7 @@ impl<'context> Elaborator<'context> { /// Parses an attribute in the form of a function call (e.g. `#[foo(a b, c d)]`) into /// the function and quoted arguments called (e.g. `("foo", vec![(a b, location), (c d, location)])`) #[allow(clippy::type_complexity)] - fn parse_attribute( + pub(crate) fn parse_attribute( annotation: &str, file: FileId, ) -> Result)>, (CompilationError, FileId)> { @@ -284,13 +285,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, @@ -301,6 +303,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 65e94c4fcf4..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::{ @@ -62,6 +62,11 @@ impl<'context> Elaborator<'context> { 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 }); @@ -397,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, @@ -620,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); 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 bba87f9a934..e84ed76050d 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::{ @@ -122,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 @@ -165,6 +168,8 @@ pub struct Elaborator<'context> { /// Temporary flag to enable the experimental arithmetic generics feature enable_arithmetic_generics: bool, + + pub(crate) interpreter_call_stack: im::Vector, } #[derive(Default)] @@ -188,6 +193,7 @@ impl<'context> Elaborator<'context> { crate_id: CrateId, debug_comptime_in_file: Option, enable_arithmetic_generics: bool, + interpreter_call_stack: im::Vector, ) -> Self { Self { scopes: ScopeForest::default(), @@ -210,6 +216,8 @@ impl<'context> Elaborator<'context> { debug_comptime_in_file, unresolved_globals: BTreeMap::new(), enable_arithmetic_generics, + current_trait: None, + interpreter_call_stack, } } @@ -225,6 +233,7 @@ impl<'context> Elaborator<'context> { crate_id, debug_comptime_in_file, enable_arithmetic_generics, + im::Vector::new(), ) } @@ -486,7 +495,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, ); @@ -502,7 +512,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 { @@ -625,10 +635,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 @@ -641,9 +649,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); @@ -682,33 +688,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 }) } @@ -830,6 +816,11 @@ impl<'context> Elaborator<'context> { None }; + let attributes = func.secondary_attributes().iter(); + let attributes = + attributes.filter_map(|secondary_attribute| secondary_attribute.as_custom()); + let attributes = attributes.map(|str| str.to_string()).collect(); + let meta = FuncMeta { name: name_ident, kind: func.kind, @@ -853,6 +844,7 @@ impl<'context> Elaborator<'context> { function_body: FunctionBody::Unresolved(func.kind, body, func.def.span), self_type: self.self_type.clone(), source_file: self.file, + custom_attributes: attributes, }; self.interner.push_fn_meta(meta, func_id); @@ -1028,11 +1020,7 @@ impl<'context> Elaborator<'context> { 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); @@ -1050,9 +1038,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, }); @@ -1061,7 +1049,6 @@ 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, @@ -1381,7 +1368,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(); @@ -1391,24 +1378,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(); 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 da4492eb211..d7d330f891a 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/statements.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/statements.rs @@ -39,11 +39,16 @@ 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), } } - pub(super) fn elaborate_statement(&mut self, statement: Statement) -> (StmtId, Type) { + pub(crate) fn elaborate_statement(&mut self, statement: Statement) -> (StmtId, Type) { let span = statement.span; let (hir_statement, typ) = self.elaborate_statement_value(statement); let id = self.interner.push_stmt(hir_statement); @@ -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, expr_span, || { - 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(), @@ -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 20719b9f090..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); @@ -82,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); @@ -138,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 @@ -163,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, @@ -222,4 +221,26 @@ impl<'context> Elaborator<'context> { }); } } + + 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 52407746258..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( @@ -207,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 @@ -264,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(); @@ -291,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 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 f74d7449cd3..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}; @@ -115,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), @@ -148,17 +157,18 @@ 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); + } }; - let unresolved_span = typ.span; - let location = Location::new(named_path_span.unwrap_or(unresolved_span), self.file); - + 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, @@ -202,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; @@ -224,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); @@ -260,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) { @@ -274,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 @@ -289,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; @@ -313,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); + } - // Fix the generic count so we can continue typechecking - args.resize_with(expected_count, || Type::Error); + let ordered_args = expected_kinds.iter().zip(args.ordered_args); + let ordered = + vecmap(ordered_args, |(generic, typ)| self.resolve_type_inner(typ, &generic.kind)); + + 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 { @@ -360,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); @@ -387,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 { @@ -407,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 + } } } @@ -428,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 @@ -454,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)) } @@ -674,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 { @@ -957,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, @@ -974,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)) } @@ -1151,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( @@ -1248,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 } @@ -1283,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)); } } } @@ -1440,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, @@ -1491,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, + }); + } } } @@ -1567,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) => { @@ -1665,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..cfee6bcedac 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 @@ -56,6 +56,7 @@ pub enum InterpreterError { FailingConstraint { message: Option, location: Location, + call_stack: im::Vector, }, NoMethodFound { name: String, @@ -188,11 +189,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 +266,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) @@ -343,12 +354,20 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { let msg = format!("Expected a `bool` but found `{typ}`"); CustomDiagnostic::simple_error(msg, String::new(), location.span) } - InterpreterError::FailingConstraint { message, location } => { + InterpreterError::FailingConstraint { message, location, call_stack } => { let (primary, secondary) = match message { Some(msg) => (msg.clone(), "Assertion failed".into()), None => ("Assertion failed".into(), String::new()), }; - CustomDiagnostic::simple_error(primary, secondary, location.span) + let mut diagnostic = + CustomDiagnostic::simple_error(primary, secondary, location.span); + + // Only take at most 3 frames starting from the top of the stack to avoid producing too much output + for frame in call_stack.iter().rev().take(3) { + diagnostic.add_secondary_with_file("".to_string(), frame.span, frame.file); + } + + diagnostic } InterpreterError::NoMethodFound { name, typ, location } => { let msg = format!("No method named `{name}` found for type `{typ}`"); @@ -527,6 +546,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 07c5c1a0c77..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, }; @@ -300,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) } @@ -308,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) } @@ -335,13 +337,17 @@ 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, unconstrained) => { let args = vecmap(args, |arg| arg.to_display_ast()); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter.rs index 33f8c9d8332..4980045c68d 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 @@ -70,7 +70,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { current_function: Option, ) -> Self { let bound_generics = Vec::new(); - Self { elaborator, crate_id, current_function, bound_generics, in_loop: false } + let in_loop = false; + Self { elaborator, crate_id, current_function, bound_generics, in_loop } } pub(crate) fn call_function( @@ -99,8 +100,11 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { } self.remember_bindings(&instantiation_bindings, &impl_bindings); + self.elaborator.interpreter_call_stack.push_back(location); + let result = self.call_function_inner(function, arguments, location); + self.elaborator.interpreter_call_stack.pop_back(); undo_instantiation_bindings(impl_bindings); undo_instantiation_bindings(instantiation_bindings); self.rebind_generics_from_previous_function(); @@ -1462,7 +1466,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { let message = constrain.2.and_then(|expr| self.evaluate(expr).ok()); let message = message.map(|value| value.display(self.elaborator.interner).to_string()); - Err(InterpreterError::FailingConstraint { location, message }) + let call_stack = self.elaborator.interpreter_call_stack.clone(); + Err(InterpreterError::FailingConstraint { location, message, call_stack }) } value => { let location = self.elaborator.interner.expr_location(&constrain.0); 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 6ef4aee7531..d52d4ca8c71 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,37 @@ 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_trait_impl, get_tuple, - get_type, get_u32, hir_pattern_to_tokens, mutate_func_meta_type, parse, parse_tokens, + 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_typed_expr, 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::{ - ArrayLiteral, ExpressionKind, FunctionKind, FunctionReturnType, IntegerBitSize, Literal, - UnaryOp, UnresolvedType, UnresolvedTypeData, Visibility, + ArrayLiteral, BlockExpression, ConstrainKind, Expression, ExpressionKind, FunctionKind, + FunctionReturnType, IntegerBitSize, LValue, Literal, Statement, StatementKind, UnaryOp, + UnresolvedType, UnresolvedTypeData, Visibility, + }, + elaborator::Elaborator, + hir::comptime::{ + errors::IResult, + value::{ExprValue, TypedExpr}, + InterpreterError, Value, }, - hir::comptime::{errors::IResult, value::add_token_spans, InterpreterError, Value}, hir_def::function::FunctionBody, - macros_api::{ModuleDefId, NodeInterner, Signedness}, + macros_api::{HirExpression, HirLiteral, ModuleDefId, NodeInterner, Signedness}, node_interner::{DefinitionKind, TraitImplKind}, parser::{self}, - token::{SpannedToken, Token}, + token::Token, QuotedType, Shared, Type, }; @@ -43,28 +53,51 @@ impl<'local, 'context> Interpreter<'local, 'context> { location: Location, ) -> IResult { let interner = &mut self.elaborator.interner; + let call_stack = &self.elaborator.interpreter_call_stack; 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_array" => expr_as_array(arguments, return_type, location), - "expr_as_binary_op" => expr_as_binary_op(arguments, return_type, location), - "expr_as_bool" => expr_as_bool(arguments, return_type, location), - "expr_as_function_call" => expr_as_function_call(arguments, return_type, location), - "expr_as_if" => expr_as_if(arguments, return_type, location), - "expr_as_index" => expr_as_index(arguments, return_type, location), - "expr_as_integer" => expr_as_integer(arguments, return_type, location), - "expr_as_member_access" => expr_as_member_access(arguments, return_type, location), + "expr_as_array" => expr_as_array(interner, arguments, return_type, location), + "expr_as_assert" => expr_as_assert(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(arguments, return_type, location) + expr_as_repeated_element_array(interner, arguments, return_type, location) } "expr_as_repeated_element_slice" => { - expr_as_repeated_element_slice(arguments, return_type, location) + expr_as_repeated_element_slice(interner, arguments, return_type, location) } - "expr_as_slice" => expr_as_slice(arguments, return_type, location), - "expr_as_tuple" => expr_as_tuple(arguments, return_type, location), - "expr_as_unary_op" => expr_as_unary_op(arguments, return_type, location), + "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), + "expr_resolve" => expr_resolve(self, arguments, location), "is_unconstrained" => Ok(Value::Bool(true)), + "function_def_body" => function_def_body(interner, arguments, location), + "function_def_has_named_attribute" => { + function_def_has_named_attribute(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), @@ -87,14 +120,15 @@ impl<'local, 'context> Interpreter<'local, 'context> { "quoted_as_type" => quoted_as_type(self, arguments, location), "quoted_eq" => quoted_eq(arguments, location), "slice_insert" => slice_insert(interner, arguments, location), - "slice_pop_back" => slice_pop_back(interner, arguments, location), - "slice_pop_front" => slice_pop_front(interner, arguments, location), + "slice_pop_back" => slice_pop_back(interner, arguments, location, call_stack), + "slice_pop_front" => slice_pop_front(interner, arguments, location, call_stack), "slice_push_back" => slice_push_back(interner, arguments, location), "slice_push_front" => slice_push_front(interner, arguments, location), - "slice_remove" => slice_remove(interner, arguments, location), + "slice_remove" => slice_remove(interner, arguments, location, call_stack), "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" => { @@ -110,6 +144,7 @@ impl<'local, 'context> Interpreter<'local, 'context> { "type_as_constant" => type_as_constant(arguments, return_type, location), "type_as_integer" => type_as_integer(arguments, return_type, location), "type_as_slice" => type_as_slice(arguments, return_type, location), + "type_as_str" => type_as_str(arguments, return_type, location), "type_as_struct" => type_as_struct(arguments, return_type, location), "type_as_tuple" => type_as_tuple(arguments, return_type, location), "type_eq" => type_eq(arguments, location), @@ -120,6 +155,10 @@ impl<'local, 'context> Interpreter<'local, 'context> { "type_is_bool" => type_is_bool(arguments, location), "type_is_field" => type_is_field(arguments, location), "type_of" => type_of(arguments, location), + "typed_expr_as_function_definition" => { + typed_expr_as_function_definition(interner, arguments, return_type, 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}"); @@ -129,8 +168,16 @@ impl<'local, 'context> Interpreter<'local, 'context> { } } -fn failing_constraint(message: impl Into, location: Location) -> IResult { - Err(InterpreterError::FailingConstraint { message: Some(message.into()), location }) +fn failing_constraint( + message: impl Into, + location: Location, + call_stack: &im::Vector, +) -> IResult { + Err(InterpreterError::FailingConstraint { + message: Some(message.into()), + location, + call_stack: call_stack.clone(), + }) } fn array_len( @@ -262,6 +309,7 @@ fn slice_remove( interner: &mut NodeInterner, arguments: Vec<(Value, Location)>, location: Location, + call_stack: &im::Vector, ) -> IResult { let (slice, index) = check_two_arguments(arguments, location)?; @@ -269,7 +317,7 @@ fn slice_remove( let index = get_u32(index)? as usize; if values.is_empty() { - return failing_constraint("slice_remove called on empty slice", location); + return failing_constraint("slice_remove called on empty slice", location, call_stack); } if index >= values.len() { @@ -277,7 +325,7 @@ fn slice_remove( "slice_remove: index {index} is out of bounds for a slice of length {}", values.len() ); - return failing_constraint(message, location); + return failing_constraint(message, location, call_stack); } let element = values.remove(index); @@ -300,13 +348,14 @@ fn slice_pop_front( interner: &mut NodeInterner, arguments: Vec<(Value, Location)>, location: Location, + call_stack: &im::Vector, ) -> IResult { let argument = check_one_argument(arguments, location)?; let (mut values, typ) = get_slice(interner, argument)?; match values.pop_front() { Some(element) => Ok(Value::Tuple(vec![element, Value::Slice(values, typ)])), - None => failing_constraint("slice_pop_front called on empty slice", location), + None => failing_constraint("slice_pop_front called on empty slice", location, call_stack), } } @@ -314,13 +363,14 @@ fn slice_pop_back( interner: &mut NodeInterner, arguments: Vec<(Value, Location)>, location: Location, + call_stack: &im::Vector, ) -> IResult { let argument = check_one_argument(arguments, location)?; let (mut values, typ) = get_slice(interner, argument)?; match values.pop_back() { Some(element) => Ok(Value::Tuple(vec![Value::Slice(values, typ), element])), - None => failing_constraint("slice_pop_back called on empty slice", location), + None => failing_constraint("slice_pop_back called on empty slice", location, call_stack), } } @@ -345,10 +395,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)); - option(return_type, value) + let expr = parse(argument, parser, "an expression").ok(); + + option(return_type, expr) } // fn as_module(quoted: Quoted) -> Option @@ -384,6 +438,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)) } @@ -400,6 +455,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)>, @@ -460,6 +544,21 @@ fn type_as_slice( }) } +// fn as_str(self) -> Option +fn type_as_str( + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + type_as(arguments, return_type, location, |typ| { + if let Type::String(n) = typ { + Some(Value::Type(*n)) + } else { + None + } + }) +} + // fn as_struct(self) -> Option<(StructDefinition, [Type])> fn type_as_struct( arguments: Vec<(Value, Location)>, @@ -542,7 +641,12 @@ fn type_get_trait_impl( 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) { + 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, }; @@ -561,7 +665,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)) } @@ -685,6 +791,34 @@ fn trait_impl_trait_generic_args( Ok(Value::Slice(trait_generics, slice_type)) } +fn typed_expr_as_function_definition( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + let self_argument = check_one_argument(arguments, location)?; + let typed_expr = get_typed_expr(self_argument)?; + let option_value = if let TypedExpr::ExprId(expr_id) = typed_expr { + let func_id = interner.lookup_function_from_expr(&expr_id); + func_id.map(Value::FunctionDefinition) + } else { + None + }; + option(return_type, option_value) +} + +// 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 { @@ -761,20 +895,24 @@ 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(arguments, return_type, location, |expr| { - if let ExpressionKind::Literal(Literal::Array(ArrayLiteral::Standard(exprs))) = expr { - let exprs = exprs.into_iter().map(|expr| Value::Expr(expr.kind)).collect(); + 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 { @@ -783,14 +921,65 @@ fn expr_as_array( }) } +// fn as_assert(self) -> Option<(Expr, Option)> +fn expr_as_assert( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(interner, arguments, return_type.clone(), location, |expr| { + if let ExprValue::Statement(StatementKind::Constrain(constrain)) = expr { + if constrain.2 == ConstrainKind::Assert { + let predicate = Value::expression(constrain.0.kind); + + 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); + + let option_type = tuple_types.pop().unwrap(); + let message = constrain.1.map(|message| Value::expression(message.kind)); + let message = option(option_type, message).ok()?; + + Some(Value::Tuple(vec![predicate, message])) + } else { + None + } + } 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(arguments, return_type.clone(), location, |expr| { - if let ExpressionKind::Infix(infix_expr) = expr { + 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"); @@ -807,8 +996,8 @@ fn expr_as_binary_op( fields.insert(Rc::new("op".to_string()), Value::Field(binary_op_value.into())); let unary_op = Value::Struct(fields, binary_op_type); - let lhs = Value::Expr(infix_expr.lhs.kind); - let rhs = Value::Expr(infix_expr.rhs.kind); + 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 @@ -816,14 +1005,31 @@ fn expr_as_binary_op( }) } +// 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(arguments, return_type, location, |expr| { - if let ExpressionKind::Literal(Literal::Bool(bool)) = expr { + expr_as(interner, arguments, return_type, location, |expr| { + if let ExprValue::Expression(ExpressionKind::Literal(Literal::Bool(bool))) = expr { Some(Value::Bool(bool)) } else { None @@ -831,17 +1037,69 @@ fn expr_as_bool( }) } +// 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])) @@ -853,12 +1111,13 @@ 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(arguments, return_type.clone(), location, |expr| { - if let ExpressionKind::If(if_expr) = expr { + 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 { @@ -867,12 +1126,14 @@ fn expr_as_if( assert_eq!(option_types.len(), 3); let alternative_option_type = option_types[2].clone(); - let alternative = - option(alternative_option_type, if_expr.alternative.map(|e| Value::Expr(e.kind))); + let alternative = option( + alternative_option_type, + if_expr.alternative.map(|e| Value::expression(e.kind)), + ); Some(Value::Tuple(vec![ - Value::Expr(if_expr.condition.kind), - Value::Expr(if_expr.consequence.kind), + Value::expression(if_expr.condition.kind), + Value::expression(if_expr.consequence.kind), alternative.ok()?, ])) } else { @@ -883,15 +1144,16 @@ fn expr_as_if( // fn as_index(self) -> Option fn expr_as_index( + interner: &NodeInterner, arguments: Vec<(Value, Location)>, return_type: Type, location: Location, ) -> IResult { - expr_as(arguments, return_type, location, |expr| { - if let ExpressionKind::Index(index_expr) = expr { + expr_as(interner, arguments, return_type, location, |expr| { + if let ExprValue::Expression(ExpressionKind::Index(index_expr)) = expr { Some(Value::Tuple(vec![ - Value::Expr(index_expr.collection.kind), - Value::Expr(index_expr.index.kind), + Value::expression(index_expr.collection.kind), + Value::expression(index_expr.index.kind), ])) } else { None @@ -901,29 +1163,79 @@ fn expr_as_index( // 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(arguments, return_type.clone(), location, |expr| { - if let ExpressionKind::Literal(Literal::Integer(field, sign)) = expr { + 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)])) - } else { - None } + 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(arguments, return_type, location, |expr| { - if let ExpressionKind::MemberAccess(member_access) = expr { + 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::Expr(member_access.lhs.kind), Value::Quoted(tokens)])) + 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 } @@ -932,17 +1244,20 @@ fn expr_as_member_access( // 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(arguments, return_type, location, |expr| { - if let ExpressionKind::Literal(Literal::Array(ArrayLiteral::Repeated { - repeated_element, - length, - })) = expr + 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::Expr(repeated_element.kind), Value::Expr(length.kind)])) + Some(Value::Tuple(vec![ + Value::expression(repeated_element.kind), + Value::expression(length.kind), + ])) } else { None } @@ -951,17 +1266,20 @@ fn expr_as_repeated_element_array( // 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(arguments, return_type, location, |expr| { - if let ExpressionKind::Literal(Literal::Slice(ArrayLiteral::Repeated { - repeated_element, - length, - })) = expr + 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::Expr(repeated_element.kind), Value::Expr(length.kind)])) + Some(Value::Tuple(vec![ + Value::expression(repeated_element.kind), + Value::expression(length.kind), + ])) } else { None } @@ -970,13 +1288,17 @@ fn expr_as_repeated_element_slice( // fn as_slice(self) -> Option<[Expr]> fn expr_as_slice( + interner: &NodeInterner, arguments: Vec<(Value, Location)>, return_type: Type, location: Location, ) -> IResult { - expr_as(arguments, return_type, location, |expr| { - if let ExpressionKind::Literal(Literal::Slice(ArrayLiteral::Standard(exprs))) = expr { - let exprs = exprs.into_iter().map(|expr| Value::Expr(expr.kind)).collect(); + 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 { @@ -987,13 +1309,15 @@ fn expr_as_slice( // fn as_tuple(self) -> Option<[Expr]> fn expr_as_tuple( + interner: &NodeInterner, arguments: Vec<(Value, Location)>, return_type: Type, location: Location, ) -> IResult { - expr_as(arguments, return_type, location, |expr| { - if let ExpressionKind::Tuple(expressions) = expr { - let expressions = expressions.into_iter().map(|expr| Value::Expr(expr.kind)).collect(); + 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 { @@ -1004,12 +1328,13 @@ fn expr_as_tuple( // 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(arguments, return_type.clone(), location, |expr| { - if let ExpressionKind::Prefix(prefix_expr) = expr { + 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"); @@ -1031,7 +1356,7 @@ fn expr_as_unary_op( fields.insert(Rc::new("op".to_string()), Value::Field(unary_op_value.into())); let unary_op = Value::Struct(fields, unary_op_type); - let rhs = Value::Expr(prefix_expr.rhs.kind); + let rhs = Value::expression(prefix_expr.rhs.kind); Some(Value::Tuple(vec![unary_op, rhs])) } else { None @@ -1039,26 +1364,201 @@ fn expr_as_unary_op( }) } +// 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 mut expression_kind = get_expr(self_argument)?; - while let ExpressionKind::Parenthesized(expression) = expression_kind { - expression_kind = expression.kind; - } + let expr_value = get_expr(interner, self_argument)?; + let expr_value = unwrap_expr_value(interner, expr_value); - let option_value = f(expression_kind); + let option_value = f(expr_value); option(return_type, option_value) } +// fn resolve(self, in_function: Option) -> TypedExpr +fn expr_resolve( + interpreter: &mut Interpreter, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let (self_argument, func) = check_two_arguments(arguments, location)?; + let self_argument_location = self_argument.1; + let expr_value = get_expr(interpreter.elaborator.interner, self_argument)?; + let expr_value = unwrap_expr_value(interpreter.elaborator.interner, expr_value); + + let Value::Struct(fields, _) = func.0 else { + panic!("Expected second argument to be a struct"); + }; + + let is_some = fields.get(&Rc::new("_is_some".to_string())).unwrap(); + let Value::Bool(is_some) = is_some else { + panic!("Expected is_some to be a boolean"); + }; + + let function_to_resolve_in = if *is_some { + let value = fields.get(&Rc::new("_value".to_string())).unwrap(); + let Value::FunctionDefinition(func_id) = value else { + panic!("Expected option value to be a FunctionDefinition"); + }; + Some(*func_id) + } else { + interpreter.current_function + }; + + let value = interpreter.elaborate_item(function_to_resolve_in, |elaborator| match expr_value { + ExprValue::Expression(expression_kind) => { + let expr = Expression { kind: expression_kind, span: self_argument_location.span }; + let (expr_id, _) = elaborator.elaborate_expression(expr); + Value::TypedExpr(TypedExpr::ExprId(expr_id)) + } + ExprValue::Statement(statement_kind) => { + let statement = Statement { kind: statement_kind, span: self_argument_location.span }; + let (stmt_id, _) = elaborator.elaborate_statement(statement); + Value::TypedExpr(TypedExpr::StmtId(stmt_id)) + } + ExprValue::LValue(lvalue) => { + let expr = lvalue.as_expression(); + let (expr_id, _) = elaborator.elaborate_expression(expr); + Value::TypedExpr(TypedExpr::ExprId(expr_id)) + } + }); + + Ok(value) +} + +fn unwrap_expr_value(interner: &NodeInterner, mut expr_value: ExprValue) -> ExprValue { + 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, + } + } + expr_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 has_named_attribute(self, name: Quoted) -> bool +fn function_def_has_named_attribute( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let (self_argument, name) = check_two_arguments(arguments, location)?; + let func_id = get_function_def(self_argument)?; + let name = get_quoted(name)?; + let func_meta = interner.function_meta(&func_id); + let attributes = &func_meta.custom_attributes; + if attributes.is_empty() { + return Ok(Value::Bool(false)); + }; + + let name = name.iter().map(|token| token.to_string()).collect::>().join(""); + + for attribute in attributes { + let parse_result = Elaborator::parse_attribute(attribute, location.file); + let Ok(Some((function, _arguments))) = parse_result else { + continue; + }; + + let ExpressionKind::Variable(path) = function.kind else { + continue; + }; + + if path.last_name() == name { + return Ok(Value::Bool(true)); + } + } + + Ok(Value::Bool(false)) +} + // fn name(self) -> Quoted fn function_def_name( interner: &NodeInterner, @@ -1113,32 +1613,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; @@ -1357,12 +1855,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 8d2f55b9c20..dd9ea51961e 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,10 +4,18 @@ 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, TypedExpr}, + Interpreter, InterpreterError, Value, + }, def_map::ModuleId, + type_check::generics::TraitGenerics, }, hir_def::{ function::{FuncMeta, FunctionBody}, @@ -137,9 +145,33 @@ pub(crate) fn get_u32((value, location): (Value, Location)) -> IResult { } } -pub(crate) fn get_expr((value, location): (Value, Location)) -> IResult { +pub(crate) fn get_u64((value, location): (Value, Location)) -> IResult { + match value { + Value::U64(value) => Ok(value), + value => { + 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) => Ok(expr), + 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), } } @@ -167,7 +199,7 @@ pub(crate) fn get_struct((value, location): (Value, Location)) -> IResult IResult<(TraitId, Vec)> { +) -> IResult<(TraitId, TraitGenerics)> { match value { Value::TraitConstraint(trait_id, generics) => Ok((trait_id, generics)), value => type_mismatch(value, Type::Quoted(QuotedType::TraitConstraint), location), @@ -195,6 +227,13 @@ pub(crate) fn get_type((value, location): (Value, Location)) -> IResult { } } +pub(crate) fn get_typed_expr((value, location): (Value, Location)) -> IResult { + match value { + Value::TypedExpr(typed_expr) => Ok(typed_expr), + value => type_mismatch(value, Type::Quoted(QuotedType::TypedExpr), location), + } +} + pub(crate) fn get_quoted((value, location): (Value, Location)) -> IResult>> { match value { Value::Quoted(tokens) => Ok(tokens), @@ -202,6 +241,23 @@ pub(crate) fn get_quoted((value, location): (Value, Location)) -> IResult 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 }) @@ -346,3 +402,11 @@ pub(super) fn replace_func_meta_return_type(typ: &mut Type, return_type: 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 d65c2bb7dcc..b96c4852931 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,10 +5,17 @@ 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, + 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, @@ -17,7 +24,7 @@ use crate::{ Expression, ExpressionKind, HirExpression, HirLiteral, Literal, NodeInterner, Path, StructId, }, - node_interner::{ExprId, FuncId, TraitId, TraitImplId}, + node_interner::{ExprId, FuncId, StmtId, TraitId, TraitImplId}, parser::{self, NoirParser, TopLevelStatement}, token::{SpannedToken, Token, Tokens}, QuotedType, Shared, Type, TypeBindings, @@ -54,17 +61,44 @@ 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), + TypedExpr(TypedExpr), + UnresolvedType(UnresolvedTypeData), +} + +#[derive(Debug, Clone, PartialEq, Eq, Display)] +pub enum ExprValue { + Expression(ExpressionKind), + Statement(StatementKind), + LValue(LValue), +} + +#[derive(Debug, Clone, PartialEq, Eq, Display)] +pub enum TypedExpr { + ExprId(ExprId), + StmtId(StmtId), } 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, @@ -110,6 +144,8 @@ impl Value { Value::Type(_) => Type::Quoted(QuotedType::Type), Value::Zeroed(typ) => return Cow::Borrowed(typ), Value::Expr(_) => Type::Quoted(QuotedType::Expr), + Value::TypedExpr(_) => Type::Quoted(QuotedType::TypedExpr), + Value::UnresolvedType(_) => Type::Quoted(QuotedType::UnresolvedType), }) } @@ -230,8 +266,15 @@ 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(lvalue)) => lvalue.as_expression().kind, + Value::TypedExpr(..) + | Value::Pointer(..) | Value::StructDefinition(_) | Value::TraitConstraint(..) | Value::TraitDefinition(_) @@ -239,6 +282,7 @@ impl Value { | Value::FunctionDefinition(_) | Value::Zeroed(_) | Value::Type(_) + | Value::UnresolvedType(_) | Value::ModuleDefinition(_) => { let typ = self.get_type().into_owned(); let value = self.display(interner).to_string(); @@ -354,7 +398,9 @@ impl Value { HirExpression::Literal(HirLiteral::Slice(HirArrayLiteral::Standard(elements))) } Value::Quoted(tokens) => HirExpression::Unquote(add_token_spans(tokens, location.span)), - Value::Expr(..) + Value::TypedExpr(TypedExpr::ExprId(expr_id)) => interner.expression(&expr_id), + Value::TypedExpr(TypedExpr::StmtId(..)) + | Value::Expr(..) | Value::Pointer(..) | Value::StructDefinition(_) | Value::TraitConstraint(..) @@ -363,6 +409,7 @@ impl Value { | Value::FunctionDefinition(_) | Value::Zeroed(_) | Value::Type(_) + | Value::UnresolvedType(_) | Value::ModuleDefinition(_) => { let typ = self.get_type().into_owned(); let value = self.display(interner).to_string(); @@ -384,6 +431,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]) @@ -523,7 +582,8 @@ impl<'value, 'interner> Display for ValuePrinter<'value, 'interner> { write!(f, "{}", def.name) } Value::TraitConstraint(trait_id, generics) => { - write!(f, "{}", display_trait_id_and_generics(self.interner, trait_id, generics)) + let trait_ = self.interner.get_trait(*trait_id); + write!(f, "{}{generics}", trait_.name) } Value::TraitDefinition(trait_id) => { let trait_ = self.interner.get_trait(*trait_id); @@ -563,30 +623,253 @@ impl<'value, 'interner> Display for ValuePrinter<'value, 'interner> { 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::TypedExpr(_) => write!(f, "(typed expr)"), + 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_id_and_generics( +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, - trait_id: &TraitId, - generics: &Vec, -) -> String { - let trait_ = interner.get_trait(*trait_id); - let generic_string = vecmap(generics, ToString::to_string).join(", "); - if generics.is_empty() { - format!("{}", trait_.name) - } else { - format!("{}<{generic_string}>", trait_.name) + 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 display_trait_constraint(interner: &NodeInterner, trait_constraint: &TraitConstraint) -> String { - let trait_constraint_string = display_trait_id_and_generics( - interner, - &trait_constraint.trait_id, - &trait_constraint.trait_generics, - ); - format!("{}: {}", trait_constraint.typ, trait_constraint_string) +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 6436c2562bc..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,7 +14,8 @@ use crate::ast::{ NoirStruct, NoirTrait, NoirTraitImpl, NoirTypeAlias, Pattern, TraitImplItem, TraitItem, TypeImpl, }; -use crate::macros_api::NodeInterner; +use crate::hir::resolution::errors::ResolverError; +use crate::macros_api::{Expression, NodeInterner, UnresolvedType, UnresolvedTypeData}; use crate::node_interner::ModuleAttributes; use crate::{ graph::CrateId, @@ -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, @@ -461,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 { @@ -521,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, @@ -542,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()) @@ -556,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(), + }); } } } @@ -564,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, @@ -573,7 +597,12 @@ 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.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 }; @@ -771,7 +800,7 @@ impl<'a> ModCollector<'a> { ModuleAttributes { name: mod_name.0.contents.clone(), location: mod_location, - parent: self.module_id, + parent: Some(self.module_id), }, ); @@ -782,6 +811,23 @@ impl<'a> ModCollector<'a> { 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( @@ -870,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/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 de5d146713f..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}")] @@ -158,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)] @@ -278,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 { @@ -360,12 +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) + } } } } @@ -404,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 8137e74da30..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; @@ -199,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 { @@ -208,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, @@ -219,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 29b0cb7b8af..7fa33746f31 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 @@ -164,6 +164,9 @@ pub struct FuncMeta { /// If this function is from an impl (trait or regular impl), this /// is the object type of the impl. Otherwise this is None. pub self_type: Option, + + /// Custom attributes attached to this function. + pub custom_attributes: Vec, } #[derive(Debug, Clone)] 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 9d820b9443c..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,12 +1,14 @@ +use iter_extended::vecmap; use rustc_hash::FxHashMap as HashMap; use crate::ast::{Ident, NoirFunction}; -use crate::TypeVariableId; +use crate::hir::type_check::generics::TraitGenerics; use crate::{ graph::CrateId, node_interner::{FuncId, TraitId, TraitMethodId}, Generics, Type, TypeBindings, TypeVariable, }; +use crate::{ResolvedGeneric, TypeVariableKind}; use fm::FileId; use noirc_errors::{Location, Span}; @@ -24,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 @@ -54,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, @@ -65,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, } @@ -74,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 @@ -89,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); + } } } @@ -132,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 { 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 d6d114c7075..638003d3fcd 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. @@ -148,10 +153,12 @@ pub enum QuotedType { Quoted, TopLevelItem, Type, + TypedExpr, StructDefinition, TraitConstraint, TraitDefinition, TraitImpl, + UnresolvedType, FunctionDefinition, Module, } @@ -540,7 +547,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(()) @@ -646,12 +653,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); @@ -740,10 +742,12 @@ impl std::fmt::Display for QuotedType { QuotedType::Quoted => write!(f, "Quoted"), QuotedType::TopLevelItem => write!(f, "TopLevelItem"), QuotedType::Type => write!(f, "Type"), + QuotedType::TypedExpr => write!(f, "TypedExpr"), 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"), } @@ -860,8 +864,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) @@ -936,9 +941,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); @@ -1629,6 +1637,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) } @@ -1644,107 +1661,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( @@ -1881,14 +1797,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, } @@ -1919,11 +1835,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, } @@ -1990,17 +1908,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); @@ -2145,11 +2059,15 @@ impl Type { 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); @@ -2178,11 +2096,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() { @@ -2259,8 +2179,12 @@ impl Type { 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(); @@ -2329,9 +2253,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(); @@ -2404,19 +2331,30 @@ fn convert_array_expression_to_slice( 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 { @@ -2485,7 +2423,7 @@ 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!(), @@ -2547,14 +2485,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(", ")) @@ -2624,3 +2555,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 b9b4bdef9aa..585e22ce92b 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()), } } @@ -835,6 +870,16 @@ pub enum SecondaryAttribute { Varargs, } +impl SecondaryAttribute { + pub(crate) fn as_custom(&self) -> Option<&str> { + if let Self::Custom(str) = self { + Some(str) + } else { + None + } + } +} + impl fmt::Display for SecondaryAttribute { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -927,9 +972,11 @@ pub enum Keyword { TraitDefinition, TraitImpl, Type, + TypedExpr, TypeType, Unchecked, Unconstrained, + UnresolvedType, Unsafe, Use, Where, @@ -981,9 +1028,11 @@ impl fmt::Display for Keyword { Keyword::TraitDefinition => write!(f, "TraitDefinition"), Keyword::TraitImpl => write!(f, "TraitImpl"), Keyword::Type => write!(f, "type"), + Keyword::TypedExpr => write!(f, "TypedExpr"), 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"), @@ -1038,9 +1087,11 @@ impl Keyword { "TraitImpl" => Keyword::TraitImpl, "type" => Keyword::Type, "Type" => Keyword::TypeType, + "TypedExpr" => Keyword::TypedExpr, "StructDefinition" => Keyword::StructDefinition, "unchecked" => Keyword::Unchecked, "unconstrained" => Keyword::Unconstrained, + "UnresolvedType" => Keyword::UnresolvedType, "unsafe" => Keyword::Unsafe, "use" => Keyword::Use, "where" => Keyword::Where, 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 f7bcfd58b72..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, @@ -357,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) } } 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 510b81d9acb..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. @@ -569,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))?; @@ -845,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) => { @@ -936,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) } @@ -969,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)?; @@ -1029,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"), }) @@ -1073,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) @@ -1945,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 46ec2676930..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,18 @@ 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; @@ -49,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; @@ -133,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`. /// @@ -201,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, @@ -307,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 @@ -566,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 { @@ -603,6 +639,9 @@ 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(), @@ -610,6 +649,7 @@ impl Default for NodeInterner { reference_modules: HashMap::default(), auto_import_names: HashMap::default(), comptime_scopes: vec![HashMap::default()], + trait_impl_associated_types: HashMap::default(), } } } @@ -651,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); @@ -1035,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] { @@ -1277,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] } @@ -1377,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) @@ -1394,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. @@ -1423,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, )?; @@ -1445,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; } @@ -1510,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)) } } @@ -1549,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(()) @@ -1587,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; } @@ -1604,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, @@ -1626,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' @@ -1633,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(); @@ -1986,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, @@ -2021,6 +2137,59 @@ impl NodeInterner { 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 { 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 ebb58ddc224..2e38d7ae83b 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/errors.rs @@ -64,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 b86c2c46c9b..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. @@ -333,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 { @@ -485,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), )) }) @@ -524,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 @@ -640,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, { @@ -653,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); @@ -902,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)` @@ -1147,6 +1174,7 @@ where 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| { 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 0cf5e63f5f3..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 @@ -111,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. 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 cb7271a416c..9dabb8b83b6 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,16 +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(), - trait_impl_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()), @@ -48,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(), )) } @@ -82,6 +75,23 @@ 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(), + typed_expr_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) @@ -113,6 +123,12 @@ pub(super) fn trait_impl_type() -> impl NoirParser { .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) @@ -144,6 +160,12 @@ fn quoted_type() -> impl NoirParser { .map_with_span(|_, span| UnresolvedTypeData::Quoted(QuotedType::Quoted).with_span(span)) } +/// This is the type of a typed/resolved expression. +fn typed_expr_type() -> impl NoirParser { + keyword(Keyword::TypedExpr) + .map_with_span(|_, span| UnresolvedTypeData::Quoted(QuotedType::TypedExpr).with_span(span)) +} + /// This is the type of an already resolved type. /// The only way this can appear in the token input is if an already resolved `Type` object /// was spliced into a macro's token stream via the `$` operator. @@ -154,6 +176,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))) @@ -213,25 +244,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>( diff --git a/noir/noir-repo/compiler/noirc_frontend/src/tests.rs b/noir/noir-repo/compiler/noirc_frontend/src/tests.rs index bba596ed19f..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); @@ -2889,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); } @@ -2913,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); } @@ -3114,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/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 18417b376e5..293523d7c15 100644 --- a/noir/noir-repo/cspell.json +++ b/noir/noir-repo/cspell.json @@ -20,6 +20,7 @@ "barretenberg", "barustenberg", "bbup", + "bignum", "bincode", "bindgen", "bitand", @@ -76,6 +77,7 @@ "devcontainer", "direnv", "eddsa", + "elab", "Elligator", "endianness", "envrc", @@ -119,6 +121,7 @@ "keccakf", "krate", "libc", + "Linea", "losslessly", "lvalue", "Maddiaa", @@ -126,6 +129,7 @@ "memfs", "memset", "merkle", + "metaprogramming", "metas", "microcontroller", "minreq", @@ -166,6 +170,7 @@ "pseudocode", "pubkey", "quantile", + "quasiquote", "rangemap", "repr", "reqwest", @@ -204,6 +209,8 @@ "typevar", "typevars", "udiv", + "umap", + "underconstrained", "uninstantiated", "unnormalized", "unoptimized", @@ -216,6 +223,7 @@ "wasi", "wasmer", "Weierstraß", + "zkhash", "zshell", "Linea" ], 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..ed55a541fbd --- /dev/null +++ b/noir/noir-repo/docs/docs/noir/concepts/comptime.md @@ -0,0 +1,263 @@ +--- +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` +- `TypedExpr`: A type-checked expression. +- `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/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..57f0fce24c1 --- /dev/null +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/expr.md @@ -0,0 +1,201 @@ +--- +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_assert + +#include_code as_assert noir_stdlib/src/meta/expr.nr rust + +If this expression is an assert, this returns the assert expression and the optional message. + +### 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`. + +### modify + +#include_code modify 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 `modify` 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 }`. + +### resolve + +#include_code resolve noir_stdlib/src/meta/expr.nr rust + +Resolves and type-checks this expression and returns the result as a `TypedExpr`. + +The `in_function` argument specifies where the expression is resolved: +- If it's `none`, the expression is resolved in the function where `resolve` was called +- If it's `some`, the expression is resolved in the given function + +If any names used by this expression are not in scope or if there are any type errors, +this will give compiler errors as if the expression was written directly into +the current `comptime` function. \ 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..5657e05fff7 --- /dev/null +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/function_def.md @@ -0,0 +1,67 @@ +--- +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. + +### has_named_attribute + +#include_code has_named_attribute noir_stdlib/src/meta/function_def.nr rust + +Returns true if this function has a custom attribute with the given name. + +### 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..0b6f8d5f77d --- /dev/null +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/typ.md @@ -0,0 +1,132 @@ +--- +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_str + +#include_code as_str noir_stdlib/src/meta/typ.nr rust + +If this is a `str` type, returns the length `N` as a type. + +### 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/typed_expr.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/typed_expr.md new file mode 100644 index 00000000000..eacfd9c1230 --- /dev/null +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/typed_expr.md @@ -0,0 +1,13 @@ +--- +title: TypedExpr +--- + +`std::meta::typed_expr` contains methods on the built-in `TypedExpr` type for resolved and type-checked expressions. + +## Methods + +### as_function_definition + +#include_code as_function_definition noir_stdlib/src/meta/typed_expr.nr rust + +If this expression refers to a function definitions, returns it. Otherwise returns `Option::none()`. \ No newline at end of file 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/noir_stdlib/src/array.nr b/noir/noir-repo/noir_stdlib/src/array.nr index cef79e7c7f6..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 {} diff --git a/noir/noir-repo/noir_stdlib/src/cmp.nr b/noir/noir-repo/noir_stdlib/src/cmp.nr index ec979d60753..b7f473429a7 100644 --- a/noir/noir-repo/noir_stdlib/src/cmp.nr +++ b/noir/noir-repo/noir_stdlib/src/cmp.nr @@ -7,12 +7,14 @@ 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 } } @@ -118,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 { @@ -132,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 diff --git a/noir/noir-repo/noir_stdlib/src/collections/map.nr b/noir/noir-repo/noir_stdlib/src/collections/map.nr index bd50f345356..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 @@ -183,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) ) @@ -208,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 ) @@ -278,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 @@ -313,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 @@ -356,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 ) @@ -388,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 86ae79ea644..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 @@ -194,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) ) @@ -216,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 ) @@ -283,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 @@ -315,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 @@ -353,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; @@ -368,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 ) @@ -397,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 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 3abc630ab10..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 @@ -137,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. @@ -149,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.) diff --git a/noir/noir-repo/noir_stdlib/src/hash/poseidon2.nr b/noir/noir-repo/noir_stdlib/src/hash/poseidon2.nr index 9626da0cf97..902d3cc8104 100644 --- a/noir/noir-repo/noir_stdlib/src/hash/poseidon2.nr +++ b/noir/noir-repo/noir_stdlib/src/hash/poseidon2.nr @@ -26,73 +26,40 @@ impl Poseidon2 { result } - fn perform_duplex(&mut self) -> [Field; RATE] { - // zero-pad the cache - for i in 0..RATE { - if i >= self.cache_size { - self.cache[i] = 0; - } - } + fn perform_duplex(&mut self) { // add the cache into sponge state for i in 0..RATE { - self.state[i] += self.cache[i]; + // We effectively zero-pad the cache by only adding to the state + // cache that is less than the specified `cache_size` + if i < self.cache_size { + 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..50cf2f965f9 --- /dev/null +++ b/noir/noir-repo/noir_stdlib/src/hash/sha256.nr @@ -0,0 +1,257 @@ +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 { + assert_eq(msg_block[msg_byte_ptr], msg[k]); + msg_byte_ptr = msg_byte_ptr + 1; + } + } + + 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 msg_start = BLOCK_SIZE * i; + let (new_msg_block, new_msg_byte_ptr) = unsafe { + build_msg_block_iter(msg, message_size, msg_start) + }; + if msg_start as u64 < message_size { + msg_block = new_msg_block; + } + + if !is_unconstrained() { + // Verify the block we are compressing was appropriately constructed + let new_msg_byte_ptr = verify_msg_block(msg, message_size, msg_block, msg_start); + if msg_start as u64 < message_size { + msg_byte_ptr = new_msg_byte_ptr; + } + } else if msg_start as u64 < message_size { + msg_byte_ptr = new_msg_byte_ptr; + } + + // If the block is filled, compress it. + // An un-filled block is handled after this loop. + if msg_byte_ptr == 64 { + 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 msg_start = BLOCK_SIZE * num_blocks; + let (new_msg_block, new_msg_byte_ptr) = unsafe { + build_msg_block_iter(msg, message_size, msg_start) + }; + + if msg_start as u64 < message_size { + msg_block = new_msg_block; + } + + if !is_unconstrained() { + let new_msg_byte_ptr = verify_msg_block(msg, message_size, msg_block, msg_start); + if msg_start as u64 < message_size { + msg_byte_ptr = new_msg_byte_ptr; + } + } else if msg_start as u64 < message_size { + 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/meta/expr.nr b/noir/noir-repo/noir_stdlib/src/meta/expr.nr index 94889b7c3dd..9b5eee03229 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/expr.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/expr.nr @@ -4,43 +4,449 @@ 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_assert)] + // docs:start:as_assert + fn as_assert(self) -> Option<(Expr, Option)> {} + // docs:end:as_assert + + #[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:modify + fn modify(self, f: fn[Env](Expr) -> Option) -> Expr { + // docs:end:modify + let result = modify_array(self, f); + let result = result.or_else(|| modify_assert(self, f)); + let result = result.or_else(|| modify_assign(self, f)); + let result = result.or_else(|| modify_binary_op(self, f)); + let result = result.or_else(|| modify_block(self, f)); + let result = result.or_else(|| modify_cast(self, f)); + let result = result.or_else(|| modify_comptime(self, f)); + let result = result.or_else(|| modify_if(self, f)); + let result = result.or_else(|| modify_index(self, f)); + let result = result.or_else(|| modify_function_call(self, f)); + let result = result.or_else(|| modify_member_access(self, f)); + let result = result.or_else(|| modify_method_call(self, f)); + let result = result.or_else(|| modify_repeated_element_array(self, f)); + let result = result.or_else(|| modify_repeated_element_slice(self, f)); + let result = result.or_else(|| modify_slice(self, f)); + let result = result.or_else(|| modify_tuple(self, f)); + let result = result.or_else(|| modify_unary_op(self, f)); + let result = result.or_else(|| modify_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 } + } + + #[builtin(expr_resolve)] + // docs:start:resolve + fn resolve(self, in_function: Option) -> TypedExpr {} + // docs:end:resolve +} + +fn modify_array(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { + expr.as_array().map( + |exprs: [Expr]| { + let exprs = modify_expressions(exprs, f); + new_array(exprs) + } + ) +} + +fn modify_assert(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { + expr.as_assert().map( + |expr: (Expr, Option)| { + let (predicate, msg) = expr; + let predicate = predicate.modify(f); + let msg = msg.map(|msg: Expr| msg.modify(f)); + new_assert(predicate, msg) + } + ) +} + +fn modify_assign(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { + expr.as_assign().map( + |expr: (Expr, Expr)| { + let (lhs, rhs) = expr; + let lhs = lhs.modify(f); + let rhs = rhs.modify(f); + new_assign(lhs, rhs) + } + ) +} + +fn modify_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.modify(f); + let rhs = rhs.modify(f); + new_binary_op(lhs, op, rhs) + } + ) +} + +fn modify_block(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { + expr.as_block().map( + |exprs: [Expr]| { + let exprs = modify_expressions(exprs, f); + new_block(exprs) + } + ) +} + +fn modify_cast(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { + expr.as_cast().map( + |expr: (Expr, UnresolvedType)| { + let (expr, typ) = expr; + let expr = expr.modify(f); + new_cast(expr, typ) + } + ) +} + +fn modify_comptime(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { + expr.as_comptime().map( + |exprs: [Expr]| { + let exprs = exprs.map(|expr: Expr| expr.modify(f)); + new_comptime(exprs) + } + ) +} + +fn modify_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.modify(f); + let arguments = arguments.map(|arg: Expr| arg.modify(f)); + new_function_call(function, arguments) + } + ) +} + +fn modify_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.modify(f); + let consequence = consequence.modify(f); + let alternative = alternative.map(|alternative: Expr| alternative.modify(f)); + new_if(condition, consequence, alternative) + } + ) +} + +fn modify_index(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { + expr.as_index().map( + |expr: (Expr, Expr)| { + let (object, index) = expr; + let object = object.modify(f); + let index = index.modify(f); + new_index(object, index) + } + ) +} + +fn modify_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.modify(f); + new_member_access(object, name) + } + ) +} + +fn modify_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.modify(f); + let arguments = arguments.map(|arg: Expr| arg.modify(f)); + new_method_call(object, name, generics, arguments) + } + ) +} + +fn modify_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.modify(f); + let length = length.modify(f); + new_repeated_element_array(expr, length) + } + ) +} + +fn modify_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.modify(f); + let length = length.modify(f); + new_repeated_element_slice(expr, length) + } + ) +} + +fn modify_slice(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { + expr.as_slice().map( + |exprs: [Expr]| { + let exprs = modify_expressions(exprs, f); + new_slice(exprs) + } + ) +} + +fn modify_tuple(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { + expr.as_tuple().map( + |exprs: [Expr]| { + let exprs = modify_expressions(exprs, f); + new_tuple(exprs) + } + ) +} + +fn modify_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.modify(f); + new_unary_op(op, rhs) + } + ) +} + +fn modify_unsafe(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { + expr.as_unsafe().map( + |exprs: [Expr]| { + let exprs = exprs.map(|expr: Expr| expr.modify(f)); + new_unsafe(exprs) + } + ) +} + +fn modify_expressions(exprs: [Expr], f: fn[Env](Expr) -> Option) -> [Expr] { + exprs.map(|expr: Expr| expr.modify(f)) +} + +fn new_array(exprs: [Expr]) -> Expr { + let exprs = join_expressions(exprs, quote { , }); + quote { [$exprs]}.as_expr().unwrap() +} + +fn new_assert(predicate: Expr, msg: Option) -> Expr { + if msg.is_some() { + let msg = msg.unwrap(); + quote { assert($predicate, $msg) }.as_expr().unwrap() + } else { + quote { assert($predicate) }.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) } mod tests { @@ -82,8 +488,10 @@ mod tests { assert(get_binary_op(quote { x / y }).is_divide()); assert(get_binary_op(quote { x == y }).is_equal()); assert(get_binary_op(quote { x != y }).is_not_equal()); - assert(get_binary_op(quote { x > y }).is_greater()); - assert(get_binary_op(quote { x >= y }).is_greater_or_equal()); + assert(get_binary_op(quote { x < y }).is_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()); 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..cbbbfb2f901 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,41 @@ impl FunctionDefinition { + #[builtin(function_def_body)] + // docs:start:body + fn body(self) -> Expr {} + // docs:end:body + + #[builtin(function_def_has_named_attribute)] + // docs:start:has_named_attribute + fn has_named_attribute(self, name: Quoted) -> bool {} + // docs:end:has_named_attribute + #[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 d16a8648bc2..24398054467 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/mod.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/mod.nr @@ -1,7 +1,3 @@ -use crate::collections::umap::UHashMap; -use crate::hash::BuildHasherDefault; -use crate::hash::poseidon2::Poseidon2Hasher; - mod expr; mod function_def; mod module; @@ -11,25 +7,45 @@ mod trait_constraint; mod trait_def; mod trait_impl; mod typ; +mod typed_expr; 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 { @@ -44,10 +60,16 @@ pub comptime fn derive(s: StructDefinition, traits: [TraitDefinition]) -> Quoted result } +// docs:end:derive_example +// 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: @@ -61,6 +83,7 @@ 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, @@ -69,6 +92,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 {,}); @@ -90,3 +114,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 index ebd89677c50..f3060a1648b 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/op.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/op.nr @@ -3,21 +3,45 @@ struct UnaryOp { } impl UnaryOp { - fn is_minus(self) -> bool { + // docs:start:is_minus + pub fn is_minus(self) -> bool { + // docs:end:is_minus self.op == 0 } - fn is_not(self) -> bool { + // docs:start:is_not + pub fn is_not(self) -> bool { + // docs:end:is_not self.op == 1 } - fn is_mutable_reference(self) -> bool { + // docs:start:is_mutable_reference + pub fn is_mutable_reference(self) -> bool { + // docs:end:is_mutable_reference self.op == 2 } - fn is_dereference(self) -> bool { + // 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 { @@ -25,68 +49,140 @@ struct BinaryOp { } impl BinaryOp { - fn is_add(self) -> bool { + // docs:start:is_add + pub fn is_add(self) -> bool { + // docs:end:is_add self.op == 0 } - fn is_subtract(self) -> bool { + // docs:start:is_subtract + pub fn is_subtract(self) -> bool { + // docs:end:is_subtract self.op == 1 } - fn is_multiply(self) -> bool { + // docs:start:is_multiply + pub fn is_multiply(self) -> bool { + // docs:end:is_multiply self.op == 2 } - fn is_divide(self) -> bool { + // docs:start:is_divide + pub fn is_divide(self) -> bool { + // docs:end:is_divide self.op == 3 } - fn is_equal(self) -> bool { + // docs:start:is_equal + pub fn is_equal(self) -> bool { + // docs:end:is_equal self.op == 4 } - fn is_not_equal(self) -> bool { + // docs:start:is_not_equal + pub fn is_not_equal(self) -> bool { + // docs:end:is_not_equal self.op == 5 } - fn is_less(self) -> bool { + // docs:start:is_less_than + pub fn is_less_than(self) -> bool { + // docs:end:is_less_than self.op == 6 } - fn is_less_equal(self) -> bool { + // 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 } - fn is_greater(self) -> bool { + // docs:start:is_greater_than + pub fn is_greater_than(self) -> bool { + // docs:end:is_greater_than self.op == 8 } - fn is_greater_or_equal(self) -> bool { + // 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 } - fn is_and(self) -> bool { + // docs:start:is_and + pub fn is_and(self) -> bool { + // docs:end:is_and self.op == 10 } - fn is_or(self) -> bool { + // docs:start:is_or + pub fn is_or(self) -> bool { + // docs:end:is_or self.op == 11 } - fn is_xor(self) -> bool { + // docs:start:is_xor + pub fn is_xor(self) -> bool { + // docs:end:is_xor self.op == 12 } - fn is_shift_right(self) -> bool { + // docs:start:is_shift_right + pub fn is_shift_right(self) -> bool { + // docs:end:is_shift_right self.op == 13 } - fn is_shift_left(self) -> bool { + // docs:start:is_shift_left + pub fn is_shift_left(self) -> bool { + // docs:end:is_shift_left self.op == 14 } - fn is_modulo(self) -> bool { + // 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 index 2f82ee5f434..15b02eac6bd 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/trait_impl.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/trait_impl.nr @@ -1,7 +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 67ad2a96739..12dc91a4925 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/typ.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/typ.nr @@ -3,34 +3,59 @@ 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_str)] +// docs:start:as_str + fn as_str(self) -> Option {} + // docs:end:as_str #[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/typed_expr.nr b/noir/noir-repo/noir_stdlib/src/meta/typed_expr.nr new file mode 100644 index 00000000000..8daede97438 --- /dev/null +++ b/noir/noir-repo/noir_stdlib/src/meta/typed_expr.nr @@ -0,0 +1,8 @@ +use crate::option::Option; + +impl TypedExpr { + #[builtin(typed_expr_as_function_definition)] + // docs:start:as_function_definition + fn as_function_definition(self) -> Option {} + // docs:end:as_function_definition +} 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/option.nr b/noir/noir-repo/noir_stdlib/src/option.nr index 8d6d9ef970d..5b6b36679f8 100644 --- a/noir/noir-repo/noir_stdlib/src/option.nr +++ b/noir/noir-repo/noir_stdlib/src/option.nr @@ -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 f9aa98a9ecd..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); diff --git a/noir/noir-repo/scripts/install_bb.sh b/noir/noir-repo/scripts/install_bb.sh index 91b9180f138..015091c07cf 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.48.0" +VERSION="0.51.1" BBUP_PATH=~/.bb/bbup 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 6cd13ab0e2f..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,6 +7,9 @@ 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]) { @@ -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/comptime_function_definition/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_function_definition/src/main.nr index ce09ba86e49..8528bbce153 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 @@ -32,6 +32,8 @@ comptime fn function_attr(f: FunctionDefinition) { // Check FunctionDefinition::name assert_eq(f.name(), quote { foo }); + + assert(f.has_named_attribute(quote { function_attr })); } #[mutate_add_one] @@ -49,7 +51,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_type/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_type/src/main.nr index f0b53a392ed..0b15c5605b3 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 @@ -110,9 +110,15 @@ fn main() { 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(); + + // Check Type::as_str + let str_type = quote { str<10> }.as_type(); + let constant = str_type.as_str().unwrap(); + assert_eq(constant.as_constant().unwrap(), 10); } } +// docs:start:implements_example fn function_with_where(_x: T) where T: SomeTrait { comptime { @@ -123,3 +129,4 @@ fn function_with_where(_x: T) where T: SomeTrait { 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..26be3135133 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/inject_context_attribute/src/main.nr @@ -0,0 +1,63 @@ +struct Context { + value: Field, +} + +#[inject_context] +fn foo(x: Field) { + if true { + // 20 + 1 => 21 + bar(qux(x + 1) + zero()); + } 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 zero() -> Field { + 0 +} + +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().modify(|expr| mapping_function(expr, f)); + f.set_body(body); +} + +fn mapping_function(expr: Expr, f: FunctionDefinition) -> Option { + expr.as_function_call().and_then( + |func_call: (Expr, [Expr])| { + let (name, arguments) = func_call; + name.resolve(Option::some(f)).as_function_definition().and_then( + |function_definition: FunctionDefinition| { + if function_definition.has_named_attribute(quote { inject_context }) { + let arguments = arguments.push_front(quote { _context }.as_expr().unwrap()); + let arguments = arguments.map(|arg: Expr| arg.quoted()).join(quote { , }); + Option::some(quote { $name($arguments) }.as_expr().unwrap()) + } else { + Option::none() + } + } + )} + ) +} + +fn main() { + let context = Context { value: 42 }; + foo(context, 20); +} 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/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/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 21fd4006c2a..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)); } 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/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..ea0a0f2e8a7 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/sha256_regression/Prover.toml @@ -0,0 +1,14 @@ +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] +msg_big_with_padding = [48, 130, 1, 37, 2, 1, 0, 48, 11, 6, 9, 96, 134, 72, 1, 101, 3, 4, 2, 1, 48, 130, 1, 17, 48, 37, 2, 1, 1, 4, 32, 176, 223, 31, 133, 108, 84, 158, 102, 70, 11, 165, 175, 196, 12, 201, 130, 25, 131, 46, 125, 156, 194, 28, 23, 55, 133, 157, 164, 135, 136, 220, 78, 48, 37, 2, 1, 2, 4, 32, 190, 82, 180, 235, 222, 33, 79, 50, 152, 136, 142, 35, 116, 224, 6, 242, 156, 141, 128, 248, 10, 61, 98, 86, 248, 45, 207, 210, 90, 232, 175, 38, 48, 37, 2, 1, 3, 4, 32, 0, 194, 104, 108, 237, 246, 97, 230, 116, 198, 69, 110, 26, 87, 17, 89, 110, 199, 108, 250, 36, 21, 39, 87, 110, 102, 250, 213, 174, 131, 171, 174, 48, 37, 2, 1, 11, 4, 32, 136, 155, 87, 144, 111, 15, 152, 127, 85, 25, 154, 81, 20, 58, 51, 75, 193, 116, 234, 0, 60, 30, 29, 30, 183, 141, 72, 247, 255, 203, 100, 124, 48, 37, 2, 1, 12, 4, 32, 41, 234, 106, 78, 31, 11, 114, 137, 237, 17, 92, 71, 134, 47, 62, 78, 189, 233, 201, 214, 53, 4, 47, 189, 201, 133, 6, 121, 34, 131, 64, 142, 48, 37, 2, 1, 13, 4, 32, 91, 222, 210, 193, 62, 222, 104, 82, 36, 41, 138, 253, 70, 15, 148, 208, 156, 45, 105, 171, 241, 195, 185, 43, 217, 162, 146, 201, 222, 89, 238, 38, 48, 37, 2, 1, 14, 4, 32, 76, 123, 216, 13, 51, 227, 72, 245, 59, 193, 238, 166, 103, 49, 23, 164, 171, 188, 194, 197, 156, 187, 249, 28, 198, 95, 69, 15, 182, 56, 54, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] +msg_big_no_padding = [48, 130, 1, 37, 2, 1, 0, 48, 11, 6, 9, 96, 134, 72, 1, 101, 3, 4, 2, 1, 48, 130, 1, 17, 48, 37, 2, 1, 1, 4, 32, 176, 223, 31, 133, 108, 84, 158, 102, 70, 11, 165, 175, 196, 12, 201, 130, 25, 131, 46, 125, 156, 194, 28, 23, 55, 133, 157, 164, 135, 136, 220, 78, 48, 37, 2, 1, 2, 4, 32, 190, 82, 180, 235, 222, 33, 79, 50, 152, 136, 142, 35, 116, 224, 6, 242, 156, 141, 128, 248, 10, 61, 98, 86, 248, 45, 207, 210, 90, 232, 175, 38, 48, 37, 2, 1, 3, 4, 32, 0, 194, 104, 108, 237, 246, 97, 230, 116, 198, 69, 110, 26, 87, 17, 89, 110, 199, 108, 250, 36, 21, 39, 87, 110, 102, 250, 213, 174, 131, 171, 174, 48, 37, 2, 1, 11, 4, 32, 136, 155, 87, 144, 111, 15, 152, 127, 85, 25, 154, 81, 20, 58, 51, 75, 193, 116, 234, 0, 60, 30, 29, 30, 183, 141, 72, 247, 255, 203, 100, 124, 48, 37, 2, 1, 12, 4, 32, 41, 234, 106, 78, 31, 11, 114, 137, 237, 17, 92, 71, 134, 47, 62, 78, 189, 233, 201, 214, 53, 4, 47, 189, 201, 133, 6, 121, 34, 131, 64, 142, 48, 37, 2, 1, 13, 4, 32, 91, 222, 210, 193, 62, 222, 104, 82, 36, 41, 138, 253, 70, 15, 148, 208, 156, 45, 105, 171, 241, 195, 185, 43, 217, 162, 146, 201, 222, 89, 238, 38, 48, 37, 2, 1, 14, 4, 32, 76, 123, 216, 13, 51, 227, 72, 245, 59, 193, 238, 166, 103, 49, 23, 164, 171, 188, 194, 197, 156, 187, 249, 28, 198, 95, 69, 15, 182, 56, 54, 38] +message_size = 297 + +# 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] +result_big_with_padding = [32, 85, 108, 174, 127, 112, 178, 182, 8, 43, 134, 123, 192, 211, 131, 66, 184, 240, 212, 181, 240, 180, 106, 195, 24, 117, 54, 129, 19, 10, 250, 53] \ No newline at end of file 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..83049640ac4 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/sha256_regression/src/main.nr @@ -0,0 +1,39 @@ +// A bunch of different test cases for sha256_var in the stdlib +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], + // This message is only 297 elements and we want to hash only a variable amount + msg_big_with_padding: [u8; 700], + // This is the same as `msg_big_with_padding` but with no padding + msg_big_no_padding: [u8; 297], + message_size: u64, + result_big_with_padding: 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); + + let hash_padding = std::hash::sha256_var(msg_big_with_padding, message_size); + assert_eq(hash_padding, result_big_with_padding); + + let hash_no_padding = std::hash::sha256_var(msg_big_no_padding, message_size); + assert_eq(hash_no_padding, result_big_with_padding); +} 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/verify_honk_proof/Prover.toml b/noir/noir-repo/test_programs/execution_success/verify_honk_proof/Prover.toml deleted file mode 100644 index deec22a12a8..00000000000 --- a/noir/noir-repo/test_programs/execution_success/verify_honk_proof/Prover.toml +++ /dev/null @@ -1,4 +0,0 @@ -key_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" -proof = ["0x0000000000000000000000000000000000000000000000000000000000000040", "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", "0x0000000000000000000000000000006cf7dd96d7636fda5953191b1ad776d491", "0x00000000000000000000000000000000001633d881a08d136e834cb13a28fcc6", "0x00000000000000000000000000000001254956cff6908b069fca0e6cf1c47eb1", "0x000000000000000000000000000000000006f4d4dd3890e997e75e75886bf8f7", "0x0000000000000000000000000000006cf7dd96d7636fda5953191b1ad776d491", "0x00000000000000000000000000000000001633d881a08d136e834cb13a28fcc6", "0x00000000000000000000000000000001254956cff6908b069fca0e6cf1c47eb1", "0x000000000000000000000000000000000006f4d4dd3890e997e75e75886bf8f7", "0x000000000000000000000000000000f968b227a358a305607f3efc933823d288", "0x00000000000000000000000000000000000eaf8adb390375a76d95e918b65e08", "0x000000000000000000000000000000bb34b4b447aae56f5e24f81c3acd6d547f", "0x00000000000000000000000000000000002175d012746260ebcfe339a91a81e1", "0x000000000000000000000000000000286fcda0e28617c86e195005b9f2efc555", "0x00000000000000000000000000000000000dc409eb684b23f6a97175bcb9b486", "0x000000000000000000000000000000e8de6a193cd36414f598bc7c48d67c3b59", "0x00000000000000000000000000000000002a8a791544cad8c712de871e3de50a", "0x000000000000000000000000000000d6f1e64b562df0f17ecc6aa46392f8d5a3", "0x00000000000000000000000000000000000aac977763f33fd6a360ccc50a827a", "0x000000000000000000000000000000899fa957f5597c6419e3ead9843d21d917", "0x000000000000000000000000000000000016c4611846952bd6833c35fb11c0da", "0x013dbfbfbfb2ae7d524edb15343e551d9510b3116223baaa67312d17652f2fb1", "0x2f268eb3217ef1ac66016aa14d43033f932335371795b5e6dcb0c87c8ad0d050", "0x2d5dbd52e00ae837e9868289fbe9057f16ea5b76c7e362603e8883f0de4b3e94", "0x0e357b6a266c20d5e546c2931475eb044d7e75e08ec31b5e8623aec30f964323", "0x0a9ace4dea44d0a2e8d12d495a683f508714356656aea3882436b729ead24165", "0x0c17102a98ccb76faf0f78d669ee9cfb694849896787c985225d92e1af3cab35", "0x09cc7cb719deb139c84fd9fa273e862a1b5d1cec2501c6cd8ba3c37ca06ac07f", "0x15a0369f3f95d53687dfe79483baf75597d8b281fe0595caf1f7c9ccf99d985e", "0x17fb53a42b3d1fa5d26ab19dfcc0d74d1781cee0be98dcc492c22e8f3442c4db", "0x291d6810fc6afc5c2254fd283843a74c85a77275eee3049ea8ed9c88e02a99b8", "0x0ad40d1627c31247dfb894584a71f8599cfcb85afe84b20186fc07fccae1aa4a", "0x251cd908fb4e9fe88660f2303f8d7e4d7886da32fddc0319a842b99543659c0b", "0x1885bdea3dd82085ca67502ebec8ad87213493e18a06cfa27e2c69810481b4a7", "0x239ab5ba86866bc6705091f82a6a29444dc76b0e7d94cede7eb745cce36ab2cf", "0x088d29a03baa491845d152124189dfb8bf70ba9bf1fb00c379199dbb0195c663", "0x18c9fbe3227988d2da599eba82d60f4de25b442b663585fdc611e37305fa77fc", "0x010242ae641a8cc4d06b5d24e38d9fa6254f981e28f238ccf6aad580f780d3f5", "0x00128d34b122e84d7e23276b1f13f5789a562e82c727e9ffcfd7bbaccbe69e04", "0x0776defaf478bfea4db2698542314e27213f63c96e41f98d4d82a47ed6fab55d", "0x273014a360eaaa493e398df82f18d9cae37f4b6c0ead20100cad3f5491805298", "0x2b13528eb9ab6fa705f2b48c9ec6ce054ac984e3adf17d4d73431e8456bf4a3c", "0x22dafe1d63e39cd2effb236da2e131ee1c8cf4049ce504431dcaf98f75c47ad8", "0x1afb5bc7eb8d30d807101357bb290f9c3113523f4aacc1154a27b075e46a4fa4", "0x0782dd7df679163e5f0c126abc901d00f3d7d0856b4c02a199ab691ecd7566e6", "0x2e556c722c99a84a09ffdcc719178277f8e6c9e31a4769270e3b522b944b8ea2", "0x1be933a48dca8ef26202d3f135998ac8bed6947020b7447ffb6033b0e37f2065", "0x2d8ebae210848de2464f5435f1fd4b5467ee938910d7779002614943060bbb32", "0x2da854bbee38a94a6a9c2c85dd05bb4c879173720b67f93f78b9de93cdb427b0", "0x0fa2649472af2e79489c466b58002f8f284f953085ac0a98dfabee85b78f63cf", "0x304a09437636026ef0746c4b8ac1ca0ff250c5630fb5bd03ddafddd7cbde850e", "0x0c83bb3c6ee0faa1646ee4d8dd83f67ec98e5d63ac802f7bdebfcdf21dee62f1", "0x229d7e4524b30c18a6b94f0054e6d2ea8eb2396f58f9c808d2c9f991e2be2399", "0x1265bf5e1aaddeae09242b1435e2f8f9e7487bf76a0461752777f6ea1ff75ad6", "0x2f32f53281b7a363d6bec84ca21c71c3206d906b036e8b36b0702780e3b1b870", "0x017fb18c9aef4d6d2bc99f5d7f9a002c8921fcd7c7ba69bf05930b55c2829cb7", "0x2ec761c02ef6f2eefb7c9b2d6df71795d0ce0820f86797e2e11415cb5b122f22", "0x2b1722960f42a1b40ffae3e4b9419fc8ff5cb8139a2c7e89af332ba2e95c1b5f", "0x2dafa15594da2318245475c77eae3712429226b3005852e70f567efff0a7d79a", "0x2ed44d7e3d5f44ac8f7c144ee0ba9d369c82428827c19b010384708bbc52a3f9", "0x2777eedda697c7f90aee44fa97cfe62596d16c43fa3545b48d622023ca7a446a", "0x1a47a5c1b0f41905aa0bad6248be8c7887ddea3ad9dfc8462b23a95b073c8a49", "0x093656d571e84ac676a265dd509c98513039552b7a24e001b003ca618cc4ea5c", "0x15c901e8a7ff0f1ae1989b5cfb687975c16716a8014a4052d527d4db4ecbaeb4", "0x08bfa20e83096b0be58e4c96232510c8ef9824c0a62b91ffcc4592b217753a72", "0x021913efbdfbc73aa5f4a97c79f352ac61f71248947f5eb5713c1b107c632703", "0x00df89625aef270fab2a8c33ba742e1375423f4cfb3f63514ae748e004bb8cf4", "0x2455f76c8ee59e93cbe7fe192cf0f766e1399617cabfa230cf27ca2a18cd58d5", "0x150c3e56ea4f6442ed6b11030c98682a8f5e3c9cd6fd18949254a7c79b3cb5b6", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x01e89c6fe644aea7f63301278dbdb4ea29cf4d33f8b0cdcd79cb106e0bf0a753", "0x2d49d23421e253903b8a5d0911642b9ce218cef4e350cf8689704eb1f3ae38d4", "0x072956ca447343d788791fee1ef222f280048ad4aefb6cb7bc96b538f482f525", "0x168176bf15c8ca63457acf05efbe54af452ea41f935ab82c2a96fedde10ba52f", "0x20a13690f13491f7f3b756a1dc3b69a3f96d78355c70289583032a593bfc87bc", "0x273e0a32ab3ef0d3f179b62520b31015ccfc8b53c76a1bb323b41e40ff954596", "0x28019d4b05546b44e35d5dc74375b75dabb6fae49a07381605c60423c6163d26", "0x10beda0b8dd484c63f0937820e2c7e9be832a0031efe3557631608895255ca5f", "0x095a8f04a901526e4d70b1560bfff29b5a3c30347725d1e420c1b30ee2bf8a1c", "0x1fb742e863a5c76262ffec93b3351405b0840b326fa5fffd73f40abcd5f05f05", "0x11fa63cfcb2e603fe4e4668d75f05a2cf22650b84a91d1753e83f0e7ae83b4ad", "0x2872e3d3c431a8b7ee4cec1c2a999a42c40ae33382fbba80a6d4c1a39b2d57a3", "0x17e8c2a5f809f9935d7c6d7cb2f8859a513864b53f53de3d2a14c74cd690bd1a", "0x20a552298d691393ae401382b3015689231ad988d3eb0521d414dcd2e8781053", "0x183eb6bca59a141b4e8136179a258272ec9c25ec80bdb0458b6880c711707a28", "0x03cd147a2a4c8dc272f3e240b8b0090d45e994e5fd40e07a54f6765795cd5ef8", "0x082b135b3a20da4c766242b4258e27dbc050e4b8958bb15431626f2eeed9bd2b", "0x28c894a6a719a32fe8d78ded46bc685ba035e5579c88fbc5bcbc0f09d8c5268b", "0x06418cceff50837f923e63a37c2c534d13d9f59793c3aa6274813baa64d1899e", "0x2b4a27b672f85c4fc697605da213de8b950a629602c5b8c6403e6c1c1065388a", "0x0e2b817c6a79d6d1027f0376fb26ec81a140a4402e2dcdff6152cf01f2f4dbf9", "0x2ae0fbce87dc53f0ff5473117e1c49a8197a14f8eaaec00cb5b10f94e844111f", "0x2368004a1dee06f505e75ada3e9f8cc4c801f6a2068620da51ba11f537453835", "0x2009df8e6f49f67dcaecb93e4a9ef81aaff096136d26f0fe691e14cd580c47da", "0x2e512617136e8da2817856e57f13087a75fcc512faefc6d4b2eedd73c58a9b35", "0x2848fcd535bd7c8017ca331a14919aa492ed05b04e9d0745480d291205eac8dc", "0x19bb0990cb37f3a8f6c3db78219b07d6accd08e889586660e92dd6000755f09a", "0x15520c8158b2e36c40c5fa46d5281c45d3df2c7f5d974a1f9549bfca6cbceaea", "0x0e285f4df658d99922c286c5a253d6f6f37aa6c52d7a0fc1a20f3e6da9df23e1", "0x0f9cd4667f4c1e86f83eda9e752a05c0cc630b0827a93a68322fa258dffb0f24", "0x12d8b0dbbea3dccfe5d2dd090daf8ab4d2fac74fada9c49875b0c9122663a8ad", "0x2e8c814d93f027ecff08c4e58555aadfc0f9ec3889eff2150f2b5bb6c557add0", "0x013516a1456c5831aba87e4057878f6f3f18471e0674fd1e89be3e18351ec394", "0x14418aa79dc84fd791d5638bdc103786ef8181a714ee8e022d3a1e792cbc7959", "0x14418aa79dc84fd791d5638bdc103786ef8181a714ee8e022d3a1e792cbc7959", "0x25c5e6c96a39bb36e19106d4049b675f0279084cc757c4e2acf6e497c61056a2", "0x231aaafcf2a4c6fd8da18ce5ae5b33790f2c306a2692c6383c9a0787c50ac269", "0x0a5f7665f0997081f9b38ec64e9a18542ac3a9648060f8cc720fc04669224730", "0x0f1c9d9d1ac6f62825c6038117ed30540be434e8fd2d88150dcd4fece39b335a", "0x1308871c8fcb09f07e5257f5cc5678d98842a8d18b2af09b5132d9af3cb1893e", "0x28801985290dac4eba72ed01ee06fe88f6fc533dc1a46bd86e2d35be8021b042", "0x14407f38cfba3cc61fca173b41133ab05a1c176caf8bb597588b01817e9eeaa3", "0x0ea1a9f6f95f6193e512a7bd3db0c147f66687662934aed53cb657935b1e4eb9", "0x1bc4ab6eacd61b5fd9e414b0186ef5deaadaf59aa9e53cb8d8812255baa28109", "0x00000000000000000000000000000093a4da68a2fac0ee94841efdfc57eb748c", "0x00000000000000000000000000000000001c22f1f5f927bee6adb649cc132391", "0x0000000000000000000000000000003d0c2acea76c551f58876b3c35f19f345a", "0x00000000000000000000000000000000002e94fded0a0b7f4fd1c882fd2a4e52", "0x00000000000000000000000000000022e23b6fa0f72844bf8f60ea140cca5663", "0x000000000000000000000000000000000013380f284bf3cb98b9a7cbae7d702b", "0x000000000000000000000000000000942a13cf93056815c3f7439c9eed0a103e", "0x00000000000000000000000000000000002be14bec02c6dae4625d32866de4fc", "0x000000000000000000000000000000e2a2c75dc664c12695b4f7795c61f92669", "0x000000000000000000000000000000000000725da448f376bde6cf63bcf79463", "0x000000000000000000000000000000f54eee585f8ab367dc66a587e1d4cdbd8c", "0x0000000000000000000000000000000000071106624ae5623a070f0addc18433", "0x000000000000000000000000000000d60352bea3b2adb311b1a3beb25acb8aed", "0x00000000000000000000000000000000001965b7c781e33f94e90c743c7881ed", "0x0000000000000000000000000000006458a2aa57539e2b192f9c3ed69f9fb674", "0x00000000000000000000000000000000001fc9c667723a4e66d752c6b426d444", "0x0000000000000000000000000000008d1ff1c5d59a463c5b46bcf52f41ad3c63", "0x00000000000000000000000000000000001b3e73df070a35c49a03fab1c76e9b", "0x0000000000000000000000000000001c17a62b6c0a7ab14de83391e06f780adb", "0x000000000000000000000000000000000012c7fbe2591b9ae72dd526e4ed1d7f", "0x000000000000000000000000000000a758fa0c72d6a93155cb18b3fcc7defd34", "0x00000000000000000000000000000000000cea12961770ce7cb6f2a4aed009fe", "0x000000000000000000000000000000ef6e9647803aac315fa6d287e0e66f4767", "0x0000000000000000000000000000000000259a82b8d6c6015cc51d2681f26ad4", "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", "0x0000000000000000000000000000008152b373c87004bef7d2c55ec8c540b67f", "0x00000000000000000000000000000000000a55be5fdcb0a0dce4976d7bb78b0c", "0x000000000000000000000000000000f749ea03f04ac964706139b9d1db595ecb", "0x000000000000000000000000000000000013218e14dae80c066b4e46e9309fb2", "0x0000000000000000000000000000004bbd7f950c36ce69db39e2b234a9e3f9b0", "0x00000000000000000000000000000000002a0c3994d892ca5ea26984abbb30fb", "0x0000000000000000000000000000006c1b39306846620bd546ac2c897834f259", "0x000000000000000000000000000000000020350b9f507d6e25961a11be3e494b"] -public_inputs = ["0x0000000000000000000000000000000000000000000000000000000000000003"] -verification_key = ["0x0000000000000000000000000000000000000000000000000000000000000040", "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", "0x000000000000000000000000000000ee40d90bea71fba7a412dd61fcf34e8ceb", "0x0000000000000000000000000000000000140b0936c323fd2471155617b6af56", "0x0000000000000000000000000000002b90071823185c5ff8e440fd3d73b6fefc", "0x00000000000000000000000000000000002b6c10790a5f6631c87d652e059df4", "0x00000000000000000000000000000029a17181c7934fc3fdbd352eac5cb521b9", "0x00000000000000000000000000000000001f497cbf5284ff29a2d336e5991999", "0x000000000000000000000000000000072bd9c0c6beda1fdee6d4ff0432ba9e1b", "0x000000000000000000000000000000000013ea38a0bd2aa751a490a724fac818", "0x000000000000000000000000000000c599f63dcd3edd49f08ae5c3141c1e3493", "0x00000000000000000000000000000000002bdb36be0bea09950dd32a8ccf6fbc", "0x00000000000000000000000000000047f27f29724e7f19eba0340256a0bd4b7d", "0x00000000000000000000000000000000001c1c5ccf87a962129ca785f8f35120", "0x000000000000000000000000000000c5c71efdae00679bbe4a95096e012b1817", "0x000000000000000000000000000000000017a365de041e317817d0135f2b48e0", "0x0000000000000000000000000000008ae711ac402f7848d719c93a89ba8d39f1", "0x00000000000000000000000000000000002b6fb40ed8a1935226f4f9786a0499", "0x0000000000000000000000000000002f03a71501d83de1da5715a4e9462d6198", "0x00000000000000000000000000000000001644064443b8546f48eae693af47b8", "0x00000000000000000000000000000083763ab1b6e8fe269b2fe4c7b9c448c08d", "0x000000000000000000000000000000000021d7cc18c59676a8eeb47c0111c251", "0x000000000000000000000000000000b5f937153073e03ea7d51a996e0ebc2e6b", "0x000000000000000000000000000000000011ddd0e26457373eb06e0493177672", "0x000000000000000000000000000000c5f6eb9f6fc8fa99811a4a88c74a6d018b", "0x000000000000000000000000000000000025bcd07a0732c123567834f5109558", "0x000000000000000000000000000000aeb08a0b1a4442189448b4e97490568146", "0x000000000000000000000000000000000002a1744e4771705536a88f07e0f90f", "0x000000000000000000000000000000b938568293bd0724b0ea76c2ec34c4a829", "0x0000000000000000000000000000000000053296e8f3b9ad3af877dfa9c7c2a7", "0x000000000000000000000000000000f0ca1db6323996eba26bdc86dafef9d10b", "0x00000000000000000000000000000000001441a46c58af03d5645d52721d956a", "0x0000000000000000000000000000008bbf8f884013c66c28ba09c2fbd573b656", "0x0000000000000000000000000000000000206c391ca06fac27d1908e94570243", "0x0000000000000000000000000000002d4f5aaed88ba4f79612d53b804ca8f194", "0x00000000000000000000000000000000001674011c96392df08970fa6b7b4cb8", "0x0000000000000000000000000000009f88297c1729d76c4d9306853598c91325", "0x0000000000000000000000000000000000256f51adfcacc3c1e340be4d32d3e9", "0x0000000000000000000000000000000ab9955eec0d74eb799afed2a802b24d75", "0x00000000000000000000000000000000001fcbe43ea105b30d36ed0b21b03411", "0x000000000000000000000000000000d66b1d5433f1aa5305cd1edce7c22de466", "0x00000000000000000000000000000000002331546a256b8a3b751956806680d4", "0x000000000000000000000000000000e97954ad6cd6f45fb15c91434121db4304", "0x00000000000000000000000000000000002e20a97e09d50f227ced47e7a98250", "0x0000000000000000000000000000001ebbc27eb9ebededefba79522eb58ae89b", "0x0000000000000000000000000000000000090efa4974e566e81d1177b85a30be", "0x0000000000000000000000000000005eafa070b9c9632404052642e3bc14f9fd", "0x00000000000000000000000000000000001489068864102daca6a6b8bc4d448b", "0x0000000000000000000000000000009ebc91aaaac036a6477cadbe54e8556dfd", "0x00000000000000000000000000000000000ef6d835e2ed3343b95c82c8c54037", "0x00000000000000000000000000000033b28b529dff46e93af4e7422530478e4a", "0x000000000000000000000000000000000020a86c2f8591bf190bcddcc03c42fb", "0x000000000000000000000000000000a9679d0acc088f7dc27bf6d866bcd2dda2", "0x00000000000000000000000000000000002fb9d0d2d4099402bed74f738f64cc", "0x00000000000000000000000000000023b09f876a29a061582848a8b9a5870c12", "0x00000000000000000000000000000000001d5bb906f03f0d49e9c4791bc43af9", "0x00000000000000000000000000000017aac9854ea240d8ec97bf760c4d4ba870", "0x00000000000000000000000000000000000b227a556c414ada0dc75bb303e30e", "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000002", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000009b624fa65d1a24b7f14a8f25f3789622af", "0x000000000000000000000000000000000013d47bff8c630e847b70e2732fd3f0", "0x00000000000000000000000000000061d21663e93132f32921075f4c936a84df", "0x00000000000000000000000000000000001a74ca4e118fb480b9b999902989a3"] 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 2da4082295e..00000000000 --- a/noir/noir-repo/test_programs/execution_success/verify_honk_proof/src/main.nr +++ /dev/null @@ -1,17 +0,0 @@ - -// This circuit aggregates a single Honk proof from `assert_statement_recursive`. -global SIZE_OF_PROOF_IF_LOGN_IS_28 : u32 = 439; -global HONK_IDENTIFIER : u32 = 1; -fn main( - verification_key: [Field; 128], - // 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/noir_test_success/comptime_expr/Nargo.toml b/noir/noir-repo/test_programs/noir_test_success/comptime_expr/Nargo.toml new file mode 100644 index 00000000000..a40da9c5f28 --- /dev/null +++ b/noir/noir-repo/test_programs/noir_test_success/comptime_expr/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "comptime_expr" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file 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..1488783c72c --- /dev/null +++ b/noir/noir-repo/test_programs/noir_test_success/comptime_expr/src/main.nr @@ -0,0 +1,614 @@ +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.modify(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_assert() { + comptime + { + let expr = quote { assert(true) }.as_expr().unwrap(); + let (predicate, msg) = expr.as_assert().unwrap(); + assert_eq(predicate.as_bool().unwrap(), true); + assert(msg.is_none()); + + let expr = quote { assert(false, "oops") }.as_expr().unwrap(); + let (predicate, msg) = expr.as_assert().unwrap(); + assert_eq(predicate.as_bool().unwrap(), false); + assert(msg.is_some()); + } + } + + #[test] + fn test_expr_mutate_for_assert() { + comptime + { + let expr = quote { assert(1) }.as_expr().unwrap(); + let expr = expr.modify(times_two); + let (predicate, msg) = expr.as_assert().unwrap(); + assert_eq(predicate.as_integer().unwrap(), (2, false)); + assert(msg.is_none()); + + let expr = quote { assert(1, 2) }.as_expr().unwrap(); + let expr = expr.modify(times_two); + let (predicate, msg) = expr.as_assert().unwrap(); + assert_eq(predicate.as_integer().unwrap(), (2, false)); + assert_eq(msg.unwrap().as_integer().unwrap(), (4, 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.modify(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.modify(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.modify(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.modify(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.modify(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.modify(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.modify(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.modify(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.modify(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.modify(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.modify(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.modify(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.modify(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.modify(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.modify(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.modify(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.modify(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.modify(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()); + } + } + + #[test] + fn test_resolve_to_function_definition() { + comptime + { + let expr = quote { times_two }.as_expr().unwrap(); + let func = expr.resolve(Option::none()).as_function_definition().unwrap(); + assert_eq(func.name(), quote { times_two }); + assert_eq(func.parameters().len(), 1); + } + } + + 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/rebuild.sh b/noir/noir-repo/test_programs/rebuild.sh index 452c91bfc09..1f2e199e814 100755 --- a/noir/noir-repo/test_programs/rebuild.sh +++ b/noir/noir-repo/test_programs/rebuild.sh @@ -80,7 +80,7 @@ fi rm -f "$current_dir/rebuild.log" # Process directories in parallel -parallel -j0 process_dir {} "$current_dir" ::: "${dirs_to_process[@]}" +parallel -j7 process_dir {} "$current_dir" ::: ${dirs_to_process[@]} # Check rebuild.log for failures if [ -f "$current_dir/rebuild.log" ]; then diff --git a/noir/noir-repo/tooling/debugger/src/context.rs b/noir/noir-repo/tooling/debugger/src/context.rs index 890732b579c..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![] } @@ -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, diff --git a/noir/noir-repo/tooling/lsp/Cargo.toml b/noir/noir-repo/tooling/lsp/Cargo.toml index 353a6ade904..c15895d801f 100644 --- a/noir/noir-repo/tooling/lsp/Cargo.toml +++ b/noir/noir-repo/tooling/lsp/Cargo.toml @@ -31,7 +31,7 @@ async-lsp = { workspace = true, features = ["omni-trait"] } serde_with = "3.2.0" thiserror.workspace = true fm.workspace = true -rayon = "1.8.0" +rayon.workspace = true fxhash.workspace = true convert_case = "0.6.0" diff --git a/noir/noir-repo/tooling/lsp/src/notifications/mod.rs b/noir/noir-repo/tooling/lsp/src/notifications/mod.rs index 4d2186badc3..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, }; diff --git a/noir/noir-repo/tooling/lsp/src/requests/completion.rs b/noir/noir-repo/tooling/lsp/src/requests/completion.rs index 28388230e94..cb27a9684a1 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/completion.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/completion.rs @@ -15,21 +15,24 @@ use lsp_types::{CompletionItem, CompletionItemKind, CompletionParams, Completion use noirc_errors::{Location, Span}; use noirc_frontend::{ ast::{ - AsTraitPath, BlockExpression, ConstructorExpression, Expression, ExpressionKind, - ForLoopStatement, Ident, IfExpression, LValue, Lambda, LetStatement, - MemberAccessExpression, NoirFunction, NoirStruct, NoirTraitImpl, Path, PathKind, - PathSegment, Pattern, Statement, StatementKind, TraitItem, TypeImpl, UnresolvedGeneric, - UnresolvedGenerics, UnresolvedType, UnresolvedTypeData, UseTree, UseTreeKind, + AsTraitPath, BlockExpression, CallExpression, ConstructorExpression, Expression, + ExpressionKind, ForLoopStatement, GenericTypeArgs, Ident, IfExpression, ItemVisibility, + Lambda, LetStatement, MemberAccessExpression, MethodCallExpression, NoirFunction, + NoirStruct, NoirTraitImpl, Path, PathKind, PathSegment, Pattern, Statement, TypeImpl, + UnresolvedGeneric, UnresolvedGenerics, UnresolvedType, UseTree, UseTreeKind, Visitor, }, 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::traits::Trait, macros_api::{ModuleDefId, NodeInterner}, node_interner::ReferenceId, - parser::{Item, ItemKind}, + parser::{Item, ItemKind, ParsedSubModule}, ParsedModule, StructType, Type, }; use sort_text::underscore_sort_text; @@ -44,7 +47,6 @@ mod completion_items; mod kinds; mod sort_text; mod tests; -mod traversal; pub(crate) fn on_completion_request( state: &mut LspState, @@ -159,7 +161,7 @@ impl<'a> NodeFinder<'a> { } fn find(&mut self, parsed_module: &ParsedModule) -> Option { - self.find_in_parsed_module(parsed_module); + parsed_module.accept(self); if self.completion_items.is_empty() { None @@ -177,360 +179,6 @@ impl<'a> NodeFinder<'a> { } } - 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; - } - - match &item.kind { - ItemKind::Import(use_tree) => { - let mut prefixes = Vec::new(); - self.find_in_use_tree(use_tree, &mut prefixes); - } - ItemKind::Submodules(parsed_sub_module) => { - // Switch `self.module_id` to the submodule - let previous_module_id = self.module_id; - - let def_map = &self.def_maps[&self.module_id.krate]; - let Some(module_data) = def_map.modules().get(self.module_id.local_id.0) else { - return; - }; - if let Some(child_module) = module_data.children.get(&parsed_sub_module.name) { - self.module_id = - ModuleId { krate: self.module_id.krate, local_id: *child_module }; - } - - 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), - ItemKind::Impl(type_impl) => self.find_in_type_impl(type_impl), - ItemKind::Global(let_statement) => self.find_in_let_statement(let_statement, false), - ItemKind::TypeAlias(noir_type_alias) => self.find_in_noir_type_alias(noir_type_alias), - ItemKind::Struct(noir_struct) => self.find_in_noir_struct(noir_struct), - ItemKind::Trait(noir_trait) => self.find_in_noir_trait(noir_trait), - ItemKind::ModuleDecl(_) => (), - } - } - - fn find_in_noir_function(&mut self, noir_function: &NoirFunction) { - let old_type_parameters = self.type_parameters.clone(); - self.collect_type_parameters_in_generics(&noir_function.def.generics); - - for param in &noir_function.def.parameters { - self.find_in_unresolved_type(¶m.typ); - } - - self.find_in_function_return_type(&noir_function.def.return_type); - - self.local_variables.clear(); - for param in &noir_function.def.parameters { - self.collect_local_variables(¶m.pattern); - } - - self.find_in_block_expression(&noir_function.def.body); - - self.type_parameters = old_type_parameters; - } - - fn find_in_noir_trait_impl(&mut self, noir_trait_impl: &NoirTraitImpl) { - self.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); - - for item in &noir_trait_impl.items { - self.find_in_trait_impl_item(item); - } - - self.type_parameters.clear(); - } - - 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); - - for (method, span) in &type_impl.methods { - self.find_in_noir_function(method); - - // Optimization: stop looking in functions past the completion cursor - if span.end() as usize > self.byte_index { - break; - } - } - - self.type_parameters.clear(); - } - - fn find_in_noir_struct(&mut self, noir_struct: &NoirStruct) { - self.type_parameters.clear(); - self.collect_type_parameters_in_generics(&noir_struct.generics); - - for (_name, unresolved_type) in &noir_struct.fields { - self.find_in_unresolved_type(unresolved_type); - } - - self.type_parameters.clear(); - } - - fn find_in_trait_item(&mut self, trait_item: &TraitItem) { - match trait_item { - TraitItem::Function { - name: _, - generics, - parameters, - return_type, - where_clause, - body, - } => { - let old_type_parameters = self.type_parameters.clone(); - self.collect_type_parameters_in_generics(generics); - - for (_name, unresolved_type) in parameters { - self.find_in_unresolved_type(unresolved_type); - } - - self.find_in_function_return_type(return_type); - - for unresolved_trait_constraint in where_clause { - self.find_in_unresolved_type(&unresolved_trait_constraint.typ); - } - - if let Some(body) = body { - self.local_variables.clear(); - for (name, _) in parameters { - self.local_variables.insert(name.to_string(), name.span()); - } - self.find_in_block_expression(body); - }; - - self.type_parameters = old_type_parameters; - } - TraitItem::Constant { name: _, typ, default_value } => { - self.find_in_unresolved_type(typ); - - if let Some(default_value) = default_value { - self.find_in_expression(default_value); - } - } - TraitItem::Type { name: _ } => (), - } - } - - fn find_in_block_expression(&mut self, block_expression: &BlockExpression) { - let old_local_variables = self.local_variables.clone(); - for statement in &block_expression.statements { - self.find_in_statement(statement); - - // Optimization: stop looking in statements past the completion cursor - if statement.span.end() as usize > self.byte_index { - break; - } - } - self.local_variables = old_local_variables; - } - - fn find_in_statement(&mut self, statement: &Statement) { - match &statement.kind { - StatementKind::Let(let_statement) => { - self.find_in_let_statement(let_statement, true); - } - 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) => { - // When entering a comptime block, regular local variables shouldn't be offered anymore - let old_local_variables = self.local_variables.clone(); - self.local_variables.clear(); - - self.find_in_statement(statement); - - self.local_variables = old_local_variables; - } - StatementKind::Semi(expression) => { - self.find_in_expression(expression); - } - StatementKind::Break | StatementKind::Continue | StatementKind::Error => (), - } - } - - fn find_in_let_statement( - &mut self, - let_statement: &LetStatement, - collect_local_variables: bool, - ) { - self.find_in_unresolved_type(&let_statement.r#type); - self.find_in_expression(&let_statement.expression); - - if collect_local_variables { - self.collect_local_variables(&let_statement.pattern); - } - } - - fn find_in_for_loop_statement(&mut self, for_loop_statement: &ForLoopStatement) { - let old_local_variables = self.local_variables.clone(); - let ident = &for_loop_statement.identifier; - self.local_variables.insert(ident.to_string(), ident.span()); - - self.find_in_for_range(&for_loop_statement.range); - self.find_in_expression(&for_loop_statement.block); - - self.local_variables = old_local_variables; - } - - fn find_in_lvalue(&mut self, lvalue: &LValue) { - match lvalue { - LValue::Ident(ident) => { - if self.byte == Some(b'.') && ident.span().end() as usize == self.byte_index - 1 { - let location = Location::new(ident.span(), self.file); - if let Some(ReferenceId::Local(definition_id)) = - self.interner.find_referenced(location) - { - let typ = self.interner.definition_type(definition_id); - let prefix = ""; - self.complete_type_fields_and_methods(&typ, prefix); - } - } - } - LValue::MemberAccess { object, field_name: _, span: _ } => self.find_in_lvalue(object), - LValue::Index { array, index, span: _ } => { - self.find_in_lvalue(array); - self.find_in_expression(index); - } - LValue::Dereference(lvalue, _) => self.find_in_lvalue(lvalue), - } - } - - 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); - } - ExpressionKind::MethodCall(method_call_expression) => { - self.find_in_method_call_expression(method_call_expression); - } - 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::Variable(path) => { - self.find_in_path(path, RequestedItems::AnyItems); - } - 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, _) => { - // When entering a comptime block, regular local variables shouldn't be offered anymore - let old_local_variables = self.local_variables.clone(); - self.local_variables.clear(); - - self.find_in_block_expression(block_expression); - - self.local_variables = old_local_variables; - } - 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); - } - ExpressionKind::Quote(_) | ExpressionKind::Resolved(_) | ExpressionKind::Error => (), - } - - // "foo." (no identifier afterwards) is parsed as the expression on the left hand-side of the dot. - // Here we check if there's a dot at the completion position, and if the expression - // ends right before the dot. If so, it means we want to complete the expression's type fields and methods. - // We only do this after visiting nested expressions, because in an expression like `foo & bar.` we want - // to complete for `bar`, not for `foo & bar`. - if self.completion_items.is_empty() - && self.byte == Some(b'.') - && expression.span.end() as usize == self.byte_index - 1 - { - let location = Location::new(expression.span, self.file); - if let Some(typ) = self.interner.type_at_location(location) { - let typ = typ.follow_bindings(); - let prefix = ""; - self.complete_type_fields_and_methods(&typ, prefix); - } - } - } - - fn find_in_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; - } - - for (_field_name, expression) in &constructor_expression.fields { - self.find_in_expression(expression); - } - } - fn complete_constructor_field_name(&mut self, constructor_expression: &ConstructorExpression) { let location = Location::new(constructor_expression.type_name.last_ident().span(), self.file); @@ -558,120 +206,62 @@ impl<'a> NodeFinder<'a> { } } - fn find_in_member_access_expression( - &mut self, - member_access_expression: &MemberAccessExpression, - ) { - let ident = &member_access_expression.rhs; - - if self.byte_index == ident.span().end() as usize { - // Assuming member_access_expression is of the form `foo.bar`, we are right after `bar` - let location = Location::new(member_access_expression.lhs.span, self.file); - if let Some(typ) = self.interner.type_at_location(location) { - let typ = typ.follow_bindings(); - let prefix = ident.to_string().to_case(Case::Snake); - self.complete_type_fields_and_methods(&typ, &prefix); - return; - } - } - - self.find_in_expression(&member_access_expression.lhs); + fn find_in_path(&mut self, path: &Path, requested_items: RequestedItems) { + self.find_in_path_impl(path, requested_items, false); } - fn find_in_if_expression(&mut self, if_expression: &IfExpression) { - self.find_in_expression(&if_expression.condition); - - let old_local_variables = self.local_variables.clone(); - self.find_in_expression(&if_expression.consequence); - self.local_variables = old_local_variables; - - if let Some(alternative) = &if_expression.alternative { - let old_local_variables = self.local_variables.clone(); - self.find_in_expression(alternative); - self.local_variables = old_local_variables; + fn find_in_path_impl( + &mut self, + path: &Path, + requested_items: RequestedItems, + mut in_the_middle: bool, + ) { + if !self.includes_span(path.span) { + return; } - } - fn find_in_lambda(&mut self, lambda: &Lambda) { - for (_, unresolved_type) in &lambda.parameters { - self.find_in_unresolved_type(unresolved_type); - } + let after_colons = self.byte == Some(b':'); - let old_local_variables = self.local_variables.clone(); - for (pattern, _) in &lambda.parameters { - self.collect_local_variables(pattern); - } + let mut idents: Vec = Vec::new(); - self.find_in_expression(&lambda.body); + // 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; - self.local_variables = old_local_variables; - } + // Check if we are at the end of the ident + if self.byte_index == ident.span().end() as usize { + idents.push(ident.clone()); + break; + } - fn find_in_as_trait_path(&mut self, as_trait_path: &AsTraitPath) { - self.find_in_path(&as_trait_path.trait_path, RequestedItems::OnlyTypes); - } + // 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; + } - fn find_in_unresolved_type(&mut self, unresolved_type: &UnresolvedType) { - if !self.includes_span(unresolved_type.span) { - return; - } + idents.push(ident.clone()); - match &unresolved_type.typ { - UnresolvedTypeData::Array(_, unresolved_type) => { - self.find_in_unresolved_type(unresolved_type); - } - UnresolvedTypeData::Slice(unresolved_type) => { - self.find_in_unresolved_type(unresolved_type); - } - UnresolvedTypeData::Parenthesized(unresolved_type) => { - self.find_in_unresolved_type(unresolved_type); - } - UnresolvedTypeData::Named(path, unresolved_types, _) => { - self.find_in_path(path, RequestedItems::OnlyTypes); - self.find_in_unresolved_types(unresolved_types); - } - UnresolvedTypeData::TraitAsType(path, unresolved_types) => { - self.find_in_path(path, RequestedItems::OnlyTypes); - self.find_in_unresolved_types(unresolved_types); - } - UnresolvedTypeData::MutableReference(unresolved_type) => { - self.find_in_unresolved_type(unresolved_type); - } - UnresolvedTypeData::Tuple(unresolved_types) => { - self.find_in_unresolved_types(unresolved_types); - } - UnresolvedTypeData::Function(args, ret, env, _) => { - self.find_in_unresolved_types(args); - self.find_in_unresolved_type(ret); - self.find_in_unresolved_type(env); - } - UnresolvedTypeData::AsTraitPath(as_trait_path) => { - self.find_in_as_trait_path(as_trait_path); + // Stop if the cursor is right after this ident and '::' + if after_colons && self.byte_index == ident.span().end() as usize + 2 { + break; } - UnresolvedTypeData::Expression(_) - | UnresolvedTypeData::FormatString(_, _) - | UnresolvedTypeData::String(_) - | UnresolvedTypeData::Unspecified - | UnresolvedTypeData::Quoted(_) - | UnresolvedTypeData::FieldElement - | UnresolvedTypeData::Integer(_, _) - | UnresolvedTypeData::Bool - | UnresolvedTypeData::Unit - | UnresolvedTypeData::Resolved(_) - | UnresolvedTypeData::Error => (), } - } - fn find_in_path(&mut self, path: &Path, requested_items: RequestedItems) { - // Only offer completions if we are right at the end of the path - if self.byte_index != path.span.end() as usize { - return; + if idents.len() < path.segments.len() { + in_the_middle = true; } - let after_colons = self.byte == Some(b':'); - - let mut idents: Vec = - path.segments.iter().map(|segment| segment.ident.clone()).collect(); let prefix; let at_root; @@ -688,6 +278,21 @@ impl<'a> NodeFinder<'a> { let is_single_segment = !after_colons && idents.is_empty() && path.kind == PathKind::Plain; let module_id; + let module_completion_kind = if after_colons || !idents.is_empty() { + ModuleCompletionKind::DirectChildren + } else { + ModuleCompletionKind::AllVisibleItems + }; + + // 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 { @@ -703,6 +308,7 @@ impl<'a> NodeFinder<'a> { &Type::Struct(struct_type, vec![]), &prefix, FunctionKind::Any, + function_completion_kind, ); return; } @@ -713,25 +319,28 @@ impl<'a> NodeFinder<'a> { 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); + 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); + self.complete_trait_methods( + trait_, + &prefix, + FunctionKind::Any, + function_completion_kind, + ); return; } ModuleDefId::GlobalId(_) => return, } } - let module_completion_kind = if after_colons { - ModuleCompletionKind::DirectChildren - } else { - ModuleCompletionKind::AllVisibleItems - }; - let function_completion_kind = FunctionCompletionKind::NameAndParameters; - self.complete_in_module( module_id, &prefix, @@ -746,7 +355,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 => { @@ -754,7 +363,7 @@ impl<'a> NodeFinder<'a> { self.type_parameters_completion(&prefix); } } - self.complete_auto_imports(&prefix, requested_items); + self.complete_auto_imports(&prefix, requested_items, function_completion_kind); } } @@ -925,17 +534,30 @@ impl<'a> NodeFinder<'a> { }; } - fn complete_type_fields_and_methods(&mut self, typ: &Type, prefix: &str) { + 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); + 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); + return self.complete_type_fields_and_methods( + &type_alias.typ, + prefix, + function_completion_kind, + ); } Type::Tuple(types) => { self.complete_tuple_fields(types); @@ -959,205 +581,656 @@ impl<'a> NodeFinder<'a> { | Type::Error => (), } - self.complete_type_methods(typ, prefix, FunctionKind::SelfType(typ)); + 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, + module_id: ModuleId, + prefix: &str, + path_kind: PathKind, + at_root: bool, + module_completion_kind: ModuleCompletionKind, + function_completion_kind: FunctionCompletionKind, + requested_items: RequestedItems, + ) { + let def_map = &self.def_maps[&module_id.krate]; + let Some(mut module_data) = def_map.modules().get(module_id.local_id.0) else { + return; + }; + + if at_root { + match path_kind { + PathKind::Crate => { + let Some(root_module_data) = def_map.modules().get(def_map.root().0) else { + return; + }; + module_data = root_module_data; + } + PathKind::Super => { + let Some(parent) = module_data.parent else { + return; + }; + let Some(parent_module_data) = def_map.modules().get(parent.0) else { + return; + }; + module_data = parent_module_data; + } + PathKind::Dep => (), + PathKind::Plain => (), + } + } + + let function_kind = FunctionKind::Any; + + let items = match module_completion_kind { + ModuleCompletionKind::DirectChildren => module_data.definitions(), + ModuleCompletionKind::AllVisibleItems => module_data.scope(), + }; + + for ident in items.names() { + let name = &ident.0.contents; + + if name_matches(name, prefix) { + let per_ns = module_data.find_name(ident); + if let Some((module_def_id, 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, 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); + } + } + } + } + } + + if at_root && path_kind == PathKind::Plain { + for dependency in self.dependencies { + let dependency_name = dependency.as_name(); + if name_matches(&dependency_name, prefix) { + self.completion_items.push(crate_completion_item(dependency_name)); + } + } + + if name_matches("crate::", prefix) { + self.completion_items.push(simple_completion_item( + "crate::", + CompletionItemKind::KEYWORD, + None, + )); + } + + if module_data.parent.is_some() && name_matches("super::", prefix) { + self.completion_items.push(simple_completion_item( + "super::", + CompletionItemKind::KEYWORD, + None, + )); + } + } + } + + fn resolve_module(&self, segments: Vec) -> Option { + if let Some(ModuleDefId::ModuleId(module_id)) = self.resolve_path(segments) { + Some(module_id) + } else { + None + } + } + + fn resolve_path(&self, segments: Vec) -> Option { + let 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); + if let Ok(path_resolution) = path_resolver.resolve(self.def_maps, path, &mut None) { + return Some(path_resolution.module_def_id); + } + + // 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); + } + } + + None + } + + fn includes_span(&self, span: Span) -> bool { + span.start() as usize <= self.byte_index && self.byte_index <= span.end() as usize + } +} + +impl<'a> Visitor for NodeFinder<'a> { + fn visit_item(&mut self, item: &Item) -> bool { + 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; + } + } + + self.includes_span(item.span) + } + + fn visit_import(&mut self, use_tree: &UseTree) -> bool { + let mut prefixes = Vec::new(); + self.find_in_use_tree(use_tree, &mut prefixes); + false + } + + fn visit_parsed_submodule(&mut self, parsed_sub_module: &ParsedSubModule, span: Span) -> bool { + // Switch `self.module_id` to the submodule + let previous_module_id = self.module_id; + + let def_map = &self.def_maps[&self.module_id.krate]; + let Some(module_data) = def_map.modules().get(self.module_id.local_id.0) else { + return false; + }; + if let Some(child_module) = module_data.children.get(&parsed_sub_module.name) { + self.module_id = ModuleId { krate: self.module_id.krate, local_id: *child_module }; + } + + let old_auto_import_line = self.auto_import_line; + self.nesting += 1; + + if let Some(lsp_location) = to_lsp_location(self.files, self.file, span) { + self.auto_import_line = (lsp_location.range.start.line + 1) as usize; + } + + parsed_sub_module.accept_children(self); + + // Restore the old module before continuing + self.module_id = previous_module_id; + self.nesting -= 1; + self.auto_import_line = old_auto_import_line; + + false + } + + fn visit_noir_function(&mut self, noir_function: &NoirFunction, span: Span) -> bool { + let old_type_parameters = self.type_parameters.clone(); + self.collect_type_parameters_in_generics(&noir_function.def.generics); + + for param in &noir_function.def.parameters { + param.typ.accept(self); + } + + noir_function.def.return_type.accept(self); + + self.local_variables.clear(); + for param in &noir_function.def.parameters { + self.collect_local_variables(¶m.pattern); + } + + noir_function.def.body.accept(Some(span), self); + + self.type_parameters = old_type_parameters; + + false + } + + fn visit_noir_trait_impl(&mut self, noir_trait_impl: &NoirTraitImpl, _: Span) -> bool { + self.find_in_path(&noir_trait_impl.trait_name, RequestedItems::OnlyTypes); + noir_trait_impl.object_type.accept(self); + + self.type_parameters.clear(); + self.collect_type_parameters_in_generics(&noir_trait_impl.impl_generics); + + for item in &noir_trait_impl.items { + item.accept(self); + } + + self.type_parameters.clear(); + + false + } + + fn visit_type_impl(&mut self, type_impl: &TypeImpl, _: Span) -> bool { + type_impl.object_type.accept(self); + + self.type_parameters.clear(); + self.collect_type_parameters_in_generics(&type_impl.generics); + + for (method, span) in &type_impl.methods { + method.accept(*span, self); + + // Optimization: stop looking in functions past the completion cursor + if span.end() as usize > self.byte_index { + break; + } + } + + self.type_parameters.clear(); + + false + } + + fn visit_noir_struct(&mut self, noir_struct: &NoirStruct, _: Span) -> bool { + self.type_parameters.clear(); + self.collect_type_parameters_in_generics(&noir_struct.generics); + + for (_name, unresolved_type) in &noir_struct.fields { + unresolved_type.accept(self); + } + + self.type_parameters.clear(); + + false + } + + fn visit_trait_item_function( + &mut self, + _name: &Ident, + generics: &UnresolvedGenerics, + parameters: &[(Ident, UnresolvedType)], + return_type: &noirc_frontend::ast::FunctionReturnType, + where_clause: &[noirc_frontend::ast::UnresolvedTraitConstraint], + body: &Option, + ) -> bool { + let old_type_parameters = self.type_parameters.clone(); + self.collect_type_parameters_in_generics(generics); + + for (_name, unresolved_type) in parameters { + unresolved_type.accept(self); + } + + return_type.accept(self); + + for unresolved_trait_constraint in where_clause { + unresolved_trait_constraint.typ.accept(self); + } + + if let Some(body) = body { + self.local_variables.clear(); + for (name, _) in parameters { + self.local_variables.insert(name.to_string(), name.span()); + } + body.accept(None, self); + }; + + self.type_parameters = old_type_parameters; + + false + } + + fn visit_call_expression(&mut self, call_expression: &CallExpression, _: Span) -> bool { + // + // 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 false; + } + } + + // 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 false; + } + } + + true } - fn complete_type_methods(&mut self, typ: &Type, prefix: &str, function_kind: FunctionKind) { - let Some(methods_by_name) = self.interner.get_type_methods(typ) else { - return; - }; - - for (name, methods) in methods_by_name { - for func_id in methods.iter() { - if name_matches(name, prefix) { - if let Some(completion_item) = self.function_completion_item( - func_id, - FunctionCompletionKind::NameAndParameters, - function_kind, - ) { - self.completion_items.push(completion_item); - self.suggested_module_def_ids.insert(ModuleDefId::FunctionId(func_id)); - } - } + fn visit_method_call_expression( + &mut self, + method_call_expression: &MethodCallExpression, + _: Span, + ) -> bool { + // 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 false; } } + + true } - fn complete_trait_methods( + fn visit_block_expression( &mut self, - trait_: &Trait, - prefix: &str, - function_kind: FunctionKind, - ) { - for (name, func_id) in &trait_.method_ids { - if name_matches(name, prefix) { - if let Some(completion_item) = self.function_completion_item( - *func_id, - FunctionCompletionKind::NameAndParameters, - function_kind, - ) { - self.completion_items.push(completion_item); - self.suggested_module_def_ids.insert(ModuleDefId::FunctionId(*func_id)); - } + block_expression: &BlockExpression, + _: Option, + ) -> bool { + let old_local_variables = self.local_variables.clone(); + for statement in &block_expression.statements { + statement.accept(self); + + // Optimization: stop looking in statements past the completion cursor + if statement.span.end() as usize > self.byte_index { + break; } } + self.local_variables = old_local_variables; + + false } - 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 visit_let_statement(&mut self, let_statement: &LetStatement) -> bool { + let_statement.accept_children(self); + self.collect_local_variables(&let_statement.pattern); + false + } + + fn visit_global(&mut self, let_statement: &LetStatement, _: Span) -> bool { + let_statement.accept_children(self); + false + } + + fn visit_comptime_statement(&mut self, statement: &Statement) -> bool { + // 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(); + + statement.accept(self); + + self.local_variables = old_local_variables; + + false + } + + fn visit_for_loop_statement(&mut self, for_loop_statement: &ForLoopStatement) -> bool { + let old_local_variables = self.local_variables.clone(); + let ident = &for_loop_statement.identifier; + self.local_variables.insert(ident.to_string(), ident.span()); + + for_loop_statement.accept_children(self); + + self.local_variables = old_local_variables; + + false + } + + fn visit_lvalue_ident(&mut self, 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, + ); } } } - 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())); - } + fn visit_variable(&mut self, path: &Path, _: Span) -> bool { + self.find_in_path(path, RequestedItems::AnyItems); + false } - #[allow(clippy::too_many_arguments)] - fn complete_in_module( - &mut self, - module_id: ModuleId, - prefix: &str, - path_kind: PathKind, - at_root: bool, - module_completion_kind: ModuleCompletionKind, - function_completion_kind: FunctionCompletionKind, - requested_items: RequestedItems, - ) { - let def_map = &self.def_maps[&module_id.krate]; - let Some(mut module_data) = def_map.modules().get(module_id.local_id.0) else { - return; - }; + fn visit_expression(&mut self, expression: &Expression) -> bool { + expression.accept_children(self); - if at_root { - match path_kind { - PathKind::Crate => { - let Some(root_module_data) = def_map.modules().get(def_map.root().0) else { - return; - }; - module_data = root_module_data; - } - PathKind::Super => { - let Some(parent) = module_data.parent else { - return; - }; - let Some(parent_module_data) = def_map.modules().get(parent.0) else { - return; - }; - module_data = parent_module_data; - } - PathKind::Dep => (), - PathKind::Plain => (), + // "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, + ); } } - let function_kind = FunctionKind::Any; + false + } - let items = match module_completion_kind { - ModuleCompletionKind::DirectChildren => module_data.definitions(), - ModuleCompletionKind::AllVisibleItems => module_data.scope(), - }; + fn visit_comptime_expression( + &mut self, + block_expression: &BlockExpression, + span: Span, + ) -> bool { + // 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(); - for ident in items.names() { - let name = &ident.0.contents; + block_expression.accept(Some(span), self); - 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, - function_kind, - requested_items, - ) { - self.completion_items.push(completion_item); - self.suggested_module_def_ids.insert(module_def_id); - } - } + self.local_variables = old_local_variables; - 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, - function_kind, - requested_items, - ) { - self.completion_items.push(completion_item); - self.suggested_module_def_ids.insert(module_def_id); - } - } - } - } + false + } - if at_root && path_kind == PathKind::Plain { - for dependency in self.dependencies { - let dependency_name = dependency.as_name(); - if name_matches(&dependency_name, prefix) { - self.completion_items.push(crate_completion_item(dependency_name)); - } - } + fn visit_constructor_expression( + &mut self, + constructor_expression: &ConstructorExpression, + _: Span, + ) -> bool { + self.find_in_path(&constructor_expression.type_name, RequestedItems::OnlyTypes); - if name_matches("crate::", prefix) { - self.completion_items.push(simple_completion_item( - "crate::", - CompletionItemKind::KEYWORD, - None, - )); - } + // 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 false; + } - if module_data.parent.is_some() && name_matches("super::", prefix) { - self.completion_items.push(simple_completion_item( - "super::", - CompletionItemKind::KEYWORD, - None, - )); - } + for (_field_name, expression) in &constructor_expression.fields { + expression.accept(self); } + + false } - fn resolve_module(&self, segments: Vec) -> Option { - if let Some(ModuleDefId::ModuleId(module_id)) = self.resolve_path(segments) { - Some(module_id) - } else { - None + fn visit_member_access_expression( + &mut self, + member_access_expression: &MemberAccessExpression, + _: Span, + ) -> bool { + 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 false; + } } + + true } - fn resolve_path(&self, segments: Vec) -> Option { - let last_segment = segments.last().unwrap().clone(); + fn visit_if_expression(&mut self, if_expression: &IfExpression, _: Span) -> bool { + if_expression.condition.accept(self); - let path_segments = segments.into_iter().map(PathSegment::from).collect(); - let path = Path { segments: path_segments, kind: PathKind::Plain, span: Span::default() }; + let old_local_variables = self.local_variables.clone(); + if_expression.consequence.accept(self); + self.local_variables = old_local_variables; - let path_resolver = StandardPathResolver::new(self.root_module_id); - if let Ok(path_resolution) = path_resolver.resolve(self.def_maps, path, &mut None) { - return Some(path_resolution.module_def_id); + if let Some(alternative) = &if_expression.alternative { + let old_local_variables = self.local_variables.clone(); + alternative.accept(self); + self.local_variables = old_local_variables; } - // 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); - } + false + } + + fn visit_lambda(&mut self, lambda: &Lambda, _: Span) -> bool { + for (_, unresolved_type) in &lambda.parameters { + unresolved_type.accept(self); } - None + let old_local_variables = self.local_variables.clone(); + for (pattern, _) in &lambda.parameters { + self.collect_local_variables(pattern); + } + + lambda.body.accept(self); + + self.local_variables = old_local_variables; + + false } - fn includes_span(&self, span: Span) -> bool { - span.start() as usize <= self.byte_index && self.byte_index <= span.end() as usize + fn visit_as_trait_path(&mut self, as_trait_path: &AsTraitPath, _: Span) -> bool { + self.find_in_path(&as_trait_path.trait_path, RequestedItems::OnlyTypes); + + false + } + + fn visit_unresolved_type(&mut self, unresolved_type: &UnresolvedType) -> bool { + self.includes_span(unresolved_type.span) + } + + fn visit_named_type( + &mut self, + path: &Path, + unresolved_types: &GenericTypeArgs, + _: Span, + ) -> bool { + self.find_in_path(path, RequestedItems::OnlyTypes); + unresolved_types.accept(self); + false } } @@ -1218,6 +1291,21 @@ fn module_def_id_from_reference_id(reference_id: ReferenceId) -> Option, +) -> bool { + can_reference_module_id( + def_maps, + current_module_id.krate, + current_module_id.local_id, + target_module_id, + visibility, + ) +} + #[cfg(test)] mod completion_name_matches_tests { use crate::requests::completion::name_matches; 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 index 8d7824502c1..32f777dec34 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/completion/auto_import.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/completion/auto_import.rs @@ -3,7 +3,7 @@ use std::collections::BTreeMap; use lsp_types::{Position, Range, TextEdit}; use noirc_frontend::{ ast::ItemVisibility, - graph::{CrateId, Dependency}, + graph::CrateId, hir::def_map::{CrateDefMap, ModuleId}, macros_api::{ModuleDefId, NodeInterner}, node_interner::ReferenceId, @@ -17,7 +17,12 @@ use super::{ }; impl<'a> NodeFinder<'a> { - pub(super) fn complete_auto_imports(&mut self, prefix: &str, requested_items: RequestedItems) { + 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() { @@ -33,7 +38,7 @@ impl<'a> NodeFinder<'a> { let Some(mut completion_item) = self.module_def_id_completion_item( *module_def_id, name.clone(), - FunctionCompletionKind::NameAndParameters, + function_completion_kind, FunctionKind::Any, requested_items, ) else { @@ -47,7 +52,6 @@ impl<'a> NodeFinder<'a> { &self.module_id, current_module_parent_id, self.interner, - self.dependencies, ); } else { let Some(parent_module) = get_parent_module(self.interner, *module_def_id) @@ -74,7 +78,6 @@ impl<'a> NodeFinder<'a> { &self.module_id, current_module_parent_id, self.interner, - self.dependencies, ); } @@ -149,7 +152,6 @@ fn module_id_path( current_module_id: &ModuleId, current_module_parent_id: Option, interner: &NodeInterner, - dependencies: &[Dependency], ) -> String { if Some(target_module_id) == current_module_parent_id { return "super".to_string(); @@ -162,9 +164,14 @@ fn module_id_path( 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: current_attributes.parent }; + &ModuleId { krate: target_module_id.krate, local_id: parent_local_id }; if current_module_id == parent_module_id { is_relative = true; @@ -186,24 +193,13 @@ fn module_id_path( } } - let crate_id = target_module_id.krate; - let crate_name = if is_relative { - None - } else { - match crate_id { - CrateId::Root(_) => Some("crate".to_string()), - CrateId::Stdlib(_) => Some("std".to_string()), - CrateId::Crate(_) => dependencies - .iter() - .find(|dep| dep.crate_id == crate_id) - .map(|dep| dep.name.to_string()), - CrateId::Dummy => unreachable!("ICE: A dummy CrateId should not be accessible"), + if !is_relative { + // We don't record module attributes for the root module, + // so we handle that case separately + if let CrateId::Root(_) = target_module_id.krate { + segments.push("crate"); } - }; - - if let Some(crate_name) = &crate_name { - segments.push(crate_name); - }; + } 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 75eba7fb3c7..54340075b15 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/completion/builtins.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/completion/builtins.rs @@ -3,21 +3,47 @@ use noirc_frontend::token::Keyword; use strum::IntoEnumIterator; use super::{ - completion_items::{simple_completion_item, snippet_completion_item}, + completion_items::{ + completion_item_with_trigger_parameter_hints_command, 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) { + 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) { - self.completion_items.push(snippet_completion_item( - format!("{}(…)", func.name), - CompletionItemKind::FUNCTION, - format!("{}({})", func.name, func.parameters), - Some(func.description.to_string()), - )); + 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( + completion_item_with_trigger_parameter_hints_command( + snippet_completion_item( + label, + CompletionItemKind::FUNCTION, + insert_text, + description, + ), + ), + ); } } } @@ -71,11 +97,14 @@ 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::TypedExpr => Some("TypedExpr"), Keyword::TypeType => Some("Type"), + Keyword::UnresolvedType => Some("UnresolvedType"), Keyword::As | Keyword::Assert @@ -102,7 +131,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 @@ -180,9 +208,11 @@ pub(super) fn keyword_builtin_function(keyword: &Keyword) -> Option u64 { 0 } + pub fn bar(x: i32) -> u64 { 0 } + fn bar_is_private(x: i32) -> u64 { 0 } } use foo::>|< "#; @@ -469,19 +470,19 @@ mod completion_tests { assert_completion_excluding_auto_import( src, vec![ - snippet_completion_item( + completion_item_with_trigger_parameter_hints_command(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( + completion_item_with_trigger_parameter_hints_command(snippet_completion_item( "assert_eq(…)", CompletionItemKind::FUNCTION, "assert_eq(${1:lhs}, ${2:rhs})", Some("fn(T, T)".to_string()), - ), + )), ], ) .await; @@ -1638,4 +1639,156 @@ mod completion_tests { }) ); } + + #[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 deleted file mode 100644 index b487c8baf36..00000000000 --- a/noir/noir-repo/tooling/lsp/src/requests/completion/traversal.rs +++ /dev/null @@ -1,132 +0,0 @@ -/// This file includes the completion logic that's just about -/// traversing the AST without any additional logic. -use noirc_frontend::{ - ast::{ - ArrayLiteral, AssignStatement, CallExpression, CastExpression, ConstrainStatement, - Expression, ForRange, FunctionReturnType, IndexExpression, InfixExpression, Literal, - MethodCallExpression, NoirTrait, NoirTypeAlias, TraitImplItem, UnresolvedType, - }, - ParsedModule, -}; - -use super::NodeFinder; - -impl<'a> NodeFinder<'a> { - pub(super) fn find_in_parsed_module(&mut self, parsed_module: &ParsedModule) { - for item in &parsed_module.items { - self.find_in_item(item); - } - } - - pub(super) fn find_in_trait_impl_item(&mut self, item: &TraitImplItem) { - match item { - TraitImplItem::Function(noir_function) => self.find_in_noir_function(noir_function), - TraitImplItem::Constant(_, _, _) => (), - TraitImplItem::Type { .. } => (), - } - } - - pub(super) fn find_in_noir_trait(&mut self, noir_trait: &NoirTrait) { - for item in &noir_trait.items { - self.find_in_trait_item(item); - } - } - - pub(super) fn find_in_constrain_statement(&mut self, constrain_statement: &ConstrainStatement) { - self.find_in_expression(&constrain_statement.0); - - if let Some(exp) = &constrain_statement.1 { - self.find_in_expression(exp); - } - } - - pub(super) fn find_in_assign_statement(&mut self, assign_statement: &AssignStatement) { - self.find_in_lvalue(&assign_statement.lvalue); - self.find_in_expression(&assign_statement.expression); - } - - pub(super) fn find_in_for_range(&mut self, for_range: &ForRange) { - match for_range { - ForRange::Range(start, end) => { - self.find_in_expression(start); - self.find_in_expression(end); - } - ForRange::Array(expression) => self.find_in_expression(expression), - } - } - - pub(super) fn find_in_expressions(&mut self, expressions: &[Expression]) { - for expression in expressions { - self.find_in_expression(expression); - } - } - - pub(super) fn find_in_literal(&mut self, literal: &Literal) { - match literal { - Literal::Array(array_literal) => self.find_in_array_literal(array_literal), - Literal::Slice(array_literal) => self.find_in_array_literal(array_literal), - Literal::Bool(_) - | Literal::Integer(_, _) - | Literal::Str(_) - | Literal::RawStr(_, _) - | Literal::FmtStr(_) - | Literal::Unit => (), - } - } - - pub(super) fn find_in_array_literal(&mut self, array_literal: &ArrayLiteral) { - match array_literal { - ArrayLiteral::Standard(expressions) => self.find_in_expressions(expressions), - ArrayLiteral::Repeated { repeated_element, length } => { - self.find_in_expression(repeated_element); - self.find_in_expression(length); - } - } - } - - pub(super) fn find_in_index_expression(&mut self, index_expression: &IndexExpression) { - self.find_in_expression(&index_expression.collection); - self.find_in_expression(&index_expression.index); - } - - pub(super) fn find_in_call_expression(&mut self, call_expression: &CallExpression) { - self.find_in_expression(&call_expression.func); - self.find_in_expressions(&call_expression.arguments); - } - - pub(super) fn find_in_method_call_expression( - &mut self, - method_call_expression: &MethodCallExpression, - ) { - self.find_in_expression(&method_call_expression.object); - self.find_in_expressions(&method_call_expression.arguments); - } - - pub(super) fn find_in_cast_expression(&mut self, cast_expression: &CastExpression) { - self.find_in_expression(&cast_expression.lhs); - } - - pub(super) fn find_in_infix_expression(&mut self, infix_expression: &InfixExpression) { - self.find_in_expression(&infix_expression.lhs); - self.find_in_expression(&infix_expression.rhs); - } - - pub(super) fn find_in_unresolved_types(&mut self, unresolved_type: &[UnresolvedType]) { - for unresolved_type in unresolved_type { - self.find_in_unresolved_type(unresolved_type); - } - } - - pub(super) fn find_in_function_return_type(&mut self, return_type: &FunctionReturnType) { - match return_type { - noirc_frontend::ast::FunctionReturnType::Default(_) => (), - noirc_frontend::ast::FunctionReturnType::Ty(unresolved_type) => { - self.find_in_unresolved_type(unresolved_type); - } - } - } - - pub(super) fn find_in_noir_type_alias(&mut self, noir_type_alias: &NoirTypeAlias) { - self.find_in_unresolved_type(&noir_type_alias.typ); - } -} diff --git a/noir/noir-repo/tooling/lsp/src/requests/document_symbol.rs b/noir/noir-repo/tooling/lsp/src/requests/document_symbol.rs index 5d2635b3549..e06a3451681 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/document_symbol.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/document_symbol.rs @@ -10,9 +10,9 @@ use noirc_errors::Span; use noirc_frontend::{ ast::{ Expression, FunctionReturnType, Ident, LetStatement, NoirFunction, NoirStruct, NoirTrait, - NoirTraitImpl, TraitImplItem, TraitItem, TypeImpl, UnresolvedType, UnresolvedTypeData, + NoirTraitImpl, TypeImpl, UnresolvedType, UnresolvedTypeData, Visitor, }, - parser::{Item, ItemKind, ParsedSubModule}, + parser::ParsedSubModule, ParsedModule, }; @@ -40,8 +40,7 @@ pub(crate) fn on_document_symbol_request( let (parsed_module, _errors) = noirc_frontend::parse_program(source); let mut collector = DocumentSymbolCollector::new(file_id, args.files); - let mut symbols = Vec::new(); - collector.collect_in_parsed_module(&parsed_module, &mut symbols); + let symbols = collector.collect(&parsed_module); DocumentSymbolResponse::Nested(symbols) }) }); @@ -52,67 +51,103 @@ pub(crate) fn on_document_symbol_request( struct DocumentSymbolCollector<'a> { file_id: FileId, files: &'a FileMap, + symbols: Vec, } impl<'a> DocumentSymbolCollector<'a> { fn new(file_id: FileId, files: &'a FileMap) -> Self { - Self { file_id, files } + Self { file_id, files, symbols: Vec::new() } } - fn collect_in_parsed_module( - &mut self, - parsed_module: &ParsedModule, - symbols: &mut Vec, - ) { - for item in &parsed_module.items { - self.collect_in_item(item, symbols); - } + fn collect(&mut self, parsed_module: &ParsedModule) -> Vec { + parsed_module.accept(self); + + std::mem::take(&mut self.symbols) } - fn collect_in_item(&mut self, item: &Item, symbols: &mut Vec) { - match &item.kind { - ItemKind::Function(noir_function) => { - self.collect_in_noir_function(noir_function, item.span, symbols); - } - ItemKind::Struct(noir_struct) => { - self.collect_in_noir_struct(noir_struct, item.span, symbols); - } - ItemKind::Trait(noir_trait) => { - self.collect_in_noir_trait(noir_trait, item.span, symbols); - } - ItemKind::TraitImpl(noir_trait_impl) => { - self.collect_in_noir_trait_impl(noir_trait_impl, item.span, symbols); - } - ItemKind::Impl(type_impl) => { - self.collect_in_type_impl(type_impl, item.span, symbols); - } - ItemKind::Submodules(parsed_sub_module) => { - self.collect_in_parsed_sub_module(parsed_sub_module, item.span, symbols); - } - ItemKind::Global(let_statement) => { - self.collect_in_global(let_statement, item.span, symbols); - } - ItemKind::Import(..) | ItemKind::TypeAlias(..) | ItemKind::ModuleDecl(..) => (), - } + fn collect_in_type(&mut self, name: &Ident, typ: Option<&UnresolvedType>) { + let Some(name_location) = self.to_lsp_location(name.span()) else { + return; + }; + + let span = if let Some(typ) = typ { + Span::from(name.span().start()..typ.span.end()) + } else { + name.span() + }; + + let Some(location) = self.to_lsp_location(span) else { + return; + }; + + #[allow(deprecated)] + self.symbols.push(DocumentSymbol { + name: name.to_string(), + detail: None, + kind: SymbolKind::TYPE_PARAMETER, + tags: None, + deprecated: None, + range: location.range, + selection_range: name_location.range, + children: None, + }); } - fn collect_in_noir_function( + fn collect_in_constant( &mut self, - noir_function: &NoirFunction, - span: Span, - symbols: &mut Vec, + name: &Ident, + typ: &UnresolvedType, + default_value: Option<&Expression>, ) { + let Some(name_location) = self.to_lsp_location(name.span()) else { + return; + }; + + let mut span = name.span(); + + // If there's a type span, extend the span to include it + 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 { + span = Span::from(span.start()..default_value.span.end()); + } + let Some(location) = self.to_lsp_location(span) else { return; }; + #[allow(deprecated)] + self.symbols.push(DocumentSymbol { + name: name.to_string(), + detail: None, + kind: SymbolKind::CONSTANT, + tags: None, + deprecated: None, + range: location.range, + selection_range: name_location.range, + children: None, + }); + } + + fn to_lsp_location(&self, span: Span) -> Option { + super::to_lsp_location(self.files, self.file_id, span) + } +} + +impl<'a> Visitor for DocumentSymbolCollector<'a> { + fn visit_noir_function(&mut self, noir_function: &NoirFunction, span: Span) -> bool { + let Some(location) = self.to_lsp_location(span) else { + return false; + }; + let Some(selection_location) = self.to_lsp_location(noir_function.name_ident().span()) else { - return; + return false; }; #[allow(deprecated)] - symbols.push(DocumentSymbol { + self.symbols.push(DocumentSymbol { name: noir_function.name().to_string(), detail: Some(noir_function.def.signature()), kind: SymbolKind::FUNCTION, @@ -122,20 +157,17 @@ impl<'a> DocumentSymbolCollector<'a> { selection_range: selection_location.range, children: None, }); + + false } - fn collect_in_noir_struct( - &mut self, - noir_struct: &NoirStruct, - span: Span, - symbols: &mut Vec, - ) { + fn visit_noir_struct(&mut self, noir_struct: &NoirStruct, span: Span) -> bool { let Some(location) = self.to_lsp_location(span) else { - return; + return false; }; let Some(selection_location) = self.to_lsp_location(noir_struct.name.span()) else { - return; + return false; }; let mut children = Vec::new(); @@ -164,7 +196,7 @@ impl<'a> DocumentSymbolCollector<'a> { } #[allow(deprecated)] - symbols.push(DocumentSymbol { + self.symbols.push(DocumentSymbol { name: noir_struct.name.to_string(), detail: None, kind: SymbolKind::STRUCT, @@ -174,29 +206,31 @@ impl<'a> DocumentSymbolCollector<'a> { selection_range: selection_location.range, children: Some(children), }); + + false } - fn collect_in_noir_trait( - &mut self, - noir_trait: &NoirTrait, - span: Span, - symbols: &mut Vec, - ) { + fn visit_noir_trait(&mut self, noir_trait: &NoirTrait, span: Span) -> bool { let Some(location) = self.to_lsp_location(span) else { - return; + return false; }; let Some(selection_location) = self.to_lsp_location(noir_trait.name.span()) else { - return; + return false; }; - let mut children = Vec::new(); + let old_symbols = std::mem::take(&mut self.symbols); + self.symbols = Vec::new(); + for item in &noir_trait.items { - self.collect_in_noir_trait_item(item, &mut children); + item.accept(self); } + let children = std::mem::take(&mut self.symbols); + self.symbols = old_symbols; + #[allow(deprecated)] - symbols.push(DocumentSymbol { + self.symbols.push(DocumentSymbol { name: noir_trait.name.to_string(), detail: None, kind: SymbolKind::INTERFACE, @@ -206,175 +240,124 @@ impl<'a> DocumentSymbolCollector<'a> { selection_range: selection_location.range, children: Some(children), }); - } - - fn collect_in_noir_trait_item( - &mut self, - trait_item: &TraitItem, - symbols: &mut Vec, - ) { - // Ideally `TraitItem` has a `span` for the entire definition, and we'd use that - // for the `range` property. For now we do our best to find a reasonable span. - match trait_item { - TraitItem::Function { name, parameters, return_type, body, .. } => { - let Some(name_location) = self.to_lsp_location(name.span()) else { - return; - }; - - let mut span = name.span(); - - // If there are parameters, extend the span to include the last parameter. - if let Some((param_name, _param_type)) = parameters.last() { - span = Span::from(span.start()..param_name.span().end()); - } - // If there's a return type, extend the span to include it - match return_type { - FunctionReturnType::Default(return_type_span) => { - span = Span::from(span.start()..return_type_span.end()); - } - FunctionReturnType::Ty(typ) => { - span = Span::from(span.start()..typ.span.end()); - } - } - - // If there's a body, extend the span to include it - if let Some(body) = body { - if let Some(statement) = body.statements.last() { - span = Span::from(span.start()..statement.span.end()); - } - } - - let Some(location) = self.to_lsp_location(span) else { - return; - }; - - #[allow(deprecated)] - symbols.push(DocumentSymbol { - name: name.to_string(), - detail: None, - kind: SymbolKind::METHOD, - tags: None, - deprecated: None, - range: location.range, - selection_range: name_location.range, - children: None, - }); - } - TraitItem::Constant { name, typ, default_value } => { - self.collect_in_constant(name, typ, default_value.as_ref(), symbols); - } - TraitItem::Type { name } => { - self.collect_in_type(name, None, symbols); - } - } + false } - fn collect_in_constant( + fn visit_trait_item_function( &mut self, name: &Ident, - typ: &UnresolvedType, - default_value: Option<&Expression>, - symbols: &mut Vec, - ) { + _generics: &noirc_frontend::ast::UnresolvedGenerics, + parameters: &[(Ident, UnresolvedType)], + return_type: &FunctionReturnType, + _where_clause: &[noirc_frontend::ast::UnresolvedTraitConstraint], + body: &Option, + ) -> bool { let Some(name_location) = self.to_lsp_location(name.span()) else { - return; + return false; }; let mut span = name.span(); - // If there's a type span, extend the span to include it - span = Span::from(span.start()..typ.span.end()); + // If there are parameters, extend the span to include the last parameter. + if let Some((param_name, _param_type)) = parameters.last() { + span = Span::from(span.start()..param_name.span().end()); + } - // If there's a default value, extend the span to include it - if let Some(default_value) = default_value { - span = Span::from(span.start()..default_value.span.end()); + // If there's a return type, extend the span to include it + match return_type { + FunctionReturnType::Default(return_type_span) => { + span = Span::from(span.start()..return_type_span.end()); + } + FunctionReturnType::Ty(typ) => { + span = Span::from(span.start()..typ.span.end()); + } + } + + // If there's a body, extend the span to include it + if let Some(body) = body { + if let Some(statement) = body.statements.last() { + span = Span::from(span.start()..statement.span.end()); + } } let Some(location) = self.to_lsp_location(span) else { - return; + return false; }; #[allow(deprecated)] - symbols.push(DocumentSymbol { + self.symbols.push(DocumentSymbol { name: name.to_string(), detail: None, - kind: SymbolKind::CONSTANT, + kind: SymbolKind::METHOD, tags: None, deprecated: None, range: location.range, selection_range: name_location.range, children: None, }); + + false } - fn collect_in_type( + fn visit_trait_item_constant( &mut self, name: &Ident, - typ: Option<&UnresolvedType>, - symbols: &mut Vec, - ) { - let Some(name_location) = self.to_lsp_location(name.span()) else { - return; - }; - - let span = if let Some(typ) = typ { - Span::from(name.span().start()..typ.span.end()) - } else { - name.span() - }; - - let Some(location) = self.to_lsp_location(span) else { - return; - }; + typ: &UnresolvedType, + default_value: &Option, + ) -> bool { + self.collect_in_constant(name, typ, default_value.as_ref()); + false + } - #[allow(deprecated)] - symbols.push(DocumentSymbol { - name: name.to_string(), - detail: None, - kind: SymbolKind::TYPE_PARAMETER, - tags: None, - deprecated: None, - range: location.range, - selection_range: name_location.range, - children: None, - }); + fn visit_trait_item_type(&mut self, name: &Ident) { + self.collect_in_type(name, None); } - fn collect_in_noir_trait_impl( - &mut self, - noir_trait_impl: &NoirTraitImpl, - span: Span, - symbols: &mut Vec, - ) { + fn visit_noir_trait_impl(&mut self, noir_trait_impl: &NoirTraitImpl, span: Span) -> bool { let Some(location) = self.to_lsp_location(span) else { - return; + return false; }; let Some(name_location) = self.to_lsp_location(noir_trait_impl.trait_name.span) else { - return; + return false; }; let mut trait_name = String::new(); 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('>'); } - let mut children = Vec::new(); + let old_symbols = std::mem::take(&mut self.symbols); + self.symbols = Vec::new(); + for trait_impl_item in &noir_trait_impl.items { - self.collect_in_trait_impl_item(trait_impl_item, &mut children); + trait_impl_item.accept(self); } + let children = std::mem::take(&mut self.symbols); + self.symbols = old_symbols; + #[allow(deprecated)] - symbols.push(DocumentSymbol { + self.symbols.push(DocumentSymbol { name: format!("impl {} for {}", trait_name, noir_trait_impl.object_type), detail: None, kind: SymbolKind::NAMESPACE, @@ -384,54 +367,52 @@ impl<'a> DocumentSymbolCollector<'a> { selection_range: name_location.range, children: Some(children), }); + + false } - fn collect_in_trait_impl_item( + fn visit_trait_impl_item_constant( &mut self, - trait_impl_item: &TraitImplItem, - symbols: &mut Vec, - ) { - match trait_impl_item { - TraitImplItem::Function(noir_function) => { - let span = Span::from( - noir_function.name_ident().span().start()..noir_function.span().end(), - ); - self.collect_in_noir_function(noir_function, span, symbols); - } - TraitImplItem::Constant(name, typ, default_value) => { - self.collect_in_constant(name, typ, Some(default_value), symbols); - } - TraitImplItem::Type { name, alias } => self.collect_in_type(name, Some(alias), symbols), - } + name: &Ident, + typ: &UnresolvedType, + default_value: &Expression, + ) -> bool { + self.collect_in_constant(name, typ, Some(default_value)); + false } - fn collect_in_type_impl( - &mut self, - type_impl: &TypeImpl, - span: Span, - symbols: &mut Vec, - ) { + fn visit_trait_impl_item_type(&mut self, name: &Ident, alias: &UnresolvedType) -> bool { + self.collect_in_type(name, Some(alias)); + false + } + + fn visit_type_impl(&mut self, type_impl: &TypeImpl, span: Span) -> bool { let Some(location) = self.to_lsp_location(span) else { - return; + return false; }; let UnresolvedTypeData::Named(name_path, ..) = &type_impl.object_type.typ else { - return; + return false; }; let name = name_path.last_ident(); let Some(name_location) = self.to_lsp_location(name.span()) else { - return; + return false; }; - let mut children = Vec::new(); + let old_symbols = std::mem::take(&mut self.symbols); + self.symbols = Vec::new(); + for (noir_function, noir_function_span) in &type_impl.methods { - self.collect_in_noir_function(noir_function, *noir_function_span, &mut children); + noir_function.accept(*noir_function_span, self); } + let children = std::mem::take(&mut self.symbols); + self.symbols = old_symbols; + #[allow(deprecated)] - symbols.push(DocumentSymbol { + self.symbols.push(DocumentSymbol { name: name.to_string(), detail: None, kind: SymbolKind::NAMESPACE, @@ -441,29 +422,31 @@ impl<'a> DocumentSymbolCollector<'a> { selection_range: name_location.range, children: Some(children), }); + + false } - fn collect_in_parsed_sub_module( - &mut self, - parsed_sub_module: &ParsedSubModule, - span: Span, - symbols: &mut Vec, - ) { + fn visit_parsed_submodule(&mut self, parsed_sub_module: &ParsedSubModule, span: Span) -> bool { let Some(name_location) = self.to_lsp_location(parsed_sub_module.name.span()) else { - return; + return false; }; let Some(location) = self.to_lsp_location(span) else { - return; + return false; }; - let mut children = Vec::new(); + let old_symbols = std::mem::take(&mut self.symbols); + self.symbols = Vec::new(); + for item in &parsed_sub_module.contents.items { - self.collect_in_item(item, &mut children); + item.accept(self); } + let children = std::mem::take(&mut self.symbols); + self.symbols = old_symbols; + #[allow(deprecated)] - symbols.push(DocumentSymbol { + self.symbols.push(DocumentSymbol { name: parsed_sub_module.name.to_string(), detail: None, kind: SymbolKind::MODULE, @@ -473,24 +456,21 @@ impl<'a> DocumentSymbolCollector<'a> { selection_range: name_location.range, children: Some(children), }); + + false } - fn collect_in_global( - &mut self, - global: &LetStatement, - span: Span, - symbols: &mut Vec, - ) { + fn visit_global(&mut self, global: &LetStatement, span: Span) -> bool { let Some(name_location) = self.to_lsp_location(global.pattern.span()) else { - return; + return false; }; let Some(location) = self.to_lsp_location(span) else { - return; + return false; }; #[allow(deprecated)] - symbols.push(DocumentSymbol { + self.symbols.push(DocumentSymbol { name: global.pattern.to_string(), detail: None, kind: SymbolKind::CONSTANT, @@ -500,10 +480,8 @@ impl<'a> DocumentSymbolCollector<'a> { selection_range: name_location.range, children: None, }); - } - fn to_lsp_location(&self, span: Span) -> Option { - super::to_lsp_location(self.files, self.file_id, span) + false } } 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 95d8b82f84f..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 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 => unreachable!("ICE: A dummy CrateId should not be accessible"), - }; - - if let Some(crate_name) = &crate_name { - segments.push(crate_name); + // 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); }; if segments.is_empty() { @@ -416,9 +423,12 @@ 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); @@ -804,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 a1e083187d3..43e0227fa26 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/inlay_hint.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/inlay_hint.rs @@ -10,14 +10,15 @@ use noirc_errors::{Location, Span}; use noirc_frontend::{ self, ast::{ - BlockExpression, Expression, ExpressionKind, Ident, LetStatement, NoirFunction, Pattern, - Statement, StatementKind, TraitImplItem, TraitItem, UnresolvedTypeData, + CallExpression, Expression, ExpressionKind, ForLoopStatement, Ident, Lambda, LetStatement, + MethodCallExpression, NoirFunction, NoirTraitImpl, Pattern, Statement, TypeImpl, + UnresolvedTypeData, Visitor, }, hir_def::stmt::HirPattern, macros_api::NodeInterner, node_interner::ReferenceId, - parser::{Item, ItemKind}, - ParsedModule, Type, TypeBinding, TypeVariable, TypeVariableKind, + parser::{Item, ParsedSubModule}, + Type, TypeBinding, TypeVariable, TypeVariableKind, }; use crate::{utils, LspState}; @@ -47,7 +48,7 @@ pub(crate) fn on_inlay_hint_request( let mut collector = InlayHintCollector::new(args.files, file_id, args.interner, span, options); - collector.collect_in_parsed_module(&parsed_moduled); + parsed_moduled.accept(&mut collector); collector.inlay_hints }) }); @@ -73,264 +74,6 @@ impl<'a> InlayHintCollector<'a> { ) -> InlayHintCollector<'a> { InlayHintCollector { files, file_id, interner, span, options, inlay_hints: Vec::new() } } - fn collect_in_parsed_module(&mut self, parsed_module: &ParsedModule) { - for item in &parsed_module.items { - self.collect_in_item(item); - } - } - - fn collect_in_item(&mut self, item: &Item) { - if !self.intersects_span(item.span) { - return; - } - - match &item.kind { - ItemKind::Function(noir_function) => { - self.collect_in_noir_function(noir_function, item.span); - } - ItemKind::Trait(noir_trait) => { - for item in &noir_trait.items { - self.collect_in_trait_item(item); - } - } - ItemKind::TraitImpl(noir_trait_impl) => { - for trait_impl_item in &noir_trait_impl.items { - self.collect_in_trait_impl_item(trait_impl_item, item.span); - } - - self.show_closing_brace_hint(item.span, || { - format!( - " impl {} for {}", - noir_trait_impl.trait_name, noir_trait_impl.object_type - ) - }); - } - ItemKind::Impl(type_impl) => { - for (noir_function, span) in &type_impl.methods { - self.collect_in_noir_function(noir_function, *span); - } - - self.show_closing_brace_hint(item.span, || { - format!(" impl {}", type_impl.object_type) - }); - } - ItemKind::Global(let_statement) => self.collect_in_let_statement(let_statement), - ItemKind::Submodules(parsed_submodule) => { - self.collect_in_parsed_module(&parsed_submodule.contents); - - self.show_closing_brace_hint(item.span, || { - if parsed_submodule.is_contract { - format!(" contract {}", parsed_submodule.name) - } else { - format!(" mod {}", parsed_submodule.name) - } - }); - } - ItemKind::ModuleDecl(_) => (), - ItemKind::Import(_) => (), - ItemKind::Struct(_) => (), - ItemKind::TypeAlias(_) => (), - } - } - - fn collect_in_trait_item(&mut self, item: &TraitItem) { - match item { - TraitItem::Function { body, .. } => { - if let Some(body) = body { - self.collect_in_block_expression(body); - } - } - TraitItem::Constant { name: _, typ: _, default_value } => { - if let Some(default_value) = default_value { - self.collect_in_expression(default_value); - } - } - TraitItem::Type { .. } => (), - } - } - - fn collect_in_trait_impl_item(&mut self, item: &TraitImplItem, span: Span) { - match item { - TraitImplItem::Function(noir_function) => { - self.collect_in_noir_function(noir_function, span); - } - TraitImplItem::Constant(_name, _typ, default_value) => { - self.collect_in_expression(default_value); - } - TraitImplItem::Type { .. } => (), - } - } - - fn collect_in_noir_function(&mut self, noir_function: &NoirFunction, span: Span) { - self.collect_in_block_expression(&noir_function.def.body); - - self.show_closing_brace_hint(span, || format!(" fn {}", noir_function.def.name)); - } - - fn collect_in_let_statement(&mut self, let_statement: &LetStatement) { - // Only show inlay hints for let variables that don't have an explicit type annotation - if let UnresolvedTypeData::Unspecified = let_statement.r#type.typ { - self.collect_in_pattern(&let_statement.pattern); - }; - - self.collect_in_expression(&let_statement.expression); - } - - fn collect_in_block_expression(&mut self, block_expression: &BlockExpression) { - for statement in &block_expression.statements { - self.collect_in_statement(statement); - } - } - - fn collect_in_statement(&mut self, statement: &Statement) { - if !self.intersects_span(statement.span) { - return; - } - - match &statement.kind { - StatementKind::Let(let_statement) => self.collect_in_let_statement(let_statement), - StatementKind::Constrain(constrain_statement) => { - self.collect_in_expression(&constrain_statement.0); - } - StatementKind::Expression(expression) => self.collect_in_expression(expression), - StatementKind::Assign(assign_statement) => { - self.collect_in_expression(&assign_statement.expression); - } - StatementKind::For(for_loop_statement) => { - self.collect_in_ident(&for_loop_statement.identifier, false); - self.collect_in_expression(&for_loop_statement.block); - } - StatementKind::Comptime(statement) => self.collect_in_statement(statement), - StatementKind::Semi(expression) => self.collect_in_expression(expression), - StatementKind::Break => (), - StatementKind::Continue => (), - StatementKind::Error => (), - } - } - - fn collect_in_expression(&mut self, expression: &Expression) { - if !self.intersects_span(expression.span) { - return; - } - - match &expression.kind { - ExpressionKind::Block(block_expression) => { - self.collect_in_block_expression(block_expression); - } - ExpressionKind::Prefix(prefix_expression) => { - self.collect_in_expression(&prefix_expression.rhs); - } - ExpressionKind::Index(index_expression) => { - self.collect_in_expression(&index_expression.collection); - self.collect_in_expression(&index_expression.index); - } - ExpressionKind::Call(call_expression) => { - self.collect_call_parameter_names( - get_expression_name(&call_expression.func), - call_expression.func.span, - &call_expression.arguments, - ); - - self.collect_in_expression(&call_expression.func); - for arg in &call_expression.arguments { - self.collect_in_expression(arg); - } - } - ExpressionKind::MethodCall(method_call_expression) => { - self.collect_call_parameter_names( - Some(method_call_expression.method_name.to_string()), - method_call_expression.method_name.span(), - &method_call_expression.arguments, - ); - - self.collect_in_expression(&method_call_expression.object); - for arg in &method_call_expression.arguments { - self.collect_in_expression(arg); - } - } - ExpressionKind::Constructor(constructor_expression) => { - for (_name, expr) in &constructor_expression.fields { - self.collect_in_expression(expr); - } - } - ExpressionKind::MemberAccess(member_access_expression) => { - self.collect_in_expression(&member_access_expression.lhs); - } - ExpressionKind::Cast(cast_expression) => { - self.collect_in_expression(&cast_expression.lhs); - } - ExpressionKind::Infix(infix_expression) => { - self.collect_in_expression(&infix_expression.lhs); - self.collect_in_expression(&infix_expression.rhs); - } - ExpressionKind::If(if_expression) => { - self.collect_in_expression(&if_expression.condition); - self.collect_in_expression(&if_expression.consequence); - if let Some(alternative) = &if_expression.alternative { - self.collect_in_expression(alternative); - } - } - ExpressionKind::Tuple(expressions) => { - for expression in expressions { - self.collect_in_expression(expression); - } - } - ExpressionKind::Lambda(lambda) => { - for (pattern, typ) in &lambda.parameters { - if matches!(typ.typ, UnresolvedTypeData::Unspecified) { - self.collect_in_pattern(pattern); - } - } - - self.collect_in_expression(&lambda.body); - } - ExpressionKind::Parenthesized(parenthesized) => { - self.collect_in_expression(parenthesized); - } - ExpressionKind::Unquote(expression) => { - self.collect_in_expression(expression); - } - 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); - } - ExpressionKind::Literal(..) - | ExpressionKind::Variable(..) - | ExpressionKind::Quote(..) - | ExpressionKind::Resolved(..) - | ExpressionKind::Error => (), - } - } - - fn collect_in_pattern(&mut self, pattern: &Pattern) { - if !self.options.type_hints.enabled { - return; - } - - match pattern { - Pattern::Identifier(ident) => { - self.collect_in_ident(ident, true); - } - Pattern::Mutable(pattern, _span, _is_synthesized) => { - self.collect_in_pattern(pattern); - } - Pattern::Tuple(patterns, _span) => { - for pattern in patterns { - self.collect_in_pattern(pattern); - } - } - Pattern::Struct(_path, patterns, _span) => { - for (_ident, pattern) in patterns { - self.collect_in_pattern(pattern); - } - } - } - } fn collect_in_ident(&mut self, ident: &Ident, editable: bool) { if !self.options.type_hints.enabled { @@ -525,6 +268,112 @@ impl<'a> InlayHintCollector<'a> { } } +impl<'a> Visitor for InlayHintCollector<'a> { + fn visit_item(&mut self, item: &Item) -> bool { + self.intersects_span(item.span) + } + + fn visit_noir_trait_impl(&mut self, noir_trait_impl: &NoirTraitImpl, span: Span) -> bool { + self.show_closing_brace_hint(span, || { + format!(" impl {} for {}", noir_trait_impl.trait_name, noir_trait_impl.object_type) + }); + + true + } + + fn visit_type_impl(&mut self, type_impl: &TypeImpl, span: Span) -> bool { + self.show_closing_brace_hint(span, || format!(" impl {}", type_impl.object_type)); + + true + } + + fn visit_parsed_submodule(&mut self, parsed_submodule: &ParsedSubModule, span: Span) -> bool { + self.show_closing_brace_hint(span, || { + if parsed_submodule.is_contract { + format!(" contract {}", parsed_submodule.name) + } else { + format!(" mod {}", parsed_submodule.name) + } + }); + + true + } + + fn visit_noir_function(&mut self, noir_function: &NoirFunction, span: Span) -> bool { + self.show_closing_brace_hint(span, || format!(" fn {}", noir_function.def.name)); + + true + } + + fn visit_statement(&mut self, statement: &Statement) -> bool { + self.intersects_span(statement.span) + } + + fn visit_let_statement(&mut self, let_statement: &LetStatement) -> bool { + // Only show inlay hints for let variables that don't have an explicit type annotation + if let UnresolvedTypeData::Unspecified = let_statement.r#type.typ { + let_statement.pattern.accept(self); + }; + + let_statement.expression.accept(self); + + false + } + + fn visit_for_loop_statement(&mut self, for_loop_statement: &ForLoopStatement) -> bool { + self.collect_in_ident(&for_loop_statement.identifier, false); + true + } + + fn visit_expression(&mut self, expression: &Expression) -> bool { + self.intersects_span(expression.span) + } + + fn visit_call_expression(&mut self, call_expression: &CallExpression, _: Span) -> bool { + self.collect_call_parameter_names( + get_expression_name(&call_expression.func), + call_expression.func.span, + &call_expression.arguments, + ); + + true + } + + fn visit_method_call_expression( + &mut self, + method_call_expression: &MethodCallExpression, + _: Span, + ) -> bool { + self.collect_call_parameter_names( + Some(method_call_expression.method_name.to_string()), + method_call_expression.method_name.span(), + &method_call_expression.arguments, + ); + + true + } + + fn visit_lambda(&mut self, lambda: &Lambda, _: Span) -> bool { + for (pattern, typ) in &lambda.parameters { + if matches!(typ.typ, UnresolvedTypeData::Unspecified) { + pattern.accept(self); + } + } + + lambda.body.accept(self); + + false + } + + fn visit_pattern(&mut self, _: &Pattern) -> bool { + self.options.type_hints.enabled + } + + fn visit_identifier_pattern(&mut self, ident: &Ident) { + self.collect_in_ident(ident, true); + } +} + fn string_part(str: impl Into) -> InlayHintLabelPart { InlayHintLabelPart { value: str.into(), location: None, tooltip: None, command: None } } @@ -692,6 +541,7 @@ fn get_expression_name(expression: &Expression) -> Option { | ExpressionKind::Unquote(..) | ExpressionKind::Comptime(..) | ExpressionKind::Resolved(..) + | ExpressionKind::Interned(..) | ExpressionKind::Literal(..) | ExpressionKind::Unsafe(..) | ExpressionKind::Error => 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 index c2c69185547..8f1bbb1570f 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/signature_help.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/signature_help.rs @@ -7,10 +7,14 @@ use lsp_types::{ }; use noirc_errors::{Location, Span}; use noirc_frontend::{ - ast::{CallExpression, Expression, FunctionReturnType, MethodCallExpression}, + ast::{ + CallExpression, ConstrainKind, ConstrainStatement, Expression, ExpressionKind, + FunctionReturnType, MethodCallExpression, Statement, Visitor, + }, hir_def::{function::FuncMeta, stmt::HirPattern}, macros_api::NodeInterner, node_interner::ReferenceId, + parser::Item, ParsedModule, Type, }; @@ -19,7 +23,6 @@ use crate::{utils, LspState}; use super::process_request; mod tests; -mod traversal; pub(crate) fn on_signature_help_request( state: &mut LspState, @@ -61,49 +64,11 @@ impl<'a> SignatureFinder<'a> { } fn find(&mut self, parsed_module: &ParsedModule) -> Option { - self.find_in_parsed_module(parsed_module); + parsed_module.accept(self); 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], @@ -119,18 +84,7 @@ impl<'a> SignatureFinder<'a> { 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 active_parameter = self.compute_active_parameter(arguments); let location = Location::new(name_span, self.file); // Check if the call references a named function @@ -146,6 +100,7 @@ impl<'a> SignatureFinder<'a> { // 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; } @@ -266,6 +221,60 @@ impl<'a> SignatureFinder<'a> { } } + fn assert_signature_information(&self, active_parameter: Option) -> SignatureInformation { + self.hardcoded_signature_information( + active_parameter, + "assert", + &["predicate: bool", "[failure_message: str]"], + ) + } + + fn assert_eq_signature_information( + &self, + active_parameter: Option, + ) -> SignatureInformation { + self.hardcoded_signature_information( + active_parameter, + "assert_eq", + &["lhs: T", "rhs: T", "[failure_message: str]"], + ) + } + + fn hardcoded_signature_information( + &self, + active_parameter: Option, + name: &str, + arguments: &[&str], + ) -> SignatureInformation { + let mut label = String::new(); + let mut parameters = Vec::new(); + + label.push_str(name); + label.push('('); + for (index, typ) in arguments.iter().enumerate() { + if index > 0 { + label.push_str(", "); + } + + let parameter_start = label.chars().count(); + label.push_str(typ); + let parameter_end = label.chars().count(); + + parameters.push(ParameterInformation { + label: ParameterLabel::LabelOffsets([parameter_start as u32, parameter_end as u32]), + documentation: None, + }); + } + label.push(')'); + + 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) => { @@ -285,7 +294,124 @@ impl<'a> SignatureFinder<'a> { self.signature_help = Some(signature_help); } + fn compute_active_parameter(&self, arguments: &[Expression]) -> Option { + 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); + } + + active_parameter + } + fn includes_span(&self, span: Span) -> bool { span.start() as usize <= self.byte_index && self.byte_index <= span.end() as usize } } + +impl<'a> Visitor for SignatureFinder<'a> { + fn visit_item(&mut self, item: &Item) -> bool { + self.includes_span(item.span) + } + + fn visit_statement(&mut self, statement: &Statement) -> bool { + self.includes_span(statement.span) + } + + fn visit_expression(&mut self, expression: &Expression) -> bool { + self.includes_span(expression.span) + } + + fn visit_call_expression(&mut self, call_expression: &CallExpression, span: Span) -> bool { + call_expression.accept_children(self); + + 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, + ); + + false + } + + fn visit_method_call_expression( + &mut self, + method_call_expression: &MethodCallExpression, + span: Span, + ) -> bool { + method_call_expression.accept_children(self); + + 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, + ); + + false + } + + fn visit_constrain_statement(&mut self, constrain_statement: &ConstrainStatement) -> bool { + constrain_statement.accept_children(self); + + if self.signature_help.is_some() { + return false; + } + + let arguments_span = if let Some(expr) = &constrain_statement.1 { + Span::from(constrain_statement.0.span.start()..expr.span.end()) + } else { + constrain_statement.0.span + }; + + if !self.includes_span(arguments_span) { + return false; + } + + match constrain_statement.2 { + ConstrainKind::Assert => { + let mut arguments = vec![constrain_statement.0.clone()]; + if let Some(expr) = &constrain_statement.1 { + arguments.push(expr.clone()); + } + + let active_parameter = self.compute_active_parameter(&arguments); + let signature_information = self.assert_signature_information(active_parameter); + self.set_signature_help(signature_information); + } + ConstrainKind::AssertEq => { + if let ExpressionKind::Infix(infix) = &constrain_statement.0.kind { + let mut arguments = vec![infix.lhs.clone(), infix.rhs.clone()]; + if let Some(expr) = &constrain_statement.1 { + arguments.push(expr.clone()); + } + + let active_parameter = self.compute_active_parameter(&arguments); + let signature_information = + self.assert_eq_signature_information(active_parameter); + self.set_signature_help(signature_information); + } + } + ConstrainKind::Constrain => (), + } + + false + } +} diff --git a/noir/noir-repo/tooling/lsp/src/requests/signature_help/tests.rs b/noir/noir-repo/tooling/lsp/src/requests/signature_help/tests.rs index c48ee159084..4b3f3c38156 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/signature_help/tests.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/signature_help/tests.rs @@ -193,4 +193,51 @@ mod signature_help_tests { assert_eq!(signature.active_parameter, Some(0)); } + + #[test] + async fn test_signature_help_for_assert() { + let src = r#" + fn bar() { + assert(>|<1, "hello"); + } + "#; + + 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, "assert(predicate: bool, [failure_message: str])"); + + let params = signature.parameters.as_ref().unwrap(); + assert_eq!(params.len(), 2); + + check_label(&signature.label, ¶ms[0].label, "predicate: bool"); + check_label(&signature.label, ¶ms[1].label, "[failure_message: str]"); + + assert_eq!(signature.active_parameter, Some(0)); + } + + #[test] + async fn test_signature_help_for_assert_eq() { + let src = r#" + fn bar() { + assert_eq(>|])"); + + let params = signature.parameters.as_ref().unwrap(); + assert_eq!(params.len(), 3); + + check_label(&signature.label, ¶ms[0].label, "lhs: T"); + check_label(&signature.label, ¶ms[1].label, "rhs: T"); + check_label(&signature.label, ¶ms[2].label, "[failure_message: str]"); + + 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 deleted file mode 100644 index 22f92a86124..00000000000 --- a/noir/noir-repo/tooling/lsp/src/requests/signature_help/traversal.rs +++ /dev/null @@ -1,304 +0,0 @@ -/// This file includes the signature help logic that's just about -/// traversing the AST without any additional logic. -use super::SignatureFinder; - -use noirc_frontend::{ - ast::{ - ArrayLiteral, AssignStatement, BlockExpression, CastExpression, ConstrainStatement, - ConstructorExpression, Expression, ExpressionKind, ForLoopStatement, ForRange, - IfExpression, IndexExpression, InfixExpression, LValue, Lambda, LetStatement, Literal, - MemberAccessExpression, NoirFunction, NoirTrait, NoirTraitImpl, Statement, StatementKind, - TraitImplItem, TraitItem, TypeImpl, - }, - parser::{Item, ItemKind}, - ParsedModule, -}; - -impl<'a> SignatureFinder<'a> { - pub(super) fn find_in_parsed_module(&mut self, parsed_module: &ParsedModule) { - for item in &parsed_module.items { - self.find_in_item(item); - } - } - - pub(super) fn find_in_item(&mut self, item: &Item) { - if !self.includes_span(item.span) { - return; - } - - match &item.kind { - ItemKind::Submodules(parsed_sub_module) => { - self.find_in_parsed_module(&parsed_sub_module.contents); - } - ItemKind::Function(noir_function) => self.find_in_noir_function(noir_function), - ItemKind::TraitImpl(noir_trait_impl) => self.find_in_noir_trait_impl(noir_trait_impl), - ItemKind::Impl(type_impl) => self.find_in_type_impl(type_impl), - ItemKind::Global(let_statement) => self.find_in_let_statement(let_statement), - ItemKind::Trait(noir_trait) => self.find_in_noir_trait(noir_trait), - ItemKind::Import(..) - | ItemKind::TypeAlias(_) - | ItemKind::Struct(_) - | ItemKind::ModuleDecl(_) => (), - } - } - - pub(super) fn find_in_noir_function(&mut self, noir_function: &NoirFunction) { - self.find_in_block_expression(&noir_function.def.body); - } - - pub(super) fn find_in_noir_trait_impl(&mut self, noir_trait_impl: &NoirTraitImpl) { - for item in &noir_trait_impl.items { - self.find_in_trait_impl_item(item); - } - } - - pub(super) fn find_in_trait_impl_item(&mut self, item: &TraitImplItem) { - match item { - TraitImplItem::Function(noir_function) => self.find_in_noir_function(noir_function), - TraitImplItem::Constant(_, _, _) => (), - TraitImplItem::Type { .. } => (), - } - } - - pub(super) fn find_in_type_impl(&mut self, type_impl: &TypeImpl) { - for (method, span) in &type_impl.methods { - if self.includes_span(*span) { - self.find_in_noir_function(method); - } - } - } - - pub(super) fn find_in_noir_trait(&mut self, noir_trait: &NoirTrait) { - for item in &noir_trait.items { - self.find_in_trait_item(item); - } - } - - pub(super) fn find_in_trait_item(&mut self, trait_item: &TraitItem) { - match trait_item { - TraitItem::Function { body, .. } => { - if let Some(body) = body { - self.find_in_block_expression(body); - }; - } - TraitItem::Constant { default_value, .. } => { - if let Some(default_value) = default_value { - self.find_in_expression(default_value); - } - } - TraitItem::Type { .. } => (), - } - } - - pub(super) fn find_in_block_expression(&mut self, block_expression: &BlockExpression) { - for statement in &block_expression.statements { - if self.includes_span(statement.span) { - self.find_in_statement(statement); - } - } - } - - pub(super) fn find_in_statement(&mut self, statement: &Statement) { - if !self.includes_span(statement.span) { - return; - } - - match &statement.kind { - StatementKind::Let(let_statement) => { - self.find_in_let_statement(let_statement); - } - StatementKind::Constrain(constrain_statement) => { - self.find_in_constrain_statement(constrain_statement); - } - StatementKind::Expression(expression) => { - self.find_in_expression(expression); - } - StatementKind::Assign(assign_statement) => { - self.find_in_assign_statement(assign_statement); - } - StatementKind::For(for_loop_statement) => { - self.find_in_for_loop_statement(for_loop_statement); - } - StatementKind::Comptime(statement) => { - self.find_in_statement(statement); - } - StatementKind::Semi(expression) => { - self.find_in_expression(expression); - } - StatementKind::Break | StatementKind::Continue | StatementKind::Error => (), - } - } - - pub(super) fn find_in_let_statement(&mut self, let_statement: &LetStatement) { - self.find_in_expression(&let_statement.expression); - } - - pub(super) fn find_in_constrain_statement(&mut self, constrain_statement: &ConstrainStatement) { - self.find_in_expression(&constrain_statement.0); - - if let Some(exp) = &constrain_statement.1 { - self.find_in_expression(exp); - } - } - - pub(super) fn find_in_assign_statement(&mut self, assign_statement: &AssignStatement) { - self.find_in_lvalue(&assign_statement.lvalue); - self.find_in_expression(&assign_statement.expression); - } - - pub(super) fn find_in_for_loop_statement(&mut self, for_loop_statement: &ForLoopStatement) { - self.find_in_for_range(&for_loop_statement.range); - self.find_in_expression(&for_loop_statement.block); - } - - pub(super) fn find_in_lvalue(&mut self, lvalue: &LValue) { - match lvalue { - LValue::Ident(_) => (), - LValue::MemberAccess { object, field_name: _, span: _ } => self.find_in_lvalue(object), - LValue::Index { array, index, span: _ } => { - self.find_in_lvalue(array); - self.find_in_expression(index); - } - LValue::Dereference(lvalue, _) => self.find_in_lvalue(lvalue), - } - } - - pub(super) fn find_in_for_range(&mut self, for_range: &ForRange) { - match for_range { - ForRange::Range(start, end) => { - self.find_in_expression(start); - self.find_in_expression(end); - } - ForRange::Array(expression) => self.find_in_expression(expression), - } - } - - pub(super) fn find_in_expressions(&mut self, expressions: &[Expression]) { - for expression in expressions { - self.find_in_expression(expression); - } - } - - pub(super) fn find_in_expression(&mut self, expression: &Expression) { - match &expression.kind { - ExpressionKind::Literal(literal) => self.find_in_literal(literal), - ExpressionKind::Block(block_expression) => { - self.find_in_block_expression(block_expression); - } - ExpressionKind::Prefix(prefix_expression) => { - self.find_in_expression(&prefix_expression.rhs); - } - ExpressionKind::Index(index_expression) => { - self.find_in_index_expression(index_expression); - } - ExpressionKind::Call(call_expression) => { - self.find_in_call_expression(call_expression, expression.span); - } - ExpressionKind::MethodCall(method_call_expression) => { - self.find_in_method_call_expression(method_call_expression, expression.span); - } - ExpressionKind::Constructor(constructor_expression) => { - self.find_in_constructor_expression(constructor_expression); - } - ExpressionKind::MemberAccess(member_access_expression) => { - self.find_in_member_access_expression(member_access_expression); - } - ExpressionKind::Cast(cast_expression) => { - self.find_in_cast_expression(cast_expression); - } - ExpressionKind::Infix(infix_expression) => { - self.find_in_infix_expression(infix_expression); - } - ExpressionKind::If(if_expression) => { - self.find_in_if_expression(if_expression); - } - ExpressionKind::Tuple(expressions) => { - self.find_in_expressions(expressions); - } - ExpressionKind::Lambda(lambda) => self.find_in_lambda(lambda), - ExpressionKind::Parenthesized(expression) => { - self.find_in_expression(expression); - } - ExpressionKind::Unquote(expression) => { - self.find_in_expression(expression); - } - ExpressionKind::Comptime(block_expression, _) => { - self.find_in_block_expression(block_expression); - } - ExpressionKind::Unsafe(block_expression, _) => { - self.find_in_block_expression(block_expression); - } - ExpressionKind::Variable(_) - | ExpressionKind::AsTraitPath(_) - | ExpressionKind::Quote(_) - | ExpressionKind::Resolved(_) - | ExpressionKind::Error => (), - } - } - - pub(super) fn find_in_literal(&mut self, literal: &Literal) { - match literal { - Literal::Array(array_literal) => self.find_in_array_literal(array_literal), - Literal::Slice(array_literal) => self.find_in_array_literal(array_literal), - Literal::Bool(_) - | Literal::Integer(_, _) - | Literal::Str(_) - | Literal::RawStr(_, _) - | Literal::FmtStr(_) - | Literal::Unit => (), - } - } - - pub(super) fn find_in_array_literal(&mut self, array_literal: &ArrayLiteral) { - match array_literal { - ArrayLiteral::Standard(expressions) => self.find_in_expressions(expressions), - ArrayLiteral::Repeated { repeated_element, length } => { - self.find_in_expression(repeated_element); - self.find_in_expression(length); - } - } - } - - pub(super) fn find_in_index_expression(&mut self, index_expression: &IndexExpression) { - self.find_in_expression(&index_expression.collection); - self.find_in_expression(&index_expression.index); - } - - pub(super) fn find_in_constructor_expression( - &mut self, - constructor_expression: &ConstructorExpression, - ) { - for (_field_name, expression) in &constructor_expression.fields { - self.find_in_expression(expression); - } - } - - pub(super) fn find_in_member_access_expression( - &mut self, - member_access_expression: &MemberAccessExpression, - ) { - self.find_in_expression(&member_access_expression.lhs); - } - - pub(super) fn find_in_cast_expression(&mut self, cast_expression: &CastExpression) { - self.find_in_expression(&cast_expression.lhs); - } - - pub(super) fn find_in_infix_expression(&mut self, infix_expression: &InfixExpression) { - self.find_in_expression(&infix_expression.lhs); - self.find_in_expression(&infix_expression.rhs); - } - - pub(super) fn find_in_if_expression(&mut self, if_expression: &IfExpression) { - self.find_in_expression(&if_expression.condition); - self.find_in_expression(&if_expression.consequence); - - if let Some(alternative) = &if_expression.alternative { - self.find_in_expression(alternative); - } - } - - pub(super) fn find_in_lambda(&mut self, lambda: &Lambda) { - self.find_in_expression(&lambda.body); - } -} diff --git a/noir/noir-repo/tooling/lsp/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 046eca88099..c5d4bbc9788 100644 --- a/noir/noir-repo/tooling/nargo/Cargo.toml +++ b/noir/noir-repo/tooling/nargo/Cargo.toml @@ -23,7 +23,7 @@ noirc_printable_type.workspace = true iter-extended.workspace = true thiserror.workspace = true tracing.workspace = true -rayon = "1.8.0" +rayon.workspace = true jsonrpc.workspace = true rand.workspace = true serde.workspace = true diff --git a/noir/noir-repo/tooling/nargo/src/errors.rs b/noir/noir-repo/tooling/nargo/src/errors.rs index f9668653d0b..0b8378702cb 100644 --- a/noir/noir-repo/tooling/nargo/src/errors.rs +++ b/noir/noir-repo/tooling/nargo/src/errors.rs @@ -2,8 +2,8 @@ use std::collections::BTreeMap; use acvm::{ acir::circuit::{ - ErrorSelector, OpcodeLocation, RawAssertionPayload, ResolvedAssertionPayload, - ResolvedOpcodeLocation, + brillig::BrilligFunctionId, ErrorSelector, OpcodeLocation, RawAssertionPayload, + ResolvedAssertionPayload, ResolvedOpcodeLocation, }, pwg::{ErrorLocation, OpcodeResolutionError}, AcirField, FieldElement, @@ -66,7 +66,7 @@ impl NargoError { ) -> Option { match self { NargoError::ExecutionError(error) => match error { - ExecutionError::AssertionFailed(payload, _) => match payload { + ExecutionError::AssertionFailed(payload, _, _) => match payload { ResolvedAssertionPayload::String(message) => Some(message.to_string()), ResolvedAssertionPayload::Raw(raw) => { let abi_type = error_types.get(&raw.selector)?; @@ -90,7 +90,11 @@ impl NargoError { #[derive(Debug, Error)] pub enum ExecutionError { #[error("Failed assertion")] - AssertionFailed(ResolvedAssertionPayload, Vec), + AssertionFailed( + ResolvedAssertionPayload, + Vec, + Option, + ), #[error("Failed to solve program: '{}'", .0)] SolvingError(OpcodeResolutionError, Option>), @@ -106,7 +110,7 @@ fn extract_locations_from_error( OpcodeResolutionError::BrilligFunctionFailed { .. }, acir_call_stack, ) => acir_call_stack.clone(), - ExecutionError::AssertionFailed(_, call_stack) => Some(call_stack.clone()), + ExecutionError::AssertionFailed(_, call_stack, _) => Some(call_stack.clone()), ExecutionError::SolvingError( OpcodeResolutionError::IndexOutOfBounds { opcode_location: error_location, .. }, acir_call_stack, @@ -148,6 +152,7 @@ fn extract_locations_from_error( OpcodeResolutionError::BrilligFunctionFailed { function_id, .. }, _, ) => Some(*function_id), + ExecutionError::AssertionFailed(_, _, function_id) => *function_id, _ => None, }; @@ -158,13 +163,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 { @@ -184,6 +192,7 @@ fn extract_message_from_error( NargoError::ExecutionError(ExecutionError::AssertionFailed( ResolvedAssertionPayload::String(message), _, + _, )) => { format!("Assertion failed: '{message}'") } diff --git a/noir/noir-repo/tooling/nargo/src/ops/execute.rs b/noir/noir-repo/tooling/nargo/src/ops/execute.rs index 2e214c4e425..59d554d7ca5 100644 --- a/noir/noir-repo/tooling/nargo/src/ops/execute.rs +++ b/noir/noir-repo/tooling/nargo/src/ops/execute.rs @@ -117,10 +117,18 @@ impl<'a, F: AcirField, B: BlackBoxFunctionSolver, E: ForeignCallExecutor> _ => None, }; + let brillig_function_id = match &error { + OpcodeResolutionError::BrilligFunctionFailed { function_id, .. } => { + Some(*function_id) + } + _ => None, + }; + return Err(NargoError::ExecutionError(match assertion_payload { Some(payload) => ExecutionError::AssertionFailed( payload, call_stack.expect("Should have call stack for an assertion failure"), + brillig_function_id, ), None => ExecutionError::SolvingError(error, call_stack), })); @@ -167,6 +175,10 @@ impl<'a, F: AcirField, B: BlackBoxFunctionSolver, E: ForeignCallExecutor> } } } + // 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 f3d9f92caaa..284be56d247 100644 --- a/noir/noir-repo/tooling/nargo_cli/Cargo.toml +++ b/noir/noir-repo/tooling/nargo_cli/Cargo.toml @@ -31,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 @@ -42,7 +42,7 @@ toml.workspace = true serde.workspace = true serde_json.workspace = true prettytable-rs = "0.10" -rayon = "1.8.0" +rayon.workspace = true thiserror.workspace = true tower.workspace = true async-lsp = { workspace = true, features = ["client-monitor", "stdio", "tracing", "tokio"] } 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/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/src/rewrite/expr.rs b/noir/noir-repo/tooling/nargo_fmt/src/rewrite/expr.rs index 4fee7d3e197..caa60b17cc2 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/rewrite/expr.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/rewrite/expr.rs @@ -175,6 +175,9 @@ pub(crate) fn rewrite( 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 8d1e27078a8..6121f8debf6 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/rewrite/typ.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/rewrite/typ.rs @@ -73,6 +73,6 @@ pub(crate) fn rewrite(visitor: &FmtVisitor, _shape: Shape, typ: UnresolvedType) | UnresolvedTypeData::FormatString(_, _) | UnresolvedTypeData::Quoted(_) | UnresolvedTypeData::TraitAsType(_, _) => visitor.slice(typ.span).into(), - UnresolvedTypeData::Error => unreachable!(), + UnresolvedTypeData::Interned(_) | UnresolvedTypeData::Error => unreachable!(), } } 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/noir_js_backend_barretenberg/package.json b/noir/noir-repo/tooling/noir_js_backend_barretenberg/package.json index a5593cc284c..4ec715e27eb 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.51.1", "@noir-lang/types": "workspace:*", "fflate": "^0.8.0" }, 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/profiler/src/cli/gates_flamegraph_cmd.rs b/noir/noir-repo/tooling/profiler/src/cli/gates_flamegraph_cmd.rs index 0fa12239d05..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(); 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 d7f3cbb9b85..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(); 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/yarn.lock b/noir/noir-repo/yarn.lock index f77e9f7e72e..5d1d41f58fd 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.51.1": + version: 0.51.1 + resolution: "@aztec/bb.js@npm:0.51.1" dependencies: comlink: ^4.4.1 commander: ^10.0.1 debug: ^4.3.4 tslib: ^2.4.0 bin: - bb.js: ./dest/node/main.js + bb.js: dest/node/main.js + checksum: 246953d4d2becc86001b8e6d49ab8c0b4fa4833a9bf1d2177d0fb4e271e66f9dc65e3b02b69b00fbfcb935a11e2f90b5ac229b8c8938c34604fe54b29b3accfb 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.51.1 "@noir-lang/types": "workspace:*" "@types/node": ^20.6.2 "@types/prettier": ^3