diff --git a/CHANGELOG.md b/CHANGELOG.md index eb6b9e91..2215fae3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ ### Added - **aiken-lang**: Data now has a generic argument that can be used to specify the blueprint type. @KtorZ +- **aiken-lang**: New types `PRNG` and `Fuzzer` in the prelude. @KtorZ +- **aiken-lang**: Test definitions now accept an (optional) argument alongside a new keyword `via` to specify fuzzers. @KtorZ +- **aiken-project**: Property-based testing framework with integrated shrinking. @KtorZ +- **aiken**: The `check` command now accept an extra arg `--seed` to provide an initial seed for the pseudo-random generator of properties. @KtorZ ### Fixed diff --git a/Cargo.lock b/Cargo.lock index 680d68d9..cac8e616 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aes" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", @@ -30,9 +30,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.7" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" +checksum = "8b79b82693f705137f8fb9b37871d99e4f9a7df12b917eed79c3d3954830a60b" dependencies = [ "cfg-if", "once_cell", @@ -67,6 +67,7 @@ dependencies = [ "ordinal", "owo-colors", "pallas", + "rand", "regex", "serde_json", "thiserror", @@ -88,6 +89,7 @@ dependencies = [ "num-bigint", "ordinal", "owo-colors", + "pallas", "petgraph", "pretty_assertions", "strum", @@ -123,7 +125,9 @@ version = "1.0.24-alpha" dependencies = [ "aiken-lang", "askama", + "blst", "built", + "cryptoxide", "dirs", "fslock", "futures", @@ -135,6 +139,7 @@ dependencies = [ "itertools", "miette", "notify", + "num-bigint", "owo-colors", "pallas", "petgraph", @@ -163,9 +168,9 @@ checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" [[package]] name = "anstream" -version = "0.6.11" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" dependencies = [ "anstyle", "anstyle-parse", @@ -177,9 +182,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" [[package]] name = "anstyle-parse" @@ -211,9 +216,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.79" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" [[package]] name = "arrayvec" @@ -247,7 +252,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -273,7 +278,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -457,9 +462,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.9.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" dependencies = [ "memchr", "serde", @@ -477,9 +482,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" [[package]] name = "byteorder" @@ -528,11 +533,10 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.83" +version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "02f341c093d19155a6e41631ce5971aac4e9a868262212153124c15fa22d1cdc" dependencies = [ - "jobserver", "libc", ] @@ -544,9 +548,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.32" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41daef31d7a747c5c847246f36de49ced6f7403b4cdabc807a97b5cc184cda7a" +checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" dependencies = [ "num-traits", ] @@ -573,9 +577,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.18" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" +checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" dependencies = [ "clap_builder", "clap_derive", @@ -583,9 +587,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.18" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" +checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" dependencies = [ "anstream", "anstyle", @@ -598,30 +602,30 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.4.9" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df631ae429f6613fcd3a7c1adbdb65f637271e561b03680adaa6573015dfb106" +checksum = "885e4d7d5af40bfb99ae6f9433e292feac98d452dcb3ec3d25dfe7552b77da8c" dependencies = [ "clap", ] [[package]] name = "clap_derive" -version = "4.4.7" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] name = "clap_lex" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "colorchoice" @@ -695,18 +699,18 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-channel" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b" +checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" dependencies = [ "crossbeam-utils", ] @@ -848,9 +852,9 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" [[package]] name = "ecdsa" @@ -868,9 +872,9 @@ dependencies = [ [[package]] name = "either" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" [[package]] name = "elliptic-curve" @@ -1071,7 +1075,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -1187,7 +1191,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 2.1.0", + "indexmap 2.2.5", "slab", "tokio", "tokio-util", @@ -1196,9 +1200,9 @@ dependencies = [ [[package]] name = "half" -version = "1.8.2" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" [[package]] name = "hashbrown" @@ -1233,9 +1237,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.4" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" @@ -1391,9 +1395,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.1.0" +version = "2.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" dependencies = [ "equivalent", "hashbrown 0.14.3", @@ -1452,9 +1456,9 @@ dependencies = [ [[package]] name = "insta" -version = "1.34.0" +version = "1.35.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d64600be34b2fcfc267740a243fa7744441bb4947a619ac4e5bb6507f35fbfc" +checksum = "7c985c1bef99cf13c58fade470483d81a2bfe846ebde60ed28cc2dddec2df9e2" dependencies = [ "console", "lazy_static", @@ -1472,20 +1476,20 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "is-terminal" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ - "hermit-abi 0.3.4", - "rustix", + "hermit-abi 0.3.9", + "libc", "windows-sys 0.52.0", ] [[package]] name = "is_ci" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" +checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" [[package]] name = "itertools" @@ -1502,20 +1506,11 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" -[[package]] -name = "jobserver" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" -dependencies = [ - "libc", -] - [[package]] name = "js-sys" -version = "0.3.67" +version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" +checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" dependencies = [ "wasm-bindgen", ] @@ -1571,9 +1566,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.152" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libgit2-sys" @@ -1606,9 +1601,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.14" +version = "1.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "295c17e837573c8c821dbaeb3cceb3d745ad082f7572191409e69cbc1b3fd050" +checksum = "037731f5d3aaa87a5675e895b63ddff1a87624bc29f77004ea829809654e48f6" dependencies = [ "cc", "libc", @@ -1640,9 +1635,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "lsp-server" @@ -1710,7 +1705,7 @@ checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -1758,9 +1753,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", ] @@ -1851,20 +1846,25 @@ dependencies = [ ] [[package]] -name = "num-integer" -version = "0.1.45" +name = "num-conv" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", "libm", @@ -1876,7 +1876,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.3.4", + "hermit-abi 0.3.9", "libc", ] @@ -1897,9 +1897,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl" -version = "0.10.63" +version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ "bitflags 2.4.2", "cfg-if", @@ -1918,7 +1918,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -1929,9 +1929,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.99" +version = "0.9.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae" +checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" dependencies = [ "cc", "libc", @@ -2238,27 +2238,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap 2.1.0", + "indexmap 2.2.5", ] [[package]] name = "pin-project" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -2285,9 +2285,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "powerfmt" @@ -2427,11 +2427,11 @@ dependencies = [ [[package]] name = "pulldown-cmark" -version = "0.9.3" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998" +checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.2", "memchr", "unicase", ] @@ -2492,9 +2492,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" +checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" dependencies = [ "either", "rayon-core", @@ -2544,9 +2544,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b7fa1134405e2ec9353fd416b17f8dacd46c473d7d3fd1cf202706a14eb792a" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" dependencies = [ "aho-corasick", "memchr", @@ -2561,9 +2561,9 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" -version = "0.11.23" +version = "0.11.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" +checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" dependencies = [ "base64 0.21.7", "bytes", @@ -2583,9 +2583,11 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", "system-configuration", "tokio", "tokio-native-tls", @@ -2615,9 +2617,9 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.30" +version = "0.38.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" dependencies = [ "bitflags 2.4.2", "errno", @@ -2626,6 +2628,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + [[package]] name = "rustversion" version = "1.0.14" @@ -2646,9 +2657,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "same-file" @@ -2731,40 +2742,40 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.195" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.195" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] name = "serde_json" -version = "1.0.111" +version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" dependencies = [ - "indexmap 2.1.0", + "indexmap 2.2.5", "itoa", "ryu", "serde", @@ -2778,7 +2789,7 @@ checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -2903,12 +2914,12 @@ checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -2945,9 +2956,9 @@ dependencies = [ [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" [[package]] name = "strum" @@ -3025,9 +3036,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.48" +version = "2.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" dependencies = [ "proc-macro2", "quote", @@ -3063,13 +3074,12 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.9.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", "rustix", "windows-sys 0.52.0", ] @@ -3107,22 +3117,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.56" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.56" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -3136,11 +3146,12 @@ dependencies = [ [[package]] name = "time" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" dependencies = [ "deranged", + "num-conv", "powerfmt", "serde", "time-core", @@ -3169,9 +3180,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.35.1" +version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" dependencies = [ "backtrace", "bytes", @@ -3204,7 +3215,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -3269,7 +3280,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.1.0", + "indexmap 2.2.5", "serde", "serde_spanned", "toml_datetime", @@ -3355,7 +3366,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -3420,18 +3431,18 @@ checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-width" @@ -3606,9 +3617,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" +checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -3616,24 +3627,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" +checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.40" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461" +checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" dependencies = [ "cfg-if", "js-sys", @@ -3643,9 +3654,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" +checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3653,28 +3664,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" +checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" +checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" [[package]] name = "web-sys" -version = "0.3.67" +version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" +checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" dependencies = [ "js-sys", "wasm-bindgen", @@ -3738,7 +3749,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.4", ] [[package]] @@ -3758,17 +3769,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", ] [[package]] @@ -3779,9 +3790,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" [[package]] name = "windows_aarch64_msvc" @@ -3791,9 +3802,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" [[package]] name = "windows_i686_gnu" @@ -3803,9 +3814,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" [[package]] name = "windows_i686_msvc" @@ -3815,9 +3826,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" [[package]] name = "windows_x86_64_gnu" @@ -3827,9 +3838,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" [[package]] name = "windows_x86_64_gnullvm" @@ -3839,9 +3850,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" [[package]] name = "windows_x86_64_msvc" @@ -3851,15 +3862,15 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" [[package]] name = "winnow" -version = "0.5.34" +version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7cf47b659b318dccbd69cc4797a39ae128f533dce7902a1096044d1967b9c16" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" dependencies = [ "memchr", ] @@ -3906,7 +3917,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -3926,7 +3937,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] diff --git a/aikup/aikup b/aikup/aikup index 6f5b5430..66748696 100755 --- a/aikup/aikup +++ b/aikup/aikup @@ -1,5 +1,6 @@ #!/usr/bin/env bash set -e +set -o pipefail AIKEN_DIR=${AIKEN_DIR-"$HOME/.aiken"} AIKEN_BIN_DIR="$AIKEN_DIR/bin" @@ -86,7 +87,9 @@ main() { # Download the binaries tarball and unpack it into the .aiken bin directory. say "downloading aiken" - ensure curl -# -L "$BIN_TARBALL_URL" | tar -xzC "$AIKEN_BIN_DIR" + curl -f -# -L "$BIN_TARBALL_URL" | tar -xzC "$AIKEN_BIN_DIR" || { + err "failed to download aiken: version not found or network error" + } for bin in "${BINS[@]}"; do bin_path="$AIKEN_BIN_DIR/$bin" diff --git a/crates/aiken-lang/Cargo.toml b/crates/aiken-lang/Cargo.toml index 8ff5bc89..4f5f6d76 100644 --- a/crates/aiken-lang/Cargo.toml +++ b/crates/aiken-lang/Cargo.toml @@ -21,6 +21,7 @@ itertools = "0.10.5" miette = "5.9.0" ordinal = "0.3.2" owo-colors = { version = "3.5.0", features = ["supports-colors"] } +pallas.workspace = true strum = "0.24.1" thiserror = "1.0.39" vec1 = "1.10.1" diff --git a/crates/aiken-lang/src/ast.rs b/crates/aiken-lang/src/ast.rs index 1811243b..49a6848c 100644 --- a/crates/aiken-lang/src/ast.rs +++ b/crates/aiken-lang/src/ast.rs @@ -5,6 +5,7 @@ use crate::{ parser::token::{Base, Token}, tipo::{PatternConstructor, Type, TypeInfo}, }; +use indexmap::IndexMap; use miette::Diagnostic; use owo_colors::{OwoColorize, Stream::Stdout}; use std::{ @@ -127,6 +128,62 @@ impl TypedModule { Ok(()) } + + // TODO: Avoid cloning definitions here. This would likely require having a lifetime on + // 'Project', so that we can enforce that those references live from the ast to here. + pub fn register_definitions( + &self, + functions: &mut IndexMap, + data_types: &mut IndexMap, + ) { + for def in self.definitions() { + match def { + Definition::Fn(func) => { + functions.insert( + FunctionAccessKey { + module_name: self.name.clone(), + function_name: func.name.clone(), + }, + func.clone(), + ); + } + + Definition::Test(test) => { + functions.insert( + FunctionAccessKey { + module_name: self.name.clone(), + function_name: test.name.clone(), + }, + test.clone().into(), + ); + } + + Definition::DataType(dt) => { + data_types.insert( + DataTypeKey { + module_name: self.name.clone(), + defined_type: dt.name.clone(), + }, + dt.clone(), + ); + } + + Definition::Validator(v) => { + let module_name = self.name.as_str(); + + if let Some((k, v)) = v.into_function_definition(module_name, |f, _| Some(f)) { + functions.insert(k, v); + } + + if let Some((k, v)) = v.into_function_definition(module_name, |_, f| f) { + functions.insert(k, v); + } + } + + Definition::TypeAlias(_) | Definition::ModuleConstant(_) | Definition::Use(_) => {} + } + } + } } fn str_to_keyword(word: &str) -> Option { @@ -154,16 +211,20 @@ fn str_to_keyword(word: &str) -> Option { "and" => Some(Token::And), "or" => Some(Token::Or), "validator" => Some(Token::Validator), + "via" => Some(Token::Via), _ => None, } } -pub type TypedFunction = Function, TypedExpr>; -pub type UntypedFunction = Function<(), UntypedExpr>; +pub type TypedFunction = Function, TypedExpr, TypedArg>; +pub type UntypedFunction = Function<(), UntypedExpr, UntypedArg>; + +pub type TypedTest = Function, TypedExpr, TypedArgVia>; +pub type UntypedTest = Function<(), UntypedExpr, UntypedArgVia>; #[derive(Debug, Clone, PartialEq)] -pub struct Function { - pub arguments: Vec>, +pub struct Function { + pub arguments: Vec, pub body: Expr, pub doc: Option, pub location: Span, @@ -178,9 +239,47 @@ pub struct Function { pub type TypedTypeAlias = TypeAlias>; pub type UntypedTypeAlias = TypeAlias<()>; -impl TypedFunction { +impl From for UntypedFunction { + fn from(f: UntypedTest) -> Self { + Function { + doc: f.doc, + location: f.location, + name: f.name, + public: f.public, + arguments: f.arguments.into_iter().map(|arg| arg.into()).collect(), + return_annotation: f.return_annotation, + return_type: f.return_type, + body: f.body, + can_error: f.can_error, + end_position: f.end_position, + } + } +} + +impl From for TypedFunction { + fn from(f: TypedTest) -> Self { + Function { + doc: f.doc, + location: f.location, + name: f.name, + public: f.public, + arguments: f.arguments.into_iter().map(|arg| arg.into()).collect(), + return_annotation: f.return_annotation, + return_type: f.return_type, + body: f.body, + can_error: f.can_error, + end_position: f.end_position, + } + } +} + +impl TypedTest { pub fn test_hint(&self) -> Option<(BinOp, Box, Box)> { - do_test_hint(&self.body) + if self.arguments.is_empty() { + do_test_hint(&self.body) + } else { + None + } } } @@ -235,9 +334,77 @@ pub struct TypeAlias { pub tipo: T, } +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct DataTypeKey { + pub module_name: String, + pub defined_type: String, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub struct FunctionAccessKey { + pub module_name: String, + pub function_name: String, +} + pub type TypedDataType = DataType>; impl TypedDataType { + pub fn bool() -> Self { + DataType { + constructors: vec![ + RecordConstructor { + location: Span::empty(), + name: "False".to_string(), + arguments: vec![], + doc: None, + sugar: false, + }, + RecordConstructor { + location: Span::empty(), + name: "True".to_string(), + arguments: vec![], + doc: None, + sugar: false, + }, + ], + doc: None, + location: Span::empty(), + name: "Bool".to_string(), + opaque: false, + parameters: vec![], + public: true, + typed_parameters: vec![], + } + } + + pub fn prng() -> Self { + DataType { + constructors: vec![ + RecordConstructor { + location: Span::empty(), + name: "Seeded".to_string(), + arguments: vec![], + doc: None, + sugar: false, + }, + RecordConstructor { + location: Span::empty(), + name: "Replayed".to_string(), + arguments: vec![], + doc: None, + sugar: false, + }, + ], + doc: None, + location: Span::empty(), + name: "PRNG".to_string(), + opaque: false, + parameters: vec![], + public: true, + typed_parameters: vec![], + } + } + pub fn ordering() -> Self { DataType { constructors: vec![ @@ -358,18 +525,51 @@ pub type UntypedValidator = Validator<(), UntypedExpr>; pub struct Validator { pub doc: Option, pub end_position: usize, - pub fun: Function, - pub other_fun: Option>, + pub fun: Function>, + pub other_fun: Option>>, pub location: Span, pub params: Vec>, } +impl TypedValidator { + pub fn into_function_definition<'a, F>( + &'a self, + module_name: &str, + select: F, + ) -> Option<(FunctionAccessKey, TypedFunction)> + where + F: Fn(&'a TypedFunction, Option<&'a TypedFunction>) -> Option<&'a TypedFunction> + 'a, + { + match select(&self.fun, self.other_fun.as_ref()) { + None => None, + Some(fun) => { + let mut fun = fun.clone(); + + fun.arguments = self + .params + .clone() + .into_iter() + .chain(fun.arguments) + .collect(); + + Some(( + FunctionAccessKey { + module_name: module_name.to_string(), + function_name: fun.name.clone(), + }, + fun, + )) + } + } + } +} + pub type TypedDefinition = Definition, TypedExpr, String>; pub type UntypedDefinition = Definition<(), UntypedExpr, ()>; #[derive(Debug, Clone, PartialEq)] pub enum Definition { - Fn(Function), + Fn(Function>), TypeAlias(TypeAlias), @@ -379,7 +579,7 @@ pub enum Definition { ModuleConstant(ModuleConstant), - Test(Function), + Test(Function>), Validator(Validator), } @@ -634,6 +834,30 @@ impl Arg { } } +pub type TypedArgVia = ArgVia, TypedExpr>; +pub type UntypedArgVia = ArgVia<(), UntypedExpr>; + +#[derive(Debug, Clone, PartialEq)] +pub struct ArgVia { + pub arg_name: ArgName, + pub location: Span, + pub via: Expr, + pub tipo: T, + pub annotation: Option, +} + +impl From> for Arg { + fn from(arg: ArgVia) -> Arg { + Arg { + arg_name: arg.arg_name, + location: arg.location, + tipo: arg.tipo, + annotation: None, + doc: None, + } + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub enum ArgName { Discarded { diff --git a/crates/aiken-lang/src/builtins.rs b/crates/aiken-lang/src/builtins.rs index c9016593..e8714020 100644 --- a/crates/aiken-lang/src/builtins.rs +++ b/crates/aiken-lang/src/builtins.rs @@ -1,7 +1,9 @@ use crate::{ - ast::{Arg, ArgName, CallArg, Function, ModuleKind, Span, TypedDataType, TypedFunction, UnOp}, + ast::{ + Arg, ArgName, CallArg, DataTypeKey, Function, FunctionAccessKey, ModuleKind, Span, + TypedDataType, TypedFunction, UnOp, + }, expr::TypedExpr, - gen_uplc::builder::{DataTypeKey, FunctionAccessKey}, tipo::{ fields::FieldMap, Type, TypeConstructor, TypeInfo, TypeVar, ValueConstructor, ValueConstructorVariant, @@ -26,6 +28,8 @@ pub const STRING: &str = "String"; pub const OPTION: &str = "Option"; pub const ORDERING: &str = "Ordering"; pub const REDEEMER_WRAPPER: &str = "RedeemerWrapper"; +pub const PRNG: &str = "PRNG"; +pub const FUZZER: &str = "Fuzzer"; /// Build a prelude that can be injected /// into a compiler pipeline @@ -80,22 +84,7 @@ pub fn prelude(id_gen: &IdGenerator) -> TypeInfo { // Bool prelude.types_constructors.insert( BOOL.to_string(), - vec!["True".to_string(), "False".to_string()], - ); - - prelude.values.insert( - "True".to_string(), - ValueConstructor::public( - bool(), - ValueConstructorVariant::Record { - module: "".into(), - name: "True".to_string(), - field_map: None::, - arity: 0, - location: Span::empty(), - constructors_count: 2, - }, - ), + vec!["False".to_string(), "True".to_string()], ); prelude.values.insert( @@ -113,6 +102,21 @@ pub fn prelude(id_gen: &IdGenerator) -> TypeInfo { ), ); + prelude.values.insert( + "True".to_string(), + ValueConstructor::public( + bool(), + ValueConstructorVariant::Record { + module: "".into(), + name: "True".to_string(), + field_map: None::, + arity: 0, + location: Span::empty(), + constructors_count: 2, + }, + ), + ); + prelude.types.insert( BOOL.to_string(), TypeConstructor { @@ -153,7 +157,7 @@ pub fn prelude(id_gen: &IdGenerator) -> TypeInfo { MILLER_LOOP_RESULT.to_string(), TypeConstructor { parameters: vec![], - tipo: int(), + tipo: miller_loop_result(), location: Span::empty(), module: "".to_string(), public: true, @@ -412,6 +416,90 @@ pub fn prelude(id_gen: &IdGenerator) -> TypeInfo { ), ); + // PRNG + // + // pub type PRNG { + // Seeded { seed: ByteArray, choices: ByteArray } + // Replayed { cursor: Int, choices: ByteArray } + // } + + prelude.types.insert( + PRNG.to_string(), + TypeConstructor { + location: Span::empty(), + parameters: vec![], + tipo: prng(), + module: "".to_string(), + public: true, + }, + ); + + prelude.types_constructors.insert( + PRNG.to_string(), + vec!["Seeded".to_string(), "Replayed".to_string()], + ); + + let mut seeded_fields = HashMap::new(); + seeded_fields.insert("seed".to_string(), (0, Span::empty())); + seeded_fields.insert("choices".to_string(), (1, Span::empty())); + prelude.values.insert( + "Seeded".to_string(), + ValueConstructor::public( + function(vec![byte_array(), byte_array()], prng()), + ValueConstructorVariant::Record { + module: "".into(), + name: "Seeded".to_string(), + field_map: Some(FieldMap { + arity: 2, + fields: seeded_fields, + is_function: false, + }), + arity: 2, + location: Span::empty(), + constructors_count: 2, + }, + ), + ); + + let mut replayed_fields = HashMap::new(); + replayed_fields.insert("cursor".to_string(), (0, Span::empty())); + replayed_fields.insert("choices".to_string(), (1, Span::empty())); + prelude.values.insert( + "Replayed".to_string(), + ValueConstructor::public( + function(vec![int(), byte_array()], prng()), + ValueConstructorVariant::Record { + module: "".into(), + name: "Replayed".to_string(), + field_map: Some(FieldMap { + arity: 2, + fields: replayed_fields, + is_function: false, + }), + arity: 2, + location: Span::empty(), + constructors_count: 2, + }, + ), + ); + + // Fuzzer + // + // pub type Fuzzer = + // fn(PRNG) -> Option<(PRNG, a)> + + let fuzzer_value = generic_var(id_gen.next()); + prelude.types.insert( + FUZZER.to_string(), + TypeConstructor { + location: Span::empty(), + parameters: vec![fuzzer_value.clone()], + tipo: fuzzer(fuzzer_value), + module: "".to_string(), + public: true, + }, + ); + prelude } @@ -1133,6 +1221,16 @@ pub fn prelude_data_types(id_gen: &IdGenerator) -> IndexMap IndexMap Rc { }) } +pub fn prng() -> Rc { + Rc::new(Type::App { + args: vec![], + public: true, + name: PRNG.to_string(), + module: "".to_string(), + }) +} + +pub fn fuzzer(a: Rc) -> Rc { + Rc::new(Type::Fn { + args: vec![prng()], + ret: option(tuple(vec![prng(), a])), + }) +} + pub fn list(t: Rc) -> Rc { Rc::new(Type::App { public: true, diff --git a/crates/aiken-lang/src/expr.rs b/crates/aiken-lang/src/expr.rs index dccc9aaa..fbcca706 100644 --- a/crates/aiken-lang/src/expr.rs +++ b/crates/aiken-lang/src/expr.rs @@ -1,18 +1,23 @@ -use std::rc::Rc; - -use vec1::Vec1; - use crate::{ ast::{ self, Annotation, Arg, AssignmentKind, BinOp, ByteArrayFormatPreference, CallArg, Curve, - DefinitionLocation, IfBranch, Located, LogicalOpChainKind, ParsedCallArg, Pattern, - RecordUpdateSpread, Span, TraceKind, TypedClause, TypedRecordUpdateArg, UnOp, - UntypedClause, UntypedRecordUpdateArg, + DataType, DataTypeKey, DefinitionLocation, IfBranch, Located, LogicalOpChainKind, + ParsedCallArg, Pattern, RecordConstructorArg, RecordUpdateSpread, Span, TraceKind, + TypedClause, TypedDataType, TypedRecordUpdateArg, UnOp, UntypedClause, + UntypedRecordUpdateArg, }, builtins::void, + gen_uplc::builder::{ + check_replaceable_opaque_type, convert_opaque_type, lookup_data_type_by_tipo, + }, parser::token::Base, - tipo::{ModuleValueConstructor, PatternConstructor, Type, ValueConstructor}, + tipo::{ModuleValueConstructor, PatternConstructor, Type, TypeVar, ValueConstructor}, }; +use indexmap::IndexMap; +use pallas::ledger::primitives::alonzo::{Constr, PlutusData}; +use std::rc::Rc; +use uplc::{machine::value::from_pallas_bigint, KeyValuePairs}; +use vec1::Vec1; #[derive(Debug, Clone, PartialEq)] pub enum TypedExpr { @@ -573,6 +578,185 @@ pub const DEFAULT_TODO_STR: &str = "aiken::todo"; pub const DEFAULT_ERROR_STR: &str = "aiken::error"; impl UntypedExpr { + // Reify some opaque 'PlutusData' into an 'UntypedExpr', using a Type annotation. We also need + // an extra map to lookup record & enum constructor's names as they're completely erased when + // in their PlutusData form, and the Type annotation only contains type name. + // + // The function performs some sanity check to ensure that the type does indeed somewhat + // correspond to the data being given. + pub fn reify( + data_types: &IndexMap<&DataTypeKey, &TypedDataType>, + data: PlutusData, + tipo: &Type, + ) -> Result { + if let Type::Var { tipo } = tipo { + if let TypeVar::Link { tipo } = &*tipo.borrow() { + return UntypedExpr::reify(data_types, data, tipo); + } + } + + // NOTE: Opaque types are tricky. We can't tell from a type only if it is + // opaque or not. We have to lookup its datatype definition. + // + // Also, we can't -- in theory -- peak into an opaque type. More so, if it + // has a single constructor with a single argument, it is an zero-cost + // wrapper. That means the underlying PlutusData has no residue of that + // wrapper. So we have to manually reconstruct it before crawling further + // down the type tree. + if check_replaceable_opaque_type(tipo, data_types) { + let DataType { name, .. } = lookup_data_type_by_tipo(data_types, tipo) + .expect("Type just disappeared from known types? {tipo:?}"); + + let inner_type = convert_opaque_type(&tipo.clone().into(), data_types, false); + + let value = UntypedExpr::reify(data_types, data, &inner_type)?; + + return Ok(UntypedExpr::Call { + location: Span::empty(), + arguments: vec![CallArg { + label: None, + location: Span::empty(), + value, + }], + fun: Box::new(UntypedExpr::Var { + name, + location: Span::empty(), + }), + }); + } + + match data { + PlutusData::BigInt(ref i) => Ok(UntypedExpr::UInt { + location: Span::empty(), + base: Base::Decimal { + numeric_underscore: false, + }, + value: from_pallas_bigint(i).to_string(), + }), + PlutusData::BoundedBytes(bytes) => Ok(UntypedExpr::ByteArray { + location: Span::empty(), + bytes: bytes.into(), + preferred_format: ByteArrayFormatPreference::HexadecimalString, + }), + PlutusData::Array(args) => { + match tipo { + Type::App { + module, + name, + args: type_args, + .. + } if module.is_empty() && name.as_str() == "List" => { + if let [inner] = &type_args[..] { + Ok(UntypedExpr::List { + location: Span::empty(), + elements: args + .into_iter() + .map(|arg| UntypedExpr::reify(data_types, arg, inner)) + .collect::, _>>()?, + tail: None, + }) + } else { + Err("invalid List type annotation: the list has multiple type-parameters.".to_string()) + } + } + Type::Tuple { elems } => Ok(UntypedExpr::Tuple { + location: Span::empty(), + elems: args + .into_iter() + .zip(elems) + .map(|(arg, arg_type)| UntypedExpr::reify(data_types, arg, arg_type)) + .collect::, _>>()?, + }), + _ => Err(format!( + "invalid type annotation. expected List but got: {tipo:?}" + )), + } + } + + PlutusData::Constr(Constr { + tag, + any_constructor, + fields, + }) => { + let ix = if tag == 102 { + any_constructor.unwrap() as usize + } else if tag < 128 { + tag as usize - 121 + } else { + tag as usize - 1280 + 7 + }; + + if let Type::App { .. } = tipo { + if let Some(DataType { constructors, .. }) = + lookup_data_type_by_tipo(data_types, tipo) + { + let constructor = &constructors[ix]; + + return if fields.is_empty() { + Ok(UntypedExpr::Var { + location: Span::empty(), + name: constructor.name.to_string(), + }) + } else { + let arguments = fields + .into_iter() + .zip(constructor.arguments.iter()) + .map( + |( + field, + RecordConstructorArg { + ref label, + ref tipo, + .. + }, + )| { + UntypedExpr::reify(data_types, field, tipo).map(|value| { + CallArg { + label: label.clone(), + location: Span::empty(), + value, + } + }) + }, + ) + .collect::, _>>()?; + + Ok(UntypedExpr::Call { + location: Span::empty(), + arguments, + fun: Box::new(UntypedExpr::Var { + name: constructor.name.to_string(), + location: Span::empty(), + }), + }) + }; + } + } + + Err(format!( + "invalid type annotation {tipo:?} for constructor: {tag:?} with {fields:?}" + )) + } + + PlutusData::Map(indef_or_def) => { + let kvs = match indef_or_def { + KeyValuePairs::Def(kvs) => kvs, + KeyValuePairs::Indef(kvs) => kvs, + }; + + UntypedExpr::reify( + data_types, + PlutusData::Array( + kvs.into_iter() + .map(|(k, v)| PlutusData::Array(vec![k, v])) + .collect(), + ), + tipo, + ) + } + } + } + pub fn todo(reason: Option, location: Span) -> Self { UntypedExpr::Trace { location, diff --git a/crates/aiken-lang/src/format.rs b/crates/aiken-lang/src/format.rs index 176aa02c..25caf3ec 100644 --- a/crates/aiken-lang/src/format.rs +++ b/crates/aiken-lang/src/format.rs @@ -1,11 +1,12 @@ use crate::{ ast::{ - Annotation, Arg, ArgName, AssignmentKind, BinOp, ByteArrayFormatPreference, CallArg, - ClauseGuard, Constant, CurveType, DataType, Definition, Function, IfBranch, + Annotation, Arg, ArgName, ArgVia, AssignmentKind, BinOp, ByteArrayFormatPreference, + CallArg, ClauseGuard, Constant, CurveType, DataType, Definition, Function, IfBranch, LogicalOpChainKind, ModuleConstant, Pattern, RecordConstructor, RecordConstructorArg, RecordUpdateSpread, Span, TraceKind, TypeAlias, TypedArg, UnOp, UnqualifiedImport, - UntypedArg, UntypedClause, UntypedClauseGuard, UntypedDefinition, UntypedFunction, - UntypedModule, UntypedPattern, UntypedRecordUpdateArg, Use, Validator, CAPTURE_VARIABLE, + UntypedArg, UntypedArgVia, UntypedClause, UntypedClauseGuard, UntypedDefinition, + UntypedFunction, UntypedModule, UntypedPattern, UntypedRecordUpdateArg, Use, Validator, + CAPTURE_VARIABLE, }, docvec, expr::{FnStyle, UntypedExpr, DEFAULT_ERROR_STR, DEFAULT_TODO_STR}, @@ -231,16 +232,7 @@ impl<'comments> Formatter<'comments> { return_annotation, end_position, .. - }) => self.definition_fn( - public, - "fn", - name, - args, - return_annotation, - body, - *end_position, - false, - ), + }) => self.definition_fn(public, name, args, return_annotation, body, *end_position), Definition::Validator(Validator { end_position, @@ -257,16 +249,7 @@ impl<'comments> Formatter<'comments> { end_position, can_error, .. - }) => self.definition_fn( - &false, - "test", - name, - args, - &None, - body, - *end_position, - *can_error, - ), + }) => self.definition_test(name, args, body, *end_position, *can_error), Definition::TypeAlias(TypeAlias { alias, @@ -488,25 +471,38 @@ impl<'comments> Formatter<'comments> { commented(doc, comments) } + fn fn_arg_via<'a, A>(&mut self, arg: &'a ArgVia) -> Document<'a> { + let comments = self.pop_comments(arg.location.start); + + let doc_comments = self.doc_comments(arg.location.start); + + let doc = arg + .arg_name + .to_doc() + .append(" via ") + .append(self.expr(&arg.via, false)) + .group(); + + let doc = doc_comments.append(doc.group()).group(); + + commented(doc, comments) + } + #[allow(clippy::too_many_arguments)] fn definition_fn<'a>( &mut self, public: &'a bool, - keyword: &'a str, name: &'a str, args: &'a [UntypedArg], return_annotation: &'a Option, body: &'a UntypedExpr, end_location: usize, - can_error: bool, ) -> Document<'a> { // Fn name and args let head = pub_(*public) - .append(keyword) - .append(" ") + .append("fn ") .append(name) - .append(wrap_args(args.iter().map(|e| (self.fn_arg(e), false)))) - .append(if can_error { " fail" } else { "" }); + .append(wrap_args(args.iter().map(|e| (self.fn_arg(e), false)))); // Add return annotation let head = match return_annotation { @@ -531,6 +527,39 @@ impl<'comments> Formatter<'comments> { .append("}") } + #[allow(clippy::too_many_arguments)] + fn definition_test<'a>( + &mut self, + name: &'a str, + args: &'a [UntypedArgVia], + body: &'a UntypedExpr, + end_location: usize, + can_error: bool, + ) -> Document<'a> { + // Fn name and args + let head = "test " + .to_doc() + .append(name) + .append(wrap_args(args.iter().map(|e| (self.fn_arg_via(e), false)))) + .append(if can_error { " fail" } else { "" }) + .group(); + + // Format body + let body = self.expr(body, true); + + // Add any trailing comments + let body = match printed_comments(self.pop_comments(end_location), false) { + Some(comments) => body.append(line()).append(comments), + None => body, + }; + + // Stick it all together + head.append(" {") + .append(line().append(body).nest(INDENT).group()) + .append(line()) + .append("}") + } + fn definition_validator<'a>( &mut self, params: &'a [UntypedArg], @@ -550,13 +579,11 @@ impl<'comments> Formatter<'comments> { let first_fn = self .definition_fn( &false, - "fn", &fun.name, &fun.arguments, &fun.return_annotation, &fun.body, fun.end_position, - false, ) .group(); let first_fn = commented(fun_doc_comments.append(first_fn).group(), fun_comments); @@ -570,13 +597,11 @@ impl<'comments> Formatter<'comments> { let other_fn = self .definition_fn( &false, - "fn", &other.name, &other.arguments, &other.return_annotation, &other.body, other.end_position, - false, ) .group(); diff --git a/crates/aiken-lang/src/gen_uplc.rs b/crates/aiken-lang/src/gen_uplc.rs index 86fbeaeb..44b7f16f 100644 --- a/crates/aiken-lang/src/gen_uplc.rs +++ b/crates/aiken-lang/src/gen_uplc.rs @@ -2,25 +2,21 @@ pub mod air; pub mod builder; pub mod tree; -use petgraph::{algo, Graph}; -use std::collections::HashMap; -use std::rc::Rc; - -use indexmap::{IndexMap, IndexSet}; -use itertools::Itertools; -use uplc::{ - ast::{Constant as UplcConstant, Name, NamedDeBruijn, Program, Term, Type as UplcType}, - builder::{CONSTR_FIELDS_EXPOSER, CONSTR_INDEX_EXPOSER, EXPECT_ON_LIST}, - builtins::DefaultFunction, - machine::cost_model::ExBudget, - optimize::aiken_optimize_and_intern, - parser::interner::Interner, +use self::{ + air::Air, + builder::{ + air_holds_msg, cast_validator_args, constants_ir, convert_type_to_data, extract_constant, + lookup_data_type_by_tipo, modify_cyclic_calls, modify_self_calls, rearrange_list_clauses, + AssignmentProperties, ClauseProperties, CodeGenSpecialFuncs, CycleFunctionNames, + HoistableFunction, Variant, + }, + tree::{AirMsg, AirTree, TreePath}, }; - use crate::{ ast::{ - AssignmentKind, BinOp, Bls12_381Point, Curve, Pattern, Span, TraceLevel, TypedArg, - TypedClause, TypedDataType, TypedFunction, TypedPattern, TypedValidator, UnOp, + AssignmentKind, BinOp, Bls12_381Point, Curve, DataTypeKey, FunctionAccessKey, Pattern, + Span, TraceLevel, Tracing, TypedArg, TypedClause, TypedDataType, TypedFunction, + TypedPattern, TypedValidator, UnOp, }, builtins::{bool, data, int, list, string, void}, expr::TypedExpr, @@ -41,25 +37,26 @@ use crate::{ }, IdGenerator, }; - -use self::{ - air::Air, - builder::{ - air_holds_msg, cast_validator_args, constants_ir, convert_type_to_data, extract_constant, - lookup_data_type_by_tipo, modify_cyclic_calls, modify_self_calls, rearrange_list_clauses, - AssignmentProperties, ClauseProperties, CodeGenSpecialFuncs, CycleFunctionNames, - DataTypeKey, FunctionAccessKey, HoistableFunction, Variant, - }, - tree::{AirMsg, AirTree, TreePath}, +use indexmap::{IndexMap, IndexSet}; +use itertools::Itertools; +use petgraph::{algo, Graph}; +use std::{collections::HashMap, rc::Rc}; +use uplc::{ + ast::{Constant as UplcConstant, Name, NamedDeBruijn, Program, Term, Type as UplcType}, + builder::{CONSTR_FIELDS_EXPOSER, CONSTR_INDEX_EXPOSER, EXPECT_ON_LIST}, + builtins::DefaultFunction, + machine::cost_model::ExBudget, + optimize::aiken_optimize_and_intern, + parser::interner::Interner, }; #[derive(Clone)] pub struct CodeGenerator<'a> { /// immutable index maps - functions: IndexMap, - data_types: IndexMap, - module_types: IndexMap<&'a String, &'a TypeInfo>, - module_src: IndexMap, + functions: IndexMap<&'a FunctionAccessKey, &'a TypedFunction>, + data_types: IndexMap<&'a DataTypeKey, &'a TypedDataType>, + module_types: IndexMap<&'a str, &'a TypeInfo>, + module_src: IndexMap<&'a str, &'a (String, LineNumbers)>, /// immutable option tracing: TraceLevel, /// mutable index maps that are reset @@ -74,19 +71,23 @@ pub struct CodeGenerator<'a> { } impl<'a> CodeGenerator<'a> { + pub fn data_types(&self) -> &IndexMap<&'a DataTypeKey, &'a TypedDataType> { + &self.data_types + } + pub fn new( - functions: IndexMap, - data_types: IndexMap, - module_types: IndexMap<&'a String, &'a TypeInfo>, - module_src: IndexMap, - tracing: TraceLevel, + functions: IndexMap<&'a FunctionAccessKey, &'a TypedFunction>, + data_types: IndexMap<&'a DataTypeKey, &'a TypedDataType>, + module_types: IndexMap<&'a str, &'a TypeInfo>, + module_src: IndexMap<&'a str, &'a (String, LineNumbers)>, + tracing: Tracing, ) -> Self { CodeGenerator { functions, data_types, module_types, module_src, - tracing, + tracing: tracing.trace_level(true), defined_functions: IndexMap::new(), special_functions: CodeGenSpecialFuncs::new(), code_gen_functions: IndexMap::new(), @@ -107,21 +108,6 @@ impl<'a> CodeGenerator<'a> { } } - pub fn insert_function( - &mut self, - module_name: String, - function_name: String, - value: &'a TypedFunction, - ) -> Option<&'a TypedFunction> { - self.functions.insert( - FunctionAccessKey { - module_name, - function_name, - }, - value, - ) - } - pub fn generate( &mut self, TypedValidator { @@ -130,16 +116,16 @@ impl<'a> CodeGenerator<'a> { params, .. }: &TypedValidator, - module_name: &String, + module_name: &str, ) -> Program { let mut air_tree_fun = self.build(&fun.body, module_name, &[]); air_tree_fun = wrap_validator_condition(air_tree_fun, self.tracing); - let (src_code, lines) = self.module_src.get(module_name).unwrap().clone(); + let (src_code, lines) = self.module_src.get(module_name).unwrap(); let mut validator_args_tree = - self.check_validator_args(&fun.arguments, true, air_tree_fun, &src_code, &lines); + self.check_validator_args(&fun.arguments, true, air_tree_fun, src_code, lines); validator_args_tree = AirTree::no_op(validator_args_tree); @@ -162,8 +148,8 @@ impl<'a> CodeGenerator<'a> { &other.arguments, true, air_tree_fun_other, - &src_code, - &lines, + src_code, + lines, ); validator_args_tree_other = AirTree::no_op(validator_args_tree_other); @@ -198,8 +184,13 @@ impl<'a> CodeGenerator<'a> { self.finalize(term) } - pub fn generate_test(&mut self, test_body: &TypedExpr, module_name: &String) -> Program { - let mut air_tree = self.build(test_body, module_name, &[]); + pub fn generate_raw( + &mut self, + body: &TypedExpr, + args: &[TypedArg], + module_name: &str, + ) -> Program { + let mut air_tree = self.build(body, module_name, &[]); air_tree = AirTree::no_op(air_tree); @@ -208,7 +199,13 @@ impl<'a> CodeGenerator<'a> { // optimizations on air tree let full_vec = full_tree.to_vec(); - let term = self.uplc_code_gen(full_vec); + let mut term = self.uplc_code_gen(full_vec); + + term = if args.is_empty() { + term + } else { + cast_validator_args(term, args) + }; self.finalize(term) } @@ -239,7 +236,7 @@ impl<'a> CodeGenerator<'a> { fn build( &mut self, body: &TypedExpr, - module_build_name: &String, + module_build_name: &str, context: &[TypedExpr], ) -> AirTree { if !context.is_empty() { @@ -254,7 +251,7 @@ impl<'a> CodeGenerator<'a> { panic!("Dangling expressions without an assignment") }; - let replaced_type = convert_opaque_type(tipo, &self.data_types); + let replaced_type = convert_opaque_type(tipo, &self.data_types, true); let air_value = self.build(value, module_build_name, &[]); @@ -449,7 +446,7 @@ impl<'a> CodeGenerator<'a> { constructor: ModuleValueConstructor::Fn { name, .. }, .. } => { - let type_info = self.module_types.get(module_name).unwrap(); + let type_info = self.module_types.get(module_name.as_str()).unwrap(); let value = type_info.values.get(name).unwrap(); let ValueConstructorVariant::ModuleFn { builtin, .. } = &value.variant @@ -722,7 +719,7 @@ impl<'a> CodeGenerator<'a> { function_name: name.clone(), }); - let type_info = self.module_types.get(module_name).unwrap(); + let type_info = self.module_types.get(module_name.as_str()).unwrap(); let value = type_info.values.get(name).unwrap(); @@ -894,7 +891,7 @@ impl<'a> CodeGenerator<'a> { if props.full_check { let mut index_map = IndexMap::new(); - let non_opaque_tipo = convert_opaque_type(tipo, &self.data_types); + let non_opaque_tipo = convert_opaque_type(tipo, &self.data_types, true); let val = AirTree::local_var(name, tipo.clone()); @@ -932,7 +929,7 @@ impl<'a> CodeGenerator<'a> { let name = &format!("__discard_expect_{}", name); let mut index_map = IndexMap::new(); - let non_opaque_tipo = convert_opaque_type(tipo, &self.data_types); + let non_opaque_tipo = convert_opaque_type(tipo, &self.data_types, true); let val = AirTree::local_var(name, tipo.clone()); @@ -1325,7 +1322,7 @@ impl<'a> CodeGenerator<'a> { msg_func: Option, ) -> AirTree { assert!(tipo.get_generic().is_none()); - let tipo = &convert_opaque_type(tipo, &self.data_types); + let tipo = &convert_opaque_type(tipo, &self.data_types, true); if tipo.is_primitive() { // Since we would return void anyway and ignore then we can just return value here and ignore @@ -1761,7 +1758,7 @@ impl<'a> CodeGenerator<'a> { final_clause: TypedClause, subject_tipo: &Rc, props: &mut ClauseProperties, - module_name: &String, + module_name: &str, ) -> AirTree { assert!( !subject_tipo.is_void(), @@ -2774,7 +2771,7 @@ impl<'a> CodeGenerator<'a> { let param = AirTree::local_var(&arg_name, data()); - let actual_type = convert_opaque_type(&arg.tipo, &self.data_types); + let actual_type = convert_opaque_type(&arg.tipo, &self.data_types, true); let msg_func = match self.tracing { TraceLevel::Silent => None, @@ -3565,7 +3562,13 @@ impl<'a> CodeGenerator<'a> { let code_gen_func = self .code_gen_functions .get(&generic_function_key.function_name) - .unwrap_or_else(|| panic!("Missing Code Gen Function Definition")); + .unwrap_or_else(|| { + panic!( + "Missing function definition for {}. Known definitions: {:?}", + generic_function_key.function_name, + self.code_gen_functions.keys(), + ) + }); if !dependency_functions .iter() @@ -3625,12 +3628,13 @@ impl<'a> CodeGenerator<'a> { let mut function_def_types = function_def .arguments .iter() - .map(|arg| convert_opaque_type(&arg.tipo, &self.data_types)) + .map(|arg| convert_opaque_type(&arg.tipo, &self.data_types, true)) .collect_vec(); function_def_types.push(convert_opaque_type( &function_def.return_type, &self.data_types, + true, )); let mono_types: IndexMap> = if !function_def_types.is_empty() { @@ -3832,7 +3836,9 @@ impl<'a> CodeGenerator<'a> { } DefaultFunction::MkCons | DefaultFunction::MkPairData => { - unimplemented!("MkCons and MkPairData should be handled by an anon function or using [] or ( a, b, .., z).\n") + unimplemented!( + "MkCons and MkPairData should be handled by an anon function or using [] or ( a, b, .., z).\n" + ) } _ => { let mut term: Term = (*builtin).into(); @@ -4225,7 +4231,9 @@ impl<'a> CodeGenerator<'a> { } DefaultFunction::MkCons | DefaultFunction::MkPairData => { - unimplemented!("MkCons and MkPairData should be handled by an anon function or using [] or ( a, b, .., z).\n") + unimplemented!( + "MkCons and MkPairData should be handled by an anon function or using [] or ( a, b, .., z).\n" + ) } _ => { let mut term: Term = func.into(); diff --git a/crates/aiken-lang/src/gen_uplc/builder.rs b/crates/aiken-lang/src/gen_uplc/builder.rs index 373bbc35..6db18f17 100644 --- a/crates/aiken-lang/src/gen_uplc/builder.rs +++ b/crates/aiken-lang/src/gen_uplc/builder.rs @@ -1,7 +1,21 @@ -use std::{collections::HashMap, ops::Deref, rc::Rc}; - +use super::{ + air::{Air, ExpectLevel}, + tree::{AirMsg, AirTree, TreePath}, +}; +use crate::{ + ast::{ + AssignmentKind, BinOp, ClauseGuard, Constant, DataType, DataTypeKey, FunctionAccessKey, + Pattern, Span, TraceLevel, TypedArg, TypedClause, TypedClauseGuard, TypedDataType, + TypedPattern, UnOp, + }, + builtins::{bool, data, function, int, list, string, void}, + expr::TypedExpr, + line_numbers::{LineColumn, LineNumbers}, + tipo::{PatternConstructor, Type, TypeVar, ValueConstructor, ValueConstructorVariant}, +}; use indexmap::{IndexMap, IndexSet}; use itertools::{Itertools, Position}; +use std::{collections::HashMap, ops::Deref, rc::Rc}; use uplc::{ ast::{Constant as UplcConstant, Name, Term, Type as UplcType}, builder::{CONSTR_FIELDS_EXPOSER, CONSTR_INDEX_EXPOSER}, @@ -13,27 +27,6 @@ use uplc::{ Constr, KeyValuePairs, PlutusData, }; -use crate::{ - ast::{ - AssignmentKind, DataType, Pattern, Span, TraceLevel, TypedArg, TypedClause, - TypedClauseGuard, TypedDataType, TypedPattern, - }, - builtins::{bool, data, function, int, list, string, void}, - expr::TypedExpr, - line_numbers::{LineColumn, LineNumbers}, - tipo::{PatternConstructor, TypeVar, ValueConstructor, ValueConstructorVariant}, -}; - -use crate::{ - ast::{BinOp, ClauseGuard, Constant, UnOp}, - tipo::Type, -}; - -use super::{ - air::{Air, ExpectLevel}, - tree::{AirMsg, AirTree, TreePath}, -}; - pub type Variant = String; pub type Params = Vec; @@ -68,18 +61,6 @@ pub enum HoistableFunction { CyclicLink(FunctionAccessKey), } -#[derive(Clone, Debug, Eq, PartialEq, Hash)] -pub struct DataTypeKey { - pub module_name: String, - pub defined_type: String, -} - -#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub struct FunctionAccessKey { - pub module_name: String, - pub function_name: String, -} - #[derive(Clone, Debug)] pub struct AssignmentProperties { pub value_type: Rc, @@ -312,7 +293,7 @@ pub fn get_generic_id_and_type(tipo: &Type, param: &Type) -> Vec<(u64, Rc) } pub fn lookup_data_type_by_tipo( - data_types: &IndexMap, + data_types: &IndexMap<&DataTypeKey, &TypedDataType>, tipo: &Type, ) -> Option>> { match tipo { @@ -365,7 +346,8 @@ pub fn get_arg_type_name(tipo: &Type) -> String { pub fn convert_opaque_type( t: &Rc, - data_types: &IndexMap, + data_types: &IndexMap<&DataTypeKey, &TypedDataType>, + deep: bool, ) -> Rc { if check_replaceable_opaque_type(t, data_types) && matches!(t.as_ref(), Type::App { .. }) { let data_type = lookup_data_type_by_tipo(data_types, t).unwrap(); @@ -382,7 +364,11 @@ pub fn convert_opaque_type( let mono_type = find_and_replace_generics(generic_type, &mono_types); - convert_opaque_type(&mono_type, data_types) + if deep { + convert_opaque_type(&mono_type, data_types, deep) + } else { + mono_type + } } else { match t.as_ref() { Type::App { @@ -393,7 +379,7 @@ pub fn convert_opaque_type( } => { let mut new_args = vec![]; for arg in args { - let arg = convert_opaque_type(arg, data_types); + let arg = convert_opaque_type(arg, data_types, deep); new_args.push(arg); } Type::App { @@ -407,11 +393,11 @@ pub fn convert_opaque_type( Type::Fn { args, ret } => { let mut new_args = vec![]; for arg in args { - let arg = convert_opaque_type(arg, data_types); + let arg = convert_opaque_type(arg, data_types, deep); new_args.push(arg); } - let ret = convert_opaque_type(ret, data_types); + let ret = convert_opaque_type(ret, data_types, deep); Type::Fn { args: new_args, @@ -421,7 +407,7 @@ pub fn convert_opaque_type( } Type::Var { tipo: var_tipo } => { if let TypeVar::Link { tipo } = &var_tipo.borrow().clone() { - convert_opaque_type(tipo, data_types) + convert_opaque_type(tipo, data_types, deep) } else { t.clone() } @@ -429,7 +415,7 @@ pub fn convert_opaque_type( Type::Tuple { elems } => { let mut new_elems = vec![]; for arg in elems { - let arg = convert_opaque_type(arg, data_types); + let arg = convert_opaque_type(arg, data_types, deep); new_elems.push(arg); } Type::Tuple { elems: new_elems }.into() @@ -439,8 +425,8 @@ pub fn convert_opaque_type( } pub fn check_replaceable_opaque_type( - t: &Rc, - data_types: &IndexMap, + t: &Type, + data_types: &IndexMap<&DataTypeKey, &TypedDataType>, ) -> bool { let data_type = lookup_data_type_by_tipo(data_types, t); @@ -636,7 +622,7 @@ pub fn monomorphize(air_tree: &mut AirTree, mono_types: &IndexMap> pub fn erase_opaque_type_operations( air_tree: &mut AirTree, - data_types: &IndexMap, + data_types: &IndexMap<&DataTypeKey, &TypedDataType>, ) { if let AirTree::Constr { tipo, args, .. } = air_tree { if check_replaceable_opaque_type(tipo, data_types) { @@ -652,7 +638,7 @@ pub fn erase_opaque_type_operations( let mut held_types = air_tree.mut_held_types(); while let Some(tipo) = held_types.pop() { - *tipo = convert_opaque_type(tipo, data_types); + *tipo = convert_opaque_type(tipo, data_types, true); } } @@ -917,7 +903,7 @@ pub fn modify_cyclic_calls( pub fn pattern_has_conditions( pattern: &TypedPattern, - data_types: &IndexMap, + data_types: &IndexMap<&DataTypeKey, &TypedDataType>, ) -> bool { match pattern { Pattern::List { .. } | Pattern::Int { .. } => true, @@ -943,7 +929,7 @@ pub fn pattern_has_conditions( // TODO: write some tests pub fn rearrange_list_clauses( clauses: Vec, - data_types: &IndexMap, + data_types: &IndexMap<&DataTypeKey, &TypedDataType>, ) -> Vec { let mut sorted_clauses = clauses; @@ -1181,7 +1167,7 @@ pub fn find_list_clause_or_default_first(clauses: &[TypedClause]) -> &TypedClaus .unwrap_or(&clauses[0]) } -pub fn convert_data_to_type(term: Term, field_type: &Rc) -> Term { +pub fn convert_data_to_type(term: Term, field_type: &Type) -> Term { if field_type.is_int() { Term::un_i_data().apply(term) } else if field_type.is_bytearray() { @@ -1222,7 +1208,7 @@ pub fn convert_data_to_type(term: Term, field_type: &Rc) -> Term, - field_type: &Rc, + field_type: &Type, error_term: Term, ) -> Term { if field_type.is_int() { @@ -1494,9 +1480,9 @@ pub fn convert_type_to_data(term: Term, field_type: &Rc) -> Term) -> Option> { } pub fn get_src_code_by_span( - module_name: &String, + module_name: &str, span: &Span, - module_src: &IndexMap, + module_src: &IndexMap<&str, &(String, LineNumbers)>, ) -> String { let (src, _) = module_src .get(module_name) @@ -1969,9 +1955,9 @@ pub fn get_src_code_by_span( } pub fn get_line_columns_by_span( - module_name: &String, + module_name: &str, span: &Span, - module_src: &IndexMap, + module_src: &IndexMap<&str, &(String, LineNumbers)>, ) -> LineColumn { let (_, lines) = module_src .get(module_name) diff --git a/crates/aiken-lang/src/parser/definition/snapshots/def_invalid_property_test.snap b/crates/aiken-lang/src/parser/definition/snapshots/def_invalid_property_test.snap new file mode 100644 index 00000000..e7c37498 --- /dev/null +++ b/crates/aiken-lang/src/parser/definition/snapshots/def_invalid_property_test.snap @@ -0,0 +1,59 @@ +--- +source: crates/aiken-lang/src/parser/definition/test.rs +description: "Code:\n\ntest foo(x via f, y via g) {\n True\n}\n" +--- +Test( + Function { + arguments: [ + ArgVia { + arg_name: Named { + name: "x", + label: "x", + location: 9..10, + is_validator_param: false, + }, + location: 9..10, + via: Var { + location: 15..16, + name: "f", + }, + tipo: (), + annotation: None, + }, + ArgVia { + arg_name: Named { + name: "y", + label: "y", + location: 18..19, + is_validator_param: false, + }, + location: 18..19, + via: Var { + location: 24..25, + name: "g", + }, + tipo: (), + annotation: None, + }, + ], + body: Var { + location: 33..37, + name: "True", + }, + doc: None, + location: 0..26, + name: "foo", + public: false, + return_annotation: Some( + Constructor { + location: 0..39, + module: None, + name: "Bool", + arguments: [], + }, + ), + return_type: (), + end_position: 38, + can_error: false, + }, +) diff --git a/crates/aiken-lang/src/parser/definition/snapshots/def_property_test.snap b/crates/aiken-lang/src/parser/definition/snapshots/def_property_test.snap new file mode 100644 index 00000000..db3b686e --- /dev/null +++ b/crates/aiken-lang/src/parser/definition/snapshots/def_property_test.snap @@ -0,0 +1,48 @@ +--- +source: crates/aiken-lang/src/parser/definition/test.rs +description: "Code:\n\ntest foo(x via fuzz.any_int) {\n True\n}\n" +--- +Test( + Function { + arguments: [ + ArgVia { + arg_name: Named { + name: "x", + label: "x", + location: 9..10, + is_validator_param: false, + }, + location: 9..10, + via: FieldAccess { + location: 15..27, + label: "any_int", + container: Var { + location: 15..19, + name: "fuzz", + }, + }, + tipo: (), + annotation: None, + }, + ], + body: Var { + location: 35..39, + name: "True", + }, + doc: None, + location: 0..28, + name: "foo", + public: false, + return_annotation: Some( + Constructor { + location: 0..41, + module: None, + name: "Bool", + arguments: [], + }, + ), + return_type: (), + end_position: 40, + can_error: false, + }, +) diff --git a/crates/aiken-lang/src/parser/definition/snapshots/def_property_test_annotated_fuzzer.snap b/crates/aiken-lang/src/parser/definition/snapshots/def_property_test_annotated_fuzzer.snap new file mode 100644 index 00000000..295bde81 --- /dev/null +++ b/crates/aiken-lang/src/parser/definition/snapshots/def_property_test_annotated_fuzzer.snap @@ -0,0 +1,55 @@ +--- +source: crates/aiken-lang/src/parser/definition/test.rs +description: "Code:\n\ntest foo(x: Int via foo()) {\n True\n}\n" +--- +Test( + Function { + arguments: [ + ArgVia { + arg_name: Named { + name: "x", + label: "x", + location: 9..10, + is_validator_param: false, + }, + location: 9..15, + via: Call { + arguments: [], + fun: Var { + location: 20..23, + name: "foo", + }, + location: 20..25, + }, + tipo: (), + annotation: Some( + Constructor { + location: 12..15, + module: None, + name: "Int", + arguments: [], + }, + ), + }, + ], + body: Var { + location: 33..37, + name: "True", + }, + doc: None, + location: 0..26, + name: "foo", + public: false, + return_annotation: Some( + Constructor { + location: 0..39, + module: None, + name: "Bool", + arguments: [], + }, + ), + return_type: (), + end_position: 38, + can_error: false, + }, +) diff --git a/crates/aiken-lang/src/parser/definition/snapshots/def_test.snap b/crates/aiken-lang/src/parser/definition/snapshots/def_test.snap new file mode 100644 index 00000000..3ddb5121 --- /dev/null +++ b/crates/aiken-lang/src/parser/definition/snapshots/def_test.snap @@ -0,0 +1,28 @@ +--- +source: crates/aiken-lang/src/parser/definition/test.rs +description: "Code:\n\ntest foo() {\n True\n}\n" +--- +Test( + Function { + arguments: [], + body: Var { + location: 17..21, + name: "True", + }, + doc: None, + location: 0..10, + name: "foo", + public: false, + return_annotation: Some( + Constructor { + location: 0..23, + module: None, + name: "Bool", + arguments: [], + }, + ), + return_type: (), + end_position: 22, + can_error: false, + }, +) diff --git a/crates/aiken-lang/src/parser/definition/snapshots/def_test_fail.snap b/crates/aiken-lang/src/parser/definition/snapshots/def_test_fail.snap index a6e3e07b..a2da8f18 100644 --- a/crates/aiken-lang/src/parser/definition/snapshots/def_test_fail.snap +++ b/crates/aiken-lang/src/parser/definition/snapshots/def_test_fail.snap @@ -37,7 +37,14 @@ Test( location: 0..26, name: "invalid_inputs", public: false, - return_annotation: None, + return_annotation: Some( + Constructor { + location: 0..61, + module: None, + name: "Bool", + arguments: [], + }, + ), return_type: (), end_position: 60, can_error: true, diff --git a/crates/aiken-lang/src/parser/definition/test.rs b/crates/aiken-lang/src/parser/definition/test.rs index 6691edb2..726d68f8 100644 --- a/crates/aiken-lang/src/parser/definition/test.rs +++ b/crates/aiken-lang/src/parser/definition/test.rs @@ -3,7 +3,13 @@ use chumsky::prelude::*; use crate::{ ast, expr::UntypedExpr, - parser::{error::ParseError, expr, token::Token}, + parser::{ + annotation, + chain::{call::parser as call, field_access, tuple_index::parser as tuple_index, Chain}, + error::ParseError, + expr::{self, var}, + token::Token, + }, }; pub fn parser() -> impl Parser { @@ -13,8 +19,12 @@ pub fn parser() -> impl Parser name}) - .then_ignore(just(Token::LeftParen)) - .then_ignore(just(Token::RightParen)) + .then( + via() + .separated_by(just(Token::Comma)) + .allow_trailing() + .delimited_by(just(Token::LeftParen), just(Token::RightParen)), + ) .then(just(Token::Fail).ignored().or_not()) .map_with_span(|name, span| (name, span)) .then( @@ -22,26 +32,88 @@ pub fn parser() -> impl Parser impl Parser { + choice(( + select! {Token::DiscardName {name} => name}.map_with_span(|name, span| { + ast::ArgName::Discarded { + label: name.clone(), name, - public: false, - return_annotation: None, - return_type: (), - can_error: fail.is_some() || old_fail.is_some(), + location: span, + } + }), + select! {Token::Name {name} => name}.map_with_span(move |name, location| { + ast::ArgName::Named { + label: name.clone(), + name, + location, + is_validator_param: false, + } + }), + )) + .then(just(Token::Colon).ignore_then(annotation()).or_not()) + .map_with_span(|(arg_name, annotation), location| (arg_name, annotation, location)) + .then_ignore(just(Token::Via)) + .then(fuzzer()) + .map(|((arg_name, annotation, location), via)| ast::ArgVia { + arg_name, + via, + annotation, + tipo: (), + location, + }) +} + +pub fn fuzzer<'a>() -> impl Parser + 'a { + recursive(|expression| { + let chain = choice(( + tuple_index(), + field_access::parser(), + call(expression.clone()), + )); + + var() + .then(chain.repeated()) + .foldl(|expr, chain| match chain { + Chain::Call(args, span) => expr.call(args, span), + Chain::FieldAccess(label, span) => expr.field_access(label, span), + Chain::TupleIndex(index, span) => expr.tuple_index(index, span), }) - }) + }) } #[cfg(test)] mod tests { use crate::assert_definition; + #[test] + fn def_test() { + assert_definition!( + r#" + test foo() { + True + } + "# + ); + } + #[test] fn def_test_fail() { assert_definition!( @@ -54,4 +126,37 @@ mod tests { "# ); } + + #[test] + fn def_property_test() { + assert_definition!( + r#" + test foo(x via fuzz.any_int) { + True + } + "# + ); + } + + #[test] + fn def_invalid_property_test() { + assert_definition!( + r#" + test foo(x via f, y via g) { + True + } + "# + ); + } + + #[test] + fn def_property_test_annotated_fuzzer() { + assert_definition!( + r#" + test foo(x: Int via foo()) { + True + } + "# + ); + } } diff --git a/crates/aiken-lang/src/parser/expr/record.rs b/crates/aiken-lang/src/parser/expr/record.rs index 5cc4cc25..fe12ad74 100644 --- a/crates/aiken-lang/src/parser/expr/record.rs +++ b/crates/aiken-lang/src/parser/expr/record.rs @@ -187,6 +187,11 @@ pub fn parser( mod tests { use crate::assert_expr; + #[test] + fn record_enum() { + assert_expr!(r#"Winter"#); + } + #[test] fn record_create_labeled() { assert_expr!(r#"User { name: "Aiken", age, thing: 2 }"#); diff --git a/crates/aiken-lang/src/parser/expr/snapshots/record_enum.snap b/crates/aiken-lang/src/parser/expr/snapshots/record_enum.snap new file mode 100644 index 00000000..8885f178 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/record_enum.snap @@ -0,0 +1,8 @@ +--- +source: crates/aiken-lang/src/parser/expr/record.rs +description: "Code:\n\nWinter" +--- +Var { + location: 0..6, + name: "Winter", +} diff --git a/crates/aiken-lang/src/parser/lexer.rs b/crates/aiken-lang/src/parser/lexer.rs index 9dea075a..3e0df9cf 100644 --- a/crates/aiken-lang/src/parser/lexer.rs +++ b/crates/aiken-lang/src/parser/lexer.rs @@ -240,6 +240,7 @@ pub fn lexer() -> impl Parser, Error = ParseError> { "type" => Token::Type, "when" => Token::When, "validator" => Token::Validator, + "via" => Token::Via, _ => { if s.chars().next().map_or(false, |c| c.is_uppercase()) { Token::UpName { diff --git a/crates/aiken-lang/src/parser/token.rs b/crates/aiken-lang/src/parser/token.rs index cae2665b..d48e1179 100644 --- a/crates/aiken-lang/src/parser/token.rs +++ b/crates/aiken-lang/src/parser/token.rs @@ -89,6 +89,7 @@ pub enum Token { When, Trace, Validator, + Via, } impl fmt::Display for Token { @@ -176,6 +177,7 @@ impl fmt::Display for Token { Token::Test => "test", Token::Fail => "fail", Token::Validator => "validator", + Token::Via => "via", }; write!(f, "\"{s}\"") } diff --git a/crates/aiken-lang/src/tests/check.rs b/crates/aiken-lang/src/tests/check.rs index d7bc8eb7..3f743156 100644 --- a/crates/aiken-lang/src/tests/check.rs +++ b/crates/aiken-lang/src/tests/check.rs @@ -50,6 +50,33 @@ fn check_validator( check_module(ast, ModuleKind::Validator) } +#[test] +fn bls12_381_elements_in_data_type() { + let source_code = r#" + type Datum { + D0(G1Element) + D1(G2Element) + } + "#; + + assert!(check(parse(source_code)).is_ok()) +} + +#[test] +fn bls12_381_ml_result_in_data_type() { + let source_code = r#" + type Datum { + thing: MillerLoopResult + } + "#; + + let res = check(parse(source_code)); + + dbg!(&res); + + assert!(matches!(res, Err((_, Error::IllegalTypeInData { .. })))) +} + #[test] fn validator_illegal_return_type() { let source_code = r#" @@ -1175,6 +1202,113 @@ fn pipe_with_wrong_type_and_full_args() { )) } +#[test] +fn fuzzer_ok_basic() { + let source_code = r#" + fn int() -> Fuzzer { todo } + + test prop(n via int()) { todo } + "#; + + assert!(check(parse(source_code)).is_ok()); +} + +#[test] +fn fuzzer_ok_explicit() { + let source_code = r#" + fn int(prng: PRNG) -> Option<(PRNG, Int)> { todo } + + test prop(n via int) { todo } + "#; + + assert!(check(parse(source_code)).is_ok()); +} + +#[test] +fn fuzzer_ok_list() { + let source_code = r#" + fn int() -> Fuzzer { todo } + fn list(a: Fuzzer) -> Fuzzer> { todo } + + test prop(xs via list(int())) { todo } + "#; + + assert!(check(parse(source_code)).is_ok()); +} + +#[test] +fn fuzzer_err_unbound() { + let source_code = r#" + fn any() -> Fuzzer { todo } + fn list(a: Fuzzer) -> Fuzzer> { todo } + + test prop(xs via list(any())) { todo } + "#; + + assert!(matches!( + check(parse(source_code)), + Err((_, Error::GenericLeftAtBoundary { .. })) + )) +} + +#[test] +fn fuzzer_err_unify_1() { + let source_code = r#" + test prop(xs via Void) { todo } + "#; + + assert!(matches!( + check(parse(source_code)), + Err(( + _, + Error::CouldNotUnify { + situation: None, + .. + } + )) + )) +} + +#[test] +fn fuzzer_err_unify_2() { + let source_code = r#" + fn any() -> Fuzzer { todo } + test prop(xs via any) { todo } + "#; + + assert!(matches!( + check(parse(source_code)), + Err(( + _, + Error::CouldNotUnify { + situation: None, + .. + } + )) + )) +} + +#[test] +fn fuzzer_err_unify_3() { + let source_code = r#" + fn list(a: Fuzzer) -> Fuzzer> { todo } + fn int() -> Fuzzer { todo } + + test prop(xs: Int via list(int())) { todo } + "#; + + assert!(matches!( + check(parse(source_code)), + Err(( + _, + Error::CouldNotUnify { + situation: Some(UnifyErrorSituation::FuzzerAnnotationMismatch), + .. + } + )) + )) +} + #[test] fn utf8_hex_literal_warning() { let source_code = r#" diff --git a/crates/aiken-lang/src/tipo.rs b/crates/aiken-lang/src/tipo.rs index 0f180ea5..c1ee9711 100644 --- a/crates/aiken-lang/src/tipo.rs +++ b/crates/aiken-lang/src/tipo.rs @@ -57,6 +57,18 @@ pub enum Type { } impl Type { + pub fn qualifier(&self) -> Option<(String, String)> { + match self { + Type::App { module, name, .. } => Some((module.to_string(), name.to_string())), + Type::Fn { .. } => None, + Type::Var { ref tipo } => match &*tipo.borrow() { + TypeVar::Link { ref tipo } => tipo.qualifier(), + _ => None, + }, + Type::Tuple { .. } => Some((String::new(), "Tuple".to_string())), + } + } + pub fn is_result_constructor(&self) -> bool { match self { Type::Fn { ret, .. } => ret.is_result(), diff --git a/crates/aiken-lang/src/tipo/environment.rs b/crates/aiken-lang/src/tipo/environment.rs index 3ba8e904..8bb9b383 100644 --- a/crates/aiken-lang/src/tipo/environment.rs +++ b/crates/aiken-lang/src/tipo/environment.rs @@ -10,7 +10,7 @@ use crate::{ RecordConstructor, RecordConstructorArg, Span, TypeAlias, TypedDefinition, TypedPattern, UnqualifiedImport, UntypedArg, UntypedDefinition, Use, Validator, PIPE_VARIABLE, }, - builtins::{self, function, generic_var, tuple, unbound_var}, + builtins::{function, generic_var, tuple, unbound_var}, tipo::fields::FieldMap, IdGenerator, }; @@ -1185,23 +1185,22 @@ impl<'a> Environment<'a> { }) } - Definition::Test(Function { name, location, .. }) => { - assert_unique_value_name(names, name, location)?; - hydrators.insert(name.clone(), Hydrator::new()); - let arg_types = vec![]; - let return_type = builtins::bool(); - self.insert_variable( - name.clone(), - ValueConstructorVariant::ModuleFn { - name: name.clone(), - field_map: None, - module: module_name.to_owned(), - arity: 0, - location: *location, - builtin: None, - }, - function(arg_types, return_type), - ); + Definition::Test(test) => { + let arguments = test + .arguments + .iter() + .map(|arg| arg.clone().into()) + .collect::>(); + + self.register_function( + &test.name, + &arguments, + &test.return_annotation, + module_name, + hydrators, + names, + &test.location, + )?; } Definition::DataType(DataType { diff --git a/crates/aiken-lang/src/tipo/error.rs b/crates/aiken-lang/src/tipo/error.rs index 450a7218..c8c49c21 100644 --- a/crates/aiken-lang/src/tipo/error.rs +++ b/crates/aiken-lang/src/tipo/error.rs @@ -1,7 +1,7 @@ use super::Type; -use crate::error::ExtraData; use crate::{ ast::{Annotation, BinOp, CallArg, LogicalOpChainKind, Span, UntypedPattern}, + error::ExtraData, expr::{self, UntypedExpr}, format::Formatter, levenshtein, @@ -259,14 +259,28 @@ You can use '{discard}' and numbers to distinguish between similar names. name: String, }, - #[error("I found a data type that has a function type in it. This is not allowed.\n")] + #[error("I found a type definition that has a function type in it. This is not allowed.\n")] #[diagnostic(code("illegal::function_in_type"))] - #[diagnostic(help("Data-types can't hold functions. If you want to define method-like functions, group the type definition and the methods under a common namespace in a standalone module."))] + #[diagnostic(help( + "Data-types can't hold functions. If you want to define method-like functions, group the type definition and the methods under a common namespace in a standalone module." + ))] FunctionTypeInData { #[label] location: Span, }, + #[error("I found a type definition that has an unsupported type in it.\n")] + #[diagnostic(code("illegal::type_in_data"))] + #[diagnostic(help( + r#"Data-types can't contain type {type_info} because it isn't serializable into a Plutus Data. Yet, this is a strong requirement for types found in compound structures such as List or Tuples."#, + type_info = tipo.to_pretty(0).if_supports_color(Stdout, |s| s.red()) + ))] + IllegalTypeInData { + #[label] + location: Span, + tipo: Rc, + }, + #[error("I found a discarded expression not bound to a variable.\n")] #[diagnostic(code("implicit_discard"))] #[diagnostic(help( @@ -465,9 +479,13 @@ If you really meant to return that last expression, try to replace it with the f "I stumbled upon an invalid (non-local) clause guard '{}'.\n", name.if_supports_color(Stdout, |s| s.purple()) )] - #[diagnostic(url("https://aiken-lang.org/language-tour/control-flow#checking-equality-and-ordering-in-patterns"))] + #[diagnostic(url( + "https://aiken-lang.org/language-tour/control-flow#checking-equality-and-ordering-in-patterns" + ))] #[diagnostic(code("illegal::clause_guard"))] - #[diagnostic(help("There are some conditions regarding what can be used in a guard. Values must be either local to the function, or defined as module constants. You can't use functions or records in there."))] + #[diagnostic(help( + "There are some conditions regarding what can be used in a guard. Values must be either local to the function, or defined as module constants. You can't use functions or records in there." + ))] NonLocalClauseGuardVariable { #[label] location: Span, @@ -480,7 +498,7 @@ If you really meant to return that last expression, try to replace it with the f #[diagnostic(url("https://aiken-lang.org/language-tour/primitive-types#tuples"))] #[diagnostic(code("illegal::tuple_index"))] #[diagnostic(help( - r#"Because you used a tuple-index on an element, I assumed it had to be a tuple or some kind, but instead I found: + r#"Because you used a tuple-index on an element, I assumed it had to be a tuple but instead I found something of type: ╰─▶ {type_info}"#, type_info = tipo.to_pretty(0).if_supports_color(Stdout, |s| s.red()) @@ -625,7 +643,9 @@ You can help me by providing a type-annotation for 'x', as such: #[error("I almost got caught in an endless loop while inferring a recursive type.\n")] #[diagnostic(url("https://aiken-lang.org/language-tour/custom-types#type-annotations"))] #[diagnostic(code("missing::type_annotation"))] - #[diagnostic(help("I have several aptitudes, but inferring recursive types isn't one them. It is still possible to define recursive types just fine, but I will need a little help in the form of type annotation to infer their types should they show up."))] + #[diagnostic(help( + "I have several aptitudes, but inferring recursive types isn't one them. It is still possible to define recursive types just fine, but I will need a little help in the form of type annotation to infer their types should they show up." + ))] RecursiveType { #[label] location: Span, @@ -934,6 +954,27 @@ The best thing to do from here is to remove it."#))] #[label("{} arguments", if *count < 2 { "not enough" } else { "too many" })] location: Span, }, + + #[error("I caught a test with too many arguments.\n")] + #[diagnostic(code("illegal::test_arity"))] + #[diagnostic(help( + "Tests are allowed to have 0 or 1 argument, but no more. Here I've found a test definition with {count} arguments. If you need to provide multiple values to a test, use a Record or a Tuple.", + ))] + IncorrectTestArity { + count: usize, + #[label("too many arguments")] + location: Span, + }, + + #[error("I choked on a generic type left in an outward-facing interface.\n")] + #[diagnostic(code("illegal::generic_in_abi"))] + #[diagnostic(help( + "Functions of the outer-most parts of a project, such as a validator or a property-based test, must be fully instantiated. That means they can no longer carry unbound generic variables. The type must be fully-known at this point since many structural validation must occur to ensure a safe boundary between the on-chain and off-chain worlds." + ))] + GenericLeftAtBoundary { + #[label("unbound generic at boundary")] + location: Span, + }, } impl ExtraData for Error { @@ -951,6 +992,7 @@ impl ExtraData for Error { | Error::DuplicateVarInPattern { .. } | Error::ExtraVarInAlternativePattern { .. } | Error::FunctionTypeInData { .. } + | Error::IllegalTypeInData { .. } | Error::ImplicitlyDiscardedExpression { .. } | Error::IncorrectFieldsArity { .. } | Error::IncorrectFunctionCallArity { .. } @@ -984,6 +1026,8 @@ impl ExtraData for Error { | Error::UnnecessarySpreadOperator { .. } | Error::UpdateMultiConstructorType { .. } | Error::ValidatorImported { .. } + | Error::IncorrectTestArity { .. } + | Error::GenericLeftAtBoundary { .. } | Error::ValidatorMustReturnBool { .. } => None, Error::UnknownType { name, .. } @@ -1181,14 +1225,14 @@ fn suggest_unify( ( format!( - "{} - {}", + "{}.{{{}}}", + expected_module.if_supports_color(Stdout, |s| s.bright_blue()), expected_str.if_supports_color(Stdout, |s| s.green()), - expected_module.if_supports_color(Stdout, |s| s.bright_blue()) ), format!( - "{} - {}", + "{}.{{{}}}", + given_module.if_supports_color(Stdout, |s| s.bright_blue()), given_str.if_supports_color(Stdout, |s| s.red()), - given_module.if_supports_color(Stdout, |s| s.bright_blue()) ), ) } @@ -1263,6 +1307,21 @@ fn suggest_unify( expected, given }, + Some(UnifyErrorSituation::FuzzerAnnotationMismatch) => formatdoc! { + r#"While comparing the return annotation of a Fuzzer with its actual return type, I realized that both don't match. + + I am inferring the Fuzzer should return: + + {} + + but I found a conflicting annotation saying it returns: + + {} + + Either, fix (or remove) the annotation or adjust the Fuzzer to return the expected type."#, + expected, + given + }, None => formatdoc! { r#"I am inferring the following type: @@ -1652,6 +1711,8 @@ pub enum UnifyErrorSituation { /// The operands of a binary operator were incorrect. Operator(BinOp), + + FuzzerAnnotationMismatch, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index 83e55f33..b7bee950 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -1860,7 +1860,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { } } - fn infer_value_constructor( + pub fn infer_value_constructor( &mut self, module: &Option, name: &str, diff --git a/crates/aiken-lang/src/tipo/infer.rs b/crates/aiken-lang/src/tipo/infer.rs index 5dbea954..f81baf05 100644 --- a/crates/aiken-lang/src/tipo/infer.rs +++ b/crates/aiken-lang/src/tipo/infer.rs @@ -1,24 +1,25 @@ -use std::collections::HashMap; - -use crate::{ - ast::{ - ArgName, DataType, Definition, Function, Layer, ModuleConstant, ModuleKind, - RecordConstructor, RecordConstructorArg, Tracing, TypeAlias, TypedDefinition, - TypedFunction, TypedModule, UntypedDefinition, UntypedModule, Use, Validator, - }, - builtins, - builtins::function, - line_numbers::LineNumbers, - IdGenerator, -}; - use super::{ environment::{generalise, EntityKind, Environment}, - error::{Error, Warning}, + error::{Error, UnifyErrorSituation, Warning}, expr::ExprTyper, hydrator::Hydrator, TypeInfo, ValueConstructor, ValueConstructorVariant, }; +use crate::{ + ast::{ + Annotation, Arg, ArgName, ArgVia, DataType, Definition, Function, Layer, ModuleConstant, + ModuleKind, RecordConstructor, RecordConstructorArg, Tracing, TypeAlias, TypedArg, + TypedDefinition, TypedFunction, TypedModule, UntypedArg, UntypedDefinition, UntypedModule, + Use, Validator, + }, + builtins, + builtins::{function, fuzzer, generic_var}, + expr::{TypedExpr, UntypedExpr}, + line_numbers::LineNumbers, + tipo::{Span, Type, TypeVar}, + IdGenerator, +}; +use std::{borrow::Borrow, collections::HashMap, ops::Deref, rc::Rc}; impl UntypedModule { pub fn infer( @@ -159,97 +160,14 @@ fn infer_definition( tracing: Tracing, ) -> Result { match def { - Definition::Fn(Function { - doc, - location, - name, - public, - arguments: args, - body, - return_annotation, - end_position, - can_error, - .. - }) => { - let preregistered_fn = environment - .get_variable(&name) - .expect("Could not find preregistered type for function"); - - let field_map = preregistered_fn.field_map().cloned(); - - let preregistered_type = preregistered_fn.tipo.clone(); - - let (args_types, return_type) = preregistered_type - .function_types() - .expect("Preregistered type for fn was not a fn"); - - // Infer the type using the preregistered args + return types as a starting point - let (tipo, args, body, safe_to_generalise) = - environment.in_new_scope(|environment| { - let args = args - .into_iter() - .zip(&args_types) - .map(|(arg_name, tipo)| arg_name.set_type(tipo.clone())) - .collect(); - - let mut expr_typer = ExprTyper::new(environment, lines, tracing); - - expr_typer.hydrator = hydrators - .remove(&name) - .expect("Could not find hydrator for fn"); - - let (args, body) = - expr_typer.infer_fn_with_known_types(args, body, Some(return_type))?; - - let args_types = args.iter().map(|a| a.tipo.clone()).collect(); - - let tipo = function(args_types, body.tipo()); - - let safe_to_generalise = !expr_typer.ungeneralised_function_used; - - Ok::<_, Error>((tipo, args, body, safe_to_generalise)) - })?; - - // Assert that the inferred type matches the type of any recursive call - environment.unify(preregistered_type, tipo.clone(), location, false)?; - - // Generalise the function if safe to do so - let tipo = if safe_to_generalise { - environment.ungeneralised_functions.remove(&name); - - let tipo = generalise(tipo, 0); - - let module_fn = ValueConstructorVariant::ModuleFn { - name: name.clone(), - field_map, - module: module_name.to_owned(), - arity: args.len(), - location, - builtin: None, - }; - - environment.insert_variable(name.clone(), module_fn, tipo.clone()); - - tipo - } else { - tipo - }; - - Ok(Definition::Fn(Function { - doc, - location, - name, - public, - arguments: args, - return_annotation, - return_type: tipo - .return_type() - .expect("Could not find return type for fn"), - body, - can_error, - end_position, - })) - } + Definition::Fn(f) => Ok(Definition::Fn(infer_function( + f, + module_name, + hydrators, + environment, + lines, + tracing, + )?)), Definition::Validator(Validator { doc, @@ -412,20 +330,105 @@ fn infer_definition( } Definition::Test(f) => { - if let Definition::Fn(f) = infer_definition( - Definition::Fn(f), + let (typed_via, annotation) = match f.arguments.first() { + Some(arg) => { + if f.arguments.len() > 1 { + return Err(Error::IncorrectTestArity { + count: f.arguments.len(), + location: f.arguments.get(1).expect("arguments.len() > 1").location, + }); + } + + let typed_via = + ExprTyper::new(environment, lines, tracing).infer(arg.via.clone())?; + + let (inferred_annotation, inner_type) = + infer_fuzzer(environment, &typed_via.tipo(), &arg.via.location())?; + + // Replace the pre-registered type for the test function, to allow inferring + // the function body with the right type arguments. + let scope = environment + .scope + .get_mut(&f.name) + .expect("Could not find preregistered type for test"); + if let Type::Fn { ref ret, .. } = scope.tipo.as_ref() { + scope.tipo = Rc::new(Type::Fn { + ret: ret.clone(), + args: vec![inner_type.clone()], + }) + } + + // Ensure that the annotation, if any, matches the type inferred from the + // Fuzzer. + if let Some(ref provided_annotation) = arg.annotation { + let hydrator: &mut Hydrator = hydrators.get_mut(&f.name).unwrap(); + + let given = + hydrator.type_from_annotation(provided_annotation, environment)?; + + if !provided_annotation.is_logically_equal(&inferred_annotation) { + return Err(Error::CouldNotUnify { + location: arg.location, + expected: inner_type.clone(), + given, + situation: Some(UnifyErrorSituation::FuzzerAnnotationMismatch), + rigid_type_names: hydrator.rigid_names(), + }); + } + } + + Ok((Some((typed_via, inner_type)), Some(inferred_annotation))) + } + None => Ok((None, None)), + }?; + + let typed_f = infer_function( + f.into(), module_name, hydrators, environment, lines, tracing, - )? { - environment.unify(f.return_type.clone(), builtins::bool(), f.location, false)?; + )?; - Ok(Definition::Test(f)) - } else { - unreachable!("test definition inferred as something other than a function?") - } + environment.unify( + typed_f.return_type.clone(), + builtins::bool(), + typed_f.location, + false, + )?; + + Ok(Definition::Test(Function { + doc: typed_f.doc, + location: typed_f.location, + name: typed_f.name, + public: typed_f.public, + arguments: match typed_via { + Some((via, tipo)) => { + let Arg { + arg_name, location, .. + } = typed_f + .arguments + .first() + .expect("has exactly one argument") + .to_owned(); + + vec![ArgVia { + annotation, + arg_name, + location, + tipo, + via, + }] + } + None => vec![], + }, + return_annotation: typed_f.return_annotation, + return_type: typed_f.return_type, + body: typed_f.body, + can_error: typed_f.can_error, + end_position: typed_f.end_position, + })) } Definition::TypeAlias(TypeAlias { @@ -545,6 +548,13 @@ fn infer_definition( location: *location, }); } + + if tipo.is_ml_result() { + return Err(Error::IllegalTypeInData { + location: *location, + tipo: tipo.clone(), + }); + } } } @@ -633,3 +643,204 @@ fn infer_definition( } } } + +fn infer_function( + f: Function<(), UntypedExpr, UntypedArg>, + module_name: &String, + hydrators: &mut HashMap, + environment: &mut Environment<'_>, + lines: &LineNumbers, + tracing: Tracing, +) -> Result, TypedExpr, TypedArg>, Error> { + let Function { + doc, + location, + name, + public, + arguments, + body, + return_annotation, + end_position, + can_error, + .. + } = f; + + let preregistered_fn = environment + .get_variable(&name) + .expect("Could not find preregistered type for function"); + + let field_map = preregistered_fn.field_map().cloned(); + + let preregistered_type = preregistered_fn.tipo.clone(); + + let (args_types, return_type) = preregistered_type + .function_types() + .expect("Preregistered type for fn was not a fn"); + + // Infer the type using the preregistered args + return types as a starting point + let (tipo, arguments, body, safe_to_generalise) = environment.in_new_scope(|environment| { + let args = arguments + .into_iter() + .zip(&args_types) + .map(|(arg_name, tipo)| arg_name.set_type(tipo.clone())) + .collect(); + + let mut expr_typer = ExprTyper::new(environment, lines, tracing); + + expr_typer.hydrator = hydrators + .remove(&name) + .expect("Could not find hydrator for fn"); + + let (args, body) = expr_typer.infer_fn_with_known_types(args, body, Some(return_type))?; + + let args_types = args.iter().map(|a| a.tipo.clone()).collect(); + + let tipo = function(args_types, body.tipo()); + + let safe_to_generalise = !expr_typer.ungeneralised_function_used; + + Ok::<_, Error>((tipo, args, body, safe_to_generalise)) + })?; + + // Assert that the inferred type matches the type of any recursive call + environment.unify(preregistered_type, tipo.clone(), location, false)?; + + // Generalise the function if safe to do so + let tipo = if safe_to_generalise { + environment.ungeneralised_functions.remove(&name); + + let tipo = generalise(tipo, 0); + + let module_fn = ValueConstructorVariant::ModuleFn { + name: name.clone(), + field_map, + module: module_name.to_owned(), + arity: arguments.len(), + location, + builtin: None, + }; + + environment.insert_variable(name.clone(), module_fn, tipo.clone()); + + tipo + } else { + tipo + }; + + Ok(Function { + doc, + location, + name, + public, + arguments, + return_annotation, + return_type: tipo + .return_type() + .expect("Could not find return type for fn"), + body, + can_error, + end_position, + }) +} + +fn infer_fuzzer( + environment: &mut Environment<'_>, + tipo: &Rc, + location: &Span, +) -> Result<(Annotation, Rc), Error> { + let could_not_unify = || Error::CouldNotUnify { + location: *location, + expected: fuzzer(generic_var(0)), + given: tipo.clone(), + situation: None, + rigid_type_names: HashMap::new(), + }; + + match tipo.borrow() { + Type::Fn { ret, .. } => match ret.borrow() { + Type::App { + module, name, args, .. + } if module.is_empty() && name == "Option" && args.len() == 1 => { + match args.first().expect("args.len() == 1").borrow() { + Type::Tuple { elems } if elems.len() == 2 => { + let wrapped = elems.get(1).expect("Tuple has two elements"); + + // NOTE: Although we've drilled through the Fuzzer structure to get here, + // we still need to enforce that: + // + // 1. The Fuzzer is a function with a single argument of type PRNG + // 2. It returns not only a wrapped type, but also a new PRNG + // + // All-in-all, we could bundle those verification through the + // `infer_fuzzer` function, but instead, we can also just piggyback on + // `unify` now that we have figured out the type carried by the fuzzer. + environment.unify( + tipo.clone(), + fuzzer(wrapped.clone()), + *location, + false, + )?; + + Ok((annotate_fuzzer(wrapped, location)?, wrapped.clone())) + } + _ => Err(could_not_unify()), + } + } + _ => Err(could_not_unify()), + }, + + Type::Var { tipo } => match &*tipo.deref().borrow() { + TypeVar::Link { tipo } => infer_fuzzer(environment, tipo, location), + _ => Err(Error::GenericLeftAtBoundary { + location: *location, + }), + }, + + Type::App { .. } | Type::Tuple { .. } => Err(could_not_unify()), + } +} + +fn annotate_fuzzer(tipo: &Type, location: &Span) -> Result { + match tipo { + Type::App { + name, module, args, .. + } => { + let arguments = args + .iter() + .map(|arg| annotate_fuzzer(arg, location)) + .collect::, _>>()?; + Ok(Annotation::Constructor { + name: name.to_owned(), + module: if module.is_empty() { + None + } else { + Some(module.to_owned()) + }, + arguments, + location: *location, + }) + } + + Type::Tuple { elems } => { + let elems = elems + .iter() + .map(|arg| annotate_fuzzer(arg, location)) + .collect::, _>>()?; + Ok(Annotation::Tuple { + elems, + location: *location, + }) + } + + Type::Var { tipo } => match &*tipo.deref().borrow() { + TypeVar::Link { tipo } => annotate_fuzzer(tipo, location), + _ => Err(Error::GenericLeftAtBoundary { + location: *location, + }), + }, + Type::Fn { .. } => Err(Error::IllegalTypeInData { + location: *location, + tipo: Rc::new(tipo.clone()), + }), + } +} diff --git a/crates/aiken-lsp/src/server/lsp_project.rs b/crates/aiken-lsp/src/server/lsp_project.rs index 1134689f..e5b1c04e 100644 --- a/crates/aiken-lsp/src/server/lsp_project.rs +++ b/crates/aiken-lsp/src/server/lsp_project.rs @@ -1,7 +1,6 @@ -use std::{collections::HashMap, path::PathBuf}; - use aiken_lang::{ast::Tracing, line_numbers::LineNumbers}; use aiken_project::{config::Config, error::Error as ProjectError, module::CheckedModule, Project}; +use std::{collections::HashMap, path::PathBuf}; #[derive(Debug)] pub struct SourceInfo { @@ -30,9 +29,9 @@ impl LspProject { pub fn compile(&mut self) -> Result<(), Vec> { let checkpoint = self.project.checkpoint(); - let result = self - .project - .check(true, None, false, false, Tracing::silent()); + let result = + self.project + .check(true, None, false, false, u32::default(), Tracing::silent()); self.project.restore(checkpoint); diff --git a/crates/aiken-project/Cargo.toml b/crates/aiken-project/Cargo.toml index cc88c1d5..3e5d2ded 100644 --- a/crates/aiken-project/Cargo.toml +++ b/crates/aiken-project/Cargo.toml @@ -7,9 +7,9 @@ repository = "https://github.com/aiken-lang/aiken/crates/project" homepage = "https://github.com/aiken-lang/aiken" license = "Apache-2.0" authors = [ - "Lucas Rosa ", - "Kasey White ", - "KtorZ ", + "Lucas Rosa ", + "Kasey White ", + "KtorZ ", ] rust-version = "1.66.1" build = "build.rs" @@ -43,8 +43,11 @@ zip = "0.6.4" aiken-lang = { path = "../aiken-lang", version = "1.0.24-alpha" } uplc = { path = '../uplc', version = "1.0.24-alpha" } +num-bigint = "0.4.4" +cryptoxide = "0.4.4" [dev-dependencies] +blst = "0.3.11" indoc = "2.0.1" insta.workspace = true proptest = "1.2.0" diff --git a/crates/aiken-project/src/blueprint/parameter.rs b/crates/aiken-project/src/blueprint/parameter.rs index ef0633e6..56b145b8 100644 --- a/crates/aiken-project/src/blueprint/parameter.rs +++ b/crates/aiken-project/src/blueprint/parameter.rs @@ -5,7 +5,7 @@ use super::{ }; use std::{iter, ops::Deref}; use uplc::{ - ast::{Constant, Data as UplcData, DeBruijn, Term}, + ast::{Constant, Data as UplcData}, PlutusData, }; @@ -30,7 +30,7 @@ impl Parameter { pub fn validate( &self, definitions: &Definitions>, - term: &Term, + constant: &Constant, ) -> Result<(), Error> { let schema = &definitions .lookup(&self.schema) @@ -42,11 +42,7 @@ impl Parameter { })? .annotated; - if let Term::Constant(constant) = term { - validate_schema(schema, definitions, constant) - } else { - Err(Error::NonConstantParameter) - } + validate_schema(schema, definitions, constant) } } diff --git a/crates/aiken-project/src/blueprint/schema.rs b/crates/aiken-project/src/blueprint/schema.rs index f07e02e7..dc40915c 100644 --- a/crates/aiken-project/src/blueprint/schema.rs +++ b/crates/aiken-project/src/blueprint/schema.rs @@ -1,5 +1,7 @@ -use crate::blueprint::definitions::{Definitions, Reference}; -use crate::CheckedModule; +use crate::{ + blueprint::definitions::{Definitions, Reference}, + CheckedModule, +}; use aiken_lang::{ ast::{Definition, TypedDataType, TypedDefinition}, builtins::wrapped_redeemer, @@ -12,8 +14,7 @@ use serde::{ ser::{Serialize, SerializeStruct, Serializer}, }; use serde_json as json; -use std::rc::Rc; -use std::{collections::HashMap, fmt, ops::Deref}; +use std::{collections::HashMap, fmt, ops::Deref, rc::Rc}; // NOTE: Can be anything BUT 0 pub const REDEEMER_DISCRIMINANT: usize = 1; @@ -385,21 +386,6 @@ impl Annotated { Type::Fn { .. } => unreachable!(), } } - - fn into_data(self, type_info: &Type) -> Result, Error> { - match self { - Annotated { - title, - description, - annotated: Schema::Data(data), - } => Ok(Annotated { - title, - description, - annotated: data, - }), - _ => Err(Error::new(ErrorContext::ExpectedData, type_info)), - } - } } impl Data { @@ -409,28 +395,24 @@ impl Data { type_parameters: &mut HashMap>, definitions: &mut Definitions>, ) -> Result { + if data_type.opaque { + // NOTE: No breadcrumbs here which is *okay*, as the caller will backtrack + // and add the necessary type information. + return Err(Error { + context: ErrorContext::IllegalOpaqueType, + breadcrumbs: vec![], + }); + } + let mut variants = vec![]; - let len_constructors = data_type.constructors.len(); for (index, constructor) in data_type.constructors.iter().enumerate() { let mut fields = vec![]; - let len_arguments = data_type.constructors.len(); for field in constructor.arguments.iter() { let reference = Annotated::do_from_type(&field.tipo, modules, type_parameters, definitions)?; - // NOTE: Opaque data-types with a single variant and a single field are transparent, they - // are erased completely at compilation time. - if data_type.opaque && len_constructors == 1 && len_arguments == 1 { - let schema = definitions - .lookup(&reference) - .expect("Schema definition registered just above") - .clone(); - definitions.remove(&reference); - return Ok(schema.into_data(&field.tipo)?.annotated); - } - fields.push(Annotated { title: field.label.clone(), description: field.doc.clone().map(|s| s.trim().to_string()), @@ -479,7 +461,7 @@ fn find_data_type(name: &str, definitions: &[TypedDefinition]) -> Option { - return Some(data_type.clone()) + return Some(data_type.clone()); } Definition::Fn { .. } | Definition::Validator { .. } @@ -928,7 +910,9 @@ pub struct Error { #[derive(Debug, PartialEq, Clone, thiserror::Error)] pub enum ErrorContext { - #[error("I failed at my own job and couldn't figure out how to generate a specification for a type.")] + #[error( + "I failed at my own job and couldn't figure out how to generate a specification for a type." + )] UnsupportedType, #[error("I discovered a type hole where I would expect a concrete type.")] @@ -942,6 +926,9 @@ pub enum ErrorContext { #[error("I figured you tried to export a function in your contract's binary interface.")] UnexpectedFunction, + + #[error("I caught an opaque type trying to escape")] + IllegalOpaqueType, } impl Error { @@ -963,6 +950,26 @@ impl Error { pub fn help(&self) -> String { match self.context { + ErrorContext::IllegalOpaqueType => format!( + r#"Opaque types cannot figure anywhere in an outward-facing type like a validator's redeemer or datum. This is because an {opaque} type hides its implementation details, and likely enforce invariants that cannot be expressed only structurally. In particular, the {opaque} type {signature} cannot be safely constructed from any Plutus Data. + +Hence, {opaque} types are forbidden from interface points with the off-chain world. Instead, use an intermediate representation and construct the {opaque} type at runtime using constructors and methods provided for that type (e.g. {Dict}.{from_list}, {Rational}.{new}, ...)."#, + opaque = "opaque".if_supports_color(Stdout, |s| s.purple()), + signature = Error::fmt_breadcrumbs(&[self + .breadcrumbs + .last() + .expect("always at least one breadcrumb") + .to_owned()]), + Dict = "Dict" + .if_supports_color(Stdout, |s| s.bright_blue()) + .if_supports_color(Stdout, |s| s.bold()), + from_list = "from_list".if_supports_color(Stdout, |s| s.blue()), + Rational = "Rational" + .if_supports_color(Stdout, |s| s.bright_blue()) + .if_supports_color(Stdout, |s| s.bold()), + new = "new".if_supports_color(Stdout, |s| s.blue()), + ), + ErrorContext::UnsupportedType => format!( r#"I do not know how to generate a portable Plutus specification for the following type: diff --git a/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__opaque_singleton_multi_variants.snap b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__opaque_singleton_multi_variants.snap new file mode 100644 index 00000000..e397e816 --- /dev/null +++ b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__opaque_singleton_multi_variants.snap @@ -0,0 +1,22 @@ +--- +source: crates/aiken-project/src/blueprint/validator.rs +description: "Code:\n\npub opaque type Rational {\n numerator: Int,\n denominator: Int,\n}\n\nvalidator {\n fn opaque_singleton_multi_variants(redeemer: Rational, ctx: Void) {\n True\n }\n}\n" +--- +Schema { + error: Error { + context: IllegalOpaqueType, + breadcrumbs: [ + App { + public: true, + module: "test_module", + name: "Rational", + args: [], + }, + ], + }, + location: 117..135, + source_code: NamedSource { + name: "", + source: "", + , +} diff --git a/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__opaque_singleton_variants.snap b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__opaque_singleton_variants.snap index 2485eb95..18a09e86 100644 --- a/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__opaque_singleton_variants.snap +++ b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__opaque_singleton_variants.snap @@ -2,32 +2,46 @@ source: crates/aiken-project/src/blueprint/validator.rs description: "Code:\n\npub opaque type Dict {\n inner: List<(ByteArray, value)>\n}\n\ntype UUID { UUID }\n\nvalidator {\n fn opaque_singleton_variants(redeemer: Dict, ctx: Void) {\n True\n }\n}\n" --- -{ - "title": "test_module.opaque_singleton_variants", - "redeemer": { - "title": "redeemer", - "schema": { - "$ref": "#/definitions/test_module~1Dict$test_module~1UUID_Int" - } - }, - "compiledCode": "58f201000032323232323232323223232253330064a22930a99803a4811856616c696461746f722072657475726e65642066616c73650013656323300100100222533300a00114984c8cc00c00cc034008c8c8c94cccccc04000454cc0280205854cc0280205854cc028020584dd68008a998050040b18058011929999998078008a998048038b0a998048038b0a998048038b0a998048038b09bae0013009001300b001533333300a001153300400216137560022a660080042c2a660080042c2a660080042c9211972656465656d65723a20446963743c555549442c20496e743e005734ae7155ceaab9e5573eae855d12ba41", - "hash": "c3f68ad7fb4d6c26e1f19799fe0ded6c9904bf04b924835ddad2abf0", - "definitions": { - "ByteArray": { - "dataType": "bytes" +Schema { + error: Error { + context: IllegalOpaqueType, + breadcrumbs: [ + App { + public: true, + module: "test_module", + name: "Dict", + args: [ + Var { + tipo: RefCell { + value: Link { + tipo: App { + public: false, + module: "test_module", + name: "UUID", + args: [], + }, + }, + }, + }, + Var { + tipo: RefCell { + value: Link { + tipo: App { + public: true, + module: "", + name: "Int", + args: [], + }, + }, + }, + }, + ], + }, + ], }, - "Int": { - "dataType": "integer" - }, - "test_module/Dict$test_module/UUID_Int": { - "title": "Dict", - "dataType": "map", - "keys": { - "$ref": "#/definitions/ByteArray" - }, - "values": { - "$ref": "#/definitions/Int" - } - } - } + location: 137..162, + source_code: NamedSource { + name: "", + source: "", + , } diff --git a/crates/aiken-project/src/blueprint/validator.rs b/crates/aiken-project/src/blueprint/validator.rs index cd891418..46387de5 100644 --- a/crates/aiken-project/src/blueprint/validator.rs +++ b/crates/aiken-project/src/blueprint/validator.rs @@ -13,9 +13,8 @@ use aiken_lang::{ use miette::NamedSource; use serde; use std::borrow::Borrow; -use std::rc::Rc; use uplc::{ - ast::{Constant, DeBruijn, Program, Term}, + ast::{Constant, DeBruijn, Program}, PlutusData, }; @@ -199,14 +198,14 @@ impl Validator { pub fn apply( self, definitions: &Definitions>, - arg: &Term, + arg: &PlutusData, ) -> Result { match self.parameters.split_first() { None => Err(Error::NoParametersToApply), Some((head, tail)) => { - head.validate(definitions, arg)?; + head.validate(definitions, &Constant::Data(arg.clone()))?; Ok(Self { - program: self.program.apply_term(arg), + program: self.program.apply_data(arg.clone()), parameters: tail.to_vec(), ..self }) @@ -218,7 +217,7 @@ impl Validator { &self, definitions: &Definitions>, ask: F, - ) -> Result, Error> + ) -> Result where F: Fn(&Annotated, &Definitions>) -> Result, { @@ -242,7 +241,7 @@ impl Validator { let data = ask(&schema, definitions)?; - Ok(Term::Constant(Rc::new(Constant::Data(data.clone())))) + Ok(data.clone()) } } } @@ -250,17 +249,6 @@ impl Validator { #[cfg(test)] mod tests { - use std::collections::HashMap; - - use aiken_lang::{ - self, - ast::{TraceLevel, Tracing}, - builtins, - }; - use uplc::ast as uplc_ast; - - use crate::tests::TestProject; - use super::{ super::{ definitions::{Definitions, Reference}, @@ -269,16 +257,22 @@ mod tests { }, *, }; + use crate::tests::TestProject; + use aiken_lang::{ + self, + ast::{TraceLevel, Tracing}, + builtins, + }; + use std::collections::HashMap; + use uplc::ast as uplc_ast; macro_rules! assert_validator { ($code:expr) => { let mut project = TestProject::new(); let modules = CheckedModules::singleton(project.check(project.parse(indoc::indoc! { $code }))); - let mut generator = modules.new_generator( - &project.functions, - &project.data_types, - &project.module_types, + + let mut generator = project.new_generator( Tracing::All(TraceLevel::Verbose), ); @@ -296,15 +290,23 @@ mod tests { let validator = validators .get(0) .unwrap() - .as_ref() - .expect("Failed to create validator blueprint"); + .as_ref(); - insta::with_settings!({ - description => concat!("Code:\n\n", indoc::indoc! { $code }), - omit_expression => true - }, { - insta::assert_json_snapshot!(validator); - }); + match validator { + Err(e) => insta::with_settings!({ + description => concat!("Code:\n\n", indoc::indoc! { $code }), + omit_expression => true + }, { + insta::assert_debug_snapshot!(e); + }), + + Ok(validator) => insta::with_settings!({ + description => concat!("Code:\n\n", indoc::indoc! { $code }), + omit_expression => true + }, { + insta::assert_json_snapshot!(validator); + }), + }; }; } @@ -514,6 +516,24 @@ mod tests { ); } + #[test] + fn opaque_singleton_multi_variants() { + assert_validator!( + r#" + pub opaque type Rational { + numerator: Int, + denominator: Int, + } + + validator { + fn opaque_singleton_multi_variants(redeemer: Rational, ctx: Void) { + True + } + } + "# + ); + } + #[test] fn nested_data() { assert_validator!( @@ -599,7 +619,7 @@ mod tests { fn validate_arguments_integer() { let definitions = fixture_definitions(); - let term = Term::data(uplc_ast::Data::integer(42.into())); + let term = Constant::Data(uplc_ast::Data::integer(42.into())); let param = Parameter { title: None, @@ -613,7 +633,7 @@ mod tests { fn validate_arguments_bytestring() { let definitions = fixture_definitions(); - let term = Term::data(uplc_ast::Data::bytestring(vec![102, 111, 111])); + let term = Constant::Data(uplc_ast::Data::bytestring(vec![102, 111, 111])); let param = Parameter { title: None, @@ -642,7 +662,7 @@ mod tests { .into(), ); - let term = Term::data(uplc_ast::Data::list(vec![ + let term = Constant::Data(uplc_ast::Data::list(vec![ uplc_ast::Data::integer(42.into()), uplc_ast::Data::integer(14.into()), ])); @@ -671,7 +691,7 @@ mod tests { .into(), ); - let term = Term::data(uplc_ast::Data::list(vec![uplc_ast::Data::bytestring( + let term = Constant::Data(uplc_ast::Data::list(vec![uplc_ast::Data::bytestring( vec![102, 111, 111], )])); @@ -703,7 +723,7 @@ mod tests { .into(), ); - let term = Term::data(uplc_ast::Data::list(vec![ + let term = Constant::Data(uplc_ast::Data::list(vec![ uplc_ast::Data::integer(42.into()), uplc_ast::Data::bytestring(vec![102, 111, 111]), ])); @@ -734,7 +754,7 @@ mod tests { .into(), ); - let term = Term::data(uplc_ast::Data::map(vec![( + let term = Constant::Data(uplc_ast::Data::map(vec![( uplc_ast::Data::bytestring(vec![102, 111, 111]), uplc_ast::Data::integer(42.into()), )])); @@ -750,7 +770,7 @@ mod tests { let definitions = fixture_definitions(); - let term = Term::data(uplc_ast::Data::constr(1, vec![])); + let term = Constant::Data(uplc_ast::Data::constr(1, vec![])); let param: Parameter = schema.into(); @@ -785,7 +805,7 @@ mod tests { .into(), ); - let term = Term::data(uplc_ast::Data::constr( + let term = Constant::Data(uplc_ast::Data::constr( 0, vec![uplc_ast::Data::constr(0, vec![])], )); @@ -841,7 +861,7 @@ mod tests { .into(), ); - let term = Term::data(uplc_ast::Data::constr( + let term = Constant::Data(uplc_ast::Data::constr( 1, vec![ uplc_ast::Data::integer(14.into()), diff --git a/crates/aiken-project/src/error.rs b/crates/aiken-project/src/error.rs index f75947fc..94553372 100644 --- a/crates/aiken-project/src/error.rs +++ b/crates/aiken-project/src/error.rs @@ -93,7 +93,7 @@ pub enum Error { path: PathBuf, verbose: bool, src: String, - evaluation_hint: Option, + assertion: Option, }, #[error( @@ -125,6 +125,12 @@ pub enum Error { impl Error { pub fn report(&self) { + if let Error::TestFailure { verbose, .. } = self { + if !verbose { + return; + } + } + println!("{self:?}") } @@ -320,49 +326,54 @@ impl Diagnostic for Error { Error::Parse { error, .. } => error.kind.help(), Error::Type { error, .. } => error.help(), Error::StandardIo(_) => None, - Error::MissingManifest { .. } => Some(Box::new("Try running `aiken new ` to initialise a project with an example manifest.")), + Error::MissingManifest { .. } => Some(Box::new( + "Try running `aiken new ` to initialise a project with an example manifest.", + )), Error::TomlLoading { .. } => None, Error::Format { .. } => None, - Error::TestFailure { evaluation_hint, .. } => match evaluation_hint { + Error::TestFailure { assertion, .. } => match assertion { None => None, - Some(hint) => Some(Box::new(hint.to_string())) + Some(hint) => Some(Box::new(hint.to_string())), }, Error::Http(_) => None, Error::ZipExtract(_) => None, Error::JoinError(_) => None, - Error::UnknownPackageVersion{..} => Some(Box::new("Perhaps, double-check the package repository and version?")), - Error::UnableToResolvePackage{..} => Some(Box::new("The network is unavailable and the package isn't in the local cache either. Try connecting to the Internet so I can look it up?")), + Error::UnknownPackageVersion { .. } => Some(Box::new( + "Perhaps, double-check the package repository and version?", + )), + Error::UnableToResolvePackage { .. } => Some(Box::new( + "The network is unavailable and the package isn't in the local cache either. Try connecting to the Internet so I can look it up?", + )), Error::Json(error) => Some(Box::new(format!("{error}"))), - Error::MalformedStakeAddress { error } => Some(Box::new(format!("A stake address must be provided either as a base16-encoded string, or as a bech32-encoded string with the 'stake' or 'stake_test' prefix.{hint}", hint = match error { - Some(error) => format!("\n\nHere's the error I encountered: {error}"), - None => String::new(), - }))), - Error::NoValidatorNotFound { known_validators } => { - Some(Box::new(format!( - "Here's a list of all validators I've found in your project. Please double-check this list against the options that you've provided:\n\n{}", - known_validators - .iter() - .map(|title| format!( - "→ {title}", - title = title.if_supports_color(Stdout, |s| s.purple()) - )) - .collect::>() - .join("\n") - ))) - }, - Error::MoreThanOneValidatorFound { known_validators } => { - Some(Box::new(format!( - "Here's a list of all validators I've found in your project. Select one of them using the appropriate options:\n\n{}", - known_validators - .iter() - .map(|title| format!( - "→ {title}", - title = title.if_supports_color(Stdout, |s| s.purple()) - )) - .collect::>() - .join("\n") - ))) - }, + Error::MalformedStakeAddress { error } => Some(Box::new(format!( + "A stake address must be provided either as a base16-encoded string, or as a bech32-encoded string with the 'stake' or 'stake_test' prefix.{hint}", + hint = match error { + Some(error) => format!("\n\nHere's the error I encountered: {error}"), + None => String::new(), + } + ))), + Error::NoValidatorNotFound { known_validators } => Some(Box::new(format!( + "Here's a list of all validators I've found in your project. Please double-check this list against the options that you've provided:\n\n{}", + known_validators + .iter() + .map(|title| format!( + "→ {title}", + title = title.if_supports_color(Stdout, |s| s.purple()) + )) + .collect::>() + .join("\n") + ))), + Error::MoreThanOneValidatorFound { known_validators } => Some(Box::new(format!( + "Here's a list of all validators I've found in your project. Select one of them using the appropriate options:\n\n{}", + known_validators + .iter() + .map(|title| format!( + "→ {title}", + title = title.if_supports_color(Stdout, |s| s.purple()) + )) + .collect::>() + .join("\n") + ))), Error::Module(e) => e.help(), } } diff --git a/crates/aiken-project/src/lib.rs b/crates/aiken-project/src/lib.rs index 959897c9..daac7e86 100644 --- a/crates/aiken-project/src/lib.rs +++ b/crates/aiken-project/src/lib.rs @@ -10,21 +10,34 @@ pub mod options; pub mod package_name; pub mod paths; pub mod pretty; -pub mod script; pub mod telemetry; -#[cfg(test)] -mod tests; +pub mod test_framework; +pub mod utils; pub mod watch; -use crate::blueprint::{ - definitions::Definitions, - schema::{Annotated, Schema}, - Blueprint, +#[cfg(test)] +mod tests; + +use crate::{ + blueprint::{ + definitions::Definitions, + schema::{Annotated, Schema}, + Blueprint, + }, + config::Config, + error::{Error, Warning}, + module::{CheckedModule, CheckedModules, ParsedModule, ParsedModules}, + telemetry::Event, }; use aiken_lang::{ - ast::{Definition, Function, ModuleKind, Tracing, TypedDataType, TypedFunction, Validator}, + ast::{ + DataTypeKey, Definition, FunctionAccessKey, ModuleKind, Tracing, TypedDataType, + TypedFunction, + }, builtins, - gen_uplc::builder::{DataTypeKey, FunctionAccessKey}, + expr::UntypedExpr, + gen_uplc::CodeGenerator, + line_numbers::LineNumbers, tipo::TypeInfo, IdGenerator, }; @@ -37,8 +50,6 @@ use pallas::ledger::{ primitives::babbage::{self as cardano, PolicyId}, traverse::ComputeHash, }; - -use script::{EvalHint, EvalInfo, Script}; use std::{ collections::HashMap, fs::{self, File}, @@ -46,19 +57,12 @@ use std::{ path::{Path, PathBuf}, }; use telemetry::EventListener; +use test_framework::{Test, TestResult}; use uplc::{ - ast::{DeBruijn, Name, Program, Term}, - machine::cost_model::ExBudget, + ast::{Name, Program}, PlutusData, }; -use crate::{ - config::Config, - error::{Error, Warning}, - module::{CheckedModule, CheckedModules, ParsedModule, ParsedModules}, - telemetry::Event, -}; - #[derive(Debug)] pub struct Source { pub path: PathBuf, @@ -87,6 +91,7 @@ where event_listener: T, functions: IndexMap, data_types: IndexMap, + module_sources: HashMap, } impl Project @@ -125,9 +130,20 @@ where event_listener, functions, data_types, + module_sources: HashMap::new(), } } + pub fn new_generator(&'_ self, tracing: Tracing) -> CodeGenerator<'_> { + CodeGenerator::new( + utils::indexmap::as_ref_values(&self.functions), + utils::indexmap::as_ref_values(&self.data_types), + utils::indexmap::as_str_ref_values(&self.module_types), + utils::indexmap::as_str_ref_values(&self.module_sources), + tracing, + ) + } + pub fn warnings(&mut self) -> Vec { std::mem::take(&mut self.warnings) } @@ -210,6 +226,7 @@ where match_tests: Option>, verbose: bool, exact_match: bool, + seed: u32, tracing: Tracing, ) -> Result<(), Vec> { let options = Options { @@ -221,6 +238,7 @@ where match_tests, verbose, exact_match, + seed, } }, }; @@ -279,12 +297,7 @@ where m.attach_doc_and_module_comments(); }); - let mut generator = self.checked_modules.new_generator( - &self.functions, - &self.data_types, - &self.module_types, - options.tracing, - ); + let mut generator = self.new_generator(options.tracing); let blueprint = Blueprint::new(&self.config, &self.checked_modules, &mut generator) .map_err(Error::Blueprint)?; @@ -311,6 +324,7 @@ where match_tests, verbose, exact_match, + seed, } => { let tests = self.collect_tests(verbose, match_tests, exact_match, options.tracing)?; @@ -319,31 +333,21 @@ where self.event_listener.handle_event(Event::RunningTests); } - let results = self.eval_scripts(tests); + let tests = self.run_tests(tests, seed); - let errors: Vec = results + let errors: Vec = tests .iter() .filter_map(|e| { - if e.success { + if e.is_success() { None } else { - Some(Error::TestFailure { - name: e.script.name.clone(), - path: e.script.input_path.clone(), - evaluation_hint: e - .script - .evaluation_hint - .as_ref() - .map(|hint| hint.to_string()), - src: e.script.program.to_pretty(), - verbose, - }) + Some(e.into_error(verbose)) } }) .collect(); self.event_listener - .handle_event(Event::FinishedTests { tests: results }); + .handle_event(Event::FinishedTests { tests }); if !errors.is_empty() { Err(errors) @@ -359,6 +363,7 @@ where &self, title: Option<&String>, stake_address: Option<&String>, + mainnet: bool, ) -> Result { // Parse stake address let stake_address = stake_address @@ -398,9 +403,15 @@ where if n > 0 { Err(blueprint::error::Error::ParameterizedValidator { n }.into()) } else { + let network = if mainnet { + Network::Mainnet + } else { + Network::Testnet + }; + Ok(validator .program - .address(Network::Testnet, delegation_part.to_owned())) + .address(network, delegation_part.to_owned())) } }) } @@ -437,7 +448,7 @@ where &self, title: Option<&String>, ask: F, - ) -> Result, Error> + ) -> Result where F: Fn( &Annotated, @@ -454,19 +465,19 @@ where |known_validators| Error::MoreThanOneValidatorFound { known_validators }; let when_missing = |known_validators| Error::NoValidatorNotFound { known_validators }; - let term = blueprint.with_validator(title, when_too_many, when_missing, |validator| { + let data = blueprint.with_validator(title, when_too_many, when_missing, |validator| { validator .ask_next_parameter(&blueprint.definitions, &ask) .map_err(|e| e.into()) })?; - Ok(term) + Ok(data) } pub fn apply_parameter( &self, title: Option<&String>, - param: &Term, + param: &PlutusData, ) -> Result { // Read blueprint let blueprint = File::open(self.blueprint_path()) @@ -649,11 +660,18 @@ where self.warnings.extend(type_warnings); - // Register the types from this module so they can be imported into - // other modules. + // Register module sources for an easier access later. + self.module_sources + .insert(name.clone(), (code.clone(), LineNumbers::new(&code))); + + // Register the types from this module so they can be + // imported into other modules. self.module_types .insert(name.clone(), ast.type_info.clone()); + // Register function definitions & data-types for easier access later. + ast.register_definitions(&mut self.functions, &mut self.data_types); + let checked_module = CheckedModule { kind, extra, @@ -677,9 +695,8 @@ where match_tests: Option>, exact_match: bool, tracing: Tracing, - ) -> Result, Error> { + ) -> Result, Error> { let mut scripts = Vec::new(); - let mut testable_validators = Vec::new(); let match_tests = match_tests.map(|mt| { mt.into_iter() @@ -711,161 +728,83 @@ where } for def in checked_module.ast.definitions() { - match def { - Definition::Validator(Validator { - params, - fun, - other_fun, - .. - }) => { - let mut fun = fun.clone(); + if let Definition::Test(func) = def { + if let Some(match_tests) = &match_tests { + let is_match = match_tests.iter().any(|(module, names)| { + let matched_module = + module.is_empty() || checked_module.name.contains(module); - fun.arguments = params.clone().into_iter().chain(fun.arguments).collect(); + let matched_name = match names { + None => true, + Some(names) => names.iter().any(|name| { + if exact_match { + name == &func.name + } else { + func.name.contains(name) + } + }), + }; - testable_validators.push((&checked_module.name, fun)); + matched_module && matched_name + }); - if let Some(other) = other_fun { - let mut other = other.clone(); - - other.arguments = - params.clone().into_iter().chain(other.arguments).collect(); - - testable_validators.push((&checked_module.name, other)); - } - } - Definition::Test(func) => { - if let Some(match_tests) = &match_tests { - let is_match = match_tests.iter().any(|(module, names)| { - let matched_module = - module.is_empty() || checked_module.name.contains(module); - - let matched_name = match names { - None => true, - Some(names) => names.iter().any(|name| { - if exact_match { - name == &func.name - } else { - func.name.contains(name) - } - }), - }; - - matched_module && matched_name - }); - - if is_match { - scripts.push(( - checked_module.input_path.clone(), - checked_module.name.clone(), - func, - )) - } - } else { + if is_match { scripts.push(( checked_module.input_path.clone(), checked_module.name.clone(), func, )) } + } else { + scripts.push(( + checked_module.input_path.clone(), + checked_module.name.clone(), + func, + )) } - _ => (), } } } - let mut programs = Vec::new(); + let mut generator = self.new_generator(tracing); - let mut generator = self.checked_modules.new_generator( - &self.functions, - &self.data_types, - &self.module_types, - tracing, - ); - - for (module_name, testable_validator) in &testable_validators { - generator.insert_function( - module_name.to_string(), - testable_validator.name.clone(), - testable_validator, - ); - } - - for (input_path, module_name, func_def) in scripts { - let Function { - name, - body, - can_error, - .. - } = func_def; + let mut tests = Vec::new(); + for (input_path, module_name, test) in scripts.into_iter() { if verbose { self.event_listener.handle_event(Event::GeneratingUPLCFor { - name: name.clone(), + name: test.name.clone(), path: input_path.clone(), }) } - let evaluation_hint = func_def.test_hint().map(|(bin_op, left_src, right_src)| { - let left = generator - .clone() - .generate_test(&left_src, &module_name) - .try_into() - .unwrap(); - - let right = generator - .clone() - .generate_test(&right_src, &module_name) - .try_into() - .unwrap(); - - EvalHint { - bin_op, - left, - right, - can_error: *can_error, - } - }); - - let program = generator.generate_test(body, &module_name); - - let script = Script::new( - input_path, + tests.push(Test::from_function_definition( + &mut generator, + test.to_owned(), module_name, - name.to_string(), - *can_error, - program.try_into().unwrap(), - evaluation_hint, - ); - - programs.push(script); + input_path, + )); } - Ok(programs) + Ok(tests) } - fn eval_scripts(&self, scripts: Vec