Merge remote-tracking branch 'origin/trunk' into case-multi-patterns

This commit is contained in:
Richard Feldman 2020-01-11 13:10:09 -05:00
commit a4e238b09c
47 changed files with 5116 additions and 1917 deletions

View file

@ -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:

214
Cargo.lock generated
View file

@ -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"

View file

@ -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"

View file

@ -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:

View file

@ -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);

View file

@ -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<Located<Expr>>,
branches: Vec<(Located<Pattern>, Located<Expr>)>,
},
If {
cond_var: Variable,
branch_var: Variable,
loc_cond: Box<Located<Expr>>,
loc_then: Box<Located<Expr>>,
loc_else: Box<Located<Expr>>,
},
// Let
LetRec(Vec<Def>, Box<Located<Expr>>, Variable),
LetNonRec(Box<Def>, Box<Located<Expr>>, 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!");

View file

@ -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 {

View file

@ -17,7 +17,7 @@ use im_rc::Vector;
#[derive(Clone, Debug, PartialEq)]
pub enum Pattern {
Identifier(Symbol),
AppliedTag(Variable, Symbol, Vec<Located<Pattern>>),
AppliedTag(Variable, Symbol, Vec<(Variable, Located<Pattern>)>),
IntLiteral(i64),
FloatLiteral(f64),
StrLiteral(Box<str>),
@ -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<Ident, (Symbol,
QualifiedIdentifier(_name) => {
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) => {

View file

@ -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>) -> Type {
pub fn builtin_type(module_name: &str, type_name: &str, args: Vec<Type>) -> Type {
Type::Apply {
module_name: module_name.into(),
name: type_name.into(),

View file

@ -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<Pattern>, 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);

View file

@ -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<Symbol, Located<Type>>,
@ -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,

477
src/crane/build.rs Normal file
View file

@ -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<InlinableString, ScopeEntry>;
#[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<B>,
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<B>,
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<B>,
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<B>,
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<B>,
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 (&param, (_, 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);
}

135
src/crane/convert.rs Normal file
View file

@ -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<B: Backend>(
cfg: TargetFrontendConfig,
module: &mut Module<B>,
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);
}
}
}

View file

@ -1,3 +1,2 @@
pub mod build;
pub mod convert;
pub mod env;

View file

@ -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<Expr<'a>>>,
update: Option<&'a Located<Expr<'a>>>,
loc_fields: &'a Vec<'a, Located<AssignedField<'a, Expr<'a>>>>,
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));

View file

@ -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<Item = &'a CommentOrNewline<'a>>,
{
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,
}
}

View file

@ -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,
}

View file

@ -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<Expr>, module: &Module<'ctx>, name: Option<Lowercase>, procs, &mut Procs<'ctx>) -> Located<Expr> {
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");
}
};
}

View file

@ -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;

View file

@ -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<InlinableString, (Option<Proc<'a>>, 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<InlinableString>,
) -> 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<Pattern>)],
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);
}
}
}

View file

@ -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<InlinableString, (Variable, PointerValue<'ctx>)>;
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<BasicValueEnum> = Vec::with_capacity(args.len());
let mut arg_vals: Vec<BasicValueEnum> =
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<BasicValueEnum> = 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<BasicValueEnum> = 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::<BasicValueEnum>::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::<BasicValueEnum>::into(then_val), &then_bb),
(&Into::<BasicValueEnum>::into(else_val), &else_bb),
(&Into::<BasicValueEnum>::into(then_val), &then_block),
(&Into::<BasicValueEnum>::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();
}

View file

@ -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<BasicTypeEnu
}
/// TODO could this be added to Inkwell itself as a method on BasicValueEnum?
fn get_fn_type<'ctx>(
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) => {

2
src/llvm/mod.rs Normal file
View file

@ -0,0 +1,2 @@
pub mod build;
pub mod convert;

513
src/mono/expr.rs Normal file
View file

@ -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<InlinableString, Option<Proc<'a>>>;
#[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<InlinableString>,
) -> 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<Pattern>)],
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<can::expr::Expr>,
branches: std::vec::Vec<(Located<can::pattern::Pattern>, Located<can::expr::Expr>)>,
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."
);
}
}
}
}

View file

@ -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<Self, ()> {
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::<i64>() as u32;
const F64_SIZE: u32 = std::mem::size_of::<f64>() 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(&[])),
}

View file

@ -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<AssignedField<'a, TypeAnnotation<'a>>>>),
Record {
fields: &'a [Loc<AssignedField<'a, TypeAnnotation<'a>>>],
/// 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<TypeAnnotation<'a>>>,
},
/// A record fragment, e.g. `{ name: String, email: Email }...r`
RecordFragment(
Vec<'a, Loc<AssignedField<'a, TypeAnnotation<'a>>>>,
// the fragment type variable, e.g. the `r` in `{ name: String }...r`
&'a Loc<TypeAnnotation<'a>>,
),
/// A tag union, e.g. `[
TagUnion {
tags: &'a [Loc<Tag<'a>>],
/// 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<TypeAnnotation<'a>>>,
},
/// 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<TypeAnnotation<'a>>],
},
Private {
name: Loc<&'a str>,
args: &'a [Loc<TypeAnnotation<'a>>],
},
// 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<Val>),
// An optional field, e.g. `{ name? : String }`. Only for types
OptionalField(Loc<&'a str>, &'a [CommentOrNewline<'a>], &'a Loc<Val>),
// 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)

View file

@ -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))
}

View file

@ -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<Exp
space0_after(
between!(
char('('),
space0_around(loc!(pattern(min_indent)), min_indent),
space0_around(loc_pattern(min_indent), min_indent),
char(')')
),
min_indent,
@ -755,27 +751,36 @@ fn parse_closure_param<'a>(
// 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<Pattern<'a>>> {
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<Pattern<'a>>> {
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<Pattern<'a>>> {
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<Pattern<'a>>, Vec<'a, Located<Pattern<'a>>>)| {
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<Pattern<'a>>>> {
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<Exp
then(
// Spaces, then '-', then *not* more spaces.
not_followed_by(
and!(space1(min_indent), loc!(char('-'))),
and!(
space1(min_indent),
either!(
// Try to parse a number literal *before* trying to parse unary negate,
// because otherwise (foo -1) will parse as (foo (Num.neg 1))
loc!(number_literal()),
loc!(char('-'))
)
),
one_of!(char(' '), char('#'), char('\n')),
),
move |arena, state, (spaces, loc_minus_char)| {
let region = loc_minus_char.region;
let loc_op = Located {
region,
value: UnaryOp::Negate,
};
move |arena, state, (spaces, num_or_minus_char)| {
match num_or_minus_char {
Either::First(loc_num_literal) => 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 -> ...

View file

@ -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) => {

View file

@ -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<TypeAnnotation<'a
expression(min_indent)
}
macro_rules! tag_union {
($min_indent:expr) => {
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<Tag<'a>>>,
Option<&'a Located<TypeAnnotation<'a>>>,
)| TypeAnnotation::TagUnion {
tags: tags.into_bump_slice(),
ext
}
)
};
}
pub fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>> {
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<TypeAnnotation<'a>>>
}),
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<TypeAn
)
}
#[inline(always)]
#[allow(clippy::type_complexity)]
fn tag_type<'a>(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>, Located<&'a str>>,
Vec<'a, Located<TypeAnnotation<'a>>>
)| 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<AssignedField<'a, TypeAnnotation<'a>>>>,
Option<&'a Located<TypeAnnotation<'a>>>,
)| Record {
fields: fields.into_bump_slice(),
ext
}
)
}

View file

@ -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!("<Type Mismatch: {:?}>", 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,

View file

@ -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<Symbol, Variable>;
@ -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)));

View file

@ -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<Option<Variable>> 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<OptVariable> 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<Variable>, 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) => {

View file

@ -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<Type>,
},
/// 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<Lowercase, Type>),
@ -389,6 +395,7 @@ pub enum ErrorType {
Vec<(Lowercase, ErrorType)>,
Box<ErrorType>,
),
Boolean(boolean_algebra::Bool),
Error,
}

View file

@ -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<Variable>;
@ -15,9 +16,9 @@ struct Context {
second_desc: Descriptor,
}
struct RecordStructure {
fields: ImMap<RecordFieldLabel, Variable>,
ext: Variable,
pub struct RecordStructure {
pub fields: ImMap<RecordFieldLabel, Variable>,
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<RecordFieldLabel, Variable>,
var: Variable,

View file

@ -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<Substitution> {
// 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<Bool>, Box<Bool>),
Or(Box<Bool>, Box<Bool>),
Not(Box<Bool>),
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<I>(mut it: I) -> Bool
where
I: Iterator<Item = Bool>,
{
if let Some(first) = it.next() {
it.fold(first, or)
} else {
Zero
}
}
pub fn all(terms: Product<Bool>) -> Bool {
let mut it = terms.into_iter();
if let Some(first) = it.next() {
it.fold(first, and)
} else {
One
}
}
type Substitution = ImMap<Variable, Bool>;
type Product<A> = ImSet<A>;
type Sum<A> = ImSet<A>;
#[allow(clippy::should_implement_trait)]
impl Bool {
pub fn variables(&self) -> ImSet<Variable> {
let mut result = ImSet::default();
self.variables_help(&mut result);
result
}
fn variables_help(&self, vars: &mut ImSet<Variable>) {
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<F>(&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<ImSet<Bool>> = 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<Product<Bool>> = 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<Bool>, q: &Product<Bool>) -> bool {
p.iter().all(|x| q.contains(x))
}
fn consensus(p: &Product<Bool>, q: &Product<Bool>) -> Option<Product<Bool>> {
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<Bool>, qs: &Product<Bool>) -> Product<Bool> {
let it1 = ps
.clone()
.into_iter()
.filter(|p| qs.contains(&not(p.clone())));
let it2 = qs
.clone()
.into_iter()
.filter(|q| ps.contains(&not(q.clone())));
it1.chain(it2).collect()
}
pub fn try_unify(p: Bool, q: Bool) -> Option<Substitution> {
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<BooleanAlgebra>, Box<BooleanAlgebra>),
Conjunction(Box<BooleanAlgebra>, Box<BooleanAlgebra>),
Negation(Box<BooleanAlgebra>),
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<Variable>, 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<Variable> = 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<Bool>, h: Product<Bool>) -> 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<Sum<Bool>>;
type Sop = Sum<Product<Bool>>;
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<Bool> {
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<Bool> {
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<A>(set: ImSet<A>) -> 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<Variable> {
let mut vars = Vec::new();
variables_help(self, &mut vars);
vars
fn unit<A>(a: A) -> ImSet<A>
where
A: Clone + Eq + core::hash::Hash,
{
let mut result = ImSet::default();
result.insert(a);
result
}
fn normalize_disj(mut sum: Sum<Bool>) -> Sum<Bool> {
let is_always_true =
sum.clone().into_iter().any(|x| sum.contains(&not(x))) || sum.contains(&One);
if is_always_true {
unit(One)
} else {
sum.remove(&Zero);
sum
}
}
fn variables_help(bconstraint: &BooleanAlgebra, variables: &mut Vec<Variable>) {
use BooleanAlgebra::*;
fn normalize_conj(mut product: Product<Bool>) -> Product<Bool> {
let is_always_false = product
.clone()
.into_iter()
.any(|x| product.contains(&not(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<Variable, BooleanAlgebra>,
}
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())
}

View file

@ -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<Variable>, 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<Type>, 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<Type>, 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<Type>,
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 {
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
}

File diff suppressed because it is too large Load diff

View file

@ -89,7 +89,6 @@ pub fn can_expr(expr_str: &str) -> (Expr, Output, Vec<Problem>, VarStore, Variab
pub fn uniq_expr(
expr_str: &str,
) -> (
Output,
Output,
Vec<Problem>,
Subs,
@ -108,7 +107,6 @@ pub fn uniq_expr_with(
expr_str: &str,
declared_idents: &ImMap<Ident, (Symbol, Region)>,
) -> (
Output,
Output,
Vec<Problem>,
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,

View file

@ -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<roc::subs::Variable, Bool>) {
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(),
);
}
}

603
tests/test_emit.rs Normal file
View file

@ -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<SimpleJITBackend> =
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<unsafe extern "C" fn() -> $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
);
}
}

View file

@ -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<unsafe extern "C" fn() -> $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
);
}
}

View file

@ -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
"#
));
}

View file

@ -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",
);
}
}

View file

@ -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]

View file

@ -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",
// );
// }
}