diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d36f478721..5d74085bac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 + - name: Install LLVM run: sudo ./ci/install-llvm.sh 8 @@ -20,6 +21,22 @@ jobs: - run: rustup component add rustfmt + - name: Cache cargo registry + uses: actions/cache@v1 + with: + path: ~/.cargo/registry + key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + - name: Cache cargo index + uses: actions/cache@v1 + with: + path: ~/.cargo/git + key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} + - name: Cache cargo build + uses: actions/cache@v1 + with: + path: target + key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} + - uses: actions-rs/cargo@v1 name: cargo fmt --check with: diff --git a/Cargo.lock b/Cargo.lock index f59e5b3e71..d4a91b57ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -39,6 +39,11 @@ name = "bumpalo" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "byteorder" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "bytes" version = "0.5.3" @@ -62,6 +67,105 @@ dependencies = [ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "cranelift" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cranelift-codegen 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift-frontend 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cranelift-bforest" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cranelift-entity 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cranelift-codegen" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift-bforest 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift-codegen-meta 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift-codegen-shared 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift-entity 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "target-lexicon 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cranelift-codegen-shared 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift-entity 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cranelift-entity" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cranelift-frontend" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cranelift-codegen 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "target-lexicon 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cranelift-module" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cranelift-codegen 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift-entity 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cranelift-native" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cranelift-codegen 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)", + "raw-cpuid 7.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "target-lexicon 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cranelift-simplejit" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cranelift-codegen 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift-module 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift-native 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)", + "errno 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "region 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "target-lexicon 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "difference" version = "2.0.0" @@ -81,6 +185,25 @@ dependencies = [ "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "errno" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "errno-dragonfly 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "fixedbitset" version = "0.1.9" @@ -178,6 +301,11 @@ dependencies = [ "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "gcc" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "hermit-abi" version = "0.1.5" @@ -300,6 +428,14 @@ dependencies = [ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "mach" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "maplit" version = "1.0.2" @@ -556,6 +692,16 @@ dependencies = [ "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "raw-cpuid" +version = "7.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rdrand" version = "0.4.0" @@ -585,11 +731,26 @@ name = "regex-syntax" version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "region" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "mach 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "roc" version = "0.1.0" dependencies = [ "bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift-codegen 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift-module 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift-simplejit 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "im 14.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "im-rc 14.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -603,10 +764,19 @@ dependencies = [ "pretty_assertions 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "quickcheck 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)", "quickcheck_macros 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "target-lexicon 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "tokio 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "wyhash 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "scopeguard" version = "1.0.0" @@ -664,6 +834,29 @@ dependencies = [ "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "target-lexicon" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "thiserror" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "thiserror-impl 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "thread_local" version = "0.3.6" @@ -742,13 +935,26 @@ dependencies = [ "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" "checksum bitmaps 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "81e039a80914325b37fde728ef7693c212f0ac913d5599607d7b95a9484aae0b" "checksum bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ad807f2fc2bf185eeb98ff3a901bd46dc5ad58163d0fa4577ba0d25674d71708" +"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" "checksum bytes 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "10004c15deb332055f7a4a208190aed362cf9a7c2f6ab70a305fba50e1105f38" "checksum cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)" = "f52a465a666ca3d838ebbf08b241383421412fe7ebb463527bba275526d89f76" "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +"checksum cranelift 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f0f3b64a6eff63e7ea2dc39ecd36fa43f9f790c8c81ad802748bf51981409701" +"checksum cranelift-bforest 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)" = "56aa72ef104c5d634f2f9e84ef2c47e116c1d185fae13f196b97ca84b0a514f1" +"checksum cranelift-codegen 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)" = "460b9d20793543599308d22f5a1172c196e63a780c4e9aacb0b3f4f63d63ffe1" +"checksum cranelift-codegen-meta 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cc70e4e8ccebd53a4f925147def857c9e9f7fe0fdbef4bb645a420473e012f50" +"checksum cranelift-codegen-shared 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3992000be4d18df0fe332b7c42c120de896e8ec54cd7b6cfa050910a8c9f6e2f" +"checksum cranelift-entity 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)" = "722957e05064d97a3157bf0976deed0f3e8ee4f8a4ce167a7c724ca63a4e8bd9" +"checksum cranelift-frontend 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)" = "13051964302dc7948e8869735de42591559ea55e319b9b92da5b38f8e6a75cb7" +"checksum cranelift-module 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f1632d5670f0b02ce967a385b859b7af9593485f6e198ebf0970fc8b0f8f9841" +"checksum cranelift-native 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)" = "21398a0bc6ba389ea86964ac4a495426dd61080f2ddd306184777a8560fe9976" +"checksum cranelift-simplejit 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1236096fd657486d6ef35c8958a681f3f1377d42d7dc371ef9c7193e65c07521" "checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" "checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" "checksum env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3" +"checksum errno 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c2a071601ed01b988f896ab14b95e67335d1eeb50190932a1320f7fe3cadc84e" +"checksum errno-dragonfly 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067" "checksum fixedbitset 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "86d4de0081402f5e88cdac65c8dcdcc73118c1a7a465e2a05f0da05843a8ea33" "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" @@ -761,6 +967,7 @@ dependencies = [ "checksum futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "171be33efae63c2d59e6dbba34186fe0d6394fb378069a76dfd80fdcffd43c16" "checksum futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0bae52d6b29cf440e298856fec3965ee6fa71b06aa7495178615953fd669e5f9" "checksum futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c0d66274fb76985d3c62c886d1da7ac4c0903a8c9f754e8fe0f35a6a6cc39e76" +"checksum gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)" = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" "checksum hermit-abi 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f629dc602392d3ec14bfc8a09b5e644d7ffd725102b48b81e59f90f2633621d7" "checksum im 14.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b047368c60cde48aa2b2e2fa4794eec965526749b60b55f8cec1bad926e7f6e9" "checksum im-rc 14.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ed5135086ffe74654d797c02fd673c4046cdb7f552c98f1b1aa6851d6572f84f" @@ -774,6 +981,7 @@ dependencies = [ "checksum llvm-sys 80.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cf2969773884a5701f0c255e2a14d48d4522a66db898ec1088cb21879a228377" "checksum lock_api 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e57b3997725d2b60dbec1297f6c2e2957cc383db1cebd6be812163f969c7d586" "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +"checksum mach 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "86dd2487cdfea56def77b88438a2c915fb45113c5319bfe7e14306ca4cd0b0e1" "checksum maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" "checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" "checksum num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "76dac5ed2a876980778b8b85f75a71b6cbf0db0b1232ee12f826bccb00d09d72" @@ -805,10 +1013,13 @@ dependencies = [ "checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" "checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" "checksum rand_xoshiro 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9fcdd2e881d02f1d9390ae47ad8e5696a9e4be7b547a1da2afbc61973217004" +"checksum raw-cpuid 7.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b4a349ca83373cfa5d6dbb66fd76e58b2cca08da71a5f6400de0a0a6a9bceeaf" "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" "checksum regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd" "checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" +"checksum region 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "448e868c6e4cfddfa49b6a72c95906c04e8547465e9536575b95c70a4044f856" +"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" "checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" @@ -817,6 +1028,9 @@ dependencies = [ "checksum smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44e59e0c9fa00817912ae6e4e6e3c4fe04455e75699d06eedc7d85917ed8e8f4" "checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" "checksum syn 1.0.12 (registry+https://github.com/rust-lang/crates.io-index)" = "ddc157159e2a7df58cd67b1cace10b8ed256a404fb0070593f137d8ba6bef4de" +"checksum target-lexicon 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6f4c118a7a38378f305a9e111fcb2f7f838c0be324bfb31a77ea04f7f6e684b4" +"checksum thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6f357d1814b33bc2dc221243f8424104bfe72dbe911d5b71b3816a2dff1c977e" +"checksum thiserror-impl 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2e25d25307eb8436894f727aba8f65d07adf02e5b35a13cebed48bd282bfef" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" "checksum tokio 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0e1bef565a52394086ecac0a6fa3b8ace4cb3a138ee1d96bd2b93283b56824e3" "checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" diff --git a/Cargo.toml b/Cargo.toml index 706b8de9b9..135d0a236d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,9 +22,14 @@ inlinable_string = "0.1.0" inkwell = { git = "https://github.com/TheDan64/inkwell", branch = "llvm8-0" } futures = "0.3" lazy_static = "1.4" +target-lexicon = "0.9" # NOTE: we must use the same version of target-lexicon as cranelift! +cranelift = "0.52" # All cranelift crates should have the same version! +cranelift-simplejit = "0.52" # All cranelift crates should have the same version! +cranelift-module = "0.52" # All cranelift crates should have the same version! +cranelift-codegen = "0.52" # All cranelift crates should have the same version! [dev-dependencies] -pretty_assertions = "0.5.1" +pretty_assertions = "0.5.1 " maplit = "1.0.1" indoc = "0.3.3" quickcheck = "0.8" diff --git a/roc-for-elm-programmers.md b/roc-for-elm-programmers.md index 4e264272b7..75fbec116e 100644 --- a/roc-for-elm-programmers.md +++ b/roc-for-elm-programmers.md @@ -444,6 +444,59 @@ It's not even syntactically possible for me to expose the `@UserId` tag, because > Even trying to use `==` on them would be a type mismatch, because I would be comparing > a `[ UserId.@UserId Int ]*` with a `[ Main.@UserId Int ]*`, which are incompatible. +## Phantom Types + +[Phantom types](https://medium.com/@ckoster22/advanced-types-in-elm-phantom-types-808044c5946d) +exist in Elm but not in Roc. This is because phantom types can't be defined +using type aliases (in fact, there is a custom error message in Elm if you +try to do this), and Roc only has type aliases. However, in Roc, you can achieve +the same API and runtime performance characteristics as if you had phantom types, +only using *phantom values* instead. + +A phantom value is one which affects types, but which is never read at runtime. +(It is similar to Rust's [`PhantomData`](https://doc.rust-lang.org/std/marker/struct.PhantomData.html) +but not quite the same.) +For example, let's say I wanted to define a units library - a classic example +of phantom types. I could do that like this: + +``` +Quantity units : [ @Quantity Float units ] + +km : Float -> Quantity [ Km ] +km = \quantity -> @Number quantity Km + +cm : Float -> Quantity [ Cm ] +cm = \quantity -> @Number quantity Cm + +mm : Float -> Quantity [ Mm ] +mm = \quantity -> @Number quantity Mm +``` + +Because `@Quantity` is a private tag, `Quantity` is opaque, meaning nobody +outside this module can instantiate one. That in turn means that the only +way other modules can get a `Quantity [ Km ]` value is by calling this +`km` function. Phantom types can do this too; the usual benefit they bring +is that `Quantity` would only have `units` in its type, not in its value, +which in turn means you wouldn't be carrying the value around at runtime. + +In Roc, this is also true. Because the `units` value in `@Quantity Float units` +is never read, it gets optimized away during code generation - +as if it had never been defined at all. However, the fact that it *was* +defined means it was present during type-checking, which was the whole goal. + +The above `Quantity` unit would compile to a plain `Float` representation +at runtime, because `units` gets optimized away as phantom, and the +remaining `[ @Quantity Float ]` gets unboxed to `Float`. + +> Phantom values are not quite the same as unused fields, because they +> affect types. If we had `[ @Quantity Float Str ]` and the `Str` field +> got set, but never read, it would generate a compiler warning for +> being unused, becuase it is dead code that has no impact on anything. +> +> In contrast, phantom values - those which are set but never read, *and* +> which affect types depending on which value is set, do not generate +> warnings because they have exactly this useful purpose. + ## Modules and Shadowing In Elm, my main module (where `main` lives) might begin like this: diff --git a/src/can/annotation.rs b/src/can/annotation.rs index 3ed64b116f..b37005b3d9 100644 --- a/src/can/annotation.rs +++ b/src/can/annotation.rs @@ -73,26 +73,25 @@ fn can_annotation_help( Type::Variable(var) } } - Record(fields) => { + Record { fields, ext } => { let mut field_types = SendMap::default(); - for field in fields { + + for field in fields.iter() { can_assigned_field(&field.value, var_store, rigids, &mut field_types); } - // This is a closed record, so the fragment must be {} - let fragment_type = Type::EmptyRec; + let ext_type = match ext { + Some(loc_ann) => can_annotation_help(&loc_ann.value, var_store, rigids), + None => Type::EmptyRec, + }; - Type::Record(field_types, Box::new(fragment_type)) + Type::Record(field_types, Box::new(ext_type)) } - RecordFragment(fields, fragment) => { - let mut field_types = SendMap::default(); - for field in fields { - can_assigned_field(&field.value, var_store, rigids, &mut field_types); - } - - let fragment_type = can_annotation_help(&fragment.value, var_store, rigids); - - Type::Record(field_types, Box::new(fragment_type)) + TagUnion { tags, ext } => { + panic!( + "TODO canonicalize tag union annotation: {:?} {:?}", + tags, ext + ); } SpaceBefore(nested, _) | SpaceAfter(nested, _) => { can_annotation_help(nested, var_store, rigids) @@ -118,11 +117,6 @@ fn can_assigned_field<'a>( let label = Lowercase::from(field_name.value); field_types.insert(label, field_type); } - OptionalField(field_name, _, annotation) => { - let field_type = can_annotation_help(&annotation.value, var_store, rigids); - let label = Lowercase::from(field_name.value); - field_types.insert(label, field_type); - } LabelOnly(loc_field_name) => { // Interpret { a, b } as { a : a, b : b } let field_name = Lowercase::from(loc_field_name.value); diff --git a/src/can/expr.rs b/src/can/expr.rs index 1b9601da5f..0301dd193e 100644 --- a/src/can/expr.rs +++ b/src/can/expr.rs @@ -46,16 +46,22 @@ pub enum Expr { symbol_for_lookup: Symbol, resolved_symbol: Symbol, }, - // Pattern Matching - /// When is guaranteed to be exhaustive at this point. (If it wasn't, then - /// a _ branch was added at the end that will throw a runtime error.) - /// Also, `If` is desugared into `When` matching on `False` and `_` at this point. + // Branching When { cond_var: Variable, expr_var: Variable, loc_cond: Box>, branches: Vec<(Located, Located)>, }, + If { + cond_var: Variable, + branch_var: Variable, + loc_cond: Box>, + loc_then: Box>, + loc_else: Box>, + }, + + // Let LetRec(Vec, Box>, Variable), LetNonRec(Box, Box>, Variable), @@ -554,8 +560,40 @@ pub fn canonicalize_expr( Output::default(), ) } - ast::Expr::If(_) - | ast::Expr::MalformedIdent(_) + ast::Expr::If((cond, then_branch, else_branch)) => { + let (loc_cond, mut output) = + canonicalize_expr(env, var_store, scope, cond.region, &cond.value); + let (loc_then, then_output) = canonicalize_expr( + env, + var_store, + scope, + then_branch.region, + &then_branch.value, + ); + let (loc_else, else_output) = canonicalize_expr( + env, + var_store, + scope, + else_branch.region, + &else_branch.value, + ); + + output.references = output.references.union(then_output.references); + output.references = output.references.union(else_output.references); + + ( + If { + cond_var: var_store.fresh(), + branch_var: var_store.fresh(), + loc_cond: Box::new(loc_cond), + loc_then: Box::new(loc_then), + loc_else: Box::new(loc_else), + }, + output, + ) + } + + ast::Expr::MalformedIdent(_) | ast::Expr::MalformedClosure | ast::Expr::PrecedenceConflict(_, _, _) => { panic!( @@ -973,8 +1011,6 @@ fn canonicalize_field<'a>( ) } - OptionalField(_, _, _) => panic!("invalid in expressions"), - // A label with no value, e.g. `{ name }` (this is sugar for { name: name }) LabelOnly(_) => { panic!("Somehow a LabelOnly record field was not desugared!"); diff --git a/src/can/operator.rs b/src/can/operator.rs index a36bd86bdc..1db166f67d 100644 --- a/src/can/operator.rs +++ b/src/can/operator.rs @@ -298,7 +298,6 @@ fn desugar_field<'a>( spaces, desugar_expr(arena, loc_expr), ), - OptionalField(_, _, _) => panic!("invalid in expressions"), LabelOnly(loc_str) => { // Desugar { x } into { x: x } let loc_expr = Located { diff --git a/src/can/pattern.rs b/src/can/pattern.rs index 804589cf31..29c93dbab6 100644 --- a/src/can/pattern.rs +++ b/src/can/pattern.rs @@ -17,7 +17,7 @@ use im_rc::Vector; #[derive(Clone, Debug, PartialEq)] pub enum Pattern { Identifier(Symbol), - AppliedTag(Variable, Symbol, Vec>), + AppliedTag(Variable, Symbol, Vec<(Variable, Located)>), IntLiteral(i64), FloatLiteral(f64), StrLiteral(Box), @@ -94,6 +94,32 @@ pub fn canonicalize_pattern<'a>( vec![], ) } + &Apply(tag, patterns) => { + let mut can_patterns = Vec::with_capacity(patterns.len()); + for loc_pattern in *patterns { + can_patterns.push(( + var_store.fresh(), + canonicalize_pattern( + env, + var_store, + scope, + pattern_type, + &loc_pattern.value, + loc_pattern.region, + shadowable_idents, + ), + )); + } + + let symbol = match tag.value { + GlobalTag(name) => Symbol::from_global_tag(name), + PrivateTag(name) => Symbol::from_private_tag(&env.home, name), + _ => unreachable!("Other patterns cannot be applied"), + }; + + Pattern::AppliedTag(var_store.fresh(), symbol, can_patterns) + } + &FloatLiteral(ref string) => match pattern_type { WhenBranch => { let float = finish_parsing_float(string) @@ -344,13 +370,10 @@ pub fn remove_idents(pattern: &ast::Pattern, idents: &mut ImMap { panic!("TODO implement QualifiedIdentifier pattern in remove_idents."); } - Apply(_, _) => { - panic!("TODO implement Apply pattern in remove_idents."); - // AppliedVariant(_, Some(loc_args)) => { - // for loc_arg in loc_args { - // remove_idents(loc_arg.value, idents); - // } - // } + Apply(_, patterns) => { + for loc_pattern in *patterns { + remove_idents(&loc_pattern.value, idents); + } } RecordDestructure(patterns) => { for loc_pattern in patterns { @@ -410,16 +433,10 @@ fn add_idents_from_pattern<'a>( QualifiedIdentifier(_name) => { panic!("TODO implement QualifiedIdentifier pattern."); } - Apply(_, _) => { - panic!("TODO implement Apply pattern."); - // &AppliedVariant(_, ref opt_loc_args) => match opt_loc_args { - // &None => (), - // &Some(ref loc_args) => { - // for loc_arg in loc_args.iter() { - // add_idents_from_pattern(loc_arg, scope, answer); - // } - // } - // }, + Apply(_tag, patterns) => { + for loc_pattern in *patterns { + add_idents_from_pattern(&loc_pattern.region, &loc_pattern.value, scope, answer); + } } RecordDestructure(patterns) => { diff --git a/src/constrain/builtins.rs b/src/constrain/builtins.rs index 2b9b288bb5..7addc79fdf 100644 --- a/src/constrain/builtins.rs +++ b/src/constrain/builtins.rs @@ -48,7 +48,7 @@ fn number_literal_type(module_name: &str, type_name: &str) -> Type { } #[inline(always)] -fn builtin_type(module_name: &str, type_name: &str, args: Vec) -> Type { +pub fn builtin_type(module_name: &str, type_name: &str, args: Vec) -> Type { Type::Apply { module_name: module_name.into(), name: type_name.into(), diff --git a/src/constrain/expr.rs b/src/constrain/expr.rs index c6906f1474..09ee76d5ac 100644 --- a/src/constrain/expr.rs +++ b/src/constrain/expr.rs @@ -12,7 +12,7 @@ use crate::constrain::builtins::{ use crate::constrain::pattern::{constrain_pattern, PatternState}; use crate::region::{Located, Region}; use crate::subs::Variable; -use crate::types::AnnotationSource::*; +use crate::types::AnnotationSource::{self, *}; use crate::types::Constraint::{self, *}; use crate::types::Expected::{self, *}; use crate::types::PReason; @@ -297,6 +297,89 @@ pub fn constrain_expr( ]), ) } + + If { + cond_var, + branch_var, + loc_cond, + loc_then, + loc_else, + } => { + // TODO use Bool alias here, so we don't allocate this type every time + let bool_type = Type::TagUnion( + vec![("True".into(), vec![]), ("False".into(), vec![])], + Box::new(Type::EmptyTagUnion), + ); + let expect_bool = Expected::ForReason(Reason::IfCondition, bool_type, region); + + let cond_con = Eq(Type::Variable(*cond_var), expect_bool, loc_cond.region); + + match expected { + FromAnnotation(name, arity, _, tipe) => { + let then_con = constrain_expr( + rigids, + loc_then.region, + &loc_then.value, + FromAnnotation( + name.clone(), + arity, + AnnotationSource::TypedIfBranch(0), + tipe.clone(), + ), + ); + let else_con = constrain_expr( + rigids, + loc_else.region, + &loc_else.value, + FromAnnotation( + name, + arity, + AnnotationSource::TypedIfBranch(1), + tipe.clone(), + ), + ); + + let ast_con = Eq(Type::Variable(*branch_var), NoExpectation(tipe), region); + + exists( + vec![*cond_var, *branch_var], + And(vec![cond_con, then_con, else_con, ast_con]), + ) + } + _ => { + let then_con = constrain_expr( + rigids, + loc_then.region, + &loc_then.value, + ForReason( + Reason::IfBranch { index: 0 }, + Type::Variable(*branch_var), + region, + ), + ); + let else_con = constrain_expr( + rigids, + loc_else.region, + &loc_else.value, + ForReason( + Reason::IfBranch { index: 1 }, + Type::Variable(*branch_var), + region, + ), + ); + + exists( + vec![*cond_var, *branch_var], + And(vec![ + cond_con, + then_con, + else_con, + Eq(Type::Variable(*branch_var), expected, region), + ]), + ) + } + } + } When { cond_var, expr_var, @@ -595,8 +678,6 @@ fn constrain_def_pattern(loc_pattern: &Located, expr_type: Type) -> Pat } pub fn constrain_def(rigids: &Rigids, def: &Def, body_con: Constraint) -> Constraint { - use crate::types::AnnotationSource; - let expr_var = def.expr_var; let expr_type = Type::Variable(expr_var); @@ -681,7 +762,6 @@ pub fn rec_defs_help( mut rigid_info: Info, mut flex_info: Info, ) -> Constraint { - use crate::types::AnnotationSource; for def in defs { let expr_var = def.expr_var; let expr_type = Type::Variable(expr_var); diff --git a/src/constrain/pattern.rs b/src/constrain/pattern.rs index 68ec590fd4..aa1c0ac9a5 100644 --- a/src/constrain/pattern.rs +++ b/src/constrain/pattern.rs @@ -4,7 +4,7 @@ use crate::can::symbol::Symbol; use crate::collections::SendMap; use crate::region::{Located, Region}; use crate::subs::Variable; -use crate::types::{Constraint, PExpected, PatternCategory, RecordFieldLabel, Type}; +use crate::types::{Constraint, Expected, PExpected, PatternCategory, RecordFieldLabel, Type}; pub struct PatternState { pub headers: SendMap>, @@ -85,8 +85,14 @@ pub fn constrain_pattern( field_types.insert(label.clone(), pat_type.clone()); - // TODO investigate: shouldn't guard_var be constrained somewhere? - if let Some((_guard_var, loc_guard)) = guard { + if let Some((guard_var, loc_guard)) = guard { + state.constraints.push(Constraint::Eq( + Type::Variable(*guard_var), + Expected::NoExpectation(pat_type.clone()), + region, + )); + state.vars.push(*guard_var); + constrain_pattern(&loc_guard.value, loc_guard.region, expected, state); } @@ -99,12 +105,23 @@ pub fn constrain_pattern( state.constraints.push(record_con); } - AppliedTag(ext_var, symbol, _arguments) => { + AppliedTag(ext_var, symbol, patterns) => { + let mut argument_types = Vec::with_capacity(patterns.len()); + for (pattern_var, loc_pattern) in patterns { + state.vars.push(*pattern_var); + + let pattern_type = Type::Variable(*pattern_var); + argument_types.push(pattern_type.clone()); + + let expected = PExpected::NoExpectation(pattern_type); + constrain_pattern(&loc_pattern.value, loc_pattern.region, expected, state); + } + let tag_con = Constraint::Pattern( region, PatternCategory::Ctor(symbol.clone()), Type::TagUnion( - vec![(symbol.clone(), vec![])], + vec![(symbol.clone(), argument_types)], Box::new(Type::Variable(*ext_var)), ), expected, diff --git a/src/crane/build.rs b/src/crane/build.rs new file mode 100644 index 0000000000..0c8f3b41ab --- /dev/null +++ b/src/crane/build.rs @@ -0,0 +1,477 @@ +use bumpalo::collections::Vec; +use bumpalo::Bump; +use cranelift::frontend::Switch; +use cranelift::prelude::{ + AbiParam, ExternalName, FloatCC, FunctionBuilder, FunctionBuilderContext, IntCC, MemFlags, +}; +use cranelift_codegen::ir::entities::{StackSlot, Value}; +use cranelift_codegen::ir::stackslot::{StackSlotData, StackSlotKind}; +use cranelift_codegen::ir::{immediates::Offset32, types, InstBuilder, Signature, Type}; +use cranelift_codegen::isa::TargetFrontendConfig; +use cranelift_codegen::Context; +use cranelift_module::{Backend, FuncId, Linkage, Module}; +use inlinable_string::InlinableString; + +use crate::collections::ImMap; +use crate::crane::convert::{sig_from_layout, type_from_layout, type_from_var}; +use crate::mono::expr::{Expr, Proc, Procs}; +use crate::mono::layout::{Builtin, Layout}; +use crate::subs::{Subs, Variable}; + +type Scope = ImMap; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ScopeEntry { + Stack { expr_type: Type, slot: StackSlot }, + Heap { expr_type: Type, ptr: Value }, + Arg { expr_type: Type, param: Value }, + Func { sig: Signature, func_id: FuncId }, +} + +pub struct Env<'a> { + pub arena: &'a Bump, + pub cfg: TargetFrontendConfig, + pub subs: Subs, +} + +pub fn build_expr<'a, B: Backend>( + env: &Env<'a>, + scope: &Scope, + module: &mut Module, + builder: &mut FunctionBuilder, + expr: &Expr<'a>, + procs: &Procs<'a>, +) -> Value { + use crate::mono::expr::Expr::*; + + match expr { + Int(num) => builder.ins().iconst(types::I64, *num), + Float(num) => builder.ins().f64const(*num), + Bool(val) => builder.ins().bconst(types::B1, *val), + Byte(val) => builder.ins().iconst(types::I8, *val as i64), + Cond { + cond_lhs, + cond_rhs, + pass, + fail, + cond_layout, + ret_var, + } => { + let branch = Branch2 { + cond_lhs, + cond_rhs, + pass, + fail, + cond_layout, + ret_var: *ret_var, + }; + + build_branch2(env, scope, module, builder, branch, procs) + } + Switch { + cond, + branches, + default_branch, + ret_var, + cond_var, + } => { + let ret_type = type_from_var(*ret_var, &env.subs, env.cfg); + let switch_args = SwitchArgs { + cond_var: *cond_var, + cond_expr: cond, + branches, + default_branch, + ret_type, + }; + + build_switch(env, scope, module, builder, switch_args, procs) + } + Store(ref stores, ref ret) => { + let mut scope = im_rc::HashMap::clone(scope); + let arena = &env.arena; + let subs = &env.subs; + let cfg = env.cfg; + + for (name, var, expr) in stores.iter() { + let val = build_expr(env, &scope, module, builder, &expr, procs); + let content = subs.get_without_compacting(*var).content; + let layout = Layout::from_content(arena, content, subs) + .unwrap_or_else(|()| panic!("TODO generate a runtime error for this Store!")); + let expr_type = type_from_layout(cfg, &layout, subs); + + let slot = builder.create_stack_slot(StackSlotData::new( + StackSlotKind::ExplicitSlot, + layout.stack_size(cfg), + )); + + builder.ins().stack_store(val, slot, Offset32::new(0)); + + // Make a new scope which includes the binding we just encountered. + // This should be done *after* compiling the bound expr, since any + // recursive (in the LetRec sense) bindings should already have + // been extracted as procedures. Nothing in here should need to + // access itself! + scope = im_rc::HashMap::clone(&scope); + + scope.insert(name.clone(), ScopeEntry::Stack { expr_type, slot }); + } + + build_expr(env, &scope, module, builder, ret, procs) + } + CallByName(ref name, ref args) => { + // TODO try one of these alternative strategies (preferably the latter): + // + // 1. use SIMD string comparison to compare these strings faster + // 2. pre-register Bool.or using module.add_function, and see if LLVM inlines it + // 3. intern all these strings + if name == "Bool.or" { + panic!("TODO create a branch for ||"); + } else if name == "Bool.and" { + panic!("TODO create a branch for &&"); + } else { + let mut arg_vals = Vec::with_capacity_in(args.len(), env.arena); + + for arg in args.iter() { + arg_vals.push(build_expr(env, scope, module, builder, arg, procs)); + } + + let fn_id = match scope.get(name) { + Some(ScopeEntry::Func{ func_id, .. }) => *func_id, + other => panic!( + "CallByName could not find function named {:?} in scope; instead, found {:?} in scope {:?}", + name, other, scope + ), + }; + let local_func = module.declare_func_in_func(fn_id, &mut builder.func); + let call = builder.ins().call(local_func, &arg_vals); + let results = builder.inst_results(call); + + debug_assert!(results.len() == 1); + + results[0] + } + } + FunctionPointer(ref name) => { + let fn_id = match scope.get(name) { + Some(ScopeEntry::Func{ func_id, .. }) => *func_id, + other => panic!( + "FunctionPointer could not find function named {:?} in scope; instead, found {:?} in scope {:?}", + name, other, scope + ), + }; + + let func_ref = module.declare_func_in_func(fn_id, &mut builder.func); + + builder.ins().func_addr(env.cfg.pointer_type(), func_ref) + } + CallByPointer(ref sub_expr, ref args, ref fn_var) => { + let subs = &env.subs; + let mut arg_vals = Vec::with_capacity_in(args.len(), env.arena); + + for arg in args.iter() { + arg_vals.push(build_expr(env, scope, module, builder, arg, procs)); + } + + let content = subs.get_without_compacting(*fn_var).content; + let layout = Layout::from_content(env.arena, content, &subs) + .unwrap_or_else(|()| panic!("TODO generate a runtime error here!")); + let sig = sig_from_layout(env.cfg, module, layout, &subs); + let callee = build_expr(env, scope, module, builder, sub_expr, procs); + let sig_ref = builder.import_signature(sig); + let call = builder.ins().call_indirect(sig_ref, callee, &arg_vals); + let results = builder.inst_results(call); + + debug_assert!(results.len() == 1); + + results[0] + } + Load(name) => match scope.get(name) { + Some(ScopeEntry::Stack { expr_type, slot }) => { + builder + .ins() + .stack_load(*expr_type, *slot, Offset32::new(0)) + } + Some(ScopeEntry::Arg { param, .. }) => *param, + Some(ScopeEntry::Heap { expr_type, ptr }) => { + builder + .ins() + .load(*expr_type, MemFlags::new(), *ptr, Offset32::new(0)) + } + Some(ScopeEntry::Func { .. }) => { + panic!("TODO I don't yet know how to return fn pointers") + } + None => panic!("Could not find a var for {:?} in scope {:?}", name, scope), + }, + _ => { + panic!("I don't yet know how to crane build {:?}", expr); + } + } +} + +struct Branch2<'a> { + cond_lhs: &'a Expr<'a>, + cond_rhs: &'a Expr<'a>, + cond_layout: &'a Layout<'a>, + pass: &'a Expr<'a>, + fail: &'a Expr<'a>, + ret_var: Variable, +} + +fn build_branch2<'a, B: Backend>( + env: &Env<'a>, + scope: &Scope, + module: &mut Module, + builder: &mut FunctionBuilder, + branch: Branch2<'a>, + procs: &Procs<'a>, +) -> Value { + let subs = &env.subs; + let cfg = env.cfg; + + // Declare a variable which each branch will mutate to be the value of that branch. + // At the end of the expression, we will evaluate to this. + let ret_type = type_from_var(branch.ret_var, subs, cfg); + let ret = cranelift::frontend::Variable::with_u32(0); + + // The block we'll jump to once the switch has completed. + let ret_block = builder.create_ebb(); + + builder.declare_var(ret, ret_type); + + let lhs = build_expr(env, scope, module, builder, branch.cond_lhs, procs); + let rhs = build_expr(env, scope, module, builder, branch.cond_rhs, procs); + let pass_block = builder.create_ebb(); + let fail_block = builder.create_ebb(); + + match branch.cond_layout { + Layout::Builtin(Builtin::Float64) => { + // For floats, first do a `fcmp` comparison to get a bool answer about equality, + // then use `brnz` to branch if that bool equality answer was nonzero (aka true). + let is_eq = builder.ins().fcmp(FloatCC::Equal, lhs, rhs); + + builder.ins().brnz(is_eq, pass_block, &[]); + } + Layout::Builtin(Builtin::Int64) => { + // For ints, we can compare and branch in the same instruction: `icmp` + builder + .ins() + .br_icmp(IntCC::Equal, lhs, rhs, pass_block, &[]); + } + other => panic!("I don't know how to build a conditional for {:?}", other), + } + + // Unconditionally jump to fail_block (if we didn't just jump to pass_block). + builder.ins().jump(fail_block, &[]); + + let mut build_branch = |expr, block| { + builder.switch_to_block(block); + + // TODO re-enable this once Switch stops making unsealed + // EBBs, e.g. https://docs.rs/cranelift-frontend/0.52.0/src/cranelift_frontend/switch.rs.html#143 + // builder.seal_block(block); + + // Mutate the ret variable to be the outcome of this branch. + let value = build_expr(env, scope, module, builder, expr, procs); + + builder.def_var(ret, value); + + // Unconditionally jump to ret_block, making the whole expression evaluate to ret. + builder.ins().jump(ret_block, &[]); + }; + + build_branch(branch.pass, pass_block); + build_branch(branch.fail, fail_block); + + // Finally, build ret_block - which contains our terminator instruction. + { + builder.switch_to_block(ret_block); + // TODO re-enable this once Switch stops making unsealed + // EBBs, e.g. https://docs.rs/cranelift-frontend/0.52.0/src/cranelift_frontend/switch.rs.html#143 + // builder.seal_block(block); + + // Now that ret has been mutated by the switch statement, evaluate to it. + builder.use_var(ret) + } +} +struct SwitchArgs<'a> { + pub cond_expr: &'a Expr<'a>, + pub cond_var: Variable, + pub branches: &'a [(u64, Expr<'a>)], + pub default_branch: &'a Expr<'a>, + pub ret_type: Type, +} + +fn build_switch<'a, B: Backend>( + env: &Env<'a>, + scope: &Scope, + module: &mut Module, + builder: &mut FunctionBuilder, + switch_args: SwitchArgs<'a>, + procs: &Procs<'a>, +) -> Value { + let mut switch = Switch::new(); + let SwitchArgs { + branches, + cond_expr, + default_branch, + ret_type, + .. + } = switch_args; + let mut blocks = Vec::with_capacity_in(branches.len(), env.arena); + + // Declare a variable which each branch will mutate to be the value of that branch. + // At the end of the expression, we will evaluate to this. + let ret = cranelift::frontend::Variable::with_u32(0); + + builder.declare_var(ret, ret_type); + + // The block for the conditional's default branch. + let default_block = builder.create_ebb(); + + // The block we'll jump to once the switch has completed. + let ret_block = builder.create_ebb(); + + // Build the blocks for each branch, and register them in the switch. + // Do this before emitting the switch, because it needs to be emitted at the front. + for (int, _) in branches { + let block = builder.create_ebb(); + + blocks.push(block); + + switch.set_entry(*int, block); + } + + // Run the switch. Each branch will mutate ret and then jump to ret_ebb. + let cond = build_expr(env, scope, module, builder, cond_expr, procs); + + switch.emit(builder, cond, default_block); + + let mut build_branch = |block, expr| { + builder.switch_to_block(block); + // TODO re-enable this once Switch stops making unsealed + // EBBs, e.g. https://docs.rs/cranelift-frontend/0.52.0/src/cranelift_frontend/switch.rs.html#143 + // builder.seal_block(block); + + // Mutate the ret variable to be the outcome of this branch. + let value = build_expr(env, scope, module, builder, expr, procs); + + builder.def_var(ret, value); + + // Unconditionally jump to ret_block, making the whole expression evaluate to ret. + builder.ins().jump(ret_block, &[]); + }; + + // Build the blocks for each branch + for ((_, expr), block) in branches.iter().zip(blocks) { + build_branch(block, expr); + } + + // Build the block for the default branch + build_branch(default_block, default_branch); + + // Finally, build ret_block - which contains our terminator instruction. + { + builder.switch_to_block(ret_block); + // TODO re-enable this once Switch stops making unsealed + // EBBs, e.g. https://docs.rs/cranelift-frontend/0.52.0/src/cranelift_frontend/switch.rs.html#143 + // builder.seal_block(block); + + // Now that ret has been mutated by the switch statement, evaluate to it. + builder.use_var(ret) + } +} + +pub fn declare_proc<'a, B: Backend>( + env: &Env<'a>, + module: &mut Module, + name: InlinableString, + proc: &Proc<'a>, +) -> (FuncId, Signature) { + let args = proc.args; + let subs = &env.subs; + let cfg = env.cfg; + // TODO this rtype_from_var is duplicated when building this Proc + let ret_type = type_from_var(proc.ret_var, subs, env.cfg); + + // Create a signature for the function + let mut sig = module.make_signature(); + + // Add return type to the signature + sig.returns.push(AbiParam::new(ret_type)); + + // Add params to the signature + for (layout, _name, _var) in args.iter() { + let arg_type = type_from_layout(cfg, &layout, subs); + + sig.params.push(AbiParam::new(arg_type)); + } + + // Declare the function in the module + let fn_id = module + .declare_function(&name, Linkage::Local, &sig) + .unwrap_or_else(|err| panic!("Error when building function {:?} - {:?}", name, err)); + + (fn_id, sig) +} + +// TODO trim down these arguments +#[allow(clippy::too_many_arguments)] +pub fn define_proc_body<'a, B: Backend>( + env: &Env<'a>, + ctx: &mut Context, + module: &mut Module, + fn_id: FuncId, + scope: &Scope, + sig: Signature, + proc: Proc<'a>, + procs: &Procs<'a>, +) { + let args = proc.args; + let subs = &env.subs; + let cfg = env.cfg; + + // Build the body of the function + { + let mut scope = scope.clone(); + let arena = env.arena; + + ctx.func.signature = sig; + ctx.func.name = ExternalName::user(0, fn_id.as_u32()); + + let mut func_ctx = FunctionBuilderContext::new(); + let mut builder: FunctionBuilder = FunctionBuilder::new(&mut ctx.func, &mut func_ctx); + + let block = builder.create_ebb(); + + builder.switch_to_block(block); + builder.append_ebb_params_for_function_params(block); + + // Add args to scope + for (¶m, (_, arg_name, var)) in builder.ebb_params(block).iter().zip(args) { + let content = subs.get_without_compacting(*var).content; + // TODO this type_from_content is duplicated when building this Proc + // + let layout = Layout::from_content(arena, content, subs) + .unwrap_or_else(|()| panic!("TODO generate a runtime error here!")); + let expr_type = type_from_layout(cfg, &layout, subs); + + scope.insert(arg_name.clone(), ScopeEntry::Arg { expr_type, param }); + } + + let body = build_expr(env, &scope, module, &mut builder, &proc.body, procs); + + builder.ins().return_(&[body]); + // TODO re-enable this once Switch stops making unsealed + // EBBs, e.g. https://docs.rs/cranelift-frontend/0.52.0/src/cranelift_frontend/switch.rs.html#143 + // builder.seal_block(block); + builder.seal_all_blocks(); + + builder.finalize(); + } + + module + .define_function(fn_id, ctx) + .expect("Defining Cranelift function failed"); + + module.clear_context(ctx); +} diff --git a/src/crane/convert.rs b/src/crane/convert.rs new file mode 100644 index 0000000000..5fa2d3aa0d --- /dev/null +++ b/src/crane/convert.rs @@ -0,0 +1,135 @@ +use cranelift::prelude::AbiParam; +use cranelift_codegen::ir::{types, Signature, Type}; +use cranelift_codegen::isa::TargetFrontendConfig; + +use crate::mono::layout::Layout; +use crate::subs::FlatType::*; +use crate::subs::{Content, Subs, Variable}; +use cranelift_module::{Backend, Module}; + +pub fn type_from_var(var: Variable, subs: &Subs, cfg: TargetFrontendConfig) -> Type { + let content = subs.get_without_compacting(var).content; + + type_from_content(&content, subs, cfg) +} + +pub fn type_from_content(content: &Content, subs: &Subs, cfg: TargetFrontendConfig) -> Type { + match content { + Content::Structure(flat_type) => match flat_type { + Apply { + module_name, + name, + args, + } => { + let module_name = module_name.as_str(); + let name = name.as_str(); + + if module_name == crate::types::MOD_NUM && name == crate::types::TYPE_NUM { + let arg = *args.iter().next().unwrap(); + let arg_content = subs.get_without_compacting(arg).content; + + num_to_crane_type(arg_content) + } else { + panic!( + "TODO handle type_from_content for FlatType::Apply of {}.{} with args {:?}", + module_name, name, args + ); + } + } + Func(_, _) => cfg.pointer_type(), + other => panic!("TODO handle type_from_content for {:?}", other), + }, + other => panic!("Cannot convert {:?} to Crane Type", other), + } +} + +fn num_to_crane_type(content: Content) -> Type { + match content { + Content::Structure(flat_type) => match flat_type { + Apply { + module_name, + name, + args, + } => { + let module_name = module_name.as_str(); + let name = name.as_str(); + + if module_name == crate::types::MOD_FLOAT + && name == crate::types::TYPE_FLOATINGPOINT + && args.is_empty() + { + debug_assert!(args.is_empty()); + types::F64 + } else if module_name == crate::types::MOD_INT + && name == crate::types::TYPE_INTEGER + && args.is_empty() + { + debug_assert!(args.is_empty()); + types::I64 + } else { + panic!( + "Unrecognized numeric type: {}.{} with args {:?}", + module_name, name, args + ) + } + } + other => panic!( + "TODO handle num_to_crane_type (branch 0) for {:?} which is NESTED inside Num.Num", + other + ), + }, + + other => panic!( + "TODO handle num_to_crane_type (branch 1) for {:?} which is NESTED inside Num.Num", + other + ), + } +} + +pub fn type_from_layout(cfg: TargetFrontendConfig, layout: &Layout<'_>, _subs: &Subs) -> Type { + use crate::mono::layout::Builtin::*; + use crate::mono::layout::Layout::*; + + match layout { + Pointer(_) | FunctionPointer(_, _) => cfg.pointer_type(), + Struct(_fields) => { + panic!("TODO layout_to_crane_type for Struct"); + } + Builtin(builtin) => match builtin { + Int64 => types::I64, + Float64 => types::F64, + Str => panic!("TODO layout_to_crane_type for Builtin::Str"), + Map(_, _) => panic!("TODO layout_to_crane_type for Builtin::Map"), + Set(_) => panic!("TODO layout_to_crane_type for Builtin::Set"), + }, + } +} + +pub fn sig_from_layout( + cfg: TargetFrontendConfig, + module: &mut Module, + layout: Layout, + subs: &Subs, +) -> Signature { + match layout { + Layout::FunctionPointer(args, ret) => { + let ret_type = type_from_layout(cfg, &ret, subs); + let mut sig = module.make_signature(); + + // Add return type to the signature + sig.returns.push(AbiParam::new(ret_type)); + + // Add params to the signature + for layout in args.iter() { + let arg_type = type_from_layout(cfg, &layout, subs); + + sig.params.push(AbiParam::new(arg_type)); + } + + sig + } + _ => { + panic!("Could not make Signature from Layout {:?}", layout); + } + } +} diff --git a/src/gen/mod.rs b/src/crane/mod.rs similarity index 71% rename from src/gen/mod.rs rename to src/crane/mod.rs index a5edbec50e..7a41ebfd10 100644 --- a/src/gen/mod.rs +++ b/src/crane/mod.rs @@ -1,3 +1,2 @@ pub mod build; pub mod convert; -pub mod env; diff --git a/src/fmt/expr.rs b/src/fmt/expr.rs index 4be2ee43d8..b59b18cb9f 100644 --- a/src/fmt/expr.rs +++ b/src/fmt/expr.rs @@ -1,6 +1,8 @@ use crate::fmt::def::fmt_def; use crate::fmt::pattern::fmt_pattern; -use crate::fmt::spaces::{add_spaces, fmt_comments_only, fmt_spaces, newline, INDENT}; +use crate::fmt::spaces::{ + add_spaces, fmt_comments_only, fmt_if_spaces, fmt_spaces, is_comment, newline, INDENT, +}; use crate::parse::ast::{AssignedField, Base, CommentOrNewline, Expr, Pattern}; use crate::region::Located; use bumpalo::collections::{String, Vec}; @@ -73,8 +75,9 @@ pub fn fmt_expr<'a>( } buf.push_str("\"\"\""); } - Int(string) => buf.push_str(string), - Float(string) => buf.push_str(string), + Int(string) | Float(string) | GlobalTag(string) | PrivateTag(string) => { + buf.push_str(string) + } NonBase10Int { base, string, @@ -259,22 +262,6 @@ pub fn fmt_field<'a>( buf.push(' '); fmt_expr(buf, &value.value, indent, apply_needs_parens, true); } - OptionalField(name, spaces, value) => { - if is_multiline { - newline(buf, indent); - } - - buf.push_str(name.value); - buf.push('?'); - - 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); @@ -419,7 +406,6 @@ pub fn is_multiline_field<'a, Val>(field: &'a AssignedField<'a, Val>) -> bool { match field { LabeledValue(_, spaces, _) => !spaces.is_empty(), - OptionalField(_, spaces, _) => !spaces.is_empty(), LabelOnly(_) => false, AssignedField::SpaceBefore(_, _) | AssignedField::SpaceAfter(_, _) => true, Malformed(text) => text.chars().any(|c| c == '\n'), @@ -444,29 +430,76 @@ fn fmt_if<'a>( indent }; + buf.push_str("if"); + if is_multiline_condition { - buf.push_str("if"); - newline(buf, return_indent); - fmt_expr(buf, &loc_condition.value, return_indent, false, false); - newline(buf, indent); - buf.push_str("then"); + match &loc_condition.value { + Expr::SpaceBefore(expr_below, spaces_above_expr) => { + fmt_if_spaces(buf, spaces_above_expr.iter(), return_indent); + newline(buf, return_indent); + + match &expr_below { + Expr::SpaceAfter(expr_above, spaces_below_expr) => { + fmt_expr(buf, &expr_above, return_indent, false, false); + fmt_if_spaces(buf, spaces_below_expr.iter(), return_indent); + newline(buf, indent); + } + + _ => { + fmt_expr(buf, &expr_below, return_indent, false, false); + } + } + } + _ => { + fmt_expr(buf, &loc_condition.value, return_indent, false, false); + } + } } else { - buf.push_str("if "); + buf.push(' '); fmt_expr(buf, &loc_condition.value, indent, false, true); - buf.push_str(" then"); + buf.push(' '); } + buf.push_str("then"); + if is_multiline { - newline(buf, return_indent); + 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_if_spaces(buf, spaces_below.iter(), return_indent); + + if any_comments_below { + newline(buf, return_indent); + } + + match &expr_below { + Expr::SpaceAfter(expr_above, spaces_above) => { + fmt_expr(buf, &expr_above, return_indent, false, false); + + fmt_if_spaces(buf, spaces_above.iter(), return_indent); + newline(buf, indent); + } + + _ => { + fmt_expr(buf, &expr_below, return_indent, false, false); + } + } + } + _ => { + fmt_expr(buf, &loc_condition.value, return_indent, false, false); + } + } } else { buf.push_str(" "); + fmt_expr(buf, &loc_then.value, return_indent, false, false); } - fmt_expr(buf, &loc_then.value, return_indent, false, false); - if is_multiline { - buf.push('\n'); - newline(buf, indent); buf.push_str("else"); newline(buf, return_indent); } else { @@ -510,7 +543,7 @@ pub fn fmt_closure<'a>( any_args_printed = true; } - fmt_pattern(buf, &loc_pattern.value, indent, true); + fmt_pattern(buf, &loc_pattern.value, indent, false); } if !arguments_are_multiline { @@ -543,13 +576,26 @@ pub fn fmt_closure<'a>( pub fn fmt_record<'a>( buf: &mut String<'a>, - _update: Option<&'a Located>>, + update: Option<&'a Located>>, loc_fields: &'a Vec<'a, Located>>>, indent: u16, apply_needs_parens: bool, ) { buf.push('{'); + match update { + None => {} + // We are presuming this to be a Var() + // If it wasnt a Var() we would not have made + // it this far. For example "{ 4 & hello = 9 }" + // doesnt make sense. + Some(record_var) => { + buf.push(' '); + fmt_expr(buf, &record_var.value, indent, false, false); + buf.push_str(" &"); + } + } + let is_multiline = loc_fields .iter() .any(|loc_field| is_multiline_field(&loc_field.value)); diff --git a/src/fmt/spaces.rs b/src/fmt/spaces.rs index 6135b54363..cfef8ac386 100644 --- a/src/fmt/spaces.rs +++ b/src/fmt/spaces.rs @@ -22,15 +22,17 @@ where { use self::CommentOrNewline::*; + // Only ever print two newlines back to back. + // (Two newlines renders as one blank line.) let mut consecutive_newlines = 0; let mut iter = spaces.peekable(); + let mut encountered_comment = false; + while let Some(space) = iter.next() { match space { Newline => { - // Only ever print two newlines back to back. - // (Two newlines renders as one blank line.) - if consecutive_newlines < 2 { + if !encountered_comment && (consecutive_newlines < 2) { if iter.peek() == Some(&&Newline) { buf.push('\n'); } else { @@ -45,13 +47,41 @@ where LineComment(comment) => { fmt_comment(buf, comment, indent); - // Reset to 1 because we just printed a \n - consecutive_newlines = 1; + encountered_comment = true; } } } } +/// Similar to fmt_comments_only, but does not finish with a newline() +pub fn fmt_if_spaces<'a, I>(buf: &mut String<'a>, spaces: I, indent: u16) +where + I: Iterator>, +{ + use self::CommentOrNewline::*; + + let mut iter = spaces.peekable(); + + while let Some(space) = iter.next() { + match space { + Newline => {} + LineComment(comment) => { + buf.push('#'); + buf.push_str(comment); + } + } + match iter.peek() { + None => {} + Some(next_space) => match next_space { + Newline => {} + LineComment(_) => { + newline(buf, indent); + } + }, + } + } +} + /// Like format_spaces, but remove newlines and keep only comments. pub fn fmt_comments_only<'a, I>(buf: &mut String<'a>, spaces: I, indent: u16) where @@ -75,3 +105,10 @@ fn fmt_comment<'a>(buf: &mut String<'a>, comment: &'a str, indent: u16) { newline(buf, indent); } + +pub fn is_comment<'a>(space: &'a CommentOrNewline<'a>) -> bool { + match space { + CommentOrNewline::Newline => false, + CommentOrNewline::LineComment(_) => true, + } +} diff --git a/src/gen/env.rs b/src/gen/env.rs deleted file mode 100644 index 5270842390..0000000000 --- a/src/gen/env.rs +++ /dev/null @@ -1,11 +0,0 @@ -use crate::subs::Subs; -use inkwell::builder::Builder; -use inkwell::context::Context; -use inkwell::module::Module; - -pub struct Env<'ctx, 'env> { - pub context: &'ctx Context, - pub builder: &'env Builder<'ctx>, - pub module: &'ctx Module<'ctx>, - pub subs: Subs, -} diff --git a/src/gen/proc.rs b/src/gen/proc.rs deleted file mode 100644 index 16db6b172d..0000000000 --- a/src/gen/proc.rs +++ /dev/null @@ -1,87 +0,0 @@ -use inkwell::basic_block::BasicBlock; -use inkwell::module::Linkage; -use inkwell::types::BasicType; -use inkwell::types::BasicTypeEnum; -use inkwell::values::BasicValueEnum::{self, *}; -use inkwell::values::{BasicValue, FunctionValue, IntValue, PointerValue}; -use inkwell::{FloatPredicate, IntPredicate}; - -use crate::can::expr::Expr; -use crate::can::ident::Lowercase; -use crate::can::pattern::Pattern::{self, *}; -use crate::can::symbol::Symbol; -use crate::collections::ImMap; -use crate::collections::MutMap; -use crate::gen::convert::content_to_basic_type; -use crate::gen::env::Env; -use crate::subs::{Content, FlatType, Subs}; - -fn extract_procs(loc_expr: Located, module: &Module<'ctx>, name: Option, procs, &mut Procs<'ctx>) -> Located { - let mut procs = Vec::new(); - - match expr { - LetNonRec(def, ret_expr, var) => { - let loc_pattern = def.loc_pattern; - let loc_expr = def.loc_expr; - - // If we're defining a named closure, insert it into Procs and then - // remove the Let. When code later goes to look it up, it'll be in Procs! - // - // Before: - // - // identity = \a -> a - // - // identity 5 - // - // After: (`identity` is now in Procs) - // - // identity 5 - // - let pattern = match loc_pattern.value { - Identifier(name) => { - match &loc_expr.value { - Closure(_, _, _, _, _) => { - // Extract Procs, but discard the resulting Expr::Var. - // That Var looks up the pointer, which we won't use here! - extract_procs(loc_expr, Some(name), procs); - - // Discard this LetNonRec by replacing it with its ret_expr. - return ret_expr; - } - _ => { - // If this isn't a Closure, proceed as normal. - Identifier(name) - } - } - } - pat => pat - } - - // At this point, it's safe to assume we aren't assigning a Closure to a def. - // Extract Procs from the def body and the ret expression, and return the result! - let ret_expr = extract_procs(ret_expr, None, procs); - let loc_expr = extract_procs(def.loc_expr, None, procs); - let loc_pattern = Located { region: def.loc_pattern.region, value: pattern }; - let def = Def { loc_pattern, loc_expr, ..def }; - - LetNonRec(def, ret_expr, var) - } - - Closure(var, symbol, recursive, loc_args, boxed_ret) => { - let (loc_ret, var) = boxed_ret; - let name = match name { - Some(name) => name.as_str(), - None => { - // Give the closure a name like "_0" or "_1". - // We know procs.len() will be unique! - format!("_{}", procs.len()).as_str(); - } - }; - - let fn_val = module.add_function(name, fn_type, linkage); - - panic!("push to procs"); - } - }; -} - diff --git a/src/lib.rs b/src/lib.rs index 856367f195..35af6036fd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,13 +23,14 @@ pub mod uniqueness; pub mod string; pub mod constrain; +pub mod crane; pub mod ena; pub mod fmt; -pub mod gen; pub mod infer; -pub mod ll; +pub mod llvm; pub mod load; pub mod module; +pub mod mono; pub mod pretty_print_types; pub mod solve; pub mod subs; diff --git a/src/ll/expr.rs b/src/ll/expr.rs deleted file mode 100644 index 6abb64d33e..0000000000 --- a/src/ll/expr.rs +++ /dev/null @@ -1,396 +0,0 @@ -use crate::can; -use crate::can::pattern::Pattern; -use crate::collections::MutMap; -use crate::gen::convert::content_to_basic_type; -use crate::ll::layout::Layout; -use crate::region::Located; -use crate::subs::{Subs, Variable}; -use bumpalo::collections::Vec; -use bumpalo::Bump; -use inkwell::context::Context; -use inkwell::module::Module; -use inkwell::types::{BasicType, BasicTypeEnum}; -use inkwell::values::FunctionValue; -use inlinable_string::InlinableString; - -pub type Procs<'a, 'ctx> = MutMap>, FunctionValue<'ctx>)>; - -#[derive(Clone, Debug, PartialEq)] -pub struct Proc<'a> { - pub args: &'a [(Layout<'a>, InlinableString, Variable)], - pub body: Expr<'a>, - pub closes_over: Layout<'a>, - pub ret_var: Variable, -} - -struct Env<'a, 'ctx> { - arena: &'a Bump, - subs: &'a Subs, - module: &'ctx Module<'ctx>, - context: &'ctx Context, -} - -#[derive(Clone, Debug, PartialEq)] -pub enum Expr<'a> { - // Literals - Int(i64), - Float(f64), - Str(&'a str), - - // Load/Store - Load(InlinableString), - Store(&'a [(InlinableString, Variable, Expr<'a>)], &'a Expr<'a>), - - // Functions - FunctionPointer(InlinableString), - CallByPointer(InlinableString, &'a [Expr<'a>]), - CallByName(InlinableString, &'a [Expr<'a>]), - - // Exactly two conditional branches, e.g. if/else - Cond { - // The left-hand side of the conditional comparison and the right-hand side. - // These are stored separately because there are different machine instructions - // for e.g. "compare float and jump" vs. "compare integer and jump" - cond_lhs: &'a Expr<'a>, - cond_rhs: &'a Expr<'a>, - // What to do if the condition either passes or fails - pass: &'a Expr<'a>, - fail: &'a Expr<'a>, - ret_var: Variable, - }, - /// More than two conditional branches, e.g. a 3-way when-expression - Branches { - /// The left-hand side of the conditional. We compile this to LLVM once, - /// then reuse it to test against each different compiled cond_rhs value. - cond_lhs: &'a Expr<'a>, - /// ( cond_rhs, pass, fail ) - branches: &'a [(Expr<'a>, Expr<'a>, Expr<'a>)], - ret_var: Variable, - }, - - Struct(&'a [(InlinableString, Expr<'a>)]), - - RuntimeError(&'a str), -} - -impl<'a> Expr<'a> { - pub fn new<'ctx>( - arena: &'a Bump, - subs: &'a Subs, - module: &'ctx Module<'ctx>, - context: &'ctx Context, - can_expr: can::expr::Expr, - procs: &mut Procs<'a, 'ctx>, - ) -> Self { - let env = Env { - arena, - subs, - module, - context, - }; - - from_can(&env, can_expr, procs, None) - } -} - -fn from_can<'a, 'ctx>( - env: &Env<'a, 'ctx>, - can_expr: can::expr::Expr, - procs: &mut Procs<'a, 'ctx>, - name: Option, -) -> Expr<'a> { - use crate::can::expr::Expr::*; - use crate::can::pattern::Pattern::*; - - match can_expr { - Int(_, val) => Expr::Int(val), - Float(_, val) => Expr::Float(val), - Str(string) | BlockStr(string) => Expr::Str(env.arena.alloc(string)), - Var { - resolved_symbol, .. - } => Expr::Load(resolved_symbol.into()), - LetNonRec(def, ret_expr, _) => { - let arena = env.arena; - let loc_pattern = def.loc_pattern; - let loc_expr = def.loc_expr; - let mut stored = Vec::with_capacity_in(1, arena); - - // If we're defining a named closure, insert it into Procs and then - // remove the Let. When code gen later goes to look it up, it'll be in Procs! - // - // Before: - // - // identity = \a -> a - // - // identity 5 - // - // After: (`identity` is now in Procs) - // - // identity 5 - // - if let Identifier(name) = &loc_pattern.value { - if let Closure(_, _, _, _, _) = &loc_expr.value { - // Extract Procs, but discard the resulting Expr::Load. - // That Load looks up the pointer, which we won't use here! - from_can(env, loc_expr.value, procs, Some(name.clone().into())); - - // Discard this LetNonRec by replacing it with its ret_expr. - return from_can(env, ret_expr.value, procs, None); - } - } - - // If it wasn't specifically an Identifier & Closure, proceed as normal. - store_pattern( - env, - loc_pattern.value, - loc_expr.value, - def.expr_var, - procs, - &mut stored, - ); - - // At this point, it's safe to assume we aren't assigning a Closure to a def. - // Extract Procs from the def body and the ret expression, and return the result! - let ret = from_can(env, ret_expr.value, procs, None); - - Expr::Store(stored.into_bump_slice(), arena.alloc(ret)) - } - - Closure(_, _symbol, _, loc_args, boxed_body) => { - let (loc_body, ret_var) = *boxed_body; - let name = name.unwrap_or_else(|| - // Give the closure a name like "_0" or "_1". - // We know procs.len() will be unique! - format!("_{}", procs.len()).into()); - - add_closure(env, name, loc_body.value, ret_var, &loc_args, procs) - } - - Call(boxed, loc_args, _) => { - let (_, loc_expr, _) = *boxed; - let mut args = Vec::with_capacity_in(loc_args.len(), env.arena); - - for (_, loc_arg) in loc_args { - args.push(from_can(env, loc_arg.value, procs, None)); - } - - match from_can(env, loc_expr.value, procs, None) { - Expr::Load(proc_name) => Expr::CallByName(proc_name, args.into_bump_slice()), - Expr::FunctionPointer(proc_name) => { - // Call by pointer - the closure was anonymous, e.g. - // - // ((\a -> a) 5) - // - // It might even be the anonymous result of a conditional: - // - // ((if x > 0 then \a -> a else \_ -> 0) 5) - Expr::CallByPointer(proc_name, args.into_bump_slice()) - } - non_ptr => { - panic!( - "Tried to call by pointer, but encountered a non-pointer: {:?}", - non_ptr - ); - } - } - } - - When { - cond_var, - expr_var, - loc_cond, - branches, - } => { - debug_assert!(!branches.is_empty()); - - if branches.len() == 2 { - let arena = env.arena; - let mut iter = branches.into_iter(); - let (loc_pat1, loc_then) = iter.next().unwrap(); - let (loc_pat2, loc_else) = iter.next().unwrap(); - - match (&loc_pat1.value, &loc_pat2.value) { - (IntLiteral(int), IntLiteral(_)) | (IntLiteral(int), Underscore) => { - let cond_lhs = arena.alloc(from_can(env, loc_cond.value, procs, None)); - let cond_rhs = arena.alloc(Expr::Int(*int)); - let pass = arena.alloc(from_can(env, loc_then.value, procs, None)); - let fail = arena.alloc(from_can(env, loc_else.value, procs, None)); - - Expr::Cond { - cond_lhs, - cond_rhs, - pass, - fail, - ret_var: expr_var, - } - } - (FloatLiteral(float), FloatLiteral(_)) | (FloatLiteral(float), Underscore) => { - let cond_lhs = arena.alloc(from_can(env, loc_cond.value, procs, None)); - let cond_rhs = arena.alloc(Expr::Float(*float)); - let pass = arena.alloc(from_can(env, loc_then.value, procs, None)); - let fail = arena.alloc(from_can(env, loc_else.value, procs, None)); - - Expr::Cond { - cond_lhs, - cond_rhs, - pass, - fail, - ret_var: expr_var, - } - } - _ => { - panic!("TODO handle more conds"); - } - } - } else if branches.len() == 1 { - // A when-expression with exactly 1 branch is essentially a LetNonRec. - // As such, we can compile it direcly to a Store. - let arena = env.arena; - let mut stored = Vec::with_capacity_in(1, arena); - let (loc_pattern, loc_branch) = branches.into_iter().next().unwrap(); - - store_pattern( - env, - loc_pattern.value, - loc_cond.value, - cond_var, - procs, - &mut stored, - ); - - let ret = from_can(env, loc_branch.value, procs, None); - - Expr::Store(stored.into_bump_slice(), arena.alloc(ret)) - } else { - // /// More than two conditional branches, e.g. a 3-way when-expression - // Expr::Branches { - // /// The left-hand side of the conditional. We compile this to LLVM once, - // /// then reuse it to test against each different compiled cond_rhs value. - // cond_lhs: &'a Expr<'a>, - // /// ( cond_rhs, pass, fail ) - // branches: &'a [(Expr<'a>, Expr<'a>, Expr<'a>)], - // ret_var: Variable, - // }, - panic!("TODO support when-expressions of more than 2 branches."); - } - } - - other => panic!("TODO convert canonicalized {:?} to ll::Expr", other), - } -} - -fn add_closure<'a, 'ctx>( - env: &Env<'a, 'ctx>, - name: InlinableString, - can_body: can::expr::Expr, - ret_var: Variable, - loc_args: &[(Variable, Located)], - procs: &mut Procs<'a, 'ctx>, -) -> Expr<'a> { - let subs = &env.subs; - let context = env.context; - let arena = env.arena; - let ret_content = subs.get_without_compacting(ret_var).content; - let ret_type = content_to_basic_type(&ret_content, subs, context).unwrap_or_else(|err| { - panic!( - "Error converting function return value content to basic type: {:?}", - err - ) - }); - - let mut arg_names = Vec::with_capacity_in(loc_args.len(), arena); - let mut arg_basic_types = Vec::with_capacity_in(loc_args.len(), arena); - let mut proc_args = Vec::with_capacity_in(loc_args.len(), arena); - - for (arg_var, loc_arg) in loc_args.iter() { - let content = subs.get_without_compacting(*arg_var).content; - - arg_basic_types.push( - content_to_basic_type(&content, subs, context).unwrap_or_else(|err| { - panic!( - "Error converting function arg content to basic type: {:?}", - err - ) - }), - ); - - let layout = match Layout::from_content(arena, content, subs) { - Ok(layout) => layout, - Err(()) => { - return invalid_closure(env, name, ret_type, procs); - } - }; - - let arg_name: InlinableString = match &loc_arg.value { - Pattern::Identifier(name) => name.as_str().into(), - _ => { - panic!("TODO determine arg_name for pattern {:?}", loc_arg.value); - } - }; - - arg_names.push(arg_name.clone()); - proc_args.push((layout, arg_name, *arg_var)); - } - - let fn_type = ret_type.fn_type(arg_basic_types.into_bump_slice(), false); - let fn_val = env.module.add_function(&name, fn_type, None); - let proc = Proc { - args: proc_args.into_bump_slice(), - body: from_can(env, can_body, procs, None), - closes_over: Layout::Struct(&[]), - ret_var, - }; - - procs.insert(name.clone(), (Some(proc), fn_val)); - - Expr::FunctionPointer(name) -} - -fn invalid_closure<'a, 'ctx>( - env: &Env<'a, 'ctx>, - name: InlinableString, - ret_type: BasicTypeEnum<'ctx>, - procs: &mut Procs<'a, 'ctx>, -) -> Expr<'a> { - let fn_type = ret_type.fn_type(&[], false); - let fn_val = env.module.add_function(&name, fn_type, None); - - procs.insert(name.clone(), (None, fn_val)); - - Expr::FunctionPointer(name) -} - -fn store_pattern<'a, 'ctx>( - env: &Env<'a, 'ctx>, - can_pat: Pattern, - can_expr: can::expr::Expr, - var: Variable, - procs: &mut Procs<'a, 'ctx>, - stored: &mut Vec<'a, (InlinableString, Variable, Expr<'a>)>, -) { - use crate::can::pattern::Pattern::*; - - // If we're defining a named closure, insert it into Procs and then - // remove the Let. When code gen later goes to look it up, it'll be in Procs! - // - // Before: - // - // identity = \a -> a - // - // identity 5 - // - // After: (`identity` is now in Procs) - // - // identity 5 - // - match can_pat { - Identifier(name) => stored.push((name.into(), var, from_can(env, can_expr, procs, None))), - Underscore => { - // Since _ is never read, it's safe to reassign it. - stored.push(("_".into(), var, from_can(env, can_expr, procs, None))) - } - _ => { - panic!("TODO store_pattern for {:?}", can_pat); - } - } -} diff --git a/src/gen/build.rs b/src/llvm/build.rs similarity index 54% rename from src/gen/build.rs rename to src/llvm/build.rs index a977397f49..4917b9395d 100644 --- a/src/gen/build.rs +++ b/src/llvm/build.rs @@ -1,3 +1,8 @@ +use bumpalo::collections::Vec; +use bumpalo::Bump; +use inkwell::builder::Builder; +use inkwell::context::Context; +use inkwell::module::{Linkage, Module}; use inkwell::types::BasicTypeEnum; use inkwell::values::BasicValueEnum::{self, *}; use inkwell::values::{FunctionValue, IntValue, PointerValue}; @@ -5,10 +10,11 @@ use inkwell::{FloatPredicate, IntPredicate}; use inlinable_string::InlinableString; use crate::collections::ImMap; -use crate::gen::convert::{content_to_basic_type, layout_to_basic_type}; -use crate::gen::env::Env; -use crate::ll::expr::{Expr, Proc, Procs}; -use crate::subs::Variable; +use crate::llvm::convert::{ + content_to_basic_type, get_fn_type, layout_to_basic_type, type_from_var, +}; +use crate::mono::expr::{Expr, Proc, Procs}; +use crate::subs::{Subs, Variable}; /// This is for Inkwell's FunctionValue::verify - we want to know the verification /// output in debug builds, but we don't want it to print to stdout in release builds! @@ -20,14 +26,22 @@ const PRINT_FN_VERIFICATION_OUTPUT: bool = false; type Scope<'ctx> = ImMap)>; +pub struct Env<'a, 'ctx, 'env> { + pub arena: &'a Bump, + pub context: &'ctx Context, + pub builder: &'env Builder<'ctx>, + pub module: &'ctx Module<'ctx>, + pub subs: Subs, +} + pub fn build_expr<'a, 'ctx, 'env>( - env: &Env<'ctx, 'env>, + env: &Env<'a, 'ctx, 'env>, scope: &Scope<'ctx>, parent: FunctionValue<'ctx>, expr: &Expr<'a>, - procs: &Procs<'a, 'ctx>, + procs: &Procs<'a>, ) -> BasicValueEnum<'ctx> { - use crate::ll::expr::Expr::*; + use crate::mono::expr::Expr::*; match expr { Int(num) => env.context.i64_type().const_int(*num as u64, false).into(), @@ -38,8 +52,9 @@ pub fn build_expr<'a, 'ctx, 'env>( pass, fail, ret_var, + .. } => { - let cond = Cond2 { + let cond = Branch2 { cond_lhs, cond_rhs, pass, @@ -47,11 +62,29 @@ pub fn build_expr<'a, 'ctx, 'env>( ret_var: *ret_var, }; - build_cond(env, scope, parent, cond, procs) + build_branch2(env, scope, parent, cond, procs) } Branches { .. } => { panic!("TODO build_branches(env, scope, parent, cond_lhs, branches, procs)"); } + Switch { + cond, + branches, + default_branch, + ret_var, + cond_var, + } => { + let ret_type = type_from_var(*ret_var, &env.subs, env.context); + let switch_args = SwitchArgs { + cond_var: *cond_var, + cond_expr: cond, + branches, + default_branch, + ret_type, + }; + + build_switch(env, scope, parent, switch_args, procs) + } Store(ref stores, ref ret) => { let mut scope = im_rc::HashMap::clone(scope); let subs = &env.subs; @@ -88,12 +121,14 @@ pub fn build_expr<'a, 'ctx, 'env>( // // 1. use SIMD string comparison to compare these strings faster // 2. pre-register Bool.or using module.add_function, and see if LLVM inlines it + // 3. intern all these strings if name == "Bool.or" { panic!("TODO create a phi node for ||"); } else if name == "Bool.and" { panic!("TODO create a phi node for &&"); } else { - let mut arg_vals: Vec = Vec::with_capacity(args.len()); + let mut arg_vals: Vec = + Vec::with_capacity_in(args.len(), env.arena); for arg in args.iter() { arg_vals.push(build_expr(env, scope, parent, arg, procs)); @@ -106,35 +141,45 @@ pub fn build_expr<'a, 'ctx, 'env>( let call = env.builder.build_call(fn_val, arg_vals.as_slice(), "tmp"); - call.try_as_basic_value() - .left() - .unwrap_or_else(|| panic!("LLVM error: Invalid call by name.")) + call.try_as_basic_value().left().unwrap_or_else(|| { + panic!("LLVM error: Invalid call by name for name {:?}", name) + }) } } - CallByPointer(ref _ptr, ref args) => { - let mut arg_vals: Vec = Vec::with_capacity(args.len()); + FunctionPointer(ref fn_name) => { + let ptr = env + .module + .get_function(fn_name) + .unwrap_or_else(|| { + panic!("Could not get pointer to unknown function {:?}", fn_name) + }) + .as_global_value() + .as_pointer_value(); + + BasicValueEnum::PointerValue(ptr) + } + CallByPointer(ref sub_expr, ref args, _var) => { + let mut arg_vals: Vec = Vec::with_capacity_in(args.len(), env.arena); for arg in args.iter() { arg_vals.push(build_expr(env, scope, parent, arg, procs)); } - panic!("TODO do a load(ptr) to get back the pointer, then pass *that* in here!"); + let call = match build_expr(env, scope, parent, sub_expr, procs) { + BasicValueEnum::PointerValue(ptr) => { + env.builder.build_call(ptr, arg_vals.as_slice(), "tmp") + } + non_ptr => { + panic!( + "Tried to call by pointer, but encountered a non-pointer: {:?}", + non_ptr + ); + } + }; - // let call = match build_expr(env, scope, parent, expr, procs) { - // BasicValueEnum::PointerValue(ptr) => { - // env.builder.build_call(ptr, arg_vals.as_slice(), "tmp") - // } - // non_ptr => { - // panic!( - // "Tried to call by pointer, but encountered a non-pointer: {:?}", - // non_ptr - // ); - // } - // }; - - // call.try_as_basic_value() - // .left() - // .unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer.")) + call.try_as_basic_value() + .left() + .unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer.")) } Load(name) => match scope.get(name) { @@ -142,12 +187,12 @@ pub fn build_expr<'a, 'ctx, 'env>( None => panic!("Could not find a var for {:?} in scope {:?}", name, scope), }, _ => { - panic!("I don't yet know how to build {:?}", expr); + panic!("I don't yet know how to LLVM build {:?}", expr); } } } -struct Cond2<'a> { +struct Branch2<'a> { cond_lhs: &'a Expr<'a>, cond_rhs: &'a Expr<'a>, pass: &'a Expr<'a>, @@ -155,12 +200,12 @@ struct Cond2<'a> { ret_var: Variable, } -fn build_cond<'a, 'ctx, 'env>( - env: &Env<'ctx, 'env>, +fn build_branch2<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, scope: &Scope<'ctx>, parent: FunctionValue<'ctx>, - cond: Cond2<'a>, - procs: &Procs<'a, 'ctx>, + cond: Branch2<'a>, + procs: &Procs<'a>, ) -> BasicValueEnum<'ctx> { let builder = env.builder; let context = env.context; @@ -201,88 +246,127 @@ fn build_cond<'a, 'ctx, 'env>( } } -// fn build_branches<'a, 'ctx, 'env>( -// env: &Env<'ctx, 'env>, -// scope: &Scope<'ctx>, -// parent: FunctionValue<'ctx>, -// cond_lhs: &'a Expr<'a>, -// branches: &'a [(Expr<'a>, Expr<'a>, Expr<'a>)], -// ret_type: BasicValueEnum<'ctx>, -// procs: &Procs<'a, 'ctx>, -// ) -> BasicValueEnum<'ctx> { -// let builder = env.builder; -// let context = env.context; -// let lhs = build_expr(env, scope, parent, cond_lhs, procs); -// let mut branch_iter = branches.into_iter(); -// let content = subs.get_without_compacting(cond.ret_var).content; -// let ret_type = content_to_basic_type(&content, subs, context).unwrap_or_else(|err| { -// panic!( -// "Error converting cond branch ret_type content {:?} to basic type: {:?}", -// cond.pass, err -// ) -// }); +struct SwitchArgs<'a, 'ctx> { + pub cond_expr: &'a Expr<'a>, + pub cond_var: Variable, + pub branches: &'a [(u64, Expr<'a>)], + pub default_branch: &'a Expr<'a>, + pub ret_type: BasicTypeEnum<'ctx>, +} -// for (cond_rhs, cond_pass, cond_else) in branches { -// let rhs = build_expr(env, scope, parent, cond_rhs, procs); -// let pass = build_expr(env, scope, parent, cond_pass, procs); -// let fail = build_expr(env, scope, parent, cond_else, procs); +fn build_switch<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + scope: &Scope<'ctx>, + parent: FunctionValue<'ctx>, + switch_args: SwitchArgs<'a, 'ctx>, + procs: &Procs<'a>, +) -> BasicValueEnum<'ctx> { + let arena = env.arena; + let builder = env.builder; + let context = env.context; + let SwitchArgs { + branches, + cond_expr, + default_branch, + ret_type, + .. + } = switch_args; -// let cond = Cond { -// lhs, -// rhs, -// pass, -// fail, -// ret_type, -// }; + let cont_block = context.append_basic_block(parent, "cont"); -// build_cond(env, scope, parent, cond, procs) -// } -// } + // Build the condition + let cond = build_expr(env, scope, parent, cond_expr, procs).into_int_value(); + + // Build the cases + let mut incoming = Vec::with_capacity_in(branches.len(), arena); + let mut cases = Vec::with_capacity_in(branches.len(), arena); + + for (int, _) in branches.iter() { + let int_val = context.i64_type().const_int(*int as u64, false); + let block = context.append_basic_block(parent, format!("branch{}", int).as_str()); + + cases.push((int_val, &*arena.alloc(block))); + } + + let default_block = context.append_basic_block(parent, "default"); + + builder.build_switch(cond, &default_block, &cases); + + for ((_, branch_expr), (_, block)) in branches.iter().zip(cases) { + builder.position_at_end(&block); + + let branch_val = build_expr(env, scope, parent, branch_expr, procs); + + builder.build_unconditional_branch(&cont_block); + + incoming.push((branch_val, block)); + } + + // The block for the conditional's default branch. + builder.position_at_end(&default_block); + + let default_val = build_expr(env, scope, parent, default_branch, procs); + + builder.build_unconditional_branch(&cont_block); + + incoming.push((default_val, &default_block)); + + // emit merge block + builder.position_at_end(&cont_block); + + let phi = builder.build_phi(ret_type, "branch"); + + for (branch_val, block) in incoming { + phi.add_incoming(&[(&Into::::into(branch_val), block)]); + } + + phi.as_basic_value() +} // TODO trim down these arguments #[allow(clippy::too_many_arguments)] fn build_phi2<'a, 'ctx, 'env>( - env: &Env<'ctx, 'env>, + env: &Env<'a, 'ctx, 'env>, scope: &Scope<'ctx>, parent: FunctionValue<'ctx>, comparison: IntValue<'ctx>, pass: &'a Expr<'a>, fail: &'a Expr<'a>, ret_type: BasicTypeEnum<'ctx>, - procs: &Procs<'a, 'ctx>, + procs: &Procs<'a>, ) -> BasicValueEnum<'ctx> { let builder = env.builder; let context = env.context; - // build branch - let then_bb = context.append_basic_block(parent, "then"); - let else_bb = context.append_basic_block(parent, "else"); - let cont_bb = context.append_basic_block(parent, "branchcont"); + // build blocks + let then_block = context.append_basic_block(parent, "then"); + let else_block = context.append_basic_block(parent, "else"); + let cont_block = context.append_basic_block(parent, "branchcont"); - builder.build_conditional_branch(comparison, &then_bb, &else_bb); + builder.build_conditional_branch(comparison, &then_block, &else_block); // build then block - builder.position_at_end(&then_bb); + builder.position_at_end(&then_block); let then_val = build_expr(env, scope, parent, pass, procs); - builder.build_unconditional_branch(&cont_bb); + builder.build_unconditional_branch(&cont_block); - let then_bb = builder.get_insert_block().unwrap(); + let then_block = builder.get_insert_block().unwrap(); // build else block - builder.position_at_end(&else_bb); + builder.position_at_end(&else_block); let else_val = build_expr(env, scope, parent, fail, procs); - builder.build_unconditional_branch(&cont_bb); + builder.build_unconditional_branch(&cont_block); - let else_bb = builder.get_insert_block().unwrap(); + let else_block = builder.get_insert_block().unwrap(); // emit merge block - builder.position_at_end(&cont_bb); + builder.position_at_end(&cont_block); let phi = builder.build_phi(ret_type, "branch"); phi.add_incoming(&[ - (&Into::::into(then_val), &then_bb), - (&Into::::into(else_val), &else_bb), + (&Into::::into(then_val), &then_block), + (&Into::::into(else_val), &else_block), ]); phi.as_basic_value() @@ -301,8 +385,8 @@ fn set_name(bv_enum: BasicValueEnum<'_>, name: &str) { } /// Creates a new stack allocation instruction in the entry block of the function. -pub fn create_entry_block_alloca<'ctx>( - env: &Env<'ctx, '_>, +pub fn create_entry_block_alloca<'a, 'ctx>( + env: &Env<'a, 'ctx, '_>, parent: FunctionValue<'_>, basic_type: BasicTypeEnum<'ctx>, name: &str, @@ -319,38 +403,46 @@ pub fn create_entry_block_alloca<'ctx>( } pub fn build_proc<'a, 'ctx, 'env>( - env: &Env<'ctx, 'env>, - scope: &Scope<'ctx>, + env: &Env<'a, 'ctx, 'env>, name: InlinableString, proc: Proc<'a>, - procs: &Procs<'a, 'ctx>, -) { + procs: &Procs<'a>, +) -> FunctionValue<'ctx> { let args = proc.args; - let mut arg_names = Vec::new(); - let mut arg_basic_types = Vec::with_capacity(args.len()); + let arena = env.arena; + let subs = &env.subs; + let context = &env.context; + let ret_content = subs.get_without_compacting(proc.ret_var).content; + // TODO this content_to_basic_type is duplicated when building this Proc + let ret_type = content_to_basic_type(&ret_content, subs, context).unwrap_or_else(|err| { + panic!( + "Error converting function return value content to basic type: {:?}", + err + ) + }); + let mut arg_basic_types = Vec::with_capacity_in(args.len(), arena); + let mut arg_names = Vec::new_in(arena); for (layout, name, _var) in args.iter() { - let arg_type = layout_to_basic_type(&layout, &env.subs, env.context); + let arg_type = layout_to_basic_type(&layout, subs, env.context); arg_basic_types.push(arg_type); arg_names.push(name); } - // Retrieve the function value from the module - let fn_val = env.module.get_function(&name).unwrap_or_else(|| { - panic!( - "Function {:?} should have been registered in the LLVM module, but it was not!", - name - ) - }); + let fn_type = get_fn_type(&ret_type, &arg_basic_types); + + let fn_val = env + .module + .add_function(&name, fn_type, Some(Linkage::Private)); // Add a basic block for the entry point - let entry = env.context.append_basic_block(fn_val, "entry"); + let entry = context.append_basic_block(fn_val, "entry"); let builder = env.builder; builder.position_at_end(&entry); - let mut scope = scope.clone(); + let mut scope = ImMap::default(); // Add args to scope for ((arg_val, arg_type), (_, arg_name, var)) in @@ -369,9 +461,11 @@ pub fn build_proc<'a, 'ctx, 'env>( builder.build_return(Some(&body)); - if fn_val.verify(PRINT_FN_VERIFICATION_OUTPUT) { - // TODO call pass_manager.run_on(&fn_val) to optimize it! - } else { + fn_val +} + +pub fn verify_fn(fn_val: FunctionValue<'_>) { + if !fn_val.verify(PRINT_FN_VERIFICATION_OUTPUT) { unsafe { fn_val.delete(); } diff --git a/src/gen/convert.rs b/src/llvm/convert.rs similarity index 87% rename from src/gen/convert.rs rename to src/llvm/convert.rs index c956ea29aa..e72a9a51b8 100644 --- a/src/gen/convert.rs +++ b/src/llvm/convert.rs @@ -3,11 +3,26 @@ use inkwell::types::BasicTypeEnum::{self, *}; use inkwell::types::{BasicType, FunctionType}; use inkwell::AddressSpace; -use crate::ll::layout::Layout; +use crate::mono::layout::Layout; use crate::subs::FlatType::*; -use crate::subs::{Content, Subs}; +use crate::subs::{Content, Subs, Variable}; use crate::types; +pub fn type_from_var<'ctx>( + var: Variable, + subs: &Subs, + context: &'ctx Context, +) -> BasicTypeEnum<'ctx> { + let content = subs.get_without_compacting(var).content; + + content_to_basic_type(&content, subs, context).unwrap_or_else(|err| { + panic!( + "Error converting Content to basic type: {:?} - {:?}", + content, err + ) + }) +} + pub fn content_to_basic_type<'ctx>( content: &Content, subs: &Subs, @@ -46,8 +61,9 @@ pub fn content_to_basic_type<'ctx>( arg_basic_types.push(content_to_basic_type(&arg_content, subs, context)?); } let fn_type = get_fn_type(&ret_type, arg_basic_types.as_slice()); + let ptr_type = fn_type.ptr_type(AddressSpace::Generic); - Ok(fn_type.ptr_type(AddressSpace::Global).as_basic_type_enum()) + Ok(ptr_type.as_basic_type_enum()) } other => panic!("TODO handle content_to_basic_type for {:?}", other), }, @@ -99,7 +115,7 @@ fn num_to_basic_type(content: Content, context: &Context) -> Result( +pub fn get_fn_type<'ctx>( bt_enum: &BasicTypeEnum<'ctx>, arg_types: &[BasicTypeEnum<'ctx>], ) -> FunctionType<'ctx> { @@ -118,8 +134,8 @@ pub fn layout_to_basic_type<'ctx>( _subs: &Subs, context: &'ctx Context, ) -> BasicTypeEnum<'ctx> { - use crate::ll::layout::Builtin::*; - use crate::ll::layout::Layout::*; + use crate::mono::layout::Builtin::*; + use crate::mono::layout::Layout::*; match layout { FunctionPointer(_arg_layouts, _ret_layout) => { diff --git a/src/llvm/mod.rs b/src/llvm/mod.rs new file mode 100644 index 0000000000..7a41ebfd10 --- /dev/null +++ b/src/llvm/mod.rs @@ -0,0 +1,2 @@ +pub mod build; +pub mod convert; diff --git a/src/mono/expr.rs b/src/mono/expr.rs new file mode 100644 index 0000000000..4326997a1e --- /dev/null +++ b/src/mono/expr.rs @@ -0,0 +1,513 @@ +use crate::can; +use crate::can::pattern::Pattern; +use crate::collections::MutMap; +use crate::mono::layout::{Builtin, Layout}; +use crate::region::Located; +use crate::subs::{Content, FlatType, Subs, Variable}; +use bumpalo::collections::Vec; +use bumpalo::Bump; +use inlinable_string::InlinableString; + +pub type Procs<'a> = MutMap>>; + +#[derive(Clone, Debug, PartialEq)] +pub struct Proc<'a> { + pub args: &'a [(Layout<'a>, InlinableString, Variable)], + pub body: Expr<'a>, + pub closes_over: Layout<'a>, + pub ret_var: Variable, +} + +struct Env<'a> { + pub arena: &'a Bump, + pub subs: &'a Subs, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Expr<'a> { + // Literals + Int(i64), + Float(f64), + Str(&'a str), + /// Closed tag unions containing exactly two (0-arity) tags compile to Expr::Bool, + /// so they can (at least potentially) be emitted as 1-bit machine bools. + /// + /// So [ True, False ] compiles to this, and so do [ A, B ] and [ Foo, Bar ]. + /// However, a union like [ True, False, Other Int ] would not. + Bool(bool), + /// Closed tag unions containing between 3 and 256 tags (all of 0 arity) + /// compile to bytes, e.g. [ Blue, Black, Red, Green, White ] + Byte(u8), + + // Load/Store + Load(InlinableString), + Store(&'a [(InlinableString, Variable, Expr<'a>)], &'a Expr<'a>), + + // Functions + FunctionPointer(InlinableString), + CallByName(InlinableString, &'a [Expr<'a>]), + CallByPointer(&'a Expr<'a>, &'a [Expr<'a>], Variable), + + // Exactly two conditional branches, e.g. if/else + Cond { + // The left-hand side of the conditional comparison and the right-hand side. + // These are stored separately because there are different machine instructions + // for e.g. "compare float and jump" vs. "compare integer and jump" + cond_lhs: &'a Expr<'a>, + cond_rhs: &'a Expr<'a>, + cond_layout: Layout<'a>, + // What to do if the condition either passes or fails + pass: &'a Expr<'a>, + fail: &'a Expr<'a>, + ret_var: Variable, + }, + /// More than two conditional branches, e.g. a 3-way when-expression + Branches { + /// The left-hand side of the conditional. We compile this to LLVM once, + /// then reuse it to test against each different compiled cond_rhs value. + cond: &'a Expr<'a>, + /// ( cond_rhs, pass, fail ) + branches: &'a [(Expr<'a>, Expr<'a>, Expr<'a>)], + default: &'a Expr<'a>, + ret_var: Variable, + }, + /// Conditional branches for integers. These are more efficient. + Switch { + /// This *must* be an integer, because Switch potentially compiles to a jump table. + cond: &'a Expr<'a>, + cond_var: Variable, + /// The u64 in the tuple will be compared directly to the condition Expr. + /// If they are equal, this branch will be taken. + branches: &'a [(u64, Expr<'a>)], + /// If no other branches pass, this default branch will be taken. + default_branch: &'a Expr<'a>, + /// Each branch must return a value of this type. + ret_var: Variable, + }, + Tag { + variant_var: Variable, + ext_var: Variable, + name: InlinableString, + arguments: &'a [Expr<'a>], + }, + + Struct(&'a [(InlinableString, Expr<'a>)]), + + RuntimeError(&'a str), +} + +impl<'a> Expr<'a> { + pub fn new( + arena: &'a Bump, + subs: &'a Subs, + can_expr: can::expr::Expr, + procs: &mut Procs<'a>, + ) -> Self { + let env = Env { arena, subs }; + + from_can(&env, can_expr, procs, None) + } +} + +fn from_can<'a>( + env: &Env<'a>, + can_expr: can::expr::Expr, + procs: &mut Procs<'a>, + name: Option, +) -> Expr<'a> { + use crate::can::expr::Expr::*; + use crate::can::pattern::Pattern::*; + + match can_expr { + Int(_, val) => Expr::Int(val), + Float(_, val) => Expr::Float(val), + Str(string) | BlockStr(string) => Expr::Str(env.arena.alloc(string)), + Var { + resolved_symbol, .. + } => Expr::Load(resolved_symbol.into()), + LetNonRec(def, ret_expr, _) => { + let arena = env.arena; + let loc_pattern = def.loc_pattern; + let loc_expr = def.loc_expr; + let mut stored = Vec::with_capacity_in(1, arena); + + // If we're defining a named closure, insert it into Procs and then + // remove the Let. When code gen later goes to look it up, it'll be in Procs! + // + // Before: + // + // identity = \a -> a + // + // identity 5 + // + // After: (`identity` is now in Procs) + // + // identity 5 + // + if let Identifier(name) = &loc_pattern.value { + if let Closure(_, _, _, _, _) = &loc_expr.value { + // Extract Procs, but discard the resulting Expr::Load. + // That Load looks up the pointer, which we won't use here! + from_can(env, loc_expr.value, procs, Some(name.clone().into())); + + // Discard this LetNonRec by replacing it with its ret_expr. + return from_can(env, ret_expr.value, procs, None); + } + } + + // If it wasn't specifically an Identifier & Closure, proceed as normal. + store_pattern( + env, + loc_pattern.value, + loc_expr.value, + def.expr_var, + procs, + &mut stored, + ); + + // At this point, it's safe to assume we aren't assigning a Closure to a def. + // Extract Procs from the def body and the ret expression, and return the result! + let ret = from_can(env, ret_expr.value, procs, None); + + Expr::Store(stored.into_bump_slice(), arena.alloc(ret)) + } + + Closure(_, _symbol, _, loc_args, boxed_body) => { + let (loc_body, ret_var) = *boxed_body; + let name = name.unwrap_or_else(|| gen_closure_name(procs)); + + add_closure(env, name, loc_body.value, ret_var, &loc_args, procs) + } + + Call(boxed, loc_args, _) => { + let (fn_var, loc_expr, _) = *boxed; + let mut args = Vec::with_capacity_in(loc_args.len(), env.arena); + + for (_, loc_arg) in loc_args { + args.push(from_can(env, loc_arg.value, procs, None)); + } + + match from_can(env, loc_expr.value, procs, None) { + Expr::Load(proc_name) => Expr::CallByName(proc_name, args.into_bump_slice()), + ptr => { + // Call by pointer - the closure was anonymous, e.g. + // + // ((\a -> a) 5) + // + // It might even be the anonymous result of a conditional: + // + // ((if x > 0 then \a -> a else \_ -> 0) 5) + Expr::CallByPointer(&*env.arena.alloc(ptr), args.into_bump_slice(), fn_var) + } + } + } + + When { + cond_var, + expr_var, + loc_cond, + branches, + } => from_can_when(env, cond_var, expr_var, *loc_cond, branches, procs), + + other => panic!("TODO convert canonicalized {:?} to ll::Expr", other), + } +} + +fn add_closure<'a>( + env: &Env<'a>, + name: InlinableString, + can_body: can::expr::Expr, + ret_var: Variable, + loc_args: &[(Variable, Located)], + procs: &mut Procs<'a>, +) -> Expr<'a> { + let subs = &env.subs; + let arena = env.arena; + let mut proc_args = Vec::with_capacity_in(loc_args.len(), arena); + + for (arg_var, loc_arg) in loc_args.iter() { + let content = subs.get_without_compacting(*arg_var).content; + + let layout = match Layout::from_content(arena, content, subs) { + Ok(layout) => layout, + Err(()) => { + // Invalid closure! + procs.insert(name.clone(), None); + + return Expr::FunctionPointer(name); + } + }; + + let arg_name: InlinableString = match &loc_arg.value { + Pattern::Identifier(name) => name.as_str().into(), + _ => { + panic!("TODO determine arg_name for pattern {:?}", loc_arg.value); + } + }; + + proc_args.push((layout, arg_name, *arg_var)); + } + + let proc = Proc { + args: proc_args.into_bump_slice(), + body: from_can(env, can_body, procs, None), + closes_over: Layout::Struct(&[]), + ret_var, + }; + + procs.insert(name.clone(), Some(proc)); + + Expr::FunctionPointer(name) +} + +fn store_pattern<'a>( + env: &Env<'a>, + can_pat: Pattern, + can_expr: can::expr::Expr, + var: Variable, + procs: &mut Procs<'a>, + stored: &mut Vec<'a, (InlinableString, Variable, Expr<'a>)>, +) { + use crate::can::pattern::Pattern::*; + + // If we're defining a named closure, insert it into Procs and then + // remove the Let. When code gen later goes to look it up, it'll be in Procs! + // + // Before: + // + // identity = \a -> a + // + // identity 5 + // + // After: (`identity` is now in Procs) + // + // identity 5 + // + match can_pat { + Identifier(name) => stored.push((name.into(), var, from_can(env, can_expr, procs, None))), + Underscore => { + // Since _ is never read, it's safe to reassign it. + stored.push(("_".into(), var, from_can(env, can_expr, procs, None))) + } + _ => { + panic!("TODO store_pattern for {:?}", can_pat); + } + } +} + +fn gen_closure_name(procs: &Procs<'_>) -> InlinableString { + // Give the closure a name like "_0" or "_1". + // We know procs.len() will be unique! + format!("_{}", procs.len()).into() +} + +fn from_can_when<'a>( + env: &Env<'a>, + cond_var: Variable, + expr_var: Variable, + loc_cond: Located, + branches: std::vec::Vec<(Located, Located)>, + procs: &mut Procs<'a>, +) -> Expr<'a> { + use crate::can::pattern::Pattern::*; + + match branches.len() { + 0 => { + // A when-expression with no branches is a runtime error. + // We can't know what to return! + panic!("TODO compile a 0-branch when-expression to a RuntimeError"); + } + 1 => { + // A when-expression with exactly 1 branch is essentially a LetNonRec. + // As such, we can compile it direcly to a Store. + let arena = env.arena; + let mut stored = Vec::with_capacity_in(1, arena); + let (loc_pattern, loc_branch) = branches.into_iter().next().unwrap(); + + store_pattern( + env, + loc_pattern.value, + loc_cond.value, + cond_var, + procs, + &mut stored, + ); + + let ret = from_can(env, loc_branch.value, procs, None); + + Expr::Store(stored.into_bump_slice(), arena.alloc(ret)) + } + 2 => { + // A when-expression with exactly 2 branches compiles to a Cond. + let arena = env.arena; + let mut iter = branches.into_iter(); + let (loc_pat1, loc_then) = iter.next().unwrap(); + let (loc_pat2, loc_else) = iter.next().unwrap(); + + match (&loc_pat1.value, &loc_pat2.value) { + (IntLiteral(int), IntLiteral(_)) | (IntLiteral(int), Underscore) => { + let cond_lhs = arena.alloc(from_can(env, loc_cond.value, procs, None)); + let cond_rhs = arena.alloc(Expr::Int(*int)); + let pass = arena.alloc(from_can(env, loc_then.value, procs, None)); + let fail = arena.alloc(from_can(env, loc_else.value, procs, None)); + + Expr::Cond { + cond_layout: Layout::Builtin(Builtin::Int64), + cond_lhs, + cond_rhs, + pass, + fail, + ret_var: expr_var, + } + } + (FloatLiteral(float), FloatLiteral(_)) | (FloatLiteral(float), Underscore) => { + let cond_lhs = arena.alloc(from_can(env, loc_cond.value, procs, None)); + let cond_rhs = arena.alloc(Expr::Float(*float)); + let pass = arena.alloc(from_can(env, loc_then.value, procs, None)); + let fail = arena.alloc(from_can(env, loc_else.value, procs, None)); + + Expr::Cond { + cond_layout: Layout::Builtin(Builtin::Float64), + cond_lhs, + cond_rhs, + pass, + fail, + ret_var: expr_var, + } + } + _ => { + panic!("TODO handle more conds"); + } + } + } + _ => { + // This is a when-expression with 3+ branches. + let arena = env.arena; + let cond = from_can(env, loc_cond.value, procs, None); + let subs = &env.subs; + let content = subs.get_without_compacting(cond_var).content; + + // We can Switch on integers and tags, because they both have + // representations that work as integer values. + // + // TODO we can also Switch on record fields if we're pattern matching + // on a record field that's also Switchable. + let is_switchable = match &content { + Content::Structure(FlatType::Apply { + module_name, + name, + args, + }) if module_name.as_str() == crate::types::MOD_NUM + && name.as_str() == crate::types::TYPE_NUM => + { + debug_assert!(args.len() == 1); + + let arg = args.iter().next().unwrap(); + + match subs.get_without_compacting(*arg).content { + Content::Structure(FlatType::Apply { + module_name, name, .. + }) if module_name.as_str() == crate::types::MOD_INT => { + // This check shouldn't be necessary; the only + // type that fits the pattern of Num.Num Int._____ + // is an Int! + debug_assert!(name.as_str() == crate::types::TYPE_INTEGER); + + true + } + _ => false, + } + } + _ => false, + }; + + // If the condition is an Int or Float, we can potentially use + // a Switch for more efficiency. + if is_switchable { + // These are integer literals or underscore patterns, + // so they're eligible for user in a jump table. + let mut jumpable_branches = Vec::with_capacity_in(branches.len(), arena); + let mut opt_default_branch = None; + + for (loc_pat, loc_expr) in branches { + let mono_expr = from_can(env, loc_expr.value, procs, None); + + match &loc_pat.value { + IntLiteral(int) => { + // Switch only compares the condition to the + // alternatives based on their bit patterns, + // so casting from i64 to u64 makes no difference here. + jumpable_branches.push((*int as u64, mono_expr)); + } + Identifier(_symbol) => { + // Since this is an ident, it must be + // the last pattern in the `when`. + // We can safely treat this like an `_` + // except that we need to wrap this branch + // in a `Store` so the identifier is in scope! + + opt_default_branch = Some(arena.alloc(if true { + // Using `if true` for this TODO panic to avoid a warning + panic!("TODO wrap this expr in an Expr::Store: {:?}", mono_expr) + } else { + mono_expr + })); + } + Underscore => { + // We should always have exactly one default branch! + debug_assert!(opt_default_branch.is_none()); + + opt_default_branch = Some(arena.alloc(mono_expr)); + } + Shadowed(_loc_ident) => { + panic!("TODO runtime error for shadowing in a pattern"); + } + // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments! + UnsupportedPattern(_region) => { + panic!("TODO runtime error for unsupported pattern"); + } + AppliedTag(_, _, _) + | StrLiteral(_) + | RecordDestructure(_, _) + | FloatLiteral(_) => { + // The type checker should have converted these mismatches into RuntimeErrors already! + if cfg!(debug_assetions) { + panic!("A type mismatch in a pattern was not converted to a runtime error: {:?}", loc_pat); + } else { + unreachable!(); + } + } + } + } + + // If the default branch was never set, that means + // our canonical Expr didn't have one. An earlier + // step in the compilation process should have + // ruled this out! + debug_assert!(opt_default_branch.is_some()); + let default_branch = opt_default_branch.unwrap(); + + Expr::Switch { + cond: arena.alloc(cond), + branches: jumpable_branches.into_bump_slice(), + default_branch, + ret_var: expr_var, + cond_var, + } + } else { + // /// More than two conditional branches, e.g. a 3-way when-expression + // Expr::Branches { + // /// The left-hand side of the conditional. We compile this to LLVM once, + // /// then reuse it to test against each different compiled cond_rhs value. + // cond_lhs: &'a Expr<'a>, + // /// ( cond_rhs, pass, fail ) + // branches: &'a [(Expr<'a>, Expr<'a>, Expr<'a>)], + // ret_var: Variable, + // }, + panic!( + "TODO support when-expressions of 3+ branches whose conditions aren't integers." + ); + } + } + } +} diff --git a/src/ll/layout.rs b/src/mono/layout.rs similarity index 73% rename from src/ll/layout.rs rename to src/mono/layout.rs index b4bf4672d7..b6580b8cc2 100644 --- a/src/ll/layout.rs +++ b/src/mono/layout.rs @@ -1,7 +1,8 @@ -use crate::subs::{Content, FlatType, Subs}; +use crate::subs::{Content, FlatType, Subs, Variable}; use crate::types; use bumpalo::collections::Vec; use bumpalo::Bump; +use cranelift_codegen::isa::TargetFrontendConfig; use inlinable_string::InlinableString; /// Types for code gen must be monomorphic. No type variables allowed! @@ -24,6 +25,11 @@ pub enum Builtin<'a> { } impl<'a> Layout<'a> { + pub fn from_var(arena: &'a Bump, var: Variable, subs: &Subs) -> Result { + let content = subs.get_without_compacting(var).content; + + Self::from_content(arena, content, subs) + } /// Returns Err(()) if given an error, or Ok(Layout) if given a non-erroneous Structure. /// Panics if given a FlexVar or RigidVar, since those should have been /// monomorphized away already! @@ -41,6 +47,50 @@ impl<'a> Layout<'a> { Error => Err(()), } } + + pub fn stack_size(&self, cfg: TargetFrontendConfig) -> u32 { + use Layout::*; + + match self { + Builtin(builtin) => builtin.stack_size(cfg), + Struct(fields) => { + let mut sum = 0; + + for (_, field_layout) in *fields { + sum += field_layout.stack_size(cfg); + } + + sum + } + Pointer(_) | FunctionPointer(_, _) => pointer_size(cfg), + } + } +} + +fn pointer_size(cfg: TargetFrontendConfig) -> u32 { + cfg.pointer_bytes() as u32 +} + +impl<'a> Builtin<'a> { + const I64_SIZE: u32 = std::mem::size_of::() as u32; + const F64_SIZE: u32 = std::mem::size_of::() as u32; + + /// Number of machine words in an empty one of these + const STR_WORDS: u32 = 3; + const MAP_WORDS: u32 = 6; + const SET_WORDS: u32 = Builtin::MAP_WORDS; // Set is an alias for Map with {} for value + + pub fn stack_size(&self, cfg: TargetFrontendConfig) -> u32 { + use Builtin::*; + + match self { + Int64 => Builtin::I64_SIZE, + Float64 => Builtin::F64_SIZE, + Str => Builtin::STR_WORDS * pointer_size(cfg), + Map(_, _) => Builtin::MAP_WORDS * pointer_size(cfg), + Set(_) => Builtin::SET_WORDS * pointer_size(cfg), + } + } } fn layout_from_flat_type<'a>( @@ -102,6 +152,9 @@ fn layout_from_flat_type<'a>( EmptyTagUnion => { panic!("TODO make Layout for empty Tag Union"); } + Boolean(_) => { + panic!("TODO make Layout for Boolean"); + } Erroneous(_) => Err(()), EmptyRecord => Ok(Layout::Struct(&[])), } diff --git a/src/ll/mod.rs b/src/mono/mod.rs similarity index 100% rename from src/ll/mod.rs rename to src/mono/mod.rs diff --git a/src/parse/ast.rs b/src/parse/ast.rs index beb74138d8..6b433d2cfa 100644 --- a/src/parse/ast.rs +++ b/src/parse/ast.rs @@ -217,15 +217,20 @@ pub enum TypeAnnotation<'a> { /// A bound type variable, e.g. `a` in `(a -> a)` BoundVariable(&'a str), - /// A plain record, e.g. `{ name: String, email: Email }` - Record(Vec<'a, Loc>>>), + Record { + fields: &'a [Loc>>], + /// The row type variable in an open record, e.g. the `r` in `{ name: Str }r`. + /// This is None if it's a closed record annotation like `{ name: Str }`. + ext: Option<&'a Loc>>, + }, - /// A record fragment, e.g. `{ name: String, email: Email }...r` - RecordFragment( - Vec<'a, Loc>>>, - // the fragment type variable, e.g. the `r` in `{ name: String }...r` - &'a Loc>, - ), + /// A tag union, e.g. `[ + TagUnion { + tags: &'a [Loc>], + /// The row type variable in an open tag union, e.g. the `a` in `[ Foo, Bar ]a`. + /// This is None if it's a closed tag union like `[ Foo, Bar]`. + ext: Option<&'a Loc>>, + }, /// The `*` type variable, e.g. in (List *) Wildcard, @@ -238,14 +243,31 @@ pub enum TypeAnnotation<'a> { Malformed(&'a str), } +#[derive(Debug, Clone, PartialEq)] +pub enum Tag<'a> { + Global { + name: Loc<&'a str>, + args: &'a [Loc>], + }, + + Private { + name: Loc<&'a str>, + args: &'a [Loc>], + }, + + // We preserve this for the formatter; canonicalization ignores it. + SpaceBefore(&'a Tag<'a>, &'a [CommentOrNewline<'a>]), + SpaceAfter(&'a Tag<'a>, &'a [CommentOrNewline<'a>]), + + /// A malformed tag, which will code gen to a runtime error + Malformed(&'a str), +} + #[derive(Debug, Clone, PartialEq)] pub enum AssignedField<'a, Val> { // Both a label and a value, e.g. `{ name: "blah" }` LabeledValue(Loc<&'a str>, &'a [CommentOrNewline<'a>], &'a Loc), - // An optional field, e.g. `{ name? : String }`. Only for types - OptionalField(Loc<&'a str>, &'a [CommentOrNewline<'a>], &'a Loc), - // A label with no value, e.g. `{ name }` (this is sugar for { name: name }) LabelOnly(Loc<&'a str>), @@ -497,6 +519,15 @@ impl<'a, Val> Spaceable<'a> for AssignedField<'a, Val> { } } +impl<'a> Spaceable<'a> for Tag<'a> { + fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self { + Tag::SpaceBefore(self, spaces) + } + fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self { + Tag::SpaceAfter(self, spaces) + } +} + impl<'a> Spaceable<'a> for Def<'a> { fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self { Def::SpaceBefore(self, spaces) diff --git a/src/parse/ident.rs b/src/parse/ident.rs index 1841e7bdcc..93987ffd31 100644 --- a/src/parse/ident.rs +++ b/src/parse/ident.rs @@ -49,7 +49,7 @@ impl<'a> Ident<'a> { /// Sometimes we may want to check for those later in the process, and give /// more contextually-aware error messages than "unexpected `if`" or the like. #[inline(always)] -pub fn parse_into<'a, I>( +pub fn parse_ident<'a, I>( arena: &'a Bump, chars: &mut I, state: State<'a>, @@ -103,7 +103,7 @@ where } }; - let mut chars_parsed = 1; + let mut chars_parsed = part_buf.len(); let mut next_char = None; while let Some(ch) = chars.next() { @@ -310,7 +310,7 @@ where pub fn ident<'a>() -> impl Parser<'a, Ident<'a>> { move |arena: &'a Bump, state: State<'a>| { // Discard next_char; we don't need it. - let ((string, _), state) = parse_into(arena, &mut state.input.chars(), state)?; + let ((string, _), state) = parse_ident(arena, &mut state.input.chars(), state)?; Ok((string, state)) } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 1a778d313d..56c77aac4e 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -359,7 +359,6 @@ pub fn assigned_expr_field_to_pattern<'a>( ) } } - AssignedField::OptionalField(_, _, _) => panic!("invalid in literals"), AssignedField::LabelOnly(name) => Pattern::Identifier(name.value), AssignedField::SpaceBefore(nested, spaces) => Pattern::SpaceBefore( arena.alloc(assigned_expr_field_to_pattern(arena, nested)?), @@ -400,9 +399,6 @@ pub fn assigned_pattern_field_to_pattern<'a>( ) } } - AssignedField::OptionalField(_, _, _) => { - panic!("invalid as a pattern"); - } AssignedField::LabelOnly(name) => Located::at(name.region, Pattern::Identifier(name.value)), AssignedField::SpaceBefore(nested, spaces) => { let can_nested = assigned_pattern_field_to_pattern(arena, nested, backup_region)?; @@ -434,7 +430,7 @@ pub fn loc_parenthetical_def<'a>(min_indent: u16) -> impl Parser<'a, Located( // e.g. \User.UserId userId -> ... between!( char('('), - space0_around(loc!(pattern(min_indent)), min_indent), + space0_around(loc_pattern(min_indent), min_indent), char(')') ), // The least common, but still allowed, e.g. \Foo -> ... - loc!(tag_pattern()) + loc_tag_pattern(min_indent) ) .parse(arena, state) } -fn pattern<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>> { +fn loc_pattern<'a>(min_indent: u16) -> impl Parser<'a, Located>> { one_of!( - underscore_pattern(), - tag_pattern(), - ident_pattern(), - record_destructure(min_indent), - string_pattern(), - int_pattern() + loc_parenthetical_pattern(min_indent), + loc!(underscore_pattern()), + loc_tag_pattern(min_indent), + loc!(ident_pattern()), + loc!(record_destructure(min_indent)), + loc!(string_pattern()), + loc!(number_pattern()) ) } -fn int_pattern<'a>() -> impl Parser<'a, Pattern<'a>> { +fn loc_parenthetical_pattern<'a>(min_indent: u16) -> impl Parser<'a, Located>> { + between!( + char('('), + move |arena, state| loc_pattern(min_indent).parse(arena, state), + char(')') + ) +} + +fn number_pattern<'a>() -> impl Parser<'a, Pattern<'a>> { map_with_arena!(number_literal(), |arena, expr| { expr_to_pattern(arena, &expr).unwrap() }) @@ -799,7 +804,7 @@ fn underscore_pattern<'a>() -> impl Parser<'a, Pattern<'a>> { fn record_destructure<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>> { then( - record_without_update!(loc!(pattern(min_indent)), min_indent), + record_without_update!(loc_pattern(min_indent), min_indent), move |arena, state, assigned_fields| { let mut patterns = Vec::with_capacity_in(assigned_fields.len(), arena); for assigned_field in assigned_fields { @@ -818,10 +823,29 @@ fn record_destructure<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>> { ) } -fn tag_pattern<'a>() -> impl Parser<'a, Pattern<'a>> { - one_of!( - map!(private_tag(), Pattern::PrivateTag), - map!(global_tag(), Pattern::GlobalTag) +fn loc_tag_pattern<'a>(min_indent: u16) -> impl Parser<'a, Located>> { + map_with_arena!( + and!( + loc!(one_of!( + map!(private_tag(), Pattern::PrivateTag), + map!(global_tag(), Pattern::GlobalTag) + )), + // This can optionally be an applied pattern, e.g. (Foo bar) instead of (Foo) + zero_or_more!(space1_before(loc_pattern(min_indent), min_indent)) + ), + |arena: &'a Bump, + (loc_tag, loc_args): (Located>, Vec<'a, Located>>)| { + if loc_args.is_empty() { + loc_tag + } else { + // TODO FIME this region doesn't cover the tag's + // arguments; need to add them to the region! + let region = loc_tag.region; + let value = Pattern::Apply(&*arena.alloc(loc_tag), loc_args.into_bump_slice()); + + Located { region, value } + } + } ) } @@ -938,10 +962,11 @@ pub fn case_branches<'a>( Ok((branches, state)) } } + fn alternative_patterns<'a>(min_indent: u16) -> impl Parser<'a, Vec<'a, Located>>> { sep_by1( char('|'), - space0_around(loc!(pattern(min_indent)), min_indent), + space0_around(loc_pattern(min_indent), min_indent), ) } @@ -996,49 +1021,61 @@ fn unary_negate_function_arg<'a>(min_indent: u16) -> impl Parser<'a, Located Ok((loc_num_literal, state)), + Either::Second(Located { region, .. }) => { + let loc_op = Located { + region, + value: UnaryOp::Negate, + }; - // Continue parsing the function arg as normal. - let (loc_expr, state) = loc_function_arg(min_indent).parse(arena, state)?; - let region = Region { - start_col: loc_op.region.start_col, - start_line: loc_op.region.start_line, - end_col: loc_expr.region.end_col, - end_line: loc_expr.region.end_line, - }; - let value = Expr::UnaryOp(arena.alloc(loc_expr), loc_op); - let loc_expr = Located { - // Start from where the unary op started, - // and end where its argument expr ended. - // This is relevant in case (for example) - // we have an expression involving parens, - // for example `-(foo bar)` - region, - value, - }; + // Continue parsing the function arg as normal. + let (loc_expr, state) = loc_function_arg(min_indent).parse(arena, state)?; + let region = Region { + start_col: loc_op.region.start_col, + start_line: loc_op.region.start_line, + end_col: loc_expr.region.end_col, + end_line: loc_expr.region.end_line, + }; + let value = Expr::UnaryOp(arena.alloc(loc_expr), loc_op); + let loc_expr = Located { + // Start from where the unary op started, + // and end where its argument expr ended. + // This is relevant in case (for example) + // we have an expression involving parens, + // for example `-(foo bar)` + region, + value, + }; - // spaces can be empy if it's all space characters (no newlines or comments). - let value = if spaces.is_empty() { - loc_expr.value - } else { - Expr::SpaceBefore(arena.alloc(loc_expr.value), spaces) - }; + // spaces can be empy if it's all space characters (no newlines or comments). + let value = if spaces.is_empty() { + loc_expr.value + } else { + Expr::SpaceBefore(arena.alloc(loc_expr.value), spaces) + }; - Ok(( - Located { - region: loc_expr.region, - value, - }, - state, - )) + Ok(( + Located { + region: loc_expr.region, + value, + }, + state, + )) + } + } }, ) } @@ -1406,7 +1443,17 @@ pub fn record_literal<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> { /// This is mainly for matching tags in closure params, e.g. \@Foo -> ... fn private_tag<'a>() -> impl Parser<'a, &'a str> { - skip_first!(char('@'), global_tag()) + // TODO should be refactored so the name is not allocated again. + map_with_arena!( + skip_first!(char('@'), global_tag()), + |arena: &'a Bump, name: &'a str| { + use bumpalo::collections::string::String; + let mut buf = String::with_capacity_in(1 + name.len(), arena); + buf.push('@'); + buf.push_str(name); + buf.into_bump_str() + } + ) } /// This is mainly for matching tags in closure params, e.g. \Foo -> ... diff --git a/src/parse/parser.rs b/src/parse/parser.rs index 784559a35a..17a6ddf4d7 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -905,9 +905,6 @@ macro_rules! record_field { // You must have a field name, e.g. "email" let (loc_label, state) = loc!(lowercase_ident()).parse(arena, state)?; - let (opt_field, state) = - $crate::parse::parser::optional(char('?')).parse(arena, state)?; - let (spaces, state) = space0($min_indent).parse(arena, state)?; // Having a value is optional; both `{ email }` and `{ email: blah }` work. // (This is true in both literals and types.) @@ -917,27 +914,24 @@ macro_rules! record_field { )) .parse(arena, state)?; - let answer = match (opt_loc_val, opt_field) { - (Some(loc_val), None) => LabeledValue(loc_label, spaces, arena.alloc(loc_val)), - (Some(loc_val), Some(_)) => OptionalField(loc_label, spaces, arena.alloc(loc_val)), + let answer = match opt_loc_val { + Some(loc_val) => LabeledValue(loc_label, spaces, arena.alloc(loc_val)), // If no value was provided, record it as a Var. // Canonicalize will know what to do with a Var later. - (None, None) => { + None => { if !spaces.is_empty() { SpaceAfter(arena.alloc(LabelOnly(loc_label)), spaces) } else { LabelOnly(loc_label) } } - (None, Some(_)) => { - panic!("TODO should `{ x? }` be valid? realistically, how of often does `{ a : a }` occur in a type?"); - } }; Ok((answer, state)) } }; } + #[macro_export] macro_rules! record_without_update { ($val_parser:expr, $min_indent:expr) => { diff --git a/src/parse/type_annotation.rs b/src/parse/type_annotation.rs index 23e97efd5d..52ae181d1d 100644 --- a/src/parse/type_annotation.rs +++ b/src/parse/type_annotation.rs @@ -1,9 +1,11 @@ use crate::collections::arena_join; -use crate::parse::ast::{Attempting, TypeAnnotation}; +use crate::parse::ast::{AssignedField, Attempting, Tag, TypeAnnotation}; use crate::parse::blankspace::{space0_around, space0_before, space1_before}; use crate::parse::parser::{ - char, optional, string, unexpected, unexpected_eof, ParseResult, Parser, State, + allocated, char, optional, string, unexpected, unexpected_eof, Either, ParseResult, Parser, + State, }; +use crate::parse::{global_tag, private_tag}; use crate::region::Located; use bumpalo::collections::string::String; use bumpalo::collections::vec::Vec; @@ -13,6 +15,33 @@ pub fn located<'a>(min_indent: u16) -> impl Parser<'a, Located { + map!( + and!( + collection!( + char('['), + loc!(tag_type($min_indent)), + char(','), + char(']'), + $min_indent + ), + optional( + // This could be an open tag union, e.g. `[ Foo, Bar ]a` + move |arena, state| allocated(term($min_indent)).parse(arena, state) + ) + ), + |(tags, ext): ( + Vec<'a, Located>>, + Option<&'a Located>>, + )| TypeAnnotation::TagUnion { + tags: tags.into_bump_slice(), + ext + } + ) + }; +} + pub fn term<'a>(min_indent: u16) -> impl Parser<'a, Located>> { one_of!( // The `*` type variable, e.g. in (List *) Wildcard, @@ -21,6 +50,7 @@ pub fn term<'a>(min_indent: u16) -> impl Parser<'a, Located>> }), loc_parenthetical_type(min_indent), loc!(record_type(min_indent)), + loc!(tag_union!(min_indent)), loc!(applied_type(min_indent)), loc!(parse_type_variable) ) @@ -38,25 +68,56 @@ fn loc_parenthetical_type<'a>(min_indent: u16) -> impl Parser<'a, Located(min_indent: u16) -> impl Parser<'a, Tag<'a>> { + map!( + and!( + either!(loc!(private_tag()), loc!(global_tag())), + // Optionally parse space-separated arguments for the constructor, + // e.g. `ok err` in `Result ok err` + zero_or_more!(space1_before( + move |arena, state| term(min_indent).parse(arena, state), + min_indent, + )) + ), + |(either_name, args): ( + Either, Located<&'a str>>, + Vec<'a, Located>> + )| match either_name { + Either::First(name) => Tag::Private { + name, + args: args.into_bump_slice() + }, + Either::Second(name) => Tag::Global { + name, + args: args.into_bump_slice() + }, + } + ) +} + #[inline(always)] fn record_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>> { use crate::parse::type_annotation::TypeAnnotation::*; - map_with_arena!( + map!( and!( record_without_update!( move |arena, state| term(min_indent).parse(arena, state), min_indent ), - optional(skip_first!( - // This could be a record fragment, e.g. `{ name: String }...r` - string("..."), - move |arena, state| term(min_indent).parse(arena, state) - )) + optional( + // This could be an open record, e.g. `{ name: Str }r` + move |arena, state| allocated(term(min_indent)).parse(arena, state) + ) ), - |arena: &'a Bump, (rec, opt_bound_var)| match opt_bound_var { - None => Record(rec), - Some(loc_bound_var) => RecordFragment(rec, arena.alloc(loc_bound_var)), + |(fields, ext): ( + Vec<'a, Located>>>, + Option<&'a Located>>, + )| Record { + fields: fields.into_bump_slice(), + ext } ) } diff --git a/src/pretty_print_types.rs b/src/pretty_print_types.rs index d568e49ade..7a0b0251e1 100644 --- a/src/pretty_print_types.rs +++ b/src/pretty_print_types.rs @@ -2,6 +2,7 @@ use crate::can::ident::{Lowercase, ModuleName, Uppercase}; use crate::collections::{MutMap, MutSet}; use crate::subs::{Content, FlatType, Subs, Variable}; use crate::types::{self, name_type_var}; +use crate::uniqueness::boolean_algebra::Bool; static WILDCARD: &str = "*"; static EMPTY_RECORD: &str = "{}"; @@ -104,6 +105,11 @@ fn find_names_needed( find_names_needed(ext_var, subs, roots, root_appearances, names_taken); } + Structure(Boolean(b)) => { + for var in b.variables() { + find_names_needed(var, subs, roots, root_appearances, names_taken); + } + } RigidVar(name) => { // User-defined names are already taken. // We must not accidentally generate names that collide with them! @@ -202,6 +208,13 @@ fn write_flat_type(flat_type: FlatType, subs: &mut Subs, buf: &mut String, paren EmptyTagUnion => buf.push_str(EMPTY_TAG_UNION), Func(args, ret) => write_fn(args, ret, subs, buf, parens), Record(fields, ext_var) => { + use crate::unify::gather_fields; + use crate::unify::RecordStructure; + + // If the `ext` has concrete fields (e.g. { foo : Int}{ bar : Bool }), merge them + let RecordStructure { fields, ext } = gather_fields(subs, fields, ext_var); + let ext_var = ext; + if fields.is_empty() { buf.push_str(EMPTY_RECORD) } else { @@ -295,12 +308,52 @@ fn write_flat_type(flat_type: FlatType, subs: &mut Subs, buf: &mut String, paren } } } + Boolean(b) => { + write_boolean(b, subs, buf, Parens::InTypeParam); + } Erroneous(problem) => { buf.push_str(&format!("", problem)); } } } +fn write_boolean(boolean: Bool, subs: &mut Subs, buf: &mut String, parens: Parens) { + let is_atom = boolean.is_var() || boolean == Bool::Zero || boolean == Bool::One; + let write_parens = parens == Parens::InTypeParam && !is_atom; + + if write_parens { + buf.push_str("("); + } + + match boolean { + Bool::Variable(var) => write_content(subs.get(var).content, subs, buf, parens), + Bool::Or(p, q) => { + write_boolean(*p, subs, buf, Parens::InTypeParam); + buf.push_str(" | "); + write_boolean(*q, subs, buf, Parens::InTypeParam); + } + Bool::And(p, q) => { + write_boolean(*p, subs, buf, Parens::InTypeParam); + buf.push_str(" & "); + write_boolean(*q, subs, buf, Parens::InTypeParam); + } + Bool::Not(p) => { + buf.push_str("!"); + write_boolean(*p, subs, buf, Parens::InTypeParam); + } + Bool::Zero => { + buf.push_str("Attr.Shared"); + } + Bool::One => { + buf.push_str("Attr.Unique"); + } + }; + + if write_parens { + buf.push_str(")"); + } +} + fn write_apply( module_name: ModuleName, type_name: Uppercase, diff --git a/src/solve.rs b/src/solve.rs index 98cc401418..7df5e89b08 100644 --- a/src/solve.rs +++ b/src/solve.rs @@ -7,6 +7,7 @@ use crate::types::Constraint::{self, *}; use crate::types::Problem; use crate::types::Type::{self, *}; use crate::unify::{unify, Unified}; +use crate::uniqueness::boolean_algebra; type Env = ImMap; @@ -401,6 +402,13 @@ fn type_to_variable( register(subs, rank, pools, content) } + // This case is important so e.g. `Bool::Variable(v) ~ Attr.Shared` + Boolean(boolean_algebra::Bool::Variable(var)) => *var, + Boolean(b) => { + let content = Content::Structure(FlatType::Boolean(b.clone())); + + register(subs, rank, pools, content) + } Function(args, ret_type) => { let mut arg_vars = Vec::with_capacity(args.len()); @@ -673,6 +681,15 @@ fn adjust_rank_content( rank } + Boolean(b) => { + let mut rank = Rank::toplevel(); + for var in b.variables() { + rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); + } + + rank + } + Erroneous(_) => group_rank, } } @@ -806,6 +823,12 @@ fn deep_copy_var_help( TagUnion(new_tags, deep_copy_var_help(subs, max_rank, pools, ext_var)) } + + Boolean(b) => { + let mut mapper = |var| deep_copy_var_help(subs, max_rank, pools, var); + + Boolean(b.map_variables(&mut mapper)) + } }; subs.set(copy, make_descriptor(Structure(new_flat_type))); diff --git a/src/subs.rs b/src/subs.rs index 2420a3363f..a76e9f7dbe 100644 --- a/src/subs.rs +++ b/src/subs.rs @@ -2,7 +2,9 @@ use crate::can::ident::{Lowercase, ModuleName, Uppercase}; use crate::can::symbol::Symbol; use crate::collections::{ImMap, ImSet, MutSet, SendMap}; use crate::ena::unify::{InPlace, UnificationTable, UnifyKey}; +use crate::types; use crate::types::{name_type_var, ErrorType, Problem, RecordFieldLabel, TypeExt}; +use crate::uniqueness::boolean_algebra; use std::fmt; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -134,7 +136,7 @@ impl Into> for OptVariable { } } -#[derive(Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Variable(usize); impl Variable { @@ -145,6 +147,10 @@ impl Variable { const NULL: Variable = Variable(0); const FIRST_USER_SPACE_VAR: Variable = Variable(1); + + pub fn unsafe_debug_variable(v: usize) -> Self { + Variable(v) + } } impl Into for Variable { @@ -422,6 +428,18 @@ pub enum Content { Error, } +impl Content { + #[inline(always)] + pub fn is_number(&self) -> bool { + match &self { + Content::Structure(FlatType::Apply { + module_name, name, .. + }) => module_name.as_str() == types::MOD_NUM && name.as_str() == types::TYPE_NUM, + _ => false, + } + } +} + #[derive(Clone, Debug, PartialEq, Eq)] pub enum FlatType { Apply { @@ -435,6 +453,7 @@ pub enum FlatType { Erroneous(Problem), EmptyRecord, EmptyTagUnion, + Boolean(boolean_algebra::Bool), } #[derive(PartialEq, Eq, Debug, Clone, Copy)] @@ -478,6 +497,10 @@ fn occurs(subs: &mut Subs, seen: &ImSet, var: Variable) -> bool { .values() .any(|vars| vars.iter().any(|var| occurs(subs, &new_seen, *var))) } + Boolean(b) => b + .variables() + .iter() + .any(|var| occurs(subs, &new_seen, *var)), EmptyRecord | EmptyTagUnion | Erroneous(_) => false, } } @@ -556,6 +579,12 @@ fn get_var_names( taken_names } + FlatType::Boolean(b) => b + .variables() + .into_iter() + .fold(taken_names, |answer, arg_var| { + get_var_names(subs, arg_var, answer) + }), }, } } @@ -755,6 +784,8 @@ fn flat_type_to_err_type(subs: &mut Subs, state: &mut NameState, flat_type: Flat } } + Boolean(b) => ErrorType::Boolean(b), + Erroneous(_) => ErrorType::Error, } } @@ -806,6 +837,11 @@ fn restore_content(subs: &mut Subs, content: &Content) { subs.restore(*ext_var); } + Boolean(b) => { + for var in b.variables() { + subs.restore(var); + } + } Erroneous(_) => (), }, Alias(_, _, args, var) => { diff --git a/src/types.rs b/src/types.rs index d74a254e19..0bad5fc719 100644 --- a/src/types.rs +++ b/src/types.rs @@ -7,6 +7,7 @@ use crate::operator::{ArgSide, BinOp}; use crate::region::Located; use crate::region::Region; use crate::subs::Variable; +use crate::uniqueness::boolean_algebra; use std::fmt; // The standard modules @@ -39,6 +40,8 @@ pub enum Type { name: Uppercase, args: Vec, }, + /// Boolean type used in uniqueness inference + Boolean(boolean_algebra::Bool), Variable(Variable), /// A type error, which will code gen to a runtime error Erroneous(Problem), @@ -185,6 +188,7 @@ impl fmt::Debug for Type { } } } + Type::Boolean(b) => write!(f, "{:?}", b), } } } @@ -320,6 +324,8 @@ pub enum Reason { IntLiteral, InterpolatedStringVar, WhenBranch { index: usize }, + IfCondition, + IfBranch { index: usize }, ElemInList, RecordUpdateValue(Lowercase), RecordUpdateKeys(Ident, SendMap), @@ -389,6 +395,7 @@ pub enum ErrorType { Vec<(Lowercase, ErrorType)>, Box, ), + Boolean(boolean_algebra::Bool), Error, } diff --git a/src/unify.rs b/src/unify.rs index cec73411be..e093f8a9a6 100644 --- a/src/unify.rs +++ b/src/unify.rs @@ -5,6 +5,7 @@ use crate::subs::Content::{self, *}; use crate::subs::{Descriptor, FlatType, Mark, OptVariable, Subs, Variable}; use crate::types::RecordFieldLabel; use crate::types::{Mismatch, Problem}; +use crate::uniqueness::boolean_algebra; type Pool = Vec; @@ -15,9 +16,9 @@ struct Context { second_desc: Descriptor, } -struct RecordStructure { - fields: ImMap, - ext: Variable, +pub struct RecordStructure { + pub fields: ImMap, + pub ext: Variable, } struct TagUnionStructure { @@ -404,6 +405,18 @@ fn unify_flat_type( unify_tag_union(subs, pool, ctx, union1, union2) } + (Boolean(b1), Boolean(b2)) => { + if let Some(substitution) = boolean_algebra::try_unify(b1.clone(), b2.clone()) { + for (var, replacement) in substitution { + subs.set_content(var, Structure(FlatType::Boolean(replacement))); + } + + vec![] + } else { + mismatch() + } + } + ( Apply { module_name: l_module_name, @@ -506,7 +519,7 @@ fn unify_flex( } } -fn gather_fields( +pub fn gather_fields( subs: &mut Subs, fields: ImMap, var: Variable, diff --git a/src/uniqueness/boolean_algebra.rs b/src/uniqueness/boolean_algebra.rs index 827ea7d49c..8a715486ec 100644 --- a/src/uniqueness/boolean_algebra.rs +++ b/src/uniqueness/boolean_algebra.rs @@ -1,168 +1,573 @@ -use crate::collections::ImMap; +// Based on work by Edsko de Vries for tfp 2007 +// +// http://www.edsko.net/tcd/pub/tfp07-prototype-snapshot.tar.gz +// +// Thank you Edsko! +// +// quoting from that work: +// +// > Main reference for this module is "Boolean Reasoning: The Logic of +// > Boolean Equations" by Frank Markham Brown. +use crate::collections::{ImMap, ImSet}; use crate::subs::Variable; -pub fn unify(typ: &BooleanAlgebra, _expected: &BooleanAlgebra) -> Option { - // find the most general unifier. - let mut val = typ.clone(); - let fv = val.variables(); - let (mgu, consistency_condition) = boolean_unification(&mut val, &fv); +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Bool { + Zero, + One, + And(Box, Box), + Or(Box, Box), + Not(Box), + Variable(Variable), +} - // the consistency_condition must be a base term, and must evaluate to False - if !consistency_condition.evaluate() { - Some(mgu) +use self::Bool::*; + +#[inline(always)] +pub fn not(nested: Bool) -> Bool { + Not(Box::new(nested)) +} + +#[inline(always)] +pub fn and(left: Bool, right: Bool) -> Bool { + And(Box::new(left), Box::new(right)) +} + +#[inline(always)] +pub fn or(left: Bool, right: Bool) -> Bool { + Or(Box::new(left), Box::new(right)) +} + +pub fn any(mut it: I) -> Bool +where + I: Iterator, +{ + if let Some(first) = it.next() { + it.fold(first, or) + } else { + Zero + } +} + +pub fn all(terms: Product) -> Bool { + let mut it = terms.into_iter(); + + if let Some(first) = it.next() { + it.fold(first, and) + } else { + One + } +} + +type Substitution = ImMap; + +type Product = ImSet; +type Sum = ImSet; + +#[allow(clippy::should_implement_trait)] +impl Bool { + pub fn variables(&self) -> ImSet { + let mut result = ImSet::default(); + + self.variables_help(&mut result); + result + } + + fn variables_help(&self, vars: &mut ImSet) { + match self { + Zero => (), + One => (), + And(left, right) => { + left.variables_help(vars); + right.variables_help(vars) + } + Or(left, right) => { + left.variables_help(vars); + right.variables_help(vars) + } + Not(nested) => nested.variables_help(vars), + Variable(var) => { + vars.insert(*var); + } + }; + } + + pub fn map_variables(&self, f: &mut F) -> Self + where + F: FnMut(Variable) -> Variable, + { + match self { + Zero => Zero, + One => One, + And(left, right) => and(left.map_variables(f), right.map_variables(f)), + Or(left, right) => or(left.map_variables(f), right.map_variables(f)), + Not(nested) => not(nested.map_variables(f)), + Variable(current) => Variable(f(*current)), + } + } + + pub fn substitute(&self, substitutions: &Substitution) -> Self { + match self { + Zero => Zero, + One => One, + And(left, right) => and( + left.substitute(substitutions), + right.substitute(substitutions), + ), + Or(left, right) => or( + left.substitute(substitutions), + right.substitute(substitutions), + ), + Not(nested) => not(nested.substitute(substitutions)), + Variable(current) => match substitutions.get(current) { + Some(new) => new.clone(), + None => Variable(*current), + }, + } + } + + #[inline(always)] + pub fn is_var(&self) -> bool { + match self { + Variable(_) => true, + _ => false, + } + } + + #[inline(always)] + pub fn not(nested: Bool) -> Bool { + not(nested) + } + + #[inline(always)] + pub fn and(left: Bool, right: Bool) -> Bool { + and(left, right) + } + + #[inline(always)] + pub fn or(left: Bool, right: Bool) -> Bool { + or(left, right) + } +} + +pub fn simplify(term: Bool) -> Bool { + let normalized = normalize_term(term); + let a = term_to_sop(normalized); + let b = normalize_sop(a); + let after_bcf = bcf(b); + sop_to_term(simplify_sop(after_bcf)) +} + +#[inline(always)] +pub fn sop_to_term(sop: Sop) -> Bool { + any(sop.into_iter().map(all)) +} + +pub fn simplify_sop(sop: Sop) -> Sop { + // sort by length longest to shortest (proxy for how many variables there are) + let mut sorted: Vec> = sop.clone().into_iter().collect(); + + sorted.sort_by(|x, y| y.len().cmp(&x.len())); + + // filter out anything that is included in the remaining elements + let mut active = sop; + let mut result = ImSet::default(); + for t in sorted { + if !(active.remove(&t).is_some() && included(all(t.clone()), sop_to_term(active.clone()))) { + result.insert(t); + } + } + + result +} + +/// Blake canonical form +fn bcf(sop: Sop) -> Sop { + absorptive(syllogistic(sop)) +} + +fn syllogistic(terms: Sop) -> Sop { + let mut cs_prime = ImSet::default(); + + for c in cartesian_product(terms.clone()) + .iter() + .filter_map(|(x, y)| consensus(x, y)) + { + if !terms + .clone() + .into_iter() + .any(|x| included_term(c.clone(), x)) + { + cs_prime.insert(c); + } + } + + if cs_prime.is_empty() { + terms + } else { + syllogistic(terms.union(cs_prime)) + } +} + +/// Absorption (apply the identify p + pq = p) +fn absorptive(sop: Sop) -> Sop { + // TODO this is extremely inefficient! + let mut accum: Vec> = Vec::new(); + + for product in sop { + accum = accum + .into_iter() + .filter(|v| !absorbs(&product, v)) + .collect(); + accum.push(product); + } + + accum.into() +} + +/// Does p absorb q? (can we replace p + q by p?) +/// TODO investigate: either the comment or the implementation is wrong I think? +fn absorbs(p: &Product, q: &Product) -> bool { + p.iter().all(|x| q.contains(x)) +} + +fn consensus(p: &Product, q: &Product) -> Option> { + let mut it = oppositions(p, q).into_iter(); + + // oppositions must have exactly one element + if let Some(x) = it.next() { + if it.next().is_none() { + let compx = not(x.clone()); + + return Some( + p.clone() + .into_iter() + .chain(q.clone().into_iter()) + .filter(|y| *y != x && *y != compx) + .collect(), + ); + } + } + + None +} + +fn oppositions(ps: &Product, qs: &Product) -> Product { + let it1 = ps + .clone() + .into_iter() + .filter(|p| qs.contains(¬(p.clone()))); + let it2 = qs + .clone() + .into_iter() + .filter(|q| ps.contains(¬(q.clone()))); + + it1.chain(it2).collect() +} + +pub fn try_unify(p: Bool, q: Bool) -> Option { + let (sub, consistency) = unify(p, q); + + let substitution = sub + .into_iter() + .filter(|(x, p)| *p != Variable(*x)) + .collect(); + + if consistency == Zero { + Some(substitution) } else { - // the unification has no solution None } } -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum BooleanAlgebra { - Ground(bool), - Disjunction(Box, Box), - Conjunction(Box, Box), - Negation(Box), - Variable(Variable), +fn unify(p: Bool, q: Bool) -> (Substitution, Bool) { + let condition = q.is_var() && !p.is_var(); + let t = if condition { + or(and(q.clone(), not(p.clone())), and(not(q), p)) + } else { + or(and(p.clone(), not(q.clone())), and(not(p), q)) + }; + + unify0(t.variables(), t) } -impl BooleanAlgebra { - pub fn simplify(&mut self) { - *self = simplify(self.clone()); +fn unify0(names: ImSet, mut term: Bool) -> (Substitution, Bool) { + // NOTE sort is required for stable test order that is the same as the Haskell ref. impl. + let mut substitution: Substitution = ImMap::default(); + + let mut sorted_names: Vec = names.into_iter().collect(); + sorted_names.sort(); + + for x in sorted_names.into_iter() { + let mut sub_zero = ImMap::default(); + sub_zero.insert(x, Zero); + + let mut sub_one = ImMap::default(); + sub_one.insert(x, One); + + let subbed_zero = term.substitute(&sub_zero); + let subbed_one = term.substitute(&sub_one); + + term = and(subbed_zero.clone(), subbed_one.clone()); + + let replacement = simplify(or( + subbed_zero.substitute(&substitution), + and(Variable(x), not(subbed_one.substitute(&substitution))), + )); + + substitution.insert(x, replacement); } - pub fn substitute(&self, var: Variable, expanded: &BooleanAlgebra) -> Self { - use BooleanAlgebra::*; - match self { - Variable(v) if v == &var => expanded.clone(), - Variable(_) | Ground(_) => self.clone(), + (substitution, simplify(term)) +} - Negation(t) => Negation(Box::new(t.substitute(var, expanded))), +// --- Simplification --- - Disjunction(l, r) => Disjunction( - Box::new(l.substitute(var, expanded)), - Box::new(r.substitute(var, expanded)), - ), +/// Normalization of terms. Applies (in bottom-up fashion) the identities +/// +/// x * 1 = x +/// x * 0 = 0 +/// x + 1 = 1 +/// x + 0 = x +/// !1 = 0 +/// !0 = 1 +pub fn normalize_term(term: Bool) -> Bool { + match term { + And(left, right) => { + let p = normalize_term(*left); + let q = normalize_term(*right); - Conjunction(l, r) => Conjunction( - Box::new(l.substitute(var, expanded)), - Box::new(r.substitute(var, expanded)), - ), + match (p == One, p == Zero, q == One, q == Zero) { + (true, _, _, _) => q, + (_, true, _, _) => Zero, + (_, _, true, _) => p, + (_, _, _, true) => Zero, + _ => and(p, q), + } + } + Or(left, right) => { + let p = normalize_term(*left); + let q = normalize_term(*right); + + match (p == One, p == Zero, q == One, q == Zero) { + (true, _, _, _) => One, + (_, true, _, _) => q, + (_, _, true, _) => One, + (_, _, _, true) => p, + _ => or(p, q), + } + } + Not(nested) => { + let p = normalize_term(*nested); + + match (p == One, p == Zero) { + (true, _) => Zero, + (_, true) => One, + _ => not(p), + } + } + _ => term, + } +} + +// --- Inclusion --- + +pub fn included(g: Bool, h: Bool) -> bool { + contradiction(and(g, not(h))) +} + +fn included_term(g: Product, h: Product) -> bool { + included(all(g), all(h)) +} + +// --- Tautology / Contradiction --- + +fn tautology(term: Bool) -> bool { + normalize_pos(term_to_pos(normalize_term(term))).is_empty() +} + +pub fn contradiction(term: Bool) -> bool { + tautology(not(term)) +} + +// --- Normalization of POS / SOP + +type Pos = Product>; +type Sop = Sum>; + +fn term_to_pos(term: Bool) -> Pos { + conj_to_list(cnf(term)) + .into_iter() + .map(disj_to_list) + .collect() +} + +pub fn term_to_sop(term: Bool) -> Sop { + disj_to_list(dnf(term)) + .into_iter() + .map(conj_to_list) + .collect() +} + +fn conj_to_list(term: Bool) -> Product { + match term { + And(left, right) => { + let p = conj_to_list(*left); + let q = conj_to_list(*right); + + p.union(q) + } + _ => unit(term), + } +} + +fn disj_to_list(term: Bool) -> Sum { + match term { + Or(left, right) => { + let p = disj_to_list(*left); + let q = disj_to_list(*right); + + p.union(q) + } + _ => unit(term), + } +} + +fn normalize_pos(pos: Pos) -> Pos { + let singleton_one = unit(One); + + pos.into_iter() + .map(normalize_disj) + .filter(|normalized| *normalized != singleton_one) + .collect() +} + +pub fn normalize_sop(sop: Sop) -> Sop { + let singleton_zero = unit(Zero); + + sop.into_iter() + .map(normalize_conj) + .filter(|normalized| *normalized != singleton_zero) + .collect() +} + +fn cartesian_product(set: ImSet) -> ImSet<(A, A)> +where + A: Eq + Clone + core::hash::Hash, +{ + let mut result = ImSet::default(); + + for x in set.clone().into_iter() { + for y in set.clone() { + if x == y { + break; + } + + result.insert((x.clone(), y)); } } - pub fn evaluate(&self) -> bool { - use BooleanAlgebra::*; - match self { - Variable(v) => panic!( - "Cannot evaluate boolean expression with unbound variable {:?}", - v - ), - Ground(b) => *b, - Negation(t) => !(t.evaluate()), - Disjunction(l, r) => l.evaluate() || r.evaluate(), - Conjunction(l, r) => l.evaluate() && r.evaluate(), - } - } + result +} - pub fn variables(&self) -> Vec { - let mut vars = Vec::new(); - variables_help(self, &mut vars); - vars +fn unit(a: A) -> ImSet +where + A: Clone + Eq + core::hash::Hash, +{ + let mut result = ImSet::default(); + result.insert(a); + result +} + +fn normalize_disj(mut sum: Sum) -> Sum { + let is_always_true = + sum.clone().into_iter().any(|x| sum.contains(¬(x))) || sum.contains(&One); + + if is_always_true { + unit(One) + } else { + sum.remove(&Zero); + sum } } -fn variables_help(bconstraint: &BooleanAlgebra, variables: &mut Vec) { - use BooleanAlgebra::*; +fn normalize_conj(mut product: Product) -> Product { + let is_always_false = product + .clone() + .into_iter() + .any(|x| product.contains(¬(x))) + || product.contains(&Zero); - match bconstraint { - Variable(v) => variables.push(v.clone()), - Ground(_) => {} - Negation(t) => variables_help(t, variables), - Disjunction(l, r) => { - variables_help(l, variables); - variables_help(r, variables); - } - Conjunction(l, r) => { - variables_help(l, variables); - variables_help(r, variables); - } + if is_always_false { + unit(Zero) + } else { + product.remove(&One); + product } } -fn simplify(bconstraint: BooleanAlgebra) -> BooleanAlgebra { - use BooleanAlgebra::*; - match bconstraint { - Variable(_) | Ground(_) => bconstraint, - - Negation(nested) => match simplify(*nested) { - Ground(t) => Ground(!t), - other => Negation(Box::new(other)), - }, - - Disjunction(l, r) => match (simplify(*l), simplify(*r)) { - (Ground(true), _) => Ground(true), - (_, Ground(true)) => Ground(true), - (Ground(false), rr) => rr, - (ll, Ground(false)) => ll, - (ll, rr) => Disjunction(Box::new(ll), Box::new(rr)), - }, - - Conjunction(l, r) => match (simplify(*l), simplify(*r)) { - (Ground(true), rr) => rr, - (ll, Ground(true)) => ll, - (Ground(false), _) => Ground(false), - (_, Ground(false)) => Ground(false), - (ll, rr) => Conjunction(Box::new(ll), Box::new(rr)), - }, +/// Conjunction Normal Form +fn cnf(term: Bool) -> Bool { + match nnf(term) { + And(p, q) => and(cnf(*p), cnf(*q)), + Or(p, q) => distr_cnf(cnf(*p), cnf(*q)), + other => other, } } -#[derive(Debug, Clone, PartialEq)] -pub struct Substitution { - pairs: ImMap, -} - -impl Substitution { - pub fn empty() -> Self { - Substitution { - pairs: ImMap::default(), - } - } - - pub fn insert(&mut self, var: Variable, term: BooleanAlgebra) { - self.pairs.insert(var, term); - } - - pub fn get(&self, var: Variable) -> Option<&BooleanAlgebra> { - self.pairs.get(&var) +fn distr_cnf(p: Bool, q: Bool) -> Bool { + match p { + And(p1, p2) => and(distr_cnf(*p1, q.clone()), distr_cnf(*p2, q)), + _ => distr_cnf_help(p, q), } } -fn boolean_unification( - term: &mut BooleanAlgebra, - variables: &[Variable], -) -> (Substitution, BooleanAlgebra) { - use BooleanAlgebra::*; - let mut substitution = Substitution::empty(); - - for var in variables { - let t0 = term.clone().substitute(*var, &Ground(false)); - let t1 = term.clone().substitute(*var, &Ground(true)); - - *term = Conjunction(Box::new(t1.clone()), Box::new(t0.clone())); - term.simplify(); - - let mut sub = Disjunction( - Box::new(t0), - Box::new(Conjunction( - Box::new(Variable(*var)), - Box::new(Negation(Box::new(t1))), - )), - ); - sub.simplify(); - - substitution.insert(var.clone(), sub); +fn distr_cnf_help(p: Bool, q: Bool) -> Bool { + match q { + And(q1, q2) => and(distr_cnf(p.clone(), *q1), distr_cnf(p, *q2)), + _ => or(p, q), + } +} + +/// Disjunction Normal Form +pub fn dnf(term: Bool) -> Bool { + match nnf(term) { + And(p, q) => distr_dnf(dnf(*p), dnf(*q)), + Or(p, q) => or(dnf(*p), dnf(*q)), + other => other, + } +} + +fn distr_dnf(p: Bool, q: Bool) -> Bool { + match p { + Or(p1, p2) => or(distr_dnf(*p1, q.clone()), distr_dnf(*p2, q)), + _ => distr_dnf_help(p, q), + } +} + +fn distr_dnf_help(p: Bool, q: Bool) -> Bool { + match q { + Or(q1, q2) => or(distr_dnf(p.clone(), *q1), distr_dnf(p, *q2)), + _ => and(p, q), + } +} + +/// Negation Normal Form +pub fn nnf(term: Bool) -> Bool { + match term { + Not(n) => nnf_help(*n), + _ => term, + } +} + +pub fn nnf_help(term: Bool) -> Bool { + match term { + Zero => One, + One => Zero, + And(p, q) => or(nnf_help(*p), nnf_help(*q)), + Or(p, q) => and(nnf_help(*p), nnf_help(*q)), + // double negation + Not(nested) => nnf(*nested), + Variable(_) => not(term), } - - (substitution, term.clone()) } diff --git a/src/uniqueness/constrain.rs b/src/uniqueness/constrain.rs index a87a07f586..1e6482156d 100644 --- a/src/uniqueness/constrain.rs +++ b/src/uniqueness/constrain.rs @@ -1,103 +1,26 @@ -use crate::collections::SendMap; -use crate::region::Region; -use crate::subs::{VarStore, Variable}; -use crate::types::Constraint::{self, *}; -use crate::types::Expected::{self, *}; -use crate::types::Type::{self, *}; -use crate::types::{self, LetConstraint, Reason}; - -pub fn exists(flex_vars: Vec, constraint: Constraint) -> Constraint { - Constraint::Let(Box::new(LetConstraint { - rigid_vars: Vec::new(), - flex_vars, - def_types: SendMap::default(), - defs_constraint: constraint, - ret_constraint: Constraint::True, - })) -} +use crate::subs::VarStore; +use crate::types::Type; +use crate::uniqueness::boolean_algebra::Bool; pub fn lift(var_store: &VarStore, typ: Type) -> Type { let uniq_var = var_store.fresh(); - let uniq_type = Variable(uniq_var); + let uniq_type = Bool::Variable(uniq_var); attr_type(uniq_type, typ) } -pub fn int_literal(var_store: &VarStore, expected: Expected, region: Region) -> Constraint { - let typ = lift(var_store, number_literal_type("Int", "Integer")); - let reason = Reason::IntLiteral; - - num_literal(var_store, typ, reason, expected, region) -} - -#[inline(always)] -pub fn float_literal(var_store: &VarStore, expected: Expected, region: Region) -> Constraint { - let typ = lift(var_store, number_literal_type("Float", "FloatingPoint")); - let reason = Reason::FloatLiteral; - - num_literal(var_store, typ, reason, expected, region) -} - -#[inline(always)] -fn num_literal( - var_store: &VarStore, - literal_type: Type, - reason: Reason, - expected: Expected, - region: Region, -) -> Constraint { - let num_var = var_store.fresh(); - let num_type = Variable(num_var); - let expected_literal = ForReason(reason, literal_type, region); - - And(vec![ - Eq(num_type.clone(), expected_literal, region), - Eq(num_type, expected, region), - ]) -} - -#[inline(always)] -fn number_literal_type(module_name: &str, type_name: &str) -> Type { - builtin_type( - types::MOD_NUM, - types::TYPE_NUM, - vec![builtin_type(module_name, type_name, Vec::new())], - ) -} - -#[inline(always)] -fn builtin_type(module_name: &str, type_name: &str, args: Vec) -> Type { - Type::Apply { - module_name: module_name.into(), - name: type_name.into(), - args, - } -} - -pub fn empty_list_type(var: Variable) -> Type { - list_type(Type::Variable(var)) -} - -pub fn list_type(typ: Type) -> Type { - builtin_type("List", "List", vec![typ]) -} - -pub fn str_type() -> Type { - builtin_type("Str", "Str", Vec::new()) -} - -type Uniqueness = Type; +type Uniqueness = Bool; pub fn attr_type(uniq: Uniqueness, typ: Type) -> Type { - builtin_type("Attr", "Attr", vec![uniq, typ]) + crate::constrain::builtins::builtin_type("Attr", "Attr", vec![Type::Boolean(uniq), typ]) } pub fn shared_type() -> Uniqueness { - builtin_type("Attr", "Shared", Vec::new()) + Bool::Zero } /// We usually just leave a type parameter unbound (written `*`) when it's unique #[allow(dead_code)] pub fn unique_type() -> Uniqueness { - builtin_type("Attr", "Unique", Vec::new()) + Bool::One } diff --git a/src/uniqueness/mod.rs b/src/uniqueness/mod.rs index 6b14a2e360..726e71f38e 100644 --- a/src/uniqueness/mod.rs +++ b/src/uniqueness/mod.rs @@ -1,13 +1,14 @@ use crate::can::def::Def; use crate::can::expr::Expr; use crate::can::expr::Field; -use crate::can::expr::Output; use crate::can::ident::Lowercase; use crate::can::pattern; use crate::can::pattern::{Pattern, RecordDestruct}; -use crate::can::procedure::{Procedure, References}; +use crate::can::procedure::Procedure; use crate::can::symbol::Symbol; use crate::collections::{ImMap, SendMap}; +use crate::constrain::builtins; +use crate::constrain::expr::exists; use crate::constrain::expr::{Info, Rigids}; use crate::ident::Ident; use crate::region::{Located, Region}; @@ -21,7 +22,7 @@ use crate::types::PReason::{self}; use crate::types::Reason; use crate::types::RecordFieldLabel; use crate::types::Type::{self, *}; -use crate::uniqueness::constrain::exists; +use crate::uniqueness::boolean_algebra::Bool; use crate::uniqueness::sharing::VarUsage; pub use crate::can::expr::Expr::*; @@ -35,17 +36,17 @@ pub struct Env { pub procedures: ImMap, } -pub fn canonicalize_declaration( +pub fn constrain_declaration( var_store: &VarStore, region: Region, loc_expr: Located, _declared_idents: &ImMap, expected: Expected, -) -> (Output, Constraint) { +) -> Constraint { let rigids = ImMap::default(); let mut var_usage = VarUsage::default(); - canonicalize_expr( + constrain_expr( &rigids, var_store, &mut var_usage, @@ -61,7 +62,7 @@ pub struct PatternState { pub constraints: Vec, } -fn canonicalize_pattern( +fn constrain_pattern( var_store: &VarStore, state: &mut PatternState, pattern: &Located, @@ -85,7 +86,7 @@ fn canonicalize_pattern( state.constraints.push(Constraint::Pattern( pattern.region, PatternCategory::Int, - Type::int(), + constrain::lift(var_store, Type::int()), expected, )); } @@ -93,7 +94,7 @@ fn canonicalize_pattern( state.constraints.push(Constraint::Pattern( pattern.region, PatternCategory::Float, - Type::float(), + constrain::lift(var_store, Type::float()), expected, )); } @@ -102,12 +103,14 @@ fn canonicalize_pattern( state.constraints.push(Constraint::Pattern( pattern.region, PatternCategory::Str, - Type::string(), + constrain::lift(var_store, Type::string()), expected, )); } RecordDestructure(ext_var, patterns) => { + let mut pattern_uniq_vars = Vec::with_capacity(patterns.len()); + state.vars.push(*ext_var); let ext_type = Type::Variable(*ext_var); @@ -119,32 +122,48 @@ fn canonicalize_pattern( guard, } in patterns { - let pat_type = Type::Variable(*var); - let pattern_expected = PExpected::NoExpectation(pat_type.clone()); + let pat_uniq_var = var_store.fresh(); + pattern_uniq_vars.push(pat_uniq_var); - match guard { - Some((_guard_var, loc_guard)) => { - state.headers.insert( - symbol.clone(), - Located { - region: pattern.region, - value: pat_type.clone(), - }, - ); + let pat_type = + constrain::attr_type(Bool::Variable(pat_uniq_var), Type::Variable(*var)); + let expected = PExpected::NoExpectation(pat_type.clone()); - canonicalize_pattern(var_store, state, loc_guard, pattern_expected); - } - None => { - canonicalize_pattern(var_store, state, pattern, pattern_expected); - } + if !state.headers.contains_key(&symbol) { + state.headers.insert( + symbol.clone(), + Located::at(pattern.region, pat_type.clone()), + ); + } + + field_types.insert(label.clone(), pat_type.clone()); + + if let Some((guard_var, loc_guard)) = guard { + state.constraints.push(Eq( + Type::Variable(*guard_var), + Expected::NoExpectation(pat_type.clone()), + pattern.region, + )); + state.vars.push(*guard_var); + constrain_pattern(var_store, state, loc_guard, expected); } state.vars.push(*var); - field_types.insert(label.clone(), pat_type); } - let record_type = - constrain::lift(var_store, Type::Record(field_types, Box::new(ext_type))); + let record_uniq_type = if pattern_uniq_vars.is_empty() { + // explicitly keep uniqueness of empty record (match) free + let empty_var = var_store.fresh(); + state.vars.push(empty_var); + Bool::Variable(empty_var) + } else { + boolean_algebra::any(pattern_uniq_vars.into_iter().map(Bool::Variable)) + }; + + let record_type = constrain::attr_type( + record_uniq_type, + Type::Record(field_types, Box::new(ext_type)), + ); let record_con = Constraint::Pattern( pattern.region, PatternCategory::Record, @@ -155,8 +174,52 @@ fn canonicalize_pattern( state.constraints.push(record_con); } - AppliedTag(_, _, _) => { - panic!("TODO add_constraints for {:?}", pattern); + AppliedTag(ext_var, symbol, patterns) => { + let mut argument_types = Vec::with_capacity(patterns.len()); + let mut pattern_uniq_vars = Vec::with_capacity(patterns.len()); + + for (pattern_var, loc_pattern) in patterns { + state.vars.push(*pattern_var); + + let pat_uniq_var = var_store.fresh(); + pattern_uniq_vars.push(pat_uniq_var); + + let pattern_type = constrain::attr_type( + Bool::Variable(pat_uniq_var), + Type::Variable(*pattern_var), + ); + argument_types.push(pattern_type.clone()); + + let expected = PExpected::NoExpectation(pattern_type); + constrain_pattern(var_store, state, loc_pattern, expected); + } + + let record_uniq_type = if pattern_uniq_vars.is_empty() { + // explicitly keep uniqueness of empty tag union (match) free + let empty_var = var_store.fresh(); + state.vars.push(empty_var); + Bool::Variable(empty_var) + } else { + boolean_algebra::any(pattern_uniq_vars.into_iter().map(Bool::Variable)) + }; + + let union_type = constrain::attr_type( + record_uniq_type, + Type::TagUnion( + vec![(symbol.clone(), argument_types)], + Box::new(Type::Variable(*ext_var)), + ), + ); + + let tag_con = Constraint::Pattern( + pattern.region, + PatternCategory::Ctor(symbol.clone()), + union_type, + expected, + ); + + state.vars.push(*ext_var); + state.constraints.push(tag_con); } Underscore | Shadowed(_) | UnsupportedPattern(_) => { @@ -165,51 +228,61 @@ fn canonicalize_pattern( } } -pub fn canonicalize_expr( +pub fn constrain_expr( rigids: &Rigids, var_store: &VarStore, var_usage: &mut VarUsage, region: Region, expr: &Expr, expected: Expected, -) -> (Output, Constraint) { +) -> Constraint { pub use crate::can::expr::Expr::*; match expr { - Int(_, _) => { - let constraint = constrain::int_literal(var_store, expected, region); - (Output::default(), constraint) - } - Float(_, _) => { - let constraint = constrain::float_literal(var_store, expected, region); - (Output::default(), constraint) - } + Int(var, _) => And(vec![ + Eq( + Type::Variable(*var), + Expected::ForReason( + Reason::IntLiteral, + constrain::lift(var_store, Type::int()), + region, + ), + region, + ), + Eq(Type::Variable(*var), expected, region), + ]), + Float(var, _) => And(vec![ + Eq( + Type::Variable(*var), + Expected::ForReason( + Reason::FloatLiteral, + constrain::lift(var_store, Type::float()), + region, + ), + region, + ), + Eq(Type::Variable(*var), expected, region), + ]), BlockStr(_) | Str(_) => { - let inferred = constrain::lift(var_store, constrain::str_type()); - let constraint = Eq(inferred, expected, region); - (Output::default(), constraint) - } - EmptyRecord => { - let constraint = Eq(constrain::lift(var_store, EmptyRec), expected, region); - - (Output::default(), constraint) + let inferred = constrain::lift(var_store, Type::string()); + Eq(inferred, expected, region) } + EmptyRecord => Eq(constrain::lift(var_store, EmptyRec), expected, region), Record(variable, fields) => { // NOTE: canonicalization guarantees at least one field // zero fields generates an EmptyRecord let mut field_types = SendMap::default(); let mut field_vars = Vec::with_capacity(fields.len()); - // Constraints need capacity for each field + 1 for the record itself. - let mut constraints = Vec::with_capacity(1 + fields.len()); - let mut output = Output::default(); + // Constraints need capacity for each field + 1 for the record itself + 1 for ext + let mut constraints = Vec::with_capacity(2 + fields.len()); for (label, ref field) in fields.iter() { let field_var = var_store.fresh(); let field_type = Variable(field_var); let field_expected = Expected::NoExpectation(field_type.clone()); let loc_expr = &*field.loc_expr; - let (field_out, field_con) = canonicalize_expr( + let field_con = constrain_expr( rigids, var_store, var_usage, @@ -222,7 +295,6 @@ pub fn canonicalize_expr( field_types.insert(label.clone(), field_type); constraints.push(field_con); - output.references = output.references.union(field_out.references); } let record_type = constrain::lift( @@ -243,23 +315,60 @@ pub fn canonicalize_expr( let constraint = exists(field_vars, And(constraints)); - (output, constraint) + (constraint) } - Tag { .. } => { - panic!("TODO implement tag"); + Tag { + variant_var, + ext_var, + name, + arguments, + } => { + let mut vars = Vec::with_capacity(arguments.len()); + let mut types = Vec::with_capacity(arguments.len()); + let mut arg_cons = Vec::with_capacity(arguments.len()); + + for (var, loc_expr) in arguments { + let arg_con = constrain_expr( + rigids, + var_store, + var_usage, + loc_expr.region, + &loc_expr.value, + Expected::NoExpectation(Type::Variable(*var)), + ); + + arg_cons.push(arg_con); + vars.push(*var); + types.push(Type::Variable(*var)); + } + + let union_type = constrain::lift( + var_store, + Type::TagUnion( + vec![(name.clone(), types)], + Box::new(Type::Variable(*ext_var)), + ), + ); + + let union_con = Eq(union_type, expected.clone(), region); + let ast_con = Eq(Type::Variable(*variant_var), expected, region); + + vars.push(*variant_var); + arg_cons.push(union_con); + arg_cons.push(ast_con); + + exists(vars, And(arg_cons)) } List(variable, loc_elems) => { if loc_elems.is_empty() { let list_var = *variable; - let inferred = constrain::lift(var_store, constrain::empty_list_type(list_var)); - let constraint = Eq(inferred, expected, region); - (Output::default(), constraint) + let inferred = constrain::lift(var_store, builtins::empty_list_type(list_var)); + Eq(inferred, expected, region) } else { // constrain `expected ~ List a` and that all elements `~ a`. let list_var = *variable; // `v` in the type (List v) let list_type = Type::Variable(list_var); let mut constraints = Vec::with_capacity(1 + (loc_elems.len() * 2)); - let mut references = References::new(); for (elem_var, loc_elem) in loc_elems.iter() { let elem_type = Variable(*elem_var); @@ -269,7 +378,7 @@ pub fn canonicalize_expr( Expected::ForReason(Reason::ElemInList, elem_type, region), region, ); - let (elem_out, constraint) = canonicalize_expr( + let constraint = constrain_expr( rigids, var_store, var_usage, @@ -280,130 +389,117 @@ pub fn canonicalize_expr( constraints.push(list_elem_constraint); constraints.push(constraint); - - references = references.union(elem_out.references); } - let inferred = constrain::lift(var_store, constrain::list_type(list_type)); + let inferred = constrain::lift(var_store, builtins::list_type(list_type)); constraints.push(Eq(inferred, expected, region)); - let mut output = Output::default(); - - output.references = references; - - // A list literal is never a tail call! - output.tail_call = None; - - (output, And(constraints)) + And(constraints) } } Var { symbol_for_lookup, .. } => { var_usage.register(symbol_for_lookup); - match var_usage.get_usage(symbol_for_lookup) { + let usage = var_usage.get_usage(symbol_for_lookup); + + match usage { Some(sharing::ReferenceCount::Shared) => { // the variable is used/consumed more than once, so it must be Shared let val_var = var_store.fresh(); let uniq_var = var_store.fresh(); let val_type = Variable(val_var); - let uniq_type = Variable(uniq_var); + let uniq_type = Bool::Variable(uniq_var); let attr_type = constrain::attr_type(uniq_type.clone(), val_type); - ( - Output::default(), - And(vec![ - Lookup(symbol_for_lookup.clone(), expected.clone(), region), - Eq(attr_type, expected, region), - Eq( - uniq_type, - Expected::NoExpectation(constrain::shared_type()), - region, - ), - ]), - ) + And(vec![ + Lookup(symbol_for_lookup.clone(), expected.clone(), region), + Eq(attr_type, expected, region), + Eq( + Type::Boolean(uniq_type), + Expected::NoExpectation(Type::Boolean(constrain::shared_type())), + region, + ), + ]) } Some(sharing::ReferenceCount::Unique) => { // no additional constraints, keep uniqueness unbound - ( - Output::default(), - Lookup(symbol_for_lookup.clone(), expected.clone(), region), - ) + Lookup(symbol_for_lookup.clone(), expected.clone(), region) } None => panic!("symbol not analyzed"), } } - Closure(_fn_var, _symbol, _recursion, args, boxed_body) => { - let (body, ret_var) = &**boxed_body; - - // first, generate constraints for the arguments - let mut arg_types = Vec::new(); - let mut arg_vars = Vec::new(); - + Closure(fn_var, _symbol, _recursion, args, boxed) => { + let (loc_body_expr, ret_var) = &**boxed; let mut state = PatternState { headers: SendMap::default(), - vars: Vec::with_capacity(1), + vars: Vec::with_capacity(args.len()), constraints: Vec::with_capacity(1), }; - let mut vars = Vec::with_capacity(state.vars.capacity() + 1); - let ret_type = Variable(*ret_var); + let mut pattern_types = Vec::with_capacity(state.vars.capacity()); + let ret_var = *ret_var; + let ret_type = Type::Variable(ret_var); - vars.push(*ret_var); + vars.push(ret_var); + vars.push(*fn_var); - for (arg_var, pattern) in args { - let arg_typ = Variable(*arg_var); - canonicalize_pattern( - var_store, - &mut state, - &pattern, - PExpected::NoExpectation(arg_typ.clone()), - ); - arg_types.push(arg_typ); - arg_vars.push(arg_var); + for (pattern_var, loc_pattern) in args { + let pattern_type = Type::Variable(*pattern_var); + let pattern_expected = PExpected::NoExpectation(pattern_type.clone()); - vars.push(*arg_var); + pattern_types.push(pattern_type); + + constrain_pattern(var_store, &mut state, loc_pattern, pattern_expected); + + vars.push(*pattern_var); } - let fn_typ = constrain::lift( + let fn_type = constrain::lift( var_store, - Type::Function(arg_types, Box::new(ret_type.clone())), + Type::Function(pattern_types, Box::new(ret_type.clone())), ); - - let (output, ret_constraint) = canonicalize_expr( + let body_type = Expected::NoExpectation(ret_type); + let ret_constraint = constrain_expr( rigids, var_store, var_usage, - region, - &body.value, - Expected::NoExpectation(ret_type), + loc_body_expr.region, + &loc_body_expr.value, + body_type, ); + let defs_constraint = And(state.constraints); + // remove identifiers bound in the arguments from VarUsage + // makes e.g. `(\x -> x) (\x -> x)` count as unique in both cases for (_, pattern) in args { for identifier in pattern::symbols_from_pattern(&pattern.value) { var_usage.unregister(&identifier); } } - let defs_constraint = And(state.constraints); - let constraint = exists( + exists( vars, And(vec![ Let(Box::new(LetConstraint { rigid_vars: Vec::new(), flex_vars: state.vars, def_types: state.headers, - defs_constraint: defs_constraint, + defs_constraint, ret_constraint, })), - // "the closure's type is equal to expected type" - Eq(fn_typ, expected, region), + // "the closure's type is equal to expected type" + Eq(fn_type.clone(), expected, region), + // "fn_var is equal to the closure's type" - fn_var is used in code gen + Eq( + Type::Variable(*fn_var), + Expected::NoExpectation(fn_type), + region, + ), ]), - ); - - (output, constraint) + ) } Call(boxed, loc_args, _) => { @@ -415,8 +511,11 @@ pub fn canonicalize_expr( let mut vars = Vec::with_capacity(2 + loc_args.len()); + vars.push(*fn_var); + vars.push(*ret_var); + // Canonicalize the function expression and its arguments - let (_, fn_con) = canonicalize_expr( + let fn_con = constrain_expr( rigids, var_store, var_usage, @@ -442,7 +541,7 @@ pub fn canonicalize_expr( }; let expected_arg = Expected::ForReason(reason, arg_type.clone(), region); - let (_, arg_con) = canonicalize_expr( + let arg_con = constrain_expr( rigids, var_store, var_usage, @@ -462,52 +561,55 @@ pub fn canonicalize_expr( region, ); - ( - Output::default(), - exists( - vars, - And(vec![ - fn_con, - Eq(fn_type, expected_fn_type, fn_region), - And(arg_cons), - Eq(ret_type, expected, region), - ]), - ), + exists( + vars, + And(vec![ + fn_con, + Eq(fn_type, expected_fn_type, fn_region), + And(arg_cons), + Eq(ret_type, expected, region), + ]), ) } - LetRec(defs, loc_ret, _) => { + LetRec(defs, loc_ret, var) => { // NOTE doesn't currently unregister bound symbols // may be a problem when symbols are not globally unique - let (_, body_con) = canonicalize_expr( + let body_con = constrain_expr( rigids, var_store, var_usage, loc_ret.region, &loc_ret.value, - expected, - ); - ( - Output::default(), - constrain_recursive_defs(rigids, var_store, var_usage, defs, body_con), - ) - } - LetNonRec(def, loc_ret, _) => { - // NOTE doesn't currently unregister bound symbols - // may be a problem when symbols are not globally unique - let (_, body_con) = canonicalize_expr( - rigids, - var_store, - var_usage, - loc_ret.region, - &loc_ret.value, - expected, + expected.clone(), ); - ( - Output::default(), - constrain_def(rigids, var_store, var_usage, def, body_con), - ) + And(vec![ + constrain_recursive_defs(rigids, var_store, var_usage, defs, body_con), + // Record the type of tne entire def-expression in the variable. + // Code gen will need that later! + Eq(Type::Variable(*var), expected, loc_ret.region), + ]) } + LetNonRec(def, loc_ret, var) => { + // NOTE doesn't currently unregister bound symbols + // may be a problem when symbols are not globally unique + let body_con = constrain_expr( + rigids, + var_store, + var_usage, + loc_ret.region, + &loc_ret.value, + expected.clone(), + ); + + And(vec![ + constrain_def(rigids, var_store, var_usage, def, body_con), + // Record the type of tne entire def-expression in the variable. + // Code gen will need that later! + Eq(Type::Variable(*var), expected, loc_ret.region), + ]) + } + If { .. } => panic!("TODO constrain uniq if"), When { cond_var, loc_cond, @@ -516,7 +618,7 @@ pub fn canonicalize_expr( } => { let cond_var = *cond_var; let cond_type = Variable(cond_var); - let (mut output, expr_con) = canonicalize_expr( + let expr_con = constrain_expr( rigids, var_store, var_usage, @@ -533,7 +635,7 @@ pub fn canonicalize_expr( Expected::FromAnnotation(name, arity, _, typ) => { for (index, (loc_pattern, loc_expr)) in branches.iter().enumerate() { let mut branch_var_usage = old_var_usage.clone(); - let branch_con = canonicalize_when_branch( + let branch_con = constrain_when_branch( var_store, &mut branch_var_usage, rigids, @@ -551,7 +653,6 @@ pub fn canonicalize_expr( TypedWhenBranch(index), typ.clone(), ), - &mut output, ); // required for a case like @@ -583,7 +684,7 @@ pub fn canonicalize_expr( for (index, (loc_pattern, loc_expr)) in branches.iter().enumerate() { let mut branch_var_usage = old_var_usage.clone(); - let branch_con = canonicalize_when_branch( + let branch_con = constrain_when_branch( var_store, &mut branch_var_usage, rigids, @@ -600,7 +701,6 @@ pub fn canonicalize_expr( branch_type.clone(), region, ), - &mut output, ); // required for a case like @@ -635,7 +735,7 @@ pub fn canonicalize_expr( } } - (output, And(constraints)) + And(constraints) } Update { @@ -647,7 +747,7 @@ pub fn canonicalize_expr( } => { let mut fields: SendMap = SendMap::default(); let mut vars = Vec::with_capacity(updates.len() + 2); - let mut cons = Vec::with_capacity(updates.len() + 1); + let mut cons = Vec::with_capacity(updates.len() + 3); for (field_name, Field { var, loc_expr, .. }) in updates.clone() { let (var, tipe, con) = constrain_field_update( rigids, @@ -694,7 +794,7 @@ pub fn canonicalize_expr( cons.push(fields_con); cons.push(record_con); - (Output::default(), exists(vars, And(cons))) + exists(vars, And(cons)) } Access { @@ -704,17 +804,25 @@ pub fn canonicalize_expr( field, } => { let ext_type = Type::Variable(*ext_var); - let field_type = Type::Variable(*field_var); + + let field_uniq_var = var_store.fresh(); + let field_uniq_type = Bool::Variable(field_uniq_var); + let field_type = + constrain::attr_type(field_uniq_type.clone(), Type::Variable(*field_var)); let mut rec_field_types = SendMap::default(); rec_field_types.insert(field.clone(), field_type.clone()); - let record_type = - constrain::lift(var_store, Type::Record(rec_field_types, Box::new(ext_type))); + let record_uniq_var = var_store.fresh(); + let record_uniq_type = Bool::Variable(record_uniq_var); + let record_type = constrain::attr_type( + record_uniq_type.clone(), + Type::Record(rec_field_types, Box::new(ext_type)), + ); let record_expected = Expected::NoExpectation(record_type); - let (output, mut constraint) = canonicalize_expr( + let mut constraint = constrain_expr( rigids, var_store, var_usage, @@ -723,12 +831,18 @@ pub fn canonicalize_expr( record_expected, ); - constraint = exists( - vec![*field_var, *ext_var], - And(vec![constraint, Eq(field_type, expected, region)]), + let uniq_con = Eq( + Type::Boolean(field_uniq_type), + Expected::NoExpectation(Type::Boolean(record_uniq_type)), + region, ); - (output, constraint) + constraint = exists( + vec![*field_var, *ext_var, field_uniq_var, record_uniq_var], + And(vec![constraint, Eq(field_type, expected, region), uniq_con]), + ); + + constraint } Accessor { @@ -736,36 +850,53 @@ pub fn canonicalize_expr( field_var, ext_var, } => { - let ext_type = Variable(*ext_var); - let field_type = Variable(*field_var); let mut field_types = SendMap::default(); + let field_uniq_var = var_store.fresh(); + let field_uniq_type = Bool::Variable(field_uniq_var); + let field_type = + constrain::attr_type(field_uniq_type.clone(), Type::Variable(*field_var)); + field_types.insert(field.clone(), field_type.clone()); - let record_type = - constrain::lift(var_store, Type::Record(field_types, Box::new(ext_type))); + let record_uniq_var = var_store.fresh(); + let record_uniq_type = Bool::Variable(record_uniq_var); + let record_type = constrain::attr_type( + record_uniq_type.clone(), + Type::Record(field_types, Box::new(Type::Variable(*ext_var))), + ); - ( - Output::default(), - exists( - vec![*field_var, *ext_var], - Eq( - Type::Function(vec![record_type], Box::new(field_type)), - expected, - region, - ), - ), + let fn_uniq_var = var_store.fresh(); + let fn_type = constrain::attr_type( + Bool::Variable(fn_uniq_var), + Type::Function(vec![record_type], Box::new(field_type)), + ); + + let uniq_con = Eq( + Type::Boolean(field_uniq_type), + Expected::NoExpectation(Type::Boolean(record_uniq_type)), + region, + ); + + exists( + vec![ + *field_var, + *ext_var, + fn_uniq_var, + field_uniq_var, + record_uniq_var, + ], + And(vec![Eq(fn_type, expected, region), uniq_con]), ) } - RuntimeError(_) => (Output::default(), True), - // _ => panic!("{:?}", expr), + RuntimeError(_) => True, } } // TODO trim down these arguments #[allow(clippy::too_many_arguments)] #[inline(always)] -fn canonicalize_when_branch( +fn constrain_when_branch( var_store: &VarStore, var_usage: &mut VarUsage, rigids: &Rigids, @@ -774,9 +905,8 @@ fn canonicalize_when_branch( loc_expr: &Located, pattern_expected: PExpected, expr_expected: Expected, - _output: &mut Output, ) -> Constraint { - let (_, ret_constraint) = canonicalize_expr( + let ret_constraint = constrain_expr( rigids, var_store, var_usage, @@ -792,7 +922,7 @@ fn canonicalize_when_branch( }; // mutates the state, so return value is not used - canonicalize_pattern(var_store, &mut state, &loc_pattern, pattern_expected); + constrain_pattern(var_store, &mut state, &loc_pattern, pattern_expected); Constraint::Let(Box::new(LetConstraint { rigid_vars: Vec::new(), @@ -818,11 +948,143 @@ fn constrain_def_pattern( constraints: Vec::with_capacity(1), }; - canonicalize_pattern(var_store, &mut state, loc_pattern, pattern_expected); + constrain_pattern(var_store, &mut state, loc_pattern, pattern_expected); state } +/// Turn e.g. `Int` into `Attr.Attr * Int` +fn annotation_to_attr_type(var_store: &VarStore, ann: &Type) -> (Vec, Type) { + use crate::types::Type::*; + + match ann { + Variable(_) | Boolean(_) | Erroneous(_) => (vec![], ann.clone()), + EmptyRec | EmptyTagUnion => { + let uniq_var = var_store.fresh(); + ( + vec![uniq_var], + constrain::attr_type(Bool::Variable(uniq_var), ann.clone()), + ) + } + + Function(arguments, result) => { + let uniq_var = var_store.fresh(); + let (mut arg_vars, args_lifted) = annotation_to_attr_type_many(var_store, arguments); + let (result_vars, result_lifted) = annotation_to_attr_type(var_store, result); + + arg_vars.extend(result_vars); + arg_vars.push(uniq_var); + + ( + arg_vars, + constrain::attr_type( + Bool::Variable(uniq_var), + Type::Function(args_lifted, Box::new(result_lifted)), + ), + ) + } + + Apply { + module_name, + name, + args, + } => { + let uniq_var = var_store.fresh(); + let (mut arg_vars, args_lifted) = annotation_to_attr_type_many(var_store, args); + + arg_vars.push(uniq_var); + + ( + arg_vars, + constrain::attr_type( + Bool::Variable(uniq_var), + Type::Apply { + module_name: module_name.clone(), + name: name.clone(), + args: args_lifted, + }, + ), + ) + } + + Record(fields, ext_type) => { + let uniq_var = var_store.fresh(); + let mut vars = Vec::with_capacity(fields.len()); + let mut lifted_fields = SendMap::default(); + + for (label, tipe) in fields.clone() { + let (new_vars, lifted_field) = annotation_to_attr_type(var_store, &tipe); + vars.extend(new_vars); + lifted_fields.insert(label, lifted_field); + } + + vars.push(uniq_var); + + ( + vars, + constrain::attr_type( + Bool::Variable(uniq_var), + Type::Record(lifted_fields, ext_type.clone()), + ), + ) + } + + TagUnion(tags, ext_type) => { + let uniq_var = var_store.fresh(); + let mut vars = Vec::with_capacity(tags.len()); + let mut lifted_tags = Vec::with_capacity(tags.len()); + + for (tag, fields) in tags { + let (new_vars, lifted_fields) = annotation_to_attr_type_many(var_store, fields); + vars.extend(new_vars); + lifted_tags.push((tag.clone(), lifted_fields)); + } + + vars.push(uniq_var); + + ( + vars, + constrain::attr_type( + Bool::Variable(uniq_var), + Type::TagUnion(lifted_tags, ext_type.clone()), + ), + ) + } + + Alias(module_name, uppercase, fields, actual) => { + let uniq_var = var_store.fresh(); + + let (mut actual_vars, lifted_actual) = annotation_to_attr_type(var_store, actual); + + actual_vars.push(uniq_var); + + ( + actual_vars, + constrain::attr_type( + Bool::Variable(uniq_var), + Type::Alias( + module_name.clone(), + uppercase.clone(), + fields.clone(), + Box::new(lifted_actual), + ), + ), + ) + } + } +} + +fn annotation_to_attr_type_many(var_store: &VarStore, anns: &[Type]) -> (Vec, Vec) { + anns.iter() + .fold((Vec::new(), Vec::new()), |(mut vars, mut types), value| { + let (new_vars, tipe) = annotation_to_attr_type(var_store, value); + vars.extend(new_vars); + types.push(tipe); + + (vars, types) + }) +} + pub fn constrain_def( rigids: &Rigids, var_store: &VarStore, @@ -844,6 +1106,9 @@ pub fn constrain_def( let expr_con = match &def.annotation { Some((annotation, free_vars)) => { let mut ftv: Rigids = rigids.clone(); + let (uniq_vars, annotation) = annotation_to_attr_type(var_store, annotation); + + pattern_state.vars.extend(uniq_vars); for (var, name) in free_vars { // if the rigid is known already, nothing needs to happen @@ -860,7 +1125,7 @@ pub fn constrain_def( def.loc_pattern.clone(), annotation.arity(), AnnotationSource::TypedBody, - annotation.clone(), + annotation, ); pattern_state.constraints.push(Eq( @@ -869,7 +1134,7 @@ pub fn constrain_def( Region::zero(), )); - canonicalize_expr( + constrain_expr( &ftv, var_store, var_usage, @@ -877,19 +1142,15 @@ pub fn constrain_def( &def.loc_expr.value, annotation_expected, ) - .1 - } - None => { - canonicalize_expr( - rigids, - var_store, - var_usage, - def.loc_expr.region, - &def.loc_expr.value, - Expected::NoExpectation(expr_type), - ) - .1 } + None => constrain_expr( + rigids, + var_store, + var_usage, + def.loc_expr.region, + &def.loc_expr.value, + Expected::NoExpectation(expr_type), + ), }; Let(Box::new(LetConstraint { @@ -947,7 +1208,7 @@ pub fn rec_defs_help( constraints: Vec::with_capacity(1), }; - canonicalize_pattern( + constrain_pattern( var_store, &mut pattern_state, &def.loc_pattern, @@ -959,7 +1220,7 @@ pub fn rec_defs_help( let mut new_rigids = Vec::new(); match &def.annotation { None => { - let (_, expr_con) = canonicalize_expr( + let expr_con = constrain_expr( rigids, var_store, var_usage, @@ -1002,7 +1263,7 @@ pub fn rec_defs_help( AnnotationSource::TypedBody, annotation.clone(), ); - let (_, expr_con) = canonicalize_expr( + let expr_con = constrain_expr( &ftv, var_store, var_usage, @@ -1074,7 +1335,7 @@ fn constrain_field_update( let field_type = Type::Variable(var); let reason = Reason::RecordUpdateValue(field); let expected = Expected::ForReason(reason, field_type.clone(), region); - let (_, con) = canonicalize_expr( + let con = constrain_expr( rigids, var_store, var_usage, diff --git a/tests/helpers/mod.rs b/tests/helpers/mod.rs index 7360d22797..bf91aca8f3 100644 --- a/tests/helpers/mod.rs +++ b/tests/helpers/mod.rs @@ -89,7 +89,6 @@ pub fn can_expr(expr_str: &str) -> (Expr, Output, Vec, VarStore, Variab pub fn uniq_expr( expr_str: &str, ) -> ( - Output, Output, Vec, Subs, @@ -108,7 +107,6 @@ pub fn uniq_expr_with( expr_str: &str, declared_idents: &ImMap, ) -> ( - Output, Output, Vec, Subs, @@ -124,12 +122,13 @@ pub fn uniq_expr_with( let next_var = var_store1.into(); let subs1 = Subs::new(next_var); + // double check let var_store2 = VarStore::new(next_var); let variable2 = var_store2.fresh(); let expected2 = Expected::NoExpectation(Type::Variable(variable2)); - let (output2, constraint2) = roc::uniqueness::canonicalize_declaration( + let constraint2 = roc::uniqueness::constrain_declaration( &var_store2, Region::zero(), loc_expr, @@ -140,7 +139,6 @@ pub fn uniq_expr_with( let subs2 = Subs::new(var_store2.into()); ( - output2, output, problems, subs1, diff --git a/tests/test_boolean_algebra.rs b/tests/test_boolean_algebra.rs index 50adbcce26..e1b4495a23 100644 --- a/tests/test_boolean_algebra.rs +++ b/tests/test_boolean_algebra.rs @@ -1,3 +1,6 @@ +#[macro_use] +extern crate maplit; + #[macro_use] extern crate pretty_assertions; @@ -8,13 +11,24 @@ mod helpers; #[cfg(test)] mod test_boolean_algebra { + use roc::subs; use roc::subs::VarStore; use roc::uniqueness::boolean_algebra; - use roc::uniqueness::boolean_algebra::BooleanAlgebra::{self, *}; + use roc::uniqueness::boolean_algebra::Bool::{self, *}; // HELPERS - fn simplify_eq(mut a: BooleanAlgebra, mut b: BooleanAlgebra) { - assert_eq!(a.simplify(), b.simplify()); + fn to_var(v: usize) -> subs::Variable { + subs::Variable::unsafe_debug_variable(v) + } + + fn simplify_eq(a: Bool, b: Bool) { + assert_eq!(boolean_algebra::simplify(a), boolean_algebra::simplify(b)); + } + + fn unify_eq(a: Bool, b: Bool, expected: std::collections::HashMap) { + let result = boolean_algebra::try_unify(a, b); + + assert_eq!(result, Some(expected.into())); } #[test] @@ -22,10 +36,7 @@ mod test_boolean_algebra { let var_store = VarStore::default(); let var = var_store.fresh(); - simplify_eq( - Disjunction(Box::new(Ground(true)), Box::new(Variable(var))), - Ground(true), - ); + simplify_eq(Bool::or(One, Variable(var)), One); } #[test] @@ -33,10 +44,7 @@ mod test_boolean_algebra { let var_store = VarStore::default(); let var = var_store.fresh(); - simplify_eq( - Disjunction(Box::new(Ground(false)), Box::new(Variable(var))), - Variable(var), - ); + simplify_eq(Bool::or(Zero, Variable(var)), Variable(var)); } #[test] @@ -44,42 +52,127 @@ mod test_boolean_algebra { let var_store = VarStore::default(); let var = var_store.fresh(); - simplify_eq( - Conjunction(Box::new(Ground(false)), Box::new(Variable(var))), - Ground(false), + simplify_eq(Bool::and(Zero, Variable(var)), Zero); + } + + #[test] + fn unify_example_const() { + unify_eq( + Variable(to_var(1)), + Variable(to_var(4)), + hashmap![to_var(1) => Variable(to_var(4))].into(), + ); + unify_eq( + Variable(to_var(5)), + Variable(to_var(2)), + hashmap![to_var(2) => Variable(to_var(5))].into(), + ); + unify_eq( + Variable(to_var(6)), + Variable(to_var(0)), + hashmap![to_var(0) => Variable(to_var(6))].into(), ); } #[test] - fn unify_single_var() { - let var_store = VarStore::default(); - let var = var_store.fresh(); - - let result = boolean_algebra::unify(&Variable(var), &Ground(true)); - - if let Some(sub) = result { - assert_eq!(Some(&Ground(false)), sub.get(var)); - } else { - panic!("result is None"); - } + fn unify_example_apply() { + unify_eq( + Variable(to_var(1)), + Variable(to_var(6)), + hashmap![to_var(1) => Variable(to_var(6))].into(), + ); + unify_eq( + Variable(to_var(5)), + Variable(to_var(6)), + hashmap![to_var(5) => Variable(to_var(6))].into(), + ); + unify_eq( + Variable(to_var(3)), + Variable(to_var(7)), + hashmap![to_var(3) => Variable(to_var(7))].into(), + ); + unify_eq( + Variable(to_var(8)), + Variable(to_var(4)), + hashmap![to_var(4) => Variable(to_var(8))].into(), + ); + unify_eq( + Variable(to_var(9)), + Variable(to_var(2)), + hashmap![to_var(2) => Variable(to_var(9))].into(), + ); + unify_eq( + Variable(to_var(10)), + Variable(to_var(0)), + hashmap![to_var(0) => Variable(to_var(10))].into(), + ); } #[test] - fn unify_or() { - let var_store = VarStore::default(); - let a = var_store.fresh(); - let b = var_store.fresh(); - - let result = boolean_algebra::unify( - &Disjunction(Box::new(Variable(a)), Box::new(Variable(b))), - &Ground(true), + fn unify_example_fst() { + unify_eq( + Variable(to_var(1)), + Variable(to_var(5)), + hashmap![to_var(1) => Variable(to_var(5))].into(), ); + unify_eq( + Variable(to_var(3)), + Variable(to_var(2)), + hashmap![to_var(2) => Variable(to_var(3))].into(), + ); + unify_eq( + Variable(to_var(5)), + Bool::or(Bool::or(Variable(to_var(3)), Zero), Zero), + hashmap![to_var(3) => Variable(to_var(5))].into(), + ); + unify_eq( + Variable(to_var(6)), + Variable(to_var(0)), + hashmap![to_var(0) => Variable(to_var(6))].into(), + ); + } - if let Some(sub) = result { - assert_eq!(Some(&Variable(b)), sub.get(a)); - assert_eq!(Some(&Ground(false)), sub.get(b)); - } else { - panic!("result is None"); - } + #[test] + fn unify_example_idid() { + unify_eq( + Variable(to_var(3)), + Variable(to_var(4)), + hashmap![to_var(3) => Variable(to_var(4))].into(), + ); + unify_eq( + Variable(to_var(5)), + Variable(to_var(2)), + hashmap![to_var(2) => Variable(to_var(5))].into(), + ); + unify_eq( + Variable(to_var(5)), + Variable(to_var(1)), + hashmap![to_var(1) => Variable(to_var(5))].into(), + ); + unify_eq( + Zero, + Variable(to_var(5)), + hashmap![to_var(5) => Zero].into(), + ); + unify_eq( + Variable(to_var(7)), + Variable(to_var(4)), + hashmap![to_var(4) => Variable(to_var(7))].into(), + ); + unify_eq( + Variable(to_var(8)), + Variable(to_var(9)), + hashmap![to_var(8) => Variable(to_var(9))].into(), + ); + unify_eq( + Variable(to_var(7)), + Zero, + hashmap![to_var(7) => Zero].into(), + ); + unify_eq( + Zero, + Variable(to_var(0)), + hashmap![to_var(0) => Zero].into(), + ); } } diff --git a/tests/test_emit.rs b/tests/test_emit.rs new file mode 100644 index 0000000000..5e95375ebd --- /dev/null +++ b/tests/test_emit.rs @@ -0,0 +1,603 @@ +#[macro_use] +extern crate pretty_assertions; +#[macro_use] +extern crate indoc; + +extern crate bumpalo; +extern crate inkwell; +extern crate roc; + +mod helpers; + +#[cfg(test)] +mod test_crane { + use crate::helpers::can_expr; + use bumpalo::Bump; + use cranelift::prelude::{AbiParam, ExternalName, FunctionBuilder, FunctionBuilderContext}; + use cranelift_codegen::ir::InstBuilder; + use cranelift_codegen::isa; + use cranelift_codegen::settings; + use cranelift_codegen::verifier::verify_function; + use cranelift_module::{default_libcall_names, Linkage, Module}; + use cranelift_simplejit::{SimpleJITBackend, SimpleJITBuilder}; + use inkwell::context::Context; + use inkwell::execution_engine::JitFunction; + use inkwell::passes::PassManager; + use inkwell::types::BasicType; + use inkwell::OptimizationLevel; + use roc::collections::{ImMap, MutMap}; + use roc::crane::build::{declare_proc, define_proc_body, ScopeEntry}; + use roc::crane::convert::type_from_content; + use roc::infer::infer_expr; + use roc::llvm::build::build_proc; + use roc::llvm::convert::content_to_basic_type; + use roc::mono::expr::Expr; + use roc::subs::Subs; + use std::mem; + use target_lexicon::HOST; + + macro_rules! assert_crane_evals_to { + ($src:expr, $expected:expr, $ty:ty) => { + let arena = Bump::new(); + let mut module: Module = + Module::new(SimpleJITBuilder::new(default_libcall_names())); + let mut ctx = module.make_context(); + let mut func_ctx = FunctionBuilderContext::new(); + + let (expr, _output, _problems, var_store, variable, constraint) = can_expr($src); + let mut subs = Subs::new(var_store.into()); + let mut unify_problems = Vec::new(); + let content = infer_expr(&mut subs, &mut unify_problems, &constraint, variable); + let shared_builder = settings::builder(); + let shared_flags = settings::Flags::new(shared_builder); + let cfg = match isa::lookup(HOST) { + Err(err) => { + panic!( + "Unsupported target ISA for test runner {:?} - error: {:?}", + HOST, err + ); + } + Ok(isa_builder) => { + let isa = isa_builder.finish(shared_flags.clone()); + + isa.frontend_config() + } + }; + + let main_fn_name = "$Test.main"; + + // Compute main_fn_ret_type before moving subs to Env + let main_ret_type = type_from_content(&content, &mut subs, cfg); + + // Compile and add all the Procs before adding main + let mut procs = MutMap::default(); + let env = roc::crane::build::Env { + arena: &arena, + subs, + cfg, + }; + + // Populate Procs and Subs, and get the low-level Expr from the canonical Expr + let mono_expr = Expr::new(&arena, &env.subs, expr, &mut procs); + let mut scope = ImMap::default(); + let mut declared = Vec::with_capacity(procs.len()); + + // Declare all the Procs, then insert them into scope so their bodies + // can look up their Funcs in scope later when calling each other by value. + for (name, opt_proc) in procs.iter() { + if let Some(proc) = opt_proc { + let (func_id, sig) = declare_proc(&env, &mut module, name.clone(), proc); + + declared.push((proc.clone(), sig.clone(), func_id)); + + scope.insert(name.clone(), ScopeEntry::Func { func_id, sig }); + } + } + + // Now that scope includes all the Procs, we can build their bodies. + for (proc, sig, fn_id) in declared { + define_proc_body( + &env, + &mut ctx, + &mut module, + fn_id, + &scope, + sig, + proc, + &procs, + ); + + // Verify the function we just defined + if let Err(errors) = verify_function(&ctx.func, &shared_flags) { + // NOTE: We don't include proc here because it's already + // been moved. If you need to know which proc failed, go back + // and add some logging. + panic!("Errors defining proc: {}", errors); + } + } + + // Add main itself + let mut sig = module.make_signature(); + sig.returns.push(AbiParam::new(main_ret_type)); + + let main_fn = module + .declare_function(main_fn_name, Linkage::Local, &sig) + .unwrap(); + + ctx.func.signature = sig; + ctx.func.name = ExternalName::user(0, main_fn.as_u32()); + + { + let mut builder: FunctionBuilder = + FunctionBuilder::new(&mut ctx.func, &mut func_ctx); + let block = builder.create_ebb(); + + builder.switch_to_block(block); + // TODO try deleting this line and seeing if everything still works. + builder.append_ebb_params_for_function_params(block); + + let main_body = + roc::crane::build::build_expr(&env, &scope, &mut module, &mut builder, &mono_expr, &procs); + + builder.ins().return_(&[main_body]); + // TODO re-enable this once Switch stops making unsealed + // EBBs, e.g. https://docs.rs/cranelift-frontend/0.52.0/src/cranelift_frontend/switch.rs.html#143 + // builder.seal_block(block); + builder.seal_all_blocks(); + builder.finalize(); + } + + module.define_function(main_fn, &mut ctx).unwrap(); + module.clear_context(&mut ctx); + + // Perform linking + module.finalize_definitions(); + + // Verify the main function + if let Err(errors) = verify_function(&ctx.func, &shared_flags) { + panic!("Errors defining {} - {}", main_fn_name, errors); + } + + let main_ptr = module.get_finalized_function(main_fn); + let run_main = unsafe { mem::transmute::<_, fn() -> $ty>(main_ptr) }; + + assert_eq!(run_main(), $expected); + }; + } + + macro_rules! assert_llvm_evals_to { + ($src:expr, $expected:expr, $ty:ty) => { + let arena = Bump::new(); + let (expr, _output, _problems, var_store, variable, constraint) = can_expr($src); + let mut subs = Subs::new(var_store.into()); + let mut unify_problems = Vec::new(); + let content = infer_expr(&mut subs, &mut unify_problems, &constraint, variable); + + let context = Context::create(); + let module = context.create_module("app"); + let builder = context.create_builder(); + let fpm = PassManager::create(&module); + + // Enable optimizations when running cargo test --release + if !cfg!(debug_assetions) { + fpm.add_instruction_combining_pass(); + fpm.add_reassociate_pass(); + fpm.add_basic_alias_analysis_pass(); + fpm.add_promote_memory_to_register_pass(); + fpm.add_cfg_simplification_pass(); + fpm.add_gvn_pass(); + // TODO figure out why enabling any of these (even alone) causes LLVM to segfault + // fpm.add_strip_dead_prototypes_pass(); + // fpm.add_dead_arg_elimination_pass(); + // fpm.add_function_inlining_pass(); + } + + fpm.initialize(); + + // Compute main_fn_type before moving subs to Env + let main_fn_type = content_to_basic_type(&content, &mut subs, &context) + .expect("Unable to infer type for test expr") + .fn_type(&[], false); + let main_fn_name = "$Test.main"; + + // Compile and add all the Procs before adding main + let mut procs = MutMap::default(); + let env = roc::llvm::build::Env { + arena: &arena, + subs, + builder: &builder, + context: &context, + module: arena.alloc(module), + }; + + // Populate Procs and get the low-level Expr from the canonical Expr + let main_body = Expr::new(&arena, &env.subs, expr, &mut procs); + + // Add all the Procs to the module + for (name, opt_proc) in procs.clone() { + if let Some(proc) = opt_proc { + // 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); + let fn_val = build_proc(&env, name, proc, &procs); + + 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); + + // Add main's body + let basic_block = context.append_basic_block(main_fn, "entry"); + + builder.position_at_end(&basic_block); + + let ret = roc::llvm::build::build_expr( + &env, + &ImMap::default(), + main_fn, + &main_body, + &mut MutMap::default(), + ); + + builder.build_return(Some(&ret)); + + if main_fn.verify(true) { + fpm.run_on(&main_fn); + } else { + panic!("Function {} failed LLVM verification.", main_fn_name); + } + + // Uncomment this to see the module's LLVM instruction output: + // env.module.print_to_stderr(); + + let execution_engine = env + .module + .create_jit_execution_engine(OptimizationLevel::None) + .expect("Error creating JIT execution engine for test"); + + 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!(main.call(), $expected); + } + }; + } + + macro_rules! assert_evals_to { + ($src:expr, $expected:expr, $ty:ty) => { + // Run Cranelift tests, then LLVM tests, in separate scopes. + // These each rebuild everything from scratch, starting with + // parsing the source, so that there's no chance their passing + // or failing depends on leftover state from the previous one. + { + assert_crane_evals_to!($src, $expected, $ty); + } + { + assert_llvm_evals_to!($src, $expected, $ty); + } + }; + } + + #[test] + fn basic_int() { + assert_evals_to!("123", 123, i64); + } + + #[test] + fn basic_float() { + assert_evals_to!("1234.0", 1234.0, f64); + } + + #[test] + fn branch_first_float() { + assert_evals_to!( + indoc!( + r#" + when 1.23 is + 1.23 -> 12 + _ -> 34 + "# + ), + 12, + i64 + ); + } + + #[test] + fn branch_second_float() { + assert_evals_to!( + indoc!( + r#" + when 2.34 is + 1.23 -> 63 + _ -> 48 + "# + ), + 48, + i64 + ); + } + + #[test] + fn branch_first_int() { + assert_evals_to!( + indoc!( + r#" + when 1 is + 1 -> 12 + _ -> 34 + "# + ), + 12, + i64 + ); + } + + #[test] + fn branch_second_int() { + assert_evals_to!( + indoc!( + r#" + when 2 is + 1 -> 63 + _ -> 48 + "# + ), + 48, + i64 + ); + } + + #[test] + fn gen_when_one_branch() { + assert_evals_to!( + indoc!( + r#" + when 3.14 is + _ -> 23 + "# + ), + 23, + i64 + ); + } + + #[test] + fn gen_large_when_int() { + assert_evals_to!( + indoc!( + r#" + foo = \num -> + when num is + 0 -> 200 + -3 -> 111 # TODO adding more negative numbers reproduces parsing bugs here + 3 -> 789 + 1 -> 123 + 2 -> 456 + _ -> 1000 + + foo -3 + "# + ), + 111, + i64 + ); + } + + // #[test] + // fn gen_large_when_float() { + // assert_evals_to!( + // indoc!( + // r#" + // foo = \num -> + // when num is + // 0.5 -> 200.1 + // -3.6 -> 111.2 # TODO adding more negative numbers reproduces parsing bugs here + // 3.6 -> 789.5 + // 1.7 -> 123.3 + // 2.8 -> 456.4 + // _ -> 1000.6 + + // foo -3.6 + // "# + // ), + // 111.2, + // f64 + // ); + // } + + #[test] + fn gen_basic_def() { + assert_evals_to!( + indoc!( + r#" + answer = 42 + + answer + "# + ), + 42, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + pi = 3.14 + + pi + "# + ), + 3.14, + f64 + ); + } + + #[test] + fn gen_multiple_defs() { + assert_evals_to!( + indoc!( + r#" + answer = 42 + + pi = 3.14 + + answer + "# + ), + 42, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + answer = 42 + + pi = 3.14 + + pi + "# + ), + 3.14, + f64 + ); + } + + #[test] + fn gen_chained_defs() { + assert_evals_to!( + indoc!( + r#" + x = i1 + i3 = i2 + i1 = 1337 + i2 = i1 + y = 12.4 + + i3 + "# + ), + 1337, + i64 + ); + } + + #[test] + fn gen_nested_defs() { + assert_evals_to!( + indoc!( + r#" + x = 5 + + answer = + i3 = i2 + + nested = + a = 1.0 + b = 5 + + i1 + + i1 = 1337 + i2 = i1 + + + nested + + # None of this should affect anything, even though names + # overlap with the previous nested defs + unused = + nested = 17 + + i1 = 84.2 + + nested + + y = 12.4 + + answer + "# + ), + 1337, + 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 gen_when_fn() { + assert_evals_to!( + indoc!( + r#" + limitedNegate = \num -> + when num is + 1 -> -1 + -1 -> 1 + _ -> num + + limitedNegate 1 + "# + ), + -1, + i64 + ); + } + + #[test] + fn apply_unnamed_fn() { + assert_evals_to!( + indoc!( + r#" + (\a -> a) 5 + "# + ), + 5, + i64 + ); + } + + #[test] + fn return_unnamed_fn() { + assert_evals_to!( + indoc!( + r#" + alwaysIdentity : Num.Num Int.Integer -> (Num.Num Float.FloatingPoint -> Num.Num Float.FloatingPoint) + alwaysIdentity = \num -> + (\a -> a) + + (alwaysIdentity 2) 3.14 + "# + ), + 3.14, + f64 + ); + } +} diff --git a/tests/test_eval.rs b/tests/test_eval.rs deleted file mode 100644 index 65e601e474..0000000000 --- a/tests/test_eval.rs +++ /dev/null @@ -1,311 +0,0 @@ -#[macro_use] -extern crate pretty_assertions; -#[macro_use] -extern crate indoc; - -extern crate bumpalo; -extern crate inkwell; -extern crate roc; - -mod helpers; - -#[cfg(test)] -mod test_gen { - use crate::helpers::can_expr; - use bumpalo::Bump; - use inkwell::context::Context; - use inkwell::execution_engine::JitFunction; - use inkwell::types::BasicType; - use inkwell::OptimizationLevel; - use roc::collections::{ImMap, MutMap}; - use roc::gen::build::{build_expr, build_proc}; - use roc::gen::convert::content_to_basic_type; - use roc::gen::env::Env; - use roc::infer::infer_expr; - use roc::ll::expr::Expr; - use roc::subs::Subs; - - macro_rules! assert_evals_to { - ($src:expr, $expected:expr, $ty:ty) => { - let arena = Bump::new(); - let (expr, _output, _problems, var_store, variable, constraint) = can_expr($src); - let mut subs = Subs::new(var_store.into()); - let mut unify_problems = Vec::new(); - let content = infer_expr(&mut subs, &mut unify_problems, &constraint, variable); - - let context = Context::create(); - let builder = context.create_builder(); - let module = context.create_module("app"); - - // Compute main_fn_type before moving subs to Env - let main_fn_type = content_to_basic_type(&content, &mut subs, &context) - .expect("Unable to infer type for test expr") - .fn_type(&[], false); - let main_fn_name = "$Test.main"; - - // Compile and add all the Procs before adding main - let mut procs = MutMap::default(); - let env = Env { - subs, - builder: &builder, - context: &context, - module: arena.alloc(module), - }; - - // Populate Procs and get the low-level Expr from the canonical Expr - let main_body = Expr::new(&arena, &env.subs, &env.module, &context, expr, &mut procs); - - // Add all the Procs to the module - for (name, (opt_proc, _fn_val)) in procs.clone() { - if let Some(proc) = opt_proc { - build_proc(&env, &ImMap::default(), name, proc, &procs); - } - } - - // Add main to the module. - let main_fn = env.module.add_function(main_fn_name, main_fn_type, None); - - // Add main's body - let basic_block = context.append_basic_block(main_fn, "entry"); - - builder.position_at_end(&basic_block); - - let ret = build_expr( - &env, - &ImMap::default(), - main_fn, - &main_body, - &mut MutMap::default(), - ); - - builder.build_return(Some(&ret)); - - if !main_fn.verify(true) { - panic!("Function {} failed LLVM verification.", main_fn_name); - } - - let execution_engine = env - .module - .create_jit_execution_engine(OptimizationLevel::None) - .expect("errored"); - - unsafe { - // Uncomment this to see the module's LLVM instruction output: - // env.module.print_to_stderr(); - - 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!(main.call(), $expected); - } - }; - } - - #[test] - fn basic_int() { - assert_evals_to!("123", 123, i64); - } - - #[test] - fn basic_float() { - assert_evals_to!("1234.0", 1234.0, f64); - } - - #[test] - fn gen_when_take_first_branch() { - assert_evals_to!( - indoc!( - r#" - when 1 is - 1 -> 12 - _ -> 34 - "# - ), - 12, - i64 - ); - } - - #[test] - fn gen_when_take_second_branch() { - assert_evals_to!( - indoc!( - r#" - when 2 is - 1 -> 63 - _ -> 48 - "# - ), - 48, - i64 - ); - } - #[test] - fn gen_when_one_branch() { - assert_evals_to!( - indoc!( - r#" - when 3.14 is - _ -> 23 - "# - ), - 23, - i64 - ); - } - - #[test] - fn gen_basic_def() { - assert_evals_to!( - indoc!( - r#" - answer = 42 - - answer - "# - ), - 42, - i64 - ); - - assert_evals_to!( - indoc!( - r#" - pi = 3.14 - - pi - "# - ), - 3.14, - f64 - ); - } - - #[test] - fn gen_multiple_defs() { - assert_evals_to!( - indoc!( - r#" - answer = 42 - - pi = 3.14 - - answer - "# - ), - 42, - i64 - ); - - assert_evals_to!( - indoc!( - r#" - answer = 42 - - pi = 3.14 - - pi - "# - ), - 3.14, - f64 - ); - } - - #[test] - fn gen_chained_defs() { - assert_evals_to!( - indoc!( - r#" - x = i1 - i3 = i2 - i1 = 1337 - i2 = i1 - y = 12.4 - - i3 - "# - ), - 1337, - i64 - ); - } - - #[test] - fn gen_nested_defs() { - assert_evals_to!( - indoc!( - r#" - x = 5 - - answer = - i3 = i2 - - nested = - a = 1.0 - b = 5 - - i1 - - i1 = 1337 - i2 = i1 - - - nested - - # None of this should affect anything, even though names - # overlap with the previous nested defs - unused = - nested = 17 - - i1 = 84.2 - - nested - - y = 12.4 - - answer - "# - ), - 1337, - 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 gen_when_fn() { - assert_evals_to!( - indoc!( - r#" - limitedNegate = \num -> - when num is - 1 -> -1 - _ -> 0 - - limitedNegate 1 - "# - ), - -1, - i64 - ); - } -} diff --git a/tests/test_format.rs b/tests/test_format.rs index b09814fd80..ebcfb4bb89 100644 --- a/tests/test_format.rs +++ b/tests/test_format.rs @@ -108,24 +108,23 @@ mod test_format { expr_formats_to( indoc!( r#" - # This variable is for greeting + # This variable is for greeting - a = "Hello" + a = "Hello" - a - "# + a + "# ), indoc!( r#" - # This variable is for greeting + # This variable is for greeting + a = "Hello" - a = "Hello" - - a - "# + a + "# ), ); } @@ -169,13 +168,13 @@ mod test_format { expr_formats_same(indoc!( r#" - f = \x, y -> - a = 3 - b = 6 + f = \x, y -> + a = 3 + b = 6 - c + c - "string" + "string" "# )); } @@ -241,7 +240,7 @@ mod test_format { expr_formats_same(indoc!( r#" """ - + "" \""" ""\" """ @@ -294,6 +293,24 @@ mod test_format { )); } + #[test] + fn destructure_tag_closure() { + expr_formats_same(indoc!( + r#" + \Foo a -> Foo a + "# + )); + } + + #[test] + fn destructure_nested_tag_closure() { + expr_formats_same(indoc!( + r#" + \Foo (Bar a) -> Foo (Bar a) + "# + )); + } + // DEFS #[test] @@ -321,22 +338,22 @@ mod test_format { expr_formats_to( indoc!( r#" - x = 5 + x = 5 - y = 10 + y = 10 - 42 - "# + 42 + "# ), indoc!( r#" - x = 5 + x = 5 - y = 10 + y = 10 - 42 - "# + 42 + "# ), ); } @@ -396,7 +413,6 @@ mod test_format { y = 10 # v-- This is the return value - 42 "# )); @@ -404,17 +420,28 @@ mod test_format { #[test] fn space_between_comments() { - expr_formats_same(indoc!( - r#" - # 9 + expr_formats_to( + indoc!( + r#" + # 9 - # A - # B + # A + # B - # C - 9 - "# - )); + # C + 9 + "# + ), + indoc!( + r#" + # 9 + # A + # B + # C + 9 + "# + ), + ); } #[test] @@ -434,40 +461,38 @@ mod test_format { indoc!( r#" # First - # Second x "# ), ); - // expr_formats_to( - // indoc!( - // r#" - // f = \x -> - // # 1st - // - // - // - // - // # 2nd - // x - // - // f 4 - // "# - // ), - // indoc!( - // r#" - // f = \x -> - // # 1st - // - // # 2nd - // x - // - // f 4 - // "# - // ), - // ); + expr_formats_to( + indoc!( + r#" + f = \x -> + # 1st + + + + + # 2nd + x + + f 4 + "# + ), + indoc!( + r#" + f = \x -> + # 1st + # 2nd + x + + f 4 + "# + ), + ); } #[test] fn doesnt_detect_comment_in_comment() { @@ -515,15 +540,77 @@ mod test_format { )); } - // #[test] - // fn record_field_destructuring() { - // expr_formats_same(indoc!( - // r#" - // when foo is - // { x: 5 } -> 42 - // "# - // )); - // } + // #[test] + // fn record_field_destructuring() { + // expr_formats_same(indoc!( + // r#" + // when foo is + // { x: 5 } -> 42 + // "# + // )); + // } + + #[test] + fn record_updating() { + expr_formats_same(indoc!( + r#" + { shoes & leftShoe: nothing } + "# + )); + + expr_formats_to( + indoc!( + r#" + { shoes & rightShoe : nothing } + "# + ), + indoc!( + r#" + { shoes & rightShoe: nothing } + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + { shoes & rightShoe : nothing } + "# + ), + indoc!( + r#" + { shoes & rightShoe: nothing } + "# + ), + ); + + expr_formats_same(indoc!( + r#" + { shoes & + rightShoe: newRightShoe, + leftShoe: newLeftShoe + } + "# + )); + + expr_formats_to( + indoc!( + r#" + { shoes + & rightShoe: bareFoot + , leftShoe: bareFoot } + "# + ), + indoc!( + r#" + { shoes & + rightShoe: bareFoot, + leftShoe: bareFoot + } + "# + ), + ); + } #[test] fn def_closure() { @@ -716,26 +803,56 @@ mod test_format { x: 4, y: 42 } - "# + "# )); } + // #[test] + // fn multi_line_list_def() { + // expr_formats_same(indoc!( + // r#" + // scores = + // [ + // 5, + // 10 + // ] + // + // scores + // "# + // )); + // } + // + // #[test] + // fn multi_line_record_def() { + // expr_formats_same(indoc!( + // r#" + // pos = + // { + // x: 5, + // x: 10 + // } + // + // pos + // "# + // )); + // } + #[test] fn two_fields_center_newline() { expr_formats_to( indoc!( r#" - { x: 4, - y: 42 - } - "# + { x: 4, + y: 42 + } + "# ), indoc!( r#" - { - x: 4, - y: 42 - } + { + x: 4, + y: 42 + } "# ), ); @@ -759,13 +876,13 @@ mod test_format { expr_formats_same(indoc!( r#" if foo bar then a b c else d e f - "# + "# )); expr_formats_same(indoc!( r#" if foo (a b c) then a b c else d e f - "# + "# )); } @@ -777,7 +894,6 @@ mod test_format { waterWillBoil pressure temperature then turnOnAc - else identity "# @@ -805,7 +921,6 @@ mod test_format { willBoil home water then \_ -> leave - else identity "# @@ -813,71 +928,114 @@ mod test_format { ); } - // #[test] - // fn if_removes_newlines() { - // expr_formats_to( - // indoc!( - // r#" - // if - // - // # You never know! - // isPrime 8 - // - // # Top Comment - // - // # Bottom Comment - // - // - // then - // - // # A - // - // # B - // - // nothing - // - // # B again - // - // else - // - // # C - // # D - // - // # E - // # F - // - // just (div 1 8) - // "# - // ), - // indoc!( - // r#" - // if - // # You never know! - // isPrime 8 + #[test] + fn if_removes_newlines_from_else() { + expr_formats_to( + indoc!( + r#" + if + isPrime 8 + then + nothing + else + # C + # D - // # Top Comment + # E + # F - // # Bottom Comment - // then - // # A + just (div 1 8) + "# + ), + indoc!( + r#" + if + isPrime 8 + then + nothing + else + # C + # D + # E + # F + just (div 1 8) + "# + ), + ); + } - // # B + #[test] + fn if_removes_newlines_from_then() { + expr_formats_to( + indoc!( + r#" + if + isPrime 9 + then + # EE + # FF - // nothing + nothing - // # B again - // - // else - // # C - // # D + # GG + + else + just (div 1 9) + "# + ), + indoc!( + r#" + if + isPrime 9 + then + # EE + # FF + nothing + # GG + else + just (div 1 9) + "# + ), + ); + } + + #[test] + fn if_removes_newlines_from_condition() { + expr_formats_to( + indoc!( + r#" + if + + # Is + + # It + + isPrime 10 + + # Prime? + + then + nothing + else + just (div 1 10) + "# + ), + indoc!( + r#" + if + # Is + # It + isPrime 10 + # Prime? + then + nothing + else + just (div 1 10) + "# + ), + ); + } - // # E - // # F - // just (div 1 8) - // "# - // ), - // ); - // } #[test] fn multi_line_if() { expr_formats_to( @@ -885,6 +1043,8 @@ mod test_format { r#" if lessThan four five then four + + else five "# @@ -893,7 +1053,6 @@ mod test_format { r#" if lessThan four five then four - else five "# @@ -921,7 +1080,6 @@ mod test_format { r#" if lessThan three four then three - else four "# @@ -932,7 +1090,6 @@ mod test_format { r#" if foo bar then a b c - else d e f "# @@ -961,7 +1118,7 @@ mod test_format { _ -> 2 - "# + "# )); } @@ -970,28 +1127,28 @@ mod test_format { expr_formats_to( indoc!( r#" - when year is - 1999 -> + when year is + 1999 -> - 1 + 1 - _ -> + _ -> - 0 - "# + 0 + "# ), indoc!( r#" - when year is - 1999 -> - 1 + when year is + 1999 -> + 1 - _ -> - 0 - "# + _ -> + 0 + "# ), ); } @@ -1013,7 +1170,7 @@ mod test_format { # more comment 2 - "# + "# )); } @@ -1026,7 +1183,7 @@ mod test_format { when c is _ -> 1 - "# + "# )); } @@ -1071,25 +1228,25 @@ mod test_format { expr_formats_to( indoc!( r#" - when b is - 1 -> - 1 # when 1 + when b is + 1 -> + 1 # when 1 - # fall through - _ -> - 2 + # fall through + _ -> + 2 "# ), indoc!( r#" - when b is - 1 -> - 1 + when b is + 1 -> + 1 - # when 1 - # fall through - _ -> - 2 + # when 1 + # fall through + _ -> + 2 "# ), ); @@ -1129,15 +1286,15 @@ mod test_format { fn def_returning_closure() { expr_formats_same(indoc!( r#" - f = \x -> x - g = \x -> x + f = \x -> x + g = \x -> x - \x -> - a = f x - b = f x + \x -> + a = f x + b = f x - x - "# + x + "# )); } diff --git a/tests/test_infer.rs b/tests/test_infer.rs index 73f4b9dd12..ad7aa41a1b 100644 --- a/tests/test_infer.rs +++ b/tests/test_infer.rs @@ -607,20 +607,19 @@ mod test_infer { ); } - // #[test] - // TODO FIXME this should work, but instead causes a stack overflow! - // fn recursive_identity() { - // infer_eq( - // indoc!( - // r#" - // identity = \val -> val + #[test] + fn recursive_identity() { + infer_eq( + indoc!( + r#" + identity = \val -> val - // identity identity - // "# - // ), - // "a -> a", - // ); - // } + identity identity + "# + ), + "a -> a", + ); + } #[test] fn identity_function() { @@ -813,20 +812,20 @@ mod test_infer { ); } - // #[test] - // fn if_with_int_literals() { - // infer_eq( - // indoc!( - // r#" - // if 1 == 1 then - // 42 - // else - // 24 - // "# - // ), - // "Int", - // ); - // } + #[test] + fn if_with_int_literals() { + infer_eq( + indoc!( + r#" + if True then + 42 + else + 24 + "# + ), + "Int", + ); + } #[test] fn when_with_int_literals() { @@ -1006,7 +1005,7 @@ mod test_infer { { user & year: "foo" } "# ), - "{ year : Str }{ name : Str }", + "{ name : Str, year : Str }", ); } @@ -1039,7 +1038,7 @@ mod test_infer { r#"\@Foo -> 42 "# ), - "[ Test.Foo ]* -> Int", + "[ Test.@Foo ]* -> Int", ); } @@ -1068,16 +1067,95 @@ mod test_infer { ); } - // currently doesn't work because of a parsing issue - // @Foo x y isn't turned into Apply(@Foo, [x,y]) currently - // #[test] - // fn private_tag_application() { - // infer_eq( - // indoc!( - // r#"@Foo "happy" 2020 - // "# - // ), - // "[ Test.Foo Str Int ]*", - // ); - // } + #[test] + fn private_tag_application() { + infer_eq( + indoc!( + r#"@Foo "happy" 2020 + "# + ), + "[ Test.@Foo Str Int ]*", + ); + } + + #[test] + fn record_extraction() { + with_larger_debug_stack(|| { + infer_eq( + indoc!( + r#" + f = \x -> + when x is + { a, b } -> a + + f + "# + ), + "{ a : a, b : * }* -> a", + ); + }); + } + + #[test] + fn record_field_pattern_match_with_guard() { + infer_eq( + indoc!( + r#" + when foo is + { x: 4 } -> x + "# + ), + "Int", + ); + } + + #[test] + fn tag_union_pattern_match() { + infer_eq( + indoc!( + r#" + \Foo x -> Foo x + "# + ), + "[ Foo a ]* -> [ Foo a ]*", + ); + } + + #[test] + fn tag_union_pattern_match_ignored_field() { + infer_eq( + indoc!( + r#" + \Foo x _ -> Foo x "y" + "# + ), + "[ Foo a * ]* -> [ Foo a Str ]*", + ); + } + + #[test] + fn global_tag_with_field() { + infer_eq( + indoc!( + r#" + when Foo 4 is + Foo x -> x + "# + ), + "Int", + ); + } + + #[test] + fn private_tag_with_field() { + infer_eq( + indoc!( + r#" + when @Foo 4 is + @Foo x -> x + "# + ), + "Int", + ); + } } diff --git a/tests/test_parse.rs b/tests/test_parse.rs index 722b7a0473..2e3fbb0b2f 100644 --- a/tests/test_parse.rs +++ b/tests/test_parse.rs @@ -25,7 +25,7 @@ mod test_parse { use roc::parse::ast::CommentOrNewline::*; use roc::parse::ast::Expr::{self, *}; use roc::parse::ast::Pattern::{self, *}; - use roc::parse::ast::{Attempting, Def, InterfaceHeader, Spaceable}; + use roc::parse::ast::{Attempting, Def, InterfaceHeader, Spaceable, Tag, TypeAnnotation}; use roc::parse::module::{interface_header, module_defs}; use roc::parse::parser::{Fail, FailReason, Parser, State}; use roc::region::{Located, Region}; @@ -641,7 +641,7 @@ mod test_parse { assert_eq!(Ok(expected), actual); } - // VARIANT + // TAG #[test] fn basic_global_tag() { @@ -661,6 +661,38 @@ mod test_parse { assert_eq!(Ok(expected), actual); } + #[test] + fn apply_private_tag() { + let arena = Bump::new(); + let arg1 = arena.alloc(Located::new(0, 0, 6, 8, Int("12"))); + let arg2 = arena.alloc(Located::new(0, 0, 9, 11, Int("34"))); + let args = bumpalo::vec![in &arena; &*arg1, &*arg2]; + let expected = Expr::Apply( + arena.alloc(Located::new(0, 0, 0, 5, Expr::PrivateTag("@Whee"))), + args, + CalledVia::Space, + ); + let actual = parse_with(&arena, "@Whee 12 34"); + + assert_eq!(Ok(expected), actual); + } + + #[test] + fn apply_global_tag() { + let arena = Bump::new(); + let arg1 = arena.alloc(Located::new(0, 0, 5, 7, Int("12"))); + let arg2 = arena.alloc(Located::new(0, 0, 8, 10, Int("34"))); + let args = bumpalo::vec![in &arena; &*arg1, &*arg2]; + let expected = Expr::Apply( + arena.alloc(Located::new(0, 0, 0, 4, Expr::GlobalTag("Whee"))), + args, + CalledVia::Space, + ); + let actual = parse_with(&arena, "Whee 12 34"); + + assert_eq!(Ok(expected), actual); + } + #[test] fn qualified_global_tag() { let arena = Bump::new(); @@ -1245,27 +1277,17 @@ mod test_parse { let arena = Bump::new(); let newline = bumpalo::vec![in &arena; Newline]; let newlines = bumpalo::vec![in &arena; Newline, Newline]; + let applied_ann = TypeAnnotation::Apply(&[], "Int", &[]); let signature = Def::Annotation( Located::new(0, 0, 0, 3, Identifier("foo")), - Located::new( - 0, - 0, - 6, - 9, - roc::parse::ast::TypeAnnotation::Apply(&[], "Int", &[]), - ), + Located::new(0, 0, 6, 9, applied_ann), ); let def = Def::Body( arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))), arena.alloc(Located::new(1, 1, 6, 7, Int("4"))), ); - let loc_def = &*arena.alloc(Located::new( - 1, - 1, - 0, - 7, - Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice()), - )); + let spaced_def = Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice()); + let loc_def = &*arena.alloc(Located::new(1, 1, 0, 7, spaced_def)); let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature)); let defs = bumpalo::vec![in &arena; loc_ann, loc_def]; @@ -1288,7 +1310,7 @@ mod test_parse { #[test] fn type_signature_function_def() { - use roc::parse::ast::TypeAnnotation; + use TypeAnnotation; let arena = Bump::new(); let newline = bumpalo::vec![in &arena; Newline]; let newlines = bumpalo::vec![in &arena; Newline, Newline]; @@ -1302,16 +1324,10 @@ mod test_parse { Located::new(0, 0, 11, 16, float_type) ]; let return_type = Located::new(0, 0, 20, 24, bool_type); - + let fn_ann = TypeAnnotation::Function(&arguments, &return_type); let signature = Def::Annotation( Located::new(0, 0, 0, 3, Identifier("foo")), - Located::new( - 0, - 0, - 20, - 24, - TypeAnnotation::Function(&arguments, &return_type), - ), + Located::new(0, 0, 20, 24, fn_ann), ); let args = bumpalo::vec![in &arena; @@ -1326,13 +1342,8 @@ mod test_parse { arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))), arena.alloc(Located::new(1, 1, 6, 17, closure)), ); - let loc_def = &*arena.alloc(Located::new( - 1, - 1, - 0, - 17, - Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice()), - )); + let spaced = Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice()); + let loc_def = &*arena.alloc(Located::new(1, 1, 0, 17, spaced)); let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature)); let defs = bumpalo::vec![in &arena; loc_ann, loc_def]; @@ -1353,6 +1364,220 @@ mod test_parse { ); } + #[test] + fn ann_private_open_union() { + let arena = Bump::new(); + let newline = bumpalo::vec![in &arena; Newline]; + let newlines = bumpalo::vec![in &arena; Newline, Newline]; + let tag1 = Tag::Private { + name: Located::new(0, 0, 8, 13, "@True"), + args: &[], + }; + let tag2arg = Located::new(0, 0, 24, 29, TypeAnnotation::Apply(&[], "Thing", &[])); + let tag2args = bumpalo::vec![in &arena; tag2arg]; + let tag2 = Tag::Private { + name: Located::new(0, 0, 15, 23, "@Perhaps"), + args: tag2args.into_bump_slice(), + }; + let tags = bumpalo::vec![in &arena; + Located::new(0, 0, 8, 13, tag1), + Located::new(0, 0, 15, 29, tag2) + ]; + let loc_wildcard = Located::new(0, 0, 31, 32, TypeAnnotation::Wildcard); + let applied_ann = TypeAnnotation::TagUnion { + tags: tags.into_bump_slice(), + ext: Some(arena.alloc(loc_wildcard)), + }; + let signature = Def::Annotation( + Located::new(0, 0, 0, 3, Identifier("foo")), + Located::new(0, 0, 6, 32, applied_ann), + ); + let def = Def::Body( + arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))), + arena.alloc(Located::new(1, 1, 6, 10, Expr::GlobalTag("True"))), + ); + let spaced_def = Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice()); + let loc_def = &*arena.alloc(Located::new(1, 1, 0, 10, spaced_def)); + + let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature)); + let defs = bumpalo::vec![in &arena; loc_ann, loc_def]; + let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice()); + let loc_ret = Located::new(3, 3, 0, 2, ret); + let expected = Defs(defs, arena.alloc(loc_ret)); + + assert_parses_to( + indoc!( + r#" + foo : [ @True, @Perhaps Thing ]* + foo = True + + 42 + "# + ), + expected, + ); + } + + #[test] + fn ann_private_closed_union() { + let arena = Bump::new(); + let newline = bumpalo::vec![in &arena; Newline]; + let newlines = bumpalo::vec![in &arena; Newline, Newline]; + let tag1 = Tag::Private { + name: Located::new(0, 0, 8, 13, "@True"), + args: &[], + }; + let tag2arg = Located::new(0, 0, 24, 29, TypeAnnotation::Apply(&[], "Thing", &[])); + let tag2args = bumpalo::vec![in &arena; tag2arg]; + let tag2 = Tag::Private { + name: Located::new(0, 0, 15, 23, "@Perhaps"), + args: tag2args.into_bump_slice(), + }; + let tags = bumpalo::vec![in &arena; + Located::new(0, 0, 8, 13, tag1), + Located::new(0, 0, 15, 29, tag2) + ]; + let applied_ann = TypeAnnotation::TagUnion { + tags: tags.into_bump_slice(), + ext: None, + }; + let signature = Def::Annotation( + Located::new(0, 0, 0, 3, Identifier("foo")), + Located::new(0, 0, 6, 31, applied_ann), + ); + let def = Def::Body( + arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))), + arena.alloc(Located::new(1, 1, 6, 10, Expr::GlobalTag("True"))), + ); + let spaced_def = Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice()); + let loc_def = &*arena.alloc(Located::new(1, 1, 0, 10, spaced_def)); + + let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature)); + let defs = bumpalo::vec![in &arena; loc_ann, loc_def]; + let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice()); + let loc_ret = Located::new(3, 3, 0, 2, ret); + let expected = Defs(defs, arena.alloc(loc_ret)); + + assert_parses_to( + indoc!( + r#" + foo : [ @True, @Perhaps Thing ] + foo = True + + 42 + "# + ), + expected, + ); + } + + #[test] + fn ann_global_open_union() { + let arena = Bump::new(); + let newline = bumpalo::vec![in &arena; Newline]; + let newlines = bumpalo::vec![in &arena; Newline, Newline]; + let tag1 = Tag::Global { + name: Located::new(0, 0, 8, 12, "True"), + args: &[], + }; + let tag2arg = Located::new(0, 0, 22, 27, TypeAnnotation::Apply(&[], "Thing", &[])); + let tag2args = bumpalo::vec![in &arena; tag2arg]; + let tag2 = Tag::Global { + name: Located::new(0, 0, 14, 21, "Perhaps"), + args: tag2args.into_bump_slice(), + }; + let tags = bumpalo::vec![in &arena; + Located::new(0, 0, 8, 12, tag1), + Located::new(0, 0, 14, 27, tag2) + ]; + let loc_wildcard = Located::new(0, 0, 29, 30, TypeAnnotation::Wildcard); + let applied_ann = TypeAnnotation::TagUnion { + tags: tags.into_bump_slice(), + ext: Some(arena.alloc(loc_wildcard)), + }; + let signature = Def::Annotation( + Located::new(0, 0, 0, 3, Identifier("foo")), + Located::new(0, 0, 6, 30, applied_ann), + ); + let def = Def::Body( + arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))), + arena.alloc(Located::new(1, 1, 6, 10, Expr::GlobalTag("True"))), + ); + let spaced_def = Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice()); + let loc_def = &*arena.alloc(Located::new(1, 1, 0, 10, spaced_def)); + + let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature)); + let defs = bumpalo::vec![in &arena; loc_ann, loc_def]; + let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice()); + let loc_ret = Located::new(3, 3, 0, 2, ret); + let expected = Defs(defs, arena.alloc(loc_ret)); + + assert_parses_to( + indoc!( + r#" + foo : [ True, Perhaps Thing ]* + foo = True + + 42 + "# + ), + expected, + ); + } + + #[test] + fn ann_global_closed_union() { + let arena = Bump::new(); + let newline = bumpalo::vec![in &arena; Newline]; + let newlines = bumpalo::vec![in &arena; Newline, Newline]; + let tag1 = Tag::Global { + name: Located::new(0, 0, 8, 12, "True"), + args: &[], + }; + let tag2arg = Located::new(0, 0, 22, 27, TypeAnnotation::Apply(&[], "Thing", &[])); + let tag2args = bumpalo::vec![in &arena; tag2arg]; + let tag2 = Tag::Global { + name: Located::new(0, 0, 14, 21, "Perhaps"), + args: tag2args.into_bump_slice(), + }; + let tags = bumpalo::vec![in &arena; + Located::new(0, 0, 8, 12, tag1), + Located::new(0, 0, 14, 27, tag2) + ]; + let applied_ann = TypeAnnotation::TagUnion { + tags: tags.into_bump_slice(), + ext: None, + }; + let signature = Def::Annotation( + Located::new(0, 0, 0, 3, Identifier("foo")), + Located::new(0, 0, 6, 29, applied_ann), + ); + let def = Def::Body( + arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))), + arena.alloc(Located::new(1, 1, 6, 10, Expr::GlobalTag("True"))), + ); + let spaced_def = Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice()); + let loc_def = &*arena.alloc(Located::new(1, 1, 0, 10, spaced_def)); + + let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature)); + let defs = bumpalo::vec![in &arena; loc_ann, loc_def]; + let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice()); + let loc_ret = Located::new(3, 3, 0, 2, ret); + let expected = Defs(defs, arena.alloc(loc_ret)); + + assert_parses_to( + indoc!( + r#" + foo : [ True, Perhaps Thing ] + foo = True + + 42 + "# + ), + expected, + ); + } + // WHEN #[test] diff --git a/tests/test_uniqueness_infer.rs b/tests/test_uniqueness_infer.rs index daa3e180e7..7846095dc2 100644 --- a/tests/test_uniqueness_infer.rs +++ b/tests/test_uniqueness_infer.rs @@ -17,22 +17,15 @@ mod test_infer_uniq { // HELPERS fn infer_eq(src: &str, expected: &str) { - let ( - _output2, - _output1, - _, - mut subs1, - variable1, - mut subs2, - variable2, - constraint1, - constraint2, - ) = uniq_expr(src); + let (_output1, _, mut subs1, variable1, mut subs2, variable2, constraint1, constraint2) = + uniq_expr(src); let mut unify_problems = Vec::new(); let content1 = infer_expr(&mut subs1, &mut unify_problems, &constraint1, variable1); let content2 = infer_expr(&mut subs2, &mut unify_problems, &constraint2, variable2); + dbg!(unify_problems); + name_all_type_vars(variable1, &mut subs1); name_all_type_vars(variable2, &mut subs2); @@ -514,15 +507,13 @@ mod test_infer_uniq { infer_eq( indoc!( r#" - identity = \a -> a + identity = \a -> a + x = identity 5 - x = identity 5 - - identity - "# + identity + "# ), - // TODO investigate why not shared - // perhaps because `x` is DCE'd? + // TODO investigate why not shared? "Attr.Attr * (a -> a)", ); } @@ -637,20 +628,19 @@ mod test_infer_uniq { ); } - // #[test] - // TODO FIXME this should work, but instead causes a stack overflow! - // fn recursive_identity() { - // infer_eq( - // indoc!( - // r#" - // identity = \val -> val + #[test] + fn recursive_identity() { + infer_eq( + indoc!( + r#" + identity = \val -> val - // identity identity - // "# - // ), - // "a -> a", - // ); - // } + identity identity + "# + ), + "Attr.Attr Attr.Shared (a -> a)", + ); + } #[test] fn identity_function() { @@ -843,20 +833,20 @@ mod test_infer_uniq { ); } - // #[test] - // fn if_with_int_literals() { - // infer_eq( - // indoc!( - // r#" - // if 1 == 1 then - // 42 - // else - // 24 - // "# - // ), - // "Int", - // ); - // } + #[test] + fn if_with_int_literals() { + infer_eq( + indoc!( + r#" + if True then + 42 + else + 24 + "# + ), + "Attr.Attr * Int", + ); + } #[test] fn when_with_int_literals() { @@ -872,11 +862,6 @@ mod test_infer_uniq { ); } - #[test] - fn accessor_function() { - infer_eq(".foo", "Attr.Attr * { foo : a }* -> a"); - } - #[test] fn record() { infer_eq("{ foo: 42 }", "Attr.Attr * { foo : (Attr.Attr * Int) }"); @@ -887,19 +872,6 @@ mod test_infer_uniq { infer_eq("{ foo: 42 }.foo", "Attr.Attr * Int"); } - #[test] - fn record_pattern_match_infer() { - infer_eq( - indoc!( - r#" - when foo is - { x: 4 }-> x - "# - ), - "Int", - ); - } - #[test] fn empty_record_pattern() { infer_eq( @@ -927,7 +899,226 @@ mod test_infer_uniq { { user & year: "foo" } "# ), - "Attr.Attr * { year : (Attr.Attr * Str) }{ name : (Attr.Attr * Str) }", + "Attr.Attr * { name : (Attr.Attr * Str), year : (Attr.Attr * Str) }", ); } + + #[test] + fn bare_tag() { + infer_eq( + indoc!( + r#"Foo + "# + ), + "Attr.Attr * [ Foo ]*", + ); + } + + #[test] + fn single_tag_pattern() { + infer_eq( + indoc!( + r#"\Foo -> 42 + "# + ), + "Attr.Attr * (Attr.Attr * [ Foo ]* -> Attr.Attr * Int)", + ); + } + + #[test] + fn single_private_tag_pattern() { + infer_eq( + indoc!( + r#"\@Foo -> 42 + "# + ), + "Attr.Attr * (Attr.Attr * [ Test.@Foo ]* -> Attr.Attr * Int)", + ); + } + + #[test] + fn two_tag_pattern() { + infer_eq( + indoc!( + r#"\x -> + when x is + True -> 1 + False -> 0 + "# + ), + "Attr.Attr * (Attr.Attr * [ False, True ]* -> Attr.Attr * Int)", + ); + } + + #[test] + fn tag_application() { + infer_eq( + indoc!( + r#"Foo "happy" 2020 + "# + ), + "Attr.Attr * [ Foo (Attr.Attr * Str) (Attr.Attr * Int) ]*", + ); + } + + #[test] + fn private_tag_application() { + infer_eq( + indoc!( + r#"@Foo "happy" 2020 + "# + ), + "Attr.Attr * [ Test.@Foo (Attr.Attr * Str) (Attr.Attr * Int) ]*", + ); + } + + #[test] + fn record_field_access() { + infer_eq( + indoc!( + r#" + \rec -> rec.left + "# + ), + "Attr.Attr * (Attr.Attr a { left : (Attr.Attr a b) }* -> Attr.Attr a b)", + ); + } + + #[test] + fn record_field_accessor_function() { + infer_eq( + indoc!( + r#" + .left + "# + ), + "Attr.Attr * (Attr.Attr a { left : (Attr.Attr a b) }* -> Attr.Attr a b)", + ); + } + + #[test] + fn record_field_pattern_match() { + infer_eq( + indoc!( + r#" + \{ left } -> left + "# + ), + "Attr.Attr * (Attr.Attr a { left : (Attr.Attr a b) }* -> Attr.Attr a b)", + ); + } + + #[test] + fn record_field_pattern_match_two() { + infer_eq( + indoc!( + r#" + \{ left, right } -> { left, right } + "# + ), + "Attr.Attr * (Attr.Attr (a | b) { left : (Attr.Attr a c), right : (Attr.Attr b d) }* -> Attr.Attr * { left : (Attr.Attr a c), right : (Attr.Attr b d) })", + ); + } + + #[test] + fn record_field_pattern_match_with_guard() { + infer_eq( + indoc!( + r#" + when foo is + { x: 4 } -> x + "# + ), + "Attr.Attr * Int", + ); + } + + #[test] + fn tag_union_pattern_match() { + infer_eq( + indoc!( + r#" + \Foo x -> Foo x + "# + ), + // 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 * (Attr.Attr a [ Foo (Attr.Attr a b) ]* -> Attr.Attr * [ Foo (Attr.Attr a b) ]*)", + ); + } + + #[test] + fn tag_union_pattern_match_ignored_field() { + infer_eq( + indoc!( + r#" + \Foo x _ -> Foo x "y" + "# + ), + // TODO: is it safe to ignore uniqueness constraints from patterns that bind no identifiers? + // i.e. the `a` could be ignored in this example, is that true in general? + // seems like it because we don't really extract anything. + "Attr.Attr * (Attr.Attr (b | a) [ Foo (Attr.Attr b c) (Attr.Attr a *) ]* -> Attr.Attr * [ Foo (Attr.Attr b c) (Attr.Attr * Str) ]*)" + ); + } + + #[test] + fn global_tag_with_field() { + infer_eq( + indoc!( + r#" + when Foo 4 is + Foo x -> x + "# + ), + "Attr.Attr a Int", + ); + } + + #[test] + fn private_tag_with_field() { + infer_eq( + indoc!( + r#" + when @Foo 4 is + @Foo x -> x + "# + ), + "Attr.Attr a Int", + ); + } + + #[test] + fn type_annotation() { + infer_eq( + indoc!( + r#" + x : Int + x = 4 + + x + "# + ), + "Attr.Attr a Int", + ); + } + + // TODO when type signatures are supported, ensure this works + // #[test] + // fn num_identity() { + // infer_eq( + // indoc!( + // r#" + // numIdentity : Num a -> Num a + // numIdentity = \x -> x + // + // x = numIdentity 42 + // y = numIdentity 3.14 + // + // numIdentity + // "# + // ), + // "tbd", + // ); + // } }