From a48fa94d7e02f40d436fe413dbaf2dc2420c4ae8 Mon Sep 17 00:00:00 2001 From: AztecBot Date: Mon, 9 Sep 2024 13:37:51 +0000 Subject: [PATCH 1/3] chore: apply sync fixes --- .aztec-sync-commit | 2 +- .gitattributes | 1 - acvm-repo/acir/codegen/acir.cpp | 16 +- .../acir/tests/test_program_serialization.rs | 73 +++- acvm-repo/acvm/tests/solver.rs | 87 +++- .../test/shared/complex_foreign_call.ts | 15 +- acvm-repo/acvm_js/test/shared/foreign_call.ts | 9 +- acvm-repo/brillig/src/opcodes.rs | 4 +- acvm-repo/brillig_vm/src/lib.rs | 381 ++++++++++++------ aztec_macros/src/utils/hir_utils.rs | 4 +- compiler/noirc_driver/src/lib.rs | 5 - .../brillig/brillig_gen/brillig_directive.rs | 34 +- .../src/brillig/brillig_ir/entry_point.rs | 106 ++--- .../src/brillig/brillig_ir/instructions.rs | 8 +- .../noirc_evaluator/src/ssa/opt/mem2reg.rs | 115 +++++- compiler/noirc_frontend/src/ast/mod.rs | 1 + compiler/noirc_frontend/src/ast/visitor.rs | 55 ++- .../noirc_frontend/src/elaborator/comptime.rs | 45 ++- compiler/noirc_frontend/src/elaborator/mod.rs | 27 +- .../noirc_frontend/src/elaborator/types.rs | 11 +- .../noirc_frontend/src/hir/comptime/errors.rs | 41 +- .../src/hir/comptime/interpreter.rs | 22 +- .../src/hir/comptime/interpreter/builtin.rs | 266 ++++++++++-- .../noirc_frontend/src/hir/comptime/tests.rs | 2 +- .../noirc_frontend/src/hir/comptime/value.rs | 10 +- .../src/hir/def_collector/dc_crate.rs | 11 +- .../noirc_frontend/src/hir/def_map/mod.rs | 2 - compiler/noirc_frontend/src/parser/mod.rs | 4 +- compiler/noirc_frontend/src/parser/parser.rs | 4 +- compiler/noirc_frontend/src/tests.rs | 2 - docs/docs/noir/concepts/generics.md | 59 ++- .../standard_library/meta/function_def.md | 28 +- .../docs/noir/standard_library/meta/module.md | 8 + .../noir/standard_library/meta/struct_def.md | 33 ++ docs/docs/noir/standard_library/meta/typ.md | 24 ++ noir_stdlib/src/cmp.nr | 8 +- noir_stdlib/src/meta/function_def.nr | 15 + noir_stdlib/src/meta/module.nr | 5 + noir_stdlib/src/meta/struct_def.nr | 21 +- noir_stdlib/src/meta/typ.nr | 5 + .../Nargo.toml | 7 + .../src/main.nr | 32 ++ .../arithmetic_generics_underflow/Nargo.toml | 7 + .../arithmetic_generics_underflow/src/main.nr | 14 + .../comptime_function_definition/src/main.nr | 17 + .../comptime_module/src/main.nr | 18 + .../Nargo.toml | 2 +- .../comptime_struct_definition/src/main.nr | 50 +++ .../comptime_type/src/main.nr | 35 ++ .../comptime_type_definition/src/main.nr | 26 -- .../references_aliasing/src/main.nr | 13 + .../execution_success/derive/src/main.nr | 6 + tooling/debugger/src/context.rs | 64 ++- tooling/lsp/src/requests/completion.rs | 153 ++++++- .../lsp/src/requests/completion/builtins.rs | 36 +- .../requests/completion/completion_items.rs | 72 +++- tooling/lsp/src/requests/completion/kinds.rs | 4 +- tooling/lsp/src/requests/completion/tests.rs | 68 +++- tooling/nargo_cli/build.rs | 2 +- 59 files changed, 1738 insertions(+), 457 deletions(-) delete mode 100644 .gitattributes create mode 100644 test_programs/compile_failure/arithmetic_generics_intermediate_underflow/Nargo.toml create mode 100644 test_programs/compile_failure/arithmetic_generics_intermediate_underflow/src/main.nr create mode 100644 test_programs/compile_failure/arithmetic_generics_underflow/Nargo.toml create mode 100644 test_programs/compile_failure/arithmetic_generics_underflow/src/main.nr rename test_programs/compile_success_empty/{comptime_type_definition => comptime_struct_definition}/Nargo.toml (69%) create mode 100644 test_programs/compile_success_empty/comptime_struct_definition/src/main.nr delete mode 100644 test_programs/compile_success_empty/comptime_type_definition/src/main.nr diff --git a/.aztec-sync-commit b/.aztec-sync-commit index a9f74612f46..c82e80ca7e5 100644 --- a/.aztec-sync-commit +++ b/.aztec-sync-commit @@ -1 +1 @@ -05cc59fd28b4d0ee89343106e538c0db0e70f52f +ae863471fed30ea3382aea8223d7ddf8e9eef4ee diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 204cff5c097..00000000000 --- a/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -*.nr linguist-language=rust diff --git a/acvm-repo/acir/codegen/acir.cpp b/acvm-repo/acir/codegen/acir.cpp index 0ccf7e4639d..ef5baf076a0 100644 --- a/acvm-repo/acir/codegen/acir.cpp +++ b/acvm-repo/acir/codegen/acir.cpp @@ -610,8 +610,8 @@ namespace Program { struct CalldataCopy { Program::MemoryAddress destination_address; - uint64_t size; - uint64_t offset; + Program::MemoryAddress size_address; + Program::MemoryAddress offset_address; friend bool operator==(const CalldataCopy&, const CalldataCopy&); std::vector bincodeSerialize() const; @@ -5278,8 +5278,8 @@ namespace Program { inline bool operator==(const BrilligOpcode::CalldataCopy &lhs, const BrilligOpcode::CalldataCopy &rhs) { if (!(lhs.destination_address == rhs.destination_address)) { return false; } - if (!(lhs.size == rhs.size)) { return false; } - if (!(lhs.offset == rhs.offset)) { return false; } + if (!(lhs.size_address == rhs.size_address)) { return false; } + if (!(lhs.offset_address == rhs.offset_address)) { return false; } return true; } @@ -5304,8 +5304,8 @@ template <> template void serde::Serializable::serialize(const Program::BrilligOpcode::CalldataCopy &obj, Serializer &serializer) { serde::Serializable::serialize(obj.destination_address, serializer); - serde::Serializable::serialize(obj.size, serializer); - serde::Serializable::serialize(obj.offset, serializer); + serde::Serializable::serialize(obj.size_address, serializer); + serde::Serializable::serialize(obj.offset_address, serializer); } template <> @@ -5313,8 +5313,8 @@ template Program::BrilligOpcode::CalldataCopy serde::Deserializable::deserialize(Deserializer &deserializer) { Program::BrilligOpcode::CalldataCopy obj; obj.destination_address = serde::Deserializable::deserialize(deserializer); - obj.size = serde::Deserializable::deserialize(deserializer); - obj.offset = serde::Deserializable::deserialize(deserializer); + obj.size_address = serde::Deserializable::deserialize(deserializer); + obj.offset_address = serde::Deserializable::deserialize(deserializer); return obj; } diff --git a/acvm-repo/acir/tests/test_program_serialization.rs b/acvm-repo/acir/tests/test_program_serialization.rs index 7bed57e22a0..838886a03ce 100644 --- a/acvm-repo/acir/tests/test_program_serialization.rs +++ b/acvm-repo/acir/tests/test_program_serialization.rs @@ -164,10 +164,20 @@ fn simple_brillig_foreign_call() { let brillig_bytecode = BrilligBytecode { bytecode: vec![ + brillig::Opcode::Const { + destination: MemoryAddress(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(1_usize), + }, + brillig::Opcode::Const { + destination: MemoryAddress(1), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0_usize), + }, brillig::Opcode::CalldataCopy { destination_address: MemoryAddress(0), - size: 1, - offset: 0, + size_address: MemoryAddress(0), + offset_address: MemoryAddress(1), }, brillig::Opcode::ForeignCall { function: "invert".into(), @@ -204,11 +214,12 @@ fn simple_brillig_foreign_call() { let bytes = Program::serialize_program(&program); let expected_serialization: Vec = vec![ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 173, 80, 49, 10, 192, 32, 12, 52, 45, 45, 133, 110, 190, - 68, 127, 224, 103, 28, 92, 28, 68, 124, 191, 130, 9, 4, 137, 46, 122, 16, 46, 119, 7, 33, - 9, 168, 142, 175, 21, 96, 255, 32, 147, 230, 32, 207, 33, 155, 61, 88, 56, 55, 203, 240, - 125, 175, 177, 1, 110, 170, 197, 101, 55, 242, 43, 100, 132, 159, 229, 33, 22, 159, 242, - 234, 87, 51, 45, 121, 90, 200, 42, 48, 209, 35, 111, 164, 1, 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 173, 81, 49, 10, 128, 48, 12, 108, 196, 138, 224, 230, + 75, 226, 15, 252, 140, 131, 139, 131, 136, 239, 111, 161, 9, 28, 165, 205, 210, 28, 132, + 36, 119, 16, 114, 9, 133, 130, 53, 7, 73, 29, 37, 107, 143, 80, 238, 148, 204, 99, 56, 200, + 111, 22, 227, 190, 83, 93, 16, 146, 193, 112, 22, 225, 34, 168, 205, 142, 174, 241, 218, + 206, 179, 121, 49, 188, 109, 57, 84, 191, 159, 255, 122, 63, 235, 199, 189, 190, 197, 237, + 13, 45, 1, 20, 245, 146, 30, 92, 2, 0, 0, ]; assert_eq!(bytes, expected_serialization) @@ -230,20 +241,40 @@ fn complex_brillig_foreign_call() { let brillig_bytecode = BrilligBytecode { bytecode: vec![ + brillig::Opcode::Const { + destination: MemoryAddress(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(3_usize), + }, + brillig::Opcode::Const { + destination: MemoryAddress(1), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0_usize), + }, brillig::Opcode::CalldataCopy { destination_address: MemoryAddress(32), - size: 3, - offset: 0, + size_address: MemoryAddress(0), + offset_address: MemoryAddress(1), }, brillig::Opcode::Const { destination: MemoryAddress(0), value: FieldElement::from(32_usize), bit_size: BitSize::Integer(IntegerBitSize::U32), }, + brillig::Opcode::Const { + destination: MemoryAddress(3), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(1_usize), + }, + brillig::Opcode::Const { + destination: MemoryAddress(4), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(3_usize), + }, brillig::Opcode::CalldataCopy { destination_address: MemoryAddress(1), - size: 1, - offset: 3, + size_address: MemoryAddress(3), + offset_address: MemoryAddress(4), }, // Oracles are named 'foreign calls' in brillig brillig::Opcode::ForeignCall { @@ -307,15 +338,17 @@ fn complex_brillig_foreign_call() { let bytes = Program::serialize_program(&program); let expected_serialization: Vec = vec![ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 213, 84, 75, 10, 132, 48, 12, 77, 90, 199, 17, 102, 55, - 39, 24, 152, 57, 64, 199, 19, 120, 23, 113, 167, 232, 210, 227, 107, 49, 98, 124, 22, 92, - 88, 65, 31, 148, 244, 147, 207, 75, 66, 202, 52, 33, 27, 23, 203, 254, 33, 210, 136, 244, - 247, 150, 214, 152, 117, 11, 145, 238, 24, 254, 28, 207, 151, 59, 139, 163, 185, 1, 71, - 123, 2, 71, 82, 253, 191, 96, 191, 99, 246, 37, 106, 253, 108, 96, 126, 18, 154, 230, 43, - 149, 243, 83, 100, 134, 133, 246, 70, 134, 182, 131, 183, 2, 78, 172, 247, 250, 1, 71, 132, - 17, 196, 46, 137, 150, 105, 238, 82, 197, 133, 33, 254, 75, 101, 89, 182, 77, 87, 87, 189, - 5, 85, 164, 251, 85, 251, 31, 188, 51, 216, 161, 173, 134, 254, 192, 66, 186, 28, 208, 219, - 243, 253, 166, 165, 196, 115, 217, 7, 253, 216, 100, 109, 69, 5, 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 213, 85, 81, 14, 194, 48, 8, 133, 118, 206, 26, 255, 60, + 129, 137, 30, 160, 211, 11, 120, 23, 227, 159, 70, 63, 61, 190, 146, 209, 140, 177, 46, + 251, 24, 77, 182, 151, 44, 116, 45, 16, 120, 64, 139, 208, 34, 252, 63, 228, 245, 134, 165, + 99, 73, 251, 30, 250, 72, 186, 55, 150, 113, 30, 26, 180, 243, 21, 75, 197, 232, 86, 16, + 163, 47, 16, 35, 136, 250, 47, 176, 222, 150, 117, 49, 229, 207, 103, 230, 167, 130, 118, + 190, 106, 254, 223, 178, 12, 154, 104, 50, 114, 48, 28, 188, 30, 82, 247, 236, 180, 23, 62, + 171, 236, 178, 185, 202, 27, 194, 216, 119, 36, 54, 142, 35, 185, 149, 203, 233, 18, 131, + 34, 220, 48, 167, 38, 176, 191, 18, 181, 168, 5, 63, 178, 179, 8, 123, 232, 186, 234, 254, + 126, 125, 158, 143, 175, 87, 148, 74, 51, 194, 73, 172, 207, 234, 28, 149, 157, 182, 149, + 144, 15, 70, 78, 23, 51, 122, 83, 190, 15, 208, 181, 70, 122, 152, 126, 56, 83, 244, 10, + 181, 6, 0, 0, ]; assert_eq!(bytes, expected_serialization) diff --git a/acvm-repo/acvm/tests/solver.rs b/acvm-repo/acvm/tests/solver.rs index 2a06e07f092..cd25ed6197b 100644 --- a/acvm-repo/acvm/tests/solver.rs +++ b/acvm-repo/acvm/tests/solver.rs @@ -1,6 +1,7 @@ use std::collections::{BTreeMap, HashSet}; use std::sync::Arc; +use acir::brillig::{BitSize, IntegerBitSize}; use acir::{ acir_field::GenericFieldElement, brillig::{BinaryFieldOp, HeapArray, MemoryAddress, Opcode as BrilligOpcode, ValueOrArray}, @@ -122,10 +123,20 @@ fn inversion_brillig_oracle_equivalence() { let brillig_bytecode = BrilligBytecode { bytecode: vec![ + BrilligOpcode::Const { + destination: MemoryAddress(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(2u64), + }, + BrilligOpcode::Const { + destination: MemoryAddress(1), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, BrilligOpcode::CalldataCopy { destination_address: MemoryAddress(0), - size: 2, - offset: 0, + size_address: MemoryAddress(0), + offset_address: MemoryAddress(1), }, equal_opcode, // Oracles are named 'foreign calls' in brillig @@ -258,10 +269,20 @@ fn double_inversion_brillig_oracle() { let brillig_bytecode = BrilligBytecode { bytecode: vec![ + BrilligOpcode::Const { + destination: MemoryAddress(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(3u64), + }, + BrilligOpcode::Const { + destination: MemoryAddress(1), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, BrilligOpcode::CalldataCopy { destination_address: MemoryAddress(0), - size: 3, - offset: 0, + size_address: MemoryAddress(0), + offset_address: MemoryAddress(1), }, equal_opcode, // Oracles are named 'foreign calls' in brillig @@ -366,12 +387,21 @@ fn oracle_dependent_execution() { let brillig_bytecode = BrilligBytecode { bytecode: vec![ + BrilligOpcode::Const { + destination: MemoryAddress(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(3u64), + }, + BrilligOpcode::Const { + destination: MemoryAddress(1), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, BrilligOpcode::CalldataCopy { destination_address: MemoryAddress(0), - size: 3, - offset: 0, - }, - // Oracles are named 'foreign calls' in brillig + size_address: MemoryAddress(0), + offset_address: MemoryAddress(1), + }, // Oracles are named 'foreign calls' in brillig BrilligOpcode::ForeignCall { function: "invert".into(), destinations: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(1))], @@ -498,10 +528,20 @@ fn brillig_oracle_predicate() { let brillig_bytecode = BrilligBytecode { bytecode: vec![ + BrilligOpcode::Const { + destination: MemoryAddress(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(2u64), + }, + BrilligOpcode::Const { + destination: MemoryAddress(1), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, BrilligOpcode::CalldataCopy { destination_address: MemoryAddress(0), - size: 2, - offset: 0, + size_address: MemoryAddress(0), + offset_address: MemoryAddress(1), }, equal_opcode, // Oracles are named 'foreign calls' in brillig @@ -607,8 +647,11 @@ fn unsatisfied_opcode_resolved_brillig() { let w_y = Witness(5); let w_result = Witness(6); - let calldata_copy_opcode = - BrilligOpcode::CalldataCopy { destination_address: MemoryAddress(0), size: 2, offset: 0 }; + let calldata_copy_opcode = BrilligOpcode::CalldataCopy { + destination_address: MemoryAddress(0), + size_address: MemoryAddress(0), + offset_address: MemoryAddress(1), + }; let equal_opcode = BrilligOpcode::BinaryFieldOp { op: BinaryFieldOp::Equals, @@ -627,7 +670,23 @@ fn unsatisfied_opcode_resolved_brillig() { let stop_opcode = BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 0 }; let brillig_bytecode = BrilligBytecode { - bytecode: vec![calldata_copy_opcode, equal_opcode, jmp_if_opcode, trap_opcode, stop_opcode], + bytecode: vec![ + BrilligOpcode::Const { + destination: MemoryAddress(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(2u64), + }, + BrilligOpcode::Const { + destination: MemoryAddress(1), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, + calldata_copy_opcode, + equal_opcode, + jmp_if_opcode, + trap_opcode, + stop_opcode, + ], }; let opcode_a = Expression { @@ -679,7 +738,7 @@ fn unsatisfied_opcode_resolved_brillig() { ACVMStatus::Failure(OpcodeResolutionError::BrilligFunctionFailed { function_id: BrilligFunctionId(0), payload: None, - call_stack: vec![OpcodeLocation::Brillig { acir_index: 0, brillig_index: 3 }] + call_stack: vec![OpcodeLocation::Brillig { acir_index: 0, brillig_index: 5 }] }), "The first opcode is not satisfiable, expected an error indicating this" ); diff --git a/acvm-repo/acvm_js/test/shared/complex_foreign_call.ts b/acvm-repo/acvm_js/test/shared/complex_foreign_call.ts index 53597ece157..ba26e3d139a 100644 --- a/acvm-repo/acvm_js/test/shared/complex_foreign_call.ts +++ b/acvm-repo/acvm_js/test/shared/complex_foreign_call.ts @@ -2,13 +2,14 @@ import { WitnessMap } from '@noir-lang/acvm_js'; // See `complex_brillig_foreign_call` integration test in `acir/tests/test_program_serialization.rs`. export const bytecode = Uint8Array.from([ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 213, 84, 75, 10, 132, 48, 12, 77, 90, 199, 17, 102, 55, 39, 24, 152, 57, 64, 199, - 19, 120, 23, 113, 167, 232, 210, 227, 107, 49, 98, 124, 22, 92, 88, 65, 31, 148, 244, 147, 207, 75, 66, 202, 52, 33, - 27, 23, 203, 254, 33, 210, 136, 244, 247, 150, 214, 152, 117, 11, 145, 238, 24, 254, 28, 207, 151, 59, 139, 163, 185, - 1, 71, 123, 2, 71, 82, 253, 191, 96, 191, 99, 246, 37, 106, 253, 108, 96, 126, 18, 154, 230, 43, 149, 243, 83, 100, - 134, 133, 246, 70, 134, 182, 131, 183, 2, 78, 172, 247, 250, 1, 71, 132, 17, 196, 46, 137, 150, 105, 238, 82, 197, - 133, 33, 254, 75, 101, 89, 182, 77, 87, 87, 189, 5, 85, 164, 251, 85, 251, 31, 188, 51, 216, 161, 173, 134, 254, 192, - 66, 186, 28, 208, 219, 243, 253, 166, 165, 196, 115, 217, 7, 253, 216, 100, 109, 69, 5, 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 213, 85, 81, 14, 194, 48, 8, 133, 118, 206, 26, 255, 60, 129, 137, 30, 160, 211, + 11, 120, 23, 227, 159, 70, 63, 61, 190, 146, 209, 140, 177, 46, 251, 24, 77, 182, 151, 44, 116, 45, 16, 120, 64, 139, + 208, 34, 252, 63, 228, 245, 134, 165, 99, 73, 251, 30, 250, 72, 186, 55, 150, 113, 30, 26, 180, 243, 21, 75, 197, 232, + 86, 16, 163, 47, 16, 35, 136, 250, 47, 176, 222, 150, 117, 49, 229, 207, 103, 230, 167, 130, 118, 190, 106, 254, 223, + 178, 12, 154, 104, 50, 114, 48, 28, 188, 30, 82, 247, 236, 180, 23, 62, 171, 236, 178, 185, 202, 27, 194, 216, 119, + 36, 54, 142, 35, 185, 149, 203, 233, 18, 131, 34, 220, 48, 167, 38, 176, 191, 18, 181, 168, 5, 63, 178, 179, 8, 123, + 232, 186, 234, 254, 126, 125, 158, 143, 175, 87, 148, 74, 51, 194, 73, 172, 207, 234, 28, 149, 157, 182, 149, 144, 15, + 70, 78, 23, 51, 122, 83, 190, 15, 208, 181, 70, 122, 152, 126, 56, 83, 244, 10, 181, 6, 0, 0, ]); export const initialWitnessMap: WitnessMap = new Map([ [1, '0x0000000000000000000000000000000000000000000000000000000000000001'], diff --git a/acvm-repo/acvm_js/test/shared/foreign_call.ts b/acvm-repo/acvm_js/test/shared/foreign_call.ts index 3500e03776d..498a914cff4 100644 --- a/acvm-repo/acvm_js/test/shared/foreign_call.ts +++ b/acvm-repo/acvm_js/test/shared/foreign_call.ts @@ -2,10 +2,11 @@ import { WitnessMap } from '@noir-lang/acvm_js'; // See `simple_brillig_foreign_call` integration test in `acir/tests/test_program_serialization.rs`. export const bytecode = Uint8Array.from([ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 173, 80, 49, 10, 192, 32, 12, 52, 45, 45, 133, 110, 190, 68, 127, 224, 103, 28, 92, - 28, 68, 124, 191, 130, 9, 4, 137, 46, 122, 16, 46, 119, 7, 33, 9, 168, 142, 175, 21, 96, 255, 32, 147, 230, 32, 207, - 33, 155, 61, 88, 56, 55, 203, 240, 125, 175, 177, 1, 110, 170, 197, 101, 55, 242, 43, 100, 132, 159, 229, 33, 22, 159, - 242, 234, 87, 51, 45, 121, 90, 200, 42, 48, 209, 35, 111, 164, 1, 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 173, 81, 49, 10, 128, 48, 12, 108, 196, 138, 224, 230, 75, 226, 15, 252, 140, 131, + 139, 131, 136, 239, 111, 161, 9, 28, 165, 205, 210, 28, 132, 36, 119, 16, 114, 9, 133, 130, 53, 7, 73, 29, 37, 107, + 143, 80, 238, 148, 204, 99, 56, 200, 111, 22, 227, 190, 83, 93, 16, 146, 193, 112, 22, 225, 34, 168, 205, 142, 174, + 241, 218, 206, 179, 121, 49, 188, 109, 57, 84, 191, 159, 255, 122, 63, 235, 199, 189, 190, 197, 237, 13, 45, 1, 20, + 245, 146, 30, 92, 2, 0, 0, ]); export const initialWitnessMap: WitnessMap = new Map([ [1, '0x0000000000000000000000000000000000000000000000000000000000000005'], diff --git a/acvm-repo/brillig/src/opcodes.rs b/acvm-repo/brillig/src/opcodes.rs index 2054a34d459..ac469cebf87 100644 --- a/acvm-repo/brillig/src/opcodes.rs +++ b/acvm-repo/brillig/src/opcodes.rs @@ -210,8 +210,8 @@ pub enum BrilligOpcode { /// Copies calldata after the offset to the specified address and length CalldataCopy { destination_address: MemoryAddress, - size: usize, - offset: usize, + size_address: MemoryAddress, + offset_address: MemoryAddress, }, /// We don't support dynamic jumps or calls /// See https://github.com/ethereum/aleth/issues/3404 for reasoning diff --git a/acvm-repo/brillig_vm/src/lib.rs b/acvm-repo/brillig_vm/src/lib.rs index 5097ecf4707..2c2ab17230f 100644 --- a/acvm-repo/brillig_vm/src/lib.rs +++ b/acvm-repo/brillig_vm/src/lib.rs @@ -236,8 +236,10 @@ impl<'a, F: AcirField, B: BlackBoxFunctionSolver> VM<'a, F, B> { } self.set_program_counter(*destination) } - Opcode::CalldataCopy { destination_address, size, offset } => { - let values: Vec<_> = self.calldata[*offset..(*offset + size)] + Opcode::CalldataCopy { destination_address, size_address, offset_address } => { + let size = self.memory.read(*size_address).to_usize(); + let offset = self.memory.read(*offset_address).to_usize(); + let values: Vec<_> = self.calldata[offset..(offset + size)] .iter() .map(|value| MemoryValue::new_field(*value)) .collect(); @@ -754,24 +756,17 @@ mod tests { #[test] fn add_single_step_smoke() { - let calldata = vec![FieldElement::from(27u128)]; - - // Add opcode to add the value in address `0` and `1` - // and place the output in address `2` - let calldata_copy = Opcode::CalldataCopy { - destination_address: MemoryAddress::from(0), - size: 1, - offset: 0, - }; + let calldata = vec![]; + + let opcodes = [Opcode::Const { + destination: MemoryAddress(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(27u128), + }]; // Start VM - let opcodes = [calldata_copy]; let mut vm = VM::new(calldata, &opcodes, vec![], &StubbedBlackBoxSolver); - // Process a single VM opcode - // - // After processing a single opcode, we should have - // the vm status as finished since there is only one opcode let status = vm.process_opcode(); assert_eq!(status, VMStatus::Finished { return_data_offset: 0, return_data_size: 0 }); @@ -786,7 +781,6 @@ mod tests { #[test] fn jmpif_opcode() { let mut calldata: Vec = vec![]; - let mut opcodes = vec![]; let lhs = { calldata.push(2u128.into()); @@ -800,21 +794,35 @@ mod tests { let destination = MemoryAddress::from(calldata.len()); - opcodes.push(Opcode::CalldataCopy { - destination_address: MemoryAddress::from(0), - size: 2, - offset: 0, - }); - - opcodes.push(Opcode::BinaryFieldOp { destination, op: BinaryFieldOp::Equals, lhs, rhs }); - opcodes.push(Opcode::Jump { location: 3 }); - opcodes.push(Opcode::JumpIf { condition: destination, location: 4 }); + let opcodes = vec![ + Opcode::Const { + destination: MemoryAddress(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(2u64), + }, + Opcode::Const { + destination: MemoryAddress(1), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, + Opcode::CalldataCopy { + destination_address: MemoryAddress(0), + size_address: MemoryAddress(0), + offset_address: MemoryAddress(1), + }, + Opcode::BinaryFieldOp { destination, op: BinaryFieldOp::Equals, lhs, rhs }, + Opcode::Jump { location: 5 }, + Opcode::JumpIf { condition: destination, location: 6 }, + ]; let mut vm = VM::new(calldata, &opcodes, vec![], &StubbedBlackBoxSolver); let status = vm.process_opcode(); assert_eq!(status, VMStatus::InProgress); - + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::InProgress); + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::InProgress); let status = vm.process_opcode(); assert_eq!(status, VMStatus::InProgress); @@ -832,48 +840,49 @@ mod tests { fn jmpifnot_opcode() { let calldata: Vec = vec![1u128.into(), 2u128.into()]; - let calldata_copy = Opcode::CalldataCopy { - destination_address: MemoryAddress::from(0), - size: 2, - offset: 0, - }; - - let jump_opcode = Opcode::Jump { location: 3 }; - - let trap_opcode = Opcode::Trap { revert_data: HeapArray::default() }; - - let not_equal_cmp_opcode = Opcode::BinaryFieldOp { - op: BinaryFieldOp::Equals, - lhs: MemoryAddress::from(0), - rhs: MemoryAddress::from(1), - destination: MemoryAddress::from(2), - }; - - let jump_if_not_opcode = - Opcode::JumpIfNot { condition: MemoryAddress::from(2), location: 2 }; - - let add_opcode = Opcode::BinaryFieldOp { - op: BinaryFieldOp::Add, - lhs: MemoryAddress::from(0), - rhs: MemoryAddress::from(1), - destination: MemoryAddress::from(2), - }; - - let opcodes = [ - calldata_copy, - jump_opcode, - trap_opcode, - not_equal_cmp_opcode, - jump_if_not_opcode, - add_opcode, + let opcodes = vec![ + Opcode::Const { + destination: MemoryAddress(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(2u64), + }, + Opcode::Const { + destination: MemoryAddress(1), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, + Opcode::CalldataCopy { + destination_address: MemoryAddress(0), + size_address: MemoryAddress(0), + offset_address: MemoryAddress(1), + }, + Opcode::Jump { location: 5 }, + Opcode::Trap { revert_data: HeapArray::default() }, + Opcode::BinaryFieldOp { + op: BinaryFieldOp::Equals, + lhs: MemoryAddress::from(0), + rhs: MemoryAddress::from(1), + destination: MemoryAddress::from(2), + }, + Opcode::JumpIfNot { condition: MemoryAddress::from(2), location: 4 }, + Opcode::BinaryFieldOp { + op: BinaryFieldOp::Add, + lhs: MemoryAddress::from(0), + rhs: MemoryAddress::from(1), + destination: MemoryAddress::from(2), + }, ]; + let mut vm = VM::new(calldata, &opcodes, vec![], &StubbedBlackBoxSolver); + + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::InProgress); + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::InProgress); let status = vm.process_opcode(); assert_eq!(status, VMStatus::InProgress); - let status = vm.process_opcode(); assert_eq!(status, VMStatus::InProgress); - let status = vm.process_opcode(); assert_eq!(status, VMStatus::InProgress); @@ -888,7 +897,7 @@ mod tests { status, VMStatus::Failure { reason: FailureReason::Trap { revert_data_offset: 0, revert_data_size: 0 }, - call_stack: vec![2] + call_stack: vec![4] } ); @@ -903,10 +912,20 @@ mod tests { let calldata: Vec = vec![((2_u128.pow(32)) - 1).into()]; let opcodes = &[ + Opcode::Const { + destination: MemoryAddress(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(1u64), + }, + Opcode::Const { + destination: MemoryAddress(1), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, Opcode::CalldataCopy { - destination_address: MemoryAddress::from(0), - size: 1, - offset: 0, + destination_address: MemoryAddress(0), + size_address: MemoryAddress(0), + offset_address: MemoryAddress(1), }, Opcode::Cast { destination: MemoryAddress::from(1), @@ -919,10 +938,12 @@ mod tests { let status = vm.process_opcode(); assert_eq!(status, VMStatus::InProgress); - let status = vm.process_opcode(); assert_eq!(status, VMStatus::InProgress); - + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::InProgress); + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::InProgress); let status = vm.process_opcode(); assert_eq!(status, VMStatus::Finished { return_data_offset: 1, return_data_size: 1 }); @@ -936,22 +957,34 @@ mod tests { fn mov_opcode() { let calldata: Vec = vec![(1u128).into(), (2u128).into(), (3u128).into()]; - let calldata_copy = Opcode::CalldataCopy { - destination_address: MemoryAddress::from(0), - size: 3, - offset: 0, - }; - - let mov_opcode = - Opcode::Mov { destination: MemoryAddress::from(2), source: MemoryAddress::from(0) }; - - let opcodes = &[calldata_copy, mov_opcode]; + let opcodes = &[ + Opcode::Const { + destination: MemoryAddress(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(3u64), + }, + Opcode::Const { + destination: MemoryAddress(1), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, + Opcode::CalldataCopy { + destination_address: MemoryAddress(0), + size_address: MemoryAddress(0), + offset_address: MemoryAddress(1), + }, + Opcode::Mov { destination: MemoryAddress::from(2), source: MemoryAddress::from(0) }, + ]; let mut vm = VM::new(calldata, opcodes, vec![], &StubbedBlackBoxSolver); let status = vm.process_opcode(); assert_eq!(status, VMStatus::InProgress); - let status = vm.process_opcode(); + assert_eq!(status, VMStatus::InProgress); + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::InProgress); + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::Finished { return_data_offset: 0, return_data_size: 0 }); let VM { memory, .. } = vm; @@ -968,28 +1001,32 @@ mod tests { let calldata: Vec = vec![(0u128).into(), (1u128).into(), (2u128).into(), (3u128).into()]; - let calldata_copy = Opcode::CalldataCopy { - destination_address: MemoryAddress::from(0), - size: 4, - offset: 0, - }; - - let cast_zero = Opcode::Cast { - destination: MemoryAddress::from(0), - source: MemoryAddress::from(0), - bit_size: BitSize::Integer(IntegerBitSize::U1), - }; - - let cast_one = Opcode::Cast { - destination: MemoryAddress::from(1), - source: MemoryAddress::from(1), - bit_size: BitSize::Integer(IntegerBitSize::U1), - }; - let opcodes = &[ - calldata_copy, - cast_zero, - cast_one, + Opcode::Const { + destination: MemoryAddress(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(4u64), + }, + Opcode::Const { + destination: MemoryAddress(1), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, + Opcode::CalldataCopy { + destination_address: MemoryAddress(0), + size_address: MemoryAddress(0), + offset_address: MemoryAddress(1), + }, + Opcode::Cast { + destination: MemoryAddress::from(0), + source: MemoryAddress::from(0), + bit_size: BitSize::Integer(IntegerBitSize::U1), + }, + Opcode::Cast { + destination: MemoryAddress::from(1), + source: MemoryAddress::from(1), + bit_size: BitSize::Integer(IntegerBitSize::U1), + }, Opcode::ConditionalMov { destination: MemoryAddress(4), // Sets 3_u128 to memory address 4 source_a: MemoryAddress(2), @@ -1007,16 +1044,16 @@ mod tests { let status = vm.process_opcode(); assert_eq!(status, VMStatus::InProgress); - let status = vm.process_opcode(); assert_eq!(status, VMStatus::InProgress); - let status = vm.process_opcode(); assert_eq!(status, VMStatus::InProgress); - let status = vm.process_opcode(); assert_eq!(status, VMStatus::InProgress); - + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::InProgress); + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::InProgress); let status = vm.process_opcode(); assert_eq!(status, VMStatus::Finished { return_data_offset: 0, return_data_size: 0 }); @@ -1036,11 +1073,23 @@ mod tests { vec![(2u128).into(), (2u128).into(), (0u128).into(), (5u128).into(), (6u128).into()]; let calldata_size = calldata.len(); - let calldata_copy = Opcode::CalldataCopy { - destination_address: MemoryAddress::from(0), - size: 5, - offset: 0, - }; + let calldata_copy_opcodes = vec![ + Opcode::Const { + destination: MemoryAddress(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(5u64), + }, + Opcode::Const { + destination: MemoryAddress(1), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, + Opcode::CalldataCopy { + destination_address: MemoryAddress(0), + size_address: MemoryAddress(0), + offset_address: MemoryAddress(1), + }, + ]; let cast_opcodes: Vec<_> = (0..calldata_size) .map(|index| Opcode::Cast { @@ -1082,7 +1131,8 @@ mod tests { destination: MemoryAddress::from(2), }; - let opcodes: Vec<_> = std::iter::once(calldata_copy) + let opcodes: Vec<_> = calldata_copy_opcodes + .into_iter() .chain(cast_opcodes) .chain([equal_opcode, not_equal_opcode, less_than_opcode, less_than_equal_opcode]) .collect(); @@ -1091,6 +1141,10 @@ mod tests { // Calldata copy let status = vm.process_opcode(); assert_eq!(status, VMStatus::InProgress); + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::InProgress); + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::InProgress); for _ in 0..calldata_size { let status = vm.process_opcode(); @@ -1242,7 +1296,7 @@ mod tests { let r_tmp = MemoryAddress::from(3); let r_pointer = MemoryAddress::from(4); - let start: [Opcode; 5] = [ + let start = [ // sum = 0 Opcode::Const { destination: r_sum, value: 0u128.into(), bit_size: BitSize::Field }, // i = 0 @@ -1263,10 +1317,20 @@ mod tests { value: 5u128.into(), bit_size: BitSize::Integer(bit_size), }, + Opcode::Const { + destination: MemoryAddress(100), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(memory.len() as u32), + }, + Opcode::Const { + destination: MemoryAddress(101), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, Opcode::CalldataCopy { destination_address: MemoryAddress(5), - size: memory.len(), - offset: 0, + size_address: MemoryAddress(100), + offset_address: MemoryAddress(101), }, ]; let loop_body = [ @@ -1359,8 +1423,8 @@ mod tests { Opcode::Const { destination: r_pointer, value: 4u128.into(), bit_size }, // call recursive_fn Opcode::Call { - location: 5, // Call after 'start' - }, + location: 5, // Call after 'start' + }, // end program by jumping to end Opcode::Jump { location: 100 }, ]; @@ -1510,10 +1574,20 @@ mod tests { vec![(1u128).into(), (3u128).into(), (2u128).into(), (4u128).into()]; let invert_program = vec![ + Opcode::Const { + destination: MemoryAddress(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(initial_matrix.len() as u32), + }, + Opcode::Const { + destination: MemoryAddress(1), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, Opcode::CalldataCopy { - destination_address: MemoryAddress::from(2), - size: initial_matrix.len(), - offset: 0, + destination_address: MemoryAddress(2), + size_address: MemoryAddress(0), + offset_address: MemoryAddress(1), }, // input = 0 Opcode::Const { @@ -1600,10 +1674,20 @@ mod tests { // First call: let string_double_program = vec![ + Opcode::Const { + destination: MemoryAddress(100), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(input_string.len() as u32), + }, + Opcode::Const { + destination: MemoryAddress(101), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, Opcode::CalldataCopy { destination_address: MemoryAddress(4), - size: input_string.len(), - offset: 0, + size_address: MemoryAddress(100), + offset_address: MemoryAddress(101), }, // input_pointer = 4 Opcode::Const { @@ -1698,10 +1782,20 @@ mod tests { vec![(1u128).into(), (3u128).into(), (2u128).into(), (4u128).into()]; let invert_program = vec![ + Opcode::Const { + destination: MemoryAddress(100), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(initial_matrix.len() as u32), + }, + Opcode::Const { + destination: MemoryAddress(101), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, Opcode::CalldataCopy { - destination_address: MemoryAddress::from(2), - size: initial_matrix.len(), - offset: 0, + destination_address: MemoryAddress(2), + size_address: MemoryAddress(100), + offset_address: MemoryAddress(101), }, // input = 0 Opcode::Const { @@ -1797,10 +1891,20 @@ mod tests { vec![(34u128).into(), (37u128).into(), (78u128).into(), (85u128).into()]; let matrix_mul_program = vec![ + Opcode::Const { + destination: MemoryAddress(100), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(matrix_a.len() + matrix_b.len()), + }, + Opcode::Const { + destination: MemoryAddress(101), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, Opcode::CalldataCopy { - destination_address: MemoryAddress::from(3), - size: matrix_a.len() + matrix_b.len(), - offset: 0, + destination_address: MemoryAddress(3), + size_address: MemoryAddress(100), + offset_address: MemoryAddress(101), }, // input = 3 Opcode::Const { @@ -1944,11 +2048,24 @@ mod tests { let r_input = MemoryAddress::from(r_ptr); let r_output = MemoryAddress::from(r_ptr + 1); - let program: Vec<_> = std::iter::once(Opcode::CalldataCopy { - destination_address: MemoryAddress::from(0), - size: memory.len(), - offset: 0, - }) + let program: Vec<_> = vec![ + Opcode::Const { + destination: MemoryAddress(100), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(memory.len()), + }, + Opcode::Const { + destination: MemoryAddress(101), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, + Opcode::CalldataCopy { + destination_address: MemoryAddress(0), + size_address: MemoryAddress(100), + offset_address: MemoryAddress(101), + }, + ] + .into_iter() .chain(memory.iter().enumerate().map(|(index, mem_value)| Opcode::Cast { destination: MemoryAddress(index), source: MemoryAddress(index), diff --git a/aztec_macros/src/utils/hir_utils.rs b/aztec_macros/src/utils/hir_utils.rs index 0a8ce371708..200ce3099cb 100644 --- a/aztec_macros/src/utils/hir_utils.rs +++ b/aztec_macros/src/utils/hir_utils.rs @@ -195,7 +195,7 @@ pub fn inject_fn( let trait_id = None; items.functions.push(UnresolvedFunctions { file_id, functions, trait_id, self_type: None }); - let mut errors = Elaborator::elaborate(context, *crate_id, items, None, false); + let mut errors = Elaborator::elaborate(context, *crate_id, items, None); errors.retain(|(error, _)| !CustomDiagnostic::from(error).is_warning()); if !errors.is_empty() { @@ -241,7 +241,7 @@ pub fn inject_global( let mut items = CollectedItems::default(); items.globals.push(UnresolvedGlobal { file_id, module_id, global_id, stmt_def: global }); - let _errors = Elaborator::elaborate(context, *crate_id, items, None, false); + let _errors = Elaborator::elaborate(context, *crate_id, items, None); } pub fn fully_qualified_note_path(context: &HirContext, note_id: StructId) -> Option { diff --git a/compiler/noirc_driver/src/lib.rs b/compiler/noirc_driver/src/lib.rs index a315e7ed397..31c279bc0f3 100644 --- a/compiler/noirc_driver/src/lib.rs +++ b/compiler/noirc_driver/src/lib.rs @@ -120,10 +120,6 @@ pub struct CompileOptions { #[arg(long, hide = true)] pub show_artifact_paths: bool, - /// Temporary flag to enable the experimental arithmetic generics feature - #[arg(long, hide = true)] - pub arithmetic_generics: bool, - /// 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. @@ -289,7 +285,6 @@ pub fn check_crate( crate_id, context, options.debug_comptime_in_file.as_deref(), - options.arithmetic_generics, error_on_unused_imports, macros, ); diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs index c17088a5d8c..faf4242a9ca 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs @@ -1,5 +1,5 @@ use acvm::acir::{ - brillig::{BinaryFieldOp, BitSize, MemoryAddress, Opcode as BrilligOpcode}, + brillig::{BinaryFieldOp, BitSize, IntegerBitSize, MemoryAddress, Opcode as BrilligOpcode}, AcirField, }; @@ -19,11 +19,25 @@ pub(crate) fn directive_invert() -> GeneratedBrillig { let zero_const = MemoryAddress::from(2); let input_is_zero = MemoryAddress::from(3); // Location of the stop opcode - let stop_location = 6; + let stop_location = 8; GeneratedBrillig { byte_code: vec![ - BrilligOpcode::CalldataCopy { destination_address: input, size: 1, offset: 0 }, + BrilligOpcode::Const { + destination: MemoryAddress(20), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: F::from(1_usize), + }, + BrilligOpcode::Const { + destination: MemoryAddress::from(21), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: F::from(0_usize), + }, + BrilligOpcode::CalldataCopy { + destination_address: input, + size_address: MemoryAddress::from(20), + offset_address: MemoryAddress::from(21), + }, // Put value zero in register (2) BrilligOpcode::Const { destination: zero_const, @@ -74,10 +88,20 @@ pub(crate) fn directive_quotient() -> GeneratedBrillig { GeneratedBrillig { byte_code: vec![ + BrilligOpcode::Const { + destination: MemoryAddress::from(10), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: F::from(2_usize), + }, + BrilligOpcode::Const { + destination: MemoryAddress::from(11), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: F::from(0_usize), + }, BrilligOpcode::CalldataCopy { destination_address: MemoryAddress::from(0), - size: 2, - offset: 0, + size_address: MemoryAddress::from(10), + offset_address: MemoryAddress::from(11), }, // No cast, since calldata is typed as field by default //q = a/b is set into register (2) diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs index 5ee00cf6c29..c85940cc1c7 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs @@ -46,6 +46,9 @@ impl BrilligContext { arguments: &[BrilligParameter], return_parameters: &[BrilligParameter], ) { + // We need to allocate the variable for every argument first so any register allocation doesn't mangle the expected order. + let mut argument_variables = self.allocate_function_arguments(arguments); + let calldata_size = Self::flattened_tuple_size(arguments); let return_data_size = Self::flattened_tuple_size(return_parameters); @@ -64,75 +67,45 @@ impl BrilligContext { // Copy calldata self.copy_and_cast_calldata(arguments); - // Allocate the variables for every argument: let mut current_calldata_pointer = Self::calldata_start_offset(); - let mut argument_variables: Vec<_> = arguments - .iter() - .map(|argument| match argument { - BrilligParameter::SingleAddr(bit_size) => { - let single_address = self.allocate_register(); - let var = BrilligVariable::SingleAddr(SingleAddrVariable { - address: single_address, - bit_size: *bit_size, - }); - self.mov_instruction(single_address, MemoryAddress(current_calldata_pointer)); - current_calldata_pointer += 1; - var - } - BrilligParameter::Array(_, _) => { - let pointer_to_the_array_in_calldata = - self.make_usize_constant_instruction(current_calldata_pointer.into()); - let rc_register = self.make_usize_constant_instruction(1_usize.into()); - let flattened_size = Self::flattened_size(argument); - let var = BrilligVariable::BrilligArray(BrilligArray { - pointer: pointer_to_the_array_in_calldata.address, - size: flattened_size, - rc: rc_register.address, - }); - - current_calldata_pointer += flattened_size; - var - } - BrilligParameter::Slice(_, _) => { - let pointer_to_the_array_in_calldata = - self.make_usize_constant_instruction(current_calldata_pointer.into()); - - let flattened_size = Self::flattened_size(argument); - let size_register = self.make_usize_constant_instruction(flattened_size.into()); - let rc_register = self.make_usize_constant_instruction(1_usize.into()); - - let var = BrilligVariable::BrilligVector(BrilligVector { - pointer: pointer_to_the_array_in_calldata.address, - size: size_register.address, - rc: rc_register.address, - }); - - current_calldata_pointer += flattened_size; - var - } - }) - .collect(); - - // Deflatten arrays + // Initialize the variables with the calldata for (argument_variable, argument) in argument_variables.iter_mut().zip(arguments) { match (argument_variable, argument) { + (BrilligVariable::SingleAddr(single_address), BrilligParameter::SingleAddr(_)) => { + self.mov_instruction( + single_address.address, + MemoryAddress(current_calldata_pointer), + ); + current_calldata_pointer += 1; + } ( BrilligVariable::BrilligArray(array), BrilligParameter::Array(item_type, item_count), ) => { + let flattened_size = array.size; + self.usize_const_instruction(array.pointer, current_calldata_pointer.into()); + self.usize_const_instruction(array.rc, 1_usize.into()); + + // Deflatten the array let deflattened_address = self.deflatten_array(item_type, array.size, array.pointer); self.mov_instruction(array.pointer, deflattened_address); array.size = item_type.len() * item_count; self.deallocate_register(deflattened_address); + + current_calldata_pointer += flattened_size; } ( BrilligVariable::BrilligVector(vector), BrilligParameter::Slice(item_type, item_count), ) => { let flattened_size = Self::flattened_size(argument); + self.usize_const_instruction(vector.pointer, current_calldata_pointer.into()); + self.usize_const_instruction(vector.rc, 1_usize.into()); + self.usize_const_instruction(vector.size, flattened_size.into()); + // Deflatten the vector let deflattened_address = self.deflatten_array(item_type, flattened_size, vector.pointer); self.mov_instruction(vector.pointer, deflattened_address); @@ -140,14 +113,45 @@ impl BrilligContext { vector.size, (item_type.len() * item_count).into(), ); - self.deallocate_register(deflattened_address); + + current_calldata_pointer += flattened_size; } - _ => {} + _ => unreachable!("ICE: cannot match variables against arguments"), } } } + fn allocate_function_arguments( + &mut self, + arguments: &[BrilligParameter], + ) -> Vec { + arguments + .iter() + .map(|argument| match argument { + BrilligParameter::SingleAddr(bit_size) => { + BrilligVariable::SingleAddr(SingleAddrVariable { + address: self.allocate_register(), + bit_size: *bit_size, + }) + } + BrilligParameter::Array(_, _) => { + let flattened_size = Self::flattened_size(argument); + BrilligVariable::BrilligArray(BrilligArray { + pointer: self.allocate_register(), + size: flattened_size, + rc: self.allocate_register(), + }) + } + BrilligParameter::Slice(_, _) => BrilligVariable::BrilligVector(BrilligVector { + pointer: self.allocate_register(), + size: self.allocate_register(), + rc: self.allocate_register(), + }), + }) + .collect() + } + fn copy_and_cast_calldata(&mut self, arguments: &[BrilligParameter]) { let calldata_size = Self::flattened_tuple_size(arguments); self.calldata_copy_instruction( diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs index afea3f326b0..c728b36c193 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs @@ -481,11 +481,15 @@ impl BrilligContext< ) { self.debug_show.calldata_copy_instruction(destination, calldata_size, offset); + let size_var = self.make_usize_constant_instruction(calldata_size.into()); + let offset_var = self.make_usize_constant_instruction(offset.into()); self.push_opcode(BrilligOpcode::CalldataCopy { destination_address: destination, - size: calldata_size, - offset, + size_address: size_var.address, + offset_address: offset_var.address, }); + self.deallocate_single_addr(size_var); + self.deallocate_single_addr(offset_var); } pub(super) fn trap_instruction(&mut self, revert_data: HeapArray) { diff --git a/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs b/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs index 89445c4d195..8c128c452d0 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs @@ -181,7 +181,12 @@ impl<'f> PerFunctionContext<'f> { self.last_loads.get(store_address).is_none() }; - if remove_load && !is_reference_param { + let is_reference_alias = block + .expressions + .get(store_address) + .map_or(false, |expression| matches!(expression, Expression::Dereference(_))); + + if remove_load && !is_reference_param && !is_reference_alias { self.instructions_to_remove.insert(*store_instruction); } } @@ -286,19 +291,19 @@ impl<'f> PerFunctionContext<'f> { } else { references.mark_value_used(address, self.inserter.function); - let expression = if let Some(expression) = references.expressions.get(&result) { - expression.clone() - } else { - references.expressions.insert(result, Expression::Other(result)); - Expression::Other(result) - }; - if let Some(aliases) = references.aliases.get_mut(&expression) { + let expression = + references.expressions.entry(result).or_insert(Expression::Other(result)); + // Make sure this load result is marked an alias to itself + if let Some(aliases) = references.aliases.get_mut(expression) { + // If we have an alias set, add to the set aliases.insert(result); } else { + // Otherwise, create a new alias set containing just the load result references .aliases .insert(Expression::Other(result), AliasSet::known(result)); } + // Mark that we know a load result is equivalent to the address of a load. references.set_known_value(result, address); self.last_loads.insert(address, (instruction, block_id)); @@ -789,4 +794,98 @@ mod tests { // We expect the last eq to be optimized out assert_eq!(b1_instructions.len(), 0); } + + #[test] + fn keep_store_to_alias_in_loop_block() { + // This test makes sure the instruction `store Field 2 at v5` in b2 remains after mem2reg. + // Although the only instruction on v5 is a lone store without any loads, + // v5 is an alias of the reference v0 which is stored in v2. + // This test makes sure that we are not inadvertently removing stores to aliases across blocks. + // + // acir(inline) fn main f0 { + // b0(): + // v0 = allocate + // store Field 0 at v0 + // v2 = allocate + // store v0 at v2 + // jmp b1(Field 0) + // b1(v3: Field): + // v4 = eq v3, Field 0 + // jmpif v4 then: b2, else: b3 + // b2(): + // v5 = load v2 + // store Field 2 at v5 + // v8 = add v3, Field 1 + // jmp b1(v8) + // b3(): + // v9 = load v0 + // v10 = eq v9, Field 2 + // constrain v9 == Field 2 + // v11 = load v2 + // v12 = load v10 + // v13 = eq v12, Field 2 + // constrain v11 == Field 2 + // return + // } + let main_id = Id::test_new(0); + let mut builder = FunctionBuilder::new("main".into(), main_id); + + let v0 = builder.insert_allocate(Type::field()); + let zero = builder.numeric_constant(0u128, Type::field()); + builder.insert_store(v0, zero); + + let v2 = builder.insert_allocate(Type::field()); + // Construct alias + builder.insert_store(v2, v0); + let v2_type = builder.current_function.dfg.type_of_value(v2); + assert!(builder.current_function.dfg.value_is_reference(v2)); + + let b1 = builder.insert_block(); + builder.terminate_with_jmp(b1, vec![zero]); + + // Loop header + builder.switch_to_block(b1); + let v3 = builder.add_block_parameter(b1, Type::field()); + let is_zero = builder.insert_binary(v3, BinaryOp::Eq, zero); + + let b2 = builder.insert_block(); + let b3 = builder.insert_block(); + builder.terminate_with_jmpif(is_zero, b2, b3); + + // Loop body + builder.switch_to_block(b2); + let v5 = builder.insert_load(v2, v2_type.clone()); + let two = builder.numeric_constant(2u128, Type::field()); + builder.insert_store(v5, two); + let one = builder.numeric_constant(1u128, Type::field()); + let v3_plus_one = builder.insert_binary(v3, BinaryOp::Add, one); + builder.terminate_with_jmp(b1, vec![v3_plus_one]); + + builder.switch_to_block(b3); + let v9 = builder.insert_load(v0, Type::field()); + let _ = builder.insert_binary(v9, BinaryOp::Eq, two); + + builder.insert_constrain(v9, two, None); + let v11 = builder.insert_load(v2, v2_type); + let v12 = builder.insert_load(v11, Type::field()); + let _ = builder.insert_binary(v12, BinaryOp::Eq, two); + + builder.insert_constrain(v11, two, None); + builder.terminate_with_return(vec![]); + + let ssa = builder.finish(); + + // We expect the same result as above. + let ssa = ssa.mem2reg(); + + let main = ssa.main(); + assert_eq!(main.reachable_blocks().len(), 4); + + // The store from the original SSA should remain + assert_eq!(count_stores(main.entry_block(), &main.dfg), 2); + assert_eq!(count_stores(b2, &main.dfg), 1); + + assert_eq!(count_loads(b2, &main.dfg), 1); + assert_eq!(count_loads(b3, &main.dfg), 3); + } } diff --git a/compiler/noirc_frontend/src/ast/mod.rs b/compiler/noirc_frontend/src/ast/mod.rs index 3fd63249201..1ed88115fa0 100644 --- a/compiler/noirc_frontend/src/ast/mod.rs +++ b/compiler/noirc_frontend/src/ast/mod.rs @@ -12,6 +12,7 @@ mod traits; mod type_alias; mod visitor; +pub use visitor::AttributeTarget; pub use visitor::Visitor; pub use expression::*; diff --git a/compiler/noirc_frontend/src/ast/visitor.rs b/compiler/noirc_frontend/src/ast/visitor.rs index 0aeeed39dd0..64b479b5fd6 100644 --- a/compiler/noirc_frontend/src/ast/visitor.rs +++ b/compiler/noirc_frontend/src/ast/visitor.rs @@ -16,7 +16,7 @@ use crate::{ QuotedTypeId, }, parser::{Item, ItemKind, ParsedSubModule}, - token::{SecondaryAttribute, Tokens}, + token::{CustomAtrribute, SecondaryAttribute, Tokens}, ParsedModule, QuotedType, }; @@ -26,6 +26,13 @@ use super::{ UnresolvedTypeExpression, }; +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum AttributeTarget { + Module, + Struct, + Function, +} + /// Implements the [Visitor pattern](https://en.wikipedia.org/wiki/Visitor_pattern) for Noir's AST. /// /// In this implementation, methods must return a bool: @@ -433,7 +440,15 @@ pub trait Visitor { true } - fn visit_secondary_attribute(&mut self, _: &SecondaryAttribute, _: Span) {} + fn visit_secondary_attribute( + &mut self, + _: &SecondaryAttribute, + _target: AttributeTarget, + ) -> bool { + true + } + + fn visit_custom_attribute(&mut self, _: &CustomAtrribute, _target: AttributeTarget) {} } impl ParsedModule { @@ -484,7 +499,7 @@ impl Item { module_declaration.accept(self.span, visitor); } ItemKind::InnerAttribute(attribute) => { - attribute.accept(self.span, visitor); + attribute.accept(AttributeTarget::Module, visitor); } } } @@ -492,6 +507,10 @@ impl Item { impl ParsedSubModule { pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { + for attribute in &self.outer_attributes { + attribute.accept(AttributeTarget::Module, visitor); + } + if visitor.visit_parsed_submodule(self, span) { self.accept_children(visitor); } @@ -510,6 +529,10 @@ impl NoirFunction { } pub fn accept_children(&self, visitor: &mut impl Visitor) { + for attribute in self.secondary_attributes() { + attribute.accept(AttributeTarget::Function, visitor); + } + for param in &self.def.parameters { param.typ.accept(visitor); } @@ -674,6 +697,10 @@ impl NoirStruct { } pub fn accept_children(&self, visitor: &mut impl Visitor) { + for attribute in &self.attributes { + attribute.accept(AttributeTarget::Struct, visitor); + } + for (_name, unresolved_type) in &self.fields { unresolved_type.accept(visitor); } @@ -694,6 +721,10 @@ impl NoirTypeAlias { impl ModuleDeclaration { pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { + for attribute in &self.outer_attributes { + attribute.accept(AttributeTarget::Module, visitor); + } + visitor.visit_module_declaration(self, span); } } @@ -1295,8 +1326,22 @@ impl Pattern { } impl SecondaryAttribute { - pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { - visitor.visit_secondary_attribute(self, span); + pub fn accept(&self, target: AttributeTarget, visitor: &mut impl Visitor) { + if visitor.visit_secondary_attribute(self, target) { + self.accept_children(target, visitor); + } + } + + pub fn accept_children(&self, target: AttributeTarget, visitor: &mut impl Visitor) { + if let SecondaryAttribute::Custom(custom) = self { + custom.accept(target, visitor); + } + } +} + +impl CustomAtrribute { + pub fn accept(&self, target: AttributeTarget, visitor: &mut impl Visitor) { + visitor.visit_custom_attribute(self, target); } } diff --git a/compiler/noirc_frontend/src/elaborator/comptime.rs b/compiler/noirc_frontend/src/elaborator/comptime.rs index e9650a625e8..0cd0824b6d9 100644 --- a/compiler/noirc_frontend/src/elaborator/comptime.rs +++ b/compiler/noirc_frontend/src/elaborator/comptime.rs @@ -52,10 +52,41 @@ impl<'context> Elaborator<'context> { /// Elaborate an expression from the middle of a comptime scope. /// When this happens we require additional information to know /// what variables should be in scope. - pub fn elaborate_item_from_comptime<'a, T>( + pub fn elaborate_item_from_comptime_in_function<'a, T>( &'a mut self, current_function: Option, f: impl FnOnce(&mut Elaborator<'a>) -> T, + ) -> T { + self.elaborate_item_from_comptime(f, |elaborator| { + if let Some(function) = current_function { + let meta = elaborator.interner.function_meta(&function); + elaborator.current_item = Some(DependencyId::Function(function)); + elaborator.crate_id = meta.source_crate; + elaborator.local_module = meta.source_module; + elaborator.file = meta.source_file; + elaborator.introduce_generics_into_scope(meta.all_generics.clone()); + } + }) + } + + pub fn elaborate_item_from_comptime_in_module<'a, T>( + &'a mut self, + module: ModuleId, + file: FileId, + f: impl FnOnce(&mut Elaborator<'a>) -> T, + ) -> T { + self.elaborate_item_from_comptime(f, |elaborator| { + elaborator.current_item = None; + elaborator.crate_id = module.krate; + elaborator.local_module = module.local_id; + elaborator.file = file; + }) + } + + fn elaborate_item_from_comptime<'a, T>( + &'a mut self, + f: impl FnOnce(&mut Elaborator<'a>) -> T, + setup: impl FnOnce(&mut Elaborator<'a>), ) -> T { // Create a fresh elaborator to ensure no state is changed from // this elaborator @@ -64,21 +95,13 @@ impl<'context> Elaborator<'context> { self.def_maps, self.crate_id, self.debug_comptime_in_file, - self.enable_arithmetic_generics, self.interpreter_call_stack.clone(), ); elaborator.function_context.push(FunctionContext::default()); elaborator.scopes.start_function(); - if let Some(function) = current_function { - let meta = elaborator.interner.function_meta(&function); - elaborator.current_item = Some(DependencyId::Function(function)); - elaborator.crate_id = meta.source_crate; - elaborator.local_module = meta.source_module; - elaborator.file = meta.source_file; - elaborator.introduce_generics_into_scope(meta.all_generics.clone()); - } + setup(&mut elaborator); elaborator.populate_scope_from_comptime_scopes(); @@ -352,7 +375,7 @@ impl<'context> Elaborator<'context> { } } - fn add_item( + pub(crate) fn add_item( &mut self, item: TopLevelStatement, generated_items: &mut CollectedItems, diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 6b23336b5ea..d321d04bef9 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -168,9 +168,6 @@ pub struct Elaborator<'context> { /// they are elaborated (e.g. in a function's type or another global's RHS). unresolved_globals: BTreeMap, - /// Temporary flag to enable the experimental arithmetic generics feature - enable_arithmetic_generics: bool, - pub(crate) interpreter_call_stack: im::Vector, } @@ -194,7 +191,6 @@ impl<'context> Elaborator<'context> { def_maps: &'context mut DefMaps, crate_id: CrateId, debug_comptime_in_file: Option, - enable_arithmetic_generics: bool, interpreter_call_stack: im::Vector, ) -> Self { Self { @@ -217,7 +213,6 @@ impl<'context> Elaborator<'context> { current_trait_impl: None, debug_comptime_in_file, unresolved_globals: BTreeMap::new(), - enable_arithmetic_generics, current_trait: None, interpreter_call_stack, } @@ -227,14 +222,12 @@ impl<'context> Elaborator<'context> { context: &'context mut Context, crate_id: CrateId, debug_comptime_in_file: Option, - enable_arithmetic_generics: bool, ) -> Self { Self::new( &mut context.def_interner, &mut context.def_maps, crate_id, debug_comptime_in_file, - enable_arithmetic_generics, im::Vector::new(), ) } @@ -244,16 +237,8 @@ impl<'context> Elaborator<'context> { crate_id: CrateId, items: CollectedItems, debug_comptime_in_file: Option, - enable_arithmetic_generics: bool, ) -> Vec<(CompilationError, FileId)> { - Self::elaborate_and_return_self( - context, - crate_id, - items, - debug_comptime_in_file, - enable_arithmetic_generics, - ) - .errors + Self::elaborate_and_return_self(context, crate_id, items, debug_comptime_in_file).errors } pub fn elaborate_and_return_self( @@ -261,20 +246,14 @@ impl<'context> Elaborator<'context> { crate_id: CrateId, items: CollectedItems, debug_comptime_in_file: Option, - enable_arithmetic_generics: bool, ) -> Self { - let mut this = Self::from_context( - context, - crate_id, - debug_comptime_in_file, - enable_arithmetic_generics, - ); + let mut this = Self::from_context(context, crate_id, debug_comptime_in_file); this.elaborate_items(items); this.check_and_pop_function_context(); this } - fn elaborate_items(&mut self, mut items: CollectedItems) { + pub(crate) fn elaborate_items(&mut self, mut items: CollectedItems) { // We must first resolve and intern the globals before we can resolve any stmts inside each function. // Each function uses its own resolver with a newly created ScopeForest, and must be resolved again to be within a function's scope // diff --git a/compiler/noirc_frontend/src/elaborator/types.rs b/compiler/noirc_frontend/src/elaborator/types.rs index 8dccd5f0344..39ef4e0bb8e 100644 --- a/compiler/noirc_frontend/src/elaborator/types.rs +++ b/compiler/noirc_frontend/src/elaborator/types.rs @@ -450,7 +450,6 @@ impl<'context> Elaborator<'context> { } UnresolvedTypeExpression::Constant(int, _) => Type::Constant(int), 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); @@ -463,15 +462,7 @@ impl<'context> Elaborator<'context> { Type::Error } } - (lhs, rhs) => { - if !self.enable_arithmetic_generics { - let span = - if !matches!(lhs, Type::Constant(_)) { lhs_span } else { rhs_span }; - self.push_err(ResolverError::InvalidArrayLengthExpr { span }); - } - - Type::InfixExpr(Box::new(lhs), op, Box::new(rhs)).canonicalize() - } + (lhs, rhs) => Type::InfixExpr(Box::new(lhs), op, Box::new(rhs)).canonicalize(), } } UnresolvedTypeExpression::AsTraitPath(path) => self.resolve_as_trait_path(*path), diff --git a/compiler/noirc_frontend/src/hir/comptime/errors.rs b/compiler/noirc_frontend/src/hir/comptime/errors.rs index 5d4d814f3ee..4f419a2119d 100644 --- a/compiler/noirc_frontend/src/hir/comptime/errors.rs +++ b/compiler/noirc_frontend/src/hir/comptime/errors.rs @@ -194,7 +194,6 @@ pub enum InterpreterError { candidates: Vec, location: Location, }, - Unimplemented { item: String, location: Location, @@ -211,6 +210,16 @@ pub enum InterpreterError { attribute: String, location: Location, }, + GenericNameShouldBeAnIdent { + name: Rc, + location: Location, + }, + DuplicateGeneric { + name: Rc, + struct_name: String, + duplicate_location: Location, + existing_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. @@ -279,8 +288,10 @@ impl InterpreterError { | InterpreterError::FunctionAlreadyResolved { location, .. } | InterpreterError::MultipleMatchingImpls { location, .. } | InterpreterError::ExpectedIdentForStructField { location, .. } + | InterpreterError::InvalidAttribute { location, .. } + | InterpreterError::GenericNameShouldBeAnIdent { location, .. } + | InterpreterError::DuplicateGeneric { duplicate_location: location, .. } | InterpreterError::TypeAnnotationsNeededForMethodCall { location } => *location, - InterpreterError::InvalidAttribute { location, .. } => *location, InterpreterError::FailedToParseMacro { error, file, .. } => { Location::new(error.span(), *file) @@ -589,6 +600,32 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { let secondary = "Note that this method expects attribute contents, without the leading `#[` or trailing `]`".to_string(); CustomDiagnostic::simple_error(msg, secondary, location.span) } + InterpreterError::GenericNameShouldBeAnIdent { name, location } => { + let msg = + "Generic name needs to be a valid identifier (one word beginning with a letter)" + .to_string(); + let secondary = format!("`{name}` is not a valid identifier"); + CustomDiagnostic::simple_error(msg, secondary, location.span) + } + InterpreterError::DuplicateGeneric { + name, + struct_name, + duplicate_location, + existing_location, + } => { + let msg = format!("`{struct_name}` already has a generic named `{name}`"); + let secondary = format!("`{name}` added here a second time"); + let mut error = + CustomDiagnostic::simple_error(msg, secondary, duplicate_location.span); + + let existing_msg = format!("`{name}` was previously defined here"); + error.add_secondary_with_file( + existing_msg, + existing_location.span, + existing_location.file, + ); + error + } } } } diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs index 9f559b7c5e6..5f58c18d66e 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs @@ -2,6 +2,7 @@ use std::collections::VecDeque; use std::{collections::hash_map::Entry, rc::Rc}; use acvm::{acir::AcirField, FieldElement}; +use fm::FileId; use im::Vector; use iter_extended::try_vecmap; use noirc_errors::Location; @@ -10,6 +11,7 @@ use rustc_hash::FxHashMap as HashMap; use crate::ast::{BinaryOpKind, FunctionKind, IntegerBitSize, Signedness}; use crate::elaborator::Elaborator; use crate::graph::CrateId; +use crate::hir::def_map::ModuleId; use crate::hir_def::expr::ImplKind; use crate::hir_def::function::FunctionBody; use crate::macros_api::UnaryOp; @@ -170,7 +172,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { Some(body) => Ok(body), None => { if matches!(&meta.function_body, FunctionBody::Unresolved(..)) { - self.elaborate_item(None, |elaborator| { + self.elaborate_in_function(None, |elaborator| { elaborator.elaborate_function(function); }); @@ -183,13 +185,25 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { } } - fn elaborate_item( + fn elaborate_in_function( &mut self, function: Option, f: impl FnOnce(&mut Elaborator) -> T, ) -> T { self.unbind_generics_from_previous_function(); - let result = self.elaborator.elaborate_item_from_comptime(function, f); + let result = self.elaborator.elaborate_item_from_comptime_in_function(function, f); + self.rebind_generics_from_previous_function(); + result + } + + fn elaborate_in_module( + &mut self, + module: ModuleId, + file: FileId, + f: impl FnOnce(&mut Elaborator) -> T, + ) -> T { + self.unbind_generics_from_previous_function(); + let result = self.elaborator.elaborate_item_from_comptime_in_module(module, file, f); self.rebind_generics_from_previous_function(); result } @@ -1244,7 +1258,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { let mut result = self.call_function(function_id, arguments, bindings, location)?; if call.is_macro_call { let expr = result.into_expression(self.elaborator.interner, location)?; - let expr = self.elaborate_item(self.current_function, |elaborator| { + let expr = self.elaborate_in_function(self.current_function, |elaborator| { elaborator.elaborate_expression(expr).0 }); result = self.evaluate(expr)?; diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index 072ac86b974..c3aeac4aec4 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -25,10 +25,14 @@ use crate::{ FunctionReturnType, IntegerBitSize, LValue, Literal, Statement, StatementKind, UnaryOp, UnresolvedType, UnresolvedTypeData, Visibility, }, - hir::comptime::{ - errors::IResult, - value::{ExprValue, TypedExpr}, - InterpreterError, Value, + hir::def_collector::dc_crate::CollectedItems, + hir::{ + comptime::{ + errors::IResult, + value::{ExprValue, TypedExpr}, + InterpreterError, Value, + }, + def_map::ModuleId, }, hir_def::function::FunctionBody, lexer::Lexer, @@ -36,7 +40,7 @@ use crate::{ node_interner::{DefinitionKind, TraitImplKind}, parser::{self}, token::{Attribute, SecondaryAttribute, Token}, - QuotedType, Shared, Type, + Kind, QuotedType, ResolvedGeneric, Shared, Type, TypeVariable, }; use self::builtin_helpers::{get_array, get_str, get_u8}; @@ -96,11 +100,16 @@ impl<'local, 'context> Interpreter<'local, 'context> { "expr_resolve" => expr_resolve(self, arguments, location), "is_unconstrained" => Ok(Value::Bool(true)), "fmtstr_quoted_contents" => fmtstr_quoted_contents(interner, arguments, location), + "fresh_type_variable" => fresh_type_variable(interner), "function_def_add_attribute" => function_def_add_attribute(self, arguments, location), "function_def_body" => function_def_body(interner, arguments, location), "function_def_has_named_attribute" => { function_def_has_named_attribute(interner, arguments, location) } + "function_def_is_unconstrained" => { + function_def_is_unconstrained(interner, arguments, location) + } + "function_def_module" => function_def_module(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), @@ -112,6 +121,10 @@ impl<'local, 'context> Interpreter<'local, 'context> { "function_def_set_return_public" => { function_def_set_return_public(self, arguments, location) } + "function_def_set_unconstrained" => { + function_def_set_unconstrained(self, arguments, location) + } + "module_add_item" => module_add_item(self, arguments, location), "module_functions" => module_functions(self, arguments, location), "module_has_named_attribute" => module_has_named_attribute(self, arguments, location), "module_is_contract" => module_is_contract(self, arguments, location), @@ -134,13 +147,16 @@ impl<'local, 'context> Interpreter<'local, 'context> { "slice_push_front" => slice_push_front(interner, arguments, location), "slice_remove" => slice_remove(interner, arguments, location, call_stack), "str_as_bytes" => str_as_bytes(interner, arguments, location), - "struct_def_add_attribute" => struct_def_add_attribute(self, arguments, location), + "struct_def_add_attribute" => struct_def_add_attribute(interner, arguments, location), + "struct_def_add_generic" => struct_def_add_generic(interner, arguments, location), "struct_def_as_type" => struct_def_as_type(interner, arguments, location), "struct_def_fields" => struct_def_fields(interner, arguments, location), "struct_def_generics" => struct_def_generics(interner, arguments, location), "struct_def_has_named_attribute" => { struct_def_has_named_attribute(interner, arguments, location) } + "struct_def_module" => struct_def_module(self, arguments, location), + "struct_def_name" => struct_def_name(interner, arguments, location), "struct_def_set_fields" => struct_def_set_fields(interner, arguments, location), "to_le_radix" => to_le_radix(arguments, return_type, location), "trait_constraint_eq" => trait_constraint_eq(interner, arguments, location), @@ -273,13 +289,13 @@ fn str_as_bytes( // fn add_attribute(self, attribute: str) fn struct_def_add_attribute( - interpreter: &mut Interpreter, + interner: &mut NodeInterner, arguments: Vec<(Value, Location)>, location: Location, ) -> IResult { let (self_argument, attribute) = check_two_arguments(arguments, location)?; let attribute_location = attribute.1; - let attribute = get_str(interpreter.elaborator.interner, attribute)?; + let attribute = get_str(interner, attribute)?; let mut tokens = Lexer::lex(&format!("#[{}]", attribute)).0 .0; if let Some(Token::EOF) = tokens.last().map(|token| token.token()) { @@ -308,13 +324,68 @@ fn struct_def_add_attribute( }; let struct_id = get_struct(self_argument)?; - interpreter.elaborator.interner.update_struct_attributes(struct_id, |attributes| { + interner.update_struct_attributes(struct_id, |attributes| { attributes.push(attribute.clone()); }); Ok(Value::Unit) } +// fn add_generic(self, generic_name: str) +fn struct_def_add_generic( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let (self_argument, generic) = check_two_arguments(arguments, location)?; + let generic_location = generic.1; + let generic = get_str(interner, generic)?; + + let mut tokens = Lexer::lex(&generic).0 .0; + if let Some(Token::EOF) = tokens.last().map(|token| token.token()) { + tokens.pop(); + } + + if tokens.len() != 1 { + return Err(InterpreterError::GenericNameShouldBeAnIdent { + name: generic, + location: generic_location, + }); + } + + let Token::Ident(generic_name) = tokens.pop().unwrap().into_token() else { + return Err(InterpreterError::GenericNameShouldBeAnIdent { + name: generic, + location: generic_location, + }); + }; + + let struct_id = get_struct(self_argument)?; + let the_struct = interner.get_struct(struct_id); + let mut the_struct = the_struct.borrow_mut(); + let name = Rc::new(generic_name); + + for generic in &the_struct.generics { + if generic.name == name { + return Err(InterpreterError::DuplicateGeneric { + name, + struct_name: the_struct.name.to_string(), + existing_location: Location::new(generic.span, the_struct.location.file), + duplicate_location: generic_location, + }); + } + } + + let type_var = TypeVariable::unbound(interner.next_type_variable_id()); + let span = generic_location.span; + let kind = Kind::Normal; + let typ = Type::NamedGeneric(type_var.clone(), name.clone(), kind.clone()); + let new_generic = ResolvedGeneric { name, type_var, span, kind }; + the_struct.generics.push(new_generic); + + Ok(Value::Type(typ)) +} + /// fn as_type(self) -> Type fn struct_def_as_type( interner: &NodeInterner, @@ -398,6 +469,39 @@ fn struct_def_fields( Ok(Value::Slice(fields, typ)) } +// fn module(self) -> Module +fn struct_def_module( + interpreter: &Interpreter, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let self_argument = check_one_argument(arguments, location)?; + let struct_id = get_struct(self_argument)?; + let struct_module_id = struct_id.module_id(); + + // A struct's module is its own module. To get the module where its defined we need + // to look for its parent. + let module_data = interpreter.elaborator.get_module(struct_module_id); + let parent_local_id = module_data.parent.expect("Expected struct module parent to exist"); + let parent = ModuleId { krate: struct_module_id.krate, local_id: parent_local_id }; + + Ok(Value::ModuleDefinition(parent)) +} + +// fn name(self) -> Quoted +fn struct_def_name( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let self_argument = check_one_argument(arguments, location)?; + let struct_id = get_struct(self_argument)?; + let the_struct = interner.get_struct(struct_id); + + let name = Token::Ident(the_struct.borrow().name.to_string()); + Ok(Value::Quoted(Rc::new(vec![name]))) +} + /// fn set_fields(self, new_fields: [(Quoted, Type)]) {} /// Returns (name, type) pairs of each field of this StructDefinition fn struct_def_set_fields( @@ -567,9 +671,10 @@ fn quoted_as_module( let path = parse(argument, parser::path_no_turbofish(), "a path").ok(); let option_value = path.and_then(|path| { - let module = interpreter.elaborate_item(interpreter.current_function, |elaborator| { - elaborator.resolve_module_by_path(path) - }); + let module = interpreter + .elaborate_in_function(interpreter.current_function, |elaborator| { + elaborator.resolve_module_by_path(path) + }); module.map(Value::ModuleDefinition) }); @@ -585,7 +690,7 @@ fn quoted_as_trait_constraint( let argument = check_one_argument(arguments, location)?; let trait_bound = parse(argument, parser::trait_bound(), "a trait constraint")?; let bound = interpreter - .elaborate_item(interpreter.current_function, |elaborator| { + .elaborate_in_function(interpreter.current_function, |elaborator| { elaborator.resolve_trait_bound(&trait_bound, Type::Unit) }) .ok_or(InterpreterError::FailedToResolveTraitBound { trait_bound, location })?; @@ -601,8 +706,8 @@ fn quoted_as_type( ) -> IResult { let argument = check_one_argument(arguments, location)?; let typ = parse(argument, parser::parse_type(), "a type")?; - let typ = - interpreter.elaborate_item(interpreter.current_function, |elab| elab.resolve_type(typ)); + let typ = interpreter + .elaborate_in_function(interpreter.current_function, |elab| elab.resolve_type(typ)); Ok(Value::Type(typ)) } @@ -680,11 +785,10 @@ fn type_as_constant( location: Location, ) -> IResult { type_as(arguments, return_type, location, |typ| { - if let Type::Constant(n) = typ { - Some(Value::U32(n)) - } else { - None - } + // Prefer to use `evaluate_to_u32` over matching on `Type::Constant` + // since arithmetic generics may be `Type::InfixExpr`s which evaluate to + // constants but are not actually the `Type::Constant` variant. + typ.evaluate_to_u32().map(Value::U32) }) } @@ -786,7 +890,7 @@ where F: FnOnce(Type) -> Option, { let value = check_one_argument(arguments, location)?; - let typ = get_type(value)?; + let typ = get_type(value)?.follow_bindings(); let option_value = f(typ); @@ -815,13 +919,13 @@ 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( + let option_value = match interner.lookup_trait_implementation( &typ, trait_id, &generics.ordered, &generics.named, ) { - Ok((TraitImplKind::Normal(trait_impl_id), _)) => Some(Value::TraitImpl(trait_impl_id)), + Ok(TraitImplKind::Normal(trait_impl_id)) => Some(Value::TraitImpl(trait_impl_id)), _ => None, }; @@ -840,7 +944,7 @@ fn type_implements( let (trait_id, generics) = get_trait_constraint(constraint)?; let implements = interner - .try_lookup_trait_implementation(&typ, trait_id, &generics.ordered, &generics.named) + .lookup_trait_implementation(&typ, trait_id, &generics.ordered, &generics.named) .is_ok(); Ok(Value::Bool(implements)) } @@ -1673,23 +1777,25 @@ fn expr_resolve( 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)) - } - }); + let value = + interpreter.elaborate_in_function(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) } @@ -1736,6 +1842,11 @@ fn fmtstr_quoted_contents( Ok(Value::Quoted(Rc::new(tokens))) } +// fn fresh_type_variable() -> Type +fn fresh_type_variable(interner: &NodeInterner) -> IResult { + Ok(Value::Type(interner.next_type_variable())) +} + // fn add_attribute(self, attribute: str) fn function_def_add_attribute( interpreter: &mut Interpreter, @@ -1822,6 +1933,30 @@ fn function_def_has_named_attribute( Ok(Value::Bool(has_named_attribute(&name, attributes, location))) } +// fn is_unconstrained(self) -> bool +fn function_def_is_unconstrained( + 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 is_unconstrained = interner.function_modifiers(&func_id).is_unconstrained; + Ok(Value::Bool(is_unconstrained)) +} + +// fn module(self) -> Module +fn function_def_module( + 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 module = interner.function_module(func_id); + Ok(Value::ModuleDefinition(module)) +} + // fn name(self) -> Quoted fn function_def_name( interner: &NodeInterner, @@ -1940,7 +2075,7 @@ fn function_def_set_parameters( "a pattern", )?; - let hir_pattern = interpreter.elaborate_item(Some(func_id), |elaborator| { + let hir_pattern = interpreter.elaborate_in_function(Some(func_id), |elaborator| { elaborator.elaborate_pattern_and_store_ids( parameter_pattern, parameter_type.clone(), @@ -2007,6 +2142,53 @@ fn function_def_set_return_public( Ok(Value::Unit) } +// fn set_unconstrained(self, value: bool) +fn function_def_set_unconstrained( + interpreter: &mut Interpreter, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let (self_argument, unconstrained) = check_two_arguments(arguments, location)?; + + let func_id = get_function_def(self_argument)?; + check_function_not_yet_resolved(interpreter, func_id, location)?; + + let unconstrained = get_bool(unconstrained)?; + + let modifiers = interpreter.elaborator.interner.function_modifiers_mut(&func_id); + modifiers.is_unconstrained = unconstrained; + + Ok(Value::Unit) +} + +// fn add_item(self, item: Quoted) +fn module_add_item( + interpreter: &mut Interpreter, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let (self_argument, item) = check_two_arguments(arguments, location)?; + let module_id = get_module(self_argument)?; + let module_data = interpreter.elaborator.get_module(module_id); + + let parser = parser::top_level_items(); + let top_level_statements = parse(item, parser, "a top-level item")?; + + interpreter.elaborate_in_module(module_id, module_data.location.file, |elaborator| { + let mut generated_items = CollectedItems::default(); + + for top_level_statement in top_level_statements { + elaborator.add_item(top_level_statement, &mut generated_items, location); + } + + if !generated_items.is_empty() { + elaborator.elaborate_items(generated_items); + } + }); + + Ok(Value::Unit) +} + // fn functions(self) -> [FunctionDefinition] fn module_functions( interpreter: &Interpreter, diff --git a/compiler/noirc_frontend/src/hir/comptime/tests.rs b/compiler/noirc_frontend/src/hir/comptime/tests.rs index 64b489422a0..a47dbeace50 100644 --- a/compiler/noirc_frontend/src/hir/comptime/tests.rs +++ b/compiler/noirc_frontend/src/hir/comptime/tests.rs @@ -51,7 +51,7 @@ fn interpret_helper(src: &str) -> Result { let main = context.get_main_function(&krate).expect("Expected 'main' function"); let mut elaborator = - Elaborator::elaborate_and_return_self(&mut context, krate, collector.items, None, false); + Elaborator::elaborate_and_return_self(&mut context, krate, collector.items, None); assert_eq!(elaborator.errors.len(), 0); let mut interpreter = elaborator.setup_interpreter(); diff --git a/compiler/noirc_frontend/src/hir/comptime/value.rs b/compiler/noirc_frontend/src/hir/comptime/value.rs index 7d6e4475c7b..04c557552bd 100644 --- a/compiler/noirc_frontend/src/hir/comptime/value.rs +++ b/compiler/noirc_frontend/src/hir/comptime/value.rs @@ -653,9 +653,15 @@ impl<'value, 'interner> Display for ValuePrinter<'value, 'interner> { Value::FunctionDefinition(function_id) => { write!(f, "{}", self.interner.function_name(function_id)) } - Value::ModuleDefinition(_) => write!(f, "(module)"), + Value::ModuleDefinition(module_id) => { + if let Some(attributes) = self.interner.try_module_attributes(module_id) { + write!(f, "{}", &attributes.name) + } else { + write!(f, "(crate root)") + } + } Value::Zeroed(typ) => write!(f, "(zeroed {typ})"), - Value::Type(typ) => write!(f, "{}", typ), + Value::Type(typ) => write!(f, "{:?}", typ), Value::Expr(ExprValue::Expression(expr)) => { write!(f, "{}", remove_interned_in_expression_kind(self.interner, expr.clone())) } diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index 3cfa0989d7d..7ee1840690a 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -271,7 +271,6 @@ impl DefCollector { ast: SortedModule, root_file_id: FileId, debug_comptime_in_file: Option<&str>, - enable_arithmetic_generics: bool, error_on_unused_items: bool, macro_processors: &[&dyn MacroProcessor], ) -> Vec<(CompilationError, FileId)> { @@ -291,7 +290,6 @@ impl DefCollector { dep.crate_id, context, debug_comptime_in_file, - enable_arithmetic_generics, error_on_usage_tracker, macro_processors, )); @@ -471,13 +469,8 @@ impl DefCollector { }) }); - let mut more_errors = Elaborator::elaborate( - context, - crate_id, - def_collector.items, - debug_comptime_in_file, - enable_arithmetic_generics, - ); + let mut more_errors = + Elaborator::elaborate(context, crate_id, def_collector.items, debug_comptime_in_file); errors.append(&mut more_errors); diff --git a/compiler/noirc_frontend/src/hir/def_map/mod.rs b/compiler/noirc_frontend/src/hir/def_map/mod.rs index a1c4d04cb30..75b860bf2c6 100644 --- a/compiler/noirc_frontend/src/hir/def_map/mod.rs +++ b/compiler/noirc_frontend/src/hir/def_map/mod.rs @@ -76,7 +76,6 @@ impl CrateDefMap { crate_id: CrateId, 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)> { @@ -133,7 +132,6 @@ impl CrateDefMap { ast, root_file_id, debug_comptime_in_file, - enable_arithmetic_generics, error_on_unused_imports, macro_processors, )); diff --git a/compiler/noirc_frontend/src/parser/mod.rs b/compiler/noirc_frontend/src/parser/mod.rs index 2995e90ab01..66be0fdced5 100644 --- a/compiler/noirc_frontend/src/parser/mod.rs +++ b/compiler/noirc_frontend/src/parser/mod.rs @@ -26,8 +26,8 @@ use noirc_errors::Span; pub use parser::path::path_no_turbofish; pub use parser::traits::trait_bound; pub use parser::{ - block, expression, fresh_statement, lvalue, parse_program, parse_type, pattern, - top_level_items, visibility, + block, expression, fresh_statement, lvalue, module, parse_program, parse_type, pattern, + top_level_items, top_level_statement, visibility, }; #[derive(Debug, Clone)] diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index 1aee697aa88..48d25e7a1d8 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -175,7 +175,7 @@ fn program() -> impl NoirParser { /// module: top_level_statement module /// | %empty -fn module() -> impl NoirParser { +pub fn module() -> impl NoirParser { recursive(|module_parser| { empty() .to(ParsedModule::default()) @@ -202,7 +202,7 @@ pub fn top_level_items() -> impl NoirParser> { /// | module_declaration /// | use_statement /// | global_declaration -fn top_level_statement<'a>( +pub fn top_level_statement<'a>( module_parser: impl NoirParser + 'a, ) -> impl NoirParser + 'a { choice(( diff --git a/compiler/noirc_frontend/src/tests.rs b/compiler/noirc_frontend/src/tests.rs index cd075d2c374..3e01c370154 100644 --- a/compiler/noirc_frontend/src/tests.rs +++ b/compiler/noirc_frontend/src/tests.rs @@ -96,7 +96,6 @@ pub(crate) fn get_program(src: &str) -> (ParsedModule, Context, Vec<(Compilation }; let debug_comptime_in_file = None; - let enable_arithmetic_generics = false; let error_on_unused_imports = true; let macro_processors = &[]; @@ -107,7 +106,6 @@ pub(crate) fn get_program(src: &str) -> (ParsedModule, Context, Vec<(Compilation program.clone().into_sorted(), root_file_id, debug_comptime_in_file, - enable_arithmetic_generics, error_on_unused_imports, macro_processors, )); diff --git a/docs/docs/noir/concepts/generics.md b/docs/docs/noir/concepts/generics.md index 3e416eee093..f05540f9f55 100644 --- a/docs/docs/noir/concepts/generics.md +++ b/docs/docs/noir/concepts/generics.md @@ -45,17 +45,20 @@ fn main() { The `print` function will print `Hello!` an arbitrary number of times, twice in this case. +## Numeric Generics + If we want to be generic over array lengths (which are type-level integers), we can use numeric -generics. Using these looks just like using regular generics, but these generics can resolve to -integers at compile-time, rather than resolving to types. Here's an example of a struct that is -generic over the size of the array it contains internally: +generics. Using these looks similar to using regular generics, but introducing them into scope +requires declaring them with `let MyGenericName: IntegerType`. This can be done anywhere a normal +generic is declared. Instead of types, these generics resolve to integers at compile-time. +Here's an example of a struct that is generic over the size of the array it contains internally: ```rust -struct BigInt { +struct BigInt { limbs: [u32; N], } -impl BigInt { +impl BigInt { // `N` is in scope of all methods in the impl fn first(first: BigInt, second: BigInt) -> Self { assert(first.limbs != second.limbs); @@ -77,7 +80,7 @@ This is what [traits](../concepts/traits.md) are for in Noir. Here's an example any type `T` that implements the `Eq` trait for equality: ```rust -fn first_element_is_equal(array1: [T; N], array2: [T; N]) -> bool +fn first_element_is_equal(array1: [T; N], array2: [T; N]) -> bool where T: Eq { if (array1.len() == 0) | (array2.len() == 0) { @@ -161,3 +164,47 @@ fn example() { assert(10 as u32 == foo.generic_method::()); } ``` + +## Arithmetic Generics + +In addition to numeric generics, Noir also allows a limited form of arithmetic on generics. +When you have a numeric generic such as `N`, you can use the following operators on it in a +type position: `+`, `-`, `*`, `/`, and `%`. + +Note that type checking arithmetic generics is a best effort guess from the compiler and there +are many cases of types that are equal that the compiler may not see as such. For example, +we know that `T * (N + M)` should be equal to `T*N + T*M` but the compiler does not currently +apply the distributive law and thus sees these as different types. + +Even with this limitation though, the compiler can handle common cases decently well: + +```rust +trait Serialize { + fn serialize(self) -> [Field; N]; +} + +impl Serialize<1> for Field { + fn serialize(self) -> [Field; 1] { + [self] + } +} + +impl Serialize for [T; N] + where T: Serialize { .. } + +impl Serialize for (T, U) + where T: Serialize, U: Serialize { .. } + +fn main() { + let data = (1, [2, 3, 4]); + assert(data.serialize().len(), 4); +} +``` + +Note that if there is any over or underflow the types will fail to unify: + +#include_code underflow-example test_programs/compile_failure/arithmetic_generics_underflow/src/main.nr rust + +This also applies if there is underflow in an intermediate calculation: + +#include_code intermediate-underflow-example test_programs/compile_failure/arithmetic_generics_intermediate_underflow/src/main.nr rust diff --git a/docs/docs/noir/standard_library/meta/function_def.md b/docs/docs/noir/standard_library/meta/function_def.md index 7c7531fb24a..63dd1fad2e5 100644 --- a/docs/docs/noir/standard_library/meta/function_def.md +++ b/docs/docs/noir/standard_library/meta/function_def.md @@ -11,7 +11,7 @@ a function definition in the source program. #include_code add_attribute noir_stdlib/src/meta/function_def.nr rust -Adds an attribute to the function. This is only valid +Adds an attribute to the function. 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. @@ -19,7 +19,7 @@ This means any functions called at compile-time are invalid targets for this met #include_code body noir_stdlib/src/meta/function_def.nr rust -Returns the body of the function as an expression. This is only valid +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. @@ -29,6 +29,18 @@ This means any functions called at compile-time are invalid targets for this met Returns true if this function has a custom attribute with the given name. +### is_unconstrained + +#include_code is_unconstrained noir_stdlib/src/meta/function_def.nr rust + +Returns true if this function is unconstrained. + +### module + +#include_code module noir_stdlib/src/meta/function_def.nr rust + +Returns the module where the function is defined. + ### name #include_code name noir_stdlib/src/meta/function_def.nr rust @@ -78,6 +90,14 @@ This means any functions called at compile-time are invalid targets for this met #include_code set_return_public noir_stdlib/src/meta/function_def.nr rust -Mutates the function's return visibility to public (if `true` is given) or private (if `false` is given). -This is only valid on functions in the current crate which have not yet been resolved. +Mutates the function's return visibility to public (if `true` is given) or private (if `false` is given). +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_unconstrained + +#include_code set_unconstrained noir_stdlib/src/meta/function_def.nr rust + +Mutates the function to be unconstrained (if `true` is given) or not (if `false` is given). +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. \ No newline at end of file diff --git a/docs/docs/noir/standard_library/meta/module.md b/docs/docs/noir/standard_library/meta/module.md index 870e366461c..de042760d51 100644 --- a/docs/docs/noir/standard_library/meta/module.md +++ b/docs/docs/noir/standard_library/meta/module.md @@ -8,6 +8,14 @@ declarations in the source program. ## Methods +### add_item + +#include_code add_item noir_stdlib/src/meta/module.nr rust + +Adds a top-level item (a function, a struct, a global, etc.) to the module. +Adding multiple items in one go is also valid if the `Quoted` value has multiple items in it. +Note that the items are type-checked as if they are inside the module they are being added to. + ### name #include_code name noir_stdlib/src/meta/module.nr rust diff --git a/docs/docs/noir/standard_library/meta/struct_def.md b/docs/docs/noir/standard_library/meta/struct_def.md index 5da4a458d88..c088e538fc9 100644 --- a/docs/docs/noir/standard_library/meta/struct_def.md +++ b/docs/docs/noir/standard_library/meta/struct_def.md @@ -13,6 +13,24 @@ This type corresponds to `struct Name { field1: Type1, ... }` items in the sourc Adds an attribute to the struct. +### add_generic + +#include_code add_generic noir_stdlib/src/meta/struct_def.nr rust + +Adds an generic to the struct. Returns the new generic type. +Errors if the given generic name isn't a single identifier or if +the struct already has a generic with the same name. + +This method should be used carefully, if there is existing code referring +to the struct type it may be checked before this function is called and +see the struct with the original number of generics. This method should +thus be preferred to use on code generated from other macros and structs +that are not used in function signatures. + +Example: + +#include_code add-generic-example test_programs/compile_success_empty/comptime_struct_definition/src/main.nr rust + ### as_type #include_code as_type noir_stdlib/src/meta/struct_def.nr rust @@ -56,6 +74,21 @@ Returns each field of this struct as a pair of (field name, field type). Returns true if this struct has a custom attribute with the given name. +### module + +#include_code module noir_stdlib/src/meta/struct_def.nr rust + +Returns the module where the struct is defined. + +### name + +#include_code name noir_stdlib/src/meta/struct_def.nr rust + +Returns the name of this struct + +Note that the returned quoted value will be just the struct name, it will +not be the full path to the struct, nor will it include any generics. + ### set_fields #include_code set_fields noir_stdlib/src/meta/struct_def.nr rust diff --git a/docs/docs/noir/standard_library/meta/typ.md b/docs/docs/noir/standard_library/meta/typ.md index 0b6f8d5f77d..1334092a9fa 100644 --- a/docs/docs/noir/standard_library/meta/typ.md +++ b/docs/docs/noir/standard_library/meta/typ.md @@ -5,6 +5,30 @@ title: Type `std::meta::typ` contains methods on the built-in `Type` type used for representing a type in the source program. +## Functions + +#include_code fresh_type_variable noir_stdlib/src/meta/typ.nr rust + +Creates and returns an unbound type variable. This is a special kind of type internal +to type checking which will type check with any other type. When it is type checked +against another type it will also be set to that type. For example, if `a` is a type +variable and we have the type equality `(a, i32) = (u8, i32)`, the compiler will set +`a` equal to `u8`. + +Unbound type variables will often be rendered as `_` while printing them. Bound type +variables will appear as the type they are bound to. + +This can be used in conjunction with functions which internally perform type checks +such as `Type::implements` or `Type::get_trait_impl` to potentially grab some of the types used. + +Note that calling `Type::implements` or `Type::get_trait_impl` on a type variable will always +fail. + +Example: + +#include_code serialize-setup test_programs/compile_success_empty/comptime_type/src/main.nr rust +#include_code fresh-type-variable-example test_programs/compile_success_empty/comptime_type/src/main.nr rust + ## Methods ### as_array diff --git a/noir_stdlib/src/cmp.nr b/noir_stdlib/src/cmp.nr index b7f473429a7..521604a4e20 100644 --- a/noir_stdlib/src/cmp.nr +++ b/noir_stdlib/src/cmp.nr @@ -11,7 +11,13 @@ trait 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; + let body = |fields| { + if s.fields().len() == 0 { + quote { true } + } else { + fields + } + }; crate::meta::make_trait_impl(s, quote { Eq }, signature, for_each_field, quote { & }, body) } // docs:end:derive_eq diff --git a/noir_stdlib/src/meta/function_def.nr b/noir_stdlib/src/meta/function_def.nr index 0bff86ef102..5ce3dbeabff 100644 --- a/noir_stdlib/src/meta/function_def.nr +++ b/noir_stdlib/src/meta/function_def.nr @@ -14,6 +14,16 @@ impl FunctionDefinition { fn has_named_attribute(self, name: Quoted) -> bool {} // docs:end:has_named_attribute + #[builtin(function_def_is_unconstrained)] + // docs:start:is_unconstrained + fn is_unconstrained(self) -> bool {} + // docs:end:is_unconstrained + + #[builtin(function_def_module)] + // docs:start:module + fn module(self) -> Module {} + // docs:end:module + #[builtin(function_def_name)] // docs:start:name fn name(self) -> Quoted {} @@ -48,4 +58,9 @@ impl FunctionDefinition { // docs:start:set_return_public fn set_return_public(self, public: bool) {} // docs:end:set_return_public + + #[builtin(function_def_set_unconstrained)] + // docs:start:set_unconstrained + fn set_unconstrained(self, value: bool) {} + // docs:end:set_unconstrained } diff --git a/noir_stdlib/src/meta/module.nr b/noir_stdlib/src/meta/module.nr index b3f76812b8a..bee6612e1bf 100644 --- a/noir_stdlib/src/meta/module.nr +++ b/noir_stdlib/src/meta/module.nr @@ -1,4 +1,9 @@ impl Module { + #[builtin(module_add_item)] + // docs:start:add_item + fn add_item(self, item: Quoted) {} + // docs:end:add_item + #[builtin(module_has_named_attribute)] // docs:start:has_named_attribute fn has_named_attribute(self, name: Quoted) -> bool {} diff --git a/noir_stdlib/src/meta/struct_def.nr b/noir_stdlib/src/meta/struct_def.nr index 6c0270a8eec..5db720b91d3 100644 --- a/noir_stdlib/src/meta/struct_def.nr +++ b/noir_stdlib/src/meta/struct_def.nr @@ -4,10 +4,15 @@ impl StructDefinition { fn add_attribute(self, attribute: str) {} // docs:end:add_attribute + #[builtin(struct_def_add_generic)] + // docs:start:add_generic + fn add_generic(self, generic_name: str) -> Type {} + // docs:end:add_generic + /// 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 + // docs:start:as_type fn as_type(self) -> Type {} // docs:end:as_type @@ -18,17 +23,27 @@ impl StructDefinition { /// Return each generic on this struct. #[builtin(struct_def_generics)] -// docs:start: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 + // docs:start:fields fn fields(self) -> [(Quoted, Type)] {} // docs:end:fields + #[builtin(struct_def_module)] + // docs:start:module + fn module(self) -> Module {} + // docs:end:module + + #[builtin(struct_def_name)] + // docs:start:name + fn name(self) -> Quoted {} + // docs:end:name + /// Sets the fields of this struct to the given fields list. /// All existing fields of the struct will be overridden with the given fields. /// Each element of the fields list corresponds to the name and type of a field. diff --git a/noir_stdlib/src/meta/typ.nr b/noir_stdlib/src/meta/typ.nr index 12dc91a4925..71bd6fd7f1c 100644 --- a/noir_stdlib/src/meta/typ.nr +++ b/noir_stdlib/src/meta/typ.nr @@ -1,6 +1,11 @@ use crate::cmp::Eq; use crate::option::Option; +#[builtin(fresh_type_variable)] +// docs:start:fresh_type_variable +pub fn fresh_type_variable() -> Type {} +// docs:end:fresh_type_variable + impl Type { #[builtin(type_as_array)] // docs:start:as_array diff --git a/test_programs/compile_failure/arithmetic_generics_intermediate_underflow/Nargo.toml b/test_programs/compile_failure/arithmetic_generics_intermediate_underflow/Nargo.toml new file mode 100644 index 00000000000..0c5d98628a1 --- /dev/null +++ b/test_programs/compile_failure/arithmetic_generics_intermediate_underflow/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "arithmetic_generics_intermediate_underflow" +type = "bin" +authors = [""] +compiler_version = ">=0.33.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/arithmetic_generics_intermediate_underflow/src/main.nr b/test_programs/compile_failure/arithmetic_generics_intermediate_underflow/src/main.nr new file mode 100644 index 00000000000..58cf2f648e5 --- /dev/null +++ b/test_programs/compile_failure/arithmetic_generics_intermediate_underflow/src/main.nr @@ -0,0 +1,32 @@ +// docs:start:intermediate-underflow-example +fn main() { + // From main it looks like there's nothing sketchy going on + seems_fine([]); +} + +// Since `seems_fine` says it can receive and return any length N +fn seems_fine(array: [Field; N]) -> [Field; N] { + // But inside `seems_fine` we pop from the array which + // requires the length to be greater than zero. + + // error: Could not determine array length `(0 - 1)` + push_zero(pop(array)) +} + +fn pop(array: [Field; N]) -> [Field; N - 1] { + let mut result: [Field; N - 1] = std::mem::zeroed(); + for i in 0..N { + result[i] = array[i]; + } + result +} + +fn push_zero(array: [Field; N]) -> [Field; N + 1] { + let mut result: [Field; N + 1] = std::mem::zeroed(); + for i in 0..N { + result[i] = array[i]; + } + // index N is already zeroed + result +} +// docs:end:intermediate-underflow-example diff --git a/test_programs/compile_failure/arithmetic_generics_underflow/Nargo.toml b/test_programs/compile_failure/arithmetic_generics_underflow/Nargo.toml new file mode 100644 index 00000000000..f024f4c3b59 --- /dev/null +++ b/test_programs/compile_failure/arithmetic_generics_underflow/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "arithmetic_generics_underflow" +type = "bin" +authors = [""] +compiler_version = ">=0.33.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/arithmetic_generics_underflow/src/main.nr b/test_programs/compile_failure/arithmetic_generics_underflow/src/main.nr new file mode 100644 index 00000000000..4df83ac56e0 --- /dev/null +++ b/test_programs/compile_failure/arithmetic_generics_underflow/src/main.nr @@ -0,0 +1,14 @@ +// docs:start:underflow-example +fn pop(array: [Field; N]) -> [Field; N - 1] { + let mut result: [Field; N - 1] = std::mem::zeroed(); + for i in 0..N { + result[i] = array[i]; + } + result +} + +fn main() { + // error: Could not determine array length `(0 - 1)` + pop([]); +} +// docs:end:underflow-example diff --git a/test_programs/compile_success_empty/comptime_function_definition/src/main.nr b/test_programs/compile_success_empty/comptime_function_definition/src/main.nr index 48651022b31..62f119cc0c0 100644 --- a/test_programs/compile_success_empty/comptime_function_definition/src/main.nr +++ b/test_programs/compile_success_empty/comptime_function_definition/src/main.nr @@ -69,3 +69,20 @@ contract some_contract { fn set_pub_return(f: FunctionDefinition) { f.set_return_public(true); } + +mod foo { + #[attr] + pub fn some() {} + + fn attr(f: FunctionDefinition) { + assert_eq(f.module().name(), quote { foo }); + + assert(!f.is_unconstrained()); + + f.set_unconstrained(true); + assert(f.is_unconstrained()); + + f.set_unconstrained(false); + assert(!f.is_unconstrained()); + } +} diff --git a/test_programs/compile_success_empty/comptime_module/src/main.nr b/test_programs/compile_success_empty/comptime_module/src/main.nr index 1d1690c4017..baf45c517ed 100644 --- a/test_programs/compile_success_empty/comptime_module/src/main.nr +++ b/test_programs/compile_success_empty/comptime_module/src/main.nr @@ -42,6 +42,22 @@ fn outer_attribute_separate_module(m: Module) { increment_counter(); } +struct Foo {} + +#[add_function] +mod add_to_me { + fn add_to_me_function() {} +} + +fn add_function(m: Module) { + m.add_item( + quote { pub fn added_function() -> super::Foo { + add_to_me_function(); + super::Foo {} + } } + ); +} + fn main() { comptime { @@ -73,6 +89,8 @@ fn main() { yet_another_module::generated_outer_function(); yet_another_module::generated_inner_function(); + + let _ = add_to_me::added_function(); } // docs:start:as_module_example diff --git a/test_programs/compile_success_empty/comptime_type_definition/Nargo.toml b/test_programs/compile_success_empty/comptime_struct_definition/Nargo.toml similarity index 69% rename from test_programs/compile_success_empty/comptime_type_definition/Nargo.toml rename to test_programs/compile_success_empty/comptime_struct_definition/Nargo.toml index 099545a9e71..4495d27e028 100644 --- a/test_programs/compile_success_empty/comptime_type_definition/Nargo.toml +++ b/test_programs/compile_success_empty/comptime_struct_definition/Nargo.toml @@ -1,5 +1,5 @@ [package] -name = "comptime_type_definition" +name = "comptime_struct_definition" type = "bin" authors = [""] compiler_version = ">=0.31.0" diff --git a/test_programs/compile_success_empty/comptime_struct_definition/src/main.nr b/test_programs/compile_success_empty/comptime_struct_definition/src/main.nr new file mode 100644 index 00000000000..da2871a253d --- /dev/null +++ b/test_programs/compile_success_empty/comptime_struct_definition/src/main.nr @@ -0,0 +1,50 @@ +#[my_comptime_fn] +struct MyType { + field1: [A; 10], + field2: (B, C), +} + +#[mutate_struct_fields] +struct I32AndField { + z: i8, +} + +comptime fn my_comptime_fn(typ: StructDefinition) { + let _ = typ.as_type(); + assert_eq(typ.generics().len(), 3); + assert_eq(typ.fields().len(), 2); + assert_eq(typ.name(), quote { MyType }); +} + +comptime fn mutate_struct_fields(s: StructDefinition) { + let fields = &[ + (quote[x], quote[i32].as_type()), + (quote[y], quote[Field].as_type()) + ]; + s.set_fields(fields); +} + +mod foo { + #[attr] + struct Foo {} + + comptime fn attr(s: StructDefinition) { + assert_eq(s.module().name(), quote { foo }); + } + + #[add_generic] + struct Bar {} + + // docs:start:add-generic-example + comptime fn add_generic(s: StructDefinition) { + assert_eq(s.generics().len(), 0); + let new_generic = s.add_generic("T"); + + let generics = s.generics(); + assert_eq(generics.len(), 1); + assert_eq(generics[0], new_generic); + } + // docs:end:add-generic-example +} + +fn main() {} diff --git a/test_programs/compile_success_empty/comptime_type/src/main.nr b/test_programs/compile_success_empty/comptime_type/src/main.nr index 0b15c5605b3..c9307570c87 100644 --- a/test_programs/compile_success_empty/comptime_type/src/main.nr +++ b/test_programs/compile_success_empty/comptime_type/src/main.nr @@ -19,6 +19,18 @@ struct StructDoesNotImplementSomeTrait { } +// docs:start:serialize-setup +trait Serialize {} + +impl Serialize<1> for Field {} + +impl Serialize for [T; N] + where T: Serialize {} + +impl Serialize for (T, U) + where T: Serialize, U: Serialize {} +// docs:end:serialize-setup + fn main() { comptime { @@ -115,6 +127,29 @@ fn main() { let str_type = quote { str<10> }.as_type(); let constant = str_type.as_str().unwrap(); assert_eq(constant.as_constant().unwrap(), 10); + + // Check std::meta::typ::fresh_type_variable + // docs:start:fresh-type-variable-example + let typevar1 = std::meta::typ::fresh_type_variable(); + let constraint = quote { Serialize<$typevar1> }.as_trait_constraint(); + let field_type = quote { Field }.as_type(); + + // Search for a trait impl (binding typevar1 to 1 when the impl is found): + assert(field_type.implements(constraint)); + + // typevar1 should be bound to the "1" generic now: + assert_eq(typevar1.as_constant().unwrap(), 1); + + // If we want to do the same with a different type, we need to + // create a new type variable now that `typevar1` is bound + let typevar2 = std::meta::typ::fresh_type_variable(); + let constraint = quote { Serialize<$typevar2> }.as_trait_constraint(); + let array_type = quote { [(Field, Field); 5] }.as_type(); + assert(array_type.implements(constraint)); + + // Now typevar2 should be bound to the serialized pair size 2 times the array length 5 + assert_eq(typevar2.as_constant().unwrap(), 10); + // docs:end:fresh-type-variable-example } } diff --git a/test_programs/compile_success_empty/comptime_type_definition/src/main.nr b/test_programs/compile_success_empty/comptime_type_definition/src/main.nr deleted file mode 100644 index aca8d067dde..00000000000 --- a/test_programs/compile_success_empty/comptime_type_definition/src/main.nr +++ /dev/null @@ -1,26 +0,0 @@ -fn main() {} - -#[my_comptime_fn] -struct MyType { - field1: [A; 10], - field2: (B, C), -} - -#[mutate_struct_fields] -struct I32AndField { - z: i8, -} - -comptime fn my_comptime_fn(typ: StructDefinition) { - let _ = typ.as_type(); - assert_eq(typ.generics().len(), 3); - assert_eq(typ.fields().len(), 2); -} - -comptime fn mutate_struct_fields(s: StructDefinition) { - let fields = &[ - (quote[x], quote[i32].as_type()), - (quote[y], quote[Field].as_type()) - ]; - s.set_fields(fields); -} diff --git a/test_programs/compile_success_empty/references_aliasing/src/main.nr b/test_programs/compile_success_empty/references_aliasing/src/main.nr index 0d96bc2a734..d3e4257851b 100644 --- a/test_programs/compile_success_empty/references_aliasing/src/main.nr +++ b/test_programs/compile_success_empty/references_aliasing/src/main.nr @@ -5,6 +5,7 @@ fn main() { assert(*xref == 101); regression_2445(); + single_alias_inside_loop(); } fn increment(mut r: &mut Field) { @@ -26,3 +27,15 @@ fn regression_2445() { assert(**array[0] == 2); assert(**array[1] == 2); } + +fn single_alias_inside_loop() { + let mut var = 0; + let ref = &mut &mut var; + + for _ in 0..1 { + **ref = 2; + } + + assert(var == 2); + assert(**ref == 2); +} diff --git a/test_programs/execution_success/derive/src/main.nr b/test_programs/execution_success/derive/src/main.nr index 5ec2fb32a79..b32612831d7 100644 --- a/test_programs/execution_success/derive/src/main.nr +++ b/test_programs/execution_success/derive/src/main.nr @@ -35,6 +35,9 @@ struct MyOtherOtherStruct { x: T, } +#[derive(Eq, Default, Hash, Ord)] +struct EmptyStruct { } + fn main() { let s = MyStruct { my_field: 1 }; s.do_nothing(); @@ -53,6 +56,9 @@ fn main() { let mut hasher = TestHasher { result: 0 }; o1.hash(&mut hasher); assert_eq(hasher.finish(), 12 + 24 + 54); + + let empty = EmptyStruct {}; + assert_eq(empty, empty); } struct TestHasher { diff --git a/tooling/debugger/src/context.rs b/tooling/debugger/src/context.rs index 0d348cf172d..dde3fe84d88 100644 --- a/tooling/debugger/src/context.rs +++ b/tooling/debugger/src/context.rs @@ -976,10 +976,20 @@ mod tests { let brillig_bytecode = BrilligBytecode { bytecode: vec![ + BrilligOpcode::Const { + destination: MemoryAddress(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(1u64), + }, + BrilligOpcode::Const { + destination: MemoryAddress(1), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, BrilligOpcode::CalldataCopy { destination_address: MemoryAddress(0), - size: 1, - offset: 0, + size_address: MemoryAddress(0), + offset_address: MemoryAddress(1), }, BrilligOpcode::Const { destination: MemoryAddress::from(1), @@ -1036,7 +1046,7 @@ mod tests { }) ); - // Execute the first Brillig opcode (calldata copy) + // Const let result = context.step_into_opcode(); assert!(matches!(result, DebugCommandResult::Ok)); assert_eq!( @@ -1048,7 +1058,7 @@ mod tests { }) ); - // execute the second Brillig opcode (const) + // Const let result = context.step_into_opcode(); assert!(matches!(result, DebugCommandResult::Ok)); assert_eq!( @@ -1060,26 +1070,50 @@ mod tests { }) ); - // try to execute the third Brillig opcode (and resolve the foreign call) + // Calldatacopy let result = context.step_into_opcode(); assert!(matches!(result, DebugCommandResult::Ok)); assert_eq!( context.get_current_debug_location(), Some(DebugLocation { circuit_id: 0, - opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 2 }, + opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 3 }, brillig_function_id: Some(BrilligFunctionId(0)), }) ); - // retry the third Brillig opcode (foreign call should be finished) + // Const let result = context.step_into_opcode(); assert!(matches!(result, DebugCommandResult::Ok)); assert_eq!( context.get_current_debug_location(), Some(DebugLocation { circuit_id: 0, - opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 3 }, + opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 4 }, + brillig_function_id: Some(BrilligFunctionId(0)), + }) + ); + + // try to execute the Brillig opcode (and resolve the foreign call) + let result = context.step_into_opcode(); + assert!(matches!(result, DebugCommandResult::Ok)); + assert_eq!( + context.get_current_debug_location(), + Some(DebugLocation { + circuit_id: 0, + opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 4 }, + brillig_function_id: Some(BrilligFunctionId(0)), + }) + ); + + // retry the Brillig opcode (foreign call should be finished) + let result = context.step_into_opcode(); + assert!(matches!(result, DebugCommandResult::Ok)); + assert_eq!( + context.get_current_debug_location(), + Some(DebugLocation { + circuit_id: 0, + opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 5 }, brillig_function_id: Some(BrilligFunctionId(0)), }) ); @@ -1101,10 +1135,20 @@ mod tests { // This Brillig block is equivalent to: z = x + y let brillig_bytecode = BrilligBytecode { bytecode: vec![ + BrilligOpcode::Const { + destination: MemoryAddress(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(2u64), + }, + BrilligOpcode::Const { + destination: MemoryAddress(1), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, BrilligOpcode::CalldataCopy { destination_address: MemoryAddress(0), - size: 2, - offset: 0, + size_address: MemoryAddress(0), + offset_address: MemoryAddress(1), }, BrilligOpcode::BinaryFieldOp { destination: MemoryAddress::from(0), diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 59758f4b972..dad0d37aba7 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -5,7 +5,7 @@ use std::{ use async_lsp::ResponseError; use completion_items::{ - crate_completion_item, field_completion_item, simple_completion_item, + crate_completion_item, field_completion_item, simple_completion_item, snippet_completion_item, struct_field_completion_item, }; use convert_case::{Case, Casing}; @@ -15,11 +15,11 @@ use lsp_types::{CompletionItem, CompletionItemKind, CompletionParams, Completion use noirc_errors::{Location, Span}; use noirc_frontend::{ ast::{ - AsTraitPath, BlockExpression, CallExpression, ConstructorExpression, Expression, - ExpressionKind, ForLoopStatement, GenericTypeArgs, Ident, IfExpression, ItemVisibility, - Lambda, LetStatement, MemberAccessExpression, MethodCallExpression, NoirFunction, - NoirStruct, NoirTraitImpl, Path, PathKind, Pattern, Statement, TypeImpl, UnresolvedGeneric, - UnresolvedGenerics, UnresolvedType, UseTree, UseTreeKind, Visitor, + AsTraitPath, AttributeTarget, BlockExpression, CallExpression, ConstructorExpression, + Expression, ExpressionKind, ForLoopStatement, GenericTypeArgs, Ident, IfExpression, + ItemVisibility, Lambda, LetStatement, MemberAccessExpression, MethodCallExpression, + NoirFunction, NoirStruct, NoirTraitImpl, Path, PathKind, Pattern, Statement, TypeImpl, + UnresolvedGeneric, UnresolvedGenerics, UnresolvedType, UseTree, UseTreeKind, Visitor, }, graph::{CrateId, Dependency}, hir::def_map::{CrateDefMap, LocalModuleId, ModuleId}, @@ -27,6 +27,7 @@ use noirc_frontend::{ macros_api::{ModuleDefId, NodeInterner}, node_interner::ReferenceId, parser::{Item, ItemKind, ParsedSubModule}, + token::CustomAtrribute, ParsedModule, StructType, Type, }; use sort_text::underscore_sort_text; @@ -106,6 +107,7 @@ struct NodeFinder<'a> { nesting: usize, /// The line where an auto_import must be inserted auto_import_line: usize, + self_type: Option, } impl<'a> NodeFinder<'a> { @@ -147,6 +149,7 @@ impl<'a> NodeFinder<'a> { suggested_module_def_ids: HashSet::new(), nesting: 0, auto_import_line: 0, + self_type: None, } } @@ -191,8 +194,9 @@ impl<'a> NodeFinder<'a> { fields.remove(&field.0.contents); } + let self_prefix = false; for (field, typ) in fields { - self.completion_items.push(struct_field_completion_item(field, typ)); + self.completion_items.push(struct_field_completion_item(field, typ, self_prefix)); } } @@ -293,6 +297,7 @@ impl<'a> NodeFinder<'a> { &prefix, FunctionKind::Any, function_completion_kind, + false, // self_prefix ); return; } @@ -308,6 +313,7 @@ impl<'a> NodeFinder<'a> { &prefix, FunctionKind::Any, function_completion_kind, + false, // self_prefix ); return; } @@ -340,11 +346,21 @@ impl<'a> NodeFinder<'a> { self.local_variables_completion(&prefix); self.builtin_functions_completion(&prefix, function_completion_kind); self.builtin_values_completion(&prefix); + if let Some(self_type) = &self.self_type { + let self_prefix = true; + self.complete_type_fields_and_methods( + &self_type.clone(), + &prefix, + function_completion_kind, + self_prefix, + ); + } } RequestedItems::OnlyTypes => { self.builtin_types_completion(&prefix); self.type_parameters_completion(&prefix); } + RequestedItems::OnlyAttributeFunctions(..) => (), } self.complete_auto_imports(&prefix, requested_items, function_completion_kind); } @@ -518,16 +534,18 @@ impl<'a> NodeFinder<'a> { typ: &Type, prefix: &str, function_completion_kind: FunctionCompletionKind, + self_prefix: bool, ) { match typ { Type::Struct(struct_type, generics) => { - self.complete_struct_fields(&struct_type.borrow(), generics, prefix); + self.complete_struct_fields(&struct_type.borrow(), generics, prefix, self_prefix); } Type::MutableReference(typ) => { return self.complete_type_fields_and_methods( typ, prefix, function_completion_kind, + self_prefix, ); } Type::Alias(type_alias, _) => { @@ -536,10 +554,11 @@ impl<'a> NodeFinder<'a> { &type_alias.typ, prefix, function_completion_kind, + self_prefix, ); } Type::Tuple(types) => { - self.complete_tuple_fields(types); + self.complete_tuple_fields(types, self_prefix); } Type::FieldElement | Type::Array(_, _) @@ -565,6 +584,7 @@ impl<'a> NodeFinder<'a> { prefix, FunctionKind::SelfType(typ), function_completion_kind, + self_prefix, ); } @@ -574,6 +594,7 @@ impl<'a> NodeFinder<'a> { prefix: &str, function_kind: FunctionKind, function_completion_kind: FunctionCompletionKind, + self_prefix: bool, ) { let Some(methods_by_name) = self.interner.get_type_methods(typ) else { return; @@ -587,6 +608,8 @@ impl<'a> NodeFinder<'a> { func_id, function_completion_kind, function_kind, + None, // attribute first type + self_prefix, ) { self.completion_items.push(completion_item); self.suggested_module_def_ids.insert(ModuleDefId::FunctionId(func_id)); @@ -603,6 +626,8 @@ impl<'a> NodeFinder<'a> { function_kind: FunctionKind, function_completion_kind: FunctionCompletionKind, ) { + let self_prefix = false; + for (name, func_id) in &trait_.method_ids { if name_matches(name, prefix) { if let Some(completion_item) = self.function_completion_item( @@ -610,6 +635,8 @@ impl<'a> NodeFinder<'a> { *func_id, function_completion_kind, function_kind, + None, // attribute first type + self_prefix, ) { self.completion_items.push(completion_item); self.suggested_module_def_ids.insert(ModuleDefId::FunctionId(*func_id)); @@ -623,17 +650,19 @@ impl<'a> NodeFinder<'a> { struct_type: &StructType, generics: &[Type], prefix: &str, + self_prefix: bool, ) { for (name, typ) in &struct_type.get_fields(generics) { if name_matches(name, prefix) { - self.completion_items.push(struct_field_completion_item(name, typ)); + self.completion_items.push(struct_field_completion_item(name, typ, self_prefix)); } } } - fn complete_tuple_fields(&mut self, types: &[Type]) { + fn complete_tuple_fields(&mut self, types: &[Type], self_prefix: bool) { for (index, typ) in types.iter().enumerate() { - self.completion_items.push(field_completion_item(&index.to_string(), typ.to_string())); + let name = index.to_string(); + self.completion_items.push(field_completion_item(&name, typ.to_string(), self_prefix)); } } @@ -761,6 +790,66 @@ impl<'a> NodeFinder<'a> { None } + fn suggest_attributes(&mut self, prefix: &str, target: AttributeTarget) { + self.suggest_builtin_attributes(prefix, target); + + let function_completion_kind = FunctionCompletionKind::NameAndParameters; + let requested_items = RequestedItems::OnlyAttributeFunctions(target); + + self.complete_in_module( + self.module_id, + prefix, + PathKind::Plain, + true, + function_completion_kind, + requested_items, + ); + + self.complete_auto_imports(prefix, requested_items, function_completion_kind); + } + + fn suggest_no_arguments_attributes(&mut self, prefix: &str, attributes: &[&str]) { + for name in attributes { + if name_matches(name, prefix) { + self.completion_items.push(simple_completion_item( + *name, + CompletionItemKind::METHOD, + None, + )); + } + } + } + + fn suggest_one_argument_attributes(&mut self, prefix: &str, attributes: &[&str]) { + for name in attributes { + if name_matches(name, prefix) { + self.completion_items.push(snippet_completion_item( + format!("{}(…)", name), + CompletionItemKind::METHOD, + format!("{}(${{1:name}})", name), + None, + )); + } + } + } + + fn try_set_self_type(&mut self, pattern: &Pattern) { + match pattern { + Pattern::Identifier(ident) => { + if ident.0.contents == "self" { + let location = Location::new(ident.span(), self.file); + if let Some(ReferenceId::Local(definition_id)) = + self.interner.find_referenced(location) + { + self.self_type = Some(self.interner.definition_type(definition_id)); + } + } + } + Pattern::Mutable(pattern, ..) => self.try_set_self_type(pattern), + Pattern::Tuple(..) | Pattern::Struct(..) => (), + } + } + fn includes_span(&self, span: Span) -> bool { span.start() as usize <= self.byte_index && self.byte_index <= span.end() as usize } @@ -813,10 +902,15 @@ impl<'a> Visitor for NodeFinder<'a> { } fn visit_noir_function(&mut self, noir_function: &NoirFunction, span: Span) -> bool { + for attribute in noir_function.secondary_attributes() { + attribute.accept(AttributeTarget::Function, self); + } + 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.try_set_self_type(¶m.pattern); param.typ.accept(self); } @@ -830,6 +924,7 @@ impl<'a> Visitor for NodeFinder<'a> { noir_function.def.body.accept(Some(span), self); self.type_parameters = old_type_parameters; + self.self_type = None; false } @@ -871,6 +966,10 @@ impl<'a> Visitor for NodeFinder<'a> { } fn visit_noir_struct(&mut self, noir_struct: &NoirStruct, _: Span) -> bool { + for attribute in &noir_struct.attributes { + attribute.accept(AttributeTarget::Struct, self); + } + self.type_parameters.clear(); self.collect_type_parameters_in_generics(&noir_struct.generics); @@ -945,7 +1044,13 @@ impl<'a> Visitor for NodeFinder<'a> { 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); + let self_prefix = false; + self.complete_type_fields_and_methods( + &typ, + prefix, + FunctionCompletionKind::Name, + self_prefix, + ); return false; } } @@ -973,7 +1078,13 @@ impl<'a> Visitor for NodeFinder<'a> { 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); + let self_prefix = false; + self.complete_type_fields_and_methods( + &typ, + &prefix, + FunctionCompletionKind::Name, + self_prefix, + ); return false; } } @@ -1042,10 +1153,12 @@ impl<'a> Visitor for NodeFinder<'a> { { let typ = self.interner.definition_type(definition_id); let prefix = ""; + let self_prefix = false; self.complete_type_fields_and_methods( &typ, prefix, FunctionCompletionKind::NameAndParameters, + self_prefix, ); } } @@ -1072,10 +1185,12 @@ impl<'a> Visitor for NodeFinder<'a> { if let Some(typ) = self.interner.type_at_location(location) { let typ = typ.follow_bindings(); let prefix = ""; + let self_prefix = false; self.complete_type_fields_and_methods( &typ, prefix, FunctionCompletionKind::NameAndParameters, + self_prefix, ); } } @@ -1136,10 +1251,12 @@ impl<'a> Visitor for NodeFinder<'a> { if let Some(typ) = self.interner.type_at_location(location) { let typ = typ.follow_bindings(); let prefix = ident.to_string().to_case(Case::Snake); + let self_prefix = false; self.complete_type_fields_and_methods( &typ, &prefix, FunctionCompletionKind::NameAndParameters, + self_prefix, ); return false; } @@ -1201,6 +1318,14 @@ impl<'a> Visitor for NodeFinder<'a> { unresolved_types.accept(self); false } + + fn visit_custom_attribute(&mut self, attribute: &CustomAtrribute, target: AttributeTarget) { + if self.byte_index != attribute.contents_span.end() as usize { + return; + } + + self.suggest_attributes(&attribute.contents, target); + } } /// Returns true if name matches a prefix written in code. diff --git a/tooling/lsp/src/requests/completion/builtins.rs b/tooling/lsp/src/requests/completion/builtins.rs index f449177a027..bca1061ff47 100644 --- a/tooling/lsp/src/requests/completion/builtins.rs +++ b/tooling/lsp/src/requests/completion/builtins.rs @@ -1,5 +1,5 @@ use lsp_types::CompletionItemKind; -use noirc_frontend::token::Keyword; +use noirc_frontend::{ast::AttributeTarget, token::Keyword}; use strum::IntoEnumIterator; use super::{ @@ -84,6 +84,40 @@ impl<'a> NodeFinder<'a> { } } } + + pub(super) fn suggest_builtin_attributes(&mut self, prefix: &str, target: AttributeTarget) { + match target { + AttributeTarget::Module => (), + AttributeTarget::Struct => { + self.suggest_one_argument_attributes(prefix, &["abi"]); + } + AttributeTarget::Function => { + let no_arguments_attributes = &[ + "contract_library_method", + "deprecated", + "export", + "fold", + "no_predicates", + "recursive", + "test", + "varargs", + ]; + self.suggest_no_arguments_attributes(prefix, no_arguments_attributes); + + let one_argument_attributes = &["abi", "field", "foreign", "oracle"]; + self.suggest_one_argument_attributes(prefix, one_argument_attributes); + + if name_matches("test", prefix) || name_matches("should_fail_with", prefix) { + self.completion_items.push(snippet_completion_item( + "test(should_fail_with=\"...\")", + CompletionItemKind::METHOD, + "test(should_fail_with=\"${1:message}\")", + None, + )); + } + } + } + } } pub(super) fn builtin_integer_types() -> [&'static str; 8] { diff --git a/tooling/lsp/src/requests/completion/completion_items.rs b/tooling/lsp/src/requests/completion/completion_items.rs index 21c3a607b18..c3afc225f52 100644 --- a/tooling/lsp/src/requests/completion/completion_items.rs +++ b/tooling/lsp/src/requests/completion/completion_items.rs @@ -2,10 +2,11 @@ use lsp_types::{ Command, CompletionItem, CompletionItemKind, CompletionItemLabelDetails, InsertTextFormat, }; use noirc_frontend::{ + ast::AttributeTarget, hir_def::{function::FuncMeta, stmt::HirPattern}, macros_api::ModuleDefId, node_interner::{FuncId, GlobalId}, - Type, + QuotedType, Type, }; use super::{ @@ -33,9 +34,25 @@ impl<'a> NodeFinder<'a> { | ModuleDefId::TypeAliasId(_) | ModuleDefId::TraitId(_) => (), }, + RequestedItems::OnlyAttributeFunctions(..) => { + if !matches!(module_def_id, ModuleDefId::FunctionId(..)) { + return None; + } + } RequestedItems::AnyItems => (), } + let attribute_first_type = + if let RequestedItems::OnlyAttributeFunctions(target) = requested_items { + match target { + AttributeTarget::Module => Some(Type::Quoted(QuotedType::Module)), + AttributeTarget::Struct => Some(Type::Quoted(QuotedType::StructDefinition)), + AttributeTarget::Function => Some(Type::Quoted(QuotedType::FunctionDefinition)), + } + } else { + None + }; + match module_def_id { ModuleDefId::ModuleId(_) => Some(module_completion_item(name)), ModuleDefId::FunctionId(func_id) => self.function_completion_item( @@ -43,6 +60,8 @@ impl<'a> NodeFinder<'a> { func_id, function_completion_kind, function_kind, + attribute_first_type.as_ref(), + false, // self_prefix ), ModuleDefId::TypeId(..) => Some(self.struct_completion_item(name)), ModuleDefId::TypeAliasId(..) => Some(self.type_alias_completion_item(name)), @@ -77,6 +96,8 @@ impl<'a> NodeFinder<'a> { func_id: FuncId, function_completion_kind: FunctionCompletionKind, function_kind: FunctionKind, + attribute_first_type: Option<&Type>, + self_prefix: bool, ) -> Option { let func_meta = self.interner.function_meta(&func_id); @@ -95,6 +116,17 @@ impl<'a> NodeFinder<'a> { None }; + if let Some(attribute_first_type) = attribute_first_type { + if func_meta.parameters.is_empty() { + return None; + } + + let (_, typ, _) = &func_meta.parameters.0[0]; + if typ != attribute_first_type { + return None; + } + } + match function_kind { FunctionKind::Any => (), FunctionKind::SelfType(mut self_type) => { @@ -135,6 +167,8 @@ impl<'a> NodeFinder<'a> { } else { false }; + let name = if self_prefix { format!("self.{}", name) } else { name.clone() }; + let name = &name; let description = func_meta_type_to_string(func_meta, func_self_type.is_some()); let completion_item = match function_completion_kind { @@ -143,7 +177,13 @@ impl<'a> NodeFinder<'a> { } FunctionCompletionKind::NameAndParameters => { let kind = CompletionItemKind::FUNCTION; - let insert_text = self.compute_function_insert_text(func_meta, name, function_kind); + let skip_first_argument = attribute_first_type.is_some(); + let insert_text = self.compute_function_insert_text( + func_meta, + name, + function_kind, + skip_first_argument, + ); let label = if insert_text.ends_with("()") { format!("{}()", name) } else { @@ -179,13 +219,19 @@ impl<'a> NodeFinder<'a> { func_meta: &FuncMeta, name: &str, function_kind: FunctionKind, + skip_first_argument: bool, ) -> String { let mut text = String::new(); text.push_str(name); text.push('('); + let mut parameters = func_meta.parameters.0.iter(); + if skip_first_argument { + parameters.next(); + } + let mut index = 1; - for (pattern, _, _) in &func_meta.parameters.0 { + for (pattern, _, _) in parameters { if index == 1 { match function_kind { FunctionKind::SelfType(_) => { @@ -294,12 +340,24 @@ fn type_to_self_string(typ: &Type, string: &mut String) { } } -pub(super) fn struct_field_completion_item(field: &str, typ: &Type) -> CompletionItem { - field_completion_item(field, typ.to_string()) +pub(super) fn struct_field_completion_item( + field: &str, + typ: &Type, + self_type: bool, +) -> CompletionItem { + field_completion_item(field, typ.to_string(), self_type) } -pub(super) fn field_completion_item(field: &str, typ: impl Into) -> CompletionItem { - simple_completion_item(field, CompletionItemKind::FIELD, Some(typ.into())) +pub(super) fn field_completion_item( + field: &str, + typ: impl Into, + self_type: bool, +) -> CompletionItem { + if self_type { + simple_completion_item(format!("self.{field}"), CompletionItemKind::FIELD, Some(typ.into())) + } else { + simple_completion_item(field, CompletionItemKind::FIELD, Some(typ.into())) + } } pub(super) fn simple_completion_item( diff --git a/tooling/lsp/src/requests/completion/kinds.rs b/tooling/lsp/src/requests/completion/kinds.rs index 2fe039ba331..6fa74ffdb1a 100644 --- a/tooling/lsp/src/requests/completion/kinds.rs +++ b/tooling/lsp/src/requests/completion/kinds.rs @@ -1,4 +1,4 @@ -use noirc_frontend::Type; +use noirc_frontend::{ast::AttributeTarget, Type}; /// When suggest a function as a result of completion, whether to autocomplete its name or its name and parameters. #[derive(Clone, Copy, PartialEq, Eq, Debug)] @@ -27,4 +27,6 @@ pub(super) enum RequestedItems { AnyItems, // Only suggest types. OnlyTypes, + // Only attribute functions + OnlyAttributeFunctions(AttributeTarget), } diff --git a/tooling/lsp/src/requests/completion/tests.rs b/tooling/lsp/src/requests/completion/tests.rs index a7cfa77a73d..e6a732e9142 100644 --- a/tooling/lsp/src/requests/completion/tests.rs +++ b/tooling/lsp/src/requests/completion/tests.rs @@ -7,8 +7,7 @@ mod completion_tests { completion_items::{ completion_item_with_sort_text, completion_item_with_trigger_parameter_hints_command, crate_completion_item, - field_completion_item, module_completion_item, simple_completion_item, - snippet_completion_item, + module_completion_item, simple_completion_item, snippet_completion_item, }, sort_text::{auto_import_sort_text, self_mismatch_sort_text}, }, @@ -116,6 +115,10 @@ mod completion_tests { )) } + fn field_completion_item(field: &str, typ: impl Into) -> CompletionItem { + crate::requests::completion::field_completion_item(field, typ, false) + } + #[test] async fn test_use_first_segment() { let src = r#" @@ -1888,4 +1891,65 @@ mod completion_tests { Some("(use super::barbaz)".to_string()), ); } + + #[test] + async fn test_suggests_self_fields_and_methods() { + let src = r#" + struct Foo { + foobar: Field, + } + + impl Foo { + fn foobarbaz(self) {} + + fn some_method(self) { + foob>|< + } + } + "#; + + assert_completion_excluding_auto_import( + src, + vec![ + field_completion_item("self.foobar", "Field"), + function_completion_item("self.foobarbaz()", "self.foobarbaz()", "fn(self)"), + ], + ) + .await; + } + + #[test] + async fn test_suggests_built_in_function_attribute() { + let src = r#" + #[dep>|<] + fn foo() {} + "#; + + assert_completion_excluding_auto_import( + src, + vec![simple_completion_item("deprecated", CompletionItemKind::METHOD, None)], + ) + .await; + } + + #[test] + async fn test_suggests_function_attribute() { + let src = r#" + #[some>|<] + fn foo() {} + + fn some_attr(f: FunctionDefinition, x: Field) {} + fn some_other_function(x: Field) {} + "#; + + assert_completion_excluding_auto_import( + src, + vec![function_completion_item( + "some_attr(…)", + "some_attr(${1:x})", + "fn(FunctionDefinition, Field)", + )], + ) + .await; + } } diff --git a/tooling/nargo_cli/build.rs b/tooling/nargo_cli/build.rs index 4dcfccdf085..7469c8be0f8 100644 --- a/tooling/nargo_cli/build.rs +++ b/tooling/nargo_cli/build.rs @@ -218,7 +218,7 @@ fn generate_compile_success_empty_tests(test_file: &mut File, test_data_dir: &Pa &test_dir, &format!( r#" - nargo.arg("info").arg("--arithmetic-generics").arg("--json").arg("--force"); + nargo.arg("info").arg("--json").arg("--force"); {assert_zero_opcodes}"#, ), From 50f12a1c9ebe2c308754201b795df3cf56c2f376 Mon Sep 17 00:00:00 2001 From: TomAFrench Date: Mon, 9 Sep 2024 13:55:29 +0000 Subject: [PATCH 2/3] . --- tooling/lsp/src/requests/completion.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index d4f3c606dd0..f861c8113df 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -4,10 +4,7 @@ use std::{ }; use async_lsp::ResponseError; -use completion_items::{ - crate_completion_item, field_completion_item, simple_completion_item, snippet_completion_item, - struct_field_completion_item, -}; +use completion_items::{field_completion_item, simple_completion_item, snippet_completion_item}; use convert_case::{Case, Casing}; use fm::{FileId, FileMap, PathString}; use kinds::{FunctionCompletionKind, FunctionKind, RequestedItems}; From 54006ea86b35f5ad930e48ebf0120860e97406a5 Mon Sep 17 00:00:00 2001 From: AztecBot Date: Wed, 11 Sep 2024 09:14:11 +0000 Subject: [PATCH 3/3] chore: apply sync fixes --- .aztec-sync-commit | 2 +- .gitattributes | 1 - Cargo.lock | 103 +---- acvm-repo/acir/codegen/acir.cpp | 16 +- .../acir/tests/test_program_serialization.rs | 73 +++- acvm-repo/acvm/tests/solver.rs | 89 +++- .../test/shared/complex_foreign_call.ts | 15 +- acvm-repo/acvm_js/test/shared/foreign_call.ts | 9 +- acvm-repo/brillig/src/opcodes.rs | 4 +- acvm-repo/brillig_vm/src/lib.rs | 381 ++++++++++++------ aztec_macros/src/lib.rs | 10 +- ...te_note_hash_and_optionally_a_nullifier.rs | 2 +- .../src/transforms/contract_interface.rs | 9 +- aztec_macros/src/transforms/events.rs | 53 ++- aztec_macros/src/transforms/functions.rs | 4 +- aztec_macros/src/transforms/note_interface.rs | 93 +++-- aztec_macros/src/transforms/storage.rs | 21 +- aztec_macros/src/utils/ast_utils.rs | 2 +- aztec_macros/src/utils/hir_utils.rs | 4 +- aztec_macros/src/utils/parse_utils.rs | 13 +- compiler/noirc_driver/src/lib.rs | 27 +- .../brillig/brillig_gen/brillig_directive.rs | 34 +- .../src/brillig/brillig_ir/entry_point.rs | 106 ++--- .../src/brillig/brillig_ir/instructions.rs | 46 +-- .../noirc_evaluator/src/ssa/opt/mem2reg.rs | 115 +++++- compiler/noirc_frontend/Cargo.toml | 4 - compiler/noirc_frontend/build.rs | 28 -- compiler/noirc_frontend/src/ast/docs.rs | 23 ++ compiler/noirc_frontend/src/ast/mod.rs | 3 + compiler/noirc_frontend/src/ast/statement.rs | 48 ++- compiler/noirc_frontend/src/ast/structure.rs | 14 +- compiler/noirc_frontend/src/ast/traits.rs | 8 +- compiler/noirc_frontend/src/ast/visitor.rs | 79 +++- compiler/noirc_frontend/src/debug/mod.rs | 2 + .../noirc_frontend/src/elaborator/comptime.rs | 135 +++++-- .../src/elaborator/expressions.rs | 31 +- compiler/noirc_frontend/src/elaborator/mod.rs | 62 +-- .../noirc_frontend/src/elaborator/patterns.rs | 11 + .../src/elaborator/statements.rs | 9 +- .../noirc_frontend/src/elaborator/traits.rs | 9 +- .../noirc_frontend/src/elaborator/types.rs | 11 +- compiler/noirc_frontend/src/graph/mod.rs | 38 +- .../noirc_frontend/src/hir/comptime/errors.rs | 61 ++- .../src/hir/comptime/interpreter.rs | 22 +- .../src/hir/comptime/interpreter/builtin.rs | 305 ++++++++++++-- .../interpreter/builtin/builtin_helpers.rs | 7 +- .../noirc_frontend/src/hir/comptime/tests.rs | 2 +- .../noirc_frontend/src/hir/comptime/value.rs | 166 +++++++- .../src/hir/def_collector/dc_crate.rs | 16 +- .../src/hir/def_collector/dc_mod.rs | 106 ++++- .../noirc_frontend/src/hir/def_map/mod.rs | 2 - .../src/hir/resolution/errors.rs | 9 + compiler/noirc_frontend/src/lexer/lexer.rs | 23 +- compiler/noirc_frontend/src/lexer/token.rs | 24 +- compiler/noirc_frontend/src/lib.rs | 2 +- .../src/monomorphization/errors.rs | 8 + .../src/monomorphization/mod.rs | 12 +- compiler/noirc_frontend/src/node_interner.rs | 30 ++ .../noirc_frontend/src/noir_parser.lalrpop | 170 -------- compiler/noirc_frontend/src/parser/mod.rs | 141 ++++--- compiler/noirc_frontend/src/parser/parser.rs | 191 ++++----- .../src/parser/parser/doc_comments.rs | 36 ++ .../src/parser/parser/structs.rs | 22 +- .../src/parser/parser/traits.rs | 37 +- compiler/noirc_frontend/src/tests.rs | 6 +- docs/docs/noir/concepts/generics.md | 59 ++- .../standard_library/containers/hashmap.md | 5 - docs/docs/noir/standard_library/meta/expr.md | 9 +- .../standard_library/meta/function_def.md | 30 +- .../docs/noir/standard_library/meta/module.md | 8 + .../noir/standard_library/meta/struct_def.md | 33 ++ docs/docs/noir/standard_library/meta/typ.md | 24 ++ noir_stdlib/src/append.nr | 4 +- noir_stdlib/src/cmp.nr | 8 +- noir_stdlib/src/collections/bounded_vec.nr | 314 ++++++++++++++- noir_stdlib/src/collections/map.nr | 293 ++++++++++++-- noir_stdlib/src/hash/poseidon/bn254/consts.nr | 5 - noir_stdlib/src/hash/poseidon/mod.nr | 8 - noir_stdlib/src/lib.nr | 1 + noir_stdlib/src/meta/expr.nr | 66 ++- noir_stdlib/src/meta/function_def.nr | 15 + noir_stdlib/src/meta/mod.nr | 1 + noir_stdlib/src/meta/module.nr | 5 + noir_stdlib/src/meta/op.nr | 10 +- noir_stdlib/src/meta/struct_def.nr | 21 +- noir_stdlib/src/meta/typ.nr | 5 + noir_stdlib/src/panic.nr | 4 + noir_stdlib/src/prelude.nr | 1 + .../Nargo.toml | 7 + .../src/main.nr | 32 ++ .../arithmetic_generics_underflow/Nargo.toml | 7 + .../arithmetic_generics_underflow/src/main.nr | 14 + .../quote_at_runtime/Nargo.toml | 7 + .../quote_at_runtime/src/main.nr | 7 + .../attributes_struct/src/main.nr | 2 +- .../comptime_function_definition/src/main.nr | 17 + .../comptime_global_using_trait/Nargo.toml | 7 + .../comptime_global_using_trait/src/main.nr | 3 + .../comptime_module/src/main.nr | 26 +- .../comptime_module/src/separate_module.nr | 3 +- .../Nargo.toml | 2 +- .../comptime_struct_definition/src/main.nr | 50 +++ .../comptime_type/src/main.nr | 35 ++ .../comptime_type_definition/src/main.nr | 26 -- .../inject_context_attribute/src/main.nr | 4 +- .../references_aliasing/src/main.nr | 13 + .../execution_success/derive/src/main.nr | 6 + .../noir_test_success/bounded_vec/src/main.nr | 12 +- .../comptime_expr/src/main.nr | 75 +++- tooling/debugger/src/context.rs | 64 ++- tooling/lsp/src/modules.rs | 2 +- tooling/lsp/src/notifications/mod.rs | 5 +- tooling/lsp/src/requests/completion.rs | 197 +++++++-- .../lsp/src/requests/completion/builtins.rs | 36 +- .../requests/completion/completion_items.rs | 182 +++++++-- tooling/lsp/src/requests/completion/kinds.rs | 4 +- tooling/lsp/src/requests/completion/tests.rs | 114 +++++- tooling/lsp/src/requests/document_symbol.rs | 37 +- tooling/lsp/src/requests/hover.rs | 80 ++-- tooling/lsp/src/requests/mod.rs | 4 +- tooling/lsp/src/requests/profile_run.rs | 6 +- tooling/lsp/src/requests/test_run.rs | 6 +- tooling/lsp/src/requests/tests.rs | 4 +- .../test_programs/document_symbol/src/main.nr | 2 + tooling/nargo/src/workspace.rs | 18 + tooling/nargo_cli/build.rs | 2 +- tooling/nargo_cli/src/cli/check_cmd.rs | 5 +- tooling/nargo_cli/src/cli/compile_cmd.rs | 4 +- tooling/nargo_cli/src/cli/export_cmd.rs | 5 +- tooling/nargo_cli/src/cli/fmt_cmd.rs | 4 +- tooling/nargo_cli/src/cli/test_cmd.rs | 6 +- tooling/nargo_fmt/src/visitor/item.rs | 9 +- 132 files changed, 3770 insertions(+), 1455 deletions(-) delete mode 100644 .gitattributes delete mode 100644 compiler/noirc_frontend/build.rs create mode 100644 compiler/noirc_frontend/src/ast/docs.rs delete mode 100644 compiler/noirc_frontend/src/noir_parser.lalrpop create mode 100644 compiler/noirc_frontend/src/parser/parser/doc_comments.rs create mode 100644 noir_stdlib/src/panic.nr create mode 100644 test_programs/compile_failure/arithmetic_generics_intermediate_underflow/Nargo.toml create mode 100644 test_programs/compile_failure/arithmetic_generics_intermediate_underflow/src/main.nr create mode 100644 test_programs/compile_failure/arithmetic_generics_underflow/Nargo.toml create mode 100644 test_programs/compile_failure/arithmetic_generics_underflow/src/main.nr create mode 100644 test_programs/compile_failure/quote_at_runtime/Nargo.toml create mode 100644 test_programs/compile_failure/quote_at_runtime/src/main.nr create mode 100644 test_programs/compile_success_empty/comptime_global_using_trait/Nargo.toml create mode 100644 test_programs/compile_success_empty/comptime_global_using_trait/src/main.nr rename test_programs/compile_success_empty/{comptime_type_definition => comptime_struct_definition}/Nargo.toml (69%) create mode 100644 test_programs/compile_success_empty/comptime_struct_definition/src/main.nr delete mode 100644 test_programs/compile_success_empty/comptime_type_definition/src/main.nr diff --git a/.aztec-sync-commit b/.aztec-sync-commit index a9f74612f46..6fc4a7a80a1 100644 --- a/.aztec-sync-commit +++ b/.aztec-sync-commit @@ -1 +1 @@ -05cc59fd28b4d0ee89343106e538c0db0e70f52f +365193617179fbbfb6a19c8f194a0a214beac87f diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 204cff5c097..00000000000 --- a/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -*.nr linguist-language=rust diff --git a/Cargo.lock b/Cargo.lock index 3f56f5b6965..796ac50ca9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -274,7 +274,7 @@ dependencies = [ "ark-std", "derivative", "hashbrown 0.13.2", - "itertools 0.10.5", + "itertools", "num-traits", "zeroize", ] @@ -291,7 +291,7 @@ dependencies = [ "ark-std", "derivative", "digest", - "itertools 0.10.5", + "itertools", "num-bigint", "num-traits", "paste", @@ -380,15 +380,6 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" -[[package]] -name = "ascii-canvas" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" -dependencies = [ - "term", -] - [[package]] name = "assert_cmd" version = "2.0.12" @@ -1069,7 +1060,7 @@ dependencies = [ "clap", "criterion-plot", "is-terminal", - "itertools 0.10.5", + "itertools", "num-traits", "once_cell", "oorandom", @@ -1090,7 +1081,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", - "itertools 0.10.5", + "itertools", ] [[package]] @@ -1410,15 +1401,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "ena" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c533630cf40e9caa44bd91aadc88a75d75a4c3a12b4cfde353cbed41daa1e1f1" -dependencies = [ - "log", -] - [[package]] name = "encode_unicode" version = "0.3.6" @@ -2207,15 +2189,6 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.9" @@ -2400,37 +2373,6 @@ dependencies = [ "libc", ] -[[package]] -name = "lalrpop" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" -dependencies = [ - "ascii-canvas", - "bit-set", - "ena", - "itertools 0.11.0", - "lalrpop-util", - "petgraph", - "pico-args", - "regex", - "regex-syntax 0.8.2", - "string_cache", - "term", - "tiny-keccak", - "unicode-xid", - "walkdir", -] - -[[package]] -name = "lalrpop-util" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" -dependencies = [ - "regex-automata 0.4.7", -] - [[package]] name = "lazy_static" version = "1.4.0" @@ -2731,12 +2673,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - [[package]] name = "nibble_vec" version = "0.1.0" @@ -3034,8 +2970,6 @@ dependencies = [ "fm", "im", "iter-extended", - "lalrpop", - "lalrpop-util", "noirc_arena", "noirc_errors", "noirc_printable_type", @@ -3377,12 +3311,6 @@ dependencies = [ "siphasher", ] -[[package]] -name = "pico-args" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" - [[package]] name = "pin-project-lite" version = "0.2.13" @@ -3489,12 +3417,6 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - [[package]] name = "predicates" version = "2.1.5" @@ -3503,7 +3425,7 @@ checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" dependencies = [ "difflib", "float-cmp", - "itertools 0.10.5", + "itertools", "normalize-line-endings", "predicates-core", "regex", @@ -3517,7 +3439,7 @@ checksum = "09963355b9f467184c04017ced4a2ba2d75cbcb4e7462690d388233253d4b1a9" dependencies = [ "anstyle", "difflib", - "itertools 0.10.5", + "itertools", "predicates-core", ] @@ -4396,19 +4318,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb" -[[package]] -name = "string_cache" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" -dependencies = [ - "new_debug_unreachable", - "once_cell", - "parking_lot 0.12.1", - "phf_shared", - "precomputed-hash", -] - [[package]] name = "strsim" version = "0.10.0" diff --git a/acvm-repo/acir/codegen/acir.cpp b/acvm-repo/acir/codegen/acir.cpp index 0ccf7e4639d..ef5baf076a0 100644 --- a/acvm-repo/acir/codegen/acir.cpp +++ b/acvm-repo/acir/codegen/acir.cpp @@ -610,8 +610,8 @@ namespace Program { struct CalldataCopy { Program::MemoryAddress destination_address; - uint64_t size; - uint64_t offset; + Program::MemoryAddress size_address; + Program::MemoryAddress offset_address; friend bool operator==(const CalldataCopy&, const CalldataCopy&); std::vector bincodeSerialize() const; @@ -5278,8 +5278,8 @@ namespace Program { inline bool operator==(const BrilligOpcode::CalldataCopy &lhs, const BrilligOpcode::CalldataCopy &rhs) { if (!(lhs.destination_address == rhs.destination_address)) { return false; } - if (!(lhs.size == rhs.size)) { return false; } - if (!(lhs.offset == rhs.offset)) { return false; } + if (!(lhs.size_address == rhs.size_address)) { return false; } + if (!(lhs.offset_address == rhs.offset_address)) { return false; } return true; } @@ -5304,8 +5304,8 @@ template <> template void serde::Serializable::serialize(const Program::BrilligOpcode::CalldataCopy &obj, Serializer &serializer) { serde::Serializable::serialize(obj.destination_address, serializer); - serde::Serializable::serialize(obj.size, serializer); - serde::Serializable::serialize(obj.offset, serializer); + serde::Serializable::serialize(obj.size_address, serializer); + serde::Serializable::serialize(obj.offset_address, serializer); } template <> @@ -5313,8 +5313,8 @@ template Program::BrilligOpcode::CalldataCopy serde::Deserializable::deserialize(Deserializer &deserializer) { Program::BrilligOpcode::CalldataCopy obj; obj.destination_address = serde::Deserializable::deserialize(deserializer); - obj.size = serde::Deserializable::deserialize(deserializer); - obj.offset = serde::Deserializable::deserialize(deserializer); + obj.size_address = serde::Deserializable::deserialize(deserializer); + obj.offset_address = serde::Deserializable::deserialize(deserializer); return obj; } diff --git a/acvm-repo/acir/tests/test_program_serialization.rs b/acvm-repo/acir/tests/test_program_serialization.rs index 7bed57e22a0..838886a03ce 100644 --- a/acvm-repo/acir/tests/test_program_serialization.rs +++ b/acvm-repo/acir/tests/test_program_serialization.rs @@ -164,10 +164,20 @@ fn simple_brillig_foreign_call() { let brillig_bytecode = BrilligBytecode { bytecode: vec![ + brillig::Opcode::Const { + destination: MemoryAddress(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(1_usize), + }, + brillig::Opcode::Const { + destination: MemoryAddress(1), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0_usize), + }, brillig::Opcode::CalldataCopy { destination_address: MemoryAddress(0), - size: 1, - offset: 0, + size_address: MemoryAddress(0), + offset_address: MemoryAddress(1), }, brillig::Opcode::ForeignCall { function: "invert".into(), @@ -204,11 +214,12 @@ fn simple_brillig_foreign_call() { let bytes = Program::serialize_program(&program); let expected_serialization: Vec = vec![ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 173, 80, 49, 10, 192, 32, 12, 52, 45, 45, 133, 110, 190, - 68, 127, 224, 103, 28, 92, 28, 68, 124, 191, 130, 9, 4, 137, 46, 122, 16, 46, 119, 7, 33, - 9, 168, 142, 175, 21, 96, 255, 32, 147, 230, 32, 207, 33, 155, 61, 88, 56, 55, 203, 240, - 125, 175, 177, 1, 110, 170, 197, 101, 55, 242, 43, 100, 132, 159, 229, 33, 22, 159, 242, - 234, 87, 51, 45, 121, 90, 200, 42, 48, 209, 35, 111, 164, 1, 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 173, 81, 49, 10, 128, 48, 12, 108, 196, 138, 224, 230, + 75, 226, 15, 252, 140, 131, 139, 131, 136, 239, 111, 161, 9, 28, 165, 205, 210, 28, 132, + 36, 119, 16, 114, 9, 133, 130, 53, 7, 73, 29, 37, 107, 143, 80, 238, 148, 204, 99, 56, 200, + 111, 22, 227, 190, 83, 93, 16, 146, 193, 112, 22, 225, 34, 168, 205, 142, 174, 241, 218, + 206, 179, 121, 49, 188, 109, 57, 84, 191, 159, 255, 122, 63, 235, 199, 189, 190, 197, 237, + 13, 45, 1, 20, 245, 146, 30, 92, 2, 0, 0, ]; assert_eq!(bytes, expected_serialization) @@ -230,20 +241,40 @@ fn complex_brillig_foreign_call() { let brillig_bytecode = BrilligBytecode { bytecode: vec![ + brillig::Opcode::Const { + destination: MemoryAddress(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(3_usize), + }, + brillig::Opcode::Const { + destination: MemoryAddress(1), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0_usize), + }, brillig::Opcode::CalldataCopy { destination_address: MemoryAddress(32), - size: 3, - offset: 0, + size_address: MemoryAddress(0), + offset_address: MemoryAddress(1), }, brillig::Opcode::Const { destination: MemoryAddress(0), value: FieldElement::from(32_usize), bit_size: BitSize::Integer(IntegerBitSize::U32), }, + brillig::Opcode::Const { + destination: MemoryAddress(3), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(1_usize), + }, + brillig::Opcode::Const { + destination: MemoryAddress(4), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(3_usize), + }, brillig::Opcode::CalldataCopy { destination_address: MemoryAddress(1), - size: 1, - offset: 3, + size_address: MemoryAddress(3), + offset_address: MemoryAddress(4), }, // Oracles are named 'foreign calls' in brillig brillig::Opcode::ForeignCall { @@ -307,15 +338,17 @@ fn complex_brillig_foreign_call() { let bytes = Program::serialize_program(&program); let expected_serialization: Vec = vec![ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 213, 84, 75, 10, 132, 48, 12, 77, 90, 199, 17, 102, 55, - 39, 24, 152, 57, 64, 199, 19, 120, 23, 113, 167, 232, 210, 227, 107, 49, 98, 124, 22, 92, - 88, 65, 31, 148, 244, 147, 207, 75, 66, 202, 52, 33, 27, 23, 203, 254, 33, 210, 136, 244, - 247, 150, 214, 152, 117, 11, 145, 238, 24, 254, 28, 207, 151, 59, 139, 163, 185, 1, 71, - 123, 2, 71, 82, 253, 191, 96, 191, 99, 246, 37, 106, 253, 108, 96, 126, 18, 154, 230, 43, - 149, 243, 83, 100, 134, 133, 246, 70, 134, 182, 131, 183, 2, 78, 172, 247, 250, 1, 71, 132, - 17, 196, 46, 137, 150, 105, 238, 82, 197, 133, 33, 254, 75, 101, 89, 182, 77, 87, 87, 189, - 5, 85, 164, 251, 85, 251, 31, 188, 51, 216, 161, 173, 134, 254, 192, 66, 186, 28, 208, 219, - 243, 253, 166, 165, 196, 115, 217, 7, 253, 216, 100, 109, 69, 5, 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 213, 85, 81, 14, 194, 48, 8, 133, 118, 206, 26, 255, 60, + 129, 137, 30, 160, 211, 11, 120, 23, 227, 159, 70, 63, 61, 190, 146, 209, 140, 177, 46, + 251, 24, 77, 182, 151, 44, 116, 45, 16, 120, 64, 139, 208, 34, 252, 63, 228, 245, 134, 165, + 99, 73, 251, 30, 250, 72, 186, 55, 150, 113, 30, 26, 180, 243, 21, 75, 197, 232, 86, 16, + 163, 47, 16, 35, 136, 250, 47, 176, 222, 150, 117, 49, 229, 207, 103, 230, 167, 130, 118, + 190, 106, 254, 223, 178, 12, 154, 104, 50, 114, 48, 28, 188, 30, 82, 247, 236, 180, 23, 62, + 171, 236, 178, 185, 202, 27, 194, 216, 119, 36, 54, 142, 35, 185, 149, 203, 233, 18, 131, + 34, 220, 48, 167, 38, 176, 191, 18, 181, 168, 5, 63, 178, 179, 8, 123, 232, 186, 234, 254, + 126, 125, 158, 143, 175, 87, 148, 74, 51, 194, 73, 172, 207, 234, 28, 149, 157, 182, 149, + 144, 15, 70, 78, 23, 51, 122, 83, 190, 15, 208, 181, 70, 122, 152, 126, 56, 83, 244, 10, + 181, 6, 0, 0, ]; assert_eq!(bytes, expected_serialization) diff --git a/acvm-repo/acvm/tests/solver.rs b/acvm-repo/acvm/tests/solver.rs index 2a06e07f092..41e28882993 100644 --- a/acvm-repo/acvm/tests/solver.rs +++ b/acvm-repo/acvm/tests/solver.rs @@ -1,6 +1,7 @@ use std::collections::{BTreeMap, HashSet}; use std::sync::Arc; +use acir::brillig::{BitSize, IntegerBitSize}; use acir::{ acir_field::GenericFieldElement, brillig::{BinaryFieldOp, HeapArray, MemoryAddress, Opcode as BrilligOpcode, ValueOrArray}, @@ -122,10 +123,20 @@ fn inversion_brillig_oracle_equivalence() { let brillig_bytecode = BrilligBytecode { bytecode: vec![ + BrilligOpcode::Const { + destination: MemoryAddress(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(2u64), + }, + BrilligOpcode::Const { + destination: MemoryAddress(1), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, BrilligOpcode::CalldataCopy { destination_address: MemoryAddress(0), - size: 2, - offset: 0, + size_address: MemoryAddress(0), + offset_address: MemoryAddress(1), }, equal_opcode, // Oracles are named 'foreign calls' in brillig @@ -258,10 +269,20 @@ fn double_inversion_brillig_oracle() { let brillig_bytecode = BrilligBytecode { bytecode: vec![ + BrilligOpcode::Const { + destination: MemoryAddress(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(3u64), + }, + BrilligOpcode::Const { + destination: MemoryAddress(1), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, BrilligOpcode::CalldataCopy { destination_address: MemoryAddress(0), - size: 3, - offset: 0, + size_address: MemoryAddress(0), + offset_address: MemoryAddress(1), }, equal_opcode, // Oracles are named 'foreign calls' in brillig @@ -366,12 +387,21 @@ fn oracle_dependent_execution() { let brillig_bytecode = BrilligBytecode { bytecode: vec![ + BrilligOpcode::Const { + destination: MemoryAddress(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(3u64), + }, + BrilligOpcode::Const { + destination: MemoryAddress(1), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, BrilligOpcode::CalldataCopy { destination_address: MemoryAddress(0), - size: 3, - offset: 0, - }, - // Oracles are named 'foreign calls' in brillig + size_address: MemoryAddress(0), + offset_address: MemoryAddress(1), + }, // Oracles are named 'foreign calls' in brillig BrilligOpcode::ForeignCall { function: "invert".into(), destinations: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(1))], @@ -498,10 +528,20 @@ fn brillig_oracle_predicate() { let brillig_bytecode = BrilligBytecode { bytecode: vec![ + BrilligOpcode::Const { + destination: MemoryAddress(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(2u64), + }, + BrilligOpcode::Const { + destination: MemoryAddress(1), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, BrilligOpcode::CalldataCopy { destination_address: MemoryAddress(0), - size: 2, - offset: 0, + size_address: MemoryAddress(0), + offset_address: MemoryAddress(1), }, equal_opcode, // Oracles are named 'foreign calls' in brillig @@ -607,8 +647,11 @@ fn unsatisfied_opcode_resolved_brillig() { let w_y = Witness(5); let w_result = Witness(6); - let calldata_copy_opcode = - BrilligOpcode::CalldataCopy { destination_address: MemoryAddress(0), size: 2, offset: 0 }; + let calldata_copy_opcode = BrilligOpcode::CalldataCopy { + destination_address: MemoryAddress(0), + size_address: MemoryAddress(0), + offset_address: MemoryAddress(1), + }; let equal_opcode = BrilligOpcode::BinaryFieldOp { op: BinaryFieldOp::Equals, @@ -627,7 +670,23 @@ fn unsatisfied_opcode_resolved_brillig() { let stop_opcode = BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 0 }; let brillig_bytecode = BrilligBytecode { - bytecode: vec![calldata_copy_opcode, equal_opcode, jmp_if_opcode, trap_opcode, stop_opcode], + bytecode: vec![ + BrilligOpcode::Const { + destination: MemoryAddress(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(2u64), + }, + BrilligOpcode::Const { + destination: MemoryAddress(1), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, + calldata_copy_opcode, + equal_opcode, + jmp_if_opcode, + trap_opcode, + stop_opcode, + ], }; let opcode_a = Expression { @@ -679,7 +738,7 @@ fn unsatisfied_opcode_resolved_brillig() { ACVMStatus::Failure(OpcodeResolutionError::BrilligFunctionFailed { function_id: BrilligFunctionId(0), payload: None, - call_stack: vec![OpcodeLocation::Brillig { acir_index: 0, brillig_index: 3 }] + call_stack: vec![OpcodeLocation::Brillig { acir_index: 0, brillig_index: 5 }] }), "The first opcode is not satisfiable, expected an error indicating this" ); @@ -1584,7 +1643,7 @@ proptest! { #[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); + let (result, message) = prop_assert_injective(inputs, distinct_inputs, 32, Some(8), keccak256_op); prop_assert!(result, "{}", message); } diff --git a/acvm-repo/acvm_js/test/shared/complex_foreign_call.ts b/acvm-repo/acvm_js/test/shared/complex_foreign_call.ts index 53597ece157..ba26e3d139a 100644 --- a/acvm-repo/acvm_js/test/shared/complex_foreign_call.ts +++ b/acvm-repo/acvm_js/test/shared/complex_foreign_call.ts @@ -2,13 +2,14 @@ import { WitnessMap } from '@noir-lang/acvm_js'; // See `complex_brillig_foreign_call` integration test in `acir/tests/test_program_serialization.rs`. export const bytecode = Uint8Array.from([ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 213, 84, 75, 10, 132, 48, 12, 77, 90, 199, 17, 102, 55, 39, 24, 152, 57, 64, 199, - 19, 120, 23, 113, 167, 232, 210, 227, 107, 49, 98, 124, 22, 92, 88, 65, 31, 148, 244, 147, 207, 75, 66, 202, 52, 33, - 27, 23, 203, 254, 33, 210, 136, 244, 247, 150, 214, 152, 117, 11, 145, 238, 24, 254, 28, 207, 151, 59, 139, 163, 185, - 1, 71, 123, 2, 71, 82, 253, 191, 96, 191, 99, 246, 37, 106, 253, 108, 96, 126, 18, 154, 230, 43, 149, 243, 83, 100, - 134, 133, 246, 70, 134, 182, 131, 183, 2, 78, 172, 247, 250, 1, 71, 132, 17, 196, 46, 137, 150, 105, 238, 82, 197, - 133, 33, 254, 75, 101, 89, 182, 77, 87, 87, 189, 5, 85, 164, 251, 85, 251, 31, 188, 51, 216, 161, 173, 134, 254, 192, - 66, 186, 28, 208, 219, 243, 253, 166, 165, 196, 115, 217, 7, 253, 216, 100, 109, 69, 5, 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 213, 85, 81, 14, 194, 48, 8, 133, 118, 206, 26, 255, 60, 129, 137, 30, 160, 211, + 11, 120, 23, 227, 159, 70, 63, 61, 190, 146, 209, 140, 177, 46, 251, 24, 77, 182, 151, 44, 116, 45, 16, 120, 64, 139, + 208, 34, 252, 63, 228, 245, 134, 165, 99, 73, 251, 30, 250, 72, 186, 55, 150, 113, 30, 26, 180, 243, 21, 75, 197, 232, + 86, 16, 163, 47, 16, 35, 136, 250, 47, 176, 222, 150, 117, 49, 229, 207, 103, 230, 167, 130, 118, 190, 106, 254, 223, + 178, 12, 154, 104, 50, 114, 48, 28, 188, 30, 82, 247, 236, 180, 23, 62, 171, 236, 178, 185, 202, 27, 194, 216, 119, + 36, 54, 142, 35, 185, 149, 203, 233, 18, 131, 34, 220, 48, 167, 38, 176, 191, 18, 181, 168, 5, 63, 178, 179, 8, 123, + 232, 186, 234, 254, 126, 125, 158, 143, 175, 87, 148, 74, 51, 194, 73, 172, 207, 234, 28, 149, 157, 182, 149, 144, 15, + 70, 78, 23, 51, 122, 83, 190, 15, 208, 181, 70, 122, 152, 126, 56, 83, 244, 10, 181, 6, 0, 0, ]); export const initialWitnessMap: WitnessMap = new Map([ [1, '0x0000000000000000000000000000000000000000000000000000000000000001'], diff --git a/acvm-repo/acvm_js/test/shared/foreign_call.ts b/acvm-repo/acvm_js/test/shared/foreign_call.ts index 3500e03776d..498a914cff4 100644 --- a/acvm-repo/acvm_js/test/shared/foreign_call.ts +++ b/acvm-repo/acvm_js/test/shared/foreign_call.ts @@ -2,10 +2,11 @@ import { WitnessMap } from '@noir-lang/acvm_js'; // See `simple_brillig_foreign_call` integration test in `acir/tests/test_program_serialization.rs`. export const bytecode = Uint8Array.from([ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 173, 80, 49, 10, 192, 32, 12, 52, 45, 45, 133, 110, 190, 68, 127, 224, 103, 28, 92, - 28, 68, 124, 191, 130, 9, 4, 137, 46, 122, 16, 46, 119, 7, 33, 9, 168, 142, 175, 21, 96, 255, 32, 147, 230, 32, 207, - 33, 155, 61, 88, 56, 55, 203, 240, 125, 175, 177, 1, 110, 170, 197, 101, 55, 242, 43, 100, 132, 159, 229, 33, 22, 159, - 242, 234, 87, 51, 45, 121, 90, 200, 42, 48, 209, 35, 111, 164, 1, 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 173, 81, 49, 10, 128, 48, 12, 108, 196, 138, 224, 230, 75, 226, 15, 252, 140, 131, + 139, 131, 136, 239, 111, 161, 9, 28, 165, 205, 210, 28, 132, 36, 119, 16, 114, 9, 133, 130, 53, 7, 73, 29, 37, 107, + 143, 80, 238, 148, 204, 99, 56, 200, 111, 22, 227, 190, 83, 93, 16, 146, 193, 112, 22, 225, 34, 168, 205, 142, 174, + 241, 218, 206, 179, 121, 49, 188, 109, 57, 84, 191, 159, 255, 122, 63, 235, 199, 189, 190, 197, 237, 13, 45, 1, 20, + 245, 146, 30, 92, 2, 0, 0, ]); export const initialWitnessMap: WitnessMap = new Map([ [1, '0x0000000000000000000000000000000000000000000000000000000000000005'], diff --git a/acvm-repo/brillig/src/opcodes.rs b/acvm-repo/brillig/src/opcodes.rs index 2054a34d459..ac469cebf87 100644 --- a/acvm-repo/brillig/src/opcodes.rs +++ b/acvm-repo/brillig/src/opcodes.rs @@ -210,8 +210,8 @@ pub enum BrilligOpcode { /// Copies calldata after the offset to the specified address and length CalldataCopy { destination_address: MemoryAddress, - size: usize, - offset: usize, + size_address: MemoryAddress, + offset_address: MemoryAddress, }, /// We don't support dynamic jumps or calls /// See https://github.com/ethereum/aleth/issues/3404 for reasoning diff --git a/acvm-repo/brillig_vm/src/lib.rs b/acvm-repo/brillig_vm/src/lib.rs index 5097ecf4707..2c2ab17230f 100644 --- a/acvm-repo/brillig_vm/src/lib.rs +++ b/acvm-repo/brillig_vm/src/lib.rs @@ -236,8 +236,10 @@ impl<'a, F: AcirField, B: BlackBoxFunctionSolver> VM<'a, F, B> { } self.set_program_counter(*destination) } - Opcode::CalldataCopy { destination_address, size, offset } => { - let values: Vec<_> = self.calldata[*offset..(*offset + size)] + Opcode::CalldataCopy { destination_address, size_address, offset_address } => { + let size = self.memory.read(*size_address).to_usize(); + let offset = self.memory.read(*offset_address).to_usize(); + let values: Vec<_> = self.calldata[offset..(offset + size)] .iter() .map(|value| MemoryValue::new_field(*value)) .collect(); @@ -754,24 +756,17 @@ mod tests { #[test] fn add_single_step_smoke() { - let calldata = vec![FieldElement::from(27u128)]; - - // Add opcode to add the value in address `0` and `1` - // and place the output in address `2` - let calldata_copy = Opcode::CalldataCopy { - destination_address: MemoryAddress::from(0), - size: 1, - offset: 0, - }; + let calldata = vec![]; + + let opcodes = [Opcode::Const { + destination: MemoryAddress(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(27u128), + }]; // Start VM - let opcodes = [calldata_copy]; let mut vm = VM::new(calldata, &opcodes, vec![], &StubbedBlackBoxSolver); - // Process a single VM opcode - // - // After processing a single opcode, we should have - // the vm status as finished since there is only one opcode let status = vm.process_opcode(); assert_eq!(status, VMStatus::Finished { return_data_offset: 0, return_data_size: 0 }); @@ -786,7 +781,6 @@ mod tests { #[test] fn jmpif_opcode() { let mut calldata: Vec = vec![]; - let mut opcodes = vec![]; let lhs = { calldata.push(2u128.into()); @@ -800,21 +794,35 @@ mod tests { let destination = MemoryAddress::from(calldata.len()); - opcodes.push(Opcode::CalldataCopy { - destination_address: MemoryAddress::from(0), - size: 2, - offset: 0, - }); - - opcodes.push(Opcode::BinaryFieldOp { destination, op: BinaryFieldOp::Equals, lhs, rhs }); - opcodes.push(Opcode::Jump { location: 3 }); - opcodes.push(Opcode::JumpIf { condition: destination, location: 4 }); + let opcodes = vec![ + Opcode::Const { + destination: MemoryAddress(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(2u64), + }, + Opcode::Const { + destination: MemoryAddress(1), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, + Opcode::CalldataCopy { + destination_address: MemoryAddress(0), + size_address: MemoryAddress(0), + offset_address: MemoryAddress(1), + }, + Opcode::BinaryFieldOp { destination, op: BinaryFieldOp::Equals, lhs, rhs }, + Opcode::Jump { location: 5 }, + Opcode::JumpIf { condition: destination, location: 6 }, + ]; let mut vm = VM::new(calldata, &opcodes, vec![], &StubbedBlackBoxSolver); let status = vm.process_opcode(); assert_eq!(status, VMStatus::InProgress); - + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::InProgress); + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::InProgress); let status = vm.process_opcode(); assert_eq!(status, VMStatus::InProgress); @@ -832,48 +840,49 @@ mod tests { fn jmpifnot_opcode() { let calldata: Vec = vec![1u128.into(), 2u128.into()]; - let calldata_copy = Opcode::CalldataCopy { - destination_address: MemoryAddress::from(0), - size: 2, - offset: 0, - }; - - let jump_opcode = Opcode::Jump { location: 3 }; - - let trap_opcode = Opcode::Trap { revert_data: HeapArray::default() }; - - let not_equal_cmp_opcode = Opcode::BinaryFieldOp { - op: BinaryFieldOp::Equals, - lhs: MemoryAddress::from(0), - rhs: MemoryAddress::from(1), - destination: MemoryAddress::from(2), - }; - - let jump_if_not_opcode = - Opcode::JumpIfNot { condition: MemoryAddress::from(2), location: 2 }; - - let add_opcode = Opcode::BinaryFieldOp { - op: BinaryFieldOp::Add, - lhs: MemoryAddress::from(0), - rhs: MemoryAddress::from(1), - destination: MemoryAddress::from(2), - }; - - let opcodes = [ - calldata_copy, - jump_opcode, - trap_opcode, - not_equal_cmp_opcode, - jump_if_not_opcode, - add_opcode, + let opcodes = vec![ + Opcode::Const { + destination: MemoryAddress(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(2u64), + }, + Opcode::Const { + destination: MemoryAddress(1), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, + Opcode::CalldataCopy { + destination_address: MemoryAddress(0), + size_address: MemoryAddress(0), + offset_address: MemoryAddress(1), + }, + Opcode::Jump { location: 5 }, + Opcode::Trap { revert_data: HeapArray::default() }, + Opcode::BinaryFieldOp { + op: BinaryFieldOp::Equals, + lhs: MemoryAddress::from(0), + rhs: MemoryAddress::from(1), + destination: MemoryAddress::from(2), + }, + Opcode::JumpIfNot { condition: MemoryAddress::from(2), location: 4 }, + Opcode::BinaryFieldOp { + op: BinaryFieldOp::Add, + lhs: MemoryAddress::from(0), + rhs: MemoryAddress::from(1), + destination: MemoryAddress::from(2), + }, ]; + let mut vm = VM::new(calldata, &opcodes, vec![], &StubbedBlackBoxSolver); + + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::InProgress); + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::InProgress); let status = vm.process_opcode(); assert_eq!(status, VMStatus::InProgress); - let status = vm.process_opcode(); assert_eq!(status, VMStatus::InProgress); - let status = vm.process_opcode(); assert_eq!(status, VMStatus::InProgress); @@ -888,7 +897,7 @@ mod tests { status, VMStatus::Failure { reason: FailureReason::Trap { revert_data_offset: 0, revert_data_size: 0 }, - call_stack: vec![2] + call_stack: vec![4] } ); @@ -903,10 +912,20 @@ mod tests { let calldata: Vec = vec![((2_u128.pow(32)) - 1).into()]; let opcodes = &[ + Opcode::Const { + destination: MemoryAddress(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(1u64), + }, + Opcode::Const { + destination: MemoryAddress(1), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, Opcode::CalldataCopy { - destination_address: MemoryAddress::from(0), - size: 1, - offset: 0, + destination_address: MemoryAddress(0), + size_address: MemoryAddress(0), + offset_address: MemoryAddress(1), }, Opcode::Cast { destination: MemoryAddress::from(1), @@ -919,10 +938,12 @@ mod tests { let status = vm.process_opcode(); assert_eq!(status, VMStatus::InProgress); - let status = vm.process_opcode(); assert_eq!(status, VMStatus::InProgress); - + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::InProgress); + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::InProgress); let status = vm.process_opcode(); assert_eq!(status, VMStatus::Finished { return_data_offset: 1, return_data_size: 1 }); @@ -936,22 +957,34 @@ mod tests { fn mov_opcode() { let calldata: Vec = vec![(1u128).into(), (2u128).into(), (3u128).into()]; - let calldata_copy = Opcode::CalldataCopy { - destination_address: MemoryAddress::from(0), - size: 3, - offset: 0, - }; - - let mov_opcode = - Opcode::Mov { destination: MemoryAddress::from(2), source: MemoryAddress::from(0) }; - - let opcodes = &[calldata_copy, mov_opcode]; + let opcodes = &[ + Opcode::Const { + destination: MemoryAddress(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(3u64), + }, + Opcode::Const { + destination: MemoryAddress(1), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, + Opcode::CalldataCopy { + destination_address: MemoryAddress(0), + size_address: MemoryAddress(0), + offset_address: MemoryAddress(1), + }, + Opcode::Mov { destination: MemoryAddress::from(2), source: MemoryAddress::from(0) }, + ]; let mut vm = VM::new(calldata, opcodes, vec![], &StubbedBlackBoxSolver); let status = vm.process_opcode(); assert_eq!(status, VMStatus::InProgress); - let status = vm.process_opcode(); + assert_eq!(status, VMStatus::InProgress); + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::InProgress); + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::Finished { return_data_offset: 0, return_data_size: 0 }); let VM { memory, .. } = vm; @@ -968,28 +1001,32 @@ mod tests { let calldata: Vec = vec![(0u128).into(), (1u128).into(), (2u128).into(), (3u128).into()]; - let calldata_copy = Opcode::CalldataCopy { - destination_address: MemoryAddress::from(0), - size: 4, - offset: 0, - }; - - let cast_zero = Opcode::Cast { - destination: MemoryAddress::from(0), - source: MemoryAddress::from(0), - bit_size: BitSize::Integer(IntegerBitSize::U1), - }; - - let cast_one = Opcode::Cast { - destination: MemoryAddress::from(1), - source: MemoryAddress::from(1), - bit_size: BitSize::Integer(IntegerBitSize::U1), - }; - let opcodes = &[ - calldata_copy, - cast_zero, - cast_one, + Opcode::Const { + destination: MemoryAddress(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(4u64), + }, + Opcode::Const { + destination: MemoryAddress(1), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, + Opcode::CalldataCopy { + destination_address: MemoryAddress(0), + size_address: MemoryAddress(0), + offset_address: MemoryAddress(1), + }, + Opcode::Cast { + destination: MemoryAddress::from(0), + source: MemoryAddress::from(0), + bit_size: BitSize::Integer(IntegerBitSize::U1), + }, + Opcode::Cast { + destination: MemoryAddress::from(1), + source: MemoryAddress::from(1), + bit_size: BitSize::Integer(IntegerBitSize::U1), + }, Opcode::ConditionalMov { destination: MemoryAddress(4), // Sets 3_u128 to memory address 4 source_a: MemoryAddress(2), @@ -1007,16 +1044,16 @@ mod tests { let status = vm.process_opcode(); assert_eq!(status, VMStatus::InProgress); - let status = vm.process_opcode(); assert_eq!(status, VMStatus::InProgress); - let status = vm.process_opcode(); assert_eq!(status, VMStatus::InProgress); - let status = vm.process_opcode(); assert_eq!(status, VMStatus::InProgress); - + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::InProgress); + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::InProgress); let status = vm.process_opcode(); assert_eq!(status, VMStatus::Finished { return_data_offset: 0, return_data_size: 0 }); @@ -1036,11 +1073,23 @@ mod tests { vec![(2u128).into(), (2u128).into(), (0u128).into(), (5u128).into(), (6u128).into()]; let calldata_size = calldata.len(); - let calldata_copy = Opcode::CalldataCopy { - destination_address: MemoryAddress::from(0), - size: 5, - offset: 0, - }; + let calldata_copy_opcodes = vec![ + Opcode::Const { + destination: MemoryAddress(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(5u64), + }, + Opcode::Const { + destination: MemoryAddress(1), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, + Opcode::CalldataCopy { + destination_address: MemoryAddress(0), + size_address: MemoryAddress(0), + offset_address: MemoryAddress(1), + }, + ]; let cast_opcodes: Vec<_> = (0..calldata_size) .map(|index| Opcode::Cast { @@ -1082,7 +1131,8 @@ mod tests { destination: MemoryAddress::from(2), }; - let opcodes: Vec<_> = std::iter::once(calldata_copy) + let opcodes: Vec<_> = calldata_copy_opcodes + .into_iter() .chain(cast_opcodes) .chain([equal_opcode, not_equal_opcode, less_than_opcode, less_than_equal_opcode]) .collect(); @@ -1091,6 +1141,10 @@ mod tests { // Calldata copy let status = vm.process_opcode(); assert_eq!(status, VMStatus::InProgress); + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::InProgress); + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::InProgress); for _ in 0..calldata_size { let status = vm.process_opcode(); @@ -1242,7 +1296,7 @@ mod tests { let r_tmp = MemoryAddress::from(3); let r_pointer = MemoryAddress::from(4); - let start: [Opcode; 5] = [ + let start = [ // sum = 0 Opcode::Const { destination: r_sum, value: 0u128.into(), bit_size: BitSize::Field }, // i = 0 @@ -1263,10 +1317,20 @@ mod tests { value: 5u128.into(), bit_size: BitSize::Integer(bit_size), }, + Opcode::Const { + destination: MemoryAddress(100), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(memory.len() as u32), + }, + Opcode::Const { + destination: MemoryAddress(101), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, Opcode::CalldataCopy { destination_address: MemoryAddress(5), - size: memory.len(), - offset: 0, + size_address: MemoryAddress(100), + offset_address: MemoryAddress(101), }, ]; let loop_body = [ @@ -1359,8 +1423,8 @@ mod tests { Opcode::Const { destination: r_pointer, value: 4u128.into(), bit_size }, // call recursive_fn Opcode::Call { - location: 5, // Call after 'start' - }, + location: 5, // Call after 'start' + }, // end program by jumping to end Opcode::Jump { location: 100 }, ]; @@ -1510,10 +1574,20 @@ mod tests { vec![(1u128).into(), (3u128).into(), (2u128).into(), (4u128).into()]; let invert_program = vec![ + Opcode::Const { + destination: MemoryAddress(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(initial_matrix.len() as u32), + }, + Opcode::Const { + destination: MemoryAddress(1), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, Opcode::CalldataCopy { - destination_address: MemoryAddress::from(2), - size: initial_matrix.len(), - offset: 0, + destination_address: MemoryAddress(2), + size_address: MemoryAddress(0), + offset_address: MemoryAddress(1), }, // input = 0 Opcode::Const { @@ -1600,10 +1674,20 @@ mod tests { // First call: let string_double_program = vec![ + Opcode::Const { + destination: MemoryAddress(100), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(input_string.len() as u32), + }, + Opcode::Const { + destination: MemoryAddress(101), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, Opcode::CalldataCopy { destination_address: MemoryAddress(4), - size: input_string.len(), - offset: 0, + size_address: MemoryAddress(100), + offset_address: MemoryAddress(101), }, // input_pointer = 4 Opcode::Const { @@ -1698,10 +1782,20 @@ mod tests { vec![(1u128).into(), (3u128).into(), (2u128).into(), (4u128).into()]; let invert_program = vec![ + Opcode::Const { + destination: MemoryAddress(100), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(initial_matrix.len() as u32), + }, + Opcode::Const { + destination: MemoryAddress(101), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, Opcode::CalldataCopy { - destination_address: MemoryAddress::from(2), - size: initial_matrix.len(), - offset: 0, + destination_address: MemoryAddress(2), + size_address: MemoryAddress(100), + offset_address: MemoryAddress(101), }, // input = 0 Opcode::Const { @@ -1797,10 +1891,20 @@ mod tests { vec![(34u128).into(), (37u128).into(), (78u128).into(), (85u128).into()]; let matrix_mul_program = vec![ + Opcode::Const { + destination: MemoryAddress(100), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(matrix_a.len() + matrix_b.len()), + }, + Opcode::Const { + destination: MemoryAddress(101), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, Opcode::CalldataCopy { - destination_address: MemoryAddress::from(3), - size: matrix_a.len() + matrix_b.len(), - offset: 0, + destination_address: MemoryAddress(3), + size_address: MemoryAddress(100), + offset_address: MemoryAddress(101), }, // input = 3 Opcode::Const { @@ -1944,11 +2048,24 @@ mod tests { let r_input = MemoryAddress::from(r_ptr); let r_output = MemoryAddress::from(r_ptr + 1); - let program: Vec<_> = std::iter::once(Opcode::CalldataCopy { - destination_address: MemoryAddress::from(0), - size: memory.len(), - offset: 0, - }) + let program: Vec<_> = vec![ + Opcode::Const { + destination: MemoryAddress(100), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(memory.len()), + }, + Opcode::Const { + destination: MemoryAddress(101), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, + Opcode::CalldataCopy { + destination_address: MemoryAddress(0), + size_address: MemoryAddress(100), + offset_address: MemoryAddress(101), + }, + ] + .into_iter() .chain(memory.iter().enumerate().map(|(index, mem_value)| Opcode::Cast { destination: MemoryAddress(index), source: MemoryAddress(index), diff --git a/aztec_macros/src/lib.rs b/aztec_macros/src/lib.rs index ec1d395725d..4ba8951c2f9 100644 --- a/aztec_macros/src/lib.rs +++ b/aztec_macros/src/lib.rs @@ -66,7 +66,9 @@ fn transform( // Usage -> mut ast -> aztec_library::transform(&mut ast) // Covers all functions in the ast - for submodule in ast.submodules.iter_mut().filter(|submodule| submodule.is_contract) { + for submodule in + ast.submodules.iter_mut().map(|m| &mut m.item).filter(|submodule| submodule.is_contract) + { if transform_module( &file_id, &mut submodule.contents, @@ -111,7 +113,8 @@ fn transform_module( } let has_initializer = module.functions.iter().any(|func| { - func.def + func.item + .def .attributes .secondary .iter() @@ -121,6 +124,7 @@ fn transform_module( let mut stubs: Vec<_> = vec![]; for func in module.functions.iter_mut() { + let func = &mut func.item; let mut is_private = false; let mut is_public = false; let mut is_initializer = false; @@ -175,6 +179,7 @@ fn transform_module( let private_functions: Vec<_> = module .functions .iter() + .map(|t| &t.item) .filter(|func| { func.def .attributes @@ -187,6 +192,7 @@ fn transform_module( let public_functions: Vec<_> = module .functions .iter() + .map(|func| &func.item) .filter(|func| { func.def .attributes diff --git a/aztec_macros/src/transforms/compute_note_hash_and_optionally_a_nullifier.rs b/aztec_macros/src/transforms/compute_note_hash_and_optionally_a_nullifier.rs index 8983266dab9..4d5dcc6f1af 100644 --- a/aztec_macros/src/transforms/compute_note_hash_and_optionally_a_nullifier.rs +++ b/aztec_macros/src/transforms/compute_note_hash_and_optionally_a_nullifier.rs @@ -166,7 +166,7 @@ fn generate_compute_note_hash_and_optionally_a_nullifier( assert_eq!(errors.len(), 0, "Failed to parse Noir macro code. This is either a bug in the compiler or the Noir macro code"); let mut function_ast = function_ast.into_sorted(); - function_ast.functions.remove(0) + function_ast.functions.remove(0).item } fn generate_compute_note_hash_and_optionally_a_nullifier_source( diff --git a/aztec_macros/src/transforms/contract_interface.rs b/aztec_macros/src/transforms/contract_interface.rs index 7a8a7187857..e2de30d6d93 100644 --- a/aztec_macros/src/transforms/contract_interface.rs +++ b/aztec_macros/src/transforms/contract_interface.rs @@ -1,7 +1,7 @@ use acvm::acir::AcirField; use noirc_errors::Location; -use noirc_frontend::ast::{Ident, NoirFunction, UnresolvedTypeData}; +use noirc_frontend::ast::{Documented, Ident, NoirFunction, UnresolvedTypeData}; use noirc_frontend::{ graph::CrateId, macros_api::{FieldElement, FileId, HirContext, HirExpression, HirLiteral, HirStatement}, @@ -267,15 +267,16 @@ pub fn generate_contract_interface( .methods .iter() .enumerate() - .map(|(i, (method, orig_span))| { + .map(|(i, (documented_method, orig_span))| { + let method = &documented_method.item; if method.name() == "at" || method.name() == "interface" || method.name() == "storage" { - (method.clone(), *orig_span) + (documented_method.clone(), *orig_span) } else { let (_, new_location) = stubs[i]; let mut modified_method = method.clone(); modified_method.def.name = Ident::new(modified_method.name().to_string(), new_location.span); - (modified_method, *orig_span) + (Documented::not_documented(modified_method), *orig_span) } }) .collect(); diff --git a/aztec_macros/src/transforms/events.rs b/aztec_macros/src/transforms/events.rs index ede8a350bf2..d753bb43471 100644 --- a/aztec_macros/src/transforms/events.rs +++ b/aztec_macros/src/transforms/events.rs @@ -1,4 +1,4 @@ -use noirc_frontend::ast::{ItemVisibility, NoirFunction, NoirTraitImpl, TraitImplItem}; +use noirc_frontend::ast::{Documented, ItemVisibility, NoirFunction, NoirTraitImpl, TraitImplItem}; use noirc_frontend::macros_api::{NodeInterner, StructId}; use noirc_frontend::token::SecondaryAttribute; use noirc_frontend::{ @@ -34,10 +34,11 @@ pub fn generate_event_impls( // print!("\ngenerate_event_interface_impl COUNT: {}\n", event_struct.name.0.contents); // } - for submodule in module.submodules.iter_mut() { - let annotated_event_structs = submodule.contents.types.iter_mut().filter(|typ| { - typ.attributes.iter().any(|attr| is_custom_attribute(attr, "aztec(event)")) - }); + for submodule in module.submodules.iter_mut().map(|m| &mut m.item) { + let annotated_event_structs = + submodule.contents.types.iter_mut().map(|typ| &mut typ.item).filter(|typ| { + typ.attributes.iter().any(|attr| is_custom_attribute(attr, "aztec(event)")) + }); for event_struct in annotated_event_structs { // event_struct.attributes.push(SecondaryAttribute::Abi("events".to_string())); @@ -52,7 +53,9 @@ pub fn generate_event_impls( let mut event_fields = vec![]; - for (field_ident, field_type) in event_struct.fields.iter() { + for field in event_struct.fields.iter() { + let field_ident = &field.item.name; + let field_type = &field.item.typ; event_fields.push(( field_ident.0.contents.to_string(), field_type.typ.to_string().replace("plain::", ""), @@ -64,18 +67,30 @@ pub fn generate_event_impls( event_byte_len, empty_spans, )?; - event_interface_trait_impl.items.push(TraitImplItem::Function( - generate_fn_get_event_type_id(event_type.as_str(), event_len, empty_spans)?, + event_interface_trait_impl.items.push(Documented::not_documented( + TraitImplItem::Function(generate_fn_get_event_type_id( + event_type.as_str(), + event_len, + empty_spans, + )?), + )); + event_interface_trait_impl.items.push(Documented::not_documented( + TraitImplItem::Function(generate_fn_private_to_be_bytes( + event_type.as_str(), + event_byte_len, + empty_spans, + )?), )); - event_interface_trait_impl.items.push(TraitImplItem::Function( - generate_fn_private_to_be_bytes(event_type.as_str(), event_byte_len, empty_spans)?, + event_interface_trait_impl.items.push(Documented::not_documented( + TraitImplItem::Function(generate_fn_to_be_bytes( + event_type.as_str(), + event_byte_len, + empty_spans, + )?), )); - event_interface_trait_impl.items.push(TraitImplItem::Function( - generate_fn_to_be_bytes(event_type.as_str(), event_byte_len, empty_spans)?, + event_interface_trait_impl.items.push(Documented::not_documented( + TraitImplItem::Function(generate_fn_emit(event_type.as_str(), empty_spans)?), )); - event_interface_trait_impl - .items - .push(TraitImplItem::Function(generate_fn_emit(event_type.as_str(), empty_spans)?)); submodule.contents.trait_impls.push(event_interface_trait_impl); let serialize_trait_impl = generate_trait_impl_serialize( @@ -245,7 +260,7 @@ fn generate_fn_get_event_type_id( } let mut function_ast = function_ast.into_sorted(); - let mut noir_fn = function_ast.functions.remove(0); + let mut noir_fn = function_ast.functions.remove(0).item; noir_fn.def.visibility = ItemVisibility::Public; Ok(noir_fn) } @@ -292,7 +307,7 @@ fn generate_fn_private_to_be_bytes( } let mut function_ast = function_ast.into_sorted(); - let mut noir_fn = function_ast.functions.remove(0); + let mut noir_fn = function_ast.functions.remove(0).item; noir_fn.def.visibility = ItemVisibility::Public; Ok(noir_fn) } @@ -337,7 +352,7 @@ fn generate_fn_to_be_bytes( } let mut function_ast = function_ast.into_sorted(); - let mut noir_fn = function_ast.functions.remove(0); + let mut noir_fn = function_ast.functions.remove(0).item; noir_fn.def.visibility = ItemVisibility::Public; Ok(noir_fn) } @@ -361,7 +376,7 @@ fn generate_fn_emit(event_type: &str, empty_spans: bool) -> Result, + types: &mut Vec>, func: &NoirFunction, empty_spans: bool, ) -> Result<(), AztecMacroError> { diff --git a/aztec_macros/src/transforms/note_interface.rs b/aztec_macros/src/transforms/note_interface.rs index df237926486..6e95efa637c 100644 --- a/aztec_macros/src/transforms/note_interface.rs +++ b/aztec_macros/src/transforms/note_interface.rs @@ -1,7 +1,7 @@ use noirc_errors::Span; use noirc_frontend::ast::{ - ItemVisibility, LetStatement, NoirFunction, NoirStruct, PathKind, TraitImplItem, TypeImpl, - UnresolvedTypeData, UnresolvedTypeExpression, + Documented, ItemVisibility, LetStatement, NoirFunction, NoirStruct, PathKind, StructField, + TraitImplItem, TypeImpl, UnresolvedTypeData, UnresolvedTypeExpression, }; use noirc_frontend::{ graph::CrateId, @@ -35,10 +35,10 @@ pub fn generate_note_interface_impl( empty_spans: bool, ) -> Result<(), AztecMacroError> { // Find structs annotated with #[aztec(note)] - let annotated_note_structs = module - .types - .iter_mut() - .filter(|typ| typ.attributes.iter().any(|attr| is_custom_attribute(attr, "aztec(note)"))); + let annotated_note_structs = + module.types.iter_mut().map(|t| &mut t.item).filter(|typ| { + typ.attributes.iter().any(|attr| is_custom_attribute(attr, "aztec(note)")) + }); let mut structs_to_inject = vec![]; @@ -110,26 +110,28 @@ pub fn generate_note_interface_impl( ); // Automatically inject the header field if it's not present - let (header_field_name, _) = if let Some(existing_header) = - note_struct.fields.iter().find(|(_, field_type)| match &field_type.typ { + let header_field_name = if let Some(existing_header) = + note_struct.fields.iter().find(|field| match &field.item.typ.typ { UnresolvedTypeData::Named(path, _, _) => path.last_name() == "NoteHeader", _ => false, }) { - existing_header.clone() + existing_header.clone().item.name } else { - let generated_header = ( - ident("header"), - make_type(UnresolvedTypeData::Named( + let generated_header = StructField { + name: ident("header"), + typ: make_type(UnresolvedTypeData::Named( chained_dep!("aztec", "note", "note_header", "NoteHeader"), Default::default(), false, )), - ); - note_struct.fields.push(generated_header.clone()); - generated_header + }; + note_struct.fields.push(Documented::not_documented(generated_header.clone())); + generated_header.name }; - for (field_ident, field_type) in note_struct.fields.iter() { + for field in note_struct.fields.iter() { + let field_ident = &field.item.name; + let field_type = &field.item.typ; note_fields.push(( field_ident.0.contents.to_string(), field_type.typ.to_string().replace("plain::", ""), @@ -138,7 +140,10 @@ pub fn generate_note_interface_impl( if !check_trait_method_implemented(trait_impl, "serialize_content") && !check_trait_method_implemented(trait_impl, "deserialize_content") - && !note_impl.methods.iter().any(|(func, _)| func.def.name.0.contents == "properties") + && !note_impl + .methods + .iter() + .any(|(func, _)| func.item.def.name.0.contents == "properties") { let note_serialize_content_fn = generate_note_serialize_content( ¬e_type, @@ -148,7 +153,9 @@ pub fn generate_note_interface_impl( note_interface_impl_span, empty_spans, )?; - trait_impl.items.push(TraitImplItem::Function(note_serialize_content_fn)); + trait_impl.items.push(Documented::not_documented(TraitImplItem::Function( + note_serialize_content_fn, + ))); let note_deserialize_content_fn = generate_note_deserialize_content( ¬e_type, @@ -158,7 +165,9 @@ pub fn generate_note_interface_impl( note_interface_impl_span, empty_spans, )?; - trait_impl.items.push(TraitImplItem::Function(note_deserialize_content_fn)); + trait_impl.items.push(Documented::not_documented(TraitImplItem::Function( + note_deserialize_content_fn, + ))); let note_properties_struct = generate_note_properties_struct( ¬e_type, @@ -167,7 +176,7 @@ pub fn generate_note_interface_impl( note_interface_impl_span, empty_spans, )?; - structs_to_inject.push(note_properties_struct); + structs_to_inject.push(Documented::not_documented(note_properties_struct)); let note_properties_fn = generate_note_properties_fn( ¬e_type, ¬e_fields, @@ -175,7 +184,9 @@ pub fn generate_note_interface_impl( note_interface_impl_span, empty_spans, )?; - note_impl.methods.push((note_properties_fn, note_impl.type_span)); + note_impl + .methods + .push((Documented::not_documented(note_properties_fn), note_impl.type_span)); } if !check_trait_method_implemented(trait_impl, "get_header") { @@ -185,7 +196,9 @@ pub fn generate_note_interface_impl( note_interface_impl_span, empty_spans, )?; - trait_impl.items.push(TraitImplItem::Function(get_header_fn)); + trait_impl + .items + .push(Documented::not_documented(TraitImplItem::Function(get_header_fn))); } if !check_trait_method_implemented(trait_impl, "set_header") { let set_header_fn = generate_note_set_header( @@ -194,14 +207,18 @@ pub fn generate_note_interface_impl( note_interface_impl_span, empty_spans, )?; - trait_impl.items.push(TraitImplItem::Function(set_header_fn)); + trait_impl + .items + .push(Documented::not_documented(TraitImplItem::Function(set_header_fn))); } if !check_trait_method_implemented(trait_impl, "get_note_type_id") { let note_type_id = compute_note_type_id(¬e_type); let get_note_type_id_fn = generate_get_note_type_id(note_type_id, note_interface_impl_span, empty_spans)?; - trait_impl.items.push(TraitImplItem::Function(get_note_type_id_fn)); + trait_impl + .items + .push(Documented::not_documented(TraitImplItem::Function(get_note_type_id_fn))); } if !check_trait_method_implemented(trait_impl, "compute_note_hiding_point") { @@ -210,7 +227,9 @@ pub fn generate_note_interface_impl( note_interface_impl_span, empty_spans, )?; - trait_impl.items.push(TraitImplItem::Function(compute_note_hiding_point_fn)); + trait_impl.items.push(Documented::not_documented(TraitImplItem::Function( + compute_note_hiding_point_fn, + ))); } if !check_trait_method_implemented(trait_impl, "to_be_bytes") { @@ -221,7 +240,9 @@ pub fn generate_note_interface_impl( note_interface_impl_span, empty_spans, )?; - trait_impl.items.push(TraitImplItem::Function(to_be_bytes_fn)); + trait_impl + .items + .push(Documented::not_documented(TraitImplItem::Function(to_be_bytes_fn))); } } @@ -275,7 +296,7 @@ fn generate_note_to_be_bytes( } let mut function_ast = function_ast.into_sorted(); - let mut noir_fn = function_ast.functions.remove(0); + let mut noir_fn = function_ast.functions.remove(0).item; noir_fn.def.span = impl_span; noir_fn.def.visibility = ItemVisibility::Public; Ok(noir_fn) @@ -307,7 +328,7 @@ fn generate_note_get_header( } let mut function_ast = function_ast.into_sorted(); - let mut noir_fn = function_ast.functions.remove(0); + let mut noir_fn = function_ast.functions.remove(0).item; noir_fn.def.span = impl_span; noir_fn.def.visibility = ItemVisibility::Public; Ok(noir_fn) @@ -338,7 +359,7 @@ fn generate_note_set_header( } let mut function_ast = function_ast.into_sorted(); - let mut noir_fn = function_ast.functions.remove(0); + let mut noir_fn = function_ast.functions.remove(0).item; noir_fn.def.span = impl_span; noir_fn.def.visibility = ItemVisibility::Public; Ok(noir_fn) @@ -372,7 +393,7 @@ fn generate_get_note_type_id( } let mut function_ast = function_ast.into_sorted(); - let mut noir_fn = function_ast.functions.remove(0); + let mut noir_fn = function_ast.functions.remove(0).item; noir_fn.def.span = impl_span; noir_fn.def.visibility = ItemVisibility::Public; Ok(noir_fn) @@ -407,7 +428,7 @@ fn generate_note_properties_struct( } let mut struct_ast = struct_ast.into_sorted(); - Ok(struct_ast.types.remove(0)) + Ok(struct_ast.types.remove(0).item) } // Generate the deserialize_content method as @@ -445,7 +466,7 @@ fn generate_note_deserialize_content( } let mut function_ast = function_ast.into_sorted(); - let mut noir_fn = function_ast.functions.remove(0); + let mut noir_fn = function_ast.functions.remove(0).item; noir_fn.def.span = impl_span; noir_fn.def.visibility = ItemVisibility::Public; Ok(noir_fn) @@ -483,7 +504,7 @@ fn generate_note_serialize_content( } let mut function_ast = function_ast.into_sorted(); - let mut noir_fn = function_ast.functions.remove(0); + let mut noir_fn = function_ast.functions.remove(0).item; noir_fn.def.span = impl_span; noir_fn.def.visibility = ItemVisibility::Public; Ok(noir_fn) @@ -508,7 +529,7 @@ fn generate_note_properties_fn( }); } let mut function_ast = function_ast.into_sorted(); - let mut noir_fn = function_ast.functions.remove(0); + let mut noir_fn = function_ast.functions.remove(0).item; noir_fn.def.span = impl_span; noir_fn.def.visibility = ItemVisibility::Public; Ok(noir_fn) @@ -547,7 +568,7 @@ fn generate_compute_note_hiding_point( }); } let mut function_ast = function_ast.into_sorted(); - let mut noir_fn = function_ast.functions.remove(0); + let mut noir_fn = function_ast.functions.remove(0).item; noir_fn.def.span = impl_span; noir_fn.def.visibility = ItemVisibility::Public; Ok(noir_fn) @@ -579,7 +600,7 @@ fn generate_note_exports_global( } let mut global_ast = global_ast.into_sorted(); - Ok(global_ast.globals.pop().unwrap()) + Ok(global_ast.globals.pop().unwrap().item) } // Source code generator functions. These utility methods produce Noir code as strings, that are then parsed and added to the AST. diff --git a/aztec_macros/src/transforms/storage.rs b/aztec_macros/src/transforms/storage.rs index 7dd21f1a8ac..a6bf2e14fb3 100644 --- a/aztec_macros/src/transforms/storage.rs +++ b/aztec_macros/src/transforms/storage.rs @@ -1,8 +1,8 @@ use acvm::acir::AcirField; use noirc_errors::Span; use noirc_frontend::ast::{ - BlockExpression, Expression, ExpressionKind, FunctionDefinition, GenericTypeArgs, Ident, - Literal, NoirFunction, NoirStruct, Pattern, StatementKind, TypeImpl, UnresolvedType, + BlockExpression, Documented, Expression, ExpressionKind, FunctionDefinition, GenericTypeArgs, + Ident, Literal, NoirFunction, NoirStruct, Pattern, StatementKind, TypeImpl, UnresolvedType, UnresolvedTypeData, }; use noirc_frontend::{ @@ -38,6 +38,7 @@ pub fn check_for_storage_definition( let result: Vec<&NoirStruct> = module .types .iter() + .map(|t| &t.item) .filter(|r#struct| { r#struct.attributes.iter().any(|attr| is_custom_attribute(attr, "aztec(storage)")) }) @@ -88,6 +89,7 @@ pub fn inject_context_in_storage(module: &mut SortedModule) -> Result<(), AztecM let storage_struct = module .types .iter_mut() + .map(|t| &mut t.item) .find(|r#struct| { r#struct.attributes.iter().any(|attr| is_custom_attribute(attr, "aztec(storage)")) }) @@ -96,7 +98,7 @@ pub fn inject_context_in_storage(module: &mut SortedModule) -> Result<(), AztecM storage_struct .fields .iter_mut() - .map(|(_, field)| inject_context_in_storage_field(field)) + .map(|field| inject_context_in_storage_field(&mut field.item.typ)) .collect::, _>>()?; Ok(()) } @@ -200,6 +202,7 @@ pub fn generate_storage_implementation( let definition = module .types .iter() + .map(|t| &t.item) .find(|r#struct| r#struct.name.0.contents == *storage_struct_name) .unwrap(); @@ -212,8 +215,10 @@ pub fn generate_storage_implementation( .fields .iter() .flat_map(|field| { - generate_storage_field_constructor(field, slot_zero.clone()) - .map(|expression| (field.0.clone(), expression)) + let ident = &field.item.name; + let typ = &field.item.typ; + generate_storage_field_constructor(&(ident.clone(), typ.clone()), slot_zero.clone()) + .map(|expression| (field.item.name.clone(), expression)) }) .collect(); @@ -249,7 +254,7 @@ pub fn generate_storage_implementation( type_span: Span::default(), generics: vec![generic_context_ident.into()], - methods: vec![(init, Span::default())], + methods: vec![(Documented::not_documented(init), Span::default())], where_clause: vec![], }; @@ -509,13 +514,15 @@ pub fn generate_storage_layout( let definition = module .types .iter() + .map(|t| &t.item) .find(|r#struct| r#struct.name.0.contents == *storage_struct_name) .unwrap(); let mut storable_fields = vec![]; let mut storable_fields_impl = vec![]; - definition.fields.iter().for_each(|(field_ident, _)| { + definition.fields.iter().for_each(|field| { + let field_ident = &field.item.name; storable_fields.push(format!("{}: dep::aztec::prelude::Storable", field_ident)); storable_fields_impl .push(format!("{}: dep::aztec::prelude::Storable {{ slot: 0 }}", field_ident,)); diff --git a/aztec_macros/src/utils/ast_utils.rs b/aztec_macros/src/utils/ast_utils.rs index 316aa60da62..b68946ec020 100644 --- a/aztec_macros/src/utils/ast_utils.rs +++ b/aztec_macros/src/utils/ast_utils.rs @@ -179,7 +179,7 @@ pub fn index_array(array: Ident, index: &str) -> Expression { } pub fn check_trait_method_implemented(trait_impl: &NoirTraitImpl, method_name: &str) -> bool { - trait_impl.items.iter().any(|item| match item { + trait_impl.items.iter().any(|item| match &item.item { TraitImplItem::Function(func) => func.def.name.0.contents == method_name, _ => false, }) diff --git a/aztec_macros/src/utils/hir_utils.rs b/aztec_macros/src/utils/hir_utils.rs index 0a8ce371708..200ce3099cb 100644 --- a/aztec_macros/src/utils/hir_utils.rs +++ b/aztec_macros/src/utils/hir_utils.rs @@ -195,7 +195,7 @@ pub fn inject_fn( let trait_id = None; items.functions.push(UnresolvedFunctions { file_id, functions, trait_id, self_type: None }); - let mut errors = Elaborator::elaborate(context, *crate_id, items, None, false); + let mut errors = Elaborator::elaborate(context, *crate_id, items, None); errors.retain(|(error, _)| !CustomDiagnostic::from(error).is_warning()); if !errors.is_empty() { @@ -241,7 +241,7 @@ pub fn inject_global( let mut items = CollectedItems::default(); items.globals.push(UnresolvedGlobal { file_id, module_id, global_id, stmt_def: global }); - let _errors = Elaborator::elaborate(context, *crate_id, items, None, false); + let _errors = Elaborator::elaborate(context, *crate_id, items, None); } pub fn fully_qualified_note_path(context: &HirContext, note_id: StructId) -> Option { diff --git a/aztec_macros/src/utils/parse_utils.rs b/aztec_macros/src/utils/parse_utils.rs index e7b3e347a96..dce3af1402b 100644 --- a/aztec_macros/src/utils/parse_utils.rs +++ b/aztec_macros/src/utils/parse_utils.rs @@ -64,7 +64,7 @@ fn empty_noir_trait(noir_trait: &mut NoirTrait) { empty_unresolved_generics(&mut noir_trait.generics); empty_unresolved_trait_constraints(&mut noir_trait.where_clause); for item in noir_trait.items.iter_mut() { - empty_trait_item(item); + empty_trait_item(&mut item.item); } } @@ -74,7 +74,7 @@ fn empty_noir_trait_impl(noir_trait_impl: &mut NoirTraitImpl) { empty_unresolved_type(&mut noir_trait_impl.object_type); empty_unresolved_trait_constraints(&mut noir_trait_impl.where_clause); for item in noir_trait_impl.items.iter_mut() { - empty_trait_impl_item(item); + empty_trait_impl_item(&mut item.item); } } @@ -84,7 +84,7 @@ fn empty_type_impl(type_impl: &mut TypeImpl) { empty_unresolved_generics(&mut type_impl.generics); empty_unresolved_trait_constraints(&mut type_impl.where_clause); for (noir_function, _) in type_impl.methods.iter_mut() { - empty_noir_function(noir_function); + empty_noir_function(&mut noir_function.item); } } @@ -187,9 +187,9 @@ fn empty_use_tree(use_tree: &mut UseTree) { fn empty_noir_struct(noir_struct: &mut NoirStruct) { noir_struct.span = Default::default(); empty_ident(&mut noir_struct.name); - for (name, typ) in noir_struct.fields.iter_mut() { - empty_ident(name); - empty_unresolved_type(typ); + for field in noir_struct.fields.iter_mut() { + empty_ident(&mut field.item.name); + empty_unresolved_type(&mut field.item.typ); } empty_unresolved_generics(&mut noir_struct.generics); } @@ -401,6 +401,7 @@ fn empty_pattern(pattern: &mut Pattern) { empty_pattern(pattern); } } + Pattern::Interned(_, _) => (), } } diff --git a/compiler/noirc_driver/src/lib.rs b/compiler/noirc_driver/src/lib.rs index a315e7ed397..18a13517b75 100644 --- a/compiler/noirc_driver/src/lib.rs +++ b/compiler/noirc_driver/src/lib.rs @@ -120,10 +120,6 @@ pub struct CompileOptions { #[arg(long, hide = true)] pub show_artifact_paths: bool, - /// Temporary flag to enable the experimental arithmetic generics feature - #[arg(long, hide = true)] - pub arithmetic_generics: bool, - /// 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. @@ -216,23 +212,25 @@ fn add_debug_source_to_file_manager(file_manager: &mut FileManager) { /// Adds the file from the file system at `Path` to the crate graph as a root file /// -/// Note: This methods adds the stdlib as a dependency to the crate. -/// This assumes that the stdlib has already been added to the file manager. +/// Note: If the stdlib dependency has not been added yet, it's added. Otherwise +/// this method assumes the root crate is the stdlib (useful for running tests +/// in the stdlib, getting LSP stuff for the stdlib, etc.). pub fn prepare_crate(context: &mut Context, file_name: &Path) -> CrateId { let path_to_std_lib_file = Path::new(STD_CRATE_NAME).join("lib.nr"); - let std_file_id = context - .file_manager - .name_to_id(path_to_std_lib_file) - .expect("stdlib file id is expected to be present"); - let std_crate_id = context.crate_graph.add_stdlib(std_file_id); + let std_file_id = context.file_manager.name_to_id(path_to_std_lib_file); + let std_crate_id = std_file_id.map(|std_file_id| context.crate_graph.add_stdlib(std_file_id)); let root_file_id = context.file_manager.name_to_id(file_name.to_path_buf()).unwrap_or_else(|| panic!("files are expected to be added to the FileManager before reaching the compiler file_path: {file_name:?}")); - let root_crate_id = context.crate_graph.add_crate_root(root_file_id); + if let Some(std_crate_id) = std_crate_id { + let root_crate_id = context.crate_graph.add_crate_root(root_file_id); - add_dep(context, root_crate_id, std_crate_id, STD_CRATE_NAME.parse().unwrap()); + add_dep(context, root_crate_id, std_crate_id, STD_CRATE_NAME.parse().unwrap()); - root_crate_id + root_crate_id + } else { + context.crate_graph.add_crate_root_and_stdlib(root_file_id) + } } pub fn link_to_debug_crate(context: &mut Context, root_crate_id: CrateId) { @@ -289,7 +287,6 @@ pub fn check_crate( crate_id, context, options.debug_comptime_in_file.as_deref(), - options.arithmetic_generics, error_on_unused_imports, macros, ); diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs index c17088a5d8c..faf4242a9ca 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs @@ -1,5 +1,5 @@ use acvm::acir::{ - brillig::{BinaryFieldOp, BitSize, MemoryAddress, Opcode as BrilligOpcode}, + brillig::{BinaryFieldOp, BitSize, IntegerBitSize, MemoryAddress, Opcode as BrilligOpcode}, AcirField, }; @@ -19,11 +19,25 @@ pub(crate) fn directive_invert() -> GeneratedBrillig { let zero_const = MemoryAddress::from(2); let input_is_zero = MemoryAddress::from(3); // Location of the stop opcode - let stop_location = 6; + let stop_location = 8; GeneratedBrillig { byte_code: vec![ - BrilligOpcode::CalldataCopy { destination_address: input, size: 1, offset: 0 }, + BrilligOpcode::Const { + destination: MemoryAddress(20), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: F::from(1_usize), + }, + BrilligOpcode::Const { + destination: MemoryAddress::from(21), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: F::from(0_usize), + }, + BrilligOpcode::CalldataCopy { + destination_address: input, + size_address: MemoryAddress::from(20), + offset_address: MemoryAddress::from(21), + }, // Put value zero in register (2) BrilligOpcode::Const { destination: zero_const, @@ -74,10 +88,20 @@ pub(crate) fn directive_quotient() -> GeneratedBrillig { GeneratedBrillig { byte_code: vec![ + BrilligOpcode::Const { + destination: MemoryAddress::from(10), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: F::from(2_usize), + }, + BrilligOpcode::Const { + destination: MemoryAddress::from(11), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: F::from(0_usize), + }, BrilligOpcode::CalldataCopy { destination_address: MemoryAddress::from(0), - size: 2, - offset: 0, + size_address: MemoryAddress::from(10), + offset_address: MemoryAddress::from(11), }, // No cast, since calldata is typed as field by default //q = a/b is set into register (2) diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs index 5ee00cf6c29..c85940cc1c7 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs @@ -46,6 +46,9 @@ impl BrilligContext { arguments: &[BrilligParameter], return_parameters: &[BrilligParameter], ) { + // We need to allocate the variable for every argument first so any register allocation doesn't mangle the expected order. + let mut argument_variables = self.allocate_function_arguments(arguments); + let calldata_size = Self::flattened_tuple_size(arguments); let return_data_size = Self::flattened_tuple_size(return_parameters); @@ -64,75 +67,45 @@ impl BrilligContext { // Copy calldata self.copy_and_cast_calldata(arguments); - // Allocate the variables for every argument: let mut current_calldata_pointer = Self::calldata_start_offset(); - let mut argument_variables: Vec<_> = arguments - .iter() - .map(|argument| match argument { - BrilligParameter::SingleAddr(bit_size) => { - let single_address = self.allocate_register(); - let var = BrilligVariable::SingleAddr(SingleAddrVariable { - address: single_address, - bit_size: *bit_size, - }); - self.mov_instruction(single_address, MemoryAddress(current_calldata_pointer)); - current_calldata_pointer += 1; - var - } - BrilligParameter::Array(_, _) => { - let pointer_to_the_array_in_calldata = - self.make_usize_constant_instruction(current_calldata_pointer.into()); - let rc_register = self.make_usize_constant_instruction(1_usize.into()); - let flattened_size = Self::flattened_size(argument); - let var = BrilligVariable::BrilligArray(BrilligArray { - pointer: pointer_to_the_array_in_calldata.address, - size: flattened_size, - rc: rc_register.address, - }); - - current_calldata_pointer += flattened_size; - var - } - BrilligParameter::Slice(_, _) => { - let pointer_to_the_array_in_calldata = - self.make_usize_constant_instruction(current_calldata_pointer.into()); - - let flattened_size = Self::flattened_size(argument); - let size_register = self.make_usize_constant_instruction(flattened_size.into()); - let rc_register = self.make_usize_constant_instruction(1_usize.into()); - - let var = BrilligVariable::BrilligVector(BrilligVector { - pointer: pointer_to_the_array_in_calldata.address, - size: size_register.address, - rc: rc_register.address, - }); - - current_calldata_pointer += flattened_size; - var - } - }) - .collect(); - - // Deflatten arrays + // Initialize the variables with the calldata for (argument_variable, argument) in argument_variables.iter_mut().zip(arguments) { match (argument_variable, argument) { + (BrilligVariable::SingleAddr(single_address), BrilligParameter::SingleAddr(_)) => { + self.mov_instruction( + single_address.address, + MemoryAddress(current_calldata_pointer), + ); + current_calldata_pointer += 1; + } ( BrilligVariable::BrilligArray(array), BrilligParameter::Array(item_type, item_count), ) => { + let flattened_size = array.size; + self.usize_const_instruction(array.pointer, current_calldata_pointer.into()); + self.usize_const_instruction(array.rc, 1_usize.into()); + + // Deflatten the array let deflattened_address = self.deflatten_array(item_type, array.size, array.pointer); self.mov_instruction(array.pointer, deflattened_address); array.size = item_type.len() * item_count; self.deallocate_register(deflattened_address); + + current_calldata_pointer += flattened_size; } ( BrilligVariable::BrilligVector(vector), BrilligParameter::Slice(item_type, item_count), ) => { let flattened_size = Self::flattened_size(argument); + self.usize_const_instruction(vector.pointer, current_calldata_pointer.into()); + self.usize_const_instruction(vector.rc, 1_usize.into()); + self.usize_const_instruction(vector.size, flattened_size.into()); + // Deflatten the vector let deflattened_address = self.deflatten_array(item_type, flattened_size, vector.pointer); self.mov_instruction(vector.pointer, deflattened_address); @@ -140,14 +113,45 @@ impl BrilligContext { vector.size, (item_type.len() * item_count).into(), ); - self.deallocate_register(deflattened_address); + + current_calldata_pointer += flattened_size; } - _ => {} + _ => unreachable!("ICE: cannot match variables against arguments"), } } } + fn allocate_function_arguments( + &mut self, + arguments: &[BrilligParameter], + ) -> Vec { + arguments + .iter() + .map(|argument| match argument { + BrilligParameter::SingleAddr(bit_size) => { + BrilligVariable::SingleAddr(SingleAddrVariable { + address: self.allocate_register(), + bit_size: *bit_size, + }) + } + BrilligParameter::Array(_, _) => { + let flattened_size = Self::flattened_size(argument); + BrilligVariable::BrilligArray(BrilligArray { + pointer: self.allocate_register(), + size: flattened_size, + rc: self.allocate_register(), + }) + } + BrilligParameter::Slice(_, _) => BrilligVariable::BrilligVector(BrilligVector { + pointer: self.allocate_register(), + size: self.allocate_register(), + rc: self.allocate_register(), + }), + }) + .collect() + } + fn copy_and_cast_calldata(&mut self, arguments: &[BrilligParameter]) { let calldata_size = Self::flattened_tuple_size(arguments); self.calldata_copy_instruction( diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs index afea3f326b0..fab43041e65 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs @@ -394,43 +394,7 @@ impl BrilligContext< constant, bit_size ); - if bit_size > 128 && constant.num_bits() > 128 { - let high = F::from_be_bytes_reduce( - constant.to_be_bytes().get(0..16).expect("FieldElement::to_be_bytes() too short!"), - ); - let low = F::from(constant.to_u128()); - let high_register = SingleAddrVariable::new(self.allocate_register(), 254); - let low_register = SingleAddrVariable::new(self.allocate_register(), 254); - let intermediate_register = SingleAddrVariable::new(self.allocate_register(), 254); - - self.constant(high_register.address, high_register.bit_size, high, false); - self.constant(low_register.address, low_register.bit_size, low, false); - // I want to multiply high by 2^128, but I can't get that big constant in. - // So I'll multiply by 2^64 twice. - self.constant( - intermediate_register.address, - intermediate_register.bit_size, - F::from(1_u128 << 64), - false, - ); - self.binary(high_register, intermediate_register, high_register, BrilligBinaryOp::Mul); - self.binary(high_register, intermediate_register, high_register, BrilligBinaryOp::Mul); - // Now we can add. - self.binary(high_register, low_register, intermediate_register, BrilligBinaryOp::Add); - if indirect { - self.cast( - SingleAddrVariable::new(intermediate_register.address, bit_size), - intermediate_register, - ); - self.store_instruction(result, intermediate_register.address); - } else { - self.cast(SingleAddrVariable::new(result, bit_size), intermediate_register); - } - - self.deallocate_single_addr(high_register); - self.deallocate_single_addr(low_register); - self.deallocate_single_addr(intermediate_register); - } else if indirect { + if indirect { self.push_opcode(BrilligOpcode::IndirectConst { destination_pointer: result, value: constant, @@ -481,11 +445,15 @@ impl BrilligContext< ) { self.debug_show.calldata_copy_instruction(destination, calldata_size, offset); + let size_var = self.make_usize_constant_instruction(calldata_size.into()); + let offset_var = self.make_usize_constant_instruction(offset.into()); self.push_opcode(BrilligOpcode::CalldataCopy { destination_address: destination, - size: calldata_size, - offset, + size_address: size_var.address, + offset_address: offset_var.address, }); + self.deallocate_single_addr(size_var); + self.deallocate_single_addr(offset_var); } pub(super) fn trap_instruction(&mut self, revert_data: HeapArray) { diff --git a/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs b/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs index 89445c4d195..8c128c452d0 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs @@ -181,7 +181,12 @@ impl<'f> PerFunctionContext<'f> { self.last_loads.get(store_address).is_none() }; - if remove_load && !is_reference_param { + let is_reference_alias = block + .expressions + .get(store_address) + .map_or(false, |expression| matches!(expression, Expression::Dereference(_))); + + if remove_load && !is_reference_param && !is_reference_alias { self.instructions_to_remove.insert(*store_instruction); } } @@ -286,19 +291,19 @@ impl<'f> PerFunctionContext<'f> { } else { references.mark_value_used(address, self.inserter.function); - let expression = if let Some(expression) = references.expressions.get(&result) { - expression.clone() - } else { - references.expressions.insert(result, Expression::Other(result)); - Expression::Other(result) - }; - if let Some(aliases) = references.aliases.get_mut(&expression) { + let expression = + references.expressions.entry(result).or_insert(Expression::Other(result)); + // Make sure this load result is marked an alias to itself + if let Some(aliases) = references.aliases.get_mut(expression) { + // If we have an alias set, add to the set aliases.insert(result); } else { + // Otherwise, create a new alias set containing just the load result references .aliases .insert(Expression::Other(result), AliasSet::known(result)); } + // Mark that we know a load result is equivalent to the address of a load. references.set_known_value(result, address); self.last_loads.insert(address, (instruction, block_id)); @@ -789,4 +794,98 @@ mod tests { // We expect the last eq to be optimized out assert_eq!(b1_instructions.len(), 0); } + + #[test] + fn keep_store_to_alias_in_loop_block() { + // This test makes sure the instruction `store Field 2 at v5` in b2 remains after mem2reg. + // Although the only instruction on v5 is a lone store without any loads, + // v5 is an alias of the reference v0 which is stored in v2. + // This test makes sure that we are not inadvertently removing stores to aliases across blocks. + // + // acir(inline) fn main f0 { + // b0(): + // v0 = allocate + // store Field 0 at v0 + // v2 = allocate + // store v0 at v2 + // jmp b1(Field 0) + // b1(v3: Field): + // v4 = eq v3, Field 0 + // jmpif v4 then: b2, else: b3 + // b2(): + // v5 = load v2 + // store Field 2 at v5 + // v8 = add v3, Field 1 + // jmp b1(v8) + // b3(): + // v9 = load v0 + // v10 = eq v9, Field 2 + // constrain v9 == Field 2 + // v11 = load v2 + // v12 = load v10 + // v13 = eq v12, Field 2 + // constrain v11 == Field 2 + // return + // } + let main_id = Id::test_new(0); + let mut builder = FunctionBuilder::new("main".into(), main_id); + + let v0 = builder.insert_allocate(Type::field()); + let zero = builder.numeric_constant(0u128, Type::field()); + builder.insert_store(v0, zero); + + let v2 = builder.insert_allocate(Type::field()); + // Construct alias + builder.insert_store(v2, v0); + let v2_type = builder.current_function.dfg.type_of_value(v2); + assert!(builder.current_function.dfg.value_is_reference(v2)); + + let b1 = builder.insert_block(); + builder.terminate_with_jmp(b1, vec![zero]); + + // Loop header + builder.switch_to_block(b1); + let v3 = builder.add_block_parameter(b1, Type::field()); + let is_zero = builder.insert_binary(v3, BinaryOp::Eq, zero); + + let b2 = builder.insert_block(); + let b3 = builder.insert_block(); + builder.terminate_with_jmpif(is_zero, b2, b3); + + // Loop body + builder.switch_to_block(b2); + let v5 = builder.insert_load(v2, v2_type.clone()); + let two = builder.numeric_constant(2u128, Type::field()); + builder.insert_store(v5, two); + let one = builder.numeric_constant(1u128, Type::field()); + let v3_plus_one = builder.insert_binary(v3, BinaryOp::Add, one); + builder.terminate_with_jmp(b1, vec![v3_plus_one]); + + builder.switch_to_block(b3); + let v9 = builder.insert_load(v0, Type::field()); + let _ = builder.insert_binary(v9, BinaryOp::Eq, two); + + builder.insert_constrain(v9, two, None); + let v11 = builder.insert_load(v2, v2_type); + let v12 = builder.insert_load(v11, Type::field()); + let _ = builder.insert_binary(v12, BinaryOp::Eq, two); + + builder.insert_constrain(v11, two, None); + builder.terminate_with_return(vec![]); + + let ssa = builder.finish(); + + // We expect the same result as above. + let ssa = ssa.mem2reg(); + + let main = ssa.main(); + assert_eq!(main.reachable_blocks().len(), 4); + + // The store from the original SSA should remain + assert_eq!(count_stores(main.entry_block(), &main.dfg), 2); + assert_eq!(count_stores(b2, &main.dfg), 1); + + assert_eq!(count_loads(b2, &main.dfg), 1); + assert_eq!(count_loads(b3, &main.dfg), 3); + } } diff --git a/compiler/noirc_frontend/Cargo.toml b/compiler/noirc_frontend/Cargo.toml index c0f6c8965fb..510cff08dec 100644 --- a/compiler/noirc_frontend/Cargo.toml +++ b/compiler/noirc_frontend/Cargo.toml @@ -31,7 +31,6 @@ cfg-if.workspace = true tracing.workspace = true petgraph = "0.6" rangemap = "1.4.0" -lalrpop-util = { version = "0.20.2", features = ["lexer"] } strum = "0.24" strum_macros = "0.24" @@ -39,9 +38,6 @@ strum_macros = "0.24" [dev-dependencies] base64.workspace = true -[build-dependencies] -lalrpop = "0.20.2" - [features] experimental_parser = [] bn254 = [] diff --git a/compiler/noirc_frontend/build.rs b/compiler/noirc_frontend/build.rs deleted file mode 100644 index eb896a377ae..00000000000 --- a/compiler/noirc_frontend/build.rs +++ /dev/null @@ -1,28 +0,0 @@ -use std::fs::{read_to_string, File}; -use std::io::Write; - -fn main() { - lalrpop::Configuration::new() - .emit_rerun_directives(true) - .use_cargo_dir_conventions() - .process() - .unwrap(); - - // here, we get a lint error from "extern crate core" so patching that until lalrpop does - // (adding cfg directives appears to be unsupported by lalrpop) - let out_dir = std::env::var("OUT_DIR").unwrap(); - let parser_path = std::path::Path::new(&out_dir).join("noir_parser.rs"); - let content_str = read_to_string(parser_path.clone()).unwrap(); - let mut parser_file = File::create(parser_path).unwrap(); - for line in content_str.lines() { - if line.contains("extern crate core") { - parser_file - .write_all( - format!("{}\n", line.replace("extern crate core", "use core")).as_bytes(), - ) - .unwrap(); - } else { - parser_file.write_all(format!("{}\n", line).as_bytes()).unwrap(); - } - } -} diff --git a/compiler/noirc_frontend/src/ast/docs.rs b/compiler/noirc_frontend/src/ast/docs.rs new file mode 100644 index 00000000000..f00f15c215d --- /dev/null +++ b/compiler/noirc_frontend/src/ast/docs.rs @@ -0,0 +1,23 @@ +use std::fmt::Display; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Documented { + pub item: T, + pub doc_comments: Vec, +} + +impl Documented { + pub fn new(item: T, doc_comments: Vec) -> Self { + Self { item, doc_comments } + } + + pub fn not_documented(item: T) -> Self { + Self { item, doc_comments: Vec::new() } + } +} + +impl Display for Documented { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.item) + } +} diff --git a/compiler/noirc_frontend/src/ast/mod.rs b/compiler/noirc_frontend/src/ast/mod.rs index 3fd63249201..12a8aec05eb 100644 --- a/compiler/noirc_frontend/src/ast/mod.rs +++ b/compiler/noirc_frontend/src/ast/mod.rs @@ -4,6 +4,7 @@ //! //! Noir's Ast is produced by the parser and taken as input to name resolution, //! where it is converted into the Hir (defined in the hir_def module). +mod docs; mod expression; mod function; mod statement; @@ -12,11 +13,13 @@ mod traits; mod type_alias; mod visitor; +pub use visitor::AttributeTarget; pub use visitor::Visitor; pub use expression::*; pub use function::*; +pub use docs::*; use noirc_errors::Span; use serde::{Deserialize, Serialize}; pub use statement::*; diff --git a/compiler/noirc_frontend/src/ast/statement.rs b/compiler/noirc_frontend/src/ast/statement.rs index 30db8ad63fd..52c39a49e8a 100644 --- a/compiler/noirc_frontend/src/ast/statement.rs +++ b/compiler/noirc_frontend/src/ast/statement.rs @@ -7,13 +7,13 @@ use iter_extended::vecmap; use noirc_errors::{Span, Spanned}; use super::{ - BlockExpression, Expression, ExpressionKind, GenericTypeArgs, IndexExpression, ItemVisibility, - MemberAccessExpression, MethodCallExpression, UnresolvedType, + BlockExpression, ConstructorExpression, Expression, ExpressionKind, GenericTypeArgs, + IndexExpression, ItemVisibility, 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::macros_api::{NodeInterner, SecondaryAttribute, UnresolvedTypeData}; +use crate::node_interner::{InternedExpressionKind, InternedPattern, InternedStatementKind}; use crate::parser::{ParserError, ParserErrorReason}; use crate::token::Token; @@ -565,6 +565,7 @@ pub enum Pattern { Mutable(Box, Span, /*is_synthesized*/ bool), Tuple(Vec, Span), Struct(Path, Vec<(Ident, Pattern)>, Span), + Interned(InternedPattern, Span), } impl Pattern { @@ -577,7 +578,8 @@ impl Pattern { Pattern::Identifier(ident) => ident.span(), Pattern::Mutable(_, span, _) | Pattern::Tuple(_, span) - | Pattern::Struct(_, _, span) => *span, + | Pattern::Struct(_, _, span) + | Pattern::Interned(_, span) => *span, } } pub fn name_ident(&self) -> &Ident { @@ -595,6 +597,39 @@ impl Pattern { other => panic!("Pattern::into_ident called on {other} pattern with no identifier"), } } + + pub(crate) fn try_as_expression(&self, interner: &NodeInterner) -> Option { + match self { + Pattern::Identifier(ident) => Some(Expression { + kind: ExpressionKind::Variable(Path::from_ident(ident.clone())), + span: ident.span(), + }), + Pattern::Mutable(_, _, _) => None, + Pattern::Tuple(patterns, span) => { + let mut expressions = Vec::new(); + for pattern in patterns { + expressions.push(pattern.try_as_expression(interner)?); + } + Some(Expression { kind: ExpressionKind::Tuple(expressions), span: *span }) + } + Pattern::Struct(path, patterns, span) => { + let mut fields = Vec::new(); + for (field, pattern) in patterns { + let expression = pattern.try_as_expression(interner)?; + fields.push((field.clone(), expression)); + } + Some(Expression { + kind: ExpressionKind::Constructor(Box::new(ConstructorExpression { + type_name: path.clone(), + fields, + struct_type: None, + })), + span: *span, + }) + } + Pattern::Interned(id, _) => interner.get_pattern(*id).try_as_expression(interner), + } + } } impl Recoverable for Pattern { @@ -905,6 +940,9 @@ impl Display for Pattern { let fields = vecmap(fields, |(name, pattern)| format!("{name}: {pattern}")); write!(f, "{} {{ {} }}", typename, fields.join(", ")) } + Pattern::Interned(_, _) => { + write!(f, "?Interned") + } } } } diff --git a/compiler/noirc_frontend/src/ast/structure.rs b/compiler/noirc_frontend/src/ast/structure.rs index 732cbee9232..cd42abb29c7 100644 --- a/compiler/noirc_frontend/src/ast/structure.rs +++ b/compiler/noirc_frontend/src/ast/structure.rs @@ -6,16 +6,24 @@ use crate::token::SecondaryAttribute; use iter_extended::vecmap; use noirc_errors::Span; +use super::Documented; + /// Ast node for a struct #[derive(Clone, Debug, PartialEq, Eq)] pub struct NoirStruct { pub name: Ident, pub attributes: Vec, pub generics: UnresolvedGenerics, - pub fields: Vec<(Ident, UnresolvedType)>, + pub fields: Vec>, pub span: Span, } +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct StructField { + pub name: Ident, + pub typ: UnresolvedType, +} + impl Display for NoirStruct { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let generics = vecmap(&self.generics, |generic| generic.to_string()); @@ -23,8 +31,8 @@ impl Display for NoirStruct { writeln!(f, "struct {}{} {{", self.name, generics)?; - for (name, typ) in self.fields.iter() { - writeln!(f, " {name}: {typ},")?; + for field in self.fields.iter() { + writeln!(f, " {}: {},", field.item.name, field.item.typ)?; } write!(f, "}}") diff --git a/compiler/noirc_frontend/src/ast/traits.rs b/compiler/noirc_frontend/src/ast/traits.rs index e3221f287d3..0463a5b8392 100644 --- a/compiler/noirc_frontend/src/ast/traits.rs +++ b/compiler/noirc_frontend/src/ast/traits.rs @@ -10,7 +10,7 @@ use crate::ast::{ use crate::macros_api::SecondaryAttribute; use crate::node_interner::TraitId; -use super::GenericTypeArgs; +use super::{Documented, GenericTypeArgs}; /// AST node for trait definitions: /// `trait name { ... items ... }` @@ -20,7 +20,7 @@ pub struct NoirTrait { pub generics: UnresolvedGenerics, pub where_clause: Vec, pub span: Span, - pub items: Vec, + pub items: Vec>, pub attributes: Vec, } @@ -54,7 +54,7 @@ pub struct TypeImpl { pub type_span: Span, pub generics: UnresolvedGenerics, pub where_clause: Vec, - pub methods: Vec<(NoirFunction, Span)>, + pub methods: Vec<(Documented, Span)>, } /// Ast node for an implementation of a trait for a particular type @@ -71,7 +71,7 @@ pub struct NoirTraitImpl { pub where_clause: Vec, - pub items: Vec, + pub items: Vec>, } /// Represents a simple trait constraint such as `where Foo: TraitY` diff --git a/compiler/noirc_frontend/src/ast/visitor.rs b/compiler/noirc_frontend/src/ast/visitor.rs index 0aeeed39dd0..fcb4e4c5dd1 100644 --- a/compiler/noirc_frontend/src/ast/visitor.rs +++ b/compiler/noirc_frontend/src/ast/visitor.rs @@ -12,11 +12,11 @@ use crate::{ UseTreeKind, }, node_interner::{ - ExprId, InternedExpressionKind, InternedStatementKind, InternedUnresolvedTypeData, - QuotedTypeId, + ExprId, InternedExpressionKind, InternedPattern, InternedStatementKind, + InternedUnresolvedTypeData, QuotedTypeId, }, parser::{Item, ItemKind, ParsedSubModule}, - token::{SecondaryAttribute, Tokens}, + token::{CustomAtrribute, SecondaryAttribute, Tokens}, ParsedModule, QuotedType, }; @@ -26,6 +26,14 @@ use super::{ UnresolvedTypeExpression, }; +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum AttributeTarget { + Module, + Struct, + Trait, + Function, +} + /// Implements the [Visitor pattern](https://en.wikipedia.org/wiki/Visitor_pattern) for Noir's AST. /// /// In this implementation, methods must return a bool: @@ -433,7 +441,17 @@ pub trait Visitor { true } - fn visit_secondary_attribute(&mut self, _: &SecondaryAttribute, _: Span) {} + fn visit_interned_pattern(&mut self, _: &InternedPattern, _: Span) {} + + fn visit_secondary_attribute( + &mut self, + _: &SecondaryAttribute, + _target: AttributeTarget, + ) -> bool { + true + } + + fn visit_custom_attribute(&mut self, _: &CustomAtrribute, _target: AttributeTarget) {} } impl ParsedModule { @@ -484,7 +502,7 @@ impl Item { module_declaration.accept(self.span, visitor); } ItemKind::InnerAttribute(attribute) => { - attribute.accept(self.span, visitor); + attribute.accept(AttributeTarget::Module, visitor); } } } @@ -492,6 +510,10 @@ impl Item { impl ParsedSubModule { pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { + for attribute in &self.outer_attributes { + attribute.accept(AttributeTarget::Module, visitor); + } + if visitor.visit_parsed_submodule(self, span) { self.accept_children(visitor); } @@ -510,6 +532,10 @@ impl NoirFunction { } pub fn accept_children(&self, visitor: &mut impl Visitor) { + for attribute in self.secondary_attributes() { + attribute.accept(AttributeTarget::Function, visitor); + } + for param in &self.def.parameters { param.typ.accept(visitor); } @@ -530,7 +556,7 @@ impl NoirTraitImpl { self.object_type.accept(visitor); for item in &self.items { - item.accept(visitor); + item.item.accept(visitor); } } } @@ -579,7 +605,7 @@ impl TypeImpl { self.object_type.accept(visitor); for (method, span) in &self.methods { - method.accept(*span, visitor); + method.item.accept(*span, visitor); } } } @@ -592,8 +618,12 @@ impl NoirTrait { } pub fn accept_children(&self, visitor: &mut impl Visitor) { + for attribute in &self.attributes { + attribute.accept(AttributeTarget::Trait, visitor); + } + for item in &self.items { - item.accept(visitor); + item.item.accept(visitor); } } } @@ -674,8 +704,12 @@ impl NoirStruct { } pub fn accept_children(&self, visitor: &mut impl Visitor) { - for (_name, unresolved_type) in &self.fields { - unresolved_type.accept(visitor); + for attribute in &self.attributes { + attribute.accept(AttributeTarget::Struct, visitor); + } + + for field in &self.fields { + field.item.typ.accept(visitor); } } } @@ -694,6 +728,10 @@ impl NoirTypeAlias { impl ModuleDeclaration { pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { + for attribute in &self.outer_attributes { + attribute.accept(AttributeTarget::Module, visitor); + } + visitor.visit_module_declaration(self, span); } } @@ -1290,13 +1328,30 @@ impl Pattern { } } } + Pattern::Interned(id, span) => { + visitor.visit_interned_pattern(id, *span); + } } } } impl SecondaryAttribute { - pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { - visitor.visit_secondary_attribute(self, span); + pub fn accept(&self, target: AttributeTarget, visitor: &mut impl Visitor) { + if visitor.visit_secondary_attribute(self, target) { + self.accept_children(target, visitor); + } + } + + pub fn accept_children(&self, target: AttributeTarget, visitor: &mut impl Visitor) { + if let SecondaryAttribute::Custom(custom) = self { + custom.accept(target, visitor); + } + } +} + +impl CustomAtrribute { + pub fn accept(&self, target: AttributeTarget, visitor: &mut impl Visitor) { + visitor.visit_custom_attribute(self, target); } } diff --git a/compiler/noirc_frontend/src/debug/mod.rs b/compiler/noirc_frontend/src/debug/mod.rs index 951c5220707..ed9265536f9 100644 --- a/compiler/noirc_frontend/src/debug/mod.rs +++ b/compiler/noirc_frontend/src/debug/mod.rs @@ -675,6 +675,7 @@ fn pattern_vars(pattern: &ast::Pattern) -> Vec<(ast::Ident, bool)> { stack.extend(pids.iter().map(|(_, pattern)| (pattern, is_mut))); vars.extend(pids.iter().map(|(id, _)| (id.clone(), false))); } + ast::Pattern::Interned(_, _) => (), } } vars @@ -701,6 +702,7 @@ fn pattern_to_string(pattern: &ast::Pattern) -> String { .join(", "), ) } + ast::Pattern::Interned(_, _) => "?Interned".to_string(), } } diff --git a/compiler/noirc_frontend/src/elaborator/comptime.rs b/compiler/noirc_frontend/src/elaborator/comptime.rs index e9650a625e8..c2347adcbee 100644 --- a/compiler/noirc_frontend/src/elaborator/comptime.rs +++ b/compiler/noirc_frontend/src/elaborator/comptime.rs @@ -6,6 +6,7 @@ use iter_extended::vecmap; use noirc_errors::{Location, Span}; use crate::{ + ast::Documented, hir::{ comptime::{Interpreter, InterpreterError, Value}, def_collector::{ @@ -24,7 +25,7 @@ use crate::{ Expression, ExpressionKind, HirExpression, NodeInterner, SecondaryAttribute, StructId, }, node_interner::{DefinitionKind, DependencyId, FuncId, TraitId}, - parser::{self, TopLevelStatement}, + parser::{self, TopLevelStatement, TopLevelStatementKind}, Type, TypeBindings, UnificationError, }; @@ -52,10 +53,41 @@ impl<'context> Elaborator<'context> { /// Elaborate an expression from the middle of a comptime scope. /// When this happens we require additional information to know /// what variables should be in scope. - pub fn elaborate_item_from_comptime<'a, T>( + pub fn elaborate_item_from_comptime_in_function<'a, T>( &'a mut self, current_function: Option, f: impl FnOnce(&mut Elaborator<'a>) -> T, + ) -> T { + self.elaborate_item_from_comptime(f, |elaborator| { + if let Some(function) = current_function { + let meta = elaborator.interner.function_meta(&function); + elaborator.current_item = Some(DependencyId::Function(function)); + elaborator.crate_id = meta.source_crate; + elaborator.local_module = meta.source_module; + elaborator.file = meta.source_file; + elaborator.introduce_generics_into_scope(meta.all_generics.clone()); + } + }) + } + + pub fn elaborate_item_from_comptime_in_module<'a, T>( + &'a mut self, + module: ModuleId, + file: FileId, + f: impl FnOnce(&mut Elaborator<'a>) -> T, + ) -> T { + self.elaborate_item_from_comptime(f, |elaborator| { + elaborator.current_item = None; + elaborator.crate_id = module.krate; + elaborator.local_module = module.local_id; + elaborator.file = file; + }) + } + + fn elaborate_item_from_comptime<'a, T>( + &'a mut self, + f: impl FnOnce(&mut Elaborator<'a>) -> T, + setup: impl FnOnce(&mut Elaborator<'a>), ) -> T { // Create a fresh elaborator to ensure no state is changed from // this elaborator @@ -64,21 +96,13 @@ impl<'context> Elaborator<'context> { self.def_maps, self.crate_id, self.debug_comptime_in_file, - self.enable_arithmetic_generics, self.interpreter_call_stack.clone(), ); elaborator.function_context.push(FunctionContext::default()); elaborator.scopes.start_function(); - if let Some(function) = current_function { - let meta = elaborator.interner.function_meta(&function); - elaborator.current_item = Some(DependencyId::Function(function)); - elaborator.crate_id = meta.source_crate; - elaborator.local_module = meta.source_module; - elaborator.file = meta.source_file; - elaborator.introduce_generics_into_scope(meta.all_generics.clone()); - } + setup(&mut elaborator); elaborator.populate_scope_from_comptime_scopes(); @@ -135,16 +159,18 @@ impl<'context> Elaborator<'context> { generated_items: &mut CollectedItems, ) { if let SecondaryAttribute::Custom(attribute) = attribute { - if let Err(error) = self.run_comptime_attribute_name_on_item( - &attribute.contents, - item.clone(), - span, - attribute.contents_span, - attribute_context, - generated_items, - ) { - self.errors.push(error); - } + self.elaborate_in_comptime_context(|this| { + if let Err(error) = this.run_comptime_attribute_name_on_item( + &attribute.contents, + item.clone(), + span, + attribute.contents_span, + attribute_context, + generated_items, + ) { + this.errors.push(error); + } + }); } } @@ -352,14 +378,14 @@ impl<'context> Elaborator<'context> { } } - fn add_item( + pub(crate) fn add_item( &mut self, item: TopLevelStatement, generated_items: &mut CollectedItems, location: Location, ) { - match item { - TopLevelStatement::Function(function) => { + match item.kind { + TopLevelStatementKind::Function(function) => { let module_id = self.module_id(); if let Some(id) = dc_mod::collect_function( @@ -368,6 +394,7 @@ impl<'context> Elaborator<'context> { &function, module_id, self.file, + item.doc_comments, &mut self.errors, ) { let functions = vec![(self.local_module, id, function)]; @@ -379,7 +406,7 @@ impl<'context> Elaborator<'context> { }); } } - TopLevelStatement::TraitImpl(mut trait_impl) => { + TopLevelStatementKind::TraitImpl(mut trait_impl) => { let (methods, associated_types, associated_constants) = dc_mod::collect_trait_impl_items( self.interner, @@ -409,11 +436,11 @@ impl<'context> Elaborator<'context> { resolved_trait_generics: Vec::new(), }); } - TopLevelStatement::Global(global) => { + TopLevelStatementKind::Global(global) => { let (global, error) = dc_mod::collect_global( self.interner, self.def_maps.get_mut(&self.crate_id).unwrap(), - global, + Documented::new(global, item.doc_comments), self.file, self.local_module, self.crate_id, @@ -424,11 +451,11 @@ impl<'context> Elaborator<'context> { self.errors.push(error); } } - TopLevelStatement::Struct(struct_def) => { + TopLevelStatementKind::Struct(struct_def) => { if let Some((type_id, the_struct)) = dc_mod::collect_struct( self.interner, self.def_maps.get_mut(&self.crate_id).unwrap(), - struct_def, + Documented::new(struct_def, item.doc_comments), self.file, self.local_module, self.crate_id, @@ -437,21 +464,21 @@ impl<'context> Elaborator<'context> { generated_items.types.insert(type_id, the_struct); } } - TopLevelStatement::Impl(r#impl) => { + TopLevelStatementKind::Impl(r#impl) => { let module = self.module_id(); dc_mod::collect_impl(self.interner, generated_items, r#impl, self.file, module); } // Assume that an error has already been issued - TopLevelStatement::Error => (), - - TopLevelStatement::Module(_) - | TopLevelStatement::Import(..) - | TopLevelStatement::Trait(_) - | TopLevelStatement::TypeAlias(_) - | TopLevelStatement::SubModule(_) - | TopLevelStatement::InnerAttribute(_) => { - let item = item.to_string(); + TopLevelStatementKind::Error => (), + + TopLevelStatementKind::Module(_) + | TopLevelStatementKind::Import(..) + | TopLevelStatementKind::Trait(_) + | TopLevelStatementKind::TypeAlias(_) + | TopLevelStatementKind::SubModule(_) + | TopLevelStatementKind::InnerAttribute(_) => { + let item = item.kind.to_string(); let error = InterpreterError::UnsupportedTopLevelItemUnquote { item, location }; self.errors.push(error.into_compilation_error_pair()); } @@ -574,4 +601,34 @@ impl<'context> Elaborator<'context> { } } } + + /// Perform the given function in a comptime context. + /// This will set the `in_comptime_context` flag on `self` as well as + /// push a new function context to resolve any trait constraints early. + pub(super) fn elaborate_in_comptime_context(&mut self, f: impl FnOnce(&mut Self) -> T) -> T { + let old_comptime_value = std::mem::replace(&mut self.in_comptime_context, true); + // We have to push a new FunctionContext so that we can resolve any constraints + // in this comptime block early before the function as a whole finishes elaborating. + // Otherwise the interpreter below may find expressions for which the underlying trait + // call is not yet solved for. + self.function_context.push(Default::default()); + + let result = f(self); + + self.check_and_pop_function_context(); + self.in_comptime_context = old_comptime_value; + result + } + + /// True if we're currently within a `comptime` block, function, or global + pub(super) fn in_comptime_context(&self) -> bool { + self.in_comptime_context + || match self.current_item { + Some(DependencyId::Function(id)) => { + self.interner.function_modifiers(&id).is_comptime + } + Some(DependencyId::Global(id)) => self.interner.get_global_definition(id).comptime, + _ => false, + } + } } diff --git a/compiler/noirc_frontend/src/elaborator/expressions.rs b/compiler/noirc_frontend/src/elaborator/expressions.rs index beede7a3a30..15d6ed5506b 100644 --- a/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -54,7 +54,7 @@ impl<'context> Elaborator<'context> { ExpressionKind::Tuple(tuple) => self.elaborate_tuple(tuple), ExpressionKind::Lambda(lambda) => self.elaborate_lambda(*lambda), ExpressionKind::Parenthesized(expr) => return self.elaborate_expression(*expr), - ExpressionKind::Quote(quote) => self.elaborate_quote(quote), + ExpressionKind::Quote(quote) => self.elaborate_quote(quote, expr.span), ExpressionKind::Comptime(comptime, _) => { return self.elaborate_comptime_block(comptime, expr.span) } @@ -307,7 +307,13 @@ impl<'context> Elaborator<'context> { let mut arguments = Vec::with_capacity(call.arguments.len()); let args = vecmap(call.arguments, |arg| { let span = arg.span; - let (arg, typ) = self.elaborate_expression(arg); + + let (arg, typ) = if call.is_macro_call { + self.elaborate_in_comptime_context(|this| this.elaborate_expression(arg)) + } else { + self.elaborate_expression(arg) + }; + arguments.push(arg); (typ, arg, span) }); @@ -735,20 +741,21 @@ impl<'context> Elaborator<'context> { (expr, Type::Function(arg_types, Box::new(body_type), Box::new(env_type), false)) } - fn elaborate_quote(&mut self, mut tokens: Tokens) -> (HirExpression, Type) { + fn elaborate_quote(&mut self, mut tokens: Tokens, span: Span) -> (HirExpression, Type) { tokens = self.find_unquoted_exprs_tokens(tokens); - (HirExpression::Quote(tokens), Type::Quoted(QuotedType::Quoted)) + + if self.in_comptime_context() { + (HirExpression::Quote(tokens), Type::Quoted(QuotedType::Quoted)) + } else { + self.push_err(ResolverError::QuoteInRuntimeCode { span }); + (HirExpression::Error, Type::Quoted(QuotedType::Quoted)) + } } fn elaborate_comptime_block(&mut self, block: BlockExpression, span: Span) -> (ExprId, Type) { - // We have to push a new FunctionContext so that we can resolve any constraints - // in this comptime block early before the function as a whole finishes elaborating. - // Otherwise the interpreter below may find expressions for which the underlying trait - // call is not yet solved for. - self.function_context.push(Default::default()); - let (block, _typ) = self.elaborate_block_expression(block); - - self.check_and_pop_function_context(); + let (block, _typ) = + self.elaborate_in_comptime_context(|this| this.elaborate_block_expression(block)); + let mut interpreter = self.setup_interpreter(); let value = interpreter.evaluate_block(block); let (id, typ) = self.inline_comptime_value(value, span); diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 6b23336b5ea..6871152edb5 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -158,6 +158,10 @@ pub struct Elaborator<'context> { /// Initially empty, it is set whenever a new top-level item is resolved. local_module: LocalModuleId, + /// True if we're elaborating a comptime item such as a comptime function, + /// block, global, or attribute. + in_comptime_context: bool, + crate_id: CrateId, /// The scope of --debug-comptime, or None if unset @@ -168,9 +172,6 @@ pub struct Elaborator<'context> { /// they are elaborated (e.g. in a function's type or another global's RHS). unresolved_globals: BTreeMap, - /// Temporary flag to enable the experimental arithmetic generics feature - enable_arithmetic_generics: bool, - pub(crate) interpreter_call_stack: im::Vector, } @@ -194,7 +195,6 @@ impl<'context> Elaborator<'context> { def_maps: &'context mut DefMaps, crate_id: CrateId, debug_comptime_in_file: Option, - enable_arithmetic_generics: bool, interpreter_call_stack: im::Vector, ) -> Self { Self { @@ -217,9 +217,9 @@ impl<'context> Elaborator<'context> { current_trait_impl: None, debug_comptime_in_file, unresolved_globals: BTreeMap::new(), - enable_arithmetic_generics, current_trait: None, interpreter_call_stack, + in_comptime_context: false, } } @@ -227,14 +227,12 @@ impl<'context> Elaborator<'context> { context: &'context mut Context, crate_id: CrateId, debug_comptime_in_file: Option, - enable_arithmetic_generics: bool, ) -> Self { Self::new( &mut context.def_interner, &mut context.def_maps, crate_id, debug_comptime_in_file, - enable_arithmetic_generics, im::Vector::new(), ) } @@ -244,16 +242,8 @@ impl<'context> Elaborator<'context> { crate_id: CrateId, items: CollectedItems, debug_comptime_in_file: Option, - enable_arithmetic_generics: bool, ) -> Vec<(CompilationError, FileId)> { - Self::elaborate_and_return_self( - context, - crate_id, - items, - debug_comptime_in_file, - enable_arithmetic_generics, - ) - .errors + Self::elaborate_and_return_self(context, crate_id, items, debug_comptime_in_file).errors } pub fn elaborate_and_return_self( @@ -261,20 +251,14 @@ impl<'context> Elaborator<'context> { crate_id: CrateId, items: CollectedItems, debug_comptime_in_file: Option, - enable_arithmetic_generics: bool, ) -> Self { - let mut this = Self::from_context( - context, - crate_id, - debug_comptime_in_file, - enable_arithmetic_generics, - ); + let mut this = Self::from_context(context, crate_id, debug_comptime_in_file); this.elaborate_items(items); this.check_and_pop_function_context(); this } - fn elaborate_items(&mut self, mut items: CollectedItems) { + pub(crate) fn elaborate_items(&mut self, mut items: CollectedItems) { // We must first resolve and intern the globals before we can resolve any stmts inside each function. // Each function uses its own resolver with a newly created ScopeForest, and must be resolved again to be within a function's scope // @@ -1269,7 +1253,9 @@ impl<'context> Elaborator<'context> { let struct_def = this.interner.get_struct(struct_id); this.add_existing_generics(&unresolved.generics, &struct_def.borrow().generics); - let fields = vecmap(&unresolved.fields, |(ident, typ)| { + let fields = vecmap(&unresolved.fields, |field| { + let ident = &field.item.name; + let typ = &field.item.typ; (ident.clone(), this.resolve_type(typ.clone())) }); @@ -1308,7 +1294,12 @@ impl<'context> Elaborator<'context> { let comptime = let_stmt.comptime; - let (let_statement, _typ) = self.elaborate_let(let_stmt, Some(global_id)); + let (let_statement, _typ) = if comptime { + self.elaborate_in_comptime_context(|this| this.elaborate_let(let_stmt, Some(global_id))) + } else { + self.elaborate_let(let_stmt, Some(global_id)) + }; + let statement_id = self.interner.get_global(global_id).let_statement; self.interner.replace_statement(statement_id, let_statement); @@ -1343,9 +1334,7 @@ impl<'context> Elaborator<'context> { .lookup_id(definition_id, location) .expect("The global should be defined since evaluate_let did not error"); - self.debug_comptime(location, |interner| { - interner.get_global(global_id).let_statement.to_display_ast(interner).kind - }); + self.debug_comptime(location, |interner| value.display(interner).to_string()); self.interner.get_global_mut(global_id).value = Some(value); } @@ -1442,21 +1431,6 @@ impl<'context> Elaborator<'context> { } } - /// True if we're currently within a `comptime` block, function, or global - fn in_comptime_context(&self) -> bool { - // The first context is the global context, followed by the function-specific context. - // Any context after that is a `comptime {}` block's. - if self.function_context.len() > 2 { - return true; - } - - match self.current_item { - Some(DependencyId::Function(id)) => self.interner.function_modifiers(&id).is_comptime, - Some(DependencyId::Global(id)) => self.interner.get_global_definition(id).comptime, - _ => false, - } - } - /// True if we're currently within a constrained function. /// Defaults to `true` if the current function is unknown. fn in_constrained_function(&self) -> bool { diff --git a/compiler/noirc_frontend/src/elaborator/patterns.rs b/compiler/noirc_frontend/src/elaborator/patterns.rs index 06c153d4c10..f738657fd23 100644 --- a/compiler/noirc_frontend/src/elaborator/patterns.rs +++ b/compiler/noirc_frontend/src/elaborator/patterns.rs @@ -143,6 +143,17 @@ impl<'context> Elaborator<'context> { mutable, new_definitions, ), + Pattern::Interned(id, _) => { + let pattern = self.interner.get_pattern(id).clone(); + self.elaborate_pattern_mut( + pattern, + expected_type, + definition, + mutable, + new_definitions, + global_id, + ) + } } } diff --git a/compiler/noirc_frontend/src/elaborator/statements.rs b/compiler/noirc_frontend/src/elaborator/statements.rs index d7d330f891a..9e29978a9d5 100644 --- a/compiler/noirc_frontend/src/elaborator/statements.rs +++ b/compiler/noirc_frontend/src/elaborator/statements.rs @@ -441,14 +441,9 @@ impl<'context> Elaborator<'context> { } fn elaborate_comptime_statement(&mut self, statement: Statement) -> (HirStatement, Type) { - // We have to push a new FunctionContext so that we can resolve any constraints - // in this comptime block early before the function as a whole finishes elaborating. - // Otherwise the interpreter below may find expressions for which the underlying trait - // call is not yet solved for. - self.function_context.push(Default::default()); let span = statement.span; - let (hir_statement, _typ) = self.elaborate_statement(statement); - self.check_and_pop_function_context(); + let (hir_statement, _typ) = + self.elaborate_in_comptime_context(|this| this.elaborate_statement(statement)); let mut interpreter = self.setup_interpreter(); let value = interpreter.evaluate_statement(hir_statement); let (expr, typ) = self.inline_comptime_value(value, span); diff --git a/compiler/noirc_frontend/src/elaborator/traits.rs b/compiler/noirc_frontend/src/elaborator/traits.rs index f651630baa2..d6bfd3aa647 100644 --- a/compiler/noirc_frontend/src/elaborator/traits.rs +++ b/compiler/noirc_frontend/src/elaborator/traits.rs @@ -13,7 +13,7 @@ use crate::{ BlockExpression, FunctionDefinition, FunctionReturnType, Ident, ItemVisibility, NodeInterner, NoirFunction, Param, Pattern, UnresolvedType, Visibility, }, - node_interner::{FuncId, TraitId}, + node_interner::{FuncId, ReferenceId, TraitId}, token::Attributes, Kind, ResolvedGeneric, Type, TypeBindings, TypeVariableKind, }; @@ -74,7 +74,7 @@ impl<'context> Elaborator<'context> { return_type, where_clause, body: _, - } = item + } = &item.item { self.recover_generics(|this| { let the_trait = this.interner.get_trait(trait_id); @@ -107,6 +107,11 @@ impl<'context> Elaborator<'context> { func_id, ); + if !item.doc_comments.is_empty() { + let id = ReferenceId::Function(func_id); + this.interner.set_doc_comments(id, item.doc_comments.clone()); + } + let func_meta = this.interner.function_meta(&func_id); let arguments = vecmap(&func_meta.parameters.0, |(_, typ, _)| typ.clone()); diff --git a/compiler/noirc_frontend/src/elaborator/types.rs b/compiler/noirc_frontend/src/elaborator/types.rs index 8dccd5f0344..39ef4e0bb8e 100644 --- a/compiler/noirc_frontend/src/elaborator/types.rs +++ b/compiler/noirc_frontend/src/elaborator/types.rs @@ -450,7 +450,6 @@ impl<'context> Elaborator<'context> { } UnresolvedTypeExpression::Constant(int, _) => Type::Constant(int), 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); @@ -463,15 +462,7 @@ impl<'context> Elaborator<'context> { Type::Error } } - (lhs, rhs) => { - if !self.enable_arithmetic_generics { - let span = - if !matches!(lhs, Type::Constant(_)) { lhs_span } else { rhs_span }; - self.push_err(ResolverError::InvalidArrayLengthExpr { span }); - } - - Type::InfixExpr(Box::new(lhs), op, Box::new(rhs)).canonicalize() - } + (lhs, rhs) => Type::InfixExpr(Box::new(lhs), op, Box::new(rhs)).canonicalize(), } } UnresolvedTypeExpression::AsTraitPath(path) => self.resolve_as_trait_path(*path), diff --git a/compiler/noirc_frontend/src/graph/mod.rs b/compiler/noirc_frontend/src/graph/mod.rs index 452aef74b36..094594a50ac 100644 --- a/compiler/noirc_frontend/src/graph/mod.rs +++ b/compiler/noirc_frontend/src/graph/mod.rs @@ -16,6 +16,10 @@ pub enum CrateId { Root(usize), Crate(usize), Stdlib(usize), + /// The special case of running the compiler against the stdlib. + /// In that case there's only one crate, and it's both the root + /// crate and the stdlib crate. + RootAndStdlib(usize), Dummy, } @@ -25,11 +29,17 @@ impl CrateId { } pub fn is_stdlib(&self) -> bool { - matches!(self, CrateId::Stdlib(_)) + match self { + CrateId::Stdlib(_) | CrateId::RootAndStdlib(_) => true, + CrateId::Root(_) | CrateId::Crate(_) | CrateId::Dummy => false, + } } pub fn is_root(&self) -> bool { - matches!(self, CrateId::Root(_)) + match self { + CrateId::Root(_) | CrateId::RootAndStdlib(_) => true, + CrateId::Stdlib(_) | CrateId::Crate(_) | CrateId::Dummy => false, + } } } @@ -178,7 +188,7 @@ impl CrateGraph { Some((CrateId::Root(_), _)) => { panic!("ICE: Tried to re-add the root crate as a regular crate") } - Some((CrateId::Stdlib(_), _)) => { + Some((CrateId::Stdlib(_), _)) | Some((CrateId::RootAndStdlib(_), _)) => { panic!("ICE: Tried to re-add the stdlib crate as a regular crate") } Some((CrateId::Dummy, _)) => { @@ -212,6 +222,28 @@ impl CrateGraph { crate_id } + pub fn add_crate_root_and_stdlib(&mut self, file_id: FileId) -> CrateId { + for (crate_id, crate_data) in self.arena.iter() { + if crate_id.is_root() { + panic!("ICE: Cannot add two crate roots to a graph - use `add_crate` instead"); + } + + if crate_id.is_stdlib() { + panic!("ICE: Cannot add two stdlib crates to a graph - use `add_crate` instead"); + } + + if crate_data.root_file_id == file_id { + panic!("ICE: This FileId was already added to the CrateGraph") + } + } + + let data = CrateData { root_file_id: file_id, dependencies: Vec::new() }; + let crate_id = CrateId::RootAndStdlib(self.arena.len()); + let prev = self.arena.insert(crate_id, data); + assert!(prev.is_none()); + crate_id + } + pub fn iter_keys(&self) -> impl Iterator + '_ { self.arena.keys().copied() } diff --git a/compiler/noirc_frontend/src/hir/comptime/errors.rs b/compiler/noirc_frontend/src/hir/comptime/errors.rs index 5d4d814f3ee..9c4761f3156 100644 --- a/compiler/noirc_frontend/src/hir/comptime/errors.rs +++ b/compiler/noirc_frontend/src/hir/comptime/errors.rs @@ -194,7 +194,6 @@ pub enum InterpreterError { candidates: Vec, location: Location, }, - Unimplemented { item: String, location: Location, @@ -211,6 +210,24 @@ pub enum InterpreterError { attribute: String, location: Location, }, + GenericNameShouldBeAnIdent { + name: Rc, + location: Location, + }, + DuplicateGeneric { + name: Rc, + struct_name: String, + duplicate_location: Location, + existing_location: Location, + }, + CannotResolveExpression { + location: Location, + expression: String, + }, + CannotSetFunctionBody { + location: Location, + expression: String, + }, // 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. @@ -279,8 +296,12 @@ impl InterpreterError { | InterpreterError::FunctionAlreadyResolved { location, .. } | InterpreterError::MultipleMatchingImpls { location, .. } | InterpreterError::ExpectedIdentForStructField { location, .. } - | InterpreterError::TypeAnnotationsNeededForMethodCall { location } => *location, - InterpreterError::InvalidAttribute { location, .. } => *location, + | InterpreterError::InvalidAttribute { location, .. } + | InterpreterError::GenericNameShouldBeAnIdent { location, .. } + | InterpreterError::DuplicateGeneric { duplicate_location: location, .. } + | InterpreterError::TypeAnnotationsNeededForMethodCall { location } + | InterpreterError::CannotResolveExpression { location, .. } + | InterpreterError::CannotSetFunctionBody { location, .. } => *location, InterpreterError::FailedToParseMacro { error, file, .. } => { Location::new(error.span(), *file) @@ -589,6 +610,40 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { let secondary = "Note that this method expects attribute contents, without the leading `#[` or trailing `]`".to_string(); CustomDiagnostic::simple_error(msg, secondary, location.span) } + InterpreterError::GenericNameShouldBeAnIdent { name, location } => { + let msg = + "Generic name needs to be a valid identifer (one word beginning with a letter)" + .to_string(); + let secondary = format!("`{name}` is not a valid identifier"); + CustomDiagnostic::simple_error(msg, secondary, location.span) + } + InterpreterError::DuplicateGeneric { + name, + struct_name, + duplicate_location, + existing_location, + } => { + let msg = format!("`{struct_name}` already has a generic named `{name}`"); + let secondary = format!("`{name}` added here a second time"); + let mut error = + CustomDiagnostic::simple_error(msg, secondary, duplicate_location.span); + + let existing_msg = format!("`{name}` was previously defined here"); + error.add_secondary_with_file( + existing_msg, + existing_location.span, + existing_location.file, + ); + error + } + InterpreterError::CannotResolveExpression { location, expression } => { + let msg = format!("Cannot resolve expression `{expression}`"); + CustomDiagnostic::simple_error(msg, String::new(), location.span) + } + InterpreterError::CannotSetFunctionBody { location, expression } => { + let msg = format!("`{expression}` is not a valid function body"); + CustomDiagnostic::simple_error(msg, String::new(), location.span) + } } } } diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs index 9f559b7c5e6..5f58c18d66e 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs @@ -2,6 +2,7 @@ use std::collections::VecDeque; use std::{collections::hash_map::Entry, rc::Rc}; use acvm::{acir::AcirField, FieldElement}; +use fm::FileId; use im::Vector; use iter_extended::try_vecmap; use noirc_errors::Location; @@ -10,6 +11,7 @@ use rustc_hash::FxHashMap as HashMap; use crate::ast::{BinaryOpKind, FunctionKind, IntegerBitSize, Signedness}; use crate::elaborator::Elaborator; use crate::graph::CrateId; +use crate::hir::def_map::ModuleId; use crate::hir_def::expr::ImplKind; use crate::hir_def::function::FunctionBody; use crate::macros_api::UnaryOp; @@ -170,7 +172,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { Some(body) => Ok(body), None => { if matches!(&meta.function_body, FunctionBody::Unresolved(..)) { - self.elaborate_item(None, |elaborator| { + self.elaborate_in_function(None, |elaborator| { elaborator.elaborate_function(function); }); @@ -183,13 +185,25 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { } } - fn elaborate_item( + fn elaborate_in_function( &mut self, function: Option, f: impl FnOnce(&mut Elaborator) -> T, ) -> T { self.unbind_generics_from_previous_function(); - let result = self.elaborator.elaborate_item_from_comptime(function, f); + let result = self.elaborator.elaborate_item_from_comptime_in_function(function, f); + self.rebind_generics_from_previous_function(); + result + } + + fn elaborate_in_module( + &mut self, + module: ModuleId, + file: FileId, + f: impl FnOnce(&mut Elaborator) -> T, + ) -> T { + self.unbind_generics_from_previous_function(); + let result = self.elaborator.elaborate_item_from_comptime_in_module(module, file, f); self.rebind_generics_from_previous_function(); result } @@ -1244,7 +1258,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { let mut result = self.call_function(function_id, arguments, bindings, location)?; if call.is_macro_call { let expr = result.into_expression(self.elaborator.interner, location)?; - let expr = self.elaborate_item(self.current_function, |elaborator| { + let expr = self.elaborate_in_function(self.current_function, |elaborator| { elaborator.elaborate_expression(expr).0 }); result = self.evaluate(expr)?; diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index 072ac86b974..899d62ecb61 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -22,13 +22,17 @@ use rustc_hash::FxHashMap as HashMap; use crate::{ ast::{ ArrayLiteral, BlockExpression, ConstrainKind, Expression, ExpressionKind, FunctionKind, - FunctionReturnType, IntegerBitSize, LValue, Literal, Statement, StatementKind, UnaryOp, - UnresolvedType, UnresolvedTypeData, Visibility, + FunctionReturnType, IntegerBitSize, LValue, Literal, Pattern, Statement, StatementKind, + UnaryOp, UnresolvedType, UnresolvedTypeData, Visibility, }, - hir::comptime::{ - errors::IResult, - value::{ExprValue, TypedExpr}, - InterpreterError, Value, + hir::def_collector::dc_crate::CollectedItems, + hir::{ + comptime::{ + errors::IResult, + value::{ExprValue, TypedExpr}, + InterpreterError, Value, + }, + def_map::ModuleId, }, hir_def::function::FunctionBody, lexer::Lexer, @@ -36,7 +40,7 @@ use crate::{ node_interner::{DefinitionKind, TraitImplKind}, parser::{self}, token::{Attribute, SecondaryAttribute, Token}, - QuotedType, Shared, Type, + Kind, QuotedType, ResolvedGeneric, Shared, Type, TypeVariable, }; use self::builtin_helpers::{get_array, get_str, get_u8}; @@ -74,6 +78,7 @@ impl<'local, 'context> Interpreter<'local, 'context> { "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_let" => expr_as_let(interner, arguments, return_type, location), "expr_as_member_access" => { expr_as_member_access(interner, arguments, return_type, location) } @@ -96,11 +101,16 @@ impl<'local, 'context> Interpreter<'local, 'context> { "expr_resolve" => expr_resolve(self, arguments, location), "is_unconstrained" => Ok(Value::Bool(true)), "fmtstr_quoted_contents" => fmtstr_quoted_contents(interner, arguments, location), + "fresh_type_variable" => fresh_type_variable(interner), "function_def_add_attribute" => function_def_add_attribute(self, arguments, location), "function_def_body" => function_def_body(interner, arguments, location), "function_def_has_named_attribute" => { function_def_has_named_attribute(interner, arguments, location) } + "function_def_is_unconstrained" => { + function_def_is_unconstrained(interner, arguments, location) + } + "function_def_module" => function_def_module(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), @@ -112,6 +122,10 @@ impl<'local, 'context> Interpreter<'local, 'context> { "function_def_set_return_public" => { function_def_set_return_public(self, arguments, location) } + "function_def_set_unconstrained" => { + function_def_set_unconstrained(self, arguments, location) + } + "module_add_item" => module_add_item(self, arguments, location), "module_functions" => module_functions(self, arguments, location), "module_has_named_attribute" => module_has_named_attribute(self, arguments, location), "module_is_contract" => module_is_contract(self, arguments, location), @@ -134,13 +148,16 @@ impl<'local, 'context> Interpreter<'local, 'context> { "slice_push_front" => slice_push_front(interner, arguments, location), "slice_remove" => slice_remove(interner, arguments, location, call_stack), "str_as_bytes" => str_as_bytes(interner, arguments, location), - "struct_def_add_attribute" => struct_def_add_attribute(self, arguments, location), + "struct_def_add_attribute" => struct_def_add_attribute(interner, arguments, location), + "struct_def_add_generic" => struct_def_add_generic(interner, arguments, location), "struct_def_as_type" => struct_def_as_type(interner, arguments, location), "struct_def_fields" => struct_def_fields(interner, arguments, location), "struct_def_generics" => struct_def_generics(interner, arguments, location), "struct_def_has_named_attribute" => { struct_def_has_named_attribute(interner, arguments, location) } + "struct_def_module" => struct_def_module(self, arguments, location), + "struct_def_name" => struct_def_name(interner, arguments, location), "struct_def_set_fields" => struct_def_set_fields(interner, arguments, location), "to_le_radix" => to_le_radix(arguments, return_type, location), "trait_constraint_eq" => trait_constraint_eq(interner, arguments, location), @@ -273,13 +290,13 @@ fn str_as_bytes( // fn add_attribute(self, attribute: str) fn struct_def_add_attribute( - interpreter: &mut Interpreter, + interner: &mut NodeInterner, arguments: Vec<(Value, Location)>, location: Location, ) -> IResult { let (self_argument, attribute) = check_two_arguments(arguments, location)?; let attribute_location = attribute.1; - let attribute = get_str(interpreter.elaborator.interner, attribute)?; + let attribute = get_str(interner, attribute)?; let mut tokens = Lexer::lex(&format!("#[{}]", attribute)).0 .0; if let Some(Token::EOF) = tokens.last().map(|token| token.token()) { @@ -308,13 +325,68 @@ fn struct_def_add_attribute( }; let struct_id = get_struct(self_argument)?; - interpreter.elaborator.interner.update_struct_attributes(struct_id, |attributes| { + interner.update_struct_attributes(struct_id, |attributes| { attributes.push(attribute.clone()); }); Ok(Value::Unit) } +// fn add_generic(self, generic_name: str) +fn struct_def_add_generic( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let (self_argument, generic) = check_two_arguments(arguments, location)?; + let generic_location = generic.1; + let generic = get_str(interner, generic)?; + + let mut tokens = Lexer::lex(&generic).0 .0; + if let Some(Token::EOF) = tokens.last().map(|token| token.token()) { + tokens.pop(); + } + + if tokens.len() != 1 { + return Err(InterpreterError::GenericNameShouldBeAnIdent { + name: generic, + location: generic_location, + }); + } + + let Token::Ident(generic_name) = tokens.pop().unwrap().into_token() else { + return Err(InterpreterError::GenericNameShouldBeAnIdent { + name: generic, + location: generic_location, + }); + }; + + let struct_id = get_struct(self_argument)?; + let the_struct = interner.get_struct(struct_id); + let mut the_struct = the_struct.borrow_mut(); + let name = Rc::new(generic_name); + + for generic in &the_struct.generics { + if generic.name == name { + return Err(InterpreterError::DuplicateGeneric { + name, + struct_name: the_struct.name.to_string(), + existing_location: Location::new(generic.span, the_struct.location.file), + duplicate_location: generic_location, + }); + } + } + + let type_var = TypeVariable::unbound(interner.next_type_variable_id()); + let span = generic_location.span; + let kind = Kind::Normal; + let typ = Type::NamedGeneric(type_var.clone(), name.clone(), kind.clone()); + let new_generic = ResolvedGeneric { name, type_var, span, kind }; + the_struct.generics.push(new_generic); + + Ok(Value::Type(typ)) +} + /// fn as_type(self) -> Type fn struct_def_as_type( interner: &NodeInterner, @@ -398,6 +470,39 @@ fn struct_def_fields( Ok(Value::Slice(fields, typ)) } +// fn module(self) -> Module +fn struct_def_module( + interpreter: &Interpreter, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let self_argument = check_one_argument(arguments, location)?; + let struct_id = get_struct(self_argument)?; + let struct_module_id = struct_id.module_id(); + + // A struct's module is its own module. To get the module where its defined we need + // to look for its parent. + let module_data = interpreter.elaborator.get_module(struct_module_id); + let parent_local_id = module_data.parent.expect("Expected struct module parent to exist"); + let parent = ModuleId { krate: struct_module_id.krate, local_id: parent_local_id }; + + Ok(Value::ModuleDefinition(parent)) +} + +// fn name(self) -> Quoted +fn struct_def_name( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let self_argument = check_one_argument(arguments, location)?; + let struct_id = get_struct(self_argument)?; + let the_struct = interner.get_struct(struct_id); + + let name = Token::Ident(the_struct.borrow().name.to_string()); + Ok(Value::Quoted(Rc::new(vec![name]))) +} + /// fn set_fields(self, new_fields: [(Quoted, Type)]) {} /// Returns (name, type) pairs of each field of this StructDefinition fn struct_def_set_fields( @@ -567,9 +672,10 @@ fn quoted_as_module( let path = parse(argument, parser::path_no_turbofish(), "a path").ok(); let option_value = path.and_then(|path| { - let module = interpreter.elaborate_item(interpreter.current_function, |elaborator| { - elaborator.resolve_module_by_path(path) - }); + let module = interpreter + .elaborate_in_function(interpreter.current_function, |elaborator| { + elaborator.resolve_module_by_path(path) + }); module.map(Value::ModuleDefinition) }); @@ -585,7 +691,7 @@ fn quoted_as_trait_constraint( let argument = check_one_argument(arguments, location)?; let trait_bound = parse(argument, parser::trait_bound(), "a trait constraint")?; let bound = interpreter - .elaborate_item(interpreter.current_function, |elaborator| { + .elaborate_in_function(interpreter.current_function, |elaborator| { elaborator.resolve_trait_bound(&trait_bound, Type::Unit) }) .ok_or(InterpreterError::FailedToResolveTraitBound { trait_bound, location })?; @@ -601,8 +707,8 @@ fn quoted_as_type( ) -> IResult { let argument = check_one_argument(arguments, location)?; let typ = parse(argument, parser::parse_type(), "a type")?; - let typ = - interpreter.elaborate_item(interpreter.current_function, |elab| elab.resolve_type(typ)); + let typ = interpreter + .elaborate_in_function(interpreter.current_function, |elab| elab.resolve_type(typ)); Ok(Value::Type(typ)) } @@ -680,11 +786,10 @@ fn type_as_constant( location: Location, ) -> IResult { type_as(arguments, return_type, location, |typ| { - if let Type::Constant(n) = typ { - Some(Value::U32(n)) - } else { - None - } + // Prefer to use `evaluate_to_u32` over matching on `Type::Constant` + // since arithmetic generics may be `Type::InfixExpr`s which evaluate to + // constants but are not actually the `Type::Constant` variant. + typ.evaluate_to_u32().map(Value::U32) }) } @@ -786,7 +891,7 @@ where F: FnOnce(Type) -> Option, { let value = check_one_argument(arguments, location)?; - let typ = get_type(value)?; + let typ = get_type(value)?.follow_bindings(); let option_value = f(typ); @@ -815,13 +920,13 @@ 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( + let option_value = match interner.lookup_trait_implementation( &typ, trait_id, &generics.ordered, &generics.named, ) { - Ok((TraitImplKind::Normal(trait_impl_id), _)) => Some(Value::TraitImpl(trait_impl_id)), + Ok(TraitImplKind::Normal(trait_impl_id)) => Some(Value::TraitImpl(trait_impl_id)), _ => None, }; @@ -840,7 +945,7 @@ fn type_implements( let (trait_id, generics) = get_trait_constraint(constraint)?; let implements = interner - .try_lookup_trait_implementation(&typ, trait_id, &generics.ordered, &generics.named) + .lookup_trait_implementation(&typ, trait_id, &generics.ordered, &generics.named) .is_ok(); Ok(Value::Bool(implements)) } @@ -1396,6 +1501,41 @@ fn expr_as_integer( }) } +// fn as_let(self) -> Option<(Expr, Option, Expr)> +fn expr_as_let( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(interner, arguments, return_type.clone(), location, |expr| match expr { + ExprValue::Statement(StatementKind::Let(let_statement)) => { + let option_type = extract_option_generic_type(return_type); + let Type::Tuple(mut tuple_types) = option_type else { + panic!("Expected the return type option generic arg to be a tuple"); + }; + assert_eq!(tuple_types.len(), 3); + tuple_types.pop().unwrap(); + let option_type = tuple_types.pop().unwrap(); + + let typ = if let_statement.r#type.typ == UnresolvedTypeData::Unspecified { + None + } else { + Some(Value::UnresolvedType(let_statement.r#type.typ)) + }; + + let typ = option(option_type, typ).ok()?; + + Some(Value::Tuple(vec![ + Value::pattern(let_statement.pattern), + typ, + Value::expression(let_statement.expression.kind), + ])) + } + _ => None, + }) +} + // fn as_member_access(self) -> Option<(Expr, Quoted)> fn expr_as_member_access( interner: &NodeInterner, @@ -1673,25 +1813,33 @@ fn expr_resolve( interpreter.current_function }; - let value = interpreter.elaborate_item(function_to_resolve_in, |elaborator| match expr_value { + interpreter.elaborate_in_function(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)) + Ok(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)) + Ok(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::TypedExpr(TypedExpr::ExprId(expr_id))) } - }); - - Ok(value) + ExprValue::Pattern(pattern) => { + if let Some(expression) = pattern.try_as_expression(elaborator.interner) { + let (expr_id, _) = elaborator.elaborate_expression(expression); + Ok(Value::TypedExpr(TypedExpr::ExprId(expr_id))) + } else { + let expression = Value::pattern(pattern).display(elaborator.interner).to_string(); + let location = self_argument_location; + Err(InterpreterError::CannotResolveExpression { location, expression }) + } + } + }) } fn unwrap_expr_value(interner: &NodeInterner, mut expr_value: ExprValue) -> ExprValue { @@ -1713,6 +1861,9 @@ fn unwrap_expr_value(interner: &NodeInterner, mut expr_value: ExprValue) -> Expr ExprValue::LValue(LValue::Interned(id, span)) => { expr_value = ExprValue::LValue(interner.get_lvalue(id, span).clone()); } + ExprValue::Pattern(Pattern::Interned(id, _)) => { + expr_value = ExprValue::Pattern(interner.get_pattern(id).clone()); + } _ => break, } } @@ -1736,6 +1887,11 @@ fn fmtstr_quoted_contents( Ok(Value::Quoted(Rc::new(tokens))) } +// fn fresh_type_variable() -> Type +fn fresh_type_variable(interner: &NodeInterner) -> IResult { + Ok(Value::Type(interner.next_type_variable())) +} + // fn add_attribute(self, attribute: str) fn function_def_add_attribute( interpreter: &mut Interpreter, @@ -1822,6 +1978,30 @@ fn function_def_has_named_attribute( Ok(Value::Bool(has_named_attribute(&name, attributes, location))) } +// fn is_unconstrained(self) -> bool +fn function_def_is_unconstrained( + 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 is_unconstrained = interner.function_modifiers(&func_id).is_unconstrained; + Ok(Value::Bool(is_unconstrained)) +} + +// fn module(self) -> Module +fn function_def_module( + 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 module = interner.function_module(func_id); + Ok(Value::ModuleDefinition(module)) +} + // fn name(self) -> Quoted fn function_def_name( interner: &NodeInterner, @@ -1896,6 +2076,16 @@ fn function_def_set_body( }), ExprValue::Statement(statement_kind) => statement_kind, ExprValue::LValue(lvalue) => StatementKind::Expression(lvalue.as_expression()), + ExprValue::Pattern(pattern) => { + if let Some(expression) = pattern.try_as_expression(interpreter.elaborator.interner) { + StatementKind::Expression(expression) + } else { + let expression = + Value::pattern(pattern).display(interpreter.elaborator.interner).to_string(); + let location = body_location; + return Err(InterpreterError::CannotSetFunctionBody { location, expression }); + } + } }; let statement = Statement { kind: statement_kind, span: body_location.span }; @@ -1940,7 +2130,7 @@ fn function_def_set_parameters( "a pattern", )?; - let hir_pattern = interpreter.elaborate_item(Some(func_id), |elaborator| { + let hir_pattern = interpreter.elaborate_in_function(Some(func_id), |elaborator| { elaborator.elaborate_pattern_and_store_ids( parameter_pattern, parameter_type.clone(), @@ -2007,6 +2197,53 @@ fn function_def_set_return_public( Ok(Value::Unit) } +// fn set_unconstrained(self, value: bool) +fn function_def_set_unconstrained( + interpreter: &mut Interpreter, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let (self_argument, unconstrained) = check_two_arguments(arguments, location)?; + + let func_id = get_function_def(self_argument)?; + check_function_not_yet_resolved(interpreter, func_id, location)?; + + let unconstrained = get_bool(unconstrained)?; + + let modifiers = interpreter.elaborator.interner.function_modifiers_mut(&func_id); + modifiers.is_unconstrained = unconstrained; + + Ok(Value::Unit) +} + +// fn add_item(self, item: Quoted) +fn module_add_item( + interpreter: &mut Interpreter, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let (self_argument, item) = check_two_arguments(arguments, location)?; + let module_id = get_module(self_argument)?; + let module_data = interpreter.elaborator.get_module(module_id); + + let parser = parser::top_level_items(); + let top_level_statements = parse(item, parser, "a top-level item")?; + + interpreter.elaborate_in_module(module_id, module_data.location.file, |elaborator| { + let mut generated_items = CollectedItems::default(); + + for top_level_statement in top_level_statements { + elaborator.add_item(top_level_statement, &mut generated_items, location); + } + + if !generated_items.is_empty() { + elaborator.elaborate_items(generated_items); + } + }); + + Ok(Value::Unit) +} + // fn functions(self) -> [FunctionDefinition] fn module_functions( interpreter: &Interpreter, diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs index f90d50807b8..6e72866bec0 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs @@ -5,8 +5,8 @@ use noirc_errors::Location; use crate::{ ast::{ - BlockExpression, ExpressionKind, IntegerBitSize, LValue, Signedness, StatementKind, - UnresolvedTypeData, + BlockExpression, ExpressionKind, IntegerBitSize, LValue, Pattern, Signedness, + StatementKind, UnresolvedTypeData, }, elaborator::Elaborator, hir::{ @@ -191,6 +191,9 @@ pub(crate) fn get_expr( ExprValue::LValue(LValue::Interned(id, _)) => { Ok(ExprValue::LValue(interner.get_lvalue(id, location.span).clone())) } + ExprValue::Pattern(Pattern::Interned(id, _)) => { + Ok(ExprValue::Pattern(interner.get_pattern(id).clone())) + } _ => Ok(expr), }, value => type_mismatch(value, Type::Quoted(QuotedType::Expr), location), diff --git a/compiler/noirc_frontend/src/hir/comptime/tests.rs b/compiler/noirc_frontend/src/hir/comptime/tests.rs index 64b489422a0..a47dbeace50 100644 --- a/compiler/noirc_frontend/src/hir/comptime/tests.rs +++ b/compiler/noirc_frontend/src/hir/comptime/tests.rs @@ -51,7 +51,7 @@ fn interpret_helper(src: &str) -> Result { let main = context.get_main_function(&krate).expect("Expected 'main' function"); let mut elaborator = - Elaborator::elaborate_and_return_self(&mut context, krate, collector.items, None, false); + Elaborator::elaborate_and_return_self(&mut context, krate, collector.items, None); assert_eq!(elaborator.errors.len(), 0); let mut interpreter = elaborator.setup_interpreter(); diff --git a/compiler/noirc_frontend/src/hir/comptime/value.rs b/compiler/noirc_frontend/src/hir/comptime/value.rs index 7d6e4475c7b..f6450175955 100644 --- a/compiler/noirc_frontend/src/hir/comptime/value.rs +++ b/compiler/noirc_frontend/src/hir/comptime/value.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, fmt::Display, rc::Rc}; +use std::{borrow::Cow, fmt::Display, rc::Rc, vec}; use acvm::{AcirField, FieldElement}; use chumsky::Parser; @@ -9,11 +9,11 @@ use strum_macros::Display; use crate::{ 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, + ArrayLiteral, AsTraitPath, AssignStatement, BlockExpression, CallExpression, + CastExpression, ConstrainStatement, ConstructorExpression, ForLoopStatement, ForRange, + GenericTypeArgs, Ident, IfExpression, IndexExpression, InfixExpression, IntegerBitSize, + LValue, Lambda, LetStatement, MemberAccessExpression, MethodCallExpression, Pattern, + PrefixExpression, Signedness, Statement, StatementKind, UnresolvedType, UnresolvedTypeData, }, hir::{def_map::ModuleId, type_check::generics::TraitGenerics}, hir_def::{ @@ -78,6 +78,7 @@ pub enum ExprValue { Expression(ExpressionKind), Statement(StatementKind), LValue(LValue), + Pattern(Pattern), } #[derive(Debug, Clone, PartialEq, Eq, Display)] @@ -99,6 +100,10 @@ impl Value { Value::Expr(ExprValue::LValue(lvaue)) } + pub(crate) fn pattern(pattern: Pattern) -> Self { + Value::Expr(ExprValue::Pattern(pattern)) + } + pub(crate) fn get_type(&self) -> Cow { Cow::Owned(match self { Value::Unit => Type::Unit, @@ -273,7 +278,8 @@ impl Value { }) } Value::Expr(ExprValue::LValue(lvalue)) => lvalue.as_expression().kind, - Value::TypedExpr(..) + Value::Expr(ExprValue::Pattern(_)) + | Value::TypedExpr(..) | Value::Pointer(..) | Value::StructDefinition(_) | Value::TraitConstraint(..) @@ -443,6 +449,9 @@ impl Value { Value::Expr(ExprValue::LValue(lvalue)) => { Token::InternedLValue(interner.push_lvalue(lvalue)) } + Value::Expr(ExprValue::Pattern(pattern)) => { + Token::InternedPattern(interner.push_pattern(pattern)) + } Value::UnresolvedType(typ) => { Token::InternedUnresolvedTypeData(interner.push_unresolved_type_data(typ)) } @@ -653,9 +662,15 @@ impl<'value, 'interner> Display for ValuePrinter<'value, 'interner> { Value::FunctionDefinition(function_id) => { write!(f, "{}", self.interner.function_name(function_id)) } - Value::ModuleDefinition(_) => write!(f, "(module)"), + Value::ModuleDefinition(module_id) => { + if let Some(attributes) = self.interner.try_module_attributes(module_id) { + write!(f, "{}", &attributes.name) + } else { + write!(f, "(crate root)") + } + } Value::Zeroed(typ) => write!(f, "(zeroed {typ})"), - Value::Type(typ) => write!(f, "{}", typ), + Value::Type(typ) => write!(f, "{:?}", typ), Value::Expr(ExprValue::Expression(expr)) => { write!(f, "{}", remove_interned_in_expression_kind(self.interner, expr.clone())) } @@ -665,6 +680,9 @@ impl<'value, 'interner> Display for ValuePrinter<'value, 'interner> { Value::Expr(ExprValue::LValue(lvalue)) => { write!(f, "{}", remove_interned_in_lvalue(self.interner, lvalue.clone())) } + Value::Expr(ExprValue::Pattern(pattern)) => { + write!(f, "{}", remove_interned_in_pattern(self.interner, pattern.clone())) + } Value::TypedExpr(TypedExpr::ExprId(id)) => { let hir_expr = self.interner.expression(id); let expr = hir_expr.to_display_ast(self.interner, Span::default()); @@ -676,12 +694,7 @@ impl<'value, 'interner> Display for ValuePrinter<'value, 'interner> { write!(f, "{}", stmt.kind) } 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) - } + write!(f, "{}", remove_interned_in_unresolved_type_data(self.interner, typ.clone())) } } } @@ -723,6 +736,10 @@ impl<'token, 'interner> Display for TokenPrinter<'token, 'interner> { let value = Value::UnresolvedType(UnresolvedTypeData::Interned(*id)); value.display(self.interner).fmt(f) } + Token::InternedPattern(id) => { + let value = Value::pattern(Pattern::Interned(*id, Span::default())); + value.display(self.interner).fmt(f) + } Token::UnquoteMarker(id) => { let value = Value::TypedExpr(TypedExpr::ExprId(*id)); value.display(self.interner).fmt(f) @@ -895,7 +912,9 @@ fn remove_interned_in_statement_kind( ) -> StatementKind { match statement { StatementKind::Let(let_statement) => StatementKind::Let(LetStatement { + pattern: remove_interned_in_pattern(interner, let_statement.pattern), expression: remove_interned_in_expression(interner, let_statement.expression), + r#type: remove_interned_in_unresolved_type(interner, let_statement.r#type), ..let_statement }), StatementKind::Constrain(constrain) => StatementKind::Constrain(ConstrainStatement( @@ -960,3 +979,120 @@ fn remove_interned_in_lvalue(interner: &NodeInterner, lvalue: LValue) -> LValue } } } + +fn remove_interned_in_unresolved_type( + interner: &NodeInterner, + typ: UnresolvedType, +) -> UnresolvedType { + UnresolvedType { + typ: remove_interned_in_unresolved_type_data(interner, typ.typ), + span: typ.span, + } +} + +fn remove_interned_in_unresolved_type_data( + interner: &NodeInterner, + typ: UnresolvedTypeData, +) -> UnresolvedTypeData { + match typ { + UnresolvedTypeData::Array(expr, typ) => UnresolvedTypeData::Array( + expr, + Box::new(remove_interned_in_unresolved_type(interner, *typ)), + ), + UnresolvedTypeData::Slice(typ) => { + UnresolvedTypeData::Slice(Box::new(remove_interned_in_unresolved_type(interner, *typ))) + } + UnresolvedTypeData::FormatString(expr, typ) => UnresolvedTypeData::FormatString( + expr, + Box::new(remove_interned_in_unresolved_type(interner, *typ)), + ), + UnresolvedTypeData::Parenthesized(typ) => UnresolvedTypeData::Parenthesized(Box::new( + remove_interned_in_unresolved_type(interner, *typ), + )), + UnresolvedTypeData::Named(path, generic_type_args, is_synthesized) => { + UnresolvedTypeData::Named( + path, + remove_interned_in_generic_type_args(interner, generic_type_args), + is_synthesized, + ) + } + UnresolvedTypeData::TraitAsType(path, generic_type_args) => { + UnresolvedTypeData::TraitAsType( + path, + remove_interned_in_generic_type_args(interner, generic_type_args), + ) + } + UnresolvedTypeData::MutableReference(typ) => UnresolvedTypeData::MutableReference( + Box::new(remove_interned_in_unresolved_type(interner, *typ)), + ), + UnresolvedTypeData::Tuple(types) => UnresolvedTypeData::Tuple(vecmap(types, |typ| { + remove_interned_in_unresolved_type(interner, typ) + })), + UnresolvedTypeData::Function(arg_types, ret_type, env_type, unconstrained) => { + UnresolvedTypeData::Function( + vecmap(arg_types, |typ| remove_interned_in_unresolved_type(interner, typ)), + Box::new(remove_interned_in_unresolved_type(interner, *ret_type)), + Box::new(remove_interned_in_unresolved_type(interner, *env_type)), + unconstrained, + ) + } + UnresolvedTypeData::AsTraitPath(as_trait_path) => { + UnresolvedTypeData::AsTraitPath(Box::new(AsTraitPath { + typ: remove_interned_in_unresolved_type(interner, as_trait_path.typ), + trait_generics: remove_interned_in_generic_type_args( + interner, + as_trait_path.trait_generics, + ), + ..*as_trait_path + })) + } + UnresolvedTypeData::Interned(id) => interner.get_unresolved_type_data(id).clone(), + UnresolvedTypeData::FieldElement + | UnresolvedTypeData::Integer(_, _) + | UnresolvedTypeData::Bool + | UnresolvedTypeData::Unit + | UnresolvedTypeData::String(_) + | UnresolvedTypeData::Resolved(_) + | UnresolvedTypeData::Quoted(_) + | UnresolvedTypeData::Expression(_) + | UnresolvedTypeData::Unspecified + | UnresolvedTypeData::Error => typ, + } +} + +fn remove_interned_in_generic_type_args( + interner: &NodeInterner, + args: GenericTypeArgs, +) -> GenericTypeArgs { + GenericTypeArgs { + ordered_args: vecmap(args.ordered_args, |typ| { + remove_interned_in_unresolved_type(interner, typ) + }), + named_args: vecmap(args.named_args, |(name, typ)| { + (name, remove_interned_in_unresolved_type(interner, typ)) + }), + } +} + +// Returns a new Pattern where all Interned Patterns have been turned into Pattern. +fn remove_interned_in_pattern(interner: &NodeInterner, pattern: Pattern) -> Pattern { + match pattern { + Pattern::Identifier(_) => pattern, + Pattern::Mutable(pattern, span, is_synthesized) => Pattern::Mutable( + Box::new(remove_interned_in_pattern(interner, *pattern)), + span, + is_synthesized, + ), + Pattern::Tuple(patterns, span) => Pattern::Tuple( + vecmap(patterns, |pattern| remove_interned_in_pattern(interner, pattern)), + span, + ), + Pattern::Struct(path, patterns, span) => { + let patterns = vecmap(patterns, |(name, pattern)| { + (name, remove_interned_in_pattern(interner, pattern)) + }); + Pattern::Struct(path, patterns, span) + } + Pattern::Interned(id, _) => interner.get_pattern(id).clone(), + } +} diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index 3cfa0989d7d..6265d0e65f2 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -271,7 +271,6 @@ impl DefCollector { ast: SortedModule, root_file_id: FileId, debug_comptime_in_file: Option<&str>, - enable_arithmetic_generics: bool, error_on_unused_items: bool, macro_processors: &[&dyn MacroProcessor], ) -> Vec<(CompilationError, FileId)> { @@ -291,7 +290,6 @@ impl DefCollector { dep.crate_id, context, debug_comptime_in_file, - enable_arithmetic_generics, error_on_usage_tracker, macro_processors, )); @@ -315,6 +313,11 @@ impl DefCollector { let crate_root = def_map.root; let mut def_collector = DefCollector::new(def_map); + let module_id = ModuleId { krate: crate_id, local_id: crate_root }; + context + .def_interner + .set_doc_comments(ReferenceId::Module(module_id), ast.inner_doc_comments.clone()); + // Collecting module declarations with ModCollector // and lowering the functions // i.e. Use a mod collector to collect the nodes at the root module @@ -471,13 +474,8 @@ impl DefCollector { }) }); - let mut more_errors = Elaborator::elaborate( - context, - crate_id, - def_collector.items, - debug_comptime_in_file, - enable_arithmetic_generics, - ); + let mut more_errors = + Elaborator::elaborate(context, crate_id, def_collector.items, debug_comptime_in_file); errors.append(&mut more_errors); diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs index 6c1b7632a2e..d93b708c91d 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs @@ -1,3 +1,4 @@ +use core::str; use std::path::Path; use std::rc::Rc; use std::vec; @@ -10,13 +11,13 @@ use num_traits::Num; use rustc_hash::FxHashMap as HashMap; use crate::ast::{ - FunctionDefinition, Ident, ItemVisibility, LetStatement, ModuleDeclaration, NoirFunction, - NoirStruct, NoirTrait, NoirTraitImpl, NoirTypeAlias, Pattern, TraitImplItem, TraitItem, - TypeImpl, + Documented, FunctionDefinition, Ident, ItemVisibility, LetStatement, ModuleDeclaration, + NoirFunction, NoirStruct, NoirTrait, NoirTraitImpl, NoirTypeAlias, Pattern, TraitImplItem, + TraitItem, TypeImpl, }; use crate::hir::resolution::errors::ResolverError; use crate::macros_api::{Expression, NodeInterner, StructId, UnresolvedType, UnresolvedTypeData}; -use crate::node_interner::ModuleAttributes; +use crate::node_interner::{ModuleAttributes, ReferenceId}; use crate::token::SecondaryAttribute; use crate::usage_tracker::UnusedItem; use crate::{ @@ -146,7 +147,7 @@ impl<'a> ModCollector<'a> { fn collect_globals( &mut self, context: &mut Context, - globals: Vec, + globals: Vec>, crate_id: CrateId, ) -> Vec<(CompilationError, fm::FileId)> { let mut errors = vec![]; @@ -235,7 +236,7 @@ impl<'a> ModCollector<'a> { fn collect_functions( &mut self, context: &mut Context, - functions: Vec, + functions: Vec>, krate: CrateId, ) -> Vec<(CompilationError, FileId)> { let mut unresolved_functions = UnresolvedFunctions { @@ -252,9 +253,10 @@ impl<'a> ModCollector<'a> { let Some(func_id) = collect_function( &mut context.def_interner, &mut self.def_collector.def_map, - &function, + &function.item, module, self.file_id, + function.doc_comments, &mut errors, ) else { continue; @@ -266,7 +268,7 @@ impl<'a> ModCollector<'a> { // and replace it // With this method we iterate each function in the Crate and not each module // This may not be great because we have to pull the module_data for each function - unresolved_functions.push_fn(self.module_id, func_id, function); + unresolved_functions.push_fn(self.module_id, func_id, function.item); } self.def_collector.items.functions.push(unresolved_functions); @@ -279,7 +281,7 @@ impl<'a> ModCollector<'a> { fn collect_structs( &mut self, context: &mut Context, - types: Vec, + types: Vec>, krate: CrateId, ) -> Vec<(CompilationError, FileId)> { let mut definition_errors = vec![]; @@ -304,11 +306,13 @@ impl<'a> ModCollector<'a> { fn collect_type_aliases( &mut self, context: &mut Context, - type_aliases: Vec, + type_aliases: Vec>, krate: CrateId, ) -> Vec<(CompilationError, FileId)> { let mut errors: Vec<(CompilationError, FileId)> = vec![]; for type_alias in type_aliases { + let doc_comments = type_alias.doc_comments; + let type_alias = type_alias.item; let name = type_alias.name.clone(); // And store the TypeId -> TypeAlias mapping somewhere it is reachable @@ -328,6 +332,8 @@ impl<'a> ModCollector<'a> { let type_alias_id = context.def_interner.push_type_alias(&unresolved, resolved_generics); + context.def_interner.set_doc_comments(ReferenceId::Alias(type_alias_id), doc_comments); + // Add the type alias to scope so its path can be looked up later let result = self.def_collector.def_map.modules[self.module_id.0] .declare_type_alias(name.clone(), type_alias_id); @@ -357,11 +363,13 @@ impl<'a> ModCollector<'a> { fn collect_traits( &mut self, context: &mut Context, - traits: Vec, + traits: Vec>, krate: CrateId, ) -> Vec<(CompilationError, FileId)> { let mut errors: Vec<(CompilationError, FileId)> = vec![]; for trait_definition in traits { + let doc_comments = trait_definition.doc_comments; + let trait_definition = trait_definition.item; let name = trait_definition.name.clone(); // Create the corresponding module for the trait namespace @@ -381,6 +389,8 @@ impl<'a> ModCollector<'a> { } }; + context.def_interner.set_doc_comments(ReferenceId::Trait(trait_id), doc_comments); + // Add the trait to scope so its path can be looked up later let result = self.def_collector.def_map.modules[self.module_id.0] .declare_trait(name.clone(), trait_id); @@ -406,7 +416,7 @@ impl<'a> ModCollector<'a> { let mut associated_types = Generics::new(); for trait_item in &trait_definition.items { - match trait_item { + match &trait_item.item { TraitItem::Function { name, generics, @@ -434,6 +444,13 @@ impl<'a> ModCollector<'a> { .def_interner .push_function_definition(func_id, modifiers, trait_id.0, location); + if !trait_item.doc_comments.is_empty() { + context.def_interner.set_doc_comments( + ReferenceId::Function(func_id), + trait_item.doc_comments.clone(), + ); + } + match self.def_collector.def_map.modules[trait_id.0.local_id.0] .declare_function(name.clone(), ItemVisibility::Public, func_id) { @@ -559,12 +576,15 @@ impl<'a> ModCollector<'a> { context: &mut Context, crate_id: CrateId, parent_module_id: LocalModuleId, - submodules: Vec, + submodules: Vec>, file_id: FileId, macro_processors: &[&dyn MacroProcessor], ) -> Vec<(CompilationError, FileId)> { let mut errors: Vec<(CompilationError, FileId)> = vec![]; for submodule in submodules { + let mut doc_comments = submodule.doc_comments; + let submodule = submodule.item; + match self.push_child_module( context, &submodule.name, @@ -584,6 +604,16 @@ impl<'a> ModCollector<'a> { false, ); + if !(doc_comments.is_empty() + && submodule.contents.inner_doc_comments.is_empty()) + { + doc_comments.extend(submodule.contents.inner_doc_comments.clone()); + + context + .def_interner + .set_doc_comments(ReferenceId::Module(child), doc_comments); + } + errors.extend(collect_defs( self.def_collector, submodule.contents, @@ -605,15 +635,19 @@ impl<'a> ModCollector<'a> { /// Search for a module named `mod_name` /// Parse it, add it as a child to the parent module in which it was declared /// and then collect all definitions of the child module + #[allow(clippy::too_many_arguments)] fn parse_module_declaration( &mut self, context: &mut Context, - mod_decl: ModuleDeclaration, + mod_decl: Documented, crate_id: CrateId, parent_file_id: FileId, parent_module_id: LocalModuleId, macro_processors: &[&dyn MacroProcessor], ) -> Vec<(CompilationError, FileId)> { + let mut doc_comments = mod_decl.doc_comments; + let mod_decl = mod_decl.item; + let mut errors: Vec<(CompilationError, FileId)> = vec![]; let child_file_id = match find_module(&context.file_manager, self.file_id, &mod_decl.ident) { @@ -691,6 +725,14 @@ impl<'a> ModCollector<'a> { // Track that the "foo" in `mod foo;` points to the module "foo" context.def_interner.add_module_reference(child_mod_id, location); + if !(doc_comments.is_empty() && ast.inner_doc_comments.is_empty()) { + doc_comments.extend(ast.inner_doc_comments.clone()); + + context + .def_interner + .set_doc_comments(ReferenceId::Module(child_mod_id), doc_comments); + } + errors.extend(collect_defs( self.def_collector, ast, @@ -827,6 +869,7 @@ pub fn collect_function( function: &NoirFunction, module: ModuleId, file: FileId, + doc_comments: Vec, errors: &mut Vec<(CompilationError, FileId)>, ) -> Option { if let Some(field) = function.attributes().get_field_attribute() { @@ -858,6 +901,8 @@ pub fn collect_function( interner.usage_tracker.add_unused_item(module, name.clone(), item, visibility); } + interner.set_doc_comments(ReferenceId::Function(func_id), doc_comments); + // Add function to scope/ns of the module let result = def_map.modules[module.local_id.0].declare_function(name, visibility, func_id); if let Err((first_def, second_def)) = result { @@ -874,12 +919,15 @@ pub fn collect_function( pub fn collect_struct( interner: &mut NodeInterner, def_map: &mut CrateDefMap, - struct_definition: NoirStruct, + struct_definition: Documented, file_id: FileId, module_id: LocalModuleId, krate: CrateId, definition_errors: &mut Vec<(CompilationError, FileId)>, ) -> Option<(StructId, UnresolvedStruct)> { + let doc_comments = struct_definition.doc_comments; + let struct_definition = struct_definition.item; + check_duplicate_field_names(&struct_definition, file_id, definition_errors); let name = struct_definition.name.clone(); @@ -915,6 +963,15 @@ pub fn collect_struct( } }; + interner.set_doc_comments(ReferenceId::Struct(id), doc_comments); + + for (index, field) in unresolved.struct_def.fields.iter().enumerate() { + if !field.doc_comments.is_empty() { + interner + .set_doc_comments(ReferenceId::StructMember(id, index), field.doc_comments.clone()); + } + } + // Add the struct to scope so its path can be looked up later let result = def_map.modules[module_id.0].declare_struct(name.clone(), id); @@ -945,12 +1002,15 @@ pub fn collect_impl( let mut unresolved_functions = UnresolvedFunctions { file_id, functions: Vec::new(), trait_id: None, self_type: None }; - for (mut method, _) in r#impl.methods { + for (method, _) in r#impl.methods { + let doc_comments = method.doc_comments; + let mut method = method.item; let func_id = interner.push_empty_fn(); method.def.where_clause.extend(r#impl.where_clause.clone()); let location = Location::new(method.span(), file_id); interner.push_function(func_id, &method.def, module_id, location); unresolved_functions.push_fn(module_id.local_id, func_id, method); + interner.set_doc_comments(ReferenceId::Function(func_id), doc_comments); } let key = (r#impl.object_type, module_id.local_id); @@ -1064,11 +1124,12 @@ pub(crate) fn collect_trait_impl_items( let module = ModuleId { krate, local_id }; for item in std::mem::take(&mut trait_impl.items) { - match item { + match item.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); + interner.set_doc_comments(ReferenceId::Function(func_id), item.doc_comments); unresolved_functions.push_fn(local_id, func_id, impl_method); } TraitImplItem::Constant(name, typ, expr) => { @@ -1086,11 +1147,14 @@ pub(crate) fn collect_trait_impl_items( pub(crate) fn collect_global( interner: &mut NodeInterner, def_map: &mut CrateDefMap, - global: LetStatement, + global: Documented, file_id: FileId, module_id: LocalModuleId, crate_id: CrateId, ) -> (UnresolvedGlobal, Option<(CompilationError, FileId)>) { + let doc_comments = global.doc_comments; + let global = global.item; + let name = global.pattern.name_ident().clone(); let global_id = interner.push_empty_global( @@ -1112,6 +1176,8 @@ pub(crate) fn collect_global( (err.into(), file_id) }); + interner.set_doc_comments(ReferenceId::Global(global_id), doc_comments); + let global = UnresolvedGlobal { file_id, module_id, global_id, stmt_def: global }; (global, error) } @@ -1122,7 +1188,9 @@ fn check_duplicate_field_names( definition_errors: &mut Vec<(CompilationError, FileId)>, ) { let mut seen_field_names = std::collections::HashSet::new(); - for (field_name, _) in &struct_definition.fields { + for field in &struct_definition.fields { + let field_name = &field.item.name; + if seen_field_names.insert(field_name) { continue; } diff --git a/compiler/noirc_frontend/src/hir/def_map/mod.rs b/compiler/noirc_frontend/src/hir/def_map/mod.rs index a1c4d04cb30..75b860bf2c6 100644 --- a/compiler/noirc_frontend/src/hir/def_map/mod.rs +++ b/compiler/noirc_frontend/src/hir/def_map/mod.rs @@ -76,7 +76,6 @@ impl CrateDefMap { crate_id: CrateId, 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)> { @@ -133,7 +132,6 @@ impl CrateDefMap { ast, root_file_id, debug_comptime_in_file, - enable_arithmetic_generics, error_on_unused_imports, macro_processors, )); diff --git a/compiler/noirc_frontend/src/hir/resolution/errors.rs b/compiler/noirc_frontend/src/hir/resolution/errors.rs index e74468bdf18..5abc94b89a2 100644 --- a/compiler/noirc_frontend/src/hir/resolution/errors.rs +++ b/compiler/noirc_frontend/src/hir/resolution/errors.rs @@ -124,6 +124,8 @@ pub enum ResolverError { AssociatedConstantsMustBeNumeric { span: Span }, #[error("Overflow in `{lhs} {op} {rhs}`")] OverflowInType { lhs: u32, op: crate::BinaryTypeOperator, rhs: u32, span: Span }, + #[error("`quote` cannot be used in runtime code")] + QuoteInRuntimeCode { span: Span }, } impl ResolverError { @@ -504,6 +506,13 @@ impl<'a> From<&'a ResolverError> for Diagnostic { *span, ) } + ResolverError::QuoteInRuntimeCode { span } => { + Diagnostic::simple_error( + "`quote` cannot be used in runtime code".to_string(), + "Wrap this in a `comptime` block or function to use it".to_string(), + *span, + ) + }, } } } diff --git a/compiler/noirc_frontend/src/lexer/lexer.rs b/compiler/noirc_frontend/src/lexer/lexer.rs index b7492396c90..7fbbb4fccef 100644 --- a/compiler/noirc_frontend/src/lexer/lexer.rs +++ b/compiler/noirc_frontend/src/lexer/lexer.rs @@ -2,9 +2,7 @@ use crate::token::{Attribute, DocStyle}; use super::{ errors::LexerErrorKind, - token::{ - token_to_borrowed_token, BorrowedToken, IntType, Keyword, SpannedToken, Token, Tokens, - }, + token::{IntType, Keyword, SpannedToken, Token, Tokens}, }; use acvm::{AcirField, FieldElement}; use noirc_errors::{Position, Span}; @@ -26,21 +24,6 @@ pub struct Lexer<'a> { pub type SpannedTokenResult = Result; -pub(crate) fn from_spanned_token_result( - token_result: &SpannedTokenResult, -) -> Result<(usize, BorrowedToken<'_>, usize), LexerErrorKind> { - token_result - .as_ref() - .map(|spanned_token| { - ( - spanned_token.to_span().start() as usize, - token_to_borrowed_token(spanned_token.into()), - spanned_token.to_span().end() as usize, - ) - }) - .map_err(Clone::clone) -} - impl<'a> Lexer<'a> { /// Given a source file of noir code, return all the tokens in the file /// in order, along with any lexing errors that occurred. @@ -623,7 +606,7 @@ impl<'a> Lexer<'a> { }; let comment = self.eat_while(None, |ch| ch != '\n'); - if self.skip_comments { + if doc_style.is_none() && self.skip_comments { return self.next_token(); } @@ -668,7 +651,7 @@ impl<'a> Lexer<'a> { } if depth == 0 { - if self.skip_comments { + if doc_style.is_none() && self.skip_comments { return self.next_token(); } Ok(Token::BlockComment(content, doc_style).into_span(start, self.position)) diff --git a/compiler/noirc_frontend/src/lexer/token.rs b/compiler/noirc_frontend/src/lexer/token.rs index 7b805b5fd8d..f5f7f0458d7 100644 --- a/compiler/noirc_frontend/src/lexer/token.rs +++ b/compiler/noirc_frontend/src/lexer/token.rs @@ -5,8 +5,8 @@ use std::{fmt, iter::Map, vec::IntoIter}; use crate::{ lexer::errors::LexerErrorKind, node_interner::{ - ExprId, InternedExpressionKind, InternedStatementKind, InternedUnresolvedTypeData, - QuotedTypeId, + ExprId, InternedExpressionKind, InternedPattern, InternedStatementKind, + InternedUnresolvedTypeData, QuotedTypeId, }, }; @@ -36,6 +36,7 @@ pub enum BorrowedToken<'input> { InternedStatement(InternedStatementKind), InternedLValue(InternedExpressionKind), InternedUnresolvedTypeData(InternedUnresolvedTypeData), + InternedPattern(InternedPattern), /// < Less, /// <= @@ -151,6 +152,8 @@ pub enum Token { InternedLValue(InternedExpressionKind), /// A reference to an interned `UnresolvedTypeData`. InternedUnresolvedTypeData(InternedUnresolvedTypeData), + /// A reference to an interned `Patter`. + InternedPattern(InternedPattern), /// < Less, /// <= @@ -255,6 +258,7 @@ pub fn token_to_borrowed_token(token: &Token) -> BorrowedToken<'_> { Token::InternedStatement(id) => BorrowedToken::InternedStatement(*id), Token::InternedLValue(id) => BorrowedToken::InternedLValue(*id), Token::InternedUnresolvedTypeData(id) => BorrowedToken::InternedUnresolvedTypeData(*id), + Token::InternedPattern(id) => BorrowedToken::InternedPattern(*id), Token::IntType(ref i) => BorrowedToken::IntType(i.clone()), Token::Less => BorrowedToken::Less, Token::LessEqual => BorrowedToken::LessEqual, @@ -378,7 +382,10 @@ impl fmt::Display for Token { } // 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(_) => { + Token::InternedExpr(_) + | Token::InternedStatement(_) + | Token::InternedLValue(_) + | Token::InternedPattern(_) => { write!(f, "(expr)") } Token::InternedUnresolvedTypeData(_) => write!(f, "(type)"), @@ -439,7 +446,10 @@ pub enum TokenKind { InternedStatement, InternedLValue, InternedUnresolvedTypeData, + InternedPattern, UnquoteMarker, + OuterDocComment, + InnerDocComment, } impl fmt::Display for TokenKind { @@ -457,7 +467,10 @@ impl fmt::Display for TokenKind { TokenKind::InternedStatement => write!(f, "interned statement"), TokenKind::InternedLValue => write!(f, "interned lvalue"), TokenKind::InternedUnresolvedTypeData => write!(f, "interned unresolved type"), + TokenKind::InternedPattern => write!(f, "interned pattern"), TokenKind::UnquoteMarker => write!(f, "macro result"), + TokenKind::OuterDocComment => write!(f, "outer doc comment"), + TokenKind::InnerDocComment => write!(f, "inner doc comment"), } } } @@ -481,6 +494,11 @@ impl Token { Token::InternedStatement(_) => TokenKind::InternedStatement, Token::InternedLValue(_) => TokenKind::InternedLValue, Token::InternedUnresolvedTypeData(_) => TokenKind::InternedUnresolvedTypeData, + Token::InternedPattern(_) => TokenKind::InternedPattern, + Token::LineComment(_, Some(DocStyle::Outer)) + | Token::BlockComment(_, Some(DocStyle::Outer)) => TokenKind::OuterDocComment, + Token::LineComment(_, Some(DocStyle::Inner)) + | Token::BlockComment(_, Some(DocStyle::Inner)) => TokenKind::InnerDocComment, tok => TokenKind::Token(tok.clone()), } } diff --git a/compiler/noirc_frontend/src/lib.rs b/compiler/noirc_frontend/src/lib.rs index ec09f680bc2..9f7a0564789 100644 --- a/compiler/noirc_frontend/src/lib.rs +++ b/compiler/noirc_frontend/src/lib.rs @@ -53,7 +53,7 @@ pub mod macros_api { pub use crate::token::SecondaryAttribute; pub use crate::ast::{ - BlockExpression, CallExpression, CastExpression, Expression, ExpressionKind, + BlockExpression, CallExpression, CastExpression, Documented, Expression, ExpressionKind, FunctionReturnType, Ident, IndexExpression, ItemVisibility, LetStatement, Literal, MemberAccessExpression, MethodCallExpression, NoirFunction, Path, PathKind, Pattern, Statement, UnresolvedType, UnresolvedTypeData, Visibility, diff --git a/compiler/noirc_frontend/src/monomorphization/errors.rs b/compiler/noirc_frontend/src/monomorphization/errors.rs index ce8ef3572e6..66db72eef55 100644 --- a/compiler/noirc_frontend/src/monomorphization/errors.rs +++ b/compiler/noirc_frontend/src/monomorphization/errors.rs @@ -8,6 +8,7 @@ pub enum MonomorphizationError { NoDefaultType { location: Location }, InternalError { message: &'static str, location: Location }, InterpreterError(InterpreterError), + ComptimeFnInRuntimeCode { name: String, location: Location }, } impl MonomorphizationError { @@ -15,6 +16,7 @@ impl MonomorphizationError { match self { MonomorphizationError::UnknownArrayLength { location, .. } | MonomorphizationError::InternalError { location, .. } + | MonomorphizationError::ComptimeFnInRuntimeCode { location, .. } | MonomorphizationError::NoDefaultType { location, .. } => *location, MonomorphizationError::InterpreterError(error) => error.get_location(), } @@ -43,6 +45,12 @@ impl MonomorphizationError { } MonomorphizationError::InterpreterError(error) => return error.into(), MonomorphizationError::InternalError { message, .. } => message.to_string(), + MonomorphizationError::ComptimeFnInRuntimeCode { name, location } => { + let message = format!("Comptime function {name} used in runtime code"); + let secondary = + "Comptime functions must be in a comptime block to be called".into(); + return CustomDiagnostic::simple_error(message, secondary, location.span); + } }; let location = self.location(); diff --git a/compiler/noirc_frontend/src/monomorphization/mod.rs b/compiler/noirc_frontend/src/monomorphization/mod.rs index 87b55540bbd..9357cc65c14 100644 --- a/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -133,7 +133,7 @@ pub fn monomorphize_debug( let impl_bindings = perform_impl_bindings(interner, trait_method, next_fn_id, location) .map_err(MonomorphizationError::InterpreterError)?; - monomorphizer.function(next_fn_id, new_id)?; + monomorphizer.function(next_fn_id, new_id, location)?; undo_instantiation_bindings(impl_bindings); undo_instantiation_bindings(bindings); } @@ -278,7 +278,10 @@ impl<'interner> Monomorphizer<'interner> { ) -> Result { let new_main_id = self.next_function_id(); assert_eq!(new_main_id, Program::main_id()); - self.function(main_id, new_main_id)?; + + let location = self.interner.function_meta(&main_id).location; + self.function(main_id, new_main_id, location)?; + self.return_location = self.interner.function(&main_id).block(self.interner).statements().last().and_then( |x| match self.interner.statement(x) { @@ -294,6 +297,7 @@ impl<'interner> Monomorphizer<'interner> { &mut self, f: node_interner::FuncId, id: FuncId, + location: Location, ) -> Result<(), MonomorphizationError> { if let Some((self_type, trait_id)) = self.interner.get_function_trait(&f) { let the_trait = self.interner.get_trait(trait_id); @@ -313,6 +317,10 @@ impl<'interner> Monomorphizer<'interner> { let modifiers = self.interner.function_modifiers(&f); let name = self.interner.function_name(&f).to_owned(); + if modifiers.is_comptime { + return Err(MonomorphizationError::ComptimeFnInRuntimeCode { name, location }); + } + let body_expr_id = self.interner.function(&f).as_expr(); let body_return_type = self.interner.id_type(body_expr_id); let return_type = match meta.return_type() { diff --git a/compiler/noirc_frontend/src/node_interner.rs b/compiler/noirc_frontend/src/node_interner.rs index aa51779d24b..f298559e65c 100644 --- a/compiler/noirc_frontend/src/node_interner.rs +++ b/compiler/noirc_frontend/src/node_interner.rs @@ -16,6 +16,7 @@ use rustc_hash::FxHashMap as HashMap; use crate::ast::ExpressionKind; use crate::ast::Ident; use crate::ast::LValue; +use crate::ast::Pattern; use crate::ast::StatementKind; use crate::ast::UnresolvedTypeData; use crate::graph::CrateId; @@ -222,6 +223,9 @@ pub struct NodeInterner { // Interned `UnresolvedTypeData`s during comptime code. interned_unresolved_type_datas: noirc_arena::Arena, + // Interned `Pattern`s during comptime code. + interned_patterns: noirc_arena::Arena, + /// Determins whether to run in LSP mode. In LSP mode references are tracked. pub(crate) lsp_mode: bool, @@ -269,6 +273,9 @@ pub struct NodeInterner { pub(crate) comptime_scopes: Vec>, pub(crate) usage_tracker: UsageTracker, + + /// Captures the documentation comments for each module, struct, trait, function, etc. + pub(crate) doc_comments: HashMap>, } /// A dependency in the dependency graph may be a type or a definition. @@ -607,6 +614,9 @@ pub struct InternedStatementKind(noirc_arena::Index); #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct InternedUnresolvedTypeData(noirc_arena::Index); +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct InternedPattern(noirc_arena::Index); + impl Default for NodeInterner { fn default() -> Self { NodeInterner { @@ -647,6 +657,7 @@ impl Default for NodeInterner { interned_expression_kinds: Default::default(), interned_statement_kinds: Default::default(), interned_unresolved_type_datas: Default::default(), + interned_patterns: Default::default(), lsp_mode: false, location_indices: LocationIndices::default(), reference_graph: petgraph::graph::DiGraph::new(), @@ -656,6 +667,7 @@ impl Default for NodeInterner { comptime_scopes: vec![HashMap::default()], trait_impl_associated_types: HashMap::default(), usage_tracker: UsageTracker::new(), + doc_comments: HashMap::default(), } } } @@ -2097,6 +2109,14 @@ impl NodeInterner { LValue::from_expression_kind(self.get_expression_kind(id).clone(), span) } + pub fn push_pattern(&mut self, pattern: Pattern) -> InternedPattern { + InternedPattern(self.interned_patterns.insert(pattern)) + } + + pub fn get_pattern(&self, id: InternedPattern) -> &Pattern { + &self.interned_patterns[id.0] + } + pub fn push_unresolved_type_data( &mut self, typ: UnresolvedTypeData, @@ -2196,6 +2216,16 @@ impl NodeInterner { bindings } + + pub fn set_doc_comments(&mut self, id: ReferenceId, doc_comments: Vec) { + if !doc_comments.is_empty() { + self.doc_comments.insert(id, doc_comments); + } + } + + pub fn doc_comments(&self, id: ReferenceId) -> Option<&Vec> { + self.doc_comments.get(&id) + } } impl Methods { diff --git a/compiler/noirc_frontend/src/noir_parser.lalrpop b/compiler/noirc_frontend/src/noir_parser.lalrpop deleted file mode 100644 index 01b8be8f721..00000000000 --- a/compiler/noirc_frontend/src/noir_parser.lalrpop +++ /dev/null @@ -1,170 +0,0 @@ -use noirc_errors::Span; - -use crate::lexer::token::BorrowedToken; -use crate::lexer::token as noir_token; -use crate::lexer::errors::LexerErrorKind; -use crate::parser::TopLevelStatement; -use crate::ast::{Ident, Path, PathKind, PathSegment, UseTree, UseTreeKind}; - -use lalrpop_util::ErrorRecovery; - -grammar<'input, 'err>(input: &'input str, errors: &'err mut [ErrorRecovery, &'static str>]); - -extern { - type Location = usize; - - type Error = LexerErrorKind; - - // NOTE: each token needs a terminal defined - enum BorrowedToken<'input> { - string => BorrowedToken::Str(<&'input str>), - ident => BorrowedToken::Ident(<&'input str>), - - // symbols - "<" => BorrowedToken::Less, - "<=" => BorrowedToken::LessEqual, - ">" => BorrowedToken::Greater, - ">=" => BorrowedToken::GreaterEqual, - "==" => BorrowedToken::Equal, - "!=" => BorrowedToken::NotEqual, - "+" => BorrowedToken::Plus, - "-" => BorrowedToken::Minus, - "*" => BorrowedToken::Star, - "/" => BorrowedToken::Slash, - "%" => BorrowedToken::Percent, - "&" => BorrowedToken::Ampersand, - "^" => BorrowedToken::Caret, - "<<" => BorrowedToken::ShiftLeft, - ">>" => BorrowedToken::ShiftRight, - "." => BorrowedToken::Dot, - ".." => BorrowedToken::DoubleDot, - "(" => BorrowedToken::LeftParen, - ")" => BorrowedToken::RightParen, - "{" => BorrowedToken::LeftBrace, - "}" => BorrowedToken::RightBrace, - "[" => BorrowedToken::LeftBracket, - "]" => BorrowedToken::RightBracket, - "->" => BorrowedToken::Arrow, - "|" => BorrowedToken::Pipe, - "#" => BorrowedToken::Pound, - "," => BorrowedToken::Comma, - ":" => BorrowedToken::Colon, - "::" => BorrowedToken::DoubleColon, - ";" => BorrowedToken::Semicolon, - "!" => BorrowedToken::Bang, - "=" => BorrowedToken::Assign, - // keywords - "as" => BorrowedToken::Keyword(noir_token::Keyword::As), - "assert" => BorrowedToken::Keyword(noir_token::Keyword::Assert), - "assert_eq" => BorrowedToken::Keyword(noir_token::Keyword::AssertEq), - "bool" => BorrowedToken::Keyword(noir_token::Keyword::Bool), - "break" => BorrowedToken::Keyword(noir_token::Keyword::Break), - "call_data" => BorrowedToken::Keyword(noir_token::Keyword::CallData), - "char" => BorrowedToken::Keyword(noir_token::Keyword::Char), - "comptime" => BorrowedToken::Keyword(noir_token::Keyword::Comptime), - "constrain" => BorrowedToken::Keyword(noir_token::Keyword::Constrain), - "continue" => BorrowedToken::Keyword(noir_token::Keyword::Continue), - "contract" => BorrowedToken::Keyword(noir_token::Keyword::Contract), - "crate" => BorrowedToken::Keyword(noir_token::Keyword::Crate), - "dep" => BorrowedToken::Keyword(noir_token::Keyword::Dep), - "else" => BorrowedToken::Keyword(noir_token::Keyword::Else), - "Field" => BorrowedToken::Keyword(noir_token::Keyword::Field), - "fn" => BorrowedToken::Keyword(noir_token::Keyword::Fn), - "for" => BorrowedToken::Keyword(noir_token::Keyword::For), - "fmtstr" => BorrowedToken::Keyword(noir_token::Keyword::FormatString), - "global" => BorrowedToken::Keyword(noir_token::Keyword::Global), - "if" => BorrowedToken::Keyword(noir_token::Keyword::If), - "impl" => BorrowedToken::Keyword(noir_token::Keyword::Impl), - "in" => BorrowedToken::Keyword(noir_token::Keyword::In), - "let" => BorrowedToken::Keyword(noir_token::Keyword::Let), - "mod" => BorrowedToken::Keyword(noir_token::Keyword::Mod), - "mut" => BorrowedToken::Keyword(noir_token::Keyword::Mut), - "pub" => BorrowedToken::Keyword(noir_token::Keyword::Pub), - "return" => BorrowedToken::Keyword(noir_token::Keyword::Return), - "return_data" => BorrowedToken::Keyword(noir_token::Keyword::ReturnData), - "str" => BorrowedToken::Keyword(noir_token::Keyword::String), - "struct" => BorrowedToken::Keyword(noir_token::Keyword::Struct), - "trait" => BorrowedToken::Keyword(noir_token::Keyword::Trait), - "type" => BorrowedToken::Keyword(noir_token::Keyword::Type), - "unchecked" => BorrowedToken::Keyword(noir_token::Keyword::Unchecked), - "unconstrained" => BorrowedToken::Keyword(noir_token::Keyword::Unconstrained), - "use" => BorrowedToken::Keyword(noir_token::Keyword::Use), - "where" => BorrowedToken::Keyword(noir_token::Keyword::Where), - "while" => BorrowedToken::Keyword(noir_token::Keyword::While), - // bool - "true" => BorrowedToken::Bool(true), - "false" => BorrowedToken::Bool(false), - - r"[\t\r\n ]+" => BorrowedToken::Whitespace(_), - - EOF => BorrowedToken::EOF, - } -} - -pub(crate) TopLevelStatement: TopLevelStatement = { - "use" r"[\t\r\n ]+" ";" EOF => { - TopLevelStatement::Import(use_tree, crate::ast::ItemVisibility::Private) - } -} - -UseTree: UseTree = { - // path::to::ident as SomeAlias - => { - let ident = prefix.pop().ident; - let kind = UseTreeKind::Path(ident, alias); - UseTree { prefix, kind } - }, -} - -pub(crate) Path: Path = { - "crate" "::" => { - let kind = PathKind::Crate; - let span = Span::from(lo as u32..hi as u32); - Path { segments, kind, span } - }, - - "dep" "::" => { - let kind = PathKind::Plain; - let span = Span::from(lo as u32..hi as u32); - Path { segments, kind, span } - }, - - => { - segments.insert(0, id); - let kind = PathKind::Plain; - let span = Span::from(lo as u32..hi as u32); - Path { segments, kind, span } - }, -} - -PathSegments: Vec = { - )*> => { - segments - } -} - -PathSegment: PathSegment = { - => { - let token = noir_token::Token::Ident(i.to_string()); - let span = Span::from(lo as u32..hi as u32); - PathSegment::from(Ident::from_token(token, span)) - }, -} - -Alias: Ident = { - r"[\t\r\n ]+" "as" r"[\t\r\n ]+" => <>, -} - -Ident: Ident = { - => { - let token = noir_token::Token::Ident(i.to_string()); - let span = Span::from(lo as u32..hi as u32); - Ident::from_token(token, span) - }, -} - -Bool: BorrowedToken<'input> = { - "true" => BorrowedToken::Bool(true), - "false" => BorrowedToken::Bool(false), -}; - diff --git a/compiler/noirc_frontend/src/parser/mod.rs b/compiler/noirc_frontend/src/parser/mod.rs index 2995e90ab01..968af82a8b3 100644 --- a/compiler/noirc_frontend/src/parser/mod.rs +++ b/compiler/noirc_frontend/src/parser/mod.rs @@ -12,9 +12,9 @@ mod labels; mod parser; use crate::ast::{ - Expression, Ident, ImportStatement, ItemVisibility, LetStatement, ModuleDeclaration, - NoirFunction, NoirStruct, NoirTrait, NoirTraitImpl, NoirTypeAlias, Recoverable, StatementKind, - TypeImpl, UseTree, + Documented, Expression, Ident, ImportStatement, ItemVisibility, LetStatement, + ModuleDeclaration, NoirFunction, NoirStruct, NoirTrait, NoirTraitImpl, NoirTypeAlias, + Recoverable, StatementKind, TypeImpl, UseTree, }; use crate::token::{Keyword, SecondaryAttribute, Token}; @@ -26,12 +26,18 @@ use noirc_errors::Span; pub use parser::path::path_no_turbofish; pub use parser::traits::trait_bound; pub use parser::{ - block, expression, fresh_statement, lvalue, parse_program, parse_type, pattern, - top_level_items, visibility, + block, expression, fresh_statement, lvalue, module, parse_program, parse_type, pattern, + top_level_items, top_level_statement, visibility, }; #[derive(Debug, Clone)] -pub enum TopLevelStatement { +pub struct TopLevelStatement { + pub kind: TopLevelStatementKind, + pub doc_comments: Vec, +} + +#[derive(Debug, Clone)] +pub enum TopLevelStatementKind { Function(NoirFunction), Module(ModuleDeclaration), Import(UseTree, ItemVisibility), @@ -46,21 +52,21 @@ pub enum TopLevelStatement { Error, } -impl TopLevelStatement { +impl TopLevelStatementKind { pub fn into_item_kind(self) -> Option { match self { - TopLevelStatement::Function(f) => Some(ItemKind::Function(f)), - TopLevelStatement::Module(m) => Some(ItemKind::ModuleDecl(m)), - TopLevelStatement::Import(i, visibility) => Some(ItemKind::Import(i, visibility)), - TopLevelStatement::Struct(s) => Some(ItemKind::Struct(s)), - TopLevelStatement::Trait(t) => Some(ItemKind::Trait(t)), - TopLevelStatement::TraitImpl(t) => Some(ItemKind::TraitImpl(t)), - TopLevelStatement::Impl(i) => Some(ItemKind::Impl(i)), - TopLevelStatement::TypeAlias(t) => Some(ItemKind::TypeAlias(t)), - TopLevelStatement::SubModule(s) => Some(ItemKind::Submodules(s)), - TopLevelStatement::Global(c) => Some(ItemKind::Global(c)), - TopLevelStatement::InnerAttribute(a) => Some(ItemKind::InnerAttribute(a)), - TopLevelStatement::Error => None, + TopLevelStatementKind::Function(f) => Some(ItemKind::Function(f)), + TopLevelStatementKind::Module(m) => Some(ItemKind::ModuleDecl(m)), + TopLevelStatementKind::Import(i, visibility) => Some(ItemKind::Import(i, visibility)), + TopLevelStatementKind::Struct(s) => Some(ItemKind::Struct(s)), + TopLevelStatementKind::Trait(t) => Some(ItemKind::Trait(t)), + TopLevelStatementKind::TraitImpl(t) => Some(ItemKind::TraitImpl(t)), + TopLevelStatementKind::Impl(i) => Some(ItemKind::Impl(i)), + TopLevelStatementKind::TypeAlias(t) => Some(ItemKind::TypeAlias(t)), + TopLevelStatementKind::SubModule(s) => Some(ItemKind::Submodules(s)), + TopLevelStatementKind::Global(c) => Some(ItemKind::Global(c)), + TopLevelStatementKind::InnerAttribute(a) => Some(ItemKind::InnerAttribute(a)), + TopLevelStatementKind::Error => None, } } } @@ -222,11 +228,11 @@ fn parameter_name_recovery() -> impl NoirParser { try_skip_until([Colon, RightParen, Comma], [RightParen, Comma]) } -fn top_level_statement_recovery() -> impl NoirParser { +fn top_level_statement_recovery() -> impl NoirParser { none_of([Token::RightBrace, Token::EOF]) .repeated() .ignore_then(one_of([Token::Semicolon])) - .map(|_| TopLevelStatement::Error) + .map(|_| TopLevelStatementKind::Error) } /// Force the given parser to succeed, logging any errors it had @@ -237,21 +243,22 @@ fn force<'a, T: 'a>(parser: impl NoirParser + 'a) -> impl NoirParser, - pub functions: Vec, - pub types: Vec, - pub traits: Vec, + pub functions: Vec>, + pub types: Vec>, + pub traits: Vec>, pub trait_impls: Vec, pub impls: Vec, - pub type_aliases: Vec, - pub globals: Vec, + pub type_aliases: Vec>, + pub globals: Vec>, /// Module declarations like `mod foo;` - pub module_decls: Vec, + pub module_decls: Vec>, /// Full submodules as in `mod foo { ... definitions ... }` - pub submodules: Vec, + pub submodules: Vec>, pub inner_attributes: Vec, + pub inner_doc_comments: Vec, } impl std::fmt::Display for SortedModule { @@ -296,6 +303,7 @@ impl std::fmt::Display for SortedModule { #[derive(Clone, Debug, Default)] pub struct ParsedModule { pub items: Vec, + pub inner_doc_comments: Vec, } impl ParsedModule { @@ -305,19 +313,27 @@ impl ParsedModule { for item in self.items { match item.kind { ItemKind::Import(import, visibility) => module.push_import(import, visibility), - ItemKind::Function(func) => module.push_function(func), - ItemKind::Struct(typ) => module.push_type(typ), - ItemKind::Trait(noir_trait) => module.push_trait(noir_trait), + ItemKind::Function(func) => module.push_function(func, item.doc_comments), + ItemKind::Struct(typ) => module.push_type(typ, item.doc_comments), + ItemKind::Trait(noir_trait) => module.push_trait(noir_trait, item.doc_comments), ItemKind::TraitImpl(trait_impl) => module.push_trait_impl(trait_impl), ItemKind::Impl(r#impl) => module.push_impl(r#impl), - ItemKind::TypeAlias(type_alias) => module.push_type_alias(type_alias), - ItemKind::Global(global) => module.push_global(global), - ItemKind::ModuleDecl(mod_name) => module.push_module_decl(mod_name), - ItemKind::Submodules(submodule) => module.push_submodule(submodule.into_sorted()), + ItemKind::TypeAlias(type_alias) => { + module.push_type_alias(type_alias, item.doc_comments); + } + ItemKind::Global(global) => module.push_global(global, item.doc_comments), + ItemKind::ModuleDecl(mod_name) => { + module.push_module_decl(mod_name, item.doc_comments); + } + ItemKind::Submodules(submodule) => { + module.push_submodule(submodule.into_sorted(), item.doc_comments); + } ItemKind::InnerAttribute(attribute) => module.inner_attributes.push(attribute), } } + module.inner_doc_comments = self.inner_doc_comments; + module } } @@ -326,6 +342,7 @@ impl ParsedModule { pub struct Item { pub kind: ItemKind, pub span: Span, + pub doc_comments: Vec, } #[derive(Clone, Debug)] @@ -385,16 +402,16 @@ pub struct SortedSubModule { } impl SortedModule { - fn push_function(&mut self, func: NoirFunction) { - self.functions.push(func); + fn push_function(&mut self, func: NoirFunction, doc_comments: Vec) { + self.functions.push(Documented::new(func, doc_comments)); } - fn push_type(&mut self, typ: NoirStruct) { - self.types.push(typ); + fn push_type(&mut self, typ: NoirStruct, doc_comments: Vec) { + self.types.push(Documented::new(typ, doc_comments)); } - fn push_trait(&mut self, noir_trait: NoirTrait) { - self.traits.push(noir_trait); + fn push_trait(&mut self, noir_trait: NoirTrait, doc_comments: Vec) { + self.traits.push(Documented::new(noir_trait, doc_comments)); } fn push_trait_impl(&mut self, trait_impl: NoirTraitImpl) { @@ -405,24 +422,24 @@ impl SortedModule { self.impls.push(r#impl); } - fn push_type_alias(&mut self, type_alias: NoirTypeAlias) { - self.type_aliases.push(type_alias); + fn push_type_alias(&mut self, type_alias: NoirTypeAlias, doc_comments: Vec) { + self.type_aliases.push(Documented::new(type_alias, doc_comments)); } fn push_import(&mut self, import_stmt: UseTree, visibility: ItemVisibility) { self.imports.extend(import_stmt.desugar(None, visibility)); } - fn push_module_decl(&mut self, mod_decl: ModuleDeclaration) { - self.module_decls.push(mod_decl); + fn push_module_decl(&mut self, mod_decl: ModuleDeclaration, doc_comments: Vec) { + self.module_decls.push(Documented::new(mod_decl, doc_comments)); } - fn push_submodule(&mut self, submodule: SortedSubModule) { - self.submodules.push(submodule); + fn push_submodule(&mut self, submodule: SortedSubModule, doc_comments: Vec) { + self.submodules.push(Documented::new(submodule, doc_comments)); } - fn push_global(&mut self, global: LetStatement) { - self.globals.push(global); + fn push_global(&mut self, global: LetStatement, doc_comments: Vec) { + self.globals.push(Documented::new(global, doc_comments)); } } @@ -503,27 +520,27 @@ impl Precedence { } } -impl std::fmt::Display for TopLevelStatement { +impl std::fmt::Display for TopLevelStatementKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - TopLevelStatement::Function(fun) => fun.fmt(f), - TopLevelStatement::Module(m) => m.fmt(f), - TopLevelStatement::Import(tree, visibility) => { + TopLevelStatementKind::Function(fun) => fun.fmt(f), + TopLevelStatementKind::Module(m) => m.fmt(f), + TopLevelStatementKind::Import(tree, visibility) => { if visibility == &ItemVisibility::Private { write!(f, "use {tree}") } else { write!(f, "{visibility} use {tree}") } } - TopLevelStatement::Trait(t) => t.fmt(f), - TopLevelStatement::TraitImpl(i) => i.fmt(f), - TopLevelStatement::Struct(s) => s.fmt(f), - TopLevelStatement::Impl(i) => i.fmt(f), - TopLevelStatement::TypeAlias(t) => t.fmt(f), - TopLevelStatement::SubModule(s) => s.fmt(f), - TopLevelStatement::Global(c) => c.fmt(f), - TopLevelStatement::InnerAttribute(a) => write!(f, "#![{}]", a), - TopLevelStatement::Error => write!(f, "error"), + TopLevelStatementKind::Trait(t) => t.fmt(f), + TopLevelStatementKind::TraitImpl(i) => i.fmt(f), + TopLevelStatementKind::Struct(s) => s.fmt(f), + TopLevelStatementKind::Impl(i) => i.fmt(f), + TopLevelStatementKind::TypeAlias(t) => t.fmt(f), + TopLevelStatementKind::SubModule(s) => s.fmt(f), + TopLevelStatementKind::Global(c) => c.fmt(f), + TopLevelStatementKind::InnerAttribute(a) => write!(f, "#![{}]", a), + TopLevelStatementKind::Error => write!(f, "error"), } } } diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index 1aee697aa88..0ffeb691d35 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -27,6 +27,7 @@ use self::path::as_trait_path; use self::primitives::{keyword, macro_quote_marker, mutable_reference, variable}; use self::types::{generic_type_args, maybe_comp_time}; use attributes::{attributes, inner_attribute, validate_secondary_attributes}; +use doc_comments::{inner_doc_comments, outer_doc_comments}; pub use types::parse_type; use visibility::item_visibility; pub use visibility::visibility; @@ -35,30 +36,30 @@ use super::{ foldl_with_span, labels::ParsingRuleLabel, parameter_name_recovery, parameter_recovery, parenthesized, then_commit, then_commit_ignore, top_level_statement_recovery, ExprParser, NoirParser, ParsedModule, ParsedSubModule, ParserError, ParserErrorReason, Precedence, - TopLevelStatement, + TopLevelStatementKind, }; -use super::{spanned, Item, ItemKind}; +use super::{spanned, Item, TopLevelStatement}; use crate::ast::{ - BinaryOp, BinaryOpKind, BlockExpression, ForLoopStatement, ForRange, GenericTypeArgs, Ident, - IfExpression, InfixExpression, LValue, Literal, ModuleDeclaration, NoirTypeAlias, Param, Path, - Pattern, Recoverable, Statement, TypeImpl, UnaryRhsMemberAccess, UnaryRhsMethodCall, UseTree, - UseTreeKind, Visibility, + BinaryOp, BinaryOpKind, BlockExpression, Documented, ForLoopStatement, ForRange, + GenericTypeArgs, Ident, IfExpression, InfixExpression, LValue, Literal, ModuleDeclaration, + NoirTypeAlias, Param, Path, Pattern, Recoverable, Statement, TypeImpl, UnaryRhsMemberAccess, + UnaryRhsMethodCall, UseTree, UseTreeKind, Visibility, }; use crate::ast::{ Expression, ExpressionKind, LetStatement, StatementKind, UnresolvedType, UnresolvedTypeData, }; -use crate::lexer::{lexer::from_spanned_token_result, Lexer}; +use crate::lexer::Lexer; use crate::parser::{force, ignore_then_commit, statement_recovery}; use crate::token::{Keyword, Token, TokenKind}; use acvm::AcirField; use chumsky::prelude::*; use iter_extended::vecmap; -use lalrpop_util::lalrpop_mod; use noirc_errors::{Span, Spanned}; mod assertion; mod attributes; +mod doc_comments; mod function; mod lambdas; mod literals; @@ -69,9 +70,6 @@ pub(super) mod traits; mod types; mod visibility; -// synthesized by LALRPOP -lalrpop_mod!(pub noir_parser); - #[cfg(test)] mod test_helpers; @@ -95,79 +93,9 @@ pub fn parse_program(source_program: &str) -> (ParsedModule, Vec) { parsing_errors.extend(lexing_errors.into_iter().map(Into::into)); let parsed_module = module.unwrap_or_default(); - if cfg!(feature = "experimental_parser") { - for parsed_item in &parsed_module.items { - if lalrpop_parser_supports_kind(&parsed_item.kind) { - match &parsed_item.kind { - ItemKind::Import(parsed_use_tree, _visibility) => { - prototype_parse_use_tree(Some(parsed_use_tree), source_program); - } - // other kinds prevented by lalrpop_parser_supports_kind - _ => unreachable!(), - } - } - } - } (parsed_module, parsing_errors) } -fn prototype_parse_use_tree(expected_use_tree_opt: Option<&UseTree>, input: &str) { - // TODO(https://github.com/noir-lang/noir/issues/4777): currently skipping - // recursive use trees, e.g. "use std::{foo, bar}" - if input.contains('{') { - return; - } - - let mut lexer = Lexer::new(input); - lexer = lexer.skip_whitespaces(false); - let mut errors = Vec::new(); - - // NOTE: this is a hack to get the references working - // => this likely means that we'll want to propagate the <'input> lifetime further into Token - let lexer_result = lexer.collect::>(); - let referenced_lexer_result = lexer_result.iter().map(from_spanned_token_result); - - let calculated = noir_parser::TopLevelStatementParser::new().parse( - input, - &mut errors, - referenced_lexer_result, - ); - - if let Some(expected_use_tree) = expected_use_tree_opt { - assert!( - calculated.is_ok(), - "calculated not Ok(_): {:?}\n\nlexer: {:?}\n\ninput: {:?}", - calculated, - lexer_result, - input - ); - - match calculated.unwrap() { - TopLevelStatement::Import(parsed_use_tree, _visibility) => { - assert_eq!(expected_use_tree, &parsed_use_tree); - } - unexpected_calculated => { - panic!( - "expected a TopLevelStatement::Import, but found: {:?}", - unexpected_calculated - ) - } - } - } else { - assert!( - calculated.is_err(), - "calculated not Err(_): {:?}\n\nlexer: {:?}\n\ninput: {:?}", - calculated, - lexer_result, - input - ); - } -} - -fn lalrpop_parser_supports_kind(kind: &ItemKind) -> bool { - matches!(kind, ItemKind::Import(..)) -} - /// program: module EOF fn program() -> impl NoirParser { module().then_ignore(just(Token::EOF)) @@ -175,15 +103,26 @@ fn program() -> impl NoirParser { /// module: top_level_statement module /// | %empty -fn module() -> impl NoirParser { +pub fn module() -> impl NoirParser { recursive(|module_parser| { - empty() - .to(ParsedModule::default()) - .then(spanned(top_level_statement(module_parser)).repeated()) - .foldl(|mut program, (statement, span)| { - if let Some(kind) = statement.into_item_kind() { - program.items.push(Item { kind, span }); - } + inner_doc_comments() + .then( + empty() + .to(ParsedModule::default()) + .then(spanned(top_level_statement(module_parser)).repeated()) + .foldl(|mut program, (statement, span)| { + if let Some(kind) = statement.kind.into_item_kind() { + program.items.push(Item { + kind, + span, + doc_comments: statement.doc_comments, + }); + } + program + }), + ) + .map(|(doc_comments, mut program)| { + program.inner_doc_comments = doc_comments; program }) }) @@ -194,6 +133,14 @@ pub fn top_level_items() -> impl NoirParser> { top_level_statement(module()).repeated() } +pub fn top_level_statement<'a>( + module_parser: impl NoirParser + 'a, +) -> impl NoirParser + 'a { + outer_doc_comments() + .then(top_level_statement_kind(module_parser)) + .map(|(doc_comments, kind)| TopLevelStatement { kind, doc_comments }) +} + /// top_level_statement: function_definition /// | struct_definition /// | trait_definition @@ -202,11 +149,11 @@ pub fn top_level_items() -> impl NoirParser> { /// | module_declaration /// | use_statement /// | global_declaration -fn top_level_statement<'a>( +fn top_level_statement_kind<'a>( module_parser: impl NoirParser + 'a, -) -> impl NoirParser + 'a { +) -> impl NoirParser + 'a { choice(( - function::function_definition(false).map(TopLevelStatement::Function), + function::function_definition(false).map(TopLevelStatementKind::Function), structs::struct_definition(), traits::trait_definition(), traits::trait_implementation(), @@ -217,7 +164,7 @@ fn top_level_statement<'a>( module_declaration().then_ignore(force(just(Token::Semicolon))), use_statement().then_ignore(force(just(Token::Semicolon))), global_declaration().then_ignore(force(just(Token::Semicolon))), - inner_attribute().map(TopLevelStatement::InnerAttribute), + inner_attribute().map(TopLevelStatementKind::InnerAttribute), )) .recover_via(top_level_statement_recovery()) } @@ -225,9 +172,15 @@ fn top_level_statement<'a>( /// Parses a non-trait implementation, adding a set of methods to a type. /// /// implementation: 'impl' generics type '{' function_definition ... '}' -fn implementation() -> impl NoirParser { +fn implementation() -> impl NoirParser { + let method = spanned(function::function_definition(true)); + let methods = outer_doc_comments() + .then(method) + .map(|(doc_comments, (method, span))| (Documented::new(method, doc_comments), span)) + .repeated(); + let methods_or_error = just(Token::LeftBrace) - .ignore_then(spanned(function::function_definition(true)).repeated()) + .ignore_then(methods) .then_ignore(just(Token::RightBrace)) .or_not() .validate(|methods, span, emit| { @@ -250,7 +203,7 @@ fn implementation() -> impl NoirParser { .map(|args| { let ((other_args, where_clause), methods) = args; let (generics, (object_type, type_span)) = other_args; - TopLevelStatement::Impl(TypeImpl { + TopLevelStatementKind::Impl(TypeImpl { generics, object_type, type_span, @@ -261,7 +214,7 @@ fn implementation() -> impl NoirParser { } /// global_declaration: 'global' ident global_type_annotation '=' literal -fn global_declaration() -> impl NoirParser { +fn global_declaration() -> impl NoirParser { let p = attributes::attributes() .then(maybe_comp_time()) .then(spanned(keyword(Keyword::Mut)).or_not()) @@ -285,11 +238,13 @@ fn global_declaration() -> impl NoirParser { LetStatement { pattern, r#type, comptime, expression, attributes: global_attributes } }, ) - .map(TopLevelStatement::Global) + .map(TopLevelStatementKind::Global) } /// submodule: 'mod' ident '{' module '}' -fn submodule(module_parser: impl NoirParser) -> impl NoirParser { +fn submodule( + module_parser: impl NoirParser, +) -> impl NoirParser { attributes() .then_ignore(keyword(Keyword::Mod)) .then(ident()) @@ -298,7 +253,7 @@ fn submodule(module_parser: impl NoirParser) -> impl NoirParser) -> impl NoirParser) -> impl NoirParser { +fn contract( + module_parser: impl NoirParser, +) -> impl NoirParser { attributes() .then_ignore(keyword(Keyword::Contract)) .then(ident()) @@ -317,7 +274,7 @@ fn contract(module_parser: impl NoirParser) -> impl NoirParser) -> impl NoirParser impl NoirParser { +fn type_alias_definition() -> impl NoirParser { use self::Keyword::Type; let p = ignore_then_commit(keyword(Type), ident()); @@ -335,7 +292,7 @@ fn type_alias_definition() -> impl NoirParser { let p = then_commit(p, parse_type()); p.map_with_span(|((name, generics), typ), span| { - TopLevelStatement::TypeAlias(NoirTypeAlias { name, generics, typ, span }) + TopLevelStatementKind::TypeAlias(NoirTypeAlias { name, generics, typ, span }) }) } @@ -450,20 +407,20 @@ fn optional_type_annotation<'a>() -> impl NoirParser + 'a { }) } -fn module_declaration() -> impl NoirParser { +fn module_declaration() -> impl NoirParser { attributes().then_ignore(keyword(Keyword::Mod)).then(ident()).validate( |(attributes, ident), span, emit| { let attributes = validate_secondary_attributes(attributes, span, emit); - TopLevelStatement::Module(ModuleDeclaration { ident, outer_attributes: attributes }) + TopLevelStatementKind::Module(ModuleDeclaration { ident, outer_attributes: attributes }) }, ) } -fn use_statement() -> impl NoirParser { +fn use_statement() -> impl NoirParser { item_visibility() .then_ignore(keyword(Keyword::Use)) .then(use_tree()) - .map(|(visibility, use_tree)| TopLevelStatement::Import(use_tree, visibility)) + .map(|(visibility, use_tree)| TopLevelStatementKind::Import(use_tree, visibility)) } fn rename() -> impl NoirParser> { @@ -637,7 +594,15 @@ pub fn pattern() -> impl NoirParser { .delimited_by(just(Token::LeftParen), just(Token::RightParen)) .map_with_span(Pattern::Tuple); - choice((mut_pattern, tuple_pattern, struct_pattern, ident_pattern)) + let interned = + token_kind(TokenKind::InternedPattern).map_with_span(|token, span| match token { + Token::InternedPattern(id) => Pattern::Interned(id, span), + _ => unreachable!( + "token_kind(InternedPattern) guarantees we parse an interned pattern" + ), + }); + + choice((mut_pattern, tuple_pattern, struct_pattern, ident_pattern, interned)) }) .labelled(ParsingRuleLabel::Pattern) } @@ -1580,12 +1545,12 @@ mod test { for (use_statement_str, expect_valid) in use_statements { let mut use_statement_str = use_statement_str.to_string(); - let expected_use_statement = if expect_valid { + if expect_valid { let (result_opt, _diagnostics) = parse_recover(&use_statement(), &use_statement_str); use_statement_str.push(';'); match result_opt.unwrap() { - TopLevelStatement::Import(expected_use_statement, _visibility) => { + TopLevelStatementKind::Import(expected_use_statement, _visibility) => { Some(expected_use_statement) } _ => unreachable!(), @@ -1595,8 +1560,6 @@ mod test { assert!(result.is_err()); None }; - - prototype_parse_use_tree(expected_use_statement.as_ref(), &use_statement_str); } } @@ -1842,7 +1805,7 @@ mod test { assert_eq!(errors[0].message, "expected <, where or { after impl type"); let top_level_statement = top_level_statement.unwrap(); - let TopLevelStatement::Impl(impl_) = top_level_statement else { + let TopLevelStatementKind::Impl(impl_) = top_level_statement else { panic!("Expected to parse an impl"); }; diff --git a/compiler/noirc_frontend/src/parser/parser/doc_comments.rs b/compiler/noirc_frontend/src/parser/parser/doc_comments.rs new file mode 100644 index 00000000000..151ff21017f --- /dev/null +++ b/compiler/noirc_frontend/src/parser/parser/doc_comments.rs @@ -0,0 +1,36 @@ +use chumsky::Parser; + +use crate::{ + parser::NoirParser, + token::{DocStyle, Token, TokenKind}, +}; + +use super::primitives::token_kind; + +fn outer_doc_comment() -> impl NoirParser { + token_kind(TokenKind::OuterDocComment).map(|token| match token { + Token::LineComment(comment, Some(DocStyle::Outer)) => comment, + Token::BlockComment(comment, Some(DocStyle::Outer)) => comment, + _ => unreachable!( + "Parser should have already errored due to token not being an outer doc comment" + ), + }) +} + +pub(super) fn outer_doc_comments() -> impl NoirParser> { + outer_doc_comment().repeated() +} + +fn inner_doc_comment() -> impl NoirParser { + token_kind(TokenKind::InnerDocComment).map(|token| match token { + Token::LineComment(comment, Some(DocStyle::Inner)) => comment, + Token::BlockComment(comment, Some(DocStyle::Inner)) => comment, + _ => unreachable!( + "Parser should have already errored due to token not being an inner doc comment" + ), + }) +} + +pub(super) fn inner_doc_comments() -> impl NoirParser> { + inner_doc_comment().repeated() +} diff --git a/compiler/noirc_frontend/src/parser/parser/structs.rs b/compiler/noirc_frontend/src/parser/parser/structs.rs index 58bf1693eee..66d30a6f407 100644 --- a/compiler/noirc_frontend/src/parser/parser/structs.rs +++ b/compiler/noirc_frontend/src/parser/parser/structs.rs @@ -1,6 +1,6 @@ use chumsky::prelude::*; -use crate::ast::{Ident, NoirStruct, UnresolvedType}; +use crate::ast::{Documented, NoirStruct, StructField}; use crate::{ parser::{ parser::{ @@ -8,12 +8,14 @@ use crate::{ function, parse_type, primitives::{ident, keyword}, }, - NoirParser, TopLevelStatement, + NoirParser, TopLevelStatementKind, }, token::{Keyword, Token}, }; -pub(super) fn struct_definition() -> impl NoirParser { +use super::doc_comments::outer_doc_comments; + +pub(super) fn struct_definition() -> impl NoirParser { use self::Keyword::Struct; use Token::*; @@ -34,16 +36,16 @@ pub(super) fn struct_definition() -> impl NoirParser { .then(fields) .validate(|(((attributes, name), generics), fields), span, emit| { let attributes = validate_secondary_attributes(attributes, span, emit); - TopLevelStatement::Struct(NoirStruct { name, attributes, generics, fields, span }) + TopLevelStatementKind::Struct(NoirStruct { name, attributes, generics, fields, span }) }) } -fn struct_fields() -> impl NoirParser> { - ident() - .then_ignore(just(Token::Colon)) - .then(parse_type()) - .separated_by(just(Token::Comma)) - .allow_trailing() +fn struct_fields() -> impl NoirParser>> { + let field = ident().then_ignore(just(Token::Colon)).then(parse_type()); + let field = outer_doc_comments().then(field).map(|(doc_comments, (name, typ))| { + Documented::new(StructField { name, typ }, doc_comments) + }); + field.separated_by(just(Token::Comma)).allow_trailing() } #[cfg(test)] diff --git a/compiler/noirc_frontend/src/parser/parser/traits.rs b/compiler/noirc_frontend/src/parser/parser/traits.rs index bf5a4b4d0b4..cb17bf2caf7 100644 --- a/compiler/noirc_frontend/src/parser/parser/traits.rs +++ b/compiler/noirc_frontend/src/parser/parser/traits.rs @@ -1,6 +1,7 @@ use chumsky::prelude::*; use super::attributes::{attributes, validate_secondary_attributes}; +use super::doc_comments::outer_doc_comments; use super::function::function_return_type; use super::path::path_no_turbofish; use super::{ @@ -8,21 +9,21 @@ use super::{ }; use crate::ast::{ - Expression, ItemVisibility, NoirTrait, NoirTraitImpl, TraitBound, TraitImplItem, TraitItem, - UnresolvedTraitConstraint, UnresolvedType, + Documented, Expression, ItemVisibility, NoirTrait, NoirTraitImpl, TraitBound, TraitImplItem, + TraitItem, UnresolvedTraitConstraint, UnresolvedType, }; use crate::macros_api::Pattern; use crate::{ parser::{ ignore_then_commit, parenthesized, parser::primitives::keyword, NoirParser, ParserError, - ParserErrorReason, TopLevelStatement, + ParserErrorReason, TopLevelStatementKind, }, token::{Keyword, Token}, }; use super::{generic_type_args, parse_type, primitives::ident}; -pub(super) fn trait_definition() -> impl NoirParser { +pub(super) fn trait_definition() -> impl NoirParser { let trait_body_or_error = just(Token::LeftBrace) .ignore_then(trait_body()) .then_ignore(just(Token::RightBrace)) @@ -47,7 +48,7 @@ pub(super) fn trait_definition() -> impl NoirParser { .then(trait_body_or_error) .validate(|((((attributes, name), generics), where_clause), items), span, emit| { let attributes = validate_secondary_attributes(attributes, span, emit); - TopLevelStatement::Trait(NoirTrait { + TopLevelStatementKind::Trait(NoirTrait { name, generics, where_clause, @@ -58,10 +59,12 @@ pub(super) fn trait_definition() -> impl NoirParser { }) } -fn trait_body() -> impl NoirParser> { - trait_function_declaration() - .or(trait_type_declaration()) - .or(trait_constant_declaration()) +fn trait_body() -> impl NoirParser>> { + let item = + trait_function_declaration().or(trait_type_declaration()).or(trait_constant_declaration()); + outer_doc_comments() + .then(item) + .map(|(doc_comments, item)| Documented::new(item, doc_comments)) .repeated() } @@ -122,7 +125,7 @@ fn trait_type_declaration() -> impl NoirParser { /// and an optional `where` clause is also useable. /// /// trait_implementation: 'impl' generics ident generic_args for type '{' trait_implementation_body '}' -pub(super) fn trait_implementation() -> impl NoirParser { +pub(super) fn trait_implementation() -> impl NoirParser { let body_or_error = just(Token::LeftBrace) .ignore_then(trait_implementation_body()) @@ -152,7 +155,7 @@ pub(super) fn trait_implementation() -> impl NoirParser { .map(|args| { let (((other_args, object_type), where_clause), items) = args; let ((impl_generics, trait_name), trait_generics) = other_args; - TopLevelStatement::TraitImpl(NoirTraitImpl { + TopLevelStatementKind::TraitImpl(NoirTraitImpl { impl_generics, trait_name, trait_generics, @@ -163,7 +166,7 @@ pub(super) fn trait_implementation() -> impl NoirParser { }) } -fn trait_implementation_body() -> impl NoirParser> { +fn trait_implementation_body() -> impl NoirParser>> { let function = function::function_definition(true).validate(|mut f, span, emit| { if f.def().is_unconstrained || f.def().visibility != ItemVisibility::Private { emit(ParserError::with_reason(ParserErrorReason::TraitImplFunctionModifiers, span)); @@ -190,7 +193,11 @@ fn trait_implementation_body() -> impl NoirParser> { }, ); - choice((function, alias, let_statement)).repeated() + let item = choice((function, alias, let_statement)); + outer_doc_comments() + .then(item) + .map(|(doc_comments, item)| Documented::new(item, doc_comments)) + .repeated() } pub(super) fn where_clause() -> impl NoirParser> { @@ -291,7 +298,7 @@ mod test { assert_eq!(errors[0].message, "expected <, where or { after trait name"); let top_level_statement = top_level_statement.unwrap(); - let TopLevelStatement::Trait(trait_) = top_level_statement else { + let TopLevelStatementKind::Trait(trait_) = top_level_statement else { panic!("Expected to parse a trait"); }; @@ -308,7 +315,7 @@ mod test { assert_eq!(errors[0].message, "expected <, where or { after trait impl for type"); let top_level_statement = top_level_statement.unwrap(); - let TopLevelStatement::TraitImpl(trait_impl) = top_level_statement else { + let TopLevelStatementKind::TraitImpl(trait_impl) = top_level_statement else { panic!("Expected to parse a trait impl"); }; diff --git a/compiler/noirc_frontend/src/tests.rs b/compiler/noirc_frontend/src/tests.rs index cd075d2c374..64c9b7471b4 100644 --- a/compiler/noirc_frontend/src/tests.rs +++ b/compiler/noirc_frontend/src/tests.rs @@ -96,7 +96,6 @@ pub(crate) fn get_program(src: &str) -> (ParsedModule, Context, Vec<(Compilation }; let debug_comptime_in_file = None; - let enable_arithmetic_generics = false; let error_on_unused_imports = true; let macro_processors = &[]; @@ -107,7 +106,6 @@ pub(crate) fn get_program(src: &str) -> (ParsedModule, Context, Vec<(Compilation program.clone().into_sorted(), root_file_id, debug_comptime_in_file, - enable_arithmetic_generics, error_on_unused_imports, macro_processors, )); @@ -859,7 +857,7 @@ fn get_program_captures(src: &str) -> Vec> { let interner = context.def_interner; let mut all_captures: Vec> = Vec::new(); for func in program.into_sorted().functions { - let func_id = interner.find_function(func.name()).unwrap(); + let func_id = interner.find_function(func.item.name()).unwrap(); let hir_func = interner.function(&func_id); // Iterate over function statements and apply filtering function find_lambda_captures(hir_func.block(&interner).statements(), &interner, &mut all_captures); @@ -3375,7 +3373,7 @@ fn unquoted_integer_as_integer_token() { #[attr] pub fn foobar() {} - fn attr(_f: FunctionDefinition) -> Quoted { + comptime fn attr(_f: FunctionDefinition) -> Quoted { let serialized_len = 1; // We are testing that when we unquote $serialized_len, it's unquoted // as the token `1` and not as something else that later won't be parsed correctly diff --git a/docs/docs/noir/concepts/generics.md b/docs/docs/noir/concepts/generics.md index 3e416eee093..f05540f9f55 100644 --- a/docs/docs/noir/concepts/generics.md +++ b/docs/docs/noir/concepts/generics.md @@ -45,17 +45,20 @@ fn main() { The `print` function will print `Hello!` an arbitrary number of times, twice in this case. +## Numeric Generics + If we want to be generic over array lengths (which are type-level integers), we can use numeric -generics. Using these looks just like using regular generics, but these generics can resolve to -integers at compile-time, rather than resolving to types. Here's an example of a struct that is -generic over the size of the array it contains internally: +generics. Using these looks similar to using regular generics, but introducing them into scope +requires declaring them with `let MyGenericName: IntegerType`. This can be done anywhere a normal +generic is declared. Instead of types, these generics resolve to integers at compile-time. +Here's an example of a struct that is generic over the size of the array it contains internally: ```rust -struct BigInt { +struct BigInt { limbs: [u32; N], } -impl BigInt { +impl BigInt { // `N` is in scope of all methods in the impl fn first(first: BigInt, second: BigInt) -> Self { assert(first.limbs != second.limbs); @@ -77,7 +80,7 @@ This is what [traits](../concepts/traits.md) are for in Noir. Here's an example any type `T` that implements the `Eq` trait for equality: ```rust -fn first_element_is_equal(array1: [T; N], array2: [T; N]) -> bool +fn first_element_is_equal(array1: [T; N], array2: [T; N]) -> bool where T: Eq { if (array1.len() == 0) | (array2.len() == 0) { @@ -161,3 +164,47 @@ fn example() { assert(10 as u32 == foo.generic_method::()); } ``` + +## Arithmetic Generics + +In addition to numeric generics, Noir also allows a limited form of arithmetic on generics. +When you have a numeric generic such as `N`, you can use the following operators on it in a +type position: `+`, `-`, `*`, `/`, and `%`. + +Note that type checking arithmetic generics is a best effort guess from the compiler and there +are many cases of types that are equal that the compiler may not see as such. For example, +we know that `T * (N + M)` should be equal to `T*N + T*M` but the compiler does not currently +apply the distributive law and thus sees these as different types. + +Even with this limitation though, the compiler can handle common cases decently well: + +```rust +trait Serialize { + fn serialize(self) -> [Field; N]; +} + +impl Serialize<1> for Field { + fn serialize(self) -> [Field; 1] { + [self] + } +} + +impl Serialize for [T; N] + where T: Serialize { .. } + +impl Serialize for (T, U) + where T: Serialize, U: Serialize { .. } + +fn main() { + let data = (1, [2, 3, 4]); + assert(data.serialize().len(), 4); +} +``` + +Note that if there is any over or underflow the types will fail to unify: + +#include_code underflow-example test_programs/compile_failure/arithmetic_generics_underflow/src/main.nr rust + +This also applies if there is underflow in an intermediate calculation: + +#include_code intermediate-underflow-example test_programs/compile_failure/arithmetic_generics_intermediate_underflow/src/main.nr rust diff --git a/docs/docs/noir/standard_library/containers/hashmap.md b/docs/docs/noir/standard_library/containers/hashmap.md index 651e7f5723b..70590ad30d8 100644 --- a/docs/docs/noir/standard_library/containers/hashmap.md +++ b/docs/docs/noir/standard_library/containers/hashmap.md @@ -11,11 +11,6 @@ Note that due to hash collisions, the actual maximum number of elements stored b hashmap is likely lower than `MaxLen`. This is true even with cryptographic hash functions since every hash value will be performed modulo `MaxLen`. -When creating `HashMap`s, the `MaxLen` generic should always be specified if it is not already -known. Otherwise, the compiler may infer a different value for `MaxLen` (such as zero), which -will likely change the result of the program. This behavior is set to become an error in future -versions instead. - Example: ```rust diff --git a/docs/docs/noir/standard_library/meta/expr.md b/docs/docs/noir/standard_library/meta/expr.md index 3a3c61b41f5..7ee33027354 100644 --- a/docs/docs/noir/standard_library/meta/expr.md +++ b/docs/docs/noir/standard_library/meta/expr.md @@ -85,9 +85,16 @@ array and the index. #include_code as_integer noir_stdlib/src/meta/expr.nr rust -If this element is an integer literal, return the integer as a field +If this expression is an integer literal, return the integer as a field as well as whether the integer is negative (true) or not (false). +### as_let + +#include_code as_let noir_stdlib/src/meta/expr.nr rust + +If this expression is a let statement, returns the let pattern as an `Expr`, +the optional type annotation, and the assigned expression. + ### as_member_access #include_code as_member_access noir_stdlib/src/meta/expr.nr rust diff --git a/docs/docs/noir/standard_library/meta/function_def.md b/docs/docs/noir/standard_library/meta/function_def.md index 7c7531fb24a..c9fa3b345f0 100644 --- a/docs/docs/noir/standard_library/meta/function_def.md +++ b/docs/docs/noir/standard_library/meta/function_def.md @@ -11,7 +11,7 @@ a function definition in the source program. #include_code add_attribute noir_stdlib/src/meta/function_def.nr rust -Adds an attribute to the function. This is only valid +Adds an attribute to the function. 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. @@ -19,7 +19,7 @@ This means any functions called at compile-time are invalid targets for this met #include_code body noir_stdlib/src/meta/function_def.nr rust -Returns the body of the function as an expression. This is only valid +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. @@ -29,6 +29,18 @@ This means any functions called at compile-time are invalid targets for this met Returns true if this function has a custom attribute with the given name. +### is_unconstrained + +#include_code is_unconstrained noir_stdlib/src/meta/function_def.nr rust + +Returns true if this function is unconstrained. + +### module + +#include_code module noir_stdlib/src/meta/function_def.nr rust + +Returns the module where the function is defined. + ### name #include_code name noir_stdlib/src/meta/function_def.nr rust @@ -78,6 +90,14 @@ This means any functions called at compile-time are invalid targets for this met #include_code set_return_public noir_stdlib/src/meta/function_def.nr rust -Mutates the function's return visibility to public (if `true` is given) or private (if `false` is given). -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. \ No newline at end of file +Mutates the function's return visibility to public (if `true` is given) or private (if `false` is given). +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_unconstrained + +#include_code set_unconstrained noir_stdlib/src/meta/function_def.nr rust + +Mutates the function to be unconstrained (if `true` is given) or not (if `false` is given). +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/docs/docs/noir/standard_library/meta/module.md b/docs/docs/noir/standard_library/meta/module.md index 870e366461c..de042760d51 100644 --- a/docs/docs/noir/standard_library/meta/module.md +++ b/docs/docs/noir/standard_library/meta/module.md @@ -8,6 +8,14 @@ declarations in the source program. ## Methods +### add_item + +#include_code add_item noir_stdlib/src/meta/module.nr rust + +Adds a top-level item (a function, a struct, a global, etc.) to the module. +Adding multiple items in one go is also valid if the `Quoted` value has multiple items in it. +Note that the items are type-checked as if they are inside the module they are being added to. + ### name #include_code name noir_stdlib/src/meta/module.nr rust diff --git a/docs/docs/noir/standard_library/meta/struct_def.md b/docs/docs/noir/standard_library/meta/struct_def.md index 5da4a458d88..c088e538fc9 100644 --- a/docs/docs/noir/standard_library/meta/struct_def.md +++ b/docs/docs/noir/standard_library/meta/struct_def.md @@ -13,6 +13,24 @@ This type corresponds to `struct Name { field1: Type1, ... }` items in the sourc Adds an attribute to the struct. +### add_generic + +#include_code add_generic noir_stdlib/src/meta/struct_def.nr rust + +Adds an generic to the struct. Returns the new generic type. +Errors if the given generic name isn't a single identifier or if +the struct already has a generic with the same name. + +This method should be used carefully, if there is existing code referring +to the struct type it may be checked before this function is called and +see the struct with the original number of generics. This method should +thus be preferred to use on code generated from other macros and structs +that are not used in function signatures. + +Example: + +#include_code add-generic-example test_programs/compile_success_empty/comptime_struct_definition/src/main.nr rust + ### as_type #include_code as_type noir_stdlib/src/meta/struct_def.nr rust @@ -56,6 +74,21 @@ Returns each field of this struct as a pair of (field name, field type). Returns true if this struct has a custom attribute with the given name. +### module + +#include_code module noir_stdlib/src/meta/struct_def.nr rust + +Returns the module where the struct is defined. + +### name + +#include_code name noir_stdlib/src/meta/struct_def.nr rust + +Returns the name of this struct + +Note that the returned quoted value will be just the struct name, it will +not be the full path to the struct, nor will it include any generics. + ### set_fields #include_code set_fields noir_stdlib/src/meta/struct_def.nr rust diff --git a/docs/docs/noir/standard_library/meta/typ.md b/docs/docs/noir/standard_library/meta/typ.md index 0b6f8d5f77d..1334092a9fa 100644 --- a/docs/docs/noir/standard_library/meta/typ.md +++ b/docs/docs/noir/standard_library/meta/typ.md @@ -5,6 +5,30 @@ title: Type `std::meta::typ` contains methods on the built-in `Type` type used for representing a type in the source program. +## Functions + +#include_code fresh_type_variable noir_stdlib/src/meta/typ.nr rust + +Creates and returns an unbound type variable. This is a special kind of type internal +to type checking which will type check with any other type. When it is type checked +against another type it will also be set to that type. For example, if `a` is a type +variable and we have the type equality `(a, i32) = (u8, i32)`, the compiler will set +`a` equal to `u8`. + +Unbound type variables will often be rendered as `_` while printing them. Bound type +variables will appear as the type they are bound to. + +This can be used in conjunction with functions which internally perform type checks +such as `Type::implements` or `Type::get_trait_impl` to potentially grab some of the types used. + +Note that calling `Type::implements` or `Type::get_trait_impl` on a type variable will always +fail. + +Example: + +#include_code serialize-setup test_programs/compile_success_empty/comptime_type/src/main.nr rust +#include_code fresh-type-variable-example test_programs/compile_success_empty/comptime_type/src/main.nr rust + ## Methods ### as_array diff --git a/noir_stdlib/src/append.nr b/noir_stdlib/src/append.nr index 4577ae199b8..22baa9205a0 100644 --- a/noir_stdlib/src/append.nr +++ b/noir_stdlib/src/append.nr @@ -25,11 +25,11 @@ impl Append for [T] { } impl Append for Quoted { - fn empty() -> Self { + comptime fn empty() -> Self { quote {} } - fn append(self, other: Self) -> Self { + comptime fn append(self, other: Self) -> Self { quote { $self $other } } } diff --git a/noir_stdlib/src/cmp.nr b/noir_stdlib/src/cmp.nr index b7f473429a7..521604a4e20 100644 --- a/noir_stdlib/src/cmp.nr +++ b/noir_stdlib/src/cmp.nr @@ -11,7 +11,13 @@ trait 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; + let body = |fields| { + if s.fields().len() == 0 { + quote { true } + } else { + fields + } + }; crate::meta::make_trait_impl(s, quote { Eq }, signature, for_each_field, quote { & }, body) } // docs:end:derive_eq diff --git a/noir_stdlib/src/collections/bounded_vec.nr b/noir_stdlib/src/collections/bounded_vec.nr index a4c0f642a82..fede1b17c07 100644 --- a/noir_stdlib/src/collections/bounded_vec.nr +++ b/noir_stdlib/src/collections/bounded_vec.nr @@ -1,45 +1,188 @@ use crate::{cmp::Eq, convert::From}; +/// A `BoundedVec` is a growable storage similar to a `Vec` except that it +/// is bounded with a maximum possible length. Unlike `Vec`, `BoundedVec` is not implemented +/// via slices and thus is not subject to the same restrictions slices are (notably, nested +/// slices - and thus nested vectors as well - are disallowed). +/// +/// Since a BoundedVec is backed by a normal array under the hood, growing the BoundedVec by +/// pushing an additional element is also more efficient - the length only needs to be increased +/// by one. +/// +/// For these reasons `BoundedVec` should generally be preferred over `Vec` when there +/// is a reasonable maximum bound that can be placed on the vector. +/// +/// Example: +/// +/// ```noir +/// let mut vector: BoundedVec = BoundedVec::new(); +/// for i in 0..5 { +/// vector.push(i); +/// } +/// assert(vector.len() == 5); +/// assert(vector.max_len() == 10); +/// ``` struct BoundedVec { storage: [T; MaxLen], len: u32, } impl BoundedVec { + /// Creates a new, empty vector of length zero. + /// + /// Since this container is backed by an array internally, it still needs an initial value + /// to give each element. To resolve this, each element is zeroed internally. This value + /// is guaranteed to be inaccessible unless `get_unchecked` is used. + /// + /// Example: + /// + /// ```noir + /// let empty_vector: BoundedVec = BoundedVec::new(); + /// assert(empty_vector.len() == 0); + /// ``` + /// + /// Note that whenever calling `new` the maximum length of the vector should always be specified + /// via a type signature: + /// + /// ```noir + /// fn good() -> BoundedVec { + /// // Ok! MaxLen is specified with a type annotation + /// let v1: BoundedVec = BoundedVec::new(); + /// let v2 = BoundedVec::new(); + /// + /// // Ok! MaxLen is known from the type of `good`'s return value + /// v2 + /// } + /// + /// fn bad() { + /// // Error: Type annotation needed + /// // The compiller can't infer `MaxLen` from the following code: + /// let mut v3 = BoundedVec::new(); + /// v3.push(5); + /// } + /// ``` + /// + /// This defaulting of `MaxLen` (and numeric generics in general) to zero may change in future noir versions + /// but for now make sure to use type annotations when using bounded vectors. Otherwise, you will receive a + /// constraint failure at runtime when the vec is pushed to. pub fn new() -> Self { let zeroed = crate::mem::zeroed(); BoundedVec { storage: [zeroed; MaxLen], len: 0 } } - /// Get an element from the vector at the given index. - /// Panics if the given index points beyond the end of the vector (`self.len()`). + /// Retrieves an element from the vector at the given index, starting from zero. + /// + /// If the given index is equal to or greater than the length of the vector, this + /// will issue a constraint failure. + /// + /// Example: + /// + /// ```noir + /// fn foo(v: BoundedVec) { + /// let first = v.get(0); + /// let last = v.get(v.len() - 1); + /// assert(first != last); + /// } + /// ``` pub fn get(self, index: u32) -> T { assert(index < self.len, "Attempted to read past end of BoundedVec"); self.get_unchecked(index) } - /// Get an element from the vector at the given index. - /// Responds with undefined data for `index` where `self.len < index < self.max_len()`. + /// Retrieves an element from the vector at the given index, starting from zero, without + /// performing a bounds check. + /// + /// Since this function does not perform a bounds check on length before accessing the element, + /// it is unsafe! Use at your own risk! + /// + /// Example: + /// + /// ```noir + /// fn sum_of_first_three(v: BoundedVec) -> u32 { + /// // Always ensure the length is larger than the largest + /// // index passed to get_unchecked + /// assert(v.len() > 2); + /// let first = v.get_unchecked(0); + /// let second = v.get_unchecked(1); + /// let third = v.get_unchecked(2); + /// first + second + third + /// } + /// ``` pub fn get_unchecked(self, index: u32) -> T { self.storage[index] } - /// Write an element to the vector at the given index. - /// Panics if the given index points beyond the end of the vector (`self.len()`). + /// Writes an element to the vector at the given index, starting from zero. + /// + /// If the given index is equal to or greater than the length of the vector, this will issue a constraint failure. + /// + /// Example: + /// + /// ```noir + /// fn foo(v: BoundedVec) { + /// let first = v.get(0); + /// assert(first != 42); + /// v.set(0, 42); + /// let new_first = v.get(0); + /// assert(new_first == 42); + /// } + /// ``` pub fn set(&mut self, index: u32, value: T) { assert(index < self.len, "Attempted to write past end of BoundedVec"); self.set_unchecked(index, value) } - /// Write an element to the vector at the given index. - /// Does not check whether the passed `index` is a valid index within the vector. - /// - /// Silently writes past the end of the vector for `index` where `self.len < index < self.max_len()` - /// Panics if the given index points beyond the maximum length of the vector (`self.max_len()`). + /// Writes an element to the vector at the given index, starting from zero, without performing a bounds check. + /// + /// Since this function does not perform a bounds check on length before accessing the element, it is unsafe! Use at your own risk! + /// + /// Example: + /// + /// ```noir + /// fn set_unchecked_example() { + /// let mut vec: BoundedVec = BoundedVec::new(); + /// vec.extend_from_array([1, 2]); + /// + /// // Here we're safely writing within the valid range of `vec` + /// // `vec` now has the value [42, 2] + /// vec.set_unchecked(0, 42); + /// + /// // We can then safely read this value back out of `vec`. + /// // Notice that we use the checked version of `get` which would prevent reading unsafe values. + /// assert_eq(vec.get(0), 42); + /// + /// // We've now written past the end of `vec`. + /// // As this index is still within the maximum potential length of `v`, + /// // it won't cause a constraint failure. + /// vec.set_unchecked(2, 42); + /// println(vec); + /// + /// // This will write past the end of the maximum potential length of `vec`, + /// // it will then trigger a constraint failure. + /// vec.set_unchecked(5, 42); + /// println(vec); + /// } + /// ``` pub fn set_unchecked(&mut self, index: u32, value: T) { self.storage[index] = value; } + /// Pushes an element to the end of the vector. This increases the length + /// of the vector by one. + /// + /// Panics if the new length of the vector will be greater than the max length. + /// + /// Example: + /// + /// ```noir + /// let mut v: BoundedVec = BoundedVec::new(); + /// + /// v.push(1); + /// v.push(2); + /// + /// // Panics with failed assertion "push out of bounds" + /// v.push(3); + /// ``` pub fn push(&mut self, elem: T) { assert(self.len < MaxLen, "push out of bounds"); @@ -47,20 +190,82 @@ impl BoundedVec { self.len += 1; } + /// Returns the current length of this vector + /// + /// Example: + /// + /// ```noir + /// let mut v: BoundedVec = BoundedVec::new(); + /// assert(v.len() == 0); + /// + /// v.push(100); + /// assert(v.len() == 1); + /// + /// v.push(200); + /// v.push(300); + /// v.push(400); + /// assert(v.len() == 4); + /// + /// let _ = v.pop(); + /// let _ = v.pop(); + /// assert(v.len() == 2); + /// ``` pub fn len(self) -> u32 { self.len } + /// Returns the maximum length of this vector. This is always + /// equal to the `MaxLen` parameter this vector was initialized with. + /// + /// Example: + /// + /// ```noir + /// let mut v: BoundedVec = BoundedVec::new(); + /// + /// assert(v.max_len() == 5); + /// v.push(10); + /// assert(v.max_len() == 5); + /// ``` pub fn max_len(_self: BoundedVec) -> u32 { MaxLen } - // This is a intermediate method, while we don't have an - // .extend method + /// Returns the internal array within this vector. + /// + /// Since arrays in Noir are immutable, mutating the returned storage array will not mutate + /// the storage held internally by this vector. + /// + /// Note that uninitialized elements may be zeroed out! + /// + /// Example: + /// + /// ```noir + /// let mut v: BoundedVec = BoundedVec::new(); + /// + /// assert(v.storage() == [0, 0, 0, 0, 0]); + /// + /// v.push(57); + /// assert(v.storage() == [57, 0, 0, 0, 0]); + /// ``` pub fn storage(self) -> [T; MaxLen] { self.storage } + /// Pushes each element from the given array to this vector. + /// + /// Panics if pushing each element would cause the length of this vector + /// to exceed the maximum length. + /// + /// Example: + /// + /// ```noir + /// let mut vec: BoundedVec = BoundedVec::new(); + /// vec.extend_from_array([2, 4]); + /// + /// assert(vec.len == 2); + /// assert(vec.get(0) == 2); + /// assert(vec.get(1) == 4); + /// ``` pub fn extend_from_array(&mut self, array: [T; Len]) { let new_len = self.len + array.len(); assert(new_len <= MaxLen, "extend_from_array out of bounds"); @@ -70,6 +275,21 @@ impl BoundedVec { self.len = new_len; } + /// Pushes each element from the given slice to this vector. + /// + /// Panics if pushing each element would cause the length of this vector + /// to exceed the maximum length. + /// + /// Example: + /// + /// ```noir + /// let mut vec: BoundedVec = BoundedVec::new(); + /// vec.extend_from_slice(&[2, 4]); + /// + /// assert(vec.len == 2); + /// assert(vec.get(0) == 2); + /// assert(vec.get(1) == 4); + /// ``` pub fn extend_from_slice(&mut self, slice: [T]) { let new_len = self.len + slice.len(); assert(new_len <= MaxLen, "extend_from_slice out of bounds"); @@ -79,6 +299,22 @@ impl BoundedVec { self.len = new_len; } + /// Pushes each element from the other vector to this vector. The length of + /// the other vector is left unchanged. + /// + /// Panics if pushing each element would cause the length of this vector + /// to exceed the maximum length. + /// + /// ```noir + /// let mut v1: BoundedVec = BoundedVec::new(); + /// let mut v2: BoundedVec = BoundedVec::new(); + /// + /// v2.extend_from_array([1, 2, 3]); + /// v1.extend_from_bounded_vec(v2); + /// + /// assert(v1.storage() == [1, 2, 3, 0, 0]); + /// assert(v2.storage() == [1, 2, 3, 0, 0, 0, 0]); + /// ``` pub fn extend_from_bounded_vec(&mut self, vec: BoundedVec) { let append_len = vec.len(); let new_len = self.len + append_len; @@ -94,6 +330,14 @@ impl BoundedVec { self.len = new_len; } + /// Creates a new vector, populating it with values derived from an array input. + /// The maximum length of the vector is determined based on the type signature. + /// + /// Example: + /// + /// ```noir + /// let bounded_vec: BoundedVec = BoundedVec::from_array([1, 2, 3]) + /// ``` pub fn from_array(array: [T; Len]) -> Self { assert(Len <= MaxLen, "from array out of bounds"); let mut vec: BoundedVec = BoundedVec::new(); @@ -101,6 +345,27 @@ impl BoundedVec { vec } + /// Pops the element at the end of the vector. This will decrease the length + /// of the vector by one. + /// + /// Panics if the vector is empty. + /// + /// Example: + /// + /// ```noir + /// let mut v: BoundedVec = BoundedVec::new(); + /// v.push(1); + /// v.push(2); + /// + /// let two = v.pop(); + /// let one = v.pop(); + /// + /// assert(two == 2); + /// assert(one == 1); + /// + /// // error: cannot pop from an empty vector + /// let _ = v.pop(); + /// ``` pub fn pop(&mut self) -> T { assert(self.len > 0); self.len -= 1; @@ -110,6 +375,18 @@ impl BoundedVec { elem } + /// Returns true if the given predicate returns true for any element + /// in this vector. + /// + /// Example: + /// + /// ```noir + /// let mut v: BoundedVec = BoundedVec::new(); + /// v.extend_from_array([2, 4, 6]); + /// + /// let all_even = !v.any(|elem: u32| elem % 2 != 0); + /// assert(all_even); + /// ``` pub fn any(self, predicate: fn[Env](T) -> bool) -> bool { let mut ret = false; let mut exceeded_len = false; @@ -122,6 +399,17 @@ impl BoundedVec { ret } + /// Creates a new vector of equal size by calling a closure on each element in this vector. + /// + /// Example: + /// + /// ```noir + /// let vec: BoundedVec = BoundedVec::from_array([1, 2, 3, 4]); + /// let result = vec.map(|value| value * 2); + /// + /// let expected = BoundedVec::from_array([2, 4, 6, 8]); + /// assert_eq(result, expected); + /// ``` pub fn map(self, f: fn[Env](T) -> U) -> BoundedVec { let mut ret = BoundedVec::new(); ret.len = self.len(); diff --git a/noir_stdlib/src/collections/map.nr b/noir_stdlib/src/collections/map.nr index 27a7d0d3550..e84103aafc5 100644 --- a/noir_stdlib/src/collections/map.nr +++ b/noir_stdlib/src/collections/map.nr @@ -10,14 +10,30 @@ use crate::collections::bounded_vec::BoundedVec; global MAX_LOAD_FACTOR_NUMERATOR = 3; global MAX_LOAD_FACTOR_DEN0MINATOR = 4; -// Hash table with open addressing and quadratic probing. -// Size of the underlying table must be known at compile time. -// It is advised to select capacity N as a power of two, or a prime number -// because utilized probing scheme is best tailored for it. +/// `HashMap` is used to efficiently store and look up key-value pairs. +/// +/// `HashMap` is a bounded type which can store anywhere from zero to `MaxLen` total elements. +/// Note that due to hash collisions, the actual maximum number of elements stored by any particular +/// hashmap is likely lower than `MaxLen`. This is true even with cryptographic hash functions since +/// every hash value will be performed modulo `MaxLen`. +/// +/// Example: +/// +/// ```noir +/// // Create a mapping from Fields to u32s with a maximum length of 12 +/// // using a poseidon2 hasher +/// use std::hash::poseidon2::Poseidon2Hasher; +/// let mut map: HashMap> = HashMap::default(); +/// +/// map.insert(1, 2); +/// map.insert(3, 4); +/// +/// let two = map.get(1).unwrap(); +/// ``` struct HashMap { _table: [Slot; N], - // Amount of valid elements in the map. + /// Amount of valid elements in the map. _len: u32, _build_hasher: B @@ -77,7 +93,16 @@ impl Slot { // that if we have went that far without finding desired, // it is very unlikely to be after - performance will be heavily degraded. impl HashMap { - // Creates a new instance of HashMap with specified BuildHasher. + /// Creates a hashmap with an existing `BuildHasher`. This can be used to ensure multiple + /// hashmaps are created with the same hasher instance. + /// + /// Example: + /// + /// ```noir + /// let my_hasher: BuildHasherDefault = Default::default(); + /// let hashmap: HashMap> = HashMap::with_hasher(my_hasher); + /// assert(hashmap.is_empty()); + /// ``` // docs:start:with_hasher pub fn with_hasher(_build_hasher: B) -> Self where @@ -88,7 +113,15 @@ impl HashMap { Self { _table, _len, _build_hasher } } - // Clears the map, removing all key-value entries. + /// Clears the hashmap, removing all key-value pairs from it. + /// + /// Example: + /// + /// ```noir + /// assert(!map.is_empty()); + /// map.clear(); + /// assert(map.is_empty()); + /// ``` // docs:start:clear pub fn clear(&mut self) { // docs:end:clear @@ -96,7 +129,19 @@ impl HashMap { self._len = 0; } - // Returns true if the map contains a value for the specified key. + /// Returns `true` if the hashmap contains the given key. Unlike `get`, this will not also return + /// the value associated with the key. + /// + /// Example: + /// + /// ```noir + /// if map.contains_key(7) { + /// let value = map.get(7); + /// assert(value.is_some()); + /// } else { + /// println("No value for key 7!"); + /// } + /// ``` // docs:start:contains_key pub fn contains_key( self, @@ -110,15 +155,43 @@ impl HashMap { self.get(key).is_some() } - // Returns true if the map contains no elements. + /// Returns `true` if the length of the hash map is empty. + /// + /// Example: + /// + /// ```noir + /// assert(map.is_empty()); + /// + /// map.insert(1, 2); + /// assert(!map.is_empty()); + /// + /// map.remove(1); + /// assert(map.is_empty()); + /// ``` // docs:start:is_empty pub fn is_empty(self) -> bool { // docs:end:is_empty self._len == 0 } - // Returns a BoundedVec of all valid entries in this HashMap. - // The length of the returned vector will always match the length of this HashMap. + /// Returns a vector of each key-value pair present in the hashmap. + /// + /// The length of the returned vector is always equal to the length of the hashmap. + /// + /// Example: + /// + /// ```noir + /// let entries = map.entries(); + /// + /// // The length of a hashmap may not be compile-time known, so we + /// // need to loop over its capacity instead + /// for i in 0..map.capacity() { + /// if i < entries.len() { + /// let (key, value) = entries.get(i); + /// println(f"{key} -> {value}"); + /// } + /// } + /// ``` // docs:start:entries pub fn entries(self) -> BoundedVec<(K, V), N> { // docs:end:entries @@ -138,8 +211,23 @@ impl HashMap { entries } - // Returns a BoundedVec containing all the keys within this HashMap. - // The length of the returned vector will always match the length of this HashMap. + /// Returns a vector of each key present in the hashmap. + /// + /// The length of the returned vector is always equal to the length of the hashmap. + /// + /// Example: + /// + /// ```noir + /// let keys = map.keys(); + /// + /// for i in 0..keys.max_len() { + /// if i < keys.len() { + /// let key = keys.get_unchecked(i); + /// let value = map.get(key).unwrap_unchecked(); + /// println(f"{key} -> {value}"); + /// } + /// } + /// ``` // docs:start:keys pub fn keys(self) -> BoundedVec { // docs:end:keys @@ -158,8 +246,22 @@ impl HashMap { keys } - // Returns a BoundedVec containing all the values within this HashMap. - // The length of the returned vector will always match the length of this HashMap. + /// Returns a vector of each value present in the hashmap. + /// + /// The length of the returned vector is always equal to the length of the hashmap. + /// + /// Example: + /// + /// ```noir + /// let values = map.values(); + /// + /// for i in 0..values.max_len() { + /// if i < values.len() { + /// let value = values.get_unchecked(i); + /// println(f"Found value {value}"); + /// } + /// } + /// ``` // docs:start:values pub fn values(self) -> BoundedVec { // docs:end:values @@ -180,7 +282,22 @@ impl HashMap { values } - // For each key-value entry applies mutator function. + /// Iterates through each key-value pair of the HashMap, setting each key-value pair to the + /// result returned from the given function. + /// + /// Note that since keys can be mutated, the HashMap needs to be rebuilt as it is iterated + /// through. If this is not desired, use `iter_values_mut` if only values need to be mutated, + /// or `entries` if neither keys nor values need to be mutated. + /// + /// The iteration order is left unspecified. As a result, if two keys are mutated to become + /// equal, which of the two values that will be present for the key in the resulting map is also unspecified. + /// + /// Example: + /// + /// ```noir + /// // Add 1 to each key in the map, and double the value associated with that key. + /// map.iter_mut(|k, v| (k + 1, v * 2)); + /// ``` // docs:start:iter_mut pub fn iter_mut( &mut self, @@ -205,7 +322,22 @@ impl HashMap { self._table = new_map._table; } - // For each key applies mutator function. + /// Iterates through the HashMap, mutating each key to the result returned from + /// the given function. + /// + /// Note that since keys can be mutated, the HashMap needs to be rebuilt as it is iterated + /// through. If only iteration is desired and the keys are not intended to be mutated, + /// prefer using `entries` instead. + /// + /// The iteration order is left unspecified. As a result, if two keys are mutated to become + /// equal, which of the two values that will be present for the key in the resulting map is also unspecified. + /// + /// Example: + /// + /// ```noir + /// // Double each key, leaving the value associated with that key untouched + /// map.iter_keys_mut(|k| k * 2); + /// ``` // docs:start:iter_keys_mut pub fn iter_keys_mut( &mut self, @@ -230,7 +362,16 @@ impl HashMap { self._table = new_map._table; } - // For each value applies mutator function. + /// Iterates through the HashMap, applying the given function to each value and mutating the + /// value to equal the result. This function is more efficient than `iter_mut` and `iter_keys_mut` + /// because the keys are untouched and the underlying hashmap thus does not need to be reordered. + /// + /// Example: + /// + /// ```noir + /// // Halve each value + /// map.iter_values_mut(|v| v / 2); + /// ``` // docs:start:iter_values_mut pub fn iter_values_mut(&mut self, f: fn(V) -> V) { // docs:end:iter_values_mut @@ -244,7 +385,14 @@ impl HashMap { } } - // Retains only the elements specified by the predicate. + /// Retains only the key-value pairs for which the given function returns true. + /// Any key-value pairs for which the function returns false will be removed from the map. + /// + /// Example: + /// + /// ```noir + /// map.retain(|k, v| (k != 0) & (v != 0)); + /// ``` // docs:start:retain pub fn retain(&mut self, f: fn(K, V) -> bool) { // docs:end:retain @@ -261,21 +409,67 @@ impl HashMap { } } - // Amount of active key-value entries. + /// Returns the current length of this hash map. + /// + /// Example: + /// + /// ```noir + /// // This is equivalent to checking map.is_empty() + /// assert(map.len() == 0); + /// + /// map.insert(1, 2); + /// map.insert(3, 4); + /// map.insert(5, 6); + /// assert(map.len() == 3); + /// + /// // 3 was already present as a key in the hash map, so the length is unchanged + /// map.insert(3, 7); + /// assert(map.len() == 3); + /// + /// map.remove(1); + /// assert(map.len() == 2); + /// ``` // docs:start:len pub fn len(self) -> u32 { // docs:end:len self._len } - // Get the compile-time map capacity. + /// Returns the maximum capacity of this hashmap. This is always equal to the capacity + /// specified in the hashmap's type. + /// + /// Unlike hashmaps in general purpose programming languages, hashmaps in Noir have a + /// static capacity that does not increase as the map grows larger. Thus, this capacity + /// is also the maximum possible element count that can be inserted into the hashmap. + /// Due to hash collisions (modulo the hashmap length), it is likely the actual maximum + /// element count will be lower than the full capacity. + /// + /// Example: + /// + /// ```noir + /// let empty_map: HashMap> = HashMap::default(); + /// assert(empty_map.len() == 0); + /// assert(empty_map.capacity() == 42); + /// ``` // docs:start:capacity pub fn capacity(_self: Self) -> u32 { // docs:end:capacity N } - // Get the value by key. If it does not exist, returns none(). + /// Retrieves a value from the hashmap, returning `Option::none()` if it was not found. + /// + /// Example: + /// + /// ```noir + /// fn get_example(map: HashMap>) { + /// let x = map.get(12); + /// + /// if x.is_some() { + /// assert(x.unwrap() == 42); + /// } + /// } + /// ``` // docs:start:get pub fn get( self, @@ -310,7 +504,16 @@ impl HashMap { result } - // Insert key-value entry. In case key was already present, value is overridden. + /// Inserts a new key-value pair into the map. If the key was already in the map, its + /// previous value will be overridden with the newly provided one. + /// + /// Example: + /// + /// ```noir + /// let mut map: HashMap> = HashMap::default(); + /// map.insert(12, 42); + /// assert(map.len() == 1); + /// ``` // docs:start:insert pub fn insert( &mut self, @@ -353,7 +556,23 @@ impl HashMap { } } - // Removes a key-value entry. If key is not present, HashMap remains unchanged. + /// Removes the given key-value pair from the map. If the key was not already present + /// in the map, this does nothing. + /// + /// Example: + /// + /// ```noir + /// let mut map: HashMap> = HashMap::default(); + /// map.insert(12, 42); + /// assert(!map.is_empty()); + /// + /// map.remove(12); + /// assert(map.is_empty()); + /// + /// // If a key was not present in the map, remove does nothing + /// map.remove(12); + /// assert(map.is_empty()); + /// ``` // docs:start:remove pub fn remove( &mut self, @@ -432,6 +651,22 @@ where B: BuildHasher, H: Hasher { + /// Checks if two HashMaps are equal. + /// + /// Example: + /// + /// ```noir + /// let mut map1: HashMap> = HashMap::default(); + /// let mut map2: HashMap> = HashMap::default(); + /// + /// map1.insert(1, 2); + /// map1.insert(3, 4); + /// + /// map2.insert(3, 4); + /// map2.insert(1, 2); + /// + /// assert(map1 == map2); + /// ``` fn eq(self, other: HashMap) -> bool { // docs:end:eq let mut equal = false; @@ -466,8 +701,16 @@ where B: BuildHasher + Default, H: Hasher + Default { + /// Constructs an empty HashMap. + /// + /// Example: + /// + /// ```noir + /// let hashmap: HashMap> = HashMap::default(); + /// assert(hashmap.is_empty()); + /// ``` fn default() -> Self { -// docs:end:default + // docs:end:default let _build_hasher = B::default(); let map: HashMap = HashMap::with_hasher(_build_hasher); map diff --git a/noir_stdlib/src/hash/poseidon/bn254/consts.nr b/noir_stdlib/src/hash/poseidon/bn254/consts.nr index 81d78377ce8..3b28ced5835 100644 --- a/noir_stdlib/src/hash/poseidon/bn254/consts.nr +++ b/noir_stdlib/src/hash/poseidon/bn254/consts.nr @@ -5,11 +5,6 @@ // Consistent with https://github.com/iden3/circomlib/blob/master/circuits/poseidon.circom and https://github.com/iden3/circomlib/blob/master/circuits/poseidon_constants.circom use crate::hash::poseidon::PoseidonConfig; use crate::hash::poseidon::config; -// Number of full rounds -// Number of partial rounds -fn rp() -> [u8; 16] { - [56, 57, 56, 60, 60, 63, 64, 63, 60, 66, 60, 65, 70, 60, 64, 68] -} // S-box power fn alpha() -> Field { 5 diff --git a/noir_stdlib/src/hash/poseidon/mod.nr b/noir_stdlib/src/hash/poseidon/mod.nr index 963808f6053..cf9b6187c02 100644 --- a/noir_stdlib/src/hash/poseidon/mod.nr +++ b/noir_stdlib/src/hash/poseidon/mod.nr @@ -1,5 +1,4 @@ mod bn254; // Instantiations of Poseidon for prime field of the same order as BN254 -use crate::field::modulus_num_bits; use crate::hash::Hasher; use crate::default::Default; @@ -166,13 +165,6 @@ fn sigma(x: [Field; O]) -> [Field; O] { y } -// Check security of sponge instantiation -fn check_security(rate: Field, width: Field, security: Field) -> bool { - let n = modulus_num_bits(); - - ((n - 1) as Field * (width - rate) / 2) as u8 > security as u8 -} - struct PoseidonHasher{ _state: [Field], } diff --git a/noir_stdlib/src/lib.nr b/noir_stdlib/src/lib.nr index 81c408c01bc..714079d306a 100644 --- a/noir_stdlib/src/lib.nr +++ b/noir_stdlib/src/lib.nr @@ -28,6 +28,7 @@ mod runtime; mod meta; mod append; mod mem; +mod panic; // Oracle calls are required to be wrapped in an unconstrained function // Thus, the only argument to the `println` oracle is expected to always be an ident diff --git a/noir_stdlib/src/meta/expr.nr b/noir_stdlib/src/meta/expr.nr index 43638ad791b..642dbecc36b 100644 --- a/noir_stdlib/src/meta/expr.nr +++ b/noir_stdlib/src/meta/expr.nr @@ -66,6 +66,11 @@ impl Expr { fn as_index(self) -> Option<(Expr, Expr)> {} // docs:end:as_index + #[builtin(expr_as_let)] + // docs:start:as_let + fn as_let(self) -> Option<(Expr, Option, Expr)> {} + // docs:end:as_let + #[builtin(expr_as_member_access)] // docs:start:as_member_access fn as_member_access(self) -> Option<(Expr, Quoted)> {} @@ -134,6 +139,7 @@ impl Expr { 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_let(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)); @@ -153,7 +159,7 @@ impl Expr { } // docs:start:quoted - fn quoted(self) -> Quoted { + comptime fn quoted(self) -> Quoted { // docs:end:quoted quote { $self } } @@ -280,6 +286,17 @@ fn modify_index(expr: Expr, f: fn[Env](Expr) -> Option) -> Option(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { + expr.as_let().map( + |expr: (Expr, Option, Expr)| { + let (pattern, typ, expr) = expr; + let pattern = pattern.modify(f); + let expr = expr.modify(f); + new_let(pattern, typ, expr) + } + ) +} + fn modify_member_access(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { expr.as_member_access().map( |expr: (Expr, Quoted)| { @@ -364,12 +381,12 @@ fn modify_expressions(exprs: [Expr], f: fn[Env](Expr) -> Option) -> [ exprs.map(|expr: Expr| expr.modify(f)) } -fn new_array(exprs: [Expr]) -> Expr { +comptime 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 { +comptime fn new_assert(predicate: Expr, msg: Option) -> Expr { if msg.is_some() { let msg = msg.unwrap(); quote { assert($predicate, $msg) }.as_expr().unwrap() @@ -378,7 +395,7 @@ fn new_assert(predicate: Expr, msg: Option) -> Expr { } } -fn new_assert_eq(lhs: Expr, rhs: Expr, msg: Option) -> Expr { +comptime fn new_assert_eq(lhs: Expr, rhs: Expr, msg: Option) -> Expr { if msg.is_some() { let msg = msg.unwrap(); quote { assert_eq($lhs, $rhs, $msg) }.as_expr().unwrap() @@ -387,30 +404,30 @@ fn new_assert_eq(lhs: Expr, rhs: Expr, msg: Option) -> Expr { } } -fn new_assign(lhs: Expr, rhs: Expr) -> Expr { +comptime 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 { +comptime 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 { +comptime 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 { +comptime fn new_cast(expr: Expr, typ: UnresolvedType) -> Expr { quote { ($expr) as $typ }.as_expr().unwrap() } -fn new_comptime(exprs: [Expr]) -> Expr { +comptime 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 { +comptime 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() @@ -419,21 +436,30 @@ fn new_if(condition: Expr, consequence: Expr, alternative: Option) -> Expr } } -fn new_index(object: Expr, index: Expr) -> Expr { +comptime fn new_index(object: Expr, index: Expr) -> Expr { quote { $object[$index] }.as_expr().unwrap() } -fn new_member_access(object: Expr, name: Quoted) -> Expr { +comptime fn new_let(pattern: Expr, typ: Option, expr: Expr) -> Expr { + if typ.is_some() { + let typ = typ.unwrap(); + quote { let $pattern : $typ = $expr; }.as_expr().unwrap() + } else { + quote { let $pattern = $expr; }.as_expr().unwrap() + } +} + +comptime fn new_member_access(object: Expr, name: Quoted) -> Expr { quote { $object.$name }.as_expr().unwrap() } -fn new_function_call(function: Expr, arguments: [Expr]) -> Expr { +comptime 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 { +comptime fn new_method_call(object: Expr, name: Quoted, generics: [UnresolvedType], arguments: [Expr]) -> Expr { let arguments = join_expressions(arguments, quote { , }); if generics.len() == 0 { @@ -444,30 +470,30 @@ fn new_method_call(object: Expr, name: Quoted, generics: [UnresolvedType], argum } } -fn new_repeated_element_array(expr: Expr, length: Expr) -> Expr { +comptime 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 { +comptime fn new_repeated_element_slice(expr: Expr, length: Expr) -> Expr { quote { &[$expr; $length] }.as_expr().unwrap() } -fn new_slice(exprs: [Expr]) -> Expr { +comptime fn new_slice(exprs: [Expr]) -> Expr { let exprs = join_expressions(exprs, quote { , }); quote { &[$exprs]}.as_expr().unwrap() } -fn new_tuple(exprs: [Expr]) -> Expr { +comptime 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 { +comptime 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 { +comptime fn new_unsafe(exprs: [Expr]) -> Expr { let exprs = join_expressions(exprs, quote { ; }); quote { unsafe { $exprs }}.as_expr().unwrap() } diff --git a/noir_stdlib/src/meta/function_def.nr b/noir_stdlib/src/meta/function_def.nr index 0bff86ef102..5ce3dbeabff 100644 --- a/noir_stdlib/src/meta/function_def.nr +++ b/noir_stdlib/src/meta/function_def.nr @@ -14,6 +14,16 @@ impl FunctionDefinition { fn has_named_attribute(self, name: Quoted) -> bool {} // docs:end:has_named_attribute + #[builtin(function_def_is_unconstrained)] + // docs:start:is_unconstrained + fn is_unconstrained(self) -> bool {} + // docs:end:is_unconstrained + + #[builtin(function_def_module)] + // docs:start:module + fn module(self) -> Module {} + // docs:end:module + #[builtin(function_def_name)] // docs:start:name fn name(self) -> Quoted {} @@ -48,4 +58,9 @@ impl FunctionDefinition { // docs:start:set_return_public fn set_return_public(self, public: bool) {} // docs:end:set_return_public + + #[builtin(function_def_set_unconstrained)] + // docs:start:set_unconstrained + fn set_unconstrained(self, value: bool) {} + // docs:end:set_unconstrained } diff --git a/noir_stdlib/src/meta/mod.nr b/noir_stdlib/src/meta/mod.nr index 9fc399ddbf9..1079cc6013a 100644 --- a/noir_stdlib/src/meta/mod.nr +++ b/noir_stdlib/src/meta/mod.nr @@ -122,6 +122,7 @@ mod tests { quote { 1 } } + #[test] fn returning_versus_macro_insertion() { comptime { diff --git a/noir_stdlib/src/meta/module.nr b/noir_stdlib/src/meta/module.nr index b3f76812b8a..bee6612e1bf 100644 --- a/noir_stdlib/src/meta/module.nr +++ b/noir_stdlib/src/meta/module.nr @@ -1,4 +1,9 @@ impl Module { + #[builtin(module_add_item)] + // docs:start:add_item + fn add_item(self, item: Quoted) {} + // docs:end:add_item + #[builtin(module_has_named_attribute)] // docs:start:has_named_attribute fn has_named_attribute(self, name: Quoted) -> bool {} diff --git a/noir_stdlib/src/meta/op.nr b/noir_stdlib/src/meta/op.nr index f3060a1648b..4b104486201 100644 --- a/noir_stdlib/src/meta/op.nr +++ b/noir_stdlib/src/meta/op.nr @@ -28,7 +28,7 @@ impl UnaryOp { } // docs:start:unary_quoted - pub fn quoted(self) -> Quoted { + pub comptime fn quoted(self) -> Quoted { // docs:end:unary_quoted if self.is_minus() { quote { - } @@ -39,7 +39,8 @@ impl UnaryOp { } else if self.is_dereference() { quote { * } } else { - crate::mem::zeroed() + let op = self; + crate::panic::panic(f"Unexpected unary operator in UnaryOp::quoted: {op}") } } } @@ -146,7 +147,7 @@ impl BinaryOp { } // docs:start:binary_quoted - pub fn quoted(self) -> Quoted { + pub comptime fn quoted(self) -> Quoted { // docs:end:binary_quoted if self.is_add() { quote { + } @@ -181,7 +182,8 @@ impl BinaryOp { } else if self.is_modulo() { quote { % } } else { - crate::mem::zeroed() + let op = self; + crate::panic::panic(f"Unexpected binary operator in BinaryOp::quoted: {op}") } } } diff --git a/noir_stdlib/src/meta/struct_def.nr b/noir_stdlib/src/meta/struct_def.nr index 6c0270a8eec..5db720b91d3 100644 --- a/noir_stdlib/src/meta/struct_def.nr +++ b/noir_stdlib/src/meta/struct_def.nr @@ -4,10 +4,15 @@ impl StructDefinition { fn add_attribute(self, attribute: str) {} // docs:end:add_attribute + #[builtin(struct_def_add_generic)] + // docs:start:add_generic + fn add_generic(self, generic_name: str) -> Type {} + // docs:end:add_generic + /// 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 + // docs:start:as_type fn as_type(self) -> Type {} // docs:end:as_type @@ -18,17 +23,27 @@ impl StructDefinition { /// Return each generic on this struct. #[builtin(struct_def_generics)] -// docs:start: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 + // docs:start:fields fn fields(self) -> [(Quoted, Type)] {} // docs:end:fields + #[builtin(struct_def_module)] + // docs:start:module + fn module(self) -> Module {} + // docs:end:module + + #[builtin(struct_def_name)] + // docs:start:name + fn name(self) -> Quoted {} + // docs:end:name + /// Sets the fields of this struct to the given fields list. /// All existing fields of the struct will be overridden with the given fields. /// Each element of the fields list corresponds to the name and type of a field. diff --git a/noir_stdlib/src/meta/typ.nr b/noir_stdlib/src/meta/typ.nr index 12dc91a4925..71bd6fd7f1c 100644 --- a/noir_stdlib/src/meta/typ.nr +++ b/noir_stdlib/src/meta/typ.nr @@ -1,6 +1,11 @@ use crate::cmp::Eq; use crate::option::Option; +#[builtin(fresh_type_variable)] +// docs:start:fresh_type_variable +pub fn fresh_type_variable() -> Type {} +// docs:end:fresh_type_variable + impl Type { #[builtin(type_as_array)] // docs:start:as_array diff --git a/noir_stdlib/src/panic.nr b/noir_stdlib/src/panic.nr new file mode 100644 index 00000000000..833c1f3dcfc --- /dev/null +++ b/noir_stdlib/src/panic.nr @@ -0,0 +1,4 @@ +pub fn panic(message: fmtstr) -> U { + assert(false, message); + crate::mem::zeroed() +} diff --git a/noir_stdlib/src/prelude.nr b/noir_stdlib/src/prelude.nr index b14f38bdf55..b6e54eaae60 100644 --- a/noir_stdlib/src/prelude.nr +++ b/noir_stdlib/src/prelude.nr @@ -7,3 +7,4 @@ pub use crate::cmp::{Eq, Ord}; pub use crate::default::Default; pub use crate::convert::{From, Into}; pub use crate::meta::{derive, derive_via}; +pub use crate::panic::panic; diff --git a/test_programs/compile_failure/arithmetic_generics_intermediate_underflow/Nargo.toml b/test_programs/compile_failure/arithmetic_generics_intermediate_underflow/Nargo.toml new file mode 100644 index 00000000000..0c5d98628a1 --- /dev/null +++ b/test_programs/compile_failure/arithmetic_generics_intermediate_underflow/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "arithmetic_generics_intermediate_underflow" +type = "bin" +authors = [""] +compiler_version = ">=0.33.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/arithmetic_generics_intermediate_underflow/src/main.nr b/test_programs/compile_failure/arithmetic_generics_intermediate_underflow/src/main.nr new file mode 100644 index 00000000000..58cf2f648e5 --- /dev/null +++ b/test_programs/compile_failure/arithmetic_generics_intermediate_underflow/src/main.nr @@ -0,0 +1,32 @@ +// docs:start:intermediate-underflow-example +fn main() { + // From main it looks like there's nothing sketchy going on + seems_fine([]); +} + +// Since `seems_fine` says it can receive and return any length N +fn seems_fine(array: [Field; N]) -> [Field; N] { + // But inside `seems_fine` we pop from the array which + // requires the length to be greater than zero. + + // error: Could not determine array length `(0 - 1)` + push_zero(pop(array)) +} + +fn pop(array: [Field; N]) -> [Field; N - 1] { + let mut result: [Field; N - 1] = std::mem::zeroed(); + for i in 0..N { + result[i] = array[i]; + } + result +} + +fn push_zero(array: [Field; N]) -> [Field; N + 1] { + let mut result: [Field; N + 1] = std::mem::zeroed(); + for i in 0..N { + result[i] = array[i]; + } + // index N is already zeroed + result +} +// docs:end:intermediate-underflow-example diff --git a/test_programs/compile_failure/arithmetic_generics_underflow/Nargo.toml b/test_programs/compile_failure/arithmetic_generics_underflow/Nargo.toml new file mode 100644 index 00000000000..f024f4c3b59 --- /dev/null +++ b/test_programs/compile_failure/arithmetic_generics_underflow/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "arithmetic_generics_underflow" +type = "bin" +authors = [""] +compiler_version = ">=0.33.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/arithmetic_generics_underflow/src/main.nr b/test_programs/compile_failure/arithmetic_generics_underflow/src/main.nr new file mode 100644 index 00000000000..4df83ac56e0 --- /dev/null +++ b/test_programs/compile_failure/arithmetic_generics_underflow/src/main.nr @@ -0,0 +1,14 @@ +// docs:start:underflow-example +fn pop(array: [Field; N]) -> [Field; N - 1] { + let mut result: [Field; N - 1] = std::mem::zeroed(); + for i in 0..N { + result[i] = array[i]; + } + result +} + +fn main() { + // error: Could not determine array length `(0 - 1)` + pop([]); +} +// docs:end:underflow-example diff --git a/test_programs/compile_failure/quote_at_runtime/Nargo.toml b/test_programs/compile_failure/quote_at_runtime/Nargo.toml new file mode 100644 index 00000000000..3ce62412b32 --- /dev/null +++ b/test_programs/compile_failure/quote_at_runtime/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "quote_at_runtime" +type = "bin" +authors = [""] +compiler_version = ">=0.33.0" + +[dependencies] diff --git a/test_programs/compile_failure/quote_at_runtime/src/main.nr b/test_programs/compile_failure/quote_at_runtime/src/main.nr new file mode 100644 index 00000000000..5da409682f6 --- /dev/null +++ b/test_programs/compile_failure/quote_at_runtime/src/main.nr @@ -0,0 +1,7 @@ +fn main() { + foo(quote { test }) +} + +fn foo(q: Quoted) { + println(q); +} diff --git a/test_programs/compile_success_empty/attributes_struct/src/main.nr b/test_programs/compile_success_empty/attributes_struct/src/main.nr index 25c9402035c..79bf3e29533 100644 --- a/test_programs/compile_success_empty/attributes_struct/src/main.nr +++ b/test_programs/compile_success_empty/attributes_struct/src/main.nr @@ -14,7 +14,7 @@ struct Foo { } -fn add_attribute(s: StructDefinition) { +comptime fn add_attribute(s: StructDefinition) { assert(!s.has_named_attribute(quote { foo })); s.add_attribute("foo"); assert(s.has_named_attribute(quote { foo })); diff --git a/test_programs/compile_success_empty/comptime_function_definition/src/main.nr b/test_programs/compile_success_empty/comptime_function_definition/src/main.nr index 48651022b31..6bfd8ef9699 100644 --- a/test_programs/compile_success_empty/comptime_function_definition/src/main.nr +++ b/test_programs/compile_success_empty/comptime_function_definition/src/main.nr @@ -69,3 +69,20 @@ contract some_contract { fn set_pub_return(f: FunctionDefinition) { f.set_return_public(true); } + +mod foo { + #[attr] + pub fn some() {} + + comptime fn attr(f: FunctionDefinition) { + assert_eq(f.module().name(), quote { foo }); + + assert(!f.is_unconstrained()); + + f.set_unconstrained(true); + assert(f.is_unconstrained()); + + f.set_unconstrained(false); + assert(!f.is_unconstrained()); + } +} diff --git a/test_programs/compile_success_empty/comptime_global_using_trait/Nargo.toml b/test_programs/compile_success_empty/comptime_global_using_trait/Nargo.toml new file mode 100644 index 00000000000..2de6e4d149e --- /dev/null +++ b/test_programs/compile_success_empty/comptime_global_using_trait/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "comptime_global_using_trait" +type = "bin" +authors = [""] +compiler_version = ">=0.33.0" + +[dependencies] diff --git a/test_programs/compile_success_empty/comptime_global_using_trait/src/main.nr b/test_programs/compile_success_empty/comptime_global_using_trait/src/main.nr new file mode 100644 index 00000000000..a1a2c4b125a --- /dev/null +++ b/test_programs/compile_success_empty/comptime_global_using_trait/src/main.nr @@ -0,0 +1,3 @@ +comptime global FOO: i32 = Default::default(); + +fn main() {} diff --git a/test_programs/compile_success_empty/comptime_module/src/main.nr b/test_programs/compile_success_empty/comptime_module/src/main.nr index 1d1690c4017..c3ba7ba4643 100644 --- a/test_programs/compile_success_empty/comptime_module/src/main.nr +++ b/test_programs/compile_success_empty/comptime_module/src/main.nr @@ -21,27 +21,43 @@ mod separate_module; comptime mut global counter = 0; -fn increment_counter() { +comptime fn increment_counter() { counter += 1; } -fn outer_attribute_func(m: Module) -> Quoted { +comptime fn outer_attribute_func(m: Module) -> Quoted { assert_eq(m.name(), quote { yet_another_module }); increment_counter(); quote { pub fn generated_outer_function() {} } } -fn inner_attribute_func(m: Module) -> Quoted { +comptime fn inner_attribute_func(m: Module) -> Quoted { assert_eq(m.name(), quote { yet_another_module }); increment_counter(); quote { pub fn generated_inner_function() {} } } -fn outer_attribute_separate_module(m: Module) { +comptime fn outer_attribute_separate_module(m: Module) { assert_eq(m.name(), quote { separate_module }); increment_counter(); } +struct Foo {} + +#[add_function] +mod add_to_me { + fn add_to_me_function() {} +} + +comptime fn add_function(m: Module) { + m.add_item( + quote { pub fn added_function() -> super::Foo { + add_to_me_function(); + super::Foo {} + } } + ); +} + fn main() { comptime { @@ -73,6 +89,8 @@ fn main() { yet_another_module::generated_outer_function(); yet_another_module::generated_inner_function(); + + let _ = add_to_me::added_function(); } // docs:start:as_module_example diff --git a/test_programs/compile_success_empty/comptime_module/src/separate_module.nr b/test_programs/compile_success_empty/comptime_module/src/separate_module.nr index 53784101507..c1aeac4622c 100644 --- a/test_programs/compile_success_empty/comptime_module/src/separate_module.nr +++ b/test_programs/compile_success_empty/comptime_module/src/separate_module.nr @@ -1,5 +1,6 @@ #![inner_attribute_separate_module] -fn inner_attribute_separate_module(m: Module) { + +comptime fn inner_attribute_separate_module(m: Module) { assert_eq(m.name(), quote { separate_module }); super::increment_counter(); } diff --git a/test_programs/compile_success_empty/comptime_type_definition/Nargo.toml b/test_programs/compile_success_empty/comptime_struct_definition/Nargo.toml similarity index 69% rename from test_programs/compile_success_empty/comptime_type_definition/Nargo.toml rename to test_programs/compile_success_empty/comptime_struct_definition/Nargo.toml index 099545a9e71..4495d27e028 100644 --- a/test_programs/compile_success_empty/comptime_type_definition/Nargo.toml +++ b/test_programs/compile_success_empty/comptime_struct_definition/Nargo.toml @@ -1,5 +1,5 @@ [package] -name = "comptime_type_definition" +name = "comptime_struct_definition" type = "bin" authors = [""] compiler_version = ">=0.31.0" diff --git a/test_programs/compile_success_empty/comptime_struct_definition/src/main.nr b/test_programs/compile_success_empty/comptime_struct_definition/src/main.nr new file mode 100644 index 00000000000..da2871a253d --- /dev/null +++ b/test_programs/compile_success_empty/comptime_struct_definition/src/main.nr @@ -0,0 +1,50 @@ +#[my_comptime_fn] +struct MyType { + field1: [A; 10], + field2: (B, C), +} + +#[mutate_struct_fields] +struct I32AndField { + z: i8, +} + +comptime fn my_comptime_fn(typ: StructDefinition) { + let _ = typ.as_type(); + assert_eq(typ.generics().len(), 3); + assert_eq(typ.fields().len(), 2); + assert_eq(typ.name(), quote { MyType }); +} + +comptime fn mutate_struct_fields(s: StructDefinition) { + let fields = &[ + (quote[x], quote[i32].as_type()), + (quote[y], quote[Field].as_type()) + ]; + s.set_fields(fields); +} + +mod foo { + #[attr] + struct Foo {} + + comptime fn attr(s: StructDefinition) { + assert_eq(s.module().name(), quote { foo }); + } + + #[add_generic] + struct Bar {} + + // docs:start:add-generic-example + comptime fn add_generic(s: StructDefinition) { + assert_eq(s.generics().len(), 0); + let new_generic = s.add_generic("T"); + + let generics = s.generics(); + assert_eq(generics.len(), 1); + assert_eq(generics[0], new_generic); + } + // docs:end:add-generic-example +} + +fn main() {} diff --git a/test_programs/compile_success_empty/comptime_type/src/main.nr b/test_programs/compile_success_empty/comptime_type/src/main.nr index 0b15c5605b3..c9307570c87 100644 --- a/test_programs/compile_success_empty/comptime_type/src/main.nr +++ b/test_programs/compile_success_empty/comptime_type/src/main.nr @@ -19,6 +19,18 @@ struct StructDoesNotImplementSomeTrait { } +// docs:start:serialize-setup +trait Serialize {} + +impl Serialize<1> for Field {} + +impl Serialize for [T; N] + where T: Serialize {} + +impl Serialize for (T, U) + where T: Serialize, U: Serialize {} +// docs:end:serialize-setup + fn main() { comptime { @@ -115,6 +127,29 @@ fn main() { let str_type = quote { str<10> }.as_type(); let constant = str_type.as_str().unwrap(); assert_eq(constant.as_constant().unwrap(), 10); + + // Check std::meta::typ::fresh_type_variable + // docs:start:fresh-type-variable-example + let typevar1 = std::meta::typ::fresh_type_variable(); + let constraint = quote { Serialize<$typevar1> }.as_trait_constraint(); + let field_type = quote { Field }.as_type(); + + // Search for a trait impl (binding typevar1 to 1 when the impl is found): + assert(field_type.implements(constraint)); + + // typevar1 should be bound to the "1" generic now: + assert_eq(typevar1.as_constant().unwrap(), 1); + + // If we want to do the same with a different type, we need to + // create a new type variable now that `typevar1` is bound + let typevar2 = std::meta::typ::fresh_type_variable(); + let constraint = quote { Serialize<$typevar2> }.as_trait_constraint(); + let array_type = quote { [(Field, Field); 5] }.as_type(); + assert(array_type.implements(constraint)); + + // Now typevar2 should be bound to the serialized pair size 2 times the array length 5 + assert_eq(typevar2.as_constant().unwrap(), 10); + // docs:end:fresh-type-variable-example } } diff --git a/test_programs/compile_success_empty/comptime_type_definition/src/main.nr b/test_programs/compile_success_empty/comptime_type_definition/src/main.nr deleted file mode 100644 index aca8d067dde..00000000000 --- a/test_programs/compile_success_empty/comptime_type_definition/src/main.nr +++ /dev/null @@ -1,26 +0,0 @@ -fn main() {} - -#[my_comptime_fn] -struct MyType { - field1: [A; 10], - field2: (B, C), -} - -#[mutate_struct_fields] -struct I32AndField { - z: i8, -} - -comptime fn my_comptime_fn(typ: StructDefinition) { - let _ = typ.as_type(); - assert_eq(typ.generics().len(), 3); - assert_eq(typ.fields().len(), 2); -} - -comptime fn mutate_struct_fields(s: StructDefinition) { - let fields = &[ - (quote[x], quote[i32].as_type()), - (quote[y], quote[Field].as_type()) - ]; - s.set_fields(fields); -} diff --git a/test_programs/compile_success_empty/inject_context_attribute/src/main.nr b/test_programs/compile_success_empty/inject_context_attribute/src/main.nr index 26be3135133..9dc8cf1ed47 100644 --- a/test_programs/compile_success_empty/inject_context_attribute/src/main.nr +++ b/test_programs/compile_success_empty/inject_context_attribute/src/main.nr @@ -28,7 +28,7 @@ fn zero() -> Field { 0 } -fn inject_context(f: FunctionDefinition) { +comptime 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())); @@ -39,7 +39,7 @@ fn inject_context(f: FunctionDefinition) { f.set_body(body); } -fn mapping_function(expr: Expr, f: FunctionDefinition) -> Option { +comptime fn mapping_function(expr: Expr, f: FunctionDefinition) -> Option { expr.as_function_call().and_then( |func_call: (Expr, [Expr])| { let (name, arguments) = func_call; diff --git a/test_programs/compile_success_empty/references_aliasing/src/main.nr b/test_programs/compile_success_empty/references_aliasing/src/main.nr index 0d96bc2a734..d3e4257851b 100644 --- a/test_programs/compile_success_empty/references_aliasing/src/main.nr +++ b/test_programs/compile_success_empty/references_aliasing/src/main.nr @@ -5,6 +5,7 @@ fn main() { assert(*xref == 101); regression_2445(); + single_alias_inside_loop(); } fn increment(mut r: &mut Field) { @@ -26,3 +27,15 @@ fn regression_2445() { assert(**array[0] == 2); assert(**array[1] == 2); } + +fn single_alias_inside_loop() { + let mut var = 0; + let ref = &mut &mut var; + + for _ in 0..1 { + **ref = 2; + } + + assert(var == 2); + assert(**ref == 2); +} diff --git a/test_programs/execution_success/derive/src/main.nr b/test_programs/execution_success/derive/src/main.nr index 5ec2fb32a79..b32612831d7 100644 --- a/test_programs/execution_success/derive/src/main.nr +++ b/test_programs/execution_success/derive/src/main.nr @@ -35,6 +35,9 @@ struct MyOtherOtherStruct { x: T, } +#[derive(Eq, Default, Hash, Ord)] +struct EmptyStruct { } + fn main() { let s = MyStruct { my_field: 1 }; s.do_nothing(); @@ -53,6 +56,9 @@ fn main() { let mut hasher = TestHasher { result: 0 }; o1.hash(&mut hasher); assert_eq(hasher.finish(), 12 + 24 + 54); + + let empty = EmptyStruct {}; + assert_eq(empty, empty); } struct TestHasher { diff --git a/test_programs/noir_test_success/bounded_vec/src/main.nr b/test_programs/noir_test_success/bounded_vec/src/main.nr index e5aa5f88a94..7b3e63df072 100644 --- a/test_programs/noir_test_success/bounded_vec/src/main.nr +++ b/test_programs/noir_test_success/bounded_vec/src/main.nr @@ -1,6 +1,6 @@ #[test] -fn test_vec_new_foo() { - foo(); +fn test_vec_new_good() { + good(); } #[test(should_fail)] @@ -9,19 +9,19 @@ fn test_vec_new_bad() { } // docs:start:new_example -fn foo() -> BoundedVec { +fn good() -> BoundedVec { // Ok! MaxLen is specified with a type annotation let v1: BoundedVec = BoundedVec::new(); let v2 = BoundedVec::new(); - // Ok! MaxLen is known from the type of foo's return value + // Ok! MaxLen is known from the type of `good`'s return value v2 } fn bad() { + // Error: Type annotation needed + // The compiller can't infer `MaxLen` from this code. let mut v3 = BoundedVec::new(); - - // Not Ok! We don't know if v3's MaxLen is at least 1, and the compiler often infers 0 by default. v3.push(5); } // docs:end:new_example diff --git a/test_programs/noir_test_success/comptime_expr/src/main.nr b/test_programs/noir_test_success/comptime_expr/src/main.nr index c082c1dde33..c1f70e7acee 100644 --- a/test_programs/noir_test_success/comptime_expr/src/main.nr +++ b/test_programs/noir_test_success/comptime_expr/src/main.nr @@ -16,7 +16,7 @@ mod tests { } #[test] - fn test_expr_mutate_for_array() { + fn test_expr_modify_for_array() { comptime { let expr = quote { [1, 2, 4] }.as_expr().unwrap(); @@ -46,7 +46,7 @@ mod tests { } #[test] - fn test_expr_mutate_for_assert() { + fn test_expr_modify_for_assert() { comptime { let expr = quote { assert(1) }.as_expr().unwrap(); @@ -82,7 +82,7 @@ mod tests { } #[test] - fn test_expr_mutate_for_assert_eq() { + fn test_expr_modify_for_assert_eq() { comptime { let expr = quote { assert_eq(1, 2) }.as_expr().unwrap(); @@ -113,7 +113,7 @@ mod tests { } #[test] - fn test_expr_mutate_for_assign() { + fn test_expr_modify_for_assign() { comptime { let expr = quote { { a = 1; } }.as_expr().unwrap(); @@ -142,7 +142,7 @@ mod tests { } #[test] - fn test_expr_mutate_for_block() { + fn test_expr_modify_for_block() { comptime { let expr = quote { { 1; 4; 23 } }.as_expr().unwrap(); @@ -178,7 +178,7 @@ mod tests { } #[test] - fn test_expr_mutate_for_method_call() { + fn test_expr_modify_for_method_call() { comptime { let expr = quote { foo.bar(3, 4) }.as_expr().unwrap(); @@ -209,7 +209,7 @@ mod tests { } #[test] - fn test_expr_mutate_for_integer() { + fn test_expr_modify_for_integer() { comptime { let expr = quote { 1 }.as_expr().unwrap(); @@ -243,7 +243,7 @@ mod tests { } #[test] - fn test_expr_mutate_for_binary_op() { + fn test_expr_modify_for_binary_op() { comptime { let expr = quote { 3 + 4 }.as_expr().unwrap(); @@ -280,7 +280,7 @@ mod tests { } #[test] - fn test_expr_mutate_for_cast() { + fn test_expr_modify_for_cast() { comptime { let expr = quote { 1 as Field }.as_expr().unwrap(); @@ -302,7 +302,7 @@ mod tests { } #[test] - fn test_expr_mutate_for_comptime() { + fn test_expr_modify_for_comptime() { comptime { let expr = quote { comptime { 1; 4; 23 } }.as_expr().unwrap(); @@ -342,7 +342,7 @@ mod tests { // docs:end:as_expr_example #[test] - fn test_expr_mutate_for_function_call() { + fn test_expr_modify_for_function_call() { comptime { let expr = quote { foo(42) }.as_expr().unwrap(); @@ -368,7 +368,7 @@ mod tests { } #[test] - fn test_expr_mutate_for_if() { + fn test_expr_modify_for_if() { comptime { let expr = quote { if 1 { 2 } }.as_expr().unwrap(); @@ -400,7 +400,7 @@ mod tests { } #[test] - fn test_expr_mutate_for_index() { + fn test_expr_modify_for_index() { comptime { let expr = quote { 1[2] }.as_expr().unwrap(); @@ -422,7 +422,7 @@ mod tests { } #[test] - fn test_expr_mutate_for_member_access() { + fn test_expr_modify_for_member_access() { comptime { let expr = quote { 1.bar }.as_expr().unwrap(); @@ -457,7 +457,7 @@ mod tests { } #[test] - fn test_expr_mutate_for_repeated_element_array() { + fn test_expr_modify_for_repeated_element_array() { comptime { let expr = quote { [1; 3] }.as_expr().unwrap(); @@ -480,7 +480,7 @@ mod tests { } #[test] - fn test_expr_mutate_for_repeated_element_slice() { + fn test_expr_modify_for_repeated_element_slice() { comptime { let expr = quote { &[1; 3] }.as_expr().unwrap(); @@ -505,7 +505,7 @@ mod tests { } #[test] - fn test_expr_mutate_for_slice() { + fn test_expr_modify_for_slice() { comptime { let expr = quote { &[1, 3, 5] }.as_expr().unwrap(); @@ -529,7 +529,7 @@ mod tests { } #[test] - fn test_expr_mutate_for_tuple() { + fn test_expr_modify_for_tuple() { comptime { let expr = quote { (1, 2) }.as_expr().unwrap(); @@ -553,7 +553,7 @@ mod tests { } #[test] - fn test_expr_mutate_for_unary_op() { + fn test_expr_modify_for_unary_op() { comptime { let expr = quote { -(1) }.as_expr().unwrap(); @@ -575,7 +575,7 @@ mod tests { } #[test] - fn test_expr_mutate_for_unsafe() { + fn test_expr_modify_for_unsafe() { comptime { let expr = quote { unsafe { 1; 4; 23 } }.as_expr().unwrap(); @@ -606,6 +606,41 @@ mod tests { } } + #[test] + fn test_expr_as_let() { + comptime + { + let expr = quote { let x: Field = 1; }.as_expr().unwrap(); + let (_pattern, typ, expr) = expr.as_let().unwrap(); + assert(typ.unwrap().is_field()); + assert_eq(expr.as_integer().unwrap(), (1, false)); + } + } + + #[test] + fn test_expr_modify_for_let() { + comptime + { + let expr = quote { let x : Field = 1; }.as_expr().unwrap(); + let expr = expr.modify(times_two); + let (_pattern, typ, expr) = expr.as_let().unwrap(); + assert(typ.unwrap().is_field()); + assert_eq(expr.as_integer().unwrap(), (2, false)); + } + } + + #[test] + fn test_expr_modify_for_let_without_type() { + comptime + { + let expr = quote { let x = 1; }.as_expr().unwrap(); + let expr = expr.modify(times_two); + let (_pattern, typ, expr) = expr.as_let().unwrap(); + assert(typ.is_none()); + assert_eq(expr.as_integer().unwrap(), (2, false)); + } + } + #[test] fn test_automatically_unwraps_parenthesized_expression() { comptime diff --git a/tooling/debugger/src/context.rs b/tooling/debugger/src/context.rs index 0d348cf172d..dde3fe84d88 100644 --- a/tooling/debugger/src/context.rs +++ b/tooling/debugger/src/context.rs @@ -976,10 +976,20 @@ mod tests { let brillig_bytecode = BrilligBytecode { bytecode: vec![ + BrilligOpcode::Const { + destination: MemoryAddress(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(1u64), + }, + BrilligOpcode::Const { + destination: MemoryAddress(1), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, BrilligOpcode::CalldataCopy { destination_address: MemoryAddress(0), - size: 1, - offset: 0, + size_address: MemoryAddress(0), + offset_address: MemoryAddress(1), }, BrilligOpcode::Const { destination: MemoryAddress::from(1), @@ -1036,7 +1046,7 @@ mod tests { }) ); - // Execute the first Brillig opcode (calldata copy) + // Const let result = context.step_into_opcode(); assert!(matches!(result, DebugCommandResult::Ok)); assert_eq!( @@ -1048,7 +1058,7 @@ mod tests { }) ); - // execute the second Brillig opcode (const) + // Const let result = context.step_into_opcode(); assert!(matches!(result, DebugCommandResult::Ok)); assert_eq!( @@ -1060,26 +1070,50 @@ mod tests { }) ); - // try to execute the third Brillig opcode (and resolve the foreign call) + // Calldatacopy let result = context.step_into_opcode(); assert!(matches!(result, DebugCommandResult::Ok)); assert_eq!( context.get_current_debug_location(), Some(DebugLocation { circuit_id: 0, - opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 2 }, + opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 3 }, brillig_function_id: Some(BrilligFunctionId(0)), }) ); - // retry the third Brillig opcode (foreign call should be finished) + // Const let result = context.step_into_opcode(); assert!(matches!(result, DebugCommandResult::Ok)); assert_eq!( context.get_current_debug_location(), Some(DebugLocation { circuit_id: 0, - opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 3 }, + opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 4 }, + brillig_function_id: Some(BrilligFunctionId(0)), + }) + ); + + // try to execute the Brillig opcode (and resolve the foreign call) + let result = context.step_into_opcode(); + assert!(matches!(result, DebugCommandResult::Ok)); + assert_eq!( + context.get_current_debug_location(), + Some(DebugLocation { + circuit_id: 0, + opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 4 }, + brillig_function_id: Some(BrilligFunctionId(0)), + }) + ); + + // retry the Brillig opcode (foreign call should be finished) + let result = context.step_into_opcode(); + assert!(matches!(result, DebugCommandResult::Ok)); + assert_eq!( + context.get_current_debug_location(), + Some(DebugLocation { + circuit_id: 0, + opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 5 }, brillig_function_id: Some(BrilligFunctionId(0)), }) ); @@ -1101,10 +1135,20 @@ mod tests { // This Brillig block is equivalent to: z = x + y let brillig_bytecode = BrilligBytecode { bytecode: vec![ + BrilligOpcode::Const { + destination: MemoryAddress(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(2u64), + }, + BrilligOpcode::Const { + destination: MemoryAddress(1), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, BrilligOpcode::CalldataCopy { destination_address: MemoryAddress(0), - size: 2, - offset: 0, + size_address: MemoryAddress(0), + offset_address: MemoryAddress(1), }, BrilligOpcode::BinaryFieldOp { destination: MemoryAddress::from(0), diff --git a/tooling/lsp/src/modules.rs b/tooling/lsp/src/modules.rs index d78da15a8ff..cce7f324a2e 100644 --- a/tooling/lsp/src/modules.rs +++ b/tooling/lsp/src/modules.rs @@ -122,7 +122,7 @@ pub(crate) fn module_id_path( 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 { + if target_module_id.krate.is_root() { segments.push("crate"); } } diff --git a/tooling/lsp/src/notifications/mod.rs b/tooling/lsp/src/notifications/mod.rs index 87e7bea8c3b..96e339ee212 100644 --- a/tooling/lsp/src/notifications/mod.rs +++ b/tooling/lsp/src/notifications/mod.rs @@ -7,7 +7,7 @@ use async_lsp::{ErrorCode, LanguageClient, ResponseError}; use fm::{FileManager, FileMap}; use fxhash::FxHashMap as HashMap; use lsp_types::{DiagnosticTag, Url}; -use noirc_driver::{check_crate, file_manager_with_stdlib}; +use noirc_driver::check_crate; use noirc_errors::{DiagnosticKind, FileDiagnostic}; use crate::types::{ @@ -120,7 +120,8 @@ pub(crate) fn process_workspace_for_noir_document( ResponseError::new(ErrorCode::REQUEST_FAILED, lsp_error.to_string()) })?; - let mut workspace_file_manager = file_manager_with_stdlib(&workspace.root_dir); + let mut workspace_file_manager = workspace.new_file_manager(); + insert_all_files_for_workspace_into_file_manager( state, &workspace, diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 59758f4b972..de5c36bc59f 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -4,10 +4,7 @@ use std::{ }; use async_lsp::ResponseError; -use completion_items::{ - crate_completion_item, field_completion_item, simple_completion_item, - struct_field_completion_item, -}; +use completion_items::{field_completion_item, simple_completion_item, snippet_completion_item}; use convert_case::{Case, Casing}; use fm::{FileId, FileMap, PathString}; use kinds::{FunctionCompletionKind, FunctionKind, RequestedItems}; @@ -15,11 +12,11 @@ use lsp_types::{CompletionItem, CompletionItemKind, CompletionParams, Completion use noirc_errors::{Location, Span}; use noirc_frontend::{ ast::{ - AsTraitPath, BlockExpression, CallExpression, ConstructorExpression, Expression, - ExpressionKind, ForLoopStatement, GenericTypeArgs, Ident, IfExpression, ItemVisibility, - Lambda, LetStatement, MemberAccessExpression, MethodCallExpression, NoirFunction, - NoirStruct, NoirTraitImpl, Path, PathKind, Pattern, Statement, TypeImpl, UnresolvedGeneric, - UnresolvedGenerics, UnresolvedType, UseTree, UseTreeKind, Visitor, + AsTraitPath, AttributeTarget, BlockExpression, CallExpression, ConstructorExpression, + Expression, ExpressionKind, ForLoopStatement, GenericTypeArgs, Ident, IfExpression, + ItemVisibility, Lambda, LetStatement, MemberAccessExpression, MethodCallExpression, + NoirFunction, NoirStruct, NoirTraitImpl, Path, PathKind, Pattern, Statement, TypeImpl, + UnresolvedGeneric, UnresolvedGenerics, UnresolvedType, UseTree, UseTreeKind, Visitor, }, graph::{CrateId, Dependency}, hir::def_map::{CrateDefMap, LocalModuleId, ModuleId}, @@ -27,6 +24,7 @@ use noirc_frontend::{ macros_api::{ModuleDefId, NodeInterner}, node_interner::ReferenceId, parser::{Item, ItemKind, ParsedSubModule}, + token::CustomAtrribute, ParsedModule, StructType, Type, }; use sort_text::underscore_sort_text; @@ -106,6 +104,7 @@ struct NodeFinder<'a> { nesting: usize, /// The line where an auto_import must be inserted auto_import_line: usize, + self_type: Option, } impl<'a> NodeFinder<'a> { @@ -147,6 +146,7 @@ impl<'a> NodeFinder<'a> { suggested_module_def_ids: HashSet::new(), nesting: 0, auto_import_line: 0, + self_type: None, } } @@ -180,19 +180,23 @@ impl<'a> NodeFinder<'a> { let struct_type = struct_type.borrow(); // First get all of the struct's fields - let mut fields = HashMap::new(); - let fields_as_written = struct_type.get_fields_as_written(); - for (field, typ) in &fields_as_written { - fields.insert(field, typ); - } + let mut fields: Vec<_> = + struct_type.get_fields_as_written().into_iter().enumerate().collect(); // Remove the ones that already exists in the constructor - for (field, _) in &constructor_expression.fields { - fields.remove(&field.0.contents); + for (used_name, _) in &constructor_expression.fields { + fields.retain(|(_, (name, _))| name != &used_name.0.contents); } - for (field, typ) in fields { - self.completion_items.push(struct_field_completion_item(field, typ)); + let self_prefix = false; + for (field_index, (field, typ)) in &fields { + self.completion_items.push(self.struct_field_completion_item( + field, + typ, + struct_type.id, + *field_index, + self_prefix, + )); } } @@ -293,6 +297,7 @@ impl<'a> NodeFinder<'a> { &prefix, FunctionKind::Any, function_completion_kind, + false, // self_prefix ); return; } @@ -308,6 +313,7 @@ impl<'a> NodeFinder<'a> { &prefix, FunctionKind::Any, function_completion_kind, + false, // self_prefix ); return; } @@ -340,11 +346,21 @@ impl<'a> NodeFinder<'a> { self.local_variables_completion(&prefix); self.builtin_functions_completion(&prefix, function_completion_kind); self.builtin_values_completion(&prefix); + if let Some(self_type) = &self.self_type { + let self_prefix = true; + self.complete_type_fields_and_methods( + &self_type.clone(), + &prefix, + function_completion_kind, + self_prefix, + ); + } } RequestedItems::OnlyTypes => { self.builtin_types_completion(&prefix); self.type_parameters_completion(&prefix); } + RequestedItems::OnlyAttributeFunctions(..) => (), } self.complete_auto_imports(&prefix, requested_items, function_completion_kind); } @@ -492,6 +508,7 @@ impl<'a> NodeFinder<'a> { self.collect_local_variables(pattern); } } + Pattern::Interned(..) => (), } } @@ -518,16 +535,18 @@ impl<'a> NodeFinder<'a> { typ: &Type, prefix: &str, function_completion_kind: FunctionCompletionKind, + self_prefix: bool, ) { match typ { Type::Struct(struct_type, generics) => { - self.complete_struct_fields(&struct_type.borrow(), generics, prefix); + self.complete_struct_fields(&struct_type.borrow(), generics, prefix, self_prefix); } Type::MutableReference(typ) => { return self.complete_type_fields_and_methods( typ, prefix, function_completion_kind, + self_prefix, ); } Type::Alias(type_alias, _) => { @@ -536,10 +555,11 @@ impl<'a> NodeFinder<'a> { &type_alias.typ, prefix, function_completion_kind, + self_prefix, ); } Type::Tuple(types) => { - self.complete_tuple_fields(types); + self.complete_tuple_fields(types, self_prefix); } Type::FieldElement | Type::Array(_, _) @@ -565,6 +585,7 @@ impl<'a> NodeFinder<'a> { prefix, FunctionKind::SelfType(typ), function_completion_kind, + self_prefix, ); } @@ -574,6 +595,7 @@ impl<'a> NodeFinder<'a> { prefix: &str, function_kind: FunctionKind, function_completion_kind: FunctionCompletionKind, + self_prefix: bool, ) { let Some(methods_by_name) = self.interner.get_type_methods(typ) else { return; @@ -587,6 +609,8 @@ impl<'a> NodeFinder<'a> { func_id, function_completion_kind, function_kind, + None, // attribute first type + self_prefix, ) { self.completion_items.push(completion_item); self.suggested_module_def_ids.insert(ModuleDefId::FunctionId(func_id)); @@ -603,6 +627,8 @@ impl<'a> NodeFinder<'a> { function_kind: FunctionKind, function_completion_kind: FunctionCompletionKind, ) { + let self_prefix = false; + for (name, func_id) in &trait_.method_ids { if name_matches(name, prefix) { if let Some(completion_item) = self.function_completion_item( @@ -610,6 +636,8 @@ impl<'a> NodeFinder<'a> { *func_id, function_completion_kind, function_kind, + None, // attribute first type + self_prefix, ) { self.completion_items.push(completion_item); self.suggested_module_def_ids.insert(ModuleDefId::FunctionId(*func_id)); @@ -623,17 +651,25 @@ impl<'a> NodeFinder<'a> { struct_type: &StructType, generics: &[Type], prefix: &str, + self_prefix: bool, ) { - for (name, typ) in &struct_type.get_fields(generics) { + for (field_index, (name, typ)) in struct_type.get_fields(generics).iter().enumerate() { if name_matches(name, prefix) { - self.completion_items.push(struct_field_completion_item(name, typ)); + self.completion_items.push(self.struct_field_completion_item( + name, + typ, + struct_type.id, + field_index, + self_prefix, + )); } } } - fn complete_tuple_fields(&mut self, types: &[Type]) { + fn complete_tuple_fields(&mut self, types: &[Type], self_prefix: bool) { for (index, typ) in types.iter().enumerate() { - self.completion_items.push(field_completion_item(&index.to_string(), typ.to_string())); + let name = index.to_string(); + self.completion_items.push(field_completion_item(&name, typ.to_string(), self_prefix)); } } @@ -717,7 +753,10 @@ impl<'a> NodeFinder<'a> { 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)); + let root_id = self.def_maps[&dependency.crate_id].root(); + let module_id = ModuleId { krate: dependency.crate_id, local_id: root_id }; + self.completion_items + .push(self.crate_completion_item(dependency_name, module_id)); } } @@ -761,6 +800,66 @@ impl<'a> NodeFinder<'a> { None } + fn suggest_attributes(&mut self, prefix: &str, target: AttributeTarget) { + self.suggest_builtin_attributes(prefix, target); + + let function_completion_kind = FunctionCompletionKind::NameAndParameters; + let requested_items = RequestedItems::OnlyAttributeFunctions(target); + + self.complete_in_module( + self.module_id, + prefix, + PathKind::Plain, + true, + function_completion_kind, + requested_items, + ); + + self.complete_auto_imports(prefix, requested_items, function_completion_kind); + } + + fn suggest_no_arguments_attributes(&mut self, prefix: &str, attributes: &[&str]) { + for name in attributes { + if name_matches(name, prefix) { + self.completion_items.push(simple_completion_item( + *name, + CompletionItemKind::METHOD, + None, + )); + } + } + } + + fn suggest_one_argument_attributes(&mut self, prefix: &str, attributes: &[&str]) { + for name in attributes { + if name_matches(name, prefix) { + self.completion_items.push(snippet_completion_item( + format!("{}(…)", name), + CompletionItemKind::METHOD, + format!("{}(${{1:name}})", name), + None, + )); + } + } + } + + fn try_set_self_type(&mut self, pattern: &Pattern) { + match pattern { + Pattern::Identifier(ident) => { + if ident.0.contents == "self" { + let location = Location::new(ident.span(), self.file); + if let Some(ReferenceId::Local(definition_id)) = + self.interner.find_referenced(location) + { + self.self_type = Some(self.interner.definition_type(definition_id)); + } + } + } + Pattern::Mutable(pattern, ..) => self.try_set_self_type(pattern), + Pattern::Tuple(..) | Pattern::Struct(..) | Pattern::Interned(..) => (), + } + } + fn includes_span(&self, span: Span) -> bool { span.start() as usize <= self.byte_index && self.byte_index <= span.end() as usize } @@ -813,10 +912,15 @@ impl<'a> Visitor for NodeFinder<'a> { } fn visit_noir_function(&mut self, noir_function: &NoirFunction, span: Span) -> bool { + for attribute in noir_function.secondary_attributes() { + attribute.accept(AttributeTarget::Function, self); + } + 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.try_set_self_type(¶m.pattern); param.typ.accept(self); } @@ -830,6 +934,7 @@ impl<'a> Visitor for NodeFinder<'a> { noir_function.def.body.accept(Some(span), self); self.type_parameters = old_type_parameters; + self.self_type = None; false } @@ -842,7 +947,7 @@ impl<'a> Visitor for NodeFinder<'a> { self.collect_type_parameters_in_generics(&noir_trait_impl.impl_generics); for item in &noir_trait_impl.items { - item.accept(self); + item.item.accept(self); } self.type_parameters.clear(); @@ -857,7 +962,7 @@ impl<'a> Visitor for NodeFinder<'a> { self.collect_type_parameters_in_generics(&type_impl.generics); for (method, span) in &type_impl.methods { - method.accept(*span, self); + method.item.accept(*span, self); // Optimization: stop looking in functions past the completion cursor if span.end() as usize > self.byte_index { @@ -871,11 +976,15 @@ impl<'a> Visitor for NodeFinder<'a> { } fn visit_noir_struct(&mut self, noir_struct: &NoirStruct, _: Span) -> bool { + for attribute in &noir_struct.attributes { + attribute.accept(AttributeTarget::Struct, self); + } + 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); + for field in &noir_struct.fields { + field.item.typ.accept(self); } self.type_parameters.clear(); @@ -945,7 +1054,13 @@ impl<'a> Visitor for NodeFinder<'a> { 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); + let self_prefix = false; + self.complete_type_fields_and_methods( + &typ, + prefix, + FunctionCompletionKind::Name, + self_prefix, + ); return false; } } @@ -973,7 +1088,13 @@ impl<'a> Visitor for NodeFinder<'a> { 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); + let self_prefix = false; + self.complete_type_fields_and_methods( + &typ, + &prefix, + FunctionCompletionKind::Name, + self_prefix, + ); return false; } } @@ -1042,10 +1163,12 @@ impl<'a> Visitor for NodeFinder<'a> { { let typ = self.interner.definition_type(definition_id); let prefix = ""; + let self_prefix = false; self.complete_type_fields_and_methods( &typ, prefix, FunctionCompletionKind::NameAndParameters, + self_prefix, ); } } @@ -1072,10 +1195,12 @@ impl<'a> Visitor for NodeFinder<'a> { if let Some(typ) = self.interner.type_at_location(location) { let typ = typ.follow_bindings(); let prefix = ""; + let self_prefix = false; self.complete_type_fields_and_methods( &typ, prefix, FunctionCompletionKind::NameAndParameters, + self_prefix, ); } } @@ -1136,10 +1261,12 @@ impl<'a> Visitor for NodeFinder<'a> { if let Some(typ) = self.interner.type_at_location(location) { let typ = typ.follow_bindings(); let prefix = ident.to_string().to_case(Case::Snake); + let self_prefix = false; self.complete_type_fields_and_methods( &typ, &prefix, FunctionCompletionKind::NameAndParameters, + self_prefix, ); return false; } @@ -1201,6 +1328,14 @@ impl<'a> Visitor for NodeFinder<'a> { unresolved_types.accept(self); false } + + fn visit_custom_attribute(&mut self, attribute: &CustomAtrribute, target: AttributeTarget) { + if self.byte_index != attribute.contents_span.end() as usize { + return; + } + + self.suggest_attributes(&attribute.contents, target); + } } /// Returns true if name matches a prefix written in code. diff --git a/tooling/lsp/src/requests/completion/builtins.rs b/tooling/lsp/src/requests/completion/builtins.rs index f449177a027..520c158d260 100644 --- a/tooling/lsp/src/requests/completion/builtins.rs +++ b/tooling/lsp/src/requests/completion/builtins.rs @@ -1,5 +1,5 @@ use lsp_types::CompletionItemKind; -use noirc_frontend::token::Keyword; +use noirc_frontend::{ast::AttributeTarget, token::Keyword}; use strum::IntoEnumIterator; use super::{ @@ -84,6 +84,40 @@ impl<'a> NodeFinder<'a> { } } } + + pub(super) fn suggest_builtin_attributes(&mut self, prefix: &str, target: AttributeTarget) { + match target { + AttributeTarget::Module | AttributeTarget::Trait => (), + AttributeTarget::Struct => { + self.suggest_one_argument_attributes(prefix, &["abi"]); + } + AttributeTarget::Function => { + let no_arguments_attributes = &[ + "contract_library_method", + "deprecated", + "export", + "fold", + "no_predicates", + "recursive", + "test", + "varargs", + ]; + self.suggest_no_arguments_attributes(prefix, no_arguments_attributes); + + let one_argument_attributes = &["abi", "field", "foreign", "oracle"]; + self.suggest_one_argument_attributes(prefix, one_argument_attributes); + + if name_matches("test", prefix) || name_matches("should_fail_with", prefix) { + self.completion_items.push(snippet_completion_item( + "test(should_fail_with=\"...\")", + CompletionItemKind::METHOD, + "test(should_fail_with=\"${1:message}\")", + None, + )); + } + } + } + } } pub(super) fn builtin_integer_types() -> [&'static str; 8] { diff --git a/tooling/lsp/src/requests/completion/completion_items.rs b/tooling/lsp/src/requests/completion/completion_items.rs index 21c3a607b18..7e7511cdaa3 100644 --- a/tooling/lsp/src/requests/completion/completion_items.rs +++ b/tooling/lsp/src/requests/completion/completion_items.rs @@ -1,11 +1,14 @@ use lsp_types::{ - Command, CompletionItem, CompletionItemKind, CompletionItemLabelDetails, InsertTextFormat, + Command, CompletionItem, CompletionItemKind, CompletionItemLabelDetails, Documentation, + InsertTextFormat, MarkupContent, MarkupKind, }; use noirc_frontend::{ + ast::AttributeTarget, + hir::def_map::ModuleId, hir_def::{function::FuncMeta, stmt::HirPattern}, - macros_api::ModuleDefId, - node_interner::{FuncId, GlobalId}, - Type, + macros_api::{ModuleDefId, StructId}, + node_interner::{FuncId, GlobalId, ReferenceId, TraitId, TypeAliasId}, + QuotedType, Type, }; use super::{ @@ -33,34 +36,91 @@ impl<'a> NodeFinder<'a> { | ModuleDefId::TypeAliasId(_) | ModuleDefId::TraitId(_) => (), }, + RequestedItems::OnlyAttributeFunctions(..) => { + if !matches!(module_def_id, ModuleDefId::FunctionId(..)) { + return None; + } + } RequestedItems::AnyItems => (), } + let attribute_first_type = + if let RequestedItems::OnlyAttributeFunctions(target) = requested_items { + match target { + AttributeTarget::Module => Some(Type::Quoted(QuotedType::Module)), + AttributeTarget::Struct => Some(Type::Quoted(QuotedType::StructDefinition)), + AttributeTarget::Trait => Some(Type::Quoted(QuotedType::TraitDefinition)), + AttributeTarget::Function => Some(Type::Quoted(QuotedType::FunctionDefinition)), + } + } else { + None + }; + match module_def_id { - ModuleDefId::ModuleId(_) => Some(module_completion_item(name)), + ModuleDefId::ModuleId(id) => Some(self.module_completion_item(name, id)), ModuleDefId::FunctionId(func_id) => self.function_completion_item( &name, func_id, function_completion_kind, function_kind, + attribute_first_type.as_ref(), + false, // self_prefix ), - ModuleDefId::TypeId(..) => Some(self.struct_completion_item(name)), - ModuleDefId::TypeAliasId(..) => Some(self.type_alias_completion_item(name)), - ModuleDefId::TraitId(..) => Some(self.trait_completion_item(name)), + ModuleDefId::TypeId(struct_id) => Some(self.struct_completion_item(name, struct_id)), + ModuleDefId::TypeAliasId(id) => Some(self.type_alias_completion_item(name, id)), + ModuleDefId::TraitId(trait_id) => Some(self.trait_completion_item(name, trait_id)), ModuleDefId::GlobalId(global_id) => Some(self.global_completion_item(name, global_id)), } } - fn struct_completion_item(&self, name: String) -> CompletionItem { - simple_completion_item(name.clone(), CompletionItemKind::STRUCT, Some(name)) + pub(super) fn crate_completion_item( + &self, + name: impl Into, + id: ModuleId, + ) -> CompletionItem { + self.module_completion_item(name, id) } - fn type_alias_completion_item(&self, name: String) -> CompletionItem { - simple_completion_item(name.clone(), CompletionItemKind::STRUCT, Some(name)) + pub(super) fn module_completion_item( + &self, + name: impl Into, + id: ModuleId, + ) -> CompletionItem { + let completion_item = module_completion_item(name); + self.completion_item_with_doc_comments(ReferenceId::Module(id), completion_item) } - fn trait_completion_item(&self, name: String) -> CompletionItem { - simple_completion_item(name.clone(), CompletionItemKind::INTERFACE, Some(name)) + fn struct_completion_item(&self, name: String, struct_id: StructId) -> CompletionItem { + let completion_item = + simple_completion_item(name.clone(), CompletionItemKind::STRUCT, Some(name)); + self.completion_item_with_doc_comments(ReferenceId::Struct(struct_id), completion_item) + } + + pub(super) fn struct_field_completion_item( + &self, + field: &str, + typ: &Type, + struct_id: StructId, + field_index: usize, + self_type: bool, + ) -> CompletionItem { + let completion_item = struct_field_completion_item(field, typ, self_type); + self.completion_item_with_doc_comments( + ReferenceId::StructMember(struct_id, field_index), + completion_item, + ) + } + + fn type_alias_completion_item(&self, name: String, id: TypeAliasId) -> CompletionItem { + let completion_item = + simple_completion_item(name.clone(), CompletionItemKind::STRUCT, Some(name)); + self.completion_item_with_doc_comments(ReferenceId::Alias(id), completion_item) + } + + fn trait_completion_item(&self, name: String, trait_id: TraitId) -> CompletionItem { + let completion_item = + simple_completion_item(name.clone(), CompletionItemKind::INTERFACE, Some(name)); + self.completion_item_with_doc_comments(ReferenceId::Trait(trait_id), completion_item) } fn global_completion_item(&self, name: String, global_id: GlobalId) -> CompletionItem { @@ -68,7 +128,9 @@ impl<'a> NodeFinder<'a> { let typ = self.interner.definition_type(global.definition_id); let description = typ.to_string(); - simple_completion_item(name, CompletionItemKind::CONSTANT, Some(description)) + let completion_item = + simple_completion_item(name, CompletionItemKind::CONSTANT, Some(description)); + self.completion_item_with_doc_comments(ReferenceId::Global(global_id), completion_item) } pub(super) fn function_completion_item( @@ -77,6 +139,8 @@ impl<'a> NodeFinder<'a> { func_id: FuncId, function_completion_kind: FunctionCompletionKind, function_kind: FunctionKind, + attribute_first_type: Option<&Type>, + self_prefix: bool, ) -> Option { let func_meta = self.interner.function_meta(&func_id); @@ -95,6 +159,17 @@ impl<'a> NodeFinder<'a> { None }; + if let Some(attribute_first_type) = attribute_first_type { + if func_meta.parameters.is_empty() { + return None; + } + + let (_, typ, _) = &func_meta.parameters.0[0]; + if typ != attribute_first_type { + return None; + } + } + match function_kind { FunctionKind::Any => (), FunctionKind::SelfType(mut self_type) => { @@ -135,6 +210,8 @@ impl<'a> NodeFinder<'a> { } else { false }; + let name = if self_prefix { format!("self.{}", name) } else { name.clone() }; + let name = &name; let description = func_meta_type_to_string(func_meta, func_self_type.is_some()); let completion_item = match function_completion_kind { @@ -143,11 +220,21 @@ impl<'a> NodeFinder<'a> { } FunctionCompletionKind::NameAndParameters => { let kind = CompletionItemKind::FUNCTION; - let insert_text = self.compute_function_insert_text(func_meta, name, function_kind); - let label = if insert_text.ends_with("()") { - format!("{}()", name) + let skip_first_argument = attribute_first_type.is_some(); + let insert_text = self.compute_function_insert_text( + func_meta, + name, + function_kind, + skip_first_argument, + ); + let (label, insert_text) = if insert_text.ends_with("()") { + if skip_first_argument { + (name.to_string(), insert_text.strip_suffix("()").unwrap().to_string()) + } else { + (format!("{}()", name), insert_text) + } } else { - format!("{}(…)", name) + (format!("{}(…)", name), insert_text) }; snippet_completion_item(label, kind, insert_text, Some(description)) @@ -170,7 +257,8 @@ impl<'a> NodeFinder<'a> { completion_item_with_trigger_parameter_hints_command(completion_item) } }; - + let completion_item = + self.completion_item_with_doc_comments(ReferenceId::Function(func_id), completion_item); Some(completion_item) } @@ -179,13 +267,19 @@ impl<'a> NodeFinder<'a> { func_meta: &FuncMeta, name: &str, function_kind: FunctionKind, + skip_first_argument: bool, ) -> String { let mut text = String::new(); text.push_str(name); text.push('('); + let mut parameters = func_meta.parameters.0.iter(); + if skip_first_argument { + parameters.next(); + } + let mut index = 1; - for (pattern, _, _) in &func_meta.parameters.0 { + for (pattern, _, _) in parameters { if index == 1 { match function_kind { FunctionKind::SelfType(_) => { @@ -213,6 +307,25 @@ impl<'a> NodeFinder<'a> { text } + fn completion_item_with_doc_comments( + &self, + id: ReferenceId, + completion_item: CompletionItem, + ) -> CompletionItem { + if let Some(doc_comments) = self.interner.doc_comments(id) { + let docs = doc_comments.join("\n"); + CompletionItem { + documentation: Some(Documentation::MarkupContent(MarkupContent { + kind: MarkupKind::Markdown, + value: docs, + })), + ..completion_item + } + } else { + completion_item + } + } + fn hir_pattern_to_argument(&self, pattern: &HirPattern, text: &mut String) { match pattern { HirPattern::Identifier(hir_ident) => { @@ -242,13 +355,6 @@ pub(super) fn module_completion_item(name: impl Into) -> CompletionItem ) } -pub(super) fn crate_completion_item(name: impl Into) -> CompletionItem { - completion_item_with_sort_text( - simple_completion_item(name, CompletionItemKind::MODULE, None), - crate_or_module_sort_text(), - ) -} - fn func_meta_type_to_string(func_meta: &FuncMeta, has_self_type: bool) -> String { let mut typ = &func_meta.typ; if let Type::Forall(_, typ_) = typ { @@ -294,12 +400,24 @@ fn type_to_self_string(typ: &Type, string: &mut String) { } } -pub(super) fn struct_field_completion_item(field: &str, typ: &Type) -> CompletionItem { - field_completion_item(field, typ.to_string()) +pub(super) fn struct_field_completion_item( + field: &str, + typ: &Type, + self_type: bool, +) -> CompletionItem { + field_completion_item(field, typ.to_string(), self_type) } -pub(super) fn field_completion_item(field: &str, typ: impl Into) -> CompletionItem { - simple_completion_item(field, CompletionItemKind::FIELD, Some(typ.into())) +pub(super) fn field_completion_item( + field: &str, + typ: impl Into, + self_type: bool, +) -> CompletionItem { + if self_type { + simple_completion_item(format!("self.{field}"), CompletionItemKind::FIELD, Some(typ.into())) + } else { + simple_completion_item(field, CompletionItemKind::FIELD, Some(typ.into())) + } } pub(super) fn simple_completion_item( diff --git a/tooling/lsp/src/requests/completion/kinds.rs b/tooling/lsp/src/requests/completion/kinds.rs index 2fe039ba331..6fa74ffdb1a 100644 --- a/tooling/lsp/src/requests/completion/kinds.rs +++ b/tooling/lsp/src/requests/completion/kinds.rs @@ -1,4 +1,4 @@ -use noirc_frontend::Type; +use noirc_frontend::{ast::AttributeTarget, Type}; /// When suggest a function as a result of completion, whether to autocomplete its name or its name and parameters. #[derive(Clone, Copy, PartialEq, Eq, Debug)] @@ -27,4 +27,6 @@ pub(super) enum RequestedItems { AnyItems, // Only suggest types. OnlyTypes, + // Only attribute functions + OnlyAttributeFunctions(AttributeTarget), } diff --git a/tooling/lsp/src/requests/completion/tests.rs b/tooling/lsp/src/requests/completion/tests.rs index a7cfa77a73d..1b04e060e2c 100644 --- a/tooling/lsp/src/requests/completion/tests.rs +++ b/tooling/lsp/src/requests/completion/tests.rs @@ -6,9 +6,8 @@ mod completion_tests { completion::{ completion_items::{ completion_item_with_sort_text, - completion_item_with_trigger_parameter_hints_command, crate_completion_item, - field_completion_item, module_completion_item, simple_completion_item, - snippet_completion_item, + completion_item_with_trigger_parameter_hints_command, module_completion_item, + simple_completion_item, snippet_completion_item, }, sort_text::{auto_import_sort_text, self_mismatch_sort_text}, }, @@ -116,6 +115,10 @@ mod completion_tests { )) } + fn field_completion_item(field: &str, typ: impl Into) -> CompletionItem { + crate::requests::completion::field_completion_item(field, typ, false) + } + #[test] async fn test_use_first_segment() { let src = r#" @@ -205,7 +208,7 @@ mod completion_tests { let src = r#" use s>|< "#; - assert_completion(src, vec![crate_completion_item("std")]).await; + assert_completion(src, vec![module_completion_item("std")]).await; // "std" doesn't show up anymore because of the "crate::" prefix let src = r#" @@ -281,7 +284,7 @@ mod completion_tests { src, vec![ module_completion_item("something"), - crate_completion_item("std"), + module_completion_item("std"), simple_completion_item("super::", CompletionItemKind::KEYWORD, None), ], ) @@ -310,7 +313,7 @@ mod completion_tests { mod something_else {} use crate::s>|< } - + "#; assert_completion(src, vec![module_completion_item("something")]).await; } @@ -322,7 +325,7 @@ mod completion_tests { mod something_else {} use crate::something::s>|< } - + "#; assert_completion(src, vec![module_completion_item("something_else")]).await; } @@ -1888,4 +1891,101 @@ mod completion_tests { Some("(use super::barbaz)".to_string()), ); } + + #[test] + async fn test_suggests_self_fields_and_methods() { + let src = r#" + struct Foo { + foobar: Field, + } + + impl Foo { + fn foobarbaz(self) {} + + fn some_method(self) { + foob>|< + } + } + "#; + + assert_completion_excluding_auto_import( + src, + vec![ + field_completion_item("self.foobar", "Field"), + function_completion_item("self.foobarbaz()", "self.foobarbaz()", "fn(self)"), + ], + ) + .await; + } + + #[test] + async fn test_suggests_built_in_function_attribute() { + let src = r#" + #[dep>|<] + fn foo() {} + "#; + + assert_completion_excluding_auto_import( + src, + vec![simple_completion_item("deprecated", CompletionItemKind::METHOD, None)], + ) + .await; + } + + #[test] + async fn test_suggests_function_attribute() { + let src = r#" + #[some>|<] + fn foo() {} + + fn some_attr(f: FunctionDefinition, x: Field) {} + fn some_other_function(x: Field) {} + "#; + + assert_completion_excluding_auto_import( + src, + vec![function_completion_item( + "some_attr(…)", + "some_attr(${1:x})", + "fn(FunctionDefinition, Field)", + )], + ) + .await; + } + + #[test] + async fn test_suggests_function_attribute_no_arguments() { + let src = r#" + #[some>|<] + fn foo() {} + + fn some_attr(f: FunctionDefinition) {} + "#; + + assert_completion_excluding_auto_import( + src, + vec![function_completion_item("some_attr", "some_attr", "fn(FunctionDefinition)")], + ) + .await; + } + + #[test] + async fn test_suggests_trait_attribute() { + let src = r#" + #[some>|<] + trait SomeTrait {} + + fn some_attr(t: TraitDefinition, x: Field) {} + "#; + + assert_completion_excluding_auto_import( + src, + vec![function_completion_item( + "some_attr(…)", + "some_attr(${1:x})", + "fn(TraitDefinition, Field)", + )], + ) + .await; + } } diff --git a/tooling/lsp/src/requests/document_symbol.rs b/tooling/lsp/src/requests/document_symbol.rs index e06a3451681..7ab0a45ad0a 100644 --- a/tooling/lsp/src/requests/document_symbol.rs +++ b/tooling/lsp/src/requests/document_symbol.rs @@ -10,7 +10,7 @@ use noirc_errors::Span; use noirc_frontend::{ ast::{ Expression, FunctionReturnType, Ident, LetStatement, NoirFunction, NoirStruct, NoirTrait, - NoirTraitImpl, TypeImpl, UnresolvedType, UnresolvedTypeData, Visitor, + NoirTraitImpl, TypeImpl, UnresolvedType, Visitor, }, parser::ParsedSubModule, ParsedModule, @@ -171,7 +171,9 @@ impl<'a> Visitor for DocumentSymbolCollector<'a> { }; let mut children = Vec::new(); - for (field_name, typ) in &noir_struct.fields { + for field in &noir_struct.fields { + let field_name = &field.item.name; + let typ = &field.item.typ; let span = Span::from(field_name.span().start()..typ.span.end()); let Some(field_location) = self.to_lsp_location(span) else { @@ -223,7 +225,7 @@ impl<'a> Visitor for DocumentSymbolCollector<'a> { self.symbols = Vec::new(); for item in &noir_trait.items { - item.accept(self); + item.item.accept(self); } let children = std::mem::take(&mut self.symbols); @@ -350,7 +352,7 @@ impl<'a> Visitor for DocumentSymbolCollector<'a> { self.symbols = Vec::new(); for trait_impl_item in &noir_trait_impl.items { - trait_impl_item.accept(self); + trait_impl_item.item.accept(self); } let children = std::mem::take(&mut self.symbols); @@ -391,13 +393,9 @@ impl<'a> Visitor for DocumentSymbolCollector<'a> { return false; }; - let UnresolvedTypeData::Named(name_path, ..) = &type_impl.object_type.typ else { - return false; - }; - - let name = name_path.last_ident(); + let name = type_impl.object_type.typ.to_string(); - let Some(name_location) = self.to_lsp_location(name.span()) else { + let Some(name_location) = self.to_lsp_location(type_impl.object_type.span) else { return false; }; @@ -405,7 +403,7 @@ impl<'a> Visitor for DocumentSymbolCollector<'a> { self.symbols = Vec::new(); for (noir_function, noir_function_span) in &type_impl.methods { - noir_function.accept(*noir_function_span, self); + noir_function.item.accept(*noir_function_span, self); } let children = std::mem::take(&mut self.symbols); @@ -710,6 +708,23 @@ mod document_symbol_tests { } ]), }, + #[allow(deprecated)] + DocumentSymbol { + name: "i32".to_string(), + detail: None, + kind: SymbolKind::NAMESPACE, + tags: None, + deprecated: None, + range: Range { + start: Position { line: 27, character: 0 }, + end: Position { line: 27, character: 11 } + }, + selection_range: Range { + start: Position { line: 27, character: 5 }, + end: Position { line: 27, character: 8 } + }, + children: Some(Vec::new()) + } ] ); } diff --git a/tooling/lsp/src/requests/hover.rs b/tooling/lsp/src/requests/hover.rs index ae1e57f5ecc..dab6ddd0fc6 100644 --- a/tooling/lsp/src/requests/hover.rs +++ b/tooling/lsp/src/requests/hover.rs @@ -5,7 +5,6 @@ use fm::FileMap; use lsp_types::{Hover, HoverContents, HoverParams, MarkupContent, MarkupKind}; use noirc_frontend::{ ast::Visibility, - graph::CrateId, hir::def_map::ModuleId, hir_def::{stmt::HirPattern, traits::Trait}, macros_api::{NodeInterner, StructId}, @@ -60,30 +59,37 @@ fn format_reference(reference: ReferenceId, args: &ProcessRequestCallbackArgs) - fn format_module(id: ModuleId, args: &ProcessRequestCallbackArgs) -> Option { let crate_root = args.def_maps[&id.krate].root(); + let mut string = String::new(); + 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)); + let Some(dep) = args.dependencies.iter().find(|dep| dep.crate_id == id.krate) else { + return None; + }; + string.push_str(" crate "); + string.push_str(&dep.name.to_string()); + } else { + // Note: it's not clear why `try_module_attributes` might return None here, but it happens. + // This is a workaround to avoid panicking in that case (which brings the LSP server down). + // Cases where this happens are related to generated code, so once that stops happening + // this won't be an issue anymore. + let module_attributes = args.interner.try_module_attributes(&id)?; + + if let Some(parent_local_id) = module_attributes.parent { + if format_parent_module_from_module_id( + &ModuleId { krate: id.krate, local_id: parent_local_id }, + args, + &mut string, + ) { + string.push('\n'); + } + } + string.push_str(" "); + string.push_str("mod "); + string.push_str(&module_attributes.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 - // this won't be an issue anymore. - let module_attributes = args.interner.try_module_attributes(&id)?; + append_doc_comments(args.interner, ReferenceId::Module(id), &mut string); - let mut string = String::new(); - if let Some(parent_local_id) = module_attributes.parent { - if format_parent_module_from_module_id( - &ModuleId { krate: id.krate, local_id: parent_local_id }, - args, - &mut string, - ) { - string.push('\n'); - } - } - string.push_str(" "); - string.push_str("mod "); - string.push_str(&module_attributes.name); Some(string) } @@ -108,6 +114,9 @@ fn format_struct(id: StructId, args: &ProcessRequestCallbackArgs) -> String { string.push_str(",\n"); } string.push_str(" }"); + + append_doc_comments(args.interner, ReferenceId::Struct(id), &mut string); + string } @@ -131,6 +140,9 @@ fn format_struct_member( string.push_str(": "); string.push_str(&format!("{}", field_type)); string.push_str(&go_to_type_links(field_type, args.interner, args.files)); + + append_doc_comments(args.interner, ReferenceId::StructMember(id, field_index), &mut string); + string } @@ -145,6 +157,9 @@ fn format_trait(id: TraitId, args: &ProcessRequestCallbackArgs) -> String { string.push_str("trait "); string.push_str(&a_trait.name.0.contents); format_generics(&a_trait.generics, &mut string); + + append_doc_comments(args.interner, ReferenceId::Trait(id), &mut string); + string } @@ -163,6 +178,9 @@ fn format_global(id: GlobalId, args: &ProcessRequestCallbackArgs) -> String { string.push_str(": "); string.push_str(&format!("{}", typ)); string.push_str(&go_to_type_links(&typ, args.interner, args.files)); + + append_doc_comments(args.interner, ReferenceId::Global(id), &mut string); + string } @@ -220,6 +238,8 @@ fn format_function(id: FuncId, args: &ProcessRequestCallbackArgs) -> String { string.push_str(&go_to_type_links(return_type, args.interner, args.files)); + append_doc_comments(args.interner, ReferenceId::Function(id), &mut string); + string } @@ -235,6 +255,9 @@ fn format_alias(id: TypeAliasId, args: &ProcessRequestCallbackArgs) -> String { string.push_str(&type_alias.name.0.contents); string.push_str(" = "); string.push_str(&format!("{}", &type_alias.typ)); + + append_doc_comments(args.interner, ReferenceId::Alias(id), &mut string); + string } @@ -270,6 +293,7 @@ fn format_local(id: DefinitionId, args: &ProcessRequestCallbackArgs) -> String { string } +/// Some doc comments fn format_generics(generics: &Generics, string: &mut String) { if generics.is_empty() { return; @@ -351,9 +375,9 @@ fn format_parent_module_from_module_id( } } - // We don't record module attriubtes for the root module, + // We don't record module attributes for the root module, // so we handle that case separately - if let CrateId::Root(_) = module.krate { + if module.krate.is_root() { segments.push(&args.crate_name); }; @@ -513,6 +537,16 @@ fn format_link(name: String, location: lsp_types::Location) -> String { ) } +fn append_doc_comments(interner: &NodeInterner, id: ReferenceId, string: &mut String) { + if let Some(doc_comments) = interner.doc_comments(id) { + string.push_str("\n\n---\n\n"); + for comment in doc_comments { + string.push_str(comment); + string.push('\n'); + } + } +} + #[cfg(test)] mod hover_tests { use crate::test_utils; diff --git a/tooling/lsp/src/requests/mod.rs b/tooling/lsp/src/requests/mod.rs index af58396550d..fea758e0e52 100644 --- a/tooling/lsp/src/requests/mod.rs +++ b/tooling/lsp/src/requests/mod.rs @@ -15,7 +15,7 @@ use lsp_types::{ WorkDoneProgressOptions, }; use nargo_fmt::Config; -use noirc_driver::file_manager_with_stdlib; + use noirc_frontend::graph::CrateId; use noirc_frontend::hir::def_map::CrateDefMap; use noirc_frontend::{graph::Dependency, macros_api::NodeInterner}; @@ -432,7 +432,7 @@ where ResponseError::new(ErrorCode::REQUEST_FAILED, "Could not find package for file") })?; - let mut workspace_file_manager = file_manager_with_stdlib(&workspace.root_dir); + let mut workspace_file_manager = workspace.new_file_manager(); insert_all_files_for_workspace_into_file_manager( state, &workspace, diff --git a/tooling/lsp/src/requests/profile_run.rs b/tooling/lsp/src/requests/profile_run.rs index d3b7743557a..a7362300adc 100644 --- a/tooling/lsp/src/requests/profile_run.rs +++ b/tooling/lsp/src/requests/profile_run.rs @@ -9,9 +9,7 @@ use async_lsp::{ErrorCode, ResponseError}; use nargo::ops::report_errors; use nargo_toml::{find_package_manifest, resolve_workspace_from_toml, PackageSelection}; use noirc_artifacts::debug::DebugArtifact; -use noirc_driver::{ - file_manager_with_stdlib, CompileOptions, DebugFile, NOIR_ARTIFACT_VERSION_STRING, -}; +use noirc_driver::{CompileOptions, DebugFile, NOIR_ARTIFACT_VERSION_STRING}; use noirc_errors::{debug_info::OpCodesCount, Location}; use crate::{ @@ -53,7 +51,7 @@ fn on_profile_run_request_inner( ResponseError::new(ErrorCode::REQUEST_FAILED, err) })?; - let mut workspace_file_manager = file_manager_with_stdlib(&workspace.root_dir); + let mut workspace_file_manager = workspace.new_file_manager(); insert_all_files_for_workspace_into_file_manager( state, &workspace, diff --git a/tooling/lsp/src/requests/test_run.rs b/tooling/lsp/src/requests/test_run.rs index 5f4f9cd98d0..081eb815c8b 100644 --- a/tooling/lsp/src/requests/test_run.rs +++ b/tooling/lsp/src/requests/test_run.rs @@ -4,9 +4,7 @@ use crate::insert_all_files_for_workspace_into_file_manager; use async_lsp::{ErrorCode, ResponseError}; use nargo::ops::{run_test, TestStatus}; use nargo_toml::{find_package_manifest, resolve_workspace_from_toml, PackageSelection}; -use noirc_driver::{ - check_crate, file_manager_with_stdlib, CompileOptions, NOIR_ARTIFACT_VERSION_STRING, -}; +use noirc_driver::{check_crate, CompileOptions, NOIR_ARTIFACT_VERSION_STRING}; use noirc_frontend::hir::FunctionNameMatch; use crate::{ @@ -48,7 +46,7 @@ fn on_test_run_request_inner( ResponseError::new(ErrorCode::REQUEST_FAILED, err) })?; - let mut workspace_file_manager = file_manager_with_stdlib(&workspace.root_dir); + let mut workspace_file_manager = workspace.new_file_manager(); insert_all_files_for_workspace_into_file_manager( state, &workspace, diff --git a/tooling/lsp/src/requests/tests.rs b/tooling/lsp/src/requests/tests.rs index 7203aca7f09..81910bebedb 100644 --- a/tooling/lsp/src/requests/tests.rs +++ b/tooling/lsp/src/requests/tests.rs @@ -4,7 +4,7 @@ use crate::insert_all_files_for_workspace_into_file_manager; use async_lsp::{ErrorCode, LanguageClient, ResponseError}; use lsp_types::{LogMessageParams, MessageType}; use nargo_toml::{find_package_manifest, resolve_workspace_from_toml, PackageSelection}; -use noirc_driver::{check_crate, file_manager_with_stdlib, NOIR_ARTIFACT_VERSION_STRING}; +use noirc_driver::{check_crate, NOIR_ARTIFACT_VERSION_STRING}; use crate::{ get_package_tests_in_crate, parse_diff, @@ -50,7 +50,7 @@ fn on_tests_request_inner( ResponseError::new(ErrorCode::REQUEST_FAILED, err) })?; - let mut workspace_file_manager = file_manager_with_stdlib(&workspace.root_dir); + let mut workspace_file_manager = workspace.new_file_manager(); insert_all_files_for_workspace_into_file_manager( state, &workspace, diff --git a/tooling/lsp/test_programs/document_symbol/src/main.nr b/tooling/lsp/test_programs/document_symbol/src/main.nr index 39b2c7fff12..a88a6a21e1f 100644 --- a/tooling/lsp/test_programs/document_symbol/src/main.nr +++ b/tooling/lsp/test_programs/document_symbol/src/main.nr @@ -24,3 +24,5 @@ impl SomeTrait for SomeStruct { mod submodule { global SOME_GLOBAL = 1; } + +impl i32 {} diff --git a/tooling/nargo/src/workspace.rs b/tooling/nargo/src/workspace.rs index 0795ffd9304..810a9edb7e1 100644 --- a/tooling/nargo/src/workspace.rs +++ b/tooling/nargo/src/workspace.rs @@ -9,6 +9,9 @@ use std::{ slice, }; +use fm::FileManager; +use noirc_driver::file_manager_with_stdlib; + use crate::{ constants::{CONTRACT_DIR, EXPORT_DIR, PROOFS_DIR, TARGET_DIR}, package::Package, @@ -46,6 +49,21 @@ impl Workspace { pub fn export_directory_path(&self) -> PathBuf { self.root_dir.join(EXPORT_DIR) } + + /// Returns a new `FileManager` for the root directory of this workspace. + /// If the root directory is not the standard library, the standard library + /// is added to the returned `FileManager`. + pub fn new_file_manager(&self) -> FileManager { + if self.is_stdlib() { + FileManager::new(&self.root_dir) + } else { + file_manager_with_stdlib(&self.root_dir) + } + } + + fn is_stdlib(&self) -> bool { + self.members.len() == 1 && self.members[0].name.to_string() == "std" + } } pub enum IntoIter<'a, T> { diff --git a/tooling/nargo_cli/build.rs b/tooling/nargo_cli/build.rs index 4dcfccdf085..7469c8be0f8 100644 --- a/tooling/nargo_cli/build.rs +++ b/tooling/nargo_cli/build.rs @@ -218,7 +218,7 @@ fn generate_compile_success_empty_tests(test_file: &mut File, test_data_dir: &Pa &test_dir, &format!( r#" - nargo.arg("info").arg("--arithmetic-generics").arg("--json").arg("--force"); + nargo.arg("info").arg("--json").arg("--force"); {assert_zero_opcodes}"#, ), diff --git a/tooling/nargo_cli/src/cli/check_cmd.rs b/tooling/nargo_cli/src/cli/check_cmd.rs index 5239070b4d2..65a09dcdd11 100644 --- a/tooling/nargo_cli/src/cli/check_cmd.rs +++ b/tooling/nargo_cli/src/cli/check_cmd.rs @@ -10,8 +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, - NOIR_ARTIFACT_VERSION_STRING, + check_crate, compute_function_abi, CompileOptions, NOIR_ARTIFACT_VERSION_STRING, }; use noirc_frontend::{ graph::{CrateId, CrateName}, @@ -52,7 +51,7 @@ pub(crate) fn run(args: CheckCommand, config: NargoConfig) -> Result<(), CliErro Some(NOIR_ARTIFACT_VERSION_STRING.to_string()), )?; - let mut workspace_file_manager = file_manager_with_stdlib(&workspace.root_dir); + let mut workspace_file_manager = workspace.new_file_manager(); insert_all_files_for_workspace_into_file_manager(&workspace, &mut workspace_file_manager); let parsed_files = parse_all(&workspace_file_manager); diff --git a/tooling/nargo_cli/src/cli/compile_cmd.rs b/tooling/nargo_cli/src/cli/compile_cmd.rs index 85faf574a0a..ae96f6436e2 100644 --- a/tooling/nargo_cli/src/cli/compile_cmd.rs +++ b/tooling/nargo_cli/src/cli/compile_cmd.rs @@ -9,8 +9,8 @@ use nargo::package::Package; use nargo::workspace::Workspace; use nargo::{insert_all_files_for_workspace_into_file_manager, parse_all}; use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; +use noirc_driver::DEFAULT_EXPRESSION_WIDTH; use noirc_driver::NOIR_ARTIFACT_VERSION_STRING; -use noirc_driver::{file_manager_with_stdlib, DEFAULT_EXPRESSION_WIDTH}; use noirc_driver::{CompilationResult, CompileOptions, CompiledContract}; use noirc_frontend::graph::CrateName; @@ -114,7 +114,7 @@ pub(super) fn compile_workspace_full( workspace: &Workspace, compile_options: &CompileOptions, ) -> Result<(), CliError> { - let mut workspace_file_manager = file_manager_with_stdlib(&workspace.root_dir); + let mut workspace_file_manager = workspace.new_file_manager(); insert_all_files_for_workspace_into_file_manager(workspace, &mut workspace_file_manager); let parsed_files = parse_all(&workspace_file_manager); diff --git a/tooling/nargo_cli/src/cli/export_cmd.rs b/tooling/nargo_cli/src/cli/export_cmd.rs index 19add7f30dc..c3752db7fbd 100644 --- a/tooling/nargo_cli/src/cli/export_cmd.rs +++ b/tooling/nargo_cli/src/cli/export_cmd.rs @@ -12,8 +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, - NOIR_ARTIFACT_VERSION_STRING, + compile_no_check, CompileOptions, CompiledProgram, NOIR_ARTIFACT_VERSION_STRING, }; use noirc_frontend::graph::CrateName; @@ -54,7 +53,7 @@ pub(crate) fn run(args: ExportCommand, config: NargoConfig) -> Result<(), CliErr Some(NOIR_ARTIFACT_VERSION_STRING.to_owned()), )?; - let mut workspace_file_manager = file_manager_with_stdlib(&workspace.root_dir); + let mut workspace_file_manager = workspace.new_file_manager(); insert_all_files_for_workspace_into_file_manager(&workspace, &mut workspace_file_manager); let parsed_files = parse_all(&workspace_file_manager); diff --git a/tooling/nargo_cli/src/cli/fmt_cmd.rs b/tooling/nargo_cli/src/cli/fmt_cmd.rs index 8f66a0a328f..66496db517c 100644 --- a/tooling/nargo_cli/src/cli/fmt_cmd.rs +++ b/tooling/nargo_cli/src/cli/fmt_cmd.rs @@ -3,7 +3,7 @@ use std::{fs::DirEntry, path::Path}; use clap::Args; use nargo::{insert_all_files_for_workspace_into_file_manager, ops::report_errors}; use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; -use noirc_driver::{file_manager_with_stdlib, NOIR_ARTIFACT_VERSION_STRING}; +use noirc_driver::NOIR_ARTIFACT_VERSION_STRING; use noirc_errors::CustomDiagnostic; use noirc_frontend::{hir::def_map::parse_file, parser::ParserError}; @@ -29,7 +29,7 @@ pub(crate) fn run(args: FormatCommand, config: NargoConfig) -> Result<(), CliErr Some(NOIR_ARTIFACT_VERSION_STRING.to_string()), )?; - let mut workspace_file_manager = file_manager_with_stdlib(&workspace.root_dir); + let mut workspace_file_manager = workspace.new_file_manager(); insert_all_files_for_workspace_into_file_manager(&workspace, &mut workspace_file_manager); let config = nargo_fmt::Config::read(&config.program_dir) diff --git a/tooling/nargo_cli/src/cli/test_cmd.rs b/tooling/nargo_cli/src/cli/test_cmd.rs index 2f9390d72e0..8b3c0e29c7d 100644 --- a/tooling/nargo_cli/src/cli/test_cmd.rs +++ b/tooling/nargo_cli/src/cli/test_cmd.rs @@ -9,9 +9,7 @@ use nargo::{ prepare_package, }; 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, -}; +use noirc_driver::{check_crate, CompileOptions, NOIR_ARTIFACT_VERSION_STRING}; use noirc_frontend::{ graph::CrateName, hir::{FunctionNameMatch, ParsedFiles}, @@ -65,7 +63,7 @@ pub(crate) fn run(args: TestCommand, config: NargoConfig) -> Result<(), CliError Some(NOIR_ARTIFACT_VERSION_STRING.to_string()), )?; - let mut workspace_file_manager = file_manager_with_stdlib(&workspace.root_dir); + let mut workspace_file_manager = workspace.new_file_manager(); insert_all_files_for_workspace_into_file_manager(&workspace, &mut workspace_file_manager); let parsed_files = parse_all(&workspace_file_manager); diff --git a/tooling/nargo_fmt/src/visitor/item.rs b/tooling/nargo_fmt/src/visitor/item.rs index 9e556e0fcbe..ba9a8214702 100644 --- a/tooling/nargo_fmt/src/visitor/item.rs +++ b/tooling/nargo_fmt/src/visitor/item.rs @@ -151,7 +151,7 @@ impl super::FmtVisitor<'_> { } fn visit_module(&mut self, module: ParsedModule) { - for Item { kind, span } in module.items { + for Item { kind, span, doc_comments } in module.items { match kind { ItemKind::Function(func) => { self.visit_function(span, func); @@ -165,6 +165,11 @@ impl super::FmtVisitor<'_> { continue; } + for doc_comment in doc_comments { + self.push_str(&format!("///{doc_comment}\n")); + self.push_str(&self.indent.to_string()); + } + for attribute in module.outer_attributes { self.push_str(&format!("#[{}]\n", attribute.as_ref())); self.push_str(&self.indent.to_string()); @@ -214,7 +219,7 @@ impl super::FmtVisitor<'_> { self.indent.block_indent(self.config); for (method, span) in impl_.methods { - self.visit_function(span, method); + self.visit_function(span, method.item); } self.close_block((self.last_position..span.end() - 1).into());