diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8f8a9d0246..e2b119c59f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,9 +54,10 @@ jobs: name: cargo test with: command: test + args: --no-fail-fast - uses: actions-rs/cargo@v1 name: cargo test --release with: command: test - args: --release + args: --release --no-fail-fast diff --git a/Cargo.lock b/Cargo.lock index b8825622b9..e11fcd579f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,10 +40,10 @@ dependencies = [ ] [[package]] -name = "android_log-sys" -version = "0.1.2" +name = "android_glue" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8052e2d8aabbb8d556d6abbcce2a22b9590996c5f849b9c7ce4544a2e3b984e" +checksum = "000444226fcff248f2bc4c7625be32c63caccfecc2723a2b9f78a7487a49c407" [[package]] name = "ansi_term" @@ -65,9 +65,9 @@ dependencies = [ [[package]] name = "arc-swap" -version = "0.4.6" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b585a98a234c46fc563103e9278c9391fde1f4e6850334da895d27edb9580f62" +checksum = "d663a8e9a99154b5fb793032533f6328da35e23aac63d5c152279aa8ba356825" [[package]] name = "arrayvec" @@ -81,7 +81,7 @@ version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69daec0742947f33a85931fa3cb0ce5f07929159dcbd1f0cbb5b2912e2978509" dependencies = [ - "libloading 0.5.2", + "libloading", ] [[package]] @@ -136,9 +136,9 @@ checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] name = "bstr" -version = "0.2.13" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31accafdb70df7871592c058eca3985b71104e15ac32f64706022c58867da931" +checksum = "2889e6d50f394968c8bf4240dc3f2a7eb4680844d27308f798229ac9d4725f41" dependencies = [ "lazy_static", "memchr", @@ -148,9 +148,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.3.0" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5356f1d23ee24a1f785a56d1d1a5f0fd5b0f6a0c0fb2412ce11da71649ab78f6" +checksum = "12ae9db68ad7fac5fe51304d20f016c911539251075a214f8e663babefa35187" [[package]] name = "byteorder" @@ -186,9 +186,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.54" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311" +checksum = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" [[package]] name = "cfg-if" @@ -198,44 +198,15 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "clap" -version = "2.33.1" +version = "2.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" +checksum = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" dependencies = [ "bitflags", "textwrap", "unicode-width", ] -[[package]] -name = "clap" -version = "3.0.0-beta.1" -source = "git+https://github.com/rtfeldman/clap#e1d83a78804a271b053d4d21f69b67f7eb01ad01" -dependencies = [ - "atty", - "bitflags", - "clap_derive", - "indexmap", - "lazy_static", - "strsim", - "termcolor", - "textwrap", - "unicode-width", - "vec_map", -] - -[[package]] -name = "clap_derive" -version = "3.0.0-beta.1" -source = "git+https://github.com/rtfeldman/clap#e1d83a78804a271b053d4d21f69b67f7eb01ad01" -dependencies = [ - "heck", - "proc-macro-error", - "proc-macro2 1.0.18", - "quote 1.0.6", - "syn 1.0.30", -] - [[package]] name = "cloudabi" version = "0.0.3" @@ -247,14 +218,29 @@ dependencies = [ [[package]] name = "cocoa" -version = "0.20.1" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f7b6f3f7f4f0b3ec5c5039aaa9e8c3cef97a7a480a400fd62944841314f293d" +checksum = "f29f7768b2d1be17b96158e3285951d366b40211320fb30826a76cb7a0da6400" dependencies = [ "bitflags", "block", - "core-foundation", - "core-graphics", + "core-foundation 0.6.4", + "core-graphics 0.17.3", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a4736c86d51bd878b474400d9ec888156f4037015f5d09794fab9f26eab1ad4" +dependencies = [ + "bitflags", + "block", + "core-foundation 0.7.0", + "core-graphics 0.19.0", "foreign-types", "libc", "objc", @@ -266,22 +252,50 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ff9c56c9fb2a49c05ef0e431485a22400af20d33226dc0764d891d09e724127" +[[package]] +name = "core-foundation" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" +dependencies = [ + "core-foundation-sys 0.6.2", + "libc", +] + [[package]] name = "core-foundation" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" dependencies = [ - "core-foundation-sys", + "core-foundation-sys 0.7.0", "libc", ] +[[package]] +name = "core-foundation-sys" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" + [[package]] name = "core-foundation-sys" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" +[[package]] +name = "core-graphics" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56790968ab1c8a1202a102e6de05fc6e1ec87da99e4e93e9a7d13efbfc1e95a9" +dependencies = [ + "bitflags", + "core-foundation 0.6.4", + "foreign-types", + "libc", +] + [[package]] name = "core-graphics" version = "0.19.0" @@ -289,33 +303,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59e78b2e0aaf43f08e7ae0d6bc96895ef72ff0921c7d4ff4762201b2dba376dd" dependencies = [ "bitflags", - "core-foundation", + "core-foundation 0.7.0", "foreign-types", "libc", ] [[package]] name = "core-video-sys" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ecad23610ad9757664d644e369246edde1803fcb43ed72876565098a5d3828" +checksum = "8dc065219542086f72d1e9f7aadbbab0989e980263695d129d502082d063a9d0" dependencies = [ "cfg-if", - "core-foundation-sys", - "core-graphics", + "core-foundation-sys 0.6.2", + "core-graphics 0.17.3", "libc", "objc", ] [[package]] name = "criterion" -version = "0.3.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63f696897c88b57f4ffe3c69d8e1a0613c7d0e6c4833363c8560fbde9c47b966" +checksum = "1fc755679c12bda8e5523a71e4d654b6bf2e14bd838dfc48cde6559a05caf7d1" dependencies = [ "atty", "cast", - "clap 2.33.1", + "clap", "criterion-plot", "csv", "itertools", @@ -334,9 +348,9 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.4.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddeaf7989f00f2e1d871a26a110f3ed713632feac17f65f03ca938c542618b60" +checksum = "a01e15e0ea58e8234f96146b1f91fa9d0e4dd7a38da93ff7a75d42c0b9d3a545" dependencies = [ "cast", "itertools", @@ -380,9 +394,9 @@ dependencies = [ [[package]] name = "crossbeam-queue" -version = "0.2.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab6bffe714b6bb07e42f201352c34f51fefd355ace793f9e638ebd52d23f98d2" +checksum = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db" dependencies = [ "cfg-if", "crossbeam-utils", @@ -428,21 +442,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc7ed48e89905e5e146bcc1951cc3facb9e44aea9adf5dc01078cda1bd24b662" dependencies = [ "bitflags", - "libloading 0.5.2", + "libloading", "winapi 0.3.8", ] -[[package]] -name = "derivative" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb582b60359da160a9477ee80f15c8d784c477e69c217ef2cdd4169c24ea380f" -dependencies = [ - "proc-macro2 1.0.18", - "quote 1.0.6", - "syn 1.0.30", -] - [[package]] name = "difference" version = "2.0.0" @@ -463,11 +466,11 @@ checksum = "6d9d8664cf849d7d0f3114a3a387d2f5e4303176d746d5a951aaddc66dfe9240" [[package]] name = "dlib" -version = "0.4.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b11f15d1e3268f140f68d390637d5e76d849782d971ae7063e0da69fe9709a76" +checksum = "77e51249a9d823a4cb79e3eca6dcd756153e8ed0157b6c04775d04bf1b13b76a" dependencies = [ - "libloading 0.6.2", + "libloading", ] [[package]] @@ -507,9 +510,9 @@ dependencies = [ [[package]] name = "fnv" -version = "1.0.7" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" [[package]] name = "foreign-types" @@ -603,9 +606,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39" dependencies = [ "proc-macro-hack", - "proc-macro2 1.0.18", - "quote 1.0.6", - "syn 1.0.30", + "proc-macro2 1.0.10", + "quote 1.0.3", + "syn 1.0.17", ] [[package]] @@ -663,6 +666,17 @@ dependencies = [ "wasi", ] +[[package]] +name = "gfx-auxil" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b46e6f0031330a0be08d17820f2dcaaa91cb36710a97a9500cb4f1c36e785c8" +dependencies = [ + "fxhash", + "gfx-hal", + "spirv_cross 0.18.0", +] + [[package]] name = "gfx-auxil" version = "0.4.0" @@ -671,7 +685,7 @@ checksum = "67bdbf8e8d6883c70e5a0d7379ad8ab3ac95127a3761306b36122d8f1c177a8e" dependencies = [ "fxhash", "gfx-hal", - "spirv_cross", + "spirv_cross 0.20.0", ] [[package]] @@ -681,34 +695,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92de0ddc0fde1a89b2a0e92dcc6bbb554bd34af0135e53a28d5ef064611094a4" dependencies = [ "bitflags", - "gfx-auxil", + "gfx-auxil 0.4.0", "gfx-hal", - "libloading 0.5.2", + "libloading", "log", "parking_lot", "range-alloc", "raw-window-handle", "smallvec", - "spirv_cross", + "spirv_cross 0.20.0", "winapi 0.3.8", "wio", ] [[package]] name = "gfx-backend-dx12" -version = "0.5.5" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "708067c51793d2165a9d0c11e896c0365d2591753538c6b1b386ac9af1dab2ed" +checksum = "6facbfcdbb383b3cb7ea0709932ad1273e600a31a242255e80597297ce803dca" dependencies = [ "bitflags", "d3d12", - "gfx-auxil", + "gfx-auxil 0.3.0", "gfx-hal", "log", "range-alloc", "raw-window-handle", "smallvec", - "spirv_cross", + "spirv_cross 0.18.0", "winapi 0.3.8", ] @@ -724,18 +738,18 @@ dependencies = [ [[package]] name = "gfx-backend-metal" -version = "0.5.2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "205f3ca8e74ed814ea2c0206d47d8925077673cab2e21f9b12d48ff781cf87ee" +checksum = "cfe128c29675b5afc8acdda1dfe096d6abd5e3528059ab0b98bda8215d8beed9" dependencies = [ "arrayvec", "bitflags", "block", - "cocoa", + "cocoa 0.20.0", "copyless", - "core-graphics", + "core-graphics 0.19.0", "foreign-types", - "gfx-auxil", + "gfx-auxil 0.3.0", "gfx-hal", "lazy_static", "log", @@ -745,7 +759,7 @@ dependencies = [ "range-alloc", "raw-window-handle", "smallvec", - "spirv_cross", + "spirv_cross 0.18.0", "storage-map", ] @@ -758,7 +772,7 @@ dependencies = [ "arrayvec", "ash", "byteorder", - "core-graphics", + "core-graphics 0.19.0", "gfx-hal", "lazy_static", "log", @@ -842,20 +856,11 @@ dependencies = [ "xi-unicode", ] -[[package]] -name = "heck" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "hermit-abi" -version = "0.1.13" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71" +checksum = "725cf19794cf90aa94e65050cb4191ff5d8fa87a498383774c47b332e3af952e" dependencies = [ "libc", ] @@ -906,15 +911,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "indexmap" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292" -dependencies = [ - "autocfg 1.0.0", -] - [[package]] name = "indoc" version = "0.3.5" @@ -932,9 +928,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54554010aa3d17754e484005ea0022f1c93839aabc627c2c55f3d7b47206134c" dependencies = [ "proc-macro-hack", - "proc-macro2 1.0.18", - "quote 1.0.6", - "syn 1.0.30", + "proc-macro2 1.0.10", + "quote 1.0.3", + "syn 1.0.17", "unindent", ] @@ -970,9 +966,9 @@ checksum = "cb6ee2a7da03bfc3b66ca47c92c2e392fcc053ea040a85561749b026f7aad09a" [[package]] name = "instant" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7777a24a1ce5de49fcdde84ec46efa487c3af49d5b6e6e0a50367cc5c1096182" +checksum = "f7152d2aed88aa566e7a342250f21ba2222c1ae230ad577499dbfa3c18475b80" [[package]] name = "iovec" @@ -985,9 +981,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.9.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" +checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" dependencies = [ "either", ] @@ -998,17 +994,11 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" -[[package]] -name = "jni-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" - [[package]] name = "js-sys" -version = "0.3.40" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce10c23ad2ea25ceca0093bd3192229da4c5b3c0f2de499c1ecac0d98d452177" +checksum = "6a27d435371a2fa5b6d2b028a74bbdb1234f308da363226a2854ca3ff8ba7055" dependencies = [ "wasm-bindgen", ] @@ -1037,9 +1027,9 @@ checksum = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" [[package]] name = "libc" -version = "0.2.71" +version = "0.2.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" +checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005" [[package]] name = "libloading" @@ -1051,15 +1041,6 @@ dependencies = [ "winapi 0.3.8", ] -[[package]] -name = "libloading" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cadb8e769f070c45df05c78c7520eb4cd17061d4ab262e43cfc68b4d00ac71c" -dependencies = [ - "winapi 0.3.8", -] - [[package]] name = "line_drawing" version = "0.7.0" @@ -1160,8 +1141,8 @@ checksum = "e198a0ee42bdbe9ef2c09d0b9426f3b2b47d90d93a4a9b0395c4cea605e92dc0" dependencies = [ "bitflags", "block", - "cocoa", - "core-graphics", + "cocoa 0.20.0", + "core-graphics 0.19.0", "foreign-types", "log", "objc", @@ -1169,9 +1150,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.6.22" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" +checksum = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f" dependencies = [ "cfg-if", "fuchsia-zircon", @@ -1206,15 +1187,15 @@ checksum = "f5e374eff525ce1c5b7687c4cef63943e7686524a387933ad27ca7ec43779cb3" dependencies = [ "log", "mio", - "miow 0.3.4", + "miow 0.3.3", "winapi 0.3.8", ] [[package]] name = "mio-uds" -version = "0.6.8" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" +checksum = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" dependencies = [ "iovec", "libc", @@ -1235,50 +1216,19 @@ dependencies = [ [[package]] name = "miow" -version = "0.3.4" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22dfdd1d51b2639a5abd17ed07005c3af05fb7a2a3b1a1d0d7af1000a520c1c7" +checksum = "396aa0f2003d7df8395cb93e09871561ccc3e785f0acb369170e8cc74ddf9226" dependencies = [ "socket2", "winapi 0.3.8", ] -[[package]] -name = "ndk" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a356cafe20aee088789830bfea3a61336e84ded9e545e00d3869ce95dcb80c" -dependencies = [ - "jni-sys", - "ndk-sys", - "num_enum", -] - -[[package]] -name = "ndk-glue" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1730ee2e3de41c3321160a6da815f008c4006d71b095880ea50e17cf52332b8" -dependencies = [ - "android_log-sys", - "lazy_static", - "libc", - "log", - "ndk", - "ndk-sys", -] - -[[package]] -name = "ndk-sys" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2820aca934aba5ed91c79acc72b6a44048ceacc5d36c035ed4e051f12d887d" - [[package]] name = "net2" -version = "0.2.34" +version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7" +checksum = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" dependencies = [ "cfg-if", "libc", @@ -1309,36 +1259,14 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.13.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +checksum = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6" dependencies = [ "hermit-abi", "libc", ] -[[package]] -name = "num_enum" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca565a7df06f3d4b485494f25ba05da1435950f4dc263440eda7a6fa9b8e36e4" -dependencies = [ - "derivative", - "num_enum_derive", -] - -[[package]] -name = "num_enum_derive" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffa5a33ddddfee04c0283a7653987d634e880347e96b5b2ed64de07efb59db9d" -dependencies = [ - "proc-macro-crate", - "proc-macro2 1.0.18", - "quote 1.0.6", - "syn 1.0.30", -] - [[package]] name = "objc" version = "0.2.7" @@ -1360,15 +1288,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.4.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" +checksum = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b" [[package]] name = "oorandom" -version = "11.1.1" +version = "11.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94af325bc33c7f60191be4e2c984d48aaa21e2854f473b85398344b60c9b6358" +checksum = "ebcec7c9c2a95cacc7cd0ecb89d8a8454eca13906f6deb55258ffff0adeb9405" [[package]] name = "ordered-float" @@ -1400,9 +1328,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.7.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" +checksum = "0e136c1904604defe99ce5fd71a28d473fa60a12255d511aa78a9ddf11237aeb" dependencies = [ "cfg-if", "cloudabi", @@ -1427,9 +1355,9 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb44a25c5bba983be0fc8592dfaf3e6d0935ce8be0c6b15b2a39507af34a926" dependencies = [ - "proc-macro2 1.0.18", - "quote 1.0.6", - "syn 1.0.30", + "proc-macro2 1.0.10", + "quote 1.0.3", + "syn 1.0.17", "synstructure", "unicode-xid 0.2.0", ] @@ -1442,29 +1370,29 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pin-project" -version = "0.4.17" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc93aeee735e60ecb40cf740eb319ff23eab1c5748abfdb5c180e4ce49f7791" +checksum = "b044170ce52ac41b78bdf855a045f6fe6ba72c293a33a2e3f654642127680563" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "0.4.17" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e58db2081ba5b4c93bd6be09c40fd36cb9193a8336c384f3b40012e531aa7e40" +checksum = "babd76ce3c0d7c677fd01a3c3f9be24fa760adb73fd6db48f151662c1ec7eaba" dependencies = [ - "proc-macro2 1.0.18", - "quote 1.0.6", - "syn 1.0.30", + "proc-macro2 1.0.10", + "quote 1.0.3", + "syn 1.0.17", ] [[package]] name = "pin-project-lite" -version = "0.1.6" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9df32da11d84f3a7d70205549562966279adb900e080fad3dccd8e64afccf0ad" +checksum = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae" [[package]] name = "pin-utils" @@ -1480,9 +1408,9 @@ checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" [[package]] name = "plotters" -version = "0.2.15" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d1685fbe7beba33de0330629da9d955ac75bd54f33d7b79f9a895590124f6bb" +checksum = "4e3bb8da247d27ae212529352020f3e5ee16e83c0c258061d27b08ab92675eeb" dependencies = [ "js-sys", "num-traits", @@ -1492,9 +1420,9 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.8" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" +checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" [[package]] name = "pretty_assertions" @@ -1506,52 +1434,17 @@ dependencies = [ "difference", ] -[[package]] -name = "proc-macro-crate" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10d4b51f154c8a7fb96fd6dad097cb74b863943ec010ac94b9fd1be8861fe1e" -dependencies = [ - "toml", -] - -[[package]] -name = "proc-macro-error" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18f33027081eba0a6d8aba6d1b1c3a3be58cbb12106341c2d5759fcd9b5277e7" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2 1.0.18", - "quote 1.0.6", - "syn 1.0.30", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a5b4b77fdb63c1eca72173d68d24501c54ab1269409f6b672c85deb18af69de" -dependencies = [ - "proc-macro2 1.0.18", - "quote 1.0.6", - "syn 1.0.30", - "syn-mid", - "version_check", -] - [[package]] name = "proc-macro-hack" -version = "0.5.16" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4" +checksum = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63" [[package]] name = "proc-macro-nested" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694" +checksum = "0afe1bd463b9e9ed51d0e0f0b50b6b146aec855c56fd182bb242388710a9b6de" [[package]] name = "proc-macro2" @@ -1564,9 +1457,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.18" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" +checksum = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3" dependencies = [ "unicode-xid 0.2.0", ] @@ -1611,11 +1504,11 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.6" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" +checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" dependencies = [ - "proc-macro2 1.0.18", + "proc-macro2 1.0.10", ] [[package]] @@ -1776,9 +1669,9 @@ dependencies = [ [[package]] name = "range-alloc" -version = "0.1.1" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a871f1e45a3a3f0c73fb60343c811238bb5143a81642e27c2ac7aac27ff01a63" +checksum = "dd5927936723a9e8b715d37d7e4b390455087c4bdf25b9f702309460577b14f9" [[package]] name = "raw-window-handle" @@ -1830,9 +1723,9 @@ checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" [[package]] name = "regex" -version = "1.3.9" +version = "1.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" +checksum = "7f6946991529684867e47d86474e3a6d0c0ab9b82d5821e314b1ede31fa3a4b3" dependencies = [ "aho-corasick", "memchr", @@ -1851,9 +1744,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.18" +version = "0.6.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" +checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" [[package]] name = "remove_dir_all" @@ -1865,11 +1758,10 @@ dependencies = [ ] [[package]] -name = "roc-cli" +name = "roc_build" version = "0.1.0" dependencies = [ "bumpalo", - "clap 3.0.0-beta.1", "im", "im-rc", "indoc", @@ -1883,7 +1775,6 @@ dependencies = [ "roc_can", "roc_collections", "roc_constrain", - "roc_editor", "roc_gen", "roc_load", "roc_module", @@ -2308,9 +2199,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.5" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +checksum = "535622e6be132bccd223f4bb2b8ac8d53cda3c7a6394944d3b2b33fb974f9d76" [[package]] name = "same-file" @@ -2344,26 +2235,26 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.111" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9124df5b40cbd380080b2cc6ab894c040a3070d995f5c9dc77e18c34a8ae37d" +checksum = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399" [[package]] name = "serde_derive" -version = "1.0.111" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f2c3ac8e6ca1e9c80b8be1023940162bf81ae3cffbb1809474152f2ce1eb250" +checksum = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c" dependencies = [ - "proc-macro2 1.0.18", - "quote 1.0.6", - "syn 1.0.30", + "proc-macro2 1.0.10", + "quote 1.0.3", + "syn 1.0.17", ] [[package]] name = "serde_json" -version = "1.0.53" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "993948e75b189211a9b31a7528f950c6adc21f9720b6438ff80a7fa2f864cea2" +checksum = "da07b57ee2623368351e9a0488bb0b261322a15a6e0ae53e243cbdc0f4208da9" dependencies = [ "itoa", "ryu", @@ -2398,9 +2289,9 @@ checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" [[package]] name = "smallvec" -version = "1.4.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" +checksum = "05720e22615919e4734f6a99ceae50d00226c3c5aca406e102ebc33298214e0a" [[package]] name = "smithay-client-toolkit" @@ -2430,6 +2321,17 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "spirv_cross" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "946216f8793f7199e3ea5b995ee8dc20a0ace1fcf46293a0ef4c17e1d046dbde" +dependencies = [ + "cc", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "spirv_cross" version = "0.20.0" @@ -2459,12 +2361,6 @@ dependencies = [ "lock_api", ] -[[package]] -name = "strsim" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" - [[package]] name = "syn" version = "0.15.44" @@ -2478,35 +2374,24 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.30" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93a56fabc59dce20fe48b6c832cc249c713e7ed88fa28b0ee0a3bfcaae5fe4e2" +checksum = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03" dependencies = [ - "proc-macro2 1.0.18", - "quote 1.0.6", + "proc-macro2 1.0.10", + "quote 1.0.3", "unicode-xid 0.2.0", ] -[[package]] -name = "syn-mid" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" -dependencies = [ - "proc-macro2 1.0.18", - "quote 1.0.6", - "syn 1.0.30", -] - [[package]] name = "synstructure" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" +checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" dependencies = [ - "proc-macro2 1.0.18", - "quote 1.0.6", - "syn 1.0.30", + "proc-macro2 1.0.10", + "quote 1.0.3", + "syn 1.0.17", "unicode-xid 0.2.0", ] @@ -2559,9 +2444,9 @@ dependencies = [ [[package]] name = "tinytemplate" -version = "1.1.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d3dc76004a03cec1c5932bca4cdc2e39aaa798e3f82363dd94f9adf6098c12f" +checksum = "57a3c6667d3e65eb1bc3aed6fd14011c6cbc3a0665218ab7f5daf040b9ec371a" dependencies = [ "serde", "serde_json", @@ -2569,9 +2454,9 @@ dependencies = [ [[package]] name = "tokio" -version = "0.2.21" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d099fa27b9702bed751524694adbe393e18b36b204da91eb1cbbbbb4a5ee2d58" +checksum = "34ef16d072d2b6dc8b4a56c70f5c5ced1a37752116f8e7c1e80c659aa7cb6713" dependencies = [ "bytes", "fnv", @@ -2587,15 +2472,6 @@ dependencies = [ "winapi 0.3.8", ] -[[package]] -name = "toml" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" -dependencies = [ - "serde", -] - [[package]] name = "ttf-parser" version = "0.6.1" @@ -2623,12 +2499,6 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" -[[package]] -name = "unicode-segmentation" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" - [[package]] name = "unicode-width" version = "0.1.7" @@ -2687,9 +2557,9 @@ dependencies = [ [[package]] name = "version_check" -version = "0.9.2" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" +checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" [[package]] name = "void" @@ -2716,9 +2586,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasm-bindgen" -version = "0.2.63" +version = "0.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c2dc4aa152834bc334f506c1a06b866416a8b6697d5c9f75b9a689c8486def0" +checksum = "2cc57ce05287f8376e998cbddfb4c8cb43b84a7ec55cf4551d7c00eef317a47f" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2726,47 +2596,47 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.63" +version = "0.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded84f06e0ed21499f6184df0e0cb3494727b0c5da89534e0fcc55c51d812101" +checksum = "d967d37bf6c16cca2973ca3af071d0a2523392e4a594548155d89a678f4237cd" dependencies = [ "bumpalo", "lazy_static", "log", - "proc-macro2 1.0.18", - "quote 1.0.6", - "syn 1.0.30", + "proc-macro2 1.0.10", + "quote 1.0.3", + "syn 1.0.17", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.63" +version = "0.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "838e423688dac18d73e31edce74ddfac468e37b1506ad163ffaf0a46f703ffe3" +checksum = "8bd151b63e1ea881bb742cd20e1d6127cef28399558f3b5d415289bc41eee3a4" dependencies = [ - "quote 1.0.6", + "quote 1.0.3", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.63" +version = "0.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3156052d8ec77142051a533cdd686cba889537b213f948cd1d20869926e68e92" +checksum = "d68a5b36eef1be7868f668632863292e37739656a80fc4b9acec7b0bd35a4931" dependencies = [ - "proc-macro2 1.0.18", - "quote 1.0.6", - "syn 1.0.30", + "proc-macro2 1.0.10", + "quote 1.0.3", + "syn 1.0.17", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.63" +version = "0.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9ba19973a58daf4db6f352eda73dc0e289493cd29fb2632eb172085b6521acd" +checksum = "daf76fe7d25ac79748a37538b7daeed1c7a6867c92d3245c12c6222e4a20d639" [[package]] name = "wayland-client" @@ -2830,9 +2700,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.40" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b72fe77fd39e4bd3eaa4412fd299a0be6b3dfe9d2597e2f1c20beb968f41d17" +checksum = "2d6f51648d8c56c366144378a33290049eafdd784071077f6fe37dae64c1c4cb" dependencies = [ "js-sys", "wasm-bindgen", @@ -2947,9 +2817,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "fa515c5163a99cc82bab70fd3bfdd36d827be85de63737b40fcef2ce084a436e" dependencies = [ "winapi 0.3.8", ] @@ -2962,14 +2832,15 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "winit" -version = "0.22.2" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e4ccbf7ddb6627828eace16cacde80fc6bf4dbb3469f88487262a02cf8e7862" +checksum = "fc53342d3d1a3d57f3949e0692d93d5a8adb7814d8683cef4a09c2b550e94246" dependencies = [ + "android_glue", "bitflags", - "cocoa", - "core-foundation", - "core-graphics", + "cocoa 0.19.1", + "core-foundation 0.6.4", + "core-graphics 0.17.3", "core-video-sys", "dispatch", "instant", @@ -2978,9 +2849,6 @@ dependencies = [ "log", "mio", "mio-extras", - "ndk", - "ndk-glue", - "ndk-sys", "objc", "parking_lot", "percent-encoding", @@ -3075,7 +2943,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d498dbd1fd7beb83c86709ae1c33ca50942889473473d287d56ce4770a18edfb" dependencies = [ - "proc-macro2 1.0.18", - "syn 1.0.30", + "proc-macro2 1.0.10", + "syn 1.0.17", "synstructure", ] diff --git a/Cargo.toml b/Cargo.toml index 6779427ab3..8de62c5b0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,11 +19,11 @@ members = [ "compiler/mono", "compiler/load", "compiler/gen", + "compiler/build", "vendor/ena", "vendor/pathfinding", "vendor/pretty", - "editor", - "cli" + "editor" ] # Optimizations based on https://deterministic.space/high-performance-rust.html diff --git a/cli/Cargo.toml b/cli/Cargo.toml index dd59b4fbb9..c11cb29fe0 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -46,6 +46,7 @@ roc_solve = { path = "../compiler/solve" } roc_mono = { path = "../compiler/mono" } roc_load = { path = "../compiler/load" } roc_gen = { path = "../compiler/gen" } +roc_build = { path = "../compiler/build" } roc_reporting = { path = "../compiler/reporting" } roc_editor = { path = "../editor" } # TODO switch to clap 3.0.0 once it's out. Tried adding clap = "~3.0.0-beta.1" and cargo wouldn't accept it diff --git a/cli/src/main.rs b/cli/src/main.rs index e934b82e77..5932ce29f6 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -14,6 +14,7 @@ use roc_gen::llvm::build::{ build_proc, build_proc_header, get_call_conventions, module_from_builtins, OptLevel, }; use roc_gen::llvm::convert::basic_type_from_layout; +use roc_build::program::build; use roc_load::file::{LoadedModule, LoadingProblem}; use roc_module::symbol::Symbol; use roc_mono::expr::{Env, Expr, PartialProc, Procs}; @@ -247,6 +248,7 @@ async fn build_file( Ok(binary_path) } + // TODO this should probably use more helper functions #[allow(clippy::cognitive_complexity)] fn gen( @@ -362,8 +364,8 @@ fn gen( let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; let layout = Layout::new(&arena, content, &subs, ptr_bytes).unwrap_or_else(|err| { panic!( - "Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", - err, subs + "Code gen error in CLI: could not convert to layout. Err was {:?}", + err ) }); diff --git a/cli/src/repl.rs b/cli/src/repl.rs index 88c3a5afbb..b65f7724ac 100644 --- a/cli/src/repl.rs +++ b/cli/src/repl.rs @@ -221,8 +221,8 @@ pub fn gen(src: &str, target: Triple, opt_level: OptLevel) -> Result<(String, St // Compute main_fn_type before moving subs to Env let layout = Layout::new(&arena, content, &subs, ptr_bytes).unwrap_or_else(|err| { panic!( - "Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", - err, subs + "Code gen error in test: could not convert to layout. Err was {:?}", + err ) }); let execution_engine = module @@ -329,7 +329,7 @@ pub fn gen(src: &str, target: Triple, opt_level: OptLevel) -> Result<(String, St if main_fn.verify(true) { fpm.run_on(&main_fn); } else { - panic!("Function {} failed LLVM verification.", main_fn_name); + panic!("Main function {} failed LLVM verification. Uncomment things near this error message for more details.", main_fn_name); } // Verify the module diff --git a/compiler/build/Cargo.toml b/compiler/build/Cargo.toml new file mode 100644 index 0000000000..b6c81d8c15 --- /dev/null +++ b/compiler/build/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "roc_build" +version = "0.1.0" +authors = ["Richard Feldman "] +edition = "2018" +license = "Apache-2.0" + +[dependencies] +roc_collections = { path = "../collections" } +roc_can = { path = "../can" } +roc_parse = { path = "../parse" } +roc_region = { path = "../region" } +roc_module = { path = "../module" } +roc_problem = { path = "../problem" } +roc_types = { path = "../types" } +roc_builtins = { path = "../builtins" } +roc_constrain = { path = "../constrain" } +roc_uniq = { path = "../uniq" } +roc_unify = { path = "../unify" } +roc_solve = { path = "../solve" } +roc_mono = { path = "../mono" } +roc_load = { path = "../load" } +roc_gen = { path = "../gen" } +roc_reporting = { path = "../reporting" } +im = "14" # im and im-rc should always have the same version! +im-rc = "14" # im and im-rc should always have the same version! +bumpalo = { version = "3.2", features = ["collections"] } +inlinable_string = "0.1.0" +tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded", "process", "io-driver"] } +# NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything. +# +# The reason for this fork is that the way Inkwell is designed, you have to use +# a particular branch (e.g. "llvm8-0") in Cargo.toml. That would be fine, except that +# breaking changes get pushed directly to that branch, which breaks our build +# without warning. +# +# We tried referencing a specific rev on TheDan64/inkwell directly (instead of branch), +# but although that worked locally, it did not work on GitHub Actions. (After a few +# hours of investigation, gave up trying to figure out why.) So this is the workaround: +# having an immutable tag on the rtfeldman/inkwell fork which points to +# a particular "release" of Inkwell. +# +# When we want to update Inkwell, we can sync up rtfeldman/inkwell to the latest +# commit of TheDan64/inkwell, push a new tag which points to the latest commit, +# change the tag value in this Cargo.toml to point to that tag, and `cargo update`. +# This way, GitHub Actions works and nobody's builds get broken. +inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release1" } +target-lexicon = "0.10" + +[dev-dependencies] +pretty_assertions = "0.5.1" +maplit = "1.0.1" +indoc = "0.3.3" +quickcheck = "0.8" +quickcheck_macros = "0.8" diff --git a/compiler/build/src/lib.rs b/compiler/build/src/lib.rs new file mode 100644 index 0000000000..15ced922ef --- /dev/null +++ b/compiler/build/src/lib.rs @@ -0,0 +1,13 @@ +#![warn(clippy::all, clippy::dbg_macro)] +// I'm skeptical that clippy:large_enum_variant is a good lint to have globally enabled. +// +// It warns about a performance problem where the only quick remediation is +// to allocate more on the heap, which has lots of tradeoffs - including making it +// long-term unclear which allocations *need* to happen for compilation's sake +// (e.g. recursive structures) versus those which were only added to appease clippy. +// +// Effectively optimizing data struture memory layout isn't a quick fix, +// and encouraging shortcuts here creates bad incentives. I would rather temporarily +// re-enable this when working on performance optimizations than have it block PRs. +#![allow(clippy::large_enum_variant)] +pub mod program; diff --git a/compiler/build/src/program.rs b/compiler/build/src/program.rs new file mode 100644 index 0000000000..14e372c884 --- /dev/null +++ b/compiler/build/src/program.rs @@ -0,0 +1,393 @@ +use bumpalo::Bump; +use inkwell::context::Context; +use inkwell::module::Linkage; +use inkwell::passes::PassManager; +use inkwell::types::BasicType; +use inkwell::OptimizationLevel; +use roc_collections::all::ImMap; +use roc_gen::layout_id::LayoutIds; +use roc_gen::llvm::build::{ + build_proc, build_proc_header, get_call_conventions, module_from_builtins, OptLevel, +}; +use roc_gen::llvm::convert::basic_type_from_layout; +use roc_load::file::LoadedModule; +use roc_module::symbol::Symbol; +use roc_mono::expr::{Env, Expr, PartialProc, Procs}; +use roc_mono::layout::{Layout, LayoutCache}; + +use inkwell::targets::{ + CodeModel, FileType, InitializationConfig, RelocMode, Target, TargetTriple, +}; +use std::path::{Path, PathBuf}; +use target_lexicon::{Architecture, OperatingSystem, Triple, Vendor}; + +// TODO how should imported modules factor into this? What if those use builtins too? +// TODO this should probably use more helper functions +// TODO make this polymorphic in the llvm functions so it can be reused for another backend. +#[allow(clippy::cognitive_complexity)] +pub fn build( + arena: &Bump, + loaded: LoadedModule, + filename: PathBuf, + target: Triple, + dest_filename: &Path, + opt_level: OptLevel, +) { + use roc_reporting::report::{can_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE}; + + let src = loaded.src; + let home = loaded.module_id; + let src_lines: Vec<&str> = src.split('\n').collect(); + let palette = DEFAULT_PALETTE; + + // Report parsing and canonicalization problems + let alloc = RocDocAllocator::new(&src_lines, home, &loaded.interns); + + for problem in loaded.can_problems.into_iter() { + let report = can_problem(&alloc, filename.clone(), problem); + let mut buf = String::new(); + + report.render_color_terminal(&mut buf, &alloc, &palette); + + println!("\n{}\n", buf); + } + + for problem in loaded.type_problems.into_iter() { + let report = type_problem(&alloc, filename.clone(), problem); + let mut buf = String::new(); + + report.render_color_terminal(&mut buf, &alloc, &palette); + + println!("\n{}\n", buf); + } + + // Look up the types and expressions of the `provided` values + + // TODO instead of hardcoding this to `main`, use the `provided` list and gen all of them. + let ident_ids = loaded.interns.all_ident_ids.get(&home).unwrap(); + let main_ident_id = *ident_ids.get_id(&"main".into()).unwrap_or_else(|| { + todo!("TODO gracefully handle the case where `main` wasn't declared in the app") + }); + let main_symbol = Symbol::new(home, main_ident_id); + let mut main_var = None; + let mut main_expr = None; + + for (symbol, var) in loaded.exposed_vars_by_symbol { + if symbol == main_symbol { + main_var = Some(var); + + break; + } + } + + let mut decls_by_id = loaded.declarations_by_id; + let home_decls = decls_by_id + .remove(&loaded.module_id) + .expect("Root module ID not found in loaded declarations_by_id"); + + // We use a loop label here so we can break all the way out of a nested + // loop inside DeclareRec if we find the expr there. + // + // https://doc.rust-lang.org/1.30.0/book/first-edition/loops.html#loop-labels + 'find_expr: for decl in home_decls.iter() { + use roc_can::def::Declaration::*; + + match decl { + Declare(def) => { + if def.pattern_vars.contains_key(&main_symbol) { + main_expr = Some(def.loc_expr.clone()); + + break 'find_expr; + } + } + + DeclareRec(defs) => { + for def in defs { + if def.pattern_vars.contains_key(&main_symbol) { + main_expr = Some(def.loc_expr.clone()); + + break 'find_expr; + } + } + } + InvalidCycle(_, _) | Builtin(_) => { + // These can never contain main. + } + } + } + + let loc_expr = main_expr.unwrap_or_else(|| { + panic!("TODO gracefully handle the case where `main` was declared but not exposed") + }); + let mut subs = loaded.solved.into_inner(); + let content = match main_var { + Some(var) => subs.get_without_compacting(var).content, + None => todo!("TODO gracefully handle the case where `main` was declared but not exposed"), + }; + + // Generate the binary + + let context = Context::create(); + let module = module_from_builtins(&context, "app"); + let builder = context.create_builder(); + let fpm = PassManager::create(&module); + + roc_gen::llvm::build::add_passes(&fpm, opt_level); + + fpm.initialize(); + + // Compute main_fn_type before moving subs to Env + let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; + let layout = Layout::new(&arena, content, &subs, ptr_bytes).unwrap_or_else(|err| { + panic!( + "Code gen error in Program: could not convert to layout. Err was {:?}", + err + ) + }); + + let main_fn_type = + basic_type_from_layout(&arena, &context, &layout, ptr_bytes).fn_type(&[], false); + let main_fn_name = "$main"; + + // Compile and add all the Procs before adding main + let mut env = roc_gen::llvm::build::Env { + arena: &arena, + builder: &builder, + context: &context, + interns: loaded.interns, + module: arena.alloc(module), + ptr_bytes, + }; + let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); + let mut layout_ids = LayoutIds::default(); + let mut procs = Procs::default(); + let mut mono_problems = std::vec::Vec::new(); + let mut mono_env = Env { + arena, + subs: &mut subs, + problems: &mut mono_problems, + home, + ident_ids: &mut ident_ids, + pointer_size: ptr_bytes, + jump_counter: arena.alloc(0), + }; + + // Add modules' decls to Procs + for (_, mut decls) in decls_by_id + .drain() + .chain(std::iter::once((loaded.module_id, home_decls))) + { + for decl in decls.drain(..) { + use roc_can::def::Declaration::*; + use roc_can::expr::Expr::*; + use roc_can::pattern::Pattern::*; + + match decl { + Declare(def) | Builtin(def) => match def.loc_pattern.value { + Identifier(symbol) => { + match def.loc_expr.value { + Closure(annotation, _, _, loc_args, boxed_body) => { + let (loc_body, ret_var) = *boxed_body; + + procs.insert_named( + &mut mono_env, + symbol, + annotation, + loc_args, + loc_body, + ret_var, + ); + } + body => { + let proc = PartialProc { + annotation: def.expr_var, + // This is a 0-arity thunk, so it has no arguments. + pattern_symbols: bumpalo::collections::Vec::new_in(arena), + body, + }; + + procs.partial_procs.insert(symbol, proc); + procs.module_thunks.insert(symbol); + } + }; + } + other => { + todo!("TODO gracefully handle Declare({:?})", other); + } + }, + DeclareRec(_defs) => { + todo!("TODO support DeclareRec"); + } + InvalidCycle(_loc_idents, _regions) => { + todo!("TODO handle InvalidCycle"); + } + } + } + } + + // Populate Procs further and get the low-level Expr from the canonical Expr + let main_body = Expr::new(&mut mono_env, loc_expr.value, &mut procs); + + let mut headers = Vec::with_capacity(procs.pending_specializations.len()); + let mut layout_cache = LayoutCache::default(); + + let (mut specializations, runtime_errors) = + roc_mono::expr::specialize_all(&mut mono_env, procs, &mut layout_cache); + + assert_eq!(runtime_errors, roc_collections::all::MutSet::default()); + + // Put this module's ident_ids back in the interns, so we can use them in env. + // This must happen *after* building the headers, because otherwise there's + // a conflicting mutable borrow on ident_ids. + env.interns.all_ident_ids.insert(home, ident_ids); + + // Add all the Proc headers to the module. + // We have to do this in a separate pass first, + // because their bodies may reference each other. + for ((symbol, layout), proc) in specializations.drain() { + let (fn_val, arg_basic_types) = + build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc); + + headers.push((proc, fn_val, arg_basic_types)); + } + + // Build each proc using its header info. + for (proc, fn_val, arg_basic_types) in headers { + // NOTE: This is here to be uncommented in case verification fails. + // (This approach means we don't have to defensively clone name here.) + // + // println!("\n\nBuilding and then verifying function {}\n\n", name); + build_proc(&env, &mut layout_ids, proc, fn_val, arg_basic_types); + + if fn_val.verify(true) { + fpm.run_on(&fn_val); + } else { + // NOTE: If this fails, uncomment the above println to debug. + panic!( + "Non-main function failed LLVM verification. Uncomment the above println to debug!" + ); + } + } + + // Add main to the module. + let cc = get_call_conventions(target.default_calling_convention().unwrap()); + let main_fn = env.module.add_function(main_fn_name, main_fn_type, None); + + main_fn.set_call_conventions(cc); + main_fn.set_linkage(Linkage::External); + + // Add main's body + let basic_block = context.append_basic_block(main_fn, "entry"); + + builder.position_at_end(basic_block); + + let ret = roc_gen::llvm::build::build_expr( + &env, + &mut layout_ids, + &ImMap::default(), + main_fn, + &main_body, + ); + + builder.build_return(Some(&ret)); + + // Uncomment this to see the module's un-optimized LLVM instruction output: + // env.module.print_to_stderr(); + + if main_fn.verify(true) { + fpm.run_on(&main_fn); + } else { + panic!("Function {} failed LLVM verification.", main_fn_name); + } + + // Verify the module + if let Err(errors) = env.module.verify() { + panic!("😱 LLVM errors when defining module: {:?}", errors); + } + + // Uncomment this to see the module's optimized LLVM instruction output: + // env.module.print_to_stderr(); + + // Emit the .o file + + // NOTE: arch_str is *not* the same as the beginning of the magic target triple + // string! For example, if it's "x86-64" here, the magic target triple string + // will begin with "x86_64" (with an underscore) instead. + let arch_str = match target.architecture { + Architecture::X86_64 => { + Target::initialize_x86(&InitializationConfig::default()); + + "x86-64" + } + Architecture::Arm(_) if cfg!(feature = "target-arm") => { + // NOTE: why not enable arm and wasm by default? + // + // We had some trouble getting them to link properly. This may be resolved in the + // future, or maybe it was just some weird configuration on one machine. + Target::initialize_arm(&InitializationConfig::default()); + + "arm" + } + Architecture::Wasm32 if cfg!(feature = "target-webassembly") => { + Target::initialize_webassembly(&InitializationConfig::default()); + + "wasm32" + } + _ => panic!( + "TODO gracefully handle unsupported target architecture: {:?}", + target.architecture + ), + }; + + let opt = OptimizationLevel::Default; + let reloc = RelocMode::Default; + let model = CodeModel::Default; + + // Best guide I've found on how to determine these magic strings: + // + // https://stackoverflow.com/questions/15036909/clang-how-to-list-supported-target-architectures + let target_triple_str = match target { + Triple { + architecture: Architecture::X86_64, + vendor: Vendor::Unknown, + operating_system: OperatingSystem::Linux, + .. + } => "x86_64-unknown-linux-gnu", + Triple { + architecture: Architecture::X86_64, + vendor: Vendor::Pc, + operating_system: OperatingSystem::Linux, + .. + } => "x86_64-pc-linux-gnu", + Triple { + architecture: Architecture::X86_64, + vendor: Vendor::Unknown, + operating_system: OperatingSystem::Darwin, + .. + } => "x86_64-unknown-darwin10", + Triple { + architecture: Architecture::X86_64, + vendor: Vendor::Apple, + operating_system: OperatingSystem::Darwin, + .. + } => "x86_64-apple-darwin10", + _ => panic!("TODO gracefully handle unsupported target: {:?}", target), + }; + let target_machine = Target::from_name(arch_str) + .unwrap() + .create_target_machine( + &TargetTriple::create(target_triple_str), + arch_str, + "+avx2", // TODO this string was used uncritically from an example, and should be reexamined + opt, + reloc, + model, + ) + .unwrap(); + + target_machine + .write_to_file(&env.module, FileType::Object, &dest_filename) + .expect("Writing .o file failed"); + + println!("\nSuccess! 🎉\n\n\t➡ {}\n", dest_filename.display()); +} diff --git a/compiler/builtins/README.md b/compiler/builtins/README.md new file mode 100644 index 0000000000..b746e0f4a4 --- /dev/null +++ b/compiler/builtins/README.md @@ -0,0 +1,26 @@ +# So you want to add a builtin? + +Builtins are the functions and modules that are implicitly imported into every module. Some of them compile down to llvm, others need to be constructed and defined. Making a new builtin means touching many files. Here is what it takes: + +### module/src/symbol.rs + +Towards the bottom of a file there is a `define_builtins!` macro being used that takes many modules and function names. The first level (`List`, `Int` ..) is the module name, and the second level is the function or value name (`reverse`, `mod` ..). If you wanted to add a `Int` function called `addTwo` go to `2 Int: "Int" => {` and inside that case add to the bottom `38 INT_ADD_TWO: "addTwo"` (assuming there are 37 existing ones). + +Some of these have `#` inside their name (`first#list`, #lt` ..). This is a trick we are doing to hide implementation details from Roc programmers. To a Roc programmer, a name with `#` in it is invalid, because `#` means everything after it is parsed to a comment. We are constructing these functions manually, so we are circumventing the parsing step and dont have such restrictions. We get to make functions and values with `#` which as a consequence are not accessible to Roc programmers. Roc programmers simply cannot reference them. + +But we can use these values and some of these are necessary for implementing builtins. For example, `List.get` returns tags, and it is not easy for us to create tags when composing LLVM. What is easier however, is: +- ..writing `List.#getUnsafe` that has the dangerous signature of `List elem, Int -> elem` in LLVM +- ..writing `List elem, Int -> Result elem [ OutOfBounds ]*` in a type safe way that uses `getUnsafe` internally, only after it checks if the `elem` at `Int` index exists. + +## Bottom level LLVM values and functions +### gen/src/llvm/build.rs +This is where bottom-level functions that need to be written as LLVM are created. If the function leads to a tag thats a good sign it should not be written here in `build.rs`. If its simple fundamental stuff like `INT_ADD` then it certainly should be written here. + +## More abstract values and functions that likely return tags. +### can/src/builtins.rs +If the function you are making is _not_ low level or returns something like a tag, then it should probably be written here by means of lower level functions written in `build.rs`. + +## Letting the compiler know these functions exist +Its one thing to actually write these functions, its _another_ thing to let the Roc compiler know they exist. You have to tell the compiler "Hey, this function exists, and it has this type signature". That happens in these modules: +### builtins/src/std.rs +### builtins/src/unique.rs \ No newline at end of file diff --git a/compiler/builtins/docs/Defaults.roc b/compiler/builtins/docs/Defaults.roc index 02e6b3ba8f..611ef249a1 100644 --- a/compiler/builtins/docs/Defaults.roc +++ b/compiler/builtins/docs/Defaults.roc @@ -3,6 +3,5 @@ interface Defaults imports [ Map.{ Map }, Set.{ Set }, - Float.{ Float }, - Int.{ Int } + Num.{ Num, Int, Float } ] diff --git a/compiler/builtins/docs/Num.roc b/compiler/builtins/docs/Num.roc index ae54162bfc..84dfc84c19 100644 --- a/compiler/builtins/docs/Num.roc +++ b/compiler/builtins/docs/Num.roc @@ -258,15 +258,12 @@ Int size : Num [ @Int size ] ## decimal floats have [some hardware support](http://speleotrove.com/decimal/) ## among the rare processors which support decimal float instructions at all. ## -## This specification covers these float formats, all of which Roc supports: +## This specification covers several float formats. Here are the ones Roc supports: ## -## - #F16 (16-bit binary float) # TODO show a table like we do with ints, with the min/max ranges ## - #F32 (32-bit binary float) ## - #F64 (64-bit binary float) -## - #F128 (128-bit binary float) ## - #D32 (32-bit decimal float) -## - #D64 (64-bit decimal float) -## - #D128 (128-bit decimal float) +## - #D64 (64-bit decimal float) # TODO show a table like we do with ints, with the min/max ranges ## ## Like #Int, it's possible for #Float operations to overflow. Like with ints, ## you'll typically get a crash when this happens. @@ -495,12 +492,21 @@ toU32 : Int * -> U32 toU64 : Int * -> U64 toU128 : Int * -> U128 -## Convert a #Num to a #F16. If the given number can't be precisely represented in a #F16, +## Convert a #Num to a #F32. If the given number can't be precisely represented in a #F32, ## there will be a loss of precision. -toF16 : Num * -> F16 toF32 : Num * -> F32 + +## Convert a #Num to a #F64. If the given number can't be precisely represented in a #F64, +## there will be a loss of precision. toF64 : Num * -> F64 -toF128 : Num * -> F128 + +## Convert a #Num to a #D32. If the given number can't be precisely represented in a #D32, +## there will be a loss of precision. +toD32 : Num * -> D32 + +## Convert a #Num to a #D64. If the given number can't be precisely represented in a #D64, +## there will be a loss of precision. +toD64 : Num * -> D64 ## Divide two integers and #Num.round the resulut. ## @@ -708,7 +714,7 @@ pi : Float * ## Divide two #Float numbers. ## -## `a / b` is shorthand for `Float.div a b`. +## `a / b` is shorthand for `Num.div a b`. ## ## Division by zero is undefined in mathematics. As such, you should make ## sure never to pass zero as the denomaintor to this function! @@ -722,12 +728,12 @@ pi : Float * ## ## >>> 5.0 / 7.0 ## -## >>> Float.div 5 7 +## >>> Num.div 5 7 ## -## `Float.div` can be convenient in pipelines. +## `Num.div` can be convenient in pipelines. ## ## >>> Float.pi -## >>> |> Float.div 2.0 +## >>> |> Num.div 2.0 #div : Float, Float -> Result Float DivByZero div = \numerator, denominator -> when numerator is diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index a86b578379..39d089bf67 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -78,46 +78,46 @@ pub fn aliases() -> MutMap { // Integer : [ @Integer ] add_alias( - Symbol::INT_INTEGER, + Symbol::NUM_INTEGER, BuiltinAlias { region: Region::zero(), vars: Vec::new(), - typ: single_private_tag(Symbol::INT_AT_INTEGER, Vec::new()), + typ: single_private_tag(Symbol::NUM_AT_INTEGER, Vec::new()), }, ); // Int : Num Integer add_alias( - Symbol::INT_INT, + Symbol::NUM_INT, BuiltinAlias { region: Region::zero(), vars: Vec::new(), typ: SolvedType::Apply( Symbol::NUM_NUM, - vec![SolvedType::Apply(Symbol::INT_INTEGER, Vec::new())], + vec![SolvedType::Apply(Symbol::NUM_INTEGER, Vec::new())], ), }, ); // FloatingPoint : [ @FloatingPoint ] add_alias( - Symbol::FLOAT_FLOATINGPOINT, + Symbol::NUM_FLOATINGPOINT, BuiltinAlias { region: Region::zero(), vars: Vec::new(), - typ: single_private_tag(Symbol::FLOAT_AT_FLOATINGPOINT, Vec::new()), + typ: single_private_tag(Symbol::NUM_AT_FLOATINGPOINT, Vec::new()), }, ); // Float : Num FloatingPoint add_alias( - Symbol::FLOAT_FLOAT, + Symbol::NUM_FLOAT, BuiltinAlias { region: Region::zero(), vars: Vec::new(), typ: SolvedType::Apply( Symbol::NUM_NUM, - vec![SolvedType::Apply(Symbol::FLOAT_FLOATINGPOINT, Vec::new())], + vec![SolvedType::Apply(Symbol::NUM_FLOATINGPOINT, Vec::new())], ), }, ); @@ -271,131 +271,137 @@ pub fn types() -> MutMap { SolvedType::Func(vec![num_type(flex(TVAR1))], Box::new(float_type())), ); - // Int module - - // isLt or (<) : Num a, Num a -> Bool + // isNegative : Num a -> Bool add_type( - Symbol::INT_LT, - SolvedType::Func(vec![int_type(), int_type()], Box::new(bool_type())), + Symbol::NUM_IS_NEGATIVE, + SolvedType::Func(vec![num_type(flex(TVAR1))], Box::new(bool_type())), ); - // equals : Int, Int -> Bool + // isPositive : Num a -> Bool add_type( - Symbol::INT_EQ_I64, - SolvedType::Func(vec![int_type(), int_type()], Box::new(bool_type())), + Symbol::NUM_IS_POSITIVE, + SolvedType::Func(vec![num_type(flex(TVAR1))], Box::new(bool_type())), ); - // notEquals : Int, Int -> Bool + // isZero : Num a -> Bool add_type( - Symbol::INT_NEQ_I64, - SolvedType::Func(vec![int_type(), int_type()], Box::new(bool_type())), + Symbol::NUM_IS_ZERO, + SolvedType::Func(vec![num_type(flex(TVAR1))], Box::new(bool_type())), ); - // abs : Int -> Int + // isEven : Num a -> Bool add_type( - Symbol::INT_ABS, - SolvedType::Func(vec![int_type()], Box::new(int_type())), + Symbol::NUM_IS_EVEN, + SolvedType::Func(vec![num_type(flex(TVAR1))], Box::new(bool_type())), ); - // highest : Int - add_type(Symbol::INT_HIGHEST, int_type()); - - // lowest : Int - add_type(Symbol::INT_LOWEST, int_type()); - - // div : Int, Int -> Int + // isOdd : Num a -> Bool add_type( - Symbol::INT_DIV_UNSAFE, - SolvedType::Func(vec![int_type(), int_type()], Box::new(int_type())), + Symbol::NUM_IS_ODD, + SolvedType::Func(vec![num_type(flex(TVAR1))], Box::new(bool_type())), ); - // rem : Int, Int -> Int - add_type( - Symbol::INT_REM_UNSAFE, - SolvedType::Func(vec![int_type(), int_type()], Box::new(int_type())), - ); + // maxInt : Int + add_type(Symbol::NUM_MAX_INT, int_type()); - // mod : Int, Int -> Result Int [ DivByZero ]* + // minInt : Int + add_type(Symbol::NUM_MIN_INT, int_type()); + + // div : Int, Int -> Result Int [ DivByZero ]* let div_by_zero = SolvedType::TagUnion( vec![(TagName::Global("DivByZero".into()), vec![])], Box::new(SolvedType::Wildcard), ); add_type( - Symbol::INT_MOD, + Symbol::NUM_DIV_INT, SolvedType::Func( vec![int_type(), int_type()], - Box::new(result_type(flex(TVAR1), div_by_zero)), + Box::new(result_type(int_type(), div_by_zero.clone())), + ), + ); + + // rem : Int, Int -> Result Int [ DivByZero ]* + add_type( + Symbol::NUM_REM, + SolvedType::Func( + vec![int_type(), int_type()], + Box::new(result_type(int_type(), div_by_zero.clone())), + ), + ); + + // mod : Int, Int -> Result Int [ DivByZero ]* + add_type( + Symbol::NUM_MOD_INT, + SolvedType::Func( + vec![int_type(), int_type()], + Box::new(result_type(int_type(), div_by_zero.clone())), ), ); // Float module - // isGt or (>) : Num a, Num a -> Bool - add_type( - Symbol::FLOAT_GT, - SolvedType::Func(vec![float_type(), float_type()], Box::new(bool_type())), - ); - - // eq or (==) : Num a, Num a -> Bool - add_type( - Symbol::FLOAT_EQ, - SolvedType::Func(vec![float_type(), float_type()], Box::new(bool_type())), - ); - // div : Float, Float -> Float add_type( - Symbol::FLOAT_DIV, - SolvedType::Func(vec![float_type(), float_type()], Box::new(float_type())), + Symbol::NUM_DIV_FLOAT, + SolvedType::Func( + vec![float_type(), float_type()], + Box::new(result_type(float_type(), div_by_zero.clone())), + ), ); - // mod : Float, Float -> Float + // mod : Float, Float -> Result Int [ DivByZero ]* add_type( - Symbol::FLOAT_MOD, - SolvedType::Func(vec![float_type(), float_type()], Box::new(float_type())), + Symbol::NUM_MOD_FLOAT, + SolvedType::Func( + vec![float_type(), float_type()], + Box::new(result_type(float_type(), div_by_zero)), + ), ); // sqrt : Float -> Float + let sqrt_of_negative = SolvedType::TagUnion( + vec![(TagName::Global("SqrtOfNegative".into()), vec![])], + Box::new(SolvedType::Wildcard), + ); + add_type( - Symbol::FLOAT_SQRT, - SolvedType::Func(vec![float_type()], Box::new(float_type())), + Symbol::NUM_SQRT, + SolvedType::Func( + vec![float_type()], + Box::new(result_type(float_type(), sqrt_of_negative)), + ), ); // round : Float -> Int add_type( - Symbol::FLOAT_ROUND, + Symbol::NUM_ROUND, SolvedType::Func(vec![float_type()], Box::new(int_type())), ); - // abs : Float -> Float - add_type( - Symbol::FLOAT_ABS, - SolvedType::Func(vec![float_type()], Box::new(float_type())), - ); - // sin : Float -> Float add_type( - Symbol::FLOAT_SIN, + Symbol::NUM_SIN, SolvedType::Func(vec![float_type()], Box::new(float_type())), ); // cos : Float -> Float add_type( - Symbol::FLOAT_COS, + Symbol::NUM_COS, SolvedType::Func(vec![float_type()], Box::new(float_type())), ); // tan : Float -> Float add_type( - Symbol::FLOAT_TAN, + Symbol::NUM_TAN, SolvedType::Func(vec![float_type()], Box::new(float_type())), ); - // highest : Float - add_type(Symbol::FLOAT_HIGHEST, float_type()); + // maxFloat : Float + add_type(Symbol::NUM_MAX_FLOAT, float_type()); - // lowest : Float - add_type(Symbol::FLOAT_LOWEST, float_type()); + // minFloat : Float + add_type(Symbol::NUM_MIN_FLOAT, float_type()); // Bool module @@ -447,11 +453,17 @@ pub fn types() -> MutMap { ), ); + // first : List elem -> Result elem [ ListWasEmpty ]* + let list_was_empty = SolvedType::TagUnion( + vec![(TagName::Global("ListWasEmpty".into()), vec![])], + Box::new(SolvedType::Wildcard), + ); + add_type( - Symbol::LIST_GET_UNSAFE, + Symbol::LIST_FIRST, SolvedType::Func( - vec![list_type(flex(TVAR1)), int_type()], - Box::new(flex(TVAR1)), + vec![list_type(flex(TVAR1))], + Box::new(result_type(flex(TVAR1), list_was_empty)), ), ); @@ -531,6 +543,15 @@ pub fn types() -> MutMap { ), ); + // append : List elem, List elem -> List elem + add_type( + Symbol::LIST_APPEND, + SolvedType::Func( + vec![list_type(flex(TVAR1)), list_type(flex(TVAR1))], + Box::new(list_type(flex(TVAR1))), + ), + ); + // len : List * -> Int add_type( Symbol::LIST_LEN, @@ -661,12 +682,12 @@ fn flex(tvar: VarId) -> SolvedType { #[inline(always)] fn float_type() -> SolvedType { - SolvedType::Apply(Symbol::FLOAT_FLOAT, Vec::new()) + SolvedType::Apply(Symbol::NUM_FLOAT, Vec::new()) } #[inline(always)] fn int_type() -> SolvedType { - SolvedType::Apply(Symbol::INT_INT, Vec::new()) + SolvedType::Apply(Symbol::NUM_INT, Vec::new()) } #[inline(always)] diff --git a/compiler/builtins/src/unique.rs b/compiler/builtins/src/unique.rs index 51203cb0c6..ff2520486b 100644 --- a/compiler/builtins/src/unique.rs +++ b/compiler/builtins/src/unique.rs @@ -153,27 +153,27 @@ pub fn aliases() -> MutMap { // Integer : [ @Integer ] add_alias( - Symbol::INT_INTEGER, + Symbol::NUM_INTEGER, BuiltinAlias { region: Region::zero(), vars: Vec::new(), - typ: single_private_tag(Symbol::INT_AT_INTEGER, Vec::new()), + typ: single_private_tag(Symbol::NUM_AT_INTEGER, Vec::new()), }, ); // FloatingPoint : [ @FloatingPoint ] add_alias( - Symbol::FLOAT_FLOATINGPOINT, + Symbol::NUM_FLOATINGPOINT, BuiltinAlias { region: Region::zero(), vars: Vec::new(), - typ: single_private_tag(Symbol::FLOAT_AT_FLOATINGPOINT, Vec::new()), + typ: single_private_tag(Symbol::NUM_AT_FLOATINGPOINT, Vec::new()), }, ); // Int : Num Integer add_alias( - Symbol::INT_INT, + Symbol::NUM_INT, BuiltinAlias { region: Region::zero(), vars: Vec::new(), @@ -181,7 +181,7 @@ pub fn aliases() -> MutMap { Symbol::NUM_NUM, vec![lift( star, - SolvedType::Apply(Symbol::INT_INTEGER, Vec::new()), + SolvedType::Apply(Symbol::NUM_INTEGER, Vec::new()), )], ), }, @@ -189,7 +189,7 @@ pub fn aliases() -> MutMap { // Float : Num FloatingPoint add_alias( - Symbol::FLOAT_FLOAT, + Symbol::NUM_FLOAT, BuiltinAlias { region: Region::zero(), vars: Vec::new(), @@ -197,7 +197,7 @@ pub fn aliases() -> MutMap { Symbol::NUM_NUM, vec![lift( star, - SolvedType::Apply(Symbol::FLOAT_FLOATINGPOINT, Vec::new()), + SolvedType::Apply(Symbol::NUM_FLOATINGPOINT, Vec::new()), )], ), }, @@ -323,34 +323,8 @@ pub fn types() -> MutMap { unique_function(vec![num_type(star1, a)], float_type(star2)) }); - // Int module - - // isLt or (<) : Num a, Num a -> Bool - add_type(Symbol::INT_LT, { - let_tvars! { u, v, w }; - unique_function(vec![int_type(u), int_type(v)], bool_type(w)) - }); - - // equals or (==) : Int, Int -> Bool - add_type(Symbol::INT_EQ_I64, { - let_tvars! { u, v, w }; - unique_function(vec![int_type(u), int_type(v)], bool_type(w)) - }); - - // not equals or (!=) : Int, Int -> Bool - add_type(Symbol::INT_NEQ_I64, { - let_tvars! { u, v, w }; - unique_function(vec![int_type(u), int_type(v)], bool_type(w)) - }); - - // abs : Int -> Int - add_type(Symbol::INT_ABS, { - let_tvars! { u, v }; - unique_function(vec![int_type(u)], int_type(v)) - }); - // rem : Attr * Int, Attr * Int -> Attr * (Result (Attr * Int) (Attr * [ DivByZero ]*)) - add_type(Symbol::INT_REM, { + add_type(Symbol::NUM_REM, { let_tvars! { star1, star2, star3, star4, star5 }; unique_function( vec![int_type(star1), int_type(star2)], @@ -358,25 +332,20 @@ pub fn types() -> MutMap { ) }); - add_type(Symbol::INT_REM_UNSAFE, { - let_tvars! { star1, star2, star3, }; - unique_function(vec![int_type(star1), int_type(star2)], int_type(star3)) - }); - - // highest : Int - add_type(Symbol::INT_HIGHEST, { + // maxInt : Int + add_type(Symbol::NUM_MAX_INT, { let_tvars! { star }; int_type(star) }); - // lowest : Int - add_type(Symbol::INT_LOWEST, { + // minInt : Int + add_type(Symbol::NUM_MIN_INT, { let_tvars! { star }; int_type(star) }); - // div or (//) : Int, Int -> Result Int [ DivByZero ]* - add_type(Symbol::INT_DIV, { + // divFloor or (//) : Int, Int -> Result Int [ DivByZero ]* + add_type(Symbol::NUM_DIV_INT, { let_tvars! { star1, star2, star3, star4, star5 }; unique_function( vec![int_type(star1), int_type(star2)], @@ -384,97 +353,95 @@ pub fn types() -> MutMap { ) }); - add_type(Symbol::INT_DIV_UNSAFE, { - let_tvars! { star1, star2, star3, }; - unique_function(vec![int_type(star1), int_type(star2)], int_type(star3)) - }); - - // mod : Int, Int -> Int - add_type(Symbol::INT_MOD, { - let_tvars! { star1, star2, star3, }; - unique_function(vec![int_type(star1), int_type(star2)], int_type(star3)) - }); - - // Float module - - // isGt or (>) : Num a, Num a -> Bool - add_type(Symbol::FLOAT_GT, { - let_tvars! { star1, star2, star3} - unique_function(vec![float_type(star1), float_type(star2)], bool_type(star3)) - }); - - // eq or (==) : Num a, Num a -> Bool - add_type(Symbol::FLOAT_EQ, { - let_tvars! { star1, star2, star3} - unique_function(vec![float_type(star1), float_type(star2)], bool_type(star3)) - }); - - // div : Float, Float -> Float - add_type(Symbol::FLOAT_DIV, { - let_tvars! { star1, star2, star3}; + // divFloat : Float, Float -> Float + add_type(Symbol::NUM_DIV_FLOAT, { + let_tvars! { star1, star2, star3, star4, star5}; unique_function( vec![float_type(star1), float_type(star2)], - float_type(star3), - ) - }); - - // mod : Float, Float -> Float - add_type(Symbol::FLOAT_MOD, { - let_tvars! { star1, star2, star3}; - unique_function( - vec![float_type(star1), float_type(star2)], - float_type(star3), + result_type(star3, float_type(star4), lift(star5, div_by_zero())), ) }); // round : Float -> Int - add_type(Symbol::FLOAT_ROUND, { + add_type(Symbol::NUM_ROUND, { let_tvars! { star1, star2 }; unique_function(vec![float_type(star1)], int_type(star2)) }); // sqrt : Float -> Float - add_type(Symbol::FLOAT_SQRT, { - let_tvars! { star1, star2 }; - unique_function(vec![float_type(star1)], float_type(star2)) - }); + let sqrt_of_negative = SolvedType::TagUnion( + vec![(TagName::Global("SqrtOfNegative".into()), vec![])], + Box::new(SolvedType::Wildcard), + ); - // abs : Float -> Float - add_type(Symbol::FLOAT_ABS, { - let_tvars! { star1, star2 }; - unique_function(vec![float_type(star1)], float_type(star2)) + add_type(Symbol::NUM_SQRT, { + let_tvars! { star1, star2, star3, star4 }; + unique_function( + vec![float_type(star1)], + result_type(star2, float_type(star3), lift(star4, sqrt_of_negative)), + ) }); // sin : Float -> Float - add_type(Symbol::FLOAT_SIN, { + add_type(Symbol::NUM_SIN, { let_tvars! { star1, star2 }; unique_function(vec![float_type(star1)], float_type(star2)) }); // cos : Float -> Float - add_type(Symbol::FLOAT_COS, { + add_type(Symbol::NUM_COS, { let_tvars! { star1, star2 }; unique_function(vec![float_type(star1)], float_type(star2)) }); // tan : Float -> Float - add_type(Symbol::FLOAT_TAN, { + add_type(Symbol::NUM_TAN, { let_tvars! { star1, star2 }; unique_function(vec![float_type(star1)], float_type(star2)) }); - // highest : Float - add_type(Symbol::FLOAT_HIGHEST, { + // maxFloat : Float + add_type(Symbol::NUM_MAX_FLOAT, { let_tvars! { star }; float_type(star) }); - // lowest : Float - add_type(Symbol::FLOAT_LOWEST, { + // minFloat : Float + add_type(Symbol::NUM_MIN_FLOAT, { let_tvars! { star }; float_type(star) }); + // isNegative : Num a -> Bool + add_type(Symbol::NUM_IS_NEGATIVE, { + let_tvars! { star1, star2, a }; + unique_function(vec![num_type(star1, a)], bool_type(star2)) + }); + + // isPositive : Num a -> Bool + add_type(Symbol::NUM_IS_POSITIVE, { + let_tvars! { star1, star2, a }; + unique_function(vec![num_type(star1, a)], bool_type(star2)) + }); + + // isZero : Num a -> Bool + add_type(Symbol::NUM_IS_ZERO, { + let_tvars! { star1, star2, a }; + unique_function(vec![num_type(star1, a)], bool_type(star2)) + }); + + // isEven : Num a -> Bool + add_type(Symbol::NUM_IS_EVEN, { + let_tvars! { star1, star2, a }; + unique_function(vec![num_type(star1, a)], bool_type(star2)) + }); + + // isOdd : Num a -> Bool + add_type(Symbol::NUM_IS_ODD, { + let_tvars! { star1, star2, a }; + unique_function(vec![num_type(star1, a)], bool_type(star2)) + }); + // Bool module // isEq or (==) : Attr * a, Attr * a -> Attr * Bool @@ -559,12 +526,6 @@ pub fn types() -> MutMap { ) }); - // is LIST_GET_UNSAFE still used? - add_type(Symbol::LIST_GET_UNSAFE, { - let_tvars! { star1, star2, a }; - unique_function(vec![list_type(star1, a), int_type(star2)], flex(a)) - }); - // set : Attr (w | u | v) (List (Attr u a)) // , Attr * Int // , Attr (u | v) a @@ -610,6 +571,28 @@ pub fn types() -> MutMap { ) }); + // reverse : Attr * (List (Attr * a)) -> Attr * (List (Attr * a)) + add_type(Symbol::LIST_REVERSE, { + let_tvars! { a, star1, star2 }; + + unique_function( + vec![SolvedType::Apply( + Symbol::ATTR_ATTR, + vec![ + flex(star1), + SolvedType::Apply(Symbol::LIST_LIST, vec![flex(a)]), + ], + )], + SolvedType::Apply( + Symbol::ATTR_ATTR, + vec![ + boolean(star2), + SolvedType::Apply(Symbol::LIST_LIST, vec![flex(a)]), + ], + ), + ) + }); + // To repeat an item, it must be shared! // // repeat : Attr * Int @@ -630,22 +613,31 @@ pub fn types() -> MutMap { ) }); - // reverse : Attr * (List (Attr * a)) -> Attr * (List (Attr * a)) - add_type(Symbol::LIST_REVERSE, { - let_tvars! { a, star1, star2 }; + // append : Attr * (List (Attr * a)), Attr * (List (Attr * a)) -> Attr * (List (Attr * a)) + add_type(Symbol::LIST_APPEND, { + let_tvars! { a, star1, star2, star3 }; unique_function( - vec![SolvedType::Apply( - Symbol::ATTR_ATTR, - vec![ - flex(star1), - SolvedType::Apply(Symbol::LIST_LIST, vec![flex(a)]), - ], - )], + vec![ + SolvedType::Apply( + Symbol::ATTR_ATTR, + vec![ + flex(star1), + SolvedType::Apply(Symbol::LIST_LIST, vec![flex(a)]), + ], + ), + SolvedType::Apply( + Symbol::ATTR_ATTR, + vec![ + flex(star2), + SolvedType::Apply(Symbol::LIST_LIST, vec![flex(a)]), + ], + ), + ], SolvedType::Apply( Symbol::ATTR_ATTR, vec![ - boolean(star2), + boolean(star3), SolvedType::Apply(Symbol::LIST_LIST, vec![flex(a)]), ], ), @@ -1044,7 +1036,7 @@ fn lift(u: VarId, a: SolvedType) -> SolvedType { fn float_type(u: VarId) -> SolvedType { SolvedType::Apply( Symbol::ATTR_ATTR, - vec![flex(u), SolvedType::Apply(Symbol::FLOAT_FLOAT, Vec::new())], + vec![flex(u), SolvedType::Apply(Symbol::NUM_FLOAT, Vec::new())], ) } @@ -1052,7 +1044,7 @@ fn float_type(u: VarId) -> SolvedType { fn int_type(u: VarId) -> SolvedType { SolvedType::Apply( Symbol::ATTR_ATTR, - vec![flex(u), SolvedType::Apply(Symbol::INT_INT, Vec::new())], + vec![flex(u), SolvedType::Apply(Symbol::NUM_INT, Vec::new())], ) } diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index 285a92fe71..16ecd070e5 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -1,12 +1,10 @@ use crate::env::Env; use crate::scope::Scope; use roc_collections::all::{MutMap, MutSet, SendMap}; -use roc_module::ident::Ident; -use roc_module::ident::{Lowercase, TagName}; +use roc_module::ident::{Ident, Lowercase, TagName}; use roc_module::symbol::Symbol; use roc_parse::ast::{AssignedField, Tag, TypeAnnotation}; -use roc_region::all::Located; -use roc_region::all::Region; +use roc_region::all::{Located, Region}; use roc_types::subs::{VarStore, Variable}; use roc_types::types::{Alias, Problem, Type}; diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index e14549ec8a..3d51f46f9b 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -1,11 +1,31 @@ +use crate::def::Def; +use crate::expr::Expr::*; use crate::expr::{Expr, Recursive}; -use roc_collections::all::MutMap; +use crate::pattern::Pattern; +use roc_collections::all::{MutMap, SendMap}; use roc_module::ident::TagName; -use roc_module::operator::CalledVia; +use roc_module::low_level::LowLevel; use roc_module::symbol::Symbol; use roc_region::all::{Located, Region}; use roc_types::subs::{VarStore, Variable}; +macro_rules! defs { + (@single $($x:tt)*) => (()); + (@count $($rest:expr),*) => (<[()]>::len(&[$(defs!(@single $rest)),*])); + + ($var_store:expr; $($key:expr => $func:expr,)+) => { defs!($var_store; $($key => $func),+) }; + ($var_store:expr; $($key:expr => $func:expr),*) => { + { + let _cap = defs!(@count $($key),*); + let mut _map = ::std::collections::HashMap::with_capacity_and_hasher(_cap, roc_collections::all::default_hasher()); + $( + let _ = _map.insert($key, $func($key, $var_store)); + )* + _map + } + }; +} + /// Some builtins cannot be constructed in code gen alone, and need to be defined /// as separate Roc defs. For example, List.get has this type: /// @@ -23,245 +43,699 @@ use roc_types::subs::{VarStore, Variable}; /// delegates to the compiler-internal List.getUnsafe function to do the actual /// lookup (if the bounds check passed). That internal function is hardcoded in code gen, /// which works fine because it doesn't involve any open tag unions. -pub fn builtin_defs(var_store: &mut VarStore) -> MutMap { - mut_map! { - Symbol::LIST_GET => list_get(var_store), - Symbol::LIST_FIRST => list_first(var_store), - Symbol::INT_DIV => int_div(var_store), - Symbol::INT_ABS => int_abs(var_store), - Symbol::INT_REM => int_rem(var_store), - Symbol::INT_IS_ODD => int_is_odd(var_store), - Symbol::INT_IS_EVEN => int_is_even(var_store), - Symbol::INT_IS_ZERO => int_is_zero(var_store), - Symbol::INT_IS_POSITIVE => int_is_positive(var_store), - Symbol::INT_IS_NEGATIVE => int_is_negative(var_store), - Symbol::FLOAT_IS_POSITIVE => float_is_positive(var_store), - Symbol::FLOAT_IS_NEGATIVE => float_is_negative(var_store), - Symbol::FLOAT_IS_ZERO => float_is_zero(var_store), - Symbol::FLOAT_TAN => float_tan(var_store), +pub fn builtin_defs(var_store: &mut VarStore) -> MutMap { + defs! { var_store; + Symbol::BOOL_EQ => bool_eq, + Symbol::BOOL_NEQ => bool_neq, + Symbol::BOOL_AND => bool_and, + Symbol::BOOL_OR => bool_or, + Symbol::BOOL_NOT => bool_not, + Symbol::LIST_LEN => list_len, + Symbol::LIST_GET => list_get, + Symbol::LIST_SET => list_set, + Symbol::LIST_PUSH => list_push, + Symbol::LIST_FIRST => list_first, + Symbol::LIST_IS_EMPTY => list_is_empty, + Symbol::LIST_SINGLE => list_single, + Symbol::LIST_REPEAT => list_repeat, + Symbol::LIST_REVERSE => list_reverse, + Symbol::LIST_APPEND => list_append, + Symbol::NUM_ADD => num_add, + Symbol::NUM_SUB => num_sub, + Symbol::NUM_MUL => num_mul, + Symbol::NUM_GT => num_gt, + Symbol::NUM_GTE => num_gte, + Symbol::NUM_LT => num_lt, + Symbol::NUM_LTE => num_lte, + Symbol::NUM_SIN => num_sin, + Symbol::NUM_COS => num_cos, + Symbol::NUM_TAN => num_tan, + Symbol::NUM_DIV_FLOAT => num_div_float, + Symbol::NUM_DIV_INT => num_div_int, + Symbol::NUM_ABS => num_abs, + Symbol::NUM_NEG => num_neg, + Symbol::NUM_REM => num_rem, + Symbol::NUM_SQRT => num_sqrt, + Symbol::NUM_ROUND => num_round, + Symbol::NUM_IS_ODD => num_is_odd, + Symbol::NUM_IS_EVEN => num_is_even, + Symbol::NUM_IS_ZERO => num_is_zero, + Symbol::NUM_IS_POSITIVE => num_is_positive, + Symbol::NUM_IS_NEGATIVE => num_is_negative, + Symbol::NUM_TO_FLOAT => num_to_float, } } -/// Float.tan : Float -> Float -fn float_tan(var_store: &mut VarStore) -> Expr { - use crate::expr::Expr::*; +/// Bool.isEq : val, val -> Bool +fn bool_eq(symbol: Symbol, var_store: &mut VarStore) -> Def { + let arg_var = var_store.fresh(); + let bool_var = var_store.fresh(); + let body = RunLowLevel { + op: LowLevel::Eq, + args: vec![(arg_var, Var(Symbol::ARG_1)), (arg_var, Var(Symbol::ARG_2))], + ret_var: bool_var, + }; - let body = call( - Symbol::FLOAT_DIV, - vec![ - call( - Symbol::FLOAT_SIN, - vec![Var(Symbol::FLOAT_TAN_ARG)], - var_store, + defn( + symbol, + vec![(arg_var, Symbol::ARG_1), (arg_var, Symbol::ARG_2)], + var_store, + body, + bool_var, + ) +} + +/// Bool.isNotEq : val, val -> Bool +fn bool_neq(symbol: Symbol, var_store: &mut VarStore) -> Def { + let arg_var = var_store.fresh(); + let bool_var = var_store.fresh(); + let body = RunLowLevel { + op: LowLevel::NotEq, + args: vec![(arg_var, Var(Symbol::ARG_1)), (arg_var, Var(Symbol::ARG_2))], + ret_var: bool_var, + }; + + defn( + symbol, + vec![(arg_var, Symbol::ARG_1), (arg_var, Symbol::ARG_2)], + var_store, + body, + bool_var, + ) +} + +/// Bool.or : Bool, Bool -> Bool +fn bool_or(symbol: Symbol, var_store: &mut VarStore) -> Def { + let bool_var = var_store.fresh(); + let body = RunLowLevel { + op: LowLevel::Or, + args: vec![ + (bool_var, Var(Symbol::ARG_1)), + (bool_var, Var(Symbol::ARG_2)), + ], + ret_var: bool_var, + }; + + defn( + symbol, + vec![(bool_var, Symbol::ARG_1), (bool_var, Symbol::ARG_2)], + var_store, + body, + bool_var, + ) +} + +/// Bool.not : Bool -> Bool +fn bool_not(symbol: Symbol, var_store: &mut VarStore) -> Def { + let bool_var = var_store.fresh(); + let body = RunLowLevel { + op: LowLevel::Not, + args: vec![(bool_var, Var(Symbol::ARG_1))], + ret_var: bool_var, + }; + + defn( + symbol, + vec![(bool_var, Symbol::ARG_1)], + var_store, + body, + bool_var, + ) +} + +/// Bool.and : Bool, Bool -> Bool +fn bool_and(symbol: Symbol, var_store: &mut VarStore) -> Def { + let bool_var = var_store.fresh(); + let body = RunLowLevel { + op: LowLevel::And, + args: vec![ + (bool_var, Var(Symbol::ARG_1)), + (bool_var, Var(Symbol::ARG_2)), + ], + ret_var: var_store.fresh(), + }; + + defn( + symbol, + vec![(bool_var, Symbol::ARG_1), (bool_var, Symbol::ARG_2)], + var_store, + body, + bool_var, + ) +} + +/// Num a, Num a -> Num a +fn num_binop(symbol: Symbol, var_store: &mut VarStore, op: LowLevel) -> Def { + let num_var = var_store.fresh(); + let body = RunLowLevel { + op, + args: vec![(num_var, Var(Symbol::ARG_1)), (num_var, Var(Symbol::ARG_2))], + ret_var: num_var, + }; + + defn( + symbol, + vec![(num_var, Symbol::ARG_1), (num_var, Symbol::ARG_2)], + var_store, + body, + num_var, + ) +} + +/// Num a, Num a -> Bool +fn num_bool_binop(symbol: Symbol, var_store: &mut VarStore, op: LowLevel) -> Def { + let num_var = var_store.fresh(); + let bool_var = var_store.fresh(); + let body = RunLowLevel { + op, + args: vec![(num_var, Var(Symbol::ARG_1)), (num_var, Var(Symbol::ARG_2))], + ret_var: bool_var, + }; + + defn( + symbol, + vec![(num_var, Symbol::ARG_1), (num_var, Symbol::ARG_2)], + var_store, + body, + bool_var, + ) +} + +/// Num.add : Num a, Num a -> Num a +fn num_add(symbol: Symbol, var_store: &mut VarStore) -> Def { + num_binop(symbol, var_store, LowLevel::NumAdd) +} + +/// Num.sub : Num a, Num a -> Num a +fn num_sub(symbol: Symbol, var_store: &mut VarStore) -> Def { + num_binop(symbol, var_store, LowLevel::NumSub) +} + +/// Num.mul : Num a, Num a -> Num a +fn num_mul(symbol: Symbol, var_store: &mut VarStore) -> Def { + num_binop(symbol, var_store, LowLevel::NumMul) +} + +/// Num.isGt : Num a, Num a -> Bool +fn num_gt(symbol: Symbol, var_store: &mut VarStore) -> Def { + num_bool_binop(symbol, var_store, LowLevel::NumGt) +} + +/// Num.isGte : Num a, Num a -> Bool +fn num_gte(symbol: Symbol, var_store: &mut VarStore) -> Def { + num_bool_binop(symbol, var_store, LowLevel::NumGte) +} + +/// Num.isLt : Num a, Num a -> Bool +fn num_lt(symbol: Symbol, var_store: &mut VarStore) -> Def { + num_bool_binop(symbol, var_store, LowLevel::NumLt) +} + +/// Num.isLte : Num a, Num a -> Num a +fn num_lte(symbol: Symbol, var_store: &mut VarStore) -> Def { + num_bool_binop(symbol, var_store, LowLevel::NumLte) +} + +/// Num.sin : Float -> Float +fn num_sin(symbol: Symbol, var_store: &mut VarStore) -> Def { + let float_var = var_store.fresh(); + let body = RunLowLevel { + op: LowLevel::NumSin, + args: vec![(float_var, Var(Symbol::ARG_1))], + ret_var: float_var, + }; + + defn( + symbol, + vec![(float_var, Symbol::ARG_1)], + var_store, + body, + float_var, + ) +} + +/// Num.cos : Float -> Float +fn num_cos(symbol: Symbol, var_store: &mut VarStore) -> Def { + let float_var = var_store.fresh(); + let body = RunLowLevel { + op: LowLevel::NumCos, + args: vec![(float_var, Var(Symbol::ARG_1))], + ret_var: float_var, + }; + + defn( + symbol, + vec![(float_var, Symbol::ARG_1)], + var_store, + body, + float_var, + ) +} + +/// Num.tan : Float -> Float +fn num_tan(symbol: Symbol, var_store: &mut VarStore) -> Def { + let float_var = var_store.fresh(); + let body = RunLowLevel { + op: LowLevel::NumDivUnchecked, + args: vec![ + ( + float_var, + RunLowLevel { + op: LowLevel::NumSin, + args: vec![(float_var, Var(Symbol::ARG_1))], + ret_var: float_var, + }, ), - call( - Symbol::FLOAT_COS, - vec![Var(Symbol::FLOAT_TAN_ARG)], - var_store, + ( + float_var, + RunLowLevel { + op: LowLevel::NumCos, + args: vec![(float_var, Var(Symbol::ARG_1))], + ret_var: float_var, + }, ), ], - var_store, - ); + ret_var: float_var, + }; defn( - Symbol::FLOAT_TAN, - vec![Symbol::FLOAT_TAN_ARG], + symbol, + vec![(float_var, Symbol::ARG_1)], var_store, body, + float_var, ) } -/// Float.isZero : Float -> Bool -fn float_is_zero(var_store: &mut VarStore) -> Expr { - use crate::expr::Expr::*; +/// Num.isZero : Num * -> Bool +fn num_is_zero(symbol: Symbol, var_store: &mut VarStore) -> Def { + let arg_var = var_store.fresh(); + let bool_var = var_store.fresh(); + let unbound_zero_var = var_store.fresh(); - let body = call( - Symbol::FLOAT_EQ, - vec![ - Float(var_store.fresh(), 0.0), - Var(Symbol::FLOAT_IS_ZERO_ARG), + let body = RunLowLevel { + op: LowLevel::Eq, + args: vec![ + (arg_var, Var(Symbol::ARG_1)), + (arg_var, Num(unbound_zero_var, 0)), ], - var_store, - ); + ret_var: bool_var, + }; defn( - Symbol::FLOAT_IS_ZERO, - vec![Symbol::FLOAT_IS_ZERO_ARG], + symbol, + vec![(arg_var, Symbol::ARG_1)], var_store, body, + bool_var, ) } -/// Float.isNegative : Float -> Bool -fn float_is_negative(var_store: &mut VarStore) -> Expr { - use crate::expr::Expr::*; +/// Num.isNegative : Num * -> Bool +fn num_is_negative(symbol: Symbol, var_store: &mut VarStore) -> Def { + let arg_var = var_store.fresh(); + let bool_var = var_store.fresh(); + let unbound_zero_var = var_store.fresh(); - let body = call( - Symbol::FLOAT_GT, - vec![ - Float(var_store.fresh(), 0.0), - Var(Symbol::FLOAT_IS_NEGATIVE_ARG), + let body = RunLowLevel { + op: LowLevel::NumGt, + args: vec![ + (arg_var, Num(unbound_zero_var, 0)), + (arg_var, Var(Symbol::ARG_1)), ], - var_store, - ); + ret_var: bool_var, + }; defn( - Symbol::FLOAT_IS_NEGATIVE, - vec![Symbol::FLOAT_IS_NEGATIVE_ARG], + symbol, + vec![(arg_var, Symbol::ARG_1)], var_store, body, + bool_var, ) } -/// Float.isPositive : Float -> Bool -fn float_is_positive(var_store: &mut VarStore) -> Expr { - use crate::expr::Expr::*; +/// Num.isPositive : Num * -> Bool +fn num_is_positive(symbol: Symbol, var_store: &mut VarStore) -> Def { + let arg_var = var_store.fresh(); + let bool_var = var_store.fresh(); + let unbound_zero_var = var_store.fresh(); - let body = call( - Symbol::FLOAT_GT, - vec![ - Var(Symbol::FLOAT_IS_POSITIVE_ARG), - Float(var_store.fresh(), 0.0), + let body = RunLowLevel { + op: LowLevel::NumGt, + args: vec![ + (arg_var, Var(Symbol::ARG_1)), + (arg_var, Num(unbound_zero_var, 0)), ], - var_store, - ); + ret_var: bool_var, + }; defn( - Symbol::FLOAT_IS_POSITIVE, - vec![Symbol::FLOAT_IS_POSITIVE_ARG], + symbol, + vec![(arg_var, Symbol::ARG_1)], var_store, body, + bool_var, ) } -/// Int.isNegative : Int -> Bool -fn int_is_negative(var_store: &mut VarStore) -> Expr { - use crate::expr::Expr::*; +/// Num.isOdd : Num * -> Bool +fn num_is_odd(symbol: Symbol, var_store: &mut VarStore) -> Def { + let arg_var = var_store.fresh(); + let bool_var = var_store.fresh(); + let unbound_two_var = var_store.fresh(); - let body = call( - Symbol::NUM_LT, - vec![Var(Symbol::INT_IS_NEGATIVE_ARG), Int(var_store.fresh(), 0)], - var_store, - ); - - defn( - Symbol::INT_IS_NEGATIVE, - vec![Symbol::INT_IS_NEGATIVE_ARG], - var_store, - body, - ) -} - -/// Int.isPositive : Int -> Bool -fn int_is_positive(var_store: &mut VarStore) -> Expr { - use crate::expr::Expr::*; - - let body = call( - Symbol::NUM_GT, - vec![Var(Symbol::INT_IS_POSITIVE_ARG), Int(var_store.fresh(), 0)], - var_store, - ); - - defn( - Symbol::INT_IS_POSITIVE, - vec![Symbol::INT_IS_POSITIVE_ARG], - var_store, - body, - ) -} - -/// Int.isZero : Int -> Bool -fn int_is_zero(var_store: &mut VarStore) -> Expr { - use crate::expr::Expr::*; - - let body = call( - Symbol::INT_EQ_I64, - vec![Var(Symbol::INT_IS_ZERO_ARG), Int(var_store.fresh(), 0)], - var_store, - ); - - defn( - Symbol::INT_IS_ZERO, - vec![Symbol::INT_IS_ZERO_ARG], - var_store, - body, - ) -} - -/// Int.isOdd : Int -> Bool -fn int_is_odd(var_store: &mut VarStore) -> Expr { - use crate::expr::Expr::*; - - let body = call( - Symbol::INT_EQ_I64, - vec![ - call( - Symbol::INT_REM_UNSAFE, - vec![Var(Symbol::INT_IS_ODD_ARG), Int(var_store.fresh(), 2)], - var_store, + let body = RunLowLevel { + op: LowLevel::Eq, + args: vec![ + (arg_var, Int(var_store.fresh(), 1)), + ( + arg_var, + RunLowLevel { + op: LowLevel::NumRemUnchecked, + args: vec![ + (arg_var, Var(Symbol::ARG_1)), + (arg_var, Num(unbound_two_var, 2)), + ], + ret_var: arg_var, + }, ), - Int(var_store.fresh(), 1), ], - var_store, - ); + ret_var: bool_var, + }; defn( - Symbol::INT_IS_ODD, - vec![Symbol::INT_IS_ODD_ARG], + symbol, + vec![(arg_var, Symbol::ARG_1)], var_store, body, + bool_var, ) } -/// Int.isEven : Int -> Bool -fn int_is_even(var_store: &mut VarStore) -> Expr { - use crate::expr::Expr::*; +/// Num.isEven : Num * -> Bool +fn num_is_even(symbol: Symbol, var_store: &mut VarStore) -> Def { + let arg_var = var_store.fresh(); + let arg_num_var = var_store.fresh(); + let bool_var = var_store.fresh(); - let body = call( - Symbol::INT_EQ_I64, - vec![ - call( - Symbol::INT_REM_UNSAFE, - vec![Var(Symbol::INT_IS_EVEN_ARG), Int(var_store.fresh(), 2)], - var_store, + let body = RunLowLevel { + op: LowLevel::Eq, + args: vec![ + (arg_var, Num(arg_num_var, 0)), + ( + arg_var, + RunLowLevel { + op: LowLevel::NumRemUnchecked, + args: vec![ + (arg_var, Var(Symbol::ARG_1)), + (arg_var, Num(arg_num_var, 2)), + ], + ret_var: arg_var, + }, ), - Int(var_store.fresh(), 0), ], - var_store, - ); + ret_var: bool_var, + }; defn( - Symbol::INT_IS_EVEN, - vec![Symbol::INT_IS_EVEN_ARG], + symbol, + vec![(arg_var, Symbol::ARG_1)], var_store, body, + bool_var, + ) +} + +/// Num.toFloat : Num * -> Float +fn num_to_float(symbol: Symbol, var_store: &mut VarStore) -> Def { + let arg_var = var_store.fresh(); + let float_var = var_store.fresh(); + + let body = RunLowLevel { + op: LowLevel::NumToFloat, + args: vec![(arg_var, Var(Symbol::ARG_1))], + ret_var: float_var, + }; + + defn( + symbol, + vec![(arg_var, Symbol::ARG_1)], + var_store, + body, + float_var, + ) +} + +/// Num.sqrt : Float -> Result Float [ SqrtOfNegative ]* +fn num_sqrt(symbol: Symbol, var_store: &mut VarStore) -> Def { + let bool_var = var_store.fresh(); + let float_var = var_store.fresh(); + let unbound_zero_var = var_store.fresh(); + let ret_var = var_store.fresh(); + + let body = If { + branch_var: ret_var, + cond_var: bool_var, + branches: vec![( + // if-condition + no_region( + // Num.neq denominator 0 + RunLowLevel { + op: LowLevel::NotEq, + args: vec![ + (float_var, Var(Symbol::ARG_1)), + (float_var, Float(unbound_zero_var, 0.0)), + ], + ret_var: bool_var, + }, + ), + // denominator was not zero + no_region( + // Ok (Float.#divUnchecked numerator denominator) + tag( + "Ok", + vec![ + // Num.#divUnchecked numerator denominator + RunLowLevel { + op: LowLevel::NumSqrtUnchecked, + args: vec![(float_var, Var(Symbol::ARG_1))], + ret_var: float_var, + }, + ], + var_store, + ), + ), + )], + final_else: Box::new( + // denominator was zero + no_region(tag( + "Err", + vec![tag("DivByZero", Vec::new(), var_store)], + var_store, + )), + ), + }; + + defn( + symbol, + vec![(float_var, Symbol::ARG_1)], + var_store, + body, + ret_var, + ) +} + +/// Num.round : Float -> Int +fn num_round(symbol: Symbol, var_store: &mut VarStore) -> Def { + let float_var = var_store.fresh(); + let int_var = var_store.fresh(); + + let body = RunLowLevel { + op: LowLevel::NumRound, + args: vec![(float_var, Var(Symbol::ARG_1))], + ret_var: int_var, + }; + + defn( + symbol, + vec![(float_var, Symbol::ARG_1)], + var_store, + body, + int_var, + ) +} + +/// List.isEmpty : List * -> Bool +fn list_is_empty(symbol: Symbol, var_store: &mut VarStore) -> Def { + let list_var = var_store.fresh(); + let bool_var = var_store.fresh(); + let len_var = var_store.fresh(); + let unbound_zero_var = var_store.fresh(); + + let body = RunLowLevel { + op: LowLevel::Eq, + args: vec![ + (len_var, Num(unbound_zero_var, 0)), + ( + len_var, + RunLowLevel { + op: LowLevel::ListLen, + args: vec![(list_var, Var(Symbol::ARG_1))], + ret_var: len_var, + }, + ), + ], + ret_var: bool_var, + }; + + defn( + symbol, + vec![(list_var, Symbol::ARG_1)], + var_store, + body, + bool_var, + ) +} + +/// List.reverse : List elem -> List elem +fn list_reverse(symbol: Symbol, var_store: &mut VarStore) -> Def { + let list_var = var_store.fresh(); + + let body = RunLowLevel { + op: LowLevel::ListReverse, + args: vec![(list_var, Var(Symbol::ARG_1))], + ret_var: list_var, + }; + + defn( + symbol, + vec![(list_var, Symbol::ARG_1)], + var_store, + body, + list_var, + ) +} + +/// List.append : List elem, List elem -> List elem +fn list_append(symbol: Symbol, var_store: &mut VarStore) -> Def { + let list_var = var_store.fresh(); + + let body = RunLowLevel { + op: LowLevel::ListAppend, + args: vec![ + (list_var, Var(Symbol::ARG_1)), + (list_var, Var(Symbol::ARG_2)), + ], + ret_var: list_var, + }; + + defn( + symbol, + vec![(list_var, Symbol::ARG_1), (list_var, Symbol::ARG_2)], + var_store, + body, + list_var, + ) +} + +/// List.repeat : elem, Int -> List elem +fn list_repeat(symbol: Symbol, var_store: &mut VarStore) -> Def { + let elem_var = var_store.fresh(); + let len_var = var_store.fresh(); + let list_var = var_store.fresh(); + + let body = RunLowLevel { + op: LowLevel::ListRepeat, + args: vec![ + (elem_var, Var(Symbol::ARG_1)), + (len_var, Var(Symbol::ARG_2)), + ], + ret_var: list_var, + }; + + defn( + symbol, + vec![(elem_var, Symbol::ARG_1), (len_var, Symbol::ARG_2)], + var_store, + body, + list_var, + ) +} + +/// List.single : elem -> List elem +fn list_single(symbol: Symbol, var_store: &mut VarStore) -> Def { + let elem_var = var_store.fresh(); + let list_var = var_store.fresh(); + + let body = RunLowLevel { + op: LowLevel::ListSingle, + args: vec![(elem_var, Var(Symbol::ARG_1))], + ret_var: list_var, + }; + + defn( + symbol, + vec![(elem_var, Symbol::ARG_1)], + var_store, + body, + list_var, + ) +} + +/// List.len : List * -> Int +fn list_len(symbol: Symbol, var_store: &mut VarStore) -> Def { + let len_var = var_store.fresh(); + let list_var = var_store.fresh(); + + let body = RunLowLevel { + op: LowLevel::ListLen, + args: vec![(list_var, Var(Symbol::ARG_1))], + ret_var: len_var, + }; + + defn( + symbol, + vec![(list_var, Symbol::ARG_1)], + var_store, + body, + len_var, ) } /// List.get : List elem, Int -> Result elem [ OutOfBounds ]* -fn list_get(var_store: &mut VarStore) -> Expr { - use crate::expr::Expr::*; +fn list_get(symbol: Symbol, var_store: &mut VarStore) -> Def { + let arg_list = Symbol::ARG_1; + let arg_index = Symbol::ARG_2; + let bool_var = var_store.fresh(); + let len_var = var_store.fresh(); + let list_var = var_store.fresh(); + let elem_var = var_store.fresh(); + let ret_var = var_store.fresh(); - // Perform a bounds check. If it passes, delegate to List.#getUnsafe + // Perform a bounds check. If it passes, run LowLevel::ListGetUnsafe let body = If { - cond_var: var_store.fresh(), - branch_var: var_store.fresh(), + cond_var: bool_var, + branch_var: ret_var, branches: vec![( // if-condition no_region( // index < List.len list - call( - Symbol::NUM_LT, - vec![ - Var(Symbol::LIST_GET_ARG_INDEX), - call( - Symbol::LIST_LEN, - vec![Var(Symbol::LIST_GET_ARG_LIST)], - var_store, + RunLowLevel { + op: LowLevel::NumLt, + args: vec![ + (len_var, Var(arg_index)), + ( + len_var, + RunLowLevel { + op: LowLevel::ListLen, + args: vec![(list_var, Var(arg_list))], + ret_var: len_var, + }, ), ], - var_store, - ), + ret_var: bool_var, + }, ), // then-branch no_region( @@ -269,22 +743,12 @@ fn list_get(var_store: &mut VarStore) -> Expr { tag( "Ok", vec![ - // List.getUnsafe list index - Call( - Box::new(( - var_store.fresh(), - no_region(Var(Symbol::LIST_GET_UNSAFE)), - var_store.fresh(), - )), - vec![ - (var_store.fresh(), no_region(Var(Symbol::LIST_GET_ARG_LIST))), - ( - var_store.fresh(), - no_region(Var(Symbol::LIST_GET_ARG_INDEX)), - ), - ], - CalledVia::Space, - ), + // List#getUnsafe list index + RunLowLevel { + op: LowLevel::ListGetUnsafe, + args: vec![(list_var, Var(arg_list)), (len_var, Var(arg_index))], + ret_var: elem_var, + }, ], var_store, ), @@ -304,42 +768,144 @@ fn list_get(var_store: &mut VarStore) -> Expr { }; defn( - Symbol::LIST_GET, - vec![Symbol::LIST_GET_ARG_LIST, Symbol::LIST_GET_ARG_INDEX], + symbol, + vec![(list_var, Symbol::ARG_1), (len_var, Symbol::ARG_2)], var_store, body, + ret_var, ) } -/// Int.rem : Int, Int -> Int -fn int_rem(var_store: &mut VarStore) -> Expr { - use crate::expr::Expr::*; +/// List.set : List elem, Int, elem -> List elem +fn list_set(symbol: Symbol, var_store: &mut VarStore) -> Def { + let arg_list = Symbol::ARG_1; + let arg_index = Symbol::ARG_2; + let arg_elem = Symbol::ARG_3; + let bool_var = var_store.fresh(); + let len_var = var_store.fresh(); + let list_var = var_store.fresh(); + let elem_var = var_store.fresh(); + let ret_var = var_store.fresh(); + + // Perform a bounds check. If it passes, run LowLevel::ListSet. + // Otherwise, return the list unmodified. + let body = If { + cond_var: bool_var, + branch_var: ret_var, + branches: vec![( + // if-condition + no_region( + // index < List.len list + RunLowLevel { + op: LowLevel::NumLt, + args: vec![ + (len_var, Var(arg_index)), + ( + len_var, + RunLowLevel { + op: LowLevel::ListLen, + args: vec![(list_var, Var(arg_list))], + ret_var: len_var, + }, + ), + ], + ret_var: bool_var, + }, + ), + // then-branch + no_region( + // List.setUnsafe list index + RunLowLevel { + op: LowLevel::ListSet, + args: vec![ + (list_var, Var(arg_list)), + (len_var, Var(arg_index)), + (elem_var, Var(arg_elem)), + ], + ret_var: list_var, + }, + ), + )], + final_else: Box::new( + // else-branch + no_region(Var(arg_list)), + ), + }; + + defn( + symbol, + vec![ + (list_var, Symbol::ARG_1), + (len_var, Symbol::ARG_2), + (elem_var, Symbol::ARG_3), + ], + var_store, + body, + ret_var, + ) +} + +/// List.push : List elem, elem -> List elem +fn list_push(symbol: Symbol, var_store: &mut VarStore) -> Def { + let list_var = var_store.fresh(); + let elem_var = var_store.fresh(); + + let body = RunLowLevel { + op: LowLevel::ListPush, + args: vec![ + (list_var, Var(Symbol::ARG_1)), + (elem_var, Var(Symbol::ARG_2)), + ], + ret_var: list_var, + }; + + defn( + symbol, + vec![(list_var, Symbol::ARG_1), (elem_var, Symbol::ARG_2)], + var_store, + body, + list_var, + ) +} + +/// Num.rem : Int, Int -> Result Int [ DivByZero ]* +fn num_rem(symbol: Symbol, var_store: &mut VarStore) -> Def { + let num_var = var_store.fresh(); + let unbound_zero_var = var_store.fresh(); + let bool_var = var_store.fresh(); + let ret_var = var_store.fresh(); let body = If { - branch_var: var_store.fresh(), - cond_var: var_store.fresh(), + branch_var: ret_var, + cond_var: bool_var, branches: vec![( // if condition no_region( - // Int.neq arg1 0 - call( - Symbol::INT_NEQ_I64, - vec![Var(Symbol::INT_REM_ARG_1), (Int(var_store.fresh(), 0))], - var_store, - ), + // Num.isNeq arg2 0 + RunLowLevel { + op: LowLevel::NotEq, + args: vec![ + (num_var, Var(Symbol::ARG_2)), + (num_var, Num(unbound_zero_var, 0)), + ], + ret_var: bool_var, + }, ), // arg1 was not zero no_region( - // Ok (Int.#remUnsafe arg0 arg1) + // Ok (Int.#remUnsafe arg1 arg2) tag( "Ok", vec![ - // Int.#remUnsafe arg0 arg1 - call( - Symbol::INT_REM_UNSAFE, - vec![Var(Symbol::INT_REM_ARG_0), Var(Symbol::INT_REM_ARG_1)], - var_store, - ), + // Num.#remUnsafe arg1 arg2 + RunLowLevel { + op: LowLevel::NumRemUnchecked, + args: vec![ + (num_var, Var(Symbol::ARG_1)), + (num_var, Var(Symbol::ARG_2)), + ], + ret_var: num_var, + }, ], var_store, ), @@ -353,82 +919,90 @@ fn int_rem(var_store: &mut VarStore) -> Expr { }; defn( - Symbol::INT_REM, - vec![Symbol::INT_REM_ARG_0, Symbol::INT_REM_ARG_1], + symbol, + vec![(num_var, Symbol::ARG_1), (num_var, Symbol::ARG_2)], var_store, body, + ret_var, ) } -/// Int.abs : Int -> Int -fn int_abs(var_store: &mut VarStore) -> Expr { - use crate::expr::Expr::*; +/// Num.neg : Num a -> Num a +fn num_neg(symbol: Symbol, var_store: &mut VarStore) -> Def { + let num_var = var_store.fresh(); - let body = If { - branch_var: var_store.fresh(), - cond_var: var_store.fresh(), - branches: vec![( - // if-condition - no_region( - // Int.isLt 0 n - // 0 < n - call( - Symbol::INT_LT, - vec![Int(var_store.fresh(), 0), Var(Symbol::INT_ABS_ARG)], - var_store, - ), - ), - // int is at least 0, so just pass it along - no_region(Var(Symbol::INT_ABS_ARG)), - )], - final_else: Box::new( - // int is below 0, so negate it. - no_region(call( - Symbol::NUM_NEG, - vec![Var(Symbol::INT_ABS_ARG)], - var_store, - )), - ), + let body = RunLowLevel { + op: LowLevel::NumNeg, + args: vec![(num_var, Var(Symbol::ARG_1))], + ret_var: num_var, }; - defn(Symbol::INT_ABS, vec![Symbol::INT_ABS_ARG], var_store, body) + defn( + symbol, + vec![(num_var, Symbol::ARG_1)], + var_store, + body, + num_var, + ) } -/// Int.div : Int, Int -> Result Int [ DivByZero ]* -fn int_div(var_store: &mut VarStore) -> Expr { - use crate::expr::Expr::*; +/// Num.abs : Num a -> Num a +fn num_abs(symbol: Symbol, var_store: &mut VarStore) -> Def { + let num_var = var_store.fresh(); + + let body = RunLowLevel { + op: LowLevel::NumAbs, + args: vec![(num_var, Var(Symbol::ARG_1))], + ret_var: num_var, + }; + + defn( + symbol, + vec![(num_var, Symbol::ARG_1)], + var_store, + body, + num_var, + ) +} + +/// Num.div : Float, Float -> Result Float [ DivByZero ]* +fn num_div_float(symbol: Symbol, var_store: &mut VarStore) -> Def { + let bool_var = var_store.fresh(); + let num_var = var_store.fresh(); + let unbound_zero_var = var_store.fresh(); + let ret_var = var_store.fresh(); let body = If { - branch_var: var_store.fresh(), - cond_var: var_store.fresh(), + branch_var: ret_var, + cond_var: bool_var, branches: vec![( // if-condition no_region( - // Int.neq denominator 0 - call( - Symbol::INT_NEQ_I64, - vec![ - Var(Symbol::INT_DIV_ARG_DENOMINATOR), - (Int(var_store.fresh(), 0)), + // Num.neq denominator 0 + RunLowLevel { + op: LowLevel::NotEq, + args: vec![ + (num_var, Var(Symbol::ARG_2)), + (num_var, Float(unbound_zero_var, 0.0)), ], - var_store, - ), + ret_var: bool_var, + }, ), // denominator was not zero no_region( - // Ok (Int.#divUnsafe numerator denominator) + // Ok (Float.#divUnchecked numerator denominator) tag( "Ok", vec![ - // Int.#divUnsafe numerator denominator - call( - Symbol::INT_DIV_UNSAFE, - vec![ - Var(Symbol::INT_DIV_ARG_NUMERATOR), - Var(Symbol::INT_DIV_ARG_DENOMINATOR), + // Num.#divUnchecked numerator denominator + RunLowLevel { + op: LowLevel::NumDivUnchecked, + args: vec![ + (num_var, Var(Symbol::ARG_1)), + (num_var, Var(Symbol::ARG_2)), ], - var_store, - ), + ret_var: num_var, + }, ], var_store, ), @@ -445,36 +1019,127 @@ fn int_div(var_store: &mut VarStore) -> Expr { }; defn( - Symbol::INT_DIV, - vec![ - Symbol::INT_DIV_ARG_NUMERATOR, - Symbol::INT_DIV_ARG_DENOMINATOR, - ], + symbol, + vec![(num_var, Symbol::ARG_1), (num_var, Symbol::ARG_2)], var_store, body, + ret_var, + ) +} + +/// Num.div : Int, Int -> Result Int [ DivByZero ]* +fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def { + let bool_var = var_store.fresh(); + let num_var = var_store.fresh(); + let unbound_zero_var = var_store.fresh(); + let ret_var = var_store.fresh(); + + let body = If { + branch_var: ret_var, + cond_var: bool_var, + branches: vec![( + // if-condition + no_region( + // Num.neq denominator 0 + RunLowLevel { + op: LowLevel::NotEq, + args: vec![ + (num_var, Var(Symbol::ARG_2)), + (num_var, Int(unbound_zero_var, 0)), + ], + ret_var: bool_var, + }, + ), + // denominator was not zero + no_region( + // Ok (Int.#divUnchecked numerator denominator) + tag( + "Ok", + vec![ + // Num.#divUnchecked numerator denominator + RunLowLevel { + op: LowLevel::NumDivUnchecked, + args: vec![ + (num_var, Var(Symbol::ARG_1)), + (num_var, Var(Symbol::ARG_2)), + ], + ret_var: num_var, + }, + ], + var_store, + ), + ), + )], + final_else: Box::new( + // denominator was zero + no_region(tag( + "Err", + vec![tag("DivByZero", Vec::new(), var_store)], + var_store, + )), + ), + }; + + defn( + symbol, + vec![(num_var, Symbol::ARG_1), (num_var, Symbol::ARG_2)], + var_store, + body, + ret_var, ) } /// List.first : List elem -> Result elem [ ListWasEmpty ]* -fn list_first(var_store: &mut VarStore) -> Expr { - use crate::expr::Expr::*; +fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def { + let bool_var = var_store.fresh(); + let list_var = var_store.fresh(); + let len_var = var_store.fresh(); + let zero_var = var_store.fresh(); + let list_elem_var = var_store.fresh(); + let ret_var = var_store.fresh(); // Perform a bounds check. If it passes, delegate to List.getUnsafe. let body = If { - // TODO Use "when" instead of "if" so that we can have False be the first branch. - // We want that for branch prediction; usually we expect the list to be nonempty. - cond_var: var_store.fresh(), - branch_var: var_store.fresh(), + cond_var: bool_var, + branch_var: ret_var, branches: vec![( // if-condition no_region( - // List.isEmpty list - call( - Symbol::LIST_IS_EMPTY, - vec![Var(Symbol::LIST_FIRST_ARG)], + // List.len list != 0 + RunLowLevel { + op: LowLevel::NotEq, + args: vec![ + (len_var, Int(zero_var, 0)), + ( + len_var, + RunLowLevel { + op: LowLevel::ListLen, + args: vec![(list_var, Var(Symbol::ARG_1))], + ret_var: len_var, + }, + ), + ], + ret_var: bool_var, + }, + ), + // list was not empty + no_region( + // Ok (List.#getUnsafe list 0) + tag( + "Ok", + vec![ + // List.#getUnsafe list 0 + RunLowLevel { + op: LowLevel::ListGetUnsafe, + args: vec![(list_var, Var(Symbol::ARG_1)), (len_var, Int(zero_var, 0))], + ret_var: list_elem_var, + }, + ], var_store, ), ), + )], + final_else: Box::new( // list was empty no_region( // Err ListWasEmpty @@ -484,32 +1149,15 @@ fn list_first(var_store: &mut VarStore) -> Expr { var_store, ), ), - )], - final_else: Box::new( - // list was not empty - no_region( - // Ok (List.#getUnsafe list 0) - tag( - "Ok", - vec![ - // List.#getUnsafe list 0 - call( - Symbol::LIST_GET_UNSAFE, - vec![(Var(Symbol::LIST_FIRST_ARG)), (Int(var_store.fresh(), 0))], - var_store, - ), - ], - var_store, - ), - ), ), }; defn( - Symbol::LIST_FIRST, - vec![Symbol::LIST_FIRST_ARG], + symbol, + vec![(list_var, Symbol::ARG_1)], var_store, body, + ret_var, ) } @@ -535,35 +1183,39 @@ fn tag(name: &'static str, args: Vec, var_store: &mut VarStore) -> Expr { } #[inline(always)] -fn call(symbol: Symbol, args: Vec, var_store: &mut VarStore) -> Expr { - Expr::Call( - Box::new(( - var_store.fresh(), - no_region(Expr::Var(symbol)), - var_store.fresh(), - )), - args.into_iter() - .map(|expr| (var_store.fresh(), no_region(expr))) - .collect::)>>(), - CalledVia::Space, - ) -} - -#[inline(always)] -fn defn(fn_name: Symbol, args: Vec, var_store: &mut VarStore, body: Expr) -> Expr { - use crate::expr::Expr::*; +fn defn( + fn_name: Symbol, + args: Vec<(Variable, Symbol)>, + var_store: &mut VarStore, + body: Expr, + ret_var: Variable, +) -> Def { use crate::pattern::Pattern::*; let closure_args = args .into_iter() - .map(|symbol| (var_store.fresh(), no_region(Identifier(symbol)))) + .map(|(var, symbol)| (var, no_region(Identifier(symbol)))) .collect(); - Closure( + let expr = Closure( var_store.fresh(), fn_name, Recursive::NotRecursive, closure_args, - Box::new((no_region(body), var_store.fresh())), - ) + Box::new((no_region(body), ret_var)), + ); + + Def { + loc_pattern: Located { + region: Region::zero(), + value: Pattern::Identifier(fn_name), + }, + loc_expr: Located { + region: Region::zero(), + value: expr, + }, + expr_var: var_store.fresh(), + pattern_vars: SendMap::default(), + annotation: None, + } } diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index d8194bef68..79f71f6608 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -10,7 +10,7 @@ use crate::pattern::{bindings_from_patterns, canonicalize_pattern, Pattern}; use crate::procedure::References; use crate::scope::Scope; use roc_collections::all::{default_hasher, ImMap, ImSet, MutMap, MutSet, SendMap}; -use roc_module::ident::{Ident, Lowercase}; +use roc_module::ident::Lowercase; use roc_module::symbol::Symbol; use roc_parse::ast; use roc_parse::pattern::PatternType; @@ -41,9 +41,7 @@ pub struct Annotation { #[derive(Debug)] pub struct CanDefs { - // TODO don't store the Ident in here (lots of cloning!) - instead, - // make refs_by_symbol be something like MutMap - pub refs_by_symbol: MutMap, References)>, + pub refs_by_symbol: MutMap, pub can_defs_by_symbol: MutMap, pub aliases: SendMap, } @@ -79,7 +77,10 @@ enum PendingDef<'a> { ann: &'a Located>, }, - ShadowedAlias, + /// An invalid alias, that is ignored in the rest of the pipeline + /// e.g. a shadowed alias, or a definition like `MyAlias 1 : Int` + /// with an incorrect pattern + InvalidAlias, } #[derive(Clone, Debug, PartialEq)] @@ -87,10 +88,8 @@ enum PendingDef<'a> { pub enum Declaration { Declare(Def), DeclareRec(Vec), - InvalidCycle( - Vec>, - Vec<(Region /* pattern */, Region /* expr */)>, - ), + Builtin(Def), + InvalidCycle(Vec, Vec<(Region /* pattern */, Region /* expr */)>), } impl Declaration { @@ -100,6 +99,7 @@ impl Declaration { Declare(_) => 1, DeclareRec(defs) => defs.len(), InvalidCycle(_, _) => 0, + Builtin(_) => 0, } } } @@ -153,7 +153,7 @@ pub fn canonicalize_defs<'a>( match iter.peek() { Some(Located { value: Body(body_pattern, body_expr), - .. + region: body_region, }) => { if pattern.value.equivalent(&body_pattern.value) { iter.next(); @@ -167,6 +167,10 @@ pub fn canonicalize_defs<'a>( &mut scope, pattern_type, ) + } else if loc_def.region.lines_between(body_region) > 1 { + // there is a line of whitespace between the annotation and the body + // treat annotation and body separately + to_pending_def(env, var_store, &loc_def.value, &mut scope, pattern_type) } else { // the pattern of the annotation does not match the pattern of the body directly below it env.problems.push(Problem::SignatureDefMismatch { @@ -557,17 +561,18 @@ pub fn sort_can_defs( if is_invalid_cycle { // We want to show the entire cycle in the error message, so expand it out. - let mut loc_idents_in_cycle: Vec> = Vec::new(); + let mut loc_symbols = Vec::new(); for symbol in cycle { - let refs = refs_by_symbol.get(&symbol).unwrap_or_else(|| { - panic!( - "Symbol not found in refs_by_symbol: {:?} - refs_by_symbol was: {:?}", - symbol, refs_by_symbol - ) - }); - - loc_idents_in_cycle.push(refs.0.clone()); + match refs_by_symbol.get(&symbol) { + None => unreachable!( + r#"Symbol `{:?}` not found in refs_by_symbol! refs_by_symbol was: {:?}"#, + symbol, refs_by_symbol + ), + Some((region, _)) => { + loc_symbols.push(Located::at(*region, symbol)); + } + } } let mut regions = Vec::with_capacity(can_defs_by_symbol.len()); @@ -575,16 +580,19 @@ pub fn sort_can_defs( regions.push((def.loc_pattern.region, def.loc_expr.region)); } - // Sort them to make the report more helpful. - loc_idents_in_cycle.sort(); + // Sort them by line number to make the report more helpful. + loc_symbols.sort(); regions.sort(); + let symbols_in_cycle: Vec = + loc_symbols.into_iter().map(|s| s.value).collect(); + problems.push(Problem::RuntimeError(RuntimeError::CircularDef( - loc_idents_in_cycle.clone(), + symbols_in_cycle.clone(), regions.clone(), ))); - declarations.push(Declaration::InvalidCycle(loc_idents_in_cycle, regions)); + declarations.push(Declaration::InvalidCycle(symbols_in_cycle, regions)); } else { // slightly inefficient, because we know this becomes exactly one DeclareRec already group_to_declaration( @@ -717,6 +725,7 @@ fn pattern_to_vars_by_symbol( | FloatLiteral(_) | StrLiteral(_) | Underscore + | MalformedPattern(_, _) | UnsupportedPattern(_) => {} Shadowed(_, _) => {} @@ -733,7 +742,7 @@ fn canonicalize_pending_def<'a>( scope: &mut Scope, can_defs_by_symbol: &mut MutMap, var_store: &mut VarStore, - refs_by_symbol: &mut MutMap, References)>, + refs_by_symbol: &mut MutMap, aliases: &mut SendMap, ) -> Output { use PendingDef::*; @@ -899,9 +908,8 @@ fn canonicalize_pending_def<'a>( .union(&can_ann.introduced_variables); } - ShadowedAlias => { - // Since this alias was shadowed, it gets ignored and has no - // effect on the output. + InvalidAlias => { + // invalid aliases (shadowed, incorrect patterns) get ignored } TypedBody(loc_pattern, loc_can_pattern, loc_ann, loc_expr) => { let ann = @@ -1001,7 +1009,7 @@ fn canonicalize_pending_def<'a>( // Store the referenced locals in the refs_by_symbol map, so we can later figure out // which defined names reference each other. - for (ident, (symbol, region)) in scope.idents() { + for (_, (symbol, region)) in scope.idents() { if !vars_by_symbol.contains_key(&symbol) { continue; } @@ -1016,16 +1024,7 @@ fn canonicalize_pending_def<'a>( can_output.references.clone() }; - refs_by_symbol.insert( - *symbol, - ( - Located { - value: ident.clone(), - region: *region, - }, - refs, - ), - ); + refs_by_symbol.insert(*symbol, (*region, refs)); can_defs_by_symbol.insert( *symbol, @@ -1146,23 +1145,7 @@ fn canonicalize_pending_def<'a>( can_output.references.clone() }; - let ident = env - .ident_ids - .get_name(symbol.ident_id()) - .unwrap_or_else(|| { - panic!("Could not find {:?} in env.ident_ids", symbol); - }); - - refs_by_symbol.insert( - symbol, - ( - Located { - value: ident.clone().into(), - region, - }, - refs, - ), - ); + refs_by_symbol.insert(symbol, (region, refs)); can_defs_by_symbol.insert( symbol, @@ -1258,6 +1241,10 @@ fn decl_to_let( Declaration::InvalidCycle(symbols, regions) => { Expr::RuntimeError(RuntimeError::CircularDef(symbols, regions)) } + Declaration::Builtin(_) => { + // Builtins should only be added to top-level decls, not to let-exprs! + unreachable!() + } } } @@ -1365,7 +1352,13 @@ fn to_pending_def<'a>( }); } _ => { - panic!("TODO gracefully handle an invalid pattern appearing where a type alias rigid var should be."); + // any other pattern in this position is a syntax error. + env.problems.push(Problem::InvalidAliasRigid { + alias_name: symbol, + region: loc_var.region, + }); + + return PendingDef::InvalidAlias; } } } @@ -1386,7 +1379,7 @@ fn to_pending_def<'a>( shadow: loc_shadowed_symbol, }); - PendingDef::ShadowedAlias + PendingDef::InvalidAlias } } } diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 35bf2a14e2..bb597511ec 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -1,4 +1,5 @@ use crate::annotation::IntroducedVariables; +use crate::builtins::builtin_defs; use crate::def::{can_defs_with_return, Def}; use crate::env::Env; use crate::num::{ @@ -10,6 +11,7 @@ use crate::procedure::References; use crate::scope::Scope; use roc_collections::all::{ImSet, MutMap, MutSet, SendMap}; use roc_module::ident::{Lowercase, TagName}; +use roc_module::low_level::LowLevel; use roc_module::operator::CalledVia; use roc_module::symbol::Symbol; use roc_parse::ast; @@ -20,7 +22,6 @@ use roc_types::subs::{VarStore, Variable}; use roc_types::types::Alias; use std::fmt::Debug; use std::i64; -use std::ops::Neg; #[derive(Clone, Default, Debug, PartialEq)] pub struct Output { @@ -89,6 +90,11 @@ pub enum Expr { Vec<(Variable, Located)>, CalledVia, ), + RunLowLevel { + op: LowLevel, + args: Vec<(Variable, Expr)>, + ret_var: Variable, + }, Closure( Variable, @@ -177,12 +183,13 @@ pub fn canonicalize_expr<'a>( let (expr, output) = match expr { ast::Expr::Num(string) => { - let answer = num_expr_from_result(var_store, finish_parsing_int(*string), env); + let answer = num_expr_from_result(var_store, finish_parsing_int(*string), region, env); (answer, Output::default()) } ast::Expr::Float(string) => { - let answer = float_expr_from_result(var_store, finish_parsing_float(string), env); + let answer = + float_expr_from_result(var_store, finish_parsing_float(string), region, env); (answer, Output::default()) } @@ -623,13 +630,9 @@ pub fn canonicalize_expr<'a>( base, is_negative, } => { - let mut result = finish_parsing_base(string, *base); - - if *is_negative { - result = result.map(i64::neg); - } - - let answer = int_expr_from_result(var_store, result, env); + // the minus sign is added before parsing, to get correct overflow/underflow behavior + let result = finish_parsing_base(string, *base, *is_negative); + let answer = int_expr_from_result(var_store, result, region, *base, env); (answer, Output::default()) } @@ -1007,3 +1010,290 @@ fn canonicalize_lookup( (can_expr, output) } + +/// Currently uses the heuristic of "only inline if it's a builtin" +pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) -> Expr { + use Expr::*; + + match expr { + // Num stores the `a` variable in `Num a`. Not the same as the variable + // stored in Int and Float below, which is strictly for better error messages + other @ Num(_, _) + | other @ Int(_, _) + | other @ Float(_, _) + | other @ Str(_) + | other @ BlockStr(_) + | other @ RuntimeError(_) + | other @ EmptyRecord + | other @ Accessor { .. } + | other @ Update { .. } + | other @ Var(_) + | other @ RunLowLevel { .. } => other, + + List { + elem_var, + loc_elems, + } => { + let mut new_elems = Vec::with_capacity(loc_elems.len()); + + for loc_elem in loc_elems { + let value = inline_calls(var_store, scope, loc_elem.value); + + new_elems.push(Located { + value, + region: loc_elem.region, + }); + } + + List { + elem_var, + loc_elems: new_elems, + } + } + // Branching + When { + cond_var, + expr_var, + region, + loc_cond, + branches, + } => { + let loc_cond = Box::new(Located { + region: loc_cond.region, + value: inline_calls(var_store, scope, loc_cond.value), + }); + + let mut new_branches = Vec::with_capacity(branches.len()); + + for branch in branches { + let value = Located { + value: inline_calls(var_store, scope, branch.value.value), + region: branch.value.region, + }; + let guard = match branch.guard { + Some(loc_expr) => Some(Located { + region: loc_expr.region, + value: inline_calls(var_store, scope, loc_expr.value), + }), + None => None, + }; + let new_branch = WhenBranch { + patterns: branch.patterns, + value, + guard, + }; + + new_branches.push(new_branch); + } + + When { + cond_var, + expr_var, + region, + loc_cond, + branches: new_branches, + } + } + If { + cond_var, + branch_var, + branches, + final_else, + } => { + let mut new_branches = Vec::with_capacity(branches.len()); + + for (loc_cond, loc_expr) in branches { + let loc_cond = Located { + value: inline_calls(var_store, scope, loc_cond.value), + region: loc_cond.region, + }; + + let loc_expr = Located { + value: inline_calls(var_store, scope, loc_expr.value), + region: loc_expr.region, + }; + + new_branches.push((loc_cond, loc_expr)); + } + + let final_else = Box::new(Located { + region: final_else.region, + value: inline_calls(var_store, scope, final_else.value), + }); + + If { + cond_var, + branch_var, + branches: new_branches, + final_else, + } + } + + LetRec(defs, loc_expr, var, aliases) => { + let mut new_defs = Vec::with_capacity(defs.len()); + + for def in defs { + new_defs.push(Def { + loc_pattern: def.loc_pattern, + loc_expr: Located { + region: def.loc_expr.region, + value: inline_calls(var_store, scope, def.loc_expr.value), + }, + expr_var: def.expr_var, + pattern_vars: def.pattern_vars, + annotation: def.annotation, + }); + } + + let loc_expr = Located { + region: loc_expr.region, + value: inline_calls(var_store, scope, loc_expr.value), + }; + + LetRec(new_defs, Box::new(loc_expr), var, aliases) + } + + LetNonRec(def, loc_expr, var, aliases) => { + let def = Def { + loc_pattern: def.loc_pattern, + loc_expr: Located { + region: def.loc_expr.region, + value: inline_calls(var_store, scope, def.loc_expr.value), + }, + expr_var: def.expr_var, + pattern_vars: def.pattern_vars, + annotation: def.annotation, + }; + + let loc_expr = Located { + region: loc_expr.region, + value: inline_calls(var_store, scope, loc_expr.value), + }; + + LetNonRec(Box::new(def), Box::new(loc_expr), var, aliases) + } + + Closure(var, symbol, recursive, patterns, boxed_expr) => { + let (loc_expr, expr_var) = *boxed_expr; + let loc_expr = Located { + value: inline_calls(var_store, scope, loc_expr.value), + region: loc_expr.region, + }; + + Closure( + var, + symbol, + recursive, + patterns, + Box::new((loc_expr, expr_var)), + ) + } + + Record { record_var, fields } => { + todo!( + "Inlining for Record with record_var {:?} and fields {:?}", + record_var, + fields + ); + } + + Access { + record_var, + ext_var, + field_var, + loc_expr, + field, + } => { + todo!("Inlining for Access with record_var {:?}, ext_var {:?}, field_var {:?}, loc_expr {:?}, field {:?}", record_var, ext_var, field_var, loc_expr, field); + } + + Tag { + variant_var, + ext_var, + name, + arguments, + } => { + todo!( + "Inlining for Tag with variant_var {:?}, ext_var {:?}, name {:?}, arguments {:?}", + variant_var, + ext_var, + name, + arguments + ); + } + + Call(boxed_tuple, args, called_via) => { + let (fn_var, loc_expr, expr_var) = *boxed_tuple; + + match loc_expr.value { + Var(symbol) if symbol.is_builtin() => match builtin_defs(var_store).get(&symbol) { + Some(Def { + loc_expr: + Located { + value: Closure(_var, _, recursive, params, boxed_body), + .. + }, + .. + }) => { + debug_assert_eq!(*recursive, Recursive::NotRecursive); + + // Since this is a canonicalized Expr, we should have + // already detected any arity mismatches and replaced this + // with a RuntimeError if there was a mismatch. + debug_assert_eq!(params.len(), args.len()); + + // Start with the function's body as the answer. + let (mut loc_answer, _body_var) = *boxed_body.clone(); + + // Wrap the body in one LetNonRec for each argument, + // such that at the end we have all the arguments in + // scope with the values the caller provided. + for ((_param_var, loc_pattern), (expr_var, loc_expr)) in + params.iter().cloned().zip(args.into_iter()).rev() + { + // TODO get the correct vars into here. + // Not sure if param_var should be involved. + let pattern_vars = SendMap::default(); + + // TODO get the actual correct aliases + let aliases = SendMap::default(); + + let def = Def { + loc_pattern, + loc_expr, + expr_var, + pattern_vars, + annotation: None, + }; + + loc_answer = Located { + region: Region::zero(), + value: LetNonRec( + Box::new(def), + Box::new(loc_answer), + var_store.fresh(), + aliases, + ), + }; + } + + loc_answer.value + } + Some(_) => { + unreachable!("Tried to inline a non-function"); + } + None => { + unreachable!( + "Tried to inline a builtin that wasn't registered: {:?}", + symbol + ); + } + }, + _ => { + // For now, we only inline calls to builtins. Leave this alone! + Call(Box::new((fn_var, loc_expr, expr_var)), args, called_via) + } + } + } + } +} diff --git a/compiler/can/src/lib.rs b/compiler/can/src/lib.rs index d5c81f482a..44c7bf3faa 100644 --- a/compiler/can/src/lib.rs +++ b/compiler/can/src/lib.rs @@ -24,6 +24,3 @@ pub mod pattern; pub mod procedure; pub mod scope; pub mod string; - -#[macro_use] -extern crate roc_collections; diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index ebe55d9a25..38b9947d4f 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -137,11 +137,16 @@ pub fn canonicalize_module_defs<'a>( let mut references = MutSet::default(); - // Gather up all the symbols that were referenced across all the defs. + // Gather up all the symbols that were referenced across all the defs' lookups. for symbol in output.references.lookups.iter() { references.insert(*symbol); } + // Gather up all the symbols that were referenced across all the defs' calls. + for symbol in output.references.calls.iter() { + references.insert(*symbol); + } + // Gather up all the symbols that were referenced from other modules. for symbol in env.referenced_symbols.iter() { references.insert(*symbol); @@ -151,6 +156,7 @@ pub fn canonicalize_module_defs<'a>( (Ok(declarations), output) => { use crate::def::Declaration::*; + // Record the variables for all exposed symbols. let mut exposed_vars_by_symbol = Vec::with_capacity(exposed_symbols.len()); for decl in declarations.iter() { @@ -193,6 +199,14 @@ pub fn canonicalize_module_defs<'a>( InvalidCycle(identifiers, _) => { panic!("TODO gracefully handle potentially attempting to expose invalid cyclic defs {:?}" , identifiers); } + Builtin(def) => { + // Builtins cannot be exposed in module declarations. + // This should never happen! + debug_assert!(def + .pattern_vars + .iter() + .all(|(symbol, _)| !exposed_symbols.contains(symbol))); + } } } @@ -222,6 +236,11 @@ pub fn canonicalize_module_defs<'a>( references.insert(symbol); } + // Incorporate any remaining output.calls entries into references. + for symbol in output.references.calls { + references.insert(symbol); + } + Ok(ModuleOutput { aliases, rigid_variables, diff --git a/compiler/can/src/num.rs b/compiler/can/src/num.rs index 7fa1528d13..9a0b810a6f 100644 --- a/compiler/can/src/num.rs +++ b/compiler/can/src/num.rs @@ -3,22 +3,30 @@ use crate::expr::Expr; use roc_parse::ast::Base; use roc_problem::can::Problem; use roc_problem::can::RuntimeError::*; +use roc_problem::can::{FloatErrorKind, IntErrorKind}; +use roc_region::all::Region; use roc_types::subs::VarStore; use std::i64; +// TODO use rust's integer parsing again +// +// We're waiting for libcore here, see https://github.com/rust-lang/rust/issues/22639 +// There is a nightly API for exposing the parse error. + #[inline(always)] pub fn num_expr_from_result( var_store: &mut VarStore, - result: Result, + result: Result, + region: Region, env: &mut Env, ) -> Expr { match result { Ok(int) => Expr::Num(var_store.fresh(), int), - Err(raw) => { + Err((raw, error)) => { // (Num *) compiles to Int if it doesn't // get specialized to something else first, // so use int's overflow bounds here. - let runtime_error = IntOutsideRange(raw.into()); + let runtime_error = InvalidInt(error, Base::Decimal, region, raw.into()); env.problem(Problem::RuntimeError(runtime_error.clone())); @@ -30,14 +38,16 @@ pub fn num_expr_from_result( #[inline(always)] pub fn int_expr_from_result( var_store: &mut VarStore, - result: Result, + result: Result, + region: Region, + base: Base, env: &mut Env, ) -> Expr { // Int stores a variable to generate better error messages match result { Ok(int) => Expr::Int(var_store.fresh(), int), - Err(raw) => { - let runtime_error = IntOutsideRange(raw.into()); + Err((raw, error)) => { + let runtime_error = InvalidInt(error, base, region, raw.into()); env.problem(Problem::RuntimeError(runtime_error.clone())); @@ -49,14 +59,15 @@ pub fn int_expr_from_result( #[inline(always)] pub fn float_expr_from_result( var_store: &mut VarStore, - result: Result, + result: Result, + region: Region, env: &mut Env, ) -> Expr { // Float stores a variable to generate better error messages match result { Ok(float) => Expr::Float(var_store.fresh(), float), - Err(raw) => { - let runtime_error = FloatOutsideRange(raw.into()); + Err((raw, error)) => { + let runtime_error = InvalidFloat(error, region, raw.into()); env.problem(Problem::RuntimeError(runtime_error.clone())); @@ -66,28 +77,183 @@ pub fn float_expr_from_result( } #[inline(always)] -pub fn finish_parsing_int(raw: &str) -> Result { +pub fn finish_parsing_int(raw: &str) -> Result { // Ignore underscores. - raw.replace("_", "").parse::().map_err(|_| raw) + let radix = 10; + from_str_radix::(raw.replace("_", "").as_str(), radix).map_err(|e| (raw, e.kind)) } #[inline(always)] -pub fn finish_parsing_base(raw: &str, base: Base) -> Result { +pub fn finish_parsing_base( + raw: &str, + base: Base, + is_negative: bool, +) -> Result { let radix = match base { Base::Hex => 16, + Base::Decimal => 10, Base::Octal => 8, Base::Binary => 2, }; - // Ignore underscores. - i64::from_str_radix(raw.replace("_", "").as_str(), radix).map_err(|_| raw) + // Ignore underscores, insert - when negative to get correct underflow/overflow behavior + (if is_negative { + from_str_radix::(format!("-{}", raw.replace("_", "")).as_str(), radix) + } else { + from_str_radix::(raw.replace("_", "").as_str(), radix) + }) + .map_err(|e| (raw, e.kind)) } #[inline(always)] -pub fn finish_parsing_float(raw: &str) -> Result { +pub fn finish_parsing_float(raw: &str) -> Result { // Ignore underscores. match raw.replace("_", "").parse::() { Ok(float) if float.is_finite() => Ok(float), - _ => Err(raw), + Ok(float) => { + if float.is_sign_positive() { + Err((raw, FloatErrorKind::PositiveInfinity)) + } else { + Err((raw, FloatErrorKind::NegativeInfinity)) + } + } + Err(_) => Err((raw, FloatErrorKind::Error)), + } +} + +/// Integer parsing code taken from the rust libcore, +/// pulled in so we can give custom error messages +/// +/// The Rust Project is dual-licensed under Apache 2.0 and MIT terms. +/// As roc is Apache licensed, we use this rust code under the apache 2.0 license +/// +/// Thanks to the rust-lang project and its contributors + +trait FromStrRadixHelper: PartialOrd + Copy { + fn min_value() -> Self; + fn max_value() -> Self; + fn from_u32(u: u32) -> Self; + fn checked_mul(&self, other: u32) -> Option; + fn checked_sub(&self, other: u32) -> Option; + fn checked_add(&self, other: u32) -> Option; +} + +macro_rules! doit { + ($($t:ty)*) => ($(impl FromStrRadixHelper for $t { + #[inline] + fn min_value() -> Self { Self::min_value() } + #[inline] + fn max_value() -> Self { Self::max_value() } + #[inline] + fn from_u32(u: u32) -> Self { u as Self } + #[inline] + fn checked_mul(&self, other: u32) -> Option { + Self::checked_mul(*self, other as Self) + } + #[inline] + fn checked_sub(&self, other: u32) -> Option { + Self::checked_sub(*self, other as Self) + } + #[inline] + fn checked_add(&self, other: u32) -> Option { + Self::checked_add(*self, other as Self) + } + })*) +} +// We only need the i64 implementation, but libcore defines +// doit! { i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize } +doit! { i64 } + +fn from_str_radix(src: &str, radix: u32) -> Result { + use self::IntErrorKind::*; + use self::ParseIntError as PIE; + + assert!( + radix >= 2 && radix <= 36, + "from_str_radix_int: must lie in the range `[2, 36]` - found {}", + radix + ); + + if src.is_empty() { + return Err(PIE { kind: Empty }); + } + + let is_signed_ty = T::from_u32(0) > T::min_value(); + + // all valid digits are ascii, so we will just iterate over the utf8 bytes + // and cast them to chars. .to_digit() will safely return None for anything + // other than a valid ascii digit for the given radix, including the first-byte + // of multi-byte sequences + let src = src.as_bytes(); + + let (is_positive, digits) = match src[0] { + b'+' => (true, &src[1..]), + b'-' if is_signed_ty => (false, &src[1..]), + _ => (true, src), + }; + + if digits.is_empty() { + return Err(PIE { kind: Empty }); + } + + let mut result = T::from_u32(0); + if is_positive { + // The number is positive + for &c in digits { + let x = match (c as char).to_digit(radix) { + Some(x) => x, + None => return Err(PIE { kind: InvalidDigit }), + }; + result = match result.checked_mul(radix) { + Some(result) => result, + None => return Err(PIE { kind: Overflow }), + }; + result = match result.checked_add(x) { + Some(result) => result, + None => return Err(PIE { kind: Overflow }), + }; + } + } else { + // The number is negative + for &c in digits { + let x = match (c as char).to_digit(radix) { + Some(x) => x, + None => return Err(PIE { kind: InvalidDigit }), + }; + result = match result.checked_mul(radix) { + Some(result) => result, + None => return Err(PIE { kind: Underflow }), + }; + result = match result.checked_sub(x) { + Some(result) => result, + None => return Err(PIE { kind: Underflow }), + }; + } + } + Ok(result) +} + +/// An error which can be returned when parsing an integer. +/// +/// This error is used as the error type for the `from_str_radix()` functions +/// on the primitive integer types, such as [`i8::from_str_radix`]. +/// +/// # Potential causes +/// +/// Among other causes, `ParseIntError` can be thrown because of leading or trailing whitespace +/// in the string e.g., when it is obtained from the standard input. +/// Using the [`str.trim()`] method ensures that no whitespace remains before parsing. +/// +/// [`str.trim()`]: ../../std/primitive.str.html#method.trim +/// [`i8::from_str_radix`]: ../../std/primitive.i8.html#method.from_str_radix +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ParseIntError { + kind: IntErrorKind, +} + +impl ParseIntError { + /// Outputs the detailed cause of parsing an integer failing. + pub fn kind(&self) -> &IntErrorKind { + &self.kind } } diff --git a/compiler/can/src/operator.rs b/compiler/can/src/operator.rs index 6e005d9396..925c6a8f48 100644 --- a/compiler/can/src/operator.rs +++ b/compiler/can/src/operator.rs @@ -300,7 +300,7 @@ fn desugar_field<'a>( } } -// TODO move this desugaring to canonicalization, to avoid dealing with strings as much +// TODO move this desugaring to canonicalization, so we can use Symbols instead of strings #[inline(always)] fn binop_to_function(binop: BinOp) -> (&'static str, &'static str) { use self::BinOp::*; @@ -308,8 +308,8 @@ fn binop_to_function(binop: BinOp) -> (&'static str, &'static str) { match binop { Caret => (ModuleName::NUM, "pow"), Star => (ModuleName::NUM, "mul"), - Slash => (ModuleName::FLOAT, "div"), - DoubleSlash => (ModuleName::INT, "div"), + Slash => (ModuleName::NUM, "div"), + DoubleSlash => (ModuleName::NUM, "divFloor"), Percent => (ModuleName::NUM, "rem"), DoublePercent => (ModuleName::NUM, "mod"), Plus => (ModuleName::NUM, "add"), diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index dc536be5c6..7ec9b20fe7 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -5,7 +5,7 @@ use roc_module::ident::{Ident, Lowercase, TagName}; use roc_module::symbol::Symbol; use roc_parse::ast; use roc_parse::pattern::PatternType; -use roc_problem::can::{Problem, RuntimeError}; +use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError}; use roc_region::all::{Located, Region}; use roc_types::subs::{VarStore, Variable}; @@ -35,6 +35,8 @@ pub enum Pattern { Shadowed(Region, Located), // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments! UnsupportedPattern(Region), + // parse error patterns + MalformedPattern(MalformedPatternProblem, Region), } #[derive(Clone, Debug, PartialEq)] @@ -76,6 +78,7 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec) { | FloatLiteral(_) | StrLiteral(_) | Underscore + | MalformedPattern(_, _) | UnsupportedPattern(_) => {} Shadowed(_, _) => {} @@ -165,32 +168,30 @@ pub fn canonicalize_pattern<'a>( } FloatLiteral(ref string) => match pattern_type { - WhenBranch => { - let float = finish_parsing_float(string) - .unwrap_or_else(|_| panic!("TODO handle malformed float pattern")); - - Pattern::FloatLiteral(float) - } - ptype @ DefExpr | ptype @ TopLevelDef | ptype @ FunctionArg => { - unsupported_pattern(env, ptype, region) - } + WhenBranch => match finish_parsing_float(string) { + Err(_error) => { + let problem = MalformedPatternProblem::MalformedFloat; + malformed_pattern(env, problem, region) + } + Ok(float) => Pattern::FloatLiteral(float), + }, + ptype => unsupported_pattern(env, ptype, region), }, Underscore => match pattern_type { WhenBranch | FunctionArg => Pattern::Underscore, - ptype @ DefExpr | ptype @ TopLevelDef => unsupported_pattern(env, ptype, region), + ptype => unsupported_pattern(env, ptype, region), }, NumLiteral(string) => match pattern_type { - WhenBranch => { - let int = finish_parsing_int(string) - .unwrap_or_else(|_| panic!("TODO handle malformed int pattern")); - - Pattern::NumLiteral(var_store.fresh(), int) - } - ptype @ DefExpr | ptype @ TopLevelDef | ptype @ FunctionArg => { - unsupported_pattern(env, ptype, region) - } + WhenBranch => match finish_parsing_int(string) { + Err(_error) => { + let problem = MalformedPatternProblem::MalformedInt; + malformed_pattern(env, problem, region) + } + Ok(int) => Pattern::NumLiteral(var_store.fresh(), int), + }, + ptype => unsupported_pattern(env, ptype, region), }, NonBase10Literal { @@ -198,29 +199,33 @@ pub fn canonicalize_pattern<'a>( base, is_negative, } => match pattern_type { - WhenBranch => { - let int = finish_parsing_base(string, *base) - .unwrap_or_else(|_| panic!("TODO handle malformed {:?} pattern", base)); - - if *is_negative { - Pattern::IntLiteral(-int) - } else { - Pattern::IntLiteral(int) + WhenBranch => match finish_parsing_base(string, *base, *is_negative) { + Err(_error) => { + let problem = MalformedPatternProblem::MalformedBase(*base); + malformed_pattern(env, problem, region) } - } - ptype @ DefExpr | ptype @ TopLevelDef | ptype @ FunctionArg => { - unsupported_pattern(env, ptype, region) - } + Ok(int) => { + if *is_negative { + Pattern::IntLiteral(-int) + } else { + Pattern::IntLiteral(int) + } + } + }, + ptype => unsupported_pattern(env, ptype, region), }, - StrLiteral(_string) => match pattern_type { + StrLiteral(string) => match pattern_type { WhenBranch => { - panic!("TODO check whether string pattern is malformed."); - // Pattern::StrLiteral((*string).into()) - } - ptype @ DefExpr | ptype @ TopLevelDef | ptype @ FunctionArg => { - unsupported_pattern(env, ptype, region) + // TODO report whether string was malformed + Pattern::StrLiteral((*string).into()) } + ptype => unsupported_pattern(env, ptype, region), + }, + + BlockStrLiteral(_lines) => match pattern_type { + WhenBranch => todo!("TODO block string literal pattern"), + ptype => unsupported_pattern(env, ptype, region), }, SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) | Nested(sub_pattern) => { @@ -288,7 +293,7 @@ pub fn canonicalize_pattern<'a>( }, }); } - _ => panic!("invalid pattern in record"), + _ => unreachable!("Any other pattern should have given a parse error"), } } @@ -304,7 +309,15 @@ pub fn canonicalize_pattern<'a>( unreachable!("should have been handled in RecordDestructure"); } - _ => panic!("TODO finish restoring can_pattern branch for {:?}", pattern), + Malformed(_str) => { + let problem = MalformedPatternProblem::Unknown; + malformed_pattern(env, problem, region) + } + + QualifiedIdentifier { .. } => { + let problem = MalformedPatternProblem::QualifiedIdentifier; + malformed_pattern(env, problem, region) + } }; Located { @@ -325,6 +338,20 @@ fn unsupported_pattern<'a>( Pattern::UnsupportedPattern(region) } +/// When we detect a malformed pattern like `3.X` or `0b5`, +/// report it to Env and return an UnsupportedPattern runtime error pattern. +fn malformed_pattern<'a>( + env: &mut Env<'a>, + problem: MalformedPatternProblem, + region: Region, +) -> Pattern { + env.problem(Problem::RuntimeError(RuntimeError::MalformedPattern( + problem, region, + ))); + + Pattern::MalformedPattern(problem, region) +} + pub fn bindings_from_patterns<'a, I>(loc_patterns: I, scope: &Scope) -> Vec<(Symbol, Region)> where I: Iterator>, @@ -374,6 +401,7 @@ fn add_bindings_from_patterns( | StrLiteral(_) | Underscore | Shadowed(_, _) + | MalformedPattern(_, _) | UnsupportedPattern(_) => (), } } diff --git a/compiler/can/tests/can_inline.rs b/compiler/can/tests/can_inline.rs new file mode 100644 index 0000000000..8156a8adb3 --- /dev/null +++ b/compiler/can/tests/can_inline.rs @@ -0,0 +1,110 @@ +#[macro_use] +extern crate pretty_assertions; +#[macro_use] +extern crate indoc; + +extern crate bumpalo; +extern crate roc_can; +extern crate roc_parse; +extern crate roc_region; + +mod helpers; + +#[cfg(test)] +mod can_inline { + use crate::helpers::{can_expr_with, test_home}; + use bumpalo::Bump; + use roc_can::expr::inline_calls; + use roc_can::expr::Expr::{self, *}; + use roc_can::scope::Scope; + use roc_types::subs::VarStore; + + fn assert_inlines_to(input: &str, expected: Expr, var_store: &mut VarStore) { + let arena = Bump::new(); + let scope = &mut Scope::new(test_home()); + let actual_out = can_expr_with(&arena, test_home(), input); + let actual = inline_calls(var_store, scope, actual_out.loc_expr.value); + + assert_eq!(actual, expected); + } + + #[test] + fn inline_empty_record() { + // fn inline_list_len() { + let var_store = &mut VarStore::default(); + + assert_inlines_to( + indoc!( + r#" + {} + "# + ), + EmptyRecord, + var_store, + ); + + // TODO testing with hardcoded variables is very brittle. + // Should find a better way to test this! + // (One idea would be to traverse both Exprs and zero out all the Variables, + // so they always pass equality.) + // let aliases = SendMap::default(); + // assert_inlines_to( + // indoc!( + // r#" + // Int.isZero 5 + // "# + // ), + // LetNonRec( + // Box::new(Def { + // loc_pattern: Located { + // region: Region::zero(), + // value: Pattern::Identifier(Symbol::ARG_1), + // }, + // pattern_vars: SendMap::default(), + // loc_expr: Located { + // region: Region::new(0, 0, 11, 12), + // value: Num(unsafe { Variable::unsafe_test_debug_variable(7) }, 5), + // }, + // expr_var: unsafe { Variable::unsafe_test_debug_variable(8) }, + // annotation: None, + // }), + // Box::new(Located { + // region: Region::zero(), + // value: Expr::Call( + // Box::new(( + // unsafe { Variable::unsafe_test_debug_variable(138) }, + // Located { + // region: Region::zero(), + // value: Expr::Var(Symbol::BOOL_EQ), + // }, + // unsafe { Variable::unsafe_test_debug_variable(139) }, + // )), + // vec![ + // ( + // unsafe { Variable::unsafe_test_debug_variable(140) }, + // Located { + // region: Region::zero(), + // value: Var(Symbol::ARG_1), + // }, + // ), + // ( + // unsafe { Variable::unsafe_test_debug_variable(141) }, + // Located { + // region: Region::zero(), + // value: Int( + // unsafe { Variable::unsafe_test_debug_variable(137) }, + // 0, + // ), + // }, + // ), + // ], + // CalledVia::Space, + // ), + // }), + // unsafe { Variable::unsafe_test_debug_variable(198) }, + // aliases, + // ), + // var_store, + // ) + } +} diff --git a/compiler/can/tests/test_can.rs b/compiler/can/tests/test_can.rs index 1648639491..8deac2c668 100644 --- a/compiler/can/tests/test_can.rs +++ b/compiler/can/tests/test_can.rs @@ -16,8 +16,8 @@ mod test_can { use bumpalo::Bump; use roc_can::expr::Expr::{self, *}; use roc_can::expr::Recursive; - use roc_problem::can::{Problem, RuntimeError}; - use roc_region::all::{Located, Region}; + use roc_problem::can::{FloatErrorKind, IntErrorKind, Problem, RuntimeError}; + use roc_region::all::Region; use std::{f64, i64}; fn assert_can(input: &str, expected: Expr) { @@ -73,41 +73,65 @@ mod test_can { #[test] fn int_too_large() { + use roc_parse::ast::Base; + let string = (i64::MAX as i128 + 1).to_string(); assert_can( &string.clone(), - RuntimeError(RuntimeError::IntOutsideRange(string.into())), + RuntimeError(RuntimeError::InvalidInt( + IntErrorKind::Overflow, + Base::Decimal, + Region::zero(), + string.into(), + )), ); } #[test] fn int_too_small() { + use roc_parse::ast::Base; + let string = (i64::MIN as i128 - 1).to_string(); assert_can( &string.clone(), - RuntimeError(RuntimeError::IntOutsideRange(string.into())), + RuntimeError(RuntimeError::InvalidInt( + IntErrorKind::Underflow, + Base::Decimal, + Region::zero(), + string.into(), + )), ); } #[test] fn float_too_large() { let string = format!("{}1.0", f64::MAX); + let region = Region::zero(); assert_can( &string.clone(), - RuntimeError(RuntimeError::FloatOutsideRange(string.into())), + RuntimeError(RuntimeError::InvalidFloat( + FloatErrorKind::PositiveInfinity, + region, + string.into(), + )), ); } #[test] fn float_too_small() { let string = format!("{}1.0", f64::MIN); + let region = Region::zero(); assert_can( &string.clone(), - RuntimeError(RuntimeError::FloatOutsideRange(string.into())), + RuntimeError(RuntimeError::InvalidFloat( + FloatErrorKind::NegativeInfinity, + region, + string.into(), + )), ); } @@ -131,6 +155,46 @@ mod test_can { assert_can_float("-0.0", -0.0); } + #[test] + fn num_max() { + assert_can_num(&(i64::MAX.to_string()), i64::MAX); + } + + #[test] + fn num_min() { + assert_can_num(&(i64::MIN.to_string()), i64::MIN); + } + + #[test] + fn hex_max() { + assert_can_int(&format!("0x{:x}", i64::MAX), i64::MAX); + } + + #[test] + fn hex_min() { + assert_can_int(&format!("-0x{:x}", i64::MAX as i128 + 1), i64::MIN); + } + + #[test] + fn oct_max() { + assert_can_int(&format!("0o{:o}", i64::MAX), i64::MAX); + } + + #[test] + fn oct_min() { + assert_can_int(&format!("-0o{:o}", i64::MAX as i128 + 1), i64::MIN); + } + + #[test] + fn bin_max() { + assert_can_int(&format!("0b{:b}", i64::MAX), i64::MAX); + } + + #[test] + fn bin_min() { + assert_can_int(&format!("-0b{:b}", i64::MAX as i128 + 1), i64::MIN); + } + #[test] fn hex_zero() { assert_can_int("0x0", 0x0); @@ -505,10 +569,14 @@ mod test_can { "# ); + let home = test_home(); let arena = Bump::new(); let CanExprOut { - loc_expr, problems, .. - } = can_expr_with(&arena, test_home(), src); + loc_expr, + problems, + interns, + .. + } = can_expr_with(&arena, home, src); let is_circular_def = if let RuntimeError(RuntimeError::CircularDef(_, _)) = loc_expr.value { @@ -518,7 +586,7 @@ mod test_can { }; let problem = Problem::RuntimeError(RuntimeError::CircularDef( - vec![Located::at(Region::new(0, 0, 0, 1), "x".into())], + vec![interns.symbol(home, "x".into())], vec![(Region::new(0, 0, 0, 1), Region::new(0, 0, 4, 5))], )); @@ -537,16 +605,20 @@ mod test_can { x "# ); + let home = test_home(); let arena = Bump::new(); let CanExprOut { - loc_expr, problems, .. - } = can_expr_with(&arena, test_home(), src); + loc_expr, + problems, + interns, + .. + } = can_expr_with(&arena, home, src); let problem = Problem::RuntimeError(RuntimeError::CircularDef( vec![ - Located::at(Region::new(0, 0, 0, 1), "x".into()), - Located::at(Region::new(1, 1, 0, 1), "y".into()), - Located::at(Region::new(2, 2, 0, 1), "z".into()), + interns.symbol(home, "x".into()), + interns.symbol(home, "y".into()), + interns.symbol(home, "z".into()), ], vec![ (Region::new(0, 0, 0, 1), Region::new(0, 0, 4, 5)), diff --git a/compiler/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index 8a4f2731df..71c02eb980 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -13,7 +13,7 @@ use roc_types::types::Type::{self, *}; pub fn int_literal(num_var: Variable, expected: Expected, region: Region) -> Constraint { let num_type = Variable(num_var); let reason = Reason::IntLiteral; - let expected_literal = ForReason(reason, Type::Apply(Symbol::INT_INT, vec![]), region); + let expected_literal = ForReason(reason, Type::Apply(Symbol::NUM_INT, vec![]), region); exists( vec![num_var], @@ -28,7 +28,7 @@ pub fn int_literal(num_var: Variable, expected: Expected, region: Region) pub fn float_literal(num_var: Variable, expected: Expected, region: Region) -> Constraint { let num_type = Variable(num_var); let reason = Reason::FloatLiteral; - let expected_literal = ForReason(reason, Type::Apply(Symbol::FLOAT_FLOAT, vec![]), region); + let expected_literal = ForReason(reason, Type::Apply(Symbol::NUM_FLOAT, vec![]), region); exists( vec![num_var], diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index 3626bb9775..bb5b292e92 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -118,8 +118,8 @@ pub fn constrain_expr( let record_type = Type::Record( field_types, // TODO can we avoid doing Box::new on every single one of these? - // For example, could we have a single lazy_static global Box they - // could all share? + // We can put `static EMPTY_REC: Type = Type::EmptyRec`, but that requires a + // lifetime parameter on `Type` Box::new(Type::EmptyRec), ); let record_con = Eq(record_type, expected.clone(), Category::Record, region); @@ -600,11 +600,7 @@ pub fn constrain_expr( } } - // TODO check for exhaustiveness. If this `case` is non-exaustive, then: - // - // 1. Record a Problem. - // 2. Add an extra _ branch at the end which throws a runtime error. - + // exhautiveness checking happens when converting to mono::Expr exists(vec![cond_var, *expr_var], And(constraints)) } Access { @@ -778,7 +774,52 @@ pub fn constrain_expr( exists(vars, And(arg_cons)) } - RuntimeError(_) => True, + RunLowLevel { args, ret_var, op } => { + // This is a modified version of what we do for function calls. + + // The operation's return type + let ret_type = Variable(*ret_var); + + // This will be used in the occurs check + let mut vars = Vec::with_capacity(1 + args.len()); + + vars.push(*ret_var); + + let mut arg_types = Vec::with_capacity(args.len()); + let mut arg_cons = Vec::with_capacity(args.len()); + + let mut add_arg = |index, arg_type: Type, arg| { + let reason = Reason::LowLevelOpArg { + op: *op, + arg_index: Index::zero_based(index), + }; + let expected_arg = ForReason(reason, arg_type.clone(), Region::zero()); + let arg_con = constrain_expr(env, Region::zero(), arg, expected_arg); + + arg_types.push(arg_type); + arg_cons.push(arg_con); + }; + + for (index, (arg_var, arg)) in args.iter().enumerate() { + vars.push(*arg_var); + + add_arg(index, Variable(*arg_var), arg); + } + + let category = Category::LowLevelOpResult(*op); + + exists( + vars, + And(vec![ + And(arg_cons), + Eq(ret_type, expected, category, region), + ]), + ) + } + RuntimeError(_) => { + // Runtime Errors have no constraints because they're going to crash. + True + } } } @@ -798,7 +839,6 @@ fn constrain_when_branch( constraints: Vec::with_capacity(1), }; - // TODO ensure this is correct // TODO investigate for error messages, is it better to unify all branches with a variable, // then unify that variable with the expectation? for loc_pattern in &when_branch.patterns { @@ -872,38 +912,35 @@ pub fn constrain_decls( ) -> Constraint { let mut constraint = Constraint::SaveTheEnvironment; + let mut env = Env { + home, + rigids: ImMap::default(), + }; + for decl in decls.iter().rev() { - // NOTE: rigids are empty because they are not shared between top-level definitions + // Clear the rigids from the previous iteration. + // rigids are not shared between top-level definitions + env.rigids.clear(); + match decl { - Declaration::Declare(def) => { + Declaration::Declare(def) | Declaration::Builtin(def) => { constraint = exists_with_aliases( aliases.clone(), Vec::new(), - constrain_def( - &Env { - home, - rigids: ImMap::default(), - }, - def, - constraint, - ), + constrain_def(&env, def, constraint), ); } Declaration::DeclareRec(defs) => { constraint = exists_with_aliases( aliases.clone(), Vec::new(), - constrain_recursive_defs( - &Env { - home, - rigids: ImMap::default(), - }, - defs, - constraint, - ), + constrain_recursive_defs(&env, defs, constraint), ); } - Declaration::InvalidCycle(_, _) => panic!("TODO handle invalid cycle"), + Declaration::InvalidCycle(_, _) => { + // invalid cycles give a canonicalization error. we skip them here. + continue; + } } } @@ -970,8 +1007,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint { expr_type, annotation_expected.clone(), Category::Storage, - // TODO proper region - Region::zero(), + annotation.region, )); constrain_expr( diff --git a/compiler/constrain/src/module.rs b/compiler/constrain/src/module.rs index 7af9047d1b..10668956c0 100644 --- a/compiler/constrain/src/module.rs +++ b/compiler/constrain/src/module.rs @@ -1,15 +1,15 @@ use crate::expr::constrain_decls; -use roc_builtins::std::Mode; +use roc_builtins::std::{Mode, StdLib}; use roc_can::constraint::{Constraint, LetConstraint}; use roc_can::module::ModuleOutput; use roc_collections::all::{ImMap, MutMap, MutSet, SendMap}; use roc_module::ident::Lowercase; use roc_module::symbol::{ModuleId, Symbol}; -use roc_region::all::Located; +use roc_region::all::{Located, Region}; use roc_types::boolean_algebra::Bool; use roc_types::solved_types::{BuiltinAlias, SolvedBool, SolvedType}; use roc_types::subs::{VarId, VarStore, Variable}; -use roc_types::types::{Alias, Type}; +use roc_types::types::{Alias, Problem, Type}; pub type SubsByModule = MutMap; @@ -284,7 +284,7 @@ fn to_type(solved_type: &SolvedType, free_vars: &mut FreeVars, var_store: &mut V Type::Alias(*symbol, type_variables, Box::new(actual)) } - Error => Type::Erroneous(roc_types::types::Problem::SolvedTypeError), + Error => Type::Erroneous(Problem::SolvedTypeError), Erroneous(problem) => Type::Erroneous(problem.clone()), } } @@ -349,3 +349,134 @@ pub fn constrain_imported_aliases( ret_constraint: body_con, })) } + +/// Run pre_constrain_imports to get imported_symbols and imported_aliases. +pub fn constrain_imports( + imported_symbols: Vec, + imported_aliases: MutMap, + constraint: Constraint, + var_store: &mut VarStore, +) -> Constraint { + let (_introduced_rigids, constraint) = + constrain_imported_values(imported_symbols, constraint, var_store); + + // TODO determine what to do with those rigids + // for var in introduced_rigids { + // output.ftv.insert(var, format!("internal_{:?}", var).into()); + // } + + constrain_imported_aliases(imported_aliases, constraint, var_store) +} + +pub struct ConstrainableImports { + pub imported_symbols: Vec, + pub imported_aliases: MutMap, + pub unused_imports: MutSet, +} + +/// Run this before constraining imports. +/// +/// Constraining imports is split into two different functions, because this +/// part of the work needs to be done on the main thread, whereas the rest of it +/// can be done on a different thread. +pub fn pre_constrain_imports( + home: ModuleId, + references: &MutSet, + imported_modules: MutSet, + exposed_types: &mut SubsByModule, + stdlib: &StdLib, +) -> ConstrainableImports { + let mut imported_symbols = Vec::with_capacity(references.len()); + let mut imported_aliases = MutMap::default(); + let mut unused_imports = imported_modules; // We'll remove these as we encounter them. + + // Translate referenced symbols into constraints. We do this on the main + // thread because we need exclusive access to the exposed_types map, in order + // to get the necessary constraint info for any aliases we imported. We also + // resolve builtin types now, so we can use a refernce to stdlib instead of + // having to either clone it or recreate it from scratch on the other thread. + for &symbol in references.iter() { + let module_id = symbol.module_id(); + + // We used this module, so clearly it is not unused! + unused_imports.remove(&module_id); + + if module_id.is_builtin() { + // For builtin modules, we create imports from the + // hardcoded builtin map. + match stdlib.types.get(&symbol) { + Some((solved_type, region)) => { + let loc_symbol = Located { + value: symbol, + region: *region, + }; + + imported_symbols.push(Import { + loc_symbol, + solved_type: solved_type.clone(), + }); + } + None => { + let is_valid_alias = stdlib.applies.contains(&symbol) + // This wasn't a builtin value or Apply; maybe it was a builtin alias. + || stdlib.aliases.contains_key(&symbol); + + if !is_valid_alias { + panic!( + "Could not find {:?} in builtin types {:?} or aliases {:?}", + symbol, stdlib.types, stdlib.aliases + ); + } + } + } + } else if module_id != home { + // We already have constraints for our own symbols. + let region = Region::zero(); // TODO this should be the region where this symbol was declared in its home module. Look that up! + let loc_symbol = Located { + value: symbol, + region, + }; + + match exposed_types.get(&module_id) { + Some(ExposedModuleTypes::Valid(solved_types, new_aliases)) => { + let solved_type = solved_types.get(&symbol).unwrap_or_else(|| { + panic!( + "Could not find {:?} in solved_types {:?}", + loc_symbol.value, solved_types + ) + }); + + // TODO should this be a union? + for (k, v) in new_aliases.clone() { + imported_aliases.insert(k, v); + } + + imported_symbols.push(Import { + loc_symbol, + solved_type: solved_type.clone(), + }); + } + Some(ExposedModuleTypes::Invalid) => { + // If that module was invalid, use True constraints + // for everything imported from it. + imported_symbols.push(Import { + loc_symbol, + solved_type: SolvedType::Erroneous(Problem::InvalidModule), + }); + } + None => { + panic!( + "Could not find module {:?} in exposed_types {:?}", + module_id, exposed_types + ); + } + } + } + } + + ConstrainableImports { + imported_symbols, + imported_aliases, + unused_imports, + } +} diff --git a/compiler/constrain/src/pattern.rs b/compiler/constrain/src/pattern.rs index 2340175905..4beede88d1 100644 --- a/compiler/constrain/src/pattern.rs +++ b/compiler/constrain/src/pattern.rs @@ -52,6 +52,7 @@ fn headers_from_annotation_help( } Underscore | Shadowed(_, _) + | MalformedPattern(_, _) | UnsupportedPattern(_) | NumLiteral(_, _) | IntLiteral(_) @@ -117,7 +118,7 @@ pub fn constrain_pattern( state: &mut PatternState, ) { match pattern { - Underscore | UnsupportedPattern(_) | Shadowed(_, _) => { + Underscore | UnsupportedPattern(_) | MalformedPattern(_, _) | Shadowed(_, _) => { // Neither the _ pattern nor erroneous ones add any constraints. } @@ -146,7 +147,7 @@ pub fn constrain_pattern( state.constraints.push(Constraint::Pattern( region, PatternCategory::Float, - builtins::builtin_type(Symbol::INT_INT, vec![]), + builtins::builtin_type(Symbol::NUM_INT, vec![]), expected, )); } @@ -155,7 +156,7 @@ pub fn constrain_pattern( state.constraints.push(Constraint::Pattern( region, PatternCategory::Float, - builtins::builtin_type(Symbol::FLOAT_FLOAT, vec![]), + builtins::builtin_type(Symbol::NUM_FLOAT, vec![]), expected, )); } diff --git a/compiler/constrain/src/uniq.rs b/compiler/constrain/src/uniq.rs index 8d2718dc71..7c147ba41f 100644 --- a/compiler/constrain/src/uniq.rs +++ b/compiler/constrain/src/uniq.rs @@ -70,7 +70,7 @@ pub fn constrain_decls( for decl in decls.iter().rev() { // NOTE: rigids are empty because they are not shared between top-level definitions match decl { - Declaration::Declare(def) => { + Declaration::Declare(def) | Declaration::Builtin(def) => { sharing::annotate_usage(&def.loc_expr.value, &mut var_usage); } Declaration::DeclareRec(defs) => { @@ -78,24 +78,33 @@ pub fn constrain_decls( sharing::annotate_usage(&def.loc_expr.value, &mut var_usage); } } - Declaration::InvalidCycle(_, _) => panic!("TODO handle invalid cycle"), + Declaration::InvalidCycle(_, _) => { + // any usage of a value defined in an invalid cycle will blow up + // so for the analysis usage by such values doesn't count + continue; + } } } aliases_to_attr_type(var_store, &mut aliases); + let mut env = Env { + home, + rigids: ImMap::default(), + }; + for decl in decls.iter().rev() { - // NOTE: rigids are empty because they are not shared between top-level definitions + // clear the set of rigids from the previous iteration. + // rigids are not shared between top-level definitions. + env.rigids.clear(); + match decl { - Declaration::Declare(def) => { + Declaration::Declare(def) | Declaration::Builtin(def) => { constraint = exists_with_aliases( aliases.clone(), Vec::new(), constrain_def( - &Env { - home, - rigids: ImMap::default(), - }, + &env, var_store, &var_usage, &mut ImSet::default(), @@ -109,10 +118,7 @@ pub fn constrain_decls( aliases.clone(), Vec::new(), constrain_recursive_defs( - &Env { - home, - rigids: ImMap::default(), - }, + &env, var_store, &var_usage, &mut ImSet::default(), @@ -121,7 +127,10 @@ pub fn constrain_decls( ), ); } - Declaration::InvalidCycle(_, _) => panic!("TODO handle invalid cycle"), + Declaration::InvalidCycle(_, _) => { + // invalid cycles give a canonicalization error. we skip them here. + continue; + } } } @@ -333,7 +342,7 @@ fn constrain_pattern( state.constraints.push(tag_con); } - Underscore | Shadowed(_, _) | UnsupportedPattern(_) => { + Underscore | Shadowed(_, _) | MalformedPattern(_, _) | UnsupportedPattern(_) => { // no constraints } } @@ -370,11 +379,11 @@ fn unique_num(var_store: &mut VarStore, symbol: Symbol) -> (Variable, Variable, } fn unique_int(var_store: &mut VarStore) -> (Variable, Variable, Type) { - unique_num(var_store, Symbol::INT_INTEGER) + unique_num(var_store, Symbol::NUM_INTEGER) } fn unique_float(var_store: &mut VarStore) -> (Variable, Variable, Type) { - unique_num(var_store, Symbol::FLOAT_FLOATINGPOINT) + unique_num(var_store, Symbol::NUM_FLOATINGPOINT) } pub fn constrain_expr( @@ -795,6 +804,54 @@ pub fn constrain_expr( ]), ) } + RunLowLevel { op, args, ret_var } => { + // This is a modified version of what we do for function calls. + + let ret_type = Variable(*ret_var); + let mut vars = Vec::with_capacity(1 + args.len()); + + vars.push(*ret_var); + + // Canonicalize the function expression and its arguments + + let mut arg_types = Vec::with_capacity(args.len()); + let mut arg_cons = Vec::with_capacity(args.len()); + + for (index, (arg_var, arg_expr)) in args.iter().enumerate() { + let arg_type = Variable(*arg_var); + + let reason = Reason::LowLevelOpArg { + op: *op, + arg_index: Index::zero_based(index), + }; + + let expected_arg = Expected::ForReason(reason, arg_type.clone(), region); + let arg_con = constrain_expr( + env, + var_store, + var_usage, + applied_usage_constraint, + Region::zero(), + arg_expr, + expected_arg, + ); + + vars.push(*arg_var); + arg_types.push(arg_type); + arg_cons.push(arg_con); + } + + let expected_uniq_type = var_store.fresh(); + vars.push(expected_uniq_type); + + exists( + vars, + And(vec![ + And(arg_cons), + Eq(ret_type, expected, Category::LowLevelOpResult(*op), region), + ]), + ) + } LetRec(defs, loc_ret, var, unlifted_aliases) => { // NOTE doesn't currently unregister bound symbols // may be a problem when symbols are not globally unique diff --git a/compiler/fmt/src/annotation.rs b/compiler/fmt/src/annotation.rs new file mode 100644 index 0000000000..126a3a2630 --- /dev/null +++ b/compiler/fmt/src/annotation.rs @@ -0,0 +1,500 @@ +use crate::spaces::{fmt_comments_only, fmt_condition_spaces, fmt_spaces, newline, INDENT}; +use bumpalo::collections::String; +use roc_parse::ast::{AssignedField, Expr, Tag, TypeAnnotation}; +use roc_region::all::Located; + +/// Does an AST node need parens around it? +/// +/// Usually not, but there are two cases where it may be required +/// +/// 1. In a function type, function types are in parens +/// +/// a -> b, c -> d +/// (a -> b), c -> d +/// +/// 2. In applications, applications are in brackets +/// This is true in patterns, type annotations and expressions +/// +/// Just (Just a) +/// List (List a) +/// reverse (reverse l) +#[derive(PartialEq, Eq, Clone, Copy)] +pub enum Parens { + NotNeeded, + InFunctionType, + InApply, +} + +/// In an AST node, do we show newlines around it +/// +/// Sometimes, we only want to show comments, at other times +/// we also want to show newlines. By default the formatter +/// takes care of inserting newlines, but sometimes the user's +/// newlines are taken into account. +#[derive(PartialEq, Eq, Clone, Copy)] +pub enum Newlines { + Yes, + No, +} + +pub fn fmt_annotation<'a>(buf: &mut String<'a>, annotation: &'a TypeAnnotation<'a>, indent: u16) { + annotation.format(buf, indent); +} + +pub trait Formattable<'a> { + fn is_multiline(&self) -> bool; + + fn format_with_options( + &self, + buf: &mut String<'a>, + _parens: Parens, + _newlines: Newlines, + indent: u16, + ) { + self.format(buf, indent); + } + + fn format(&self, buf: &mut String<'a>, indent: u16) { + self.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent); + } +} + +/// A Located formattable value is also formattable +impl<'a, T> Formattable<'a> for Located +where + T: Formattable<'a>, +{ + fn is_multiline(&self) -> bool { + self.value.is_multiline() + } + + fn format_with_options( + &self, + buf: &mut String<'a>, + parens: Parens, + newlines: Newlines, + indent: u16, + ) { + self.value + .format_with_options(buf, parens, newlines, indent) + } + + fn format(&self, buf: &mut String<'a>, indent: u16) { + self.value.format(buf, indent) + } +} + +impl<'a> Formattable<'a> for TypeAnnotation<'a> { + fn is_multiline(&self) -> bool { + use roc_parse::ast::TypeAnnotation::*; + + match self { + // Return whether these spaces contain any Newlines + SpaceBefore(_, spaces) | SpaceAfter(_, spaces) => { + debug_assert!(!spaces.is_empty()); + + // "spaces" always contain either a newline or comment, and comments have newlines + true + } + + Wildcard | BoundVariable(_) | Malformed(_) => false, + Function(args, result) => { + (&result.value).is_multiline() + || args.iter().any(|loc_arg| (&loc_arg.value).is_multiline()) + } + Apply(_, _, args) => args.iter().any(|loc_arg| loc_arg.value.is_multiline()), + As(lhs, _, rhs) => lhs.value.is_multiline() || rhs.value.is_multiline(), + + Record { fields, ext } => { + match ext { + Some(ann) if ann.value.is_multiline() => return true, + _ => {} + } + + fields.iter().any(|field| field.value.is_multiline()) + } + + TagUnion { tags, ext } => { + match ext { + Some(ann) if ann.value.is_multiline() => return true, + _ => {} + } + + tags.iter().any(|tag| tag.value.is_multiline()) + } + } + } + + fn format_with_options( + &self, + buf: &mut String<'a>, + parens: Parens, + newlines: Newlines, + indent: u16, + ) { + use roc_parse::ast::TypeAnnotation::*; + + match self { + Function(arguments, result) => { + let write_parens = parens != Parens::NotNeeded; + + if write_parens { + buf.push('(') + } + + let mut it = arguments.iter().peekable(); + + while let Some(argument) = it.next() { + (&argument.value).format_with_options( + buf, + Parens::InFunctionType, + Newlines::No, + indent, + ); + + if it.peek().is_some() { + buf.push_str(", "); + } + } + + buf.push_str(" -> "); + + (&result.value).format_with_options( + buf, + Parens::InFunctionType, + Newlines::No, + indent, + ); + + if write_parens { + buf.push(')') + } + } + Apply(_, name, arguments) => { + // NOTE apply is never multiline + let write_parens = parens == Parens::InApply && !arguments.is_empty(); + + if write_parens { + buf.push('(') + } + + buf.push_str(name); + + for argument in *arguments { + buf.push(' '); + (&argument.value).format_with_options( + buf, + Parens::InApply, + Newlines::No, + indent, + ); + } + + if write_parens { + buf.push(')') + } + } + BoundVariable(v) => buf.push_str(v), + Wildcard => buf.push('*'), + + TagUnion { tags, ext } => { + tags.format(buf, indent); + + if let Some(loc_ext_ann) = *ext { + loc_ext_ann.value.format(buf, indent); + } + } + + Record { fields, ext } => { + fields.format(buf, indent); + + if let Some(loc_ext_ann) = *ext { + loc_ext_ann.value.format(buf, indent); + } + } + + As(lhs, _spaces, rhs) => { + // TODO use spaces? + lhs.value.format(buf, indent); + buf.push_str(" as "); + rhs.value.format(buf, indent); + } + + SpaceBefore(ann, _spaces) | SpaceAfter(ann, _spaces) => { + ann.format_with_options(buf, parens, newlines, indent) + } + + Malformed(raw) => buf.push_str(raw), + } + } +} + +/// Fields are subtly different on the type and term level: +/// +/// > type: { x : Int, y : Bool } +/// > term: { x: 100, y: True } +/// +/// So we need two instances, each having the specific separator +impl<'a> Formattable<'a> for AssignedField<'a, TypeAnnotation<'a>> { + fn is_multiline(&self) -> bool { + is_multiline_assigned_field_help(self) + } + + fn format_with_options( + &self, + buf: &mut String<'a>, + parens: Parens, + newlines: Newlines, + indent: u16, + ) { + // we abuse the `Newlines` type to decide between multiline or single-line layout + format_assigned_field_help(self, buf, parens, indent, " : ", newlines == Newlines::Yes); + } +} + +impl<'a> Formattable<'a> for AssignedField<'a, Expr<'a>> { + fn is_multiline(&self) -> bool { + is_multiline_assigned_field_help(self) + } + + fn format_with_options( + &self, + buf: &mut String<'a>, + parens: Parens, + newlines: Newlines, + indent: u16, + ) { + // we abuse the `Newlines` type to decide between multiline or single-line layout + format_assigned_field_help(self, buf, parens, indent, ": ", newlines == Newlines::Yes); + } +} + +fn is_multiline_assigned_field_help<'a, T: Formattable<'a>>(afield: &AssignedField<'a, T>) -> bool { + use self::AssignedField::*; + + match afield { + LabeledValue(_, spaces, ann) => !spaces.is_empty() || ann.value.is_multiline(), + LabelOnly(_) => false, + AssignedField::SpaceBefore(_, _) | AssignedField::SpaceAfter(_, _) => true, + Malformed(text) => text.chars().any(|c| c == '\n'), + } +} + +fn format_assigned_field_help<'a, T>( + zelf: &AssignedField<'a, T>, + buf: &mut String<'a>, + parens: Parens, + indent: u16, + separator: &str, + is_multiline: bool, +) where + T: Formattable<'a>, +{ + // TODO multiline? + + use self::AssignedField::*; + + match zelf { + LabeledValue(name, spaces, ann) => { + if is_multiline { + newline(buf, indent); + } + + buf.push_str(name.value); + + if !spaces.is_empty() { + fmt_spaces(buf, spaces.iter(), indent); + } + + buf.push_str(separator); + ann.value.format(buf, indent); + } + LabelOnly(name) => { + if is_multiline { + newline(buf, indent); + } + + buf.push_str(name.value); + } + AssignedField::SpaceBefore(sub_field, spaces) => { + fmt_comments_only(buf, spaces.iter(), indent); + format_assigned_field_help(sub_field, buf, parens, indent, separator, is_multiline); + } + AssignedField::SpaceAfter(sub_field, spaces) => { + format_assigned_field_help(sub_field, buf, parens, indent, separator, is_multiline); + fmt_comments_only(buf, spaces.iter(), indent); + } + Malformed(raw) => { + buf.push_str(raw); + } + } +} + +impl<'a> Formattable<'a> for Tag<'a> { + fn is_multiline(&self) -> bool { + use self::Tag::*; + + match self { + Global { args, .. } | Private { args, .. } => { + args.iter().any(|arg| (&arg.value).is_multiline()) + } + Tag::SpaceBefore(_, _) | Tag::SpaceAfter(_, _) => true, + Malformed(text) => text.chars().any(|c| c == '\n'), + } + } + + fn format_with_options( + &self, + buf: &mut String<'a>, + _parens: Parens, + _newlines: Newlines, + indent: u16, + ) { + let is_multiline = self.is_multiline(); + + match self { + Tag::Global { name, args } => { + buf.push_str(name.value); + if is_multiline { + let arg_indent = indent + INDENT; + + for arg in *args { + newline(buf, arg_indent); + arg.format_with_options(buf, Parens::InApply, Newlines::No, arg_indent); + } + } else { + for arg in *args { + buf.push(' '); + arg.format_with_options(buf, Parens::InApply, Newlines::No, indent); + } + } + } + Tag::Private { name, args } => { + buf.push('@'); + buf.push_str(name.value); + if is_multiline { + let arg_indent = indent + INDENT; + + for arg in *args { + newline(buf, arg_indent); + arg.format_with_options(buf, Parens::InApply, Newlines::No, arg_indent); + } + } else { + for arg in *args { + buf.push(' '); + arg.format_with_options(buf, Parens::InApply, Newlines::No, indent); + } + } + } + Tag::SpaceBefore(_, _) | Tag::SpaceAfter(_, _) => unreachable!(), + Tag::Malformed(raw) => buf.push_str(raw), + } + } +} + +macro_rules! implement_format_sequence { + ($start:expr, $end:expr, $t:ident) => { + fn format_with_options( + &self, + buf: &mut String<'a>, + _parens: Parens, + _newlines: Newlines, + indent: u16, + ) { + buf.push($start); + + let mut iter = self.iter().peekable(); + + let is_multiline = self.is_multiline(); + + let item_indent = if is_multiline { + indent + INDENT + } else { + indent + }; + + while let Some(item) = iter.next() { + if is_multiline { + match &item.value { + $t::SpaceBefore(expr_below, spaces_above_expr) => { + newline(buf, item_indent); + fmt_comments_only(buf, spaces_above_expr.iter(), item_indent); + + match &expr_below { + $t::SpaceAfter(expr_above, spaces_below_expr) => { + expr_above.format(buf, item_indent); + + if iter.peek().is_some() { + buf.push(','); + } + + fmt_condition_spaces( + buf, + spaces_below_expr.iter(), + item_indent, + ); + } + _ => { + expr_below.format(buf, item_indent); + if iter.peek().is_some() { + buf.push(','); + } + } + } + } + + $t::SpaceAfter(sub_expr, spaces) => { + newline(buf, item_indent); + + sub_expr.format(buf, item_indent); + if iter.peek().is_some() { + buf.push(','); + } + + fmt_condition_spaces(buf, spaces.iter(), item_indent); + } + + _ => { + newline(buf, item_indent); + item.format(buf, item_indent); + if iter.peek().is_some() { + buf.push(','); + } + } + } + } else { + buf.push(' '); + item.format(buf, item_indent); + if iter.peek().is_some() { + buf.push(','); + } + } + } + + if is_multiline { + newline(buf, indent); + } + + if !self.is_empty() && !is_multiline { + buf.push(' '); + } + buf.push($end); + } + }; +} + +impl<'a> Formattable<'a> for &'a [Located>] { + fn is_multiline(&self) -> bool { + self.iter().any(|t| t.value.is_multiline()) + } + + implement_format_sequence!('[', ']', Tag); +} + +impl<'a> Formattable<'a> for &'a [Located>>] { + fn is_multiline(&self) -> bool { + self.iter().any(|f| f.value.is_multiline()) + } + + implement_format_sequence!('{', '}', AssignedField); +} diff --git a/compiler/fmt/src/def.rs b/compiler/fmt/src/def.rs index 8a35aa01f6..3e95988b80 100644 --- a/compiler/fmt/src/def.rs +++ b/compiler/fmt/src/def.rs @@ -1,46 +1,110 @@ -use crate::expr::{fmt_expr, is_multiline_expr}; +use crate::annotation::{Formattable, Newlines, Parens}; use crate::pattern::fmt_pattern; -use crate::spaces::{fmt_spaces, newline, INDENT}; +use crate::spaces::{fmt_spaces, is_comment, newline, INDENT}; use bumpalo::collections::String; -use roc_parse::ast::{Def, Expr}; +use roc_parse::ast::{Def, Expr, Pattern}; -pub fn fmt_def<'a>(buf: &mut String<'a>, def: &'a Def<'a>, indent: u16) { - use roc_parse::ast::Def::*; +/// A Located formattable value is also formattable +impl<'a> Formattable<'a> for Def<'a> { + fn is_multiline(&self) -> bool { + use roc_parse::ast::Def::*; - match def { - Annotation(_, _) => panic!("TODO have format_def support Annotation"), - Alias { .. } => panic!("TODO have format_def support Alias"), - Body(loc_pattern, loc_expr) => { - fmt_pattern(buf, &loc_pattern.value, indent, true, false); - buf.push_str(" ="); - if is_multiline_expr(&loc_expr.value) { - match &loc_expr.value { - Expr::Record { .. } | Expr::List(_) => { - newline(buf, indent + INDENT); - fmt_expr(buf, &loc_expr.value, indent + INDENT, false, true); - } - _ => { + match self { + Alias { ann, .. } => ann.is_multiline(), + Annotation(loc_pattern, loc_annotation) => { + loc_pattern.is_multiline() || loc_annotation.is_multiline() + } + Body(loc_pattern, loc_expr) => loc_pattern.is_multiline() || loc_expr.is_multiline(), + + TypedBody(_loc_pattern, _loc_annotation, _loc_expr) => { + unreachable!("annotations and bodies have not yet been merged into TypedBody"); + } + + SpaceBefore(sub_def, spaces) | SpaceAfter(sub_def, spaces) => { + spaces.iter().any(|s| is_comment(s)) || sub_def.is_multiline() + } + Nested(def) => def.is_multiline(), + } + } + + fn format_with_options( + &self, + buf: &mut String<'a>, + _parens: Parens, + _newlines: Newlines, + indent: u16, + ) { + use roc_parse::ast::Def::*; + + match self { + Annotation(loc_pattern, loc_annotation) => { + loc_pattern.format(buf, indent); + buf.push_str(" : "); + loc_annotation.format(buf, indent); + } + Alias { name, vars, ann } => { + buf.push_str(name.value); + + if vars.is_empty() { + buf.push(' '); + } else { + for var in *vars { buf.push(' '); - fmt_expr(buf, &loc_expr.value, indent, false, true); + fmt_pattern(buf, &var.value, indent, Parens::NotNeeded); } } - } else { - buf.push(' '); - fmt_expr(buf, &loc_expr.value, indent, false, true); - } - } - TypedBody(_loc_pattern, _loc_annotation, _loc_expr) => { - panic!("TODO support Annotation in TypedBody"); - } - SpaceBefore(sub_def, spaces) => { - fmt_spaces(buf, spaces.iter(), indent); - fmt_def(buf, sub_def, indent); - } - SpaceAfter(sub_def, spaces) => { - fmt_def(buf, sub_def, indent); - fmt_spaces(buf, spaces.iter(), indent); + buf.push_str(" : "); + + ann.format(buf, indent) + } + Body(loc_pattern, loc_expr) => { + fmt_body(buf, &loc_pattern.value, &loc_expr.value, indent); + } + TypedBody(_loc_pattern, _loc_annotation, _loc_expr) => { + unreachable!("annotations and bodies have not yet been merged into TypedBody"); + } + SpaceBefore(sub_def, spaces) => { + fmt_spaces(buf, spaces.iter(), indent); + sub_def.format(buf, indent); + } + SpaceAfter(sub_def, spaces) => { + sub_def.format(buf, indent); + fmt_spaces(buf, spaces.iter(), indent); + } + Nested(def) => def.format(buf, indent), } - Nested(def) => fmt_def(buf, def, indent), + } +} + +pub fn fmt_def<'a>(buf: &mut String<'a>, def: &Def<'a>, indent: u16) { + def.format(buf, indent); +} + +pub fn fmt_body<'a>( + buf: &mut String<'a>, + pattern: &'a Pattern<'a>, + body: &'a Expr<'a>, + indent: u16, +) { + pattern.format_with_options(buf, Parens::InApply, Newlines::No, indent); + buf.push_str(" ="); + if body.is_multiline() { + match body { + Expr::SpaceBefore(_, _) => { + body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT); + } + Expr::Record { .. } | Expr::List(_) => { + newline(buf, indent + INDENT); + body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT); + } + _ => { + buf.push(' '); + body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); + } + } + } else { + buf.push(' '); + body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); } } diff --git a/compiler/fmt/src/expr.rs b/compiler/fmt/src/expr.rs index 219eb8ebec..d98dbd5d11 100644 --- a/compiler/fmt/src/expr.rs +++ b/compiler/fmt/src/expr.rs @@ -1,205 +1,281 @@ +use crate::annotation::{Formattable, Newlines, Parens}; use crate::def::fmt_def; use crate::pattern::fmt_pattern; use crate::spaces::{ - add_spaces, fmt_comments_only, fmt_condition_spaces, fmt_spaces, is_comment, newline, INDENT, + add_spaces, fmt_comments_only, fmt_condition_spaces, fmt_spaces, newline, INDENT, }; use bumpalo::collections::{String, Vec}; use roc_module::operator::{self, BinOp}; use roc_parse::ast::{AssignedField, Base, CommentOrNewline, Expr, Pattern, WhenBranch}; use roc_region::all::Located; -pub fn fmt_expr<'a>( - buf: &mut String<'a>, - expr: &'a Expr<'a>, - indent: u16, - apply_needs_parens: bool, - format_newlines: bool, -) { - use self::Expr::*; +impl<'a> Formattable<'a> for Expr<'a> { + fn is_multiline(&self) -> bool { + use roc_parse::ast::Expr::*; + // TODO cache these answers using a Map, so + // we don't have to traverse subexpressions repeatedly - match expr { - SpaceBefore(sub_expr, spaces) => { - if format_newlines { - fmt_spaces(buf, spaces.iter(), indent); - } else { - fmt_comments_only(buf, spaces.iter(), indent); - } - fmt_expr(buf, sub_expr, indent, apply_needs_parens, format_newlines); - } - SpaceAfter(sub_expr, spaces) => { - fmt_expr(buf, sub_expr, indent, apply_needs_parens, format_newlines); - if format_newlines { - fmt_spaces(buf, spaces.iter(), indent); - } else { - fmt_comments_only(buf, spaces.iter(), indent); - } - } - ParensAround(sub_expr) => { - buf.push('('); - fmt_expr(buf, sub_expr, indent, false, true); - buf.push(')'); - } - Str(string) => { - buf.push('"'); - buf.push_str(string); - buf.push('"'); - } - Var { module_name, ident } => { - if !module_name.is_empty() { - buf.push_str(module_name); - buf.push('.'); + match self { + // Return whether these spaces contain any Newlines + SpaceBefore(_sub_expr, spaces) | SpaceAfter(_sub_expr, spaces) => { + debug_assert!(!spaces.is_empty()); + + // "spaces" always contain either a newline or comment, and comments have newlines + true } - buf.push_str(ident); + // These expressions never have newlines + Float(_) + | Num(_) + | NonBase10Int { .. } + | Str(_) + | Access(_, _) + | AccessorFunction(_) + | Var { .. } + | MalformedIdent(_) + | MalformedClosure + | GlobalTag(_) + | PrivateTag(_) => false, + + // These expressions always have newlines + Defs(_, _) | When(_, _) => true, + + List(elems) => elems.iter().any(|loc_expr| loc_expr.is_multiline()), + + BlockStr(lines) => lines.len() > 1, + Apply(loc_expr, args, _) => { + loc_expr.is_multiline() || args.iter().any(|loc_arg| loc_arg.is_multiline()) + } + + If(loc_cond, loc_if_true, loc_if_false) => { + loc_cond.is_multiline() || loc_if_true.is_multiline() || loc_if_false.is_multiline() + } + + BinOp((loc_left, _, loc_right)) => { + let next_is_multiline_bin_op: bool = match &loc_right.value { + Expr::BinOp((_, _, nested_loc_right)) => nested_loc_right.is_multiline(), + _ => false, + }; + + next_is_multiline_bin_op || loc_left.is_multiline() || loc_right.is_multiline() + } + + UnaryOp(loc_subexpr, _) | PrecedenceConflict(_, _, _, loc_subexpr) => { + loc_subexpr.is_multiline() + } + + ParensAround(subexpr) | Nested(subexpr) => subexpr.is_multiline(), + + Closure(loc_patterns, loc_body) => { + // check the body first because it's more likely to be multiline + loc_body.is_multiline() + || loc_patterns + .iter() + .any(|loc_pattern| loc_pattern.is_multiline()) + } + + Record { fields, .. } => fields.iter().any(|loc_field| loc_field.is_multiline()), } - Apply(loc_expr, loc_args, _) => { - if apply_needs_parens { + } + + fn format_with_options( + &self, + buf: &mut String<'a>, + parens: Parens, + newlines: Newlines, + indent: u16, + ) { + use self::Expr::*; + + let format_newlines = newlines == Newlines::Yes; + let apply_needs_parens = parens == Parens::InApply; + + match self { + SpaceBefore(sub_expr, spaces) => { + if format_newlines { + fmt_spaces(buf, spaces.iter(), indent); + } else { + fmt_comments_only(buf, spaces.iter(), indent); + } + sub_expr.format_with_options(buf, parens, newlines, indent); + } + SpaceAfter(sub_expr, spaces) => { + sub_expr.format_with_options(buf, parens, newlines, indent); + if format_newlines { + fmt_spaces(buf, spaces.iter(), indent); + } else { + fmt_comments_only(buf, spaces.iter(), indent); + } + } + ParensAround(sub_expr) => { buf.push('('); - } - - fmt_expr(buf, &loc_expr.value, indent, true, true); - - let multiline_args = loc_args - .iter() - .any(|loc_arg| is_multiline_expr(&loc_arg.value)); - - if multiline_args { - let arg_indent = indent + INDENT; - - for loc_arg in loc_args { - newline(buf, arg_indent); - fmt_expr(buf, &loc_arg.value, arg_indent, true, false); - } - } else { - for loc_arg in loc_args { - buf.push(' '); - fmt_expr(buf, &loc_arg.value, indent, true, true); - } - } - - if apply_needs_parens { + sub_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); buf.push(')'); } - } - BlockStr(lines) => { - buf.push_str("\"\"\""); - for line in lines.iter() { - buf.push_str(line); + Str(string) => { + buf.push('"'); + buf.push_str(string); + buf.push('"'); } - buf.push_str("\"\"\""); - } - Num(string) | Float(string) | GlobalTag(string) | PrivateTag(string) => { - buf.push_str(string) - } - NonBase10Int { - base, - string, - is_negative, - } => { - if *is_negative { - buf.push('-'); + Var { module_name, ident } => { + if !module_name.is_empty() { + buf.push_str(module_name); + buf.push('.'); + } + + buf.push_str(ident); } + Apply(loc_expr, loc_args, _) => { + if apply_needs_parens { + buf.push('('); + } - buf.push('0'); + loc_expr.format_with_options(buf, Parens::InApply, Newlines::Yes, indent); - buf.push(match base { - Base::Hex => 'x', - Base::Octal => 'o', - Base::Binary => 'b', - }); + let multiline_args = loc_args.iter().any(|loc_arg| loc_arg.is_multiline()); - buf.push_str(string); - } - Record { fields, update } => { - fmt_record(buf, *update, fields, indent, apply_needs_parens); - } - Closure(loc_patterns, loc_ret) => { - fmt_closure(buf, loc_patterns, loc_ret, indent); - } - Defs(defs, ret) => { - // It should theoretically be impossible to *parse* an empty defs list. - // (Canonicalization can remove defs later, but that hasn't happened yet!) - debug_assert!(!defs.is_empty()); + if multiline_args { + let arg_indent = indent + INDENT; - // The first def is located last in the list, because it gets added there - // with .push() for efficiency. (The order of parsed defs doesn't - // matter because canonicalization sorts them anyway.) - // The other defs in the list are in their usual order. - if let Some(loc_first_def) = defs.last() { - let other_spaced_defs = &defs[0..defs.len() - 1]; + for loc_arg in loc_args { + newline(buf, arg_indent); + loc_arg.format_with_options(buf, Parens::InApply, Newlines::No, arg_indent); + } + } else { + for loc_arg in loc_args { + buf.push(' '); + loc_arg.format_with_options(buf, Parens::InApply, Newlines::Yes, indent); + } + } - fmt_def(buf, &loc_first_def.value, indent); - - for loc_def in other_spaced_defs.iter() { - fmt_def(buf, &loc_def.value, indent); + if apply_needs_parens { + buf.push(')'); } } - - let empty_line_before_return = empty_line_before_expr(&ret.value); - - if !empty_line_before_return { - buf.push('\n'); + BlockStr(lines) => { + buf.push_str("\"\"\""); + for line in lines.iter() { + buf.push_str(line); + } + buf.push_str("\"\"\""); } - - // Even if there were no defs, which theoretically should never happen, - // still print the return value. - fmt_expr(buf, &ret.value, indent, false, true); - } - If(loc_condition, loc_then, loc_else) => { - fmt_if(buf, loc_condition, loc_then, loc_else, indent); - } - When(loc_condition, branches) => fmt_when(buf, loc_condition, branches, indent), - List(loc_items) => { - fmt_list(buf, &loc_items, indent); - } - BinOp((loc_left_side, bin_op, loc_right_side)) => fmt_bin_op( - buf, - loc_left_side, - bin_op, - loc_right_side, - false, - apply_needs_parens, - indent, - ), - UnaryOp(sub_expr, unary_op) => { - match &unary_op.value { - operator::UnaryOp::Negate => { + Num(string) | Float(string) | GlobalTag(string) | PrivateTag(string) => { + buf.push_str(string) + } + NonBase10Int { + base, + string, + is_negative, + } => { + if *is_negative { buf.push('-'); } - operator::UnaryOp::Not => { - buf.push('!'); - } - } - fmt_expr( + match base { + Base::Hex => buf.push_str("0x"), + Base::Octal => buf.push_str("0o"), + Base::Binary => buf.push_str("0b"), + Base::Decimal => { /* nothing */ } + } + + buf.push_str(string); + } + Record { fields, update } => { + fmt_record(buf, *update, fields, indent); + } + Closure(loc_patterns, loc_ret) => { + fmt_closure(buf, loc_patterns, loc_ret, indent); + } + Defs(defs, ret) => { + // It should theoretically be impossible to *parse* an empty defs list. + // (Canonicalization can remove defs later, but that hasn't happened yet!) + debug_assert!(!defs.is_empty()); + + // The first def is located last in the list, because it gets added there + // with .push() for efficiency. (The order of parsed defs doesn't + // matter because canonicalization sorts them anyway.) + // The other defs in the list are in their usual order. + // + // But, the first element of `defs` could be the annotation belonging to the final + // element, so format the annotation first. + let it = defs.iter().peekable(); + + /* + // so if it exists, format the annotation + if let Some(Located { + value: Def::Annotation(_, _), + .. + }) = it.peek() + { + let def = it.next().unwrap(); + fmt_def(buf, &def.value, indent); + } + + // then (using iter_back to get the last value of the `defs` vec) format the first body + if let Some(loc_first_def) = it.next_back() { + fmt_def(buf, &loc_first_def.value, indent); + } + */ + + // then format the other defs in order + for loc_def in it { + fmt_def(buf, &loc_def.value, indent); + } + + let empty_line_before_return = empty_line_before_expr(&ret.value); + + if !empty_line_before_return { + buf.push('\n'); + } + + // Even if there were no defs, which theoretically should never happen, + // still print the return value. + ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); + } + If(loc_condition, loc_then, loc_else) => { + fmt_if(buf, loc_condition, loc_then, loc_else, indent); + } + When(loc_condition, branches) => fmt_when(buf, loc_condition, branches, indent), + List(loc_items) => { + fmt_list(buf, &loc_items, indent); + } + BinOp((loc_left_side, bin_op, loc_right_side)) => fmt_bin_op( buf, - &sub_expr.value, + loc_left_side, + bin_op, + loc_right_side, + false, + parens, indent, - apply_needs_parens, - format_newlines, - ); + ), + UnaryOp(sub_expr, unary_op) => { + match &unary_op.value { + operator::UnaryOp::Negate => { + buf.push('-'); + } + operator::UnaryOp::Not => { + buf.push('!'); + } + } + + sub_expr.format_with_options(buf, parens, newlines, indent); + } + Nested(nested_expr) => { + nested_expr.format_with_options(buf, parens, newlines, indent); + } + AccessorFunction(key) => { + buf.push('.'); + buf.push_str(key); + } + Access(expr, key) => { + expr.format_with_options(buf, parens, Newlines::Yes, indent); + buf.push('.'); + buf.push_str(key); + } + MalformedIdent(_) => {} + MalformedClosure => {} + PrecedenceConflict(_, _, _, _) => {} } - Nested(nested_expr) => { - fmt_expr( - buf, - nested_expr, - indent, - apply_needs_parens, - format_newlines, - ); - } - AccessorFunction(key) => { - buf.push('.'); - buf.push_str(key); - } - Access(expr, key) => { - fmt_expr(buf, expr, indent, apply_needs_parens, true); - buf.push('.'); - buf.push_str(key); - } - MalformedIdent(_) => {} - MalformedClosure => {} - PrecedenceConflict(_, _, _, _) => {} } } @@ -209,13 +285,13 @@ fn fmt_bin_op<'a>( loc_bin_op: &'a Located, loc_right_side: &'a Located>, part_of_multi_line_bin_ops: bool, - apply_needs_parens: bool, + apply_needs_parens: Parens, indent: u16, ) { - fmt_expr(buf, &loc_left_side.value, indent, apply_needs_parens, false); + loc_left_side.format_with_options(buf, apply_needs_parens, Newlines::No, indent); - let is_multiline = is_multiline_expr(&loc_right_side.value) - || is_multiline_expr(&loc_left_side.value) + let is_multiline = (&loc_right_side.value).is_multiline() + || (&loc_left_side.value).is_multiline() || part_of_multi_line_bin_ops; if is_multiline { @@ -260,21 +336,17 @@ fn fmt_bin_op<'a>( } _ => { - fmt_expr(buf, &loc_right_side.value, indent, apply_needs_parens, true); + loc_right_side.format_with_options(buf, apply_needs_parens, Newlines::Yes, indent); } } } -pub fn fmt_list<'a>( - buf: &mut String<'a>, - loc_items: &'a Vec<'a, &'a Located>>, - indent: u16, -) { +pub fn fmt_list<'a>(buf: &mut String<'a>, loc_items: &[&Located>], indent: u16) { buf.push('['); let mut iter = loc_items.iter().peekable(); - let is_multiline = loc_items.iter().any(|item| is_multiline_expr(&item.value)); + let is_multiline = loc_items.iter().any(|item| (&item.value).is_multiline()); let item_indent = if is_multiline { indent + INDENT @@ -291,7 +363,7 @@ pub fn fmt_list<'a>( match &expr_below { Expr::SpaceAfter(expr_above, spaces_below_expr) => { - fmt_expr(buf, expr_above, item_indent, false, false); + expr_above.format(buf, item_indent); if iter.peek().is_some() { buf.push(','); @@ -300,7 +372,7 @@ pub fn fmt_list<'a>( fmt_condition_spaces(buf, spaces_below_expr.iter(), item_indent); } _ => { - fmt_expr(buf, expr_below, item_indent, false, false); + expr_below.format(buf, item_indent); if iter.peek().is_some() { buf.push(','); } @@ -311,7 +383,7 @@ pub fn fmt_list<'a>( Expr::SpaceAfter(sub_expr, spaces) => { newline(buf, item_indent); - fmt_expr(buf, sub_expr, item_indent, false, false); + sub_expr.format(buf, item_indent); if iter.peek().is_some() { buf.push(','); @@ -322,7 +394,7 @@ pub fn fmt_list<'a>( _ => { newline(buf, item_indent); - fmt_expr(buf, &item.value, item_indent, false, true); + item.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, item_indent); if iter.peek().is_some() { buf.push(','); } @@ -330,7 +402,7 @@ pub fn fmt_list<'a>( } } else { buf.push(' '); - fmt_expr(buf, &item.value, item_indent, false, true); + item.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, item_indent); if iter.peek().is_some() { buf.push(','); } @@ -347,50 +419,6 @@ pub fn fmt_list<'a>( buf.push(']'); } -pub fn fmt_field<'a>( - buf: &mut String<'a>, - assigned_field: &'a AssignedField<'a, Expr<'a>>, - is_multiline: bool, - indent: u16, - apply_needs_parens: bool, -) { - use self::AssignedField::*; - - match assigned_field { - LabeledValue(name, spaces, value) => { - if is_multiline { - newline(buf, indent); - } - - buf.push_str(name.value); - - if !spaces.is_empty() { - fmt_spaces(buf, spaces.iter(), indent); - } - - buf.push(':'); - buf.push(' '); - fmt_expr(buf, &value.value, indent, apply_needs_parens, true); - } - LabelOnly(name) => { - if is_multiline { - newline(buf, indent); - } - - buf.push_str(name.value); - } - AssignedField::SpaceBefore(sub_expr, spaces) => { - fmt_comments_only(buf, spaces.iter(), indent); - fmt_field(buf, sub_expr, is_multiline, indent, apply_needs_parens); - } - AssignedField::SpaceAfter(sub_expr, spaces) => { - fmt_field(buf, sub_expr, is_multiline, indent, apply_needs_parens); - fmt_comments_only(buf, spaces.iter(), indent); - } - Malformed(string) => buf.push_str(string), - } -} - pub fn empty_line_before_expr<'a>(expr: &'a Expr<'a>) -> bool { use roc_parse::ast::Expr::*; @@ -420,128 +448,13 @@ pub fn empty_line_before_expr<'a>(expr: &'a Expr<'a>) -> bool { } } -pub fn is_multiline_pattern<'a>(pattern: &'a Pattern<'a>) -> bool { - match pattern { - Pattern::SpaceBefore(_, spaces) | Pattern::SpaceAfter(_, spaces) => { - debug_assert!(!spaces.is_empty()); - - // "spaces" always contain either a newline or comment, and comments have newlines - true - } - - Pattern::Nested(nested_pat) => is_multiline_pattern(nested_pat), - Pattern::Identifier(_) - | Pattern::GlobalTag(_) - | Pattern::PrivateTag(_) - | Pattern::Apply(_, _) - | Pattern::RecordDestructure(_) - | Pattern::RecordField(_, _) - | Pattern::NumLiteral(_) - | Pattern::NonBase10Literal { .. } - | Pattern::FloatLiteral(_) - | Pattern::StrLiteral(_) - | Pattern::BlockStrLiteral(_) - | Pattern::Underscore - | Pattern::Malformed(_) - | Pattern::QualifiedIdentifier { .. } => false, - } -} - -pub fn is_multiline_expr<'a>(expr: &'a Expr<'a>) -> bool { - use roc_parse::ast::Expr::*; - // TODO cache these answers using a Map, so - // we don't have to traverse subexpressions repeatedly - - match expr { - // Return whether these spaces contain any Newlines - SpaceBefore(_, spaces) | SpaceAfter(_, spaces) => { - debug_assert!(!spaces.is_empty()); - - // "spaces" always contain either a newline or comment, and comments have newlines - true - } - - // These expressions never have newlines - Float(_) - | Num(_) - | NonBase10Int { .. } - | Str(_) - | Access(_, _) - | AccessorFunction(_) - | Var { .. } - | MalformedIdent(_) - | MalformedClosure - | GlobalTag(_) - | PrivateTag(_) => false, - - // These expressions always have newlines - Defs(_, _) | When(_, _) => true, - - List(elems) => elems - .iter() - .any(|loc_expr| is_multiline_expr(&loc_expr.value)), - - BlockStr(lines) => lines.len() > 1, - Apply(loc_expr, args, _) => { - is_multiline_expr(&loc_expr.value) - || args.iter().any(|loc_arg| is_multiline_expr(&loc_arg.value)) - } - - If(loc_cond, loc_if_true, loc_if_false) => { - is_multiline_expr(&loc_cond.value) - || is_multiline_expr(&loc_if_true.value) - || is_multiline_expr(&loc_if_false.value) - } - - BinOp((loc_left, _, loc_right)) => { - let next_is_multiline_bin_op: bool = match &loc_right.value { - Expr::BinOp((_, _, nested_loc_right)) => is_multiline_expr(&nested_loc_right.value), - _ => false, - }; - - is_multiline_expr(&loc_left.value) - || is_multiline_expr(&loc_right.value) - || next_is_multiline_bin_op - } - - UnaryOp(loc_subexpr, _) | PrecedenceConflict(_, _, _, loc_subexpr) => { - is_multiline_expr(&loc_subexpr.value) - } - - ParensAround(subexpr) | Nested(subexpr) => is_multiline_expr(&subexpr), - - Closure(loc_patterns, loc_body) => { - // check the body first because it's more likely to be multiline - is_multiline_expr(&loc_body.value) - || loc_patterns - .iter() - .any(|loc_pattern| is_multiline_pattern(&loc_pattern.value)) - } - - Record { fields, .. } => fields - .iter() - .any(|loc_field| is_multiline_field(&loc_field.value)), - } -} - -pub fn is_multiline_field<'a, Val>(field: &'a AssignedField<'a, Val>) -> bool { - use self::AssignedField::*; - - match field { - LabeledValue(_, spaces, _) => !spaces.is_empty(), - LabelOnly(_) => false, - AssignedField::SpaceBefore(_, _) | AssignedField::SpaceAfter(_, _) => true, - Malformed(text) => text.chars().any(|c| c == '\n'), - } -} - fn fmt_when<'a>( buf: &mut String<'a>, loc_condition: &'a Located>, - branches: &'a Vec<'a, &'a WhenBranch<'a>>, + branches: &[&'a WhenBranch<'a>], indent: u16, ) { - let is_multiline_condition = is_multiline_expr(&loc_condition.value); + let is_multiline_condition = loc_condition.is_multiline(); buf.push_str( "\ when", @@ -555,24 +468,24 @@ fn fmt_when<'a>( newline(buf, condition_indent); match &expr_below { Expr::SpaceAfter(expr_above, spaces_below_expr) => { - fmt_expr(buf, &expr_above, condition_indent, false, false); + expr_above.format(buf, condition_indent); fmt_condition_spaces(buf, spaces_below_expr.iter(), condition_indent); newline(buf, indent); } _ => { - fmt_expr(buf, &expr_below, condition_indent, false, false); + expr_below.format(buf, condition_indent); } } } _ => { newline(buf, condition_indent); - fmt_expr(buf, &loc_condition.value, condition_indent, false, false); + loc_condition.format(buf, condition_indent); newline(buf, indent); } } } else { buf.push(' '); - fmt_expr(buf, &loc_condition.value, indent, false, true); + loc_condition.format(buf, indent); buf.push(' '); } buf.push_str("is\n"); @@ -588,7 +501,12 @@ fn fmt_when<'a>( Some(last_pattern) => first_pattern.region.start_line != last_pattern.region.end_line, }; - fmt_pattern(buf, &first_pattern.value, indent + INDENT, false, true); + fmt_pattern( + buf, + &first_pattern.value, + indent + INDENT, + Parens::NotNeeded, + ); for when_pattern in rest { if is_multiline { buf.push_str("\n"); @@ -597,12 +515,12 @@ fn fmt_when<'a>( } else { buf.push_str(" | "); } - fmt_pattern(buf, &when_pattern.value, indent + INDENT, false, true); + fmt_pattern(buf, &when_pattern.value, indent + INDENT, Parens::NotNeeded); } if let Some(guard_expr) = &branch.guard { buf.push_str(" if "); - fmt_expr(buf, &guard_expr.value, indent + INDENT, false, true); + guard_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT); } buf.push_str(" ->\n"); @@ -611,10 +529,20 @@ fn fmt_when<'a>( match expr.value { Expr::SpaceBefore(nested, spaces) => { fmt_comments_only(buf, spaces.iter(), indent + (INDENT * 2)); - fmt_expr(buf, &nested, indent + (INDENT * 2), false, true); + nested.format_with_options( + buf, + Parens::NotNeeded, + Newlines::Yes, + indent + 2 * INDENT, + ); } _ => { - fmt_expr(buf, &expr.value, indent + (INDENT * 2), false, true); + expr.format_with_options( + buf, + Parens::NotNeeded, + Newlines::Yes, + indent + 2 * INDENT, + ); } } @@ -632,9 +560,9 @@ fn fmt_if<'a>( loc_else: &'a Located>, indent: u16, ) { - let is_multiline_then = is_multiline_expr(&loc_then.value); - let is_multiline_else = is_multiline_expr(&loc_else.value); - let is_multiline_condition = is_multiline_expr(&loc_condition.value); + let is_multiline_then = loc_then.is_multiline(); + let is_multiline_else = loc_else.is_multiline(); + let is_multiline_condition = loc_condition.is_multiline(); let is_multiline = is_multiline_then || is_multiline_else || is_multiline_condition; let return_indent = if is_multiline { @@ -653,33 +581,33 @@ fn fmt_if<'a>( match &expr_below { Expr::SpaceAfter(expr_above, spaces_below_expr) => { - fmt_expr(buf, &expr_above, return_indent, false, false); + expr_above.format(buf, return_indent); fmt_condition_spaces(buf, spaces_below_expr.iter(), return_indent); newline(buf, indent); } _ => { - fmt_expr(buf, &expr_below, return_indent, false, false); + expr_below.format(buf, return_indent); } } } Expr::SpaceAfter(expr_above, spaces_below_expr) => { newline(buf, return_indent); - fmt_expr(buf, &expr_above, return_indent, false, false); + expr_above.format(buf, return_indent); fmt_condition_spaces(buf, spaces_below_expr.iter(), return_indent); newline(buf, indent); } _ => { newline(buf, return_indent); - fmt_expr(buf, &loc_condition.value, return_indent, false, false); + loc_condition.format(buf, return_indent); newline(buf, indent); } } } else { buf.push(' '); - fmt_expr(buf, &loc_condition.value, indent, false, true); + loc_condition.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); buf.push(' '); } @@ -688,38 +616,30 @@ fn fmt_if<'a>( if is_multiline { match &loc_then.value { Expr::SpaceBefore(expr_below, spaces_below) => { - let any_comments_below = spaces_below.iter().any(is_comment); - - if !any_comments_below { - newline(buf, return_indent); - } - - fmt_condition_spaces(buf, spaces_below.iter(), return_indent); - - if any_comments_below { - newline(buf, return_indent); - } + // we want exactly one newline, user-inserted extra newlines are ignored. + newline(buf, return_indent); + fmt_comments_only(buf, spaces_below.iter(), return_indent); match &expr_below { Expr::SpaceAfter(expr_above, spaces_above) => { - fmt_expr(buf, &expr_above, return_indent, false, false); + expr_above.format(buf, return_indent); fmt_condition_spaces(buf, spaces_above.iter(), return_indent); newline(buf, indent); } _ => { - fmt_expr(buf, &expr_below, return_indent, false, false); + expr_below.format(buf, return_indent); } } } _ => { - fmt_expr(buf, &loc_condition.value, return_indent, false, false); + loc_condition.format(buf, return_indent); } } } else { buf.push_str(" "); - fmt_expr(buf, &loc_then.value, return_indent, false, false); + loc_then.format(buf, return_indent); } if is_multiline { @@ -729,7 +649,7 @@ fn fmt_if<'a>( buf.push_str(" else "); } - fmt_expr(buf, &loc_else.value, return_indent, false, false); + loc_else.format(buf, return_indent); } pub fn fmt_closure<'a>( @@ -744,7 +664,7 @@ pub fn fmt_closure<'a>( let arguments_are_multiline = loc_patterns .iter() - .any(|loc_pattern| is_multiline_pattern(&loc_pattern.value)); + .any(|loc_pattern| loc_pattern.is_multiline()); // If the arguments are multiline, go down a line and indent. let indent = if arguments_are_multiline { @@ -753,56 +673,60 @@ pub fn fmt_closure<'a>( indent }; - let mut any_args_printed = false; + let mut it = loc_patterns.iter().peekable(); - for loc_pattern in loc_patterns.iter() { - if any_args_printed { - buf.push(','); + while let Some(loc_pattern) = it.next() { + loc_pattern.format(buf, indent); - if !arguments_are_multiline { - buf.push(' '); + if it.peek().is_some() { + if arguments_are_multiline { + buf.push(','); + newline(buf, indent); + } else { + buf.push_str(", "); } - } else { - any_args_printed = true; } - - fmt_pattern(buf, &loc_pattern.value, indent, false, false); } - if !arguments_are_multiline { + if arguments_are_multiline { + newline(buf, indent); + } else { buf.push(' '); } buf.push_str("->"); - let is_multiline = is_multiline_expr(&loc_ret.value); + let is_multiline = (&loc_ret.value).is_multiline(); // If the body is multiline, go down a line and indent. - let indent = if is_multiline { + let body_indent = if is_multiline { indent + INDENT } else { indent }; - let newline_is_next = match &loc_ret.value { - SpaceBefore(_, _) => true, - _ => false, + // the body of the Closure can be on the same line, or + // on a new line. If it's on the same line, insert a space. + + match &loc_ret.value { + SpaceBefore(_, _) => { + // the body starts with (first comment and then) a newline + // do nothing + } + _ => { + // add a space after the `->` + buf.push(' '); + } }; - if !newline_is_next { - // Push a space after the "->" preceding this. - buf.push(' '); - } - - fmt_expr(buf, &loc_ret.value, indent, false, true); + loc_ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, body_indent); } pub fn fmt_record<'a>( buf: &mut String<'a>, update: Option<&'a Located>>, - loc_fields: &'a Vec<'a, Located>>>, + loc_fields: &[Located>>], indent: u16, - apply_needs_parens: bool, ) { buf.push('{'); @@ -814,14 +738,12 @@ pub fn fmt_record<'a>( // doesnt make sense. Some(record_var) => { buf.push(' '); - fmt_expr(buf, &record_var.value, indent, false, false); + record_var.format(buf, indent); buf.push_str(" &"); } } - let is_multiline = loc_fields - .iter() - .any(|loc_field| is_multiline_field(&loc_field.value)); + let is_multiline = loc_fields.iter().any(|loc_field| loc_field.is_multiline()); let mut iter = loc_fields.iter().peekable(); let field_indent = if is_multiline { @@ -834,14 +756,15 @@ pub fn fmt_record<'a>( indent }; + // we abuse the `Newlines` type to decide between multiline or single-line layout + let newlines = if is_multiline { + Newlines::Yes + } else { + Newlines::No + }; + while let Some(field) = iter.next() { - fmt_field( - buf, - &field.value, - is_multiline, - field_indent, - apply_needs_parens, - ); + field.format_with_options(buf, Parens::NotNeeded, newlines, field_indent); if iter.peek().is_some() { buf.push(','); diff --git a/compiler/fmt/src/lib.rs b/compiler/fmt/src/lib.rs index 207fc56cc8..c03633bddf 100644 --- a/compiler/fmt/src/lib.rs +++ b/compiler/fmt/src/lib.rs @@ -10,6 +10,7 @@ // and encouraging shortcuts here creates bad incentives. I would rather temporarily // re-enable this when working on performance optimizations than have it block PRs. #![allow(clippy::large_enum_variant)] +pub mod annotation; pub mod def; pub mod expr; pub mod module; diff --git a/compiler/fmt/src/pattern.rs b/compiler/fmt/src/pattern.rs index 44a8cca814..ac35bcd585 100644 --- a/compiler/fmt/src/pattern.rs +++ b/compiler/fmt/src/pattern.rs @@ -1,4 +1,5 @@ -use crate::spaces::{fmt_comments_only, fmt_spaces}; +use crate::annotation::{Formattable, Newlines, Parens}; +use crate::spaces::{fmt_comments_only, fmt_spaces, is_comment}; use bumpalo::collections::String; use roc_parse::ast::{Base, Pattern}; @@ -6,116 +7,158 @@ pub fn fmt_pattern<'a>( buf: &mut String<'a>, pattern: &'a Pattern<'a>, indent: u16, - apply_needs_parens: bool, - only_comments: bool, + parens: Parens, ) { - use self::Pattern::*; + pattern.format_with_options(buf, parens, Newlines::No, indent); +} - match pattern { - Identifier(string) => buf.push_str(string), - GlobalTag(name) | PrivateTag(name) => { - buf.push_str(name); +impl<'a> Formattable<'a> for Pattern<'a> { + fn is_multiline(&self) -> bool { + // Theory: a pattern should only be multiline when it contains a comment + match self { + Pattern::SpaceBefore(_, spaces) | Pattern::SpaceAfter(_, spaces) => { + debug_assert!(!spaces.is_empty()); + + spaces.iter().any(|s| is_comment(s)) + } + + Pattern::Nested(nested_pat) => nested_pat.is_multiline(), + + Pattern::RecordDestructure(fields) => fields.iter().any(|f| f.is_multiline()), + Pattern::RecordField(_, subpattern) => subpattern.is_multiline(), + + Pattern::Identifier(_) + | Pattern::GlobalTag(_) + | Pattern::PrivateTag(_) + | Pattern::Apply(_, _) + | Pattern::NumLiteral(_) + | Pattern::NonBase10Literal { .. } + | Pattern::FloatLiteral(_) + | Pattern::StrLiteral(_) + | Pattern::BlockStrLiteral(_) + | Pattern::Underscore + | Pattern::Malformed(_) + | Pattern::QualifiedIdentifier { .. } => false, } - Apply(loc_pattern, loc_arg_patterns) => { - if apply_needs_parens { - buf.push('('); + } + + fn format_with_options( + &self, + buf: &mut String<'a>, + parens: Parens, + newlines: Newlines, + indent: u16, + ) { + use self::Pattern::*; + + match self { + Identifier(string) => buf.push_str(string), + GlobalTag(name) | PrivateTag(name) => { + buf.push_str(name); } + Apply(loc_pattern, loc_arg_patterns) => { + // Sometimes, an Apply pattern needs parens around it. + // In particular when an Apply's argument is itself an Apply (> 0) arguments + let parens = !loc_arg_patterns.is_empty() && parens == Parens::InApply; - fmt_pattern(buf, &loc_pattern.value, indent, true, only_comments); - - for loc_arg in loc_arg_patterns.iter() { - buf.push(' '); - fmt_pattern(buf, &loc_arg.value, indent, true, only_comments); - } - - if apply_needs_parens { - buf.push(')'); - } - } - RecordDestructure(loc_patterns) => { - buf.push_str("{ "); - - let mut is_first = true; - - for loc_pattern in *loc_patterns { - if is_first { - is_first = false; - } else { - buf.push_str(", "); + if parens { + buf.push('('); } - fmt_pattern(buf, &loc_pattern.value, indent, true, only_comments); + loc_pattern.format_with_options(buf, Parens::InApply, Newlines::No, indent); + + for loc_arg in loc_arg_patterns.iter() { + buf.push(' '); + loc_arg.format_with_options(buf, Parens::InApply, Newlines::No, indent); + } + + if parens { + buf.push(')'); + } + } + RecordDestructure(loc_patterns) => { + buf.push_str("{ "); + + let mut it = loc_patterns.iter().peekable(); + + while let Some(loc_pattern) = it.next() { + loc_pattern.format(buf, indent); + + if it.peek().is_some() { + buf.push_str(", "); + } + } + + buf.push_str(" }"); } - buf.push_str(" }"); - } - - RecordField(name, loc_pattern) => { - buf.push_str(name); - buf.push_str(": "); - fmt_pattern(buf, &loc_pattern.value, indent, true, only_comments); - } - - NumLiteral(string) => buf.push_str(string), - NonBase10Literal { - base, - string, - is_negative, - } => { - if *is_negative { - buf.push('-'); + RecordField(name, loc_pattern) => { + buf.push_str(name); + buf.push_str(": "); + loc_pattern.format(buf, indent); } - buf.push('0'); + NumLiteral(string) => buf.push_str(string), + NonBase10Literal { + base, + string, + is_negative, + } => { + if *is_negative { + buf.push('-'); + } - buf.push(match base { - Base::Hex => 'x', - Base::Octal => 'o', - Base::Binary => 'b', - }); + match base { + Base::Hex => buf.push_str("0x"), + Base::Octal => buf.push_str("0o"), + Base::Binary => buf.push_str("0b"), + Base::Decimal => { /* nothing */ } + } - buf.push_str(string); - } - FloatLiteral(string) => buf.push_str(string), - StrLiteral(string) => buf.push_str(string), - BlockStrLiteral(lines) => { - for line in *lines { - buf.push_str(line) + buf.push_str(string); } - } - Underscore => buf.push('_'), - - // Space - SpaceBefore(sub_pattern, spaces) => { - if only_comments { - fmt_comments_only(buf, spaces.iter(), indent) - } else { - fmt_spaces(buf, spaces.iter(), indent); + FloatLiteral(string) => buf.push_str(string), + StrLiteral(string) => buf.push_str(string), + BlockStrLiteral(lines) => { + for line in *lines { + buf.push_str(line) + } } - fmt_pattern(buf, sub_pattern, indent, apply_needs_parens, only_comments); - } - SpaceAfter(sub_pattern, spaces) => { - fmt_pattern(buf, sub_pattern, indent, apply_needs_parens, only_comments); - if only_comments { - fmt_comments_only(buf, spaces.iter(), indent) - } else { - fmt_spaces(buf, spaces.iter(), indent); + Underscore => buf.push('_'), + + // Space + SpaceBefore(sub_pattern, spaces) => { + if !sub_pattern.is_multiline() { + fmt_comments_only(buf, spaces.iter(), indent) + } else { + fmt_spaces(buf, spaces.iter(), indent); + } + sub_pattern.format_with_options(buf, parens, newlines, indent); } - } - - Nested(sub_pattern) => { - fmt_pattern(buf, sub_pattern, indent, apply_needs_parens, only_comments); - } - - // Malformed - Malformed(string) => buf.push_str(string), - QualifiedIdentifier { module_name, ident } => { - if !module_name.is_empty() { - buf.push_str(module_name); - buf.push('.'); + SpaceAfter(sub_pattern, spaces) => { + sub_pattern.format_with_options(buf, parens, newlines, indent); + // if only_comments { + if !sub_pattern.is_multiline() { + fmt_comments_only(buf, spaces.iter(), indent) + } else { + fmt_spaces(buf, spaces.iter(), indent); + } } - buf.push_str(ident); + Nested(sub_pattern) => { + sub_pattern.format_with_options(buf, parens, newlines, indent); + } + + // Malformed + Malformed(string) => buf.push_str(string), + QualifiedIdentifier { module_name, ident } => { + if !module_name.is_empty() { + buf.push_str(module_name); + buf.push('.'); + } + + buf.push_str(ident); + } } } } diff --git a/compiler/fmt/tests/test_fmt.rs b/compiler/fmt/tests/test_fmt.rs index 13d922e8d4..317834078e 100644 --- a/compiler/fmt/tests/test_fmt.rs +++ b/compiler/fmt/tests/test_fmt.rs @@ -11,8 +11,8 @@ extern crate roc_parse; mod test_fmt { use bumpalo::collections::String; use bumpalo::Bump; + use roc_fmt::annotation::{Formattable, Newlines, Parens}; use roc_fmt::def::fmt_def; - use roc_fmt::expr::fmt_expr; use roc_fmt::module::fmt_module; use roc_parse::ast::{Attempting, Expr}; use roc_parse::blankspace::space0_before; @@ -38,7 +38,7 @@ mod test_fmt { Ok(actual) => { let mut buf = String::new_in(&arena); - fmt_expr(&mut buf, &actual, 0, false, true); + actual.format_with_options(&mut buf, Parens::NotNeeded, Newlines::Yes, 0); assert_eq!(buf, expected) } @@ -665,14 +665,40 @@ mod test_fmt { expr_formats_same(indoc!( r#" - identity = \a, - b - -> a + identity = \a, b -> a identity 43 "# )); + // expr_formats_same(indoc!( + // r#" + // identity = + // \{ + // x, + // y + // } + // -> a + // + // identity 43 + // "# + // )); + // + expr_formats_same(indoc!( + r#" + identity = \a, + b, + # it's c!! + c + -> a + + identity 43 + "# + )); + } + + #[test] + fn closure_multiline_pattern() { expr_formats_same(indoc!( r#" identity = \a, @@ -2055,4 +2081,140 @@ mod test_fmt { "# )); } + + /// Annotations and aliases + + #[test] + fn list_alias() { + expr_formats_same(indoc!( + r#" + ConsList a : [ Cons a (ConsList a), Nil ] + + f : ConsList a -> ConsList a + f = \_ -> Nil + + f + "# + )); + } + + #[test] + fn wildcard() { + expr_formats_same(indoc!( + r#" + f : List * + f = [] + + a + "# + )); + } + + #[test] + fn identity() { + expr_formats_same(indoc!( + r#" + f : a -> a + f = [] + + a + "# + )); + } + + #[test] + fn tag_union() { + expr_formats_same(indoc!( + r#" + f : [ True, False ] -> [ True, False ] + f = \x -> x + + a + "# + )); + } + + #[test] + fn recursive_tag_union() { + expr_formats_same(indoc!( + r#" + f : [ Cons a (ConsList a), Nil ] as ConsList a -> [ Just a, Nothing ] + f = \list -> + when list is + Nil -> + Nothing + + Cons first _ -> + Just first + + f + "# + )); + } + + #[test] + fn record_type() { + expr_formats_same(indoc!( + r#" + f : { foo : Int } + f = { foo: 1000 } + + a + "# + )); + } + + #[test] + fn record_pattern_with_apply_guard() { + expr_formats_same(indoc!( + r#" + when { x: 1 } is + { x: Just 4 } -> + 4 + "# + )); + } + + #[test] + fn record_pattern_with_record_guard() { + expr_formats_same(indoc!( + r#" + when { x: 1 } is + { x: { x: True } } -> + 4 + "# + )); + } + + #[test] + fn body_starts_with_spaces_multiline() { + expr_formats_same(indoc!( + r#" + y = + Foo + 1 + 2 + + y + "# + )); + } + + // this is a parse error atm + // #[test] + // fn multiline_apply() { + // expr_formats_same(indoc!( + // r#" + // f : + // Result a + // { x : Int + // , y : Float + // } + // c + // -> Int + // f = + // \_ -> 4 + // "# + // )); + // } } diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 37faa18b57..3a905cad80 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -1,4 +1,5 @@ use crate::layout_id::LayoutIds; +use crate::llvm::compare::{build_eq, build_neq}; use crate::llvm::convert::{ basic_type_from_layout, collection, get_fn_type, get_ptr_type, ptr_int, }; @@ -11,10 +12,11 @@ use inkwell::module::{Linkage, Module}; use inkwell::passes::{PassManager, PassManagerBuilder}; use inkwell::types::{BasicTypeEnum, FunctionType, IntType, PointerType, StructType}; use inkwell::values::BasicValueEnum::{self, *}; -use inkwell::values::{FunctionValue, IntValue, PointerValue, StructValue}; +use inkwell::values::{FloatValue, FunctionValue, IntValue, PointerValue, StructValue}; use inkwell::AddressSpace; -use inkwell::{FloatPredicate, IntPredicate, OptimizationLevel}; +use inkwell::{IntPredicate, OptimizationLevel}; use roc_collections::all::ImMap; +use roc_module::low_level::LowLevel; use roc_module::symbol::{Interns, Symbol}; use roc_mono::expr::{Expr, Proc}; use roc_mono::layout::{Builtin, Layout}; @@ -33,7 +35,7 @@ pub enum OptLevel { Optimize, } -type Scope<'a, 'ctx> = ImMap, PointerValue<'ctx>)>; +pub type Scope<'a, 'ctx> = ImMap, PointerValue<'ctx>)>; pub struct Env<'a, 'ctx, 'env> { pub arena: &'a Bump, @@ -285,66 +287,23 @@ pub fn build_expr<'a, 'ctx, 'env>( build_expr(env, layout_ids, &scope, parent, ret) } - CallByName { name, layout, args } => match *name { - Symbol::BOOL_OR => { - // The (||) operator - debug_assert!(args.len() == 2); + CallByName { name, layout, args } => { + let mut arg_tuples: Vec<(BasicValueEnum, &'a Layout<'a>)> = + Vec::with_capacity_in(args.len(), env.arena); - let comparison = - build_expr(env, layout_ids, scope, parent, &args[0].0).into_int_value(); - let build_then = || env.context.bool_type().const_int(true as u64, false).into(); - let build_else = || build_expr(env, layout_ids, scope, parent, &args[1].0); - - let ret_type = env.context.bool_type().into(); - - build_basic_phi2(env, parent, comparison, build_then, build_else, ret_type) + for (arg, arg_layout) in args.iter() { + arg_tuples.push((build_expr(env, layout_ids, scope, parent, arg), arg_layout)); } - Symbol::BOOL_AND => { - // The (&&) operator - debug_assert!(args.len() == 2); - let comparison = - build_expr(env, layout_ids, scope, parent, &args[0].0).into_int_value(); - let build_then = || build_expr(env, layout_ids, scope, parent, &args[1].0); - let build_else = || { - env.context - .bool_type() - .const_int(false as u64, false) - .into() - }; - - let ret_type = env.context.bool_type().into(); - - build_basic_phi2(env, parent, comparison, build_then, build_else, ret_type) - } - Symbol::BOOL_NOT => { - // The (!) operator - debug_assert!(args.len() == 1); - - let arg = build_expr(env, layout_ids, scope, parent, &args[0].0); - - let int_val = env.builder.build_not(arg.into_int_value(), "bool_not"); - - BasicValueEnum::IntValue(int_val) - } - _ => { - let mut arg_tuples: Vec<(BasicValueEnum, &'a Layout<'a>)> = - Vec::with_capacity_in(args.len(), env.arena); - - for (arg, arg_layout) in args.iter() { - arg_tuples.push((build_expr(env, layout_ids, scope, parent, arg), arg_layout)); - } - - call_with_args( - env, - layout_ids, - layout, - *name, - parent, - arg_tuples.into_bump_slice(), - ) - } - }, + call_with_args( + env, + layout_ids, + layout, + *name, + parent, + arg_tuples.into_bump_slice(), + ) + } FunctionPointer(symbol, layout) => { let fn_name = layout_ids .get(*symbol, layout) @@ -427,6 +386,13 @@ pub fn build_expr<'a, 'ctx, 'env>( BasicValueEnum::PointerValue(ptr) } } + EmptyArray => { + let struct_type = collection(env.context, env.ptr_bytes); + + // The pointer should be null (aka zero) and the length should be zero, + // so the whole struct should be a const_zero + BasicValueEnum::StructValue(struct_type.const_zero()) + } Array { elem_layout, elems } => { let ctx = env.context; let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); @@ -482,7 +448,7 @@ pub fn build_expr<'a, 'ctx, 'env>( .build_insert_value(struct_val, len, Builtin::WRAPPER_LEN, "insert_len") .unwrap(); - // + // Bitcast to an array of raw bytes builder.build_bitcast( struct_val.into_struct_value(), collection(ctx, ptr_bytes), @@ -494,6 +460,7 @@ pub fn build_expr<'a, 'ctx, 'env>( Struct(sorted_fields) => { let ctx = env.context; let builder = env.builder; + let ptr_bytes = env.ptr_bytes; // Determine types let num_fields = sorted_fields.len(); @@ -501,26 +468,38 @@ pub fn build_expr<'a, 'ctx, 'env>( let mut field_vals = Vec::with_capacity_in(num_fields, env.arena); for (field_expr, field_layout) in sorted_fields.iter() { - let val = build_expr(env, layout_ids, &scope, parent, field_expr); - let field_type = - basic_type_from_layout(env.arena, env.context, &field_layout, env.ptr_bytes); + // Zero-sized fields have no runtime representation. + // The layout of the struct expects them to be dropped! + if field_layout.stack_size(ptr_bytes) != 0 { + field_types.push(basic_type_from_layout( + env.arena, + env.context, + &field_layout, + env.ptr_bytes, + )); - field_types.push(field_type); - field_vals.push(val); + field_vals.push(build_expr(env, layout_ids, &scope, parent, field_expr)); + } } - // Create the struct_type - let struct_type = ctx.struct_type(field_types.into_bump_slice(), false); - let mut struct_val = struct_type.const_zero().into(); + // If the record has only one field that isn't zero-sized, + // unwrap it. This is what the layout expects us to do. + if field_vals.len() == 1 { + field_vals.pop().unwrap() + } else { + // Create the struct_type + let struct_type = ctx.struct_type(field_types.into_bump_slice(), false); + let mut struct_val = struct_type.const_zero().into(); - // Insert field exprs into struct_val - for (index, field_val) in field_vals.into_iter().enumerate() { - struct_val = builder - .build_insert_value(struct_val, field_val, index as u32, "insert_field") - .unwrap(); + // Insert field exprs into struct_val + for (index, field_val) in field_vals.into_iter().enumerate() { + struct_val = builder + .build_insert_value(struct_val, field_val, index as u32, "insert_field") + .unwrap(); + } + + BasicValueEnum::StructValue(struct_val.into_struct_value()) } - - BasicValueEnum::StructValue(struct_val.into_struct_value()) } Tag { union_size, @@ -530,6 +509,7 @@ pub fn build_expr<'a, 'ctx, 'env>( let it = arguments.iter(); let ctx = env.context; + let ptr_bytes = env.ptr_bytes; let builder = env.builder; // Determine types @@ -538,26 +518,40 @@ pub fn build_expr<'a, 'ctx, 'env>( let mut field_vals = Vec::with_capacity_in(num_fields, env.arena); for (field_expr, field_layout) in it { - let val = build_expr(env, layout_ids, &scope, parent, field_expr); - let field_type = - basic_type_from_layout(env.arena, env.context, &field_layout, env.ptr_bytes); + // Zero-sized fields have no runtime representation. + // The layout of the struct expects them to be dropped! + if field_layout.stack_size(ptr_bytes) != 0 { + let val = build_expr(env, layout_ids, &scope, parent, field_expr); + let field_type = basic_type_from_layout( + env.arena, + env.context, + &field_layout, + env.ptr_bytes, + ); - field_types.push(field_type); - field_vals.push(val); + field_types.push(field_type); + field_vals.push(val); + } } - // Create the struct_type - let struct_type = ctx.struct_type(field_types.into_bump_slice(), false); - let mut struct_val = struct_type.const_zero().into(); + // If the struct has only one field that isn't zero-sized, + // unwrap it. This is what the layout expects us to do. + if field_vals.len() == 1 { + field_vals.pop().unwrap() + } else { + // Create the struct_type + let struct_type = ctx.struct_type(field_types.into_bump_slice(), false); + let mut struct_val = struct_type.const_zero().into(); - // Insert field exprs into struct_val - for (index, field_val) in field_vals.into_iter().enumerate() { - struct_val = builder - .build_insert_value(struct_val, field_val, index as u32, "insert_field") - .unwrap(); + // Insert field exprs into struct_val + for (index, field_val) in field_vals.into_iter().enumerate() { + struct_val = builder + .build_insert_value(struct_val, field_val, index as u32, "insert_field") + .unwrap(); + } + + BasicValueEnum::StructValue(struct_val.into_struct_value()) } - - BasicValueEnum::StructValue(struct_val.into_struct_value()) } Tag { arguments, @@ -578,15 +572,20 @@ pub fn build_expr<'a, 'ctx, 'env>( let mut field_vals = Vec::with_capacity_in(num_fields, env.arena); for (field_expr, field_layout) in arguments.iter() { - let val = build_expr(env, layout_ids, &scope, parent, field_expr); - let field_type = - basic_type_from_layout(env.arena, env.context, &field_layout, ptr_size); - - field_types.push(field_type); - field_vals.push(val); - let field_size = field_layout.stack_size(ptr_size); - filler -= field_size; + + // Zero-sized fields have no runtime representation. + // The layout of the struct expects them to be dropped! + if field_size != 0 { + let val = build_expr(env, layout_ids, &scope, parent, field_expr); + let field_type = + basic_type_from_layout(env.arena, env.context, &field_layout, ptr_size); + + field_types.push(field_type); + field_vals.push(val); + + filler -= field_size; + } } // TODO verify that this is required (better safe than sorry) @@ -644,6 +643,7 @@ pub fn build_expr<'a, 'ctx, 'env>( wrapper_val = builder .build_insert_value(wrapper_val, result, 0, "insert_field") .unwrap(); + wrapper_val.into_struct_value().into() } AccessAtIndex { @@ -652,19 +652,29 @@ pub fn build_expr<'a, 'ctx, 'env>( is_unwrapped, .. } if *is_unwrapped => { + use inkwell::values::BasicValueEnum::*; + let builder = env.builder; // Get Struct val - // Since this is a one-element tag union, we get the correct struct immediately - let argument = build_expr(env, layout_ids, &scope, parent, expr).into_struct_value(); - - builder - .build_extract_value( - argument, - *index as u32, - env.arena.alloc(format!("tag_field_access_{}_", index)), - ) - .unwrap() + // Since this is a one-element tag union, we get the underlying value + // right away. However, that struct might have only one field which + // is not zero-sized, which would make it unwrapped. If that happens, + // we must be + match build_expr(env, layout_ids, &scope, parent, expr) { + StructValue(argument) => builder + .build_extract_value( + argument, + *index as u32, + env.arena.alloc(format!("tag_field_access_{}_", index)), + ) + .unwrap(), + other => { + // If it's not a Struct, that means it was unwrapped, + // so we should return it directly. + other + } + } } AccessAtIndex { @@ -700,9 +710,13 @@ pub fn build_expr<'a, 'ctx, 'env>( .build_extract_value(struct_value, *index as u32, "") .expect("desired field did not decode") } - _ => { - panic!("I don't yet know how to LLVM build {:?}", expr); + RuntimeErrorFunction(_) => { + todo!("LLVM build runtime error function of {:?}", expr); } + RuntimeError(_) => { + todo!("LLVM build runtime error of {:?}", expr); + } + RunLowLevel(op, args) => run_low_level(env, layout_ids, scope, parent, *op, args), } } @@ -715,7 +729,7 @@ fn load_symbol<'a, 'ctx, 'env>( Some((_, ptr)) => env .builder .build_load(*ptr, symbol.ident_string(&env.interns)), - None => panic!("Could not find a var for {:?} in scope {:?}", symbol, scope), + None => panic!("There was no entry for {:?} in scope {:?}", symbol, scope), } } @@ -837,9 +851,12 @@ fn build_switch<'a, 'ctx, 'env>( // // they either need to all be i8, or i64 let int_val = match cond_layout { + Layout::Builtin(Builtin::Int128) => context.i128_type().const_int(*int as u64, false), /* TODO file an issue: you can't currently have an int literal bigger than 64 bits long, and also (as we see here), you can't currently have (at least in Inkwell) a when-branch with an i128 literal in its pattren */ Layout::Builtin(Builtin::Int64) => context.i64_type().const_int(*int as u64, false), - Layout::Builtin(Builtin::Bool) => context.bool_type().const_int(*int as u64, false), - Layout::Builtin(Builtin::Byte) => context.i8_type().const_int(*int as u64, false), + Layout::Builtin(Builtin::Int32) => context.i32_type().const_int(*int as u64, false), + Layout::Builtin(Builtin::Int16) => context.i16_type().const_int(*int as u64, false), + Layout::Builtin(Builtin::Int8) => context.i8_type().const_int(*int as u64, false), + Layout::Builtin(Builtin::Int1) => context.bool_type().const_int(*int as u64, false), _ => panic!("Can't cast to cond_layout = {:?}", cond_layout), }; let block = context.append_basic_block(parent, format!("branch{}", int).as_str()); @@ -928,6 +945,42 @@ where phi.as_basic_value() } +fn build_basic_phi1<'a, 'ctx, 'env, PassFn>( + env: &Env<'a, 'ctx, 'env>, + parent: FunctionValue<'ctx>, + comparison: IntValue<'ctx>, + mut build_pass: PassFn, + ret_type: BasicTypeEnum<'ctx>, +) -> BasicValueEnum<'ctx> +where + PassFn: FnMut() -> BasicValueEnum<'ctx>, +{ + let builder = env.builder; + let context = env.context; + + // build blocks + let then_block = context.append_basic_block(parent, "then"); + let cont_block = context.append_basic_block(parent, "branchcont"); + + builder.build_conditional_branch(comparison, then_block, cont_block); + + // build then block + builder.position_at_end(then_block); + let then_val = build_pass(); + builder.build_unconditional_branch(cont_block); + + let then_block = builder.get_insert_block().unwrap(); + + // emit merge block + builder.position_at_end(cont_block); + + let phi = builder.build_phi(ret_type, "branch"); + + phi.add_incoming(&[(&then_val, then_block)]); + + phi.as_basic_value() +} + /// TODO could this be added to Inkwell itself as a method on BasicValueEnum? fn set_name(bv_enum: BasicValueEnum<'_>, name: &str) { match bv_enum { @@ -1040,6 +1093,194 @@ pub fn verify_fn(fn_val: FunctionValue<'_>) { } } +/// List.single : a -> List a +fn list_single<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + elem: BasicValueEnum<'ctx>, + elem_layout: &Layout<'a>, +) -> BasicValueEnum<'ctx> { + let builder = env.builder; + let ctx = env.context; + + let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); + let elem_bytes = elem_layout.stack_size(env.ptr_bytes) as u64; + + let ptr = { + let bytes_len = elem_bytes; + let len_type = env.ptr_int(); + let len = len_type.const_int(bytes_len, false); + + env.builder + .build_array_malloc(elem_type, len, "create_list_ptr") + .unwrap() + + // TODO check if malloc returned null; if so, runtime error for OOM! + }; + + // Put the element into the list + let elem_ptr = unsafe { + builder.build_in_bounds_gep( + ptr, + &[ctx.i64_type().const_int( + // 0 as in 0 index of our new list + 0 as u64, false, + )], + "index", + ) + }; + + builder.build_store(elem_ptr, elem); + + let ptr_bytes = env.ptr_bytes; + let int_type = ptr_int(ctx, ptr_bytes); + let ptr_as_int = builder.build_ptr_to_int(ptr, int_type, "list_cast_ptr"); + let struct_type = collection(ctx, ptr_bytes); + let len = BasicValueEnum::IntValue(env.ptr_int().const_int(1, false)); + + let mut struct_val; + + // Store the pointer + struct_val = builder + .build_insert_value( + struct_type.get_undef(), + ptr_as_int, + Builtin::WRAPPER_PTR, + "insert_ptr", + ) + .unwrap(); + + // Store the length + struct_val = builder + .build_insert_value(struct_val, len, Builtin::WRAPPER_LEN, "insert_len") + .unwrap(); + + // + builder.build_bitcast( + struct_val.into_struct_value(), + collection(ctx, ptr_bytes), + "cast_collection", + ) +} + +/// List.repeat : Int, elem -> List elem +fn list_repeat<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + parent: FunctionValue<'ctx>, + list_len: IntValue<'ctx>, + elem: BasicValueEnum<'ctx>, + elem_layout: &Layout<'a>, +) -> BasicValueEnum<'ctx> { + let builder = env.builder; + let ctx = env.context; + let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); + + // list_len > 0 + // We have to do a loop below, continuously adding the `elem` + // to the output list `List elem` until we have reached the + // number of repeats. This `comparison` is used to check + // if we need to do any looping; because if we dont, then we + // dont need to allocate memory for the index or the check + // if index != 0 + let comparison = builder.build_int_compare( + IntPredicate::UGT, + list_len, + ctx.i64_type().const_int(0, false), + "atleastzero", + ); + + let build_then = || { + // Allocate space for the new array that we'll copy into. + let list_ptr = builder + .build_array_malloc(elem_type, list_len, "create_list_ptr") + .unwrap(); + + // TODO check if malloc returned null; if so, runtime error for OOM! + + let index_name = "#index"; + let start_alloca = builder.build_alloca(ctx.i64_type(), index_name); + + // Start at the last element in the list. + let last_elem_index = builder.build_int_sub( + list_len, + ctx.i64_type().const_int(1, false), + "lastelemindex", + ); + builder.build_store(start_alloca, last_elem_index); + + let loop_bb = ctx.append_basic_block(parent, "loop"); + builder.build_unconditional_branch(loop_bb); + builder.position_at_end(loop_bb); + + // #index = #index - 1 + let curr_index = builder + .build_load(start_alloca, index_name) + .into_int_value(); + let next_index = + builder.build_int_sub(curr_index, ctx.i64_type().const_int(1, false), "nextindex"); + + builder.build_store(start_alloca, next_index); + let elem_ptr = + unsafe { builder.build_in_bounds_gep(list_ptr, &[curr_index], "load_index") }; + + // Mutate the new array in-place to change the element. + builder.build_store(elem_ptr, elem); + + // #index != 0 + let end_cond = builder.build_int_compare( + IntPredicate::NE, + ctx.i64_type().const_int(0, false), + curr_index, + "loopcond", + ); + + let after_bb = ctx.append_basic_block(parent, "afterloop"); + + builder.build_conditional_branch(end_cond, loop_bb, after_bb); + builder.position_at_end(after_bb); + + let ptr_bytes = env.ptr_bytes; + let int_type = ptr_int(ctx, ptr_bytes); + let ptr_as_int = builder.build_ptr_to_int(list_ptr, int_type, "list_cast_ptr"); + let struct_type = collection(ctx, ptr_bytes); + + let mut struct_val; + + // Store the pointer + struct_val = builder + .build_insert_value( + struct_type.get_undef(), + ptr_as_int, + Builtin::WRAPPER_PTR, + "insert_ptr", + ) + .unwrap(); + + // Store the length + struct_val = builder + .build_insert_value(struct_val, list_len, Builtin::WRAPPER_LEN, "insert_len") + .unwrap(); + + builder.build_bitcast( + struct_val.into_struct_value(), + collection(ctx, ptr_bytes), + "cast_collection", + ) + }; + + let build_else = || empty_list(env); + + let struct_type = collection(ctx, env.ptr_bytes); + + build_basic_phi2( + env, + parent, + comparison, + build_then, + build_else, + BasicTypeEnum::StructType(struct_type), + ) +} + #[inline(always)] #[allow(clippy::cognitive_complexity)] fn call_with_args<'a, 'ctx, 'env>( @@ -1047,788 +1288,37 @@ fn call_with_args<'a, 'ctx, 'env>( layout_ids: &mut LayoutIds<'a>, layout: &Layout<'a>, symbol: Symbol, - parent: FunctionValue<'ctx>, + _parent: FunctionValue<'ctx>, args: &[(BasicValueEnum<'ctx>, &'a Layout<'a>)], ) -> BasicValueEnum<'ctx> { - match symbol { - Symbol::INT_ADD | Symbol::NUM_ADD => { - debug_assert!(args.len() == 2); - - let int_val = env.builder.build_int_add( - args[0].0.into_int_value(), - args[1].0.into_int_value(), - "add_i64", - ); - - BasicValueEnum::IntValue(int_val) - } - Symbol::FLOAT_ADD => { - debug_assert!(args.len() == 2); - - let float_val = env.builder.build_float_add( - args[0].0.into_float_value(), - args[1].0.into_float_value(), - "add_f64", - ); - - BasicValueEnum::FloatValue(float_val) - } - Symbol::INT_SUB | Symbol::NUM_SUB => { - debug_assert!(args.len() == 2); - - let int_val = env.builder.build_int_sub( - args[0].0.into_int_value(), - args[1].0.into_int_value(), - "sub_i64", - ); - - BasicValueEnum::IntValue(int_val) - } - Symbol::FLOAT_DIV => { - debug_assert!(args.len() == 2); - - let float_val = env.builder.build_float_div( - args[0].0.into_float_value(), - args[1].0.into_float_value(), - "div_f64", - ); - - BasicValueEnum::FloatValue(float_val) - } - Symbol::FLOAT_SUB => { - debug_assert!(args.len() == 2); - - let float_val = env.builder.build_float_sub( - args[0].0.into_float_value(), - args[1].0.into_float_value(), - "sub_f64", - ); - - BasicValueEnum::FloatValue(float_val) - } - Symbol::FLOAT_ABS => call_intrinsic(LLVM_FABS_F64, env, args), - Symbol::INT_GTE | Symbol::NUM_GTE => { - debug_assert!(args.len() == 2); - - let bool_val = env.builder.build_int_compare( - IntPredicate::SGE, - args[0].0.into_int_value(), - args[1].0.into_int_value(), - "gte_i64", - ); - - BasicValueEnum::IntValue(bool_val) - } - Symbol::FLOAT_GTE => { - debug_assert!(args.len() == 2); - - let bool_val = env.builder.build_float_compare( - FloatPredicate::OGE, - args[0].0.into_float_value(), - args[1].0.into_float_value(), - "gte_F64", - ); - - BasicValueEnum::IntValue(bool_val) - } - Symbol::INT_GT | Symbol::NUM_GT => { - debug_assert!(args.len() == 2); - - let bool_val = env.builder.build_int_compare( - IntPredicate::SGT, - args[0].0.into_int_value(), - args[1].0.into_int_value(), - "gt_i64", - ); - - BasicValueEnum::IntValue(bool_val) - } - Symbol::FLOAT_GT => { - debug_assert!(args.len() == 2); - - let bool_val = env.builder.build_float_compare( - FloatPredicate::OGT, - args[0].0.into_float_value(), - args[1].0.into_float_value(), - "gt_f64", - ); - - BasicValueEnum::IntValue(bool_val) - } - Symbol::INT_LTE | Symbol::NUM_LTE => { - debug_assert!(args.len() == 2); - - let bool_val = env.builder.build_int_compare( - IntPredicate::SLE, - args[0].0.into_int_value(), - args[1].0.into_int_value(), - "lte_i64", - ); - - BasicValueEnum::IntValue(bool_val) - } - Symbol::FLOAT_LTE => { - debug_assert!(args.len() == 2); - - let bool_val = env.builder.build_float_compare( - FloatPredicate::OLE, - args[0].0.into_float_value(), - args[1].0.into_float_value(), - "lte_f64", - ); - - BasicValueEnum::IntValue(bool_val) - } - Symbol::INT_LT | Symbol::NUM_LT => { - debug_assert!(args.len() == 2); - - let bool_val = env.builder.build_int_compare( - IntPredicate::SLT, - args[0].0.into_int_value(), - args[1].0.into_int_value(), - "lt_i64", - ); - - BasicValueEnum::IntValue(bool_val) - } - Symbol::FLOAT_LT => { - debug_assert!(args.len() == 2); - - let bool_val = env.builder.build_float_compare( - FloatPredicate::OLT, - args[0].0.into_float_value(), - args[1].0.into_float_value(), - "lt_f64", - ); - - BasicValueEnum::IntValue(bool_val) - } - Symbol::FLOAT_SIN => call_intrinsic(LLVM_SIN_F64, env, args), - Symbol::FLOAT_COS => call_intrinsic(LLVM_COS_F64, env, args), - Symbol::NUM_MUL => { - debug_assert!(args.len() == 2); - - let int_val = env.builder.build_int_mul( - args[0].0.into_int_value(), - args[1].0.into_int_value(), - "mul_i64", - ); - - BasicValueEnum::IntValue(int_val) - } - Symbol::NUM_NEG => { - debug_assert!(args.len() == 1); - - let int_val = env - .builder - .build_int_neg(args[0].0.into_int_value(), "negate_i64"); - - BasicValueEnum::IntValue(int_val) - } - Symbol::LIST_LEN => { - debug_assert!(args.len() == 1); - - BasicValueEnum::IntValue(load_list_len(env.builder, args[0].0.into_struct_value())) - } - Symbol::LIST_IS_EMPTY => { - debug_assert!(args.len() == 1); - - let list_struct = args[0].0.into_struct_value(); - let builder = env.builder; - let list_len = load_list_len(builder, list_struct); - let zero = env.ptr_int().const_zero(); - let answer = builder.build_int_compare(IntPredicate::EQ, list_len, zero, "is_zero"); - - BasicValueEnum::IntValue(answer) - } - Symbol::INT_REM_UNSAFE => { - debug_assert!(args.len() == 2); - - let int_val = env.builder.build_int_unsigned_rem( - args[0].0.into_int_value(), - args[1].0.into_int_value(), - "rem_i64", - ); - - BasicValueEnum::IntValue(int_val) - } - Symbol::INT_EQ_I64 => { - debug_assert!(args.len() == 2); - - let int_val = env.builder.build_int_compare( - IntPredicate::EQ, - args[0].0.into_int_value(), - args[1].0.into_int_value(), - "cmp_i64", - ); - - BasicValueEnum::IntValue(int_val) - } - Symbol::INT_NEQ_I64 => { - debug_assert!(args.len() == 2); - - let int_val = env.builder.build_int_compare( - IntPredicate::NE, - args[0].0.into_int_value(), - args[1].0.into_int_value(), - "cmp_i64", - ); - - BasicValueEnum::IntValue(int_val) - } - Symbol::INT_EQ_I1 => { - debug_assert!(args.len() == 2); - - let int_val = env.builder.build_int_compare( - IntPredicate::EQ, - args[0].0.into_int_value(), - args[1].0.into_int_value(), - "cmp_i1", - ); - - BasicValueEnum::IntValue(int_val) - } - Symbol::INT_NEQ_I1 => { - debug_assert!(args.len() == 2); - - let int_val = env.builder.build_int_compare( - IntPredicate::NE, - args[0].0.into_int_value(), - args[1].0.into_int_value(), - "cmp_i1", - ); - - BasicValueEnum::IntValue(int_val) - } - Symbol::INT_EQ_I8 => { - debug_assert!(args.len() == 2); - - let int_val = env.builder.build_int_compare( - IntPredicate::EQ, - args[0].0.into_int_value(), - args[1].0.into_int_value(), - "cmp_i8", - ); - - BasicValueEnum::IntValue(int_val) - } - Symbol::INT_NEQ_I8 => { - debug_assert!(args.len() == 2); - - let int_val = env.builder.build_int_compare( - IntPredicate::NE, - args[0].0.into_int_value(), - args[1].0.into_int_value(), - "cmp_i8", - ); - - BasicValueEnum::IntValue(int_val) - } - Symbol::NUM_TO_FLOAT => { - // TODO specialize this to be not just for i64! - let builtin_fn_name = "i64_to_f64_"; - - let fn_val = env - .module - .get_function(builtin_fn_name) - .unwrap_or_else(|| panic!("Unrecognized builtin function: {:?} - if you're working on the Roc compiler, do you need to rebuild the bitcode? See compiler/builtins/bitcode/README.md", builtin_fn_name)); - - let mut arg_vals: Vec = Vec::with_capacity_in(args.len(), env.arena); - - for (arg, _layout) in args.iter() { - arg_vals.push(*arg); + let fn_name = layout_ids + .get(symbol, layout) + .to_symbol_string(symbol, &env.interns); + let fn_val = env + .module + .get_function(fn_name.as_str()) + .unwrap_or_else(|| { + if symbol.is_builtin() { + panic!("Unrecognized builtin function: {:?}", symbol) + } else { + panic!("Unrecognized non-builtin function: {:?}", symbol) } + }); + let mut arg_vals: Vec = Vec::with_capacity_in(args.len(), env.arena); - let call = env - .builder - .build_call(fn_val, arg_vals.into_bump_slice(), "call_builtin"); - - call.set_call_convention(fn_val.get_call_conventions()); - - call.try_as_basic_value() - .left() - .unwrap_or_else(|| panic!("LLVM error: Invalid call for builtin {:?}", symbol)) - } - Symbol::FLOAT_EQ => { - debug_assert!(args.len() == 2); - - let int_val = env.builder.build_float_compare( - FloatPredicate::OEQ, - args[0].0.into_float_value(), - args[1].0.into_float_value(), - "cmp_f64", - ); - - BasicValueEnum::IntValue(int_val) - } - Symbol::LIST_GET_UNSAFE => { - let builder = env.builder; - - // List.get : List elem, Int -> [ Ok elem, OutOfBounds ]* - debug_assert!(args.len() == 2); - - let (_, list_layout) = &args[0]; - - let wrapper_struct = args[0].0.into_struct_value(); - let elem_index = args[1].0.into_int_value(); - - match list_layout { - Layout::Builtin(Builtin::List(elem_layout)) => { - let ctx = env.context; - let elem_type = - basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); - let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); - // Load the pointer to the array data - let array_data_ptr = load_list_ptr(builder, wrapper_struct, ptr_type); - - // Assume the bounds have already been checked earlier - // (e.g. by List.get or List.first, which wrap List.#getUnsafe) - let elem_ptr = unsafe { - builder.build_in_bounds_gep(array_data_ptr, &[elem_index], "elem") - }; - - builder.build_load(elem_ptr, "List.get") - } - _ => { - unreachable!("Invalid List layout for List.get: {:?}", list_layout); - } - } - } - Symbol::FLOAT_SQRT => call_intrinsic(LLVM_SQRT_F64, env, args), - Symbol::FLOAT_ROUND => call_intrinsic(LLVM_LROUND_I64_F64, env, args), - Symbol::LIST_SET => list_set(parent, args, env, InPlace::Clone), - Symbol::LIST_SET_IN_PLACE => list_set(parent, args, env, InPlace::InPlace), - Symbol::LIST_PUSH => list_push(args, env), - Symbol::LIST_SINGLE => { - // List.single : a -> List a - debug_assert!(args.len() == 1); - - let (elem, elem_layout) = args[0]; - - let builder = env.builder; - let ctx = env.context; - - let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); - let elem_bytes = elem_layout.stack_size(env.ptr_bytes) as u64; - - let ptr = { - let bytes_len = elem_bytes; - let len_type = env.ptr_int(); - let len = len_type.const_int(bytes_len, false); - - env.builder - .build_array_malloc(elem_type, len, "create_list_ptr") - .unwrap() - - // TODO check if malloc returned null; if so, runtime error for OOM! - }; - - // Put the element into the list - let elem_ptr = unsafe { - builder.build_in_bounds_gep( - ptr, - &[ctx.i64_type().const_int( - // 0 as in 0 index of our new list - 0 as u64, false, - )], - "index", - ) - }; - - builder.build_store(elem_ptr, elem); - - let ptr_bytes = env.ptr_bytes; - let int_type = ptr_int(ctx, ptr_bytes); - let ptr_as_int = builder.build_ptr_to_int(ptr, int_type, "list_cast_ptr"); - let struct_type = collection(ctx, ptr_bytes); - let len = BasicValueEnum::IntValue(env.ptr_int().const_int(1, false)); - - let mut struct_val; - - // Store the pointer - struct_val = builder - .build_insert_value( - struct_type.get_undef(), - ptr_as_int, - Builtin::WRAPPER_PTR, - "insert_ptr", - ) - .unwrap(); - - // Store the length - struct_val = builder - .build_insert_value(struct_val, len, Builtin::WRAPPER_LEN, "insert_len") - .unwrap(); - - // - builder.build_bitcast( - struct_val.into_struct_value(), - collection(ctx, ptr_bytes), - "cast_collection", - ) - } - Symbol::LIST_REPEAT => { - // List.repeat : Int, elem -> List elem - debug_assert!(args.len() == 2); - - // Number of repeats - let list_len = args[0].0.into_int_value(); - - let builder = env.builder; - let ctx = env.context; - - let (elem, elem_layout) = args[1]; - let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); - - // list_len > 0 - // We have to do a loop below, continuously adding the `elem` - // to the output list `List elem` until we have reached the - // number of repeats. This `comparison` is used to check - // if we need to do any looping; because if we dont, then we - // dont need to allocate memory for the index or the check - // if index != 0 - let comparison = builder.build_int_compare( - IntPredicate::UGT, - list_len, - ctx.i64_type().const_int(0, false), - "atleastzero", - ); - - let build_then = || { - // Allocate space for the new array that we'll copy into. - let elem_bytes = elem_layout.stack_size(env.ptr_bytes) as u64; - - let list_ptr = { - let bytes_len = elem_bytes; - let len_type = env.ptr_int(); - let len = len_type.const_int(bytes_len, false); - - env.builder - .build_array_malloc(elem_type, len, "create_list_ptr") - .unwrap() - - // TODO check if malloc returned null; if so, runtime error for OOM! - }; - - let index_name = "#index"; - let start_alloca = builder.build_alloca(ctx.i64_type(), index_name); - - builder.build_store(start_alloca, list_len); - - let loop_bb = ctx.append_basic_block(parent, "loop"); - builder.build_unconditional_branch(loop_bb); - builder.position_at_end(loop_bb); - - // #index = #index - 1 - let curr_index = builder - .build_load(start_alloca, index_name) - .into_int_value(); - let next_index = builder.build_int_sub( - curr_index, - ctx.i64_type().const_int(1, false), - "nextindex", - ); - - builder.build_store(start_alloca, next_index); - let elem_ptr = - unsafe { builder.build_in_bounds_gep(list_ptr, &[curr_index], "load_index") }; - - // Mutate the new array in-place to change the element. - builder.build_store(elem_ptr, elem); - - // #index != 0 - let end_cond = builder.build_int_compare( - IntPredicate::NE, - ctx.i64_type().const_int(0, false), - curr_index, - "loopcond", - ); - - let after_bb = ctx.append_basic_block(parent, "afterloop"); - - builder.build_conditional_branch(end_cond, loop_bb, after_bb); - builder.position_at_end(after_bb); - - let ptr_bytes = env.ptr_bytes; - let int_type = ptr_int(ctx, ptr_bytes); - let ptr_as_int = builder.build_ptr_to_int(list_ptr, int_type, "list_cast_ptr"); - let struct_type = collection(ctx, ptr_bytes); - - let mut struct_val; - - // Store the pointer - struct_val = builder - .build_insert_value( - struct_type.get_undef(), - ptr_as_int, - Builtin::WRAPPER_PTR, - "insert_ptr", - ) - .unwrap(); - - // Store the length - struct_val = builder - .build_insert_value(struct_val, list_len, Builtin::WRAPPER_LEN, "insert_len") - .unwrap(); - - builder.build_bitcast( - struct_val.into_struct_value(), - collection(ctx, ptr_bytes), - "cast_collection", - ) - }; - - let build_else = || empty_list(env); - - let struct_type = collection(ctx, env.ptr_bytes); - - build_basic_phi2( - env, - parent, - comparison, - build_then, - build_else, - BasicTypeEnum::StructType(struct_type), - ) - } - Symbol::LIST_REVERSE => { - // List.reverse : List elem -> List elem - debug_assert_eq!(args.len(), 1); - - let (list, list_layout) = &args[0]; - - let wrapper_struct = list.into_struct_value(); - - let builder = env.builder; - let ctx = env.context; - - let list_len = load_list_len(builder, wrapper_struct); - - match list_layout { - Layout::Builtin(Builtin::List(elem_layout)) => { - // Allocate space for the new array that we'll copy into. - let elem_bytes = elem_layout.stack_size(env.ptr_bytes) as u64; - - let elem_type = - basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); - - let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); - - let reversed_list_ptr = { - let len_type = env.ptr_int(); - let len = len_type.const_int(elem_bytes, false); - - env.builder - .build_array_malloc(elem_type, len, "create_reversed_list_ptr") - .unwrap() - - // TODO check if malloc returned null; if so, runtime error for OOM! - }; - - let index_name = "#index"; - let start_alloca = builder.build_alloca(ctx.i64_type(), index_name); - - builder.build_store(start_alloca, list_len); - - let loop_bb = ctx.append_basic_block(parent, "loop"); - builder.build_unconditional_branch(loop_bb); - builder.position_at_end(loop_bb); - - // #index = #index - 1 - let curr_index = builder - .build_load(start_alloca, index_name) - .into_int_value(); - let next_index = builder.build_int_sub( - curr_index, - ctx.i64_type().const_int(1, false), - "nextindex", - ); - - builder.build_store(start_alloca, next_index); - - let list_ptr = load_list_ptr(builder, wrapper_struct, ptr_type); - - // The pointer to the element in the input list - let elem_ptr = unsafe { - builder.build_in_bounds_gep(list_ptr, &[curr_index], "load_index") - }; - - // The pointer to the element in the reversed list - let reverse_elem_ptr = unsafe { - builder.build_in_bounds_gep( - reversed_list_ptr, - &[builder.build_int_sub( - list_len, - builder.build_int_add( - curr_index, - ctx.i64_type().const_int(1, false), - "curr_index_plus_one", - ), - "next_index", - )], - "load_index_reversed_list", - ) - }; - - let elem = builder.build_load(elem_ptr, "get_elem"); - - // Mutate the new array in-place to change the element. - builder.build_store(reverse_elem_ptr, elem); - - // #index != 0 - let end_cond = builder.build_int_compare( - IntPredicate::NE, - ctx.i64_type().const_int(0, false), - curr_index, - "loopcond", - ); - - let after_bb = ctx.append_basic_block(parent, "afterloop"); - - builder.build_conditional_branch(end_cond, loop_bb, after_bb); - builder.position_at_end(after_bb); - - let ptr_bytes = env.ptr_bytes; - let int_type = ptr_int(ctx, ptr_bytes); - let ptr_as_int = - builder.build_ptr_to_int(reversed_list_ptr, int_type, "list_cast_ptr"); - let struct_type = collection(ctx, ptr_bytes); - - let mut struct_val; - - // Store the pointer - struct_val = builder - .build_insert_value( - struct_type.get_undef(), - ptr_as_int, - Builtin::WRAPPER_PTR, - "insert_ptr", - ) - .unwrap(); - - // Store the length - struct_val = builder - .build_insert_value( - struct_val, - list_len, - Builtin::WRAPPER_LEN, - "insert_len", - ) - .unwrap(); - - builder.build_bitcast( - struct_val.into_struct_value(), - collection(ctx, ptr_bytes), - "cast_collection", - ) - } - Layout::Builtin(Builtin::EmptyList) => empty_list(env), - _ => { - unreachable!("Invalid List layout for List.get: {:?}", list_layout); - } - } - } - Symbol::LIST_APPEND => { - // List.append : List elem, List elem -> List elem - debug_assert_eq!(args.len(), 2); - - let (first_list, first_list_layout) = &args[0]; - let (second_list, second_list_layout) = &args[1]; - - let builder = env.builder; - - let ctx = env.context; - - let first_wrapper_struct = first_list.into_struct_value(); - let second_wrapper_struct = second_list.into_struct_value(); - - let first_list_len = load_list_len(builder, first_wrapper_struct); - let second_list_len = load_list_len(builder, second_wrapper_struct); - - match first_list_layout { - Layout::Builtin(Builtin::List(elem_layout)) => match second_list_layout { - Layout::Builtin(Builtin::List(_)) => {} - - Layout::Builtin(Builtin::EmptyList) => { - let elem_type = - basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); - - let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); - - clone_nonempty_list( - env, - first_list_len, - load_list_ptr(builder, first_wrapper_struct, ptr_type), - elem_layout, - ) - } - - _ => { - unreachable!("Invalid List layout for List.get: {:?}", second_list_layout); - } - }, - - Layout::Builtin(Builtin::EmptyList) => match second_list_layout { - Layout::Builtin(Builtin::List(elem_layout)) => { - let elem_type = - basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); - - let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); - - clone_nonempty_list( - env, - second_list_len, - load_list_ptr(builder, second_wrapper_struct, ptr_type), - elem_layout, - ) - } - Layout::Builtin(Builtin::EmptyList) => empty_list(env), - _ => { - unreachable!("Invalid List layout for List.get: {:?}", second_list_layout); - } - }, - _ => { - unreachable!("Invalid List layout for List.get: {:?}", first_list_layout); - } - } - } - Symbol::INT_DIV_UNSAFE => { - debug_assert!(args.len() == 2); - - let int_val = env.builder.build_int_signed_div( - args[0].0.into_int_value(), - args[1].0.into_int_value(), - "div_i64", - ); - - BasicValueEnum::IntValue(int_val) - } - _ => { - let fn_name = layout_ids - .get(symbol, layout) - .to_symbol_string(symbol, &env.interns); - let fn_val = env - .module - .get_function(fn_name.as_str()) - .unwrap_or_else(|| panic!("Unrecognized function: {:?}", symbol)); - - let mut arg_vals: Vec = Vec::with_capacity_in(args.len(), env.arena); - - for (arg, _layout) in args.iter() { - arg_vals.push(*arg); - } - - let call = env - .builder - .build_call(fn_val, arg_vals.into_bump_slice(), "call"); - - call.set_call_convention(fn_val.get_call_conventions()); - - call.try_as_basic_value() - .left() - .unwrap_or_else(|| panic!("LLVM error: Invalid call by name for name {:?}", symbol)) - } + for (arg, _layout) in args.iter() { + arg_vals.push(*arg); } + + let call = env + .builder + .build_call(fn_val, arg_vals.into_bump_slice(), "call"); + + call.set_call_convention(fn_val.get_call_conventions()); + + call.try_as_basic_value() + .left() + .unwrap_or_else(|| panic!("LLVM error: Invalid call by name for name {:?}", symbol)) } fn call_intrinsic<'a, 'ctx, 'env>( @@ -1861,7 +1351,7 @@ fn call_intrinsic<'a, 'ctx, 'env>( }) } -fn load_list_len<'ctx>( +pub fn load_list_len<'ctx>( builder: &Builder<'ctx>, wrapper_struct: StructValue<'ctx>, ) -> IntValue<'ctx> { @@ -1871,6 +1361,19 @@ fn load_list_len<'ctx>( .into_int_value() } +fn list_is_not_empty<'ctx>( + builder: &Builder<'ctx>, + ctx: &'ctx Context, + list_len: IntValue<'ctx>, +) -> IntValue<'ctx> { + builder.build_int_compare( + IntPredicate::UGT, + list_len, + ctx.i64_type().const_int(0, false), + "greaterthanzero", + ) +} + fn load_list_ptr<'ctx>( builder: &Builder<'ctx>, wrapper_struct: StructValue<'ctx>, @@ -1980,22 +1483,18 @@ fn bounds_check_comparison<'ctx>( builder.build_int_compare(IntPredicate::ULT, elem_index, len, "bounds_check") } +/// List.push List elem, elem -> List elem fn list_push<'a, 'ctx, 'env>( - args: &[(BasicValueEnum<'ctx>, &'a Layout<'a>)], env: &Env<'a, 'ctx, 'env>, + original_wrapper: StructValue<'ctx>, + elem: BasicValueEnum<'ctx>, + elem_layout: &Layout<'a>, ) -> BasicValueEnum<'ctx> { - // List.push List elem, elem -> List elem let builder = env.builder; let ctx = env.context; - debug_assert!(args.len() == 2); - - let original_wrapper = args[0].0.into_struct_value(); - // Load the usize length from the wrapper. let list_len = load_list_len(builder, original_wrapper); - - let (elem, elem_layout) = args[1]; let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); @@ -2083,7 +1582,7 @@ fn list_set<'a, 'ctx, 'env>( // List.set : List elem, Int, elem -> List elem let builder = env.builder; - debug_assert!(args.len() == 3); + debug_assert_eq!(args.len(), 3); let original_wrapper = args[0].0.into_struct_value(); let elem_index = args[1].0.into_int_value(); @@ -2155,3 +1654,676 @@ pub fn get_call_conventions(cc: CallingConvention) -> u32 { /// Source: https://llvm.org/doxygen/namespacellvm_1_1CallingConv.html pub static C_CALL_CONV: u32 = 0; pub static COLD_CALL_CONV: u32 = 9; + +fn run_low_level<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout_ids: &mut LayoutIds<'a>, + scope: &Scope<'a, 'ctx>, + parent: FunctionValue<'ctx>, + op: LowLevel, + args: &[(Expr<'a>, Layout<'a>)], +) -> BasicValueEnum<'ctx> { + use LowLevel::*; + + match op { + ListLen => { + // List.len : List * -> Int + debug_assert_eq!(args.len(), 1); + + let arg = build_expr(env, layout_ids, scope, parent, &args[0].0); + + load_list_len(env.builder, arg.into_struct_value()).into() + } + ListSingle => { + // List.single : a -> List a + debug_assert_eq!(args.len(), 1); + + let arg = build_expr(env, layout_ids, scope, parent, &args[0].0); + + list_single(env, arg, &args[0].1) + } + ListRepeat => { + // List.repeat : Int, elem -> List elem + debug_assert_eq!(args.len(), 2); + + let list_len = build_expr(env, layout_ids, scope, parent, &args[0].0).into_int_value(); + let elem = build_expr(env, layout_ids, scope, parent, &args[1].0); + let elem_layout = &args[1].1; + + list_repeat(env, parent, list_len, elem, elem_layout) + } + ListReverse => { + // List.reverse : List elem -> List elem + debug_assert_eq!(args.len(), 1); + + let (list, list_layout) = &args[0]; + + let wrapper_struct = + build_expr(env, layout_ids, scope, parent, list).into_struct_value(); + + let builder = env.builder; + let ctx = env.context; + + let list_len = load_list_len(builder, wrapper_struct); + + // list_len > 0 + // We do this check to avoid allocating memory. If the input + // list is empty, then we can just return an empty list. + let comparison = builder.build_int_compare( + IntPredicate::UGT, + list_len, + ctx.i64_type().const_int(0, false), + "greaterthanzero", + ); + + let build_then = || { + match list_layout { + Layout::Builtin(Builtin::List(elem_layout)) => { + // Allocate space for the new array that we'll copy into. + let elem_type = + basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); + + let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); + + let reversed_list_ptr = env + .builder + .build_array_malloc(elem_type, list_len, "create_reversed_list_ptr") + .unwrap(); + + // TODO check if malloc returned null; if so, runtime error for OOM! + + let index_name = "#index"; + let start_alloca = builder.build_alloca(ctx.i64_type(), index_name); + + // Start at the last element in the list. + let last_elem_index = builder.build_int_sub( + list_len, + ctx.i64_type().const_int(1, false), + "lastelemindex", + ); + builder.build_store(start_alloca, last_elem_index); + + let loop_bb = ctx.append_basic_block(parent, "loop"); + builder.build_unconditional_branch(loop_bb); + builder.position_at_end(loop_bb); + + // #index = #index - 1 + let curr_index = builder + .build_load(start_alloca, index_name) + .into_int_value(); + let next_index = builder.build_int_sub( + curr_index, + ctx.i64_type().const_int(1, false), + "nextindex", + ); + + builder.build_store(start_alloca, next_index); + + let list_ptr = load_list_ptr(builder, wrapper_struct, ptr_type); + + // The pointer to the element in the input list + let elem_ptr = unsafe { + builder.build_in_bounds_gep(list_ptr, &[curr_index], "load_index") + }; + + // The pointer to the element in the reversed list + let reverse_elem_ptr = unsafe { + builder.build_in_bounds_gep( + reversed_list_ptr, + &[builder.build_int_sub( + list_len, + builder.build_int_add( + curr_index, + ctx.i64_type().const_int(1, false), + "curr_index_plus_one", + ), + "next_index", + )], + "load_index_reversed_list", + ) + }; + + let elem = builder.build_load(elem_ptr, "get_elem"); + + // Mutate the new array in-place to change the element. + builder.build_store(reverse_elem_ptr, elem); + + // #index != 0 + let end_cond = builder.build_int_compare( + IntPredicate::NE, + ctx.i64_type().const_int(0, false), + curr_index, + "loopcond", + ); + + let after_bb = ctx.append_basic_block(parent, "afterloop"); + + builder.build_conditional_branch(end_cond, loop_bb, after_bb); + builder.position_at_end(after_bb); + + let ptr_bytes = env.ptr_bytes; + let int_type = ptr_int(ctx, ptr_bytes); + let ptr_as_int = + builder.build_ptr_to_int(reversed_list_ptr, int_type, "list_cast_ptr"); + let struct_type = collection(ctx, ptr_bytes); + + let mut struct_val; + + // Store the pointer + struct_val = builder + .build_insert_value( + struct_type.get_undef(), + ptr_as_int, + Builtin::WRAPPER_PTR, + "insert_ptr", + ) + .unwrap(); + + // Store the length + struct_val = builder + .build_insert_value( + struct_val, + list_len, + Builtin::WRAPPER_LEN, + "insert_len", + ) + .unwrap(); + + builder.build_bitcast( + struct_val.into_struct_value(), + collection(ctx, ptr_bytes), + "cast_collection", + ) + } + Layout::Builtin(Builtin::EmptyList) => empty_list(env), + _ => { + unreachable!("Invalid List layout for List.get: {:?}", list_layout); + } + } + }; + + let build_else = || empty_list(env); + + let struct_type = collection(ctx, env.ptr_bytes); + + build_basic_phi2( + env, + parent, + comparison, + build_then, + build_else, + BasicTypeEnum::StructType(struct_type), + ) + } + ListAppend => { + // List.append : List elem, List elem -> List elem + debug_assert_eq!(args.len(), 2); + + let (first_list, first_list_layout) = &args[0]; + + match first_list_layout { + Layout::Builtin(Builtin::EmptyList) => { + // In practice, this code cannot be reached, because if a list + // did have the layout of `EmptyList`, then it would not make + // it here. But this code is still run, in the sense that this + // LLVM is still built this case. + empty_list(env) + } + Layout::Builtin(Builtin::List(elem_layout)) => { + let first_list_wrapper_struct = + build_expr(env, layout_ids, scope, parent, first_list).into_struct_value(); + + let builder = env.builder; + let ctx = env.context; + + let first_list_len = load_list_len(builder, first_list_wrapper_struct); + + // first_list_len > 0 + // We do this check to avoid allocating memory. If the first input + // list is empty, then we can just return the second list cloned + let first_list_empty_comparison = + list_is_not_empty(builder, ctx, first_list_len); + + let build_first_list_non_empty_then = || { + let (second_list, second_list_layout) = &args[1]; + let second_list_wrapper_struct = + build_expr(env, layout_ids, scope, parent, second_list) + .into_struct_value(); + + let second_list_len = load_list_len(builder, second_list_wrapper_struct); + + match second_list_layout { + Layout::Builtin(Builtin::EmptyList) => { + let elem_type = basic_type_from_layout( + env.arena, + ctx, + elem_layout, + env.ptr_bytes, + ); + let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); + + let (new_wrapper, _) = clone_nonempty_list( + env, + first_list_len, + load_list_ptr(builder, first_list_wrapper_struct, ptr_type), + elem_layout, + ); + + BasicValueEnum::StructValue(new_wrapper) + } + Layout::Builtin(Builtin::List(_)) => { + // second_list_len > 0 + // We do this check to avoid allocating memory. If the second input + // list is empty, then we can just return the first list cloned + let second_list_empty_comparison = + list_is_not_empty(builder, ctx, second_list_len); + + let build_second_list_non_empty_then = || {}; + let elem_type = basic_type_from_layout( + env.arena, + ctx, + elem_layout, + env.ptr_bytes, + ); + + empty_list(env) + } + _ => { + unreachable!( + "Invalid List layout for List.get: {:?}", + first_list_layout + ); + } + } + }; + + let struct_type = collection(ctx, env.ptr_bytes); + + let build_first_list_non_empty_else = || empty_list(env); + + build_basic_phi2( + env, + parent, + first_list_empty_comparison, + build_first_list_non_empty_then, + build_first_list_non_empty_else, + BasicTypeEnum::StructType(struct_type), + ) + } + _ => { + unreachable!("Invalid List layout for List.get: {:?}", first_list_layout); + } + } + } + ListPush => { + // List.push List elem, elem -> List elem + debug_assert_eq!(args.len(), 2); + + let original_wrapper = + build_expr(env, layout_ids, scope, parent, &args[0].0).into_struct_value(); + let elem = build_expr(env, layout_ids, scope, parent, &args[1].0); + let elem_layout = &args[1].1; + + list_push(env, original_wrapper, elem, elem_layout) + } + NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumSin | NumCos | NumToFloat => { + debug_assert_eq!(args.len(), 1); + + let arg = build_expr(env, layout_ids, scope, parent, &args[0].0); + let arg_layout = &args[0].1; + + match arg_layout { + Layout::Builtin(arg_builtin) => { + use roc_mono::layout::Builtin::*; + + match arg_builtin { + Int128 | Int64 | Int32 | Int16 | Int8 => { + build_int_unary_op(env, arg.into_int_value(), arg_layout, op) + } + Float128 | Float64 | Float32 | Float16 => { + build_float_unary_op(env, arg.into_float_value(), arg_layout, op) + } + _ => { + unreachable!("Compiler bug: tried to run numeric operation {:?} on invalid builtin layout: ({:?})", op, arg_layout); + } + } + } + _ => { + unreachable!( + "Compiler bug: tried to run numeric operation {:?} on invalid layout: {:?}", + op, arg_layout + ); + } + } + } + NumAdd | NumSub | NumMul | NumLt | NumLte | NumGt | NumGte | NumRemUnchecked + | NumDivUnchecked => { + debug_assert_eq!(args.len(), 2); + + let lhs_arg = build_expr(env, layout_ids, scope, parent, &args[0].0); + let lhs_layout = &args[0].1; + let rhs_arg = build_expr(env, layout_ids, scope, parent, &args[1].0); + let rhs_layout = &args[1].1; + + match (lhs_layout, rhs_layout) { + (Layout::Builtin(lhs_builtin), Layout::Builtin(rhs_builtin)) + if lhs_builtin == rhs_builtin => + { + use roc_mono::layout::Builtin::*; + + match lhs_builtin { + Int128 | Int64 | Int32 | Int16 | Int8 => build_int_binop( + env, + lhs_arg.into_int_value(), + lhs_layout, + rhs_arg.into_int_value(), + rhs_layout, + op, + ), + Float128 | Float64 | Float32 | Float16 => build_float_binop( + env, + lhs_arg.into_float_value(), + lhs_layout, + rhs_arg.into_float_value(), + rhs_layout, + op, + ), + _ => { + unreachable!("Compiler bug: tried to run numeric operation {:?} on invalid builtin layout: ({:?})", op, lhs_layout); + } + } + } + _ => { + unreachable!("Compiler bug: tried to run numeric operation {:?} on invalid layouts. The 2 layouts were: ({:?}) and ({:?})", op, lhs_layout, rhs_layout); + } + } + } + Eq => { + debug_assert_eq!(args.len(), 2); + + let lhs_arg = build_expr(env, layout_ids, scope, parent, &args[0].0); + let lhs_layout = &args[0].1; + let rhs_arg = build_expr(env, layout_ids, scope, parent, &args[1].0); + let rhs_layout = &args[1].1; + + build_eq(env, lhs_arg, rhs_arg, lhs_layout, rhs_layout) + } + NotEq => { + debug_assert_eq!(args.len(), 2); + + let lhs_arg = build_expr(env, layout_ids, scope, parent, &args[0].0); + let lhs_layout = &args[0].1; + let rhs_arg = build_expr(env, layout_ids, scope, parent, &args[1].0); + let rhs_layout = &args[1].1; + + build_neq(env, lhs_arg, rhs_arg, lhs_layout, rhs_layout) + } + And => { + // The (&&) operator + debug_assert_eq!(args.len(), 2); + + let lhs_arg = build_expr(env, layout_ids, scope, parent, &args[0].0); + let rhs_arg = build_expr(env, layout_ids, scope, parent, &args[1].0); + let bool_val = env.builder.build_and( + lhs_arg.into_int_value(), + rhs_arg.into_int_value(), + "bool_and", + ); + + BasicValueEnum::IntValue(bool_val) + } + Or => { + // The (||) operator + debug_assert_eq!(args.len(), 2); + + let lhs_arg = build_expr(env, layout_ids, scope, parent, &args[0].0); + let rhs_arg = build_expr(env, layout_ids, scope, parent, &args[1].0); + let bool_val = env.builder.build_or( + lhs_arg.into_int_value(), + rhs_arg.into_int_value(), + "bool_or", + ); + + BasicValueEnum::IntValue(bool_val) + } + Not => { + // The (!) operator + debug_assert_eq!(args.len(), 1); + + let arg = build_expr(env, layout_ids, scope, parent, &args[0].0); + let bool_val = env.builder.build_not(arg.into_int_value(), "bool_not"); + + BasicValueEnum::IntValue(bool_val) + } + ListGetUnsafe => { + // List.get : List elem, Int -> [ Ok elem, OutOfBounds ]* + debug_assert_eq!(args.len(), 2); + + let builder = env.builder; + let (_, list_layout) = &args[0]; + let wrapper_struct = + build_expr(env, layout_ids, scope, parent, &args[0].0).into_struct_value(); + let elem_index = + build_expr(env, layout_ids, scope, parent, &args[1].0).into_int_value(); + + match list_layout { + Layout::Builtin(Builtin::List(elem_layout)) => { + let ctx = env.context; + let elem_type = + basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); + let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); + // Load the pointer to the array data + let array_data_ptr = load_list_ptr(builder, wrapper_struct, ptr_type); + + // Assume the bounds have already been checked earlier + // (e.g. by List.get or List.first, which wrap List.#getUnsafe) + let elem_ptr = unsafe { + builder.build_in_bounds_gep(array_data_ptr, &[elem_index], "elem") + }; + + builder.build_load(elem_ptr, "List.get") + } + _ => { + unreachable!( + "Invalid List layout for ListGetUnsafe operation: {:?}", + list_layout + ); + } + } + } + ListSet => list_set( + parent, + &[ + ( + build_expr(env, layout_ids, scope, parent, &args[0].0), + &args[0].1, + ), + ( + build_expr(env, layout_ids, scope, parent, &args[1].0), + &args[1].1, + ), + ( + build_expr(env, layout_ids, scope, parent, &args[2].0), + &args[2].1, + ), + ], + env, + InPlace::Clone, + ), + ListSetInPlace => list_set( + parent, + &[ + ( + build_expr(env, layout_ids, scope, parent, &args[0].0), + &args[0].1, + ), + ( + build_expr(env, layout_ids, scope, parent, &args[1].0), + &args[1].1, + ), + ( + build_expr(env, layout_ids, scope, parent, &args[2].0), + &args[2].1, + ), + ], + env, + InPlace::InPlace, + ), + } +} + +fn build_int_binop<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + lhs: IntValue<'ctx>, + _lhs_layout: &Layout<'a>, + rhs: IntValue<'ctx>, + _rhs_layout: &Layout<'a>, + op: LowLevel, +) -> BasicValueEnum<'ctx> { + use inkwell::IntPredicate::*; + use roc_module::low_level::LowLevel::*; + + let bd = env.builder; + + match op { + NumAdd => bd.build_int_add(lhs, rhs, "add_int").into(), + NumSub => bd.build_int_sub(lhs, rhs, "sub_int").into(), + NumMul => bd.build_int_mul(lhs, rhs, "mul_int").into(), + NumGt => bd.build_int_compare(SGT, lhs, rhs, "int_gt").into(), + NumGte => bd.build_int_compare(SGE, lhs, rhs, "int_gte").into(), + NumLt => bd.build_int_compare(SLT, lhs, rhs, "int_lt").into(), + NumLte => bd.build_int_compare(SLE, lhs, rhs, "int_lte").into(), + NumRemUnchecked => bd.build_int_signed_rem(lhs, rhs, "rem_int").into(), + NumDivUnchecked => bd.build_int_signed_div(lhs, rhs, "div_int").into(), + _ => { + unreachable!("Unrecognized int binary operation: {:?}", op); + } + } +} + +fn build_float_binop<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + lhs: FloatValue<'ctx>, + _lhs_layout: &Layout<'a>, + rhs: FloatValue<'ctx>, + _rhs_layout: &Layout<'a>, + op: LowLevel, +) -> BasicValueEnum<'ctx> { + use inkwell::FloatPredicate::*; + use roc_module::low_level::LowLevel::*; + + let bd = env.builder; + + match op { + NumAdd => bd.build_float_add(lhs, rhs, "add_float").into(), + NumSub => bd.build_float_sub(lhs, rhs, "sub_float").into(), + NumMul => bd.build_float_mul(lhs, rhs, "mul_float").into(), + NumGt => bd.build_float_compare(OGT, lhs, rhs, "float_gt").into(), + NumGte => bd.build_float_compare(OGE, lhs, rhs, "float_gte").into(), + NumLt => bd.build_float_compare(OLT, lhs, rhs, "float_lt").into(), + NumLte => bd.build_float_compare(OLE, lhs, rhs, "float_lte").into(), + NumRemUnchecked => bd.build_float_rem(lhs, rhs, "rem_float").into(), + NumDivUnchecked => bd.build_float_div(lhs, rhs, "div_float").into(), + _ => { + unreachable!("Unrecognized int binary operation: {:?}", op); + } + } +} + +fn build_int_unary_op<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + arg: IntValue<'ctx>, + arg_layout: &Layout<'a>, + op: LowLevel, +) -> BasicValueEnum<'ctx> { + use roc_module::low_level::LowLevel::*; + + let bd = env.builder; + + match op { + NumNeg => bd.build_int_neg(arg, "negate_int").into(), + NumAbs => { + // This is how libc's abs() is implemented - it uses no branching! + // + // abs = \arg -> + // shifted = arg >>> 63 + // + // (xor arg shifted) - shifted + + let ctx = env.context; + let shifted_name = "abs_shift_right"; + let shifted_alloca = { + let bits_to_shift = ((arg_layout.stack_size(env.ptr_bytes) as u64) * 8) - 1; + let shift_val = ctx.i64_type().const_int(bits_to_shift, false); + let shifted = bd.build_right_shift(arg, shift_val, true, shifted_name); + let alloca = bd.build_alloca( + basic_type_from_layout(env.arena, ctx, arg_layout, env.ptr_bytes), + "#int_abs_help", + ); + + // shifted = arg >>> 63 + bd.build_store(alloca, shifted); + + alloca + }; + + let xored_arg = bd.build_xor( + arg, + bd.build_load(shifted_alloca, shifted_name).into_int_value(), + "xor_arg_shifted", + ); + + BasicValueEnum::IntValue(bd.build_int_sub( + xored_arg, + bd.build_load(shifted_alloca, shifted_name).into_int_value(), + "sub_xored_shifted", + )) + } + NumToFloat => { + // TODO specialize this to be not just for i64! + let builtin_fn_name = "i64_to_f64_"; + + let fn_val = env + .module + .get_function(builtin_fn_name) + .unwrap_or_else(|| panic!("Unrecognized builtin function: {:?} - if you're working on the Roc compiler, do you need to rebuild the bitcode? See compiler/builtins/bitcode/README.md", builtin_fn_name)); + + let call = env + .builder + .build_call(fn_val, &[arg.into()], "call_builtin"); + + call.set_call_convention(fn_val.get_call_conventions()); + + call.try_as_basic_value() + .left() + .unwrap_or_else(|| panic!("LLVM error: Invalid call for low-level op {:?}", op)) + } + _ => { + unreachable!("Unrecognized int unary operation: {:?}", op); + } + } +} + +fn build_float_unary_op<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + arg: FloatValue<'ctx>, + arg_layout: &Layout<'a>, + op: LowLevel, +) -> BasicValueEnum<'ctx> { + use roc_module::low_level::LowLevel::*; + + let bd = env.builder; + + match op { + NumNeg => bd.build_float_neg(arg, "negate_float").into(), + NumAbs => call_intrinsic(LLVM_FABS_F64, env, &[(arg.into(), arg_layout)]), + NumSqrtUnchecked => call_intrinsic(LLVM_SQRT_F64, env, &[(arg.into(), arg_layout)]), + NumRound => call_intrinsic(LLVM_LROUND_I64_F64, env, &[(arg.into(), arg_layout)]), + NumSin => call_intrinsic(LLVM_SIN_F64, env, &[(arg.into(), arg_layout)]), + NumCos => call_intrinsic(LLVM_COS_F64, env, &[(arg.into(), arg_layout)]), + NumToFloat => arg.into(), /* Converting from Float to Float is a no-op */ + _ => { + unreachable!("Unrecognized int unary operation: {:?}", op); + } + } +} diff --git a/compiler/gen/src/llvm/compare.rs b/compiler/gen/src/llvm/compare.rs new file mode 100644 index 0000000000..2b46b68f37 --- /dev/null +++ b/compiler/gen/src/llvm/compare.rs @@ -0,0 +1,112 @@ +use crate::llvm::build::Env; +use inkwell::values::BasicValueEnum; +use inkwell::{FloatPredicate, IntPredicate}; +use roc_mono::layout::{Builtin, Layout}; + +pub fn build_eq<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + lhs_val: BasicValueEnum<'ctx>, + rhs_val: BasicValueEnum<'ctx>, + lhs_layout: &Layout<'a>, + rhs_layout: &Layout<'a>, +) -> BasicValueEnum<'ctx> { + match (lhs_layout, rhs_layout) { + (Layout::Builtin(lhs_builtin), Layout::Builtin(rhs_builtin)) => { + let int_cmp = |pred, label| { + let int_val = env.builder.build_int_compare( + pred, + lhs_val.into_int_value(), + rhs_val.into_int_value(), + label, + ); + + BasicValueEnum::IntValue(int_val) + }; + + let float_cmp = |pred, label| { + let int_val = env.builder.build_float_compare( + pred, + lhs_val.into_float_value(), + rhs_val.into_float_value(), + label, + ); + + BasicValueEnum::IntValue(int_val) + }; + + match (lhs_builtin, rhs_builtin) { + (Builtin::Int128, Builtin::Int128) => int_cmp(IntPredicate::EQ, "eq_i128"), + (Builtin::Int64, Builtin::Int64) => int_cmp(IntPredicate::EQ, "eq_i64"), + (Builtin::Int32, Builtin::Int32) => int_cmp(IntPredicate::EQ, "eq_i32"), + (Builtin::Int16, Builtin::Int16) => int_cmp(IntPredicate::EQ, "eq_i16"), + (Builtin::Int8, Builtin::Int8) => int_cmp(IntPredicate::EQ, "eq_i8"), + (Builtin::Int1, Builtin::Int1) => int_cmp(IntPredicate::EQ, "eq_i1"), + (Builtin::Float64, Builtin::Float64) => float_cmp(FloatPredicate::OEQ, "eq_f64"), + (Builtin::Float32, Builtin::Float32) => float_cmp(FloatPredicate::OEQ, "eq_f32"), + (b1, b2) => { + todo!("Handle equals for builtin layouts {:?} == {:?}", b1, b2); + } + } + } + (other1, other2) => { + // TODO NOTE: This should ultimately have a _ => todo!("type mismatch!") branch + todo!("implement equals for layouts {:?} == {:?}", other1, other2); + } + } +} + +pub fn build_neq<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + lhs_val: BasicValueEnum<'ctx>, + rhs_val: BasicValueEnum<'ctx>, + lhs_layout: &Layout<'a>, + rhs_layout: &Layout<'a>, +) -> BasicValueEnum<'ctx> { + match (lhs_layout, rhs_layout) { + (Layout::Builtin(lhs_builtin), Layout::Builtin(rhs_builtin)) => { + let int_cmp = |pred, label| { + let int_val = env.builder.build_int_compare( + pred, + lhs_val.into_int_value(), + rhs_val.into_int_value(), + label, + ); + + BasicValueEnum::IntValue(int_val) + }; + + let float_cmp = |pred, label| { + let int_val = env.builder.build_float_compare( + pred, + lhs_val.into_float_value(), + rhs_val.into_float_value(), + label, + ); + + BasicValueEnum::IntValue(int_val) + }; + + match (lhs_builtin, rhs_builtin) { + (Builtin::Int128, Builtin::Int128) => int_cmp(IntPredicate::NE, "neq_i128"), + (Builtin::Int64, Builtin::Int64) => int_cmp(IntPredicate::NE, "neq_i64"), + (Builtin::Int32, Builtin::Int32) => int_cmp(IntPredicate::NE, "neq_i32"), + (Builtin::Int16, Builtin::Int16) => int_cmp(IntPredicate::NE, "neq_i16"), + (Builtin::Int8, Builtin::Int8) => int_cmp(IntPredicate::NE, "neq_i8"), + (Builtin::Int1, Builtin::Int1) => int_cmp(IntPredicate::NE, "neq_i1"), + (Builtin::Float64, Builtin::Float64) => float_cmp(FloatPredicate::ONE, "neq_f64"), + (Builtin::Float32, Builtin::Float32) => float_cmp(FloatPredicate::ONE, "neq_f32"), + (b1, b2) => { + todo!("Handle not equals for builtin layouts {:?} == {:?}", b1, b2); + } + } + } + (other1, other2) => { + // TODO NOTE: This should ultimately have a _ => todo!("type mismatch!") branch + todo!( + "implement not equals for layouts {:?} == {:?}", + other1, + other2 + ); + } + } +} diff --git a/compiler/gen/src/llvm/convert.rs b/compiler/gen/src/llvm/convert.rs index e9beac7620..5d6b91bdd6 100644 --- a/compiler/gen/src/llvm/convert.rs +++ b/compiler/gen/src/llvm/convert.rs @@ -121,10 +121,16 @@ pub fn basic_type_from_layout<'ctx>( } Builtin(builtin) => match builtin { + Int128 => context.i128_type().as_basic_type_enum(), Int64 => context.i64_type().as_basic_type_enum(), + Int32 => context.i32_type().as_basic_type_enum(), + Int16 => context.i16_type().as_basic_type_enum(), + Int8 => context.i8_type().as_basic_type_enum(), + Int1 => context.bool_type().as_basic_type_enum(), + Float128 => context.f128_type().as_basic_type_enum(), Float64 => context.f64_type().as_basic_type_enum(), - Bool => context.bool_type().as_basic_type_enum(), - Byte => context.i8_type().as_basic_type_enum(), + Float32 => context.f32_type().as_basic_type_enum(), + Float16 => context.f16_type().as_basic_type_enum(), Str | EmptyStr => context .i8_type() .ptr_type(AddressSpace::Generic) diff --git a/compiler/gen/src/llvm/mod.rs b/compiler/gen/src/llvm/mod.rs index 7a41ebfd10..df876b1179 100644 --- a/compiler/gen/src/llvm/mod.rs +++ b/compiler/gen/src/llvm/mod.rs @@ -1,2 +1,3 @@ pub mod build; +pub mod compare; pub mod convert; diff --git a/compiler/gen/test.asm b/compiler/gen/test.asm deleted file mode 100644 index 7c703ef496..0000000000 --- a/compiler/gen/test.asm +++ /dev/null @@ -1,4 +0,0 @@ - .text - .file "my_module" - - .section ".note.GNU-stack","",@progbits diff --git a/compiler/gen/tests/gen_builtins.rs b/compiler/gen/tests/gen_builtins.rs deleted file mode 100644 index 42bb2461c5..0000000000 --- a/compiler/gen/tests/gen_builtins.rs +++ /dev/null @@ -1,805 +0,0 @@ -#[macro_use] -extern crate pretty_assertions; -#[macro_use] -extern crate indoc; - -extern crate bumpalo; -extern crate inkwell; -extern crate libc; -extern crate roc_gen; - -#[macro_use] -mod helpers; - -#[cfg(test)] -mod gen_builtins { - use crate::helpers::{can_expr, infer_expr, uniq_expr, with_larger_debug_stack, CanExprOut}; - use bumpalo::Bump; - use inkwell::context::Context; - use inkwell::execution_engine::JitFunction; - use inkwell::passes::PassManager; - use inkwell::types::BasicType; - use inkwell::OptimizationLevel; - use roc_collections::all::ImMap; - use roc_gen::llvm::build::{build_proc, build_proc_header}; - use roc_gen::llvm::convert::basic_type_from_layout; - use roc_mono::expr::{Expr, Procs}; - use roc_mono::layout::Layout; - use roc_types::subs::Subs; - - #[test] - fn f64_sqrt() { - assert_evals_to!("Float.sqrt 144", 12.0, f64); - } - - #[test] - fn f64_round() { - assert_evals_to!("Float.round 3.6", 4, i64); - } - - #[test] - fn f64_abs() { - assert_evals_to!("Float.abs -4.7", 4.7, f64); - assert_evals_to!("Float.abs 5.8", 5.8, f64); - } - - #[test] - fn i64_abs() { - assert_evals_to!("Int.abs -6", 6, i64); - assert_evals_to!("Int.abs 7", 7, i64); - } - - #[test] - fn empty_list_literal() { - assert_evals_to!("[]", &[], &'static [i64]); - } - - #[test] - fn int_list_literal() { - assert_evals_to!("[ 12, 9, 6, 3 ]", &[12, 9, 6, 3], &'static [i64]); - } - - #[test] - fn gen_if_fn() { - assert_evals_to!( - indoc!( - r#" - limitedNegate = \num -> - if num == 1 then - -1 - else if num == -1 then - 1 - else - num - - limitedNegate 1 - "# - ), - -1, - i64 - ); - } - - #[test] - fn gen_float_eq() { - assert_evals_to!( - indoc!( - r#" - 1.0 == 1.0 - "# - ), - true, - bool - ); - } - - #[test] - fn gen_add_f64() { - with_larger_debug_stack(|| { - assert_evals_to!( - indoc!( - r#" - 1.1 + 2.4 + 3 - "# - ), - 6.5, - f64 - ); - }) - } - - #[test] - fn gen_div_f64() { - assert_evals_to!( - indoc!( - r#" - 48 / 2 - "# - ), - 24.0, - f64 - ); - } - - #[test] - fn gen_int_eq() { - assert_evals_to!( - indoc!( - r#" - 4 == 4 - "# - ), - true, - bool - ); - } - - #[test] - fn gen_int_neq() { - assert_evals_to!( - indoc!( - r#" - 4 != 5 - "# - ), - true, - bool - ); - } - - #[test] - fn gen_add_i64() { - with_larger_debug_stack(|| { - assert_evals_to!( - indoc!( - r#" - 1 + 2 + 3 - "# - ), - 6, - i64 - ); - }) - } - - #[test] - fn gen_sub_f64() { - assert_evals_to!( - indoc!( - r#" - 1.5 - 2.4 - 3 - "# - ), - -3.9, - f64 - ); - } - - #[test] - fn gen_sub_i64() { - assert_evals_to!( - indoc!( - r#" - 1 - 2 - 3 - "# - ), - -4, - i64 - ); - } - - #[test] - fn gen_mul_i64() { - assert_evals_to!( - indoc!( - r#" - 2 * 4 * 6 - "# - ), - 48, - i64 - ); - } - - #[test] - fn gen_div_i64() { - assert_evals_to!( - indoc!( - r#" - when 1000 // 10 is - Ok val -> val - Err _ -> -1 - "# - ), - 100, - i64 - ); - } - - #[test] - fn gen_div_by_zero_i64() { - assert_evals_to!( - indoc!( - r#" - when 1000 // 0 is - Err DivByZero -> 99 - _ -> -24 - "# - ), - 99, - i64 - ); - } - - #[test] - fn gen_rem_i64() { - assert_evals_to!( - indoc!( - r#" - when Int.rem 8 3 is - Ok val -> val - _ -> -1 - "# - ), - 2, - i64 - ); - } - - #[test] - fn gen_rem_div_by_zero_i64() { - assert_evals_to!( - indoc!( - r#" - when Int.rem 8 0 is - Err DivByZero -> 4 - _ -> -23 - "# - ), - 4, - i64 - ); - } - - #[test] - fn gen_is_zero_i64() { - assert_evals_to!("Int.isZero 0", true, bool); - assert_evals_to!("Int.isZero 1", false, bool); - } - - #[test] - fn gen_is_positive_i64() { - assert_evals_to!("Int.isPositive 0", false, bool); - assert_evals_to!("Int.isPositive 1", true, bool); - assert_evals_to!("Int.isPositive -5", false, bool); - } - - #[test] - fn gen_is_negative_i64() { - assert_evals_to!("Int.isNegative 0", false, bool); - assert_evals_to!("Int.isNegative 3", false, bool); - assert_evals_to!("Int.isNegative -2", true, bool); - } - - #[test] - fn gen_is_positive_f64() { - assert_evals_to!("Float.isPositive 0.0", false, bool); - assert_evals_to!("Float.isPositive 4.7", true, bool); - assert_evals_to!("Float.isPositive -8.5", false, bool); - } - - #[test] - fn gen_is_negative_f64() { - assert_evals_to!("Float.isNegative 0.0", false, bool); - assert_evals_to!("Float.isNegative 9.9", false, bool); - assert_evals_to!("Float.isNegative -4.4", true, bool); - } - - #[test] - fn gen_is_zero_f64() { - assert_evals_to!("Float.isZero 0", true, bool); - assert_evals_to!("Float.isZero 0_0", true, bool); - assert_evals_to!("Float.isZero 0.0", true, bool); - assert_evals_to!("Float.isZero 1", false, bool); - } - - #[test] - fn gen_is_odd() { - assert_evals_to!("Int.isOdd 4", false, bool); - assert_evals_to!("Int.isOdd 5", true, bool); - } - - #[test] - fn gen_is_even() { - assert_evals_to!("Int.isEven 6", true, bool); - assert_evals_to!("Int.isEven 7", false, bool); - } - - #[test] - fn sin() { - assert_evals_to!("Float.sin 0", 0.0, f64); - assert_evals_to!("Float.sin 1.41421356237", 0.9877659459922529, f64); - } - - #[test] - fn cos() { - assert_evals_to!("Float.cos 0", 1.0, f64); - assert_evals_to!("Float.cos 3.14159265359", -1.0, f64); - } - - #[test] - fn tan() { - assert_evals_to!("Float.tan 0", 0.0, f64); - assert_evals_to!("Float.tan 1", 1.557407724654902, f64); - } - - #[test] - fn lt_i64() { - assert_evals_to!("1 < 2", true, bool); - assert_evals_to!("1 < 1", false, bool); - assert_evals_to!("2 < 1", false, bool); - assert_evals_to!("0 < 0", false, bool); - } - - #[test] - fn lte_i64() { - assert_evals_to!("1 <= 1", true, bool); - assert_evals_to!("2 <= 1", false, bool); - assert_evals_to!("1 <= 2", true, bool); - assert_evals_to!("0 <= 0", true, bool); - } - - #[test] - fn gt_i64() { - assert_evals_to!("2 > 1", true, bool); - assert_evals_to!("2 > 2", false, bool); - assert_evals_to!("1 > 1", false, bool); - assert_evals_to!("0 > 0", false, bool); - } - - #[test] - fn gte_i64() { - assert_evals_to!("1 >= 1", true, bool); - assert_evals_to!("1 >= 2", false, bool); - assert_evals_to!("2 >= 1", true, bool); - assert_evals_to!("0 >= 0", true, bool); - } - - #[test] - fn lt_f64() { - assert_evals_to!("1.1 < 1.2", true, bool); - assert_evals_to!("1.1 < 1.1", false, bool); - assert_evals_to!("1.2 < 1.1", false, bool); - assert_evals_to!("0.0 < 0.0", false, bool); - } - - #[test] - fn lte_f64() { - assert_evals_to!("1.1 <= 1.1", true, bool); - assert_evals_to!("1.2 <= 1.1", false, bool); - assert_evals_to!("1.1 <= 1.2", true, bool); - assert_evals_to!("0.0 <= 0.0", true, bool); - } - - #[test] - fn gt_f64() { - assert_evals_to!("2.2 > 1.1", true, bool); - assert_evals_to!("2.2 > 2.2", false, bool); - assert_evals_to!("1.1 > 2.2", false, bool); - assert_evals_to!("0.0 > 0.0", false, bool); - } - - #[test] - fn gte_f64() { - assert_evals_to!("1.1 >= 1.1", true, bool); - assert_evals_to!("1.1 >= 1.2", false, bool); - assert_evals_to!("1.2 >= 1.1", true, bool); - assert_evals_to!("0.0 >= 0.0", true, bool); - } - - #[test] - fn gen_order_of_arithmetic_ops() { - assert_evals_to!( - indoc!( - r#" - 1 + 3 * 7 - 2 - "# - ), - 20, - i64 - ); - } - - #[test] - fn gen_order_of_arithmetic_ops_complex_float() { - assert_evals_to!( - indoc!( - r#" - 48 / 2 + 3 - "# - ), - 27.0, - f64 - ); - } - - #[test] - fn if_guard_bind_variable() { - assert_evals_to!( - indoc!( - r#" - when 10 is - x if x == 5 -> 0 - _ -> 42 - "# - ), - 42, - i64 - ); - - assert_evals_to!( - indoc!( - r#" - when 10 is - x if x == 10 -> 42 - _ -> 0 - "# - ), - 42, - i64 - ); - } - #[test] - fn tail_call_elimination() { - with_larger_debug_stack(|| { - assert_evals_to!( - indoc!( - r#" - sum = \n, accum -> - when n is - 0 -> accum - _ -> sum (n - 1) (n + accum) - - sum 1_000_000 0 - "# - ), - 500000500000, - i64 - ); - }) - } - #[test] - fn int_negate() { - assert_evals_to!("Num.neg 123", -123, i64); - } - - #[test] - fn gen_basic_fn() { - assert_evals_to!( - indoc!( - r#" - always42 : Num.Num Int.Integer -> Num.Num Int.Integer - always42 = \num -> 42 - - always42 5 - "# - ), - 42, - i64 - ); - } - - #[test] - fn list_push() { - assert_evals_to!("List.push [1] 2", &[1, 2], &'static [i64]); - assert_evals_to!("List.push [1, 1] 2", &[1, 1, 2], &'static [i64]); - assert_evals_to!("List.push [] 3", &[3], &'static [i64]); - assert_evals_to!( - "List.push [ True, False ] True", - &[true, false, true], - &'static [bool] - ); - } - - #[test] - fn list_single() { - assert_evals_to!("List.single 1", &[1], &'static [i64]); - assert_evals_to!("List.single 5.6", &[5.6], &'static [f64]); - } - - #[test] - fn list_repeat() { - assert_evals_to!("List.repeat 5 1", &[1, 1, 1, 1, 1], &'static [i64]); - assert_evals_to!("List.repeat 4 2", &[2, 2, 2, 2], &'static [i64]); - - assert_evals_to!("List.repeat 2 []", &[&[], &[]], &'static [&'static [i64]]); - } - - #[test] - fn list_reverse() { - assert_evals_to!("List.reverse [1, 2, 3]", &[3, 2, 1], &'static [i64]); - assert_evals_to!("List.reverse [4]", &[4], &'static [i64]); - assert_evals_to!("List.reverse []", &[], &'static [i64]); - } - - #[test] - fn empty_list_len() { - with_larger_debug_stack(|| { - assert_evals_to!("List.len []", 0, usize); - }) - } - - #[test] - fn basic_int_list_len() { - with_larger_debug_stack(|| { - assert_evals_to!("List.len [ 12, 9, 6, 3 ]", 4, usize); - }) - } - - #[test] - fn loaded_int_list_len() { - assert_evals_to!( - indoc!( - r#" - nums = [ 2, 4, 6 ] - - List.len nums - "# - ), - 3, - usize - ); - } - - #[test] - fn fn_int_list_len() { - assert_evals_to!( - indoc!( - r#" - getLen = \list -> List.len list - - nums = [ 2, 4, 6, 8 ] - - getLen nums - "# - ), - 4, - usize - ); - } - - #[test] - fn int_list_is_empty() { - assert_evals_to!("List.isEmpty [ 12, 9, 6, 3 ]", false, bool); - } - - #[test] - fn empty_list_is_empty() { - with_larger_debug_stack(|| { - assert_evals_to!("List.isEmpty []", true, bool); - }) - } - - #[test] - fn first_int_list() { - with_larger_debug_stack(|| { - assert_evals_to!( - indoc!( - r#" - when List.first [ 12, 9, 6, 3 ] is - Ok val -> val - Err _ -> -1 - "# - ), - 12, - i64 - ); - }) - } - - #[test] - fn first_empty_list() { - with_larger_debug_stack(|| { - assert_evals_to!( - indoc!( - r#" - when List.first [] is - Ok val -> val - Err _ -> -1 - "# - ), - -1, - i64 - ); - }) - } - - #[test] - fn get_int_list_ok() { - assert_evals_to!( - indoc!( - r#" - when List.get [ 12, 9, 6 ] 1 is - Ok val -> val - Err _ -> -1 - "# - ), - 9, - i64 - ); - } - - #[test] - fn get_int_list_oob() { - assert_evals_to!( - indoc!( - r#" - when List.get [ 12, 9, 6 ] 1000 is - Ok val -> val - Err _ -> -1 - "# - ), - -1, - i64 - ); - } - - #[test] - fn get_set_unique_int_list() { - assert_evals_to!("List.getUnsafe (List.set [ 12, 9, 7, 3 ] 1 42) 1", 42, i64); - } - - #[test] - fn set_unique_int_list() { - assert_evals_to!( - "List.set [ 12, 9, 7, 1, 5 ] 2 33", - &[12, 9, 33, 1, 5], - &'static [i64] - ); - } - - #[test] - fn set_unique_list_oob() { - assert_evals_to!( - "List.set [ 3, 17, 4.1 ] 1337 9.25", - &[3.0, 17.0, 4.1], - &'static [f64] - ); - } - - #[test] - fn set_shared_int_list() { - assert_evals_to!( - indoc!( - r#" - shared = [ 2.1, 4.3 ] - - # This should not mutate the original - x = List.getUnsafe (List.set shared 1 7.7) 1 - - { x, y: List.getUnsafe shared 1 } - "# - ), - (7.7, 4.3), - (f64, f64) - ); - } - - #[test] - fn set_shared_list_oob() { - assert_evals_to!( - indoc!( - r#" - shared = [ 2, 4 ] - - # This List.set is out of bounds, and should have no effect - x = List.getUnsafe (List.set shared 422 0) 1 - - { x, y: List.getUnsafe shared 1 } - "# - ), - (4, 4), - (i64, i64) - ); - } - - #[test] - fn get_unique_int_list() { - assert_evals_to!( - indoc!( - r#" - shared = [ 2, 4 ] - - List.getUnsafe shared 1 - "# - ), - 4, - i64 - ); - } - - #[test] - fn int_to_float() { - assert_evals_to!( - indoc!( - r#" - Num.toFloat 0x9 - "# - ), - 9.0, - f64 - ); - } - - #[test] - fn gen_quicksort() { - with_larger_debug_stack(|| { - assert_evals_to!( - indoc!( - r#" - quicksort : List (Num a) -> List (Num a) - quicksort = \list -> - quicksortHelp list 0 (List.len list - 1) - - - quicksortHelp : List (Num a), Int, Int -> List (Num a) - quicksortHelp = \list, low, high -> - if low < high then - when partition low high list is - Pair partitionIndex partitioned -> - partitioned - |> quicksortHelp low (partitionIndex - 1) - |> quicksortHelp (partitionIndex + 1) high - else - list - - - swap : Int, Int, List a -> List a - swap = \i, j, list -> - when Pair (List.get list i) (List.get list j) is - Pair (Ok atI) (Ok atJ) -> - list - |> List.set i atJ - |> List.set j atI - - _ -> - [] - - partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ] - partition = \low, high, initialList -> - when List.get initialList high is - Ok pivot -> - when partitionHelp (low - 1) low initialList high pivot is - Pair newI newList -> - Pair (newI + 1) (swap (newI + 1) high newList) - - Err _ -> - Pair (low - 1) initialList - - - partitionHelp : Int, Int, List (Num a), Int, Int -> [ Pair Int (List (Num a)) ] - partitionHelp = \i, j, list, high, pivot -> - if j < high then - when List.get list j is - Ok value -> - if value <= pivot then - partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) high pivot - else - partitionHelp i (j + 1) list high pivot - - Err _ -> - Pair i list - else - Pair i list - - - - quicksort [ 7, 4, 21, 19 ] - "# - ), - &[4, 7, 19, 21], - &'static [i64] - ); - }) - } -} diff --git a/compiler/gen/tests/gen_list.rs b/compiler/gen/tests/gen_list.rs new file mode 100644 index 0000000000..e26efa2f9e --- /dev/null +++ b/compiler/gen/tests/gen_list.rs @@ -0,0 +1,465 @@ +#[macro_use] +extern crate pretty_assertions; +#[macro_use] +extern crate indoc; + +extern crate bumpalo; +extern crate inkwell; +extern crate libc; +extern crate roc_gen; + +#[macro_use] +mod helpers; + +#[cfg(test)] +mod gen_list { + use crate::helpers::{can_expr, infer_expr, uniq_expr, with_larger_debug_stack, CanExprOut}; + use bumpalo::Bump; + use inkwell::context::Context; + use inkwell::execution_engine::JitFunction; + use inkwell::passes::PassManager; + use inkwell::types::BasicType; + use inkwell::OptimizationLevel; + use roc_collections::all::ImMap; + use roc_gen::llvm::build::{build_proc, build_proc_header}; + use roc_gen::llvm::convert::basic_type_from_layout; + use roc_mono::expr::{Expr, Procs}; + use roc_mono::layout::Layout; + use roc_types::subs::Subs; + + #[test] + fn empty_list_literal() { + assert_evals_to!("[]", &[], &'static [i64]); + } + + #[test] + fn int_list_literal() { + assert_evals_to!("[ 12, 9, 6, 3 ]", &[12, 9, 6, 3], &'static [i64]); + } + + #[test] + fn list_push() { + assert_evals_to!("List.push [1] 2", &[1, 2], &'static [i64]); + assert_evals_to!("List.push [1, 1] 2", &[1, 1, 2], &'static [i64]); + assert_evals_to!("List.push [] 3", &[3], &'static [i64]); + assert_evals_to!( + indoc!( + r#" + initThrees : List Int + initThrees = + [] + + List.push (List.push initThrees 3) 3 + "# + ), + &[3, 3], + &'static [i64] + ); + assert_evals_to!( + "List.push [ True, False ] True", + &[true, false, true], + &'static [bool] + ); + assert_evals_to!( + "List.push [ 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22 ] 23", + &[11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23], + &'static [i64] + ); + } + + #[test] + fn list_single() { + assert_evals_to!("List.single 1", &[1], &'static [i64]); + assert_evals_to!("List.single 5.6", &[5.6], &'static [f64]); + } + + #[test] + fn list_repeat() { + assert_evals_to!("List.repeat 5 1", &[1, 1, 1, 1, 1], &'static [i64]); + assert_evals_to!("List.repeat 4 2", &[2, 2, 2, 2], &'static [i64]); + + assert_evals_to!("List.repeat 2 []", &[&[], &[]], &'static [&'static [i64]]); + assert_evals_to!( + indoc!( + r#" + noStrs : List Str + noStrs = + [] + + List.repeat 2 noStrs + "# + ), + &[&[], &[]], + &'static [&'static [i64]] + ); + + assert_evals_to!( + "List.repeat 15 4", + &[4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4], + &'static [i64] + ); + } + + #[test] + fn list_reverse() { + assert_evals_to!( + "List.reverse [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ]", + &[12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1], + &'static [i64] + ); + assert_evals_to!("List.reverse [1, 2, 3]", &[3, 2, 1], &'static [i64]); + assert_evals_to!("List.reverse [4]", &[4], &'static [i64]); + assert_evals_to!( + indoc!( + r#" + emptyList : List Int + emptyList = + [] + + List.reverse emptyList + "# + ), + &[], + &'static [i64] + ); + assert_evals_to!("List.reverse []", &[], &'static [i64]); + } + + #[test] + fn list_append() { + assert_evals_to!("List.append [] []", &[], &'static [i64]); + assert_evals_to!("List.append [ 12, 13 ] []", &[12, 13], &'static [i64]); + + // assert_evals_to!( + // "List.append [ 1, 2 ] [ 3, 4 ]", + // &[1, 2, 3, 4], + // &'static [i64] + // ); + } + + #[test] + fn empty_list_len() { + assert_evals_to!("List.len []", 0, usize); + } + + #[test] + fn basic_int_list_len() { + assert_evals_to!("List.len [ 12, 9, 6, 3 ]", 4, usize); + } + + #[test] + fn loaded_int_list_len() { + assert_evals_to!( + indoc!( + r#" + nums = [ 2, 4, 6 ] + + List.len nums + "# + ), + 3, + usize + ); + } + + #[test] + fn fn_int_list_len() { + assert_evals_to!( + indoc!( + r#" + getLen = \list -> List.len list + + nums = [ 2, 4, 6, 8 ] + + getLen nums + "# + ), + 4, + usize + ); + } + + #[test] + fn int_list_is_empty() { + assert_evals_to!("List.isEmpty [ 12, 9, 6, 3 ]", false, bool); + } + + #[test] + fn empty_list_is_empty() { + assert_evals_to!("List.isEmpty []", true, bool); + } + + #[test] + fn first_int_list() { + assert_evals_to!( + indoc!( + r#" + when List.first [ 12, 9, 6, 3 ] is + Ok val -> val + Err _ -> -1 + "# + ), + 12, + i64 + ); + } + + #[test] + fn first_wildcard_empty_list() { + assert_evals_to!( + indoc!( + r#" + when List.first [] is + Ok _ -> 5 + Err _ -> -1 + "# + ), + -1, + i64 + ); + } + + // #[test] + // fn first_empty_list() { + // assert_evals_to!( + // indoc!( + // r#" + // when List.first [] is + // Ok val -> val + // Err _ -> -1 + // "# + // ), + // -1, + // i64 + // ); + // } + + #[test] + fn get_empty_list() { + assert_evals_to!( + indoc!( + r#" + when List.get [] 0 is + Ok val -> val + Err _ -> -1 + "# + ), + -1, + i64 + ); + } + + #[test] + fn get_wildcard_empty_list() { + assert_evals_to!( + indoc!( + r#" + when List.get [] 0 is + Ok _ -> 5 + Err _ -> -1 + "# + ), + -1, + i64 + ); + } + + #[test] + fn get_int_list_ok() { + assert_evals_to!( + indoc!( + r#" + when List.get [ 12, 9, 6 ] 1 is + Ok val -> val + Err _ -> -1 + "# + ), + 9, + i64 + ); + } + + #[test] + fn get_int_list_oob() { + assert_evals_to!( + indoc!( + r#" + when List.get [ 12, 9, 6 ] 1000 is + Ok val -> val + Err _ -> -1 + "# + ), + -1, + i64 + ); + } + + #[test] + fn get_set_unique_int_list() { + assert_evals_to!( + indoc!( + r#" + when List.get (List.set [ 12, 9, 7, 3 ] 1 42) 1 is + Ok val -> val + Err _ -> -1 + "# + ), + 42, + i64 + ); + } + + #[test] + fn set_unique_int_list() { + assert_evals_to!( + "List.set [ 12, 9, 7, 1, 5 ] 2 33", + &[12, 9, 33, 1, 5], + &'static [i64] + ); + } + + #[test] + fn set_unique_list_oob() { + assert_evals_to!( + "List.set [ 3, 17, 4.1 ] 1337 9.25", + &[3.0, 17.0, 4.1], + &'static [f64] + ); + } + + #[test] + fn set_shared_int_list() { + assert_evals_to!( + indoc!( + r#" + shared = [ 2.1, 4.3 ] + + # This should not mutate the original + x = + when List.get (List.set shared 1 7.7) 1 is + Ok num -> num + Err _ -> 0 + + y = + when List.get shared 1 is + Ok num -> num + Err _ -> 0 + + { x, y } + "# + ), + (7.7, 4.3), + (f64, f64) + ); + } + + #[test] + fn set_shared_list_oob() { + assert_evals_to!( + indoc!( + r#" + shared = [ 2, 4 ] + + # This List.set is out of bounds, and should have no effect + x = + when List.get (List.set shared 422 0) 1 is + Ok num -> num + Err _ -> 0 + + y = + when List.get shared 1 is + Ok num -> num + Err _ -> 0 + + { x, y } + "# + ), + (4, 4), + (i64, i64) + ); + } + + #[test] + fn get_unique_int_list() { + assert_evals_to!( + indoc!( + r#" + unique = [ 2, 4 ] + + when List.get unique 1 is + Ok num -> num + Err _ -> -1 + "# + ), + 4, + i64 + ); + } + + #[test] + fn gen_quicksort() { + with_larger_debug_stack(|| { + // assert_evals_to!( + // indoc!( + // r#" + // quicksort : List (Num a) -> List (Num a) + // quicksort = \list -> + // quicksortHelp list 0 (List.len list - 1) + + // quicksortHelp : List (Num a), Int, Int -> List (Num a) + // quicksortHelp = \list, low, high -> + // if low < high then + // when partition low high list is + // Pair partitionIndex partitioned -> + // partitioned + // |> quicksortHelp low (partitionIndex - 1) + // |> quicksortHelp (partitionIndex + 1) high + // else + // list + + // swap : Int, Int, List a -> List a + // swap = \i, j, list -> + // when Pair (List.get list i) (List.get list j) is + // Pair (Ok atI) (Ok atJ) -> + // list + // |> List.set i atJ + // |> List.set j atI + + // _ -> + // [] + + // partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ] + // partition = \low, high, initialList -> + // when List.get initialList high is + // Ok pivot -> + // when partitionHelp (low - 1) low initialList high pivot is + // Pair newI newList -> + // Pair (newI + 1) (swap (newI + 1) high newList) + + // Err _ -> + // Pair (low - 1) initialList + + // partitionHelp : Int, Int, List (Num a), Int, Int -> [ Pair Int (List (Num a)) ] + // partitionHelp = \i, j, list, high, pivot -> + // if j < high then + // when List.get list j is + // Ok value -> + // if value <= pivot then + // partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) high pivot + // else + // partitionHelp i (j + 1) list high pivot + + // Err _ -> + // Pair i list + // else + // Pair i list + + // quicksort [ 7, 4, 21, 19 ] + // "# + // ), + // &[4, 7, 19, 21], + // &'static [i64] + // ); + }) + } +} diff --git a/compiler/gen/tests/gen_num.rs b/compiler/gen/tests/gen_num.rs new file mode 100644 index 0000000000..ad4ccb1d21 --- /dev/null +++ b/compiler/gen/tests/gen_num.rs @@ -0,0 +1,512 @@ +#[macro_use] +extern crate pretty_assertions; +#[macro_use] +extern crate indoc; + +extern crate bumpalo; +extern crate inkwell; +extern crate libc; +extern crate roc_gen; + +#[macro_use] +mod helpers; + +#[cfg(test)] +mod gen_num { + use crate::helpers::{can_expr, infer_expr, uniq_expr, CanExprOut}; + use bumpalo::Bump; + use inkwell::context::Context; + use inkwell::execution_engine::JitFunction; + use inkwell::passes::PassManager; + use inkwell::types::BasicType; + use inkwell::OptimizationLevel; + use roc_collections::all::ImMap; + use roc_gen::llvm::build::{build_proc, build_proc_header}; + use roc_gen::llvm::convert::basic_type_from_layout; + use roc_mono::expr::{Expr, Procs}; + use roc_mono::layout::Layout; + use roc_types::subs::Subs; + + #[test] + fn f64_sqrt() { + // FIXME this works with normal types, but fails when checking uniqueness types + assert_evals_to!( + indoc!( + r#" + when Num.sqrt 100 is + Ok val -> val + Err _ -> -1 + "# + ), + 10.0, + f64 + ); + } + + #[test] + fn f64_round() { + assert_evals_to!("Num.round 3.6", 4, i64); + } + + #[test] + fn f64_abs() { + assert_evals_to!("Num.abs -4.7", 4.7, f64); + assert_evals_to!("Num.abs 5.8", 5.8, f64); + } + + #[test] + fn i64_abs() { + assert_evals_to!("Num.abs -6", 6, i64); + assert_evals_to!("Num.abs 7", 7, i64); + assert_evals_to!("Num.abs 0", 0, i64); + assert_evals_to!("Num.abs -0", 0, i64); + assert_evals_to!("Num.abs -1", 1, i64); + assert_evals_to!("Num.abs 1", 1, i64); + assert_evals_to!("Num.abs 9_000_000_000_000", 9_000_000_000_000, i64); + assert_evals_to!("Num.abs -9_000_000_000_000", 9_000_000_000_000, i64); + } + + #[test] + fn gen_if_fn() { + assert_evals_to!( + indoc!( + r#" + limitedNegate = \num -> + if num == 1 then + -1 + else if num == -1 then + 1 + else + num + + limitedNegate 1 + "# + ), + -1, + i64 + ); + } + + #[test] + fn gen_float_eq() { + assert_evals_to!( + indoc!( + r#" + 1.0 == 1.0 + "# + ), + true, + bool + ); + } + + #[test] + fn gen_add_f64() { + assert_evals_to!( + indoc!( + r#" + 1.1 + 2.4 + 3 + "# + ), + 6.5, + f64 + ); + } + + #[test] + fn gen_div_f64() { + // FIXME this works with normal types, but fails when checking uniqueness types + assert_evals_to!( + indoc!( + r#" + when 48 / 2 is + Ok val -> val + Err _ -> -1 + "# + ), + 24.0, + f64 + ); + } + + #[test] + fn gen_int_eq() { + assert_evals_to!( + indoc!( + r#" + 4 == 4 + "# + ), + true, + bool + ); + } + + #[test] + fn gen_int_neq() { + assert_evals_to!( + indoc!( + r#" + 4 != 5 + "# + ), + true, + bool + ); + } + + #[test] + fn gen_add_i64() { + assert_evals_to!( + indoc!( + r#" + 1 + 2 + 3 + "# + ), + 6, + i64 + ); + } + + #[test] + fn gen_sub_f64() { + assert_evals_to!( + indoc!( + r#" + 1.5 - 2.4 - 3 + "# + ), + -3.9, + f64 + ); + } + + #[test] + fn gen_sub_i64() { + assert_evals_to!( + indoc!( + r#" + 1 - 2 - 3 + "# + ), + -4, + i64 + ); + } + + #[test] + fn gen_mul_i64() { + assert_evals_to!( + indoc!( + r#" + 2 * 4 * 6 + "# + ), + 48, + i64 + ); + } + + #[test] + fn gen_div_i64() { + assert_evals_to!( + indoc!( + r#" + when 1000 // 10 is + Ok val -> val + Err _ -> -1 + "# + ), + 100, + i64 + ); + } + + #[test] + fn gen_div_by_zero_i64() { + assert_evals_to!( + indoc!( + r#" + when 1000 // 0 is + Err DivByZero -> 99 + _ -> -24 + "# + ), + 99, + i64 + ); + } + + #[test] + fn gen_rem_i64() { + assert_evals_to!( + indoc!( + r#" + when Num.rem 8 3 is + Ok val -> val + Err _ -> -1 + "# + ), + 2, + i64 + ); + } + + #[test] + fn gen_rem_div_by_zero_i64() { + assert_evals_to!( + indoc!( + r#" + when Num.rem 8 0 is + Err DivByZero -> 4 + Ok _ -> -23 + "# + ), + 4, + i64 + ); + } + + #[test] + fn gen_is_zero_i64() { + assert_evals_to!("Num.isZero 0", true, bool); + assert_evals_to!("Num.isZero 1", false, bool); + } + + #[test] + fn gen_is_positive_i64() { + assert_evals_to!("Num.isPositive 0", false, bool); + assert_evals_to!("Num.isPositive 1", true, bool); + assert_evals_to!("Num.isPositive -5", false, bool); + } + + #[test] + fn gen_is_negative_i64() { + assert_evals_to!("Num.isNegative 0", false, bool); + assert_evals_to!("Num.isNegative 3", false, bool); + assert_evals_to!("Num.isNegative -2", true, bool); + } + + #[test] + fn gen_is_positive_f64() { + assert_evals_to!("Num.isPositive 0.0", false, bool); + assert_evals_to!("Num.isPositive 4.7", true, bool); + assert_evals_to!("Num.isPositive -8.5", false, bool); + } + + #[test] + fn gen_is_negative_f64() { + assert_evals_to!("Num.isNegative 0.0", false, bool); + assert_evals_to!("Num.isNegative 9.9", false, bool); + assert_evals_to!("Num.isNegative -4.4", true, bool); + } + + #[test] + fn gen_is_zero_f64() { + assert_evals_to!("Num.isZero 0", true, bool); + assert_evals_to!("Num.isZero 0_0", true, bool); + assert_evals_to!("Num.isZero 0.0", true, bool); + assert_evals_to!("Num.isZero 1", false, bool); + } + + #[test] + fn gen_is_odd() { + assert_evals_to!("Num.isOdd 4", false, bool); + assert_evals_to!("Num.isOdd 5", true, bool); + } + + #[test] + fn gen_is_even() { + assert_evals_to!("Num.isEven 6", true, bool); + assert_evals_to!("Num.isEven 7", false, bool); + } + + #[test] + fn sin() { + assert_evals_to!("Num.sin 0", 0.0, f64); + assert_evals_to!("Num.sin 1.41421356237", 0.9877659459922529, f64); + } + + #[test] + fn cos() { + assert_evals_to!("Num.cos 0", 1.0, f64); + assert_evals_to!("Num.cos 3.14159265359", -1.0, f64); + } + + #[test] + fn tan() { + assert_evals_to!("Num.tan 0", 0.0, f64); + assert_evals_to!("Num.tan 1", 1.557407724654902, f64); + } + + #[test] + fn lt_i64() { + assert_evals_to!("1 < 2", true, bool); + assert_evals_to!("1 < 1", false, bool); + assert_evals_to!("2 < 1", false, bool); + assert_evals_to!("0 < 0", false, bool); + } + + #[test] + fn lte_i64() { + assert_evals_to!("1 <= 1", true, bool); + assert_evals_to!("2 <= 1", false, bool); + assert_evals_to!("1 <= 2", true, bool); + assert_evals_to!("0 <= 0", true, bool); + } + + #[test] + fn gt_i64() { + assert_evals_to!("2 > 1", true, bool); + assert_evals_to!("2 > 2", false, bool); + assert_evals_to!("1 > 1", false, bool); + assert_evals_to!("0 > 0", false, bool); + } + + #[test] + fn gte_i64() { + assert_evals_to!("1 >= 1", true, bool); + assert_evals_to!("1 >= 2", false, bool); + assert_evals_to!("2 >= 1", true, bool); + assert_evals_to!("0 >= 0", true, bool); + } + + #[test] + fn lt_f64() { + assert_evals_to!("1.1 < 1.2", true, bool); + assert_evals_to!("1.1 < 1.1", false, bool); + assert_evals_to!("1.2 < 1.1", false, bool); + assert_evals_to!("0.0 < 0.0", false, bool); + } + + #[test] + fn lte_f64() { + assert_evals_to!("1.1 <= 1.1", true, bool); + assert_evals_to!("1.2 <= 1.1", false, bool); + assert_evals_to!("1.1 <= 1.2", true, bool); + assert_evals_to!("0.0 <= 0.0", true, bool); + } + + #[test] + fn gt_f64() { + assert_evals_to!("2.2 > 1.1", true, bool); + assert_evals_to!("2.2 > 2.2", false, bool); + assert_evals_to!("1.1 > 2.2", false, bool); + assert_evals_to!("0.0 > 0.0", false, bool); + } + + #[test] + fn gte_f64() { + assert_evals_to!("1.1 >= 1.1", true, bool); + assert_evals_to!("1.1 >= 1.2", false, bool); + assert_evals_to!("1.2 >= 1.1", true, bool); + assert_evals_to!("0.0 >= 0.0", true, bool); + } + + #[test] + fn gen_order_of_arithmetic_ops() { + assert_evals_to!( + indoc!( + r#" + 1 + 3 * 7 - 2 + "# + ), + 20, + i64 + ); + } + + #[test] + fn gen_order_of_arithmetic_ops_complex_float() { + assert_evals_to!( + indoc!( + r#" + 3 - 48 * 2.0 + "# + ), + -93.0, + f64 + ); + } + + #[test] + fn if_guard_bind_variable() { + assert_evals_to!( + indoc!( + r#" + when 10 is + x if x == 5 -> 0 + _ -> 42 + "# + ), + 42, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + when 10 is + x if x == 10 -> 42 + _ -> 0 + "# + ), + 42, + i64 + ); + } + + #[test] + fn tail_call_elimination() { + assert_evals_to!( + indoc!( + r#" + sum = \n, accum -> + when n is + 0 -> accum + _ -> sum (n - 1) (n + accum) + + sum 1_000_000 0 + "# + ), + 500000500000, + i64 + ); + } + + #[test] + fn int_negate() { + assert_evals_to!("Num.neg 123", -123, i64); + } + + #[test] + fn gen_basic_fn() { + assert_evals_to!( + indoc!( + r#" + always42 : Num.Num Num.Integer -> Num.Num Num.Integer + always42 = \num -> 42 + + always42 5 + "# + ), + 42, + i64 + ); + } + + #[test] + fn int_to_float() { + assert_evals_to!("Num.toFloat 0x9", 9.0, f64); + } + + #[test] + fn num_to_float() { + assert_evals_to!("Num.toFloat 9", 9.0, f64); + } + + #[test] + fn float_to_float() { + assert_evals_to!("Num.toFloat 0.5", 0.5, f64); + } +} diff --git a/compiler/gen/tests/gen_primitives.rs b/compiler/gen/tests/gen_primitives.rs index 8923cb24f8..8b5b9037cd 100644 --- a/compiler/gen/tests/gen_primitives.rs +++ b/compiler/gen/tests/gen_primitives.rs @@ -13,7 +13,7 @@ mod helpers; #[cfg(test)] mod gen_primitives { - use crate::helpers::{can_expr, infer_expr, uniq_expr, with_larger_debug_stack, CanExprOut}; + use crate::helpers::{can_expr, infer_expr, uniq_expr, CanExprOut}; use bumpalo::Bump; use inkwell::context::Context; use inkwell::execution_engine::JitFunction; @@ -298,7 +298,7 @@ mod gen_primitives { } #[test] - fn apply_unnamed_fn() { + fn apply_unnamed_identity() { assert_evals_to!( indoc!( r#" @@ -406,30 +406,27 @@ mod gen_primitives { #[test] fn gen_chained_defs() { - with_larger_debug_stack(|| { - assert_evals_to!( - indoc!( - r#" - x = i1 - i3 = i2 - i1 = 1337 - i2 = i1 - y = 12.4 - - i3 - "# - ), - 1337, - i64 - ); - }) + assert_evals_to!( + indoc!( + r#" + x = i1 + i3 = i2 + i1 = 1337 + i2 = i1 + y = 12.4 + + i3 + "# + ), + 1337, + i64 + ); } #[test] fn gen_nested_defs() { - with_larger_debug_stack(|| { - assert_evals_to!( - indoc!( - r#" + assert_evals_to!( + indoc!( + r#" x = 5 answer = @@ -460,10 +457,9 @@ mod gen_primitives { answer "# - ), - 1337, - i64 - ); - }) + ), + 1337, + i64 + ); } } diff --git a/compiler/gen/tests/gen_tags.rs b/compiler/gen/tests/gen_tags.rs index 1e04a956ca..96ae1a3477 100644 --- a/compiler/gen/tests/gen_tags.rs +++ b/compiler/gen/tests/gen_tags.rs @@ -13,7 +13,7 @@ mod helpers; #[cfg(test)] mod gen_tags { - use crate::helpers::{can_expr, infer_expr, uniq_expr, with_larger_debug_stack, CanExprOut}; + use crate::helpers::{can_expr, infer_expr, uniq_expr, CanExprOut}; use bumpalo::Bump; use inkwell::context::Context; use inkwell::execution_engine::JitFunction; @@ -213,10 +213,9 @@ mod gen_tags { #[test] fn even_odd() { - with_larger_debug_stack(|| { - assert_evals_to!( - indoc!( - r#" + assert_evals_to!( + indoc!( + r#" even = \n -> when n is 0 -> True @@ -231,11 +230,10 @@ mod gen_tags { odd 5 && even 42 "# - ), - true, - bool - ); - }) + ), + true, + bool + ); } #[test] diff --git a/compiler/gen/tests/helpers/eval.rs b/compiler/gen/tests/helpers/eval.rs index 6a42c8d117..cc196415e9 100644 --- a/compiler/gen/tests/helpers/eval.rs +++ b/compiler/gen/tests/helpers/eval.rs @@ -39,7 +39,7 @@ macro_rules! assert_llvm_evals_to { // Compute main_fn_type before moving subs to Env let layout = Layout::new(&arena, content, &subs, ptr_bytes) - .unwrap_or_else(|err| panic!("Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", err, subs)); + .unwrap_or_else(|err| panic!("Code gen error in NON-OPTIMIZED test: could not convert to layout. Err was {:?}", err)); let execution_engine = module .create_jit_execution_engine(OptimizationLevel::None) @@ -73,40 +73,48 @@ macro_rules! assert_llvm_evals_to { pointer_size: ptr_bytes, jump_counter: arena.alloc(0), }; + let main_body = Expr::new(&mut mono_env, loc_expr.value, &mut procs); - // Put this module's ident_ids back in the interns, so we can use them in Env. - env.interns.all_ident_ids.insert(home, ident_ids); + let mut headers = Vec::with_capacity(procs.pending_specializations.len()); + let mut layout_cache = roc_mono::layout::LayoutCache::default(); - let mut headers = Vec::with_capacity(procs.len()); - let (mut proc_map, runtime_errors) = procs.into_map(); + let (mut specializations, runtime_errors) = + roc_mono::expr::specialize_all(&mut mono_env, procs, &mut layout_cache); assert_eq!(runtime_errors, roc_collections::all::MutSet::default()); + // Put this module's ident_ids back in the interns, so we can use them in env. + // This must happen *after* building the headers, because otherwise there's + // a conflicting mutable borrow on ident_ids. + env.interns.all_ident_ids.insert(home, ident_ids); + // Add all the Proc headers to the module. // We have to do this in a separate pass first, // because their bodies may reference each other. - for (symbol, mut procs_by_layout) in proc_map.drain() { - for (layout, proc) in procs_by_layout.drain() { - let (fn_val, arg_basic_types) = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc); + for ((symbol, layout), proc) in specializations.drain() { + let (fn_val, arg_basic_types) = + build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc); - headers.push((proc, fn_val, arg_basic_types)); - } + headers.push((proc, fn_val, arg_basic_types)); } // Build each proc using its header info. for (proc, fn_val, arg_basic_types) in headers { - // NOTE: This is here to be uncommented in case verification fails. - // (This approach means we don't have to defensively clone name here.) - // - // println!("\n\nBuilding and then verifying function {}\n\n", name); build_proc(&env, &mut layout_ids, proc, fn_val, arg_basic_types); if fn_val.verify(true) { fpm.run_on(&fn_val); } else { - // NOTE: If this fails, uncomment the above println to debug. - panic!("Non-main function failed LLVM verification. Uncomment the above println to debug!"); + eprintln!( + "\n\nFunction {:?} failed LLVM verification in NON-OPTIMIZED build. Its content was:\n", fn_val.get_name().to_str().unwrap() + ); + + fn_val.print_to_stderr(); + + panic!( + "The preceding code was from {:?}, which failed LLVM verification in NON-OPTIMIZED build.", fn_val.get_name().to_str().unwrap() + ); } } @@ -137,7 +145,7 @@ macro_rules! assert_llvm_evals_to { if main_fn.verify(true) { fpm.run_on(&main_fn); } else { - panic!("Function {} failed LLVM verification.", main_fn_name); + panic!("Main function {} failed LLVM verification in NON-OPTIMIZED build. Uncomment things nearby to see more details.", main_fn_name); } // Verify the module @@ -203,7 +211,7 @@ macro_rules! assert_opt_evals_to { // Compute main_fn_type before moving subs to Env let layout = Layout::new(&arena, content, &subs, ptr_bytes) - .unwrap_or_else(|err| panic!("Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", err, subs)); + .unwrap_or_else(|err| panic!("Code gen error in OPTIMIZED test: could not convert to layout. Err was {:?}", err)); let execution_engine = module @@ -240,38 +248,45 @@ macro_rules! assert_opt_evals_to { }; let main_body = Expr::new(&mut mono_env, loc_expr.value, &mut procs); - // Put this module's ident_ids back in the interns, so we can use them in Env. - env.interns.all_ident_ids.insert(home, ident_ids); + let mut headers = Vec::with_capacity(procs.pending_specializations.len()); + let mut layout_cache = roc_mono::layout::LayoutCache::default(); - let mut headers = Vec::with_capacity(procs.len()); - let (mut proc_map, runtime_errors) = procs.into_map(); + let (mut specializations, runtime_errors) = + roc_mono::expr::specialize_all(&mut mono_env, procs, &mut layout_cache); assert_eq!(runtime_errors, roc_collections::all::MutSet::default()); + // Put this module's ident_ids back in the interns, so we can use them in env. + // This must happen *after* building the headers, because otherwise there's + // a conflicting mutable borrow on ident_ids. + env.interns.all_ident_ids.insert(home, ident_ids); + // Add all the Proc headers to the module. // We have to do this in a separate pass first, // because their bodies may reference each other. - for (symbol, mut procs_by_layout) in proc_map.drain() { - for (layout, proc) in procs_by_layout.drain() { - let (fn_val, arg_basic_types) = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc); + for ((symbol, layout), proc) in specializations.drain() { + let (fn_val, arg_basic_types) = + build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc); - headers.push((proc, fn_val, arg_basic_types)); - } + headers.push((proc, fn_val, arg_basic_types)); } // Build each proc using its header info. for (proc, fn_val, arg_basic_types) in headers { - // NOTE: This is here to be uncommented in case verification fails. - // (This approach means we don't have to defensively clone name here.) - // - // println!("\n\nBuilding and then verifying function {}\n\n", name); build_proc(&env, &mut layout_ids, proc, fn_val, arg_basic_types); if fn_val.verify(true) { fpm.run_on(&fn_val); } else { - // NOTE: If this fails, uncomment the above println to debug. - panic!("Non-main function failed LLVM verification. Uncomment the above println to debug!"); + eprintln!( + "\n\nFunction {:?} failed LLVM verification in OPTIMIZED build. Its content was:\n", fn_val.get_name().to_str().unwrap() + ); + + fn_val.print_to_stderr(); + + panic!( + "The preceding code was from {:?}, which failed LLVM verification in OPTIMIZED build.", fn_val.get_name().to_str().unwrap() + ); } } @@ -302,161 +317,7 @@ macro_rules! assert_opt_evals_to { if main_fn.verify(true) { fpm.run_on(&main_fn); } else { - panic!("Function {} failed LLVM verification.", main_fn_name); - } - - // Verify the module - if let Err(errors) = env.module.verify() { - panic!("Errors defining module: {:?}", errors); - } - - // Uncomment this to see the module's optimized LLVM instruction output: - // env.module.print_to_stderr(); - - unsafe { - let main: JitFunction $ty> = execution_engine - .get_function(main_fn_name) - .ok() - .ok_or(format!("Unable to JIT compile `{}`", main_fn_name)) - .expect("errored"); - - assert_eq!($transform(main.call()), $expected); - } - }; -} - -#[macro_export] -macro_rules! emit_expr { - ($src:expr, $expected:expr, $ty:ty, $transform:expr) => { - let arena = Bump::new(); - let (loc_expr, _output, problems, subs, var, constraint, home, interns) = uniq_expr($src); - let errors = problems.into_iter().filter(|problem| { - use roc_problem::can::Problem::*; - - // Ignore "unused" problems - match problem { - UnusedDef(_, _) | UnusedArgument(_, _, _) | UnusedImport(_, _) => false, - _ => true, - } - }).collect::>(); - - assert_eq!(errors, Vec::new(), "Encountered errors: {:?}", errors); - - let mut unify_problems = Vec::new(); - let (content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var); - - assert_eq!(unify_problems, Vec::new(), "Encountered one or more type mismatches: {:?}", unify_problems); - - let context = Context::create(); - let module = context.create_module("app"); - let builder = context.create_builder(); - let opt_level = if cfg!(debug_assertions) { - roc_gen::llvm::build::OptLevel::Normal - } else { - roc_gen::llvm::build::OptLevel::Optimize - }; - let fpm = PassManager::create(&module); - - roc_gen::llvm::build::add_passes(&fpm, opt_level); - - fpm.initialize(); - - // Compute main_fn_type before moving subs to Env - let layout = Layout::new(&arena, content, &subs, ptr_bytes) - .unwrap_or_else(|err| panic!("Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", err, subs)); - - let execution_engine = - module - .create_jit_execution_engine(OptimizationLevel::None) - .expect("Error creating JIT execution engine for test"); - - let main_fn_type = basic_type_from_layout(&arena, &context, &layout, ptr_bytes) - .fn_type(&[], false); - let main_fn_name = "$Test.main"; - - // Compile and add all the Procs before adding main - let mut env = roc_gen::llvm::build::Env { - arena: &arena, - builder: &builder, - context: &context, - interns, - module: arena.alloc(module), - ptr_bytes - }; - let mut procs = Procs::default(); - let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); - - // Populate Procs and get the low-level Expr from the canonical Expr - let mut mono_env = roc_mono::expr::Env { - arena: &arena, - subs: &mut subs, - problems: &mut mono_problems, - home, - ident_ids: &mut ident_ids, - pointer_size: ptr_bytes, - jump_counter: arena.alloc(0), - }; - let main_body = Expr::new(&mut mono_env, loc_expr.value, &mut procs); - - // Put this module's ident_ids back in the interns, so we can use them in Env. - env.interns.all_ident_ids.insert(home, ident_ids); - - let mut headers = Vec::with_capacity(procs.len()); - - // Add all the Proc headers to the module. - // We have to do this in a separate pass first, - // because their bodies may reference each other. - for (symbol, opt_proc) in procs.as_map().into_iter() { - if let Some(proc) = opt_proc { - let (fn_val, arg_basic_types) = build_proc_header(&env, symbol, &proc); - - headers.push((proc, fn_val, arg_basic_types)); - } - } - - // Build each proc using its header info. - for (proc, fn_val, arg_basic_types) in headers { - // NOTE: This is here to be uncommented in case verification fails. - // (This approach means we don't have to defensively clone name here.) - // - // println!("\n\nBuilding and then verifying function {}\n\n", name); - build_proc(&env, proc, &procs, fn_val, arg_basic_types); - - if fn_val.verify(true) { - fpm.run_on(&fn_val); - } else { - // NOTE: If this fails, uncomment the above println to debug. - panic!("Non-main function failed LLVM verification. Uncomment the above println to debug!"); - } - } - - // Add main to the module. - let main_fn = env.module.add_function(main_fn_name, main_fn_type, None); - - main_fn.set_call_conventions($crate::helpers::eval::MAIN_CALLING_CONVENTION); - - // Add main's body - let basic_block = context.append_basic_block(main_fn, "entry"); - - builder.position_at_end(basic_block); - - let ret = roc_gen::llvm::build::build_expr( - &env, - &ImMap::default(), - main_fn, - &main_body, - &mut Procs::default(), - ); - - builder.build_return(Some(&ret)); - - // Uncomment this to see the module's un-optimized LLVM instruction output: - // env.module.print_to_stderr(); - - if main_fn.verify(true) { - fpm.run_on(&main_fn); - } else { - panic!("Function {} failed LLVM verification.", main_fn_name); + panic!("main function {} failed LLVM verification in OPTIMIZED build. Uncomment nearby statements to see more details.", main_fn_name); } // Verify the module diff --git a/compiler/gen/tests/helpers/mod.rs b/compiler/gen/tests/helpers/mod.rs index 8c58841d7c..b0d25a15c9 100644 --- a/compiler/gen/tests/helpers/mod.rs +++ b/compiler/gen/tests/helpers/mod.rs @@ -6,14 +6,12 @@ pub mod eval; use self::bumpalo::Bump; use roc_builtins::unique::uniq_stdlib; use roc_can::constraint::Constraint; -use roc_can::def::Def; use roc_can::env::Env; use roc_can::expected::Expected; use roc_can::expr::{canonicalize_expr, Expr, Output}; use roc_can::operator; -use roc_can::pattern::Pattern; use roc_can::scope::Scope; -use roc_collections::all::{ImMap, ImSet, MutMap, SendMap, SendSet}; +use roc_collections::all::{ImMap, MutMap, SendMap}; use roc_constrain::expr::constrain_expr; use roc_constrain::module::{constrain_imported_values, load_builtin_aliases, Import}; use roc_module::ident::Ident; @@ -26,34 +24,15 @@ use roc_region::all::{Located, Region}; use roc_solve::solve; use roc_types::subs::{Content, Subs, VarStore, Variable}; use roc_types::types::Type; -use std::hash::Hash; -use std::path::{Path, PathBuf}; pub fn test_home() -> ModuleId { ModuleIds::default().get_or_insert(&"Test".into()) } -pub fn infer_expr( - subs: Subs, - problems: &mut Vec, - constraint: &Constraint, - expr_var: Variable, -) -> (Content, Subs) { - let env = solve::Env { - aliases: MutMap::default(), - vars_by_symbol: SendMap::default(), - }; - let (solved, _) = solve::run(&env, problems, subs, constraint); - - let content = solved.inner().get_without_compacting(expr_var).content; - - (content, solved.into_inner()) -} - /// Used in the with_larger_debug_stack() function, for tests that otherwise /// run out of stack space in debug builds (but don't in --release builds) #[allow(dead_code)] -const EXPANDED_STACK_SIZE: usize = 4 * 1024 * 1024; +const EXPANDED_STACK_SIZE: usize = 8 * 1024 * 1024; /// Without this, some tests pass in `cargo test --release` but fail without /// the --release flag because they run out of stack space. This increases @@ -90,12 +69,23 @@ where run_test() } -#[allow(dead_code)] -pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result, Fail> { - parse_loc_with(arena, input).map(|loc_expr| loc_expr.value) +pub fn infer_expr( + subs: Subs, + problems: &mut Vec, + constraint: &Constraint, + expr_var: Variable, +) -> (Content, Subs) { + let env = solve::Env { + aliases: MutMap::default(), + vars_by_symbol: SendMap::default(), + }; + let (solved, _) = solve::run(&env, problems, subs, constraint); + + let content = solved.inner().get_without_compacting(expr_var).content; + + (content, solved.into_inner()) } -#[allow(dead_code)] pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result>, Fail> { let state = State::new(&input, Attempting::Module); let parser = space0_before(loc(roc_parse::expr::expr(0)), 0); @@ -106,12 +96,10 @@ pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result CanExprOut { can_expr_with(&Bump::new(), test_home(), expr_str) } -#[allow(dead_code)] pub fn uniq_expr( expr_str: &str, ) -> ( @@ -129,7 +117,6 @@ pub fn uniq_expr( uniq_expr_with(&Bump::new(), expr_str, declared_idents) } -#[allow(dead_code)] pub fn uniq_expr_with( arena: &Bump, expr_str: &str, @@ -241,29 +228,16 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut &loc_expr.value, ); - let mut with_builtins = loc_expr.value; - // Add builtin defs (e.g. List.get) directly to the canonical Expr, // since we aren't using modules here. + let mut with_builtins = loc_expr.value; let builtin_defs = roc_can::builtins::builtin_defs(&mut var_store); - for (symbol, expr) in builtin_defs { + for (symbol, def) in builtin_defs { if output.references.lookups.contains(&symbol) || output.references.calls.contains(&symbol) { with_builtins = Expr::LetNonRec( - Box::new(Def { - loc_pattern: Located { - region: Region::zero(), - value: Pattern::Identifier(symbol), - }, - loc_expr: Located { - region: Region::zero(), - value: expr, - }, - expr_var: var_store.fresh(), - pattern_vars: SendMap::default(), - annotation: None, - }), + Box::new(def), Box::new(Located { region: Region::zero(), value: with_builtins, @@ -340,162 +314,3 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut constraint, } } - -#[allow(dead_code)] -pub fn mut_map_from_pairs(pairs: I) -> MutMap -where - I: IntoIterator, - K: Hash + Eq, -{ - let mut answer = MutMap::default(); - - for (key, value) in pairs { - answer.insert(key, value); - } - - answer -} - -#[allow(dead_code)] -pub fn im_map_from_pairs(pairs: I) -> ImMap -where - I: IntoIterator, - K: Hash + Eq + Clone, - V: Clone, -{ - let mut answer = ImMap::default(); - - for (key, value) in pairs { - answer.insert(key, value); - } - - answer -} - -#[allow(dead_code)] -pub fn send_set_from(elems: I) -> SendSet -where - I: IntoIterator, - V: Hash + Eq + Clone, -{ - let mut answer = SendSet::default(); - - for elem in elems { - answer.insert(elem); - } - - answer -} - -#[allow(dead_code)] -pub fn fixtures_dir<'a>() -> PathBuf { - Path::new("tests").join("fixtures").join("build") -} - -#[allow(dead_code)] -pub fn builtins_dir<'a>() -> PathBuf { - PathBuf::new().join("builtins") -} - -// Check constraints -// -// Keep track of the used (in types or expectations) variables, and the declared variables (in -// flex_vars or rigid_vars fields of LetConstraint. These roc_collections should match: no duplicates -// and no variables that are used but not declared are allowed. -// -// There is one exception: the initial variable (that stores the type of the whole expression) is -// never declared, but is used. -#[allow(dead_code)] -pub fn assert_correct_variable_usage(constraint: &Constraint) { - // variables declared in constraint (flex_vars or rigid_vars) - // and variables actually used in constraints - let (declared, used) = variable_usage(constraint); - - let used: ImSet = used.clone().into(); - let mut decl: ImSet = declared.rigid_vars.clone().into(); - - for var in declared.flex_vars.clone() { - decl.insert(var); - } - - let diff = used.clone().relative_complement(decl); - - // NOTE: this checks whether we're using variables that are not declared. For recursive type - // definitions, their rigid types are declared twice, which is correct! - if !diff.is_empty() { - println!("VARIABLE USAGE PROBLEM"); - - println!("used: {:?}", &used); - println!("rigids: {:?}", &declared.rigid_vars); - println!("flexs: {:?}", &declared.flex_vars); - - println!("difference: {:?}", &diff); - - panic!("variable usage problem (see stdout for details)"); - } -} - -#[derive(Default)] -pub struct SeenVariables { - pub rigid_vars: Vec, - pub flex_vars: Vec, -} - -pub fn variable_usage(con: &Constraint) -> (SeenVariables, Vec) { - let mut declared = SeenVariables::default(); - let mut used = ImSet::default(); - variable_usage_help(con, &mut declared, &mut used); - - used.remove(unsafe { &Variable::unsafe_test_debug_variable(1) }); - - let mut used_vec: Vec = used.into_iter().collect(); - used_vec.sort(); - - declared.rigid_vars.sort(); - declared.flex_vars.sort(); - - (declared, used_vec) -} - -fn variable_usage_help(con: &Constraint, declared: &mut SeenVariables, used: &mut ImSet) { - use Constraint::*; - - match con { - True | SaveTheEnvironment => (), - Eq(tipe, expectation, _, _) => { - for v in tipe.variables() { - used.insert(v); - } - - for v in expectation.get_type_ref().variables() { - used.insert(v); - } - } - Lookup(_, expectation, _) => { - for v in expectation.get_type_ref().variables() { - used.insert(v); - } - } - Pattern(_, _, tipe, pexpectation) => { - for v in tipe.variables() { - used.insert(v); - } - - for v in pexpectation.get_type_ref().variables() { - used.insert(v); - } - } - Let(letcon) => { - declared.rigid_vars.extend(letcon.rigid_vars.clone()); - declared.flex_vars.extend(letcon.flex_vars.clone()); - - variable_usage_help(&letcon.defs_constraint, declared, used); - variable_usage_help(&letcon.ret_constraint, declared, used); - } - And(constraints) => { - for sub in constraints { - variable_usage_help(sub, declared, used); - } - } - } -} diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 8f880f1bff..d18136937a 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -5,7 +5,7 @@ use roc_can::def::Declaration; use roc_can::module::{canonicalize_module_defs, Module}; use roc_collections::all::{default_hasher, MutMap, MutSet}; use roc_constrain::module::{ - constrain_imported_aliases, constrain_imported_values, load_builtin_aliases, Import, + constrain_imports, load_builtin_aliases, pre_constrain_imports, ConstrainableImports, }; use roc_constrain::module::{constrain_module, ExposedModuleTypes, SubsByModule}; use roc_module::ident::{Ident, ModuleName}; @@ -16,9 +16,8 @@ use roc_parse::parser::{Fail, Parser, State}; use roc_region::all::{Located, Region}; use roc_solve::module::SolvedModule; use roc_solve::solve; -use roc_types::solved_types::{Solved, SolvedType}; +use roc_types::solved_types::Solved; use roc_types::subs::{Subs, VarStore, Variable}; -use roc_types::types; use std::collections::{HashMap, HashSet}; use std::fs::read_to_string; use std::io; @@ -806,97 +805,22 @@ fn spawn_solve_module( exposed_types: &mut SubsByModule, stdlib: &StdLib, ) { - // Get the constriants for this module's imports. We do this on the main thread + let home = module.module_id; + + // Get the constraints for this module's imports. We do this on the main thread // to avoid having to lock the map of exposed types, or to clone it // (which would be more expensive for the main thread). - let home = module.module_id; - let mut imported_symbols = Vec::with_capacity(module.references.len()); - let mut imported_aliases = MutMap::default(); - let mut unused_imports = imported_modules; // We'll remove these as we encounter them. - - // Translate referenced symbols into constraints. We do this on the main - // thread because we need exclusive access to the exposed_types map, in order - // to get the necessary constraint info for any aliases we imported. We also - // resolve builtin types now, so we can use a refernce to stdlib instead of - // having to either clone it or recreate it from scratch on the other thread. - for &symbol in module.references.iter() { - let module_id = symbol.module_id(); - - // We used this one, so clearly it is not unused! - unused_imports.remove(&module_id); - - if module_id.is_builtin() { - // For builtin modules, we create imports from the - // hardcoded builtin map. - match stdlib.types.get(&symbol) { - Some((solved_type, region)) => { - let loc_symbol = Located { - value: symbol, - region: *region, - }; - - imported_symbols.push(Import { - loc_symbol, - solved_type: solved_type.clone(), - }); - } - None => { - let is_valid_alias = stdlib.applies.contains(&symbol) - // This wasn't a builtin value or Apply; maybe it was a builtin alias. - || stdlib.aliases.contains_key(&symbol); - - if !is_valid_alias { - panic!( - "Could not find {:?} in builtin types {:?} or aliases {:?}", - symbol, stdlib.types, stdlib.aliases - ); - } - } - } - } else if module_id != home { - // We already have constraints for our own symbols. - let region = Region::zero(); // TODO this should be the region where this symbol was declared in its home module. Look that up! - let loc_symbol = Located { - value: symbol, - region, - }; - - match exposed_types.get(&module_id) { - Some(ExposedModuleTypes::Valid(solved_types, new_aliases)) => { - let solved_type = solved_types.get(&symbol).unwrap_or_else(|| { - panic!( - "Could not find {:?} in solved_types {:?}", - loc_symbol.value, solved_types - ) - }); - - // TODO should this be a union? - for (k, v) in new_aliases.clone() { - imported_aliases.insert(k, v); - } - - imported_symbols.push(Import { - loc_symbol, - solved_type: solved_type.clone(), - }); - } - Some(ExposedModuleTypes::Invalid) => { - // If that module was invalid, use True constraints - // for everything imported from it. - imported_symbols.push(Import { - loc_symbol, - solved_type: SolvedType::Erroneous(types::Problem::InvalidModule), - }); - } - None => { - panic!( - "Could not find module {:?} in exposed_types {:?}", - module_id, exposed_types - ); - } - } - } - } + let ConstrainableImports { + imported_symbols, + imported_aliases, + unused_imports, + } = pre_constrain_imports( + home, + &module.references, + imported_modules, + exposed_types, + stdlib, + ); for unused_import in unused_imports { todo!( @@ -920,10 +844,12 @@ fn spawn_solve_module( // Finish constraining the module by wrapping the existing Constraint // in the ones we just computed. We can do this off the main thread. - // TODO what to do with the introduced rigids? - let (_introduced_rigids, constraint) = - constrain_imported_values(imported_symbols, constraint, &mut var_store); - let constraint = constrain_imported_aliases(imported_aliases, constraint, &mut var_store); + let constraint = constrain_imports( + imported_symbols, + imported_aliases, + constraint, + &mut var_store, + ); let mut constraint = load_builtin_aliases(aliases, constraint, &mut var_store); // Turn Apply into Alias diff --git a/compiler/load/tests/fixtures/build/app_with_deps/WithBuiltins.roc b/compiler/load/tests/fixtures/build/app_with_deps/WithBuiltins.roc index 37300efb8c..23ba8842f4 100644 --- a/compiler/load/tests/fixtures/build/app_with_deps/WithBuiltins.roc +++ b/compiler/load/tests/fixtures/build/app_with_deps/WithBuiltins.roc @@ -2,15 +2,15 @@ interface WithBuiltins exposes [ floatTest, divisionFn, divisionTest, intTest, constantNum, fromDep2, divDep1ByDep2 ] imports [ Dep1, Dep2.{ two } ] -floatTest = Float.highest +floatTest = Num.maxFloat -divisionFn = Float.div +divisionFn = Num.div x = 5.0 -divisionTest = Float.highest / x +divisionTest = Num.maxFloat / x -intTest = Int.highest +intTest = Num.maxInt constantNum = 5 diff --git a/compiler/load/tests/fixtures/build/interface_with_deps/WithBuiltins.roc b/compiler/load/tests/fixtures/build/interface_with_deps/WithBuiltins.roc index 37300efb8c..23ba8842f4 100644 --- a/compiler/load/tests/fixtures/build/interface_with_deps/WithBuiltins.roc +++ b/compiler/load/tests/fixtures/build/interface_with_deps/WithBuiltins.roc @@ -2,15 +2,15 @@ interface WithBuiltins exposes [ floatTest, divisionFn, divisionTest, intTest, constantNum, fromDep2, divDep1ByDep2 ] imports [ Dep1, Dep2.{ two } ] -floatTest = Float.highest +floatTest = Num.maxFloat -divisionFn = Float.div +divisionFn = Num.div x = 5.0 -divisionTest = Float.highest / x +divisionTest = Num.maxFloat / x -intTest = Int.highest +intTest = Num.maxInt constantNum = 5 diff --git a/compiler/load/tests/test_load.rs b/compiler/load/tests/test_load.rs index 812f774e40..d549378493 100644 --- a/compiler/load/tests/test_load.rs +++ b/compiler/load/tests/test_load.rs @@ -112,6 +112,7 @@ mod test_load { ); } } + Builtin(_) => {} cycle @ InvalidCycle(_, _) => { panic!("Unexpected cyclic def in module declarations: {:?}", cycle); } @@ -204,12 +205,12 @@ mod test_load { loaded_module, hashmap! { "floatTest" => "Float", - "divisionFn" => "Float, Float -> Float", - "divisionTest" => "Float", + "divisionFn" => "Float, Float -> Result Float [ DivByZero ]*", + "divisionTest" => "Result Float [ DivByZero ]*", "intTest" => "Int", "x" => "Float", "constantNum" => "Num *", - "divDep1ByDep2" => "Float", + "divDep1ByDep2" => "Result Float [ DivByZero ]*", "fromDep2" => "Float", }, ); diff --git a/compiler/load/tests/test_uniq_load.rs b/compiler/load/tests/test_uniq_load.rs index 2c80134534..bec8fcdd00 100644 --- a/compiler/load/tests/test_uniq_load.rs +++ b/compiler/load/tests/test_uniq_load.rs @@ -107,6 +107,7 @@ mod test_uniq_load { ); } } + Builtin(_) => {} cycle @ InvalidCycle(_, _) => { panic!("Unexpected cyclic def in module declarations: {:?}", cycle); } @@ -199,12 +200,12 @@ mod test_uniq_load { loaded_module, hashmap! { "floatTest" => "Attr Shared Float", - "divisionFn" => "Attr Shared (Attr * Float, Attr * Float -> Attr * Float)", - "divisionTest" => "Attr * Float", + "divisionFn" => "Attr Shared (Attr * Float, Attr * Float -> Attr * (Result (Attr * Float) (Attr * [ DivByZero ]*)))", + "divisionTest" => "Attr * (Result (Attr * Float) (Attr * [ DivByZero ]*))", "intTest" => "Attr * Int", "x" => "Attr * Float", "constantNum" => "Attr * (Num (Attr * *))", - "divDep1ByDep2" => "Attr * Float", + "divDep1ByDep2" => "Attr * (Result (Attr * Float) (Attr * [ DivByZero ]*))", "fromDep2" => "Attr * Float", }, ); @@ -272,6 +273,8 @@ mod test_uniq_load { let loaded_module = load_fixture("interface_with_deps", "Primary", subs_by_module).await; + // the inferred signature for withDefault is wrong, part of the alias in alias issue. + // "withDefault" => "Attr * (Attr * (Res.Res (Attr a b) (Attr * *)), Attr a b -> Attr a b)", expect_types( loaded_module, hashmap! { @@ -284,7 +287,7 @@ mod test_uniq_load { "w" => "Attr * (Dep1.Identity (Attr * {}))", "succeed" => "Attr * (Attr b a -> Attr * (Dep1.Identity (Attr b a)))", "yay" => "Attr * (Res.Res (Attr * {}) (Attr * err))", - "withDefault" => "Attr * (Attr (* | a | b) (Res.Res (Attr a c) (Attr b *)), Attr a c -> Attr a c)", + "withDefault" => "Attr * (Attr (* | b | c) (Res.Res (Attr b a) (Attr c *)), Attr b a -> Attr b a)", }, ); }); @@ -300,8 +303,8 @@ mod test_uniq_load { loaded_module, hashmap! { "withDefault" =>"Attr * (Attr (* | b | c) (Res (Attr b a) (Attr c err)), Attr b a -> Attr b a)", - "map" => "Attr * (Attr (* | c | d) (Res (Attr d a) (Attr c err)), Attr * (Attr d a -> Attr e b) -> Attr * (Res (Attr e b) (Attr c err)))", - "andThen" => "Attr * (Attr (* | c | d) (Res (Attr d a) (Attr c err)), Attr * (Attr d a -> Attr e (Res (Attr f b) (Attr c err))) -> Attr e (Res (Attr f b) (Attr c err)))" + "map" => "Attr * (Attr (* | c | d) (Res (Attr c a) (Attr d err)), Attr * (Attr c a -> Attr e b) -> Attr * (Res (Attr e b) (Attr d err)))", + "andThen" => "Attr * (Attr (* | c | d) (Res (Attr c a) (Attr d err)), Attr * (Attr c a -> Attr f (Res (Attr e b) (Attr d err))) -> Attr f (Res (Attr e b) (Attr d err)))", }, ); }); diff --git a/compiler/module/src/ident.rs b/compiler/module/src/ident.rs index 9dfcab2580..622ac062ac 100644 --- a/compiler/module/src/ident.rs +++ b/compiler/module/src/ident.rs @@ -51,14 +51,13 @@ impl TagName { impl ModuleName { // NOTE: After adding one of these, go to `impl ModuleId` and // add a corresponding ModuleId to there! - pub const FLOAT: &'static str = "Float"; pub const BOOL: &'static str = "Bool"; - pub const INT: &'static str = "Int"; pub const STR: &'static str = "Str"; + pub const NUM: &'static str = "Num"; pub const LIST: &'static str = "List"; pub const MAP: &'static str = "Map"; pub const SET: &'static str = "Set"; - pub const NUM: &'static str = "Num"; + pub const RESULT: &'static str = "Result"; pub fn as_str(&self) -> &str { &*self.0 diff --git a/compiler/module/src/lib.rs b/compiler/module/src/lib.rs index b8a460f173..4c837c4275 100644 --- a/compiler/module/src/lib.rs +++ b/compiler/module/src/lib.rs @@ -12,6 +12,7 @@ #![allow(clippy::large_enum_variant)] pub mod ident; +pub mod low_level; pub mod operator; pub mod symbol; diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs new file mode 100644 index 0000000000..e4de53d5f0 --- /dev/null +++ b/compiler/module/src/low_level.rs @@ -0,0 +1,36 @@ +/// Low-level operations that get translated directly into e.g. LLVM instructions. +/// These are always wrapped when exposed to end users, and can only make it +/// into an Expr when added directly by can::builtins +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum LowLevel { + ListLen, + ListGetUnsafe, + ListSet, + ListSetInPlace, + ListSingle, + ListRepeat, + ListReverse, + ListAppend, + ListPush, + NumAdd, + NumSub, + NumMul, + NumGt, + NumGte, + NumLt, + NumLte, + NumDivUnchecked, + NumRemUnchecked, + NumAbs, + NumNeg, + NumSin, + NumCos, + NumSqrtUnchecked, + NumRound, + NumToFloat, + Eq, + NotEq, + And, + Or, + Not, +} diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index d03f9801ba..152a8ee0f7 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -58,7 +58,7 @@ impl Symbol { .get_name(self.module_id()) .unwrap_or_else(|| { panic!( - "module_string could not find IdentIds for {:?} in interns {:?}", + "module_string could not find IdentIds for module {:?} in {:?}", self.module_id(), interns ) @@ -71,7 +71,7 @@ impl Symbol { .get(&self.module_id()) .unwrap_or_else(|| { panic!( - "ident_string could not find IdentIds for {:?} in interns {:?}", + "ident_string could not find IdentIds for module {:?} in {:?}", self.module_id(), interns ) @@ -177,7 +177,7 @@ lazy_static! { std::sync::Mutex::new(roc_collections::all::MutMap::default()); } -#[derive(Debug)] +#[derive(Debug, Default)] pub struct Interns { pub module_ids: ModuleIds, pub all_ident_ids: MutMap, @@ -575,91 +575,55 @@ define_builtins! { 0 ATTR: "#Attr" => { 0 UNDERSCORE: "_" // the _ used in pattern matches. This is Symbol 0. 1 ATTR_ATTR: "Attr" // the #Attr.Attr type alias, used in uniqueness types. + 2 ARG_1: "#arg1" + 3 ARG_2: "#arg2" + 4 ARG_3: "#arg3" + 5 ARG_4: "#arg4" + 6 ARG_5: "#arg5" + 7 ARG_6: "#arg6" + 8 ARG_7: "#arg7" + 9 ARG_8: "#arg8" } 1 NUM: "Num" => { 0 NUM_NUM: "Num" imported // the Num.Num type alias 1 NUM_AT_NUM: "@Num" // the Num.@Num private tag - 2 NUM_ABS: "abs" - 3 NUM_NEG: "neg" - 4 NUM_ADD: "add" - 5 NUM_SUB: "sub" - 6 NUM_MUL: "mul" - 7 NUM_LT: "isLt" - 8 NUM_LTE: "isLte" - 9 NUM_GT: "isGt" - 10 NUM_GTE: "isGte" - 11 NUM_TO_FLOAT: "toFloat" + 2 NUM_INT: "Int" imported // the Int.Int type alias + 3 NUM_INTEGER: "Integer" imported // Int : Num Integer + 4 NUM_AT_INTEGER: "@Integer" // the Int.@Integer private tag + 5 NUM_FLOAT: "Float" imported // the Float.Float type alias + 6 NUM_FLOATINGPOINT: "FloatingPoint" imported // Float : Num FloatingPoint + 7 NUM_AT_FLOATINGPOINT: "@FloatingPoint" // the Float.@FloatingPoint private tag + 8 NUM_MAX_INT: "maxInt" + 9 NUM_MIN_INT: "minInt" + 10 NUM_MAX_FLOAT: "maxFloat" + 11 NUM_MIN_FLOAT: "minFloat" + 12 NUM_ABS: "abs" + 13 NUM_NEG: "neg" + 14 NUM_ADD: "add" + 15 NUM_SUB: "sub" + 16 NUM_MUL: "mul" + 17 NUM_LT: "isLt" + 18 NUM_LTE: "isLte" + 19 NUM_GT: "isGt" + 20 NUM_GTE: "isGte" + 21 NUM_TO_FLOAT: "toFloat" + 22 NUM_SIN: "sin" + 23 NUM_COS: "cos" + 24 NUM_TAN: "tan" + 25 NUM_IS_ZERO: "isZero" + 26 NUM_IS_EVEN: "isEven" + 27 NUM_IS_ODD: "isOdd" + 28 NUM_IS_POSITIVE: "isPositive" + 29 NUM_IS_NEGATIVE: "isNegative" + 30 NUM_REM: "rem" + 31 NUM_DIV_FLOAT: "div" + 32 NUM_DIV_INT: "divFloor" + 33 NUM_MOD_INT: "modInt" + 34 NUM_MOD_FLOAT: "modFloat" + 35 NUM_SQRT: "sqrt" + 36 NUM_ROUND: "round" } - 2 INT: "Int" => { - 0 INT_INT: "Int" imported // the Int.Int type alias - 1 INT_INTEGER: "Integer" imported // Int : Num Integer - 2 INT_AT_INTEGER: "@Integer" // the Int.@Integer private tag - 3 INT_DIV: "div" - 4 INT_MOD: "mod" - 5 INT_HIGHEST: "highest" - 6 INT_LOWEST: "lowest" - 7 INT_ADD: "#add" - 8 INT_SUB: "#sub" - 9 INT_EQ_I64: "#eqi64" // Equality on 64-bit integers, the standard in Roc - 10 INT_EQ_I1: "#eqi1" // Equality on boolean (theoretically i1) values - 11 INT_EQ_I8: "#eqi8" // Equality on byte (theoretically i8) values - 12 INT_DIV_UNSAFE: "divUnsafe" // TODO remove once we can code gen Result - 13 INT_LT: "#lt" - 14 INT_LTE: "#lte" - 15 INT_GT: "#gt" - 16 INT_GTE: "#gte" - 17 INT_DIV_ARG_NUMERATOR: "div#numerator" // The first argument to `//`, the numerator - 18 INT_DIV_ARG_DENOMINATOR: "div#denominator" // The first argument to `//`, the denominator - 19 INT_NEQ_I64: "#neqi64" - 20 INT_NEQ_I1: "#neqi1" - 21 INT_NEQ_I8: "#neqi8" - 22 INT_ABS: "abs" - 23 INT_ABS_ARG: "abs#arg" - 24 INT_REM_UNSAFE: "remUnsafe" - 25 INT_REM: "rem" - 26 INT_REM_ARG_0: "rem#arg0" - 27 INT_REM_ARG_1: "rem#arg1" - 28 INT_IS_ODD: "isOdd" - 29 INT_IS_ODD_ARG: "isOdd#arg" - 30 INT_IS_EVEN: "isEven" - 31 INT_IS_EVEN_ARG: "isEven#arg" - 32 INT_IS_ZERO: "isZero" - 33 INT_IS_ZERO_ARG: "isZero#arg" - 34 INT_IS_POSITIVE: "isPositive" - 35 INT_IS_POSITIVE_ARG: "isPositive#arg" - 36 INT_IS_NEGATIVE: "isNegative" - 37 INT_IS_NEGATIVE_ARG: "isNegative#arg" - } - 3 FLOAT: "Float" => { - 0 FLOAT_FLOAT: "Float" imported // the Float.Float type alias - 1 FLOAT_FLOATINGPOINT: "FloatingPoint" imported // Float : Num FloatingPoint - 2 FLOAT_AT_FLOATINGPOINT: "@FloatingPoint" // the Float.@FloatingPoint private tag - 3 FLOAT_DIV: "div" - 4 FLOAT_MOD: "mod" - 5 FLOAT_SQRT: "sqrt" - 6 FLOAT_HIGHEST: "highest" - 7 FLOAT_LOWEST: "lowest" - 8 FLOAT_ADD: "#add" - 9 FLOAT_SUB: "#sub" - 10 FLOAT_EQ: "eq" - 11 FLOAT_ROUND: "round" - 12 FLOAT_LT: "#lt" - 13 FLOAT_LTE: "#lte" - 14 FLOAT_GT: "gt" - 15 FLOAT_GTE: "#gte" - 16 FLOAT_ABS: "abs" - 17 FLOAT_IS_POSITIVE: "isPositive" - 18 FLOAT_IS_POSITIVE_ARG: "isPositive#arg" - 19 FLOAT_IS_NEGATIVE: "isNegative" - 20 FLOAT_IS_NEGATIVE_ARG: "isNegative#arg" - 21 FLOAT_IS_ZERO: "isZero" - 22 FLOAT_IS_ZERO_ARG: "isZero#arg" - 23 FLOAT_SIN: "sin" - 24 FLOAT_COS: "cos" - 25 FLOAT_TAN: "tan" - 26 FLOAT_TAN_ARG: "tan#arg" - } - 4 BOOL: "Bool" => { + 2 BOOL: "Bool" => { 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias 1 BOOL_AND: "and" 2 BOOL_OR: "or" @@ -668,40 +632,35 @@ define_builtins! { 5 BOOL_EQ: "isEq" 6 BOOL_NEQ: "isNotEq" } - 5 STR: "Str" => { + 3 STR: "Str" => { 0 STR_STR: "Str" imported // the Str.Str type alias 1 STR_AT_STR: "@Str" // the Str.@Str private tag 2 STR_ISEMPTY: "isEmpty" 3 STR_APPEND: "append" } - 6 LIST: "List" => { + 4 LIST: "List" => { 0 LIST_LIST: "List" imported // the List.List type alias 1 LIST_AT_LIST: "@List" // the List.@List private tag 2 LIST_IS_EMPTY: "isEmpty" 3 LIST_GET: "get" - 4 LIST_GET_ARG_LIST: "get#list" - 5 LIST_GET_ARG_INDEX: "get#index" - 6 LIST_SET: "set" - 7 LIST_SET_IN_PLACE: "#setInPlace" - 8 LIST_PUSH: "push" - 9 LIST_MAP: "map" - 10 LIST_LEN: "len" - 11 LIST_FOLDL: "foldl" - 12 LIST_FOLDR: "foldr" - 13 LIST_GET_UNSAFE: "getUnsafe" - 14 LIST_CONCAT: "concat" - 15 LIST_FIRST: "first" - 16 LIST_FIRST_ARG: "first#list" - 17 LIST_SINGLE: "single" - 18 LIST_REPEAT: "repeat" - 19 LIST_REVERSE: "reverse" - 20 LIST_APPEND: "append" + 4 LIST_SET: "set" + 5 LIST_PUSH: "push" + 6 LIST_MAP: "map" + 7 LIST_LEN: "len" + 8 LIST_FOLDL: "foldl" + 9 LIST_FOLDR: "foldr" + 10 LIST_CONCAT: "concat" + 11 LIST_FIRST: "first" + 12 LIST_SINGLE: "single" + 13 LIST_REPEAT: "repeat" + 14 LIST_REVERSE: "reverse" + 15 LIST_APPEND: "append" } - 7 RESULT: "Result" => { + 5 RESULT: "Result" => { 0 RESULT_RESULT: "Result" imported // the Result.Result type alias 1 RESULT_MAP: "map" } - 8 MAP: "Map" => { + 6 MAP: "Map" => { 0 MAP_MAP: "Map" imported // the Map.Map type alias 1 MAP_AT_MAP: "@Map" // the Map.@Map private tag 2 MAP_EMPTY: "empty" @@ -709,7 +668,7 @@ define_builtins! { 4 MAP_GET: "get" 5 MAP_INSERT: "insert" } - 9 SET: "Set" => { + 7 SET: "Set" => { 0 SET_SET: "Set" imported // the Set.Set type alias 1 SET_AT_SET: "@Set" // the Set.@Set private tag 2 SET_EMPTY: "empty" @@ -721,5 +680,5 @@ define_builtins! { 8 SET_DIFF: "diff" } - num_modules: 10 // Keep this count up to date by hand! (Rust macros can't do arithmetic.) + num_modules: 8 // Keep this count up to date by hand! (TODO: see the mut_map! macro for how we could determine this count correctly in the macro) } diff --git a/compiler/mono/src/decision_tree.rs b/compiler/mono/src/decision_tree.rs index 57442daea6..a8a5b2324b 100644 --- a/compiler/mono/src/decision_tree.rs +++ b/compiler/mono/src/decision_tree.rs @@ -1,15 +1,14 @@ use crate::expr::Env; use crate::expr::Expr; use crate::expr::Pattern; -use bumpalo::Bump; -use roc_collections::all::{MutMap, MutSet}; -use roc_module::ident::TagName; -use roc_module::symbol::Symbol; - -use crate::expr::specialize_equality; use crate::layout::Builtin; use crate::layout::Layout; use crate::pattern::{Ctor, RenderAs, TagId, Union}; +use bumpalo::Bump; +use roc_collections::all::{MutMap, MutSet}; +use roc_module::ident::TagName; +use roc_module::low_level::LowLevel; +use roc_module::symbol::Symbol; /// COMPILE CASES @@ -1030,14 +1029,14 @@ fn test_to_equality<'a>( let lhs = Expr::Byte(test_byte); let rhs = path_to_expr(env, cond_symbol, &path, &cond_layout); - tests.push((lhs, rhs, Layout::Builtin(Builtin::Byte))); + tests.push((lhs, rhs, Layout::Builtin(Builtin::Int8))); } Test::IsBit(test_bit) => { let lhs = Expr::Bool(test_bit); let rhs = path_to_expr(env, cond_symbol, &path, &cond_layout); - tests.push((lhs, rhs, Layout::Builtin(Builtin::Bool))); + tests.push((lhs, rhs, Layout::Builtin(Builtin::Int1))); } Test::IsStr(test_str) => { @@ -1059,7 +1058,7 @@ fn test_to_equality<'a>( let lhs = Expr::Bool(true); let rhs = Expr::Store(stores, env.arena.alloc(expr)); - tests.push((lhs, rhs, Layout::Builtin(Builtin::Bool))); + tests.push((lhs, rhs, Layout::Builtin(Builtin::Int1))); } } } @@ -1122,9 +1121,9 @@ fn decide_to_branching<'a>( let condition = boolean_all(env.arena, tests); let branch_symbol = env.unique_symbol(); - let stores = [(branch_symbol, Layout::Builtin(Builtin::Bool), condition)]; + let stores = [(branch_symbol, Layout::Builtin(Builtin::Int1), condition)]; - let cond_layout = Layout::Builtin(Builtin::Bool); + let cond_layout = Layout::Builtin(Builtin::Int1); ( env.arena.alloc(stores), @@ -1203,16 +1202,18 @@ fn boolean_all<'a>(arena: &'a Bump, tests: Vec<(Expr<'a>, Expr<'a>, Layout<'a>)> let mut expr = Expr::Bool(true); for (lhs, rhs, layout) in tests.into_iter().rev() { - let test = specialize_equality(arena, lhs, rhs, layout.clone()); + let test = Expr::RunLowLevel( + LowLevel::Eq, + bumpalo::vec![in arena; (lhs, layout.clone()), (rhs, layout.clone())].into_bump_slice(), + ); - expr = Expr::CallByName { - name: Symbol::BOOL_AND, - layout, - args: arena.alloc([ - (test, Layout::Builtin(Builtin::Bool)), - (expr, Layout::Builtin(Builtin::Bool)), + expr = Expr::RunLowLevel( + LowLevel::And, + arena.alloc([ + (test, Layout::Builtin(Builtin::Int1)), + (expr, Layout::Builtin(Builtin::Int1)), ]), - }; + ); } expr diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 6e9bb3d8f8..c5551bf924 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -1,9 +1,10 @@ -use crate::layout::{Builtin, Layout, LayoutCache}; +use crate::layout::{list_layout_from_elem, Builtin, Layout, LayoutCache, LayoutProblem}; use crate::pattern::{Ctor, Guard, RenderAs, TagId}; use bumpalo::collections::Vec; use bumpalo::Bump; use roc_collections::all::{default_hasher, MutMap, MutSet}; use roc_module::ident::{Ident, Lowercase, TagName}; +use roc_module::low_level::LowLevel; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_region::all::{Located, Region}; use roc_types::subs::{Content, FlatType, Subs, Variable}; @@ -13,10 +14,17 @@ use std::hash::Hash; #[derive(Clone, Debug, PartialEq)] pub struct PartialProc<'a> { pub annotation: Variable, - pub patterns: Vec<'a, Symbol>, + pub pattern_symbols: Vec<'a, Symbol>, pub body: roc_can::expr::Expr, } +#[derive(Clone, Debug, PartialEq)] +pub struct PendingSpecialization<'a> { + pub fn_var: Variable, + pub ret_var: Variable, + pub pattern_vars: Vec<'a, Variable>, +} + #[derive(Clone, Debug, PartialEq)] pub struct Proc<'a> { pub name: Symbol, @@ -28,12 +36,9 @@ pub struct Proc<'a> { #[derive(Clone, Debug, PartialEq, Default)] pub struct Procs<'a> { - pub user_defined: MutMap>, + pub partial_procs: MutMap>, pub module_thunks: MutSet, - runtime_errors: MutSet, - specializations: MutMap, Proc<'a>>>, - pending_specializations: MutMap>, - builtin: MutSet, + pub pending_specializations: MutMap, PendingSpecialization<'a>>>, } impl<'a> Procs<'a> { @@ -46,14 +51,17 @@ impl<'a> Procs<'a> { loc_body: Located, ret_var: Variable, ) { - let (_, arg_symbols, body) = patterns_to_when(env, loc_args, ret_var, loc_body); + let (_, pattern_symbols, body) = patterns_to_when(env, loc_args, ret_var, loc_body); - // a named closure - self.user_defined.insert( + // a named closure. Since these aren't specialized by the surrounding + // context, we can't add pending specializations for them yet. + // (If we did, all named polymorphic functions would immediately error + // on trying to convert a flex var to a Layout.) + self.partial_procs.insert( name, PartialProc { annotation, - patterns: arg_symbols, + pattern_symbols, body: body.value, }, ); @@ -70,44 +78,64 @@ impl<'a> Procs<'a> { loc_body: Located, ret_var: Variable, layout_cache: &mut LayoutCache<'a>, - ) -> Result, ()> { - let (arg_vars, arg_symbols, body) = patterns_to_when(env, loc_args, ret_var, loc_body); + ) -> Layout<'a> { + let (pattern_vars, pattern_symbols, body) = + patterns_to_when(env, loc_args, ret_var, loc_body); // an anonymous closure. These will always be specialized already - // by the surrounding context + // by the surrounding context, so we can add pending specializations + // for them immediately. + let layout = layout_cache + .from_var(env.arena, annotation, env.subs, env.pointer_size) + .unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {:?}", err)); - match specialize_proc_body( - env, - self, - annotation, + let pending = PendingSpecialization { ret_var, + fn_var: annotation, + pattern_vars, + }; + + self.add_pending_specialization(symbol, layout.clone(), pending); + + debug_assert!(!self.partial_procs.contains_key(&symbol), "Procs was told to insert a value for symbol {:?}, but there was already an entry for that key! Procs should never attempt to insert duplicates.", symbol); + + self.partial_procs.insert( symbol, - &arg_vars, - &arg_symbols, - annotation, - body.value, - layout_cache, - ) { - Ok(proc) => { - let layout = layout_cache - .from_var(env.arena, annotation, env.subs, env.pointer_size) - .unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {:?}", err)); + PartialProc { + annotation, + pattern_symbols, + body: body.value, + }, + ); - self.insert_specialization(symbol, layout.clone(), proc); - - Ok(layout) - } - Err(()) => { - self.runtime_errors.insert(symbol); - - Err(()) - } - } + layout } - fn insert_specialization(&mut self, symbol: Symbol, layout: Layout<'a>, proc: Proc<'a>) { + fn add_pending_specialization( + &mut self, + symbol: Symbol, + layout: Layout<'a>, + pending: PendingSpecialization<'a>, + ) { + let all_pending = self + .pending_specializations + .entry(symbol) + .or_insert_with(|| HashMap::with_capacity_and_hasher(1, default_hasher())); + + all_pending.insert(layout, pending); + } +} + +#[derive(Default)] +pub struct Specializations<'a> { + by_symbol: MutMap, Proc<'a>>>, + runtime_errors: MutSet, +} + +impl<'a> Specializations<'a> { + pub fn insert(&mut self, symbol: Symbol, layout: Layout<'a>, proc: Proc<'a>) { let procs_by_layout = self - .specializations + .by_symbol .entry(symbol) .or_insert_with(|| HashMap::with_capacity_and_hasher(1, default_hasher())); @@ -117,16 +145,26 @@ impl<'a> Procs<'a> { !procs_by_layout.contains_key(&layout) || procs_by_layout.get(&layout) == Some(&proc) ); + // We shouldn't already have a runtime error recorded for this symbol + debug_assert!(!self.runtime_errors.contains(&symbol)); + procs_by_layout.insert(layout, proc); } - fn get_user_defined(&self, symbol: Symbol) -> Option<&PartialProc<'a>> { - self.user_defined.get(&symbol) + pub fn runtime_error(&mut self, symbol: Symbol) { + // We shouldn't already have a normal proc recorded for this symbol + debug_assert!(!self.by_symbol.contains_key(&symbol)); + + self.runtime_errors.insert(symbol); + } + + pub fn into_owned(self) -> (MutMap, Proc<'a>>>, MutSet) { + (self.by_symbol, self.runtime_errors) } pub fn len(&self) -> usize { let runtime_errors: usize = self.runtime_errors.len(); - let specializations: usize = self.specializations.len(); + let specializations: usize = self.by_symbol.len(); runtime_errors + specializations } @@ -134,26 +172,6 @@ impl<'a> Procs<'a> { pub fn is_empty(&self) -> bool { self.len() == 0 } - - fn insert_builtin(&mut self, symbol: Symbol) { - self.builtin.insert(symbol); - } - - pub fn into_map(self) -> (MutMap, Proc<'a>>>, MutSet) { - let mut specializations = self.specializations; - - for symbol in self.builtin.iter() { - // Builtins should only ever be stored as empty maps. - debug_assert!( - !specializations.contains_key(&symbol) - || specializations.get(&symbol).unwrap().is_empty() - ); - - specializations.insert(*symbol, MutMap::default()); - } - - (specializations, self.runtime_errors) - } } pub struct Env<'a, 'i> { @@ -207,6 +225,7 @@ pub enum Expr<'a> { args: &'a [(Expr<'a>, Layout<'a>)], }, CallByPointer(&'a Expr<'a>, &'a [Expr<'a>], Layout<'a>), + RunLowLevel(LowLevel, &'a [(Expr<'a>, Layout<'a>)]), // Exactly two conditional branches, e.g. if/else Cond { @@ -259,6 +278,7 @@ pub enum Expr<'a> { elem_layout: Layout<'a>, elems: &'a [Expr<'a>], }, + EmptyArray, RuntimeError(&'a str), } @@ -289,7 +309,7 @@ enum IntOrFloat { /// Given the `a` in `Num a`, determines whether it's an int or a float fn num_argument_to_int_or_float(subs: &Subs, var: Variable) -> IntOrFloat { match subs.get_without_compacting(var).content { - Content::Alias(Symbol::INT_INTEGER, args, _) => { + Content::Alias(Symbol::NUM_INTEGER, args, _) => { debug_assert!(args.is_empty()); IntOrFloat::IntType } @@ -297,7 +317,7 @@ fn num_argument_to_int_or_float(subs: &Subs, var: Variable) -> IntOrFloat { // If this was still a (Num *), assume compiling it to an Int IntOrFloat::IntType } - Content::Alias(Symbol::FLOAT_FLOATINGPOINT, args, _) => { + Content::Alias(Symbol::NUM_FLOATINGPOINT, args, _) => { debug_assert!(args.is_empty()); IntOrFloat::FloatType } @@ -316,34 +336,6 @@ fn num_argument_to_int_or_float(subs: &Subs, var: Variable) -> IntOrFloat { } } -/// Given a `Num a`, determines whether it's an int or a float -fn num_to_int_or_float(subs: &Subs, var: Variable) -> IntOrFloat { - match subs.get_without_compacting(var).content { - Content::Alias(Symbol::NUM_NUM, args, _) => { - debug_assert!(args.len() == 1); - - num_argument_to_int_or_float(subs, args[0].1) - } - - Content::Alias(Symbol::INT_INT, _, _) => IntOrFloat::IntType, - Content::Alias(Symbol::FLOAT_FLOAT, _, _) => IntOrFloat::FloatType, - - Content::Structure(FlatType::Apply(Symbol::ATTR_ATTR, attr_args)) => { - debug_assert!(attr_args.len() == 2); - - // Recurse on the second argument - num_to_int_or_float(subs, attr_args[1]) - } - - other => { - panic!( - "Input variable is not a Num, but {:?} is a {:?}", - var, other - ); - } - } -} - /// turn record/tag patterns into a when expression, e.g. /// /// foo = \{ x } -> body @@ -446,6 +438,12 @@ fn pattern_to_when<'a>( (env.unique_symbol(), Located::at_zero(RuntimeError(error))) } + MalformedPattern(problem, region) => { + // create the runtime error here, instead of delegating to When. + let error = roc_problem::can::RuntimeError::MalformedPattern(*problem, *region); + (env.unique_symbol(), Located::at_zero(RuntimeError(error))) + } + AppliedTag { .. } | RecordDestructure { .. } => { let symbol = env.unique_symbol(); @@ -491,9 +489,9 @@ fn from_can<'a>( Str(string) | BlockStr(string) => Expr::Str(env.arena.alloc(string)), Var(symbol) => { if procs.module_thunks.contains(&symbol) { - let partial_proc = procs.get_user_defined(symbol).unwrap(); + let partial_proc = procs.partial_procs.get(&symbol).unwrap(); let fn_var = partial_proc.annotation; - let ret_var = partial_proc.annotation; + let ret_var = fn_var; // These are the same for a thunk. // This is a top-level declaration, which will code gen to a 0-arity thunk. call_by_name( @@ -516,15 +514,26 @@ fn from_can<'a>( Closure(ann, name, _, loc_args, boxed_body) => { let (loc_body, ret_var) = *boxed_body; + let layout = + procs.insert_anonymous(env, name, ann, loc_args, loc_body, ret_var, layout_cache); - match procs.insert_anonymous(env, name, ann, loc_args, loc_body, ret_var, layout_cache) - { - Ok(layout) => Expr::FunctionPointer(name, layout), - Err(()) => { - // TODO make this message better - Expr::RuntimeErrorFunction("This function threw a runtime error.") - } + Expr::FunctionPointer(name, layout) + } + + RunLowLevel { op, args, .. } => { + let op = optimize_low_level(env.subs, op, &args); + let mut mono_args = Vec::with_capacity_in(args.len(), env.arena); + + for (arg_var, arg_expr) in args { + let arg = from_can(env, arg_expr, procs, layout_cache); + let layout = layout_cache + .from_var(env.arena, arg_var, env.subs, env.pointer_size) + .unwrap_or_else(|err| todo!("TODO turn fn_var into a RuntimeError {:?}", err)); + + mono_args.push((arg, layout)); } + + Expr::RunLowLevel(op, mono_args.into_bump_slice()) } Call(boxed, loc_args, _) => { @@ -534,70 +543,15 @@ fn from_can<'a>( Expr::Load(proc_name) => { // Some functions can potentially mutate in-place. // If we have one of those, switch to the in-place version if appropriate. - match specialize_builtin_functions( + call_by_name( env, - proc_name, - loc_args.as_slice(), + procs, + fn_var, ret_var, + proc_name, + loc_args, layout_cache, - ) { - Symbol::LIST_SET => { - let subs = &env.subs; - // The first arg is the one with the List in it. - // List.set : List elem, Int, elem -> List elem - let (list_arg_var, _) = loc_args.get(0).unwrap(); - - let content = subs.get_without_compacting(*list_arg_var).content; - - match content { - Content::Structure(FlatType::Apply( - Symbol::ATTR_ATTR, - attr_args, - )) => { - debug_assert!(attr_args.len() == 2); - - // If the first argument (the List) is unique, - // then we can safely upgrade to List.set_in_place - let attr_arg_content = - subs.get_without_compacting(attr_args[0]).content; - - let new_name = if attr_arg_content.is_unique(subs) { - Symbol::LIST_SET_IN_PLACE - } else { - Symbol::LIST_SET - }; - - call_by_name( - env, - procs, - fn_var, - ret_var, - new_name, - loc_args, - layout_cache, - ) - } - _ => call_by_name( - env, - procs, - fn_var, - ret_var, - proc_name, - loc_args, - layout_cache, - ), - } - } - specialized_proc_symbol => call_by_name( - env, - procs, - fn_var, - ret_var, - specialized_proc_symbol, - loc_args, - layout_cache, - ), - } + ) } ptr => { // Call by pointer - the closure was anonymous, e.g. @@ -651,6 +605,7 @@ fn from_can<'a>( final_else, } => { let mut expr = from_can(env, final_else.value, procs, layout_cache); + let arena = env.arena; let ret_layout = layout_cache .from_var(env.arena, branch_var, env.subs, env.pointer_size) @@ -675,8 +630,8 @@ fn from_can<'a>( }; expr = Expr::Store( - env.arena - .alloc(vec![(branch_symbol, Layout::Builtin(Builtin::Bool), cond)]), + bumpalo::vec![in arena; (branch_symbol, Layout::Builtin(Builtin::Int1), cond)] + .into_bump_slice(), env.arena.alloc(cond_expr), ); } @@ -691,16 +646,16 @@ fn from_can<'a>( } => { let arena = env.arena; - let btree = crate::layout::record_fields_btree( + let sorted_fields = crate::layout::sort_record_fields( env.arena, record_var, env.subs, env.pointer_size, ); - let mut field_tuples = Vec::with_capacity_in(btree.len(), arena); + let mut field_tuples = Vec::with_capacity_in(sorted_fields.len(), arena); - for (label, layout) in btree { + for (label, layout) in sorted_fields { let field = fields.remove(&label).unwrap(); let expr = from_can(env, field.loc_expr.value, procs, layout_cache); @@ -800,7 +755,7 @@ fn from_can<'a>( } => { let arena = env.arena; - let btree = crate::layout::record_fields_btree( + let sorted_fields = crate::layout::sort_record_fields( env.arena, record_var, env.subs, @@ -808,9 +763,9 @@ fn from_can<'a>( ); let mut index = None; - let mut field_layouts = Vec::with_capacity_in(btree.len(), env.arena); + let mut field_layouts = Vec::with_capacity_in(sorted_fields.len(), env.arena); - for (current, (label, field_layout)) in btree.into_iter().enumerate() { + for (current, (label, field_layout)) in sorted_fields.into_iter().enumerate() { field_layouts.push(field_layout); if label == field { @@ -834,28 +789,30 @@ fn from_can<'a>( } => { let arena = env.arena; let subs = &env.subs; - let elem_content = subs.get_without_compacting(elem_var).content; - let elem_layout = match elem_content { - // We have to special-case the empty list, because trying to - // compute a layout for an unbound var won't work. - Content::FlexVar(_) => Layout::Builtin(Builtin::EmptyList), - _ => match layout_cache.from_var(arena, elem_var, env.subs, env.pointer_size) { - Ok(layout) => layout.clone(), - Err(()) => { - panic!("TODO gracefully handle List with invalid element layout"); + + match list_layout_from_elem(arena, subs, elem_var, env.pointer_size) { + Ok(Layout::Builtin(Builtin::EmptyList)) => Expr::EmptyArray, + Ok(Layout::Builtin(Builtin::List(elem_layout))) => { + let mut elems = Vec::with_capacity_in(loc_elems.len(), arena); + + for loc_elem in loc_elems { + elems.push(from_can(env, loc_elem.value, procs, layout_cache)); } - }, - }; - let mut elems = Vec::with_capacity_in(loc_elems.len(), arena); - - for loc_elem in loc_elems { - elems.push(from_can(env, loc_elem.value, procs, layout_cache)); - } - - Expr::Array { - elem_layout, - elems: elems.into_bump_slice(), + Expr::Array { + elem_layout: elem_layout.clone(), + elems: elems.into_bump_slice(), + } + } + Ok(_) => { + unreachable!(); + } + Err(problem) => { + todo!( + "gracefully handle List with element layout problem: {:?}", + problem + ); + } } } Accessor { .. } => todo!("record accessor"), @@ -1337,99 +1294,45 @@ fn call_by_name<'a>( loc_args: std::vec::Vec<(Variable, Located)>, layout_cache: &mut LayoutCache<'a>, ) -> Expr<'a> { - // create specialized procedure to call - - // If we need to specialize the body, this will get populated with the info - // we need to do that. This is defined outside the procs.get_user_defined(...) call - // because if we tried to specialize the body inside that match, we would - // get a borrow checker error about trying to borrow `procs` as mutable - // while there is still an active immutable borrow. - #[allow(clippy::type_complexity)] - let opt_specialize_body: Option<(Variable, roc_can::expr::Expr, Vec<'a, Symbol>)>; - + // Register a pending_specialization for this function match layout_cache.from_var(env.arena, fn_var, env.subs, env.pointer_size) { Ok(layout) => { - match procs.get_user_defined(proc_name) { - Some(partial_proc) => { - match procs - .specializations - .get(&proc_name) - .and_then(|procs_by_layout| procs_by_layout.get(&layout)) - { - Some(_) => { - // a specialization with this layout already exists. - opt_specialize_body = None; - } - None => { - if procs.pending_specializations.get(&proc_name) == Some(&layout) { - // If we're already in the process of specializing this, don't - // try to specialize it further; otherwise, we'll loop forever. - opt_specialize_body = None; - } else { - opt_specialize_body = Some(( - partial_proc.annotation, - partial_proc.body.clone(), - partial_proc.patterns.clone(), - )); - } - } - } - } - None => { - opt_specialize_body = None; - - // This happens for built-in symbols (they are never defined as a Closure) - procs.insert_builtin(proc_name); - } - }; - - if let Some((annotation, body, loc_patterns)) = opt_specialize_body { - // register proc, so specialization doesn't loop infinitely - procs - .pending_specializations - .insert(proc_name, layout.clone()); - - let arg_vars = loc_args.iter().map(|v| v.0).collect::>(); - - match specialize_proc_body( - env, - procs, - fn_var, - ret_var, - proc_name, - &arg_vars, - &loc_patterns, - annotation, - body, - layout_cache, - ) { - Ok(proc) => { - procs.insert_specialization(proc_name, layout.clone(), proc); - } - Err(()) => { - procs.runtime_errors.insert(proc_name); - } - } - } - - // generate actual call - let mut args = Vec::with_capacity_in(loc_args.len(), env.arena); + // Build the CallByName node + let arena = env.arena; + let mut args = Vec::with_capacity_in(loc_args.len(), arena); + let mut pattern_vars = Vec::with_capacity_in(loc_args.len(), arena); for (var, loc_arg) in loc_args { - let layout = layout_cache - .from_var(&env.arena, var, &env.subs, env.pointer_size) - .unwrap_or_else(|err| panic!("TODO gracefully handle bad layout: {:?}", err)); + pattern_vars.push(var); - args.push((from_can(env, loc_arg.value, procs, layout_cache), layout)); + match layout_cache.from_var(&env.arena, var, &env.subs, env.pointer_size) { + Ok(layout) => { + args.push((from_can(env, loc_arg.value, procs, layout_cache), layout)); + } + Err(_) => { + // One of this function's arguments code gens to a runtime error, + // so attempting to call it will immediately crash. + return Expr::RuntimeError(""); + } + } } + let pending = PendingSpecialization { + pattern_vars, + ret_var, + fn_var, + }; + + // register the pending specialization, so this gets code genned later + procs.add_pending_specialization(proc_name, layout.clone(), pending); + Expr::CallByName { name: proc_name, layout, args: args.into_bump_slice(), } } - Err(()) => { + Err(_) => { // This function code gens to a runtime error, // so attempting to call it will immediately crash. Expr::RuntimeError("") @@ -1437,30 +1340,100 @@ fn call_by_name<'a>( } } -#[allow(clippy::too_many_arguments)] -fn specialize_proc_body<'a>( +pub fn specialize_all<'a>( + env: &mut Env<'a, '_>, + mut procs: Procs<'a>, + layout_cache: &mut LayoutCache<'a>, +) -> (MutMap<(Symbol, Layout<'a>), Proc<'a>>, MutSet) { + let mut answer = + HashMap::with_capacity_and_hasher(procs.pending_specializations.len(), default_hasher()); + let mut runtime_errors = MutSet::default(); + let mut is_finished = procs.pending_specializations.is_empty(); + + // TODO replace this synchronous loop with a work-stealing queue which + // processes each entry in pending_specializations in parallel, one + // module at a time (because the &mut env will need exclusive access to + // that module's IdentIds; the only reason Env is &mut in specialize is + // that we need to generate unique symbols and register them in them module's + // IdentIds). + while !is_finished { + let Procs { + partial_procs, + module_thunks, + mut pending_specializations, + } = procs; + + procs = Procs { + partial_procs, + module_thunks, + pending_specializations: MutMap::default(), + }; + + for (name, mut by_layout) in pending_specializations.drain() { + for (layout, pending) in by_layout.drain() { + // If we've already seen this (Symbol, Layout) combination before, + // don't try to specialize it again. If we do, we'll loop forever! + if !answer.contains_key(&(name, layout.clone())) { + // TODO should pending_procs hold a Rc? + let partial_proc = procs + .partial_procs + .get(&name) + .unwrap_or_else(|| panic!("Could not find partial_proc for {:?}", name)) + .clone(); + + match specialize(env, &mut procs, name, layout_cache, pending, partial_proc) { + Ok(proc) => { + answer.insert((name, layout), proc); + } + Err(_) => { + runtime_errors.insert(name); + } + } + } + } + } + + is_finished = procs.pending_specializations.is_empty(); + } + + (answer, runtime_errors) +} + +fn specialize<'a>( env: &mut Env<'a, '_>, procs: &mut Procs<'a>, - fn_var: Variable, - ret_var: Variable, proc_name: Symbol, - loc_args: &[Variable], - pattern_symbols: &[Symbol], - annotation: Variable, - body: roc_can::expr::Expr, layout_cache: &mut LayoutCache<'a>, -) -> Result, ()> { + pending: PendingSpecialization<'a>, + partial_proc: PartialProc<'a>, +) -> Result, LayoutProblem> { + let PendingSpecialization { + ret_var, + fn_var, + pattern_vars, + } = pending; + + let PartialProc { + annotation, + pattern_symbols, + body, + } = partial_proc; + // unify the called function with the specialized signature, then specialize the function body let snapshot = env.subs.snapshot(); let unified = roc_unify::unify::unify(env.subs, annotation, fn_var); + debug_assert!(matches!(unified, roc_unify::unify::Unified::Success(_))); + let specialized_body = from_can(env, body, procs, layout_cache); // reset subs, so we don't get type errors when specializing for a different signature env.subs.rollback_to(snapshot); - let mut proc_args = Vec::with_capacity_in(loc_args.len(), &env.arena); + let mut proc_args = Vec::with_capacity_in(pattern_vars.len(), &env.arena); - for (arg_var, arg_name) in loc_args.iter().zip(pattern_symbols.iter()) { + debug_assert!(pattern_vars.len() == pattern_symbols.len()); + + for (arg_var, arg_name) in pattern_vars.iter().zip(pattern_symbols.iter()) { let layout = layout_cache.from_var(&env.arena, *arg_var, env.subs, env.pointer_size)?; proc_args.push((layout, *arg_name)); @@ -1545,7 +1518,10 @@ fn from_can_pattern<'a>( StrLiteral(v) => Pattern::StrLiteral(v.clone()), Shadowed(region, ident) => Pattern::Shadowed(*region, ident.clone()), UnsupportedPattern(region) => Pattern::UnsupportedPattern(*region), - + MalformedPattern(_problem, region) => { + // TODO preserve malformed problem information here? + Pattern::UnsupportedPattern(*region) + } NumLiteral(var, num) => match num_argument_to_int_or_float(env.subs, *var) { IntOrFloat::IntType => Pattern::IntLiteral(*num), IntOrFloat::FloatType => Pattern::FloatLiteral(*num as u64), @@ -1708,16 +1684,16 @@ fn from_can_pattern<'a>( let mut it = destructs.iter(); let mut opt_destruct = it.next(); - let btree = crate::layout::record_fields_btree( + let sorted_fields = crate::layout::sort_record_fields( env.arena, *whole_var, env.subs, env.pointer_size, ); - let mut field_layouts = Vec::with_capacity_in(btree.len(), env.arena); + let mut field_layouts = Vec::with_capacity_in(sorted_fields.len(), env.arena); - for (label, field_layout) in btree.into_iter() { + for (label, field_layout) in sorted_fields.into_iter() { if let Some(destruct) = opt_destruct { if destruct.value.label == label { opt_destruct = it.next(); @@ -1772,101 +1748,40 @@ fn from_can_record_destruct<'a>( } } -pub fn specialize_equality<'a>( - arena: &'a Bump, - lhs: Expr<'a>, - rhs: Expr<'a>, - layout: Layout<'a>, -) -> Expr<'a> { - let name = match &layout { - Layout::Builtin(builtin) => match builtin { - Builtin::Int64 => Symbol::INT_EQ_I64, - Builtin::Float64 => Symbol::FLOAT_EQ, - Builtin::Byte => Symbol::INT_EQ_I8, - Builtin::Bool => Symbol::INT_EQ_I1, - other => todo!("Cannot yet compare for equality {:?}", other), - }, - other => todo!("Cannot yet compare for equality {:?}", other), - }; +/// Potentially translate LowLevel operations into more efficient ones based on +/// uniqueness type info. +/// +/// For example, turning LowLevel::ListSet to LowLevel::ListSetInPlace if the +/// list is Unique. +fn optimize_low_level( + subs: &Subs, + op: LowLevel, + args: &[(Variable, roc_can::expr::Expr)], +) -> LowLevel { + match op { + LowLevel::ListSet => { + // The first arg is the one with the List in it. + // List.set : List elem, Int, elem -> List elem + let list_arg_var = args[0].0; + let content = subs.get_without_compacting(list_arg_var).content; - Expr::CallByName { - name, - layout: layout.clone(), - args: arena.alloc([(lhs, layout.clone()), (rhs, layout)]), - } -} + match content { + Content::Structure(FlatType::Apply(Symbol::ATTR_ATTR, attr_args)) => { + debug_assert_eq!(attr_args.len(), 2); -fn specialize_builtin_functions<'a>( - env: &mut Env<'a, '_>, - symbol: Symbol, - loc_args: &[(Variable, Located)], - ret_var: Variable, - layout_cache: &mut LayoutCache<'a>, -) -> Symbol { - use IntOrFloat::*; + // If the first argument (the List) is unique, + // then we can safely upgrade to List.set_in_place + let attr_arg_content = subs.get_without_compacting(attr_args[0]).content; - if !symbol.is_builtin() { - // return unchanged - symbol - } else { - match symbol { - Symbol::NUM_ADD => match num_to_int_or_float(env.subs, ret_var) { - FloatType => Symbol::FLOAT_ADD, - IntType => Symbol::INT_ADD, - }, - Symbol::NUM_SUB => match num_to_int_or_float(env.subs, ret_var) { - FloatType => Symbol::FLOAT_SUB, - IntType => Symbol::INT_SUB, - }, - Symbol::NUM_LTE => match num_to_int_or_float(env.subs, loc_args[0].0) { - FloatType => Symbol::FLOAT_LTE, - IntType => Symbol::INT_LTE, - }, - Symbol::NUM_LT => match num_to_int_or_float(env.subs, loc_args[0].0) { - FloatType => Symbol::FLOAT_LT, - IntType => Symbol::INT_LT, - }, - Symbol::NUM_GTE => match num_to_int_or_float(env.subs, loc_args[0].0) { - FloatType => Symbol::FLOAT_GTE, - IntType => Symbol::INT_GTE, - }, - Symbol::NUM_GT => match num_to_int_or_float(env.subs, loc_args[0].0) { - FloatType => Symbol::FLOAT_GT, - IntType => Symbol::INT_GT, - }, - // TODO make this work for more than just int/float - Symbol::BOOL_EQ => { - match layout_cache.from_var(env.arena, loc_args[0].0, env.subs, env.pointer_size) { - Ok(Layout::Builtin(builtin)) => match builtin { - Builtin::Int64 => Symbol::INT_EQ_I64, - Builtin::Float64 => Symbol::FLOAT_EQ, - Builtin::Bool => Symbol::INT_EQ_I1, - Builtin::Byte => Symbol::INT_EQ_I8, - _ => panic!("Equality not implemented for {:?}", builtin), - }, - Ok(complex) => panic!( - "TODO support equality on complex layouts like {:?}", - complex - ), - Err(()) => panic!("Invalid layout"), + if attr_arg_content.is_unique(subs) { + LowLevel::ListSetInPlace + } else { + LowLevel::ListSet + } } + _ => op, } - Symbol::BOOL_NEQ => { - match layout_cache.from_var(env.arena, loc_args[0].0, env.subs, env.pointer_size) { - Ok(Layout::Builtin(builtin)) => match builtin { - Builtin::Int64 => Symbol::INT_NEQ_I64, - Builtin::Bool => Symbol::INT_NEQ_I1, - Builtin::Byte => Symbol::INT_NEQ_I8, - _ => panic!("Not-Equality not implemented for {:?}", builtin), - }, - Ok(complex) => panic!( - "TODO support equality on complex layouts like {:?}", - complex - ), - Err(()) => panic!("Invalid layout"), - } - } - _ => symbol, } + _ => op, } } diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 18a5a80764..40fef509b6 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -4,10 +4,18 @@ use roc_collections::all::MutMap; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; use roc_types::subs::{Content, FlatType, Subs, Variable}; -use std::collections::BTreeMap; pub const MAX_ENUM_SIZE: usize = (std::mem::size_of::() * 8) as usize; +/// If a (Num *) gets translated to a Layout, this is the numeric type it defaults to. +const DEFAULT_NUM_BUILTIN: Builtin<'_> = Builtin::Int64; + +#[derive(Debug, Clone)] +pub enum LayoutProblem { + UnresolvedTypeVar, + Erroneous, +} + /// Types for code gen must be monomorphic. No type variables allowed! #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum Layout<'a> { @@ -21,10 +29,16 @@ pub enum Layout<'a> { #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum Builtin<'a> { + Int128, Int64, + Int32, + Int16, + Int8, + Int1, + Float128, Float64, - Bool, - Byte, + Float32, + Float16, Str, Map(&'a Layout<'a>, &'a Layout<'a>), Set(&'a Layout<'a>), @@ -41,23 +55,18 @@ impl<'a> Layout<'a> { content: Content, subs: &Subs, pointer_size: u32, - ) -> Result { + ) -> Result { use roc_types::subs::Content::*; match content { - var @ FlexVar(_) | var @ RigidVar(_) => { - panic!( - "Layout::new encountered an unresolved {:?} - subs was {:?}", - var, subs - ); - } + FlexVar(_) | RigidVar(_) => Err(LayoutProblem::UnresolvedTypeVar), Structure(flat_type) => layout_from_flat_type(arena, flat_type, subs, pointer_size), - Alias(Symbol::INT_INT, args, _) => { + Alias(Symbol::NUM_INT, args, _) => { debug_assert!(args.is_empty()); Ok(Layout::Builtin(Builtin::Int64)) } - Alias(Symbol::FLOAT_FLOAT, args, _) => { + Alias(Symbol::NUM_FLOAT, args, _) => { debug_assert!(args.is_empty()); Ok(Layout::Builtin(Builtin::Float64)) } @@ -67,7 +76,7 @@ impl<'a> Layout<'a> { subs, pointer_size, ), - Error => Err(()), + Error => Err(LayoutProblem::Erroneous), } } @@ -79,7 +88,7 @@ impl<'a> Layout<'a> { var: Variable, subs: &Subs, pointer_size: u32, - ) -> Result { + ) -> Result { let content = subs.get_without_compacting(var).content; Self::new(arena, content, subs, pointer_size) @@ -140,7 +149,7 @@ impl<'a> Layout<'a> { /// Avoid recomputing Layout from Variable multiple times. #[derive(Default)] pub struct LayoutCache<'a> { - layouts: MutMap, ()>>, + layouts: MutMap, LayoutProblem>>, } impl<'a> LayoutCache<'a> { @@ -153,7 +162,7 @@ impl<'a> LayoutCache<'a> { var: Variable, subs: &Subs, pointer_size: u32, - ) -> Result, ()> { + ) -> Result, LayoutProblem> { // Store things according to the root Variable, to avoid duplicate work. let var = subs.get_root_key_without_compacting(var); @@ -169,10 +178,16 @@ impl<'a> LayoutCache<'a> { } impl<'a> Builtin<'a> { + const I128_SIZE: u32 = std::mem::size_of::() as u32; const I64_SIZE: u32 = std::mem::size_of::() as u32; + const I32_SIZE: u32 = std::mem::size_of::() as u32; + const I16_SIZE: u32 = std::mem::size_of::() as u32; + const I8_SIZE: u32 = std::mem::size_of::() as u32; + const I1_SIZE: u32 = std::mem::size_of::() as u32; + const F128_SIZE: u32 = 16; const F64_SIZE: u32 = std::mem::size_of::() as u32; - const BOOL_SIZE: u32 = std::mem::size_of::() as u32; - const BYTE_SIZE: u32 = std::mem::size_of::() as u32; + const F32_SIZE: u32 = std::mem::size_of::() as u32; + const F16_SIZE: u32 = 2; /// Number of machine words in an empty one of these pub const STR_WORDS: u32 = 2; @@ -180,7 +195,7 @@ impl<'a> Builtin<'a> { pub const SET_WORDS: u32 = Builtin::MAP_WORDS; // Set is an alias for Map with {} for value pub const LIST_WORDS: u32 = 2; - /// Layout of collection wrapper for List and Str - a struct of (pointre, length). + /// Layout of collection wrapper for List and Str - a struct of (pointer, length). /// /// We choose this layout (with pointer first) because it's how /// Rust slices are laid out, meaning we can cast to/from them for free. @@ -191,10 +206,16 @@ impl<'a> Builtin<'a> { use Builtin::*; match self { + Int128 => Builtin::I128_SIZE, Int64 => Builtin::I64_SIZE, + Int32 => Builtin::I32_SIZE, + Int16 => Builtin::I16_SIZE, + Int8 => Builtin::I8_SIZE, + Int1 => Builtin::I1_SIZE, + Float128 => Builtin::F128_SIZE, Float64 => Builtin::F64_SIZE, - Bool => Builtin::BOOL_SIZE, - Byte => Builtin::BYTE_SIZE, + Float32 => Builtin::F32_SIZE, + Float16 => Builtin::F16_SIZE, Str | EmptyStr => Builtin::STR_WORDS * pointer_size, Map(_, _) | EmptyMap => Builtin::MAP_WORDS * pointer_size, Set(_) | EmptySet => Builtin::SET_WORDS * pointer_size, @@ -206,7 +227,8 @@ impl<'a> Builtin<'a> { use Builtin::*; match self { - Int64 | Float64 | Bool | Byte | EmptyStr | EmptyMap | EmptyList | EmptySet => true, + Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Float128 | Float64 | Float32 + | Float16 | EmptyStr | EmptyMap | EmptyList | EmptySet => true, Str | Map(_, _) | Set(_) | List(_) => false, } } @@ -217,23 +239,23 @@ fn layout_from_flat_type<'a>( flat_type: FlatType, subs: &Subs, pointer_size: u32, -) -> Result, ()> { +) -> Result, LayoutProblem> { use roc_types::subs::FlatType::*; match flat_type { Apply(symbol, args) => { match symbol { - Symbol::INT_INT => { - debug_assert!(args.is_empty()); + Symbol::NUM_INT => { + debug_assert_eq!(args.len(), 0); Ok(Layout::Builtin(Builtin::Int64)) } - Symbol::FLOAT_FLOAT => { - debug_assert!(args.is_empty()); + Symbol::NUM_FLOAT => { + debug_assert_eq!(args.len(), 0); Ok(Layout::Builtin(Builtin::Float64)) } - Symbol::NUM_NUM => { + Symbol::NUM_NUM | Symbol::NUM_AT_NUM => { // Num.Num should only ever have 1 argument, e.g. Num.Num Int.Integer - debug_assert!(args.len() == 1); + debug_assert_eq!(args.len(), 1); let var = args.iter().next().unwrap(); let content = subs.get_without_compacting(*var).content; @@ -241,20 +263,9 @@ fn layout_from_flat_type<'a>( layout_from_num_content(content) } Symbol::STR_STR => Ok(Layout::Builtin(Builtin::Str)), - Symbol::LIST_LIST => { - use roc_types::subs::Content::*; - - match subs.get_without_compacting(args[0]).content { - FlexVar(_) | RigidVar(_) => Ok(Layout::Builtin(Builtin::EmptyList)), - content => { - let elem_layout = Layout::new(arena, content, subs, pointer_size)?; - - Ok(Layout::Builtin(Builtin::List(arena.alloc(elem_layout)))) - } - } - } + Symbol::LIST_LIST => list_layout_from_elem(arena, subs, args[0], pointer_size), Symbol::ATTR_ATTR => { - debug_assert!(args.len() == 2); + debug_assert_eq!(args.len(), 2); // The first argument is the uniqueness info; // that doesn't affect layout, so we don't need it here. @@ -290,26 +301,44 @@ fn layout_from_flat_type<'a>( Record(fields, ext_var) => { debug_assert!(ext_var_is_empty_record(subs, ext_var)); - let btree = fields - .into_iter() - .collect::>(); + // Sort the fields by label + let mut sorted_fields = Vec::with_capacity_in(fields.len(), arena); - let mut layouts = Vec::with_capacity_in(btree.len(), arena); + for tuple in fields { + sorted_fields.push(tuple); + } + + sorted_fields.sort_by(|(label1, _), (label2, _)| label1.cmp(label2)); + + // Determine the layouts of the fields, maintaining sort order + let mut layouts = Vec::with_capacity_in(sorted_fields.len(), arena); + + for (_, field_var) in sorted_fields { + use LayoutProblem::*; - for (_, field_var) in btree { let field_content = subs.get_without_compacting(field_var).content; - let field_layout = match Layout::new(arena, field_content, subs, pointer_size) { - Ok(layout) => layout, - Err(()) => { + + match Layout::new(arena, field_content, subs, pointer_size) { + Ok(layout) => { + // Drop any zero-sized fields like {} + if layout.stack_size(pointer_size) != 0 { + layouts.push(layout); + } + } + Err(UnresolvedTypeVar) | Err(Erroneous) => { // Invalid field! panic!("TODO gracefully handle record with invalid field.var"); } - }; - - layouts.push(field_layout); + } } - Ok(Layout::Struct(layouts.into_bump_slice())) + if layouts.len() == 1 { + // If the record has only one field that isn't zero-sized, + // unwrap it. + Ok(layouts.pop().unwrap()) + } else { + Ok(Layout::Struct(layouts.into_bump_slice())) + } } TagUnion(tags, ext_var) => { debug_assert!(ext_var_is_empty_tag_union(subs, ext_var)); @@ -325,36 +354,43 @@ fn layout_from_flat_type<'a>( Boolean(_) => { panic!("TODO make Layout for Boolean"); } - Erroneous(_) => Err(()), + Erroneous(_) => Err(LayoutProblem::Erroneous), EmptyRecord => Ok(Layout::Struct(&[])), } } -pub fn record_fields_btree<'a>( +pub fn sort_record_fields<'a>( arena: &'a Bump, var: Variable, subs: &Subs, pointer_size: u32, -) -> BTreeMap> { +) -> Vec<'a, (Lowercase, Layout<'a>)> { let mut fields_map = MutMap::default(); + match roc_types::pretty_print::chase_ext_record(subs, var, &mut fields_map) { Ok(()) | Err((_, Content::FlexVar(_))) => { - // collect into btreemap to sort - fields_map - .into_iter() - .map(|(label, var)| { - ( - label, - Layout::from_var(arena, var, subs, pointer_size) - .expect("invalid layout from var"), - ) - }) - .collect::>>() + // Sort the fields by label + let mut sorted_fields = Vec::with_capacity_in(fields_map.len(), arena); + + for (label, var) in fields_map { + let layout = Layout::from_var(arena, var, subs, pointer_size) + .expect("invalid layout from var"); + + // Drop any zero-sized fields like {} + if layout.stack_size(pointer_size) != 0 { + sorted_fields.push((label, layout)); + } + } + + sorted_fields.sort_by(|(label1, _), (label2, _)| label1.cmp(label2)); + + sorted_fields } Err(other) => panic!("invalid content in record variable: {:?}", other), } } +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum UnionVariant<'a> { Never, Unit, @@ -375,7 +411,7 @@ pub fn union_sorted_tags<'a>( Ok(()) | Err((_, Content::FlexVar(_))) => { union_sorted_tags_help(arena, tags_vec, subs, pointer_size) } - Err(other) => panic!("invalid content in record variable: {:?}", other), + Err(other) => panic!("invalid content in tag union variable: {:?}", other), } } @@ -385,10 +421,7 @@ fn union_sorted_tags_help<'a>( subs: &Subs, pointer_size: u32, ) -> UnionVariant<'a> { - // for this union be be an enum, none of the tags may have any arguments - let has_no_arguments = tags_vec.iter().all(|(_, args)| args.is_empty()); - - // sort up-front, make sure the ordering stays intact! + // sort up front; make sure the ordering stays intact! tags_vec.sort(); match tags_vec.len() { @@ -396,72 +429,104 @@ fn union_sorted_tags_help<'a>( // trying to instantiate a type with no values UnionVariant::Never } - 1 if has_no_arguments => { - // a unit type - UnionVariant::Unit - } - 2 if has_no_arguments => { - // type can be stored in a boolean - - // tags_vec is sorted, - let ttrue = tags_vec.remove(1).0; - let ffalse = tags_vec.remove(0).0; - - UnionVariant::BoolUnion { ffalse, ttrue } - } - 3..=MAX_ENUM_SIZE if has_no_arguments => { - // type can be stored in a byte - // needs the sorted tag names to determine the tag_id - let mut tag_names = Vec::with_capacity_in(tags_vec.len(), arena); - - for (label, _) in tags_vec { - tag_names.push(label); - } - - UnionVariant::ByteUnion(tag_names) - } 1 => { - // special-case NUM_AT_NUM: if its argument is a FlexVar, make it Int let (tag_name, arguments) = tags_vec.remove(0); // just one tag in the union (but with arguments) can be a struct let mut layouts = Vec::with_capacity_in(tags_vec.len(), arena); + // special-case NUM_AT_NUM: if its argument is a FlexVar, make it Int match tag_name { TagName::Private(Symbol::NUM_AT_NUM) => { layouts.push(unwrap_num_tag(subs, arguments[0]).expect("invalid num layout")); } _ => { - for var in arguments.iter() { - let layout = Layout::from_var(arena, *var, subs, pointer_size) - .expect("invalid layout from var"); - layouts.push(layout); + for var in arguments { + match Layout::from_var(arena, var, subs, pointer_size) { + Ok(layout) => { + // Drop any zero-sized arguments like {} + if layout.stack_size(pointer_size) != 0 { + layouts.push(layout); + } + } + Err(LayoutProblem::UnresolvedTypeVar) => { + // If we encounter an unbound type var (e.g. `Ok *`) + // then it's zero-sized; drop the argument. + } + Err(LayoutProblem::Erroneous) => { + // An erroneous type var will code gen to a runtime + // error, so we don't need to store any data for it. + } + } } } } - UnionVariant::Unwrapped(layouts) - } - _ => { + if layouts.is_empty() { + UnionVariant::Unit + } else { + UnionVariant::Unwrapped(layouts) + } + } + num_tags => { // default path - let mut result = Vec::with_capacity_in(tags_vec.len(), arena); + let mut answer = Vec::with_capacity_in(tags_vec.len(), arena); + let mut has_any_arguments = false; for (tag_name, arguments) in tags_vec { - // resverse space for the tag discriminant + // reserve space for the tag discriminant let mut arg_layouts = Vec::with_capacity_in(arguments.len() + 1, arena); - // add the tag discriminant + // add the tag discriminant (size currently always hardcoded to i64) arg_layouts.push(Layout::Builtin(Builtin::Int64)); for var in arguments { - let layout = Layout::from_var(arena, var, subs, pointer_size) - .expect("invalid layout from var"); - arg_layouts.push(layout); + match Layout::from_var(arena, var, subs, pointer_size) { + Ok(layout) => { + // Drop any zero-sized arguments like {} + if layout.stack_size(pointer_size) != 0 { + has_any_arguments = true; + + arg_layouts.push(layout); + } + } + Err(LayoutProblem::UnresolvedTypeVar) => { + // If we encounter an unbound type var (e.g. `Ok *`) + // then it's zero-sized; drop the argument. + } + Err(LayoutProblem::Erroneous) => { + // An erroneous type var will code gen to a runtime + // error, so we don't need to store any data for it. + } + } } - result.push((tag_name, arg_layouts.into_bump_slice())); + answer.push((tag_name, arg_layouts.into_bump_slice())); + } + + match num_tags { + 2 if !has_any_arguments => { + // type can be stored in a boolean + + // tags_vec is sorted, and answer is sorted the same way + let ttrue = answer.remove(1).0; + let ffalse = answer.remove(0).0; + + UnionVariant::BoolUnion { ffalse, ttrue } + } + 3..=MAX_ENUM_SIZE if !has_any_arguments => { + // type can be stored in a byte + // needs the sorted tag names to determine the tag_id + let mut tag_names = Vec::with_capacity_in(answer.len(), arena); + + for (tag_name, _) in answer { + tag_names.push(tag_name); + } + + UnionVariant::ByteUnion(tag_names) + } + _ => UnionVariant::Wrapped(answer), } - UnionVariant::Wrapped(result) } } } @@ -475,35 +540,43 @@ pub fn layout_from_tag_union<'a>( use UnionVariant::*; let tags_vec: std::vec::Vec<_> = tags.into_iter().collect(); - let first_tag = tags_vec[0].clone(); - let variant = union_sorted_tags_help(arena, tags_vec, subs, pointer_size); - match variant { - Never => panic!("TODO gracefully handle trying to instantiate Never"), - Unit => Layout::Struct(&[]), - BoolUnion { .. } => Layout::Builtin(Builtin::Bool), - ByteUnion(_) => Layout::Builtin(Builtin::Byte), - Unwrapped(field_layouts) => match first_tag.0 { - TagName::Private(Symbol::NUM_AT_NUM) => { - let arguments = first_tag.1; - debug_assert!(arguments.len() == 1); - let var = arguments.iter().next().unwrap(); + if tags_vec[0].0 != TagName::Private(Symbol::NUM_AT_NUM) { + let variant = union_sorted_tags_help(arena, tags_vec, subs, pointer_size); - unwrap_num_tag(subs, *var).expect("invalid Num argument") + match variant { + Never => panic!("TODO gracefully handle trying to instantiate Never"), + Unit => Layout::Struct(&[]), + BoolUnion { .. } => Layout::Builtin(Builtin::Int1), + ByteUnion(_) => Layout::Builtin(Builtin::Int8), + Unwrapped(mut field_layouts) => { + if field_layouts.len() == 1 { + field_layouts.pop().unwrap() + } else { + Layout::Struct(field_layouts.into_bump_slice()) + } } - _ => Layout::Struct(field_layouts.into_bump_slice()), - }, - Wrapped(tags) => { - let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena); + Wrapped(tags) => { + let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena); - for (_, tag_layout) in tags { - tag_layouts.push(tag_layout); + for (_, tag_layout) in tags { + tag_layouts.push(tag_layout); + } + Layout::Union(tag_layouts.into_bump_slice()) } - Layout::Union(tag_layouts.into_bump_slice()) } + } else { + let arguments = &tags_vec[0].1; + + debug_assert_eq!(arguments.len(), 1); + + let var = arguments.iter().next().unwrap(); + + unwrap_num_tag(subs, *var).expect("invalid Num argument") } } +#[cfg(debug_assertions)] fn ext_var_is_empty_tag_union(subs: &Subs, ext_var: Variable) -> bool { // the ext_var is empty let mut ext_fields = std::vec::Vec::new(); @@ -513,6 +586,13 @@ fn ext_var_is_empty_tag_union(subs: &Subs, ext_var: Variable) -> bool { } } +#[cfg(not(debug_assertions))] +fn ext_var_is_empty_tag_union(_: &Subs, _: Variable) -> bool { + // This should only ever be used in debug_assert! macros + unreachable!(); +} + +#[cfg(debug_assertions)] fn ext_var_is_empty_record(subs: &Subs, ext_var: Variable) -> bool { // the ext_var is empty let mut ext_fields = MutMap::default(); @@ -522,20 +602,27 @@ fn ext_var_is_empty_record(subs: &Subs, ext_var: Variable) -> bool { } } -fn layout_from_num_content<'a>(content: Content) -> Result, ()> { +#[cfg(not(debug_assertions))] +fn ext_var_is_empty_record(_: &Subs, _: Variable) -> bool { + // This should only ever be used in debug_assert! macros + unreachable!(); +} + +fn layout_from_num_content<'a>(content: Content) -> Result, LayoutProblem> { use roc_types::subs::Content::*; use roc_types::subs::FlatType::*; match content { - var @ FlexVar(_) | var @ RigidVar(_) => { - panic!( - "Layout::from_num_content encountered an unresolved {:?}", - var - ); + FlexVar(_) | RigidVar(_) => { + // If a Num makes it all the way through type checking with an unbound + // type variable, then assume it's a 64-bit integer. + // + // (e.g. for (5 + 5) assume both 5s are 64-bit integers.) + Ok(Layout::Builtin(DEFAULT_NUM_BUILTIN)) } Structure(Apply(symbol, args)) => match symbol { - Symbol::INT_INTEGER => Ok(Layout::Builtin(Builtin::Int64)), - Symbol::FLOAT_FLOATINGPOINT => Ok(Layout::Builtin(Builtin::Float64)), + Symbol::NUM_INTEGER => Ok(Layout::Builtin(Builtin::Int64)), + Symbol::NUM_FLOATINGPOINT => Ok(Layout::Builtin(Builtin::Float64)), _ => { panic!( "Invalid Num.Num type application: {:?}", @@ -543,44 +630,71 @@ fn layout_from_num_content<'a>(content: Content) -> Result, ()> { ); } }, + Alias(_, _, _) => { + todo!("TODO recursively resolve type aliases in num_from_content"); + } Structure(_) => { panic!("Invalid Num.Num type application: {:?}", content); } - Alias(_, _, _) => { - panic!("TODO recursively resolve type aliases in num_from_content"); - } - Error => Err(()), + Error => Err(LayoutProblem::Erroneous), } } -fn unwrap_num_tag<'a>(subs: &Subs, var: Variable) -> Result, ()> { +fn unwrap_num_tag<'a>(subs: &Subs, var: Variable) -> Result, LayoutProblem> { match subs.get_without_compacting(var).content { Content::Structure(flat_type) => match flat_type { FlatType::Apply(Symbol::ATTR_ATTR, args) => { - debug_assert!(args.len() == 2); + debug_assert_eq!(args.len(), 2); let arg_var = args.get(1).unwrap(); unwrap_num_tag(subs, *arg_var) } _ => { - panic!("TODO handle Num.@Num flat_type {:?}", flat_type); + todo!("TODO handle Num.@Num flat_type {:?}", flat_type); } }, - Content::Alias(Symbol::INT_INTEGER, args, _) => { + Content::Alias(Symbol::NUM_INTEGER, args, _) => { debug_assert!(args.is_empty()); Ok(Layout::Builtin(Builtin::Int64)) } - Content::Alias(Symbol::FLOAT_FLOATINGPOINT, args, _) => { + Content::Alias(Symbol::NUM_FLOATINGPOINT, args, _) => { debug_assert!(args.is_empty()); Ok(Layout::Builtin(Builtin::Float64)) } - Content::FlexVar(_) => { + Content::FlexVar(_) | Content::RigidVar(_) => { // If this was still a (Num *) then default to compiling it to i64 - Ok(Layout::Builtin(Builtin::Int64)) + Ok(Layout::Builtin(DEFAULT_NUM_BUILTIN)) } other => { - panic!("TODO non structure Num.@Num flat_type {:?}", other); + todo!("TODO non structure Num.@Num flat_type {:?}", other); + } + } +} + +pub fn list_layout_from_elem<'a>( + arena: &'a Bump, + subs: &Subs, + var: Variable, + pointer_size: u32, +) -> Result, LayoutProblem> { + match subs.get_without_compacting(var).content { + Content::Structure(FlatType::Apply(Symbol::ATTR_ATTR, args)) => { + debug_assert_eq!(args.len(), 2); + + let arg_var = args.get(1).unwrap(); + + list_layout_from_elem(arena, subs, *arg_var, pointer_size) + } + Content::FlexVar(_) | Content::RigidVar(_) => { + // If this was still a (List *) then it must have been an empty list + Ok(Layout::Builtin(Builtin::EmptyList)) + } + content => { + let elem_layout = Layout::new(arena, content, subs, pointer_size)?; + + // This is a normal list. + Ok(Layout::Builtin(Builtin::List(arena.alloc(elem_layout)))) } } } diff --git a/compiler/mono/tests/helpers/mod.rs b/compiler/mono/tests/helpers/mod.rs index 199f019f48..4a50b4f422 100644 --- a/compiler/mono/tests/helpers/mod.rs +++ b/compiler/mono/tests/helpers/mod.rs @@ -46,25 +46,6 @@ pub fn infer_expr( (content, solved.into_inner()) } -/// Used in the with_larger_debug_stack() function, for tests that otherwise -/// run out of stack space in debug builds (but don't in --release builds) -#[allow(dead_code)] -const EXPANDED_STACK_SIZE: usize = 4 * 1024 * 1024; - -/// In --release builds, don't increase the stack size. Run the test normally. -/// This way, we find out if any of our tests are blowing the stack even after -/// optimizations in release builds. -#[cfg(not(debug_assertions))] -#[inline(always)] -pub fn with_larger_debug_stack(run_test: F) -where - F: FnOnce() -> (), - F: Send, - F: 'static, -{ - run_test() -} - #[allow(dead_code)] pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result, Fail> { parse_loc_with(arena, input).map(|loc_expr| loc_expr.value) @@ -217,6 +198,33 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut &loc_expr.value, ); + // Add the builtins' defs. + let mut with_builtins = loc_expr.value; + + // Add builtin defs (e.g. List.get) directly to the canonical Expr, + // since we aren't using modules here. + let builtin_defs = roc_can::builtins::builtin_defs(&mut var_store); + + for (symbol, def) in builtin_defs { + if output.references.lookups.contains(&symbol) || output.references.calls.contains(&symbol) + { + with_builtins = roc_can::expr::Expr::LetNonRec( + Box::new(def), + Box::new(Located { + region: Region::zero(), + value: with_builtins, + }), + var_store.fresh(), + SendMap::default(), + ); + } + } + + let loc_expr = Located { + region: loc_expr.region, + value: with_builtins, + }; + let constraint = constrain_expr( &roc_constrain::expr::Env { rigids: ImMap::default(), diff --git a/compiler/mono/tests/test_mono.rs b/compiler/mono/tests/test_mono.rs index b775a7f4ac..97f4df56fb 100644 --- a/compiler/mono/tests/test_mono.rs +++ b/compiler/mono/tests/test_mono.rs @@ -1,7 +1,5 @@ #[macro_use] extern crate pretty_assertions; -// #[macro_use] -// extern crate indoc; extern crate bumpalo; extern crate roc_mono; @@ -17,11 +15,14 @@ mod test_mono { use roc_mono::expr::Expr::{self, *}; use roc_mono::expr::Procs; use roc_mono::layout; - use roc_mono::layout::{Builtin, Layout}; + use roc_mono::layout::{Builtin, Layout, LayoutCache}; use roc_types::subs::Subs; // HELPERS + const I64_LAYOUT: Layout<'static> = Layout::Builtin(Builtin::Int64); + const F64_LAYOUT: Layout<'static> = Layout::Builtin(Builtin::Float64); + fn compiles_to(src: &str, expected: Expr<'_>) { compiles_to_with_interns(src, |_| expected) } @@ -40,6 +41,7 @@ mod test_mono { mut interns, .. } = can_expr(src); + let subs = Subs::new(var_store.into()); let mut unify_problems = Vec::new(); let (_content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var); @@ -64,6 +66,11 @@ mod test_mono { }; let mono_expr = Expr::new(&mut mono_env, loc_expr.value, &mut procs); + let (_, runtime_errors) = + roc_mono::expr::specialize_all(&mut mono_env, procs, &mut LayoutCache::default()); + + assert_eq!(runtime_errors, roc_collections::all::MutSet::default()); + // Put this module's ident_ids back in the interns interns.all_ident_ids.insert(home, ident_ids); @@ -85,7 +92,7 @@ mod test_mono { compiles_to( "3.0 + 4", CallByName { - name: Symbol::FLOAT_ADD, + name: Symbol::NUM_ADD, layout: Layout::FunctionPointer( &[ Layout::Builtin(Builtin::Float64), @@ -106,7 +113,7 @@ mod test_mono { compiles_to( "0xDEADBEEF + 4", CallByName { - name: Symbol::INT_ADD, + name: Symbol::NUM_ADD, layout: Layout::FunctionPointer( &[ Layout::Builtin(Builtin::Int64), @@ -128,7 +135,7 @@ mod test_mono { compiles_to( "3 + 5", CallByName { - name: Symbol::INT_ADD, + name: Symbol::NUM_ADD, layout: Layout::FunctionPointer( &[ Layout::Builtin(Builtin::Int64), @@ -201,13 +208,13 @@ mod test_mono { Store( &[( gen_symbol_0, - Layout::Builtin(layout::Builtin::Bool), + Layout::Builtin(layout::Builtin::Int1), Expr::Bool(true), )], &Cond { cond_symbol: gen_symbol_0, branch_symbol: gen_symbol_0, - cond_layout: Builtin(Bool), + cond_layout: Builtin(Int1), pass: (&[] as &[_], &Expr::Str("bar")), fail: (&[] as &[_], &Expr::Str("foo")), ret_layout: Builtin(Str), @@ -239,26 +246,26 @@ mod test_mono { Store( &[( gen_symbol_0, - Layout::Builtin(layout::Builtin::Bool), + Layout::Builtin(layout::Builtin::Int1), Expr::Bool(true), )], &Cond { cond_symbol: gen_symbol_0, branch_symbol: gen_symbol_0, - cond_layout: Builtin(Bool), + cond_layout: Builtin(Int1), pass: (&[] as &[_], &Expr::Str("bar")), fail: ( &[] as &[_], &Store( &[( gen_symbol_1, - Layout::Builtin(layout::Builtin::Bool), + Layout::Builtin(layout::Builtin::Int1), Expr::Bool(false), )], &Cond { cond_symbol: gen_symbol_1, branch_symbol: gen_symbol_1, - cond_layout: Builtin(Bool), + cond_layout: Builtin(Int1), pass: (&[] as &[_], &Expr::Str("foo")), fail: (&[] as &[_], &Expr::Str("baz")), ret_layout: Builtin(Str), @@ -297,13 +304,13 @@ mod test_mono { Store( &[( gen_symbol_0, - Layout::Builtin(layout::Builtin::Bool), + Layout::Builtin(layout::Builtin::Int1), Expr::Bool(true), )], &Cond { cond_symbol: gen_symbol_0, branch_symbol: gen_symbol_0, - cond_layout: Builtin(Bool), + cond_layout: Builtin(Int1), pass: (&[] as &[_], &Expr::Str("bar")), fail: (&[] as &[_], &Expr::Str("foo")), ret_layout: Builtin(Str), @@ -340,41 +347,78 @@ mod test_mono { fn polymorphic_identity() { compiles_to( r#" - id = \x -> x + id = \x -> x - id { x: id 0x4 } + id { x: id 0x4, y: 0.1 } "#, { - use self::Builtin::*; let home = test_home(); let gen_symbol_0 = Interns::from_index(home, 0); + let struct_layout = Layout::Struct(&[I64_LAYOUT, F64_LAYOUT]); CallByName { name: gen_symbol_0, layout: Layout::FunctionPointer( - &[Layout::Struct(&[Layout::Builtin(Builtin::Int64)])], - &Layout::Struct(&[Layout::Builtin(Builtin::Int64)]), + &[struct_layout.clone()], + &struct_layout.clone(), ), args: &[( - Struct(&[( - CallByName { - name: gen_symbol_0, - layout: Layout::FunctionPointer( - &[Layout::Builtin(Builtin::Int64)], - &Layout::Builtin(Builtin::Int64), - ), - args: &[(Int(4), Layout::Builtin(Int64))], - }, - Layout::Builtin(Int64), - )]), - Layout::Struct(&[Layout::Builtin(Int64)]), + Struct(&[ + ( + CallByName { + name: gen_symbol_0, + layout: Layout::FunctionPointer(&[I64_LAYOUT], &I64_LAYOUT), + args: &[(Int(4), I64_LAYOUT)], + }, + I64_LAYOUT, + ), + (Float(0.1), F64_LAYOUT), + ]), + struct_layout, )], } }, ) } + // #[test] + // fn list_get_unique() { + // compiles_to( + // r#" + // unique = [ 2, 4 ] + + // List.get unique 1 + // "#, + // { + // use self::Builtin::*; + // let home = test_home(); + + // let gen_symbol_0 = Interns::from_index(home, 0); + // let list_layout = Layout::Builtin(Builtin::List(&I64_LAYOUT)); + + // CallByName { + // name: gen_symbol_0, + // layout: Layout::FunctionPointer(&[list_layout.clone()], &list_layout.clone()), + // args: &[( + // Struct(&[( + // CallByName { + // name: gen_symbol_0, + // layout: Layout::FunctionPointer( + // &[Layout::Builtin(Builtin::Int64)], + // &Layout::Builtin(Builtin::Int64), + // ), + // args: &[(Int(4), Layout::Builtin(Int64))], + // }, + // Layout::Builtin(Int64), + // )]), + // Layout::Struct(&[Layout::Builtin(Int64)]), + // )], + // } + // }, + // ) + // } + // needs LetRec to be converted to mono // #[test] // fn polymorphic_recursive() { @@ -443,7 +487,7 @@ mod test_mono { let home = test_home(); let var_x = interns.symbol(home, "x".into()); - let stores = [(var_x, Layout::Builtin(Builtin::Bool), Bool(true))]; + let stores = [(var_x, Layout::Builtin(Builtin::Int1), Bool(true))]; let load = Load(var_x); @@ -467,7 +511,7 @@ mod test_mono { let home = test_home(); let var_x = interns.symbol(home, "x".into()); - let stores = [(var_x, Layout::Builtin(Builtin::Bool), Bool(false))]; + let stores = [(var_x, Layout::Builtin(Builtin::Int1), Bool(false))]; let load = Load(var_x); @@ -493,7 +537,7 @@ mod test_mono { let var_x = interns.symbol(home, "x".into()); // orange gets index (and therefore tag_id) 1 - let stores = [(var_x, Layout::Builtin(Builtin::Byte), Byte(2))]; + let stores = [(var_x, Layout::Builtin(Builtin::Int8), Byte(2))]; let load = Load(var_x); @@ -504,15 +548,12 @@ mod test_mono { #[test] fn set_unique_int_list() { - compiles_to("List.getUnsafe (List.set [ 12, 9, 7, 3 ] 1 42) 1", { + compiles_to("List.get (List.set [ 12, 9, 7, 3 ] 1 42) 1", { CallByName { - name: Symbol::LIST_GET_UNSAFE, + name: Symbol::LIST_GET, layout: Layout::FunctionPointer( - &[ - Layout::Builtin(Builtin::List(&Layout::Builtin(Builtin::Int64))), - Layout::Builtin(Builtin::Int64), - ], - &Layout::Builtin(Builtin::Int64), + &[Layout::Builtin(Builtin::List(&I64_LAYOUT)), I64_LAYOUT], + &Layout::Union(&[&[I64_LAYOUT], &[I64_LAYOUT, I64_LAYOUT]]), ), args: &vec![ ( @@ -520,31 +561,27 @@ mod test_mono { name: Symbol::LIST_SET, layout: Layout::FunctionPointer( &[ - Layout::Builtin(Builtin::List(&Layout::Builtin( - Builtin::Int64, - ))), - Layout::Builtin(Builtin::Int64), - Layout::Builtin(Builtin::Int64), + Layout::Builtin(Builtin::List(&I64_LAYOUT)), + I64_LAYOUT, + I64_LAYOUT, ], - &Layout::Builtin(Builtin::List(&Layout::Builtin(Builtin::Int64))), + &Layout::Builtin(Builtin::List(&I64_LAYOUT)), ), args: &vec![ ( Array { - elem_layout: Layout::Builtin(Builtin::Int64), + elem_layout: I64_LAYOUT, elems: &vec![Int(12), Int(9), Int(7), Int(3)], }, - Layout::Builtin(Builtin::List(&Layout::Builtin( - Builtin::Int64, - ))), + Layout::Builtin(Builtin::List(&I64_LAYOUT)), ), - (Int(1), Layout::Builtin(Builtin::Int64)), - (Int(42), Layout::Builtin(Builtin::Int64)), + (Int(1), I64_LAYOUT), + (Int(42), I64_LAYOUT), ], }, - Layout::Builtin(Builtin::List(&Layout::Builtin(Builtin::Int64))), + Layout::Builtin(Builtin::List(&I64_LAYOUT)), ), - (Int(1), Layout::Builtin(Builtin::Int64)), + (Int(1), I64_LAYOUT), ], } }); diff --git a/compiler/mono/tests/test_opt.rs b/compiler/mono/tests/test_opt.rs deleted file mode 100644 index 8f2498cb7b..0000000000 --- a/compiler/mono/tests/test_opt.rs +++ /dev/null @@ -1,298 +0,0 @@ -#[macro_use] -extern crate pretty_assertions; -#[macro_use] -extern crate indoc; - -extern crate bumpalo; -extern crate roc_mono; - -mod helpers; - -// Test optimizations -#[cfg(test)] -mod test_opt { - use crate::helpers::{infer_expr, uniq_expr}; - use bumpalo::Bump; - use roc_module::symbol::Symbol; - use roc_mono::expr::Expr::{self, *}; - use roc_mono::expr::Procs; - use roc_mono::layout::{Builtin, Layout}; - - // HELPERS - - #[derive(Debug, Default, PartialEq, Eq)] - struct CallProblems { - missing: Vec, - unexpected: Vec, - } - - fn contains_named_calls(src: &str, mut calls: Vec) { - let arena = Bump::new(); - let (loc_expr, _, _problems, subs, var, constraint, home, mut interns) = uniq_expr(src); - - let mut unify_problems = Vec::new(); - let (_content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var); - - // Compile and add all the Procs before adding main - let mut procs = Procs::default(); - let mut ident_ids = interns.all_ident_ids.remove(&home).unwrap(); - - // assume 64-bit pointers - let pointer_size = std::mem::size_of::() as u32; - - // Populate Procs and Subs, and get the low-level Expr from the canonical Expr - let mut mono_problems = Vec::new(); - let mut mono_env = roc_mono::expr::Env { - arena: &arena, - subs: &mut subs, - problems: &mut mono_problems, - home, - ident_ids: &mut ident_ids, - pointer_size, - jump_counter: arena.alloc(0), - }; - let mono_expr = Expr::new(&mut mono_env, loc_expr.value, &mut procs); - - let unexpected_calls = extract_named_calls(&mono_expr, &mut calls); - let expected = CallProblems::default(); - let actual = CallProblems { - missing: calls, - unexpected: unexpected_calls, - }; - - assert_eq!(expected, actual); - } - - fn extract_named_calls(expr: &Expr<'_>, calls: &mut Vec) -> Vec { - let mut unexpected_calls = Vec::new(); - - // The calls must be sorted so we can binary_search them for matches. - calls.sort(); - - extract_named_calls_help(expr, calls, &mut unexpected_calls); - - unexpected_calls - } - fn extract_named_calls_help( - expr: &Expr<'_>, - calls: &mut Vec, - unexpected_calls: &mut Vec, - ) { - match expr { - Int(_) - | Float(_) - | Str(_) - | Bool(_) - | Byte(_) - | Load(_) - | FunctionPointer(_, _) - | RuntimeError(_) - | RuntimeErrorFunction(_) => (), - - Store(paths, sub_expr) => { - for (_, _, path_expr) in paths.iter() { - extract_named_calls_help(path_expr, calls, unexpected_calls); - } - - extract_named_calls_help(sub_expr, calls, unexpected_calls); - } - - CallByPointer(sub_expr, args, _) => { - extract_named_calls_help(sub_expr, calls, unexpected_calls); - - for arg in args.iter() { - extract_named_calls_help(arg, calls, unexpected_calls); - } - } - - CallByName { - name, - layout: _, - args, - } => { - // Search for the symbol. If we found it, check it off the list. - // If we didn't find it, add it to the list of unexpected calls. - match calls.binary_search(name) { - Ok(index) => { - calls.remove(index); - } - Err(_) => { - unexpected_calls.push(*name); - } - } - - for (arg, _) in args.iter() { - extract_named_calls_help(arg, calls, unexpected_calls); - } - } - - Cond { - cond_symbol: _, - branch_symbol: _, - cond_layout: _, - pass, - fail, - ret_layout: _, - } => { - extract_named_calls_help(pass.1, calls, unexpected_calls); - extract_named_calls_help(fail.1, calls, unexpected_calls); - } - Switch { - cond, - cond_layout: _, - branches, - default_branch, - ret_layout: _, - } => { - extract_named_calls_help(cond, calls, unexpected_calls); - extract_named_calls_help(default_branch.1, calls, unexpected_calls); - - for (_, _, branch_expr) in branches.iter() { - extract_named_calls_help(branch_expr, calls, unexpected_calls); - } - } - - Tag { - tag_layout: _, - tag_name: _, - tag_id: _, - union_size: _, - arguments, - } => { - for (tag_expr, _) in arguments.iter() { - extract_named_calls_help(tag_expr, calls, unexpected_calls); - } - } - Struct(fields) => { - for (field, _) in fields.iter() { - extract_named_calls_help(field, calls, unexpected_calls); - } - } - AccessAtIndex { - index: _, - field_layouts: _, - expr: sub_expr, - is_unwrapped: _, - } => { - extract_named_calls_help(sub_expr, calls, unexpected_calls); - } - - Array { - elem_layout: _, - elems, - } => { - for elem in elems.iter() { - extract_named_calls_help(elem, calls, unexpected_calls); - } - } - } - } - - fn compiles_to(src: &str, expected: Expr<'_>) { - let arena = Bump::new(); - let (loc_expr, _, _problems, subs, var, constraint, home, mut interns) = uniq_expr(src); - - let mut unify_problems = Vec::new(); - let (_content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var); - - // Compile and add all the Procs before adding main - let mut procs = Procs::default(); - let mut ident_ids = interns.all_ident_ids.remove(&home).unwrap(); - - // assume 64-bit pointers - let pointer_size = std::mem::size_of::() as u32; - - // Populate Procs and Subs, and get the low-level Expr from the canonical Expr - let mut mono_problems = Vec::new(); - let mut mono_env = roc_mono::expr::Env { - arena: &arena, - subs: &mut subs, - problems: &mut mono_problems, - home, - ident_ids: &mut ident_ids, - pointer_size, - jump_counter: arena.alloc(0), - }; - let mono_expr = Expr::new(&mut mono_env, loc_expr.value, &mut procs); - - assert_eq!(mono_expr, expected); - } - - #[test] - fn int_literal() { - compiles_to("5", Int(5)); - } - - #[test] - fn float_literal() { - compiles_to("0.5", Float(0.5)); - } - - #[test] - fn set_unique_int_list() { - // This should optimize List.set to List.set_in_place - compiles_to( - "List.getUnsafe (List.set [ 12, 9, 7, 3 ] 1 42) 1", - CallByName { - name: Symbol::LIST_GET_UNSAFE, - layout: Layout::FunctionPointer( - &[ - Layout::Builtin(Builtin::List(&Layout::Builtin(Builtin::Int64))), - Layout::Builtin(Builtin::Int64), - ], - &Layout::Builtin(Builtin::Int64), - ), - args: &vec![ - ( - CallByName { - name: Symbol::LIST_SET_IN_PLACE, - layout: Layout::FunctionPointer( - &[ - Layout::Builtin(Builtin::List(&Layout::Builtin( - Builtin::Int64, - ))), - Layout::Builtin(Builtin::Int64), - Layout::Builtin(Builtin::Int64), - ], - &Layout::Builtin(Builtin::List(&Layout::Builtin(Builtin::Int64))), - ), - args: &vec![ - ( - Array { - elem_layout: Layout::Builtin(Builtin::Int64), - elems: &vec![Int(12), Int(9), Int(7), Int(3)], - }, - Layout::Builtin(Builtin::List(&Layout::Builtin( - Builtin::Int64, - ))), - ), - (Int(1), Layout::Builtin(Builtin::Int64)), - (Int(42), Layout::Builtin(Builtin::Int64)), - ], - }, - Layout::Builtin(Builtin::List(&Layout::Builtin(Builtin::Int64))), - ), - (Int(1), Layout::Builtin(Builtin::Int64)), - ], - }, - ); - } - - #[test] - fn set_shared_int_list() { - // This should *NOT* optimize List.set to List.set_in_place - contains_named_calls( - indoc!( - r#" - shared = [ 2, 4 ] - - # This should not mutate the original - x = List.set shared 1 0 - - { x, y: List.getUnsafe shared 1 } - "# - ), - vec![Symbol::LIST_SET, Symbol::LIST_GET_UNSAFE], - ); - } -} diff --git a/compiler/parse/src/ast.rs b/compiler/parse/src/ast.rs index 2e87baa5ee..d0fff3ce4b 100644 --- a/compiler/parse/src/ast.rs +++ b/compiler/parse/src/ast.rs @@ -346,6 +346,7 @@ pub enum Base { Octal, Binary, Hex, + Decimal, } impl<'a> Pattern<'a> { diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index 744cecc133..15448fe461 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -660,11 +660,8 @@ fn parse_def_expr<'a>( region: loc_first_pattern.region, }; - // Add the first def to the end of the defs. (It's fine that we - // reorder the first one to the end, because canonicalize will - // sort all these defs based on their mutual dependencies anyway. Only - // their regions will ever be visible to the user.) - defs.push(arena.alloc(loc_first_def)); + // for formatting reasons, we must insert the first def first! + defs.insert(0, arena.alloc(loc_first_def)); Ok((Expr::Defs(defs, arena.alloc(loc_ret)), state)) }, diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index 6b8205b5c3..41e5fb1ba7 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -1414,10 +1414,7 @@ mod test_parse { newline.into_bump_slice(), ); let loc_def2 = &*arena.alloc(Located::new(2, 2, 0, 5, def2)); - // NOTE: The first def always gets reordered to the end (because it - // gets added by .push(), since that's more efficient and since - // canonicalization is going to re-sort these all anyway.) - let defs = bumpalo::vec![in &arena; loc_def2, loc_def1]; + let defs = bumpalo::vec![in &arena; loc_def1, loc_def2]; let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice()); let loc_ret = Located::new(4, 4, 0, 2, ret); let reset_indentation = bumpalo::vec![in &arena; LineComment(" leading comment")]; @@ -1467,10 +1464,7 @@ mod test_parse { newline.into_bump_slice(), ); let loc_def2 = &*arena.alloc(Located::new(2, 2, 0, 5, def2)); - // NOTE: The first def always gets reordered to the end (because it - // gets added by .push(), since that's more efficient and since - // canonicalization is going to re-sort these all anyway.) - let defs = bumpalo::vec![in &arena; loc_def2, loc_def1]; + let defs = bumpalo::vec![in &arena; loc_def1, loc_def2 ]; let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice()); let loc_ret = Located::new(4, 4, 0, 2, ret); let reset_indentation = bumpalo::vec![in &arena; LineComment(" leading comment")]; diff --git a/compiler/problem/src/can.rs b/compiler/problem/src/can.rs index 3b37e40645..ae92b3361e 100644 --- a/compiler/problem/src/can.rs +++ b/compiler/problem/src/can.rs @@ -3,6 +3,7 @@ use roc_collections::all::MutSet; use roc_module::ident::{Ident, Lowercase, TagName}; use roc_module::operator::BinOp; use roc_module::symbol::{ModuleId, Symbol}; +use roc_parse::ast::Base; use roc_parse::pattern::PatternType; use roc_region::all::{Located, Region}; @@ -50,6 +51,10 @@ pub enum Problem { annotation_pattern: Region, def_pattern: Region, }, + InvalidAliasRigid { + alias_name: Symbol, + region: Region, + }, } #[derive(Clone, Debug, PartialEq)] @@ -57,6 +62,36 @@ pub enum PrecedenceProblem { BothNonAssociative(Region, Located, Located), } +/// Enum to store the various types of errors that can cause parsing an integer to fail. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum IntErrorKind { + /// Value being parsed is empty. + /// + /// Among other causes, this variant will be constructed when parsing an empty string. + /// In roc, this can happen with non-base-10 literals, e.g. `0x` or `0b` without any digits + Empty, + /// Contains an invalid digit. + /// + /// Among other causes, this variant will be constructed when parsing a string that + /// contains a letter. + InvalidDigit, + /// Integer is too large to store in target integer type. + Overflow, + /// Integer is too small to store in target integer type. + Underflow, +} + +/// Enum to store the various types of errors that can cause parsing a float to fail. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum FloatErrorKind { + /// Probably an invalid digit + Error, + /// the literal is too small for f64 + NegativeInfinity, + /// the literal is too large for f64 + PositiveInfinity, +} + #[derive(Clone, Debug, PartialEq)] pub enum RuntimeError { Shadowing { @@ -65,7 +100,8 @@ pub enum RuntimeError { }, // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments! UnsupportedPattern(Region), - UnrecognizedFunctionName(Located), + // Example: when 1 is 1.X -> 32 + MalformedPattern(MalformedPatternProblem, Region), LookupNotInScope(Located, MutSet>), ValueNotExposed { module_name: InlinableString, @@ -80,17 +116,19 @@ pub enum RuntimeError { InvalidPrecedence(PrecedenceProblem, Region), MalformedIdentifier(Box, Region), MalformedClosure(Region), - FloatOutsideRange(Box), - IntOutsideRange(Box), - InvalidHex(std::num::ParseIntError, Box), - InvalidOctal(std::num::ParseIntError, Box), - InvalidBinary(std::num::ParseIntError, Box), - QualifiedPatternIdent(InlinableString), - CircularDef( - Vec>, - Vec<(Region /* pattern */, Region /* expr */)>, - ), + InvalidFloat(FloatErrorKind, Region, Box), + InvalidInt(IntErrorKind, Base, Region, Box), + CircularDef(Vec, Vec<(Region /* pattern */, Region /* expr */)>), /// When the author specifies a type annotation but no implementation NoImplementation, } + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum MalformedPatternProblem { + MalformedInt, + MalformedFloat, + MalformedBase(Base), + Unknown, + QualifiedIdentifier, +} diff --git a/compiler/region/src/all.rs b/compiler/region/src/all.rs index 372f503ea0..08230b4729 100644 --- a/compiler/region/src/all.rs +++ b/compiler/region/src/all.rs @@ -78,6 +78,17 @@ impl Region { Self::zero() } } + + pub fn lines_between(&self, other: &Region) -> u32 { + if self.end_line <= other.start_line { + other.start_line - self.end_line + } else if self.start_line >= other.end_line { + self.start_line - other.end_line + } else { + // intersection + 0 + } + } } #[test] diff --git a/compiler/reporting/src/error/canonicalize.rs b/compiler/reporting/src/error/canonicalize.rs index 702980fc95..244e5bb04d 100644 --- a/compiler/reporting/src/error/canonicalize.rs +++ b/compiler/reporting/src/error/canonicalize.rs @@ -1,6 +1,6 @@ use roc_collections::all::MutSet; use roc_problem::can::PrecedenceProblem::BothNonAssociative; -use roc_problem::can::{Problem, RuntimeError}; +use roc_problem::can::{FloatErrorKind, IntErrorKind, Problem, RuntimeError}; use roc_region::all::Region; use std::path::PathBuf; @@ -247,6 +247,21 @@ pub fn can_problem<'b>( alloc.region(Region::span_across(annotation_pattern, def_pattern)), alloc.reflow("Is it a typo? If not, put either a newline or comment between them."), ]), + Problem::InvalidAliasRigid { alias_name, region } => alloc.stack(vec![ + alloc.concat(vec![ + alloc.reflow("This pattern in the definition of "), + alloc.symbol_unqualified(alias_name), + alloc.reflow(" is not what I expect:"), + ]), + alloc.region(region), + alloc.concat(vec![ + alloc.reflow("Only type variables like "), + alloc.type_variable("a".into()), + alloc.reflow(" or "), + alloc.type_variable("value".into()), + alloc.reflow(" can occur in this position."), + ]), + ]), Problem::RuntimeError(runtime_error) => pretty_runtime_error(alloc, runtime_error), }; @@ -283,13 +298,13 @@ fn pretty_runtime_error<'b>( RuntimeError::LookupNotInScope(loc_name, options) => { not_found(alloc, loc_name.region, &loc_name.value, "value", options) } - RuntimeError::CircularDef(mut idents, regions) => { - let first = idents.remove(0); + RuntimeError::CircularDef(mut symbols, regions) => { + let first = symbols.remove(0); - if idents.is_empty() { + if symbols.is_empty() { alloc .reflow("The ") - .append(alloc.ident(first.value)) + .append(alloc.symbol_unqualified(first)) .append(alloc.reflow( " value is defined directly in terms of itself, causing an infinite loop.", )) @@ -299,62 +314,205 @@ fn pretty_runtime_error<'b>( alloc.stack(vec![ alloc .reflow("The ") - .append(alloc.ident(first.value.clone())) + .append(alloc.symbol_unqualified(first)) .append( alloc.reflow(" definition is causing a very tricky infinite loop:"), ), alloc.region(regions[0].0), alloc .reflow("The ") - .append(alloc.ident(first.value.clone())) + .append(alloc.symbol_unqualified(first)) .append(alloc.reflow( " value depends on itself through the following chain of definitions:", )), crate::report::cycle( alloc, 4, - alloc.ident(first.value), - idents + alloc.symbol_unqualified(first), + symbols .into_iter() - .map(|ident| alloc.ident(ident.value)) + .map(|s| alloc.symbol_unqualified(s)) .collect::>(), ), // TODO hint? ]) } } - other => { - // // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments! - // UnsupportedPattern(Region), - // UnrecognizedFunctionName(Located), - // SymbolNotExposed { - // module_name: InlinableString, - // ident: InlinableString, - // region: Region, - // }, - // ModuleNotImported { - // module_name: InlinableString, - // ident: InlinableString, - // region: Region, - // }, - // InvalidPrecedence(PrecedenceProblem, Region), - // MalformedIdentifier(Box, Region), - // MalformedClosure(Region), - // FloatOutsideRange(Box), - // IntOutsideRange(Box), - // InvalidHex(std::num::ParseIntError, Box), - // InvalidOctal(std::num::ParseIntError, Box), - // InvalidBinary(std::num::ParseIntError, Box), - // QualifiedPatternIdent(InlinableString), - // CircularDef( - // Vec>, - // Vec<(Region /* pattern */, Region /* expr */)>, - // ), - // - // /// When the author specifies a type annotation but no implementation - // NoImplementation, - todo!("TODO implement run time error reporting for {:?}", other) + RuntimeError::MalformedPattern(problem, region) => { + use roc_parse::ast::Base; + use roc_problem::can::MalformedPatternProblem::*; + + let name = match problem { + MalformedInt => " integer ", + MalformedFloat => " float ", + MalformedBase(Base::Hex) => " hex integer ", + MalformedBase(Base::Binary) => " binary integer ", + MalformedBase(Base::Octal) => " octal integer ", + MalformedBase(Base::Decimal) => " integer ", + Unknown => " ", + QualifiedIdentifier => " qualified ", + }; + + let hint = match problem { + MalformedInt | MalformedFloat | MalformedBase(_) => alloc + .hint() + .append(alloc.reflow("Learn more about number literals at TODO")), + Unknown => alloc.nil(), + QualifiedIdentifier => alloc.hint().append( + alloc.reflow("In patterns, only private and global tags can be qualified"), + ), + }; + + alloc.stack(vec![ + alloc.concat(vec![ + alloc.reflow("This"), + alloc.text(name), + alloc.reflow("pattern is malformed:"), + ]), + alloc.region(region), + hint, + ]) } + RuntimeError::UnsupportedPattern(_) => { + todo!("unsupported patterns are currently not parsed!") + } + RuntimeError::ValueNotExposed { .. } => todo!("value not exposed"), + RuntimeError::ModuleNotImported { .. } => todo!("module not imported"), + RuntimeError::InvalidPrecedence(_, _) => { + // do nothing, reported with PrecedenceProblem + unreachable!() + } + RuntimeError::MalformedIdentifier(_, _) => { + todo!("malformed identifier, currently gives a parse error and thus is unreachable") + } + RuntimeError::MalformedClosure(_) => todo!(""), + RuntimeError::InvalidFloat(sign @ FloatErrorKind::PositiveInfinity, region, _raw_str) + | RuntimeError::InvalidFloat(sign @ FloatErrorKind::NegativeInfinity, region, _raw_str) => { + let hint = alloc + .hint() + .append(alloc.reflow("Learn more about number literals at TODO")); + + let big_or_small = if let FloatErrorKind::PositiveInfinity = sign { + "big" + } else { + "small" + }; + + alloc.stack(vec![ + alloc.concat(vec![ + alloc.reflow("This float literal is too "), + alloc.text(big_or_small), + alloc.reflow(":"), + ]), + alloc.region(region), + alloc.concat(vec![ + alloc.reflow("Roc uses signed 64-bit floating points, allowing values between"), + alloc.text(format!("{:e}", f64::MIN)), + alloc.reflow(" and "), + alloc.text(format!("{:e}", f64::MAX)), + ]), + hint, + ]) + } + RuntimeError::InvalidFloat(FloatErrorKind::Error, region, _raw_str) => { + let hint = alloc + .hint() + .append(alloc.reflow("Learn more about number literals at TODO")); + + alloc.stack(vec![ + alloc.concat(vec![ + alloc.reflow("This float literal contains an invalid digit:"), + ]), + alloc.region(region), + alloc.concat(vec![ + alloc.reflow("Floating point literals can only contain the digits 0-9, or use scientific notation 10e4"), + ]), + hint, + ]) + } + RuntimeError::InvalidInt(error @ IntErrorKind::InvalidDigit, base, region, _raw_str) + | RuntimeError::InvalidInt(error @ IntErrorKind::Empty, base, region, _raw_str) => { + use roc_parse::ast::Base::*; + + let (problem, contains) = if let IntErrorKind::InvalidDigit = error { + ( + "an invalid digit", + alloc.reflow(" can only contain the digits "), + ) + } else { + ( + "no digits", + alloc.reflow(" must contain at least one of the digits "), + ) + }; + + let name = match base { + Decimal => "integer", + Octal => "octal integer", + Hex => "hex integer", + Binary => "binary integer", + }; + + let plurals = match base { + Decimal => "Integer literals", + Octal => "Octal (base-8) integer literals", + Hex => "Hexadecimal (base-16) integer literals", + Binary => "Binary (base-2) integer literals", + }; + + let charset = match base { + Decimal => "0-9", + Octal => "0-7", + Hex => "0-9, a-f and A-F", + Binary => "0 and 1", + }; + + let hint = alloc + .hint() + .append(alloc.reflow("Learn more about number literals at TODO")); + + alloc.stack(vec![ + alloc.concat(vec![ + alloc.reflow("This "), + alloc.text(name), + alloc.reflow(" literal contains "), + alloc.text(problem), + alloc.text(":"), + ]), + alloc.region(region), + alloc.concat(vec![ + alloc.text(plurals), + contains, + alloc.text(charset), + alloc.text("."), + ]), + hint, + ]) + } + RuntimeError::InvalidInt(error_kind @ IntErrorKind::Underflow, _base, region, _raw_str) + | RuntimeError::InvalidInt(error_kind @ IntErrorKind::Overflow, _base, region, _raw_str) => { + let big_or_small = if let IntErrorKind::Underflow = error_kind { + "small" + } else { + "big" + }; + + let hint = alloc + .hint() + .append(alloc.reflow("Learn more about number literals at TODO")); + + alloc.stack(vec![ + alloc.concat(vec![ + alloc.reflow("This integer literal is too "), + alloc.text(big_or_small), + alloc.reflow(":"), + ]), + alloc.region(region), + alloc.reflow("Roc uses signed 64-bit integers, allowing values between −9_223_372_036_854_775_808 and 9_223_372_036_854_775_807."), + hint, + ]) + } + RuntimeError::NoImplementation => todo!("no implementation, unreachable"), } } diff --git a/compiler/reporting/src/error/type.rs b/compiler/reporting/src/error/type.rs index 57d4d1fe57..727e32ede8 100644 --- a/compiler/reporting/src/error/type.rs +++ b/compiler/reporting/src/error/type.rs @@ -765,6 +765,13 @@ fn to_expr_report<'b>( None, ) } + Reason::LowLevelOpArg { op, arg_index } => { + panic!( + "Compiler bug: argument #{} to low-level operation {:?} was the wrong type!", + arg_index.ordinal(), + op + ); + } Reason::FloatLiteral | Reason::IntLiteral | Reason::NumLiteral => { unreachable!("I don't think these can be reached") } @@ -892,6 +899,12 @@ fn add_category<'b>( alloc.text(" call produces:"), ]), CallResult(None) => alloc.concat(vec![this_is, alloc.text(":")]), + LowLevelOpResult(op) => { + panic!( + "Compiler bug: invalid return type from low-level op {:?}", + op + ); + } Uniqueness => alloc.concat(vec![ this_is, @@ -1478,24 +1491,24 @@ fn to_diff<'b>( let right = to_doc(alloc, Parens::Unnecessary, type2); let is_int = |t: &ErrorType| match t { - ErrorType::Type(Symbol::INT_INT, _) => true, - ErrorType::Alias(Symbol::INT_INT, _, _) => true, + ErrorType::Type(Symbol::NUM_INT, _) => true, + ErrorType::Alias(Symbol::NUM_INT, _, _) => true, ErrorType::Type(Symbol::NUM_NUM, args) => match &args.get(0) { - Some(ErrorType::Type(Symbol::INT_INTEGER, _)) => true, - Some(ErrorType::Alias(Symbol::INT_INTEGER, _, _)) => true, + Some(ErrorType::Type(Symbol::NUM_INTEGER, _)) => true, + Some(ErrorType::Alias(Symbol::NUM_INTEGER, _, _)) => true, _ => false, }, ErrorType::Alias(Symbol::NUM_NUM, args, _) => match &args.get(0) { - Some((_, ErrorType::Type(Symbol::INT_INTEGER, _))) => true, - Some((_, ErrorType::Alias(Symbol::INT_INTEGER, _, _))) => true, + Some((_, ErrorType::Type(Symbol::NUM_INTEGER, _))) => true, + Some((_, ErrorType::Alias(Symbol::NUM_INTEGER, _, _))) => true, _ => false, }, _ => false, }; let is_float = |t: &ErrorType| match t { - ErrorType::Type(Symbol::FLOAT_FLOAT, _) => true, - ErrorType::Alias(Symbol::FLOAT_FLOAT, _, _) => true, + ErrorType::Type(Symbol::NUM_FLOAT, _) => true, + ErrorType::Alias(Symbol::NUM_FLOAT, _, _) => true, _ => false, }; @@ -2284,7 +2297,7 @@ fn type_problem_to_pretty<'b>( alloc.reflow(" with "), alloc.symbol_qualified(Symbol::NUM_TO_FLOAT), alloc.reflow(" and "), - alloc.symbol_qualified(Symbol::FLOAT_ROUND), + alloc.symbol_qualified(Symbol::NUM_ROUND), alloc.reflow("."), ])), diff --git a/compiler/reporting/tests/helpers/mod.rs b/compiler/reporting/tests/helpers/mod.rs index 80e60db99b..877e983412 100644 --- a/compiler/reporting/tests/helpers/mod.rs +++ b/compiler/reporting/tests/helpers/mod.rs @@ -132,10 +132,7 @@ pub fn can_expr_with( let loc_expr = match parse_loc_with(&arena, expr_str) { Ok(e) => e, Err(fail) => { - let interns = Interns { - module_ids: ModuleIds::default(), - all_ident_ids: MutMap::default(), - }; + let interns = Interns::default(); return Err(ParseErrOut { fail, diff --git a/compiler/reporting/tests/test_reporting.rs b/compiler/reporting/tests/test_reporting.rs index 2fb67fc285..73a3b924f7 100644 --- a/compiler/reporting/tests/test_reporting.rs +++ b/compiler/reporting/tests/test_reporting.rs @@ -1058,7 +1058,7 @@ mod test_reporting { Int - Hint: Convert between Int and Float with `Num.toFloat` and `Float.round`. + Hint: Convert between Int and Float with `Num.toFloat` and `Num.round`. "# ), ) @@ -1094,7 +1094,7 @@ mod test_reporting { Int - Hint: Convert between Int and Float with `Num.toFloat` and `Float.round`. + Hint: Convert between Int and Float with `Num.toFloat` and `Num.round`. "# ), ) @@ -1129,7 +1129,7 @@ mod test_reporting { Int -> Int - Hint: Convert between Int and Float with `Num.toFloat` and `Float.round`. + Hint: Convert between Int and Float with `Num.toFloat` and `Num.round`. "# ), ) @@ -1462,7 +1462,132 @@ mod test_reporting { { x : Int } - Hint: Convert between Int and Float with `Num.toFloat` and `Float.round`. + Hint: Convert between Int and Float with `Num.toFloat` and `Num.round`. + "# + ), + ) + } + + #[test] + fn malformed_int_pattern() { + report_problem_as( + indoc!( + r#" + when 1 is + 100A -> 3 + _ -> 4 + "# + ), + indoc!( + r#" + -- SYNTAX PROBLEM -------------------------------------------------------------- + + This integer pattern is malformed: + + 2 ┆ 100A -> 3 + ┆ ^^^^ + + Hint: Learn more about number literals at TODO + "# + ), + ) + } + + #[test] + fn malformed_float_pattern() { + report_problem_as( + indoc!( + r#" + when 1 is + 2.X -> 3 + _ -> 4 + "# + ), + indoc!( + r#" + -- SYNTAX PROBLEM -------------------------------------------------------------- + + This float pattern is malformed: + + 2 ┆ 2.X -> 3 + ┆ ^^^ + + Hint: Learn more about number literals at TODO + "# + ), + ) + } + + #[test] + fn malformed_hex_pattern() { + report_problem_as( + indoc!( + r#" + when 1 is + 0xZ -> 3 + _ -> 4 + "# + ), + indoc!( + r#" + -- SYNTAX PROBLEM -------------------------------------------------------------- + + This hex integer pattern is malformed: + + 2 ┆ 0xZ -> 3 + ┆ ^^^ + + Hint: Learn more about number literals at TODO + "# + ), + ) + } + + #[test] + fn malformed_oct_pattern() { + report_problem_as( + indoc!( + r#" + when 1 is + 0o9 -> 3 + _ -> 4 + "# + ), + indoc!( + r#" + -- SYNTAX PROBLEM -------------------------------------------------------------- + + This octal integer pattern is malformed: + + 2 ┆ 0o9 -> 3 + ┆ ^^^ + + Hint: Learn more about number literals at TODO + "# + ), + ) + } + + #[test] + fn malformed_bin_pattern() { + report_problem_as( + indoc!( + r#" + when 1 is + 0b4 -> 3 + _ -> 4 + "# + ), + indoc!( + r#" + -- SYNTAX PROBLEM -------------------------------------------------------------- + + This binary integer pattern is malformed: + + 2 ┆ 0b4 -> 3 + ┆ ^^^ + + Hint: Learn more about number literals at TODO "# ), ) @@ -1702,6 +1827,7 @@ mod test_reporting { #[test] fn circular_definition_self() { + // invalid recursion report_problem_as( indoc!( r#" @@ -1723,6 +1849,7 @@ mod test_reporting { #[test] fn circular_definition() { + // invalid mutual recursion report_problem_as( indoc!( r#" @@ -1947,7 +2074,7 @@ mod test_reporting { Num Integer - Hint: Convert between Int and Float with `Num.toFloat` and `Float.round`. + Hint: Convert between Int and Float with `Num.toFloat` and `Num.round`. "# ), ) @@ -2409,11 +2536,11 @@ mod test_reporting { } #[test] - fn circular_alias() { + fn cyclic_alias() { report_problem_as( indoc!( r#" - Foo : { x: Bar } + Foo : { x : Bar } Bar : { y : Foo } f : Foo @@ -2426,18 +2553,18 @@ mod test_reporting { r#" -- CYCLIC ALIAS ---------------------------------------------------------------- - The `Bar` alias is recursive in an invalid way: + The `Foo` alias is recursive in an invalid way: - 2 ┆ Bar : { y : Foo } + 1 ┆ Foo : { x : Bar } ┆ ^^^^^^^^^^^ - The `Bar` alias depends on itself through the following chain of + The `Foo` alias depends on itself through the following chain of definitions: ┌─────┐ - │ Bar - │ ↓ │ Foo + │ ↓ + │ Bar └─────┘ Recursion in aliases is only allowed if recursion happens behind a @@ -2712,6 +2839,57 @@ mod test_reporting { ) } + #[test] + fn annotation_newline_body_is_fine() { + report_problem_as( + indoc!( + r#" + bar : Int + + foo = \x -> x + + foo bar + "# + ), + indoc!(""), + ) + } + + #[test] + fn invalid_alias_rigid_var_pattern() { + report_problem_as( + indoc!( + r#" + MyAlias 1 : Int + + 4 + "# + ), + indoc!( + r#" + -- SYNTAX PROBLEM -------------------------------------------------------------- + + This pattern in the definition of `MyAlias` is not what I expect: + + 1 ┆ MyAlias 1 : Int + ┆ ^ + + Only type variables like `a` or `value` can occur in this position. + + -- SYNTAX PROBLEM -------------------------------------------------------------- + + `MyAlias` is not used anywhere in your code. + + 1 ┆ MyAlias 1 : Int + ┆ ^^^^^^^^^^^^^^^ + + If you didn't intend on using `MyAlias` then remove it so future readers + of your code don't wonder why it is there. + "# + ), + ) + } + #[test] fn invalid_num() { report_problem_as( @@ -2946,4 +3124,275 @@ mod test_reporting { ), ) } + + #[test] + fn integer_out_of_range() { + report_problem_as( + indoc!( + r#" + x = 9_223_372_036_854_775_807_000 + + y = -9_223_372_036_854_775_807_000 + + h = 0x8FFF_FFFF_FFFF_FFFF + l = -0x8FFF_FFFF_FFFF_FFFF + + minlit = -9_223_372_036_854_775_808 + maxlit = 9_223_372_036_854_775_807 + + x + y + h + l + minlit + maxlit + "# + ), + indoc!( + r#" + -- SYNTAX PROBLEM -------------------------------------------------------------- + + This integer literal is too big: + + 1 ┆ x = 9_223_372_036_854_775_807_000 + ┆ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Roc uses signed 64-bit integers, allowing values between + −9_223_372_036_854_775_808 and 9_223_372_036_854_775_807. + + Hint: Learn more about number literals at TODO + + -- SYNTAX PROBLEM -------------------------------------------------------------- + + This integer literal is too small: + + 3 ┆ y = -9_223_372_036_854_775_807_000 + ┆ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Roc uses signed 64-bit integers, allowing values between + −9_223_372_036_854_775_808 and 9_223_372_036_854_775_807. + + Hint: Learn more about number literals at TODO + + -- SYNTAX PROBLEM -------------------------------------------------------------- + + This integer literal is too big: + + 5 ┆ h = 0x8FFF_FFFF_FFFF_FFFF + ┆ ^^^^^^^^^^^^^^^^^^^^^ + + Roc uses signed 64-bit integers, allowing values between + −9_223_372_036_854_775_808 and 9_223_372_036_854_775_807. + + Hint: Learn more about number literals at TODO + + -- SYNTAX PROBLEM -------------------------------------------------------------- + + This integer literal is too small: + + 6 ┆ l = -0x8FFF_FFFF_FFFF_FFFF + ┆ ^^^^^^^^^^^^^^^^^^^^^^ + + Roc uses signed 64-bit integers, allowing values between + −9_223_372_036_854_775_808 and 9_223_372_036_854_775_807. + + Hint: Learn more about number literals at TODO + "# + ), + ) + } + + #[test] + fn float_out_of_range() { + report_problem_as( + &format!( + r#" + overflow = 1{:e} + underflow = -1{:e} + + overflow + underflow + "#, + f64::MAX, + f64::MAX, + ), + indoc!( + r#" + -- SYNTAX PROBLEM -------------------------------------------------------------- + + This float literal is too big: + + 2 ┆ overflow = 11.7976931348623157e308 + ┆ ^^^^^^^^^^^^^^^^^^^^^^^ + + Roc uses signed 64-bit floating points, allowing values + between-1.7976931348623157e308 and 1.7976931348623157e308 + + Hint: Learn more about number literals at TODO + + -- SYNTAX PROBLEM -------------------------------------------------------------- + + This float literal is too small: + + 3 ┆ underflow = -11.7976931348623157e308 + ┆ ^^^^^^^^^^^^^^^^^^^^^^^^ + + Roc uses signed 64-bit floating points, allowing values + between-1.7976931348623157e308 and 1.7976931348623157e308 + + Hint: Learn more about number literals at TODO + "# + ), + ) + } + + #[test] + fn integer_malformed() { + // the generated messages here are incorrect. Waiting for a rust nightly feature to land, + // see https://github.com/rust-lang/rust/issues/22639 + // this test is here to spot regressions in error reporting + report_problem_as( + indoc!( + r#" + dec = 100A + + hex = 0xZZZ + + oct = 0o9 + + bin = 0b2 + + dec + hex + oct + bin + "# + ), + indoc!( + r#" + -- SYNTAX PROBLEM -------------------------------------------------------------- + + This integer literal contains an invalid digit: + + 1 ┆ dec = 100A + ┆ ^^^^ + + Integer literals can only contain the digits 0-9. + + Hint: Learn more about number literals at TODO + + -- SYNTAX PROBLEM -------------------------------------------------------------- + + This hex integer literal contains an invalid digit: + + 3 ┆ hex = 0xZZZ + ┆ ^^^^^ + + Hexadecimal (base-16) integer literals can only contain the digits + 0-9, a-f and A-F. + + Hint: Learn more about number literals at TODO + + -- SYNTAX PROBLEM -------------------------------------------------------------- + + This octal integer literal contains an invalid digit: + + 5 ┆ oct = 0o9 + ┆ ^^^ + + Octal (base-8) integer literals can only contain the digits 0-7. + + Hint: Learn more about number literals at TODO + + -- SYNTAX PROBLEM -------------------------------------------------------------- + + This binary integer literal contains an invalid digit: + + 7 ┆ bin = 0b2 + ┆ ^^^ + + Binary (base-2) integer literals can only contain the digits 0 and 1. + + Hint: Learn more about number literals at TODO + "# + ), + ) + } + + #[test] + fn integer_empty() { + report_problem_as( + indoc!( + r#" + dec = 20 + + hex = 0x + + oct = 0o + + bin = 0b + + dec + hex + oct + bin + "# + ), + indoc!( + r#" + -- SYNTAX PROBLEM -------------------------------------------------------------- + + This hex integer literal contains no digits: + + 3 ┆ hex = 0x + ┆ ^^ + + Hexadecimal (base-16) integer literals must contain at least one of + the digits 0-9, a-f and A-F. + + Hint: Learn more about number literals at TODO + + -- SYNTAX PROBLEM -------------------------------------------------------------- + + This octal integer literal contains no digits: + + 5 ┆ oct = 0o + ┆ ^^ + + Octal (base-8) integer literals must contain at least one of the + digits 0-7. + + Hint: Learn more about number literals at TODO + + -- SYNTAX PROBLEM -------------------------------------------------------------- + + This binary integer literal contains no digits: + + 7 ┆ bin = 0b + ┆ ^^ + + Binary (base-2) integer literals must contain at least one of the + digits 0 and 1. + + Hint: Learn more about number literals at TODO + "# + ), + ) + } + + #[test] + fn float_malformed() { + report_problem_as( + indoc!( + r#" + x = 3.0A + + x + "# + ), + indoc!( + r#" + -- SYNTAX PROBLEM -------------------------------------------------------------- + + This float literal contains an invalid digit: + + 1 ┆ x = 3.0A + ┆ ^^^^ + + Floating point literals can only contain the digits 0-9, or use + scientific notation 10e4 + + Hint: Learn more about number literals at TODO + "# + ), + ) + } } diff --git a/compiler/solve/tests/test_solve.rs b/compiler/solve/tests/solve_expr.rs similarity index 98% rename from compiler/solve/tests/test_solve.rs rename to compiler/solve/tests/solve_expr.rs index 20eda05399..df9125ceee 100644 --- a/compiler/solve/tests/test_solve.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -8,7 +8,7 @@ extern crate bumpalo; mod helpers; #[cfg(test)] -mod test_solve { +mod solve_expr { use crate::helpers::{ assert_correct_variable_usage, can_expr, infer_expr, with_larger_debug_stack, CanExprOut, }; @@ -1214,7 +1214,7 @@ mod test_solve { infer_eq( indoc!( r#" - int : Num.Num Int.Integer + int : Num.Num Num.Integer int "# @@ -1243,7 +1243,7 @@ mod test_solve { infer_eq( indoc!( r#" - int : Int.Int + int : Num.Int int = 5 int @@ -1273,7 +1273,7 @@ mod test_solve { infer_eq( indoc!( r#" - int : Num.Num Int.Integer + int : Num.Num Num.Integer int = 5.5 int @@ -1316,7 +1316,7 @@ mod test_solve { infer_eq( indoc!( r#" - float : Float.Float + float : Num.Float float = 5.5 float @@ -1446,7 +1446,7 @@ mod test_solve { infer_eq( indoc!( r#" - float : Num.Num Float.FloatingPoint + float : Num.Num Num.FloatingPoint float = 5.5 float @@ -1496,7 +1496,7 @@ mod test_solve { infer_eq_without_problem( indoc!( r#" - x : Num.Num Int.Integer + x : Num.Num Num.Integer x = when 2 is 3 -> 4 @@ -1708,7 +1708,7 @@ mod test_solve { r#" Foo a : { foo : a } - v : Foo (Num.Num Int.Integer) + v : Foo (Num.Num Num.Integer) v = { foo: 42 } v @@ -1772,7 +1772,7 @@ mod test_solve { r#" Peano : [ S Peano, Z ] - length : Peano -> Num.Num Int.Integer + length : Peano -> Num.Num Num.Integer length = \peano -> when peano is Z -> 0 @@ -1872,10 +1872,10 @@ mod test_solve { infer_eq( indoc!( r#" - r : { x : (Num.Num Int.Integer) } + r : { x : (Num.Num Num.Integer) } r = { x : 1 } - s : { left : { x : Num.Num Float.FloatingPoint } } + s : { left : { x : Num.Num Num.FloatingPoint } } s = { left: { x : 3.14 } } when 0 is @@ -2010,7 +2010,7 @@ mod test_solve { infer_eq_without_problem( indoc!( r#" - { x, y } : { x : Str.Str, y : Num.Num Float.FloatingPoint } + { x, y } : { x : Str.Str, y : Num.Num Num.FloatingPoint } { x, y } = { x : "foo", y : 3.14 } x @@ -2025,7 +2025,7 @@ mod test_solve { infer_eq( indoc!( r#" - Foo : { x : Str.Str, y : Num.Num Float.FloatingPoint } + Foo : { x : Str.Str, y : Num.Num Num.FloatingPoint } { x, y } : Foo { x, y } = { x : "foo", y : 3.14 } @@ -2061,7 +2061,7 @@ mod test_solve { infer_eq_without_problem( indoc!( r#" - Foo : { x : Str.Str, y : Num.Num Float.FloatingPoint } + Foo : { x : Str.Str, y : Num.Num Num.FloatingPoint } { x, y } : Foo { x, y } = { x : "foo", y : 3.14 } @@ -2163,7 +2163,7 @@ mod test_solve { ListB a : [ Cons a (ListC a) ] ListC a : [ Cons a (ListA a), Nil ] - val : ListC Int.Int + val : ListC Num.Int val = Cons 1 (Cons 2 (Cons 3 Nil)) val @@ -2524,7 +2524,7 @@ mod test_solve { #[test] fn rigids() { - // I was sligtly surprised this works + // I was slightly surprised this works infer_eq_without_problem( indoc!( r#" @@ -2533,10 +2533,9 @@ mod test_solve { x : List b x = [] - v = List.getUnsafe input 0 - - List.push x v - + when List.get input 0 is + Ok val -> List.push x val + Err _ -> f input f "# ), @@ -2549,7 +2548,7 @@ mod test_solve { #[should_panic] fn rigid_record_quantification() { // the ext here is qualified on the outside (because we have rank 1 types, not rank 2). - // That means e.g. `f : { bar : String, foo : Int } -> Bool }` is a valid argument. but + // That means e.g. `f : { bar : String, foo : Int } -> Bool }` is a valid argument, but // that function could not be applied to the `{ foo : Int }` list. Therefore, this function // is not allowed. // diff --git a/compiler/solve/tests/test_uniq_solve.rs b/compiler/solve/tests/solve_uniq_expr.rs similarity index 91% rename from compiler/solve/tests/test_uniq_solve.rs rename to compiler/solve/tests/solve_uniq_expr.rs index 7afe9a31e4..618e72c542 100644 --- a/compiler/solve/tests/test_uniq_solve.rs +++ b/compiler/solve/tests/solve_uniq_expr.rs @@ -8,7 +8,7 @@ extern crate bumpalo; mod helpers; #[cfg(test)] -mod test_uniq_solve { +mod solve_uniq_expr { use crate::helpers::{ assert_correct_variable_usage, infer_expr, uniq_expr, with_larger_debug_stack, }; @@ -1017,7 +1017,7 @@ mod test_uniq_solve { .left "# ), - "Attr * (Attr (* | a) { left : (Attr a b) }* -> Attr a b)", + "Attr * (Attr (* | b) { left : (Attr b a) }* -> Attr b a)", ); } @@ -1029,7 +1029,7 @@ mod test_uniq_solve { \rec -> rec.left "# ), - "Attr * (Attr (* | a) { left : (Attr a b) }* -> Attr a b)", + "Attr * (Attr (* | b) { left : (Attr b a) }* -> Attr b a)", ); } @@ -1041,8 +1041,7 @@ mod test_uniq_solve { \{ left, right } -> { left, right } "# ), - // "Attr * (Attr (* | a | b) { left : (Attr a c), right : (Attr b d) }* -> Attr * { left : (Attr a c), right : (Attr b d) })" - "Attr * (Attr (* | a | b) { left : (Attr b c), right : (Attr a d) }* -> Attr * { left : (Attr b c), right : (Attr a d) })" + "Attr * (Attr (* | b | d) { left : (Attr b a), right : (Attr d c) }* -> Attr * { left : (Attr b a), right : (Attr d c) })" ); } @@ -1069,7 +1068,7 @@ mod test_uniq_solve { ), // NOTE: Foo loses the relation to the uniqueness attribute `a` // That is fine. Whenever we try to extract from it, the relation will be enforced - "Attr * (Attr (* | a) [ Foo (Attr a b) ]* -> Attr * [ Foo (Attr a b) ]*)", + "Attr * (Attr (* | b) [ Foo (Attr b a) ]* -> Attr * [ Foo (Attr b a) ]*)", ); } @@ -1084,9 +1083,7 @@ mod test_uniq_solve { // TODO: is it safe to ignore uniqueness constraints from patterns that bind no identifiers? // i.e. the `b` could be ignored in this example, is that true in general? // seems like it because we don't really extract anything. - // - // "Attr * (Attr (* | a | b) [ Foo (Attr b c) (Attr a *) ]* -> Attr * [ Foo (Attr b c) (Attr * Str) ]*)" - "Attr * (Attr (* | a | b) [ Foo (Attr a c) (Attr b *) ]* -> Attr * [ Foo (Attr a c) (Attr * Str) ]*)" + "Attr * (Attr (* | b | c) [ Foo (Attr b a) (Attr c *) ]* -> Attr * [ Foo (Attr b a) (Attr * Str) ]*)" ); } @@ -1121,7 +1118,7 @@ mod test_uniq_solve { infer_eq( indoc!( r#" - x : Num.Num Int.Integer + x : Num.Num Num.Integer x = 4 x @@ -1139,7 +1136,7 @@ mod test_uniq_solve { \{ left } -> left "# ), - "Attr * (Attr (* | a) { left : (Attr a b) }* -> Attr a b)", + "Attr * (Attr (* | b) { left : (Attr b a) }* -> Attr b a)", ); } @@ -1151,7 +1148,7 @@ mod test_uniq_solve { \{ left } -> left "# ), - "Attr * (Attr (* | a) { left : (Attr a b) }* -> Attr a b)", + "Attr * (Attr (* | b) { left : (Attr b a) }* -> Attr b a)", ); } @@ -1166,7 +1163,7 @@ mod test_uniq_solve { numIdentity "# ), - "Attr * (Attr a (Num (Attr b p)) -> Attr a (Num (Attr b p)))", + "Attr * (Attr b (Num (Attr a p)) -> Attr b (Num (Attr a p)))", ); } @@ -1181,7 +1178,7 @@ mod test_uniq_solve { x "# ), - "Attr * (Attr (* | a) { left : (Attr a b) }* -> Attr a b)", + "Attr * (Attr (* | b) { left : (Attr b a) }* -> Attr b a)", ); } @@ -1196,7 +1193,7 @@ mod test_uniq_solve { x "# ), - "Attr * (Attr (* | a) { left : (Attr a b) }* -> Attr a b)", + "Attr * (Attr (* | b) { left : (Attr b a) }* -> Attr b a)", ); } @@ -1214,7 +1211,7 @@ mod test_uniq_solve { { numIdentity, p, q } "# ), - "Attr * { numIdentity : (Attr Shared (Attr a (Num (Attr b p)) -> Attr a (Num (Attr b p)))), p : (Attr * (Num (Attr * p))), q : (Attr * Float) }" + "Attr * { numIdentity : (Attr Shared (Attr b (Num (Attr a p)) -> Attr b (Num (Attr a p)))), p : (Attr * (Num (Attr * p))), q : (Attr * Float) }" ); } @@ -1230,7 +1227,7 @@ mod test_uniq_solve { r "# ), - "Attr * (Attr a { x : (Attr Shared b) }c -> Attr a { x : (Attr Shared b) }c)", + "Attr * (Attr c { x : (Attr Shared a) }b -> Attr c { x : (Attr Shared a) }b)", ); } @@ -1246,7 +1243,7 @@ mod test_uniq_solve { r "# ), - "Attr * (Attr a { x : (Attr Shared b), y : (Attr Shared c) }d -> Attr a { x : (Attr Shared b), y : (Attr Shared c) }d)", + "Attr * (Attr d { x : (Attr Shared a), y : (Attr Shared b) }c -> Attr d { x : (Attr Shared a), y : (Attr Shared b) }c)", ); } @@ -1265,7 +1262,7 @@ mod test_uniq_solve { p) "# ), - "Attr * (Attr a { x : (Attr Shared b), y : (Attr Shared c) }d -> Attr a { x : (Attr Shared b), y : (Attr Shared c) }d)" + "Attr * (Attr d { x : (Attr Shared a), y : (Attr Shared b) }c -> Attr d { x : (Attr Shared a), y : (Attr Shared b) }c)" ); } @@ -1281,7 +1278,7 @@ mod test_uniq_solve { r "# ), - "Attr * (Attr a { x : (Attr Shared b) }c -> Attr a { x : (Attr Shared b) }c)", + "Attr * (Attr c { x : (Attr Shared a) }b -> Attr c { x : (Attr Shared a) }b)", ); } @@ -1293,7 +1290,7 @@ mod test_uniq_solve { \r -> { r & x: r.x, y: r.x } "# ), - "Attr * (Attr a { x : (Attr Shared b), y : (Attr Shared b) }c -> Attr a { x : (Attr Shared b), y : (Attr Shared b) }c)" + "Attr * (Attr c { x : (Attr Shared a), y : (Attr Shared a) }b -> Attr c { x : (Attr Shared a), y : (Attr Shared a) }b)" ); } @@ -1309,7 +1306,7 @@ mod test_uniq_solve { r "# ), - "Attr * (Attr (a | b) { foo : (Attr b { bar : (Attr Shared d), baz : (Attr Shared c) }e) }f -> Attr (a | b) { foo : (Attr b { bar : (Attr Shared d), baz : (Attr Shared c) }e) }f)" + "Attr * (Attr (f | d) { foo : (Attr d { bar : (Attr Shared a), baz : (Attr Shared b) }c) }e -> Attr (f | d) { foo : (Attr d { bar : (Attr Shared a), baz : (Attr Shared b) }c) }e)" ); } @@ -1327,7 +1324,7 @@ mod test_uniq_solve { r "# ), - "Attr * (Attr (a | b) { foo : (Attr b { bar : (Attr Shared c) }d) }e -> Attr (a | b) { foo : (Attr b { bar : (Attr Shared c) }d) }e)" + "Attr * (Attr (e | c) { foo : (Attr c { bar : (Attr Shared a) }b) }d -> Attr (e | c) { foo : (Attr c { bar : (Attr Shared a) }b) }d)" ); } @@ -1346,7 +1343,7 @@ mod test_uniq_solve { s "# ), - "Attr * (Attr a { x : (Attr Shared b), y : (Attr Shared b) }c -> Attr a { x : (Attr Shared b), y : (Attr Shared b) }c)", + "Attr * (Attr c { x : (Attr Shared a), y : (Attr Shared a) }b -> Attr c { x : (Attr Shared a), y : (Attr Shared a) }b)", ); } @@ -1362,9 +1359,7 @@ mod test_uniq_solve { r.tic.tac.toe "# ), - // "Attr * (Attr (* | a | b | c | d | e) { foo : (Attr (d | b | e) { bar : (Attr (e | b) { baz : (Attr b f) }*) }*), tic : (Attr (a | b | c) { tac : (Attr (c | b) { toe : (Attr b f) }*) }*) }* -> Attr b f)" - "Attr * (Attr (* | a | b | c | d | e) { foo : (Attr (a | b | e) { bar : (Attr (e | b) { baz : (Attr b f) }*) }*), tic : (Attr (d | b | c) { tac : (Attr (c | b) { toe : (Attr b f) }*) }*) }* -> Attr b f)" - + "Attr * (Attr (* | b | c | d | e | f) { foo : (Attr (d | b | c) { bar : (Attr (c | b) { baz : (Attr b a) }*) }*), tic : (Attr (f | b | e) { tac : (Attr (e | b) { toe : (Attr b a) }*) }*) }* -> Attr b a)" ); } @@ -1373,7 +1368,7 @@ mod test_uniq_solve { infer_eq( indoc!( r#" - x : Num.Num Int.Integer + x : Num.Num Num.Integer x = when 2 is 3 -> 4 @@ -1697,7 +1692,7 @@ mod test_uniq_solve { map "# ), - "Attr Shared (Attr Shared (Attr a b -> c), Attr (d | a) [ Cons (Attr a b) (Attr (d | a) e), Nil ]* as e -> Attr f [ Cons c (Attr f g), Nil ]* as g)" , + "Attr Shared (Attr Shared (Attr b a -> c), Attr (e | b) [ Cons (Attr b a) (Attr (e | b) d), Nil ]* as d -> Attr g [ Cons c (Attr g f), Nil ]* as f)" , ); } @@ -1738,7 +1733,7 @@ mod test_uniq_solve { map "# ), - "Attr Shared (Attr a [ S (Attr a b), Z ]* as b -> Attr c [ S (Attr c d), Z ]* as d)", + "Attr Shared (Attr b [ S (Attr b a), Z ]* as a -> Attr d [ S (Attr d c), Z ]* as c)", ); } @@ -1812,7 +1807,7 @@ mod test_uniq_solve { infer_eq( indoc!( r#" - { x, y } : { x : Str.Str, y : Num.Num Float.FloatingPoint } + { x, y } : { x : Str.Str, y : Num.Num Num.FloatingPoint } { x, y } = { x : "foo", y : 3.14 } x @@ -1900,7 +1895,7 @@ mod test_uniq_solve { head "# ), - "Attr * (Attr (* | a) (AssocList (Attr b k) (Attr c v)) -> Attr * (Maybe (Attr a { key : (Attr b k), value : (Attr c v) })))" + "Attr * (Attr (* | c) (AssocList (Attr a k) (Attr b v)) -> Attr * (Maybe (Attr c { key : (Attr a k), value : (Attr b v) })))" ); } @@ -1924,7 +1919,7 @@ mod test_uniq_solve { head "# ), - "Attr * (Attr (* | a) (ConsList (Attr a { key : (Attr c k), value : (Attr b v) })) -> Attr * (Maybe (Attr a { key : (Attr c k), value : (Attr b v) })))" + "Attr * (Attr (* | c) (ConsList (Attr c { key : (Attr a k), value : (Attr b v) })) -> Attr * (Maybe (Attr c { key : (Attr a k), value : (Attr b v) })))" ); } @@ -1944,7 +1939,7 @@ mod test_uniq_solve { map "# ), - "Attr * (Attr (b | c) (ConsList (Attr c a)) -> Attr (b | c) (ConsList (Attr c a)))", + "Attr * (Attr (c | b) (ConsList (Attr b a)) -> Attr (c | b) (ConsList (Attr b a)))", ); } @@ -2065,7 +2060,7 @@ mod test_uniq_solve { toAs "# ), - "Attr Shared (Attr Shared (Attr a b -> c), Attr (d | a | e) [ Cons (Attr e f) (Attr (d | a | e) [ Cons (Attr a b) (Attr (d | a | e) g), Nil ]*), Nil ]* as g -> Attr h [ Cons (Attr e f) (Attr * [ Cons c (Attr h i) ]*), Nil ]* as i)" + "Attr Shared (Attr Shared (Attr b a -> c), Attr (g | b | e) [ Cons (Attr e d) (Attr (g | b | e) [ Cons (Attr b a) (Attr (g | b | e) f), Nil ]*), Nil ]* as f -> Attr i [ Cons (Attr e d) (Attr * [ Cons c (Attr i h) ]*), Nil ]* as h)" ); } @@ -2099,10 +2094,10 @@ mod test_uniq_solve { infer_eq( indoc!( r#" - Float.highest / Float.highest - "# + Num.maxFloat / Num.maxFloat + "# ), - "Attr * Float", + "Attr * (Result (Attr * Float) (Attr * [ DivByZero ]*))", ); } @@ -2111,10 +2106,10 @@ mod test_uniq_solve { infer_eq( indoc!( r#" - 3.0 / 4.0 - "# + 3.0 / 4.0 + "# ), - "Attr * Float", + "Attr * (Result (Attr * Float) (Attr * [ DivByZero ]*))", ); } @@ -2123,10 +2118,10 @@ mod test_uniq_solve { infer_eq( indoc!( r#" - 3.0 / Float.highest - "# + 3.0 / Num.maxFloat + "# ), - "Attr * Float", + "Attr * (Result (Attr * Float) (Attr * [ DivByZero ]*))", ); } @@ -2135,8 +2130,8 @@ mod test_uniq_solve { infer_eq( indoc!( r#" - Int.highest // Int.highest - "# + Num.maxInt // Num.maxInt + "# ), "Attr * (Result (Attr * Int) (Attr * [ DivByZero ]*))", ); @@ -2147,8 +2142,8 @@ mod test_uniq_solve { infer_eq( indoc!( r#" - 3 // 4 - "# + 3 // 4 + "# ), "Attr * (Result (Attr * Int) (Attr * [ DivByZero ]*))", ); @@ -2159,8 +2154,8 @@ mod test_uniq_solve { infer_eq( indoc!( r#" - 3 // Int.highest - "# + 3 // Num.maxInt + "# ), "Attr * (Result (Attr * Int) (Attr * [ DivByZero ]*))", ); @@ -2171,12 +2166,12 @@ mod test_uniq_solve { infer_eq( indoc!( r#" - \list -> - p = List.get list 1 - q = List.get list 1 + \list -> + p = List.get list 1 + q = List.get list 1 - { p, q } - "# + { p, q } + "# ), "Attr * (Attr * (List (Attr Shared a)) -> Attr * { p : (Attr * (Result (Attr Shared a) (Attr * [ OutOfBounds ]*))), q : (Attr * (Result (Attr Shared a) (Attr * [ OutOfBounds ]*))) })" ); @@ -2196,7 +2191,7 @@ mod test_uniq_solve { list "# ), - "Attr * (Attr a (List (Attr Shared (Num (Attr Shared b)))) -> Attr a (List (Attr Shared (Num (Attr Shared b)))))", + "Attr * (Attr b (List (Attr Shared (Num (Attr Shared a)))) -> Attr b (List (Attr Shared (Num (Attr Shared a)))))", ); } @@ -2212,7 +2207,7 @@ mod test_uniq_solve { List.set list 0 42 "# ), - "Attr * (Attr (a | b) (List (Attr b (Num (Attr c d)))) -> Attr (a | b) (List (Attr b (Num (Attr c d)))))", + "Attr * (Attr (d | c) (List (Attr c (Num (Attr b a)))) -> Attr (d | c) (List (Attr c (Num (Attr b a)))))", ); } @@ -2234,7 +2229,7 @@ mod test_uniq_solve { sum "# ), - "Attr * (Attr (* | a) (List (Attr a (Num (Attr a b)))) -> Attr c (Num (Attr c b)))", + "Attr * (Attr (* | b) (List (Attr b (Num (Attr b a)))) -> Attr c (Num (Attr c a)))", ); } @@ -2242,7 +2237,7 @@ mod test_uniq_solve { fn num_add() { infer_eq( "Num.add", - "Attr * (Attr a (Num (Attr a b)), Attr c (Num (Attr c b)) -> Attr d (Num (Attr d b)))", + "Attr * (Attr b (Num (Attr b a)), Attr c (Num (Attr c a)) -> Attr d (Num (Attr d a)))", ); } @@ -2258,12 +2253,12 @@ mod test_uniq_solve { #[test] fn list_get() { - infer_eq("List.get", "Attr * (Attr (* | a) (List (Attr a b)), Attr * Int -> Attr * (Result (Attr a b) (Attr * [ OutOfBounds ]*)))"); + infer_eq("List.get", "Attr * (Attr (* | b) (List (Attr b a)), Attr * Int -> Attr * (Result (Attr b a) (Attr * [ OutOfBounds ]*)))"); } #[test] fn list_set() { - infer_eq("List.set", "Attr * (Attr (* | a | b) (List (Attr a c)), Attr * Int, Attr (a | b) c -> Attr * (List (Attr a c)))"); + infer_eq("List.set", "Attr * (Attr (* | b | c) (List (Attr b a)), Attr * Int, Attr (b | c) a -> Attr * (List (Attr b a)))"); } #[test] @@ -2299,7 +2294,7 @@ mod test_uniq_solve { fn list_foldr() { infer_eq( "List.foldr", - "Attr * (Attr (* | a) (List (Attr a b)), Attr Shared (Attr a b, c -> c), c -> c)", + "Attr * (Attr (* | b) (List (Attr b a)), Attr Shared (Attr b a, c -> c), c -> c)", ); } @@ -2327,7 +2322,7 @@ mod test_uniq_solve { reverse "# ), - "Attr * (Attr (* | a) (List (Attr a b)) -> Attr * (List (Attr a b)))", + "Attr * (Attr (* | b) (List (Attr b a)) -> Attr * (List (Attr b a)))", ); } @@ -2336,7 +2331,9 @@ mod test_uniq_solve { infer_eq( indoc!( r#" - List.getUnsafe (List.set [ 12, 9, 7, 3 ] 1 42) 1 + when List.get (List.set [ 12, 9, 7, 3 ] 1 42) 1 is + Ok num -> num + Err OutOfBounds -> 0 "# ), "Attr * (Num (Attr * *))", @@ -2373,7 +2370,7 @@ mod test_uniq_solve { fn set_foldl() { infer_eq( "Set.foldl", - "Attr * (Attr (* | a) (Set (Attr a b)), Attr Shared (Attr a b, c -> c), c -> c)", + "Attr * (Attr (* | b) (Set (Attr b a)), Attr Shared (Attr b a, c -> c), c -> c)", ); } @@ -2386,7 +2383,7 @@ mod test_uniq_solve { fn set_remove() { infer_eq( "Set.remove", - "Attr * (Attr * (Set (Attr a b)), Attr * b -> Attr * (Set (Attr a b)))", + "Attr * (Attr * (Set (Attr b a)), Attr * a -> Attr * (Set (Attr b a)))", ); } @@ -2402,7 +2399,7 @@ mod test_uniq_solve { #[test] fn map_get() { - infer_eq("Map.get", "Attr * (Attr (* | a) (Map (Attr * b) (Attr a c)), Attr * b -> Attr * (Result (Attr a c) (Attr * [ KeyNotFound ]*)))"); + infer_eq("Map.get", "Attr * (Attr (* | c) (Map (Attr * a) (Attr c b)), Attr * a -> Attr * (Result (Attr c b) (Attr * [ KeyNotFound ]*)))"); } #[test] @@ -2562,8 +2559,7 @@ mod test_uniq_solve { map "# ), - // "Attr * (Attr (* | c | d) (Result (Attr c a) (Attr d e)), Attr * (Attr c a -> Attr f b) -> Attr * (Result (Attr f b) (Attr d e)))" - "Attr * (Attr (* | c | d) (Result (Attr d a) (Attr c e)), Attr * (Attr d a -> Attr f b) -> Attr * (Result (Attr f b) (Attr c e)))" + "Attr * (Attr (* | c | d) (Result (Attr c a) (Attr d e)), Attr * (Attr c a -> Attr f b) -> Attr * (Result (Attr f b) (Attr d e)))" ); } @@ -2581,8 +2577,7 @@ mod test_uniq_solve { withDefault "# ), - // "Attr * (Attr (* | b | c) (Result (Attr b a) (Attr c e)), Attr b a -> Attr b a)", - "Attr * (Attr (* | b | c) (Result (Attr c a) (Attr b e)), Attr c a -> Attr c a)", + "Attr * (Attr (* | b | c) (Result (Attr b a) (Attr c e)), Attr b a -> Attr b a)", ); } @@ -2597,7 +2592,7 @@ mod test_uniq_solve { Err _ -> default "# ), - "Attr * (Attr (* | a | b) [ Err (Attr a *), Ok (Attr b c) ]*, Attr b c -> Attr b c)", + "Attr * (Attr (* | a | c) [ Err (Attr a *), Ok (Attr c b) ]*, Attr c b -> Attr c b)", ); } @@ -2685,7 +2680,7 @@ mod test_uniq_solve { f "# ), - "Attr * (Attr a (Num (Attr a b)), Attr c (Num (Attr c b)) -> Attr d (Num (Attr d b)))", + "Attr * (Attr b (Num (Attr b a)), Attr c (Num (Attr c a)) -> Attr d (Num (Attr d a)))", ); } @@ -2712,7 +2707,7 @@ mod test_uniq_solve { \x -> Num.abs x "# ), - "Attr * (Attr a (Num (Attr a b)) -> Attr c (Num (Attr c b)))", + "Attr * (Attr b (Num (Attr b a)) -> Attr c (Num (Attr c a)))", ); } diff --git a/compiler/types/src/pretty_print.rs b/compiler/types/src/pretty_print.rs index 71afd198eb..1e85280f94 100644 --- a/compiler/types/src/pretty_print.rs +++ b/compiler/types/src/pretty_print.rs @@ -140,6 +140,11 @@ fn find_names_needed( // We must not accidentally generate names that collide with them! names_taken.insert(name); } + Structure(Apply(Symbol::ATTR_ATTR, args)) => { + // assign uniqueness var names based on when they occur in the base type + find_names_needed(args[1], subs, roots, root_appearances, names_taken); + find_names_needed(args[0], subs, roots, root_appearances, names_taken); + } Structure(Apply(_, args)) => { for var in args { find_names_needed(var, subs, roots, root_appearances, names_taken); @@ -153,21 +158,30 @@ fn find_names_needed( find_names_needed(ret_var, subs, roots, root_appearances, names_taken); } Structure(Record(fields, ext_var)) => { - for (_, var) in fields { - find_names_needed(var, subs, roots, root_appearances, names_taken); + let mut sorted_fields: Vec<_> = fields.iter().collect(); + sorted_fields.sort(); + + for (_, var) in sorted_fields { + find_names_needed(*var, subs, roots, root_appearances, names_taken); } find_names_needed(ext_var, subs, roots, root_appearances, names_taken); } Structure(TagUnion(tags, ext_var)) => { - for var in tags.values().flatten() { + let mut sorted_tags: Vec<_> = tags.iter().collect(); + sorted_tags.sort(); + + for var in sorted_tags.into_iter().map(|(_, v)| v).flatten() { find_names_needed(*var, subs, roots, root_appearances, names_taken); } find_names_needed(ext_var, subs, roots, root_appearances, names_taken); } Structure(RecursiveTagUnion(rec_var, tags, ext_var)) => { - for var in tags.values().flatten() { + let mut sorted_tags: Vec<_> = tags.iter().collect(); + sorted_tags.sort(); + + for var in sorted_tags.into_iter().map(|(_, v)| v).flatten() { find_names_needed(*var, subs, roots, root_appearances, names_taken); } @@ -178,6 +192,7 @@ fn find_names_needed( Bool::Shared => {} Bool::Container(cvar, mvars) => { find_names_needed(cvar, subs, roots, root_appearances, names_taken); + for var in mvars { find_names_needed(var, subs, roots, root_appearances, names_taken); } @@ -276,7 +291,8 @@ fn write_content(env: &Env, content: Content, subs: &Subs, buf: &mut String, par match symbol { Symbol::NUM_NUM => { - debug_assert!(args.len() == 1); + debug_assert_eq!(args.len(), 1); + let (_, arg_var) = args .get(0) .expect("Num was not applied to a type argument!"); @@ -284,8 +300,8 @@ fn write_content(env: &Env, content: Content, subs: &Subs, buf: &mut String, par match &content { Alias(nested, _, _) => match *nested { - Symbol::INT_INTEGER => buf.push_str("Int"), - Symbol::FLOAT_FLOATINGPOINT => buf.push_str("Float"), + Symbol::NUM_INTEGER => buf.push_str("Int"), + Symbol::NUM_FLOATINGPOINT => buf.push_str("Float"), _ => write_parens!(write_parens, buf, { buf.push_str("Num "); @@ -297,8 +313,8 @@ fn write_content(env: &Env, content: Content, subs: &Subs, buf: &mut String, par let attr_content = subs.get_without_compacting(nested_args[1]).content; match &attr_content { Alias(nested, _, _) => match *nested { - Symbol::INT_INTEGER => buf.push_str("Int"), - Symbol::FLOAT_FLOATINGPOINT => buf.push_str("Float"), + Symbol::NUM_INTEGER => buf.push_str("Int"), + Symbol::NUM_FLOATINGPOINT => buf.push_str("Float"), _ => write_parens!(write_parens, buf, { buf.push_str("Num "); write_content(env, content, subs, buf, parens); @@ -554,7 +570,8 @@ pub fn chase_ext_tag_union( chase_ext_tag_union(subs, ext_var, fields) } Content::Structure(Apply(Symbol::ATTR_ATTR, arguments)) => { - debug_assert!(arguments.len() == 2); + debug_assert_eq!(arguments.len(), 2); + chase_ext_tag_union(subs, arguments[1], fields) } Content::Alias(_, _, var) => chase_ext_tag_union(subs, var, fields), @@ -581,7 +598,8 @@ pub fn chase_ext_record( Structure(EmptyRecord) => Ok(()), Content::Structure(Apply(Symbol::ATTR_ATTR, arguments)) => { - debug_assert!(arguments.len() == 2); + debug_assert_eq!(arguments.len(), 2); + chase_ext_record(subs, arguments[1], fields) } @@ -689,10 +707,10 @@ fn write_apply( match &arg_content { Content::Structure(FlatType::Apply(symbol, nested_args)) => match *symbol { - Symbol::INT_INTEGER if nested_args.is_empty() => { + Symbol::NUM_INTEGER if nested_args.is_empty() => { buf.push_str("Int"); } - Symbol::FLOAT_FLOATINGPOINT if nested_args.is_empty() => { + Symbol::NUM_FLOATINGPOINT if nested_args.is_empty() => { buf.push_str("Float"); } Symbol::ATTR_ATTR => match nested_args @@ -703,10 +721,10 @@ fn write_apply( double_nested_symbol, double_nested_args, ))) => match double_nested_symbol { - Symbol::INT_INTEGER if double_nested_args.is_empty() => { + Symbol::NUM_INTEGER if double_nested_args.is_empty() => { buf.push_str("Int"); } - Symbol::FLOAT_FLOATINGPOINT if double_nested_args.is_empty() => { + Symbol::NUM_FLOATINGPOINT if double_nested_args.is_empty() => { buf.push_str("Float"); } _ => default_case(subs, arg_content), diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 2be37f3d01..c1870d3dff 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -545,6 +545,22 @@ impl Content { _ => false, } } + + #[cfg(debug_assertions)] + #[allow(dead_code)] + pub fn dbg(self, subs: &Subs) -> Self { + let home = roc_module::symbol::ModuleIds::default().get_or_insert(&"#Dbg".into()); + let mut interns = roc_module::symbol::Interns::default(); + + interns.all_ident_ids = roc_module::symbol::IdentIds::exposed_builtins(0); + + eprintln!( + "{}", + crate::pretty_print::content_to_string(self.clone(), subs, home, &interns) + ); + + self + } } #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index fb08d0a0b6..daed54a647 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -4,6 +4,7 @@ use crate::subs::{Subs, VarStore, Variable}; use inlinable_string::InlinableString; use roc_collections::all::{union, ImMap, ImSet, Index, MutMap, MutSet, SendMap}; use roc_module::ident::{Ident, Lowercase, TagName}; +use roc_module::low_level::LowLevel; use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_region::all::{Located, Region}; use std::fmt; @@ -747,6 +748,10 @@ pub enum Reason { name: Option, arity: u8, }, + LowLevelOpArg { + op: LowLevel, + arg_index: Index, + }, FloatLiteral, IntLiteral, NumLiteral, @@ -771,6 +776,7 @@ pub enum Reason { pub enum Category { Lookup(Symbol), CallResult(Option), + LowLevelOpResult(LowLevel), TagApply(TagName), Lambda, Uniqueness, @@ -919,10 +925,10 @@ fn write_error_type_help( let argument = arguments.remove(0).1; match argument { - Type(Symbol::INT_INTEGER, _) => { + Type(Symbol::NUM_INTEGER, _) => { buf.push_str("Int"); } - Type(Symbol::FLOAT_FLOATINGPOINT, _) => { + Type(Symbol::NUM_FLOATINGPOINT, _) => { buf.push_str("Float"); } other => { @@ -1020,10 +1026,10 @@ fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens: let argument = arguments.remove(0).1; match argument { - Type(Symbol::INT_INTEGER, _) => { + Type(Symbol::NUM_INTEGER, _) => { buf.push_str("Int"); } - Type(Symbol::FLOAT_FLOATINGPOINT, _) => { + Type(Symbol::NUM_FLOATINGPOINT, _) => { buf.push_str("Float"); } other => { diff --git a/compiler/uniq/src/builtins.rs b/compiler/uniq/src/builtins.rs index 2dee07af8c..5fa7afa6b0 100644 --- a/compiler/uniq/src/builtins.rs +++ b/compiler/uniq/src/builtins.rs @@ -14,7 +14,7 @@ use roc_types::types::Type::{self, *}; pub fn int_literal(num_var: Variable, expected: Expected, region: Region) -> Constraint { let num_type = Variable(num_var); let reason = Reason::IntLiteral; - let int_type = builtin_type(Symbol::INT_INT, vec![]); + let int_type = builtin_type(Symbol::NUM_INT, vec![]); let expected_literal = ForReason(reason, int_type, region); exists( @@ -30,7 +30,7 @@ pub fn int_literal(num_var: Variable, expected: Expected, region: Region) pub fn float_literal(num_var: Variable, expected: Expected, region: Region) -> Constraint { let num_type = Variable(num_var); let reason = Reason::FloatLiteral; - let float_type = builtin_type(Symbol::FLOAT_FLOAT, vec![]); + let float_type = builtin_type(Symbol::NUM_FLOAT, vec![]); let expected_literal = ForReason(reason, float_type, region); exists( diff --git a/compiler/uniq/src/sharing.rs b/compiler/uniq/src/sharing.rs index 3b8468fad9..09f32f715f 100644 --- a/compiler/uniq/src/sharing.rs +++ b/compiler/uniq/src/sharing.rs @@ -527,7 +527,8 @@ pub fn annotate_usage(expr: &Expr, usage: &mut VarUsage) { | Str(_) | BlockStr(_) | EmptyRecord - | Accessor { .. } => {} + | Accessor { .. } + | RunLowLevel { .. } => {} Var(symbol) => usage.register_unique(*symbol), @@ -720,7 +721,7 @@ fn special_case_builtins( } Symbol::LIST_SET => { - debug_assert!(loc_args.len() == 3); + debug_assert_eq!(loc_args.len(), 3); let loc_list = &loc_args[0].1; let loc_index = &loc_args[1].1; diff --git a/compiler/uniq/tests/test_usage_analysis.rs b/compiler/uniq/tests/test_usage_analysis.rs index f8ed992d85..f34ce92fe6 100644 --- a/compiler/uniq/tests/test_usage_analysis.rs +++ b/compiler/uniq/tests/test_usage_analysis.rs @@ -18,19 +18,14 @@ mod test_usage_analysis { use roc_collections::all::{ImMap, ImSet}; use roc_module::ident::Lowercase; use roc_module::symbol::{Interns, Symbol}; - use roc_uniq::sharing; - use roc_uniq::sharing::FieldAccess; - use roc_uniq::sharing::VarUsage; - use roc_uniq::sharing::{Container, Mark, Usage}; + use roc_uniq::sharing::{self, Container, FieldAccess, Mark, Usage, VarUsage}; + use std::collections::HashMap; use Container::*; use Mark::*; use Usage::*; - fn field_access_seq( - accesses: Vec>, - expected_ref: std::collections::HashMap<&str, Usage>, - ) { + fn field_access_seq(accesses: Vec>, expected_ref: HashMap<&str, Usage>) { use Mark::*; use Usage::*; @@ -43,13 +38,12 @@ mod test_usage_analysis { match usage { Usage::Access(_, _, fields) => { - let mut actual: std::collections::HashMap = - std::collections::HashMap::default(); + let mut actual: HashMap = HashMap::default(); for (k, v) in fields.into_iter() { actual.insert(k, v); } - let mut expected = std::collections::HashMap::default(); + let mut expected = HashMap::default(); for (k, v) in expected_ref { expected.insert(k.into(), v); } @@ -60,10 +54,7 @@ mod test_usage_analysis { } } - fn field_access_par( - accesses: Vec>, - expected_ref: std::collections::HashMap<&str, Usage>, - ) { + fn field_access_par(accesses: Vec>, expected_ref: HashMap<&str, Usage>) { use Mark::*; use Usage::*; @@ -76,13 +67,12 @@ mod test_usage_analysis { match usage { Usage::Access(_, _, fields) => { - let mut actual: std::collections::HashMap = - std::collections::HashMap::default(); + let mut actual: HashMap = HashMap::default(); for (k, v) in fields.into_iter() { actual.insert(k, v); } - let mut expected = std::collections::HashMap::default(); + let mut expected = HashMap::default(); for (k, v) in expected_ref { expected.insert(k.into(), v); } @@ -93,7 +83,7 @@ mod test_usage_analysis { } } - fn field_access(fields: std::collections::HashMap<&str, Usage>) -> FieldAccess { + fn field_access(fields: HashMap<&str, Usage>) -> FieldAccess { let mut new_fields = ImMap::default(); for (k, v) in fields { diff --git a/roc-for-elm-programmers.md b/roc-for-elm-programmers.md index eb852846b1..e4f733b786 100644 --- a/roc-for-elm-programmers.md +++ b/roc-for-elm-programmers.md @@ -362,7 +362,7 @@ Now let's say I do a pattern match with no type annotations. ```elm when foo is MyInt num -> num + 1 - MyFloat float -> Float.round float + MyFloat float -> Num.round float ``` The inferred type of this expression would be `[ MyInt Int, MyFloat Float ]`, @@ -386,7 +386,7 @@ toInt : [ Foo, Bar Float ] -> Int toInt = \tag -> when tag is Foo -> 1 - Bar float -> Float.round float + Bar float -> Num.round float ``` Each of these type annotations involves a *tag union* - a collection of tags bracketed by `[` and `]`. @@ -837,7 +837,7 @@ If you put these into a hypothetical Roc REPL, here's what you'd see: In Elm, operators are functions. In Roc, all operators are syntax sugar. This means, for example, that you cannot write `(/)` in Roc; that would be a syntax -error. However, the `/` operator in Roc is infix syntax sugar for `Float.div`, +error. However, the `/` operator in Roc is infix syntax sugar for `Num.div`, which is a normal function you can pass to anything you like. Elm has one unary operator, namely `-`. (In Elm, `-x` means @@ -915,11 +915,11 @@ a b c In Roc, the `|>` operator inserts the previous expression as the *first* argument to the subsequent expression, rather than as the *last* argument as it does in Elm. -This makes a number of operations more useful in pipelines. For example, in Roc, `|> Float.div 2.0` divides by 2: +This makes a number of operations more useful in pipelines. For example, in Roc, `|> Num.div 2.0` divides by 2: ```elixir 2000 - |> Float.div 2.0 + |> Num.div 2.0 # 1000.0 : Float ``` @@ -1032,11 +1032,11 @@ Here are various Roc expressions involving operators, and what they desugar to. | `a + b` | `Num.add a b` | | `a - b` | `Num.sub a b` | | `a * b` | `Num.mul a b` | -| `a / b` | `Float.div a b` | -| `a // b` | `Int.div a b` | +| `a / b` | `Num.div a b` | +| `a // b` | `Num.divFloor a b` | | `a ^ b` | `Num.pow a b` | -| `a % b` | `Float.rem a b` | -| `a %% b` | `Float.mod a b` | +| `a % b` | `Num.rem a b` | +| `a %% b` | `Num.mod a b` | | `-a` | `Num.neg a` | | `-f x y` | `Num.neg (f x y)` | | `a == b` | `Bool.isEq a b` |