diff --git a/.noir-sync-commit b/.noir-sync-commit index 19e6077a33b..d3b7e1db860 100644 --- a/.noir-sync-commit +++ b/.noir-sync-commit @@ -1 +1 @@ -c4273a0c8f8b751a3dbe097e070e4e7b2c8ec438 +ae87d287ab1fae0f999dfd0d1166fbddb927ba97 diff --git a/boxes/boxes/react/src/contracts/src/main.nr b/boxes/boxes/react/src/contracts/src/main.nr index 90c5e16673a..83c0fccead6 100644 --- a/boxes/boxes/react/src/contracts/src/main.nr +++ b/boxes/boxes/react/src/contracts/src/main.nr @@ -16,7 +16,8 @@ contract BoxReact { } #[private] - #[initializer] + #['private] + #['initializer] fn constructor( number: Field, owner: AztecAddress, @@ -30,6 +31,7 @@ contract BoxReact { } #[private] + #['private] fn setNumber( number: Field, owner: AztecAddress, diff --git a/boxes/boxes/vanilla/src/contracts/src/main.nr b/boxes/boxes/vanilla/src/contracts/src/main.nr index 8763e30760f..73e50e49c8c 100644 --- a/boxes/boxes/vanilla/src/contracts/src/main.nr +++ b/boxes/boxes/vanilla/src/contracts/src/main.nr @@ -16,7 +16,8 @@ contract Vanilla { } #[private] - #[initializer] + #['private] + #['initializer] fn constructor( number: Field, owner: AztecAddress, @@ -30,6 +31,7 @@ contract Vanilla { } #[private] + #['private] fn setNumber( number: Field, owner: AztecAddress, diff --git a/boxes/init/src/main.nr b/boxes/init/src/main.nr index 7924ff03505..c7f6de05921 100644 --- a/boxes/init/src/main.nr +++ b/boxes/init/src/main.nr @@ -4,6 +4,7 @@ use dep::aztec::macros::aztec; #[aztec] contract Main { #[private] - #[initializer] + #['private] + #['initializer] fn constructor() { } } diff --git a/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr index 52a42d1694d..47172e72d7f 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr @@ -93,6 +93,7 @@ pub comptime fn generate_public_dispatch(m: Module) -> Quoted { // functions having this attribute. However, the public MACRO will // handle the public_dispatch function specially and do nothing. #[public] + #['public] pub unconstrained fn public_dispatch(selector: Field) { $dispatch } diff --git a/noir-projects/noir-contracts/contracts/app_subscription_contract/src/main.nr b/noir-projects/noir-contracts/contracts/app_subscription_contract/src/main.nr index dfd2f5480ab..8adcbb95e8b 100644 --- a/noir-projects/noir-contracts/contracts/app_subscription_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/app_subscription_contract/src/main.nr @@ -34,6 +34,7 @@ contract AppSubscription { global SUBSCRIPTION_TXS = 5; #[private] + #['private] fn entrypoint(payload: DAppPayload, user_address: AztecAddress) { // Default msg_sender for entrypoints is now Fr.max_value rather than 0 addr (see #7190 & #7404) assert(context.msg_sender().to_field() == MAX_FIELD_VALUE); @@ -64,7 +65,8 @@ contract AppSubscription { } #[public] - #[initializer] + #['public] + #['initializer] fn constructor( target_address: AztecAddress, subscription_recipient_address: AztecAddress, @@ -80,6 +82,7 @@ contract AppSubscription { } #[private] + #['private] fn subscribe(subscriber: AztecAddress, nonce: Field, expiry_block_number: Field, tx_count: Field) { assert(tx_count as u64 <= SUBSCRIPTION_TXS as u64); diff --git a/noir-projects/noir-contracts/contracts/auth_contract/src/main.nr b/noir-projects/noir-contracts/contracts/auth_contract/src/main.nr index 18f4cd0aebb..1b2d99856ce 100644 --- a/noir-projects/noir-contracts/contracts/auth_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/auth_contract/src/main.nr @@ -24,7 +24,8 @@ contract Auth { } #[public] - #[initializer] + #['public] + #['initializer] fn constructor(admin: AztecAddress) { assert(!admin.is_zero(), "invalid admin"); storage.admin.initialize(admin); @@ -32,6 +33,7 @@ contract Auth { // docs:start:shared_mutable_schedule #[public] + #['public] fn set_authorized(authorized: AztecAddress) { assert_eq(storage.admin.read(), context.msg_sender(), "caller is not admin"); storage.authorized.schedule_value_change(authorized); @@ -40,7 +42,8 @@ contract Auth { // docs:start:public_getter #[public] - #[view] + #['public] + #['view] fn get_authorized() -> AztecAddress { // docs:start:shared_mutable_get_current_public storage.authorized.get_current_value_in_public() @@ -49,7 +52,8 @@ contract Auth { // docs:end:public_getter #[public] - #[view] + #['public] + #['view] fn get_scheduled_authorized() -> AztecAddress { // docs:start:shared_mutable_get_scheduled_public let (scheduled_value, _block_of_change): (AztecAddress, u32) = storage.authorized.get_scheduled_value_in_public(); @@ -58,17 +62,20 @@ contract Auth { } #[public] - #[view] + #['public] + #['view] fn get_authorized_delay() -> pub u32 { storage.authorized.get_current_delay_in_public() } #[public] + #['public] fn set_authorized_delay(new_delay: u32) { storage.authorized.schedule_delay_change(new_delay); } #[private] + #['private] fn do_private_authorized_thing() { // Reading a value from authorized in private automatically adds an extra validity condition: the base rollup // circuit will reject this tx if included in a block past the block horizon, which is as far as the circuit can @@ -80,7 +87,8 @@ contract Auth { } #[private] - #[view] + #['private] + #['view] fn get_authorized_in_private() -> AztecAddress { storage.authorized.get_current_value_in_private() } diff --git a/noir-projects/noir-contracts/contracts/auth_registry_contract/src/main.nr b/noir-projects/noir-contracts/contracts/auth_registry_contract/src/main.nr index 965f39ea20a..63a744345f9 100644 --- a/noir-projects/noir-contracts/contracts/auth_registry_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/auth_registry_contract/src/main.nr @@ -22,6 +22,7 @@ contract AuthRegistry { * @param authorize True if the caller is authorized to perform the message hash, false otherwise */ #[public] + #['public] fn set_authorized(message_hash: Field, authorize: bool) { storage.approved_actions.at(context.msg_sender()).at(message_hash).write(authorize); } @@ -34,6 +35,7 @@ contract AuthRegistry { * @param reject True if all actions should be rejected, false otherwise */ #[public] + #['public] fn set_reject_all(reject: bool) { storage.reject_all.at(context.msg_sender()).write(reject); } @@ -49,6 +51,7 @@ contract AuthRegistry { * @return `IS_VALID_SELECTOR` if the action was consumed, revert otherwise */ #[public] + #['public] fn consume(on_behalf_of: AztecAddress, inner_hash: Field) -> Field { assert_eq(false, storage.reject_all.at(on_behalf_of).read(), "rejecting all"); @@ -79,6 +82,7 @@ contract AuthRegistry { * @param authorize True if the message hash should be authorized, false otherwise */ #[private] + #['private] fn set_authorized_private(approver: AztecAddress, message_hash: Field, authorize: bool) { assert_current_call_valid_authwit(&mut context, approver); AuthRegistry::at(context.this_address())._set_authorized(approver, message_hash, authorize).enqueue(&mut context); @@ -93,7 +97,8 @@ contract AuthRegistry { * @param authorize True if the caller is authorized to perform the message hash, false otherwise */ #[public] - #[internal] + #['public] + #['internal] fn _set_authorized(approver: AztecAddress, message_hash: Field, authorize: bool) { storage.approved_actions.at(approver).at(message_hash).write(authorize); } @@ -105,7 +110,8 @@ contract AuthRegistry { * @return True if all actions are rejected, false otherwise */ #[public] - #[view] + #['public] + #['view] fn is_reject_all(on_behalf_of: AztecAddress) -> bool { storage.reject_all.at(on_behalf_of).read() } @@ -118,7 +124,8 @@ contract AuthRegistry { * @return True if the caller is authorized to perform the action, false otherwise */ #[public] - #[view] + #['public] + #['view] fn is_consumable(on_behalf_of: AztecAddress, message_hash: Field) -> bool { storage.approved_actions.at(on_behalf_of).at(message_hash).read() } diff --git a/noir-projects/noir-contracts/contracts/auth_wit_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/auth_wit_test_contract/src/main.nr index 8fbf2ba8c82..6d705d070f3 100644 --- a/noir-projects/noir-contracts/contracts/auth_wit_test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/auth_wit_test_contract/src/main.nr @@ -7,11 +7,13 @@ contract AuthWitTest { use dep::authwit::auth::{assert_inner_hash_valid_authwit, assert_inner_hash_valid_authwit_public}; #[private] + #['private] fn consume(on_behalf_of: AztecAddress, inner_hash: Field) { assert_inner_hash_valid_authwit(&mut context, on_behalf_of, inner_hash); } #[public] + #['public] fn consume_public(on_behalf_of: AztecAddress, inner_hash: Field) { assert_inner_hash_valid_authwit_public(&mut context, on_behalf_of, inner_hash); } diff --git a/noir-projects/noir-contracts/contracts/avm_initializer_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/avm_initializer_test_contract/src/main.nr index 8c74544a783..157f27e1663 100644 --- a/noir-projects/noir-contracts/contracts/avm_initializer_test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/avm_initializer_test_contract/src/main.nr @@ -14,12 +14,14 @@ contract AvmInitializerTest { * Storage ************************************************************************/ #[public] - #[initializer] + #['public] + #['initializer] fn constructor() { storage.immutable.initialize(42); } #[public] + #['public] fn read_storage_immutable() -> pub Field { storage.immutable.read() } diff --git a/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr index 79b67ccf7de..bd80f6b4904 100644 --- a/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr @@ -50,34 +50,40 @@ contract AvmTest { * Storage ************************************************************************/ #[public] + #['public] fn set_storage_single(a: Field) { storage.single.write(a); } #[public] + #['public] fn read_storage_single() -> Field { storage.single.read() } // should still be able to use ` -> pub *` for return type even though macro forces `pub` #[public] + #['public] fn set_read_storage_single(a: Field) -> pub Field { storage.single.write(a); storage.single.read() } #[public] + #['public] fn set_storage_list(a: Field, b: Field) { storage.list.write(Note { a, b }); } #[public] + #['public] fn read_storage_list() -> [Field; 2] { let note: Note = storage.list.read(); note.serialize() } #[public] + #['public] fn set_storage_map(to: AztecAddress, amount: u32) -> Field { storage.map.at(to).write(amount); // returns storage slot for key @@ -85,6 +91,7 @@ contract AvmTest { } #[public] + #['public] fn add_storage_map(to: AztecAddress, amount: u32) -> Field { let new_balance = storage.map.at(to).read().add(amount); storage.map.at(to).write(new_balance); @@ -93,11 +100,13 @@ contract AvmTest { } #[public] + #['public] fn read_storage_map(address: AztecAddress) -> u32 { storage.map.at(address).read() } #[public] + #['public] fn add_args_return(arg_a: Field, arg_b: Field) -> Field { arg_a + arg_b } @@ -106,51 +115,61 @@ contract AvmTest { * General Opcodes ************************************************************************/ #[public] + #['public] fn set_opcode_u8() -> u8 { 8 as u8 } #[public] + #['public] fn set_opcode_u32() -> u32 { 1 << 30 as u8 } #[public] + #['public] fn set_opcode_u64() -> u64 { 1 << 60 as u8 } #[public] + #['public] fn set_opcode_small_field() -> Field { big_field_128_bits } #[public] + #['public] fn set_opcode_big_field() -> Field { big_field_136_bits } #[public] + #['public] fn set_opcode_really_big_field() -> Field { big_field_254_bits } #[public] + #['public] fn add_u128(a: U128, b: U128) -> U128 { a + b } #[public] + #['public] fn modulo2(a: u64) -> u64 { a % 2 } #[public] + #['public] fn elliptic_curve_add(lhs: Point, rhs: Point) -> Point { lhs + rhs } #[public] + #['public] fn elliptic_curve_add_and_double() -> Point { let g = Point { x: 1, y: 17631683881184975370165255887551781615748388533673675138860, is_infinite: false }; @@ -160,6 +179,7 @@ contract AvmTest { } #[public] + #['public] fn variable_base_msm() -> Point { let g = Point { x: 1, y: 17631683881184975370165255887551781615748388533673675138860, is_infinite: false }; let scalar = Scalar { lo: 3, hi: 0 }; @@ -169,6 +189,7 @@ contract AvmTest { } #[public] + #['public] fn pedersen_commit(x: Field, y: Field) -> EmbeddedCurvePoint { let commitment = dep::std::hash::pedersen_commitment_with_separator([x, y], 20); commitment @@ -179,6 +200,7 @@ contract AvmTest { ************************************************************************/ #[public] + #['public] fn u128_addition_overflow() -> U128 { let max_u128: U128 = U128::from_hex("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); let one: U128 = U128::from_integer(1); @@ -186,12 +208,14 @@ contract AvmTest { } #[public] + #['public] fn u128_from_integer_overflow() -> U128 { let should_overflow: Field = 2.pow_32(128); // U128::max() + 1; U128::from_integer(should_overflow) } #[public] + #['public] fn to_radix_le(input: Field) -> [u8; 10] { input.to_le_radix(/*base=*/ 2) } @@ -206,11 +230,13 @@ contract AvmTest { } #[public] + #['public] fn assertion_failure() { helper_with_failed_assertion() } #[public] + #['public] fn debug_logging() { dep::aztec::oracle::debug_log::debug_log("just text"); dep::aztec::oracle::debug_log::debug_log_format("second: {1}", [1, 2, 3, 4]); @@ -219,12 +245,14 @@ contract AvmTest { } #[public] + #['public] fn assert_same(arg_a: Field, arg_b: Field) -> pub Field { assert(arg_a == arg_b, "Values are not equal"); 1 } #[public] + #['public] fn assert_calldata_copy(args: [Field; 3]) { let offset = 0; let cd: [Field; 3] = dep::aztec::context::public_context::calldata_copy(offset, 3); @@ -232,6 +260,7 @@ contract AvmTest { } #[public] + #['public] fn return_oracle() -> [Field; 3] { dep::aztec::context::public_context::avm_return([1, 2, 3]); [4, 5, 6] // Should not get here. @@ -241,31 +270,37 @@ contract AvmTest { * Hashing functions ************************************************************************/ #[public] + #['public] fn keccak_hash(data: [u8; 10]) -> [u8; 32] { std::hash::keccak256(data, data.len() as u32) } #[public] + #['public] fn keccak_f1600(data: [u64; 25]) -> [u64; 25] { std::hash::keccak::keccakf1600(data) } #[public] + #['public] fn poseidon2_hash(data: [Field; 10]) -> Field { std::hash::poseidon2::Poseidon2::hash(data, data.len()) } #[public] + #['public] fn sha256_hash(data: [u8; 10]) -> [u8; 32] { std::hash::sha256(data) } #[public] + #['public] fn pedersen_hash(data: [Field; 10]) -> Field { std::hash::pedersen_hash(data) } #[public] + #['public] fn pedersen_hash_with_index(data: [Field; 10]) -> Field { std::hash::pedersen_hash_with_separator(data, /*index=*/ 20) } @@ -274,6 +309,7 @@ contract AvmTest { * Contract instance ************************************************************************/ #[public] + #['public] fn test_get_contract_instance_raw() { let fields = get_contract_instance_internal_avm(context.this_address()); // The values here should match those in `avm_simulator.test.ts>Contract>GETCONTRACTINSTANCE deserializes correctly` @@ -287,6 +323,7 @@ contract AvmTest { } #[public] + #['public] fn test_get_contract_instance() { let ci = get_contract_instance_avm(context.this_address()); assert(ci.is_some(), "Contract instance not found!"); @@ -296,77 +333,92 @@ contract AvmTest { * AvmContext functions ************************************************************************/ #[public] + #['public] fn get_address() -> AztecAddress { context.this_address() } #[public] + #['public] fn get_storage_address() -> AztecAddress { context.storage_address() } #[public] + #['public] fn get_sender() -> AztecAddress { context.msg_sender() } #[public] + #['public] fn get_function_selector() -> FunctionSelector { context.selector() } #[public] + #['public] fn get_transaction_fee() -> Field { context.transaction_fee() } #[public] + #['public] fn get_chain_id() -> Field { context.chain_id() } #[public] + #['public] fn get_version() -> Field { context.version() } #[public] + #['public] fn get_block_number() -> Field { context.block_number() } #[public] + #['public] fn get_timestamp() -> u64 { context.timestamp() } #[public] + #['public] fn get_fee_per_l2_gas() -> Field { context.fee_per_l2_gas() } #[public] + #['public] fn get_fee_per_da_gas() -> Field { context.fee_per_da_gas() } #[public] + #['public] fn get_l2_gas_left() -> Field { context.l2_gas_left() } #[public] + #['public] fn get_da_gas_left() -> Field { context.da_gas_left() } #[public] + #['public] fn assert_timestamp(expected_timestamp: u64) { let timestamp = context.timestamp(); assert(timestamp == expected_timestamp, "timestamp does not match"); } #[public] + #['public] fn check_selector() { assert( context.selector() == comptime { @@ -376,11 +428,13 @@ contract AvmTest { } #[public] + #['public] fn get_args_hash(_a: u8, _fields: [Field; 3]) -> Field { context.get_args_hash() } #[public] + #['public] fn emit_unencrypted_log() { context.emit_unencrypted_log(/*message=*/ [10, 20, 30]); context.emit_unencrypted_log(/*message=*/ "Hello, world!"); @@ -389,35 +443,41 @@ contract AvmTest { } #[public] + #['public] fn note_hash_exists(note_hash: Field, leaf_index: Field) -> bool { context.note_hash_exists(note_hash, leaf_index) } // Use the standard context interface to emit a new note hash #[public] + #['public] fn new_note_hash(note_hash: Field) { context.push_note_hash(note_hash); } // Use the standard context interface to emit a new nullifier #[public] + #['public] fn new_nullifier(nullifier: Field) { context.push_nullifier(nullifier); } // Use the standard context interface to check for a nullifier #[public] + #['public] fn nullifier_exists(nullifier: Field) -> bool { context.nullifier_exists(nullifier, context.storage_address()) } #[public] + #['public] fn assert_nullifier_exists(nullifier: Field) { assert(context.nullifier_exists(nullifier, context.storage_address()), "Nullifier doesn't exist!"); } // Use the standard context interface to emit a new nullifier #[public] + #['public] fn emit_nullifier_and_check(nullifier: Field) { context.push_nullifier(nullifier); let exists = context.nullifier_exists(nullifier, context.storage_address()); @@ -426,6 +486,7 @@ contract AvmTest { // Create the same nullifier twice (shouldn't work!) #[public] + #['public] fn nullifier_collision(nullifier: Field) { context.push_nullifier(nullifier); // Can't do this twice! @@ -433,11 +494,13 @@ contract AvmTest { } #[public] + #['public] fn l1_to_l2_msg_exists(msg_hash: Field, msg_leaf_index: Field) -> bool { context.l1_to_l2_msg_exists(msg_hash, msg_leaf_index) } #[public] + #['public] fn send_l2_to_l1_msg(recipient: EthAddress, content: Field) { context.message_portal(recipient, content) } @@ -446,6 +509,7 @@ contract AvmTest { * Nested calls ************************************************************************/ #[public] + #['public] fn nested_call_to_add_with_gas( arg_a: Field, arg_b: Field, @@ -457,35 +521,41 @@ contract AvmTest { // Use the `call_public_function` wrapper to initiate a nested call to the add function #[public] + #['public] fn nested_call_to_add(arg_a: Field, arg_b: Field) -> pub Field { AvmTest::at(context.this_address()).add_args_return(arg_a, arg_b).call(&mut context) } // Indirectly call_static the external call opcode to initiate a nested call to the add function #[public] + #['public] fn nested_static_call_to_add(arg_a: Field, arg_b: Field) -> pub Field { AvmTest::at(context.this_address()).add_args_return(arg_a, arg_b).view(&mut context) } // Indirectly call_static `set_storage_single`. Should revert since it's accessing storage. #[public] + #['public] fn nested_static_call_to_set_storage() { AvmTest::at(context.this_address()).set_storage_single(20).view(&mut context); } #[public] + #['public] fn create_same_nullifier_in_nested_call(nestedAddress: AztecAddress, nullifier: Field) { context.push_nullifier(nullifier); AvmTest::at(nestedAddress).new_nullifier(nullifier).call(&mut context); } #[public] + #['public] fn create_different_nullifier_in_nested_call(nestedAddress: AztecAddress, nullifier: Field) { context.push_nullifier(nullifier); AvmTest::at(nestedAddress).new_nullifier(nullifier + 1).call(&mut context); } #[public] + #['public] fn nested_call_to_assert_same(arg_a: Field, arg_b: Field) -> pub Field { AvmTest::at(context.this_address()).assert_same(arg_a, arg_b).call(&mut context) } @@ -494,6 +564,7 @@ contract AvmTest { * Enqueue a public call from private */ #[private] + #['private] fn enqueue_public_from_private() { AvmTest::at(context.this_address()).set_opcode_u8().enqueue_view(&mut context); AvmTest::at(context.this_address()).set_read_storage_single(5).enqueue(&mut context); @@ -506,6 +577,7 @@ contract AvmTest { * be optimized away. ************************************************************************/ #[public] + #['public] fn bulk_testing(args_field: [Field; 10], args_u8: [u8; 10]) { dep::aztec::oracle::debug_log::debug_log("set_storage_single"); set_storage_single(30); diff --git a/noir-projects/noir-contracts/contracts/benchmarking_contract/src/main.nr b/noir-projects/noir-contracts/contracts/benchmarking_contract/src/main.nr index 2116a55fa3a..3b59926503e 100644 --- a/noir-projects/noir-contracts/contracts/benchmarking_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/benchmarking_contract/src/main.nr @@ -20,6 +20,7 @@ contract Benchmarking { // Creates a new value note for the target owner. Use this method to seed an initial set of notes. #[private] + #['private] fn create_note(owner: AztecAddress, outgoing_viewer: AztecAddress, value: Field) { // docs:start:increment_valuenote increment(storage.notes.at(owner), value, owner, outgoing_viewer); @@ -31,6 +32,7 @@ contract Benchmarking { // See https://discourse.aztec.network/t/utxo-concurrency-issues-for-private-state/635 // by @rahul-kothari for a full explanation on why this is needed. #[private] + #['private] fn recreate_note(owner: AztecAddress, outgoing_viewer: AztecAddress, index: u32) { let owner_notes = storage.notes.at(owner); let mut getter_options = NoteGetterOptions::new(); @@ -41,6 +43,7 @@ contract Benchmarking { // Reads and writes to public storage and enqueues a call to another public function. #[public] + #['public] fn increment_balance(owner: AztecAddress, value: Field) { let current = storage.balances.at(owner).read(); storage.balances.at(owner).write(current + value); @@ -49,6 +52,7 @@ contract Benchmarking { // Emits a public log. #[public] + #['public] fn broadcast(owner: AztecAddress) { context.emit_unencrypted_log(storage.balances.at(owner).read()); } diff --git a/noir-projects/noir-contracts/contracts/card_game_contract/src/main.nr b/noir-projects/noir-contracts/contracts/card_game_contract/src/main.nr index 095f870193b..efbf2f00cbf 100644 --- a/noir-projects/noir-contracts/contracts/card_game_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/card_game_contract/src/main.nr @@ -22,6 +22,7 @@ contract CardGame { } #[private] + #['private] fn buy_pack(seed: Field // The randomness used to generate the cards. Passed in for now. ) { let buyer = context.msg_sender(); @@ -32,6 +33,7 @@ contract CardGame { } #[private] + #['private] fn join_game(game: u32, cards_fields: [Field; 2]) { let cards = cards_fields.map(|card_field| Card::from_field(card_field)); @@ -46,7 +48,8 @@ contract CardGame { } #[public] - #[internal] + #['public] + #['internal] fn on_game_joined(game: u32, player: AztecAddress, deck_strength: u32) { let game_storage = storage.games.at(game as Field); @@ -57,6 +60,7 @@ contract CardGame { } #[public] + #['public] fn start_game(game: u32) { let game_storage = storage.games.at(game as Field); @@ -66,6 +70,7 @@ contract CardGame { } #[private] + #['private] fn play_card(game: u32, card: Card) { let player = context.msg_sender(); @@ -78,7 +83,8 @@ contract CardGame { } #[public] - #[internal] + #['public] + #['internal] fn on_card_played(game: u32, player: AztecAddress, card_as_field: Field) { let game_storage = storage.games.at(game as Field); @@ -93,6 +99,7 @@ contract CardGame { } #[private] + #['private] fn claim_cards(game: u32, cards_fields: [Field; PLAYABLE_CARDS]) { let player = context.msg_sender(); let cards = cards_fields.map(|card_field| Card::from_field(card_field)); @@ -103,7 +110,8 @@ contract CardGame { } #[public] - #[internal] + #['public] + #['internal] fn on_cards_claimed(game: u32, player: AztecAddress, cards_hash: Field) { let game_storage = storage.games.at(game as Field); let mut game_data = game_storage.read(); diff --git a/noir-projects/noir-contracts/contracts/child_contract/src/main.nr b/noir-projects/noir-contracts/contracts/child_contract/src/main.nr index 04a90c9a08d..b9ec1d59cb7 100644 --- a/noir-projects/noir-contracts/contracts/child_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/child_contract/src/main.nr @@ -21,19 +21,22 @@ contract Child { // Returns a sum of the input and the chain id and version of the contract in private circuit public input's return_values. #[private] + #['private] fn value(input: Field) -> Field { input + context.chain_id() + context.version() } // Returns a sum of the input and the chain id and version of the contract in private circuit public input's return_values. // Can only be called from this contract. #[private] - #[internal] + #['private] + #['internal] fn value_internal(input: Field) -> Field { input + context.chain_id() + context.version() } // Returns base_value + chain_id + version + block_number + timestamp #[public] + #['public] fn pub_get_value(base_value: Field) -> Field { let return_value = base_value + context.chain_id() @@ -46,6 +49,7 @@ contract Child { // Sets `current_value` to `new_value` #[public] + #['public] fn pub_set_value(new_value: Field) -> Field { storage.current_value.write(new_value); context.emit_unencrypted_log(new_value); @@ -54,6 +58,7 @@ contract Child { } #[private] + #['private] fn private_set_value(new_value: Field, owner: AztecAddress) -> Field { let owner_keys = get_public_keys(owner); @@ -63,6 +68,7 @@ contract Child { } #[private] + #['private] fn private_get_value(amount: Field, owner: AztecAddress) -> Field { let mut options = NoteGetterOptions::new(); options = options.select(ValueNote::properties().value, Comparator.EQ, amount).set_limit(1); @@ -72,6 +78,7 @@ contract Child { // Increments `current_value` by `new_value` #[public] + #['public] fn pub_inc_value(new_value: Field) -> Field { let old_value = storage.current_value.read(); storage.current_value.write(old_value + new_value); @@ -82,7 +89,8 @@ contract Child { // Increments `current_value` by `new_value`. Can only be called from this contract. #[public] - #[internal] + #['public] + #['internal] fn pub_inc_value_internal(new_value: Field) -> Field { let old_value = storage.current_value.read(); storage.current_value.write(old_value + new_value); @@ -92,6 +100,7 @@ contract Child { } #[public] + #['public] fn set_value_twice_with_nested_first() { let _result = Child::at(context.this_address()).pub_set_value(10).call(&mut context); storage.current_value.write(20); @@ -99,6 +108,7 @@ contract Child { } #[public] + #['public] fn set_value_twice_with_nested_last() { storage.current_value.write(20); context.emit_unencrypted_log(20); @@ -106,6 +116,7 @@ contract Child { } #[public] + #['public] fn set_value_with_two_nested_calls() { Child::at(context.this_address()).set_value_twice_with_nested_first().call(&mut context); Child::at(context.this_address()).set_value_twice_with_nested_last().call(&mut context); diff --git a/noir-projects/noir-contracts/contracts/claim_contract/src/main.nr b/noir-projects/noir-contracts/contracts/claim_contract/src/main.nr index 93d42b7e9ee..2a1fced7b88 100644 --- a/noir-projects/noir-contracts/contracts/claim_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/claim_contract/src/main.nr @@ -19,13 +19,15 @@ contract Claim { } #[public] - #[initializer] + #['public] + #['initializer] fn constructor(target_contract: AztecAddress, reward_token: AztecAddress) { storage.target_contract.initialize(target_contract); storage.reward_token.initialize(reward_token); } #[private] + #['private] fn claim(proof_note: ValueNote, recipient: AztecAddress) { // 1) Check that the note corresponds to the target contract and belongs to the sender let target_address = storage.target_contract.read_private(); diff --git a/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/main.nr b/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/main.nr index 3112cd5dbcf..e70c0ccb7ad 100644 --- a/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/main.nr @@ -32,7 +32,12 @@ contract ContractClassRegisterer { // docs:end:import_pop_capsule #[private] - fn register(artifact_hash: Field, private_functions_root: Field, public_bytecode_commitment: Field) { + #['private] + fn register( + artifact_hash: Field, + private_functions_root: Field, + public_bytecode_commitment: Field + ) { // TODO: Validate public_bytecode_commitment is the correct commitment of packed_public_bytecode // TODO: Validate packed_public_bytecode is legit public bytecode @@ -65,6 +70,7 @@ contract ContractClassRegisterer { } #[private] + #['private] fn broadcast_private_function( contract_class_id: ContractClassId, artifact_metadata_hash: Field, @@ -107,6 +113,7 @@ contract ContractClassRegisterer { } #[private] + #['private] fn broadcast_unconstrained_function( contract_class_id: ContractClassId, artifact_metadata_hash: Field, diff --git a/noir-projects/noir-contracts/contracts/contract_instance_deployer_contract/src/main.nr b/noir-projects/noir-contracts/contracts/contract_instance_deployer_contract/src/main.nr index 98b576ce140..bca64c13f1a 100644 --- a/noir-projects/noir-contracts/contracts/contract_instance_deployer_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/contract_instance_deployer_contract/src/main.nr @@ -27,6 +27,7 @@ contract ContractInstanceDeployer { } #[private] + #['private] fn deploy( salt: Field, contract_class_id: ContractClassId, diff --git a/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr b/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr index 42bc8c396f8..c6b803a15f7 100644 --- a/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr @@ -17,8 +17,9 @@ contract Counter { // docs:end:storage_struct // docs:start:constructor - #[initializer] + #['initializer] #[private] + #['private] // We can name our initializer anything we want as long as it's marked as aztec(initializer) fn initialize(headstart: u64, owner: AztecAddress, outgoing_viewer: AztecAddress) { let counters = storage.counters; @@ -28,6 +29,7 @@ contract Counter { // docs:start:increment #[private] + #['private] fn increment(owner: AztecAddress, outgoing_viewer: AztecAddress) { unsafe { dep::aztec::oracle::debug_log::debug_log_format("Incrementing counter for owner {0}", [owner.to_field()]); diff --git a/noir-projects/noir-contracts/contracts/crowdfunding_contract/src/main.nr b/noir-projects/noir-contracts/contracts/crowdfunding_contract/src/main.nr index a6c6b183041..76ecc48d30e 100644 --- a/noir-projects/noir-contracts/contracts/crowdfunding_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/crowdfunding_contract/src/main.nr @@ -47,7 +47,8 @@ contract Crowdfunding { // docs:start:init-header // docs:start:init-header-error #[public] - #[initializer] + #['public] + #['initializer] // this-will-error:init-header-error fn init(donation_token: AztecAddress, operator: AztecAddress, deadline: u64) { // docs:end:init-header @@ -60,6 +61,7 @@ contract Crowdfunding { // docs:start:donate #[private] + #['private] fn donate(amount: u64) { // 1) Check that the deadline has not passed --> we do that via the router contract to conceal which contract // is performing the check. @@ -87,6 +89,7 @@ contract Crowdfunding { // docs:start:operator-withdrawals // Withdraws balance to the operator. Requires that msg_sender() is the operator. #[private] + #['private] fn withdraw(amount: u64) { // 1) Check that msg_sender() is the operator let operator_address = storage.operator.read_private(); @@ -100,7 +103,8 @@ contract Crowdfunding { // docs:end:operator-withdrawals #[public] - #[internal] + #['public] + #['internal] fn _publish_donation_receipts(amount: u64, to: AztecAddress) { WithdrawalProcessed { amount, who: to }.emit(encode_event(&mut context)); } diff --git a/noir-projects/noir-contracts/contracts/delegated_on_contract/src/main.nr b/noir-projects/noir-contracts/contracts/delegated_on_contract/src/main.nr index 30a4ab02771..f9a5266da96 100644 --- a/noir-projects/noir-contracts/contracts/delegated_on_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/delegated_on_contract/src/main.nr @@ -18,6 +18,7 @@ contract DelegatedOn { } #[private] + #['private] fn private_set_value(new_value: Field, owner: AztecAddress) -> Field { let msg_sender_keys = get_public_keys(context.msg_sender()); let owner_keys = get_public_keys(owner); @@ -28,12 +29,14 @@ contract DelegatedOn { } #[public] + #['public] fn public_set_value(new_value: Field) -> Field { storage.current_value.write(new_value); new_value } #[private] + #['private] fn get_private_value(amount: Field, owner: AztecAddress) -> pub Field { let mut options = NoteGetterOptions::new(); options = options.select(ValueNote::properties().value, Comparator.EQ, amount).set_limit(1); diff --git a/noir-projects/noir-contracts/contracts/delegator_contract/src/main.nr b/noir-projects/noir-contracts/contracts/delegator_contract/src/main.nr index 80e095d0e43..e206c7e2ea3 100644 --- a/noir-projects/noir-contracts/contracts/delegator_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/delegator_contract/src/main.nr @@ -14,6 +14,7 @@ contract Delegator { a_map_with_private_values: Map, Context>, } #[private] + #['private] fn private_delegate_set_value( target_contract: AztecAddress, value: Field, @@ -23,14 +24,17 @@ contract Delegator { DelegatedOn::at(target_contract).private_set_value(value, owner).delegate_call(&mut context) } #[private] + #['private] fn enqueued_delegate_set_value(target_contract: AztecAddress, value: Field) { DelegatedOn::at(target_contract).public_set_value(value).delegate_enqueue(&mut context) } #[public] + #['public] fn public_delegate_set_value(target_contract: AztecAddress, value: Field) -> Field { DelegatedOn::at(target_contract).public_set_value(value).delegate_call(&mut context) } #[private] + #['private] fn get_private_value(amount: Field, owner: AztecAddress) -> Field { let mut options = NoteGetterOptions::new(); options = options.select(ValueNote::properties().value, Comparator.EQ, amount).set_limit(1); diff --git a/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr b/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr index c62ddf6663d..59cc4859e31 100644 --- a/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr @@ -29,7 +29,7 @@ contract DocsExample { // how to import methods from other files/folders within your workspace use crate::types::{card_note::CardNote, leader::Leader}; - #[storage_no_init] + #['storage_no_init] struct Storage { // Shows how to create a custom struct in PublicMutable // docs:start:storage-leader-declaration @@ -99,12 +99,14 @@ contract DocsExample { } #[public] + #['public] fn initialize_shared_immutable(points: u8) { let mut new_leader = Leader { account: context.msg_sender(), points }; storage.shared_immutable.initialize(new_leader); } #[private] + #['private] fn match_shared_immutable(account: AztecAddress, points: u8) { let expected = Leader { account, points }; let read = storage.shared_immutable.read_private(); @@ -114,6 +116,7 @@ contract DocsExample { } #[private] + #['private] fn get_shared_immutable_constrained_private_indirect() -> Leader { // This is a private function that calls another private function // and returns the response. @@ -125,6 +128,7 @@ contract DocsExample { } #[public] + #['public] fn get_shared_immutable_constrained_public_indirect() -> Leader { // This is a public function that calls another public function // and returns the response. @@ -136,19 +140,22 @@ contract DocsExample { } #[public] - #[view] + #['public] + #['view] fn get_shared_immutable_constrained_public() -> Leader { storage.shared_immutable.read_public() } #[public] + #['public] fn get_shared_immutable_constrained_public_multiple() -> [Leader; 5] { let a = storage.shared_immutable.read_public(); [a, a, a, a, a] } #[private] - #[view] + #['private] + #['view] fn get_shared_immutable_constrained_private() -> Leader { storage.shared_immutable.read_private() } @@ -158,6 +165,7 @@ contract DocsExample { } #[public] + #['public] fn initialize_public_immutable(points: u8) { // docs:start:initialize_public_immutable let mut new_leader = Leader { account: context.msg_sender(), points }; @@ -173,6 +181,7 @@ contract DocsExample { // docs:start:initialize-private-mutable #[private] + #['private] fn initialize_private_immutable(randomness: Field, points: u8) { let msg_sender_keys = get_public_keys(context.msg_sender()); @@ -189,6 +198,7 @@ contract DocsExample { // docs:end:initialize-private-mutable #[private] + #['private] // msg_sender() is 0 at deploy time. So created another function fn initialize_private(randomness: Field, points: u8) { let msg_sender_keys = get_public_keys(context.msg_sender()); @@ -206,6 +216,7 @@ contract DocsExample { } #[private] + #['private] fn insert_notes(amounts: [u8; 3]) { let msg_sender_keys = get_public_keys(context.msg_sender()); @@ -222,6 +233,7 @@ contract DocsExample { } } #[private] + #['private] fn insert_note(amount: u8, randomness: Field) { let msg_sender_keys = get_public_keys(context.msg_sender()); @@ -242,6 +254,7 @@ contract DocsExample { } // docs:end:state_vars-NoteGetterOptionsComparatorExampleNoir #[private] + #['private] fn update_legendary_card(randomness: Field, points: u8) { let msg_sender_keys = get_public_keys(context.msg_sender()); @@ -257,6 +270,7 @@ contract DocsExample { DocsExample::at(context.this_address()).update_leader(context.msg_sender(), points).enqueue(&mut context); } #[private] + #['private] fn increase_legendary_points() { // Ensure `points` > current value // Also serves as a e2e test that you can `get_note()` and then `replace()` @@ -281,16 +295,19 @@ contract DocsExample { DocsExample::at(context.this_address()).update_leader(context.msg_sender(), points).enqueue(&mut context); } #[private] - #[view] + #['private] + #['view] fn verify_private_authwit(inner_hash: Field) -> Field { 1 } #[public] + #['public] fn spend_public_authwit(inner_hash: Field) -> Field { 1 } #[public] - #[internal] + #['public] + #['internal] fn update_leader(account: AztecAddress, points: u8) { let new_leader = Leader { account, points }; storage.leader.write(new_leader); @@ -308,6 +325,7 @@ contract DocsExample { // docs:end:private_mutable_is_initialized // docs:start:get_note-private-immutable #[private] + #['private] fn get_imm_card() -> CardNote { storage.private_immutable.get_note() } @@ -323,6 +341,7 @@ contract DocsExample { use dep::aztec::context::inputs::PrivateContextInputs; // docs:start:simple_macro_example #[private] + #['private] fn simple_macro_example(a: Field, b: Field) -> Field { a + b } diff --git a/noir-projects/noir-contracts/contracts/easy_private_token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/easy_private_token_contract/src/main.nr index 8e118629ed8..ececc9162d6 100644 --- a/noir-projects/noir-contracts/contracts/easy_private_token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/easy_private_token_contract/src/main.nr @@ -17,7 +17,8 @@ contract EasyPrivateToken { * initialize the contract's initial state variables. */ #[private] - #[initializer] + #['private] + #['initializer] fn constructor(initial_supply: u64, owner: AztecAddress, outgoing_viewer: AztecAddress) { let balances = storage.balances; @@ -26,6 +27,7 @@ contract EasyPrivateToken { // Mints `amount` of tokens to `owner`. #[private] + #['private] fn mint(amount: u64, owner: AztecAddress, outgoing_viewer: AztecAddress) { let balances = storage.balances; @@ -34,12 +36,8 @@ contract EasyPrivateToken { // Transfers `amount` of tokens from `sender` to a `recipient`. #[private] - fn transfer( - amount: u64, - sender: AztecAddress, - recipient: AztecAddress, - outgoing_viewer: AztecAddress - ) { + #['private] + fn transfer(amount: u64, sender: AztecAddress, recipient: AztecAddress, outgoing_viewer: AztecAddress) { let balances = storage.balances; balances.at(sender).sub(amount, sender, outgoing_viewer); diff --git a/noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/main.nr b/noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/main.nr index 5d65675ab8a..17f79da58fa 100644 --- a/noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/main.nr @@ -21,7 +21,8 @@ contract EasyPrivateVoting { // docs:start:constructor #[public] - #[initializer] + #['public] + #['initializer] // annotation to mark function as a constructor fn constructor(admin: AztecAddress) { storage.admin.write(admin); @@ -32,6 +33,7 @@ contract EasyPrivateVoting { // docs:start:cast_vote #[private] + #['private] // annotation to mark function as private and expose private context fn cast_vote(candidate: Field) { let msg_sender_npk_m_hash = get_public_keys(context.msg_sender()).npk_m.hash(); @@ -45,7 +47,8 @@ contract EasyPrivateVoting { // docs:start:add_to_tally_public #[public] - #[internal] + #['public] + #['internal] fn add_to_tally_public(candidate: Field) { assert(storage.vote_ended.read() == false, "Vote has ended"); // assert that vote has not ended let new_tally = storage.tally.at(candidate).read() + 1; @@ -55,6 +58,7 @@ contract EasyPrivateVoting { // docs:start:end_vote #[public] + #['public] fn end_vote() { assert(storage.admin.read().eq(context.msg_sender()), "Only admin can end votes"); // assert that caller is admin storage.vote_ended.write(true); diff --git a/noir-projects/noir-contracts/contracts/ecdsa_k_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/ecdsa_k_account_contract/src/main.nr index 3e3b9c0405d..41507b112e9 100644 --- a/noir-projects/noir-contracts/contracts/ecdsa_k_account_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/ecdsa_k_account_contract/src/main.nr @@ -25,7 +25,8 @@ contract EcdsaKAccount { // Creates a new account out of an ECDSA public key to use for signature verification #[private] - #[initializer] + #['private] + #['initializer] fn constructor(signing_pub_key_x: [u8; 32], signing_pub_key_y: [u8; 32]) { let this = context.this_address(); let this_keys = get_public_keys(this); @@ -39,14 +40,16 @@ contract EcdsaKAccount { // Note: If you globally change the entrypoint signature don't forget to update account_entrypoint.ts #[private] + #['private] fn entrypoint(app_payload: AppPayload, fee_payload: FeePayload, cancellable: bool) { let actions = AccountActions::init(&mut context, is_valid_impl); actions.entrypoint(app_payload, fee_payload, cancellable); } #[private] - #[noinitcheck] - #[view] + #['private] + #['noinitcheck] + #['view] fn verify_private_authwit(inner_hash: Field) -> Field { let actions = AccountActions::init(&mut context, is_valid_impl); actions.verify_private_authwit(inner_hash) diff --git a/noir-projects/noir-contracts/contracts/ecdsa_r_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/ecdsa_r_account_contract/src/main.nr index be0c4d14ccb..f20fbcdd684 100644 --- a/noir-projects/noir-contracts/contracts/ecdsa_r_account_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/ecdsa_r_account_contract/src/main.nr @@ -24,7 +24,8 @@ contract EcdsaRAccount { // Creates a new account out of an ECDSA public key to use for signature verification #[private] - #[initializer] + #['private] + #['initializer] fn constructor(signing_pub_key_x: [u8; 32], signing_pub_key_y: [u8; 32]) { let this = context.this_address(); let this_keys = get_public_keys(this); @@ -38,14 +39,16 @@ contract EcdsaRAccount { // Note: If you globally change the entrypoint signature don't forget to update account_entrypoint.ts #[private] + #['private] fn entrypoint(app_payload: AppPayload, fee_payload: FeePayload, cancellable: bool) { let actions = AccountActions::init(&mut context, is_valid_impl); actions.entrypoint(app_payload, fee_payload, cancellable); } #[private] - #[noinitcheck] - #[view] + #['private] + #['noinitcheck] + #['view] fn verify_private_authwit(inner_hash: Field) -> Field { let actions = AccountActions::init(&mut context, is_valid_impl); actions.verify_private_authwit(inner_hash) diff --git a/noir-projects/noir-contracts/contracts/escrow_contract/src/main.nr b/noir-projects/noir-contracts/contracts/escrow_contract/src/main.nr index 785f6dbcfc1..cac065c15a2 100644 --- a/noir-projects/noir-contracts/contracts/escrow_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/escrow_contract/src/main.nr @@ -21,7 +21,8 @@ contract Escrow { // Creates a new instance #[private] - #[initializer] + #['private] + #['initializer] fn constructor(owner: AztecAddress) { let owner_keys = get_public_keys(owner); let msg_sender_keys = get_public_keys(context.msg_sender()); @@ -33,6 +34,7 @@ contract Escrow { // Withdraws balance. Requires that msg.sender is the owner. #[private] + #['private] fn withdraw(token: AztecAddress, amount: Field, recipient: AztecAddress) { let sender = context.msg_sender(); diff --git a/noir-projects/noir-contracts/contracts/fee_juice_contract/src/main.nr b/noir-projects/noir-contracts/contracts/fee_juice_contract/src/main.nr index 910cd957ca8..538aa16618c 100644 --- a/noir-projects/noir-contracts/contracts/fee_juice_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/fee_juice_contract/src/main.nr @@ -23,6 +23,7 @@ contract FeeJuice { // Not flagged as initializer to reduce cost of checking init nullifier in all functions. // This function should be called as entrypoint to initialize the contract by minting itself funds. #[private] + #['private] fn initialize(portal_address: EthAddress) { // Validate contract class parameters are correct let self = context.this_address(); @@ -40,12 +41,14 @@ contract FeeJuice { // the contract to a specific L1 portal address, since the Fee Juice address // is a hardcoded constant in the rollup circuits. #[public] + #['public] fn set_portal(portal_address: EthAddress) { assert(storage.portal_address.read_public().is_zero()); storage.portal_address.initialize(portal_address); } #[private] + #['private] fn claim(to: AztecAddress, amount: Field, secret: Field) { let content_hash = get_bridge_gas_msg_hash(to, amount); let portal_address = storage.portal_address.read_private(); @@ -61,14 +64,16 @@ contract FeeJuice { } #[public] - #[internal] + #['public] + #['internal] fn _increase_public_balance(to: AztecAddress, amount: Field) { let new_balance = storage.balances.at(to).read().add(U128::from_integer(amount)); storage.balances.at(to).write(new_balance); } #[public] - #[view] + #['public] + #['view] fn check_balance(fee_limit: Field) { let fee_limit = U128::from_integer(fee_limit); assert(storage.balances.at(context.msg_sender()).read() >= fee_limit, "Balance too low"); @@ -76,7 +81,8 @@ contract FeeJuice { // utility function for testing #[public] - #[view] + #['public] + #['view] fn balance_of_public(owner: AztecAddress) -> pub Field { storage.balances.at(owner).read().to_field() } diff --git a/noir-projects/noir-contracts/contracts/fpc_contract/src/main.nr b/noir-projects/noir-contracts/contracts/fpc_contract/src/main.nr index 33c966b0c45..abf6e375e22 100644 --- a/noir-projects/noir-contracts/contracts/fpc_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/fpc_contract/src/main.nr @@ -18,12 +18,14 @@ contract FPC { } #[public] - #[initializer] + #['public] + #['initializer] fn constructor(other_asset: AztecAddress) { storage.other_asset.initialize(other_asset); } #[private] + #['private] fn fee_entrypoint_private(amount: Field, asset: AztecAddress, secret_hash: Field, nonce: Field) { assert(asset == storage.other_asset.read_private()); Token::at(asset).unshield(context.msg_sender(), context.this_address(), amount, nonce).call(&mut context); @@ -40,6 +42,7 @@ contract FPC { } #[private] + #['private] fn fee_entrypoint_public(amount: Field, asset: AztecAddress, nonce: Field) { FPC::at(context.this_address()).prepare_fee(context.msg_sender(), amount, asset, nonce).enqueue(&mut context); context.set_as_fee_payer(); @@ -55,7 +58,8 @@ contract FPC { } #[public] - #[internal] + #['public] + #['internal] fn prepare_fee(from: AztecAddress, amount: Field, asset: AztecAddress, nonce: Field) { // docs:start:public_call Token::at(asset).transfer_public(from, context.this_address(), amount, nonce).call(&mut context); @@ -63,7 +67,8 @@ contract FPC { } #[public] - #[internal] + #['public] + #['internal] fn pay_refund(refund_address: AztecAddress, amount: Field, asset: AztecAddress) { // Just do public refunds for the present let refund = compute_rebate(context, amount); @@ -71,7 +76,8 @@ contract FPC { } #[public] - #[internal] + #['public] + #['internal] fn pay_refund_with_shielded_rebate(amount: Field, asset: AztecAddress, secret_hash: Field) { let refund = compute_rebate(context, amount); Token::at(asset).shield(context.this_address(), refund, secret_hash, 0).call(&mut context); diff --git a/noir-projects/noir-contracts/contracts/import_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/import_test_contract/src/main.nr index 6f5a19f3fda..62f3e4e9d9c 100644 --- a/noir-projects/noir-contracts/contracts/import_test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/import_test_contract/src/main.nr @@ -15,6 +15,7 @@ contract ImportTest { // See yarn-project/simulator/src/client/private_execution.ts // See yarn-project/end-to-end/src/e2e_nested_contract.test.ts #[private] + #['private] fn main_contract(target: AztecAddress) -> Field { Test::at(target).test_code_gen( 1, @@ -39,6 +40,7 @@ contract ImportTest { // Used for testing calling a function with no arguments // See yarn-project/end-to-end/src/e2e_nested_contract.test.ts #[private] + #['private] fn call_no_args(target: AztecAddress) -> AztecAddress { Test::at(target).get_this_address().call(&mut context) } @@ -47,6 +49,7 @@ contract ImportTest { // Used for testing calling a public function // See yarn-project/end-to-end/src/e2e_nested_contract.test.ts #[private] + #['private] fn call_public_fn(target: AztecAddress) { Test::at(target).emit_nullifier_public(1).enqueue(&mut context); } @@ -55,6 +58,7 @@ contract ImportTest { // Used for testing calling a public function from another public function // See yarn-project/end-to-end/src/e2e_nested_contract.test.ts #[public] + #['public] fn pub_call_public_fn(target: AztecAddress) { Test::at(target).emit_nullifier_public(1).call(&mut context); } diff --git a/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr b/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr index 971162f2d7e..4c31aa5ca69 100644 --- a/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr @@ -21,7 +21,8 @@ contract InclusionProofs { } #[public] - #[initializer] + #['public] + #['initializer] fn constructor(public_value: Field) { storage.public_value.write(public_value); } @@ -29,6 +30,7 @@ contract InclusionProofs { // docs:start:create_note // Creates a value note owned by `owner`. #[private] + #['private] fn create_note(owner: AztecAddress, value: Field) { let msg_sender_keys = get_public_keys(context.msg_sender()); let owner_keys = get_public_keys(owner); @@ -39,6 +41,7 @@ contract InclusionProofs { // docs:end:create_note #[private] + #['private] fn test_note_inclusion( owner: AztecAddress, use_block_number: bool, @@ -69,6 +72,7 @@ contract InclusionProofs { } #[private] + #['private] fn test_note_inclusion_fail_case( owner: AztecAddress, use_block_number: bool, @@ -89,6 +93,7 @@ contract InclusionProofs { // Proves that the note was not yet nullified at block `block_number`. #[private] + #['private] fn test_note_not_nullified( owner: AztecAddress, use_block_number: bool, @@ -118,6 +123,7 @@ contract InclusionProofs { } #[private] + #['private] fn test_note_validity( owner: AztecAddress, use_block_number: bool, @@ -146,6 +152,7 @@ contract InclusionProofs { // docs:start:nullify_note #[private] + #['private] fn nullify_note(owner: AztecAddress) { let private_values = storage.private_values.at(owner); let mut options = NoteGetterOptions::new(); @@ -159,6 +166,7 @@ contract InclusionProofs { // Note: I am not getting a nullifier of the note that was created in this contract in this function because it is // currently not possible to obtain a nullified note from PXE. #[private] + #['private] fn test_nullifier_inclusion( nullifier: Field, use_block_number: bool, @@ -175,17 +183,20 @@ contract InclusionProofs { } #[public] + #['public] fn push_nullifier_public(nullifier: Field) { context.push_nullifier(nullifier); } // Proves nullifier existed at latest block #[public] + #['public] fn test_nullifier_inclusion_from_public(nullifier: Field) { assert(context.nullifier_exists(nullifier, context.this_address())); } #[private] + #['private] fn test_storage_historical_read_unset_slot(block_number: u32 // The block at which we'll read the public storage value ) { let header = context.get_header_at(block_number); @@ -200,6 +211,7 @@ contract InclusionProofs { } #[private] + #['private] fn test_storage_historical_read( expected: Field, use_block_number: bool, @@ -218,6 +230,7 @@ contract InclusionProofs { // Proves that a contract was publicly deployed and/or initialized at block `block_number`. #[private] + #['private] fn test_contract_inclusion( contract_address: AztecAddress, block_number: u32, @@ -240,6 +253,7 @@ contract InclusionProofs { // Proves that a contract was NOT publicly deployed and/or initialized at block `block_number`. #[private] + #['private] fn test_contract_non_inclusion( contract_address: AztecAddress, block_number: u32, diff --git a/noir-projects/noir-contracts/contracts/lending_contract/src/main.nr b/noir-projects/noir-contracts/contracts/lending_contract/src/main.nr index 66d2af3a916..d92b2f704df 100644 --- a/noir-projects/noir-contracts/contracts/lending_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/lending_contract/src/main.nr @@ -37,11 +37,13 @@ contract Lending { // Constructs the contract. #[private] - #[initializer] + #['private] + #['initializer] fn constructor( ) {} #[public] + #['public] fn init( oracle: AztecAddress, loan_to_value: Field, @@ -69,6 +71,7 @@ contract Lending { // Create a position. #[public] + #['public] fn update_accumulator() -> Asset { let asset_loc = storage.assets.at(0); let mut asset: Asset = asset_loc.read(); @@ -94,6 +97,7 @@ contract Lending { } #[private] + #['private] fn deposit_private( from: AztecAddress, amount: Field, @@ -114,6 +118,7 @@ contract Lending { } #[public] + #['public] fn deposit_public(amount: Field, nonce: Field, on_behalf_of: Field, collateral_asset: AztecAddress) { let _ = Token::at(collateral_asset).transfer_public(context.msg_sender(), context.this_address(), amount, nonce).call(&mut context); let _ = Lending::at(context.this_address())._deposit( @@ -124,7 +129,8 @@ contract Lending { } #[public] - #[internal] + #['public] + #['internal] fn _deposit(owner: AztecAddress, amount: Field, collateral_asset: AztecAddress) { let _asset = Lending::at(context.this_address()).update_accumulator().call(&mut context); @@ -137,18 +143,21 @@ contract Lending { } #[private] + #['private] fn withdraw_private(secret: Field, to: AztecAddress, amount: Field) { let on_behalf_of = compute_identifier(secret, 0, context.msg_sender().to_field()); Lending::at(context.this_address())._withdraw(AztecAddress::from_field(on_behalf_of), to, amount).enqueue(&mut context); } #[public] + #['public] fn withdraw_public(to: AztecAddress, amount: Field) { let _ = Lending::at(context.this_address())._withdraw(context.msg_sender(), to, amount).call(&mut context); } #[public] - #[internal] + #['public] + #['internal] fn _withdraw(owner: AztecAddress, recipient: AztecAddress, amount: Field) { let asset = Lending::at(context.this_address()).update_accumulator().call(&mut context); let price = PriceFeed::at(asset.oracle).get_price(0).view(&mut context).price; @@ -185,18 +194,21 @@ contract Lending { } #[private] + #['private] fn borrow_private(secret: Field, to: AztecAddress, amount: Field) { let on_behalf_of = compute_identifier(secret, 0, context.msg_sender().to_field()); let _ = Lending::at(context.this_address())._borrow(AztecAddress::from_field(on_behalf_of), to, amount).enqueue(&mut context); } #[public] + #['public] fn borrow_public(to: AztecAddress, amount: Field) { let _ = Lending::at(context.this_address())._borrow(context.msg_sender(), to, amount).call(&mut context); } #[public] - #[internal] + #['public] + #['internal] fn _borrow(owner: AztecAddress, to: AztecAddress, amount: Field) { let asset = Lending::at(context.this_address()).update_accumulator().call(&mut context); let price = PriceFeed::at(asset.oracle).get_price(0).view(&mut context).price; @@ -229,6 +241,7 @@ contract Lending { } #[private] + #['private] fn repay_private( from: AztecAddress, amount: Field, @@ -245,13 +258,15 @@ contract Lending { } #[public] + #['public] fn repay_public(amount: Field, nonce: Field, owner: AztecAddress, stable_coin: AztecAddress) { let _ = Token::at(stable_coin).burn_public(context.msg_sender(), amount, nonce).call(&mut context); let _ = Lending::at(context.this_address())._repay(owner, amount, stable_coin).call(&mut context); } #[public] - #[internal] + #['public] + #['internal] fn _repay(owner: AztecAddress, amount: Field, stable_coin: AztecAddress) { let asset = Lending::at(context.this_address()).update_accumulator().call(&mut context); @@ -270,13 +285,15 @@ contract Lending { } #[public] - #[view] + #['public] + #['view] fn get_asset(asset_id: Field) -> Asset { storage.assets.at(asset_id).read() } #[public] - #[view] + #['public] + #['view] fn get_position(owner: AztecAddress) -> pub Position { let collateral = storage.collateral.at(owner).read(); let static_debt = storage.static_debt.at(owner).read(); @@ -289,7 +306,8 @@ contract Lending { } #[public] - #[view] + #['public] + #['view] fn get_assets() -> pub [AztecAddress; 2] { [storage.collateral_asset.read(), storage.stable_coin.read()] } diff --git a/noir-projects/noir-contracts/contracts/multi_call_entrypoint_contract/src/main.nr b/noir-projects/noir-contracts/contracts/multi_call_entrypoint_contract/src/main.nr index 2351f61ecc0..443158ecde6 100644 --- a/noir-projects/noir-contracts/contracts/multi_call_entrypoint_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/multi_call_entrypoint_contract/src/main.nr @@ -8,6 +8,7 @@ contract MultiCallEntrypoint { use dep::aztec::macros::functions::private; #[private] + #['private] fn entrypoint(app_payload: AppPayload) { app_payload.execute_calls(&mut context); } diff --git a/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr b/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr index 6ec923ba8c2..bc1302c1aaa 100644 --- a/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr @@ -52,7 +52,8 @@ contract NFT { } #[public] - #[initializer] + #['public] + #['initializer] fn constructor(admin: AztecAddress, name: str<31>, symbol: str<31>) { assert(!admin.is_zero(), "invalid admin"); storage.admin.write(admin); @@ -62,18 +63,21 @@ contract NFT { } #[public] + #['public] fn set_admin(new_admin: AztecAddress) { assert(storage.admin.read().eq(context.msg_sender()), "caller is not an admin"); storage.admin.write(new_admin); } #[public] + #['public] fn set_minter(minter: AztecAddress, approve: bool) { assert(storage.admin.read().eq(context.msg_sender()), "caller is not an admin"); storage.minters.at(minter).write(approve); } #[public] + #['public] fn mint(to: AztecAddress, token_id: Field) { assert(token_id != 0, "zero token ID not supported"); assert(storage.minters.at(context.msg_sender()).read(), "caller is not a minter"); @@ -85,42 +89,49 @@ contract NFT { } #[public] - #[view] + #['public] + #['view] fn public_get_name() -> pub FieldCompressedString { storage.name.read_public() } #[private] - #[view] + #['private] + #['view] fn private_get_name() -> pub FieldCompressedString { storage.name.read_private() } #[public] - #[view] + #['public] + #['view] fn public_get_symbol() -> pub FieldCompressedString { storage.symbol.read_public() } #[private] - #[view] + #['private] + #['view] fn private_get_symbol() -> pub FieldCompressedString { storage.symbol.read_private() } #[public] - #[view] + #['public] + #['view] fn get_admin() -> Field { storage.admin.read().to_field() } #[public] - #[view] + #['public] + #['view] fn is_minter(minter: AztecAddress) -> bool { storage.minters.at(minter).read() } #[public] + #['public] fn transfer_in_public(from: AztecAddress, to: AztecAddress, token_id: Field, nonce: Field) { if (!from.eq(context.msg_sender())) { assert_current_call_valid_authwit_public(&mut context, from); @@ -139,6 +150,7 @@ contract NFT { /// as an argument so that we can derive `transfer_preparer_storage_slot_commitment` off-chain and then pass it /// as an argument to the followup call to `finalize_transfer_to_private`. #[private] + #['private] fn prepare_transfer_to_private( from: AztecAddress, to: AztecAddress, @@ -176,7 +188,8 @@ contract NFT { } #[public] - #[internal] + #['public] + #['internal] fn _store_point_in_transient_storage(point: Point, slot: Field) { // We don't perform check for the overwritten value to be non-zero because the slots are siloed to `to` // and hence `to` can interfere only with his own execution. @@ -188,6 +201,7 @@ contract NFT { /// The `transfer_preparer_storage_slot_commitment` has to be computed off-chain the same way as was done /// in the preparation call. #[public] + #['public] fn finalize_transfer_to_private( token_id: Field, transfer_preparer_storage_slot_commitment: Field @@ -231,6 +245,7 @@ contract NFT { * @param inner_hash The inner hash of the authwit to cancel. */ #[private] + #['private] fn cancel_authwit(inner_hash: Field) { let on_behalf_of = context.msg_sender(); let nullifier = compute_authwit_nullifier(on_behalf_of, inner_hash); @@ -238,6 +253,7 @@ contract NFT { } #[private] + #['private] fn transfer_in_private(from: AztecAddress, to: AztecAddress, token_id: Field, nonce: Field) { if (!from.eq(context.msg_sender())) { assert_current_call_valid_authwit(&mut context, from); @@ -260,6 +276,7 @@ contract NFT { } #[private] + #['private] fn transfer_to_public(from: AztecAddress, to: AztecAddress, token_id: Field, nonce: Field) { if (!from.eq(context.msg_sender())) { assert_current_call_valid_authwit(&mut context, from); @@ -276,14 +293,16 @@ contract NFT { } #[public] - #[internal] + #['public] + #['internal] fn _finish_transfer_to_public(to: AztecAddress, token_id: Field) { storage.public_owners.at(token_id).write(to); } // Returns zero address when the token does not have a public owner. Reverts if the token does not exist. #[public] - #[view] + #['public] + #['view] fn owner_of(token_id: Field) -> AztecAddress { assert(storage.nft_exists.at(token_id).read(), "token does not exist"); storage.public_owners.at(token_id).read() diff --git a/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr b/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr index ac1526eca14..0598b2b0069 100644 --- a/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr @@ -8,12 +8,14 @@ contract Parent { use dep::aztec::{context::gas::GasOpts, macros::functions::{private, public}}; // Private function to call another private function in the target_contract using the provided selector #[private] + #['private] fn entry_point(target_contract: AztecAddress, target_selector: FunctionSelector) -> Field { // Call the target private function context.call_private_function(target_contract, target_selector, [0]).unpack_into() } // Public function to directly call another public function to the target_contract using the selector and value provided #[public] + #['public] fn pub_entry_point( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -28,6 +30,7 @@ contract Parent { } // Same as pub_entry_point, but calls the target contract twice, using the return value from the first invocation as the argument for the second. #[public] + #['public] fn pub_entry_point_twice( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -48,6 +51,7 @@ contract Parent { } // Private function to enqueue a call to the target_contract address using the selector and argument provided #[private] + #['private] fn enqueue_call_to_child( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -59,10 +63,8 @@ contract Parent { // - one through a nested call to enqueue_call_to_child with value 10, // - followed by one issued directly from this function with value 20. #[private] - fn enqueue_calls_to_child_with_nested_first( - target_contract: AztecAddress, - target_selector: FunctionSelector - ) { + #['private] + fn enqueue_calls_to_child_with_nested_first(target_contract: AztecAddress, target_selector: FunctionSelector) { let enqueue_call_to_child_selector = comptime { FunctionSelector::from_signature("enqueue_call_to_child((Field),(u32),Field)") }; @@ -77,10 +79,8 @@ contract Parent { // - one issued directly from this function with value 20, // - followed by one through a nested call to enqueue_call_to_child with value 10. #[private] - fn enqueue_calls_to_child_with_nested_last( - target_contract: AztecAddress, - target_selector: FunctionSelector - ) { + #['private] + fn enqueue_calls_to_child_with_nested_last(target_contract: AztecAddress, target_selector: FunctionSelector) { context.call_public_function(target_contract, target_selector, [20]); let enqueue_call_to_child_selector = comptime { FunctionSelector::from_signature("enqueue_call_to_child((Field),(u32),Field)") @@ -93,6 +93,7 @@ contract Parent { } // Private function to enqueue a call to the target_contract address using the selector and argument provided #[private] + #['private] fn enqueue_call_to_child_twice( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -105,6 +106,7 @@ contract Parent { } // Private function to enqueue a call to the pub_entry_point function of this same contract, passing the target arguments provided #[private] + #['private] fn enqueue_call_to_pub_entry_point( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -122,6 +124,7 @@ contract Parent { } // Private function to enqueue two calls to the pub_entry_point function of this same contract, passing the target arguments provided #[private] + #['private] fn enqueue_calls_to_pub_entry_point( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -143,6 +146,7 @@ contract Parent { ); } #[private] + #['private] fn private_static_call( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -152,6 +156,7 @@ contract Parent { context.static_call_private_function(target_contract, target_selector, args).unpack_into() } #[private] + #['private] fn private_call( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -162,6 +167,7 @@ contract Parent { } // Private function to set a static context and verify correct propagation for nested private calls #[private] + #['private] fn private_nested_static_call( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -180,6 +186,7 @@ contract Parent { } // Public function to directly call another public function to the target_contract using the selector and value provided #[public] + #['public] fn public_static_call( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -194,6 +201,7 @@ contract Parent { } // Public function to set a static context and verify correct propagation for nested public calls #[public] + #['public] fn public_nested_static_call( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -211,11 +219,8 @@ contract Parent { } // Private function to enqueue a static call to the pub_entry_point function of another contract, passing the target arguments provided #[private] - fn enqueue_static_nested_call_to_pub_function( - target_contract: AztecAddress, - target_selector: FunctionSelector, - args: [Field; 1] - ) { + #['private] + fn enqueue_static_nested_call_to_pub_function(target_contract: AztecAddress, target_selector: FunctionSelector, args: [Field; 1]) { // Call the target public function through the pub entrypoint statically let pub_entry_point_selector = FunctionSelector::from_signature("pub_entry_point((Field),(u32),Field)"); let this_address = context.this_address(); @@ -227,6 +232,7 @@ contract Parent { } // Private function to enqueue a static call to the pub_entry_point function of another contract, passing the target arguments provided #[private] + #['private] fn enqueue_static_call_to_pub_function( target_contract: AztecAddress, target_selector: FunctionSelector, diff --git a/noir-projects/noir-contracts/contracts/pending_note_hashes_contract/src/main.nr b/noir-projects/noir-contracts/contracts/pending_note_hashes_contract/src/main.nr index dc4ba79a8b7..d90babbc5b1 100644 --- a/noir-projects/noir-contracts/contracts/pending_note_hashes_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/pending_note_hashes_contract/src/main.nr @@ -27,11 +27,8 @@ contract PendingNoteHashes { // getting / reading that note all in the same contract function // Realistic way to describe this test is "Mint note A, then burn note A in the same transaction" #[private] - fn test_insert_then_get_then_nullify_flat( - amount: Field, - owner: AztecAddress, - outgoing_viewer: AztecAddress - ) -> Field { + #['private] + fn test_insert_then_get_then_nullify_flat(amount: Field, owner: AztecAddress, outgoing_viewer: AztecAddress) -> Field { let owner_balance = storage.balances.at(owner); let owner_keys = get_public_keys(owner); @@ -62,6 +59,7 @@ contract PendingNoteHashes { // Confirm cannot access note hashes inserted later in same function #[private] + #['private] fn test_bad_get_then_insert_flat(amount: Field, owner: AztecAddress) -> Field { let owner_balance = storage.balances.at(owner); @@ -82,10 +80,12 @@ contract PendingNoteHashes { // Dummy nested/inner function (to pass a function which does nothing) #[private] + #['private] fn dummy(amount: Field, owner: AztecAddress, outgoing_viewer: AztecAddress) {} // Nested/inner function to create and insert a note #[private] + #['private] fn insert_note(amount: Field, owner: AztecAddress, outgoing_viewer: AztecAddress) { let owner_balance = storage.balances.at(owner); @@ -109,6 +109,7 @@ contract PendingNoteHashes { // TESTING: inserts a static randomness value to test notes with // the same note hash are dealt with correctly #[private] + #['private] fn insert_note_static_randomness( amount: Field, owner: AztecAddress, @@ -136,6 +137,7 @@ contract PendingNoteHashes { // Nested/inner function to create and insert a note // then emit another note log for the same note #[private] + #['private] fn insert_note_extra_emit(amount: Field, owner: AztecAddress, outgoing_viewer: AztecAddress) { let mut owner_balance = storage.balances.at(owner); @@ -169,6 +171,7 @@ contract PendingNoteHashes { // Nested/inner function to get a note and confirm it matches the expected value #[private] + #['private] fn get_then_nullify_note(expected_value: Field, owner: AztecAddress) -> Field { let owner_balance = storage.balances.at(owner); @@ -182,6 +185,7 @@ contract PendingNoteHashes { // Nested/inner function to get a note and confirms that none is returned #[private] + #['private] fn get_note_zero_balance(owner: AztecAddress) { let owner_balance = storage.balances.at(owner); @@ -195,6 +199,7 @@ contract PendingNoteHashes { // and "read" / get of that pending note hash in another nested call // Realistic way to describe this test is "Mint note A, then burn note A in the same transaction" #[private] + #['private] fn test_insert_then_get_then_nullify_all_in_nested_calls( amount: Field, owner: AztecAddress, @@ -218,6 +223,7 @@ contract PendingNoteHashes { // same test as above, but insert 2, get 2, nullify 2 #[private] + #['private] fn test_insert2_then_get2_then_nullify2_all_in_nested_calls( amount: Field, owner: AztecAddress, @@ -258,6 +264,7 @@ contract PendingNoteHashes { // same test as above, but insert 2, get 1, nullify 1 #[private] + #['private] fn test_insert2_then_get2_then_nullify1_all_in_nested_calls( amount: Field, owner: AztecAddress, @@ -292,6 +299,7 @@ contract PendingNoteHashes { // one nullifier will be squashed with the pending note, one will become persistent. // ONLY WORKS IF THERE IS A PERSISTENT NOTE TO GET #[private] + #['private] fn test_insert1_then_get2_then_nullify2_all_in_nested_calls( amount: Field, owner: AztecAddress, @@ -329,7 +337,8 @@ contract PendingNoteHashes { // it being present at that stage in the kernel. // If we can somehow force the simulator to allow execution to succeed can ensure that this test fails in the kernel // #[private] - //fn test_bad_get_in_nested_call_then_insert( + // #['private] + // fn test_bad_get_in_nested_call_then_insert( // amount: Field, // owner: AztecAddress, // get_then_nullify_fn_selector: FunctionSelector, @@ -337,6 +346,7 @@ contract PendingNoteHashes { //} #[private] + #['private] fn test_recursively_create_notes( owner: AztecAddress, outgoing_viewer: AztecAddress, @@ -348,11 +358,8 @@ contract PendingNoteHashes { } #[private] - fn recursively_destroy_and_create_notes( - owner: AztecAddress, - outgoing_viewer: AztecAddress, - executions_left: u64 - ) { + #['private] + fn recursively_destroy_and_create_notes(owner: AztecAddress, outgoing_viewer: AztecAddress, executions_left: u64) { assert(executions_left > 0); destroy_max_notes(owner, storage); @@ -369,6 +376,7 @@ contract PendingNoteHashes { // by using an existing note's counter via its header. This is used to check that // the pxe rejects the note log later. #[private] + #['private] fn test_emit_bad_note_log(owner: AztecAddress, outgoing_viewer: AztecAddress) { let owner_balance = storage.balances.at(owner); diff --git a/noir-projects/noir-contracts/contracts/price_feed_contract/src/main.nr b/noir-projects/noir-contracts/contracts/price_feed_contract/src/main.nr index f360c87ad74..d3bbd678ffb 100644 --- a/noir-projects/noir-contracts/contracts/price_feed_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/price_feed_contract/src/main.nr @@ -16,13 +16,15 @@ contract PriceFeed { } #[public] + #['public] fn set_price(asset_id: Field, price: Field) { let asset = storage.assets.at(asset_id); asset.write(Asset { price: U128::from_integer(price) }); } #[public] - #[view] + #['public] + #['view] fn get_price(asset_id: Field) -> Asset { storage.assets.at(asset_id).read() } diff --git a/noir-projects/noir-contracts/contracts/private_fpc_contract/src/main.nr b/noir-projects/noir-contracts/contracts/private_fpc_contract/src/main.nr index 2c5663962c3..c25d634435d 100644 --- a/noir-projects/noir-contracts/contracts/private_fpc_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/private_fpc_contract/src/main.nr @@ -18,13 +18,15 @@ contract PrivateFPC { } #[public] - #[initializer] + #['public] + #['initializer] fn constructor(other_asset: AztecAddress, admin: AztecAddress) { let settings = Settings { other_asset, admin }; storage.settings.initialize(settings); } #[private] + #['private] fn fund_transaction_privately(amount: Field, asset: AztecAddress) { // TODO: Once SharedImmutable performs only 1 merkle proof here, we'll save ~4k gates let settings = storage.settings.read_private(); diff --git a/noir-projects/noir-contracts/contracts/router_contract/src/main.nr b/noir-projects/noir-contracts/contracts/router_contract/src/main.nr index 914b3829f75..430337b8768 100644 --- a/noir-projects/noir-contracts/contracts/router_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/router_contract/src/main.nr @@ -14,13 +14,15 @@ contract Router { /// Asserts that the current timestamp in the enqueued public call satisfies the `operation` with respect /// to the `value. #[private] + #['private] fn check_timestamp(operation: u8, value: u64) { Router::at(context.this_address())._check_timestamp(operation, value).enqueue_view(&mut context); } #[public] - #[internal] - #[view] + #['public] + #['internal] + #['view] fn _check_timestamp(operation: u8, value: u64) { let lhs_field = context.timestamp() as Field; let rhs_field = value as Field; @@ -31,13 +33,15 @@ contract Router { /// Asserts that the current block number in the enqueued public call satisfies the `operation` with respect /// to the `value. #[private] + #['private] fn check_block_number(operation: u8, value: Field) { Router::at(context.this_address())._check_block_number(operation, value).enqueue_view(&mut context); } #[public] - #[internal] - #[view] + #['public] + #['internal] + #['view] fn _check_block_number(operation: u8, value: Field) { assert(compare(context.block_number(), operation, value), "Block number mismatch."); } diff --git a/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/main.nr index 7a2aba19720..ec17610217a 100644 --- a/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/main.nr @@ -27,7 +27,8 @@ contract SchnorrAccount { // Constructs the contract #[private] - #[initializer] + #['private] + #['initializer] fn constructor(signing_pub_key_x: Field, signing_pub_key_y: Field) { let this = context.this_address(); let this_keys = get_public_keys(this); @@ -41,15 +42,17 @@ contract SchnorrAccount { // Note: If you globally change the entrypoint signature don't forget to update account_entrypoint.ts file #[private] - #[noinitcheck] + #['private] + #['noinitcheck] fn entrypoint(app_payload: AppPayload, fee_payload: FeePayload, cancellable: bool) { let actions = AccountActions::init(&mut context, is_valid_impl); actions.entrypoint(app_payload, fee_payload, cancellable); } #[private] - #[noinitcheck] - #[view] + #['private] + #['noinitcheck] + #['view] fn verify_private_authwit(inner_hash: Field) -> Field { let actions = AccountActions::init(&mut context, is_valid_impl); actions.verify_private_authwit(inner_hash) diff --git a/noir-projects/noir-contracts/contracts/schnorr_hardcoded_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/schnorr_hardcoded_account_contract/src/main.nr index f61dea42972..ec73f0cc4a9 100644 --- a/noir-projects/noir-contracts/contracts/schnorr_hardcoded_account_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/schnorr_hardcoded_account_contract/src/main.nr @@ -17,13 +17,15 @@ contract SchnorrHardcodedAccount { // Note: If you globally change the entrypoint signature don't forget to update account_entrypoint.ts #[private] + #['private] fn entrypoint(app_payload: AppPayload, fee_payload: FeePayload, cancellable: bool) { let actions = AccountActions::init(&mut context, is_valid_impl); actions.entrypoint(app_payload, fee_payload, cancellable); } #[private] - #[view] + #['private] + #['view] fn verify_private_authwit(inner_hash: Field) -> Field { let actions = AccountActions::init(&mut context, is_valid_impl); actions.verify_private_authwit(inner_hash) diff --git a/noir-projects/noir-contracts/contracts/schnorr_single_key_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/schnorr_single_key_account_contract/src/main.nr index b9f00815f63..8a570394374 100644 --- a/noir-projects/noir-contracts/contracts/schnorr_single_key_account_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/schnorr_single_key_account_contract/src/main.nr @@ -15,13 +15,15 @@ contract SchnorrSingleKeyAccount { // Note: If you globally change the entrypoint signature don't forget to update account_entrypoint.ts #[private] + #['private] fn entrypoint(app_payload: AppPayload, fee_payload: FeePayload, cancellable: bool) { let actions = AccountActions::init(&mut context, is_valid_impl); actions.entrypoint(app_payload, fee_payload, cancellable); } #[private] - #[view] + #['private] + #['view] fn verify_private_authwit(inner_hash: Field) -> Field { let actions = AccountActions::init(&mut context, is_valid_impl); actions.verify_private_authwit(inner_hash) diff --git a/noir-projects/noir-contracts/contracts/spam_contract/src/main.nr b/noir-projects/noir-contracts/contracts/spam_contract/src/main.nr index e6206501fee..bb6c71a032b 100644 --- a/noir-projects/noir-contracts/contracts/spam_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/spam_contract/src/main.nr @@ -29,6 +29,7 @@ contract Spam { } #[private] + #['private] fn spam(nullifier_seed: Field, nullifier_count: u32, call_public: bool) { let caller = context.msg_sender(); let caller_keys = get_public_keys(caller); @@ -61,7 +62,8 @@ contract Spam { } #[public] - #[internal] + #['public] + #['internal] fn public_spam(start: u32, end: u32) { let one = U128::from_integer(1); for i in start..end { diff --git a/noir-projects/noir-contracts/contracts/stateful_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/stateful_test_contract/src/main.nr index fd229a997d6..d8f5eb2b164 100644 --- a/noir-projects/noir-contracts/contracts/stateful_test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/stateful_test_contract/src/main.nr @@ -17,13 +17,15 @@ contract StatefulTest { } #[private] - #[initializer] + #['private] + #['initializer] fn constructor(owner: AztecAddress, outgoing_viewer: AztecAddress, value: Field) { StatefulTest::at(context.this_address()).create_note_no_init_check(owner, outgoing_viewer, value).call(&mut context); } #[private] - #[initializer] + #['private] + #['initializer] fn wrong_constructor() { let selector = FunctionSelector::from_signature("not_exists(Field)"); let _res = context.call_public_function(context.this_address(), selector, [42]); @@ -32,12 +34,14 @@ contract StatefulTest { // Having _ignored_arg here as it makes the params the same as for the private constructor which makes // contract_class_registration tests way less cluttered. This is a test contract. Don't judge me. #[public] - #[initializer] + #['public] + #['initializer] fn public_constructor(owner: AztecAddress, _ignored_arg: AztecAddress, value: Field) { StatefulTest::at(context.this_address()).increment_public_value_no_init_check(owner, value).call(&mut context); } #[private] + #['private] fn create_note(owner: AztecAddress, outgoing_viewer: AztecAddress, value: Field) { if (value != 0) { let loc = storage.notes.at(owner); @@ -46,7 +50,8 @@ contract StatefulTest { } #[private] - #[noinitcheck] + #['private] + #['noinitcheck] fn create_note_no_init_check(owner: AztecAddress, outgoing_viewer: AztecAddress, value: Field) { if (value != 0) { let loc = storage.notes.at(owner); @@ -55,6 +60,7 @@ contract StatefulTest { } #[private] + #['private] fn destroy_and_create(recipient: AztecAddress, amount: Field) { assert_is_initialized_private(&mut context); let sender = context.msg_sender(); @@ -67,7 +73,8 @@ contract StatefulTest { } #[private] - #[noinitcheck] + #['private] + #['noinitcheck] fn destroy_and_create_no_init_check(recipient: AztecAddress, amount: Field) { let sender = context.msg_sender(); @@ -79,13 +86,15 @@ contract StatefulTest { } #[public] + #['public] fn increment_public_value(owner: AztecAddress, value: Field) { let loc = storage.public_values.at(owner); loc.write(loc.read() + value); } #[public] - #[noinitcheck] + #['public] + #['noinitcheck] fn increment_public_value_no_init_check(owner: AztecAddress, value: Field) { let loc = storage.public_values.at(owner); loc.write(loc.read() + value); @@ -101,8 +110,9 @@ contract StatefulTest { } #[public] - #[noinitcheck] - #[view] + #['public] + #['noinitcheck] + #['view] fn get_public_value(owner: AztecAddress) -> pub Field { storage.public_values.at(owner).read() } diff --git a/noir-projects/noir-contracts/contracts/static_child_contract/src/main.nr b/noir-projects/noir-contracts/contracts/static_child_contract/src/main.nr index 0ec1b4bce79..e7500389948 100644 --- a/noir-projects/noir-contracts/contracts/static_child_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/static_child_contract/src/main.nr @@ -21,7 +21,8 @@ contract StaticChild { // Returns base_value + chain_id + version + block_number + timestamp statically #[public] - #[view] + #['public] + #['view] fn pub_get_value(base_value: Field) -> Field { let return_value = base_value + context.chain_id() @@ -34,6 +35,7 @@ contract StaticChild { // Sets `current_value` to `new_value` #[public] + #['public] fn pub_set_value(new_value: Field) -> Field { storage.current_value.write(new_value); context.emit_unencrypted_log(new_value); @@ -42,7 +44,8 @@ contract StaticChild { // View function that attempts to modify state. Should always fail regardless how it's called. #[private] - #[view] + #['private] + #['view] fn private_illegal_set_value(new_value: Field, owner: AztecAddress) -> Field { let msg_sender_keys = get_public_keys(context.msg_sender()); let owner_keys = get_public_keys(owner); @@ -55,6 +58,7 @@ contract StaticChild { // Modify a note #[private] + #['private] fn private_set_value( new_value: Field, owner: AztecAddress, @@ -77,7 +81,8 @@ contract StaticChild { // Retrieve note value statically #[private] - #[view] + #['private] + #['view] fn private_get_value(amount: Field, owner: AztecAddress) -> Field { let owner_npk_m_hash = get_public_keys(owner).npk_m.hash(); let mut options = NoteGetterOptions::new(); @@ -92,6 +97,7 @@ contract StaticChild { // Increments `current_value` by `new_value` #[public] + #['public] fn pub_inc_value(new_value: Field) -> Field { let old_value = storage.current_value.read(); storage.current_value.write(old_value + new_value); @@ -101,7 +107,8 @@ contract StaticChild { // View function that attempts to modify state. Should always fail regardless how it's called. #[public] - #[view] + #['public] + #['view] fn pub_illegal_inc_value(new_value: Field) -> Field { let old_value = storage.current_value.read(); storage.current_value.write(old_value + new_value); diff --git a/noir-projects/noir-contracts/contracts/static_parent_contract/src/main.nr b/noir-projects/noir-contracts/contracts/static_parent_contract/src/main.nr index d363d073fb0..7854861cbd9 100644 --- a/noir-projects/noir-contracts/contracts/static_parent_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/static_parent_contract/src/main.nr @@ -10,6 +10,7 @@ contract StaticParent { // Public function to directly call another public function to the target_contract using the selector and value provided #[public] + #['public] fn public_call( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -25,6 +26,7 @@ contract StaticParent { // Private function to directly call another private function to the target_contract using the selector and args provided #[private] + #['private] fn private_call( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -35,6 +37,7 @@ contract StaticParent { // Just like function above but with 3 args. #[private] + #['private] fn private_call_3_args( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -45,6 +48,7 @@ contract StaticParent { // Private function to enqueue a call to a public function of another contract, passing the target arguments provided #[private] + #['private] fn enqueue_call( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -54,6 +58,7 @@ contract StaticParent { } #[private] + #['private] fn private_static_call( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -64,6 +69,7 @@ contract StaticParent { // Private function to statically call another private function to the target_contract using the selector and values provided #[private] + #['private] fn private_static_call_3_args( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -74,6 +80,7 @@ contract StaticParent { // Same as above but using a specific function from the interface #[private] + #['private] fn private_get_value_from_child( target_contract: AztecAddress, value: Field, @@ -84,6 +91,7 @@ contract StaticParent { // Private function to set a static context and verify correct propagation for nested private calls #[private] + #['private] fn private_nested_static_call( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -94,6 +102,7 @@ contract StaticParent { // Just like function above but with 3 args. #[private] + #['private] fn private_nested_static_call_3_args( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -104,6 +113,7 @@ contract StaticParent { // Public function to statically call another public function to the target_contract using the selector and value provided #[public] + #['public] fn public_static_call( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -119,12 +129,14 @@ contract StaticParent { // Same as above but using a specific function from the interface #[public] + #['public] fn public_get_value_from_child(target_contract: AztecAddress, value: Field) -> Field { StaticChild::at(target_contract).pub_get_value(value).view(&mut context) } // Public function to set a static context and verify correct propagation for nested public calls #[public] + #['public] fn public_nested_static_call( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -136,6 +148,7 @@ contract StaticParent { // Private function to enqueue a static call to a public function of another contract, passing the target arguments provided #[private] + #['private] fn enqueue_static_call_to_pub_function( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -146,17 +159,15 @@ contract StaticParent { // Same as above but using a specific function from the interface #[private] + #['private] fn enqueue_public_get_value_from_child(target_contract: AztecAddress, value: Field) { StaticChild::at(target_contract).pub_get_value(value).enqueue_view(&mut context); } // Private function to set a static context and verify correct propagation of nested enqueuing of public calls #[private] - fn enqueue_static_nested_call_to_pub_function( - target_contract: AztecAddress, - target_selector: FunctionSelector, - args: [Field; 1] - ) { + #['private] + fn enqueue_static_nested_call_to_pub_function(target_contract: AztecAddress, target_selector: FunctionSelector, args: [Field; 1]) { // Call the target public function through the pub entrypoint statically StaticParent::at(context.this_address()).public_call(target_contract, target_selector, args[0]).enqueue_view(&mut context) } diff --git a/noir-projects/noir-contracts/contracts/test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test_contract/src/main.nr index 111f8fad6db..0759e75860c 100644 --- a/noir-projects/noir-contracts/contracts/test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test_contract/src/main.nr @@ -53,11 +53,13 @@ contract Test { } #[private] + #['private] fn get_ovsk_app(ovpk_m_hash: Field) -> Field { context.request_ovsk_app(ovpk_m_hash) } #[private] + #['private] fn get_master_incoming_viewing_public_key(address: AztecAddress) -> [Field; 2] { let ivpk_m = get_public_keys(address).ivpk_m; @@ -66,11 +68,13 @@ contract Test { // Get the address of this contract (taken from the input context) #[private] + #['private] fn get_this_address() -> AztecAddress { context.this_address() } #[private] + #['private] fn set_tx_max_block_number(max_block_number: u32, enqueue_public_call: bool) { context.set_tx_max_block_number(max_block_number); @@ -80,10 +84,12 @@ contract Test { } #[public] - #[internal] + #['public] + #['internal] fn dummy_public_call() {} #[private] + #['private] fn call_create_note( value: Field, owner: AztecAddress, @@ -109,6 +115,7 @@ contract Test { } #[private] + #['private] fn call_get_notes(storage_slot: Field, active_or_nullified: bool) -> Field { assert( storage_slot != storage.example_constant.get_storage_slot(), "this storage slot is reserved for example_constant" @@ -125,6 +132,7 @@ contract Test { } #[private] + #['private] fn call_get_notes_many(storage_slot: Field, active_or_nullified: bool) -> [Field; 2] { assert( storage_slot != storage.example_constant.get_storage_slot(), "this storage slot is reserved for example_constant" @@ -171,6 +179,7 @@ contract Test { } #[private] + #['private] fn call_destroy_note(storage_slot: Field) { assert( storage_slot != storage.example_constant.get_storage_slot(), "this storage slot is reserved for example_constant" @@ -186,6 +195,7 @@ contract Test { } #[private] + #['private] fn test_code_gen( a_field: Field, a_bool: bool, @@ -213,6 +223,7 @@ contract Test { } #[private] + #['private] fn test_setting_teardown() { context.set_public_teardown_function( context.this_address(), @@ -224,12 +235,14 @@ contract Test { } #[private] + #['private] fn test_setting_fee_payer() { context.set_as_fee_payer(); } // Purely exists for testing #[public] + #['public] fn create_l2_to_l1_message_public(amount: Field, secret_hash: Field, portal_address: EthAddress) { // Create a commitment to the amount let note = DummyNote::new(amount, secret_hash); @@ -239,12 +252,14 @@ contract Test { } #[public] + #['public] fn create_l2_to_l1_message_arbitrary_recipient_public(content: Field, recipient: EthAddress) { // Public oracle call to emit new commitment. context.message_portal(recipient, content); } #[private] + #['private] fn create_l2_to_l1_message_arbitrary_recipient_private(content: Field, recipient: EthAddress) { // Public oracle call to emit new commitment. context.message_portal(recipient, content); @@ -252,18 +267,21 @@ contract Test { // Purely exists for testing #[public] + #['public] fn emit_nullifier_public(nullifier: Field) { context.push_nullifier(nullifier); } // Forcefully emits a nullifier (for testing purposes) #[private] + #['private] fn emit_nullifier(nullifier: Field) { context.push_nullifier(nullifier); } // For testing non-note encrypted logs #[private] + #['private] fn emit_array_as_encrypted_log( fields: [Field; 5], owner: AztecAddress, @@ -307,6 +325,7 @@ contract Test { } #[private] + #['private] fn emit_encrypted_logs_nested(value: Field, owner: AztecAddress, outgoing_viewer: AztecAddress) { let mut storage_slot = storage.example_constant.get_storage_slot() + 1; Test::at(context.this_address()).call_create_note(value, owner, outgoing_viewer, storage_slot).call(&mut context); @@ -323,6 +342,7 @@ contract Test { // docs:start:is-time-equal #[public] + #['public] fn is_time_equal(time: u64) -> u64 { assert(context.timestamp() == time); time @@ -330,6 +350,7 @@ contract Test { // docs:end:is-time-equal #[public] + #['public] fn emit_unencrypted(value: Field) { // docs:start:emit_unencrypted context.emit_unencrypted_log(/*message=*/ value); @@ -339,6 +360,7 @@ contract Test { } #[public] + #['public] fn consume_mint_public_message( to: AztecAddress, amount: Field, @@ -352,6 +374,7 @@ contract Test { } #[private] + #['private] fn consume_mint_private_message( secret_hash_for_redeeming_minted_notes: Field, amount: Field, @@ -368,6 +391,7 @@ contract Test { } #[public] + #['public] fn consume_message_from_arbitrary_sender_public( content: Field, secret: Field, @@ -379,24 +403,32 @@ contract Test { } #[private] - fn consume_message_from_arbitrary_sender_private(content: Field, secret: Field, sender: EthAddress) { + #['private] + fn consume_message_from_arbitrary_sender_private( + content: Field, + secret: Field, + sender: EthAddress + ) { // Consume message and emit nullifier context.consume_l1_to_l2_message(content, secret, sender); } #[private] + #['private] fn set_constant(value: Field) { let mut note = TestNote::new(value); storage.example_constant.initialize(&mut note).discard(); } #[private] + #['private] fn assert_private_global_vars(chain_id: Field, version: Field) { assert(context.chain_id() == chain_id, "Invalid chain id"); assert(context.version() == version, "Invalid version"); } #[public] + #['public] fn assert_public_global_vars( chain_id: Field, version: Field, @@ -414,22 +446,26 @@ contract Test { } #[private] + #['private] fn assert_header_private(header_hash: Field) { assert(context.historical_header.hash() == header_hash, "Invalid header hash"); } // TODO(4840): add AVM opcodes for getting header (members) //#[public] + //#['public] //fn assert_header_public(header_hash: Field) { // assert(context.historical_header.hash() == header_hash, "Invalid header hash"); //} #[private] + #['private] fn deploy_contract(target: AztecAddress) { aztec_deploy_contract(&mut context, target); } #[private] + #['private] // Adapted from TokenContract#redeem_shield but without an initcheck so it can be run in simulator/src/client/private_execution.test.ts fn consume_note_from_secret(secret: Field) { let notes_set = storage.example_set; @@ -446,6 +482,7 @@ contract Test { } #[private] + #['private] fn test_nullifier_key_freshness(address: AztecAddress, public_nullifying_key: Point) { assert_eq(get_public_keys(address).npk_m.inner, public_nullifying_key); } diff --git a/noir-projects/noir-contracts/contracts/test_log_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test_log_contract/src/main.nr index 4c685a0a93c..8a6f0731877 100644 --- a/noir-projects/noir-contracts/contracts/test_log_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test_log_contract/src/main.nr @@ -36,6 +36,7 @@ contract TestLog { global EXAMPLE_EVENT_0_CIPHERTEXT_BYTES_LEN = 144; #[private] + #['private] fn emit_encrypted_events(other: AztecAddress, randomness: [Field; 2], preimages: [Field; 4]) { let event0 = ExampleEvent0 { value0: preimages[0], value1: preimages[1] }; @@ -80,6 +81,7 @@ contract TestLog { } #[public] + #['public] fn emit_unencrypted_events(preimages: [Field; 4]) { let event0 = ExampleEvent0 { value0: preimages[0], value1: preimages[1] }; diff --git a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/main.nr index 7640690f749..3f8afba77d6 100644 --- a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/main.nr @@ -40,31 +40,36 @@ contract TokenBlacklist { // docs:start:constructor #[public] - #[initializer] + #['public] + #['initializer] fn constructor(admin: AztecAddress) { let admin_roles = UserFlags { is_admin: true, is_minter: false, is_blacklisted: false }; storage.roles.at(admin).schedule_value_change(admin_roles); } #[public] - #[view] + #['public] + #['view] fn total_supply() -> pub Field { storage.total_supply.read().to_field() } #[public] - #[view] + #['public] + #['view] fn balance_of_public(owner: AztecAddress) -> pub Field { storage.public_balances.at(owner).read().to_field() } #[public] - #[view] + #['public] + #['view] fn get_roles(user: AztecAddress) -> UserFlags { storage.roles.at(user).get_current_value_in_public() } #[public] + #['public] fn update_roles(user: AztecAddress, roles: UserFlags) { let caller_roles = storage.roles.at(context.msg_sender()).get_current_value_in_public(); assert(caller_roles.is_admin, "caller is not admin"); @@ -73,6 +78,7 @@ contract TokenBlacklist { } #[public] + #['public] fn mint_public(to: AztecAddress, amount: Field) { let to_roles = storage.roles.at(to).get_current_value_in_public(); assert(!to_roles.is_blacklisted, "Blacklisted: Recipient"); @@ -89,6 +95,7 @@ contract TokenBlacklist { } #[public] + #['public] fn mint_private(amount: Field, secret_hash: Field) { let caller_roles = storage.roles.at(context.msg_sender()).get_current_value_in_public(); assert(caller_roles.is_minter, "caller is not minter"); @@ -102,6 +109,7 @@ contract TokenBlacklist { } #[public] + #['public] fn shield(from: AztecAddress, amount: Field, secret_hash: Field, nonce: Field) { let from_roles = storage.roles.at(from).get_current_value_in_public(); assert(!from_roles.is_blacklisted, "Blacklisted: Sender"); @@ -124,6 +132,7 @@ contract TokenBlacklist { } #[public] + #['public] fn transfer_public(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) { let from_roles = storage.roles.at(from).get_current_value_in_public(); assert(!from_roles.is_blacklisted, "Blacklisted: Sender"); @@ -145,6 +154,7 @@ contract TokenBlacklist { } #[public] + #['public] fn burn_public(from: AztecAddress, amount: Field, nonce: Field) { let from_roles = storage.roles.at(from).get_current_value_in_public(); assert(!from_roles.is_blacklisted, "Blacklisted: Sender"); @@ -164,6 +174,7 @@ contract TokenBlacklist { } #[private] + #['private] fn redeem_shield(to: AztecAddress, amount: Field, secret: Field) { let to_roles = storage.roles.at(to).get_current_value_in_private(); assert(!to_roles.is_blacklisted, "Blacklisted: Recipient"); @@ -189,6 +200,7 @@ contract TokenBlacklist { } #[private] + #['private] fn unshield(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) { let from_roles = storage.roles.at(from).get_current_value_in_private(); assert(!from_roles.is_blacklisted, "Blacklisted: Sender"); @@ -209,6 +221,7 @@ contract TokenBlacklist { // docs:start:transfer_private #[private] + #['private] fn transfer(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) { let from_roles = storage.roles.at(from).get_current_value_in_private(); assert(!from_roles.is_blacklisted, "Blacklisted: Sender"); @@ -232,6 +245,7 @@ contract TokenBlacklist { } #[private] + #['private] fn burn(from: AztecAddress, amount: Field, nonce: Field) { let from_roles = storage.roles.at(from).get_current_value_in_private(); assert(!from_roles.is_blacklisted, "Blacklisted: Sender"); @@ -250,14 +264,16 @@ contract TokenBlacklist { /// Internal /// #[public] - #[internal] + #['public] + #['internal] fn _increase_public_balance(to: AztecAddress, amount: Field) { let new_balance = storage.public_balances.at(to).read().add(U128::from_integer(amount)); storage.public_balances.at(to).write(new_balance); } #[public] - #[internal] + #['public] + #['internal] fn _reduce_total_supply(amount: Field) { // Only to be called from burn. let new_supply = storage.total_supply.read().sub(U128::from_integer(amount)); diff --git a/noir-projects/noir-contracts/contracts/token_bridge_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_bridge_contract/src/main.nr index ba0560efc9b..ee6be4f8ea7 100644 --- a/noir-projects/noir-contracts/contracts/token_bridge_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_bridge_contract/src/main.nr @@ -28,7 +28,8 @@ contract TokenBridge { // Constructs the contract. #[public] - #[initializer] + #['public] + #['initializer] fn constructor(token: AztecAddress, portal_address: EthAddress) { storage.token.write(token); storage.portal_address.initialize(portal_address); @@ -36,11 +37,13 @@ contract TokenBridge { // docs:end:token_bridge_storage_and_constructor #[private] + #['private] fn get_portal_address() -> EthAddress { storage.portal_address.read_private() } #[public] + #['public] fn get_portal_address_public() -> EthAddress { storage.portal_address.read_public() } @@ -48,6 +51,7 @@ contract TokenBridge { // docs:start:claim_public // Consumes a L1->L2 message and calls the token contract to mint the appropriate amount publicly #[public] + #['public] fn claim_public(to: AztecAddress, amount: Field, secret: Field, message_leaf_index: Field) { let content_hash = get_mint_public_content_hash(to, amount); @@ -68,6 +72,7 @@ contract TokenBridge { // Burns the appropriate amount of tokens and creates a L2 to L1 withdraw message publicly // Requires `msg.sender` to give approval to the bridge to burn tokens on their behalf using witness signatures #[public] + #['public] fn exit_to_l1_public( recipient: EthAddress, // ethereum address to withdraw to amount: Field, @@ -87,6 +92,7 @@ contract TokenBridge { // User needs to call token.redeem_shield() to get the private assets // TODO(#8416): Consider creating a truly private claim flow. #[private] + #['private] fn claim_private( secret_hash_for_redeeming_minted_notes: Field, // secret hash used to redeem minted notes at a later time. This enables anyone to call this function and mint tokens to a user on their behalf amount: Field, @@ -112,6 +118,7 @@ contract TokenBridge { // Burns the appropriate amount of tokens and creates a L2 to L1 withdraw message privately // Requires `msg.sender` (caller of the method) to give approval to the bridge to burn tokens on their behalf using witness signatures #[private] + #['private] fn exit_to_l1_private( token: AztecAddress, recipient: EthAddress, // ethereum address to withdraw to @@ -134,7 +141,8 @@ contract TokenBridge { /// docs:end:exit_to_l1_private // docs:start:get_token #[public] - #[view] + #['public] + #['view] fn get_token() -> AztecAddress { storage.token.read() } @@ -145,7 +153,8 @@ contract TokenBridge { // Also, note that user hashes their secret in private and only sends the hash in public // meaning only user can `redeem_shield` at a later time with their secret. #[public] - #[internal] + #['public] + #['internal] fn _call_mint_on_token(amount: Field, secret_hash: Field) { Token::at(storage.token.read()).mint_private(amount, secret_hash).call(&mut context); } @@ -153,7 +162,8 @@ contract TokenBridge { // docs:start:assert_token_is_same #[public] - #[internal] + #['public] + #['internal] fn _assert_token_is_same(token: AztecAddress) { assert(storage.token.read().eq(token), "Token address is not the same as seen in storage"); } diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr index fe056215b44..8097676dfe9 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr @@ -83,7 +83,8 @@ contract Token { // docs:start:constructor #[public] - #[initializer] + #['public] + #['initializer] fn constructor(admin: AztecAddress, name: str<31>, symbol: str<31>, decimals: u8) { assert(!admin.is_zero(), "invalid admin"); storage.admin.write(admin); @@ -97,6 +98,7 @@ contract Token { // docs:end:constructor // docs:start:set_admin #[public] + #['public] fn set_admin(new_admin: AztecAddress) { assert(storage.admin.read().eq(context.msg_sender()), "caller is not admin"); // docs:start:write_admin @@ -105,35 +107,41 @@ contract Token { } // docs:end:set_admin #[public] - #[view] + #['public] + #['view] fn public_get_name() -> FieldCompressedString { storage.name.read_public() } #[private] - #[view] + #['private] + #['view] fn private_get_name() -> FieldCompressedString { storage.name.read_private() } #[public] - #[view] + #['public] + #['view] fn public_get_symbol() -> pub FieldCompressedString { storage.symbol.read_public() } #[private] - #[view] + #['private] + #['view] fn private_get_symbol() -> pub FieldCompressedString { storage.symbol.read_private() } #[public] - #[view] + #['public] + #['view] fn public_get_decimals() -> pub u8 { // docs:start:read_decimals_public storage.decimals.read_public() // docs:end:read_decimals_public } #[private] - #[view] + #['private] + #['view] fn private_get_decimals() -> pub u8 { // docs:start:read_decimals_private storage.decimals.read_private() @@ -141,34 +149,39 @@ contract Token { } // docs:start:admin #[public] - #[view] + #['public] + #['view] fn get_admin() -> Field { storage.admin.read().to_field() } // docs:end:admin // docs:start:is_minter #[public] - #[view] + #['public] + #['view] fn is_minter(minter: AztecAddress) -> bool { storage.minters.at(minter).read() } // docs:end:is_minter // docs:start:total_supply #[public] - #[view] + #['public] + #['view] fn total_supply() -> Field { storage.total_supply.read().to_integer() } // docs:end:total_supply // docs:start:balance_of_public #[public] - #[view] + #['public] + #['view] fn balance_of_public(owner: AztecAddress) -> Field { storage.public_balances.at(owner).read().to_integer() } // docs:end:balance_of_public // docs:start:set_minter #[public] + #['public] fn set_minter(minter: AztecAddress, approve: bool) { // docs:start:read_admin assert(storage.admin.read().eq(context.msg_sender()), "caller is not admin"); @@ -180,6 +193,7 @@ contract Token { // docs:end:set_minter // docs:start:mint_public #[public] + #['public] fn mint_public(to: AztecAddress, amount: Field) { // docs:start:read_minter assert(storage.minters.at(context.msg_sender()).read(), "caller is not minter"); @@ -193,6 +207,7 @@ contract Token { // docs:end:mint_public // docs:start:mint_private #[public] + #['public] fn mint_private(amount: Field, secret_hash: Field) { assert(storage.minters.at(context.msg_sender()).read(), "caller is not minter"); let pending_shields = storage.pending_shields; @@ -206,6 +221,7 @@ contract Token { // docs:end:mint_private // TODO: Nuke this - test functions do not belong to token contract! #[private] + #['private] fn privately_mint_private_note(amount: Field) { let caller = context.msg_sender(); let caller_keys = get_public_keys(caller); @@ -213,7 +229,8 @@ contract Token { Token::at(context.this_address()).assert_minter_and_mint(context.msg_sender(), amount).enqueue(&mut context); } #[public] - #[internal] + #['public] + #['internal] fn assert_minter_and_mint(minter: AztecAddress, amount: Field) { assert(storage.minters.at(minter).read(), "caller is not minter"); let supply = storage.total_supply.read() + U128::from_integer(amount); @@ -221,6 +238,7 @@ contract Token { } // docs:start:shield #[public] + #['public] fn shield(from: AztecAddress, amount: Field, secret_hash: Field, nonce: Field) { if (!from.eq(context.msg_sender())) { // The redeem is only spendable once, so we need to ensure that you cannot insert multiple shields from the same message. @@ -238,6 +256,7 @@ contract Token { // docs:end:shield // docs:start:transfer_public #[public] + #['public] fn transfer_public(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) { if (!from.eq(context.msg_sender())) { assert_current_call_valid_authwit_public(&mut context, from); @@ -253,6 +272,7 @@ contract Token { // docs:end:transfer_public // docs:start:burn_public #[public] + #['public] fn burn_public(from: AztecAddress, amount: Field, nonce: Field) { // docs:start:assert_current_call_valid_authwit_public if (!from.eq(context.msg_sender())) { @@ -270,6 +290,7 @@ contract Token { // docs:end:burn_public // docs:start:redeem_shield #[private] + #['private] fn redeem_shield(to: AztecAddress, amount: Field, secret: Field) { let secret_hash = compute_secret_hash(secret); // Pop 1 note (set_limit(1)) which has an amount stored in a field with index 0 (select(0, amount)) and @@ -293,6 +314,7 @@ contract Token { // docs:end:redeem_shield // docs:start:unshield #[private] + #['private] fn unshield(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) { if (!from.eq(context.msg_sender())) { assert_current_call_valid_authwit(&mut context, from); @@ -307,6 +329,7 @@ contract Token { // docs:end:unshield // docs:start:transfer #[private] + #['private] fn transfer(to: AztecAddress, amount: Field) { let from = context.msg_sender(); @@ -375,8 +398,9 @@ contract Token { } // TODO(#7728): even though the amount should be a U128, we can't have that type in a contract interface due to // serialization issues. - #[internal] + #['internal] #[private] + #['private] fn _recurse_subtract_balance(account: AztecAddress, amount: Field) -> U128 { subtract_balance( &mut context, @@ -392,6 +416,7 @@ contract Token { */ // docs:start:cancel_authwit #[private] + #['private] fn cancel_authwit(inner_hash: Field) { let on_behalf_of = context.msg_sender(); let nullifier = compute_authwit_nullifier(on_behalf_of, inner_hash); @@ -400,6 +425,7 @@ contract Token { // docs:end:cancel_authwit // docs:start:transfer_from #[private] + #['private] fn transfer_from(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) { // docs:start:assert_current_call_valid_authwit if (!from.eq(context.msg_sender())) { @@ -423,6 +449,7 @@ contract Token { // docs:end:transfer_from // docs:start:burn #[private] + #['private] fn burn(from: AztecAddress, amount: Field, nonce: Field) { if (!from.eq(context.msg_sender())) { assert_current_call_valid_authwit(&mut context, from); @@ -455,6 +482,7 @@ contract Token { /// the public `complete_refund(...)` function. // docs:start:setup_refund #[private] + #['private] fn setup_refund( fee_payer: AztecAddress, // Address of the entity which will receive the fee note. user: AztecAddress, // A user for which we are setting up the fee refund. @@ -540,7 +568,8 @@ contract Token { // to serialization issues. // docs:start:complete_refund #[public] - #[internal] + #['public] + #['internal] fn complete_refund(fee_payer_point: Point, user_point: Point, funded_amount: Field) { // TODO(#7728): Remove the next line let funded_amount = U128::from_integer(funded_amount); @@ -573,7 +602,8 @@ contract Token { /// Internal /// // docs:start:increase_public_balance #[public] - #[internal] + #['public] + #['internal] fn _increase_public_balance(to: AztecAddress, amount: Field) { let new_balance = storage.public_balances.at(to).read().add(U128::from_integer(amount)); storage.public_balances.at(to).write(new_balance); @@ -581,7 +611,8 @@ contract Token { // docs:end:increase_public_balance // docs:start:reduce_total_supply #[public] - #[internal] + #['public] + #['internal] fn _reduce_total_supply(amount: Field) { // Only to be called from burn. let new_supply = storage.total_supply.read().sub(U128::from_integer(amount)); diff --git a/noir-projects/noir-contracts/contracts/uniswap_contract/src/main.nr b/noir-projects/noir-contracts/contracts/uniswap_contract/src/main.nr index 0b1aeb7cd55..4eedebb2a91 100644 --- a/noir-projects/noir-contracts/contracts/uniswap_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/uniswap_contract/src/main.nr @@ -23,7 +23,8 @@ contract Uniswap { } #[public] - #[initializer] + #['public] + #['initializer] fn constructor(portal_address: EthAddress) { storage.portal_address.initialize(portal_address); } @@ -31,6 +32,7 @@ contract Uniswap { // docs:start:swap_public #[public] + #['public] fn swap_public( sender: AztecAddress, input_asset_bridge: AztecAddress, @@ -92,6 +94,7 @@ contract Uniswap { // docs:start:swap_private #[private] + #['private] fn swap_private( input_asset: AztecAddress, // since private, we pass here and later assert that this is as expected by input_bridge input_asset_bridge: AztecAddress, @@ -155,8 +158,13 @@ contract Uniswap { // Note that private can't read public return values so created an internal public that handles everything // this method is used for both private and public swaps. #[public] - #[internal] - fn _approve_bridge_and_exit_input_asset_to_L1(token: AztecAddress, token_bridge: AztecAddress, amount: Field) { + #['public] + #['internal] + fn _approve_bridge_and_exit_input_asset_to_L1( + token: AztecAddress, + token_bridge: AztecAddress, + amount: Field + ) { // Since we will authorize and instantly spend the funds, all in public, we can use the same nonce // every interaction. In practice, the authwit should be squashed, so this is also cheap! let nonce = 0xdeadbeef; @@ -182,8 +190,9 @@ contract Uniswap { // docs:start:assert_token_is_same #[public] - #[internal] - #[view] + #['public] + #['internal] + #['view] fn _assert_token_is_same(token: AztecAddress, token_bridge: AztecAddress) { assert( token.eq(TokenBridge::at(token_bridge).get_token().view(&mut context)), "input_asset address is not the same as seen in the bridge contract" diff --git a/noir-projects/noir-protocol-circuits/crates/bug-collecting-crate/test-error-msg-format.nr b/noir-projects/noir-protocol-circuits/crates/bug-collecting-crate/test-error-msg-format.nr index aa9c274ffa5..96dd9cdcd2b 100644 --- a/noir-projects/noir-protocol-circuits/crates/bug-collecting-crate/test-error-msg-format.nr +++ b/noir-projects/noir-protocol-circuits/crates/bug-collecting-crate/test-error-msg-format.nr @@ -3,5 +3,5 @@ Should be able to use special characters (,.-) in the string in a macro. error: Malformed function attribute ┌─ /mnt/user-data/leila/aztec-packages/yarn-project/noir-protocol-circuits/src/crates/private-kernel-lib/src/private_kernel_init.nr:436:5 │ -436 │ #[test(should_fail_with="tx_request.function_data must match call_stack_item.function_data")] - │ --------------------------------------------------------------------------------------------- test(should_fail_with="tx_request.function_data must match call_stack_item.function_data") is not a valid attribute +436 │ #[test(should_fail_with = "tx_request.function_data must match call_stack_item.function_data")] + │ --------------------------------------------------------------------------------------------- test(should_fail_with = "tx_request.function_data must match call_stack_item.function_data") is not a valid attribute diff --git a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_inner.nr b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_inner.nr index 5ad472b7a24..af0f502bee5 100644 --- a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_inner.nr +++ b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_inner.nr @@ -156,7 +156,7 @@ mod tests { } } - #[test(should_fail_with="Invalid vk index")] + #[test(should_fail_with = "Invalid vk index")] fn invalid_previous_kernel() { let mut builder = PrivateKernelInnerInputsBuilder::new(); builder.previous_kernel = builder.previous_kernel.in_vk_tree(BASE_ROLLUP_INDEX); diff --git a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_reset.nr b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_reset.nr index fb72fbdf47f..cc339828154 100644 --- a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_reset.nr +++ b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_reset.nr @@ -610,7 +610,7 @@ mod tests { assert(output_nullifiers[2].value() != nullifiers[2].value()); } - #[test(should_fail_with="Invalid vk index")] + #[test(should_fail_with = "Invalid vk index")] fn invalid_previous_kernel() { let mut builder = PrivateKernelResetInputsBuilder::new(); builder.previous_kernel = builder.previous_kernel.in_vk_tree(BASE_ROLLUP_INDEX); diff --git a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_tail.nr b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_tail.nr index 859856dd1e8..3639fade081 100644 --- a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_tail.nr +++ b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_tail.nr @@ -311,7 +311,7 @@ mod tests { } } - #[test(should_fail_with="Invalid vk index")] + #[test(should_fail_with = "Invalid vk index")] fn invalid_previous_kernel() { let mut builder = PrivateKernelTailInputsBuilder::new(); builder.previous_kernel = builder.previous_kernel.in_vk_tree(BASE_ROLLUP_INDEX); diff --git a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_tail_to_public.nr b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_tail_to_public.nr index 4ee6fbe4d03..e6bf91ede9a 100644 --- a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_tail_to_public.nr +++ b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_tail_to_public.nr @@ -390,7 +390,7 @@ mod tests { let _res = builder.execute(); } } - // #[test(should_fail_with="Invalid vk index")] + // #[test(should_fail_with = "Invalid vk index")] // fn invalid_previous_kernel() { // let mut builder = PrivateKernelTailToPublicInputsBuilder::new(); // builder.previous_kernel = builder.previous_kernel.in_vk_tree(BASE_ROLLUP_INDEX); diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_merge.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_merge.nr index 08ed1cc6039..a127809b853 100644 --- a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_merge.nr +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_merge.nr @@ -247,7 +247,7 @@ mod tests { } } - #[test(should_fail_with="Invalid vk index")] + #[test(should_fail_with = "Invalid vk index")] fn invalid_previous_kernel() { let mut builder = PublicKernelMergeCircuitPrivateInputsBuilder::new(); builder.previous_kernel = builder.previous_kernel.in_vk_tree(BASE_ROLLUP_INDEX); diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_tail.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_tail.nr index 747a907091a..ce2e6b62a9a 100644 --- a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_tail.nr +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_tail.nr @@ -635,7 +635,7 @@ mod tests { let _res = builder.execute(); } - #[test(should_fail_with="Invalid vk index")] + #[test(should_fail_with = "Invalid vk index")] fn invalid_previous_kernel() { let mut builder = PublicKernelTailCircuitPrivateInputsBuilder::new(); builder.previous_kernel = builder.previous_kernel.in_vk_tree(BASE_ROLLUP_INDEX); diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/base_rollup_inputs.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/base_rollup_inputs.nr index 650ce8580d9..680dd98310c 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/base_rollup_inputs.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/base_rollup_inputs.nr @@ -1462,7 +1462,7 @@ mod tests { let _res = builder.execute(); } - #[test(should_fail_with="Invalid vk index")] + #[test(should_fail_with = "Invalid vk index")] fn invalid_previous_kernel() { let mut builder = BaseRollupInputsBuilder::new(); builder.kernel_data = builder.kernel_data.in_vk_tree(BASE_ROLLUP_INDEX); diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/block_merge/block_merge_rollup_inputs.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/block_merge/block_merge_rollup_inputs.nr index 1bc280da955..0fda86c3e3c 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/block_merge/block_merge_rollup_inputs.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/block_merge/block_merge_rollup_inputs.nr @@ -162,7 +162,7 @@ mod tests { let _outputs = inputs.block_merge_rollup_circuit(); } - #[test(should_fail_with="Invalid vk index")] + #[test(should_fail_with = "Invalid vk index")] fn invalid_previous_circuit() { let mut inputs = default_block_merge_rollup_inputs(); let vk_tree = dep::types::tests::fixtures::vk_tree::get_vk_merkle_tree(); diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/merge/merge_rollup_inputs.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/merge/merge_rollup_inputs.nr index d28ea891239..b9a284353a7 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/merge/merge_rollup_inputs.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/merge/merge_rollup_inputs.nr @@ -200,7 +200,7 @@ mod tests { let _outputs = inputs.merge_rollup_circuit(); } - #[test(should_fail_with="Invalid vk index")] + #[test(should_fail_with = "Invalid vk index")] fn invalid_previous_circuit() { let mut inputs = default_merge_rollup_inputs(); let vk_tree = dep::types::tests::fixtures::vk_tree::get_vk_merkle_tree(); diff --git a/noir/noir-repo/Cargo.lock b/noir/noir-repo/Cargo.lock index b74cca92786..a24908fd2e6 100644 --- a/noir/noir-repo/Cargo.lock +++ b/noir/noir-repo/Cargo.lock @@ -2362,6 +2362,18 @@ dependencies = [ "redox_syscall 0.4.1", ] +[[package]] +name = "light-poseidon" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c9a85a9752c549ceb7578064b4ed891179d20acd85f27318573b64d2d7ee7ee" +dependencies = [ + "ark-bn254", + "ark-ff", + "num-bigint", + "thiserror", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -2527,9 +2539,9 @@ dependencies = [ "rand 0.8.5", "rayon", "serde", - "tempfile", "thiserror", "tracing", + "walkdir", ] [[package]] @@ -2537,6 +2549,7 @@ name = "nargo_cli" version = "0.35.0" dependencies = [ "acvm", + "ark-bn254", "assert_cmd", "assert_fs", "async-lsp", @@ -2552,6 +2565,7 @@ dependencies = [ "fm", "iai", "iter-extended", + "light-poseidon", "nargo", "nargo_fmt", "nargo_toml", @@ -3005,11 +3019,10 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -3026,19 +3039,18 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", diff --git a/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs b/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs index a3d78e7b3b5..d5902c4d85c 100644 --- a/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs @@ -42,6 +42,10 @@ impl FunctionInput { pub fn witness(witness: Witness, num_bits: u32) -> FunctionInput { FunctionInput { input: ConstantOrWitnessEnum::Witness(witness), num_bits } } + + pub fn is_constant(&self) -> bool { + matches!(self.input, ConstantOrWitnessEnum::Constant(_)) + } } #[derive(Clone, PartialEq, Eq, Debug, Error)] diff --git a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/poseidon2.rs b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/poseidon2.rs index 64823e37029..3aa735388ca 100644 --- a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/poseidon2.rs +++ b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/poseidon2.rs @@ -544,13 +544,23 @@ impl<'a> Poseidon2<'a> { } /// Performs a poseidon hash with a sponge construction equivalent to the one in poseidon2.nr -pub fn poseidon_hash(inputs: &[FieldElement]) -> Result { +/// +/// The `is_variable_length` parameter is there to so we can produce an equivalent hash with +/// the Barretenberg implementation which distinguishes between variable and fixed length inputs. +/// Set it to true if the input length matches the static size expected by the Noir function. +pub fn poseidon_hash( + inputs: &[FieldElement], + is_variable_length: bool, +) -> Result { let two_pow_64 = 18446744073709551616_u128.into(); let iv = FieldElement::from(inputs.len()) * two_pow_64; let mut sponge = Poseidon2Sponge::new(iv, 3); for input in inputs.iter() { sponge.absorb(*input)?; } + if is_variable_length { + sponge.absorb(FieldElement::from(1u32))?; + } sponge.squeeze() } @@ -640,7 +650,7 @@ mod test { FieldElement::from(3u128), FieldElement::from(4u128), ]; - let result = super::poseidon_hash(&fields).expect("should hash successfully"); + let result = super::poseidon_hash(&fields, false).expect("should hash successfully"); assert_eq!( result, field_from_hex("130bf204a32cac1f0ace56c78b731aa3809f06df2731ebcf6b3464a15788b1b9"), diff --git a/noir/noir-repo/compiler/fm/src/lib.rs b/noir/noir-repo/compiler/fm/src/lib.rs index 37da29fc982..fad2634bab4 100644 --- a/noir/noir-repo/compiler/fm/src/lib.rs +++ b/noir/noir-repo/compiler/fm/src/lib.rs @@ -100,6 +100,11 @@ impl FileManager { self.id_to_path.get(&file_id).map(|path| path.as_path()) } + pub fn has_file(&self, file_name: &Path) -> bool { + let file_name = self.root.join(file_name); + self.name_to_id(file_name).is_some() + } + // TODO: This should accept a &Path instead of a PathBuf pub fn name_to_id(&self, file_name: PathBuf) -> Option { self.file_map.get_file_id(&PathString::from_path(file_name)) diff --git a/noir/noir-repo/compiler/noirc_driver/src/lib.rs b/noir/noir-repo/compiler/noirc_driver/src/lib.rs index c1e90ebe992..64cb6083c7c 100644 --- a/noir/noir-repo/compiler/noirc_driver/src/lib.rs +++ b/noir/noir-repo/compiler/noirc_driver/src/lib.rs @@ -451,7 +451,7 @@ fn compile_contract_inner( .secondary .iter() .filter_map(|attr| { - if let SecondaryAttribute::Custom(attribute) = attr { + if let SecondaryAttribute::Tag(attribute) = attr { Some(&attribute.contents) } else { None diff --git a/noir/noir-repo/compiler/noirc_errors/src/reporter.rs b/noir/noir-repo/compiler/noirc_errors/src/reporter.rs index 76e308969b4..f029b4e6de8 100644 --- a/noir/noir-repo/compiler/noirc_errors/src/reporter.rs +++ b/noir/noir-repo/compiler/noirc_errors/src/reporter.rs @@ -10,7 +10,7 @@ use codespan_reporting::term::termcolor::{ColorChoice, StandardStream}; pub struct CustomDiagnostic { pub message: String, pub secondaries: Vec, - notes: Vec, + pub notes: Vec, pub kind: DiagnosticKind, pub deprecated: bool, pub unnecessary: bool, diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs index b0f283eeaeb..1069416b7b8 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs @@ -8,7 +8,9 @@ use crate::ssa::ir::dfg::CallStack; use crate::ssa::ir::types::Type as SsaType; use crate::ssa::ir::{instruction::Endian, types::NumericType}; use acvm::acir::circuit::brillig::{BrilligFunctionId, BrilligInputs, BrilligOutputs}; -use acvm::acir::circuit::opcodes::{AcirFunctionId, BlockId, BlockType, MemOp}; +use acvm::acir::circuit::opcodes::{ + AcirFunctionId, BlockId, BlockType, ConstantOrWitnessEnum, MemOp, +}; use acvm::acir::circuit::{AssertionPayload, ExpressionOrMemory, ExpressionWidth, Opcode}; use acvm::brillig_vm::{MemoryValue, VMStatus, VM}; use acvm::{ @@ -1459,22 +1461,7 @@ impl AcirContext { } _ => (vec![], vec![]), }; - // Allow constant inputs for most blackbox - // EmbeddedCurveAdd needs to be fixed first in bb - // Poseidon2Permutation requires witness input - let allow_constant_inputs = matches!( - name, - BlackBoxFunc::MultiScalarMul - | BlackBoxFunc::Keccakf1600 - | BlackBoxFunc::Blake2s - | BlackBoxFunc::Blake3 - | BlackBoxFunc::AND - | BlackBoxFunc::XOR - | BlackBoxFunc::AES128Encrypt - | BlackBoxFunc::EmbeddedCurveAdd - ); - // Convert `AcirVar` to `FunctionInput` - let inputs = self.prepare_inputs_for_black_box_func_call(inputs, allow_constant_inputs)?; + let inputs = self.prepare_inputs_for_black_box_func(inputs, name)?; // Call Black box with `FunctionInput` let mut results = vecmap(&constant_outputs, |c| self.add_constant(*c)); let outputs = self.acir_ir.call_black_box( @@ -1496,6 +1483,34 @@ impl AcirContext { Ok(results) } + fn prepare_inputs_for_black_box_func( + &mut self, + inputs: Vec, + name: BlackBoxFunc, + ) -> Result>>, RuntimeError> { + // Allow constant inputs for most blackbox, but: + // - EmbeddedCurveAdd requires all-or-nothing constant inputs + // - Poseidon2Permutation requires witness input + let allow_constant_inputs = matches!( + name, + BlackBoxFunc::MultiScalarMul + | BlackBoxFunc::Keccakf1600 + | BlackBoxFunc::Blake2s + | BlackBoxFunc::Blake3 + | BlackBoxFunc::AND + | BlackBoxFunc::XOR + | BlackBoxFunc::AES128Encrypt + | BlackBoxFunc::EmbeddedCurveAdd + ); + // Convert `AcirVar` to `FunctionInput` + let mut inputs = + self.prepare_inputs_for_black_box_func_call(inputs, allow_constant_inputs)?; + if name == BlackBoxFunc::EmbeddedCurveAdd { + inputs = self.all_or_nothing_for_ec_add(inputs)?; + } + Ok(inputs) + } + /// Black box function calls expect their inputs to be in a specific data structure (FunctionInput). /// /// This function will convert `AcirVar` into `FunctionInput` for a blackbox function call. @@ -1536,6 +1551,41 @@ impl AcirContext { Ok(witnesses) } + /// EcAdd has 6 inputs representing the two points to add + /// Each point must be either all constant, or all witnesses + fn all_or_nothing_for_ec_add( + &mut self, + inputs: Vec>>, + ) -> Result>>, RuntimeError> { + let mut has_constant = false; + let mut has_witness = false; + let mut result = inputs.clone(); + for (i, input) in inputs.iter().enumerate() { + if input[0].is_constant() { + has_constant = true; + } else { + has_witness = true; + } + if i % 3 == 2 { + if has_constant && has_witness { + // Convert the constants to witness if mixed constant and witness, + for j in i - 2..i + 1 { + if let ConstantOrWitnessEnum::Constant(constant) = inputs[j][0].input() { + let constant = self.add_constant(constant); + let witness_var = self.get_or_create_witness_var(constant)?; + let witness = self.var_to_witness(witness_var)?; + result[j] = + vec![FunctionInput::witness(witness, inputs[j][0].num_bits())]; + } + } + } + has_constant = false; + has_witness = false; + } + } + Ok(result) + } + /// Returns a vector of `AcirVar`s constrained to be the decomposition of the given input /// over given radix. /// diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs index 6aa9acaca22..d8dba499a43 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs @@ -118,15 +118,15 @@ impl Intrinsic { // These apply a constraint that the input must fit into a specified number of limbs. Intrinsic::ToBits(_) | Intrinsic::ToRadix(_) => true, + // These imply a check that the slice is non-empty and should fail otherwise. + Intrinsic::SlicePopBack | Intrinsic::SlicePopFront | Intrinsic::SliceRemove => true, + Intrinsic::ArrayLen | Intrinsic::ArrayAsStrUnchecked | Intrinsic::AsSlice | Intrinsic::SlicePushBack | Intrinsic::SlicePushFront - | Intrinsic::SlicePopBack - | Intrinsic::SlicePopFront | Intrinsic::SliceInsert - | Intrinsic::SliceRemove | Intrinsic::StrAsBytes | Intrinsic::FromField | Intrinsic::AsField diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs index dbfa12b7f5e..6d16117d3d8 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs @@ -166,6 +166,13 @@ pub(super) fn simplify_call( } } Intrinsic::SlicePopBack => { + let length = dfg.get_numeric_constant(arguments[0]); + if length.map_or(true, |length| length.is_zero()) { + // If the length is zero then we're trying to pop the last element from an empty slice. + // Defer the error to acir_gen. + return SimplifyResult::None; + } + let slice = dfg.get_array_constant(arguments[1]); if let Some((_, typ)) = slice { simplify_slice_pop_back(typ, arguments, dfg, block, call_stack.clone()) @@ -174,6 +181,13 @@ pub(super) fn simplify_call( } } Intrinsic::SlicePopFront => { + let length = dfg.get_numeric_constant(arguments[0]); + if length.map_or(true, |length| length.is_zero()) { + // If the length is zero then we're trying to pop the first element from an empty slice. + // Defer the error to acir_gen. + return SimplifyResult::None; + } + let slice = dfg.get_array_constant(arguments[1]); if let Some((mut slice, typ)) = slice { let element_count = typ.element_size(); @@ -225,6 +239,13 @@ pub(super) fn simplify_call( } } Intrinsic::SliceRemove => { + let length = dfg.get_numeric_constant(arguments[0]); + if length.map_or(true, |length| length.is_zero()) { + // If the length is zero then we're trying to remove an element from an empty slice. + // Defer the error to acir_gen. + return SimplifyResult::None; + } + let slice = dfg.get_array_constant(arguments[1]); let index = dfg.get_numeric_constant(arguments[2]); if let (Some((mut slice, typ)), Some(index)) = (slice, index) { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs index ea422fdff09..3b86ded4a87 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs @@ -843,4 +843,57 @@ mod test { let instructions = main.dfg[main.entry_block()].instructions(); assert_eq!(instructions.len(), 10); } + + // This test currently fails. It being fixed will address the issue https://github.com/noir-lang/noir/issues/5756 + #[test] + #[should_panic] + fn constant_array_deduplication() { + // fn main f0 { + // b0(v0: u64): + // v5 = call keccakf1600([v0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0]) + // v6 = call keccakf1600([v0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0, u64 0]) + // } + // + // Here we're checking a situation where two identical arrays are being initialized twice and being assigned separate `ValueId`s. + // This would result in otherwise identical instructions not being deduplicated. + let main_id = Id::test_new(0); + + // Compiling main + let mut builder = FunctionBuilder::new("main".into(), main_id); + let v0 = builder.add_parameter(Type::unsigned(64)); + let zero = builder.numeric_constant(0u128, Type::unsigned(64)); + let typ = Type::Array(Arc::new(vec![Type::unsigned(64)]), 25); + + let array_contents = vec![ + v0, zero, zero, zero, zero, zero, zero, zero, zero, zero, zero, zero, zero, zero, zero, + zero, zero, zero, zero, zero, zero, zero, zero, zero, zero, + ]; + let array1 = builder.array_constant(array_contents.clone().into(), typ.clone()); + let array2 = builder.array_constant(array_contents.into(), typ.clone()); + + assert_eq!(array1, array2, "arrays were assigned different value ids"); + + let keccakf1600 = + builder.import_intrinsic("keccakf1600").expect("keccakf1600 intrinsic should exist"); + let _v10 = builder.insert_call(keccakf1600, vec![array1], vec![typ.clone()]); + let _v11 = builder.insert_call(keccakf1600, vec![array2], vec![typ.clone()]); + + let ssa = builder.finish(); + + println!("{ssa}"); + + let main = ssa.main(); + let instructions = main.dfg[main.entry_block()].instructions(); + let starting_instruction_count = instructions.len(); + assert_eq!(starting_instruction_count, 2); + + let ssa = ssa.fold_constants(); + + println!("{ssa}"); + + let main = ssa.main(); + let instructions = main.dfg[main.entry_block()].instructions(); + let ending_instruction_count = instructions.len(); + assert_eq!(ending_instruction_count, 1); + } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/capacity_tracker.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/capacity_tracker.rs index 836c812843e..ef208588718 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/capacity_tracker.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/capacity_tracker.rs @@ -99,7 +99,9 @@ impl<'a> SliceCapacityTracker<'a> { let slice_contents = arguments[argument_index]; if let Some(contents_capacity) = slice_sizes.get(&slice_contents) { - let new_capacity = *contents_capacity - 1; + // We use a saturating sub here as calling `pop_front` or `pop_back` + // on a zero-length slice would otherwise underflow. + let new_capacity = contents_capacity.saturating_sub(1); slice_sizes.insert(result_slice, new_capacity); } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/normalize_value_ids.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/normalize_value_ids.rs index f11b310494b..6914bf87c5d 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/normalize_value_ids.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/normalize_value_ids.rs @@ -115,6 +115,11 @@ impl Context { terminator.mutate_blocks(|old_block| self.new_ids.blocks[&old_block]); new_function.dfg.set_block_terminator(new_block_id, terminator); } + + // Also map the values in the databus + let old_databus = &old_function.dfg.data_bus; + new_function.dfg.data_bus = old_databus + .map_values(|old_value| self.new_ids.map_value(new_function, old_function, old_value)); } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs index 57862c699e2..bfcfada2d94 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs @@ -119,7 +119,9 @@ impl Context { } SizeChange::Dec { old, new } => { let old_capacity = self.get_or_find_capacity(&function.dfg, old); - self.slice_sizes.insert(new, old_capacity - 1); + // We use a saturating sub here as calling `pop_front` or `pop_back` on a zero-length slice + // would otherwise underflow. + self.slice_sizes.insert(new, old_capacity.saturating_sub(1)); } } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/mod.rs index 94e81b19582..07f15f37c6e 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/mod.rs @@ -19,6 +19,7 @@ pub use visitor::Visitor; pub use expression::*; pub use function::*; +use acvm::FieldElement; pub use docs::*; use noirc_errors::Span; use serde::{Deserialize, Serialize}; @@ -219,7 +220,7 @@ pub struct UnaryRhsMethodCall { #[derive(Debug, PartialEq, Eq, Clone, Hash)] pub enum UnresolvedTypeExpression { Variable(Path), - Constant(u32, Span), + Constant(FieldElement, Span), BinaryOperation( Box, BinaryTypeOperator, @@ -421,12 +422,13 @@ impl UnresolvedTypeExpression { fn from_expr_helper(expr: Expression) -> Result { match expr.kind { ExpressionKind::Literal(Literal::Integer(int, _)) => match int.try_to_u32() { - Some(int) => Ok(UnresolvedTypeExpression::Constant(int, expr.span)), + Some(int) => Ok(UnresolvedTypeExpression::Constant(int.into(), expr.span)), None => Err(expr), }, ExpressionKind::Variable(path) => Ok(UnresolvedTypeExpression::Variable(path)), ExpressionKind::Prefix(prefix) if prefix.operator == UnaryOp::Minus => { - let lhs = Box::new(UnresolvedTypeExpression::Constant(0, expr.span)); + let lhs = + Box::new(UnresolvedTypeExpression::Constant(FieldElement::zero(), expr.span)); let rhs = Box::new(UnresolvedTypeExpression::from_expr_helper(prefix.rhs)?); let op = BinaryTypeOperator::Subtraction; Ok(UnresolvedTypeExpression::BinaryOperation(lhs, op, rhs, expr.span)) diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/structure.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/structure.rs index 1675629ff14..8c8fe6e6387 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/structure.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/structure.rs @@ -19,8 +19,15 @@ pub struct NoirStruct { pub span: Span, } +impl NoirStruct { + pub fn is_abi(&self) -> bool { + self.attributes.iter().any(|attr| attr.is_abi()) + } +} + #[derive(Clone, Debug, PartialEq, Eq)] pub struct StructField { + pub visibility: ItemVisibility, pub name: Ident, pub typ: UnresolvedType, } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/traits.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/traits.rs index 61ef6f6276d..723df775b1e 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/traits.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/traits.rs @@ -18,6 +18,7 @@ use super::{Documented, GenericTypeArgs, ItemVisibility}; pub struct NoirTrait { pub name: Ident, pub generics: UnresolvedGenerics, + pub bounds: Vec, pub where_clause: Vec, pub span: Span, pub items: Vec>, @@ -134,7 +135,12 @@ impl Display for NoirTrait { let generics = vecmap(&self.generics, |generic| generic.to_string()); let generics = if generics.is_empty() { "".into() } else { generics.join(", ") }; - writeln!(f, "trait {}{} {{", self.name, generics)?; + write!(f, "trait {}{}", self.name, generics)?; + if !self.bounds.is_empty() { + let bounds = vecmap(&self.bounds, |bound| bound.to_string()).join(" + "); + write!(f, ": {}", bounds)?; + } + writeln!(f, " {{")?; for item in self.items.iter() { let item = item.to_string(); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/visitor.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/visitor.rs index 6e3e830e3f9..632c5656137 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/visitor.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/visitor.rs @@ -1391,7 +1391,7 @@ impl SecondaryAttribute { } pub fn accept_children(&self, target: AttributeTarget, visitor: &mut impl Visitor) { - if let SecondaryAttribute::Custom(custom) = self { + if let SecondaryAttribute::Meta(custom) = self { custom.accept(target, visitor); } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/comptime.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/comptime.rs index 426e160206f..65cb6072c62 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/comptime.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/comptime.rs @@ -158,7 +158,7 @@ impl<'context> Elaborator<'context> { attribute_context: AttributeContext, generated_items: &mut CollectedItems, ) { - if let SecondaryAttribute::Custom(attribute) = attribute { + if let SecondaryAttribute::Meta(attribute) = attribute { self.elaborate_in_comptime_context(|this| { if let Err(error) = this.run_comptime_attribute_name_on_item( &attribute.contents, @@ -188,25 +188,45 @@ impl<'context> Elaborator<'context> { let location = Location::new(attribute_span, self.file); let Some((function, arguments)) = Self::parse_attribute(attribute, location)? else { - // Do not issue an error if the attribute is unknown - return Ok(()); + return Err(( + ResolverError::UnableToParseAttribute { + attribute: attribute.to_string(), + span: attribute_span, + } + .into(), + self.file, + )); }; // Elaborate the function, rolling back any errors generated in case it is unknown let error_count = self.errors.len(); + let function_string = function.to_string(); let function = self.elaborate_expression(function).0; self.errors.truncate(error_count); let definition_id = match self.interner.expression(&function) { HirExpression::Ident(ident, _) => ident.id, - _ => return Ok(()), + _ => { + return Err(( + ResolverError::AttributeFunctionIsNotAPath { + function: function_string, + span: attribute_span, + } + .into(), + self.file, + )) + } }; let Some(definition) = self.interner.try_definition(definition_id) else { - // If there's no such function, don't return an error. - // This preserves backwards compatibility in allowing custom attributes that - // do not refer to comptime functions. - return Ok(()); + return Err(( + ResolverError::AttributeFunctionNotInScope { + name: function_string, + span: attribute_span, + } + .into(), + self.file, + )); }; let DefinitionKind::Function(function) = definition.kind else { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs index cbd72788c85..31a518ca97f 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -1,3 +1,4 @@ +use acvm::{AcirField, FieldElement}; use iter_extended::vecmap; use noirc_errors::{Location, Span}; use regex::Regex; @@ -6,13 +7,15 @@ use rustc_hash::FxHashSet as HashSet; use crate::{ ast::{ ArrayLiteral, BlockExpression, CallExpression, CastExpression, ConstructorExpression, - Expression, ExpressionKind, Ident, IfExpression, IndexExpression, InfixExpression, Lambda, - Literal, MemberAccessExpression, MethodCallExpression, PrefixExpression, StatementKind, - UnaryOp, UnresolvedTypeData, UnresolvedTypeExpression, + Expression, ExpressionKind, Ident, IfExpression, IndexExpression, InfixExpression, + ItemVisibility, Lambda, Literal, MemberAccessExpression, MethodCallExpression, + PrefixExpression, StatementKind, UnaryOp, UnresolvedTypeData, UnresolvedTypeExpression, }, hir::{ comptime::{self, InterpreterError}, - resolution::errors::ResolverError, + resolution::{ + errors::ResolverError, import::PathResolutionError, visibility::method_call_is_visible, + }, type_check::{generics::TraitGenerics, TypeCheckError}, }, hir_def::{ @@ -23,7 +26,7 @@ use crate::{ HirPrefixExpression, }, stmt::HirStatement, - traits::TraitConstraint, + traits::{ResolvedTraitBound, TraitConstraint}, }, node_interner::{DefinitionKind, ExprId, FuncId, InternedStatementKind, TraitMethodId}, token::Tokens, @@ -161,7 +164,7 @@ impl<'context> Elaborator<'context> { (Lit(int), self.polymorphic_integer_or_field()) } Literal::Str(str) | Literal::RawStr(str, _) => { - let len = Type::Constant(str.len() as u32, Kind::u32()); + let len = Type::Constant(str.len().into(), Kind::u32()); (Lit(HirLiteral::Str(str)), Type::String(Box::new(len))) } Literal::FmtStr(str) => self.elaborate_fmt_string(str, span), @@ -203,7 +206,7 @@ impl<'context> Elaborator<'context> { elem_id }); - let length = Type::Constant(elements.len() as u32, Kind::u32()); + let length = Type::Constant(elements.len().into(), Kind::u32()); (HirArrayLiteral::Standard(elements), first_elem_type, length) } ArrayLiteral::Repeated { repeated_element, length } => { @@ -211,7 +214,7 @@ impl<'context> Elaborator<'context> { let length = UnresolvedTypeExpression::from_expr(*length, span).unwrap_or_else(|error| { self.push_err(ResolverError::ParserError(Box::new(error))); - UnresolvedTypeExpression::Constant(0, span) + UnresolvedTypeExpression::Constant(FieldElement::zero(), span) }); let length = self.convert_expression_type(length, &Kind::u32(), span); @@ -267,7 +270,7 @@ impl<'context> Elaborator<'context> { } } - let len = Type::Constant(str.len() as u32, Kind::u32()); + let len = Type::Constant(str.len().into(), Kind::u32()); let typ = Type::FmtString(Box::new(len), Box::new(Type::Tuple(capture_types))); (HirExpression::Literal(HirLiteral::FmtStr(str, fmt_str_idents)), typ) } @@ -448,6 +451,8 @@ impl<'context> Elaborator<'context> { let method_call = HirMethodCallExpression { method, object, arguments, location, generics }; + self.check_method_call_visibility(func_id, &object_type, &method_call.method); + // Desugar the method call into a normal, resolved function call // so that the backend doesn't need to worry about methods // TODO: update object_type here? @@ -486,6 +491,20 @@ impl<'context> Elaborator<'context> { } } + fn check_method_call_visibility(&mut self, func_id: FuncId, object_type: &Type, name: &Ident) { + if !method_call_is_visible( + object_type, + func_id, + self.module_id(), + self.interner, + self.def_maps, + ) { + self.push_err(ResolverError::PathResolutionError(PathResolutionError::Private( + name.clone(), + ))); + } + } + fn elaborate_constructor( &mut self, constructor: ConstructorExpression, @@ -548,7 +567,7 @@ impl<'context> Elaborator<'context> { let generics = struct_generics.clone(); let fields = constructor.fields; - let field_types = r#type.borrow().get_fields(&struct_generics); + let field_types = r#type.borrow().get_fields_with_visibility(&struct_generics); let fields = self.resolve_constructor_expr_fields(struct_type.clone(), field_types, fields, span); let expr = HirExpression::Constructor(HirConstructorExpression { @@ -576,7 +595,7 @@ impl<'context> Elaborator<'context> { fn resolve_constructor_expr_fields( &mut self, struct_type: Shared, - field_types: Vec<(String, Type)>, + field_types: Vec<(String, ItemVisibility, Type)>, fields: Vec<(Ident, Expression)>, span: Span, ) -> Vec<(Ident, ExprId)> { @@ -588,10 +607,11 @@ impl<'context> Elaborator<'context> { let expected_field_with_index = field_types .iter() .enumerate() - .find(|(_, (name, _))| name == &field_name.0.contents); - let expected_index = expected_field_with_index.map(|(index, _)| index); + .find(|(_, (name, _, _))| name == &field_name.0.contents); + let expected_index_and_visibility = + expected_field_with_index.map(|(index, (_, visibility, _))| (index, visibility)); let expected_type = - expected_field_with_index.map(|(_, (_, typ))| typ).unwrap_or(&Type::Error); + expected_field_with_index.map(|(_, (_, _, typ))| typ).unwrap_or(&Type::Error); let field_span = field.span; let (resolved, field_type) = self.elaborate_expression(field); @@ -618,11 +638,21 @@ impl<'context> Elaborator<'context> { }); } - if let Some(expected_index) = expected_index { + if let Some((index, visibility)) = expected_index_and_visibility { + let struct_type = struct_type.borrow(); + let field_span = field_name.span(); + let field_name = &field_name.0.contents; + self.check_struct_field_visibility( + &struct_type, + field_name, + *visibility, + field_span, + ); + self.interner.add_struct_member_reference( - struct_type.borrow().id, - expected_index, - Location::new(field_name.span(), self.file), + struct_type.id, + index, + Location::new(field_span, self.file), ); } @@ -665,7 +695,7 @@ impl<'context> Elaborator<'context> { fn elaborate_cast(&mut self, cast: CastExpression, span: Span) -> (HirExpression, Type) { let (lhs, lhs_type) = self.elaborate_expression(cast.lhs); let r#type = self.resolve_type(cast.r#type); - let result = self.check_cast(&lhs_type, &r#type, span); + let result = self.check_cast(&lhs, &lhs_type, &r#type, span); let expr = HirExpression::Cast(HirCastExpression { lhs, r#type }); (expr, result) } @@ -713,9 +743,11 @@ impl<'context> Elaborator<'context> { // that implements the trait. let constraint = TraitConstraint { typ: operand_type.clone(), - trait_id: trait_id.trait_id, - trait_generics: TraitGenerics::default(), - span, + trait_bound: ResolvedTraitBound { + trait_id: trait_id.trait_id, + trait_generics: TraitGenerics::default(), + span, + }, }; self.push_trait_constraint(constraint, expr_id); self.type_check_operator_method(expr_id, trait_id, operand_type, span); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs index bd78febc1d0..5067ac05c44 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs @@ -3,7 +3,7 @@ use std::{ rc::Rc, }; -use crate::ast::ItemVisibility; +use crate::{ast::ItemVisibility, hir_def::traits::ResolvedTraitBound, StructField, TypeBindings}; use crate::{ ast::{ BlockExpression, FunctionKind, GenericTypeArgs, Ident, NoirFunction, NoirStruct, Param, @@ -54,6 +54,7 @@ mod unquote; use fm::FileId; use iter_extended::vecmap; use noirc_errors::{Location, Span}; +use types::bind_ordered_generics; use self::traits::check_trait_impl_method_matches_declaration; @@ -433,7 +434,8 @@ impl<'context> Elaborator<'context> { // Now remove all the `where` clause constraints we added for constraint in &func_meta.trait_constraints { - self.interner.remove_assumed_trait_implementations_for_trait(constraint.trait_id); + self.interner + .remove_assumed_trait_implementations_for_trait(constraint.trait_bound.trait_id); } let func_scope_tree = self.scopes.end_function(); @@ -479,9 +481,9 @@ impl<'context> Elaborator<'context> { self.verify_trait_constraint( &constraint.typ, - constraint.trait_id, - &constraint.trait_generics.ordered, - &constraint.trait_generics.named, + constraint.trait_bound.trait_id, + &constraint.trait_bound.trait_generics.ordered, + &constraint.trait_bound.trait_generics.named, expr_id, span, ); @@ -510,7 +512,8 @@ impl<'context> Elaborator<'context> { let generic_type = Type::NamedGeneric(new_generic, Rc::new(name)); let trait_bound = TraitBound { trait_path, trait_id: None, trait_generics }; - if let Some(new_constraint) = self.resolve_trait_bound(&trait_bound, generic_type.clone()) { + if let Some(trait_bound) = self.resolve_trait_bound(&trait_bound) { + let new_constraint = TraitConstraint { typ: generic_type.clone(), trait_bound }; trait_constraints.push(new_constraint); } @@ -668,14 +671,11 @@ impl<'context> Elaborator<'context> { constraint: &UnresolvedTraitConstraint, ) -> Option { let typ = self.resolve_type(constraint.typ.clone()); - self.resolve_trait_bound(&constraint.trait_bound, typ) + let trait_bound = self.resolve_trait_bound(&constraint.trait_bound)?; + Some(TraitConstraint { typ, trait_bound }) } - pub fn resolve_trait_bound( - &mut self, - bound: &TraitBound, - typ: Type, - ) -> Option { + pub fn resolve_trait_bound(&mut self, bound: &TraitBound) -> Option { let the_trait = self.lookup_trait_or_error(bound.trait_path.clone())?; let trait_id = the_trait.id; let span = bound.trait_path.span; @@ -683,7 +683,7 @@ impl<'context> Elaborator<'context> { let (ordered, named) = self.resolve_type_args(bound.trait_generics.clone(), trait_id, span); let trait_generics = TraitGenerics { ordered, named }; - Some(TraitConstraint { typ, trait_id, trait_generics, span }) + Some(ResolvedTraitBound { trait_id, trait_generics, span }) } /// Extract metadata from a NoirFunction @@ -751,7 +751,7 @@ impl<'context> Elaborator<'context> { ); if is_entry_point { - self.mark_parameter_type_as_used(&typ); + self.mark_type_as_used(&typ); } let pattern = self.elaborate_pattern_and_store_ids( @@ -760,7 +760,6 @@ impl<'context> Elaborator<'context> { DefinitionKind::Local(None), &mut parameter_idents, true, // warn_if_unused - None, ); parameters.push((pattern, typ.clone(), visibility)); @@ -832,30 +831,33 @@ impl<'context> Elaborator<'context> { self.current_item = None; } - fn mark_parameter_type_as_used(&mut self, typ: &Type) { + fn mark_type_as_used(&mut self, typ: &Type) { match typ { - Type::Array(_n, typ) => self.mark_parameter_type_as_used(typ), - Type::Slice(typ) => self.mark_parameter_type_as_used(typ), + Type::Array(_n, typ) => self.mark_type_as_used(typ), + Type::Slice(typ) => self.mark_type_as_used(typ), Type::Tuple(types) => { for typ in types { - self.mark_parameter_type_as_used(typ); + self.mark_type_as_used(typ); } } Type::Struct(struct_type, generics) => { self.mark_struct_as_constructed(struct_type.clone()); for generic in generics { - self.mark_parameter_type_as_used(generic); + self.mark_type_as_used(generic); + } + for (_, typ) in struct_type.borrow().get_fields(generics) { + self.mark_type_as_used(&typ); } } Type::Alias(alias_type, generics) => { - self.mark_parameter_type_as_used(&alias_type.borrow().get_type(generics)); + self.mark_type_as_used(&alias_type.borrow().get_type(generics)); } Type::MutableReference(typ) => { - self.mark_parameter_type_as_used(typ); + self.mark_type_as_used(typ); } Type::InfixExpr(left, _op, right) => { - self.mark_parameter_type_as_used(left); - self.mark_parameter_type_as_used(right); + self.mark_type_as_used(left); + self.mark_type_as_used(right); } Type::FieldElement | Type::Integer(..) @@ -872,15 +874,6 @@ impl<'context> Elaborator<'context> { | Type::Forall(..) | Type::Error => (), } - - if let Type::Alias(alias_type, generics) = typ { - self.mark_parameter_type_as_used(&alias_type.borrow().get_type(generics)); - return; - } - - if let Type::Struct(struct_type, _generics) = typ { - self.mark_struct_as_constructed(struct_type.clone()); - } } fn run_function_lints(&mut self, func: &FuncMeta, modifiers: &FunctionModifiers) { @@ -948,21 +941,52 @@ impl<'context> Elaborator<'context> { fn add_trait_constraints_to_scope(&mut self, func_meta: &FuncMeta) { for constraint in &func_meta.trait_constraints { - let object = constraint.typ.clone(); - let trait_id = constraint.trait_id; - let generics = constraint.trait_generics.clone(); - - if !self.interner.add_assumed_trait_implementation(object, trait_id, generics) { - if let Some(the_trait) = self.interner.try_get_trait(trait_id) { - let trait_name = the_trait.name.to_string(); - let typ = constraint.typ.clone(); - let span = func_meta.location.span; - self.push_err(TypeCheckError::UnneededTraitConstraint { - trait_name, - typ, - span, - }); + self.add_trait_bound_to_scope( + func_meta, + &constraint.typ, + &constraint.trait_bound, + constraint.trait_bound.trait_id, + ); + } + } + + fn add_trait_bound_to_scope( + &mut self, + func_meta: &FuncMeta, + object: &Type, + trait_bound: &ResolvedTraitBound, + starting_trait_id: TraitId, + ) { + let trait_id = trait_bound.trait_id; + let generics = trait_bound.trait_generics.clone(); + + if !self.interner.add_assumed_trait_implementation(object.clone(), trait_id, generics) { + if let Some(the_trait) = self.interner.try_get_trait(trait_id) { + let trait_name = the_trait.name.to_string(); + let typ = object.clone(); + let span = func_meta.location.span; + self.push_err(TypeCheckError::UnneededTraitConstraint { trait_name, typ, span }); + } + } + + // Also add assumed implementations for the parent traits, if any + if let Some(trait_bounds) = + self.interner.try_get_trait(trait_id).map(|the_trait| the_trait.trait_bounds.clone()) + { + for parent_trait_bound in trait_bounds { + // Avoid looping forever in case there are cycles + if parent_trait_bound.trait_id == starting_trait_id { + continue; } + + let parent_trait_bound = + self.instantiate_parent_trait_bound(trait_bound, &parent_trait_bound); + self.add_trait_bound_to_scope( + func_meta, + object, + &parent_trait_bound, + starting_trait_id, + ); } } } @@ -978,6 +1002,8 @@ impl<'context> Elaborator<'context> { self.file = trait_impl.file_id; self.local_module = trait_impl.module_id; + self.check_parent_traits_are_implemented(&trait_impl); + self.generics = trait_impl.resolved_generics; self.current_trait_impl = trait_impl.impl_id; @@ -994,6 +1020,73 @@ impl<'context> Elaborator<'context> { self.generics.clear(); } + fn check_parent_traits_are_implemented(&mut self, trait_impl: &UnresolvedTraitImpl) { + let Some(trait_id) = trait_impl.trait_id else { + return; + }; + + let Some(object_type) = &trait_impl.resolved_object_type else { + return; + }; + + let Some(the_trait) = self.interner.try_get_trait(trait_id) else { + return; + }; + + if the_trait.trait_bounds.is_empty() { + return; + } + + let impl_trait = the_trait.name.to_string(); + let the_trait_file = the_trait.location.file; + + let mut bindings = TypeBindings::new(); + bind_ordered_generics( + &the_trait.generics, + &trait_impl.resolved_trait_generics, + &mut bindings, + ); + + // Note: we only check if the immediate parents are implemented, we don't check recursively. + // Why? If a parent isn't implemented, we get an error. If a parent is implemented, we'll + // do the same check for the parent, so this trait's parents parents will be checked, so the + // recursion is guaranteed. + for parent_trait_bound in the_trait.trait_bounds.clone() { + let Some(parent_trait) = self.interner.try_get_trait(parent_trait_bound.trait_id) + else { + continue; + }; + + let parent_trait_bound = ResolvedTraitBound { + trait_generics: parent_trait_bound + .trait_generics + .map(|typ| typ.substitute(&bindings)), + ..parent_trait_bound + }; + + if self + .interner + .try_lookup_trait_implementation( + object_type, + parent_trait_bound.trait_id, + &parent_trait_bound.trait_generics.ordered, + &parent_trait_bound.trait_generics.named, + ) + .is_err() + { + let missing_trait = + format!("{}{}", parent_trait.name, parent_trait_bound.trait_generics); + self.push_err(ResolverError::TraitNotImplemented { + impl_trait: impl_trait.clone(), + missing_trait, + type_missing_trait: trait_impl.object_type.to_string(), + span: trait_impl.object_type.span, + missing_trait_location: Location::new(parent_trait_bound.span, the_trait_file), + }); + } + } + } + fn collect_impls( &mut self, module: LocalModuleId, @@ -1283,6 +1376,13 @@ impl<'context> Elaborator<'context> { self.local_module = typ.module_id; let fields = self.resolve_struct_fields(&typ.struct_def, *type_id); + + if typ.struct_def.is_abi() { + for field in &fields { + self.mark_type_as_used(&field.typ); + } + } + let fields_len = fields.len(); self.interner.update_struct(*type_id, |struct_def| { struct_def.set_fields(fields); @@ -1321,7 +1421,7 @@ impl<'context> Elaborator<'context> { &mut self, unresolved: &NoirStruct, struct_id: StructId, - ) -> Vec<(Ident, Type)> { + ) -> Vec { self.recover_generics(|this| { this.current_item = Some(DependencyId::Struct(struct_id)); @@ -1333,7 +1433,8 @@ impl<'context> Elaborator<'context> { let fields = vecmap(&unresolved.fields, |field| { let ident = &field.item.name; let typ = &field.item.typ; - (ident.clone(), this.resolve_type(typ.clone())) + let visibility = field.item.visibility; + StructField { visibility, name: ident.clone(), typ: this.resolve_type(typ.clone()) } }); this.resolving_ids.remove(&struct_id); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/patterns.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/patterns.rs index bb8d041eab4..d55011f98a1 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/patterns.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/patterns.rs @@ -4,7 +4,8 @@ use rustc_hash::FxHashSet as HashSet; use crate::{ ast::{ - Expression, ExpressionKind, Ident, Path, Pattern, TypePath, UnresolvedType, ERROR_IDENT, + Expression, ExpressionKind, Ident, ItemVisibility, Path, Pattern, TypePath, UnresolvedType, + ERROR_IDENT, }, hir::{ def_collector::dc_crate::CompilationError, @@ -36,7 +37,6 @@ impl<'context> Elaborator<'context> { None, &mut Vec::new(), warn_if_unused, - None, ) } @@ -49,7 +49,6 @@ impl<'context> Elaborator<'context> { definition_kind: DefinitionKind, created_ids: &mut Vec, warn_if_unused: bool, - global_id: Option, ) -> HirPattern { self.elaborate_pattern_mut( pattern, @@ -58,7 +57,6 @@ impl<'context> Elaborator<'context> { None, created_ids, warn_if_unused, - global_id, ) } @@ -71,7 +69,6 @@ impl<'context> Elaborator<'context> { mutable: Option, new_definitions: &mut Vec, warn_if_unused: bool, - global_id: Option, ) -> HirPattern { match pattern { Pattern::Identifier(name) => { @@ -81,7 +78,7 @@ impl<'context> Elaborator<'context> { (Some(_), DefinitionKind::Local(_)) => DefinitionKind::Local(None), (_, other) => other, }; - let ident = if let Some(global_id) = global_id { + let ident = if let DefinitionKind::Global(global_id) = definition { // Globals don't need to be added to scope, they're already in the def_maps let id = self.interner.get_global(global_id).definition_id; let location = Location::new(name.span(), self.file); @@ -111,7 +108,6 @@ impl<'context> Elaborator<'context> { Some(span), new_definitions, warn_if_unused, - global_id, ); let location = Location::new(span, self.file); HirPattern::Mutable(Box::new(pattern), location) @@ -143,7 +139,6 @@ impl<'context> Elaborator<'context> { mutable, new_definitions, warn_if_unused, - global_id, ) }); let location = Location::new(span, self.file); @@ -167,7 +162,6 @@ impl<'context> Elaborator<'context> { mutable, new_definitions, warn_if_unused, - global_id, ) } } @@ -272,7 +266,9 @@ impl<'context> Elaborator<'context> { let mut unseen_fields = struct_type.borrow().field_names(); for (field, pattern) in fields { - let field_type = expected_type.get_field_type(&field.0.contents).unwrap_or(Type::Error); + let (field_type, visibility) = expected_type + .get_field_type_and_visibility(&field.0.contents) + .unwrap_or((Type::Error, ItemVisibility::Public)); let resolved = self.elaborate_pattern_mut( pattern, field_type, @@ -280,12 +276,18 @@ impl<'context> Elaborator<'context> { mutable, new_definitions, true, // warn_if_unused - None, ); if unseen_fields.contains(&field) { unseen_fields.remove(&field); seen_fields.insert(field.clone()); + + self.check_struct_field_visibility( + &struct_type.borrow(), + &field.0.contents, + visibility, + field.span(), + ); } else if seen_fields.contains(&field) { // duplicate field self.push_err(ResolverError::DuplicateField { field: field.clone() }); @@ -319,8 +321,8 @@ impl<'context> Elaborator<'context> { warn_if_unused: bool, definition: DefinitionKind, ) -> HirIdent { - if definition.is_global() { - return self.add_global_variable_decl(name, definition); + if let DefinitionKind::Global(global_id) = definition { + return self.add_global_variable_decl(name, global_id); } let location = Location::new(name.span(), self.file); @@ -365,47 +367,19 @@ impl<'context> Elaborator<'context> { } } - pub fn add_global_variable_decl( - &mut self, - name: Ident, - definition: DefinitionKind, - ) -> HirIdent { - let comptime = self.in_comptime_context(); + pub fn add_global_variable_decl(&mut self, name: Ident, global_id: GlobalId) -> HirIdent { let scope = self.scopes.get_mut_scope(); - - // This check is necessary to maintain the same definition ids in the interner. Currently, each function uses a new resolver that has its own ScopeForest and thus global scope. - // We must first check whether an existing definition ID has been inserted as otherwise there will be multiple definitions for the same global statement. - // This leads to an error in evaluation where the wrong definition ID is selected when evaluating a statement using the global. The check below prevents this error. - let mut global_id = None; - let global = self.interner.get_all_globals(); - for global_info in global { - if global_info.local_id == self.local_module && global_info.ident == name { - global_id = Some(global_info.id); - } - } - - let (ident, resolver_meta) = if let Some(id) = global_id { - let global = self.interner.get_global(id); - let hir_ident = HirIdent::non_trait_method(global.definition_id, global.location); - let ident = hir_ident.clone(); - let resolver_meta = ResolverMeta { num_times_used: 0, ident, warn_if_unused: true }; - (hir_ident, resolver_meta) - } else { - let location = Location::new(name.span(), self.file); - let name = name.0.contents.clone(); - let id = self.interner.push_definition(name, false, comptime, definition, location); - let ident = HirIdent::non_trait_method(id, location); - let resolver_meta = - ResolverMeta { num_times_used: 0, ident: ident.clone(), warn_if_unused: true }; - (ident, resolver_meta) - }; + let global = self.interner.get_global(global_id); + let ident = HirIdent::non_trait_method(global.definition_id, global.location); + let resolver_meta = + ResolverMeta { num_times_used: 0, ident: ident.clone(), warn_if_unused: true }; let old_global_value = scope.add_key_value(name.0.contents.clone(), resolver_meta); if let Some(old_global_value) = old_global_value { self.push_err(ResolverError::DuplicateDefinition { - name: name.0.contents.clone(), - first_span: old_global_value.ident.location.span, second_span: name.span(), + name: name.0.contents, + first_span: old_global_value.ident.location.span, }); } ident @@ -645,7 +619,7 @@ impl<'context> Elaborator<'context> { if let ImplKind::TraitMethod(mut method) = ident.impl_kind { method.constraint.apply_bindings(&bindings); if method.assumed { - let trait_generics = method.constraint.trait_generics.clone(); + let trait_generics = method.constraint.trait_bound.trait_generics.clone(); let object_type = method.constraint.typ; let trait_impl = TraitImplKind::Assumed { object_type, trait_generics }; self.interner.select_impl_for_expression(expr_id, trait_impl); @@ -738,7 +712,7 @@ impl<'context> Elaborator<'context> { HirMethodReference::TraitMethodId(method_id, generics) => { let mut constraint = self.interner.get_trait(method_id.trait_id).as_constraint(span); - constraint.trait_generics = generics; + constraint.trait_bound.trait_generics = generics; ImplKind::TraitMethod(TraitMethod { method_id, constraint, assumed: false }) } }; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/statements.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/statements.rs index 6d5d0c9b467..238160e5aa4 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/statements.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/statements.rs @@ -3,11 +3,14 @@ use noirc_errors::{Location, Span, Spanned}; use crate::{ ast::{ AssignStatement, BinaryOpKind, ConstrainKind, ConstrainStatement, Expression, - ExpressionKind, ForLoopStatement, ForRange, InfixExpression, LValue, LetStatement, Path, - Statement, StatementKind, + ExpressionKind, ForLoopStatement, ForRange, Ident, InfixExpression, ItemVisibility, LValue, + LetStatement, Path, Statement, StatementKind, }, hir::{ - resolution::errors::ResolverError, + resolution::{ + errors::ResolverError, import::PathResolutionError, + visibility::struct_member_is_visible, + }, type_check::{Source, TypeCheckError}, }, hir_def::{ @@ -18,7 +21,7 @@ use crate::{ }, }, node_interner::{DefinitionId, DefinitionKind, GlobalId, StmtId}, - Type, + StructType, Type, }; use super::{lints, Elaborator}; @@ -107,7 +110,6 @@ impl<'context> Elaborator<'context> { definition, &mut Vec::new(), warn_if_unused, - global_id, ); let attributes = let_stmt.attributes; @@ -439,10 +441,12 @@ impl<'context> Elaborator<'context> { match &lhs_type { Type::Struct(s, args) => { let s = s.borrow(); - if let Some((field, index)) = s.get_field(field_name, args) { + if let Some((field, visibility, index)) = s.get_field(field_name, args) { let reference_location = Location::new(span, self.file); self.interner.add_struct_member_reference(s.id, index, reference_location); + self.check_struct_field_visibility(&s, field_name, visibility, span); + return Some((field, index)); } } @@ -497,6 +501,20 @@ impl<'context> Elaborator<'context> { None } + pub(super) fn check_struct_field_visibility( + &mut self, + struct_type: &StructType, + field_name: &str, + visibility: ItemVisibility, + span: Span, + ) { + if !struct_member_is_visible(struct_type.id, visibility, self.module_id(), self.def_maps) { + self.push_err(ResolverError::PathResolutionError(PathResolutionError::Private( + Ident::new(field_name.to_string(), span), + ))); + } + } + fn elaborate_comptime_statement(&mut self, statement: Statement) -> (HirStatement, Type) { let span = statement.span; let (hir_statement, _typ) = diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/trait_impls.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/trait_impls.rs index 858cfa5cdd6..20f048bed05 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/trait_impls.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/trait_impls.rs @@ -167,12 +167,14 @@ impl<'context> Elaborator<'context> { let mut substituted_method_ids = HashSet::default(); for method_constraint in method.trait_constraints.iter() { let substituted_constraint_type = method_constraint.typ.substitute(&bindings); - let substituted_trait_generics = - method_constraint.trait_generics.map(|generic| generic.substitute(&bindings)); + let substituted_trait_generics = method_constraint + .trait_bound + .trait_generics + .map(|generic| generic.substitute(&bindings)); substituted_method_ids.insert(( substituted_constraint_type, - method_constraint.trait_id, + method_constraint.trait_bound.trait_id, substituted_trait_generics, )); } @@ -180,7 +182,8 @@ impl<'context> Elaborator<'context> { for override_trait_constraint in override_meta.trait_constraints.clone() { let override_constraint_is_from_impl = trait_impl_where_clause.iter().any(|impl_constraint| { - impl_constraint.trait_id == override_trait_constraint.trait_id + impl_constraint.trait_bound.trait_id + == override_trait_constraint.trait_bound.trait_id }); if override_constraint_is_from_impl { continue; @@ -188,15 +191,16 @@ impl<'context> Elaborator<'context> { if !substituted_method_ids.contains(&( override_trait_constraint.typ.clone(), - override_trait_constraint.trait_id, - override_trait_constraint.trait_generics.clone(), + override_trait_constraint.trait_bound.trait_id, + override_trait_constraint.trait_bound.trait_generics.clone(), )) { - let the_trait = self.interner.get_trait(override_trait_constraint.trait_id); + let the_trait = + self.interner.get_trait(override_trait_constraint.trait_bound.trait_id); self.push_err(DefCollectorErrorKind::ImplIsStricterThanTrait { constraint_typ: override_trait_constraint.typ, constraint_name: the_trait.name.0.contents.clone(), - constraint_generics: override_trait_constraint.trait_generics, - constraint_span: override_trait_constraint.span, + constraint_generics: override_trait_constraint.trait_bound.trait_generics, + constraint_span: override_trait_constraint.trait_bound.span, trait_method_name: method.name.0.contents.clone(), trait_method_span: method.location.span, }); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/traits.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/traits.rs index b4042bd3e31..e877682972c 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/traits.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/traits.rs @@ -10,8 +10,11 @@ use crate::{ UnresolvedTraitConstraint, UnresolvedType, }, hir::{def_collector::dc_crate::UnresolvedTrait, type_check::TypeCheckError}, - hir_def::{function::Parameters, traits::TraitFunction}, - node_interner::{FuncId, NodeInterner, ReferenceId, TraitId}, + hir_def::{ + function::Parameters, + traits::{ResolvedTraitBound, TraitFunction}, + }, + node_interner::{DependencyId, FuncId, NodeInterner, ReferenceId, TraitId}, ResolvedGeneric, Type, TypeBindings, }; @@ -34,10 +37,17 @@ impl<'context> Elaborator<'context> { this.generics.push(associated_type.clone()); } + let resolved_trait_bounds = this.resolve_trait_bounds(unresolved_trait); + for bound in &resolved_trait_bounds { + this.interner + .add_trait_dependency(DependencyId::Trait(bound.trait_id), *trait_id); + } + let methods = this.resolve_trait_methods(*trait_id, unresolved_trait); this.interner.update_trait(*trait_id, |trait_def| { trait_def.set_methods(methods); + trait_def.set_trait_bounds(resolved_trait_bounds); }); }); @@ -53,6 +63,14 @@ impl<'context> Elaborator<'context> { self.current_trait = None; } + fn resolve_trait_bounds( + &mut self, + unresolved_trait: &UnresolvedTrait, + ) -> Vec { + let bounds = &unresolved_trait.trait_def.bounds; + bounds.iter().filter_map(|bound| self.resolve_trait_bound(bound)).collect() + } + fn resolve_trait_methods( &mut self, trait_id: TraitId, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs index ef06cfdaad8..8ffbd15bdab 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs @@ -28,7 +28,7 @@ use crate::{ }, function::{FuncMeta, Parameters}, stmt::HirStatement, - traits::{NamedType, TraitConstraint}, + traits::{NamedType, ResolvedTraitBound, Trait, TraitConstraint}, }, node_interner::{ DefinitionKind, DependencyId, ExprId, GlobalId, ImplSearchErrorKind, NodeInterner, TraitId, @@ -417,7 +417,17 @@ impl<'context> Elaborator<'context> { .map(|let_statement| Kind::Numeric(Box::new(let_statement.r#type))) .unwrap_or(Kind::u32()); - Some(Type::Constant(self.eval_global_as_array_length(id, path), kind)) + // TODO(https://github.com/noir-lang/noir/issues/6238): + // support non-u32 generics here + if !kind.unifies(&Kind::u32()) { + let error = TypeCheckError::EvaluatedGlobalIsntU32 { + expected_kind: Kind::u32().to_string(), + expr_kind: kind.to_string(), + expr_span: path.span(), + }; + self.push_err(error); + } + Some(Type::Constant(self.eval_global_as_array_length(id, path).into(), kind)) } _ => None, } @@ -586,7 +596,7 @@ impl<'context> Elaborator<'context> { continue; } - let the_trait = self.interner.get_trait(constraint.trait_id); + let the_trait = self.interner.get_trait(constraint.trait_bound.trait_id); if let Some(method) = the_trait.find_method(path.last_name()) { return Some(TraitPathResolution { method: TraitMethod { method_id: method, constraint, assumed: true }, @@ -833,12 +843,27 @@ impl<'context> Elaborator<'context> { } } - pub(super) fn check_cast(&mut self, from: &Type, to: &Type, span: Span) -> Type { - match from.follow_bindings() { - Type::Integer(..) | Type::FieldElement | Type::Bool => (), + pub(super) fn check_cast( + &mut self, + from_expr_id: &ExprId, + from: &Type, + to: &Type, + span: Span, + ) -> Type { + let from_follow_bindings = from.follow_bindings(); - Type::TypeVariable(var) if var.is_integer() || var.is_integer_or_field() => (), + let from_value_opt = match self.interner.expression(from_expr_id) { + HirExpression::Literal(HirLiteral::Integer(int, false)) => Some(int), + // TODO(https://github.com/noir-lang/noir/issues/6247): + // handle negative literals + _ => None, + }; + + let from_is_polymorphic = match from_follow_bindings { + Type::Integer(..) | Type::FieldElement | Type::Bool => false, + + Type::TypeVariable(ref var) if var.is_integer() || var.is_integer_or_field() => true, Type::TypeVariable(_) => { // NOTE: in reality the expected type can also include bool, but for the compiler's simplicity // we only allow integer types. If a bool is in `from` it will need an explicit type annotation. @@ -846,13 +871,32 @@ impl<'context> Elaborator<'context> { self.unify(from, &expected, || TypeCheckError::InvalidCast { from: from.clone(), span, + reason: "casting from a non-integral type is unsupported".into(), }); + true } Type::Error => return Type::Error, from => { - self.push_err(TypeCheckError::InvalidCast { from, span }); + let reason = "casting from this type is unsupported".into(); + self.push_err(TypeCheckError::InvalidCast { from, span, reason }); return Type::Error; } + }; + + // TODO(https://github.com/noir-lang/noir/issues/6247): + // handle negative literals + // when casting a polymorphic value to a specifically sized type, + // check that it fits or throw a warning + if let (Some(from_value), Some(to_maximum_size)) = + (from_value_opt, to.integral_maximum_size()) + { + if from_is_polymorphic && from_value > to_maximum_size { + let from = from.clone(); + let to = to.clone(); + let reason = format!("casting untyped value ({from_value}) to a type with a maximum size ({to_maximum_size}) that's smaller than it"); + // we warn that the 'to' type is too small for the value + self.push_err(TypeCheckError::DownsizingCast { from, to, span, reason }); + } } match to { @@ -1332,15 +1376,16 @@ impl<'context> Elaborator<'context> { for constraint in &func_meta.trait_constraints { if *object_type == constraint.typ { - if let Some(the_trait) = self.interner.try_get_trait(constraint.trait_id) { - for (method_index, method) in the_trait.methods.iter().enumerate() { - if method.name.0.contents == method_name { - let trait_method = - TraitMethodId { trait_id: constraint.trait_id, method_index }; - - let generics = constraint.trait_generics.clone(); - return Some(HirMethodReference::TraitMethodId(trait_method, generics)); - } + if let Some(the_trait) = + self.interner.try_get_trait(constraint.trait_bound.trait_id) + { + if let Some(method) = self.lookup_method_in_trait( + the_trait, + method_name, + &constraint.trait_bound, + the_trait.id, + ) { + return Some(method); } } } @@ -1355,6 +1400,44 @@ impl<'context> Elaborator<'context> { None } + fn lookup_method_in_trait( + &self, + the_trait: &Trait, + method_name: &str, + trait_bound: &ResolvedTraitBound, + starting_trait_id: TraitId, + ) -> Option { + if let Some(trait_method) = the_trait.find_method(method_name) { + return Some(HirMethodReference::TraitMethodId( + trait_method, + trait_bound.trait_generics.clone(), + )); + } + + // Search in the parent traits, if any + for parent_trait_bound in &the_trait.trait_bounds { + if let Some(the_trait) = self.interner.try_get_trait(parent_trait_bound.trait_id) { + // Avoid looping forever in case there are cycles + if the_trait.id == starting_trait_id { + continue; + } + + let parent_trait_bound = + self.instantiate_parent_trait_bound(trait_bound, parent_trait_bound); + if let Some(method) = self.lookup_method_in_trait( + the_trait, + method_name, + &parent_trait_bound, + starting_trait_id, + ) { + return Some(method); + } + } + } + + None + } + pub(super) fn type_check_call( &mut self, call: &HirCallExpression, @@ -1757,55 +1840,86 @@ impl<'context> Elaborator<'context> { } pub fn bind_generics_from_trait_constraint( - &mut self, + &self, constraint: &TraitConstraint, assumed: bool, bindings: &mut TypeBindings, ) { - let the_trait = self.interner.get_trait(constraint.trait_id); - assert_eq!(the_trait.generics.len(), constraint.trait_generics.ordered.len()); - - for (param, arg) in the_trait.generics.iter().zip(&constraint.trait_generics.ordered) { - // Avoid binding t = t - if !arg.occurs(param.type_var.id()) { - bindings.insert( - param.type_var.id(), - (param.type_var.clone(), param.kind(), arg.clone()), - ); - } - } - - let mut associated_types = the_trait.associated_types.clone(); - assert_eq!(associated_types.len(), constraint.trait_generics.named.len()); - - for arg in &constraint.trait_generics.named { - let i = associated_types - .iter() - .position(|typ| *typ.name == arg.name.0.contents) - .unwrap_or_else(|| { - unreachable!("Expected to find associated type named {}", arg.name) - }); - - let param = associated_types.swap_remove(i); - - // Avoid binding t = t - if !arg.typ.occurs(param.type_var.id()) { - bindings.insert( - param.type_var.id(), - (param.type_var.clone(), param.kind(), arg.typ.clone()), - ); - } - } + self.bind_generics_from_trait_bound(&constraint.trait_bound, bindings); // If the trait impl is already assumed to exist we should add any type bindings for `Self`. // Otherwise `self` will be replaced with a fresh type variable, which will require the user // to specify a redundant type annotation. if assumed { + let the_trait = self.interner.get_trait(constraint.trait_bound.trait_id); let self_type = the_trait.self_type_typevar.clone(); let kind = the_trait.self_type_typevar.kind(); bindings.insert(self_type.id(), (self_type, kind, constraint.typ.clone())); } } + + pub fn bind_generics_from_trait_bound( + &self, + trait_bound: &ResolvedTraitBound, + bindings: &mut TypeBindings, + ) { + let the_trait = self.interner.get_trait(trait_bound.trait_id); + + bind_ordered_generics(&the_trait.generics, &trait_bound.trait_generics.ordered, bindings); + + let associated_types = the_trait.associated_types.clone(); + bind_named_generics(associated_types, &trait_bound.trait_generics.named, bindings); + } + + pub fn instantiate_parent_trait_bound( + &self, + trait_bound: &ResolvedTraitBound, + parent_trait_bound: &ResolvedTraitBound, + ) -> ResolvedTraitBound { + let mut bindings = TypeBindings::new(); + self.bind_generics_from_trait_bound(trait_bound, &mut bindings); + ResolvedTraitBound { + trait_generics: parent_trait_bound.trait_generics.map(|typ| typ.substitute(&bindings)), + ..*parent_trait_bound + } + } +} + +pub(crate) fn bind_ordered_generics( + params: &[ResolvedGeneric], + args: &[Type], + bindings: &mut TypeBindings, +) { + assert_eq!(params.len(), args.len()); + + for (param, arg) in params.iter().zip(args) { + bind_generic(param, arg, bindings); + } +} + +pub(crate) fn bind_named_generics( + mut params: Vec, + args: &[NamedType], + bindings: &mut TypeBindings, +) { + assert_eq!(params.len(), args.len()); + + for arg in args { + let i = params + .iter() + .position(|typ| *typ.name == arg.name.0.contents) + .unwrap_or_else(|| unreachable!("Expected to find associated type named {}", arg.name)); + + let param = params.swap_remove(i); + bind_generic(¶m, &arg.typ, bindings); + } +} + +fn bind_generic(param: &ResolvedGeneric, arg: &Type, bindings: &mut TypeBindings) { + // Avoid binding t = t + if !arg.occurs(param.type_var.id()) { + bindings.insert(param.type_var.id(), (param.type_var.clone(), param.kind(), arg.clone())); + } } pub fn try_eval_array_length_id( diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/display.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/display.rs index fa45c41f8ec..60661211a09 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/display.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/display.rs @@ -509,8 +509,11 @@ impl<'token, 'interner> Display for TokenPrinter<'token, 'interner> { } fn display_trait_constraint(interner: &NodeInterner, trait_constraint: &TraitConstraint) -> String { - let trait_ = interner.get_trait(trait_constraint.trait_id); - format!("{}: {}{}", trait_constraint.typ, trait_.name, trait_constraint.trait_generics) + let trait_ = interner.get_trait(trait_constraint.trait_bound.trait_id); + format!( + "{}: {}{}", + trait_constraint.typ, trait_.name, trait_constraint.trait_bound.trait_generics + ) } // Returns a new Expression where all Interned and Resolved expressions have been turned into non-interned ExpressionKind. diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs index 3542c724b30..97d90b905d4 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs @@ -417,7 +417,7 @@ impl HirArrayLiteral { let repeated_element = Box::new(repeated_element.to_display_ast(interner)); let length = match length { Type::Constant(length, _kind) => { - let literal = Literal::Integer((*length as u128).into(), false); + let literal = Literal::Integer(*length, false); let expr_kind = ExpressionKind::Literal(literal); Box::new(Expression::new(expr_kind, span)) } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter.rs index 1690295ffda..ffb759e74a2 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter.rs @@ -586,12 +586,14 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { DefinitionKind::NumericGeneric(type_variable, numeric_typ) => { let value = match &*type_variable.borrow() { TypeBinding::Unbound(_, _) => None, - TypeBinding::Bound(binding) => binding.evaluate_to_u32(), + TypeBinding::Bound(binding) => { + binding.evaluate_to_field_element(&Kind::Numeric(numeric_typ.clone())) + } }; if let Some(value) = value { let typ = self.elaborator.interner.id_type(id); - self.evaluate_integer((value as u128).into(), false, id) + self.evaluate_integer(value, false, id) } else { let location = self.elaborator.interner.expr_location(&id); let typ = Type::TypeVariable(type_variable.clone()); @@ -895,71 +897,138 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { } fn evaluate_infix(&mut self, infix: HirInfixExpression, id: ExprId) -> IResult { - let lhs = self.evaluate(infix.lhs)?; - let rhs = self.evaluate(infix.rhs)?; + let lhs_value = self.evaluate(infix.lhs)?; + let rhs_value = self.evaluate(infix.rhs)?; if self.elaborator.interner.get_selected_impl_for_expression(id).is_some() { - return self.evaluate_overloaded_infix(infix, lhs, rhs, id); + return self.evaluate_overloaded_infix(infix, lhs_value, rhs_value, id); } - let make_error = |this: &mut Self, lhs: Value, rhs: Value, operator| { - let location = this.elaborator.interner.expr_location(&id); - let lhs = lhs.get_type().into_owned(); - let rhs = rhs.get_type().into_owned(); - Err(InvalidValuesForBinary { lhs, rhs, location, operator }) + let lhs_type = lhs_value.get_type().into_owned(); + let rhs_type = rhs_value.get_type().into_owned(); + let location = self.elaborator.interner.expr_location(&id); + + let error = |operator| { + let lhs = lhs_type.clone(); + let rhs = rhs_type.clone(); + InterpreterError::InvalidValuesForBinary { lhs, rhs, location, operator } }; use InterpreterError::InvalidValuesForBinary; match infix.operator.kind { - BinaryOpKind::Add => match (lhs, rhs) { + BinaryOpKind::Add => match (lhs_value.clone(), rhs_value.clone()) { (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Field(lhs + rhs)), - (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::I8(lhs + rhs)), - (Value::I16(lhs), Value::I16(rhs)) => Ok(Value::I16(lhs + rhs)), - (Value::I32(lhs), Value::I32(rhs)) => Ok(Value::I32(lhs + rhs)), - (Value::I64(lhs), Value::I64(rhs)) => Ok(Value::I64(lhs + rhs)), - (Value::U8(lhs), Value::U8(rhs)) => Ok(Value::U8(lhs + rhs)), - (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::U16(lhs + rhs)), - (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs + rhs)), - (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs + rhs)), - (lhs, rhs) => make_error(self, lhs, rhs, "+"), + (Value::I8(lhs), Value::I8(rhs)) => { + Ok(Value::I8(lhs.checked_add(rhs).ok_or(error("+"))?)) + } + (Value::I16(lhs), Value::I16(rhs)) => { + Ok(Value::I16(lhs.checked_add(rhs).ok_or(error("+"))?)) + } + (Value::I32(lhs), Value::I32(rhs)) => { + Ok(Value::I32(lhs.checked_add(rhs).ok_or(error("+"))?)) + } + (Value::I64(lhs), Value::I64(rhs)) => { + Ok(Value::I64(lhs.checked_add(rhs).ok_or(error("+"))?)) + } + (Value::U8(lhs), Value::U8(rhs)) => { + Ok(Value::U8(lhs.checked_add(rhs).ok_or(error("+"))?)) + } + (Value::U16(lhs), Value::U16(rhs)) => { + Ok(Value::U16(lhs.checked_add(rhs).ok_or(error("+"))?)) + } + (Value::U32(lhs), Value::U32(rhs)) => { + Ok(Value::U32(lhs.checked_add(rhs).ok_or(error("+"))?)) + } + (Value::U64(lhs), Value::U64(rhs)) => { + Ok(Value::U64(lhs.checked_add(rhs).ok_or(error("+"))?)) + } + (lhs, rhs) => Err(error("+")), }, - BinaryOpKind::Subtract => match (lhs, rhs) { + BinaryOpKind::Subtract => match (lhs_value.clone(), rhs_value.clone()) { (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Field(lhs - rhs)), - (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::I8(lhs - rhs)), - (Value::I16(lhs), Value::I16(rhs)) => Ok(Value::I16(lhs - rhs)), - (Value::I32(lhs), Value::I32(rhs)) => Ok(Value::I32(lhs - rhs)), - (Value::I64(lhs), Value::I64(rhs)) => Ok(Value::I64(lhs - rhs)), - (Value::U8(lhs), Value::U8(rhs)) => Ok(Value::U8(lhs - rhs)), - (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::U16(lhs - rhs)), - (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs - rhs)), - (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs - rhs)), - (lhs, rhs) => make_error(self, lhs, rhs, "-"), + (Value::I8(lhs), Value::I8(rhs)) => { + Ok(Value::I8(lhs.checked_sub(rhs).ok_or(error("-"))?)) + } + (Value::I16(lhs), Value::I16(rhs)) => { + Ok(Value::I16(lhs.checked_sub(rhs).ok_or(error("-"))?)) + } + (Value::I32(lhs), Value::I32(rhs)) => { + Ok(Value::I32(lhs.checked_sub(rhs).ok_or(error("-"))?)) + } + (Value::I64(lhs), Value::I64(rhs)) => { + Ok(Value::I64(lhs.checked_sub(rhs).ok_or(error("-"))?)) + } + (Value::U8(lhs), Value::U8(rhs)) => { + Ok(Value::U8(lhs.checked_sub(rhs).ok_or(error("-"))?)) + } + (Value::U16(lhs), Value::U16(rhs)) => { + Ok(Value::U16(lhs.checked_sub(rhs).ok_or(error("-"))?)) + } + (Value::U32(lhs), Value::U32(rhs)) => { + Ok(Value::U32(lhs.checked_sub(rhs).ok_or(error("-"))?)) + } + (Value::U64(lhs), Value::U64(rhs)) => { + Ok(Value::U64(lhs.checked_sub(rhs).ok_or(error("-"))?)) + } + (lhs, rhs) => Err(error("-")), }, - BinaryOpKind::Multiply => match (lhs, rhs) { + BinaryOpKind::Multiply => match (lhs_value.clone(), rhs_value.clone()) { (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Field(lhs * rhs)), - (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::I8(lhs * rhs)), - (Value::I16(lhs), Value::I16(rhs)) => Ok(Value::I16(lhs * rhs)), - (Value::I32(lhs), Value::I32(rhs)) => Ok(Value::I32(lhs * rhs)), - (Value::I64(lhs), Value::I64(rhs)) => Ok(Value::I64(lhs * rhs)), - (Value::U8(lhs), Value::U8(rhs)) => Ok(Value::U8(lhs * rhs)), - (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::U16(lhs * rhs)), - (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs * rhs)), - (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs * rhs)), - (lhs, rhs) => make_error(self, lhs, rhs, "*"), + (Value::I8(lhs), Value::I8(rhs)) => { + Ok(Value::I8(lhs.checked_mul(rhs).ok_or(error("*"))?)) + } + (Value::I16(lhs), Value::I16(rhs)) => { + Ok(Value::I16(lhs.checked_mul(rhs).ok_or(error("*"))?)) + } + (Value::I32(lhs), Value::I32(rhs)) => { + Ok(Value::I32(lhs.checked_mul(rhs).ok_or(error("*"))?)) + } + (Value::I64(lhs), Value::I64(rhs)) => { + Ok(Value::I64(lhs.checked_mul(rhs).ok_or(error("*"))?)) + } + (Value::U8(lhs), Value::U8(rhs)) => { + Ok(Value::U8(lhs.checked_mul(rhs).ok_or(error("*"))?)) + } + (Value::U16(lhs), Value::U16(rhs)) => { + Ok(Value::U16(lhs.checked_mul(rhs).ok_or(error("*"))?)) + } + (Value::U32(lhs), Value::U32(rhs)) => { + Ok(Value::U32(lhs.checked_mul(rhs).ok_or(error("*"))?)) + } + (Value::U64(lhs), Value::U64(rhs)) => { + Ok(Value::U64(lhs.checked_mul(rhs).ok_or(error("*"))?)) + } + (lhs, rhs) => Err(error("*")), }, - BinaryOpKind::Divide => match (lhs, rhs) { + BinaryOpKind::Divide => match (lhs_value.clone(), rhs_value.clone()) { (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Field(lhs / rhs)), - (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::I8(lhs / rhs)), - (Value::I16(lhs), Value::I16(rhs)) => Ok(Value::I16(lhs / rhs)), - (Value::I32(lhs), Value::I32(rhs)) => Ok(Value::I32(lhs / rhs)), - (Value::I64(lhs), Value::I64(rhs)) => Ok(Value::I64(lhs / rhs)), - (Value::U8(lhs), Value::U8(rhs)) => Ok(Value::U8(lhs / rhs)), - (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::U16(lhs / rhs)), - (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs / rhs)), - (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs / rhs)), - (lhs, rhs) => make_error(self, lhs, rhs, "/"), + (Value::I8(lhs), Value::I8(rhs)) => { + Ok(Value::I8(lhs.checked_div(rhs).ok_or(error("/"))?)) + } + (Value::I16(lhs), Value::I16(rhs)) => { + Ok(Value::I16(lhs.checked_div(rhs).ok_or(error("/"))?)) + } + (Value::I32(lhs), Value::I32(rhs)) => { + Ok(Value::I32(lhs.checked_div(rhs).ok_or(error("/"))?)) + } + (Value::I64(lhs), Value::I64(rhs)) => { + Ok(Value::I64(lhs.checked_div(rhs).ok_or(error("/"))?)) + } + (Value::U8(lhs), Value::U8(rhs)) => { + Ok(Value::U8(lhs.checked_div(rhs).ok_or(error("/"))?)) + } + (Value::U16(lhs), Value::U16(rhs)) => { + Ok(Value::U16(lhs.checked_div(rhs).ok_or(error("/"))?)) + } + (Value::U32(lhs), Value::U32(rhs)) => { + Ok(Value::U32(lhs.checked_div(rhs).ok_or(error("/"))?)) + } + (Value::U64(lhs), Value::U64(rhs)) => { + Ok(Value::U64(lhs.checked_div(rhs).ok_or(error("/"))?)) + } + (lhs, rhs) => Err(error("/")), }, - BinaryOpKind::Equal => match (lhs, rhs) { + BinaryOpKind::Equal => match (lhs_value.clone(), rhs_value.clone()) { (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Bool(lhs == rhs)), (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::Bool(lhs == rhs)), (Value::I16(lhs), Value::I16(rhs)) => Ok(Value::Bool(lhs == rhs)), @@ -970,9 +1039,9 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::Bool(lhs == rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::Bool(lhs == rhs)), (Value::Bool(lhs), Value::Bool(rhs)) => Ok(Value::Bool(lhs == rhs)), - (lhs, rhs) => make_error(self, lhs, rhs, "=="), + (lhs, rhs) => Err(error("==")), }, - BinaryOpKind::NotEqual => match (lhs, rhs) { + BinaryOpKind::NotEqual => match (lhs_value.clone(), rhs_value.clone()) { (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Bool(lhs != rhs)), (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::Bool(lhs != rhs)), (Value::I16(lhs), Value::I16(rhs)) => Ok(Value::Bool(lhs != rhs)), @@ -983,9 +1052,9 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::Bool(lhs != rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::Bool(lhs != rhs)), (Value::Bool(lhs), Value::Bool(rhs)) => Ok(Value::Bool(lhs != rhs)), - (lhs, rhs) => make_error(self, lhs, rhs, "!="), + (lhs, rhs) => Err(error("!=")), }, - BinaryOpKind::Less => match (lhs, rhs) { + BinaryOpKind::Less => match (lhs_value.clone(), rhs_value.clone()) { (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Bool(lhs < rhs)), (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::Bool(lhs < rhs)), (Value::I16(lhs), Value::I16(rhs)) => Ok(Value::Bool(lhs < rhs)), @@ -995,9 +1064,9 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::Bool(lhs < rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::Bool(lhs < rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::Bool(lhs < rhs)), - (lhs, rhs) => make_error(self, lhs, rhs, "<"), + (lhs, rhs) => Err(error("<")), }, - BinaryOpKind::LessEqual => match (lhs, rhs) { + BinaryOpKind::LessEqual => match (lhs_value.clone(), rhs_value.clone()) { (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Bool(lhs <= rhs)), (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::Bool(lhs <= rhs)), (Value::I16(lhs), Value::I16(rhs)) => Ok(Value::Bool(lhs <= rhs)), @@ -1007,9 +1076,9 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::Bool(lhs <= rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::Bool(lhs <= rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::Bool(lhs <= rhs)), - (lhs, rhs) => make_error(self, lhs, rhs, "<="), + (lhs, rhs) => Err(error("<=")), }, - BinaryOpKind::Greater => match (lhs, rhs) { + BinaryOpKind::Greater => match (lhs_value.clone(), rhs_value.clone()) { (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Bool(lhs > rhs)), (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::Bool(lhs > rhs)), (Value::I16(lhs), Value::I16(rhs)) => Ok(Value::Bool(lhs > rhs)), @@ -1019,9 +1088,9 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::Bool(lhs > rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::Bool(lhs > rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::Bool(lhs > rhs)), - (lhs, rhs) => make_error(self, lhs, rhs, ">"), + (lhs, rhs) => Err(error(">")), }, - BinaryOpKind::GreaterEqual => match (lhs, rhs) { + BinaryOpKind::GreaterEqual => match (lhs_value.clone(), rhs_value.clone()) { (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Bool(lhs >= rhs)), (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::Bool(lhs >= rhs)), (Value::I16(lhs), Value::I16(rhs)) => Ok(Value::Bool(lhs >= rhs)), @@ -1031,9 +1100,9 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::Bool(lhs >= rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::Bool(lhs >= rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::Bool(lhs >= rhs)), - (lhs, rhs) => make_error(self, lhs, rhs, ">="), + (lhs, rhs) => Err(error(">=")), }, - BinaryOpKind::And => match (lhs, rhs) { + BinaryOpKind::And => match (lhs_value.clone(), rhs_value.clone()) { (Value::Bool(lhs), Value::Bool(rhs)) => Ok(Value::Bool(lhs & rhs)), (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::I8(lhs & rhs)), (Value::I16(lhs), Value::I16(rhs)) => Ok(Value::I16(lhs & rhs)), @@ -1043,9 +1112,9 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::U16(lhs & rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs & rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs & rhs)), - (lhs, rhs) => make_error(self, lhs, rhs, "&"), + (lhs, rhs) => Err(error("&")), }, - BinaryOpKind::Or => match (lhs, rhs) { + BinaryOpKind::Or => match (lhs_value.clone(), rhs_value.clone()) { (Value::Bool(lhs), Value::Bool(rhs)) => Ok(Value::Bool(lhs | rhs)), (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::I8(lhs | rhs)), (Value::I16(lhs), Value::I16(rhs)) => Ok(Value::I16(lhs | rhs)), @@ -1055,9 +1124,9 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::U16(lhs | rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs | rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs | rhs)), - (lhs, rhs) => make_error(self, lhs, rhs, "|"), + (lhs, rhs) => Err(error("|")), }, - BinaryOpKind::Xor => match (lhs, rhs) { + BinaryOpKind::Xor => match (lhs_value.clone(), rhs_value.clone()) { (Value::Bool(lhs), Value::Bool(rhs)) => Ok(Value::Bool(lhs ^ rhs)), (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::I8(lhs ^ rhs)), (Value::I16(lhs), Value::I16(rhs)) => Ok(Value::I16(lhs ^ rhs)), @@ -1067,40 +1136,88 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::U16(lhs ^ rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs ^ rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs ^ rhs)), - (lhs, rhs) => make_error(self, lhs, rhs, "^"), + (lhs, rhs) => Err(error("^")), }, - BinaryOpKind::ShiftRight => match (lhs, rhs) { - (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::I8(lhs >> rhs)), - (Value::I16(lhs), Value::I16(rhs)) => Ok(Value::I16(lhs >> rhs)), - (Value::I32(lhs), Value::I32(rhs)) => Ok(Value::I32(lhs >> rhs)), - (Value::I64(lhs), Value::I64(rhs)) => Ok(Value::I64(lhs >> rhs)), - (Value::U8(lhs), Value::U8(rhs)) => Ok(Value::U8(lhs >> rhs)), - (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::U16(lhs >> rhs)), - (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs >> rhs)), - (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs >> rhs)), - (lhs, rhs) => make_error(self, lhs, rhs, ">>"), + BinaryOpKind::ShiftRight => match (lhs_value.clone(), rhs_value.clone()) { + (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::I8( + lhs.checked_shr(rhs.try_into().map_err(|_| error(">>"))?).ok_or(error(">>"))?, + )), + (Value::I16(lhs), Value::I16(rhs)) => Ok(Value::I16( + lhs.checked_shr(rhs.try_into().map_err(|_| error(">>"))?).ok_or(error(">>"))?, + )), + (Value::I32(lhs), Value::I32(rhs)) => Ok(Value::I32( + lhs.checked_shr(rhs.try_into().map_err(|_| error(">>"))?).ok_or(error(">>"))?, + )), + (Value::I64(lhs), Value::I64(rhs)) => Ok(Value::I64( + lhs.checked_shr(rhs.try_into().map_err(|_| error(">>"))?).ok_or(error(">>"))?, + )), + (Value::U8(lhs), Value::U8(rhs)) => { + Ok(Value::U8(lhs.checked_shr(rhs.into()).ok_or(error(">>"))?)) + } + (Value::U16(lhs), Value::U16(rhs)) => { + Ok(Value::U16(lhs.checked_shr(rhs.into()).ok_or(error(">>"))?)) + } + (Value::U32(lhs), Value::U32(rhs)) => { + Ok(Value::U32(lhs.checked_shr(rhs).ok_or(error(">>"))?)) + } + (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64( + lhs.checked_shr(rhs.try_into().map_err(|_| error(">>"))?).ok_or(error(">>"))?, + )), + (lhs, rhs) => Err(error(">>")), }, - BinaryOpKind::ShiftLeft => match (lhs, rhs) { - (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::I8(lhs << rhs)), - (Value::I16(lhs), Value::I16(rhs)) => Ok(Value::I16(lhs << rhs)), - (Value::I32(lhs), Value::I32(rhs)) => Ok(Value::I32(lhs << rhs)), - (Value::I64(lhs), Value::I64(rhs)) => Ok(Value::I64(lhs << rhs)), - (Value::U8(lhs), Value::U8(rhs)) => Ok(Value::U8(lhs << rhs)), - (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::U16(lhs << rhs)), - (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs << rhs)), - (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs << rhs)), - (lhs, rhs) => make_error(self, lhs, rhs, "<<"), + BinaryOpKind::ShiftLeft => match (lhs_value.clone(), rhs_value.clone()) { + (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::I8( + lhs.checked_shl(rhs.try_into().map_err(|_| error("<<"))?).ok_or(error("<<"))?, + )), + (Value::I16(lhs), Value::I16(rhs)) => Ok(Value::I16( + lhs.checked_shl(rhs.try_into().map_err(|_| error("<<"))?).ok_or(error("<<"))?, + )), + (Value::I32(lhs), Value::I32(rhs)) => Ok(Value::I32( + lhs.checked_shl(rhs.try_into().map_err(|_| error("<<"))?).ok_or(error("<<"))?, + )), + (Value::I64(lhs), Value::I64(rhs)) => Ok(Value::I64( + lhs.checked_shl(rhs.try_into().map_err(|_| error("<<"))?).ok_or(error("<<"))?, + )), + (Value::U8(lhs), Value::U8(rhs)) => { + Ok(Value::U8(lhs.checked_shl(rhs.into()).ok_or(error("<<"))?)) + } + (Value::U16(lhs), Value::U16(rhs)) => { + Ok(Value::U16(lhs.checked_shl(rhs.into()).ok_or(error("<<"))?)) + } + (Value::U32(lhs), Value::U32(rhs)) => { + Ok(Value::U32(lhs.checked_shl(rhs).ok_or(error("<<"))?)) + } + (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64( + lhs.checked_shl(rhs.try_into().map_err(|_| error("<<"))?).ok_or(error("<<"))?, + )), + (lhs, rhs) => Err(error("<<")), }, - BinaryOpKind::Modulo => match (lhs, rhs) { - (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::I8(lhs % rhs)), - (Value::I16(lhs), Value::I16(rhs)) => Ok(Value::I16(lhs % rhs)), - (Value::I32(lhs), Value::I32(rhs)) => Ok(Value::I32(lhs % rhs)), - (Value::I64(lhs), Value::I64(rhs)) => Ok(Value::I64(lhs % rhs)), - (Value::U8(lhs), Value::U8(rhs)) => Ok(Value::U8(lhs % rhs)), - (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::U16(lhs % rhs)), - (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs % rhs)), - (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs % rhs)), - (lhs, rhs) => make_error(self, lhs, rhs, "%"), + BinaryOpKind::Modulo => match (lhs_value.clone(), rhs_value.clone()) { + (Value::I8(lhs), Value::I8(rhs)) => { + Ok(Value::I8(lhs.checked_rem(rhs).ok_or(error("%"))?)) + } + (Value::I16(lhs), Value::I16(rhs)) => { + Ok(Value::I16(lhs.checked_rem(rhs).ok_or(error("%"))?)) + } + (Value::I32(lhs), Value::I32(rhs)) => { + Ok(Value::I32(lhs.checked_rem(rhs).ok_or(error("%"))?)) + } + (Value::I64(lhs), Value::I64(rhs)) => { + Ok(Value::I64(lhs.checked_rem(rhs).ok_or(error("%"))?)) + } + (Value::U8(lhs), Value::U8(rhs)) => { + Ok(Value::U8(lhs.checked_rem(rhs).ok_or(error("%"))?)) + } + (Value::U16(lhs), Value::U16(rhs)) => { + Ok(Value::U16(lhs.checked_rem(rhs).ok_or(error("%"))?)) + } + (Value::U32(lhs), Value::U32(rhs)) => { + Ok(Value::U32(lhs.checked_rem(rhs).ok_or(error("%"))?)) + } + (Value::U64(lhs), Value::U64(rhs)) => { + Ok(Value::U64(lhs.checked_rem(rhs).ok_or(error("%"))?)) + } + (lhs, rhs) => Err(error("%")), }, } } @@ -1638,7 +1755,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { Value::Pointer(elem, true) => Ok(elem.borrow().clone()), other => Ok(other), }, - HirLValue::Dereference { lvalue, element_type: _, location } => { + HirLValue::Dereference { lvalue, element_type, location } => { match self.evaluate_lvalue(lvalue)? { Value::Pointer(value, _) => Ok(value.borrow().clone()), value => { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index 495b1f0276c..15ceffa3e97 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -19,8 +19,8 @@ use rustc_hash::FxHashMap as HashMap; use crate::{ ast::{ ArrayLiteral, BlockExpression, ConstrainKind, Expression, ExpressionKind, ForRange, - FunctionKind, FunctionReturnType, Ident, IntegerBitSize, LValue, Literal, Pattern, - Signedness, Statement, StatementKind, UnaryOp, UnresolvedType, UnresolvedTypeData, + FunctionKind, FunctionReturnType, Ident, IntegerBitSize, ItemVisibility, LValue, Literal, + Pattern, Signedness, Statement, StatementKind, UnaryOp, UnresolvedType, UnresolvedTypeData, Visibility, }, hir::{ @@ -34,8 +34,9 @@ use crate::{ }, hir_def::expr::{HirExpression, HirLiteral}, hir_def::function::FunctionBody, + hir_def::{self}, node_interner::{DefinitionKind, NodeInterner, TraitImplKind}, - parser::Parser, + parser::{Parser, StatementOrExpressionOrLValue}, token::{Attribute, SecondaryAttribute, Token}, Kind, QuotedType, ResolvedGeneric, Shared, Type, TypeVariable, }; @@ -317,7 +318,7 @@ fn str_as_bytes( let bytes: im::Vector = string.bytes().map(Value::U8).collect(); let byte_array_type = Type::Array( - Box::new(Type::Constant(bytes.len() as u32, Kind::u32())), + Box::new(Type::Constant(bytes.len().into(), Kind::u32())), Box::new(Type::Integer(Signedness::Unsigned, IntegerBitSize::Eight)), ); Ok(Value::Array(bytes, byte_array_type)) @@ -499,9 +500,9 @@ fn struct_def_fields( let mut fields = im::Vector::new(); - for (name, typ) in struct_def.get_fields_as_written() { - let name = Value::Quoted(Rc::new(vec![Token::Ident(name)])); - let typ = Value::Type(typ); + for field in struct_def.get_fields_as_written() { + let name = Value::Quoted(Rc::new(vec![Token::Ident(field.name.to_string())])); + let typ = Value::Type(field.typ); fields.push_back(Value::Tuple(vec![name, typ])); } @@ -568,7 +569,11 @@ fn struct_def_set_fields( match name_tokens.first() { Some(Token::Ident(name)) if name_tokens.len() == 1 => { - Ok((Ident::new(name.clone(), field_location.span), typ)) + Ok(hir_def::types::StructField { + visibility: ItemVisibility::Public, + name: Ident::new(name.clone(), field_location.span), + typ, + }) } _ => { let value = name_value.display(interner).to_string(); @@ -688,23 +693,20 @@ fn quoted_as_expr( let argument = check_one_argument(arguments, location)?; let result = - parse(interner, argument.clone(), Parser::parse_expression_or_error, "an expression"); - if let Ok(expr) = result { - return option(return_type, Some(Value::expression(expr.kind))); - } - - let result = - parse(interner, argument.clone(), Parser::parse_statement_or_error, "an expression"); - if let Ok(stmt) = result { - return option(return_type, Some(Value::statement(stmt.kind))); - } - - let result = parse(interner, argument, Parser::parse_lvalue_or_error, "an expression"); - if let Ok(lvalue) = result { - return option(return_type, Some(Value::lvalue(lvalue))); - } + parse(interner, argument, Parser::parse_statement_or_expression_or_lvalue, "an expression"); + + let value = + result.ok().map( + |statement_or_expression_or_lvalue| match statement_or_expression_or_lvalue { + StatementOrExpressionOrLValue::Expression(expr) => Value::expression(expr.kind), + StatementOrExpressionOrLValue::Statement(statement) => { + Value::statement(statement.kind) + } + StatementOrExpressionOrLValue::LValue(lvalue) => Value::lvalue(lvalue), + }, + ); - option(return_type, None) + option(return_type, value) } // fn as_module(quoted: Quoted) -> Option @@ -749,7 +751,7 @@ fn quoted_as_trait_constraint( )?; let bound = interpreter .elaborate_in_function(interpreter.current_function, |elaborator| { - elaborator.resolve_trait_bound(&trait_bound, Type::Unit) + elaborator.resolve_trait_bound(&trait_bound) }) .ok_or(InterpreterError::FailedToResolveTraitBound { trait_bound, location })?; @@ -806,8 +808,12 @@ fn to_le_radix( let value = get_field(value)?; let radix = get_u32(radix)?; let limb_count = if let Type::Array(length, _) = return_type { - if let Type::Constant(limb_count, _kind) = *length { - limb_count + if let Type::Constant(limb_count, kind) = *length { + if kind.unifies(&Kind::u32()) { + limb_count + } else { + return Err(InterpreterError::TypeAnnotationsNeededForMethodCall { location }); + } } else { return Err(InterpreterError::TypeAnnotationsNeededForMethodCall { location }); } @@ -817,10 +823,11 @@ fn to_le_radix( // Decompose the integer into its radix digits in little endian form. let decomposed_integer = compute_to_radix_le(value, radix); - let decomposed_integer = vecmap(0..limb_count as usize, |i| match decomposed_integer.get(i) { - Some(digit) => Value::U8(*digit), - None => Value::U8(0), - }); + let decomposed_integer = + vecmap(0..limb_count.to_u128() as usize, |i| match decomposed_integer.get(i) { + Some(digit) => Value::U8(*digit), + None => Value::U8(0), + }); Ok(Value::Array( decomposed_integer.into(), Type::Integer(Signedness::Unsigned, IntegerBitSize::Eight), @@ -2245,7 +2252,7 @@ fn function_def_add_attribute( } } - if let Attribute::Secondary(SecondaryAttribute::Custom(attribute)) = attribute { + if let Attribute::Secondary(SecondaryAttribute::Tag(attribute)) = attribute { let func_meta = interpreter.elaborator.interner.function_meta_mut(&func_id); func_meta.custom_attributes.push(attribute); } @@ -2458,7 +2465,6 @@ fn function_def_set_parameters( DefinitionKind::Local(None), &mut parameter_idents, true, // warn_if_unused - None, ) }); @@ -2746,7 +2752,7 @@ fn trait_def_as_trait_constraint( let trait_id = get_trait_def(argument)?; let constraint = interner.get_trait(trait_id).as_constraint(location.span); - Ok(Value::TraitConstraint(trait_id, constraint.trait_generics)) + Ok(Value::TraitConstraint(trait_id, constraint.trait_bound.trait_generics)) } /// Creates a value that holds an `Option`. diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/tests.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/tests.rs index 458a186a3f8..e033ec6ddb9 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/tests.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/tests.rs @@ -78,6 +78,23 @@ fn interpreter_works() { assert_eq!(result, Value::Field(3u128.into())); } +#[test] +fn interpreter_type_checking_works() { + let program = "comptime fn main() -> pub u8 { 3 }"; + let result = interpret(program); + assert_eq!(result, Value::U8(3u8)); +} + +#[test] +fn let_statement_works() { + let program = "comptime fn main() -> pub i8 { + let x = 4; + x + }"; + let result = interpret(program); + assert_eq!(result, Value::I8(4)); +} + #[test] fn mutation_works() { let program = "comptime fn main() -> pub i8 { @@ -277,5 +294,5 @@ fn generic_functions() { } "; let result = interpret(program); - assert!(matches!(result, Value::U8(2))); + assert_eq!(result, Value::U8(2)); } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/value.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/value.rs index 4b55735fcb1..945fb45026d 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/value.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/value.rs @@ -120,7 +120,7 @@ impl Value { Value::U32(_) => Type::Integer(Signedness::Unsigned, IntegerBitSize::ThirtyTwo), Value::U64(_) => Type::Integer(Signedness::Unsigned, IntegerBitSize::SixtyFour), Value::String(value) => { - let length = Type::Constant(value.len() as u32, Kind::u32()); + let length = Type::Constant(value.len().into(), Kind::u32()); Type::String(Box::new(length)) } Value::FormatString(_, typ) => return Cow::Borrowed(typ), diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs index ba1075dfb30..b9ce8f361f7 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs @@ -1013,12 +1013,14 @@ pub fn collect_struct( let parent_module_id = ModuleId { krate, local_id: module_id }; - interner.usage_tracker.add_unused_item( - parent_module_id, - name.clone(), - UnusedItem::Struct(id), - visibility, - ); + if !unresolved.struct_def.is_abi() { + interner.usage_tracker.add_unused_item( + parent_module_id, + name.clone(), + UnusedItem::Struct(id), + visibility, + ); + } if let Err((first_def, second_def)) = result { let error = DefCollectorErrorKind::Duplicate { @@ -1201,6 +1203,7 @@ pub(crate) fn collect_global( let global = global.item; let name = global.pattern.name_ident().clone(); + let is_abi = global.attributes.iter().any(|attribute| attribute.is_abi()); let global_id = interner.push_empty_global( name.clone(), @@ -1215,13 +1218,15 @@ pub(crate) fn collect_global( // Add the statement to the scope so its path can be looked up later let result = def_map.modules[module_id.0].declare_global(name.clone(), visibility, global_id); - let parent_module_id = ModuleId { krate: crate_id, local_id: module_id }; - interner.usage_tracker.add_unused_item( - parent_module_id, - name, - UnusedItem::Global(global_id), - visibility, - ); + if !is_abi { + let parent_module_id = ModuleId { krate: crate_id, local_id: module_id }; + interner.usage_tracker.add_unused_item( + parent_module_id, + name, + UnusedItem::Global(global_id), + visibility, + ); + } let error = result.err().map(|(first_def, second_def)| { let err = diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/errors.rs index 4e9520ad761..4f9907d6a16 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/errors.rs @@ -1,5 +1,6 @@ +use acvm::FieldElement; pub use noirc_errors::Span; -use noirc_errors::{CustomDiagnostic as Diagnostic, FileDiagnostic}; +use noirc_errors::{CustomDiagnostic as Diagnostic, FileDiagnostic, Location}; use thiserror::Error; use crate::{ @@ -125,7 +126,12 @@ pub enum ResolverError { #[error("Associated constants may only be a field or integer type")] AssociatedConstantsMustBeNumeric { span: Span }, #[error("Overflow in `{lhs} {op} {rhs}`")] - OverflowInType { lhs: u32, op: crate::BinaryTypeOperator, rhs: u32, span: Span }, + OverflowInType { + lhs: FieldElement, + op: crate::BinaryTypeOperator, + rhs: FieldElement, + span: Span, + }, #[error("`quote` cannot be used in runtime code")] QuoteInRuntimeCode { span: Span }, #[error("Comptime-only type `{typ}` cannot be used in runtime code")] @@ -138,6 +144,20 @@ pub enum ResolverError { UnsupportedNumericGenericType(#[from] UnsupportedNumericGenericType), #[error("Type `{typ}` is more private than item `{item}`")] TypeIsMorePrivateThenItem { typ: String, item: String, span: Span }, + #[error("Unable to parse attribute `{attribute}`")] + UnableToParseAttribute { attribute: String, span: Span }, + #[error("Attribute function `{function}` is not a path")] + AttributeFunctionIsNotAPath { function: String, span: Span }, + #[error("Attribute function `{name}` is not in scope")] + AttributeFunctionNotInScope { name: String, span: Span }, + #[error("The trait `{missing_trait}` is not implemented for `{type_missing_trait}")] + TraitNotImplemented { + impl_trait: String, + missing_trait: String, + type_missing_trait: String, + span: Span, + missing_trait_location: Location, + }, } impl ResolverError { @@ -546,6 +566,35 @@ impl<'a> From<&'a ResolverError> for Diagnostic { *span, ) }, + ResolverError::UnableToParseAttribute { attribute, span } => { + Diagnostic::simple_error( + format!("Unable to parse attribute `{attribute}`"), + "Attribute should be a function or function call".into(), + *span, + ) + }, + ResolverError::AttributeFunctionIsNotAPath { function, span } => { + Diagnostic::simple_error( + format!("Attribute function `{function}` is not a path"), + "An attribute's function should be a single identifier or a path".into(), + *span, + ) + }, + ResolverError::AttributeFunctionNotInScope { name, span } => { + Diagnostic::simple_error( + format!("Attribute function `{name}` is not in scope"), + String::new(), + *span, + ) + }, + ResolverError::TraitNotImplemented { impl_trait, missing_trait: the_trait, type_missing_trait: typ, span, missing_trait_location} => { + let mut diagnostic = Diagnostic::simple_error( + format!("The trait bound `{typ}: {the_trait}` is not satisfied"), + format!("The trait `{the_trait}` is not implemented for `{typ}") + , *span); + diagnostic.add_secondary_with_file(format!("required by this bound in `{impl_trait}"), missing_trait_location.span, missing_trait_location.file); + diagnostic + }, } } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/import.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/import.rs index f0b7f941ef8..93039b1ea7f 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/import.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/import.rs @@ -12,6 +12,7 @@ use crate::ast::{Ident, ItemVisibility, Path, PathKind, PathSegment}; use crate::hir::def_map::{CrateDefMap, LocalModuleId, ModuleDefId, ModuleId, PerNs}; use super::errors::ResolverError; +use super::visibility::can_reference_module_id; #[derive(Debug, Clone)] pub struct ImportDirective { @@ -169,7 +170,7 @@ fn resolve_path_to_ns( import_path, import_directive.module_id, def_maps, - true, + true, // plain or crate usage_tracker, path_references, ); @@ -199,7 +200,7 @@ fn resolve_path_to_ns( import_path, import_directive.module_id, def_maps, - true, + true, // plain or crate usage_tracker, path_references, ) @@ -224,7 +225,7 @@ fn resolve_path_to_ns( import_path, parent_module_id, def_maps, - false, + false, // plain or crate usage_tracker, path_references, ) @@ -253,7 +254,7 @@ fn resolve_path_from_crate_root( import_path, starting_mod, def_maps, - false, + true, // plain or crate usage_tracker, path_references, ) @@ -266,7 +267,7 @@ fn resolve_name_in_module( import_path: &[PathSegment], starting_mod: LocalModuleId, def_maps: &BTreeMap, - plain: bool, + plain_or_crate: bool, usage_tracker: &mut UsageTracker, path_references: &mut Option<&mut Vec>, ) -> NamespaceResolutionResult { @@ -331,9 +332,9 @@ fn resolve_name_in_module( }; warning = warning.or_else(|| { - // If the path is plain, the first segment will always refer to + // If the path is plain or crate, the first segment will always refer to // something that's visible from the current module. - if (plain && index == 0) + if (plain_or_crate && index == 0) || can_reference_module_id( def_maps, importing_crate, @@ -424,61 +425,3 @@ fn resolve_external_dep( path_references, ) } - -// Returns false if the given private function is being called from a non-child module, or -// if the given pub(crate) function is being called from another crate. Otherwise returns true. -pub fn can_reference_module_id( - def_maps: &BTreeMap, - importing_crate: CrateId, - current_module: LocalModuleId, - target_module: ModuleId, - visibility: ItemVisibility, -) -> bool { - // Note that if the target module is in a different crate from the current module then we will either - // return true as the target module is public or return false as it is private without looking at the `CrateDefMap` in either case. - let same_crate = target_module.krate == importing_crate; - let target_crate_def_map = &def_maps[&target_module.krate]; - - match visibility { - ItemVisibility::Public => true, - ItemVisibility::PublicCrate => same_crate, - ItemVisibility::Private => { - same_crate - && (module_descendent_of_target( - target_crate_def_map, - target_module.local_id, - current_module, - ) || module_is_parent_of_struct_module( - target_crate_def_map, - current_module, - target_module.local_id, - )) - } - } -} - -// Returns true if `current` is a (potentially nested) child module of `target`. -// This is also true if `current == target`. -fn module_descendent_of_target( - def_map: &CrateDefMap, - target: LocalModuleId, - current: LocalModuleId, -) -> bool { - if current == target { - return true; - } - - def_map.modules[current.0] - .parent - .map_or(false, |parent| module_descendent_of_target(def_map, target, parent)) -} - -/// Returns true if `target` is a struct and its parent is `current`. -fn module_is_parent_of_struct_module( - def_map: &CrateDefMap, - current: LocalModuleId, - target: LocalModuleId, -) -> bool { - let module_data = &def_map.modules[target.0]; - module_data.is_struct && module_data.parent == Some(current) -} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/mod.rs index 01a3fe856e5..223b88b5c5d 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/mod.rs @@ -8,3 +8,4 @@ pub mod errors; pub mod import; pub mod path_resolver; +pub mod visibility; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/visibility.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/visibility.rs new file mode 100644 index 00000000000..492f303d2c4 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/visibility.rs @@ -0,0 +1,141 @@ +use crate::graph::CrateId; +use crate::node_interner::{FuncId, NodeInterner, StructId}; +use crate::Type; + +use std::collections::BTreeMap; + +use crate::ast::ItemVisibility; +use crate::hir::def_map::{CrateDefMap, DefMaps, LocalModuleId, ModuleId}; + +// Returns false if the given private function is being called from a non-child module, or +// if the given pub(crate) function is being called from another crate. Otherwise returns true. +pub fn can_reference_module_id( + def_maps: &BTreeMap, + importing_crate: CrateId, + current_module: LocalModuleId, + target_module: ModuleId, + visibility: ItemVisibility, +) -> bool { + // Note that if the target module is in a different crate from the current module then we will either + // return true as the target module is public or return false as it is private without looking at the `CrateDefMap` in either case. + let same_crate = target_module.krate == importing_crate; + + match visibility { + ItemVisibility::Public => true, + ItemVisibility::PublicCrate => same_crate, + ItemVisibility::Private => { + let target_crate_def_map = &def_maps[&target_module.krate]; + same_crate + && (module_descendent_of_target( + target_crate_def_map, + target_module.local_id, + current_module, + ) || module_is_parent_of_struct_module( + target_crate_def_map, + current_module, + target_module.local_id, + )) + } + } +} + +// Returns true if `current` is a (potentially nested) child module of `target`. +// This is also true if `current == target`. +pub(crate) fn module_descendent_of_target( + def_map: &CrateDefMap, + target: LocalModuleId, + current: LocalModuleId, +) -> bool { + if current == target { + return true; + } + + def_map.modules[current.0] + .parent + .map_or(false, |parent| module_descendent_of_target(def_map, target, parent)) +} + +/// Returns true if `target` is a struct and its parent is `current`. +fn module_is_parent_of_struct_module( + def_map: &CrateDefMap, + current: LocalModuleId, + target: LocalModuleId, +) -> bool { + let module_data = &def_map.modules[target.0]; + module_data.is_struct && module_data.parent == Some(current) +} + +pub fn struct_member_is_visible( + struct_id: StructId, + visibility: ItemVisibility, + current_module_id: ModuleId, + def_maps: &BTreeMap, +) -> bool { + match visibility { + ItemVisibility::Public => true, + ItemVisibility::PublicCrate => { + struct_id.parent_module_id(def_maps).krate == current_module_id.krate + } + ItemVisibility::Private => { + let struct_parent_module_id = struct_id.parent_module_id(def_maps); + if struct_parent_module_id.krate != current_module_id.krate { + return false; + } + + if struct_parent_module_id.local_id == current_module_id.local_id { + return true; + } + + let def_map = &def_maps[¤t_module_id.krate]; + module_descendent_of_target( + def_map, + struct_parent_module_id.local_id, + current_module_id.local_id, + ) + } + } +} + +pub fn method_call_is_visible( + object_type: &Type, + func_id: FuncId, + current_module: ModuleId, + interner: &NodeInterner, + def_maps: &DefMaps, +) -> bool { + let modifiers = interner.function_modifiers(&func_id); + match modifiers.visibility { + ItemVisibility::Public => true, + ItemVisibility::PublicCrate => { + if object_type.is_primitive() { + current_module.krate.is_stdlib() + } else { + interner.function_module(func_id).krate == current_module.krate + } + } + ItemVisibility::Private => { + if object_type.is_primitive() { + let func_module = interner.function_module(func_id); + can_reference_module_id( + def_maps, + current_module.krate, + current_module.local_id, + func_module, + modifiers.visibility, + ) + } else { + let func_meta = interner.function_meta(&func_id); + if let Some(struct_id) = func_meta.struct_id { + struct_member_is_visible( + struct_id, + modifiers.visibility, + current_module, + def_maps, + ) + } else { + true + } + } + } + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/errors.rs index 54699792901..99de6bca434 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/errors.rs @@ -48,12 +48,17 @@ pub enum TypeCheckError { TypeMismatchWithSource { expected: Type, actual: Type, span: Span, source: Source }, #[error("Expected type {expected_kind:?} is not the same as {expr_kind:?}")] TypeKindMismatch { expected_kind: String, expr_kind: String, expr_span: Span }, + // TODO(https://github.com/noir-lang/noir/issues/6238): implement handling for larger types + #[error("Expected type {expected_kind} when evaluating globals, but found {expr_kind} (this warning may become an error in the future)")] + EvaluatedGlobalIsntU32 { expected_kind: String, expr_kind: String, expr_span: Span }, #[error("Expected {expected:?} found {found:?}")] ArityMisMatch { expected: usize, found: usize, span: Span }, #[error("Return type in a function cannot be public")] PublicReturnType { typ: Type, span: Span }, #[error("Cannot cast type {from}, 'as' is only for primitive field or integer types")] - InvalidCast { from: Type, span: Span }, + InvalidCast { from: Type, span: Span, reason: String }, + #[error("Casting value of type {from} to a smaller type ({to})")] + DownsizingCast { from: Type, to: Type, span: Span, reason: String }, #[error("Expected a function, but found a(n) {found}")] ExpectedFunction { found: Type, span: Span }, #[error("Type {lhs_type} has no member named {field_name}")] @@ -227,6 +232,15 @@ impl<'a> From<&'a TypeCheckError> for Diagnostic { *expr_span, ) } + // TODO(https://github.com/noir-lang/noir/issues/6238): implement + // handling for larger types + TypeCheckError::EvaluatedGlobalIsntU32 { expected_kind, expr_kind, expr_span } => { + Diagnostic::simple_warning( + format!("Expected type {expected_kind} when evaluating globals, but found {expr_kind} (this warning may become an error in the future)"), + String::new(), + *expr_span, + ) + } TypeCheckError::TraitMethodParameterTypeMismatch { method_name, expected_typ, actual_typ, parameter_index, parameter_span } => { Diagnostic::simple_error( format!("Parameter #{parameter_index} of method `{method_name}` must be of type {expected_typ}, not {actual_typ}"), @@ -284,8 +298,14 @@ impl<'a> From<&'a TypeCheckError> for Diagnostic { }; Diagnostic::simple_error(msg, String::new(), *span) } - TypeCheckError::InvalidCast { span, .. } - | TypeCheckError::ExpectedFunction { span, .. } + TypeCheckError::InvalidCast { span, reason, .. } => { + Diagnostic::simple_error(error.to_string(), reason.clone(), *span) + } + TypeCheckError::DownsizingCast { span, reason, .. } => { + Diagnostic::simple_warning(error.to_string(), reason.clone(), *span) + } + + TypeCheckError::ExpectedFunction { span, .. } | TypeCheckError::AccessUnknownMember { span, .. } | TypeCheckError::UnsupportedCast { span } | TypeCheckError::TupleIndexOutOfBounds { span, .. } @@ -486,8 +506,8 @@ impl NoMatchingImplFoundError { let constraints = failing_constraints .into_iter() .map(|constraint| { - let r#trait = interner.try_get_trait(constraint.trait_id)?; - let name = format!("{}{}", r#trait.name, constraint.trait_generics); + let r#trait = interner.try_get_trait(constraint.trait_bound.trait_id)?; + let name = format!("{}{}", r#trait.name, constraint.trait_bound.trait_generics); Some((constraint.typ, name)) }) .collect::>>()?; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/generics.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/generics.rs index 86fc2d25d4e..370223f1f11 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/generics.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/generics.rs @@ -133,6 +133,10 @@ impl TraitGenerics { vecmap(&self.named, |named| NamedType { name: named.name.clone(), typ: f(&named.typ) }); TraitGenerics { ordered, named } } + + pub fn is_empty(&self) -> bool { + self.ordered.is_empty() && self.named.is_empty() + } } impl std::fmt::Display for TraitGenerics { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/expr.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/expr.rs index 71e0256a7e8..5d3fe632a74 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/expr.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/expr.rs @@ -11,7 +11,7 @@ use crate::token::Tokens; use crate::Shared; use super::stmt::HirPattern; -use super::traits::TraitConstraint; +use super::traits::{ResolvedTraitBound, TraitConstraint}; use super::types::{StructType, Type}; /// A HirExpression is the result of an Expression in the AST undergoing @@ -250,9 +250,11 @@ impl HirMethodCallExpression { let id = interner.trait_method_id(method_id); let constraint = TraitConstraint { typ: object_type, - trait_id: method_id.trait_id, - trait_generics, - span: location.span, + trait_bound: ResolvedTraitBound { + trait_id: method_id.trait_id, + trait_generics, + span: location.span, + }, }; (id, ImplKind::TraitMethod(TraitMethod { method_id, constraint, assumed: false })) } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/traits.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/traits.rs index 3859db26e39..534805c2dad 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/traits.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/traits.rs @@ -72,6 +72,9 @@ pub struct Trait { /// match the definition in the trait, we bind this TypeVariable to whatever /// the correct Self type is for that particular impl block. pub self_type_typevar: TypeVariable, + + /// The resolved trait bounds (for example in `trait Foo: Bar + Baz`, this would be `Bar + Baz`) + pub trait_bounds: Vec, } #[derive(Debug)] @@ -101,15 +104,25 @@ pub struct TraitImpl { #[derive(Debug, Clone, PartialEq, Eq)] pub struct TraitConstraint { pub typ: Type, - pub trait_id: TraitId, - pub trait_generics: TraitGenerics, - pub span: Span, + pub trait_bound: ResolvedTraitBound, } impl TraitConstraint { pub fn apply_bindings(&mut self, type_bindings: &TypeBindings) { self.typ = self.typ.substitute(type_bindings); + self.trait_bound.apply_bindings(type_bindings); + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ResolvedTraitBound { + pub trait_id: TraitId, + pub trait_generics: TraitGenerics, + pub span: Span, +} +impl ResolvedTraitBound { + pub fn apply_bindings(&mut self, type_bindings: &TypeBindings) { for typ in &mut self.trait_generics.ordered { *typ = typ.substitute(type_bindings); } @@ -137,6 +150,10 @@ impl Trait { self.methods = methods; } + pub fn set_trait_bounds(&mut self, trait_bounds: Vec) { + self.trait_bounds = trait_bounds; + } + pub fn find_method(&self, name: &str) -> Option { for (idx, method) in self.methods.iter().enumerate() { if &method.name == name { @@ -169,9 +186,11 @@ impl Trait { TraitConstraint { typ: Type::TypeVariable(self.self_type_typevar.clone()), - trait_generics: TraitGenerics { ordered, named }, - trait_id: self.id, - span, + trait_bound: ResolvedTraitBound { + trait_generics: TraitGenerics { ordered, named }, + trait_id: self.id, + span, + }, } } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types.rs index 5c2322acfda..fa2a455c06d 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types.rs @@ -5,8 +5,10 @@ use std::{ rc::Rc, }; +use acvm::{AcirField, FieldElement}; + use crate::{ - ast::IntegerBitSize, + ast::{IntegerBitSize, ItemVisibility}, hir::type_check::{generics::TraitGenerics, TypeCheckError}, node_interner::{ExprId, NodeInterner, TraitId, TypeAliasId}, }; @@ -110,9 +112,11 @@ pub enum Type { /// will be and thus needs the full TypeVariable link. Forall(GenericTypeVars, Box), - /// A type-level integer. Included to let an Array's size type variable - /// bind to an integer without special checks to bind it to a non-type. - Constant(u32, Kind), + /// A type-level integer. Included to let + /// 1. an Array's size type variable + /// bind to an integer without special checks to bind it to a non-type. + /// 2. values to be used at the type level + Constant(FieldElement, Kind), /// The type of quoted code in macros. This is always a comptime-only type Quoted(QuotedType), @@ -158,14 +162,29 @@ pub enum Kind { impl Kind { pub(crate) fn is_error(&self) -> bool { - match self { - Self::Numeric(typ) => **typ == Type::Error, + match self.follow_bindings() { + Self::Numeric(typ) => *typ == Type::Error, _ => false, } } pub(crate) fn is_numeric(&self) -> bool { - matches!(self, Self::Numeric { .. }) + matches!(self.follow_bindings(), Self::Numeric { .. }) + } + + pub(crate) fn is_type_level_field_element(&self) -> bool { + let type_level = false; + self.is_field_element(type_level) + } + + /// If value_level, only check for Type::FieldElement, + /// else only check for a type-level FieldElement + fn is_field_element(&self, value_level: bool) -> bool { + match self.follow_bindings() { + Kind::Numeric(typ) => typ.is_field_element(value_level), + Kind::IntegerOrField => value_level, + _ => false, + } } pub(crate) fn u32() -> Self { @@ -229,6 +248,21 @@ impl Kind { Kind::Numeric(typ) => Some(*typ.clone()), } } + + fn integral_maximum_size(&self) -> Option { + match self.follow_bindings() { + Kind::Any | Kind::IntegerOrField | Kind::Integer | Kind::Normal => None, + Self::Numeric(typ) => typ.integral_maximum_size(), + } + } + + /// Ensure the given value fits in self.integral_maximum_size() + fn ensure_value_fits(&self, value: FieldElement) -> Option { + match self.integral_maximum_size() { + None => Some(value), + Some(maximum_size) => (value <= maximum_size).then_some(value), + } + } } impl std::fmt::Display for Kind { @@ -269,7 +303,6 @@ pub type TypeBindings = HashMap; /// Represents a struct type in the type system. Each instance of this /// rust struct will be shared across all Type::Struct variants that represent /// the same struct type. -#[derive(Eq)] pub struct StructType { /// A unique id representing this struct type. Used to check if two /// struct types are equal. @@ -280,12 +313,18 @@ pub struct StructType { /// Fields are ordered and private, they should only /// be accessed through get_field(), get_fields(), or instantiate() /// since these will handle applying generic arguments to fields as well. - fields: Vec<(Ident, Type)>, + fields: Vec, pub generics: Generics, pub location: Location, } +pub struct StructField { + pub visibility: ItemVisibility, + pub name: Ident, + pub typ: Type, +} + /// Corresponds to generic lists such as `` in the source program. /// Used mainly for resolved types which no longer need information such /// as names or kinds @@ -329,6 +368,8 @@ impl std::hash::Hash for StructType { } } +impl Eq for StructType {} + impl PartialEq for StructType { fn eq(&self, other: &Self) -> bool { self.id == other.id @@ -353,7 +394,7 @@ impl StructType { name: Ident, location: Location, - fields: Vec<(Ident, Type)>, + fields: Vec, generics: Generics, ) -> StructType { StructType { id, fields, name, location, generics } @@ -363,7 +404,7 @@ impl StructType { /// fields are resolved strictly after the struct itself is initially /// created. Therefore, this method is used to set the fields once they /// become known. - pub fn set_fields(&mut self, fields: Vec<(Ident, Type)>) { + pub fn set_fields(&mut self, fields: Vec) { self.fields = fields; } @@ -371,12 +412,16 @@ impl StructType { self.fields.len() } - /// Returns the field matching the given field name, as well as its field index. - pub fn get_field(&self, field_name: &str, generic_args: &[Type]) -> Option<(Type, usize)> { + /// Returns the field matching the given field name, as well as its visibility and field index. + pub fn get_field( + &self, + field_name: &str, + generic_args: &[Type], + ) -> Option<(Type, ItemVisibility, usize)> { assert_eq!(self.generics.len(), generic_args.len()); - self.fields.iter().enumerate().find(|(_, (name, _))| name.0.contents == field_name).map( - |(i, (_, typ))| { + self.fields.iter().enumerate().find(|(_, field)| field.name.0.contents == field_name).map( + |(i, field)| { let substitutions = self .generics .iter() @@ -389,28 +434,46 @@ impl StructType { }) .collect(); - (typ.substitute(&substitutions), i) + (field.typ.substitute(&substitutions), field.visibility, i) }, ) } /// Returns all the fields of this type, after being applied to the given generic arguments. + pub fn get_fields_with_visibility( + &self, + generic_args: &[Type], + ) -> Vec<(String, ItemVisibility, Type)> { + let substitutions = self.get_fields_substitutions(generic_args); + + vecmap(&self.fields, |field| { + let name = field.name.0.contents.clone(); + (name, field.visibility, field.typ.substitute(&substitutions)) + }) + } + pub fn get_fields(&self, generic_args: &[Type]) -> Vec<(String, Type)> { + let substitutions = self.get_fields_substitutions(generic_args); + + vecmap(&self.fields, |field| { + let name = field.name.0.contents.clone(); + (name, field.typ.substitute(&substitutions)) + }) + } + + fn get_fields_substitutions( + &self, + generic_args: &[Type], + ) -> HashMap { assert_eq!(self.generics.len(), generic_args.len()); - let substitutions = self - .generics + self.generics .iter() .zip(generic_args) .map(|(old, new)| { (old.type_var.id(), (old.type_var.clone(), old.type_var.kind(), new.clone())) }) - .collect(); - - vecmap(&self.fields, |(name, typ)| { - let name = name.0.contents.clone(); - (name, typ.substitute(&substitutions)) - }) + .collect() } /// Returns the name and raw types of each field of this type. @@ -419,23 +482,27 @@ impl StructType { /// /// This method is almost never what is wanted for type checking or monomorphization, /// prefer to use `get_fields` whenever possible. - pub fn get_fields_as_written(&self) -> Vec<(String, Type)> { - vecmap(&self.fields, |(name, typ)| (name.0.contents.clone(), typ.clone())) + pub fn get_fields_as_written(&self) -> Vec { + vecmap(&self.fields, |field| StructField { + visibility: field.visibility, + name: field.name.clone(), + typ: field.typ.clone(), + }) } /// Returns the field at the given index. Panics if no field exists at the given index. - pub fn field_at(&self, index: usize) -> &(Ident, Type) { + pub fn field_at(&self, index: usize) -> &StructField { &self.fields[index] } pub fn field_names(&self) -> BTreeSet { - self.fields.iter().map(|(name, _)| name.clone()).collect() + self.fields.iter().map(|field| field.name.clone()).collect() } /// Search the fields of a struct for any types with a `TypeKind::Numeric` pub fn find_numeric_generics_in_fields(&self, found_names: &mut Vec) { - for (_, field) in self.fields.iter() { - field.find_numeric_type_vars(found_names); + for field in self.fields.iter() { + field.typ.find_numeric_type_vars(found_names); } } @@ -680,8 +747,10 @@ impl TypeVariable { /// and if unbound, that it's a Kind::Integer pub fn is_integer(&self) -> bool { match &*self.borrow() { - TypeBinding::Bound(binding) => matches!(binding, Type::Integer(..)), - TypeBinding::Unbound(_, type_var_kind) => matches!(type_var_kind, Kind::Integer), + TypeBinding::Bound(binding) => matches!(binding.follow_bindings(), Type::Integer(..)), + TypeBinding::Unbound(_, type_var_kind) => { + matches!(type_var_kind.follow_bindings(), Kind::Integer) + } } } @@ -690,9 +759,20 @@ impl TypeVariable { pub fn is_integer_or_field(&self) -> bool { match &*self.borrow() { TypeBinding::Bound(binding) => { - matches!(binding, Type::Integer(..) | Type::FieldElement) + matches!(binding.follow_bindings(), Type::Integer(..) | Type::FieldElement) } - TypeBinding::Unbound(_, type_var_kind) => matches!(type_var_kind, Kind::IntegerOrField), + TypeBinding::Unbound(_, type_var_kind) => { + matches!(type_var_kind.follow_bindings(), Kind::IntegerOrField) + } + } + } + + /// If value_level, only check for Type::FieldElement, + /// else only check for a type-level FieldElement + fn is_field_element(&self, value_level: bool) -> bool { + match &*self.borrow() { + TypeBinding::Bound(binding) => binding.is_field_element(value_level), + TypeBinding::Unbound(_, type_var_kind) => type_var_kind.is_field_element(value_level), } } } @@ -923,6 +1003,17 @@ impl Type { matches!(self.follow_bindings(), Type::Integer(_, _)) } + /// If value_level, only check for Type::FieldElement, + /// else only check for a type-level FieldElement + fn is_field_element(&self, value_level: bool) -> bool { + match self.follow_bindings() { + Type::FieldElement => value_level, + Type::TypeVariable(var) => var.is_field_element(value_level), + Type::Constant(_, kind) => !value_level && kind.is_field_element(true), + _ => false, + } + } + pub fn is_signed(&self) -> bool { matches!(self.follow_bindings(), Type::Integer(Signedness::Signed, _)) } @@ -950,6 +1041,34 @@ impl Type { } } + pub fn is_primitive(&self) -> bool { + match self.follow_bindings() { + Type::FieldElement + | Type::Array(_, _) + | Type::Slice(_) + | Type::Integer(..) + | Type::Bool + | Type::String(_) + | Type::FmtString(_, _) + | Type::Unit + | Type::Function(..) + | Type::Tuple(..) => true, + Type::Alias(alias_type, generics) => { + alias_type.borrow().get_type(&generics).is_primitive() + } + Type::MutableReference(typ) => typ.is_primitive(), + Type::Struct(..) + | Type::TypeVariable(..) + | Type::TraitAsType(..) + | Type::NamedGeneric(..) + | Type::Forall(..) + | Type::Constant(..) + | Type::Quoted(..) + | Type::InfixExpr(..) + | Type::Error => false, + } + } + pub fn find_numeric_type_vars(&self, found_names: &mut Vec) { // Return whether the named generic has a Kind::Numeric and save its name let named_generic_is_numeric = |typ: &Type, found_names: &mut Vec| { @@ -1635,15 +1754,14 @@ impl Type { } (Constant(value, kind), other) | (other, Constant(value, kind)) => { - // TODO(https://github.com/noir-lang/noir/pull/6137): replace evaluate_to_u32 - if let Some(other_value) = other.evaluate_to_u32() { + if let Some(other_value) = other.evaluate_to_field_element(kind) { if *value == other_value && kind.unifies(&other.kind()) { Ok(()) } else { Err(UnificationError) } } else if let InfixExpr(lhs, op, rhs) = other { - if let Some(inverse) = op.inverse() { + if let Some(inverse) = op.approx_inverse() { // Handle cases like `4 = a + b` by trying to solve to `a = 4 - b` let new_type = InfixExpr( Box::new(Constant(*value, kind.clone())), @@ -1805,19 +1923,38 @@ impl Type { /// If this type is a Type::Constant (used in array lengths), or is bound /// to a Type::Constant, return the constant as a u32. pub fn evaluate_to_u32(&self) -> Option { - if let Some((binding, _kind)) = self.get_inner_type_variable() { + self.evaluate_to_field_element(&Kind::u32()) + .and_then(|field_element| field_element.try_to_u32()) + } + + // TODO(https://github.com/noir-lang/noir/issues/6260): remove + // the unifies checks once all kinds checks are implemented? + pub(crate) fn evaluate_to_field_element(&self, kind: &Kind) -> Option { + if let Some((binding, binding_kind)) = self.get_inner_type_variable() { if let TypeBinding::Bound(binding) = &*binding.borrow() { - return binding.evaluate_to_u32(); + if kind.unifies(&binding_kind) { + return binding.evaluate_to_field_element(&binding_kind); + } } } match self.canonicalize() { - Type::Array(len, _elem) => len.evaluate_to_u32(), - Type::Constant(x, _kind) => Some(x), + Type::Constant(x, constant_kind) => { + if kind.unifies(&constant_kind) { + kind.ensure_value_fits(x) + } else { + None + } + } Type::InfixExpr(lhs, op, rhs) => { - let lhs_u32 = lhs.evaluate_to_u32()?; - let rhs_u32 = rhs.evaluate_to_u32()?; - op.function(lhs_u32, rhs_u32, &lhs.infix_kind(&rhs)) + let infix_kind = lhs.infix_kind(&rhs); + if kind.unifies(&infix_kind) { + let lhs_value = lhs.evaluate_to_field_element(&infix_kind)?; + let rhs_value = rhs.evaluate_to_field_element(&infix_kind)?; + op.function(lhs_value, rhs_value, &infix_kind) + } else { + None + } } _ => None, } @@ -1831,8 +1968,8 @@ impl Type { // only to have to call .into_iter again afterward. Trying to elide // collecting to a Vec leads to us dropping the temporary Ref before // the iterator is returned - Type::Struct(def, args) => vecmap(&def.borrow().fields, |(name, _)| { - let name = &name.0.contents; + Type::Struct(def, args) => vecmap(&def.borrow().fields, |field| { + let name = &field.name.0.contents; let typ = def.borrow().get_field(name, args).unwrap().0; (name.clone(), typ) }), @@ -1847,14 +1984,20 @@ impl Type { /// Retrieves the type of the given field name /// Panics if the type is not a struct or tuple. - pub fn get_field_type(&self, field_name: &str) -> Option { + pub fn get_field_type_and_visibility( + &self, + field_name: &str, + ) -> Option<(Type, ItemVisibility)> { match self.follow_bindings() { - Type::Struct(def, args) => { - def.borrow().get_field(field_name, &args).map(|(typ, _)| typ) - } + Type::Struct(def, args) => def + .borrow() + .get_field(field_name, &args) + .map(|(typ, visibility, _)| (typ, visibility)), Type::Tuple(fields) => { let mut fields = fields.into_iter().enumerate(); - fields.find(|(i, _)| i.to_string() == *field_name).map(|(_, typ)| typ) + fields + .find(|(i, _)| i.to_string() == *field_name) + .map(|(_, typ)| (typ, ItemVisibility::Public)) } _ => None, } @@ -2188,7 +2331,6 @@ impl Type { } self.clone() } - Function(args, ret, env, unconstrained) => { let args = vecmap(args, |arg| arg.follow_bindings()); let ret = Box::new(ret.follow_bindings()); @@ -2313,6 +2455,51 @@ impl Type { _ => None, } } + + pub(crate) fn integral_maximum_size(&self) -> Option { + match self { + Type::FieldElement => None, + Type::Integer(sign, num_bits) => { + let mut max_bit_size = num_bits.bit_size(); + if sign == &Signedness::Signed { + max_bit_size -= 1; + } + Some(((1u128 << max_bit_size) - 1).into()) + } + Type::Bool => Some(FieldElement::one()), + Type::TypeVariable(var) => { + let binding = &var.1; + match &*binding.borrow() { + TypeBinding::Unbound(_, type_var_kind) => match type_var_kind { + Kind::Any | Kind::Normal | Kind::Integer | Kind::IntegerOrField => None, + Kind::Numeric(typ) => typ.integral_maximum_size(), + }, + TypeBinding::Bound(typ) => typ.integral_maximum_size(), + } + } + Type::Alias(alias, args) => alias.borrow().get_type(args).integral_maximum_size(), + Type::NamedGeneric(binding, _name) => match &*binding.borrow() { + TypeBinding::Bound(typ) => typ.integral_maximum_size(), + TypeBinding::Unbound(_, kind) => kind.integral_maximum_size(), + }, + Type::MutableReference(typ) => typ.integral_maximum_size(), + Type::InfixExpr(lhs, _op, rhs) => lhs.infix_kind(rhs).integral_maximum_size(), + Type::Constant(_, kind) => kind.integral_maximum_size(), + + Type::Array(..) + | Type::Slice(..) + | Type::String(..) + | Type::FmtString(..) + | Type::Unit + | Type::Tuple(..) + | Type::Struct(..) + | Type::TraitAsType(..) + | Type::Function(..) + | Type::Forall(..) + | Type::Quoted(..) + | Type::Error => None, + } + } } /// Wraps a given `expression` in `expression.as_slice()` @@ -2350,15 +2537,29 @@ fn convert_array_expression_to_slice( impl BinaryTypeOperator { /// Perform the actual rust numeric operation associated with this operator - // TODO(https://github.com/noir-lang/noir/pull/6137): the Kind is included - // since it'll be needed for size checks - pub fn function(self, a: u32, b: u32, _kind: &Kind) -> Option { - match self { - BinaryTypeOperator::Addition => a.checked_add(b), - BinaryTypeOperator::Subtraction => a.checked_sub(b), - BinaryTypeOperator::Multiplication => a.checked_mul(b), - BinaryTypeOperator::Division => a.checked_div(b), - BinaryTypeOperator::Modulo => a.checked_rem(b), + pub fn function(self, a: FieldElement, b: FieldElement, kind: &Kind) -> Option { + match kind.follow_bindings().integral_maximum_size() { + None => match self { + BinaryTypeOperator::Addition => Some(a + b), + BinaryTypeOperator::Subtraction => Some(a - b), + BinaryTypeOperator::Multiplication => Some(a * b), + BinaryTypeOperator::Division => (b != FieldElement::zero()).then(|| a / b), + BinaryTypeOperator::Modulo => None, + }, + Some(_maximum_size) => { + let a = a.to_i128(); + let b = b.to_i128(); + + let result = match self { + BinaryTypeOperator::Addition => a.checked_add(b)?, + BinaryTypeOperator::Subtraction => a.checked_sub(b)?, + BinaryTypeOperator::Multiplication => a.checked_mul(b)?, + BinaryTypeOperator::Division => a.checked_div(b)?, + BinaryTypeOperator::Modulo => a.checked_rem(b)?, + }; + + Some(result.into()) + } } } @@ -2368,6 +2569,17 @@ impl BinaryTypeOperator { /// Return the operator that will "undo" this operation if applied to the rhs fn inverse(self) -> Option { + match self { + BinaryTypeOperator::Addition => Some(BinaryTypeOperator::Subtraction), + BinaryTypeOperator::Subtraction => Some(BinaryTypeOperator::Addition), + BinaryTypeOperator::Multiplication => None, + BinaryTypeOperator::Division => None, + BinaryTypeOperator::Modulo => None, + } + } + + /// Return the operator that will "undo" this operation if applied to the rhs + fn approx_inverse(self) -> Option { match self { BinaryTypeOperator::Addition => Some(BinaryTypeOperator::Subtraction), BinaryTypeOperator::Subtraction => Some(BinaryTypeOperator::Addition), diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types/arithmetic.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types/arithmetic.rs index af94ef27535..81f638ebca4 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types/arithmetic.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types/arithmetic.rs @@ -1,5 +1,7 @@ use std::collections::BTreeMap; +use acvm::{AcirField, FieldElement}; + use crate::{BinaryTypeOperator, Type, TypeBindings, UnificationError}; impl Type { @@ -15,10 +17,11 @@ impl Type { pub fn canonicalize(&self) -> Type { match self.follow_bindings() { Type::InfixExpr(lhs, op, rhs) => { - // evaluate_to_u32 also calls canonicalize so if we just called - // `self.evaluate_to_u32()` we'd get infinite recursion. + let kind = lhs.infix_kind(&rhs); + // evaluate_to_field_element also calls canonicalize so if we just called + // `self.evaluate_to_field_element(..)` we'd get infinite recursion. if let (Some(lhs_u32), Some(rhs_u32)) = - (lhs.evaluate_to_u32(), rhs.evaluate_to_u32()) + (lhs.evaluate_to_field_element(&kind), rhs.evaluate_to_field_element(&kind)) { let kind = lhs.infix_kind(&rhs); if let Some(result) = op.function(lhs_u32, rhs_u32, &kind) { @@ -58,7 +61,11 @@ impl Type { // Maps each term to the number of times that term was used. let mut sorted = BTreeMap::new(); - let zero_value = if op == BinaryTypeOperator::Addition { 0 } else { 1 }; + let zero_value = if op == BinaryTypeOperator::Addition { + FieldElement::zero() + } else { + FieldElement::one() + }; let mut constant = zero_value; // Push each non-constant term to `sorted` to sort them. Recur on InfixExprs with the same operator. @@ -113,7 +120,7 @@ impl Type { /// Precondition: `lhs & rhs are in canonical form` /// /// - Simplifies `(N +/- M) -/+ M` to `N` - /// - Simplifies `(N */÷ M) ÷/* M` to `N` + /// - Simplifies `(N * M) ÷ M` to `N` fn try_simplify_non_constants_in_lhs( lhs: &Type, op: BinaryTypeOperator, @@ -125,7 +132,10 @@ impl Type { // Note that this is exact, syntactic equality, not unification. // `rhs` is expected to already be in canonical form. - if l_op.inverse() != Some(op) || l_rhs.canonicalize() != *rhs { + if l_op.approx_inverse() != Some(op) + || l_op == BinaryTypeOperator::Division + || l_rhs.canonicalize() != *rhs + { return None; } @@ -175,14 +185,15 @@ impl Type { fn parse_partial_constant_expr( lhs: &Type, rhs: &Type, - ) -> Option<(Box, BinaryTypeOperator, u32, u32)> { - let rhs = rhs.evaluate_to_u32()?; + ) -> Option<(Box, BinaryTypeOperator, FieldElement, FieldElement)> { + let kind = lhs.infix_kind(rhs); + let rhs = rhs.evaluate_to_field_element(&kind)?; let Type::InfixExpr(l_type, l_op, l_rhs) = lhs.follow_bindings() else { return None; }; - let l_rhs = l_rhs.evaluate_to_u32()?; + let l_rhs = l_rhs.evaluate_to_field_element(&kind)?; Some((l_type, l_op, l_rhs, rhs)) } @@ -191,7 +202,8 @@ impl Type { /// Precondition: `lhs & rhs are in canonical form` /// /// - Simplifies `(N +/- C1) +/- C2` to `N +/- (C1 +/- C2)` if C1 and C2 are constants. - /// - Simplifies `(N */÷ C1) */÷ C2` to `N */÷ (C1 */÷ C2)` if C1 and C2 are constants. + /// - Simplifies `(N * C1) ÷ C2` to `N * (C1 ÷ C2)` if C1 and C2 are constants which divide + /// without a remainder. fn try_simplify_partial_constants( lhs: &Type, mut op: BinaryTypeOperator, @@ -210,13 +222,13 @@ impl Type { let constant = Type::Constant(result, lhs.infix_kind(rhs)); Some(Type::InfixExpr(l_type, l_op, Box::new(constant))) } - (Multiplication | Division, Multiplication | Division) => { - // If l_op is a division we want to inverse the rhs operator. - if l_op == Division { - op = op.inverse()?; - } + (Multiplication, Division) => { + // We need to ensure the result divides evenly to preserve integer division semantics + let divides_evenly = !lhs.infix_kind(rhs).is_type_level_field_element() + && l_const.to_i128().checked_rem(r_const.to_i128()) == Some(0); + // If op is a division we need to ensure it divides evenly - if op == Division && (r_const == 0 || l_const % r_const != 0) { + if op == Division && (r_const == FieldElement::zero() || !divides_evenly) { None } else { let result = op.function(l_const, r_const, &lhs.infix_kind(rhs))?; @@ -236,9 +248,10 @@ impl Type { bindings: &mut TypeBindings, ) -> Result<(), UnificationError> { if let Type::InfixExpr(lhs_a, op_a, rhs_a) = self { - if let Some(inverse) = op_a.inverse() { - if let Some(rhs_a_u32) = rhs_a.evaluate_to_u32() { - let rhs_a = Box::new(Type::Constant(rhs_a_u32, lhs_a.infix_kind(rhs_a))); + if let Some(inverse) = op_a.approx_inverse() { + let kind = lhs_a.infix_kind(rhs_a); + if let Some(rhs_a_value) = rhs_a.evaluate_to_field_element(&kind) { + let rhs_a = Box::new(Type::Constant(rhs_a_value, kind)); let new_other = Type::InfixExpr(Box::new(other.clone()), inverse, rhs_a); let mut tmp_bindings = bindings.clone(); @@ -251,9 +264,10 @@ impl Type { } if let Type::InfixExpr(lhs_b, op_b, rhs_b) = other { - if let Some(inverse) = op_b.inverse() { - if let Some(rhs_b_u32) = rhs_b.evaluate_to_u32() { - let rhs_b = Box::new(Type::Constant(rhs_b_u32, lhs_b.infix_kind(rhs_b))); + if let Some(inverse) = op_b.approx_inverse() { + let kind = lhs_b.infix_kind(rhs_b); + if let Some(rhs_b_value) = rhs_b.evaluate_to_field_element(&kind) { + let rhs_b = Box::new(Type::Constant(rhs_b_value, kind)); let new_self = Type::InfixExpr(Box::new(self.clone()), inverse, rhs_b); let mut tmp_bindings = bindings.clone(); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/lexer/lexer.rs b/noir/noir-repo/compiler/noirc_frontend/src/lexer/lexer.rs index 069487acb46..adc68351e3c 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/lexer/lexer.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/lexer/lexer.rs @@ -301,6 +301,11 @@ impl<'a> Lexer<'a> { } self.next_char(); + let is_tag = self.peek_char_is('\''); + if is_tag { + self.next_char(); + } + let contents_start = self.position + 1; let word = self.eat_while(None, |ch| ch != ']'); @@ -321,7 +326,7 @@ impl<'a> Lexer<'a> { let span = Span::inclusive(start, end); let contents_span = Span::inclusive(contents_start, contents_end); - let attribute = Attribute::lookup_attribute(&word, span, contents_span)?; + let attribute = Attribute::lookup_attribute(&word, span, contents_span, is_tag)?; if is_inner { match attribute { Attribute::Function(attribute) => Err(LexerErrorKind::InvalidInnerAttribute { @@ -838,17 +843,17 @@ mod tests { } #[test] - fn custom_attribute() { - let input = r#"#[custom(hello)]"#; + fn tag_attribute() { + let input = r#"#['custom(hello)]"#; let mut lexer = Lexer::new(input); let token = lexer.next_token().unwrap(); assert_eq!( token.token(), - &Token::Attribute(Attribute::Secondary(SecondaryAttribute::Custom(CustomAttribute { + &Token::Attribute(Attribute::Secondary(SecondaryAttribute::Tag(CustomAttribute { contents: "custom(hello)".to_string(), - span: Span::from(0..16), - contents_span: Span::from(2..15) + span: Span::from(0..17), + contents_span: Span::from(3..16) }))) ); } @@ -943,7 +948,7 @@ mod tests { let token = lexer.next_token().unwrap(); assert_eq!( token.token(), - &Token::InnerAttribute(SecondaryAttribute::Custom(CustomAttribute { + &Token::InnerAttribute(SecondaryAttribute::Meta(CustomAttribute { contents: "something".to_string(), span: Span::from(0..13), contents_span: Span::from(3..12), diff --git a/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs b/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs index d96ead2002e..a8e463fb93b 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs @@ -378,8 +378,16 @@ impl fmt::Display for Token { Token::Keyword(k) => write!(f, "{k}"), Token::Attribute(ref a) => write!(f, "{a}"), Token::InnerAttribute(ref a) => write!(f, "#![{a}]"), - Token::LineComment(ref s, _style) => write!(f, "//{s}"), - Token::BlockComment(ref s, _style) => write!(f, "/*{s}*/"), + Token::LineComment(ref s, style) => match style { + Some(DocStyle::Inner) => write!(f, "//!{s}"), + Some(DocStyle::Outer) => write!(f, "///{s}"), + None => write!(f, "//{s}"), + }, + Token::BlockComment(ref s, style) => match style { + Some(DocStyle::Inner) => write!(f, "/*!{s}*/"), + Some(DocStyle::Outer) => write!(f, "/**{s}*/"), + None => write!(f, "/*{s}*/"), + }, Token::Quote(ref stream) => { write!(f, "quote {{")?; for token in stream.0.iter() { @@ -456,6 +464,7 @@ pub enum TokenKind { InternedUnresolvedTypeData, InternedPattern, UnquoteMarker, + Comment, OuterDocComment, InnerDocComment, } @@ -477,6 +486,7 @@ impl fmt::Display for TokenKind { TokenKind::InternedUnresolvedTypeData => write!(f, "interned unresolved type"), TokenKind::InternedPattern => write!(f, "interned pattern"), TokenKind::UnquoteMarker => write!(f, "macro result"), + TokenKind::Comment => write!(f, "comment"), TokenKind::OuterDocComment => write!(f, "outer doc comment"), TokenKind::InnerDocComment => write!(f, "inner doc comment"), } @@ -503,6 +513,7 @@ impl Token { Token::InternedLValue(_) => TokenKind::InternedLValue, Token::InternedUnresolvedTypeData(_) => TokenKind::InternedUnresolvedTypeData, Token::InternedPattern(_) => TokenKind::InternedPattern, + Token::LineComment(_, None) | Token::BlockComment(_, None) => TokenKind::Comment, Token::LineComment(_, Some(DocStyle::Outer)) | Token::BlockComment(_, Some(DocStyle::Outer)) => TokenKind::OuterDocComment, Token::LineComment(_, Some(DocStyle::Inner)) @@ -635,8 +646,8 @@ impl fmt::Display for TestScope { match self { TestScope::None => write!(f, ""), TestScope::ShouldFailWith { reason } => match reason { - Some(failure_reason) => write!(f, "(should_fail_with = ({failure_reason}))"), - None => write!(f, "should_fail"), + Some(failure_reason) => write!(f, "(should_fail_with = {failure_reason:?})"), + None => write!(f, "(should_fail)"), }, } } @@ -738,6 +749,7 @@ impl Attribute { word: &str, span: Span, contents_span: Span, + is_tag: bool, ) -> Result { let word_segments: Vec<&str> = word .split(|c| c == '(' || c == ')') @@ -758,6 +770,14 @@ impl Attribute { is_valid.ok_or(LexerErrorKind::MalformedFuncAttribute { span, found: word.to_owned() }) }; + if is_tag { + return Ok(Attribute::Secondary(SecondaryAttribute::Tag(CustomAttribute { + contents: word.to_owned(), + span, + contents_span, + }))); + } + let attribute = match &word_segments[..] { // Primary Attributes ["foreign", name] => { @@ -814,7 +834,7 @@ impl Attribute { ["allow", tag] => Attribute::Secondary(SecondaryAttribute::Allow(tag.to_string())), tokens => { tokens.iter().try_for_each(|token| validate(token))?; - Attribute::Secondary(SecondaryAttribute::Custom(CustomAttribute { + Attribute::Secondary(SecondaryAttribute::Meta(CustomAttribute { contents: word.to_owned(), span, contents_span, @@ -933,7 +953,13 @@ pub enum SecondaryAttribute { ContractLibraryMethod, Export, Field(String), - Custom(CustomAttribute), + + /// A custom tag attribute: #['foo] + Tag(CustomAttribute), + + /// An attribute expected to run a comptime function of the same name: #[foo] + Meta(CustomAttribute), + Abi(String), /// A variable-argument comptime function. @@ -950,7 +976,7 @@ pub enum SecondaryAttribute { impl SecondaryAttribute { pub(crate) fn as_custom(&self) -> Option<&CustomAttribute> { - if let Self::Custom(attribute) = self { + if let Self::Tag(attribute) = self { Some(attribute) } else { None @@ -965,7 +991,8 @@ impl SecondaryAttribute { } SecondaryAttribute::Export => Some("export".to_string()), SecondaryAttribute::Field(_) => Some("field".to_string()), - SecondaryAttribute::Custom(custom) => custom.name(), + SecondaryAttribute::Tag(custom) => custom.name(), + SecondaryAttribute::Meta(custom) => custom.name(), SecondaryAttribute::Abi(_) => Some("abi".to_string()), SecondaryAttribute::Varargs => Some("varargs".to_string()), SecondaryAttribute::UseCallersScope => Some("use_callers_scope".to_string()), @@ -979,6 +1006,10 @@ impl SecondaryAttribute { _ => false, } } + + pub(crate) fn is_abi(&self) -> bool { + matches!(self, SecondaryAttribute::Abi(_)) + } } impl fmt::Display for SecondaryAttribute { @@ -986,9 +1017,10 @@ impl fmt::Display for SecondaryAttribute { match self { SecondaryAttribute::Deprecated(None) => write!(f, "#[deprecated]"), SecondaryAttribute::Deprecated(Some(ref note)) => { - write!(f, r#"#[deprecated("{note}")]"#) + write!(f, r#"#[deprecated({note:?})]"#) } - SecondaryAttribute::Custom(ref attribute) => write!(f, "#[{}]", attribute.contents), + SecondaryAttribute::Tag(ref attribute) => write!(f, "#['{}]", attribute.contents), + SecondaryAttribute::Meta(ref attribute) => write!(f, "#[{}]", attribute.contents), SecondaryAttribute::ContractLibraryMethod => write!(f, "#[contract_library_method]"), SecondaryAttribute::Export => write!(f, "#[export]"), SecondaryAttribute::Field(ref k) => write!(f, "#[field({k})]"), @@ -1041,7 +1073,8 @@ impl AsRef for SecondaryAttribute { match self { SecondaryAttribute::Deprecated(Some(string)) => string, SecondaryAttribute::Deprecated(None) => "", - SecondaryAttribute::Custom(attribute) => &attribute.contents, + SecondaryAttribute::Tag(attribute) => &attribute.contents, + SecondaryAttribute::Meta(attribute) => &attribute.contents, SecondaryAttribute::Field(string) | SecondaryAttribute::Abi(string) | SecondaryAttribute::Allow(string) => string, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/locations.rs b/noir/noir-repo/compiler/noirc_frontend/src/locations.rs index f3439316538..48660142d0a 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/locations.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/locations.rs @@ -47,7 +47,10 @@ impl NodeInterner { ReferenceId::StructMember(id, field_index) => { let struct_type = self.get_struct(id); let struct_type = struct_type.borrow(); - Location::new(struct_type.field_at(field_index).0.span(), struct_type.location.file) + Location::new( + struct_type.field_at(field_index).name.span(), + struct_type.location.file, + ) } ReferenceId::Trait(id) => { let trait_type = self.get_trait(id); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/errors.rs index 4b1951a1aac..e2ff94f521b 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/errors.rs @@ -11,6 +11,7 @@ pub enum MonomorphizationError { InterpreterError(InterpreterError), ComptimeFnInRuntimeCode { name: String, location: Location }, ComptimeTypeInRuntimeCode { typ: String, location: Location }, + CheckedTransmuteFailed { actual: Type, expected: Type, location: Location }, } impl MonomorphizationError { @@ -21,6 +22,7 @@ impl MonomorphizationError { | MonomorphizationError::InternalError { location, .. } | MonomorphizationError::ComptimeFnInRuntimeCode { location, .. } | MonomorphizationError::ComptimeTypeInRuntimeCode { location, .. } + | MonomorphizationError::CheckedTransmuteFailed { location, .. } | MonomorphizationError::NoDefaultType { location, .. } => *location, MonomorphizationError::InterpreterError(error) => error.get_location(), } @@ -45,6 +47,9 @@ impl MonomorphizationError { MonomorphizationError::UnknownConstant { .. } => { "Could not resolve constant".to_string() } + MonomorphizationError::CheckedTransmuteFailed { actual, expected, .. } => { + format!("checked_transmute failed: `{actual}` != `{expected}`") + } MonomorphizationError::NoDefaultType { location } => { let message = "Type annotation needed".into(); let secondary = "Could not determine type of generic argument".into(); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs index 295aa9056b6..3ca4c5651ec 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -928,9 +928,11 @@ impl<'interner> Monomorphizer<'interner> { TypeBinding::Unbound(_, _) => { unreachable!("Unbound type variable used in expression") } - TypeBinding::Bound(binding) => binding.evaluate_to_u32().unwrap_or_else(|| { - panic!("Non-numeric type variable used in expression expecting a value") - }), + TypeBinding::Bound(binding) => binding + .evaluate_to_field_element(&Kind::Numeric(numeric_typ.clone())) + .unwrap_or_else(|| { + panic!("Non-numeric type variable used in expression expecting a value") + }), }; let location = self.interner.id_location(expr_id); @@ -942,12 +944,7 @@ impl<'interner> Monomorphizer<'interner> { } let typ = Self::convert_type(&typ, ident.location)?; - ast::Expression::Literal(ast::Literal::Integer( - (value as u128).into(), - false, - typ, - location, - )) + ast::Expression::Literal(ast::Literal::Integer(value, false, typ, location)) } }; @@ -1289,7 +1286,7 @@ impl<'interner> Monomorphizer<'interner> { }; let call = self - .try_evaluate_call(&func, &id, &return_type) + .try_evaluate_call(&func, &id, &call.arguments, &arguments, &return_type)? .unwrap_or(ast::Expression::Call(ast::Call { func, arguments, return_type, location })); if !block_expressions.is_empty() { @@ -1371,13 +1368,15 @@ impl<'interner> Monomorphizer<'interner> { &mut self, func: &ast::Expression, expr_id: &node_interner::ExprId, + arguments: &[node_interner::ExprId], + argument_values: &[ast::Expression], result_type: &ast::Type, - ) -> Option { + ) -> Result, MonomorphizationError> { if let ast::Expression::Ident(ident) = func { if let Definition::Builtin(opcode) = &ident.definition { // TODO(#1736): Move this builtin to the SSA pass let location = self.interner.expr_location(expr_id); - return match opcode.as_str() { + return Ok(match opcode.as_str() { "modulus_num_bits" => { let bits = (FieldElement::max_num_bits() as u128).into(); let typ = @@ -1406,11 +1405,35 @@ impl<'interner> Monomorphizer<'interner> { let bytes = FieldElement::modulus().to_bytes_le(); Some(self.modulus_slice_literal(bytes, IntegerBitSize::Eight, location)) } + "checked_transmute" => { + Some(self.checked_transmute(*expr_id, arguments, argument_values)?) + } _ => None, - }; + }); } } - None + Ok(None) + } + + fn checked_transmute( + &mut self, + expr_id: node_interner::ExprId, + arguments: &[node_interner::ExprId], + argument_values: &[ast::Expression], + ) -> Result { + let location = self.interner.expr_location(&expr_id); + let actual = self.interner.id_type(arguments[0]).follow_bindings(); + let expected = self.interner.id_type(expr_id).follow_bindings(); + + if actual.unify(&expected).is_err() { + Err(MonomorphizationError::CheckedTransmuteFailed { actual, expected, location }) + } else { + // Evaluate `checked_transmute(arg)` to `{ arg }` + // in case the user did `&mut checked_transmute(arg)`. Wrapping the + // arg in a block prevents mutating the original argument. + let argument = argument_values[0].clone(); + Ok(ast::Expression::Block(vec![argument])) + } } fn modulus_slice_literal( diff --git a/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs b/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs index b80c37c2ce4..ca7e0c6aa59 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs @@ -24,6 +24,7 @@ use crate::hir::def_map::DefMaps; use crate::hir::def_map::{LocalModuleId, ModuleDefId, ModuleId}; use crate::hir::type_check::generics::TraitGenerics; use crate::hir_def::traits::NamedType; +use crate::hir_def::traits::ResolvedTraitBound; use crate::usage_tracker::UnusedItem; use crate::usage_tracker::UsageTracker; use crate::QuotedType; @@ -291,6 +292,7 @@ pub enum DependencyId { Global(GlobalId), Function(FuncId), Alias(TypeAliasId), + Trait(TraitId), Variable(Location), } @@ -732,6 +734,7 @@ impl NodeInterner { methods: Vec::new(), method_ids: unresolved_trait.method_ids.clone(), associated_types, + trait_bounds: Vec::new(), }; self.traits.insert(type_id, new_trait); @@ -1531,9 +1534,11 @@ impl NodeInterner { let named = trait_associated_types.to_vec(); TraitConstraint { typ: object_type.clone(), - trait_id, - trait_generics: TraitGenerics { ordered, named }, - span: Span::default(), + trait_bound: ResolvedTraitBound { + trait_id, + trait_generics: TraitGenerics { ordered, named }, + span: Span::default(), + }, } }; @@ -1613,9 +1618,11 @@ impl NodeInterner { let constraint = TraitConstraint { typ: existing_object_type, - trait_id, - trait_generics, - span: Span::default(), + trait_bound: ResolvedTraitBound { + trait_id, + trait_generics, + span: Span::default(), + }, }; matching_impls.push((impl_kind.clone(), fresh_bindings, constraint)); } @@ -1635,8 +1642,8 @@ impl NodeInterner { Err(ImplSearchErrorKind::Nested(errors)) } else { let impls = vecmap(matching_impls, |(_, _, constraint)| { - let name = &self.get_trait(constraint.trait_id).name; - format!("{}: {name}{}", constraint.typ, constraint.trait_generics) + let name = &self.get_trait(constraint.trait_bound.trait_id).name; + format!("{}: {name}{}", constraint.typ, constraint.trait_bound.trait_generics) }); Err(ImplSearchErrorKind::MultipleMatching(impls)) } @@ -1658,20 +1665,22 @@ impl NodeInterner { let constraint_type = constraint.typ.force_substitute(instantiation_bindings).substitute(type_bindings); - let trait_generics = vecmap(&constraint.trait_generics.ordered, |generic| { - generic.force_substitute(instantiation_bindings).substitute(type_bindings) - }); + let trait_generics = + vecmap(&constraint.trait_bound.trait_generics.ordered, |generic| { + generic.force_substitute(instantiation_bindings).substitute(type_bindings) + }); - let trait_associated_types = vecmap(&constraint.trait_generics.named, |generic| { - let typ = generic.typ.force_substitute(instantiation_bindings); - NamedType { name: generic.name.clone(), typ: typ.substitute(type_bindings) } - }); + let trait_associated_types = + vecmap(&constraint.trait_bound.trait_generics.named, |generic| { + let typ = generic.typ.force_substitute(instantiation_bindings); + NamedType { name: generic.name.clone(), typ: typ.substitute(type_bindings) } + }); // We can ignore any associated types on the constraint since those should not affect // which impl we choose. self.lookup_trait_implementation_helper( &constraint_type, - constraint.trait_id, + constraint.trait_bound.trait_id, &trait_generics, &trait_associated_types, // Use a fresh set of type bindings here since the constraint_type originates from @@ -2016,6 +2025,10 @@ impl NodeInterner { self.add_dependency(dependent, DependencyId::Alias(dependency)); } + pub fn add_trait_dependency(&mut self, dependent: DependencyId, dependency: TraitId) { + self.add_dependency(dependent, DependencyId::Trait(dependency)); + } + pub fn add_dependency(&mut self, dependent: DependencyId, dependency: DependencyId) { let dependent_index = self.get_or_insert_dependency(dependent); let dependency_index = self.get_or_insert_dependency(dependency); @@ -2071,6 +2084,11 @@ impl NodeInterner { push_error(alias.name.to_string(), &scc, i, alias.location); break; } + DependencyId::Trait(trait_id) => { + let the_trait = self.get_trait(trait_id); + push_error(the_trait.name.to_string(), &scc, i, the_trait.location); + break; + } // Mutually recursive functions are allowed DependencyId::Function(_) => (), // Local variables should never be in a dependency cycle, scoping rules @@ -2099,6 +2117,7 @@ impl NodeInterner { DependencyId::Global(id) => { Cow::Borrowed(self.get_global(id).ident.0.contents.as_ref()) } + DependencyId::Trait(id) => Cow::Owned(self.get_trait(id).name.to_string()), DependencyId::Variable(loc) => { unreachable!("Variable used at location {loc:?} caught in a dependency cycle") } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/errors.rs index f9cc539d7b7..3315b38a351 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/errors.rs @@ -174,12 +174,17 @@ impl ParserError { impl std::fmt::Display for ParserError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let token_to_string = |token: &Token| match token { + Token::EOF => token.to_string(), + _ => format!("'{}'", token), + }; + let reason_str: String = if self.reason.is_none() { "".to_string() } else { format!("\nreason: {}", Diagnostic::from(self)) }; - let mut expected = vecmap(&self.expected_tokens, ToString::to_string); + let mut expected = vecmap(&self.expected_tokens, token_to_string); expected.append(&mut vecmap(&self.expected_labels, |label| format!("{label}"))); if expected.is_empty() { @@ -192,7 +197,7 @@ impl std::fmt::Display for ParserError { "Expected a{} {} but found {}{}", if vowel { "n" } else { "" }, first, - self.found, + token_to_string(&self.found), reason_str ) } else { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/labels.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/labels.rs index 5c9ec236e07..604c87801f8 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/labels.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/labels.rs @@ -11,6 +11,7 @@ pub enum ParsingRuleLabel { Cast, Expression, FieldAccess, + Function, GenericParameter, Global, Identifier, @@ -41,6 +42,7 @@ impl fmt::Display for ParsingRuleLabel { ParsingRuleLabel::Cast => write!(f, "cast"), ParsingRuleLabel::Expression => write!(f, "expression"), ParsingRuleLabel::FieldAccess => write!(f, "field access"), + ParsingRuleLabel::Function => write!(f, "function"), ParsingRuleLabel::GenericParameter => write!(f, "generic parameter"), ParsingRuleLabel::Global => write!(f, "global"), ParsingRuleLabel::Identifier => write!(f, "identifier"), diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs index 21c182a52cd..17c156476a7 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs @@ -20,7 +20,7 @@ use crate::token::SecondaryAttribute; pub use errors::ParserError; pub use errors::ParserErrorReason; use noirc_errors::Span; -pub use parser::{parse_program, Parser}; +pub use parser::{parse_program, Parser, StatementOrExpressionOrLValue}; #[derive(Clone, Default)] pub struct SortedModule { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs index d0b0579ce24..0030144b5e1 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs @@ -3,7 +3,7 @@ use modifiers::Modifiers; use noirc_errors::Span; use crate::{ - ast::{Ident, ItemVisibility, LValue}, + ast::{Ident, ItemVisibility}, lexer::{Lexer, SpannedTokenResult}, token::{IntType, Keyword, SpannedToken, Token, TokenKind, Tokens}, }; @@ -28,6 +28,7 @@ mod parse_many; mod path; mod pattern; mod statement; +mod statement_or_expression_or_lvalue; mod structs; mod tests; mod traits; @@ -37,6 +38,8 @@ mod types; mod use_tree; mod where_clause; +pub use statement_or_expression_or_lvalue::StatementOrExpressionOrLValue; + /// Entry function for the parser - also handles lexing internally. /// /// Given a source_program string, return the ParsedModule Ast representation @@ -124,28 +127,6 @@ impl<'a> Parser<'a> { ParsedModule { items, inner_doc_comments } } - /// Parses an LValue. If an LValue can't be parsed, an error is recorded and a default LValue is returned. - pub(crate) fn parse_lvalue_or_error(&mut self) -> LValue { - let start_span = self.current_token_span; - - if let Some(token) = self.eat_kind(TokenKind::InternedLValue) { - match token.into_token() { - Token::InternedLValue(lvalue) => { - return LValue::Interned(lvalue, self.span_since(start_span)); - } - _ => unreachable!(), - } - } - - let expr = self.parse_expression_or_error(); - if let Some(lvalue) = LValue::from_expression(expr) { - lvalue - } else { - self.expected_label(ParsingRuleLabel::LValue); - LValue::Ident(Ident::default()) - } - } - /// Invokes `parsing_function` (`parsing_function` must be some `parse_*` method of the parser) /// and returns the result if the parser has no errors, and if the parser consumed all tokens. /// Otherwise returns the list of errors. diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/attributes.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/attributes.rs index d0f7221a6be..ffba74003b7 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/attributes.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/attributes.rs @@ -55,10 +55,10 @@ mod tests { #[test] fn parses_inner_attribute() { - let src = "#![hello]"; + let src = "#!['hello]"; let mut parser = Parser::for_str(src); - let Some(SecondaryAttribute::Custom(custom)) = parser.parse_inner_attribute() else { - panic!("Expected inner custom attribute"); + let Some(SecondaryAttribute::Tag(custom)) = parser.parse_inner_attribute() else { + panic!("Expected inner tag attribute"); }; expect_no_errors(&parser.errors); assert_eq!(custom.contents, "hello"); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/expression.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/expression.rs index e2b942faebf..93bb4980801 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/expression.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/expression.rs @@ -428,6 +428,12 @@ impl<'a> Parser<'a> { Some(if self.eat_colon() { let expression = self.parse_expression_or_error(); (ident, expression) + } else if self.at(Token::Assign) { + // If we find '=' instead of ':', assume the user meant ':`, error and continue + self.expected_token(Token::Colon); + self.bump(); + let expression = self.parse_expression_or_error(); + (ident, expression) } else { (ident.clone(), ident.into()) }) @@ -1337,6 +1343,34 @@ mod tests { assert_eq!(expr.to_string(), "2"); } + #[test] + fn parses_constructor_with_fields_recovers_if_assign_instead_of_colon() { + let src = " + Foo { x = 1, y } + ^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + let expr = parser.parse_expression_or_error(); + + let error = get_single_error(&parser.errors, span); + assert_eq!(error.to_string(), "Expected a ':' but found '='"); + + let ExpressionKind::Constructor(mut constructor) = expr.kind else { + panic!("Expected constructor"); + }; + assert_eq!(constructor.typ.to_string(), "Foo"); + assert_eq!(constructor.fields.len(), 2); + + let (name, expr) = constructor.fields.remove(0); + assert_eq!(name.to_string(), "x"); + assert_eq!(expr.to_string(), "1"); + + let (name, expr) = constructor.fields.remove(0); + assert_eq!(name.to_string(), "y"); + assert_eq!(expr.to_string(), "y"); + } + #[test] fn parses_parses_if_true() { let src = "if true { 1 }"; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/function.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/function.rs index acec7942e24..a60bc6e7c1d 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/function.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/function.rs @@ -442,7 +442,7 @@ mod tests { assert_eq!(noir_function.parameters().len(), 1); let error = get_single_error(&errors, span); - assert_eq!(error.to_string(), "Expected a pattern but found 1"); + assert_eq!(error.to_string(), "Expected a pattern but found '1'"); } #[test] @@ -478,7 +478,7 @@ mod tests { assert_eq!(noir_function.parameters().len(), 2); let error = get_single_error(&errors, span); - assert_eq!(error.to_string(), "Expected a type but found ,"); + assert_eq!(error.to_string(), "Expected a type but found ','"); } #[test] diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/global.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/global.rs index 2ea6457dc0b..d24064673b1 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/global.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/global.rs @@ -163,6 +163,6 @@ mod tests { let (src, span) = get_source_with_error_span(src); let (_, errors) = parse_program(&src); let error = get_single_error(&errors, span); - assert_eq!(error.to_string(), "Expected a ; but found end of input"); + assert_eq!(error.to_string(), "Expected a ';' but found end of input"); } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/impls.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/impls.rs index 460c7bded15..9215aec2742 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/impls.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/impls.rs @@ -7,7 +7,7 @@ use crate::{ UnresolvedGeneric, UnresolvedType, UnresolvedTypeData, }, parser::{labels::ParsingRuleLabel, ParserErrorReason}, - token::{Keyword, Token, TokenKind}, + token::{Keyword, Token}, }; use super::{parse_many::without_separator, Parser}; @@ -79,31 +79,28 @@ impl<'a> Parser<'a> { } fn parse_type_impl_method(&mut self) -> Option<(Documented, Span)> { - self.parse_item_in_list( - ParsingRuleLabel::TokenKind(TokenKind::Token(Token::Keyword(Keyword::Fn))), - |parser| { - let doc_comments = parser.parse_outer_doc_comments(); - let start_span = parser.current_token_span; - let attributes = parser.parse_attributes(); - let modifiers = parser.parse_modifiers( - false, // allow mutable - ); + self.parse_item_in_list(ParsingRuleLabel::Function, |parser| { + let doc_comments = parser.parse_outer_doc_comments(); + let start_span = parser.current_token_span; + let attributes = parser.parse_attributes(); + let modifiers = parser.parse_modifiers( + false, // allow mutable + ); - if parser.eat_keyword(Keyword::Fn) { - let method = parser.parse_function( - attributes, - modifiers.visibility, - modifiers.comptime.is_some(), - modifiers.unconstrained.is_some(), - true, // allow_self - ); - Some((Documented::new(method, doc_comments), parser.span_since(start_span))) - } else { - parser.modifiers_not_followed_by_an_item(modifiers); - None - } - }, - ) + if parser.eat_keyword(Keyword::Fn) { + let method = parser.parse_function( + attributes, + modifiers.visibility, + modifiers.comptime.is_some(), + modifiers.unconstrained.is_some(), + true, // allow_self + ); + Some((Documented::new(method, doc_comments), parser.span_since(start_span))) + } else { + parser.modifiers_not_followed_by_an_item(modifiers); + None + } + }) } /// TraitImpl = 'impl' Generics Path GenericTypeArgs 'for' Type TraitImplBody @@ -542,7 +539,7 @@ mod tests { assert_eq!(type_impl.methods.len(), 1); let error = get_single_error(&errors, span); - assert_eq!(error.to_string(), "Expected a fn but found hello"); + assert_eq!(error.to_string(), "Expected a function but found 'hello'"); } #[test] @@ -562,6 +559,6 @@ mod tests { assert_eq!(trait_imp.items.len(), 1); let error = get_single_error(&errors, span); - assert_eq!(error.to_string(), "Expected a trait impl item but found hello"); + assert_eq!(error.to_string(), "Expected a trait impl item but found 'hello'"); } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/item.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/item.rs index 0faa2ba80ee..4fbcd7abac5 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/item.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/item.rs @@ -224,7 +224,7 @@ mod tests { let (module, errors) = parse_program(&src); assert_eq!(module.items.len(), 2); let error = get_single_error(&errors, span); - assert_eq!(error.to_string(), "Expected an item but found hello"); + assert_eq!(error.to_string(), "Expected an item but found 'hello'"); } #[test] @@ -237,6 +237,6 @@ mod tests { let (module, errors) = parse_program(&src); assert_eq!(module.items.len(), 1); let error = get_single_error(&errors, span); - assert_eq!(error.to_string(), "Expected a } but found end of input"); + assert_eq!(error.to_string(), "Expected a '}' but found end of input"); } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/item_visibility.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/item_visibility.rs index 1731284e354..5aea5f6a45f 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/item_visibility.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/item_visibility.rs @@ -73,7 +73,7 @@ mod tests { let visibility = parser.parse_item_visibility(); assert_eq!(visibility, ItemVisibility::Public); let error = get_single_error(&parser.errors, span); - assert_eq!(error.to_string(), "Expected a crate but found end of input"); + assert_eq!(error.to_string(), "Expected a 'crate' but found end of input"); } #[test] @@ -87,7 +87,7 @@ mod tests { let visibility = parser.parse_item_visibility(); assert_eq!(visibility, ItemVisibility::Public); let error = get_single_error(&parser.errors, span); - assert_eq!(error.to_string(), "Expected a crate but found hello"); + assert_eq!(error.to_string(), "Expected a 'crate' but found 'hello'"); } #[test] fn parses_public_visibility_missing_paren_after_pub_crate() { @@ -100,7 +100,7 @@ mod tests { let visibility = parser.parse_item_visibility(); assert_eq!(visibility, ItemVisibility::PublicCrate); let error = get_single_error(&parser.errors, span); - assert_eq!(error.to_string(), "Expected a ) but found end of input"); + assert_eq!(error.to_string(), "Expected a ')' but found end of input"); } #[test] diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/pattern.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/pattern.rs index a10fe18fd79..c4dcab55d73 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/pattern.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/pattern.rs @@ -217,6 +217,11 @@ impl<'a> Parser<'a> { Some(if self.eat_colon() { (ident, self.parse_pattern_or_error()) + } else if self.at(Token::Assign) { + // If we find '=' instead of ':', assume the user meant ':`, error and continue + self.expected_token(Token::Colon); + self.bump(); + (ident, self.parse_pattern_or_error()) } else { (ident.clone(), Pattern::Identifier(ident)) }) @@ -252,7 +257,8 @@ mod tests { ast::Pattern, parser::{ parser::tests::{ - expect_no_errors, get_single_error_reason, get_source_with_error_span, + expect_no_errors, get_single_error, get_single_error_reason, + get_source_with_error_span, }, Parser, ParserErrorReason, }, @@ -342,6 +348,34 @@ mod tests { assert_eq!(pattern.to_string(), "y"); } + #[test] + fn parses_struct_pattern_recovers_if_assign_instead_of_colon() { + let src = " + foo::Bar { x = one, y } + ^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + let pattern = parser.parse_pattern_or_error(); + + let error = get_single_error(&parser.errors, span); + assert_eq!(error.to_string(), "Expected a ':' but found '='"); + + let Pattern::Struct(path, mut patterns, _) = pattern else { + panic!("Expected a struct pattern") + }; + assert_eq!(path.to_string(), "foo::Bar"); + assert_eq!(patterns.len(), 2); + + let (ident, pattern) = patterns.remove(0); + assert_eq!(ident.to_string(), "x"); + assert_eq!(pattern.to_string(), "one"); + + let (ident, pattern) = patterns.remove(0); + assert_eq!(ident.to_string(), "y"); + assert_eq!(pattern.to_string(), "y"); + } + #[test] fn parses_unclosed_struct_pattern() { let src = "foo::Bar { x"; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/statement.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/statement.rs index d118be5d54a..ff6117c9348 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/statement.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/statement.rs @@ -702,7 +702,7 @@ mod tests { let statement = parser.parse_statement_or_error(); assert!(matches!(statement.kind, StatementKind::Let(..))); let error = get_single_error(&parser.errors, span); - assert_eq!(error.to_string(), "Expected a statement but found ]"); + assert_eq!(error.to_string(), "Expected a statement but found ']'"); } #[test] diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/statement_or_expression_or_lvalue.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/statement_or_expression_or_lvalue.rs new file mode 100644 index 00000000000..fdc187f3fb2 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/statement_or_expression_or_lvalue.rs @@ -0,0 +1,55 @@ +use crate::{ + ast::{AssignStatement, Expression, LValue, Statement, StatementKind}, + token::{Token, TokenKind}, +}; + +use super::Parser; + +#[derive(Debug)] +pub enum StatementOrExpressionOrLValue { + Statement(Statement), + Expression(Expression), + LValue(LValue), +} + +impl<'a> Parser<'a> { + /// Parses either a statement, an expression or an LValue. Returns `StatementKind::Error` + /// if none can be parsed, recording an error if so. + /// + /// This method is only used in `Quoted::as_expr`. + pub(crate) fn parse_statement_or_expression_or_lvalue( + &mut self, + ) -> StatementOrExpressionOrLValue { + let start_span = self.current_token_span; + + // First check if it's an interned LValue + if let Some(token) = self.eat_kind(TokenKind::InternedLValue) { + match token.into_token() { + Token::InternedLValue(lvalue) => { + let lvalue = LValue::Interned(lvalue, self.span_since(start_span)); + + // If it is, it could be something like `lvalue = expr`: check that. + if self.eat(Token::Assign) { + let expression = self.parse_expression_or_error(); + let kind = StatementKind::Assign(AssignStatement { lvalue, expression }); + return StatementOrExpressionOrLValue::Statement(Statement { + kind, + span: self.span_since(start_span), + }); + } else { + return StatementOrExpressionOrLValue::LValue(lvalue); + } + } + _ => unreachable!(), + } + } + + // Otherwise, check if it's a statement (which in turn checks if it's an expression) + let statement = self.parse_statement_or_error(); + if let StatementKind::Expression(expr) = statement.kind { + StatementOrExpressionOrLValue::Expression(expr) + } else { + StatementOrExpressionOrLValue::Statement(statement) + } + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/structs.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/structs.rs index 6775cf35a78..da8ac64e021 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/structs.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/structs.rs @@ -61,17 +61,24 @@ impl<'a> Parser<'a> { fn parse_struct_field(&mut self) -> Option> { let mut doc_comments; let name; + let mut visibility; // Loop until we find an identifier, skipping anything that's not one loop { let doc_comments_start_span = self.current_token_span; doc_comments = self.parse_outer_doc_comments(); + visibility = self.parse_item_visibility(); + if let Some(ident) = self.eat_ident() { name = ident; break; } + if visibility != ItemVisibility::Private { + self.expected_identifier(); + } + if !doc_comments.is_empty() { self.push_error( ParserErrorReason::DocCommentDoesNotDocumentAnything, @@ -97,7 +104,7 @@ impl<'a> Parser<'a> { self.eat_or_error(Token::Colon); let typ = self.parse_type_or_error(); - Some(Documented::new(StructField { name, typ }, doc_comments)) + Some(Documented::new(StructField { visibility, name, typ }, doc_comments)) } fn empty_struct( @@ -265,6 +272,6 @@ mod tests { assert_eq!(noir_struct.fields.len(), 1); let error = get_single_error(&errors, span); - assert_eq!(error.to_string(), "Expected an identifier but found 42"); + assert_eq!(error.to_string(), "Expected an identifier but found '42'"); } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/traits.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/traits.rs index 3bae152e75f..fead6a34c82 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/traits.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/traits.rs @@ -11,7 +11,7 @@ use super::parse_many::without_separator; use super::Parser; impl<'a> Parser<'a> { - /// Trait = 'trait' identifier Generics WhereClause TraitBody + /// Trait = 'trait' identifier Generics ( ':' TraitBounds )? WhereClause TraitBody pub(crate) fn parse_trait( &mut self, attributes: Vec<(Attribute, Span)>, @@ -26,12 +26,14 @@ impl<'a> Parser<'a> { }; let generics = self.parse_generics(); + let bounds = if self.eat_colon() { self.parse_trait_bounds() } else { Vec::new() }; let where_clause = self.parse_where_clause(); let items = self.parse_trait_body(); NoirTrait { name, generics, + bounds, where_clause, span: self.span_since(start_span), items, @@ -180,6 +182,7 @@ fn empty_trait( NoirTrait { name: Ident::default(), generics: Vec::new(), + bounds: Vec::new(), where_clause: Vec::new(), span, items: Vec::new(), @@ -292,4 +295,16 @@ mod tests { }; assert!(body.is_some()); } + + #[test] + fn parse_trait_inheirtance() { + let src = "trait Foo: Bar + Baz {}"; + let noir_trait = parse_trait_no_errors(src); + assert_eq!(noir_trait.bounds.len(), 2); + + assert_eq!(noir_trait.bounds[0].to_string(), "Bar"); + assert_eq!(noir_trait.bounds[1].to_string(), "Baz"); + + assert_eq!(noir_trait.to_string(), "trait Foo: Bar + Baz {\n}"); + } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/type_expression.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/type_expression.rs index c3f27d9d49a..7dd59aedb45 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/type_expression.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/type_expression.rs @@ -1,14 +1,11 @@ use crate::{ - ast::{ - Expression, ExpressionKind, GenericTypeArgs, Literal, UnresolvedType, UnresolvedTypeData, - UnresolvedTypeExpression, - }, - parser::{labels::ParsingRuleLabel, ParserError, ParserErrorReason}, + ast::{GenericTypeArgs, UnresolvedType, UnresolvedTypeData, UnresolvedTypeExpression}, + parser::{labels::ParsingRuleLabel, ParserError}, token::Token, BinaryTypeOperator, }; -use acvm::acir::AcirField; +use acvm::acir::{AcirField, FieldElement}; use noirc_errors::Span; use super::{parse_many::separated_by_comma_until_right_paren, Parser}; @@ -119,7 +116,7 @@ impl<'a> Parser<'a> { if self.eat(Token::Minus) { return match self.parse_term_type_expression() { Some(rhs) => { - let lhs = UnresolvedTypeExpression::Constant(0, start_span); + let lhs = UnresolvedTypeExpression::Constant(FieldElement::zero(), start_span); let op = BinaryTypeOperator::Subtraction; let span = self.span_since(start_span); Some(UnresolvedTypeExpression::BinaryOperation( @@ -165,20 +162,6 @@ impl<'a> Parser<'a> { return None; }; - let int = if let Some(int) = int.try_to_u32() { - int - } else { - let err_expr = Expression { - kind: ExpressionKind::Literal(Literal::Integer(int, false)), - span: self.previous_token_span, - }; - self.push_error( - ParserErrorReason::InvalidTypeExpression(err_expr), - self.previous_token_span, - ); - 0 - }; - Some(UnresolvedTypeExpression::Constant(int, self.previous_token_span)) } @@ -267,7 +250,7 @@ impl<'a> Parser<'a> { // If we ate '-' what follows must be a type expression, never a type return match self.parse_term_type_expression() { Some(rhs) => { - let lhs = UnresolvedTypeExpression::Constant(0, start_span); + let lhs = UnresolvedTypeExpression::Constant(FieldElement::zero(), start_span); let op = BinaryTypeOperator::Subtraction; let span = self.span_since(start_span); let type_expr = UnresolvedTypeExpression::BinaryOperation( @@ -444,7 +427,7 @@ mod tests { let UnresolvedTypeExpression::Constant(n, _) = expr else { panic!("Expected constant"); }; - assert_eq!(n, 42); + assert_eq!(n, 42_u32.into()); } #[test] @@ -496,7 +479,7 @@ mod tests { let UnresolvedTypeExpression::Constant(n, _) = expr else { panic!("Expected constant"); }; - assert_eq!(n, 42); + assert_eq!(n, 42_u32.into()); } #[test] diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/types.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/types.rs index 6702704d32c..be3d5287cab 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/types.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/types.rs @@ -1,3 +1,5 @@ +use acvm::{AcirField, FieldElement}; + use crate::{ ast::{UnresolvedType, UnresolvedTypeData, UnresolvedTypeExpression}, parser::{labels::ParsingRuleLabel, ParserErrorReason}, @@ -137,7 +139,8 @@ impl<'a> Parser<'a> { if !self.eat_less() { self.expected_token(Token::Less); - let expr = UnresolvedTypeExpression::Constant(0, self.current_token_span); + let expr = + UnresolvedTypeExpression::Constant(FieldElement::zero(), self.current_token_span); return Some(UnresolvedTypeData::String(expr)); } @@ -145,7 +148,7 @@ impl<'a> Parser<'a> { Ok(expr) => expr, Err(error) => { self.errors.push(error); - UnresolvedTypeExpression::Constant(0, self.current_token_span) + UnresolvedTypeExpression::Constant(FieldElement::zero(), self.current_token_span) } }; @@ -161,7 +164,8 @@ impl<'a> Parser<'a> { if !self.eat_less() { self.expected_token(Token::Less); - let expr = UnresolvedTypeExpression::Constant(0, self.current_token_span); + let expr = + UnresolvedTypeExpression::Constant(FieldElement::zero(), self.current_token_span); let typ = UnresolvedTypeData::Error.with_span(self.span_at_previous_token_end()); return Some(UnresolvedTypeData::FormatString(expr, Box::new(typ))); } @@ -170,7 +174,7 @@ impl<'a> Parser<'a> { Ok(expr) => expr, Err(error) => { self.errors.push(error); - UnresolvedTypeExpression::Constant(0, self.current_token_span) + UnresolvedTypeExpression::Constant(FieldElement::zero(), self.current_token_span) } }; @@ -584,7 +588,7 @@ mod tests { let mut parser = Parser::for_str(&src); parser.parse_type(); let error = get_single_error(&parser.errors, span); - assert_eq!(error.to_string(), "Expected a ] but found end of input"); + assert_eq!(error.to_string(), "Expected a ']' but found end of input"); } #[test] diff --git a/noir/noir-repo/compiler/noirc_frontend/src/tests.rs b/noir/noir-repo/compiler/noirc_frontend/src/tests.rs index 65841b643c4..17acd17dcc9 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/tests.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/tests.rs @@ -4,6 +4,7 @@ mod bound_checks; mod imports; mod name_shadowing; mod references; +mod traits; mod turbofish; mod unused_items; mod visibility; @@ -194,7 +195,7 @@ fn check_trait_implementation_duplicate_method() { x + 2 * y } } - + fn main() { let _ = Foo { bar: 1, array: [2, 3] }; // silence Foo never constructed warning }"; @@ -643,7 +644,7 @@ fn check_impl_struct_not_trait() { Self { bar: x, array: [x,y] } } } - + fn main() { let _ = Default { x: 1, z: 1 }; // silence Default never constructed warning } @@ -1571,7 +1572,7 @@ fn struct_numeric_generic_in_function() { inner: u64 } - pub fn bar() { + pub fn bar() { let _ = Foo { inner: 1 }; // silence Foo never constructed warning } "#; @@ -1946,6 +1947,125 @@ fn numeric_generic_used_in_turbofish() { assert_no_errors(src); } +// TODO(https://github.com/noir-lang/noir/issues/6245): +// allow u16 to be used as an array size +#[test] +fn numeric_generic_u16_array_size() { + let src = r#" + fn len(_arr: [Field; N]) -> u32 { + N + } + + pub fn foo() -> u32 { + let fields: [Field; N] = [0; N]; + len(fields) + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 2); + assert!(matches!( + errors[0].0, + CompilationError::TypeError(TypeCheckError::TypeKindMismatch { .. }), + )); + assert!(matches!( + errors[1].0, + CompilationError::TypeError(TypeCheckError::TypeKindMismatch { .. }), + )); +} + +// TODO(https://github.com/noir-lang/noir/issues/6238): +// The EvaluatedGlobalIsntU32 warning is a stopgap +// (originally from https://github.com/noir-lang/noir/issues/6125) +#[test] +fn numeric_generic_field_larger_than_u32() { + let src = r#" + global A: Field = 4294967297; + + fn foo() { } + + fn main() { + let _ = foo::(); + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 2); + assert!(matches!( + errors[0].0, + CompilationError::TypeError(TypeCheckError::EvaluatedGlobalIsntU32 { .. }), + )); + assert!(matches!( + errors[1].0, + CompilationError::ResolverError(ResolverError::IntegerTooLarge { .. }) + )); +} + +// TODO(https://github.com/noir-lang/noir/issues/6238): +// The EvaluatedGlobalIsntU32 warning is a stopgap +// (originally from https://github.com/noir-lang/noir/issues/6126) +#[test] +fn numeric_generic_field_arithmetic_larger_than_u32() { + let src = r#" + struct Foo {} + + impl Foo { + fn size(self) -> Field { + F + } + } + + // 2^32 - 1 + global A: Field = 4294967295; + + fn foo() -> Foo { + Foo {} + } + + fn main() { + let _ = foo::().size(); + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 2); + + assert!(matches!( + errors[0].0, + CompilationError::TypeError(TypeCheckError::EvaluatedGlobalIsntU32 { .. }), + )); + + assert!(matches!( + errors[1].0, + CompilationError::ResolverError(ResolverError::UnusedVariable { .. }) + )); +} + +#[test] +fn cast_256_to_u8_size_checks() { + let src = r#" + fn main() { + assert(256 as u8 == 0); + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + assert!(matches!( + errors[0].0, + CompilationError::TypeError(TypeCheckError::DownsizingCast { .. }), + )); +} + +// TODO(https://github.com/noir-lang/noir/issues/6247): +// add negative integer literal checks +#[test] +fn cast_negative_one_to_u8_size_checks() { + let src = r#" + fn main() { + assert((-1) as u8 != 0); + } + "#; + let errors = get_program_errors(src); + assert!(errors.is_empty()); +} + #[test] fn constant_used_with_numeric_generic() { let src = r#" @@ -2036,17 +2156,31 @@ fn numeric_generics_type_kind_mismatch() { } global M: u16 = 3; - + fn main() { let _ = bar::(); } "#; let errors = get_program_errors(src); - assert_eq!(errors.len(), 1); + assert_eq!(errors.len(), 3); + + // TODO(https://github.com/noir-lang/noir/issues/6238): + // The EvaluatedGlobalIsntU32 warning is a stopgap assert!(matches!( errors[0].0, + CompilationError::TypeError(TypeCheckError::EvaluatedGlobalIsntU32 { .. }), + )); + + assert!(matches!( + errors[1].0, CompilationError::TypeError(TypeCheckError::TypeKindMismatch { .. }), )); + + // TODO(https://github.com/noir-lang/noir/issues/6238): see above + assert!(matches!( + errors[2].0, + CompilationError::TypeError(TypeCheckError::EvaluatedGlobalIsntU32 { .. }), + )); } #[test] @@ -2060,7 +2194,7 @@ fn numeric_generics_value_kind_mismatch_u32_u64() { } impl BoundedVec { - pub fn extend_from_bounded_vec(&mut self, _vec: BoundedVec) { + pub fn extend_from_bounded_vec(&mut self, _vec: BoundedVec) { // We do this to avoid an unused variable warning on `self` let _ = self.len; for _ in 0..Len { } @@ -2351,7 +2485,7 @@ fn trait_impl_where_clause_stricter_pass() { fn bad_foo() where A: OtherTrait { } } - + fn main() { let _ = Option { inner: 1 }; // silence Option never constructed warning } @@ -3116,8 +3250,10 @@ fn struct_array_len() { )); } +// TODO(https://github.com/noir-lang/noir/issues/6245): +// support u16 as an array size #[test] -fn non_u32_in_array_length() { +fn non_u32_as_array_length() { let src = r#" global ARRAY_LEN: u8 = 3; @@ -3127,10 +3263,13 @@ fn non_u32_in_array_length() { "#; let errors = get_program_errors(src); - assert_eq!(errors.len(), 1); - + assert_eq!(errors.len(), 2); assert!(matches!( errors[0].0, + CompilationError::TypeError(TypeCheckError::EvaluatedGlobalIsntU32 { .. }) + )); + assert!(matches!( + errors[1].0, CompilationError::TypeError(TypeCheckError::TypeKindMismatch { .. }) )); } @@ -3185,17 +3324,17 @@ fn trait_unconstrained_methods_typechecked_correctly() { self } - unconstrained fn foo(self) -> u64; + unconstrained fn foo(self) -> Field; } - impl Foo for Field { - unconstrained fn foo(self) -> u64 { - self as u64 + impl Foo for u64 { + unconstrained fn foo(self) -> Field { + self as Field } } unconstrained fn main() { - assert_eq(2.foo() as Field, 2.identity()); + assert_eq(2.foo(), 2.identity() as Field); } "#; @@ -3203,3 +3342,50 @@ fn trait_unconstrained_methods_typechecked_correctly() { println!("{errors:?}"); assert_eq!(errors.len(), 0); } + +#[test] +fn error_if_attribute_not_in_scope() { + let src = r#" + #[not_in_scope] + fn main() {} + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + assert!(matches!( + errors[0].0, + CompilationError::ResolverError(ResolverError::AttributeFunctionNotInScope { .. }) + )); +} + +#[test] +fn arithmetic_generics_rounding_pass() { + let src = r#" + fn main() { + // 3/2*2 = 2 + round::<3, 2>([1, 2]); + } + + fn round(_x: [Field; N / M * M]) {} + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 0); +} + +#[test] +fn arithmetic_generics_rounding_fail() { + let src = r#" + fn main() { + // Do not simplify N/M*M to just N + // This should be 3/2*2 = 2, not 3 + round::<3, 2>([1, 2, 3]); + } + + fn round(_x: [Field; N / M * M]) {} + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/tests/traits.rs b/noir/noir-repo/compiler/noirc_frontend/src/tests/traits.rs new file mode 100644 index 00000000000..ee84cc0e890 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/tests/traits.rs @@ -0,0 +1,150 @@ +use crate::hir::def_collector::dc_crate::CompilationError; +use crate::hir::resolution::errors::ResolverError; +use crate::tests::get_program_errors; + +use super::assert_no_errors; + +#[test] +fn trait_inheritance() { + let src = r#" + pub trait Foo { + fn foo(self) -> Field; + } + + pub trait Bar { + fn bar(self) -> Field; + } + + pub trait Baz: Foo + Bar { + fn baz(self) -> Field; + } + + pub fn foo(baz: T) -> (Field, Field, Field) where T: Baz { + (baz.foo(), baz.bar(), baz.baz()) + } + + fn main() {} + "#; + assert_no_errors(src); +} + +#[test] +fn trait_inheritance_with_generics() { + let src = r#" + trait Foo { + fn foo(self) -> T; + } + + trait Bar: Foo { + fn bar(self); + } + + pub fn foo(x: T) -> i32 where T: Bar { + x.foo() + } + + fn main() {} + "#; + assert_no_errors(src); +} + +#[test] +fn trait_inheritance_with_generics_2() { + let src = r#" + pub trait Foo { + fn foo(self) -> T; + } + + pub trait Bar: Foo { + fn bar(self) -> (T, U); + } + + pub fn foo(x: T) -> i32 where T: Bar { + x.foo() + } + + fn main() {} + "#; + assert_no_errors(src); +} + +#[test] +fn trait_inheritance_with_generics_3() { + let src = r#" + trait Foo {} + + trait Bar: Foo {} + + impl Foo for () {} + + impl Bar for () {} + + fn main() {} + "#; + assert_no_errors(src); +} + +#[test] +fn trait_inheritance_with_generics_4() { + let src = r#" + trait Foo { type A; } + + trait Bar: Foo {} + + impl Foo for () { type A = i32; } + + impl Bar for () {} + + fn main() {} + "#; + assert_no_errors(src); +} + +#[test] +fn trait_inheritance_dependency_cycle() { + let src = r#" + trait Foo: Bar {} + trait Bar: Foo {} + fn main() {} + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + assert!(matches!( + errors[0].0, + CompilationError::ResolverError(ResolverError::DependencyCycle { .. }) + )); +} + +#[test] +fn trait_inheritance_missing_parent_implementation() { + let src = r#" + pub trait Foo {} + + pub trait Bar: Foo {} + + pub struct Struct {} + + impl Bar for Struct {} + + fn main() { + let _ = Struct {}; // silence Struct never constructed warning + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::ResolverError(ResolverError::TraitNotImplemented { + impl_trait, + missing_trait: the_trait, + type_missing_trait: typ, + .. + }) = &errors[0].0 + else { + panic!("Expected a TraitNotImplemented error, got {:?}", &errors[0].0); + }; + + assert_eq!(the_trait, "Foo"); + assert_eq!(typ, "Struct"); + assert_eq!(impl_trait, "Bar"); +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/tests/unused_items.rs b/noir/noir-repo/compiler/noirc_frontend/src/tests/unused_items.rs index 4d8e504b705..51bdf785688 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/tests/unused_items.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/tests/unused_items.rs @@ -210,3 +210,67 @@ fn warns_on_unused_global() { assert_eq!(ident.to_string(), "foo"); assert_eq!(item.item_type(), "global"); } + +#[test] +fn does_not_warn_on_unused_global_if_it_has_an_abi_attribute() { + let src = r#" + contract foo { + #[abi(notes)] + global bar = 1; + } + + fn main() {} + "#; + assert_no_errors(src); +} + +#[test] +fn no_warning_on_inner_struct_when_parent_is_used() { + let src = r#" + struct Bar { + inner: [Field; 3], + } + + struct Foo { + a: Field, + bar: Bar, + } + + fn main(foos: [Foo; 1]) { + assert_eq(foos[0].a, 10); + } + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 0); +} + +#[test] +fn no_warning_on_struct_if_it_has_an_abi_attribute() { + let src = r#" + #[abi(functions)] + struct Foo { + a: Field, + } + + fn main() {} + "#; + assert_no_errors(src); +} + +#[test] +fn no_warning_on_indirect_struct_if_it_has_an_abi_attribute() { + let src = r#" + struct Bar { + field: Field, + } + + #[abi(functions)] + struct Foo { + bar: Bar, + } + + fn main() {} + "#; + assert_no_errors(src); +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/tests/visibility.rs b/noir/noir-repo/compiler/noirc_frontend/src/tests/visibility.rs index 30a01818879..f02771b3760 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/tests/visibility.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/tests/visibility.rs @@ -106,6 +106,61 @@ fn errors_if_trying_to_access_public_function_inside_private_module() { assert_eq!(ident.to_string(), "bar"); } +#[test] +fn warns_if_calling_private_struct_method() { + let src = r#" + mod moo { + pub struct Foo {} + + impl Foo { + fn bar(self) { + let _ = self; + } + } + } + + pub fn method(foo: moo::Foo) { + foo.bar() + } + + fn main() {} + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::ResolverError(ResolverError::PathResolutionError( + PathResolutionError::Private(ident), + )) = &errors[0].0 + else { + panic!("Expected a private error"); + }; + + assert_eq!(ident.to_string(), "bar"); +} + +#[test] +fn does_not_warn_if_calling_pub_crate_struct_method_from_same_crate() { + let src = r#" + mod moo { + pub struct Foo {} + + impl Foo { + pub(crate) fn bar(self) { + let _ = self; + } + } + } + + pub fn method(foo: moo::Foo) { + foo.bar() + } + + fn main() {} + "#; + assert_no_errors(src); +} + #[test] fn does_not_error_if_calling_private_struct_function_from_same_struct() { let src = r#" @@ -146,3 +201,146 @@ fn does_not_error_if_calling_private_struct_function_from_same_module() { "#; assert_no_errors(src); } + +#[test] +fn error_when_accessing_private_struct_field() { + let src = r#" + mod moo { + pub struct Foo { + x: Field + } + } + + fn foo(foo: moo::Foo) -> Field { + foo.x + } + + fn main() {} + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::ResolverError(ResolverError::PathResolutionError( + PathResolutionError::Private(ident), + )) = &errors[0].0 + else { + panic!("Expected a private error"); + }; + + assert_eq!(ident.to_string(), "x"); +} + +#[test] +fn does_not_error_when_accessing_private_struct_field_from_nested_module() { + let src = r#" + struct Foo { + x: Field + } + + mod nested { + fn foo(foo: super::Foo) -> Field { + foo.x + } + } + + fn main() { + let _ = Foo { x: 1 }; + } + "#; + assert_no_errors(src); +} + +#[test] +fn does_not_error_when_accessing_pub_crate_struct_field_from_nested_module() { + let src = r#" + mod moo { + pub(crate) struct Foo { + pub(crate) x: Field + } + } + + fn foo(foo: moo::Foo) -> Field { + foo.x + } + + fn main() { + let _ = moo::Foo { x: 1 }; + } + "#; + assert_no_errors(src); +} + +#[test] +fn error_when_using_private_struct_field_in_constructor() { + let src = r#" + mod moo { + pub struct Foo { + x: Field + } + } + + fn main() { + let _ = moo::Foo { x: 1 }; + } + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::ResolverError(ResolverError::PathResolutionError( + PathResolutionError::Private(ident), + )) = &errors[0].0 + else { + panic!("Expected a private error"); + }; + + assert_eq!(ident.to_string(), "x"); +} + +#[test] +fn error_when_using_private_struct_field_in_struct_pattern() { + let src = r#" + mod moo { + pub struct Foo { + x: Field + } + } + + fn foo(foo: moo::Foo) -> Field { + let moo::Foo { x } = foo; + x + } + + fn main() { + } + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::ResolverError(ResolverError::PathResolutionError( + PathResolutionError::Private(ident), + )) = &errors[0].0 + else { + panic!("Expected a private error"); + }; + + assert_eq!(ident.to_string(), "x"); +} + +#[test] +fn does_not_error_if_referring_to_top_level_private_module_via_crate() { + let src = r#" + mod foo { + pub fn bar() {} + } + + use crate::foo::bar; + + fn main() { + bar() + } + "#; + assert_no_errors(src); +} diff --git a/noir/noir-repo/cspell.json b/noir/noir-repo/cspell.json index dbc5fb5a43e..6fd25a77182 100644 --- a/noir/noir-repo/cspell.json +++ b/noir/noir-repo/cspell.json @@ -194,8 +194,11 @@ "stdlib", "structs", "subexpression", + "subtrait", "subshell", "subtyping", + "supertrait", + "supertraits", "swcurve", "Taiko", "tarjan", diff --git a/noir/noir-repo/deny.toml b/noir/noir-repo/deny.toml index 2d6d3e658b5..3518628ee44 100644 --- a/noir/noir-repo/deny.toml +++ b/noir/noir-repo/deny.toml @@ -7,7 +7,7 @@ yanked = "warn" ignore = [ "RUSTSEC-2020-0168", # mach unmaintained - "RUSTSEC-2020-0016" # net2 unmaintained + "RUSTSEC-2020-0016", # net2 unmaintained ] # This section is considered when running `cargo deny check bans`. @@ -58,7 +58,7 @@ allow = [ # bitmaps 2.1.0, im 15.1.0 "MPL-2.0", # Boost Software License - "BSL-1.0" + "BSL-1.0", ] # Allow 1 or more licenses on a per-crate basis, so that particular licenses @@ -101,7 +101,4 @@ unknown-git = "deny" # # crates.io rejects git dependencies so anything depending on these is unpublishable and you'll ruin my day # when I find out. -allow-git = [ - "https://github.com/jfecher/chumsky", - "https://github.com/noir-lang/clap-markdown", -] +allow-git = ["https://github.com/noir-lang/clap-markdown"] diff --git a/noir/noir-repo/docs/docs/noir/concepts/data_types/index.md b/noir/noir-repo/docs/docs/noir/concepts/data_types/index.md index 11f51e2b65a..0f2db2b2d75 100644 --- a/noir/noir-repo/docs/docs/noir/concepts/data_types/index.md +++ b/noir/noir-repo/docs/docs/noir/concepts/data_types/index.md @@ -105,7 +105,7 @@ type Bad2 = Bad1; // ^^^^^^^^^^^ 'Bad2' recursively depends on itself: Bad2 -> Bad1 -> Bad2 ``` -By default, like functions, type aliases are private to the module the exist in. You can use `pub` +By default, like functions, type aliases are private to the module they exist in. You can use `pub` to make the type alias public or `pub(crate)` to make it public to just its crate: ```rust diff --git a/noir/noir-repo/docs/docs/noir/concepts/data_types/structs.md b/noir/noir-repo/docs/docs/noir/concepts/data_types/structs.md index e529347f27d..29951ae843a 100644 --- a/noir/noir-repo/docs/docs/noir/concepts/data_types/structs.md +++ b/noir/noir-repo/docs/docs/noir/concepts/data_types/structs.md @@ -69,7 +69,9 @@ fn get_octopus() -> Animal { The new variables can be bound with names different from the original struct field names, as showcased in the `legs --> feet` binding in the example above. -By default, like functions, structs are private to the module the exist in. You can use `pub` +### Visibility + +By default, like functions, structs are private to the module they exist in. You can use `pub` to make the struct public or `pub(crate)` to make it public to just its crate: ```rust @@ -79,4 +81,16 @@ pub struct Animal { legs: Field, eyes: u8, } +``` + +The same applies to struct fields: by default they are private to the module they exist in, +but they can be made `pub` or `pub(crate)`: + +```rust +// This struct is now public +pub struct Animal { + hands: Field, // private to its module + pub(crate) legs: Field, // accessible from the entire crate + pub eyes: u8, // accessible from anywhere +} ``` \ No newline at end of file diff --git a/noir/noir-repo/docs/docs/noir/concepts/globals.md b/noir/noir-repo/docs/docs/noir/concepts/globals.md index 1145c55dfc7..6b8314399a2 100644 --- a/noir/noir-repo/docs/docs/noir/concepts/globals.md +++ b/noir/noir-repo/docs/docs/noir/concepts/globals.md @@ -73,7 +73,7 @@ function performs side-effects like `println`, as these will still occur on each ### Visibility -By default, like functions, globals are private to the module the exist in. You can use `pub` +By default, like functions, globals are private to the module they exist in. You can use `pub` to make the global public or `pub(crate)` to make it public to just its crate: ```rust diff --git a/noir/noir-repo/docs/docs/noir/concepts/traits.md b/noir/noir-repo/docs/docs/noir/concepts/traits.md index 5d07e0c68f0..9da00a77587 100644 --- a/noir/noir-repo/docs/docs/noir/concepts/traits.md +++ b/noir/noir-repo/docs/docs/noir/concepts/traits.md @@ -464,9 +464,35 @@ Since we have an impl for our own type, the behavior of this code will not chang to provide its own `impl Default for Foo`. The downside of this pattern is that it requires extra wrapping and unwrapping of values when converting to and from the `Wrapper` and `Foo` types. +### Trait Inheritance + +Sometimes, you might need one trait to use another trait’s functionality (like "inheritance" in some other languages). In this case, you can specify this relationship by listing any child traits after the parent trait's name and a colon. Now, whenever the parent trait is implemented it will require the child traits to be implemented as well. A parent trait is also called a "super trait." + +```rust +trait Person { + fn name(self) -> String; +} + +// Person is a supertrait of Student. +// Implementing Student requires you to also impl Person. +trait Student: Person { + fn university(self) -> String; +} + +trait Programmer { + fn fav_language(self) -> String; +} + +// CompSciStudent (computer science student) is a subtrait of both Programmer +// and Student. Implementing CompSciStudent requires you to impl both supertraits. +trait CompSciStudent: Programmer + Student { + fn git_username(self) -> String; +} +``` + ### Visibility -By default, like functions, traits are private to the module the exist in. You can use `pub` +By default, like functions, traits are private to the module they exist in. You can use `pub` to make the trait public or `pub(crate)` to make it public to just its crate: ```rust diff --git a/noir/noir-repo/docs/docs/noir/modules_packages_crates/modules.md b/noir/noir-repo/docs/docs/noir/modules_packages_crates/modules.md index 05399c38b4c..14aa1f0579a 100644 --- a/noir/noir-repo/docs/docs/noir/modules_packages_crates/modules.md +++ b/noir/noir-repo/docs/docs/noir/modules_packages_crates/modules.md @@ -212,7 +212,7 @@ In this example, the module `some_module` re-exports two public names defined in ### Visibility -By default, like functions, modules are private to the module (or crate) the exist in. You can use `pub` +By default, like functions, modules are private to the module (or crate) they exist in. You can use `pub` to make the module public or `pub(crate)` to make it public to just its crate: ```rust diff --git a/noir/noir-repo/docs/docs/noir/standard_library/mem.md b/noir/noir-repo/docs/docs/noir/standard_library/mem.md new file mode 100644 index 00000000000..95d36ac2a72 --- /dev/null +++ b/noir/noir-repo/docs/docs/noir/standard_library/mem.md @@ -0,0 +1,52 @@ +--- +title: Memory Module +description: + This module contains functions which manipulate memory in a low-level way +keywords: + [ + mem, memory, zeroed, transmute, checked_transmute + ] +--- + +# `std::mem::zeroed` + +```rust +fn zeroed() -> T +``` + +Returns a zeroed value of any type. +This function is generally unsafe to use as the zeroed bit pattern is not guaranteed to be valid for all types. +It can however, be useful in cases when the value is guaranteed not to be used such as in a BoundedVec library implementing a growable vector, up to a certain length, backed by an array. +The array can be initialized with zeroed values which are guaranteed to be inaccessible until the vector is pushed to. +Similarly, enumerations in noir can be implemented using this method by providing zeroed values for the unused variants. + +This function currently supports the following types: + +- Field +- Bool +- Uint +- Array +- Slice +- String +- Tuple +- Functions + +Using it on other types could result in unexpected behavior. + +# `std::mem::checked_transmute` + +```rust +fn checked_transmute(value: T) -> U +``` + +Transmutes a value of one type into the same value but with a new type `U`. + +This function is safe to use since both types are asserted to be equal later during compilation after the concrete values for generic types become known. +This function is useful for cases where the compiler may fails a type check that is expected to pass where +a user knows the two types to be equal. For example, when using arithmetic generics there are cases the compiler +does not see as equal, such as `[Field; N*(A + B)]` and `[Field; N*A + N*B]`, which users may know to be equal. +In these cases, `checked_transmute` can be used to cast the value to the desired type while also preserving safety +by checking this equality once `N`, `A`, `B` are fully resolved. + +Note that since this safety check is performed after type checking rather than during, no error is issued if the function +containing `checked_transmute` is never called. diff --git a/noir/noir-repo/docs/docs/noir/standard_library/recursion.mdx b/noir/noir-repo/docs/docs/noir/standard_library/recursion.mdx new file mode 100644 index 00000000000..60414a2fa51 --- /dev/null +++ b/noir/noir-repo/docs/docs/noir/standard_library/recursion.mdx @@ -0,0 +1,85 @@ +--- +title: Recursive Proofs +description: Learn about how to write recursive proofs in Noir. +keywords: [recursion, recursive proofs, verification_key, verify_proof] +--- + +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; + +Noir supports recursively verifying proofs, meaning you verify the proof of a Noir program in another Noir program. This enables creating proofs of arbitrary size by doing step-wise verification of smaller components of a large proof. + +Read [the explainer on recursion](../../explainers/explainer-recursion.md) to know more about this function and the [guide on how to use it.](../../how_to/how-to-recursion.md) + +## The `#[recursive]` Attribute + +In Noir, the `#[recursive]` attribute is used to indicate that a circuit is designed for recursive proof generation. When applied, it informs the compiler and the tooling that the circuit should be compiled in a way that makes its proofs suitable for recursive verification. This attribute eliminates the need for manual flagging of recursion at the tooling level, streamlining the proof generation process for recursive circuits. + +### Example usage with `#[recursive]` + +```rust +#[recursive] +fn main(x: Field, y: pub Field) { + assert(x == y, "x and y are not equal"); +} + +// This marks the circuit as recursion-friendly and indicates that proofs generated from this circuit +// are intended for recursive verification. +``` + +By incorporating this attribute directly in the circuit's definition, tooling like Nargo and NoirJS can automatically execute recursive-specific duties for Noir programs (e.g. recursive-friendly proof artifact generation) without additional flags or configurations. + +## Verifying Recursive Proofs + +```rust +#[foreign(recursive_aggregation)] +pub fn verify_proof(verification_key: [Field], proof: [Field], public_inputs: [Field], key_hash: Field) {} +``` + + + +## Example usage + +```rust + +fn main( + verification_key : [Field; 114], + proof : [Field; 93], + public_inputs : [Field; 1], + key_hash : Field, + proof_b : [Field; 93], +) { + std::verify_proof( + verification_key, + proof, + public_inputs, + key_hash + ); + + std::verify_proof( + verification_key, + proof_b, + public_inputs, + key_hash + ); +} +``` + +You can see a full example of recursive proofs in [this example recursion demo repo](https://github.com/noir-lang/noir-examples/tree/master/recursion). + +## Parameters + +### `verification_key` + +The verification key for the zk program that is being verified. + +### `proof` + +The proof for the zk program that is being verified. + +### `public_inputs` + +These represent the public inputs of the proof we are verifying. + +### `key_hash` + +A key hash is used to check the validity of the verification key. The circuit implementing this opcode can use this hash to ensure that the key provided to the circuit matches the key produced by the circuit creator. diff --git a/noir/noir-repo/docs/docs/noir/standard_library/zeroed.md b/noir/noir-repo/docs/docs/noir/standard_library/zeroed.md deleted file mode 100644 index f450fecdd36..00000000000 --- a/noir/noir-repo/docs/docs/noir/standard_library/zeroed.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: Zeroed Function -description: - The zeroed function returns a zeroed value of any type. -keywords: - [ - zeroed - ] ---- - -Implements `fn zeroed() -> T` to return a zeroed value of any type. This function is generally unsafe to use as the zeroed bit pattern is not guaranteed to be valid for all types. It can however, be useful in cases when the value is guaranteed not to be used such as in a BoundedVec library implementing a growable vector, up to a certain length, backed by an array. The array can be initialized with zeroed values which are guaranteed to be inaccessible until the vector is pushed to. Similarly, enumerations in noir can be implemented using this method by providing zeroed values for the unused variants. - -You can access the function at `std::unsafe::zeroed`. - -This function currently supports the following types: - -- Field -- Bool -- Uint -- Array -- Slice -- String -- Tuple -- Function - -Using it on other types could result in unexpected behavior. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/recursion.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/recursion.mdx new file mode 100644 index 00000000000..60414a2fa51 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/recursion.mdx @@ -0,0 +1,85 @@ +--- +title: Recursive Proofs +description: Learn about how to write recursive proofs in Noir. +keywords: [recursion, recursive proofs, verification_key, verify_proof] +--- + +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; + +Noir supports recursively verifying proofs, meaning you verify the proof of a Noir program in another Noir program. This enables creating proofs of arbitrary size by doing step-wise verification of smaller components of a large proof. + +Read [the explainer on recursion](../../explainers/explainer-recursion.md) to know more about this function and the [guide on how to use it.](../../how_to/how-to-recursion.md) + +## The `#[recursive]` Attribute + +In Noir, the `#[recursive]` attribute is used to indicate that a circuit is designed for recursive proof generation. When applied, it informs the compiler and the tooling that the circuit should be compiled in a way that makes its proofs suitable for recursive verification. This attribute eliminates the need for manual flagging of recursion at the tooling level, streamlining the proof generation process for recursive circuits. + +### Example usage with `#[recursive]` + +```rust +#[recursive] +fn main(x: Field, y: pub Field) { + assert(x == y, "x and y are not equal"); +} + +// This marks the circuit as recursion-friendly and indicates that proofs generated from this circuit +// are intended for recursive verification. +``` + +By incorporating this attribute directly in the circuit's definition, tooling like Nargo and NoirJS can automatically execute recursive-specific duties for Noir programs (e.g. recursive-friendly proof artifact generation) without additional flags or configurations. + +## Verifying Recursive Proofs + +```rust +#[foreign(recursive_aggregation)] +pub fn verify_proof(verification_key: [Field], proof: [Field], public_inputs: [Field], key_hash: Field) {} +``` + + + +## Example usage + +```rust + +fn main( + verification_key : [Field; 114], + proof : [Field; 93], + public_inputs : [Field; 1], + key_hash : Field, + proof_b : [Field; 93], +) { + std::verify_proof( + verification_key, + proof, + public_inputs, + key_hash + ); + + std::verify_proof( + verification_key, + proof_b, + public_inputs, + key_hash + ); +} +``` + +You can see a full example of recursive proofs in [this example recursion demo repo](https://github.com/noir-lang/noir-examples/tree/master/recursion). + +## Parameters + +### `verification_key` + +The verification key for the zk program that is being verified. + +### `proof` + +The proof for the zk program that is being verified. + +### `public_inputs` + +These represent the public inputs of the proof we are verifying. + +### `key_hash` + +A key hash is used to check the validity of the verification key. The circuit implementing this opcode can use this hash to ensure that the key provided to the circuit matches the key produced by the circuit creator. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/recursion.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/recursion.mdx new file mode 100644 index 00000000000..60414a2fa51 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/recursion.mdx @@ -0,0 +1,85 @@ +--- +title: Recursive Proofs +description: Learn about how to write recursive proofs in Noir. +keywords: [recursion, recursive proofs, verification_key, verify_proof] +--- + +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; + +Noir supports recursively verifying proofs, meaning you verify the proof of a Noir program in another Noir program. This enables creating proofs of arbitrary size by doing step-wise verification of smaller components of a large proof. + +Read [the explainer on recursion](../../explainers/explainer-recursion.md) to know more about this function and the [guide on how to use it.](../../how_to/how-to-recursion.md) + +## The `#[recursive]` Attribute + +In Noir, the `#[recursive]` attribute is used to indicate that a circuit is designed for recursive proof generation. When applied, it informs the compiler and the tooling that the circuit should be compiled in a way that makes its proofs suitable for recursive verification. This attribute eliminates the need for manual flagging of recursion at the tooling level, streamlining the proof generation process for recursive circuits. + +### Example usage with `#[recursive]` + +```rust +#[recursive] +fn main(x: Field, y: pub Field) { + assert(x == y, "x and y are not equal"); +} + +// This marks the circuit as recursion-friendly and indicates that proofs generated from this circuit +// are intended for recursive verification. +``` + +By incorporating this attribute directly in the circuit's definition, tooling like Nargo and NoirJS can automatically execute recursive-specific duties for Noir programs (e.g. recursive-friendly proof artifact generation) without additional flags or configurations. + +## Verifying Recursive Proofs + +```rust +#[foreign(recursive_aggregation)] +pub fn verify_proof(verification_key: [Field], proof: [Field], public_inputs: [Field], key_hash: Field) {} +``` + + + +## Example usage + +```rust + +fn main( + verification_key : [Field; 114], + proof : [Field; 93], + public_inputs : [Field; 1], + key_hash : Field, + proof_b : [Field; 93], +) { + std::verify_proof( + verification_key, + proof, + public_inputs, + key_hash + ); + + std::verify_proof( + verification_key, + proof_b, + public_inputs, + key_hash + ); +} +``` + +You can see a full example of recursive proofs in [this example recursion demo repo](https://github.com/noir-lang/noir-examples/tree/master/recursion). + +## Parameters + +### `verification_key` + +The verification key for the zk program that is being verified. + +### `proof` + +The proof for the zk program that is being verified. + +### `public_inputs` + +These represent the public inputs of the proof we are verifying. + +### `key_hash` + +A key hash is used to check the validity of the verification key. The circuit implementing this opcode can use this hash to ensure that the key provided to the circuit matches the key produced by the circuit creator. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/recursion.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/recursion.mdx new file mode 100644 index 00000000000..60414a2fa51 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/recursion.mdx @@ -0,0 +1,85 @@ +--- +title: Recursive Proofs +description: Learn about how to write recursive proofs in Noir. +keywords: [recursion, recursive proofs, verification_key, verify_proof] +--- + +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; + +Noir supports recursively verifying proofs, meaning you verify the proof of a Noir program in another Noir program. This enables creating proofs of arbitrary size by doing step-wise verification of smaller components of a large proof. + +Read [the explainer on recursion](../../explainers/explainer-recursion.md) to know more about this function and the [guide on how to use it.](../../how_to/how-to-recursion.md) + +## The `#[recursive]` Attribute + +In Noir, the `#[recursive]` attribute is used to indicate that a circuit is designed for recursive proof generation. When applied, it informs the compiler and the tooling that the circuit should be compiled in a way that makes its proofs suitable for recursive verification. This attribute eliminates the need for manual flagging of recursion at the tooling level, streamlining the proof generation process for recursive circuits. + +### Example usage with `#[recursive]` + +```rust +#[recursive] +fn main(x: Field, y: pub Field) { + assert(x == y, "x and y are not equal"); +} + +// This marks the circuit as recursion-friendly and indicates that proofs generated from this circuit +// are intended for recursive verification. +``` + +By incorporating this attribute directly in the circuit's definition, tooling like Nargo and NoirJS can automatically execute recursive-specific duties for Noir programs (e.g. recursive-friendly proof artifact generation) without additional flags or configurations. + +## Verifying Recursive Proofs + +```rust +#[foreign(recursive_aggregation)] +pub fn verify_proof(verification_key: [Field], proof: [Field], public_inputs: [Field], key_hash: Field) {} +``` + + + +## Example usage + +```rust + +fn main( + verification_key : [Field; 114], + proof : [Field; 93], + public_inputs : [Field; 1], + key_hash : Field, + proof_b : [Field; 93], +) { + std::verify_proof( + verification_key, + proof, + public_inputs, + key_hash + ); + + std::verify_proof( + verification_key, + proof_b, + public_inputs, + key_hash + ); +} +``` + +You can see a full example of recursive proofs in [this example recursion demo repo](https://github.com/noir-lang/noir-examples/tree/master/recursion). + +## Parameters + +### `verification_key` + +The verification key for the zk program that is being verified. + +### `proof` + +The proof for the zk program that is being verified. + +### `public_inputs` + +These represent the public inputs of the proof we are verifying. + +### `key_hash` + +A key hash is used to check the validity of the verification key. The circuit implementing this opcode can use this hash to ensure that the key provided to the circuit matches the key produced by the circuit creator. diff --git a/noir/noir-repo/noir_stdlib/src/collections/bounded_vec.nr b/noir/noir-repo/noir_stdlib/src/collections/bounded_vec.nr index 7cd9942e02a..c2a3ff9b7ca 100644 --- a/noir/noir-repo/noir_stdlib/src/collections/bounded_vec.nr +++ b/noir/noir-repo/noir_stdlib/src/collections/bounded_vec.nr @@ -554,7 +554,7 @@ mod bounded_vec_tests { assert_eq(bounded_vec.storage()[2], 3); } - #[test(should_fail_with="from array out of bounds")] + #[test(should_fail_with = "from array out of bounds")] fn max_len_lower_then_array_len() { let _: BoundedVec = BoundedVec::from_array([0; 3]); } diff --git a/noir/noir-repo/noir_stdlib/src/collections/vec.nr b/noir/noir-repo/noir_stdlib/src/collections/vec.nr index f24ed4ac783..1e641c384f0 100644 --- a/noir/noir-repo/noir_stdlib/src/collections/vec.nr +++ b/noir/noir-repo/noir_stdlib/src/collections/vec.nr @@ -1,5 +1,5 @@ pub struct Vec { - slice: [T] + pub(crate) slice: [T] } // A mutable vector type implemented as a wrapper around immutable slices. // A separate type is technically not needed but helps differentiate which operations are mutable. diff --git a/noir/noir-repo/noir_stdlib/src/ec/consts/te.nr b/noir/noir-repo/noir_stdlib/src/ec/consts/te.nr index e82302eadee..f2425f6a786 100644 --- a/noir/noir-repo/noir_stdlib/src/ec/consts/te.nr +++ b/noir/noir-repo/noir_stdlib/src/ec/consts/te.nr @@ -2,13 +2,14 @@ use crate::ec::tecurve::affine::Point as TEPoint; use crate::ec::tecurve::affine::Curve as TECurve; pub struct BabyJubjub { - curve: TECurve, - base8: TEPoint, - suborder: Field, + pub curve: TECurve, + pub base8: TEPoint, + pub suborder: Field, } #[field(bn254)] -#[deprecated = "It's recommmended to use the external noir-edwards library (https://github.com/noir-lang/noir-edwards)"] +// Uncommenting this results in deprecated warnings in the stdlib +// #[deprecated] pub fn baby_jubjub() -> BabyJubjub { BabyJubjub { // Baby Jubjub (ERC-2494) parameters in affine representation diff --git a/noir/noir-repo/noir_stdlib/src/ec/montcurve.nr b/noir/noir-repo/noir_stdlib/src/ec/montcurve.nr index b8077b6b639..11b6a964c2d 100644 --- a/noir/noir-repo/noir_stdlib/src/ec/montcurve.nr +++ b/noir/noir-repo/noir_stdlib/src/ec/montcurve.nr @@ -16,16 +16,16 @@ pub mod affine { // Curve specification pub struct Curve { // Montgomery Curve configuration (ky^2 = x^3 + j*x^2 + x) - j: Field, - k: Field, + pub j: Field, + pub k: Field, // Generator as point in Cartesian coordinates - gen: Point + pub gen: Point } // Point in Cartesian coordinates pub struct Point { - x: Field, - y: Field, - infty: bool // Indicator for point at infinity + pub x: Field, + pub y: Field, + pub infty: bool // Indicator for point at infinity } impl Point { @@ -40,7 +40,7 @@ pub mod affine { } // Conversion to CurveGroup coordinates - fn into_group(self) -> curvegroup::Point { + pub fn into_group(self) -> curvegroup::Point { if self.is_zero() { curvegroup::Point::zero() } else { @@ -55,14 +55,14 @@ pub mod affine { } // Negation - fn negate(self) -> Self { + pub fn negate(self) -> Self { let Self {x, y, infty} = self; Self { x, y: 0 - y, infty } } // Map into equivalent Twisted Edwards curve - fn into_tecurve(self) -> TEPoint { + pub fn into_tecurve(self) -> TEPoint { let Self {x, y, infty} = self; if infty | (y * (x + 1) == 0) { @@ -95,7 +95,7 @@ pub mod affine { } // Conversion to CurveGroup coordinates - fn into_group(self) -> curvegroup::Curve { + pub fn into_group(self) -> curvegroup::Curve { curvegroup::Curve::new(self.j, self.k, self.gen.into_group()) } @@ -114,17 +114,17 @@ pub mod affine { // Scalar multiplication with scalar represented by a bit array (little-endian convention). // If k is the natural number represented by `bits`, then this computes p + ... + p k times. - fn bit_mul(self, bits: [u1; N], p: Point) -> Point { + pub fn bit_mul(self, bits: [u1; N], p: Point) -> Point { self.into_tecurve().bit_mul(bits, p.into_tecurve()).into_montcurve() } // Scalar multiplication (p + ... + p n times) - fn mul(self, n: Field, p: Point) -> Point { + pub fn mul(self, n: Field, p: Point) -> Point { self.into_tecurve().mul(n, p.into_tecurve()).into_montcurve() } // Multi-scalar multiplication (n[0]*p[0] + ... + n[N]*p[N], where * denotes scalar multiplication) - fn msm(self, n: [Field; N], p: [Point; N]) -> Point { + pub fn msm(self, n: [Field; N], p: [Point; N]) -> Point { let mut out = Point::zero(); for i in 0..N { @@ -135,12 +135,12 @@ pub mod affine { } // Point subtraction - fn subtract(self, p1: Point, p2: Point) -> Point { + pub fn subtract(self, p1: Point, p2: Point) -> Point { self.add(p1, p2.negate()) } // Conversion to equivalent Twisted Edwards curve - fn into_tecurve(self) -> TECurve { + pub fn into_tecurve(self) -> TECurve { let Self {j, k, gen} = self; TECurve::new((j + 2) / k, (j - 2) / k, gen.into_tecurve()) } @@ -165,7 +165,7 @@ pub mod affine { } // Point mapping from equivalent Short Weierstrass curve - fn map_from_swcurve(self, p: SWPoint) -> Point { + pub fn map_from_swcurve(self, p: SWPoint) -> Point { let SWPoint {x, y, infty} = p; let j = self.j; let k = self.k; @@ -174,7 +174,7 @@ pub mod affine { } // Elligator 2 map-to-curve method; see . - fn elligator2_map(self, u: Field) -> Point { + pub fn elligator2_map(self, u: Field) -> Point { let j = self.j; let k = self.k; let z = ZETA; // Non-square Field element required for map @@ -205,7 +205,7 @@ pub mod affine { } // SWU map-to-curve method (via rational map) - fn swu_map(self, z: Field, u: Field) -> Point { + pub fn swu_map(self, z: Field, u: Field) -> Point { self.map_from_swcurve(self.into_swcurve().swu_map(z, u)) } } @@ -223,16 +223,16 @@ pub mod curvegroup { use crate::cmp::Eq; pub struct Curve { // Montgomery Curve configuration (ky^2 z = x*(x^2 + j*x*z + z*z)) - j: Field, - k: Field, + pub j: Field, + pub k: Field, // Generator as point in projective coordinates - gen: Point + pub gen: Point } // Point in projective coordinates pub struct Point { - x: Field, - y: Field, - z: Field + pub x: Field, + pub y: Field, + pub z: Field } impl Point { @@ -247,7 +247,7 @@ pub mod curvegroup { } // Conversion to affine coordinates - fn into_affine(self) -> affine::Point { + pub fn into_affine(self) -> affine::Point { if self.is_zero() { affine::Point::zero() } else { @@ -262,14 +262,14 @@ pub mod curvegroup { } // Negation - fn negate(self) -> Self { + pub fn negate(self) -> Self { let Self {x, y, z} = self; Point::new(x, 0 - y, z) } // Map into equivalent Twisted Edwards curve - fn into_tecurve(self) -> TEPoint { + pub fn into_tecurve(self) -> TEPoint { self.into_affine().into_tecurve().into_group() } } @@ -297,7 +297,7 @@ pub mod curvegroup { } // Conversion to affine coordinates - fn into_affine(self) -> affine::Curve { + pub fn into_affine(self) -> affine::Curve { affine::Curve::new(self.j, self.k, self.gen.into_affine()) } @@ -316,7 +316,7 @@ pub mod curvegroup { // Scalar multiplication with scalar represented by a bit array (little-endian convention). // If k is the natural number represented by `bits`, then this computes p + ... + p k times. - fn bit_mul(self, bits: [u1; N], p: Point) -> Point { + pub fn bit_mul(self, bits: [u1; N], p: Point) -> Point { self.into_tecurve().bit_mul(bits, p.into_tecurve()).into_montcurve() } @@ -326,7 +326,7 @@ pub mod curvegroup { } // Multi-scalar multiplication (n[0]*p[0] + ... + n[N]*p[N], where * denotes scalar multiplication) - fn msm(self, n: [Field; N], p: [Point; N]) -> Point { + pub fn msm(self, n: [Field; N], p: [Point; N]) -> Point { let mut out = Point::zero(); for i in 0..N { @@ -342,13 +342,13 @@ pub mod curvegroup { } // Conversion to equivalent Twisted Edwards curve - fn into_tecurve(self) -> TECurve { + pub fn into_tecurve(self) -> TECurve { let Self {j, k, gen} = self; TECurve::new((j + 2) / k, (j - 2) / k, gen.into_tecurve()) } // Conversion to equivalent Short Weierstrass curve - fn into_swcurve(self) -> SWCurve { + pub fn into_swcurve(self) -> SWCurve { let j = self.j; let k = self.k; let a0 = (3 - j * j) / (3 * k * k); @@ -363,17 +363,17 @@ pub mod curvegroup { } // Point mapping from equivalent Short Weierstrass curve - fn map_from_swcurve(self, p: SWPoint) -> Point { + pub fn map_from_swcurve(self, p: SWPoint) -> Point { self.into_affine().map_from_swcurve(p.into_affine()).into_group() } // Elligator 2 map-to-curve method - fn elligator2_map(self, u: Field) -> Point { + pub fn elligator2_map(self, u: Field) -> Point { self.into_affine().elligator2_map(u).into_group() } // SWU map-to-curve method (via rational map) - fn swu_map(self, z: Field, u: Field) -> Point { + pub fn swu_map(self, z: Field, u: Field) -> Point { self.into_affine().swu_map(z, u).into_group() } } diff --git a/noir/noir-repo/noir_stdlib/src/ec/swcurve.nr b/noir/noir-repo/noir_stdlib/src/ec/swcurve.nr index 9c40ddd1adc..6e1054ad84e 100644 --- a/noir/noir-repo/noir_stdlib/src/ec/swcurve.nr +++ b/noir/noir-repo/noir_stdlib/src/ec/swcurve.nr @@ -12,16 +12,16 @@ pub mod affine { // Curve specification pub struct Curve { // Short Weierstrass curve // Coefficients in defining equation y^2 = x^3 + ax + b - a: Field, - b: Field, + pub a: Field, + pub b: Field, // Generator as point in Cartesian coordinates - gen: Point + pub gen: Point } // Point in Cartesian coordinates pub struct Point { - x: Field, - y: Field, - infty: bool // Indicator for point at infinity + pub x: Field, + pub y: Field, + pub infty: bool // Indicator for point at infinity } impl Point { @@ -36,7 +36,7 @@ pub mod affine { } // Conversion to CurveGroup coordinates - fn into_group(self) -> curvegroup::Point { + pub fn into_group(self) -> curvegroup::Point { let Self {x, y, infty} = self; if infty { @@ -52,7 +52,7 @@ pub mod affine { } // Negation - fn negate(self) -> Self { + pub fn negate(self) -> Self { let Self {x, y, infty} = self; Self { x, y: 0 - y, infty } } @@ -82,7 +82,7 @@ pub mod affine { } // Conversion to CurveGroup coordinates - fn into_group(self) -> curvegroup::Curve { + pub fn into_group(self) -> curvegroup::Curve { let Curve{a, b, gen} = self; curvegroup::Curve { a, b, gen: gen.into_group() } @@ -100,7 +100,7 @@ pub mod affine { } // Mixed point addition, i.e. first argument in affine, second in CurveGroup coordinates. - fn mixed_add(self, p1: Point, p2: curvegroup::Point) -> curvegroup::Point { + pub fn mixed_add(self, p1: Point, p2: curvegroup::Point) -> curvegroup::Point { if p1.is_zero() { p2 } else if p2.is_zero() { @@ -133,7 +133,7 @@ pub mod affine { // Scalar multiplication with scalar represented by a bit array (little-endian convention). // If k is the natural number represented by `bits`, then this computes p + ... + p k times. - fn bit_mul(self, bits: [u1; N], p: Point) -> Point { + pub fn bit_mul(self, bits: [u1; N], p: Point) -> Point { self.into_group().bit_mul(bits, p.into_group()).into_affine() } @@ -161,7 +161,7 @@ pub mod affine { // Simplified Shallue-van de Woestijne-Ulas map-to-curve method; see . // First determine non-square z != -1 in Field s.t. g(x) - z irreducible over Field and g(b/(z*a)) is square, // where g(x) = x^3 + a*x + b. swu_map(c,z,.) then maps a Field element to a point on curve c. - fn swu_map(self, z: Field, u: Field) -> Point { + pub fn swu_map(self, z: Field, u: Field) -> Point { // Check whether curve is admissible assert(self.a * self.b != 0); @@ -196,16 +196,16 @@ pub mod curvegroup { // Curve specification pub struct Curve { // Short Weierstrass curve // Coefficients in defining equation y^2 = x^3 + axz^4 + bz^6 - a: Field, - b: Field, + pub a: Field, + pub b: Field, // Generator as point in Cartesian coordinates - gen: Point + pub gen: Point } // Point in three-dimensional Jacobian coordinates pub struct Point { - x: Field, - y: Field, - z: Field // z = 0 corresponds to point at infinity. + pub x: Field, + pub y: Field, + pub z: Field // z = 0 corresponds to point at infinity. } impl Point { @@ -236,7 +236,7 @@ pub mod curvegroup { } // Negation - fn negate(self) -> Self { + pub fn negate(self) -> Self { let Self {x, y, z} = self; Self { x, y: 0 - y, z } } @@ -338,7 +338,7 @@ pub mod curvegroup { // Scalar multiplication with scalar represented by a bit array (little-endian convention). // If k is the natural number represented by `bits`, then this computes p + ... + p k times. - fn bit_mul(self, bits: [u1; N], p: Point) -> Point { + pub fn bit_mul(self, bits: [u1; N], p: Point) -> Point { let mut out = Point::zero(); for i in 0..N { @@ -363,7 +363,7 @@ pub mod curvegroup { } // Multi-scalar multiplication (n[0]*p[0] + ... + n[N]*p[N], where * denotes scalar multiplication) - fn msm(self, n: [Field; N], p: [Point; N]) -> Point { + pub fn msm(self, n: [Field; N], p: [Point; N]) -> Point { let mut out = Point::zero(); for i in 0..N { @@ -379,7 +379,7 @@ pub mod curvegroup { } // Simplified SWU map-to-curve method - fn swu_map(self, z: Field, u: Field) -> Point { + pub fn swu_map(self, z: Field, u: Field) -> Point { self.into_affine().swu_map(z, u).into_group() } } diff --git a/noir/noir-repo/noir_stdlib/src/ec/tecurve.nr b/noir/noir-repo/noir_stdlib/src/ec/tecurve.nr index c37b7c94a54..0eb1521b19d 100644 --- a/noir/noir-repo/noir_stdlib/src/ec/tecurve.nr +++ b/noir/noir-repo/noir_stdlib/src/ec/tecurve.nr @@ -14,15 +14,15 @@ pub mod affine { // Curve specification pub struct Curve { // Twisted Edwards curve // Coefficients in defining equation ax^2 + y^2 = 1 + dx^2y^2 - a: Field, - d: Field, + pub a: Field, + pub d: Field, // Generator as point in Cartesian coordinates - gen: Point + pub gen: Point } // Point in Cartesian coordinates pub struct Point { - x: Field, - y: Field + pub x: Field, + pub y: Field } impl Point { @@ -38,7 +38,7 @@ pub mod affine { } // Conversion to CurveGroup coordinates - fn into_group(self) -> curvegroup::Point { + pub fn into_group(self) -> curvegroup::Point { let Self {x, y} = self; curvegroup::Point::new(x, y, x * y, 1) @@ -50,13 +50,13 @@ pub mod affine { } // Negation - fn negate(self) -> Self { + pub fn negate(self) -> Self { let Self {x, y} = self; Point::new(0 - x, y) } // Map into prime-order subgroup of equivalent Montgomery curve - fn into_montcurve(self) -> MPoint { + pub fn into_montcurve(self) -> MPoint { if self.is_zero() { MPoint::zero() } else { @@ -93,7 +93,7 @@ pub mod affine { } // Conversion to CurveGroup coordinates - fn into_group(self) -> curvegroup::Curve { + pub fn into_group(self) -> curvegroup::Curve { let Curve{a, d, gen} = self; curvegroup::Curve { a, d, gen: gen.into_group() } @@ -111,7 +111,7 @@ pub mod affine { } // Mixed point addition, i.e. first argument in affine, second in CurveGroup coordinates. - fn mixed_add(self, p1: Point, p2: curvegroup::Point) -> curvegroup::Point { + pub fn mixed_add(self, p1: Point, p2: curvegroup::Point) -> curvegroup::Point { let Point{x: x1, y: y1} = p1; let curvegroup::Point{x: x2, y: y2, t: t2, z: z2} = p2; @@ -133,17 +133,17 @@ pub mod affine { // Scalar multiplication with scalar represented by a bit array (little-endian convention). // If k is the natural number represented by `bits`, then this computes p + ... + p k times. - fn bit_mul(self, bits: [u1; N], p: Point) -> Point { + pub fn bit_mul(self, bits: [u1; N], p: Point) -> Point { self.into_group().bit_mul(bits, p.into_group()).into_affine() } // Scalar multiplication (p + ... + p n times) - fn mul(self, n: Field, p: Point) -> Point { + pub fn mul(self, n: Field, p: Point) -> Point { self.into_group().mul(n, p.into_group()).into_affine() } // Multi-scalar multiplication (n[0]*p[0] + ... + n[N]*p[N], where * denotes scalar multiplication) - fn msm(self, n: [Field; N], p: [Point; N]) -> Point { + pub fn msm(self, n: [Field; N], p: [Point; N]) -> Point { let mut out = Point::zero(); for i in 0..N { @@ -154,7 +154,7 @@ pub mod affine { } // Point subtraction - fn subtract(self, p1: Point, p2: Point) -> Point { + pub fn subtract(self, p1: Point, p2: Point) -> Point { self.add(p1, p2.negate()) } @@ -178,17 +178,17 @@ pub mod affine { } // Point mapping from equivalent Short Weierstrass curve - fn map_from_swcurve(self, p: SWPoint) -> Point { + pub fn map_from_swcurve(self, p: SWPoint) -> Point { self.into_montcurve().map_from_swcurve(p).into_tecurve() } // Elligator 2 map-to-curve method (via rational map) - fn elligator2_map(self, u: Field) -> Point { + pub fn elligator2_map(self, u: Field) -> Point { self.into_montcurve().elligator2_map(u).into_tecurve() } // Simplified SWU map-to-curve method (via rational map) - fn swu_map(self, z: Field, u: Field) -> Point { + pub fn swu_map(self, z: Field, u: Field) -> Point { self.into_montcurve().swu_map(z, u).into_tecurve() } } @@ -207,17 +207,17 @@ pub mod curvegroup { // Curve specification pub struct Curve { // Twisted Edwards curve // Coefficients in defining equation a(x^2 + y^2)z^2 = z^4 + dx^2y^2 - a: Field, - d: Field, + pub a: Field, + pub d: Field, // Generator as point in projective coordinates - gen: Point + pub gen: Point } // Point in extended twisted Edwards coordinates pub struct Point { - x: Field, - y: Field, - t: Field, - z: Field + pub x: Field, + pub y: Field, + pub t: Field, + pub z: Field } impl Point { @@ -245,14 +245,14 @@ pub mod curvegroup { } // Negation - fn negate(self) -> Self { + pub fn negate(self) -> Self { let Self {x, y, t, z} = self; Point::new(0 - x, y, 0 - t, z) } // Map into prime-order subgroup of equivalent Montgomery curve - fn into_montcurve(self) -> MPoint { + pub fn into_montcurve(self) -> MPoint { self.into_affine().into_montcurve().into_group() } } @@ -341,7 +341,7 @@ pub mod curvegroup { // Scalar multiplication with scalar represented by a bit array (little-endian convention). // If k is the natural number represented by `bits`, then this computes p + ... + p k times. - fn bit_mul(self, bits: [u1; N], p: Point) -> Point { + pub fn bit_mul(self, bits: [u1; N], p: Point) -> Point { let mut out = Point::zero(); for i in 0..N { @@ -366,7 +366,7 @@ pub mod curvegroup { } // Multi-scalar multiplication (n[0]*p[0] + ... + n[N]*p[N], where * denotes scalar multiplication) - fn msm(self, n: [Field; N], p: [Point; N]) -> Point { + pub fn msm(self, n: [Field; N], p: [Point; N]) -> Point { let mut out = Point::zero(); for i in 0..N { @@ -377,17 +377,17 @@ pub mod curvegroup { } // Point subtraction - fn subtract(self, p1: Point, p2: Point) -> Point { + pub fn subtract(self, p1: Point, p2: Point) -> Point { self.add(p1, p2.negate()) } // Conversion to equivalent Montgomery curve - fn into_montcurve(self) -> MCurve { + pub fn into_montcurve(self) -> MCurve { self.into_affine().into_montcurve().into_group() } // Conversion to equivalent Short Weierstrass curve - fn into_swcurve(self) -> SWCurve { + pub fn into_swcurve(self) -> SWCurve { self.into_montcurve().into_swcurve() } @@ -397,17 +397,17 @@ pub mod curvegroup { } // Point mapping from equivalent short Weierstrass curve - fn map_from_swcurve(self, p: SWPoint) -> Point { + pub fn map_from_swcurve(self, p: SWPoint) -> Point { self.into_montcurve().map_from_swcurve(p).into_tecurve() } // Elligator 2 map-to-curve method (via rational maps) - fn elligator2_map(self, u: Field) -> Point { + pub fn elligator2_map(self, u: Field) -> Point { self.into_montcurve().elligator2_map(u).into_tecurve() } // Simplified SWU map-to-curve method (via rational map) - fn swu_map(self, z: Field, u: Field) -> Point { + pub fn swu_map(self, z: Field, u: Field) -> Point { self.into_montcurve().swu_map(z, u).into_tecurve() } } diff --git a/noir/noir-repo/noir_stdlib/src/embedded_curve_ops.nr b/noir/noir-repo/noir_stdlib/src/embedded_curve_ops.nr index f5e7c7e8528..ad7196b4494 100644 --- a/noir/noir-repo/noir_stdlib/src/embedded_curve_ops.nr +++ b/noir/noir-repo/noir_stdlib/src/embedded_curve_ops.nr @@ -5,9 +5,9 @@ use crate::cmp::Eq; /// By definition, the base field of the embedded curve is the scalar field of the proof system curve, i.e the Noir Field. /// x and y denotes the Weierstrass coordinates of the point, if is_infinite is false. pub struct EmbeddedCurvePoint { - x: Field, - y: Field, - is_infinite: bool + pub x: Field, + pub y: Field, + pub is_infinite: bool } impl EmbeddedCurvePoint { @@ -57,8 +57,8 @@ impl Eq for EmbeddedCurvePoint { /// By definition, the scalar field of the embedded curve is base field of the proving system curve. /// It may not fit into a Field element, so it is represented with two Field elements; its low and high limbs. pub struct EmbeddedCurveScalar { - lo: Field, - hi: Field, + pub lo: Field, + pub hi: Field, } impl EmbeddedCurveScalar { diff --git a/noir/noir-repo/noir_stdlib/src/field/bn254.nr b/noir/noir-repo/noir_stdlib/src/field/bn254.nr index 9f580fe8a5c..9349e67aed3 100644 --- a/noir/noir-repo/noir_stdlib/src/field/bn254.nr +++ b/noir/noir-repo/noir_stdlib/src/field/bn254.nr @@ -5,20 +5,18 @@ global PLO: Field = 53438638232309528389504892708671455233; global PHI: Field = 64323764613183177041862057485226039389; pub(crate) global TWO_POW_128: Field = 0x100000000000000000000000000000000; +global TWO_POW_64: Field = 0x10000000000000000; // Decomposes a single field into two 16 byte fields. -fn compute_decomposition(x: Field) -> (Field, Field) { - let x_bytes: [u8; 32] = x.to_le_bytes(); - - let mut low: Field = 0; - let mut high: Field = 0; - - let mut offset = 1; - for i in 0..16 { - low += (x_bytes[i] as Field) * offset; - high += (x_bytes[i + 16] as Field) * offset; - offset *= 256; - } +fn compute_decomposition(mut x: Field) -> (Field, Field) { + // Here's we're taking advantage of truncating 64 bit limbs from the input field + // and then subtracting them from the input such the field division is equivalent to integer division. + let low_lower_64 = (x as u64) as Field; + x = (x - low_lower_64) / TWO_POW_64; + let low_upper_64 = (x as u64) as Field; + + let high = (x - low_upper_64) / TWO_POW_64; + let low = low_upper_64 * TWO_POW_64 + low_lower_64; (low, high) } diff --git a/noir/noir-repo/noir_stdlib/src/field/mod.nr b/noir/noir-repo/noir_stdlib/src/field/mod.nr index 2847951cdf3..4a329aaeb59 100644 --- a/noir/noir-repo/noir_stdlib/src/field/mod.nr +++ b/noir/noir-repo/noir_stdlib/src/field/mod.nr @@ -45,15 +45,15 @@ impl Field { /// (e.g. 254 for the BN254 field) allow for multiple bit decompositions. This is due to how the `Field` will /// wrap around due to overflow when verifying the decomposition. #[builtin(to_be_bits)] - // docs:start:to_be_bits + // docs:start:to_be_bits pub fn to_be_bits(self: Self) -> [u1; N] {} // docs:end:to_be_bits /// Decomposes `self` into its little endian byte decomposition as a `[u8;N]` array /// This array will be zero padded should not all bytes be necessary to represent `self`. - /// + /// /// # Failures - /// The length N of the array must be big enough to contain all the bytes of the 'self', + /// The length N of the array must be big enough to contain all the bytes of the 'self', /// and no more than the number of bytes required to represent the field modulus /// /// # Safety @@ -84,9 +84,9 @@ impl Field { /// Decomposes `self` into its big endian byte decomposition as a `[u8;N]` array of length required to represent the field modulus /// This array will be zero padded should not all bytes be necessary to represent `self`. - /// + /// /// # Failures - /// The length N of the array must be big enough to contain all the bytes of the 'self', + /// The length N of the array must be big enough to contain all the bytes of the 'self', /// and no more than the number of bytes required to represent the field modulus /// /// # Safety diff --git a/noir/noir-repo/noir_stdlib/src/hash/mimc.nr b/noir/noir-repo/noir_stdlib/src/hash/mimc.nr index 1bcc9e3fcc7..84ee177b663 100644 --- a/noir/noir-repo/noir_stdlib/src/hash/mimc.nr +++ b/noir/noir-repo/noir_stdlib/src/hash/mimc.nr @@ -6,7 +6,9 @@ use crate::default::Default; // You must use constants generated for the native field // Rounds number should be ~ log(p)/log(exp) // For 254 bit primes, exponent 7 and 91 rounds seems to be recommended -#[deprecated = "It's recommmended to use the external MiMC library (https://github.com/noir-lang/mimc)"] +// +// Uncommenting this results in deprecated warnings in the stdlib +// #[deprecated] fn mimc(x: Field, k: Field, constants: [Field; N], exp: Field) -> Field { //round 0 let mut t = x + k; @@ -117,7 +119,7 @@ global MIMC_BN254_CONSTANTS: [Field; MIMC_BN254_ROUNDS] = [ //mimc implementation with hardcoded parameters for BN254 curve. #[field(bn254)] -#[deprecated = "It's recommmended to use the external MiMC library (https://github.com/noir-lang/mimc)"] +#[deprecated] pub fn mimc_bn254(array: [Field; N]) -> Field { let exponent = 7; let mut r = 0; diff --git a/noir/noir-repo/noir_stdlib/src/hash/sha256.nr b/noir/noir-repo/noir_stdlib/src/hash/sha256.nr index 081f7deb0fa..6c56a722fa7 100644 --- a/noir/noir-repo/noir_stdlib/src/hash/sha256.nr +++ b/noir/noir-repo/noir_stdlib/src/hash/sha256.nr @@ -79,7 +79,6 @@ fn verify_msg_block( } global BLOCK_SIZE = 64; -global ZERO = 0; // Variable size SHA-256 hash pub fn sha256_var(msg: [u8; N], message_size: u64) -> [u8; 32] { diff --git a/noir/noir-repo/noir_stdlib/src/mem.nr b/noir/noir-repo/noir_stdlib/src/mem.nr index 88d17e20ee3..0d47a21b50d 100644 --- a/noir/noir-repo/noir_stdlib/src/mem.nr +++ b/noir/noir-repo/noir_stdlib/src/mem.nr @@ -4,3 +4,14 @@ #[builtin(zeroed)] pub fn zeroed() -> T {} +/// Transmutes a value of type T to a value of type U. +/// +/// Both types are asserted to be equal during compilation but after type checking. +/// If not, a compilation error is issued. +/// +/// This function is useful for types using arithmetic generics in cases +/// which the compiler otherwise cannot prove equal during type checking. +/// You can use this to obtain a value of the correct type while still asserting +/// that it is equal to the previous. +#[builtin(checked_transmute)] +pub fn checked_transmute(value: T) -> U {} diff --git a/noir/noir-repo/noir_stdlib/src/meta/mod.nr b/noir/noir-repo/noir_stdlib/src/meta/mod.nr index 378f0df1ba8..f756be364b1 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/mod.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/mod.nr @@ -118,6 +118,8 @@ pub comptime fn make_trait_impl( } mod tests { + use crate::meta::derive_via; + // docs:start:quote-example comptime fn quote_one() -> Quoted { quote { 1 } diff --git a/noir/noir-repo/noir_stdlib/src/meta/op.nr b/noir/noir-repo/noir_stdlib/src/meta/op.nr index 197daaabaa6..39ec918fe39 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/op.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/op.nr @@ -1,8 +1,22 @@ -#[derive(Eq, Hash)] pub struct UnaryOp { op: Field } +// Cannot derive Eq or Hash since they internally use paths +// starting with std:: which is invalid within the same crate. +// We'd need to use crate:: instead. +impl crate::cmp::Eq for UnaryOp { + fn eq(self, other: Self) -> bool { + self.op == other.op + } +} + +impl crate::hash::Hash for UnaryOp { + fn hash(self, h: &mut H) where H: crate::hash::Hasher { + self.op.hash(h); + } +} + impl UnaryOp { // docs:start:is_minus pub fn is_minus(self) -> bool { @@ -46,11 +60,22 @@ impl UnaryOp { } } -#[derive(Eq, Hash)] pub struct BinaryOp { op: Field } +impl crate::cmp::Eq for BinaryOp { + fn eq(self, other: Self) -> bool { + self.op == other.op + } +} + +impl crate::hash::Hash for BinaryOp { + fn hash(self, h: &mut H) where H: crate::hash::Hasher { + self.op.hash(h); + } +} + impl BinaryOp { // docs:start:is_add pub fn is_add(self) -> bool { diff --git a/noir/noir-repo/noir_stdlib/src/meta/trait_def.nr b/noir/noir-repo/noir_stdlib/src/meta/trait_def.nr index 0c2e7cfa5c1..9bf0132f79e 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/trait_def.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/trait_def.nr @@ -3,7 +3,7 @@ use crate::cmp::Eq; impl TraitDefinition { #[builtin(trait_def_as_trait_constraint)] -// docs:start:as_trait_constraint + // docs:start:as_trait_constraint pub comptime fn as_trait_constraint(_self: Self) -> TraitConstraint {} // docs:end:as_trait_constraint } diff --git a/noir/noir-repo/noir_stdlib/src/option.nr b/noir/noir-repo/noir_stdlib/src/option.nr index 6b3a2bf2f3c..0c120a71568 100644 --- a/noir/noir-repo/noir_stdlib/src/option.nr +++ b/noir/noir-repo/noir_stdlib/src/option.nr @@ -57,7 +57,7 @@ impl Option { } /// Asserts `self.is_some()` with a provided custom message and returns the contained `Some` value - fn expect(self, message: fmtstr) -> T { + pub fn expect(self, message: fmtstr) -> T { assert(self.is_some(), message); self._value } diff --git a/noir/noir-repo/noir_stdlib/src/schnorr.nr b/noir/noir-repo/noir_stdlib/src/schnorr.nr index 0623f116dea..76db04400e2 100644 --- a/noir/noir-repo/noir_stdlib/src/schnorr.nr +++ b/noir/noir-repo/noir_stdlib/src/schnorr.nr @@ -37,7 +37,7 @@ pub fn verify_signature_noir( if ((sig_s.lo != 0) | (sig_s.hi != 0)) & ((sig_e.lo != 0) | (sig_e.hi != 0)) { let (r_is_infinite, result) = calculate_signature_challenge(public_key, sig_s, sig_e, message); - is_ok = !r_is_infinite; + is_ok &= !r_is_infinite; for i in 0..32 { is_ok &= result[i] == signature[32 + i]; } diff --git a/noir/noir-repo/noir_stdlib/src/sha256.nr b/noir/noir-repo/noir_stdlib/src/sha256.nr index 7679e517317..39e72cfbeb8 100644 --- a/noir/noir-repo/noir_stdlib/src/sha256.nr +++ b/noir/noir-repo/noir_stdlib/src/sha256.nr @@ -1,11 +1,11 @@ // This file is kept for backwards compatibility. -#[deprecated = "replace with std::hash::sha256::digest"] +#[deprecated] pub fn digest(msg: [u8; N]) -> [u8; 32] { crate::hash::sha256::digest(msg) } -#[deprecated = "replace with std::hash::sha256::sha256_var"] +#[deprecated] pub fn sha256_var(msg: [u8; N], message_size: u64) -> [u8; 32] { crate::hash::sha256::sha256_var(msg, message_size) } diff --git a/noir/noir-repo/noir_stdlib/src/sha512.nr b/noir/noir-repo/noir_stdlib/src/sha512.nr index 0a8a5bf4760..c66d898b0cf 100644 --- a/noir/noir-repo/noir_stdlib/src/sha512.nr +++ b/noir/noir-repo/noir_stdlib/src/sha512.nr @@ -1,6 +1,6 @@ // This file is kept for backwards compatibility. -#[deprecated = "replace with std::hash::sha512::digest"] +#[deprecated] pub fn digest(msg: [u8; N]) -> [u8; 64] { crate::hash::sha512::digest(msg) } diff --git a/noir/noir-repo/noir_stdlib/src/uint128.nr b/noir/noir-repo/noir_stdlib/src/uint128.nr index 730b675975d..e4a4342c3d1 100644 --- a/noir/noir-repo/noir_stdlib/src/uint128.nr +++ b/noir/noir-repo/noir_stdlib/src/uint128.nr @@ -4,8 +4,8 @@ use crate::cmp::{Eq, Ord, Ordering}; global pow64 : Field = 18446744073709551616; //2^64; global pow63 : Field = 9223372036854775808; // 2^63; pub struct U128 { - lo: Field, - hi: Field, + pub(crate) lo: Field, + pub(crate) hi: Field, } impl U128 { diff --git a/noir/noir-repo/test_programs/compile_failure/checked_transmute/Nargo.toml b/noir/noir-repo/test_programs/compile_failure/checked_transmute/Nargo.toml new file mode 100644 index 00000000000..9d01c873b03 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_failure/checked_transmute/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "checked_transmute" +type = "bin" +authors = [""] +compiler_version = ">=0.35.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/compile_failure/checked_transmute/src/main.nr b/noir/noir-repo/test_programs/compile_failure/checked_transmute/src/main.nr new file mode 100644 index 00000000000..058fa0ec911 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_failure/checked_transmute/src/main.nr @@ -0,0 +1,9 @@ +use std::mem::checked_transmute; + +fn main() { + let _: [Field; 2] = transmute_fail([1]); +} + +pub fn transmute_fail(x: [Field; N]) -> [Field; N + 1] { + checked_transmute(x) +} diff --git a/noir/noir-repo/test_programs/compile_success_contract/abi_attribute/src/main.nr b/noir/noir-repo/test_programs/compile_success_contract/abi_attribute/src/main.nr index 164512b03db..c4d4a24e3c2 100644 --- a/noir/noir-repo/test_programs/compile_success_contract/abi_attribute/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_contract/abi_attribute/src/main.nr @@ -1,9 +1,9 @@ contract Foo { #[abi(foo)] - global foo: Field = 42; + pub global foo: Field = 42; #[abi(bar)] - struct Bar { + pub struct Bar { inner: Field } } diff --git a/noir/noir-repo/test_programs/compile_success_contract/contract_with_impl/src/main.nr b/noir/noir-repo/test_programs/compile_success_contract/contract_with_impl/src/main.nr index 1c6b6c217c4..9d45b88fbc9 100644 --- a/noir/noir-repo/test_programs/compile_success_contract/contract_with_impl/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_contract/contract_with_impl/src/main.nr @@ -1,7 +1,9 @@ contract Foo { - struct T { x: [Field] } + pub struct T { x: [Field] } impl T { - fn t(self) {} + fn t(self) { + let _ = self; + } } } diff --git a/noir/noir-repo/test_programs/compile_success_contract/non_entry_point_method/src/main.nr b/noir/noir-repo/test_programs/compile_success_contract/non_entry_point_method/src/main.nr index b768653262a..f49c2f14f9d 100644 --- a/noir/noir-repo/test_programs/compile_success_contract/non_entry_point_method/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_contract/non_entry_point_method/src/main.nr @@ -1,6 +1,6 @@ contract Foo { - struct PlaceholderStruct{x : u32 } + pub struct PlaceholderStruct{x : u32 } #[contract_library_method] - fn has_mut(_context: &mut PlaceholderStruct) {} + pub fn has_mut(_context: &mut PlaceholderStruct) {} } diff --git a/noir/noir-repo/test_programs/compile_success_contract/simple_contract/src/main.nr b/noir/noir-repo/test_programs/compile_success_contract/simple_contract/src/main.nr index 7412e1386bf..7742ed6139b 100644 --- a/noir/noir-repo/test_programs/compile_success_contract/simple_contract/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_contract/simple_contract/src/main.nr @@ -10,7 +10,7 @@ contract Foo { } // Regression for issue #3344 #[contract_library_method] - fn foo(x: u8) -> u8 { + pub fn foo(x: u8) -> u8 { x } } diff --git a/noir/noir-repo/test_programs/compile_success_empty/arithmetic_generics/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/arithmetic_generics/src/main.nr index 0a7d319485c..4a057a75e43 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/arithmetic_generics/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/arithmetic_generics/src/main.nr @@ -97,7 +97,7 @@ fn mul_add() -> Equiv, (), W< } // (N + 1) * N == N * N + N -fn demo_proof() -> Equiv, (Equiv, (), W, ()>, Equiv, (Equiv, (), W, ()>, Equiv, (), W<(N * (N + 1))>, ()>), W, (Equiv, (), W<(N * (N + 1))>, ()>, Equiv, (), W, ()>)>), W, (Equiv, (Equiv, (), W, ()>, Equiv, (), W<(N * (N + 1))>, ()>), W, (Equiv, (), W<(N * (N + 1))>, ()>, Equiv, (), W, ()>)>, Equiv, (), W, ()>)> { +pub fn demo_proof() -> Equiv, (Equiv, (), W, ()>, Equiv, (Equiv, (), W, ()>, Equiv, (), W<(N * (N + 1))>, ()>), W, (Equiv, (), W<(N * (N + 1))>, ()>, Equiv, (), W, ()>)>), W, (Equiv, (Equiv, (), W, ()>, Equiv, (), W<(N * (N + 1))>, ()>), W, (Equiv, (), W<(N * (N + 1))>, ()>, Equiv, (), W, ()>)>, Equiv, (), W, ()>)> { let p1: Equiv, (), W, ()> = mul_comm(); let p2: Equiv, (), W, ()> = mul_add::(); let p3_sub: Equiv, (), W, ()> = mul_one_r(); @@ -117,9 +117,12 @@ fn test_constant_folding() { // N * C1 / C2 = N * (C1 / C2) let _: W = W:: {}; - + // This case is invalid due to integer division + // If N does not divide evenly with 10 then we cannot simplify it. + // e.g. 15 / 10 * 2 = 2 versus 15 / 5 = 3 + // // N / C1 * C2 = N / (C1 / C2) - let _: W = W:: {}; + // let _: W = W:: {}; } fn test_non_constant_folding() { @@ -131,7 +134,9 @@ fn test_non_constant_folding() { // N * M / M = N let _: W = W:: {}; - + // This case is not true due to integer division rounding! + // Consider 5 / 2 * 2 which should equal 4, not 5 + // // N / M * M = N - let _: W = W:: {}; + // let _: W = W:: {}; } diff --git a/noir/noir-repo/test_programs/compile_success_empty/attribute_args/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/attribute_args/src/main.nr index 6178df5e749..492afd9e2f1 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/attribute_args/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/attribute_args/src/main.nr @@ -1,7 +1,7 @@ #[attr_with_args(1, 2)] #[varargs(1, 2)] #[varargs(1, 2, 3, 4)] -struct Foo {} +pub struct Foo {} comptime fn attr_with_args(s: StructDefinition, a: Field, b: Field) { // Ensure all variables are in scope. diff --git a/noir/noir-repo/test_programs/compile_success_empty/attributes_multiple/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/attributes_multiple/src/main.nr index 581fb989b4c..aadfe5caf3c 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/attributes_multiple/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/attributes_multiple/src/main.nr @@ -2,6 +2,6 @@ fn main() { another_func() } -#[aztec(private)] -#[internal] +#['aztec(private)] +#['internal] fn another_func() {} diff --git a/noir/noir-repo/test_programs/compile_success_empty/attributes_struct/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/attributes_struct/src/main.nr index 669cfc32927..f02e7973878 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/attributes_struct/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/attributes_struct/src/main.nr @@ -1,6 +1,6 @@ -#[some_attribute] -#[another_attribute] -struct SomeStruct { +#['some_attribute] +#['another_attribute] +pub struct SomeStruct { a: Field, b: Field } @@ -11,7 +11,7 @@ fn main() {} #[abi(something)] #[add_attribute] -struct Foo { +pub struct Foo { } diff --git a/noir/noir-repo/test_programs/compile_success_empty/checked_transmute/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/checked_transmute/Nargo.toml new file mode 100644 index 00000000000..f3392ec79bb --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/checked_transmute/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "checked_transmute" +type = "bin" +authors = [""] +compiler_version = ">=0.35.0" + +[dependencies] diff --git a/noir/noir-repo/test_programs/compile_success_empty/checked_transmute/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/checked_transmute/src/main.nr new file mode 100644 index 00000000000..fa6240fb43a --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/checked_transmute/src/main.nr @@ -0,0 +1,15 @@ +use std::mem::checked_transmute; + +fn main() { + // 1*(2 + 3) = 1*2 + 1*3 = 5 + let _: [Field; 5] = distribute::<1, 2, 3>([1, 2, 3, 4, 5]); +} + +pub fn distribute(x: [Field; N * (A + B)]) -> [Field; N * A + N * B] { + // asserts: [Field; N * (A + B)] = [Field; N * A + N * B] + // -> N * A + B = N * A + N * B + // + // This assert occurs during monomorphization when the actual values for N, A, and B + // become known. This also means if this function is not called, the assert will not trigger. + checked_transmute(x) +} diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_global_using_trait/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_global_using_trait/src/main.nr index a1a2c4b125a..86bde1c5eba 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/comptime_global_using_trait/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_global_using_trait/src/main.nr @@ -1,3 +1,3 @@ -comptime global FOO: i32 = Default::default(); +pub comptime global FOO: i32 = Default::default(); fn main() {} diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_module/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_module/src/main.nr index 5099f3b7acb..09b1e98744d 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/comptime_module/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_module/src/main.nr @@ -1,6 +1,6 @@ -#[outer_attribute] +#['outer_attribute] mod foo { - #![some_attribute] + #!['some_attribute] pub fn x() {} pub fn y() {} pub fn z() {} @@ -12,7 +12,7 @@ mod foo { contract bar {} -#[some_attribute] +#['some_attribute] mod another_module {} #[outer_attribute_func] diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_struct_definition/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_struct_definition/src/main.nr index da2871a253d..97d99d0de6b 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/comptime_struct_definition/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_struct_definition/src/main.nr @@ -1,11 +1,11 @@ #[my_comptime_fn] -struct MyType { +pub struct MyType { field1: [A; 10], field2: (B, C), } #[mutate_struct_fields] -struct I32AndField { +pub struct I32AndField { z: i8, } @@ -26,14 +26,14 @@ comptime fn mutate_struct_fields(s: StructDefinition) { mod foo { #[attr] - struct Foo {} + pub struct Foo {} comptime fn attr(s: StructDefinition) { assert_eq(s.module().name(), quote { foo }); } #[add_generic] - struct Bar {} + pub struct Bar {} // docs:start:add-generic-example comptime fn add_generic(s: StructDefinition) { diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_trait_constraint/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_trait_constraint/src/main.nr index 448da96a460..43075058480 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/comptime_trait_constraint/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_trait_constraint/src/main.nr @@ -1,4 +1,4 @@ -use std::hash::{Hash, Hasher}; +use std::hash::Hasher; trait TraitWithGenerics { fn foo(self) -> (A, B); diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_trait_impl/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_trait_impl/src/main.nr index 87b48e7a357..8498e75d7f4 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/comptime_trait_impl/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_trait_impl/src/main.nr @@ -1,9 +1,7 @@ -use std::meta::type_of; - trait SomeTrait { fn foo(); } -struct SomeStruct { +pub struct SomeStruct { } diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_traits/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_traits/src/main.nr index 7d1e116dd0c..60fe264c57c 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/comptime_traits/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_traits/src/main.nr @@ -26,7 +26,7 @@ impl Neg for MyType { } } -fn neg_at_comptime() { +pub fn neg_at_comptime() { comptime { let value = MyType { value: 1 }; diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_type/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_type/src/main.nr index 2b1bd215960..68c3477b027 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/comptime_type/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_type/src/main.nr @@ -7,13 +7,13 @@ struct Foo { trait SomeTrait { } -struct StructImplementsSomeTrait { +pub struct StructImplementsSomeTrait { } impl SomeTrait for StructImplementsSomeTrait {} -struct StructDoesNotImplementSomeTrait { +pub struct StructDoesNotImplementSomeTrait { } @@ -160,7 +160,7 @@ fn main() { } // docs:start:implements_example -fn function_with_where(_x: T) where T: SomeTrait { +pub fn function_with_where(_x: T) where T: SomeTrait { comptime { let t = quote { T }.as_type(); diff --git a/noir/noir-repo/test_programs/compile_success_empty/ec_baby_jubjub/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/ec_baby_jubjub/src/main.nr index 616ac7ef6ee..207869e5291 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/ec_baby_jubjub/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/ec_baby_jubjub/src/main.nr @@ -1,7 +1,6 @@ // Tests may be checked against https://github.com/cfrg/draft-irtf-cfrg-hash-to-curve/tree/main/poc use std::ec::tecurve::affine::Curve as AffineCurve; use std::ec::tecurve::affine::Point as Gaffine; -use std::ec::tecurve::curvegroup::Curve; use std::ec::tecurve::curvegroup::Point as G; use std::ec::swcurve::affine::Point as SWGaffine; diff --git a/noir/noir-repo/test_programs/compile_success_empty/embedded_curve_add_simplification/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/embedded_curve_add_simplification/src/main.nr index 39992a6454b..5a619906775 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/embedded_curve_add_simplification/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/embedded_curve_add_simplification/src/main.nr @@ -1,4 +1,4 @@ -use std::embedded_curve_ops::{EmbeddedCurvePoint, EmbeddedCurveScalar, multi_scalar_mul}; +use std::embedded_curve_ops::EmbeddedCurvePoint; fn main() { let zero = EmbeddedCurvePoint::point_at_infinity(); diff --git a/noir/noir-repo/test_programs/compile_success_empty/function_attribute/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/function_attribute/src/main.nr index ec22b730d3f..1bc524d4cb5 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/function_attribute/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/function_attribute/src/main.nr @@ -1,5 +1,5 @@ #[function_attr] -fn foo() {} +pub fn foo() {} struct Foo {} diff --git a/noir/noir-repo/test_programs/compile_success_empty/method_call_regression/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/method_call_regression/src/main.nr index de58271cae6..26493c4836b 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/method_call_regression/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/method_call_regression/src/main.nr @@ -9,7 +9,9 @@ struct Struct { a: A, b: B } // Before the fix, this candidate is searched first, binding ? to `u8` permanently. impl Struct { - fn foo(self) {} + fn foo(self) { + let _ = self; + } } // Then this candidate would be searched next but would not be a valid @@ -19,5 +21,7 @@ impl Struct { // method is actually selected. So this candidate is now valid since // `Struct` unifies with `Struct` with `? = u32`. impl Struct { - fn foo(self) {} + fn foo(self) { + let _ = self; + } } diff --git a/noir/noir-repo/test_programs/compile_success_empty/mod_nr_entrypoint/src/foo/mod.nr b/noir/noir-repo/test_programs/compile_success_empty/mod_nr_entrypoint/src/foo/mod.nr index 4eac6cb8514..216294fbf08 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/mod_nr_entrypoint/src/foo/mod.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/mod_nr_entrypoint/src/foo/mod.nr @@ -1,4 +1,4 @@ -mod bar; +pub mod bar; pub fn in_foo_mod() -> Field { 1 diff --git a/noir/noir-repo/test_programs/compile_success_empty/no_duplicate_methods/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/no_duplicate_methods/src/main.nr index 3ca9c841a8c..8b809715529 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/no_duplicate_methods/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/no_duplicate_methods/src/main.nr @@ -7,7 +7,7 @@ trait ToField2 { fn to_field(self) -> Field; } -struct Foo { x: Field } +pub struct Foo { x: Field } impl ToField for Foo { fn to_field(self) -> Field { diff --git a/noir/noir-repo/test_programs/compile_success_empty/numeric_generics_explicit/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/numeric_generics_explicit/src/main.nr index 5c618e9db36..c940e28dac2 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/numeric_generics_explicit/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/numeric_generics_explicit/src/main.nr @@ -85,7 +85,7 @@ trait Deserialize { fn deserialize(fields: [Field; N]) -> Self; } -struct PublicStorage {} +pub struct PublicStorage {} impl PublicStorage { fn read() -> T where T: Deserialize { @@ -102,10 +102,10 @@ impl PublicStorage { // Check that we can thread numeric generics into nested structs // and also that we can handle nested structs with numeric generics // which are declared after the parent struct -struct NestedNumeric { +pub struct NestedNumeric { a: Field, b: InnerNumeric } -struct InnerNumeric { +pub struct InnerNumeric { inner: [u32; N], } diff --git a/noir/noir-repo/test_programs/compile_success_empty/regression_2099/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/regression_2099/src/main.nr index 660f72f56e5..b390daec8c8 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/regression_2099/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/regression_2099/src/main.nr @@ -1,13 +1,5 @@ use std::ec::tecurve::affine::Curve as AffineCurve; use std::ec::tecurve::affine::Point as Gaffine; -use std::ec::tecurve::curvegroup::Curve; -use std::ec::tecurve::curvegroup::Point as G; - -use std::ec::swcurve::affine::Point as SWGaffine; -use std::ec::swcurve::curvegroup::Point as SWG; - -use std::ec::montcurve::affine::Point as MGaffine; -use std::ec::montcurve::curvegroup::Point as MG; fn main() { // Define Baby Jubjub (ERC-2494) parameters in affine representation diff --git a/noir/noir-repo/test_programs/compile_success_empty/regression_4436/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/regression_4436/src/main.nr index 336d0f1f4ed..30e4a942bdf 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/regression_4436/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/regression_4436/src/main.nr @@ -3,15 +3,15 @@ trait LibTrait { fn get_constant() -> Field; } -global STRUCT_A_LEN: u32 = 3; -global STRUCT_B_LEN: u32 = 5; +pub global STRUCT_A_LEN: u32 = 3; +pub global STRUCT_B_LEN: u32 = 5; -struct StructA; -struct StructB; +pub struct StructA; +pub struct StructB; impl LibTrait for StructA { fn broadcast() { - Self::get_constant(); + let _ = Self::get_constant(); } fn get_constant() -> Field { @@ -20,7 +20,7 @@ impl LibTrait for StructA { } impl LibTrait for StructB { fn broadcast() { - Self::get_constant(); + let _ = Self::get_constant(); } fn get_constant() -> Field { diff --git a/noir/noir-repo/test_programs/compile_success_empty/schnorr_simplification/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/schnorr_simplification/src/main.nr index e1095cd7fe2..1a9023e2d7a 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/schnorr_simplification/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/schnorr_simplification/src/main.nr @@ -1,5 +1,3 @@ -use std::embedded_curve_ops; - // Note: If main has any unsized types, then the verifier will never be able // to figure out the circuit instance fn main() { diff --git a/noir/noir-repo/test_programs/compile_success_empty/slice_init_with_complex_type/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/slice_init_with_complex_type/src/main.nr index 01ccf2fdeff..27339f812e7 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/slice_init_with_complex_type/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/slice_init_with_complex_type/src/main.nr @@ -4,14 +4,14 @@ struct strct1 { fn main() { let var1: [[i32; 1]] = [[0]]; - let var2: [[i32; 1]] = var1; + let _var2: [[i32; 1]] = var1; let var1: [(i32, u8)] = [(1, 2)]; - let var2: [(i32, u8)] = var1; + let _var2: [(i32, u8)] = var1; let var3: [strct1] = [strct1 { elem1: 1321351 }]; - let var4: [strct1] = var3; + let _var4: [strct1] = var3; let var1: [i32; 1] = [0]; - let var2: [[i32; 1]] = [var1]; + let _var2: [[i32; 1]] = [var1]; } diff --git a/noir/noir-repo/test_programs/compile_success_empty/static_assert/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/static_assert/src/main.nr index e61d9388ceb..11d30e4e069 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/static_assert/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/static_assert/src/main.nr @@ -9,7 +9,7 @@ global GLOBAL_THREE = GLOBAL_ONE + GLOBAL_TWO; global GLOBAL_ARRAY_PAIR = [GLOBAL_ONE, GLOBAL_TWO]; global GLOBAL_SLICE_PAIR = &[GLOBAL_ONE, GLOBAL_TWO]; -struct Foo { +pub struct Foo { field: Field, array: [Field; 3], slice: [Field], diff --git a/noir/noir-repo/test_programs/compile_success_empty/struct_public_field/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/struct_public_field/Nargo.toml new file mode 100644 index 00000000000..37307b94af5 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/struct_public_field/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "struct_public_field" +type = "bin" +authors = [""] + +[dependencies] +dependency = {path = "dependency"} \ No newline at end of file diff --git a/noir/noir-repo/test_programs/compile_success_empty/struct_public_field/dependency/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/struct_public_field/dependency/Nargo.toml new file mode 100644 index 00000000000..2e471678a44 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/struct_public_field/dependency/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "dependency" +type = "lib" +authors = [""] + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/compile_success_empty/struct_public_field/dependency/src/lib.nr b/noir/noir-repo/test_programs/compile_success_empty/struct_public_field/dependency/src/lib.nr new file mode 100644 index 00000000000..0e9bd4bd9f8 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/struct_public_field/dependency/src/lib.nr @@ -0,0 +1,4 @@ +pub struct Point { + pub x: Field, + pub y: Field, +} diff --git a/noir/noir-repo/test_programs/compile_success_empty/struct_public_field/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/struct_public_field/src/main.nr new file mode 100644 index 00000000000..c269c474de7 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/struct_public_field/src/main.nr @@ -0,0 +1,8 @@ +use dependency::Point; + +fn main() { + let point = Point { x: 1, y: 2 }; + let _ = point.x; + let Point { x, y } = point; + let _ = (x, y); +} diff --git a/noir/noir-repo/test_programs/compile_success_empty/trait_allowed_item_name_matches/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/trait_allowed_item_name_matches/src/main.nr index 44cad58c2a6..4104ca71037 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/trait_allowed_item_name_matches/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/trait_allowed_item_name_matches/src/main.nr @@ -1,22 +1,22 @@ -trait Trait1 { +pub trait Trait1 { // types and consts with the same name are allowed type Tralala; let Tralala: u32; } -trait Trait2 { +pub trait Trait2 { // consts and types with the same name are allowed let Tralala: u32; type Tralala; } -trait Trait3 { +pub trait Trait3 { // types and functions with the same name are allowed type Tralala; fn Tralala(); } -trait Trait4 { +pub trait Trait4 { // functions and types with the same name are allowed fn Tralala(); type Tralala; diff --git a/noir/noir-repo/test_programs/compile_success_empty/trait_as_constraint/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/trait_as_constraint/src/main.nr index 1911f045c27..b516376aae5 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/trait_as_constraint/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/trait_as_constraint/src/main.nr @@ -1,5 +1,5 @@ #[test_as_constraint] -trait Foo {} +pub trait Foo {} comptime fn test_as_constraint(t: TraitDefinition) { let constraint = t.as_trait_constraint(); diff --git a/noir/noir-repo/test_programs/compile_success_empty/trait_associated_member_names_clashes/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/trait_associated_member_names_clashes/src/main.nr index 412a75010f6..06bf311aa3c 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/trait_associated_member_names_clashes/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/trait_associated_member_names_clashes/src/main.nr @@ -6,7 +6,7 @@ trait Trait2 { fn tralala() -> Field; } -struct Struct1 { +pub struct Struct1 { } impl Struct1 { diff --git a/noir/noir-repo/test_programs/compile_success_empty/trait_call_full_path/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/trait_call_full_path/src/main.nr index 2d4b003f2ad..aea0f436dce 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/trait_call_full_path/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/trait_call_full_path/src/main.nr @@ -1,5 +1,5 @@ mod foo { - trait Trait { + pub trait Trait { fn me(self) -> Self; } diff --git a/noir/noir-repo/test_programs/compile_success_empty/trait_function_calls/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/trait_function_calls/src/main.nr index 62af0e756cd..352f18a74c0 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/trait_function_calls/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/trait_function_calls/src/main.nr @@ -26,6 +26,7 @@ trait Trait1a { self.trait_method2() * 7892 - self.vl } fn trait_method2(self) -> Field { + let _ = self; 43278 } } @@ -43,6 +44,7 @@ trait Trait1b { struct Struct1b { vl: Field } impl Trait1b for Struct1b { fn trait_method2(self) -> Field { + let _ = self; 2394 } } @@ -56,6 +58,7 @@ trait Trait1c { struct Struct1c { vl: Field } impl Trait1c for Struct1c { fn trait_method2(self) -> Field { + let _ = self; 5485 } } @@ -65,6 +68,7 @@ trait Trait1d { self.trait_method2() * 2825 - self.vl } fn trait_method2(self) -> Field { + let _ = self; 29341 } } @@ -89,6 +93,7 @@ impl Trait1e for Struct1e { self.trait_method2() * 47324 - self.vl } fn trait_method2(self) -> Field { + let _ = self; 58945 } } @@ -105,6 +110,7 @@ impl Trait1f for Struct1f { self.trait_method2() * 34875 - self.vl } fn trait_method2(self) -> Field { + let _ = self; 5748 } } @@ -112,6 +118,7 @@ impl Trait1f for Struct1f { trait Trait1g { fn trait_method1(self) -> Field; fn trait_method2(self) -> Field { + let _ = self; 37845 } } @@ -134,6 +141,7 @@ impl Trait1h for Struct1h { self.trait_method2() * 3482 - self.vl } fn trait_method2(self) -> Field { + let _ = self; 8542 } } @@ -148,6 +156,7 @@ impl Trait1i for Struct1i { self.trait_method2() * 23478 - self.vl } fn trait_method2(self) -> Field { + let _ = self; 98543 } } @@ -290,6 +299,7 @@ trait Trait3a { b.trait_method2() * 8344 - b.vl + a } fn trait_method2(self) -> Field { + let _ = self; 19212 } } @@ -307,6 +317,7 @@ trait Trait3b { struct Struct3b { vl: Field } impl Trait3b for Struct3b { fn trait_method2(self) -> Field { + let _ = self; 2392 } } @@ -320,6 +331,7 @@ trait Trait3c { struct Struct3c { vl: Field } impl Trait3c for Struct3c { fn trait_method2(self) -> Field { + let _ = self; 7743 } } @@ -329,6 +341,7 @@ trait Trait3d { b.trait_method2() * 291 - b.vl + a } fn trait_method2(self) -> Field { + let _ = self; 3328 } } @@ -353,6 +366,7 @@ impl Trait3e for Struct3e { b.trait_method2() * 81232 - b.vl + a } fn trait_method2(self) -> Field { + let _ = self; 80002 } } @@ -369,6 +383,7 @@ impl Trait3f for Struct3f { b.trait_method2() * 29223 - b.vl + a } fn trait_method2(self) -> Field { + let _ = self; 63532 } } @@ -376,6 +391,7 @@ impl Trait3f for Struct3f { trait Trait3g { fn trait_function1(a: Field, b: Self) -> Field; fn trait_method2(self) -> Field { + let _ = self; 8887 } } @@ -398,6 +414,7 @@ impl Trait3h for Struct3h { b.trait_method2() * 74747 - b.vl + a } fn trait_method2(self) -> Field { + let _ = self; 6283 } } @@ -412,6 +429,7 @@ impl Trait3i for Struct3i { b.trait_method2() * 1237 - b.vl + a } fn trait_method2(self) -> Field { + let _ = self; 84352 } } @@ -425,7 +443,7 @@ trait Trait4a { 2932 } } -struct Struct4a { vl: Field } +pub struct Struct4a { vl: Field } impl Trait4a for Struct4a {} // 4b) trait default function -> trait overriden function trait Trait4b { @@ -436,7 +454,7 @@ trait Trait4b { 2932 } } -struct Struct4b { vl: Field } +pub struct Struct4b { vl: Field } impl Trait4b for Struct4b { fn trait_function2() -> Field { 9353 @@ -449,7 +467,7 @@ trait Trait4c { } fn trait_function2() -> Field; } -struct Struct4c { vl: Field } +pub struct Struct4c { vl: Field } impl Trait4c for Struct4c { fn trait_function2() -> Field { 2928 @@ -464,7 +482,7 @@ trait Trait4d { 9332 } } -struct Struct4d { vl: Field } +pub struct Struct4d { vl: Field } impl Trait4d for Struct4d { fn trait_function1() -> Field { Self::trait_function2() * 8374 @@ -479,7 +497,7 @@ trait Trait4e { 28328 } } -struct Struct4e { vl: Field } +pub struct Struct4e { vl: Field } impl Trait4e for Struct4e { fn trait_function1() -> Field { Self::trait_function2() * 12323 @@ -495,7 +513,7 @@ trait Trait4f { } fn trait_function2() -> Field; } -struct Struct4f { vl: Field } +pub struct Struct4f { vl: Field } impl Trait4f for Struct4f { fn trait_function1() -> Field { Self::trait_function2() * 21392 @@ -511,7 +529,7 @@ trait Trait4g { 2932 } } -struct Struct4g { vl: Field } +pub struct Struct4g { vl: Field } impl Trait4g for Struct4g { fn trait_function1() -> Field { Self::trait_function2() * 3345 @@ -524,7 +542,7 @@ trait Trait4h { 5756 } } -struct Struct4h { vl: Field } +pub struct Struct4h { vl: Field } impl Trait4h for Struct4h { fn trait_function1() -> Field { Self::trait_function2() * 6478 @@ -538,7 +556,7 @@ trait Trait4i { fn trait_function1() -> Field; fn trait_function2() -> Field; } -struct Struct4i { vl: Field } +pub struct Struct4i { vl: Field } impl Trait4i for Struct4i { fn trait_function1() -> Field { Self::trait_function2() * 8239 diff --git a/noir/noir-repo/test_programs/compile_success_empty/trait_multi_module_test/src/module1.nr b/noir/noir-repo/test_programs/compile_success_empty/trait_multi_module_test/src/module1.nr index 4d41ff2909a..b5c05d69378 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/trait_multi_module_test/src/module1.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/trait_multi_module_test/src/module1.nr @@ -1,2 +1,2 @@ -trait MyTrait { +pub trait MyTrait { } diff --git a/noir/noir-repo/test_programs/compile_success_empty/trait_multi_module_test/src/module2.nr b/noir/noir-repo/test_programs/compile_success_empty/trait_multi_module_test/src/module2.nr index 3cadb6d78cb..c1335fe95e4 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/trait_multi_module_test/src/module2.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/trait_multi_module_test/src/module2.nr @@ -1,2 +1,2 @@ -struct MyStruct { +pub struct MyStruct { } diff --git a/noir/noir-repo/test_programs/compile_success_empty/trait_multi_module_test/src/module4.nr b/noir/noir-repo/test_programs/compile_success_empty/trait_multi_module_test/src/module4.nr index f9458e83c4a..f1e3c407531 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/trait_multi_module_test/src/module4.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/trait_multi_module_test/src/module4.nr @@ -1,2 +1,2 @@ -trait MyTrait4 { +pub trait MyTrait4 { } diff --git a/noir/noir-repo/test_programs/compile_success_empty/trait_multi_module_test/src/module5.nr b/noir/noir-repo/test_programs/compile_success_empty/trait_multi_module_test/src/module5.nr index cd9b7f0bf39..8c1c41ea25e 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/trait_multi_module_test/src/module5.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/trait_multi_module_test/src/module5.nr @@ -1,2 +1,2 @@ -struct MyStruct5 { +pub struct MyStruct5 { } diff --git a/noir/noir-repo/test_programs/compile_success_empty/trait_where_clause/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/trait_where_clause/src/main.nr index 655450d05ac..86e7f70a3a3 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/trait_where_clause/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/trait_where_clause/src/main.nr @@ -42,6 +42,7 @@ impl StaticTrait for Static100 { struct Static200 {} impl StaticTrait for Static200 { fn static_function(slf: Self) -> Field { + let _ = slf; 200 } } diff --git a/noir/noir-repo/test_programs/compile_success_empty/trait_where_clause/src/the_trait.nr b/noir/noir-repo/test_programs/compile_success_empty/trait_where_clause/src/the_trait.nr index c5cac4a1186..6390856731e 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/trait_where_clause/src/the_trait.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/trait_where_clause/src/the_trait.nr @@ -1,9 +1,10 @@ -trait Asd { +pub trait Asd { fn asd(self) -> Field; } -trait StaticTrait { +pub trait StaticTrait { fn static_function(slf: Self) -> Field { + let _ = slf; 100 } } diff --git a/noir/noir-repo/test_programs/compile_success_empty/type_path/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/type_path/src/main.nr index 96f3a29d96b..968812801c6 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/type_path/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/type_path/src/main.nr @@ -8,7 +8,7 @@ fn main() { } } -struct Foo {} +pub struct Foo {} impl Foo { fn static() {} diff --git a/noir/noir-repo/test_programs/compile_success_empty/unary_operators/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/unary_operators/src/main.nr index ef622fd3eb9..8793086c1c1 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/unary_operators/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/unary_operators/src/main.nr @@ -3,5 +3,5 @@ fn main() { assert(x == 1 - 2); let y: i32 = -1; - assert(x == 1 - 2); + assert(y == 1 - 2); } diff --git a/noir/noir-repo/test_programs/compile_success_empty/unquote_function/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/unquote_function/src/main.nr index 273a091b26d..7b6442abe8a 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/unquote_function/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/unquote_function/src/main.nr @@ -3,7 +3,7 @@ fn main() { } #[output_function] -fn foo() {} +pub fn foo() {} comptime fn output_function(_f: FunctionDefinition) -> Quoted { quote { diff --git a/noir/noir-repo/test_programs/compile_success_empty/unquote_multiple_items_from_annotation/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/unquote_multiple_items_from_annotation/src/main.nr index 04f07f038e5..11d50fc2ab5 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/unquote_multiple_items_from_annotation/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/unquote_multiple_items_from_annotation/src/main.nr @@ -1,5 +1,5 @@ #[foo] -struct Foo {} +pub struct Foo {} fn main() { assert_eq(ONE, 1); diff --git a/noir/noir-repo/test_programs/compile_success_empty/unused_variables/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/unused_variables/src/main.nr index f82cace0509..2fb57e3b275 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/unused_variables/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/unused_variables/src/main.nr @@ -1 +1,4 @@ -fn main(x: Field, y: pub Field) {} +fn main(x: Field, y: pub Field) { + let _ = x; + let _ = y; +} diff --git a/noir/noir-repo/test_programs/compile_success_empty/use_callers_scope/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/use_callers_scope/src/main.nr index b4e8a7f7c4d..30db6c48f7c 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/use_callers_scope/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/use_callers_scope/src/main.nr @@ -1,7 +1,7 @@ #[bar::struct_attr] -struct Foo {} +pub struct Foo {} -struct Bar {} +pub struct Bar {} #[bar::fn_attr] fn main() {} diff --git a/noir/noir-repo/test_programs/compile_success_empty/workspace_reexport_bug/library/src/lib.nr b/noir/noir-repo/test_programs/compile_success_empty/workspace_reexport_bug/library/src/lib.nr index e3a1539ea65..e56c127f562 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/workspace_reexport_bug/library/src/lib.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/workspace_reexport_bug/library/src/lib.nr @@ -1,2 +1,2 @@ // Re-export -use library2::ReExportMeFromAnotherLib; +pub use library2::ReExportMeFromAnotherLib; diff --git a/noir/noir-repo/test_programs/compile_success_empty/workspace_reexport_bug/library2/src/lib.nr b/noir/noir-repo/test_programs/compile_success_empty/workspace_reexport_bug/library2/src/lib.nr index 7e5a29a1424..c38c7bd1675 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/workspace_reexport_bug/library2/src/lib.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/workspace_reexport_bug/library2/src/lib.nr @@ -1,5 +1,5 @@ // When we re-export this type from another library and then use it in // main, we get a panic -struct ReExportMeFromAnotherLib { +pub struct ReExportMeFromAnotherLib { x : Field, } diff --git a/noir/noir-repo/test_programs/execution_success/check_large_field_bits/Nargo.toml b/noir/noir-repo/test_programs/execution_success/check_large_field_bits/Nargo.toml new file mode 100644 index 00000000000..33d5dd66484 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/check_large_field_bits/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "check_large_field_bits" +type = "bin" +authors = [""] +compiler_version = ">=0.35.0" + +[dependencies] diff --git a/noir/noir-repo/test_programs/execution_success/check_large_field_bits/src/main.nr b/noir/noir-repo/test_programs/execution_success/check_large_field_bits/src/main.nr new file mode 100644 index 00000000000..542a06ecb6f --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/check_large_field_bits/src/main.nr @@ -0,0 +1,45 @@ +// 2^32 + 1 +global A: Field = 4294967297; +global B: Field = 4294967297; + +// 2^33 + 2 +global C: Field = A + B; + +fn main() { + // 2 * (2^32 + 1) == 2^33 + 2 + assert(C == 8589934594); + + let mut leading_zeroes = 0; + let mut stop = false; + let bits: [u1; 64] = C.to_be_bits(); + for i in 0..64 { + if (bits[i] == 0) & !stop { + leading_zeroes += 1; + } else { + stop = true; + } + } + let size = 64 - leading_zeroes; + + // 8589934594 has 34 bits + assert(size == 34); + C.assert_max_bit_size::<34>(); + + assert( + C.to_be_bits() == [ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 + ] + ); + + // leading big-endian bits past 34 are 0's + assert( + C.to_be_bits() == [ + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 + ] + ); + assert( + C.to_be_bits() == [ + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 + ] + ); +} diff --git a/noir/noir-repo/test_programs/execution_success/inline_never_basic/src/main.nr b/noir/noir-repo/test_programs/execution_success/inline_never_basic/src/main.nr index 1922aaedb6c..505a1641c76 100644 --- a/noir/noir-repo/test_programs/execution_success/inline_never_basic/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/inline_never_basic/src/main.nr @@ -2,7 +2,7 @@ fn main(x: Field, y: pub Field) { basic_check(x, y); } -#[inline(never)] +#['inline(never)] fn basic_check(x: Field, y: Field) { assert(x != y); } diff --git a/noir/noir-repo/test_programs/execution_success/regression_5462/Nargo.toml b/noir/noir-repo/test_programs/execution_success/regression_5462/Nargo.toml new file mode 100644 index 00000000000..97015f78963 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/regression_5462/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "regression_5462" +type = "bin" +authors = [""] +compiler_version = ">=0.35.0" + +[dependencies] diff --git a/noir/noir-repo/test_programs/execution_success/regression_5462/src/main.nr b/noir/noir-repo/test_programs/execution_success/regression_5462/src/main.nr new file mode 100644 index 00000000000..092f7ab96a5 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/regression_5462/src/main.nr @@ -0,0 +1,11 @@ +fn main() { + let empty_slice: [u8] = &[]; + + if empty_slice != &[] { + let _ = empty_slice.pop_front(); + } + + if empty_slice.len() != 0 { + let _ = empty_slice.pop_front(); + } +} diff --git a/noir/noir-repo/test_programs/execution_success/slice_regex/src/main.nr b/noir/noir-repo/test_programs/execution_success/slice_regex/src/main.nr index 15768248473..3b860839a6e 100644 --- a/noir/noir-repo/test_programs/execution_success/slice_regex/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/slice_regex/src/main.nr @@ -208,7 +208,7 @@ fn main() { let result = three_ones_regex.match("1111".as_bytes().as_slice()); println(result); assert_eq(result, Match { succeeded: true, match_ends: 3, leftover: &[] }); - // TODO(https://github.com/noir-lang/noir/issues/5462): re-enable these cases and complete the test using array_regex below + // TODO(https://github.com/noir-lang/noir/issues/6285): re-enable these cases and complete the test using array_regex below // // // 1* // let ones_regex: Star, 5> = Star { inner: "1" }; @@ -279,7 +279,9 @@ fn main() { // }); } -// array_regex: use to complete test once https://github.com/noir-lang/noir/issues/5462 is resolved +// TODO +// array_regex execution_success test: +// use to complete test once https://github.com/noir-lang/noir/issues/6285 is resolved // // // offset <= len <= N // struct Bvec { diff --git a/noir/noir-repo/test_programs/execution_success/trait_inheritance/Nargo.toml b/noir/noir-repo/test_programs/execution_success/trait_inheritance/Nargo.toml new file mode 100644 index 00000000000..b8390fc800d --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/trait_inheritance/Nargo.toml @@ -0,0 +1,5 @@ +[package] +name = "trait_inheritance" +type = "bin" +authors = [""] +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_success/trait_inheritance/Prover.toml b/noir/noir-repo/test_programs/execution_success/trait_inheritance/Prover.toml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/noir/noir-repo/test_programs/execution_success/trait_inheritance/src/main.nr b/noir/noir-repo/test_programs/execution_success/trait_inheritance/src/main.nr new file mode 100644 index 00000000000..1d17d386189 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/trait_inheritance/src/main.nr @@ -0,0 +1,33 @@ +trait Foo { + fn foo(self) -> Field; +} + +trait Bar: Foo { + fn bar(self) -> Field { + self.foo() + 1 + } + + fn baz(self) -> Field; +} + +struct Struct { + x: Field, +} + +impl Foo for Struct { + fn foo(self) -> Field { + self.x + } +} + +impl Bar for Struct { + fn baz(self) -> Field { + self.foo() + 2 + } +} + +fn main() { + let s = Struct { x: 1 }; + assert_eq(s.bar(), 2); + assert_eq(s.baz(), 3); +} diff --git a/noir/noir-repo/test_programs/noir_test_success/bounded_vec/src/main.nr b/noir/noir-repo/test_programs/noir_test_success/bounded_vec/src/main.nr index 0ad5840ba32..fa2c55e6934 100644 --- a/noir/noir-repo/test_programs/noir_test_success/bounded_vec/src/main.nr +++ b/noir/noir-repo/test_programs/noir_test_success/bounded_vec/src/main.nr @@ -79,7 +79,7 @@ fn set_unchecked_example() { // 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. + // it won't cause a constraint failure. vec.set_unchecked(2, 42); println(vec); @@ -189,13 +189,13 @@ fn test_vec_extend_from_bounded_vec() { // docs:end:bounded-vec-extend-from-bounded-vec-example } -#[test(should_fail_with="extend_from_array out of bounds")] +#[test(should_fail_with = "extend_from_array out of bounds")] fn test_vec_extend_from_array_out_of_bound() { let mut vec: BoundedVec = BoundedVec::new(); vec.extend_from_array([2, 4, 6]); } -#[test(should_fail_with="extend_from_array out of bounds")] +#[test(should_fail_with = "extend_from_array out of bounds")] fn test_vec_extend_from_array_twice_out_of_bound() { let mut vec: BoundedVec = BoundedVec::new(); vec.extend_from_array([2]); @@ -223,14 +223,14 @@ fn test_vec_get_uninitialized() { let _x = vec.get(0); } -#[test(should_fail_with="push out of bounds")] +#[test(should_fail_with = "push out of bounds")] fn test_vec_push_out_of_bound() { let mut vec: BoundedVec = BoundedVec::new(); vec.push(1); vec.push(2); } -#[test(should_fail_with="extend_from_bounded_vec out of bounds")] +#[test(should_fail_with = "extend_from_bounded_vec out of bounds")] fn test_vec_extend_from_bounded_vec_out_of_bound() { let mut vec: BoundedVec = BoundedVec::new(); @@ -240,7 +240,7 @@ fn test_vec_extend_from_bounded_vec_out_of_bound() { vec.extend_from_bounded_vec(another_vec); } -#[test(should_fail_with="extend_from_bounded_vec out of bounds")] +#[test(should_fail_with = "extend_from_bounded_vec out of bounds")] fn test_vec_extend_from_bounded_vec_twice_out_of_bound() { let mut vec: BoundedVec = BoundedVec::new(); vec.extend_from_array([1, 2]); diff --git a/noir/noir-repo/test_programs/test_libraries/exporting_lib/src/lib.nr b/noir/noir-repo/test_programs/test_libraries/exporting_lib/src/lib.nr index fdd9f139d41..7da75ce5413 100644 --- a/noir/noir-repo/test_programs/test_libraries/exporting_lib/src/lib.nr +++ b/noir/noir-repo/test_programs/test_libraries/exporting_lib/src/lib.nr @@ -1,8 +1,8 @@ -struct MyStruct { - inner: Field +pub struct MyStruct { + pub inner: Field } -type FooStruct = MyStruct; +pub type FooStruct = MyStruct; pub fn is_struct_zero(val: MyStruct) -> bool { val.inner == 0 diff --git a/noir/noir-repo/test_programs/test_libraries/reexporting_lib/src/lib.nr b/noir/noir-repo/test_programs/test_libraries/reexporting_lib/src/lib.nr index 1bced548304..f106971028d 100644 --- a/noir/noir-repo/test_programs/test_libraries/reexporting_lib/src/lib.nr +++ b/noir/noir-repo/test_programs/test_libraries/reexporting_lib/src/lib.nr @@ -1,3 +1,3 @@ -use exporting_lib::{MyStruct, FooStruct}; +pub use exporting_lib::{MyStruct, FooStruct}; -use exporting_lib as lib; +pub use exporting_lib as lib; diff --git a/noir/noir-repo/tooling/lsp/src/lib.rs b/noir/noir-repo/tooling/lsp/src/lib.rs index 771f67e1fa2..39e14a74007 100644 --- a/noir/noir-repo/tooling/lsp/src/lib.rs +++ b/noir/noir-repo/tooling/lsp/src/lib.rs @@ -267,12 +267,16 @@ fn byte_span_to_range<'a, F: files::Files<'a> + ?Sized>( pub(crate) fn resolve_workspace_for_source_path(file_path: &Path) -> Result { if let Some(toml_path) = find_file_manifest(file_path) { - return resolve_workspace_from_toml( + match resolve_workspace_from_toml( &toml_path, PackageSelection::All, Some(NOIR_ARTIFACT_VERSION_STRING.to_string()), - ) - .map_err(|err| LspError::WorkspaceResolutionError(err.to_string())); + ) { + Ok(workspace) => return Ok(workspace), + Err(error) => { + eprintln!("Error while processing {:?}: {}", toml_path, error); + } + } } let Some(parent_folder) = file_path @@ -285,14 +289,22 @@ pub(crate) fn resolve_workspace_for_source_path(file_path: &Path) -> Result name, + Err(error) => { + eprintln!("{}", error); + CrateName::from_str("root").unwrap() + } + }; + let assumed_package = Package { version: None, compiler_required_version: Some(NOIR_ARTIFACT_VERSION_STRING.to_string()), root_dir: PathBuf::from(parent_folder), package_type: PackageType::Binary, entry_path: PathBuf::from(file_path), - name: CrateName::from_str(parent_folder) - .map_err(|err| LspError::WorkspaceResolutionError(err.to_string()))?, + name: crate_name, dependencies: BTreeMap::new(), expression_width: None, }; @@ -309,7 +321,11 @@ pub(crate) fn workspace_package_for_file<'a>( workspace: &'a Workspace, file_path: &Path, ) -> Option<&'a Package> { - workspace.members.iter().find(|package| file_path.starts_with(&package.root_dir)) + if workspace.is_assumed { + workspace.members.first() + } else { + workspace.members.iter().find(|package| file_path.starts_with(&package.root_dir)) + } } pub(crate) fn prepare_package<'file_manager, 'parsed_files>( diff --git a/noir/noir-repo/tooling/lsp/src/notifications/mod.rs b/noir/noir-repo/tooling/lsp/src/notifications/mod.rs index 6cddd278e62..d228eb564e6 100644 --- a/noir/noir-repo/tooling/lsp/src/notifications/mod.rs +++ b/noir/noir-repo/tooling/lsp/src/notifications/mod.rs @@ -6,11 +6,12 @@ use crate::{ insert_all_files_for_workspace_into_file_manager, PackageCacheData, WorkspaceCacheData, }; use async_lsp::{ErrorCode, LanguageClient, ResponseError}; -use fm::{FileManager, FileMap}; +use fm::{FileId, FileManager, FileMap}; use fxhash::FxHashMap as HashMap; -use lsp_types::{DiagnosticTag, Url}; +use lsp_types::{DiagnosticRelatedInformation, DiagnosticTag, Url}; use noirc_driver::check_crate; -use noirc_errors::{DiagnosticKind, FileDiagnostic}; +use noirc_errors::reporter::CustomLabel; +use noirc_errors::{DiagnosticKind, FileDiagnostic, Location}; use crate::types::{ notification, Diagnostic, DiagnosticSeverity, DidChangeConfigurationParams, @@ -195,11 +196,13 @@ fn publish_diagnostics( for file_diagnostic in file_diagnostics.into_iter() { let file_id = file_diagnostic.file_id; - let diagnostic = file_diagnostic_to_diagnostic(file_diagnostic, files); - let path = fm.path(file_id).expect("file must exist to have emitted diagnostic"); if let Ok(uri) = Url::from_file_path(path) { - diagnostics_per_url.entry(uri).or_default().push(diagnostic); + if let Some(diagnostic) = + file_diagnostic_to_diagnostic(file_diagnostic, files, fm, uri.clone()) + { + diagnostics_per_url.entry(uri).or_default().push(diagnostic); + } } } @@ -228,17 +231,21 @@ fn publish_diagnostics( state.files_with_errors.insert(package_root_dir.clone(), new_files_with_errors); } -fn file_diagnostic_to_diagnostic(file_diagnostic: FileDiagnostic, files: &FileMap) -> Diagnostic { +fn file_diagnostic_to_diagnostic( + file_diagnostic: FileDiagnostic, + files: &FileMap, + fm: &FileManager, + uri: Url, +) -> Option { let file_id = file_diagnostic.file_id; let diagnostic = file_diagnostic.diagnostic; - // TODO: Should this be the first item in secondaries? Should we bail when we find a range? - let range = diagnostic - .secondaries - .into_iter() - .filter_map(|sec| byte_span_to_range(files, file_id, sec.span.into())) - .last() - .unwrap_or_default(); + if diagnostic.secondaries.is_empty() { + return None; + } + + let span = diagnostic.secondaries.first().unwrap().span; + let range = byte_span_to_range(files, file_id, span.into())?; let severity = match diagnostic.kind { DiagnosticKind::Error => DiagnosticSeverity::ERROR, @@ -255,13 +262,60 @@ fn file_diagnostic_to_diagnostic(file_diagnostic: FileDiagnostic, files: &FileMa tags.push(DiagnosticTag::DEPRECATED); } - Diagnostic { + let secondaries = diagnostic + .secondaries + .into_iter() + .filter_map(|secondary| secondary_to_related_information(secondary, file_id, files, fm)); + let notes = diagnostic.notes.into_iter().map(|message| DiagnosticRelatedInformation { + location: lsp_types::Location { uri: uri.clone(), range }, + message, + }); + let call_stack = diagnostic + .call_stack + .into_iter() + .filter_map(|frame| call_stack_frame_to_related_information(frame, files, fm)); + let related_information: Vec<_> = secondaries.chain(notes).chain(call_stack).collect(); + + Some(Diagnostic { range, severity: Some(severity), message: diagnostic.message, tags: if tags.is_empty() { None } else { Some(tags) }, + related_information: if related_information.is_empty() { + None + } else { + Some(related_information) + }, ..Default::default() - } + }) +} + +fn secondary_to_related_information( + secondary: CustomLabel, + file_id: FileId, + files: &FileMap, + fm: &FileManager, +) -> Option { + let secondary_file = secondary.file.unwrap_or(file_id); + let path = fm.path(secondary_file)?; + let uri = Url::from_file_path(path).ok()?; + let range = byte_span_to_range(files, file_id, secondary.span.into())?; + let message = secondary.message; + Some(DiagnosticRelatedInformation { location: lsp_types::Location { uri, range }, message }) +} + +fn call_stack_frame_to_related_information( + frame: Location, + files: &FileMap, + fm: &FileManager, +) -> Option { + let path = fm.path(frame.file)?; + let uri = Url::from_file_path(path).ok()?; + let range = byte_span_to_range(files, frame.file, frame.span.into())?; + Some(DiagnosticRelatedInformation { + location: lsp_types::Location { uri, range }, + message: "Error originated here".to_string(), + }) } pub(super) fn on_exit( diff --git a/noir/noir-repo/tooling/lsp/src/requests/code_action/fill_struct_fields.rs b/noir/noir-repo/tooling/lsp/src/requests/code_action/fill_struct_fields.rs index be8602d99a9..739f0bf4a21 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/code_action/fill_struct_fields.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/code_action/fill_struct_fields.rs @@ -31,8 +31,8 @@ impl<'a> CodeActionFinder<'a> { let mut fields = struct_type.get_fields_as_written(); // Remove the ones that already exists in the constructor - for (field, _) in &constructor.fields { - fields.retain(|(name, _)| name != &field.0.contents); + for (constructor_field, _) in &constructor.fields { + fields.retain(|field| field.name.0.contents != constructor_field.0.contents); } if fields.is_empty() { @@ -93,7 +93,7 @@ impl<'a> CodeActionFinder<'a> { new_text.push(' '); } - for (index, (name, _)) in fields.iter().enumerate() { + for (index, field) in fields.iter().enumerate() { if index > 0 { new_text.push(','); if let Some(line_indent) = &line_indent { @@ -103,7 +103,7 @@ impl<'a> CodeActionFinder<'a> { new_text.push(' '); } } - new_text.push_str(name); + new_text.push_str(&field.name.0.contents); new_text.push_str(": ()"); } diff --git a/noir/noir-repo/tooling/lsp/src/requests/completion.rs b/noir/noir-repo/tooling/lsp/src/requests/completion.rs index 2882cb143bf..658033fc526 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/completion.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/completion.rs @@ -24,10 +24,12 @@ use noirc_frontend::{ UseTreeKind, Visitor, }, graph::{CrateId, Dependency}, - hir::def_map::{CrateDefMap, LocalModuleId, ModuleDefId, ModuleId}, + hir::{ + def_map::{CrateDefMap, LocalModuleId, ModuleDefId, ModuleId}, + resolution::visibility::{method_call_is_visible, struct_member_is_visible}, + }, hir_def::traits::Trait, - node_interner::NodeInterner, - node_interner::ReferenceId, + node_interner::{NodeInterner, ReferenceId, StructId}, parser::{Item, ItemKind, ParsedSubModule}, token::{CustomAttribute, Token, Tokens}, Kind, ParsedModule, StructType, Type, TypeBinding, @@ -202,14 +204,14 @@ impl<'a> NodeFinder<'a> { // Remove the ones that already exists in the constructor for (used_name, _) in &constructor_expression.fields { - fields.retain(|(_, (name, _))| name != &used_name.0.contents); + fields.retain(|(_, field)| field.name.0.contents != used_name.0.contents); } let self_prefix = false; - for (field_index, (field, typ)) in &fields { + for (field_index, field) in &fields { self.completion_items.push(self.struct_field_completion_item( - field, - typ, + &field.name.0.contents, + &field.typ, struct_type.id, *field_index, self_prefix, @@ -629,6 +631,9 @@ impl<'a> NodeFinder<'a> { return; }; + let struct_id = get_type_struct_id(typ); + let is_primitive = typ.is_primitive(); + for (name, methods) in methods_by_name { for (func_id, method_type) in methods.iter() { if function_kind == FunctionKind::Any { @@ -639,6 +644,31 @@ impl<'a> NodeFinder<'a> { } } + if let Some(struct_id) = struct_id { + let modifiers = self.interner.function_modifiers(&func_id); + let visibility = modifiers.visibility; + if !struct_member_is_visible( + struct_id, + visibility, + self.module_id, + self.def_maps, + ) { + continue; + } + } + + if is_primitive + && !method_call_is_visible( + typ, + func_id, + self.module_id, + self.interner, + self.def_maps, + ) + { + continue; + } + if name_matches(name, prefix) { let completion_items = self.function_completion_items( name, @@ -691,16 +721,25 @@ impl<'a> NodeFinder<'a> { prefix: &str, self_prefix: bool, ) { - for (field_index, (name, typ)) in struct_type.get_fields(generics).iter().enumerate() { - if name_matches(name, prefix) { - self.completion_items.push(self.struct_field_completion_item( - name, - typ, - struct_type.id, - field_index, - self_prefix, - )); + for (field_index, (name, visibility, typ)) in + struct_type.get_fields_with_visibility(generics).iter().enumerate() + { + if !struct_member_is_visible(struct_type.id, *visibility, self.module_id, self.def_maps) + { + continue; + } + + if !name_matches(name, prefix) { + continue; } + + self.completion_items.push(self.struct_field_completion_item( + name, + typ, + struct_type.id, + field_index, + self_prefix, + )); } } @@ -1691,6 +1730,18 @@ fn get_array_element_type(typ: Type) -> Option { } } +fn get_type_struct_id(typ: &Type) -> Option { + match typ { + Type::Struct(struct_type, _) => Some(struct_type.borrow().id), + Type::Alias(type_alias, generics) => { + let type_alias = type_alias.borrow(); + let typ = type_alias.get_type(generics); + get_type_struct_id(&typ) + } + _ => None, + } +} + /// Returns true if name matches a prefix written in code. /// `prefix` must already be in snake case. /// This method splits both name and prefix by underscore, diff --git a/noir/noir-repo/tooling/lsp/src/requests/completion/builtins.rs b/noir/noir-repo/tooling/lsp/src/requests/completion/builtins.rs index cf2af4036f7..078e2faf036 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/completion/builtins.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/completion/builtins.rs @@ -109,9 +109,9 @@ impl<'a> NodeFinder<'a> { if name_matches("test", prefix) || name_matches("should_fail_with", prefix) { self.completion_items.push(snippet_completion_item( - "test(should_fail_with=\"...\")", + "test(should_fail_with = \"...\")", CompletionItemKind::METHOD, - "test(should_fail_with=\"${1:message}\")", + "test(should_fail_with = \"${1:message}\")", None, )); } diff --git a/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs b/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs index bc8bb75e10c..668255eb34d 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs @@ -1071,6 +1071,22 @@ mod completion_tests { assert_completion(src, vec![field_completion_item("bar", "i32")]).await; } + #[test] + async fn test_does_not_suggest_private_struct_field() { + let src = r#" + mod moo { + pub struct Some { + property: i32, + } + } + + fn foo(s: moo::Some) { + s.>|< + } + "#; + assert_completion(src, vec![]).await; + } + #[test] async fn test_suggests_struct_impl_method() { let src = r#" @@ -1805,6 +1821,37 @@ mod completion_tests { .await; } + #[test] + async fn test_does_not_suggest_private_struct_methods() { + let src = r#" + mod moo { + pub struct Foo {} + + impl Foo { + fn bar(self) {} + } + } + + fn x(f: moo::Foo) { + f.>|<() + } + "#; + assert_completion(src, vec![]).await; + } + + #[test] + async fn test_does_not_suggest_private_primitive_methods() { + let src = r#" + fn foo(x: Field) { + x.>|< + } + "#; + let items = get_completions(src).await; + if items.iter().any(|item| item.label == "__assert_max_bit_size") { + panic!("Private method __assert_max_bit_size was suggested"); + } + } + #[test] async fn test_suggests_pub_use() { let src = r#" diff --git a/noir/noir-repo/tooling/lsp/src/requests/hover.rs b/noir/noir-repo/tooling/lsp/src/requests/hover.rs index 7b1fa7352a6..5087955ea77 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/hover.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/hover.rs @@ -137,11 +137,11 @@ fn format_struct(id: StructId, args: &ProcessRequestCallbackArgs) -> String { string.push_str(&struct_type.name.0.contents); format_generics(&struct_type.generics, &mut string); string.push_str(" {\n"); - for (field_name, field_type) in struct_type.get_fields_as_written() { + for field in struct_type.get_fields_as_written() { string.push_str(" "); - string.push_str(&field_name); + string.push_str(&field.name.0.contents); string.push_str(": "); - string.push_str(&format!("{}", field_type)); + string.push_str(&format!("{}", field.typ)); string.push_str(",\n"); } string.push_str(" }"); @@ -158,7 +158,7 @@ fn format_struct_member( ) -> String { let struct_type = args.interner.get_struct(id); let struct_type = struct_type.borrow(); - let (field_name, field_type) = struct_type.field_at(field_index); + let field = struct_type.field_at(field_index); let mut string = String::new(); if format_parent_module(ReferenceId::Struct(id), args, &mut string) { @@ -167,10 +167,10 @@ fn format_struct_member( string.push_str(&struct_type.name.0.contents); string.push('\n'); string.push_str(" "); - string.push_str(&field_name.0.contents); + string.push_str(&field.name.0.contents); string.push_str(": "); - string.push_str(&format!("{}", field_type)); - string.push_str(&go_to_type_links(field_type, args.interner, args.files)); + string.push_str(&format!("{}", field.typ)); + string.push_str(&go_to_type_links(&field.typ, args.interner, args.files)); append_doc_comments(args.interner, ReferenceId::StructMember(id, field_index), &mut string); diff --git a/noir/noir-repo/tooling/lsp/src/requests/inlay_hint.rs b/noir/noir-repo/tooling/lsp/src/requests/inlay_hint.rs index e119ee0d5b6..f7b3e6a748d 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/inlay_hint.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/inlay_hint.rs @@ -97,8 +97,8 @@ impl<'a> InlayHintCollector<'a> { ReferenceId::StructMember(struct_id, field_index) => { let struct_type = self.interner.get_struct(struct_id); let struct_type = struct_type.borrow(); - let (_field_name, field_type) = struct_type.field_at(field_index); - self.push_type_hint(lsp_location, field_type, false); + let field = struct_type.field_at(field_index); + self.push_type_hint(lsp_location, &field.typ, false); } ReferenceId::Module(_) | ReferenceId::Struct(_) diff --git a/noir/noir-repo/tooling/lsp/src/trait_impl_method_stub_generator.rs b/noir/noir-repo/tooling/lsp/src/trait_impl_method_stub_generator.rs index 14b40858bb1..b433ee2ec88 100644 --- a/noir/noir-repo/tooling/lsp/src/trait_impl_method_stub_generator.rs +++ b/noir/noir-repo/tooling/lsp/src/trait_impl_method_stub_generator.rs @@ -98,9 +98,9 @@ impl<'a> TraitImplMethodStubGenerator<'a> { } self.append_type(&constraint.typ); self.string.push_str(": "); - let trait_ = self.interner.get_trait(constraint.trait_id); + let trait_ = self.interner.get_trait(constraint.trait_bound.trait_id); self.string.push_str(&trait_.name.0.contents); - self.append_trait_generics(&constraint.trait_generics); + self.append_trait_generics(&constraint.trait_bound.trait_generics); } } diff --git a/noir/noir-repo/tooling/lsp/src/visibility.rs b/noir/noir-repo/tooling/lsp/src/visibility.rs index d6e26f7bc48..207302f327e 100644 --- a/noir/noir-repo/tooling/lsp/src/visibility.rs +++ b/noir/noir-repo/tooling/lsp/src/visibility.rs @@ -5,7 +5,7 @@ use noirc_frontend::{ graph::CrateId, hir::{ def_map::{CrateDefMap, ModuleId}, - resolution::import::can_reference_module_id, + resolution::visibility::can_reference_module_id, }, }; diff --git a/noir/noir-repo/tooling/nargo/Cargo.toml b/noir/noir-repo/tooling/nargo/Cargo.toml index c5d4bbc9788..1dbb9978b0b 100644 --- a/noir/noir-repo/tooling/nargo/Cargo.toml +++ b/noir/noir-repo/tooling/nargo/Cargo.toml @@ -27,15 +27,13 @@ rayon.workspace = true jsonrpc.workspace = true rand.workspace = true serde.workspace = true +walkdir = "2.5.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] noir_fuzzer.workspace = true proptest.workspace = true [dev-dependencies] -# TODO: This dependency is used to generate unit tests for `get_all_paths_in_dir` -# TODO: once that method is moved to nargo_cli, we can move this dependency to nargo_cli -tempfile.workspace = true jsonrpc-http-server = "18.0" jsonrpc-core-client = "18.0" jsonrpc-derive = "18.0" diff --git a/noir/noir-repo/tooling/nargo/src/lib.rs b/noir/noir-repo/tooling/nargo/src/lib.rs index 0118e83d2a3..88f07e0c292 100644 --- a/noir/noir-repo/tooling/nargo/src/lib.rs +++ b/noir/noir-repo/tooling/nargo/src/lib.rs @@ -13,7 +13,10 @@ pub mod ops; pub mod package; pub mod workspace; -use std::collections::{BTreeMap, HashMap}; +use std::{ + collections::{BTreeMap, HashMap, HashSet}, + path::PathBuf, +}; use fm::{FileManager, FILE_EXTENSION}; use noirc_driver::{add_dep, prepare_crate, prepare_dependency}; @@ -23,6 +26,7 @@ use noirc_frontend::{ }; use package::{Dependency, Package}; use rayon::prelude::*; +use walkdir::WalkDir; pub use self::errors::NargoError; @@ -58,8 +62,14 @@ pub fn insert_all_files_for_workspace_into_file_manager_with_overrides( file_manager: &mut FileManager, overrides: &HashMap<&std::path::Path, &str>, ) { + let mut processed_entry_paths = HashSet::new(); for package in workspace.clone().into_iter() { - insert_all_files_for_package_into_file_manager(package, file_manager, overrides); + insert_all_files_for_package_into_file_manager( + package, + file_manager, + overrides, + &mut processed_entry_paths, + ); } } // We will pre-populate the file manager with all the files in the package @@ -72,27 +82,55 @@ fn insert_all_files_for_package_into_file_manager( package: &Package, file_manager: &mut FileManager, overrides: &HashMap<&std::path::Path, &str>, + processed_entry_paths: &mut HashSet, ) { + if processed_entry_paths.contains(&package.entry_path) { + return; + } + processed_entry_paths.insert(package.entry_path.clone()); + // Start off at the entry path and read all files in the parent directory. let entry_path_parent = package .entry_path .parent() .unwrap_or_else(|| panic!("The entry path is expected to be a single file within a directory and so should have a parent {:?}", package.entry_path)); - // Get all files in the package and add them to the file manager - let paths = get_all_noir_source_in_dir(entry_path_parent) - .expect("could not get all paths in the package"); - for path in paths { + for entry in WalkDir::new(entry_path_parent) { + let Ok(entry) = entry else { + continue; + }; + + if !entry.file_type().is_file() { + continue; + } + + if !entry.path().extension().map_or(false, |ext| ext == FILE_EXTENSION) { + continue; + }; + + let path = entry.into_path(); + + // Avoid reading the source if the file is already there + if file_manager.has_file(&path) { + continue; + } + let source = if let Some(src) = overrides.get(path.as_path()) { src.to_string() } else { std::fs::read_to_string(path.as_path()) .unwrap_or_else(|_| panic!("could not read file {:?} into string", path)) }; + file_manager.add_file_with_source(path.as_path(), source); } - insert_all_files_for_packages_dependencies_into_file_manager(package, file_manager); + insert_all_files_for_packages_dependencies_into_file_manager( + package, + file_manager, + overrides, + processed_entry_paths, + ); } // Inserts all files for the dependencies of the package into the file manager @@ -100,6 +138,8 @@ fn insert_all_files_for_package_into_file_manager( fn insert_all_files_for_packages_dependencies_into_file_manager( package: &Package, file_manager: &mut FileManager, + overrides: &HashMap<&std::path::Path, &str>, + processed_entry_paths: &mut HashSet, ) { for (_, dep) in package.dependencies.iter() { match dep { @@ -107,9 +147,9 @@ fn insert_all_files_for_packages_dependencies_into_file_manager( insert_all_files_for_package_into_file_manager( package, file_manager, - &HashMap::new(), + overrides, + processed_entry_paths, ); - insert_all_files_for_packages_dependencies_into_file_manager(package, file_manager); } } } @@ -143,84 +183,3 @@ pub fn prepare_package<'file_manager, 'parsed_files>( (context, crate_id) } - -// Get all Noir source files in the directory and subdirectories. -// -// Panics: If the path is not a path to a directory. -fn get_all_noir_source_in_dir(dir: &std::path::Path) -> std::io::Result> { - get_all_paths_in_dir(dir, |path| { - path.extension().map_or(false, |extension| extension == FILE_EXTENSION) - }) -} - -// Get all paths in the directory and subdirectories. -// -// Panics: If the path is not a path to a directory. -// -// TODO: Along with prepare_package, this function is an abstraction leak -// TODO: given that this crate should not know about the file manager. -// TODO: We can clean this up in a future refactor -fn get_all_paths_in_dir( - dir: &std::path::Path, - predicate: fn(&std::path::Path) -> bool, -) -> std::io::Result> { - assert!(dir.is_dir(), "directory {dir:?} is not a path to a directory"); - - let mut paths = Vec::new(); - - if dir.is_dir() { - for entry in std::fs::read_dir(dir)? { - let entry = entry?; - let path = entry.path(); - if path.is_dir() { - let mut sub_paths = get_all_paths_in_dir(&path, predicate)?; - paths.append(&mut sub_paths); - } else if predicate(&path) { - paths.push(path); - } - } - } - - Ok(paths) -} - -#[cfg(test)] -mod tests { - use crate::get_all_paths_in_dir; - use std::{ - fs::{self, File}, - path::Path, - }; - use tempfile::tempdir; - - fn create_test_dir_structure(temp_dir: &Path) -> std::io::Result<()> { - fs::create_dir(temp_dir.join("sub_dir1"))?; - File::create(temp_dir.join("sub_dir1/file1.txt"))?; - fs::create_dir(temp_dir.join("sub_dir2"))?; - File::create(temp_dir.join("sub_dir2/file2.txt"))?; - File::create(temp_dir.join("file3.txt"))?; - Ok(()) - } - - #[test] - fn test_get_all_paths_in_dir() { - let temp_dir = tempdir().expect("could not create a temporary directory"); - create_test_dir_structure(temp_dir.path()) - .expect("could not create test directory structure"); - - let paths = get_all_paths_in_dir(temp_dir.path(), |_| true) - .expect("could not get all paths in the test directory"); - - // This should be the paths to all of the files in the directory and the subdirectory - let expected_paths = vec![ - temp_dir.path().join("file3.txt"), - temp_dir.path().join("sub_dir1/file1.txt"), - temp_dir.path().join("sub_dir2/file2.txt"), - ]; - - assert_eq!(paths.len(), expected_paths.len()); - for path in expected_paths { - assert!(paths.contains(&path)); - } - } -} diff --git a/noir/noir-repo/tooling/nargo_cli/Cargo.toml b/noir/noir-repo/tooling/nargo_cli/Cargo.toml index acd5623871e..d72556ab936 100644 --- a/noir/noir-repo/tooling/nargo_cli/Cargo.toml +++ b/noir/noir-repo/tooling/nargo_cli/Cargo.toml @@ -71,6 +71,7 @@ tracing-appender = "0.2.3" tokio-util = { version = "0.7.8", features = ["compat"] } [dev-dependencies] +ark-bn254.workspace = true tempfile.workspace = true dirs.workspace = true assert_cmd = "2.0.8" @@ -85,6 +86,7 @@ sha2.workspace = true sha3.workspace = true iai = "0.1.1" test-binary = "3.0.2" +light-poseidon = "0.2.0" [[bench]] diff --git a/noir/noir-repo/tooling/nargo_cli/benches/README.md b/noir/noir-repo/tooling/nargo_cli/benches/README.md new file mode 100644 index 00000000000..a579b82bbb5 --- /dev/null +++ b/noir/noir-repo/tooling/nargo_cli/benches/README.md @@ -0,0 +1,13 @@ +# Benchmarks + +To generate flamegraphs for the execution of a specific program, execute the following commands: + +```shell +./scripts/benchmark_start.sh +cargo bench -p nargo_cli --bench criterion -- --profile-time=30 +./scripts/benchmark_stop.sh +``` + +Afterwards the flamegraph is available at `target/criterion/_execute/profile/flamegraph.svg` + +Alternatively, omit `` to run profiling on all test programs defined in [utils.rs](./utils.rs). \ No newline at end of file diff --git a/noir/noir-repo/tooling/nargo_cli/benches/criterion.rs b/noir/noir-repo/tooling/nargo_cli/benches/criterion.rs index effab7d7c27..949f7add393 100644 --- a/noir/noir-repo/tooling/nargo_cli/benches/criterion.rs +++ b/noir/noir-repo/tooling/nargo_cli/benches/criterion.rs @@ -1,33 +1,154 @@ //! Select representative tests to bench with criterion +use acvm::{acir::native_types::WitnessMap, FieldElement}; use assert_cmd::prelude::{CommandCargoExt, OutputAssertExt}; use criterion::{criterion_group, criterion_main, Criterion}; -use paste::paste; +use noirc_abi::{ + input_parser::{Format, InputValue}, + Abi, InputMap, +}; +use noirc_artifacts::program::ProgramArtifact; +use noirc_driver::CompiledProgram; use pprof::criterion::{Output, PProfProfiler}; +use std::hint::black_box; +use std::path::Path; +use std::{cell::RefCell, collections::BTreeMap}; use std::{process::Command, time::Duration}; + include!("./utils.rs"); -macro_rules! criterion_command { - ($command_name:tt, $command_string:expr) => { - paste! { - fn [](c: &mut Criterion) { - let test_program_dirs = get_selected_tests(); - for test_program_dir in test_program_dirs { - let mut cmd = Command::cargo_bin("nargo").unwrap(); - cmd.arg("--program-dir").arg(&test_program_dir); - cmd.arg($command_string); - cmd.arg("--force"); - - let benchmark_name = format!("{}_{}", test_program_dir.file_name().unwrap().to_str().unwrap(), $command_string); - c.bench_function(&benchmark_name, |b| { - b.iter(|| cmd.assert().success()) - }); - } - } +/// Compile the test program in a sub-process +fn compile_program(test_program_dir: &Path) { + let mut cmd = Command::cargo_bin("nargo").unwrap(); + cmd.arg("--program-dir").arg(test_program_dir); + cmd.arg("compile"); + cmd.arg("--force"); + cmd.assert().success(); +} + +/// Read the bytecode(s) of the program(s) from the compilation artifacts +/// from all the binary packages. Pair them up with their respective input. +/// +/// Based on `ExecuteCommand::run`. +fn read_compiled_programs_and_inputs( + dir: &Path, +) -> Vec<(CompiledProgram, WitnessMap)> { + let toml_path = nargo_toml::get_package_manifest(dir).expect("failed to read manifest"); + let workspace = nargo_toml::resolve_workspace_from_toml( + &toml_path, + nargo_toml::PackageSelection::All, + Some(noirc_driver::NOIR_ARTIFACT_VERSION_STRING.to_string()), + ) + .expect("failed to resolve workspace"); + + let mut programs = Vec::new(); + let binary_packages = workspace.into_iter().filter(|package| package.is_binary()); + + for package in binary_packages { + let program_artifact_path = workspace.package_build_path(package); + let program: CompiledProgram = read_program_from_file(&program_artifact_path).into(); + + let (inputs, _) = read_inputs_from_file( + &package.root_dir, + nargo::constants::PROVER_INPUT_FILE, + Format::Toml, + &program.abi, + ); + + let initial_witness = + program.abi.encode(&inputs, None).expect("failed to encode input witness"); + + programs.push((program, initial_witness)); + } + programs +} + +/// Read the bytecode and ABI from the compilation output +fn read_program_from_file(circuit_path: &Path) -> ProgramArtifact { + let file_path = circuit_path.with_extension("json"); + let input_string = std::fs::read(file_path).expect("failed to read artifact file"); + serde_json::from_slice(&input_string).expect("failed to deserialize artifact") +} + +/// Read the inputs from Prover.toml +fn read_inputs_from_file( + path: &Path, + file_name: &str, + format: Format, + abi: &Abi, +) -> (InputMap, Option) { + if abi.is_empty() { + return (BTreeMap::new(), None); + } + + let file_path = path.join(file_name).with_extension(format.ext()); + if !file_path.exists() { + if abi.parameters.is_empty() { + return (BTreeMap::new(), None); + } else { + panic!("input file doesn't exist: {}", file_path.display()); } - }; + } + + let input_string = std::fs::read_to_string(file_path).expect("failed to read input file"); + let mut input_map = format.parse(&input_string, abi).expect("failed to parse input"); + let return_value = input_map.remove(noirc_abi::MAIN_RETURN_NAME); + + (input_map, return_value) +} + +/// Use the nargo CLI to compile a test program, then benchmark its execution +/// by executing the command directly from the benchmark, so that we can have +/// meaningful flamegraphs about the ACVM. +fn criterion_selected_tests_execution(c: &mut Criterion) { + for test_program_dir in get_selected_tests() { + let benchmark_name = + format!("{}_execute", test_program_dir.file_name().unwrap().to_str().unwrap()); + + // The program and its inputs will be populated in the first setup. + let artifacts = RefCell::new(None); + + let mut foreign_call_executor = + nargo::ops::DefaultForeignCallExecutor::new(false, None, None, None); + + c.bench_function(&benchmark_name, |b| { + b.iter_batched( + || { + // Setup will be called many times to set a batch (which we don't use), + // but we can compile it only once, and then the executions will not have to do so. + // It is done as a setup so that we only compile the test programs that we filter for. + if artifacts.borrow().is_some() { + return; + } + compile_program(&test_program_dir); + // Parse the artifacts for use in the benchmark routine + let programs = read_compiled_programs_and_inputs(&test_program_dir); + // Warn, but don't stop, if we haven't found any binary packages. + if programs.is_empty() { + eprintln!("\nWARNING: There is nothing to benchmark in {benchmark_name}"); + } + // Store them for execution + artifacts.replace(Some(programs)); + }, + |_| { + let artifacts = artifacts.borrow(); + let artifacts = artifacts.as_ref().expect("setup compiled them"); + + for (program, initial_witness) in artifacts { + let _witness_stack = black_box(nargo::ops::execute_program( + black_box(&program.program), + black_box(initial_witness.clone()), + &bn254_blackbox_solver::Bn254BlackBoxSolver, + &mut foreign_call_executor, + )) + .expect("failed to execute program"); + } + }, + criterion::BatchSize::SmallInput, + ); + }); + } } -criterion_command!(execution, "execute"); criterion_group! { name = execution_benches; diff --git a/noir/noir-repo/tooling/nargo_cli/benches/utils.rs b/noir/noir-repo/tooling/nargo_cli/benches/utils.rs index 47968f7e898..37c94241b08 100644 --- a/noir/noir-repo/tooling/nargo_cli/benches/utils.rs +++ b/noir/noir-repo/tooling/nargo_cli/benches/utils.rs @@ -6,6 +6,7 @@ fn get_selected_tests() -> Vec { Ok(dir) => PathBuf::from(dir), Err(_) => std::env::current_dir().unwrap(), }; + let test_dir = manifest_dir .parent() .unwrap() @@ -15,5 +16,11 @@ fn get_selected_tests() -> Vec { .join("execution_success"); let selected_tests = vec!["struct", "eddsa", "regression"]; - selected_tests.into_iter().map(|t| test_dir.join(t)).collect() + let mut selected_tests = + selected_tests.into_iter().map(|t| test_dir.join(t)).collect::>(); + + let test_dir = test_dir.parent().unwrap().join("benchmarks"); + selected_tests.extend(test_dir.read_dir().unwrap().filter_map(|e| e.ok()).map(|e| e.path())); + + selected_tests } diff --git a/noir/noir-repo/tooling/nargo_cli/build.rs b/noir/noir-repo/tooling/nargo_cli/build.rs index 9f694080cf5..94f74a06149 100644 --- a/noir/noir-repo/tooling/nargo_cli/build.rs +++ b/noir/noir-repo/tooling/nargo_cli/build.rs @@ -59,6 +59,15 @@ const IGNORED_BRILLIG_TESTS: [&str; 11] = [ "is_unconstrained", ]; +/// Some tests are expected to have warnings +/// These should be fixed and removed from this list. +const TESTS_WITH_EXPECTED_WARNINGS: [&str; 2] = [ + // TODO(https://github.com/noir-lang/noir/issues/6238): remove from list once issue is closed + "brillig_cast", + // TODO(https://github.com/noir-lang/noir/issues/6238): remove from list once issue is closed + "macros_in_comptime", +]; + fn read_test_cases( test_data_dir: &Path, test_sub_dir: &str, @@ -234,13 +243,21 @@ fn generate_compile_success_empty_tests(test_file: &mut File, test_data_dir: &Pa for (test_name, test_dir) in test_cases { let test_dir = test_dir.display(); - let assert_zero_opcodes = r#" + let mut assert_zero_opcodes = r#" let output = nargo.output().expect("Failed to execute command"); if !output.status.success() {{ panic!("`nargo info` failed with: {}", String::from_utf8(output.stderr).unwrap_or_default()); }} - + "#.to_string(); + + if !TESTS_WITH_EXPECTED_WARNINGS.contains(&test_name.as_str()) { + assert_zero_opcodes += r#" + nargo.assert().success().stderr(predicate::str::contains("warning:").not()); + "#; + } + + assert_zero_opcodes += r#" // `compile_success_empty` tests should be able to compile down to an empty circuit. let json: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap_or_else(|e| {{ panic!("JSON was not well-formatted {:?}\n\n{:?}", e, std::str::from_utf8(&output.stdout)) @@ -284,7 +301,7 @@ fn generate_compile_success_contract_tests(test_file: &mut File, test_data_dir: &test_dir, r#" nargo.arg("compile").arg("--force"); - nargo.assert().success();"#, + nargo.assert().success().stderr(predicate::str::contains("warning:").not());"#, ); } writeln!(test_file, "}}").unwrap(); diff --git a/noir/noir-repo/tooling/nargo_cli/tests/stdlib-props.proptest-regressions b/noir/noir-repo/tooling/nargo_cli/tests/stdlib-props.proptest-regressions index ab88db8b6c2..d41d504e530 100644 --- a/noir/noir-repo/tooling/nargo_cli/tests/stdlib-props.proptest-regressions +++ b/noir/noir-repo/tooling/nargo_cli/tests/stdlib-props.proptest-regressions @@ -5,3 +5,4 @@ # It is recommended to check this file in to source control so that # everyone who runs the test benefits from these saved cases. cc 88db0227d5547742f771c14b1679f6783570b46bf7cf9e6897ee1aca4bd5034d # shrinks to io = SnippetInputOutput { description: "force_brillig = false, max_len = 200", inputs: {"input": Vec([Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(1), Field(5), Field(20), Field(133), Field(233), Field(99), Field(2⁶), Field(196), Field(232), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0)]), "message_size": Field(2⁶)}, expected_output: Vec([Field(102), Field(26), Field(94), Field(212), Field(102), Field(1), Field(215), Field(217), Field(167), Field(175), Field(158), Field(18), Field(20), Field(244), Field(158), Field(200), Field(2⁷), Field(186), Field(251), Field(243), Field(20), Field(207), Field(22), Field(3), Field(139), Field(81), Field(207), Field(2⁴), Field(50), Field(167), Field(1), Field(163)]) } +cc 0f334fe0c29748e8d0964d63f0d1f3a4eee536afa665eabc838045d8e1c67792 # shrinks to io = SnippetInputOutput { description: "force_brillig = true, max_len = 135", inputs: {"input": Vec([Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(29), Field(131), Field(217), Field(115), Field(221), Field(92), Field(23), Field(14), Field(58), Field(90), Field(232), Field(155), Field(59), Field(209), Field(2⁴×15), Field(137), Field(214), Field(129), Field(11), Field(140), Field(99), Field(131), Field(188), Field(159), Field(27), Field(206), Field(89), Field(137), Field(248), Field(30), Field(149), Field(194), Field(121), Field(127), Field(245), Field(202), Field(155), Field(203), Field(122), Field(2⁵), Field(209), Field(194), Field(214), Field(11), Field(82), Field(26), Field(244), Field(34), Field(30), Field(125), Field(83), Field(2⁴×13), Field(30), Field(2⁴×10), Field(85), Field(245), Field(220), Field(211), Field(190), Field(46), Field(159), Field(87), Field(74), Field(51), Field(42), Field(202), Field(230), Field(137), Field(127), Field(29), Field(126), Field(243), Field(106), Field(156), Field(2⁴×6), Field(154), Field(70), Field(100), Field(130)]), "message_size": Field(135)}, expected_output: Vec([Field(149), Field(114), Field(68), Field(219), Field(215), Field(147), Field(139), Field(34), Field(145), Field(204), Field(248), Field(145), Field(21), Field(119), Field(2⁵), Field(125), Field(181), Field(142), Field(106), Field(169), Field(202), Field(111), Field(110), Field(6), Field(210), Field(250), Field(2⁴), Field(110), Field(209), Field(2), Field(33), Field(104)]) } diff --git a/noir/noir-repo/tooling/nargo_cli/tests/stdlib-props.rs b/noir/noir-repo/tooling/nargo_cli/tests/stdlib-props.rs index d1ea5bbfaf6..9e1a2823e70 100644 --- a/noir/noir-repo/tooling/nargo_cli/tests/stdlib-props.rs +++ b/noir/noir-repo/tooling/nargo_cli/tests/stdlib-props.rs @@ -1,6 +1,7 @@ use std::{cell::RefCell, collections::BTreeMap, path::Path}; -use acvm::{acir::native_types::WitnessStack, FieldElement}; +use acvm::{acir::native_types::WitnessStack, AcirField, FieldElement}; +use iter_extended::vecmap; use nargo::{ ops::{execute_program, DefaultForeignCallExecutor}, parse_all, @@ -254,8 +255,91 @@ fn fuzz_sha512_equivalence() { ); } +#[test] +fn fuzz_poseidon2_equivalence() { + use bn254_blackbox_solver::poseidon_hash; + + // Test empty, small, then around the RATE value, then bigger inputs. + for max_len in [0, 1, 3, 4, 100] { + let source = format!( + "fn main(input: [Field; {max_len}], message_size: u32) -> pub Field {{ + std::hash::poseidon2::Poseidon2::hash(input, message_size) + }}" + ); + + let strategy = (0..=max_len) + .prop_flat_map(field_vec_strategy) + .prop_map(move |mut msg| { + let output = poseidon_hash(&msg, msg.len() < max_len).expect("failed to hash"); + + // The input has to be padded to the maximum length. + let msg_size = msg.len(); + msg.resize(max_len, FieldElement::from(0u64)); + + let inputs = vec![ + ("input", InputValue::Vec(vecmap(msg, InputValue::Field))), + ("message_size", InputValue::Field(FieldElement::from(msg_size))), + ]; + + SnippetInputOutput::new(inputs, InputValue::Field(output)) + .with_description(format!("max_len = {max_len}")) + }) + .boxed(); + + run_snippet_proptest(source.clone(), false, strategy); + } +} + +#[test] +fn fuzz_poseidon_equivalence() { + use light_poseidon::{Poseidon, PoseidonHasher}; + + let poseidon_hash = |inputs: &[FieldElement]| { + let mut poseidon = Poseidon::::new_circom(inputs.len()).unwrap(); + let frs: Vec = inputs.iter().map(|f| f.into_repr()).collect::>(); + let hash = poseidon.hash(&frs).expect("failed to hash"); + FieldElement::from_repr(hash) + }; + + // Noir has hashes up to length 16, but the reference library won't work with more than 12. + for len in 1..light_poseidon::MAX_X5_LEN { + let source = format!( + "fn main(input: [Field; {len}]) -> pub Field {{ + let h1 = std::hash::poseidon::bn254::hash_{len}(input); + let h2 = {{ + let mut hasher = std::hash::poseidon::PoseidonHasher::default(); + input.hash(&mut hasher); + hasher.finish() + }}; + assert_eq(h1, h2); + h1 + }}" + ); + + let strategy = field_vec_strategy(len) + .prop_map(move |msg| { + let output = poseidon_hash(&msg); + let inputs = vec![("input", InputValue::Vec(vecmap(msg, InputValue::Field)))]; + + SnippetInputOutput::new(inputs, InputValue::Field(output)) + .with_description(format!("len = {len}")) + }) + .boxed(); + + run_snippet_proptest(source.clone(), false, strategy); + } +} + fn bytes_input(bytes: &[u8]) -> InputValue { InputValue::Vec( bytes.iter().map(|b| InputValue::Field(FieldElement::from(*b as u32))).collect(), ) } + +fn field_vec_strategy(len: usize) -> impl Strategy> { + // Generate Field elements from random 32 byte vectors. + let field = prop::collection::vec(any::(), 32) + .prop_map(|bytes| FieldElement::from_be_bytes_reduce(&bytes)); + + prop::collection::vec(field, len) +} diff --git a/noir/noir-repo/tooling/nargo_fmt/src/visitor/item.rs b/noir/noir-repo/tooling/nargo_fmt/src/visitor/item.rs index b71ea801752..4724c8d957d 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/visitor/item.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/visitor/item.rs @@ -6,8 +6,10 @@ use crate::{ }, visitor::expr::{format_seq, NewlineMode}, }; -use noirc_frontend::ast::{ - ItemVisibility, NoirFunction, TraitImplItemKind, UnresolvedTypeData, Visibility, +use noirc_frontend::{ + ast::{ItemVisibility, NoirFunction, TraitImplItemKind, UnresolvedTypeData, Visibility}, + lexer::Lexer, + token::{SecondaryAttribute, TokenKind}, }; use noirc_frontend::{ hir::resolution::errors::Span, @@ -26,7 +28,8 @@ impl super::FmtVisitor<'_> { let name_span = func.name_ident().span(); let func_span = func.span(); - let mut result = self.slice(start..name_span.end()).to_owned(); + let fn_header = self.slice(start..name_span.end()); + let mut result = self.format_fn_header(fn_header, &func); let params_open = self.span_before(name_span.end()..func_span.start(), Token::LeftParen).start(); @@ -108,6 +111,134 @@ impl super::FmtVisitor<'_> { (result.trim_end().to_string(), last_line_contains_single_line_comment(maybe_comment)) } + // This formats the function outer doc comments, attributes, modifiers, and `fn name`. + fn format_fn_header(&self, src: &str, func: &NoirFunction) -> String { + let mut result = String::new(); + let mut lexer = Lexer::new(src).skip_comments(false).peekable(); + + // First there might be outer doc comments + while let Some(Ok(token)) = lexer.peek() { + if token.kind() == TokenKind::OuterDocComment { + result.push_str(&token.to_string()); + result.push('\n'); + result.push_str(&self.indent.to_string()); + lexer.next(); + + self.append_comments_if_any(&mut lexer, &mut result); + } else { + break; + } + } + + // Then, optionally, attributes + while let Some(Ok(token)) = lexer.peek() { + if token.kind() == TokenKind::Attribute { + result.push_str(&token.to_string()); + result.push('\n'); + result.push_str(&self.indent.to_string()); + lexer.next(); + + self.append_comments_if_any(&mut lexer, &mut result); + } else { + break; + } + } + + self.append_comments_if_any(&mut lexer, &mut result); + + // Then, optionally, the `unconstrained` keyword + // (eventually we'll stop accepting this, but we keep it for backwards compatibility) + if let Some(Ok(token)) = lexer.peek() { + if let Token::Keyword(Keyword::Unconstrained) = token.token() { + lexer.next(); + } + } + + self.append_comments_if_any(&mut lexer, &mut result); + + // Then the visibility + let mut has_visibility = false; + if let Some(Ok(token)) = lexer.peek() { + if let Token::Keyword(Keyword::Pub) = token.token() { + has_visibility = true; + lexer.next(); + if let Some(Ok(token)) = lexer.peek() { + if let Token::LeftParen = token.token() { + lexer.next(); // Skip '(' + lexer.next(); // Skip 'crate' + lexer.next(); // Skip ')' + } + } + } + } + + if has_visibility { + result.push_str(&func.def.visibility.to_string()); + result.push(' '); + } + + self.append_comments_if_any(&mut lexer, &mut result); + + // Then, optionally, and again, the `unconstrained` keyword + if let Some(Ok(token)) = lexer.peek() { + if let Token::Keyword(Keyword::Unconstrained) = token.token() { + lexer.next(); + } + } + + if func.def.is_unconstrained { + result.push_str("unconstrained "); + } + + self.append_comments_if_any(&mut lexer, &mut result); + + // Then, optionally, the `comptime` keyword + if let Some(Ok(token)) = lexer.peek() { + if let Token::Keyword(Keyword::Comptime) = token.token() { + lexer.next(); + } + } + + if func.def.is_comptime { + result.push_str("comptime "); + } + + self.append_comments_if_any(&mut lexer, &mut result); + + // Then the `fn` keyword + lexer.next(); // Skip fn + result.push_str("fn "); + + self.append_comments_if_any(&mut lexer, &mut result); + + // Then the function name + result.push_str(&func.def.name.0.contents); + + result + } + + fn append_comments_if_any( + &self, + lexer: &mut std::iter::Peekable>, + result: &mut String, + ) { + while let Some(Ok(token)) = lexer.peek() { + match token.token() { + Token::LineComment(..) => { + result.push_str(&token.to_string()); + result.push('\n'); + result.push_str(&self.indent.to_string()); + lexer.next(); + } + Token::BlockComment(..) => { + result.push_str(&token.to_string()); + lexer.next(); + } + _ => break, + } + } + } + fn format_return_type( &self, span: Span, @@ -170,7 +301,9 @@ impl super::FmtVisitor<'_> { } for attribute in module.outer_attributes { - self.push_str(&format!("#[{}]\n", attribute.as_ref())); + let is_tag = matches!(attribute, SecondaryAttribute::Tag(_)); + let tag = if is_tag { "'" } else { "" }; + self.push_str(&format!("#[{tag}{}]\n", attribute.as_ref())); self.push_str(&self.indent.to_string()); } diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/expected/fn.nr b/noir/noir-repo/tooling/nargo_fmt/tests/expected/fn.nr index 73248d0c04f..19f022751c4 100644 --- a/noir/noir-repo/tooling/nargo_fmt/tests/expected/fn.nr +++ b/noir/noir-repo/tooling/nargo_fmt/tests/expected/fn.nr @@ -71,3 +71,21 @@ fn whitespace_before_generics(foo: T) {} fn more_whitespace_before_generics(foo: T) {} fn with_unconstrained(x: unconstrained fn() -> ()) {} + +pub(crate) fn one() {} + +pub unconstrained fn two() {} + +pub unconstrained comptime fn two() {} + +/// Documented +#[test] +fn three() {} + +#[test(should_fail)] +// comment +fn four() {} + +#[test(should_fail_with = "oops")] +fn five() {} + diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/expected/struct.nr b/noir/noir-repo/tooling/nargo_fmt/tests/expected/struct.nr index 8fc642f7cd5..f3651de607d 100644 --- a/noir/noir-repo/tooling/nargo_fmt/tests/expected/struct.nr +++ b/noir/noir-repo/tooling/nargo_fmt/tests/expected/struct.nr @@ -5,7 +5,7 @@ struct Foo { struct Pair { first: Foo, - second: Field, + pub second: Field, } impl Foo { diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/expected/trait.nr b/noir/noir-repo/tooling/nargo_fmt/tests/expected/trait.nr new file mode 100644 index 00000000000..0467585fac3 --- /dev/null +++ b/noir/noir-repo/tooling/nargo_fmt/tests/expected/trait.nr @@ -0,0 +1,2 @@ +trait Foo: Bar + Baz {} + diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/input/fn.nr b/noir/noir-repo/tooling/nargo_fmt/tests/input/fn.nr index 8db6022808f..9e19222decb 100644 --- a/noir/noir-repo/tooling/nargo_fmt/tests/input/fn.nr +++ b/noir/noir-repo/tooling/nargo_fmt/tests/input/fn.nr @@ -47,12 +47,29 @@ pub fn from_baz(x: [Field; crate::foo::MAGIC_NUMBER]) {} fn id< T , let I : Field > ( x : [ Field ; I ] ) -> [Field; I ] { } -fn id_two(x: [Field ; I]) -> [ Field; I] {} fn whitespace_before_generics < T > (foo: T) {} -fn more_whitespace_before_generics < +fn more_whitespace_before_generics < T > (foo: T) {} fn with_unconstrained(x: unconstrained fn() -> ()) {} + +pub ( crate ) fn one() {} + +unconstrained pub fn two() {} + +unconstrained pub comptime fn two() {} + +/// Documented +#[test] fn three() {} + +#[test(should_fail)] +// comment +fn four() {} + +#[test(should_fail_with = "oops")] +fn five() {} + diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/input/struct.nr b/noir/noir-repo/tooling/nargo_fmt/tests/input/struct.nr index 5e3530e8364..2e5fff47f9e 100644 --- a/noir/noir-repo/tooling/nargo_fmt/tests/input/struct.nr +++ b/noir/noir-repo/tooling/nargo_fmt/tests/input/struct.nr @@ -5,7 +5,7 @@ struct Foo { struct Pair { first: Foo, - second: Field, + pub second: Field, } impl Foo { diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/input/trait.nr b/noir/noir-repo/tooling/nargo_fmt/tests/input/trait.nr new file mode 100644 index 00000000000..0467585fac3 --- /dev/null +++ b/noir/noir-repo/tooling/nargo_fmt/tests/input/trait.nr @@ -0,0 +1,2 @@ +trait Foo: Bar + Baz {} +