diff --git a/.noir-sync-commit b/.noir-sync-commit index 3f0f925a890..d3b7e1db860 100644 --- a/.noir-sync-commit +++ b/.noir-sync-commit @@ -1 +1 @@ -be9dcfe56d808b1bd5ef552d41274705b2df7062 +ae87d287ab1fae0f999dfd0d1166fbddb927ba97 diff --git a/noir/noir-repo/.release-please-manifest.json b/noir/noir-repo/.release-please-manifest.json index 8be8ad41ac0..cbf7bdda17c 100644 --- a/noir/noir-repo/.release-please-manifest.json +++ b/noir/noir-repo/.release-please-manifest.json @@ -1,4 +1,4 @@ { - ".": "0.34.0", - "acvm-repo": "0.50.0" + ".": "0.35.0", + "acvm-repo": "0.51.0" } diff --git a/noir/noir-repo/CHANGELOG.md b/noir/noir-repo/CHANGELOG.md index 9779d7ab964..6aa552ce90e 100644 --- a/noir/noir-repo/CHANGELOG.md +++ b/noir/noir-repo/CHANGELOG.md @@ -1,5 +1,117 @@ # Changelog +## [0.35.0](https://github.com/noir-lang/noir/compare/v0.34.0...v0.35.0) (2024-10-03) + + +### ⚠ BREAKING CHANGES + +* Syncing TypeVariableKind with Kind ([#6094](https://github.com/noir-lang/noir/issues/6094)) +* remove sha256 opcode (https://github.com/AztecProtocol/aztec-packages/pull/4571) +* add support for u1 in the avm, ToRadix's radix arg is a memory addr (https://github.com/AztecProtocol/aztec-packages/pull/8570) +* Infer globals to be u32 when used in a type ([#6083](https://github.com/noir-lang/noir/issues/6083)) +* removing implicit numeric generics ([#5837](https://github.com/noir-lang/noir/issues/5837)) + +### Features + +* (LSP) if in runtime code, always suggest functions that return Quoted as macro calls ([#6098](https://github.com/noir-lang/noir/issues/6098)) ([4a160cb](https://github.com/noir-lang/noir/commit/4a160cb99cbd9928c034a7009f398974fc6fdb11)) +* (LSP) remove unused imports ([#6129](https://github.com/noir-lang/noir/issues/6129)) ([98bc460](https://github.com/noir-lang/noir/commit/98bc46002cdd8daff1baf0756ecc60dbdf420fd9)) +* (LSP) show global value on hover ([#6097](https://github.com/noir-lang/noir/issues/6097)) ([3d9d072](https://github.com/noir-lang/noir/commit/3d9d07210544c9d27051eb5e629585760f48cd1c)) +* (LSP) suggest $vars inside `quote { ... }` ([#6114](https://github.com/noir-lang/noir/issues/6114)) ([73245b3](https://github.com/noir-lang/noir/commit/73245b3aae0c65780a102ac6842f06df65e5fc35)) +* Add `Expr::as_constructor` ([#5980](https://github.com/noir-lang/noir/issues/5980)) ([76dea7b](https://github.com/noir-lang/noir/commit/76dea7b409baa98236f6433f17c2ce9206dd4ba3)) +* Add `Expr::as_for` and `Expr::as_for_range` ([#6039](https://github.com/noir-lang/noir/issues/6039)) ([abcae75](https://github.com/noir-lang/noir/commit/abcae750f022dd7c49cee616edddd7b1cc93f3b8)) +* Add `Expr::as_lambda` ([#6048](https://github.com/noir-lang/noir/issues/6048)) ([31130dc](https://github.com/noir-lang/noir/commit/31130dc7aec24a7a7b9f342df94b14f295eb2103)) +* Add a `comptime` string type for string handling at compile-time ([#6026](https://github.com/noir-lang/noir/issues/6026)) ([5d2984f](https://github.com/noir-lang/noir/commit/5d2984fce4f55e43cb418e40462d430227b71768)) +* Add support for u1 in the avm, ToRadix's radix arg is a memory addr (https://github.com/AztecProtocol/aztec-packages/pull/8570) ([e8bbce7](https://github.com/noir-lang/noir/commit/e8bbce71fde3fc7af410c30920c2a547389d8248)) +* Allow silencing an unused variable defined via `let` ([#6149](https://github.com/noir-lang/noir/issues/6149)) ([a2bc059](https://github.com/noir-lang/noir/commit/a2bc059f993d3e9ca06a2fe4857ef1c522c97286)) +* Allow visibility modifiers in struct definitions ([#6054](https://github.com/noir-lang/noir/issues/6054)) ([199be58](https://github.com/noir-lang/noir/commit/199be584a36d20660ada49473050c5191251d6c5)) +* Check unconstrained trait impl method matches ([#6057](https://github.com/noir-lang/noir/issues/6057)) ([aedc983](https://github.com/noir-lang/noir/commit/aedc9832240e55473c504fa2e6e3b3af618bda08)) +* Default to outputting witness with file named after package ([#6031](https://github.com/noir-lang/noir/issues/6031)) ([e74b4ae](https://github.com/noir-lang/noir/commit/e74b4ae3ebcf301eedc5d0059bcebd5dced75d72)) +* Detect unconstructed structs ([#6061](https://github.com/noir-lang/noir/issues/6061)) ([bcb438b](https://github.com/noir-lang/noir/commit/bcb438b0816fbe08344535545612d32b4730af79)) +* Do not double error on import with error ([#6131](https://github.com/noir-lang/noir/issues/6131)) ([9b26650](https://github.com/noir-lang/noir/commit/9b26650f4a45c220484fc187500c7307af9c88d7)) +* Expose `derived_generators` and `pedersen_commitment_with_separator` from the stdlib ([#6154](https://github.com/noir-lang/noir/issues/6154)) ([877b806](https://github.com/noir-lang/noir/commit/877b806ee02cb640472c6bb2b1ed7bc76b861a9b)) +* Faster LSP by caching file managers ([#6047](https://github.com/noir-lang/noir/issues/6047)) ([c48a4f8](https://github.com/noir-lang/noir/commit/c48a4f83063ff55574d5b4a6277950a9edbc6317)) +* Hoist constant allocation outside of loops ([#6158](https://github.com/noir-lang/noir/issues/6158)) ([180bfc9](https://github.com/noir-lang/noir/commit/180bfc99944cd42b3f44048213458d1399687cef)) +* Implement `to_be_radix` in the comptime interpreter ([#6043](https://github.com/noir-lang/noir/issues/6043)) ([1550278](https://github.com/noir-lang/noir/commit/1550278f1e96392967b477b9b12be3bb0eea8fd6)) +* Implement solver for mov_registers_to_registers ([#6089](https://github.com/noir-lang/noir/issues/6089)) ([4170c55](https://github.com/noir-lang/noir/commit/4170c55019bd27fd51be8a46637514dfe86de53c)) +* Implement type paths ([#6093](https://github.com/noir-lang/noir/issues/6093)) ([2174ffb](https://github.com/noir-lang/noir/commit/2174ffb92b5d88e7e0926c91f42bc7f849e8ddc1)) +* Let `Module::functions` and `Module::structs` return them in definition order ([#6178](https://github.com/noir-lang/noir/issues/6178)) ([dec9874](https://github.com/noir-lang/noir/commit/dec98747197442f6c2a15e6543c5d453dff4b967)) +* Let LSP suggest macro calls too ([#6090](https://github.com/noir-lang/noir/issues/6090)) ([26d275b](https://github.com/noir-lang/noir/commit/26d275b65fa339d877c90d5c6c13ac8ef47189e1)) +* Let LSP suggest trait impl methods as you are typing them ([#6029](https://github.com/noir-lang/noir/issues/6029)) ([dfed81b](https://github.com/noir-lang/noir/commit/dfed81b4b39b2f783d6e81a78ee27fba7032e01c)) +* LSP autocompletion for `TypePath` ([#6117](https://github.com/noir-lang/noir/issues/6117)) ([3f79d8f](https://github.com/noir-lang/noir/commit/3f79d8f04c5f90c6b21359a3d0960446ebf84b2d)) +* **metaprogramming:** Add `#[use_callers_scope]` ([#6050](https://github.com/noir-lang/noir/issues/6050)) ([8c34046](https://github.com/noir-lang/noir/commit/8c340461c3f7054839009c4b1ed5ac8a0dd55e09)) +* Optimize allocating immediate amounts of memory (https://github.com/AztecProtocol/aztec-packages/pull/8579) ([e8bbce7](https://github.com/noir-lang/noir/commit/e8bbce71fde3fc7af410c30920c2a547389d8248)) +* Optimize constraints in sha256 ([#6145](https://github.com/noir-lang/noir/issues/6145)) ([164d29e](https://github.com/noir-lang/noir/commit/164d29e4d1960d16fdeafe2cc8ea8144a769f7b2)) +* **perf:** Allow array set last uses optimization in return block of Brillig functions ([#6119](https://github.com/noir-lang/noir/issues/6119)) ([5598059](https://github.com/noir-lang/noir/commit/5598059576c6cbc72474aff4b18bc5e4bb9f08e1)) +* **perf:** Handle array set optimization across blocks for Brillig functions ([#6153](https://github.com/noir-lang/noir/issues/6153)) ([12cb80a](https://github.com/noir-lang/noir/commit/12cb80a214fd81eb7619413a6d0663369be38512)) +* **perf:** Optimize array set from get ([#6207](https://github.com/noir-lang/noir/issues/6207)) ([dfeb1c5](https://github.com/noir-lang/noir/commit/dfeb1c51c564ec345978a9a0efef3e4e96ab638a)) +* **perf:** Remove inc_rc instructions for arrays which are never mutably borrowed ([#6168](https://github.com/noir-lang/noir/issues/6168)) ([a195442](https://github.com/noir-lang/noir/commit/a19544247fffaf5d2fe0d6d45013f833576f7c61)) +* **perf:** Remove redundant inc rc without instructions between ([#6183](https://github.com/noir-lang/noir/issues/6183)) ([be9dcfe](https://github.com/noir-lang/noir/commit/be9dcfe56d808b1bd5ef552d41274705b2df7062)) +* **perf:** Remove unused loads in mem2reg and last stores per function ([#5925](https://github.com/noir-lang/noir/issues/5925)) ([19eef30](https://github.com/noir-lang/noir/commit/19eef30cdbd8a3a4671aabbbe66b5481a5dec3f7)) +* **perf:** Remove useless paired RC instructions within a block during DIE ([#6160](https://github.com/noir-lang/noir/issues/6160)) ([59c4118](https://github.com/noir-lang/noir/commit/59c41182faa19d1cb8c9be5c11d50636fc17dad7)) +* **perf:** Simplify the cfg after DIE ([#6184](https://github.com/noir-lang/noir/issues/6184)) ([a1b5046](https://github.com/noir-lang/noir/commit/a1b50466bfd8c44d50440e00ecb50e29425471e5)) +* Pretty print Quoted token stream ([#6111](https://github.com/noir-lang/noir/issues/6111)) ([cd81f85](https://github.com/noir-lang/noir/commit/cd81f85856a477e208533ebd0915b5901c1bb184)) +* Refactor SSA passes to run on individual functions ([#6072](https://github.com/noir-lang/noir/issues/6072)) ([85c502c](https://github.com/noir-lang/noir/commit/85c502c9fa69b151fdff1a97b5a97ad78cb599ab)) +* Remove aztec macros ([#6087](https://github.com/noir-lang/noir/issues/6087)) ([9d96207](https://github.com/noir-lang/noir/commit/9d962077630131840f0cb7c211f462b579b0b577)) +* Remove orphaned blocks from cfg to improve `simplify_cfg` pass. ([#6198](https://github.com/noir-lang/noir/issues/6198)) ([b4712c5](https://github.com/noir-lang/noir/commit/b4712c5ba50ef38789978522afcd251ffbcf8780)) +* Remove sha256 opcode (https://github.com/AztecProtocol/aztec-packages/pull/4571) ([e8bbce7](https://github.com/noir-lang/noir/commit/e8bbce71fde3fc7af410c30920c2a547389d8248)) +* Remove unnecessary branching in keccak impl ([#6133](https://github.com/noir-lang/noir/issues/6133)) ([9c69dce](https://github.com/noir-lang/noir/commit/9c69dce2250b6fc656af8d9c06d7fac34b35c73a)) +* Represent assertions more similarly to function calls ([#6103](https://github.com/noir-lang/noir/issues/6103)) ([3ecd0e2](https://github.com/noir-lang/noir/commit/3ecd0e29441d27bc77c49993495209a70be0d86e)) +* Show test output when running via LSP ([#6049](https://github.com/noir-lang/noir/issues/6049)) ([9fb010e](https://github.com/noir-lang/noir/commit/9fb010ef8a93cf25e4d361ee42aa8969e5a46bab)) +* Simplify sha256 implementation ([#6142](https://github.com/noir-lang/noir/issues/6142)) ([acdfbbc](https://github.com/noir-lang/noir/commit/acdfbbc4ecc9d213dc885a12952e29e188420dff)) +* Skip `remove_enable_side_effects` pass on brillig functions ([#6199](https://github.com/noir-lang/noir/issues/6199)) ([2303615](https://github.com/noir-lang/noir/commit/2303615815a2a60de8ac3dd53349f85201660917)) +* **ssa:** Simplify signed casts ([#6166](https://github.com/noir-lang/noir/issues/6166)) ([eec3a61](https://github.com/noir-lang/noir/commit/eec3a6152493e56866ec5338ff52f823c530778e)) +* Swap endianness in-place in keccak implementation ([#6128](https://github.com/noir-lang/noir/issues/6128)) ([e3cdebe](https://github.com/noir-lang/noir/commit/e3cdebe515e4dc4ee6e16e01bd8af25135939798)) +* Syncing TypeVariableKind with Kind ([#6094](https://github.com/noir-lang/noir/issues/6094)) ([6440e18](https://github.com/noir-lang/noir/commit/6440e183085160d77563b4e735ccaaf199e21693)) +* Visibility for globals ([#6161](https://github.com/noir-lang/noir/issues/6161)) ([103b54d](https://github.com/noir-lang/noir/commit/103b54db8a5a81ecf76381fe99320c1e1f606898)) +* Visibility for modules ([#6165](https://github.com/noir-lang/noir/issues/6165)) ([fcdbcb9](https://github.com/noir-lang/noir/commit/fcdbcb91afb18771cbb5ee48628e171845f22f5f)) +* Visibility for traits ([#6056](https://github.com/noir-lang/noir/issues/6056)) ([5bbd9ba](https://github.com/noir-lang/noir/commit/5bbd9ba9a6d6494fd16813b44036b78c871f6613)) +* Visibility for type aliases ([#6058](https://github.com/noir-lang/noir/issues/6058)) ([66d2a07](https://github.com/noir-lang/noir/commit/66d2a07f0fedb04422c218cbe8d6fb080efac994)) + + +### Bug Fixes + +* (LSP) make goto and hover work well for attributes ([#6152](https://github.com/noir-lang/noir/issues/6152)) ([c679bc6](https://github.com/noir-lang/noir/commit/c679bc6bbd291b6264820dd497b37279116a1cd2)) +* Allow macros to change types on each iteration of a comptime loop ([#6105](https://github.com/noir-lang/noir/issues/6105)) ([0864e7c](https://github.com/noir-lang/noir/commit/0864e7c945089cc06f8cc9e5c7d933c465d8c892)) +* Allow providing default implementations of unconstrained trait methods ([#6138](https://github.com/noir-lang/noir/issues/6138)) ([7679bbc](https://github.com/noir-lang/noir/commit/7679bbc10cb2fa480489fe1aad83fe77ec2af7e8)) +* Always parse all tokens from quoted token streams ([#6064](https://github.com/noir-lang/noir/issues/6064)) ([23ed74b](https://github.com/noir-lang/noir/commit/23ed74bc94ec4da8dbd35da0ae39b26c7ef601e5)) +* Be more lenient with semicolons on interned expressions ([#6062](https://github.com/noir-lang/noir/issues/6062)) ([052c4fe](https://github.com/noir-lang/noir/commit/052c4fe52a4df9d6492f9b0d6b449151b87b18d5)) +* Consider constants as used values to keep their rc ops ([#6122](https://github.com/noir-lang/noir/issues/6122)) ([1217005](https://github.com/noir-lang/noir/commit/12170056102ea15698aacc820876fee0bb7d0c68)) +* Correct stack trace order in comptime assertion failures ([#6066](https://github.com/noir-lang/noir/issues/6066)) ([04f1636](https://github.com/noir-lang/noir/commit/04f1636ca0ccd741c72fa98d6c26227ea9835b0c)) +* Databus panic for fns with empty params (https://github.com/AztecProtocol/aztec-packages/pull/8847) ([d252748](https://github.com/noir-lang/noir/commit/d2527482dafef694be2f389e5b4dbc813234da71)) +* Decode databus return values ([#6095](https://github.com/noir-lang/noir/issues/6095)) ([c40eb1f](https://github.com/noir-lang/noir/commit/c40eb1fd8a0ba63b2d122e42b47dfa9dca5bf7b0)) +* Disable side-effects for no_predicates functions ([#6027](https://github.com/noir-lang/noir/issues/6027)) ([fc74c55](https://github.com/noir-lang/noir/commit/fc74c55ffed892962413c6fe15af62e1d2e7b785)) +* Disambiguate field or int static trait method call ([#6112](https://github.com/noir-lang/noir/issues/6112)) ([5b27ea4](https://github.com/noir-lang/noir/commit/5b27ea4d8031318723cc2b97f76758d401a565a0)) +* Do not duplicate constant arrays in brillig ([#6155](https://github.com/noir-lang/noir/issues/6155)) ([68f3022](https://github.com/noir-lang/noir/commit/68f3022fcdaab6e379e43091b3242e6ea51cff26)) +* **docs:** Rename recursion.md to recursion.mdx ([#6195](https://github.com/noir-lang/noir/issues/6195)) ([054e48b](https://github.com/noir-lang/noir/commit/054e48b76e7b083feb500d30c54912f9db57c565)) +* Don't crash on untyped global used as array length ([#6076](https://github.com/noir-lang/noir/issues/6076)) ([426f295](https://github.com/noir-lang/noir/commit/426f2955cbe4f086581d05eea7d06c47e0491195)) +* Ensure to_bytes returns the canonical decomposition ([#6084](https://github.com/noir-lang/noir/issues/6084)) ([b280a79](https://github.com/noir-lang/noir/commit/b280a79cf8a4fd2a97200e5436e0ec7cb7134711)) +* Error on `&mut x` when `x` is not mutable ([#6037](https://github.com/noir-lang/noir/issues/6037)) ([57afc7d](https://github.com/noir-lang/noir/commit/57afc7ddd424220106af7b9c6e0715007f6ea8b8)) +* Fix canonicalization bug ([#6033](https://github.com/noir-lang/noir/issues/6033)) ([7397772](https://github.com/noir-lang/noir/commit/739777214863de4088162711953f26ca992b356e)) +* Fix comptime type formatting ([#6079](https://github.com/noir-lang/noir/issues/6079)) ([e678091](https://github.com/noir-lang/noir/commit/e67809165c277423e25110c3f1f8eff6e8daa0e4)) +* Handle multi-byte utf8 characters in formatter ([#6118](https://github.com/noir-lang/noir/issues/6118)) ([b1d0619](https://github.com/noir-lang/noir/commit/b1d061926376965805ef3ece3e32d94df81462a6)) +* Handle parenthesized expressions in array length ([#6132](https://github.com/noir-lang/noir/issues/6132)) ([9f0b397](https://github.com/noir-lang/noir/commit/9f0b3971ee41e78241cbea4e3f81bac4edd5897d)) +* Ignore compression of blocks after msg.len in sha256_var ([#6206](https://github.com/noir-lang/noir/issues/6206)) ([76eec71](https://github.com/noir-lang/noir/commit/76eec710ff73e5e45fdddcd41ae2cd74e879cfa5)) +* Infer globals to be u32 when used in a type ([#6083](https://github.com/noir-lang/noir/issues/6083)) ([78262c9](https://github.com/noir-lang/noir/commit/78262c96d5b116c77e50653f9059da60824db812)) +* Initialise databus using return values ([#6074](https://github.com/noir-lang/noir/issues/6074)) ([e17dfa5](https://github.com/noir-lang/noir/commit/e17dfa55719f0cfb1080dd25eeda7b70ed44b60d)) +* Let LSP suggest fields and methods in LValue chains ([#6051](https://github.com/noir-lang/noir/issues/6051)) ([5bf6567](https://github.com/noir-lang/noir/commit/5bf6567320629835ef6fa7765ca87e9b38ae4c9a)) +* Let token pretty printer handle `+=` and similar token sequences ([#6135](https://github.com/noir-lang/noir/issues/6135)) ([684b6cc](https://github.com/noir-lang/noir/commit/684b6cc7deb3ed7ecbb2cea4663e8e9a3ae075f0)) +* **mem2reg:** Remove possibility of underflow ([#6107](https://github.com/noir-lang/noir/issues/6107)) ([aea5cc7](https://github.com/noir-lang/noir/commit/aea5cc789ccf4a4d16b1d238d99474f37920b37e)) +* Parse a statement as an expression ([#6040](https://github.com/noir-lang/noir/issues/6040)) ([ab203e4](https://github.com/noir-lang/noir/commit/ab203e4ee902b9137519f9a4261ec368d22f0a25)) +* Pass radix directly to the blackbox ([#6164](https://github.com/noir-lang/noir/issues/6164)) ([82b89c4](https://github.com/noir-lang/noir/commit/82b89c421da80b719922416d574c1bbaa73d55b4)) +* Preserve generic kind on trait methods ([#6099](https://github.com/noir-lang/noir/issues/6099)) ([1df102a](https://github.com/noir-lang/noir/commit/1df102a1ee0eb39dcbada50e10b226c7f7be0f26)) +* Prevent check_can_mutate crashing on undefined variable ([#6044](https://github.com/noir-lang/noir/issues/6044)) ([b3accfc](https://github.com/noir-lang/noir/commit/b3accfc99249ccd198051ecb98cf7962af64a629)) +* Revert mistaken stack size change ([#6212](https://github.com/noir-lang/noir/issues/6212)) ([a37117a](https://github.com/noir-lang/noir/commit/a37117aca3340447d807c1cf3ca79ba573ceaf8b)) +* **ssa:** Check if result of array set is used in value of another array set ([#6197](https://github.com/noir-lang/noir/issues/6197)) ([594ec91](https://github.com/noir-lang/noir/commit/594ec91de55c4cf191d7cdc94a00bb16711cd430)) +* **ssa:** RC correctness issue ([#6134](https://github.com/noir-lang/noir/issues/6134)) ([5b1c896](https://github.com/noir-lang/noir/commit/5b1c896c605ed1047fc17a437e0b58792a778e2d)) +* Type variables by default should have Any kind ([#6203](https://github.com/noir-lang/noir/issues/6203)) ([268f2a0](https://github.com/noir-lang/noir/commit/268f2a0240c507646c65c932748d1bdf062d00b1)) +* Unify macro result type with actual type ([#6086](https://github.com/noir-lang/noir/issues/6086)) ([af52873](https://github.com/noir-lang/noir/commit/af52873dbec9ab980d17d9ba4336181c006a9a53)) +* Update databus in flattening ([#6063](https://github.com/noir-lang/noir/issues/6063)) ([e993da1](https://github.com/noir-lang/noir/commit/e993da1b01aa98deed2af7b5cba2da216fb036a0)) + + +### Miscellaneous Chores + +* Removing implicit numeric generics ([#5837](https://github.com/noir-lang/noir/issues/5837)) ([eda9043](https://github.com/noir-lang/noir/commit/eda904328b269b5926f8a82ab82e52a485903bbe)) + ## [0.34.0](https://github.com/noir-lang/noir/compare/v0.33.0...v0.34.0) (2024-09-13) diff --git a/noir/noir-repo/Cargo.lock b/noir/noir-repo/Cargo.lock index cdc1d75a421..8be1d274678 100644 --- a/noir/noir-repo/Cargo.lock +++ b/noir/noir-repo/Cargo.lock @@ -4,7 +4,7 @@ version = 3 [[package]] name = "acir" -version = "0.50.0" +version = "0.51.0" dependencies = [ "acir_field", "base64 0.21.7", @@ -26,7 +26,7 @@ dependencies = [ [[package]] name = "acir_field" -version = "0.50.0" +version = "0.51.0" dependencies = [ "ark-bls12-381", "ark-bn254", @@ -40,7 +40,7 @@ dependencies = [ [[package]] name = "acvm" -version = "0.50.0" +version = "0.51.0" dependencies = [ "acir", "acvm_blackbox_solver", @@ -59,7 +59,7 @@ dependencies = [ [[package]] name = "acvm_blackbox_solver" -version = "0.50.0" +version = "0.51.0" dependencies = [ "acir", "blake2", @@ -97,7 +97,7 @@ dependencies = [ [[package]] name = "acvm_js" -version = "0.50.0" +version = "0.51.0" dependencies = [ "acvm", "bn254_blackbox_solver", @@ -131,17 +131,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "ahash" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" -dependencies = [ - "getrandom 0.2.15", - "once_cell", - "version_check", -] - [[package]] name = "ahash" version = "0.8.11" @@ -589,7 +578,7 @@ dependencies = [ [[package]] name = "bn254_blackbox_solver" -version = "0.50.0" +version = "0.51.0" dependencies = [ "acir", "acvm_blackbox_solver", @@ -607,7 +596,7 @@ dependencies = [ [[package]] name = "brillig" -version = "0.50.0" +version = "0.51.0" dependencies = [ "acir_field", "serde", @@ -615,7 +604,7 @@ dependencies = [ [[package]] name = "brillig_vm" -version = "0.50.0" +version = "0.51.0" dependencies = [ "acir", "acvm_blackbox_solver", @@ -761,14 +750,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "chumsky" -version = "0.8.0" -source = "git+https://github.com/jfecher/chumsky?rev=ad9d312#ad9d312d9ffbc66c14514fa2b5752f4127b44f1e" -dependencies = [ - "hashbrown 0.11.2", -] - [[package]] name = "ciborium" version = "0.2.1" @@ -1552,7 +1533,7 @@ dependencies = [ [[package]] name = "fm" -version = "0.34.0" +version = "0.35.0" dependencies = [ "codespan-reporting", "iter-extended", @@ -1830,15 +1811,6 @@ dependencies = [ "rayon", ] -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" -dependencies = [ - "ahash 0.7.8", -] - [[package]] name = "hashbrown" version = "0.12.3" @@ -1851,7 +1823,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.8.11", + "ahash", ] [[package]] @@ -2099,7 +2071,7 @@ version = "0.11.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "321f0f839cd44a4686e9504b0a62b4d69a50b62072144c71c68f5873c167b8d9" dependencies = [ - "ahash 0.8.11", + "ahash", "clap", "crossbeam-channel", "crossbeam-utils", @@ -2158,7 +2130,7 @@ dependencies = [ [[package]] name = "iter-extended" -version = "0.34.0" +version = "0.35.0" [[package]] name = "itertools" @@ -2391,6 +2363,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" @@ -2536,7 +2520,7 @@ dependencies = [ [[package]] name = "nargo" -version = "0.34.0" +version = "0.35.0" dependencies = [ "acvm", "fm", @@ -2556,16 +2540,17 @@ dependencies = [ "rand 0.8.5", "rayon", "serde", - "tempfile", "thiserror", "tracing", + "walkdir", ] [[package]] name = "nargo_cli" -version = "0.34.0" +version = "0.35.0" dependencies = [ "acvm", + "ark-bn254", "assert_cmd", "assert_fs", "async-lsp", @@ -2581,6 +2566,7 @@ dependencies = [ "fm", "iai", "iter-extended", + "light-poseidon", "nargo", "nargo_fmt", "nargo_toml", @@ -2597,9 +2583,12 @@ dependencies = [ "pprof 0.13.0", "predicates 2.1.5", "prettytable-rs", + "proptest", "rayon", "serde", "serde_json", + "sha2", + "sha3", "similar-asserts", "tempfile", "termcolor", @@ -2616,9 +2605,10 @@ dependencies = [ [[package]] name = "nargo_fmt" -version = "0.34.0" +version = "0.35.0" dependencies = [ "bytecount", + "noirc_errors", "noirc_frontend", "serde", "similar-asserts", @@ -2628,7 +2618,7 @@ dependencies = [ [[package]] name = "nargo_toml" -version = "0.34.0" +version = "0.35.0" dependencies = [ "dirs", "fm", @@ -2702,7 +2692,7 @@ dependencies = [ [[package]] name = "noir_debugger" -version = "0.34.0" +version = "0.35.0" dependencies = [ "acvm", "assert_cmd", @@ -2726,7 +2716,7 @@ dependencies = [ [[package]] name = "noir_fuzzer" -version = "0.34.0" +version = "0.35.0" dependencies = [ "acvm", "noirc_abi", @@ -2749,11 +2739,10 @@ dependencies = [ [[package]] name = "noir_lsp" -version = "0.34.0" +version = "0.35.0" dependencies = [ "acvm", "async-lsp", - "chumsky", "codespan-lsp", "convert_case 0.6.0", "fm", @@ -2779,7 +2768,7 @@ dependencies = [ [[package]] name = "noir_profiler" -version = "0.34.0" +version = "0.35.0" dependencies = [ "acir", "clap", @@ -2801,7 +2790,7 @@ dependencies = [ [[package]] name = "noir_wasm" -version = "0.34.0" +version = "0.35.0" dependencies = [ "acvm", "build-data", @@ -2825,7 +2814,7 @@ dependencies = [ [[package]] name = "noirc_abi" -version = "0.34.0" +version = "0.35.0" dependencies = [ "acvm", "iter-extended", @@ -2844,7 +2833,7 @@ dependencies = [ [[package]] name = "noirc_abi_wasm" -version = "0.34.0" +version = "0.35.0" dependencies = [ "acvm", "build-data", @@ -2861,11 +2850,11 @@ dependencies = [ [[package]] name = "noirc_arena" -version = "0.34.0" +version = "0.35.0" [[package]] name = "noirc_artifacts" -version = "0.34.0" +version = "0.35.0" dependencies = [ "acvm", "codespan-reporting", @@ -2880,7 +2869,7 @@ dependencies = [ [[package]] name = "noirc_driver" -version = "0.34.0" +version = "0.35.0" dependencies = [ "acvm", "build-data", @@ -2899,11 +2888,10 @@ dependencies = [ [[package]] name = "noirc_errors" -version = "0.34.0" +version = "0.35.0" dependencies = [ "acvm", "base64 0.21.7", - "chumsky", "codespan", "codespan-reporting", "flate2", @@ -2917,7 +2905,7 @@ dependencies = [ [[package]] name = "noirc_evaluator" -version = "0.34.0" +version = "0.35.0" dependencies = [ "acvm", "bn254_blackbox_solver", @@ -2940,13 +2928,12 @@ dependencies = [ [[package]] name = "noirc_frontend" -version = "0.34.0" +version = "0.35.0" dependencies = [ "acvm", "base64 0.21.7", "bn254_blackbox_solver", "cfg-if 1.0.0", - "chumsky", "fm", "im", "iter-extended", @@ -2971,7 +2958,7 @@ dependencies = [ [[package]] name = "noirc_printable_type" -version = "0.34.0" +version = "0.35.0" dependencies = [ "acvm", "iter-extended", @@ -3033,11 +3020,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", ] @@ -3054,19 +3040,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/Cargo.toml b/noir/noir-repo/Cargo.toml index a6cfa7de07f..0a282631288 100644 --- a/noir/noir-repo/Cargo.toml +++ b/noir/noir-repo/Cargo.toml @@ -40,7 +40,7 @@ resolver = "2" [workspace.package] # x-release-please-start-version -version = "0.34.0" +version = "0.35.0" # x-release-please-end authors = ["The Noir Team "] edition = "2021" @@ -57,13 +57,13 @@ unused_qualifications = "warn" [workspace.dependencies] # ACVM workspace dependencies -acir_field = { version = "0.50.0", path = "acvm-repo/acir_field", default-features = false } -acir = { version = "0.50.0", path = "acvm-repo/acir", default-features = false } -acvm = { version = "0.50.0", path = "acvm-repo/acvm" } -brillig = { version = "0.50.0", path = "acvm-repo/brillig", default-features = false } -brillig_vm = { version = "0.50.0", path = "acvm-repo/brillig_vm", default-features = false } -acvm_blackbox_solver = { version = "0.50.0", path = "acvm-repo/blackbox_solver", default-features = false } -bn254_blackbox_solver = { version = "0.50.0", path = "acvm-repo/bn254_blackbox_solver", default-features = false } +acir_field = { version = "0.51.0", path = "acvm-repo/acir_field", default-features = false } +acir = { version = "0.51.0", path = "acvm-repo/acir", default-features = false } +acvm = { version = "0.51.0", path = "acvm-repo/acvm" } +brillig = { version = "0.51.0", path = "acvm-repo/brillig", default-features = false } +brillig_vm = { version = "0.51.0", path = "acvm-repo/brillig_vm", default-features = false } +acvm_blackbox_solver = { version = "0.51.0", path = "acvm-repo/blackbox_solver", default-features = false } +bn254_blackbox_solver = { version = "0.51.0", path = "acvm-repo/bn254_blackbox_solver", default-features = false } # Noir compiler workspace dependencies fm = { path = "compiler/fm" } @@ -87,8 +87,12 @@ bb_abstraction_leaks = { path = "tooling/bb_abstraction_leaks" } acvm_cli = { path = "tooling/acvm_cli" } # Arkworks -ark-bn254 = { version = "^0.4.0", default-features = false, features = ["curve"] } -ark-bls12-381 = { version = "^0.4.0", default-features = false, features = ["curve"] } +ark-bn254 = { version = "^0.4.0", default-features = false, features = [ + "curve", +] } +ark-bls12-381 = { version = "^0.4.0", default-features = false, features = [ + "curve", +] } grumpkin = { version = "0.1.0", package = "noir_grumpkin", features = ["std"] } ark-ec = { version = "^0.4.0", default-features = false } ark-ff = { version = "^0.4.0", default-features = false } @@ -117,10 +121,6 @@ clap = { version = "4.3.19", features = ["derive", "env"] } codespan = { version = "0.11.1", features = ["serialization"] } codespan-lsp = "0.11.1" codespan-reporting = "0.11.1" -chumsky = { git = "https://github.com/jfecher/chumsky", rev = "ad9d312", default-features = false, features = [ - "ahash", - "std", -] } # Benchmarking criterion = "0.5.0" @@ -153,6 +153,8 @@ rand = "0.8.5" proptest = "1.2.0" proptest-derive = "0.4.0" rayon = "1.8.0" +sha2 = { version = "0.10.6", features = ["compress"] } +sha3 = "0.10.6" im = { version = "15.1", features = ["serde"] } tracing = "0.1.40" diff --git a/noir/noir-repo/acvm-repo/CHANGELOG.md b/noir/noir-repo/acvm-repo/CHANGELOG.md index 8d47e10c45a..9541edb74a3 100644 --- a/noir/noir-repo/acvm-repo/CHANGELOG.md +++ b/noir/noir-repo/acvm-repo/CHANGELOG.md @@ -5,6 +5,92 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.51.0](https://github.com/noir-lang/noir/compare/v0.50.0...v0.51.0) (2024-10-03) + + +### ⚠ BREAKING CHANGES + +* remove sha256 opcode (https://github.com/AztecProtocol/aztec-packages/pull/4571) +* add support for u1 in the avm, ToRadix's radix arg is a memory addr (https://github.com/AztecProtocol/aztec-packages/pull/8570) +* Add Not instruction in brillig (https://github.com/AztecProtocol/aztec-packages/pull/8488) +* **avm:** variants for SET opcode (https://github.com/AztecProtocol/aztec-packages/pull/8441) +* **avm/brillig:** take addresses in calldatacopy (https://github.com/AztecProtocol/aztec-packages/pull/8388) +* constant inputs for blackbox (https://github.com/AztecProtocol/aztec-packages/pull/7222) + +### Features + +* (bb) 128-bit challenges (https://github.com/AztecProtocol/aztec-packages/pull/8406) ([3c3ed1e](https://github.com/noir-lang/noir/commit/3c3ed1e3d28946a02071c524dd128afe131bc3da)) +* **acir_gen:** Width aware ACIR gen addition ([#5493](https://github.com/noir-lang/noir/issues/5493)) ([85fa592](https://github.com/noir-lang/noir/commit/85fa592fdef3b8589ce03b232e1b51565837b540)) +* Add assertions for ACVM `FunctionInput` `bit_size` ([#5864](https://github.com/noir-lang/noir/issues/5864)) ([8712f4c](https://github.com/noir-lang/noir/commit/8712f4c20d23f3809bcfb03f2e3ba0e5ace20a1d)) +* Add Not instruction in brillig (https://github.com/AztecProtocol/aztec-packages/pull/8488) ([95e19ab](https://github.com/noir-lang/noir/commit/95e19ab9486ad054241b6e53e40e55bdba9dc7e5)) +* Add recursive aggregation object to proving/verification keys (https://github.com/AztecProtocol/aztec-packages/pull/6770) ([4ea25db](https://github.com/noir-lang/noir/commit/4ea25dbde87488e758139619a3ce4edf93c6ebd6)) +* Add reusable procedures to brillig generation (https://github.com/AztecProtocol/aztec-packages/pull/7981) ([5c4f19f](https://github.com/noir-lang/noir/commit/5c4f19f097dd3704522996330c961bf0a2db8d99)) +* Add support for u1 in the avm, ToRadix's radix arg is a memory addr (https://github.com/AztecProtocol/aztec-packages/pull/8570) ([e8bbce7](https://github.com/noir-lang/noir/commit/e8bbce71fde3fc7af410c30920c2a547389d8248)) +* Added indirect const instruction (https://github.com/AztecProtocol/aztec-packages/pull/8065) ([5c4f19f](https://github.com/noir-lang/noir/commit/5c4f19f097dd3704522996330c961bf0a2db8d99)) +* Adding aggregation to honk and rollup (https://github.com/AztecProtocol/aztec-packages/pull/7466) ([4ea25db](https://github.com/noir-lang/noir/commit/4ea25dbde87488e758139619a3ce4edf93c6ebd6)) +* Automate verify_honk_proof input generation (https://github.com/AztecProtocol/aztec-packages/pull/8092) ([5c4f19f](https://github.com/noir-lang/noir/commit/5c4f19f097dd3704522996330c961bf0a2db8d99)) +* **avm/brillig:** Take addresses in calldatacopy (https://github.com/AztecProtocol/aztec-packages/pull/8388) ([3c3ed1e](https://github.com/noir-lang/noir/commit/3c3ed1e3d28946a02071c524dd128afe131bc3da)) +* **avm:** Variants for SET opcode (https://github.com/AztecProtocol/aztec-packages/pull/8441) ([3c3ed1e](https://github.com/noir-lang/noir/commit/3c3ed1e3d28946a02071c524dd128afe131bc3da)) +* Avoid heap allocs when going to/from field (https://github.com/AztecProtocol/aztec-packages/pull/7547) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* Change the layout of arrays and vectors to be a single pointer (https://github.com/AztecProtocol/aztec-packages/pull/8448) ([d4832ec](https://github.com/noir-lang/noir/commit/d4832ece9d3ad16544afea49cc7caf40501a2cc3)) +* Constant inputs for blackbox (https://github.com/AztecProtocol/aztec-packages/pull/7222) ([fb97bb9](https://github.com/noir-lang/noir/commit/fb97bb9b795c9d7af395b82fd6f0ea8111d59c11)) +* Hook up secondary calldata column in dsl (https://github.com/AztecProtocol/aztec-packages/pull/7759) ([4ea25db](https://github.com/noir-lang/noir/commit/4ea25dbde87488e758139619a3ce4edf93c6ebd6)) +* Integrate new proving systems in e2e (https://github.com/AztecProtocol/aztec-packages/pull/6971) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* Make Brillig do integer arithmetic operations using u128 instead of Bigint (https://github.com/AztecProtocol/aztec-packages/pull/7518) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* Make token transfer be recursive (https://github.com/AztecProtocol/aztec-packages/pull/7730) ([4ea25db](https://github.com/noir-lang/noir/commit/4ea25dbde87488e758139619a3ce4edf93c6ebd6)) +* New test programs for wasm benchmarking (https://github.com/AztecProtocol/aztec-packages/pull/8389) ([95e19ab](https://github.com/noir-lang/noir/commit/95e19ab9486ad054241b6e53e40e55bdba9dc7e5)) +* Note hashes as points (https://github.com/AztecProtocol/aztec-packages/pull/7618) ([4ea25db](https://github.com/noir-lang/noir/commit/4ea25dbde87488e758139619a3ce4edf93c6ebd6)) +* Optimize allocating immediate amounts of memory (https://github.com/AztecProtocol/aztec-packages/pull/8579) ([e8bbce7](https://github.com/noir-lang/noir/commit/e8bbce71fde3fc7af410c30920c2a547389d8248)) +* Optimize constant array handling in brillig_gen (https://github.com/AztecProtocol/aztec-packages/pull/7661) ([4ea25db](https://github.com/noir-lang/noir/commit/4ea25dbde87488e758139619a3ce4edf93c6ebd6)) +* Optimize to_radix (https://github.com/AztecProtocol/aztec-packages/pull/8073) ([5c4f19f](https://github.com/noir-lang/noir/commit/5c4f19f097dd3704522996330c961bf0a2db8d99)) +* Pass calldata ids to the backend (https://github.com/AztecProtocol/aztec-packages/pull/7875) ([4ea25db](https://github.com/noir-lang/noir/commit/4ea25dbde87488e758139619a3ce4edf93c6ebd6)) +* Poseidon2 gates for Ultra arithmetisation (https://github.com/AztecProtocol/aztec-packages/pull/7494) ([5c4f19f](https://github.com/noir-lang/noir/commit/5c4f19f097dd3704522996330c961bf0a2db8d99)) +* **profiler:** Add support for brillig functions in opcodes-flamegraph (https://github.com/AztecProtocol/aztec-packages/pull/7698) ([4ea25db](https://github.com/noir-lang/noir/commit/4ea25dbde87488e758139619a3ce4edf93c6ebd6)) +* Remove sha256 opcode (https://github.com/AztecProtocol/aztec-packages/pull/4571) ([e8bbce7](https://github.com/noir-lang/noir/commit/e8bbce71fde3fc7af410c30920c2a547389d8248)) +* Removing superfluous call to MSM (https://github.com/AztecProtocol/aztec-packages/pull/7708) ([4ea25db](https://github.com/noir-lang/noir/commit/4ea25dbde87488e758139619a3ce4edf93c6ebd6)) +* Report gates and VKs of private protocol circuits with megahonk (https://github.com/AztecProtocol/aztec-packages/pull/7722) ([4ea25db](https://github.com/noir-lang/noir/commit/4ea25dbde87488e758139619a3ce4edf93c6ebd6)) +* Simplify constant calls to `poseidon2_permutation`, `schnorr_verify` and `embedded_curve_add` ([#5140](https://github.com/noir-lang/noir/issues/5140)) ([2823ba7](https://github.com/noir-lang/noir/commit/2823ba7242db788ca1d7f6e7a48be2f1de62f278)) +* Small optimization in toradix (https://github.com/AztecProtocol/aztec-packages/pull/8040) ([5c4f19f](https://github.com/noir-lang/noir/commit/5c4f19f097dd3704522996330c961bf0a2db8d99)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/7392) ([fb97bb9](https://github.com/noir-lang/noir/commit/fb97bb9b795c9d7af395b82fd6f0ea8111d59c11)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/7400) ([fb97bb9](https://github.com/noir-lang/noir/commit/fb97bb9b795c9d7af395b82fd6f0ea8111d59c11)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/7432) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/7444) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/7454) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/7512) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/7577) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/7583) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/7743) ([4ea25db](https://github.com/noir-lang/noir/commit/4ea25dbde87488e758139619a3ce4edf93c6ebd6)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/7862) ([4ea25db](https://github.com/noir-lang/noir/commit/4ea25dbde87488e758139619a3ce4edf93c6ebd6)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/7945) ([4ea25db](https://github.com/noir-lang/noir/commit/4ea25dbde87488e758139619a3ce4edf93c6ebd6)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/7958) ([5c4f19f](https://github.com/noir-lang/noir/commit/5c4f19f097dd3704522996330c961bf0a2db8d99)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/8008) ([5c4f19f](https://github.com/noir-lang/noir/commit/5c4f19f097dd3704522996330c961bf0a2db8d99)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/8093) ([5c4f19f](https://github.com/noir-lang/noir/commit/5c4f19f097dd3704522996330c961bf0a2db8d99)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/8125) ([f0c2686](https://github.com/noir-lang/noir/commit/f0c268606a71381ab4504396695a0adb9b3258b6)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/8237) ([f0c2686](https://github.com/noir-lang/noir/commit/f0c268606a71381ab4504396695a0adb9b3258b6)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/8423) ([3c3ed1e](https://github.com/noir-lang/noir/commit/3c3ed1e3d28946a02071c524dd128afe131bc3da)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/8435) ([3c3ed1e](https://github.com/noir-lang/noir/commit/3c3ed1e3d28946a02071c524dd128afe131bc3da)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/8466) ([3c3ed1e](https://github.com/noir-lang/noir/commit/3c3ed1e3d28946a02071c524dd128afe131bc3da)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/8482) ([d4832ec](https://github.com/noir-lang/noir/commit/d4832ece9d3ad16544afea49cc7caf40501a2cc3)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/8512) ([95e19ab](https://github.com/noir-lang/noir/commit/95e19ab9486ad054241b6e53e40e55bdba9dc7e5)) +* Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/8526) ([95e19ab](https://github.com/noir-lang/noir/commit/95e19ab9486ad054241b6e53e40e55bdba9dc7e5)) +* TXE nr deployments, dependency cleanup for CLI (https://github.com/AztecProtocol/aztec-packages/pull/7548) ([4ea25db](https://github.com/noir-lang/noir/commit/4ea25dbde87488e758139619a3ce4edf93c6ebd6)) +* Typing return values of embedded_curve_ops (https://github.com/AztecProtocol/aztec-packages/pull/7413) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* Unify all acir recursion constraints based on RecursionConstraint and proof_type (https://github.com/AztecProtocol/aztec-packages/pull/7993) ([5c4f19f](https://github.com/noir-lang/noir/commit/5c4f19f097dd3704522996330c961bf0a2db8d99)) + + +### Bug Fixes + +* Add trailing extra arguments for backend in gates_flamegraph (https://github.com/AztecProtocol/aztec-packages/pull/7472) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* **debugger:** Update the debugger to handle the new Brillig debug metadata format ([#5706](https://github.com/noir-lang/noir/issues/5706)) ([a31f82e](https://github.com/noir-lang/noir/commit/a31f82e598def60d00c65b79b8c5411f8aa832aa)) +* Deflatten databus visibilities (https://github.com/AztecProtocol/aztec-packages/pull/7761) ([4ea25db](https://github.com/noir-lang/noir/commit/4ea25dbde87488e758139619a3ce4edf93c6ebd6)) +* Do not duplicate redundant Brillig debug metadata ([#5696](https://github.com/noir-lang/noir/issues/5696)) ([e4f7dbe](https://github.com/noir-lang/noir/commit/e4f7dbe63b55807b3ff0b4d6f47a8b7f847299fb)) +* Export brillig names in contract functions (https://github.com/AztecProtocol/aztec-packages/pull/8212) ([f0c2686](https://github.com/noir-lang/noir/commit/f0c268606a71381ab4504396695a0adb9b3258b6)) +* Handle multiple entry points for Brillig call stack resolution after metadata deduplication ([#5788](https://github.com/noir-lang/noir/issues/5788)) ([38fe9dd](https://github.com/noir-lang/noir/commit/38fe9dda111952fdb894df90a319c087382edfc9)) +* Move BigInt modulus checks to runtime in brillig ([#5374](https://github.com/noir-lang/noir/issues/5374)) ([741d339](https://github.com/noir-lang/noir/commit/741d33991f8e2918bf092c354ca56047e0274533)) +* Restrict keccak256_injective test input to 8 bits ([#5977](https://github.com/noir-lang/noir/issues/5977)) ([a1b1346](https://github.com/noir-lang/noir/commit/a1b1346bf7525c508fd390393c307475cc2345d7)) +* Revert "feat: Sync from noir (https://github.com/AztecProtocol/aztec-packages/pull/7512)" (https://github.com/AztecProtocol/aztec-packages/pull/7558) ([daad75c](https://github.com/noir-lang/noir/commit/daad75c26d19ae707b90a7424b77dab9937e8575)) +* Runtime brillig bigint id assignment ([#5369](https://github.com/noir-lang/noir/issues/5369)) ([a8928dd](https://github.com/noir-lang/noir/commit/a8928ddcffcae15babf7aa5aff0e462e4549552e)) + ## [0.50.0](https://github.com/noir-lang/noir/compare/v0.49.0...v0.50.0) (2024-09-13) diff --git a/noir/noir-repo/acvm-repo/acir/Cargo.toml b/noir/noir-repo/acvm-repo/acir/Cargo.toml index b707ae9ad79..69d0f273bb3 100644 --- a/noir/noir-repo/acvm-repo/acir/Cargo.toml +++ b/noir/noir-repo/acvm-repo/acir/Cargo.toml @@ -2,7 +2,7 @@ name = "acir" description = "ACIR is the IR that the VM processes, it is analogous to LLVM IR" # x-release-please-start-version -version = "0.50.0" +version = "0.51.0" # x-release-please-end authors.workspace = true edition.workspace = true 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 fbe179d7c04..8bb9a680ea9 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/acir_field/Cargo.toml b/noir/noir-repo/acvm-repo/acir_field/Cargo.toml index 168628b0d4a..4947d0b572a 100644 --- a/noir/noir-repo/acvm-repo/acir_field/Cargo.toml +++ b/noir/noir-repo/acvm-repo/acir_field/Cargo.toml @@ -2,7 +2,7 @@ name = "acir_field" description = "The field implementation being used by ACIR." # x-release-please-start-version -version = "0.50.0" +version = "0.51.0" # x-release-please-end authors.workspace = true edition.workspace = true diff --git a/noir/noir-repo/acvm-repo/acvm/Cargo.toml b/noir/noir-repo/acvm-repo/acvm/Cargo.toml index 29375a57a6e..b086e7ac197 100644 --- a/noir/noir-repo/acvm-repo/acvm/Cargo.toml +++ b/noir/noir-repo/acvm-repo/acvm/Cargo.toml @@ -2,7 +2,7 @@ name = "acvm" description = "The virtual machine that processes ACIR given a backend/proof system." # x-release-please-start-version -version = "0.50.0" +version = "0.51.0" # x-release-please-end authors.workspace = true edition.workspace = true diff --git a/noir/noir-repo/acvm-repo/acvm_js/Cargo.toml b/noir/noir-repo/acvm-repo/acvm_js/Cargo.toml index b5b55dbb91a..7ec7e5a282e 100644 --- a/noir/noir-repo/acvm-repo/acvm_js/Cargo.toml +++ b/noir/noir-repo/acvm-repo/acvm_js/Cargo.toml @@ -2,7 +2,7 @@ name = "acvm_js" description = "Typescript wrapper around the ACVM allowing execution of ACIR code" # x-release-please-start-version -version = "0.50.0" +version = "0.51.0" # x-release-please-end authors.workspace = true edition.workspace = true diff --git a/noir/noir-repo/acvm-repo/acvm_js/build.sh b/noir/noir-repo/acvm-repo/acvm_js/build.sh index c07d2d8a4c1..16fb26e55db 100755 --- a/noir/noir-repo/acvm-repo/acvm_js/build.sh +++ b/noir/noir-repo/acvm-repo/acvm_js/build.sh @@ -25,7 +25,7 @@ function run_if_available { require_command jq require_command cargo require_command wasm-bindgen -#require_command wasm-opt +require_command wasm-opt self_path=$(dirname "$(readlink -f "$0")") pname=$(cargo read-manifest | jq -r '.name') diff --git a/noir/noir-repo/acvm-repo/acvm_js/package.json b/noir/noir-repo/acvm-repo/acvm_js/package.json index 95b8a46456f..03197cc41f8 100644 --- a/noir/noir-repo/acvm-repo/acvm_js/package.json +++ b/noir/noir-repo/acvm-repo/acvm_js/package.json @@ -1,6 +1,6 @@ { "name": "@noir-lang/acvm_js", - "version": "0.50.0", + "version": "0.51.0", "publishConfig": { "access": "public" }, diff --git a/noir/noir-repo/acvm-repo/blackbox_solver/Cargo.toml b/noir/noir-repo/acvm-repo/blackbox_solver/Cargo.toml index b8b17db433c..b57c9356198 100644 --- a/noir/noir-repo/acvm-repo/blackbox_solver/Cargo.toml +++ b/noir/noir-repo/acvm-repo/blackbox_solver/Cargo.toml @@ -2,7 +2,7 @@ name = "acvm_blackbox_solver" description = "A solver for the blackbox functions found in ACIR and Brillig" # x-release-please-start-version -version = "0.50.0" +version = "0.51.0" # x-release-please-end authors.workspace = true edition.workspace = true @@ -22,8 +22,8 @@ num-bigint = "0.4" blake2 = "0.10.6" blake3 = "1.5.0" -sha2 = { version="0.10.6", features = ["compress",] } -sha3 = "0.10.6" +sha2.workspace = true +sha3.workspace = true keccak = "0.1.4" k256 = { version = "0.11.0", features = [ "ecdsa", diff --git a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/Cargo.toml b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/Cargo.toml index c892dc45fdd..bd63f51410a 100644 --- a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/Cargo.toml +++ b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/Cargo.toml @@ -2,7 +2,7 @@ name = "bn254_blackbox_solver" description = "Solvers for black box functions which are specific for the bn254 curve" # x-release-please-start-version -version = "0.50.0" +version = "0.51.0" # x-release-please-end authors.workspace = true edition.workspace = true 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/acvm-repo/brillig/Cargo.toml b/noir/noir-repo/acvm-repo/brillig/Cargo.toml index 0dec8fa2147..8fe66201a04 100644 --- a/noir/noir-repo/acvm-repo/brillig/Cargo.toml +++ b/noir/noir-repo/acvm-repo/brillig/Cargo.toml @@ -2,7 +2,7 @@ name = "brillig" description = "Brillig is the bytecode ACIR uses for non-determinism." # x-release-please-start-version -version = "0.50.0" +version = "0.51.0" # x-release-please-end authors.workspace = true edition.workspace = true diff --git a/noir/noir-repo/acvm-repo/brillig_vm/Cargo.toml b/noir/noir-repo/acvm-repo/brillig_vm/Cargo.toml index a5e1f7182b1..e82274d203d 100644 --- a/noir/noir-repo/acvm-repo/brillig_vm/Cargo.toml +++ b/noir/noir-repo/acvm-repo/brillig_vm/Cargo.toml @@ -2,7 +2,7 @@ name = "brillig_vm" description = "The virtual machine that processes Brillig bytecode, used to introduce non-determinism to the ACVM" # x-release-please-start-version -version = "0.50.0" +version = "0.51.0" # x-release-please-end authors.workspace = true edition.workspace = true 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/abi_gen.rs b/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs index be89b24fee5..ad54d0f7d6b 100644 --- a/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs +++ b/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs @@ -7,13 +7,17 @@ use noirc_abi::{ Abi, AbiErrorType, AbiParameter, AbiReturnType, AbiType, AbiValue, AbiVisibility, Sign, }; use noirc_frontend::ast::{Signedness, Visibility}; +use noirc_frontend::TypeBinding; use noirc_frontend::{ hir::Context, - hir_def::{expr::HirArrayLiteral, function::Param, stmt::HirPattern, types::Type}, - macros_api::{HirExpression, HirLiteral}, + hir_def::{ + expr::{HirArrayLiteral, HirExpression, HirLiteral}, + function::Param, + stmt::HirPattern, + types::Type, + }, node_interner::{FuncId, NodeInterner}, }; -use noirc_frontend::{TypeBinding, TypeVariableKind}; /// Arranges a function signature and a generated circuit's return witnesses into a /// `noirc_abi::Abi`. @@ -68,13 +72,18 @@ pub(super) fn abi_type_from_hir_type(context: &Context, typ: &Type) -> AbiType { AbiType::Integer { sign, width: (*bit_width).into() } } - Type::TypeVariable(binding, TypeVariableKind::IntegerOrField) - | Type::TypeVariable(binding, TypeVariableKind::Integer) => match &*binding.borrow() { - TypeBinding::Bound(typ) => abi_type_from_hir_type(context, typ), - TypeBinding::Unbound(_) => { - abi_type_from_hir_type(context, &Type::default_int_or_field_type()) + Type::TypeVariable(binding) => { + if binding.is_integer() || binding.is_integer_or_field() { + match &*binding.borrow() { + TypeBinding::Bound(typ) => abi_type_from_hir_type(context, typ), + TypeBinding::Unbound(_id, _kind) => { + abi_type_from_hir_type(context, &Type::default_int_or_field_type()) + } + } + } else { + unreachable!("{typ} cannot be used in the abi") } - }, + } Type::Bool => AbiType::Boolean, Type::String(size) => { let size = size @@ -102,7 +111,6 @@ pub(super) fn abi_type_from_hir_type(context: &Context, typ: &Type) -> AbiType { | Type::Constant(..) | Type::InfixExpr(..) | Type::TraitAsType(..) - | Type::TypeVariable(_, _) | Type::NamedGeneric(..) | Type::Forall(..) | Type::Quoted(_) diff --git a/noir/noir-repo/compiler/noirc_driver/src/lib.rs b/noir/noir-repo/compiler/noirc_driver/src/lib.rs index 1d69e435738..2f0122524eb 100644 --- a/noir/noir-repo/compiler/noirc_driver/src/lib.rs +++ b/noir/noir-repo/compiler/noirc_driver/src/lib.rs @@ -445,7 +445,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/Cargo.toml b/noir/noir-repo/compiler/noirc_errors/Cargo.toml index 61b274c605f..a6927eb647a 100644 --- a/noir/noir-repo/compiler/noirc_errors/Cargo.toml +++ b/noir/noir-repo/compiler/noirc_errors/Cargo.toml @@ -16,7 +16,6 @@ acvm.workspace = true codespan-reporting.workspace = true codespan.workspace = true fm.workspace = true -chumsky.workspace = true noirc_printable_type.workspace = true serde.workspace = true serde_with = "3.2.0" diff --git a/noir/noir-repo/compiler/noirc_errors/src/position.rs b/noir/noir-repo/compiler/noirc_errors/src/position.rs index 9b031f56ae2..8131db323b9 100644 --- a/noir/noir-repo/compiler/noirc_errors/src/position.rs +++ b/noir/noir-repo/compiler/noirc_errors/src/position.rs @@ -8,7 +8,7 @@ use std::{ pub type Position = u32; -#[derive(PartialOrd, Eq, Ord, Debug, Clone)] +#[derive(PartialOrd, Eq, Ord, Debug, Clone, Default)] pub struct Spanned { pub contents: T, span: Span, @@ -121,26 +121,6 @@ impl From> for Span { } } -impl chumsky::Span for Span { - type Context = (); - - type Offset = u32; - - fn new(_context: Self::Context, range: Range) -> Self { - Span(ByteSpan::from(range)) - } - - fn context(&self) -> Self::Context {} - - fn start(&self) -> Self::Offset { - self.start() - } - - fn end(&self) -> Self::Offset { - self.end() - } -} - #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] pub struct Location { pub span: Span, 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.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs index ad6645df228..efc7c6018c1 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs @@ -118,6 +118,7 @@ pub(crate) fn optimize_into_acir( .run_pass(Ssa::remove_enable_side_effects, "After EnableSideEffectsIf removal:") .run_pass(Ssa::fold_constants_using_constraints, "After Constraint Folding:") .run_pass(Ssa::dead_instruction_elimination, "After Dead Instruction Elimination:") + .run_pass(Ssa::simplify_cfg, "After Simplifying:") .run_pass(Ssa::array_set_optimization, "After Array Set Optimizations:") .finish(); 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/acir_gen/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs index 15d72c2ae74..b560fafd337 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -1303,6 +1303,29 @@ impl<'a> Context<'a> { } } + /// Returns the acir value at the provided databus offset + fn get_from_call_data( + &mut self, + offset: &mut AcirVar, + call_data_block: BlockId, + typ: &Type, + ) -> Result { + match typ { + Type::Numeric(_) => self.array_get_value(&Type::field(), call_data_block, offset), + Type::Array(arc, len) => { + let mut result = Vector::new(); + for _i in 0..*len { + for sub_type in arc.iter() { + let element = self.get_from_call_data(offset, call_data_block, sub_type)?; + result.push_back(element); + } + } + Ok(AcirValue::Array(result)) + } + _ => unimplemented!("Unsupported type in databus"), + } + } + /// Generates a read opcode for the array /// `index_side_effect == false` means that we ensured `var_index` will have a type matching the value in the array fn array_get( @@ -1316,27 +1339,19 @@ impl<'a> Context<'a> { let block_id = self.ensure_array_is_initialized(array, dfg)?; let results = dfg.instruction_results(instruction); let res_typ = dfg.type_of_value(results[0]); - // Get operations to call-data parameters are replaced by a get to the call-data-bus array - if let Some(call_data) = - self.data_bus.call_data.iter().find(|cd| cd.index_map.contains_key(&array)) - { - let type_size = res_typ.flattened_size(); - let type_size = self.acir_context.add_constant(FieldElement::from(type_size as i128)); - let offset = self.acir_context.mul_var(var_index, type_size)?; + let call_data = + self.data_bus.call_data.iter().find(|cd| cd.index_map.contains_key(&array)).cloned(); + if let Some(call_data) = call_data { + let call_data_block = self.ensure_array_is_initialized(call_data.array_id, dfg)?; let bus_index = self .acir_context .add_constant(FieldElement::from(call_data.index_map[&array] as i128)); - let new_index = self.acir_context.add_var(offset, bus_index)?; - return self.array_get( - instruction, - call_data.array_id, - new_index, - dfg, - index_side_effect, - ); + let mut current_index = self.acir_context.add_var(bus_index, var_index)?; + let result = self.get_from_call_data(&mut current_index, call_data_block, &res_typ)?; + self.define_result(dfg, instruction, result.clone()); + return Ok(result); } - // Compiler sanity check assert!( !res_typ.contains_slice_element(), diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/data_bus.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/data_bus.rs index 3056fb5973d..de9ae8a24d7 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/data_bus.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/data_bus.rs @@ -121,16 +121,24 @@ impl FunctionBuilder { databus.index += 1; } Type::Array(typ, len) => { - assert!(typ.len() == 1, "unsupported composite type"); databus.map.insert(value, databus.index); - for i in 0..len { - // load each element of the array - let index = self - .current_function - .dfg - .make_constant(FieldElement::from(i as i128), Type::length_type()); - let element = self.insert_array_get(value, index, typ[0].clone()); - self.add_to_data_bus(element, databus); + + let mut index = 0; + for _i in 0..len { + for subitem_typ in typ.iter() { + // load each element of the array, and add it to the databus + let index_var = self + .current_function + .dfg + .make_constant(FieldElement::from(index as i128), Type::length_type()); + let element = self.insert_array_get(value, index_var, subitem_typ.clone()); + index += match subitem_typ { + Type::Array(_, _) | Type::Slice(_) => subitem_typ.element_size(), + Type::Numeric(_) => 1, + _ => unreachable!("Unsupported type for databus"), + }; + self.add_to_data_bus(element, databus); + } } } Type::Reference(_) => { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/cfg.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/cfg.rs index b9166bf1d56..38e6efa5b9a 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/cfg.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/cfg.rs @@ -59,7 +59,7 @@ impl ControlFlowGraph { /// Clears out a given block's successors. This also removes the given block from /// being a predecessor of any of its previous successors. - fn invalidate_block_successors(&mut self, basic_block_id: BasicBlockId) { + pub(crate) fn invalidate_block_successors(&mut self, basic_block_id: BasicBlockId) { let node = self .data .get_mut(&basic_block_id) 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 e30707effed..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 @@ -630,9 +630,9 @@ impl Instruction { } } Instruction::ArraySet { array, index, value, .. } => { - let array = dfg.get_array_constant(*array); - let index = dfg.get_numeric_constant(*index); - if let (Some((array, element_type)), Some(index)) = (array, index) { + let array_const = dfg.get_array_constant(*array); + let index_const = dfg.get_numeric_constant(*index); + if let (Some((array, element_type)), Some(index)) = (array_const, index_const) { let index = index.try_to_u32().expect("Expected array index to fit in u32") as usize; @@ -641,7 +641,8 @@ impl Instruction { return SimplifiedTo(new_array); } } - None + + try_optimize_array_set_from_previous_get(dfg, *array, *index, *value) } Instruction::Truncate { value, bit_size, max_bit_size } => { if bit_size == max_bit_size { @@ -817,6 +818,98 @@ fn try_optimize_array_get_from_previous_set( SimplifyResult::None } +/// If we have an array set whose value is from an array get on the same array at the same index, +/// we can simplify that array set to the array we were looking to perform an array set upon. +/// +/// Simple case: +/// v3 = array_get v1, index v2 +/// v5 = array_set v1, index v2, value v3 +/// +/// If we could not immediately simplify the array set from its value, we can try to follow +/// the array set backwards in the case we have constant indices: +/// +/// v3 = array_get v1, index 1 +/// v5 = array_set v1, index 2, value [Field 100, Field 101, Field 102] +/// v7 = array_set mut v5, index 1, value v3 +/// +/// We want to optimize `v7` to `v5`. We see that `v3` comes from an array get to `v1`. We follow `v5` backwards and see an array set +/// to `v1` and see that the previous array set occurs to a different constant index. +/// +/// For each array_set: +/// - If the index is non-constant we fail the optimization since any index may be changed. +/// - If the index is constant and is our target index, we conservatively fail the optimization. +/// - Otherwise, we check the array value of the `array_set`. We will refer to this array as array'. +/// In the case above, array' is `v1` from `v5 = array set ...` +/// - If the original `array_set` value comes from an `array_get`, check the array in that `array_get` against array'. +/// - If the two values are equal we can simplify. +/// - Continuing the example above, as we have `v3 = array_get v1, index 1`, `v1` is +/// what we want to check against array'. We now know we can simplify `v7` to `v5` as it is unchanged. +/// - If they are not equal, recur marking the current `array_set` array as the new array id to use in the checks +fn try_optimize_array_set_from_previous_get( + dfg: &DataFlowGraph, + mut array_id: ValueId, + target_index: ValueId, + target_value: ValueId, +) -> SimplifyResult { + let array_from_get = match &dfg[target_value] { + Value::Instruction { instruction, .. } => match &dfg[*instruction] { + Instruction::ArrayGet { array, index } => { + if *array == array_id && *index == target_index { + // If array and index match from the value, we can immediately simplify + return SimplifyResult::SimplifiedTo(array_id); + } else if *index == target_index { + *array + } else { + return SimplifyResult::None; + } + } + _ => return SimplifyResult::None, + }, + _ => return SimplifyResult::None, + }; + + // At this point we have determined that the value we are writing in the `array_set` instruction + // comes from an `array_get` from the same index at which we want to write it at. + // It's possible that we're acting on the same array where other indices have been mutated in between + // the `array_get` and `array_set` (resulting in the `array_id` not matching). + // + // We then inspect the set of `array_set`s which which led to the current array the `array_set` is acting on. + // If we can work back to the array on which the `array_get` was reading from without having another `array_set` + // act on the same index then we can be sure that the new `array_set` can be removed without affecting the final result. + let Some(target_index) = dfg.get_numeric_constant(target_index) else { + return SimplifyResult::None; + }; + + let original_array_id = array_id; + // Arbitrary number of maximum tries just to prevent this optimization from taking too long. + let max_tries = 5; + for _ in 0..max_tries { + match &dfg[array_id] { + Value::Instruction { instruction, .. } => match &dfg[*instruction] { + Instruction::ArraySet { array, index, .. } => { + let Some(index) = dfg.get_numeric_constant(*index) else { + return SimplifyResult::None; + }; + + if index == target_index { + return SimplifyResult::None; + } + + if *array == array_from_get { + return SimplifyResult::SimplifiedTo(original_array_id); + } + + array_id = *array; // recur + } + _ => return SimplifyResult::None, + }, + _ => return SimplifyResult::None, + } + } + + SimplifyResult::None +} + pub(crate) type ErrorType = HirType; pub(crate) fn error_selector_from_type(typ: &ErrorType) -> ErrorSelector { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs index 3068f2b5c37..3b202b38b11 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/array_set.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/array_set.rs index 6d48b8c0d67..b2fe137c8bc 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/array_set.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/array_set.rs @@ -1,7 +1,10 @@ +use std::mem; + use crate::ssa::{ ir::{ basic_block::BasicBlockId, dfg::DataFlowGraph, + function::{Function, RuntimeType}, instruction::{Instruction, InstructionId, TerminatorInstruction}, types::Type::{Array, Slice}, value::ValueId, @@ -17,94 +20,126 @@ impl Ssa { #[tracing::instrument(level = "trace", skip(self))] pub(crate) fn array_set_optimization(mut self) -> Self { for func in self.functions.values_mut() { - let reachable_blocks = func.reachable_blocks(); - - if !func.runtime().is_entry_point() { - assert_eq!(reachable_blocks.len(), 1, "Expected there to be 1 block remaining in Acir function for array_set optimization"); - } - let mut array_to_last_use = HashMap::default(); - let mut instructions_to_update = HashSet::default(); - let mut arrays_from_load = HashSet::default(); - - for block in reachable_blocks.iter() { - analyze_last_uses( - &func.dfg, - *block, - &mut array_to_last_use, - &mut instructions_to_update, - &mut arrays_from_load, - ); - } - for block in reachable_blocks { - make_mutable(&mut func.dfg, block, &instructions_to_update); - } + func.array_set_optimization(); } self } } -/// Builds the set of ArraySet instructions that can be made mutable -/// because their input value is unused elsewhere afterward. -fn analyze_last_uses( - dfg: &DataFlowGraph, - block_id: BasicBlockId, - array_to_last_use: &mut HashMap, - instructions_that_can_be_made_mutable: &mut HashSet, - arrays_from_load: &mut HashSet, -) { - let block = &dfg[block_id]; +impl Function { + pub(crate) fn array_set_optimization(&mut self) { + let reachable_blocks = self.reachable_blocks(); - for instruction_id in block.instructions() { - match &dfg[*instruction_id] { - Instruction::ArrayGet { array, .. } => { - let array = dfg.resolve(*array); + if !self.runtime().is_entry_point() { + assert_eq!(reachable_blocks.len(), 1, "Expected there to be 1 block remaining in Acir function for array_set optimization"); + } - if let Some(existing) = array_to_last_use.insert(array, *instruction_id) { - instructions_that_can_be_made_mutable.remove(&existing); - } - } - Instruction::ArraySet { array, .. } => { - let array = dfg.resolve(*array); + let mut context = Context::new(&self.dfg, matches!(self.runtime(), RuntimeType::Brillig)); - if let Some(existing) = array_to_last_use.insert(array, *instruction_id) { - instructions_that_can_be_made_mutable.remove(&existing); - } - // If the array we are setting does not come from a load we can safely mark it mutable. - // If the array comes from a load we may potentially being mutating an array at a reference - // that is loaded from by other values. - let terminator = dfg[block_id].unwrap_terminator(); - // If we are in a return block we are not concerned about the array potentially being mutated again. - let is_return_block = matches!(terminator, TerminatorInstruction::Return { .. }); - // We also want to check that the array is not part of the terminator arguments, as this means it is used again. - let mut array_in_terminator = false; - terminator.for_each_value(|value| { - if value == array { - array_in_terminator = true; + for block in reachable_blocks.iter() { + context.analyze_last_uses(*block); + } + + let instructions_to_update = mem::take(&mut context.instructions_that_can_be_made_mutable); + for block in reachable_blocks { + make_mutable(&mut self.dfg, block, &instructions_to_update); + } + } +} + +struct Context<'f> { + dfg: &'f DataFlowGraph, + is_brillig_runtime: bool, + array_to_last_use: HashMap, + instructions_that_can_be_made_mutable: HashSet, + arrays_from_load: HashSet, + inner_nested_arrays: HashMap, +} + +impl<'f> Context<'f> { + fn new(dfg: &'f DataFlowGraph, is_brillig_runtime: bool) -> Self { + Context { + dfg, + is_brillig_runtime, + array_to_last_use: HashMap::default(), + instructions_that_can_be_made_mutable: HashSet::default(), + arrays_from_load: HashSet::default(), + inner_nested_arrays: HashMap::default(), + } + } + + /// Builds the set of ArraySet instructions that can be made mutable + /// because their input value is unused elsewhere afterward. + fn analyze_last_uses(&mut self, block_id: BasicBlockId) { + let block = &self.dfg[block_id]; + + for instruction_id in block.instructions() { + match &self.dfg[*instruction_id] { + Instruction::ArrayGet { array, .. } => { + let array = self.dfg.resolve(*array); + + if let Some(existing) = self.array_to_last_use.insert(array, *instruction_id) { + self.instructions_that_can_be_made_mutable.remove(&existing); } - }); - if (!arrays_from_load.contains(&array) || is_return_block) && !array_in_terminator { - instructions_that_can_be_made_mutable.insert(*instruction_id); } - } - Instruction::Call { arguments, .. } => { - for argument in arguments { - if matches!(dfg.type_of_value(*argument), Array { .. } | Slice { .. }) { - let argument = dfg.resolve(*argument); + Instruction::ArraySet { array, value, .. } => { + let array = self.dfg.resolve(*array); + + if let Some(existing) = self.array_to_last_use.insert(array, *instruction_id) { + self.instructions_that_can_be_made_mutable.remove(&existing); + } + if self.is_brillig_runtime { + let value = self.dfg.resolve(*value); + + if let Some(existing) = self.inner_nested_arrays.get(&value) { + self.instructions_that_can_be_made_mutable.remove(existing); + } + let result = self.dfg.instruction_results(*instruction_id)[0]; + self.inner_nested_arrays.insert(result, *instruction_id); + } - if let Some(existing) = array_to_last_use.insert(argument, *instruction_id) + // If the array we are setting does not come from a load we can safely mark it mutable. + // If the array comes from a load we may potentially being mutating an array at a reference + // that is loaded from by other values. + let terminator = self.dfg[block_id].unwrap_terminator(); + // If we are in a return block we are not concerned about the array potentially being mutated again. + let is_return_block = + matches!(terminator, TerminatorInstruction::Return { .. }); + // We also want to check that the array is not part of the terminator arguments, as this means it is used again. + let mut array_in_terminator = false; + terminator.for_each_value(|value| { + if value == array { + array_in_terminator = true; + } + }); + if (!self.arrays_from_load.contains(&array) || is_return_block) + && !array_in_terminator + { + self.instructions_that_can_be_made_mutable.insert(*instruction_id); + } + } + Instruction::Call { arguments, .. } => { + for argument in arguments { + if matches!(self.dfg.type_of_value(*argument), Array { .. } | Slice { .. }) { - instructions_that_can_be_made_mutable.remove(&existing); + let argument = self.dfg.resolve(*argument); + + if let Some(existing) = + self.array_to_last_use.insert(argument, *instruction_id) + { + self.instructions_that_can_be_made_mutable.remove(&existing); + } } } } - } - Instruction::Load { .. } => { - let result = dfg.instruction_results(*instruction_id)[0]; - if matches!(dfg.type_of_value(result), Array { .. } | Slice { .. }) { - arrays_from_load.insert(result); + Instruction::Load { .. } => { + let result = self.dfg.instruction_results(*instruction_id)[0]; + if matches!(self.dfg.type_of_value(result), Array { .. } | Slice { .. }) { + self.arrays_from_load.insert(result); + } } + _ => (), } - _ => (), } } } @@ -162,29 +197,31 @@ mod tests { // from and cloned in a loop. If the array is inadvertently marked mutable, and is cloned in a previous iteration // of the loop, its clone will also be altered. // - // acir(inline) fn main f0 { + // brillig fn main f0 { // b0(): - // v2 = allocate - // store [Field 0, Field 0, Field 0, Field 0, Field 0] at v2 // v3 = allocate - // store [Field 0, Field 0, Field 0, Field 0, Field 0] at v3 + // store [[Field 0, Field 0, Field 0, Field 0, Field 0], [Field 0, Field 0, Field 0, Field 0, Field 0]] at v3 + // v4 = allocate + // store [[Field 0, Field 0, Field 0, Field 0, Field 0], [Field 0, Field 0, Field 0, Field 0, Field 0]] at v4 // jmp b1(u32 0) - // b1(v5: u32): - // v7 = lt v5, u32 5 - // jmpif v7 then: b3, else: b2 + // b1(v6: u32): + // v8 = lt v6, u32 5 + // jmpif v8 then: b3, else: b2 // b3(): - // v8 = eq v5, u32 5 - // jmpif v8 then: b4, else: b5 + // v9 = eq v6, u32 5 + // jmpif v9 then: b4, else: b5 // b4(): - // v9 = load v2 - // store v9 at v3 + // v10 = load v3 + // store v10 at v4 // jmp b5() // b5(): - // v10 = load v2 - // v12 = array_set v10, index v5, value Field 20 - // store v12 at v2 - // v14 = add v5, u32 1 - // jmp b1(v14) + // v11 = load v3 + // v13 = array_get v11, index Field 0 + // v14 = array_set v13, index v6, value Field 20 + // v15 = array_set v11, index v6, value v14 + // store v15 at v3 + // v17 = add v6, u32 1 + // jmp b1(v17) // b2(): // return // } @@ -196,13 +233,16 @@ mod tests { let zero = builder.field_constant(0u128); let array_constant = builder.array_constant(vector![zero, zero, zero, zero, zero], array_type.clone()); + let nested_array_type = Type::Array(Arc::new(vec![array_type.clone()]), 2); + let nested_array_constant = builder + .array_constant(vector![array_constant, array_constant], nested_array_type.clone()); - let v2 = builder.insert_allocate(array_type.clone()); + let v3 = builder.insert_allocate(array_type.clone()); - builder.insert_store(v2, array_constant); + builder.insert_store(v3, nested_array_constant); - let v3 = builder.insert_allocate(array_type.clone()); - builder.insert_store(v3, array_constant); + let v4 = builder.insert_allocate(array_type.clone()); + builder.insert_store(v4, nested_array_constant); let b1 = builder.insert_block(); let zero_u32 = builder.numeric_constant(0u128, Type::unsigned(32)); @@ -212,35 +252,38 @@ mod tests { builder.switch_to_block(b1); let v5 = builder.add_block_parameter(b1, Type::unsigned(32)); let five = builder.numeric_constant(5u128, Type::unsigned(32)); - let v7 = builder.insert_binary(v5, BinaryOp::Lt, five); + let v8 = builder.insert_binary(v5, BinaryOp::Lt, five); let b2 = builder.insert_block(); let b3 = builder.insert_block(); let b4 = builder.insert_block(); let b5 = builder.insert_block(); - builder.terminate_with_jmpif(v7, b3, b2); + builder.terminate_with_jmpif(v8, b3, b2); // Loop body // b3 is the if statement conditional builder.switch_to_block(b3); let two = builder.numeric_constant(5u128, Type::unsigned(32)); - let v8 = builder.insert_binary(v5, BinaryOp::Eq, two); - builder.terminate_with_jmpif(v8, b4, b5); + let v9 = builder.insert_binary(v5, BinaryOp::Eq, two); + builder.terminate_with_jmpif(v9, b4, b5); // b4 is the rest of the loop after the if statement builder.switch_to_block(b4); - let v9 = builder.insert_load(v2, array_type.clone()); - builder.insert_store(v3, v9); + let v10 = builder.insert_load(v3, nested_array_type.clone()); + builder.insert_store(v4, v10); builder.terminate_with_jmp(b5, vec![]); builder.switch_to_block(b5); - let v10 = builder.insert_load(v2, array_type.clone()); + let v11 = builder.insert_load(v3, nested_array_type.clone()); let twenty = builder.field_constant(20u128); - let v12 = builder.insert_array_set(v10, v5, twenty); - builder.insert_store(v2, v12); + let v13 = builder.insert_array_get(v11, zero, array_type.clone()); + let v14 = builder.insert_array_set(v13, v5, twenty); + let v15 = builder.insert_array_set(v11, v5, v14); + + builder.insert_store(v3, v15); let one = builder.numeric_constant(1u128, Type::unsigned(32)); - let v14 = builder.insert_binary(v5, BinaryOp::Add, one); - builder.terminate_with_jmp(b1, vec![v14]); + let v17 = builder.insert_binary(v5, BinaryOp::Add, one); + builder.terminate_with_jmp(b1, vec![v17]); builder.switch_to_block(b2); builder.terminate_with_return(vec![]); @@ -258,7 +301,7 @@ mod tests { .filter(|instruction| matches!(&main.dfg[**instruction], Instruction::ArraySet { .. })) .collect::>(); - assert_eq!(array_set_instructions.len(), 1); + assert_eq!(array_set_instructions.len(), 2); if let Instruction::ArraySet { mutable, .. } = &main.dfg[*array_set_instructions[0]] { // The single array set should not be marked mutable assert!(!mutable); diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/as_slice_length.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/as_slice_length.rs index 69eab1da0ed..59917e8589b 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/as_slice_length.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/as_slice_length.rs @@ -20,13 +20,19 @@ impl Ssa { #[tracing::instrument(level = "trace", skip(self))] pub(crate) fn as_slice_optimization(mut self) -> Self { for func in self.functions.values_mut() { - let known_slice_lengths = known_slice_lengths(func); - replace_known_slice_lengths(func, known_slice_lengths); + func.as_slice_optimization(); } self } } +impl Function { + pub(crate) fn as_slice_optimization(&mut self) { + let known_slice_lengths = known_slice_lengths(self); + replace_known_slice_lengths(self, known_slice_lengths); + } +} + fn known_slice_lengths(func: &Function) -> HashMap { let mut known_slice_lengths = HashMap::default(); for block_id in func.reachable_blocks() { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/assert_constant.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/assert_constant.rs index ae0681a55ff..348c78683a0 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/assert_constant.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/assert_constant.rs @@ -26,22 +26,31 @@ impl Ssa { mut self, ) -> Result { for function in self.functions.values_mut() { - for block in function.reachable_blocks() { - // Unfortunately we can't just use instructions.retain(...) here since - // check_instruction can also return an error - let instructions = function.dfg[block].take_instructions(); - let mut filtered_instructions = Vec::with_capacity(instructions.len()); + function.evaluate_static_assert_and_assert_constant()?; + } + Ok(self) + } +} - for instruction in instructions { - if check_instruction(function, instruction)? { - filtered_instructions.push(instruction); - } - } +impl Function { + pub(crate) fn evaluate_static_assert_and_assert_constant( + &mut self, + ) -> Result<(), RuntimeError> { + for block in self.reachable_blocks() { + // Unfortunately we can't just use instructions.retain(...) here since + // check_instruction can also return an error + let instructions = self.dfg[block].take_instructions(); + let mut filtered_instructions = Vec::with_capacity(instructions.len()); - *function.dfg[block].instructions_mut() = filtered_instructions; + for instruction in instructions { + if check_instruction(self, instruction)? { + filtered_instructions.push(instruction); + } } + + *self.dfg[block].instructions_mut() = filtered_instructions; } - Ok(self) + Ok(()) } } 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 ff9a63c8d79..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 @@ -44,7 +44,7 @@ impl Ssa { #[tracing::instrument(level = "trace", skip(self))] pub(crate) fn fold_constants(mut self) -> Ssa { for function in self.functions.values_mut() { - constant_fold(function, false); + function.constant_fold(false); } self } @@ -57,25 +57,27 @@ impl Ssa { #[tracing::instrument(level = "trace", skip(self))] pub(crate) fn fold_constants_using_constraints(mut self) -> Ssa { for function in self.functions.values_mut() { - constant_fold(function, true); + function.constant_fold(true); } self } } -/// The structure of this pass is simple: -/// Go through each block and re-insert all instructions. -fn constant_fold(function: &mut Function, use_constraint_info: bool) { - let mut context = Context { use_constraint_info, ..Default::default() }; - context.block_queue.push(function.entry_block()); +impl Function { + /// The structure of this pass is simple: + /// Go through each block and re-insert all instructions. + pub(crate) fn constant_fold(&mut self, use_constraint_info: bool) { + let mut context = Context { use_constraint_info, ..Default::default() }; + context.block_queue.push(self.entry_block()); - while let Some(block) = context.block_queue.pop() { - if context.visited_blocks.contains(&block) { - continue; - } + while let Some(block) = context.block_queue.pop() { + if context.visited_blocks.contains(&block) { + continue; + } - context.visited_blocks.insert(block); - context.fold_constants_in_block(function, block); + context.visited_blocks.insert(block); + context.fold_constants_in_block(self, block); + } } } @@ -841,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/die.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/die.rs index 33351405606..beca7c41e5c 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/die.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/die.rs @@ -25,44 +25,46 @@ impl Ssa { #[tracing::instrument(level = "trace", skip(self))] pub(crate) fn dead_instruction_elimination(mut self) -> Ssa { for function in self.functions.values_mut() { - dead_instruction_elimination(function, true); + function.dead_instruction_elimination(true); } self } } -/// Removes any unused instructions in the reachable blocks of the given function. -/// -/// The blocks of the function are iterated in post order, such that any blocks containing -/// instructions that reference results from an instruction in another block are evaluated first. -/// If we did not iterate blocks in this order we could not safely say whether or not the results -/// of its instructions are needed elsewhere. -fn dead_instruction_elimination(function: &mut Function, insert_out_of_bounds_checks: bool) { - let mut context = Context::default(); - for call_data in &function.dfg.data_bus.call_data { - context.mark_used_instruction_results(&function.dfg, call_data.array_id); - } +impl Function { + /// Removes any unused instructions in the reachable blocks of the given function. + /// + /// The blocks of the function are iterated in post order, such that any blocks containing + /// instructions that reference results from an instruction in another block are evaluated first. + /// If we did not iterate blocks in this order we could not safely say whether or not the results + /// of its instructions are needed elsewhere. + pub(crate) fn dead_instruction_elimination(&mut self, insert_out_of_bounds_checks: bool) { + let mut context = Context::default(); + for call_data in &self.dfg.data_bus.call_data { + context.mark_used_instruction_results(&self.dfg, call_data.array_id); + } - let mut inserted_out_of_bounds_checks = false; + let mut inserted_out_of_bounds_checks = false; - let blocks = PostOrder::with_function(function); - for block in blocks.as_slice() { - inserted_out_of_bounds_checks |= context.remove_unused_instructions_in_block( - function, - *block, - insert_out_of_bounds_checks, - ); - } + let blocks = PostOrder::with_function(self); + for block in blocks.as_slice() { + inserted_out_of_bounds_checks |= context.remove_unused_instructions_in_block( + self, + *block, + insert_out_of_bounds_checks, + ); + } - // If we inserted out of bounds check, let's run the pass again with those new - // instructions (we don't want to remove those checks, or instructions that are - // dependencies of those checks) - if inserted_out_of_bounds_checks { - dead_instruction_elimination(function, false); - return; - } + // If we inserted out of bounds check, let's run the pass again with those new + // instructions (we don't want to remove those checks, or instructions that are + // dependencies of those checks) + if inserted_out_of_bounds_checks { + self.dead_instruction_elimination(false); + return; + } - context.remove_rc_instructions(&mut function.dfg); + context.remove_rc_instructions(&mut self.dfg); + } } /// Per function context for tracking unused values and which instructions to remove. @@ -163,45 +165,6 @@ impl Context { false } - fn track_inc_rcs_to_remove( - &self, - instruction_id: InstructionId, - function: &Function, - inc_rcs: &mut HashMap>, - inc_rcs_to_remove: &mut HashSet, - ) { - let instruction = &function.dfg[instruction_id]; - // DIE loops over a block in reverse order, so we insert an RC instruction for possible removal - // when we see a DecrementRc and check whether it was possibly mutated when we see an IncrementRc. - match instruction { - Instruction::IncrementRc { value } => { - if let Some(inc_rc) = pop_rc_for(*value, function, inc_rcs) { - if !inc_rc.possibly_mutated { - inc_rcs_to_remove.insert(inc_rc.id); - inc_rcs_to_remove.insert(instruction_id); - } - } - } - Instruction::DecrementRc { value } => { - let typ = function.dfg.type_of_value(*value); - - // We assume arrays aren't mutated until we find an array_set - let inc_rc = - RcInstruction { id: instruction_id, array: *value, possibly_mutated: false }; - inc_rcs.entry(typ).or_default().push(inc_rc); - } - Instruction::ArraySet { array, .. } => { - let typ = function.dfg.type_of_value(*array); - if let Some(inc_rcs) = inc_rcs.get_mut(&typ) { - for inc_rc in inc_rcs { - inc_rc.possibly_mutated = true; - } - } - } - _ => {} - } - } - /// Returns true if an instruction can be removed. /// /// An instruction can be removed as long as it has no side-effects, and none of its result 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/mem2reg.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs index 68c04e3b4b4..165e0e36b71 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs @@ -123,16 +123,22 @@ impl Ssa { #[tracing::instrument(level = "trace", skip(self))] pub(crate) fn mem2reg(mut self) -> Ssa { for function in self.functions.values_mut() { - let mut context = PerFunctionContext::new(function); - context.mem2reg(); - context.remove_instructions(); - context.update_data_bus(); + function.mem2reg(); } self } } +impl Function { + pub(crate) fn mem2reg(&mut self) { + let mut context = PerFunctionContext::new(self); + context.mem2reg(); + context.remove_instructions(); + context.update_data_bus(); + } +} + struct PerFunctionContext<'f> { cfg: ControlFlowGraph, post_order: PostOrder, 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/rc.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/rc.rs index 06025fd9e8b..c879f6c8fff 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/rc.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/rc.rs @@ -22,7 +22,7 @@ impl Ssa { #[tracing::instrument(level = "trace", skip(self))] pub(crate) fn remove_paired_rc(mut self) -> Ssa { for function in self.functions.values_mut() { - remove_paired_rc(function); + function.remove_paired_rc(); } self } @@ -44,26 +44,28 @@ pub(crate) struct RcInstruction { pub(crate) possibly_mutated: bool, } -/// This function is very simplistic for now. It takes advantage of the fact that dec_rc -/// instructions are currently issued only at the end of a function for parameters and will -/// only check the first and last block for inc & dec rc instructions to be removed. The rest -/// of the function is still checked for array_set instructions. -/// -/// This restriction lets this function largely ignore merging intermediate results from other -/// blocks and handling loops. -fn remove_paired_rc(function: &mut Function) { - // `dec_rc` is only issued for parameters currently so we can speed things - // up a bit by skipping any functions without them. - if !contains_array_parameter(function) { - return; - } +impl Function { + /// This function is very simplistic for now. It takes advantage of the fact that dec_rc + /// instructions are currently issued only at the end of a function for parameters and will + /// only check the first and last block for inc & dec rc instructions to be removed. The rest + /// of the function is still checked for array_set instructions. + /// + /// This restriction lets this function largely ignore merging intermediate results from other + /// blocks and handling loops. + pub(crate) fn remove_paired_rc(&mut self) { + // `dec_rc` is only issued for parameters currently so we can speed things + // up a bit by skipping any functions without them. + if !contains_array_parameter(self) { + return; + } - let mut context = Context::default(); + let mut context = Context::default(); - context.find_rcs_in_entry_block(function); - context.scan_for_array_sets(function); - let to_remove = context.find_rcs_to_remove(function); - remove_instructions(to_remove, function); + context.find_rcs_in_entry_block(self); + context.scan_for_array_sets(self); + let to_remove = context.find_rcs_to_remove(self); + remove_instructions(to_remove, self); + } } fn contains_array_parameter(function: &mut Function) -> bool { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs index 984c0de04ca..4b2d753f072 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs @@ -21,24 +21,30 @@ impl Ssa { #[tracing::instrument(level = "trace", skip(self))] pub(crate) fn remove_bit_shifts(mut self) -> Ssa { for function in self.functions.values_mut() { - remove_bit_shifts(function); + function.remove_bit_shifts(); } self } } -/// The structure of this pass is simple: -/// Go through each block and re-insert all instructions. -fn remove_bit_shifts(function: &mut Function) { - if let RuntimeType::Brillig = function.runtime() { - return; - } +impl Function { + /// The structure of this pass is simple: + /// Go through each block and re-insert all instructions. + pub(crate) fn remove_bit_shifts(&mut self) { + if let RuntimeType::Brillig = self.runtime() { + return; + } - let block = function.entry_block(); - let mut context = - Context { function, new_instructions: Vec::new(), block, call_stack: CallStack::default() }; + let block = self.entry_block(); + let mut context = Context { + function: self, + new_instructions: Vec::new(), + block, + call_stack: CallStack::default(), + }; - context.remove_bit_shifts(); + context.remove_bit_shifts(); + } } struct Context<'f> { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs index a56786b2603..daae2cb08ce 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs @@ -16,7 +16,7 @@ use crate::ssa::{ ir::{ basic_block::BasicBlockId, dfg::DataFlowGraph, - function::Function, + function::{Function, RuntimeType}, instruction::{BinaryOp, Instruction, Intrinsic}, types::Type, value::Value, @@ -29,23 +29,30 @@ impl Ssa { #[tracing::instrument(level = "trace", skip(self))] pub(crate) fn remove_enable_side_effects(mut self) -> Ssa { for function in self.functions.values_mut() { - remove_enable_side_effects(function); + function.remove_enable_side_effects(); } self } } -fn remove_enable_side_effects(function: &mut Function) { - let mut context = Context::default(); - context.block_queue.push(function.entry_block()); - - while let Some(block) = context.block_queue.pop() { - if context.visited_blocks.contains(&block) { - continue; +impl Function { + pub(crate) fn remove_enable_side_effects(&mut self) { + if matches!(self.runtime(), RuntimeType::Brillig) { + // Brillig functions do not make use of the `EnableSideEffects` instruction so are unaffected by this pass. + return; } - context.visited_blocks.insert(block); - context.remove_enable_side_effects_in_block(function, block); + let mut context = Context::default(); + context.block_queue.push(self.entry_block()); + + while let Some(block) = context.block_queue.pop() { + if context.visited_blocks.contains(&block) { + continue; + } + + context.visited_blocks.insert(block); + context.remove_enable_side_effects_in_block(self, block); + } } } 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 8d6225afabf..299669b9564 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 @@ -28,17 +28,23 @@ impl Ssa { #[tracing::instrument(level = "trace", skip(self))] pub(crate) fn remove_if_else(mut self) -> Ssa { for function in self.functions.values_mut() { - // This should match the check in flatten_cfg - if let crate::ssa::ir::function::RuntimeType::Brillig = function.runtime() { - continue; - } - - Context::default().remove_if_else(function); + function.remove_if_else(); } self } } +impl Function { + pub(crate) fn remove_if_else(&mut self) { + // This should match the check in flatten_cfg + if let crate::ssa::ir::function::RuntimeType::Brillig = self.runtime() { + // skip + } else { + Context::default().remove_if_else(self); + } + } +} + #[derive(Default)] struct Context { slice_sizes: HashMap, @@ -112,7 +118,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_evaluator/src/ssa/opt/resolve_is_unconstrained.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/resolve_is_unconstrained.rs index 2c9e33ae528..1768cbddec3 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/resolve_is_unconstrained.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/resolve_is_unconstrained.rs @@ -17,40 +17,42 @@ impl Ssa { #[tracing::instrument(level = "trace", skip(self))] pub(crate) fn resolve_is_unconstrained(mut self) -> Self { for func in self.functions.values_mut() { - replace_is_unconstrained_result(func); + func.replace_is_unconstrained_result(); } self } } -fn replace_is_unconstrained_result(func: &mut Function) { - let mut is_unconstrained_calls = HashSet::default(); - // Collect all calls to is_unconstrained - for block_id in func.reachable_blocks() { - for &instruction_id in func.dfg[block_id].instructions() { - let target_func = match &func.dfg[instruction_id] { - Instruction::Call { func, .. } => *func, - _ => continue, - }; +impl Function { + pub(crate) fn replace_is_unconstrained_result(&mut self) { + let mut is_unconstrained_calls = HashSet::default(); + // Collect all calls to is_unconstrained + for block_id in self.reachable_blocks() { + for &instruction_id in self.dfg[block_id].instructions() { + let target_func = match &self.dfg[instruction_id] { + Instruction::Call { func, .. } => *func, + _ => continue, + }; - if let Value::Intrinsic(Intrinsic::IsUnconstrained) = &func.dfg[target_func] { - is_unconstrained_calls.insert(instruction_id); + if let Value::Intrinsic(Intrinsic::IsUnconstrained) = &self.dfg[target_func] { + is_unconstrained_calls.insert(instruction_id); + } } } - } - for instruction_id in is_unconstrained_calls { - let call_returns = func.dfg.instruction_results(instruction_id); - let original_return_id = call_returns[0]; + for instruction_id in is_unconstrained_calls { + let call_returns = self.dfg.instruction_results(instruction_id); + let original_return_id = call_returns[0]; - // We replace the result with a fresh id. This will be unused, so the DIE pass will remove the leftover intrinsic call. - func.dfg.replace_result(instruction_id, original_return_id); + // We replace the result with a fresh id. This will be unused, so the DIE pass will remove the leftover intrinsic call. + self.dfg.replace_result(instruction_id, original_return_id); - let is_within_unconstrained = func.dfg.make_constant( - FieldElement::from(matches!(func.runtime(), RuntimeType::Brillig)), - Type::bool(), - ); - // Replace all uses of the original return value with the constant - func.dfg.set_value_from_id(original_return_id, is_within_unconstrained); + let is_within_unconstrained = self.dfg.make_constant( + FieldElement::from(matches!(self.runtime(), RuntimeType::Brillig)), + Type::bool(), + ); + // Replace all uses of the original return value with the constant + self.dfg.set_value_from_id(original_return_id, is_within_unconstrained); + } } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/simplify_cfg.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/simplify_cfg.rs index 6887873dbc3..46941775c5e 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/simplify_cfg.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/simplify_cfg.rs @@ -36,48 +36,50 @@ impl Ssa { #[tracing::instrument(level = "trace", skip(self))] pub(crate) fn simplify_cfg(mut self) -> Self { for function in self.functions.values_mut() { - simplify_function(function); + function.simplify_function(); } self } } -/// Simplify a function's cfg by going through each block to check for any simple blocks that can -/// be inlined into their predecessor. -fn simplify_function(function: &mut Function) { - let mut cfg = ControlFlowGraph::with_function(function); - let mut stack = vec![function.entry_block()]; - let mut visited = HashSet::new(); - - while let Some(block) = stack.pop() { - if visited.insert(block) { - stack.extend(function.dfg[block].successors().filter(|block| !visited.contains(block))); - } - - // This call is before try_inline_into_predecessor so that if it succeeds in changing a - // jmpif into a jmp, the block may then be inlined entirely into its predecessor in try_inline_into_predecessor. - check_for_constant_jmpif(function, block, &mut cfg); - - let mut predecessors = cfg.predecessors(block); - - if predecessors.len() == 1 { - let predecessor = predecessors.next().expect("Already checked length of predecessors"); - drop(predecessors); - - // If the block has only 1 predecessor, we can safely remove its block parameters - remove_block_parameters(function, block, predecessor); - - // Note: this function relies on `remove_block_parameters` being called first. - // Otherwise the inlined block will refer to parameters that no longer exist. - // - // If successful, `block` will be empty and unreachable after this call, so any - // optimizations performed after this point on the same block should check if - // the inlining here was successful before continuing. - try_inline_into_predecessor(function, &mut cfg, block, predecessor); - } else { - drop(predecessors); +impl Function { + /// Simplify a function's cfg by going through each block to check for any simple blocks that can + /// be inlined into their predecessor. + pub(crate) fn simplify_function(&mut self) { + let mut cfg = ControlFlowGraph::with_function(self); + let mut stack = vec![self.entry_block()]; + let mut visited = HashSet::new(); + + while let Some(block) = stack.pop() { + if visited.insert(block) { + stack.extend(self.dfg[block].successors().filter(|block| !visited.contains(block))); + } - check_for_double_jmp(function, block, &mut cfg); + // This call is before try_inline_into_predecessor so that if it succeeds in changing a + // jmpif into a jmp, the block may then be inlined entirely into its predecessor in try_inline_into_predecessor. + check_for_constant_jmpif(self, block, &mut cfg); + + let mut predecessors = cfg.predecessors(block); + if predecessors.len() == 1 { + let predecessor = + predecessors.next().expect("Already checked length of predecessors"); + drop(predecessors); + + // If the block has only 1 predecessor, we can safely remove its block parameters + remove_block_parameters(self, block, predecessor); + + // Note: this function relies on `remove_block_parameters` being called first. + // Otherwise the inlined block will refer to parameters that no longer exist. + // + // If successful, `block` will be empty and unreachable after this call, so any + // optimizations performed after this point on the same block should check if + // the inlining here was successful before continuing. + try_inline_into_predecessor(self, &mut cfg, block, predecessor); + } else { + drop(predecessors); + + check_for_double_jmp(self, block, &mut cfg); + } } } } @@ -96,14 +98,23 @@ fn check_for_constant_jmpif( }) = function.dfg[block].terminator() { if let Some(constant) = function.dfg.get_numeric_constant(*condition) { - let destination = - if constant.is_zero() { *else_destination } else { *then_destination }; + let (destination, unchosen_destination) = if constant.is_zero() { + (*else_destination, *then_destination) + } else { + (*then_destination, *else_destination) + }; let arguments = Vec::new(); let call_stack = call_stack.clone(); let jmp = TerminatorInstruction::Jmp { destination, arguments, call_stack }; function.dfg[block].set_terminator(jmp); cfg.recompute_block(function, block); + + // If `block` was the only predecessor to `unchosen_destination` then it's no long reachable through the CFG, + // we can then invalidate it successors as it's an invalid predecessor. + if cfg.predecessors(unchosen_destination).len() == 0 { + cfg.invalidate_block_successors(unchosen_destination); + } } } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/unrolling.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/unrolling.rs index f3e11e04e3a..d6ed11ddf0e 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/unrolling.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/unrolling.rs @@ -74,16 +74,49 @@ impl Ssa { pub(crate) fn try_to_unroll_loops(mut self) -> (Ssa, Vec) { let mut errors = vec![]; for function in self.functions.values_mut() { - // Loop unrolling in brillig can lead to a code explosion currently. This can - // also be true for ACIR, but we have no alternative to unrolling in ACIR. - // Brillig also generally prefers smaller code rather than faster code. - if function.runtime() == RuntimeType::Brillig { - continue; + function.try_to_unroll_loops(&mut errors); + } + (self, errors) + } +} + +impl Function { + // TODO(https://github.com/noir-lang/noir/issues/6192): are both this and + // TODO: Ssa::unroll_loops_iteratively needed? Likely able to be combined + pub(crate) fn unroll_loops_iteratively(&mut self) -> Result<(), RuntimeError> { + // Try to unroll loops first: + let mut unroll_errors = vec![]; + self.try_to_unroll_loops(&mut unroll_errors); + + // Keep unrolling until no more errors are found + while !unroll_errors.is_empty() { + let prev_unroll_err_count = unroll_errors.len(); + + // Simplify the SSA before retrying + + // Do a mem2reg after the last unroll to aid simplify_cfg + self.mem2reg(); + self.simplify_function(); + // Do another mem2reg after simplify_cfg to aid the next unroll + self.mem2reg(); + + // Unroll again + self.try_to_unroll_loops(&mut unroll_errors); + // If we didn't manage to unroll any more loops, exit + if unroll_errors.len() >= prev_unroll_err_count { + return Err(unroll_errors.swap_remove(0)); } + } + Ok(()) + } - errors.extend(find_all_loops(function).unroll_each_loop(function)); + pub(crate) fn try_to_unroll_loops(&mut self, errors: &mut Vec) { + // Loop unrolling in brillig can lead to a code explosion currently. This can + // also be true for ACIR, but we have no alternative to unrolling in ACIR. + // Brillig also generally prefers smaller code rather than faster code. + if self.runtime() != RuntimeType::Brillig { + errors.extend(find_all_loops(self).unroll_each_loop(self)); } - (self, errors) } } diff --git a/noir/noir-repo/compiler/noirc_frontend/Cargo.toml b/noir/noir-repo/compiler/noirc_frontend/Cargo.toml index 510cff08dec..d729dabcb04 100644 --- a/noir/noir-repo/compiler/noirc_frontend/Cargo.toml +++ b/noir/noir-repo/compiler/noirc_frontend/Cargo.toml @@ -16,7 +16,6 @@ noirc_errors.workspace = true noirc_printable_type.workspace = true fm.workspace = true iter-extended.workspace = true -chumsky.workspace = true thiserror.workspace = true smol_str.workspace = true im.workspace = true diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs index 7b0a6d028de..64edae8322f 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs @@ -1,13 +1,15 @@ use std::borrow::Cow; use std::fmt::Display; +use thiserror::Error; + use crate::ast::{ Ident, ItemVisibility, Path, Pattern, Recoverable, Statement, StatementKind, UnresolvedTraitConstraint, UnresolvedType, UnresolvedTypeData, Visibility, }; -use crate::hir::def_collector::errors::DefCollectorErrorKind; -use crate::macros_api::StructId; -use crate::node_interner::{ExprId, InternedExpressionKind, InternedStatementKind, QuotedTypeId}; +use crate::node_interner::{ + ExprId, InternedExpressionKind, InternedStatementKind, QuotedTypeId, StructId, +}; use crate::token::{Attributes, FunctionAttribute, Token, Tokens}; use crate::{Kind, Type}; use acvm::{acir::AcirField, FieldElement}; @@ -75,6 +77,13 @@ pub enum UnresolvedGeneric { Resolved(QuotedTypeId, Span), } +#[derive(Error, PartialEq, Eq, Debug, Clone)] +#[error("The only supported types of numeric generics are integers, fields, and booleans")] +pub struct UnsupportedNumericGenericType { + pub ident: Ident, + pub typ: UnresolvedTypeData, +} + impl UnresolvedGeneric { pub fn span(&self) -> Span { match self { @@ -84,7 +93,7 @@ impl UnresolvedGeneric { } } - pub fn kind(&self) -> Result { + pub fn kind(&self) -> Result { match self { UnresolvedGeneric::Variable(_) => Ok(Kind::Normal), UnresolvedGeneric::Numeric { typ, .. } => { @@ -100,14 +109,14 @@ impl UnresolvedGeneric { fn resolve_numeric_kind_type( &self, typ: &UnresolvedType, - ) -> Result { + ) -> Result { use crate::ast::UnresolvedTypeData::{FieldElement, Integer}; match typ.typ { FieldElement => Ok(Type::FieldElement), Integer(sign, bits) => Ok(Type::Integer(sign, bits)), // Only fields and integers are supported for numeric kinds - _ => Err(DefCollectorErrorKind::UnsupportedNumericGenericType { + _ => Err(UnsupportedNumericGenericType { ident: self.ident().clone(), typ: typ.typ.clone(), }), @@ -300,6 +309,7 @@ impl Expression { pub type BinaryOp = Spanned; #[derive(PartialEq, PartialOrd, Eq, Ord, Hash, Debug, Copy, Clone)] +#[cfg_attr(test, derive(strum_macros::EnumIter))] pub enum BinaryOpKind { Add, Subtract, @@ -864,7 +874,7 @@ impl FunctionDefinition { impl Display for FunctionDefinition { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { writeln!(f, "{:?}", self.attributes)?; - write!(f, "fn {} {}", self.signature(), self.body) + write!(f, "{} {}", self.signature(), self.body) } } 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/statement.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs index 4abea8cebb4..441eff99d9e 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs @@ -7,15 +7,18 @@ use iter_extended::vecmap; use noirc_errors::{Span, Spanned}; use super::{ - BlockExpression, ConstructorExpression, Expression, ExpressionKind, GenericTypeArgs, - IndexExpression, ItemVisibility, MemberAccessExpression, MethodCallExpression, UnresolvedType, + BinaryOpKind, BlockExpression, ConstructorExpression, Expression, ExpressionKind, + GenericTypeArgs, IndexExpression, InfixExpression, ItemVisibility, MemberAccessExpression, + MethodCallExpression, UnresolvedType, }; +use crate::ast::UnresolvedTypeData; use crate::elaborator::types::SELF_TYPE_NAME; use crate::lexer::token::SpannedToken; -use crate::macros_api::{NodeInterner, SecondaryAttribute, UnresolvedTypeData}; -use crate::node_interner::{InternedExpressionKind, InternedPattern, InternedStatementKind}; +use crate::node_interner::{ + InternedExpressionKind, InternedPattern, InternedStatementKind, NodeInterner, +}; use crate::parser::{ParserError, ParserErrorReason}; -use crate::token::Token; +use crate::token::{SecondaryAttribute, Token}; /// This is used when an identifier fails to parse in the parser. /// Instead of failing the parse, we can often recover using this @@ -177,7 +180,7 @@ impl StatementKind { } } -#[derive(Eq, Debug, Clone)] +#[derive(Eq, Debug, Clone, Default)] pub struct Ident(pub Spanned); impl Ident { @@ -330,12 +333,12 @@ impl Display for UseTree { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.prefix)?; + if !self.prefix.segments.is_empty() { + write!(f, "::")?; + } + match &self.kind { UseTreeKind::Path(name, alias) => { - if !(self.prefix.segments.is_empty() && self.prefix.kind == PathKind::Plain) { - write!(f, "::")?; - } - write!(f, "{name}")?; if let Some(alias) = alias { @@ -345,7 +348,7 @@ impl Display for UseTree { Ok(()) } UseTreeKind::List(trees) => { - write!(f, "::{{")?; + write!(f, "{{")?; let tree = vecmap(trees, ToString::to_string).join(", "); write!(f, "{tree}}}") } @@ -464,7 +467,9 @@ impl Path { } pub fn is_ident(&self) -> bool { - self.segments.len() == 1 && self.kind == PathKind::Plain + self.kind == PathKind::Plain + && self.segments.len() == 1 + && self.segments.first().unwrap().generics.is_none() } pub fn as_ident(&self) -> Option<&Ident> { @@ -481,6 +486,10 @@ impl Path { self.segments.first().cloned().map(|segment| segment.ident) } + pub fn is_empty(&self) -> bool { + self.segments.is_empty() && self.kind == PathKind::Plain + } + pub fn as_string(&self) -> String { let mut string = String::new(); @@ -647,14 +656,6 @@ impl Pattern { } } - pub(crate) fn into_ident(self) -> Ident { - match self { - Pattern::Identifier(ident) => ident, - Pattern::Mutable(pattern, _, _) => pattern.into_ident(), - other => panic!("Pattern::into_ident called on {other} pattern with no identifier"), - } - } - pub(crate) fn try_as_expression(&self, interner: &NodeInterner) -> Option { match self { Pattern::Identifier(ident) => Some(Expression { @@ -723,37 +724,36 @@ impl LValue { Expression::new(kind, span) } - pub fn from_expression(expr: Expression) -> LValue { + pub fn from_expression(expr: Expression) -> Option { LValue::from_expression_kind(expr.kind, expr.span) } - pub fn from_expression_kind(expr: ExpressionKind, span: Span) -> LValue { + pub fn from_expression_kind(expr: ExpressionKind, span: Span) -> Option { match expr { - ExpressionKind::Variable(path) => LValue::Ident(path.as_ident().unwrap().clone()), - ExpressionKind::MemberAccess(member_access) => LValue::MemberAccess { - object: Box::new(LValue::from_expression(member_access.lhs)), + ExpressionKind::Variable(path) => Some(LValue::Ident(path.as_ident().unwrap().clone())), + ExpressionKind::MemberAccess(member_access) => Some(LValue::MemberAccess { + object: Box::new(LValue::from_expression(member_access.lhs)?), field_name: member_access.rhs, span, - }, - ExpressionKind::Index(index) => LValue::Index { - array: Box::new(LValue::from_expression(index.collection)), + }), + ExpressionKind::Index(index) => Some(LValue::Index { + array: Box::new(LValue::from_expression(index.collection)?), index: index.index, span, - }, + }), ExpressionKind::Prefix(prefix) => { if matches!( prefix.operator, crate::ast::UnaryOp::Dereference { implicitly_added: false } ) { - LValue::Dereference(Box::new(LValue::from_expression(prefix.rhs)), span) + Some(LValue::Dereference(Box::new(LValue::from_expression(prefix.rhs)?), span)) } else { - panic!("Called LValue::from_expression with an invalid prefix operator") + None } } - ExpressionKind::Interned(id) => LValue::Interned(id, span), - _ => { - panic!("Called LValue::from_expression with an invalid expression") - } + ExpressionKind::Parenthesized(expr) => LValue::from_expression(*expr), + ExpressionKind::Interned(id) => Some(LValue::Interned(id, span)), + _ => None, } } @@ -768,13 +768,57 @@ impl LValue { } } +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct ForBounds { + pub start: Expression, + pub end: Expression, + pub inclusive: bool, +} + +impl ForBounds { + /// Create a half-open range bounded inclusively below and exclusively above (`start..end`), + /// desugaring `start..=end` into `start..end+1` if necessary. + /// + /// Returns the `start` and `end` expressions. + pub(crate) fn into_half_open(self) -> (Expression, Expression) { + let end = if self.inclusive { + let end_span = self.end.span; + let end = ExpressionKind::Infix(Box::new(InfixExpression { + lhs: self.end, + operator: Spanned::from(end_span, BinaryOpKind::Add), + rhs: Expression::new(ExpressionKind::integer(FieldElement::from(1u32)), end_span), + })); + Expression::new(end, end_span) + } else { + self.end + }; + + (self.start, end) + } +} + #[derive(Debug, PartialEq, Eq, Clone)] pub enum ForRange { - Range(/*start:*/ Expression, /*end:*/ Expression), + Range(ForBounds), Array(Expression), } impl ForRange { + /// Create a half-open range, bounded inclusively below and exclusively above. + pub fn range(start: Expression, end: Expression) -> Self { + Self::Range(ForBounds { start, end, inclusive: false }) + } + + /// Create a range bounded inclusively below and above. + pub fn range_inclusive(start: Expression, end: Expression) -> Self { + Self::Range(ForBounds { start, end, inclusive: true }) + } + + /// Create a range over some array. + pub fn array(value: Expression) -> Self { + Self::Array(value) + } + /// Create a 'for' expression taking care of desugaring a 'for e in array' loop /// into the following if needed: /// @@ -877,7 +921,7 @@ impl ForRange { let for_loop = Statement { kind: StatementKind::For(ForLoopStatement { identifier: fresh_identifier, - range: ForRange::Range(start_range, end_range), + range: ForRange::range(start_range, end_range), block: new_block, span: for_loop_span, }), @@ -1007,7 +1051,14 @@ impl Display for Pattern { impl Display for ForLoopStatement { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let range = match &self.range { - ForRange::Range(start, end) => format!("{start}..{end}"), + ForRange::Range(bounds) => { + format!( + "{}{}{}", + bounds.start, + if bounds.inclusive { "..=" } else { ".." }, + bounds.end + ) + } ForRange::Array(expr) => expr.to_string(), }; 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 3df9939dc70..723df775b1e 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/traits.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/traits.rs @@ -7,8 +7,8 @@ use crate::ast::{ BlockExpression, Expression, FunctionReturnType, Ident, NoirFunction, Path, UnresolvedGenerics, UnresolvedType, }; -use crate::macros_api::SecondaryAttribute; use crate::node_interner::TraitId; +use crate::token::SecondaryAttribute; use super::{Documented, GenericTypeArgs, ItemVisibility}; @@ -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(); @@ -216,7 +222,24 @@ impl Display for TraitBound { impl Display for NoirTraitImpl { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - writeln!(f, "impl {}{} for {} {{", self.trait_name, self.trait_generics, self.object_type)?; + write!(f, "impl")?; + if !self.impl_generics.is_empty() { + write!( + f, + "<{}>", + self.impl_generics.iter().map(ToString::to_string).collect::>().join(", ") + )?; + } + + write!(f, " {}{} for {}", self.trait_name, self.trait_generics, self.object_type)?; + if !self.where_clause.is_empty() { + write!( + f, + " where {}", + self.where_clause.iter().map(ToString::to_string).collect::>().join(", ") + )?; + } + 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 ed4d17cadd8..632c5656137 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/visitor.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/visitor.rs @@ -21,9 +21,9 @@ use crate::{ }; use super::{ - FunctionReturnType, GenericTypeArgs, IntegerBitSize, ItemVisibility, Pattern, Signedness, - TraitImplItemKind, TypePath, UnresolvedGenerics, UnresolvedTraitConstraint, UnresolvedType, - UnresolvedTypeData, UnresolvedTypeExpression, + ForBounds, FunctionReturnType, GenericTypeArgs, IntegerBitSize, ItemVisibility, Pattern, + Signedness, TraitImplItemKind, TypePath, UnresolvedGenerics, UnresolvedTraitConstraint, + UnresolvedType, UnresolvedTypeData, UnresolvedTypeExpression, }; #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -1192,7 +1192,7 @@ impl ForRange { pub fn accept_children(&self, visitor: &mut impl Visitor) { match self { - ForRange::Range(start, end) => { + ForRange::Range(ForBounds { start, end, inclusive: _ }) => { start.accept(visitor); end.accept(visitor); } @@ -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/debug/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/debug/mod.rs index 66de265f869..fed3149118b 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/debug/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/debug/mod.rs @@ -1,5 +1,6 @@ use crate::ast::PathSegment; -use crate::parser::{parse_program, ParsedModule}; +use crate::parse_program; +use crate::parser::ParsedModule; use crate::{ ast, ast::{Path, PathKind}, 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 560be895628..65cb6072c62 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/comptime.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/comptime.rs @@ -1,12 +1,11 @@ use std::{collections::BTreeMap, fmt::Display}; -use chumsky::Parser; use fm::FileId; use iter_extended::vecmap; use noirc_errors::{Location, Span}; use crate::{ - ast::Documented, + ast::{Documented, Expression, ExpressionKind}, hir::{ comptime::{Interpreter, InterpreterError, Value}, def_collector::{ @@ -19,13 +18,11 @@ use crate::{ def_map::{LocalModuleId, ModuleId}, resolution::errors::ResolverError, }, - hir_def::expr::HirIdent, + hir_def::expr::{HirExpression, HirIdent}, lexer::Lexer, - macros_api::{ - Expression, ExpressionKind, HirExpression, NodeInterner, SecondaryAttribute, StructId, - }, - node_interner::{DefinitionKind, DependencyId, FuncId, TraitId}, - parser::{self, TopLevelStatement, TopLevelStatementKind}, + node_interner::{DefinitionKind, DependencyId, FuncId, NodeInterner, StructId, TraitId}, + parser::{Item, ItemKind, Parser}, + token::SecondaryAttribute, Type, TypeBindings, UnificationError, }; @@ -161,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, @@ -191,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 { @@ -263,9 +280,10 @@ impl<'context> Elaborator<'context> { return Err((lexing_errors.swap_remove(0).into(), location.file)); } - let expression = parser::expression() - .parse(tokens) - .map_err(|mut errors| (errors.swap_remove(0).into(), location.file))?; + let Some(expression) = Parser::for_tokens(tokens).parse_option(Parser::parse_expression) + else { + return Ok(None); + }; let (mut func, mut arguments) = match expression.kind { ExpressionKind::Call(call) => (*call.func, call.arguments), @@ -372,7 +390,7 @@ impl<'context> Elaborator<'context> { fn add_items( &mut self, - items: Vec, + items: Vec, generated_items: &mut CollectedItems, location: Location, ) { @@ -383,12 +401,12 @@ impl<'context> Elaborator<'context> { pub(crate) fn add_item( &mut self, - item: TopLevelStatement, + item: Item, generated_items: &mut CollectedItems, location: Location, ) { match item.kind { - TopLevelStatementKind::Function(function) => { + ItemKind::Function(function) => { let module_id = self.module_id(); if let Some(id) = dc_mod::collect_function( @@ -409,7 +427,7 @@ impl<'context> Elaborator<'context> { }); } } - TopLevelStatementKind::TraitImpl(mut trait_impl) => { + ItemKind::TraitImpl(mut trait_impl) => { let (methods, associated_types, associated_constants) = dc_mod::collect_trait_impl_items( self.interner, @@ -439,7 +457,7 @@ impl<'context> Elaborator<'context> { resolved_trait_generics: Vec::new(), }); } - TopLevelStatementKind::Global(global, visibility) => { + ItemKind::Global(global, visibility) => { let (global, error) = dc_mod::collect_global( self.interner, self.def_maps.get_mut(&self.crate_id).unwrap(), @@ -455,7 +473,7 @@ impl<'context> Elaborator<'context> { self.errors.push(error); } } - TopLevelStatementKind::Struct(struct_def) => { + ItemKind::Struct(struct_def) => { if let Some((type_id, the_struct)) = dc_mod::collect_struct( self.interner, self.def_maps.get_mut(&self.crate_id).unwrap(), @@ -468,20 +486,17 @@ impl<'context> Elaborator<'context> { generated_items.types.insert(type_id, the_struct); } } - TopLevelStatementKind::Impl(r#impl) => { + ItemKind::Impl(r#impl) => { let module = self.module_id(); dc_mod::collect_impl(self.interner, generated_items, r#impl, self.file, module); } - // Assume that an error has already been issued - TopLevelStatementKind::Error => (), - - TopLevelStatementKind::Module(_) - | TopLevelStatementKind::Import(..) - | TopLevelStatementKind::Trait(_) - | TopLevelStatementKind::TypeAlias(_) - | TopLevelStatementKind::SubModule(_) - | TopLevelStatementKind::InnerAttribute(_) => { + ItemKind::ModuleDecl(_) + | ItemKind::Import(..) + | ItemKind::Trait(_) + | ItemKind::TypeAlias(_) + | ItemKind::Submodules(_) + | ItemKind::InnerAttribute(_) => { let item = item.kind.to_string(); let error = InterpreterError::UnsupportedTopLevelItemUnquote { item, location }; self.errors.push(error.into_compilation_error_pair()); 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 46a22bb232f..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; @@ -5,27 +6,27 @@ use rustc_hash::FxHashSet as HashSet; use crate::{ ast::{ - ArrayLiteral, ConstructorExpression, IfExpression, InfixExpression, Lambda, UnaryOp, - UnresolvedTypeData, UnresolvedTypeExpression, + ArrayLiteral, BlockExpression, CallExpression, CastExpression, ConstructorExpression, + 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::{ expr::{ HirArrayLiteral, HirBinaryOp, HirBlockExpression, HirCallExpression, HirCastExpression, HirConstructorExpression, HirExpression, HirIfExpression, HirIndexExpression, - HirInfixExpression, HirLambda, HirMemberAccess, HirMethodCallExpression, + HirInfixExpression, HirLambda, HirLiteral, HirMemberAccess, HirMethodCallExpression, HirPrefixExpression, }, - traits::TraitConstraint, - }, - macros_api::{ - BlockExpression, CallExpression, CastExpression, Expression, ExpressionKind, HirLiteral, - HirStatement, Ident, IndexExpression, Literal, MemberAccessExpression, - MethodCallExpression, PrefixExpression, StatementKind, + stmt::HirStatement, + traits::{ResolvedTraitBound, TraitConstraint}, }, node_interner::{DefinitionKind, ExprId, FuncId, InternedStatementKind, TraitMethodId}, token::Tokens, @@ -163,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), @@ -205,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 } => { @@ -213,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); @@ -269,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) } @@ -450,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? @@ -488,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, @@ -550,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 { @@ -578,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)> { @@ -590,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); @@ -620,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), ); } @@ -667,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) } @@ -715,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/lints.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/lints.rs index 8253921d305..c0a18d219b7 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/lints.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/lints.rs @@ -1,14 +1,15 @@ use crate::{ - ast::{FunctionKind, Ident}, + ast::{FunctionKind, Ident, NoirFunction, Signedness, UnaryOp, Visibility}, graph::CrateId, hir::{ resolution::errors::{PubPosition, ResolverError}, type_check::TypeCheckError, }, - hir_def::{expr::HirIdent, function::FuncMeta}, - macros_api::{ - HirExpression, HirLiteral, NodeInterner, NoirFunction, Signedness, UnaryOp, Visibility, + hir_def::{ + expr::{HirExpression, HirIdent, HirLiteral}, + function::FuncMeta, }, + node_interner::NodeInterner, node_interner::{DefinitionKind, ExprId, FuncId, FunctionModifiers}, Type, }; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs index c5e457c405b..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,56 +3,42 @@ use std::{ rc::Rc, }; +use crate::{ast::ItemVisibility, hir_def::traits::ResolvedTraitBound, StructField, TypeBindings}; use crate::{ - ast::{FunctionKind, GenericTypeArgs, UnresolvedTraitConstraint}, + ast::{ + BlockExpression, FunctionKind, GenericTypeArgs, Ident, NoirFunction, NoirStruct, Param, + Path, Pattern, TraitBound, UnresolvedGeneric, UnresolvedGenerics, + UnresolvedTraitConstraint, UnresolvedTypeData, UnsupportedNumericGenericType, + }, + graph::CrateId, hir::{ def_collector::dc_crate::{ - filter_literal_globals, CompilationError, ImplMap, UnresolvedGlobal, UnresolvedStruct, - UnresolvedTypeAlias, + filter_literal_globals, CompilationError, ImplMap, UnresolvedFunctions, + UnresolvedGlobal, UnresolvedStruct, UnresolvedTraitImpl, UnresolvedTypeAlias, }, - def_map::DefMaps, + def_collector::{dc_crate::CollectedItems, errors::DefCollectorErrorKind}, + def_map::{DefMaps, ModuleData}, + def_map::{LocalModuleId, ModuleDefId, ModuleId, MAIN_FUNCTION}, resolution::errors::ResolverError, + resolution::import::PathResolution, scope::ScopeForest as GenericScopeForest, type_check::{generics::TraitGenerics, TypeCheckError}, + Context, }, + hir_def::traits::TraitImpl, hir_def::{ expr::{HirCapturedVar, HirIdent}, - function::FunctionBody, + function::{FuncMeta, FunctionBody, HirFunction}, traits::TraitConstraint, types::{Generics, Kind, ResolvedGeneric}, }, - macros_api::{ - BlockExpression, Ident, NodeInterner, NoirFunction, NoirStruct, Pattern, - SecondaryAttribute, StructId, - }, node_interner::{ - DefinitionKind, DependencyId, ExprId, FuncId, FunctionModifiers, GlobalId, ReferenceId, - TraitId, TypeAliasId, + DefinitionKind, DependencyId, ExprId, FuncId, FunctionModifiers, GlobalId, NodeInterner, + ReferenceId, StructId, TraitId, TraitImplId, TypeAliasId, }, - token::CustomAttribute, + token::{CustomAttribute, SecondaryAttribute}, Shared, Type, TypeVariable, }; -use crate::{ - ast::{TraitBound, UnresolvedGeneric, UnresolvedGenerics}, - graph::CrateId, - hir::{ - def_collector::{dc_crate::CollectedItems, errors::DefCollectorErrorKind}, - def_map::{LocalModuleId, ModuleDefId, ModuleId, MAIN_FUNCTION}, - resolution::import::PathResolution, - Context, - }, - hir_def::function::{FuncMeta, HirFunction}, - macros_api::{Param, Path, UnresolvedTypeData}, - node_interner::TraitImplId, -}; -use crate::{ - hir::{ - def_collector::dc_crate::{UnresolvedFunctions, UnresolvedTraitImpl}, - def_map::ModuleData, - }, - hir_def::traits::TraitImpl, - macros_api::ItemVisibility, -}; mod comptime; mod expressions; @@ -68,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; @@ -359,8 +346,9 @@ impl<'context> Elaborator<'context> { fn introduce_generics_into_scope(&mut self, all_generics: Vec) { // Introduce all numeric generics into scope for generic in &all_generics { - if let Kind::Numeric(typ) = &generic.kind { - let definition = DefinitionKind::GenericType(generic.type_var.clone()); + if let Kind::Numeric(typ) = &generic.kind() { + let definition = + DefinitionKind::NumericGeneric(generic.type_var.clone(), typ.clone()); let ident = Ident::new(generic.name.to_string(), generic.span); let hir_ident = self.add_variable_decl( ident, false, // mutable @@ -446,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(); @@ -475,9 +464,9 @@ impl<'context> Elaborator<'context> { let context = self.function_context.pop().expect("Imbalanced function_context pushes"); for typ in context.type_variables { - if let Type::TypeVariable(variable, kind) = typ.follow_bindings() { + if let Type::TypeVariable(variable) = typ.follow_bindings() { let msg = "TypeChecker should only track defaultable type vars"; - variable.bind(kind.default_type().expect(msg)); + variable.bind(variable.kind().default_type().expect(msg)); } } @@ -492,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, ); @@ -515,14 +504,16 @@ impl<'context> Elaborator<'context> { trait_constraints: &mut Vec, ) -> Type { let new_generic_id = self.interner.next_type_variable_id(); - let new_generic = TypeVariable::unbound(new_generic_id); + + let new_generic = TypeVariable::unbound(new_generic_id, Kind::Normal); generics.push(new_generic.clone()); let name = format!("impl {trait_path}"); - let generic_type = Type::NamedGeneric(new_generic, Rc::new(name), Kind::Normal); + 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); } @@ -534,19 +525,20 @@ impl<'context> Elaborator<'context> { pub fn add_generics(&mut self, generics: &UnresolvedGenerics) -> Generics { vecmap(generics, |generic| { let mut is_error = false; - let (type_var, name, kind) = match self.resolve_generic(generic) { + let (type_var, name) = match self.resolve_generic(generic) { Ok(values) => values, Err(error) => { self.push_err(error); is_error = true; let id = self.interner.next_type_variable_id(); - (TypeVariable::unbound(id), Rc::new("(error)".into()), Kind::Normal) + let kind = self.resolve_generic_kind(generic); + (TypeVariable::unbound(id, kind), Rc::new("(error)".into())) } }; let span = generic.span(); let name_owned = name.as_ref().clone(); - let resolved_generic = ResolvedGeneric { name, type_var, kind, span }; + let resolved_generic = ResolvedGeneric { name, type_var, span }; // Check for name collisions of this generic // Checking `is_error` here prevents DuplicateDefinition errors when @@ -571,25 +563,22 @@ impl<'context> Elaborator<'context> { fn resolve_generic( &mut self, generic: &UnresolvedGeneric, - ) -> Result<(TypeVariable, Rc, Kind), ResolverError> { + ) -> Result<(TypeVariable, Rc), ResolverError> { // Map the generic to a fresh type variable match generic { UnresolvedGeneric::Variable(_) | UnresolvedGeneric::Numeric { .. } => { let id = self.interner.next_type_variable_id(); - let typevar = TypeVariable::unbound(id); - let ident = generic.ident(); - let kind = self.resolve_generic_kind(generic); + let typevar = TypeVariable::unbound(id, kind); + let ident = generic.ident(); let name = Rc::new(ident.0.contents.clone()); - Ok((typevar, name, kind)) + Ok((typevar, name)) } // An already-resolved generic is only possible if it is the result of a // previous macro call being inserted into a generics list. UnresolvedGeneric::Resolved(id, span) => { match self.interner.get_quoted_type(*id).follow_bindings() { - Type::NamedGeneric(type_variable, name, kind) => { - Ok((type_variable, name, kind)) - } + Type::NamedGeneric(type_variable, name) => Ok((type_variable.clone(), name)), other => Err(ResolverError::MacroResultInGenericsListNotAGeneric { span: *span, typ: other.clone(), @@ -604,17 +593,21 @@ impl<'context> Elaborator<'context> { /// sure only primitive numeric types are being used. pub(super) fn resolve_generic_kind(&mut self, generic: &UnresolvedGeneric) -> Kind { if let UnresolvedGeneric::Numeric { ident, typ } = generic { - let typ = typ.clone(); - let typ = if typ.is_type_expression() { - self.resolve_type_inner(typ, &Kind::Numeric(Box::new(Type::default_int_type()))) + let unresolved_typ = typ.clone(); + let typ = if unresolved_typ.is_type_expression() { + self.resolve_type_inner( + unresolved_typ.clone(), + &Kind::Numeric(Box::new(Type::default_int_type())), + ) } else { - self.resolve_type(typ.clone()) + self.resolve_type(unresolved_typ.clone()) }; if !matches!(typ, Type::FieldElement | Type::Integer(_, _)) { - let unsupported_typ_err = ResolverError::UnsupportedNumericGenericType { - ident: ident.clone(), - typ: typ.clone(), - }; + let unsupported_typ_err = + ResolverError::UnsupportedNumericGenericType(UnsupportedNumericGenericType { + ident: ident.clone(), + typ: unresolved_typ.typ.clone(), + }); self.push_err(unsupported_typ_err); } Kind::Numeric(Box::new(typ)) @@ -678,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; @@ -693,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 @@ -749,6 +739,7 @@ impl<'context> Elaborator<'context> { UnresolvedTypeData::TraitAsType(path, args) => { self.desugar_impl_trait_arg(path, args, &mut generics, &mut trait_constraints) } + // Function parameters have Kind::Normal _ => self.resolve_type_inner(typ, &Kind::Normal), }; @@ -760,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( @@ -769,7 +760,6 @@ impl<'context> Elaborator<'context> { DefinitionKind::Local(None), &mut parameter_idents, true, // warn_if_unused - None, ); parameters.push((pattern, typ.clone(), visibility)); @@ -841,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(..) @@ -881,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) { @@ -957,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, + ); } } } @@ -987,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; @@ -1003,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, @@ -1134,7 +1218,7 @@ impl<'context> Elaborator<'context> { // object types in each method overlap or not. If they do, we issue an error. // If not, that is specialization which is allowed. let name = method.name_ident().clone(); - if module.declare_function(name, ItemVisibility::Public, *method_id).is_err() { + if module.declare_function(name, method.def.visibility, *method_id).is_err() { let existing = module.find_func_with_name(method.name_ident()).expect( "declare_function should only error if there is an existing function", ); @@ -1292,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); @@ -1330,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)); @@ -1342,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); @@ -1394,7 +1486,7 @@ impl<'context> Elaborator<'context> { } if let Some(name) = name { - self.interner.register_global(global_id, name, self.module_id()); + self.interner.register_global(global_id, name, global.visibility, self.module_id()); } self.local_module = old_module; 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 6ed59a61e4e..d55011f98a1 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/patterns.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/patterns.rs @@ -3,19 +3,21 @@ use noirc_errors::{Location, Span}; use rustc_hash::FxHashSet as HashSet; use crate::{ - ast::{TypePath, UnresolvedType, ERROR_IDENT}, + ast::{ + Expression, ExpressionKind, Ident, ItemVisibility, Path, Pattern, TypePath, UnresolvedType, + ERROR_IDENT, + }, hir::{ def_collector::dc_crate::CompilationError, resolution::errors::ResolverError, type_check::{Source, TypeCheckError}, }, hir_def::{ - expr::{HirIdent, HirMethodReference, ImplKind}, + expr::{HirExpression, HirIdent, HirMethodReference, ImplKind, TraitMethod}, stmt::HirPattern, }, - macros_api::{Expression, ExpressionKind, HirExpression, Ident, Path, Pattern}, node_interner::{DefinitionId, DefinitionKind, ExprId, FuncId, GlobalId, TraitImplKind}, - ResolvedGeneric, Shared, StructType, Type, TypeBindings, + Kind, ResolvedGeneric, Shared, StructType, Type, TypeBindings, }; use super::{Elaborator, ResolverMeta}; @@ -35,7 +37,6 @@ impl<'context> Elaborator<'context> { None, &mut Vec::new(), warn_if_unused, - None, ) } @@ -48,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, @@ -57,7 +57,6 @@ impl<'context> Elaborator<'context> { None, created_ids, warn_if_unused, - global_id, ) } @@ -70,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) => { @@ -80,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); @@ -110,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) @@ -142,7 +139,6 @@ impl<'context> Elaborator<'context> { mutable, new_definitions, warn_if_unused, - global_id, ) }); let location = Location::new(span, self.file); @@ -166,7 +162,6 @@ impl<'context> Elaborator<'context> { mutable, new_definitions, warn_if_unused, - global_id, ) } } @@ -271,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, @@ -279,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() }); @@ -318,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); @@ -364,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 @@ -488,7 +463,7 @@ impl<'context> Elaborator<'context> { ) -> Vec { let generics_with_types = generics.iter().zip(turbofish_generics); vecmap(generics_with_types, |(generic, unresolved_type)| { - self.resolve_type_inner(unresolved_type, &generic.kind) + self.resolve_type_inner(unresolved_type, &generic.kind()) }) } @@ -524,11 +499,15 @@ impl<'context> Elaborator<'context> { } fn resolve_variable(&mut self, path: Path) -> HirIdent { - if let Some((method, constraint, assumed)) = self.resolve_trait_generic_path(&path) { + if let Some(trait_path_resolution) = self.resolve_trait_generic_path(&path) { + if let Some(error) = trait_path_resolution.error { + self.push_err(error); + } + HirIdent { location: Location::new(path.span, self.file), - id: self.interner.trait_method_id(method), - impl_kind: ImplKind::TraitMethod(method, constraint, assumed), + id: self.interner.trait_method_id(trait_path_resolution.method.method_id), + impl_kind: ImplKind::TraitMethod(trait_path_resolution.method), } } else { // If the Path is being used as an Expression, then it is referring to a global from a separate module @@ -557,13 +536,14 @@ impl<'context> Elaborator<'context> { self.interner.add_global_reference(global_id, hir_ident.location); } - DefinitionKind::GenericType(_) => { + DefinitionKind::NumericGeneric(_, ref numeric_typ) => { // Initialize numeric generics to a polymorphic integer type in case // they're used in expressions. We must do this here since type_check_variable // does not check definition kinds and otherwise expects parameters to // already be typed. if self.interner.definition_type(hir_ident.id) == Type::Error { - let typ = Type::polymorphic_integer_or_field(self.interner); + let type_var_kind = Kind::Numeric(numeric_typ.clone()); + let typ = self.type_variable_with_kind(type_var_kind); self.interner.push_definition_type(hir_ident.id, typ); } } @@ -593,8 +573,12 @@ impl<'context> Elaborator<'context> { // We need to do this first since otherwise instantiating the type below // will replace each trait generic with a fresh type variable, rather than // the type used in the trait constraint (if it exists). See #4088. - if let ImplKind::TraitMethod(_, constraint, assumed) = &ident.impl_kind { - self.bind_generics_from_trait_constraint(constraint, *assumed, &mut bindings); + if let ImplKind::TraitMethod(method) = &ident.impl_kind { + self.bind_generics_from_trait_constraint( + &method.constraint, + method.assumed, + &mut bindings, + ); } // An identifiers type may be forall-quantified in the case of generic functions. @@ -632,18 +616,18 @@ impl<'context> Elaborator<'context> { } } - if let ImplKind::TraitMethod(_, mut constraint, assumed) = ident.impl_kind { - constraint.apply_bindings(&bindings); - if assumed { - let trait_generics = constraint.trait_generics.clone(); - let object_type = constraint.typ; + if let ImplKind::TraitMethod(mut method) = ident.impl_kind { + method.constraint.apply_bindings(&bindings); + if method.assumed { + 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); } else { // Currently only one impl can be selected per expr_id, so this // constraint needs to be pushed after any other constraints so // that monomorphization can resolve this trait method to the correct impl. - self.push_trait_constraint(constraint, expr_id); + self.push_trait_constraint(method.constraint, expr_id); } } @@ -728,8 +712,8 @@ 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; - ImplKind::TraitMethod(method_id, constraint, false) + 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/scope.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/scope.rs index 8e746256142..b83a1494ab9 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/scope.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/scope.rs @@ -1,11 +1,10 @@ use noirc_errors::{Location, Spanned}; -use crate::ast::{PathKind, ERROR_IDENT}; +use crate::ast::{Ident, Path, PathKind, ERROR_IDENT}; use crate::hir::def_map::{LocalModuleId, ModuleId}; use crate::hir::resolution::import::{PathResolution, PathResolutionResult}; use crate::hir::resolution::path_resolver::{PathResolver, StandardPathResolver}; use crate::hir::scope::{Scope as GenericScope, ScopeTree as GenericScopeTree}; -use crate::macros_api::Ident; use crate::{ hir::{ def_map::{ModuleDefId, TryFromModuleDefId}, @@ -15,8 +14,7 @@ use crate::{ expr::{HirCapturedVar, HirIdent}, traits::Trait, }, - macros_api::{Path, StructId}, - node_interner::{DefinitionId, TraitId}, + node_interner::{DefinitionId, StructId, TraitId}, Shared, StructType, }; use crate::{Type, TypeAlias}; @@ -92,7 +90,13 @@ impl<'context> Elaborator<'context> { } fn resolve_path_in_module(&mut self, path: Path, module_id: ModuleId) -> PathResolutionResult { - let resolver = StandardPathResolver::new(module_id); + let self_type_module_id = if let Some(Type::Struct(struct_type, _)) = &self.self_type { + Some(struct_type.borrow().id.module_id()) + } else { + None + }; + + let resolver = StandardPathResolver::new(module_id, self_type_module_id); if !self.interner.lsp_mode { return resolver.resolve( 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 55b641ca3d4..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,23 +3,25 @@ use noirc_errors::{Location, Span, Spanned}; use crate::{ ast::{ AssignStatement, BinaryOpKind, ConstrainKind, ConstrainStatement, Expression, - ExpressionKind, InfixExpression, LValue, + 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::{ expr::HirIdent, stmt::{ HirAssignStatement, HirConstrainStatement, HirForStatement, HirLValue, HirLetStatement, + HirStatement, }, }, - macros_api::{ - ForLoopStatement, ForRange, HirStatement, LetStatement, Path, Statement, StatementKind, - }, node_interner::{DefinitionId, DefinitionKind, GlobalId, StmtId}, - Type, + StructType, Type, }; use super::{lints, Elaborator}; @@ -108,7 +110,6 @@ impl<'context> Elaborator<'context> { definition, &mut Vec::new(), warn_if_unused, - global_id, ); let attributes = let_stmt.attributes; @@ -197,7 +198,7 @@ impl<'context> Elaborator<'context> { pub(super) fn elaborate_for(&mut self, for_loop: ForLoopStatement) -> (HirStatement, Type) { let (start, end) = match for_loop.range { - ForRange::Range(start, end) => (start, end), + ForRange::Range(bounds) => bounds.into_half_open(), ForRange::Array(_) => { let for_stmt = for_loop.range.into_for(for_loop.identifier, for_loop.block, for_loop.span); @@ -440,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)); } } @@ -498,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 aa7e1cb89c5..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 @@ -1,8 +1,7 @@ use crate::{ - ast::UnresolvedTypeExpression, + ast::{Ident, UnresolvedType, UnresolvedTypeData, UnresolvedTypeExpression}, graph::CrateId, hir::def_collector::{dc_crate::UnresolvedTraitImpl, errors::DefCollectorErrorKind}, - macros_api::{Ident, UnresolvedType, UnresolvedTypeData}, node_interner::TraitImplId, ResolvedGeneric, }; @@ -154,22 +153,28 @@ impl<'context> Elaborator<'context> { // Substitute each generic on the trait function with the corresponding generic on the impl function for ( ResolvedGeneric { type_var: trait_fn_generic, .. }, - ResolvedGeneric { name, type_var: impl_fn_generic, kind, .. }, + ResolvedGeneric { name, type_var: impl_fn_generic, .. }, ) in method.direct_generics.iter().zip(&override_meta.direct_generics) { - let arg = Type::NamedGeneric(impl_fn_generic.clone(), name.clone(), kind.clone()); - bindings.insert(trait_fn_generic.id(), (trait_fn_generic.clone(), arg)); + let trait_fn_kind = trait_fn_generic.kind(); + let arg = Type::NamedGeneric(impl_fn_generic.clone(), name.clone()); + bindings.insert( + trait_fn_generic.id(), + (trait_fn_generic.clone(), trait_fn_kind.clone(), arg), + ); } 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, )); } @@ -177,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; @@ -185,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 d7c8769620d..e877682972c 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/traits.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/traits.rs @@ -5,16 +5,17 @@ use noirc_errors::{Location, Span}; use crate::{ ast::{ - FunctionKind, TraitItem, UnresolvedGeneric, UnresolvedGenerics, UnresolvedTraitConstraint, + BlockExpression, FunctionDefinition, FunctionKind, FunctionReturnType, Ident, + ItemVisibility, NoirFunction, TraitItem, UnresolvedGeneric, UnresolvedGenerics, + UnresolvedTraitConstraint, UnresolvedType, }, hir::{def_collector::dc_crate::UnresolvedTrait, type_check::TypeCheckError}, - hir_def::{function::Parameters, traits::TraitFunction}, - macros_api::{ - BlockExpression, FunctionDefinition, FunctionReturnType, Ident, ItemVisibility, - NodeInterner, NoirFunction, UnresolvedType, + hir_def::{ + function::Parameters, + traits::{ResolvedTraitBound, TraitFunction}, }, - node_interner::{FuncId, ReferenceId, TraitId}, - Kind, ResolvedGeneric, Type, TypeBindings, TypeVariableKind, + node_interner::{DependencyId, FuncId, NodeInterner, ReferenceId, TraitId}, + ResolvedGeneric, Type, TypeBindings, }; use super::Elaborator; @@ -36,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); }); }); @@ -55,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, @@ -81,8 +97,7 @@ impl<'context> Elaborator<'context> { self.recover_generics(|this| { let the_trait = this.interner.get_trait(trait_id); let self_typevar = the_trait.self_type_typevar.clone(); - let self_type = - Type::TypeVariable(self_typevar.clone(), TypeVariableKind::Normal); + let self_type = Type::TypeVariable(self_typevar.clone()); let name_span = the_trait.name.span(); this.add_existing_generic( @@ -92,7 +107,6 @@ impl<'context> Elaborator<'context> { name: Rc::new("Self".to_owned()), type_var: self_typevar, span: name_span, - kind: Kind::Normal, }, ); this.self_type = Some(self_type.clone()); @@ -281,11 +295,12 @@ pub(crate) fn check_trait_impl_method_matches_declaration( // Substitute each generic on the trait function with the corresponding generic on the impl function for ( ResolvedGeneric { type_var: trait_fn_generic, .. }, - ResolvedGeneric { name, type_var: impl_fn_generic, kind, .. }, + ResolvedGeneric { name, type_var: impl_fn_generic, .. }, ) in trait_fn_meta.direct_generics.iter().zip(&meta.direct_generics) { - let arg = Type::NamedGeneric(impl_fn_generic.clone(), name.clone(), kind.clone()); - bindings.insert(trait_fn_generic.id(), (trait_fn_generic.clone(), arg)); + let trait_fn_kind = trait_fn_generic.kind(); + let arg = Type::NamedGeneric(impl_fn_generic.clone(), name.clone()); + bindings.insert(trait_fn_generic.id(), (trait_fn_generic.clone(), trait_fn_kind, arg)); } let (declaration_type, _) = trait_fn_meta.typ.instantiate_with_bindings(bindings, interner); 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 264b83956f8..8ffbd15bdab 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs @@ -7,14 +7,15 @@ use rustc_hash::FxHashMap as HashMap; use crate::{ ast::{ - AsTraitPath, BinaryOpKind, GenericTypeArgs, IntegerBitSize, UnresolvedGeneric, - UnresolvedGenerics, UnresolvedTypeExpression, + AsTraitPath, BinaryOpKind, GenericTypeArgs, Ident, IntegerBitSize, Path, PathKind, + Signedness, UnaryOp, UnresolvedGeneric, UnresolvedGenerics, UnresolvedType, + UnresolvedTypeData, UnresolvedTypeExpression, }, hir::{ comptime::{Interpreter, Value}, def_collector::dc_crate::CompilationError, def_map::ModuleDefId, - resolution::errors::ResolverError, + resolution::{errors::ResolverError, import::PathResolutionError}, type_check::{ generics::{Generic, TraitGenerics}, NoMatchingImplFoundError, Source, TypeCheckError, @@ -22,22 +23,20 @@ use crate::{ }, hir_def::{ expr::{ - HirBinaryOp, HirCallExpression, HirMemberAccess, HirMethodReference, - HirPrefixExpression, + HirBinaryOp, HirCallExpression, HirExpression, HirLiteral, HirMemberAccess, + HirMethodReference, HirPrefixExpression, TraitMethod, }, function::{FuncMeta, Parameters}, - traits::{NamedType, TraitConstraint}, - }, - macros_api::{ - HirExpression, HirLiteral, HirStatement, Ident, NodeInterner, Path, PathKind, - SecondaryAttribute, Signedness, UnaryOp, UnresolvedType, UnresolvedTypeData, + stmt::HirStatement, + traits::{NamedType, ResolvedTraitBound, Trait, TraitConstraint}, }, node_interner::{ - DefinitionKind, DependencyId, ExprId, FuncId, GlobalId, ImplSearchErrorKind, TraitId, + DefinitionKind, DependencyId, ExprId, GlobalId, ImplSearchErrorKind, NodeInterner, TraitId, TraitImplKind, TraitMethodId, }, + token::SecondaryAttribute, Generics, Kind, ResolvedGeneric, Type, TypeBinding, TypeBindings, TypeVariable, - TypeVariableKind, UnificationError, + UnificationError, }; use super::{lints, Elaborator}; @@ -45,6 +44,11 @@ use super::{lints, Elaborator}; pub const SELF_TYPE_NAME: &str = "Self"; pub const WILDCARD_TYPE: &str = "_"; +pub(super) struct TraitPathResolution { + pub(super) method: TraitMethod, + pub(super) error: Option, +} + impl<'context> Elaborator<'context> { /// Translates an UnresolvedType to a Type with a `TypeKind::Normal` pub(crate) fn resolve_type(&mut self, typ: UnresolvedType) -> Type { @@ -126,7 +130,7 @@ impl<'context> Elaborator<'context> { let env = Box::new(self.resolve_type_inner(*env, kind)); match *env { - Type::Unit | Type::Tuple(_) | Type::NamedGeneric(_, _, _) => { + Type::Unit | Type::Tuple(_) | Type::NamedGeneric(_, _) => { Type::Function(args, ret, env, unconstrained) } _ => { @@ -169,14 +173,10 @@ impl<'context> Elaborator<'context> { _ => (), } - if !kind.matches_opt(resolved_type.kind()) { + if !kind.unifies(&resolved_type.kind()) { let expected_typ_err = CompilationError::TypeError(TypeCheckError::TypeKindMismatch { expected_kind: kind.to_string(), - expr_kind: resolved_type - .kind() - .as_ref() - .map(Kind::to_string) - .unwrap_or("unknown".to_string()), + expr_kind: resolved_type.kind().to_string(), expr_span: span, }); self.errors.push((expected_typ_err, self.file)); @@ -230,7 +230,7 @@ impl<'context> Elaborator<'context> { return self_type; } } else if name == WILDCARD_TYPE { - return self.interner.next_type_variable(); + return self.interner.next_type_variable_with_kind(Kind::Any); } } else if let Some(typ) = self.lookup_associated_type_on_self(&path) { if !args.is_empty() { @@ -333,7 +333,7 @@ impl<'context> Elaborator<'context> { let ordered_args = expected_kinds.iter().zip(args.ordered_args); let ordered = - vecmap(ordered_args, |(generic, typ)| self.resolve_type_inner(typ, &generic.kind)); + vecmap(ordered_args, |(generic, typ)| self.resolve_type_inner(typ, &generic.kind())); let mut associated = Vec::new(); @@ -377,7 +377,7 @@ impl<'context> Elaborator<'context> { let expected = required_args.remove(index); seen_args.insert(name.0.contents.clone(), name.span()); - let typ = self.resolve_type_inner(typ, &expected.kind); + let typ = self.resolve_type_inner(typ, &expected.kind()); resolved.push(NamedType { name, typ }); } @@ -396,7 +396,7 @@ impl<'context> Elaborator<'context> { let name = path.last_name(); if let Some(generic) = self.find_generic(name) { let generic = generic.clone(); - return Some(Type::NamedGeneric(generic.type_var, generic.name, generic.kind)); + return Some(Type::NamedGeneric(generic.type_var, generic.name)); } } else if let Some(typ) = self.lookup_associated_type_on_self(path) { return Some(typ); @@ -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, } @@ -452,7 +462,7 @@ impl<'context> Elaborator<'context> { }); return Type::Error; } - if let Some(result) = op.function(lhs, rhs) { + if let Some(result) = op.function(lhs, rhs, &lhs_kind) { Type::Constant(result, lhs_kind) } else { self.push_err(ResolverError::OverflowInType { lhs, op, rhs, span }); @@ -470,15 +480,13 @@ impl<'context> Elaborator<'context> { } fn check_kind(&mut self, typ: Type, expected_kind: &Kind, span: Span) -> Type { - if let Some(kind) = typ.kind() { - if !kind.unifies(expected_kind) { - self.push_err(TypeCheckError::TypeKindMismatch { - expected_kind: expected_kind.to_string(), - expr_kind: kind.to_string(), - expr_span: span, - }); - return Type::Error; - } + if !typ.kind().unifies(expected_kind) { + self.push_err(TypeCheckError::TypeKindMismatch { + expected_kind: expected_kind.to_string(), + expr_kind: typ.kind().to_string(), + expr_span: span, + }); + return Type::Error; } typ } @@ -529,10 +537,7 @@ impl<'context> Elaborator<'context> { // // Returns the trait method, trait constraint, and whether the impl is assumed to exist by a where clause or not // E.g. `t.method()` with `where T: Foo` in scope will return `(Foo::method, T, vec![Bar])` - fn resolve_trait_static_method_by_self( - &mut self, - path: &Path, - ) -> Option<(TraitMethodId, TraitConstraint, bool)> { + fn resolve_trait_static_method_by_self(&mut self, path: &Path) -> Option { let trait_impl = self.current_trait_impl?; let trait_id = self.interner.try_get_trait_implementation(trait_impl)?.borrow().trait_id; @@ -544,7 +549,10 @@ impl<'context> Elaborator<'context> { let the_trait = self.interner.get_trait(trait_id); let method = the_trait.find_method(method.0.contents.as_str())?; let constraint = the_trait.as_constraint(path.span); - return Some((method, constraint, true)); + return Some(TraitPathResolution { + method: TraitMethod { method_id: method, constraint, assumed: true }, + error: None, + }); } } None @@ -554,16 +562,18 @@ impl<'context> Elaborator<'context> { // // Returns the trait method, trait constraint, and whether the impl is assumed to exist by a where clause or not // E.g. `t.method()` with `where T: Foo` in scope will return `(Foo::method, T, vec![Bar])` - fn resolve_trait_static_method( - &mut self, - path: &Path, - ) -> Option<(TraitMethodId, TraitConstraint, bool)> { - let func_id: FuncId = self.lookup(path.clone()).ok()?; + fn resolve_trait_static_method(&mut self, path: &Path) -> Option { + let path_resolution = self.resolve_path(path.clone()).ok()?; + let ModuleDefId::FunctionId(func_id) = path_resolution.module_def_id else { return None }; + let meta = self.interner.function_meta(&func_id); let the_trait = self.interner.get_trait(meta.trait_id?); let method = the_trait.find_method(path.last_name())?; let constraint = the_trait.as_constraint(path.span); - Some((method, constraint, false)) + Some(TraitPathResolution { + method: TraitMethod { method_id: method, constraint, assumed: false }, + error: path_resolution.error, + }) } // This resolves a static trait method T::trait_method by iterating over the where clause @@ -574,21 +584,24 @@ impl<'context> Elaborator<'context> { fn resolve_trait_method_by_named_generic( &mut self, path: &Path, - ) -> Option<(TraitMethodId, TraitConstraint, bool)> { + ) -> Option { if path.segments.len() != 2 { return None; } for constraint in self.trait_bounds.clone() { - if let Type::NamedGeneric(_, name, _) = &constraint.typ { + if let Type::NamedGeneric(_, name) = &constraint.typ { // if `path` is `T::method_name`, we're looking for constraint of the form `T: SomeTrait` if path.segments[0].ident.0.contents != name.as_str() { 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((method, constraint, true)); + return Some(TraitPathResolution { + method: TraitMethod { method_id: method, constraint, assumed: true }, + error: None, + }); } } } @@ -602,7 +615,7 @@ impl<'context> Elaborator<'context> { pub(super) fn resolve_trait_generic_path( &mut self, path: &Path, - ) -> Option<(TraitMethodId, TraitConstraint, bool)> { + ) -> Option { self.resolve_trait_static_method_by_self(path) .or_else(|| self.resolve_trait_static_method(path)) .or_else(|| self.resolve_trait_method_by_named_generic(path)) @@ -697,11 +710,21 @@ impl<'context> Elaborator<'context> { typ } + /// Return a fresh integer type variable and log it + /// in self.type_variables to default it later. + pub(super) fn type_variable_with_kind(&mut self, type_var_kind: Kind) -> Type { + let typ = Type::type_variable_with_kind(self.interner, type_var_kind); + self.push_type_variable(typ.clone()); + typ + } + /// Translates a (possibly Unspecified) UnresolvedType to a Type. /// Any UnresolvedType::Unspecified encountered are replaced with fresh type variables. pub(super) fn resolve_inferred_type(&mut self, typ: UnresolvedType) -> Type { match &typ.typ { - UnresolvedTypeData::Unspecified => self.interner.next_type_variable(), + UnresolvedTypeData::Unspecified => { + self.interner.next_type_variable_with_kind(Kind::Any) + } _ => self.resolve_type(typ), } } @@ -790,7 +813,7 @@ impl<'context> Elaborator<'context> { // Could do a single unification for the entire function type, but matching beforehand // lets us issue a more precise error on the individual argument that fails to type check. match function { - Type::TypeVariable(binding, TypeVariableKind::Normal) => { + Type::TypeVariable(binding) if binding.kind() == Kind::Normal => { if let TypeBinding::Bound(typ) = &*binding.borrow() { return self.bind_function_type(typ.clone(), args, span); } @@ -801,7 +824,8 @@ impl<'context> Elaborator<'context> { let expected = Type::Function(args, Box::new(ret.clone()), Box::new(env_type), false); - if let Err(error) = binding.try_bind(expected, span) { + let expected_kind = expected.kind(); + if let Err(error) = binding.try_bind(expected, &expected_kind, span) { self.push_err(error); } ret @@ -819,28 +843,60 @@ 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::TypeVariable(_, TypeVariableKind::IntegerOrField) - | Type::TypeVariable(_, TypeVariableKind::Integer) - | 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(); + + let from_value_opt = match self.interner.expression(from_expr_id) { + HirExpression::Literal(HirLiteral::Integer(int, false)) => Some(int), - Type::TypeVariable(_, _) => { + // 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. - let expected = Type::polymorphic_integer_or_field(self.interner); + let expected = self.polymorphic_integer_or_field(); 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 { @@ -878,8 +934,8 @@ impl<'context> Elaborator<'context> { // Matches on TypeVariable must be first to follow any type // bindings. - (TypeVariable(var, _), other) | (other, TypeVariable(var, _)) => { - if let TypeBinding::Bound(binding) = &*var.borrow() { + (TypeVariable(var), other) | (other, TypeVariable(var)) => { + if let TypeBinding::Bound(ref binding) = &*var.borrow() { return self.comparator_operand_type_rules(other, binding, op, span); } @@ -943,14 +999,14 @@ impl<'context> Elaborator<'context> { span, }); - let use_impl = !lhs_type.is_numeric(); + let use_impl = !lhs_type.is_numeric_value(); // If this operator isn't valid for fields we have to possibly narrow - // TypeVariableKind::IntegerOrField to TypeVariableKind::Integer. + // Kind::IntegerOrField to Kind::Integer. // Doing so also ensures a type error if Field is used. // The is_numeric check is to allow impls for custom types to bypass this. - if !op.kind.is_valid_for_field_type() && lhs_type.is_numeric() { - let target = Type::polymorphic_integer(self.interner); + if !op.kind.is_valid_for_field_type() && lhs_type.is_numeric_value() { + let target = self.polymorphic_integer(); use crate::ast::BinaryOpKind::*; use TypeCheckError::*; @@ -991,22 +1047,22 @@ impl<'context> Elaborator<'context> { // Matches on TypeVariable must be first so that we follow any type // bindings. - (TypeVariable(int, _), other) | (other, TypeVariable(int, _)) => { + (TypeVariable(int), other) | (other, TypeVariable(int)) => { if op.kind == BinaryOpKind::ShiftLeft || op.kind == BinaryOpKind::ShiftRight { self.unify( rhs_type, &Type::Integer(Signedness::Unsigned, IntegerBitSize::Eight), || TypeCheckError::InvalidShiftSize { span }, ); - let use_impl = if lhs_type.is_numeric() { - let integer_type = Type::polymorphic_integer(self.interner); + let use_impl = if lhs_type.is_numeric_value() { + let integer_type = self.polymorphic_integer(); self.bind_type_variables_for_infix(lhs_type, op, &integer_type, span) } else { true }; return Ok((lhs_type.clone(), use_impl)); } - if let TypeBinding::Bound(binding) = &*int.borrow() { + if let TypeBinding::Bound(ref binding) = &*int.borrow() { return self.infix_operand_type_rules(binding, op, other, span); } let use_impl = self.bind_type_variables_for_infix(lhs_type, op, rhs_type, span); @@ -1091,21 +1147,21 @@ impl<'context> Elaborator<'context> { // Matches on TypeVariable must be first so that we follow any type // bindings. - TypeVariable(int, _) => { - if let TypeBinding::Bound(binding) = &*int.borrow() { + TypeVariable(int) => { + if let TypeBinding::Bound(ref binding) = &*int.borrow() { return self.prefix_operand_type_rules(op, binding, span); } // The `!` prefix operator is not valid for Field, so if this is a numeric // type we constrain it to just (non-Field) integer types. - if matches!(op, crate::ast::UnaryOp::Not) && rhs_type.is_numeric() { + if matches!(op, crate::ast::UnaryOp::Not) && rhs_type.is_numeric_value() { let integer_type = Type::polymorphic_integer(self.interner); self.unify(rhs_type, &integer_type, || { TypeCheckError::InvalidUnaryOp { kind: rhs_type.to_string(), span } }); } - Ok((rhs_type.clone(), !rhs_type.is_numeric())) + Ok((rhs_type.clone(), !rhs_type.is_numeric_value())) } Integer(sign_x, bit_width_x) => { if *op == UnaryOp::Minus && *sign_x == Signedness::Unsigned { @@ -1187,7 +1243,11 @@ impl<'context> Elaborator<'context> { let object_type = object_type.substitute(&bindings); bindings.insert( the_trait.self_type_typevar.id(), - (the_trait.self_type_typevar.clone(), object_type.clone()), + ( + the_trait.self_type_typevar.clone(), + the_trait.self_type_typevar.kind(), + object_type.clone(), + ), ); self.interner.select_impl_for_expression( expr_id, @@ -1268,7 +1328,7 @@ impl<'context> Elaborator<'context> { }); None } - Type::NamedGeneric(_, _, _) => { + Type::NamedGeneric(_, _) => { self.lookup_method_in_trait_constraints(object_type, method_name, span) } // Mutable references to another type should resolve to methods of their element type. @@ -1284,7 +1344,7 @@ impl<'context> Elaborator<'context> { Type::Error => None, // The type variable must be unbound at this point since follow_bindings was called - Type::TypeVariable(_, TypeVariableKind::Normal) => { + Type::TypeVariable(var) if var.kind() == Kind::Normal => { self.push_err(TypeCheckError::TypeAnnotationsNeededForMethodCall { span }); None } @@ -1316,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); } } } @@ -1339,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, @@ -1631,9 +1730,9 @@ impl<'context> Elaborator<'context> { | Type::Bool | Type::Unit | Type::Error - | Type::TypeVariable(_, _) + | Type::TypeVariable(_) | Type::Constant(..) - | Type::NamedGeneric(_, _, _) + | Type::NamedGeneric(_, _) | Type::Quoted(_) | Type::Forall(_, _) => (), @@ -1647,7 +1746,7 @@ impl<'context> Elaborator<'context> { } Type::Array(length, element_type) => { - if let Type::NamedGeneric(type_variable, name, _) = length.as_ref() { + if let Type::NamedGeneric(type_variable, name) = length.as_ref() { found.insert(name.to_string(), type_variable.clone()); } Self::find_numeric_generics_in_type(element_type, found); @@ -1672,7 +1771,7 @@ impl<'context> Elaborator<'context> { Type::Struct(struct_type, generics) => { for (i, generic) in generics.iter().enumerate() { - if let Type::NamedGeneric(type_variable, name, _) = generic { + if let Type::NamedGeneric(type_variable, name) = generic { if struct_type.borrow().generics[i].is_numeric() { found.insert(name.to_string(), type_variable.clone()); } @@ -1683,7 +1782,7 @@ impl<'context> Elaborator<'context> { } Type::Alias(alias, generics) => { for (i, generic) in generics.iter().enumerate() { - if let Type::NamedGeneric(type_variable, name, _) = generic { + if let Type::NamedGeneric(type_variable, name) = generic { if alias.borrow().generics[i].is_numeric() { found.insert(name.to_string(), type_variable.clone()); } @@ -1694,12 +1793,12 @@ impl<'context> Elaborator<'context> { } Type::MutableReference(element) => Self::find_numeric_generics_in_type(element, found), Type::String(length) => { - if let Type::NamedGeneric(type_variable, name, _) = length.as_ref() { + if let Type::NamedGeneric(type_variable, name) = length.as_ref() { found.insert(name.to_string(), type_variable.clone()); } } Type::FmtString(length, fields) => { - if let Type::NamedGeneric(type_variable, name, _) = length.as_ref() { + if let Type::NamedGeneric(type_variable, name) = length.as_ref() { found.insert(name.to_string(), type_variable.clone()); } Self::find_numeric_generics_in_type(fields, found); @@ -1741,47 +1840,85 @@ 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()); + self.bind_generics_from_trait_bound(&constraint.trait_bound, bindings); - for (param, arg) in the_trait.generics.iter().zip(&constraint.trait_generics.ordered) { - // Avoid binding t = t - if !arg.occurs(param.type_var.id()) { - bindings.insert(param.type_var.id(), (param.type_var.clone(), arg.clone())); - } + // 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())); } + } - let mut associated_types = the_trait.associated_types.clone(); - assert_eq!(associated_types.len(), constraint.trait_generics.named.len()); + 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); - 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) - }); + bind_ordered_generics(&the_trait.generics, &trait_bound.trait_generics.ordered, bindings); - let param = associated_types.swap_remove(i); + let associated_types = the_trait.associated_types.clone(); + bind_named_generics(associated_types, &trait_bound.trait_generics.named, bindings); + } - // Avoid binding t = t - if !arg.typ.occurs(param.type_var.id()) { - bindings.insert(param.type_var.id(), (param.type_var.clone(), arg.typ.clone())); - } + 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 } + } +} - // If the trait impl is already assumed to exist we should add any type bindings for `Self`. - // Otherwise `self` will be replaced with a fresh type variable, which will require the user - // to specify a redundant type annotation. - if assumed { - let self_type = the_trait.self_type_typevar.clone(); - bindings.insert(self_type.id(), (self_type, constraint.typ.clone())); - } +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())); } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/unquote.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/unquote.rs index fd7e02df905..982ad3d2e1f 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/unquote.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/unquote.rs @@ -1,5 +1,5 @@ use crate::{ - macros_api::Path, + ast::Path, token::{SpannedToken, Token, Tokens}, }; 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 105f6e09395..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 @@ -7,14 +7,13 @@ use crate::{ ast::{ ArrayLiteral, AsTraitPath, AssignStatement, BlockExpression, CallExpression, CastExpression, ConstrainStatement, ConstructorExpression, Expression, ExpressionKind, - ForLoopStatement, ForRange, GenericTypeArgs, IfExpression, IndexExpression, + ForBounds, ForLoopStatement, ForRange, GenericTypeArgs, IfExpression, IndexExpression, InfixExpression, LValue, Lambda, LetStatement, Literal, MemberAccessExpression, MethodCallExpression, Pattern, PrefixExpression, Statement, StatementKind, UnresolvedType, UnresolvedTypeData, }, hir_def::traits::TraitConstraint, - macros_api::NodeInterner, - node_interner::InternedStatementKind, + node_interner::{InternedStatementKind, NodeInterner}, token::{Keyword, Token}, Type, }; @@ -268,6 +267,7 @@ impl<'interner> TokenPrettyPrinter<'interner> { | Token::Dot | Token::DoubleColon | Token::DoubleDot + | Token::DoubleDotEqual | Token::Caret | Token::Pound | Token::Pipe @@ -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. @@ -714,10 +717,13 @@ fn remove_interned_in_statement_kind( }), StatementKind::For(for_loop) => StatementKind::For(ForLoopStatement { range: match for_loop.range { - ForRange::Range(from, to) => ForRange::Range( - remove_interned_in_expression(interner, from), - remove_interned_in_expression(interner, to), - ), + ForRange::Range(ForBounds { start, end, inclusive }) => { + ForRange::Range(ForBounds { + start: remove_interned_in_expression(interner, start), + end: remove_interned_in_expression(interner, end), + inclusive, + }) + } ForRange::Array(expr) => { ForRange::Array(remove_interned_in_expression(interner, expr)) } 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 4344d19829a..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 @@ -9,10 +9,11 @@ use crate::ast::{ UnresolvedType, UnresolvedTypeData, UnresolvedTypeExpression, }; use crate::ast::{ConstrainStatement, Expression, Statement, StatementKind}; -use crate::hir_def::expr::{HirArrayLiteral, HirBlockExpression, HirExpression, HirIdent}; +use crate::hir_def::expr::{ + HirArrayLiteral, HirBlockExpression, HirExpression, HirIdent, HirLiteral, +}; use crate::hir_def::stmt::{HirLValue, HirPattern, HirStatement}; use crate::hir_def::types::{Type, TypeBinding}; -use crate::macros_api::HirLiteral; use crate::node_interner::{ExprId, NodeInterner, StmtId}; // TODO: @@ -51,7 +52,7 @@ impl HirStatement { }), HirStatement::For(for_stmt) => StatementKind::For(ForLoopStatement { identifier: for_stmt.identifier.to_display_ast(interner), - range: ForRange::Range( + range: ForRange::range( for_stmt.start_range.to_display_ast(interner), for_stmt.end_range.to_display_ast(interner), ), @@ -315,10 +316,10 @@ impl Type { let name = Path::from_ident(type_def.name.clone()); UnresolvedTypeData::Named(name, generics, false) } - Type::TypeVariable(binding, kind) => match &*binding.borrow() { + Type::TypeVariable(binding) => match &*binding.borrow() { TypeBinding::Bound(typ) => return typ.to_display_ast(), - TypeBinding::Unbound(id) => { - let name = format!("var_{:?}_{}", kind, id); + TypeBinding::Unbound(id, type_var_kind) => { + let name = format!("var_{:?}_{}", type_var_kind, id); let path = Path::from_single(name, Span::empty(0)); let expression = UnresolvedTypeExpression::Variable(path); UnresolvedTypeData::Expression(expression) @@ -333,7 +334,7 @@ impl Type { let name = Path::from_single(name.as_ref().clone(), Span::default()); UnresolvedTypeData::TraitAsType(name, generics) } - Type::NamedGeneric(_var, name, _kind) => { + Type::NamedGeneric(_var, name) => { let name = Path::from_single(name.as_ref().clone(), Span::default()); UnresolvedTypeData::Named(name, GenericTypeArgs::default(), true) } @@ -373,7 +374,7 @@ impl Type { match self.follow_bindings() { Type::Constant(length, _kind) => UnresolvedTypeExpression::Constant(length, span), - Type::NamedGeneric(_var, name, _kind) => { + Type::NamedGeneric(_var, name) => { let path = Path::from_single(name.as_ref().clone(), span); UnresolvedTypeExpression::Variable(path) } @@ -416,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 e920073b453..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 @@ -8,14 +8,13 @@ use iter_extended::try_vecmap; use noirc_errors::Location; use rustc_hash::FxHashMap as HashMap; -use crate::ast::{BinaryOpKind, FunctionKind, IntegerBitSize, Signedness}; +use crate::ast::{BinaryOpKind, FunctionKind, IntegerBitSize, Signedness, UnaryOp}; use crate::elaborator::Elaborator; use crate::graph::CrateId; use crate::hir::def_map::ModuleId; use crate::hir::type_check::TypeCheckError; use crate::hir_def::expr::ImplKind; use crate::hir_def::function::FunctionBody; -use crate::macros_api::UnaryOp; use crate::monomorphization::{ perform_impl_bindings, perform_instantiation_bindings, resolve_trait_method, undo_instantiation_bindings, @@ -27,17 +26,17 @@ use crate::{ expr::{ HirArrayLiteral, HirBlockExpression, HirCallExpression, HirCastExpression, HirConstructorExpression, HirExpression, HirIdent, HirIfExpression, HirIndexExpression, - HirInfixExpression, HirLambda, HirMemberAccess, HirMethodCallExpression, + HirInfixExpression, HirLambda, HirLiteral, HirMemberAccess, HirMethodCallExpression, HirPrefixExpression, }, stmt::{ HirAssignStatement, HirConstrainStatement, HirForStatement, HirLValue, HirLetStatement, - HirPattern, + HirPattern, HirStatement, }, + types::Kind, }, - macros_api::{HirLiteral, HirStatement, NodeInterner}, - node_interner::{DefinitionId, DefinitionKind, ExprId, FuncId, StmtId}, - Shared, Type, TypeBinding, TypeBindings, TypeVariableKind, + node_interner::{DefinitionId, DefinitionKind, ExprId, FuncId, NodeInterner, StmtId}, + Shared, Type, TypeBinding, TypeBindings, }; use super::errors::{IResult, InterpreterError}; @@ -62,7 +61,7 @@ pub struct Interpreter<'local, 'interner> { /// Since the interpreter monomorphizes as it interprets, we can bind over the same generic /// multiple times. Without this map, when one of these inner functions exits we would /// unbind the generic completely instead of resetting it to its previous binding. - bound_generics: Vec>, + bound_generics: Vec>, } #[allow(unused)] @@ -89,7 +88,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { // To match the monomorphizer, we need to call follow_bindings on each of // the instantiation bindings before we unbind the generics from the previous function. // This is because the instantiation bindings refer to variables from the call site. - for (_, binding) in instantiation_bindings.values_mut() { + for (_, kind, binding) in instantiation_bindings.values_mut() { + *kind = kind.follow_bindings(); *binding = binding.follow_bindings(); } @@ -98,7 +98,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { let mut impl_bindings = perform_impl_bindings(self.elaborator.interner, trait_method, function, location)?; - for (_, binding) in impl_bindings.values_mut() { + for (_, kind, binding) in impl_bindings.values_mut() { + *kind = kind.follow_bindings(); *binding = binding.follow_bindings(); } @@ -335,8 +336,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { fn unbind_generics_from_previous_function(&mut self) { if let Some(bindings) = self.bound_generics.last() { - for var in bindings.keys() { - var.unbind(var.id()); + for (var, (_, kind)) in bindings { + var.unbind(var.id(), kind.clone()); } } // Push a new bindings list for the current function @@ -348,7 +349,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { self.bound_generics.pop(); if let Some(bindings) = self.bound_generics.last() { - for (var, binding) in bindings { + for (var, (binding, _kind)) in bindings { var.force_bind(binding.clone()); } } @@ -360,12 +361,12 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { .last_mut() .expect("remember_bindings called with no bound_generics on the stack"); - for (var, binding) in main_bindings.values() { - bound_generics.insert(var.clone(), binding.follow_bindings()); + for (var, kind, binding) in main_bindings.values() { + bound_generics.insert(var.clone(), (binding.follow_bindings(), kind.clone())); } - for (var, binding) in impl_bindings.values() { - bound_generics.insert(var.clone(), binding.follow_bindings()); + for (var, kind, binding) in impl_bindings.values() { + bound_generics.insert(var.clone(), (binding.follow_bindings(), kind.clone())); } } @@ -543,8 +544,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { InterpreterError::VariableNotInScope { location } })?; - if let ImplKind::TraitMethod(method, _, _) = ident.impl_kind { - let method_id = resolve_trait_method(self.elaborator.interner, method, id)?; + if let ImplKind::TraitMethod(method) = ident.impl_kind { + let method_id = resolve_trait_method(self.elaborator.interner, method.method_id, id)?; let typ = self.elaborator.interner.id_type(id).follow_bindings(); let bindings = self.elaborator.interner.get_instantiation_bindings(id).clone(); return Ok(Value::Function(method_id, typ, Rc::new(bindings))); @@ -582,18 +583,20 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { Ok(value) } } - DefinitionKind::GenericType(type_variable) => { + DefinitionKind::NumericGeneric(type_variable, numeric_typ) => { let value = match &*type_variable.borrow() { - TypeBinding::Unbound(_) => None, - TypeBinding::Bound(binding) => binding.evaluate_to_u32(), + TypeBinding::Unbound(_, _) => None, + 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(), TypeVariableKind::Normal); + let typ = Type::TypeVariable(type_variable.clone()); Err(InterpreterError::NonIntegerArrayLength { typ, location }) } } @@ -752,14 +755,18 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { Ok(Value::I64(value)) } } - } else if let Type::TypeVariable(variable, TypeVariableKind::IntegerOrField) = &typ { - Ok(Value::Field(value)) - } else if let Type::TypeVariable(variable, TypeVariableKind::Integer) = &typ { - let value: u64 = value - .try_to_u64() - .ok_or(InterpreterError::IntegerOutOfRangeForType { value, typ, location })?; - let value = if is_negative { 0u64.wrapping_sub(value) } else { value }; - Ok(Value::U64(value)) + } else if let Type::TypeVariable(variable) = &typ { + if variable.is_integer_or_field() { + Ok(Value::Field(value)) + } else if variable.is_integer() { + let value: u64 = value + .try_to_u64() + .ok_or(InterpreterError::IntegerOutOfRangeForType { value, typ, location })?; + let value = if is_negative { 0u64.wrapping_sub(value) } else { value }; + Ok(Value::U64(value)) + } else { + Err(InterpreterError::NonIntegerIntegerLiteral { typ, location }) + } } else { Err(InterpreterError::NonIntegerIntegerLiteral { typ, location }) } @@ -890,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)), @@ -965,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)), @@ -978,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)), @@ -990,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)), @@ -1002,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)), @@ -1014,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)), @@ -1026,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)), @@ -1038,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)), @@ -1050,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)), @@ -1062,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("%")), }, } } @@ -1633,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 9960486120e..aca0a6dd6ca 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 @@ -10,7 +10,6 @@ use builtin_helpers::{ mutate_func_meta_type, parse, quote_ident, replace_func_meta_parameters, replace_func_meta_return_type, }; -use chumsky::{chain::Chain, prelude::choice, primitive::just, Parser}; use im::Vector; use iter_extended::{try_vecmap, vecmap}; use noirc_errors::Location; @@ -20,8 +19,9 @@ use rustc_hash::FxHashMap as HashMap; use crate::{ ast::{ ArrayLiteral, BlockExpression, ConstrainKind, Expression, ExpressionKind, ForRange, - FunctionKind, FunctionReturnType, IntegerBitSize, LValue, Literal, Pattern, Statement, - StatementKind, UnaryOp, UnresolvedType, UnresolvedTypeData, Visibility, + FunctionKind, FunctionReturnType, Ident, IntegerBitSize, ItemVisibility, LValue, Literal, + Pattern, Signedness, Statement, StatementKind, UnaryOp, UnresolvedType, UnresolvedTypeData, + Visibility, }, hir::{ comptime::{ @@ -30,11 +30,13 @@ use crate::{ InterpreterError, Value, }, def_collector::dc_crate::CollectedItems, + def_map::ModuleDefId, }, + hir_def::expr::{HirExpression, HirLiteral}, hir_def::function::FunctionBody, - macros_api::{HirExpression, HirLiteral, Ident, ModuleDefId, NodeInterner, Signedness}, - node_interner::{DefinitionKind, TraitImplKind}, - parser, + hir_def::{self}, + node_interner::{DefinitionKind, NodeInterner, TraitImplKind}, + parser::{Parser, StatementOrExpressionOrLValue}, token::{Attribute, SecondaryAttribute, Token}, Kind, QuotedType, ResolvedGeneric, Shared, Type, TypeVariable, }; @@ -189,6 +191,9 @@ impl<'local, 'context> Interpreter<'local, 'context> { "type_as_array" => type_as_array(arguments, return_type, location), "type_as_constant" => type_as_constant(arguments, return_type, location), "type_as_integer" => type_as_integer(arguments, return_type, location), + "type_as_mutable_reference" => { + type_as_mutable_reference(arguments, return_type, location) + } "type_as_slice" => type_as_slice(arguments, return_type, location), "type_as_str" => type_as_str(arguments, return_type, location), "type_as_struct" => type_as_struct(arguments, return_type, location), @@ -201,6 +206,7 @@ impl<'local, 'context> Interpreter<'local, 'context> { "type_implements" => type_implements(interner, arguments, location), "type_is_bool" => type_is_bool(arguments, location), "type_is_field" => type_is_field(arguments, location), + "type_is_unit" => type_is_unit(arguments, location), "type_of" => type_of(arguments, location), "typed_expr_as_function_definition" => { typed_expr_as_function_definition(interner, arguments, return_type, location) @@ -208,7 +214,15 @@ impl<'local, 'context> Interpreter<'local, 'context> { "typed_expr_get_type" => { typed_expr_get_type(interner, arguments, return_type, location) } + "unresolved_type_as_mutable_reference" => { + unresolved_type_as_mutable_reference(interner, arguments, return_type, location) + } + "unresolved_type_as_slice" => { + unresolved_type_as_slice(interner, arguments, return_type, location) + } + "unresolved_type_is_bool" => unresolved_type_is_bool(interner, arguments, location), "unresolved_type_is_field" => unresolved_type_is_field(interner, arguments, location), + "unresolved_type_is_unit" => unresolved_type_is_unit(interner, arguments, location), "zeroed" => zeroed(return_type), _ => { let item = format!("Comptime evaluation for builtin function {name}"); @@ -301,7 +315,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)) @@ -400,11 +414,11 @@ fn struct_def_add_generic( } } - let type_var = TypeVariable::unbound(interner.next_type_variable_id()); + let type_var_kind = Kind::Normal; + let type_var = TypeVariable::unbound(interner.next_type_variable_id(), type_var_kind); let span = generic_location.span; - let kind = Kind::Normal; - let typ = Type::NamedGeneric(type_var.clone(), name.clone(), kind.clone()); - let new_generic = ResolvedGeneric { name, type_var, span, kind }; + let typ = Type::NamedGeneric(type_var.clone(), name.clone()); + let new_generic = ResolvedGeneric { name, type_var, span }; the_struct.generics.push(new_generic); Ok(Value::Type(typ)) @@ -422,7 +436,7 @@ fn struct_def_as_type( let struct_def = struct_def_rc.borrow(); let generics = vecmap(&struct_def.generics, |generic| { - Type::NamedGeneric(generic.type_var.clone(), generic.name.clone(), generic.kind.clone()) + Type::NamedGeneric(generic.type_var.clone(), generic.name.clone()) }); drop(struct_def); @@ -483,9 +497,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])); } @@ -552,7 +566,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(); @@ -671,15 +689,21 @@ fn quoted_as_expr( ) -> IResult { let argument = check_one_argument(arguments, location)?; - let expr_parser = parser::expression().map(|expr| Value::expression(expr.kind)); - let statement_parser = parser::fresh_statement().map(Value::statement); - let lvalue_parser = parser::lvalue(parser::expression()).map(Value::lvalue); - let parser = choice((expr_parser, statement_parser, lvalue_parser)); - let parser = parser.then_ignore(just(Token::Semicolon).or_not()); + let result = + parse(interner, argument, Parser::parse_statement_or_expression_or_lvalue, "an expression"); - let expr = parse(interner, argument, parser, "an expression").ok(); + 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, expr) + option(return_type, value) } // fn as_module(quoted: Quoted) -> Option @@ -691,9 +715,13 @@ fn quoted_as_module( ) -> IResult { let argument = check_one_argument(arguments, location)?; - let path = - parse(interpreter.elaborator.interner, argument, parser::path_no_turbofish(), "a path") - .ok(); + let path = parse( + interpreter.elaborator.interner, + argument, + Parser::parse_path_no_turbofish_or_error, + "a path", + ) + .ok(); let option_value = path.and_then(|path| { let module = interpreter .elaborate_in_function(interpreter.current_function, |elaborator| { @@ -715,12 +743,12 @@ fn quoted_as_trait_constraint( let trait_bound = parse( interpreter.elaborator.interner, argument, - parser::trait_bound(), + Parser::parse_trait_bound_or_error, "a 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 })?; @@ -734,7 +762,8 @@ fn quoted_as_type( location: Location, ) -> IResult { let argument = check_one_argument(arguments, location)?; - let typ = parse(interpreter.elaborator.interner, argument, parser::parse_type(), "a type")?; + let typ = + parse(interpreter.elaborator.interner, argument, Parser::parse_type_or_error, "a type")?; let typ = interpreter .elaborate_in_function(interpreter.current_function, |elab| elab.resolve_type(typ)); Ok(Value::Type(typ)) @@ -776,8 +805,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 }); } @@ -787,10 +820,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), @@ -851,6 +885,21 @@ fn type_as_integer( }) } +// fn as_mutable_reference(self) -> Option +fn type_as_mutable_reference( + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + type_as(arguments, return_type, location, |typ| { + if let Type::MutableReference(typ) = typ { + Some(Value::Type(*typ)) + } else { + None + } + }) +} + // fn as_slice(self) -> Option fn type_as_slice( arguments: Vec<(Value, Location)>, @@ -1009,6 +1058,14 @@ fn type_is_field(arguments: Vec<(Value, Location)>, location: Location) -> IResu Ok(Value::Bool(matches!(typ, Type::FieldElement))) } +// fn is_unit(self) -> bool +fn type_is_unit(arguments: Vec<(Value, Location)>, location: Location) -> IResult { + let value = check_one_argument(arguments, location)?; + let typ = get_type(value)?; + + Ok(Value::Bool(matches!(typ, Type::Unit))) +} + // fn type_of(x: T) -> Type fn type_of(arguments: Vec<(Value, Location)>, location: Location) -> IResult { let (value, _) = check_one_argument(arguments, location)?; @@ -1111,6 +1168,49 @@ fn typed_expr_get_type( option(return_type, option_value) } +// fn as_mutable_reference(self) -> Option +fn unresolved_type_as_mutable_reference( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + unresolved_type_as(interner, arguments, return_type, location, |typ| { + if let UnresolvedTypeData::MutableReference(typ) = typ { + Some(Value::UnresolvedType(typ.typ)) + } else { + None + } + }) +} + +// fn as_slice(self) -> Option +fn unresolved_type_as_slice( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + unresolved_type_as(interner, arguments, return_type, location, |typ| { + if let UnresolvedTypeData::Slice(typ) = typ { + Some(Value::UnresolvedType(typ.typ)) + } else { + None + } + }) +} + +// fn is_bool(self) -> bool +fn unresolved_type_is_bool( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let self_argument = check_one_argument(arguments, location)?; + let typ = get_unresolved_type(interner, self_argument)?; + Ok(Value::Bool(matches!(typ, UnresolvedTypeData::Bool))) +} + // fn is_field(self) -> bool fn unresolved_type_is_field( interner: &NodeInterner, @@ -1122,6 +1222,36 @@ fn unresolved_type_is_field( Ok(Value::Bool(matches!(typ, UnresolvedTypeData::FieldElement))) } +// fn is_unit(self) -> bool +fn unresolved_type_is_unit( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let self_argument = check_one_argument(arguments, location)?; + let typ = get_unresolved_type(interner, self_argument)?; + Ok(Value::Bool(matches!(typ, UnresolvedTypeData::Unit))) +} + +// Helper function for implementing the `unresolved_type_as_...` functions. +fn unresolved_type_as( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, + f: F, +) -> IResult +where + F: FnOnce(UnresolvedTypeData) -> Option, +{ + let value = check_one_argument(arguments, location)?; + let typ = get_unresolved_type(interner, value)?; + + let option_value = f(typ); + + option(return_type, option_value) +} + // fn zeroed() -> T fn zeroed(return_type: Type) -> IResult { match return_type { @@ -1192,14 +1322,14 @@ fn zeroed(return_type: Type) -> IResult { Ok(Value::Pointer(Shared::new(element), false)) } // Optimistically assume we can resolve this type later or that the value is unused - Type::TypeVariable(_, _) + Type::TypeVariable(_) | Type::Forall(_, _) | Type::Constant(..) | Type::InfixExpr(..) | Type::Quoted(_) | Type::Error | Type::TraitAsType(..) - | Type::NamedGeneric(_, _, _) => Ok(Value::Zeroed(return_type)), + | Type::NamedGeneric(_, _) => Ok(Value::Zeroed(return_type)), } } @@ -1510,7 +1640,8 @@ fn expr_as_for_range( ) -> IResult { expr_as(interner, arguments, return_type, location, |expr| { if let ExprValue::Statement(StatementKind::For(for_statement)) = expr { - if let ForRange::Range(from, to) = for_statement.range { + if let ForRange::Range(bounds) = for_statement.range { + let (from, to) = bounds.into_half_open(); let identifier = Value::Quoted(Rc::new(vec![Token::Ident(for_statement.identifier.0.contents)])); let from = Value::expression(from.kind); @@ -2075,7 +2206,7 @@ fn fmtstr_quoted_contents( // fn fresh_type_variable() -> Type fn fresh_type_variable(interner: &NodeInterner) -> IResult { - Ok(Value::Type(interner.next_type_variable())) + Ok(Value::Type(interner.next_type_variable_with_kind(Kind::Any))) } // fn add_attribute(self, attribute: str) @@ -2118,7 +2249,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); } @@ -2320,7 +2451,7 @@ fn function_def_set_parameters( let parameter_pattern = parse( interpreter.elaborator.interner, (tuple.pop().unwrap(), parameters_argument_location), - parser::pattern(), + Parser::parse_pattern_or_error, "a pattern", )?; @@ -2331,7 +2462,6 @@ fn function_def_set_parameters( DefinitionKind::Local(None), &mut parameter_idents, true, // warn_if_unused - None, ) }); @@ -2421,7 +2551,7 @@ fn module_add_item( let module_id = get_module(self_argument)?; let module_data = interpreter.elaborator.get_module(module_id); - let parser = parser::top_level_items(); + let parser = Parser::parse_top_level_items; let top_level_statements = parse(interpreter.elaborator.interner, item, parser, "a top-level item")?; @@ -2602,7 +2732,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/interpreter/builtin/builtin_helpers.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs index 20303e49e15..3f9d92cfe88 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs @@ -5,7 +5,9 @@ use acvm::FieldElement; use noirc_errors::Location; use crate::hir::comptime::display::tokens_to_string; +use crate::hir::comptime::value::add_token_spans; use crate::lexer::Lexer; +use crate::parser::Parser; use crate::{ ast::{ BlockExpression, ExpressionKind, Ident, IntegerBitSize, LValue, Pattern, Signedness, @@ -14,7 +16,7 @@ use crate::{ hir::{ comptime::{ errors::IResult, - value::{add_token_spans, ExprValue, TypedExpr}, + value::{ExprValue, TypedExpr}, Interpreter, InterpreterError, Value, }, def_map::ModuleId, @@ -24,9 +26,7 @@ use crate::{ function::{FuncMeta, FunctionBody}, stmt::HirPattern, }, - macros_api::{NodeInterner, StructId}, - node_interner::{FuncId, TraitId, TraitImplId}, - parser::NoirParser, + node_interner::{FuncId, NodeInterner, StructId, TraitId, TraitImplId}, token::{SecondaryAttribute, Token, Tokens}, QuotedType, Type, }; @@ -403,27 +403,32 @@ pub(super) fn lex(input: &str) -> Vec { tokens } -pub(super) fn parse( +pub(super) fn parse<'a, T, F>( interner: &NodeInterner, (value, location): (Value, Location), - parser: impl NoirParser, + parser: F, rule: &'static str, -) -> IResult { - let parser = parser.then_ignore(chumsky::primitive::end()); +) -> IResult +where + F: FnOnce(&mut Parser<'a>) -> T, +{ let tokens = get_quoted((value, location))?; let quoted = add_token_spans(tokens.clone(), location.span); parse_tokens(tokens, quoted, interner, location, parser, rule) } -pub(super) fn parse_tokens( +pub(super) fn parse_tokens<'a, T, F>( tokens: Rc>, quoted: Tokens, interner: &NodeInterner, location: Location, - parser: impl NoirParser, + parsing_function: F, rule: &'static str, -) -> IResult { - parser.parse(quoted).map_err(|mut errors| { +) -> IResult +where + F: FnOnce(&mut Parser<'a>) -> T, +{ + Parser::for_tokens(quoted).parse_result(parsing_function).map_err(|mut errors| { let error = errors.swap_remove(0); let tokens = tokens_to_string(tokens, interner); InterpreterError::FailedToParseMacro { error, tokens, rule, file: location.file } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/foreign.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/foreign.rs index 5ae60bb4d00..d1ab6a1dabd 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/foreign.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/foreign.rs @@ -6,7 +6,7 @@ use noirc_errors::Location; use crate::{ hir::comptime::{errors::IResult, InterpreterError, Value}, - macros_api::NodeInterner, + node_interner::NodeInterner, }; use super::builtin::builtin_helpers::{ 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 5b03b27e0b2..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 @@ -14,7 +14,7 @@ use crate::hir::def_collector::dc_crate::DefCollector; use crate::hir::def_collector::dc_mod::collect_defs; use crate::hir::def_map::{CrateDefMap, LocalModuleId, ModuleData}; use crate::hir::{Context, ParsedFiles}; -use crate::parser::parse_program; +use crate::parse_program; fn interpret_helper(src: &str) -> Result { let file = FileId::default(); @@ -28,7 +28,8 @@ fn interpret_helper(src: &str) -> Result { location, Vec::new(), Vec::new(), - false, + false, // is contract + false, // is struct ))); assert_eq!(root, module_id); @@ -77,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 { @@ -160,6 +178,19 @@ fn for_loop() { assert_eq!(result, Value::U8(15)); } +#[test] +fn for_loop_inclusive() { + let program = "comptime fn main() -> pub u8 { + let mut x = 0; + for i in 0 ..= 6 { + x += i; + } + x + }"; + let result = interpret(program); + assert_eq!(result, Value::U8(21)); +} + #[test] fn for_loop_u16() { let program = "comptime fn main() -> pub u16 { @@ -263,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 f01e188e498..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 @@ -1,7 +1,6 @@ use std::{borrow::Cow, rc::Rc, vec}; use acvm::{AcirField, FieldElement}; -use chumsky::Parser; use im::Vector; use iter_extended::{try_vecmap, vecmap}; use noirc_errors::{Location, Span}; @@ -9,17 +8,17 @@ use strum_macros::Display; use crate::{ ast::{ - ArrayLiteral, BlockExpression, ConstructorExpression, Ident, IntegerBitSize, LValue, - Pattern, Signedness, Statement, StatementKind, UnresolvedType, UnresolvedTypeData, + ArrayLiteral, BlockExpression, ConstructorExpression, Expression, ExpressionKind, Ident, + IntegerBitSize, LValue, Literal, Path, Pattern, Signedness, Statement, StatementKind, + UnresolvedType, UnresolvedTypeData, }, hir::{def_map::ModuleId, type_check::generics::TraitGenerics}, - hir_def::expr::{HirArrayLiteral, HirConstructorExpression, HirIdent, HirLambda, ImplKind}, - macros_api::{ - Expression, ExpressionKind, HirExpression, HirLiteral, Literal, NodeInterner, Path, - StructId, + hir_def::expr::{ + HirArrayLiteral, HirConstructorExpression, HirExpression, HirIdent, HirLambda, HirLiteral, + ImplKind, }, - node_interner::{ExprId, FuncId, StmtId, TraitId, TraitImplId}, - parser::{self, NoirParser, TopLevelStatement}, + node_interner::{ExprId, FuncId, NodeInterner, StmtId, StructId, TraitId, TraitImplId}, + parser::{Item, Parser}, token::{SpannedToken, Token, Tokens}, Kind, QuotedType, Shared, Type, TypeBindings, }; @@ -121,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), @@ -261,7 +260,8 @@ impl Value { tokens_to_parse.0.insert(0, SpannedToken::new(Token::LeftBrace, location.span)); tokens_to_parse.0.push(SpannedToken::new(Token::RightBrace, location.span)); - return match parser::expression().parse(tokens_to_parse) { + let parser = Parser::for_tokens(tokens_to_parse); + return match parser.parse_result(Parser::parse_expression_or_error) { Ok(expr) => Ok(expr), Err(mut errors) => { let error = errors.swap_remove(0); @@ -523,8 +523,8 @@ impl Value { self, location: Location, interner: &NodeInterner, - ) -> IResult> { - let parser = parser::top_level_items(); + ) -> IResult> { + let parser = Parser::parse_top_level_items; match self { Value::Quoted(tokens) => { parse_tokens(tokens, interner, parser, location, "top-level item") @@ -543,15 +543,18 @@ pub(crate) fn unwrap_rc(rc: Rc) -> T { Rc::try_unwrap(rc).unwrap_or_else(|rc| (*rc).clone()) } -fn parse_tokens( +fn parse_tokens<'a, T, F>( tokens: Rc>, interner: &NodeInterner, - parser: impl NoirParser, + parsing_function: F, location: Location, rule: &'static str, -) -> IResult { - let parser = parser.then_ignore(chumsky::primitive::end()); - match parser.parse(add_token_spans(tokens.clone(), location.span)) { +) -> IResult +where + F: FnOnce(&mut Parser<'a>) -> T, +{ + let parser = Parser::for_tokens(add_token_spans(tokens.clone(), location.span)); + match parser.parse_result(parsing_function) { Ok(expr) => Ok(expr), Err(mut errors) => { let error = errors.swap_remove(0); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index faf72e86fb4..16fd43ba2a2 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -14,7 +14,7 @@ use crate::{Generics, Type}; use crate::hir::resolution::import::{resolve_import, ImportDirective, PathResolution}; use crate::hir::Context; -use crate::macros_api::Expression; +use crate::ast::Expression; use crate::node_interner::{ FuncId, GlobalId, ModuleAttributes, NodeInterner, ReferenceId, StructId, TraitId, TraitImplId, TypeAliasId, @@ -23,7 +23,7 @@ use crate::node_interner::{ use crate::ast::{ ExpressionKind, GenericTypeArgs, Ident, ItemVisibility, LetStatement, Literal, NoirFunction, NoirStruct, NoirTrait, NoirTypeAlias, Path, PathKind, PathSegment, UnresolvedGenerics, - UnresolvedTraitConstraint, UnresolvedType, + UnresolvedTraitConstraint, UnresolvedType, UnsupportedNumericGenericType, }; use crate::parser::{ParserError, SortedModule}; @@ -111,6 +111,7 @@ pub struct UnresolvedGlobal { pub module_id: LocalModuleId, pub global_id: GlobalId, pub stmt_def: LetStatement, + pub visibility: ItemVisibility, } pub struct ModuleAttribute { @@ -231,12 +232,19 @@ impl From for CompilationError { CompilationError::ResolverError(value) } } + impl From for CompilationError { fn from(value: TypeCheckError) -> Self { CompilationError::TypeError(value) } } +impl From for CompilationError { + fn from(value: UnsupportedNumericGenericType) -> Self { + Self::ResolverError(value.into()) + } +} + impl DefCollector { pub fn new(def_map: CrateDefMap) -> DefCollector { DefCollector { @@ -507,7 +515,7 @@ impl DefCollector { } fn add_import_reference( - def_id: crate::macros_api::ModuleDefId, + def_id: crate::hir::def_map::ModuleDefId, name: &Ident, interner: &mut NodeInterner, file_id: FileId, @@ -547,6 +555,7 @@ fn inject_prelude( if let Ok(PathResolution { module_def_id, error }) = path_resolver::resolve_path( &context.def_maps, ModuleId { krate: crate_id, local_id: crate_root }, + None, path, &mut context.def_interner.usage_tracker, &mut None, @@ -564,6 +573,7 @@ fn inject_prelude( ImportDirective { visibility: ItemVisibility::Private, module_id: crate_root, + self_type_module_id: None, path: Path { segments, kind: PathKind::Plain, span: Span::default() }, alias: None, is_prelude: true, 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 b530e023152..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 @@ -11,13 +11,12 @@ use num_traits::Num; use rustc_hash::FxHashMap as HashMap; use crate::ast::{ - Documented, FunctionDefinition, Ident, ItemVisibility, LetStatement, ModuleDeclaration, - NoirFunction, NoirStruct, NoirTrait, NoirTraitImpl, NoirTypeAlias, Pattern, TraitImplItemKind, - TraitItem, TypeImpl, + Documented, Expression, FunctionDefinition, Ident, ItemVisibility, LetStatement, + ModuleDeclaration, NoirFunction, NoirStruct, NoirTrait, NoirTraitImpl, NoirTypeAlias, Pattern, + TraitImplItemKind, TraitItem, TypeImpl, UnresolvedType, UnresolvedTypeData, }; use crate::hir::resolution::errors::ResolverError; -use crate::macros_api::{Expression, NodeInterner, StructId, UnresolvedType, UnresolvedTypeData}; -use crate::node_interner::{ModuleAttributes, ReferenceId}; +use crate::node_interner::{ModuleAttributes, NodeInterner, ReferenceId, StructId}; use crate::token::SecondaryAttribute; use crate::usage_tracker::UnusedItem; use crate::{ @@ -82,6 +81,7 @@ pub fn collect_defs( collector.def_collector.imports.push(ImportDirective { visibility: import.visibility, module_id: collector.module_id, + self_type_module_id: None, path: import.path, alias: import.alias, is_prelude: false, @@ -357,7 +357,12 @@ impl<'a> ModCollector<'a> { if context.def_interner.is_in_lsp_mode() { let parent_module_id = ModuleId { krate, local_id: self.module_id }; let name = name.to_string(); - context.def_interner.register_type_alias(type_alias_id, name, parent_module_id); + context.def_interner.register_type_alias( + type_alias_id, + name, + visibility, + parent_module_id, + ); } } errors @@ -386,7 +391,8 @@ impl<'a> ModCollector<'a> { Vec::new(), Vec::new(), false, - false, + false, // is contract + false, // is struct ) { Ok(module_id) => TraitId(ModuleId { krate, local_id: module_id.local_id }), Err(error) => { @@ -531,8 +537,10 @@ impl<'a> ModCollector<'a> { associated_types.push(ResolvedGeneric { name: Rc::new(name.to_string()), - type_var: TypeVariable::unbound(type_variable_id), - kind: Kind::Numeric(Box::new(typ)), + type_var: TypeVariable::unbound( + type_variable_id, + Kind::Numeric(Box::new(typ)), + ), span: name.span(), }); } @@ -556,8 +564,7 @@ impl<'a> ModCollector<'a> { let type_variable_id = context.def_interner.next_type_variable_id(); associated_types.push(ResolvedGeneric { name: Rc::new(name.to_string()), - type_var: TypeVariable::unbound(type_variable_id), - kind: Kind::Normal, + type_var: TypeVariable::unbound(type_variable_id, Kind::Normal), span: name.span(), }); } @@ -589,7 +596,12 @@ impl<'a> ModCollector<'a> { if context.def_interner.is_in_lsp_mode() { let parent_module_id = ModuleId { krate, local_id: self.module_id }; - context.def_interner.register_trait(trait_id, name.to_string(), parent_module_id); + context.def_interner.register_trait( + trait_id, + name.to_string(), + visibility, + parent_module_id, + ); } self.def_collector.items.traits.insert(trait_id, unresolved); @@ -619,6 +631,7 @@ impl<'a> ModCollector<'a> { submodule.contents.inner_attributes.clone(), true, submodule.is_contract, + false, // is struct ) { Ok(child) => { self.collect_attributes( @@ -718,7 +731,8 @@ impl<'a> ModCollector<'a> { mod_decl.outer_attributes.clone(), ast.inner_attributes.clone(), true, - false, + false, // is contract + false, // is struct ) { Ok(child_mod_id) => { self.collect_attributes( @@ -770,6 +784,7 @@ impl<'a> ModCollector<'a> { inner_attributes: Vec, add_to_parent_scope: bool, is_contract: bool, + is_struct: bool, ) -> Result { push_child_module( &mut context.def_interner, @@ -782,6 +797,7 @@ impl<'a> ModCollector<'a> { inner_attributes, add_to_parent_scope, is_contract, + is_struct, ) } @@ -817,6 +833,7 @@ fn push_child_module( inner_attributes: Vec, add_to_parent_scope: bool, is_contract: bool, + is_struct: bool, ) -> Result { // Note: the difference between `location` and `mod_location` is: // - `mod_location` will point to either the token "foo" in `mod foo { ... }` @@ -826,8 +843,14 @@ fn push_child_module( // Eventually the location put in `ModuleData` is used for codelenses about `contract`s, // so we keep using `location` so that it continues to work as usual. let location = Location::new(mod_name.span(), mod_location.file); - let new_module = - ModuleData::new(Some(parent), location, outer_attributes, inner_attributes, is_contract); + let new_module = ModuleData::new( + Some(parent), + location, + outer_attributes, + inner_attributes, + is_contract, + is_struct, + ); let module_id = def_map.modules.insert(new_module); let modules = &mut def_map.modules; @@ -866,7 +889,7 @@ fn push_child_module( ); if interner.is_in_lsp_mode() { - interner.register_module(mod_id, mod_name.0.contents.clone()); + interner.register_module(mod_id, visibility, mod_name.0.contents.clone()); } } @@ -962,8 +985,9 @@ pub fn collect_struct( location, Vec::new(), Vec::new(), - false, - false, + false, // add to parent scope + false, // is contract + true, // is struct ) { Ok(module_id) => { interner.new_struct(&unresolved, resolved_generics, krate, module_id.local_id, file_id) @@ -989,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 { @@ -1177,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(), @@ -1191,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 = @@ -1207,7 +1236,7 @@ pub(crate) fn collect_global( interner.set_doc_comments(ReferenceId::Global(global_id), doc_comments); - let global = UnresolvedGlobal { file_id, module_id, global_id, stmt_def: global }; + let global = UnresolvedGlobal { file_id, module_id, global_id, stmt_def: global, visibility }; (global, error) } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/errors.rs index 2f47505db94..d72f493092d 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/errors.rs @@ -1,4 +1,4 @@ -use crate::ast::{Ident, ItemVisibility, Path, UnresolvedTypeData}; +use crate::ast::{Ident, ItemVisibility, Path, UnsupportedNumericGenericType}; use crate::hir::resolution::import::PathResolutionError; use crate::hir::type_check::generics::TraitGenerics; @@ -71,8 +71,6 @@ pub enum DefCollectorErrorKind { "Either the type or the trait must be from the same crate as the trait implementation" )] TraitImplOrphaned { span: Span }, - #[error("The only supported types of numeric generics are integers, fields, and booleans")] - UnsupportedNumericGenericType { ident: Ident, typ: UnresolvedTypeData }, #[error("impl has stricter requirements than trait")] ImplIsStricterThanTrait { constraint_typ: crate::Type, @@ -82,6 +80,8 @@ pub enum DefCollectorErrorKind { trait_method_name: String, trait_method_span: Span, }, + #[error("{0}")] + UnsupportedNumericGenericType(#[from] UnsupportedNumericGenericType), } impl DefCollectorErrorKind { @@ -90,6 +90,19 @@ impl DefCollectorErrorKind { } } +impl<'a> From<&'a UnsupportedNumericGenericType> for Diagnostic { + fn from(error: &'a UnsupportedNumericGenericType) -> Diagnostic { + let name = &error.ident.0.contents; + let typ = &error.typ; + + Diagnostic::simple_error( + format!("{name} has a type of {typ}. The only supported numeric generic types are `u1`, `u8`, `u16`, and `u32`."), + "Unsupported numeric generic type".to_string(), + error.ident.0.span(), + ) + } +} + impl fmt::Display for DuplicateType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { @@ -266,15 +279,6 @@ impl<'a> From<&'a DefCollectorErrorKind> for Diagnostic { "Either the type or the trait must be from the same crate as the trait implementation".into(), *span, ), - DefCollectorErrorKind::UnsupportedNumericGenericType { ident, typ } => { - let name = &ident.0.contents; - - Diagnostic::simple_error( - format!("{name} has a type of {typ}. The only supported types of numeric generics are integers and fields"), - "Unsupported numeric generic type".to_string(), - ident.0.span(), - ) - } DefCollectorErrorKind::ImplIsStricterThanTrait { constraint_typ, constraint_name, constraint_generics, constraint_span, trait_method_name, trait_method_span } => { let constraint = format!("{}{}", constraint_name, constraint_generics); @@ -286,6 +290,7 @@ impl<'a> From<&'a DefCollectorErrorKind> for Diagnostic { diag.add_secondary(format!("definition of `{trait_method_name}` from trait"), *trait_method_span); diag } + DefCollectorErrorKind::UnsupportedNumericGenericType(err) => err.into(), } } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/mod.rs index 9afe897a167..60ee2c52842 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/mod.rs @@ -2,7 +2,8 @@ use crate::graph::CrateId; use crate::hir::def_collector::dc_crate::{CompilationError, DefCollector}; use crate::hir::Context; use crate::node_interner::{FuncId, GlobalId, NodeInterner, StructId}; -use crate::parser::{parse_program, ParsedModule, ParserError}; +use crate::parse_program; +use crate::parser::{ParsedModule, ParserError}; use crate::token::{FunctionAttribute, SecondaryAttribute, TestScope}; use fm::{FileId, FileManager}; use noirc_arena::{Arena, Index}; @@ -102,7 +103,8 @@ impl CrateDefMap { location, Vec::new(), ast.inner_attributes.clone(), - false, + false, // is contract + false, // is struct )); let def_map = CrateDefMap { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/module_data.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/module_data.rs index ff36bcf27d6..fe6fe8285d3 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/module_data.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/module_data.rs @@ -26,6 +26,9 @@ pub struct ModuleData { /// True if this module is a `contract Foo { ... }` module containing contract functions pub is_contract: bool, + /// True if this module is actually a struct + pub is_struct: bool, + pub attributes: Vec, } @@ -36,6 +39,7 @@ impl ModuleData { outer_attributes: Vec, inner_attributes: Vec, is_contract: bool, + is_struct: bool, ) -> ModuleData { let mut attributes = outer_attributes; attributes.extend(inner_attributes); @@ -47,6 +51,7 @@ impl ModuleData { definitions: ItemScope::default(), location, is_contract, + is_struct, attributes, } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/mod.rs index c631edfa889..015b7deb6e0 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/mod.rs @@ -11,7 +11,7 @@ use crate::graph::{CrateGraph, CrateId}; use crate::hir_def::function::FuncMeta; use crate::node_interner::{FuncId, NodeInterner, StructId}; use crate::parser::ParserError; -use crate::{Generics, Kind, ParsedModule, ResolvedGeneric, Type, TypeVariable}; +use crate::{Generics, Kind, ParsedModule, ResolvedGeneric, TypeVariable}; use def_collector::dc_crate::CompilationError; use def_map::{Contract, CrateDefMap}; use fm::{FileId, FileManager}; @@ -280,19 +280,20 @@ impl Context<'_, '_> { vecmap(generics, |generic| { // Map the generic to a fresh type variable let id = interner.next_type_variable_id(); - let type_var = TypeVariable::unbound(id); + + let type_var_kind = generic.kind().unwrap_or_else(|err| { + errors.push((err.into(), file_id)); + // When there's an error, unify with any other kinds + Kind::Any + }); + let type_var = TypeVariable::unbound(id, type_var_kind); let ident = generic.ident(); let span = ident.0.span(); // Check for name collisions of this generic let name = Rc::new(ident.0.contents.clone()); - let kind = generic.kind().unwrap_or_else(|err| { - errors.push((err.into(), file_id)); - Kind::Numeric(Box::new(Type::Error)) - }); - - ResolvedGeneric { name, type_var, kind, span } + ResolvedGeneric { name, type_var, span } }) } 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 f3c61a7fbe2..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,9 +1,13 @@ +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::{ - ast::Ident, hir::comptime::InterpreterError, parser::ParserError, usage_tracker::UnusedItem, + ast::{Ident, UnsupportedNumericGenericType}, + hir::comptime::InterpreterError, + parser::ParserError, + usage_tracker::UnusedItem, Type, }; @@ -103,8 +107,6 @@ pub enum ResolverError { NoPredicatesAttributeOnUnconstrained { ident: Ident }, #[error("#[fold] attribute is only allowed on constrained functions")] FoldAttributeOnUnconstrained { ident: Ident }, - #[error("The only supported types of numeric generics are integers, fields, and booleans")] - UnsupportedNumericGenericType { ident: Ident, typ: Type }, #[error("expected type, found numeric generic parameter")] NumericGenericUsedForType { name: String, span: Span }, #[error("Invalid array length construction")] @@ -124,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")] @@ -133,8 +140,24 @@ pub enum ResolverError { MutatingComptimeInNonComptimeContext { name: String, span: Span }, #[error("Failed to parse `{statement}` as an expression")] InvalidInternedStatementInExpr { statement: String, span: Span }, + #[error("{0}")] + 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 { @@ -443,15 +466,6 @@ impl<'a> From<&'a ResolverError> for Diagnostic { diag.add_note("The `#[fold]` attribute specifies whether a constrained function should be treated as a separate circuit rather than inlined into the program entry point".to_owned()); diag } - ResolverError::UnsupportedNumericGenericType { ident , typ } => { - let name = &ident.0.contents; - - Diagnostic::simple_error( - format!("{name} has a type of {typ}. The only supported types of numeric generics are integers, fields, and booleans."), - "Unsupported numeric generic type".to_string(), - ident.0.span(), - ) - } ResolverError::NumericGenericUsedForType { name, span } => { Diagnostic::simple_error( format!("expected type, found numeric generic parameter {name}"), @@ -544,6 +558,7 @@ impl<'a> From<&'a ResolverError> for Diagnostic { *span, ) }, + ResolverError::UnsupportedNumericGenericType(err) => err.into(), ResolverError::TypeIsMorePrivateThenItem { typ, item, span } => { Diagnostic::simple_warning( format!("Type `{typ}` is more private than item `{item}`"), @@ -551,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 73a1b1ccb7c..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 @@ -5,17 +5,20 @@ use crate::graph::CrateId; use crate::hir::def_collector::dc_crate::CompilationError; use crate::node_interner::ReferenceId; use crate::usage_tracker::UsageTracker; + use std::collections::BTreeMap; 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 { pub visibility: ItemVisibility, pub module_id: LocalModuleId, + pub self_type_module_id: Option, pub path: Path, pub alias: Option, pub is_prelude: bool, @@ -92,18 +95,15 @@ pub fn resolve_import( path_references: &mut Option<&mut Vec>, ) -> Result { let module_scope = import_directive.module_id; - let NamespaceResolution { - module_id: resolved_module, - namespace: resolved_namespace, - mut error, - } = resolve_path_to_ns( - import_directive, - crate_id, - crate_id, - def_maps, - usage_tracker, - path_references, - )?; + let NamespaceResolution { module_id: resolved_module, namespace: resolved_namespace, error } = + resolve_path_to_ns( + import_directive, + crate_id, + crate_id, + def_maps, + usage_tracker, + path_references, + )?; let name = resolve_path_name(import_directive); @@ -113,14 +113,16 @@ pub fn resolve_import( .map(|(_, visibility, _)| visibility) .expect("Found empty namespace"); - error = error.or_else(|| { - if can_reference_module_id( - def_maps, - crate_id, - import_directive.module_id, - resolved_module, - visibility, - ) { + let error = error.or_else(|| { + if import_directive.self_type_module_id == Some(resolved_module) + || can_reference_module_id( + def_maps, + crate_id, + import_directive.module_id, + resolved_module, + visibility, + ) + { None } else { Some(PathResolutionError::Private(name.clone())) @@ -168,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, ); @@ -198,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, ) @@ -223,7 +225,7 @@ fn resolve_path_to_ns( import_path, parent_module_id, def_maps, - false, + false, // plain or crate usage_tracker, path_references, ) @@ -252,7 +254,7 @@ fn resolve_path_from_crate_root( import_path, starting_mod, def_maps, - false, + true, // plain or crate usage_tracker, path_references, ) @@ -265,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 { @@ -330,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, @@ -408,6 +410,7 @@ fn resolve_external_dep( let dep_directive = ImportDirective { visibility: ItemVisibility::Private, module_id: dep_module.local_id, + self_type_module_id: directive.self_type_module_id, path, alias: directive.alias.clone(), is_prelude: false, @@ -422,47 +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, - ) - } - } -} - -// 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)) -} 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/path_resolver.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/path_resolver.rs index 50089d849ae..562366fae77 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/path_resolver.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/path_resolver.rs @@ -2,6 +2,7 @@ use super::import::{resolve_import, ImportDirective, PathResolution, PathResolut use crate::ast::{ItemVisibility, Path}; use crate::node_interner::ReferenceId; use crate::usage_tracker::UsageTracker; + use std::collections::BTreeMap; use crate::graph::CrateId; @@ -28,11 +29,13 @@ pub trait PathResolver { pub struct StandardPathResolver { // Module that we are resolving the path in module_id: ModuleId, + // The module of the self type, if any (for example, the ModuleId of a struct) + self_type_module_id: Option, } impl StandardPathResolver { - pub fn new(module_id: ModuleId) -> StandardPathResolver { - Self { module_id } + pub fn new(module_id: ModuleId, self_type_module_id: Option) -> StandardPathResolver { + Self { module_id, self_type_module_id } } } @@ -44,7 +47,14 @@ impl PathResolver for StandardPathResolver { usage_tracker: &mut UsageTracker, path_references: &mut Option<&mut Vec>, ) -> PathResolutionResult { - resolve_path(def_maps, self.module_id, path, usage_tracker, path_references) + resolve_path( + def_maps, + self.module_id, + self.self_type_module_id, + path, + usage_tracker, + path_references, + ) } fn local_module_id(&self) -> LocalModuleId { @@ -61,6 +71,7 @@ impl PathResolver for StandardPathResolver { pub fn resolve_path( def_maps: &BTreeMap, module_id: ModuleId, + self_type_module_id: Option, path: Path, usage_tracker: &mut UsageTracker, path_references: &mut Option<&mut Vec>, @@ -69,6 +80,7 @@ pub fn resolve_path( let import = ImportDirective { visibility: ItemVisibility::Private, module_id: module_id.local_id, + self_type_module_id, path, alias: None, is_prelude: false, 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 00e73e682e8..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 @@ -5,14 +5,14 @@ use noirc_errors::CustomDiagnostic as Diagnostic; use noirc_errors::Span; use thiserror::Error; -use crate::ast::ConstrainKind; -use crate::ast::{BinaryOpKind, FunctionReturnType, IntegerBitSize, Signedness}; +use crate::ast::{ + BinaryOpKind, ConstrainKind, FunctionReturnType, Ident, IntegerBitSize, Signedness, +}; use crate::hir::resolution::errors::ResolverError; use crate::hir_def::expr::HirBinaryOp; use crate::hir_def::traits::TraitConstraint; use crate::hir_def::types::Type; -use crate::macros_api::Ident; -use crate::macros_api::NodeInterner; +use crate::node_interner::NodeInterner; #[derive(Error, Debug, Clone, PartialEq, Eq)] pub enum Source { @@ -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 b86e2350279..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 @@ -4,8 +4,7 @@ use iter_extended::vecmap; use crate::{ hir_def::traits::NamedType, - macros_api::NodeInterner, - node_interner::{FuncId, TraitId, TypeAliasId}, + node_interner::{FuncId, NodeInterner, TraitId, TypeAliasId}, ResolvedGeneric, StructType, Type, }; @@ -134,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 063b960863c..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 @@ -70,7 +70,14 @@ pub enum ImplKind { /// and eventually linked to this id. The boolean indicates whether the impl /// is already assumed to exist - e.g. when resolving a path such as `T::default` /// when there is a corresponding `T: Default` constraint in scope. - TraitMethod(TraitMethodId, TraitConstraint, bool), + TraitMethod(TraitMethod), +} + +#[derive(Debug, Clone)] +pub struct TraitMethod { + pub method_id: TraitMethodId, + pub constraint: TraitConstraint, + pub assumed: bool, } impl Eq for HirIdent {} @@ -243,11 +250,13 @@ 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(method_id, constraint, false)) + (id, ImplKind::TraitMethod(TraitMethod { method_id, constraint, assumed: false })) } }; let func_var = HirIdent { location, id, impl_kind }; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/function.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/function.rs index 39c87607446..6ecfdefe996 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/function.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/function.rs @@ -5,11 +5,10 @@ use noirc_errors::{Location, Span}; use super::expr::{HirBlockExpression, HirExpression, HirIdent}; use super::stmt::HirPattern; use super::traits::TraitConstraint; -use crate::ast::{FunctionKind, FunctionReturnType, Visibility}; +use crate::ast::{BlockExpression, FunctionKind, FunctionReturnType, Visibility}; use crate::graph::CrateId; use crate::hir::def_map::LocalModuleId; -use crate::macros_api::{BlockExpression, StructId}; -use crate::node_interner::{ExprId, NodeInterner, TraitId, TraitImplId}; +use crate::node_interner::{ExprId, NodeInterner, StructId, TraitId, TraitImplId}; use crate::token::CustomAttribute; use crate::{ResolvedGeneric, Type}; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/stmt.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/stmt.rs index 0b4dbeb3006..b97e99583bb 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/stmt.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/stmt.rs @@ -1,7 +1,7 @@ use super::expr::HirIdent; use crate::ast::Ident; -use crate::macros_api::SecondaryAttribute; use crate::node_interner::{ExprId, StmtId}; +use crate::token::SecondaryAttribute; use crate::Type; use fm::FileId; use noirc_errors::{Location, Span}; 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 0572ba403a1..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 @@ -3,12 +3,12 @@ use rustc_hash::FxHashMap as HashMap; use crate::ast::{Ident, NoirFunction}; use crate::hir::type_check::generics::TraitGenerics; +use crate::ResolvedGeneric; use crate::{ graph::CrateId, node_interner::{FuncId, TraitId, TraitMethodId}, Generics, Type, TypeBindings, TypeVariable, }; -use crate::{ResolvedGeneric, TypeVariableKind}; use fm::FileId; use noirc_errors::{Location, Span}; @@ -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 { @@ -168,10 +185,12 @@ impl Trait { }); TraitConstraint { - typ: Type::TypeVariable(self.self_type_typevar.clone(), TypeVariableKind::Normal), - trait_generics: TraitGenerics { ordered, named }, - trait_id: self.id, - span, + typ: Type::TypeVariable(self.self_type_typevar.clone()), + 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 c170d2cc08f..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}, }; @@ -78,7 +80,7 @@ pub enum Type { /// is a process that replaces each NamedGeneric in a generic function with a TypeVariable. /// Doing this at each call site of a generic function is how they can be called with /// different argument types each time. - TypeVariable(TypeVariable, TypeVariableKind), + TypeVariable(TypeVariable), /// `impl Trait` when used in a type position. /// These are only matched based on the TraitId. The trait name parameter is only @@ -87,7 +89,7 @@ pub enum Type { /// NamedGenerics are the 'T' or 'U' in a user-defined generic function /// like `fn foo(...) {}`. Unlike TypeVariables, they cannot be bound over. - NamedGeneric(TypeVariable, Rc, Kind), + NamedGeneric(TypeVariable, Rc), /// A functions with arguments, a return type and environment. /// the environment should be `Unit` by default, @@ -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), @@ -134,34 +138,83 @@ pub enum Type { /// is expected (such as in an array length position) are expected to be of kind `Kind::Numeric`. #[derive(PartialEq, Eq, Clone, Hash, Debug, PartialOrd, Ord)] pub enum Kind { + /// Can bind to any type + // TODO(https://github.com/noir-lang/noir/issues/6194): evaluate need for and usage of + Any, + + /// Can bind to any type, except Type::Constant and Type::InfixExpr Normal, + + /// A generic integer or field type. This is a more specific kind of TypeVariable + /// that can only be bound to Type::Field, Type::Integer, or other polymorphic integers. + /// This is the type of undecorated integer literals like `46`. Typing them in this way + /// allows them to be polymorphic over the actual integer/field type used without requiring + /// type annotations on each integer literal. + IntegerOrField, + + /// A generic integer type. This is a more specific kind of TypeVariable + /// that can only be bound to Type::Integer, or other polymorphic integers. + Integer, + + /// Can bind to a Type::Constant or Type::InfixExpr of the given kind Numeric(Box), } 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 matches_opt(&self, other: Option) -> bool { - other.as_ref().map_or(true, |other_kind| self.unifies(other_kind)) + 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 { Self::Numeric(Box::new(Type::Integer(Signedness::Unsigned, IntegerBitSize::ThirtyTwo))) } + pub(crate) fn follow_bindings(&self) -> Self { + match self { + Self::Any => Self::Any, + Self::Normal => Self::Normal, + Self::Integer => Self::Integer, + Self::IntegerOrField => Self::IntegerOrField, + Self::Numeric(typ) => Self::Numeric(Box::new(typ.follow_bindings())), + } + } + /// Unifies this kind with the other. Returns true on success pub(crate) fn unifies(&self, other: &Kind) -> bool { match (self, other) { - (Kind::Normal, Kind::Normal) => true, + // Kind::Any unifies with everything + (Kind::Any, _) | (_, Kind::Any) => true, + + // Kind::Normal unifies with Kind::Integer and Kind::IntegerOrField + (Kind::Normal, Kind::Integer | Kind::IntegerOrField) + | (Kind::Integer | Kind::IntegerOrField, Kind::Normal) => true, + + // Kind::Integer unifies with Kind::IntegerOrField + (Kind::Integer | Kind::IntegerOrField, Kind::Integer | Kind::IntegerOrField) => true, + + // Kind::Numeric unifies along its Type argument (Kind::Numeric(lhs), Kind::Numeric(rhs)) => { let mut bindings = TypeBindings::new(); let unifies = lhs.try_unify(rhs, &mut bindings).is_ok(); @@ -170,7 +223,9 @@ impl Kind { } unifies } - _ => false, + + // everything unifies with itself + (lhs, rhs) => lhs == rhs, } } @@ -181,18 +236,49 @@ impl Kind { Err(UnificationError) } } + + /// Returns the default type this type variable should be bound to if it is still unbound + /// during monomorphization. + pub(crate) fn default_type(&self) -> Option { + match self { + Kind::Any => None, + Kind::IntegerOrField => Some(Type::default_int_or_field_type()), + Kind::Integer => Some(Type::default_int_type()), + Kind::Normal => None, + 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 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { + Kind::Any => write!(f, "any"), Kind::Normal => write!(f, "normal"), + Kind::Integer => write!(f, "int"), + Kind::IntegerOrField => write!(f, "intOrField"), Kind::Numeric(typ) => write!(f, "numeric {}", typ), } } } #[derive(Debug, PartialEq, Eq, Copy, Clone, Hash, PartialOrd, Ord)] +#[cfg_attr(test, derive(strum_macros::EnumIter))] pub enum QuotedType { Expr, Quoted, @@ -209,15 +295,14 @@ pub enum QuotedType { CtString, } -/// A list of TypeVariableIds to bind to a type. Storing the +/// A list of (TypeVariableId, Kind)'s to bind to a type. Storing the /// TypeVariable in addition to the matching TypeVariableId allows /// the binding to later be undone if needed. -pub type TypeBindings = HashMap; +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. @@ -228,15 +313,21 @@ 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. +/// as names or kinds pub type GenericTypeVars = Vec; /// Corresponds to generic lists such as `` with additional @@ -248,17 +339,20 @@ pub type Generics = Vec; pub struct ResolvedGeneric { pub name: Rc, pub type_var: TypeVariable, - pub kind: Kind, pub span: Span, } impl ResolvedGeneric { pub fn as_named_generic(self) -> Type { - Type::NamedGeneric(self.type_var, self.name, self.kind) + Type::NamedGeneric(self.type_var, self.name) + } + + pub fn kind(&self) -> Kind { + self.type_var.kind() } pub(crate) fn is_numeric(&self) -> bool { - self.kind.is_numeric() + self.kind().is_numeric() } } @@ -274,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 @@ -298,7 +394,7 @@ impl StructType { name: Ident, location: Location, - fields: Vec<(Ident, Type)>, + fields: Vec, generics: Generics, ) -> StructType { StructType { id, fields, name, location, generics } @@ -308,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; } @@ -316,39 +412,68 @@ 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() .zip(generic_args) - .map(|(old, new)| (old.type_var.id(), (old.type_var.clone(), new.clone()))) + .map(|(old, new)| { + ( + old.type_var.id(), + (old.type_var.clone(), old.type_var.kind(), new.clone()), + ) + }) .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(), new.clone()))) - .collect(); - - vecmap(&self.fields, |(name, typ)| { - let name = name.0.contents.clone(); - (name, typ.substitute(&substitutions)) - }) + .map(|(old, new)| { + (old.type_var.id(), (old.type_var.clone(), old.type_var.kind(), new.clone())) + }) + .collect() } /// Returns the name and raw types of each field of this type. @@ -357,30 +482,34 @@ 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); } } /// Instantiate this struct type, returning a Vec of the new generic args (in /// the same order as self.generics) pub fn instantiate(&self, interner: &mut NodeInterner) -> Vec { - vecmap(&self.generics, |_| interner.next_type_variable()) + vecmap(&self.generics, |generic| interner.next_type_variable_with_kind(generic.kind())) } } @@ -454,7 +583,9 @@ impl TypeAlias { .generics .iter() .zip(generic_args) - .map(|(old, new)| (old.type_var.id(), (old.type_var.clone(), new.clone()))) + .map(|(old, new)| { + (old.type_var.id(), (old.type_var.clone(), old.type_var.kind(), new.clone())) + }) .collect(); self.typ.substitute(&substitutions) @@ -527,31 +658,14 @@ pub enum BinaryTypeOperator { Modulo, } -#[derive(Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord)] -pub enum TypeVariableKind { - /// Can bind to any type - Normal, - - /// A generic integer or field type. This is a more specific kind of TypeVariable - /// that can only be bound to Type::Field, Type::Integer, or other polymorphic integers. - /// This is the type of undecorated integer literals like `46`. Typing them in this way - /// allows them to be polymorphic over the actual integer/field type used without requiring - /// type annotations on each integer literal. - IntegerOrField, - - /// A generic integer type. This is a more specific kind of TypeVariable - /// that can only be bound to Type::Integer, or other polymorphic integers. - Integer, -} - /// A TypeVariable is a mutable reference that is either /// bound to some type, or unbound with a given TypeVariableId. #[derive(PartialEq, Eq, Clone, Hash, PartialOrd, Ord)] pub struct TypeVariable(TypeVariableId, Shared); impl TypeVariable { - pub fn unbound(id: TypeVariableId) -> Self { - TypeVariable(id, Shared::new(TypeBinding::Unbound(id))) + pub fn unbound(id: TypeVariableId, type_var_kind: Kind) -> Self { + TypeVariable(id, Shared::new(TypeBinding::Unbound(id, type_var_kind))) } pub fn id(&self) -> TypeVariableId { @@ -568,19 +682,27 @@ impl TypeVariable { TypeBinding::Bound(binding) => { unreachable!("TypeVariable::bind, cannot bind bound var {} to {}", binding, typ) } - TypeBinding::Unbound(id) => *id, + TypeBinding::Unbound(id, _) => *id, }; assert!(!typ.occurs(id), "{self:?} occurs within {typ:?}"); *self.1.borrow_mut() = TypeBinding::Bound(typ); } - pub fn try_bind(&self, binding: Type, span: Span) -> Result<(), TypeCheckError> { + pub fn try_bind(&self, binding: Type, kind: &Kind, span: Span) -> Result<(), TypeCheckError> { + if !binding.kind().unifies(kind) { + return Err(TypeCheckError::TypeKindMismatch { + expected_kind: format!("{}", kind), + expr_kind: format!("{}", binding.kind()), + expr_span: span, + }); + } + let id = match &*self.1.borrow() { TypeBinding::Bound(binding) => { unreachable!("Expected unbound, found bound to {binding}") } - TypeBinding::Unbound(id) => *id, + TypeBinding::Unbound(id, _) => *id, }; if binding.occurs(id) { @@ -599,18 +721,60 @@ impl TypeVariable { /// Unbind this type variable, setting it to Unbound(id). /// /// This is generally a logic error to use outside of monomorphization. - pub fn unbind(&self, id: TypeVariableId) { - *self.1.borrow_mut() = TypeBinding::Unbound(id); + pub fn unbind(&self, id: TypeVariableId, type_var_kind: Kind) { + *self.1.borrow_mut() = TypeBinding::Unbound(id, type_var_kind); } /// Forcibly bind a type variable to a new type - even if the type /// variable is already bound to a different type. This generally /// a logic error to use outside of monomorphization. + /// + /// Asserts that the given type is compatible with the given Kind pub fn force_bind(&self, typ: Type) { if !typ.occurs(self.id()) { *self.1.borrow_mut() = TypeBinding::Bound(typ); } } + + pub fn kind(&self) -> Kind { + match &*self.borrow() { + TypeBinding::Bound(binding) => binding.kind(), + TypeBinding::Unbound(_, type_var_kind) => type_var_kind.clone(), + } + } + + /// Check that if bound, it's an integer + /// and if unbound, that it's a Kind::Integer + pub fn is_integer(&self) -> bool { + match &*self.borrow() { + TypeBinding::Bound(binding) => matches!(binding.follow_bindings(), Type::Integer(..)), + TypeBinding::Unbound(_, type_var_kind) => { + matches!(type_var_kind.follow_bindings(), Kind::Integer) + } + } + } + + /// Check that if bound, it's an integer or field + /// and if unbound, that it's a Kind::IntegerOrField + pub fn is_integer_or_field(&self) -> bool { + match &*self.borrow() { + TypeBinding::Bound(binding) => { + matches!(binding.follow_bindings(), Type::Integer(..) | Type::FieldElement) + } + 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), + } + } } /// TypeBindings are the mutable insides of a TypeVariable. @@ -618,12 +782,12 @@ impl TypeVariable { #[derive(Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] pub enum TypeBinding { Bound(Type), - Unbound(TypeVariableId), + Unbound(TypeVariableId, Kind), } impl TypeBinding { pub fn is_unbound(&self) -> bool { - matches!(self, TypeBinding::Unbound(_)) + matches!(self, TypeBinding::Unbound(_, _)) } } @@ -647,22 +811,18 @@ impl std::fmt::Display for Type { Signedness::Signed => write!(f, "i{num_bits}"), Signedness::Unsigned => write!(f, "u{num_bits}"), }, - Type::TypeVariable(var, TypeVariableKind::Normal) => write!(f, "{}", var.borrow()), - Type::TypeVariable(binding, TypeVariableKind::Integer) => { - if let TypeBinding::Unbound(_) = &*binding.borrow() { - write!(f, "{}", Type::default_int_type()) - } else { - write!(f, "{}", binding.borrow()) - } - } - Type::TypeVariable(binding, TypeVariableKind::IntegerOrField) => { - if let TypeBinding::Unbound(_) = &*binding.borrow() { - // Show a Field by default if this TypeVariableKind::IntegerOrField is unbound, since that is - // what they bind to by default anyway. It is less confusing than displaying it - // as a generic. - write!(f, "Field") - } else { - write!(f, "{}", binding.borrow()) + Type::TypeVariable(var) => { + let binding = &var.1; + match &*binding.borrow() { + TypeBinding::Unbound(_, type_var_kind) => match type_var_kind { + Kind::Any | Kind::Normal => write!(f, "{}", var.borrow()), + Kind::Integer => write!(f, "{}", Type::default_int_type()), + Kind::IntegerOrField => write!(f, "Field"), + Kind::Numeric(_typ) => write!(f, "_"), + }, + TypeBinding::Bound(binding) => { + write!(f, "{}", binding) + } } } Type::Struct(s, args) => { @@ -695,10 +855,10 @@ impl std::fmt::Display for Type { } Type::Unit => write!(f, "()"), Type::Error => write!(f, "error"), - Type::NamedGeneric(binding, name, _) => match &*binding.borrow() { + Type::NamedGeneric(binding, name) => match &*binding.borrow() { TypeBinding::Bound(binding) => binding.fmt(f), - TypeBinding::Unbound(_) if name.is_empty() => write!(f, "_"), - TypeBinding::Unbound(_) => write!(f, "{name}"), + TypeBinding::Unbound(_, _) if name.is_empty() => write!(f, "_"), + TypeBinding::Unbound(_, _) => write!(f, "{name}"), }, Type::Constant(x, _kind) => write!(f, "{x}"), Type::Forall(typevars, typ) => { @@ -759,7 +919,7 @@ impl std::fmt::Display for TypeBinding { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { TypeBinding::Bound(typ) => typ.fmt(f), - TypeBinding::Unbound(id) => id.fmt(f), + TypeBinding::Unbound(id, _) => id.fmt(f), } } } @@ -795,23 +955,25 @@ impl Type { Type::Integer(Signedness::Unsigned, IntegerBitSize::ThirtyTwo) } + pub fn type_variable_with_kind(interner: &NodeInterner, type_var_kind: Kind) -> Type { + let id = interner.next_type_variable_id(); + let var = TypeVariable::unbound(id, type_var_kind); + Type::TypeVariable(var) + } + pub fn type_variable(id: TypeVariableId) -> Type { - let var = TypeVariable::unbound(id); - Type::TypeVariable(var, TypeVariableKind::Normal) + let var = TypeVariable::unbound(id, Kind::Any); + Type::TypeVariable(var) } - pub fn polymorphic_integer_or_field(interner: &mut NodeInterner) -> Type { - let id = interner.next_type_variable_id(); - let kind = TypeVariableKind::IntegerOrField; - let var = TypeVariable::unbound(id); - Type::TypeVariable(var, kind) + pub fn polymorphic_integer_or_field(interner: &NodeInterner) -> Type { + let type_var_kind = Kind::IntegerOrField; + Self::type_variable_with_kind(interner, type_var_kind) } - pub fn polymorphic_integer(interner: &mut NodeInterner) -> Type { - let id = interner.next_type_variable_id(); - let kind = TypeVariableKind::Integer; - let var = TypeVariable::unbound(id); - Type::TypeVariable(var, kind) + pub fn polymorphic_integer(interner: &NodeInterner) -> Type { + let type_var_kind = Kind::Integer; + Self::type_variable_with_kind(interner, type_var_kind) } /// A bit of an awkward name for this function - this function returns @@ -820,9 +982,9 @@ impl Type { /// they shouldn't be bound over until monomorphization. pub fn is_bindable(&self) -> bool { match self { - Type::TypeVariable(binding, _) => match &*binding.borrow() { + Type::TypeVariable(binding) => match &*binding.borrow() { TypeBinding::Bound(binding) => binding.is_bindable(), - TypeBinding::Unbound(_) => true, + TypeBinding::Unbound(_, _) => true, }, Type::Alias(alias, args) => alias.borrow().get_type(args).is_bindable(), _ => false, @@ -841,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, _)) } @@ -849,21 +1022,63 @@ impl Type { matches!(self.follow_bindings(), Type::Integer(Signedness::Unsigned, _)) } - pub fn is_numeric(&self) -> bool { + /// While Kind::is_numeric refers to numeric _types_, + /// this method checks for numeric _values_ + pub fn is_numeric_value(&self) -> bool { + use Kind as K; use Type::*; - use TypeVariableKind as K; - matches!( - self.follow_bindings(), - FieldElement | Integer(..) | Bool | TypeVariable(_, K::Integer | K::IntegerOrField) - ) + match self.follow_bindings() { + FieldElement => true, + Integer(..) => true, + Bool => true, + TypeVariable(var) => match &*var.borrow() { + TypeBinding::Bound(typ) => typ.is_numeric_value(), + TypeBinding::Unbound(_, type_var_kind) => { + matches!(type_var_kind, K::Integer | K::IntegerOrField) + } + }, + _ => false, + } + } + + 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 TypeKind::Numeric and save its name + // Return whether the named generic has a Kind::Numeric and save its name let named_generic_is_numeric = |typ: &Type, found_names: &mut Vec| { - if let Type::NamedGeneric(_, name, Kind::Numeric { .. }) = typ { - found_names.push(name.to_string()); - true + if let Type::NamedGeneric(var, name) = typ { + if var.kind().is_numeric() { + found_names.push(name.to_string()); + true + } else { + false + } } else { false } @@ -879,13 +1094,13 @@ impl Type { | Type::Forall(_, _) | Type::Quoted(_) => {} - Type::TypeVariable(type_var, _) => { + Type::TypeVariable(type_var) => { if let TypeBinding::Bound(typ) = &*type_var.borrow() { named_generic_is_numeric(typ, found_names); } } - Type::NamedGeneric(_, _, _) => { + Type::NamedGeneric(_, _) => { named_generic_is_numeric(self, found_names); } @@ -962,8 +1177,8 @@ impl Type { | Type::Error => true, Type::FmtString(_, _) - | Type::TypeVariable(_, _) - | Type::NamedGeneric(_, _, _) + | Type::TypeVariable(_) + | Type::NamedGeneric(_, _) | Type::Function(_, _, _, _) | Type::MutableReference(_) | Type::Forall(_, _) @@ -1005,8 +1220,8 @@ impl Type { | Type::Bool | Type::Unit | Type::Constant(_, _) - | Type::TypeVariable(_, _) - | Type::NamedGeneric(_, _, _) + | Type::TypeVariable(_) + | Type::NamedGeneric(_, _) | Type::InfixExpr(..) | Type::Error => true, @@ -1054,7 +1269,7 @@ impl Type { | Type::InfixExpr(..) | Type::Error => true, - Type::TypeVariable(type_var, _) | Type::NamedGeneric(type_var, _, _) => { + Type::TypeVariable(type_var) | Type::NamedGeneric(type_var, _) => { if let TypeBinding::Bound(typ) = &*type_var.borrow() { typ.is_valid_for_unconstrained_boundary() } else { @@ -1094,10 +1309,10 @@ impl Type { pub fn generic_count(&self) -> usize { match self { Type::Forall(generics, _) => generics.len(), - Type::TypeVariable(type_variable, _) | Type::NamedGeneric(type_variable, _, _) => { + Type::TypeVariable(type_variable) | Type::NamedGeneric(type_variable, _) => { match &*type_variable.borrow() { TypeBinding::Bound(binding) => binding.generic_count(), - TypeBinding::Unbound(_) => 0, + TypeBinding::Unbound(_, _) => 0, } } _ => 0, @@ -1105,9 +1320,10 @@ impl Type { } /// Takes a monomorphic type and generalizes it over each of the type variables in the - /// given type bindings, ignoring what each type variable is bound to in the TypeBindings. + /// given type bindings, ignoring what each type variable is bound to in the TypeBindings + /// and their Kind's pub(crate) fn generalize_from_substitutions(self, type_bindings: TypeBindings) -> Type { - let polymorphic_type_vars = vecmap(type_bindings, |(_, (type_var, _))| type_var); + let polymorphic_type_vars = vecmap(type_bindings, |(_, (type_var, _kind, _))| type_var); Type::Forall(polymorphic_type_vars, Box::new(self)) } @@ -1130,15 +1346,15 @@ impl Type { } } - pub(crate) fn kind(&self) -> Option { + pub(crate) fn kind(&self) -> Kind { match self { - Type::NamedGeneric(_, _, kind) => Some(kind.clone()), - Type::Constant(_, kind) => Some(kind.clone()), - Type::TypeVariable(var, _) => match *var.borrow() { + Type::NamedGeneric(var, _) => var.kind(), + Type::Constant(_, kind) => kind.clone(), + Type::TypeVariable(var) => match &*var.borrow() { TypeBinding::Bound(ref typ) => typ.kind(), - TypeBinding::Unbound(_) => None, + TypeBinding::Unbound(_, ref type_var_kind) => type_var_kind.clone(), }, - Type::InfixExpr(lhs, _op, rhs) => Some(lhs.infix_kind(rhs)), + Type::InfixExpr(lhs, _op, rhs) => lhs.infix_kind(rhs), Type::FieldElement | Type::Array(..) | Type::Slice(..) @@ -1155,26 +1371,18 @@ impl Type { | Type::MutableReference(..) | Type::Forall(..) | Type::Quoted(..) - | Type::Error => Some(Kind::Normal), + | Type::Error => Kind::Normal, } } - /// if both Kind's are equal to Some(_), return that Kind, - /// otherwise return a Kind error - /// if both Kind's are None, default to u32 - /// if exactly one Kind is None, return the other one + /// Unifies self and other kinds or fails with a Kind error fn infix_kind(&self, other: &Self) -> Kind { - match (self.kind(), other.kind()) { - (Some(self_kind), Some(other_kind)) => { - if self_kind == other_kind { - self_kind - } else { - Kind::Numeric(Box::new(Type::Error)) - } - } - (None, None) => Kind::u32(), - (Some(self_kind), None) => self_kind, - (None, Some(other_kind)) => other_kind, + let self_kind = self.kind(); + let other_kind = other.kind(); + if self_kind.unifies(&other_kind) { + self_kind + } else { + Kind::Numeric(Box::new(Type::Error)) } } @@ -1203,9 +1411,9 @@ impl Type { .expect("Cannot have variable sized strings as a parameter to main"), Type::FmtString(_, _) | Type::Unit - | Type::TypeVariable(_, _) + | Type::TypeVariable(_) | Type::TraitAsType(..) - | Type::NamedGeneric(_, _, _) + | Type::NamedGeneric(_, _) | Type::Function(_, _, _, _) | Type::MutableReference(_) | Type::Forall(_, _) @@ -1261,71 +1469,62 @@ impl Type { ) -> Result<(), UnificationError> { let target_id = match &*var.borrow() { TypeBinding::Bound(_) => unreachable!(), - TypeBinding::Unbound(id) => *id, + TypeBinding::Unbound(id, _) => *id, }; + if !self.kind().unifies(&Kind::IntegerOrField) { + return Err(UnificationError); + } + let this = self.substitute(bindings).follow_bindings(); match &this { Type::Integer(..) => { - bindings.insert(target_id, (var.clone(), this)); + bindings.insert(target_id, (var.clone(), Kind::Integer, this)); Ok(()) } Type::FieldElement if !only_integer => { - bindings.insert(target_id, (var.clone(), this)); + bindings.insert(target_id, (var.clone(), Kind::IntegerOrField, this)); Ok(()) } - Type::TypeVariable(self_var, TypeVariableKind::IntegerOrField) => { + Type::TypeVariable(self_var) => { let borrow = self_var.borrow(); match &*borrow { TypeBinding::Bound(typ) => { typ.try_bind_to_polymorphic_int(var, bindings, only_integer) } // Avoid infinitely recursive bindings - TypeBinding::Unbound(id) if *id == target_id => Ok(()), - TypeBinding::Unbound(new_target_id) => { + TypeBinding::Unbound(ref id, _) if *id == target_id => Ok(()), + TypeBinding::Unbound(ref new_target_id, Kind::IntegerOrField) => { + let type_var_kind = Kind::IntegerOrField; if only_integer { + let var_clone = var.clone(); + Kind::Integer.unify(&type_var_kind)?; // Integer is more specific than IntegerOrField so we bind the type // variable to Integer instead. - let clone = Type::TypeVariable(var.clone(), TypeVariableKind::Integer); - bindings.insert(*new_target_id, (self_var.clone(), clone)); + let clone = Type::TypeVariable(var_clone); + bindings + .insert(*new_target_id, (self_var.clone(), type_var_kind, clone)); } else { - bindings.insert(target_id, (var.clone(), this.clone())); + bindings.insert( + target_id, + (var.clone(), Kind::IntegerOrField, this.clone()), + ); } Ok(()) } - } - } - Type::TypeVariable(self_var, TypeVariableKind::Integer) => { - let borrow = self_var.borrow(); - match &*borrow { - TypeBinding::Bound(typ) => { - typ.try_bind_to_polymorphic_int(var, bindings, only_integer) - } - // Avoid infinitely recursive bindings - TypeBinding::Unbound(id) if *id == target_id => Ok(()), - TypeBinding::Unbound(_) => { - bindings.insert(target_id, (var.clone(), this.clone())); + TypeBinding::Unbound(_new_target_id, Kind::Integer) => { + Kind::Integer.unify(&Kind::Integer)?; + bindings.insert(target_id, (var.clone(), Kind::Integer, this.clone())); Ok(()) } - } - } - Type::TypeVariable(self_var, TypeVariableKind::Normal) => { - let borrow = self_var.borrow(); - match &*borrow { - TypeBinding::Bound(typ) => { - typ.try_bind_to_polymorphic_int(var, bindings, only_integer) - } - // Avoid infinitely recursive bindings - TypeBinding::Unbound(id) if *id == target_id => Ok(()), - TypeBinding::Unbound(new_target_id) => { + TypeBinding::Unbound(new_target_id, ref type_var_kind) => { + let var_clone = var.clone(); // Bind to the most specific type variable kind - let clone_kind = if only_integer { - TypeVariableKind::Integer - } else { - TypeVariableKind::IntegerOrField - }; - let clone = Type::TypeVariable(var.clone(), clone_kind); - bindings.insert(*new_target_id, (self_var.clone(), clone)); + let clone_kind = + if only_integer { Kind::Integer } else { Kind::IntegerOrField }; + clone_kind.unify(type_var_kind)?; + let clone = Type::TypeVariable(var_clone); + bindings.insert(*new_target_id, (self_var.clone(), clone_kind, clone)); Ok(()) } } @@ -1335,7 +1534,7 @@ impl Type { } /// Try to bind the given type variable to self. Although the given type variable - /// is expected to be of TypeVariableKind::Normal, this binding can still fail + /// is expected to be of Kind::Normal, this binding can still fail /// if the given type variable occurs within `self` as that would create a recursive type. /// /// If successful, the binding is placed in the @@ -1344,18 +1543,23 @@ impl Type { &self, var: &TypeVariable, bindings: &mut TypeBindings, + kind: Kind, ) -> Result<(), UnificationError> { let target_id = match &*var.borrow() { TypeBinding::Bound(_) => unreachable!(), - TypeBinding::Unbound(id) => *id, + TypeBinding::Unbound(id, _) => *id, }; + if !self.kind().unifies(&kind) { + return Err(UnificationError); + } + let this = self.substitute(bindings).follow_bindings(); - if let Some(binding) = this.get_inner_type_variable() { + if let Some((binding, kind)) = this.get_inner_type_variable() { match &*binding.borrow() { - TypeBinding::Bound(typ) => return typ.try_bind_to(var, bindings), + TypeBinding::Bound(typ) => return typ.try_bind_to(var, bindings, kind), // Don't recursively bind the same id to itself - TypeBinding::Unbound(id) if *id == target_id => return Ok(()), + TypeBinding::Unbound(id, _) if *id == target_id => return Ok(()), _ => (), } } @@ -1365,14 +1569,15 @@ impl Type { if this.occurs(target_id) { Err(UnificationError) } else { - bindings.insert(target_id, (var.clone(), this.clone())); + bindings.insert(target_id, (var.clone(), this.kind(), this.clone())); Ok(()) } } - fn get_inner_type_variable(&self) -> Option> { + fn get_inner_type_variable(&self) -> Option<(Shared, Kind)> { match self { - Type::TypeVariable(var, _) | Type::NamedGeneric(var, _, _) => Some(var.1.clone()), + Type::TypeVariable(var) => Some((var.1.clone(), var.kind())), + Type::NamedGeneric(var, _) => Some((var.1.clone(), var.kind())), _ => None, } } @@ -1398,7 +1603,6 @@ impl Type { bindings: &mut TypeBindings, ) -> Result<(), UnificationError> { use Type::*; - use TypeVariableKind as Kind; let lhs = match self { Type::InfixExpr(..) => Cow::Owned(self.canonicalize()), @@ -1418,27 +1622,36 @@ impl Type { alias.try_unify(other, bindings) } - (TypeVariable(var, Kind::IntegerOrField), other) - | (other, TypeVariable(var, Kind::IntegerOrField)) => { - other.try_unify_to_type_variable(var, bindings, |bindings| { - let only_integer = false; - other.try_bind_to_polymorphic_int(var, bindings, only_integer) - }) - } - - (TypeVariable(var, Kind::Integer), other) - | (other, TypeVariable(var, Kind::Integer)) => { - other.try_unify_to_type_variable(var, bindings, |bindings| { - let only_integer = true; - other.try_bind_to_polymorphic_int(var, bindings, only_integer) - }) - } - - (TypeVariable(var, Kind::Normal), other) | (other, TypeVariable(var, Kind::Normal)) => { - other.try_unify_to_type_variable(var, bindings, |bindings| { - other.try_bind_to(var, bindings) - }) - } + (TypeVariable(var), other) | (other, TypeVariable(var)) => match &*var.borrow() { + TypeBinding::Bound(typ) => { + if typ.is_numeric_value() { + other.try_unify_to_type_variable(var, bindings, |bindings| { + let only_integer = matches!(typ, Type::Integer(..)); + other.try_bind_to_polymorphic_int(var, bindings, only_integer) + }) + } else { + other.try_unify_to_type_variable(var, bindings, |bindings| { + other.try_bind_to(var, bindings, typ.kind()) + }) + } + } + TypeBinding::Unbound(_id, Kind::IntegerOrField) => other + .try_unify_to_type_variable(var, bindings, |bindings| { + let only_integer = false; + other.try_bind_to_polymorphic_int(var, bindings, only_integer) + }), + TypeBinding::Unbound(_id, Kind::Integer) => { + other.try_unify_to_type_variable(var, bindings, |bindings| { + let only_integer = true; + other.try_bind_to_polymorphic_int(var, bindings, only_integer) + }) + } + TypeBinding::Unbound(_id, type_var_kind) => { + other.try_unify_to_type_variable(var, bindings, |bindings| { + other.try_bind_to(var, bindings, type_var_kind.clone()) + }) + } + }, (Array(len_a, elem_a), Array(len_b, elem_b)) => { len_a.try_unify(len_b, bindings)?; @@ -1479,7 +1692,7 @@ impl Type { } } - (NamedGeneric(binding, _, _), other) | (other, NamedGeneric(binding, _, _)) + (NamedGeneric(binding, _), other) | (other, NamedGeneric(binding, _)) if !binding.borrow().is_unbound() => { if let TypeBinding::Bound(link) = &*binding.borrow() { @@ -1489,13 +1702,13 @@ impl Type { } } - (NamedGeneric(binding_a, name_a, kind_a), NamedGeneric(binding_b, name_b, kind_b)) => { + (NamedGeneric(binding_a, name_a), NamedGeneric(binding_b, name_b)) => { // Bound NamedGenerics are caught by the check above assert!(binding_a.borrow().is_unbound()); assert!(binding_b.borrow().is_unbound()); if name_a == name_b { - kind_a.unify(kind_b) + binding_a.kind().unify(&binding_b.kind()) } else { Err(UnificationError) } @@ -1541,14 +1754,14 @@ impl Type { } (Constant(value, kind), other) | (other, Constant(value, kind)) => { - if let Some(other_value) = other.evaluate_to_u32() { - if *value == other_value && kind.matches_opt(other.kind()) { + 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())), @@ -1583,18 +1796,23 @@ impl Type { bindings: &mut TypeBindings, // Bind the type variable to a type. This is factored out since depending on the - // TypeVariableKind, there are different methods to check whether the variable can + // Kind, there are different methods to check whether the variable can // bind to the given type or not. bind_variable: impl FnOnce(&mut TypeBindings) -> Result<(), UnificationError>, ) -> Result<(), UnificationError> { match &*type_variable.borrow() { // If it is already bound, unify against what it is bound to TypeBinding::Bound(link) => link.try_unify(self, bindings), - TypeBinding::Unbound(id) => { + TypeBinding::Unbound(id, _) => { // We may have already "bound" this type variable in this call to // try_unify, so check those bindings as well. match bindings.get(id) { - Some((_, binding)) => binding.clone().try_unify(self, bindings), + Some((_, kind, binding)) => { + if !kind.unifies(&binding.kind()) { + return Err(UnificationError); + } + binding.clone().try_unify(self, bindings) + } // Otherwise, bind it None => bind_variable(bindings), @@ -1697,7 +1915,7 @@ impl Type { /// Apply the given type bindings, making them permanently visible for each /// clone of each type variable bound. pub fn apply_type_bindings(bindings: TypeBindings) { - for (type_variable, binding) in bindings.values() { + for (type_variable, _kind, binding) in bindings.values() { type_variable.bind(binding.clone()); } } @@ -1705,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) = 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, _) => 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 = lhs.evaluate_to_u32()?; - let rhs = rhs.evaluate_to_u32()?; - op.function(lhs, 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, } @@ -1731,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) }), @@ -1747,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, } @@ -1771,9 +2014,9 @@ impl Type { match self { Type::Forall(typevars, typ) => { for var in typevars { - bindings - .entry(var.id()) - .or_insert_with(|| (var.clone(), interner.next_type_variable())); + bindings.entry(var.id()).or_insert_with(|| { + (var.clone(), var.kind(), interner.next_type_variable_with_kind(var.kind())) + }); } let instantiated = typ.force_substitute(&bindings); @@ -1792,8 +2035,8 @@ impl Type { let replacements = typevars .iter() .map(|var| { - let new = interner.next_type_variable(); - (var.id(), (var.clone(), new)) + let new = interner.next_type_variable_with_kind(var.kind()); + (var.id(), (var.clone(), var.kind(), new)) }) .collect(); @@ -1827,7 +2070,7 @@ impl Type { let replacements = typevars .iter() .zip(bindings) - .map(|(var, binding)| (var.id(), (var.clone(), binding))) + .map(|(var, binding)| (var.id(), (var.clone(), var.kind(), binding))) .collect(); let instantiated = typ.substitute(&replacements); @@ -1839,9 +2082,7 @@ impl Type { fn type_variable_id(&self) -> Option { match self { - Type::TypeVariable(variable, _) | Type::NamedGeneric(variable, _, _) => { - Some(variable.0) - } + Type::TypeVariable(variable) | Type::NamedGeneric(variable, _) => Some(variable.0), _ => None, } } @@ -1894,15 +2135,24 @@ impl Type { // type variables that have already been bound over. // This is needed for monomorphizing trait impl methods. match type_bindings.get(&binding.0) { - Some((_, replacement)) if substitute_bound_typevars => { + Some((_, _kind, replacement)) if substitute_bound_typevars => { recur_on_binding(binding.0, replacement) } _ => match &*binding.borrow() { TypeBinding::Bound(binding) => { binding.substitute_helper(type_bindings, substitute_bound_typevars) } - TypeBinding::Unbound(id) => match type_bindings.get(id) { - Some((_, replacement)) => recur_on_binding(binding.0, replacement), + TypeBinding::Unbound(id, _) => match type_bindings.get(id) { + Some((_, kind, replacement)) => { + assert!( + kind.unifies(&replacement.kind()), + "while substituting (unbound): expected kind of unbound TypeVariable ({:?}) to match the kind of its binding ({:?})", + kind, + replacement.kind() + ); + + recur_on_binding(binding.0, replacement) + } None => self.clone(), }, }, @@ -1928,7 +2178,7 @@ impl Type { let fields = fields.substitute_helper(type_bindings, substitute_bound_typevars); Type::FmtString(Box::new(size), Box::new(fields)) } - Type::NamedGeneric(binding, _, _) | Type::TypeVariable(binding, _) => { + Type::NamedGeneric(binding, _) | Type::TypeVariable(binding) => { substitute_binding(binding) } // Do not substitute_helper fields, it can lead to infinite recursion @@ -2017,12 +2267,12 @@ impl Type { || args.named.iter().any(|arg| arg.typ.occurs(target_id)) } Type::Tuple(fields) => fields.iter().any(|field| field.occurs(target_id)), - Type::NamedGeneric(type_var, _, _) | Type::TypeVariable(type_var, _) => { + Type::NamedGeneric(type_var, _) | Type::TypeVariable(type_var) => { match &*type_var.borrow() { TypeBinding::Bound(binding) => { type_var.id() == target_id || binding.occurs(target_id) } - TypeBinding::Unbound(id) => *id == target_id, + TypeBinding::Unbound(id, _) => *id == target_id, } } Type::Forall(typevars, typ) => { @@ -2075,13 +2325,12 @@ impl Type { def.borrow().get_type(args).follow_bindings() } Tuple(args) => Tuple(vecmap(args, |arg| arg.follow_bindings())), - TypeVariable(var, _) | NamedGeneric(var, _, _) => { + TypeVariable(var) | NamedGeneric(var, _) => { if let TypeBinding::Bound(typ) = &*var.borrow() { return typ.follow_bindings(); } self.clone() } - Function(args, ret, env, unconstrained) => { let args = vecmap(args, |arg| arg.follow_bindings()); let ret = Box::new(ret.follow_bindings()); @@ -2114,7 +2363,7 @@ impl Type { } pub fn from_generics(generics: &GenericTypeVars) -> Vec { - vecmap(generics, |var| Type::TypeVariable(var.clone(), TypeVariableKind::Normal)) + vecmap(generics, |var| Type::TypeVariable(var.clone())) } /// Replace any `Type::NamedGeneric` in this type with a `Type::TypeVariable` @@ -2156,12 +2405,11 @@ impl Type { typ.replace_named_generics_with_type_variables(); *self = typ; } - Type::TypeVariable(var, _) => { + Type::TypeVariable(var) => { let var = var.borrow(); if let TypeBinding::Bound(binding) = &*var { - let mut binding = binding.clone(); + let binding = binding.clone(); drop(var); - binding.replace_named_generics_with_type_variables(); *self = binding; } } @@ -2173,7 +2421,7 @@ impl Type { generic.typ.replace_named_generics_with_type_variables(); } } - Type::NamedGeneric(var, _, _) => { + Type::NamedGeneric(var, _) => { let type_binding = var.borrow(); if let TypeBinding::Bound(binding) = &*type_binding { let mut binding = binding.clone(); @@ -2182,7 +2430,7 @@ impl Type { *self = binding; } else { drop(type_binding); - *self = Type::TypeVariable(var.clone(), TypeVariableKind::Normal); + *self = Type::TypeVariable(var.clone()); } } Type::Function(args, ret, env, _unconstrained) => { @@ -2207,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()` @@ -2244,13 +2537,29 @@ fn convert_array_expression_to_slice( impl BinaryTypeOperator { /// Perform the actual rust numeric operation associated with this operator - pub fn function(self, a: u32, b: u32) -> 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()) + } } } @@ -2263,21 +2572,20 @@ impl BinaryTypeOperator { match self { BinaryTypeOperator::Addition => Some(BinaryTypeOperator::Subtraction), BinaryTypeOperator::Subtraction => Some(BinaryTypeOperator::Addition), - BinaryTypeOperator::Multiplication => Some(BinaryTypeOperator::Division), - BinaryTypeOperator::Division => Some(BinaryTypeOperator::Multiplication), + BinaryTypeOperator::Multiplication => None, + BinaryTypeOperator::Division => None, BinaryTypeOperator::Modulo => None, } } -} -impl TypeVariableKind { - /// Returns the default type this type variable should be bound to if it is still unbound - /// during monomorphization. - pub(crate) fn default_type(&self) -> Option { + /// Return the operator that will "undo" this operation if applied to the rhs + fn approx_inverse(self) -> Option { match self { - TypeVariableKind::IntegerOrField => Some(Type::default_int_or_field_type()), - TypeVariableKind::Integer => Some(Type::default_int_type()), - TypeVariableKind::Normal => None, + BinaryTypeOperator::Addition => Some(BinaryTypeOperator::Subtraction), + BinaryTypeOperator::Subtraction => Some(BinaryTypeOperator::Addition), + BinaryTypeOperator::Multiplication => Some(BinaryTypeOperator::Division), + BinaryTypeOperator::Division => Some(BinaryTypeOperator::Multiplication), + BinaryTypeOperator::Modulo => None, } } } @@ -2309,16 +2617,15 @@ impl From<&Type> for PrintableType { } Signedness::Signed => PrintableType::SignedInteger { width: (*bit_width).into() }, }, - Type::TypeVariable(binding, TypeVariableKind::Integer) => match &*binding.borrow() { + Type::TypeVariable(binding) => match &*binding.borrow() { TypeBinding::Bound(typ) => typ.into(), - TypeBinding::Unbound(_) => Type::default_int_type().into(), - }, - Type::TypeVariable(binding, TypeVariableKind::IntegerOrField) => { - match &*binding.borrow() { - TypeBinding::Bound(typ) => typ.into(), - TypeBinding::Unbound(_) => Type::default_int_or_field_type().into(), + TypeBinding::Unbound(_, Kind::Integer) => Type::default_int_type().into(), + TypeBinding::Unbound(_, Kind::IntegerOrField) => { + Type::default_int_or_field_type().into() } - } + TypeBinding::Unbound(_, Kind::Numeric(typ)) => (*typ.clone()).into(), + TypeBinding::Unbound(_, Kind::Any | Kind::Normal) => unreachable!(), + }, Type::Bool => PrintableType::Boolean, Type::String(size) => { let size = size.evaluate_to_u32().expect("Cannot print variable sized strings"); @@ -2337,7 +2644,6 @@ impl From<&Type> for PrintableType { Type::Alias(alias, args) => alias.borrow().get_type(args).into(), Type::TraitAsType(..) => unreachable!(), Type::Tuple(types) => PrintableType::Tuple { types: vecmap(types, |typ| typ.into()) }, - Type::TypeVariable(_, _) => unreachable!(), Type::NamedGeneric(..) => unreachable!(), Type::Forall(..) => unreachable!(), Type::Function(arguments, return_type, env, unconstrained) => PrintableType::Function { @@ -2371,12 +2677,18 @@ impl std::fmt::Debug for Type { Signedness::Signed => write!(f, "i{num_bits}"), Signedness::Unsigned => write!(f, "u{num_bits}"), }, - Type::TypeVariable(var, TypeVariableKind::Normal) => write!(f, "{:?}", var), - Type::TypeVariable(binding, TypeVariableKind::IntegerOrField) => { - write!(f, "IntOrField{:?}", binding) - } - Type::TypeVariable(binding, TypeVariableKind::Integer) => { - write!(f, "Int{:?}", binding) + Type::TypeVariable(var) => { + let binding = &var.1; + if let TypeBinding::Unbound(_, type_var_kind) = &*binding.borrow() { + match type_var_kind { + Kind::Any | Kind::Normal => write!(f, "{:?}", var), + Kind::IntegerOrField => write!(f, "IntOrField{:?}", binding), + Kind::Integer => write!(f, "Int{:?}", binding), + Kind::Numeric(typ) => write!(f, "Numeric({:?}: {:?})", binding, typ), + } + } else { + write!(f, "{}", binding.borrow()) + } } Type::Struct(s, args) => { let args = vecmap(args, |arg| format!("{:?}", arg)); @@ -2406,8 +2718,8 @@ impl std::fmt::Debug for Type { } Type::Unit => write!(f, "()"), Type::Error => write!(f, "error"), - Type::NamedGeneric(binding, name, kind) => match kind { - Kind::Normal => { + Type::NamedGeneric(binding, name) => match binding.kind() { + Kind::Any | Kind::Normal | Kind::Integer | Kind::IntegerOrField => { write!(f, "{}{:?}", name, binding) } Kind::Numeric(typ) => { @@ -2467,7 +2779,8 @@ impl std::fmt::Debug for StructType { impl std::hash::Hash for Type { fn hash(&self, state: &mut H) { - if let Some(variable) = self.get_inner_type_variable() { + if let Some((variable, kind)) = self.get_inner_type_variable() { + kind.hash(state); if let TypeBinding::Bound(typ) = &*variable.borrow() { typ.hash(state); return; @@ -2503,7 +2816,7 @@ impl std::hash::Hash for Type { alias.hash(state); args.hash(state); } - Type::TypeVariable(var, _) | Type::NamedGeneric(var, ..) => var.hash(state), + Type::TypeVariable(var) | Type::NamedGeneric(var, ..) => var.hash(state), Type::TraitAsType(trait_id, _, args) => { trait_id.hash(state); args.hash(state); @@ -2532,13 +2845,19 @@ impl std::hash::Hash for Type { impl PartialEq for Type { fn eq(&self, other: &Self) -> bool { - if let Some(variable) = self.get_inner_type_variable() { + if let Some((variable, kind)) = self.get_inner_type_variable() { + if kind != other.kind() { + return false; + } if let TypeBinding::Bound(typ) = &*variable.borrow() { return typ == other; } } - if let Some(variable) = other.get_inner_type_variable() { + if let Some((variable, other_kind)) = other.get_inner_type_variable() { + if self.kind() != other_kind { + return false; + } if let TypeBinding::Bound(typ) = &*variable.borrow() { return self == typ; } @@ -2592,8 +2911,8 @@ impl PartialEq for Type { // still want them to be equal for canonicalization checks in arithmetic generics. // Without this we'd fail the `serialize` test. ( - NamedGeneric(lhs_var, _, _) | TypeVariable(lhs_var, _), - NamedGeneric(rhs_var, _, _) | TypeVariable(rhs_var, _), + NamedGeneric(lhs_var, _) | TypeVariable(lhs_var), + NamedGeneric(rhs_var, _) | TypeVariable(rhs_var), ) => lhs_var.id() == rhs_var.id(), _ => false, } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types/arithmetic.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types/arithmetic.rs index 54b4c27a1f3..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,13 +17,15 @@ 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)) { - if let Some(result) = op.function(lhs_u32, rhs_u32) { - return Type::Constant(result, lhs.infix_kind(&rhs)); + let kind = lhs.infix_kind(&rhs); + if let Some(result) = op.function(lhs_u32, rhs_u32, &kind) { + return Type::Constant(result, kind); } } @@ -57,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. @@ -68,7 +76,7 @@ impl Type { queue.push(*rhs); } Type::Constant(new_constant, new_constant_kind) => { - if let Some(result) = op.function(constant, new_constant) { + if let Some(result) = op.function(constant, new_constant, &new_constant_kind) { constant = result; } else { let constant = Type::Constant(new_constant, new_constant_kind); @@ -112,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, @@ -124,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; } @@ -174,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)) } @@ -190,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, @@ -205,20 +218,20 @@ impl Type { if l_op == Subtraction { op = op.inverse()?; } - let result = op.function(l_const, r_const)?; + let result = op.function(l_const, r_const, &lhs.infix_kind(rhs))?; 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)?; + let result = op.function(l_const, r_const, &lhs.infix_kind(rhs))?; let constant = Box::new(Type::Constant(result, lhs.infix_kind(rhs))); Some(Type::InfixExpr(l_type, l_op, constant)) } @@ -235,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(); @@ -250,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/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/lexer/errors.rs index 6e64c509195..66f79bd444b 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/lexer/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/lexer/errors.rs @@ -143,10 +143,3 @@ impl<'a> From<&'a LexerErrorKind> for Diagnostic { Diagnostic::simple_error(primary, secondary, span) } } - -impl From for chumsky::error::Simple { - fn from(error: LexerErrorKind) -> Self { - let (_, message, span) = error.parts(); - chumsky::error::Simple::custom(span, message) - } -} 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 95eb41fd6d0..adc68351e3c 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/lexer/lexer.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/lexer/lexer.rs @@ -86,6 +86,11 @@ impl<'a> Lexer<'a> { self.peek_char() == Some(ch) } + /// Peeks at the character two positions ahead and returns true if it is equal to the char argument + fn peek2_char_is(&mut self, ch: char) -> bool { + self.peek2_char() == Some(ch) + } + fn ampersand(&mut self) -> SpannedTokenResult { if self.peek_char_is('&') { // When we issue this error the first '&' will already be consumed @@ -152,6 +157,7 @@ impl<'a> Lexer<'a> { Ok(token.into_single_span(self.position)) } + /// If `single` is followed by `character` then extend it as `double`. fn single_double_peek_token( &mut self, character: char, @@ -169,13 +175,23 @@ impl<'a> Lexer<'a> { } } - /// Given that some tokens can contain two characters, such as <= , !=, >= - /// Glue will take the first character of the token and check if it can be glued onto the next character - /// forming a double token + /// Given that some tokens can contain two characters, such as <= , !=, >=, or even three like ..= + /// Glue will take the first character of the token and check if it can be glued onto the next character(s) + /// forming a double or triple token + /// + /// Returns an error if called with a token which cannot be extended with anything. fn glue(&mut self, prev_token: Token) -> SpannedTokenResult { - let spanned_prev_token = prev_token.clone().into_single_span(self.position); match prev_token { - Token::Dot => self.single_double_peek_token('.', prev_token, Token::DoubleDot), + Token::Dot => { + if self.peek_char_is('.') && self.peek2_char_is('=') { + let start = self.position; + self.next_char(); + self.next_char(); + Ok(Token::DoubleDotEqual.into_span(start, start + 2)) + } else { + self.single_double_peek_token('.', prev_token, Token::DoubleDot) + } + } Token::Less => { let start = self.position; if self.peek_char_is('=') { @@ -214,7 +230,7 @@ impl<'a> Lexer<'a> { return self.parse_block_comment(start); } - Ok(spanned_prev_token) + Ok(prev_token.into_single_span(start)) } _ => Err(LexerErrorKind::NotADoubleChar { span: Span::single_char(self.position), @@ -285,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 != ']'); @@ -305,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 { @@ -703,8 +724,8 @@ mod tests { use crate::token::{CustomAttribute, FunctionAttribute, SecondaryAttribute, TestScope}; #[test] - fn test_single_double_char() { - let input = "! != + ( ) { } [ ] | , ; : :: < <= > >= & - -> . .. % / * = == << >>"; + fn test_single_multi_char() { + let input = "! != + ( ) { } [ ] | , ; : :: < <= > >= & - -> . .. ..= % / * = == << >>"; let expected = vec![ Token::Bang, @@ -730,6 +751,7 @@ mod tests { Token::Arrow, Token::Dot, Token::DoubleDot, + Token::DoubleDotEqual, Token::Percent, Token::Slash, Token::Star, @@ -821,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) }))) ); } @@ -926,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 97faea4b445..4037135a889 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs @@ -1,6 +1,6 @@ use acvm::{acir::AcirField, FieldElement}; use noirc_errors::{Position, Span, Spanned}; -use std::{fmt, iter::Map, vec::IntoIter}; +use std::fmt; use crate::{ lexer::errors::LexerErrorKind, @@ -73,6 +73,8 @@ pub enum BorrowedToken<'input> { Dot, /// .. DoubleDot, + /// ..= + DoubleDotEqual, /// ( LeftParen, /// ) @@ -190,6 +192,8 @@ pub enum Token { Dot, /// .. DoubleDot, + /// ..= + DoubleDotEqual, /// ( LeftParen, /// ) @@ -279,6 +283,7 @@ pub fn token_to_borrowed_token(token: &Token) -> BorrowedToken<'_> { Token::ShiftRight => BorrowedToken::ShiftRight, Token::Dot => BorrowedToken::Dot, Token::DoubleDot => BorrowedToken::DoubleDot, + Token::DoubleDotEqual => BorrowedToken::DoubleDotEqual, Token::LeftParen => BorrowedToken::LeftParen, Token::RightParen => BorrowedToken::RightParen, Token::LeftBrace => BorrowedToken::LeftBrace, @@ -373,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() { @@ -409,6 +422,7 @@ impl fmt::Display for Token { Token::ShiftRight => write!(f, ">>"), Token::Dot => write!(f, "."), Token::DoubleDot => write!(f, ".."), + Token::DoubleDotEqual => write!(f, "..="), Token::LeftParen => write!(f, "("), Token::RightParen => write!(f, ")"), Token::LeftBrace => write!(f, "{{"), @@ -450,6 +464,7 @@ pub enum TokenKind { InternedUnresolvedTypeData, InternedPattern, UnquoteMarker, + Comment, OuterDocComment, InnerDocComment, } @@ -471,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"), } @@ -497,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)) @@ -629,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)"), }, } } @@ -732,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 == ')') @@ -752,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] => { @@ -807,7 +833,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, @@ -916,7 +942,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. @@ -933,7 +965,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 @@ -948,7 +980,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()), @@ -962,6 +995,10 @@ impl SecondaryAttribute { _ => false, } } + + pub(crate) fn is_abi(&self) -> bool { + matches!(self, SecondaryAttribute::Abi(_)) + } } impl fmt::Display for SecondaryAttribute { @@ -969,9 +1006,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})]"), @@ -1023,7 +1061,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, @@ -1221,25 +1260,6 @@ impl Keyword { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Tokens(pub Vec); -type TokenMapIter = Map, fn(SpannedToken) -> (Token, Span)>; - -impl<'a> From for chumsky::Stream<'a, Token, Span, TokenMapIter> { - fn from(tokens: Tokens) -> Self { - let end_of_input = match tokens.0.last() { - Some(spanned_token) => spanned_token.to_span(), - None => Span::single_char(0), - }; - - fn get_span(token: SpannedToken) -> (Token, Span) { - let span = token.to_span(); - (token.into_token(), span) - } - - let iter = tokens.0.into_iter().map(get_span as fn(_) -> _); - chumsky::Stream::from_iter(end_of_input, iter) - } -} - #[cfg(test)] mod keywords { use strum::IntoEnumIterator; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/lib.rs b/noir/noir-repo/compiler/noirc_frontend/src/lib.rs index 4391c760701..9d98b125e32 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/lib.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/lib.rs @@ -36,31 +36,3 @@ pub use hir_def::types::*; // Unit tests that involve all modules pub mod tests; - -// API for experimental macros feature -pub mod macros_api { - - pub use acvm::FieldElement; - pub use fm::FileId; - pub use noirc_errors::Span; - - pub use crate::graph::CrateId; - pub use crate::hir_def::expr::{HirExpression, HirLiteral}; - pub use crate::hir_def::stmt::HirStatement; - pub use crate::node_interner::{NodeInterner, StructId}; - pub use crate::parser::{parse_program, SortedModule}; - pub use crate::token::SecondaryAttribute; - - pub use crate::ast::{ - BlockExpression, CallExpression, CastExpression, Documented, Expression, ExpressionKind, - FunctionReturnType, Ident, IndexExpression, ItemVisibility, LetStatement, Literal, - MemberAccessExpression, MethodCallExpression, NoirFunction, Path, PathKind, Pattern, - Statement, UnresolvedType, UnresolvedTypeData, Visibility, - }; - pub use crate::ast::{ - ForLoopStatement, ForRange, FunctionDefinition, ImportStatement, NoirStruct, Param, - PrefixExpression, Signedness, StatementKind, TypeImpl, UnaryOp, - }; - pub use crate::hir::{def_map::ModuleDefId, Context as HirContext}; - pub use crate::{StructType, Type}; -} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/locations.rs b/noir/noir-repo/compiler/noirc_frontend/src/locations.rs index cba667d5dcb..48660142d0a 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/locations.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/locations.rs @@ -6,8 +6,9 @@ use rustc_hash::FxHashMap as HashMap; use crate::{ ast::{FunctionDefinition, ItemVisibility}, hir::def_map::{ModuleDefId, ModuleId}, - macros_api::{NodeInterner, StructId}, - node_interner::{DefinitionId, FuncId, GlobalId, ReferenceId, TraitId, TypeAliasId}, + node_interner::{ + DefinitionId, FuncId, GlobalId, NodeInterner, ReferenceId, StructId, TraitId, TypeAliasId, + }, }; use petgraph::prelude::NodeIndex as PetGraphIndex; @@ -46,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); @@ -277,8 +281,12 @@ impl NodeInterner { .next() } - pub(crate) fn register_module(&mut self, id: ModuleId, name: String) { - let visibility = ItemVisibility::Public; + pub(crate) fn register_module( + &mut self, + id: ModuleId, + visibility: ItemVisibility, + name: String, + ) { self.register_name_for_auto_import(name, ModuleDefId::ModuleId(id), visibility, None); } @@ -286,11 +294,10 @@ impl NodeInterner { &mut self, id: GlobalId, name: String, + visibility: ItemVisibility, parent_module_id: ModuleId, ) { self.add_definition_location(ReferenceId::Global(id), Some(parent_module_id)); - - let visibility = ItemVisibility::Public; self.register_name_for_auto_import(name, ModuleDefId::GlobalId(id), visibility, None); } @@ -305,10 +312,14 @@ impl NodeInterner { self.register_name_for_auto_import(name, ModuleDefId::TypeId(id), visibility, None); } - pub(crate) fn register_trait(&mut self, id: TraitId, name: String, parent_module_id: ModuleId) { + pub(crate) fn register_trait( + &mut self, + id: TraitId, + name: String, + visibility: ItemVisibility, + parent_module_id: ModuleId, + ) { self.add_definition_location(ReferenceId::Trait(id), Some(parent_module_id)); - - let visibility = ItemVisibility::Public; self.register_name_for_auto_import(name, ModuleDefId::TraitId(id), visibility, None); } @@ -316,11 +327,10 @@ impl NodeInterner { &mut self, id: TypeAliasId, name: String, + visibility: ItemVisibility, parent_module_id: ModuleId, ) { self.add_definition_location(ReferenceId::Alias(id), Some(parent_module_id)); - - let visibility = ItemVisibility::Public; self.register_name_for_auto_import(name, ModuleDefId::TypeAliasId(id), visibility, None); } 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 12cc3b55b1f..3ca4c5651ec 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -21,7 +21,7 @@ use crate::{ types, }, node_interner::{self, DefinitionKind, NodeInterner, StmtId, TraitImplKind, TraitMethodId}, - Type, TypeBinding, TypeBindings, + Kind, Type, TypeBinding, TypeBindings, }; use acvm::{acir::AcirField, FieldElement}; use iter_extended::{btree_map, try_vecmap, vecmap}; @@ -850,14 +850,21 @@ impl<'interner> Monomorphizer<'interner> { ) -> Result { let typ = self.interner.id_type(expr_id); - if let ImplKind::TraitMethod(method, _, _) = ident.impl_kind { - return self.resolve_trait_method_expr(expr_id, typ, method); + if let ImplKind::TraitMethod(method) = ident.impl_kind { + return self.resolve_trait_method_expr(expr_id, typ, method.method_id); } // Ensure all instantiation bindings are bound. // This ensures even unused type variables like `fn foo() {}` have concrete types if let Some(bindings) = self.interner.try_get_instantiation_bindings(expr_id) { - for (_, binding) in bindings.values() { + for (_, kind, binding) in bindings.values() { + match kind { + Kind::Any => (), + Kind::Normal => (), + Kind::Integer => (), + Kind::IntegerOrField => (), + Kind::Numeric(typ) => Self::check_type(typ, ident.location)?, + } Self::check_type(binding, ident.location)?; } } @@ -916,18 +923,26 @@ impl<'interner> Monomorphizer<'interner> { ast::Expression::Ident(ident) } }, - DefinitionKind::GenericType(type_variable) => { + DefinitionKind::NumericGeneric(type_variable, numeric_typ) => { let value = match &*type_variable.borrow() { - TypeBinding::Unbound(_) => { + 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 value = FieldElement::from(value as u128); let location = self.interner.id_location(expr_id); + + if !Kind::Numeric(numeric_typ.clone()) + .unifies(&Kind::Numeric(Box::new(typ.clone()))) + { + let message = "ICE: Generic's kind does not match expected type"; + return Err(MonomorphizationError::InternalError { location, message }); + } + let typ = Self::convert_type(&typ, ident.location)?; ast::Expression::Literal(ast::Literal::Integer(value, false, typ, location)) } @@ -967,8 +982,8 @@ impl<'interner> Monomorphizer<'interner> { HirType::TraitAsType(..) => { unreachable!("All TraitAsType should be replaced before calling convert_type"); } - HirType::NamedGeneric(binding, _, _) => { - if let TypeBinding::Bound(binding) = &*binding.borrow() { + HirType::NamedGeneric(binding, _) => { + if let TypeBinding::Bound(ref binding) = &*binding.borrow() { return Self::convert_type(binding, location); } @@ -979,15 +994,18 @@ impl<'interner> Monomorphizer<'interner> { ast::Type::Field } - HirType::TypeVariable(binding, kind) => { - if let TypeBinding::Bound(binding) = &*binding.borrow() { - return Self::convert_type(binding, location); - } + HirType::TypeVariable(ref binding) => { + let type_var_kind = match &*binding.borrow() { + TypeBinding::Bound(ref binding) => { + return Self::convert_type(binding, location); + } + TypeBinding::Unbound(_, ref type_var_kind) => type_var_kind.clone(), + }; // Default any remaining unbound type variables. // This should only happen if the variable in question is unused // and within a larger generic type. - let default = match kind.default_type() { + let default = match type_var_kind.default_type() { Some(typ) => typ, None => return Err(MonomorphizationError::NoDefaultType { location }), }; @@ -1085,23 +1103,26 @@ impl<'interner> Monomorphizer<'interner> { HirType::FmtString(_size, fields) => Self::check_type(fields.as_ref(), location), HirType::Array(_length, element) => Self::check_type(element.as_ref(), location), HirType::Slice(element) => Self::check_type(element.as_ref(), location), - HirType::NamedGeneric(binding, _, _) => { - if let TypeBinding::Bound(binding) = &*binding.borrow() { + HirType::NamedGeneric(binding, _) => { + if let TypeBinding::Bound(ref binding) = &*binding.borrow() { return Self::check_type(binding, location); } Ok(()) } - HirType::TypeVariable(binding, kind) => { - if let TypeBinding::Bound(binding) = &*binding.borrow() { - return Self::check_type(binding, location); - } + HirType::TypeVariable(ref binding) => { + let type_var_kind = match &*binding.borrow() { + TypeBinding::Bound(binding) => { + return Self::check_type(binding, location); + } + TypeBinding::Unbound(_, ref type_var_kind) => type_var_kind.clone(), + }; // Default any remaining unbound type variables. // This should only happen if the variable in question is unused // and within a larger generic type. - let default = match kind.default_type() { + let default = match type_var_kind.default_type() { Some(typ) => typ, None => return Err(MonomorphizationError::NoDefaultType { location }), }; @@ -1265,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() { @@ -1347,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 = @@ -1382,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( @@ -1441,9 +1488,9 @@ impl<'interner> Monomorphizer<'interner> { fn follow_bindings(&self, bindings: &TypeBindings) -> TypeBindings { bindings .iter() - .map(|(id, (var, binding))| { + .map(|(id, (var, kind, binding))| { let binding2 = binding.follow_bindings(); - (*id, (var.clone(), binding2)) + (*id, (var.clone(), kind.clone(), binding2)) }) .collect() } @@ -1910,14 +1957,14 @@ fn unwrap_struct_type( } pub fn perform_instantiation_bindings(bindings: &TypeBindings) { - for (var, binding) in bindings.values() { + for (var, _kind, binding) in bindings.values() { var.force_bind(binding.clone()); } } pub fn undo_instantiation_bindings(bindings: TypeBindings) { - for (id, (var, _)) in bindings { - var.unbind(id); + for (id, (var, kind, _)) in bindings { + var.unbind(id, kind); } } @@ -1944,7 +1991,7 @@ pub fn perform_impl_bindings( interner.function_meta(&impl_method).typ.unwrap_forall().1.clone(); // Make each NamedGeneric in this type bindable by replacing it with a TypeVariable - // with the same internal id and binding. + // with the same internal id, binding. trait_method_type.replace_named_generics_with_type_variables(); impl_method_type.replace_named_generics_with_type_variables(); 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 a95282a1ec9..ca7e0c6aa59 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs @@ -13,22 +13,18 @@ use petgraph::prelude::DiGraph; use petgraph::prelude::NodeIndex as PetGraphIndex; use rustc_hash::FxHashMap as HashMap; -use crate::ast::ExpressionKind; -use crate::ast::Ident; -use crate::ast::LValue; -use crate::ast::Pattern; -use crate::ast::StatementKind; -use crate::ast::UnresolvedTypeData; +use crate::ast::{ + ExpressionKind, Ident, LValue, Pattern, StatementKind, UnaryOp, UnresolvedTypeData, +}; use crate::graph::CrateId; use crate::hir::comptime; use crate::hir::def_collector::dc_crate::CompilationError; use crate::hir::def_collector::dc_crate::{UnresolvedStruct, UnresolvedTrait, UnresolvedTypeAlias}; use crate::hir::def_map::DefMaps; -use crate::hir::def_map::{LocalModuleId, ModuleId}; +use crate::hir::def_map::{LocalModuleId, ModuleDefId, ModuleId}; use crate::hir::type_check::generics::TraitGenerics; use crate::hir_def::traits::NamedType; -use crate::macros_api::ModuleDefId; -use crate::macros_api::UnaryOp; +use crate::hir_def::traits::ResolvedTraitBound; use crate::usage_tracker::UnusedItem; use crate::usage_tracker::UsageTracker; use crate::QuotedType; @@ -39,7 +35,7 @@ use crate::hir_def::expr::HirIdent; use crate::hir_def::stmt::HirLetStatement; use crate::hir_def::traits::TraitImpl; use crate::hir_def::traits::{Trait, TraitConstraint}; -use crate::hir_def::types::{StructType, Type}; +use crate::hir_def::types::{Kind, StructType, Type}; use crate::hir_def::{ expr::HirExpression, function::{FuncMeta, HirFunction}, @@ -49,7 +45,7 @@ use crate::locations::LocationIndices; use crate::token::{Attributes, SecondaryAttribute}; use crate::GenericTypeVars; use crate::Generics; -use crate::{Shared, TypeAlias, TypeBindings, TypeVariable, TypeVariableId, TypeVariableKind}; +use crate::{Shared, TypeAlias, TypeBindings, TypeVariable, TypeVariableId}; /// An arbitrary number to limit the recursion depth when searching for trait impls. /// This is needed to stop recursing for cases such as `impl Foo for T where T: Eq` @@ -296,6 +292,7 @@ pub enum DependencyId { Global(GlobalId), Function(FuncId), Alias(TypeAliasId), + Trait(TraitId), Variable(Location), } @@ -586,7 +583,7 @@ pub enum DefinitionKind { /// Generic types in functions (T, U in `fn foo(...)` are declared as variables /// in scope in case they resolve to numeric generics later. - GenericType(TypeVariable), + NumericGeneric(TypeVariable, Box), } impl DefinitionKind { @@ -601,7 +598,7 @@ impl DefinitionKind { DefinitionKind::Function(_) => None, DefinitionKind::Global(_) => None, DefinitionKind::Local(id) => *id, - DefinitionKind::GenericType(_) => None, + DefinitionKind::NumericGeneric(_, _) => None, } } } @@ -733,10 +730,11 @@ impl NodeInterner { crate_id: unresolved_trait.crate_id, location: Location::new(unresolved_trait.trait_def.span, unresolved_trait.file_id), generics, - self_type_typevar: TypeVariable::unbound(self.next_type_variable_id()), + self_type_typevar: TypeVariable::unbound(self.next_type_variable_id(), Kind::Normal), methods: Vec::new(), method_ids: unresolved_trait.method_ids.clone(), associated_types, + trait_bounds: Vec::new(), }; self.traits.insert(type_id, new_trait); @@ -1336,6 +1334,10 @@ impl NodeInterner { Type::type_variable(self.next_type_variable_id()) } + pub fn next_type_variable_with_kind(&self, kind: Kind) -> Type { + Type::type_variable_with_kind(self, kind) + } + pub fn store_instantiation_bindings( &mut self, expr_id: ExprId, @@ -1532,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(), + }, } }; @@ -1614,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)); } @@ -1636,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)) } @@ -1659,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 @@ -1737,7 +1745,16 @@ impl NodeInterner { // Replace each generic with a fresh type variable let substitutions = impl_generics .into_iter() - .map(|typevar| (typevar.id(), (typevar, self.next_type_variable()))) + .map(|typevar| { + let typevar_kind = typevar.kind(); + let typevar_id = typevar.id(); + let substitution = ( + typevar, + typevar_kind.clone(), + self.next_type_variable_with_kind(typevar_kind), + ); + (typevar_id, substitution) + }) .collect(); let instantiated_object_type = object_type.substitute(&substitutions); @@ -2008,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); @@ -2063,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 @@ -2091,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") } @@ -2137,6 +2164,7 @@ impl NodeInterner { pub fn get_lvalue(&self, id: InternedExpressionKind, span: Span) -> LValue { LValue::from_expression_kind(self.get_expression_kind(id).clone(), span) + .expect("Called LValue::from_expression with an invalid expression") } pub fn push_pattern(&mut self, pattern: Pattern) -> InternedPattern { @@ -2228,11 +2256,17 @@ impl NodeInterner { let trait_generics = the_trait.generics.clone(); let self_type_var = the_trait.self_type_typevar.clone(); - bindings.insert(self_type_var.id(), (self_type_var, impl_self_type)); + bindings.insert( + self_type_var.id(), + (self_type_var.clone(), self_type_var.kind(), impl_self_type), + ); for (trait_generic, trait_impl_generic) in trait_generics.iter().zip(trait_impl_generics) { let type_var = trait_generic.type_var.clone(); - bindings.insert(type_var.id(), (type_var, trait_impl_generic.clone())); + bindings.insert( + type_var.id(), + (type_var, trait_generic.kind(), trait_impl_generic.clone()), + ); } // Now that the normal bindings are added, we still need to bind the associated types @@ -2241,7 +2275,10 @@ impl NodeInterner { for (trait_type, impl_type) in trait_associated_types.iter().zip(impl_associated_types) { let type_variable = trait_type.type_var.clone(); - bindings.insert(type_variable.id(), (type_variable, impl_type.typ.clone())); + bindings.insert( + type_variable.id(), + (type_variable, trait_type.kind(), impl_type.typ.clone()), + ); } bindings @@ -2362,22 +2399,26 @@ fn get_type_method_key(typ: &Type) -> Option { Type::Array(_, _) => Some(Array), Type::Slice(_) => Some(Slice), Type::Integer(_, _) => Some(FieldOrInt), - Type::TypeVariable(_, TypeVariableKind::IntegerOrField) => Some(FieldOrInt), - Type::TypeVariable(_, TypeVariableKind::Integer) => Some(FieldOrInt), + Type::TypeVariable(var) => { + if var.is_integer() || var.is_integer_or_field() { + Some(FieldOrInt) + } else { + None + } + } Type::Bool => Some(Bool), Type::String(_) => Some(String), Type::FmtString(_, _) => Some(FmtString), Type::Unit => Some(Unit), Type::Tuple(_) => Some(Tuple), Type::Function(_, _, _, _) => Some(Function), - Type::NamedGeneric(_, _, _) => Some(Generic), + Type::NamedGeneric(_, _) => Some(Generic), Type::Quoted(quoted) => Some(Quoted(*quoted)), Type::MutableReference(element) => get_type_method_key(element), Type::Alias(alias, _) => get_type_method_key(&alias.borrow().typ), // We do not support adding methods to these types - Type::TypeVariable(_, _) - | Type::Forall(_, _) + Type::Forall(_, _) | Type::Constant(..) | Type::Error | Type::Struct(_, _) 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 75fe1bf747f..3315b38a351 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/errors.rs @@ -1,4 +1,4 @@ -use crate::ast::{Expression, IntegerBitSize}; +use crate::ast::{Expression, IntegerBitSize, ItemVisibility}; use crate::lexer::errors::LexerErrorKind; use crate::lexer::token::Token; use crate::token::TokenKind; @@ -13,26 +13,50 @@ use super::labels::ParsingRuleLabel; #[derive(Debug, Clone, PartialEq, Eq, Error)] pub enum ParserErrorReason { - #[error("Unexpected '{0}', expected a field name")] + #[error("Unexpected `;`")] + UnexpectedSemicolon, + #[error("Unexpected `,`")] + UnexpectedComma, + #[error("Expected a `{token}` separating these two {items}")] + ExpectedTokenSeparatingTwoItems { token: Token, items: &'static str }, + #[error("Invalid left-hand side of assignment")] + InvalidLeftHandSideOfAssignment, + #[error("Expected trait, found {found}")] + ExpectedTrait { found: String }, + #[error("Visibility `{visibility}` is not followed by an item")] + VisibilityNotFollowedByAnItem { visibility: ItemVisibility }, + #[error("`unconstrained` is not followed by an item")] + UnconstrainedNotFollowedByAnItem, + #[error("`comptime` is not followed by an item")] + ComptimeNotFollowedByAnItem, + #[error("`mut` cannot be applied to this item")] + MutableNotApplicable, + #[error("`comptime` cannot be applied to this item")] + ComptimeNotApplicable, + #[error("`unconstrained` cannot be applied to this item")] + UnconstrainedNotApplicable, + #[error("Expected an identifier or `(expression) after `$` for unquoting")] + ExpectedIdentifierOrLeftParenAfterDollar, + #[error("`&mut` can only be used with `self")] + RefMutCanOnlyBeUsedWithSelf, + #[error("Invalid pattern")] + InvalidPattern, + #[error("Documentation comment does not document anything")] + DocCommentDoesNotDocumentAnything, + + #[error("Missing type for function parameter")] + MissingTypeForFunctionParameter, + #[error("Missing type for numeric generic")] + MissingTypeForNumericGeneric, + #[error("Expected a function body (`{{ ... }}`), not `;`")] + ExpectedFunctionBody, + #[error("Expected the global to have a value")] + GlobalWithoutValue, + + #[error("Unexpected '{0}', expected a field name or number")] ExpectedFieldName(Token), - #[error("expected a pattern but found a type - {0}")] + #[error("Expected a pattern but found a type - {0}")] ExpectedPatternButFoundType(Token), - #[error("expected an identifier after .")] - ExpectedIdentifierAfterDot, - #[error("expected an identifier after ::")] - ExpectedIdentifierAfterColons, - #[error("expected {{ or -> after function parameters")] - ExpectedLeftBraceOrArrowAfterFunctionParameters, - #[error("expected {{ after if condition")] - ExpectedLeftBraceAfterIfCondition, - #[error("expected <, where or {{ after trait name")] - ExpectedLeftBracketOrWhereOrLeftBraceOrArrowAfterTraitName, - #[error("expected <, where or {{ after impl type")] - ExpectedLeftBracketOrWhereOrLeftBraceOrArrowAfterImplType, - #[error("expected <, where or {{ after trait impl for type")] - ExpectedLeftBracketOrWhereOrLeftBraceOrArrowAfterTraitImplForType, - #[error("expected ( or < after function name")] - ExpectedLeftParenOrLeftBracketAfterFunctionName, #[error("Expected a ; separating these two statements")] MissingSeparatingSemi, #[error("constrain keyword is deprecated")] @@ -105,6 +129,20 @@ impl ParserError { } } + pub fn expected_token(token: Token, found: Token, span: Span) -> ParserError { + let mut error = ParserError::empty(found, span); + error.expected_tokens.insert(token); + error + } + + pub fn expected_one_of_tokens(tokens: &[Token], found: Token, span: Span) -> ParserError { + let mut error = ParserError::empty(found, span); + for token in tokens { + error.expected_tokens.insert(token.clone()); + } + error + } + pub fn expected_label(label: ParsingRuleLabel, found: Token, span: Span) -> ParserError { let mut error = ParserError::empty(found, span); error.expected_labels.insert(label); @@ -136,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() { @@ -154,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 { @@ -206,7 +249,7 @@ impl<'a> From<&'a ParserError> for Diagnostic { Diagnostic::simple_warning(reason.to_string(), "".into(), error.span) } ParserErrorReason::ExpectedPatternButFoundType(ty) => Diagnostic::simple_error( - "Expected a ; separating these two statements".into(), + format!("Expected a pattern but found a type - {ty}"), format!("{ty} is a type and cannot be used as a variable name"), error.span, ), @@ -229,45 +272,3 @@ impl<'a> From<&'a ParserError> for Diagnostic { } } } - -impl chumsky::Error for ParserError { - type Span = Span; - type Label = ParsingRuleLabel; - - fn expected_input_found(span: Self::Span, expected: Iter, found: Option) -> Self - where - Iter: IntoIterator>, - { - ParserError { - expected_tokens: expected.into_iter().map(|opt| opt.unwrap_or(Token::EOF)).collect(), - expected_labels: SmallOrdSet::new(), - found: found.unwrap_or(Token::EOF), - reason: None, - span, - } - } - - fn with_label(mut self, label: Self::Label) -> Self { - self.expected_tokens.clear(); - self.expected_labels.clear(); - self.expected_labels.insert(label); - self - } - - // Merge two errors into a new one that should encompass both. - // If one error has a more specific reason with it then keep - // that reason and discard the other if present. - // The spans of both errors must match, otherwise the error - // messages and error spans may not line up. - fn merge(mut self, mut other: Self) -> Self { - self.expected_tokens.append(&mut other.expected_tokens); - self.expected_labels.append(&mut other.expected_labels); - - if self.reason.is_none() { - self.reason = other.reason; - } - - self.span = self.span.merge(other.span); - self - } -} 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 fd082dfbe56..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,14 +11,27 @@ pub enum ParsingRuleLabel { Cast, Expression, FieldAccess, + Function, + GenericParameter, Global, + Identifier, + Integer, IntegerType, + Item, + LValue, Parameter, + Path, Pattern, Statement, Term, + TraitBound, + TraitImplItem, + TraitItem, + Type, TypeExpression, + TypeOrTypeExpression, TokenKind(TokenKind), + UseSegment, } impl fmt::Display for ParsingRuleLabel { @@ -29,14 +42,27 @@ 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"), + ParsingRuleLabel::Integer => write!(f, "integer"), ParsingRuleLabel::IntegerType => write!(f, "integer type"), + ParsingRuleLabel::Item => write!(f, "item"), + ParsingRuleLabel::LValue => write!(f, "left-hand side of assignment"), ParsingRuleLabel::Parameter => write!(f, "parameter"), + ParsingRuleLabel::Path => write!(f, "path"), ParsingRuleLabel::Pattern => write!(f, "pattern"), ParsingRuleLabel::Statement => write!(f, "statement"), ParsingRuleLabel::Term => write!(f, "term"), + ParsingRuleLabel::TraitBound => write!(f, "trait bound"), + ParsingRuleLabel::TraitImplItem => write!(f, "trait impl item"), + ParsingRuleLabel::TraitItem => write!(f, "trait item"), + ParsingRuleLabel::Type => write!(f, "type"), ParsingRuleLabel::TypeExpression => write!(f, "type expression"), - ParsingRuleLabel::TokenKind(token_kind) => write!(f, "{token_kind:?}"), + ParsingRuleLabel::TypeOrTypeExpression => write!(f, "type or type expression"), + ParsingRuleLabel::TokenKind(token_kind) => write!(f, "{token_kind}"), + ParsingRuleLabel::UseSegment => write!(f, "identifier, `crate`, `dep` or `super`"), } } } 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 ad6dd1459e9..17c156476a7 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs @@ -12,233 +12,15 @@ mod labels; mod parser; use crate::ast::{ - Documented, Expression, Ident, ImportStatement, ItemVisibility, LetStatement, - ModuleDeclaration, NoirFunction, NoirStruct, NoirTrait, NoirTraitImpl, NoirTypeAlias, - Recoverable, StatementKind, TypeImpl, UseTree, + Documented, Ident, ImportStatement, ItemVisibility, LetStatement, ModuleDeclaration, + NoirFunction, NoirStruct, NoirTrait, NoirTraitImpl, NoirTypeAlias, TypeImpl, UseTree, }; -use crate::token::{Keyword, SecondaryAttribute, Token}; +use crate::token::SecondaryAttribute; -use chumsky::prelude::*; -use chumsky::primitive::Container; pub use errors::ParserError; pub use errors::ParserErrorReason; use noirc_errors::Span; -pub use parser::path::path_no_turbofish; -pub use parser::traits::trait_bound; -pub use parser::{ - block, expression, fresh_statement, lvalue, module, parse_program, parse_type, pattern, - top_level_items, top_level_statement, visibility, -}; - -#[derive(Debug, Clone)] -pub struct TopLevelStatement { - pub kind: TopLevelStatementKind, - pub doc_comments: Vec, -} - -#[derive(Debug, Clone)] -pub enum TopLevelStatementKind { - Function(NoirFunction), - Module(ModuleDeclaration), - Import(UseTree, ItemVisibility), - Struct(NoirStruct), - Trait(NoirTrait), - TraitImpl(NoirTraitImpl), - Impl(TypeImpl), - TypeAlias(NoirTypeAlias), - SubModule(ParsedSubModule), - Global(LetStatement, ItemVisibility), - InnerAttribute(SecondaryAttribute), - Error, -} - -impl TopLevelStatementKind { - pub fn into_item_kind(self) -> Option { - match self { - TopLevelStatementKind::Function(f) => Some(ItemKind::Function(f)), - TopLevelStatementKind::Module(m) => Some(ItemKind::ModuleDecl(m)), - TopLevelStatementKind::Import(i, visibility) => Some(ItemKind::Import(i, visibility)), - TopLevelStatementKind::Struct(s) => Some(ItemKind::Struct(s)), - TopLevelStatementKind::Trait(t) => Some(ItemKind::Trait(t)), - TopLevelStatementKind::TraitImpl(t) => Some(ItemKind::TraitImpl(t)), - TopLevelStatementKind::Impl(i) => Some(ItemKind::Impl(i)), - TopLevelStatementKind::TypeAlias(t) => Some(ItemKind::TypeAlias(t)), - TopLevelStatementKind::SubModule(s) => Some(ItemKind::Submodules(s)), - TopLevelStatementKind::Global(c, visibility) => Some(ItemKind::Global(c, visibility)), - TopLevelStatementKind::InnerAttribute(a) => Some(ItemKind::InnerAttribute(a)), - TopLevelStatementKind::Error => None, - } - } -} - -// Helper trait that gives us simpler type signatures for return types: -// e.g. impl Parser versus impl Parser> -pub trait NoirParser: Parser + Sized + Clone {} -impl NoirParser for P where P: Parser + Clone {} - -// ExprParser just serves as a type alias for NoirParser + Clone -pub trait ExprParser: NoirParser {} -impl

ExprParser for P where P: NoirParser {} - -fn parenthesized(parser: P) -> impl NoirParser -where - P: NoirParser, - T: Recoverable, -{ - use Token::*; - parser.delimited_by(just(LeftParen), just(RightParen)).recover_with(nested_delimiters( - LeftParen, - RightParen, - [(LeftBracket, RightBracket)], - Recoverable::error, - )) -} - -fn spanned(parser: P) -> impl NoirParser<(T, Span)> -where - P: NoirParser, -{ - parser.map_with_span(|value, span| (value, span)) -} - -// Parse with the first parser, then continue by -// repeating the second parser 0 or more times. -// The passed in function is then used to combine the -// results of both parsers along with their spans at -// each step. -fn foldl_with_span( - first_parser: P1, - to_be_repeated: P2, - f: F, -) -> impl NoirParser -where - P1: NoirParser, - P2: NoirParser, - F: Fn(T1, T2, Span) -> T1 + Clone, -{ - spanned(first_parser) - .then(spanned(to_be_repeated).repeated()) - .foldl(move |(a, a_span), (b, b_span)| { - let span = a_span.merge(b_span); - (f(a, b, span), span) - }) - .map(|(value, _span)| value) -} - -/// Sequence the two parsers. -/// Fails if the first parser fails, otherwise forces -/// the second parser to succeed while logging any errors. -fn then_commit<'a, P1, P2, T1, T2>( - first_parser: P1, - second_parser: P2, -) -> impl NoirParser<(T1, T2)> + 'a -where - P1: NoirParser + 'a, - P2: NoirParser + 'a, - T2: Clone + Recoverable + 'a, -{ - let second_parser = skip_then_retry_until(second_parser) - .map_with_span(|option, span| option.unwrap_or_else(|| Recoverable::error(span))); - - first_parser.then(second_parser) -} - -fn then_commit_ignore<'a, P1, P2, T1, T2>( - first_parser: P1, - second_parser: P2, -) -> impl NoirParser + 'a -where - P1: NoirParser + 'a, - P2: NoirParser + 'a, - T1: 'a, - T2: Clone + 'a, -{ - let second_parser = skip_then_retry_until(second_parser); - first_parser.then_ignore(second_parser) -} - -fn ignore_then_commit<'a, P1, P2, T1: 'a, T2: Clone + 'a>( - first_parser: P1, - second_parser: P2, -) -> impl NoirParser + 'a -where - P1: NoirParser + 'a, - P2: NoirParser + 'a, - T2: Recoverable, -{ - let second_parser = skip_then_retry_until(second_parser) - .map_with_span(|option, span| option.unwrap_or_else(|| Recoverable::error(span))); - - first_parser.ignore_then(second_parser) -} - -fn skip_then_retry_until<'a, P, T>(parser: P) -> impl NoirParser> + 'a -where - P: NoirParser + 'a, - T: Clone + 'a, -{ - let terminators = [ - Token::EOF, - Token::Colon, - Token::Semicolon, - Token::RightBrace, - Token::Keyword(Keyword::Let), - Token::Keyword(Keyword::Constrain), - ]; - force(parser.recover_with(chumsky::prelude::skip_then_retry_until(terminators))) -} - -/// General recovery strategy: try to skip to the target token, failing if we encounter the -/// 'too_far' token beforehand. -/// -/// Expects all of `too_far` to be contained within `targets` -fn try_skip_until(targets: C1, too_far: C2) -> impl NoirParser -where - T: Recoverable + Clone, - C1: Container + Clone, - C2: Container + Clone, -{ - chumsky::prelude::none_of(targets) - .repeated() - .ignore_then(one_of(too_far.clone()).rewind()) - .try_map(move |peek, span| { - if too_far.get_iter().any(|t| t == peek) { - // This error will never be shown to the user - Err(ParserError::empty(Token::EOF, span)) - } else { - Ok(Recoverable::error(span)) - } - }) -} - -/// Recovery strategy for statements: If a statement fails to parse skip until the next ';' or fail -/// if we find a '}' first. -fn statement_recovery() -> impl NoirParser { - use Token::*; - try_skip_until([Semicolon, RightBrace], RightBrace) -} - -fn parameter_recovery() -> impl NoirParser { - use Token::*; - try_skip_until([Comma, RightParen], RightParen) -} - -fn parameter_name_recovery() -> impl NoirParser { - use Token::*; - try_skip_until([Colon, RightParen, Comma], [RightParen, Comma]) -} - -fn top_level_statement_recovery() -> impl NoirParser { - none_of([Token::RightBrace, Token::EOF]) - .repeated() - .ignore_then(one_of([Token::Semicolon])) - .map(|_| TopLevelStatementKind::Error) -} - -/// Force the given parser to succeed, logging any errors it had -fn force<'a, T: 'a>(parser: impl NoirParser + 'a) -> impl NoirParser> + 'a { - parser.map(Some).recover_via(empty().map(|_| None)) -} +pub use parser::{parse_program, Parser, StatementOrExpressionOrLValue}; #[derive(Clone, Default)] pub struct SortedModule { @@ -362,6 +144,35 @@ pub enum ItemKind { InnerAttribute(SecondaryAttribute), } +impl std::fmt::Display for ItemKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ItemKind::Function(fun) => fun.fmt(f), + ItemKind::ModuleDecl(m) => m.fmt(f), + ItemKind::Import(tree, visibility) => { + if visibility == &ItemVisibility::Private { + write!(f, "use {tree}") + } else { + write!(f, "{visibility} use {tree}") + } + } + ItemKind::Trait(t) => t.fmt(f), + ItemKind::TraitImpl(i) => i.fmt(f), + ItemKind::Struct(s) => s.fmt(f), + ItemKind::Impl(i) => i.fmt(f), + ItemKind::TypeAlias(t) => t.fmt(f), + ItemKind::Submodules(s) => s.fmt(f), + ItemKind::Global(c, visibility) => { + if visibility != &ItemVisibility::Private { + write!(f, "{visibility} ")?; + } + c.fmt(f) + } + ItemKind::InnerAttribute(a) => write!(f, "#![{}]", a), + } + } +} + /// A submodule defined via `mod name { contents }` in some larger file. /// These submodules always share the same file as some larger ParsedModule #[derive(Clone, Debug)] @@ -453,112 +264,6 @@ impl SortedModule { } } -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd)] -pub enum Precedence { - Lowest, - Or, - And, - Xor, - LessGreater, - Shift, - Sum, - Product, - Highest, -} - -impl Precedence { - // Higher the number, the higher(more priority) the precedence - // XXX: Check the precedence is correct for operators - fn token_precedence(tok: &Token) -> Option { - let precedence = match tok { - Token::Equal => Precedence::Lowest, - Token::NotEqual => Precedence::Lowest, - Token::Pipe => Precedence::Or, - Token::Ampersand => Precedence::And, - Token::Caret => Precedence::Xor, - Token::Less => Precedence::LessGreater, - Token::LessEqual => Precedence::LessGreater, - Token::Greater => Precedence::LessGreater, - Token::GreaterEqual => Precedence::LessGreater, - Token::ShiftLeft => Precedence::Shift, - Token::ShiftRight => Precedence::Shift, - Token::Plus => Precedence::Sum, - Token::Minus => Precedence::Sum, - Token::Slash => Precedence::Product, - Token::Star => Precedence::Product, - Token::Percent => Precedence::Product, - _ => return None, - }; - - assert_ne!(precedence, Precedence::Highest, "expression_with_precedence in the parser currently relies on the highest precedence level being uninhabited"); - Some(precedence) - } - - /// Return the next higher precedence. E.g. `Sum.next() == Product` - fn next(self) -> Self { - use Precedence::*; - match self { - Lowest => Or, - Or => Xor, - Xor => And, - And => LessGreater, - LessGreater => Shift, - Shift => Sum, - Sum => Product, - Product => Highest, - Highest => Highest, - } - } - - /// TypeExpressions only contain basic arithmetic operators and - /// notably exclude `>` due to parsing conflicts with generic type brackets. - fn next_type_precedence(self) -> Self { - use Precedence::*; - match self { - Lowest => Sum, - Sum => Product, - Product => Highest, - Highest => Highest, - other => unreachable!("Unexpected precedence level in type expression: {:?}", other), - } - } - - /// The operators with the lowest precedence still useable in type expressions - /// are '+' and '-' with precedence Sum. - fn lowest_type_precedence() -> Self { - Precedence::Sum - } -} - -impl std::fmt::Display for TopLevelStatementKind { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - TopLevelStatementKind::Function(fun) => fun.fmt(f), - TopLevelStatementKind::Module(m) => m.fmt(f), - TopLevelStatementKind::Import(tree, visibility) => { - if visibility != &ItemVisibility::Private { - write!(f, "{visibility} ")?; - } - write!(f, "use {tree}") - } - TopLevelStatementKind::Trait(t) => t.fmt(f), - TopLevelStatementKind::TraitImpl(i) => i.fmt(f), - TopLevelStatementKind::Struct(s) => s.fmt(f), - TopLevelStatementKind::Impl(i) => i.fmt(f), - TopLevelStatementKind::TypeAlias(t) => t.fmt(f), - TopLevelStatementKind::SubModule(s) => s.fmt(f), - TopLevelStatementKind::Global(c, visibility) => { - if visibility != &ItemVisibility::Private { - write!(f, "{visibility} ")?; - } - c.fmt(f) - } - TopLevelStatementKind::InnerAttribute(a) => write!(f, "#![{}]", a), - TopLevelStatementKind::Error => write!(f, "error"), - } - } -} - impl std::fmt::Display for ParsedModule { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.clone().into_sorted().fmt(f) 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 df656dc5a7d..0030144b5e1 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs @@ -1,88 +1,44 @@ -//! This file contains the bulk of the implementation of noir's parser. -//! -//! Noir's parser is built off the [chumsky library](https://docs.rs/chumsky/latest/chumsky/) -//! for parser combinators. In this technique, parsers are built from smaller parsers that -//! parse e.g. only a single token. Then there are functions which can combine multiple -//! parsers together to create a larger one. These functions are called parser combinators. -//! For example, `a.then(b)` combines two parsers a and b and returns one that parses a -//! then parses b and fails if either fails. Other combinators like `a.or(b)` exist as well -//! and are used extensively. Note that these form a PEG grammar so if there are multiple -//! options as in `a.or(b)` the first matching parse will be chosen. -//! -//! Noir's grammar is not formally specified but can be estimated by inspecting each function. -//! For example, a function `f` parsing `choice((a, b, c))` can be roughly translated to -//! BNF as `f: a | b | c`. -//! -//! Occasionally there will also be recovery strategies present, either via `recover_via(Parser)` -//! or `recover_with(Strategy)`. The difference between the two functions isn't quite so important, -//! but both allow the parser to recover from a parsing error, log the error, and return an error -//! expression instead. These are used to parse cases such as `fn foo( { }` where we know the user -//! meant to write a function and thus we should log the error and return a function with no parameters, -//! rather than failing to parse a function and trying to subsequently parse a struct. Generally using -//! recovery strategies improves parser errors but using them incorrectly can be problematic since they -//! prevent other parsers from being tried afterward since there is no longer an error. Thus, they should -//! be limited to cases like the above `fn` example where it is clear we shouldn't back out of the -//! current parser to try alternative parsers in a `choice` expression. -use self::path::{as_trait_path, type_path}; -use self::primitives::{ - interned_statement, interned_statement_expr, keyword, macro_quote_marker, mutable_reference, - variable, +use acvm::FieldElement; +use modifiers::Modifiers; +use noirc_errors::Span; + +use crate::{ + ast::{Ident, ItemVisibility}, + lexer::{Lexer, SpannedTokenResult}, + token::{IntType, Keyword, SpannedToken, Token, TokenKind, Tokens}, }; -use self::types::{generic_type_args, maybe_comp_time}; -use attributes::{attributes, inner_attribute, validate_secondary_attributes}; -use doc_comments::{inner_doc_comments, outer_doc_comments}; -use types::interned_unresolved_type; -pub use types::parse_type; -use visibility::item_visibility; -pub use visibility::visibility; - -use super::{ - foldl_with_span, labels::ParsingRuleLabel, parameter_name_recovery, parameter_recovery, - parenthesized, then_commit, then_commit_ignore, top_level_statement_recovery, ExprParser, - NoirParser, ParsedModule, ParsedSubModule, ParserError, ParserErrorReason, Precedence, - TopLevelStatementKind, -}; -use super::{spanned, Item, TopLevelStatement}; -use crate::ast::{ - BinaryOp, BinaryOpKind, BlockExpression, Documented, ForLoopStatement, ForRange, - GenericTypeArgs, Ident, IfExpression, InfixExpression, LValue, Literal, ModuleDeclaration, - NoirTypeAlias, Param, Path, Pattern, Recoverable, Statement, TypeImpl, UnaryRhsMemberAccess, - UnaryRhsMethodCall, UseTree, UseTreeKind, Visibility, -}; -use crate::ast::{ - Expression, ExpressionKind, LetStatement, StatementKind, UnresolvedType, UnresolvedTypeData, -}; -use crate::lexer::Lexer; -use crate::parser::{force, ignore_then_commit, statement_recovery}; -use crate::token::{Keyword, Token, TokenKind}; -use acvm::AcirField; -use chumsky::prelude::*; -use iter_extended::vecmap; -use noirc_errors::{Span, Spanned}; +use super::{labels::ParsingRuleLabel, ParsedModule, ParserError, ParserErrorReason}; -mod assertion; +mod arguments; mod attributes; mod doc_comments; +mod expression; mod function; -mod lambdas; -mod literals; -pub(super) mod path; -mod primitives; +mod generics; +mod global; +mod impls; +mod infix; +mod item; +mod item_visibility; +mod lambda; +mod modifiers; +mod module; +mod parse_many; +mod path; +mod pattern; +mod statement; +mod statement_or_expression_or_lvalue; mod structs; -pub(super) mod traits; +mod tests; +mod traits; +mod type_alias; +mod type_expression; mod types; -mod visibility; - -#[cfg(test)] -mod test_helpers; +mod use_tree; +mod where_clause; -use literals::literal; -use path::{maybe_empty_path, path}; -use primitives::{ - dereference, ident, interned_expr, negation, not, nothing, right_shift_operator, token_kind, -}; -use traits::where_clause; +pub use statement_or_expression_or_lvalue::StatementOrExpressionOrLValue; /// Entry function for the parser - also handles lexing internally. /// @@ -91,1752 +47,491 @@ use traits::where_clause; /// Vec is non-empty, there may be Error nodes in the Ast to fill in the gaps that /// failed to parse. Otherwise the Ast is guaranteed to have 0 Error nodes. pub fn parse_program(source_program: &str) -> (ParsedModule, Vec) { - let (tokens, lexing_errors) = Lexer::lex(source_program); - let (module, mut parsing_errors) = program().parse_recovery_verbose(tokens); - - parsing_errors.extend(lexing_errors.into_iter().map(Into::into)); - let parsed_module = module.unwrap_or_default(); - - (parsed_module, parsing_errors) -} - -/// program: module EOF -fn program() -> impl NoirParser { - module().then_ignore(just(Token::EOF)) -} - -/// module: top_level_statement module -/// | %empty -pub fn module() -> impl NoirParser { - recursive(|module_parser| { - inner_doc_comments() - .then( - empty() - .to(ParsedModule::default()) - .then(spanned(top_level_statement(module_parser)).repeated()) - .foldl(|mut program, (statement, span)| { - if let Some(kind) = statement.kind.into_item_kind() { - program.items.push(Item { - kind, - span, - doc_comments: statement.doc_comments, - }); - } - program - }), - ) - .map(|(doc_comments, mut program)| { - program.inner_doc_comments = doc_comments; - program - }) - }) -} - -/// This parser is used for parsing top level statements in macros -pub fn top_level_items() -> impl NoirParser> { - top_level_statement(module()).repeated() -} - -pub fn top_level_statement<'a>( - module_parser: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - outer_doc_comments() - .then(top_level_statement_kind(module_parser)) - .map(|(doc_comments, kind)| TopLevelStatement { kind, doc_comments }) -} - -/// top_level_statement: function_definition -/// | struct_definition -/// | trait_definition -/// | implementation -/// | submodule -/// | module_declaration -/// | use_statement -/// | global_declaration -fn top_level_statement_kind<'a>( - module_parser: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - choice(( - function::function_definition(false).map(TopLevelStatementKind::Function), - structs::struct_definition(), - traits::trait_definition(), - traits::trait_implementation(), - implementation(), - type_alias_definition().then_ignore(force(just(Token::Semicolon))), - submodule(module_parser.clone()), - contract(module_parser), - module_declaration().then_ignore(force(just(Token::Semicolon))), - use_statement().then_ignore(force(just(Token::Semicolon))), - global_declaration().then_ignore(force(just(Token::Semicolon))), - inner_attribute().map(TopLevelStatementKind::InnerAttribute), - )) - .recover_via(top_level_statement_recovery()) -} - -/// Parses a non-trait implementation, adding a set of methods to a type. -/// -/// implementation: 'impl' generics type '{' function_definition ... '}' -fn implementation() -> impl NoirParser { - let method = spanned(function::function_definition(true)); - let methods = outer_doc_comments() - .then(method) - .map(|(doc_comments, (method, span))| (Documented::new(method, doc_comments), span)) - .repeated(); - - let methods_or_error = just(Token::LeftBrace) - .ignore_then(methods) - .then_ignore(just(Token::RightBrace)) - .or_not() - .validate(|methods, span, emit| { - if let Some(methods) = methods { - methods - } else { - emit(ParserError::with_reason( - ParserErrorReason::ExpectedLeftBracketOrWhereOrLeftBraceOrArrowAfterImplType, - span, - )); - vec![] - } - }); - - keyword(Keyword::Impl) - .ignore_then(function::generics()) - .then(parse_type().map_with_span(|typ, span| (typ, span))) - .then(where_clause()) - .then(methods_or_error) - .map(|args| { - let ((other_args, where_clause), methods) = args; - let (generics, (object_type, type_span)) = other_args; - TopLevelStatementKind::Impl(TypeImpl { - generics, - object_type, - type_span, - where_clause, - methods, - }) - }) -} - -/// global_declaration: 'global' ident global_type_annotation '=' literal -fn global_declaration() -> impl NoirParser { - let p = attributes::attributes() - .then(item_visibility()) - .then(maybe_comp_time()) - .then(spanned(keyword(Keyword::Mut)).or_not()) - .then_ignore(keyword(Keyword::Global).labelled(ParsingRuleLabel::Global)) - .then(ident().map(Pattern::Identifier)); - - let p = then_commit(p, optional_type_annotation()); - let p = then_commit_ignore(p, just(Token::Assign)); - let p = then_commit(p, expression()); - p.validate( - |((((((attributes, visibility), comptime), mutable), mut pattern), r#type), expression), - span, - emit| { - let global_attributes = - attributes::validate_secondary_attributes(attributes, span, emit); - - // Only comptime globals are allowed to be mutable, but we always parse the `mut` - // and throw the error in name resolution. - if let Some((_, mut_span)) = mutable { - let span = mut_span.merge(pattern.span()); - pattern = Pattern::Mutable(Box::new(pattern), span, false); - } - ( - LetStatement { - pattern, - r#type, - comptime, - expression, - attributes: global_attributes, - }, - visibility, - ) - }, - ) - .map(|(let_statement, visibility)| TopLevelStatementKind::Global(let_statement, visibility)) -} - -/// submodule: 'mod' ident '{' module '}' -fn submodule( - module_parser: impl NoirParser, -) -> impl NoirParser { - attributes() - .then(item_visibility()) - .then_ignore(keyword(Keyword::Mod)) - .then(ident()) - .then_ignore(just(Token::LeftBrace)) - .then(module_parser) - .then_ignore(just(Token::RightBrace)) - .validate(|(((attributes, visibility), name), contents), span, emit| { - let attributes = validate_secondary_attributes(attributes, span, emit); - TopLevelStatementKind::SubModule(ParsedSubModule { - visibility, - name, - contents, - outer_attributes: attributes, - is_contract: false, - }) - }) -} - -/// contract: 'contract' ident '{' module '}' -fn contract( - module_parser: impl NoirParser, -) -> impl NoirParser { - attributes() - .then(item_visibility()) - .then_ignore(keyword(Keyword::Contract)) - .then(ident()) - .then_ignore(just(Token::LeftBrace)) - .then(module_parser) - .then_ignore(just(Token::RightBrace)) - .validate(|(((attributes, visibility), name), contents), span, emit| { - let attributes = validate_secondary_attributes(attributes, span, emit); - TopLevelStatementKind::SubModule(ParsedSubModule { - visibility, - name, - contents, - outer_attributes: attributes, - is_contract: true, - }) - }) -} - -fn type_alias_definition() -> impl NoirParser { - use self::Keyword::Type; - - item_visibility() - .then_ignore(keyword(Type)) - .then(ident()) - .then(function::generics()) - .then_ignore(just(Token::Assign)) - .then(parse_type()) - .map_with_span(|(((visibility, name), generics), typ), span| { - TopLevelStatementKind::TypeAlias(NoirTypeAlias { - name, - generics, - typ, - visibility, - span, - }) - }) -} - -fn self_parameter() -> impl NoirParser { - let mut_ref_pattern = just(Token::Ampersand).then_ignore(keyword(Keyword::Mut)); - let mut_pattern = keyword(Keyword::Mut); - - mut_ref_pattern - .or(mut_pattern) - .map_with_span(|token, span| (token, span)) - .or_not() - .then(filter_map(move |span, found: Token| match found { - Token::Ident(ref word) if word == "self" => Ok(span), - _ => Err(ParserError::expected_label(ParsingRuleLabel::Parameter, found, span)), - })) - .map(|(pattern_keyword, ident_span)| { - let ident = Ident::new("self".to_string(), ident_span); - let path = Path::from_single("Self".to_owned(), ident_span); - let no_args = GenericTypeArgs::default(); - let mut self_type = - UnresolvedTypeData::Named(path, no_args, true).with_span(ident_span); - let mut pattern = Pattern::Identifier(ident); - - match pattern_keyword { - Some((Token::Ampersand, _)) => { - self_type = UnresolvedTypeData::MutableReference(Box::new(self_type)) - .with_span(ident_span); - } - Some((Token::Keyword(_), span)) => { - pattern = Pattern::Mutable(Box::new(pattern), span.merge(ident_span), true); - } - _ => (), - } - - Param { span: pattern.span(), pattern, typ: self_type, visibility: Visibility::Private } - }) -} - -/// Function declaration parameters differ from other parameters in that parameter -/// patterns are not allowed in declarations. All parameters must be identifiers. -fn function_declaration_parameters() -> impl NoirParser> { - let typ = parse_type().recover_via(parameter_recovery()); - let typ = just(Token::Colon).ignore_then(typ); - - let full_parameter = ident().recover_via(parameter_name_recovery()).then(typ); - let self_parameter = self_parameter().validate(|param, span, emit| { - match param.pattern { - Pattern::Identifier(ident) => (ident, param.typ), - other => { - emit(ParserError::with_reason( - ParserErrorReason::PatternInTraitFunctionParameter, - span, - )); - // into_ident panics on tuple or struct patterns but should be fine to call here - // since the `self` parser can only parse `self`, `mut self` or `&mut self`. - (other.into_ident(), param.typ) + let lexer = Lexer::new(source_program); + let mut parser = Parser::for_lexer(lexer); + let program = parser.parse_program(); + let errors = parser.errors; + (program, errors) +} + +enum TokenStream<'a> { + Lexer(Lexer<'a>), + Tokens(Tokens), +} + +impl<'a> TokenStream<'a> { + fn next(&mut self) -> Option { + match self { + TokenStream::Lexer(lexer) => lexer.next(), + TokenStream::Tokens(tokens) => { + // NOTE: `TokenStream::Tokens` is only created via `Parser::for_tokens(tokens)` which + // reverses `tokens`. That's why using `pop` here is fine (done for performance reasons). + tokens.0.pop().map(Ok) } } - }); - - let parameter = full_parameter.or(self_parameter); - - parameter - .separated_by(just(Token::Comma)) - .allow_trailing() - .labelled(ParsingRuleLabel::Parameter) -} - -fn block_expr<'a>( - statement: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - block(statement).map(ExpressionKind::Block).map_with_span(Expression::new) -} - -pub fn block<'a>( - statement: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - use Token::*; - - statement - .recover_via(statement_recovery()) - .then(just(Semicolon).or_not().map_with_span(|s, span| (s, span))) - .map_with_span(|(kind, rest), span| (Statement { kind, span }, rest)) - .repeated() - .validate(check_statements_require_semicolon) - .delimited_by(just(LeftBrace), just(RightBrace)) - .recover_with(nested_delimiters( - LeftBrace, - RightBrace, - [(LeftParen, RightParen), (LeftBracket, RightBracket)], - |span| vec![Statement { kind: StatementKind::Error, span }], - )) - .map(|statements| BlockExpression { statements }) + } } -fn check_statements_require_semicolon( - statements: Vec<(Statement, (Option, Span))>, - _span: Span, - emit: &mut dyn FnMut(ParserError), -) -> Vec { - let last = statements.len().saturating_sub(1); - let iter = statements.into_iter().enumerate(); - vecmap(iter, |(i, (statement, (semicolon, span)))| { - statement.add_semicolon(semicolon, span, i == last, emit) - }) -} +pub struct Parser<'a> { + pub(crate) errors: Vec, + tokens: TokenStream<'a>, -/// Parse an optional ': type' -fn optional_type_annotation<'a>() -> impl NoirParser + 'a { - ignore_then_commit(just(Token::Colon), parse_type()).or_not().map_with_span(|r#type, span| { - r#type.unwrap_or(UnresolvedTypeData::Unspecified.with_span(span)) - }) + // We always have one look-ahead token for these cases: + // - check if we get `&` or `&mut` + // - check if we get `>` or `>>` + token: SpannedToken, + next_token: SpannedToken, + current_token_span: Span, + previous_token_span: Span, } -fn module_declaration() -> impl NoirParser { - attributes().then(item_visibility()).then_ignore(keyword(Keyword::Mod)).then(ident()).validate( - |((attributes, visibility), ident), span, emit| { - let attributes = validate_secondary_attributes(attributes, span, emit); - TopLevelStatementKind::Module(ModuleDeclaration { - visibility, - ident, - outer_attributes: attributes, - }) - }, - ) -} +impl<'a> Parser<'a> { + pub fn for_lexer(lexer: Lexer<'a>) -> Self { + Self::new(TokenStream::Lexer(lexer)) + } -fn use_statement() -> impl NoirParser { - item_visibility() - .then_ignore(keyword(Keyword::Use)) - .then(use_tree()) - .map(|(visibility, use_tree)| TopLevelStatementKind::Import(use_tree, visibility)) -} + pub fn for_tokens(mut tokens: Tokens) -> Self { + tokens.0.reverse(); + Self::new(TokenStream::Tokens(tokens)) + } -fn rename() -> impl NoirParser> { - ignore_then_commit(keyword(Keyword::As), ident()).or_not() -} + pub fn for_str(str: &'a str) -> Self { + Self::for_lexer(Lexer::new(str)) + } -fn use_tree() -> impl NoirParser { - recursive(|use_tree| { - let simple = path::path_no_turbofish().then(rename()).map(|(mut prefix, alias)| { - let ident = prefix.pop().ident; - UseTree { prefix, kind: UseTreeKind::Path(ident, alias) } - }); - - let list = { - let prefix = maybe_empty_path().then_ignore(just(Token::DoubleColon)); - let tree = use_tree - .separated_by(just(Token::Comma)) - .allow_trailing() - .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)) - .map(UseTreeKind::List); - - prefix.then(tree).map(|(prefix, kind)| UseTree { prefix, kind }) + fn new(tokens: TokenStream<'a>) -> Self { + let mut parser = Self { + errors: Vec::new(), + tokens, + token: eof_spanned_token(), + next_token: eof_spanned_token(), + current_token_span: Default::default(), + previous_token_span: Default::default(), }; + parser.read_two_first_tokens(); + parser + } - choice((list, simple)) - }) -} - -fn statement<'a, P, P2>( - expr_parser: P, - expr_no_constructors: P2, -) -> impl NoirParser + 'a -where - P: ExprParser + 'a, - P2: ExprParser + 'a, -{ - recursive(|statement| { - choice(( - assertion::constrain(expr_parser.clone()), - assertion::assertion(expr_parser.clone()), - declaration(expr_parser.clone()), - assignment(expr_parser.clone()), - if_statement(expr_no_constructors.clone(), statement.clone()), - block_statement(statement.clone()), - for_loop(expr_no_constructors.clone(), statement.clone()), - break_statement(), - continue_statement(), - return_statement(expr_parser.clone()), - comptime_statement(expr_parser.clone(), expr_no_constructors, statement), - interned_statement(), - expr_parser.map(StatementKind::Expression), - )) - }) -} - -pub fn fresh_statement() -> impl NoirParser { - statement(expression(), expression_no_constructors(expression())) -} - -fn break_statement() -> impl NoirParser { - keyword(Keyword::Break).to(StatementKind::Break) -} - -fn continue_statement() -> impl NoirParser { - keyword(Keyword::Continue).to(StatementKind::Continue) -} - -fn comptime_statement<'a, P1, P2, S>( - expr: P1, - expr_no_constructors: P2, - statement: S, -) -> impl NoirParser + 'a -where - P1: ExprParser + 'a, - P2: ExprParser + 'a, - S: NoirParser + 'a, -{ - let comptime_statement = choice(( - declaration(expr), - for_loop(expr_no_constructors, statement.clone()), - block(statement).map_with_span(|block, span| { - StatementKind::Expression(Expression::new(ExpressionKind::Block(block), span)) - }), - )) - .map_with_span(|kind, span| Box::new(Statement { kind, span })); - - keyword(Keyword::Comptime).ignore_then(comptime_statement).map(StatementKind::Comptime) -} - -/// Comptime in an expression position only accepts entire blocks -fn comptime_expr<'a, S>(statement: S) -> impl NoirParser + 'a -where - S: NoirParser + 'a, -{ - keyword(Keyword::Comptime) - .ignore_then(spanned(block(statement))) - .map(|(block, span)| ExpressionKind::Comptime(block, span)) -} - -fn unsafe_expr<'a, S>(statement: S) -> impl NoirParser + 'a -where - S: NoirParser + 'a, -{ - keyword(Keyword::Unsafe) - .ignore_then(spanned(block(statement))) - .map(|(block, span)| ExpressionKind::Unsafe(block, span)) -} - -fn let_statement<'a, P>( - expr_parser: P, -) -> impl NoirParser<((Pattern, UnresolvedType), Expression)> + 'a -where - P: ExprParser + 'a, -{ - let p = - ignore_then_commit(keyword(Keyword::Let).labelled(ParsingRuleLabel::Statement), pattern()); - let p = p.then(optional_type_annotation()); - let p = then_commit_ignore(p, just(Token::Assign)); - then_commit(p, expr_parser) -} - -fn declaration<'a, P>(expr_parser: P) -> impl NoirParser + 'a -where - P: ExprParser + 'a, -{ - attributes().then(let_statement(expr_parser)).validate( - |(attributes, ((pattern, typ), expr)), span, emit| { - let attributes = attributes::validate_secondary_attributes(attributes, span, emit); - StatementKind::new_let(pattern, typ, expr, attributes) - }, - ) -} - -pub fn pattern() -> impl NoirParser { - recursive(|pattern| { - let ident_pattern = ident().map(Pattern::Identifier).map_err(|mut error| { - if matches!(error.found(), Token::IntType(..)) { - error = ParserError::with_reason( - ParserErrorReason::ExpectedPatternButFoundType(error.found().clone()), - error.span(), - ); - } - - error - }); - - let mut_pattern = keyword(Keyword::Mut) - .ignore_then(pattern.clone()) - .map_with_span(|inner, span| Pattern::Mutable(Box::new(inner), span, false)); - - let short_field = ident().map(|name| (name.clone(), Pattern::Identifier(name))); - let long_field = ident().then_ignore(just(Token::Colon)).then(pattern.clone()); - - let struct_pattern_fields = long_field - .or(short_field) - .separated_by(just(Token::Comma)) - .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)); - - let struct_pattern = path(super::parse_type()) - .then(struct_pattern_fields) - .map_with_span(|(typename, fields), span| Pattern::Struct(typename, fields, span)); - - let tuple_pattern = pattern - .separated_by(just(Token::Comma)) - .delimited_by(just(Token::LeftParen), just(Token::RightParen)) - .map_with_span(Pattern::Tuple); - - let interned = - token_kind(TokenKind::InternedPattern).map_with_span(|token, span| match token { - Token::InternedPattern(id) => Pattern::Interned(id, span), - _ => unreachable!( - "token_kind(InternedPattern) guarantees we parse an interned pattern" - ), - }); - - choice((mut_pattern, tuple_pattern, struct_pattern, ident_pattern, interned)) - }) - .labelled(ParsingRuleLabel::Pattern) -} - -fn assignment<'a, P>(expr_parser: P) -> impl NoirParser + 'a -where - P: ExprParser + 'a, -{ - let fallible = - lvalue(expr_parser.clone()).then(assign_operator()).labelled(ParsingRuleLabel::Statement); - - then_commit(fallible, expr_parser).map_with_span( - |((identifier, operator), expression), span| { - StatementKind::assign(identifier, operator, expression, span) - }, - ) -} - -/// Parse an assignment operator `=` optionally prefixed by a binary operator for a combined -/// assign statement shorthand. Notably, this must handle a few corner cases with how `>>` is -/// lexed as two separate greater-than operators rather than a single right-shift. -fn assign_operator() -> impl NoirParser { - let shorthand_operators = Token::assign_shorthand_operators(); - // We need to explicitly check for right_shift here since it is actually - // two separate greater-than operators. - let shorthand_operators = right_shift_operator().or(one_of(shorthand_operators)); - let shorthand_syntax = shorthand_operators.then_ignore(just(Token::Assign)); - - // Since >> is lexed as two separate "greater-than"s, >>= is lexed as > >=, so - // we need to account for that case here as well. - let right_shift_fix = - just(Token::Greater).then(just(Token::GreaterEqual)).to(Token::ShiftRight); - - let shorthand_syntax = shorthand_syntax.or(right_shift_fix); - just(Token::Assign).or(shorthand_syntax) -} - -enum LValueRhs { - MemberAccess(Ident, Span), - Index(Expression, Span), -} - -pub fn lvalue<'a, P>(expr_parser: P) -> impl NoirParser + 'a -where - P: ExprParser + 'a, -{ - recursive(|lvalue| { - let l_ident = ident().map(LValue::Ident); + /// Program = Module + pub(crate) fn parse_program(&mut self) -> ParsedModule { + self.parse_module( + false, // nested + ) + } - let dereferences = just(Token::Star) - .ignore_then(lvalue.clone()) - .map_with_span(|lvalue, span| LValue::Dereference(Box::new(lvalue), span)); + /// Module = InnerDocComments Item* + pub(crate) fn parse_module(&mut self, nested: bool) -> ParsedModule { + let inner_doc_comments = self.parse_inner_doc_comments(); + let items = self.parse_module_items(nested); - let parenthesized = lvalue.delimited_by(just(Token::LeftParen), just(Token::RightParen)); + ParsedModule { items, inner_doc_comments } + } - let interned = - token_kind(TokenKind::InternedLValue).map_with_span(|token, span| match token { - Token::InternedLValue(id) => LValue::Interned(id, span), - _ => unreachable!( - "token_kind(InternedLValue) guarantees we parse an interned lvalue" - ), - }); + /// 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. + pub fn parse_result(mut self, parsing_function: F) -> Result> + where + F: FnOnce(&mut Parser<'a>) -> T, + { + let item = parsing_function(&mut self); + if !self.at_eof() { + self.expected_token(Token::EOF); + return Err(self.errors); + } - let term = choice((parenthesized, dereferences, l_ident, interned)); + if self.errors.is_empty() { + Ok(item) + } else { + Err(self.errors) + } + } - let l_member_rhs = - just(Token::Dot).ignore_then(field_name()).map_with_span(LValueRhs::MemberAccess); + /// 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 None. + pub fn parse_option(self, parsing_function: F) -> Option + where + F: FnOnce(&mut Parser<'a>) -> Option, + { + match self.parse_result(parsing_function) { + Ok(item) => item, + Err(_) => None, + } + } - let l_index = expr_parser - .delimited_by(just(Token::LeftBracket), just(Token::RightBracket)) - .map_with_span(LValueRhs::Index); + /// Bumps this parser by one token. Returns the token that was previously the "current" token. + fn bump(&mut self) -> SpannedToken { + self.previous_token_span = self.current_token_span; + let next_next_token = self.read_token_internal(); + let next_token = std::mem::replace(&mut self.next_token, next_next_token); + let token = std::mem::replace(&mut self.token, next_token); + self.current_token_span = self.token.to_span(); + token + } - term.then(l_member_rhs.or(l_index).repeated()).foldl(|lvalue, rhs| match rhs { - LValueRhs::MemberAccess(field_name, span) => { - let span = lvalue.span().merge(span); - LValue::MemberAccess { object: Box::new(lvalue), field_name, span } - } - LValueRhs::Index(index, span) => { - let span = lvalue.span().merge(span); - LValue::Index { array: Box::new(lvalue), index, span } - } - }) - }) -} + fn read_two_first_tokens(&mut self) { + self.token = self.read_token_internal(); + self.current_token_span = self.token.to_span(); + self.next_token = self.read_token_internal(); + } -fn call_data() -> impl NoirParser { - keyword(Keyword::CallData).then(parenthesized(literal())).validate(|token, span, emit| { - match token { - (_, ExpressionKind::Literal(Literal::Integer(x, _))) => { - let id = x.to_u128() as u32; - Visibility::CallData(id) - } - _ => { - emit(ParserError::with_reason(ParserErrorReason::InvalidCallDataIdentifier, span)); - Visibility::CallData(0) + fn read_token_internal(&mut self) -> SpannedToken { + loop { + let token = self.tokens.next(); + if let Some(token) = token { + match token { + Ok(token) => return token, + Err(lexer_error) => self.errors.push(lexer_error.into()), + } + } else { + return eof_spanned_token(); } } - }) -} - -pub fn expression() -> impl ExprParser { - recursive(|expr| { - expression_with_precedence( - Precedence::Lowest, - expr.clone(), - expression_no_constructors(expr.clone()), - statement(expr.clone(), expression_no_constructors(expr)), - false, - true, - ) - }) - .labelled(ParsingRuleLabel::Expression) -} - -fn expression_no_constructors<'a, P>(expr_parser: P) -> impl ExprParser + 'a -where - P: ExprParser + 'a, -{ - recursive(|expr_no_constructors| { - expression_with_precedence( - Precedence::Lowest, - expr_parser.clone(), - expr_no_constructors.clone(), - statement(expr_parser, expr_no_constructors), - false, - false, - ) - }) - .labelled(ParsingRuleLabel::Expression) -} - -fn return_statement<'a, P>(expr_parser: P) -> impl NoirParser + 'a -where - P: ExprParser + 'a, -{ - ignore_then_commit(keyword(Keyword::Return), expr_parser.or_not()) - .validate(|_, span, emit| { - emit(ParserError::with_reason(ParserErrorReason::EarlyReturn, span)); - StatementKind::Error - }) - .labelled(ParsingRuleLabel::Statement) -} + } -// An expression is a single term followed by 0 or more (OP subexpression)* -// where OP is an operator at the given precedence level and subexpression -// is an expression at the current precedence level plus one. -fn expression_with_precedence<'a, P, P2, S>( - precedence: Precedence, - expr_parser: P, - expr_no_constructors: P2, - statement: S, - // True if we should only parse the restricted subset of operators valid within type expressions - is_type_expression: bool, - // True if we should also parse constructors `Foo { field1: value1, ... }` as an expression. - // This is disabled when parsing the condition of an if statement due to a parsing conflict - // with `then` bodies containing only a single variable. - allow_constructors: bool, -) -> impl NoirParser + 'a -where - P: ExprParser + 'a, - P2: ExprParser + 'a, - S: NoirParser + 'a, -{ - if precedence == Precedence::Highest { - if is_type_expression { - type_expression_term(expr_parser).boxed().labelled(ParsingRuleLabel::Term) + fn eat_kind(&mut self, kind: TokenKind) -> Option { + if self.token.kind() == kind { + Some(self.bump()) } else { - term(expr_parser, expr_no_constructors, statement, allow_constructors) - .boxed() - .labelled(ParsingRuleLabel::Term) + None } - } else { - let next_precedence = - if is_type_expression { precedence.next_type_precedence() } else { precedence.next() }; - - let next_expr = expression_with_precedence( - next_precedence, - expr_parser, - expr_no_constructors, - statement, - is_type_expression, - allow_constructors, - ); - - next_expr - .clone() - .then(then_commit(operator_with_precedence(precedence), next_expr).repeated()) - .foldl(create_infix_expression) - .boxed() - .labelled(ParsingRuleLabel::Expression) } -} - -fn create_infix_expression(lhs: Expression, (operator, rhs): (BinaryOp, Expression)) -> Expression { - let span = lhs.span.merge(rhs.span); - let infix = Box::new(InfixExpression { lhs, operator, rhs }); - - Expression { span, kind: ExpressionKind::Infix(infix) } -} -fn operator_with_precedence(precedence: Precedence) -> impl NoirParser> { - right_shift_operator() - .or(any()) // Parse any single token, we're validating it as an operator next - .try_map(move |token, span| { - if Precedence::token_precedence(&token) == Some(precedence) { - Ok(token.try_into_binary_op(span).unwrap()) + fn eat_keyword(&mut self, keyword: Keyword) -> bool { + if let Token::Keyword(kw) = self.token.token() { + if *kw == keyword { + self.bump(); + true } else { - Err(ParserError::expected_label(ParsingRuleLabel::BinaryOperator, token, span)) + false } - }) -} - -fn term<'a, P, P2, S>( - expr_parser: P, - expr_no_constructors: P2, - statement: S, - allow_constructors: bool, -) -> impl NoirParser + 'a -where - P: ExprParser + 'a, - P2: ExprParser + 'a, - S: NoirParser + 'a, -{ - recursive(move |term_parser| { - choice(( - not(term_parser.clone()), - negation(term_parser.clone()), - mutable_reference(term_parser.clone()), - dereference(term_parser), - )) - .map_with_span(Expression::new) - // right-unary operators like a[0] or a.f bind more tightly than left-unary - // operators like - or !, so that !a[0] is parsed as !(a[0]). This is a bit - // awkward for casts so -a as i32 actually binds as -(a as i32). - .or(atom_or_right_unary( - expr_parser, - expr_no_constructors, - statement, - allow_constructors, - parse_type(), - )) - }) -} - -/// The equivalent of a 'term' for use in type expressions. Unlike regular terms, the grammar here -/// is restricted to no longer include right-unary expressions, unary not, and most atoms. -fn type_expression_term<'a, P>(expr_parser: P) -> impl NoirParser + 'a -where - P: ExprParser + 'a, -{ - recursive(move |term_parser| { - negation(term_parser).map_with_span(Expression::new).or(type_expression_atom(expr_parser)) - }) -} - -fn atom_or_right_unary<'a, P, P2, S>( - expr_parser: P, - expr_no_constructors: P2, - statement: S, - allow_constructors: bool, - type_parser: impl NoirParser + 'a, -) -> impl NoirParser + 'a -where - P: ExprParser + 'a, - P2: ExprParser + 'a, - S: NoirParser + 'a, -{ - enum UnaryRhs { - Call((Option, Vec)), - ArrayIndex(Expression), - Cast(UnresolvedType), - MemberAccess(UnaryRhsMemberAccess), - /// This is to allow `foo.` (no identifier afterwards) to be parsed as `foo` - /// and produce an error, rather than just erroring (for LSP). - JustADot, + } else { + false + } } - // `(arg1, ..., argN)` in `my_func(arg1, ..., argN)` - // Optionally accepts a leading `!` for macro calls. - let call_rhs = just(Token::Bang) - .or_not() - .then(parenthesized(expression_list(expr_parser.clone()))) - .map(UnaryRhs::Call); - - // `[expr]` in `arr[expr]` - let array_rhs = expr_parser - .clone() - .delimited_by(just(Token::LeftBracket), just(Token::RightBracket)) - .map(UnaryRhs::ArrayIndex); - - // `as Type` in `atom as Type` - let cast_rhs = keyword(Keyword::As) - .ignore_then(type_parser.clone()) - .map(UnaryRhs::Cast) - .labelled(ParsingRuleLabel::Cast); - - // A turbofish operator is optional in a method call to specify generic types - let turbofish = primitives::turbofish(type_parser); - - // `::!(arg1, .., argN)` with the turbofish and macro portions being optional. - let method_call_rhs = turbofish - .then(just(Token::Bang).or_not()) - .then(parenthesized(expression_list(expr_parser.clone()))) - .validate(|((turbofish, macro_call), args), span, emit| { - if turbofish.as_ref().map_or(false, |generics| !generics.named_args.is_empty()) { - let reason = ParserErrorReason::AssociatedTypesNotAllowedInMethodCalls; - emit(ParserError::with_reason(reason, span)); + fn eat_ident(&mut self) -> Option { + if let Some(token) = self.eat_kind(TokenKind::Ident) { + match token.into_token() { + Token::Ident(ident) => Some(Ident::new(ident, self.previous_token_span)), + _ => unreachable!(), } + } else { + None + } + } - let macro_call = macro_call.is_some(); - let turbofish = turbofish.map(|generics| generics.ordered_args); - UnaryRhsMethodCall { turbofish, macro_call, args } - }); - - // `.foo` or `.foo(args)` in `atom.foo` or `atom.foo(args)` - let member_rhs = just(Token::Dot) - .ignore_then(field_name()) - .then(method_call_rhs.or_not()) - .map(|(method_or_field, method_call)| { - UnaryRhs::MemberAccess(UnaryRhsMemberAccess { method_or_field, method_call }) - }) - .labelled(ParsingRuleLabel::FieldAccess); - - let just_a_dot = - just(Token::Dot).map(|_| UnaryRhs::JustADot).validate(|value, span, emit_error| { - emit_error(ParserError::with_reason( - ParserErrorReason::ExpectedIdentifierAfterDot, - span, - )); - value - }); - - let rhs = choice((call_rhs, array_rhs, cast_rhs, member_rhs, just_a_dot)); - - foldl_with_span( - atom(expr_parser, expr_no_constructors, statement, allow_constructors), - rhs, - |lhs, rhs, span| match rhs { - UnaryRhs::Call((is_macro, args)) => { - Expression::call(lhs, is_macro.is_some(), args, span) + fn eat_self(&mut self) -> bool { + if let Token::Ident(ident) = self.token.token() { + if ident == "self" { + self.bump(); + return true; } - UnaryRhs::ArrayIndex(index) => Expression::index(lhs, index, span), - UnaryRhs::Cast(r#type) => Expression::cast(lhs, r#type, span), - UnaryRhs::MemberAccess(field) => { - Expression::member_access_or_method_call(lhs, field, span) - } - UnaryRhs::JustADot => lhs, - }, - ) -} - -fn if_expr<'a, P, S>(expr_no_constructors: P, statement: S) -> impl NoirParser + 'a -where - P: ExprParser + 'a, - S: NoirParser + 'a, -{ - recursive(|if_parser| { - let if_block = block_expr(statement.clone()); - // The else block could also be an `else if` block, in which case we must recursively parse it. - let else_block = block_expr(statement).or(if_parser.map_with_span(|kind, span| { - // Wrap the inner `if` expression in a block expression. - // i.e. rewrite the sugared form `if cond1 {} else if cond2 {}` as `if cond1 {} else { if cond2 {} }`. - let if_expression = Expression::new(kind, span); - let desugared_else = BlockExpression { - statements: vec![Statement { - kind: StatementKind::Expression(if_expression), - span, - }], - }; - Expression::new(ExpressionKind::Block(desugared_else), span) - })); - - keyword(Keyword::If) - .ignore_then(expr_no_constructors) - .then(if_block.then(keyword(Keyword::Else).ignore_then(else_block).or_not()).or_not()) - .validate(|(condition, consequence_and_alternative), span, emit_error| { - if let Some((consequence, alternative)) = consequence_and_alternative { - ExpressionKind::If(Box::new(IfExpression { - condition, - consequence, - alternative, - })) - } else { - // We allow `if cond` without a block mainly for LSP, so that it parses right - // and autocompletion works there. - emit_error(ParserError::with_reason( - ParserErrorReason::ExpectedLeftBraceAfterIfCondition, - span, - )); - - let span_end = condition.span.end(); - ExpressionKind::If(Box::new(IfExpression { - condition, - consequence: Expression::new( - ExpressionKind::Error, - Span::from(span_end..span_end), - ), - alternative: None, - })) - } - }) - }) -} - -fn if_statement<'a, P, S>( - expr_no_constructors: P, - statement: S, -) -> impl NoirParser + 'a -where - P: ExprParser + 'a, - S: NoirParser + 'a, -{ - if_expr(expr_no_constructors, statement).map_with_span(|expression_kind, span| { - StatementKind::Expression(Expression::new(expression_kind, span)) - }) -} - -fn block_statement<'a, S>(statement: S) -> impl NoirParser + 'a -where - S: NoirParser + 'a, -{ - block(statement).map_with_span(|block, span| { - StatementKind::Expression(Expression::new(ExpressionKind::Block(block), span)) - }) -} - -fn for_loop<'a, P, S>(expr_no_constructors: P, statement: S) -> impl NoirParser + 'a -where - P: ExprParser + 'a, - S: NoirParser + 'a, -{ - keyword(Keyword::For) - .ignore_then(ident()) - .then_ignore(keyword(Keyword::In)) - .then(for_range(expr_no_constructors)) - .then(block_expr(statement)) - .map_with_span(|((identifier, range), block), span| { - StatementKind::For(ForLoopStatement { identifier, range, block, span }) - }) -} - -/// The 'range' of a for loop. Either an actual range `start .. end` or an array expression. -fn for_range

(expr_no_constructors: P) -> impl NoirParser -where - P: ExprParser, -{ - expr_no_constructors - .clone() - .then_ignore(just(Token::DoubleDot)) - .then(expr_no_constructors.clone()) - .map(|(start, end)| ForRange::Range(start, end)) - .or(expr_no_constructors.map(ForRange::Array)) -} - -fn array_expr

(expr_parser: P) -> impl NoirParser -where - P: ExprParser, -{ - standard_array(expr_parser.clone()).or(array_sugar(expr_parser)) -} - -/// [a, b, c, ...] -fn standard_array

(expr_parser: P) -> impl NoirParser -where - P: ExprParser, -{ - expression_list(expr_parser) - .delimited_by(just(Token::LeftBracket), just(Token::RightBracket)) - .validate(|elements, _span, _emit| ExpressionKind::array(elements)) -} - -/// [a; N] -fn array_sugar

(expr_parser: P) -> impl NoirParser -where - P: ExprParser, -{ - expr_parser - .clone() - .then(just(Token::Semicolon).ignore_then(expr_parser)) - .delimited_by(just(Token::LeftBracket), just(Token::RightBracket)) - .map(|(lhs, count)| ExpressionKind::repeated_array(lhs, count)) -} - -fn slice_expr

(expr_parser: P) -> impl NoirParser -where - P: ExprParser, -{ - just(Token::Ampersand) - .ignore_then(standard_slice(expr_parser.clone()).or(slice_sugar(expr_parser))) -} - -/// &[a, b, c, ...] -fn standard_slice

(expr_parser: P) -> impl NoirParser -where - P: ExprParser, -{ - expression_list(expr_parser) - .delimited_by(just(Token::LeftBracket), just(Token::RightBracket)) - .validate(|elements, _span, _emit| ExpressionKind::slice(elements)) -} - -/// &[a; N] -fn slice_sugar

(expr_parser: P) -> impl NoirParser -where - P: ExprParser, -{ - expr_parser - .clone() - .then(just(Token::Semicolon).ignore_then(expr_parser)) - .delimited_by(just(Token::LeftBracket), just(Token::RightBracket)) - .map(|(lhs, count)| ExpressionKind::repeated_slice(lhs, count)) -} + } -fn expression_list

(expr_parser: P) -> impl NoirParser> -where - P: ExprParser, -{ - expr_parser.separated_by(just(Token::Comma)).allow_trailing() -} + false + } -/// Atoms are parameterized on whether constructor expressions are allowed or not. -/// Certain constructs like `if` and `for` disallow constructor expressions when a -/// block may be expected. -fn atom<'a, P, P2, S>( - expr_parser: P, - expr_no_constructors: P2, - statement: S, - allow_constructors: bool, -) -> impl NoirParser + 'a -where - P: ExprParser + 'a, - P2: ExprParser + 'a, - S: NoirParser + 'a, -{ - choice(( - if_expr(expr_no_constructors, statement.clone()), - slice_expr(expr_parser.clone()), - array_expr(expr_parser.clone()), - if allow_constructors { - constructor(expr_parser.clone()).boxed() + fn eat_int_type(&mut self) -> Option { + let is_int_type = matches!(self.token.token(), Token::IntType(..)); + if is_int_type { + let token = self.bump(); + match token.into_token() { + Token::IntType(int_type) => Some(int_type), + _ => unreachable!(), + } } else { - nothing().boxed() - }, - lambdas::lambda(expr_parser.clone()), - block(statement.clone()).map(ExpressionKind::Block), - comptime_expr(statement.clone()), - unsafe_expr(statement), - quote(), - unquote(expr_parser.clone()), - variable(), - literal(), - as_trait_path(parse_type()).map(ExpressionKind::AsTraitPath), - type_path(parse_type()), - macro_quote_marker(), - interned_expr(), - interned_statement_expr(), - )) - .map_with_span(Expression::new) - .or(parenthesized(expr_parser.clone()).map_with_span(|sub_expr, span| { - Expression::new(ExpressionKind::Parenthesized(sub_expr.into()), span) - })) - .or(tuple(expr_parser)) - .labelled(ParsingRuleLabel::Atom) -} - -/// Atoms within type expressions are limited to only variables, literals, and parenthesized -/// type expressions. -fn type_expression_atom<'a, P>(expr_parser: P) -> impl NoirParser + 'a -where - P: ExprParser + 'a, -{ - primitives::variable_no_turbofish() - .or(literal()) - .map_with_span(Expression::new) - .or(parenthesized(expr_parser)) - .labelled(ParsingRuleLabel::Atom) -} - -fn quote() -> impl NoirParser { - token_kind(TokenKind::Quote).map(|token| { - ExpressionKind::Quote(match token { - Token::Quote(tokens) => tokens, - _ => unreachable!("token_kind(Quote) should guarantee parsing only a quote token"), - }) - }) -} - -/// unquote: '$' variable -/// | '$' '(' expression ')' -fn unquote<'a, P>(expr_parser: P) -> impl NoirParser + 'a -where - P: ExprParser + 'a, -{ - let unquote = variable().map_with_span(Expression::new).or(parenthesized(expr_parser)); - just(Token::DollarSign).ignore_then(unquote).map(|expr| ExpressionKind::Unquote(Box::new(expr))) -} + None + } + } -fn tuple

(expr_parser: P) -> impl NoirParser -where - P: ExprParser, -{ - parenthesized(expression_list(expr_parser)).map_with_span(|elements, span| { - let kind = if elements.is_empty() { - ExpressionKind::Literal(Literal::Unit) + fn eat_int(&mut self) -> Option { + if matches!(self.token.token(), Token::Int(..)) { + let token = self.bump(); + match token.into_token() { + Token::Int(int) => Some(int), + _ => unreachable!(), + } } else { - ExpressionKind::Tuple(elements) - }; - Expression::new(kind, span) - }) -} - -fn field_name() -> impl NoirParser { - ident().or(token_kind(TokenKind::Literal).validate(|token, span, emit| match token { - Token::Int(_) => Ident::from(Spanned::from(span, token.to_string())), - other => { - emit(ParserError::with_reason(ParserErrorReason::ExpectedFieldName(other), span)); - Ident::error(span) + None } - })) -} - -fn constructor(expr_parser: impl ExprParser) -> impl NoirParser { - let args = constructor_field(expr_parser) - .separated_by(just(Token::Comma)) - .allow_trailing() - .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)); - - let path = path(super::parse_type()).map(UnresolvedType::from_path); - let interned_unresolved_type = interned_unresolved_type(); - let typ = choice((path, interned_unresolved_type)); - - typ.then(args).map(ExpressionKind::constructor) -} - -fn constructor_field

(expr_parser: P) -> impl NoirParser<(Ident, Expression)> -where - P: ExprParser, -{ - let long_form = ident().then_ignore(just(Token::Colon)).then(expr_parser); - let short_form = ident().map(|ident| (ident.clone(), ident.into())); - long_form.or(short_form) -} - -#[cfg(test)] -mod test { - use super::test_helpers::*; - use super::*; - use crate::ast::ArrayLiteral; - - #[test] - fn parse_infix() { - let valid = vec!["x + 6", "x - k", "x + (x + a)", " x * (x + a) + (x - 4)"]; - parse_all(expression(), valid); - parse_all_failing(expression(), vec!["y ! x"]); } - #[test] - fn parse_function_call() { - let valid = vec![ - "std::hash ()", - " std::hash(x,y,a+b)", - "crate::foo (x)", - "hash (x,)", - "(foo + bar)()", - "(bar)()()()", - ]; - parse_all(expression(), valid); + fn eat_bool(&mut self) -> Option { + if matches!(self.token.token(), Token::Bool(..)) { + let token = self.bump(); + match token.into_token() { + Token::Bool(bool) => Some(bool), + _ => unreachable!(), + } + } else { + None + } } - #[test] - fn parse_cast() { - let expression_nc = expression_no_constructors(expression()); - parse_all( - atom_or_right_unary( - expression(), - expression_no_constructors(expression()), - fresh_statement(), - true, - parse_type(), - ), - vec!["x as u8", "x as u16", "0 as Field", "(x + 3) as [Field; 8]"], - ); - parse_all_failing( - atom_or_right_unary(expression(), expression_nc, fresh_statement(), true, parse_type()), - vec!["x as pub u8"], - ); + fn eat_str(&mut self) -> Option { + if matches!(self.token.token(), Token::Str(..)) { + let token = self.bump(); + match token.into_token() { + Token::Str(string) => Some(string), + _ => unreachable!(), + } + } else { + None + } } - #[test] - fn parse_array_index() { - let valid = vec![ - "x[9]", - "y[x+a]", - " foo [foo+5]", - "baz[bar]", - "foo.bar[3] as Field .baz as u32 [7]", - ]; - parse_all( - atom_or_right_unary( - expression(), - expression_no_constructors(expression()), - fresh_statement(), - true, - parse_type(), - ), - valid, - ); + fn eat_raw_str(&mut self) -> Option<(String, u8)> { + if matches!(self.token.token(), Token::RawStr(..)) { + let token = self.bump(); + match token.into_token() { + Token::RawStr(string, n) => Some((string, n)), + _ => unreachable!(), + } + } else { + None + } } - fn expr_to_array(expr: ExpressionKind) -> ArrayLiteral { - let lit = match expr { - ExpressionKind::Literal(literal) => literal, - _ => unreachable!("expected a literal"), - }; - - match lit { - Literal::Array(arr) => arr, - _ => unreachable!("expected an array"), + fn eat_fmt_str(&mut self) -> Option { + if matches!(self.token.token(), Token::FmtStr(..)) { + let token = self.bump(); + match token.into_token() { + Token::FmtStr(string) => Some(string), + _ => unreachable!(), + } + } else { + None } } - #[test] - fn parse_array() { - let valid = vec![ - "[0, 1, 2,3, 4]", - "[0,1,2,3,4,]", // Trailing commas are valid syntax - "[0;5]", - ]; - - for expr in parse_all(array_expr(expression()), valid) { - match expr_to_array(expr) { - ArrayLiteral::Standard(elements) => assert_eq!(elements.len(), 5), - ArrayLiteral::Repeated { length, .. } => { - assert_eq!(length.kind, ExpressionKind::integer(5i128.into())); - } + fn eat_quote(&mut self) -> Option { + if matches!(self.token.token(), Token::Quote(..)) { + let token = self.bump(); + match token.into_token() { + Token::Quote(tokens) => Some(tokens), + _ => unreachable!(), } + } else { + None } - - parse_all_failing( - array_expr(expression()), - vec!["0,1,2,3,4]", "[[0,1,2,3,4]", "[0,1,2,,]", "[0,1,2,3,4"], - ); } - #[test] - fn parse_array_sugar() { - let valid = vec!["[0;7]", "[(1, 2); 4]", "[0;Four]", "[2;1+3-a]"]; - parse_all(array_expr(expression()), valid); - - let invalid = vec!["[0;;4]", "[1, 2; 3]"]; - parse_all_failing(array_expr(expression()), invalid); + fn eat_comma(&mut self) -> bool { + self.eat(Token::Comma) } - fn expr_to_slice(expr: ExpressionKind) -> ArrayLiteral { - let lit = match expr { - ExpressionKind::Literal(literal) => literal, - _ => unreachable!("expected a literal"), - }; - - match lit { - Literal::Slice(arr) => arr, - _ => unreachable!("expected a slice: {:?}", lit), + fn eat_commas(&mut self) -> bool { + if self.eat_comma() { + while self.eat_comma() { + self.push_error(ParserErrorReason::UnexpectedComma, self.previous_token_span); + } + true + } else { + false } } - #[test] - fn parse_slice() { - let valid = vec![ - "&[0, 1, 2,3, 4]", - "&[0,1,2,3,4,]", // Trailing commas are valid syntax - "&[0;5]", - ]; - - for expr in parse_all(slice_expr(expression()), valid) { - match expr_to_slice(expr) { - ArrayLiteral::Standard(elements) => assert_eq!(elements.len(), 5), - ArrayLiteral::Repeated { length, .. } => { - assert_eq!(length.kind, ExpressionKind::integer(5i128.into())); - } + fn eat_semicolon(&mut self) -> bool { + self.eat(Token::Semicolon) + } + + fn eat_semicolons(&mut self) -> bool { + if self.eat_semicolon() { + while self.eat_semicolon() { + self.push_error(ParserErrorReason::UnexpectedSemicolon, self.previous_token_span); } + true + } else { + false } - - parse_all_failing( - slice_expr(expression()), - vec!["0,1,2,3,4]", "&[[0,1,2,3,4]", "&[0,1,2,,]", "&[0,1,2,3,4"], - ); } - #[test] - fn parse_slice_sugar() { - let valid = vec!["&[0;7]", "&[(1, 2); 4]", "&[0;Four]", "&[2;1+3-a]"]; - parse_all(slice_expr(expression()), valid); - - let invalid = vec!["&[0;;4]", "&[1, 2; 3]"]; - parse_all_failing(slice_expr(expression()), invalid); + fn eat_colon(&mut self) -> bool { + self.eat(Token::Colon) } - #[test] - fn parse_block() { - parse_with(block(fresh_statement()), "{ [0,1,2,3,4] }").unwrap(); - - parse_all_failing( - block(fresh_statement()), - vec![ - "[0,1,2,3,4] }", - "{ [0,1,2,3,4]", - "{ [0,1,2,,] }", // Contents of the block must still be a valid expression - "{ [0,1,2,3 }", - "{ 0,1,2,3] }", - "[[0,1,2,3,4]}", - ], - ); + fn eat_double_colon(&mut self) -> bool { + self.eat(Token::DoubleColon) } - #[test] - fn parse_let() { - // Why is it valid to specify a let declaration as having type u8? - // - // Let statements are not type checked here, so the parser will accept as - // long as it is a type. Other statements such as Public are type checked - // Because for now, they can only have one type - parse_all( - declaration(expression()), - vec!["let _ = 42", "let x = y", "let x : u8 = y", "let x: u16 = y"], - ); + fn eat_left_paren(&mut self) -> bool { + self.eat(Token::LeftParen) } - #[test] - fn parse_invalid_pub() { - // pub cannot be used to declare a statement - parse_all_failing(fresh_statement(), vec!["pub x = y", "pub x : pub Field = y"]); + fn eat_right_paren(&mut self) -> bool { + self.eat(Token::RightParen) } - #[test] - fn parse_for_loop() { - parse_all( - for_loop(expression_no_constructors(expression()), fresh_statement()), - vec!["for i in x+y..z {}", "for i in 0..100 { foo; bar }"], - ); - - parse_all_failing( - for_loop(expression_no_constructors(expression()), fresh_statement()), - vec![ - "for 1 in x+y..z {}", // Cannot have a literal as the loop identifier - "for i in 0...100 {}", // Only '..' is supported, there are no inclusive ranges yet - "for i in 0..=100 {}", // Only '..' is supported, there are no inclusive ranges yet - ], - ); + fn eat_left_brace(&mut self) -> bool { + self.eat(Token::LeftBrace) } - #[test] - fn parse_parenthesized_expression() { - parse_all( - atom(expression(), expression_no_constructors(expression()), fresh_statement(), true), - vec!["(0)", "(x+a)", "({(({{({(nested)})}}))})"], - ); - parse_all_failing( - atom(expression(), expression_no_constructors(expression()), fresh_statement(), true), - vec!["(x+a", "((x+a)", "(,)"], - ); + fn eat_left_bracket(&mut self) -> bool { + self.eat(Token::LeftBracket) } - #[test] - fn parse_tuple() { - parse_all(tuple(expression()), vec!["()", "(x,)", "(a,b+2)", "(a,(b,c,),d,)"]); + fn eat_right_bracket(&mut self) -> bool { + self.eat(Token::RightBracket) } - #[test] - fn parse_if_expr() { - parse_all( - if_expr(expression_no_constructors(expression()), fresh_statement()), - vec!["if x + a { } else { }", "if x {}", "if x {} else if y {} else {}"], - ); - - parse_all_failing( - if_expr(expression_no_constructors(expression()), fresh_statement()), - vec!["if (x / a) + 1 {} else", "if foo then 1 else 2", "if true { 1 }else 3"], - ); + fn eat_less(&mut self) -> bool { + self.eat(Token::Less) } - #[test] - fn parse_if_without_block() { - let src = "if foo"; - let parser = if_expr(expression_no_constructors(expression()), fresh_statement()); - let (expression_kind, errors) = parse_recover(parser, src); - - let expression_kind = expression_kind.unwrap(); - let ExpressionKind::If(if_expression) = expression_kind else { - panic!("Expected an if expression, got {:?}", expression_kind); - }; - - assert_eq!(if_expression.consequence.kind, ExpressionKind::Error); - assert_eq!(if_expression.alternative, None); - - assert_eq!(errors.len(), 1); - assert_eq!(errors[0].message, "expected { after if condition"); + fn eat_assign(&mut self) -> bool { + self.eat(Token::Assign) } - #[test] - fn parse_module_declaration() { - parse_with(module_declaration(), "mod foo").unwrap(); - parse_with(module_declaration(), "#[attr] mod foo").unwrap(); - parse_with(module_declaration(), "mod 1").unwrap_err(); + fn eat_dot(&mut self) -> bool { + self.eat(Token::Dot) } - #[test] - fn parse_submodule_declaration() { - parse_with(submodule(module()), "mod foo {}").unwrap(); - parse_with(submodule(module()), "#[attr] mod foo {}").unwrap(); + fn eat_pipe(&mut self) -> bool { + self.eat(Token::Pipe) } - #[test] - fn parse_contract() { - parse_with(contract(module()), "contract foo {}").unwrap(); - parse_with(contract(module()), "#[attr] contract foo {}").unwrap(); + fn eat(&mut self, token: Token) -> bool { + if self.token.token() == &token { + self.bump(); + true + } else { + false + } } - #[test] - fn parse_use() { - let valid_use_statements = [ - "use std::hash", - "use std", - "use foo::bar as hello", - "use bar as bar", - "use foo::{}", - "use foo::{bar,}", - "use foo::{bar, hello}", - "use foo::{bar as bar2, hello}", - "use foo::{bar as bar2, hello::{foo}, nested::{foo, bar}}", - "use std::{println, bar::baz}", - ]; - - let invalid_use_statements = [ - "use std as ;", - "use foobar as as;", - "use hello:: as foo;", - "use foo bar::baz", - "use foo bar::{baz}", - "use foo::{,}", - ]; - - let use_statements = valid_use_statements - .into_iter() - .map(|valid_str| (valid_str, true)) - .chain(invalid_use_statements.into_iter().map(|invalid_str| (invalid_str, false))); - - for (use_statement_str, expect_valid) in use_statements { - let mut use_statement_str = use_statement_str.to_string(); - if expect_valid { - let (result_opt, _diagnostics) = - parse_recover(&use_statement(), &use_statement_str); - use_statement_str.push(';'); - match result_opt.unwrap() { - TopLevelStatementKind::Import(expected_use_statement, _visibility) => { - Some(expected_use_statement) - } - _ => unreachable!(), - } - } else { - let result = parse_with(&use_statement(), &use_statement_str); - assert!(result.is_err()); - None - }; + fn eat_keyword_or_error(&mut self, keyword: Keyword) { + if !self.eat_keyword(keyword) { + self.expected_token(Token::Keyword(keyword)); } } - #[test] - fn parse_type_aliases() { - let cases = vec!["type foo = u8", "type bar = String", "type baz = Vec"]; - parse_all(type_alias_definition(), cases); - - let failing = vec!["type = u8", "type foo", "type foo = 1"]; - parse_all_failing(type_alias_definition(), failing); + fn eat_or_error(&mut self, token: Token) { + if !self.eat(token.clone()) { + self.expected_token(token); + } } - #[test] - fn parse_member_access() { - let cases = vec!["a.b", "a + b.c", "foo.bar as u32"]; - parse_all(expression(), cases); + fn at(&self, token: Token) -> bool { + self.token.token() == &token } - #[test] - fn parse_constructor() { - let cases = vec![ - "Baz", - "Bar { ident: 32 }", - "Baz { other: 2 + 42, ident: foo() + 1 }", - "Baz { other, ident: foo() + 1, foo }", - ]; - - parse_all(expression(), cases); - parse_with(expression(), "Foo { a + b }").unwrap_err(); + fn at_keyword(&self, keyword: Keyword) -> bool { + self.at(Token::Keyword(keyword)) } - // Semicolons are: - // - Required after non-expression statements - // - Optional after for, if, block expressions - // - Optional after an expression as the last statement of a block - // - Required after an expression as the non-final statement of a block - #[test] - fn parse_semicolons() { - let cases = vec![ - "{ if true {} if false {} foo }", - "{ if true {}; if false {} foo }", - "{ for x in 0..1 {} if false {} foo; }", - "{ let x = 2; }", - "{ expr1; expr2 }", - "{ expr1; expr2; }", - ]; - parse_all(block(fresh_statement()), cases); - - let failing = vec![ - // We disallow multiple semicolons after a statement unlike rust where it is a warning - "{ test;; foo }", - "{ for x in 0..1 {} foo if false {} }", - "{ let x = 2 }", - "{ expr1 expr2 }", - ]; - parse_all_failing(block(fresh_statement()), failing); + fn next_is(&self, token: Token) -> bool { + self.next_token.token() == &token } - #[test] - fn statement_recovery() { - let cases = vec![ - Case { source: "let a = 4 + 3", expect: "let a = (4 + 3)", errors: 0 }, - Case { source: "let a: = 4 + 3", expect: "let a: error = (4 + 3)", errors: 1 }, - Case { source: "let = 4 + 3", expect: "let $error = (4 + 3)", errors: 1 }, - Case { source: "let = ", expect: "let $error = Error", errors: 2 }, - Case { source: "let", expect: "let $error = Error", errors: 3 }, - Case { source: "foo = one two three", expect: "foo = one", errors: 1 }, - Case { source: "constrain", expect: "constrain Error", errors: 2 }, - Case { source: "assert", expect: "assert()", errors: 1 }, - Case { source: "constrain x ==", expect: "constrain (x == Error)", errors: 2 }, - Case { source: "assert(x ==)", expect: "assert((x == Error))", errors: 1 }, - Case { source: "assert(x == x, x)", expect: "assert((x == x), x)", errors: 0 }, - Case { source: "assert_eq(x,)", expect: "assert_eq(x)", errors: 0 }, - Case { source: "assert_eq(x, x, x, x)", expect: "assert_eq(x, x, x, x)", errors: 0 }, - Case { source: "assert_eq(x, x, x)", expect: "assert_eq(x, x, x)", errors: 0 }, - ]; - - check_cases_with_errors(&cases[..], fresh_statement()); + fn at_eof(&self) -> bool { + self.token.token() == &Token::EOF } - #[test] - fn return_validation() { - let cases = [ - Case { - source: "{ return 42; }", - expect: concat!("{\n", " Error\n", "}",), - errors: 1, - }, - Case { - source: "{ return 1; return 2; }", - expect: concat!("{\n", " Error\n", " Error\n", "}"), - errors: 2, - }, - Case { - source: "{ return 123; let foo = 4 + 3; }", - expect: concat!("{\n", " Error\n", " let foo = (4 + 3)\n", "}"), - errors: 1, - }, - Case { - source: "{ return 1 + 2 }", - expect: concat!("{\n", " Error\n", "}",), - errors: 2, - }, - Case { source: "{ return; }", expect: concat!("{\n", " Error\n", "}",), errors: 1 }, - ]; - - check_cases_with_errors(&cases[..], block(fresh_statement())); + fn span_since(&self, start_span: Span) -> Span { + if self.current_token_span == start_span { + start_span + } else { + let end_span = self.previous_token_span; + Span::from(start_span.start()..end_span.end()) + } } - #[test] - fn expr_no_constructors() { - let cases = [ - Case { - source: "{ if structure { a: 1 } {} }", - expect: concat!( - "{\n", - " if structure {\n", - " Error\n", - " }\n", - " {\n", - " }\n", - "}", - ), - errors: 1, - }, - Case { - source: "{ if ( structure { a: 1 } ) {} }", - expect: concat!("{\n", " if ((structure { a: 1 })) {\n", " }\n", "}",), - errors: 0, - }, - Case { - source: "{ if ( structure {} ) {} }", - expect: concat!("{\n", " if ((structure { })) {\n", " }\n", "}"), - errors: 0, - }, - Case { - source: "{ if (a { x: 1 }, b { y: 2 }) {} }", - expect: concat!("{\n", " if ((a { x: 1 }), (b { y: 2 })) {\n", " }\n", "}",), - errors: 0, - }, - Case { - source: "{ if ({ let foo = bar { baz: 42 }; foo == bar { baz: 42 }}) {} }", - expect: concat!( - "{\n", - " if ({\n", - " let foo = (bar { baz: 42 })\n", - " (foo == (bar { baz: 42 }))\n", - " }) {\n", - " }\n", - "}", - ), - errors: 0, - }, - ]; - - check_cases_with_errors(&cases[..], block(fresh_statement())); + fn span_at_previous_token_end(&self) -> Span { + Span::from(self.previous_token_span.end()..self.previous_token_span.end()) } - #[test] - fn test_quote() { - let cases = vec![ - "quote {}", - "quote { a.b }", - "quote { ) ( }", // invalid syntax is fine in a quote - "quote { { } }", // Nested `{` and `}` shouldn't close the quote as long as they are matched. - "quote { 1 { 2 { 3 { 4 { 5 } 4 4 } 3 3 } 2 2 } 1 1 }", - ]; - parse_all(quote(), cases); - - let failing = vec!["quote {}}", "quote a", "quote { { { } } } }"]; - parse_all_failing(quote(), failing); + fn expected_identifier(&mut self) { + self.expected_label(ParsingRuleLabel::Identifier); } - #[test] - fn test_parses_block_statement_not_infix_expression() { - let src = r#" - { - {} - -1 - }"#; - let (block_expr, _) = parse_recover(block(fresh_statement()), src); - let block_expr = block_expr.expect("Failed to parse module"); - assert_eq!(block_expr.statements.len(), 2); + fn expected_token(&mut self, token: Token) { + self.errors.push(ParserError::expected_token( + token, + self.token.token().clone(), + self.current_token_span, + )); } - #[test] - fn test_parses_if_statement_not_infix_expression() { - let src = r#" - { - if 1 { 2 } else { 3 } - -1 - }"#; - let (block_expr, _) = parse_recover(block(fresh_statement()), src); - let block_expr = block_expr.expect("Failed to parse module"); - assert_eq!(block_expr.statements.len(), 2); + fn expected_one_of_tokens(&mut self, tokens: &[Token]) { + self.errors.push(ParserError::expected_one_of_tokens( + tokens, + self.token.token().clone(), + self.current_token_span, + )); } - #[test] - fn test_parses_if_statement_followed_by_tuple_as_two_separate_statements() { - // Regression for #1310: this should not be parsed as a function call - let src = r#" - { - if 1 { 2 } else { 3 } (1, 2) - }"#; - let (block_expr, _) = parse_recover(block(fresh_statement()), src); - let block_expr = block_expr.expect("Failed to parse module"); - assert_eq!(block_expr.statements.len(), 2); + fn expected_label(&mut self, label: ParsingRuleLabel) { + self.errors.push(ParserError::expected_label( + label, + self.token.token().clone(), + self.current_token_span, + )); } - #[test] - fn test_parses_member_access_without_member_name() { - let src = "{ foo. }"; + fn expected_token_separating_items(&mut self, token: Token, items: &'static str, span: Span) { + self.push_error(ParserErrorReason::ExpectedTokenSeparatingTwoItems { token, items }, span); + } - let (Some(block_expression), errors) = parse_recover(block(fresh_statement()), src) else { - panic!("Expected to be able to parse a block expression"); - }; + fn modifiers_not_followed_by_an_item(&mut self, modifiers: Modifiers) { + self.visibility_not_followed_by_an_item(modifiers); + self.unconstrained_not_followed_by_an_item(modifiers); + self.comptime_not_followed_by_an_item(modifiers); + } - assert_eq!(errors.len(), 1); - assert_eq!(errors[0].message, "expected an identifier after ."); + fn visibility_not_followed_by_an_item(&mut self, modifiers: Modifiers) { + if modifiers.visibility != ItemVisibility::Private { + self.push_error( + ParserErrorReason::VisibilityNotFollowedByAnItem { + visibility: modifiers.visibility, + }, + modifiers.visibility_span, + ); + } + } - let statement = &block_expression.statements[0]; - let StatementKind::Expression(expr) = &statement.kind else { - panic!("Expected an expression statement"); - }; + fn unconstrained_not_followed_by_an_item(&mut self, modifiers: Modifiers) { + if let Some(span) = modifiers.unconstrained { + self.push_error(ParserErrorReason::UnconstrainedNotFollowedByAnItem, span); + } + } - let ExpressionKind::Variable(var) = &expr.kind else { - panic!("Expected a variable expression"); - }; + fn comptime_not_followed_by_an_item(&mut self, modifiers: Modifiers) { + if let Some(span) = modifiers.comptime { + self.push_error(ParserErrorReason::ComptimeNotFollowedByAnItem, span); + } + } - assert_eq!(var.to_string(), "foo"); + fn comptime_mutable_and_unconstrained_not_applicable(&mut self, modifiers: Modifiers) { + self.mutable_not_applicable(modifiers); + self.comptime_not_applicable(modifiers); + self.unconstrained_not_applicable(modifiers); } - #[test] - fn parse_recover_impl_without_body() { - let src = "impl Foo"; + fn mutable_not_applicable(&mut self, modifiers: Modifiers) { + if let Some(span) = modifiers.mutable { + self.push_error(ParserErrorReason::MutableNotApplicable, span); + } + } - let (top_level_statement, errors) = parse_recover(implementation(), src); - assert_eq!(errors.len(), 1); - assert_eq!(errors[0].message, "expected <, where or { after impl type"); + fn comptime_not_applicable(&mut self, modifiers: Modifiers) { + if let Some(span) = modifiers.comptime { + self.push_error(ParserErrorReason::ComptimeNotApplicable, span); + } + } - let top_level_statement = top_level_statement.unwrap(); - let TopLevelStatementKind::Impl(impl_) = top_level_statement else { - panic!("Expected to parse an impl"); - }; + fn unconstrained_not_applicable(&mut self, modifiers: Modifiers) { + if let Some(span) = modifiers.unconstrained { + self.push_error(ParserErrorReason::UnconstrainedNotApplicable, span); + } + } - assert_eq!(impl_.object_type.to_string(), "Foo"); - assert!(impl_.methods.is_empty()); + fn push_error(&mut self, reason: ParserErrorReason, span: Span) { + self.errors.push(ParserError::with_reason(reason, span)); } } + +fn eof_spanned_token() -> SpannedToken { + SpannedToken::new(Token::EOF, Default::default()) +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/arguments.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/arguments.rs new file mode 100644 index 00000000000..380f42809a6 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/arguments.rs @@ -0,0 +1,40 @@ +use crate::{ast::Expression, token::Token}; + +use super::{parse_many::separated_by_comma_until_right_paren, Parser}; + +pub(crate) struct CallArguments { + pub(crate) arguments: Vec, + pub(crate) is_macro_call: bool, +} + +impl<'a> Parser<'a> { + /// Arguments = '(' ArgumentsList? ')' + /// + /// ArgumentsList = Expression ( ',' Expression )? ','? + pub(crate) fn parse_arguments(&mut self) -> Option> { + if !self.eat_left_paren() { + return None; + } + + let arguments = self.parse_many( + "arguments", + separated_by_comma_until_right_paren(), + Self::parse_expression_in_list, + ); + + Some(arguments) + } + + /// CallArguments = '!'? Arguments + pub(super) fn parse_call_arguments(&mut self) -> Option { + let is_macro_call = self.at(Token::Bang) && self.next_is(Token::LeftParen); + + if is_macro_call { + // Given that we expected '!' '(', it's safe to skip the '!' because the next + // `self.parse_arguments()` will always return `Some`. + self.bump(); + } + + self.parse_arguments().map(|arguments| CallArguments { arguments, is_macro_call }) + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/assertion.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/assertion.rs deleted file mode 100644 index 9eb429ef295..00000000000 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/assertion.rs +++ /dev/null @@ -1,196 +0,0 @@ -use crate::ast::StatementKind; -use crate::parser::{ignore_then_commit, then_commit, ParserError, ParserErrorReason}; -use crate::parser::{labels::ParsingRuleLabel, parenthesized, ExprParser, NoirParser}; - -use crate::ast::{ConstrainKind, ConstrainStatement}; -use crate::token::{Keyword, Token}; - -use chumsky::prelude::*; - -use super::keyword; - -pub(super) fn constrain<'a, P>(expr_parser: P) -> impl NoirParser + 'a -where - P: ExprParser + 'a, -{ - ignore_then_commit( - keyword(Keyword::Constrain).labelled(ParsingRuleLabel::Statement), - expr_parser, - ) - .map_with_span(|expr, span| { - StatementKind::Constrain(ConstrainStatement { - kind: ConstrainKind::Constrain, - arguments: vec![expr], - span, - }) - }) - .validate(|expr, span, emit| { - emit(ParserError::with_reason(ParserErrorReason::ConstrainDeprecated, span)); - expr - }) -} - -pub(super) fn assertion<'a, P>(expr_parser: P) -> impl NoirParser + 'a -where - P: ExprParser + 'a, -{ - let keyword = choice(( - keyword(Keyword::Assert).map(|_| ConstrainKind::Assert), - keyword(Keyword::AssertEq).map(|_| ConstrainKind::AssertEq), - )); - - let argument_parser = expr_parser.separated_by(just(Token::Comma)).allow_trailing(); - - then_commit(keyword, parenthesized(argument_parser)) - .labelled(ParsingRuleLabel::Statement) - .map_with_span(|(kind, arguments), span| { - StatementKind::Constrain(ConstrainStatement { arguments, kind, span }) - }) -} - -#[cfg(test)] -mod test { - use super::*; - use crate::{ - ast::{BinaryOpKind, ExpressionKind, Literal}, - parser::parser::{ - expression, - test_helpers::{parse_all, parse_all_failing, parse_with}, - }, - }; - - /// Deprecated constrain usage test - #[test] - fn parse_constrain() { - let errors = parse_with(constrain(expression()), "constrain x == y").unwrap_err(); - assert_eq!(errors.len(), 1); - assert!(format!("{}", errors.first().unwrap()).contains("deprecated")); - - // Currently we disallow constrain statements where the outer infix operator - // produces a value. This would require an implicit `==` which - // may not be intuitive to the user. - // - // If this is deemed useful, one would either apply a transformation - // or interpret it with an `==` in the evaluator - let disallowed_operators = vec![ - BinaryOpKind::And, - BinaryOpKind::Subtract, - BinaryOpKind::Divide, - BinaryOpKind::Multiply, - BinaryOpKind::Or, - ]; - - for operator in disallowed_operators { - let src = format!("constrain x {} y;", operator.as_string()); - let errors = parse_with(constrain(expression()), &src).unwrap_err(); - assert_eq!(errors.len(), 2); - assert!(format!("{}", errors.first().unwrap()).contains("deprecated")); - } - - // These are general cases which should always work. - // - // The first case is the most noteworthy. It contains two `==` - // The first (inner) `==` is a predicate which returns 0/1 - // The outer layer is an infix `==` which is - // associated with the Constrain statement - let errors = parse_all_failing( - constrain(expression()), - vec![ - "constrain ((x + y) == k) + z == y", - "constrain (x + !y) == y", - "constrain (x ^ y) == y", - "constrain (x ^ y) == (y + m)", - "constrain x + x ^ x == y | m", - ], - ); - assert_eq!(errors.len(), 5); - assert!(errors - .iter() - .all(|err| { err.is_error() && err.to_string().contains("deprecated") })); - } - - /// This is the standard way to declare an assert statement - #[test] - fn parse_assert() { - parse_with(assertion(expression()), "assert(x == y)").unwrap(); - - // Currently we disallow constrain statements where the outer infix operator - // produces a value. This would require an implicit `==` which - // may not be intuitive to the user. - // - // If this is deemed useful, one would either apply a transformation - // or interpret it with an `==` in the evaluator - let disallowed_operators = vec![ - BinaryOpKind::And, - BinaryOpKind::Subtract, - BinaryOpKind::Divide, - BinaryOpKind::Multiply, - BinaryOpKind::Or, - ]; - - for operator in disallowed_operators { - let src = format!("assert(x {} y);", operator.as_string()); - parse_with(assertion(expression()), &src).unwrap_err(); - } - - // These are general cases which should always work. - // - // The first case is the most noteworthy. It contains two `==` - // The first (inner) `==` is a predicate which returns 0/1 - // The outer layer is an infix `==` which is - // associated with the Constrain statement - parse_all( - assertion(expression()), - vec![ - "assert(((x + y) == k) + z == y)", - "assert((x + !y) == y)", - "assert((x ^ y) == y)", - "assert((x ^ y) == (y + m))", - "assert(x + x ^ x == y | m)", - ], - ); - - match parse_with(assertion(expression()), "assert(x == y, \"assertion message\")").unwrap() - { - StatementKind::Constrain(ConstrainStatement { arguments, .. }) => { - let message = arguments.last().unwrap(); - match &message.kind { - ExpressionKind::Literal(Literal::Str(message_string)) => { - assert_eq!(message_string, "assertion message"); - } - _ => unreachable!(), - } - } - _ => unreachable!(), - } - } - - /// This is the standard way to assert that two expressions are equivalent - #[test] - fn parse_assert_eq() { - parse_all( - assertion(expression()), - vec![ - "assert_eq(x, y)", - "assert_eq(((x + y) == k) + z, y)", - "assert_eq(x + !y, y)", - "assert_eq(x ^ y, y)", - "assert_eq(x ^ y, y + m)", - "assert_eq(x + x ^ x, y | m)", - ], - ); - match parse_with(assertion(expression()), "assert_eq(x, y, \"assertion message\")").unwrap() - { - StatementKind::Constrain(ConstrainStatement { arguments, .. }) => { - let message = arguments.last().unwrap(); - match &message.kind { - ExpressionKind::Literal(Literal::Str(message_string)) => { - assert_eq!(message_string, "assertion message"); - } - _ => unreachable!(), - } - } - _ => unreachable!(), - } - } -} 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 66d0ca29ca6..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 @@ -1,78 +1,81 @@ -use chumsky::Parser; use noirc_errors::Span; -use crate::{ - macros_api::SecondaryAttribute, - parser::{NoirParser, ParserError, ParserErrorReason}, - token::{Attribute, Attributes, Token, TokenKind}, -}; +use crate::parser::ParserErrorReason; +use crate::token::SecondaryAttribute; +use crate::token::{Attribute, Token, TokenKind}; -use super::primitives::token_kind; +use super::parse_many::without_separator; +use super::Parser; -fn attribute() -> impl NoirParser { - token_kind(TokenKind::Attribute).map(|token| match token { - Token::Attribute(attribute) => attribute, - _ => unreachable!("Parser should have already errored due to token not being an attribute"), - }) -} +impl<'a> Parser<'a> { + /// InnerAttribute = inner_attribute + pub(super) fn parse_inner_attribute(&mut self) -> Option { + let token = self.eat_kind(TokenKind::InnerAttribute)?; + match token.into_token() { + Token::InnerAttribute(attribute) => Some(attribute), + _ => unreachable!(), + } + } -pub(super) fn attributes() -> impl NoirParser> { - attribute().repeated() -} + /// Attributes = attribute* + pub(super) fn parse_attributes(&mut self) -> Vec<(Attribute, Span)> { + self.parse_many("attributes", without_separator(), Self::parse_attribute) + } -pub(super) fn validate_attributes( - attributes: Vec, - span: Span, - emit: &mut dyn FnMut(ParserError), -) -> Attributes { - let mut primary = None; - let mut secondary = Vec::new(); + fn parse_attribute(&mut self) -> Option<(Attribute, Span)> { + self.eat_kind(TokenKind::Attribute).map(|token| match token.into_token() { + Token::Attribute(attribute) => (attribute, self.previous_token_span), + _ => unreachable!(), + }) + } - for attribute in attributes { - match attribute { - Attribute::Function(attr) => { - if primary.is_some() { - emit(ParserError::with_reason( - ParserErrorReason::MultipleFunctionAttributesFound, - span, - )); + pub(super) fn validate_secondary_attributes( + &mut self, + attributes: Vec<(Attribute, Span)>, + ) -> Vec { + attributes + .into_iter() + .filter_map(|(attribute, span)| match attribute { + Attribute::Function(..) => { + self.push_error(ParserErrorReason::NoFunctionAttributesAllowedOnStruct, span); + None } - primary = Some(attr); - } - Attribute::Secondary(attr) => secondary.push(attr), - } + Attribute::Secondary(attr) => Some(attr), + }) + .collect() } - - Attributes { function: primary, secondary } } -pub(super) fn validate_secondary_attributes( - attributes: Vec, - span: Span, - emit: &mut dyn FnMut(ParserError), -) -> Vec { - let mut struct_attributes = vec![]; +#[cfg(test)] +mod tests { + use crate::{ + parser::{parser::tests::expect_no_errors, Parser}, + token::{Attribute, FunctionAttribute, SecondaryAttribute, TestScope}, + }; - for attribute in attributes { - match attribute { - Attribute::Function(..) => { - emit(ParserError::with_reason( - ParserErrorReason::NoFunctionAttributesAllowedOnStruct, - span, - )); - } - Attribute::Secondary(attr) => struct_attributes.push(attr), - } + #[test] + fn parses_inner_attribute() { + let src = "#!['hello]"; + let mut parser = Parser::for_str(src); + 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"); } - struct_attributes -} + #[test] + fn parses_attributes() { + let src = "#[test] #[deprecated]"; + let mut parser = Parser::for_str(src); + let mut attributes = parser.parse_attributes(); + expect_no_errors(&parser.errors); + assert_eq!(attributes.len(), 2); + + let (attr, _) = attributes.remove(0); + assert!(matches!(attr, Attribute::Function(FunctionAttribute::Test(TestScope::None)))); -pub(super) fn inner_attribute() -> impl NoirParser { - token_kind(TokenKind::InnerAttribute).map(|token| match token { - Token::InnerAttribute(attribute) => attribute, - _ => unreachable!( - "Parser should have already errored due to token not being an inner attribute" - ), - }) + let (attr, _) = attributes.remove(0); + assert!(matches!(attr, Attribute::Secondary(SecondaryAttribute::Deprecated(None)))); + } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/doc_comments.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/doc_comments.rs index 151ff21017f..578a49641f6 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/doc_comments.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/doc_comments.rs @@ -1,36 +1,58 @@ -use chumsky::Parser; - -use crate::{ - parser::NoirParser, - token::{DocStyle, Token, TokenKind}, -}; - -use super::primitives::token_kind; - -fn outer_doc_comment() -> impl NoirParser { - token_kind(TokenKind::OuterDocComment).map(|token| match token { - Token::LineComment(comment, Some(DocStyle::Outer)) => comment, - Token::BlockComment(comment, Some(DocStyle::Outer)) => comment, - _ => unreachable!( - "Parser should have already errored due to token not being an outer doc comment" - ), - }) -} +use crate::token::{DocStyle, Token, TokenKind}; -pub(super) fn outer_doc_comments() -> impl NoirParser> { - outer_doc_comment().repeated() -} +use super::{parse_many::without_separator, Parser}; + +impl<'a> Parser<'a> { + /// InnerDocComments = inner_doc_comment* + pub(super) fn parse_inner_doc_comments(&mut self) -> Vec { + self.parse_many("inner doc comments", without_separator(), Self::parse_inner_doc_comment) + } + + fn parse_inner_doc_comment(&mut self) -> Option { + self.eat_kind(TokenKind::InnerDocComment).map(|token| match token.into_token() { + Token::LineComment(comment, Some(DocStyle::Inner)) + | Token::BlockComment(comment, Some(DocStyle::Inner)) => comment, + _ => unreachable!(), + }) + } -fn inner_doc_comment() -> impl NoirParser { - token_kind(TokenKind::InnerDocComment).map(|token| match token { - Token::LineComment(comment, Some(DocStyle::Inner)) => comment, - Token::BlockComment(comment, Some(DocStyle::Inner)) => comment, - _ => unreachable!( - "Parser should have already errored due to token not being an inner doc comment" - ), - }) + /// OuterDocComments = outer_doc_comments* + pub(super) fn parse_outer_doc_comments(&mut self) -> Vec { + self.parse_many("outer doc comments", without_separator(), Self::parse_outer_doc_comment) + } + + fn parse_outer_doc_comment(&mut self) -> Option { + self.eat_kind(TokenKind::OuterDocComment).map(|token| match token.into_token() { + Token::LineComment(comment, Some(DocStyle::Outer)) + | Token::BlockComment(comment, Some(DocStyle::Outer)) => comment, + _ => unreachable!(), + }) + } } -pub(super) fn inner_doc_comments() -> impl NoirParser> { - inner_doc_comment().repeated() +#[cfg(test)] +mod tests { + use crate::parser::{parser::tests::expect_no_errors, Parser}; + + #[test] + fn parses_inner_doc_comments() { + let src = "//! Hello\n//! World"; + let mut parser = Parser::for_str(src); + let comments = parser.parse_inner_doc_comments(); + expect_no_errors(&parser.errors); + assert_eq!(comments.len(), 2); + assert_eq!(comments[0], " Hello"); + assert_eq!(comments[1], " World"); + } + + #[test] + fn parses_outer_doc_comments() { + let src = "/// Hello\n/// World"; + let mut parser = Parser::for_str(src); + let comments = parser.parse_outer_doc_comments(); + expect_no_errors(&parser.errors); + assert_eq!(comments.len(), 2); + assert_eq!(comments[0], " Hello"); + assert_eq!(comments[1], " World"); + } } 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 new file mode 100644 index 00000000000..93bb4980801 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/expression.rs @@ -0,0 +1,1627 @@ +use iter_extended::vecmap; +use noirc_errors::Span; + +use crate::{ + ast::{ + ArrayLiteral, BlockExpression, CallExpression, CastExpression, ConstructorExpression, + Expression, ExpressionKind, GenericTypeArgs, Ident, IfExpression, IndexExpression, Literal, + MemberAccessExpression, MethodCallExpression, Statement, TypePath, UnaryOp, UnresolvedType, + }, + parser::{labels::ParsingRuleLabel, parser::parse_many::separated_by_comma, ParserErrorReason}, + token::{Keyword, Token, TokenKind}, +}; + +use super::{ + parse_many::{ + separated_by_comma_until_right_brace, separated_by_comma_until_right_paren, + without_separator, + }, + Parser, +}; + +impl<'a> Parser<'a> { + pub(crate) fn parse_expression_or_error(&mut self) -> Expression { + self.parse_expression_or_error_impl(true) // allow constructors + } + + /// Expression = EqualOrNotEqualExpression + pub(crate) fn parse_expression(&mut self) -> Option { + self.parse_expression_impl(true) // allow constructors + } + + /// When parsing `if` conditions we don't allow constructors. + /// For example `if foo { 1 }` shouldn't have `foo { 1 }` as the condition, but `foo` instead. + /// The same goes with `for`: `for x in foo { 1 }` should have `foo` as the collection, not `foo { 1 }`. + /// + /// ExpressionExceptConstructor = "Expression except ConstructorException" + pub(crate) fn parse_expression_except_constructor_or_error(&mut self) -> Expression { + self.parse_expression_or_error_impl(false) // allow constructors + } + + pub(crate) fn parse_expression_or_error_impl( + &mut self, + allow_constructors: bool, + ) -> Expression { + if let Some(expr) = self.parse_expression_impl(allow_constructors) { + expr + } else { + self.push_expected_expression(); + Expression { kind: ExpressionKind::Error, span: self.span_at_previous_token_end() } + } + } + + fn parse_expression_impl(&mut self, allow_constructors: bool) -> Option { + self.parse_equal_or_not_equal(allow_constructors) + } + + /// Term + /// = UnaryOp Term + /// | AtomOrUnaryRightExpression + pub(super) fn parse_term(&mut self, allow_constructors: bool) -> Option { + let start_span = self.current_token_span; + + if let Some(operator) = self.parse_unary_op() { + let Some(rhs) = self.parse_term(allow_constructors) else { + self.expected_label(ParsingRuleLabel::Expression); + return None; + }; + let kind = ExpressionKind::prefix(operator, rhs); + let span = self.span_since(start_span); + return Some(Expression { kind, span }); + } + + self.parse_atom_or_unary_right(allow_constructors) + } + + /// UnaryOp = '&' 'mut' | '-' | '!' | '*' + fn parse_unary_op(&mut self) -> Option { + if self.at(Token::Ampersand) && self.next_is(Token::Keyword(Keyword::Mut)) { + self.bump(); + self.bump(); + Some(UnaryOp::MutableReference) + } else if self.eat(Token::Minus) { + Some(UnaryOp::Minus) + } else if self.eat(Token::Bang) { + Some(UnaryOp::Not) + } else if self.eat(Token::Star) { + Some(UnaryOp::Dereference { implicitly_added: false }) + } else { + None + } + } + + /// AtomOrUnaryRightExpression + /// = Atom + /// | UnaryRightExpression + fn parse_atom_or_unary_right(&mut self, allow_constructors: bool) -> Option { + let start_span = self.current_token_span; + let mut atom = self.parse_atom(allow_constructors)?; + let mut parsed; + + loop { + (atom, parsed) = self.parse_unary_right(atom, start_span); + if parsed { + continue; + } else { + break; + } + } + + Some(atom) + } + + /// UnaryRightExpression + /// = CallExpression + /// | MemberAccessOrMethodCallExpression + /// | CastExpression + /// | IndexExpression + fn parse_unary_right(&mut self, mut atom: Expression, start_span: Span) -> (Expression, bool) { + let mut parsed; + + (atom, parsed) = self.parse_call(atom, start_span); + if parsed { + return (atom, parsed); + } + + (atom, parsed) = self.parse_member_access_or_method_call(atom, start_span); + if parsed { + return (atom, parsed); + } + + (atom, parsed) = self.parse_cast(atom, start_span); + if parsed { + return (atom, parsed); + } + + self.parse_index(atom, start_span) + } + + /// CallExpression = Atom CallArguments + fn parse_call(&mut self, atom: Expression, start_span: Span) -> (Expression, bool) { + if let Some(call_arguments) = self.parse_call_arguments() { + let kind = ExpressionKind::Call(Box::new(CallExpression { + func: Box::new(atom), + arguments: call_arguments.arguments, + is_macro_call: call_arguments.is_macro_call, + })); + let span = self.span_since(start_span); + let atom = Expression { kind, span }; + (atom, true) + } else { + (atom, false) + } + } + + /// MemberAccessOrMethodCallExpression + /// = MemberAccessExpression + /// | MethodCallExpression + /// + /// MemberAccessExpression = Atom '.' identifier + /// + /// MethodCallExpression = Atom '.' identifier CallArguments + fn parse_member_access_or_method_call( + &mut self, + atom: Expression, + start_span: Span, + ) -> (Expression, bool) { + if !self.eat_dot() { + return (atom, false); + } + + let Some(field_name) = self.parse_member_access_field_name() else { return (atom, true) }; + + let generics = self.parse_generics_after_member_access_field_name(); + + let kind = if let Some(call_arguments) = self.parse_call_arguments() { + ExpressionKind::MethodCall(Box::new(MethodCallExpression { + object: atom, + method_name: field_name, + generics, + arguments: call_arguments.arguments, + is_macro_call: call_arguments.is_macro_call, + })) + } else { + ExpressionKind::MemberAccess(Box::new(MemberAccessExpression { + lhs: atom, + rhs: field_name, + })) + }; + + let span = self.span_since(start_span); + let atom = Expression { kind, span }; + (atom, true) + } + + fn parse_member_access_field_name(&mut self) -> Option { + if let Some(ident) = self.eat_ident() { + Some(ident) + } else if let Some(int) = self.eat_int() { + Some(Ident::new(int.to_string(), self.previous_token_span)) + } else { + self.push_error( + ParserErrorReason::ExpectedFieldName(self.token.token().clone()), + self.current_token_span, + ); + None + } + } + + /// CastExpression = Atom 'as' Type + fn parse_cast(&mut self, atom: Expression, start_span: Span) -> (Expression, bool) { + if !self.eat_keyword(Keyword::As) { + return (atom, false); + } + + let typ = self.parse_type_or_error(); + let kind = ExpressionKind::Cast(Box::new(CastExpression { lhs: atom, r#type: typ })); + let span = self.span_since(start_span); + let atom = Expression { kind, span }; + (atom, true) + } + + /// IndexExpression = Atom '[' Expression ']' + fn parse_index(&mut self, atom: Expression, start_span: Span) -> (Expression, bool) { + if !self.eat_left_bracket() { + return (atom, false); + } + + let index = self.parse_expression_or_error(); + self.eat_or_error(Token::RightBracket); + let kind = ExpressionKind::Index(Box::new(IndexExpression { collection: atom, index })); + let span = self.span_since(start_span); + let atom = Expression { kind, span }; + (atom, true) + } + + fn parse_generics_after_member_access_field_name(&mut self) -> Option> { + if self.eat_double_colon() { + let generics = + self.parse_path_generics(ParserErrorReason::AssociatedTypesNotAllowedInMethodCalls); + if generics.is_none() { + self.expected_token(Token::Less); + } + generics + } else { + None + } + } + + /// Atom + /// = Literal + /// | ParenthesesExpression + /// | UnsafeExpression + /// | PathExpression + /// | IfExpression + /// | Lambda + /// | ComptimeExpression + /// | UnquoteExpression + /// | TypePathExpression + /// | AsTraitPath + /// | ResolvedExpression + /// | InternedExpression + /// | InternedStatementExpression + fn parse_atom(&mut self, allow_constructors: bool) -> Option { + let start_span = self.current_token_span; + let kind = self.parse_atom_kind(allow_constructors)?; + Some(Expression { kind, span: self.span_since(start_span) }) + } + + fn parse_atom_kind(&mut self, allow_constructors: bool) -> Option { + if let Some(literal) = self.parse_literal() { + return Some(literal); + } + + if let Some(kind) = self.parse_parentheses_expression() { + return Some(kind); + } + + if let Some(kind) = self.parse_unsafe_expr() { + return Some(kind); + } + + if let Some(kind) = self.parse_path_expr(allow_constructors) { + return Some(kind); + } + + // A constructor where the type is an interned unresolved type data is valid + if matches!(self.token.token(), Token::InternedUnresolvedTypeData(..)) + && self.next_is(Token::LeftBrace) + { + let span = self.current_token_span; + let typ = self.parse_interned_type().unwrap(); + self.eat_or_error(Token::LeftBrace); + let typ = UnresolvedType { typ, span }; + return Some(self.parse_constructor(typ)); + } + + if let Some(kind) = self.parse_if_expr() { + return Some(kind); + } + + if let Some(kind) = self.parse_lambda() { + return Some(kind); + } + + if let Some(kind) = self.parse_comptime_expr() { + return Some(kind); + } + + if let Some(kind) = self.parse_unquote_expr() { + return Some(kind); + } + + if let Some(kind) = self.parse_type_path_expr() { + return Some(kind); + } + + if let Some(as_trait_path) = self.parse_as_trait_path() { + return Some(ExpressionKind::AsTraitPath(as_trait_path)); + } + + if let Some(kind) = self.parse_resolved_expr() { + return Some(kind); + } + + if let Some(kind) = self.parse_interned_expr() { + return Some(kind); + } + + if let Some(kind) = self.parse_interned_statement_expr() { + return Some(kind); + } + + None + } + + /// ResolvedExpression = unquote_marker + fn parse_resolved_expr(&mut self) -> Option { + if let Some(token) = self.eat_kind(TokenKind::UnquoteMarker) { + match token.into_token() { + Token::UnquoteMarker(expr_id) => return Some(ExpressionKind::Resolved(expr_id)), + _ => unreachable!(""), + } + } + + None + } + + /// InternedExpression = interned_expr + fn parse_interned_expr(&mut self) -> Option { + if let Some(token) = self.eat_kind(TokenKind::InternedExpr) { + match token.into_token() { + Token::InternedExpr(id) => return Some(ExpressionKind::Interned(id)), + _ => unreachable!(""), + } + } + + None + } + + /// InternedStatementExpression = interned_statement + fn parse_interned_statement_expr(&mut self) -> Option { + if let Some(token) = self.eat_kind(TokenKind::InternedStatement) { + match token.into_token() { + Token::InternedStatement(id) => return Some(ExpressionKind::InternedStatement(id)), + _ => unreachable!(""), + } + } + + None + } + + /// UnsafeExpression = 'unsafe' Block + fn parse_unsafe_expr(&mut self) -> Option { + if !self.eat_keyword(Keyword::Unsafe) { + return None; + } + + let start_span = self.current_token_span; + if let Some(block) = self.parse_block() { + Some(ExpressionKind::Unsafe(block, self.span_since(start_span))) + } else { + Some(ExpressionKind::Error) + } + } + + /// PathExpression + /// = VariableExpression + /// | ConstructorExpression + /// + /// VariableExpression = Path + fn parse_path_expr(&mut self, allow_constructors: bool) -> Option { + let Some(path) = self.parse_path() else { + return None; + }; + + if allow_constructors && self.eat_left_brace() { + let typ = UnresolvedType::from_path(path); + return Some(self.parse_constructor(typ)); + } + + Some(ExpressionKind::Variable(path)) + } + + /// ConstructorExpression = Type '{' ConstructorFields? '}' + /// + /// ConstructorFields = ConstructorField ( ',' ConstructorField )* ','? + /// + /// ConstructorField = identifier ( ':' Expression )? + fn parse_constructor(&mut self, typ: UnresolvedType) -> ExpressionKind { + let fields = self.parse_many( + "constructor fields", + separated_by_comma_until_right_brace(), + Self::parse_constructor_field, + ); + + ExpressionKind::Constructor(Box::new(ConstructorExpression { + typ, + fields, + struct_type: None, + })) + } + + fn parse_constructor_field(&mut self) -> Option<(Ident, Expression)> { + let Some(ident) = self.eat_ident() else { + return None; + }; + + 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()) + }) + } + + /// IfExpression = 'if' ExpressionExceptConstructor Block ( 'else' ( Block | IfExpression ) )? + pub(super) fn parse_if_expr(&mut self) -> Option { + if !self.eat_keyword(Keyword::If) { + return None; + } + + let condition = self.parse_expression_except_constructor_or_error(); + + let start_span = self.current_token_span; + let Some(consequence) = self.parse_block() else { + self.expected_token(Token::LeftBrace); + let span = self.span_at_previous_token_end(); + return Some(ExpressionKind::If(Box::new(IfExpression { + condition, + consequence: Expression { kind: ExpressionKind::Error, span }, + alternative: None, + }))); + }; + let span = self.span_since(start_span); + let consequence = Expression { kind: ExpressionKind::Block(consequence), span }; + + let alternative = if self.eat_keyword(Keyword::Else) { + let start_span = self.current_token_span; + if let Some(block) = self.parse_block() { + let span = self.span_since(start_span); + Some(Expression { kind: ExpressionKind::Block(block), span }) + } else if let Some(if_expr) = self.parse_if_expr() { + Some(Expression { kind: if_expr, span: self.span_since(start_span) }) + } else { + self.expected_token(Token::LeftBrace); + None + } + } else { + None + }; + + Some(ExpressionKind::If(Box::new(IfExpression { condition, consequence, alternative }))) + } + + /// ComptimeExpression = 'comptime' Block + fn parse_comptime_expr(&mut self) -> Option { + if !self.eat_keyword(Keyword::Comptime) { + return None; + } + + let start_span = self.current_token_span; + + let Some(block) = self.parse_block() else { + self.expected_token(Token::LeftBrace); + return None; + }; + + Some(ExpressionKind::Comptime(block, self.span_since(start_span))) + } + + /// UnquoteExpression + /// = '$' identifier + /// | '$' '(' Expression ')' + fn parse_unquote_expr(&mut self) -> Option { + let start_span = self.current_token_span; + + if !self.eat(Token::DollarSign) { + return None; + } + + if let Some(path) = self.parse_path() { + let expr = Expression { + kind: ExpressionKind::Variable(path), + span: self.span_since(start_span), + }; + return Some(ExpressionKind::Unquote(Box::new(expr))); + } + + let span_at_left_paren = self.current_token_span; + if self.eat_left_paren() { + let expr = self.parse_expression_or_error(); + self.eat_or_error(Token::RightParen); + let expr = Expression { + kind: ExpressionKind::Parenthesized(Box::new(expr)), + span: self.span_since(span_at_left_paren), + }; + return Some(ExpressionKind::Unquote(Box::new(expr))); + } + + self.push_error( + ParserErrorReason::ExpectedIdentifierOrLeftParenAfterDollar, + self.current_token_span, + ); + + None + } + + /// TypePathExpression = PrimitiveType '::' identifier ( '::' GenericTypeArgs )? + fn parse_type_path_expr(&mut self) -> Option { + let start_span = self.current_token_span; + let Some(typ) = self.parse_primitive_type() else { + return None; + }; + let typ = UnresolvedType { typ, span: self.span_since(start_span) }; + + self.eat_or_error(Token::DoubleColon); + + let item = if let Some(ident) = self.eat_ident() { + ident + } else { + self.expected_identifier(); + Ident::new(String::new(), self.span_at_previous_token_end()) + }; + + let turbofish = if self.eat_double_colon() { + let generics = self.parse_generic_type_args(); + if generics.is_empty() { + self.expected_token(Token::Less); + } + generics + } else { + GenericTypeArgs::default() + }; + + Some(ExpressionKind::TypePath(TypePath { typ, item, turbofish })) + } + + /// Literal + /// = bool + /// | int + /// | str + /// | rawstr + /// | fmtstr + /// | QuoteExpression + /// | ArrayExpression + /// | SliceExpression + /// | BlockExpression + /// + /// QuoteExpression = 'quote' '{' token* '}' + /// + /// ArrayExpression = ArrayLiteral + /// + /// BlockExpression = Block + fn parse_literal(&mut self) -> Option { + if let Some(bool) = self.eat_bool() { + return Some(ExpressionKind::Literal(Literal::Bool(bool))); + } + + if let Some(int) = self.eat_int() { + return Some(ExpressionKind::integer(int)); + } + + if let Some(string) = self.eat_str() { + return Some(ExpressionKind::Literal(Literal::Str(string))); + } + + if let Some((string, n)) = self.eat_raw_str() { + return Some(ExpressionKind::Literal(Literal::RawStr(string, n))); + } + + if let Some(string) = self.eat_fmt_str() { + return Some(ExpressionKind::Literal(Literal::FmtStr(string))); + } + + if let Some(tokens) = self.eat_quote() { + return Some(ExpressionKind::Quote(tokens)); + } + + if let Some(literal) = self.parse_array_literal() { + return Some(ExpressionKind::Literal(Literal::Array(literal))); + } + + if let Some(literal) = self.parse_slice_literal() { + return Some(ExpressionKind::Literal(Literal::Slice(literal))); + } + + if let Some(kind) = self.parse_block() { + return Some(ExpressionKind::Block(kind)); + } + + None + } + + /// ArrayLiteral + /// = StandardArrayLiteral + /// | RepeatedArrayLiteral + /// + /// StandardArrayLiteral = '[' ArrayElements? ']' + /// + /// ArrayElements = Expression ( ',' Expression )? ','? + /// + /// RepeatedArrayLiteral = '[' Expression ';' TypeExpression ']' + fn parse_array_literal(&mut self) -> Option { + if !self.eat_left_bracket() { + return None; + } + + if self.eat_right_bracket() { + return Some(ArrayLiteral::Standard(Vec::new())); + } + + let first_expr = self.parse_expression_or_error(); + if first_expr.kind == ExpressionKind::Error { + return Some(ArrayLiteral::Standard(Vec::new())); + } + + if self.eat_semicolon() { + let length = self.parse_expression_or_error(); + self.eat_or_error(Token::RightBracket); + return Some(ArrayLiteral::Repeated { + repeated_element: Box::new(first_expr), + length: Box::new(length), + }); + } + + let comma_after_first_expr = self.eat_comma(); + let second_expr_span = self.current_token_span; + + let mut exprs = self.parse_many( + "expressions", + separated_by_comma().until(Token::RightBracket), + Self::parse_expression_in_list, + ); + + if !exprs.is_empty() && !comma_after_first_expr { + self.expected_token_separating_items(Token::Comma, "expressions", second_expr_span); + } + + exprs.insert(0, first_expr); + + Some(ArrayLiteral::Standard(exprs)) + } + + /// SliceExpression = '&' ArrayLiteral + fn parse_slice_literal(&mut self) -> Option { + if !(self.at(Token::Ampersand) && self.next_is(Token::LeftBracket)) { + return None; + } + + self.bump(); + self.parse_array_literal() + } + + /// ParenthesesExpression + /// = UnitLiteral + /// | ParenthesizedExpression + /// | TupleExpression + /// + /// UnitLiteral = '(' ')' + /// + /// ParenthesizedExpression = '(' Expression ')' + /// + /// TupleExpression = '(' Expression ( ',' Expression )+ ','? ')' + fn parse_parentheses_expression(&mut self) -> Option { + if !self.eat_left_paren() { + return None; + } + + if self.eat_right_paren() { + return Some(ExpressionKind::Literal(Literal::Unit)); + } + + let (mut exprs, trailing_comma) = self.parse_many_return_trailing_separator_if_any( + "expressions", + separated_by_comma_until_right_paren(), + Self::parse_expression_in_list, + ); + + Some(if exprs.len() == 1 && !trailing_comma { + ExpressionKind::Parenthesized(Box::new(exprs.remove(0))) + } else { + ExpressionKind::Tuple(exprs) + }) + } + + pub(super) fn parse_expression_in_list(&mut self) -> Option { + if let Some(expr) = self.parse_expression() { + Some(expr) + } else { + self.expected_label(ParsingRuleLabel::Expression); + None + } + } + + /// Block = '{' Statement* '}' + pub(super) fn parse_block(&mut self) -> Option { + if !self.eat_left_brace() { + return None; + } + + let statements = self.parse_many( + "statements", + without_separator().until(Token::RightBrace), + Self::parse_statement_in_block, + ); + + let statements = self.check_statements_require_semicolon(statements); + + Some(BlockExpression { statements }) + } + + fn parse_statement_in_block(&mut self) -> Option<(Statement, (Option, Span))> { + if let Some(statement) = self.parse_statement() { + Some(statement) + } else { + self.expected_label(ParsingRuleLabel::Statement); + None + } + } + + fn check_statements_require_semicolon( + &mut self, + statements: Vec<(Statement, (Option, Span))>, + ) -> Vec { + let last = statements.len().saturating_sub(1); + let iter = statements.into_iter().enumerate(); + vecmap(iter, |(i, (statement, (semicolon, span)))| { + statement + .add_semicolon(semicolon, span, i == last, &mut |error| self.errors.push(error)) + }) + } + + pub(super) fn push_expected_expression(&mut self) { + self.expected_label(ParsingRuleLabel::Expression); + } +} + +#[cfg(test)] +mod tests { + use strum::IntoEnumIterator; + + use crate::{ + ast::{ + ArrayLiteral, BinaryOpKind, Expression, ExpressionKind, Literal, StatementKind, + UnaryOp, UnresolvedTypeData, + }, + parser::{ + parser::tests::{ + expect_no_errors, get_single_error, get_single_error_reason, + get_source_with_error_span, + }, + Parser, ParserErrorReason, + }, + token::Token, + }; + + fn parse_expression_no_errors(src: &str) -> Expression { + let mut parser = Parser::for_str(src); + let expr = parser.parse_expression_or_error(); + assert_eq!(expr.span.end() as usize, src.len()); + expect_no_errors(&parser.errors); + expr + } + + #[test] + fn parses_bool_literals() { + let src = "true"; + let expr = parse_expression_no_errors(src); + assert!(matches!(expr.kind, ExpressionKind::Literal(Literal::Bool(true)))); + + let src = "false"; + let expr = parse_expression_no_errors(src); + assert!(matches!(expr.kind, ExpressionKind::Literal(Literal::Bool(false)))); + } + + #[test] + fn parses_integer_literal() { + let src = "42"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Literal(Literal::Integer(field, negative)) = expr.kind else { + panic!("Expected integer literal"); + }; + assert_eq!(field, 42_u128.into()); + assert!(!negative); + } + + #[test] + fn parses_negative_integer_literal() { + let src = "-42"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Literal(Literal::Integer(field, negative)) = expr.kind else { + panic!("Expected integer literal"); + }; + assert_eq!(field, 42_u128.into()); + assert!(negative); + } + + #[test] + fn parses_parenthesized_expression() { + let src = "(42)"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Parenthesized(expr) = expr.kind else { + panic!("Expected parenthesized expression"); + }; + let ExpressionKind::Literal(Literal::Integer(field, negative)) = expr.kind else { + panic!("Expected integer literal"); + }; + assert_eq!(field, 42_u128.into()); + assert!(!negative); + } + + #[test] + fn parses_unit() { + let src = "()"; + let expr = parse_expression_no_errors(src); + assert!(matches!(expr.kind, ExpressionKind::Literal(Literal::Unit))); + } + + #[test] + fn parses_str() { + let src = "\"hello\""; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Literal(Literal::Str(string)) = expr.kind else { + panic!("Expected string literal"); + }; + assert_eq!(string, "hello"); + } + + #[test] + fn parses_raw_str() { + let src = "r#\"hello\"#"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Literal(Literal::RawStr(string, n)) = expr.kind else { + panic!("Expected raw string literal"); + }; + assert_eq!(string, "hello"); + assert_eq!(n, 1); + } + + #[test] + fn parses_fmt_str() { + let src = "f\"hello\""; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Literal(Literal::FmtStr(string)) = expr.kind else { + panic!("Expected format string literal"); + }; + assert_eq!(string, "hello"); + } + + #[test] + fn parses_tuple_expression() { + let src = "(1, 2)"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Tuple(mut exprs) = expr.kind else { + panic!("Expected tuple expression"); + }; + assert_eq!(exprs.len(), 2); + + let expr = exprs.remove(0); + let ExpressionKind::Literal(Literal::Integer(field, negative)) = expr.kind else { + panic!("Expected integer literal"); + }; + assert_eq!(field, 1_u128.into()); + assert!(!negative); + + let expr = exprs.remove(0); + let ExpressionKind::Literal(Literal::Integer(field, negative)) = expr.kind else { + panic!("Expected integer literal"); + }; + assert_eq!(field, 2_u128.into()); + assert!(!negative); + } + + #[test] + fn parses_block_expression_with_a_single_expression() { + let src = "{ 1 }"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Block(mut block) = expr.kind else { + panic!("Expected block expression"); + }; + assert_eq!(block.statements.len(), 1); + + let statement = block.statements.remove(0); + let StatementKind::Expression(expr) = statement.kind else { + panic!("Expected expression statement"); + }; + + let ExpressionKind::Literal(Literal::Integer(field, negative)) = expr.kind else { + panic!("Expected integer literal"); + }; + assert_eq!(field, 1_u128.into()); + assert!(!negative); + } + + #[test] + fn parses_block_expression_with_multiple_statements() { + let src = " + { + let x = 1; + let y = 2; + 3 + }"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Block(block) = expr.kind else { + panic!("Expected block expression"); + }; + assert_eq!(block.statements.len(), 3); + assert_eq!(block.statements[0].kind.to_string(), "let x = 1"); + assert_eq!(block.statements[1].kind.to_string(), "let y = 2"); + assert_eq!(block.statements[2].kind.to_string(), "3"); + } + + #[test] + fn parses_block_expression_adds_semicolons() { + let src = " + { + 1 + 2 + 3 + }"; + let mut parser = Parser::for_str(src); + let expr = parser.parse_expression_or_error(); + assert_eq!(expr.span.end() as usize, src.len()); + assert_eq!(parser.errors.len(), 2); + assert!(matches!( + parser.errors[0].reason(), + Some(ParserErrorReason::MissingSeparatingSemi) + )); + assert!(matches!( + parser.errors[1].reason(), + Some(ParserErrorReason::MissingSeparatingSemi) + )); + let ExpressionKind::Block(block) = expr.kind else { + panic!("Expected block expression"); + }; + assert_eq!(block.statements.len(), 3); + } + + #[test] + fn parses_unsafe_expression() { + let src = "unsafe { 1 }"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Unsafe(block, _) = expr.kind else { + panic!("Expected unsafe expression"); + }; + assert_eq!(block.statements.len(), 1); + } + + #[test] + fn parses_unclosed_parentheses() { + let src = " + ( + ^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + parser.parse_expression(); + let error = get_single_error(&parser.errors, span); + assert_eq!(error.to_string(), "Expected an expression but found end of input"); + } + + #[test] + fn parses_missing_comma_in_tuple() { + let src = " + (1 2) + ^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + parser.parse_expression(); + let reason = get_single_error_reason(&parser.errors, span); + let ParserErrorReason::ExpectedTokenSeparatingTwoItems { token, items } = reason else { + panic!("Expected a different error"); + }; + assert_eq!(token, &Token::Comma); + assert_eq!(*items, "expressions"); + } + + #[test] + fn parses_empty_array_expression() { + let src = "[]"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Literal(Literal::Array(ArrayLiteral::Standard(exprs))) = expr.kind + else { + panic!("Expected array literal"); + }; + assert!(exprs.is_empty()); + } + + #[test] + fn parses_array_expression_with_one_element() { + let src = "[1]"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Literal(Literal::Array(ArrayLiteral::Standard(exprs))) = expr.kind + else { + panic!("Expected array literal"); + }; + assert_eq!(exprs.len(), 1); + assert_eq!(exprs[0].to_string(), "1"); + } + + #[test] + fn parses_array_expression_with_two_elements() { + let src = "[1, 3]"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Literal(Literal::Array(ArrayLiteral::Standard(exprs))) = expr.kind + else { + panic!("Expected array literal"); + }; + assert_eq!(exprs.len(), 2); + assert_eq!(exprs[0].to_string(), "1"); + assert_eq!(exprs[1].to_string(), "3"); + } + + #[test] + fn parses_array_expression_with_two_elements_missing_comma() { + let src = " + [1 3] + ^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + let expr = parser.parse_expression_or_error(); + assert_eq!(expr.span.end() as usize, src.len()); + + let reason = get_single_error_reason(&parser.errors, span); + let ParserErrorReason::ExpectedTokenSeparatingTwoItems { token, items } = reason else { + panic!("Expected a different error"); + }; + assert_eq!(token, &Token::Comma); + assert_eq!(*items, "expressions"); + + let ExpressionKind::Literal(Literal::Array(ArrayLiteral::Standard(exprs))) = expr.kind + else { + panic!("Expected array literal"); + }; + assert_eq!(exprs.len(), 2); + assert_eq!(exprs[0].to_string(), "1"); + assert_eq!(exprs[1].to_string(), "3"); + } + + #[test] + fn parses_repeated_array_expression() { + let src = "[1; 10]"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Literal(Literal::Array(ArrayLiteral::Repeated { + repeated_element, + length, + })) = expr.kind + else { + panic!("Expected array literal"); + }; + assert_eq!(repeated_element.to_string(), "1"); + assert_eq!(length.to_string(), "10"); + } + + #[test] + fn parses_empty_slice_expression() { + let src = "&[]"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Literal(Literal::Slice(ArrayLiteral::Standard(exprs))) = expr.kind + else { + panic!("Expected slice literal"); + }; + assert!(exprs.is_empty()); + } + + #[test] + fn parses_variable_ident() { + let src = "foo"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Variable(path) = expr.kind else { + panic!("Expected variable"); + }; + assert_eq!(path.to_string(), "foo"); + } + + #[test] + fn parses_variable_path() { + let src = "foo::bar"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Variable(path) = expr.kind else { + panic!("Expected variable"); + }; + assert_eq!(path.to_string(), "foo::bar"); + } + + #[test] + fn parses_variable_path_with_turbofish() { + let src = "foo::<9>"; + parse_expression_no_errors(src); + } + + #[test] + fn parses_mutable_ref() { + let src = "&mut foo"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Prefix(prefix) = expr.kind else { + panic!("Expected prefix expression"); + }; + assert!(matches!(prefix.operator, UnaryOp::MutableReference)); + + let ExpressionKind::Variable(path) = prefix.rhs.kind else { + panic!("Expected variable"); + }; + assert_eq!(path.to_string(), "foo"); + } + + #[test] + fn parses_minus() { + let src = "-foo"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Prefix(prefix) = expr.kind else { + panic!("Expected prefix expression"); + }; + assert!(matches!(prefix.operator, UnaryOp::Minus)); + + let ExpressionKind::Variable(path) = prefix.rhs.kind else { + panic!("Expected variable"); + }; + assert_eq!(path.to_string(), "foo"); + } + + #[test] + fn parses_not() { + let src = "!foo"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Prefix(prefix) = expr.kind else { + panic!("Expected prefix expression"); + }; + assert!(matches!(prefix.operator, UnaryOp::Not)); + + let ExpressionKind::Variable(path) = prefix.rhs.kind else { + panic!("Expected variable"); + }; + assert_eq!(path.to_string(), "foo"); + } + + #[test] + fn parses_dereference() { + let src = "*foo"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Prefix(prefix) = expr.kind else { + panic!("Expected prefix expression"); + }; + assert!(matches!(prefix.operator, UnaryOp::Dereference { implicitly_added: false })); + + let ExpressionKind::Variable(path) = prefix.rhs.kind else { + panic!("Expected variable"); + }; + assert_eq!(path.to_string(), "foo"); + } + + #[test] + fn parses_quote() { + let src = "quote { 1 }"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Quote(tokens) = expr.kind else { + panic!("Expected quote expression"); + }; + assert_eq!(tokens.0.len(), 1); + } + + #[test] + fn parses_call() { + let src = "foo(1, 2)"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Call(call) = expr.kind else { + panic!("Expected call expression"); + }; + assert_eq!(call.func.to_string(), "foo"); + assert_eq!(call.arguments.len(), 2); + assert!(!call.is_macro_call); + } + + #[test] + fn parses_call_missing_comma() { + let src = " + foo(1 2) + ^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + let expr = parser.parse_expression_or_error(); + assert_eq!(expr.span.end() as usize, src.len()); + let reason = get_single_error_reason(&parser.errors, span); + let ParserErrorReason::ExpectedTokenSeparatingTwoItems { token, items } = reason else { + panic!("Expected a different error"); + }; + assert_eq!(token, &Token::Comma); + assert_eq!(*items, "arguments"); + + let ExpressionKind::Call(call) = expr.kind else { + panic!("Expected call expression"); + }; + assert_eq!(call.func.to_string(), "foo"); + assert_eq!(call.arguments.len(), 2); + assert!(!call.is_macro_call); + } + + #[test] + fn parses_call_with_wrong_expression() { + let src = "foo(]) "; + let mut parser = Parser::for_str(src); + parser.parse_expression_or_error(); + assert!(!parser.errors.is_empty()); + } + + #[test] + fn parses_call_with_turbofish() { + let src = "foo::(1, 2)"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Call(call) = expr.kind else { + panic!("Expected call expression"); + }; + assert_eq!(call.func.to_string(), "foo::"); + assert_eq!(call.arguments.len(), 2); + assert!(!call.is_macro_call); + } + + #[test] + fn parses_macro_call() { + let src = "foo!(1, 2)"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Call(call) = expr.kind else { + panic!("Expected call expression"); + }; + assert_eq!(call.func.to_string(), "foo"); + assert_eq!(call.arguments.len(), 2); + assert!(call.is_macro_call); + } + + #[test] + fn parses_member_access() { + let src = "foo.bar"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::MemberAccess(member_access) = expr.kind else { + panic!("Expected member access expression"); + }; + assert_eq!(member_access.lhs.to_string(), "foo"); + assert_eq!(member_access.rhs.to_string(), "bar"); + } + + #[test] + fn parses_method_call() { + let src = "foo.bar(1, 2)"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::MethodCall(method_call) = expr.kind else { + panic!("Expected method call expression"); + }; + assert_eq!(method_call.object.to_string(), "foo"); + assert_eq!(method_call.method_name.to_string(), "bar"); + assert!(!method_call.is_macro_call); + assert_eq!(method_call.arguments.len(), 2); + assert!(method_call.generics.is_none()); + } + + #[test] + fn parses_method_call_with_turbofish() { + let src = "foo.bar::(1, 2)"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::MethodCall(method_call) = expr.kind else { + panic!("Expected method call expression"); + }; + assert_eq!(method_call.object.to_string(), "foo"); + assert_eq!(method_call.method_name.to_string(), "bar"); + assert!(!method_call.is_macro_call); + assert_eq!(method_call.arguments.len(), 2); + assert_eq!(method_call.generics.unwrap().len(), 2); + } + + #[test] + fn parses_method_macro_call() { + let src = "foo.bar!(1, 2)"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::MethodCall(method_call) = expr.kind else { + panic!("Expected method call expression"); + }; + assert_eq!(method_call.object.to_string(), "foo"); + assert_eq!(method_call.method_name.to_string(), "bar"); + assert!(method_call.is_macro_call); + assert_eq!(method_call.arguments.len(), 2); + assert!(method_call.generics.is_none()); + } + + #[test] + fn parses_empty_constructor() { + let src = "Foo {}"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Constructor(constructor) = expr.kind else { + panic!("Expected constructor"); + }; + assert_eq!(constructor.typ.to_string(), "Foo"); + assert!(constructor.fields.is_empty()); + } + + #[test] + fn parses_constructor_with_fields() { + let src = "Foo { x: 1, y, z: 2 }"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Constructor(mut constructor) = expr.kind else { + panic!("Expected constructor"); + }; + assert_eq!(constructor.typ.to_string(), "Foo"); + assert_eq!(constructor.fields.len(), 3); + + 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"); + + let (name, expr) = constructor.fields.remove(0); + assert_eq!(name.to_string(), "z"); + 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 }"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::If(if_expr) = expr.kind else { + panic!("Expected if"); + }; + assert_eq!(if_expr.condition.to_string(), "true"); + let ExpressionKind::Block(block_expr) = if_expr.consequence.kind else { + panic!("Expected block"); + }; + assert_eq!(block_expr.statements.len(), 1); + assert_eq!(block_expr.statements[0].kind.to_string(), "1"); + assert!(if_expr.alternative.is_none()); + } + + #[test] + fn parses_parses_if_var() { + let src = "if foo { 1 }"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::If(if_expr) = expr.kind else { + panic!("Expected if"); + }; + assert_eq!(if_expr.condition.to_string(), "foo"); + } + + #[test] + fn parses_parses_if_else() { + let src = "if true { 1 } else { 2 }"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::If(if_expr) = expr.kind else { + panic!("Expected if"); + }; + assert_eq!(if_expr.condition.to_string(), "true"); + assert!(if_expr.alternative.is_some()); + } + + #[test] + fn parses_parses_if_else_if() { + let src = "if true { 1 } else if false { 2 } else { 3 }"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::If(if_expr) = expr.kind else { + panic!("Expected if"); + }; + assert_eq!(if_expr.condition.to_string(), "true"); + let ExpressionKind::If(..) = if_expr.alternative.unwrap().kind else { + panic!("Expected if"); + }; + } + + #[test] + fn parses_cast() { + let src = "1 as u8"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Cast(cast_expr) = expr.kind else { + panic!("Expected cast"); + }; + assert_eq!(cast_expr.lhs.to_string(), "1"); + assert_eq!(cast_expr.r#type.to_string(), "u8"); + } + + #[test] + fn parses_cast_missing_type() { + let src = " + 1 as + ^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + parser.parse_expression(); + let error = get_single_error(&parser.errors, span); + assert_eq!(error.to_string(), "Expected a type but found end of input"); + } + + #[test] + fn parses_index() { + let src = "1[2]"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Index(index_expr) = expr.kind else { + panic!("Expected index"); + }; + assert_eq!(index_expr.collection.to_string(), "1"); + assert_eq!(index_expr.index.to_string(), "2"); + } + + #[test] + fn parses_operators() { + for operator in BinaryOpKind::iter() { + let src = format!("1 {operator} 2"); + let mut parser = Parser::for_str(&src); + let expr = parser.parse_expression_or_error(); + assert_eq!(expr.span.end() as usize, src.len()); + assert!(parser.errors.is_empty(), "Expected no errors for {operator}"); + let ExpressionKind::Infix(infix_expr) = expr.kind else { + panic!("Expected infix for {operator}"); + }; + assert_eq!(infix_expr.lhs.to_string(), "1"); + assert_eq!(infix_expr.operator.contents, operator); + assert_eq!(infix_expr.rhs.to_string(), "2"); + } + } + + #[test] + fn parses_operator_precedence() { + // This test produces a gigantic expression with lots of infix expressions without parentheses. + // We parse it, then we transform that to a string. Because `InfixExpression::to_string()` adds parentheses + // around it, we can check the operator precedence is correct by checking where parentheses were placed. + let multiply_or_divide_or_modulo = "1 * 2 / 3 % 4"; + let expected_multiply_or_divide_or_modulo = "(((1 * 2) / 3) % 4)"; + + let add_or_subtract = format!("{multiply_or_divide_or_modulo} + {multiply_or_divide_or_modulo} - {multiply_or_divide_or_modulo}"); + let expected_add_or_subtract = format!("(({expected_multiply_or_divide_or_modulo} + {expected_multiply_or_divide_or_modulo}) - {expected_multiply_or_divide_or_modulo})"); + + let shift = format!("{add_or_subtract} << {add_or_subtract} >> {add_or_subtract}"); + let expected_shift = format!("(({expected_add_or_subtract} << {expected_add_or_subtract}) >> {expected_add_or_subtract})"); + + let less_or_greater = format!("{shift} < {shift} > {shift} <= {shift} >= {shift}"); + let expected_less_or_greater = format!("(((({expected_shift} < {expected_shift}) > {expected_shift}) <= {expected_shift}) >= {expected_shift})"); + + let xor = format!("{less_or_greater} ^ {less_or_greater}"); + let expected_xor = format!("({expected_less_or_greater} ^ {expected_less_or_greater})"); + + let and = format!("{xor} & {xor}"); + let expected_and = format!("({expected_xor} & {expected_xor})"); + + let or = format!("{and} | {and}"); + let expected_or = format!("({expected_and} | {expected_and})"); + + let equal_or_not_equal = format!("{or} == {or} != {or}"); + let expected_equal_or_not_equal = + format!("(({expected_or} == {expected_or}) != {expected_or})"); + + let src = &equal_or_not_equal; + let expected_src = expected_equal_or_not_equal; + + let expr = parse_expression_no_errors(src); + let ExpressionKind::Infix(infix_expr) = expr.kind else { + panic!("Expected infix"); + }; + assert_eq!(infix_expr.to_string(), expected_src); + } + + #[test] + fn parses_empty_lambda() { + let src = "|| 1"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Lambda(lambda) = expr.kind else { + panic!("Expected lambda"); + }; + assert!(lambda.parameters.is_empty()); + assert_eq!(lambda.body.to_string(), "1"); + assert!(matches!(lambda.return_type.typ, UnresolvedTypeData::Unspecified)); + } + + #[test] + fn parses_lambda_with_arguments() { + let src = "|x, y: Field| 1"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Lambda(mut lambda) = expr.kind else { + panic!("Expected lambda"); + }; + assert_eq!(lambda.parameters.len(), 2); + + let (pattern, typ) = lambda.parameters.remove(0); + assert_eq!(pattern.to_string(), "x"); + assert!(matches!(typ.typ, UnresolvedTypeData::Unspecified)); + + let (pattern, typ) = lambda.parameters.remove(0); + assert_eq!(pattern.to_string(), "y"); + assert!(matches!(typ.typ, UnresolvedTypeData::FieldElement)); + } + + #[test] + fn parses_lambda_with_return_type() { + let src = "|| -> Field 1"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Lambda(lambda) = expr.kind else { + panic!("Expected lambda"); + }; + assert!(lambda.parameters.is_empty()); + assert_eq!(lambda.body.to_string(), "1"); + assert!(matches!(lambda.return_type.typ, UnresolvedTypeData::FieldElement)); + } + + #[test] + fn parses_as_trait_path() { + let src = "::baz"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::AsTraitPath(as_trait_path) = expr.kind else { + panic!("Expected as_trait_path") + }; + assert_eq!(as_trait_path.typ.typ.to_string(), "Field"); + assert_eq!(as_trait_path.trait_path.to_string(), "foo::Bar"); + assert!(as_trait_path.trait_generics.is_empty()); + assert_eq!(as_trait_path.impl_item.to_string(), "baz"); + } + + #[test] + fn parses_comptime_expression() { + let src = "comptime { 1 }"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Comptime(block, _) = expr.kind else { + panic!("Expected comptime block"); + }; + assert_eq!(block.statements.len(), 1); + } + + #[test] + fn parses_type_path() { + let src = "Field::foo"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::TypePath(type_path) = expr.kind else { + panic!("Expected type_path"); + }; + assert_eq!(type_path.typ.to_string(), "Field"); + assert_eq!(type_path.item.to_string(), "foo"); + assert!(type_path.turbofish.is_empty()); + } + + #[test] + fn parses_type_path_with_generics() { + let src = "Field::foo::"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::TypePath(type_path) = expr.kind else { + panic!("Expected type_path"); + }; + assert_eq!(type_path.typ.to_string(), "Field"); + assert_eq!(type_path.item.to_string(), "foo"); + assert!(!type_path.turbofish.is_empty()); + } + + #[test] + fn parses_unquote_var() { + let src = "$foo::bar"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Unquote(expr) = expr.kind else { + panic!("Expected unquote"); + }; + let ExpressionKind::Variable(path) = expr.kind else { + panic!("Expected unquote"); + }; + assert_eq!(path.to_string(), "foo::bar"); + } + + #[test] + fn parses_unquote_expr() { + let src = "$(1 + 2)"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Unquote(expr) = expr.kind else { + panic!("Expected unquote"); + }; + assert_eq!(expr.kind.to_string(), "((1 + 2))"); + } +} 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 dc8b968ea7a..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 @@ -1,319 +1,492 @@ -use super::{ - attributes::{attributes, validate_attributes}, - block, fresh_statement, ident, keyword, maybe_comp_time, nothing, parameter_name_recovery, - parameter_recovery, parenthesized, parse_type, pattern, - primitives::token_kind, - self_parameter, - visibility::{item_visibility, visibility}, - where_clause, NoirParser, -}; -use crate::token::{Keyword, Token, TokenKind}; -use crate::{ - ast::{BlockExpression, IntegerBitSize}, - parser::spanned, +use crate::ast::{ + BlockExpression, GenericTypeArgs, Ident, Path, Pattern, UnresolvedTraitConstraint, + UnresolvedType, }; +use crate::token::{Attribute, Attributes, Keyword, Token}; +use crate::{ast::UnresolvedGenerics, parser::labels::ParsingRuleLabel}; use crate::{ ast::{ - FunctionDefinition, FunctionReturnType, ItemVisibility, NoirFunction, Param, Visibility, + FunctionDefinition, FunctionReturnType, ItemVisibility, NoirFunction, Param, + UnresolvedTypeData, Visibility, }, - macros_api::UnresolvedTypeData, - parser::{ParserError, ParserErrorReason}, -}; -use crate::{ - ast::{Signedness, UnresolvedGeneric, UnresolvedGenerics}, - parser::labels::ParsingRuleLabel, + parser::ParserErrorReason, }; +use acvm::AcirField; -use chumsky::prelude::*; use noirc_errors::Span; -/// function_definition: attribute function_modifiers 'fn' ident generics '(' function_parameters ')' function_return_type block -/// function_modifiers 'fn' ident generics '(' function_parameters ')' function_return_type block -pub(super) fn function_definition(allow_self: bool) -> impl NoirParser { - let body_or_error = - spanned(block(fresh_statement()).or_not()).validate(|(body, body_span), span, emit| { - if let Some(body) = body { - (body, body_span) - } else { - emit(ParserError::with_reason( - ParserErrorReason::ExpectedLeftBraceOrArrowAfterFunctionParameters, - span, - )); - (BlockExpression { statements: vec![] }, Span::from(span.end()..span.end())) - } - }); - - attributes() - .then(function_modifiers()) - .then_ignore(keyword(Keyword::Fn)) - .then(ident()) - .then(generics()) - .then( - parenthesized(function_parameters(allow_self)) - .then(function_return_type()) - .then(where_clause()) - .then(body_or_error) - // Allow parsing just `fn foo` for recovery and LSP autocompletion - .or_not(), +use super::parse_many::separated_by_comma_until_right_paren; +use super::pattern::SelfPattern; +use super::{pattern::PatternOrSelf, Parser}; + +pub(crate) struct FunctionDefinitionWithOptionalBody { + pub(crate) name: Ident, + pub(crate) generics: UnresolvedGenerics, + pub(crate) parameters: Vec, + pub(crate) body: Option, + pub(crate) span: Span, + pub(crate) where_clause: Vec, + pub(crate) return_type: FunctionReturnType, + pub(crate) return_visibility: Visibility, +} + +impl<'a> Parser<'a> { + /// Function = 'fn' identifier Generics FunctionParameters ( '->' Visibility Type )? WhereClause ( Block | ';' ) + pub(crate) fn parse_function( + &mut self, + attributes: Vec<(Attribute, Span)>, + visibility: ItemVisibility, + is_comptime: bool, + is_unconstrained: bool, + allow_self: bool, + ) -> NoirFunction { + self.parse_function_definition( + attributes, + visibility, + is_comptime, + is_unconstrained, + allow_self, ) - .validate(|args, span, emit| { - let ( - (((attributes, (is_unconstrained, visibility, is_comptime)), name), generics), - params_and_others, - ) = args; - - // Validate collected attributes, filtering them into function and secondary variants - let attributes = validate_attributes(attributes, span, emit); - - let function_definition = if let Some(params_and_others) = params_and_others { - let ( - ((parameters, (return_visibility, return_type)), where_clause), - (body, body_span), - ) = params_and_others; - - FunctionDefinition { - span: body_span, - name, - attributes, - is_unconstrained, - visibility, - is_comptime, - generics, - parameters, - body, - where_clause, - return_type, - return_visibility, - } + .into() + } + + pub(crate) fn parse_function_definition( + &mut self, + attributes: Vec<(Attribute, Span)>, + visibility: ItemVisibility, + is_comptime: bool, + is_unconstrained: bool, + allow_self: bool, + ) -> FunctionDefinition { + let attributes = self.validate_attributes(attributes); + + let func = self.parse_function_definition_with_optional_body( + false, // allow optional body + allow_self, + ); + + FunctionDefinition { + name: func.name, + attributes, + is_unconstrained, + is_comptime, + visibility, + generics: func.generics, + parameters: func.parameters, + body: func.body.unwrap_or_else(empty_body), + span: func.span, + where_clause: func.where_clause, + return_type: func.return_type, + return_visibility: func.return_visibility, + } + } + + pub(super) fn parse_function_definition_with_optional_body( + &mut self, + allow_optional_body: bool, + allow_self: bool, + ) -> FunctionDefinitionWithOptionalBody { + let Some(name) = self.eat_ident() else { + self.expected_identifier(); + return empty_function(self.previous_token_span); + }; + + let generics = self.parse_generics(); + let parameters = self.parse_function_parameters(allow_self); + + let (return_type, return_visibility) = if self.eat(Token::Arrow) { + let visibility = self.parse_visibility(); + (FunctionReturnType::Ty(self.parse_type_or_error()), visibility) + } else { + (FunctionReturnType::Default(self.span_at_previous_token_end()), Visibility::Private) + }; + + let where_clause = self.parse_where_clause(); + + let body_start_span = self.current_token_span; + let body = if self.eat_semicolons() { + if !allow_optional_body { + self.push_error(ParserErrorReason::ExpectedFunctionBody, body_start_span); + } + + None + } else { + Some(self.parse_block().unwrap_or_else(empty_body)) + }; + + FunctionDefinitionWithOptionalBody { + name, + generics, + parameters, + body, + span: self.span_since(body_start_span), + where_clause, + return_type, + return_visibility, + } + } + + /// FunctionParameters = '(' FunctionParametersList? ')' + /// + /// FunctionParametersList = FunctionParameter ( ',' FunctionParameter )* ','? + /// + /// FunctionParameter = Visibility PatternOrSelf ':' Type + fn parse_function_parameters(&mut self, allow_self: bool) -> Vec { + if !self.eat_left_paren() { + return Vec::new(); + } + + self.parse_many("parameters", separated_by_comma_until_right_paren(), |parser| { + parser.parse_function_parameter(allow_self) + }) + } + + fn parse_function_parameter(&mut self, allow_self: bool) -> Option { + loop { + let start_span = self.current_token_span; + + let pattern_or_self = if allow_self { + self.parse_pattern_or_self() } else { - emit(ParserError::with_reason( - ParserErrorReason::ExpectedLeftParenOrLeftBracketAfterFunctionName, - span, - )); - - let empty_span = Span::from(span.end()..span.end()); - FunctionDefinition { - span: empty_span, - name, - attributes, - is_unconstrained, - visibility, - is_comptime, - generics, - parameters: Vec::new(), - body: BlockExpression { statements: vec![] }, - where_clause: Vec::new(), - return_type: FunctionReturnType::Default(empty_span), - return_visibility: Visibility::Private, + self.parse_pattern().map(PatternOrSelf::Pattern) + }; + + let Some(pattern_or_self) = pattern_or_self else { + self.expected_label(ParsingRuleLabel::Pattern); + + // Let's try with the next token + self.bump(); + if self.at_eof() { + return None; + } else { + continue; } }; - function_definition.into() - }) -} -/// function_modifiers: 'unconstrained'? (visibility)? -/// -/// returns (is_unconstrained, visibility) for whether each keyword was present -pub(super) fn function_modifiers() -> impl NoirParser<(bool, ItemVisibility, bool)> { - keyword(Keyword::Unconstrained).or_not().then(item_visibility()).then(maybe_comp_time()).map( - |((unconstrained, visibility), comptime)| (unconstrained.is_some(), visibility, comptime), - ) -} + return Some(match pattern_or_self { + PatternOrSelf::Pattern(pattern) => self.pattern_param(pattern, start_span), + PatternOrSelf::SelfPattern(self_pattern) => self.self_pattern_param(self_pattern), + }); + } + } -pub(super) fn numeric_generic() -> impl NoirParser { - keyword(Keyword::Let) - .ignore_then(ident()) - .then_ignore(just(Token::Colon)) - .then(parse_type()) - .map(|(ident, typ)| UnresolvedGeneric::Numeric { ident, typ }) - .validate(|generic, span, emit| { - if let UnresolvedGeneric::Numeric { typ, .. } = &generic { - if let UnresolvedTypeData::Integer(signedness, bit_size) = typ.typ { - if matches!(signedness, Signedness::Signed) - || matches!(bit_size, IntegerBitSize::SixtyFour) - { - emit(ParserError::with_reason( - ParserErrorReason::ForbiddenNumericGenericType, - span, - )); - } + fn pattern_param(&mut self, pattern: Pattern, start_span: Span) -> Param { + let (visibility, typ) = if !self.eat_colon() { + self.push_error( + ParserErrorReason::MissingTypeForFunctionParameter, + Span::from(pattern.span().start()..self.current_token_span.end()), + ); + + let visibility = Visibility::Private; + let typ = UnresolvedType { typ: UnresolvedTypeData::Error, span: Span::default() }; + (visibility, typ) + } else { + (self.parse_visibility(), self.parse_type_or_error()) + }; + + Param { visibility, pattern, typ, span: self.span_since(start_span) } + } + + fn self_pattern_param(&mut self, self_pattern: SelfPattern) -> Param { + let ident_span = self.previous_token_span; + let ident = Ident::new("self".to_string(), ident_span); + let path = Path::from_single("Self".to_owned(), ident_span); + let no_args = GenericTypeArgs::default(); + let mut self_type = UnresolvedTypeData::Named(path, no_args, true).with_span(ident_span); + let mut pattern = Pattern::Identifier(ident); + + if self_pattern.reference { + self_type = + UnresolvedTypeData::MutableReference(Box::new(self_type)).with_span(ident_span); + } else if self_pattern.mutable { + pattern = Pattern::Mutable(Box::new(pattern), ident_span, true); + } + + Param { + visibility: Visibility::Private, + pattern, + typ: self_type, + span: self.span_since(ident_span), + } + } + + /// Visibility + /// = 'pub' + /// | 'return_data' + /// | 'call_data' '(' int ')' + /// | nothing + fn parse_visibility(&mut self) -> Visibility { + if self.eat_keyword(Keyword::Pub) { + return Visibility::Public; + } + + if self.eat_keyword(Keyword::ReturnData) { + return Visibility::ReturnData; + } + + if self.eat_keyword(Keyword::CallData) { + if self.eat_left_paren() { + if let Some(int) = self.eat_int() { + self.eat_or_error(Token::RightParen); + + let id = int.to_u128() as u32; + return Visibility::CallData(id); + } else { + self.expected_label(ParsingRuleLabel::Integer); + self.eat_right_paren(); + return Visibility::CallData(0); } + } else { + self.expected_token(Token::LeftParen); + return Visibility::CallData(0); } - generic - }) -} + } -pub(super) fn generic_type() -> impl NoirParser { - ident().map(UnresolvedGeneric::Variable) -} + Visibility::Private + } + + fn validate_attributes(&mut self, attributes: Vec<(Attribute, Span)>) -> Attributes { + let mut primary = None; + let mut secondary = Vec::new(); + + for (attribute, span) in attributes { + match attribute { + Attribute::Function(attr) => { + if primary.is_some() { + self.push_error(ParserErrorReason::MultipleFunctionAttributesFound, span); + } + primary = Some(attr); + } + Attribute::Secondary(attr) => secondary.push(attr), + } + } -pub(super) fn resolved_generic() -> impl NoirParser { - token_kind(TokenKind::QuotedType).map_with_span(|token, span| match token { - Token::QuotedType(id) => UnresolvedGeneric::Resolved(id, span), - _ => unreachable!("token_kind(QuotedType) guarantees we parse a quoted type"), - }) + Attributes { function: primary, secondary } + } } -pub(super) fn generic() -> impl NoirParser { - generic_type().or(numeric_generic()).or(resolved_generic()) +fn empty_function(span: Span) -> FunctionDefinitionWithOptionalBody { + FunctionDefinitionWithOptionalBody { + name: Ident::default(), + generics: Vec::new(), + parameters: Vec::new(), + body: None, + span: Span::from(span.end()..span.end()), + where_clause: Vec::new(), + return_type: FunctionReturnType::Default(Span::default()), + return_visibility: Visibility::Private, + } } -/// non_empty_ident_list: ident ',' non_empty_ident_list -/// | ident -/// -/// generics: '<' non_empty_ident_list '>' -/// | %empty -pub(super) fn generics() -> impl NoirParser { - generic() - .separated_by(just(Token::Comma)) - .allow_trailing() - .delimited_by(just(Token::Less), just(Token::Greater)) - .or_not() - .map(|opt| opt.unwrap_or_default()) +fn empty_body() -> BlockExpression { + BlockExpression { statements: Vec::new() } } -pub(super) fn function_return_type() -> impl NoirParser<(Visibility, FunctionReturnType)> { - #[allow(deprecated)] - just(Token::Arrow).ignore_then(visibility()).then(spanned(parse_type())).or_not().map_with_span( - |ret, span| match ret { - Some((visibility, (ty, _))) => (visibility, FunctionReturnType::Ty(ty)), - None => (Visibility::Private, FunctionReturnType::Default(span)), +#[cfg(test)] +mod tests { + use crate::{ + ast::{ItemVisibility, NoirFunction, UnresolvedTypeData, Visibility}, + parser::{ + parser::{ + parse_program, + tests::{ + expect_no_errors, get_single_error, get_single_error_reason, + get_source_with_error_span, + }, + }, + ItemKind, ParserErrorReason, }, - ) -} + }; -fn function_parameters<'a>(allow_self: bool) -> impl NoirParser> + 'a { - let typ = parse_type().recover_via(parameter_recovery()); + fn parse_function_no_error(src: &str) -> NoirFunction { + let (mut module, errors) = parse_program(src); + expect_no_errors(&errors); + assert_eq!(module.items.len(), 1); + let item = module.items.remove(0); + let ItemKind::Function(noir_function) = item.kind else { + panic!("Expected function"); + }; + noir_function + } - let full_parameter = pattern() - .recover_via(parameter_name_recovery()) - .then_ignore(just(Token::Colon)) - .then(visibility()) - .then(typ) - .map_with_span(|((pattern, visibility), typ), span| Param { - visibility, - pattern, - typ, - span, - }); + #[test] + fn parse_simple_function() { + let src = "fn foo() {}"; + let noir_function = parse_function_no_error(src); + assert_eq!("foo", noir_function.def.name.to_string()); + assert!(noir_function.def.parameters.is_empty()); + assert!(noir_function.def.generics.is_empty()); + } - let self_parameter = if allow_self { self_parameter().boxed() } else { nothing().boxed() }; + #[test] + fn parse_function_with_generics() { + let src = "fn foo() {}"; + let noir_function = parse_function_no_error(src); + assert_eq!(noir_function.def.generics.len(), 1); + } - let parameter = full_parameter.or(self_parameter); + #[test] + fn parse_function_with_arguments() { + let src = "fn foo(x: Field, y: Field) {}"; + let mut noir_function = parse_function_no_error(src); + assert_eq!(noir_function.def.parameters.len(), 2); - parameter - .separated_by(just(Token::Comma)) - .allow_trailing() - .labelled(ParsingRuleLabel::Parameter) -} + let param = noir_function.def.parameters.remove(0); + assert_eq!("x", param.pattern.to_string()); + assert_eq!("Field", param.typ.to_string()); + assert_eq!(param.visibility, Visibility::Private); -#[cfg(test)] -mod test { - use super::*; - use crate::parser::parser::test_helpers::*; + let param = noir_function.def.parameters.remove(0); + assert_eq!("y", param.pattern.to_string()); + assert_eq!("Field", param.typ.to_string()); + assert_eq!(param.visibility, Visibility::Private); + } #[test] - fn regression_skip_comment() { - parse_all( - function_definition(false), - vec![ - "fn main( - // This comment should be skipped - x : Field, - // And this one - y : Field, - ) { - }", - "fn main(x : Field, y : Field,) { - foo::bar( - // Comment for x argument - x, - // Comment for y argument - y - ) - }", - ], - ); + fn parse_function_with_argument_pub_visibility() { + let src = "fn foo(x: pub Field) {}"; + let mut noir_function = parse_function_no_error(src); + assert_eq!(noir_function.def.parameters.len(), 1); + + let param = noir_function.def.parameters.remove(0); + assert_eq!("x", param.pattern.to_string()); + assert_eq!("Field", param.typ.to_string()); + assert_eq!(param.visibility, Visibility::Public); } #[test] - fn parse_function() { - parse_all( - function_definition(false), - vec![ - "fn func_name() {}", - "fn f(foo: pub u8, y : pub Field) -> u8 { x + a }", - "fn f(f: pub Field, y : Field, z : Field) -> u8 { x + a }", - "fn func_name(f: Field, y : pub Field, z : pub [u8;5],) {}", - "fn f(f: pub Field, y : Field, z : Field) -> u8 { x + a }", - "fn f(f: pub Field, y : T, z : Field) -> u8 { x + a }", - "fn func_name(x: [Field], y : [Field;2],y : pub [Field;2], z : pub [u8;5]) {}", - "fn main(x: pub u8, y: pub u8) -> pub [u8; 2] { [x, y] }", - "fn f(f: pub Field, y : Field, z : Field) -> u8 { x + a }", - "fn f(f: pub Field, y : T, z : Field) -> u8 { x + a }", - "fn func_name(f: Field, y : T) where T: SomeTrait {}", - "fn func_name(f: Field, y : T) where T: SomeTrait + SomeTrait2 {}", - "fn func_name(f: Field, y : T) where T: SomeTrait, T: SomeTrait2 {}", - "fn func_name(f: Field, y : T) where T: SomeTrait + SomeTrait2 {}", - "fn func_name(f: Field, y : T) where T: SomeTrait + SomeTrait2 {}", - "fn func_name(f: Field, y : T) where T: SomeTrait + SomeTrait2 {}", - "fn func_name(f: Field, y : T) where T: SomeTrait + SomeTrait2 {}", - "fn func_name(f: Field, y : T) where T: SomeTrait + SomeTrait2 + TraitY {}", - "fn func_name(f: Field, y : T, z : U) where SomeStruct: SomeTrait {}", - // 'where u32: SomeTrait' is allowed in Rust. - // It will result in compiler error in case SomeTrait isn't implemented for u32. - "fn func_name(f: Field, y : T) where u32: SomeTrait {}", - // A trailing plus is allowed by Rust, so we support it as well. - "fn func_name(f: Field, y : T) where T: SomeTrait + {}", - // The following should produce compile error on later stage. From the parser's perspective it's fine - "fn func_name(f: Field, y : Field, z : Field) where T: SomeTrait {}", - // TODO: this fails with known EOF != EOF error - // https://github.com/noir-lang/noir/issues/4763 - // fn func_name(x: impl Eq) {} with error Expected an end of input but found end of input - // "fn func_name(x: impl Eq) {}", - "fn func_name(x: impl Eq, y : T) where T: SomeTrait + Eq {}", - "fn func_name(x: [Field; N]) {}", - ], - ); + fn parse_function_with_argument_return_data_visibility() { + let src = "fn foo(x: return_data Field) {}"; + let mut noir_function = parse_function_no_error(src); + assert_eq!(noir_function.def.parameters.len(), 1); - parse_all_failing( - function_definition(false), - vec![ - "fn x2( f: []Field,,) {}", - "fn ( f: []Field) {}", - "fn ( f: []Field) {}", - // TODO: Check for more specific error messages - "fn func_name(f: Field, y : pub Field, z : pub [u8;5],) where T: {}", - "fn func_name(f: Field, y : pub Field, z : pub [u8;5],) where SomeTrait {}", - "fn func_name(f: Field, y : pub Field, z : pub [u8;5],) SomeTrait {}", - // A leading plus is not allowed. - "fn func_name(f: Field, y : T) where T: + SomeTrait {}", - "fn func_name(f: Field, y : T) where T: TraitX + {}", - // Test ill-formed numeric generics - "fn func_name(y: T) {}", - "fn func_name(y: T) {}", - "fn func_name(y: T) {}", - // Test failure of missing `let` - "fn func_name(y: T) {}", - // Test that signed numeric generics are banned - "fn func_name() {}", - // Test that `u64` is banned - "fn func_name(x: [Field; N]) {}", - ], - ); + let param = noir_function.def.parameters.remove(0); + assert_eq!(param.visibility, Visibility::ReturnData); } #[test] - fn parse_recover_function_without_body() { - let src = "fn foo(x: i32)"; + fn parse_function_with_argument_call_data_visibility() { + let src = "fn foo(x: call_data(42) Field) {}"; + let mut noir_function = parse_function_no_error(src); + assert_eq!(noir_function.def.parameters.len(), 1); + + let param = noir_function.def.parameters.remove(0); + assert_eq!(param.visibility, Visibility::CallData(42)); + } - let (noir_function, errors) = parse_recover(function_definition(false), src); + #[test] + fn parse_function_return_type() { + let src = "fn foo() -> Field {}"; + let noir_function = parse_function_no_error(src); + assert_eq!(noir_function.def.return_visibility, Visibility::Private); + assert_eq!(noir_function.return_type().typ, UnresolvedTypeData::FieldElement); + } + + #[test] + fn parse_function_return_visibility() { + let src = "fn foo() -> pub Field {}"; + let noir_function = parse_function_no_error(src); + assert_eq!(noir_function.def.return_visibility, Visibility::Public); + assert_eq!(noir_function.return_type().typ, UnresolvedTypeData::FieldElement); + } + + #[test] + fn parse_function_unclosed_parentheses() { + let src = "fn foo(x: i32,"; + let (module, errors) = parse_program(src); assert_eq!(errors.len(), 1); - assert_eq!(errors[0].message, "expected { or -> after function parameters"); + assert_eq!(module.items.len(), 1); + let item = &module.items[0]; + let ItemKind::Function(noir_function) = &item.kind else { + panic!("Expected function"); + }; + assert_eq!("foo", noir_function.def.name.to_string()); + } + + #[test] + fn parse_error_multiple_function_attributes_found() { + let src = " + #[foreign(foo)] #[oracle(bar)] fn foo() {} + ^^^^^^^^^^^^^^ + "; + let (src, span) = get_source_with_error_span(src); + let (_, errors) = parse_program(&src); + let reason = get_single_error_reason(&errors, span); + assert!(matches!(reason, ParserErrorReason::MultipleFunctionAttributesFound)); + } + + #[test] + fn parse_function_found_semicolon_instead_of_braces() { + let src = " + fn foo(); + ^ + "; + let (src, span) = get_source_with_error_span(src); + let (_, errors) = parse_program(&src); + let reason = get_single_error_reason(&errors, span); + assert!(matches!(reason, ParserErrorReason::ExpectedFunctionBody)); + } - let noir_function = noir_function.unwrap(); - assert_eq!(noir_function.name(), "foo"); + #[test] + fn recovers_on_wrong_parameter_name() { + let src = " + fn foo(1 x: i32) {} + ^ + "; + let (src, span) = get_source_with_error_span(src); + let (module, errors) = parse_program(&src); + assert_eq!(module.items.len(), 1); + let ItemKind::Function(noir_function) = &module.items[0].kind else { + panic!("Expected function"); + }; assert_eq!(noir_function.parameters().len(), 1); - assert!(noir_function.def.body.statements.is_empty()); + + let error = get_single_error(&errors, span); + assert_eq!(error.to_string(), "Expected a pattern but found '1'"); + } + + #[test] + fn recovers_on_missing_colon_after_parameter_name() { + let src = " + fn foo(x, y: i32) {} + ^^ + "; + let (src, span) = get_source_with_error_span(src); + let (module, errors) = parse_program(&src); + assert_eq!(module.items.len(), 1); + let ItemKind::Function(noir_function) = &module.items[0].kind else { + panic!("Expected function"); + }; + assert_eq!(noir_function.parameters().len(), 2); + + let error = get_single_error(&errors, span); + assert!(error.to_string().contains("Missing type for function parameter")); + } + + #[test] + fn recovers_on_missing_type_after_parameter_colon() { + let src = " + fn foo(x: , y: i32) {} + ^ + "; + let (src, span) = get_source_with_error_span(src); + let (module, errors) = parse_program(&src); + assert_eq!(module.items.len(), 1); + let ItemKind::Function(noir_function) = &module.items[0].kind else { + panic!("Expected function"); + }; + assert_eq!(noir_function.parameters().len(), 2); + + let error = get_single_error(&errors, span); + assert_eq!(error.to_string(), "Expected a type but found ','"); + } + + #[test] + fn parse_function_with_unconstrained_after_visibility() { + let src = "pub unconstrained fn foo() {}"; + let noir_function = parse_function_no_error(src); + assert_eq!("foo", noir_function.def.name.to_string()); + assert!(noir_function.def.is_unconstrained); + assert_eq!(noir_function.def.visibility, ItemVisibility::Public); } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/generics.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/generics.rs new file mode 100644 index 00000000000..2c8ba5a2a65 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/generics.rs @@ -0,0 +1,283 @@ +use crate::{ + ast::{ + GenericTypeArg, GenericTypeArgs, IntegerBitSize, Signedness, UnresolvedGeneric, + UnresolvedGenerics, UnresolvedType, UnresolvedTypeData, + }, + parser::{labels::ParsingRuleLabel, ParserErrorReason}, + token::{Keyword, Token, TokenKind}, +}; + +use super::{parse_many::separated_by_comma, Parser}; + +impl<'a> Parser<'a> { + /// Generics = ( '<' GenericsList? '>' )? + /// + /// GenericsList = Generic ( ',' Generic )* ','? + pub(super) fn parse_generics(&mut self) -> UnresolvedGenerics { + if !self.eat_less() { + return Vec::new(); + } + + self.parse_many( + "generic parameters", + separated_by_comma().until(Token::Greater), + Self::parse_generic_in_list, + ) + } + + fn parse_generic_in_list(&mut self) -> Option { + if let Some(generic) = self.parse_generic() { + Some(generic) + } else { + self.expected_label(ParsingRuleLabel::GenericParameter); + None + } + } + + /// Generic + /// = VariableGeneric + /// | NumericGeneric + /// | ResolvedGeneric + fn parse_generic(&mut self) -> Option { + if let Some(generic) = self.parse_variable_generic() { + return Some(generic); + } + + if let Some(generic) = self.parse_numeric_generic() { + return Some(generic); + } + + if let Some(generic) = self.parse_resolved_generic() { + return Some(generic); + } + + None + } + + /// VariableGeneric = identifier + fn parse_variable_generic(&mut self) -> Option { + self.eat_ident().map(UnresolvedGeneric::Variable) + } + + /// NumericGeneric = 'let' identifier ':' Type + fn parse_numeric_generic(&mut self) -> Option { + if !self.eat_keyword(Keyword::Let) { + return None; + } + + let ident = self.eat_ident()?; + + if !self.eat_colon() { + // If we didn't get a type after the colon, error and assume it's u32 + self.push_error( + ParserErrorReason::MissingTypeForNumericGeneric, + self.current_token_span, + ); + let typ = UnresolvedType { + typ: UnresolvedTypeData::Integer(Signedness::Unsigned, IntegerBitSize::ThirtyTwo), + span: self.span_at_previous_token_end(), + }; + return Some(UnresolvedGeneric::Numeric { ident, typ }); + } + + let typ = self.parse_type_or_error(); + if let UnresolvedTypeData::Integer(signedness, bit_size) = &typ.typ { + if matches!(signedness, Signedness::Signed) + || matches!(bit_size, IntegerBitSize::SixtyFour) + { + self.push_error(ParserErrorReason::ForbiddenNumericGenericType, typ.span); + } + } + + Some(UnresolvedGeneric::Numeric { ident, typ }) + } + + /// ResolvedGeneric = quoted_type + fn parse_resolved_generic(&mut self) -> Option { + let token = self.eat_kind(TokenKind::QuotedType)?; + match token.into_token() { + Token::QuotedType(id) => { + Some(UnresolvedGeneric::Resolved(id, self.previous_token_span)) + } + _ => unreachable!(), + } + } + + /// GenericTypeArgs = ( '<' GenericTypeArgsList? '>' ) + /// + /// GenericTypeArgsList = GenericTypeArg ( ',' GenericTypeArg )* ','? + /// + /// GenericTypeArg + /// = NamedTypeArg + /// | OrderedTypeArg + /// + /// NamedTypeArg = identifier '=' Type + /// + /// OrderedTypeArg = TypeOrTypeExpression + pub(super) fn parse_generic_type_args(&mut self) -> GenericTypeArgs { + let mut generic_type_args = GenericTypeArgs::default(); + if !self.eat_less() { + return generic_type_args; + } + + let generics = self.parse_many( + "generic parameters", + separated_by_comma().until(Token::Greater), + Self::parse_generic_type_arg, + ); + + for generic in generics { + match generic { + GenericTypeArg::Ordered(typ) => { + generic_type_args.ordered_args.push(typ); + } + GenericTypeArg::Named(name, typ) => { + generic_type_args.named_args.push((name, typ)); + } + } + } + + generic_type_args + } + + fn parse_generic_type_arg(&mut self) -> Option { + if matches!(self.token.token(), Token::Ident(..)) && self.next_is(Token::Assign) { + let ident = self.eat_ident().unwrap(); + + self.eat_assign(); + + let typ = self.parse_type_or_error(); + return Some(GenericTypeArg::Named(ident, typ)); + } + + // Otherwise + let Some(typ) = self.parse_type_or_type_expression() else { + self.expected_label(ParsingRuleLabel::TypeOrTypeExpression); + return None; + }; + + Some(GenericTypeArg::Ordered(typ)) + } +} + +#[cfg(test)] +mod tests { + use crate::{ + ast::{GenericTypeArgs, IntegerBitSize, Signedness, UnresolvedGeneric, UnresolvedTypeData}, + parser::{ + parser::tests::{ + expect_no_errors, get_single_error_reason, get_source_with_error_span, + }, + Parser, ParserErrorReason, + }, + }; + + fn parse_generics_no_errors(src: &str) -> Vec { + let mut parser = Parser::for_str(src); + let generics = parser.parse_generics(); + expect_no_errors(&parser.errors); + generics + } + + fn parse_generic_type_args_no_errors(src: &str) -> GenericTypeArgs { + let mut parser = Parser::for_str(src); + let generics = parser.parse_generic_type_args(); + expect_no_errors(&parser.errors); + generics + } + + #[test] + fn parses_no_generics() { + let src = "1"; + let generics = parse_generics_no_errors(src); + assert!(generics.is_empty()); + } + + #[test] + fn parses_generics() { + let src = ""; + let mut generics = parse_generics_no_errors(src); + assert_eq!(generics.len(), 2); + + let generic = generics.remove(0); + let UnresolvedGeneric::Variable(ident) = generic else { + panic!("Expected generic variable"); + }; + assert_eq!("A", ident.to_string()); + + let generic = generics.remove(0); + let UnresolvedGeneric::Numeric { ident, typ } = generic else { + panic!("Expected generic numeric"); + }; + assert_eq!("B", ident.to_string()); + assert_eq!( + typ.typ, + UnresolvedTypeData::Integer(Signedness::Unsigned, IntegerBitSize::ThirtyTwo) + ); + } + + #[test] + fn parses_no_generic_type_args() { + let src = "1"; + let generics = parse_generic_type_args_no_errors(src); + assert!(generics.is_empty()); + } + + #[test] + fn parses_generic_type_args() { + let src = ""; + let generics = parse_generic_type_args_no_errors(src); + assert!(!generics.is_empty()); + assert_eq!(generics.ordered_args.len(), 1); + assert_eq!(generics.ordered_args[0].to_string(), "i32"); + assert_eq!(generics.named_args.len(), 1); + assert_eq!(generics.named_args[0].0.to_string(), "X"); + assert_eq!(generics.named_args[0].1.to_string(), "Field"); + } + + #[test] + fn parses_generic_type_arg_that_is_a_path() { + let src = ""; + let generics = parse_generic_type_args_no_errors(src); + assert!(!generics.is_empty()); + assert_eq!(generics.ordered_args.len(), 1); + assert_eq!(generics.ordered_args[0].to_string(), "foo::Bar"); + assert_eq!(generics.named_args.len(), 0); + } + + #[test] + fn parses_generic_type_arg_that_is_an_int() { + let src = "<1>"; + let generics = parse_generic_type_args_no_errors(src); + assert!(!generics.is_empty()); + assert_eq!(generics.ordered_args.len(), 1); + assert_eq!(generics.ordered_args[0].to_string(), "1"); + } + + #[test] + fn parse_numeric_generic_error_if_invalid_integer() { + let src = " + + ^^^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + parser.parse_generics(); + let reason = get_single_error_reason(&parser.errors, span); + assert!(matches!(reason, ParserErrorReason::ForbiddenNumericGenericType)); + } + + #[test] + fn parse_arithmetic_generic_on_variable() { + let src = ""; + let generics = parse_generic_type_args_no_errors(src); + assert_eq!(generics.ordered_args[0].to_string(), "(N - 1)"); + } + + #[test] + fn parse_var_with_turbofish_in_generic() { + let src = ">"; + let generics = parse_generic_type_args_no_errors(src); + assert_eq!(generics.ordered_args[0].to_string(), "N<1>"); + } +} 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 new file mode 100644 index 00000000000..d24064673b1 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/global.rs @@ -0,0 +1,168 @@ +use noirc_errors::Span; + +use crate::{ + ast::{ + Expression, ExpressionKind, Ident, LetStatement, Pattern, UnresolvedType, + UnresolvedTypeData, + }, + parser::ParserErrorReason, + token::{Attribute, Token}, +}; + +use super::Parser; + +impl<'a> Parser<'a> { + /// Global = 'global' identifier OptionalTypeAnnotation '=' Expression ';' + pub(crate) fn parse_global( + &mut self, + attributes: Vec<(Attribute, Span)>, + comptime: bool, + mutable: bool, + ) -> LetStatement { + // Only comptime globals are allowed to be mutable, but we always parse the `mut` + // and throw the error in name resolution. + + let attributes = self.validate_secondary_attributes(attributes); + + let Some(ident) = self.eat_ident() else { + self.eat_semicolons(); + return LetStatement { + pattern: ident_to_pattern(Ident::default(), mutable), + r#type: UnresolvedType { + typ: UnresolvedTypeData::Unspecified, + span: Span::default(), + }, + expression: Expression { kind: ExpressionKind::Error, span: Span::default() }, + attributes, + comptime, + }; + }; + + let pattern = ident_to_pattern(ident, mutable); + + let typ = self.parse_optional_type_annotation(); + + let expression = if self.eat_assign() { + self.parse_expression_or_error() + } else { + self.push_error(ParserErrorReason::GlobalWithoutValue, pattern.span()); + Expression { kind: ExpressionKind::Error, span: Span::default() } + }; + + if !self.eat_semicolons() { + self.expected_token(Token::Semicolon); + } + + LetStatement { pattern, r#type: typ, expression, attributes, comptime } + } +} + +fn ident_to_pattern(ident: Ident, mutable: bool) -> Pattern { + if mutable { + Pattern::Mutable(Box::new(Pattern::Identifier(ident)), Span::default(), false) + } else { + Pattern::Identifier(ident) + } +} + +#[cfg(test)] +mod tests { + use crate::{ + ast::{ + IntegerBitSize, ItemVisibility, LetStatement, Pattern, Signedness, UnresolvedTypeData, + }, + parser::{ + parser::{ + parse_program, + tests::{ + expect_no_errors, get_single_error, get_single_error_reason, + get_source_with_error_span, + }, + }, + ItemKind, ParserErrorReason, + }, + }; + + fn parse_global_no_errors(src: &str) -> (LetStatement, ItemVisibility) { + let (mut module, errors) = parse_program(src); + expect_no_errors(&errors); + assert_eq!(module.items.len(), 1); + let item = module.items.remove(0); + let ItemKind::Global(let_statement, visibility) = item.kind else { + panic!("Expected global"); + }; + (let_statement, visibility) + } + + #[test] + fn parse_global_no_type_annotation() { + let src = "global foo = 1;"; + let (let_statement, visibility) = parse_global_no_errors(src); + let Pattern::Identifier(name) = &let_statement.pattern else { + panic!("Expected identifier pattern"); + }; + assert_eq!("foo", name.to_string()); + assert!(matches!(let_statement.r#type.typ, UnresolvedTypeData::Unspecified)); + assert!(!let_statement.comptime); + assert_eq!(visibility, ItemVisibility::Private); + } + + #[test] + fn parse_global_with_type_annotation() { + let src = "global foo: i32 = 1;"; + let (let_statement, _visibility) = parse_global_no_errors(src); + let Pattern::Identifier(name) = &let_statement.pattern else { + panic!("Expected identifier pattern"); + }; + assert_eq!("foo", name.to_string()); + assert!(matches!( + let_statement.r#type.typ, + UnresolvedTypeData::Integer(Signedness::Signed, IntegerBitSize::ThirtyTwo) + )); + } + + #[test] + fn parse_comptime_global() { + let src = "comptime global foo: i32 = 1;"; + let (let_statement, _visibility) = parse_global_no_errors(src); + assert!(let_statement.comptime); + } + + #[test] + fn parse_mutable_global() { + let src = "mut global foo: i32 = 1;"; + let (let_statement, _visibility) = parse_global_no_errors(src); + let Pattern::Mutable(pattern, _, _) = &let_statement.pattern else { + panic!("Expected mutable pattern"); + }; + let pattern: &Pattern = pattern; + let Pattern::Identifier(name) = pattern else { + panic!("Expected identifier pattern"); + }; + assert_eq!("foo", name.to_string()); + } + + #[test] + fn parse_global_no_value() { + let src = " + global foo; + ^^^ + "; + let (src, span) = get_source_with_error_span(src); + let (_, errors) = parse_program(&src); + let reason = get_single_error_reason(&errors, span); + assert!(matches!(reason, ParserErrorReason::GlobalWithoutValue)); + } + + #[test] + fn parse_global_no_semicolon() { + let src = " + global foo = 1 + ^ + "; + 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"); + } +} 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 new file mode 100644 index 00000000000..9215aec2742 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/impls.rs @@ -0,0 +1,564 @@ +use noirc_errors::Span; + +use crate::{ + ast::{ + Documented, Expression, ExpressionKind, GenericTypeArgs, Ident, ItemVisibility, + NoirFunction, NoirTraitImpl, Path, TraitImplItem, TraitImplItemKind, TypeImpl, + UnresolvedGeneric, UnresolvedType, UnresolvedTypeData, + }, + parser::{labels::ParsingRuleLabel, ParserErrorReason}, + token::{Keyword, Token}, +}; + +use super::{parse_many::without_separator, Parser}; + +pub(crate) enum Impl { + Impl(TypeImpl), + TraitImpl(NoirTraitImpl), +} + +impl<'a> Parser<'a> { + /// Impl + /// = TypeImpl + /// | TraitImpl + pub(crate) fn parse_impl(&mut self) -> Impl { + let generics = self.parse_generics(); + + let type_span_start = self.current_token_span; + let object_type = self.parse_type_or_error(); + let type_span = self.span_since(type_span_start); + + if self.eat_keyword(Keyword::For) { + if let UnresolvedTypeData::Named(trait_name, trait_generics, _) = object_type.typ { + return Impl::TraitImpl(self.parse_trait_impl( + generics, + trait_generics, + trait_name, + )); + } else { + self.push_error( + ParserErrorReason::ExpectedTrait { found: object_type.typ.to_string() }, + self.current_token_span, + ); + + // Error, but we continue parsing the type and assume this is going to be a regular type impl + self.parse_type(); + }; + } + + self.parse_type_impl(object_type, type_span, generics) + } + + /// TypeImpl = 'impl' Generics Type TypeImplBody + fn parse_type_impl( + &mut self, + object_type: UnresolvedType, + type_span: Span, + generics: Vec, + ) -> Impl { + let where_clause = self.parse_where_clause(); + let methods = self.parse_type_impl_body(); + + Impl::Impl(TypeImpl { object_type, type_span, generics, where_clause, methods }) + } + + /// TypeImplBody = '{' TypeImplItem* '}' + /// + /// TypeImplItem = OuterDocComments Attributes Modifiers Function + fn parse_type_impl_body(&mut self) -> Vec<(Documented, Span)> { + if !self.eat_left_brace() { + self.expected_token(Token::LeftBrace); + return Vec::new(); + } + + self.parse_many( + "type impl methods", + without_separator().until(Token::RightBrace), + Self::parse_type_impl_method, + ) + } + + fn parse_type_impl_method(&mut self) -> Option<(Documented, Span)> { + 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 + } + }) + } + + /// TraitImpl = 'impl' Generics Path GenericTypeArgs 'for' Type TraitImplBody + fn parse_trait_impl( + &mut self, + impl_generics: Vec, + trait_generics: GenericTypeArgs, + trait_name: Path, + ) -> NoirTraitImpl { + let object_type = self.parse_type_or_error(); + let where_clause = self.parse_where_clause(); + let items = self.parse_trait_impl_body(); + + NoirTraitImpl { + impl_generics, + trait_name, + trait_generics, + object_type, + where_clause, + items, + } + } + + /// TraitImplBody = '{' TraitImplItem* '}' + fn parse_trait_impl_body(&mut self) -> Vec> { + if !self.eat_left_brace() { + self.expected_token(Token::LeftBrace); + return Vec::new(); + } + + self.parse_many( + "trait impl item", + without_separator().until(Token::RightBrace), + Self::parse_trait_impl_item, + ) + } + + fn parse_trait_impl_item(&mut self) -> Option> { + self.parse_item_in_list(ParsingRuleLabel::TraitImplItem, |parser| { + let start_span = parser.current_token_span; + let doc_comments = parser.parse_outer_doc_comments(); + + if let Some(kind) = parser.parse_trait_impl_item_kind() { + let item = TraitImplItem { kind, span: parser.span_since(start_span) }; + Some(Documented::new(item, doc_comments)) + } else { + None + } + }) + } + + /// TraitImplItem + /// = TraitImplType + /// | TraitImplConstant + /// | TraitImplFunction + fn parse_trait_impl_item_kind(&mut self) -> Option { + if let Some(kind) = self.parse_trait_impl_type() { + return Some(kind); + } + + if let Some(kind) = self.parse_trait_impl_constant() { + return Some(kind); + } + + self.parse_trait_impl_function() + } + + /// TraitImplType = 'type' identifier ( ':' Type )? ';' + fn parse_trait_impl_type(&mut self) -> Option { + if !self.eat_keyword(Keyword::Type) { + return None; + } + + let Some(name) = self.eat_ident() else { + self.expected_identifier(); + self.eat_semicolons(); + return Some(TraitImplItemKind::Type { + name: Ident::default(), + alias: UnresolvedType { typ: UnresolvedTypeData::Error, span: Span::default() }, + }); + }; + + let alias = if self.eat_assign() { + self.parse_type_or_error() + } else { + UnresolvedType { typ: UnresolvedTypeData::Error, span: Span::default() } + }; + + self.eat_semicolons(); + + Some(TraitImplItemKind::Type { name, alias }) + } + + /// TraitImplConstant = 'let' identifier OptionalTypeAnnotation ';' + fn parse_trait_impl_constant(&mut self) -> Option { + if !self.eat_keyword(Keyword::Let) { + return None; + } + + let name = match self.eat_ident() { + Some(name) => name, + None => { + self.expected_identifier(); + Ident::default() + } + }; + + let typ = self.parse_optional_type_annotation(); + + let expr = if self.eat_assign() { + self.parse_expression_or_error() + } else { + self.expected_token(Token::Assign); + Expression { kind: ExpressionKind::Error, span: Span::default() } + }; + + self.eat_semicolons(); + + Some(TraitImplItemKind::Constant(name, typ, expr)) + } + + /// TraitImplFunction = Attributes Modifiers Function + fn parse_trait_impl_function(&mut self) -> Option { + let attributes = self.parse_attributes(); + + let modifiers = self.parse_modifiers( + false, // allow mut + ); + if modifiers.visibility != ItemVisibility::Private { + self.push_error( + ParserErrorReason::TraitImplVisibilityIgnored, + modifiers.visibility_span, + ); + } + + if !self.eat_keyword(Keyword::Fn) { + self.modifiers_not_followed_by_an_item(modifiers); + return None; + } + + let noir_function = self.parse_function( + attributes, + ItemVisibility::Public, + modifiers.comptime.is_some(), + modifiers.unconstrained.is_some(), + true, // allow_self + ); + Some(TraitImplItemKind::Function(noir_function)) + } +} + +#[cfg(test)] +mod tests { + use crate::{ + ast::{ + ItemVisibility, NoirTraitImpl, Pattern, TraitImplItemKind, TypeImpl, UnresolvedTypeData, + }, + parser::{ + parser::{ + parse_program, + tests::{expect_no_errors, get_single_error, get_source_with_error_span}, + }, + ItemKind, + }, + }; + + fn parse_type_impl_no_errors(src: &str) -> TypeImpl { + let (mut module, errors) = parse_program(src); + expect_no_errors(&errors); + assert_eq!(module.items.len(), 1); + let item = module.items.remove(0); + let ItemKind::Impl(type_impl) = item.kind else { + panic!("Expected type impl"); + }; + type_impl + } + + fn parse_trait_impl_no_errors(src: &str) -> NoirTraitImpl { + let (mut module, errors) = parse_program(src); + expect_no_errors(&errors); + assert_eq!(module.items.len(), 1); + let item = module.items.remove(0); + let ItemKind::TraitImpl(noir_trait_impl) = item.kind else { + panic!("Expected trait impl"); + }; + noir_trait_impl + } + + #[test] + fn parse_empty_impl() { + let src = "impl Foo {}"; + let type_impl = parse_type_impl_no_errors(src); + assert_eq!(type_impl.object_type.to_string(), "Foo"); + assert!(type_impl.generics.is_empty()); + assert!(type_impl.methods.is_empty()); + } + + #[test] + fn parse_empty_impl_with_generics() { + let src = "impl Foo {}"; + let type_impl = parse_type_impl_no_errors(src); + assert_eq!(type_impl.object_type.to_string(), "Foo"); + assert_eq!(type_impl.generics.len(), 2); + assert!(type_impl.methods.is_empty()); + } + + #[test] + fn parse_impl_with_methods() { + let src = "impl Foo { unconstrained fn foo() {} pub comptime fn bar() {} }"; + let mut type_impl = parse_type_impl_no_errors(src); + assert_eq!(type_impl.object_type.to_string(), "Foo"); + assert_eq!(type_impl.methods.len(), 2); + + let (method, _) = type_impl.methods.remove(0); + let method = method.item; + assert_eq!(method.def.name.to_string(), "foo"); + assert!(method.def.is_unconstrained); + assert!(!method.def.is_comptime); + assert_eq!(method.def.visibility, ItemVisibility::Private); + + let (method, _) = type_impl.methods.remove(0); + let method = method.item; + assert_eq!(method.def.name.to_string(), "bar"); + assert!(method.def.is_comptime); + assert_eq!(method.def.visibility, ItemVisibility::Public); + } + + #[test] + fn parse_impl_with_attribute_on_method() { + let src = " + impl Foo { + #[something] + fn foo(self) {} + } + "; + let type_impl = parse_type_impl_no_errors(src); + let attributes = type_impl.methods[0].0.item.attributes(); + assert_eq!(attributes.secondary.len(), 1); + } + + #[test] + fn parse_impl_with_self_argument() { + let src = "impl Foo { fn foo(self) {} }"; + let mut type_impl = parse_type_impl_no_errors(src); + assert_eq!(type_impl.methods.len(), 1); + + let (method, _) = type_impl.methods.remove(0); + let mut method = method.item; + assert_eq!(method.def.name.to_string(), "foo"); + assert_eq!(method.def.parameters.len(), 1); + + let param = method.def.parameters.remove(0); + let Pattern::Identifier(name) = param.pattern else { + panic!("Expected identifier pattern"); + }; + assert_eq!(name.to_string(), "self"); + assert_eq!(param.typ.to_string(), "Self"); + } + + #[test] + fn parse_impl_with_mut_self_argument() { + let src = "impl Foo { fn foo(mut self) {} }"; + let mut type_impl = parse_type_impl_no_errors(src); + assert_eq!(type_impl.methods.len(), 1); + + let (method, _) = type_impl.methods.remove(0); + let mut method = method.item; + assert_eq!(method.def.name.to_string(), "foo"); + assert_eq!(method.def.parameters.len(), 1); + + let param = method.def.parameters.remove(0); + let Pattern::Mutable(pattern, _, true) = param.pattern else { + panic!("Expected mutable pattern"); + }; + let pattern: &Pattern = &pattern; + let Pattern::Identifier(name) = pattern else { + panic!("Expected identifier pattern"); + }; + assert_eq!(name.to_string(), "self"); + assert_eq!(param.typ.to_string(), "Self"); + } + + #[test] + fn parse_impl_with_reference_mut_self_argument() { + let src = "impl Foo { fn foo(&mut self) {} }"; + let mut type_impl = parse_type_impl_no_errors(src); + assert_eq!(type_impl.methods.len(), 1); + + let (method, _) = type_impl.methods.remove(0); + let mut method = method.item; + assert_eq!(method.def.name.to_string(), "foo"); + assert_eq!(method.def.parameters.len(), 1); + + let param = method.def.parameters.remove(0); + let Pattern::Identifier(name) = param.pattern else { + panic!("Expected identifier pattern"); + }; + assert_eq!(name.to_string(), "self"); + assert_eq!(param.typ.to_string(), "&mut Self"); + } + + #[test] + fn parse_impl_with_self_argument_followed_by_type() { + let src = "impl Foo { fn foo(self: Foo) {} }"; + let mut type_impl = parse_type_impl_no_errors(src); + assert_eq!(type_impl.methods.len(), 1); + + let (method, _) = type_impl.methods.remove(0); + let mut method = method.item; + assert_eq!(method.def.name.to_string(), "foo"); + assert_eq!(method.def.parameters.len(), 1); + + let param = method.def.parameters.remove(0); + let Pattern::Identifier(name) = param.pattern else { + panic!("Expected identifier pattern"); + }; + assert_eq!(name.to_string(), "self"); + assert_eq!(param.typ.to_string(), "Foo"); + } + + #[test] + fn parse_empty_impl_missing_right_brace() { + let src = "impl Foo {"; + let (module, errors) = parse_program(src); + assert_eq!(errors.len(), 1); + assert_eq!(module.items.len(), 1); + let item = &module.items[0]; + let ItemKind::Impl(type_impl) = &item.kind else { + panic!("Expected type impl"); + }; + assert_eq!(type_impl.object_type.to_string(), "Foo"); + } + + #[test] + fn parse_empty_impl_incorrect_body() { + let src = "impl Foo { hello fn foo() {} }"; + let (module, errors) = parse_program(src); + assert_eq!(errors.len(), 1); + assert_eq!(module.items.len(), 1); + let item = &module.items[0]; + let ItemKind::Impl(type_impl) = &item.kind else { + panic!("Expected type impl"); + }; + assert_eq!(type_impl.object_type.to_string(), "Foo"); + assert_eq!(type_impl.methods.len(), 1); + } + + #[test] + fn parse_empty_trait_impl() { + let src = "impl Foo for Field {}"; + let trait_impl = parse_trait_impl_no_errors(src); + assert_eq!(trait_impl.trait_name.to_string(), "Foo"); + assert!(matches!(trait_impl.object_type.typ, UnresolvedTypeData::FieldElement)); + assert!(trait_impl.items.is_empty()); + assert!(trait_impl.impl_generics.is_empty()); + } + + #[test] + fn parse_empty_trait_impl_with_generics() { + let src = "impl Foo for Field {}"; + let trait_impl = parse_trait_impl_no_errors(src); + assert_eq!(trait_impl.trait_name.to_string(), "Foo"); + assert!(matches!(trait_impl.object_type.typ, UnresolvedTypeData::FieldElement)); + assert!(trait_impl.items.is_empty()); + assert_eq!(trait_impl.impl_generics.len(), 1); + } + + #[test] + fn parse_trait_impl_with_function() { + let src = "impl Foo for Field { fn foo() {} }"; + let mut trait_impl = parse_trait_impl_no_errors(src); + assert_eq!(trait_impl.trait_name.to_string(), "Foo"); + assert_eq!(trait_impl.items.len(), 1); + + let item = trait_impl.items.remove(0).item; + let TraitImplItemKind::Function(function) = item.kind else { + panic!("Expected function"); + }; + assert_eq!(function.def.name.to_string(), "foo"); + assert_eq!(function.def.visibility, ItemVisibility::Public); + } + + #[test] + fn parse_trait_impl_with_generic_type_args() { + let src = "impl Foo for Field { }"; + let trait_impl = parse_trait_impl_no_errors(src); + assert_eq!(trait_impl.trait_name.to_string(), "Foo"); + assert!(!trait_impl.trait_generics.is_empty()); + } + + #[test] + fn parse_trait_impl_with_type() { + let src = "impl Foo for Field { type Foo = i32; }"; + let mut trait_impl = parse_trait_impl_no_errors(src); + assert_eq!(trait_impl.trait_name.to_string(), "Foo"); + assert_eq!(trait_impl.items.len(), 1); + + let item = trait_impl.items.remove(0).item; + let TraitImplItemKind::Type { name, alias } = item.kind else { + panic!("Expected type"); + }; + assert_eq!(name.to_string(), "Foo"); + assert_eq!(alias.to_string(), "i32"); + } + + #[test] + fn parse_trait_impl_with_let() { + let src = "impl Foo for Field { let x: Field = 1; }"; + let mut trait_impl = parse_trait_impl_no_errors(src); + assert_eq!(trait_impl.trait_name.to_string(), "Foo"); + assert_eq!(trait_impl.items.len(), 1); + + let item = trait_impl.items.remove(0).item; + let TraitImplItemKind::Constant(name, typ, expr) = item.kind else { + panic!("Expected constant"); + }; + assert_eq!(name.to_string(), "x"); + assert_eq!(typ.to_string(), "Field"); + assert_eq!(expr.to_string(), "1"); + } + + #[test] + fn recovers_on_unknown_impl_item() { + let src = " + impl Foo { hello fn foo() {} } + ^^^^^ + "; + let (src, span) = get_source_with_error_span(src); + let (module, errors) = parse_program(&src); + + assert_eq!(module.items.len(), 1); + let item = &module.items[0]; + let ItemKind::Impl(type_impl) = &item.kind else { + panic!("Expected impl"); + }; + assert_eq!(type_impl.methods.len(), 1); + + let error = get_single_error(&errors, span); + assert_eq!(error.to_string(), "Expected a function but found 'hello'"); + } + + #[test] + fn recovers_on_unknown_trait_impl_item() { + let src = " + impl Foo for i32 { hello fn foo() {} } + ^^^^^ + "; + let (src, span) = get_source_with_error_span(src); + let (module, errors) = parse_program(&src); + + assert_eq!(module.items.len(), 1); + let item = &module.items[0]; + let ItemKind::TraitImpl(trait_imp) = &item.kind else { + panic!("Expected trait impl"); + }; + 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'"); + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/infix.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/infix.rs new file mode 100644 index 00000000000..f006923b8a2 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/infix.rs @@ -0,0 +1,193 @@ +use noirc_errors::{Span, Spanned}; + +use crate::{ + ast::{BinaryOpKind, Expression, ExpressionKind, InfixExpression}, + token::Token, +}; + +use super::Parser; + +impl<'a> Parser<'a> { + /// EqualOrNotEqualExpression + /// = OrExpression ( ( '==' | '!=' ) OrExpression )* + pub(super) fn parse_equal_or_not_equal( + &mut self, + allow_constructors: bool, + ) -> Option { + self.parse_infix(allow_constructors, Parser::parse_or, |parser| { + if parser.eat(Token::Equal) { + Some(BinaryOpKind::Equal) + } else if parser.eat(Token::NotEqual) { + Some(BinaryOpKind::NotEqual) + } else { + None + } + }) + } + + /// OrExpression + /// = AndExpression ( '|' AndExpression )* + pub(super) fn parse_or(&mut self, allow_constructors: bool) -> Option { + self.parse_infix(allow_constructors, Parser::parse_and, |parser| { + // Don't parse `x |= ...`, etc. + if parser.next_is(Token::Assign) { + None + } else if parser.eat(Token::Pipe) { + Some(BinaryOpKind::Or) + } else { + None + } + }) + } + + /// AndExpression + /// = XorExpression ( '&' XorExpression )* + pub(super) fn parse_and(&mut self, allow_constructors: bool) -> Option { + self.parse_infix(allow_constructors, Parser::parse_xor, |parser| { + // Don't parse `x |= ...`, etc. + if parser.next_is(Token::Assign) { + None + } else if parser.eat(Token::Ampersand) { + Some(BinaryOpKind::And) + } else { + None + } + }) + } + + /// XorExpression + /// = LessOrGreaterExpression ( '^' LessOrGreaterExpression )* + pub(super) fn parse_xor(&mut self, allow_constructors: bool) -> Option { + self.parse_infix(allow_constructors, Parser::parse_less_or_greater, |parser| { + // Don't parse `x |= ...`, etc. + if parser.next_is(Token::Assign) { + None + } else if parser.eat(Token::Caret) { + Some(BinaryOpKind::Xor) + } else { + None + } + }) + } + + /// LessOrGreaterExpression + /// = ShiftExpression ( ( '<' | '<=' | '>' | '>=' ) ShiftExpression )* + pub(super) fn parse_less_or_greater(&mut self, allow_constructors: bool) -> Option { + self.parse_infix(allow_constructors, Parser::parse_shift, |parser| { + if parser.eat(Token::Less) { + Some(BinaryOpKind::Less) + } else if parser.eat(Token::LessEqual) { + Some(BinaryOpKind::LessEqual) + } else if parser.next_token.token() != &Token::GreaterEqual + && parser.eat(Token::Greater) + { + // Make sure to skip the `>>=` case, as `>>=` is lexed as `> >=`. + Some(BinaryOpKind::Greater) + } else if parser.eat(Token::GreaterEqual) { + Some(BinaryOpKind::GreaterEqual) + } else { + None + } + }) + } + + /// ShiftExpression + /// = AddOrSubtractExpression ( ( '<<' | '>' '>' ) AddOrSubtractExpression )* + pub(super) fn parse_shift(&mut self, allow_constructors: bool) -> Option { + self.parse_infix(allow_constructors, Parser::parse_add_or_subtract, |parser| { + if !parser.next_is(Token::Assign) && parser.eat(Token::ShiftLeft) { + Some(BinaryOpKind::ShiftLeft) + } else if parser.at(Token::Greater) && parser.next_is(Token::Greater) { + // Right-shift (>>) is issued as two separate > tokens by the lexer as this makes it easier + // to parse nested generic types. For normal expressions however, it means we have to manually + // parse two greater-than tokens as a single right-shift here. + parser.bump(); + parser.bump(); + Some(BinaryOpKind::ShiftRight) + } else { + None + } + }) + } + + /// AddOrSubtractExpression + /// = MultiplyOrDivideOrModuloExpression ( ( '+' | '-' ) MultiplyOrDivideOrModuloExpression )* + pub(super) fn parse_add_or_subtract(&mut self, allow_constructors: bool) -> Option { + self.parse_infix(allow_constructors, Parser::parse_multiply_or_divide_or_modulo, |parser| { + if parser.next_is(Token::Assign) { + None + } else if parser.eat(Token::Plus) { + Some(BinaryOpKind::Add) + } else if parser.eat(Token::Minus) { + Some(BinaryOpKind::Subtract) + } else { + None + } + }) + } + + /// MultiplyOrDivideOrModuloExpression + /// = Term ( ( '*' | '/' | '%' ) Term )* + pub(super) fn parse_multiply_or_divide_or_modulo( + &mut self, + allow_constructors: bool, + ) -> Option { + self.parse_infix(allow_constructors, Parser::parse_term, |parser| { + if parser.next_is(Token::Assign) { + None + } else if parser.eat(Token::Star) { + Some(BinaryOpKind::Multiply) + } else if parser.eat(Token::Slash) { + Some(BinaryOpKind::Divide) + } else if parser.eat(Token::Percent) { + Some(BinaryOpKind::Modulo) + } else { + None + } + }) + } + + fn parse_infix( + &mut self, + allow_constructors: bool, + mut next: Next, + mut op: Op, + ) -> Option + where + Next: FnMut(&mut Parser<'a>, bool) -> Option, + Op: FnMut(&mut Parser<'a>) -> Option, + { + let start_span = self.current_token_span; + let mut lhs = next(self, allow_constructors)?; + + loop { + let operator_start_span = self.current_token_span; + let Some(operator) = op(self) else { + break; + }; + let operator = Spanned::from(operator_start_span, operator); + + let Some(rhs) = next(self, allow_constructors) else { + self.push_expected_expression(); + break; + }; + + lhs = self.new_infix_expression(lhs, operator, rhs, start_span); + } + + Some(lhs) + } + + fn new_infix_expression( + &self, + lhs: Expression, + operator: Spanned, + rhs: Expression, + start_span: Span, + ) -> Expression { + let infix_expr = InfixExpression { lhs, operator, rhs }; + let kind = ExpressionKind::Infix(Box::new(infix_expr)); + let span = self.span_since(start_span); + Expression { kind, span } + } +} 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 new file mode 100644 index 00000000000..4fbcd7abac5 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/item.rs @@ -0,0 +1,242 @@ +use crate::{ + parser::{labels::ParsingRuleLabel, Item, ItemKind}, + token::{Keyword, Token}, +}; + +use super::{impls::Impl, parse_many::without_separator, Parser}; + +impl<'a> Parser<'a> { + pub(crate) fn parse_top_level_items(&mut self) -> Vec { + self.parse_module_items( + false, // nested + ) + } + + pub(crate) fn parse_module_items(&mut self, nested: bool) -> Vec { + self.parse_many("items", without_separator(), |parser| { + parser.parse_module_item_in_list(nested) + }) + } + + fn parse_module_item_in_list(&mut self, nested: bool) -> Option { + loop { + // We only break out of the loop on `}` if we are inside a `mod { ..` + if nested && self.at(Token::RightBrace) { + return None; + } + + // We always break on EOF (we don't error because if we are inside `mod { ..` + // the outer parsing logic will error instead) + if self.at_eof() { + return None; + } + + let Some(item) = self.parse_item() else { + // If we couldn't parse an item we check which token we got + match self.token.token() { + Token::RightBrace if nested => { + return None; + } + Token::EOF => return None, + _ => (), + } + + self.expected_label(ParsingRuleLabel::Item); + // We'll try parsing an item starting on the next token + self.bump(); + continue; + }; + + return Some(item); + } + } + + /// Parses an item inside an impl or trait, with good recovery: + /// - If we run into EOF, we error that we expect a '}' + /// - If we can't parse an item and we don't end up in '}', error but try with the next token + pub(super) fn parse_item_in_list( + &mut self, + label: ParsingRuleLabel, + mut f: F, + ) -> Option + where + F: FnMut(&mut Parser<'a>) -> Option, + { + loop { + if self.at_eof() { + self.expected_token(Token::RightBrace); + return None; + } + + let Some(item) = f(self) else { + if !self.at(Token::RightBrace) { + self.expected_label(label.clone()); + + // Try with the next token + self.bump(); + continue; + } + + return None; + }; + + return Some(item); + } + } + + /// Item = OuterDocComments ItemKind + fn parse_item(&mut self) -> Option { + let start_span = self.current_token_span; + let doc_comments = self.parse_outer_doc_comments(); + let kind = self.parse_item_kind()?; + let span = self.span_since(start_span); + + Some(Item { kind, span, doc_comments }) + } + + /// ItemKind + /// = InnerAttribute + /// | Attributes Modifiers + /// ( Use + /// | ModOrContract + /// | Struct + /// | Impl + /// | Trait + /// | Global + /// | TypeAlias + /// | Function + /// ) + fn parse_item_kind(&mut self) -> Option { + if let Some(kind) = self.parse_inner_attribute() { + return Some(ItemKind::InnerAttribute(kind)); + } + + let start_span = self.current_token_span; + let attributes = self.parse_attributes(); + + let modifiers = self.parse_modifiers( + true, // allow mut + ); + + if self.eat_keyword(Keyword::Use) { + self.comptime_mutable_and_unconstrained_not_applicable(modifiers); + + let use_tree = self.parse_use_tree(); + return Some(ItemKind::Import(use_tree, modifiers.visibility)); + } + + if let Some(is_contract) = self.eat_mod_or_contract() { + self.comptime_mutable_and_unconstrained_not_applicable(modifiers); + + return Some(self.parse_mod_or_contract(attributes, is_contract, modifiers.visibility)); + } + + if self.eat_keyword(Keyword::Struct) { + self.comptime_mutable_and_unconstrained_not_applicable(modifiers); + + return Some(ItemKind::Struct(self.parse_struct( + attributes, + modifiers.visibility, + start_span, + ))); + } + + if self.eat_keyword(Keyword::Impl) { + self.comptime_mutable_and_unconstrained_not_applicable(modifiers); + + return Some(match self.parse_impl() { + Impl::Impl(type_impl) => ItemKind::Impl(type_impl), + Impl::TraitImpl(noir_trait_impl) => ItemKind::TraitImpl(noir_trait_impl), + }); + } + + if self.eat_keyword(Keyword::Trait) { + self.comptime_mutable_and_unconstrained_not_applicable(modifiers); + + return Some(ItemKind::Trait(self.parse_trait( + attributes, + modifiers.visibility, + start_span, + ))); + } + + if self.eat_keyword(Keyword::Global) { + self.unconstrained_not_applicable(modifiers); + + return Some(ItemKind::Global( + self.parse_global( + attributes, + modifiers.comptime.is_some(), + modifiers.mutable.is_some(), + ), + modifiers.visibility, + )); + } + + if self.eat_keyword(Keyword::Type) { + self.comptime_mutable_and_unconstrained_not_applicable(modifiers); + + return Some(ItemKind::TypeAlias( + self.parse_type_alias(modifiers.visibility, start_span), + )); + } + + if self.eat_keyword(Keyword::Fn) { + self.mutable_not_applicable(modifiers); + + return Some(ItemKind::Function(self.parse_function( + attributes, + modifiers.visibility, + modifiers.comptime.is_some(), + modifiers.unconstrained.is_some(), + false, // allow_self + ))); + } + + None + } + + fn eat_mod_or_contract(&mut self) -> Option { + if self.eat_keyword(Keyword::Mod) { + Some(false) + } else if self.eat_keyword(Keyword::Contract) { + Some(true) + } else { + None + } + } +} + +#[cfg(test)] +mod tests { + use crate::{ + parse_program, + parser::parser::tests::{get_single_error, get_source_with_error_span}, + }; + + #[test] + fn recovers_on_unknown_item() { + let src = " + fn foo() {} hello fn bar() {} + ^^^^^ + "; + let (src, span) = get_source_with_error_span(src); + 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'"); + } + + #[test] + fn errors_on_eof_in_nested_mod() { + let src = " + mod foo { fn foo() {} + ^ + "; + let (src, span) = get_source_with_error_span(src); + 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"); + } +} 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 new file mode 100644 index 00000000000..5aea5f6a45f --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/item_visibility.rs @@ -0,0 +1,114 @@ +use crate::{ + ast::ItemVisibility, + token::{Keyword, Token}, +}; + +use super::Parser; + +impl<'a> Parser<'a> { + /// ItemVisibility + /// = 'pub' // ItemVisibility::Public + /// | 'pub' '(' 'crate' ')' // ItemVisibility::PublicCrate + /// | nothing // ItemVisibility::Private + pub(super) fn parse_item_visibility(&mut self) -> ItemVisibility { + if !self.eat_keyword(Keyword::Pub) { + return ItemVisibility::Private; + } + + if !self.eat_left_paren() { + // `pub` + return ItemVisibility::Public; + } + + if !self.eat_keyword(Keyword::Crate) { + // `pub(` or `pub()` + self.expected_token(Token::Keyword(Keyword::Crate)); + self.eat_right_paren(); + return ItemVisibility::Public; + } + + self.eat_or_error(Token::RightParen); + + // `pub(crate)`` + ItemVisibility::PublicCrate + } +} + +#[cfg(test)] +mod tests { + use crate::{ + ast::ItemVisibility, + parser::{ + parser::tests::{expect_no_errors, get_single_error, get_source_with_error_span}, + Parser, + }, + }; + + #[test] + fn parses_private_visibility() { + let src = "("; + let mut parser = Parser::for_str(src); + let visibility = parser.parse_item_visibility(); + expect_no_errors(&parser.errors); + assert_eq!(visibility, ItemVisibility::Private); + } + + #[test] + fn parses_public_visibility() { + let src = "pub"; + let mut parser = Parser::for_str(src); + let visibility = parser.parse_item_visibility(); + expect_no_errors(&parser.errors); + assert_eq!(visibility, ItemVisibility::Public); + } + + #[test] + fn parses_public_visibility_unclosed_parentheses() { + let src = " + pub( + ^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + 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"); + } + + #[test] + fn parses_public_visibility_no_crate_after_pub() { + let src = " + pub(hello + ^^^^^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + 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'"); + } + #[test] + fn parses_public_visibility_missing_paren_after_pub_crate() { + let src = " + pub(crate + ^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + 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"); + } + + #[test] + fn parses_public_crate_visibility() { + let src = "pub(crate)"; + let mut parser = Parser::for_str(src); + let visibility = parser.parse_item_visibility(); + expect_no_errors(&parser.errors); + assert_eq!(visibility, ItemVisibility::PublicCrate); + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/lambda.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/lambda.rs new file mode 100644 index 00000000000..a6eeb428621 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/lambda.rs @@ -0,0 +1,58 @@ +use crate::{ + ast::{ExpressionKind, Lambda, Pattern, UnresolvedType}, + parser::labels::ParsingRuleLabel, + token::Token, +}; + +use super::{parse_many::separated_by_comma, Parser}; + +impl<'a> Parser<'a> { + /// Lambda = '|' LambdaParameters? '|' ( '->' Type )? Expression + /// + /// LambdaParameters = LambdaParameter ( ',' LambdaParameter )? ','? + /// + /// LambdaParameter + /// = Pattern OptionalTypeAnnotation + pub(super) fn parse_lambda(&mut self) -> Option { + if !self.eat_pipe() { + return None; + } + + let parameters = self.parse_lambda_parameters(); + let return_type = if self.eat(Token::Arrow) { + self.parse_type_or_error() + } else { + self.unspecified_type_at_previous_token_end() + }; + let body = self.parse_expression_or_error(); + + Some(ExpressionKind::Lambda(Box::new(Lambda { parameters, return_type, body }))) + } + + fn parse_lambda_parameters(&mut self) -> Vec<(Pattern, UnresolvedType)> { + self.parse_many( + "parameters", + separated_by_comma().until(Token::Pipe), + Self::parse_lambda_parameter, + ) + } + + fn parse_lambda_parameter(&mut self) -> Option<(Pattern, UnresolvedType)> { + loop { + let Some(pattern) = self.parse_pattern() else { + self.expected_label(ParsingRuleLabel::Pattern); + + // Let's try with the next token. + self.bump(); + if self.at_eof() { + return None; + } else { + continue; + } + }; + + let typ = self.parse_optional_type_annotation(); + return Some((pattern, typ)); + } + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/lambdas.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/lambdas.rs deleted file mode 100644 index 5ef0b918375..00000000000 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/lambdas.rs +++ /dev/null @@ -1,43 +0,0 @@ -use chumsky::{primitive::just, Parser}; - -use super::{parse_type, pattern}; -use crate::ast::{Expression, ExpressionKind, Lambda, Pattern, UnresolvedType}; -use crate::macros_api::UnresolvedTypeData; -use crate::{ - parser::{labels::ParsingRuleLabel, parameter_name_recovery, parameter_recovery, NoirParser}, - token::Token, -}; - -pub(super) fn lambda<'a>( - expr_parser: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - lambda_parameters() - .delimited_by(just(Token::Pipe), just(Token::Pipe)) - .then(lambda_return_type()) - .then(expr_parser) - .map(|((parameters, return_type), body)| { - ExpressionKind::Lambda(Box::new(Lambda { parameters, return_type, body })) - }) -} - -fn lambda_parameters() -> impl NoirParser> { - let typ = parse_type().recover_via(parameter_recovery()); - let typ = just(Token::Colon).ignore_then(typ); - - let parameter = - pattern().recover_via(parameter_name_recovery()).then(typ.or_not().map_with_span( - |typ, span| typ.unwrap_or(UnresolvedTypeData::Unspecified.with_span(span)), - )); - - parameter - .separated_by(just(Token::Comma)) - .allow_trailing() - .labelled(ParsingRuleLabel::Parameter) -} - -fn lambda_return_type() -> impl NoirParser { - just(Token::Arrow) - .ignore_then(parse_type()) - .or_not() - .map_with_span(|ret, span| ret.unwrap_or(UnresolvedTypeData::Unspecified.with_span(span))) -} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/literals.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/literals.rs deleted file mode 100644 index b25b6acc9e2..00000000000 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/literals.rs +++ /dev/null @@ -1,158 +0,0 @@ -use chumsky::Parser; - -use crate::{ - ast::ExpressionKind, - parser::NoirParser, - token::{Token, TokenKind}, -}; - -use super::primitives::token_kind; - -pub(super) fn literal() -> impl NoirParser { - token_kind(TokenKind::Literal).map(|token| match token { - Token::Int(x) => ExpressionKind::integer(x), - Token::Bool(b) => ExpressionKind::boolean(b), - Token::Str(s) => ExpressionKind::string(s), - Token::RawStr(s, hashes) => ExpressionKind::raw_string(s, hashes), - Token::FmtStr(s) => ExpressionKind::format_string(s), - unexpected => unreachable!("Non-literal {} parsed as a literal", unexpected), - }) -} - -#[cfg(test)] -mod test { - use super::*; - use crate::ast::Literal; - use crate::parser::parser::{ - expression, expression_no_constructors, fresh_statement, term, test_helpers::*, - }; - - fn expr_to_lit(expr: ExpressionKind) -> Literal { - match expr { - ExpressionKind::Literal(literal) => literal, - _ => unreachable!("expected a literal"), - } - } - - #[test] - fn parse_int() { - let int = parse_with(literal(), "5").unwrap(); - let hex = parse_with(literal(), "0x05").unwrap(); - - match (expr_to_lit(int), expr_to_lit(hex)) { - (Literal::Integer(int, false), Literal::Integer(hex, false)) => assert_eq!(int, hex), - _ => unreachable!(), - } - } - - #[test] - fn parse_string() { - let expr = parse_with(literal(), r#""hello""#).unwrap(); - match expr_to_lit(expr) { - Literal::Str(s) => assert_eq!(s, "hello"), - _ => unreachable!(), - }; - } - - #[test] - fn parse_bool() { - let expr_true = parse_with(literal(), "true").unwrap(); - let expr_false = parse_with(literal(), "false").unwrap(); - - match (expr_to_lit(expr_true), expr_to_lit(expr_false)) { - (Literal::Bool(t), Literal::Bool(f)) => { - assert!(t); - assert!(!f); - } - _ => unreachable!(), - }; - } - - #[test] - fn parse_unary() { - parse_all( - term(expression(), expression_no_constructors(expression()), fresh_statement(), true), - vec!["!hello", "-hello", "--hello", "-!hello", "!-hello"], - ); - parse_all_failing( - term(expression(), expression_no_constructors(expression()), fresh_statement(), true), - vec!["+hello", "/hello"], - ); - } - - #[test] - fn parse_raw_string_expr() { - let cases = vec![ - Case { source: r#" r"foo" "#, expect: r#"r"foo""#, errors: 0 }, - Case { source: r##" r#"foo"# "##, expect: r##"r#"foo"#"##, errors: 0 }, - // backslash - Case { source: r#" r"\\" "#, expect: r#"r"\\""#, errors: 0 }, - Case { source: r##" r#"\"# "##, expect: r##"r#"\"#"##, errors: 0 }, - Case { source: r##" r#"\\"# "##, expect: r##"r#"\\"#"##, errors: 0 }, - Case { source: r##" r#"\\\"# "##, expect: r##"r#"\\\"#"##, errors: 0 }, - // escape sequence - Case { - source: r##" r#"\t\n\\t\\n\\\t\\\n\\\\"# "##, - expect: r##"r#"\t\n\\t\\n\\\t\\\n\\\\"#"##, - errors: 0, - }, - Case { source: r##" r#"\\\\\\\\"# "##, expect: r##"r#"\\\\\\\\"#"##, errors: 0 }, - // mismatch - errors: - Case { source: r###" r#"foo"## "###, expect: r##"r#"foo"#"##, errors: 1 }, - Case { source: r##" r##"foo"# "##, expect: "(none)", errors: 2 }, - // mismatch: short: - Case { source: r##" r"foo"# "##, expect: r#"r"foo""#, errors: 1 }, - Case { source: r#" r#"foo" "#, expect: "(none)", errors: 2 }, - // empty string - Case { source: r#"r"""#, expect: r#"r"""#, errors: 0 }, - #[allow(clippy::needless_raw_string_hashes)] - Case { source: r####"r###""###"####, expect: r####"r###""###"####, errors: 0 }, - // miscellaneous - Case { source: r##" r#\"foo\"# "##, expect: "r", errors: 2 }, - Case { source: r#" r\"foo\" "#, expect: "r", errors: 1 }, - Case { source: r##" r##"foo"# "##, expect: "(none)", errors: 2 }, - // missing 'r' letter - Case { source: r##" ##"foo"# "##, expect: r#""foo""#, errors: 2 }, - Case { source: r#" #"foo" "#, expect: "foo", errors: 2 }, - // whitespace - Case { source: r##" r #"foo"# "##, expect: "r", errors: 2 }, - Case { source: r##" r# "foo"# "##, expect: "r", errors: 3 }, - Case { source: r#" r#"foo" # "#, expect: "(none)", errors: 2 }, - // after identifier - Case { source: r##" bar#"foo"# "##, expect: "bar", errors: 2 }, - // nested - Case { - source: r###"r##"foo r#"bar"# r"baz" ### bye"##"###, - expect: r###"r##"foo r#"bar"# r"baz" ### bye"##"###, - errors: 0, - }, - ]; - - check_cases_with_errors(&cases[..], expression()); - } - - #[test] - fn parse_raw_string_lit() { - let lit_cases = vec![ - Case { source: r#" r"foo" "#, expect: r#"r"foo""#, errors: 0 }, - Case { source: r##" r#"foo"# "##, expect: r##"r#"foo"#"##, errors: 0 }, - // backslash - Case { source: r#" r"\\" "#, expect: r#"r"\\""#, errors: 0 }, - Case { source: r##" r#"\"# "##, expect: r##"r#"\"#"##, errors: 0 }, - Case { source: r##" r#"\\"# "##, expect: r##"r#"\\"#"##, errors: 0 }, - Case { source: r##" r#"\\\"# "##, expect: r##"r#"\\\"#"##, errors: 0 }, - // escape sequence - Case { - source: r##" r#"\t\n\\t\\n\\\t\\\n\\\\"# "##, - expect: r##"r#"\t\n\\t\\n\\\t\\\n\\\\"#"##, - errors: 0, - }, - Case { source: r##" r#"\\\\\\\\"# "##, expect: r##"r#"\\\\\\\\"#"##, errors: 0 }, - // mismatch - errors: - Case { source: r###" r#"foo"## "###, expect: r##"r#"foo"#"##, errors: 1 }, - Case { source: r##" r##"foo"# "##, expect: "(none)", errors: 2 }, - ]; - - check_cases_with_errors(&lit_cases[..], literal()); - } -} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/modifiers.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/modifiers.rs new file mode 100644 index 00000000000..a668d3bae6a --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/modifiers.rs @@ -0,0 +1,52 @@ +use noirc_errors::Span; + +use crate::{ast::ItemVisibility, token::Keyword}; + +use super::Parser; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub(crate) struct Modifiers { + pub(crate) visibility: ItemVisibility, + pub(crate) visibility_span: Span, + pub(crate) unconstrained: Option, + pub(crate) comptime: Option, + pub(crate) mutable: Option, +} + +impl<'a> Parser<'a> { + /// Modifiers = ItemVisibility 'unconstrained'? 'comptime'? 'mut'? + /// + /// NOTE: we also allow `unconstrained` before the visibility for backwards compatibility. + /// The formatter will put it after the visibility. + pub(crate) fn parse_modifiers(&mut self, allow_mutable: bool) -> Modifiers { + let unconstrained = if self.eat_keyword(Keyword::Unconstrained) { + Some(self.previous_token_span) + } else { + None + }; + + let start_span = self.current_token_span; + let visibility = self.parse_item_visibility(); + let visibility_span = self.span_since(start_span); + + let unconstrained = if unconstrained.is_none() { + if self.eat_keyword(Keyword::Unconstrained) { + Some(self.previous_token_span) + } else { + None + } + } else { + unconstrained + }; + + let comptime = + if self.eat_keyword(Keyword::Comptime) { Some(self.previous_token_span) } else { None }; + let mutable = if allow_mutable && self.eat_keyword(Keyword::Mut) { + Some(self.previous_token_span) + } else { + None + }; + + Modifiers { visibility, visibility_span, unconstrained, comptime, mutable } + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/module.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/module.rs new file mode 100644 index 00000000000..263338863c0 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/module.rs @@ -0,0 +1,104 @@ +use noirc_errors::Span; + +use crate::{ + ast::{Ident, ItemVisibility, ModuleDeclaration}, + parser::{ItemKind, ParsedSubModule}, + token::{Attribute, Token}, +}; + +use super::Parser; + +impl<'a> Parser<'a> { + /// ModOrContract + /// = ( 'mod' | 'contract' ) identifier ( '{' Module '}' | ';' ) + pub(super) fn parse_mod_or_contract( + &mut self, + attributes: Vec<(Attribute, Span)>, + is_contract: bool, + visibility: ItemVisibility, + ) -> ItemKind { + let outer_attributes = self.validate_secondary_attributes(attributes); + + let Some(ident) = self.eat_ident() else { + self.expected_identifier(); + return ItemKind::ModuleDecl(ModuleDeclaration { + visibility, + ident: Ident::default(), + outer_attributes, + }); + }; + + if self.eat_left_brace() { + let contents = self.parse_module( + true, // nested + ); + self.eat_or_error(Token::RightBrace); + ItemKind::Submodules(ParsedSubModule { + visibility, + name: ident, + contents, + outer_attributes, + is_contract, + }) + } else { + if !self.eat_semicolons() { + self.expected_token(Token::Semicolon); + } + ItemKind::ModuleDecl(ModuleDeclaration { visibility, ident, outer_attributes }) + } + } +} + +#[cfg(test)] +mod tests { + use crate::parser::{ + parser::{parse_program, tests::expect_no_errors}, + ItemKind, + }; + + #[test] + fn parse_module_declaration() { + // TODO: `contract foo;` is parsed correctly but we don't it's considered a module + let src = "mod foo;"; + let (module, errors) = parse_program(src); + expect_no_errors(&errors); + assert_eq!(module.items.len(), 1); + let item = &module.items[0]; + let ItemKind::ModuleDecl(module) = &item.kind else { + panic!("Expected module declaration"); + }; + assert_eq!("foo", module.ident.to_string()); + } + + #[test] + fn parse_submodule() { + let src = "mod foo { mod bar; }"; + let (module, errors) = parse_program(src); + dbg!(&errors); + expect_no_errors(&errors); + assert_eq!(module.items.len(), 1); + let item = &module.items[0]; + let ItemKind::Submodules(parsed_submodule) = &item.kind else { + panic!("Expected submodules declaration"); + }; + assert!(!parsed_submodule.is_contract); + assert_eq!("foo", parsed_submodule.name.to_string()); + assert_eq!(parsed_submodule.contents.items.len(), 1); + } + + #[test] + fn parse_contract() { + let src = "contract foo {}"; + let (module, errors) = parse_program(src); + dbg!(&errors); + expect_no_errors(&errors); + assert_eq!(module.items.len(), 1); + let item = &module.items[0]; + let ItemKind::Submodules(parsed_submodule) = &item.kind else { + panic!("Expected submodules declaration"); + }; + assert!(parsed_submodule.is_contract); + assert_eq!("foo", parsed_submodule.name.to_string()); + assert_eq!(parsed_submodule.contents.items.len(), 0); + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/parse_many.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/parse_many.rs new file mode 100644 index 00000000000..ea4dfe97122 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/parse_many.rs @@ -0,0 +1,108 @@ +use crate::token::Token; + +use super::Parser; + +impl<'a> Parser<'a> { + /// Parses a list of items separated by a token, optionally ending when another token is found. + /// The given function `f` must parse one item (eventually parsing many, if separators are found). + /// If no item is parsed, `f` must report an error and return `None`. + pub(super) fn parse_many( + &mut self, + items: &'static str, + separated_by: SeparatedBy, + f: F, + ) -> Vec + where + F: FnMut(&mut Parser<'a>) -> Option, + { + self.parse_many_return_trailing_separator_if_any(items, separated_by, f).0 + } + + /// Same as parse_many, but returns a bool indicating whether a trailing separator was found. + pub(super) fn parse_many_return_trailing_separator_if_any( + &mut self, + items: &'static str, + separated_by: SeparatedBy, + mut f: F, + ) -> (Vec, bool) + where + F: FnMut(&mut Parser<'a>) -> Option, + { + let mut elements: Vec = Vec::new(); + let mut trailing_separator = false; + loop { + if let Some(end) = &separated_by.until { + if self.eat(end.clone()) { + break; + } + } + + let start_span = self.current_token_span; + let Some(element) = f(self) else { + if let Some(end) = &separated_by.until { + self.eat(end.clone()); + } + break; + }; + + if let Some(separator) = &separated_by.token { + if !trailing_separator && !elements.is_empty() { + self.expected_token_separating_items(separator.clone(), items, start_span); + } + } + + elements.push(element); + + trailing_separator = if let Some(separator) = &separated_by.token { + self.eat(separator.clone()) + } else { + true + }; + + if !trailing_separator && !separated_by.continue_if_separator_is_missing { + if let Some(end) = &separated_by.until { + self.eat(end.clone()); + } + break; + } + } + + (elements, trailing_separator) + } +} + +pub(super) struct SeparatedBy { + pub(super) token: Option, + pub(super) until: Option, + pub(super) continue_if_separator_is_missing: bool, +} + +impl SeparatedBy { + pub(super) fn until(self, token: Token) -> SeparatedBy { + SeparatedBy { until: Some(token), ..self } + } + + pub(super) fn stop_if_separator_is_missing(self) -> SeparatedBy { + SeparatedBy { continue_if_separator_is_missing: false, ..self } + } +} + +pub(super) fn separated_by(token: Token) -> SeparatedBy { + SeparatedBy { token: Some(token), until: None, continue_if_separator_is_missing: true } +} + +pub(super) fn separated_by_comma() -> SeparatedBy { + separated_by(Token::Comma) +} + +pub(super) fn separated_by_comma_until_right_paren() -> SeparatedBy { + separated_by_comma().until(Token::RightParen) +} + +pub(super) fn separated_by_comma_until_right_brace() -> SeparatedBy { + separated_by_comma().until(Token::RightBrace) +} + +pub(super) fn without_separator() -> SeparatedBy { + SeparatedBy { token: None, until: None, continue_if_separator_is_missing: true } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/path.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/path.rs index 1c9c24f5376..99aedc6df89 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/path.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/path.rs @@ -1,171 +1,356 @@ -use crate::ast::{AsTraitPath, Ident, Path, PathKind, PathSegment, TypePath, UnresolvedType}; -use crate::macros_api::ExpressionKind; -use crate::parser::{NoirParser, ParserError, ParserErrorReason}; +use crate::ast::{AsTraitPath, Ident, Path, PathKind, PathSegment, UnresolvedType}; +use crate::parser::ParserErrorReason; use crate::token::{Keyword, Token}; -use chumsky::prelude::*; use noirc_errors::Span; -use super::keyword; -use super::primitives::{ident, path_segment, path_segment_no_turbofish, turbofish}; -use super::types::{generic_type_args, primitive_type}; +use crate::{parser::labels::ParsingRuleLabel, token::TokenKind}; -pub(super) fn path<'a>( - type_parser: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - path_inner(path_segment(type_parser)) -} +use super::Parser; -pub fn path_no_turbofish() -> impl NoirParser { - path_inner(path_segment_no_turbofish()) -} +impl<'a> Parser<'a> { + #[cfg(test)] + pub(crate) fn parse_path_or_error(&mut self) -> Path { + if let Some(path) = self.parse_path() { + path + } else { + self.expected_label(ParsingRuleLabel::Path); -fn path_inner<'a>(segment: impl NoirParser + 'a) -> impl NoirParser + 'a { - let segments = segment - .separated_by(just(Token::DoubleColon)) - .at_least(1) - .then(just(Token::DoubleColon).then_ignore(none_of(Token::LeftBrace).rewind()).or_not()) - .validate(|(path_segments, trailing_colons), span, emit_error| { - if trailing_colons.is_some() { - emit_error(ParserError::with_reason( - ParserErrorReason::ExpectedIdentifierAfterColons, - span, - )); + Path { + segments: Vec::new(), + kind: PathKind::Plain, + span: self.span_at_previous_token_end(), } - path_segments - }); - let make_path = |kind| move |segments, span| Path { segments, kind, span }; - - let prefix = |key| keyword(key).ignore_then(just(Token::DoubleColon)); - let path_kind = - |key, kind| prefix(key).ignore_then(segments.clone()).map_with_span(make_path(kind)); - - choice(( - path_kind(Keyword::Crate, PathKind::Crate), - path_kind(Keyword::Dep, PathKind::Dep), - path_kind(Keyword::Super, PathKind::Super), - segments.map_with_span(make_path(PathKind::Plain)), - )) -} + } + } -/// Parses `::path_segment` -/// These paths only support exactly two segments. -pub(super) fn as_trait_path<'a>( - type_parser: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - just(Token::Less) - .ignore_then(type_parser.clone()) - .then_ignore(keyword(Keyword::As)) - .then(path(type_parser.clone())) - .then(generic_type_args(type_parser)) - .then_ignore(just(Token::Greater)) - .then_ignore(just(Token::DoubleColon)) - .then(ident()) - .map(|(((typ, trait_path), trait_generics), impl_item)| AsTraitPath { - typ, - trait_path, - trait_generics, - impl_item, - }) -} + /// Tries to parse a Path. + /// Note that `crate::`, `super::`, etc., are not valid paths on their own. + /// + /// Path = PathKind identifier Turbofish? ( '::' identifier Turbofish? )* + /// + /// Turbofish = '::' PathGenerics + pub(crate) fn parse_path(&mut self) -> Option { + self.parse_path_impl( + true, // allow turbofish + true, // allow trailing double colon + ) + } -/// Parses `MyType::path_segment` -/// These paths only support exactly two segments. -/// Unlike normal paths `MyType` here can also be a primitive type or interned type -/// in addition to a named type. -pub(super) fn type_path<'a>( - type_parser: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - primitive_type() - .then_ignore(just(Token::DoubleColon)) - .then(ident().or_not()) - .then(turbofish(type_parser)) - .validate(|((typ, item), turbofish), span, emit| { - let turbofish = turbofish.unwrap_or_default(); - let item = if let Some(item) = item { - item - } else { - emit(ParserError::with_reason( - ParserErrorReason::ExpectedIdentifierAfterColons, - span, - )); - Ident::new(String::new(), Span::from(span.end()..span.end())) - }; - ExpressionKind::TypePath(TypePath { typ, item, turbofish }) - }) -} + pub(crate) fn parse_path_no_turbofish_or_error(&mut self) -> Path { + if let Some(path) = self.parse_path_no_turbofish() { + path + } else { + self.expected_label(ParsingRuleLabel::Path); + + Path { + segments: Vec::new(), + kind: PathKind::Plain, + span: self.span_at_previous_token_end(), + } + } + } -fn empty_path() -> impl NoirParser { - let make_path = |kind| move |_, span| Path { segments: Vec::new(), kind, span }; - let path_kind = |key, kind| keyword(key).map_with_span(make_path(kind)); + /// PathNoTurbofish = PathKind identifier ( '::' identifier )* + pub fn parse_path_no_turbofish(&mut self) -> Option { + self.parse_path_impl( + false, // allow turbofish + true, // allow trailing double colon + ) + } - choice((path_kind(Keyword::Crate, PathKind::Crate), path_kind(Keyword::Dep, PathKind::Plain))) -} + pub(super) fn parse_path_impl( + &mut self, + allow_turbofish: bool, + allow_trailing_double_colon: bool, + ) -> Option { + let start_span = self.current_token_span; + + let kind = self.parse_path_kind(); + + let path = self.parse_optional_path_after_kind( + kind, + allow_turbofish, + allow_trailing_double_colon, + start_span, + )?; + if path.segments.is_empty() { + if path.kind != PathKind::Plain { + self.expected_identifier(); + } + None + } else { + Some(path) + } + } + + pub(super) fn parse_optional_path_after_kind( + &mut self, + kind: PathKind, + allow_turbofish: bool, + allow_trailing_double_colon: bool, + start_span: Span, + ) -> Option { + let path = self.parse_path_after_kind( + kind, + allow_turbofish, + allow_trailing_double_colon, + start_span, + ); + + if path.segments.is_empty() && path.kind == PathKind::Plain { + None + } else { + Some(path) + } + } + + /// Parses a path assuming the path's kind (plain, `crate::`, `super::`, etc.) + /// was already parsed. Note that this method always returns a Path, even if it + /// ends up being just `crate::` or an empty path. + pub(super) fn parse_path_after_kind( + &mut self, + kind: PathKind, + allow_turbofish: bool, + allow_trailing_double_colon: bool, + start_span: Span, + ) -> Path { + let mut segments = Vec::new(); + + if self.token.kind() == TokenKind::Ident { + loop { + let ident = self.eat_ident().unwrap(); + let span = ident.span(); + + let generics = if allow_turbofish + && self.at(Token::DoubleColon) + && self.next_is(Token::Less) + { + self.bump(); + self.parse_path_generics(ParserErrorReason::AssociatedTypesNotAllowedInPaths) + } else { + None + }; + + segments.push(PathSegment { ident, generics, span }); + + if self.at(Token::DoubleColon) + && matches!(self.next_token.token(), Token::Ident(..)) + { + // Skip the double colons + self.bump(); + } else { + if allow_trailing_double_colon && self.eat_double_colon() { + self.expected_identifier(); + break; + } + + break; + } + } + } + + Path { segments, kind, span: self.span_since(start_span) } + } + + /// PathGenerics = GenericTypeArgs + pub(super) fn parse_path_generics( + &mut self, + on_named_arg_error: ParserErrorReason, + ) -> Option> { + if self.token.token() != &Token::Less { + return None; + }; -pub(super) fn maybe_empty_path() -> impl NoirParser { - path_no_turbofish().or(empty_path()) + let generics = self.parse_generic_type_args(); + for (name, _typ) in &generics.named_args { + self.push_error(on_named_arg_error.clone(), name.span()); + } + + Some(generics.ordered_args) + } + + /// PathKind + /// | 'crate' '::' + /// | 'dep' '::' + /// | 'super' '::' + /// | nothing + pub(super) fn parse_path_kind(&mut self) -> PathKind { + let kind = if self.eat_keyword(Keyword::Crate) { + PathKind::Crate + } else if self.eat_keyword(Keyword::Dep) { + PathKind::Dep + } else if self.eat_keyword(Keyword::Super) { + PathKind::Super + } else { + PathKind::Plain + }; + if kind != PathKind::Plain { + self.eat_or_error(Token::DoubleColon); + } + kind + } + + /// AsTraitPath = '<' Type 'as' PathNoTurbofish GenericTypeArgs '>' '::' identifier + pub(super) fn parse_as_trait_path(&mut self) -> Option { + if !self.eat_less() { + return None; + } + + let typ = self.parse_type_or_error(); + self.eat_keyword_or_error(Keyword::As); + let trait_path = self.parse_path_no_turbofish_or_error(); + let trait_generics = self.parse_generic_type_args(); + self.eat_or_error(Token::Greater); + self.eat_or_error(Token::DoubleColon); + let impl_item = if let Some(ident) = self.eat_ident() { + ident + } else { + self.expected_identifier(); + Ident::new(String::new(), self.span_at_previous_token_end()) + }; + + Some(AsTraitPath { typ, trait_path, trait_generics, impl_item }) + } } #[cfg(test)] -mod test { - use super::*; - use crate::parser::{ - parse_type, - parser::test_helpers::{parse_all_failing, parse_recover, parse_with}, +mod tests { + + use crate::{ + ast::{Path, PathKind}, + parser::{ + parser::tests::{expect_no_errors, get_single_error, get_source_with_error_span}, + Parser, + }, }; + fn parse_path_no_errors(src: &str) -> Path { + let mut parser = Parser::for_str(src); + let path = parser.parse_path_or_error(); + expect_no_errors(&parser.errors); + path + } + #[test] - fn parse_path() { - let cases = vec![ - ("std", vec!["std"]), - ("std::hash", vec!["std", "hash"]), - ("std::hash::collections", vec!["std", "hash", "collections"]), - ("foo::bar", vec!["foo", "bar"]), - ("crate::std::hash", vec!["std", "hash"]), - ]; - - for (src, expected_segments) in cases { - let path: Path = parse_with(path(parse_type()), src).unwrap(); - for (segment, expected) in path.segments.into_iter().zip(expected_segments) { - assert_eq!(segment.ident.0.contents, expected); - } - } + fn parses_plain_one_segment() { + let src = "foo"; + let path = parse_path_no_errors(src); + assert_eq!(path.kind, PathKind::Plain); + assert_eq!(path.segments.len(), 1); + assert_eq!(path.segments[0].ident.to_string(), "foo"); + assert!(path.segments[0].generics.is_none()); + } - parse_all_failing(path(parse_type()), vec!["std::", "::std", "std::hash::", "foo::1"]); + #[test] + fn parses_plain_two_segments() { + let src = "foo::bar"; + let path = parse_path_no_errors(src); + assert_eq!(path.kind, PathKind::Plain); + assert_eq!(path.segments.len(), 2); + assert_eq!(path.segments[0].ident.to_string(), "foo"); + assert!(path.segments[0].generics.is_none()); + assert_eq!(path.segments[1].ident.to_string(), "bar"); + assert!(path.segments[1].generics.is_none()); } #[test] - fn parse_path_kinds() { - let cases = vec![ - ("std", PathKind::Plain), - ("hash::collections", PathKind::Plain), - ("crate::std::hash", PathKind::Crate), - ("super::foo", PathKind::Super), - ]; - - for (src, expected_path_kind) in cases { - let path = parse_with(path(parse_type()), src).unwrap(); - assert_eq!(path.kind, expected_path_kind); - } + fn parses_crate_two_segments() { + let src = "crate::foo::bar"; + let path = parse_path_no_errors(src); + assert_eq!(path.kind, PathKind::Crate); + assert_eq!(path.segments.len(), 2); + assert_eq!(path.segments[0].ident.to_string(), "foo"); + assert!(path.segments[0].generics.is_none()); + assert_eq!(path.segments[1].ident.to_string(), "bar"); + assert!(path.segments[1].generics.is_none()); + } - parse_all_failing( - path(parse_type()), - vec!["crate", "crate::std::crate", "foo::bar::crate", "foo::dep"], - ); + #[test] + fn parses_super_two_segments() { + let src = "super::foo::bar"; + let path = parse_path_no_errors(src); + assert_eq!(path.kind, PathKind::Super); + assert_eq!(path.segments.len(), 2); + assert_eq!(path.segments[0].ident.to_string(), "foo"); + assert!(path.segments[0].generics.is_none()); + assert_eq!(path.segments[1].ident.to_string(), "bar"); + assert!(path.segments[1].generics.is_none()); } #[test] - fn parse_path_with_trailing_colons() { - let src = "foo::bar::"; + fn parses_dep_two_segments() { + let src = "dep::foo::bar"; + let path = parse_path_no_errors(src); + assert_eq!(path.kind, PathKind::Dep); + assert_eq!(path.segments.len(), 2); + assert_eq!(path.segments[0].ident.to_string(), "foo"); + assert!(path.segments[0].generics.is_none()); + assert_eq!(path.segments[1].ident.to_string(), "bar"); + assert!(path.segments[1].generics.is_none()); + } + + #[test] + fn parses_plain_one_segment_with_trailing_colons() { + let src = "foo::"; + let mut parser = Parser::for_str(src); + let path = parser.parse_path_or_error(); + assert_eq!(path.span.end() as usize, src.len()); + assert_eq!(parser.errors.len(), 1); + assert_eq!(path.kind, PathKind::Plain); + assert_eq!(path.segments.len(), 1); + assert_eq!(path.segments[0].ident.to_string(), "foo"); + assert!(path.segments[0].generics.is_none()); + } - let (path, errors) = parse_recover(path_no_turbofish(), src); - let path = path.unwrap(); + #[test] + fn parses_with_turbofish() { + let src = "foo::::bar"; + let mut path = parse_path_no_errors(src); + assert_eq!(path.kind, PathKind::Plain); assert_eq!(path.segments.len(), 2); - assert_eq!(path.segments[0].ident.0.contents, "foo"); - assert_eq!(path.segments[1].ident.0.contents, "bar"); + assert_eq!(path.segments[0].ident.to_string(), "foo"); + + let generics = path.segments.remove(0).generics; + assert_eq!(generics.unwrap().len(), 2); + + let generics = path.segments.remove(0).generics; + assert!(generics.is_none()); + } + + #[test] + fn parses_path_stops_before_trailing_double_colon() { + let src = "foo::bar::"; + let mut parser = Parser::for_str(src); + let path = parser.parse_path_or_error(); + assert_eq!(path.span.end() as usize, src.len()); + assert_eq!(parser.errors.len(), 1); + assert_eq!(path.to_string(), "foo::bar"); + } + + #[test] + fn parses_path_with_turbofish_stops_before_trailing_double_colon() { + let src = "foo::bar::<1>::"; + let mut parser = Parser::for_str(src); + let path = parser.parse_path_or_error(); + assert_eq!(path.span.end() as usize, src.len()); + assert_eq!(parser.errors.len(), 1); + assert_eq!(path.to_string(), "foo::bar::<1>"); + } + + #[test] + fn errors_on_crate_double_colons() { + let src = " + crate:: + ^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + let path = parser.parse_path(); + assert!(path.is_none()); - assert_eq!(errors.len(), 1); - assert_eq!(errors[0].message, "expected an identifier after ::"); + let error = get_single_error(&parser.errors, span); + assert_eq!(error.to_string(), "Expected an identifier but found end of input"); } } 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 new file mode 100644 index 00000000000..c4dcab55d73 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/pattern.rs @@ -0,0 +1,406 @@ +use noirc_errors::Span; + +use crate::{ + ast::{Ident, Path, Pattern}, + parser::{labels::ParsingRuleLabel, ParserErrorReason}, + token::{Keyword, Token, TokenKind}, +}; + +use super::{ + parse_many::{separated_by_comma_until_right_brace, separated_by_comma_until_right_paren}, + Parser, +}; + +pub(crate) enum PatternOrSelf { + Pattern(Pattern), + SelfPattern(SelfPattern), +} + +/// SelfPattern is guaranteed to be `self`, `&self` or `&mut self` without a colon following it. +pub(crate) struct SelfPattern { + pub(crate) reference: bool, + pub(crate) mutable: bool, +} + +impl<'a> Parser<'a> { + pub(crate) fn parse_pattern_or_error(&mut self) -> Pattern { + if let Some(pattern) = self.parse_pattern() { + return pattern; + } + + self.expected_label(ParsingRuleLabel::Pattern); + Pattern::Identifier(Ident::new(String::new(), self.span_at_previous_token_end())) + } + + /// Pattern + /// = 'mut' PatternNoMut + pub(crate) fn parse_pattern(&mut self) -> Option { + let start_span = self.current_token_span; + let mutable = self.eat_keyword(Keyword::Mut); + self.parse_pattern_after_modifiers(mutable, start_span) + } + + /// PatternOrSelf + /// = Pattern + /// | SelfPattern + pub(crate) fn parse_pattern_or_self(&mut self) -> Option { + let start_span = self.current_token_span; + + if !self.next_is_colon() && self.eat_self() { + return Some(PatternOrSelf::SelfPattern(SelfPattern { + reference: false, + mutable: false, + })); + } + + if self.eat_keyword(Keyword::Mut) { + if !self.next_is_colon() && self.eat_self() { + return Some(PatternOrSelf::SelfPattern(SelfPattern { + reference: false, + mutable: true, + })); + } else { + return Some(PatternOrSelf::Pattern( + self.parse_pattern_after_modifiers(true, start_span)?, + )); + } + } + + if self.at(Token::Ampersand) && self.next_is(Token::Keyword(Keyword::Mut)) { + self.bump(); + self.bump(); + if !self.next_is_colon() && self.eat_self() { + return Some(PatternOrSelf::SelfPattern(SelfPattern { + reference: true, + mutable: true, + })); + } else { + self.push_error( + ParserErrorReason::RefMutCanOnlyBeUsedWithSelf, + self.current_token_span, + ); + return Some(PatternOrSelf::Pattern( + self.parse_pattern_after_modifiers(true, start_span)?, + )); + } + } + + Some(PatternOrSelf::Pattern(self.parse_pattern_after_modifiers(false, start_span)?)) + } + + fn next_is_colon(&self) -> bool { + self.next_is(Token::Colon) + } + + pub(crate) fn parse_pattern_after_modifiers( + &mut self, + mutable: bool, + start_span: Span, + ) -> Option { + let pattern = self.parse_pattern_no_mut()?; + Some(if mutable { + Pattern::Mutable( + Box::new(pattern), + self.span_since(start_span), + false, // is synthesized + ) + } else { + pattern + }) + } + + /// PatternNoMut + /// = InternedPattern + /// | TuplePattern + /// | StructPattern + /// | IdentifierPattern + /// + /// IdentifierPattern = identifier + fn parse_pattern_no_mut(&mut self) -> Option { + let start_span = self.current_token_span; + + if let Some(pattern) = self.parse_interned_pattern() { + return Some(pattern); + } + + if let Some(pattern) = self.parse_tuple_pattern() { + return Some(pattern); + } + + let Some(mut path) = self.parse_path() else { + if self.at_built_in_type() { + self.push_error( + ParserErrorReason::ExpectedPatternButFoundType(self.token.token().clone()), + self.current_token_span, + ); + } + return None; + }; + + if self.eat_left_brace() { + return Some(self.parse_struct_pattern(path, start_span)); + } + + if !path.is_ident() { + self.push_error(ParserErrorReason::InvalidPattern, path.span); + + let ident = path.segments.pop().unwrap().ident; + return Some(Pattern::Identifier(ident)); + } + + let ident = path.segments.remove(0).ident; + Some(Pattern::Identifier(ident)) + } + + /// InternedPattern = interned_pattern + fn parse_interned_pattern(&mut self) -> Option { + let Some(token) = self.eat_kind(TokenKind::InternedPattern) else { + return None; + }; + + match token.into_token() { + Token::InternedPattern(pattern) => { + Some(Pattern::Interned(pattern, self.previous_token_span)) + } + _ => unreachable!(), + } + } + + /// TuplePattern = '(' PatternList? ')' + /// + /// PatternList = Pattern ( ',' Pattern )* ','? + fn parse_tuple_pattern(&mut self) -> Option { + let start_span = self.current_token_span; + + if !self.eat_left_paren() { + return None; + } + + let patterns = self.parse_many( + "tuple elements", + separated_by_comma_until_right_paren(), + Self::parse_tuple_pattern_element, + ); + + Some(Pattern::Tuple(patterns, self.span_since(start_span))) + } + + fn parse_tuple_pattern_element(&mut self) -> Option { + if let Some(pattern) = self.parse_pattern() { + Some(pattern) + } else { + self.expected_label(ParsingRuleLabel::Pattern); + None + } + } + + /// StructPattern = Path '{' StructPatternFields? '}' + /// + /// StructPatternFields = StructPatternField ( ',' StructPatternField )? ','? + /// + /// StructPatternField = identifier ( ':' Pattern )? + fn parse_struct_pattern(&mut self, path: Path, start_span: Span) -> Pattern { + let fields = self.parse_many( + "struct fields", + separated_by_comma_until_right_brace(), + Self::parse_struct_pattern_field, + ); + + Pattern::Struct(path, fields, self.span_since(start_span)) + } + + fn parse_struct_pattern_field(&mut self) -> Option<(Ident, Pattern)> { + let Some(ident) = self.eat_ident() else { + self.expected_identifier(); + return None; + }; + + 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)) + }) + } + + fn at_built_in_type(&self) -> bool { + matches!( + self.token.token(), + Token::Bool(..) + | Token::IntType(..) + | Token::Keyword(Keyword::Bool) + | Token::Keyword(Keyword::CtString) + | Token::Keyword(Keyword::Expr) + | Token::Keyword(Keyword::Field) + | Token::Keyword(Keyword::FunctionDefinition) + | Token::Keyword(Keyword::Module) + | Token::Keyword(Keyword::Quoted) + | Token::Keyword(Keyword::StructDefinition) + | Token::Keyword(Keyword::TraitConstraint) + | Token::Keyword(Keyword::TraitDefinition) + | Token::Keyword(Keyword::TraitImpl) + | Token::Keyword(Keyword::TypedExpr) + | Token::Keyword(Keyword::TypeType) + | Token::Keyword(Keyword::UnresolvedType) + ) + } +} + +#[cfg(test)] +mod tests { + + use crate::{ + ast::Pattern, + parser::{ + parser::tests::{ + expect_no_errors, get_single_error, get_single_error_reason, + get_source_with_error_span, + }, + Parser, ParserErrorReason, + }, + token::{Keyword, Token}, + }; + + fn parse_pattern_no_errors(src: &str) -> Pattern { + let mut parser = Parser::for_str(src); + let pattern = parser.parse_pattern_or_error(); + expect_no_errors(&parser.errors); + pattern + } + + #[test] + fn parses_identifier_pattern() { + let src = "foo"; + let pattern = parse_pattern_no_errors(src); + let Pattern::Identifier(ident) = pattern else { panic!("Expected an identifier pattern") }; + assert_eq!(ident.to_string(), "foo"); + } + + #[test] + fn parses_mutable_pattern() { + let src = "mut foo"; + let pattern = parse_pattern_no_errors(src); + let Pattern::Mutable(pattern, _, _) = pattern else { panic!("Expected a mutable pattern") }; + let pattern: &Pattern = &pattern; + let Pattern::Identifier(ident) = pattern else { panic!("Expected an identifier pattern") }; + assert_eq!(ident.to_string(), "foo"); + } + + #[test] + fn parses_tuple_pattern() { + let src = "(foo, bar)"; + let pattern = parse_pattern_no_errors(src); + let Pattern::Tuple(mut patterns, _) = pattern else { panic!("Expected a tuple pattern") }; + assert_eq!(patterns.len(), 2); + + let pattern = patterns.remove(0); + let Pattern::Identifier(ident) = pattern else { panic!("Expected an identifier pattern") }; + assert_eq!(ident.to_string(), "foo"); + + let pattern = patterns.remove(0); + let Pattern::Identifier(ident) = pattern else { panic!("Expected an identifier pattern") }; + assert_eq!(ident.to_string(), "bar"); + } + + #[test] + fn parses_unclosed_tuple_pattern() { + let src = "(foo,"; + let mut parser = Parser::for_str(src); + let pattern = parser.parse_pattern_or_error(); + assert_eq!(parser.errors.len(), 1); + let Pattern::Tuple(patterns, _) = pattern else { panic!("Expected a tuple pattern") }; + assert_eq!(patterns.len(), 1); + } + + #[test] + fn parses_struct_pattern_no_fields() { + let src = "foo::Bar {}"; + let mut parser = Parser::for_str(src); + let pattern = parser.parse_pattern_or_error(); + expect_no_errors(&parser.errors); + let Pattern::Struct(path, patterns, _) = pattern else { + panic!("Expected a struct pattern") + }; + assert_eq!(path.to_string(), "foo::Bar"); + assert!(patterns.is_empty()); + } + + #[test] + fn parses_struct_pattern() { + let src = "foo::Bar { x: one, y }"; + let pattern = parse_pattern_no_errors(src); + 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_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"; + let mut parser = Parser::for_str(src); + let pattern = parser.parse_pattern_or_error(); + assert_eq!(parser.errors.len(), 1); + let Pattern::Struct(path, _, _) = pattern else { panic!("Expected a struct pattern") }; + assert_eq!(path.to_string(), "foo::Bar"); + } + + #[test] + fn errors_on_reserved_type() { + let src = " + Field + ^^^^^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + let pattern = parser.parse_pattern(); + assert!(pattern.is_none()); + + let reason = get_single_error_reason(&parser.errors, span); + assert!(matches!( + reason, + ParserErrorReason::ExpectedPatternButFoundType(Token::Keyword(Keyword::Field)) + )); + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/primitives.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/primitives.rs deleted file mode 100644 index 7fcca89f70c..00000000000 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/primitives.rs +++ /dev/null @@ -1,166 +0,0 @@ -use chumsky::prelude::*; - -use crate::ast::{ExpressionKind, GenericTypeArgs, Ident, PathSegment, UnaryOp}; -use crate::macros_api::{StatementKind, UnresolvedType}; -use crate::parser::ParserErrorReason; -use crate::{ - parser::{labels::ParsingRuleLabel, ExprParser, NoirParser, ParserError}, - token::{Keyword, Token, TokenKind}, -}; - -use super::path::{path, path_no_turbofish}; -use super::types::required_generic_type_args; - -/// This parser always parses no input and fails -pub(super) fn nothing() -> impl NoirParser { - one_of([]).map(|_| unreachable!("parser should always error")) -} - -pub(super) fn keyword(keyword: Keyword) -> impl NoirParser { - just(Token::Keyword(keyword)) -} - -pub(super) fn token_kind(token_kind: TokenKind) -> impl NoirParser { - filter_map(move |span, found: Token| { - if found.kind() == token_kind { - Ok(found) - } else { - Err(ParserError::expected_label( - ParsingRuleLabel::TokenKind(token_kind.clone()), - found, - span, - )) - } - }) -} - -pub(super) fn path_segment<'a>( - type_parser: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - ident().then(turbofish(type_parser)).validate(|(ident, generics), span, emit| { - if generics.as_ref().map_or(false, |generics| !generics.named_args.is_empty()) { - let reason = ParserErrorReason::AssociatedTypesNotAllowedInPaths; - emit(ParserError::with_reason(reason, span)); - } - - let generics = generics.map(|generics| generics.ordered_args); - PathSegment { ident, generics, span } - }) -} - -pub(super) fn path_segment_no_turbofish() -> impl NoirParser { - ident().map(PathSegment::from) -} - -pub(super) fn ident() -> impl NoirParser { - token_kind(TokenKind::Ident).map_with_span(Ident::from_token) -} - -// Right-shift (>>) is issued as two separate > tokens by the lexer as this makes it easier -// to parse nested generic types. For normal expressions however, it means we have to manually -// parse two greater-than tokens as a single right-shift here. -pub(super) fn right_shift_operator() -> impl NoirParser { - just(Token::Greater).then(just(Token::Greater)).to(Token::ShiftRight) -} - -pub(super) fn not

(term_parser: P) -> impl NoirParser -where - P: ExprParser, -{ - just(Token::Bang).ignore_then(term_parser).map(|rhs| ExpressionKind::prefix(UnaryOp::Not, rhs)) -} - -pub(super) fn negation

(term_parser: P) -> impl NoirParser -where - P: ExprParser, -{ - just(Token::Minus) - .ignore_then(term_parser) - .map(|rhs| ExpressionKind::prefix(UnaryOp::Minus, rhs)) -} - -pub(super) fn mutable_reference

(term_parser: P) -> impl NoirParser -where - P: ExprParser, -{ - just(Token::Ampersand) - .ignore_then(keyword(Keyword::Mut)) - .ignore_then(term_parser) - .map(|rhs| ExpressionKind::prefix(UnaryOp::MutableReference, rhs)) -} - -pub(super) fn dereference

(term_parser: P) -> impl NoirParser -where - P: ExprParser, -{ - just(Token::Star) - .ignore_then(term_parser) - .map(|rhs| ExpressionKind::prefix(UnaryOp::Dereference { implicitly_added: false }, rhs)) -} - -pub(super) fn turbofish<'a>( - type_parser: impl NoirParser + 'a, -) -> impl NoirParser> + 'a { - just(Token::DoubleColon).ignore_then(required_generic_type_args(type_parser)).or_not() -} - -pub(super) fn variable() -> impl NoirParser { - path(super::parse_type()).map(ExpressionKind::Variable) -} - -pub(super) fn variable_no_turbofish() -> impl NoirParser { - path_no_turbofish().map(ExpressionKind::Variable) -} - -pub(super) fn macro_quote_marker() -> impl NoirParser { - token_kind(TokenKind::UnquoteMarker).map(|token| match token { - Token::UnquoteMarker(expr_id) => ExpressionKind::Resolved(expr_id), - other => unreachable!("Non-unquote-marker parsed as an unquote marker: {other:?}"), - }) -} - -pub(super) fn interned_expr() -> impl NoirParser { - token_kind(TokenKind::InternedExpr).map(|token| match token { - Token::InternedExpr(id) => ExpressionKind::Interned(id), - _ => unreachable!("token_kind(InternedExpr) guarantees we parse an interned expr"), - }) -} - -pub(super) fn interned_statement() -> impl NoirParser { - token_kind(TokenKind::InternedStatement).map(|token| match token { - Token::InternedStatement(id) => StatementKind::Interned(id), - _ => { - unreachable!("token_kind(InternedStatement) guarantees we parse an interned statement") - } - }) -} - -// This rule is so that we can re-parse StatementKind::Expression and Semi in -// an expression position (ignoring the semicolon) if needed. -pub(super) fn interned_statement_expr() -> impl NoirParser { - token_kind(TokenKind::InternedStatement).map(|token| match token { - Token::InternedStatement(id) => ExpressionKind::InternedStatement(id), - _ => { - unreachable!("token_kind(InternedStatement) guarantees we parse an interned statement") - } - }) -} - -#[cfg(test)] -mod test { - use crate::parser::parser::{ - expression, expression_no_constructors, fresh_statement, term, test_helpers::*, - }; - - #[test] - fn parse_unary() { - parse_all( - term(expression(), expression_no_constructors(expression()), fresh_statement(), true), - vec!["!hello", "-hello", "--hello", "-!hello", "!-hello"], - ); - parse_all_failing( - term(expression(), expression_no_constructors(expression()), fresh_statement(), true), - vec!["+hello", "/hello"], - ); - } -} 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 new file mode 100644 index 00000000000..ff6117c9348 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/statement.rs @@ -0,0 +1,725 @@ +use noirc_errors::{Span, Spanned}; + +use crate::{ + ast::{ + AssignStatement, BinaryOp, BinaryOpKind, ConstrainKind, ConstrainStatement, Expression, + ExpressionKind, ForBounds, ForLoopStatement, ForRange, Ident, InfixExpression, LValue, + LetStatement, Statement, StatementKind, + }, + parser::{labels::ParsingRuleLabel, ParserErrorReason}, + token::{Attribute, Keyword, Token, TokenKind}, +}; + +use super::Parser; + +impl<'a> Parser<'a> { + pub(crate) fn parse_statement_or_error(&mut self) -> Statement { + if let Some((statement, (_token, _span))) = self.parse_statement() { + statement + } else { + self.expected_label(ParsingRuleLabel::Statement); + Statement { kind: StatementKind::Error, span: self.span_at_previous_token_end() } + } + } + + /// Statement = Attributes StatementKind ';'? + pub(crate) fn parse_statement(&mut self) -> Option<(Statement, (Option, Span))> { + loop { + let attributes = self.parse_attributes(); + let start_span = self.current_token_span; + let kind = self.parse_statement_kind(attributes); + + let (semicolon_token, semicolon_span) = if self.at(Token::Semicolon) { + let token = self.token.clone(); + self.bump(); + let span = token.to_span(); + + (Some(token.into_token()), span) + } else { + (None, self.previous_token_span) + }; + + let span = self.span_since(start_span); + + if let Some(kind) = kind { + let statement = Statement { kind, span }; + return Some((statement, (semicolon_token, semicolon_span))); + } + + self.expected_label(ParsingRuleLabel::Statement); + + if semicolon_token.is_some() || self.at(Token::RightBrace) || self.at_eof() { + return None; + } else { + self.bump(); + } + } + } + + /// StatementKind + /// = BreakStatement + /// | ContinueStatement + /// | ReturnStatement + /// | LetStatement + /// | ConstrainStatement + /// | ComptimeStatement + /// | ForStatement + /// | IfStatement + /// | BlockStatement + /// | AssignStatement + /// | ExpressionStatement + /// + /// BreakStatement = 'break' + /// + /// ContinueStatement = 'continue' + /// + /// ReturnStatement = 'return' Expression? + /// + /// IfStatement = IfExpression + /// + /// BlockStatement = Block + /// + /// AssignStatement = Expression '=' Expression + /// + /// ExpressionStatement = Expression + fn parse_statement_kind( + &mut self, + attributes: Vec<(Attribute, Span)>, + ) -> Option { + let start_span = self.current_token_span; + + if let Some(token) = self.eat_kind(TokenKind::InternedStatement) { + match token.into_token() { + Token::InternedStatement(statement) => { + return Some(StatementKind::Interned(statement)) + } + _ => unreachable!(), + } + } + + if self.eat_keyword(Keyword::Break) { + return Some(StatementKind::Break); + } + + if self.eat_keyword(Keyword::Continue) { + return Some(StatementKind::Continue); + } + + if self.eat_keyword(Keyword::Return) { + self.parse_expression(); + self.push_error(ParserErrorReason::EarlyReturn, self.span_since(start_span)); + return Some(StatementKind::Error); + } + + if self.at_keyword(Keyword::Let) { + let let_statement = self.parse_let_statement(attributes)?; + return Some(StatementKind::Let(let_statement)); + } + + if let Some(constrain) = self.parse_constrain_statement() { + return Some(StatementKind::Constrain(constrain)); + } + + if self.at_keyword(Keyword::Comptime) { + return self.parse_comptime_statement(attributes); + } + + if let Some(for_loop) = self.parse_for() { + return Some(StatementKind::For(for_loop)); + } + + if let Some(kind) = self.parse_if_expr() { + return Some(StatementKind::Expression(Expression { + kind, + span: self.span_since(start_span), + })); + } + + if let Some(block) = self.parse_block() { + return Some(StatementKind::Expression(Expression { + kind: ExpressionKind::Block(block), + span: self.span_since(start_span), + })); + } + + 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)); + self.eat_or_error(Token::Assign); + let expression = self.parse_expression_or_error(); + return Some(StatementKind::Assign(AssignStatement { lvalue, expression })); + } + _ => unreachable!(), + } + } + + let expression = self.parse_expression()?; + + if self.eat_assign() { + if let Some(lvalue) = LValue::from_expression(expression.clone()) { + let expression = self.parse_expression_or_error(); + return Some(StatementKind::Assign(AssignStatement { lvalue, expression })); + } else { + self.push_error( + ParserErrorReason::InvalidLeftHandSideOfAssignment, + expression.span, + ); + } + } + + if let Some(operator) = self.next_is_op_assign() { + if let Some(lvalue) = LValue::from_expression(expression.clone()) { + // Desugar `a = b` to `a = a b`. This relies on the evaluation of `a` having no side effects, + // which is currently enforced by the restricted syntax of LValues. + let infix = InfixExpression { + lhs: expression, + operator, + rhs: self.parse_expression_or_error(), + }; + let expression = Expression::new( + ExpressionKind::Infix(Box::new(infix)), + self.span_since(start_span), + ); + return Some(StatementKind::Assign(AssignStatement { lvalue, expression })); + } else { + self.push_error( + ParserErrorReason::InvalidLeftHandSideOfAssignment, + expression.span, + ); + } + } + + Some(StatementKind::Expression(expression)) + } + + fn next_is_op_assign(&mut self) -> Option { + let start_span = self.current_token_span; + let operator = if self.next_is(Token::Assign) { + match self.token.token() { + Token::Plus => Some(BinaryOpKind::Add), + Token::Minus => Some(BinaryOpKind::Subtract), + Token::Star => Some(BinaryOpKind::Multiply), + Token::Slash => Some(BinaryOpKind::Divide), + Token::Percent => Some(BinaryOpKind::Modulo), + Token::Ampersand => Some(BinaryOpKind::And), + Token::Caret => Some(BinaryOpKind::Xor), + Token::ShiftLeft => Some(BinaryOpKind::ShiftLeft), + Token::Pipe => Some(BinaryOpKind::Or), + _ => None, + } + } else if self.at(Token::Greater) && self.next_is(Token::GreaterEqual) { + // >>= + Some(BinaryOpKind::ShiftRight) + } else { + None + }; + + if let Some(operator) = operator { + self.bump(); + self.bump(); + Some(Spanned::from(self.span_since(start_span), operator)) + } else { + None + } + } + + /// ForStatement = 'for' identifier 'in' ForRange Block + fn parse_for(&mut self) -> Option { + let start_span = self.current_token_span; + + if !self.eat_keyword(Keyword::For) { + return None; + } + + let Some(identifier) = self.eat_ident() else { + self.expected_identifier(); + let identifier = Ident::default(); + return Some(self.empty_for_loop(identifier, start_span)); + }; + + if !self.eat_keyword(Keyword::In) { + self.expected_token(Token::Keyword(Keyword::In)); + return Some(self.empty_for_loop(identifier, start_span)); + } + + let range = self.parse_for_range(); + + let block_start_span = self.current_token_span; + let block = if let Some(block) = self.parse_block() { + Expression { + kind: ExpressionKind::Block(block), + span: self.span_since(block_start_span), + } + } else { + self.expected_token(Token::LeftBrace); + Expression { kind: ExpressionKind::Error, span: self.span_since(block_start_span) } + }; + + Some(ForLoopStatement { identifier, range, block, span: self.span_since(start_span) }) + } + + /// ForRange + /// = ExpressionExceptConstructor + /// | ExpressionExceptConstructor '..' ExpressionExceptConstructor + fn parse_for_range(&mut self) -> ForRange { + let expr = self.parse_expression_except_constructor_or_error(); + + if self.eat(Token::DoubleDot) { + let end = self.parse_expression_except_constructor_or_error(); + ForRange::Range(ForBounds { start: expr, end, inclusive: false }) + } else if self.eat(Token::DoubleDotEqual) { + let end = self.parse_expression_except_constructor_or_error(); + ForRange::Range(ForBounds { start: expr, end, inclusive: true }) + } else { + ForRange::Array(expr) + } + } + + fn empty_for_loop(&mut self, identifier: Ident, start_span: Span) -> ForLoopStatement { + ForLoopStatement { + identifier, + range: ForRange::Array(Expression { + kind: ExpressionKind::Error, + span: Span::default(), + }), + block: Expression { kind: ExpressionKind::Error, span: Span::default() }, + span: self.span_since(start_span), + } + } + + /// ComptimeStatement + /// = ComptimeBlock + /// | ComptimeLet + /// | ComptimeFor + /// + /// ComptimeBlock = 'comptime' Block + /// + /// ComptimeLet = 'comptime' LetStatement + /// + /// ComptimeFor = 'comptime' ForStatement + fn parse_comptime_statement( + &mut self, + attributes: Vec<(Attribute, Span)>, + ) -> Option { + let start_span = self.current_token_span; + + if !self.eat_keyword(Keyword::Comptime) { + return None; + } + + if let Some(kind) = self.parse_comptime_statement_kind(attributes) { + return Some(StatementKind::Comptime(Box::new(Statement { + kind, + span: self.span_since(start_span), + }))); + } + + self.expected_one_of_tokens(&[ + Token::LeftBrace, + Token::Keyword(Keyword::Let), + Token::Keyword(Keyword::For), + ]); + + None + } + + fn parse_comptime_statement_kind( + &mut self, + attributes: Vec<(Attribute, Span)>, + ) -> Option { + let start_span = self.current_token_span; + + if let Some(block) = self.parse_block() { + return Some(StatementKind::Expression(Expression { + kind: ExpressionKind::Block(block), + span: self.span_since(start_span), + })); + } + + if let Some(let_statement) = self.parse_let_statement(attributes) { + return Some(StatementKind::Let(let_statement)); + } + + if let Some(for_loop) = self.parse_for() { + return Some(StatementKind::For(for_loop)); + } + + None + } + + /// LetStatement = 'let' pattern OptionalTypeAnnotation '=' Expression + fn parse_let_statement(&mut self, attributes: Vec<(Attribute, Span)>) -> Option { + if !self.eat_keyword(Keyword::Let) { + return None; + } + + let attributes = self.validate_secondary_attributes(attributes); + let pattern = self.parse_pattern_or_error(); + let r#type = self.parse_optional_type_annotation(); + let expression = if self.eat_assign() { + self.parse_expression_or_error() + } else { + self.expected_token(Token::Assign); + Expression { kind: ExpressionKind::Error, span: self.current_token_span } + }; + + Some(LetStatement { pattern, r#type, expression, attributes, comptime: false }) + } + + /// ConstrainStatement + /// = 'constrain' Expression + /// | 'assert' Arguments + /// | 'assert_eq' Arguments + fn parse_constrain_statement(&mut self) -> Option { + let start_span = self.current_token_span; + let Some(kind) = self.parse_constrain_kind() else { + return None; + }; + + Some(match kind { + ConstrainKind::Assert | ConstrainKind::AssertEq => { + let arguments = self.parse_arguments(); + if arguments.is_none() { + self.expected_token(Token::LeftParen); + } + let arguments = arguments.unwrap_or_default(); + + ConstrainStatement { kind, arguments, span: self.span_since(start_span) } + } + ConstrainKind::Constrain => { + self.push_error(ParserErrorReason::ConstrainDeprecated, self.previous_token_span); + + let expression = self.parse_expression_or_error(); + ConstrainStatement { + kind, + arguments: vec![expression], + span: self.span_since(start_span), + } + } + }) + } + + fn parse_constrain_kind(&mut self) -> Option { + if self.eat_keyword(Keyword::Assert) { + Some(ConstrainKind::Assert) + } else if self.eat_keyword(Keyword::AssertEq) { + Some(ConstrainKind::AssertEq) + } else if self.eat_keyword(Keyword::Constrain) { + Some(ConstrainKind::Constrain) + } else { + None + } + } +} + +#[cfg(test)] +mod tests { + use crate::{ + ast::{ + ConstrainKind, ExpressionKind, ForRange, LValue, Statement, StatementKind, + UnresolvedTypeData, + }, + parser::{ + parser::tests::{ + expect_no_errors, get_single_error, get_single_error_reason, + get_source_with_error_span, + }, + Parser, ParserErrorReason, + }, + }; + + fn parse_statement_no_errors(src: &str) -> Statement { + let mut parser = Parser::for_str(src); + let statement = parser.parse_statement_or_error(); + expect_no_errors(&parser.errors); + statement + } + + #[test] + fn parses_break() { + let src = "break"; + let statement = parse_statement_no_errors(src); + assert!(matches!(statement.kind, StatementKind::Break)); + } + + #[test] + fn parses_continue() { + let src = "continue"; + let statement = parse_statement_no_errors(src); + assert!(matches!(statement.kind, StatementKind::Continue)); + } + + #[test] + fn parses_let_statement_no_type() { + let src = "let x = 1;"; + let statement = parse_statement_no_errors(src); + let StatementKind::Let(let_statement) = statement.kind else { + panic!("Expected let statement"); + }; + assert_eq!(let_statement.pattern.to_string(), "x"); + assert!(matches!(let_statement.r#type.typ, UnresolvedTypeData::Unspecified)); + assert_eq!(let_statement.expression.to_string(), "1"); + assert!(!let_statement.comptime); + } + + #[test] + fn parses_let_statement_with_type() { + let src = "let x: Field = 1;"; + let statement = parse_statement_no_errors(src); + let StatementKind::Let(let_statement) = statement.kind else { + panic!("Expected let statement"); + }; + assert_eq!(let_statement.pattern.to_string(), "x"); + assert_eq!(let_statement.r#type.to_string(), "Field"); + assert_eq!(let_statement.expression.to_string(), "1"); + assert!(!let_statement.comptime); + } + + #[test] + fn parses_assert() { + let src = "assert(true, \"good\")"; + let statement = parse_statement_no_errors(src); + let StatementKind::Constrain(constrain) = statement.kind else { + panic!("Expected constrain statement"); + }; + assert_eq!(constrain.kind, ConstrainKind::Assert); + assert_eq!(constrain.arguments.len(), 2); + } + + #[test] + fn parses_assert_eq() { + let src = "assert_eq(1, 2, \"bad\")"; + let statement = parse_statement_no_errors(src); + let StatementKind::Constrain(constrain) = statement.kind else { + panic!("Expected constrain statement"); + }; + assert_eq!(constrain.kind, ConstrainKind::AssertEq); + assert_eq!(constrain.arguments.len(), 3); + } + + #[test] + fn parses_constrain() { + let src = " + constrain 1 + ^^^^^^^^^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + let statement = parser.parse_statement_or_error(); + let StatementKind::Constrain(constrain) = statement.kind else { + panic!("Expected constrain statement"); + }; + assert_eq!(constrain.kind, ConstrainKind::Constrain); + assert_eq!(constrain.arguments.len(), 1); + + let reason = get_single_error_reason(&parser.errors, span); + assert!(matches!(reason, ParserErrorReason::ConstrainDeprecated)); + } + + #[test] + fn parses_comptime_block() { + let src = "comptime { 1 }"; + let statement = parse_statement_no_errors(src); + let StatementKind::Comptime(statement) = statement.kind else { + panic!("Expected comptime statement"); + }; + let StatementKind::Expression(expr) = statement.kind else { + panic!("Expected expression statement"); + }; + let ExpressionKind::Block(block) = expr.kind else { + panic!("Expected block expression"); + }; + assert_eq!(block.statements.len(), 1); + } + + #[test] + fn parses_comptime_let() { + let src = "comptime let x = 1;"; + let statement = parse_statement_no_errors(src); + let StatementKind::Comptime(statement) = statement.kind else { + panic!("Expected comptime statement"); + }; + let StatementKind::Let(..) = statement.kind else { + panic!("Expected let statement"); + }; + } + + #[test] + fn parses_for_array() { + let src = "for i in x { }"; + let statement = parse_statement_no_errors(src); + let StatementKind::For(for_loop) = statement.kind else { + panic!("Expected for loop"); + }; + assert_eq!(for_loop.identifier.to_string(), "i"); + let ForRange::Array(expr) = for_loop.range else { + panic!("Expected array"); + }; + assert_eq!(expr.to_string(), "x"); + } + + #[test] + fn parses_for_range() { + let src = "for i in 0..10 { }"; + let statement = parse_statement_no_errors(src); + let StatementKind::For(for_loop) = statement.kind else { + panic!("Expected for loop"); + }; + assert_eq!(for_loop.identifier.to_string(), "i"); + let ForRange::Range(bounds) = for_loop.range else { + panic!("Expected range"); + }; + assert_eq!(bounds.start.to_string(), "0"); + assert_eq!(bounds.end.to_string(), "10"); + assert!(!bounds.inclusive); + } + + #[test] + fn parses_for_range_inclusive() { + let src = "for i in 0..=10 { }"; + let statement = parse_statement_no_errors(src); + let StatementKind::For(for_loop) = statement.kind else { + panic!("Expected for loop"); + }; + assert_eq!(for_loop.identifier.to_string(), "i"); + let ForRange::Range(bounds) = for_loop.range else { + panic!("Expected range"); + }; + assert_eq!(bounds.start.to_string(), "0"); + assert_eq!(bounds.end.to_string(), "10"); + assert!(bounds.inclusive); + } + + #[test] + fn parses_comptime_for() { + let src = "comptime for i in x { }"; + let statement = parse_statement_no_errors(src); + let StatementKind::Comptime(statement) = statement.kind else { + panic!("Expected comptime"); + }; + let StatementKind::For(for_loop) = statement.kind else { + panic!("Expected for loop"); + }; + assert_eq!(for_loop.identifier.to_string(), "i"); + assert!(matches!(for_loop.range, ForRange::Array(..))); + } + + #[test] + fn parses_assignment() { + let src = "x = 1"; + let statement = parse_statement_no_errors(src); + let StatementKind::Assign(assign) = statement.kind else { + panic!("Expected assign"); + }; + let LValue::Ident(ident) = assign.lvalue else { + panic!("Expected ident"); + }; + assert_eq!(ident.to_string(), "x"); + assert_eq!(assign.expression.to_string(), "1"); + } + + #[test] + fn parses_assignment_with_parentheses() { + let src = "(x)[0] = 1"; + let statement = parse_statement_no_errors(src); + let StatementKind::Assign(..) = statement.kind else { + panic!("Expected assign"); + }; + } + + #[test] + fn parses_op_assignment() { + let src = "x += 1"; + let statement = parse_statement_no_errors(src); + let StatementKind::Assign(assign) = statement.kind else { + panic!("Expected assign"); + }; + assert_eq!(assign.to_string(), "x = (x + 1)"); + } + + #[test] + fn parses_op_assignment_with_shift_right() { + let src = "x >>= 1"; + let statement = parse_statement_no_errors(src); + let StatementKind::Assign(assign) = statement.kind else { + panic!("Expected assign"); + }; + assert_eq!(assign.to_string(), "x = (x >> 1)"); + } + + #[test] + fn parses_if_statement_followed_by_tuple() { + // This shouldn't be parsed as a call + let src = "{ if 1 { 2 } (3, 4) }"; + let statement = parse_statement_no_errors(src); + let StatementKind::Expression(expr) = statement.kind else { + panic!("Expected expr"); + }; + let ExpressionKind::Block(block) = expr.kind else { + panic!("Expected block"); + }; + assert_eq!(block.statements.len(), 2); + } + + #[test] + fn parses_block_followed_by_tuple() { + // This shouldn't be parsed as a call + let src = "{ { 2 } (3, 4) }"; + let statement = parse_statement_no_errors(src); + let StatementKind::Expression(expr) = statement.kind else { + panic!("Expected expr"); + }; + let ExpressionKind::Block(block) = expr.kind else { + panic!("Expected block"); + }; + assert_eq!(block.statements.len(), 2); + } + + #[test] + fn errors_on_return_statement() { + // This shouldn't be parsed as a call + let src = " + return 1 + ^^^^^^^^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + let statement = parser.parse_statement_or_error(); + assert!(matches!(statement.kind, StatementKind::Error)); + let reason = get_single_error_reason(&parser.errors, span); + assert!(matches!(reason, ParserErrorReason::EarlyReturn)); + } + + #[test] + fn recovers_on_unknown_statement_followed_by_actual_statement() { + let src = " + ] let x = 1; + ^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + 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 ']'"); + } + + #[test] + fn recovers_on_unknown_statement_followed_by_semicolon() { + let src = " ] ;"; + let mut parser = Parser::for_str(src); + let statement = parser.parse_statement(); + assert!(statement.is_none()); + assert_eq!(parser.errors.len(), 2); + } + + #[test] + fn recovers_on_unknown_statement_followed_by_right_brace() { + let src = " ] }"; + let mut parser = Parser::for_str(src); + let statement = parser.parse_statement(); + assert!(statement.is_none()); + assert_eq!(parser.errors.len(), 2); + } +} 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 729f276e9b8..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 @@ -1,84 +1,277 @@ -use chumsky::prelude::*; +use noirc_errors::Span; -use crate::ast::{Documented, NoirStruct, StructField}; -use crate::parser::parser::visibility::item_visibility; use crate::{ - parser::{ - parser::{ - attributes::{attributes, validate_secondary_attributes}, - function, parse_type, - primitives::{ident, keyword}, - }, - NoirParser, TopLevelStatementKind, - }, - token::{Keyword, Token}, + ast::{Documented, Ident, ItemVisibility, NoirStruct, StructField, UnresolvedGenerics}, + parser::ParserErrorReason, + token::{Attribute, SecondaryAttribute, Token}, }; -use super::doc_comments::outer_doc_comments; - -pub(super) fn struct_definition() -> impl NoirParser { - use self::Keyword::Struct; - use Token::*; - - let fields = struct_fields() - .delimited_by(just(LeftBrace), just(RightBrace)) - .recover_with(nested_delimiters( - LeftBrace, - RightBrace, - [(LeftParen, RightParen), (LeftBracket, RightBracket)], - |_| vec![], - )) - .or(just(Semicolon).to(Vec::new())); - - attributes() - .then(item_visibility()) - .then_ignore(keyword(Struct)) - .then(ident()) - .then(function::generics()) - .then(fields) - .validate(|((((attributes, visibility), name), generics), fields), span, emit| { - let attributes = validate_secondary_attributes(attributes, span, emit); - TopLevelStatementKind::Struct(NoirStruct { - name, +use super::{parse_many::separated_by_comma_until_right_brace, Parser}; + +impl<'a> Parser<'a> { + /// Struct = 'struct' identifier Generics '{' StructField* '}' + /// + /// StructField = OuterDocComments identifier ':' Type + pub(crate) fn parse_struct( + &mut self, + attributes: Vec<(Attribute, Span)>, + visibility: ItemVisibility, + start_span: Span, + ) -> NoirStruct { + let attributes = self.validate_secondary_attributes(attributes); + + let Some(name) = self.eat_ident() else { + self.expected_identifier(); + return self.empty_struct( + Ident::default(), attributes, visibility, - generics, - fields, - span, - }) - }) -} + Vec::new(), + start_span, + ); + }; + + let generics = self.parse_generics(); + + if self.eat_semicolons() { + return self.empty_struct(name, attributes, visibility, generics, start_span); + } + + if !self.eat_left_brace() { + self.expected_token(Token::LeftBrace); + return self.empty_struct(name, attributes, visibility, generics, start_span); + } + + let fields = self.parse_many( + "struct fields", + separated_by_comma_until_right_brace(), + Self::parse_struct_field, + ); + + NoirStruct { + name, + attributes, + visibility, + generics, + fields, + span: self.span_since(start_span), + } + } + + 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(); -fn struct_fields() -> impl NoirParser>> { - let field = ident().then_ignore(just(Token::Colon)).then(parse_type()); - let field = outer_doc_comments().then(field).map(|(doc_comments, (name, typ))| { - Documented::new(StructField { name, typ }, doc_comments) - }); - field.separated_by(just(Token::Comma)).allow_trailing() + 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, + self.span_since(doc_comments_start_span), + ); + } + + // Though we do have to stop at EOF + if self.at_eof() { + self.expected_token(Token::RightBrace); + return None; + } + + // Or if we find a right brace + if self.at(Token::RightBrace) { + return None; + } + + self.expected_identifier(); + self.bump(); + } + + self.eat_or_error(Token::Colon); + + let typ = self.parse_type_or_error(); + Some(Documented::new(StructField { visibility, name, typ }, doc_comments)) + } + + fn empty_struct( + &self, + name: Ident, + attributes: Vec, + visibility: ItemVisibility, + generics: UnresolvedGenerics, + start_span: Span, + ) -> NoirStruct { + NoirStruct { + name, + attributes, + visibility, + generics, + fields: Vec::new(), + span: self.span_since(start_span), + } + } } #[cfg(test)] -mod test { - use super::*; - use crate::parser::parser::test_helpers::*; +mod tests { + use crate::{ + ast::{IntegerBitSize, NoirStruct, Signedness, UnresolvedGeneric, UnresolvedTypeData}, + parser::{ + parser::{ + parse_program, + tests::{ + expect_no_errors, get_single_error, get_single_error_reason, + get_source_with_error_span, + }, + }, + ItemKind, ParserErrorReason, + }, + }; + + fn parse_struct_no_errors(src: &str) -> NoirStruct { + let (mut module, errors) = parse_program(src); + expect_no_errors(&errors); + assert_eq!(module.items.len(), 1); + let item = module.items.remove(0); + let ItemKind::Struct(noir_struct) = item.kind else { + panic!("Expected struct"); + }; + noir_struct + } #[test] - fn parse_structs() { - let cases = vec![ - "struct Foo;", - "struct Foo { }", - "struct Bar { ident: Field, }", - "struct Baz { ident: Field, other: Field }", - "#[attribute] struct Baz { ident: Field, other: Field }", - ]; - parse_all(struct_definition(), cases); - - let failing = vec![ - "struct { }", - "struct Foo { bar: pub Field }", - "struct Foo { bar: pub Field }", - "#[oracle(some)] struct Foo { bar: Field }", - ]; - parse_all_failing(struct_definition(), failing); + fn parse_empty_struct() { + let src = "struct Foo {}"; + let noir_struct = parse_struct_no_errors(src); + assert_eq!("Foo", noir_struct.name.to_string()); + assert!(noir_struct.fields.is_empty()); + assert!(noir_struct.generics.is_empty()); + } + + #[test] + fn parse_empty_struct_followed_by_semicolon() { + let src = "struct Foo;"; + let noir_struct = parse_struct_no_errors(src); + assert_eq!("Foo", noir_struct.name.to_string()); + assert!(noir_struct.fields.is_empty()); + assert!(noir_struct.generics.is_empty()); + } + + #[test] + fn parse_empty_struct_with_generics() { + let src = "struct Foo {}"; + let mut noir_struct = parse_struct_no_errors(src); + assert_eq!("Foo", noir_struct.name.to_string()); + assert!(noir_struct.fields.is_empty()); + assert_eq!(noir_struct.generics.len(), 2); + + let generic = noir_struct.generics.remove(0); + let UnresolvedGeneric::Variable(ident) = generic else { + panic!("Expected generic variable"); + }; + assert_eq!("A", ident.to_string()); + + let generic = noir_struct.generics.remove(0); + let UnresolvedGeneric::Numeric { ident, typ } = generic else { + panic!("Expected generic numeric"); + }; + assert_eq!("B", ident.to_string()); + assert_eq!( + typ.typ, + UnresolvedTypeData::Integer(Signedness::Unsigned, IntegerBitSize::ThirtyTwo) + ); + } + + #[test] + fn parse_struct_with_fields() { + let src = "struct Foo { x: i32, y: Field }"; + let mut noir_struct = parse_struct_no_errors(src); + assert_eq!("Foo", noir_struct.name.to_string()); + assert_eq!(noir_struct.fields.len(), 2); + + let field = noir_struct.fields.remove(0).item; + assert_eq!("x", field.name.to_string()); + assert!(matches!( + field.typ.typ, + UnresolvedTypeData::Integer(Signedness::Signed, IntegerBitSize::ThirtyTwo) + )); + + let field = noir_struct.fields.remove(0).item; + assert_eq!("y", field.name.to_string()); + assert!(matches!(field.typ.typ, UnresolvedTypeData::FieldElement)); + } + + #[test] + fn parse_empty_struct_with_doc_comments() { + let src = "/// Hello\nstruct Foo {}"; + let (module, errors) = parse_program(src); + expect_no_errors(&errors); + assert_eq!(module.items.len(), 1); + let item = &module.items[0]; + assert_eq!(item.doc_comments.len(), 1); + let ItemKind::Struct(noir_struct) = &item.kind else { + panic!("Expected struct"); + }; + assert_eq!("Foo", noir_struct.name.to_string()); + } + + #[test] + fn parse_unclosed_struct() { + let src = "struct Foo {"; + let (module, errors) = parse_program(src); + assert_eq!(errors.len(), 1); + assert_eq!(module.items.len(), 1); + let item = &module.items[0]; + let ItemKind::Struct(noir_struct) = &item.kind else { + panic!("Expected struct"); + }; + assert_eq!("Foo", noir_struct.name.to_string()); + } + + #[test] + fn parse_error_no_function_attributes_allowed_on_struct() { + let src = " + #[test] struct Foo {} + ^^^^^^^ + "; + let (src, span) = get_source_with_error_span(src); + let (_, errors) = parse_program(&src); + let reason = get_single_error_reason(&errors, span); + assert!(matches!(reason, ParserErrorReason::NoFunctionAttributesAllowedOnStruct)); + } + + #[test] + fn recovers_on_non_field() { + let src = " + struct Foo { 42 x: i32 } + ^^ + "; + let (src, span) = get_source_with_error_span(src); + let (module, errors) = parse_program(&src); + + assert_eq!(module.items.len(), 1); + let item = &module.items[0]; + let ItemKind::Struct(noir_struct) = &item.kind else { + panic!("Expected struct"); + }; + assert_eq!("Foo", noir_struct.name.to_string()); + 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'"); } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/test_helpers.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/test_helpers.rs deleted file mode 100644 index 83c1e148f0e..00000000000 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/test_helpers.rs +++ /dev/null @@ -1,122 +0,0 @@ -use chumsky::primitive::just; -use chumsky::Parser; -use iter_extended::vecmap; -use noirc_errors::CustomDiagnostic; - -use crate::{ - lexer::Lexer, - parser::{force, NoirParser}, - token::Token, -}; - -pub(crate) fn parse_with(parser: P, program: &str) -> Result> -where - P: NoirParser, -{ - let (tokens, lexer_errors) = Lexer::lex(program); - if !lexer_errors.is_empty() { - return Err(vecmap(&lexer_errors, Into::into)); - } - parser.then_ignore(just(Token::EOF)).parse(tokens).map_err(|errors| vecmap(&errors, Into::into)) -} - -pub(crate) fn parse_recover(parser: P, program: &str) -> (Option, Vec) -where - P: NoirParser, -{ - let (tokens, lexer_errors) = Lexer::lex(program); - let (opt, errs) = parser.then_ignore(force(just(Token::EOF))).parse_recovery(tokens); - - let mut errors = vecmap(&lexer_errors, Into::into); - errors.extend(errs.iter().map(Into::into)); - - (opt, errors) -} - -pub(crate) fn parse_all(parser: P, programs: Vec<&str>) -> Vec -where - P: NoirParser, -{ - vecmap(programs, move |program| { - let message = format!("Failed to parse:\n{program}"); - let (op_t, diagnostics) = parse_recover(&parser, program); - diagnostics.iter().for_each(|diagnostic| { - if diagnostic.is_error() { - panic!("{} with error {}", &message, diagnostic); - } - }); - op_t.expect(&message) - }) -} - -pub(crate) fn parse_all_failing(parser: P, programs: Vec<&str>) -> Vec -where - P: NoirParser, - T: std::fmt::Display, -{ - programs - .into_iter() - .flat_map(|program| match parse_with(&parser, program) { - Ok(expr) => { - unreachable!( - "Expected this input to fail:\n{}\nYet it successfully parsed as:\n{}", - program, expr - ) - } - Err(diagnostics) => { - if diagnostics.iter().all(|diagnostic: &CustomDiagnostic| diagnostic.is_warning()) { - unreachable!( - "Expected at least one error when parsing:\n{}\nYet it successfully parsed without errors:\n", - program - ) - }; - diagnostics - } - }) - .collect() -} - -#[derive(Copy, Clone)] -pub(crate) struct Case { - pub(crate) source: &'static str, - pub(crate) errors: usize, - pub(crate) expect: &'static str, -} - -pub(crate) fn check_cases_with_errors(cases: &[Case], parser: P) -where - P: NoirParser + Clone, - T: std::fmt::Display, -{ - let show_errors = |v| vecmap(&v, ToString::to_string).join("\n"); - - let results = vecmap(cases, |&case| { - let (opt, errors) = parse_recover(parser.clone(), case.source); - let actual = opt.map(|ast| ast.to_string()); - let actual = if let Some(s) = &actual { s.to_string() } else { "(none)".to_string() }; - - let result = ((errors.len(), actual.clone()), (case.errors, case.expect.to_string())); - if result.0 != result.1 { - let num_errors = errors.len(); - let shown_errors = show_errors(errors); - eprintln!( - concat!( - "\nExpected {expected_errors} error(s) and got {num_errors}:", - "\n\n{shown_errors}", - "\n\nFrom input: {src}", - "\nExpected AST: {expected_result}", - "\nActual AST: {actual}\n", - ), - expected_errors = case.errors, - num_errors = num_errors, - shown_errors = shown_errors, - src = case.source, - expected_result = case.expect, - actual = actual, - ); - } - result - }); - - assert_eq!(vecmap(&results, |t| t.0.clone()), vecmap(&results, |t| t.1.clone()),); -} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/tests.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/tests.rs new file mode 100644 index 00000000000..ea8b1fc638d --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/tests.rs @@ -0,0 +1,55 @@ +#![cfg(test)] + +use noirc_errors::Span; + +use crate::parser::{ParserError, ParserErrorReason}; + +pub(super) fn get_source_with_error_span(src: &str) -> (String, Span) { + let mut lines: Vec<&str> = src.trim_end().lines().collect(); + let squiggles_line = lines.pop().expect("Expected at least two lines in src (the last one should have squiggles for the error location)"); + let squiggle_index = squiggles_line + .chars() + .position(|char| char == '^') + .expect("Expected at least one `^` character in the last line of the src"); + let squiggle_length = squiggles_line.len() - squiggle_index; + let last_line = lines.last().expect("Expected at least two lines in src"); + let src = lines.join("\n"); + let span_start = src.len() - last_line.len() + squiggle_index; + let span_end = span_start + squiggle_length; + let span = Span::from(span_start as u32..span_end as u32); + (src, span) +} + +pub(super) fn get_single_error(errors: &[ParserError], expected_span: Span) -> &ParserError { + if errors.is_empty() { + panic!("Expected an error, found none"); + } + + if errors.len() > 1 { + for error in errors { + println!("{}", error); + } + panic!("Expected one error, found {} errors (printed above)", errors.len()); + } + + assert_eq!(errors[0].span(), expected_span); + &errors[0] +} + +pub(super) fn get_single_error_reason( + errors: &[ParserError], + expected_span: Span, +) -> &ParserErrorReason { + get_single_error(errors, expected_span).reason().unwrap() +} + +pub(super) fn expect_no_errors(errors: &[ParserError]) { + if errors.is_empty() { + return; + } + + for error in errors { + println!("{}", error); + } + panic!("Expected no errors, found {} errors (printed above)", errors.len()); +} 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 b95319f6da0..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 @@ -1,343 +1,310 @@ -use chumsky::prelude::*; - -use super::attributes::{attributes, validate_secondary_attributes}; -use super::doc_comments::outer_doc_comments; -use super::function::{function_modifiers, function_return_type}; -use super::path::path_no_turbofish; -use super::visibility::item_visibility; -use super::{ - block, expression, fresh_statement, function, function_declaration_parameters, let_statement, -}; +use noirc_errors::Span; -use crate::ast::{ - Documented, Expression, ItemVisibility, NoirTrait, NoirTraitImpl, TraitBound, TraitImplItem, - TraitImplItemKind, TraitItem, UnresolvedTraitConstraint, UnresolvedType, -}; -use crate::macros_api::Pattern; -use crate::parser::spanned; +use crate::ast::{Documented, ItemVisibility, NoirTrait, Pattern, TraitItem, UnresolvedType}; use crate::{ - parser::{ - ignore_then_commit, parenthesized, parser::primitives::keyword, NoirParser, ParserError, - ParserErrorReason, TopLevelStatementKind, - }, - token::{Keyword, Token}, + ast::{Ident, UnresolvedTypeData}, + parser::{labels::ParsingRuleLabel, ParserErrorReason}, + token::{Attribute, Keyword, SecondaryAttribute, Token}, }; -use super::{generic_type_args, parse_type, primitives::ident}; - -pub(super) fn trait_definition() -> impl NoirParser { - let trait_body_or_error = just(Token::LeftBrace) - .ignore_then(trait_body()) - .then_ignore(just(Token::RightBrace)) - .or_not() - .validate(|items, span, emit| { - if let Some(items) = items { - items - } else { - emit(ParserError::with_reason( - ParserErrorReason::ExpectedLeftBracketOrWhereOrLeftBraceOrArrowAfterTraitName, - span, - )); - vec![] - } - }); - - attributes() - .then(item_visibility()) - .then_ignore(keyword(Keyword::Trait)) - .then(ident()) - .then(function::generics()) - .then(where_clause()) - .then(trait_body_or_error) - .validate( - |(((((attributes, visibility), name), generics), where_clause), items), span, emit| { - let attributes = validate_secondary_attributes(attributes, span, emit); - TopLevelStatementKind::Trait(NoirTrait { - name, - generics, - where_clause, - span, - items, - attributes, - visibility, - }) - }, +use super::parse_many::without_separator; +use super::Parser; + +impl<'a> Parser<'a> { + /// Trait = 'trait' identifier Generics ( ':' TraitBounds )? WhereClause TraitBody + pub(crate) fn parse_trait( + &mut self, + attributes: Vec<(Attribute, Span)>, + visibility: ItemVisibility, + start_span: Span, + ) -> NoirTrait { + let attributes = self.validate_secondary_attributes(attributes); + + let Some(name) = self.eat_ident() else { + self.expected_identifier(); + return empty_trait(attributes, visibility, self.span_since(start_span)); + }; + + 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, + attributes, + visibility, + } + } + + /// TraitBody = '{' ( OuterDocComments TraitItem )* '}' + fn parse_trait_body(&mut self) -> Vec> { + if !self.eat_left_brace() { + self.expected_token(Token::LeftBrace); + return Vec::new(); + } + + self.parse_many( + "trait items", + without_separator().until(Token::RightBrace), + Self::parse_trait_item_in_list, ) -} + } -fn trait_body() -> impl NoirParser>> { - let item = - trait_function_declaration().or(trait_type_declaration()).or(trait_constant_declaration()); - outer_doc_comments() - .then(item) - .map(|(doc_comments, item)| Documented::new(item, doc_comments)) - .repeated() -} + fn parse_trait_item_in_list(&mut self) -> Option> { + self.parse_item_in_list(ParsingRuleLabel::TraitItem, |parser| { + let doc_comments = parser.parse_outer_doc_comments(); + parser.parse_trait_item().map(|item| Documented::new(item, doc_comments)) + }) + } -fn optional_default_value() -> impl NoirParser> { - ignore_then_commit(just(Token::Assign), expression()).or_not() -} + /// TraitItem + /// = TraitType + /// | TraitConstant + /// | TraitFunction + fn parse_trait_item(&mut self) -> Option { + if let Some(item) = self.parse_trait_type() { + return Some(item); + } -fn trait_constant_declaration() -> impl NoirParser { - keyword(Keyword::Let) - .ignore_then(ident()) - .then_ignore(just(Token::Colon)) - .then(parse_type()) - .then(optional_default_value()) - .then_ignore(just(Token::Semicolon)) - .map(|((name, typ), default_value)| TraitItem::Constant { name, typ, default_value }) -} + if let Some(item) = self.parse_trait_constant() { + return Some(item); + } -/// trait_function_declaration: 'fn' ident generics '(' declaration_parameters ')' function_return_type -fn trait_function_declaration() -> impl NoirParser { - let trait_function_body_or_semicolon = - block(fresh_statement()).map(Option::from).or(just(Token::Semicolon).to(Option::None)); - - let trait_function_body_or_semicolon_or_error = - trait_function_body_or_semicolon.or_not().validate(|body, span, emit| { - if let Some(body) = body { - body - } else { - emit(ParserError::with_reason( - ParserErrorReason::ExpectedLeftBraceOrArrowAfterFunctionParameters, - span, - )); - None - } - }); - - function_modifiers() - .then_ignore(keyword(Keyword::Fn)) - .then(ident()) - .then(function::generics()) - .then(parenthesized(function_declaration_parameters())) - .then(function_return_type().map(|(_, typ)| typ)) - .then(where_clause()) - .then(trait_function_body_or_semicolon_or_error) - .map( - |((((((modifiers, name), generics), parameters), return_type), where_clause), body)| { - TraitItem::Function { - name, - generics, - parameters, - return_type, - where_clause, - body, - is_unconstrained: modifiers.0, - visibility: modifiers.1, - is_comptime: modifiers.2, - } - }, - ) -} + if let Some(item) = self.parse_trait_function() { + return Some(item); + } -/// trait_type_declaration: 'type' ident generics -fn trait_type_declaration() -> impl NoirParser { - keyword(Keyword::Type) - .ignore_then(ident()) - .then_ignore(just(Token::Semicolon)) - .map(|name| TraitItem::Type { name }) -} + None + } -/// Parses a trait implementation, implementing a particular trait for a type. -/// This has a similar syntax to `implementation`, but the `for type` clause is required, -/// and an optional `where` clause is also useable. -/// -/// trait_implementation: 'impl' generics ident generic_args for type '{' trait_implementation_body '}' -pub(super) fn trait_implementation() -> impl NoirParser { - let body_or_error = - just(Token::LeftBrace) - .ignore_then(trait_implementation_body()) - .then_ignore(just(Token::RightBrace)) - .or_not() - .validate(|items, span, emit| { - if let Some(items) = items { - items - } else { - emit(ParserError::with_reason( - ParserErrorReason::ExpectedLeftBracketOrWhereOrLeftBraceOrArrowAfterTraitImplForType, - span, - )); + /// TraitType = 'type' identifier ';' + fn parse_trait_type(&mut self) -> Option { + if !self.eat_keyword(Keyword::Type) { + return None; + } - vec![] - } - }); - - keyword(Keyword::Impl) - .ignore_then(function::generics()) - .then(path_no_turbofish()) - .then(generic_type_args(parse_type())) - .then_ignore(keyword(Keyword::For)) - .then(parse_type()) - .then(where_clause()) - .then(body_or_error) - .map(|args| { - let (((other_args, object_type), where_clause), items) = args; - let ((impl_generics, trait_name), trait_generics) = other_args; - TopLevelStatementKind::TraitImpl(NoirTraitImpl { - impl_generics, - trait_name, - trait_generics, - object_type, - items, - where_clause, - }) - }) -} + let name = match self.eat_ident() { + Some(name) => name, + None => { + self.expected_identifier(); + Ident::default() + } + }; + + self.eat_semicolons(); -fn trait_implementation_body() -> impl NoirParser>> { - let function = function::function_definition(true).validate(|mut f, span, emit| { - if f.def().visibility != ItemVisibility::Private { - emit(ParserError::with_reason(ParserErrorReason::TraitImplVisibilityIgnored, span)); + Some(TraitItem::Type { name }) + } + + /// TraitConstant = 'let' identifier ':' Type ( '=' Expression ) ';' + fn parse_trait_constant(&mut self) -> Option { + if !self.eat_keyword(Keyword::Let) { + return None; } - // Trait impl functions are always public - f.def_mut().visibility = ItemVisibility::Public; - TraitImplItemKind::Function(f) - }); - - let alias = keyword(Keyword::Type) - .ignore_then(ident()) - .then_ignore(just(Token::Assign)) - .then(parse_type()) - .then_ignore(just(Token::Semicolon)) - .map(|(name, alias)| TraitImplItemKind::Type { name, alias }); - - let let_statement = let_statement(expression()).then_ignore(just(Token::Semicolon)).try_map( - |((pattern, typ), expr), span| match pattern { - Pattern::Identifier(ident) => Ok(TraitImplItemKind::Constant(ident, typ, expr)), - _ => Err(ParserError::with_reason( - ParserErrorReason::PatternInTraitFunctionParameter, - span, - )), - }, - ); - let item = choice((function, alias, let_statement)); - outer_doc_comments() - .then(spanned(item).map(|(kind, span)| TraitImplItem { kind, span })) - .map(|(doc_comments, item)| Documented::new(item, doc_comments)) - .repeated() -} + let name = match self.eat_ident() { + Some(name) => name, + None => { + self.expected_identifier(); + Ident::default() + } + }; + + let typ = if self.eat_colon() { + self.parse_type_or_error() + } else { + self.expected_token(Token::Colon); + UnresolvedType { typ: UnresolvedTypeData::Unspecified, span: Span::default() } + }; + + let default_value = + if self.eat_assign() { Some(self.parse_expression_or_error()) } else { None }; -pub(super) fn where_clause() -> impl NoirParser> { - struct MultiTraitConstraint { - typ: UnresolvedType, - trait_bounds: Vec, + self.eat_semicolons(); + + Some(TraitItem::Constant { name, typ, default_value }) } - let constraints = parse_type() - .then_ignore(just(Token::Colon)) - .then(trait_bounds()) - .map(|(typ, trait_bounds)| MultiTraitConstraint { typ, trait_bounds }); - - keyword(Keyword::Where) - .ignore_then(constraints.separated_by(just(Token::Comma)).allow_trailing()) - .or_not() - .map(|option| option.unwrap_or_default()) - .map(|x: Vec| { - let mut result: Vec = Vec::new(); - for constraint in x { - for bound in constraint.trait_bounds { - result.push(UnresolvedTraitConstraint { - typ: constraint.typ.clone(), - trait_bound: bound, - }); + /// TraitFunction = Modifiers Function + fn parse_trait_function(&mut self) -> Option { + let modifiers = self.parse_modifiers( + false, // allow mut + ); + + if !self.eat_keyword(Keyword::Fn) { + self.modifiers_not_followed_by_an_item(modifiers); + return None; + } + + let function = self.parse_function_definition_with_optional_body( + true, // allow optional body + true, // allow self + ); + + let parameters = function + .parameters + .into_iter() + .filter_map(|param| { + if let Pattern::Identifier(ident) = param.pattern { + Some((ident, param.typ)) + } else { + self.push_error(ParserErrorReason::InvalidPattern, param.pattern.span()); + None } - } - result + }) + .collect(); + + Some(TraitItem::Function { + is_unconstrained: modifiers.unconstrained.is_some(), + visibility: modifiers.visibility, + is_comptime: modifiers.comptime.is_some(), + name: function.name, + generics: function.generics, + parameters, + return_type: function.return_type, + where_clause: function.where_clause, + body: function.body, }) + } } -fn trait_bounds() -> impl NoirParser> { - trait_bound().separated_by(just(Token::Plus)).at_least(1).allow_trailing() -} - -pub fn trait_bound() -> impl NoirParser { - path_no_turbofish().then(generic_type_args(parse_type())).map(|(trait_path, trait_generics)| { - TraitBound { trait_path, trait_generics, trait_id: None } - }) +fn empty_trait( + attributes: Vec, + visibility: ItemVisibility, + span: Span, +) -> NoirTrait { + NoirTrait { + name: Ident::default(), + generics: Vec::new(), + bounds: Vec::new(), + where_clause: Vec::new(), + span, + items: Vec::new(), + attributes, + visibility, + } } #[cfg(test)] -mod test { - use super::*; - use crate::parser::parser::test_helpers::*; +mod tests { + use crate::{ + ast::{NoirTrait, TraitItem}, + parser::{ + parser::{parse_program, tests::expect_no_errors}, + ItemKind, + }, + }; + + fn parse_trait_no_errors(src: &str) -> NoirTrait { + let (mut module, errors) = parse_program(src); + expect_no_errors(&errors); + assert_eq!(module.items.len(), 1); + let item = module.items.remove(0); + let ItemKind::Trait(noir_trait) = item.kind else { + panic!("Expected trait"); + }; + noir_trait + } #[test] - fn parse_trait() { - parse_all( - trait_definition(), - vec![ - // Empty traits are legal in Rust and sometimes used as a way to whitelist certain types - // for a particular operation. Also known as `tag` or `marker` traits: - // https://stackoverflow.com/questions/71895489/what-is-the-purpose-of-defining-empty-impl-in-rust - "trait Empty {}", - "trait TraitWithDefaultBody { fn foo(self) {} }", - "trait TraitAcceptingMutableRef { fn foo(&mut self); }", - "trait TraitWithTypeBoundOperation { fn identity() -> Self; }", - "trait TraitWithAssociatedType { type Element; fn item(self, index: Field) -> Self::Element; }", - "trait TraitWithAssociatedConstant { let Size: Field; }", - "trait TraitWithAssociatedConstantWithDefaultValue { let Size: Field = 10; }", - "trait GenericTrait { fn elem(&mut self, index: Field) -> T; }", - "trait GenericTraitWithConstraints where T: SomeTrait { fn elem(self, index: Field) -> T; }", - "trait TraitWithMultipleGenericParams where A: SomeTrait, B: AnotherTrait { let Size: Field; fn zero() -> Self; }", - "trait TraitWithMultipleGenericParams where A: SomeTrait, B: AnotherTrait, { let Size: Field; fn zero() -> Self; }", - ], - ); + fn parse_empty_trait() { + let src = "trait Foo {}"; + let noir_trait = parse_trait_no_errors(src); + assert_eq!(noir_trait.name.to_string(), "Foo"); + assert!(noir_trait.generics.is_empty()); + assert!(noir_trait.where_clause.is_empty()); + assert!(noir_trait.items.is_empty()); + } - parse_all_failing( - trait_definition(), - vec!["trait MissingBody", "trait WrongDelimiter { fn foo() -> u8, fn bar() -> u8 }"], - ); + #[test] + fn parse_trait_with_generics() { + let src = "trait Foo {}"; + let noir_trait = parse_trait_no_errors(src); + assert_eq!(noir_trait.name.to_string(), "Foo"); + assert_eq!(noir_trait.generics.len(), 2); + assert!(noir_trait.where_clause.is_empty()); + assert!(noir_trait.items.is_empty()); } #[test] - fn parse_recover_function_without_left_brace_or_semicolon() { - let src = "fn foo(x: i32)"; + fn parse_trait_with_where_clause() { + let src = "trait Foo where A: Z {}"; + let noir_trait = parse_trait_no_errors(src); + assert_eq!(noir_trait.name.to_string(), "Foo"); + assert_eq!(noir_trait.generics.len(), 2); + assert_eq!(noir_trait.where_clause.len(), 1); + assert!(noir_trait.items.is_empty()); + } - let (trait_item, errors) = parse_recover(trait_function_declaration(), src); - assert_eq!(errors.len(), 1); - assert_eq!(errors[0].message, "expected { or -> after function parameters"); + #[test] + fn parse_trait_with_type() { + let src = "trait Foo { type Elem; }"; + let mut noir_trait = parse_trait_no_errors(src); + assert_eq!(noir_trait.items.len(), 1); + + let item = noir_trait.items.remove(0).item; + let TraitItem::Type { name } = item else { + panic!("Expected type"); + }; + assert_eq!(name.to_string(), "Elem"); + } - let Some(TraitItem::Function { name, parameters, body, .. }) = trait_item else { - panic!("Expected to parser trait item as function"); + #[test] + fn parse_trait_with_constant() { + let src = "trait Foo { let x: Field = 1; }"; + let mut noir_trait = parse_trait_no_errors(src); + assert_eq!(noir_trait.items.len(), 1); + + let item = noir_trait.items.remove(0).item; + let TraitItem::Constant { name, typ, default_value } = item else { + panic!("Expected constant"); }; + assert_eq!(name.to_string(), "x"); + assert_eq!(typ.to_string(), "Field"); + assert_eq!(default_value.unwrap().to_string(), "1"); + } - assert_eq!(name.to_string(), "foo"); - assert_eq!(parameters.len(), 1); + #[test] + fn parse_trait_with_function_no_body() { + let src = "trait Foo { fn foo(); }"; + let mut noir_trait = parse_trait_no_errors(src); + assert_eq!(noir_trait.items.len(), 1); + + let item = noir_trait.items.remove(0).item; + let TraitItem::Function { body, .. } = item else { + panic!("Expected function"); + }; assert!(body.is_none()); } #[test] - fn parse_recover_trait_without_body() { - let src = "trait Foo"; - - let (top_level_statement, errors) = parse_recover(trait_definition(), src); - assert_eq!(errors.len(), 1); - assert_eq!(errors[0].message, "expected <, where or { after trait name"); - - let top_level_statement = top_level_statement.unwrap(); - let TopLevelStatementKind::Trait(trait_) = top_level_statement else { - panic!("Expected to parse a trait"); + fn parse_trait_with_function_with_body() { + let src = "trait Foo { fn foo() {} }"; + let mut noir_trait = parse_trait_no_errors(src); + assert_eq!(noir_trait.items.len(), 1); + + let item = noir_trait.items.remove(0).item; + let TraitItem::Function { body, .. } = item else { + panic!("Expected function"); }; - - assert_eq!(trait_.name.to_string(), "Foo"); - assert!(trait_.items.is_empty()); + assert!(body.is_some()); } #[test] - fn parse_recover_trait_impl_without_body() { - let src = "impl Foo for Bar"; + 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); - let (top_level_statement, errors) = parse_recover(trait_implementation(), src); - assert_eq!(errors.len(), 1); - assert_eq!(errors[0].message, "expected <, where or { after trait impl for type"); - - let top_level_statement = top_level_statement.unwrap(); - let TopLevelStatementKind::TraitImpl(trait_impl) = top_level_statement else { - panic!("Expected to parse a trait impl"); - }; + assert_eq!(noir_trait.bounds[0].to_string(), "Bar"); + assert_eq!(noir_trait.bounds[1].to_string(), "Baz"); - assert!(trait_impl.items.is_empty()); + assert_eq!(noir_trait.to_string(), "trait Foo: Bar + Baz {\n}"); } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/type_alias.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/type_alias.rs new file mode 100644 index 00000000000..52815dc3783 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/type_alias.rs @@ -0,0 +1,92 @@ +use noirc_errors::Span; + +use crate::{ + ast::{Ident, ItemVisibility, NoirTypeAlias, UnresolvedType, UnresolvedTypeData}, + token::Token, +}; + +use super::Parser; + +impl<'a> Parser<'a> { + /// TypeAlias = 'type' identifier Generics '=' Type ';' + pub(crate) fn parse_type_alias( + &mut self, + visibility: ItemVisibility, + start_span: Span, + ) -> NoirTypeAlias { + let Some(name) = self.eat_ident() else { + self.expected_identifier(); + return NoirTypeAlias { + visibility, + name: Ident::default(), + generics: Vec::new(), + typ: UnresolvedType { typ: UnresolvedTypeData::Error, span: Span::default() }, + span: start_span, + }; + }; + + let generics = self.parse_generics(); + + if !self.eat_assign() { + self.expected_token(Token::Assign); + + let span = self.span_since(start_span); + self.eat_semicolons(); + + return NoirTypeAlias { + visibility, + name, + generics, + typ: UnresolvedType { typ: UnresolvedTypeData::Error, span: Span::default() }, + span, + }; + } + + let typ = self.parse_type_or_error(); + let span = self.span_since(start_span); + if !self.eat_semicolons() { + self.expected_token(Token::Semicolon); + } + + NoirTypeAlias { visibility, name, generics, typ, span } + } +} + +#[cfg(test)] +mod tests { + use crate::{ + ast::{NoirTypeAlias, UnresolvedTypeData}, + parser::{ + parser::{parse_program, tests::expect_no_errors}, + ItemKind, + }, + }; + + fn parse_type_alias_no_errors(src: &str) -> NoirTypeAlias { + let (mut module, errors) = parse_program(src); + expect_no_errors(&errors); + assert_eq!(module.items.len(), 1); + let item = module.items.remove(0); + let ItemKind::TypeAlias(alias) = item.kind else { + panic!("Expected global"); + }; + alias + } + + #[test] + fn parse_type_alias_no_generics() { + let src = "type Foo = Field;"; + let alias = parse_type_alias_no_errors(src); + assert_eq!("Foo", alias.name.to_string()); + assert!(alias.generics.is_empty()); + assert_eq!(alias.typ.typ, UnresolvedTypeData::FieldElement); + } + + #[test] + fn parse_type_alias_with_generics() { + let src = "type Foo = Field;"; + let alias = parse_type_alias_no_errors(src); + assert_eq!("Foo", alias.name.to_string()); + assert_eq!(alias.generics.len(), 1); + } +} 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 new file mode 100644 index 00000000000..7dd59aedb45 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/type_expression.rs @@ -0,0 +1,620 @@ +use crate::{ + ast::{GenericTypeArgs, UnresolvedType, UnresolvedTypeData, UnresolvedTypeExpression}, + parser::{labels::ParsingRuleLabel, ParserError}, + token::Token, + BinaryTypeOperator, +}; + +use acvm::acir::{AcirField, FieldElement}; +use noirc_errors::Span; + +use super::{parse_many::separated_by_comma_until_right_paren, Parser}; + +impl<'a> Parser<'a> { + /// TypeExpression= AddOrSubtractTypeExpression + pub(crate) fn parse_type_expression( + &mut self, + ) -> Result { + match self.parse_add_or_subtract_type_expression() { + Some(type_expr) => Ok(type_expr), + None => self.expected_type_expression_after_this(), + } + } + + /// AddOrSubtractTypeExpression + /// = MultiplyOrDivideOrModuloTypeExpression ( ( '+' | '-' ) MultiplyOrDivideOrModuloTypeExpression )* + fn parse_add_or_subtract_type_expression(&mut self) -> Option { + let start_span = self.current_token_span; + let lhs = self.parse_multiply_or_divide_or_modulo_type_expression()?; + Some(self.parse_add_or_subtract_type_expression_after_lhs(lhs, start_span)) + } + + fn parse_add_or_subtract_type_expression_after_lhs( + &mut self, + mut lhs: UnresolvedTypeExpression, + start_span: Span, + ) -> UnresolvedTypeExpression { + loop { + let operator = if self.eat(Token::Plus) { + BinaryTypeOperator::Addition + } else if self.eat(Token::Minus) { + BinaryTypeOperator::Subtraction + } else { + break; + }; + + match self.parse_multiply_or_divide_or_modulo_type_expression() { + Some(rhs) => { + let span = self.span_since(start_span); + lhs = UnresolvedTypeExpression::BinaryOperation( + Box::new(lhs), + operator, + Box::new(rhs), + span, + ); + } + None => { + self.push_expected_expression(); + } + } + } + + lhs + } + + /// MultiplyOrDivideOrModuloTypeExpression + /// = TermTypeExpression ( ( '*' | '/' | '%' ) TermTypeExpression )* + fn parse_multiply_or_divide_or_modulo_type_expression( + &mut self, + ) -> Option { + let start_span = self.current_token_span; + let lhs = self.parse_term_type_expression()?; + Some(self.parse_multiply_or_divide_or_modulo_type_expression_after_lhs(lhs, start_span)) + } + + fn parse_multiply_or_divide_or_modulo_type_expression_after_lhs( + &mut self, + mut lhs: UnresolvedTypeExpression, + start_span: Span, + ) -> UnresolvedTypeExpression { + loop { + let operator = if self.eat(Token::Star) { + BinaryTypeOperator::Multiplication + } else if self.eat(Token::Slash) { + BinaryTypeOperator::Division + } else if self.eat(Token::Percent) { + BinaryTypeOperator::Modulo + } else { + break; + }; + + match self.parse_term_type_expression() { + Some(rhs) => { + let span = self.span_since(start_span); + lhs = UnresolvedTypeExpression::BinaryOperation( + Box::new(lhs), + operator, + Box::new(rhs), + span, + ); + } + None => { + self.push_expected_expression(); + break; + } + } + } + + lhs + } + + /// TermTypeExpression + /// = '- TermTypeExpression + /// | AtomTypeExpression + fn parse_term_type_expression(&mut self) -> Option { + let start_span = self.current_token_span; + if self.eat(Token::Minus) { + return match self.parse_term_type_expression() { + Some(rhs) => { + let lhs = UnresolvedTypeExpression::Constant(FieldElement::zero(), start_span); + let op = BinaryTypeOperator::Subtraction; + let span = self.span_since(start_span); + Some(UnresolvedTypeExpression::BinaryOperation( + Box::new(lhs), + op, + Box::new(rhs), + span, + )) + } + None => { + self.push_expected_expression(); + None + } + }; + } + + self.parse_atom_type_expression() + } + + /// AtomTypeExpression + /// = ConstantTypeExpression + /// | VariableTypeExpression + /// | ParenthesizedTypeExpression + fn parse_atom_type_expression(&mut self) -> Option { + if let Some(type_expr) = self.parse_constant_type_expression() { + return Some(type_expr); + } + + if let Some(type_expr) = self.parse_variable_type_expression() { + return Some(type_expr); + } + + if let Some(type_expr) = self.parse_parenthesized_type_expression() { + return Some(type_expr); + } + + None + } + + /// ConstantTypeExpression = int + fn parse_constant_type_expression(&mut self) -> Option { + let Some(int) = self.eat_int() else { + return None; + }; + + Some(UnresolvedTypeExpression::Constant(int, self.previous_token_span)) + } + + /// VariableTypeExpression = Path + fn parse_variable_type_expression(&mut self) -> Option { + let path = self.parse_path()?; + Some(UnresolvedTypeExpression::Variable(path)) + } + + /// ParenthesizedTypeExpression = '(' TypeExpression ')' + fn parse_parenthesized_type_expression(&mut self) -> Option { + // Make sure not to parse `()` as a parenthesized expression + if self.at(Token::LeftParen) && !self.next_is(Token::RightParen) { + self.bump(); + match self.parse_type_expression() { + Ok(type_expr) => { + self.eat_or_error(Token::RightParen); + Some(type_expr) + } + Err(error) => { + self.errors.push(error); + self.eat_right_paren(); + None + } + } + } else { + None + } + } + + /// TypeOrTypeExpression = Type | TypeExpression + pub(crate) fn parse_type_or_type_expression(&mut self) -> Option { + let typ = self.parse_add_or_subtract_type_or_type_expression()?; + let span = typ.span; + + // If we end up with a Variable type expression, make it a Named type (they are equivalent), + // but for testing purposes and simplicity we default to types instead of type expressions. + Some( + if let UnresolvedTypeData::Expression(UnresolvedTypeExpression::Variable(path)) = + typ.typ + { + UnresolvedType { + typ: UnresolvedTypeData::Named(path, GenericTypeArgs::default(), false), + span, + } + } else { + typ + }, + ) + } + + fn parse_add_or_subtract_type_or_type_expression(&mut self) -> Option { + let start_span = self.current_token_span; + let lhs = self.parse_multiply_or_divide_or_modulo_type_or_type_expression()?; + + // If lhs is a type then no operator can follow, so we stop right away + if !type_is_type_expr(&lhs) { + return Some(lhs); + } + + let lhs = type_to_type_expr(lhs).unwrap(); + let lhs = self.parse_add_or_subtract_type_expression_after_lhs(lhs, start_span); + Some(type_expr_to_type(lhs, self.span_since(start_span))) + } + + fn parse_multiply_or_divide_or_modulo_type_or_type_expression( + &mut self, + ) -> Option { + let start_span = self.current_token_span; + let lhs = self.parse_term_type_or_type_expression()?; + + // If lhs is a type then no operator can follow, so we stop right away + if !type_is_type_expr(&lhs) { + return Some(lhs); + } + + let lhs = type_to_type_expr(lhs).unwrap(); + let lhs = + self.parse_multiply_or_divide_or_modulo_type_expression_after_lhs(lhs, start_span); + Some(type_expr_to_type(lhs, self.span_since(start_span))) + } + + fn parse_term_type_or_type_expression(&mut self) -> Option { + let start_span = self.current_token_span; + if self.eat(Token::Minus) { + // 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(FieldElement::zero(), start_span); + let op = BinaryTypeOperator::Subtraction; + let span = self.span_since(start_span); + let type_expr = UnresolvedTypeExpression::BinaryOperation( + Box::new(lhs), + op, + Box::new(rhs), + span, + ); + let typ = UnresolvedTypeData::Expression(type_expr); + Some(UnresolvedType { typ, span }) + } + None => { + self.push_expected_expression(); + None + } + }; + } + + self.parse_atom_type_or_type_expression() + } + + fn parse_atom_type_or_type_expression(&mut self) -> Option { + let start_span = self.current_token_span; + + if let Some(path) = self.parse_path() { + let generics = self.parse_generic_type_args(); + let typ = UnresolvedTypeData::Named(path, generics, false); + let span = self.span_since(start_span); + return Some(UnresolvedType { typ, span }); + } + + if let Some(type_expr) = self.parse_constant_type_expression() { + let typ = UnresolvedTypeData::Expression(type_expr); + let span = self.span_since(start_span); + return Some(UnresolvedType { typ, span }); + } + + if let Some(typ) = self.parse_parenthesized_type_or_type_expression() { + return Some(typ); + } + + self.parse_type() + } + + fn parse_parenthesized_type_or_type_expression(&mut self) -> Option { + let start_span = self.current_token_span; + + if !self.eat_left_paren() { + return None; + } + + if self.eat_right_paren() { + return Some(UnresolvedType { + typ: UnresolvedTypeData::Unit, + span: self.span_since(start_span), + }); + } + + let Some(typ) = self.parse_type_or_type_expression() else { + self.expected_label(ParsingRuleLabel::TypeOrTypeExpression); + return None; + }; + + // If what we just parsed is a type expression then this must be a parenthesized type + // expression (there's no such thing as a tuple of type expressions) + if let UnresolvedTypeData::Expression(type_expr) = typ.typ { + self.eat_or_error(Token::RightParen); + return Some(UnresolvedType { + typ: UnresolvedTypeData::Expression(type_expr), + span: typ.span, + }); + } + + if self.eat_right_paren() { + return Some(UnresolvedType { + typ: UnresolvedTypeData::Parenthesized(Box::new(typ)), + span: self.span_since(start_span), + }); + } + + let comma_after_first_type = self.eat_comma(); + let second_type_span = self.current_token_span; + + let mut types = self.parse_many( + "tuple items", + separated_by_comma_until_right_paren(), + Self::parse_type_in_list, + ); + + if !types.is_empty() && !comma_after_first_type { + self.expected_token_separating_items(Token::Comma, "tuple items", second_type_span); + } + + types.insert(0, typ); + + Some(UnresolvedType { + typ: UnresolvedTypeData::Tuple(types), + span: self.span_since(start_span), + }) + } + + fn expected_type_expression_after_this( + &mut self, + ) -> Result { + Err(ParserError::expected_label( + ParsingRuleLabel::TypeExpression, + self.token.token().clone(), + self.current_token_span, + )) + } +} + +fn type_to_type_expr(typ: UnresolvedType) -> Option { + match typ.typ { + UnresolvedTypeData::Named(var, generics, _) => { + if generics.is_empty() { + Some(UnresolvedTypeExpression::Variable(var)) + } else { + None + } + } + UnresolvedTypeData::Expression(type_expr) => Some(type_expr), + _ => None, + } +} + +fn type_is_type_expr(typ: &UnresolvedType) -> bool { + match &typ.typ { + UnresolvedTypeData::Named(_, generics, _) => generics.is_empty(), + UnresolvedTypeData::Expression(..) => true, + _ => false, + } +} + +fn type_expr_to_type(lhs: UnresolvedTypeExpression, span: Span) -> UnresolvedType { + let lhs = UnresolvedTypeData::Expression(lhs); + UnresolvedType { typ: lhs, span } +} + +#[cfg(test)] +mod tests { + use core::panic; + + use crate::{ + ast::{UnresolvedType, UnresolvedTypeData, UnresolvedTypeExpression}, + parser::{ + parser::tests::{ + expect_no_errors, get_single_error_reason, get_source_with_error_span, + }, + Parser, ParserErrorReason, + }, + token::Token, + BinaryTypeOperator, + }; + + fn parse_type_expression_no_errors(src: &str) -> UnresolvedTypeExpression { + let mut parser = Parser::for_str(src); + let expr = parser.parse_type_expression().unwrap(); + expect_no_errors(&parser.errors); + expr + } + + fn parse_type_or_type_expression_no_errors(src: &str) -> UnresolvedType { + let mut parser = Parser::for_str(src); + let typ = parser.parse_type_or_type_expression().unwrap(); + expect_no_errors(&parser.errors); + typ + } + + #[test] + fn parses_constant_type_expression() { + let src = "42"; + let expr = parse_type_expression_no_errors(src); + let UnresolvedTypeExpression::Constant(n, _) = expr else { + panic!("Expected constant"); + }; + assert_eq!(n, 42_u32.into()); + } + + #[test] + fn parses_variable_type_expression() { + let src = "foo::bar"; + let expr = parse_type_expression_no_errors(src); + let UnresolvedTypeExpression::Variable(path) = expr else { + panic!("Expected path"); + }; + assert_eq!(path.to_string(), "foo::bar"); + } + + #[test] + fn parses_binary_type_expression() { + let src = "1 + 2 * 3 + 4"; + let expr = parse_type_expression_no_errors(src); + let UnresolvedTypeExpression::BinaryOperation(lhs, operator, rhs, _) = expr else { + panic!("Expected binary operation"); + }; + assert_eq!(lhs.to_string(), "(1 + (2 * 3))"); + assert_eq!(operator, BinaryTypeOperator::Addition); + assert_eq!(rhs.to_string(), "4"); + } + + #[test] + fn parses_parenthesized_type_expression() { + let src = "(N)"; + let expr = parse_type_expression_no_errors(src); + let UnresolvedTypeExpression::Variable(path) = expr else { + panic!("Expected variable"); + }; + assert_eq!(path.to_string(), "N"); + } + + #[test] + fn parses_minus_type_expression() { + let src = "-N"; + let expr = parse_type_expression_no_errors(src); + assert_eq!(expr.to_string(), "(0 - N)"); + } + + #[test] + fn parse_type_or_type_expression_constant() { + let src = "42"; + let typ = parse_type_or_type_expression_no_errors(src); + let UnresolvedTypeData::Expression(expr) = typ.typ else { + panic!("Expected expression"); + }; + let UnresolvedTypeExpression::Constant(n, _) = expr else { + panic!("Expected constant"); + }; + assert_eq!(n, 42_u32.into()); + } + + #[test] + fn parse_type_or_type_expression_variable() { + let src = "foo::Bar"; + let typ = parse_type_or_type_expression_no_errors(src); + let UnresolvedTypeData::Named(path, generics, _) = typ.typ else { + panic!("Expected named type"); + }; + assert_eq!(path.to_string(), "foo::Bar"); + assert!(generics.is_empty()); + } + + #[test] + fn parses_type_or_type_expression_binary() { + let src = "1 + 2 * 3 + 4"; + let typ = parse_type_or_type_expression_no_errors(src); + let UnresolvedTypeData::Expression(expr) = typ.typ else { + panic!("Expected expression"); + }; + let UnresolvedTypeExpression::BinaryOperation(lhs, operator, rhs, _) = expr else { + panic!("Expected binary operation"); + }; + assert_eq!(lhs.to_string(), "(1 + (2 * 3))"); + assert_eq!(operator, BinaryTypeOperator::Addition); + assert_eq!(rhs.to_string(), "4"); + } + + #[test] + fn parses_type_or_type_expression_minus() { + let src = "-N"; + let typ = parse_type_or_type_expression_no_errors(src); + let UnresolvedTypeData::Expression(expr) = typ.typ else { + panic!("Expected expression"); + }; + assert_eq!(expr.to_string(), "(0 - N)"); + } + + #[test] + fn parses_type_or_type_expression_unit() { + let src = "()"; + let typ = parse_type_or_type_expression_no_errors(src); + let UnresolvedTypeData::Unit = typ.typ else { + panic!("Expected unit type"); + }; + } + + #[test] + fn parses_type_or_type_expression_parenthesized_type() { + let src = "(Field)"; + let typ = parse_type_or_type_expression_no_errors(src); + let UnresolvedTypeData::Parenthesized(typ) = typ.typ else { + panic!("Expected parenthesized type"); + }; + let UnresolvedTypeData::FieldElement = typ.typ else { + panic!("Expected field type"); + }; + } + + #[test] + fn parses_type_or_type_expression_parenthesized_constant() { + let src = "(1)"; + let typ = parse_type_or_type_expression_no_errors(src); + let UnresolvedTypeData::Expression(expr) = typ.typ else { + panic!("Expected expression type"); + }; + assert_eq!(expr.to_string(), "1"); + } + + #[test] + fn parses_type_or_type_expression_tuple_type() { + let src = "(Field, bool)"; + let typ = parse_type_or_type_expression_no_errors(src); + let UnresolvedTypeData::Tuple(types) = typ.typ else { + panic!("Expected tuple type"); + }; + let UnresolvedTypeData::FieldElement = types[0].typ else { + panic!("Expected field type"); + }; + let UnresolvedTypeData::Bool = types[1].typ else { + panic!("Expected bool type"); + }; + } + + #[test] + fn parses_type_or_type_expression_tuple_type_missing_comma() { + let src = " + (Field bool) + ^^^^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + + let typ = parser.parse_type_or_type_expression().unwrap(); + + let reason = get_single_error_reason(&parser.errors, span); + let ParserErrorReason::ExpectedTokenSeparatingTwoItems { token, items } = reason else { + panic!("Expected a different error"); + }; + assert_eq!(token, &Token::Comma); + assert_eq!(*items, "tuple items"); + + let UnresolvedTypeData::Tuple(types) = typ.typ else { + panic!("Expected tuple type"); + }; + let UnresolvedTypeData::FieldElement = types[0].typ else { + panic!("Expected field type"); + }; + let UnresolvedTypeData::Bool = types[1].typ else { + panic!("Expected bool type"); + }; + } + + #[test] + fn parses_type_or_type_expression_tuple_type_single_element() { + let src = "(Field,)"; + let mut parser = Parser::for_str(src); + let typ = parser.parse_type_or_type_expression().unwrap(); + expect_no_errors(&parser.errors); + let UnresolvedTypeData::Tuple(types) = typ.typ else { + panic!("Expected tuple type"); + }; + assert_eq!(types.len(), 1); + let UnresolvedTypeData::FieldElement = types[0].typ else { + panic!("Expected field type"); + }; + } + + #[test] + fn parses_type_or_type_expression_var_minus_one() { + let src = "N - 1"; + let typ = parse_type_or_type_expression_no_errors(src); + let UnresolvedTypeData::Expression(expr) = typ.typ else { + panic!("Expected expression type"); + }; + assert_eq!(expr.to_string(), "(N - 1)"); + } +} 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 9dd41d1a288..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,412 +1,683 @@ -use super::path::{as_trait_path, path_no_turbofish}; -use super::primitives::{ident, token_kind}; -use super::{ - expression_with_precedence, keyword, nothing, parenthesized, NoirParser, ParserError, - ParserErrorReason, Precedence, -}; -use crate::ast::{ - Expression, GenericTypeArg, GenericTypeArgs, Recoverable, UnresolvedType, UnresolvedTypeData, - UnresolvedTypeExpression, +use acvm::{AcirField, FieldElement}; + +use crate::{ + ast::{UnresolvedType, UnresolvedTypeData, UnresolvedTypeExpression}, + parser::{labels::ParsingRuleLabel, ParserErrorReason}, + token::{Keyword, Token, TokenKind}, + QuotedType, }; -use crate::QuotedType; -use crate::parser::labels::ParsingRuleLabel; -use crate::token::{Keyword, Token, TokenKind}; +use super::{parse_many::separated_by_comma_until_right_paren, Parser}; -use chumsky::prelude::*; -use noirc_errors::Span; +impl<'a> Parser<'a> { + pub(crate) fn parse_type_or_error(&mut self) -> UnresolvedType { + if let Some(typ) = self.parse_type() { + typ + } else { + self.expected_label(ParsingRuleLabel::Type); + self.unspecified_type_at_previous_token_end() + } + } -pub fn parse_type<'a>() -> impl NoirParser + 'a { - recursive(parse_type_inner) -} + pub(crate) fn parse_type(&mut self) -> Option { + let start_span = self.current_token_span; + let typ = self.parse_unresolved_type_data()?; + let span = self.span_since(start_span); + Some(UnresolvedType { typ, span }) + } -pub(super) fn parse_type_inner<'a>( - recursive_type_parser: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - choice(( - primitive_type(), - format_string_type(recursive_type_parser.clone()), - named_type(recursive_type_parser.clone()), - named_trait(recursive_type_parser.clone()), - slice_type(recursive_type_parser.clone()), - array_type(recursive_type_parser.clone()), - parenthesized_type(recursive_type_parser.clone()), - tuple_type(recursive_type_parser.clone()), - function_type(recursive_type_parser.clone()), - mutable_reference_type(recursive_type_parser.clone()), - as_trait_path_type(recursive_type_parser), - )) -} + fn parse_unresolved_type_data(&mut self) -> Option { + if let Some(typ) = self.parse_primitive_type() { + return Some(typ); + } -pub(super) fn primitive_type() -> impl NoirParser { - choice(( - field_type(), - int_type(), - bool_type(), - string_type(), - comptime_type(), - resolved_type(), - interned_unresolved_type(), - )) -} + if let Some(typ) = self.parse_parentheses_type() { + return Some(typ); + } -fn as_trait_path_type<'a>( - type_parser: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - as_trait_path(type_parser) - .map_with_span(|path, span| UnresolvedTypeData::AsTraitPath(Box::new(path)).with_span(span)) -} + if let Some(typ) = self.parse_array_or_slice_type() { + return Some(typ); + } -pub(super) fn parenthesized_type( - recursive_type_parser: impl NoirParser, -) -> impl NoirParser { - recursive_type_parser - .delimited_by(just(Token::LeftParen), just(Token::RightParen)) - .map_with_span(|typ, span| UnresolvedType { - typ: UnresolvedTypeData::Parenthesized(Box::new(typ)), - span, - }) -} + if let Some(typ) = self.parses_mutable_reference_type() { + return Some(typ); + } -pub(super) fn maybe_comp_time() -> impl NoirParser { - keyword(Keyword::Comptime).or_not().map(|opt| opt.is_some()) -} + if let Some(typ) = self.parse_function_type() { + return Some(typ); + } -pub(super) fn field_type() -> impl NoirParser { - keyword(Keyword::Field) - .map_with_span(|_, span| UnresolvedTypeData::FieldElement.with_span(span)) -} + if let Some(typ) = self.parse_trait_as_type() { + return Some(typ); + } -pub(super) fn bool_type() -> impl NoirParser { - keyword(Keyword::Bool).map_with_span(|_, span| UnresolvedTypeData::Bool.with_span(span)) -} + if let Some(typ) = self.parse_as_trait_path_type() { + return Some(typ); + } -pub(super) fn comptime_type() -> impl NoirParser { - choice(( - expr_type(), - struct_definition_type(), - trait_constraint_type(), - trait_definition_type(), - trait_impl_type(), - unresolved_type_type(), - function_definition_type(), - module_type(), - type_of_quoted_types(), - top_level_item_type(), - quoted_type(), - typed_expr_type(), - comptime_string_type(), - )) -} + if let Some(path) = self.parse_path_no_turbofish() { + let generics = self.parse_generic_type_args(); + return Some(UnresolvedTypeData::Named(path, generics, false)); + } -/// This is the type `Expr` - the type of a quoted, untyped expression object used for macros -pub(super) fn expr_type() -> impl NoirParser { - keyword(Keyword::Expr) - .map_with_span(|_, span| UnresolvedTypeData::Quoted(QuotedType::Expr).with_span(span)) -} + None + } -/// This is the type `StructDefinition` - the type of a quoted struct definition -pub(super) fn struct_definition_type() -> impl NoirParser { - keyword(Keyword::StructDefinition).map_with_span(|_, span| { - UnresolvedTypeData::Quoted(QuotedType::StructDefinition).with_span(span) - }) -} + pub(super) fn parse_primitive_type(&mut self) -> Option { + if let Some(typ) = self.parse_field_type() { + return Some(typ); + } -/// This is the type `TraitConstraint` - the type of a quoted trait constraint -pub(super) fn trait_constraint_type() -> impl NoirParser { - keyword(Keyword::TraitConstraint).map_with_span(|_, span| { - UnresolvedTypeData::Quoted(QuotedType::TraitConstraint).with_span(span) - }) -} + if let Some(typ) = self.parse_int_type() { + return Some(typ); + } -pub(super) fn trait_definition_type() -> impl NoirParser { - keyword(Keyword::TraitDefinition).map_with_span(|_, span| { - UnresolvedTypeData::Quoted(QuotedType::TraitDefinition).with_span(span) - }) -} + if let Some(typ) = self.parse_bool_type() { + return Some(typ); + } -pub(super) fn trait_impl_type() -> impl NoirParser { - keyword(Keyword::TraitImpl) - .map_with_span(|_, span| UnresolvedTypeData::Quoted(QuotedType::TraitImpl).with_span(span)) -} + if let Some(typ) = self.parse_str_type() { + return Some(typ); + } -pub(super) fn unresolved_type_type() -> impl NoirParser { - keyword(Keyword::UnresolvedType).map_with_span(|_, span| { - UnresolvedTypeData::Quoted(QuotedType::UnresolvedType).with_span(span) - }) -} + if let Some(typ) = self.parse_fmtstr_type() { + return Some(typ); + } -pub(super) fn function_definition_type() -> impl NoirParser { - keyword(Keyword::FunctionDefinition).map_with_span(|_, span| { - UnresolvedTypeData::Quoted(QuotedType::FunctionDefinition).with_span(span) - }) -} + if let Some(typ) = self.parse_comptime_type() { + return Some(typ); + } -pub(super) fn module_type() -> impl NoirParser { - keyword(Keyword::Module) - .map_with_span(|_, span| UnresolvedTypeData::Quoted(QuotedType::Module).with_span(span)) -} + if let Some(typ) = self.parse_resolved_type() { + return Some(typ); + } -/// This is the type `TopLevelItem` - the type of a quoted statement in the top level. -/// E.g. a type definition, trait definition, trait impl, function, etc. -fn top_level_item_type() -> impl NoirParser { - keyword(Keyword::TopLevelItem).map_with_span(|_, span| { - UnresolvedTypeData::Quoted(QuotedType::TopLevelItem).with_span(span) - }) -} + if let Some(typ) = self.parse_interned_type() { + return Some(typ); + } -/// This is the type `Type` - the type of a quoted noir type. -fn type_of_quoted_types() -> impl NoirParser { - keyword(Keyword::TypeType) - .map_with_span(|_, span| UnresolvedTypeData::Quoted(QuotedType::Type).with_span(span)) -} + None + } -/// This is the type of a quoted, unparsed token stream. -fn quoted_type() -> impl NoirParser { - keyword(Keyword::Quoted) - .map_with_span(|_, span| UnresolvedTypeData::Quoted(QuotedType::Quoted).with_span(span)) -} + fn parse_bool_type(&mut self) -> Option { + if self.eat_keyword(Keyword::Bool) { + return Some(UnresolvedTypeData::Bool); + } -/// This is the type of a typed/resolved expression. -fn typed_expr_type() -> impl NoirParser { - keyword(Keyword::TypedExpr) - .map_with_span(|_, span| UnresolvedTypeData::Quoted(QuotedType::TypedExpr).with_span(span)) -} + None + } -/// This is the `CtString` type for dynamically-sized compile-time strings -fn comptime_string_type() -> impl NoirParser { - keyword(Keyword::CtString) - .map_with_span(|_, span| UnresolvedTypeData::Quoted(QuotedType::CtString).with_span(span)) -} + fn parse_field_type(&mut self) -> Option { + if self.eat_keyword(Keyword::Field) { + return Some(UnresolvedTypeData::FieldElement); + } -/// This is the type of an already resolved type. -/// The only way this can appear in the token input is if an already resolved `Type` object -/// was spliced into a macro's token stream via the `$` operator. -pub(super) fn resolved_type() -> impl NoirParser { - token_kind(TokenKind::QuotedType).map_with_span(|token, span| match token { - Token::QuotedType(id) => UnresolvedTypeData::Resolved(id).with_span(span), - _ => unreachable!("token_kind(QuotedType) guarantees we parse a quoted type"), - }) -} + None + } -pub(super) fn interned_unresolved_type() -> impl NoirParser { - token_kind(TokenKind::InternedUnresolvedTypeData).map_with_span(|token, span| match token { - Token::InternedUnresolvedTypeData(id) => UnresolvedTypeData::Interned(id).with_span(span), - _ => unreachable!( - "token_kind(InternedUnresolvedTypeData) guarantees we parse an interned unresolved type" - ), - }) -} + fn parse_int_type(&mut self) -> Option { + if let Some(int_type) = self.eat_int_type() { + return Some(match UnresolvedTypeData::from_int_token(int_type) { + Ok(typ) => typ, + Err(err) => { + self.push_error( + ParserErrorReason::InvalidBitSize(err.0), + self.previous_token_span, + ); + UnresolvedTypeData::Error + } + }); + } -pub(super) fn string_type() -> impl NoirParser { - keyword(Keyword::String) - .ignore_then(type_expression().delimited_by(just(Token::Less), just(Token::Greater))) - .map_with_span(|expr, span| UnresolvedTypeData::String(expr).with_span(span)) -} + None + } -pub(super) fn format_string_type<'a>( - type_parser: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - keyword(Keyword::FormatString) - .ignore_then( - type_expression() - .then_ignore(just(Token::Comma)) - .then(type_parser) - .delimited_by(just(Token::Less), just(Token::Greater)), - ) - .map_with_span(|(size, fields), span| { - UnresolvedTypeData::FormatString(size, Box::new(fields)).with_span(span) - }) -} + fn parse_str_type(&mut self) -> Option { + if !self.eat_keyword(Keyword::String) { + return None; + } -pub(super) fn int_type() -> impl NoirParser { - filter_map(|span, token: Token| match token { - Token::IntType(int_type) => Ok(int_type), - unexpected => { - Err(ParserError::expected_label(ParsingRuleLabel::IntegerType, unexpected, span)) - } - }) - .validate(|token, span, emit| { - UnresolvedTypeData::from_int_token(token).map(|data| data.with_span(span)).unwrap_or_else( - |err| { - emit(ParserError::with_reason(ParserErrorReason::InvalidBitSize(err.0), span)); - UnresolvedType::error(span) - }, - ) - }) -} + if !self.eat_less() { + self.expected_token(Token::Less); + let expr = + UnresolvedTypeExpression::Constant(FieldElement::zero(), self.current_token_span); + return Some(UnresolvedTypeData::String(expr)); + } -pub(super) fn named_type<'a>( - type_parser: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - path_no_turbofish().then(generic_type_args(type_parser)).map_with_span(|(path, args), span| { - UnresolvedTypeData::Named(path, args, false).with_span(span) - }) -} + let expr = match self.parse_type_expression() { + Ok(expr) => expr, + Err(error) => { + self.errors.push(error); + UnresolvedTypeExpression::Constant(FieldElement::zero(), self.current_token_span) + } + }; -pub(super) fn named_trait<'a>( - type_parser: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - keyword(Keyword::Impl) - .ignore_then(path_no_turbofish()) - .then(generic_type_args(type_parser)) - .map_with_span(|(path, args), span| { - UnresolvedTypeData::TraitAsType(path, args).with_span(span) - }) -} + self.eat_or_error(Token::Greater); -pub(super) fn generic_type_args<'a>( - type_parser: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - required_generic_type_args(type_parser).or_not().map(Option::unwrap_or_default) -} + Some(UnresolvedTypeData::String(expr)) + } -pub(super) fn required_generic_type_args<'a>( - type_parser: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - let generic_type_arg = type_parser - .clone() - .then_ignore(one_of([Token::Comma, Token::Greater]).rewind()) - .or(type_expression_validated()); - - let named_arg = ident() - .then_ignore(just(Token::Assign)) - .then(generic_type_arg.clone()) - .map(|(name, typ)| GenericTypeArg::Named(name, typ)); - - // We need to parse named arguments first since otherwise when we see - // `Foo = Bar`, just `Foo` is a valid type, and we'd parse an ordered - // generic before erroring that an `=` is invalid after an ordered generic. - choice((named_arg, generic_type_arg.map(GenericTypeArg::Ordered))) - .boxed() - // Without checking for a terminating ',' or '>' here we may incorrectly - // parse a generic `N * 2` as just the type `N` then fail when there is no - // separator afterward. Failing early here ensures we try the `type_expression` - // parser afterward. - .separated_by(just(Token::Comma)) - .allow_trailing() - .at_least(1) - .delimited_by(just(Token::Less), just(Token::Greater)) - .map(GenericTypeArgs::from) -} + fn parse_fmtstr_type(&mut self) -> Option { + if !self.eat_keyword(Keyword::FormatString) { + return None; + } -pub(super) fn array_type<'a>( - type_parser: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - just(Token::LeftBracket) - .ignore_then(type_parser) - .then(just(Token::Semicolon).ignore_then(type_expression())) - .then_ignore(just(Token::RightBracket)) - .map_with_span(|(element_type, size), span| { - UnresolvedTypeData::Array(size, Box::new(element_type)).with_span(span) - }) -} + if !self.eat_less() { + self.expected_token(Token::Less); + 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))); + } -pub(super) fn slice_type( - type_parser: impl NoirParser, -) -> impl NoirParser { - just(Token::LeftBracket) - .ignore_then(type_parser) - .then_ignore(just(Token::RightBracket)) - .map_with_span(|element_type, span| { - UnresolvedTypeData::Slice(Box::new(element_type)).with_span(span) - }) -} + let expr = match self.parse_type_expression() { + Ok(expr) => expr, + Err(error) => { + self.errors.push(error); + UnresolvedTypeExpression::Constant(FieldElement::zero(), self.current_token_span) + } + }; -fn type_expression() -> impl NoirParser { - type_expression_inner().try_map(UnresolvedTypeExpression::from_expr) -} + if !self.eat_commas() { + self.expected_token(Token::Comma); + } + + let typ = self.parse_type_or_error(); + + self.eat_or_error(Token::Greater); + + Some(UnresolvedTypeData::FormatString(expr, Box::new(typ))) + } + + fn parse_comptime_type(&mut self) -> Option { + if self.eat_keyword(Keyword::Expr) { + return Some(UnresolvedTypeData::Quoted(QuotedType::Expr)); + } + if self.eat_keyword(Keyword::Quoted) { + return Some(UnresolvedTypeData::Quoted(QuotedType::Quoted)); + } + if self.eat_keyword(Keyword::TopLevelItem) { + return Some(UnresolvedTypeData::Quoted(QuotedType::TopLevelItem)); + } + if self.eat_keyword(Keyword::TypeType) { + return Some(UnresolvedTypeData::Quoted(QuotedType::Type)); + } + if self.eat_keyword(Keyword::TypedExpr) { + return Some(UnresolvedTypeData::Quoted(QuotedType::TypedExpr)); + } + if self.eat_keyword(Keyword::StructDefinition) { + return Some(UnresolvedTypeData::Quoted(QuotedType::StructDefinition)); + } + if self.eat_keyword(Keyword::TraitConstraint) { + return Some(UnresolvedTypeData::Quoted(QuotedType::TraitConstraint)); + } + if self.eat_keyword(Keyword::TraitDefinition) { + return Some(UnresolvedTypeData::Quoted(QuotedType::TraitDefinition)); + } + if self.eat_keyword(Keyword::TraitImpl) { + return Some(UnresolvedTypeData::Quoted(QuotedType::TraitImpl)); + } + if self.eat_keyword(Keyword::UnresolvedType) { + return Some(UnresolvedTypeData::Quoted(QuotedType::UnresolvedType)); + } + if self.eat_keyword(Keyword::FunctionDefinition) { + return Some(UnresolvedTypeData::Quoted(QuotedType::FunctionDefinition)); + } + if self.eat_keyword(Keyword::Module) { + return Some(UnresolvedTypeData::Quoted(QuotedType::Module)); + } + if self.eat_keyword(Keyword::CtString) { + return Some(UnresolvedTypeData::Quoted(QuotedType::CtString)); + } + None + } -/// This parser is the same as `type_expression()`, however, it continues parsing and -/// emits a parser error in the case of an invalid type expression rather than halting the parser. -pub(super) fn type_expression_validated() -> impl NoirParser { - type_expression_inner().validate(|expr, span, emit| { - let type_expr = UnresolvedTypeExpression::from_expr(expr, span); - match type_expr { - Ok(type_expression) => UnresolvedTypeData::Expression(type_expression).with_span(span), - Err(parser_error) => { - emit(parser_error); - UnresolvedType::error(span) + fn parse_function_type(&mut self) -> Option { + let unconstrained = self.eat_keyword(Keyword::Unconstrained); + + if !self.eat_keyword(Keyword::Fn) { + if unconstrained { + self.expected_token(Token::Keyword(Keyword::Fn)); + return Some(UnresolvedTypeData::Function( + Vec::new(), + Box::new(self.unspecified_type_at_previous_token_end()), + Box::new(self.unspecified_type_at_previous_token_end()), + unconstrained, + )); } + + return None; } - }) -} -fn type_expression_inner() -> impl NoirParser { - recursive(|expr| { - expression_with_precedence( - Precedence::lowest_type_precedence(), - expr, - nothing(), - nothing(), - true, - false, - ) - }) - .labelled(ParsingRuleLabel::TypeExpression) -} + let env = if self.eat_left_bracket() { + let typ = self.parse_type_or_error(); + self.eat_or_error(Token::RightBracket); + typ + } else { + UnresolvedTypeData::Unit.with_span(self.span_at_previous_token_end()) + }; + + if !self.eat_left_paren() { + self.expected_token(Token::LeftParen); + + return Some(UnresolvedTypeData::Function( + Vec::new(), + Box::new(self.unspecified_type_at_previous_token_end()), + Box::new(self.unspecified_type_at_previous_token_end()), + unconstrained, + )); + } + + let args = self.parse_many( + "parameters", + separated_by_comma_until_right_paren(), + Self::parse_parameter, + ); + + let ret = if self.eat(Token::Arrow) { + self.parse_type_or_error() + } else { + self.expected_token(Token::Arrow); + UnresolvedTypeData::Unit.with_span(self.span_at_previous_token_end()) + }; + + Some(UnresolvedTypeData::Function(args, Box::new(ret), Box::new(env), unconstrained)) + } -pub(super) fn tuple_type(type_parser: T) -> impl NoirParser -where - T: NoirParser, -{ - let fields = type_parser.separated_by(just(Token::Comma)).allow_trailing(); - parenthesized(fields).map_with_span(|fields, span| { - if fields.is_empty() { - UnresolvedTypeData::Unit.with_span(span) + fn parse_parameter(&mut self) -> Option { + let typ = self.parse_type_or_error(); + if let UnresolvedTypeData::Error = typ.typ { + None } else { - UnresolvedTypeData::Tuple(fields).with_span(span) + Some(typ) } - }) -} + } -pub(super) fn function_type(type_parser: T) -> impl NoirParser -where - T: NoirParser, -{ - let args = parenthesized(type_parser.clone().separated_by(just(Token::Comma)).allow_trailing()); - - let env = just(Token::LeftBracket) - .ignore_then(type_parser.clone()) - .then_ignore(just(Token::RightBracket)) - .or_not() - .map_with_span(|t, span| { - t.unwrap_or_else(|| UnresolvedTypeData::Unit.with_span(Span::empty(span.end()))) - }); - - keyword(Keyword::Unconstrained) - .or_not() - .then(keyword(Keyword::Fn)) - .map(|(unconstrained_token, _fn_token)| unconstrained_token.is_some()) - .then(env) - .then(args) - .then_ignore(just(Token::Arrow)) - .then(type_parser) - .map_with_span(|(((unconstrained, env), args), ret), span| { - UnresolvedTypeData::Function(args, Box::new(ret), Box::new(env), unconstrained) - .with_span(span) - }) -} + fn parse_trait_as_type(&mut self) -> Option { + if !self.eat_keyword(Keyword::Impl) { + return None; + } + + let Some(path) = self.parse_path_no_turbofish() else { + self.expected_label(ParsingRuleLabel::Path); + return None; + }; -pub(super) fn mutable_reference_type(type_parser: T) -> impl NoirParser -where - T: NoirParser, -{ - just(Token::Ampersand) - .ignore_then(keyword(Keyword::Mut)) - .ignore_then(type_parser) - .map_with_span(|element, span| { - UnresolvedTypeData::MutableReference(Box::new(element)).with_span(span) + let generics = self.parse_generic_type_args(); + + Some(UnresolvedTypeData::TraitAsType(path, generics)) + } + + fn parse_as_trait_path_type(&mut self) -> Option { + let as_trait_path = self.parse_as_trait_path()?; + Some(UnresolvedTypeData::AsTraitPath(Box::new(as_trait_path))) + } + + fn parse_resolved_type(&mut self) -> Option { + if let Some(token) = self.eat_kind(TokenKind::QuotedType) { + match token.into_token() { + Token::QuotedType(id) => { + return Some(UnresolvedTypeData::Resolved(id)); + } + _ => unreachable!(), + } + } + + None + } + + pub(super) fn parse_interned_type(&mut self) -> Option { + if let Some(token) = self.eat_kind(TokenKind::InternedUnresolvedTypeData) { + match token.into_token() { + Token::InternedUnresolvedTypeData(id) => { + return Some(UnresolvedTypeData::Interned(id)); + } + _ => unreachable!(), + } + } + + None + } + + fn parses_mutable_reference_type(&mut self) -> Option { + if self.eat(Token::Ampersand) { + self.eat_keyword_or_error(Keyword::Mut); + return Some(UnresolvedTypeData::MutableReference(Box::new( + self.parse_type_or_error(), + ))); + }; + + None + } + + fn parse_array_or_slice_type(&mut self) -> Option { + if !self.eat_left_bracket() { + return None; + } + + let typ = self.parse_type_or_error(); + + if self.eat_semicolon() { + match self.parse_type_expression() { + Ok(expr) => { + self.eat_or_error(Token::RightBracket); + Some(UnresolvedTypeData::Array(expr, Box::new(typ))) + } + Err(error) => { + self.errors.push(error); + self.eat_or_error(Token::RightBracket); + Some(UnresolvedTypeData::Slice(Box::new(typ))) + } + } + } else { + self.eat_or_error(Token::RightBracket); + Some(UnresolvedTypeData::Slice(Box::new(typ))) + } + } + + fn parse_parentheses_type(&mut self) -> Option { + if !self.eat_left_paren() { + return None; + } + + if self.eat_right_paren() { + return Some(UnresolvedTypeData::Unit); + } + + let (mut types, trailing_comma) = self.parse_many_return_trailing_separator_if_any( + "tuple elements", + separated_by_comma_until_right_paren(), + Self::parse_type_in_list, + ); + + Some(if types.len() == 1 && !trailing_comma { + UnresolvedTypeData::Parenthesized(Box::new(types.remove(0))) + } else { + UnresolvedTypeData::Tuple(types) }) + } + + pub(super) fn parse_type_in_list(&mut self) -> Option { + if let Some(typ) = self.parse_type() { + Some(typ) + } else { + self.expected_label(ParsingRuleLabel::Type); + None + } + } + + /// OptionalTypeAnnotation = ( ':' Type )? + pub(super) fn parse_optional_type_annotation(&mut self) -> UnresolvedType { + if self.eat_colon() { + self.parse_type_or_error() + } else { + self.unspecified_type_at_previous_token_end() + } + } + + pub(super) fn unspecified_type_at_previous_token_end(&self) -> UnresolvedType { + UnresolvedTypeData::Unspecified.with_span(self.span_at_previous_token_end()) + } } #[cfg(test)] -mod test { - use super::*; - use crate::parser::parser::test_helpers::*; +mod tests { + use strum::IntoEnumIterator; + + use crate::{ + ast::{IntegerBitSize, Signedness, UnresolvedType, UnresolvedTypeData}, + parser::{ + parser::tests::{expect_no_errors, get_single_error, get_source_with_error_span}, + Parser, + }, + QuotedType, + }; + + fn parse_type_no_errors(src: &str) -> UnresolvedType { + let mut parser = Parser::for_str(src); + let typ = parser.parse_type_or_error(); + expect_no_errors(&parser.errors); + typ + } + + #[test] + fn parses_unit_type() { + let src = "()"; + let typ = parse_type_no_errors(src); + assert!(matches!(typ.typ, UnresolvedTypeData::Unit)); + } + + #[test] + fn parses_bool_type() { + let src = "bool"; + let typ = parse_type_no_errors(src); + assert!(matches!(typ.typ, UnresolvedTypeData::Bool)); + } + + #[test] + fn parses_int_type() { + let src = "u32"; + let typ = parse_type_no_errors(src); + assert!(matches!( + typ.typ, + UnresolvedTypeData::Integer(Signedness::Unsigned, IntegerBitSize::ThirtyTwo) + )); + } + + #[test] + fn parses_field_type() { + let src = "Field"; + let typ = parse_type_no_errors(src); + assert!(matches!(typ.typ, UnresolvedTypeData::FieldElement)); + } + + #[test] + fn parses_str_type() { + let src = "str<10>"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::String(expr) = typ.typ else { panic!("Expected a string type") }; + assert_eq!(expr.to_string(), "10"); + } + + #[test] + fn parses_fmtstr_type() { + let src = "fmtstr<10, T>"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::FormatString(expr, typ) = typ.typ else { + panic!("Expected a format string type") + }; + assert_eq!(expr.to_string(), "10"); + assert_eq!(typ.to_string(), "T"); + } + + #[test] + fn parses_comptime_types() { + for quoted_type in QuotedType::iter() { + let src = quoted_type.to_string(); + let typ = parse_type_no_errors(&src); + let UnresolvedTypeData::Quoted(parsed_qouted_type) = typ.typ else { + panic!("Expected a quoted type for {}", quoted_type) + }; + assert_eq!(parsed_qouted_type, quoted_type); + } + } + + #[test] + fn parses_tuple_type() { + let src = "(Field, bool)"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::Tuple(mut types) = typ.typ else { panic!("Expected a tuple type") }; + assert_eq!(types.len(), 2); + + let typ = types.remove(0); + assert!(matches!(typ.typ, UnresolvedTypeData::FieldElement)); + + let typ = types.remove(0); + assert!(matches!(typ.typ, UnresolvedTypeData::Bool)); + } + + #[test] + fn parses_tuple_type_one_element() { + let src = "(Field,)"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::Tuple(mut types) = typ.typ else { panic!("Expected a tuple type") }; + assert_eq!(types.len(), 1); + + let typ = types.remove(0); + assert!(matches!(typ.typ, UnresolvedTypeData::FieldElement)); + } + + #[test] + fn parses_parenthesized_type() { + let src = "(Field)"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::Parenthesized(typ) = typ.typ else { + panic!("Expected a parenthesized type") + }; + assert!(matches!(typ.typ, UnresolvedTypeData::FieldElement)); + } + + #[test] + fn parses_unclosed_parentheses_type() { + let src = "(Field"; + let mut parser = Parser::for_str(src); + let typ = parser.parse_type_or_error(); + assert_eq!(parser.errors.len(), 1); + let UnresolvedTypeData::Parenthesized(typ) = typ.typ else { + panic!("Expected a parenthesized type") + }; + assert!(matches!(typ.typ, UnresolvedTypeData::FieldElement)); + } + + #[test] + fn parses_mutable_reference_type() { + let src = "&mut Field"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::MutableReference(typ) = typ.typ else { + panic!("Expected a mutable reference type") + }; + assert!(matches!(typ.typ, UnresolvedTypeData::FieldElement)); + } + + #[test] + fn parses_named_type_no_generics() { + let src = "foo::Bar"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::Named(path, generics, _) = typ.typ else { + panic!("Expected a named type") + }; + assert_eq!(path.to_string(), "foo::Bar"); + assert!(generics.is_empty()); + } + + #[test] + fn parses_slice_type() { + let src = "[Field]"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::Slice(typ) = typ.typ else { panic!("Expected a slice type") }; + assert!(matches!(typ.typ, UnresolvedTypeData::FieldElement)); + } + + #[test] + fn errors_if_missing_right_bracket_after_slice_type() { + let src = " + [Field + ^ + "; + let (src, span) = get_source_with_error_span(src); + 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"); + } + + #[test] + fn parses_array_type() { + let src = "[Field; 10]"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::Array(expr, typ) = typ.typ else { + panic!("Expected an array type") + }; + assert!(matches!(typ.typ, UnresolvedTypeData::FieldElement)); + assert_eq!(expr.to_string(), "10"); + } + + #[test] + fn parses_empty_function_type() { + let src = "fn() -> Field"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::Function(args, ret, env, unconstrained) = typ.typ else { + panic!("Expected a function type") + }; + assert!(args.is_empty()); + assert_eq!(ret.typ.to_string(), "Field"); + assert!(matches!(env.typ, UnresolvedTypeData::Unit)); + assert!(!unconstrained); + } + + #[test] + fn parses_function_type_with_arguments() { + let src = "fn(Field, bool) -> Field"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::Function(args, _ret, _env, _unconstrained) = typ.typ else { + panic!("Expected a function type") + }; + assert_eq!(args.len(), 2); + assert_eq!(args[0].typ.to_string(), "Field"); + assert_eq!(args[1].typ.to_string(), "bool"); + } + + #[test] + fn parses_function_type_with_return_type() { + let src = "fn() -> Field"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::Function(_args, ret, _env, _unconstrained) = typ.typ else { + panic!("Expected a function type") + }; + assert_eq!(ret.typ.to_string(), "Field"); + } + + #[test] + fn parses_function_type_with_env() { + let src = "fn[Field]() -> Field"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::Function(_args, _ret, env, _unconstrained) = typ.typ else { + panic!("Expected a function type") + }; + assert_eq!(env.typ.to_string(), "Field"); + } + + #[test] + fn parses_unconstrained_function_type() { + let src = "unconstrained fn() -> Field"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::Function(_args, _ret, _env, unconstrained) = typ.typ else { + panic!("Expected a function type") + }; + assert!(unconstrained); + } + + #[test] + fn parses_trait_as_type_no_generics() { + let src = "impl foo::Bar"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::TraitAsType(path, generics) = typ.typ else { + panic!("Expected trait as type") + }; + assert_eq!(path.to_string(), "foo::Bar"); + assert!(generics.is_empty()); + } #[test] - fn parse_type_expression() { - parse_all(type_expression(), vec!["(123)", "123", "(1 + 1)", "(1 + (1))"]); + fn parses_as_trait_path() { + let src = "::baz"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::AsTraitPath(as_trait_path) = typ.typ else { + panic!("Expected as_trait_path") + }; + assert_eq!(as_trait_path.typ.typ.to_string(), "Field"); + assert_eq!(as_trait_path.trait_path.to_string(), "foo::Bar"); + assert!(as_trait_path.trait_generics.is_empty()); + assert_eq!(as_trait_path.impl_item.to_string(), "baz"); } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/use_tree.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/use_tree.rs new file mode 100644 index 00000000000..1c43732c94f --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/use_tree.rs @@ -0,0 +1,253 @@ +use noirc_errors::Span; + +use crate::{ + ast::{Ident, Path, PathKind, UseTree, UseTreeKind}, + parser::labels::ParsingRuleLabel, + token::{Keyword, Token}, +}; + +use super::{parse_many::separated_by_comma_until_right_brace, Parser}; + +impl<'a> Parser<'a> { + /// Use = 'use' PathKind PathNoTurbofish UseTree + /// + /// UseTree = PathNoTurbofish ( '::' '{' UseTreeList? '}' )? + /// + /// UseTreeList = UseTree (',' UseTree)* ','? + pub(super) fn parse_use_tree(&mut self) -> UseTree { + let start_span = self.current_token_span; + + let kind = self.parse_path_kind(); + + let use_tree = self.parse_use_tree_without_kind( + start_span, kind, false, // nested + ); + if !self.eat_semicolons() { + self.expected_token(Token::Semicolon); + } + use_tree + } + + pub(super) fn parse_use_tree_without_kind( + &mut self, + start_span: Span, + kind: PathKind, + nested: bool, + ) -> UseTree { + let prefix = self.parse_path_after_kind( + kind, false, // allow turbofish + false, // allow trailing double colon + start_span, + ); + let trailing_double_colon = if prefix.segments.is_empty() && kind != PathKind::Plain { + true + } else { + self.eat_double_colon() + }; + + if trailing_double_colon { + if self.eat_left_brace() { + let use_trees = self.parse_many( + "use trees", + separated_by_comma_until_right_brace(), + Self::parse_use_tree_in_list, + ); + + UseTree { prefix, kind: UseTreeKind::List(use_trees) } + } else { + self.expected_token(Token::LeftBrace); + self.parse_path_use_tree_end(prefix, nested) + } + } else { + self.parse_path_use_tree_end(prefix, nested) + } + } + + fn parse_use_tree_in_list(&mut self) -> Option { + let start_span = self.current_token_span; + + let use_tree = self.parse_use_tree_without_kind( + start_span, + PathKind::Plain, + true, // nested + ); + + // If we didn't advance at all, we are done + if start_span == self.current_token_span { + self.expected_label(ParsingRuleLabel::UseSegment); + None + } else { + Some(use_tree) + } + } + + pub(super) fn parse_path_use_tree_end(&mut self, mut prefix: Path, nested: bool) -> UseTree { + if prefix.segments.is_empty() { + if nested { + self.expected_identifier(); + } else { + self.expected_label(ParsingRuleLabel::UseSegment); + } + UseTree { prefix, kind: UseTreeKind::Path(Ident::default(), None) } + } else { + let ident = prefix.segments.pop().unwrap().ident; + if self.eat_keyword(Keyword::As) { + if let Some(alias) = self.eat_ident() { + UseTree { prefix, kind: UseTreeKind::Path(ident, Some(alias)) } + } else { + self.expected_identifier(); + UseTree { prefix, kind: UseTreeKind::Path(ident, None) } + } + } else { + UseTree { prefix, kind: UseTreeKind::Path(ident, None) } + } + } + } +} + +#[cfg(test)] +mod tests { + use crate::{ + ast::{ItemVisibility, PathKind, UseTree, UseTreeKind}, + parser::{ + parser::{parse_program, tests::expect_no_errors}, + ItemKind, + }, + }; + + fn parse_use_tree_no_errors(src: &str) -> (UseTree, ItemVisibility) { + let (mut module, errors) = parse_program(src); + expect_no_errors(&errors); + assert_eq!(module.items.len(), 1); + let item = module.items.remove(0); + let ItemKind::Import(use_tree, visibility) = item.kind else { + panic!("Expected import"); + }; + (use_tree, visibility) + } + + #[test] + fn parse_simple() { + let src = "use foo;"; + let (use_tree, visibility) = parse_use_tree_no_errors(src); + assert_eq!(visibility, ItemVisibility::Private); + assert_eq!(use_tree.prefix.kind, PathKind::Plain); + assert_eq!("foo", use_tree.to_string()); + let UseTreeKind::Path(ident, alias) = &use_tree.kind else { + panic!("Expected path"); + }; + assert_eq!("foo", ident.to_string()); + assert!(alias.is_none()); + } + + #[test] + fn parse_simple_pub() { + let src = "pub use foo;"; + let (_use_tree, visibility) = parse_use_tree_no_errors(src); + assert_eq!(visibility, ItemVisibility::Public); + } + + #[test] + fn parse_simple_pub_crate() { + let src = "pub(crate) use foo;"; + let (_use_tree, visibility) = parse_use_tree_no_errors(src); + assert_eq!(visibility, ItemVisibility::PublicCrate); + } + + #[test] + fn parse_simple_with_alias() { + let src = "use foo as bar;"; + let (use_tree, visibility) = parse_use_tree_no_errors(src); + assert_eq!(visibility, ItemVisibility::Private); + assert_eq!(use_tree.prefix.kind, PathKind::Plain); + assert_eq!("foo as bar", use_tree.to_string()); + let UseTreeKind::Path(ident, alias) = use_tree.kind else { + panic!("Expected path"); + }; + assert_eq!("foo", ident.to_string()); + assert_eq!("bar", alias.unwrap().to_string()); + } + + #[test] + fn parse_with_crate_prefix() { + let src = "use crate::foo;"; + let (use_tree, visibility) = parse_use_tree_no_errors(src); + assert_eq!(visibility, ItemVisibility::Private); + assert_eq!(use_tree.prefix.kind, PathKind::Crate); + assert_eq!("crate::foo", use_tree.to_string()); + let UseTreeKind::Path(ident, alias) = use_tree.kind else { + panic!("Expected path"); + }; + assert_eq!("foo", ident.to_string()); + assert!(alias.is_none()); + } + + #[test] + fn parse_with_dep_prefix() { + let src = "use dep::foo;"; + let (use_tree, visibility) = parse_use_tree_no_errors(src); + assert_eq!(visibility, ItemVisibility::Private); + assert_eq!(use_tree.prefix.kind, PathKind::Dep); + assert_eq!("dep::foo", use_tree.to_string()); + let UseTreeKind::Path(ident, alias) = use_tree.kind else { + panic!("Expected path"); + }; + assert_eq!("foo", ident.to_string()); + assert!(alias.is_none()); + } + + #[test] + fn parse_with_super_prefix() { + let src = "use super::foo;"; + let (use_tree, visibility) = parse_use_tree_no_errors(src); + assert_eq!(visibility, ItemVisibility::Private); + assert_eq!(use_tree.prefix.kind, PathKind::Super); + assert_eq!("super::foo", use_tree.to_string()); + let UseTreeKind::Path(ident, alias) = use_tree.kind else { + panic!("Expected path"); + }; + assert_eq!("foo", ident.to_string()); + assert!(alias.is_none()); + } + + #[test] + fn parse_list() { + let src = "use foo::{bar, baz};"; + let (use_tree, visibility) = parse_use_tree_no_errors(src); + assert_eq!(visibility, ItemVisibility::Private); + assert_eq!(use_tree.prefix.kind, PathKind::Plain); + assert_eq!("foo::{bar, baz}", use_tree.to_string()); + let UseTreeKind::List(use_trees) = &use_tree.kind else { + panic!("Expected list"); + }; + assert_eq!(use_trees.len(), 2); + } + + #[test] + fn parse_list_trailing_comma() { + let src = "use foo::{bar, baz, };"; + let (use_tree, visibility) = parse_use_tree_no_errors(src); + assert_eq!(visibility, ItemVisibility::Private); + assert_eq!(use_tree.prefix.kind, PathKind::Plain); + assert_eq!("foo::{bar, baz}", use_tree.to_string()); + let UseTreeKind::List(use_trees) = &use_tree.kind else { + panic!("Expected list"); + }; + assert_eq!(use_trees.len(), 2); + } + + #[test] + fn parse_list_that_starts_with_crate() { + let src = "use crate::{foo, bar};"; + let (use_tree, visibility) = parse_use_tree_no_errors(src); + assert_eq!(visibility, ItemVisibility::Private); + assert_eq!("crate::{foo, bar}", use_tree.to_string()); + } + + #[test] + fn errors_on_crate_in_subtree() { + let src = "use foo::{crate::bar}"; + let (_, errors) = parse_program(src); + assert!(!errors.is_empty()); + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/visibility.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/visibility.rs deleted file mode 100644 index ea46becc932..00000000000 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/visibility.rs +++ /dev/null @@ -1,36 +0,0 @@ -use chumsky::{ - prelude::{choice, empty, just}, - Parser, -}; - -use crate::{ - ast::{ItemVisibility, Visibility}, - parser::NoirParser, - token::{Keyword, Token}, -}; - -use super::{call_data, primitives::keyword}; - -/// visibility_modifier: 'pub(crate)'? 'pub'? '' -pub(crate) fn item_visibility() -> impl NoirParser { - let is_pub_crate = (keyword(Keyword::Pub) - .then_ignore(just(Token::LeftParen)) - .then_ignore(keyword(Keyword::Crate)) - .then_ignore(just(Token::RightParen))) - .map(|_| ItemVisibility::PublicCrate); - - let is_pub = keyword(Keyword::Pub).map(|_| ItemVisibility::Public); - - let is_private = empty().map(|_| ItemVisibility::Private); - - choice((is_pub_crate, is_pub, is_private)) -} - -pub fn visibility() -> impl NoirParser { - keyword(Keyword::Pub) - .map(|_| Visibility::Public) - .or(call_data()) - .or(keyword(Keyword::ReturnData).map(|_| Visibility::ReturnData)) - .or_not() - .map(|opt| opt.unwrap_or(Visibility::Private)) -} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/where_clause.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/where_clause.rs new file mode 100644 index 00000000000..a753ffb6fd2 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/where_clause.rs @@ -0,0 +1,188 @@ +use crate::{ + ast::{GenericTypeArgs, Path, PathKind, TraitBound, UnresolvedTraitConstraint, UnresolvedType}, + parser::labels::ParsingRuleLabel, + token::{Keyword, Token}, +}; + +use super::{ + parse_many::{separated_by, separated_by_comma}, + Parser, +}; + +impl<'a> Parser<'a> { + /// WhereClause = 'where' WhereClauseItems? + /// + /// WhereClauseItems = WhereClauseItem ( ',' WhereClauseItem )* ','? + /// + /// WhereClauseItem = Type ':' TraitBounds + pub(super) fn parse_where_clause(&mut self) -> Vec { + if !self.eat_keyword(Keyword::Where) { + return Vec::new(); + } + + // Constraints might end up being empty, but that's accepted as valid syntax + let constraints = + self.parse_many("where clauses", separated_by_comma(), Self::parse_single_where_clause); + + constraints + .into_iter() + .flat_map(|(typ, trait_bounds)| { + trait_bounds.into_iter().map(move |trait_bound| UnresolvedTraitConstraint { + typ: typ.clone(), + trait_bound, + }) + }) + .collect() + } + + fn parse_single_where_clause(&mut self) -> Option<(UnresolvedType, Vec)> { + let Some(typ) = self.parse_type() else { + return None; + }; + + self.eat_or_error(Token::Colon); + + let trait_bounds = self.parse_trait_bounds(); + + Some((typ, trait_bounds)) + } + + /// TraitBounds = TraitBound ( '+' TraitBound )? '+'? + pub(super) fn parse_trait_bounds(&mut self) -> Vec { + self.parse_many( + "trait bounds", + separated_by(Token::Plus).stop_if_separator_is_missing(), + Self::parse_trait_bound_in_list, + ) + } + + fn parse_trait_bound_in_list(&mut self) -> Option { + if let Some(trait_bound) = self.parse_trait_bound() { + Some(trait_bound) + } else { + self.expected_label(ParsingRuleLabel::TraitBound); + None + } + } + + pub(crate) fn parse_trait_bound_or_error(&mut self) -> TraitBound { + if let Some(trait_bound) = self.parse_trait_bound() { + return trait_bound; + } + + self.expected_label(ParsingRuleLabel::TraitBound); + TraitBound { + trait_path: Path { + kind: PathKind::Plain, + segments: Vec::new(), + span: self.span_at_previous_token_end(), + }, + trait_id: None, + trait_generics: GenericTypeArgs::default(), + } + } + + /// TraitBound = PathNoTurbofish GenericTypeArgs + pub(crate) fn parse_trait_bound(&mut self) -> Option { + let trait_path = self.parse_path_no_turbofish()?; + let trait_generics = self.parse_generic_type_args(); + Some(TraitBound { trait_path, trait_generics, trait_id: None }) + } +} + +#[cfg(test)] +mod tests { + use crate::{ + ast::UnresolvedTraitConstraint, + parser::{ + parser::tests::{ + expect_no_errors, get_single_error_reason, get_source_with_error_span, + }, + Parser, ParserErrorReason, + }, + token::Token, + }; + + fn parse_where_clause_no_errors(src: &str) -> Vec { + let mut parser = Parser::for_str(src); + let constraints = parser.parse_where_clause(); + expect_no_errors(&parser.errors); + constraints + } + + #[test] + fn parses_no_where_clause() { + let src = "{"; + let constraints = parse_where_clause_no_errors(src); + assert!(constraints.is_empty()); + } + + #[test] + fn parses_one_where_clause_with_two_constraints() { + let src = "where Foo: Bar + Baz"; + let mut constraints = parse_where_clause_no_errors(src); + assert_eq!(constraints.len(), 2); + + let constraint = constraints.remove(0); + assert_eq!(constraint.typ.to_string(), "Foo"); + assert_eq!(constraint.trait_bound.trait_path.to_string(), "Bar"); + assert_eq!(constraint.trait_bound.trait_generics.ordered_args[0].to_string(), "T"); + + let constraint = constraints.remove(0); + assert_eq!(constraint.typ.to_string(), "Foo"); + assert_eq!(constraint.trait_bound.trait_path.to_string(), "Baz"); + } + + #[test] + fn parses_two_where_clauses() { + let src = "where Foo: Bar, i32: Qux {"; + let mut constraints = parse_where_clause_no_errors(src); + assert_eq!(constraints.len(), 2); + + let constraint = constraints.remove(0); + assert_eq!(constraint.typ.to_string(), "Foo"); + assert_eq!(constraint.trait_bound.trait_path.to_string(), "Bar"); + assert_eq!(constraint.trait_bound.trait_generics.ordered_args[0].to_string(), "T"); + + let constraint = constraints.remove(0); + assert_eq!(constraint.typ.to_string(), "i32"); + assert_eq!(constraint.trait_bound.trait_path.to_string(), "Qux"); + } + + #[test] + fn parses_two_where_clauses_missing_comma() { + let src = " + where Foo: Bar i32: Qux { + ^^^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + let mut constraints = parser.parse_where_clause(); + + let reason = get_single_error_reason(&parser.errors, span); + let ParserErrorReason::ExpectedTokenSeparatingTwoItems { token, items } = reason else { + panic!("Expected a different error"); + }; + assert_eq!(token, &Token::Comma); + assert_eq!(*items, "where clauses"); + + assert_eq!(constraints.len(), 2); + + let constraint = constraints.remove(0); + assert_eq!(constraint.typ.to_string(), "Foo"); + assert_eq!(constraint.trait_bound.trait_path.to_string(), "Bar"); + assert_eq!(constraint.trait_bound.trait_generics.ordered_args[0].to_string(), "T"); + + let constraint = constraints.remove(0); + assert_eq!(constraint.typ.to_string(), "i32"); + assert_eq!(constraint.trait_bound.trait_path.to_string(), "Qux"); + } + + #[test] + fn parses_where_clause_missing_trait_bound() { + let src = "where Foo: "; + let mut parser = Parser::for_str(src); + parser.parse_where_clause(); + assert!(!parser.errors.is_empty()); + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/tests.rs b/noir/noir-repo/compiler/noirc_frontend/src/tests.rs index a27cd1087c4..b2800717d90 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; @@ -30,16 +31,13 @@ use crate::hir::Context; use crate::node_interner::{NodeInterner, StmtId}; use crate::hir::def_collector::dc_crate::DefCollector; +use crate::hir::def_map::{CrateDefMap, LocalModuleId}; use crate::hir_def::expr::HirExpression; use crate::hir_def::stmt::HirStatement; use crate::monomorphization::monomorphize; use crate::parser::{ItemKind, ParserErrorReason}; use crate::token::SecondaryAttribute; -use crate::ParsedModule; -use crate::{ - hir::def_map::{CrateDefMap, LocalModuleId}, - parse_program, -}; +use crate::{parse_program, ParsedModule}; use fm::FileManager; use noirc_arena::Arena; @@ -90,7 +88,8 @@ pub(crate) fn get_program(src: &str) -> (ParsedModule, Context, Vec<(Compilation location, Vec::new(), inner_attributes.clone(), - false, + false, // is contract + false, // is struct )); let def_map = CrateDefMap { @@ -1071,6 +1070,18 @@ fn resolve_for_expr() { assert_no_errors(src); } +#[test] +fn resolve_for_expr_incl() { + let src = r#" + fn main(x : u64) { + for i in 1..=20 { + let _z = x + i; + }; + } + "#; + assert_no_errors(src); +} + #[test] fn resolve_call_expr() { let src = r#" @@ -1586,9 +1597,7 @@ fn struct_numeric_generic_in_struct() { assert_eq!(errors.len(), 1); assert!(matches!( errors[0].0, - CompilationError::DefinitionError( - DefCollectorErrorKind::UnsupportedNumericGenericType { .. } - ), + CompilationError::ResolverError(ResolverError::UnsupportedNumericGenericType(_)), )); } @@ -1641,7 +1650,6 @@ fn bool_generic_as_loop_bound() { "#; let errors = get_program_errors(src); assert_eq!(errors.len(), 3); - assert!(matches!( errors[0].0, CompilationError::ResolverError(ResolverError::UnsupportedNumericGenericType { .. }), @@ -1706,7 +1714,7 @@ fn normal_generic_as_array_length() { #[test] fn numeric_generic_as_param_type() { let src = r#" - pub fn foo(x: I) -> I { + pub fn foo(x: I) -> I { let _q: I = 5; x } @@ -1731,6 +1739,68 @@ fn numeric_generic_as_param_type() { )); } +#[test] +fn numeric_generic_as_unused_param_type() { + let src = r#" + pub fn foo(_x: I) { } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + assert!(matches!( + errors[0].0, + CompilationError::TypeError(TypeCheckError::TypeKindMismatch { .. }), + )); +} + +#[test] +fn numeric_generic_as_unused_trait_fn_param_type() { + let src = r#" + trait Foo { + fn foo(_x: I) { } + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 2); + assert!(matches!( + errors[0].0, + CompilationError::TypeError(TypeCheckError::TypeKindMismatch { .. }), + )); + // Foo is unused + assert!(matches!( + errors[1].0, + CompilationError::ResolverError(ResolverError::UnusedItem { .. }), + )); +} + +#[test] +fn numeric_generic_as_return_type() { + let src = r#" + // std::mem::zeroed() without stdlib + trait Zeroed { + fn zeroed(self) -> T; + } + + fn foo(x: T) -> I where T: Zeroed { + x.zeroed() + } + + fn main() {} + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 2); + + // Error from the return type + assert!(matches!( + errors[0].0, + CompilationError::TypeError(TypeCheckError::TypeKindMismatch { .. }), + )); + // foo is unused + assert!(matches!( + errors[1].0, + CompilationError::ResolverError(ResolverError::UnusedItem { .. }), + )); +} + #[test] fn numeric_generic_used_in_nested_type_fails() { let src = r#" @@ -1877,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#" @@ -1973,11 +2162,25 @@ fn numeric_generics_type_kind_mismatch() { } "#; 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] @@ -2385,23 +2588,6 @@ fn impl_not_found_for_inner_impl() { )); } -#[test] -fn no_super() { - let src = "use super::some_func;"; - let errors = get_program_errors(src); - assert_eq!(errors.len(), 1); - - let CompilationError::DefinitionError(DefCollectorErrorKind::PathResolutionError( - PathResolutionError::NoSuper(span), - )) = &errors[0].0 - else { - panic!("Expected a 'no super' error, got {:?}", errors[0].0); - }; - - assert_eq!(span.start(), 4); - assert_eq!(span.end(), 9); -} - #[test] fn cannot_call_unconstrained_function_outside_of_unsafe() { let src = r#" @@ -3036,7 +3222,38 @@ fn infer_globals_to_u32_from_type_use() { } #[test] -fn non_u32_in_array_length() { +fn struct_array_len() { + let src = r#" + struct Array { + inner: [T; N], + } + + impl Array { + pub fn len(self) -> u32 { + N as u32 + } + } + + fn main(xs: [Field; 2]) { + let ys = Array { + inner: xs, + }; + assert(ys.len() == 2); + } + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + assert!(matches!( + errors[0].0, + CompilationError::ResolverError(ResolverError::UnusedVariable { .. }) + )); +} + +// TODO(https://github.com/noir-lang/noir/issues/6245): +// support u16 as an array size +#[test] +fn non_u32_as_array_length() { let src = r#" global ARRAY_LEN: u8 = 3; @@ -3046,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 { .. }) )); } @@ -3084,7 +3304,8 @@ fn use_numeric_generic_in_trait_method() { } fn main() { - let _ = Bar{}.foo([1,2,3]); + let bytes: [u8; 3] = [1,2,3]; + let _ = Bar{}.foo(bytes); } "#; @@ -3103,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); } "#; @@ -3123,58 +3344,48 @@ fn trait_unconstrained_methods_typechecked_correctly() { } #[test] -fn errors_if_type_alias_aliases_more_private_type() { +fn error_if_attribute_not_in_scope() { let src = r#" - struct Foo {} - pub type Bar = Foo; - - pub fn no_unused_warnings(_b: Bar) { - let _ = Foo {}; - } - - fn main() {} + #[not_in_scope] + fn main() {} "#; let errors = get_program_errors(src); assert_eq!(errors.len(), 1); - let CompilationError::ResolverError(ResolverError::TypeIsMorePrivateThenItem { - typ, item, .. - }) = &errors[0].0 - else { - panic!("Expected an unused item error"); - }; - - assert_eq!(typ, "Foo"); - assert_eq!(item, "Bar"); + assert!(matches!( + errors[0].0, + CompilationError::ResolverError(ResolverError::AttributeFunctionNotInScope { .. }) + )); } #[test] -fn errors_if_type_alias_aliases_more_private_type_in_generic() { +fn arithmetic_generics_rounding_pass() { let src = r#" - pub struct Generic { value: T } + fn main() { + // 3/2*2 = 2 + round::<3, 2>([1, 2]); + } - struct Foo {} - pub type Bar = Generic; + fn round(_x: [Field; N / M * M]) {} + "#; - pub fn no_unused_warnings(_b: Bar) { - let _ = Foo {}; - let _ = Generic { value: 1 }; - } + let errors = get_program_errors(src); + assert_eq!(errors.len(), 0); +} - fn main() {} +#[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); - - let CompilationError::ResolverError(ResolverError::TypeIsMorePrivateThenItem { - typ, item, .. - }) = &errors[0].0 - else { - panic!("Expected an unused item error"); - }; - - assert_eq!(typ, "Foo"); - assert_eq!(item, "Bar"); } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/tests/imports.rs b/noir/noir-repo/compiler/noirc_frontend/src/tests/imports.rs index dfdc60e15e4..1da7f5ce0ce 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/tests/imports.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/tests/imports.rs @@ -21,6 +21,23 @@ fn use_super() { assert_no_errors(src); } +#[test] +fn no_super() { + let src = "use super::some_func;"; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::DefinitionError(DefCollectorErrorKind::PathResolutionError( + PathResolutionError::NoSuper(span), + )) = &errors[0].0 + else { + panic!("Expected a 'no super' error, got {:?}", errors[0].0); + }; + + assert_eq!(span.start(), 4); + assert_eq!(span.end(), 9); +} + #[test] fn use_super_in_path() { let src = r#" @@ -56,7 +73,7 @@ fn warns_on_use_of_private_exported_item() { "#; let errors = get_program_errors(src); - assert_eq!(errors.len(), 2); // An existing bug causes this error to be duplicated + assert_eq!(errors.len(), 1); assert!(matches!( &errors[0].0, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/tests/metaprogramming.rs b/noir/noir-repo/compiler/noirc_frontend/src/tests/metaprogramming.rs index d980cba5cfd..ec52310b3d6 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/tests/metaprogramming.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/tests/metaprogramming.rs @@ -2,6 +2,17 @@ use crate::hir::def_collector::dc_crate::CompilationError; use super::get_program_errors; +// Regression for #5388 +#[test] +fn comptime_let() { + let src = r#"fn main() { + comptime let my_var = 2; + assert_eq(my_var, 2); + }"#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 0); +} + #[test] fn comptime_type_in_runtime_code() { let source = "pub fn foo(_f: FunctionDefinition) {}"; 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/turbofish.rs b/noir/noir-repo/compiler/noirc_frontend/src/tests/turbofish.rs index 43d536fd196..71e63e878e8 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/tests/turbofish.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/tests/turbofish.rs @@ -29,11 +29,11 @@ fn turbofish_numeric_generic_nested_call() { } impl Foo { - fn static_method() -> [u8; N] { + pub fn static_method() -> [u8; N] { [0; N] } - fn impl_method(self) -> [T; N] { + pub fn impl_method(self) -> [T; N] { [self.a; N] } } @@ -108,7 +108,7 @@ fn turbofish_in_middle_of_variable_unsupported_yet() { } impl Foo { - fn new(x: T) -> Self { + pub fn new(x: T) -> Self { Foo { x } } } @@ -196,3 +196,21 @@ fn turbofish_in_struct_pattern_generic_count_mismatch() { assert_eq!(*expected, 1); assert_eq!(*found, 2); } + +#[test] +fn numeric_turbofish() { + let src = r#" + struct Reader { + } + + impl Reader { + fn read(_self: Self) {} + } + + fn main() { + let reader: Reader<1234> = Reader {}; + let _ = reader.read::<1234>(); + } + "#; + assert_no_errors(src); +} 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 a4d379b6358..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 @@ -189,76 +189,88 @@ fn errors_on_unused_type_alias() { } #[test] -fn errors_if_type_alias_aliases_more_private_type() { +fn warns_on_unused_global() { let src = r#" - struct Foo {} - pub type Bar = Foo; - pub fn no_unused_warnings(_b: Bar) { - let _ = Foo {}; + global foo = 1; + global bar = 1; + + fn main() { + let _ = bar; } - fn main() {} "#; let errors = get_program_errors(src); assert_eq!(errors.len(), 1); - let CompilationError::ResolverError(ResolverError::TypeIsMorePrivateThenItem { - typ, item, .. - }) = &errors[0].0 + let CompilationError::ResolverError(ResolverError::UnusedItem { ident, item }) = &errors[0].0 else { - panic!("Expected an unused item error"); + panic!("Expected an unused item warning"); }; - assert_eq!(typ, "Foo"); - assert_eq!(item, "Bar"); + assert_eq!(ident.to_string(), "foo"); + assert_eq!(item.item_type(), "global"); } #[test] -fn errors_if_type_alias_aliases_more_private_type_in_generic() { +fn does_not_warn_on_unused_global_if_it_has_an_abi_attribute() { let src = r#" - pub struct Generic { value: T } - struct Foo {} - pub type Bar = Generic; - pub fn no_unused_warnings(_b: Bar) { - let _ = Foo {}; - let _ = Generic { value: 1 }; + contract foo { + #[abi(notes)] + global bar = 1; } + fn main() {} "#; + assert_no_errors(src); +} - let errors = get_program_errors(src); - assert_eq!(errors.len(), 1); +#[test] +fn no_warning_on_inner_struct_when_parent_is_used() { + let src = r#" + struct Bar { + inner: [Field; 3], + } - let CompilationError::ResolverError(ResolverError::TypeIsMorePrivateThenItem { - typ, item, .. - }) = &errors[0].0 - else { - panic!("Expected an unused item error"); - }; + struct Foo { + a: Field, + bar: Bar, + } + + fn main(foos: [Foo; 1]) { + assert_eq(foos[0].a, 10); + } + "#; - assert_eq!(typ, "Foo"); - assert_eq!(item, "Bar"); + let errors = get_program_errors(src); + assert_eq!(errors.len(), 0); } #[test] -fn warns_on_unused_global() { - let src = r#" - global foo = 1; - global bar = 1; - - fn main() { - let _ = bar; +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); +} - let errors = get_program_errors(src); - assert_eq!(errors.len(), 1); +#[test] +fn no_warning_on_indirect_struct_if_it_has_an_abi_attribute() { + let src = r#" + struct Bar { + field: Field, + } - let CompilationError::ResolverError(ResolverError::UnusedItem { ident, item }) = &errors[0].0 - else { - panic!("Expected an unused item warning"); - }; + #[abi(functions)] + struct Foo { + bar: Bar, + } - assert_eq!(ident.to_string(), "foo"); - assert_eq!(item.item_type(), "global"); + 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 e6c2680ea19..f02771b3760 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/tests/visibility.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/tests/visibility.rs @@ -3,7 +3,7 @@ use crate::{ def_collector::{dc_crate::CompilationError, errors::DefCollectorErrorKind}, resolution::{errors::ResolverError, import::PathResolutionError}, }, - tests::get_program_errors, + tests::{assert_no_errors, get_program_errors}, }; #[test] @@ -28,17 +28,14 @@ fn errors_once_on_unused_import_that_is_not_accessible() { )) )); } - #[test] fn errors_if_type_alias_aliases_more_private_type() { let src = r#" struct Foo {} pub type Bar = Foo; - pub fn no_unused_warnings(_b: Bar) { let _ = Foo {}; } - fn main() {} "#; @@ -60,15 +57,12 @@ fn errors_if_type_alias_aliases_more_private_type() { fn errors_if_type_alias_aliases_more_private_type_in_generic() { let src = r#" pub struct Generic { value: T } - struct Foo {} pub type Bar = Generic; - pub fn no_unused_warnings(_b: Bar) { let _ = Foo {}; let _ = Generic { value: 1 }; } - fn main() {} "#; @@ -100,7 +94,7 @@ fn errors_if_trying_to_access_public_function_inside_private_module() { "#; let errors = get_program_errors(src); - assert_eq!(errors.len(), 2); // There's a bug that duplicates this error + assert_eq!(errors.len(), 1); let CompilationError::ResolverError(ResolverError::PathResolutionError( PathResolutionError::Private(ident), @@ -111,3 +105,242 @@ 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#" + struct Foo { + + } + + impl Foo { + fn foo() { + Foo::bar() + } + + fn bar() {} + } + + fn main() { + let _ = Foo {}; + } + "#; + assert_no_errors(src); +} + +#[test] +fn does_not_error_if_calling_private_struct_function_from_same_module() { + let src = r#" + struct Foo; + + impl Foo { + fn bar() -> Field { + 0 + } + } + + fn main() { + let _ = Foo {}; + assert_eq(Foo::bar(), 0); + } + "#; + 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/compiler/noirc_frontend/src/usage_tracker.rs b/noir/noir-repo/compiler/noirc_frontend/src/usage_tracker.rs index 275ca1f964b..0a112c6937d 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/usage_tracker.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/usage_tracker.rs @@ -3,8 +3,7 @@ use std::collections::HashMap; use crate::{ ast::{Ident, ItemVisibility}, hir::def_map::ModuleId, - macros_api::StructId, - node_interner::{FuncId, GlobalId, TraitId, TypeAliasId}, + node_interner::{FuncId, GlobalId, StructId, TraitId, TypeAliasId}, }; #[derive(Debug, Copy, Clone, PartialEq, Eq)] diff --git a/noir/noir-repo/compiler/wasm/package.json b/noir/noir-repo/compiler/wasm/package.json index e20abe5f4ff..009f861c5b1 100644 --- a/noir/noir-repo/compiler/wasm/package.json +++ b/noir/noir-repo/compiler/wasm/package.json @@ -3,7 +3,7 @@ "contributors": [ "The Noir Team " ], - "version": "0.34.0", + "version": "0.35.0", "license": "(MIT OR Apache-2.0)", "main": "dist/main.js", "types": "./dist/types/src/index.d.cts", 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/explainers/explainer-recursion.md b/noir/noir-repo/docs/docs/explainers/explainer-recursion.md index 18846176ca7..df8529ef4e0 100644 --- a/noir/noir-repo/docs/docs/explainers/explainer-recursion.md +++ b/noir/noir-repo/docs/docs/explainers/explainer-recursion.md @@ -111,7 +111,7 @@ He might find it more efficient to generate a proof for that setup phase separat ## What params do I need -As you can see in the [recursion reference](noir/standard_library/recursion.md), a simple recursive proof requires: +As you can see in the [recursion reference](noir/standard_library/recursion.mdx), a simple recursive proof requires: - The proof to verify - The Verification Key of the circuit that generated the proof diff --git a/noir/noir-repo/docs/docs/how_to/how-to-recursion.md b/noir/noir-repo/docs/docs/how_to/how-to-recursion.md index 71f02fa5435..c8c4dc9f5b4 100644 --- a/noir/noir-repo/docs/docs/how_to/how-to-recursion.md +++ b/noir/noir-repo/docs/docs/how_to/how-to-recursion.md @@ -25,7 +25,7 @@ This guide shows you how to use recursive proofs in your NoirJS app. For the sak - You already have a NoirJS app. If you don't, please visit the [NoirJS tutorial](../tutorials/noirjs_app.md) and the [reference](../reference/NoirJS/noir_js/index.md). - You are familiar with what are recursive proofs and you have read the [recursion explainer](../explainers/explainer-recursion.md) -- You already built a recursive circuit following [the reference](../noir/standard_library/recursion.md), and understand how it works. +- You already built a recursive circuit following [the reference](../noir/standard_library/recursion.mdx), and understand how it works. It is also assumed that you're not using `noir_wasm` for compilation, and instead you've used [`nargo compile`](../reference/nargo_commands.md) to generate the `json` you're now importing into your project. However, the guide should work just the same if you're using `noir_wasm`. diff --git a/noir/noir-repo/docs/docs/noir/concepts/control_flow.md b/noir/noir-repo/docs/docs/noir/concepts/control_flow.md index 045d3c3a5f5..b365bb22728 100644 --- a/noir/noir-repo/docs/docs/noir/concepts/control_flow.md +++ b/noir/noir-repo/docs/docs/noir/concepts/control_flow.md @@ -42,6 +42,8 @@ for i in 0..10 { } ``` +Alternatively, `start..=end` can be used for a range that is inclusive on both ends. + The index for loops is of type `u64`. ### Break and Continue 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/black_box_fns.md b/noir/noir-repo/docs/docs/noir/standard_library/black_box_fns.md index d5694250f05..d6079ab182c 100644 --- a/noir/noir-repo/docs/docs/noir/standard_library/black_box_fns.md +++ b/noir/noir-repo/docs/docs/noir/standard_library/black_box_fns.md @@ -25,7 +25,7 @@ Here is a list of the current black box functions: - XOR - RANGE - [Keccak256](./cryptographic_primitives/hashes.mdx#keccak256) -- [Recursive proof verification](./recursion.md) +- [Recursive proof verification](./recursion.mdx) Most black box functions are included as part of the Noir standard library, however `AND`, `XOR` and `RANGE` are used as part of the Noir language syntax. For instance, using the bitwise operator `&` will invoke the `AND` black box function. diff --git a/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/ciphers.mdx b/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/ciphers.mdx index 0103791d2e4..d2ceb63175a 100644 --- a/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/ciphers.mdx +++ b/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/ciphers.mdx @@ -7,7 +7,7 @@ keywords: sidebar_position: 0 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; ## aes128 @@ -25,4 +25,4 @@ fn main() { ``` - \ No newline at end of file + \ No newline at end of file diff --git a/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx b/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx index 6787c9f46a1..d46bdc0729b 100644 --- a/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx +++ b/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx @@ -5,7 +5,7 @@ keywords: [cryptographic primitives, Noir project, ecdsa, secp256k1, secp256r1, sidebar_position: 3 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; Noir supports ECDSA signatures verification over the secp256k1 and secp256r1 curves. @@ -25,7 +25,7 @@ fn main(hashed_message : [u8;32], pub_key_x : [u8;32], pub_key_y : [u8;32], sign } ``` - + ## ecdsa_secp256k1::verify_signature_slice @@ -33,7 +33,7 @@ Verifier for ECDSA Secp256k1 signatures where the message is a slice. #include_code ecdsa_secp256k1_slice noir_stdlib/src/ecdsa_secp256k1.nr rust - + ## ecdsa_secp256r1::verify_signature @@ -51,7 +51,7 @@ fn main(hashed_message : [u8;32], pub_key_x : [u8;32], pub_key_y : [u8;32], sign } ``` - + ## ecdsa_secp256r1::verify_signature @@ -59,4 +59,4 @@ Verifier for ECDSA Secp256r1 signatures where the message is a slice. #include_code ecdsa_secp256r1_slice noir_stdlib/src/ecdsa_secp256r1.nr rust - + diff --git a/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/eddsa.mdx b/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/eddsa.mdx index 1ad42a5ac96..b283de693c8 100644 --- a/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/eddsa.mdx +++ b/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/eddsa.mdx @@ -5,7 +5,7 @@ keywords: [cryptographic primitives, Noir project, eddsa, signatures] sidebar_position: 5 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; ## eddsa::eddsa_poseidon_verify @@ -23,7 +23,7 @@ use std::hash::poseidon2::Poseidon2Hasher; eddsa_verify::(pub_key_a.x, pub_key_a.y, s_a, r8_a.x, r8_a.y, msg); ``` - + ## eddsa::eddsa_to_pub diff --git a/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx b/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx index f1122fc37d5..e10688857a6 100644 --- a/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx +++ b/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx @@ -5,7 +5,7 @@ keywords: [cryptographic primitives, Noir project, scalar multiplication] sidebar_position: 1 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; The following functions perform operations over the embedded curve whose coordinates are defined by the configured noir field. For the BN254 scalar field, this is BabyJubJub or Grumpkin. @@ -74,4 +74,4 @@ fn main() { } ``` - + diff --git a/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/hashes.mdx b/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/hashes.mdx index d2a8204bccb..c33ce34e4d1 100644 --- a/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/hashes.mdx +++ b/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/hashes.mdx @@ -8,7 +8,7 @@ keywords: sidebar_position: 0 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; ## sha256 @@ -28,7 +28,7 @@ fn main() { ``` - + ## blake2s @@ -45,7 +45,7 @@ fn main() { } ``` - + ## blake3 @@ -62,7 +62,7 @@ fn main() { } ``` - + ## pedersen_hash @@ -74,7 +74,7 @@ example: #include_code pedersen-hash test_programs/execution_success/pedersen_hash/src/main.nr rust - + ## pedersen_commitment @@ -86,7 +86,7 @@ example: #include_code pedersen-commitment test_programs/execution_success/pedersen_commitment/src/main.nr rust - + ## keccak256 @@ -100,7 +100,7 @@ example: #include_code keccak256 test_programs/execution_success/keccak256/src/main.nr rust - + ## poseidon diff --git a/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/schnorr.mdx b/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/schnorr.mdx index 2c9eb18cd34..286a0ac6c7d 100644 --- a/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/schnorr.mdx +++ b/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/schnorr.mdx @@ -5,7 +5,7 @@ keywords: [cryptographic primitives, Noir project, schnorr, signatures] sidebar_position: 2 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; ## schnorr::verify_signature @@ -34,7 +34,7 @@ const signature = Array.from( ... ``` - + ## schnorr::verify_signature_slice @@ -43,4 +43,4 @@ where the message is a slice. #include_code schnorr_verify_slice noir_stdlib/src/schnorr.nr 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/meta/typ.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/typ.md index 5a8b43b1dfa..71a36e629c6 100644 --- a/noir/noir-repo/docs/docs/noir/standard_library/meta/typ.md +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/typ.md @@ -63,6 +63,12 @@ return the numeric constant. If this is an integer type, return a boolean which is `true` if the type is signed, as well as the number of bits of this integer type. +### as_mutable_reference + +#include_code as_mutable_reference noir_stdlib/src/meta/typ.nr rust + +If this is a mutable reference type `&mut T`, returns the mutable type `T`. + ### as_slice #include_code as_slice noir_stdlib/src/meta/typ.nr rust @@ -146,6 +152,12 @@ fn foo() where T: Default { `true` if this type is `Field`. +### is_unit + +#include_code is_unit noir_stdlib/src/meta/typ.nr rust + +`true` if this type is the unit `()` type. + ## Trait Implementations ```rust diff --git a/noir/noir-repo/docs/docs/noir/standard_library/meta/unresolved_type.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/unresolved_type.md index 9c61f91dee2..8535cbab19c 100644 --- a/noir/noir-repo/docs/docs/noir/standard_library/meta/unresolved_type.md +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/unresolved_type.md @@ -6,8 +6,32 @@ title: UnresolvedType ## Methods +### as_mutable_reference + +#include_code as_mutable_reference noir_stdlib/src/meta/unresolved_type.nr rust + +If this is a mutable reference type `&mut T`, returns the mutable type `T`. + +### as_slice + +#include_code as_slice noir_stdlib/src/meta/unresolved_type.nr rust + +If this is a slice `&[T]`, returns the element type `T`. + +### is_bool + +#include_code is_bool noir_stdlib/src/meta/unresolved_type.nr rust + +Returns `true` if this type is `bool`. + ### is_field #include_code is_field noir_stdlib/src/meta/unresolved_type.nr rust Returns true if this type refers to the Field type. + +### is_unit + +#include_code is_unit noir_stdlib/src/meta/unresolved_type.nr rust + +Returns true if this type is the unit `()` type. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/recursion.md b/noir/noir-repo/docs/docs/noir/standard_library/recursion.mdx similarity index 96% rename from noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/recursion.md rename to noir/noir-repo/docs/docs/noir/standard_library/recursion.mdx index 7f4dcebf084..60414a2fa51 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/recursion.md +++ b/noir/noir-repo/docs/docs/noir/standard_library/recursion.mdx @@ -4,7 +4,7 @@ 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.mdx'; +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. @@ -35,7 +35,7 @@ By incorporating this attribute directly in the circuit's definition, tooling li pub fn verify_proof(verification_key: [Field], proof: [Field], public_inputs: [Field], key_hash: Field) {} ``` - + ## Example usage diff --git a/noir/noir-repo/docs/src/components/Notes/_blackbox.jsx b/noir/noir-repo/docs/src/components/Notes/_blackbox.jsx new file mode 100644 index 00000000000..ae3c5987cb6 --- /dev/null +++ b/noir/noir-repo/docs/src/components/Notes/_blackbox.jsx @@ -0,0 +1,12 @@ +import Link from '@docusaurus/Link'; + +export default function BlackBoxInfo({ to }) { + return ( +

+ ); +} diff --git a/noir/noir-repo/docs/src/components/Notes/_blackbox.mdx b/noir/noir-repo/docs/src/components/Notes/_blackbox.mdx deleted file mode 100644 index 514ca00a7e7..00000000000 --- a/noir/noir-repo/docs/src/components/Notes/_blackbox.mdx +++ /dev/null @@ -1,5 +0,0 @@ -:::info - -This is a black box function. Read [this section](/docs/noir/standard_library/black_box_fns) to learn more about black box functions in Noir. - -::: diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/explainers/explainer-recursion.md b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/explainers/explainer-recursion.md index 18846176ca7..df8529ef4e0 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/explainers/explainer-recursion.md +++ b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/explainers/explainer-recursion.md @@ -111,7 +111,7 @@ He might find it more efficient to generate a proof for that setup phase separat ## What params do I need -As you can see in the [recursion reference](noir/standard_library/recursion.md), a simple recursive proof requires: +As you can see in the [recursion reference](noir/standard_library/recursion.mdx), a simple recursive proof requires: - The proof to verify - The Verification Key of the circuit that generated the proof diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/how_to/how-to-recursion.md b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/how_to/how-to-recursion.md index 71f02fa5435..c8c4dc9f5b4 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/how_to/how-to-recursion.md +++ b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/how_to/how-to-recursion.md @@ -25,7 +25,7 @@ This guide shows you how to use recursive proofs in your NoirJS app. For the sak - You already have a NoirJS app. If you don't, please visit the [NoirJS tutorial](../tutorials/noirjs_app.md) and the [reference](../reference/NoirJS/noir_js/index.md). - You are familiar with what are recursive proofs and you have read the [recursion explainer](../explainers/explainer-recursion.md) -- You already built a recursive circuit following [the reference](../noir/standard_library/recursion.md), and understand how it works. +- You already built a recursive circuit following [the reference](../noir/standard_library/recursion.mdx), and understand how it works. It is also assumed that you're not using `noir_wasm` for compilation, and instead you've used [`nargo compile`](../reference/nargo_commands.md) to generate the `json` you're now importing into your project. However, the guide should work just the same if you're using `noir_wasm`. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/noir/standard_library/black_box_fns.md b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/noir/standard_library/black_box_fns.md index d5694250f05..d6079ab182c 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/noir/standard_library/black_box_fns.md +++ b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/noir/standard_library/black_box_fns.md @@ -25,7 +25,7 @@ Here is a list of the current black box functions: - XOR - RANGE - [Keccak256](./cryptographic_primitives/hashes.mdx#keccak256) -- [Recursive proof verification](./recursion.md) +- [Recursive proof verification](./recursion.mdx) Most black box functions are included as part of the Noir standard library, however `AND`, `XOR` and `RANGE` are used as part of the Noir language syntax. For instance, using the bitwise operator `&` will invoke the `AND` black box function. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/ciphers.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/ciphers.mdx index d75e50d4b89..d6a5e1a79eb 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/ciphers.mdx +++ b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/ciphers.mdx @@ -7,7 +7,7 @@ keywords: sidebar_position: 0 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; ## aes128 @@ -29,4 +29,4 @@ fn main() { ``` - \ No newline at end of file + \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx index 8520071e95f..4c22e70e8de 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx +++ b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx @@ -5,7 +5,7 @@ keywords: [cryptographic primitives, Noir project, ecdsa, secp256k1, secp256r1, sidebar_position: 3 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; Noir supports ECDSA signatures verification over the secp256k1 and secp256r1 curves. @@ -34,7 +34,7 @@ fn main(hashed_message : [u8;32], pub_key_x : [u8;32], pub_key_y : [u8;32], sign } ``` - + ## ecdsa_secp256k1::verify_signature_slice @@ -51,7 +51,7 @@ pub fn verify_signature_slice( > Source code: noir_stdlib/src/ecdsa_secp256k1.nr#L13-L20 - + ## ecdsa_secp256r1::verify_signature @@ -78,7 +78,7 @@ fn main(hashed_message : [u8;32], pub_key_x : [u8;32], pub_key_y : [u8;32], sign } ``` - + ## ecdsa_secp256r1::verify_signature @@ -95,4 +95,4 @@ pub fn verify_signature_slice( > Source code: noir_stdlib/src/ecdsa_secp256r1.nr#L13-L20 - + diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/eddsa.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/eddsa.mdx index 1ad42a5ac96..ef4386052eb 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/eddsa.mdx +++ b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/eddsa.mdx @@ -5,7 +5,7 @@ keywords: [cryptographic primitives, Noir project, eddsa, signatures] sidebar_position: 5 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; ## eddsa::eddsa_poseidon_verify @@ -23,7 +23,7 @@ use std::hash::poseidon2::Poseidon2Hasher; eddsa_verify::(pub_key_a.x, pub_key_a.y, s_a, r8_a.x, r8_a.y, msg); ``` - + ## eddsa::eddsa_to_pub diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx index 0230f6a8ab9..68d033e9d60 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx +++ b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx @@ -5,7 +5,7 @@ keywords: [cryptographic primitives, Noir project, scalar multiplication] sidebar_position: 1 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; The following functions perform operations over the embedded curve whose coordinates are defined by the configured noir field. For the BN254 scalar field, this is BabyJubJub or Grumpkin. @@ -95,4 +95,4 @@ fn main() { } ``` - + diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/hashes.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/hashes.mdx index dadff87bb69..ddcfbb2175f 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/hashes.mdx +++ b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/hashes.mdx @@ -8,7 +8,7 @@ keywords: sidebar_position: 0 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; ## sha256 @@ -36,7 +36,7 @@ fn main() { ``` - + ## blake2s @@ -57,7 +57,7 @@ fn main() { } ``` - + ## blake3 @@ -78,7 +78,7 @@ fn main() { } ``` - + ## pedersen_hash @@ -101,7 +101,7 @@ fn main(x: Field, y: Field, expected_hash: Field) { > Source code: test_programs/execution_success/pedersen_hash/src/main.nr#L1-L7 - + ## pedersen_commitment @@ -125,7 +125,7 @@ fn main(x: Field, y: Field, expected_commitment: std::embedded_curve_ops::Embedd > Source code: test_programs/execution_success/pedersen_commitment/src/main.nr#L1-L8 - + ## keccak256 @@ -164,7 +164,7 @@ fn main(x: Field, result: [u8; 32]) { > Source code: test_programs/execution_success/keccak256/src/main.nr#L1-L21 - + ## poseidon diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/schnorr.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/schnorr.mdx index a32138daaa6..00e7f257612 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/schnorr.mdx +++ b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/schnorr.mdx @@ -5,7 +5,7 @@ keywords: [cryptographic primitives, Noir project, schnorr, signatures] sidebar_position: 2 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; ## schnorr::verify_signature @@ -43,7 +43,7 @@ const signature = Array.from( ... ``` - + ## schnorr::verify_signature_slice @@ -61,4 +61,4 @@ pub fn verify_signature_slice( > Source code: noir_stdlib/src/schnorr.nr#L13-L20 - + diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/noir/standard_library/recursion.md b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/noir/standard_library/recursion.mdx similarity index 96% rename from noir/noir-repo/docs/versioned_docs/version-v0.32.0/noir/standard_library/recursion.md rename to noir/noir-repo/docs/versioned_docs/version-v0.32.0/noir/standard_library/recursion.mdx index 8cfb37fc52d..8fdb8e8f514 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.32.0/noir/standard_library/recursion.md +++ b/noir/noir-repo/docs/versioned_docs/version-v0.32.0/noir/standard_library/recursion.mdx @@ -4,7 +4,7 @@ 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.mdx'; +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. @@ -35,7 +35,7 @@ By incorporating this attribute directly in the circuit's definition, tooling li pub fn verify_proof(verification_key: [Field], proof: [Field], public_inputs: [Field], key_hash: Field) {} ``` - + ## Example usage diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/explainers/explainer-recursion.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/explainers/explainer-recursion.md index 18846176ca7..df8529ef4e0 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/explainers/explainer-recursion.md +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/explainers/explainer-recursion.md @@ -111,7 +111,7 @@ He might find it more efficient to generate a proof for that setup phase separat ## What params do I need -As you can see in the [recursion reference](noir/standard_library/recursion.md), a simple recursive proof requires: +As you can see in the [recursion reference](noir/standard_library/recursion.mdx), a simple recursive proof requires: - The proof to verify - The Verification Key of the circuit that generated the proof diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/how-to-recursion.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/how-to-recursion.md index 71f02fa5435..c8c4dc9f5b4 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/how-to-recursion.md +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/how_to/how-to-recursion.md @@ -25,7 +25,7 @@ This guide shows you how to use recursive proofs in your NoirJS app. For the sak - You already have a NoirJS app. If you don't, please visit the [NoirJS tutorial](../tutorials/noirjs_app.md) and the [reference](../reference/NoirJS/noir_js/index.md). - You are familiar with what are recursive proofs and you have read the [recursion explainer](../explainers/explainer-recursion.md) -- You already built a recursive circuit following [the reference](../noir/standard_library/recursion.md), and understand how it works. +- You already built a recursive circuit following [the reference](../noir/standard_library/recursion.mdx), and understand how it works. It is also assumed that you're not using `noir_wasm` for compilation, and instead you've used [`nargo compile`](../reference/nargo_commands.md) to generate the `json` you're now importing into your project. However, the guide should work just the same if you're using `noir_wasm`. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/black_box_fns.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/black_box_fns.md index d5694250f05..d6079ab182c 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/black_box_fns.md +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/black_box_fns.md @@ -25,7 +25,7 @@ Here is a list of the current black box functions: - XOR - RANGE - [Keccak256](./cryptographic_primitives/hashes.mdx#keccak256) -- [Recursive proof verification](./recursion.md) +- [Recursive proof verification](./recursion.mdx) Most black box functions are included as part of the Noir standard library, however `AND`, `XOR` and `RANGE` are used as part of the Noir language syntax. For instance, using the bitwise operator `&` will invoke the `AND` black box function. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/ciphers.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/ciphers.mdx index d75e50d4b89..d6a5e1a79eb 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/ciphers.mdx +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/ciphers.mdx @@ -7,7 +7,7 @@ keywords: sidebar_position: 0 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; ## aes128 @@ -29,4 +29,4 @@ fn main() { ``` - \ No newline at end of file + \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx index 8520071e95f..4c22e70e8de 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx @@ -5,7 +5,7 @@ keywords: [cryptographic primitives, Noir project, ecdsa, secp256k1, secp256r1, sidebar_position: 3 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; Noir supports ECDSA signatures verification over the secp256k1 and secp256r1 curves. @@ -34,7 +34,7 @@ fn main(hashed_message : [u8;32], pub_key_x : [u8;32], pub_key_y : [u8;32], sign } ``` - + ## ecdsa_secp256k1::verify_signature_slice @@ -51,7 +51,7 @@ pub fn verify_signature_slice( > Source code: noir_stdlib/src/ecdsa_secp256k1.nr#L13-L20 - + ## ecdsa_secp256r1::verify_signature @@ -78,7 +78,7 @@ fn main(hashed_message : [u8;32], pub_key_x : [u8;32], pub_key_y : [u8;32], sign } ``` - + ## ecdsa_secp256r1::verify_signature @@ -95,4 +95,4 @@ pub fn verify_signature_slice( > Source code: noir_stdlib/src/ecdsa_secp256r1.nr#L13-L20 - + diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/eddsa.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/eddsa.mdx index 1ad42a5ac96..b283de693c8 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/eddsa.mdx +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/eddsa.mdx @@ -5,7 +5,7 @@ keywords: [cryptographic primitives, Noir project, eddsa, signatures] sidebar_position: 5 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; ## eddsa::eddsa_poseidon_verify @@ -23,7 +23,7 @@ use std::hash::poseidon2::Poseidon2Hasher; eddsa_verify::(pub_key_a.x, pub_key_a.y, s_a, r8_a.x, r8_a.y, msg); ``` - + ## eddsa::eddsa_to_pub diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx index 719549bc418..69e0265c81a 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx @@ -5,7 +5,7 @@ keywords: [cryptographic primitives, Noir project, scalar multiplication] sidebar_position: 1 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; The following functions perform operations over the embedded curve whose coordinates are defined by the configured noir field. For the BN254 scalar field, this is BabyJubJub or Grumpkin. @@ -92,4 +92,4 @@ fn main() { } ``` - + diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/hashes.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/hashes.mdx index 63a4a2afd53..797ff8cc22c 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/hashes.mdx +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/hashes.mdx @@ -8,7 +8,7 @@ keywords: sidebar_position: 0 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; ## sha256 @@ -36,7 +36,7 @@ fn main() { ``` - + ## blake2s @@ -57,7 +57,7 @@ fn main() { } ``` - + ## blake3 @@ -78,7 +78,7 @@ fn main() { } ``` - + ## pedersen_hash @@ -101,7 +101,7 @@ fn main(x: Field, y: Field, expected_hash: Field) { > Source code: test_programs/execution_success/pedersen_hash/src/main.nr#L1-L7 - + ## pedersen_commitment @@ -125,7 +125,7 @@ fn main(x: Field, y: Field, expected_commitment: std::embedded_curve_ops::Embedd > Source code: test_programs/execution_success/pedersen_commitment/src/main.nr#L1-L8 - + ## keccak256 @@ -164,7 +164,7 @@ fn main(x: Field, result: [u8; 32]) { > Source code: test_programs/execution_success/keccak256/src/main.nr#L1-L21 - + ## poseidon diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/schnorr.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/schnorr.mdx index a32138daaa6..00e7f257612 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/schnorr.mdx +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/schnorr.mdx @@ -5,7 +5,7 @@ keywords: [cryptographic primitives, Noir project, schnorr, signatures] sidebar_position: 2 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; ## schnorr::verify_signature @@ -43,7 +43,7 @@ const signature = Array.from( ... ``` - + ## schnorr::verify_signature_slice @@ -61,4 +61,4 @@ pub fn verify_signature_slice( > Source code: noir_stdlib/src/schnorr.nr#L13-L20 - + diff --git a/noir/noir-repo/docs/docs/noir/standard_library/recursion.md b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/recursion.mdx similarity index 96% rename from noir/noir-repo/docs/docs/noir/standard_library/recursion.md rename to noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/recursion.mdx index 7f4dcebf084..60414a2fa51 100644 --- a/noir/noir-repo/docs/docs/noir/standard_library/recursion.md +++ b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/recursion.mdx @@ -4,7 +4,7 @@ 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.mdx'; +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. @@ -35,7 +35,7 @@ By incorporating this attribute directly in the circuit's definition, tooling li pub fn verify_proof(verification_key: [Field], proof: [Field], public_inputs: [Field], key_hash: Field) {} ``` - + ## Example usage diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.34.0/explainers/explainer-recursion.md b/noir/noir-repo/docs/versioned_docs/version-v0.34.0/explainers/explainer-recursion.md index 18846176ca7..df8529ef4e0 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.34.0/explainers/explainer-recursion.md +++ b/noir/noir-repo/docs/versioned_docs/version-v0.34.0/explainers/explainer-recursion.md @@ -111,7 +111,7 @@ He might find it more efficient to generate a proof for that setup phase separat ## What params do I need -As you can see in the [recursion reference](noir/standard_library/recursion.md), a simple recursive proof requires: +As you can see in the [recursion reference](noir/standard_library/recursion.mdx), a simple recursive proof requires: - The proof to verify - The Verification Key of the circuit that generated the proof diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.34.0/how_to/how-to-recursion.md b/noir/noir-repo/docs/versioned_docs/version-v0.34.0/how_to/how-to-recursion.md index 71f02fa5435..c8c4dc9f5b4 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.34.0/how_to/how-to-recursion.md +++ b/noir/noir-repo/docs/versioned_docs/version-v0.34.0/how_to/how-to-recursion.md @@ -25,7 +25,7 @@ This guide shows you how to use recursive proofs in your NoirJS app. For the sak - You already have a NoirJS app. If you don't, please visit the [NoirJS tutorial](../tutorials/noirjs_app.md) and the [reference](../reference/NoirJS/noir_js/index.md). - You are familiar with what are recursive proofs and you have read the [recursion explainer](../explainers/explainer-recursion.md) -- You already built a recursive circuit following [the reference](../noir/standard_library/recursion.md), and understand how it works. +- You already built a recursive circuit following [the reference](../noir/standard_library/recursion.mdx), and understand how it works. It is also assumed that you're not using `noir_wasm` for compilation, and instead you've used [`nargo compile`](../reference/nargo_commands.md) to generate the `json` you're now importing into your project. However, the guide should work just the same if you're using `noir_wasm`. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/black_box_fns.md b/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/black_box_fns.md index d5694250f05..d6079ab182c 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/black_box_fns.md +++ b/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/black_box_fns.md @@ -25,7 +25,7 @@ Here is a list of the current black box functions: - XOR - RANGE - [Keccak256](./cryptographic_primitives/hashes.mdx#keccak256) -- [Recursive proof verification](./recursion.md) +- [Recursive proof verification](./recursion.mdx) Most black box functions are included as part of the Noir standard library, however `AND`, `XOR` and `RANGE` are used as part of the Noir language syntax. For instance, using the bitwise operator `&` will invoke the `AND` black box function. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/ciphers.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/ciphers.mdx index d75e50d4b89..d6a5e1a79eb 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/ciphers.mdx +++ b/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/ciphers.mdx @@ -7,7 +7,7 @@ keywords: sidebar_position: 0 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; ## aes128 @@ -29,4 +29,4 @@ fn main() { ``` - \ No newline at end of file + \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx index 8520071e95f..4c22e70e8de 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx +++ b/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx @@ -5,7 +5,7 @@ keywords: [cryptographic primitives, Noir project, ecdsa, secp256k1, secp256r1, sidebar_position: 3 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; Noir supports ECDSA signatures verification over the secp256k1 and secp256r1 curves. @@ -34,7 +34,7 @@ fn main(hashed_message : [u8;32], pub_key_x : [u8;32], pub_key_y : [u8;32], sign } ``` - + ## ecdsa_secp256k1::verify_signature_slice @@ -51,7 +51,7 @@ pub fn verify_signature_slice( > Source code: noir_stdlib/src/ecdsa_secp256k1.nr#L13-L20 - + ## ecdsa_secp256r1::verify_signature @@ -78,7 +78,7 @@ fn main(hashed_message : [u8;32], pub_key_x : [u8;32], pub_key_y : [u8;32], sign } ``` - + ## ecdsa_secp256r1::verify_signature @@ -95,4 +95,4 @@ pub fn verify_signature_slice( > Source code: noir_stdlib/src/ecdsa_secp256r1.nr#L13-L20 - + diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/eddsa.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/eddsa.mdx index 1ad42a5ac96..b283de693c8 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/eddsa.mdx +++ b/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/eddsa.mdx @@ -5,7 +5,7 @@ keywords: [cryptographic primitives, Noir project, eddsa, signatures] sidebar_position: 5 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; ## eddsa::eddsa_poseidon_verify @@ -23,7 +23,7 @@ use std::hash::poseidon2::Poseidon2Hasher; eddsa_verify::(pub_key_a.x, pub_key_a.y, s_a, r8_a.x, r8_a.y, msg); ``` - + ## eddsa::eddsa_to_pub diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx index 8ded020bf27..2da1e34f008 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx +++ b/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx @@ -5,7 +5,7 @@ keywords: [cryptographic primitives, Noir project, scalar multiplication] sidebar_position: 1 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; The following functions perform operations over the embedded curve whose coordinates are defined by the configured noir field. For the BN254 scalar field, this is BabyJubJub or Grumpkin. @@ -92,4 +92,4 @@ fn main() { } ``` - + diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/hashes.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/hashes.mdx index 2581690e034..d6640d26f25 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/hashes.mdx +++ b/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/hashes.mdx @@ -8,7 +8,7 @@ keywords: sidebar_position: 0 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; ## sha256 @@ -36,7 +36,7 @@ fn main() { ``` - + ## blake2s @@ -57,7 +57,7 @@ fn main() { } ``` - + ## blake3 @@ -78,7 +78,7 @@ fn main() { } ``` - + ## pedersen_hash @@ -101,7 +101,7 @@ fn main(x: Field, y: Field, expected_hash: Field) { > Source code: test_programs/execution_success/pedersen_hash/src/main.nr#L1-L7 - + ## pedersen_commitment @@ -125,7 +125,7 @@ fn main(x: Field, y: Field, expected_commitment: std::embedded_curve_ops::Embedd > Source code: test_programs/execution_success/pedersen_commitment/src/main.nr#L1-L8 - + ## keccak256 @@ -164,7 +164,7 @@ fn main(x: Field, result: [u8; 32]) { > Source code: test_programs/execution_success/keccak256/src/main.nr#L1-L21 - + ## poseidon diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/schnorr.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/schnorr.mdx index a32138daaa6..00e7f257612 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/schnorr.mdx +++ b/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/schnorr.mdx @@ -5,7 +5,7 @@ keywords: [cryptographic primitives, Noir project, schnorr, signatures] sidebar_position: 2 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; ## schnorr::verify_signature @@ -43,7 +43,7 @@ const signature = Array.from( ... ``` - + ## schnorr::verify_signature_slice @@ -61,4 +61,4 @@ pub fn verify_signature_slice( > Source code: noir_stdlib/src/schnorr.nr#L13-L20 - + diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/recursion.md b/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/recursion.mdx similarity index 96% rename from noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/recursion.md rename to noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/recursion.mdx index 7f4dcebf084..60414a2fa51 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/recursion.md +++ b/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/recursion.mdx @@ -4,7 +4,7 @@ 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.mdx'; +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. @@ -35,7 +35,7 @@ By incorporating this attribute directly in the circuit's definition, tooling li pub fn verify_proof(verification_key: [Field], proof: [Field], public_inputs: [Field], key_hash: Field) {} ``` - + ## Example usage diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/explainers/cspell.json b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/explainers/cspell.json new file mode 100644 index 00000000000..c60b0a597b1 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/explainers/cspell.json @@ -0,0 +1,5 @@ +{ + "words": [ + "Cryptdoku" + ] +} diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/explainers/explainer-oracle.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/explainers/explainer-oracle.md new file mode 100644 index 00000000000..821e1f95c04 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/explainers/explainer-oracle.md @@ -0,0 +1,57 @@ +--- +title: Oracles +description: This guide provides an in-depth understanding of how Oracles work in Noir programming. Learn how to use outside calculations in your programs, constrain oracles, and understand their uses and limitations. +keywords: + - Noir Programming + - Oracles + - JSON-RPC + - Foreign Call Handlers + - Constrained Functions + - Blockchain Programming +sidebar_position: 1 +--- + +If you've seen "The Matrix" you may recall "The Oracle" as Gloria Foster smoking cigarettes and baking cookies. While she appears to "know things", she is actually providing a calculation of a pre-determined future. Noir Oracles are similar, in a way. They don't calculate the future (yet), but they allow you to use outside calculations in your programs. + +![matrix oracle prediction](@site/static/img/memes/matrix_oracle.jpeg) + +A Noir program is usually self-contained. You can pass certain inputs to it, and it will generate a deterministic output for those inputs. But what if you wanted to defer some calculation to an outside process or source? + +Oracles are functions that provide this feature. + +## Use cases + +An example usage for Oracles is proving something on-chain. For example, proving that the ETH-USDC quote was below a certain target at a certain block time. Or even making more complex proofs like proving the ownership of an NFT as an anonymous login method. + +Another interesting use case is to defer expensive calculations to be made outside of the Noir program, and then constraining the result; similar to the use of [unconstrained functions](../noir/concepts//unconstrained.md). + +In short, anything that can be constrained in a Noir program but needs to be fetched from an external source is a great candidate to be used in oracles. + +## Constraining oracles + +Just like in The Matrix, Oracles are powerful. But with great power, comes great responsibility. Just because you're using them in a Noir program doesn't mean they're true. Noir has no superpowers. If you want to prove that Portugal won the Euro Cup 2016, you're still relying on potentially untrusted information. + +To give a concrete example, Alice wants to login to the [NounsDAO](https://nouns.wtf/) forum with her username "noir_nouner" by proving she owns a noun without revealing her ethereum address. Her Noir program could have an oracle call like this: + +```rust +#[oracle(getNoun)] +unconstrained fn get_noun(address: Field) -> Field +``` + +This oracle could naively resolve with the number of Nouns she possesses. However, it is useless as a trusted source, as the oracle could resolve to anything Alice wants. In order to make this oracle call actually useful, Alice would need to constrain the response from the oracle, by proving her address and the noun count belongs to the state tree of the contract. + +In short, **Oracles don't prove anything. Your Noir program does.** + +:::danger + +If you don't constrain the return of your oracle, you could be clearly opening an attack vector on your Noir program. Make double-triple sure that the return of an oracle call is constrained! + +::: + +## How to use Oracles + +On CLI, Nargo resolves oracles by making JSON RPC calls, which means it would require an RPC node to be running. + +In JavaScript, NoirJS accepts and resolves arbitrary call handlers (that is, not limited to JSON) as long as they match the expected types the developer defines. Refer to [Foreign Call Handler](../reference/NoirJS/noir_js/type-aliases/ForeignCallHandler.md) to learn more about NoirJS's call handling. + +If you want to build using oracles, follow through to the [oracle guide](../how_to/how-to-oracles.md) for a simple example on how to do that. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/explainers/explainer-recursion.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/explainers/explainer-recursion.md new file mode 100644 index 00000000000..df8529ef4e0 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/explainers/explainer-recursion.md @@ -0,0 +1,176 @@ +--- +title: Recursive proofs +description: Explore the concept of recursive proofs in Zero-Knowledge programming. Understand how recursion works in Noir, a language for writing smart contracts on the EVM blockchain. Learn through practical examples like Alice and Bob's guessing game, Charlie's recursive merkle tree, and Daniel's reusable components. Discover how to use recursive proofs to optimize computational resources and improve efficiency. + +keywords: + [ + "Recursive Proofs", + "Zero-Knowledge Programming", + "Noir", + "EVM Blockchain", + "Smart Contracts", + "Recursion in Noir", + "Alice and Bob Guessing Game", + "Recursive Merkle Tree", + "Reusable Components", + "Optimizing Computational Resources", + "Improving Efficiency", + "Verification Key", + "Aggregation", + "Recursive zkSNARK schemes", + "PLONK", + "Proving and Verification Keys" + ] +sidebar_position: 1 +pagination_next: how_to/how-to-recursion +--- + +In programming, we tend to think of recursion as something calling itself. A classic example would be the calculation of the factorial of a number: + +```js +function factorial(n) { + if (n === 0 || n === 1) { + return 1; + } else { + return n * factorial(n - 1); + } +} +``` + +In this case, while `n` is not `1`, this function will keep calling itself until it hits the base case, bubbling up the result on the call stack: + +```md + Is `n` 1? <--------- + /\ / + / \ n = n -1 + / \ / + Yes No -------- +``` + +In Zero-Knowledge, recursion has some similarities. + +It is not a Noir function calling itself, but a proof being used as an input to another circuit. In short, you verify one proof *inside* another proof, returning the proof that both proofs are valid. + +This means that, given enough computational resources, you can prove the correctness of any arbitrary number of proofs in a single proof. This could be useful to design state channels (for which a common example would be [Bitcoin's Lightning Network](https://en.wikipedia.org/wiki/Lightning_Network)), to save on gas costs by settling one proof on-chain, or simply to make business logic less dependent on a consensus mechanism. + +## Examples + +Let us look at some of these examples + +### Alice and Bob - Guessing game + +Alice and Bob are friends, and they like guessing games. They want to play a guessing game online, but for that, they need a trusted third-party that knows both of their secrets and finishes the game once someone wins. + +So, they use zero-knowledge proofs. Alice tries to guess Bob's number, and Bob will generate a ZK proof stating whether she succeeded or failed. + +This ZK proof can go on a smart contract, revealing the winner and even giving prizes. However, this means every turn needs to be verified on-chain. This incurs some cost and waiting time that may simply make the game too expensive or time-consuming to be worth it. + +As a solution, Alice proposes the following: "what if Bob generates his proof, and instead of sending it on-chain, I verify it *within* my own proof before playing my own turn?". + +She can then generate a proof that she verified his proof, and so on. + +```md + Did you fail? <-------------------------- + / \ / + / \ n = n -1 + / \ / + Yes No / + | | / + | | / + | You win / + | / + | / +Generate proof of that / + + / + my own guess ---------------- +``` + +### Charlie - Recursive merkle tree + +Charlie is a concerned citizen, and wants to be sure his vote in an election is accounted for. He votes with a ZK proof, but he has no way of knowing that his ZK proof was included in the total vote count! + +If the vote collector puts all of the votes into a [Merkle tree](https://en.wikipedia.org/wiki/Merkle_tree), everyone can prove the verification of two proofs within one proof, as such: + +```md + abcd + __________|______________ + | | + ab cd + _____|_____ ______|______ + | | | | + alice bob charlie daniel +``` + +Doing this recursively allows us to arrive on a final proof `abcd` which if true, verifies the correctness of all the votes. + +### Daniel - Reusable components + +Daniel has a big circuit and a big headache. A part of his circuit is a setup phase that finishes with some assertions that need to be made. But that section alone takes most of the proving time, and is largely independent of the rest of the circuit. + +He might find it more efficient to generate a proof for that setup phase separately, and verify that proof recursively in the actual business logic section of his circuit. This will allow for parallelization of both proofs, which results in a considerable speedup. + +## What params do I need + +As you can see in the [recursion reference](noir/standard_library/recursion.mdx), a simple recursive proof requires: + +- The proof to verify +- The Verification Key of the circuit that generated the proof +- A hash of this verification key, as it's needed for some backends +- The public inputs for the proof + +:::info + +Recursive zkSNARK schemes do not necessarily "verify a proof" in the sense that you expect a true or false to be spit out by the verifier. Rather an aggregation object is built over the public inputs. + +So, taking the example of Alice and Bob and their guessing game: + +- Alice makes her guess. Her proof is *not* recursive: it doesn't verify any proof within it! It's just a standard `assert(x != y)` circuit +- Bob verifies Alice's proof and makes his own guess. In this circuit, he doesn't exactly *prove* the verification of Alice's proof. Instead, he *aggregates* his proof to Alice's proof. The actual verification is done when the full proof is verified, for example when using `nargo verify` or through the verifier smart contract. + +We can imagine recursive proofs a [relay race](https://en.wikipedia.org/wiki/Relay_race). The first runner doesn't have to receive the baton from anyone else, as he/she already starts with it. But when his/her turn is over, the next runner needs to receive it, run a bit more, and pass it along. Even though every runner could theoretically verify the baton mid-run (why not? 🏃🔍), only at the end of the race does the referee verify that the whole race is valid. + +::: + +## Some architecture + +As with everything in computer science, there's no one-size-fits all. But there are some patterns that could help understanding and implementing them. To give three examples: + +### Adding some logic to a proof verification + +This would be an approach for something like our guessing game, where proofs are sent back and forth and are verified by each opponent. This circuit would be divided in two sections: + +- A `recursive verification` section, which would be just the call to `std::verify_proof`, and that would be skipped on the first move (since there's no proof to verify) +- A `guessing` section, which is basically the logic part where the actual guessing happens + +In such a situation, and assuming Alice is first, she would skip the first part and try to guess Bob's number. Bob would then verify her proof on the first section of his run, and try to guess Alice's number on the second part, and so on. + +### Aggregating proofs + +In some one-way interaction situations, recursion would allow for aggregation of simple proofs that don't need to be immediately verified on-chain or elsewhere. + +To give a practical example, a barman wouldn't need to verify a "proof-of-age" on-chain every time he serves alcohol to a customer. Instead, the architecture would comprise two circuits: + +- A `main`, non-recursive circuit with some logic +- A `recursive` circuit meant to verify two proofs in one proof + +The customer's proofs would be intermediate, and made on their phones, and the barman could just verify them locally. He would then aggregate them into a final proof sent on-chain (or elsewhere) at the end of the day. + +### Recursively verifying different circuits + +Nothing prevents you from verifying different circuits in a recursive proof, for example: + +- A `circuit1` circuit +- A `circuit2` circuit +- A `recursive` circuit + +In this example, a regulator could verify that taxes were paid for a specific purchase by aggregating both a `payer` circuit (proving that a purchase was made and taxes were paid), and a `receipt` circuit (proving that the payment was received) + +## How fast is it + +At the time of writing, verifying recursive proofs is surprisingly fast. This is because most of the time is spent on generating the verification key that will be used to generate the next proof. So you are able to cache the verification key and reuse it later. + +Currently, Noir JS packages don't expose the functionality of loading proving and verification keys, but that feature exists in the underlying `bb.js` package. + +## How can I try it + +Learn more about using recursion in Nargo and NoirJS in the [how-to guide](../how_to/how-to-recursion.md) and see a full example in [noir-examples](https://github.com/noir-lang/noir-examples). diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/explainers/explainer-writing-noir.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/explainers/explainer-writing-noir.md new file mode 100644 index 00000000000..3ef6e014a2f --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/explainers/explainer-writing-noir.md @@ -0,0 +1,177 @@ +--- +title: Writing Performant Noir +description: Understand new considerations when writing Noir +keywords: [Noir, programming, rust] +tags: [Optimization] +sidebar_position: 0 +--- + + +This article intends to set you up with key concepts essential for writing more viable applications that use zero knowledge proofs, namely around efficient circuits. + +## Context - 'Efficient' is subjective + +When writing a web application for a performant computer with high-speed internet connection, writing efficient code sometimes is seen as an afterthought only if needed. Large multiplications running at the innermost of nested loops may not even be on a dev's radar. +When writing firmware for a battery-powered microcontroller, you think of cpu cycles as rations to keep within a product's power budget. + +> Code is written to create applications that perform specific tasks within specific constraints + +And these constraints differ depending on where the compiled code is execute. + +### The Ethereum Virtual Machine (EVM) + +In scenarios where extremely low gas costs are required for an Ethereum application to be viable/competitive, Ethereum smart contract developers get into what is colloquially known as: "*gas golfing*". Finding the lowest execution cost of their compiled code (EVM bytecode) to achieve a specific task. + +The equivalent optimization task when writing zk circuits is affectionately referred to as "*gate golfing*", finding the lowest gate representation of the compiled Noir code. + +### Coding for circuits - a paradigm shift + +In zero knowledge cryptography, code is compiled to "circuits" consisting of arithmetic gates, and gate count is the significant cost. Depending on the proving system this is linearly proportionate to proving time, and so from a product point this should be kept as low as possible. + +Whilst writing efficient code for web apps and Solidity has a few key differences, writing efficient circuits have a different set of considerations. It is a bit of a paradigm shift, like writing code for GPUs for the first time... + +For example, drawing a circle at (0, 0) of radius `r`: +- For a single CPU thread, +``` +for theta in 0..2*pi { + let x = r * cos(theta); + let y = r * sin(theta); + draw(x, y); +} // note: would do 0 - pi/2 and draw +ve/-ve x and y. +``` + +- For GPUs (simultaneous parallel calls with x, y across image), +``` +if (x^2 + y^2 = r^2) { + draw(x, y); +} +``` + +([Related](https://www.youtube.com/watch?v=-P28LKWTzrI)) + +Whilst this CPU -> GPU does not translate to circuits exactly, it is intended to exemplify the difference in intuition when coding for different machine capabilities/constraints. + +### Context Takeaway + +For those coming from a primarily web app background, this article will explain what you need to consider when writing circuits. Furthermore, for those experienced writing efficient machine code, prepare to shift what you think is efficient 😬 + +## Translating from Rust + +For some applications using Noir, existing code might be a convenient starting point to then proceed to optimize the gate count of. + +:::note +Many valuable functions and algorithms have been written in more established languages (C/C++), and converted to modern ones (like Rust). +::: + +Fortunately for Noir developers, when needing a particular function a Rust implementation can be readily compiled into Noir with some key changes. While the compiler does a decent amount of optimizations, it won't be able to change code that has been optimized for clock-cycles into code optimized for arithmetic gates. + +A few things to do when converting Rust code to Noir: +- `println!` is not a macro, use `println` function (same for `assert_eq`) +- No early `return` in function. Use constrain via assertion instead +- No passing by reference. Remove `&` operator to pass by value (copy) +- No boolean operators (`&&`, `||`). Use bitwise operators (`&`, `|`) with boolean values +- No type `usize`. Use types `u8`, `u32`, `u64`, ... +- `main` return must be public, `pub` +- No `const`, use `global` +- Noir's LSP is your friend, so error message should be informative enough to resolve syntax issues. + +## Writing efficient Noir for performant products + +The following points help refine our understanding over time. + +:::note +A Noir program makes a statement that can be verified. +::: + +It compiles to a structure that represents the calculation, and can assert results within the calculation at any stage (via the `constrain` keyword). + +A Noir program compiles to an Abstract Circuit Intermediate Representation which is: + - Conceptually a tree structure + - Leaves (inputs) are the `Field` type + - Nodes contain arithmetic operations to combine them (gates) + - The root is the final result (return value) + +:::tip +The command `nargo info` shows the programs circuit size, and is useful to compare the value of changes made. +You can dig deeper and use the `--print-acir` param to take a closer look at individual ACIR opcodes, and the proving backend to see its gate count (eg for barretenberg, `bb gates -b ./target/program.json`). +::: + +### Use the `Field` type + +Since the native type of values in circuits are `Field`s, using them for variables in Noir means less gates converting them under the hood. +Some things to be mindful of when using a Field type for a regular integer value: +- A variable of type `Field` can be cast `as` an integer type (eg `u8`, `u64`) + - Note: this retains only the bits of the integer type. Eg a Field value of 260 as a `u8` becomes 4 +- For Field types arithmetic operations meaningfully overflow/underflow, yet for integer types they are checked according to their size +- Comparisons and bitwise operations do not exist for `Field`s, cast to an appropriately sized integer type when you need to + +:::tip +Where possible, use `Field` type for values. Using smaller value types, and bit-packing strategies, will result in MORE gates +::: + + +### Use Arithmetic over non-arithmetic operations + +Since circuits are made of arithmetic gates, the cost of arithmetic operations tends to be one gate. Whereas for procedural code, they represent several clock cycles. + +Inversely, non-arithmetic operators are achieved with multiple gates, vs 1 clock cycle for procedural code. + +| (cost\op) | arithmetic
(`*`, `+`) | bit-wise ops
(eg `<`, `\|`, `>>`) | +| - | - | - | +| **cycles** | 10+ | 1 | +| **gates** | 1 | 10+ | + +Bit-wise operations (e.g. bit shifts `<<` and `>>`), albeit commonly used in general programming and especially for clock cycle optimizations, are on the contrary expensive in gates when performed within circuits. + +Translate away from bit shifts when writing constrained functions for the best performance. + +On the flip side, feel free to use bit shifts in unconstrained functions and tests if necessary, as they are executed outside of circuits and does not induce performance hits. + +### Use static over dynamic values + +Another general theme that manifests in different ways is that static reads are represented with less gates than dynamic ones. + +Reading from read-only memory (ROM) adds less gates than random-access memory (RAM), 2 vs ~3.25 due to the additional bounds checks. Arrays of fixed length (albeit used at a lower capacity), will generate less gates than dynamic storage. + +Related to this, if an index used to access an array is not known at compile time (ie unknown until run time), then ROM will be converted to RAM, expanding the gate count. + +:::tip +Use arrays and indices that are known at compile time where possible. +Using `assert_constant(i);` before an index, `i`, is used in an array will give a compile error if `i` is NOT known at compile time. +::: + +### Leverage unconstrained execution + +Constrained verification can leverage unconstrained execution, this is especially useful for operations that are represented by many gates. +Use an [unconstrained function](../noir/concepts/unconstrained.md) to perform gate-heavy calculations, then verify and constrain the result. + +Eg division generates more gates than multiplication, so calculating the quotient in an unconstrained function then constraining the product for the quotient and divisor (+ any remainder) equals the dividend will be more efficient. + +Use ` if is_unconstrained() { /`, to conditionally execute code if being called in an unconstrained vs constrained way. + +## Advanced + +Unless you're well into the depth of gate optimization, this advanced section can be ignored. + +### Combine arithmetic operations + +A Noir program can be honed further by combining arithmetic operators in a way that makes the most of each constraint of the backend proving system. This is in scenarios where the backend might not be doing this perfectly. + +Eg Barretenberg backend (current default for Noir) is a width-4 PLONKish constraint system +$ w_1*w_2*q_m + w_1*q_1 + w_2*q_2 + w_3*q_3 + w_4*q_4 + q_c = 0 $ + +Here we see there is one occurrence of witness 1 and 2 ($w_1$, $w_2$) being multiplied together, with addition to witnesses 1-4 ($w_1$ .. $w_4$) multiplied by 4 corresponding circuit constants ($q_1$ .. $q_4$) (plus a final circuit constant, $q_c$). + +Use `nargo info --print-acir`, to inspect the ACIR opcodes (and the proving system for gates), and it may present opportunities to amend the order of operations and reduce the number of constraints. + +#### Variable as witness vs expression + +If you've come this far and really know what you're doing at the equation level, a temporary lever (that will become unnecessary/useless over time) is: `std::as_witness`. This informs the compiler to save a variable as a witness not an expression. + +The compiler will mostly be correct and optimal, but this may help some near term edge cases that are yet to optimize. +Note: When used incorrectly it will create **less** efficient circuits (higher gate count). + +## References +- Guillaume's ["`Cryptdoku`" talk](https://www.youtube.com/watch?v=MrQyzuogxgg) (Jun'23) +- Tips from Tom, Jake and Zac. +- [Idiomatic Noir](https://www.vlayer.xyz/blog/idiomatic-noir-part-1-collections) blog post diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/getting_started/_category_.json b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/getting_started/_category_.json new file mode 100644 index 00000000000..5d694210bbf --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/getting_started/_category_.json @@ -0,0 +1,5 @@ +{ + "position": 0, + "collapsible": true, + "collapsed": true +} diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/getting_started/backend/_category_.json b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/getting_started/backend/_category_.json new file mode 100644 index 00000000000..b82e92beb0c --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/getting_started/backend/_category_.json @@ -0,0 +1,6 @@ +{ + "position": 1, + "label": "Install Proving Backend", + "collapsible": true, + "collapsed": true +} diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/getting_started/backend/index.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/getting_started/backend/index.md new file mode 100644 index 00000000000..7192d954877 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/getting_started/backend/index.md @@ -0,0 +1,31 @@ +--- +title: Proving Backend Installation +description: Proving backends offer command line tools for proving and verifying Noir programs. This page describes how to install `bb` as an example. +keywords: [ + Proving + Backend + Barretenberg + bb + bbup + Installation + Terminal + Command + CLI + Version +] +pagination_next: getting_started/hello_noir/index +--- + +Proving backends each provide their own tools for working with Noir programs, providing functionality like proof generation, proof verification, and verifier smart contract generation. + +For the latest information on tooling provided by each proving backend, installation instructions, Noir version compatibility... you may refer to the proving backends' own documentation. + +You can find the full list of proving backends compatible with Noir in [Awesome Noir](https://github.com/noir-lang/awesome-noir/?tab=readme-ov-file#proving-backends). + +## Example: Installing `bb` + +`bb` is the CLI tool provided by the [Barretenberg proving backend](https://github.com/AztecProtocol/barretenberg) developed by Aztec Labs. + +You can find the instructions for installation in [`bb`'s documentation](https://github.com/AztecProtocol/aztec-packages/blob/master/barretenberg/cpp/src/barretenberg/bb/readme.md#installation). + +Once installed, we are ready to start working on [our first Noir program](../hello_noir/index.md). diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/getting_started/hello_noir/_category_.json b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/getting_started/hello_noir/_category_.json new file mode 100644 index 00000000000..976a2325de0 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/getting_started/hello_noir/_category_.json @@ -0,0 +1,5 @@ +{ + "position": 2, + "collapsible": true, + "collapsed": true +} diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/getting_started/hello_noir/index.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/getting_started/hello_noir/index.md new file mode 100644 index 00000000000..6760e54aad1 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/getting_started/hello_noir/index.md @@ -0,0 +1,157 @@ +--- +title: Creating a Project +description: + Learn how to create and verify your first Noir program using Nargo, a programming language for + zero-knowledge proofs. +keywords: + [ + Nargo, + Noir, + zero-knowledge proofs, + programming language, + create Noir program, + verify Noir program, + step-by-step guide, + ] +sidebar_position: 1 + +--- + +Now that we have installed Nargo and a proving backend, it is time to make our first hello world program! + +### 1. Create a new project directory + +Noir code can live anywhere on your computer. Let us create a _projects_ folder in the home +directory to house our first Noir program. + +Create the directory and change directory into it by running: + +```sh +mkdir ~/projects +cd ~/projects +``` + +## Nargo + +Nargo provides the ability to initiate and execute Noir projects. Read the [Nargo installation](../installation/index.md) section to learn more about Nargo and how to install it. + +### 2. Create a new Noir project + +Now that we are in the projects directory, create a new Nargo project by running: + +```sh +nargo new hello_world +``` + +`hello_world` can be any arbitrary project name, we are simply using `hello_world` for demonstration. + +In production, it is common practice to name the project folder, `circuits`, for clarity amongst other folders in the codebase (like: `contracts`, `scripts`, and `test`). + +A `hello_world` folder would be created. Similar to Rust, the folder houses _src/main.nr_ and +_Nargo.toml_ which contain the source code and environmental options of your Noir program +respectively. + +#### Intro to Noir Syntax + +Let us take a closer look at _main.nr_. The default _main.nr_ generated should look like this: + +```rust +fn main(x : Field, y : pub Field) { + assert(x != y); +} +``` + +The first line of the program specifies the program's inputs: + +```rust +x : Field, y : pub Field +``` + +Program inputs in Noir are private by default (e.g. `x`), but can be labeled public using the +keyword `pub` (e.g. `y`). To learn more about private and public values, check the +[Data Types](../../noir/concepts/data_types/index.md) section. + +The next line of the program specifies its body: + +```rust +assert(x != y); +``` + +The Noir syntax `assert` can be interpreted as something similar to constraints in other zk-contract languages. + +For more Noir syntax, check the [Language Concepts](../../noir/concepts/comments.md) chapter. + +### 3. Build in/output files + +Change directory into _hello_world_ and build in/output files for your Noir program by running: + +```sh +cd hello_world +nargo check +``` + +A _Prover.toml_ file will be generated in your project directory, to allow specifying input values to the program. + +### 4. Execute the Noir program + +Now that the project is set up, we can execute our Noir program. + +Fill in input values for execution in the _Prover.toml_ file. For example: + +```toml +x = "1" +y = "2" +``` + +Execute your Noir program: + +```sh +nargo execute witness-name +``` + +The witness corresponding to this execution will then be written to the file `./target/witness-name.gz`. + +The command also automatically compiles your Noir program if it was not already / was edited, which you may notice the compiled artifacts being written to the file `./target/hello_world.json`. + +## Proving Backend + +Proving backends provide the ability to generate and verify proofs of executing Noir programs, following Noir's tooling that compiles and executes the programs. Read the [proving backend installation](../backend/index.md) section to learn more about proving backends and how to install them. + +Barretenberg is used as an example here to demonstrate how proving and verifying could be implemented and used. Read the [`bb` installation](../backend/index.md#example-installing-bb) section for how to install Barretenberg's CLI tool; refer to [`bb`'s documentation](https://github.com/AztecProtocol/aztec-packages/blob/master/barretenberg/cpp/src/barretenberg/bb/readme.md) for full details about the tool. + +### 5. Prove an execution of the Noir program + +Using Barretenberg as an example, prove the valid execution of your Noir program running: + +```sh +bb prove -b ./target/hello_world.json -w ./target/witness-name.gz -o ./target/proof +``` + +The proof generated will then be written to the file `./target/proof`. + +:::tip +Since the params for `nargo` and `bb` often specify multiple filenames to read from or write to, remember to check each command is referring to the desired filenames. +Or for greater certainty, delete the target folder and go through each step again (compile, witness, prove, ...) to ensure files generated in past commands are being referenced in future ones. +::: + +### 6. Verify the execution proof + +Once a proof is generated, we can verify correct execution of our Noir program by verifying the proof file. + +Using Barretenberg as an example, compute the verification key for the Noir program by running: + +```sh +bb write_vk -b ./target/hello_world.json -o ./target/vk +``` + +And verify your proof by running: + +```sh +bb verify -k ./target/vk -p ./target/proof +``` + +If successful, the verification will complete in silence; if unsuccessful, the command will trigger logging of the corresponding error. + +Congratulations, you have now created and verified a proof for your very first Noir program! + +In the [next section](./project_breakdown.md), we will go into more detail on each step performed. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/getting_started/hello_noir/project_breakdown.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/getting_started/hello_noir/project_breakdown.md new file mode 100644 index 00000000000..96e653f6c08 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/getting_started/hello_noir/project_breakdown.md @@ -0,0 +1,159 @@ +--- +title: Project Breakdown +description: + Learn about the anatomy of a Nargo project, including the purpose of the Prover TOML + file, and how to prove and verify your program. +keywords: + [Nargo, Nargo project, Prover.toml, proof verification, private asset transfer] +sidebar_position: 2 +--- + +This section breaks down our hello world program from the previous section. + +## Anatomy of a Nargo Project + +Upon creating a new project with `nargo new` and building the in/output files with `nargo check` +commands, you would get a minimal Nargo project of the following structure: + + - src + - Prover.toml + - Nargo.toml + +The source directory _src_ holds the source code for your Noir program. By default only a _main.nr_ +file will be generated within it. + +### Prover.toml + +_Prover.toml_ is used for specifying the input values for executing and proving the program. You can specify `toml` files with different names by using the `--prover-name` or `-p` flags, see the [Prover](#provertoml) section below. Optionally you may specify expected output values for prove-time checking as well. + +### Nargo.toml + +_Nargo.toml_ contains the environmental options of your project. It contains a "package" section and a "dependencies" section. + +Example Nargo.toml: + +```toml +[package] +name = "noir_starter" +type = "bin" +authors = ["Alice"] +compiler_version = "0.9.0" +description = "Getting started with Noir" +entry = "circuit/main.nr" +license = "MIT" + +[dependencies] +ecrecover = {tag = "v0.9.0", git = "https://github.com/colinnielsen/ecrecover-noir.git"} +``` + +Nargo.toml for a [workspace](../../noir/modules_packages_crates/workspaces.md) will look a bit different. For example: + +```toml +[workspace] +members = ["crates/a", "crates/b"] +default-member = "crates/a" +``` + +#### Package section + +The package section defines a number of fields including: + +- `name` (**required**) - the name of the package +- `type` (**required**) - can be "bin", "lib", or "contract" to specify whether its a binary, library or Aztec contract +- `authors` (optional) - authors of the project +- `compiler_version` - specifies the version of the compiler to use. This is enforced by the compiler and follow's [Rust's versioning](https://doc.rust-lang.org/cargo/reference/manifest.html#the-version-field), so a `compiler_version = 0.18.0` will enforce Nargo version 0.18.0, `compiler_version = ^0.18.0` will enforce anything above 0.18.0 but below 0.19.0, etc. For more information, see how [Rust handles these operators](https://docs.rs/semver/latest/semver/enum.Op.html) +- `description` (optional) +- `entry` (optional) - a relative filepath to use as the entry point into your package (overrides the default of `src/lib.nr` or `src/main.nr`) +- `backend` (optional) +- `license` (optional) +- `expression_width` (optional) - Sets the default backend expression width. This field will override the default backend expression width specified by the Noir compiler (currently set to width 4). + +#### Dependencies section + +This is where you will specify any dependencies for your project. See the [Dependencies page](../../noir/modules_packages_crates/dependencies.md) for more info. + +`./proofs/` and `./contract/` directories will not be immediately visible until you create a proof or +verifier contract respectively. + +### main.nr + +The _main.nr_ file contains a `main` method, this method is the entry point into your Noir program. + +In our sample program, _main.nr_ looks like this: + +```rust +fn main(x : Field, y : Field) { + assert(x != y); +} +``` + +The parameters `x` and `y` can be seen as the API for the program and must be supplied by the prover. Since neither `x` nor `y` is marked as public, the verifier does not supply any inputs, when verifying the proof. + +The prover supplies the values for `x` and `y` in the _Prover.toml_ file. + +As for the program body, `assert` ensures that the condition to be satisfied (e.g. `x != y`) is constrained by the proof of the execution of said program (i.e. if the condition was not met, the verifier would reject the proof as an invalid proof). + +### Prover.toml + +The _Prover.toml_ file is a file which the prover uses to supply the inputs to the Noir program (both private and public). + +In our hello world program the _Prover.toml_ file looks like this: + +```toml +x = "1" +y = "2" +``` + +When the command `nargo execute` is executed, nargo will execute the Noir program using the inputs specified in `Prover.toml`, aborting if it finds that these do not satisfy the constraints defined by `main`. In this example, `x` and `y` must satisfy the inequality constraint `assert(x != y)`. + +If an output name is specified such as `nargo execute foo`, the witness generated by this execution will be written to `./target/foo.gz`. This can then be used to generate a proof of the execution. + +#### Arrays of Structs + +The following code shows how to pass an array of structs to a Noir program to generate a proof. + +```rust +// main.nr +struct Foo { + bar: Field, + baz: Field, +} + +fn main(foos: [Foo; 3]) -> pub Field { + foos[2].bar + foos[2].baz +} +``` + +Prover.toml: + +```toml +[[foos]] # foos[0] +bar = 0 +baz = 0 + +[[foos]] # foos[1] +bar = 0 +baz = 0 + +[[foos]] # foos[2] +bar = 1 +baz = 2 +``` + +#### Custom toml files + +You can specify a `toml` file with a different name to use for execution by using the `--prover-name` or `-p` flags. + +This command looks for proof inputs in the default **Prover.toml** and generates the witness and saves it at `./target/foo.gz`: + +```bash +nargo execute foo +``` + +This command looks for proof inputs in the custom **OtherProver.toml** and generates the witness and saves it at `./target/bar.gz`: + +```bash +nargo execute -p OtherProver bar +``` + +Now that you understand the concepts, you'll probably want some editor feedback while you are writing more complex code. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/getting_started/installation/_category_.json b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/getting_started/installation/_category_.json new file mode 100644 index 00000000000..0c02fb5d4d7 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/getting_started/installation/_category_.json @@ -0,0 +1,6 @@ +{ + "position": 0, + "label": "Install Nargo", + "collapsible": true, + "collapsed": true +} diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/getting_started/installation/index.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/getting_started/installation/index.md new file mode 100644 index 00000000000..53ea9c7891c --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/getting_started/installation/index.md @@ -0,0 +1,46 @@ +--- +title: Nargo Installation +description: + nargo is a command line tool for interacting with Noir programs. This page is a quick guide on how to install Nargo through the most common and easy method, noirup +keywords: [ + Nargo + Noir + Rust + Cargo + Noirup + Installation + Terminal Commands + Version Check + Nightlies + Specific Versions + Branches + Noirup Repository +] +pagination_next: getting_started/hello_noir/index +--- + +`nargo` is a tool for working with Noir programs on the CLI, providing you with the ability to start new projects, compile, execute and test Noir programs from the terminal. + +The name is inspired by Rust's package manager `cargo`; and similar to Rust's `rustup`, Noir also has an easy installation script `noirup`. + +## Installing Noirup + +Open a terminal on your machine, and write: + +```bash +curl -L https://raw.githubusercontent.com/noir-lang/noirup/main/install | bash +``` + +Close the terminal, open another one, and run + +```bash +noirup +``` + +Done. That's it. You should have the latest version working. You can check with `nargo --version`. + +You can also install nightlies, specific versions +or branches. Check out the [noirup repository](https://github.com/noir-lang/noirup) for more +information. + +Now we're ready to start working on [our first Noir program!](../hello_noir/index.md) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/getting_started/installation/other_install_methods.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/getting_started/installation/other_install_methods.md new file mode 100644 index 00000000000..3634723562b --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/getting_started/installation/other_install_methods.md @@ -0,0 +1,102 @@ +--- +title: Alternative Installations +description: There are different ways to install Nargo, the one-stop shop and command-line tool for developing Noir programs. This guide explains how to specify which version to install when using noirup, and using WSL for windows. +keywords: [ + Installation + Nargo + Noirup + Binaries + Compiling from Source + WSL for Windows + macOS + Linux + Nix + Direnv + Uninstalling Nargo + ] +sidebar_position: 1 +--- + +## Encouraged Installation Method: Noirup + +Noirup is the endorsed method for installing Nargo, streamlining the process of fetching binaries or compiling from source. It supports a range of options to cater to your specific needs, from nightly builds and specific versions to compiling from various sources. + +### Installing Noirup + +First, ensure you have `noirup` installed: + +```sh +curl -L https://raw.githubusercontent.com/noir-lang/noirup/main/install | bash +``` + +### Fetching Binaries + +With `noirup`, you can easily switch between different Nargo versions, including nightly builds: + +- **Nightly Version**: Install the latest nightly build. + + ```sh + noirup --version nightly + ``` + +- **Specific Version**: Install a specific version of Nargo. + ```sh + noirup --version + ``` + +### Compiling from Source + +`noirup` also enables compiling Nargo from various sources: + +- **From a Specific Branch**: Install from the latest commit on a branch. + + ```sh + noirup --branch + ``` + +- **From a Fork**: Install from the main branch of a fork. + + ```sh + noirup --repo + ``` + +- **From a Specific Branch in a Fork**: Install from a specific branch in a fork. + + ```sh + noirup --repo --branch + ``` + +- **From a Specific Pull Request**: Install from a specific PR. + + ```sh + noirup --pr + ``` + +- **From a Specific Commit**: Install from a specific commit. + + ```sh + noirup -C + ``` + +- **From Local Source**: Compile and install from a local directory. + ```sh + noirup --path ./path/to/local/source + ``` + +## Installation on Windows + +The default backend for Noir (Barretenberg) doesn't provide Windows binaries at this time. For that reason, Noir cannot be installed natively. However, it is available by using Windows Subsystem for Linux (WSL). + +Step 1: Follow the instructions [here](https://learn.microsoft.com/en-us/windows/wsl/install) to install and run WSL. + +step 2: Follow the [Noirup instructions](#encouraged-installation-method-noirup). + +## Uninstalling Nargo + +If you installed Nargo with `noirup`, you can uninstall Nargo by removing the files in `~/.nargo`, `~/nargo`, and `~/noir_cache`. This ensures that all installed binaries, configurations, and cache related to Nargo are fully removed from your system. + +```bash +rm -r ~/.nargo +rm -r ~/nargo +rm -r ~/noir_cache +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/how_to/_category_.json b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/how_to/_category_.json new file mode 100644 index 00000000000..23b560f610b --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/how_to/_category_.json @@ -0,0 +1,5 @@ +{ + "position": 1, + "collapsible": true, + "collapsed": true +} diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/how_to/debugger/_category_.json b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/how_to/debugger/_category_.json new file mode 100644 index 00000000000..cc2cbb1c253 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/how_to/debugger/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Debugging", + "position": 5, + "collapsible": true, + "collapsed": true +} diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/how_to/debugger/debugging_with_the_repl.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/how_to/debugger/debugging_with_the_repl.md new file mode 100644 index 00000000000..09e5bae68ad --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/how_to/debugger/debugging_with_the_repl.md @@ -0,0 +1,164 @@ +--- +title: Using the REPL Debugger +description: + Step by step guide on how to debug your Noir circuits with the REPL Debugger. +keywords: + [ + Nargo, + Noir CLI, + Noir Debugger, + REPL, + ] +sidebar_position: 1 +--- + +#### Pre-requisites + +In order to use the REPL debugger, first you need to install recent enough versions of Nargo and vscode-noir. + +## Debugging a simple circuit + +Let's debug a simple circuit: + +```rust +fn main(x : Field, y : pub Field) { + assert(x != y); +} +``` + +To start the REPL debugger, using a terminal, go to a Noir circuit's home directory. Then: + +`$ nargo debug` + +You should be seeing this in your terminal: + +``` +[main] Starting debugger +At ~/noir-examples/recursion/circuits/main/src/main.nr:1:9 + 1 -> fn main(x : Field, y : pub Field) { + 2 assert(x != y); + 3 } +> +``` + +The debugger displays the current Noir code location, and it is now waiting for us to drive it. + +Let's first take a look at the available commands. For that we'll use the `help` command. + +``` +> help +Available commands: + + opcodes display ACIR opcodes + into step into to the next opcode + next step until a new source location is reached + out step until a new source location is reached + and the current stack frame is finished + break LOCATION:OpcodeLocation add a breakpoint at an opcode location + over step until a new source location is reached + without diving into function calls + restart restart the debugging session + delete LOCATION:OpcodeLocation delete breakpoint at an opcode location + witness show witness map + witness index:u32 display a single witness from the witness map + witness index:u32 value:String update a witness with the given value + memset index:usize value:String update a memory cell with the given + value + continue continue execution until the end of the + program + vars show variable values available at this point + in execution + stacktrace display the current stack trace + memory show memory (valid when executing unconstrained code) + step step to the next ACIR opcode + +Other commands: + + help Show this help message + quit Quit repl + +``` + +Some commands operate only for unconstrained functions, such as `memory` and `memset`. If you try to use them while execution is paused at an ACIR opcode, the debugger will simply inform you that you are not executing unconstrained code: + +``` +> memory +Unconstrained VM memory not available +> +``` + +Before continuing, we can take a look at the initial witness map: + +``` +> witness +_0 = 1 +_1 = 2 +> +``` + +Cool, since `x==1`, `y==2`, and we want to check that `x != y`, our circuit should succeed. At this point we could intervene and use the witness setter command to change one of the witnesses. Let's set `y=3`, then back to 2, so we don't affect the expected result: + +``` +> witness +_0 = 1 +_1 = 2 +> witness 1 3 +_1 = 3 +> witness +_0 = 1 +_1 = 3 +> witness 1 2 +_1 = 2 +> witness +_0 = 1 +_1 = 2 +> +``` + +Now we can inspect the current state of local variables. For that we use the `vars` command. + +``` +> vars +> +``` + +We currently have no vars in context, since we are at the entry point of the program. Let's use `next` to execute until the next point in the program. + +``` +> vars +> next +At ~/noir-examples/recursion/circuits/main/src/main.nr:1:20 + 1 -> fn main(x : Field, y : pub Field) { + 2 assert(x != y); + 3 } +> vars +x:Field = 0x01 +``` + +As a result of stepping, the variable `x`, whose initial value comes from the witness map, is now in context and returned by `vars`. + +``` +> next + 1 fn main(x : Field, y : pub Field) { + 2 -> assert(x != y); + 3 } +> vars +y:Field = 0x02 +x:Field = 0x01 +``` + +Stepping again we can finally see both variables and their values. And now we can see that the next assertion should succeed. + +Let's continue to the end: + +``` +> continue +(Continuing execution...) +Finished execution +> q +[main] Circuit witness successfully solved +``` + +Upon quitting the debugger after a solved circuit, the resulting circuit witness gets saved, equivalent to what would happen if we had run the same circuit with `nargo execute`. + +We just went through the basics of debugging using Noir REPL debugger. For a comprehensive reference, check out [the reference page](../../reference/debugger/debugger_repl.md). diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/how_to/debugger/debugging_with_vs_code.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/how_to/debugger/debugging_with_vs_code.md new file mode 100644 index 00000000000..a5858c1a5eb --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/how_to/debugger/debugging_with_vs_code.md @@ -0,0 +1,68 @@ +--- +title: Using the VS Code Debugger +description: + Step by step guide on how to debug your Noir circuits with the VS Code Debugger configuration and features. +keywords: + [ + Nargo, + Noir CLI, + Noir Debugger, + VS Code, + IDE, + ] +sidebar_position: 0 +--- + +This guide will show you how to use VS Code with the vscode-noir extension to debug a Noir project. + +#### Pre-requisites + +- Nargo +- vscode-noir +- A Noir project with a `Nargo.toml`, `Prover.toml` and at least one Noir (`.nr`) containing an entry point function (typically `main`). + +## Running the debugger + +The easiest way to start debugging is to open the file you want to debug, and press `F5`. This will cause the debugger to launch, using your `Prover.toml` file as input. + +You should see something like this: + +![Debugger launched](@site/static/img/debugger/1-started.png) + +Let's inspect the state of the program. For that, we open VS Code's _Debug pane_. Look for this icon: + +![Debug pane icon](@site/static/img/debugger/2-icon.png) + +You will now see two categories of variables: Locals and Witness Map. + +![Debug pane expanded](@site/static/img/debugger/3-debug-pane.png) + +1. **Locals**: variables of your program. At this point in execution this section is empty, but as we step through the code it will get populated by `x`, `result`, `digest`, etc. + +2. **Witness map**: these are initially populated from your project's `Prover.toml` file. In this example, they will be used to populate `x` and `result` at the beginning of the `main` function. + +Most of the time you will probably be focusing mostly on locals, as they represent the high level state of your program. + +You might be interested in inspecting the witness map in case you are trying to solve a really low level issue in the compiler or runtime itself, so this concerns mostly advanced or niche users. + +Let's step through the program, by using the debugger buttons or their corresponding keyboard shortcuts. + +![Debugger buttons](@site/static/img/debugger/4-debugger-buttons.png) + +Now we can see in the variables pane that there's values for `digest`, `result` and `x`. + +![Inspecting locals](@site/static/img/debugger/5-assert.png) + +We can also inspect the values of variables by directly hovering on them on the code. + +![Hover locals](@site/static/img/debugger/6-hover.png) + +Let's set a break point at the `keccak256` function, so we can continue execution up to the point when it's first invoked without having to go one step at a time. + +We just need to click the to the right of the line number 18. Once the breakpoint appears, we can click the `continue` button or use its corresponding keyboard shortcut (`F5` by default). + +![Breakpoint](@site/static/img/debugger/7-break.png) + +Now we are debugging the `keccak256` function, notice the _Call Stack pane_ at the lower right. This lets us inspect the current call stack of our process. + +That covers most of the current debugger functionalities. Check out [the reference](../../reference/debugger/debugger_vscode.md) for more details on how to configure the debugger. \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/how_to/how-to-oracles.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/how_to/how-to-oracles.md new file mode 100644 index 00000000000..2f69902062c --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/how_to/how-to-oracles.md @@ -0,0 +1,273 @@ +--- +title: How to use Oracles +description: Learn how to use oracles in your Noir program with examples in both Nargo and NoirJS. This guide also covers writing a JSON RPC server and providing custom foreign call handlers for NoirJS. +keywords: + - Noir Programming + - Oracles + - Nargo + - NoirJS + - JSON RPC Server + - Foreign Call Handlers +sidebar_position: 1 +--- + +This guide shows you how to use oracles in your Noir program. For the sake of clarity, it assumes that: + +- You have read the [explainer on Oracles](../explainers/explainer-oracle.md) and are comfortable with the concept. +- You have a Noir program to add oracles to. You can create one using the [vite-hardhat starter](https://github.com/noir-lang/noir-starter/tree/main/vite-hardhat) as a boilerplate. +- You understand the concept of a JSON-RPC server. Visit the [JSON-RPC website](https://www.jsonrpc.org/) if you need a refresher. +- You are comfortable with server-side JavaScript (e.g. Node.js, managing packages, etc.). + +For reference, you can find the snippets used in this tutorial on the [Aztec DevRel Repository](https://github.com/AztecProtocol/dev-rel/tree/main/code-snippets/how-to-oracles). + +## Rundown + +This guide has 3 major steps: + +1. How to modify our Noir program to make use of oracle calls as unconstrained functions +2. How to write a JSON RPC Server to resolve these oracle calls with Nargo +3. How to use them in Nargo and how to provide a custom resolver in NoirJS + +## Step 1 - Modify your Noir program + +An oracle is defined in a Noir program by defining two methods: + +- An unconstrained method - This tells the compiler that it is executing an [unconstrained functions](../noir/concepts//unconstrained.md). +- A decorated oracle method - This tells the compiler that this method is an RPC call. + +An example of an oracle that returns a `Field` would be: + +```rust +#[oracle(getSqrt)] +unconstrained fn sqrt(number: Field) -> Field { } + +unconstrained fn get_sqrt(number: Field) -> Field { + sqrt(number) +} +``` + +In this example, we're wrapping our oracle function in an unconstrained method, and decorating it with `oracle(getSqrt)`. We can then call the unconstrained function as we would call any other function: + +```rust +fn main(input: Field) { + let sqrt = get_sqrt(input); +} +``` + +In the next section, we will make this `getSqrt` (defined on the `sqrt` decorator) be a method of the RPC server Noir will use. + +:::danger + +As explained in the [Oracle Explainer](../explainers/explainer-oracle.md), this `main` function is unsafe unless you constrain its return value. For example: + +```rust +fn main(input: Field) { + let sqrt = get_sqrt(input); + assert(sqrt.pow_32(2) as u64 == input as u64); // <---- constrain the return of an oracle! +} +``` + +::: + +:::info + +Currently, oracles only work with single params or array params. For example: + +```rust +#[oracle(getSqrt)] +unconstrained fn sqrt([Field; 2]) -> [Field; 2] { } +``` + +::: + +## Step 2 - Write an RPC server + +Brillig will call *one* RPC server. Most likely you will have to write your own, and you can do it in whatever language you prefer. In this guide, we will do it in Javascript. + +Let's use the above example of an oracle that consumes an array with two `Field` and returns their square roots: + +```rust +#[oracle(getSqrt)] +unconstrained fn sqrt(input: [Field; 2]) -> [Field; 2] { } + +unconstrained fn get_sqrt(input: [Field; 2]) -> [Field; 2] { + sqrt(input) +} + +fn main(input: [Field; 2]) { + let sqrt = get_sqrt(input); + assert(sqrt[0].pow_32(2) as u64 == input[0] as u64); + assert(sqrt[1].pow_32(2) as u64 == input[1] as u64); +} +``` + +:::info + +Why square root? + +In general, computing square roots is computationally more expensive than multiplications, which takes a toll when speaking about ZK applications. In this case, instead of calculating the square root in Noir, we are using our oracle to offload that computation to be made in plain. In our circuit we can simply multiply the two values. + +::: + +Now, we should write the correspondent RPC server, starting with the [default JSON-RPC 2.0 boilerplate](https://www.npmjs.com/package/json-rpc-2.0#example): + +```js +import { JSONRPCServer } from "json-rpc-2.0"; +import express from "express"; +import bodyParser from "body-parser"; + +const app = express(); +app.use(bodyParser.json()); + +const server = new JSONRPCServer(); +app.post("/", (req, res) => { + const jsonRPCRequest = req.body; + server.receive(jsonRPCRequest).then((jsonRPCResponse) => { + if (jsonRPCResponse) { + res.json(jsonRPCResponse); + } else { + res.sendStatus(204); + } + }); +}); + +app.listen(5555); +``` + +Now, we will add our `getSqrt` method, as expected by the `#[oracle(getSqrt)]` decorator in our Noir code. It maps through the params array and returns their square roots: + +```js +server.addMethod("resolve_function_call", async (params) => { + if params.function !== "getSqrt" { + throw Error("Unexpected foreign call") + }; + const values = params.inputs[0].Array.map((field) => { + return `${Math.sqrt(parseInt(field, 16))}`; + }); + return { values: [{ Array: values }] }; +}); +``` + +If you're using Typescript, the following types may be helpful in understanding the expected return value and making sure they're easy to follow: + +```js +interface SingleForeignCallParam { + Single: string, +} + +interface ArrayForeignCallParam { + Array: string[], +} + +type ForeignCallParam = SingleForeignCallParam | ArrayForeignCallParam; + +interface ForeignCallResult { + values: ForeignCallParam[], +} +``` + +::: Multidimensional Arrays + +If the Oracle function is returning an array containing other arrays, such as `[['1','2],['3','4']]`, you need to provide the values in json as flattened values. In the previous example, it would be `['1', '2', '3', '4']`. In the noir program, the Oracle signature can use a nested type, the flattened values will be automatically converted to the nested type. + +::: + +## Step 3 - Usage with Nargo + +Using the [`nargo` CLI tool](../getting_started/installation/index.md), you can use oracles in the `nargo test` and `nargo execute` commands by passing a value to `--oracle-resolver`. For example: + +```bash +nargo test --oracle-resolver http://localhost:5555 +``` + +This tells `nargo` to use your RPC Server URL whenever it finds an oracle decorator. + +## Step 4 - Usage with NoirJS + +In a JS environment, an RPC server is not strictly necessary, as you may want to resolve your oracles without needing any JSON call at all. NoirJS simply expects that you pass a callback function when you generate proofs, and that callback function can be anything. + +For example, if your Noir program expects the host machine to provide CPU pseudo-randomness, you could simply pass it as the `foreignCallHandler`. You don't strictly need to create an RPC server to serve pseudo-randomness, as you may as well get it directly in your app: + +```js +const foreignCallHandler = (name, inputs) => crypto.randomBytes(16) // etc + +await noir.execute(inputs, foreignCallHandler) +``` + +As one can see, in NoirJS, the [`foreignCallHandler`](../reference/NoirJS/noir_js/type-aliases/ForeignCallHandler.md) function simply means "a callback function that returns a value of type [`ForeignCallOutput`](../reference/NoirJS/noir_js/type-aliases/ForeignCallOutput.md). It doesn't have to be an RPC call like in the case for Nargo. + +:::tip + +Does this mean you don't have to write an RPC server like in [Step #2](#step-2---write-an-rpc-server)? + +You don't technically have to, but then how would you run `nargo test`? To use both `Nargo` and `NoirJS` in your development flow, you will have to write a JSON RPC server. + +::: + +In this case, let's make `foreignCallHandler` call the JSON RPC Server we created in [Step #2](#step-2---write-an-rpc-server), by making it a JSON RPC Client. + +For example, using the same `getSqrt` program in [Step #1](#step-1---modify-your-noir-program) (comments in the code): + +```js +import { JSONRPCClient } from "json-rpc-2.0"; + +// declaring the JSONRPCClient +const client = new JSONRPCClient((jsonRPCRequest) => { +// hitting the same JSON RPC Server we coded above + return fetch("http://localhost:5555", { + method: "POST", + headers: { + "content-type": "application/json", + }, + body: JSON.stringify(jsonRPCRequest), + }).then((response) => { + if (response.status === 200) { + return response + .json() + .then((jsonRPCResponse) => client.receive(jsonRPCResponse)); + } else if (jsonRPCRequest.id !== undefined) { + return Promise.reject(new Error(response.statusText)); + } + }); +}); + +// declaring a function that takes the name of the foreign call (getSqrt) and the inputs +const foreignCallHandler = async (name, input) => { + // notice that the "inputs" parameter contains *all* the inputs + // in this case we make the RPC request with the first parameter "numbers", which would be input[0] + const oracleReturn = await client.request(name, [ + input[0].map((i) => i.toString("hex")), + ]); + return { values: oracleReturn }; +}; + +// the rest of your NoirJS code +const input = { input: [4, 16] }; +const { witness } = await noir.execute(numbers, foreignCallHandler); +``` + +:::tip + +If you're in a NoirJS environment running your RPC server together with a frontend app, you'll probably hit a familiar problem in full-stack development: requests being blocked by [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) policy. For development only, you can simply install and use the [`cors` npm package](https://www.npmjs.com/package/cors) to get around the problem: + +```bash +yarn add cors +``` + +and use it as a middleware: + +```js +import cors from "cors"; + +const app = express(); +app.use(cors()) +``` + +::: + +## Conclusion + +Hopefully by the end of this guide, you should be able to: + +- Write your own logic around Oracles and how to write a JSON RPC server to make them work with your Nargo commands. +- Provide custom foreign call handlers for NoirJS. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/how_to/how-to-recursion.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/how_to/how-to-recursion.md new file mode 100644 index 00000000000..c8c4dc9f5b4 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/how_to/how-to-recursion.md @@ -0,0 +1,180 @@ +--- +title: How to use recursion on NoirJS +description: Learn how to implement recursion with NoirJS, a powerful tool for creating smart contracts on the EVM blockchain. This guide assumes familiarity with NoirJS, solidity verifiers, and the Barretenberg proving backend. Discover how to generate both final and intermediate proofs using `noir_js` and `backend_barretenberg`. +keywords: + [ + "NoirJS", + "EVM blockchain", + "smart contracts", + "recursion", + "solidity verifiers", + "Barretenberg backend", + "noir_js", + "backend_barretenberg", + "intermediate proofs", + "final proofs", + "nargo compile", + "json import", + "recursive circuit", + "recursive app" + ] +sidebar_position: 1 +--- + +This guide shows you how to use recursive proofs in your NoirJS app. For the sake of clarity, it is assumed that: + +- You already have a NoirJS app. If you don't, please visit the [NoirJS tutorial](../tutorials/noirjs_app.md) and the [reference](../reference/NoirJS/noir_js/index.md). +- You are familiar with what are recursive proofs and you have read the [recursion explainer](../explainers/explainer-recursion.md) +- You already built a recursive circuit following [the reference](../noir/standard_library/recursion.mdx), and understand how it works. + +It is also assumed that you're not using `noir_wasm` for compilation, and instead you've used [`nargo compile`](../reference/nargo_commands.md) to generate the `json` you're now importing into your project. However, the guide should work just the same if you're using `noir_wasm`. + +:::info + +As you've read in the [explainer](../explainers/explainer-recursion.md), a recursive proof is an intermediate proof. This means that it doesn't necessarily generate the final step that makes it verifiable in a smart contract. However, it is easy to verify within another circuit. + +While "standard" usage of NoirJS packages abstracts final proofs, it currently lacks the necessary interface to abstract away intermediate proofs. This means that these proofs need to be created by using the backend directly. + +In short: + +- `noir_js` generates *only* final proofs +- `backend_barretenberg` generates both types of proofs + +::: + +In a standard recursive app, you're also dealing with at least two circuits. For the purpose of this guide, we will assume the following: + +- `main`: a circuit of type `assert(x != y)`, where `main` is marked with a `#[recursive]` attribute. This attribute states that the backend should generate proofs that are friendly for verification within another circuit. +- `recursive`: a circuit that verifies `main` + +For a full example of how recursive proofs work, please refer to the [noir-examples](https://github.com/noir-lang/noir-examples) repository. We will *not* be using it as a reference for this guide. + +## Step 1: Setup + +In a common NoirJS app, you need to instantiate a backend with something like `const backend = new Backend(circuit)`. Then you feed it to the `noir_js` interface. + +For recursion, this doesn't happen, and the only need for `noir_js` is only to `execute` a circuit and get its witness and return value. Everything else is not interfaced, so it needs to happen on the `backend` object. + +It is also recommended that you instantiate the backend with as many threads as possible, to allow for maximum concurrency: + +```js +const backend = new Backend(circuit, { threads: 8 }) +``` + +:::tip +You can use the [`os.cpus()`](https://nodejs.org/api/os.html#oscpus) object in `nodejs` or [`navigator.hardwareConcurrency`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/hardwareConcurrency) on the browser to make the most out of those glorious cpu cores +::: + +## Step 2: Generating the witness and the proof for `main` + +After instantiating the backend, you should also instantiate `noir_js`. We will use it to execute the circuit and get the witness. + +```js +const noir = new Noir(circuit) +const { witness } = noir.execute(input) +``` + +With this witness, you are now able to generate the intermediate proof for the main circuit: + +```js +const { proof, publicInputs } = await backend.generateProof(witness) +``` + +:::warning + +Always keep in mind what is actually happening on your development process, otherwise you'll quickly become confused about what circuit we are actually running and why! + +In this case, you can imagine that Alice (running the `main` circuit) is proving something to Bob (running the `recursive` circuit), and Bob is verifying her proof within his proof. + +With this in mind, it becomes clear that our intermediate proof is the one *meant to be verified within another circuit*, so it must be Alice's. Actually, the only final proof in this theoretical scenario would be the last one, sent on-chain. + +::: + +## Step 3 - Verification and proof artifacts + +Optionally, you are able to verify the intermediate proof: + +```js +const verified = await backend.verifyProof({ proof, publicInputs }) +``` + +This can be useful to make sure our intermediate proof was correctly generated. But the real goal is to do it within another circuit. For that, we need to generate recursive proof artifacts that will be passed to the circuit that is verifying the proof we just generated. Instead of passing the proof and verification key as a byte array, we pass them as fields which makes it cheaper to verify in a circuit: + +```js +const { proofAsFields, vkAsFields, vkHash } = await backend.generateRecursiveProofArtifacts( { publicInputs, proof }, publicInputsCount) +``` + +This call takes the public inputs and the proof, but also the public inputs count. While this is easily retrievable by simply counting the `publicInputs` length, the backend interface doesn't currently abstract it away. + +:::info + +The `proofAsFields` has a constant size `[Field; 93]` and verification keys in Barretenberg are always `[Field; 114]`. + +::: + +:::warning + +One common mistake is to forget *who* makes this call. + +In a situation where Alice is generating the `main` proof, if she generates the proof artifacts and sends them to Bob, which gladly takes them as true, this would mean Alice could prove anything! + +Instead, Bob needs to make sure *he* extracts the proof artifacts, using his own instance of the `main` circuit backend. This way, Alice has to provide a valid proof for the correct `main` circuit. + +::: + +## Step 4 - Recursive proof generation + +With the artifacts, generating a recursive proof is no different from a normal proof. You simply use the `backend` (with the recursive circuit) to generate it: + +```js +const recursiveInputs = { + verification_key: vkAsFields, // array of length 114 + proof: proofAsFields, // array of length 93 + size of public inputs + publicInputs: [mainInput.y], // using the example above, where `y` is the only public input + key_hash: vkHash, +} + +const { witness, returnValue } = noir.execute(recursiveInputs) // we're executing the recursive circuit now! +const { proof, publicInputs } = backend.generateProof(witness) +const verified = backend.verifyProof({ proof, publicInputs }) +``` + +You can obviously chain this proof into another proof. In fact, if you're using recursive proofs, you're probably interested of using them this way! + +:::tip + +Managing circuits and "who does what" can be confusing. To make sure your naming is consistent, you can keep them in an object. For example: + +```js +const circuits = { + main: mainJSON, + recursive: recursiveJSON +} +const backends = { + main: new BarretenbergBackend(circuits.main), + recursive: new BarretenbergBackend(circuits.recursive) +} +const noir_programs = { + main: new Noir(circuits.main), + recursive: new Noir(circuits.recursive) +} +``` + +This allows you to neatly call exactly the method you want without conflicting names: + +```js +// Alice runs this 👇 +const { witness: mainWitness } = await noir_programs.main.execute(input) +const proof = await backends.main.generateProof(mainWitness) + +// Bob runs this 👇 +const verified = await backends.main.verifyProof(proof) +const { proofAsFields, vkAsFields, vkHash } = await backends.main.generateRecursiveProofArtifacts( + proof, + numPublicInputs, +); +const { witness: recursiveWitness } = await noir_programs.recursive.execute(recursiveInputs) +const recursiveProof = await backends.recursive.generateProof(recursiveWitness); +``` + +::: diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/how_to/how-to-solidity-verifier.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/how_to/how-to-solidity-verifier.md new file mode 100644 index 00000000000..a8169595b3d --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/how_to/how-to-solidity-verifier.md @@ -0,0 +1,259 @@ +--- +title: Generate a Solidity Verifier +description: + Learn how to run the verifier as a smart contract on the blockchain. Compile a Solidity verifier + contract for your Noir program and deploy it on any EVM blockchain acting as a verifier smart + contract. Read more to find out +keywords: + [ + solidity verifier, + smart contract, + blockchain, + compiler, + plonk_vk.sol, + EVM blockchain, + verifying Noir programs, + proving backend, + Barretenberg, + ] +sidebar_position: 0 +pagination_next: tutorials/noirjs_app +--- + +Noir has the ability to generate a verifier contract in Solidity, which can be deployed in many EVM-compatible blockchains such as Ethereum. + +This allows for a powerful feature set, as one can make use of the conciseness and the privacy provided by Noir in an immutable ledger. Applications can range from simple P2P guessing games, to complex private DeFi interactions. + +This guide shows you how to generate a Solidity Verifier and deploy it on the [Remix IDE](https://remix.ethereum.org/). It is assumed that: + +- You are comfortable with the Solidity programming language and understand how contracts are deployed on the Ethereum network +- You have Noir installed and you have a Noir program. If you don't, [get started](../getting_started/installation/index.md) with Nargo and the example Hello Noir circuit +- You are comfortable navigating RemixIDE. If you aren't or you need a refresher, you can find some video tutorials [here](https://www.youtube.com/channel/UCjTUPyFEr2xDGN6Cg8nKDaA) that could help you. + +## Rundown + +Generating a Solidity Verifier contract is actually a one-command process. However, compiling it and deploying it can have some caveats. Here's the rundown of this guide: + +1. How to generate a solidity smart contract +2. How to compile the smart contract in the RemixIDE +3. How to deploy it to a testnet + +## Step 1 - Generate a contract + +This is by far the most straightforward step. Just run: + +```sh +nargo compile +``` + +This will compile your source code into a Noir build artifact to be stored in the `./target` directory, you can then generate the smart contract using the commands: + +```sh +# Here we pass the path to the newly generated Noir artifact. +bb write_vk -b ./target/.json +bb contract +``` + +replacing `` with the name of your Noir project. A new `contract` folder would then be generated in your project directory, containing the Solidity +file `contract.sol`. It can be deployed to any EVM blockchain acting as a verifier smart contract. + +You can find more information about `bb` and the default Noir proving backend on [this page](../getting_started/hello_noir/index.md#proving-backend). + +:::info + +It is possible to generate verifier contracts of Noir programs for other smart contract platforms as long as the proving backend supplies an implementation. + +Barretenberg, the default proving backend for Nargo, supports generation of verifier contracts, for the time being these are only in Solidity. +::: + +## Step 2 - Compiling + +We will mostly skip the details of RemixIDE, as the UI can change from version to version. For now, we can just open +Remix and create a blank workspace. + +![Create Workspace](@site/static/img/how-tos/solidity_verifier_1.png) + +We will create a new file to contain the contract Nargo generated, and copy-paste its content. + +:::warning + +You'll likely see a warning advising you to not trust pasted code. While it is an important warning, it is irrelevant in the context of this guide and can be ignored. We will not be deploying anywhere near a mainnet. + +::: + +To compile our the verifier, we can navigate to the compilation tab: + +![Compilation Tab](@site/static/img/how-tos/solidity_verifier_2.png) + +Remix should automatically match a suitable compiler version. However, hitting the "Compile" button will most likely generate a "Stack too deep" error: + +![Stack too deep](@site/static/img/how-tos/solidity_verifier_3.png) + +This is due to the verify function needing to put many variables on the stack, but enabling the optimizer resolves the issue. To do this, let's open the "Advanced Configurations" tab and enable optimization. The default 200 runs will suffice. + +:::info + +This time we will see a warning about an unused function parameter. This is expected, as the `verify` function doesn't use the `_proof` parameter inside a solidity block, it is loaded from calldata and used in assembly. + +::: + +![Compilation success](@site/static/img/how-tos/solidity_verifier_4.png) + +## Step 3 - Deploying + +At this point we should have a compiled contract ready to deploy. If we navigate to the deploy section in Remix, we will see many different environments we can deploy to. The steps to deploy on each environment would be out-of-scope for this guide, so we will just use the default Remix VM. + +Looking closely, we will notice that our "Solidity Verifier" is actually three contracts working together: + +- An `UltraVerificationKey` library which simply stores the verification key for our circuit. +- An abstract contract `BaseUltraVerifier` containing most of the verifying logic. +- A main `UltraVerifier` contract that inherits from the Base and uses the Key contract. + +Remix will take care of the dependencies for us so we can simply deploy the UltraVerifier contract by selecting it and hitting "deploy": + +![Deploying UltraVerifier](@site/static/img/how-tos/solidity_verifier_5.png) + +A contract will show up in the "Deployed Contracts" section, where we can retrieve the Verification Key Hash. This is particularly useful for double-checking that the deployer contract is the correct one. + +:::note + +Why "UltraVerifier"? + +To be precise, the Noir compiler (`nargo`) doesn't generate the verifier contract directly. It compiles the Noir code into an intermediate language (ACIR), which is then executed by the backend. So it is the backend that returns the verifier smart contract, not Noir. + +In this case, the Barretenberg Backend uses the UltraPlonk proving system, hence the "UltraVerifier" name. + +::: + +## Step 4 - Verifying + +To verify a proof using the Solidity verifier contract, we call the `verify` function in this extended contract: + +```solidity +function verify(bytes calldata _proof, bytes32[] calldata _publicInputs) external view returns (bool) +``` + +When using the default example in the [Hello Noir](../getting_started/hello_noir/index.md) guide, the easiest way to confirm that the verifier contract is doing its job is by calling the `verify` function via remix with the required parameters. Note that the public inputs must be passed in separately to the rest of the proof so we must split the proof as returned from `bb`. + +First generate a proof with `bb` at the location `./proof` using the steps in [get started](../getting_started/hello_noir/index.md), this proof is in a binary format but we want to convert it into a hex string to pass into Remix, this can be done with the + +```bash +# This value must be changed to match the number of public inputs (including return values!) in your program. +NUM_PUBLIC_INPUTS=1 +PUBLIC_INPUT_BYTES=32*NUM_PUBLIC_INPUTS +HEX_PUBLIC_INPUTS=$(head -c $PUBLIC_INPUT_BYTES ./proof | od -An -v -t x1 | tr -d $' \n') +HEX_PROOF=$(tail -c +$(($PUBLIC_INPUT_BYTES + 1)) ./proof | od -An -v -t x1 | tr -d $' \n') + +echo "Public inputs:" +echo $HEX_PUBLIC_INPUTS + +echo "Proof:" +echo "0x$HEX_PROOF" +``` + +Remix expects that the public inputs will be split into an array of `bytes32` values so `HEX_PUBLIC_INPUTS` needs to be split up into 32 byte chunks which are prefixed with `0x` accordingly. + +A programmatic example of how the `verify` function is called can be seen in the example zk voting application [here](https://github.com/noir-lang/noir-examples/blob/33e598c257e2402ea3a6b68dd4c5ad492bce1b0a/foundry-voting/src/zkVote.sol#L35): + +```solidity +function castVote(bytes calldata proof, uint proposalId, uint vote, bytes32 nullifierHash) public returns (bool) { + // ... + bytes32[] memory publicInputs = new bytes32[](4); + publicInputs[0] = merkleRoot; + publicInputs[1] = bytes32(proposalId); + publicInputs[2] = bytes32(vote); + publicInputs[3] = nullifierHash; + require(verifier.verify(proof, publicInputs), "Invalid proof"); +``` + +:::info[Return Values] + +A circuit doesn't have the concept of a return value. Return values are just syntactic sugar in Noir. + +Under the hood, the return value is passed as an input to the circuit and is checked at the end of the circuit program. + +For example, if you have Noir program like this: + +```rust +fn main( + // Public inputs + pubkey_x: pub Field, + pubkey_y: pub Field, + // Private inputs + priv_key: Field, +) -> pub Field +``` + +the `verify` function will expect the public inputs array (second function parameter) to be of length 3, the two inputs and the return value. + +Passing only two inputs will result in an error such as `PUBLIC_INPUT_COUNT_INVALID(3, 2)`. + +In this case, the inputs parameter to `verify` would be an array ordered as `[pubkey_x, pubkey_y, return`. + +::: + +:::tip[Structs] + +You can pass structs to the verifier contract. They will be flattened so that the array of inputs is 1-dimensional array. + +For example, consider the following program: + +```rust +struct Type1 { + val1: Field, + val2: Field, +} + +struct Nested { + t1: Type1, + is_true: bool, +} + +fn main(x: pub Field, nested: pub Nested, y: pub Field) { + //... +} +``` + +The order of these inputs would be flattened to: `[x, nested.t1.val1, nested.t1.val2, nested.is_true, y]` + +::: + +The other function you can call is our entrypoint `verify` function, as defined above. + +:::tip + +It's worth noticing that the `verify` function is actually a `view` function. A `view` function does not alter the blockchain state, so it doesn't need to be distributed (i.e. it will run only on the executing node), and therefore doesn't cost any gas. + +This can be particularly useful in some situations. If Alice generated a proof and wants Bob to verify its correctness, Bob doesn't need to run Nargo, NoirJS, or any Noir specific infrastructure. He can simply make a call to the blockchain with the proof and verify it is correct without paying any gas. + +It would be incorrect to say that a Noir proof verification costs any gas at all. However, most of the time the result of `verify` is used to modify state (for example, to update a balance, a game state, etc). In that case the whole network needs to execute it, which does incur gas costs (calldata and execution, but not storage). + +::: + +## A Note on EVM chains + +Noir proof verification requires the ecMul, ecAdd and ecPairing precompiles. Not all EVM chains support EC Pairings, notably some of the ZK-EVMs. This means that you won't be able to use the verifier contract in all of them. You can find an incomplete list of which EVM chains support these precompiles [here](https://www.evmdiff.com/features?feature=precompiles). + +For example, chains like `zkSync ERA` and `Polygon zkEVM` do not currently support these precompiles, so proof verification via Solidity verifier contracts won't work. Here's a quick list of EVM chains that have been tested and are known to work: + +- Optimism +- Arbitrum +- Polygon PoS +- Scroll +- Celo +- BSC +- Blast L2 +- Avalanche C-Chain +- Mode +- Linea +- Moonbeam + +If you test any other chains, please open a PR on this page to update the list. See [this doc](https://github.com/noir-lang/noir-starter/tree/main/with-foundry#testing-on-chain) for more info about testing verifier contracts on different EVM chains. + +## What's next + +Now that you know how to call a Noir Solidity Verifier on a smart contract using Remix, you should be comfortable with using it with some programmatic frameworks, such as [hardhat](https://github.com/noir-lang/noir-starter/tree/main/vite-hardhat) and [foundry](https://github.com/noir-lang/noir-starter/tree/main/with-foundry). + +You can find other tools, examples, boilerplates and libraries in the [awesome-noir](https://github.com/noir-lang/awesome-noir) repository. + +You should also be ready to write and deploy your first NoirJS app and start generating proofs on websites, phones, and NodeJS environments! Head on to the [NoirJS tutorial](../tutorials/noirjs_app.md) to learn how to do that. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/how_to/merkle-proof.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/how_to/merkle-proof.mdx new file mode 100644 index 00000000000..0a128adb2de --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/how_to/merkle-proof.mdx @@ -0,0 +1,48 @@ +--- +title: Prove Merkle Tree Membership +description: + Learn how to use merkle membership proof in Noir to prove that a given leaf is a member of a + merkle tree with a specified root, at a given index. +keywords: + [merkle proof, merkle membership proof, Noir, rust, hash function, Pedersen, sha256, merkle tree] +sidebar_position: 4 +--- + +Let's walk through an example of a merkle membership proof in Noir that proves that a given leaf is +in a merkle tree. + +```rust + +fn main(message : [Field; 62], index : Field, hashpath : [Field; 40], root : Field) { + let leaf = std::hash::hash_to_field(message.as_slice()); + let merkle_root = std::merkle::compute_merkle_root(leaf, index, hashpath); + assert(merkle_root == root); +} + +``` + +The message is hashed using `hash_to_field`. The specific hash function that is being used is chosen +by the backend. The only requirement is that this hash function can heuristically be used as a +random oracle. If only collision resistance is needed, then one can call `std::hash::pedersen_hash` +instead. + +```rust +let leaf = std::hash::hash_to_field(message.as_slice()); +``` + +The leaf is then passed to a compute_merkle_root function with the root, index and hashpath. The returned root can then be asserted to be the same as the provided root. + +```rust +let merkle_root = std::merkle::compute_merkle_root(leaf, index, hashpath); +assert (merkle_root == root); +``` + +> **Note:** It is possible to re-implement the merkle tree implementation without standard library. +> However, for most usecases, it is enough. In general, the standard library will always opt to be +> as conservative as possible, while striking a balance with efficiency. + +An example, the merkle membership proof, only requires a hash function that has collision +resistance, hence a hash function like Pedersen is allowed, which in most cases is more efficient +than the even more conservative sha256. + +[View an example on the starter repo](https://github.com/noir-lang/noir-examples/blob/3ea09545cabfa464124ec2f3ea8e60c608abe6df/stealthdrop/circuits/src/main.nr#L20) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/how_to/using-devcontainers.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/how_to/using-devcontainers.mdx new file mode 100644 index 00000000000..727ec6ca667 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/how_to/using-devcontainers.mdx @@ -0,0 +1,110 @@ +--- +title: Developer Containers and Codespaces +description: "Learn how to set up a devcontainer in your GitHub repository for a seamless coding experience with Codespaces. Follow our easy 8-step guide to create your own Noir environment without installing Nargo locally." +keywords: ["Devcontainer", "Codespaces", "GitHub", "Noir Environment", "Docker Image", "Development Environment", "Remote Coding", "GitHub Codespaces", "Noir Programming", "Nargo", "VSCode Extensions", "Noirup"] +sidebar_position: 1 +--- + +Adding a developer container configuration file to your Noir project is one of the easiest way to unlock coding in browser. + +## What's a devcontainer after all? + +A [Developer Container](https://containers.dev/) (devcontainer for short) is a Docker image that comes preloaded with tools, extensions, and other tools you need to quickly get started or continue a project, without having to install Nargo locally. Think of it as a development environment in a box. + +There are many advantages to this: + +- It's platform and architecture agnostic +- You don't need to have an IDE installed, or Nargo, or use a terminal at all +- It's safer for using on a public machine or public network + +One of the best ways of using devcontainers is... not using your machine at all, for maximum control, performance, and ease of use. +Enter Codespaces. + +## Codespaces + +If a devcontainer is just a Docker image, then what stops you from provisioning a `p3dn.24xlarge` AWS EC2 instance with 92 vCPUs and 768 GiB RAM and using it to prove your 10-gate SNARK proof? + +Nothing! Except perhaps the 30-40$ per hour it will cost you. + +The problem is that provisioning takes time, and I bet you don't want to see the AWS console every time you want to code something real quick. + +Fortunately, there's an easy and free way to get a decent remote machine ready and loaded in less than 2 minutes: Codespaces. [Codespaces is a Github feature](https://github.com/features/codespaces) that allows you to code in a remote machine by using devcontainers, and it's pretty cool: + +- You can start coding Noir in less than a minute +- It uses the resources of a remote machine, so you can code on your grandma's phone if needed be +- It makes it easy to share work with your frens +- It's fully reusable, you can stop and restart whenever you need to + +:::info + +Don't take out your wallet just yet. Free GitHub accounts get about [15-60 hours of coding](https://github.com/features/codespaces) for free per month, depending on the size of your provisioned machine. + +::: + +## Tell me it's _actually_ easy + +It is! + +Github comes with a default codespace and you can use it to code your own devcontainer. That's exactly what we will be doing in this guide. + + + +8 simple steps: + +#### 1. Create a new repository on GitHub. + +#### 2. Click "Start coding with Codespaces". This will use the default image. + +#### 3. Create a folder called `.devcontainer` in the root of your repository. + +#### 4. Create a Dockerfile in that folder, and paste the following code: + +```docker +FROM --platform=linux/amd64 node:lts-bookworm-slim +SHELL ["/bin/bash", "-c"] +RUN apt update && apt install -y curl bash git tar gzip libc++-dev +RUN curl -L https://raw.githubusercontent.com/noir-lang/noirup/main/install | bash +ENV PATH="/root/.nargo/bin:$PATH" +RUN noirup +ENTRYPOINT ["nargo"] +``` +#### 5. Create a file called `devcontainer.json` in the same folder, and paste the following code: + +```json +{ + "name": "Noir on Codespaces", + "build": { + "context": ".", + "dockerfile": "Dockerfile" + }, + "customizations": { + "vscode": { + "extensions": ["noir-lang.vscode-noir"] + } + } +} +``` +#### 6. Commit and push your changes + +This will pull the new image and build it, so it could take a minute or so + +#### 8. Done! +Just wait for the build to finish, and there's your easy Noir environment. + + +Refer to [noir-starter](https://github.com/noir-lang/noir-starter/) as an example of how devcontainers can be used together with codespaces. + + + +## How do I use it? + +Using the codespace is obviously much easier than setting it up. +Just navigate to your repository and click "Code" -> "Open with Codespaces". It should take a few seconds to load, and you're ready to go. + +:::info + +If you really like the experience, you can add a badge to your readme, links to existing codespaces, and more. +Check out the [official docs](https://docs.github.com/en/codespaces/setting-up-your-project-for-codespaces/setting-up-your-repository/facilitating-quick-creation-and-resumption-of-codespaces) for more info. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/index.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/index.mdx new file mode 100644 index 00000000000..a6bd306f91d --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/index.mdx @@ -0,0 +1,67 @@ +--- +title: Noir Lang +hide_title: true +description: + Learn about the public alpha release of Noir, a domain specific language heavily influenced by Rust that compiles to + an intermediate language which can be compiled to an arithmetic circuit or a rank-1 constraint system. +keywords: + [Noir, + Domain Specific Language, + Rust, + Intermediate Language, + Arithmetic Circuit, + Rank-1 Constraint System, + Ethereum Developers, + Protocol Developers, + Blockchain Developers, + Proving System, + Smart Contract Language] +sidebar_position: 0 +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Noir Logo + +Noir is an open-source Domain-Specific Language for safe and seamless construction of privacy-preserving Zero-Knowledge programs, requiring no previous knowledge on the underlying mathematics or cryptography. + +ZK programs are programs that can generate short proofs of statements without revealing all inputs to the statements. You can read more about Zero-Knowledge Proofs [here](https://dev.to/spalladino/a-beginners-intro-to-coding-zero-knowledge-proofs-c56). + +## What's new about Noir? + +Noir works differently from most ZK languages by taking a two-pronged path. First, it compiles the program to an adaptable intermediate language known as ACIR. From there, depending on a given project's needs, ACIR can be further compiled into an arithmetic circuit for integration with the proving backend. + +:::info + +Noir is backend agnostic, which means it makes no assumptions on which proving backend powers the ZK proof. Being the language that powers [Aztec Contracts](https://docs.aztec.network/developers/contracts/main), it defaults to Aztec's Barretenberg proving backend. + +However, the ACIR output can be transformed to be compatible with other PLONK-based backends, or into a [rank-1 constraint system](https://www.rareskills.io/post/rank-1-constraint-system) suitable for backends such as Arkwork's Marlin. + +::: + +## Who is Noir for? + +Noir can be used both in complex cloud-based backends and in user's smartphones, requiring no knowledge on the underlying math or cryptography. From authorization systems that keep a password in the user's device, to complex on-chain verification of recursive proofs, Noir is designed to abstract away complexity without any significant overhead. Here are some examples of situations where Noir can be used: + + + + Noir Logo + + Aztec Contracts leverage Noir to allow for the storage and execution of private information. Writing an Aztec Contract is as easy as writing Noir, and Aztec developers can easily interact with the network storage and execution through the [Aztec.nr](https://docs.aztec.network/developers/contracts/main) library. + + + Soliditry Verifier Example + Noir can auto-generate Solidity verifier contracts that verify Noir proofs. This allows for non-interactive verification of proofs containing private information in an immutable system. This feature powers a multitude of use-case scenarios, from P2P chess tournaments, to [Aztec Layer-2 Blockchain](https://docs.aztec.network/) + + + Aztec Labs developed NoirJS, an easy interface to generate and verify Noir proofs in a Javascript environment. This allows for Noir to be used in webpages, mobile apps, games, and any other environment supporting JS execution in a standalone manner. + + + + +## Libraries + +Noir is meant to be easy to extend by simply importing Noir libraries just like in Rust. +The [awesome-noir repo](https://github.com/noir-lang/awesome-noir#libraries) is a collection of libraries developed by the Noir community. +Writing a new library is easy and makes code be composable and easy to reuse. See the section on [dependencies](noir/modules_packages_crates/dependencies.md) for more information. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/migration_notes.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/migration_notes.md new file mode 100644 index 00000000000..6bd740024e5 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/migration_notes.md @@ -0,0 +1,105 @@ +--- +title: Migration notes +description: Read about migration notes from previous versions, which could solve problems while updating +keywords: [Noir, notes, migration, updating, upgrading] +--- + +Noir is in full-speed development. Things break fast, wild, and often. This page attempts to leave some notes on errors you might encounter when upgrading and how to resolve them until proper patches are built. + +### `backend encountered an error: libc++.so.1` + +Depending on your OS, you may encounter the following error when running `nargo prove` for the first time: + +```text +The backend encountered an error: "/home/codespace/.nargo/backends/acvm-backend-barretenberg/backend_binary: error while loading shared libraries: libc++.so.1: cannot open shared object file: No such file or directory\n" +``` + +Install the `libc++-dev` library with: + +```bash +sudo apt install libc++-dev +``` + +## ≥0.19 + +### Enforcing `compiler_version` + +From this version on, the compiler will check for the `compiler_version` field in `Nargo.toml`, and will error if it doesn't match the current Nargo version in use. + +To update, please make sure this field in `Nargo.toml` matches the output of `nargo --version`. + +## ≥0.14 + +The index of the [for loops](noir/concepts/control_flow.md#loops) is now of type `u64` instead of `Field`. An example refactor would be: + +```rust +for i in 0..10 { + let i = i as Field; +} +``` + +## ≥v0.11.0 and Nargo backend + +From this version onwards, Nargo starts managing backends through the `nargo backend` command. Upgrading to the versions per usual steps might lead to: + +### `backend encountered an error` + +This is likely due to the existing locally installed version of proving backend (e.g. barretenberg) is incompatible with the version of Nargo in use. + +To fix the issue: + +1. Uninstall the existing backend + +```bash +nargo backend uninstall acvm-backend-barretenberg +``` + +You may replace _acvm-backend-barretenberg_ with the name of your backend listed in `nargo backend ls` or in ~/.nargo/backends. + +2. Reinstall a compatible version of the proving backend. + +If you are using the default barretenberg backend, simply run: + +``` +nargo prove +``` + +with your Noir program. + +This will trigger the download and installation of the latest version of barretenberg compatible with your Nargo in use. + +### `backend encountered an error: illegal instruction` + +On certain Intel-based systems, an `illegal instruction` error may arise due to incompatibility of barretenberg with certain CPU instructions. + +To fix the issue: + +1. Uninstall the existing backend + +```bash +nargo backend uninstall acvm-backend-barretenberg +``` + +You may replace _acvm-backend-barretenberg_ with the name of your backend listed in `nargo backend ls` or in ~/.nargo/backends. + +2. Reinstall a compatible version of the proving backend. + +If you are using the default barretenberg backend, simply run: + +``` +nargo backend install acvm-backend-barretenberg https://github.com/noir-lang/barretenberg-js-binary/raw/master/run-bb.tar.gz +``` + +This downloads and installs a specific bb.js based version of barretenberg binary from GitHub. + +The gzipped file is running [this bash script](https://github.com/noir-lang/barretenberg-js-binary/blob/master/run-bb-js.sh), where we need to gzip it as the Nargo currently expect the backend to be zipped up. + +Then run: + +``` +DESIRED_BINARY_VERSION=0.8.1 nargo info +``` + +This overrides the bb native binary with a bb.js node application instead, which should be compatible with most if not all hardware. This does come with the drawback of being generally slower than native binary. + +0.8.1 indicates bb.js version 0.8.1, so if you change that it will update to a different version or the default version in the script if none was supplied. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/_category_.json b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/_category_.json new file mode 100644 index 00000000000..7da08f8a8c5 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Concepts", + "position": 0, + "collapsible": true, + "collapsed": true +} \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/assert.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/assert.md new file mode 100644 index 00000000000..2132de42072 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/assert.md @@ -0,0 +1,78 @@ +--- +title: Assert Function +description: + Learn about the `assert` and `static_assert` functions in Noir, which can be used to explicitly + constrain the predicate or comparison expression that follows to be true, and what happens if + the expression is false at runtime or compile-time, respectively. +keywords: [Noir programming language, assert statement, predicate expression, comparison expression] +sidebar_position: 4 +--- + +Noir includes a special `assert` function which will explicitly constrain the predicate/comparison +expression that follows to be true. If this expression is false at runtime, the program will fail to +be proven. Example: + +```rust +fn main(x : Field, y : Field) { + assert(x == y); +} +``` + +> Assertions only work for predicate operations, such as `==`. If there's any ambiguity on the operation, the program will fail to compile. For example, it is unclear if `assert(x + y)` would check for `x + y == 0` or simply would return `true`. + +You can optionally provide a message to be logged when the assertion fails: + +```rust +assert(x == y, "x and y are not equal"); +``` + +Aside string literals, the optional message can be a format string or any other type supported as input for Noir's [print](../standard_library/logging.md) functions. This feature lets you incorporate runtime variables into your failed assertion logs: + +```rust +assert(x == y, f"Expected x == y, but got {x} == {y}"); +``` + +Using a variable as an assertion message directly: + +```rust +struct myStruct { + myField: Field +} + +let s = myStruct { myField: y }; +assert(s.myField == x, s); +``` + +There is also a special `static_assert` function that behaves like `assert`, +but that runs at compile-time. + +```rust +fn main(xs: [Field; 3]) { + let x = 2 + 2; + let y = 4; + static_assert(x == y, "expected 2 + 2 to equal 4"); + + // This passes since the length of `xs` is known at compile-time + static_assert(xs.len() == 3, "expected the input to have 3 elements"); +} +``` + +This function fails when passed a dynamic (run-time) argument: + +```rust +fn main(x : Field, y : Field) { + // this fails because `x` is not known at compile-time + static_assert(x == 2, "expected x to be known at compile-time and equal to 2"); + + let mut example_slice = &[]; + if y == 4 { + example_slice = example_slice.push_back(0); + } + + // This fails because the length of `example_slice` is not known at + // compile-time + let error_message = "expected an empty slice, known at compile-time"; + static_assert(example_slice.len() == 0, error_message); +} +``` + diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/comments.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/comments.md new file mode 100644 index 00000000000..b51a85f5c94 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/comments.md @@ -0,0 +1,33 @@ +--- +title: Comments +description: + Learn how to write comments in Noir programming language. A comment is a line of code that is + ignored by the compiler, but it can be read by programmers. Single-line and multi-line comments + are supported in Noir. +keywords: [Noir programming language, comments, single-line comments, multi-line comments] +sidebar_position: 10 +--- + +A comment is a line in your codebase which the compiler ignores, however it can be read by +programmers. + +Here is a single line comment: + +```rust +// This is a comment and is ignored +``` + +`//` is used to tell the compiler to ignore the rest of the line. + +Noir also supports multi-line block comments. Start a block comment with `/*` and end the block with `*/`. + +Noir does not natively support doc comments. You may be able to use [Rust doc comments](https://doc.rust-lang.org/reference/comments.html) in your code to leverage some Rust documentation build tools with Noir code. + +```rust +/* + This is a block comment describing a complex function. +*/ +fn main(x : Field, y : pub Field) { + assert(x != y); +} +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/comptime.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/comptime.md new file mode 100644 index 00000000000..50d7e686083 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/comptime.md @@ -0,0 +1,440 @@ +--- +title: Compile-time Code & Metaprogramming +description: Learn how to use metaprogramming in Noir to create macros or derive your own traits +keywords: [Noir, comptime, compile-time, metaprogramming, macros, quote, unquote] +sidebar_position: 15 +--- + +# Overview + +Metaprogramming in Noir is comprised of three parts: +1. `comptime` code +2. Quoting and unquoting +3. The metaprogramming API in `std::meta` + +Each of these are explained in more detail in the next sections but the wide picture is that +`comptime` allows us to write code which runs at compile-time. In this `comptime` code we +can quote and unquote snippets of the program, manipulate them, and insert them in other +parts of the program. Comptime functions which do this are said to be macros. Additionally, +there's a compile-time API of built-in types and functions provided by the compiler which allows +for greater analysis and modification of programs. + +--- + +# Comptime + +`comptime` is a new keyword in Noir which marks an item as executing or existing at compile-time. It can be used in several ways: + +- `comptime fn` to define functions which execute exclusively during compile-time. +- `comptime global` to define a global variable which is evaluated at compile-time. + - Unlike runtime globals, `comptime global`s can be mutable. +- `comptime { ... }` to execute a block of statements during compile-time. +- `comptime let` to define a variable whose value is evaluated at compile-time. +- `comptime for` to run a for loop at compile-time. Syntax sugar for `comptime { for .. }`. + +## Scoping + +Note that while in a `comptime` context, any runtime variables _local to the current function_ are never visible. + +## Evaluating + +Evaluation rules of `comptime` follows the normal unconstrained evaluation rules for other Noir code. There are a few things to note though: + +- Certain built-in functions may not be available, although more may be added over time. +- Evaluation order of global items is currently unspecified. For example, given the following two functions we can't guarantee +which `println` will execute first. The ordering of the two printouts will be arbitrary, but should be stable across multiple compilations with the same `nargo` version as long as the program is also unchanged. + +```rust +fn one() { + comptime { println("one"); } +} + +fn two() { + comptime { println("two"); } +} +``` + +- Since evaluation order is unspecified, care should be taken when using mutable globals so that they do not rely on a particular ordering. +For example, using globals to generate unique ids should be fine but relying on certain ids always being produced (especially after edits to the program) should be avoided. +- Although most ordering of globals is unspecified, two are: + - Dependencies of a crate will always be evaluated before the dependent crate. + - Any annotations on a function will be run before the function itself is resolved. This is to allow the annotation to modify the function if necessary. Note that if the + function itself was called at compile-time previously, it will already be resolved and cannot be modified. To prevent accidentally calling functions you wish to modify + at compile-time, it may be helpful to sort your `comptime` annotation functions into a different crate along with any dependencies they require. + +## Lowering + +When a `comptime` value is used in runtime code it must be lowered into a runtime value. This means replacing the expression with the literal that it evaluated to. For example, the code: + +```rust +struct Foo { array: [Field; 2], len: u32 } + +fn main() { + println(comptime { + let mut foo = std::mem::zeroed::(); + foo.array[0] = 4; + foo.len = 1; + foo + }); +} +``` + +will be converted to the following after `comptime` expressions are evaluated: + +```rust +struct Foo { array: [Field; 2], len: u32 } + +fn main() { + println(Foo { array: [4, 0], len: 1 }); +} +``` + +Not all types of values can be lowered. For example, `Type`s and `TypeDefinition`s (among other types) cannot be lowered at all. + +```rust +fn main() { + // There's nothing we could inline here to create a Type value at runtime + // let _ = get_type!(); +} + +comptime fn get_type() -> Type { ... } +``` + +--- + +# (Quasi) Quote + +Macros in Noir are `comptime` functions which return code as a value which is inserted into the call site when it is lowered there. +A code value in this case is of type `Quoted` and can be created by a `quote { ... }` expression. +More specifically, the code value `quote` creates is a token stream - a representation of source code as a series of words, numbers, string literals, or operators. +For example, the expression `quote { Hi "there reader"! }` would quote three tokens: the word "hi", the string "there reader", and an exclamation mark. +You'll note that snippets that would otherwise be invalid syntax can still be quoted. + +When a `Quoted` value is used in runtime code, it is lowered into a `quote { ... }` expression. Since this expression is only valid +in compile-time code however, we'd get an error if we tried this. Instead, we can use macro insertion to insert each token into the +program at that point, and parse it as an expression. To do this, we have to add a `!` after the function name returning the `Quoted` value. +If the value was created locally and there is no function returning it, `std::meta::unquote!(_)` can be used instead. +Calling such a function at compile-time without `!` will just return the `Quoted` value to be further manipulated. For example: + +```rust title="quote-example" showLineNumbers +comptime fn quote_one() -> Quoted { + quote { 1 } + } + + #[test] + fn returning_versus_macro_insertion() { + comptime + { + // let _a: Quoted = quote { 1 }; + let _a: Quoted = quote_one(); + + // let _b: Field = 1; + let _b: Field = quote_one!(); + + // Since integers default to fields, if we + // want a different type we have to explicitly cast + // let _c: i32 = 1 as i32; + let _c: i32 = quote_one!() as i32; + } + } +``` +> Source code: noir_stdlib/src/meta/mod.nr#L121-L142 + + +For those familiar with quoting from other languages (primarily lisps), Noir's `quote` is actually a _quasiquote_. +This means we can escape the quoting by using the unquote operator to splice values in the middle of quoted code. + +# Unquote + +The unquote operator `$` is usable within a `quote` expression. +It takes a variable as an argument, evaluates the variable, and splices the resulting value into the quoted token stream at that point. For example, + +```rust +comptime { + let x = 1 + 2; + let y = quote { $x + 4 }; +} +``` + +The value of `y` above will be the token stream containing `3`, `+`, and `4`. We can also use this to combine `Quoted` values into larger token streams: + +```rust +comptime { + let x = quote { 1 + 2 }; + let y = quote { $x + 4 }; +} +``` + +The value of `y` above is now the token stream containing five tokens: `1 + 2 + 4`. + +Note that to unquote something, a variable name _must_ follow the `$` operator in a token stream. +If it is an expression (even a parenthesized one), it will do nothing. Most likely a parse error will be given when the macro is later unquoted. + +Unquoting can also be avoided by escaping the `$` with a backslash: + +``` +comptime { + let x = quote { 1 + 2 }; + + // y contains the four tokens: `$x + 4` + let y = quote { \$x + 4 }; +} +``` + +--- + +# Annotations + +Annotations provide a way to run a `comptime` function on an item in the program. +When you use an annotation, the function with the same name will be called with that item as an argument: + +```rust +#[my_struct_annotation] +struct Foo {} + +comptime fn my_struct_annotation(s: StructDefinition) { + println("Called my_struct_annotation!"); +} + +#[my_function_annotation] +fn foo() {} + +comptime fn my_function_annotation(f: FunctionDefinition) { + println("Called my_function_annotation!"); +} +``` + +Anything returned from one of these functions will be inserted at top-level along with the original item. +Note that expressions are not valid at top-level so you'll get an error trying to return `3` or similar just as if you tried to write a program containing `3; struct Foo {}`. +You can insert other top-level items such as trait impls, structs, or functions this way though. +For example, this is the mechanism used to insert additional trait implementations into the program when deriving a trait impl from a struct: + +```rust title="derive-field-count-example" showLineNumbers +trait FieldCount { + fn field_count() -> u32; + } + + #[derive_field_count] + struct Bar { x: Field, y: [Field; 2] } + + comptime fn derive_field_count(s: StructDefinition) -> Quoted { + let typ = s.as_type(); + let field_count = s.fields().len(); + quote { + impl FieldCount for $typ { + fn field_count() -> u32 { + $field_count + } + } + } + } +``` +> Source code: noir_stdlib/src/meta/mod.nr#L144-L163 + + +## Calling annotations with additional arguments + +Arguments may optionally be given to annotations. +When this is done, these additional arguments are passed to the annotation function after the item argument. + +```rust title="annotation-arguments-example" showLineNumbers +#[assert_field_is_type(quote { i32 }.as_type())] + struct MyStruct { my_field: i32 } + + comptime fn assert_field_is_type(s: StructDefinition, typ: Type) { + // Assert the first field in `s` has type `typ` + let fields = s.fields(); + assert_eq(fields[0].1, typ); + } +``` +> Source code: noir_stdlib/src/meta/mod.nr#L165-L174 + + +We can also take any number of arguments by adding the `varargs` annotation: + +```rust title="annotation-varargs-example" showLineNumbers +#[assert_three_args(1, 2, 3)] + struct MyOtherStruct { my_other_field: u32 } + + #[varargs] + comptime fn assert_three_args(_s: StructDefinition, args: [Field]) { + assert_eq(args.len(), 3); + } +``` +> Source code: noir_stdlib/src/meta/mod.nr#L176-L184 + + +--- + +# Comptime API + +Although `comptime`, `quote`, and unquoting provide a flexible base for writing macros, +Noir's true metaprogramming ability comes from being able to interact with the compiler through a compile-time API. +This API can be accessed through built-in functions in `std::meta` as well as on methods of several `comptime` types. + +The following is an incomplete list of some `comptime` types along with some useful methods on them. + +- `Quoted`: A token stream +- `Type`: The type of a Noir type + - `fn implements(self, constraint: TraitConstraint) -> bool` + - Returns true if `self` implements the given trait constraint +- `Expr`: A syntactically valid expression. Can be used to recur on a program's parse tree to inspect how it is structured. + - Methods: + - `fn as_function_call(self) -> Option<(Expr, [Expr])>` + - If this is a function call expression, return `(function, arguments)` + - `fn as_block(self) -> Option<[Expr]>` + - If this is a block, return each statement in the block +- `FunctionDefinition`: A function definition + - Methods: + - `fn parameters(self) -> [(Quoted, Type)]` + - Returns a slice of `(name, type)` pairs for each parameter +- `StructDefinition`: A struct definition + - Methods: + - `fn as_type(self) -> Type` + - Returns this `StructDefinition` as a `Type`. Any generics are kept as-is + - `fn generics(self) -> [Quoted]` + - Return the name of each generic on this struct + - `fn fields(self) -> [(Quoted, Type)]` + - Return the name and type of each field +- `TraitConstraint`: A trait constraint such as `From` +- `TypedExpr`: A type-checked expression. +- `UnresolvedType`: A syntactic notation that refers to a Noir type that hasn't been resolved yet + +There are many more functions available by exploring the `std::meta` module and its submodules. +Using these methods is the key to writing powerful metaprogramming libraries. + +## `#[use_callers_scope]` + +Since certain functions such as `Quoted::as_type`, `Expression::as_type`, or `Quoted::as_trait_constraint` will attempt +to resolve their contents in a particular scope - it can be useful to change the scope they resolve in. By default +these functions will resolve in the current function's scope which is usually the attribute function they are called in. +If you're working on a library however, this may be a completely different module or crate to the item you're trying to +use the attribute on. If you want to be able to use `Quoted::as_type` to refer to types local to the caller's scope for +example, you can annotate your attribute function with `#[use_callers_scope]`. This will ensure your attribute, and any +closures it uses, can refer to anything in the caller's scope. `#[use_callers_scope]` also works recursively. So if both +your attribute function and a helper function it calls use it, then they can both refer to the same original caller. + +--- + +# Example: Derive + +Using all of the above, we can write a `derive` macro that behaves similarly to Rust's but is not built into the language. +From the user's perspective it will look like this: + +```rust +// Example usage +#[derive(Default, Eq, Ord)] +struct MyStruct { my_field: u32 } +``` + +To implement `derive` we'll have to create a `comptime` function that accepts +a variable amount of traits. + +```rust title="derive_example" showLineNumbers +// These are needed for the unconstrained hashmap we're using to store derive functions +use crate::collections::umap::UHashMap; +use crate::hash::BuildHasherDefault; +use crate::hash::poseidon2::Poseidon2Hasher; + +// A derive function is one that given a struct definition can +// create us a quoted trait impl from it. +pub type DeriveFunction = fn(StructDefinition) -> Quoted; + +// We'll keep a global HANDLERS map to keep track of the derive handler for each trait +comptime mut global HANDLERS: UHashMap> = UHashMap::default(); + +// Given a struct and a slice of traits to derive, create trait impls for each. +// This function is as simple as iterating over the slice, checking if we have a trait +// handler registered for the given trait, calling it, and appending the result. +#[varargs] +pub comptime fn derive(s: StructDefinition, traits: [TraitDefinition]) -> Quoted { + let mut result = quote {}; + + for trait_to_derive in traits { + let handler = unsafe { + HANDLERS.get(trait_to_derive) + }; + assert(handler.is_some(), f"No derive function registered for `{trait_to_derive}`"); + + let trait_impl = handler.unwrap()(s); + result = quote { $result $trait_impl }; + } + + result +} +``` +> Source code: noir_stdlib/src/meta/mod.nr#L31-L65 + + +Registering a derive function could be done as follows: + +```rust title="derive_via" showLineNumbers +// To register a handler for a trait, just add it to our handlers map +pub comptime fn derive_via(t: TraitDefinition, f: DeriveFunction) { + HANDLERS.insert(t, f); +} +``` +> Source code: noir_stdlib/src/meta/mod.nr#L67-L74 + + +```rust title="big-derive-usage-example" showLineNumbers +// Finally, to register a handler we call the above function as an annotation + // with our handler function. + #[derive_via(derive_do_nothing)] + trait DoNothing { + fn do_nothing(self); + } + + comptime fn derive_do_nothing(s: StructDefinition) -> Quoted { + // This is simplified since we don't handle generics or where clauses! + // In a real example we'd likely also need to introduce each of + // `s.generics()` as well as a trait constraint for each generic + // to ensure they also implement the trait. + let typ = s.as_type(); + quote { + impl DoNothing for $typ { + fn do_nothing(self) { + // Traits can't tell us what to do + println("something"); + } + } + } + } + + // Since `DoNothing` is a simple trait which: + // 1. Only has one method + // 2. Does not have any generics on the trait itself + // We can use `std::meta::make_trait_impl` to help us out. + // This helper function will generate our impl for us along with any + // necessary where clauses and still provides a flexible interface + // for us to work on each field on the struct. + comptime fn derive_do_nothing_alt(s: StructDefinition) -> Quoted { + let trait_name = quote { DoNothing }; + let method_signature = quote { fn do_nothing(self) }; + + // Call `do_nothing` recursively on each field in the struct + let for_each_field = |field_name| quote { self.$field_name.do_nothing(); }; + + // Some traits like Eq want to join each field expression with something like `&`. + // We don't need that here + let join_fields_with = quote {}; + + // The body function is a spot to insert any extra setup/teardown needed. + // We'll insert our println here. Since we recur on each field, we should see + // one println for the struct itself, followed by a println for every field (recursively). + let body = |body| quote { + println("something"); + $body + }; + crate::meta::make_trait_impl( + s, + trait_name, + method_signature, + for_each_field, + join_fields_with, + body + ) + } +``` +> Source code: noir_stdlib/src/meta/mod.nr#L186-L244 + diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/control_flow.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/control_flow.md new file mode 100644 index 00000000000..045d3c3a5f5 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/control_flow.md @@ -0,0 +1,77 @@ +--- +title: Control Flow +description: + Learn how to use loops and if expressions in the Noir programming language. Discover the syntax + and examples for for loops and if-else statements. +keywords: [Noir programming language, loops, for loop, if-else statements, Rust syntax] +sidebar_position: 2 +--- + +## If Expressions + +Noir supports `if-else` statements. The syntax is most similar to Rust's where it is not required +for the statement's conditional to be surrounded by parentheses. + +```rust +let a = 0; +let mut x: u32 = 0; + +if a == 0 { + if a != 0 { + x = 6; + } else { + x = 2; + } +} else { + x = 5; + assert(x == 5); +} +assert(x == 2); +``` + +## Loops + +Noir has one kind of loop: the `for` loop. `for` loops allow you to repeat a block of code multiple +times. + +The following block of code between the braces is run 10 times. + +```rust +for i in 0..10 { + // do something +} +``` + +The index for loops is of type `u64`. + +### Break and Continue + +In unconstrained code, `break` and `continue` are also allowed in `for` loops. These are only allowed +in unconstrained code since normal constrained code requires that Noir knows exactly how many iterations +a loop may have. `break` and `continue` can be used like so: + +```rust +for i in 0 .. 10 { + println("Iteration start") + + if i == 2 { + continue; + } + + if i == 5 { + break; + } + + println(i); +} +println("Loop end") +``` + +When used, `break` will end the current loop early and jump to the statement after the for loop. In the example +above, the `break` will stop the loop and jump to the `println("Loop end")`. + +`continue` will stop the current iteration of the loop, and jump to the start of the next iteration. In the example +above, `continue` will jump to `println("Iteration start")` when used. Note that the loop continues as normal after this. +The iteration variable `i` is still increased by one as normal when `continue` is used. + +`break` and `continue` cannot currently be used to jump out of more than a single loop at a time. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_bus.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_bus.mdx new file mode 100644 index 00000000000..e55e58622ce --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_bus.mdx @@ -0,0 +1,23 @@ +--- +title: Data Bus +sidebar_position: 13 +--- +import Experimental from '@site/src/components/Notes/_experimental.mdx'; + + + +The data bus is an optimization that the backend can use to make recursion more efficient. +In order to use it, you must define some inputs of the program entry points (usually the `main()` +function) with the `call_data` modifier, and the return values with the `return_data` modifier. +These modifiers are incompatible with `pub` and `mut` modifiers. + +## Example + +```rust +fn main(mut x: u32, y: call_data u32, z: call_data [u32;4] ) -> return_data u32 { + let a = z[x]; + a+y +} +``` + +As a result, both call_data and return_data will be treated as private inputs and encapsulated into a read-only array each, for the backend to process. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/_category_.json b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/_category_.json new file mode 100644 index 00000000000..5d694210bbf --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/_category_.json @@ -0,0 +1,5 @@ +{ + "position": 0, + "collapsible": true, + "collapsed": true +} diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/arrays.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/arrays.md new file mode 100644 index 00000000000..bb68e60fe53 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/arrays.md @@ -0,0 +1,274 @@ +--- +title: Arrays +description: + Dive into the Array data type in Noir. Grasp its methods, practical examples, and best practices for efficiently using Arrays in your Noir code. +keywords: + [ + noir, + array type, + methods, + examples, + indexing, + ] +sidebar_position: 4 +--- + +An array is one way of grouping together values into one compound type. Array types can be inferred +or explicitly specified via the syntax `[; ]`: + +```rust +fn main(x : Field, y : Field) { + let my_arr = [x, y]; + let your_arr: [Field; 2] = [x, y]; +} +``` + +Here, both `my_arr` and `your_arr` are instantiated as an array containing two `Field` elements. + +Array elements can be accessed using indexing: + +```rust +fn main() { + let a = [1, 2, 3, 4, 5]; + + let first = a[0]; + let second = a[1]; +} +``` + +All elements in an array must be of the same type (i.e. homogeneous). That is, an array cannot group +a `Field` value and a `u8` value together for example. + +You can write mutable arrays, like: + +```rust +fn main() { + let mut arr = [1, 2, 3, 4, 5]; + assert(arr[0] == 1); + + arr[0] = 42; + assert(arr[0] == 42); +} +``` + +You can instantiate a new array of a fixed size with the same value repeated for each element. The following example instantiates an array of length 32 where each element is of type Field and has the value 0. + +```rust +let array: [Field; 32] = [0; 32]; +``` + +Like in Rust, arrays in Noir are a fixed size. However, if you wish to convert an array to a [slice](./slices.mdx), you can just call `as_slice` on your array: + +```rust +let array: [Field; 32] = [0; 32]; +let sl = array.as_slice() +``` + +You can define multidimensional arrays: + +```rust +let array : [[Field; 2]; 2]; +let element = array[0][0]; +``` + +However, multidimensional slices are not supported. For example, the following code will error at compile time: + +```rust +let slice : [[Field]] = &[]; +``` + +## Types + +You can create arrays of primitive types or structs. There is not yet support for nested arrays +(arrays of arrays) or arrays of structs that contain arrays. + +## Methods + +For convenience, the STD provides some ready-to-use, common methods for arrays. +Each of these functions are located within the generic impl `impl [T; N] {`. +So anywhere `self` appears, it refers to the variable `self: [T; N]`. + +### len + +Returns the length of an array + +```rust +fn len(self) -> Field +``` + +example + +```rust +fn main() { + let array = [42, 42]; + assert(array.len() == 2); +} +``` + +### sort + +Returns a new sorted array. The original array remains untouched. Notice that this function will +only work for arrays of fields or integers, not for any arbitrary type. This is because the sorting +logic it uses internally is optimized specifically for these values. If you need a sort function to +sort any type, you should use the function `sort_via` described below. + +```rust +fn sort(self) -> [T; N] +``` + +example + +```rust +fn main() { + let arr = [42, 32]; + let sorted = arr.sort(); + assert(sorted == [32, 42]); +} +``` + +### sort_via + +Sorts the array with a custom comparison function. The ordering function must return true if the first argument should be sorted to be before the second argument or is equal to the second argument. + +Using this method with an operator like `<` that does not return `true` for equal values will result in an assertion failure for arrays with equal elements. + +```rust +fn sort_via(self, ordering: fn(T, T) -> bool) -> [T; N] +``` + +example + +```rust +fn main() { + let arr = [42, 32] + let sorted_ascending = arr.sort_via(|a, b| a <= b); + assert(sorted_ascending == [32, 42]); // verifies + + let sorted_descending = arr.sort_via(|a, b| a >= b); + assert(sorted_descending == [32, 42]); // does not verify +} +``` + +### map + +Applies a function to each element of the array, returning a new array containing the mapped elements. + +```rust +fn map(self, f: fn(T) -> U) -> [U; N] +``` + +example + +```rust +let a = [1, 2, 3]; +let b = a.map(|a| a * 2); // b is now [2, 4, 6] +``` + +### fold + +Applies a function to each element of the array, returning the final accumulated value. The first +parameter is the initial value. + +```rust +fn fold(self, mut accumulator: U, f: fn(U, T) -> U) -> U +``` + +This is a left fold, so the given function will be applied to the accumulator and first element of +the array, then the second, and so on. For a given call the expected result would be equivalent to: + +```rust +let a1 = [1]; +let a2 = [1, 2]; +let a3 = [1, 2, 3]; + +let f = |a, b| a - b; +a1.fold(10, f) //=> f(10, 1) +a2.fold(10, f) //=> f(f(10, 1), 2) +a3.fold(10, f) //=> f(f(f(10, 1), 2), 3) +``` + +example: + +```rust + +fn main() { + let arr = [2, 2, 2, 2, 2]; + let folded = arr.fold(0, |a, b| a + b); + assert(folded == 10); +} + +``` + +### reduce + +Same as fold, but uses the first element as the starting element. + +```rust +fn reduce(self, f: fn(T, T) -> T) -> T +``` + +example: + +```rust +fn main() { + let arr = [2, 2, 2, 2, 2]; + let reduced = arr.reduce(|a, b| a + b); + assert(reduced == 10); +} +``` + +### all + +Returns true if all the elements satisfy the given predicate + +```rust +fn all(self, predicate: fn(T) -> bool) -> bool +``` + +example: + +```rust +fn main() { + let arr = [2, 2, 2, 2, 2]; + let all = arr.all(|a| a == 2); + assert(all); +} +``` + +### any + +Returns true if any of the elements satisfy the given predicate + +```rust +fn any(self, predicate: fn(T) -> bool) -> bool +``` + +example: + +```rust +fn main() { + let arr = [2, 2, 2, 2, 5]; + let any = arr.any(|a| a == 5); + assert(any); +} +``` + +### as_str_unchecked + +Converts a byte array of type `[u8; N]` to a string. Note that this performs no UTF-8 validation - +the given array is interpreted as-is as a string. + +```rust +impl [u8; N] { + pub fn as_str_unchecked(self) -> str +} +``` + +example: + +```rust +fn main() { + let hi = [104, 105].as_str_unchecked(); + assert_eq(hi, "hi"); +} +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/booleans.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/booleans.md new file mode 100644 index 00000000000..2507af710e7 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/booleans.md @@ -0,0 +1,28 @@ +--- +title: Booleans +description: + Delve into the Boolean data type in Noir. Understand its methods, practical examples, and best practices for using Booleans in your Noir programs. +keywords: + [ + noir, + boolean type, + methods, + examples, + logical operations, + ] +sidebar_position: 2 +--- + + +The `bool` type in Noir has two possible values: `true` and `false`: + +```rust +fn main() { + let t = true; + let f: bool = false; +} +``` + +The boolean type is most commonly used in conditionals like `if` expressions and `assert` +statements. More about conditionals is covered in the [Control Flow](../control_flow.md) and +[Assert Function](../assert.md) sections. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/fields.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/fields.md new file mode 100644 index 00000000000..814b996facb --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/fields.md @@ -0,0 +1,240 @@ +--- +title: Fields +description: + Dive deep into the Field data type in Noir. Understand its methods, practical examples, and best practices to effectively use Fields in your Noir programs. +keywords: + [ + noir, + field type, + methods, + examples, + best practices, + ] +sidebar_position: 0 +--- + +The field type corresponds to the native field type of the proving backend. + +The size of a Noir field depends on the elliptic curve's finite field for the proving backend +adopted. For example, a field would be a 254-bit integer when paired with the default backend that +spans the Grumpkin curve. + +Fields support integer arithmetic and are often used as the default numeric type in Noir: + +```rust +fn main(x : Field, y : Field) { + let z = x + y; +} +``` + +`x`, `y` and `z` are all private fields in this example. Using the `let` keyword we defined a new +private value `z` constrained to be equal to `x + y`. + +If proving efficiency is of priority, fields should be used as a default for solving problems. +Smaller integer types (e.g. `u64`) incur extra range constraints. + +## Methods + +After declaring a Field, you can use these common methods on it: + +### to_le_bits + +Transforms the field into an array of bits, Little Endian. + +```rust title="to_le_bits" showLineNumbers +pub fn to_le_bits(self: Self) -> [u1; N] {} +``` +> Source code: noir_stdlib/src/field/mod.nr#L33-L35 + + +example: + +```rust title="to_le_bits_example" showLineNumbers +fn test_to_le_bits() { + let field = 2; + let bits: [u1; 8] = field.to_le_bits(); + assert_eq(bits, [0, 1, 0, 0, 0, 0, 0, 0]); + } +``` +> Source code: noir_stdlib/src/field/mod.nr#L255-L261 + + + +### to_be_bits + +Transforms the field into an array of bits, Big Endian. + +```rust title="to_be_bits" showLineNumbers +pub fn to_be_bits(self: Self) -> [u1; N] {} +``` +> Source code: noir_stdlib/src/field/mod.nr#L49-L51 + + +example: + +```rust title="to_be_bits_example" showLineNumbers +fn test_to_be_bits() { + let field = 2; + let bits: [u1; 8] = field.to_be_bits(); + assert_eq(bits, [0, 0, 0, 0, 0, 0, 1, 0]); + } +``` +> Source code: noir_stdlib/src/field/mod.nr#L246-L252 + + + +### to_le_bytes + +Transforms into an array of bytes, Little Endian + +```rust title="to_le_bytes" showLineNumbers +pub fn to_le_bytes(self: Self) -> [u8; N] { +``` +> Source code: noir_stdlib/src/field/mod.nr#L62-L64 + + +example: + +```rust title="to_le_bytes_example" showLineNumbers +fn test_to_le_bytes() { + let field = 2; + let bits: [u8; 8] = field.to_le_bytes(); + assert_eq(bits, [2, 0, 0, 0, 0, 0, 0, 0]); + assert_eq(Field::from_le_bytes::<8>(bits), field); + } +``` +> Source code: noir_stdlib/src/field/mod.nr#L274-L281 + + +### to_be_bytes + +Transforms into an array of bytes, Big Endian + +```rust title="to_be_bytes" showLineNumbers +pub fn to_be_bytes(self: Self) -> [u8; N] { +``` +> Source code: noir_stdlib/src/field/mod.nr#L95-L97 + + +example: + +```rust title="to_be_bytes_example" showLineNumbers +fn test_to_be_bytes() { + let field = 2; + let bits: [u8; 8] = field.to_be_bytes(); + assert_eq(bits, [0, 0, 0, 0, 0, 0, 0, 2]); + assert_eq(Field::from_be_bytes::<8>(bits), field); + } +``` +> Source code: noir_stdlib/src/field/mod.nr#L264-L271 + + + +### to_le_radix + +Decomposes into an array over the specified base, Little Endian + +```rust title="to_le_radix" showLineNumbers +pub fn to_le_radix(self: Self, radix: u32) -> [u8; N] { + crate::assert_constant(radix); + self.__to_le_radix(radix) + } +``` +> Source code: noir_stdlib/src/field/mod.nr#L119-L124 + + + +example: + +```rust title="to_le_radix_example" showLineNumbers +fn test_to_le_radix() { + let field = 2; + let bits: [u8; 8] = field.to_le_radix(256); + assert_eq(bits, [2, 0, 0, 0, 0, 0, 0, 0]); + assert_eq(Field::from_le_bytes::<8>(bits), field); + } +``` +> Source code: noir_stdlib/src/field/mod.nr#L294-L301 + + + +### to_be_radix + +Decomposes into an array over the specified base, Big Endian + +```rust title="to_be_radix" showLineNumbers +pub fn to_be_radix(self: Self, radix: u32) -> [u8; N] { + crate::assert_constant(radix); + self.__to_be_radix(radix) + } +``` +> Source code: noir_stdlib/src/field/mod.nr#L126-L131 + + +example: + +```rust title="to_be_radix_example" showLineNumbers +fn test_to_be_radix() { + let field = 2; + let bits: [u8; 8] = field.to_be_radix(256); + assert_eq(bits, [0, 0, 0, 0, 0, 0, 0, 2]); + assert_eq(Field::from_be_bytes::<8>(bits), field); + } +``` +> Source code: noir_stdlib/src/field/mod.nr#L284-L291 + + + +### pow_32 + +Returns the value to the power of the specified exponent + +```rust +fn pow_32(self, exponent: Field) -> Field +``` + +example: + +```rust +fn main() { + let field = 2 + let pow = field.pow_32(4); + assert(pow == 16); +} +``` + +### assert_max_bit_size + +Adds a constraint to specify that the field can be represented with `bit_size` number of bits + +```rust title="assert_max_bit_size" showLineNumbers +pub fn assert_max_bit_size(self, bit_size: u32) { +``` +> Source code: noir_stdlib/src/field/mod.nr#L10-L12 + + +example: + +```rust +fn main() { + let field = 2 + field.assert_max_bit_size(32); +} +``` + +### sgn0 + +Parity of (prime) Field element, i.e. sgn0(x mod p) = 0 if x ∈ \{0, ..., p-1\} is even, otherwise sgn0(x mod p) = 1. + +```rust +fn sgn0(self) -> u1 +``` + + +### lt + +Returns true if the field is less than the other field + +```rust +pub fn lt(self, another: Field) -> bool +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/function_types.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/function_types.md new file mode 100644 index 00000000000..f6121af17e2 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/function_types.md @@ -0,0 +1,26 @@ +--- +title: Function types +sidebar_position: 10 +--- + +Noir supports higher-order functions. The syntax for a function type is as follows: + +```rust +fn(arg1_type, arg2_type, ...) -> return_type +``` + +Example: + +```rust +fn assert_returns_100(f: fn() -> Field) { // f takes no args and returns a Field + assert(f() == 100); +} + +fn main() { + assert_returns_100(|| 100); // ok + assert_returns_100(|| 150); // fails +} +``` + +A function type also has an optional capture environment - this is necessary to support closures. +See [Lambdas](../lambdas.md) for more details. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/index.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/index.md new file mode 100644 index 00000000000..11f51e2b65a --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/index.md @@ -0,0 +1,126 @@ +--- +title: Data Types +description: + Get a clear understanding of the two categories of Noir data types - primitive types and compound + types. Learn about their characteristics, differences, and how to use them in your Noir + programming. +keywords: + [ + noir, + data types, + primitive types, + compound types, + private types, + public types, + ] +--- + +Every value in Noir has a type, which determines which operations are valid for it. + +All values in Noir are fundamentally composed of `Field` elements. For a more approachable +developing experience, abstractions are added on top to introduce different data types in Noir. + +Noir has two category of data types: primitive types (e.g. `Field`, integers, `bool`) and compound +types that group primitive types (e.g. arrays, tuples, structs). Each value can either be private or +public. + +## Private & Public Types + +A **private value** is known only to the Prover, while a **public value** is known by both the +Prover and Verifier. Mark values as `private` when the value should only be known to the prover. All +primitive types (including individual fields of compound types) in Noir are private by default, and +can be marked public when certain values are intended to be revealed to the Verifier. + +> **Note:** For public values defined in Noir programs paired with smart contract verifiers, once +> the proofs are verified on-chain the values can be considered known to everyone that has access to +> that blockchain. + +Public data types are treated no differently to private types apart from the fact that their values +will be revealed in proofs generated. Simply changing the value of a public type will not change the +circuit (where the same goes for changing values of private types as well). + +_Private values_ are also referred to as _witnesses_ sometimes. + +> **Note:** The terms private and public when applied to a type (e.g. `pub Field`) have a different +> meaning than when applied to a function (e.g. `pub fn foo() {}`). +> +> The former is a visibility modifier for the Prover to interpret if a value should be made known to +> the Verifier, while the latter is a visibility modifier for the compiler to interpret if a +> function should be made accessible to external Noir programs like in other languages. + +### pub Modifier + +All data types in Noir are private by default. Types are explicitly declared as public using the +`pub` modifier: + +```rust +fn main(x : Field, y : pub Field) -> pub Field { + x + y +} +``` + +In this example, `x` is **private** while `y` and `x + y` (the return value) are **public**. Note +that visibility is handled **per variable**, so it is perfectly valid to have one input that is +private and another that is public. + +> **Note:** Public types can only be declared through parameters on `main`. + +## Type Aliases + +A type alias is a new name for an existing type. Type aliases are declared with the keyword `type`: + +```rust +type Id = u8; + +fn main() { + let id: Id = 1; + let zero: u8 = 0; + assert(zero + 1 == id); +} +``` + +Type aliases can also be used with [generics](../generics.md): + +```rust +type Id = Size; + +fn main() { + let id: Id = 1; + let zero: u32 = 0; + assert(zero + 1 == id); +} +``` + +Type aliases can even refer to other aliases. An error will be issued if they form a cycle: + +```rust +// Ok! +type A = B; +type B = Field; + +type Bad1 = Bad2; + +// error: Dependency cycle found +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` +to make the type alias public or `pub(crate)` to make it public to just its crate: + +```rust +// This type alias is now public +pub type Id = u8; +``` + +## Wildcard Type +Noir can usually infer the type of the variable from the context, so specifying the type of a variable is only required when it cannot be inferred. However, specifying a complex type can be tedious, especially when it has multiple generic arguments. Often some of the generic types can be inferred from the context, and Noir only needs a hint to properly infer the other types. We can partially specify a variable's type by using `_` as a marker, indicating where we still want the compiler to infer the type. + +```rust +let a: [_; 4] = foo(b); +``` + + +### BigInt + +You can achieve BigInt functionality using the [Noir BigInt](https://github.com/shuklaayush/noir-bigint) library. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/integers.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/integers.md new file mode 100644 index 00000000000..a1d59bf3166 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/integers.md @@ -0,0 +1,156 @@ +--- +title: Integers +description: Explore the Integer data type in Noir. Learn about its methods, see real-world examples, and grasp how to efficiently use Integers in your Noir code. +keywords: [noir, integer types, methods, examples, arithmetic] +sidebar_position: 1 +--- + +An integer type is a range constrained field type. +The Noir frontend supports both unsigned and signed integer types. +The allowed sizes are 1, 8, 16, 32 and 64 bits. + +:::info + +When an integer is defined in Noir without a specific type, it will default to `Field`. + +The one exception is for loop indices which default to `u64` since comparisons on `Field`s are not possible. + +::: + +## Unsigned Integers + +An unsigned integer type is specified first with the letter `u` (indicating its unsigned nature) followed by its bit size (e.g. `8`): + +```rust +fn main() { + let x: u8 = 1; + let y: u8 = 1; + let z = x + y; + assert (z == 2); +} +``` + +The bit size determines the maximum value the integer type can store. For example, a `u8` variable can store a value in the range of 0 to 255 (i.e. $\\2^{8}-1\\$). + +## Signed Integers + +A signed integer type is specified first with the letter `i` (which stands for integer) followed by its bit size (e.g. `8`): + +```rust +fn main() { + let x: i8 = -1; + let y: i8 = -1; + let z = x + y; + assert (z == -2); +} +``` + +The bit size determines the maximum and minimum range of value the integer type can store. For example, an `i8` variable can store a value in the range of -128 to 127 (i.e. $\\-2^{7}\\$ to $\\2^{7}-1\\$). + +## 128 bits Unsigned Integers + +The built-in structure `U128` allows you to use 128-bit unsigned integers almost like a native integer type. However, there are some differences to keep in mind: +- You cannot cast between a native integer and `U128` +- There is a higher performance cost when using `U128`, compared to a native type. + +Conversion between unsigned integer types and U128 are done through the use of `from_integer` and `to_integer` functions. `from_integer` also accepts the `Field` type as input. + +```rust +fn main() { + let x = U128::from_integer(23); + let y = U128::from_hex("0x7"); + let z = x + y; + assert(z.to_integer() == 30); +} +``` + +`U128` is implemented with two 64 bits limbs, representing the low and high bits, which explains the performance cost. You should expect `U128` to be twice more costly for addition and four times more costly for multiplication. +You can construct a U128 from its limbs: +```rust +fn main(x: u64, y: u64) { + let x = U128::from_u64s_be(x,y); + assert(z.hi == x as Field); + assert(z.lo == y as Field); +} +``` + +Note that the limbs are stored as Field elements in order to avoid unnecessary conversions. +Apart from this, most operations will work as usual: + +```rust +fn main(x: U128, y: U128) { + // multiplication + let c = x * y; + // addition and subtraction + let c = c - x + y; + // division + let c = x / y; + // bit operation; + let c = x & y | y; + // bit shift + let c = x << y; + // comparisons; + let c = x < y; + let c = x == y; +} +``` + +## Overflows + +Computations that exceed the type boundaries will result in overflow errors. This happens with both signed and unsigned integers. For example, attempting to prove: + +```rust +fn main(x: u8, y: u8) { + let z = x + y; +} +``` + +With: + +```toml +x = "255" +y = "1" +``` + +Would result in: + +``` +$ nargo execute +error: Assertion failed: 'attempt to add with overflow' +┌─ ~/src/main.nr:9:13 +│ +│ let z = x + y; +│ ----- +│ += Call stack: + ... +``` + +A similar error would happen with signed integers: + +```rust +fn main() { + let x: i8 = -118; + let y: i8 = -11; + let z = x + y; +} +``` + +### Wrapping methods + +Although integer overflow is expected to error, some use-cases rely on wrapping. For these use-cases, the standard library provides `wrapping` variants of certain common operations: + +```rust +fn wrapping_add(x: T, y: T) -> T; +fn wrapping_sub(x: T, y: T) -> T; +fn wrapping_mul(x: T, y: T) -> T; +``` + +Example of how it is used: + +```rust + +fn main(x: u8, y: u8) -> pub u8 { + std::wrapping_add(x, y) +} +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/references.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/references.md new file mode 100644 index 00000000000..a5293d11cfb --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/references.md @@ -0,0 +1,23 @@ +--- +title: References +sidebar_position: 9 +--- + +Noir supports first-class references. References are a bit like pointers: they point to a specific address that can be followed to access the data stored at that address. You can use Rust-like syntax to use pointers in Noir: the `&` operator references the variable, the `*` operator dereferences it. + +Example: + +```rust +fn main() { + let mut x = 2; + + // you can reference x as &mut and pass it to multiplyBy2 + multiplyBy2(&mut x); +} + +// you can access &mut here +fn multiplyBy2(x: &mut Field) { + // and dereference it with * + *x = *x * 2; +} +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/slices.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/slices.mdx new file mode 100644 index 00000000000..cfee564a302 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/slices.mdx @@ -0,0 +1,358 @@ +--- +title: Slices +description: Explore the Slice data type in Noir. Understand its methods, see real-world examples, and learn how to effectively use Slices in your Noir programs. +keywords: [noir, slice type, methods, examples, subarrays] +sidebar_position: 5 +--- + +import Experimental from '@site/src/components/Notes/_experimental.mdx'; + + + +A slice is a dynamically-sized view into a sequence of elements. They can be resized at runtime, but because they don't own the data, they cannot be returned from a circuit. You can treat slices as arrays without a constrained size. + +```rust +fn main() -> pub u32 { + let mut slice: [Field] = &[0; 2]; + + let mut new_slice = slice.push_back(6); + new_slice.len() +} +``` + +To write a slice literal, use a preceding ampersand as in: `&[0; 2]` or +`&[1, 2, 3]`. + +It is important to note that slices are not references to arrays. In Noir, +`&[..]` is more similar to an immutable, growable vector. + +View the corresponding test file [here][test-file]. + +[test-file]: https://github.com/noir-lang/noir/blob/f387ec1475129732f72ba294877efdf6857135ac/crates/nargo_cli/tests/test_data_ssa_refactor/slices/src/main.nr + +## Methods + +For convenience, the STD provides some ready-to-use, common methods for slices: + +### push_back + +Pushes a new element to the end of the slice, returning a new slice with a length one greater than the original unmodified slice. + +```rust +fn push_back(_self: [T], _elem: T) -> [T] +``` + +example: + +```rust +fn main() -> pub Field { + let mut slice: [Field] = &[0; 2]; + + let mut new_slice = slice.push_back(6); + new_slice.len() +} +``` + +View the corresponding test file [here][test-file]. + +### push_front + +Returns a new array with the specified element inserted at index 0. The existing elements indexes are incremented by 1. + +```rust +fn push_front(_self: Self, _elem: T) -> Self +``` + +Example: + +```rust +let mut new_slice: [Field] = &[]; +new_slice = new_slice.push_front(20); +assert(new_slice[0] == 20); // returns true +``` + +View the corresponding test file [here][test-file]. + +### pop_front + +Returns a tuple of two items, the first element of the array and the rest of the array. + +```rust +fn pop_front(_self: Self) -> (T, Self) +``` + +Example: + +```rust +let (first_elem, rest_of_slice) = slice.pop_front(); +``` + +View the corresponding test file [here][test-file]. + +### pop_back + +Returns a tuple of two items, the beginning of the array with the last element omitted and the last element. + +```rust +fn pop_back(_self: Self) -> (Self, T) +``` + +Example: + +```rust +let (popped_slice, last_elem) = slice.pop_back(); +``` + +View the corresponding test file [here][test-file]. + +### append + +Loops over a slice and adds it to the end of another. + +```rust +fn append(mut self, other: Self) -> Self +``` + +Example: + +```rust +let append = &[1, 2].append(&[3, 4, 5]); +``` + +### insert + +Inserts an element at a specified index and shifts all following elements by 1. + +```rust +fn insert(_self: Self, _index: Field, _elem: T) -> Self +``` + +Example: + +```rust +new_slice = rest_of_slice.insert(2, 100); +assert(new_slice[2] == 100); +``` + +View the corresponding test file [here][test-file]. + +### remove + +Remove an element at a specified index, shifting all elements after it to the left, returning the altered slice and the removed element. + +```rust +fn remove(_self: Self, _index: Field) -> (Self, T) +``` + +Example: + +```rust +let (remove_slice, removed_elem) = slice.remove(3); +``` + +### len + +Returns the length of a slice + +```rust +fn len(self) -> Field +``` + +Example: + +```rust +fn main() { + let slice = &[42, 42]; + assert(slice.len() == 2); +} +``` + +### as_array + +Converts this slice into an array. + +Make sure to specify the size of the resulting array. +Panics if the resulting array length is different than the slice's length. + +```rust +fn as_array(self) -> [T; N] +``` + +Example: + +```rust +fn main() { + let slice = &[5, 6]; + + // Always specify the length of the resulting array! + let array: [Field; 2] = slice.as_array(); + + assert(array[0] == slice[0]); + assert(array[1] == slice[1]); +} +``` + +### map + +Applies a function to each element of the slice, returning a new slice containing the mapped elements. + +```rust +fn map(self, f: fn[Env](T) -> U) -> [U] +``` + +example + +```rust +let a = &[1, 2, 3]; +let b = a.map(|a| a * 2); // b is now &[2, 4, 6] +``` + +### fold + +Applies a function to each element of the slice, returning the final accumulated value. The first +parameter is the initial value. + +```rust +fn fold(self, mut accumulator: U, f: fn[Env](U, T) -> U) -> U +``` + +This is a left fold, so the given function will be applied to the accumulator and first element of +the slice, then the second, and so on. For a given call the expected result would be equivalent to: + +```rust +let a1 = &[1]; +let a2 = &[1, 2]; +let a3 = &[1, 2, 3]; + +let f = |a, b| a - b; +a1.fold(10, f) //=> f(10, 1) +a2.fold(10, f) //=> f(f(10, 1), 2) +a3.fold(10, f) //=> f(f(f(10, 1), 2), 3) +``` + +example: + +```rust + +fn main() { + let slice = &[2, 2, 2, 2, 2]; + let folded = slice.fold(0, |a, b| a + b); + assert(folded == 10); +} + +``` + +### reduce + +Same as fold, but uses the first element as the starting element. + +```rust +fn reduce(self, f: fn[Env](T, T) -> T) -> T +``` + +example: + +```rust +fn main() { + let slice = &[2, 2, 2, 2, 2]; + let reduced = slice.reduce(|a, b| a + b); + assert(reduced == 10); +} +``` + +### filter + +Returns a new slice containing only elements for which the given predicate returns true. + +```rust +fn filter(self, f: fn[Env](T) -> bool) -> Self +``` + +example: + +```rust +fn main() { + let slice = &[1, 2, 3, 4, 5]; + let odds = slice.filter(|x| x % 2 == 1); + assert_eq(odds, &[1, 3, 5]); +} +``` + +### join + +Flatten each element in the slice into one value, separated by `separator`. + +Note that although slices implement `Append`, `join` cannot be used on slice +elements since nested slices are prohibited. + +```rust +fn join(self, separator: T) -> T where T: Append +``` + +example: + +```rust +struct Accumulator { + total: Field, +} + +// "Append" two accumulators by adding them +impl Append for Accumulator { + fn empty() -> Self { + Self { total: 0 } + } + + fn append(self, other: Self) -> Self { + Self { total: self.total + other.total } + } +} + +fn main() { + let slice = &[1, 2, 3, 4, 5].map(|total| Accumulator { total }); + + let result = slice.join(Accumulator::empty()); + assert_eq(result, Accumulator { total: 15 }); + + // We can use a non-empty separator to insert additional elements to sum: + let separator = Accumulator { total: 10 }; + let result = slice.join(separator); + assert_eq(result, Accumulator { total: 55 }); +} +``` + +### all + +Returns true if all the elements satisfy the given predicate + +```rust +fn all(self, predicate: fn[Env](T) -> bool) -> bool +``` + +example: + +```rust +fn main() { + let slice = &[2, 2, 2, 2, 2]; + let all = slice.all(|a| a == 2); + assert(all); +} +``` + +### any + +Returns true if any of the elements satisfy the given predicate + +```rust +fn any(self, predicate: fn[Env](T) -> bool) -> bool +``` + +example: + +```rust +fn main() { + let slice = &[2, 2, 2, 2, 5]; + let any = slice.any(|a| a == 5); + assert(any); +} + +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/strings.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/strings.md new file mode 100644 index 00000000000..1fdee42425e --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/strings.md @@ -0,0 +1,79 @@ +--- +title: Strings +description: + Discover the String data type in Noir. Learn about its methods, see real-world examples, and understand how to effectively manipulate and use Strings in Noir. +keywords: + [ + noir, + string type, + methods, + examples, + concatenation, + ] +sidebar_position: 3 +--- + + +The string type is a fixed length value defined with `str`. + +You can use strings in `assert()` functions or print them with +`println()`. See more about [Logging](../../standard_library/logging.md). + +```rust + +fn main(message : pub str<11>, hex_as_string : str<4>) { + println(message); + assert(message == "hello world"); + assert(hex_as_string == "0x41"); +} +``` + +You can convert a `str` to a byte array by calling `as_bytes()` +or a vector by calling `as_bytes_vec()`. + +```rust +fn main() { + let message = "hello world"; + let message_bytes = message.as_bytes(); + let mut message_vec = message.as_bytes_vec(); + assert(message_bytes.len() == 11); + assert(message_bytes[0] == 104); + assert(message_bytes[0] == message_vec.get(0)); +} +``` + +## Escape characters + +You can use escape characters for your strings: + +| Escape Sequence | Description | +|-----------------|-----------------| +| `\r` | Carriage Return | +| `\n` | Newline | +| `\t` | Tab | +| `\0` | Null Character | +| `\"` | Double Quote | +| `\\` | Backslash | + +Example: + +```rust +let s = "Hello \"world" // prints "Hello "world" +let s = "hey \tyou"; // prints "hey you" +``` + +## Raw strings + +A raw string begins with the letter `r` and is optionally delimited by a number of hashes `#`. + +Escape characters are *not* processed within raw strings. All contents are interpreted literally. + +Example: + +```rust +let s = r"Hello world"; +let s = r#"Simon says "hello world""#; + +// Any number of hashes may be used (>= 1) as long as the string also terminates with the same number of hashes +let s = r#####"One "#, Two "##, Three "###, Four "####, Five will end the string."#####; +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/structs.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/structs.md new file mode 100644 index 00000000000..e529347f27d --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/structs.md @@ -0,0 +1,82 @@ +--- +title: Structs +description: + Explore the Struct data type in Noir. Learn about its methods, see real-world examples, and grasp how to effectively define and use Structs in your Noir programs. +keywords: + [ + noir, + struct type, + methods, + examples, + data structures, + ] +sidebar_position: 8 +--- + +A struct also allows for grouping multiple values of different types. Unlike tuples, we can also +name each field. + +> **Note:** The usage of _field_ here refers to each element of the struct and is unrelated to the +> field type of Noir. + +Defining a struct requires giving it a name and listing each field within as `: ` pairs: + +```rust +struct Animal { + hands: Field, + legs: Field, + eyes: u8, +} +``` + +An instance of a struct can then be created with actual values in `: ` pairs in any +order. Struct fields are accessible using their given names: + +```rust +fn main() { + let legs = 4; + + let dog = Animal { + eyes: 2, + hands: 0, + legs, + }; + + let zero = dog.hands; +} +``` + +Structs can also be destructured in a pattern, binding each field to a new variable: + +```rust +fn main() { + let Animal { hands, legs: feet, eyes } = get_octopus(); + + let ten = hands + feet + eyes as u8; +} + +fn get_octopus() -> Animal { + let octopus = Animal { + hands: 0, + legs: 8, + eyes: 2, + }; + + octopus +} +``` + +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` +to make the struct public or `pub(crate)` to make it public to just its crate: + +```rust +// This struct is now public +pub struct Animal { + hands: Field, + legs: Field, + eyes: u8, +} +``` \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/tuples.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/tuples.md new file mode 100644 index 00000000000..2ec5c9c4113 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/data_types/tuples.md @@ -0,0 +1,48 @@ +--- +title: Tuples +description: + Dive into the Tuple data type in Noir. Understand its methods, practical examples, and best practices for efficiently using Tuples in your Noir code. +keywords: + [ + noir, + tuple type, + methods, + examples, + multi-value containers, + ] +sidebar_position: 7 +--- + +A tuple collects multiple values like an array, but with the added ability to collect values of +different types: + +```rust +fn main() { + let tup: (u8, u64, Field) = (255, 500, 1000); +} +``` + +One way to access tuple elements is via destructuring using pattern matching: + +```rust +fn main() { + let tup = (1, 2); + + let (one, two) = tup; + + let three = one + two; +} +``` + +Another way to access tuple elements is via direct member access, using a period (`.`) followed by +the index of the element we want to access. Index `0` corresponds to the first tuple element, `1` to +the second and so on: + +```rust +fn main() { + let tup = (5, 6, 7, 8); + + let five = tup.0; + let eight = tup.3; +} +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/functions.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/functions.md new file mode 100644 index 00000000000..f656cdfd97a --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/functions.md @@ -0,0 +1,226 @@ +--- +title: Functions +description: + Learn how to declare functions and methods in Noir, a programming language with Rust semantics. + This guide covers parameter declaration, return types, call expressions, and more. +keywords: [Noir, Rust, functions, methods, parameter declaration, return types, call expressions] +sidebar_position: 1 +--- + +Functions in Noir follow the same semantics of Rust, though Noir does not support early returns. + +To declare a function the `fn` keyword is used. + +```rust +fn foo() {} +``` + +By default, functions are visible only within the package they are defined. To make them visible outside of that package (for example, as part of a [library](../modules_packages_crates/crates_and_packages.md#libraries)), you should mark them as `pub`: + +```rust +pub fn foo() {} +``` + +You can also restrict the visibility of the function to only the crate it was defined in, by specifying `pub(crate)`: + +```rust +pub(crate) fn foo() {} //foo can only be called within its crate +``` + +All parameters in a function must have a type and all types are known at compile time. The parameter +is pre-pended with a colon and the parameter type. Multiple parameters are separated using a comma. + +```rust +fn foo(x : Field, y : Field){} +``` + +The return type of a function can be stated by using the `->` arrow notation. The function below +states that the foo function must return a `Field`. If the function returns no value, then the arrow +is omitted. + +```rust +fn foo(x : Field, y : Field) -> Field { + x + y +} +``` + +Note that a `return` keyword is unneeded in this case - the last expression in a function's body is +returned. + +## Main function + +If you're writing a binary, the `main` function is the starting point of your program. You can pass all types of expressions to it, as long as they have a fixed size at compile time: + +```rust +fn main(x : Field) // this is fine: passing a Field +fn main(x : [Field; 2]) // this is also fine: passing a Field with known size at compile-time +fn main(x : (Field, bool)) // 👌: passing a (Field, bool) tuple means size 2 +fn main(x : str<5>) // this is fine, as long as you pass a string of size 5 + +fn main(x : Vec) // can't compile, has variable size +fn main(x : [Field]) // can't compile, has variable size +fn main(....// i think you got it by now +``` + +Keep in mind [tests](../../tooling/testing.md) don't differentiate between `main` and any other function. The following snippet passes tests, but won't compile or prove: + +```rust +fn main(x : [Field]) { + assert(x[0] == 1); +} + +#[test] +fn test_one() { + main(&[1, 2]); +} +``` + +```bash +$ nargo test +[testing] Running 1 test functions +[testing] Testing test_one... ok +[testing] All tests passed + +$ nargo check +The application panicked (crashed). +Message: Cannot have variable sized arrays as a parameter to main +``` + +## Call Expressions + +Calling a function in Noir is executed by using the function name and passing in the necessary +arguments. + +Below we show how to call the `foo` function from the `main` function using a call expression: + +```rust +fn main(x : Field, y : Field) { + let z = foo(x); +} + +fn foo(x : Field) -> Field { + x + x +} +``` + +## Methods + +You can define methods in Noir on any struct type in scope. + +```rust +struct MyStruct { + foo: Field, + bar: Field, +} + +impl MyStruct { + fn new(foo: Field) -> MyStruct { + MyStruct { + foo, + bar: 2, + } + } + + fn sum(self) -> Field { + self.foo + self.bar + } +} + +fn main() { + let s = MyStruct::new(40); + assert(s.sum() == 42); +} +``` + +Methods are just syntactic sugar for functions, so if we wanted to we could also call `sum` as +follows: + +```rust +assert(MyStruct::sum(s) == 42); +``` + +It is also possible to specialize which method is chosen depending on the [generic](./generics.md) type that is used. In this example, the `foo` function returns different values depending on its type: + +```rust +struct Foo {} + +impl Foo { + fn foo(self) -> Field { 1 } +} + +impl Foo { + fn foo(self) -> Field { 2 } +} + +fn main() { + let f1: Foo = Foo{}; + let f2: Foo = Foo{}; + assert(f1.foo() + f2.foo() == 3); +} +``` + +Also note that impls with the same method name defined in them cannot overlap. For example, if we already have `foo` defined for `Foo` and `Foo` like we do above, we cannot also define `foo` in an `impl Foo` since it would be ambiguous which version of `foo` to choose. + +```rust +// Including this impl in the same project as the above snippet would +// cause an overlapping impls error +impl Foo { + fn foo(self) -> Field { 3 } +} +``` + +## Lambdas + +Lambdas are anonymous functions. They follow the syntax of Rust - `|arg1, arg2, ..., argN| return_expression`. + +```rust +let add_50 = |val| val + 50; +assert(add_50(100) == 150); +``` + +See [Lambdas](./lambdas.md) for more details. + +## Attributes + +Attributes are metadata that can be applied to a function, using the following syntax: `#[attribute(value)]`. + +Supported attributes include: + +- **builtin**: the function is implemented by the compiler, for efficiency purposes. +- **deprecated**: mark the function as _deprecated_. Calling the function will generate a warning: `warning: use of deprecated function` +- **field**: Used to enable conditional compilation of code depending on the field size. See below for more details +- **oracle**: mark the function as _oracle_; meaning it is an external unconstrained function, implemented in noir_js. See [Unconstrained](./unconstrained.md) and [NoirJS](../../reference/NoirJS/noir_js/index.md) for more details. +- **test**: mark the function as unit tests. See [Tests](../../tooling/testing.md) for more details + +### Field Attribute + +The field attribute defines which field the function is compatible for. The function is conditionally compiled, under the condition that the field attribute matches the Noir native field. +The field can be defined implicitly, by using the name of the elliptic curve usually associated to it - for instance bn254, bls12_381 - or explicitly by using the field (prime) order, in decimal or hexadecimal form. +As a result, it is possible to define multiple versions of a function with each version specialized for a different field attribute. This can be useful when a function requires different parameters depending on the underlying elliptic curve. + +Example: we define the function `foo()` three times below. Once for the default Noir bn254 curve, once for the field $\mathbb F_{23}$, which will normally never be used by Noir, and once again for the bls12_381 curve. + +```rust +#[field(bn254)] +fn foo() -> u32 { + 1 +} + +#[field(23)] +fn foo() -> u32 { + 2 +} + +// This commented code would not compile as foo would be defined twice because it is the same field as bn254 +// #[field(21888242871839275222246405745257275088548364400416034343698204186575808495617)] +// fn foo() -> u32 { +// 2 +// } + +#[field(bls12_381)] +fn foo() -> u32 { + 3 +} +``` + +If the field name is not known to Noir, it will discard the function. Field names are case insensitive. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/generics.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/generics.md new file mode 100644 index 00000000000..c180a0ce7e6 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/generics.md @@ -0,0 +1,251 @@ +--- +title: Generics +description: Learn how to use Generics in Noir +keywords: [Noir, Rust, generics, functions, structs] +sidebar_position: 7 +--- + +Generics allow you to use the same functions with multiple different concrete data types. You can +read more about the concept of generics in the Rust documentation +[here](https://doc.rust-lang.org/book/ch10-01-syntax.html). + +Here is a trivial example showing the identity function that supports any type. In Rust, it is +common to refer to the most general type as `T`. We follow the same convention in Noir. + +```rust +fn id(x: T) -> T { + x +} +``` + +## Numeric Generics + +If we want to be generic over array lengths (which are type-level integers), we can use numeric +generics. Using these looks similar to using regular generics, but introducing them into scope +requires declaring them with `let MyGenericName: IntegerType`. This can be done anywhere a normal +generic is declared. Instead of types, these generics resolve to integers at compile-time. +Here's an example of a struct that is generic over the size of the array it contains internally: + +```rust +struct BigInt { + limbs: [u32; N], +} + +impl BigInt { + // `N` is in scope of all methods in the impl + fn first(first: BigInt, second: BigInt) -> Self { + assert(first.limbs != second.limbs); + first + + fn second(first: BigInt, second: Self) -> Self { + assert(first.limbs != second.limbs); + second + } +} +``` + +## In Structs + +Generics are useful for specifying types in structs. For example, we can specify that a field in a +struct will be of a certain generic type. In this case `value` is of type `T`. + +```rust +struct RepeatedValue { + value: T, + count: Field, +} + +impl RepeatedValue { + fn print(self) { + for _i in 0 .. self.count { + println(self.value); + } + } +} + +fn main() { + let repeated = RepeatedValue { value: "Hello!", count: 2 }; + repeated.print(); +} +``` + +The `print` function will print `Hello!` an arbitrary number of times, twice in this case. + +## Calling functions on generic parameters + +Since a generic type `T` can represent any type, how can we call functions on the underlying type? +In other words, how can we go from "any type `T`" to "any type `T` that has certain methods available?" + +This is what [traits](../concepts/traits.md) are for in Noir. Here's an example of a function generic over +any type `T` that implements the `Eq` trait for equality: + +```rust +fn first_element_is_equal(array1: [T; N], array2: [T; N]) -> bool + where T: Eq +{ + if (array1.len() == 0) | (array2.len() == 0) { + true + } else { + array1[0] == array2[0] + } +} + +fn main() { + assert(first_element_is_equal([1, 2, 3], [1, 5, 6])); + + // We can use first_element_is_equal for arrays of any type + // as long as we have an Eq impl for the types we pass in + let array = [MyStruct::new(), MyStruct::new()]; + assert(array_eq(array, array, MyStruct::eq)); +} + +impl Eq for MyStruct { + fn eq(self, other: MyStruct) -> bool { + self.foo == other.foo + } +} +``` + +You can find more details on traits and trait implementations on the [traits page](../concepts/traits.md). + +## Manually Specifying Generics with the Turbofish Operator + +There are times when the compiler cannot reasonably infer what type should be used for a generic, or when the developer themselves may want to manually distinguish generic type parameters. This is where the `::<>` turbofish operator comes into play. + +The `::<>` operator can follow a variable or path and can be used to manually specify generic arguments within the angle brackets. +The name "turbofish" comes from that `::<>` looks like a little fish. + +Examples: +```rust +fn main() { + let mut slice = []; + slice = slice.push_back(1); + slice = slice.push_back(2); + // Without turbofish a type annotation would be needed on the left hand side + let array = slice.as_array::<2>(); +} +``` + + +```rust +trait MyTrait { + fn ten() -> Self; +} + +impl MyTrait for Field { + fn ten() -> Self { 10 } +} + +struct Foo { + inner: T +} + +impl Foo { + fn generic_method(_self: Self) -> U where U: MyTrait { + U::ten() + } +} + +fn example() { + let foo: Foo = Foo { inner: 1 }; + // Using a type other than `Field` here (e.g. u32) would fail as + // there is no matching impl for `u32: MyTrait`. + // + // Substituting the `10` on the left hand side of this assert + // with `10 as u32` would also fail with a type mismatch as we + // are expecting a `Field` from the right hand side. + assert(10 as u32 == foo.generic_method::()); +} +``` + +## Arithmetic Generics + +In addition to numeric generics, Noir also allows a limited form of arithmetic on generics. +When you have a numeric generic such as `N`, you can use the following operators on it in a +type position: `+`, `-`, `*`, `/`, and `%`. + +Note that type checking arithmetic generics is a best effort guess from the compiler and there +are many cases of types that are equal that the compiler may not see as such. For example, +we know that `T * (N + M)` should be equal to `T*N + T*M` but the compiler does not currently +apply the distributive law and thus sees these as different types. + +Even with this limitation though, the compiler can handle common cases decently well: + +```rust +trait Serialize { + fn serialize(self) -> [Field; N]; +} + +impl Serialize<1> for Field { + fn serialize(self) -> [Field; 1] { + [self] + } +} + +impl Serialize for [T; N] + where T: Serialize { .. } + +impl Serialize for (T, U) + where T: Serialize, U: Serialize { .. } + +fn main() { + let data = (1, [2, 3, 4]); + assert_eq(data.serialize().len(), 4); +} +``` + +Note that if there is any over or underflow the types will fail to unify: + +```rust title="underflow-example" showLineNumbers +fn pop(array: [Field; N]) -> [Field; N - 1] { + let mut result: [Field; N - 1] = std::mem::zeroed(); + for i in 0..N - 1 { + result[i] = array[i]; + } + result +} + +fn main() { + // error: Could not determine array length `(0 - 1)` + pop([]); +} +``` +> Source code: test_programs/compile_failure/arithmetic_generics_underflow/src/main.nr#L1-L14 + + +This also applies if there is underflow in an intermediate calculation: + +```rust title="intermediate-underflow-example" showLineNumbers +fn main() { + // From main it looks like there's nothing sketchy going on + seems_fine([]); +} + +// Since `seems_fine` says it can receive and return any length N +fn seems_fine(array: [Field; N]) -> [Field; N] { + // But inside `seems_fine` we pop from the array which + // requires the length to be greater than zero. + + // error: Could not determine array length `(0 - 1)` + push_zero(pop(array)) +} + +fn pop(array: [Field; N]) -> [Field; N - 1] { + let mut result: [Field; N - 1] = std::mem::zeroed(); + for i in 0..N - 1 { + result[i] = array[i]; + } + result +} + +fn push_zero(array: [Field; N]) -> [Field; N + 1] { + let mut result: [Field; N + 1] = std::mem::zeroed(); + for i in 0..N { + result[i] = array[i]; + } + // index N is already zeroed + result +} +``` +> Source code: test_programs/compile_failure/arithmetic_generics_intermediate_underflow/src/main.nr#L1-L32 + diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/globals.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/globals.md new file mode 100644 index 00000000000..1145c55dfc7 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/globals.md @@ -0,0 +1,82 @@ +--- +title: Global Variables +description: + Learn about global variables in Noir. Discover how + to declare, modify, and use them in your programs. +keywords: [noir programming language, globals, global variables, constants] +sidebar_position: 8 +--- + +## Globals + + +Noir supports global variables. The global's type can be inferred by the compiler entirely: + +```rust +global N = 5; // Same as `global N: Field = 5` + +global TUPLE = (3, 2); + +fn main() { + assert(N == 5); + assert(N == TUPLE.0 + TUPLE.1); +} +``` + +:::info + +Globals can be defined as any expression, so long as they don't depend on themselves - otherwise there would be a dependency cycle! For example: + +```rust +global T = foo(T); // dependency error +``` + +::: + + +If they are initialized to a literal integer, globals can be used to specify an array's length: + +```rust +global N: u32 = 2; + +fn main(y : [Field; N]) { + assert(y[0] == y[1]) +} +``` + +A global from another module can be imported or referenced externally like any other name: + +```rust +global N = 20; + +fn main() { + assert(my_submodule::N != N); +} + +mod my_submodule { + global N: Field = 10; +} +``` + +When a global is used, Noir replaces the name with its definition on each occurrence. +This means globals defined using function calls will repeat the call each time they're used: + +```rust +global RESULT = foo(); + +fn foo() -> [Field; 100] { ... } +``` + +This is usually fine since Noir will generally optimize any function call that does not +refer to a program input into a constant. It should be kept in mind however, if the called +function performs side-effects like `println`, as these will still occur on each use. + +### Visibility + +By default, like functions, globals are private to the module the exist in. You can use `pub` +to make the global public or `pub(crate)` to make it public to just its crate: + +```rust +// This global is now public +pub global N = 5; +``` \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/lambdas.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/lambdas.md new file mode 100644 index 00000000000..be3c7e0b5ca --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/lambdas.md @@ -0,0 +1,81 @@ +--- +title: Lambdas +description: Learn how to use anonymous functions in Noir programming language. +keywords: [Noir programming language, lambda, closure, function, anonymous function] +sidebar_position: 9 +--- + +## Introduction + +Lambdas are anonymous functions. The syntax is `|arg1, arg2, ..., argN| return_expression`. + +```rust +let add_50 = |val| val + 50; +assert(add_50(100) == 150); +``` + +A block can be used as the body of a lambda, allowing you to declare local variables inside it: + +```rust +let cool = || { + let x = 100; + let y = 100; + x + y +} + +assert(cool() == 200); +``` + +## Closures + +Inside the body of a lambda, you can use variables defined in the enclosing function. Such lambdas are called **closures**. In this example `x` is defined inside `main` and is accessed from within the lambda: + +```rust +fn main() { + let x = 100; + let closure = || x + 150; + assert(closure() == 250); +} +``` + +## Passing closures to higher-order functions + +It may catch you by surprise that the following code fails to compile: + +```rust +fn foo(f: fn () -> Field) -> Field { + f() +} + +fn main() { + let (x, y) = (50, 50); + assert(foo(|| x + y) == 100); // error :( +} +``` + +The reason is that the closure's capture environment affects its type - we have a closure that captures two Fields and `foo` +expects a regular function as an argument - those are incompatible. +:::note + +Variables contained within the `||` are the closure's parameters, and the expression that follows it is the closure's body. The capture environment is comprised of any variables used in the closure's body that are not parameters. + +E.g. in |x| x + y, y would be a captured variable, but x would not be, since it is a parameter of the closure. + +::: +The syntax for the type of a closure is `fn[env](args) -> ret_type`, where `env` is the capture environment of the closure - +in this example that's `(Field, Field)`. + +The best solution in our case is to make `foo` generic over the environment type of its parameter, so that it can be called +with closures with any environment, as well as with regular functions: + +```rust +fn foo(f: fn[Env]() -> Field) -> Field { + f() +} + +fn main() { + let (x, y) = (50, 50); + assert(foo(|| x + y) == 100); // compiles fine + assert(foo(|| 60) == 60); // compiles fine +} +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/mutability.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/mutability.md new file mode 100644 index 00000000000..fdeef6a87c5 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/mutability.md @@ -0,0 +1,121 @@ +--- +title: Mutability +description: + Learn about mutable variables in Noir. Discover how + to declare, modify, and use them in your programs. +keywords: [noir programming language, mutability in noir, mutable variables] +sidebar_position: 8 +--- + +Variables in noir can be declared mutable via the `mut` keyword. Mutable variables can be reassigned +to via an assignment expression. + +```rust +let x = 2; +x = 3; // error: x must be mutable to be assigned to + +let mut y = 3; +let y = 4; // OK +``` + +The `mut` modifier can also apply to patterns: + +```rust +let (a, mut b) = (1, 2); +a = 11; // error: a must be mutable to be assigned to +b = 12; // OK + +let mut (c, d) = (3, 4); +c = 13; // OK +d = 14; // OK + +// etc. +let MyStruct { x: mut y } = MyStruct { x: a }; +// y is now in scope +``` + +Note that mutability in noir is local and everything is passed by value, so if a called function +mutates its parameters then the parent function will keep the old value of the parameters. + +```rust +fn main() -> pub Field { + let x = 3; + helper(x); + x // x is still 3 +} + +fn helper(mut x: i32) { + x = 4; +} +``` + +## Non-local mutability + +Non-local mutability can be achieved through the mutable reference type `&mut T`: + +```rust +fn set_to_zero(x: &mut Field) { + *x = 0; +} + +fn main() { + let mut y = 42; + set_to_zero(&mut y); + assert(*y == 0); +} +``` + +When creating a mutable reference, the original variable being referred to (`y` in this +example) must also be mutable. Since mutable references are a reference type, they must +be explicitly dereferenced via `*` to retrieve the underlying value. Note that this yields +a copy of the value, so mutating this copy will not change the original value behind the +reference: + +```rust +fn main() { + let mut x = 1; + let x_ref = &mut x; + + let mut y = *x_ref; + let y_ref = &mut y; + + x = 2; + *x_ref = 3; + + y = 4; + *y_ref = 5; + + assert(x == 3); + assert(*x_ref == 3); + assert(y == 5); + assert(*y_ref == 5); +} +``` + +Note that types in Noir are actually deeply immutable so the copy that occurs when +dereferencing is only a conceptual copy - no additional constraints will occur. + +Mutable references can also be stored within structs. Note that there is also +no lifetime parameter on these unlike rust. This is because the allocated memory +always lasts the entire program - as if it were an array of one element. + +```rust +struct Foo { + x: &mut Field +} + +impl Foo { + fn incr(mut self) { + *self.x += 1; + } +} + +fn main() { + let foo = Foo { x: &mut 0 }; + foo.incr(); + assert(*foo.x == 1); +} +``` + +In general, you should avoid non-local & shared mutability unless it is needed. Sticking +to only local mutability will improve readability and potentially improve compiler optimizations as well. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/ops.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/ops.md new file mode 100644 index 00000000000..c35c36c38a9 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/ops.md @@ -0,0 +1,98 @@ +--- +title: Logical Operations +description: + Learn about the supported arithmetic and logical operations in the Noir programming language. + Discover how to perform operations on private input types, integers, and booleans. +keywords: + [ + Noir programming language, + supported operations, + arithmetic operations, + logical operations, + predicate operators, + bitwise operations, + short-circuiting, + backend, + ] +sidebar_position: 3 +--- + +# Operations + +## Table of Supported Operations + +| Operation | Description | Requirements | +| :-------- | :------------------------------------------------------------: | -------------------------------------: | +| + | Adds two private input types together | Types must be private input | +| - | Subtracts two private input types together | Types must be private input | +| \* | Multiplies two private input types together | Types must be private input | +| / | Divides two private input types together | Types must be private input | +| ^ | XOR two private input types together | Types must be integer | +| & | AND two private input types together | Types must be integer | +| \| | OR two private input types together | Types must be integer | +| \<\< | Left shift an integer by another integer amount | Types must be integer, shift must be u8 | +| >> | Right shift an integer by another integer amount | Types must be integer, shift must be u8 | +| ! | Bitwise not of a value | Type must be integer or boolean | +| \< | returns a bool if one value is less than the other | Upper bound must have a known bit size | +| \<= | returns a bool if one value is less than or equal to the other | Upper bound must have a known bit size | +| > | returns a bool if one value is more than the other | Upper bound must have a known bit size | +| >= | returns a bool if one value is more than or equal to the other | Upper bound must have a known bit size | +| == | returns a bool if one value is equal to the other | Both types must not be constants | +| != | returns a bool if one value is not equal to the other | Both types must not be constants | + +### Predicate Operators + +`<,<=, !=, == , >, >=` are known as predicate/comparison operations because they compare two values. +This differs from the operations such as `+` where the operands are used in _computation_. + +### Bitwise Operations Example + +```rust +fn main(x : Field) { + let y = x as u32; + let z = y & y; +} +``` + +`z` is implicitly constrained to be the result of `y & y`. The `&` operand is used to denote bitwise +`&`. + +> `x & x` would not compile as `x` is a `Field` and not an integer type. + +### Logical Operators + +Noir has no support for the logical operators `||` and `&&`. This is because encoding the +short-circuiting that these operators require can be inefficient for Noir's backend. Instead you can +use the bitwise operators `|` and `&` which operate identically for booleans, just without the +short-circuiting. + +```rust +let my_val = 5; + +let mut flag = 1; +if (my_val > 6) | (my_val == 0) { + flag = 0; +} +assert(flag == 1); + +if (my_val != 10) & (my_val < 50) { + flag = 0; +} +assert(flag == 0); +``` + +### Shorthand operators + +Noir shorthand operators for most of the above operators, namely `+=, -=, *=, /=, %=, &=, |=, ^=, <<=`, and `>>=`. These allow for more concise syntax. For example: + +```rust +let mut i = 0; +i = i + 1; +``` + +could be written as: + +```rust +let mut i = 0; +i += 1; +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/oracles.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/oracles.mdx new file mode 100644 index 00000000000..77a2ac1550a --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/oracles.mdx @@ -0,0 +1,29 @@ +--- +title: Oracles +description: Dive into how Noir supports Oracles via RPC calls, and learn how to declare an Oracle in Noir with our comprehensive guide. +keywords: + - Noir + - Oracles + - RPC Calls + - Unconstrained Functions + - Programming + - Blockchain +sidebar_position: 6 +--- + +import Experimental from '@site/src/components/Notes/_experimental.mdx'; + + + +Noir has support for Oracles via RPC calls. This means Noir will make an RPC call and use the return value for proof generation. + +Since Oracles are not resolved by Noir, they are [`unconstrained` functions](./unconstrained.md) + +You can declare an Oracle through the `#[oracle()]` flag. Example: + +```rust +#[oracle(get_number_sequence)] +unconstrained fn get_number_sequence(_size: Field) -> [Field] {} +``` + +The timeout for when using an external RPC oracle resolver can be set with the `NARGO_FOREIGN_CALL_TIMEOUT` environment variable. This timeout is in units of milliseconds. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/shadowing.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/shadowing.md new file mode 100644 index 00000000000..5ce6130d201 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/shadowing.md @@ -0,0 +1,44 @@ +--- +title: Shadowing +sidebar_position: 12 +--- + +Noir allows for inheriting variables' values and re-declaring them with the same name similar to Rust, known as shadowing. + +For example, the following function is valid in Noir: + +```rust +fn main() { + let x = 5; + + { + let x = x * 2; + assert (x == 10); + } + + assert (x == 5); +} +``` + +In this example, a variable x is first defined with the value 5. + +The local scope that follows shadows the original x, i.e. creates a local mutable x based on the value of the original x. It is given a value of 2 times the original x. + +When we return to the main scope, x once again refers to just the original x, which stays at the value of 5. + +## Temporal mutability + +One way that shadowing is useful, in addition to ergonomics across scopes, is for temporarily mutating variables. + +```rust +fn main() { + let age = 30; + // age = age + 5; // Would error as `age` is immutable by default. + + let mut age = age + 5; // Temporarily mutates `age` with a new value. + + let age = age; // Locks `age`'s mutability again. + + assert (age == 35); +} +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/traits.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/traits.md new file mode 100644 index 00000000000..5d07e0c68f0 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/traits.md @@ -0,0 +1,475 @@ +--- +title: Traits +description: + Traits in Noir can be used to abstract out a common interface for functions across + several data types. +keywords: [noir programming language, traits, interfaces, generic, protocol] +sidebar_position: 14 +--- + +## Overview + +Traits in Noir are a useful abstraction similar to interfaces or protocols in other languages. Each trait defines +the interface of several methods contained within the trait. Types can then implement this trait by providing +implementations for these methods. For example in the program: + +```rust +struct Rectangle { + width: Field, + height: Field, +} + +impl Rectangle { + fn area(self) -> Field { + self.width * self.height + } +} + +fn log_area(r: Rectangle) { + println(r.area()); +} +``` + +We have a function `log_area` to log the area of a `Rectangle`. Now how should we change the program if we want this +function to work on `Triangle`s as well?: + +```rust +struct Triangle { + width: Field, + height: Field, +} + +impl Triangle { + fn area(self) -> Field { + self.width * self.height / 2 + } +} +``` + +Making `log_area` generic over all types `T` would be invalid since not all types have an `area` method. Instead, we can +introduce a new `Area` trait and make `log_area` generic over all types `T` that implement `Area`: + +```rust +trait Area { + fn area(self) -> Field; +} + +fn log_area(shape: T) where T: Area { + println(shape.area()); +} +``` + +We also need to explicitly implement `Area` for `Rectangle` and `Triangle`. We can do that by changing their existing +impls slightly. Note that the parameter types and return type of each of our `area` methods must match those defined +by the `Area` trait. + +```rust +impl Area for Rectangle { + fn area(self) -> Field { + self.width * self.height + } +} + +impl Area for Triangle { + fn area(self) -> Field { + self.width * self.height / 2 + } +} +``` + +Now we have a working program that is generic over any type of Shape that is used! Others can even use this program +as a library with their own types - such as `Circle` - as long as they also implement `Area` for these types. + +## Where Clauses + +As seen in `log_area` above, when we want to create a function or method that is generic over any type that implements +a trait, we can add a where clause to the generic function. + +```rust +fn log_area(shape: T) where T: Area { + println(shape.area()); +} +``` + +It is also possible to apply multiple trait constraints on the same variable at once by combining traits with the `+` +operator. Similarly, we can have multiple trait constraints by separating each with a comma: + +```rust +fn foo(elements: [T], thing: U) where + T: Default + Add + Eq, + U: Bar, +{ + let mut sum = T::default(); + + for element in elements { + sum += element; + } + + if sum == T::default() { + thing.bar(); + } +} +``` + +## Generic Implementations + +You can add generics to a trait implementation by adding the generic list after the `impl` keyword: + +```rust +trait Second { + fn second(self) -> Field; +} + +impl Second for (T, Field) { + fn second(self) -> Field { + self.1 + } +} +``` + +You can also implement a trait for every type this way: + +```rust +trait Debug { + fn debug(self); +} + +impl Debug for T { + fn debug(self) { + println(self); + } +} + +fn main() { + 1.debug(); +} +``` + +### Generic Trait Implementations With Where Clauses + +Where clauses can be placed on trait implementations themselves to restrict generics in a similar way. +For example, while `impl Foo for T` implements the trait `Foo` for every type, `impl Foo for T where T: Bar` +will implement `Foo` only for types that also implement `Bar`. This is often used for implementing generic types. +For example, here is the implementation for array equality: + +```rust +impl Eq for [T; let N: u32] where T: Eq { + // Test if two arrays have the same elements. + // Because both arrays must have length N, we know their lengths already match. + fn eq(self, other: Self) -> bool { + let mut result = true; + + for i in 0 .. self.len() { + // The T: Eq constraint is needed to call == on the array elements here + result &= self[i] == other[i]; + } + + result + } +} +``` + +Where clauses can also be placed on struct implementations. +For example, here is a method utilizing a generic type that implements the equality trait. + +```rust +struct Foo { + a: u32, + b: T, +} + +impl Foo where T: Eq { + fn eq(self, other: Self) -> bool { + (self.a == other.a) & self.b.eq(other.b) + } +} +``` + +## Generic Traits + +Traits themselves can also be generic by placing the generic arguments after the trait name. These generics are in +scope of every item within the trait. + +```rust +trait Into { + // Convert `self` to type `T` + fn into(self) -> T; +} +``` + +When implementing generic traits the generic arguments of the trait must be specified. This is also true anytime +when referencing a generic trait (e.g. in a `where` clause). + +```rust +struct MyStruct { + array: [Field; 2], +} + +impl Into<[Field; 2]> for MyStruct { + fn into(self) -> [Field; 2] { + self.array + } +} + +fn as_array(x: T) -> [Field; 2] + where T: Into<[Field; 2]> +{ + x.into() +} + +fn main() { + let array = [1, 2]; + let my_struct = MyStruct { array }; + + assert_eq(as_array(my_struct), array); +} +``` + +### Associated Types and Constants + +Traits also support associated types and constraints which can be thought of as additional generics that are referred to by name. + +Here's an example of a trait with an associated type `Foo` and a constant `Bar`: + +```rust +trait MyTrait { + type Foo; + + let Bar: u32; +} +``` + +Now when we're implementing `MyTrait` we also have to provide values for `Foo` and `Bar`: + +```rust +impl MyTrait for Field { + type Foo = i32; + + let Bar: u32 = 11; +} +``` + +Since associated constants can also be used in a type position, its values are limited to only other +expression kinds allowed in numeric generics. + +Note that currently all associated types and constants must be explicitly specified in a trait constraint. +If we leave out any, we'll get an error that we're missing one: + +```rust +// Error! Constraint is missing associated constant for `Bar` +fn foo(x: T) where T: MyTrait { + ... +} +``` + +Because all associated types and constants must be explicitly specified, they are essentially named generics, +although this is set to change in the future. Future versions of Noir will allow users to elide associated types +in trait constraints similar to Rust. When this is done, you may still refer to their value with the `::AssociatedType` +syntax: + +```rust +// Only valid in future versions of Noir: +fn foo(x: T) where T: MyTrait { + let _: ::Foo = ...; +} +``` + +The type as trait syntax is possible in Noir today but is less useful when each type must be explicitly specified anyway: + +```rust +fn foo(x: T) where T: MyTrait { + // Works, but could just use F directly + let _: >::Foo = ...; + + let _: F = ...; +} +``` + +## Trait Methods With No `self` + +A trait can contain any number of methods, each of which have access to the `Self` type which represents each type +that eventually implements the trait. Similarly, the `self` variable is available as well but is not required to be used. +For example, we can define a trait to create a default value for a type. This trait will need to return the `Self` type +but doesn't need to take any parameters: + +```rust +trait Default { + fn default() -> Self; +} +``` + +Implementing this trait can be done similarly to any other trait: + +```rust +impl Default for Field { + fn default() -> Field { + 0 + } +} + +struct MyType {} + +impl Default for MyType { + fn default() -> Field { + MyType {} + } +} +``` + +However, since there is no `self` parameter, we cannot call it via the method call syntax `object.method()`. +Instead, we'll need to refer to the function directly. This can be done either by referring to the +specific impl `MyType::default()` or referring to the trait itself `Default::default()`. In the later +case, type inference determines the impl that is selected. + +```rust +let my_struct = MyStruct::default(); + +let x: Field = Default::default(); +let result = x + Default::default(); +``` + +:::warning + +```rust +let _ = Default::default(); +``` + +If type inference cannot select which impl to use because of an ambiguous `Self` type, an impl will be +arbitrarily selected. This occurs most often when the result of a trait function call with no parameters +is unused. To avoid this, when calling a trait function with no `self` or `Self` parameters or return type, +always refer to it via the implementation type's namespace - e.g. `MyType::default()`. +This is set to change to an error in future Noir versions. + +::: + +## Default Method Implementations + +A trait can also have default implementations of its methods by giving a body to the desired functions. +Note that this body must be valid for all types that may implement the trait. As a result, the only +valid operations on `self` will be operations valid for any type or other operations on the trait itself. + +```rust +trait Numeric { + fn add(self, other: Self) -> Self; + + // Default implementation of double is (self + self) + fn double(self) -> Self { + self.add(self) + } +} +``` + +When implementing a trait with default functions, a type may choose to implement only the required functions: + +```rust +impl Numeric for Field { + fn add(self, other: Field) -> Field { + self + other + } +} +``` + +Or it may implement the optional methods as well: + +```rust +impl Numeric for u32 { + fn add(self, other: u32) -> u32 { + self + other + } + + fn double(self) -> u32 { + self * 2 + } +} +``` + +## Impl Specialization + +When implementing traits for a generic type it is possible to implement the trait for only a certain combination +of generics. This can be either as an optimization or because those specific generics are required to implement the trait. + +```rust +trait Sub { + fn sub(self, other: Self) -> Self; +} + +struct NonZero { + value: T, +} + +impl Sub for NonZero { + fn sub(self, other: Self) -> Self { + let value = self.value - other.value; + assert(value != 0); + NonZero { value } + } +} +``` + +## Overlapping Implementations + +Overlapping implementations are disallowed by Noir to ensure Noir's decision on which impl to select is never ambiguous. +This means if a trait `Foo` is already implemented +by a type `Bar` for all `T`, then we cannot also have a separate impl for `Bar` (or any other +type argument). Similarly, if there is an impl for all `T` such as `impl Debug for T`, we cannot create +any more impls to `Debug` for other types since it would be ambiguous which impl to choose for any given +method call. + +```rust +trait Trait {} + +// Previous impl defined here +impl Trait for (A, B) {} + +// error: Impl for type `(Field, Field)` overlaps with existing impl +impl Trait for (Field, Field) {} +``` + +## Trait Coherence + +Another restriction on trait implementations is coherence. This restriction ensures other crates cannot create +impls that may overlap with other impls, even if several unrelated crates are used as dependencies in the same +program. + +The coherence restriction is: to implement a trait, either the trait itself or the object type must be declared +in the crate the impl is in. + +In practice this often comes up when using types provided by libraries. If a library provides a type `Foo` that does +not implement a trait in the standard library such as `Default`, you may not `impl Default for Foo` in your own crate. +While restrictive, this prevents later issues or silent changes in the program if the `Foo` library later added its +own impl for `Default`. If you are a user of the `Foo` library in this scenario and need a trait not implemented by the +library your choices are to either submit a patch to the library or use the newtype pattern. + +### The Newtype Pattern + +The newtype pattern gets around the coherence restriction by creating a new wrapper type around the library type +that we cannot create `impl`s for. Since the new wrapper type is defined in our current crate, we can create +impls for any trait we need on it. + +```rust +struct Wrapper { + foo: some_library::Foo, +} + +impl Default for Wrapper { + fn default() -> Wrapper { + Wrapper { + foo: some_library::Foo::new(), + } + } +} +``` + +Since we have an impl for our own type, the behavior of this code will not change even if `some_library` is updated +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. + +### Visibility + +By default, like functions, traits are private to the module the exist in. You can use `pub` +to make the trait public or `pub(crate)` to make it public to just its crate: + +```rust +// This trait is now public +pub trait Trait {} +``` \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/unconstrained.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/unconstrained.md new file mode 100644 index 00000000000..b5221b8d2dd --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/concepts/unconstrained.md @@ -0,0 +1,104 @@ +--- +title: Unconstrained Functions +description: "Learn about what unconstrained functions in Noir are, how to use them and when you'd want to." + +keywords: [Noir programming language, unconstrained, open] +sidebar_position: 5 +--- + +Unconstrained functions are functions which do not constrain any of the included computation and allow for non-deterministic computation. + +## Why? + +Zero-knowledge (ZK) domain-specific languages (DSL) enable developers to generate ZK proofs from their programs by compiling code down to the constraints of an NP complete language (such as R1CS or PLONKish languages). However, the hard bounds of a constraint system can be very limiting to the functionality of a ZK DSL. + +Enabling a circuit language to perform unconstrained execution is a powerful tool. Said another way, unconstrained execution lets developers generate witnesses from code that does not generate any constraints. Being able to execute logic outside of a circuit is critical for both circuit performance and constructing proofs on information that is external to a circuit. + +Fetching information from somewhere external to a circuit can also be used to enable developers to improve circuit efficiency. + +A ZK DSL does not just prove computation, but proves that some computation was handled correctly. Thus, it is necessary that when we switch from performing some operation directly inside of a circuit to inside of an unconstrained environment that the appropriate constraints are still laid down elsewhere in the circuit. + +## Example + +An in depth example might help drive the point home. This example comes from the excellent [post](https://discord.com/channels/1113924620781883405/1124022445054111926/1128747641853972590) by Tom in the Noir Discord. + +Let's look at how we can optimize a function to turn a `u72` into an array of `u8`s. + +```rust +fn main(num: u72) -> pub [u8; 8] { + let mut out: [u8; 8] = [0; 8]; + for i in 0..8 { + out[i] = (num >> (56 - (i * 8)) as u72 & 0xff) as u8; + } + + out +} +``` + +``` +Total ACIR opcodes generated for language PLONKCSat { width: 3 }: 91 +Backend circuit size: 3619 +``` + +A lot of the operations in this function are optimized away by the compiler (all the bit-shifts turn into divisions by constants). However we can save a bunch of gates by casting to u8 a bit earlier. This automatically truncates the bit-shifted value to fit in a u8 which allows us to remove the AND against 0xff. This saves us ~480 gates in total. + +```rust +fn main(num: u72) -> pub [u8; 8] { + let mut out: [u8; 8] = [0; 8]; + for i in 0..8 { + out[i] = (num >> (56 - (i * 8)) as u8; + } + + out +} +``` + +``` +Total ACIR opcodes generated for language PLONKCSat { width: 3 }: 75 +Backend circuit size: 3143 +``` + +Those are some nice savings already but we can do better. This code is all constrained so we're proving every step of calculating out using num, but we don't actually care about how we calculate this, just that it's correct. This is where brillig comes in. + +It turns out that truncating a u72 into a u8 is hard to do inside a snark, each time we do as u8 we lay down 4 ACIR opcodes which get converted into multiple gates. It's actually much easier to calculate num from out than the other way around. All we need to do is multiply each element of out by a constant and add them all together, both relatively easy operations inside a snark. + +We can then run `u72_to_u8` as unconstrained brillig code in order to calculate out, then use that result in our constrained function and assert that if we were to do the reverse calculation we'd get back num. This looks a little like the below: + +```rust +fn main(num: u72) -> pub [u8; 8] { + let out = unsafe { + u72_to_u8(num) + }; + + let mut reconstructed_num: u72 = 0; + for i in 0..8 { + reconstructed_num += (out[i] as u72 << (56 - (8 * i))); + } + assert(num == reconstructed_num); + out +} + +unconstrained fn u72_to_u8(num: u72) -> [u8; 8] { + let mut out: [u8; 8] = [0; 8]; + for i in 0..8 { + out[i] = (num >> (56 - (i * 8))) as u8; + } + out +} +``` + +``` +Total ACIR opcodes generated for language PLONKCSat { width: 3 }: 78 +Backend circuit size: 2902 +``` + +This ends up taking off another ~250 gates from our circuit! We've ended up with more ACIR opcodes than before but they're easier for the backend to prove (resulting in fewer gates). + +Note that in order to invoke unconstrained functions we need to wrap them in an `unsafe` block, +to make it clear that the call is unconstrained. + +Generally we want to use brillig whenever there's something that's easy to verify but hard to compute within the circuit. For example, if you wanted to calculate a square root of a number it'll be a much better idea to calculate this in brillig and then assert that if you square the result you get back your number. + +## Break and Continue + +In addition to loops over runtime bounds, `break` and `continue` are also available in unconstrained code. See [break and continue](../concepts/control_flow.md#break-and-continue) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/modules_packages_crates/_category_.json b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/modules_packages_crates/_category_.json new file mode 100644 index 00000000000..1debcfe7675 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/modules_packages_crates/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Modules, Packages and Crates", + "position": 2, + "collapsible": true, + "collapsed": true +} diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/modules_packages_crates/crates_and_packages.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/modules_packages_crates/crates_and_packages.md new file mode 100644 index 00000000000..95ee9f52ab2 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/modules_packages_crates/crates_and_packages.md @@ -0,0 +1,43 @@ +--- +title: Crates and Packages +description: Learn how to use Crates and Packages in your Noir project +keywords: [Nargo, dependencies, package management, crates, package] +sidebar_position: 0 +--- + +## Crates + +A crate is the smallest amount of code that the Noir compiler considers at a time. +Crates can contain modules, and the modules may be defined in other files that get compiled with the crate, as we’ll see in the coming sections. + +### Crate Types + +A Noir crate can come in several forms: binaries, libraries or contracts. + +#### Binaries + +_Binary crates_ are programs which you can compile to an ACIR circuit which you can then create proofs against. Each must have a function called `main` that defines the ACIR circuit which is to be proved. + +#### Libraries + +_Library crates_ don't have a `main` function and they don't compile down to ACIR. Instead they define functionality intended to be shared with multiple projects, and eventually included in a binary crate. + +#### Contracts + +Contract crates are similar to binary crates in that they compile to ACIR which you can create proofs against. They are different in that they do not have a single `main` function, but are a collection of functions to be deployed to the [Aztec network](https://aztec.network). You can learn more about the technical details of Aztec in the [monorepo](https://github.com/AztecProtocol/aztec-packages) or contract [examples](https://github.com/AztecProtocol/aztec-packages/tree/master/noir-projects/noir-contracts/contracts). + +### Crate Root + +Every crate has a root, which is the source file that the compiler starts, this is also known as the root module. The Noir compiler does not enforce any conditions on the name of the file which is the crate root, however if you are compiling via Nargo the crate root must be called `lib.nr` or `main.nr` for library or binary crates respectively. + +## Packages + +A Nargo _package_ is a collection of one of more crates that provides a set of functionality. A package must include a Nargo.toml file. + +A package _must_ contain either a library or a binary crate, but not both. + +### Differences from Cargo Packages + +One notable difference between Rust's Cargo and Noir's Nargo is that while Cargo allows a package to contain an unlimited number of binary crates and a single library crate, Nargo currently only allows a package to contain a single crate. + +In future this restriction may be lifted to allow a Nargo package to contain both a binary and library crate or multiple binary crates. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/modules_packages_crates/dependencies.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/modules_packages_crates/dependencies.md new file mode 100644 index 00000000000..24e02de08fe --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/modules_packages_crates/dependencies.md @@ -0,0 +1,124 @@ +--- +title: Dependencies +description: + Learn how to specify and manage dependencies in Nargo, allowing you to upload packages to GitHub + and use them easily in your project. +keywords: [Nargo, dependencies, GitHub, package management, versioning] +sidebar_position: 1 +--- + +Nargo allows you to upload packages to GitHub and use them as dependencies. + +## Specifying a dependency + +Specifying a dependency requires a tag to a specific commit and the git url to the url containing +the package. + +Currently, there are no requirements on the tag contents. If requirements are added, it would follow +semver 2.0 guidelines. + +> Note: Without a `tag` , there would be no versioning and dependencies would change each time you +> compile your project. + +For example, to add the [ecrecover-noir library](https://github.com/colinnielsen/ecrecover-noir) to your project, add it to `Nargo.toml`: + +```toml +# Nargo.toml + +[dependencies] +ecrecover = {tag = "v0.8.0", git = "https://github.com/colinnielsen/ecrecover-noir"} +``` + +If the module is in a subdirectory, you can define a subdirectory in your git repository, for example: + +```toml +# Nargo.toml + +[dependencies] +easy_private_token_contract = {tag ="v0.1.0-alpha62", git = "https://github.com/AztecProtocol/aztec-packages", directory = "noir-contracts/contracts/easy_private_token_contract"} +``` + +## Specifying a local dependency + +You can also specify dependencies that are local to your machine. + +For example, this file structure has a library and binary crate + +```tree +├── binary_crate +│   ├── Nargo.toml +│   └── src +│   └── main.nr +└── lib_a + ├── Nargo.toml + └── src + └── lib.nr +``` + +Inside of the binary crate, you can specify: + +```toml +# Nargo.toml + +[dependencies] +lib_a = { path = "../lib_a" } +``` + +## Importing dependencies + +You can import a dependency to a Noir file using the following syntax. For example, to import the +ecrecover-noir library and local lib_a referenced above: + +```rust +use ecrecover; +use lib_a; +``` + +You can also import only the specific parts of dependency that you want to use, like so: + +```rust +use std::hash::sha256; +use std::scalar_mul::fixed_base_embedded_curve; +``` + +Lastly, as demonstrated in the +[elliptic curve example](../standard_library/cryptographic_primitives/ec_primitives.md#examples), you +can import multiple items in the same line by enclosing them in curly braces: + +```rust +use std::ec::tecurve::affine::{Curve, Point}; +``` + +We don't have a way to consume libraries from inside a [workspace](./workspaces.md) as external dependencies right now. + +Inside a workspace, these are consumed as `{ path = "../to_lib" }` dependencies in Nargo.toml. + +## Dependencies of Dependencies + +Note that when you import a dependency, you also get access to all of the dependencies of that package. + +For example, the [phy_vector](https://github.com/resurgencelabs/phy_vector) library imports an [fraction](https://github.com/resurgencelabs/fraction) library. If you're importing the phy_vector library, then you can access the functions in fractions library like so: + +```rust +use phy_vector; + +fn main(x : Field, y : pub Field) { + //... + let f = phy_vector::fraction::toFraction(true, 2, 1); + //... +} +``` + +## Available Libraries + +Noir does not currently have an official package manager. You can find a list of available Noir libraries in the [awesome-noir repo here](https://github.com/noir-lang/awesome-noir#libraries). + +Some libraries that are available today include: + +- [Standard Library](https://github.com/noir-lang/noir/tree/master/noir_stdlib) - the Noir Standard Library +- [Ethereum Storage Proof Verification](https://github.com/aragonzkresearch/noir-trie-proofs) - a library that contains the primitives necessary for RLP decoding (in the form of look-up table construction) and Ethereum state and storage proof verification (or verification of any trie proof involving 32-byte long keys) +- [BigInt](https://github.com/shuklaayush/noir-bigint) - a library that provides a custom BigUint56 data type, allowing for computations on large unsigned integers +- [ECrecover](https://github.com/colinnielsen/ecrecover-noir/tree/main) - a library to verify an ECDSA signature and return the source Ethereum address +- [Sparse Merkle Tree Verifier](https://github.com/vocdoni/smtverifier-noir/tree/main) - a library for verification of sparse Merkle trees +- [Signed Int](https://github.com/resurgencelabs/signed_int) - a library for accessing a custom Signed Integer data type, allowing access to negative numbers on Noir +- [Fraction](https://github.com/resurgencelabs/fraction) - a library for accessing fractional number data type in Noir, allowing results that aren't whole numbers diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/modules_packages_crates/modules.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/modules_packages_crates/modules.md new file mode 100644 index 00000000000..05399c38b4c --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/modules_packages_crates/modules.md @@ -0,0 +1,221 @@ +--- +title: Modules +description: + Learn how to organize your files using modules in Noir, following the same convention as Rust's + module system. Examples included. +keywords: [Noir, Rust, modules, organizing files, sub-modules] +sidebar_position: 2 +--- + +Noir's module system follows the same convention as the _newer_ version of Rust's module system. + +## Purpose of Modules + +Modules are used to organize files. Without modules all of your code would need to live in a single +file. In Noir, the compiler does not automatically scan all of your files to detect modules. This +must be done explicitly by the developer. + +## Examples + +### Importing a module in the crate root + +Filename : `src/main.nr` + +```rust +mod foo; + +fn main() { + foo::hello_world(); +} +``` + +Filename : `src/foo.nr` + +```rust +fn from_foo() {} +``` + +In the above snippet, the crate root is the `src/main.nr` file. The compiler sees the module +declaration `mod foo` which prompts it to look for a foo.nr file. + +Visually this module hierarchy looks like the following : + +``` +crate + ├── main + │ + └── foo + └── from_foo + +``` + +The module filename may also be the name of the module as a directory with the contents in a +file named `mod.nr` within that directory. The above example can alternatively be expressed like this: + +Filename : `src/main.nr` + +```rust +mod foo; + +fn main() { + foo::hello_world(); +} +``` + +Filename : `src/foo/mod.nr` + +```rust +fn from_foo() {} +``` + +Note that it's an error to have both files `src/foo.nr` and `src/foo/mod.nr` in the filesystem. + +### Importing a module throughout the tree + +All modules are accessible from the `crate::` namespace. + +``` +crate + ├── bar + ├── foo + └── main + +``` + +In the above snippet, if `bar` would like to use functions in `foo`, it can do so by `use crate::foo::function_name`. + +### Sub-modules + +Filename : `src/main.nr` + +```rust +mod foo; + +fn main() { + foo::from_foo(); +} +``` + +Filename : `src/foo.nr` + +```rust +mod bar; +fn from_foo() {} +``` + +Filename : `src/foo/bar.nr` + +```rust +fn from_bar() {} +``` + +In the above snippet, we have added an extra module to the module tree; `bar`. `bar` is a submodule +of `foo` hence we declare bar in `foo.nr` with `mod bar`. Since `foo` is not the crate root, the +compiler looks for the file associated with the `bar` module in `src/foo/bar.nr` + +Visually the module hierarchy looks as follows: + +``` +crate + ├── main + │ + └── foo + ├── from_foo + └── bar + └── from_bar +``` + +Similar to importing a module in the crate root, modules can be placed in a `mod.nr` file, like this: + +Filename : `src/main.nr` + +```rust +mod foo; + +fn main() { + foo::from_foo(); +} +``` + +Filename : `src/foo/mod.nr` + +```rust +mod bar; +fn from_foo() {} +``` + +Filename : `src/foo/bar/mod.nr` + +```rust +fn from_bar() {} +``` + +### Referencing a parent module + +Given a submodule, you can refer to its parent module using the `super` keyword. + +Filename : `src/main.nr` + +```rust +mod foo; + +fn main() { + foo::from_foo(); +} +``` + +Filename : `src/foo.nr` + +```rust +mod bar; + +fn from_foo() {} +``` + +Filename : `src/foo/bar.nr` + +```rust +// Same as bar::from_foo +use super::from_foo; + +fn from_bar() { + from_foo(); // invokes super::from_foo(), which is bar::from_foo() + super::from_foo(); // also invokes bar::from_foo() +} +``` + +### `use` visibility + +`use` declarations are private to the containing module, by default. However, like functions, +they can be marked as `pub` or `pub(crate)`. Such a use declaration serves to _re-export_ a name. +A public `use` declaration can therefore redirect some public name to a different target definition: +even a definition with a private canonical path, inside a different module. + +An example of re-exporting: + +```rust +mod some_module { + pub use foo::{bar, baz}; + mod foo { + pub fn bar() {} + pub fn baz() {} + } +} + +fn main() { + some_module::bar(); + some_module::baz(); +} +``` + +In this example, the module `some_module` re-exports two public names defined in `foo`. + +### Visibility + +By default, like functions, modules are private to the module (or crate) the exist in. You can use `pub` +to make the module public or `pub(crate)` to make it public to just its crate: + +```rust +// This module is now public and can be seen by other crates. +pub mod foo; +``` \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/modules_packages_crates/workspaces.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/modules_packages_crates/workspaces.md new file mode 100644 index 00000000000..513497f12bf --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/modules_packages_crates/workspaces.md @@ -0,0 +1,42 @@ +--- +title: Workspaces +sidebar_position: 3 +--- + +Workspaces are a feature of nargo that allow you to manage multiple related Noir packages in a single repository. A workspace is essentially a group of related projects that share common build output directories and configurations. + +Each Noir project (with it's own Nargo.toml file) can be thought of as a package. Each package is expected to contain exactly one "named circuit", being the "name" defined in Nargo.toml with the program logic defined in `./src/main.nr`. + +For a project with the following structure: + +```tree +├── crates +│ ├── a +│ │ ├── Nargo.toml +│ │ └── Prover.toml +│ │ └── src +│ │ └── main.nr +│ └── b +│ ├── Nargo.toml +│ └── Prover.toml +│ └── src +│ └── main.nr +│ +└── Nargo.toml +``` + +You can define a workspace in Nargo.toml like so: + +```toml +[workspace] +members = ["crates/a", "crates/b"] +default-member = "crates/a" +``` + +`members` indicates which packages are included in the workspace. As such, all member packages of a workspace will be processed when the `--workspace` flag is used with various commands or if a `default-member` is not specified. + +`default-member` indicates which package various commands process by default. + +Libraries can be defined in a workspace. Inside a workspace, these are consumed as `{ path = "../to_lib" }` dependencies in Nargo.toml. + +Inside a workspace, these are consumed as `{ path = "../to_lib" }` dependencies in Nargo.toml. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/_category_.json b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/_category_.json new file mode 100644 index 00000000000..af04c0933fd --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Standard Library", + "position": 1, + "collapsible": true, + "collapsed": true +} diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/bigint.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/bigint.md new file mode 100644 index 00000000000..b16fed4ed96 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/bigint.md @@ -0,0 +1,127 @@ +--- +title: Big Integers +description: How to use big integers from Noir standard library +keywords: + [ + Big Integer, + Noir programming language, + Noir libraries, + ] +--- + +The BigInt module in the standard library exposes some class of integers which do not fit (well) into a Noir native field. It implements modulo arithmetic, modulo a 'big' prime number. + +:::note + +The module can currently be considered as `Field`s with fixed modulo sizes used by a set of elliptic curves, in addition to just the native curve. [More work](https://github.com/noir-lang/noir/issues/510) is needed to achieve arbitrarily sized big integers. + +:::note + +`nargo` can be built with `--profile release-pedantic` to enable extra overflow checks which may affect `BigInt` results in some cases. +Consider the [`noir-bignum`](https://github.com/noir-lang/noir-bignum) library for an optimized alternative approach. + +::: + +Currently 6 classes of integers (i.e 'big' prime numbers) are available in the module, namely: + +- BN254 Fq: Bn254Fq +- BN254 Fr: Bn254Fr +- Secp256k1 Fq: Secpk1Fq +- Secp256k1 Fr: Secpk1Fr +- Secp256r1 Fr: Secpr1Fr +- Secp256r1 Fq: Secpr1Fq + +Where XXX Fq and XXX Fr denote respectively the order of the base and scalar field of the (usual) elliptic curve XXX. +For instance the big integer 'Secpk1Fq' in the standard library refers to integers modulo $2^{256}-2^{32}-977$. + +Feel free to explore the source code for the other primes: + +```rust title="big_int_definition" showLineNumbers +pub struct BigInt { + pointer: u32, + modulus: u32, +} +``` +> Source code: noir_stdlib/src/bigint.nr#L14-L19 + + +## Example usage + +A common use-case is when constructing a big integer from its bytes representation, and performing arithmetic operations on it: + +```rust title="big_int_example" showLineNumbers +fn big_int_example(x: u8, y: u8) { + let a = Secpk1Fq::from_le_bytes(&[x, y, 0, 45, 2]); + let b = Secpk1Fq::from_le_bytes(&[y, x, 9]); + let c = (a + b) * b / a; + let d = c.to_le_bytes(); + println(d[0]); +} +``` +> Source code: test_programs/execution_success/bigint/src/main.nr#L72-L80 + + +## Methods + +The available operations for each big integer are: + +### from_le_bytes + +Construct a big integer from its little-endian bytes representation. Example: + +```rust + // Construct a big integer from a slice of bytes + let a = Secpk1Fq::from_le_bytes(&[x, y, 0, 45, 2]); + // Construct a big integer from an array of 32 bytes + let a = Secpk1Fq::from_le_bytes_32([1;32]); + ``` + +Sure, here's the formatted version of the remaining methods: + +### to_le_bytes + +Return the little-endian bytes representation of a big integer. Example: + +```rust +let bytes = a.to_le_bytes(); +``` + +### add + +Add two big integers. Example: + +```rust +let sum = a + b; +``` + +### sub + +Subtract two big integers. Example: + +```rust +let difference = a - b; +``` + +### mul + +Multiply two big integers. Example: + +```rust +let product = a * b; +``` + +### div + +Divide two big integers. Note that division is field division and not euclidean division. Example: + +```rust +let quotient = a / b; +``` + +### eq + +Compare two big integers. Example: + +```rust +let are_equal = a == b; +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/black_box_fns.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/black_box_fns.md new file mode 100644 index 00000000000..d6079ab182c --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/black_box_fns.md @@ -0,0 +1,32 @@ +--- +title: Black Box Functions +description: Black box functions are functions in Noir that rely on backends implementing support for specialized constraints. +keywords: [noir, black box functions] +--- + +Black box functions are functions in Noir that rely on backends implementing support for specialized constraints. This makes certain zk-snark unfriendly computations cheaper than if they were implemented in Noir. + +The ACVM spec defines a set of blackbox functions which backends will be expected to implement. This allows backends to use optimized implementations of these constraints if they have them, however they may also fallback to less efficient naive implementations if not. + +## Function list + +Here is a list of the current black box functions: + +- [AES128](./cryptographic_primitives/ciphers.mdx#aes128) +- [SHA256](./cryptographic_primitives/hashes.mdx#sha256) +- [Schnorr signature verification](./cryptographic_primitives/schnorr.mdx) +- [Blake2s](./cryptographic_primitives/hashes.mdx#blake2s) +- [Blake3](./cryptographic_primitives/hashes.mdx#blake3) +- [Pedersen Hash](./cryptographic_primitives/hashes.mdx#pedersen_hash) +- [Pedersen Commitment](./cryptographic_primitives/hashes.mdx#pedersen_commitment) +- [ECDSA signature verification](./cryptographic_primitives/ecdsa_sig_verification.mdx) +- [Embedded curve operations (MSM, addition, ...)](./cryptographic_primitives/embedded_curve_ops.mdx) +- AND +- XOR +- RANGE +- [Keccak256](./cryptographic_primitives/hashes.mdx#keccak256) +- [Recursive proof verification](./recursion.mdx) + +Most black box functions are included as part of the Noir standard library, however `AND`, `XOR` and `RANGE` are used as part of the Noir language syntax. For instance, using the bitwise operator `&` will invoke the `AND` black box function. + +You can view the black box functions defined in the ACVM code [here](https://github.com/noir-lang/noir/blob/master/acvm-repo/acir/src/circuit/black_box_functions.rs). diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/bn254.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/bn254.md new file mode 100644 index 00000000000..3294f005dbb --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/bn254.md @@ -0,0 +1,46 @@ +--- +title: Bn254 Field Library +--- + +Noir provides a module in standard library with some optimized functions for bn254 Fr in `std::field::bn254`. + +## decompose + +```rust +fn decompose(x: Field) -> (Field, Field) {} +``` + +Decomposes a single field into two fields, low and high. The low field contains the lower 16 bytes of the input field and the high field contains the upper 16 bytes of the input field. Both field results are range checked to 128 bits. + + +## assert_gt + +```rust +fn assert_gt(a: Field, b: Field) {} +``` + +Asserts that a > b. This will generate less constraints than using `assert(gt(a, b))`. + +## assert_lt + +```rust +fn assert_lt(a: Field, b: Field) {} +``` + +Asserts that a < b. This will generate less constraints than using `assert(lt(a, b))`. + +## gt + +```rust +fn gt(a: Field, b: Field) -> bool {} +``` + +Returns true if a > b. + +## lt + +```rust +fn lt(a: Field, b: Field) -> bool {} +``` + +Returns true if a < b. \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/containers/boundedvec.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/containers/boundedvec.md new file mode 100644 index 00000000000..b20b49f863c --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/containers/boundedvec.md @@ -0,0 +1,419 @@ +--- +title: Bounded Vectors +keywords: [noir, vector, bounded vector, slice] +sidebar_position: 1 +--- + +A `BoundedVec` is a growable storage similar to a `Vec` except that it +is bounded with a maximum possible length. Unlike `Vec`, `BoundedVec` is not implemented +via slices and thus is not subject to the same restrictions slices are (notably, nested +slices - and thus nested vectors as well - are disallowed). + +Since a BoundedVec is backed by a normal array under the hood, growing the BoundedVec by +pushing an additional element is also more efficient - the length only needs to be increased +by one. + +For these reasons `BoundedVec` should generally be preferred over `Vec` when there +is a reasonable maximum bound that can be placed on the vector. + +Example: + +```rust +let mut vector: BoundedVec = BoundedVec::new(); +for i in 0..5 { + vector.push(i); +} +assert(vector.len() == 5); +assert(vector.max_len() == 10); +``` + +## Methods + +### new + +```rust +pub fn new() -> Self +``` + +Creates a new, empty vector of length zero. + +Since this container is backed by an array internally, it still needs an initial value +to give each element. To resolve this, each element is zeroed internally. This value +is guaranteed to be inaccessible unless `get_unchecked` is used. + +Example: + +```rust +let empty_vector: BoundedVec = BoundedVec::new(); +assert(empty_vector.len() == 0); +``` + +Note that whenever calling `new` the maximum length of the vector should always be specified +via a type signature: + +```rust title="new_example" showLineNumbers +fn good() -> BoundedVec { + // Ok! MaxLen is specified with a type annotation + let v1: BoundedVec = BoundedVec::new(); + let v2 = BoundedVec::new(); + + // Ok! MaxLen is known from the type of `good`'s return value + v2 +} + +fn bad() { + // Error: Type annotation needed + // The compiler can't infer `MaxLen` from this code. + let mut v3 = BoundedVec::new(); + v3.push(5); +} +``` +> Source code: test_programs/noir_test_success/bounded_vec/src/main.nr#L11-L27 + + +This defaulting of `MaxLen` (and numeric generics in general) to zero may change in future noir versions +but for now make sure to use type annotations when using bounded vectors. Otherwise, you will receive a constraint failure at runtime when the vec is pushed to. + +### get + +```rust +pub fn get(self, index: u64) -> T { +``` + +Retrieves an element from the vector at the given index, starting from zero. + +If the given index is equal to or greater than the length of the vector, this +will issue a constraint failure. + +Example: + +```rust +fn foo(v: BoundedVec) { + let first = v.get(0); + let last = v.get(v.len() - 1); + assert(first != last); +} +``` + +### get_unchecked + +```rust +pub fn get_unchecked(self, index: u64) -> T { +``` + +Retrieves an element from the vector at the given index, starting from zero, without +performing a bounds check. + +Since this function does not perform a bounds check on length before accessing the element, +it is unsafe! Use at your own risk! + +Example: + +```rust title="get_unchecked_example" showLineNumbers +fn sum_of_first_three(v: BoundedVec) -> u32 { + // Always ensure the length is larger than the largest + // index passed to get_unchecked + assert(v.len() > 2); + let first = v.get_unchecked(0); + let second = v.get_unchecked(1); + let third = v.get_unchecked(2); + first + second + third +} +``` +> Source code: test_programs/noir_test_success/bounded_vec/src/main.nr#L54-L64 + + +### set + +```rust +pub fn set(&mut self: Self, index: u64, value: T) { +``` + +Writes an element to the vector at the given index, starting from zero. + +If the given index is equal to or greater than the length of the vector, this will issue a constraint failure. + +Example: + +```rust +fn foo(v: BoundedVec) { + let first = v.get(0); + assert(first != 42); + v.set(0, 42); + let new_first = v.get(0); + assert(new_first == 42); +} +``` + +### set_unchecked + +```rust +pub fn set_unchecked(&mut self: Self, index: u64, value: T) -> T { +``` + +Writes an element to the vector at the given index, starting from zero, without performing a bounds check. + +Since this function does not perform a bounds check on length before accessing the element, it is unsafe! Use at your own risk! + +Example: + +```rust title="set_unchecked_example" showLineNumbers +fn set_unchecked_example() { + let mut vec: BoundedVec = BoundedVec::new(); + vec.extend_from_array([1, 2]); + + // Here we're safely writing within the valid range of `vec` + // `vec` now has the value [42, 2] + vec.set_unchecked(0, 42); + + // We can then safely read this value back out of `vec`. + // Notice that we use the checked version of `get` which would prevent reading unsafe values. + assert_eq(vec.get(0), 42); + + // We've now written past the end of `vec`. + // As this index is still within the maximum potential length of `v`, + // it won't cause a constraint failure. + vec.set_unchecked(2, 42); + println(vec); + + // This will write past the end of the maximum potential length of `vec`, + // it will then trigger a constraint failure. + vec.set_unchecked(5, 42); + println(vec); +} +``` +> Source code: test_programs/noir_test_success/bounded_vec/src/main.nr#L67-L91 + + + +### push + +```rust +pub fn push(&mut self, elem: T) { +``` + +Pushes an element to the end of the vector. This increases the length +of the vector by one. + +Panics if the new length of the vector will be greater than the max length. + +Example: + +```rust title="bounded-vec-push-example" showLineNumbers +let mut v: BoundedVec = BoundedVec::new(); + + v.push(1); + v.push(2); + + // Panics with failed assertion "push out of bounds" + v.push(3); +``` +> Source code: test_programs/noir_test_success/bounded_vec/src/main.nr#L95-L103 + + +### pop + +```rust +pub fn pop(&mut self) -> T +``` + +Pops the element at the end of the vector. This will decrease the length +of the vector by one. + +Panics if the vector is empty. + +Example: + +```rust title="bounded-vec-pop-example" showLineNumbers +let mut v: BoundedVec = BoundedVec::new(); + v.push(1); + v.push(2); + + let two = v.pop(); + let one = v.pop(); + + assert(two == 2); + assert(one == 1); + // error: cannot pop from an empty vector + // let _ = v.pop(); +``` +> Source code: test_programs/noir_test_success/bounded_vec/src/main.nr#L108-L120 + + +### len + +```rust +pub fn len(self) -> u64 { +``` + +Returns the current length of this vector + +Example: + +```rust title="bounded-vec-len-example" showLineNumbers +let mut v: BoundedVec = BoundedVec::new(); + assert(v.len() == 0); + + v.push(100); + assert(v.len() == 1); + + v.push(200); + v.push(300); + v.push(400); + assert(v.len() == 4); + + let _ = v.pop(); + let _ = v.pop(); + assert(v.len() == 2); +``` +> Source code: test_programs/noir_test_success/bounded_vec/src/main.nr#L125-L140 + + +### max_len + +```rust +pub fn max_len(_self: BoundedVec) -> u64 { +``` + +Returns the maximum length of this vector. This is always +equal to the `MaxLen` parameter this vector was initialized with. + +Example: + +```rust title="bounded-vec-max-len-example" showLineNumbers +let mut v: BoundedVec = BoundedVec::new(); + + assert(v.max_len() == 5); + v.push(10); + assert(v.max_len() == 5); +``` +> Source code: test_programs/noir_test_success/bounded_vec/src/main.nr#L145-L151 + + +### storage + +```rust +pub fn storage(self) -> [T; MaxLen] { +``` + +Returns the internal array within this vector. +Since arrays in Noir are immutable, mutating the returned storage array will not mutate +the storage held internally by this vector. + +Note that uninitialized elements may be zeroed out! + +Example: + +```rust title="bounded-vec-storage-example" showLineNumbers +let mut v: BoundedVec = BoundedVec::new(); + + assert(v.storage() == [0, 0, 0, 0, 0]); + + v.push(57); + assert(v.storage() == [57, 0, 0, 0, 0]); +``` +> Source code: test_programs/noir_test_success/bounded_vec/src/main.nr#L156-L163 + + +### extend_from_array + +```rust +pub fn extend_from_array(&mut self, array: [T; Len]) +``` + +Pushes each element from the given array to this vector. + +Panics if pushing each element would cause the length of this vector +to exceed the maximum length. + +Example: + +```rust title="bounded-vec-extend-from-array-example" showLineNumbers +let mut vec: BoundedVec = BoundedVec::new(); + vec.extend_from_array([2, 4]); + + assert(vec.len == 2); + assert(vec.get(0) == 2); + assert(vec.get(1) == 4); +``` +> Source code: test_programs/noir_test_success/bounded_vec/src/main.nr#L168-L175 + + +### extend_from_bounded_vec + +```rust +pub fn extend_from_bounded_vec(&mut self, vec: BoundedVec) +``` + +Pushes each element from the other vector to this vector. The length of +the other vector is left unchanged. + +Panics if pushing each element would cause the length of this vector +to exceed the maximum length. + +Example: + +```rust title="bounded-vec-extend-from-bounded-vec-example" showLineNumbers +let mut v1: BoundedVec = BoundedVec::new(); + let mut v2: BoundedVec = BoundedVec::new(); + + v2.extend_from_array([1, 2, 3]); + v1.extend_from_bounded_vec(v2); + + assert(v1.storage() == [1, 2, 3, 0, 0]); + assert(v2.storage() == [1, 2, 3, 0, 0, 0, 0]); +``` +> Source code: test_programs/noir_test_success/bounded_vec/src/main.nr#L180-L189 + + +### from_array + +```rust +pub fn from_array(array: [T; Len]) -> Self +``` + +Creates a new vector, populating it with values derived from an array input. +The maximum length of the vector is determined based on the type signature. + +Example: +```rust +let bounded_vec: BoundedVec = BoundedVec::from_array([1, 2, 3]) +``` + +### map + +```rust +pub fn map(self, f: fn[Env](T) -> U) -> BoundedVec +``` + +Creates a new vector of equal size by calling a closure on each element in this vector. + +Example: + +```rust title="bounded-vec-map-example" showLineNumbers +let vec: BoundedVec = BoundedVec::from_array([1, 2, 3, 4]); + let result = vec.map(|value| value * 2); +``` +> Source code: noir_stdlib/src/collections/bounded_vec.nr#L493-L496 + + +### any + +```rust +pub fn any(self, predicate: fn[Env](T) -> bool) -> bool +``` + +Returns true if the given predicate returns true for any element +in this vector. + +Example: + +```rust title="bounded-vec-any-example" showLineNumbers +let mut v: BoundedVec = BoundedVec::new(); + v.extend_from_array([2, 4, 6]); + + let all_even = !v.any(|elem: u32| elem % 2 != 0); + assert(all_even); +``` +> Source code: test_programs/noir_test_success/bounded_vec/src/main.nr#L256-L262 + diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/containers/hashmap.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/containers/hashmap.md new file mode 100644 index 00000000000..39f1ae9b32c --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/containers/hashmap.md @@ -0,0 +1,594 @@ +--- +title: HashMap +keywords: [noir, map, hash, hashmap] +sidebar_position: 1 +--- + +`HashMap` is used to efficiently store and look up key-value pairs. + +`HashMap` is a bounded type which can store anywhere from zero to `MaxLen` total elements. +Note that due to hash collisions, the actual maximum number of elements stored by any particular +hashmap is likely lower than `MaxLen`. This is true even with cryptographic hash functions since +every hash value will be performed modulo `MaxLen`. + +Example: + +```rust +// Create a mapping from Fields to u32s with a maximum length of 12 +// using a poseidon2 hasher +use std::hash::poseidon2::Poseidon2Hasher; +let mut map: HashMap> = HashMap::default(); + +map.insert(1, 2); +map.insert(3, 4); + +let two = map.get(1).unwrap(); +``` + +## Methods + +### default + +```rust title="default" showLineNumbers +impl Default for HashMap +where + B: BuildHasher + Default, + H: Hasher + Default { + /// Constructs an empty HashMap. + /// + /// Example: + /// + /// ```noir + /// let hashmap: HashMap> = HashMap::default(); + /// assert(hashmap.is_empty()); + /// ``` + fn default() -> Self { +``` +> Source code: noir_stdlib/src/collections/map.nr#L694-L708 + + +Creates a fresh, empty HashMap. + +When using this function, always make sure to specify the maximum size of the hash map. + +This is the same `default` from the `Default` implementation given further below. It is +repeated here for convenience since it is the recommended way to create a hashmap. + +Example: + +```rust title="default_example" showLineNumbers +let hashmap: HashMap> = HashMap::default(); + assert(hashmap.is_empty()); +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L201-L204 + + +Because `HashMap` has so many generic arguments that are likely to be the same throughout +your program, it may be helpful to create a type alias: + +```rust title="type_alias" showLineNumbers +type MyMap = HashMap>; +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L195-L197 + + +### with_hasher + +```rust title="with_hasher" showLineNumbers +pub fn with_hasher(_build_hasher: B) -> Self + where + B: BuildHasher { +``` +> Source code: noir_stdlib/src/collections/map.nr#L103-L107 + + +Creates a hashmap with an existing `BuildHasher`. This can be used to ensure multiple +hashmaps are created with the same hasher instance. + +Example: + +```rust title="with_hasher_example" showLineNumbers +let my_hasher: BuildHasherDefault = Default::default(); + let hashmap: HashMap> = HashMap::with_hasher(my_hasher); + assert(hashmap.is_empty()); +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L206-L210 + + +### get + +```rust title="get" showLineNumbers +pub fn get( + self, + key: K + ) -> Option + where + K: Eq + Hash, + B: BuildHasher, + H: Hasher { +``` +> Source code: noir_stdlib/src/collections/map.nr#L470-L479 + + +Retrieves a value from the hashmap, returning `Option::none()` if it was not found. + +Example: + +```rust title="get_example" showLineNumbers +fn get_example(map: HashMap>) { + let x = map.get(12); + + if x.is_some() { + assert(x.unwrap() == 42); + } +} +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L298-L306 + + +### insert + +```rust title="insert" showLineNumbers +pub fn insert( + &mut self, + key: K, + value: V + ) + where + K: Eq + Hash, + B: BuildHasher, + H: Hasher { +``` +> Source code: noir_stdlib/src/collections/map.nr#L514-L524 + + +Inserts a new key-value pair into the map. If the key was already in the map, its +previous value will be overridden with the newly provided one. + +Example: + +```rust title="insert_example" showLineNumbers +let mut map: HashMap> = HashMap::default(); + map.insert(12, 42); + assert(map.len() == 1); +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L212-L216 + + +### remove + +```rust title="remove" showLineNumbers +pub fn remove( + &mut self, + key: K + ) + where + K: Eq + Hash, + B: BuildHasher, + H: Hasher { +``` +> Source code: noir_stdlib/src/collections/map.nr#L573-L582 + + +Removes the given key-value pair from the map. If the key was not already present +in the map, this does nothing. + +Example: + +```rust title="remove_example" showLineNumbers +map.remove(12); + assert(map.is_empty()); + + // If a key was not present in the map, remove does nothing + map.remove(12); + assert(map.is_empty()); +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L220-L227 + + +### is_empty + +```rust title="is_empty" showLineNumbers +pub fn is_empty(self) -> bool { +``` +> Source code: noir_stdlib/src/collections/map.nr#L168-L170 + + +True if the length of the hash map is empty. + +Example: + +```rust title="is_empty_example" showLineNumbers +assert(map.is_empty()); + + map.insert(1, 2); + assert(!map.is_empty()); + + map.remove(1); + assert(map.is_empty()); +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L229-L237 + + +### len + +```rust title="len" showLineNumbers +pub fn len(self) -> u32 { +``` +> Source code: noir_stdlib/src/collections/map.nr#L429-L431 + + +Returns the current length of this hash map. + +Example: + +```rust title="len_example" showLineNumbers +// This is equivalent to checking map.is_empty() + assert(map.len() == 0); + + map.insert(1, 2); + map.insert(3, 4); + map.insert(5, 6); + assert(map.len() == 3); + + // 3 was already present as a key in the hash map, so the length is unchanged + map.insert(3, 7); + assert(map.len() == 3); + + map.remove(1); + assert(map.len() == 2); +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L239-L254 + + +### capacity + +```rust title="capacity" showLineNumbers +pub fn capacity(_self: Self) -> u32 { +``` +> Source code: noir_stdlib/src/collections/map.nr#L451-L453 + + +Returns the maximum capacity of this hashmap. This is always equal to the capacity +specified in the hashmap's type. + +Unlike hashmaps in general purpose programming languages, hashmaps in Noir have a +static capacity that does not increase as the map grows larger. Thus, this capacity +is also the maximum possible element count that can be inserted into the hashmap. +Due to hash collisions (modulo the hashmap length), it is likely the actual maximum +element count will be lower than the full capacity. + +Example: + +```rust title="capacity_example" showLineNumbers +let empty_map: HashMap> = HashMap::default(); + assert(empty_map.len() == 0); + assert(empty_map.capacity() == 42); +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L256-L260 + + +### clear + +```rust title="clear" showLineNumbers +pub fn clear(&mut self) { +``` +> Source code: noir_stdlib/src/collections/map.nr#L122-L124 + + +Clears the hashmap, removing all key-value pairs from it. + +Example: + +```rust title="clear_example" showLineNumbers +assert(!map.is_empty()); + map.clear(); + assert(map.is_empty()); +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L262-L266 + + +### contains_key + +```rust title="contains_key" showLineNumbers +pub fn contains_key( + self, + key: K + ) -> bool + where + K: Hash + Eq, + B: BuildHasher, + H: Hasher { +``` +> Source code: noir_stdlib/src/collections/map.nr#L142-L151 + + +True if the hashmap contains the given key. Unlike `get`, this will not also return +the value associated with the key. + +Example: + +```rust title="contains_key_example" showLineNumbers +if map.contains_key(7) { + let value = map.get(7); + assert(value.is_some()); + } else { + println("No value for key 7!"); + } +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L268-L275 + + +### entries + +```rust title="entries" showLineNumbers +pub fn entries(self) -> BoundedVec<(K, V), N> { +``` +> Source code: noir_stdlib/src/collections/map.nr#L192-L194 + + +Returns a vector of each key-value pair present in the hashmap. + +The length of the returned vector is always equal to the length of the hashmap. + +Example: + +```rust title="entries_example" showLineNumbers +let entries = map.entries(); + + // The length of a hashmap may not be compile-time known, so we + // need to loop over its capacity instead + for i in 0..map.capacity() { + if i < entries.len() { + let (key, value) = entries.get(i); + println(f"{key} -> {value}"); + } + } +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L309-L320 + + +### keys + +```rust title="keys" showLineNumbers +pub fn keys(self) -> BoundedVec { +``` +> Source code: noir_stdlib/src/collections/map.nr#L228-L230 + + +Returns a vector of each key present in the hashmap. + +The length of the returned vector is always equal to the length of the hashmap. + +Example: + +```rust title="keys_example" showLineNumbers +let keys = map.keys(); + + for i in 0..keys.max_len() { + if i < keys.len() { + let key = keys.get_unchecked(i); + let value = map.get(key).unwrap_unchecked(); + println(f"{key} -> {value}"); + } + } +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L322-L332 + + +### values + +```rust title="values" showLineNumbers +pub fn values(self) -> BoundedVec { +``` +> Source code: noir_stdlib/src/collections/map.nr#L262-L264 + + +Returns a vector of each value present in the hashmap. + +The length of the returned vector is always equal to the length of the hashmap. + +Example: + +```rust title="values_example" showLineNumbers +let values = map.values(); + + for i in 0..values.max_len() { + if i < values.len() { + let value = values.get_unchecked(i); + println(f"Found value {value}"); + } + } +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L334-L343 + + +### iter_mut + +```rust title="iter_mut" showLineNumbers +pub fn iter_mut( + &mut self, + f: fn(K, V) -> (K, V) + ) + where + K: Eq + Hash, + B: BuildHasher, + H: Hasher { +``` +> Source code: noir_stdlib/src/collections/map.nr#L298-L307 + + +Iterates through each key-value pair of the HashMap, setting each key-value pair to the +result returned from the given function. + +Note that since keys can be mutated, the HashMap needs to be rebuilt as it is iterated +through. If this is not desired, use `iter_values_mut` if only values need to be mutated, +or `entries` if neither keys nor values need to be mutated. + +The iteration order is left unspecified. As a result, if two keys are mutated to become +equal, which of the two values that will be present for the key in the resulting map is also unspecified. + +Example: + +```rust title="iter_mut_example" showLineNumbers +// Add 1 to each key in the map, and double the value associated with that key. + map.iter_mut(|k, v| (k + 1, v * 2)); +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L347-L350 + + +### iter_keys_mut + +```rust title="iter_keys_mut" showLineNumbers +pub fn iter_keys_mut( + &mut self, + f: fn(K) -> K + ) + where + K: Eq + Hash, + B: BuildHasher, + H: Hasher { +``` +> Source code: noir_stdlib/src/collections/map.nr#L338-L347 + + +Iterates through the HashMap, mutating each key to the result returned from +the given function. + +Note that since keys can be mutated, the HashMap needs to be rebuilt as it is iterated +through. If only iteration is desired and the keys are not intended to be mutated, +prefer using `entries` instead. + +The iteration order is left unspecified. As a result, if two keys are mutated to become +equal, which of the two values that will be present for the key in the resulting map is also unspecified. + +Example: + +```rust title="iter_keys_mut_example" showLineNumbers +// Double each key, leaving the value associated with that key untouched + map.iter_keys_mut(|k| k * 2); +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L352-L355 + + +### iter_values_mut + +```rust title="iter_values_mut" showLineNumbers +pub fn iter_values_mut(&mut self, f: fn(V) -> V) { +``` +> Source code: noir_stdlib/src/collections/map.nr#L372-L374 + + +Iterates through the HashMap, applying the given function to each value and mutating the +value to equal the result. This function is more efficient than `iter_mut` and `iter_keys_mut` +because the keys are untouched and the underlying hashmap thus does not need to be reordered. + +Example: + +```rust title="iter_values_mut_example" showLineNumbers +// Halve each value + map.iter_values_mut(|v| v / 2); +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L357-L360 + + +### retain + +```rust title="retain" showLineNumbers +pub fn retain(&mut self, f: fn(K, V) -> bool) { +``` +> Source code: noir_stdlib/src/collections/map.nr#L393-L395 + + +Retains only the key-value pairs for which the given function returns true. +Any key-value pairs for which the function returns false will be removed from the map. + +Example: + +```rust title="retain_example" showLineNumbers +map.retain(|k, v| (k != 0) & (v != 0)); +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L280-L282 + + +## Trait Implementations + +### default + +```rust title="default" showLineNumbers +impl Default for HashMap +where + B: BuildHasher + Default, + H: Hasher + Default { + /// Constructs an empty HashMap. + /// + /// Example: + /// + /// ```noir + /// let hashmap: HashMap> = HashMap::default(); + /// assert(hashmap.is_empty()); + /// ``` + fn default() -> Self { +``` +> Source code: noir_stdlib/src/collections/map.nr#L694-L708 + + +Constructs an empty HashMap. + +Example: + +```rust title="default_example" showLineNumbers +let hashmap: HashMap> = HashMap::default(); + assert(hashmap.is_empty()); +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L201-L204 + + +### eq + +```rust title="eq" showLineNumbers +impl Eq for HashMap +where + K: Eq + Hash, + V: Eq, + B: BuildHasher, + H: Hasher { + /// Checks if two HashMaps are equal. + /// + /// Example: + /// + /// ```noir + /// let mut map1: HashMap> = HashMap::default(); + /// let mut map2: HashMap> = HashMap::default(); + /// + /// map1.insert(1, 2); + /// map1.insert(3, 4); + /// + /// map2.insert(3, 4); + /// map2.insert(1, 2); + /// + /// assert(map1 == map2); + /// ``` + fn eq(self, other: HashMap) -> bool { +``` +> Source code: noir_stdlib/src/collections/map.nr#L643-L667 + + +Checks if two HashMaps are equal. + +Example: + +```rust title="eq_example" showLineNumbers +let mut map1: HashMap> = HashMap::default(); + let mut map2: HashMap> = HashMap::default(); + + map1.insert(1, 2); + map1.insert(3, 4); + + map2.insert(3, 4); + map2.insert(1, 2); + + assert(map1 == map2); +``` +> Source code: test_programs/execution_success/hashmap/src/main.nr#L284-L295 + diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/containers/index.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/containers/index.md new file mode 100644 index 00000000000..ea84c6d5c21 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/containers/index.md @@ -0,0 +1,5 @@ +--- +title: Containers +description: Container types provided by Noir's standard library for storing and retrieving data +keywords: [containers, data types, vec, hashmap] +--- diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/containers/vec.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/containers/vec.mdx new file mode 100644 index 00000000000..475011922f8 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/containers/vec.mdx @@ -0,0 +1,170 @@ +--- +title: Vectors +description: Delve into the Vec data type in Noir. Learn about its methods, practical examples, and best practices for using Vectors in your Noir code. +keywords: [noir, vector type, methods, examples, dynamic arrays] +sidebar_position: 6 +--- + +import Experimental from '@site/src/components/Notes/_experimental.mdx'; + + + +A vector is a collection type similar to Rust's `Vec` type. In Noir, it is a convenient way to use slices as mutable arrays. + +Example: + +```rust +let mut vector: Vec = Vec::new(); +for i in 0..5 { + vector.push(i); +} +assert(vector.len() == 5); +``` + +## Methods + +### new + +Creates a new, empty vector. + +```rust +pub fn new() -> Self +``` + +Example: + +```rust +let empty_vector: Vec = Vec::new(); +assert(empty_vector.len() == 0); +``` + +### from_slice + +Creates a vector containing each element from a given slice. Mutations to the resulting vector will not affect the original slice. + +```rust +pub fn from_slice(slice: [T]) -> Self +``` + +Example: + +```rust +let slice: [Field] = &[1, 2, 3]; +let vector_from_slice = Vec::from_slice(slice); +assert(vector_from_slice.len() == 3); +``` + +### len + +Returns the number of elements in the vector. + +```rust +pub fn len(self) -> Field +``` + +Example: + +```rust +let empty_vector: Vec = Vec::new(); +assert(empty_vector.len() == 0); +``` + +### get + +Retrieves an element from the vector at a given index. Panics if the index points beyond the vector's end. + +```rust +pub fn get(self, index: Field) -> T +``` + +Example: + +```rust +let vector: Vec = Vec::from_slice(&[10, 20, 30]); +assert(vector.get(1) == 20); +``` + +### set + +```rust +pub fn set(&mut self: Self, index: u64, value: T) { +``` + +Writes an element to the vector at the given index, starting from zero. + +Panics if the index points beyond the vector's end. + +Example: + +```rust +let vector: Vec = Vec::from_slice(&[10, 20, 30]); +assert(vector.get(1) == 20); +vector.set(1, 42); +assert(vector.get(1) == 42); +``` + +### push + +Adds a new element to the vector's end, returning a new vector with a length one greater than the original unmodified vector. + +```rust +pub fn push(&mut self, elem: T) +``` + +Example: + +```rust +let mut vector: Vec = Vec::new(); +vector.push(10); +assert(vector.len() == 1); +``` + +### pop + +Removes an element from the vector's end, returning a new vector with a length one less than the original vector, along with the removed element. Panics if the vector's length is zero. + +```rust +pub fn pop(&mut self) -> T +``` + +Example: + +```rust +let mut vector = Vec::from_slice(&[10, 20]); +let popped_elem = vector.pop(); +assert(popped_elem == 20); +assert(vector.len() == 1); +``` + +### insert + +Inserts an element at a specified index, shifting subsequent elements to the right. + +```rust +pub fn insert(&mut self, index: Field, elem: T) +``` + +Example: + +```rust +let mut vector = Vec::from_slice(&[10, 30]); +vector.insert(1, 20); +assert(vector.get(1) == 20); +``` + +### remove + +Removes an element at a specified index, shifting subsequent elements to the left, and returns the removed element. + +```rust +pub fn remove(&mut self, index: Field) -> T +``` + +Example: + +```rust +let mut vector = Vec::from_slice(&[10, 20, 30]); +let removed_elem = vector.remove(1); +assert(removed_elem == 20); +assert(vector.len() == 2); +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/cryptographic_primitives/_category_.json b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/cryptographic_primitives/_category_.json new file mode 100644 index 00000000000..5d694210bbf --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/cryptographic_primitives/_category_.json @@ -0,0 +1,5 @@ +{ + "position": 0, + "collapsible": true, + "collapsed": true +} diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/cryptographic_primitives/ciphers.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/cryptographic_primitives/ciphers.mdx new file mode 100644 index 00000000000..d6a5e1a79eb --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/cryptographic_primitives/ciphers.mdx @@ -0,0 +1,32 @@ +--- +title: Ciphers +description: + Learn about the implemented ciphers ready to use for any Noir project +keywords: + [ciphers, Noir project, aes128, encrypt] +sidebar_position: 0 +--- + +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; + +## aes128 + +Given a plaintext as an array of bytes, returns the corresponding aes128 ciphertext (CBC mode). Input padding is automatically performed using PKCS#7, so that the output length is `input.len() + (16 - input.len() % 16)`. + +```rust title="aes128" showLineNumbers +pub fn aes128_encrypt(input: [u8; N], iv: [u8; 16], key: [u8; 16]) -> [u8] {} +``` +> Source code: noir_stdlib/src/aes128.nr#L2-L4 + + +```rust +fn main() { + let input: [u8; 4] = [0, 12, 3, 15] // Random bytes, will be padded to 16 bytes. + let iv: [u8; 16] = [0; 16]; // Initialisation vector + let key: [u8; 16] = [0; 16] // AES key + let ciphertext = std::aes128::aes128_encrypt(inputs.as_bytes(), iv.as_bytes(), key.as_bytes()); // In this case, the output length will be 16 bytes. +} +``` + + + \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/cryptographic_primitives/ec_primitives.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/cryptographic_primitives/ec_primitives.md new file mode 100644 index 00000000000..f262d8160d6 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/cryptographic_primitives/ec_primitives.md @@ -0,0 +1,102 @@ +--- +title: Elliptic Curve Primitives +keywords: [cryptographic primitives, Noir project] +sidebar_position: 4 +--- + +Data structures and methods on them that allow you to carry out computations involving elliptic +curves over the (mathematical) field corresponding to `Field`. For the field currently at our +disposal, applications would involve a curve embedded in BN254, e.g. the +[Baby Jubjub curve](https://eips.ethereum.org/EIPS/eip-2494). + +## Data structures + +### Elliptic curve configurations + +(`std::ec::{tecurve,montcurve,swcurve}::{affine,curvegroup}::Curve`), i.e. the specific elliptic +curve you want to use, which would be specified using any one of the methods +`std::ec::{tecurve,montcurve,swcurve}::{affine,curvegroup}::new` which take the coefficients in the +defining equation together with a generator point as parameters. You can find more detail in the +comments in +[`noir_stdlib/src/ec/mod.nr`](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec/mod.nr), but +the gist of it is that the elliptic curves of interest are usually expressed in one of the standard +forms implemented here (Twisted Edwards, Montgomery and Short Weierstraß), and in addition to that, +you could choose to use `affine` coordinates (Cartesian coordinates - the usual (x,y) - possibly +together with a point at infinity) or `curvegroup` coordinates (some form of projective coordinates +requiring more coordinates but allowing for more efficient implementations of elliptic curve +operations). Conversions between all of these forms are provided, and under the hood these +conversions are done whenever an operation is more efficient in a different representation (or a +mixed coordinate representation is employed). + +### Points + +(`std::ec::{tecurve,montcurve,swcurve}::{affine,curvegroup}::Point`), i.e. points lying on the +elliptic curve. For a curve configuration `c` and a point `p`, it may be checked that `p` +does indeed lie on `c` by calling `c.contains(p1)`. + +## Methods + +(given a choice of curve representation, e.g. use `std::ec::tecurve::affine::Curve` and use +`std::ec::tecurve::affine::Point`) + +- The **zero element** is given by `Point::zero()`, and we can verify whether a point `p: Point` is + zero by calling `p.is_zero()`. +- **Equality**: Points `p1: Point` and `p2: Point` may be checked for equality by calling + `p1.eq(p2)`. +- **Addition**: For `c: Curve` and points `p1: Point` and `p2: Point` on the curve, adding these two + points is accomplished by calling `c.add(p1,p2)`. +- **Negation**: For a point `p: Point`, `p.negate()` is its negation. +- **Subtraction**: For `c` and `p1`, `p2` as above, subtracting `p2` from `p1` is accomplished by + calling `c.subtract(p1,p2)`. +- **Scalar multiplication**: For `c` as above, `p: Point` a point on the curve and `n: Field`, + scalar multiplication is given by `c.mul(n,p)`. If instead `n :: [u1; N]`, i.e. `n` is a bit + array, the `bit_mul` method may be used instead: `c.bit_mul(n,p)` +- **Multi-scalar multiplication**: For `c` as above and arrays `n: [Field; N]` and `p: [Point; N]`, + multi-scalar multiplication is given by `c.msm(n,p)`. +- **Coordinate representation conversions**: The `into_group` method converts a point or curve + configuration in the affine representation to one in the CurveGroup representation, and + `into_affine` goes in the other direction. +- **Curve representation conversions**: `tecurve` and `montcurve` curves and points are equivalent + and may be converted between one another by calling `into_montcurve` or `into_tecurve` on their + configurations or points. `swcurve` is more general and a curve c of one of the other two types + may be converted to this representation by calling `c.into_swcurve()`, whereas a point `p` lying + on the curve given by `c` may be mapped to its corresponding `swcurve` point by calling + `c.map_into_swcurve(p)`. +- **Map-to-curve methods**: The Elligator 2 method of mapping a field element `n: Field` into a + `tecurve` or `montcurve` with configuration `c` may be called as `c.elligator2_map(n)`. For all of + the curve configurations, the SWU map-to-curve method may be called as `c.swu_map(z,n)`, where + `z: Field` depends on `Field` and `c` and must be chosen by the user (the conditions it needs to + satisfy are specified in the comments + [here](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec/mod.nr)). + +## Examples + +The +[ec_baby_jubjub test](https://github.com/noir-lang/noir/blob/master/test_programs/compile_success_empty/ec_baby_jubjub/src/main.nr) +illustrates all of the above primitives on various forms of the Baby Jubjub curve. A couple of more +interesting examples in Noir would be: + +Public-key cryptography: Given an elliptic curve and a 'base point' on it, determine the public key +from the private key. This is a matter of using scalar multiplication. In the case of Baby Jubjub, +for example, this code would do: + +```rust +use std::ec::tecurve::affine::{Curve, Point}; + +fn bjj_pub_key(priv_key: Field) -> Point +{ + + let bjj = Curve::new(168700, 168696, G::new(995203441582195749578291179787384436505546430278305826713579947235728471134,5472060717959818805561601436314318772137091100104008585924551046643952123905)); + + let base_pt = Point::new(5299619240641551281634865583518297030282874472190772894086521144482721001553, 16950150798460657717958625567821834550301663161624707787222815936182638968203); + + bjj.mul(priv_key,base_pt) +} +``` + +This would come in handy in a Merkle proof. + +- EdDSA signature verification: This is a matter of combining these primitives with a suitable hash + function. See + [feat(stdlib): EdDSA sig verification noir#1136](https://github.com/noir-lang/noir/pull/1136) for + the case of Baby Jubjub and the Poseidon hash function. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx new file mode 100644 index 00000000000..12bf5ae8aaf --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx @@ -0,0 +1,98 @@ +--- +title: ECDSA Signature Verification +description: Learn about the cryptographic primitives regarding ECDSA over the secp256k1 and secp256r1 curves +keywords: [cryptographic primitives, Noir project, ecdsa, secp256k1, secp256r1, signatures] +sidebar_position: 3 +--- + +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; + +Noir supports ECDSA signatures verification over the secp256k1 and secp256r1 curves. + +## ecdsa_secp256k1::verify_signature + +Verifier for ECDSA Secp256k1 signatures. +See ecdsa_secp256k1::verify_signature_slice for a version that accepts slices directly. + +```rust title="ecdsa_secp256k1" showLineNumbers +pub fn verify_signature( + public_key_x: [u8; 32], + public_key_y: [u8; 32], + signature: [u8; 64], + message_hash: [u8; N] +) -> bool +``` +> Source code: noir_stdlib/src/ecdsa_secp256k1.nr#L2-L9 + + +example: + +```rust +fn main(hashed_message : [u8;32], pub_key_x : [u8;32], pub_key_y : [u8;32], signature : [u8;64]) { + let valid_signature = std::ecdsa_secp256k1::verify_signature(pub_key_x, pub_key_y, signature, hashed_message); + assert(valid_signature); +} +``` + + + +## ecdsa_secp256k1::verify_signature_slice + +Verifier for ECDSA Secp256k1 signatures where the message is a slice. + +```rust title="ecdsa_secp256k1_slice" showLineNumbers +pub fn verify_signature_slice( + public_key_x: [u8; 32], + public_key_y: [u8; 32], + signature: [u8; 64], + message_hash: [u8] +) -> bool +``` +> Source code: noir_stdlib/src/ecdsa_secp256k1.nr#L13-L20 + + + + +## ecdsa_secp256r1::verify_signature + +Verifier for ECDSA Secp256r1 signatures. +See ecdsa_secp256r1::verify_signature_slice for a version that accepts slices directly. + +```rust title="ecdsa_secp256r1" showLineNumbers +pub fn verify_signature( + public_key_x: [u8; 32], + public_key_y: [u8; 32], + signature: [u8; 64], + message_hash: [u8; N] +) -> bool +``` +> Source code: noir_stdlib/src/ecdsa_secp256r1.nr#L2-L9 + + +example: + +```rust +fn main(hashed_message : [u8;32], pub_key_x : [u8;32], pub_key_y : [u8;32], signature : [u8;64]) { + let valid_signature = std::ecdsa_secp256r1::verify_signature(pub_key_x, pub_key_y, signature, hashed_message); + assert(valid_signature); +} +``` + + + +## ecdsa_secp256r1::verify_signature + +Verifier for ECDSA Secp256r1 signatures where the message is a slice. + +```rust title="ecdsa_secp256r1_slice" showLineNumbers +pub fn verify_signature_slice( + public_key_x: [u8; 32], + public_key_y: [u8; 32], + signature: [u8; 64], + message_hash: [u8] +) -> bool +``` +> Source code: noir_stdlib/src/ecdsa_secp256r1.nr#L13-L20 + + + diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/cryptographic_primitives/eddsa.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/cryptographic_primitives/eddsa.mdx new file mode 100644 index 00000000000..b283de693c8 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/cryptographic_primitives/eddsa.mdx @@ -0,0 +1,37 @@ +--- +title: EdDSA Verification +description: Learn about the cryptographic primitives regarding EdDSA +keywords: [cryptographic primitives, Noir project, eddsa, signatures] +sidebar_position: 5 +--- + +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; + +## eddsa::eddsa_poseidon_verify + +Verifier for EdDSA signatures + +```rust +fn eddsa_poseidon_verify(public_key_x : Field, public_key_y : Field, signature_s: Field, signature_r8_x: Field, signature_r8_y: Field, message: Field) -> bool +``` + +It is also possible to specify the hash algorithm used for the signature by using the `eddsa_verify` function by passing a type implementing the Hasher trait with the turbofish operator. +For instance, if you want to use Poseidon2 instead, you can do the following: +```rust +use std::hash::poseidon2::Poseidon2Hasher; + +eddsa_verify::(pub_key_a.x, pub_key_a.y, s_a, r8_a.x, r8_a.y, msg); +``` + + + +## eddsa::eddsa_to_pub + +Private to public key conversion. + +Returns `(pub_key_x, pub_key_y)` + +```rust +fn eddsa_to_pub(secret : Field) -> (Field, Field) +``` + diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx new file mode 100644 index 00000000000..9507f16322c --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx @@ -0,0 +1,92 @@ +--- +title: Scalar multiplication +description: See how you can perform scalar multiplication in Noir +keywords: [cryptographic primitives, Noir project, scalar multiplication] +sidebar_position: 1 +--- + +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; + +The following functions perform operations over the embedded curve whose coordinates are defined by the configured noir field. +For the BN254 scalar field, this is BabyJubJub or Grumpkin. + +:::note +Suffixes `_low` and `_high` denote low and high limbs of a scalar. +::: + +## embedded_curve_ops::multi_scalar_mul + +Performs multi scalar multiplication over the embedded curve. +The function accepts arbitrary amount of point-scalar pairs on the input, it multiplies the individual pairs over +the curve and returns a sum of the resulting points. + +Points represented as x and y coordinates [x1, y1, x2, y2, ...], scalars as low and high limbs [low1, high1, low2, high2, ...]. + +```rust title="multi_scalar_mul" showLineNumbers +pub fn multi_scalar_mul( + points: [EmbeddedCurvePoint; N], + scalars: [EmbeddedCurveScalar; N] +) -> EmbeddedCurvePoint +``` +> Source code: noir_stdlib/src/embedded_curve_ops.nr#L103-L108 + + +example + +```rust +fn main(point_x: Field, point_y: Field, scalar_low: Field, scalar_high: Field) { + let point = std::embedded_curve_ops::multi_scalar_mul([point_x, point_y], [scalar_low, scalar_high]); + println(point); +} +``` + +## embedded_curve_ops::fixed_base_scalar_mul + +Performs fixed base scalar multiplication over the embedded curve (multiplies input scalar with a generator point). +The function accepts a single scalar on the input represented as 2 fields. + +```rust title="fixed_base_scalar_mul" showLineNumbers +pub fn fixed_base_scalar_mul(scalar: EmbeddedCurveScalar) -> EmbeddedCurvePoint +``` +> Source code: noir_stdlib/src/embedded_curve_ops.nr#L120-L122 + + +example + +```rust +fn main(scalar_low: Field, scalar_high: Field) { + let point = std::embedded_curve_ops::fixed_base_scalar_mul(scalar_low, scalar_high); + println(point); +} +``` + +## embedded_curve_ops::embedded_curve_add + +Adds two points on the embedded curve. +This function takes two `EmbeddedCurvePoint` structures as parameters, representing points on the curve, and returns a new `EmbeddedCurvePoint` structure that represents their sum. + +### Parameters: +- `point1` (`EmbeddedCurvePoint`): The first point to add. +- `point2` (`EmbeddedCurvePoint`): The second point to add. + +### Returns: +- `EmbeddedCurvePoint`: The resulting point after the addition of `point1` and `point2`. + +```rust title="embedded_curve_add" showLineNumbers +pub fn embedded_curve_add(point1: EmbeddedCurvePoint, point2: EmbeddedCurvePoint) -> EmbeddedCurvePoint { +``` +> Source code: noir_stdlib/src/embedded_curve_ops.nr#L132-L134 + + +example + +```rust +fn main() { + let point1 = EmbeddedCurvePoint { x: 1, y: 2 }; + let point2 = EmbeddedCurvePoint { x: 3, y: 4 }; + let result = std::embedded_curve_ops::embedded_curve_add(point1, point2); + println!("Resulting Point: ({}, {})", result.x, result.y); +} +``` + + diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/cryptographic_primitives/hashes.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/cryptographic_primitives/hashes.mdx new file mode 100644 index 00000000000..33601bae22b --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/cryptographic_primitives/hashes.mdx @@ -0,0 +1,253 @@ +--- +title: Hash methods +description: + Learn about the cryptographic primitives ready to use for any Noir project, including sha256, + blake2s, pedersen, mimc_bn254 and mimc +keywords: + [cryptographic primitives, Noir project, sha256, blake2s, pedersen, mimc_bn254, mimc, hash] +sidebar_position: 0 +--- + +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; + +## sha256 + +Given an array of bytes, returns the resulting sha256 hash. +Specify a message_size to hash only the first `message_size` bytes of the input. + +```rust title="sha256" showLineNumbers +pub fn sha256(input: [u8; N]) -> [u8; 32] +``` +> Source code: noir_stdlib/src/hash/sha256.nr#L7-L9 + + +example: +```rust title="sha256_var" showLineNumbers +let digest = std::hash::sha256_var([x as u8], 1); +``` +> Source code: test_programs/execution_success/sha256/src/main.nr#L16-L18 + + +```rust +fn main() { + let x = [163, 117, 178, 149]; // some random bytes + let hash = std::sha256::sha256_var(x, 4); +} +``` + + + + +## blake2s + +Given an array of bytes, returns an array with the Blake2 hash + +```rust title="blake2s" showLineNumbers +pub fn blake2s(input: [u8; N]) -> [u8; 32] +``` +> Source code: noir_stdlib/src/hash/mod.nr#L18-L20 + + +example: + +```rust +fn main() { + let x = [163, 117, 178, 149]; // some random bytes + let hash = std::hash::blake2s(x); +} +``` + + + +## blake3 + +Given an array of bytes, returns an array with the Blake3 hash + +```rust title="blake3" showLineNumbers +pub fn blake3(input: [u8; N]) -> [u8; 32] +``` +> Source code: noir_stdlib/src/hash/mod.nr#L24-L26 + + +example: + +```rust +fn main() { + let x = [163, 117, 178, 149]; // some random bytes + let hash = std::hash::blake3(x); +} +``` + + + +## pedersen_hash + +Given an array of Fields, returns the Pedersen hash. + +```rust title="pedersen_hash" showLineNumbers +pub fn pedersen_hash(input: [Field; N]) -> Field +``` +> Source code: noir_stdlib/src/hash/mod.nr#L77-L79 + + +example: + +```rust title="pedersen-hash" showLineNumbers +fn main(x: Field, y: Field, expected_hash: Field) { + let hash = std::hash::pedersen_hash([x, y]); + assert_eq(hash, expected_hash); +} +``` +> Source code: test_programs/execution_success/pedersen_hash/src/main.nr#L1-L7 + + + + +## pedersen_commitment + +Given an array of Fields, returns the Pedersen commitment. + +```rust title="pedersen_commitment" showLineNumbers +pub fn pedersen_commitment(input: [Field; N]) -> EmbeddedCurvePoint { +``` +> Source code: noir_stdlib/src/hash/mod.nr#L29-L31 + + +example: + +```rust title="pedersen-commitment" showLineNumbers +fn main(x: Field, y: Field, expected_commitment: std::embedded_curve_ops::EmbeddedCurvePoint) { + let commitment = std::hash::pedersen_commitment([x, y]); + assert_eq(commitment.x, expected_commitment.x); + assert_eq(commitment.y, expected_commitment.y); +} +``` +> Source code: test_programs/execution_success/pedersen_commitment/src/main.nr#L1-L8 + + + + +## keccak256 + +Given an array of bytes (`u8`), returns the resulting keccak hash as an array of +32 bytes (`[u8; 32]`). Specify a message_size to hash only the first +`message_size` bytes of the input. + +```rust title="keccak256" showLineNumbers +pub fn keccak256(input: [u8; N], message_size: u32) -> [u8; 32] +``` +> Source code: noir_stdlib/src/hash/mod.nr#L125-L127 + + +example: + +```rust title="keccak256" showLineNumbers +fn main(x: Field, result: [u8; 32]) { + // We use the `as` keyword here to denote the fact that we want to take just the first byte from the x Field + // The padding is taken care of by the program + let digest = std::hash::keccak256([x as u8], 1); + assert(digest == result); + + //#1399: variable message size + let message_size = 4; + let hash_a = std::hash::keccak256([1, 2, 3, 4], message_size); + let hash_b = std::hash::keccak256([1, 2, 3, 4, 0, 0, 0, 0], message_size); + + assert(hash_a == hash_b); + + let message_size_big = 8; + let hash_c = std::hash::keccak256([1, 2, 3, 4, 0, 0, 0, 0], message_size_big); + + assert(hash_a != hash_c); +} +``` +> Source code: test_programs/execution_success/keccak256/src/main.nr#L1-L21 + + + + +## poseidon + +Given an array of Fields, returns a new Field with the Poseidon Hash. Mind that you need to specify +how many inputs are there to your Poseidon function. + +```rust +// example for hash_1, hash_2 accepts an array of length 2, etc +fn hash_1(input: [Field; 1]) -> Field +``` + +example: + +```rust title="poseidon" showLineNumbers +use std::hash::poseidon; + +fn main(x1: [Field; 2], y1: pub Field, x2: [Field; 4], y2: pub Field) { + let hash1 = poseidon::bn254::hash_2(x1); + assert(hash1 == y1); + + let hash2 = poseidon::bn254::hash_4(x2); + assert(hash2 == y2); +} +``` +> Source code: test_programs/execution_success/poseidon_bn254_hash/src/main.nr#L1-L11 + + +## poseidon 2 + +Given an array of Fields, returns a new Field with the Poseidon2 Hash. Contrary to the Poseidon +function, there is only one hash and you can specify a message_size to hash only the first +`message_size` bytes of the input, + +```rust +// example for hashing the first three elements of the input +Poseidon2::hash(input, 3); +``` + +example: + +```rust title="poseidon2" showLineNumbers +use std::hash::poseidon2; + +fn main(inputs: [Field; 4], expected_hash: Field) { + let hash = poseidon2::Poseidon2::hash(inputs, inputs.len()); + assert_eq(hash, expected_hash); +} +``` +> Source code: test_programs/execution_success/poseidon2/src/main.nr#L1-L8 + + +## mimc_bn254 and mimc + +`mimc_bn254` is `mimc`, but with hardcoded parameters for the BN254 curve. You can use it by +providing an array of Fields, and it returns a Field with the hash. You can use the `mimc` method if +you're willing to input your own constants: + +```rust +fn mimc(x: Field, k: Field, constants: [Field; N], exp : Field) -> Field +``` + +otherwise, use the `mimc_bn254` method: + +```rust +fn mimc_bn254(array: [Field; N]) -> Field +``` + +example: + +```rust + +fn main() { + let x = [163, 117, 178, 149]; // some random bytes + let hash = std::hash::mimc::mimc_bn254(x); +} +``` + +## hash_to_field + +```rust +fn hash_to_field(_input : [Field]) -> Field {} +``` + +Calculates the `blake2s` hash of the inputs and returns the hash modulo the field modulus to return +a value which can be represented as a `Field`. + diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/cryptographic_primitives/index.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/cryptographic_primitives/index.md new file mode 100644 index 00000000000..650f30165d5 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/cryptographic_primitives/index.md @@ -0,0 +1,14 @@ +--- +title: Cryptographic Primitives +description: + Learn about the cryptographic primitives ready to use for any Noir project +keywords: + [ + cryptographic primitives, + Noir project, + ] +--- + +The Noir team is progressively adding new cryptographic primitives to the standard library. Reach out for news or if you would be interested in adding more of these calculations in Noir. + +Some methods are available thanks to the Aztec backend, not being performed using Noir. When using other backends, these methods may or may not be supplied. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/cryptographic_primitives/schnorr.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/cryptographic_primitives/schnorr.mdx new file mode 100644 index 00000000000..0058c0c0c98 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/cryptographic_primitives/schnorr.mdx @@ -0,0 +1,64 @@ +--- +title: Schnorr Signatures +description: Learn how you can verify Schnorr signatures using Noir +keywords: [cryptographic primitives, Noir project, schnorr, signatures] +sidebar_position: 2 +--- + +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; + +## schnorr::verify_signature + +Verifier for Schnorr signatures over the embedded curve (for BN254 it is Grumpkin). +See schnorr::verify_signature_slice for a version that works directly on slices. + +```rust title="schnorr_verify" showLineNumbers +pub fn verify_signature( + public_key_x: Field, + public_key_y: Field, + signature: [u8; 64], + message: [u8; N] +) -> bool +``` +> Source code: noir_stdlib/src/schnorr.nr#L4-L11 + + +where `_signature` can be generated like so using the npm package +[@noir-lang/barretenberg](https://www.npmjs.com/package/@noir-lang/barretenberg) + +```js +const { BarretenbergWasm } = require('@noir-lang/barretenberg/dest/wasm'); +const { Schnorr } = require('@noir-lang/barretenberg/dest/crypto/schnorr'); + +... + +const barretenberg = await BarretenbergWasm.new(); +const schnorr = new Schnorr(barretenberg); +const pubKey = schnorr.computePublicKey(privateKey); +const message = ... +const signature = Array.from( + schnorr.constructSignature(hash, privateKey).toBuffer() +); + +... +``` + + + +## schnorr::verify_signature_slice + +Verifier for Schnorr signatures over the embedded curve (for BN254 it is Grumpkin) +where the message is a slice. + +```rust title="schnorr_verify_slice" showLineNumbers +pub fn verify_signature_slice( + public_key_x: Field, + public_key_y: Field, + signature: [u8; 64], + message: [u8] +) -> bool +``` +> Source code: noir_stdlib/src/schnorr.nr#L15-L22 + + + diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/fmtstr.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/fmtstr.md new file mode 100644 index 00000000000..65a7da9996d --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/fmtstr.md @@ -0,0 +1,17 @@ +--- +title: fmtstr +--- + +`fmtstr` is the type resulting from using format string (`f"..."`). + +## Methods + +### quoted_contents + +```rust title="quoted_contents" showLineNumbers +comptime fn quoted_contents(self) -> Quoted {} +``` +> Source code: noir_stdlib/src/meta/format_string.nr#L3-L5 + + +Returns the format string contents (that is, without the leading and trailing double quotes) as a `Quoted` value. \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/is_unconstrained.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/is_unconstrained.md new file mode 100644 index 00000000000..51bb1bda8f1 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/is_unconstrained.md @@ -0,0 +1,69 @@ +--- +title: Is Unconstrained Function +description: + The is_unconstrained function returns wether the context at that point of the program is unconstrained or not. +keywords: + [ + unconstrained + ] +--- + +It's very common for functions in circuits to take unconstrained hints of an expensive computation and then verify it. This is done by running the hint in an unconstrained context and then verifying the result in a constrained context. + +When a function is marked as unconstrained, any subsequent functions that it calls will also be run in an unconstrained context. However, if we are implementing a library function, other users might call it within an unconstrained context or a constrained one. Generally, in an unconstrained context we prefer just computing the result instead of taking a hint of it and verifying it, since that'd mean doing the same computation twice: + +```rust + +fn my_expensive_computation(){ + ... +} + +unconstrained fn my_expensive_computation_hint(){ + my_expensive_computation() +} + +pub fn external_interface(){ + my_expensive_computation_hint(); + // verify my_expensive_computation: If external_interface is called from unconstrained, this is redundant + ... +} + +``` + +In order to improve the performance in an unconstrained context you can use the function at `std::runtime::is_unconstrained() -> bool`: + + +```rust +use dep::std::runtime::is_unconstrained; + +fn my_expensive_computation(){ + ... +} + +unconstrained fn my_expensive_computation_hint(){ + my_expensive_computation() +} + +pub fn external_interface(){ + if is_unconstrained() { + my_expensive_computation(); + } else { + my_expensive_computation_hint(); + // verify my_expensive_computation + ... + } +} + +``` + +The is_unconstrained result is resolved at compile time, so in unconstrained contexts the compiler removes the else branch, and in constrained contexts the compiler removes the if branch, reducing the amount of compute necessary to run external_interface. + +Note that using `is_unconstrained` in a `comptime` context will also return `true`: + +``` +fn main() { + comptime { + assert(is_unconstrained()); + } +} +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/logging.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/logging.md new file mode 100644 index 00000000000..db75ef9f86f --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/logging.md @@ -0,0 +1,78 @@ +--- +title: Logging +description: + Learn how to use the println statement for debugging in Noir with this tutorial. Understand the + basics of logging in Noir and how to implement it in your code. +keywords: + [ + noir logging, + println statement, + print statement, + debugging in noir, + noir std library, + logging tutorial, + basic logging in noir, + noir logging implementation, + noir debugging techniques, + rust, + ] +--- + +The standard library provides two familiar statements you can use: `println` and `print`. Despite being a limited implementation of rust's `println!` and `print!` macros, these constructs can be useful for debugging. + +You can print the output of both statements in your Noir code by using the `nargo execute` command or the `--show-output` flag when using `nargo test` (provided there are print statements in your tests). + +It is recommended to use `nargo execute` if you want to debug failing constraints with `println` or `print` statements. This is due to every input in a test being a constant rather than a witness, so we issue an error during compilation while we only print during execution (which comes after compilation). Neither `println`, nor `print` are callable for failed constraints caught at compile time. + +Both `print` and `println` are generic functions which can work on integers, fields, strings, and even structs or expressions. Note however, that slices are currently unsupported. For example: + +```rust +struct Person { + age: Field, + height: Field, +} + +fn main(age: Field, height: Field) { + let person = Person { + age: age, + height: height, + }; + println(person); + println(age + height); + println("Hello world!"); +} +``` + +You can print different types in the same statement (including strings) with a type called `fmtstr`. It can be specified in the same way as a normal string, just prepended with an "f" character: + +```rust + let fmt_str = f"i: {i}, j: {j}"; + println(fmt_str); + + let s = myStruct { y: x, x: y }; + println(s); + + println(f"i: {i}, s: {s}"); + + println(x); + println([x, y]); + + let foo = fooStruct { my_struct: s, foo: 15 }; + println(f"s: {s}, foo: {foo}"); + + println(15); // prints 0x0f, implicit Field + println(-1 as u8); // prints 255 + println(-1 as i8); // prints -1 +``` + +Examples shown above are interchangeable between the two `print` statements: + +```rust +let person = Person { age : age, height : height }; + +println(person); +print(person); + +println("Hello world!"); // Prints with a newline at the end of the input +print("Hello world!"); // Prints the input and keeps cursor on the same line +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/merkle_trees.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/merkle_trees.md new file mode 100644 index 00000000000..6a9ebf72ada --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/merkle_trees.md @@ -0,0 +1,58 @@ +--- +title: Merkle Trees +description: Learn about Merkle Trees in Noir with this tutorial. Explore the basics of computing a merkle root using a proof, with examples. +keywords: + [ + Merkle trees in Noir, + Noir programming language, + check membership, + computing root from leaf, + Noir Merkle tree implementation, + Merkle tree tutorial, + Merkle tree code examples, + Noir libraries, + pedersen hash., + ] +--- + +## compute_merkle_root + +Returns the root of the tree from the provided leaf and its hash path, using a [Pedersen hash](./cryptographic_primitives/hashes.mdx#pedersen_hash). + +```rust +fn compute_merkle_root(leaf : Field, index : Field, hash_path: [Field]) -> Field +``` + +example: + +```rust +/** + // these values are for this example only + index = "0" + priv_key = "0x000000000000000000000000000000000000000000000000000000616c696365" + secret = "0x1929ea3ab8d9106a899386883d9428f8256cfedb3c4f6b66bf4aa4d28a79988f" + note_hash_path = [ + "0x1e61bdae0f027b1b2159e1f9d3f8d00fa668a952dddd822fda80dc745d6f65cc", + "0x0e4223f3925f98934393c74975142bd73079ab0621f4ee133cee050a3c194f1a", + "0x2fd7bb412155bf8693a3bd2a3e7581a679c95c68a052f835dddca85fa1569a40" + ] + */ +fn main(index: Field, priv_key: Field, secret: Field, note_hash_path: [Field; 3]) { + + let pubkey = std::scalar_mul::fixed_base_embedded_curve(priv_key); + let pubkey_x = pubkey[0]; + let pubkey_y = pubkey[1]; + let note_commitment = std::hash::pedersen(&[pubkey_x, pubkey_y, secret]); + + let root = std::merkle::compute_merkle_root(note_commitment[0], index, note_hash_path.as_slice()); + println(root); +} +``` + +To check merkle tree membership: + +1. Include a merkle root as a program input. +2. Compute the merkle root of a given leaf, index and hash path. +3. Assert the merkle roots are equal. + +For more info about merkle trees, see the Wikipedia [page](https://en.wikipedia.org/wiki/Merkle_tree). diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/ctstring.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/ctstring.md new file mode 100644 index 00000000000..0be9bbced4e --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/ctstring.md @@ -0,0 +1,100 @@ +--- +title: CtString +--- + +`std::meta::ctstring` contains methods on the built-in `CtString` type which is +a compile-time, dynamically-sized string type. Compared to `str` and `fmtstr`, +`CtString` is useful because its size does not need to be specified in its type. This +can be used for formatting items at compile-time or general string handling in `comptime` +code. + +Since `fmtstr`s can be converted into `CtString`s, you can make use of their formatting +abilities in CtStrings by formatting in `fmtstr`s then converting the result to a CtString +afterward. + +## Traits + +### AsCtString + +```rust title="as-ctstring" showLineNumbers +pub trait AsCtString { + comptime fn as_ctstring(self) -> CtString; +} +``` +> Source code: noir_stdlib/src/meta/ctstring.nr#L43-L47 + + +Converts an object into a compile-time string. + +Implementations: + +```rust +impl AsCtString for str { ... } +impl AsCtString for fmtstr { ... } +``` + +## Methods + +### new + +```rust title="new" showLineNumbers +comptime fn new() -> Self { +``` +> Source code: noir_stdlib/src/meta/ctstring.nr#L4-L6 + + +Creates an empty `CtString`. + +### append_str + +```rust title="append_str" showLineNumbers +comptime fn append_str(self, s: str) -> Self { +``` +> Source code: noir_stdlib/src/meta/ctstring.nr#L11-L13 + + +Returns a new CtString with the given str appended onto the end. + +### append_fmtstr + +```rust title="append_fmtstr" showLineNumbers +comptime fn append_fmtstr(self, s: fmtstr) -> Self { +``` +> Source code: noir_stdlib/src/meta/ctstring.nr#L17-L19 + + +Returns a new CtString with the given fmtstr appended onto the end. + +### as_quoted_str + +```rust title="as_quoted_str" showLineNumbers +comptime fn as_quoted_str(self) -> Quoted { +``` +> Source code: noir_stdlib/src/meta/ctstring.nr#L26-L28 + + +Returns a quoted string literal from this string's contents. + +There is no direct conversion from a `CtString` to a `str` since +the size would not be known. To get around this, this function can +be used in combination with macro insertion (`!`) to insert this string +literal at this function's call site. + +Example: + +```rust title="as_quoted_str_example" showLineNumbers +let my_ctstring = "foo bar".as_ctstring(); + let my_str = my_ctstring.as_quoted_str!(); + + assert_eq(crate::meta::type_of(my_str), quote { str<7> }.as_type()); +``` +> Source code: noir_stdlib/src/meta/ctstring.nr#L90-L95 + + +## Trait Implementations + +```rust +impl Eq for CtString +impl Hash for CtString +impl Append for CtString +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/expr.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/expr.md new file mode 100644 index 00000000000..1da32b0bc09 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/expr.md @@ -0,0 +1,378 @@ +--- +title: Expr +--- + +`std::meta::expr` contains methods on the built-in `Expr` type for quoted, syntactically valid expressions. + +## Methods + +### as_array + +```rust title="as_array" showLineNumbers +comptime fn as_array(self) -> Option<[Expr]> {} +``` +> Source code: noir_stdlib/src/meta/expr.nr#L10-L12 + + +If this expression is an array, this returns a slice of each element in the array. + +### as_assert + +```rust title="as_assert" showLineNumbers +comptime fn as_assert(self) -> Option<(Expr, Option)> {} +``` +> Source code: noir_stdlib/src/meta/expr.nr#L16-L18 + + +If this expression is an assert, this returns the assert expression and the optional message. + +### as_assert_eq + +```rust title="as_assert_eq" showLineNumbers +comptime fn as_assert_eq(self) -> Option<(Expr, Expr, Option)> {} +``` +> Source code: noir_stdlib/src/meta/expr.nr#L23-L25 + + +If this expression is an assert_eq, this returns the left-hand-side and right-hand-side +expressions, together with the optional message. + +### as_assign + +```rust title="as_assign" showLineNumbers +comptime fn as_assign(self) -> Option<(Expr, Expr)> {} +``` +> Source code: noir_stdlib/src/meta/expr.nr#L30-L32 + + +If this expression is an assignment, this returns a tuple with the left hand side +and right hand side in order. + +### as_binary_op + +```rust title="as_binary_op" showLineNumbers +comptime fn as_binary_op(self) -> Option<(Expr, BinaryOp, Expr)> {} +``` +> Source code: noir_stdlib/src/meta/expr.nr#L37-L39 + + +If this expression is a binary operator operation ` `, +return the left-hand side, operator, and the right-hand side of the operation. + +### as_block + +```rust title="as_block" showLineNumbers +comptime fn as_block(self) -> Option<[Expr]> {} +``` +> Source code: noir_stdlib/src/meta/expr.nr#L44-L46 + + +If this expression is a block `{ stmt1; stmt2; ...; stmtN }`, return +a slice containing each statement. + +### as_bool + +```rust title="as_bool" showLineNumbers +comptime fn as_bool(self) -> Option {} +``` +> Source code: noir_stdlib/src/meta/expr.nr#L50-L52 + + +If this expression is a boolean literal, return that literal. + +### as_cast + +```rust title="as_cast" showLineNumbers +#[builtin(expr_as_cast)] + comptime fn as_cast(self) -> Option<(Expr, UnresolvedType)> {} +``` +> Source code: noir_stdlib/src/meta/expr.nr#L56-L59 + + +If this expression is a cast expression (`expr as type`), returns the casted +expression and the type to cast to. + +### as_comptime + +```rust title="as_comptime" showLineNumbers +comptime fn as_comptime(self) -> Option<[Expr]> {} +``` +> Source code: noir_stdlib/src/meta/expr.nr#L64-L66 + + +If this expression is a `comptime { stmt1; stmt2; ...; stmtN }` block, +return each statement in the block. + +### as_constructor + +```rust title="as_constructor" showLineNumbers +comptime fn as_constructor(self) -> Option<(UnresolvedType, [(Quoted, Expr)])> {} +``` +> Source code: noir_stdlib/src/meta/expr.nr#L71-L73 + + +If this expression is a constructor `Type { field1: expr1, ..., fieldN: exprN }`, +return the type and the fields. + +### as_for + +```rust title="as_for" showLineNumbers +comptime fn as_for(self) -> Option<(Quoted, Expr, Expr)> {} +``` +> Source code: noir_stdlib/src/meta/expr.nr#L78-L80 + + +If this expression is a for statement over a single expression, return the identifier, +the expression and the for loop body. + +### as_for_range + +```rust title="as_for" showLineNumbers +comptime fn as_for(self) -> Option<(Quoted, Expr, Expr)> {} +``` +> Source code: noir_stdlib/src/meta/expr.nr#L78-L80 + + +If this expression is a for statement over a range, return the identifier, +the range start, the range end and the for loop body. + +### as_function_call + +```rust title="as_function_call" showLineNumbers +comptime fn as_function_call(self) -> Option<(Expr, [Expr])> {} +``` +> Source code: noir_stdlib/src/meta/expr.nr#L92-L94 + + +If this expression is a function call `foo(arg1, ..., argN)`, return +the function and a slice of each argument. + +### as_if + +```rust title="as_if" showLineNumbers +comptime fn as_if(self) -> Option<(Expr, Expr, Option)> {} +``` +> Source code: noir_stdlib/src/meta/expr.nr#L100-L102 + + +If this expression is an `if condition { then_branch } else { else_branch }`, +return the condition, then branch, and else branch. If there is no else branch, +`None` is returned for that branch instead. + +### as_index + +```rust title="as_index" showLineNumbers +comptime fn as_index(self) -> Option<(Expr, Expr)> {} +``` +> Source code: noir_stdlib/src/meta/expr.nr#L107-L109 + + +If this expression is an index into an array `array[index]`, return the +array and the index. + +### as_integer + +```rust title="as_integer" showLineNumbers +comptime fn as_integer(self) -> Option<(Field, bool)> {} +``` +> Source code: noir_stdlib/src/meta/expr.nr#L114-L116 + + +If this expression is an integer literal, return the integer as a field +as well as whether the integer is negative (true) or not (false). + +### as_lambda + +```rust title="as_lambda" showLineNumbers +comptime fn as_lambda(self) -> Option<([(Expr, Option)], Option, Expr)> {} +``` +> Source code: noir_stdlib/src/meta/expr.nr#L120-L122 + + +If this expression is a lambda, returns the parameters, return type and body. + +### as_let + +```rust title="as_let" showLineNumbers +comptime fn as_let(self) -> Option<(Expr, Option, Expr)> {} +``` +> Source code: noir_stdlib/src/meta/expr.nr#L127-L129 + + +If this expression is a let statement, returns the let pattern as an `Expr`, +the optional type annotation, and the assigned expression. + +### as_member_access + +```rust title="as_member_access" showLineNumbers +comptime fn as_member_access(self) -> Option<(Expr, Quoted)> {} +``` +> Source code: noir_stdlib/src/meta/expr.nr#L134-L136 + + +If this expression is a member access `foo.bar`, return the struct/tuple +expression and the field. The field will be represented as a quoted value. + +### as_method_call + +```rust title="as_method_call" showLineNumbers +comptime fn as_method_call(self) -> Option<(Expr, Quoted, [UnresolvedType], [Expr])> {} +``` +> Source code: noir_stdlib/src/meta/expr.nr#L141-L143 + + +If this expression is a method call `foo.bar::(arg1, ..., argN)`, return +the receiver, method name, a slice of each generic argument, and a slice of each argument. + +### as_repeated_element_array + +```rust title="as_repeated_element_array" showLineNumbers +comptime fn as_repeated_element_array(self) -> Option<(Expr, Expr)> {} +``` +> Source code: noir_stdlib/src/meta/expr.nr#L148-L150 + + +If this expression is a repeated element array `[elem; length]`, return +the repeated element and the length expressions. + +### as_repeated_element_slice + +```rust title="as_repeated_element_slice" showLineNumbers +comptime fn as_repeated_element_slice(self) -> Option<(Expr, Expr)> {} +``` +> Source code: noir_stdlib/src/meta/expr.nr#L155-L157 + + +If this expression is a repeated element slice `[elem; length]`, return +the repeated element and the length expressions. + +### as_slice + +```rust title="as_slice" showLineNumbers +comptime fn as_slice(self) -> Option<[Expr]> {} +``` +> Source code: noir_stdlib/src/meta/expr.nr#L162-L164 + + +If this expression is a slice literal `&[elem1, ..., elemN]`, +return each element of the slice. + +### as_tuple + +```rust title="as_tuple" showLineNumbers +comptime fn as_tuple(self) -> Option<[Expr]> {} +``` +> Source code: noir_stdlib/src/meta/expr.nr#L169-L171 + + +If this expression is a tuple `(field1, ..., fieldN)`, +return each element of the tuple. + +### as_unary_op + +```rust title="as_unary_op" showLineNumbers +comptime fn as_unary_op(self) -> Option<(UnaryOp, Expr)> {} +``` +> Source code: noir_stdlib/src/meta/expr.nr#L176-L178 + + +If this expression is a unary operation ` `, +return the unary operator as well as the right-hand side expression. + +### as_unsafe + +```rust title="as_unsafe" showLineNumbers +comptime fn as_unsafe(self) -> Option<[Expr]> {} +``` +> Source code: noir_stdlib/src/meta/expr.nr#L183-L185 + + +If this expression is an `unsafe { stmt1; ...; stmtN }` block, +return each statement inside in a slice. + +### has_semicolon + +```rust title="has_semicolon" showLineNumbers +comptime fn has_semicolon(self) -> bool {} +``` +> Source code: noir_stdlib/src/meta/expr.nr#L204-L206 + + +`true` if this expression is trailed by a semicolon. E.g. + +``` +comptime { + let expr1 = quote { 1 + 2 }.as_expr().unwrap(); + let expr2 = quote { 1 + 2; }.as_expr().unwrap(); + + assert(expr1.as_binary_op().is_some()); + assert(expr2.as_binary_op().is_some()); + + assert(!expr1.has_semicolon()); + assert(expr2.has_semicolon()); +} +``` + +### is_break + +```rust title="is_break" showLineNumbers +comptime fn is_break(self) -> bool {} +``` +> Source code: noir_stdlib/src/meta/expr.nr#L210-L212 + + +`true` if this expression is `break`. + +### is_continue + +```rust title="is_continue" showLineNumbers +comptime fn is_continue(self) -> bool {} +``` +> Source code: noir_stdlib/src/meta/expr.nr#L216-L218 + + +`true` if this expression is `continue`. + +### modify + +```rust title="modify" showLineNumbers +comptime fn modify(self, f: fn[Env](Expr) -> Option) -> Expr { +``` +> Source code: noir_stdlib/src/meta/expr.nr#L227-L229 + + +Applies a mapping function to this expression and to all of its sub-expressions. +`f` will be applied to each sub-expression first, then applied to the expression itself. + +This happens recursively for every expression within `self`. + +For example, calling `modify` on `(&[1], &[2, 3])` with an `f` that returns `Option::some` +for expressions that are integers, doubling them, would return `(&[2], &[4, 6])`. + +### quoted + +```rust title="quoted" showLineNumbers +comptime fn quoted(self) -> Quoted { +``` +> Source code: noir_stdlib/src/meta/expr.nr#L264-L266 + + +Returns this expression as a `Quoted` value. It's the same as `quote { $self }`. + +### resolve + +```rust title="resolve" showLineNumbers +comptime fn resolve(self, in_function: Option) -> TypedExpr {} +``` +> Source code: noir_stdlib/src/meta/expr.nr#L280-L282 + + +Resolves and type-checks this expression and returns the result as a `TypedExpr`. + +The `in_function` argument specifies where the expression is resolved: +- If it's `none`, the expression is resolved in the function where `resolve` was called +- If it's `some`, the expression is resolved in the given function + +If any names used by this expression are not in scope or if there are any type errors, +this will give compiler errors as if the expression was written directly into +the current `comptime` function. \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/function_def.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/function_def.md new file mode 100644 index 00000000000..e490af68f7f --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/function_def.md @@ -0,0 +1,166 @@ +--- +title: FunctionDefinition +--- + +`std::meta::function_def` contains methods on the built-in `FunctionDefinition` type representing +a function definition in the source program. + +## Methods + +### add_attribute + +```rust title="add_attribute" showLineNumbers +comptime fn add_attribute(self, attribute: str) {} +``` +> Source code: noir_stdlib/src/meta/function_def.nr#L3-L5 + + +Adds an attribute to the function. This is only valid +on functions in the current crate which have not yet been resolved. +This means any functions called at compile-time are invalid targets for this method. + +### body + +```rust title="body" showLineNumbers +comptime fn body(self) -> Expr {} +``` +> Source code: noir_stdlib/src/meta/function_def.nr#L8-L10 + + +Returns the body of the function as an expression. This is only valid +on functions in the current crate which have not yet been resolved. +This means any functions called at compile-time are invalid targets for this method. + +### has_named_attribute + +```rust title="has_named_attribute" showLineNumbers +comptime fn has_named_attribute(self, name: str) -> bool {} +``` +> Source code: noir_stdlib/src/meta/function_def.nr#L13-L15 + + +Returns true if this function has a custom attribute with the given name. + +### is_unconstrained + +```rust title="is_unconstrained" showLineNumbers +comptime fn is_unconstrained(self) -> bool {} +``` +> Source code: noir_stdlib/src/meta/function_def.nr#L18-L20 + + +Returns true if this function is unconstrained. + +### module + +```rust title="module" showLineNumbers +comptime fn module(self) -> Module {} +``` +> Source code: noir_stdlib/src/meta/function_def.nr#L23-L25 + + +Returns the module where the function is defined. + +### name + +```rust title="name" showLineNumbers +comptime fn name(self) -> Quoted {} +``` +> Source code: noir_stdlib/src/meta/function_def.nr#L28-L30 + + +Returns the name of the function. + +### parameters + +```rust title="parameters" showLineNumbers +comptime fn parameters(self) -> [(Quoted, Type)] {} +``` +> Source code: noir_stdlib/src/meta/function_def.nr#L33-L35 + + +Returns each parameter of the function as a tuple of (parameter pattern, parameter type). + +### return_type + +```rust title="return_type" showLineNumbers +comptime fn return_type(self) -> Type {} +``` +> Source code: noir_stdlib/src/meta/function_def.nr#L38-L40 + + +The return type of the function. + +### set_body + +```rust title="set_body" showLineNumbers +comptime fn set_body(self, body: Expr) {} +``` +> Source code: noir_stdlib/src/meta/function_def.nr#L43-L45 + + +Mutate the function body to a new expression. This is only valid +on functions in the current crate which have not yet been resolved. +This means any functions called at compile-time are invalid targets for this method. + +### set_parameters + +```rust title="set_parameters" showLineNumbers +comptime fn set_parameters(self, parameters: [(Quoted, Type)]) {} +``` +> Source code: noir_stdlib/src/meta/function_def.nr#L48-L50 + + +Mutates the function's parameters to a new set of parameters. This is only valid +on functions in the current crate which have not yet been resolved. +This means any functions called at compile-time are invalid targets for this method. + +Expects a slice of (parameter pattern, parameter type) for each parameter. Requires +each parameter pattern to be a syntactically valid parameter. + +### set_return_type + +```rust title="set_return_type" showLineNumbers +comptime fn set_return_type(self, return_type: Type) {} +``` +> Source code: noir_stdlib/src/meta/function_def.nr#L53-L55 + + +Mutates the function's return type to a new type. This is only valid +on functions in the current crate which have not yet been resolved. +This means any functions called at compile-time are invalid targets for this method. + +### set_return_public + +```rust title="set_return_public" showLineNumbers +comptime fn set_return_public(self, public: bool) {} +``` +> Source code: noir_stdlib/src/meta/function_def.nr#L58-L60 + + +Mutates the function's return visibility to public (if `true` is given) or private (if `false` is given). +This is only valid on functions in the current crate which have not yet been resolved. +This means any functions called at compile-time are invalid targets for this method. + +### set_unconstrained + +```rust title="set_unconstrained" showLineNumbers +comptime fn set_unconstrained(self, value: bool) {} +``` +> Source code: noir_stdlib/src/meta/function_def.nr#L63-L65 + + +Mutates the function to be unconstrained (if `true` is given) or not (if `false` is given). +This is only valid on functions in the current crate which have not yet been resolved. +This means any functions called at compile-time are invalid targets for this method. + +## Trait Implementations + +```rust +impl Eq for FunctionDefinition +impl Hash for FunctionDefinition +``` + +Note that each function is assigned a unique ID internally and this is what is used for +equality and hashing. So even functions with identical signatures and bodies may not +be equal in this sense if they were originally different items in the source program. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/index.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/index.md new file mode 100644 index 00000000000..f81c89e05ae --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/index.md @@ -0,0 +1,210 @@ +--- +title: Metaprogramming +description: Noir's Metaprogramming API +keywords: [metaprogramming, comptime, macros, macro, quote, unquote] +--- + +`std::meta` is the entry point for Noir's metaprogramming API. This consists of `comptime` functions +and types used for inspecting and modifying Noir programs. + +## Functions + +### type_of + +```rust title="type_of" showLineNumbers +pub comptime fn type_of(x: T) -> Type {} +``` +> Source code: noir_stdlib/src/meta/mod.nr#L27-L29 + + +Returns the type of a variable at compile-time. + +Example: +```rust +comptime { + let x: i32 = 1; + let x_type: Type = std::meta::type_of(x); + + assert_eq(x_type, quote { i32 }.as_type()); +} +``` + +### unquote + +```rust title="unquote" showLineNumbers +pub comptime fn unquote(code: Quoted) -> Quoted { +``` +> Source code: noir_stdlib/src/meta/mod.nr#L19-L21 + + +Unquotes the passed-in token stream where this function was called. + +Example: +```rust +comptime { + let code = quote { 1 + 2 }; + + // let x = 1 + 2; + let x = unquote!(code); +} +``` + +### derive + +```rust title="derive" showLineNumbers +#[varargs] +pub comptime fn derive(s: StructDefinition, traits: [TraitDefinition]) -> Quoted { +``` +> Source code: noir_stdlib/src/meta/mod.nr#L47-L50 + + +Attribute placed on struct definitions. + +Creates a trait impl for each trait passed in as an argument. +To do this, the trait must have a derive handler registered +with `derive_via` beforehand. The traits in the stdlib that +can be derived this way are `Eq`, `Ord`, `Default`, and `Hash`. + +Example: +```rust +#[derive(Eq, Default)] +struct Foo { + x: i32, + y: T, +} + +fn main() { + let foo1 = Foo::default(); + let foo2 = Foo { x: 0, y: &[0] }; + assert_eq(foo1, foo2); +} +``` + +### derive_via + +```rust title="derive_via_signature" showLineNumbers +pub comptime fn derive_via(t: TraitDefinition, f: DeriveFunction) { +``` +> Source code: noir_stdlib/src/meta/mod.nr#L69-L71 + + +Attribute placed on trait definitions. + +Registers a function to create impls for the given trait +when the trait is used in a `derive` call. Users may use +this to register their own functions to enable their traits +to be derived by `derive`. + +Because this function requires a function as an argument which +should produce a trait impl for any given struct, users may find +it helpful to use a function like `std::meta::make_trait_impl` to +help creating these impls. + +Example: +```rust +#[derive_via(derive_do_nothing)] +trait DoNothing { + fn do_nothing(self); +} + +comptime fn derive_do_nothing(s: StructDefinition) -> Quoted { + let typ = s.as_type(); + quote { + impl DoNothing for $typ { + fn do_nothing(self) { + println("Nothing"); + } + } + } +} +``` + +As another example, `derive_eq` in the stdlib is used to derive the `Eq` +trait for any struct. It makes use of `make_trait_impl` to do this: + +```rust title="derive_eq" showLineNumbers +comptime fn derive_eq(s: StructDefinition) -> Quoted { + let signature = quote { fn eq(_self: Self, _other: Self) -> bool }; + let for_each_field = |name| quote { (_self.$name == _other.$name) }; + let body = |fields| { + if s.fields().len() == 0 { + quote { true } + } else { + fields + } + }; + crate::meta::make_trait_impl(s, quote { Eq }, signature, for_each_field, quote { & }, body) +} +``` +> Source code: noir_stdlib/src/cmp.nr#L10-L23 + + +### make_trait_impl + +```rust title="make_trait_impl" showLineNumbers +pub comptime fn make_trait_impl( + s: StructDefinition, + trait_name: Quoted, + function_signature: Quoted, + for_each_field: fn[Env1](Quoted) -> Quoted, + join_fields_with: Quoted, + body: fn[Env2](Quoted) -> Quoted +) -> Quoted { +``` +> Source code: noir_stdlib/src/meta/mod.nr#L88-L97 + + +A helper function to more easily create trait impls while deriving traits. + +Note that this function only works for traits which: +1. Have only one method +2. Have no generics on the trait itself. + - E.g. Using this on a trait such as `trait Foo { ... }` will result in the + generated impl incorrectly missing the `T` generic. + +If your trait fits these criteria then `make_trait_impl` is likely the easiest +way to write your derive handler. The arguments are as follows: + +- `s`: The struct to make the impl for +- `trait_name`: The name of the trait to derive. E.g. `quote { Eq }`. +- `function_signature`: The signature of the trait method to derive. E.g. `fn eq(self, other: Self) -> bool`. +- `for_each_field`: An operation to be performed on each field. E.g. `|name| quote { (self.$name == other.$name) }`. +- `join_fields_with`: A separator to join each result of `for_each_field` with. + E.g. `quote { & }`. You can also use an empty `quote {}` for no separator. +- `body`: The result of the field operations are passed into this function for any final processing. + This is the place to insert any setup/teardown code the trait requires. If the trait doesn't require + any such code, you can return the body as-is: `|body| body`. + +Example deriving `Hash`: + +```rust title="derive_hash" showLineNumbers +comptime fn derive_hash(s: StructDefinition) -> Quoted { + let name = quote { Hash }; + let signature = quote { fn hash(_self: Self, _state: &mut H) where H: std::hash::Hasher }; + let for_each_field = |name| quote { _self.$name.hash(_state); }; + crate::meta::make_trait_impl(s, name, signature, for_each_field, quote {}, |fields| fields) +} +``` +> Source code: noir_stdlib/src/hash/mod.nr#L144-L151 + + +Example deriving `Ord`: + +```rust title="derive_ord" showLineNumbers +comptime fn derive_ord(s: StructDefinition) -> Quoted { + let signature = quote { fn cmp(_self: Self, _other: Self) -> std::cmp::Ordering }; + let for_each_field = |name| quote { + if result == std::cmp::Ordering::equal() { + result = _self.$name.cmp(_other.$name); + } + }; + let body = |fields| quote { + let mut result = std::cmp::Ordering::equal(); + $fields + result + }; + crate::meta::make_trait_impl(s, quote { Ord }, signature, for_each_field, quote {}, body) +} +``` +> Source code: noir_stdlib/src/cmp.nr#L181-L196 + diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/module.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/module.md new file mode 100644 index 00000000000..efd3e61e125 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/module.md @@ -0,0 +1,82 @@ +--- +title: Module +--- + +`std::meta::module` contains methods on the built-in `Module` type which represents a module in the source program. +Note that this type represents a module generally, it isn't limited to only `mod my_submodule { ... }` +declarations in the source program. + +## Methods + +### add_item + +```rust title="add_item" showLineNumbers +comptime fn add_item(self, item: Quoted) {} +``` +> Source code: noir_stdlib/src/meta/module.nr#L3-L5 + + +Adds a top-level item (a function, a struct, a global, etc.) to the module. +Adding multiple items in one go is also valid if the `Quoted` value has multiple items in it. +Note that the items are type-checked as if they are inside the module they are being added to. + +### functions + +```rust title="functions" showLineNumbers +comptime fn functions(self) -> [FunctionDefinition] {} +``` +> Source code: noir_stdlib/src/meta/module.nr#L18-L20 + + +Returns each function defined in the module. + +### has_named_attribute + +```rust title="has_named_attribute" showLineNumbers +comptime fn has_named_attribute(self, name: str) -> bool {} +``` +> Source code: noir_stdlib/src/meta/module.nr#L8-L10 + + +Returns true if this module has a custom attribute with the given name. + +### is_contract + +```rust title="is_contract" showLineNumbers +comptime fn is_contract(self) -> bool {} +``` +> Source code: noir_stdlib/src/meta/module.nr#L13-L15 + + +`true` if this module is a contract module (was declared via `contract foo { ... }`). + +### name + +```rust title="name" showLineNumbers +comptime fn name(self) -> Quoted {} +``` +> Source code: noir_stdlib/src/meta/module.nr#L28-L30 + + +Returns the name of the module. + +### structs + +```rust title="structs" showLineNumbers +comptime fn structs(self) -> [StructDefinition] {} +``` +> Source code: noir_stdlib/src/meta/module.nr#L23-L25 + + +Returns each struct defined in the module. + +## Trait Implementations + +```rust +impl Eq for Module +impl Hash for Module +``` + +Note that each module is assigned a unique ID internally and this is what is used for +equality and hashing. So even modules with identical names and contents may not +be equal in this sense if they were originally different items in the source program. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/op.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/op.md new file mode 100644 index 00000000000..18d1f0768fd --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/op.md @@ -0,0 +1,244 @@ +--- +title: UnaryOp and BinaryOp +--- + +`std::meta::op` contains the `UnaryOp` and `BinaryOp` types as well as methods on them. +These types are used to represent a unary or binary operator respectively in Noir source code. + +## Types + +### UnaryOp + +Represents a unary operator. One of `-`, `!`, `&mut`, or `*`. + +### Methods + +#### is_minus + +```rust title="is_minus" showLineNumbers +pub fn is_minus(self) -> bool { +``` +> Source code: noir_stdlib/src/meta/op.nr#L7-L9 + + +Returns `true` if this operator is `-`. + +#### is_not + +```rust title="is_not" showLineNumbers +pub fn is_not(self) -> bool { +``` +> Source code: noir_stdlib/src/meta/op.nr#L13-L15 + + +`true` if this operator is `!` + +#### is_mutable_reference + +```rust title="is_mutable_reference" showLineNumbers +pub fn is_mutable_reference(self) -> bool { +``` +> Source code: noir_stdlib/src/meta/op.nr#L19-L21 + + +`true` if this operator is `&mut` + +#### is_dereference + +```rust title="is_dereference" showLineNumbers +pub fn is_dereference(self) -> bool { +``` +> Source code: noir_stdlib/src/meta/op.nr#L25-L27 + + +`true` if this operator is `*` + +#### quoted + +```rust title="unary_quoted" showLineNumbers +pub comptime fn quoted(self) -> Quoted { +``` +> Source code: noir_stdlib/src/meta/op.nr#L31-L33 + + +Returns this operator as a `Quoted` value. + +### Trait Implementations + +```rust +impl Eq for UnaryOp +impl Hash for UnaryOp +``` + +### BinaryOp + +Represents a binary operator. One of `+`, `-`, `*`, `/`, `%`, `==`, `!=`, `<`, `<=`, `>`, `>=`, `&`, `|`, `^`, `>>`, or `<<`. + +### Methods + +#### is_add + +```rust title="is_add" showLineNumbers +pub fn is_add(self) -> bool { +``` +> Source code: noir_stdlib/src/meta/op.nr#L55-L57 + + +`true` if this operator is `+` + +#### is_subtract + +```rust title="is_subtract" showLineNumbers +pub fn is_subtract(self) -> bool { +``` +> Source code: noir_stdlib/src/meta/op.nr#L61-L63 + + +`true` if this operator is `-` + +#### is_multiply + +```rust title="is_multiply" showLineNumbers +pub fn is_multiply(self) -> bool { +``` +> Source code: noir_stdlib/src/meta/op.nr#L67-L69 + + +`true` if this operator is `*` + +#### is_divide + +```rust title="is_divide" showLineNumbers +pub fn is_divide(self) -> bool { +``` +> Source code: noir_stdlib/src/meta/op.nr#L73-L75 + + +`true` if this operator is `/` + +#### is_modulo + +```rust title="is_modulo" showLineNumbers +pub fn is_modulo(self) -> bool { +``` +> Source code: noir_stdlib/src/meta/op.nr#L145-L147 + + +`true` if this operator is `%` + +#### is_equal + +```rust title="is_equal" showLineNumbers +pub fn is_equal(self) -> bool { +``` +> Source code: noir_stdlib/src/meta/op.nr#L79-L81 + + +`true` if this operator is `==` + +#### is_not_equal + +```rust title="is_not_equal" showLineNumbers +pub fn is_not_equal(self) -> bool { +``` +> Source code: noir_stdlib/src/meta/op.nr#L85-L87 + + +`true` if this operator is `!=` + +#### is_less_than + +```rust title="is_less_than" showLineNumbers +pub fn is_less_than(self) -> bool { +``` +> Source code: noir_stdlib/src/meta/op.nr#L91-L93 + + +`true` if this operator is `<` + +#### is_less_than_or_equal + +```rust title="is_less_than_or_equal" showLineNumbers +pub fn is_less_than_or_equal(self) -> bool { +``` +> Source code: noir_stdlib/src/meta/op.nr#L97-L99 + + +`true` if this operator is `<=` + +#### is_greater_than + +```rust title="is_greater_than" showLineNumbers +pub fn is_greater_than(self) -> bool { +``` +> Source code: noir_stdlib/src/meta/op.nr#L103-L105 + + +`true` if this operator is `>` + +#### is_greater_than_or_equal + +```rust title="is_greater_than_or_equal" showLineNumbers +pub fn is_greater_than_or_equal(self) -> bool { +``` +> Source code: noir_stdlib/src/meta/op.nr#L109-L111 + + +`true` if this operator is `>=` + +#### is_and + +```rust title="is_and" showLineNumbers +pub fn is_and(self) -> bool { +``` +> Source code: noir_stdlib/src/meta/op.nr#L115-L117 + + +`true` if this operator is `&` + +#### is_or + +```rust title="is_or" showLineNumbers +pub fn is_or(self) -> bool { +``` +> Source code: noir_stdlib/src/meta/op.nr#L121-L123 + + +`true` if this operator is `|` + +#### is_shift_right + +```rust title="is_shift_right" showLineNumbers +pub fn is_shift_right(self) -> bool { +``` +> Source code: noir_stdlib/src/meta/op.nr#L133-L135 + + +`true` if this operator is `>>` + +#### is_shift_left + +```rust title="is_shift_right" showLineNumbers +pub fn is_shift_right(self) -> bool { +``` +> Source code: noir_stdlib/src/meta/op.nr#L133-L135 + + +`true` if this operator is `<<` + +#### quoted + +```rust title="binary_quoted" showLineNumbers +pub comptime fn quoted(self) -> Quoted { +``` +> Source code: noir_stdlib/src/meta/op.nr#L151-L153 + + +Returns this operator as a `Quoted` value. + +### Trait Implementations + +```rust +impl Eq for BinaryOp +impl Hash for BinaryOp +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/quoted.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/quoted.md new file mode 100644 index 00000000000..cb779bca98f --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/quoted.md @@ -0,0 +1,138 @@ +--- +title: Quoted +--- + +`std::meta::quoted` contains methods on the built-in `Quoted` type which represents +quoted token streams and is the result of the `quote { ... }` expression. + +## Methods + +### as_expr + +```rust title="as_expr" showLineNumbers +comptime fn as_expr(self) -> Option {} +``` +> Source code: noir_stdlib/src/meta/quoted.nr#L6-L8 + + +Parses the quoted token stream as an expression. Returns `Option::none()` if +the expression failed to parse. + +Example: + +```rust title="as_expr_example" showLineNumbers +#[test] + fn test_expr_as_function_call() { + comptime + { + let expr = quote { foo(42) }.as_expr().unwrap(); + let (_function, args) = expr.as_function_call().unwrap(); + assert_eq(args.len(), 1); + assert_eq(args[0].as_integer().unwrap(), (42, false)); + } + } +``` +> Source code: test_programs/noir_test_success/comptime_expr/src/main.nr#L360-L371 + + +### as_module + +```rust title="as_module" showLineNumbers +comptime fn as_module(self) -> Option {} +``` +> Source code: noir_stdlib/src/meta/quoted.nr#L11-L13 + + +Interprets this token stream as a module path leading to the name of a module. +Returns `Option::none()` if the module isn't found or this token stream cannot be parsed as a path. + +Example: + +```rust title="as_module_example" showLineNumbers +mod baz { + pub mod qux {} +} + +#[test] +fn as_module_test() { + comptime + { + let my_mod = quote { baz::qux }.as_module().unwrap(); + assert_eq(my_mod.name(), quote { qux }); + } +} +``` +> Source code: test_programs/compile_success_empty/comptime_module/src/main.nr#L116-L129 + + +### as_trait_constraint + +```rust title="as_trait_constraint" showLineNumbers +comptime fn as_trait_constraint(self) -> TraitConstraint {} +``` +> Source code: noir_stdlib/src/meta/quoted.nr#L16-L18 + + +Interprets this token stream as a trait constraint (without an object type). +Note that this function panics instead of returning `Option::none()` if the token +stream does not parse and resolve to a valid trait constraint. + +Example: + +```rust title="implements_example" showLineNumbers +fn function_with_where(_x: T) where T: SomeTrait { + comptime + { + let t = quote { T }.as_type(); + let some_trait_i32 = quote { SomeTrait }.as_trait_constraint(); + assert(t.implements(some_trait_i32)); + + assert(t.get_trait_impl(some_trait_i32).is_none()); + } +} +``` +> Source code: test_programs/compile_success_empty/comptime_type/src/main.nr#L154-L165 + + +### as_type + +```rust title="as_type" showLineNumbers +comptime fn as_type(self) -> Type {} +``` +> Source code: noir_stdlib/src/meta/quoted.nr#L21-L23 + + +Interprets this token stream as a resolved type. Panics if the token +stream doesn't parse to a type or if the type isn't a valid type in scope. + +```rust title="implements_example" showLineNumbers +fn function_with_where(_x: T) where T: SomeTrait { + comptime + { + let t = quote { T }.as_type(); + let some_trait_i32 = quote { SomeTrait }.as_trait_constraint(); + assert(t.implements(some_trait_i32)); + + assert(t.get_trait_impl(some_trait_i32).is_none()); + } +} +``` +> Source code: test_programs/compile_success_empty/comptime_type/src/main.nr#L154-L165 + + +### tokens + +```rust title="tokens" showLineNumbers +comptime fn tokens(self) -> [Quoted] {} +``` +> Source code: noir_stdlib/src/meta/quoted.nr#L26-L28 + + +Returns a slice of the individual tokens that form this token stream. + +## Trait Implementations + +```rust +impl Eq for Quoted +impl Hash for Quoted +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/struct_def.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/struct_def.md new file mode 100644 index 00000000000..212c636d12a --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/struct_def.md @@ -0,0 +1,177 @@ +--- +title: StructDefinition +--- + +`std::meta::struct_def` contains methods on the built-in `StructDefinition` type. +This type corresponds to `struct Name { field1: Type1, ... }` items in the source program. + +## Methods + +### add_attribute + +```rust title="add_attribute" showLineNumbers +comptime fn add_attribute(self, attribute: str) {} +``` +> Source code: noir_stdlib/src/meta/struct_def.nr#L3-L5 + + +Adds an attribute to the struct. + +### add_generic + +```rust title="add_generic" showLineNumbers +comptime fn add_generic(self, generic_name: str) -> Type {} +``` +> Source code: noir_stdlib/src/meta/struct_def.nr#L8-L10 + + +Adds an generic to the struct. Returns the new generic type. +Errors if the given generic name isn't a single identifier or if +the struct already has a generic with the same name. + +This method should be used carefully, if there is existing code referring +to the struct type it may be checked before this function is called and +see the struct with the original number of generics. This method should +thus be preferred to use on code generated from other macros and structs +that are not used in function signatures. + +Example: + +```rust title="add-generic-example" showLineNumbers +comptime fn add_generic(s: StructDefinition) { + assert_eq(s.generics().len(), 0); + let new_generic = s.add_generic("T"); + + let generics = s.generics(); + assert_eq(generics.len(), 1); + assert_eq(generics[0], new_generic); + } +``` +> Source code: test_programs/compile_success_empty/comptime_struct_definition/src/main.nr#L38-L47 + + +### as_type + +```rust title="as_type" showLineNumbers +comptime fn as_type(self) -> Type {} +``` +> Source code: noir_stdlib/src/meta/struct_def.nr#L15-L17 + + +Returns this struct as a type in the source program. If this struct has +any generics, the generics are also included as-is. + +### generics + +```rust title="generics" showLineNumbers +comptime fn generics(self) -> [Type] {} +``` +> Source code: noir_stdlib/src/meta/struct_def.nr#L26-L28 + + +Returns each generic on this struct. + +Example: + +``` +#[example] +struct Foo { + bar: [T; 2], + baz: Baz, +} + +comptime fn example(foo: StructDefinition) { + assert_eq(foo.generics().len(), 2); + + // Fails because `T` isn't in scope + // let t = quote { T }.as_type(); + // assert_eq(foo.generics()[0], t); +} +``` + +### fields + +```rust title="fields" showLineNumbers +comptime fn fields(self) -> [(Quoted, Type)] {} +``` +> Source code: noir_stdlib/src/meta/struct_def.nr#L33-L35 + + +Returns each field of this struct as a pair of (field name, field type). + +### has_named_attribute + +```rust title="has_named_attribute" showLineNumbers +comptime fn has_named_attribute(self, name: str) -> bool {} +``` +> Source code: noir_stdlib/src/meta/struct_def.nr#L20-L22 + + +Returns true if this struct has a custom attribute with the given name. + +### module + +```rust title="module" showLineNumbers +comptime fn module(self) -> Module {} +``` +> Source code: noir_stdlib/src/meta/struct_def.nr#L38-L40 + + +Returns the module where the struct is defined. + +### name + +```rust title="name" showLineNumbers +comptime fn name(self) -> Quoted {} +``` +> Source code: noir_stdlib/src/meta/struct_def.nr#L43-L45 + + +Returns the name of this struct + +Note that the returned quoted value will be just the struct name, it will +not be the full path to the struct, nor will it include any generics. + +### set_fields + +```rust title="set_fields" showLineNumbers +comptime fn set_fields(self, new_fields: [(Quoted, Type)]) {} +``` +> Source code: noir_stdlib/src/meta/struct_def.nr#L52-L54 + + +Sets the fields of this struct to the given fields list where each element +is a pair of the field's name and the field's type. Expects each field name +to be a single identifier. Note that this will override any previous fields +on this struct. If those should be preserved, use `.fields()` to retrieve the +current fields on the struct type and append the new fields from there. + +Example: + +```rust +// Change this struct to: +// struct Foo { +// a: u32, +// b: i8, +// } +#[mangle_fields] +struct Foo { x: Field } + +comptime fn mangle_fields(s: StructDefinition) { + s.set_fields(&[ + (quote { a }, quote { u32 }.as_type()), + (quote { b }, quote { i8 }.as_type()), + ]); +} +``` + +## Trait Implementations + +```rust +impl Eq for StructDefinition +impl Hash for StructDefinition +``` + +Note that each struct is assigned a unique ID internally and this is what is used for +equality and hashing. So even structs with identical generics and fields may not +be equal in this sense if they were originally different items in the source program. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/trait_constraint.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/trait_constraint.md new file mode 100644 index 00000000000..3106f732b5a --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/trait_constraint.md @@ -0,0 +1,17 @@ +--- +title: TraitConstraint +--- + +`std::meta::trait_constraint` contains methods on the built-in `TraitConstraint` type which represents +a trait constraint that can be used to search for a trait implementation. This is similar +syntactically to just the trait itself, but can also contain generic arguments. E.g. `Eq`, `Default`, +`BuildHasher`. + +This type currently has no public methods but it can be used alongside `Type` in `implements` or `get_trait_impl`. + +## Trait Implementations + +```rust +impl Eq for TraitConstraint +impl Hash for TraitConstraint +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/trait_def.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/trait_def.md new file mode 100644 index 00000000000..a1f363d46ff --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/trait_def.md @@ -0,0 +1,26 @@ +--- +title: TraitDefinition +--- + +`std::meta::trait_def` contains methods on the built-in `TraitDefinition` type. This type +represents trait definitions such as `trait Foo { .. }` at the top-level of a program. + +## Methods + +### as_trait_constraint + +```rust title="as_trait_constraint" showLineNumbers +comptime fn as_trait_constraint(_self: Self) -> TraitConstraint {} +``` +> Source code: noir_stdlib/src/meta/trait_def.nr#L6-L8 + + +Converts this trait into a trait constraint. If there are any generics on this +trait, they will be kept as-is without instantiating or replacing them. + +## Trait Implementations + +```rust +impl Eq for TraitDefinition +impl Hash for TraitDefinition +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/trait_impl.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/trait_impl.md new file mode 100644 index 00000000000..66d31ed2560 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/trait_impl.md @@ -0,0 +1,60 @@ +--- +title: TraitImpl +--- + +`std::meta::trait_impl` contains methods on the built-in `TraitImpl` type which represents a trait +implementation such as `impl Foo for Bar { ... }`. + +## Methods + +### trait_generic_args + +```rust title="trait_generic_args" showLineNumbers +comptime fn trait_generic_args(self) -> [Type] {} +``` +> Source code: noir_stdlib/src/meta/trait_impl.nr#L3-L5 + + +Returns any generic arguments on the trait of this trait implementation, if any. + +```rs +impl Foo for Bar { ... } + +comptime { + let bar_type = quote { Bar }.as_type(); + let foo = quote { Foo }.as_trait_constraint(); + + let my_impl: TraitImpl = bar_type.get_trait_impl(foo).unwrap(); + + let generics = my_impl.trait_generic_args(); + assert_eq(generics.len(), 2); + + assert_eq(generics[0], quote { i32 }.as_type()); + assert_eq(generics[1], quote { Field }.as_type()); +} +``` + +### methods + +```rust title="methods" showLineNumbers +comptime fn methods(self) -> [FunctionDefinition] {} +``` +> Source code: noir_stdlib/src/meta/trait_impl.nr#L8-L10 + + +Returns each method in this trait impl. + +Example: + +```rs +comptime { + let i32_type = quote { i32 }.as_type(); + let eq = quote { Eq }.as_trait_constraint(); + + let impl_eq_for_i32: TraitImpl = i32_type.get_trait_impl(eq).unwrap(); + let methods = impl_eq_for_i32.methods(); + + assert_eq(methods.len(), 1); + assert_eq(methods[0].name(), quote { eq }); +} +``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/typ.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/typ.md new file mode 100644 index 00000000000..6c9f4b8d087 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/typ.md @@ -0,0 +1,239 @@ +--- +title: Type +--- + +`std::meta::typ` contains methods on the built-in `Type` type used for representing +a type in the source program. + +## Functions + +```rust title="fresh_type_variable" showLineNumbers +pub comptime fn fresh_type_variable() -> Type {} +``` +> Source code: noir_stdlib/src/meta/typ.nr#L5-L7 + + +Creates and returns an unbound type variable. This is a special kind of type internal +to type checking which will type check with any other type. When it is type checked +against another type it will also be set to that type. For example, if `a` is a type +variable and we have the type equality `(a, i32) = (u8, i32)`, the compiler will set +`a` equal to `u8`. + +Unbound type variables will often be rendered as `_` while printing them. Bound type +variables will appear as the type they are bound to. + +This can be used in conjunction with functions which internally perform type checks +such as `Type::implements` or `Type::get_trait_impl` to potentially grab some of the types used. + +Note that calling `Type::implements` or `Type::get_trait_impl` on a type variable will always +fail. + +Example: + +```rust title="serialize-setup" showLineNumbers +trait Serialize {} + +impl Serialize<1> for Field {} + +impl Serialize for [T; N] + where T: Serialize {} + +impl Serialize for (T, U) + where T: Serialize, U: Serialize {} +``` +> Source code: test_programs/compile_success_empty/comptime_type/src/main.nr#L20-L30 + +```rust title="fresh-type-variable-example" showLineNumbers +let typevar1 = std::meta::typ::fresh_type_variable(); + let constraint = quote { Serialize<$typevar1> }.as_trait_constraint(); + let field_type = quote { Field }.as_type(); + + // Search for a trait impl (binding typevar1 to 1 when the impl is found): + assert(field_type.implements(constraint)); + + // typevar1 should be bound to the "1" generic now: + assert_eq(typevar1.as_constant().unwrap(), 1); + + // If we want to do the same with a different type, we need to + // create a new type variable now that `typevar1` is bound + let typevar2 = std::meta::typ::fresh_type_variable(); + let constraint = quote { Serialize<$typevar2> }.as_trait_constraint(); + let array_type = quote { [(Field, Field); 5] }.as_type(); + assert(array_type.implements(constraint)); + + // Now typevar2 should be bound to the serialized pair size 2 times the array length 5 + assert_eq(typevar2.as_constant().unwrap(), 10); +``` +> Source code: test_programs/compile_success_empty/comptime_type/src/main.nr#L130-L150 + + +## Methods + +### as_array + +```rust title="as_array" showLineNumbers +comptime fn as_array(self) -> Option<(Type, Type)> {} +``` +> Source code: noir_stdlib/src/meta/typ.nr#L11-L13 + + +If this type is an array, return a pair of (element type, size type). + +Example: + +```rust +comptime { + let array_type = quote { [Field; 3] }.as_type(); + let (field_type, three_type) = array_type.as_array().unwrap(); + + assert(field_type.is_field()); + assert_eq(three_type.as_constant().unwrap(), 3); +} +``` + +### as_constant + +```rust title="as_constant" showLineNumbers +comptime fn as_constant(self) -> Option {} +``` +> Source code: noir_stdlib/src/meta/typ.nr#L16-L18 + + +If this type is a constant integer (such as the `3` in the array type `[Field; 3]`), +return the numeric constant. + +### as_integer + +```rust title="as_integer" showLineNumbers +comptime fn as_integer(self) -> Option<(bool, u8)> {} +``` +> Source code: noir_stdlib/src/meta/typ.nr#L21-L23 + + +If this is an integer type, return a boolean which is `true` +if the type is signed, as well as the number of bits of this integer type. + +### as_slice + +```rust title="as_slice" showLineNumbers +comptime fn as_slice(self) -> Option {} +``` +> Source code: noir_stdlib/src/meta/typ.nr#L26-L28 + + +If this is a slice type, return the element type of the slice. + +### as_str + +```rust title="as_str" showLineNumbers +comptime fn as_str(self) -> Option {} +``` +> Source code: noir_stdlib/src/meta/typ.nr#L31-L33 + + +If this is a `str` type, returns the length `N` as a type. + +### as_struct + +```rust title="as_struct" showLineNumbers +comptime fn as_struct(self) -> Option<(StructDefinition, [Type])> {} +``` +> Source code: noir_stdlib/src/meta/typ.nr#L36-L38 + + +If this is a struct type, returns the struct in addition to +any generic arguments on this type. + +### as_tuple + +```rust title="as_tuple" showLineNumbers +comptime fn as_tuple(self) -> Option<[Type]> {} +``` +> Source code: noir_stdlib/src/meta/typ.nr#L41-L43 + + +If this is a tuple type, returns each element type of the tuple. + +### get_trait_impl + +```rust title="get_trait_impl" showLineNumbers +comptime fn get_trait_impl(self, constraint: TraitConstraint) -> Option {} +``` +> Source code: noir_stdlib/src/meta/typ.nr#L46-L48 + + +Retrieves the trait implementation that implements the given +trait constraint for this type. If the trait constraint is not +found, `None` is returned. Note that since the concrete trait implementation +for a trait constraint specified from a `where` clause is unknown, +this function will return `None` in these cases. If you only want to know +whether a type implements a trait, use `implements` instead. + +Example: + +```rust +comptime { + let field_type = quote { Field }.as_type(); + let default = quote { Default }.as_trait_constraint(); + + let the_impl: TraitImpl = field_type.get_trait_impl(default).unwrap(); + assert(the_impl.methods().len(), 1); +} +``` + +### implements + +```rust title="implements" showLineNumbers +comptime fn implements(self, constraint: TraitConstraint) -> bool {} +``` +> Source code: noir_stdlib/src/meta/typ.nr#L51-L53 + + +`true` if this type implements the given trait. Note that unlike +`get_trait_impl` this will also return true for any `where` constraints +in scope. + +Example: + +```rust +fn foo() where T: Default { + comptime { + let field_type = quote { Field }.as_type(); + let default = quote { Default }.as_trait_constraint(); + assert(field_type.implements(default)); + + let t = quote { T }.as_type(); + assert(t.implements(default)); + } +} +``` + +### is_bool + +```rust title="is_bool" showLineNumbers +comptime fn is_bool(self) -> bool {} +``` +> Source code: noir_stdlib/src/meta/typ.nr#L56-L58 + + +`true` if this type is `bool`. + +### is_field + +```rust title="is_field" showLineNumbers +comptime fn is_field(self) -> bool {} +``` +> Source code: noir_stdlib/src/meta/typ.nr#L61-L63 + + +`true` if this type is `Field`. + +## Trait Implementations + +```rust +impl Eq for Type +impl Hash for Type +``` +Note that this is syntactic equality, this is not the same as whether two types will type check +to be the same type. Unless type inference or generics are being used however, users should not +typically have to worry about this distinction unless `std::meta::typ::fresh_type_variable` is used. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/typed_expr.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/typed_expr.md new file mode 100644 index 00000000000..1ee71c8b064 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/typed_expr.md @@ -0,0 +1,27 @@ +--- +title: TypedExpr +--- + +`std::meta::typed_expr` contains methods on the built-in `TypedExpr` type for resolved and type-checked expressions. + +## Methods + +### get_type + +```rust title="as_function_definition" showLineNumbers +comptime fn as_function_definition(self) -> Option {} +``` +> Source code: noir_stdlib/src/meta/typed_expr.nr#L7-L9 + + +If this expression refers to a function definitions, returns it. Otherwise returns `Option::none()`. + +### get_type + +```rust title="get_type" showLineNumbers +comptime fn get_type(self) -> Option {} +``` +> Source code: noir_stdlib/src/meta/typed_expr.nr#L13-L15 + + +Returns the type of the expression, or `Option::none()` if there were errors when the expression was previously resolved. \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/unresolved_type.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/unresolved_type.md new file mode 100644 index 00000000000..d6f2b1494bb --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/meta/unresolved_type.md @@ -0,0 +1,17 @@ +--- +title: UnresolvedType +--- + +`std::meta::unresolved_type` contains methods on the built-in `UnresolvedType` type for the syntax of types. + +## Methods + +### is_field + +```rust title="is_field" showLineNumbers +comptime fn is_field(self) -> bool {} +``` +> Source code: noir_stdlib/src/meta/unresolved_type.nr#L3-L5 + + +Returns true if this type refers to the Field type. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/options.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/options.md new file mode 100644 index 00000000000..a1bd4e1de5f --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/options.md @@ -0,0 +1,101 @@ +--- +title: Option Type +--- + +The `Option` type is a way to express that a value might be present (`Some(T))` or absent (`None`). It's a safer way to handle potential absence of values, compared to using nulls in many other languages. + +```rust +struct Option { + None, + Some(T), +} +``` + +The `Option` type, already imported into your Noir program, can be used directly: + +```rust +fn main() { + let none = Option::none(); + let some = Option::some(3); +} +``` + +See [this test](https://github.com/noir-lang/noir/blob/5cbfb9c4a06c8865c98ff2b594464b037d821a5c/crates/nargo_cli/tests/test_data/option/src/main.nr) for a more comprehensive set of examples of each of the methods described below. + +## Methods + +### none + +Constructs a none value. + +### some + +Constructs a some wrapper around a given value. + +### is_none + +Returns true if the Option is None. + +### is_some + +Returns true of the Option is Some. + +### unwrap + +Asserts `self.is_some()` and returns the wrapped value. + +### unwrap_unchecked + +Returns the inner value without asserting `self.is_some()`. This method can be useful within an if condition when we already know that `option.is_some()`. If the option is None, there is no guarantee what value will be returned, only that it will be of type T for an `Option`. + +### unwrap_or + +Returns the wrapped value if `self.is_some()`. Otherwise, returns the given default value. + +### unwrap_or_else + +Returns the wrapped value if `self.is_some()`. Otherwise, calls the given function to return a default value. + +### expect + +Asserts `self.is_some()` with a provided custom message and returns the contained `Some` value. The custom message is expected to be a format string. + +### map + +If self is `Some(x)`, this returns `Some(f(x))`. Otherwise, this returns `None`. + +### map_or + +If self is `Some(x)`, this returns `f(x)`. Otherwise, this returns the given default value. + +### map_or_else + +If self is `Some(x)`, this returns `f(x)`. Otherwise, this returns `default()`. + +### and + +Returns None if self is None. Otherwise, this returns `other`. + +### and_then + +If self is None, this returns None. Otherwise, this calls the given function with the Some value contained within self, and returns the result of that call. In some languages this function is called `flat_map` or `bind`. + +### or + +If self is Some, return self. Otherwise, return `other`. + +### or_else + +If self is Some, return self. Otherwise, return `default()`. + +### xor + +If only one of the two Options is Some, return that option. Otherwise, if both options are Some or both are None, None is returned. + +### filter + +Returns `Some(x)` if self is `Some(x)` and `predicate(x)` is true. Otherwise, this returns `None`. + +### flatten + +Flattens an `Option>` into a `Option`. This returns `None` if the outer Option is None. Otherwise, this returns the inner Option. 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/docs/versioned_docs/version-v0.35.0/noir/standard_library/traits.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/traits.md new file mode 100644 index 00000000000..74ed6276234 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/traits.md @@ -0,0 +1,625 @@ +--- +title: Traits +description: Noir's stdlib provides a few commonly used traits. +keywords: [traits, trait, interface, protocol, default, add, eq] +--- + +## `std::default` + +### `std::default::Default` + +```rust title="default-trait" showLineNumbers +pub trait Default { + fn default() -> Self; +} +``` +> Source code: noir_stdlib/src/default.nr#L4-L8 + + +Constructs a default value of a type. + +Implementations: +```rust +impl Default for Field { .. } + +impl Default for i8 { .. } +impl Default for i16 { .. } +impl Default for i32 { .. } +impl Default for i64 { .. } + +impl Default for u8 { .. } +impl Default for u16 { .. } +impl Default for u32 { .. } +impl Default for u64 { .. } + +impl Default for () { .. } +impl Default for bool { .. } + +impl Default for [T; N] + where T: Default { .. } + +impl Default for [T] { .. } + +impl Default for (A, B) + where A: Default, B: Default { .. } + +impl Default for (A, B, C) + where A: Default, B: Default, C: Default { .. } + +impl Default for (A, B, C, D) + where A: Default, B: Default, C: Default, D: Default { .. } + +impl Default for (A, B, C, D, E) + where A: Default, B: Default, C: Default, D: Default, E: Default { .. } +``` + +For primitive integer types, the return value of `default` is `0`. Container +types such as arrays are filled with default values of their element type, +except slices whose length is unknown and thus defaulted to zero. + +--- + +## `std::convert` + +### `std::convert::From` + +```rust title="from-trait" showLineNumbers +pub trait From { + fn from(input: T) -> Self; +} +``` +> Source code: noir_stdlib/src/convert.nr#L1-L5 + + +The `From` trait defines how to convert from a given type `T` to the type on which the trait is implemented. + +The Noir standard library provides a number of implementations of `From` between primitive types. +```rust title="from-impls" showLineNumbers +// Unsigned integers + +impl From for u32 { + fn from(value: u8) -> u32 { + value as u32 + } +} + +impl From for u64 { + fn from(value: u8) -> u64 { + value as u64 + } +} +impl From for u64 { + fn from(value: u32) -> u64 { + value as u64 + } +} + +impl From for Field { + fn from(value: u8) -> Field { + value as Field + } +} +impl From for Field { + fn from(value: u32) -> Field { + value as Field + } +} +impl From for Field { + fn from(value: u64) -> Field { + value as Field + } +} + +// Signed integers + +impl From for i32 { + fn from(value: i8) -> i32 { + value as i32 + } +} + +impl From for i64 { + fn from(value: i8) -> i64 { + value as i64 + } +} +impl From for i64 { + fn from(value: i32) -> i64 { + value as i64 + } +} + +// Booleans +impl From for u8 { + fn from(value: bool) -> u8 { + value as u8 + } +} +impl From for u32 { + fn from(value: bool) -> u32 { + value as u32 + } +} +impl From for u64 { + fn from(value: bool) -> u64 { + value as u64 + } +} +impl From for i8 { + fn from(value: bool) -> i8 { + value as i8 + } +} +impl From for i32 { + fn from(value: bool) -> i32 { + value as i32 + } +} +impl From for i64 { + fn from(value: bool) -> i64 { + value as i64 + } +} +impl From for Field { + fn from(value: bool) -> Field { + value as Field + } +} +``` +> Source code: noir_stdlib/src/convert.nr#L25-L116 + + +#### When to implement `From` + +As a general rule of thumb, `From` may be implemented in the [situations where it would be suitable in Rust](https://doc.rust-lang.org/std/convert/trait.From.html#when-to-implement-from): + +- The conversion is *infallible*: Noir does not provide an equivalent to Rust's `TryFrom`, if the conversion can fail then provide a named method instead. +- The conversion is *lossless*: semantically, it should not lose or discard information. For example, `u32: From` can losslessly convert any `u16` into a valid `u32` such that the original `u16` can be recovered. On the other hand, `u16: From` should not be implemented as `2**16` is a `u32` which cannot be losslessly converted into a `u16`. +- The conversion is *value-preserving*: the conceptual kind and meaning of the resulting value is the same, even though the Noir type and technical representation might be different. While it's possible to infallibly and losslessly convert a `u8` into a `str<2>` hex representation, `4u8` and `"04"` are too different for `str<2>: From` to be implemented. +- The conversion is *obvious*: it's the only reasonable conversion between the two types. If there's ambiguity on how to convert between them such that the same input could potentially map to two different values then a named method should be used. For instance rather than implementing `U128: From<[u8; 16]>`, the methods `U128::from_le_bytes` and `U128::from_be_bytes` are used as otherwise the endianness of the array would be ambiguous, resulting in two potential values of `U128` from the same byte array. + +One additional recommendation specific to Noir is: +- The conversion is *efficient*: it's relatively cheap to convert between the two types. Due to being a ZK DSL, it's more important to avoid unnecessary computation compared to Rust. If the implementation of `From` would encourage users to perform unnecessary conversion, resulting in additional proving time, then it may be preferable to expose functionality such that this conversion may be avoided. + +### `std::convert::Into` + +The `Into` trait is defined as the reciprocal of `From`. It should be easy to convince yourself that if we can convert to type `A` from type `B`, then it's possible to convert type `B` into type `A`. + +For this reason, implementing `From` on a type will automatically generate a matching `Into` implementation. One should always prefer implementing `From` over `Into` as implementing `Into` will not generate a matching `From` implementation. + +```rust title="into-trait" showLineNumbers +pub trait Into { + fn into(self) -> T; +} + +impl Into for U where T: From { + fn into(self) -> T { + T::from(self) + } +} +``` +> Source code: noir_stdlib/src/convert.nr#L13-L23 + + +`Into` is most useful when passing function arguments where the types don't quite match up with what the function expects. In this case, the compiler has enough type information to perform the necessary conversion by just appending `.into()` onto the arguments in question. + +--- + +## `std::cmp` + +### `std::cmp::Eq` + +```rust title="eq-trait" showLineNumbers +pub trait Eq { + fn eq(self, other: Self) -> bool; +} +``` +> Source code: noir_stdlib/src/cmp.nr#L4-L8 + + +Returns `true` if `self` is equal to `other`. Implementing this trait on a type +allows the type to be used with `==` and `!=`. + +Implementations: +```rust +impl Eq for Field { .. } + +impl Eq for i8 { .. } +impl Eq for i16 { .. } +impl Eq for i32 { .. } +impl Eq for i64 { .. } + +impl Eq for u8 { .. } +impl Eq for u16 { .. } +impl Eq for u32 { .. } +impl Eq for u64 { .. } + +impl Eq for () { .. } +impl Eq for bool { .. } + +impl Eq for [T; N] + where T: Eq { .. } + +impl Eq for [T] + where T: Eq { .. } + +impl Eq for (A, B) + where A: Eq, B: Eq { .. } + +impl Eq for (A, B, C) + where A: Eq, B: Eq, C: Eq { .. } + +impl Eq for (A, B, C, D) + where A: Eq, B: Eq, C: Eq, D: Eq { .. } + +impl Eq for (A, B, C, D, E) + where A: Eq, B: Eq, C: Eq, D: Eq, E: Eq { .. } +``` + +### `std::cmp::Ord` + +```rust title="ord-trait" showLineNumbers +pub trait Ord { + fn cmp(self, other: Self) -> Ordering; +} +``` +> Source code: noir_stdlib/src/cmp.nr#L175-L179 + + +`a.cmp(b)` compares two values returning `Ordering::less()` if `a < b`, +`Ordering::equal()` if `a == b`, or `Ordering::greater()` if `a > b`. +Implementing this trait on a type allows `<`, `<=`, `>`, and `>=` to be +used on values of the type. + +`std::cmp` also provides `max` and `min` functions for any type which implements the `Ord` trait. + +Implementations: + +```rust +impl Ord for u8 { .. } +impl Ord for u16 { .. } +impl Ord for u32 { .. } +impl Ord for u64 { .. } + +impl Ord for i8 { .. } +impl Ord for i16 { .. } +impl Ord for i32 { .. } + +impl Ord for i64 { .. } + +impl Ord for () { .. } +impl Ord for bool { .. } + +impl Ord for [T; N] + where T: Ord { .. } + +impl Ord for [T] + where T: Ord { .. } + +impl Ord for (A, B) + where A: Ord, B: Ord { .. } + +impl Ord for (A, B, C) + where A: Ord, B: Ord, C: Ord { .. } + +impl Ord for (A, B, C, D) + where A: Ord, B: Ord, C: Ord, D: Ord { .. } + +impl Ord for (A, B, C, D, E) + where A: Ord, B: Ord, C: Ord, D: Ord, E: Ord { .. } +``` + +--- + +## `std::ops` + +### `std::ops::Add`, `std::ops::Sub`, `std::ops::Mul`, and `std::ops::Div` + +These traits abstract over addition, subtraction, multiplication, and division respectively. +Implementing these traits for a given type will also allow that type to be used with the corresponding operator +for that trait (`+` for Add, etc) in addition to the normal method names. + +```rust title="add-trait" showLineNumbers +pub trait Add { + fn add(self, other: Self) -> Self; +} +``` +> Source code: noir_stdlib/src/ops/arith.nr#L1-L5 + +```rust title="sub-trait" showLineNumbers +pub trait Sub { + fn sub(self, other: Self) -> Self; +} +``` +> Source code: noir_stdlib/src/ops/arith.nr#L60-L64 + +```rust title="mul-trait" showLineNumbers +pub trait Mul { + fn mul(self, other: Self) -> Self; +} +``` +> Source code: noir_stdlib/src/ops/arith.nr#L119-L123 + +```rust title="div-trait" showLineNumbers +pub trait Div { + fn div(self, other: Self) -> Self; +} +``` +> Source code: noir_stdlib/src/ops/arith.nr#L178-L182 + + +The implementations block below is given for the `Add` trait, but the same types that implement +`Add` also implement `Sub`, `Mul`, and `Div`. + +Implementations: +```rust +impl Add for Field { .. } + +impl Add for i8 { .. } +impl Add for i16 { .. } +impl Add for i32 { .. } +impl Add for i64 { .. } + +impl Add for u8 { .. } +impl Add for u16 { .. } +impl Add for u32 { .. } +impl Add for u64 { .. } +``` + +### `std::ops::Rem` + +```rust title="rem-trait" showLineNumbers +pub trait Rem { + fn rem(self, other: Self) -> Self; +} +``` +> Source code: noir_stdlib/src/ops/arith.nr#L237-L241 + + +`Rem::rem(a, b)` is the remainder function returning the result of what is +left after dividing `a` and `b`. Implementing `Rem` allows the `%` operator +to be used with the implementation type. + +Unlike other numeric traits, `Rem` is not implemented for `Field`. + +Implementations: +```rust +impl Rem for u8 { fn rem(self, other: u8) -> u8 { self % other } } +impl Rem for u16 { fn rem(self, other: u16) -> u16 { self % other } } +impl Rem for u32 { fn rem(self, other: u32) -> u32 { self % other } } +impl Rem for u64 { fn rem(self, other: u64) -> u64 { self % other } } + +impl Rem for i8 { fn rem(self, other: i8) -> i8 { self % other } } +impl Rem for i16 { fn rem(self, other: i16) -> i16 { self % other } } +impl Rem for i32 { fn rem(self, other: i32) -> i32 { self % other } } +impl Rem for i64 { fn rem(self, other: i64) -> i64 { self % other } } +``` + +### `std::ops::Neg` + +```rust title="neg-trait" showLineNumbers +pub trait Neg { + fn neg(self) -> Self; +} +``` +> Source code: noir_stdlib/src/ops/arith.nr#L290-L294 + + +`Neg::neg` is equivalent to the unary negation operator `-`. + +Implementations: +```rust title="neg-trait-impls" showLineNumbers +impl Neg for Field { + fn neg(self) -> Field { + -self + } +} + +impl Neg for i8 { + fn neg(self) -> i8 { + -self + } +} +impl Neg for i16 { + fn neg(self) -> i16 { + -self + } +} +impl Neg for i32 { + fn neg(self) -> i32 { + -self + } +} +impl Neg for i64 { + fn neg(self) -> i64 { + -self + } +} +``` +> Source code: noir_stdlib/src/ops/arith.nr#L296-L323 + + +### `std::ops::Not` + +```rust title="not-trait" showLineNumbers +pub trait Not { + fn not(self: Self) -> Self; +} +``` +> Source code: noir_stdlib/src/ops/bit.nr#L1-L5 + + +`Not::not` is equivalent to the unary bitwise NOT operator `!`. + +Implementations: +```rust title="not-trait-impls" showLineNumbers +impl Not for bool { + fn not(self) -> bool { + !self + } +} + +impl Not for u64 { + fn not(self) -> u64 { + !self + } +} +impl Not for u32 { + fn not(self) -> u32 { + !self + } +} +impl Not for u16 { + fn not(self) -> u16 { + !self + } +} +impl Not for u8 { + fn not(self) -> u8 { + !self + } +} +impl Not for u1 { + fn not(self) -> u1 { + !self + } +} + +impl Not for i8 { + fn not(self) -> i8 { + !self + } +} +impl Not for i16 { + fn not(self) -> i16 { + !self + } +} +impl Not for i32 { + fn not(self) -> i32 { + !self + } +} +impl Not for i64 { + fn not(self) -> i64 { + !self + } +} +``` +> Source code: noir_stdlib/src/ops/bit.nr#L7-L60 + + +### `std::ops::{ BitOr, BitAnd, BitXor }` + +```rust title="bitor-trait" showLineNumbers +pub trait BitOr { + fn bitor(self, other: Self) -> Self; +} +``` +> Source code: noir_stdlib/src/ops/bit.nr#L62-L66 + +```rust title="bitand-trait" showLineNumbers +pub trait BitAnd { + fn bitand(self, other: Self) -> Self; +} +``` +> Source code: noir_stdlib/src/ops/bit.nr#L121-L125 + +```rust title="bitxor-trait" showLineNumbers +pub trait BitXor { + fn bitxor(self, other: Self) -> Self; +} +``` +> Source code: noir_stdlib/src/ops/bit.nr#L180-L184 + + +Traits for the bitwise operations `|`, `&`, and `^`. + +Implementing `BitOr`, `BitAnd` or `BitXor` for a type allows the `|`, `&`, or `^` operator respectively +to be used with the type. + +The implementations block below is given for the `BitOr` trait, but the same types that implement +`BitOr` also implement `BitAnd` and `BitXor`. + +Implementations: +```rust +impl BitOr for bool { fn bitor(self, other: bool) -> bool { self | other } } + +impl BitOr for u8 { fn bitor(self, other: u8) -> u8 { self | other } } +impl BitOr for u16 { fn bitor(self, other: u16) -> u16 { self | other } } +impl BitOr for u32 { fn bitor(self, other: u32) -> u32 { self | other } } +impl BitOr for u64 { fn bitor(self, other: u64) -> u64 { self | other } } + +impl BitOr for i8 { fn bitor(self, other: i8) -> i8 { self | other } } +impl BitOr for i16 { fn bitor(self, other: i16) -> i16 { self | other } } +impl BitOr for i32 { fn bitor(self, other: i32) -> i32 { self | other } } +impl BitOr for i64 { fn bitor(self, other: i64) -> i64 { self | other } } +``` + +### `std::ops::{ Shl, Shr }` + +```rust title="shl-trait" showLineNumbers +pub trait Shl { + fn shl(self, other: u8) -> Self; +} +``` +> Source code: noir_stdlib/src/ops/bit.nr#L239-L243 + +```rust title="shr-trait" showLineNumbers +pub trait Shr { + fn shr(self, other: u8) -> Self; +} +``` +> Source code: noir_stdlib/src/ops/bit.nr#L292-L296 + + +Traits for a bit shift left and bit shift right. + +Implementing `Shl` for a type allows the left shift operator (`<<`) to be used with the implementation type. +Similarly, implementing `Shr` allows the right shift operator (`>>`) to be used with the type. + +Note that bit shifting is not currently implemented for signed types. + +The implementations block below is given for the `Shl` trait, but the same types that implement +`Shl` also implement `Shr`. + +Implementations: +```rust +impl Shl for u8 { fn shl(self, other: u8) -> u8 { self << other } } +impl Shl for u16 { fn shl(self, other: u16) -> u16 { self << other } } +impl Shl for u32 { fn shl(self, other: u32) -> u32 { self << other } } +impl Shl for u64 { fn shl(self, other: u64) -> u64 { self << other } } +``` + +--- + +## `std::append` + +### `std::append::Append` + +`Append` can abstract over types that can be appended to - usually container types: + +```rust title="append-trait" showLineNumbers +pub trait Append { + fn empty() -> Self; + fn append(self, other: Self) -> Self; +} +``` +> Source code: noir_stdlib/src/append.nr#L9-L14 + + +`Append` requires two methods: + +- `empty`: Constructs an empty value of `Self`. +- `append`: Append two values together, returning the result. + +Additionally, it is expected that for any implementation: + +- `T::empty().append(x) == x` +- `x.append(T::empty()) == x` + +Implementations: +```rust +impl Append for [T] +impl Append for Quoted +``` diff --git a/noir/noir-repo/docs/docs/noir/standard_library/zeroed.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/zeroed.md similarity index 100% rename from noir/noir-repo/docs/docs/noir/standard_library/zeroed.md rename to noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/zeroed.md diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/backend_barretenberg/.nojekyll b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/backend_barretenberg/.nojekyll new file mode 100644 index 00000000000..e2ac6616add --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/backend_barretenberg/.nojekyll @@ -0,0 +1 @@ +TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/backend_barretenberg/classes/BarretenbergBackend.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/backend_barretenberg/classes/BarretenbergBackend.md new file mode 100644 index 00000000000..252d72a0b71 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/backend_barretenberg/classes/BarretenbergBackend.md @@ -0,0 +1,138 @@ +# BarretenbergBackend + +## Implements + +- [`Backend`](../index.md#backend) +- [`Backend`](../index.md#backend) + +## Constructors + +### new BarretenbergBackend(acirCircuit, options) + +```ts +new BarretenbergBackend(acirCircuit, options): BarretenbergBackend +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `acirCircuit` | `CompiledCircuit` | +| `options` | [`BackendOptions`](../type-aliases/BackendOptions.md) | + +#### Returns + +[`BarretenbergBackend`](BarretenbergBackend.md) + +## Properties + +| Property | Type | Description | +| :------ | :------ | :------ | +| `backend` | `UltraPlonkBackend` | - | + +## Methods + +### destroy() + +```ts +destroy(): Promise +``` + +#### Returns + +`Promise`\<`void`\> + +*** + +### generateProof() + +```ts +generateProof(compressedWitness): Promise +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `compressedWitness` | `Uint8Array` | + +#### Returns + +`Promise`\<`ProofData`\> + +#### Description + +Generates a proof + +*** + +### generateRecursiveProofArtifacts() + +```ts +generateRecursiveProofArtifacts(proofData, numOfPublicInputs): Promise +``` + +Generates artifacts that will be passed to a circuit that will verify this proof. + +Instead of passing the proof and verification key as a byte array, we pass them +as fields which makes it cheaper to verify in a circuit. + +The proof that is passed here will have been created using a circuit +that has the #[recursive] attribute on its `main` method. + +The number of public inputs denotes how many public inputs are in the inner proof. + +#### Parameters + +| Parameter | Type | Default value | +| :------ | :------ | :------ | +| `proofData` | `ProofData` | `undefined` | +| `numOfPublicInputs` | `number` | `0` | + +#### Returns + +`Promise`\<`object`\> + +#### Example + +```typescript +const artifacts = await backend.generateRecursiveProofArtifacts(proof, numOfPublicInputs); +``` + +*** + +### getVerificationKey() + +```ts +getVerificationKey(): Promise +``` + +#### Returns + +`Promise`\<`Uint8Array`\> + +*** + +### verifyProof() + +```ts +verifyProof(proofData): Promise +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `proofData` | `ProofData` | + +#### Returns + +`Promise`\<`boolean`\> + +#### Description + +Verifies a proof + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/backend_barretenberg/classes/BarretenbergVerifier.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/backend_barretenberg/classes/BarretenbergVerifier.md new file mode 100644 index 00000000000..500276ea748 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/backend_barretenberg/classes/BarretenbergVerifier.md @@ -0,0 +1,58 @@ +# BarretenbergVerifier + +## Constructors + +### new BarretenbergVerifier(options) + +```ts +new BarretenbergVerifier(options): BarretenbergVerifier +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `options` | [`BackendOptions`](../type-aliases/BackendOptions.md) | + +#### Returns + +[`BarretenbergVerifier`](BarretenbergVerifier.md) + +## Methods + +### destroy() + +```ts +destroy(): Promise +``` + +#### Returns + +`Promise`\<`void`\> + +*** + +### verifyProof() + +```ts +verifyProof(proofData, verificationKey): Promise +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `proofData` | `ProofData` | +| `verificationKey` | `Uint8Array` | + +#### Returns + +`Promise`\<`boolean`\> + +#### Description + +Verifies a proof + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/backend_barretenberg/classes/UltraHonkBackend.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/backend_barretenberg/classes/UltraHonkBackend.md new file mode 100644 index 00000000000..204aaa18db6 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/backend_barretenberg/classes/UltraHonkBackend.md @@ -0,0 +1,114 @@ +# UltraHonkBackend + +## Implements + +- [`Backend`](../index.md#backend) +- [`Backend`](../index.md#backend) + +## Constructors + +### new UltraHonkBackend(acirCircuit, options) + +```ts +new UltraHonkBackend(acirCircuit, options): UltraHonkBackend +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `acirCircuit` | `CompiledCircuit` | +| `options` | [`BackendOptions`](../type-aliases/BackendOptions.md) | + +#### Returns + +[`UltraHonkBackend`](UltraHonkBackend.md) + +## Properties + +| Property | Type | Description | +| :------ | :------ | :------ | +| `backend` | `UltraHonkBackend` | - | + +## Methods + +### destroy() + +```ts +destroy(): Promise +``` + +#### Returns + +`Promise`\<`void`\> + +*** + +### generateProof() + +```ts +generateProof(compressedWitness): Promise +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `compressedWitness` | `Uint8Array` | + +#### Returns + +`Promise`\<`ProofData`\> + +*** + +### generateRecursiveProofArtifacts() + +```ts +generateRecursiveProofArtifacts(proofData, numOfPublicInputs): Promise +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `proofData` | `ProofData` | +| `numOfPublicInputs` | `number` | + +#### Returns + +`Promise`\<`object`\> + +*** + +### getVerificationKey() + +```ts +getVerificationKey(): Promise +``` + +#### Returns + +`Promise`\<`Uint8Array`\> + +*** + +### verifyProof() + +```ts +verifyProof(proofData): Promise +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `proofData` | `ProofData` | + +#### Returns + +`Promise`\<`boolean`\> + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/backend_barretenberg/classes/UltraHonkVerifier.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/backend_barretenberg/classes/UltraHonkVerifier.md new file mode 100644 index 00000000000..aee9460153f --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/backend_barretenberg/classes/UltraHonkVerifier.md @@ -0,0 +1,58 @@ +# UltraHonkVerifier + +## Constructors + +### new UltraHonkVerifier(options) + +```ts +new UltraHonkVerifier(options): UltraHonkVerifier +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `options` | [`BackendOptions`](../type-aliases/BackendOptions.md) | + +#### Returns + +[`UltraHonkVerifier`](UltraHonkVerifier.md) + +## Methods + +### destroy() + +```ts +destroy(): Promise +``` + +#### Returns + +`Promise`\<`void`\> + +*** + +### verifyProof() + +```ts +verifyProof(proofData, verificationKey): Promise +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `proofData` | `ProofData` | +| `verificationKey` | `Uint8Array` | + +#### Returns + +`Promise`\<`boolean`\> + +#### Description + +Verifies a proof + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/backend_barretenberg/index.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/backend_barretenberg/index.md new file mode 100644 index 00000000000..4699e16dee6 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/backend_barretenberg/index.md @@ -0,0 +1,42 @@ +# backend_barretenberg + +## Exports + +### Classes + +| Class | Description | +| :------ | :------ | +| [BarretenbergBackend](classes/BarretenbergBackend.md) | - | +| [BarretenbergVerifier](classes/BarretenbergVerifier.md) | - | +| [UltraHonkBackend](classes/UltraHonkBackend.md) | - | +| [UltraHonkVerifier](classes/UltraHonkVerifier.md) | - | + +### Type Aliases + +| Type alias | Description | +| :------ | :------ | +| [BackendOptions](type-aliases/BackendOptions.md) | - | + +## References + +### CompiledCircuit + +Renames and re-exports [Backend](index.md#backend) + +*** + +### ProofData + +Renames and re-exports [Backend](index.md#backend) + +## Variables + +### Backend + +```ts +Backend: any; +``` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/backend_barretenberg/type-aliases/BackendOptions.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/backend_barretenberg/type-aliases/BackendOptions.md new file mode 100644 index 00000000000..99ade2ce6f7 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/backend_barretenberg/type-aliases/BackendOptions.md @@ -0,0 +1,18 @@ +# BackendOptions + +```ts +type BackendOptions: object; +``` + +## Type declaration + +| Member | Type | Description | +| :------ | :------ | :------ | +| `memory` | `object` | - | +| `memory.initial` | `number` | - | +| `memory.maximum` | `number` | - | +| `threads` | `number` | - | + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/backend_barretenberg/typedoc-sidebar.cjs b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/backend_barretenberg/typedoc-sidebar.cjs new file mode 100644 index 00000000000..8ecf05c0163 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/backend_barretenberg/typedoc-sidebar.cjs @@ -0,0 +1,4 @@ +// @ts-check +/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ +const typedocSidebar = { items: [{"type":"category","label":"Classes","items":[{"type":"doc","id":"reference/NoirJS/backend_barretenberg/classes/BarretenbergBackend","label":"BarretenbergBackend"},{"type":"doc","id":"reference/NoirJS/backend_barretenberg/classes/BarretenbergVerifier","label":"BarretenbergVerifier"},{"type":"doc","id":"reference/NoirJS/backend_barretenberg/classes/UltraHonkBackend","label":"UltraHonkBackend"},{"type":"doc","id":"reference/NoirJS/backend_barretenberg/classes/UltraHonkVerifier","label":"UltraHonkVerifier"}]},{"type":"category","label":"Type Aliases","items":[{"type":"doc","id":"reference/NoirJS/backend_barretenberg/type-aliases/BackendOptions","label":"BackendOptions"}]}]}; +module.exports = typedocSidebar.items; \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/.nojekyll b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/.nojekyll new file mode 100644 index 00000000000..e2ac6616add --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/.nojekyll @@ -0,0 +1 @@ +TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/classes/Noir.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/classes/Noir.md new file mode 100644 index 00000000000..ead255bc504 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/classes/Noir.md @@ -0,0 +1,52 @@ +# Noir + +## Constructors + +### new Noir(circuit) + +```ts +new Noir(circuit): Noir +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `circuit` | `CompiledCircuit` | + +#### Returns + +[`Noir`](Noir.md) + +## Methods + +### execute() + +```ts +execute(inputs, foreignCallHandler?): Promise +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `inputs` | `InputMap` | +| `foreignCallHandler`? | [`ForeignCallHandler`](../type-aliases/ForeignCallHandler.md) | + +#### Returns + +`Promise`\<`object`\> + +#### Description + +Allows to execute a circuit to get its witness and return value. + +#### Example + +```typescript +async execute(inputs) +``` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/functions/and.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/functions/and.md new file mode 100644 index 00000000000..c783283e396 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/functions/and.md @@ -0,0 +1,22 @@ +# and() + +```ts +and(lhs, rhs): string +``` + +Performs a bitwise AND operation between `lhs` and `rhs` + +## Parameters + +| Parameter | Type | Description | +| :------ | :------ | :------ | +| `lhs` | `string` | | +| `rhs` | `string` | | + +## Returns + +`string` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/functions/blake2s256.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/functions/blake2s256.md new file mode 100644 index 00000000000..7882d0da8d5 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/functions/blake2s256.md @@ -0,0 +1,21 @@ +# blake2s256() + +```ts +blake2s256(inputs): Uint8Array +``` + +Calculates the Blake2s256 hash of the input bytes + +## Parameters + +| Parameter | Type | Description | +| :------ | :------ | :------ | +| `inputs` | `Uint8Array` | | + +## Returns + +`Uint8Array` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/functions/ecdsa_secp256k1_verify.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/functions/ecdsa_secp256k1_verify.md new file mode 100644 index 00000000000..5e3cd53e9d3 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/functions/ecdsa_secp256k1_verify.md @@ -0,0 +1,28 @@ +# ecdsa\_secp256k1\_verify() + +```ts +ecdsa_secp256k1_verify( + hashed_msg, + public_key_x_bytes, + public_key_y_bytes, + signature): boolean +``` + +Verifies a ECDSA signature over the secp256k1 curve. + +## Parameters + +| Parameter | Type | Description | +| :------ | :------ | :------ | +| `hashed_msg` | `Uint8Array` | | +| `public_key_x_bytes` | `Uint8Array` | | +| `public_key_y_bytes` | `Uint8Array` | | +| `signature` | `Uint8Array` | | + +## Returns + +`boolean` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/functions/ecdsa_secp256r1_verify.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/functions/ecdsa_secp256r1_verify.md new file mode 100644 index 00000000000..0b20ff68957 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/functions/ecdsa_secp256r1_verify.md @@ -0,0 +1,28 @@ +# ecdsa\_secp256r1\_verify() + +```ts +ecdsa_secp256r1_verify( + hashed_msg, + public_key_x_bytes, + public_key_y_bytes, + signature): boolean +``` + +Verifies a ECDSA signature over the secp256r1 curve. + +## Parameters + +| Parameter | Type | Description | +| :------ | :------ | :------ | +| `hashed_msg` | `Uint8Array` | | +| `public_key_x_bytes` | `Uint8Array` | | +| `public_key_y_bytes` | `Uint8Array` | | +| `signature` | `Uint8Array` | | + +## Returns + +`boolean` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/functions/keccak256.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/functions/keccak256.md new file mode 100644 index 00000000000..d10f155ce86 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/functions/keccak256.md @@ -0,0 +1,21 @@ +# keccak256() + +```ts +keccak256(inputs): Uint8Array +``` + +Calculates the Keccak256 hash of the input bytes + +## Parameters + +| Parameter | Type | Description | +| :------ | :------ | :------ | +| `inputs` | `Uint8Array` | | + +## Returns + +`Uint8Array` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/functions/xor.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/functions/xor.md new file mode 100644 index 00000000000..8d762b895d3 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/functions/xor.md @@ -0,0 +1,22 @@ +# xor() + +```ts +xor(lhs, rhs): string +``` + +Performs a bitwise XOR operation between `lhs` and `rhs` + +## Parameters + +| Parameter | Type | Description | +| :------ | :------ | :------ | +| `lhs` | `string` | | +| `rhs` | `string` | | + +## Returns + +`string` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/index.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/index.md new file mode 100644 index 00000000000..b65a44865ee --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/index.md @@ -0,0 +1,48 @@ +# noir_js + +## Exports + +### Classes + +| Class | Description | +| :------ | :------ | +| [Noir](classes/Noir.md) | - | + +### Type Aliases + +| Type alias | Description | +| :------ | :------ | +| [ErrorWithPayload](type-aliases/ErrorWithPayload.md) | - | +| [ForeignCallHandler](type-aliases/ForeignCallHandler.md) | A callback which performs an foreign call and returns the response. | +| [ForeignCallInput](type-aliases/ForeignCallInput.md) | - | +| [ForeignCallOutput](type-aliases/ForeignCallOutput.md) | - | +| [WitnessMap](type-aliases/WitnessMap.md) | - | + +### Functions + +| Function | Description | +| :------ | :------ | +| [and](functions/and.md) | Performs a bitwise AND operation between `lhs` and `rhs` | +| [blake2s256](functions/blake2s256.md) | Calculates the Blake2s256 hash of the input bytes | +| [ecdsa\_secp256k1\_verify](functions/ecdsa_secp256k1_verify.md) | Verifies a ECDSA signature over the secp256k1 curve. | +| [ecdsa\_secp256r1\_verify](functions/ecdsa_secp256r1_verify.md) | Verifies a ECDSA signature over the secp256r1 curve. | +| [keccak256](functions/keccak256.md) | Calculates the Keccak256 hash of the input bytes | +| [xor](functions/xor.md) | Performs a bitwise XOR operation between `lhs` and `rhs` | + +## References + +### CompiledCircuit + +Renames and re-exports [InputMap](index.md#inputmap) + +## Variables + +### InputMap + +```ts +InputMap: any; +``` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/type-aliases/ErrorWithPayload.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/type-aliases/ErrorWithPayload.md new file mode 100644 index 00000000000..e8c2f4aef3d --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/type-aliases/ErrorWithPayload.md @@ -0,0 +1,15 @@ +# ErrorWithPayload + +```ts +type ErrorWithPayload: ExecutionError & object; +``` + +## Type declaration + +| Member | Type | Description | +| :------ | :------ | :------ | +| `decodedAssertionPayload` | `any` | - | + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/type-aliases/ForeignCallHandler.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/type-aliases/ForeignCallHandler.md new file mode 100644 index 00000000000..812b8b16481 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/type-aliases/ForeignCallHandler.md @@ -0,0 +1,24 @@ +# ForeignCallHandler + +```ts +type ForeignCallHandler: (name, inputs) => Promise; +``` + +A callback which performs an foreign call and returns the response. + +## Parameters + +| Parameter | Type | Description | +| :------ | :------ | :------ | +| `name` | `string` | The identifier for the type of foreign call being performed. | +| `inputs` | [`ForeignCallInput`](ForeignCallInput.md)[] | An array of hex encoded inputs to the foreign call. | + +## Returns + +`Promise`\<[`ForeignCallOutput`](ForeignCallOutput.md)[]\> + +outputs - An array of hex encoded outputs containing the results of the foreign call. + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/type-aliases/ForeignCallInput.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/type-aliases/ForeignCallInput.md new file mode 100644 index 00000000000..dd95809186a --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/type-aliases/ForeignCallInput.md @@ -0,0 +1,9 @@ +# ForeignCallInput + +```ts +type ForeignCallInput: string[]; +``` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/type-aliases/ForeignCallOutput.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/type-aliases/ForeignCallOutput.md new file mode 100644 index 00000000000..b71fb78a946 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/type-aliases/ForeignCallOutput.md @@ -0,0 +1,9 @@ +# ForeignCallOutput + +```ts +type ForeignCallOutput: string | string[]; +``` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/type-aliases/WitnessMap.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/type-aliases/WitnessMap.md new file mode 100644 index 00000000000..258c46f9d0c --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/type-aliases/WitnessMap.md @@ -0,0 +1,9 @@ +# WitnessMap + +```ts +type WitnessMap: Map; +``` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/typedoc-sidebar.cjs b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/typedoc-sidebar.cjs new file mode 100644 index 00000000000..9c7fc4a353f --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_js/typedoc-sidebar.cjs @@ -0,0 +1,4 @@ +// @ts-check +/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ +const typedocSidebar = { items: [{"type":"category","label":"Classes","items":[{"type":"doc","id":"reference/NoirJS/noir_js/classes/Noir","label":"Noir"}]},{"type":"category","label":"Type Aliases","items":[{"type":"doc","id":"reference/NoirJS/noir_js/type-aliases/ErrorWithPayload","label":"ErrorWithPayload"},{"type":"doc","id":"reference/NoirJS/noir_js/type-aliases/ForeignCallHandler","label":"ForeignCallHandler"},{"type":"doc","id":"reference/NoirJS/noir_js/type-aliases/ForeignCallInput","label":"ForeignCallInput"},{"type":"doc","id":"reference/NoirJS/noir_js/type-aliases/ForeignCallOutput","label":"ForeignCallOutput"},{"type":"doc","id":"reference/NoirJS/noir_js/type-aliases/WitnessMap","label":"WitnessMap"}]},{"type":"category","label":"Functions","items":[{"type":"doc","id":"reference/NoirJS/noir_js/functions/and","label":"and"},{"type":"doc","id":"reference/NoirJS/noir_js/functions/blake2s256","label":"blake2s256"},{"type":"doc","id":"reference/NoirJS/noir_js/functions/ecdsa_secp256k1_verify","label":"ecdsa_secp256k1_verify"},{"type":"doc","id":"reference/NoirJS/noir_js/functions/ecdsa_secp256r1_verify","label":"ecdsa_secp256r1_verify"},{"type":"doc","id":"reference/NoirJS/noir_js/functions/keccak256","label":"keccak256"},{"type":"doc","id":"reference/NoirJS/noir_js/functions/xor","label":"xor"}]}]}; +module.exports = typedocSidebar.items; \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_wasm/.nojekyll b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_wasm/.nojekyll new file mode 100644 index 00000000000..e2ac6616add --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_wasm/.nojekyll @@ -0,0 +1 @@ +TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_wasm/functions/compile.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_wasm/functions/compile.md new file mode 100644 index 00000000000..6faf763b37f --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_wasm/functions/compile.md @@ -0,0 +1,51 @@ +# compile() + +```ts +compile( + fileManager, + projectPath?, + logFn?, +debugLogFn?): Promise +``` + +Compiles a Noir project + +## Parameters + +| Parameter | Type | Description | +| :------ | :------ | :------ | +| `fileManager` | `FileManager` | The file manager to use | +| `projectPath`? | `string` | The path to the project inside the file manager. Defaults to the root of the file manager | +| `logFn`? | `LogFn` | A logging function. If not provided, console.log will be used | +| `debugLogFn`? | `LogFn` | A debug logging function. If not provided, logFn will be used | + +## Returns + +`Promise`\<[`ProgramCompilationArtifacts`](../index.md#programcompilationartifacts)\> + +## Example + +```typescript +// Node.js + +import { compile_program, createFileManager } from '@noir-lang/noir_wasm'; + +const fm = createFileManager(myProjectPath); +const myCompiledCode = await compile_program(fm); +``` + +```typescript +// Browser + +import { compile_program, createFileManager } from '@noir-lang/noir_wasm'; + +const fm = createFileManager('/'); +for (const path of files) { + await fm.writeFile(path, await getFileAsStream(path)); +} +const myCompiledCode = await compile_program(fm); +``` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_wasm/functions/compile_contract.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_wasm/functions/compile_contract.md new file mode 100644 index 00000000000..7d0b39a43ef --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_wasm/functions/compile_contract.md @@ -0,0 +1,51 @@ +# compile\_contract() + +```ts +compile_contract( + fileManager, + projectPath?, + logFn?, +debugLogFn?): Promise +``` + +Compiles a Noir project + +## Parameters + +| Parameter | Type | Description | +| :------ | :------ | :------ | +| `fileManager` | `FileManager` | The file manager to use | +| `projectPath`? | `string` | The path to the project inside the file manager. Defaults to the root of the file manager | +| `logFn`? | `LogFn` | A logging function. If not provided, console.log will be used | +| `debugLogFn`? | `LogFn` | A debug logging function. If not provided, logFn will be used | + +## Returns + +`Promise`\<[`ContractCompilationArtifacts`](../index.md#contractcompilationartifacts)\> + +## Example + +```typescript +// Node.js + +import { compile_contract, createFileManager } from '@noir-lang/noir_wasm'; + +const fm = createFileManager(myProjectPath); +const myCompiledCode = await compile_contract(fm); +``` + +```typescript +// Browser + +import { compile_contract, createFileManager } from '@noir-lang/noir_wasm'; + +const fm = createFileManager('/'); +for (const path of files) { + await fm.writeFile(path, await getFileAsStream(path)); +} +const myCompiledCode = await compile_contract(fm); +``` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_wasm/functions/createFileManager.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_wasm/functions/createFileManager.md new file mode 100644 index 00000000000..7e65c1d69c7 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_wasm/functions/createFileManager.md @@ -0,0 +1,21 @@ +# createFileManager() + +```ts +createFileManager(dataDir): FileManager +``` + +Creates a new FileManager instance based on fs in node and memfs in the browser (via webpack alias) + +## Parameters + +| Parameter | Type | Description | +| :------ | :------ | :------ | +| `dataDir` | `string` | root of the file system | + +## Returns + +`FileManager` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_wasm/functions/inflateDebugSymbols.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_wasm/functions/inflateDebugSymbols.md new file mode 100644 index 00000000000..fcea9275341 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_wasm/functions/inflateDebugSymbols.md @@ -0,0 +1,21 @@ +# inflateDebugSymbols() + +```ts +inflateDebugSymbols(debugSymbols): any +``` + +Decompresses and decodes the debug symbols + +## Parameters + +| Parameter | Type | Description | +| :------ | :------ | :------ | +| `debugSymbols` | `string` | The base64 encoded debug symbols | + +## Returns + +`any` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_wasm/index.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_wasm/index.md new file mode 100644 index 00000000000..b6e0f9d1bc0 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_wasm/index.md @@ -0,0 +1,49 @@ +# noir_wasm + +## Exports + +### Functions + +| Function | Description | +| :------ | :------ | +| [compile](functions/compile.md) | Compiles a Noir project | +| [compile\_contract](functions/compile_contract.md) | Compiles a Noir project | +| [createFileManager](functions/createFileManager.md) | Creates a new FileManager instance based on fs in node and memfs in the browser (via webpack alias) | +| [inflateDebugSymbols](functions/inflateDebugSymbols.md) | Decompresses and decodes the debug symbols | + +## References + +### compile\_program + +Renames and re-exports [compile](functions/compile.md) + +## Interfaces + +### ContractCompilationArtifacts + +The compilation artifacts of a given contract. + +#### Properties + +| Property | Type | Description | +| :------ | :------ | :------ | +| `contract` | `ContractArtifact` | The compiled contract. | +| `warnings` | `unknown`[] | Compilation warnings. | + +*** + +### ProgramCompilationArtifacts + +The compilation artifacts of a given program. + +#### Properties + +| Property | Type | Description | +| :------ | :------ | :------ | +| `name` | `string` | not part of the compilation output, injected later | +| `program` | `ProgramArtifact` | The compiled contract. | +| `warnings` | `unknown`[] | Compilation warnings. | + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_wasm/typedoc-sidebar.cjs b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_wasm/typedoc-sidebar.cjs new file mode 100644 index 00000000000..e0870710349 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/NoirJS/noir_wasm/typedoc-sidebar.cjs @@ -0,0 +1,4 @@ +// @ts-check +/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ +const typedocSidebar = { items: [{"type":"doc","id":"reference/NoirJS/noir_wasm/index","label":"API"},{"type":"category","label":"Functions","items":[{"type":"doc","id":"reference/NoirJS/noir_wasm/functions/compile","label":"compile"},{"type":"doc","id":"reference/NoirJS/noir_wasm/functions/compile_contract","label":"compile_contract"},{"type":"doc","id":"reference/NoirJS/noir_wasm/functions/createFileManager","label":"createFileManager"},{"type":"doc","id":"reference/NoirJS/noir_wasm/functions/inflateDebugSymbols","label":"inflateDebugSymbols"}]}]}; +module.exports = typedocSidebar.items; \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/_category_.json b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/_category_.json new file mode 100644 index 00000000000..5b6a20a609a --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/_category_.json @@ -0,0 +1,5 @@ +{ + "position": 4, + "collapsible": true, + "collapsed": true +} diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/debugger/_category_.json b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/debugger/_category_.json new file mode 100644 index 00000000000..27869205ad3 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/debugger/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Debugger", + "position": 1, + "collapsible": true, + "collapsed": true +} diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/debugger/debugger_known_limitations.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/debugger/debugger_known_limitations.md new file mode 100644 index 00000000000..936d416ac4b --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/debugger/debugger_known_limitations.md @@ -0,0 +1,59 @@ +--- +title: Known limitations +description: + An overview of known limitations of the current version of the Noir debugger +keywords: + [ + Nargo, + Noir Debugger, + VS Code, + ] +sidebar_position: 2 +--- + +# Debugger Known Limitations + +There are currently some limits to what the debugger can observe. + +## Mutable references + +The debugger is currently blind to any state mutated via a mutable reference. For example, in: + +``` +let mut x = 1; +let y = &mut x; +*y = 2; +``` + +The update on `x` will not be observed by the debugger. That means, when running `vars` from the debugger REPL, or inspecting the _local variables_ pane in the VS Code debugger, `x` will appear with value 1 despite having executed `*y = 2;`. + +## Variables of type function or mutable references are opaque + +When inspecting variables, any variable of type `Function` or `MutableReference` will render its value as `<>` or `<>`. + +## Debugger instrumentation affects resulting ACIR + +In order to make the state of local variables observable, the debugger compiles Noir circuits interleaving foreign calls that track any mutations to them. While this works (except in the cases described above) and doesn't introduce any behavior changes, it does as a side effect produce bigger bytecode. In particular, when running the command `opcodes` on the REPL debugger, you will notice Unconstrained VM blocks that look like this: + +``` +... +5 BRILLIG inputs=[Single(Expression { mul_terms: [], linear_combinations: [], q_c: 2 }), Single(Expression { mul_terms: [], linear_combinations: [(1, Witness(2))], q_c: 0 })] + | outputs=[] + 5.0 | Mov { destination: RegisterIndex(2), source: RegisterIndex(0) } + 5.1 | Mov { destination: RegisterIndex(3), source: RegisterIndex(1) } + 5.2 | Const { destination: RegisterIndex(0), value: Value { inner: 0 } } + 5.3 | Const { destination: RegisterIndex(1), value: Value { inner: 0 } } + 5.4 | Mov { destination: RegisterIndex(2), source: RegisterIndex(2) } + 5.5 | Mov { destination: RegisterIndex(3), source: RegisterIndex(3) } + 5.6 | Call { location: 8 } + 5.7 | Stop + 5.8 | ForeignCall { function: "__debug_var_assign", destinations: [], inputs: [RegisterIndex(RegisterIndex(2)), RegisterIndex(RegisterIndex(3))] } +... +``` + +If you are interested in debugging/inspecting compiled ACIR without these synthetic changes, you can invoke the REPL debugger with the `--skip-instrumentation` flag or launch the VS Code debugger with the `skipConfiguration` property set to true in its launch configuration. You can find more details about those in the [Debugger REPL reference](debugger_repl.md) and the [VS Code Debugger reference](debugger_vscode.md). + +:::note +Skipping debugger instrumentation means you won't be able to inspect values of local variables. +::: + diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/debugger/debugger_repl.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/debugger/debugger_repl.md new file mode 100644 index 00000000000..46e2011304e --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/debugger/debugger_repl.md @@ -0,0 +1,360 @@ +--- +title: REPL Debugger +description: + Noir Debugger REPL options and commands. +keywords: + [ + Nargo, + Noir CLI, + Noir Debugger, + REPL, + ] +sidebar_position: 1 +--- + +## Running the REPL debugger + +`nargo debug [OPTIONS] [WITNESS_NAME]` + +Runs the Noir REPL debugger. If a `WITNESS_NAME` is provided the debugger writes the resulting execution witness to a `WITNESS_NAME` file. + +### Options + +| Option | Description | +| --------------------- | ------------------------------------------------------------ | +| `-p, --prover-name ` | The name of the toml file which contains the inputs for the prover [default: Prover]| +| `--package ` | The name of the package to debug | +| `--print-acir` | Display the ACIR for compiled circuit | +| `--deny-warnings` | Treat all warnings as errors | +| `--silence-warnings` | Suppress warnings | +| `-h, --help` | Print help | + +None of these options are required. + +:::note +Since the debugger starts by compiling the target package, all Noir compiler options are also available. Check out the [compiler reference](../nargo_commands.md#nargo-compile) to learn more about the compiler options. +::: + +## REPL commands + +Once the debugger is running, it accepts the following commands. + +#### `help` (h) + +Displays the menu of available commands. + +``` +> help +Available commands: + + opcodes display ACIR opcodes + into step into to the next opcode + next step until a new source location is reached + out step until a new source location is reached + and the current stack frame is finished + break LOCATION:OpcodeLocation add a breakpoint at an opcode location + over step until a new source location is reached + without diving into function calls + restart restart the debugging session + delete LOCATION:OpcodeLocation delete breakpoint at an opcode location + witness show witness map + witness index:u32 display a single witness from the witness map + witness index:u32 value:String update a witness with the given value + memset index:usize value:String update a memory cell with the given + value + continue continue execution until the end of the + program + vars show variable values available at this point + in execution + stacktrace display the current stack trace + memory show memory (valid when executing unconstrained code) value + step step to the next ACIR opcode + +Other commands: + + help Show this help message + quit Quit repl + +``` + +### Stepping through programs + +#### `next` (n) + +Step until the next Noir source code location. While other commands, such as [`into`](#into-i) and [`step`](#step-s), allow for finer grained control of the program's execution at the opcode level, `next` is source code centric. For example: + +``` +3 ... +4 fn main(x: u32) { +5 assert(entry_point(x) == 2); +6 swap_entry_point(x, x + 1); +7 -> assert(deep_entry_point(x) == 4); +8 multiple_values_entry_point(x); +9 } +``` + + +Using `next` here would cause the debugger to jump to the definition of `deep_entry_point` (if available). + +If you want to step over `deep_entry_point` and go straight to line 8, use [the `over` command](#over) instead. + +#### `over` + +Step until the next source code location, without diving into function calls. For example: + +``` +3 ... +4 fn main(x: u32) { +5 assert(entry_point(x) == 2); +6 swap_entry_point(x, x + 1); +7 -> assert(deep_entry_point(x) == 4); +8 multiple_values_entry_point(x); +9 } +``` + + +Using `over` here would cause the debugger to execute until line 8 (`multiple_values_entry_point(x);`). + +If you want to step into `deep_entry_point` instead, use [the `next` command](#next-n). + +#### `out` + +Step until the end of the current function call. For example: + +``` + 3 ... + 4 fn main(x: u32) { + 5 assert(entry_point(x) == 2); + 6 swap_entry_point(x, x + 1); + 7 -> assert(deep_entry_point(x) == 4); + 8 multiple_values_entry_point(x); + 9 } + 10 + 11 unconstrained fn returns_multiple_values(x: u32) -> (u32, u32, u32, u32) { + 12 ... + ... + 55 + 56 unconstrained fn deep_entry_point(x: u32) -> u32 { + 57 -> level_1(x + 1) + 58 } + +``` + +Running `out` here will resume execution until line 8. + +#### `step` (s) + +Skips to the next ACIR code. A compiled Noir program is a sequence of ACIR opcodes. However, an unconstrained VM opcode denotes the start of an unconstrained code block, to be executed by the unconstrained VM. For example (redacted for brevity): + +``` +0 BLACKBOX::RANGE [(_0, num_bits: 32)] [ ] +1 -> BRILLIG inputs=[Single(Expression { mul_terms: [], linear_combinations: [(1, Witness(0))], q_c: 0 })] outputs=[Simple(Witness(1))] + 1.0 | Mov { destination: RegisterIndex(2), source: RegisterIndex(0) } + 1.1 | Const { destination: RegisterIndex(0), value: Value { inner: 0 } } + 1.2 | Const { destination: RegisterIndex(1), value: Value { inner: 0 } } + 1.3 | Mov { destination: RegisterIndex(2), source: RegisterIndex(2) } + 1.4 | Call { location: 7 } + ... + 1.43 | Return +2 EXPR [ (1, _1) -2 ] +``` + +The `->` here shows the debugger paused at an ACIR opcode: `BRILLIG`, at index 1, which denotes an unconstrained code block is about to start. + +Using the `step` command at this point would result in the debugger stopping at ACIR opcode 2, `EXPR`, skipping unconstrained computation steps. + +Use [the `into` command](#into-i) instead if you want to follow unconstrained computation step by step. + +#### `into` (i) + +Steps into the next opcode. A compiled Noir program is a sequence of ACIR opcodes. However, a BRILLIG opcode denotes the start of an unconstrained code block, to be executed by the unconstrained VM. For example (redacted for brevity): + +``` +0 BLACKBOX::RANGE [(_0, num_bits: 32)] [ ] +1 -> BRILLIG inputs=[Single(Expression { mul_terms: [], linear_combinations: [(1, Witness(0))], q_c: 0 })] outputs=[Simple(Witness(1))] + 1.0 | Mov { destination: RegisterIndex(2), source: RegisterIndex(0) } + 1.1 | Const { destination: RegisterIndex(0), value: Value { inner: 0 } } + 1.2 | Const { destination: RegisterIndex(1), value: Value { inner: 0 } } + 1.3 | Mov { destination: RegisterIndex(2), source: RegisterIndex(2) } + 1.4 | Call { location: 7 } + ... + 1.43 | Return +2 EXPR [ (1, _1) -2 ] +``` + +The `->` here shows the debugger paused at an ACIR opcode: `BRILLIG`, at index 1, which denotes an unconstrained code block is about to start. + +Using the `into` command at this point would result in the debugger stopping at opcode 1.0, `Mov ...`, allowing the debugger user to follow unconstrained computation step by step. + +Use [the `step` command](#step-s) instead if you want to skip to the next ACIR code directly. + +#### `continue` (c) + +Continues execution until the next breakpoint, or the end of the program. + +#### `restart` (res) + +Interrupts execution, and restarts a new debugging session from scratch. + +#### `opcodes` (o) + +Display the program's ACIR opcode sequence. For example: + +``` +0 BLACKBOX::RANGE [(_0, num_bits: 32)] [ ] +1 -> BRILLIG inputs=[Single(Expression { mul_terms: [], linear_combinations: [(1, Witness(0))], q_c: 0 })] outputs=[Simple(Witness(1))] + 1.0 | Mov { destination: RegisterIndex(2), source: RegisterIndex(0) } + 1.1 | Const { destination: RegisterIndex(0), value: Value { inner: 0 } } + 1.2 | Const { destination: RegisterIndex(1), value: Value { inner: 0 } } + 1.3 | Mov { destination: RegisterIndex(2), source: RegisterIndex(2) } + 1.4 | Call { location: 7 } + ... + 1.43 | Return +2 EXPR [ (1, _1) -2 ] +``` + +### Breakpoints + +#### `break [Opcode]` (or shorthand `b [Opcode]`) + +Sets a breakpoint on the specified opcode index. To get a list of the program opcode numbers, see [the `opcode` command](#opcodes-o). For example: + +``` +0 BLACKBOX::RANGE [(_0, num_bits: 32)] [ ] +1 -> BRILLIG inputs=[Single(Expression { mul_terms: [], linear_combinations: [(1, Witness(0))], q_c: 0 })] outputs=[Simple(Witness(1))] + 1.0 | Mov { destination: RegisterIndex(2), source: RegisterIndex(0) } + 1.1 | Const { destination: RegisterIndex(0), value: Value { inner: 0 } } + 1.2 | Const { destination: RegisterIndex(1), value: Value { inner: 0 } } + 1.3 | Mov { destination: RegisterIndex(2), source: RegisterIndex(2) } + 1.4 | Call { location: 7 } + ... + 1.43 | Return +2 EXPR [ (1, _1) -2 ] +``` + +In this example, issuing a `break 1.2` command adds break on opcode 1.2, as denoted by the `*` character: + +``` +0 BLACKBOX::RANGE [(_0, num_bits: 32)] [ ] +1 -> BRILLIG inputs=[Single(Expression { mul_terms: [], linear_combinations: [(1, Witness(0))], q_c: 0 })] outputs=[Simple(Witness(1))] + 1.0 | Mov { destination: RegisterIndex(2), source: RegisterIndex(0) } + 1.1 | Const { destination: RegisterIndex(0), value: Value { inner: 0 } } + 1.2 | * Const { destination: RegisterIndex(1), value: Value { inner: 0 } } + 1.3 | Mov { destination: RegisterIndex(2), source: RegisterIndex(2) } + 1.4 | Call { location: 7 } + ... + 1.43 | Return +2 EXPR [ (1, _1) -2 ] +``` + +Running [the `continue` command](#continue-c) at this point would cause the debugger to execute the program until opcode 1.2. + +#### `delete [Opcode]` (or shorthand `d [Opcode]`) + +Deletes a breakpoint at an opcode location. Usage is analogous to [the `break` command](#). + +### Variable inspection + +#### vars + +Show variable values available at this point in execution. + +:::note +The ability to inspect variable values from the debugger depends on compilation to be run in a special debug instrumentation mode. This instrumentation weaves variable tracing code with the original source code. + +So variable value inspection comes at the expense of making the resulting ACIR bytecode bigger and harder to understand and optimize. + +If you find this compromise unacceptable, you can run the debugger with the flag `--skip-debug-instrumentation`. This will compile your circuit without any additional debug information, so the resulting ACIR bytecode will be identical to the one produced by standard Noir compilation. However, if you opt for this, the `vars` command will not be available while debugging. +::: + + +### Stacktrace + +#### `stacktrace` + +Displays the current stack trace. + + +### Witness map + +#### `witness` (w) + +Show witness map. For example: + +``` +_0 = 0 +_1 = 2 +_2 = 1 +``` + +#### `witness [Witness Index]` + +Display a single witness from the witness map. For example: + +``` +> witness 1 +_1 = 2 +``` + +#### `witness [Witness Index] [New value]` + +Overwrite the given index with a new value. For example: + +``` +> witness 1 3 +_1 = 3 +``` + + +### Unconstrained VM memory + +#### `memory` + +Show unconstrained VM memory state. For example: + +``` +> memory +At opcode 1.13: Store { destination_pointer: RegisterIndex(0), source: RegisterIndex(3) } +... +> registers +0 = 0 +1 = 10 +2 = 0 +3 = 1 +4 = 1 +5 = 2³² +6 = 1 +> into +At opcode 1.14: Const { destination: RegisterIndex(5), value: Value { inner: 1 } } +... +> memory +0 = 1 +> +``` + +In the example above: we start with clean memory, then step through a `Store` opcode which stores the value of register 3 (1) into the memory address stored in register 0 (0). Thus now `memory` shows memory address 0 contains value 1. + +:::note +This command is only functional while the debugger is executing unconstrained code. +::: + +#### `memset [Memory address] [New value]` + +Update a memory cell with the given value. For example: + +``` +> memory +0 = 1 +> memset 0 2 +> memory +0 = 2 +> memset 1 4 +> memory +0 = 2 +1 = 4 +> +``` + +:::note +This command is only functional while the debugger is executing unconstrained code. +::: \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/debugger/debugger_vscode.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/debugger/debugger_vscode.md new file mode 100644 index 00000000000..c027332b3b0 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/debugger/debugger_vscode.md @@ -0,0 +1,82 @@ +--- +title: VS Code Debugger +description: + VS Code Debugger configuration and features. +keywords: + [ + Nargo, + Noir CLI, + Noir Debugger, + VS Code, + IDE, + ] +sidebar_position: 0 +--- + +# VS Code Noir Debugger Reference + +The Noir debugger enabled by the vscode-noir extension ships with default settings such that the most common scenario should run without any additional configuration steps. + +These defaults can nevertheless be overridden by defining a launch configuration file. This page provides a reference for the properties you can override via a launch configuration file, as well as documenting the Nargo `dap` command, which is a dependency of the VS Code Noir debugger. + + +## Creating and editing launch configuration files + +To create a launch configuration file from VS Code, open the _debug pane_, and click on _create a launch.json file_. + +![Creating a launch configuration file](@site/static/img/debugger/ref1-create-launch.png) + +A `launch.json` file will be created, populated with basic defaults. + +### Noir Debugger launch.json properties + +#### projectFolder + +_String, optional._ + +Absolute path to the Nargo project to debug. By default, it is dynamically determined by looking for the nearest `Nargo.toml` file to the active file at the moment of launching the debugger. + +#### proverName + +_String, optional._ + +Name of the prover input to use. Defaults to `Prover`, which looks for a file named `Prover.toml` at the `projectFolder`. + +#### generateAcir + +_Boolean, optional._ + +If true, generate ACIR opcodes instead of unconstrained opcodes which will be closer to release binaries but less convenient for debugging. Defaults to `false`. + +#### skipInstrumentation + +_Boolean, optional._ + +Skips variables debugging instrumentation of code, making debugging less convenient but the resulting binary smaller and closer to production. Defaults to `false`. + +:::note +Skipping instrumentation causes the debugger to be unable to inspect local variables. +::: + +## `nargo dap [OPTIONS]` + +When run without any option flags, it starts the Nargo Debug Adapter Protocol server, which acts as the debugging backend for the VS Code Noir Debugger. + +All option flags are related to preflight checks. The Debug Adapter Protocol specifies how errors are to be informed from a running DAP server, but it doesn't specify mechanisms to communicate server initialization errors between the DAP server and its client IDE. + +Thus `nargo dap` ships with a _preflight check_ mode. If flag `--preflight-check` and the rest of the `--preflight-*` flags are provided, Nargo will run the same initialization routine except it will not start the DAP server. + +`vscode-noir` will then run `nargo dap` in preflight check mode first before a debugging session starts. If the preflight check ends in error, vscode-noir will present stderr and stdout output from this process through its own Output pane in VS Code. This makes it possible for users to diagnose what pieces of configuration might be wrong or missing in case of initialization errors. + +If the preflight check succeeds, `vscode-noir` proceeds to start the DAP server normally but running `nargo dap` without any additional flags. + +### Options + +| Option | Description | +| --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | +| `--preflight-check` | If present, dap runs in preflight check mode. | +| `--preflight-project-folder ` | Absolute path to the project to debug for preflight check. | +| `--preflight-prover-name ` | Name of prover file to use for preflight check | +| `--preflight-generate-acir` | Optional. If present, compile in ACIR mode while running preflight check. | +| `--preflight-skip-instrumentation` | Optional. If present, compile without introducing debug instrumentation while running preflight check. | +| `-h, --help` | Print help. | diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/nargo_commands.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/nargo_commands.md new file mode 100644 index 00000000000..db1884afee2 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/nargo_commands.md @@ -0,0 +1,297 @@ +--- +title: Nargo +description: + Noir CLI Commands for Noir Prover and Verifier to create, execute, prove and verify programs, + generate Solidity verifier smart contract and compile into JSON file containing ACIR + representation and ABI of circuit. +keywords: + [ + Nargo, + Noir CLI, + Noir Prover, + Noir Verifier, + generate Solidity verifier, + compile JSON file, + ACIR representation, + ABI of circuit, + TypeScript, + ] +sidebar_position: 0 +--- + +# Command-Line Help for `nargo` + +This document contains the help content for the `nargo` command-line program. + +**Command Overview:** + +* [`nargo`↴](#nargo) +* [`nargo check`↴](#nargo-check) +* [`nargo fmt`↴](#nargo-fmt) +* [`nargo compile`↴](#nargo-compile) +* [`nargo new`↴](#nargo-new) +* [`nargo init`↴](#nargo-init) +* [`nargo execute`↴](#nargo-execute) +* [`nargo debug`↴](#nargo-debug) +* [`nargo test`↴](#nargo-test) +* [`nargo info`↴](#nargo-info) +* [`nargo lsp`↴](#nargo-lsp) + +## `nargo` + +Noir's package manager + +**Usage:** `nargo ` + +###### **Subcommands:** + +* `check` — Checks the constraint system for errors +* `fmt` — Format the Noir files in a workspace +* `compile` — Compile the program and its secret execution trace into ACIR format +* `new` — Create a Noir project in a new directory +* `init` — Create a Noir project in the current directory +* `execute` — Executes a circuit to calculate its return value +* `debug` — Executes a circuit in debug mode +* `test` — Run the tests for this program +* `info` — Provides detailed information on each of a program's function (represented by a single circuit) +* `lsp` — Starts the Noir LSP server + +###### **Options:** + + + + +## `nargo check` + +Checks the constraint system for errors + +**Usage:** `nargo check [OPTIONS]` + +###### **Options:** + +* `--package ` — The name of the package to check +* `--workspace` — Check all packages in the workspace +* `--overwrite` — Force overwrite of existing files +* `--expression-width ` — Specify the backend expression width that should be targeted +* `--bounded-codegen` — Generate ACIR with the target backend expression width. The default is to generate ACIR without a bound and split expressions after code generation. Activating this flag can sometimes provide optimizations for certain programs + + Default value: `false` +* `--force` — Force a full recompilation +* `--print-acir` — Display the ACIR for compiled circuit +* `--deny-warnings` — Treat all warnings as errors +* `--silence-warnings` — Suppress warnings +* `--debug-comptime-in-file ` — Enable printing results of comptime evaluation: provide a path suffix for the module to debug, e.g. "package_name/src/main.nr" +* `--skip-underconstrained-check` — Flag to turn off the compiler check for under constrained values. Warning: This can improve compilation speed but can also lead to correctness errors. This check should always be run on production code + + + +## `nargo fmt` + +Format the Noir files in a workspace + +**Usage:** `nargo fmt [OPTIONS]` + +###### **Options:** + +* `--check` — Run noirfmt in check mode + + + +## `nargo compile` + +Compile the program and its secret execution trace into ACIR format + +**Usage:** `nargo compile [OPTIONS]` + +###### **Options:** + +* `--package ` — The name of the package to compile +* `--workspace` — Compile all packages in the workspace +* `--expression-width ` — Specify the backend expression width that should be targeted +* `--bounded-codegen` — Generate ACIR with the target backend expression width. The default is to generate ACIR without a bound and split expressions after code generation. Activating this flag can sometimes provide optimizations for certain programs + + Default value: `false` +* `--force` — Force a full recompilation +* `--print-acir` — Display the ACIR for compiled circuit +* `--deny-warnings` — Treat all warnings as errors +* `--silence-warnings` — Suppress warnings +* `--debug-comptime-in-file ` — Enable printing results of comptime evaluation: provide a path suffix for the module to debug, e.g. "package_name/src/main.nr" +* `--skip-underconstrained-check` — Flag to turn off the compiler check for under constrained values. Warning: This can improve compilation speed but can also lead to correctness errors. This check should always be run on production code + + + +## `nargo new` + +Create a Noir project in a new directory + +**Usage:** `nargo new [OPTIONS] ` + +###### **Arguments:** + +* `` — The path to save the new project + +###### **Options:** + +* `--name ` — Name of the package [default: package directory name] +* `--lib` — Use a library template +* `--bin` — Use a binary template [default] +* `--contract` — Use a contract template + + + +## `nargo init` + +Create a Noir project in the current directory + +**Usage:** `nargo init [OPTIONS]` + +###### **Options:** + +* `--name ` — Name of the package [default: current directory name] +* `--lib` — Use a library template +* `--bin` — Use a binary template [default] +* `--contract` — Use a contract template + + + +## `nargo execute` + +Executes a circuit to calculate its return value + +**Usage:** `nargo execute [OPTIONS] [WITNESS_NAME]` + +###### **Arguments:** + +* `` — Write the execution witness to named file + +Defaults to the name of the package being executed. + +###### **Options:** + +* `-p`, `--prover-name ` — The name of the toml file which contains the inputs for the prover + + Default value: `Prover` +* `--package ` — The name of the package to execute +* `--workspace` — Execute all packages in the workspace +* `--expression-width ` — Specify the backend expression width that should be targeted +* `--bounded-codegen` — Generate ACIR with the target backend expression width. The default is to generate ACIR without a bound and split expressions after code generation. Activating this flag can sometimes provide optimizations for certain programs + + Default value: `false` +* `--force` — Force a full recompilation +* `--print-acir` — Display the ACIR for compiled circuit +* `--deny-warnings` — Treat all warnings as errors +* `--silence-warnings` — Suppress warnings +* `--debug-comptime-in-file ` — Enable printing results of comptime evaluation: provide a path suffix for the module to debug, e.g. "package_name/src/main.nr" +* `--skip-underconstrained-check` — Flag to turn off the compiler check for under constrained values. Warning: This can improve compilation speed but can also lead to correctness errors. This check should always be run on production code +* `--oracle-resolver ` — JSON RPC url to solve oracle calls + + + +## `nargo debug` + +Executes a circuit in debug mode + +**Usage:** `nargo debug [OPTIONS] [WITNESS_NAME]` + +###### **Arguments:** + +* `` — Write the execution witness to named file + +###### **Options:** + +* `-p`, `--prover-name ` — The name of the toml file which contains the inputs for the prover + + Default value: `Prover` +* `--package ` — The name of the package to execute +* `--expression-width ` — Specify the backend expression width that should be targeted +* `--bounded-codegen` — Generate ACIR with the target backend expression width. The default is to generate ACIR without a bound and split expressions after code generation. Activating this flag can sometimes provide optimizations for certain programs + + Default value: `false` +* `--force` — Force a full recompilation +* `--print-acir` — Display the ACIR for compiled circuit +* `--deny-warnings` — Treat all warnings as errors +* `--silence-warnings` — Suppress warnings +* `--debug-comptime-in-file ` — Enable printing results of comptime evaluation: provide a path suffix for the module to debug, e.g. "package_name/src/main.nr" +* `--skip-underconstrained-check` — Flag to turn off the compiler check for under constrained values. Warning: This can improve compilation speed but can also lead to correctness errors. This check should always be run on production code +* `--acir-mode` — Force ACIR output (disabling instrumentation) +* `--skip-instrumentation ` — Disable vars debug instrumentation (enabled by default) + + Possible values: `true`, `false` + + + + +## `nargo test` + +Run the tests for this program + +**Usage:** `nargo test [OPTIONS] [TEST_NAME]` + +###### **Arguments:** + +* `` — If given, only tests with names containing this string will be run + +###### **Options:** + +* `--show-output` — Display output of `println` statements +* `--exact` — Only run tests that match exactly +* `--package ` — The name of the package to test +* `--workspace` — Test all packages in the workspace +* `--expression-width ` — Specify the backend expression width that should be targeted +* `--bounded-codegen` — Generate ACIR with the target backend expression width. The default is to generate ACIR without a bound and split expressions after code generation. Activating this flag can sometimes provide optimizations for certain programs + + Default value: `false` +* `--force` — Force a full recompilation +* `--print-acir` — Display the ACIR for compiled circuit +* `--deny-warnings` — Treat all warnings as errors +* `--silence-warnings` — Suppress warnings +* `--debug-comptime-in-file ` — Enable printing results of comptime evaluation: provide a path suffix for the module to debug, e.g. "package_name/src/main.nr" +* `--skip-underconstrained-check` — Flag to turn off the compiler check for under constrained values. Warning: This can improve compilation speed but can also lead to correctness errors. This check should always be run on production code +* `--oracle-resolver ` — JSON RPC url to solve oracle calls + + + +## `nargo info` + +Provides detailed information on each of a program's function (represented by a single circuit) + +Current information provided per circuit: 1. The number of ACIR opcodes 2. Counts the final number gates in the circuit used by a backend + +**Usage:** `nargo info [OPTIONS]` + +###### **Options:** + +* `--package ` — The name of the package to detail +* `--workspace` — Detail all packages in the workspace +* `--expression-width ` — Specify the backend expression width that should be targeted +* `--bounded-codegen` — Generate ACIR with the target backend expression width. The default is to generate ACIR without a bound and split expressions after code generation. Activating this flag can sometimes provide optimizations for certain programs + + Default value: `false` +* `--force` — Force a full recompilation +* `--print-acir` — Display the ACIR for compiled circuit +* `--deny-warnings` — Treat all warnings as errors +* `--silence-warnings` — Suppress warnings +* `--debug-comptime-in-file ` — Enable printing results of comptime evaluation: provide a path suffix for the module to debug, e.g. "package_name/src/main.nr" +* `--skip-underconstrained-check` — Flag to turn off the compiler check for under constrained values. Warning: This can improve compilation speed but can also lead to correctness errors. This check should always be run on production code + + + +## `nargo lsp` + +Starts the Noir LSP server + +Starts an LSP server which allows IDEs such as VS Code to display diagnostics in Noir source. + +VS Code Noir Language Support: https://marketplace.visualstudio.com/items?itemName=noir-lang.vscode-noir + +**Usage:** `nargo lsp` + + + +
+ + + This document was generated automatically by + clap-markdown. + + diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/noir_codegen.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/noir_codegen.md new file mode 100644 index 00000000000..db8f07dc22e --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/reference/noir_codegen.md @@ -0,0 +1,114 @@ +--- +title: Noir Codegen for TypeScript +description: Learn how to use Noir codegen to generate TypeScript bindings +keywords: [Nargo, Noir, compile, TypeScript] +sidebar_position: 3 +--- + +When using TypeScript, it is extra work to interpret Noir program outputs in a type-safe way. Third party libraries may exist for popular Noir programs, but they are either hard to find or unmaintained. + +Now you can generate TypeScript bindings for your Noir programs in two steps: +1. Exporting Noir functions using `nargo export` +2. Using the TypeScript module `noir_codegen` to generate TypeScript binding + +**Note:** you can only export functions from a Noir *library* (not binary or contract program types). + +## Installation + +### Your TypeScript project + +If you don't already have a TypeScript project you can add the module with `yarn` (or `npm`), then initialize it: + +```bash +yarn add typescript -D +npx tsc --init +``` + +### Add TypeScript module - `noir_codegen` + +The following command will add the module to your project's devDependencies: + +```bash +yarn add @noir-lang/noir_codegen -D +``` + +### Nargo library +Make sure you have Nargo, v0.25.0 or greater, installed. If you don't, follow the [installation guide](../getting_started/installation/index.md). + +If you're in a new project, make a `circuits` folder and create a new Noir library: + +```bash +mkdir circuits && cd circuits +nargo new --lib myNoirLib +``` + +## Usage + +### Export ABI of specified functions + +First go to the `.nr` files in your Noir library, and add the `#[export]` macro to each function that you want to use in TypeScript. + +```rust +#[export] +fn your_function(... +``` + +From your Noir library (where `Nargo.toml` is), run the following command: + +```bash +nargo export +``` + +You will now have an `export` directory with a .json file per exported function. + +You can also specify the directory of Noir programs using `--program-dir`, for example: + +```bash +nargo export --program-dir=./circuits/myNoirLib +``` + +### Generate TypeScript bindings from exported functions + +To use the `noir-codegen` package we added to the TypeScript project: + +```bash +yarn noir-codegen ./export/your_function.json +``` + +This creates an `exports` directory with an `index.ts` file containing all exported functions. + +**Note:** adding `--out-dir` allows you to specify an output dir for your TypeScript bindings to go. Eg: + +```bash +yarn noir-codegen ./export/*.json --out-dir ./path/to/output/dir +``` + +## Example .nr function to .ts output + +Consider a Noir library with this function: + +```rust +#[export] +fn not_equal(x: Field, y: Field) -> bool { + x != y +} +``` + +After the export and codegen steps, you should have an `index.ts` like: + +```typescript +export type Field = string; + + +export const is_equal_circuit: CompiledCircuit = +{"abi":{"parameters":[{"name":"x","type":{"kind":"field"},"visibility":"private"},{"name":"y","type":{"kind":"field"},"visibility":"private"}],"return_type":{"abi_type":{"kind":"boolean"},"visibility":"private"}},"bytecode":"H4sIAAAAAAAA/7WUMQ7DIAxFQ0Krrr2JjSGYLVcpKrn/CaqqDQN12WK+hPBgmWd/wEyHbF1SS923uhOs3pfoChI+wKXMAXzIKyNj4PB0TFTYc0w5RUjoqeAeEu1wqK0F54RGkWvW44LPzExnlkbMEs4JNZmN8PxS42uHv82T8a3Jeyn2Ks+VLPcO558HmyLMCDOXAXXtpPt4R/Rt9T36ss6dS9HGPx/eG17nGegKBQAA"}; + +export async function is_equal(x: Field, y: Field, foreignCallHandler?: ForeignCallHandler): Promise { + const program = new Noir(is_equal_circuit); + const args: InputMap = { x, y }; + const { returnValue } = await program.execute(args, foreignCallHandler); + return returnValue as boolean; +} +``` + +Now the `is_equal()` function and relevant types are readily available for use in TypeScript. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/tooling/debugger.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/tooling/debugger.md new file mode 100644 index 00000000000..9b7565ba9ff --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/tooling/debugger.md @@ -0,0 +1,26 @@ +--- +title: Debugger +description: Learn about the Noir Debugger, in its REPL or VS Code versions. +keywords: [Nargo, VSCode, Visual Studio Code, REPL, Debugger] +sidebar_position: 2 +--- + +# Noir Debugger + +There are currently two ways of debugging Noir programs: + +1. From VS Code, via the [vscode-noir](https://github.com/noir-lang/vscode-noir) extension. You can install it via the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=noir-lang.vscode-noir). +2. Via the REPL debugger, which ships with Nargo. + +In order to use either version of the debugger, you will need to install recent enough versions of Noir, [Nargo](../getting_started/installation/index.md) and vscode-noir: + +- Noir & Nargo ≥0.28.0 +- Noir's VS Code extension ≥0.0.11 + +:::info +At the moment, the debugger supports debugging binary projects, but not contracts. +::: + +We cover the VS Code Noir debugger more in depth in [its VS Code debugger how-to guide](../how_to/debugger/debugging_with_vs_code.md) and [the reference](../reference/debugger/debugger_vscode.md). + +The REPL debugger is discussed at length in [the REPL debugger how-to guide](../how_to/debugger/debugging_with_the_repl.md) and [the reference](../reference/debugger/debugger_repl.md). diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/tooling/language_server.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/tooling/language_server.md new file mode 100644 index 00000000000..81e0356ef8a --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/tooling/language_server.md @@ -0,0 +1,43 @@ +--- +title: Language Server +description: Learn about the Noir Language Server, how to install the components, and configuration that may be required. +keywords: [Nargo, Language Server, LSP, VSCode, Visual Studio Code] +sidebar_position: 0 +--- + +This section helps you install and configure the Noir Language Server. + +The Language Server Protocol (LSP) has two components, the [Server](#language-server) and the [Client](#language-client). Below we describe each in the context of Noir. + +## Language Server + +The Server component is provided by the Nargo command line tool that you installed at the beginning of this guide. +As long as Nargo is installed and you've used it to run other commands in this guide, it should be good to go! + +If you'd like to verify that the `nargo lsp` command is available, you can run `nargo --help` and look for `lsp` in the list of commands. If you see it, you're using a version of Noir with LSP support. + +## Language Client + +The Client component is usually an editor plugin that launches the Server. It communicates LSP messages between the editor and the Server. For example, when you save a file, the Client will alert the Server, so it can try to compile the project and report any errors. + +Currently, Noir provides a Language Client for Visual Studio Code via the [vscode-noir](https://github.com/noir-lang/vscode-noir) extension. You can install it via the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=noir-lang.vscode-noir). + +> **Note:** Noir's Language Server Protocol support currently assumes users' VSCode workspace root to be the same as users' Noir project root (i.e. where Nargo.toml lies). +> +> If LSP features seem to be missing / malfunctioning, make sure you are opening your Noir project directly (instead of as a sub-folder) in your VSCode instance. + +When your language server is running correctly and the VSCode plugin is installed, you should see handy codelens buttons for compilation, measuring circuit size, execution, and tests: + +![Compile and Execute](@site/static/img/codelens_compile_execute.png) +![Run test](@site/static/img/codelens_run_test.png) + +You should also see your tests in the `testing` panel: + +![Testing panel](@site/static/img/codelens_testing_panel.png) + +### Configuration + +- **Noir: Enable LSP** - If checked, the extension will launch the Language Server via `nargo lsp` and communicate with it. +- **Noir: Nargo Flags** - Additional flags may be specified if you require them to be added when the extension calls `nargo lsp`. +- **Noir: Nargo Path** - An absolute path to a Nargo binary with the `lsp` command. This may be useful if Nargo is not within the `PATH` of your editor. +- **Noir > Trace: Server** - Setting this to `"messages"` or `"verbose"` will log LSP messages between the Client and Server. Useful for debugging. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/tooling/testing.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/tooling/testing.md new file mode 100644 index 00000000000..866677da567 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/tooling/testing.md @@ -0,0 +1,79 @@ +--- +title: Testing in Noir +description: Learn how to use Nargo to test your Noir program in a quick and easy way +keywords: [Nargo, testing, Noir, compile, test] +sidebar_position: 1 +--- + +You can test your Noir programs using Noir circuits. + +Nargo will automatically compile and run any functions which have the decorator `#[test]` on them if +you run `nargo test`. + +For example if you have a program like: + +```rust +fn add(x: u64, y: u64) -> u64 { + x + y +} +#[test] +fn test_add() { + assert(add(2,2) == 4); + assert(add(0,1) == 1); + assert(add(1,0) == 1); +} +``` + +Running `nargo test` will test that the `test_add` function can be executed while satisfying all +the constraints which allows you to test that add returns the expected values. Test functions can't +have any arguments currently. + +### Test fail + +You can write tests that are expected to fail by using the decorator `#[test(should_fail)]`. For example: + +```rust +fn add(x: u64, y: u64) -> u64 { + x + y +} +#[test(should_fail)] +fn test_add() { + assert(add(2,2) == 5); +} +``` + +You can be more specific and make it fail with a specific reason by using `should_fail_with = ""`: + +```rust +fn main(african_swallow_avg_speed : Field) { + assert(african_swallow_avg_speed == 65, "What is the airspeed velocity of an unladen swallow"); +} + +#[test] +fn test_king_arthur() { + main(65); +} + +#[test(should_fail_with = "What is the airspeed velocity of an unladen swallow")] +fn test_bridgekeeper() { + main(32); +} +``` + +The string given to `should_fail_with` doesn't need to exactly match the failure reason, it just needs to be a substring of it: + +```rust +fn main(african_swallow_avg_speed : Field) { + assert(african_swallow_avg_speed == 65, "What is the airspeed velocity of an unladen swallow"); +} + +#[test] +fn test_king_arthur() { + main(65); +} + +#[test(should_fail_with = "airspeed velocity")] +fn test_bridgekeeper() { + main(32); +} +``` \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/tutorials/noirjs_app.md b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/tutorials/noirjs_app.md new file mode 100644 index 00000000000..eac28168445 --- /dev/null +++ b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/tutorials/noirjs_app.md @@ -0,0 +1,362 @@ +--- +title: Building a web app with NoirJS +description: Learn how to setup a new app that uses Noir to generate and verify zero-knowledge SNARK proofs in a typescript or javascript environment. +keywords: [how to, guide, javascript, typescript, noir, barretenberg, zero-knowledge, proofs, app] +sidebar_position: 0 +pagination_next: noir/concepts/data_types/index +--- + +NoirJS is a set of packages meant to work both in a browser and a server environment. In this tutorial, we will build a simple web app using them. From here, you should get an idea on how to proceed with your own Noir projects! + +You can find the complete app code for this guide [here](https://github.com/noir-lang/tiny-noirjs-app). + +## Setup + +:::note + +Feel free to use whatever versions, just keep in mind that Nargo and the NoirJS packages are meant to be in sync. For example, Nargo 0.31.x matches `noir_js@0.31.x`, etc. + +In this guide, we will be pinned to 0.31.0. + +::: + +Before we start, we want to make sure we have Node, Nargo and the Barretenberg proving system (`bb`) installed. + +We start by opening a terminal and executing `node --version`. If we don't get an output like `v20.10.0`, that means node is not installed. Let's do that by following the handy [nvm guide](https://github.com/nvm-sh/nvm?tab=readme-ov-file#install--update-script). + +As for `Nargo`, we can follow the [Nargo guide](../getting_started/installation/index.md) to install it. If you're lazy, just paste this on a terminal and run `noirup`: + +```sh +curl -L https://raw.githubusercontent.com/noir-lang/noirup/main/install | bash +``` + +Follow the instructions on [this page](https://github.com/AztecProtocol/aztec-packages/tree/master/barretenberg/cpp/src/barretenberg/bb#installation) to install `bb`. +Version 0.41.0 is compatible with `nargo` version 0.31.0, which you can install with `bbup -v 0.41.0` once `bbup` is installed. + +Easy enough. Onwards! + +## Our project + +ZK is a powerful technology. An app that doesn't reveal one of the inputs to _anyone_ is almost unbelievable, yet Noir makes it as easy as a single line of code. + +In fact, it's so simple that it comes nicely packaged in `nargo`. Let's do that! + +### Nargo + +Run: + +```bash +nargo new circuit +``` + +And... That's about it. Your program is ready to be compiled and run. + +To compile, let's `cd` into the `circuit` folder to enter our project, and call: + +```bash +nargo compile +``` + +This compiles our circuit into `json` format and add it to a new `target` folder. + +:::info + +At this point in the tutorial, your folder structure should look like this: + +```tree +. +└── circuit <---- our working directory + ├── Nargo.toml + ├── src + │ └── main.nr + └── target + └── circuit.json +``` + +::: + +### Node and Vite + +If you want to explore Nargo, feel free to go on a side-quest now and follow the steps in the +[getting started](../getting_started/hello_noir/index.md) guide. However, we want our app to run on the browser, so we need Vite. + +Vite is a powerful tool to generate static websites. While it provides all kinds of features, let's just go barebones with some good old vanilla JS. + +To do this this, go back to the previous folder (`cd ..`) and create a new vite project by running `npm create vite` and choosing "Vanilla" and "Javascript". + +A wild `vite-project` directory should now appear in your root folder! Let's not waste any time and dive right in: + +```bash +cd vite-project +``` + +### Setting Up Vite and Configuring the Project + +Before we proceed with any coding, let's get our environment tailored for Noir. We'll start by laying down the foundations with a `vite.config.js` file. This little piece of configuration is our secret sauce for making sure everything meshes well with the NoirJS libraries and other special setups we might need, like handling WebAssembly modules. Here’s how you get that going: + +#### Creating the vite.config.js + +In your freshly minted `vite-project` folder, create a new file named `vite.config.js` and open it in your code editor. Paste the following to set the stage: + +```javascript +import { defineConfig } from 'vite'; +import copy from 'rollup-plugin-copy'; +import fs from 'fs'; +import path from 'path'; + +const wasmContentTypePlugin = { + name: 'wasm-content-type-plugin', + configureServer(server) { + server.middlewares.use(async (req, res, next) => { + if (req.url.endsWith('.wasm')) { + res.setHeader('Content-Type', 'application/wasm'); + const newPath = req.url.replace('deps', 'dist'); + const targetPath = path.join(__dirname, newPath); + const wasmContent = fs.readFileSync(targetPath); + return res.end(wasmContent); + } + next(); + }); + }, +}; + +export default defineConfig(({ command }) => { + if (command === 'serve') { + return { + build: { + target: 'esnext', + rollupOptions: { + external: ['@aztec/bb.js'] + } + }, + optimizeDeps: { + esbuildOptions: { + target: 'esnext' + } + }, + plugins: [ + copy({ + targets: [{ src: 'node_modules/**/*.wasm', dest: 'node_modules/.vite/dist' }], + copySync: true, + hook: 'buildStart', + }), + command === 'serve' ? wasmContentTypePlugin : [], + ], + }; + } + + return {}; +}); +``` + +#### Install Dependencies + +Now that our stage is set, install the necessary NoirJS packages along with our other dependencies: + +```bash +npm install && npm install @noir-lang/backend_barretenberg@0.31.0 @noir-lang/noir_js@0.31.0 +npm install rollup-plugin-copy --save-dev +``` + +:::info + +At this point in the tutorial, your folder structure should look like this: + +```tree +. +└── circuit + └── ...etc... +└── vite-project <---- our working directory + └── ...etc... +``` + +::: + +#### Some cleanup + +`npx create vite` is amazing but it creates a bunch of files we don't really need for our simple example. Actually, let's just delete everything except for `vite.config.js`, `index.html`, `main.js` and `package.json`. I feel lighter already. + +![my heart is ready for you, noir.js](@site/static/img/memes/titanic.jpeg) + +## HTML + +Our app won't run like this, of course. We need some working HTML, at least. Let's open our broken-hearted `index.html` and replace everything with this code snippet: + +```html + + + + + + +

Noir app

+
+ + +
+
+

Logs

+

Proof

+
+ + +``` + +It _could_ be a beautiful UI... Depending on which universe you live in. + +## Some good old vanilla Javascript + +Our love for Noir needs undivided attention, so let's just open `main.js` and delete everything (this is where the romantic scenery becomes a bit creepy). + +Start by pasting in this boilerplate code: + +```js +function display(container, msg) { + const c = document.getElementById(container); + const p = document.createElement('p'); + p.textContent = msg; + c.appendChild(p); +} + +document.getElementById('submitGuess').addEventListener('click', async () => { + try { + // here's where love happens + } catch (err) { + display('logs', 'Oh 💔 Wrong guess'); + } +}); +``` + +The display function doesn't do much. We're simply manipulating our website to see stuff happening. For example, if the proof fails, it will simply log a broken heart 😢 + +:::info + +At this point in the tutorial, your folder structure should look like this: + +```tree +. +└── circuit + └── ...same as above +└── vite-project + ├── vite.config.js + ├── main.js + ├── package.json + └── index.html +``` + +You'll see other files and folders showing up (like `package-lock.json`, `node_modules`) but you shouldn't have to care about those. + +::: + +## Some NoirJS + +We're starting with the good stuff now. If you've compiled the circuit as described above, you should have a `json` file we want to import at the very top of our `main.js` file: + +```ts +import circuit from '../circuit/target/circuit.json'; +``` + +[Noir is backend-agnostic](../index.mdx#whats-new-about-noir). We write Noir, but we also need a proving backend. That's why we need to import and instantiate the two dependencies we installed above: `BarretenbergBackend` and `Noir`. Let's import them right below: + +```js +import { BarretenbergBackend, BarretenbergVerifier as Verifier } from '@noir-lang/backend_barretenberg'; +import { Noir } from '@noir-lang/noir_js'; +``` + +And instantiate them inside our try-catch block: + +```ts +// try { +const backend = new BarretenbergBackend(circuit); +const noir = new Noir(circuit); +// } +``` + +:::note + +For the remainder of the tutorial, everything will be happening inside the `try` block + +::: + +## Our app + +Now for the app itself. We're capturing whatever is in the input when people press the submit button. Just add this: + +```js +const x = parseInt(document.getElementById('guessInput').value); +const input = { x, y: 2 }; +``` + +Now we're ready to prove stuff! Let's feed some inputs to our circuit and calculate the proof: + +```js +await setup(); // let's squeeze our wasm inits here + +display('logs', 'Generating proof... ⌛'); +const { witness } = await noir.execute(input); +const proof = await backend.generateProof(witness); +display('logs', 'Generating proof... ✅'); +display('results', proof.proof); +``` + +You're probably eager to see stuff happening, so go and run your app now! + +From your terminal, run `npm run dev`. If it doesn't open a browser for you, just visit `localhost:5173`. You should now see the worst UI ever, with an ugly input. + +![Getting Started 0](@site/static/img/noir_getting_started_1.png) + +Now, our circuit says `fn main(x: Field, y: pub Field)`. This means only the `y` value is public, and it's hardcoded above: `input = { x, y: 2 }`. In other words, you won't need to send your secret`x` to the verifier! + +By inputting any number other than 2 in the input box and clicking "submit", you should get a valid proof. Otherwise the proof won't even generate correctly. By the way, if you're human, you shouldn't be able to understand anything on the "proof" box. That's OK. We like you, human ❤️. + +## Verifying + +Time to celebrate, yes! But we shouldn't trust machines so blindly. Let's add these lines to see our proof being verified: + +```js +display('logs', 'Verifying proof... ⌛'); +const isValid = await backend.verifyProof(proof); + +// or to cache and use the verification key: +// const verificationKey = await backend.getVerificationKey(); +// const verifier = new Verifier(); +// const isValid = await verifier.verifyProof(proof, verificationKey); + +if (isValid) display('logs', 'Verifying proof... ✅'); +``` + +You have successfully generated a client-side Noir web app! + +![coded app without math knowledge](@site/static/img/memes/flextape.jpeg) + +## Further Reading + +You can see how noirjs is used in a full stack Next.js hardhat application in the [noir-starter repo here](https://github.com/noir-lang/noir-starter/tree/main/vite-hardhat). The example shows how to calculate a proof in the browser and verify it with a deployed Solidity verifier contract from noirjs. + +You should also check out the more advanced examples in the [noir-examples repo](https://github.com/noir-lang/noir-examples), where you'll find reference usage for some cool apps. + +## UltraHonk Backend + +Barretenberg has recently exposed a new UltraHonk backend. We can use UltraHonk in NoirJS after version 0.33.0. Everything will be the same as the tutorial above, except that the class we need to import will change: +```js +import { UltraHonkBackend, UltraHonkVerifier as Verifier } from '@noir-lang/backend_barretenberg'; +``` +The backend will then be instantiated as such: +```js +const backend = new UltraHonkBackend(circuit); +``` +Then all the commands to prove and verify your circuit will be same. + +The only feature currently unsupported with UltraHonk are [recursive proofs](../explainers/explainer-recursion.md). \ No newline at end of file diff --git a/noir/noir-repo/docs/versioned_sidebars/version-v0.35.0-sidebars.json b/noir/noir-repo/docs/versioned_sidebars/version-v0.35.0-sidebars.json new file mode 100644 index 00000000000..b9ad026f69f --- /dev/null +++ b/noir/noir-repo/docs/versioned_sidebars/version-v0.35.0-sidebars.json @@ -0,0 +1,93 @@ +{ + "sidebar": [ + { + "type": "doc", + "id": "index" + }, + { + "type": "category", + "label": "Getting Started", + "items": [ + { + "type": "autogenerated", + "dirName": "getting_started" + } + ] + }, + { + "type": "category", + "label": "The Noir Language", + "items": [ + { + "type": "autogenerated", + "dirName": "noir" + } + ] + }, + { + "type": "html", + "value": "
", + "defaultStyle": true + }, + { + "type": "category", + "label": "How To Guides", + "items": [ + { + "type": "autogenerated", + "dirName": "how_to" + } + ] + }, + { + "type": "category", + "label": "Explainers", + "items": [ + { + "type": "autogenerated", + "dirName": "explainers" + } + ] + }, + { + "type": "category", + "label": "Tutorials", + "items": [ + { + "type": "autogenerated", + "dirName": "tutorials" + } + ] + }, + { + "type": "category", + "label": "Reference", + "items": [ + { + "type": "autogenerated", + "dirName": "reference" + } + ] + }, + { + "type": "category", + "label": "Tooling", + "items": [ + { + "type": "autogenerated", + "dirName": "tooling" + } + ] + }, + { + "type": "html", + "value": "
", + "defaultStyle": true + }, + { + "type": "doc", + "id": "migration_notes", + "label": "Migration notes" + } + ] +} diff --git a/noir/noir-repo/noir_stdlib/src/array/quicksort.nr b/noir/noir-repo/noir_stdlib/src/array/quicksort.nr index 6a54ed246f5..8563a5d75bd 100644 --- a/noir/noir-repo/noir_stdlib/src/array/quicksort.nr +++ b/noir/noir-repo/noir_stdlib/src/array/quicksort.nr @@ -30,7 +30,7 @@ unconstrained fn quicksort_recursive(arr: &mut [T; N], low: } } -unconstrained pub(crate) fn quicksort(_arr: [T; N], sortfn: fn[Env](T, T) -> bool) -> [T; N] { +pub(crate) unconstrained fn quicksort(_arr: [T; N], sortfn: fn[Env](T, T) -> bool) -> [T; N] { let mut arr: [T; N] = _arr; if arr.len() <= 1 {} else { quicksort_recursive(&mut arr, 0, arr.len() - 1, sortfn); 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/umap.nr b/noir/noir-repo/noir_stdlib/src/collections/umap.nr index 9d23e216731..8a7e6f81103 100644 --- a/noir/noir-repo/noir_stdlib/src/collections/umap.nr +++ b/noir/noir-repo/noir_stdlib/src/collections/umap.nr @@ -188,7 +188,7 @@ impl UHashMap { // For each key-value entry applies mutator function. // docs:start:iter_mut - unconstrained pub fn iter_mut( + pub unconstrained fn iter_mut( &mut self, f: fn(K, V) -> (K, V) ) @@ -210,7 +210,7 @@ impl UHashMap { // For each key applies mutator function. // docs:start:iter_keys_mut - unconstrained pub fn iter_keys_mut( + pub unconstrained fn iter_keys_mut( &mut self, f: fn(K) -> K ) @@ -277,7 +277,7 @@ impl UHashMap { // Get the value by key. If it does not exist, returns none(). // docs:start:get - unconstrained pub fn get( + pub unconstrained fn get( self, key: K ) -> Option @@ -309,7 +309,7 @@ impl UHashMap { // Insert key-value entry. In case key was already present, value is overridden. // docs:start:insert - unconstrained pub fn insert( + pub unconstrained fn insert( &mut self, key: K, value: V @@ -362,7 +362,7 @@ impl UHashMap { // Removes a key-value entry. If key is not present, UHashMap remains unchanged. // docs:start:remove - unconstrained pub fn remove( + pub unconstrained fn remove( &mut self, key: K ) 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 445b6d96554..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,20 +5,20 @@ 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 { /// Elliptic curve point doubling operation /// returns the doubled point of a point P, i.e P+P - fn double(self) -> EmbeddedCurvePoint { + pub fn double(self) -> EmbeddedCurvePoint { embedded_curve_add(self, self) } /// Returns the null element of the curve; 'the point at infinity' - fn point_at_infinity() -> EmbeddedCurvePoint { + pub fn point_at_infinity() -> EmbeddedCurvePoint { EmbeddedCurvePoint { x: 0, y: 0, is_infinite: true } } } @@ -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 { @@ -67,14 +67,14 @@ impl EmbeddedCurveScalar { } #[field(bn254)] - fn from_field(scalar: Field) -> EmbeddedCurveScalar { + pub fn from_field(scalar: Field) -> EmbeddedCurveScalar { let (a,b) = crate::field::bn254::decompose(scalar); EmbeddedCurveScalar { lo: a, hi: b } } //Bytes to scalar: take the first (after the specified offset) 16 bytes of the input as the lo value, and the next 16 bytes as the hi value #[field(bn254)] - fn from_bytes(bytes: [u8; 64], offset: u32) -> EmbeddedCurveScalar { + pub(crate) fn from_bytes(bytes: [u8; 64], offset: u32) -> EmbeddedCurveScalar { let mut v = 1; let mut lo = 0 as Field; let mut hi = 0 as Field; diff --git a/noir/noir-repo/noir_stdlib/src/field/bn254.nr b/noir/noir-repo/noir_stdlib/src/field/bn254.nr index 8ff62062d5c..532fcd59a62 100644 --- a/noir/noir-repo/noir_stdlib/src/field/bn254.nr +++ b/noir/noir-repo/noir_stdlib/src/field/bn254.nr @@ -5,25 +5,23 @@ 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; +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 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; - } + let high = (x - low_upper_64) / TWO_POW_64; + let low = low_upper_64 * TWO_POW_64 + low_lower_64; (low, high) } -unconstrained pub(crate) fn decompose_hint(x: Field) -> (Field, Field) { +pub(crate) unconstrained fn decompose_hint(x: Field) -> (Field, Field) { compute_decomposition(x) } diff --git a/noir/noir-repo/noir_stdlib/src/field/mod.nr b/noir/noir-repo/noir_stdlib/src/field/mod.nr index 93245e18072..35f95541354 100644 --- a/noir/noir-repo/noir_stdlib/src/field/mod.nr +++ b/noir/noir-repo/noir_stdlib/src/field/mod.nr @@ -1,5 +1,6 @@ pub mod bn254; use bn254::lt as bn254_lt; +use crate::runtime::is_unconstrained; impl Field { /// Asserts that `self` can be represented in `bit_size` bits. @@ -45,43 +46,75 @@ 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]` slice of length `byte_size`. - /// This slice will be zero padded should not all bytes be necessary to represent `self`. + /// 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 - /// Causes a constraint failure for `Field` values exceeding `2^{8*byte_size}` as the resulting slice will not - /// be able to represent the original `Field`. + /// 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 - /// Values of `byte_size` equal to or greater than the number of bytes necessary to represent the `Field` modulus - /// (e.g. 32 for the BN254 field) allow for multiple byte decompositions. This is due to how the `Field` will - /// wrap around due to overflow when verifying the decomposition. + /// The result is ensured to be the canonical decomposition of the field element // docs:start:to_le_bytes pub fn to_le_bytes(self: Self) -> [u8; N] { - self.to_le_radix(256) + // docs:end:to_le_bytes + // Compute the byte decomposition + let bytes = self.to_le_radix(256); + + if !is_unconstrained() { + // Ensure that the byte decomposition does not overflow the modulus + let p = modulus_le_bytes(); + assert(bytes.len() <= p.len()); + let mut ok = bytes.len() != p.len(); + for i in 0..N { + if !ok { + if (bytes[N - 1 - i] != p[N - 1 - i]) { + assert(bytes[N - 1 - i] < p[N - 1 - i]); + ok = true; + } + } + } + assert(ok); + } + bytes } - // docs:end:to_le_bytes - /// Decomposes `self` into its big endian byte decomposition as a `[u8]` slice of length `byte_size`. - /// This slice will be zero padded should not all bytes be necessary to represent `self`. + /// 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 - /// Causes a constraint failure for `Field` values exceeding `2^{8*byte_size}` as the resulting slice will not - /// be able to represent the original `Field`. + /// 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 - /// Values of `byte_size` equal to or greater than the number of bytes necessary to represent the `Field` modulus - /// (e.g. 32 for the BN254 field) allow for multiple byte decompositions. This is due to how the `Field` will - /// wrap around due to overflow when verifying the decomposition. + /// The result is ensured to be the canonical decomposition of the field element // docs:start:to_be_bytes pub fn to_be_bytes(self: Self) -> [u8; N] { - self.to_be_radix(256) + // docs:end:to_be_bytes + // Compute the byte decomposition + let bytes = self.to_be_radix(256); + + if !is_unconstrained() { + // Ensure that the byte decomposition does not overflow the modulus + let p = modulus_be_bytes(); + assert(bytes.len() <= p.len()); + let mut ok = bytes.len() != p.len(); + for i in 0..N { + if !ok { + if (bytes[i] != p[i]) { + assert(bytes[i] < p[i]); + ok = true; + } + } + } + assert(ok); + } + bytes } - // docs:end:to_be_bytes // docs:start:to_le_radix pub fn to_le_radix(self: Self, radix: u32) -> [u8; N] { @@ -130,6 +163,32 @@ impl Field { lt_fallback(self, another) } } + + /// Convert a little endian byte array to a field element. + /// If the provided byte array overflows the field modulus then the Field will silently wrap around. + pub fn from_le_bytes(bytes: [u8; N]) -> Field { + let mut v = 1; + let mut result = 0; + + for i in 0..N { + result += (bytes[i] as Field) * v; + v = v * 256; + } + result + } + + /// Convert a big endian byte array to a field element. + /// If the provided byte array overflows the field modulus then the Field will silently wrap around. + pub fn from_be_bytes(bytes: [u8; N]) -> Field { + let mut v = 1; + let mut result = 0; + + for i in 0..N { + result += (bytes[N-1-i] as Field) * v; + v = v * 256; + } + result + } } #[builtin(modulus_num_bits)] @@ -207,6 +266,7 @@ mod tests { let field = 2; let bits: [u8; 8] = field.to_be_bytes(); assert_eq(bits, [0, 0, 0, 0, 0, 0, 0, 2]); + assert_eq(Field::from_be_bytes::<8>(bits), field); } // docs:end:to_be_bytes_example @@ -216,6 +276,7 @@ mod tests { let field = 2; let bits: [u8; 8] = field.to_le_bytes(); assert_eq(bits, [2, 0, 0, 0, 0, 0, 0, 0]); + assert_eq(Field::from_le_bytes::<8>(bits), field); } // docs:end:to_le_bytes_example @@ -225,6 +286,7 @@ mod tests { let field = 2; let bits: [u8; 8] = field.to_be_radix(256); assert_eq(bits, [0, 0, 0, 0, 0, 0, 0, 2]); + assert_eq(Field::from_be_bytes::<8>(bits), field); } // docs:end:to_be_radix_example @@ -234,6 +296,7 @@ mod tests { let field = 2; let bits: [u8; 8] = field.to_le_radix(256); assert_eq(bits, [2, 0, 0, 0, 0, 0, 0, 0]); + assert_eq(Field::from_le_bytes::<8>(bits), field); } // docs:end:to_le_radix_example } 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/poseidon2.nr b/noir/noir-repo/noir_stdlib/src/hash/poseidon2.nr index 6b61ca32302..fb813120fef 100644 --- a/noir/noir-repo/noir_stdlib/src/hash/poseidon2.nr +++ b/noir/noir-repo/noir_stdlib/src/hash/poseidon2.nr @@ -20,7 +20,7 @@ impl Poseidon2 { } } - fn new(iv: Field) -> Poseidon2 { + pub(crate) fn new(iv: Field) -> Poseidon2 { let mut result = Poseidon2 { cache: [0; 3], state: [0; 4], cache_size: 0, squeeze_mode: false }; result.state[RATE] = iv; result diff --git a/noir/noir-repo/noir_stdlib/src/hash/sha256.nr b/noir/noir-repo/noir_stdlib/src/hash/sha256.nr index 413c26d6f6b..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] { @@ -110,7 +109,7 @@ pub fn sha256_var(msg: [u8; N], message_size: u64) -> [u8; 32] { // If the block is filled, compress it. // An un-filled block is handled after this loop. - if msg_byte_ptr == BLOCK_SIZE { + if (msg_start < message_size) & (msg_byte_ptr == BLOCK_SIZE) { h = sha256_compression(msg_u8_to_u32(msg_block), h); } } @@ -335,4 +334,38 @@ mod tests { ]; assert_eq(sha256_var(input, input.len() as u64), result); } + + #[test] + fn same_msg_len_variable_padding() { + let input = [ + 29, 81, 165, 84, 243, 114, 101, 37, 242, 146, 127, 99, 69, 145, 39, 72, 213, 39, 253, 179, 218, 37, 217, 201, 172, 93, 198, 50, 249, 70, 15, 30, 162, 112, 187, 40, 140, 9, 236, 53, 32, 44, 38, 163, 113, 254, 192, 197, 44, 89, 71, 130, 169, 242, 17, 211, 214, 72, 19, 178, 186, 168, 147, 127, 99, 101, 252, 227, 8, 147, 150, 85, 97, 158, 17, 107, 218, 244, 82, 113, 247, 91, 208, 214, 60, 244, 87, 137, 173, 201, 130, 18, 66, 56, 198, 149, 207, 189, 175, 120, 123, 224, 177, 167, 251, 159, 143, 110, 68, 183, 189, 70, 126, 32, 35, 164, 44, 30, 44, 12, 65, 18, 62, 239, 242, 2, 248, 104, 2, 178, 64, 28, 126, 36, 137, 24, 14, 116, 91, 98, 90, 159, 218, 102, 45, 11, 110, 223, 245, 184, 52, 99, 59, 245, 136, 175, 3, 72, 164, 146, 145, 116, 22, 66, 24, 49, 193, 121, 3, 60, 37, 41, 97, 3, 190, 66, 195, 225, 63, 46, 3, 118, 4, 208, 15, 1, 40, 254, 235, 151, 123, 70, 180, 170, 44, 172, 90, 4, 254, 53, 239, 116, 246, 67, 56, 129, 61, 22, 169, 213, 65, 27, 216, 116, 162, 239, 214, 207, 126, 177, 20, 100, 25, 48, 143, 84, 215, 70, 197, 53, 65, 70, 86, 172, 61, 62, 9, 212, 167, 169, 133, 41, 126, 213, 196, 33, 192, 238, 0, 63, 246, 215, 58, 128, 110, 101, 92, 3, 170, 214, 130, 149, 52, 81, 125, 118, 233, 3, 118, 193, 104, 207, 120, 115, 77, 253, 191, 122, 0, 107, 164, 207, 113, 81, 169, 36, 201, 228, 74, 134, 131, 218, 178, 35, 30, 216, 101, 2, 103, 174, 87, 95, 50, 50, 215, 157, 5, 210, 188, 54, 211, 78, 45, 199, 96, 121, 241, 241, 176, 226, 194, 134, 130, 89, 217, 210, 186, 32, 140, 39, 91, 103, 212, 26, 87, 32, 72, 144, 228, 230, 117, 99, 188, 50, 15, 69, 79, 179, 50, 12, 106, 86, 218, 101, 73, 142, 243, 29, 250, 122, 228, 233, 29, 255, 22, 121, 114, 125, 103, 41, 250, 241, 179, 126, 158, 198, 116, 209, 65, 94, 98, 228, 175, 169, 96, 3, 9, 233, 133, 214, 55, 161, 164, 103, 80, 85, 24, 186, 64, 167, 92, 131, 53, 101, 202, 47, 25, 104, 118, 155, 14, 12, 12, 25, 116, 45, 221, 249, 28, 246, 212, 200, 157, 167, 169, 56, 197, 181, 4, 245, 146, 1, 140, 234, 191, 212, 228, 125, 87, 81, 86, 119, 30, 63, 129, 143, 32, 96 + ]; + + // Prepare inputs of different lengths + let mut input_511 = [0; 511]; + let mut input_512 = [0; 512]; // Next block + let mut input_575 = [0; 575]; + let mut input_576 = [0; 576]; // Next block + for i in 0..input.len() { + input_511[i] = input[i]; + input_512[i] = input[i]; + input_575[i] = input[i]; + input_576[i] = input[i]; + } + + // Compute hashes of all inputs (with same message length) + let fixed_length_hash = super::sha256(input); + let var_full_length_hash = sha256_var(input, input.len() as u64); + let var_length_hash_511 = sha256_var(input_511, input.len() as u64); + let var_length_hash_512 = sha256_var(input_512, input.len() as u64); + let var_length_hash_575 = sha256_var(input_575, input.len() as u64); + let var_length_hash_576 = sha256_var(input_576, input.len() as u64); + + // All of the above should have produced the same hash + assert_eq(var_full_length_hash, fixed_length_hash); + assert_eq(var_length_hash_511, fixed_length_hash); + assert_eq(var_length_hash_512, fixed_length_hash); + assert_eq(var_length_hash_575, fixed_length_hash); + assert_eq(var_length_hash_576, fixed_length_hash); + } } 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/ctstring.nr b/noir/noir-repo/noir_stdlib/src/meta/ctstring.nr index ec205fa6f88..4b4854682db 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/ctstring.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/ctstring.nr @@ -2,20 +2,20 @@ use crate::append::Append; impl CtString { // docs:start:new - comptime fn new() -> Self { + pub comptime fn new() -> Self { // docs:end:new "".as_ctstring() } // Bug: using &mut self as the object results in this method not being found // docs:start:append_str - comptime fn append_str(self, s: str) -> Self { + pub comptime fn append_str(self, s: str) -> Self { // docs:end:append_str f"{self}{s}".as_ctstring() } // docs:start:append_fmtstr - comptime fn append_fmtstr(self, s: fmtstr) -> Self { + pub comptime fn append_fmtstr(self, s: fmtstr) -> Self { // docs:end:append_fmtstr f"{self}{s}".as_ctstring() } @@ -24,7 +24,7 @@ impl CtString { /// To get around this, we return a quoted str and the underlying str can /// be accessed using macro insertion `foo.as_quoted_str!()`. // docs:start:as_quoted_str - comptime fn as_quoted_str(self) -> Quoted { + pub comptime fn as_quoted_str(self) -> Quoted { // docs:end:as_quoted_str quote { $self } } diff --git a/noir/noir-repo/noir_stdlib/src/meta/expr.nr b/noir/noir-repo/noir_stdlib/src/meta/expr.nr index c96f7d27442..83a165fc533 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/expr.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/expr.nr @@ -8,89 +8,89 @@ impl Expr { /// If this expression is an array literal `[elem1, ..., elemN]`, this returns a slice of each element in the array. #[builtin(expr_as_array)] // docs:start:as_array - comptime fn as_array(self) -> Option<[Expr]> {} + pub comptime fn as_array(self) -> Option<[Expr]> {} // docs:end:as_array /// If this expression is an assert, this returns the assert expression and the optional message. #[builtin(expr_as_assert)] // docs:start:as_assert - comptime fn as_assert(self) -> Option<(Expr, Option)> {} + pub comptime fn as_assert(self) -> Option<(Expr, Option)> {} // docs:end:as_assert /// If this expression is an assert_eq, this returns the left-hand-side and right-hand-side /// expressions, together with the optional message. #[builtin(expr_as_assert_eq)] // docs:start:as_assert_eq - comptime fn as_assert_eq(self) -> Option<(Expr, Expr, Option)> {} + pub comptime fn as_assert_eq(self) -> Option<(Expr, Expr, Option)> {} // docs:end:as_assert_eq /// If this expression is an assignment, this returns a tuple with the left hand side /// and right hand side in order. #[builtin(expr_as_assign)] // docs:start:as_assign - comptime fn as_assign(self) -> Option<(Expr, Expr)> {} + pub comptime fn as_assign(self) -> Option<(Expr, Expr)> {} // docs:end:as_assign /// If this expression is a binary operator operation ` `, /// return the left-hand side, operator, and the right-hand side of the operation. #[builtin(expr_as_binary_op)] // docs:start:as_binary_op - comptime fn as_binary_op(self) -> Option<(Expr, BinaryOp, Expr)> {} + pub comptime fn as_binary_op(self) -> Option<(Expr, BinaryOp, Expr)> {} // docs:end:as_binary_op /// If this expression is a block `{ stmt1; stmt2; ...; stmtN }`, return /// a slice containing each statement. #[builtin(expr_as_block)] // docs:start:as_block - comptime fn as_block(self) -> Option<[Expr]> {} + pub comptime fn as_block(self) -> Option<[Expr]> {} // docs:end:as_block /// If this expression is a boolean literal, return that literal. #[builtin(expr_as_bool)] // docs:start:as_bool - comptime fn as_bool(self) -> Option {} + pub comptime fn as_bool(self) -> Option {} // docs:end:as_bool /// If this expression is a cast expression `expr as type`, returns the casted /// expression and the type to cast to. // docs:start:as_cast #[builtin(expr_as_cast)] - comptime fn as_cast(self) -> Option<(Expr, UnresolvedType)> {} + pub comptime fn as_cast(self) -> Option<(Expr, UnresolvedType)> {} // docs:end:as_cast /// If this expression is a `comptime { stmt1; stmt2; ...; stmtN }` block, /// return each statement in the block. #[builtin(expr_as_comptime)] // docs:start:as_comptime - comptime fn as_comptime(self) -> Option<[Expr]> {} + pub comptime fn as_comptime(self) -> Option<[Expr]> {} // docs:end:as_comptime /// If this expression is a constructor `Type { field1: expr1, ..., fieldN: exprN }`, /// return the type and the fields. #[builtin(expr_as_constructor)] // docs:start:as_constructor - comptime fn as_constructor(self) -> Option<(UnresolvedType, [(Quoted, Expr)])> {} + pub comptime fn as_constructor(self) -> Option<(UnresolvedType, [(Quoted, Expr)])> {} // docs:end:as_constructor /// If this expression is a for statement over a single expression, return the identifier, /// the expression and the for loop body. #[builtin(expr_as_for)] // docs:start:as_for - comptime fn as_for(self) -> Option<(Quoted, Expr, Expr)> {} + pub comptime fn as_for(self) -> Option<(Quoted, Expr, Expr)> {} // docs:end:as_for /// If this expression is a for statement over a range, return the identifier, /// the range start, the range end and the for loop body. #[builtin(expr_as_for_range)] // docs:start:as_for_range - comptime fn as_for_range(self) -> Option<(Quoted, Expr, Expr, Expr)> {} + pub comptime fn as_for_range(self) -> Option<(Quoted, Expr, Expr, Expr)> {} // docs:end:as_for_range /// If this expression is a function call `foo(arg1, ..., argN)`, return /// the function and a slice of each argument. #[builtin(expr_as_function_call)] // docs:start:as_function_call - comptime fn as_function_call(self) -> Option<(Expr, [Expr])> {} + pub comptime fn as_function_call(self) -> Option<(Expr, [Expr])> {} // docs:end:as_function_call /// If this expression is an `if condition { then_branch } else { else_branch }`, @@ -98,90 +98,90 @@ impl Expr { /// `None` is returned for that branch instead. #[builtin(expr_as_if)] // docs:start:as_if - comptime fn as_if(self) -> Option<(Expr, Expr, Option)> {} + pub comptime fn as_if(self) -> Option<(Expr, Expr, Option)> {} // docs:end:as_if /// If this expression is an index into an array `array[index]`, return the /// array and the index. #[builtin(expr_as_index)] // docs:start:as_index - comptime fn as_index(self) -> Option<(Expr, Expr)> {} + pub comptime fn as_index(self) -> Option<(Expr, Expr)> {} // docs:end:as_index /// If this expression is an integer literal, return the integer as a field /// as well as whether the integer is negative (true) or not (false). #[builtin(expr_as_integer)] // docs:start:as_integer - comptime fn as_integer(self) -> Option<(Field, bool)> {} + pub comptime fn as_integer(self) -> Option<(Field, bool)> {} // docs:end:as_integer /// If this expression is a lambda, returns the parameters, return type and body. #[builtin(expr_as_lambda)] // docs:start:as_lambda - comptime fn as_lambda(self) -> Option<([(Expr, Option)], Option, Expr)> {} + pub comptime fn as_lambda(self) -> Option<([(Expr, Option)], Option, Expr)> {} // docs:end:as_lambda /// If this expression is a let statement, returns the let pattern as an `Expr`, /// the optional type annotation, and the assigned expression. #[builtin(expr_as_let)] // docs:start:as_let - comptime fn as_let(self) -> Option<(Expr, Option, Expr)> {} + pub comptime fn as_let(self) -> Option<(Expr, Option, Expr)> {} // docs:end:as_let /// If this expression is a member access `foo.bar`, return the struct/tuple /// expression and the field. The field will be represented as a quoted value. #[builtin(expr_as_member_access)] // docs:start:as_member_access - comptime fn as_member_access(self) -> Option<(Expr, Quoted)> {} + pub comptime fn as_member_access(self) -> Option<(Expr, Quoted)> {} // docs:end:as_member_access /// If this expression is a method call `foo.bar::(arg1, ..., argN)`, return /// the receiver, method name, a slice of each generic argument, and a slice of each argument. #[builtin(expr_as_method_call)] // docs:start:as_method_call - comptime fn as_method_call(self) -> Option<(Expr, Quoted, [UnresolvedType], [Expr])> {} + pub comptime fn as_method_call(self) -> Option<(Expr, Quoted, [UnresolvedType], [Expr])> {} // docs:end:as_method_call /// If this expression is a repeated element array `[elem; length]`, return /// the repeated element and the length expressions. #[builtin(expr_as_repeated_element_array)] // docs:start:as_repeated_element_array - comptime fn as_repeated_element_array(self) -> Option<(Expr, Expr)> {} + pub comptime fn as_repeated_element_array(self) -> Option<(Expr, Expr)> {} // docs:end:as_repeated_element_array /// If this expression is a repeated element slice `[elem; length]`, return /// the repeated element and the length expressions. #[builtin(expr_as_repeated_element_slice)] // docs:start:as_repeated_element_slice - comptime fn as_repeated_element_slice(self) -> Option<(Expr, Expr)> {} + pub comptime fn as_repeated_element_slice(self) -> Option<(Expr, Expr)> {} // docs:end:as_repeated_element_slice /// If this expression is a slice literal `&[elem1, ..., elemN]`, /// return each element of the slice. #[builtin(expr_as_slice)] // docs:start:as_slice - comptime fn as_slice(self) -> Option<[Expr]> {} + pub comptime fn as_slice(self) -> Option<[Expr]> {} // docs:end:as_slice /// If this expression is a tuple `(field1, ..., fieldN)`, /// return each element of the tuple. #[builtin(expr_as_tuple)] // docs:start:as_tuple - comptime fn as_tuple(self) -> Option<[Expr]> {} + pub comptime fn as_tuple(self) -> Option<[Expr]> {} // docs:end:as_tuple /// If this expression is a unary operation ` `, /// return the unary operator as well as the right-hand side expression. #[builtin(expr_as_unary_op)] // docs:start:as_unary_op - comptime fn as_unary_op(self) -> Option<(UnaryOp, Expr)> {} + pub comptime fn as_unary_op(self) -> Option<(UnaryOp, Expr)> {} // docs:end:as_unary_op /// If this expression is an `unsafe { stmt1; ...; stmtN }` block, /// return each statement inside in a slice. #[builtin(expr_as_unsafe)] // docs:start:as_unsafe - comptime fn as_unsafe(self) -> Option<[Expr]> {} + pub comptime fn as_unsafe(self) -> Option<[Expr]> {} // docs:end:as_unsafe /// Returns `true` if this expression is trailed by a semicolon. @@ -202,19 +202,19 @@ impl Expr { /// ``` #[builtin(expr_has_semicolon)] // docs:start:has_semicolon - comptime fn has_semicolon(self) -> bool {} + pub comptime fn has_semicolon(self) -> bool {} // docs:end:has_semicolon /// Returns `true` if this expression is `break`. #[builtin(expr_is_break)] // docs:start:is_break - comptime fn is_break(self) -> bool {} + pub comptime fn is_break(self) -> bool {} // docs:end:is_break /// Returns `true` if this expression is `continue`. #[builtin(expr_is_continue)] // docs:start:is_continue - comptime fn is_continue(self) -> bool {} + pub comptime fn is_continue(self) -> bool {} // docs:end:is_continue /// Applies a mapping function to this expression and to all of its sub-expressions. @@ -225,7 +225,7 @@ impl Expr { /// For example, calling `modify` on `(&[1], &[2, 3])` with an `f` that returns `Option::some` /// for expressions that are integers, doubling them, would return `(&[2], &[4, 6])`. // docs:start:modify - comptime fn modify(self, f: fn[Env](Expr) -> Option) -> Expr { + pub comptime fn modify(self, f: fn[Env](Expr) -> Option) -> Expr { // docs:end:modify let result = modify_array(self, f); let result = result.or_else(|| modify_assert(self, f)); @@ -262,7 +262,7 @@ impl Expr { /// Returns this expression as a `Quoted` value. It's the same as `quote { $self }`. // docs:start:quoted - comptime fn quoted(self) -> Quoted { + pub comptime fn quoted(self) -> Quoted { // docs:end:quoted quote { $self } } @@ -278,7 +278,7 @@ impl Expr { /// the current `comptime` function. #[builtin(expr_resolve)] // docs:start:resolve - comptime fn resolve(self, in_function: Option) -> TypedExpr {} + pub comptime fn resolve(self, in_function: Option) -> TypedExpr {} // docs:end:resolve } diff --git a/noir/noir-repo/noir_stdlib/src/meta/format_string.nr b/noir/noir-repo/noir_stdlib/src/meta/format_string.nr index 075a69fdafb..f3c18212599 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/format_string.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/format_string.nr @@ -1,6 +1,6 @@ impl fmtstr { #[builtin(fmtstr_quoted_contents)] // docs:start:quoted_contents - comptime fn quoted_contents(self) -> Quoted {} + pub comptime fn quoted_contents(self) -> Quoted {} // docs:end:quoted_contents } diff --git a/noir/noir-repo/noir_stdlib/src/meta/function_def.nr b/noir/noir-repo/noir_stdlib/src/meta/function_def.nr index cd5f9cc79fb..3c29d57e20c 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/function_def.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/function_def.nr @@ -1,67 +1,67 @@ impl FunctionDefinition { #[builtin(function_def_add_attribute)] // docs:start:add_attribute - comptime fn add_attribute(self, attribute: str) {} + pub comptime fn add_attribute(self, attribute: str) {} // docs:end:add_attribute #[builtin(function_def_body)] // docs:start:body - comptime fn body(self) -> Expr {} + pub comptime fn body(self) -> Expr {} // docs:end:body #[builtin(function_def_has_named_attribute)] // docs:start:has_named_attribute - comptime fn has_named_attribute(self, name: str) -> bool {} + pub comptime fn has_named_attribute(self, name: str) -> bool {} // docs:end:has_named_attribute #[builtin(function_def_is_unconstrained)] // docs:start:is_unconstrained - comptime fn is_unconstrained(self) -> bool {} + pub comptime fn is_unconstrained(self) -> bool {} // docs:end:is_unconstrained #[builtin(function_def_module)] // docs:start:module - comptime fn module(self) -> Module {} + pub comptime fn module(self) -> Module {} // docs:end:module #[builtin(function_def_name)] // docs:start:name - comptime fn name(self) -> Quoted {} + pub comptime fn name(self) -> Quoted {} // docs:end:name #[builtin(function_def_parameters)] // docs:start:parameters - comptime fn parameters(self) -> [(Quoted, Type)] {} + pub comptime fn parameters(self) -> [(Quoted, Type)] {} // docs:end:parameters #[builtin(function_def_return_type)] // docs:start:return_type - comptime fn return_type(self) -> Type {} + pub comptime fn return_type(self) -> Type {} // docs:end:return_type #[builtin(function_def_set_body)] // docs:start:set_body - comptime fn set_body(self, body: Expr) {} + pub comptime fn set_body(self, body: Expr) {} // docs:end:set_body #[builtin(function_def_set_parameters)] // docs:start:set_parameters - comptime fn set_parameters(self, parameters: [(Quoted, Type)]) {} + pub comptime fn set_parameters(self, parameters: [(Quoted, Type)]) {} // docs:end:set_parameters #[builtin(function_def_set_return_type)] // docs:start:set_return_type - comptime fn set_return_type(self, return_type: Type) {} + pub comptime fn set_return_type(self, return_type: Type) {} // docs:end:set_return_type #[builtin(function_def_set_return_public)] // docs:start:set_return_public - comptime fn set_return_public(self, public: bool) {} + pub comptime fn set_return_public(self, public: bool) {} // docs:end:set_return_public #[builtin(function_def_set_unconstrained)] // docs:start:set_unconstrained - comptime fn set_unconstrained(self, value: bool) {} + pub comptime fn set_unconstrained(self, value: bool) {} // docs:end:set_unconstrained } 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/module.nr b/noir/noir-repo/noir_stdlib/src/meta/module.nr index 1b29301c4ef..e9fac5ded0a 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/module.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/module.nr @@ -1,32 +1,32 @@ impl Module { #[builtin(module_add_item)] // docs:start:add_item - comptime fn add_item(self, item: Quoted) {} + pub comptime fn add_item(self, item: Quoted) {} // docs:end:add_item #[builtin(module_has_named_attribute)] // docs:start:has_named_attribute - comptime fn has_named_attribute(self, name: str) -> bool {} + pub comptime fn has_named_attribute(self, name: str) -> bool {} // docs:end:has_named_attribute #[builtin(module_is_contract)] // docs:start:is_contract - comptime fn is_contract(self) -> bool {} + pub comptime fn is_contract(self) -> bool {} // docs:end:is_contract #[builtin(module_functions)] // docs:start:functions - comptime fn functions(self) -> [FunctionDefinition] {} + pub comptime fn functions(self) -> [FunctionDefinition] {} // docs:end:functions #[builtin(module_structs)] // docs:start:structs - comptime fn structs(self) -> [StructDefinition] {} + pub comptime fn structs(self) -> [StructDefinition] {} // docs:end:structs #[builtin(module_name)] // docs:start:name - comptime fn name(self) -> Quoted {} + pub comptime fn name(self) -> Quoted {} // docs:end:name } diff --git a/noir/noir-repo/noir_stdlib/src/meta/op.nr b/noir/noir-repo/noir_stdlib/src/meta/op.nr index 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/quoted.nr b/noir/noir-repo/noir_stdlib/src/meta/quoted.nr index 6e8d001c57c..cf97107ed68 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/quoted.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/quoted.nr @@ -4,27 +4,27 @@ use crate::option::Option; impl Quoted { #[builtin(quoted_as_expr)] // docs:start:as_expr - comptime fn as_expr(self) -> Option {} + pub comptime fn as_expr(self) -> Option {} // docs:end:as_expr #[builtin(quoted_as_module)] // docs:start:as_module - comptime fn as_module(self) -> Option {} + pub comptime fn as_module(self) -> Option {} // docs:end:as_module #[builtin(quoted_as_trait_constraint)] // docs:start:as_trait_constraint - comptime fn as_trait_constraint(self) -> TraitConstraint {} + pub comptime fn as_trait_constraint(self) -> TraitConstraint {} // docs:end:as_trait_constraint #[builtin(quoted_as_type)] // docs:start:as_type - comptime fn as_type(self) -> Type {} + pub comptime fn as_type(self) -> Type {} // docs:end:as_type #[builtin(quoted_tokens)] // docs:start:tokens - comptime fn tokens(self) -> [Quoted] {} + pub comptime fn tokens(self) -> [Quoted] {} // docs:end:tokens } diff --git a/noir/noir-repo/noir_stdlib/src/meta/struct_def.nr b/noir/noir-repo/noir_stdlib/src/meta/struct_def.nr index b17c0d37613..fe7eabc7007 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/struct_def.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/struct_def.nr @@ -1,47 +1,47 @@ impl StructDefinition { #[builtin(struct_def_add_attribute)] // docs:start:add_attribute - comptime fn add_attribute(self, attribute: str) {} + pub comptime fn add_attribute(self, attribute: str) {} // docs:end:add_attribute #[builtin(struct_def_add_generic)] // docs:start:add_generic - comptime fn add_generic(self, generic_name: str) -> Type {} + pub comptime fn add_generic(self, generic_name: str) -> Type {} // docs:end:add_generic /// Return a syntactic version of this struct definition as a type. /// For example, `as_type(quote { type Foo { ... } })` would return `Foo` #[builtin(struct_def_as_type)] // docs:start:as_type - comptime fn as_type(self) -> Type {} + pub comptime fn as_type(self) -> Type {} // docs:end:as_type #[builtin(struct_def_has_named_attribute)] // docs:start:has_named_attribute - comptime fn has_named_attribute(self, name: str) -> bool {} + pub comptime fn has_named_attribute(self, name: str) -> bool {} // docs:end:has_named_attribute /// Return each generic on this struct. #[builtin(struct_def_generics)] // docs:start:generics - comptime fn generics(self) -> [Type] {} + pub comptime fn generics(self) -> [Type] {} // docs:end:generics /// Returns (name, type) pairs of each field in this struct. Each type is as-is /// with any generic arguments unchanged. #[builtin(struct_def_fields)] // docs:start:fields - comptime fn fields(self) -> [(Quoted, Type)] {} + pub comptime fn fields(self) -> [(Quoted, Type)] {} // docs:end:fields #[builtin(struct_def_module)] // docs:start:module - comptime fn module(self) -> Module {} + pub comptime fn module(self) -> Module {} // docs:end:module #[builtin(struct_def_name)] // docs:start:name - comptime fn name(self) -> Quoted {} + pub comptime fn name(self) -> Quoted {} // docs:end:name /// Sets the fields of this struct to the given fields list. @@ -50,7 +50,7 @@ impl StructDefinition { /// Each name is expected to be a single identifier. #[builtin(struct_def_set_fields)] // docs:start:set_fields - comptime fn set_fields(self, new_fields: [(Quoted, Type)]) {} + pub comptime fn set_fields(self, new_fields: [(Quoted, Type)]) {} // docs:end:set_fields } diff --git a/noir/noir-repo/noir_stdlib/src/meta/trait_def.nr b/noir/noir-repo/noir_stdlib/src/meta/trait_def.nr index 51676efbc34..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,8 +3,8 @@ use crate::cmp::Eq; impl TraitDefinition { #[builtin(trait_def_as_trait_constraint)] -// docs:start:as_trait_constraint - comptime fn as_trait_constraint(_self: Self) -> TraitConstraint {} + // 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/meta/trait_impl.nr b/noir/noir-repo/noir_stdlib/src/meta/trait_impl.nr index 6755a5c2031..9db859bf8a0 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/trait_impl.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/trait_impl.nr @@ -1,11 +1,11 @@ impl TraitImpl { #[builtin(trait_impl_trait_generic_args)] -// docs:start:trait_generic_args - comptime fn trait_generic_args(self) -> [Type] {} + // docs:start:trait_generic_args + pub comptime fn trait_generic_args(self) -> [Type] {} // docs:end:trait_generic_args #[builtin(trait_impl_methods)] -// docs:start:methods - comptime fn methods(self) -> [FunctionDefinition] {} + // docs:start:methods + pub comptime fn methods(self) -> [FunctionDefinition] {} // docs:end:methods } diff --git a/noir/noir-repo/noir_stdlib/src/meta/typ.nr b/noir/noir-repo/noir_stdlib/src/meta/typ.nr index 5a748c2c823..31fdbb49b53 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/typ.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/typ.nr @@ -1,69 +1,195 @@ +//! Contains methods on the built-in `Type` type used for representing a type in the source program. + use crate::cmp::Eq; use crate::option::Option; +/// Creates and returns an unbound type variable. This is a special kind of type internal +/// to type checking which will type check with any other type. When it is type checked +/// against another type it will also be set to that type. For example, if `a` is a type +/// variable and we have the type equality `(a, i32) = (u8, i32)`, the compiler will set +/// `a` equal to `u8`. +/// +/// Unbound type variables will often be rendered as `_` while printing them. Bound type +/// variables will appear as the type they are bound to. +/// +/// This can be used in conjunction with functions which internally perform type checks +/// such as `Type::implements` or `Type::get_trait_impl` to potentially grab some of the types used. +/// +/// Note that calling `Type::implements` or `Type::get_trait_impl` on a type variable will always +/// fail. +/// +/// Example: +/// +/// ```noir +/// trait Serialize {} +/// +/// impl Serialize<1> for Field {} +/// +/// impl Serialize for [T; N] +/// where T: Serialize {} +/// +/// impl Serialize for (T, U) +/// where T: Serialize, U: Serialize {} +/// +/// fn fresh_variable_example() { +/// let typevar1 = std::meta::typ::fresh_type_variable(); +/// let constraint = quote { Serialize<$typevar1> }.as_trait_constraint(); +/// let field_type = quote { Field }.as_type(); +/// +/// // Search for a trait impl (binding typevar1 to 1 when the impl is found): +/// assert(field_type.implements(constraint)); +/// +/// // typevar1 should be bound to the "1" generic now: +/// assert_eq(typevar1.as_constant().unwrap(), 1); +/// +/// // If we want to do the same with a different type, we need to +/// // create a new type variable now that `typevar1` is bound +/// let typevar2 = std::meta::typ::fresh_type_variable(); +/// let constraint = quote { Serialize<$typevar2> }.as_trait_constraint(); +/// let array_type = quote { [(Field, Field); 5] }.as_type(); +/// assert(array_type.implements(constraint)); +/// +/// // Now typevar2 should be bound to the serialized pair size 2 times the array length 5 +/// assert_eq(typevar2.as_constant().unwrap(), 10); +/// } +/// ``` #[builtin(fresh_type_variable)] // docs:start:fresh_type_variable pub comptime fn fresh_type_variable() -> Type {} // docs:end:fresh_type_variable impl Type { + /// If this type is an array, return a pair of (element type, size type). + /// + /// Example: + /// + /// ```noir + /// comptime { + /// let array_type = quote { [Field; 3] }.as_type(); + /// let (field_type, three_type) = array_type.as_array().unwrap(); + /// + /// assert(field_type.is_field()); + /// assert_eq(three_type.as_constant().unwrap(), 3); + /// } + /// ``` #[builtin(type_as_array)] -// docs:start:as_array - comptime fn as_array(self) -> Option<(Type, Type)> {} + // docs:start:as_array + pub comptime fn as_array(self) -> Option<(Type, Type)> {} // docs:end:as_array + /// If this type is a constant integer (such as the `3` in the array type `[Field; 3]`), + /// return the numeric constant. #[builtin(type_as_constant)] -// docs:start:as_constant - comptime fn as_constant(self) -> Option {} + // docs:start:as_constant + pub comptime fn as_constant(self) -> Option {} // docs:end:as_constant + /// If this is an integer type, return a boolean which is `true` + /// if the type is signed, as well as the number of bits of this integer type. #[builtin(type_as_integer)] -// docs:start:as_integer - comptime fn as_integer(self) -> Option<(bool, u8)> {} + // docs:start:as_integer + pub comptime fn as_integer(self) -> Option<(bool, u8)> {} // docs:end:as_integer + /// If this is a mutable reference type `&mut T`, returns the mutable type `T`. + #[builtin(type_as_mutable_reference)] + // docs:start:as_mutable_reference + comptime fn as_mutable_reference(self) -> Option {} + // docs:end:as_mutable_reference + + /// If this is a slice type, return the element type of the slice. #[builtin(type_as_slice)] -// docs:start:as_slice - comptime fn as_slice(self) -> Option {} + // docs:start:as_slice + pub comptime fn as_slice(self) -> Option {} // docs:end:as_slice + /// If this is a `str` type, returns the length `N` as a type. #[builtin(type_as_str)] -// docs:start:as_str - comptime fn as_str(self) -> Option {} + // docs:start:as_str + pub comptime fn as_str(self) -> Option {} // docs:end:as_str + /// If this is a struct type, returns the struct in addition to any generic arguments on this type. #[builtin(type_as_struct)] -// docs:start:as_struct - comptime fn as_struct(self) -> Option<(StructDefinition, [Type])> {} + // docs:start:as_struct + pub comptime fn as_struct(self) -> Option<(StructDefinition, [Type])> {} // docs:end:as_struct + /// If this is a tuple type, returns each element type of the tuple. #[builtin(type_as_tuple)] -// docs:start:as_tuple - comptime fn as_tuple(self) -> Option<[Type]> {} + // docs:start:as_tuple + pub comptime fn as_tuple(self) -> Option<[Type]> {} // docs:end:as_tuple + /// Retrieves the trait implementation that implements the given + /// trait constraint for this type. If the trait constraint is not + /// found, `None` is returned. Note that since the concrete trait implementation + /// for a trait constraint specified from a `where` clause is unknown, + /// this function will return `None` in these cases. If you only want to know + /// whether a type implements a trait, use `implements` instead. + /// + /// Example: + /// + /// ```rust + /// comptime { + /// let field_type = quote { Field }.as_type(); + /// let default = quote { Default }.as_trait_constraint(); + /// + /// let the_impl: TraitImpl = field_type.get_trait_impl(default).unwrap(); + /// assert(the_impl.methods().len(), 1); + /// } + /// ``` #[builtin(type_get_trait_impl)] -// docs:start:get_trait_impl - comptime fn get_trait_impl(self, constraint: TraitConstraint) -> Option {} + // docs:start:get_trait_impl + pub comptime fn get_trait_impl(self, constraint: TraitConstraint) -> Option {} // docs:end:get_trait_impl + /// Returns `true` if this type implements the given trait. Note that unlike + /// `get_trait_impl` this will also return true for any `where` constraints + /// in scope. + /// + /// Example: + /// + /// ```rust + /// fn foo() where T: Default { + /// comptime { + /// let field_type = quote { Field }.as_type(); + /// let default = quote { Default }.as_trait_constraint(); + /// assert(field_type.implements(default)); + /// + /// let t = quote { T }.as_type(); + /// assert(t.implements(default)); + /// } + /// } + /// ``` #[builtin(type_implements)] -// docs:start:implements - comptime fn implements(self, constraint: TraitConstraint) -> bool {} + // docs:start:implements + pub comptime fn implements(self, constraint: TraitConstraint) -> bool {} // docs:end:implements + /// Returns `true` if this type is `bool`. #[builtin(type_is_bool)] -// docs:start:is_bool - comptime fn is_bool(self) -> bool {} + // docs:start:is_bool + pub comptime fn is_bool(self) -> bool {} // docs:end:is_bool + /// Returns `true` if this type is `Field`. #[builtin(type_is_field)] -// docs:start:is_field - comptime fn is_field(self) -> bool {} + // docs:start:is_field + pub comptime fn is_field(self) -> bool {} // docs:end:is_field + + /// Returns `true` if this type is the unit `()` type. + #[builtin(type_is_unit)] + // docs:start:is_unit + comptime fn is_unit(self) -> bool {} + // docs:end:is_unit } impl Eq for Type { + /// Note that this is syntactic equality, this is not the same as whether two types will type check + /// to be the same type. Unless type inference or generics are being used however, users should not + /// typically have to worry about this distinction. comptime fn eq(self, other: Self) -> bool { type_eq(self, other) } diff --git a/noir/noir-repo/noir_stdlib/src/meta/typed_expr.nr b/noir/noir-repo/noir_stdlib/src/meta/typed_expr.nr index 1d3b073b6da..c69139ea233 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/typed_expr.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/typed_expr.nr @@ -5,12 +5,12 @@ impl TypedExpr { /// If this expression refers to a function definitions, returns it. Otherwise returns `Option::none()`. #[builtin(typed_expr_as_function_definition)] // docs:start:as_function_definition - comptime fn as_function_definition(self) -> Option {} + pub comptime fn as_function_definition(self) -> Option {} // docs:end:as_function_definition /// Returns the type of the expression, if the expression could be resolved without errors. #[builtin(typed_expr_get_type)] // docs:start:get_type - comptime fn get_type(self) -> Option {} + pub comptime fn get_type(self) -> Option {} // docs:end:get_type } diff --git a/noir/noir-repo/noir_stdlib/src/meta/unresolved_type.nr b/noir/noir-repo/noir_stdlib/src/meta/unresolved_type.nr index f53635414cc..5c2e8803674 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/unresolved_type.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/unresolved_type.nr @@ -1,6 +1,35 @@ +//! Contains methods on the built-in `UnresolvedType` type for the syntax of types. + +use crate::option::Option; + impl UnresolvedType { + /// If this is a mutable reference type `&mut T`, returns the mutable type `T`. + #[builtin(unresolved_type_as_mutable_reference)] + // docs:start:as_mutable_reference + comptime fn as_mutable_reference(self) -> Option {} + // docs:end:as_mutable_reference + + /// If this is a slice `&[T]`, returns the element type `T`. + #[builtin(unresolved_type_as_slice)] + // docs:start:as_slice + comptime fn as_slice(self) -> Option {} + // docs:end:as_slice + + /// Returns `true` if this type is `bool`. + #[builtin(unresolved_type_is_bool)] + // docs:start:is_bool + comptime fn is_bool(self) -> bool {} + // docs:end:is_bool + + /// Returns true if this type refers to the `Field` type. #[builtin(unresolved_type_is_field)] // docs:start:is_field - comptime fn is_field(self) -> bool {} + pub comptime fn is_field(self) -> bool {} // docs:end:is_field + + /// Returns true if this type is the unit `()` type. + #[builtin(unresolved_type_is_unit)] + // docs:start:is_unit + comptime fn is_unit(self) -> bool {} + // docs:end:is_unit } 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 e24aabf3cda..76db04400e2 100644 --- a/noir/noir-repo/noir_stdlib/src/schnorr.nr +++ b/noir/noir-repo/noir_stdlib/src/schnorr.nr @@ -37,10 +37,12 @@ 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]; } + } else { + is_ok = false; } is_ok } @@ -92,3 +94,12 @@ fn calculate_signature_challenge( let result = crate::hash::blake2s(hash_input); (r.is_infinite, result) } + +#[test] +fn test_zero_signature() { + let public_key: EmbeddedCurvePoint = EmbeddedCurvePoint { x: 1, y: 17631683881184975370165255887551781615748388533673675138860, is_infinite: false }; + let signature: [u8; 64] = [0; 64]; + let message: [u8; _] = [2; 64]; // every message + let verified = verify_signature_noir(public_key, signature, message); + assert(!verified); +} 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/test.nr b/noir/noir-repo/noir_stdlib/src/test.nr index e906ad54d55..4d6954913d1 100644 --- a/noir/noir-repo/noir_stdlib/src/test.nr +++ b/noir/noir-repo/noir_stdlib/src/test.nr @@ -21,30 +21,30 @@ pub struct OracleMock { } impl OracleMock { - unconstrained pub fn mock(name: str) -> Self { + pub unconstrained fn mock(name: str) -> Self { Self { id: create_mock_oracle(name) } } - unconstrained pub fn with_params

(self, params: P) -> Self { + pub unconstrained fn with_params

(self, params: P) -> Self { set_mock_params_oracle(self.id, params); self } - unconstrained pub fn get_last_params

(self) -> P { + pub unconstrained fn get_last_params

(self) -> P { get_mock_last_params_oracle(self.id) } - unconstrained pub fn returns(self, returns: R) -> Self { + pub unconstrained fn returns(self, returns: R) -> Self { set_mock_returns_oracle(self.id, returns); self } - unconstrained pub fn times(self, times: u64) -> Self { + pub unconstrained fn times(self, times: u64) -> Self { set_mock_times_oracle(self.id, times); self } - unconstrained pub fn clear(self) { + pub unconstrained fn clear(self) { clear_mock_oracle(self.id); } } diff --git a/noir/noir-repo/noir_stdlib/src/uint128.nr b/noir/noir-repo/noir_stdlib/src/uint128.nr index a3d7491eaff..4a035ef91ef 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 { @@ -98,7 +98,7 @@ impl U128 { ((ascii >= 65) & (ascii <= 90)) // Between 'A' and 'Z' } - fn decode_ascii(ascii: u8) -> Field { + pub(crate) fn decode_ascii(ascii: u8) -> Field { (if ascii < 58 { ascii - 48 } else { 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 c7b3d0b9400..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 { } @@ -148,11 +148,19 @@ fn main() { // Now typevar2 should be bound to the serialized pair size 2 times the array length 5 assert_eq(typevar2.as_constant().unwrap(), 10); // docs:end:fresh-type-variable-example + + // Check Type::is_unit + let unit = quote { () }.as_type(); + assert(unit.is_unit()); + + // Check Type::as_mutable_reference + let typ = quote { &mut Field }.as_type(); + assert_eq(typ.as_mutable_reference().unwrap(), quote { Field }.as_type()); } } // 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/comptime_unresolved_type/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/comptime_unresolved_type/Nargo.toml new file mode 100644 index 00000000000..cc266b9b9c5 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_unresolved_type/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "comptime_unresolved_type" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_unresolved_type/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_unresolved_type/src/main.nr new file mode 100644 index 00000000000..aba4461d4af --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_unresolved_type/src/main.nr @@ -0,0 +1,24 @@ +fn main() { + comptime + { + // Check UnresolvedType::is_bool + let typ = quote { x as bool }.as_expr().unwrap().as_cast().unwrap().1; + assert(typ.is_bool()); + + // Check UnresolvedType::is_field + let typ = quote { x as Field }.as_expr().unwrap().as_cast().unwrap().1; + assert(typ.is_field()); + + // Check UnresolvedType::is_unit + let typ = quote { x as () }.as_expr().unwrap().as_cast().unwrap().1; + assert(typ.is_unit()); + + // Check UnresolvedType::as_mutable_reference + let typ = quote { x as &mut Field }.as_expr().unwrap().as_cast().unwrap().1; + assert(typ.as_mutable_reference().unwrap().is_field()); + + // Check UnresolvedType::as_slice + let typ = quote { x as [Field] }.as_expr().unwrap().as_cast().unwrap().1; + assert(typ.as_slice().unwrap().is_field()); + } +} 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/brillig_loop/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_loop/src/main.nr index 770660bb2a1..9de8c66b051 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_loop/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_loop/src/main.nr @@ -4,6 +4,7 @@ fn main(sum: u32) { unsafe { assert(loop(4) == sum); + assert(loop_incl(3) == sum); assert(plain_loop() == sum); } } @@ -16,6 +17,14 @@ unconstrained fn loop(x: u32) -> u32 { sum } +unconstrained fn loop_incl(x: u32) -> u32 { + let mut sum = 0; + for i in 0..=x { + sum = sum + i; + } + sum +} + unconstrained fn plain_loop() -> u32 { let mut sum = 0; for i in 0..4 { 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..1d65b342966 --- /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/databus_composite_calldata/Nargo.toml b/noir/noir-repo/test_programs/execution_success/databus_composite_calldata/Nargo.toml new file mode 100644 index 00000000000..38c916e5d97 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/databus_composite_calldata/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "databus_composite_calldata" +type = "bin" +authors = [""] + +[dependencies] diff --git a/noir/noir-repo/test_programs/execution_success/databus_composite_calldata/Prover.toml b/noir/noir-repo/test_programs/execution_success/databus_composite_calldata/Prover.toml new file mode 100644 index 00000000000..ab154c13372 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/databus_composite_calldata/Prover.toml @@ -0,0 +1,11 @@ +zero = "0" +one = "1" +values = [[["12", "33"], ["37", "11"]],[["14", "37"], ["30", "10"]],[["10", "30"], ["30", "10"]]] + +[[foos]] +x = 1 +y = [1,2,3,4,5,6,7,8,9,0] + +[[foos]] +x = 2 +y = [1,2,3,5,6,8,7,8,9,0] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_success/databus_composite_calldata/src/main.nr b/noir/noir-repo/test_programs/execution_success/databus_composite_calldata/src/main.nr new file mode 100644 index 00000000000..e8b88e84d0d --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/databus_composite_calldata/src/main.nr @@ -0,0 +1,16 @@ +struct Foo { + x: u32, + y: [u32; 10], +} + +fn main( + foos: call_data(0) [Foo; 2], + values: call_data(0) [[[u32; 2]; 2]; 3], + zero: u32, + one: u32 +) -> pub u32 { + assert_eq(foos[zero].x + 1, foos[one].x); + assert_eq(foos[zero].y[3] + 2, foos[one].y[4]); + assert_eq(values[zero][one][zero], values[one][zero][one]); + foos[zero].x + foos[one].y[0] +} diff --git a/noir/noir-repo/test_programs/execution_success/inline_never_basic/Nargo.toml b/noir/noir-repo/test_programs/execution_success/inline_never_basic/Nargo.toml deleted file mode 100644 index 16691770d76..00000000000 --- a/noir/noir-repo/test_programs/execution_success/inline_never_basic/Nargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "inline_never_basic" -type = "bin" -authors = [""] -compiler_version = ">=0.27.0" - -[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_success/inline_never_basic/Prover.toml b/noir/noir-repo/test_programs/execution_success/inline_never_basic/Prover.toml deleted file mode 100644 index f28f2f8cc48..00000000000 --- a/noir/noir-repo/test_programs/execution_success/inline_never_basic/Prover.toml +++ /dev/null @@ -1,2 +0,0 @@ -x = "5" -y = "10" 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 deleted file mode 100644 index 1922aaedb6c..00000000000 --- a/noir/noir-repo/test_programs/execution_success/inline_never_basic/src/main.nr +++ /dev/null @@ -1,8 +0,0 @@ -fn main(x: Field, y: pub Field) { - basic_check(x, y); -} - -#[inline(never)] -fn basic_check(x: Field, y: Field) { - assert(x != y); -} diff --git a/noir/noir-repo/test_programs/execution_success/verify_honk_proof/Nargo.toml b/noir/noir-repo/test_programs/execution_success/loop/Nargo.toml similarity index 66% rename from noir/noir-repo/test_programs/execution_success/verify_honk_proof/Nargo.toml rename to noir/noir-repo/test_programs/execution_success/loop/Nargo.toml index 8fce1bf44b6..66c72338363 100644 --- a/noir/noir-repo/test_programs/execution_success/verify_honk_proof/Nargo.toml +++ b/noir/noir-repo/test_programs/execution_success/loop/Nargo.toml @@ -1,5 +1,5 @@ [package] -name = "verify_honk_proof" +name = "loop" type = "bin" authors = [""] diff --git a/noir/noir-repo/test_programs/execution_success/loop/Prover.toml b/noir/noir-repo/test_programs/execution_success/loop/Prover.toml new file mode 100644 index 00000000000..0f44bf96f44 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/loop/Prover.toml @@ -0,0 +1 @@ +six_as_u32 = "6" diff --git a/noir/noir-repo/test_programs/execution_success/loop/src/main.nr b/noir/noir-repo/test_programs/execution_success/loop/src/main.nr new file mode 100644 index 00000000000..8365cf6f801 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/loop/src/main.nr @@ -0,0 +1,23 @@ +// Tests a very simple program. +// +// The features being tested is basic looping. +fn main(six_as_u32: u32) { + assert_eq(loop(4), six_as_u32); + assert_eq(loop_incl(3), six_as_u32); +} + +fn loop(x: u32) -> u32 { + let mut sum = 0; + for i in 0..x { + sum = sum + i; + } + sum +} + +fn loop_incl(x: u32) -> u32 { + let mut sum = 0; + for i in 0..=x { + sum = sum + i; + } + sum +} 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/sha256_var_padding_regression/Nargo.toml b/noir/noir-repo/test_programs/execution_success/sha256_var_padding_regression/Nargo.toml new file mode 100644 index 00000000000..a80677c585d --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/sha256_var_padding_regression/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "sha256_var_padding_regression" +type = "bin" +authors = [""] +compiler_version = ">=0.34.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_success/sha256_var_padding_regression/Prover.toml b/noir/noir-repo/test_programs/execution_success/sha256_var_padding_regression/Prover.toml new file mode 100644 index 00000000000..7b20e870128 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/sha256_var_padding_regression/Prover.toml @@ -0,0 +1,2 @@ +preimage = [29, 81, 165, 84, 243, 114, 101, 37, 242, 146, 127, 99, 69, 145, 39, 72, 213, 39, 253, 179, 218, 37, 217, 201, 172, 93, 198, 50, 249, 70, 15, 30, 162, 112, 187, 40, 140, 9, 236, 53, 32, 44, 38, 163, 113, 254, 192, 197, 44, 89, 71, 130, 169, 242, 17, 211, 214, 72, 19, 178, 186, 168, 147, 127, 99, 101, 252, 227, 8, 147, 150, 85, 97, 158, 17, 107, 218, 244, 82, 113, 247, 91, 208, 214, 60, 244, 87, 137, 173, 201, 130, 18, 66, 56, 198, 149, 207, 189, 175, 120, 123, 224, 177, 167, 251, 159, 143, 110, 68, 183, 189, 70, 126, 32, 35, 164, 44, 30, 44, 12, 65, 18, 62, 239, 242, 2, 248, 104, 2, 178, 64, 28, 126, 36, 137, 24, 14, 116, 91, 98, 90, 159, 218, 102, 45, 11, 110, 223, 245, 184, 52, 99, 59, 245, 136, 175, 3, 72, 164, 146, 145, 116, 22, 66, 24, 49, 193, 121, 3, 60, 37, 41, 97, 3, 190, 66, 195, 225, 63, 46, 3, 118, 4, 208, 15, 1, 40, 254, 235, 151, 123, 70, 180, 170, 44, 172, 90, 4, 254, 53, 239, 116, 246, 67, 56, 129, 61, 22, 169, 213, 65, 27, 216, 116, 162, 239, 214, 207, 126, 177, 20, 100, 25, 48, 143, 84, 215, 70, 197, 53, 65, 70, 86, 172, 61, 62, 9, 212, 167, 169, 133, 41, 126, 213, 196, 33, 192, 238, 0, 63, 246, 215, 58, 128, 110, 101, 92, 3, 170, 214, 130, 149, 52, 81, 125, 118, 233, 3, 118, 193, 104, 207, 120, 115, 77, 253, 191, 122, 0, 107, 164, 207, 113, 81, 169, 36, 201, 228, 74, 134, 131, 218, 178, 35, 30, 216, 101, 2, 103, 174, 87, 95, 50, 50, 215, 157, 5, 210, 188, 54, 211, 78, 45, 199, 96, 121, 241, 241, 176, 226, 194, 134, 130, 89, 217, 210, 186, 32, 140, 39, 91, 103, 212, 26, 87, 32, 72, 144, 228, 230, 117, 99, 188, 50, 15, 69, 79, 179, 50, 12, 106, 86, 218, 101, 73, 142, 243, 29, 250, 122, 228, 233, 29, 255, 22, 121, 114, 125, 103, 41, 250, 241, 179, 126, 158, 198, 116, 209, 65, 94, 98, 228, 175, 169, 96, 3, 9, 233, 133, 214, 55, 161, 164, 103, 80, 85, 24, 186, 64, 167, 92, 131, 53, 101, 202, 47, 25, 104, 118, 155, 14, 12, 12, 25, 116, 45, 221, 249, 28, 246, 212, 200, 157, 167, 169, 56, 197, 181, 4, 245, 146, 1, 140, 234, 191, 212, 228, 125, 87, 81, 86, 119, 30, 63, 129, 143, 32, 96] +result = [205, 74, 73, 134, 202, 93, 199, 152, 171, 244, 133, 193, 132, 40, 42, 9, 248, 11, 99, 200, 135, 58, 220, 227, 45, 253, 183, 241, 69, 69, 80, 219] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_success/sha256_var_padding_regression/src/main.nr b/noir/noir-repo/test_programs/execution_success/sha256_var_padding_regression/src/main.nr new file mode 100644 index 00000000000..13f87a0efc5 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/sha256_var_padding_regression/src/main.nr @@ -0,0 +1,29 @@ +// Test to check sha256_var produces same results irrespective of number of padding bytes after message.length +// Ref: https://github.com/noir-lang/noir/issues/6163, https://gist.github.com/jp4g/d5953faae9eadb2909357474f7901e58 +fn main(preimage: [u8; 448], result: [u8; 32]) { + // Construct arrays of different lengths + let mut preimage_511 = [0; 511]; + let mut preimage_512 = [0; 512]; // Next block + let mut preimage_575 = [0; 575]; + let mut preimage_576 = [0; 576]; // Next block + for i in 0..preimage.len() { + preimage_511[i] = preimage[i]; + preimage_512[i] = preimage[i]; + preimage_575[i] = preimage[i]; + preimage_576[i] = preimage[i]; + } + let fixed_length_hash = std::hash::sha256::digest(preimage); + let var_full_length_hash = std::hash::sha256::sha256_var(preimage, preimage.len() as u64); + let var_length_hash_511 = std::hash::sha256::sha256_var(preimage_511, preimage.len() as u64); + let var_length_hash_512 = std::hash::sha256::sha256_var(preimage_512, preimage.len() as u64); + let var_length_hash_575 = std::hash::sha256::sha256_var(preimage_575, preimage.len() as u64); + let var_length_hash_576 = std::hash::sha256::sha256_var(preimage_576, preimage.len() as u64); + + // All of the above should have produced the same hash (result) + assert(fixed_length_hash == result); + assert(var_full_length_hash == result); + assert(var_length_hash_511 == result); + assert(var_length_hash_512 == result); + assert(var_length_hash_575 == result); + assert(var_length_hash_576 == result); +} 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/to_be_bytes/src/main.nr b/noir/noir-repo/test_programs/execution_success/to_be_bytes/src/main.nr index 809d8ad4563..062f9f763d5 100644 --- a/noir/noir-repo/test_programs/execution_success/to_be_bytes/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/to_be_bytes/src/main.nr @@ -8,5 +8,6 @@ fn main(x: Field) -> pub [u8; 31] { if (bytes[30] != 60) | (bytes[29] != 33) | (bytes[28] != 31) { assert(false); } + assert(Field::from_be_bytes::<31>(bytes) == x); bytes } diff --git a/noir/noir-repo/test_programs/execution_success/to_le_bytes/src/main.nr b/noir/noir-repo/test_programs/execution_success/to_le_bytes/src/main.nr index 4e232b025aa..867551b6dbd 100644 --- a/noir/noir-repo/test_programs/execution_success/to_le_bytes/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/to_le_bytes/src/main.nr @@ -2,17 +2,12 @@ fn main(x: Field, cond: bool) -> pub [u8; 31] { // The result of this byte array will be little-endian let byte_array: [u8; 31] = x.to_le_bytes(); assert(byte_array.len() == 31); - - let mut bytes = [0; 31]; - for i in 0..31 { - bytes[i] = byte_array[i]; - } - + assert(Field::from_le_bytes::<31>(byte_array) == x); if cond { // We've set x = "2040124" so we shouldn't be able to represent this as a single byte. let bad_byte_array: [u8; 1] = x.to_le_bytes(); assert_eq(bad_byte_array.len(), 1); } - bytes + byte_array } 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/execution_success/verify_honk_proof/Prover.toml b/noir/noir-repo/test_programs/execution_success/verify_honk_proof/Prover.toml deleted file mode 100644 index 45a84c26eb8..00000000000 --- a/noir/noir-repo/test_programs/execution_success/verify_honk_proof/Prover.toml +++ /dev/null @@ -1,4 +0,0 @@ -key_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" -proof = ["0x0000000000000000000000000000000000000000000000000000000000000040", "0x0000000000000000000000000000000000000000000000000000000000000011", "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000042ab5d6d1986846cf", "0x00000000000000000000000000000000000000000000000b75c020998797da78", "0x0000000000000000000000000000000000000000000000005a107acb64952eca", "0x000000000000000000000000000000000000000000000000000031e97a575e9d", "0x00000000000000000000000000000000000000000000000b5666547acf8bd5a4", "0x00000000000000000000000000000000000000000000000c410db10a01750aeb", "0x00000000000000000000000000000000000000000000000d722669117f9758a4", "0x000000000000000000000000000000000000000000000000000178cbf4206471", "0x000000000000000000000000000000000000000000000000e91b8a11e7842c38", "0x000000000000000000000000000000000000000000000007fd51009034b3357f", "0x000000000000000000000000000000000000000000000009889939f81e9c7402", "0x0000000000000000000000000000000000000000000000000000f94656a2ca48", "0x000000000000000000000000000000000000000000000006fb128b46c1ddb67f", "0x0000000000000000000000000000000000000000000000093fe27776f50224bd", "0x000000000000000000000000000000000000000000000004a0c80c0da527a081", "0x0000000000000000000000000000000000000000000000000001b52c2020d746", "0x0000000000000000000000000000005a9bae947e1e91af9e4033d8d6aa6ed632", "0x000000000000000000000000000000000025e485e013446d4ac7981c88ba6ecc", "0x000000000000000000000000000000ff1e0496e30ab24a63b32b2d1120b76e62", "0x00000000000000000000000000000000001afe0a8a685d7cd85d1010e55d9d7c", "0x000000000000000000000000000000b0804efd6573805f991458295f510a2004", "0x00000000000000000000000000000000000c81a178016e2fe18605022d5a8b0e", "0x000000000000000000000000000000eba51e76eb1cfff60a53a0092a3c3dea47", "0x000000000000000000000000000000000022e7466247b533282f5936ac4e6c15", "0x00000000000000000000000000000071b1d76edf770edff98f00ff4deec264cd", "0x00000000000000000000000000000000001e48128e68794d8861fcbb2986a383", "0x000000000000000000000000000000d3a2af4915ae6d86b097adc377fafda2d4", "0x000000000000000000000000000000000006359de9ca452dab3a4f1f8d9c9d98", "0x0000000000000000000000000000006cf7dd96d7636fda5953191b1ad776d491", "0x00000000000000000000000000000000001633d881a08d136e834cb13a28fcc6", "0x00000000000000000000000000000001254956cff6908b069fca0e6cf1c47eb1", "0x000000000000000000000000000000000006f4d4dd3890e997e75e75886bf8f7", "0x0000000000000000000000000000006cf7dd96d7636fda5953191b1ad776d491", "0x00000000000000000000000000000000001633d881a08d136e834cb13a28fcc6", "0x00000000000000000000000000000001254956cff6908b069fca0e6cf1c47eb1", "0x000000000000000000000000000000000006f4d4dd3890e997e75e75886bf8f7", "0x000000000000000000000000000000f968b227a358a305607f3efc933823d288", "0x00000000000000000000000000000000000eaf8adb390375a76d95e918b65e08", "0x000000000000000000000000000000bb34b4b447aae56f5e24f81c3acd6d547f", "0x00000000000000000000000000000000002175d012746260ebcfe339a91a81e1", "0x00000000000000000000000000000052eebbd1f6f7554e837f60c44000ed14b6", "0x00000000000000000000000000000000001c1c045a3ec94b8801f2272cc0b3f4", "0x0000000000000000000000000000004d2ef74134578f6b431a9df071ffca4292", "0x0000000000000000000000000000000000291326ade7aa6f0dfc8900eab5580b", "0x0000000000000000000000000000002433eec6418a6dba820c9527e2581fc8bc", "0x00000000000000000000000000000000000e88b7daad19af2ac2f9bdf9e50ee2", "0x000000000000000000000000000000dcfce2c427155cc3e4d035735d3dd5ece8", "0x00000000000000000000000000000000002d7d473cac1a15d0fee8b22c1a7b3e", "0x1a4249b90be4602c8ff40c7c276160ee41b2a0f8a238ce7706e9face2db03d48", "0x162204b9d54d3ffd285c393a5a1ff76ee681474fd780a21a3cf7fac5c24fc2b9", "0x30279eb953d8ee79b2155c69c04e6443c5de6bf7e02886256dd7b3cd3c9502a4", "0x0d32c1bd21baf43e72d5d82d461ef54833807ff81a877adc822f27a6db88d754", "0x0fe15e055c0803d5ebe6dd77689b70cfd82138f008810ce24535c992daaff27d", "0x1fba82c012671212ce2fc13fd09bf8fba4f7d5875ab8d37495d1ccfcff287331", "0x090738a5842fa4d2699b3726aa0dd97cb59569b4be2c6825335ec4969f859dc2", "0x0c6cb72dabbc28abcf4a50c203534e066c29f48c24ca64d672092f263df3f9d7", "0x0f27fbea0d9145f815c288b50fe7e8c10b8185d325b5264624fd57102855d05d", "0x2a815cd3fd1c43c72ee0130465250ff771d1e7be2347e4ada331b83265a03450", "0x148b4ecf2ad7ed17409417086867ee27bc1b0906dbc9cbb3714c285071e2db70", "0x08e700a42b1d6d36ee65f8ebedf47d3a44463ff9fa579dce13b7492e20142c3a", "0x2e23c69521d572ff2152c50f8c9a9191535f4cf37f95f1e0428692e78842b642", "0x14519e0354365923fb41400c19866135b45e975d56a0980260bc99f0390b1d5f", "0x04caded1f05738f736cb5bcf08d785e395e58eb7738523a20638aa16bc51593e", "0x28787eaccd38383215ea21ec02895c32d979f68ca155f534a2e2d377bff6698b", "0x20a1b81fa96c58cf11c5762c5ceb731efdcb081fca2d34d5c463d2cf40e6da18", "0x11789a06fe3bf53833741d180f068d29534d5bb58a5c64b8119542e62b189fb4", "0x23d00fcd032ace719ffcbc513bfa177a55b04377d76455c2788d999d05d011e2", "0x01f0e81b57b4a73cc118e51ede18f8724debf25c2d109db6ef45280f99f1a3fa", "0x156d1c9b61749810de728f259c2c1c1fd4dbff97101426e26087ca461c93307c", "0x1c5d619ac3a478cfd06d5eebfd879960bb321236be173813f5e78d1366d32c69", "0x250cfae4e1dfc551406f1f3d10b649a637dcb7bcb0f6f697994cf96afd35d0c1", "0x242b999f58cf5f53c874d1092bd38725aa9ea076f5bc8f176efa9ea23393874b", "0x2e15748255c4a5e0e9fe38047341b692a967257de27a85a3a38681bc9f1602ea", "0x01ef83886ea7017253699cb6371988eb8e21b4f7023d7479ece4907fe6d4a6fd", "0x08db2dbc271e375b9312f695c59c48f313235b3432cad50921c8d9ad6dd7ad7a", "0x199309f2c2cd45c15a4abb0e6554a1615ff5a6e9488a8d900bbf835fc8f664ef", "0x074be7a3d88e31ab1b59c9208c012bcfb1f85f351c709e68134996891db52b57", "0x301b1011354d2ebf46134fc4d6d734bb6ed8542d719f38f5e09a376a580cad7f", "0x12968f3eccaa27e44f14d5aaac6ecb70c00d040e07536292e685d7cab03fc563", "0x2110a023c8c22fd2ed70270a2d0a265b92a32ce2217ffe1be9a5d7d5c25f512f", "0x1e8cf4c60c53900f0430d5b44de5804fe8b38299bc803beeb4216e1a289cf624", "0x12301cb908ccb28a2616e29b831ec7105b5d3ebf45ff5fe91d50a9dd53a50b52", "0x0f1029ed107d84ff2d6d4a416cbd01da3f3d7bf5b2209ce93ba424f4b85616fc", "0x1b431d016611b8abd684afd9e92331c3325967b1116bfa91d4f44e2f8e2c9fc2", "0x281e335a0fd117064c8ace3f01e02b134a19e9b9220571ebfaaaa0e3a12d34db", "0x22559c106f77e2ae95677d5e38e288343e3b7168371aec7d3aaab9ef8150af70", "0x13f113b1d9b590149cf08c3f6e90589cda5c7b98528866b891256cb9d5d814e7", "0x10252ef388e4c80246962e98b9e976fab2cd25e1e6f1e3fd2a7d4786c5218a97", "0x16b890723dfdebd9912a9207255f95cb800222165b6fae97ec46e461f23e83f3", "0x25caf77c7d2e8e069341ec90f3c8f6d64319cfd2d77cab0625cf0377285ba11c", "0x016c84288b0bc3c50eebbe250cdd5a4ee50b2c65a24ac64d0c930cbdecb95141", "0x20a537c045b069d47dc6315f45b391f60681222381e5059ec7c8b17bf677d911", "0x2594c0edfcd4064d215a3d797bc8e3b2f401c61f3961ae96ccbec8f8fd29e81f", "0x1c831d7047483ca00ed59bdb84c47ffb8bbebbae92aa164c7b35fcffbb8a35d3", "0x2ea7f60de52b8cd6b0bd06f861fc1f2c5ed1d1fbfa53caccdb836400a03df434", "0x275c6c8bd115f7d2ce196439e2330fad305c6745bab0bf1ce3f2fa32dadc3c43", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x2b3f4e210619347288731e7f0eea1ae60dd8696fe87319044864d099a03a9536", "0x0fecd3d2aebedeb8be2b0b7e3a74de9ec8bd2cb72851541463729e0715aef48b", "0x10bee385ad0c2cd3ff88ef4d630840749e4367f9add4a300fc4f545a7778e92c", "0x1fe792730eeafbd22c4eb80e86e2b40108b1e55b2a08db786289eea5745b9e3b", "0x04d411679da432816b213cd5580dda1fd6c2f258684c036be19b5b26baca583c", "0x159f17b655d2b8a803e0cfb564918628be52d3baa950ca1b127854491624f8f4", "0x225340463867d9252a1269013536e2e1dd219aa18eadef13c324b63d44679334", "0x1885e231554e02abb40ed54b72ebf09b883d0298a6bc06fc645a30365f370ef2", "0x233344517f25170972b8ba35d01f2df2e073d322993abce7df26796126e93498", "0x08990e8faa13b18b4433ec417c5cc596885e11ffd372d5f612c08cc79a5d5c80", "0x1e960a0c892b755c28e21dcbed816c1b182d7da43bae07f8ee622bd4485f79e7", "0x27b58e2ee290a179d349ace82752528b2ff946d60c092b99ef42f53c25d0c99f", "0x2a5cf8a3193107d982edd253002c7a52ab876b445dde8307ab78fbdca022d93c", "0x2b1ab4d5277f8c82750c1c7bd043889b71465ec64a9afc1bfa37d06b1ebd0725", "0x2a0dbf5c4373a58a83d5f2a031ea0615bf12800e33b70c3191a7cbb15966ced8", "0x1f272bb4a19d14a0abdfebc9fc83125e10623b9aef730f8d25f2bf5bead38ea9", "0x2c2339cf0ae7aff56091a568c1e2c3f01f52d8ed13400737fd31eaabcaffb9bd", "0x21f5fefe6b5fa0b5da71089befb74a1a39e52b4f830cb330c3c284e154614dfd", "0x1e6f6ba4b2444586b380dc4e2b3fad111ff1f4754420a846f53ea0789ebfb0ad", "0x1193d170b0b2dd0c4a04331a4b4aa3f12920f182ec3ab547837e30f1116ca584", "0x00000000000000000000000000000025704a15c9e2ce8a69558e7bbcdcbc7784", "0x2e5d36112770fb6c985681cafe40a8c99ad13f702309e7969801dd0ed080e727", "0x0eefc2585f591bb457183134e19ad643966272234d645514bf7868d6dd8ae2cb", "0x300803e4e2339ad39b9c31f228949bbeaf9c74b7101e7be1930b088126247eaa", "0x2bb562a50ed944b438b83b01f200101a34faef7f296a75c84c731755ebddbc1a", "0x1288e0b9c742af39cbcac21357c1b29511b0bbdd3d0e3cf5e14b2eef68a28ab3", "0x20f089131cc96d86ff1cfb67fa3f51670f4bad30158751b2587354bbec76cdf9", "0x1a26c6d3e633f9bf8434cf755b5f1217dad0d455071a97a7bcf85b824f5cf07a", "0x0d7e9b8a51fccf910ec25bdbd13e70b34bd6ea6f4d629fa744f9cdf5f2beb1cf", "0x0b40f28ce428e64df9cf5a024133fc420f39decf5f6af020cc3211ab298d4631", "0x0ca4f189dde7a55fe829f46681232904f6670249a22e0deb47222bd309523a8a", "0x2c544f2e31143783977645edb2a6bdb39b875053963bfa1a5b3ae9de204a7ebe", "0x00aae4454eb48fb18ff60db6b9d015abea2e770a2f7d86d121522b834c791ba5", "0x07d74e351fd4cccf4e18475d25211efa8a359066fc693a5c8505ddb507e4b74b", "0x07d74e351fd4cccf4e18475d25211efa8a359066fc693a5c8505ddb507e4b74b", "0x2d9e5bff47207d82533e2445959941181cc890c5779bc7f24d6e8a7b9e425b5c", "0x0aea3c0c317c441a5775a9849108d7a6889b39128235f717b09b184aa08e4eb7", "0x1ca5bc6fb37c55a562f4991901c39902f42d14db71284116df74cb4e7d55e493", "0x220fed26d64cd69f40e6d145a423e4a3c8cd0dce747e7d51647441270ad4d774", "0x15be574c9358889257aa2a30ff7b5fcc31a57da7032296e2c1201c49a44bbdb6", "0x2de539925525bedd3b7f43a9c6bf0f713151a17f79ac7ff4a9cd27b15ffe892a", "0x083086693dbf465211741e2cbff70ff38eb08383faf22d397eb2742c8ad7396a", "0x1fdfa258a890598816e807c50058d7a1462edd5ff196a2eae0f862e454b49aa1", "0x10c434c6daaa8226fa8e3e302123dfdc4455cf68063df518949df5a65a945213", "0x0000000000000000000000000000006472a7874de2c454a4591ed7784df1c104", "0x000000000000000000000000000000000008c46ac53d2c4ad0c26a5d6c790082", "0x0000000000000000000000000000005e422f9cfb8725800de60dfe0a8d4104c0", "0x000000000000000000000000000000000000f10fd4e4de81a0c00073ec91c274", "0x000000000000000000000000000000b20813090eca76bc6aa4a699b1ec8d5d6d", "0x0000000000000000000000000000000000292cc9f8a744eb00e0903c29ce87a7", "0x000000000000000000000000000000350a368b415fbb99fa90a26a42b1a3affd", "0x0000000000000000000000000000000000280eb9275cb05a3435f464d1dc369d", "0x000000000000000000000000000000280df6505e20c7725fe6d29439f96ee05d", "0x000000000000000000000000000000000017ef5033a08535451e2747827aa94b", "0x0000000000000000000000000000002f9ba89ae91b4e4a4ff8ccbd0526faad2f", "0x00000000000000000000000000000000001c2013561dafcc02cb03220bdf23c4", "0x000000000000000000000000000000aac102c400f9e5da0321ded4510994434b", "0x00000000000000000000000000000000001ec8ab9cc834b492fde124962f04a1", "0x0000000000000000000000000000000673dbd698da8b8cce450d2a083aba9987", "0x00000000000000000000000000000000000a49e55bb040249cb41c63cea901cd", "0x000000000000000000000000000000133d92af8d76ee0c74a12081ee7b2ef8c4", "0x0000000000000000000000000000000000240f552d1c6cbb007650e4b142e0a5", "0x000000000000000000000000000000e29c6e7d982ec08d51c79d6261c28d742d", "0x000000000000000000000000000000000021baeec04d9be419c923626034e7b3", "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000002", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000002", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000002", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000002", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000002", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000002", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000002", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000002", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000002", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000002", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000002", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000002", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000002", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000002", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000002", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000002", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000002", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000002", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000002", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000002", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000002", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000002", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x1e940a528b42d8230e7d4dff76262a80986c0d00b2c02a9bc0559e36212d1547", "0x1ceccf21ac39f70d76ad6f7fe0dcb33b6af04555a0b1959e4714d65925e4e253", "0x096139d757046cdbdb7ee89a95f112f70882a43a46c2f739d9be115dda013420", "0x2f9c8ac67c7825b08eff0e7f7656a671f4c64e5601f2efab35b1b795801eec04", "0x2077e648e1704851cdffd7e6e56311634a7b741bab77ca34d9dff12a6a2bfe99", "0x115d48c4a97aeb3c447a060f9e0d675b0dc7f4a05a3f5776e2f279f3a165d7dc", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x000000000000000000000000000000fd38c45c3ec5b841482a80e3a56ce82555", "0x00000000000000000000000000000000000ad70b03f092f60af3e0ce1bb29d2c", "0x0000000000000000000000000000007a184d5342c90c634c0b1a050f0b97c9fb", "0x0000000000000000000000000000000000271f42abcb3bc1f0332e4b3ca85e1d", "0x0000000000000000000000000000008256322bbe2c1b8cd9d84e5ff6123477f2", "0x000000000000000000000000000000000025cab962761681dd9547f4c78814df", "0x0000000000000000000000000000008c4234510e5825c02b9ac427bcbf8e279a", "0x000000000000000000000000000000000013a14e0d7fc073c44643af38cc5396"] -public_inputs = ["0x0000000000000000000000000000000000000000000000000000000000000003"] -verification_key = ["0x0000000000000000000000000000000000000000000000000000000000000040", "0x0000000000000000000000000000000000000000000000000000000000000011", "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000002", "0x0000000000000000000000000000000000000000000000000000000000000003", "0x0000000000000000000000000000000000000000000000000000000000000004", "0x0000000000000000000000000000000000000000000000000000000000000005", "0x0000000000000000000000000000000000000000000000000000000000000006", "0x0000000000000000000000000000000000000000000000000000000000000007", "0x0000000000000000000000000000000000000000000000000000000000000008", "0x0000000000000000000000000000000000000000000000000000000000000009", "0x000000000000000000000000000000000000000000000000000000000000000a", "0x000000000000000000000000000000000000000000000000000000000000000b", "0x000000000000000000000000000000000000000000000000000000000000000c", "0x000000000000000000000000000000000000000000000000000000000000000d", "0x000000000000000000000000000000000000000000000000000000000000000e", "0x000000000000000000000000000000000000000000000000000000000000000f", "0x0000000000000000000000000000000000000000000000000000000000000010", "0x00000000000000000000000000000060e430ad1c23bfcf3514323aae3f206e84", "0x00000000000000000000000000000000001b5c3ff4c2458d8f481b1c068f27ae", "0x000000000000000000000000000000bb510ab2112def34980e4fc6998ad9dd16", "0x00000000000000000000000000000000000576e7c105b43e061e13cb877fefe1", "0x000000000000000000000000000000ced074785d11857b065d8199e6669a601c", "0x00000000000000000000000000000000000053b48a4098c1c0ae268f273952f7", "0x000000000000000000000000000000d1d4b26e941db8168cee8f6de548ae0fd8", "0x00000000000000000000000000000000001a9adf5a6dadc3d948bb61dfd63f4c", "0x0000000000000000000000000000009ce1faac6f8de6ebb18f1db17372c82ad5", "0x00000000000000000000000000000000002002681bb417184b2df070a16a3858", "0x000000000000000000000000000000161baa651a8092e0e84725594de5aba511", "0x00000000000000000000000000000000000be0064399c2a1efff9eb0cdcb2223", "0x0000000000000000000000000000008673be6fd1bdbe980a29d8c1ded54381e7", "0x000000000000000000000000000000000008a5158a7d9648cf1d234524c9fa0c", "0x0000000000000000000000000000002b4fce6e4b1c72062b296d49bca2aa4130", "0x00000000000000000000000000000000002e45a9eff4b6769e55fb710cded44f", "0x00000000000000000000000000000072b85bf733758b76bcf97333efb85a23e3", "0x000000000000000000000000000000000017da0ea508994fc82862715e4b5592", "0x00000000000000000000000000000094fa74695cf058dba8ff35aec95456c6c3", "0x0000000000000000000000000000000000211acddb851061c24b8f159e832bd1", "0x000000000000000000000000000000303b5e5c531384b9a792e11702ad3bcab0", "0x00000000000000000000000000000000000d336dff51a60b8833d5d7f6d4314c", "0x0000000000000000000000000000009f825dde88092070747180d581c342444a", "0x0000000000000000000000000000000000237fbd6511a03cca8cac01b555fe01", "0x0000000000000000000000000000007c313205159495df6d8de292079a4844ff", "0x000000000000000000000000000000000018facdfc468530dd45e8f7a1d38ce9", "0x0000000000000000000000000000000d1ce33446fc3dc4ab40ca38d92dac74e1", "0x00000000000000000000000000000000000852d8e3e0e8f4435af3e94222688b", "0x0000000000000000000000000000006c04ee19ec1dfec87ed47d6d04aa158de2", "0x000000000000000000000000000000000013240f97a584b45184c8ec31319b5f", "0x000000000000000000000000000000cefb5d240b07ceb4be26ea429b6dc9d9e0", "0x00000000000000000000000000000000002dad22022121d689f57fb38ca21349", "0x000000000000000000000000000000c9f189f2a91aeb664ce376d8b157ba98f8", "0x00000000000000000000000000000000002531a51ad54f124d58094b219818d2", "0x000000000000000000000000000000ef1e6db71809307f677677e62b4163f556", "0x0000000000000000000000000000000000272da4396fb2a7ee0638b9140e523d", "0x0000000000000000000000000000002e54c0244a7732c87bc4712a76dd8c83fb", "0x000000000000000000000000000000000007db77b3e04b7eba9643da57cbbe4d", "0x000000000000000000000000000000e0dfe1ddd7f74ae0d636c910c3e85830d8", "0x00000000000000000000000000000000000466fa9b57ec4664abd1505b490862", "0x0000000000000000000000000000009ee55ae8a32fe5384c79907067cc27192e", "0x00000000000000000000000000000000000799d0e465cec07ecb5238c854e830", "0x0000000000000000000000000000001d5910ad361e76e1c241247a823733c39f", "0x00000000000000000000000000000000002b03f2ccf7507564da2e6678bef8fe", "0x000000000000000000000000000000ee40d90bea71fba7a412dd61fcf34e8ceb", "0x0000000000000000000000000000000000140b0936c323fd2471155617b6af56", "0x0000000000000000000000000000002b90071823185c5ff8e440fd3d73b6fefc", "0x00000000000000000000000000000000002b6c10790a5f6631c87d652e059df4", "0x00000000000000000000000000000029a17181c7934fc3fdbd352eac5cb521b9", "0x00000000000000000000000000000000001f497cbf5284ff29a2d336e5991999", "0x000000000000000000000000000000072bd9c0c6beda1fdee6d4ff0432ba9e1b", "0x000000000000000000000000000000000013ea38a0bd2aa751a490a724fac818", "0x000000000000000000000000000000c599f63dcd3edd49f08ae5c3141c1e3493", "0x00000000000000000000000000000000002bdb36be0bea09950dd32a8ccf6fbc", "0x00000000000000000000000000000047f27f29724e7f19eba0340256a0bd4b7d", "0x00000000000000000000000000000000001c1c5ccf87a962129ca785f8f35120", "0x000000000000000000000000000000c5c71efdae00679bbe4a95096e012b1817", "0x000000000000000000000000000000000017a365de041e317817d0135f2b48e0", "0x0000000000000000000000000000008ae711ac402f7848d719c93a89ba8d39f1", "0x00000000000000000000000000000000002b6fb40ed8a1935226f4f9786a0499", "0x0000000000000000000000000000002f03a71501d83de1da5715a4e9462d6198", "0x00000000000000000000000000000000001644064443b8546f48eae693af47b8", "0x00000000000000000000000000000083763ab1b6e8fe269b2fe4c7b9c448c08d", "0x000000000000000000000000000000000021d7cc18c59676a8eeb47c0111c251", "0x000000000000000000000000000000b5f937153073e03ea7d51a996e0ebc2e6b", "0x000000000000000000000000000000000011ddd0e26457373eb06e0493177672", "0x000000000000000000000000000000c5f6eb9f6fc8fa99811a4a88c74a6d018b", "0x000000000000000000000000000000000025bcd07a0732c123567834f5109558", "0x000000000000000000000000000000aeb08a0b1a4442189448b4e97490568146", "0x000000000000000000000000000000000002a1744e4771705536a88f07e0f90f", "0x000000000000000000000000000000b938568293bd0724b0ea76c2ec34c4a829", "0x0000000000000000000000000000000000053296e8f3b9ad3af877dfa9c7c2a7", "0x000000000000000000000000000000f0ca1db6323996eba26bdc86dafef9d10b", "0x00000000000000000000000000000000001441a46c58af03d5645d52721d956a", "0x0000000000000000000000000000008bbf8f884013c66c28ba09c2fbd573b656", "0x0000000000000000000000000000000000206c391ca06fac27d1908e94570243", "0x0000000000000000000000000000002d4f5aaed88ba4f79612d53b804ca8f194", "0x00000000000000000000000000000000001674011c96392df08970fa6b7b4cb8", "0x0000000000000000000000000000009f88297c1729d76c4d9306853598c91325", "0x0000000000000000000000000000000000256f51adfcacc3c1e340be4d32d3e9", "0x0000000000000000000000000000000ab9955eec0d74eb799afed2a802b24d75", "0x00000000000000000000000000000000001fcbe43ea105b30d36ed0b21b03411", "0x000000000000000000000000000000d66b1d5433f1aa5305cd1edce7c22de466", "0x00000000000000000000000000000000002331546a256b8a3b751956806680d4", "0x000000000000000000000000000000e97954ad6cd6f45fb15c91434121db4304", "0x00000000000000000000000000000000002e20a97e09d50f227ced47e7a98250", "0x0000000000000000000000000000001ebbc27eb9ebededefba79522eb58ae89b", "0x0000000000000000000000000000000000090efa4974e566e81d1177b85a30be", "0x0000000000000000000000000000005eafa070b9c9632404052642e3bc14f9fd", "0x00000000000000000000000000000000001489068864102daca6a6b8bc4d448b", "0x0000000000000000000000000000009ebc91aaaac036a6477cadbe54e8556dfd", "0x00000000000000000000000000000000000ef6d835e2ed3343b95c82c8c54037", "0x00000000000000000000000000000033b28b529dff46e93af4e7422530478e4a", "0x000000000000000000000000000000000020a86c2f8591bf190bcddcc03c42fb", "0x000000000000000000000000000000a9679d0acc088f7dc27bf6d866bcd2dda2", "0x00000000000000000000000000000000002fb9d0d2d4099402bed74f738f64cc", "0x00000000000000000000000000000023b09f876a29a061582848a8b9a5870c12", "0x00000000000000000000000000000000001d5bb906f03f0d49e9c4791bc43af9", "0x00000000000000000000000000000017aac9854ea240d8ec97bf760c4d4ba870", "0x00000000000000000000000000000000000b227a556c414ada0dc75bb303e30e", "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000002", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000009b624fa65d1a24b7f14a8f25f3789622af", "0x000000000000000000000000000000000013d47bff8c630e847b70e2732fd3f0", "0x00000000000000000000000000000061d21663e93132f32921075f4c936a84df", "0x00000000000000000000000000000000001a74ca4e118fb480b9b999902989a3"] diff --git a/noir/noir-repo/test_programs/execution_success/verify_honk_proof/src/main.nr b/noir/noir-repo/test_programs/execution_success/verify_honk_proof/src/main.nr deleted file mode 100644 index 40559b64c7c..00000000000 --- a/noir/noir-repo/test_programs/execution_success/verify_honk_proof/src/main.nr +++ /dev/null @@ -1,23 +0,0 @@ - -// This circuit aggregates a single Honk proof from `assert_statement_recursive`. -global SIZE_OF_PROOF_IF_LOGN_IS_28 : u32 = 463; -global HONK_IDENTIFIER : u32 = 1; -fn main( - verification_key: [Field; 128], - // This is the proof without public inputs attached. - // - // This means: the size of this does not change with the number of public inputs. - proof: [Field; SIZE_OF_PROOF_IF_LOGN_IS_28], - public_inputs: pub [Field; 1], - // This is currently not public. It is fine given that the vk is a part of the circuit definition. - // I believe we want to eventually make it public too though. - key_hash: Field -) { - std::verify_proof_with_type( - verification_key, - proof, - public_inputs, - key_hash, - HONK_IDENTIFIER - ); -} 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 cb9879b1c9e..0ad5840ba32 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 @@ -20,7 +20,7 @@ fn good() -> BoundedVec { fn bad() { // Error: Type annotation needed - // The compiller can't infer `MaxLen` from this code. + // The compiler can't infer `MaxLen` from this code. let mut v3 = BoundedVec::new(); v3.push(5); } 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/Cargo.toml b/noir/noir-repo/tooling/lsp/Cargo.toml index 209f2afe4a4..c15895d801f 100644 --- a/noir/noir-repo/tooling/lsp/Cargo.toml +++ b/noir/noir-repo/tooling/lsp/Cargo.toml @@ -14,7 +14,6 @@ workspace = true [dependencies] acvm.workspace = true -chumsky.workspace = true codespan-lsp.workspace = true lsp-types.workspace = true nargo.workspace = true diff --git a/noir/noir-repo/tooling/lsp/src/attribute_reference_finder.rs b/noir/noir-repo/tooling/lsp/src/attribute_reference_finder.rs index f08c8073a79..39e1385a6e8 100644 --- a/noir/noir-repo/tooling/lsp/src/attribute_reference_finder.rs +++ b/noir/noir-repo/tooling/lsp/src/attribute_reference_finder.rs @@ -6,7 +6,6 @@ /// will give not only the attribute function but also any type generated by it. use std::collections::BTreeMap; -use chumsky::Parser; use fm::FileId; use noirc_errors::Span; use noirc_frontend::{ @@ -16,9 +15,8 @@ use noirc_frontend::{ def_map::{CrateDefMap, LocalModuleId, ModuleId}, resolution::path_resolver::{PathResolver, StandardPathResolver}, }, - lexer::Lexer, node_interner::ReferenceId, - parser::{path_no_turbofish, ParsedSubModule}, + parser::{ParsedSubModule, Parser}, token::CustomAttribute, usage_tracker::UsageTracker, ParsedModule, @@ -96,14 +94,12 @@ impl<'a> Visitor for AttributeReferenceFinder<'a> { Some((left, _right)) => left.to_string(), None => attribute.contents.to_string(), }; - let (tokens, _) = Lexer::lex(&name); - - let parser = path_no_turbofish(); - let Ok(path) = parser.parse(tokens) else { + let mut parser = Parser::for_str(&name); + let Some(path) = parser.parse_path_no_turbofish() else { return; }; - let resolver = StandardPathResolver::new(self.module_id); + let resolver = StandardPathResolver::new(self.module_id, None); let mut usage_tracker = UsageTracker::default(); let Ok(result) = resolver.resolve(self.def_maps, path, &mut usage_tracker, &mut None) else { 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/modules.rs b/noir/noir-repo/tooling/lsp/src/modules.rs index f1eff3b5a7d..9f9a826d6ca 100644 --- a/noir/noir-repo/tooling/lsp/src/modules.rs +++ b/noir/noir-repo/tooling/lsp/src/modules.rs @@ -3,9 +3,8 @@ use std::collections::BTreeMap; use noirc_frontend::{ ast::ItemVisibility, graph::{CrateId, Dependency}, - hir::def_map::{CrateDefMap, ModuleId}, - macros_api::{ModuleDefId, NodeInterner}, - node_interner::ReferenceId, + hir::def_map::{CrateDefMap, ModuleDefId, ModuleId}, + node_interner::{NodeInterner, ReferenceId}, }; use crate::visibility::is_visible; 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.rs b/noir/noir-repo/tooling/lsp/src/requests/code_action.rs index 64eccab8947..9299dc76368 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/code_action.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/code_action.rs @@ -15,7 +15,7 @@ use noirc_frontend::{ ast::{ConstructorExpression, ItemVisibility, NoirTraitImpl, Path, UseTree, Visitor}, graph::CrateId, hir::def_map::{CrateDefMap, LocalModuleId, ModuleId}, - macros_api::NodeInterner, + node_interner::NodeInterner, }; use noirc_frontend::{ parser::{Item, ItemKind, ParsedSubModule}, 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/code_action/import_or_qualify.rs b/noir/noir-repo/tooling/lsp/src/requests/code_action/import_or_qualify.rs index 0d97ccde2ed..bf1fe906be3 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/code_action/import_or_qualify.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/code_action/import_or_qualify.rs @@ -2,7 +2,7 @@ use lsp_types::{Position, Range, TextEdit}; use noirc_errors::Location; use noirc_frontend::{ ast::{Ident, Path}, - macros_api::ModuleDefId, + hir::def_map::ModuleDefId, }; use crate::{ @@ -186,7 +186,7 @@ fn foo(x: SomeTypeInBar) {}"#; let src = r#" mod foo { mod bar { - mod some_module_in_bar {} + pub mod some_module_in_bar {} } } @@ -198,7 +198,7 @@ fn foo(x: SomeTypeInBar) {}"#; let expected = r#" mod foo { mod bar { - mod some_module_in_bar {} + pub mod some_module_in_bar {} } } @@ -216,7 +216,7 @@ fn foo(x: SomeTypeInBar) {}"#; let src = r#"mod foo { mod bar { - mod some_module_in_bar {} + pub(crate) mod some_module_in_bar {} } } @@ -228,7 +228,7 @@ fn main() { mod foo { mod bar { - mod some_module_in_bar {} + pub(crate) mod some_module_in_bar {} } } diff --git a/noir/noir-repo/tooling/lsp/src/requests/code_lens_request.rs b/noir/noir-repo/tooling/lsp/src/requests/code_lens_request.rs index 4565569e67b..42f2af3a7bf 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/code_lens_request.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/code_lens_request.rs @@ -89,8 +89,8 @@ fn on_code_lens_request_inner( } pub(crate) fn collect_lenses_for_package( - context: &noirc_frontend::macros_api::HirContext, - crate_id: noirc_frontend::macros_api::CrateId, + context: &noirc_frontend::hir::Context, + crate_id: noirc_frontend::graph::CrateId, workspace: &Workspace, package: &Package, file_path: Option<&std::path::PathBuf>, diff --git a/noir/noir-repo/tooling/lsp/src/requests/completion.rs b/noir/noir-repo/tooling/lsp/src/requests/completion.rs index 588f5b18f1b..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, ModuleId}, + hir::{ + def_map::{CrateDefMap, LocalModuleId, ModuleDefId, ModuleId}, + resolution::visibility::{method_call_is_visible, struct_member_is_visible}, + }, hir_def::traits::Trait, - macros_api::{ModuleDefId, 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, @@ -581,8 +583,8 @@ impl<'a> NodeFinder<'a> { Type::Tuple(types) => { self.complete_tuple_fields(types, self_prefix); } - Type::TypeVariable(var, _) | Type::NamedGeneric(var, _, _) => { - if let TypeBinding::Bound(typ) = &*var.borrow() { + Type::TypeVariable(var) | Type::NamedGeneric(var, _) => { + if let TypeBinding::Bound(ref typ) = &*var.borrow() { return self.complete_type_fields_and_methods( typ, 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, + )); } } @@ -1662,8 +1701,8 @@ fn get_field_type(typ: &Type, name: &str) -> Option { } } Type::Alias(alias_type, generics) => Some(alias_type.borrow().get_type(generics)), - Type::TypeVariable(var, _) | Type::NamedGeneric(var, _, _) => { - if let TypeBinding::Bound(typ) = &*var.borrow() { + Type::TypeVariable(var) | Type::NamedGeneric(var, _) => { + if let TypeBinding::Bound(ref typ) = &*var.borrow() { get_field_type(typ, name) } else { None @@ -1680,7 +1719,7 @@ fn get_array_element_type(typ: Type) -> Option { let typ = alias_type.borrow().get_type(&generics); get_array_element_type(typ) } - Type::TypeVariable(var, _) | Type::NamedGeneric(var, _, _) => { + Type::TypeVariable(var) | Type::NamedGeneric(var, _) => { if let TypeBinding::Bound(typ) = &*var.borrow() { get_array_element_type(typ.clone()) } else { @@ -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/auto_import.rs b/noir/noir-repo/tooling/lsp/src/requests/completion/auto_import.rs index 20b126a248d..e2dd582f2f3 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/completion/auto_import.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/completion/auto_import.rs @@ -1,5 +1,5 @@ use lsp_types::{Position, Range, TextEdit}; -use noirc_frontend::macros_api::ModuleDefId; +use noirc_frontend::hir::def_map::ModuleDefId; use crate::modules::{relative_module_full_path, relative_module_id_path}; diff --git a/noir/noir-repo/tooling/lsp/src/requests/completion/completion_items.rs b/noir/noir-repo/tooling/lsp/src/requests/completion/completion_items.rs index 809988c34a5..f281f5e3abf 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/completion/completion_items.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/completion/completion_items.rs @@ -4,10 +4,9 @@ use lsp_types::{ }; use noirc_frontend::{ ast::AttributeTarget, - hir::def_map::ModuleId, + hir::def_map::{ModuleDefId, ModuleId}, hir_def::{function::FuncMeta, stmt::HirPattern}, - macros_api::{ModuleDefId, StructId}, - node_interner::{FuncId, GlobalId, ReferenceId, TraitId, TypeAliasId}, + node_interner::{FuncId, GlobalId, ReferenceId, StructId, TraitId, TypeAliasId}, QuotedType, Type, }; 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 147fb5be4f3..668255eb34d 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs @@ -595,7 +595,7 @@ mod completion_tests { vec![simple_completion_item( "lambda_var", CompletionItemKind::VARIABLE, - Some("_".to_string()), + Some("i32".to_string()), )], ) .await; @@ -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#" @@ -1575,7 +1591,7 @@ mod completion_tests { async fn test_auto_import_suggests_modules_too() { let src = r#" mod foo { - mod barbaz { + pub mod barbaz { fn hello_world() {} } } @@ -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#" @@ -2271,4 +2318,56 @@ mod completion_tests { ) .await; } + + #[test] + async fn test_does_not_auto_import_private_global() { + let src = r#"mod moo { + global foobar = 1; + } + + fn main() { + fooba>|< + }"#; + + assert_completion(src, Vec::new()).await; + } + + #[test] + async fn test_does_not_auto_import_private_type_alias() { + let src = r#"mod moo { + type foobar = i32; + } + + fn main() { + fooba>|< + }"#; + + assert_completion(src, Vec::new()).await; + } + + #[test] + async fn test_does_not_auto_import_private_trait() { + let src = r#"mod moo { + trait Foobar {} + } + + fn main() { + Fooba>|< + }"#; + + assert_completion(src, Vec::new()).await; + } + + #[test] + async fn test_does_not_auto_import_private_module() { + let src = r#"mod moo { + mod foobar {} + } + + fn main() { + fooba>|< + }"#; + + assert_completion(src, Vec::new()).await; + } } diff --git a/noir/noir-repo/tooling/lsp/src/requests/document_symbol.rs b/noir/noir-repo/tooling/lsp/src/requests/document_symbol.rs index 6c41f4dc2e5..0fc2dc4622e 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/document_symbol.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/document_symbol.rs @@ -66,6 +66,10 @@ impl<'a> DocumentSymbolCollector<'a> { } fn collect_in_type(&mut self, name: &Ident, typ: Option<&UnresolvedType>) { + if name.0.contents.is_empty() { + return; + } + let Some(name_location) = self.to_lsp_location(name.span()) else { return; }; @@ -99,6 +103,10 @@ impl<'a> DocumentSymbolCollector<'a> { typ: &UnresolvedType, default_value: Option<&Expression>, ) { + if name.0.contents.is_empty() { + return; + } + let Some(name_location) = self.to_lsp_location(name.span()) else { return; }; @@ -137,6 +145,10 @@ impl<'a> DocumentSymbolCollector<'a> { impl<'a> Visitor for DocumentSymbolCollector<'a> { fn visit_noir_function(&mut self, noir_function: &NoirFunction, span: Span) -> bool { + if noir_function.def.name.0.contents.is_empty() { + return false; + } + let Some(location) = self.to_lsp_location(span) else { return false; }; @@ -162,6 +174,10 @@ impl<'a> Visitor for DocumentSymbolCollector<'a> { } fn visit_noir_struct(&mut self, noir_struct: &NoirStruct, span: Span) -> bool { + if noir_struct.name.0.contents.is_empty() { + return false; + } + let Some(location) = self.to_lsp_location(span) else { return false; }; @@ -213,6 +229,10 @@ impl<'a> Visitor for DocumentSymbolCollector<'a> { } fn visit_noir_trait(&mut self, noir_trait: &NoirTrait, span: Span) -> bool { + if noir_trait.name.0.contents.is_empty() { + return false; + } + let Some(location) = self.to_lsp_location(span) else { return false; }; @@ -255,6 +275,10 @@ impl<'a> Visitor for DocumentSymbolCollector<'a> { _where_clause: &[noirc_frontend::ast::UnresolvedTraitConstraint], body: &Option, ) -> bool { + if name.0.contents.is_empty() { + return false; + } + let Some(name_location) = self.to_lsp_location(name.span()) else { return false; }; @@ -308,6 +332,10 @@ impl<'a> Visitor for DocumentSymbolCollector<'a> { typ: &UnresolvedType, default_value: &Option, ) -> bool { + if name.0.contents.is_empty() { + return false; + } + self.collect_in_constant(name, typ, default_value.as_ref()); false } @@ -400,6 +428,9 @@ impl<'a> Visitor for DocumentSymbolCollector<'a> { }; let name = type_impl.object_type.typ.to_string(); + if name.is_empty() { + return false; + } let Some(name_location) = self.to_lsp_location(type_impl.object_type.span) else { return false; @@ -431,6 +462,10 @@ impl<'a> Visitor for DocumentSymbolCollector<'a> { } fn visit_parsed_submodule(&mut self, parsed_sub_module: &ParsedSubModule, span: Span) -> bool { + if parsed_sub_module.name.0.contents.is_empty() { + return false; + } + let Some(name_location) = self.to_lsp_location(parsed_sub_module.name.span()) else { return false; }; @@ -465,6 +500,11 @@ impl<'a> Visitor for DocumentSymbolCollector<'a> { } fn visit_global(&mut self, global: &LetStatement, span: Span) -> bool { + let name = global.pattern.to_string(); + if name.is_empty() { + return false; + } + let Some(name_location) = self.to_lsp_location(global.pattern.span()) else { return false; }; @@ -475,7 +515,7 @@ impl<'a> Visitor for DocumentSymbolCollector<'a> { #[allow(deprecated)] self.symbols.push(DocumentSymbol { - name: global.pattern.to_string(), + name, detail: None, kind: SymbolKind::CONSTANT, tags: None, @@ -634,7 +674,7 @@ mod document_symbol_tests { deprecated: None, range: Range { start: Position { line: 15, character: 7 }, - end: Position { line: 15, character: 25 }, + end: Position { line: 15, character: 24 }, }, selection_range: Range { start: Position { line: 15, character: 7 }, diff --git a/noir/noir-repo/tooling/lsp/src/requests/hover.rs b/noir/noir-repo/tooling/lsp/src/requests/hover.rs index 2628c9b2ab6..5087955ea77 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/hover.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/hover.rs @@ -7,11 +7,15 @@ use noirc_frontend::{ ast::Visibility, elaborator::types::try_eval_array_length_id, hir::def_map::ModuleId, - hir_def::{expr::HirArrayLiteral, stmt::HirPattern, traits::Trait}, - macros_api::{HirExpression, HirLiteral, NodeInterner, StructId}, + hir_def::{ + expr::{HirArrayLiteral, HirExpression, HirLiteral}, + stmt::HirPattern, + traits::Trait, + }, node_interner::{ DefinitionId, DefinitionKind, ExprId, FuncId, GlobalId, ReferenceId, TraitId, TypeAliasId, }, + node_interner::{NodeInterner, StructId}, Generics, Shared, StructType, Type, TypeAlias, TypeBinding, TypeVariable, }; @@ -133,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(" }"); @@ -154,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) { @@ -163,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); @@ -526,7 +530,7 @@ impl<'a> TypeLinksGatherer<'a> { self.gather_type_links(generic); } } - Type::TypeVariable(var, _) => { + Type::TypeVariable(var) => { self.gather_type_variable_links(var); } Type::TraitAsType(trait_id, _, generics) => { @@ -539,7 +543,7 @@ impl<'a> TypeLinksGatherer<'a> { self.gather_type_links(&named_type.typ); } } - Type::NamedGeneric(var, _, _) => { + Type::NamedGeneric(var, _) => { self.gather_type_variable_links(var); } Type::Function(args, return_type, env, _) => { 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 2eef4f6e262..f7b3e6a748d 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/inlay_hint.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/inlay_hint.rs @@ -15,10 +15,9 @@ use noirc_frontend::{ UnresolvedTypeData, Visitor, }, hir_def::stmt::HirPattern, - macros_api::NodeInterner, - node_interner::ReferenceId, + node_interner::{NodeInterner, ReferenceId}, parser::{Item, ParsedSubModule}, - Type, TypeBinding, TypeVariable, TypeVariableKind, + Kind, Type, TypeBinding, TypeVariable, }; use crate::{utils, LspState}; @@ -98,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(_) @@ -460,19 +459,14 @@ fn push_type_parts(typ: &Type, parts: &mut Vec, files: &File parts.push(string_part("&mut ")); push_type_parts(typ, parts, files); } - Type::TypeVariable(var, TypeVariableKind::Normal) => { - push_type_variable_parts(var, parts, files); - } - Type::TypeVariable(binding, TypeVariableKind::Integer) => { - if let TypeBinding::Unbound(_) = &*binding.borrow() { - push_type_parts(&Type::default_int_type(), parts, files); - } else { - push_type_variable_parts(binding, parts, files); - } - } - Type::TypeVariable(binding, TypeVariableKind::IntegerOrField) => { - if let TypeBinding::Unbound(_) = &*binding.borrow() { - parts.push(string_part("Field")); + Type::TypeVariable(binding) => { + if let TypeBinding::Unbound(_, kind) = &*binding.borrow() { + match kind { + Kind::Any | Kind::Normal => push_type_variable_parts(binding, parts, files), + Kind::Integer => push_type_parts(&Type::default_int_type(), parts, files), + Kind::IntegerOrField => parts.push(string_part("Field")), + Kind::Numeric(ref typ) => push_type_parts(typ, parts, files), + } } else { push_type_variable_parts(binding, parts, files); } diff --git a/noir/noir-repo/tooling/lsp/src/requests/mod.rs b/noir/noir-repo/tooling/lsp/src/requests/mod.rs index 576d026081d..597d8355468 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/mod.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/mod.rs @@ -18,7 +18,7 @@ use nargo_fmt::Config; use noirc_frontend::graph::CrateId; use noirc_frontend::hir::def_map::CrateDefMap; -use noirc_frontend::{graph::Dependency, macros_api::NodeInterner}; +use noirc_frontend::{graph::Dependency, node_interner::NodeInterner}; use serde::{Deserialize, Serialize}; use crate::{ diff --git a/noir/noir-repo/tooling/lsp/src/requests/signature_help.rs b/noir/noir-repo/tooling/lsp/src/requests/signature_help.rs index b075fea1d1e..c0d40656c19 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/signature_help.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/signature_help.rs @@ -12,8 +12,7 @@ use noirc_frontend::{ MethodCallExpression, Statement, Visitor, }, hir_def::{function::FuncMeta, stmt::HirPattern}, - macros_api::NodeInterner, - node_interner::ReferenceId, + node_interner::{NodeInterner, ReferenceId}, parser::Item, ParsedModule, Type, }; 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 56d2e5e1ea1..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 @@ -3,14 +3,14 @@ use std::collections::BTreeMap; use noirc_frontend::{ ast::NoirTraitImpl, graph::CrateId, + hir::def_map::ModuleDefId, hir::{ def_map::{CrateDefMap, ModuleId}, type_check::generics::TraitGenerics, }, hir_def::{function::FuncMeta, stmt::HirPattern, traits::Trait}, - macros_api::{ModuleDefId, NodeInterner}, - node_interner::{FunctionModifiers, ReferenceId}, - Kind, ResolvedGeneric, Type, TypeVariableKind, + node_interner::{FunctionModifiers, NodeInterner, ReferenceId}, + Kind, ResolvedGeneric, Type, }; use crate::modules::relative_module_id_path; @@ -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); } } @@ -290,7 +290,7 @@ impl<'a> TraitImplMethodStubGenerator<'a> { self.string.push_str(&trait_.name.0.contents); self.append_trait_generics(trait_generics); } - Type::TypeVariable(typevar, _) => { + Type::TypeVariable(typevar) => { if typevar.id() == self.trait_.self_type_typevar.id() { self.string.push_str("Self"); return; @@ -323,8 +323,8 @@ impl<'a> TraitImplMethodStubGenerator<'a> { self.string.push_str("error"); } - Type::NamedGeneric(typevar, _name, _kind) => { - self.append_type(&Type::TypeVariable(typevar.clone(), TypeVariableKind::Normal)); + Type::NamedGeneric(typevar, _name) => { + self.append_type(&Type::TypeVariable(typevar.clone())); } Type::Function(args, ret, env, unconstrained) => { if *unconstrained { @@ -437,9 +437,11 @@ impl<'a> TraitImplMethodStubGenerator<'a> { } fn append_resolved_generic(&mut self, generic: &ResolvedGeneric) { - match &generic.kind { - Kind::Normal => self.string.push_str(&generic.name), - Kind::Numeric(typ) => { + match &generic.kind() { + Kind::Any | Kind::Normal | Kind::Integer | Kind::IntegerOrField => { + self.string.push_str(&generic.name); + } + Kind::Numeric(ref typ) => { self.string.push_str("let "); self.string.push_str(&generic.name); self.string.push_str(": "); 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 284be56d247..d72556ab936 100644 --- a/noir/noir-repo/tooling/nargo_cli/Cargo.toml +++ b/noir/noir-repo/tooling/nargo_cli/Cargo.toml @@ -45,7 +45,12 @@ prettytable-rs = "0.10" rayon.workspace = true thiserror.workspace = true tower.workspace = true -async-lsp = { workspace = true, features = ["client-monitor", "stdio", "tracing", "tokio"] } +async-lsp = { workspace = true, features = [ + "client-monitor", + "stdio", + "tracing", + "tokio", +] } const_format.workspace = true similar-asserts.workspace = true termcolor = "1.1.2" @@ -66,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" @@ -75,8 +81,13 @@ fm.workspace = true criterion.workspace = true pprof.workspace = true paste = "1.0.14" +proptest.workspace = true +sha2.workspace = true +sha3.workspace = true iai = "0.1.1" test-binary = "3.0.2" +light-poseidon = "0.2.0" + [[bench]] name = "criterion" 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 new file mode 100644 index 00000000000..d41d504e530 --- /dev/null +++ b/noir/noir-repo/tooling/nargo_cli/tests/stdlib-props.proptest-regressions @@ -0,0 +1,8 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# 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 new file mode 100644 index 00000000000..9e1a2823e70 --- /dev/null +++ b/noir/noir-repo/tooling/nargo_cli/tests/stdlib-props.rs @@ -0,0 +1,345 @@ +use std::{cell::RefCell, collections::BTreeMap, path::Path}; + +use acvm::{acir::native_types::WitnessStack, AcirField, FieldElement}; +use iter_extended::vecmap; +use nargo::{ + ops::{execute_program, DefaultForeignCallExecutor}, + parse_all, +}; +use noirc_abi::input_parser::InputValue; +use noirc_driver::{ + compile_main, file_manager_with_stdlib, prepare_crate, CompilationResult, CompileOptions, + CompiledProgram, CrateId, +}; +use noirc_frontend::hir::Context; +use proptest::prelude::*; +use sha3::Digest; + +/// Inputs and expected output of a snippet encoded in ABI format. +#[derive(Debug)] +struct SnippetInputOutput { + description: String, + inputs: BTreeMap, + expected_output: InputValue, +} +impl SnippetInputOutput { + fn new(inputs: Vec<(&str, InputValue)>, output: InputValue) -> Self { + Self { + description: "".to_string(), + inputs: inputs.into_iter().map(|(k, v)| (k.to_string(), v)).collect(), + expected_output: output, + } + } + + /// Attach some description to hint at the scenario we are testing. + fn with_description(mut self, description: String) -> Self { + self.description = description; + self + } +} + +/// Prepare a code snippet. +fn prepare_snippet(source: String) -> (Context<'static, 'static>, CrateId) { + let root = Path::new(""); + let file_name = Path::new("main.nr"); + let mut file_manager = file_manager_with_stdlib(root); + file_manager.add_file_with_source(file_name, source).expect( + "Adding source buffer to file manager should never fail when file manager is empty", + ); + let parsed_files = parse_all(&file_manager); + + let mut context = Context::new(file_manager, parsed_files); + let root_crate_id = prepare_crate(&mut context, file_name); + + (context, root_crate_id) +} + +/// Compile the main function in a code snippet. +/// +/// Use `force_brillig` to test it as an unconstrained function without having to change the code. +/// This is useful for methods that use the `runtime::is_unconstrained()` method to change their behavior. +fn prepare_and_compile_snippet( + source: String, + force_brillig: bool, +) -> CompilationResult { + let (mut context, root_crate_id) = prepare_snippet(source); + let options = CompileOptions { force_brillig, ..Default::default() }; + compile_main(&mut context, root_crate_id, &options, None) +} + +/// Compile a snippet and run property tests against it by generating random input/output pairs +/// according to the strategy, executing the snippet with the input, and asserting that the +/// output it returns is the one we expect. +fn run_snippet_proptest( + source: String, + force_brillig: bool, + strategy: BoxedStrategy, +) { + let program = match prepare_and_compile_snippet(source.clone(), force_brillig) { + Ok((program, _)) => program, + Err(e) => panic!("failed to compile program:\n{source}\n{e:?}"), + }; + + let blackbox_solver = bn254_blackbox_solver::Bn254BlackBoxSolver; + let foreign_call_executor = + RefCell::new(DefaultForeignCallExecutor::new(false, None, None, None)); + + // Generate multiple input/output + proptest!(ProptestConfig::with_cases(100), |(io in strategy)| { + let initial_witness = program.abi.encode(&io.inputs, None).expect("failed to encode"); + let mut foreign_call_executor = foreign_call_executor.borrow_mut(); + + let witness_stack: WitnessStack = execute_program( + &program.program, + initial_witness, + &blackbox_solver, + &mut *foreign_call_executor, + ) + .expect("failed to execute"); + + let main_witness = witness_stack.peek().expect("should have return value on witness stack"); + let main_witness = &main_witness.witness; + + let (_, return_value) = program.abi.decode(main_witness).expect("failed to decode"); + let return_value = return_value.expect("should decode a return value"); + + prop_assert_eq!(return_value, io.expected_output, "{}", io.description); + }); +} + +/// Run property tests on a code snippet which is assumed to execute a hashing function with the following signature: +/// +/// ```ignore +/// fn main(input: [u8; {max_len}], message_size: u32) -> pub [u8; 32] +/// ``` +/// +/// The calls are executed with and without forcing brillig, because it seems common for hash functions to run different +/// code paths based on `runtime::is_unconstrained()`. +fn run_hash_proptest( + // Different generic maximum input sizes to try. + max_lengths: &[usize], + // Some hash functions allow inputs which are less than the generic parameters, others don't. + variable_length: bool, + // Make the source code specialized for a given expected input size. + source: impl Fn(usize) -> String, + // Rust implementation of the hash function. + hash: fn(&[u8]) -> [u8; N], +) { + for max_len in max_lengths { + let max_len = *max_len; + // The maximum length is used to pick the generic version of the method. + let source = source(max_len); + // Hash functions runs differently depending on whether the code is unconstrained or not. + for force_brillig in [false, true] { + let length_strategy = + if variable_length { (0..=max_len).boxed() } else { Just(max_len).boxed() }; + // The actual input length can be up to the maximum. + let strategy = length_strategy + .prop_flat_map(|len| prop::collection::vec(any::(), len)) + .prop_map(move |mut msg| { + // The output is the hash of the data as it is. + let output = hash(&msg); + + // The input has to be padded to the maximum length. + let msg_size = msg.len(); + msg.resize(max_len, 0u8); + + let mut inputs = vec![("input", bytes_input(&msg))]; + + // Omit the `message_size` if the hash function doesn't support it. + if variable_length { + inputs.push(( + "message_size", + InputValue::Field(FieldElement::from(msg_size)), + )); + } + + SnippetInputOutput::new(inputs, bytes_input(&output)).with_description(format!( + "force_brillig = {force_brillig}, max_len = {max_len}" + )) + }) + .boxed(); + + run_snippet_proptest(source.clone(), force_brillig, strategy); + } + } +} + +/// This is just a simple test to check that property testing works. +#[test] +fn fuzz_basic() { + let program = "fn main(init: u32) -> pub u32 { + let mut x = init; + for i in 0 .. 6 { + x += i; + } + x + }"; + + let strategy = any::() + .prop_map(|init| { + let init = init / 2; + SnippetInputOutput::new( + vec![("init", InputValue::Field(init.into()))], + InputValue::Field((init + 15).into()), + ) + }) + .boxed(); + + run_snippet_proptest(program.to_string(), false, strategy); +} + +#[test] +fn fuzz_keccak256_equivalence() { + run_hash_proptest( + // XXX: Currently it fails with inputs >= 135 bytes + &[0, 1, 100, 134], + true, + |max_len| { + format!( + "fn main(input: [u8; {max_len}], message_size: u32) -> pub [u8; 32] {{ + std::hash::keccak256(input, message_size) + }}" + ) + }, + |data| sha3::Keccak256::digest(data).try_into().unwrap(), + ); +} + +#[test] +#[should_panic] // Remove once fixed +fn fuzz_keccak256_equivalence_over_135() { + run_hash_proptest( + &[135, 150], + true, + |max_len| { + format!( + "fn main(input: [u8; {max_len}], message_size: u32) -> pub [u8; 32] {{ + std::hash::keccak256(input, message_size) + }}" + ) + }, + |data| sha3::Keccak256::digest(data).try_into().unwrap(), + ); +} + +#[test] +fn fuzz_sha256_equivalence() { + run_hash_proptest( + &[0, 1, 200, 511, 512], + true, + |max_len| { + format!( + "fn main(input: [u8; {max_len}], message_size: u64) -> pub [u8; 32] {{ + std::hash::sha256_var(input, message_size) + }}" + ) + }, + |data| sha2::Sha256::digest(data).try_into().unwrap(), + ); +} + +#[test] +fn fuzz_sha512_equivalence() { + run_hash_proptest( + &[0, 1, 200], + false, + |max_len| { + format!( + "fn main(input: [u8; {max_len}]) -> pub [u8; 64] {{ + std::hash::sha512::digest(input) + }}" + ) + }, + |data| sha2::Sha512::digest(data).try_into().unwrap(), + ); +} + +#[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/Cargo.toml b/noir/noir-repo/tooling/nargo_fmt/Cargo.toml index 9868f259097..1e4d93b3125 100644 --- a/noir/noir-repo/tooling/nargo_fmt/Cargo.toml +++ b/noir/noir-repo/tooling/nargo_fmt/Cargo.toml @@ -11,6 +11,7 @@ workspace = true [dependencies] bytecount = "0.6.3" +noirc_errors.workspace = true noirc_frontend.workspace = true serde.workspace = true toml.workspace = true diff --git a/noir/noir-repo/tooling/nargo_fmt/build.rs b/noir/noir-repo/tooling/nargo_fmt/build.rs index 7d5f07c43bf..4051597c088 100644 --- a/noir/noir-repo/tooling/nargo_fmt/build.rs +++ b/noir/noir-repo/tooling/nargo_fmt/build.rs @@ -12,7 +12,7 @@ fn main() { // is the root of the repository and append the crate path let manifest_dir = match std::env::var("CARGO_MANIFEST_DIR") { Ok(dir) => PathBuf::from(dir), - Err(_) => std::env::current_dir().unwrap().join("crates").join("nargo_cli"), + Err(_) => std::env::current_dir().unwrap().join("tooling").join("nargo_fmt"), }; let test_dir = manifest_dir.join("tests"); diff --git a/noir/noir-repo/tooling/nargo_fmt/src/items.rs b/noir/noir-repo/tooling/nargo_fmt/src/items.rs index 57757982e83..e68be7cdc95 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/items.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/items.rs @@ -1,4 +1,4 @@ -use noirc_frontend::macros_api::Span; +use noirc_errors::Span; use crate::{ utils::{comment_len, find_comment_end}, diff --git a/noir/noir-repo/tooling/nargo_fmt/src/rewrite/expr.rs b/noir/noir-repo/tooling/nargo_fmt/src/rewrite/expr.rs index 873b5c87056..98b50e92476 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/rewrite/expr.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/rewrite/expr.rs @@ -1,8 +1,9 @@ +use noirc_errors::Span; use noirc_frontend::ast::{ ArrayLiteral, BlockExpression, Expression, ExpressionKind, Literal, Path, PathKind, UnaryOp, UnresolvedType, }; -use noirc_frontend::{macros_api::Span, token::Token}; +use noirc_frontend::token::Token; use crate::rewrite; use crate::visitor::{ 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 2feae4b390c..4724c8d957d 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/visitor/item.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/visitor/item.rs @@ -7,8 +7,9 @@ use crate::{ visitor::expr::{format_seq, NewlineMode}, }; use noirc_frontend::{ - ast::{ItemVisibility, NoirFunction, TraitImplItemKind, Visibility}, - macros_api::UnresolvedTypeData, + ast::{ItemVisibility, NoirFunction, TraitImplItemKind, UnresolvedTypeData, Visibility}, + lexer::Lexer, + token::{SecondaryAttribute, TokenKind}, }; use noirc_frontend::{ hir::resolution::errors::Span, @@ -27,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(); @@ -109,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, @@ -171,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()); } @@ -277,6 +409,7 @@ impl super::FmtVisitor<'_> { self.push_rewrite(use_tree, span); self.last_position = span.end(); } + ItemKind::Struct(_) | ItemKind::Trait(_) | ItemKind::TypeAlias(_) diff --git a/noir/noir-repo/tooling/nargo_fmt/src/visitor/stmt.rs b/noir/noir-repo/tooling/nargo_fmt/src/visitor/stmt.rs index 8908aabd87c..7696c4c5fd4 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/visitor/stmt.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/visitor/stmt.rs @@ -1,8 +1,10 @@ use std::iter::zip; -use noirc_frontend::macros_api::Span; +use noirc_errors::Span; -use noirc_frontend::ast::{ConstrainKind, ConstrainStatement, ForRange, Statement, StatementKind}; +use noirc_frontend::ast::{ + ConstrainKind, ConstrainStatement, ForBounds, ForRange, Statement, StatementKind, +}; use crate::{rewrite, visitor::expr::wrap_exprs}; @@ -67,11 +69,13 @@ impl super::FmtVisitor<'_> { StatementKind::For(for_stmt) => { let identifier = self.slice(for_stmt.identifier.span()); let range = match for_stmt.range { - ForRange::Range(start, end) => format!( - "{}..{}", + ForRange::Range(ForBounds { start, end, inclusive }) => format!( + "{}{}{}", rewrite::sub_expr(self, self.shape(), start), + if inclusive { "..=" } else { ".." }, rewrite::sub_expr(self, self.shape(), end) ), + ForRange::Array(array) => rewrite::sub_expr(self, self.shape(), array), }; let block = rewrite::sub_expr(self, self.shape(), for_stmt.block); diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/expected/databus.nr b/noir/noir-repo/tooling/nargo_fmt/tests/expected/databus.nr index 60934b60b2f..0e9761ed52d 100644 --- a/noir/noir-repo/tooling/nargo_fmt/tests/expected/databus.nr +++ b/noir/noir-repo/tooling/nargo_fmt/tests/expected/databus.nr @@ -1,2 +1,2 @@ -fn main(x: pub u8, y: call_data u8) -> return_data u32 {} +fn main(x: pub u8, y: call_data(0) u8) -> return_data u32 {} 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/for.nr b/noir/noir-repo/tooling/nargo_fmt/tests/expected/for.nr index 98dff672bef..748c1fe008d 100644 --- a/noir/noir-repo/tooling/nargo_fmt/tests/expected/for.nr +++ b/noir/noir-repo/tooling/nargo_fmt/tests/expected/for.nr @@ -10,6 +10,10 @@ fn for_stmt() { b *= b; } + for k in 0..=C1 { + d *= d; + } + z *= if b == 1 { 1 } else { c }; c *= c; diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/expected/impl.nr b/noir/noir-repo/tooling/nargo_fmt/tests/expected/impl.nr index 3c2fa42837a..84394f6fa1d 100644 --- a/noir/noir-repo/tooling/nargo_fmt/tests/expected/impl.nr +++ b/noir/noir-repo/tooling/nargo_fmt/tests/expected/impl.nr @@ -10,6 +10,12 @@ impl MyType { fn method(mut self) {} fn method(&mut self) {} + + fn method(self: Self) {} + + fn method(mut self: Self) {} + + fn method(&mut self: Self) {} } impl MyType { 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/databus.nr b/noir/noir-repo/tooling/nargo_fmt/tests/input/databus.nr index 60934b60b2f..e47fcc50210 100644 --- a/noir/noir-repo/tooling/nargo_fmt/tests/input/databus.nr +++ b/noir/noir-repo/tooling/nargo_fmt/tests/input/databus.nr @@ -1,2 +1,2 @@ -fn main(x: pub u8, y: call_data u8) -> return_data u32 {} +fn main(x: pub u8, y: call_data(0) u8) -> return_data u32 { } 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..8ed2c565bc4 100644 --- a/noir/noir-repo/tooling/nargo_fmt/tests/input/fn.nr +++ b/noir/noir-repo/tooling/nargo_fmt/tests/input/fn.nr @@ -56,3 +56,20 @@ 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/for.nr b/noir/noir-repo/tooling/nargo_fmt/tests/input/for.nr index 99b796df820..bf0a497fe65 100644 --- a/noir/noir-repo/tooling/nargo_fmt/tests/input/for.nr +++ b/noir/noir-repo/tooling/nargo_fmt/tests/input/for.nr @@ -12,6 +12,10 @@ fn for_stmt() { b *= b; } + + for k in 0 ..= C1 + { + d *= d; } z *= if b == 1 { 1 } else { c }; diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/input/impl.nr b/noir/noir-repo/tooling/nargo_fmt/tests/input/impl.nr index e4adb8ebd6a..53c9759ca1b 100644 --- a/noir/noir-repo/tooling/nargo_fmt/tests/input/impl.nr +++ b/noir/noir-repo/tooling/nargo_fmt/tests/input/impl.nr @@ -10,6 +10,12 @@ impl MyType { fn method(mut self) {} fn method(&mut self) {} + + fn method(self: Self) {} + + fn method(mut self: Self) {} + + fn method(&mut self: Self) {} } impl MyType { 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 {} + diff --git a/noir/noir-repo/tooling/noir_codegen/package.json b/noir/noir-repo/tooling/noir_codegen/package.json index 75429cc4281..fc38dc4ec01 100644 --- a/noir/noir-repo/tooling/noir_codegen/package.json +++ b/noir/noir-repo/tooling/noir_codegen/package.json @@ -3,7 +3,7 @@ "contributors": [ "The Noir Team " ], - "version": "0.34.0", + "version": "0.35.0", "packageManager": "yarn@3.5.1", "license": "(MIT OR Apache-2.0)", "type": "module", diff --git a/noir/noir-repo/tooling/noir_js/package.json b/noir/noir-repo/tooling/noir_js/package.json index 55347ec9af5..705c3b3c3ee 100644 --- a/noir/noir-repo/tooling/noir_js/package.json +++ b/noir/noir-repo/tooling/noir_js/package.json @@ -3,7 +3,7 @@ "contributors": [ "The Noir Team " ], - "version": "0.34.0", + "version": "0.35.0", "packageManager": "yarn@3.5.1", "license": "(MIT OR Apache-2.0)", "type": "module", diff --git a/noir/noir-repo/tooling/noir_js_backend_barretenberg/package.json b/noir/noir-repo/tooling/noir_js_backend_barretenberg/package.json index 1c3864de482..0c96f7d213c 100644 --- a/noir/noir-repo/tooling/noir_js_backend_barretenberg/package.json +++ b/noir/noir-repo/tooling/noir_js_backend_barretenberg/package.json @@ -3,7 +3,7 @@ "contributors": [ "The Noir Team " ], - "version": "0.34.0", + "version": "0.35.0", "packageManager": "yarn@3.5.1", "license": "(MIT OR Apache-2.0)", "type": "module", @@ -41,7 +41,7 @@ "lint": "NODE_NO_WARNINGS=1 eslint . --ext .ts --ignore-path ./.eslintignore --max-warnings 0" }, "dependencies": { - "@aztec/bb.js": "portal:../../../../barretenberg/ts", + "@aztec/bb.js": "0.56.0", "@noir-lang/types": "workspace:*", "fflate": "^0.8.0" }, diff --git a/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/backend.ts b/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/backend.ts index 812a7173ad2..785b0c73421 100644 --- a/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/backend.ts +++ b/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/backend.ts @@ -1,14 +1,8 @@ import { acirToUint8Array } from './serialize.js'; import { Backend, CompiledCircuit, ProofData, VerifierBackend } from '@noir-lang/types'; -import { deflattenFields, flattenFieldsAsArray } from './public_inputs.js'; -import { reconstructProofWithPublicInputs } from './verifier.js'; -import { - BackendOptions, - reconstructHonkProof, - splitHonkProof, - UltraPlonkBackend, - UltraHonkBackend as UltraHonkBackendInternal, -} from '@aztec/bb.js'; +import { deflattenFields } from './public_inputs.js'; +import { reconstructProofWithPublicInputs, reconstructProofWithPublicInputsHonk } from './verifier.js'; +import { BackendOptions, UltraPlonkBackend, UltraHonkBackend as UltraHonkBackendInternal } from '@aztec/bb.js'; import { decompressSync as gunzip } from 'fflate'; // This is the number of bytes in a UltraPlonk proof @@ -80,6 +74,12 @@ export class BarretenbergBackend implements Backend, VerifierBackend { } } +// Buffers are prepended with their size. The size takes 4 bytes. +const serializedBufferSize = 4; +const fieldByteSize = 32; +const publicInputOffset = 3; +const publicInputsOffsetBytes = publicInputOffset * fieldByteSize; + export class UltraHonkBackend implements Backend, VerifierBackend { // These type assertions are used so that we don't // have to initialize `api` in the constructor. @@ -97,15 +97,31 @@ export class UltraHonkBackend implements Backend, VerifierBackend { async generateProof(compressedWitness: Uint8Array): Promise { const proofWithPublicInputs = await this.backend.generateProof(gunzip(compressedWitness)); - const { proof, publicInputs: flatPublicInputs } = splitHonkProof(proofWithPublicInputs); - const publicInputs = deflattenFields(flatPublicInputs); + const proofAsStrings = deflattenFields(proofWithPublicInputs.slice(4)); + + const numPublicInputs = Number(proofAsStrings[1]); + + // Account for the serialized buffer size at start + const publicInputsOffset = publicInputsOffsetBytes + serializedBufferSize; + // Get the part before and after the public inputs + const proofStart = proofWithPublicInputs.slice(0, publicInputsOffset); + const publicInputsSplitIndex = numPublicInputs * fieldByteSize; + const proofEnd = proofWithPublicInputs.slice(publicInputsOffset + publicInputsSplitIndex); + // Construct the proof without the public inputs + const proof = new Uint8Array([...proofStart, ...proofEnd]); + + // Fetch the number of public inputs out of the proof string + const publicInputsConcatenated = proofWithPublicInputs.slice( + publicInputsOffset, + publicInputsOffset + publicInputsSplitIndex, + ); + const publicInputs = deflattenFields(publicInputsConcatenated); return { proof, publicInputs }; } async verifyProof(proofData: ProofData): Promise { - const flattenedPublicInputs = flattenFieldsAsArray(proofData.publicInputs); - const proof = reconstructHonkProof(flattenedPublicInputs, proofData.proof); + const proof = reconstructProofWithPublicInputsHonk(proofData); return this.backend.verifyProof(proof); } @@ -118,8 +134,7 @@ export class UltraHonkBackend implements Backend, VerifierBackend { proofData: ProofData, numOfPublicInputs: number, ): Promise<{ proofAsFields: string[]; vkAsFields: string[]; vkHash: string }> { - const flattenedPublicInputs = flattenFieldsAsArray(proofData.publicInputs); - const proof = reconstructHonkProof(flattenedPublicInputs, proofData.proof); + const proof = reconstructProofWithPublicInputsHonk(proofData); return this.backend.generateRecursiveProofArtifacts(proof, numOfPublicInputs); } diff --git a/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/verifier.ts b/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/verifier.ts index 7a8fe01d3a2..885ec80caa8 100644 --- a/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/verifier.ts +++ b/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/verifier.ts @@ -1,10 +1,6 @@ import { ProofData } from '@noir-lang/types'; import { flattenFieldsAsArray } from './public_inputs.js'; -import { - BackendOptions, - BarretenbergVerifier as BarretenbergVerifierInternal, - reconstructHonkProof, -} from '@aztec/bb.js'; +import { BackendOptions, BarretenbergVerifier as BarretenbergVerifierInternal } from '@aztec/bb.js'; export class BarretenbergVerifier { private verifier!: BarretenbergVerifierInternal; @@ -43,8 +39,7 @@ export class UltraHonkVerifier { /** @description Verifies a proof */ async verifyProof(proofData: ProofData, verificationKey: Uint8Array): Promise { - const flattenedPublicInputs = flattenFieldsAsArray(proofData.publicInputs); - const proof = reconstructHonkProof(flattenedPublicInputs, proofData.proof); + const proof = reconstructProofWithPublicInputsHonk(proofData); return this.verifier.verifyUltrahonkProof(proof, verificationKey); } @@ -52,3 +47,21 @@ export class UltraHonkVerifier { await this.verifier.destroy(); } } + +const serializedBufferSize = 4; +const fieldByteSize = 32; +const publicInputOffset = 3; +const publicInputsOffsetBytes = publicInputOffset * fieldByteSize; + +export function reconstructProofWithPublicInputsHonk(proofData: ProofData): Uint8Array { + // Flatten publicInputs + const publicInputsConcatenated = flattenFieldsAsArray(proofData.publicInputs); + + const proofStart = proofData.proof.slice(0, publicInputsOffsetBytes + serializedBufferSize); + const proofEnd = proofData.proof.slice(publicInputsOffsetBytes + serializedBufferSize); + + // Concatenate publicInputs and proof + const proofWithPublicInputs = Uint8Array.from([...proofStart, ...publicInputsConcatenated, ...proofEnd]); + + return proofWithPublicInputs; +} diff --git a/noir/noir-repo/tooling/noir_js_types/package.json b/noir/noir-repo/tooling/noir_js_types/package.json index 21dd5beabeb..61cff4bc53d 100644 --- a/noir/noir-repo/tooling/noir_js_types/package.json +++ b/noir/noir-repo/tooling/noir_js_types/package.json @@ -4,7 +4,7 @@ "The Noir Team " ], "packageManager": "yarn@3.5.1", - "version": "0.34.0", + "version": "0.35.0", "license": "(MIT OR Apache-2.0)", "homepage": "https://noir-lang.org/", "repository": { diff --git a/noir/noir-repo/tooling/noirc_abi_wasm/build.sh b/noir/noir-repo/tooling/noirc_abi_wasm/build.sh index c07d2d8a4c1..16fb26e55db 100755 --- a/noir/noir-repo/tooling/noirc_abi_wasm/build.sh +++ b/noir/noir-repo/tooling/noirc_abi_wasm/build.sh @@ -25,7 +25,7 @@ function run_if_available { require_command jq require_command cargo require_command wasm-bindgen -#require_command wasm-opt +require_command wasm-opt self_path=$(dirname "$(readlink -f "$0")") pname=$(cargo read-manifest | jq -r '.name') diff --git a/noir/noir-repo/tooling/noirc_abi_wasm/package.json b/noir/noir-repo/tooling/noirc_abi_wasm/package.json index 021e80017aa..df2bb613751 100644 --- a/noir/noir-repo/tooling/noirc_abi_wasm/package.json +++ b/noir/noir-repo/tooling/noirc_abi_wasm/package.json @@ -3,7 +3,7 @@ "contributors": [ "The Noir Team " ], - "version": "0.34.0", + "version": "0.35.0", "license": "(MIT OR Apache-2.0)", "homepage": "https://noir-lang.org/", "repository": { diff --git a/noir/noir-repo/yarn.lock b/noir/noir-repo/yarn.lock index 77708cc9b21..ae9251ac205 100644 --- a/noir/noir-repo/yarn.lock +++ b/noir/noir-repo/yarn.lock @@ -221,18 +221,19 @@ __metadata: languageName: node linkType: hard -"@aztec/bb.js@portal:../../../../barretenberg/ts::locator=%40noir-lang%2Fbackend_barretenberg%40workspace%3Atooling%2Fnoir_js_backend_barretenberg": - version: 0.0.0-use.local - resolution: "@aztec/bb.js@portal:../../../../barretenberg/ts::locator=%40noir-lang%2Fbackend_barretenberg%40workspace%3Atooling%2Fnoir_js_backend_barretenberg" +"@aztec/bb.js@npm:0.56.0": + version: 0.56.0 + resolution: "@aztec/bb.js@npm:0.56.0" dependencies: comlink: ^4.4.1 commander: ^10.0.1 debug: ^4.3.4 tslib: ^2.4.0 bin: - bb.js: ./dest/node/main.js + bb.js: dest/node/main.js + checksum: 199a1e6c408e4c1399b69169e1a0a48bac92688299312a7dd6eca242e4970808bc370808d2fe4194f17e0d1fe7f5d09676709a05e3ad6ed569ac5553134be34a languageName: node - linkType: soft + linkType: hard "@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.11, @babel/code-frame@npm:^7.16.0, @babel/code-frame@npm:^7.22.13, @babel/code-frame@npm:^7.23.5, @babel/code-frame@npm:^7.8.3": version: 7.23.5 @@ -4160,7 +4161,7 @@ __metadata: version: 0.0.0-use.local resolution: "@noir-lang/backend_barretenberg@workspace:tooling/noir_js_backend_barretenberg" dependencies: - "@aztec/bb.js": "portal:../../../../barretenberg/ts" + "@aztec/bb.js": 0.56.0 "@noir-lang/types": "workspace:*" "@types/node": ^20.6.2 "@types/prettier": ^3