mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-02 08:11:12 +00:00
Merge remote-tracking branch 'origin/trunk' into case-multi-patterns
This commit is contained in:
commit
a4e238b09c
47 changed files with 5116 additions and 1917 deletions
17
.github/workflows/ci.yml
vendored
17
.github/workflows/ci.yml
vendored
|
@ -8,6 +8,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v1
|
||||||
|
|
||||||
- name: Install LLVM
|
- name: Install LLVM
|
||||||
run: sudo ./ci/install-llvm.sh 8
|
run: sudo ./ci/install-llvm.sh 8
|
||||||
|
|
||||||
|
@ -20,6 +21,22 @@ jobs:
|
||||||
|
|
||||||
- run: rustup component add rustfmt
|
- 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
|
- uses: actions-rs/cargo@v1
|
||||||
name: cargo fmt --check
|
name: cargo fmt --check
|
||||||
with:
|
with:
|
||||||
|
|
214
Cargo.lock
generated
214
Cargo.lock
generated
|
@ -39,6 +39,11 @@ name = "bumpalo"
|
||||||
version = "2.6.0"
|
version = "2.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
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]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
|
@ -62,6 +67,105 @@ dependencies = [
|
||||||
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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]]
|
[[package]]
|
||||||
name = "difference"
|
name = "difference"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
|
@ -81,6 +185,25 @@ dependencies = [
|
||||||
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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]]
|
[[package]]
|
||||||
name = "fixedbitset"
|
name = "fixedbitset"
|
||||||
version = "0.1.9"
|
version = "0.1.9"
|
||||||
|
@ -178,6 +301,11 @@ dependencies = [
|
||||||
"slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
|
@ -300,6 +428,14 @@ dependencies = [
|
||||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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]]
|
[[package]]
|
||||||
name = "maplit"
|
name = "maplit"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
|
@ -556,6 +692,16 @@ dependencies = [
|
||||||
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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]]
|
[[package]]
|
||||||
name = "rdrand"
|
name = "rdrand"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
@ -585,11 +731,26 @@ name = "regex-syntax"
|
||||||
version = "0.6.12"
|
version = "0.6.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
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]]
|
[[package]]
|
||||||
name = "roc"
|
name = "roc"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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)",
|
"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 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)",
|
"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)",
|
"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 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)",
|
"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)",
|
"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)",
|
"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]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
|
@ -664,6 +834,29 @@ dependencies = [
|
||||||
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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]]
|
[[package]]
|
||||||
name = "thread_local"
|
name = "thread_local"
|
||||||
version = "0.3.6"
|
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 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 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 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 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 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 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 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 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 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 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 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 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"
|
"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-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-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 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 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 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"
|
"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 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 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 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 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 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"
|
"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_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_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 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 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 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 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 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 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 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"
|
"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 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 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 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 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 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"
|
"checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9"
|
||||||
|
|
|
@ -22,9 +22,14 @@ inlinable_string = "0.1.0"
|
||||||
inkwell = { git = "https://github.com/TheDan64/inkwell", branch = "llvm8-0" }
|
inkwell = { git = "https://github.com/TheDan64/inkwell", branch = "llvm8-0" }
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
lazy_static = "1.4"
|
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]
|
[dev-dependencies]
|
||||||
pretty_assertions = "0.5.1"
|
pretty_assertions = "0.5.1 "
|
||||||
maplit = "1.0.1"
|
maplit = "1.0.1"
|
||||||
indoc = "0.3.3"
|
indoc = "0.3.3"
|
||||||
quickcheck = "0.8"
|
quickcheck = "0.8"
|
||||||
|
|
|
@ -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
|
> 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.
|
> 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
|
## Modules and Shadowing
|
||||||
|
|
||||||
In Elm, my main module (where `main` lives) might begin like this:
|
In Elm, my main module (where `main` lives) might begin like this:
|
||||||
|
|
|
@ -73,26 +73,25 @@ fn can_annotation_help(
|
||||||
Type::Variable(var)
|
Type::Variable(var)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Record(fields) => {
|
Record { fields, ext } => {
|
||||||
let mut field_types = SendMap::default();
|
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);
|
can_assigned_field(&field.value, var_store, rigids, &mut field_types);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a closed record, so the fragment must be {}
|
let ext_type = match ext {
|
||||||
let fragment_type = Type::EmptyRec;
|
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) => {
|
TagUnion { tags, ext } => {
|
||||||
let mut field_types = SendMap::default();
|
panic!(
|
||||||
for field in fields {
|
"TODO canonicalize tag union annotation: {:?} {:?}",
|
||||||
can_assigned_field(&field.value, var_store, rigids, &mut field_types);
|
tags, ext
|
||||||
}
|
);
|
||||||
|
|
||||||
let fragment_type = can_annotation_help(&fragment.value, var_store, rigids);
|
|
||||||
|
|
||||||
Type::Record(field_types, Box::new(fragment_type))
|
|
||||||
}
|
}
|
||||||
SpaceBefore(nested, _) | SpaceAfter(nested, _) => {
|
SpaceBefore(nested, _) | SpaceAfter(nested, _) => {
|
||||||
can_annotation_help(nested, var_store, rigids)
|
can_annotation_help(nested, var_store, rigids)
|
||||||
|
@ -118,11 +117,6 @@ fn can_assigned_field<'a>(
|
||||||
let label = Lowercase::from(field_name.value);
|
let label = Lowercase::from(field_name.value);
|
||||||
field_types.insert(label, field_type);
|
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) => {
|
LabelOnly(loc_field_name) => {
|
||||||
// Interpret { a, b } as { a : a, b : b }
|
// Interpret { a, b } as { a : a, b : b }
|
||||||
let field_name = Lowercase::from(loc_field_name.value);
|
let field_name = Lowercase::from(loc_field_name.value);
|
||||||
|
|
|
@ -46,16 +46,22 @@ pub enum Expr {
|
||||||
symbol_for_lookup: Symbol,
|
symbol_for_lookup: Symbol,
|
||||||
resolved_symbol: Symbol,
|
resolved_symbol: Symbol,
|
||||||
},
|
},
|
||||||
// Pattern Matching
|
// Branching
|
||||||
/// 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.
|
|
||||||
When {
|
When {
|
||||||
cond_var: Variable,
|
cond_var: Variable,
|
||||||
expr_var: Variable,
|
expr_var: Variable,
|
||||||
loc_cond: Box<Located<Expr>>,
|
loc_cond: Box<Located<Expr>>,
|
||||||
branches: Vec<(Located<Pattern>, 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),
|
LetRec(Vec<Def>, Box<Located<Expr>>, Variable),
|
||||||
LetNonRec(Box<Def>, Box<Located<Expr>>, Variable),
|
LetNonRec(Box<Def>, Box<Located<Expr>>, Variable),
|
||||||
|
|
||||||
|
@ -554,8 +560,40 @@ pub fn canonicalize_expr(
|
||||||
Output::default(),
|
Output::default(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ast::Expr::If(_)
|
ast::Expr::If((cond, then_branch, else_branch)) => {
|
||||||
| ast::Expr::MalformedIdent(_)
|
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::MalformedClosure
|
||||||
| ast::Expr::PrecedenceConflict(_, _, _) => {
|
| ast::Expr::PrecedenceConflict(_, _, _) => {
|
||||||
panic!(
|
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 })
|
// A label with no value, e.g. `{ name }` (this is sugar for { name: name })
|
||||||
LabelOnly(_) => {
|
LabelOnly(_) => {
|
||||||
panic!("Somehow a LabelOnly record field was not desugared!");
|
panic!("Somehow a LabelOnly record field was not desugared!");
|
||||||
|
|
|
@ -298,7 +298,6 @@ fn desugar_field<'a>(
|
||||||
spaces,
|
spaces,
|
||||||
desugar_expr(arena, loc_expr),
|
desugar_expr(arena, loc_expr),
|
||||||
),
|
),
|
||||||
OptionalField(_, _, _) => panic!("invalid in expressions"),
|
|
||||||
LabelOnly(loc_str) => {
|
LabelOnly(loc_str) => {
|
||||||
// Desugar { x } into { x: x }
|
// Desugar { x } into { x: x }
|
||||||
let loc_expr = Located {
|
let loc_expr = Located {
|
||||||
|
|
|
@ -17,7 +17,7 @@ use im_rc::Vector;
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum Pattern {
|
pub enum Pattern {
|
||||||
Identifier(Symbol),
|
Identifier(Symbol),
|
||||||
AppliedTag(Variable, Symbol, Vec<Located<Pattern>>),
|
AppliedTag(Variable, Symbol, Vec<(Variable, Located<Pattern>)>),
|
||||||
IntLiteral(i64),
|
IntLiteral(i64),
|
||||||
FloatLiteral(f64),
|
FloatLiteral(f64),
|
||||||
StrLiteral(Box<str>),
|
StrLiteral(Box<str>),
|
||||||
|
@ -94,6 +94,32 @@ pub fn canonicalize_pattern<'a>(
|
||||||
vec![],
|
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 {
|
&FloatLiteral(ref string) => match pattern_type {
|
||||||
WhenBranch => {
|
WhenBranch => {
|
||||||
let float = finish_parsing_float(string)
|
let float = finish_parsing_float(string)
|
||||||
|
@ -344,13 +370,10 @@ pub fn remove_idents(pattern: &ast::Pattern, idents: &mut ImMap<Ident, (Symbol,
|
||||||
QualifiedIdentifier(_name) => {
|
QualifiedIdentifier(_name) => {
|
||||||
panic!("TODO implement QualifiedIdentifier pattern in remove_idents.");
|
panic!("TODO implement QualifiedIdentifier pattern in remove_idents.");
|
||||||
}
|
}
|
||||||
Apply(_, _) => {
|
Apply(_, patterns) => {
|
||||||
panic!("TODO implement Apply pattern in remove_idents.");
|
for loc_pattern in *patterns {
|
||||||
// AppliedVariant(_, Some(loc_args)) => {
|
remove_idents(&loc_pattern.value, idents);
|
||||||
// for loc_arg in loc_args {
|
}
|
||||||
// remove_idents(loc_arg.value, idents);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
RecordDestructure(patterns) => {
|
RecordDestructure(patterns) => {
|
||||||
for loc_pattern in patterns {
|
for loc_pattern in patterns {
|
||||||
|
@ -410,16 +433,10 @@ fn add_idents_from_pattern<'a>(
|
||||||
QualifiedIdentifier(_name) => {
|
QualifiedIdentifier(_name) => {
|
||||||
panic!("TODO implement QualifiedIdentifier pattern.");
|
panic!("TODO implement QualifiedIdentifier pattern.");
|
||||||
}
|
}
|
||||||
Apply(_, _) => {
|
Apply(_tag, patterns) => {
|
||||||
panic!("TODO implement Apply pattern.");
|
for loc_pattern in *patterns {
|
||||||
// &AppliedVariant(_, ref opt_loc_args) => match opt_loc_args {
|
add_idents_from_pattern(&loc_pattern.region, &loc_pattern.value, scope, answer);
|
||||||
// &None => (),
|
}
|
||||||
// &Some(ref loc_args) => {
|
|
||||||
// for loc_arg in loc_args.iter() {
|
|
||||||
// add_idents_from_pattern(loc_arg, scope, answer);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RecordDestructure(patterns) => {
|
RecordDestructure(patterns) => {
|
||||||
|
|
|
@ -48,7 +48,7 @@ fn number_literal_type(module_name: &str, type_name: &str) -> Type {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[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 {
|
Type::Apply {
|
||||||
module_name: module_name.into(),
|
module_name: module_name.into(),
|
||||||
name: type_name.into(),
|
name: type_name.into(),
|
||||||
|
|
|
@ -12,7 +12,7 @@ use crate::constrain::builtins::{
|
||||||
use crate::constrain::pattern::{constrain_pattern, PatternState};
|
use crate::constrain::pattern::{constrain_pattern, PatternState};
|
||||||
use crate::region::{Located, Region};
|
use crate::region::{Located, Region};
|
||||||
use crate::subs::Variable;
|
use crate::subs::Variable;
|
||||||
use crate::types::AnnotationSource::*;
|
use crate::types::AnnotationSource::{self, *};
|
||||||
use crate::types::Constraint::{self, *};
|
use crate::types::Constraint::{self, *};
|
||||||
use crate::types::Expected::{self, *};
|
use crate::types::Expected::{self, *};
|
||||||
use crate::types::PReason;
|
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 {
|
When {
|
||||||
cond_var,
|
cond_var,
|
||||||
expr_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 {
|
pub fn constrain_def(rigids: &Rigids, def: &Def, body_con: Constraint) -> Constraint {
|
||||||
use crate::types::AnnotationSource;
|
|
||||||
|
|
||||||
let expr_var = def.expr_var;
|
let expr_var = def.expr_var;
|
||||||
let expr_type = Type::Variable(expr_var);
|
let expr_type = Type::Variable(expr_var);
|
||||||
|
|
||||||
|
@ -681,7 +762,6 @@ pub fn rec_defs_help(
|
||||||
mut rigid_info: Info,
|
mut rigid_info: Info,
|
||||||
mut flex_info: Info,
|
mut flex_info: Info,
|
||||||
) -> Constraint {
|
) -> Constraint {
|
||||||
use crate::types::AnnotationSource;
|
|
||||||
for def in defs {
|
for def in defs {
|
||||||
let expr_var = def.expr_var;
|
let expr_var = def.expr_var;
|
||||||
let expr_type = Type::Variable(expr_var);
|
let expr_type = Type::Variable(expr_var);
|
||||||
|
|
|
@ -4,7 +4,7 @@ use crate::can::symbol::Symbol;
|
||||||
use crate::collections::SendMap;
|
use crate::collections::SendMap;
|
||||||
use crate::region::{Located, Region};
|
use crate::region::{Located, Region};
|
||||||
use crate::subs::Variable;
|
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 struct PatternState {
|
||||||
pub headers: SendMap<Symbol, Located<Type>>,
|
pub headers: SendMap<Symbol, Located<Type>>,
|
||||||
|
@ -85,8 +85,14 @@ pub fn constrain_pattern(
|
||||||
|
|
||||||
field_types.insert(label.clone(), pat_type.clone());
|
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);
|
constrain_pattern(&loc_guard.value, loc_guard.region, expected, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,12 +105,23 @@ pub fn constrain_pattern(
|
||||||
|
|
||||||
state.constraints.push(record_con);
|
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(
|
let tag_con = Constraint::Pattern(
|
||||||
region,
|
region,
|
||||||
PatternCategory::Ctor(symbol.clone()),
|
PatternCategory::Ctor(symbol.clone()),
|
||||||
Type::TagUnion(
|
Type::TagUnion(
|
||||||
vec![(symbol.clone(), vec![])],
|
vec![(symbol.clone(), argument_types)],
|
||||||
Box::new(Type::Variable(*ext_var)),
|
Box::new(Type::Variable(*ext_var)),
|
||||||
),
|
),
|
||||||
expected,
|
expected,
|
||||||
|
|
477
src/crane/build.rs
Normal file
477
src/crane/build.rs
Normal 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 (¶m, (_, arg_name, var)) in builder.ebb_params(block).iter().zip(args) {
|
||||||
|
let content = subs.get_without_compacting(*var).content;
|
||||||
|
// TODO this type_from_content is duplicated when building this Proc
|
||||||
|
//
|
||||||
|
let layout = Layout::from_content(arena, content, subs)
|
||||||
|
.unwrap_or_else(|()| panic!("TODO generate a runtime error here!"));
|
||||||
|
let expr_type = type_from_layout(cfg, &layout, subs);
|
||||||
|
|
||||||
|
scope.insert(arg_name.clone(), ScopeEntry::Arg { expr_type, param });
|
||||||
|
}
|
||||||
|
|
||||||
|
let body = build_expr(env, &scope, module, &mut builder, &proc.body, procs);
|
||||||
|
|
||||||
|
builder.ins().return_(&[body]);
|
||||||
|
// TODO re-enable this once Switch stops making unsealed
|
||||||
|
// EBBs, e.g. https://docs.rs/cranelift-frontend/0.52.0/src/cranelift_frontend/switch.rs.html#143
|
||||||
|
// builder.seal_block(block);
|
||||||
|
builder.seal_all_blocks();
|
||||||
|
|
||||||
|
builder.finalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
module
|
||||||
|
.define_function(fn_id, ctx)
|
||||||
|
.expect("Defining Cranelift function failed");
|
||||||
|
|
||||||
|
module.clear_context(ctx);
|
||||||
|
}
|
135
src/crane/convert.rs
Normal file
135
src/crane/convert.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,2 @@
|
||||||
pub mod build;
|
pub mod build;
|
||||||
pub mod convert;
|
pub mod convert;
|
||||||
pub mod env;
|
|
114
src/fmt/expr.rs
114
src/fmt/expr.rs
|
@ -1,6 +1,8 @@
|
||||||
use crate::fmt::def::fmt_def;
|
use crate::fmt::def::fmt_def;
|
||||||
use crate::fmt::pattern::fmt_pattern;
|
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::parse::ast::{AssignedField, Base, CommentOrNewline, Expr, Pattern};
|
||||||
use crate::region::Located;
|
use crate::region::Located;
|
||||||
use bumpalo::collections::{String, Vec};
|
use bumpalo::collections::{String, Vec};
|
||||||
|
@ -73,8 +75,9 @@ pub fn fmt_expr<'a>(
|
||||||
}
|
}
|
||||||
buf.push_str("\"\"\"");
|
buf.push_str("\"\"\"");
|
||||||
}
|
}
|
||||||
Int(string) => buf.push_str(string),
|
Int(string) | Float(string) | GlobalTag(string) | PrivateTag(string) => {
|
||||||
Float(string) => buf.push_str(string),
|
buf.push_str(string)
|
||||||
|
}
|
||||||
NonBase10Int {
|
NonBase10Int {
|
||||||
base,
|
base,
|
||||||
string,
|
string,
|
||||||
|
@ -259,22 +262,6 @@ pub fn fmt_field<'a>(
|
||||||
buf.push(' ');
|
buf.push(' ');
|
||||||
fmt_expr(buf, &value.value, indent, apply_needs_parens, true);
|
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) => {
|
LabelOnly(name) => {
|
||||||
if is_multiline {
|
if is_multiline {
|
||||||
newline(buf, indent);
|
newline(buf, indent);
|
||||||
|
@ -419,7 +406,6 @@ pub fn is_multiline_field<'a, Val>(field: &'a AssignedField<'a, Val>) -> bool {
|
||||||
|
|
||||||
match field {
|
match field {
|
||||||
LabeledValue(_, spaces, _) => !spaces.is_empty(),
|
LabeledValue(_, spaces, _) => !spaces.is_empty(),
|
||||||
OptionalField(_, spaces, _) => !spaces.is_empty(),
|
|
||||||
LabelOnly(_) => false,
|
LabelOnly(_) => false,
|
||||||
AssignedField::SpaceBefore(_, _) | AssignedField::SpaceAfter(_, _) => true,
|
AssignedField::SpaceBefore(_, _) | AssignedField::SpaceAfter(_, _) => true,
|
||||||
Malformed(text) => text.chars().any(|c| c == '\n'),
|
Malformed(text) => text.chars().any(|c| c == '\n'),
|
||||||
|
@ -444,29 +430,76 @@ fn fmt_if<'a>(
|
||||||
indent
|
indent
|
||||||
};
|
};
|
||||||
|
|
||||||
|
buf.push_str("if");
|
||||||
|
|
||||||
if is_multiline_condition {
|
if is_multiline_condition {
|
||||||
buf.push_str("if");
|
match &loc_condition.value {
|
||||||
newline(buf, return_indent);
|
Expr::SpaceBefore(expr_below, spaces_above_expr) => {
|
||||||
fmt_expr(buf, &loc_condition.value, return_indent, false, false);
|
fmt_if_spaces(buf, spaces_above_expr.iter(), return_indent);
|
||||||
newline(buf, indent);
|
newline(buf, return_indent);
|
||||||
buf.push_str("then");
|
|
||||||
|
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 {
|
} else {
|
||||||
buf.push_str("if ");
|
buf.push(' ');
|
||||||
fmt_expr(buf, &loc_condition.value, indent, false, true);
|
fmt_expr(buf, &loc_condition.value, indent, false, true);
|
||||||
buf.push_str(" then");
|
buf.push(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buf.push_str("then");
|
||||||
|
|
||||||
if is_multiline {
|
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 {
|
} else {
|
||||||
buf.push_str(" ");
|
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 {
|
if is_multiline {
|
||||||
buf.push('\n');
|
|
||||||
newline(buf, indent);
|
|
||||||
buf.push_str("else");
|
buf.push_str("else");
|
||||||
newline(buf, return_indent);
|
newline(buf, return_indent);
|
||||||
} else {
|
} else {
|
||||||
|
@ -510,7 +543,7 @@ pub fn fmt_closure<'a>(
|
||||||
any_args_printed = true;
|
any_args_printed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt_pattern(buf, &loc_pattern.value, indent, true);
|
fmt_pattern(buf, &loc_pattern.value, indent, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !arguments_are_multiline {
|
if !arguments_are_multiline {
|
||||||
|
@ -543,13 +576,26 @@ pub fn fmt_closure<'a>(
|
||||||
|
|
||||||
pub fn fmt_record<'a>(
|
pub fn fmt_record<'a>(
|
||||||
buf: &mut String<'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>>>>,
|
loc_fields: &'a Vec<'a, Located<AssignedField<'a, Expr<'a>>>>,
|
||||||
indent: u16,
|
indent: u16,
|
||||||
apply_needs_parens: bool,
|
apply_needs_parens: bool,
|
||||||
) {
|
) {
|
||||||
buf.push('{');
|
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
|
let is_multiline = loc_fields
|
||||||
.iter()
|
.iter()
|
||||||
.any(|loc_field| is_multiline_field(&loc_field.value));
|
.any(|loc_field| is_multiline_field(&loc_field.value));
|
||||||
|
|
|
@ -22,15 +22,17 @@ where
|
||||||
{
|
{
|
||||||
use self::CommentOrNewline::*;
|
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 consecutive_newlines = 0;
|
||||||
let mut iter = spaces.peekable();
|
let mut iter = spaces.peekable();
|
||||||
|
|
||||||
|
let mut encountered_comment = false;
|
||||||
|
|
||||||
while let Some(space) = iter.next() {
|
while let Some(space) = iter.next() {
|
||||||
match space {
|
match space {
|
||||||
Newline => {
|
Newline => {
|
||||||
// Only ever print two newlines back to back.
|
if !encountered_comment && (consecutive_newlines < 2) {
|
||||||
// (Two newlines renders as one blank line.)
|
|
||||||
if consecutive_newlines < 2 {
|
|
||||||
if iter.peek() == Some(&&Newline) {
|
if iter.peek() == Some(&&Newline) {
|
||||||
buf.push('\n');
|
buf.push('\n');
|
||||||
} else {
|
} else {
|
||||||
|
@ -45,13 +47,41 @@ where
|
||||||
LineComment(comment) => {
|
LineComment(comment) => {
|
||||||
fmt_comment(buf, comment, indent);
|
fmt_comment(buf, comment, indent);
|
||||||
|
|
||||||
// Reset to 1 because we just printed a \n
|
encountered_comment = true;
|
||||||
consecutive_newlines = 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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.
|
/// 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)
|
pub fn fmt_comments_only<'a, I>(buf: &mut String<'a>, spaces: I, indent: u16)
|
||||||
where
|
where
|
||||||
|
@ -75,3 +105,10 @@ fn fmt_comment<'a>(buf: &mut String<'a>, comment: &'a str, indent: u16) {
|
||||||
|
|
||||||
newline(buf, indent);
|
newline(buf, indent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_comment<'a>(space: &'a CommentOrNewline<'a>) -> bool {
|
||||||
|
match space {
|
||||||
|
CommentOrNewline::Newline => false,
|
||||||
|
CommentOrNewline::LineComment(_) => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
|
||||||
}
|
|
|
@ -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");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -23,13 +23,14 @@ pub mod uniqueness;
|
||||||
pub mod string;
|
pub mod string;
|
||||||
|
|
||||||
pub mod constrain;
|
pub mod constrain;
|
||||||
|
pub mod crane;
|
||||||
pub mod ena;
|
pub mod ena;
|
||||||
pub mod fmt;
|
pub mod fmt;
|
||||||
pub mod gen;
|
|
||||||
pub mod infer;
|
pub mod infer;
|
||||||
pub mod ll;
|
pub mod llvm;
|
||||||
pub mod load;
|
pub mod load;
|
||||||
pub mod module;
|
pub mod module;
|
||||||
|
pub mod mono;
|
||||||
pub mod pretty_print_types;
|
pub mod pretty_print_types;
|
||||||
pub mod solve;
|
pub mod solve;
|
||||||
pub mod subs;
|
pub mod subs;
|
||||||
|
|
396
src/ll/expr.rs
396
src/ll/expr.rs
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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::types::BasicTypeEnum;
|
||||||
use inkwell::values::BasicValueEnum::{self, *};
|
use inkwell::values::BasicValueEnum::{self, *};
|
||||||
use inkwell::values::{FunctionValue, IntValue, PointerValue};
|
use inkwell::values::{FunctionValue, IntValue, PointerValue};
|
||||||
|
@ -5,10 +10,11 @@ use inkwell::{FloatPredicate, IntPredicate};
|
||||||
use inlinable_string::InlinableString;
|
use inlinable_string::InlinableString;
|
||||||
|
|
||||||
use crate::collections::ImMap;
|
use crate::collections::ImMap;
|
||||||
use crate::gen::convert::{content_to_basic_type, layout_to_basic_type};
|
use crate::llvm::convert::{
|
||||||
use crate::gen::env::Env;
|
content_to_basic_type, get_fn_type, layout_to_basic_type, type_from_var,
|
||||||
use crate::ll::expr::{Expr, Proc, Procs};
|
};
|
||||||
use crate::subs::Variable;
|
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
|
/// 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!
|
/// 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>)>;
|
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>(
|
pub fn build_expr<'a, 'ctx, 'env>(
|
||||||
env: &Env<'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
scope: &Scope<'ctx>,
|
scope: &Scope<'ctx>,
|
||||||
parent: FunctionValue<'ctx>,
|
parent: FunctionValue<'ctx>,
|
||||||
expr: &Expr<'a>,
|
expr: &Expr<'a>,
|
||||||
procs: &Procs<'a, 'ctx>,
|
procs: &Procs<'a>,
|
||||||
) -> BasicValueEnum<'ctx> {
|
) -> BasicValueEnum<'ctx> {
|
||||||
use crate::ll::expr::Expr::*;
|
use crate::mono::expr::Expr::*;
|
||||||
|
|
||||||
match expr {
|
match expr {
|
||||||
Int(num) => env.context.i64_type().const_int(*num as u64, false).into(),
|
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,
|
pass,
|
||||||
fail,
|
fail,
|
||||||
ret_var,
|
ret_var,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
let cond = Cond2 {
|
let cond = Branch2 {
|
||||||
cond_lhs,
|
cond_lhs,
|
||||||
cond_rhs,
|
cond_rhs,
|
||||||
pass,
|
pass,
|
||||||
|
@ -47,11 +62,29 @@ pub fn build_expr<'a, 'ctx, 'env>(
|
||||||
ret_var: *ret_var,
|
ret_var: *ret_var,
|
||||||
};
|
};
|
||||||
|
|
||||||
build_cond(env, scope, parent, cond, procs)
|
build_branch2(env, scope, parent, cond, procs)
|
||||||
}
|
}
|
||||||
Branches { .. } => {
|
Branches { .. } => {
|
||||||
panic!("TODO build_branches(env, scope, parent, cond_lhs, branches, procs)");
|
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) => {
|
Store(ref stores, ref ret) => {
|
||||||
let mut scope = im_rc::HashMap::clone(scope);
|
let mut scope = im_rc::HashMap::clone(scope);
|
||||||
let subs = &env.subs;
|
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
|
// 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
|
// 2. pre-register Bool.or using module.add_function, and see if LLVM inlines it
|
||||||
|
// 3. intern all these strings
|
||||||
if name == "Bool.or" {
|
if name == "Bool.or" {
|
||||||
panic!("TODO create a phi node for ||");
|
panic!("TODO create a phi node for ||");
|
||||||
} else if name == "Bool.and" {
|
} else if name == "Bool.and" {
|
||||||
panic!("TODO create a phi node for &&");
|
panic!("TODO create a phi node for &&");
|
||||||
} else {
|
} 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() {
|
for arg in args.iter() {
|
||||||
arg_vals.push(build_expr(env, scope, parent, arg, procs));
|
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");
|
let call = env.builder.build_call(fn_val, arg_vals.as_slice(), "tmp");
|
||||||
|
|
||||||
call.try_as_basic_value()
|
call.try_as_basic_value().left().unwrap_or_else(|| {
|
||||||
.left()
|
panic!("LLVM error: Invalid call by name for name {:?}", name)
|
||||||
.unwrap_or_else(|| panic!("LLVM error: Invalid call by name."))
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CallByPointer(ref _ptr, ref args) => {
|
FunctionPointer(ref fn_name) => {
|
||||||
let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity(args.len());
|
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() {
|
for arg in args.iter() {
|
||||||
arg_vals.push(build_expr(env, scope, parent, arg, procs));
|
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) {
|
call.try_as_basic_value()
|
||||||
// BasicValueEnum::PointerValue(ptr) => {
|
.left()
|
||||||
// env.builder.build_call(ptr, arg_vals.as_slice(), "tmp")
|
.unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer."))
|
||||||
// }
|
|
||||||
// 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."))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Load(name) => match scope.get(name) {
|
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),
|
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_lhs: &'a Expr<'a>,
|
||||||
cond_rhs: &'a Expr<'a>,
|
cond_rhs: &'a Expr<'a>,
|
||||||
pass: &'a Expr<'a>,
|
pass: &'a Expr<'a>,
|
||||||
|
@ -155,12 +200,12 @@ struct Cond2<'a> {
|
||||||
ret_var: Variable,
|
ret_var: Variable,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_cond<'a, 'ctx, 'env>(
|
fn build_branch2<'a, 'ctx, 'env>(
|
||||||
env: &Env<'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
scope: &Scope<'ctx>,
|
scope: &Scope<'ctx>,
|
||||||
parent: FunctionValue<'ctx>,
|
parent: FunctionValue<'ctx>,
|
||||||
cond: Cond2<'a>,
|
cond: Branch2<'a>,
|
||||||
procs: &Procs<'a, 'ctx>,
|
procs: &Procs<'a>,
|
||||||
) -> BasicValueEnum<'ctx> {
|
) -> BasicValueEnum<'ctx> {
|
||||||
let builder = env.builder;
|
let builder = env.builder;
|
||||||
let context = env.context;
|
let context = env.context;
|
||||||
|
@ -201,88 +246,127 @@ fn build_cond<'a, 'ctx, 'env>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fn build_branches<'a, 'ctx, 'env>(
|
struct SwitchArgs<'a, 'ctx> {
|
||||||
// env: &Env<'ctx, 'env>,
|
pub cond_expr: &'a Expr<'a>,
|
||||||
// scope: &Scope<'ctx>,
|
pub cond_var: Variable,
|
||||||
// parent: FunctionValue<'ctx>,
|
pub branches: &'a [(u64, Expr<'a>)],
|
||||||
// cond_lhs: &'a Expr<'a>,
|
pub default_branch: &'a Expr<'a>,
|
||||||
// branches: &'a [(Expr<'a>, Expr<'a>, Expr<'a>)],
|
pub ret_type: BasicTypeEnum<'ctx>,
|
||||||
// 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
|
|
||||||
// )
|
|
||||||
// });
|
|
||||||
|
|
||||||
// for (cond_rhs, cond_pass, cond_else) in branches {
|
fn build_switch<'a, 'ctx, 'env>(
|
||||||
// let rhs = build_expr(env, scope, parent, cond_rhs, procs);
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
// let pass = build_expr(env, scope, parent, cond_pass, procs);
|
scope: &Scope<'ctx>,
|
||||||
// let fail = build_expr(env, scope, parent, cond_else, procs);
|
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 {
|
let cont_block = context.append_basic_block(parent, "cont");
|
||||||
// lhs,
|
|
||||||
// rhs,
|
|
||||||
// pass,
|
|
||||||
// fail,
|
|
||||||
// ret_type,
|
|
||||||
// };
|
|
||||||
|
|
||||||
// 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
|
// TODO trim down these arguments
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn build_phi2<'a, 'ctx, 'env>(
|
fn build_phi2<'a, 'ctx, 'env>(
|
||||||
env: &Env<'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
scope: &Scope<'ctx>,
|
scope: &Scope<'ctx>,
|
||||||
parent: FunctionValue<'ctx>,
|
parent: FunctionValue<'ctx>,
|
||||||
comparison: IntValue<'ctx>,
|
comparison: IntValue<'ctx>,
|
||||||
pass: &'a Expr<'a>,
|
pass: &'a Expr<'a>,
|
||||||
fail: &'a Expr<'a>,
|
fail: &'a Expr<'a>,
|
||||||
ret_type: BasicTypeEnum<'ctx>,
|
ret_type: BasicTypeEnum<'ctx>,
|
||||||
procs: &Procs<'a, 'ctx>,
|
procs: &Procs<'a>,
|
||||||
) -> BasicValueEnum<'ctx> {
|
) -> BasicValueEnum<'ctx> {
|
||||||
let builder = env.builder;
|
let builder = env.builder;
|
||||||
let context = env.context;
|
let context = env.context;
|
||||||
|
|
||||||
// build branch
|
// build blocks
|
||||||
let then_bb = context.append_basic_block(parent, "then");
|
let then_block = context.append_basic_block(parent, "then");
|
||||||
let else_bb = context.append_basic_block(parent, "else");
|
let else_block = context.append_basic_block(parent, "else");
|
||||||
let cont_bb = context.append_basic_block(parent, "branchcont");
|
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
|
// 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);
|
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
|
// 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);
|
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
|
// emit merge block
|
||||||
builder.position_at_end(&cont_bb);
|
builder.position_at_end(&cont_block);
|
||||||
|
|
||||||
let phi = builder.build_phi(ret_type, "branch");
|
let phi = builder.build_phi(ret_type, "branch");
|
||||||
|
|
||||||
phi.add_incoming(&[
|
phi.add_incoming(&[
|
||||||
(&Into::<BasicValueEnum>::into(then_val), &then_bb),
|
(&Into::<BasicValueEnum>::into(then_val), &then_block),
|
||||||
(&Into::<BasicValueEnum>::into(else_val), &else_bb),
|
(&Into::<BasicValueEnum>::into(else_val), &else_block),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
phi.as_basic_value()
|
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.
|
/// Creates a new stack allocation instruction in the entry block of the function.
|
||||||
pub fn create_entry_block_alloca<'ctx>(
|
pub fn create_entry_block_alloca<'a, 'ctx>(
|
||||||
env: &Env<'ctx, '_>,
|
env: &Env<'a, 'ctx, '_>,
|
||||||
parent: FunctionValue<'_>,
|
parent: FunctionValue<'_>,
|
||||||
basic_type: BasicTypeEnum<'ctx>,
|
basic_type: BasicTypeEnum<'ctx>,
|
||||||
name: &str,
|
name: &str,
|
||||||
|
@ -319,38 +403,46 @@ pub fn create_entry_block_alloca<'ctx>(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_proc<'a, 'ctx, 'env>(
|
pub fn build_proc<'a, 'ctx, 'env>(
|
||||||
env: &Env<'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
scope: &Scope<'ctx>,
|
|
||||||
name: InlinableString,
|
name: InlinableString,
|
||||||
proc: Proc<'a>,
|
proc: Proc<'a>,
|
||||||
procs: &Procs<'a, 'ctx>,
|
procs: &Procs<'a>,
|
||||||
) {
|
) -> FunctionValue<'ctx> {
|
||||||
let args = proc.args;
|
let args = proc.args;
|
||||||
let mut arg_names = Vec::new();
|
let arena = env.arena;
|
||||||
let mut arg_basic_types = Vec::with_capacity(args.len());
|
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() {
|
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_basic_types.push(arg_type);
|
||||||
arg_names.push(name);
|
arg_names.push(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve the function value from the module
|
let fn_type = get_fn_type(&ret_type, &arg_basic_types);
|
||||||
let fn_val = env.module.get_function(&name).unwrap_or_else(|| {
|
|
||||||
panic!(
|
let fn_val = env
|
||||||
"Function {:?} should have been registered in the LLVM module, but it was not!",
|
.module
|
||||||
name
|
.add_function(&name, fn_type, Some(Linkage::Private));
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add a basic block for the entry point
|
// 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;
|
let builder = env.builder;
|
||||||
|
|
||||||
builder.position_at_end(&entry);
|
builder.position_at_end(&entry);
|
||||||
|
|
||||||
let mut scope = scope.clone();
|
let mut scope = ImMap::default();
|
||||||
|
|
||||||
// Add args to scope
|
// Add args to scope
|
||||||
for ((arg_val, arg_type), (_, arg_name, var)) in
|
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));
|
builder.build_return(Some(&body));
|
||||||
|
|
||||||
if fn_val.verify(PRINT_FN_VERIFICATION_OUTPUT) {
|
fn_val
|
||||||
// TODO call pass_manager.run_on(&fn_val) to optimize it!
|
}
|
||||||
} else {
|
|
||||||
|
pub fn verify_fn(fn_val: FunctionValue<'_>) {
|
||||||
|
if !fn_val.verify(PRINT_FN_VERIFICATION_OUTPUT) {
|
||||||
unsafe {
|
unsafe {
|
||||||
fn_val.delete();
|
fn_val.delete();
|
||||||
}
|
}
|
|
@ -3,11 +3,26 @@ use inkwell::types::BasicTypeEnum::{self, *};
|
||||||
use inkwell::types::{BasicType, FunctionType};
|
use inkwell::types::{BasicType, FunctionType};
|
||||||
use inkwell::AddressSpace;
|
use inkwell::AddressSpace;
|
||||||
|
|
||||||
use crate::ll::layout::Layout;
|
use crate::mono::layout::Layout;
|
||||||
use crate::subs::FlatType::*;
|
use crate::subs::FlatType::*;
|
||||||
use crate::subs::{Content, Subs};
|
use crate::subs::{Content, Subs, Variable};
|
||||||
use crate::types;
|
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>(
|
pub fn content_to_basic_type<'ctx>(
|
||||||
content: &Content,
|
content: &Content,
|
||||||
subs: &Subs,
|
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)?);
|
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 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),
|
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?
|
/// 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>,
|
bt_enum: &BasicTypeEnum<'ctx>,
|
||||||
arg_types: &[BasicTypeEnum<'ctx>],
|
arg_types: &[BasicTypeEnum<'ctx>],
|
||||||
) -> FunctionType<'ctx> {
|
) -> FunctionType<'ctx> {
|
||||||
|
@ -118,8 +134,8 @@ pub fn layout_to_basic_type<'ctx>(
|
||||||
_subs: &Subs,
|
_subs: &Subs,
|
||||||
context: &'ctx Context,
|
context: &'ctx Context,
|
||||||
) -> BasicTypeEnum<'ctx> {
|
) -> BasicTypeEnum<'ctx> {
|
||||||
use crate::ll::layout::Builtin::*;
|
use crate::mono::layout::Builtin::*;
|
||||||
use crate::ll::layout::Layout::*;
|
use crate::mono::layout::Layout::*;
|
||||||
|
|
||||||
match layout {
|
match layout {
|
||||||
FunctionPointer(_arg_layouts, _ret_layout) => {
|
FunctionPointer(_arg_layouts, _ret_layout) => {
|
2
src/llvm/mod.rs
Normal file
2
src/llvm/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod build;
|
||||||
|
pub mod convert;
|
513
src/mono/expr.rs
Normal file
513
src/mono/expr.rs
Normal 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."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,8 @@
|
||||||
use crate::subs::{Content, FlatType, Subs};
|
use crate::subs::{Content, FlatType, Subs, Variable};
|
||||||
use crate::types;
|
use crate::types;
|
||||||
use bumpalo::collections::Vec;
|
use bumpalo::collections::Vec;
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
|
use cranelift_codegen::isa::TargetFrontendConfig;
|
||||||
use inlinable_string::InlinableString;
|
use inlinable_string::InlinableString;
|
||||||
|
|
||||||
/// Types for code gen must be monomorphic. No type variables allowed!
|
/// Types for code gen must be monomorphic. No type variables allowed!
|
||||||
|
@ -24,6 +25,11 @@ pub enum Builtin<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Layout<'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.
|
/// 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
|
/// Panics if given a FlexVar or RigidVar, since those should have been
|
||||||
/// monomorphized away already!
|
/// monomorphized away already!
|
||||||
|
@ -41,6 +47,50 @@ impl<'a> Layout<'a> {
|
||||||
Error => Err(()),
|
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>(
|
fn layout_from_flat_type<'a>(
|
||||||
|
@ -102,6 +152,9 @@ fn layout_from_flat_type<'a>(
|
||||||
EmptyTagUnion => {
|
EmptyTagUnion => {
|
||||||
panic!("TODO make Layout for empty Tag Union");
|
panic!("TODO make Layout for empty Tag Union");
|
||||||
}
|
}
|
||||||
|
Boolean(_) => {
|
||||||
|
panic!("TODO make Layout for Boolean");
|
||||||
|
}
|
||||||
Erroneous(_) => Err(()),
|
Erroneous(_) => Err(()),
|
||||||
EmptyRecord => Ok(Layout::Struct(&[])),
|
EmptyRecord => Ok(Layout::Struct(&[])),
|
||||||
}
|
}
|
|
@ -217,15 +217,20 @@ pub enum TypeAnnotation<'a> {
|
||||||
/// A bound type variable, e.g. `a` in `(a -> a)`
|
/// A bound type variable, e.g. `a` in `(a -> a)`
|
||||||
BoundVariable(&'a str),
|
BoundVariable(&'a str),
|
||||||
|
|
||||||
/// A plain record, e.g. `{ name: String, email: Email }`
|
Record {
|
||||||
Record(Vec<'a, Loc<AssignedField<'a, TypeAnnotation<'a>>>>),
|
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`
|
/// A tag union, e.g. `[
|
||||||
RecordFragment(
|
TagUnion {
|
||||||
Vec<'a, Loc<AssignedField<'a, TypeAnnotation<'a>>>>,
|
tags: &'a [Loc<Tag<'a>>],
|
||||||
// the fragment type variable, e.g. the `r` in `{ name: String }...r`
|
/// The row type variable in an open tag union, e.g. the `a` in `[ Foo, Bar ]a`.
|
||||||
&'a Loc<TypeAnnotation<'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 *)
|
/// The `*` type variable, e.g. in (List *)
|
||||||
Wildcard,
|
Wildcard,
|
||||||
|
@ -238,14 +243,31 @@ pub enum TypeAnnotation<'a> {
|
||||||
Malformed(&'a str),
|
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)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum AssignedField<'a, Val> {
|
pub enum AssignedField<'a, Val> {
|
||||||
// Both a label and a value, e.g. `{ name: "blah" }`
|
// Both a label and a value, e.g. `{ name: "blah" }`
|
||||||
LabeledValue(Loc<&'a str>, &'a [CommentOrNewline<'a>], &'a Loc<Val>),
|
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 })
|
// A label with no value, e.g. `{ name }` (this is sugar for { name: name })
|
||||||
LabelOnly(Loc<&'a str>),
|
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> {
|
impl<'a> Spaceable<'a> for Def<'a> {
|
||||||
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
|
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
|
||||||
Def::SpaceBefore(self, spaces)
|
Def::SpaceBefore(self, spaces)
|
||||||
|
|
|
@ -49,7 +49,7 @@ impl<'a> Ident<'a> {
|
||||||
/// Sometimes we may want to check for those later in the process, and give
|
/// 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.
|
/// more contextually-aware error messages than "unexpected `if`" or the like.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn parse_into<'a, I>(
|
pub fn parse_ident<'a, I>(
|
||||||
arena: &'a Bump,
|
arena: &'a Bump,
|
||||||
chars: &mut I,
|
chars: &mut I,
|
||||||
state: State<'a>,
|
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;
|
let mut next_char = None;
|
||||||
|
|
||||||
while let Some(ch) = chars.next() {
|
while let Some(ch) = chars.next() {
|
||||||
|
@ -310,7 +310,7 @@ where
|
||||||
pub fn ident<'a>() -> impl Parser<'a, Ident<'a>> {
|
pub fn ident<'a>() -> impl Parser<'a, Ident<'a>> {
|
||||||
move |arena: &'a Bump, state: State<'a>| {
|
move |arena: &'a Bump, state: State<'a>| {
|
||||||
// Discard next_char; we don't need it.
|
// 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))
|
Ok((string, state))
|
||||||
}
|
}
|
||||||
|
|
167
src/parse/mod.rs
167
src/parse/mod.rs
|
@ -359,7 +359,6 @@ pub fn assigned_expr_field_to_pattern<'a>(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AssignedField::OptionalField(_, _, _) => panic!("invalid in literals"),
|
|
||||||
AssignedField::LabelOnly(name) => Pattern::Identifier(name.value),
|
AssignedField::LabelOnly(name) => Pattern::Identifier(name.value),
|
||||||
AssignedField::SpaceBefore(nested, spaces) => Pattern::SpaceBefore(
|
AssignedField::SpaceBefore(nested, spaces) => Pattern::SpaceBefore(
|
||||||
arena.alloc(assigned_expr_field_to_pattern(arena, nested)?),
|
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::LabelOnly(name) => Located::at(name.region, Pattern::Identifier(name.value)),
|
||||||
AssignedField::SpaceBefore(nested, spaces) => {
|
AssignedField::SpaceBefore(nested, spaces) => {
|
||||||
let can_nested = assigned_pattern_field_to_pattern(arena, nested, backup_region)?;
|
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(
|
space0_after(
|
||||||
between!(
|
between!(
|
||||||
char('('),
|
char('('),
|
||||||
space0_around(loc!(pattern(min_indent)), min_indent),
|
space0_around(loc_pattern(min_indent), min_indent),
|
||||||
char(')')
|
char(')')
|
||||||
),
|
),
|
||||||
min_indent,
|
min_indent,
|
||||||
|
@ -755,27 +751,36 @@ fn parse_closure_param<'a>(
|
||||||
// e.g. \User.UserId userId -> ...
|
// e.g. \User.UserId userId -> ...
|
||||||
between!(
|
between!(
|
||||||
char('('),
|
char('('),
|
||||||
space0_around(loc!(pattern(min_indent)), min_indent),
|
space0_around(loc_pattern(min_indent), min_indent),
|
||||||
char(')')
|
char(')')
|
||||||
),
|
),
|
||||||
// The least common, but still allowed, e.g. \Foo -> ...
|
// The least common, but still allowed, e.g. \Foo -> ...
|
||||||
loc!(tag_pattern())
|
loc_tag_pattern(min_indent)
|
||||||
)
|
)
|
||||||
.parse(arena, state)
|
.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!(
|
one_of!(
|
||||||
underscore_pattern(),
|
loc_parenthetical_pattern(min_indent),
|
||||||
tag_pattern(),
|
loc!(underscore_pattern()),
|
||||||
ident_pattern(),
|
loc_tag_pattern(min_indent),
|
||||||
record_destructure(min_indent),
|
loc!(ident_pattern()),
|
||||||
string_pattern(),
|
loc!(record_destructure(min_indent)),
|
||||||
int_pattern()
|
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| {
|
map_with_arena!(number_literal(), |arena, expr| {
|
||||||
expr_to_pattern(arena, &expr).unwrap()
|
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>> {
|
fn record_destructure<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>> {
|
||||||
then(
|
then(
|
||||||
record_without_update!(loc!(pattern(min_indent)), min_indent),
|
record_without_update!(loc_pattern(min_indent), min_indent),
|
||||||
move |arena, state, assigned_fields| {
|
move |arena, state, assigned_fields| {
|
||||||
let mut patterns = Vec::with_capacity_in(assigned_fields.len(), arena);
|
let mut patterns = Vec::with_capacity_in(assigned_fields.len(), arena);
|
||||||
for assigned_field in assigned_fields {
|
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>> {
|
fn loc_tag_pattern<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>>> {
|
||||||
one_of!(
|
map_with_arena!(
|
||||||
map!(private_tag(), Pattern::PrivateTag),
|
and!(
|
||||||
map!(global_tag(), Pattern::GlobalTag)
|
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))
|
Ok((branches, state))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn alternative_patterns<'a>(min_indent: u16) -> impl Parser<'a, Vec<'a, Located<Pattern<'a>>>> {
|
fn alternative_patterns<'a>(min_indent: u16) -> impl Parser<'a, Vec<'a, Located<Pattern<'a>>>> {
|
||||||
sep_by1(
|
sep_by1(
|
||||||
char('|'),
|
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(
|
then(
|
||||||
// Spaces, then '-', then *not* more spaces.
|
// Spaces, then '-', then *not* more spaces.
|
||||||
not_followed_by(
|
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')),
|
one_of!(char(' '), char('#'), char('\n')),
|
||||||
),
|
),
|
||||||
move |arena, state, (spaces, loc_minus_char)| {
|
move |arena, state, (spaces, num_or_minus_char)| {
|
||||||
let region = loc_minus_char.region;
|
match num_or_minus_char {
|
||||||
let loc_op = Located {
|
Either::First(loc_num_literal) => Ok((loc_num_literal, state)),
|
||||||
region,
|
Either::Second(Located { region, .. }) => {
|
||||||
value: UnaryOp::Negate,
|
let loc_op = Located {
|
||||||
};
|
region,
|
||||||
|
value: UnaryOp::Negate,
|
||||||
|
};
|
||||||
|
|
||||||
// Continue parsing the function arg as normal.
|
// Continue parsing the function arg as normal.
|
||||||
let (loc_expr, state) = loc_function_arg(min_indent).parse(arena, state)?;
|
let (loc_expr, state) = loc_function_arg(min_indent).parse(arena, state)?;
|
||||||
let region = Region {
|
let region = Region {
|
||||||
start_col: loc_op.region.start_col,
|
start_col: loc_op.region.start_col,
|
||||||
start_line: loc_op.region.start_line,
|
start_line: loc_op.region.start_line,
|
||||||
end_col: loc_expr.region.end_col,
|
end_col: loc_expr.region.end_col,
|
||||||
end_line: loc_expr.region.end_line,
|
end_line: loc_expr.region.end_line,
|
||||||
};
|
};
|
||||||
let value = Expr::UnaryOp(arena.alloc(loc_expr), loc_op);
|
let value = Expr::UnaryOp(arena.alloc(loc_expr), loc_op);
|
||||||
let loc_expr = Located {
|
let loc_expr = Located {
|
||||||
// Start from where the unary op started,
|
// Start from where the unary op started,
|
||||||
// and end where its argument expr ended.
|
// and end where its argument expr ended.
|
||||||
// This is relevant in case (for example)
|
// This is relevant in case (for example)
|
||||||
// we have an expression involving parens,
|
// we have an expression involving parens,
|
||||||
// for example `-(foo bar)`
|
// for example `-(foo bar)`
|
||||||
region,
|
region,
|
||||||
value,
|
value,
|
||||||
};
|
};
|
||||||
|
|
||||||
// spaces can be empy if it's all space characters (no newlines or comments).
|
// spaces can be empy if it's all space characters (no newlines or comments).
|
||||||
let value = if spaces.is_empty() {
|
let value = if spaces.is_empty() {
|
||||||
loc_expr.value
|
loc_expr.value
|
||||||
} else {
|
} else {
|
||||||
Expr::SpaceBefore(arena.alloc(loc_expr.value), spaces)
|
Expr::SpaceBefore(arena.alloc(loc_expr.value), spaces)
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
Located {
|
Located {
|
||||||
region: loc_expr.region,
|
region: loc_expr.region,
|
||||||
value,
|
value,
|
||||||
},
|
},
|
||||||
state,
|
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 -> ...
|
/// This is mainly for matching tags in closure params, e.g. \@Foo -> ...
|
||||||
fn private_tag<'a>() -> impl Parser<'a, &'a str> {
|
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 -> ...
|
/// This is mainly for matching tags in closure params, e.g. \Foo -> ...
|
||||||
|
|
|
@ -905,9 +905,6 @@ macro_rules! record_field {
|
||||||
// You must have a field name, e.g. "email"
|
// You must have a field name, e.g. "email"
|
||||||
let (loc_label, state) = loc!(lowercase_ident()).parse(arena, state)?;
|
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)?;
|
let (spaces, state) = space0($min_indent).parse(arena, state)?;
|
||||||
// Having a value is optional; both `{ email }` and `{ email: blah }` work.
|
// Having a value is optional; both `{ email }` and `{ email: blah }` work.
|
||||||
// (This is true in both literals and types.)
|
// (This is true in both literals and types.)
|
||||||
|
@ -917,27 +914,24 @@ macro_rules! record_field {
|
||||||
))
|
))
|
||||||
.parse(arena, state)?;
|
.parse(arena, state)?;
|
||||||
|
|
||||||
let answer = match (opt_loc_val, opt_field) {
|
let answer = match opt_loc_val {
|
||||||
(Some(loc_val), None) => LabeledValue(loc_label, spaces, arena.alloc(loc_val)),
|
Some(loc_val) => LabeledValue(loc_label, spaces, arena.alloc(loc_val)),
|
||||||
(Some(loc_val), Some(_)) => OptionalField(loc_label, spaces, arena.alloc(loc_val)),
|
|
||||||
// If no value was provided, record it as a Var.
|
// If no value was provided, record it as a Var.
|
||||||
// Canonicalize will know what to do with a Var later.
|
// Canonicalize will know what to do with a Var later.
|
||||||
(None, None) => {
|
None => {
|
||||||
if !spaces.is_empty() {
|
if !spaces.is_empty() {
|
||||||
SpaceAfter(arena.alloc(LabelOnly(loc_label)), spaces)
|
SpaceAfter(arena.alloc(LabelOnly(loc_label)), spaces)
|
||||||
} else {
|
} else {
|
||||||
LabelOnly(loc_label)
|
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))
|
Ok((answer, state))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! record_without_update {
|
macro_rules! record_without_update {
|
||||||
($val_parser:expr, $min_indent:expr) => {
|
($val_parser:expr, $min_indent:expr) => {
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
use crate::collections::arena_join;
|
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::blankspace::{space0_around, space0_before, space1_before};
|
||||||
use crate::parse::parser::{
|
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 crate::region::Located;
|
||||||
use bumpalo::collections::string::String;
|
use bumpalo::collections::string::String;
|
||||||
use bumpalo::collections::vec::Vec;
|
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)
|
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>>> {
|
pub fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>> {
|
||||||
one_of!(
|
one_of!(
|
||||||
// The `*` type variable, e.g. in (List *) Wildcard,
|
// 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_parenthetical_type(min_indent),
|
||||||
loc!(record_type(min_indent)),
|
loc!(record_type(min_indent)),
|
||||||
|
loc!(tag_union!(min_indent)),
|
||||||
loc!(applied_type(min_indent)),
|
loc!(applied_type(min_indent)),
|
||||||
loc!(parse_type_variable)
|
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)]
|
#[inline(always)]
|
||||||
fn record_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>> {
|
fn record_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>> {
|
||||||
use crate::parse::type_annotation::TypeAnnotation::*;
|
use crate::parse::type_annotation::TypeAnnotation::*;
|
||||||
|
|
||||||
map_with_arena!(
|
map!(
|
||||||
and!(
|
and!(
|
||||||
record_without_update!(
|
record_without_update!(
|
||||||
move |arena, state| term(min_indent).parse(arena, state),
|
move |arena, state| term(min_indent).parse(arena, state),
|
||||||
min_indent
|
min_indent
|
||||||
),
|
),
|
||||||
optional(skip_first!(
|
optional(
|
||||||
// This could be a record fragment, e.g. `{ name: String }...r`
|
// This could be an open record, e.g. `{ name: Str }r`
|
||||||
string("..."),
|
move |arena, state| allocated(term(min_indent)).parse(arena, state)
|
||||||
move |arena, state| term(min_indent).parse(arena, state)
|
)
|
||||||
))
|
|
||||||
),
|
),
|
||||||
|arena: &'a Bump, (rec, opt_bound_var)| match opt_bound_var {
|
|(fields, ext): (
|
||||||
None => Record(rec),
|
Vec<'a, Located<AssignedField<'a, TypeAnnotation<'a>>>>,
|
||||||
Some(loc_bound_var) => RecordFragment(rec, arena.alloc(loc_bound_var)),
|
Option<&'a Located<TypeAnnotation<'a>>>,
|
||||||
|
)| Record {
|
||||||
|
fields: fields.into_bump_slice(),
|
||||||
|
ext
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ use crate::can::ident::{Lowercase, ModuleName, Uppercase};
|
||||||
use crate::collections::{MutMap, MutSet};
|
use crate::collections::{MutMap, MutSet};
|
||||||
use crate::subs::{Content, FlatType, Subs, Variable};
|
use crate::subs::{Content, FlatType, Subs, Variable};
|
||||||
use crate::types::{self, name_type_var};
|
use crate::types::{self, name_type_var};
|
||||||
|
use crate::uniqueness::boolean_algebra::Bool;
|
||||||
|
|
||||||
static WILDCARD: &str = "*";
|
static WILDCARD: &str = "*";
|
||||||
static EMPTY_RECORD: &str = "{}";
|
static EMPTY_RECORD: &str = "{}";
|
||||||
|
@ -104,6 +105,11 @@ fn find_names_needed(
|
||||||
|
|
||||||
find_names_needed(ext_var, subs, roots, root_appearances, names_taken);
|
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) => {
|
RigidVar(name) => {
|
||||||
// User-defined names are already taken.
|
// User-defined names are already taken.
|
||||||
// We must not accidentally generate names that collide with them!
|
// 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),
|
EmptyTagUnion => buf.push_str(EMPTY_TAG_UNION),
|
||||||
Func(args, ret) => write_fn(args, ret, subs, buf, parens),
|
Func(args, ret) => write_fn(args, ret, subs, buf, parens),
|
||||||
Record(fields, ext_var) => {
|
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() {
|
if fields.is_empty() {
|
||||||
buf.push_str(EMPTY_RECORD)
|
buf.push_str(EMPTY_RECORD)
|
||||||
} else {
|
} 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) => {
|
Erroneous(problem) => {
|
||||||
buf.push_str(&format!("<Type Mismatch: {:?}>", 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(
|
fn write_apply(
|
||||||
module_name: ModuleName,
|
module_name: ModuleName,
|
||||||
type_name: Uppercase,
|
type_name: Uppercase,
|
||||||
|
|
23
src/solve.rs
23
src/solve.rs
|
@ -7,6 +7,7 @@ use crate::types::Constraint::{self, *};
|
||||||
use crate::types::Problem;
|
use crate::types::Problem;
|
||||||
use crate::types::Type::{self, *};
|
use crate::types::Type::{self, *};
|
||||||
use crate::unify::{unify, Unified};
|
use crate::unify::{unify, Unified};
|
||||||
|
use crate::uniqueness::boolean_algebra;
|
||||||
|
|
||||||
type Env = ImMap<Symbol, Variable>;
|
type Env = ImMap<Symbol, Variable>;
|
||||||
|
|
||||||
|
@ -401,6 +402,13 @@ fn type_to_variable(
|
||||||
|
|
||||||
register(subs, rank, pools, content)
|
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) => {
|
Function(args, ret_type) => {
|
||||||
let mut arg_vars = Vec::with_capacity(args.len());
|
let mut arg_vars = Vec::with_capacity(args.len());
|
||||||
|
|
||||||
|
@ -673,6 +681,15 @@ fn adjust_rank_content(
|
||||||
rank
|
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,
|
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))
|
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)));
|
subs.set(copy, make_descriptor(Structure(new_flat_type)));
|
||||||
|
|
38
src/subs.rs
38
src/subs.rs
|
@ -2,7 +2,9 @@ use crate::can::ident::{Lowercase, ModuleName, Uppercase};
|
||||||
use crate::can::symbol::Symbol;
|
use crate::can::symbol::Symbol;
|
||||||
use crate::collections::{ImMap, ImSet, MutSet, SendMap};
|
use crate::collections::{ImMap, ImSet, MutSet, SendMap};
|
||||||
use crate::ena::unify::{InPlace, UnificationTable, UnifyKey};
|
use crate::ena::unify::{InPlace, UnificationTable, UnifyKey};
|
||||||
|
use crate::types;
|
||||||
use crate::types::{name_type_var, ErrorType, Problem, RecordFieldLabel, TypeExt};
|
use crate::types::{name_type_var, ErrorType, Problem, RecordFieldLabel, TypeExt};
|
||||||
|
use crate::uniqueness::boolean_algebra;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
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);
|
pub struct Variable(usize);
|
||||||
|
|
||||||
impl Variable {
|
impl Variable {
|
||||||
|
@ -145,6 +147,10 @@ impl Variable {
|
||||||
const NULL: Variable = Variable(0);
|
const NULL: Variable = Variable(0);
|
||||||
|
|
||||||
const FIRST_USER_SPACE_VAR: Variable = Variable(1);
|
const FIRST_USER_SPACE_VAR: Variable = Variable(1);
|
||||||
|
|
||||||
|
pub fn unsafe_debug_variable(v: usize) -> Self {
|
||||||
|
Variable(v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<OptVariable> for Variable {
|
impl Into<OptVariable> for Variable {
|
||||||
|
@ -422,6 +428,18 @@ pub enum Content {
|
||||||
Error,
|
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)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum FlatType {
|
pub enum FlatType {
|
||||||
Apply {
|
Apply {
|
||||||
|
@ -435,6 +453,7 @@ pub enum FlatType {
|
||||||
Erroneous(Problem),
|
Erroneous(Problem),
|
||||||
EmptyRecord,
|
EmptyRecord,
|
||||||
EmptyTagUnion,
|
EmptyTagUnion,
|
||||||
|
Boolean(boolean_algebra::Bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
|
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
|
||||||
|
@ -478,6 +497,10 @@ fn occurs(subs: &mut Subs, seen: &ImSet<Variable>, var: Variable) -> bool {
|
||||||
.values()
|
.values()
|
||||||
.any(|vars| vars.iter().any(|var| occurs(subs, &new_seen, *var)))
|
.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,
|
EmptyRecord | EmptyTagUnion | Erroneous(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -556,6 +579,12 @@ fn get_var_names(
|
||||||
|
|
||||||
taken_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,
|
Erroneous(_) => ErrorType::Error,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -806,6 +837,11 @@ fn restore_content(subs: &mut Subs, content: &Content) {
|
||||||
|
|
||||||
subs.restore(*ext_var);
|
subs.restore(*ext_var);
|
||||||
}
|
}
|
||||||
|
Boolean(b) => {
|
||||||
|
for var in b.variables() {
|
||||||
|
subs.restore(var);
|
||||||
|
}
|
||||||
|
}
|
||||||
Erroneous(_) => (),
|
Erroneous(_) => (),
|
||||||
},
|
},
|
||||||
Alias(_, _, args, var) => {
|
Alias(_, _, args, var) => {
|
||||||
|
|
|
@ -7,6 +7,7 @@ use crate::operator::{ArgSide, BinOp};
|
||||||
use crate::region::Located;
|
use crate::region::Located;
|
||||||
use crate::region::Region;
|
use crate::region::Region;
|
||||||
use crate::subs::Variable;
|
use crate::subs::Variable;
|
||||||
|
use crate::uniqueness::boolean_algebra;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
// The standard modules
|
// The standard modules
|
||||||
|
@ -39,6 +40,8 @@ pub enum Type {
|
||||||
name: Uppercase,
|
name: Uppercase,
|
||||||
args: Vec<Type>,
|
args: Vec<Type>,
|
||||||
},
|
},
|
||||||
|
/// Boolean type used in uniqueness inference
|
||||||
|
Boolean(boolean_algebra::Bool),
|
||||||
Variable(Variable),
|
Variable(Variable),
|
||||||
/// A type error, which will code gen to a runtime error
|
/// A type error, which will code gen to a runtime error
|
||||||
Erroneous(Problem),
|
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,
|
IntLiteral,
|
||||||
InterpolatedStringVar,
|
InterpolatedStringVar,
|
||||||
WhenBranch { index: usize },
|
WhenBranch { index: usize },
|
||||||
|
IfCondition,
|
||||||
|
IfBranch { index: usize },
|
||||||
ElemInList,
|
ElemInList,
|
||||||
RecordUpdateValue(Lowercase),
|
RecordUpdateValue(Lowercase),
|
||||||
RecordUpdateKeys(Ident, SendMap<Lowercase, Type>),
|
RecordUpdateKeys(Ident, SendMap<Lowercase, Type>),
|
||||||
|
@ -389,6 +395,7 @@ pub enum ErrorType {
|
||||||
Vec<(Lowercase, ErrorType)>,
|
Vec<(Lowercase, ErrorType)>,
|
||||||
Box<ErrorType>,
|
Box<ErrorType>,
|
||||||
),
|
),
|
||||||
|
Boolean(boolean_algebra::Bool),
|
||||||
Error,
|
Error,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
21
src/unify.rs
21
src/unify.rs
|
@ -5,6 +5,7 @@ use crate::subs::Content::{self, *};
|
||||||
use crate::subs::{Descriptor, FlatType, Mark, OptVariable, Subs, Variable};
|
use crate::subs::{Descriptor, FlatType, Mark, OptVariable, Subs, Variable};
|
||||||
use crate::types::RecordFieldLabel;
|
use crate::types::RecordFieldLabel;
|
||||||
use crate::types::{Mismatch, Problem};
|
use crate::types::{Mismatch, Problem};
|
||||||
|
use crate::uniqueness::boolean_algebra;
|
||||||
|
|
||||||
type Pool = Vec<Variable>;
|
type Pool = Vec<Variable>;
|
||||||
|
|
||||||
|
@ -15,9 +16,9 @@ struct Context {
|
||||||
second_desc: Descriptor,
|
second_desc: Descriptor,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RecordStructure {
|
pub struct RecordStructure {
|
||||||
fields: ImMap<RecordFieldLabel, Variable>,
|
pub fields: ImMap<RecordFieldLabel, Variable>,
|
||||||
ext: Variable,
|
pub ext: Variable,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TagUnionStructure {
|
struct TagUnionStructure {
|
||||||
|
@ -404,6 +405,18 @@ fn unify_flat_type(
|
||||||
unify_tag_union(subs, pool, ctx, union1, union2)
|
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 {
|
Apply {
|
||||||
module_name: l_module_name,
|
module_name: l_module_name,
|
||||||
|
@ -506,7 +519,7 @@ fn unify_flex(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gather_fields(
|
pub fn gather_fields(
|
||||||
subs: &mut Subs,
|
subs: &mut Subs,
|
||||||
fields: ImMap<RecordFieldLabel, Variable>,
|
fields: ImMap<RecordFieldLabel, Variable>,
|
||||||
var: Variable,
|
var: Variable,
|
||||||
|
|
|
@ -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;
|
use crate::subs::Variable;
|
||||||
|
|
||||||
pub fn unify(typ: &BooleanAlgebra, _expected: &BooleanAlgebra) -> Option<Substitution> {
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
// find the most general unifier.
|
pub enum Bool {
|
||||||
let mut val = typ.clone();
|
Zero,
|
||||||
let fv = val.variables();
|
One,
|
||||||
let (mgu, consistency_condition) = boolean_unification(&mut val, &fv);
|
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
|
use self::Bool::*;
|
||||||
if !consistency_condition.evaluate() {
|
|
||||||
Some(mgu)
|
#[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(¬(p.clone())));
|
||||||
|
let it2 = qs
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.filter(|q| ps.contains(¬(q.clone())));
|
||||||
|
|
||||||
|
it1.chain(it2).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_unify(p: Bool, q: Bool) -> Option<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 {
|
} else {
|
||||||
// the unification has no solution
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
fn unify(p: Bool, q: Bool) -> (Substitution, Bool) {
|
||||||
pub enum BooleanAlgebra {
|
let condition = q.is_var() && !p.is_var();
|
||||||
Ground(bool),
|
let t = if condition {
|
||||||
Disjunction(Box<BooleanAlgebra>, Box<BooleanAlgebra>),
|
or(and(q.clone(), not(p.clone())), and(not(q), p))
|
||||||
Conjunction(Box<BooleanAlgebra>, Box<BooleanAlgebra>),
|
} else {
|
||||||
Negation(Box<BooleanAlgebra>),
|
or(and(p.clone(), not(q.clone())), and(not(p), q))
|
||||||
Variable(Variable),
|
};
|
||||||
|
|
||||||
|
unify0(t.variables(), t)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BooleanAlgebra {
|
fn unify0(names: ImSet<Variable>, mut term: Bool) -> (Substitution, Bool) {
|
||||||
pub fn simplify(&mut self) {
|
// NOTE sort is required for stable test order that is the same as the Haskell ref. impl.
|
||||||
*self = simplify(self.clone());
|
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 {
|
(substitution, simplify(term))
|
||||||
use BooleanAlgebra::*;
|
}
|
||||||
match self {
|
|
||||||
Variable(v) if v == &var => expanded.clone(),
|
|
||||||
Variable(_) | Ground(_) => self.clone(),
|
|
||||||
|
|
||||||
Negation(t) => Negation(Box::new(t.substitute(var, expanded))),
|
// --- Simplification ---
|
||||||
|
|
||||||
Disjunction(l, r) => Disjunction(
|
/// Normalization of terms. Applies (in bottom-up fashion) the identities
|
||||||
Box::new(l.substitute(var, expanded)),
|
///
|
||||||
Box::new(r.substitute(var, expanded)),
|
/// 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(
|
match (p == One, p == Zero, q == One, q == Zero) {
|
||||||
Box::new(l.substitute(var, expanded)),
|
(true, _, _, _) => q,
|
||||||
Box::new(r.substitute(var, expanded)),
|
(_, 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 {
|
result
|
||||||
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(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn variables(&self) -> Vec<Variable> {
|
fn unit<A>(a: A) -> ImSet<A>
|
||||||
let mut vars = Vec::new();
|
where
|
||||||
variables_help(self, &mut vars);
|
A: Clone + Eq + core::hash::Hash,
|
||||||
vars
|
{
|
||||||
|
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(¬(x))) || sum.contains(&One);
|
||||||
|
|
||||||
|
if is_always_true {
|
||||||
|
unit(One)
|
||||||
|
} else {
|
||||||
|
sum.remove(&Zero);
|
||||||
|
sum
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn variables_help(bconstraint: &BooleanAlgebra, variables: &mut Vec<Variable>) {
|
fn normalize_conj(mut product: Product<Bool>) -> Product<Bool> {
|
||||||
use BooleanAlgebra::*;
|
let is_always_false = product
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.any(|x| product.contains(¬(x)))
|
||||||
|
|| product.contains(&Zero);
|
||||||
|
|
||||||
match bconstraint {
|
if is_always_false {
|
||||||
Variable(v) => variables.push(v.clone()),
|
unit(Zero)
|
||||||
Ground(_) => {}
|
} else {
|
||||||
Negation(t) => variables_help(t, variables),
|
product.remove(&One);
|
||||||
Disjunction(l, r) => {
|
product
|
||||||
variables_help(l, variables);
|
|
||||||
variables_help(r, variables);
|
|
||||||
}
|
|
||||||
Conjunction(l, r) => {
|
|
||||||
variables_help(l, variables);
|
|
||||||
variables_help(r, variables);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn simplify(bconstraint: BooleanAlgebra) -> BooleanAlgebra {
|
/// Conjunction Normal Form
|
||||||
use BooleanAlgebra::*;
|
fn cnf(term: Bool) -> Bool {
|
||||||
match bconstraint {
|
match nnf(term) {
|
||||||
Variable(_) | Ground(_) => bconstraint,
|
And(p, q) => and(cnf(*p), cnf(*q)),
|
||||||
|
Or(p, q) => distr_cnf(cnf(*p), cnf(*q)),
|
||||||
Negation(nested) => match simplify(*nested) {
|
other => other,
|
||||||
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)),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
fn distr_cnf(p: Bool, q: Bool) -> Bool {
|
||||||
pub struct Substitution {
|
match p {
|
||||||
pairs: ImMap<Variable, BooleanAlgebra>,
|
And(p1, p2) => and(distr_cnf(*p1, q.clone()), distr_cnf(*p2, q)),
|
||||||
}
|
_ => distr_cnf_help(p, q),
|
||||||
|
|
||||||
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 boolean_unification(
|
fn distr_cnf_help(p: Bool, q: Bool) -> Bool {
|
||||||
term: &mut BooleanAlgebra,
|
match q {
|
||||||
variables: &[Variable],
|
And(q1, q2) => and(distr_cnf(p.clone(), *q1), distr_cnf(p, *q2)),
|
||||||
) -> (Substitution, BooleanAlgebra) {
|
_ => or(p, q),
|
||||||
use BooleanAlgebra::*;
|
}
|
||||||
let mut substitution = Substitution::empty();
|
}
|
||||||
|
|
||||||
for var in variables {
|
/// Disjunction Normal Form
|
||||||
let t0 = term.clone().substitute(*var, &Ground(false));
|
pub fn dnf(term: Bool) -> Bool {
|
||||||
let t1 = term.clone().substitute(*var, &Ground(true));
|
match nnf(term) {
|
||||||
|
And(p, q) => distr_dnf(dnf(*p), dnf(*q)),
|
||||||
*term = Conjunction(Box::new(t1.clone()), Box::new(t0.clone()));
|
Or(p, q) => or(dnf(*p), dnf(*q)),
|
||||||
term.simplify();
|
other => other,
|
||||||
|
}
|
||||||
let mut sub = Disjunction(
|
}
|
||||||
Box::new(t0),
|
|
||||||
Box::new(Conjunction(
|
fn distr_dnf(p: Bool, q: Bool) -> Bool {
|
||||||
Box::new(Variable(*var)),
|
match p {
|
||||||
Box::new(Negation(Box::new(t1))),
|
Or(p1, p2) => or(distr_dnf(*p1, q.clone()), distr_dnf(*p2, q)),
|
||||||
)),
|
_ => distr_dnf_help(p, q),
|
||||||
);
|
}
|
||||||
sub.simplify();
|
}
|
||||||
|
|
||||||
substitution.insert(var.clone(), sub);
|
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())
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,103 +1,26 @@
|
||||||
use crate::collections::SendMap;
|
use crate::subs::VarStore;
|
||||||
use crate::region::Region;
|
use crate::types::Type;
|
||||||
use crate::subs::{VarStore, Variable};
|
use crate::uniqueness::boolean_algebra::Bool;
|
||||||
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,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn lift(var_store: &VarStore, typ: Type) -> Type {
|
pub fn lift(var_store: &VarStore, typ: Type) -> Type {
|
||||||
let uniq_var = var_store.fresh();
|
let uniq_var = var_store.fresh();
|
||||||
let uniq_type = Variable(uniq_var);
|
let uniq_type = Bool::Variable(uniq_var);
|
||||||
|
|
||||||
attr_type(uniq_type, typ)
|
attr_type(uniq_type, typ)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn int_literal(var_store: &VarStore, expected: Expected<Type>, region: Region) -> Constraint {
|
type Uniqueness = Bool;
|
||||||
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;
|
|
||||||
|
|
||||||
pub fn attr_type(uniq: Uniqueness, typ: Type) -> Type {
|
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 {
|
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
|
/// We usually just leave a type parameter unbound (written `*`) when it's unique
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn unique_type() -> Uniqueness {
|
pub fn unique_type() -> Uniqueness {
|
||||||
builtin_type("Attr", "Unique", Vec::new())
|
Bool::One
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -89,7 +89,6 @@ pub fn can_expr(expr_str: &str) -> (Expr, Output, Vec<Problem>, VarStore, Variab
|
||||||
pub fn uniq_expr(
|
pub fn uniq_expr(
|
||||||
expr_str: &str,
|
expr_str: &str,
|
||||||
) -> (
|
) -> (
|
||||||
Output,
|
|
||||||
Output,
|
Output,
|
||||||
Vec<Problem>,
|
Vec<Problem>,
|
||||||
Subs,
|
Subs,
|
||||||
|
@ -108,7 +107,6 @@ pub fn uniq_expr_with(
|
||||||
expr_str: &str,
|
expr_str: &str,
|
||||||
declared_idents: &ImMap<Ident, (Symbol, Region)>,
|
declared_idents: &ImMap<Ident, (Symbol, Region)>,
|
||||||
) -> (
|
) -> (
|
||||||
Output,
|
|
||||||
Output,
|
Output,
|
||||||
Vec<Problem>,
|
Vec<Problem>,
|
||||||
Subs,
|
Subs,
|
||||||
|
@ -124,12 +122,13 @@ pub fn uniq_expr_with(
|
||||||
|
|
||||||
let next_var = var_store1.into();
|
let next_var = var_store1.into();
|
||||||
let subs1 = Subs::new(next_var);
|
let subs1 = Subs::new(next_var);
|
||||||
|
|
||||||
// double check
|
// double check
|
||||||
let var_store2 = VarStore::new(next_var);
|
let var_store2 = VarStore::new(next_var);
|
||||||
|
|
||||||
let variable2 = var_store2.fresh();
|
let variable2 = var_store2.fresh();
|
||||||
let expected2 = Expected::NoExpectation(Type::Variable(variable2));
|
let expected2 = Expected::NoExpectation(Type::Variable(variable2));
|
||||||
let (output2, constraint2) = roc::uniqueness::canonicalize_declaration(
|
let constraint2 = roc::uniqueness::constrain_declaration(
|
||||||
&var_store2,
|
&var_store2,
|
||||||
Region::zero(),
|
Region::zero(),
|
||||||
loc_expr,
|
loc_expr,
|
||||||
|
@ -140,7 +139,6 @@ pub fn uniq_expr_with(
|
||||||
let subs2 = Subs::new(var_store2.into());
|
let subs2 = Subs::new(var_store2.into());
|
||||||
|
|
||||||
(
|
(
|
||||||
output2,
|
|
||||||
output,
|
output,
|
||||||
problems,
|
problems,
|
||||||
subs1,
|
subs1,
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
#[macro_use]
|
||||||
|
extern crate maplit;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate pretty_assertions;
|
extern crate pretty_assertions;
|
||||||
|
|
||||||
|
@ -8,13 +11,24 @@ mod helpers;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test_boolean_algebra {
|
mod test_boolean_algebra {
|
||||||
|
use roc::subs;
|
||||||
use roc::subs::VarStore;
|
use roc::subs::VarStore;
|
||||||
use roc::uniqueness::boolean_algebra;
|
use roc::uniqueness::boolean_algebra;
|
||||||
use roc::uniqueness::boolean_algebra::BooleanAlgebra::{self, *};
|
use roc::uniqueness::boolean_algebra::Bool::{self, *};
|
||||||
|
|
||||||
// HELPERS
|
// HELPERS
|
||||||
fn simplify_eq(mut a: BooleanAlgebra, mut b: BooleanAlgebra) {
|
fn to_var(v: usize) -> subs::Variable {
|
||||||
assert_eq!(a.simplify(), b.simplify());
|
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]
|
#[test]
|
||||||
|
@ -22,10 +36,7 @@ mod test_boolean_algebra {
|
||||||
let var_store = VarStore::default();
|
let var_store = VarStore::default();
|
||||||
let var = var_store.fresh();
|
let var = var_store.fresh();
|
||||||
|
|
||||||
simplify_eq(
|
simplify_eq(Bool::or(One, Variable(var)), One);
|
||||||
Disjunction(Box::new(Ground(true)), Box::new(Variable(var))),
|
|
||||||
Ground(true),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -33,10 +44,7 @@ mod test_boolean_algebra {
|
||||||
let var_store = VarStore::default();
|
let var_store = VarStore::default();
|
||||||
let var = var_store.fresh();
|
let var = var_store.fresh();
|
||||||
|
|
||||||
simplify_eq(
|
simplify_eq(Bool::or(Zero, Variable(var)), Variable(var));
|
||||||
Disjunction(Box::new(Ground(false)), Box::new(Variable(var))),
|
|
||||||
Variable(var),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -44,42 +52,127 @@ mod test_boolean_algebra {
|
||||||
let var_store = VarStore::default();
|
let var_store = VarStore::default();
|
||||||
let var = var_store.fresh();
|
let var = var_store.fresh();
|
||||||
|
|
||||||
simplify_eq(
|
simplify_eq(Bool::and(Zero, Variable(var)), Zero);
|
||||||
Conjunction(Box::new(Ground(false)), Box::new(Variable(var))),
|
}
|
||||||
Ground(false),
|
|
||||||
|
#[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]
|
#[test]
|
||||||
fn unify_single_var() {
|
fn unify_example_apply() {
|
||||||
let var_store = VarStore::default();
|
unify_eq(
|
||||||
let var = var_store.fresh();
|
Variable(to_var(1)),
|
||||||
|
Variable(to_var(6)),
|
||||||
let result = boolean_algebra::unify(&Variable(var), &Ground(true));
|
hashmap![to_var(1) => Variable(to_var(6))].into(),
|
||||||
|
);
|
||||||
if let Some(sub) = result {
|
unify_eq(
|
||||||
assert_eq!(Some(&Ground(false)), sub.get(var));
|
Variable(to_var(5)),
|
||||||
} else {
|
Variable(to_var(6)),
|
||||||
panic!("result is None");
|
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]
|
#[test]
|
||||||
fn unify_or() {
|
fn unify_example_fst() {
|
||||||
let var_store = VarStore::default();
|
unify_eq(
|
||||||
let a = var_store.fresh();
|
Variable(to_var(1)),
|
||||||
let b = var_store.fresh();
|
Variable(to_var(5)),
|
||||||
|
hashmap![to_var(1) => Variable(to_var(5))].into(),
|
||||||
let result = boolean_algebra::unify(
|
|
||||||
&Disjunction(Box::new(Variable(a)), Box::new(Variable(b))),
|
|
||||||
&Ground(true),
|
|
||||||
);
|
);
|
||||||
|
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 {
|
#[test]
|
||||||
assert_eq!(Some(&Variable(b)), sub.get(a));
|
fn unify_example_idid() {
|
||||||
assert_eq!(Some(&Ground(false)), sub.get(b));
|
unify_eq(
|
||||||
} else {
|
Variable(to_var(3)),
|
||||||
panic!("result is None");
|
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
603
tests/test_emit.rs
Normal 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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -108,24 +108,23 @@ mod test_format {
|
||||||
expr_formats_to(
|
expr_formats_to(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
# This variable is for greeting
|
# This variable is for greeting
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
a = "Hello"
|
a = "Hello"
|
||||||
|
|
||||||
a
|
a
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
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!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
f = \x, y ->
|
f = \x, y ->
|
||||||
a = 3
|
a = 3
|
||||||
b = 6
|
b = 6
|
||||||
|
|
||||||
c
|
c
|
||||||
|
|
||||||
"string"
|
"string"
|
||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -241,7 +240,7 @@ mod test_format {
|
||||||
expr_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
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
|
// DEFS
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -321,22 +338,22 @@ mod test_format {
|
||||||
expr_formats_to(
|
expr_formats_to(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
x = 5
|
x = 5
|
||||||
|
|
||||||
|
|
||||||
y = 10
|
y = 10
|
||||||
|
|
||||||
42
|
42
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
x = 5
|
x = 5
|
||||||
|
|
||||||
y = 10
|
y = 10
|
||||||
|
|
||||||
42
|
42
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -396,7 +413,6 @@ mod test_format {
|
||||||
y = 10
|
y = 10
|
||||||
|
|
||||||
# v-- This is the return value
|
# v-- This is the return value
|
||||||
|
|
||||||
42
|
42
|
||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
|
@ -404,17 +420,28 @@ mod test_format {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn space_between_comments() {
|
fn space_between_comments() {
|
||||||
expr_formats_same(indoc!(
|
expr_formats_to(
|
||||||
r#"
|
indoc!(
|
||||||
# 9
|
r#"
|
||||||
|
# 9
|
||||||
|
|
||||||
# A
|
# A
|
||||||
# B
|
# B
|
||||||
|
|
||||||
# C
|
# C
|
||||||
9
|
9
|
||||||
"#
|
"#
|
||||||
));
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
# 9
|
||||||
|
# A
|
||||||
|
# B
|
||||||
|
# C
|
||||||
|
9
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -434,40 +461,38 @@ mod test_format {
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
# First
|
# First
|
||||||
|
|
||||||
# Second
|
# Second
|
||||||
x
|
x
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// expr_formats_to(
|
expr_formats_to(
|
||||||
// indoc!(
|
indoc!(
|
||||||
// r#"
|
r#"
|
||||||
// f = \x ->
|
f = \x ->
|
||||||
// # 1st
|
# 1st
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// # 2nd
|
# 2nd
|
||||||
// x
|
x
|
||||||
//
|
|
||||||
// f 4
|
f 4
|
||||||
// "#
|
"#
|
||||||
// ),
|
),
|
||||||
// indoc!(
|
indoc!(
|
||||||
// r#"
|
r#"
|
||||||
// f = \x ->
|
f = \x ->
|
||||||
// # 1st
|
# 1st
|
||||||
//
|
# 2nd
|
||||||
// # 2nd
|
x
|
||||||
// x
|
|
||||||
//
|
f 4
|
||||||
// f 4
|
"#
|
||||||
// "#
|
),
|
||||||
// ),
|
);
|
||||||
// );
|
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn doesnt_detect_comment_in_comment() {
|
fn doesnt_detect_comment_in_comment() {
|
||||||
|
@ -515,15 +540,77 @@ mod test_format {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[test]
|
// #[test]
|
||||||
// fn record_field_destructuring() {
|
// fn record_field_destructuring() {
|
||||||
// expr_formats_same(indoc!(
|
// expr_formats_same(indoc!(
|
||||||
// r#"
|
// r#"
|
||||||
// when foo is
|
// when foo is
|
||||||
// { x: 5 } -> 42
|
// { 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]
|
#[test]
|
||||||
fn def_closure() {
|
fn def_closure() {
|
||||||
|
@ -716,26 +803,56 @@ mod test_format {
|
||||||
x: 4,
|
x: 4,
|
||||||
y: 42
|
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]
|
#[test]
|
||||||
fn two_fields_center_newline() {
|
fn two_fields_center_newline() {
|
||||||
expr_formats_to(
|
expr_formats_to(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
{ x: 4,
|
{ x: 4,
|
||||||
y: 42
|
y: 42
|
||||||
}
|
}
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
{
|
{
|
||||||
x: 4,
|
x: 4,
|
||||||
y: 42
|
y: 42
|
||||||
}
|
}
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -759,13 +876,13 @@ mod test_format {
|
||||||
expr_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
if foo bar then a b c else d e f
|
if foo bar then a b c else d e f
|
||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
|
|
||||||
expr_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
if foo (a b c) then a b c else d e f
|
if foo (a b c) then a b c else d e f
|
||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -777,7 +894,6 @@ mod test_format {
|
||||||
waterWillBoil pressure temperature
|
waterWillBoil pressure temperature
|
||||||
then
|
then
|
||||||
turnOnAc
|
turnOnAc
|
||||||
|
|
||||||
else
|
else
|
||||||
identity
|
identity
|
||||||
"#
|
"#
|
||||||
|
@ -805,7 +921,6 @@ mod test_format {
|
||||||
willBoil home water
|
willBoil home water
|
||||||
then
|
then
|
||||||
\_ -> leave
|
\_ -> leave
|
||||||
|
|
||||||
else
|
else
|
||||||
identity
|
identity
|
||||||
"#
|
"#
|
||||||
|
@ -813,71 +928,114 @@ mod test_format {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[test]
|
#[test]
|
||||||
// fn if_removes_newlines() {
|
fn if_removes_newlines_from_else() {
|
||||||
// expr_formats_to(
|
expr_formats_to(
|
||||||
// indoc!(
|
indoc!(
|
||||||
// r#"
|
r#"
|
||||||
// if
|
if
|
||||||
//
|
isPrime 8
|
||||||
// # You never know!
|
then
|
||||||
// isPrime 8
|
nothing
|
||||||
//
|
else
|
||||||
// # Top Comment
|
# C
|
||||||
//
|
# D
|
||||||
// # 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
|
|
||||||
|
|
||||||
// # Top Comment
|
# E
|
||||||
|
# F
|
||||||
|
|
||||||
// # Bottom Comment
|
just (div 1 8)
|
||||||
// then
|
"#
|
||||||
// # A
|
),
|
||||||
|
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
|
# GG
|
||||||
//
|
|
||||||
// else
|
else
|
||||||
// # C
|
just (div 1 9)
|
||||||
// # D
|
"#
|
||||||
|
),
|
||||||
|
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]
|
#[test]
|
||||||
fn multi_line_if() {
|
fn multi_line_if() {
|
||||||
expr_formats_to(
|
expr_formats_to(
|
||||||
|
@ -885,6 +1043,8 @@ mod test_format {
|
||||||
r#"
|
r#"
|
||||||
if lessThan four five then
|
if lessThan four five then
|
||||||
four
|
four
|
||||||
|
|
||||||
|
|
||||||
else
|
else
|
||||||
five
|
five
|
||||||
"#
|
"#
|
||||||
|
@ -893,7 +1053,6 @@ mod test_format {
|
||||||
r#"
|
r#"
|
||||||
if lessThan four five then
|
if lessThan four five then
|
||||||
four
|
four
|
||||||
|
|
||||||
else
|
else
|
||||||
five
|
five
|
||||||
"#
|
"#
|
||||||
|
@ -921,7 +1080,6 @@ mod test_format {
|
||||||
r#"
|
r#"
|
||||||
if lessThan three four then
|
if lessThan three four then
|
||||||
three
|
three
|
||||||
|
|
||||||
else
|
else
|
||||||
four
|
four
|
||||||
"#
|
"#
|
||||||
|
@ -932,7 +1090,6 @@ mod test_format {
|
||||||
r#"
|
r#"
|
||||||
if foo bar then
|
if foo bar then
|
||||||
a b c
|
a b c
|
||||||
|
|
||||||
else
|
else
|
||||||
d e f
|
d e f
|
||||||
"#
|
"#
|
||||||
|
@ -961,7 +1118,7 @@ mod test_format {
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
2
|
2
|
||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -970,28 +1127,28 @@ mod test_format {
|
||||||
expr_formats_to(
|
expr_formats_to(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
when year is
|
when year is
|
||||||
1999 ->
|
1999 ->
|
||||||
|
|
||||||
|
|
||||||
1
|
1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
|
|
||||||
0
|
0
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
when year is
|
when year is
|
||||||
1999 ->
|
1999 ->
|
||||||
1
|
1
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
0
|
0
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1013,7 +1170,7 @@ mod test_format {
|
||||||
# more comment
|
# more comment
|
||||||
2
|
2
|
||||||
|
|
||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1026,7 +1183,7 @@ mod test_format {
|
||||||
when c is
|
when c is
|
||||||
_ ->
|
_ ->
|
||||||
1
|
1
|
||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1071,25 +1228,25 @@ mod test_format {
|
||||||
expr_formats_to(
|
expr_formats_to(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
when b is
|
when b is
|
||||||
1 ->
|
1 ->
|
||||||
1 # when 1
|
1 # when 1
|
||||||
|
|
||||||
# fall through
|
# fall through
|
||||||
_ ->
|
_ ->
|
||||||
2
|
2
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
when b is
|
when b is
|
||||||
1 ->
|
1 ->
|
||||||
1
|
1
|
||||||
|
|
||||||
# when 1
|
# when 1
|
||||||
# fall through
|
# fall through
|
||||||
_ ->
|
_ ->
|
||||||
2
|
2
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1129,15 +1286,15 @@ mod test_format {
|
||||||
fn def_returning_closure() {
|
fn def_returning_closure() {
|
||||||
expr_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
f = \x -> x
|
f = \x -> x
|
||||||
g = \x -> x
|
g = \x -> x
|
||||||
|
|
||||||
\x ->
|
\x ->
|
||||||
a = f x
|
a = f x
|
||||||
b = f x
|
b = f x
|
||||||
|
|
||||||
x
|
x
|
||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -607,20 +607,19 @@ mod test_infer {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[test]
|
#[test]
|
||||||
// TODO FIXME this should work, but instead causes a stack overflow!
|
fn recursive_identity() {
|
||||||
// fn recursive_identity() {
|
infer_eq(
|
||||||
// infer_eq(
|
indoc!(
|
||||||
// indoc!(
|
r#"
|
||||||
// r#"
|
identity = \val -> val
|
||||||
// identity = \val -> val
|
|
||||||
|
|
||||||
// identity identity
|
identity identity
|
||||||
// "#
|
"#
|
||||||
// ),
|
),
|
||||||
// "a -> a",
|
"a -> a",
|
||||||
// );
|
);
|
||||||
// }
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn identity_function() {
|
fn identity_function() {
|
||||||
|
@ -813,20 +812,20 @@ mod test_infer {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[test]
|
#[test]
|
||||||
// fn if_with_int_literals() {
|
fn if_with_int_literals() {
|
||||||
// infer_eq(
|
infer_eq(
|
||||||
// indoc!(
|
indoc!(
|
||||||
// r#"
|
r#"
|
||||||
// if 1 == 1 then
|
if True then
|
||||||
// 42
|
42
|
||||||
// else
|
else
|
||||||
// 24
|
24
|
||||||
// "#
|
"#
|
||||||
// ),
|
),
|
||||||
// "Int",
|
"Int",
|
||||||
// );
|
);
|
||||||
// }
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn when_with_int_literals() {
|
fn when_with_int_literals() {
|
||||||
|
@ -1006,7 +1005,7 @@ mod test_infer {
|
||||||
{ user & year: "foo" }
|
{ user & year: "foo" }
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"{ year : Str }{ name : Str }",
|
"{ name : Str, year : Str }",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1039,7 +1038,7 @@ mod test_infer {
|
||||||
r#"\@Foo -> 42
|
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
|
#[test]
|
||||||
// @Foo x y isn't turned into Apply(@Foo, [x,y]) currently
|
fn private_tag_application() {
|
||||||
// #[test]
|
infer_eq(
|
||||||
// fn private_tag_application() {
|
indoc!(
|
||||||
// infer_eq(
|
r#"@Foo "happy" 2020
|
||||||
// indoc!(
|
"#
|
||||||
// r#"@Foo "happy" 2020
|
),
|
||||||
// "#
|
"[ Test.@Foo Str Int ]*",
|
||||||
// ),
|
);
|
||||||
// "[ 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",
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ mod test_parse {
|
||||||
use roc::parse::ast::CommentOrNewline::*;
|
use roc::parse::ast::CommentOrNewline::*;
|
||||||
use roc::parse::ast::Expr::{self, *};
|
use roc::parse::ast::Expr::{self, *};
|
||||||
use roc::parse::ast::Pattern::{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::module::{interface_header, module_defs};
|
||||||
use roc::parse::parser::{Fail, FailReason, Parser, State};
|
use roc::parse::parser::{Fail, FailReason, Parser, State};
|
||||||
use roc::region::{Located, Region};
|
use roc::region::{Located, Region};
|
||||||
|
@ -641,7 +641,7 @@ mod test_parse {
|
||||||
assert_eq!(Ok(expected), actual);
|
assert_eq!(Ok(expected), actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
// VARIANT
|
// TAG
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn basic_global_tag() {
|
fn basic_global_tag() {
|
||||||
|
@ -661,6 +661,38 @@ mod test_parse {
|
||||||
assert_eq!(Ok(expected), actual);
|
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]
|
#[test]
|
||||||
fn qualified_global_tag() {
|
fn qualified_global_tag() {
|
||||||
let arena = Bump::new();
|
let arena = Bump::new();
|
||||||
|
@ -1245,27 +1277,17 @@ mod test_parse {
|
||||||
let arena = Bump::new();
|
let arena = Bump::new();
|
||||||
let newline = bumpalo::vec![in &arena; Newline];
|
let newline = bumpalo::vec![in &arena; Newline];
|
||||||
let newlines = bumpalo::vec![in &arena; Newline, Newline];
|
let newlines = bumpalo::vec![in &arena; Newline, Newline];
|
||||||
|
let applied_ann = TypeAnnotation::Apply(&[], "Int", &[]);
|
||||||
let signature = Def::Annotation(
|
let signature = Def::Annotation(
|
||||||
Located::new(0, 0, 0, 3, Identifier("foo")),
|
Located::new(0, 0, 0, 3, Identifier("foo")),
|
||||||
Located::new(
|
Located::new(0, 0, 6, 9, applied_ann),
|
||||||
0,
|
|
||||||
0,
|
|
||||||
6,
|
|
||||||
9,
|
|
||||||
roc::parse::ast::TypeAnnotation::Apply(&[], "Int", &[]),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
let def = Def::Body(
|
let def = Def::Body(
|
||||||
arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))),
|
arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))),
|
||||||
arena.alloc(Located::new(1, 1, 6, 7, Int("4"))),
|
arena.alloc(Located::new(1, 1, 6, 7, Int("4"))),
|
||||||
);
|
);
|
||||||
let loc_def = &*arena.alloc(Located::new(
|
let spaced_def = Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice());
|
||||||
1,
|
let loc_def = &*arena.alloc(Located::new(1, 1, 0, 7, spaced_def));
|
||||||
1,
|
|
||||||
0,
|
|
||||||
7,
|
|
||||||
Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice()),
|
|
||||||
));
|
|
||||||
|
|
||||||
let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature));
|
let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature));
|
||||||
let defs = bumpalo::vec![in &arena; loc_ann, loc_def];
|
let defs = bumpalo::vec![in &arena; loc_ann, loc_def];
|
||||||
|
@ -1288,7 +1310,7 @@ mod test_parse {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn type_signature_function_def() {
|
fn type_signature_function_def() {
|
||||||
use roc::parse::ast::TypeAnnotation;
|
use TypeAnnotation;
|
||||||
let arena = Bump::new();
|
let arena = Bump::new();
|
||||||
let newline = bumpalo::vec![in &arena; Newline];
|
let newline = bumpalo::vec![in &arena; Newline];
|
||||||
let newlines = bumpalo::vec![in &arena; Newline, Newline];
|
let newlines = bumpalo::vec![in &arena; Newline, Newline];
|
||||||
|
@ -1302,16 +1324,10 @@ mod test_parse {
|
||||||
Located::new(0, 0, 11, 16, float_type)
|
Located::new(0, 0, 11, 16, float_type)
|
||||||
];
|
];
|
||||||
let return_type = Located::new(0, 0, 20, 24, bool_type);
|
let return_type = Located::new(0, 0, 20, 24, bool_type);
|
||||||
|
let fn_ann = TypeAnnotation::Function(&arguments, &return_type);
|
||||||
let signature = Def::Annotation(
|
let signature = Def::Annotation(
|
||||||
Located::new(0, 0, 0, 3, Identifier("foo")),
|
Located::new(0, 0, 0, 3, Identifier("foo")),
|
||||||
Located::new(
|
Located::new(0, 0, 20, 24, fn_ann),
|
||||||
0,
|
|
||||||
0,
|
|
||||||
20,
|
|
||||||
24,
|
|
||||||
TypeAnnotation::Function(&arguments, &return_type),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let args = bumpalo::vec![in &arena;
|
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, 0, 3, Identifier("foo"))),
|
||||||
arena.alloc(Located::new(1, 1, 6, 17, closure)),
|
arena.alloc(Located::new(1, 1, 6, 17, closure)),
|
||||||
);
|
);
|
||||||
let loc_def = &*arena.alloc(Located::new(
|
let spaced = Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice());
|
||||||
1,
|
let loc_def = &*arena.alloc(Located::new(1, 1, 0, 17, spaced));
|
||||||
1,
|
|
||||||
0,
|
|
||||||
17,
|
|
||||||
Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice()),
|
|
||||||
));
|
|
||||||
|
|
||||||
let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature));
|
let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature));
|
||||||
let defs = bumpalo::vec![in &arena; loc_ann, loc_def];
|
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
|
// WHEN
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -17,22 +17,15 @@ mod test_infer_uniq {
|
||||||
// HELPERS
|
// HELPERS
|
||||||
|
|
||||||
fn infer_eq(src: &str, expected: &str) {
|
fn infer_eq(src: &str, expected: &str) {
|
||||||
let (
|
let (_output1, _, mut subs1, variable1, mut subs2, variable2, constraint1, constraint2) =
|
||||||
_output2,
|
uniq_expr(src);
|
||||||
_output1,
|
|
||||||
_,
|
|
||||||
mut subs1,
|
|
||||||
variable1,
|
|
||||||
mut subs2,
|
|
||||||
variable2,
|
|
||||||
constraint1,
|
|
||||||
constraint2,
|
|
||||||
) = uniq_expr(src);
|
|
||||||
|
|
||||||
let mut unify_problems = Vec::new();
|
let mut unify_problems = Vec::new();
|
||||||
let content1 = infer_expr(&mut subs1, &mut unify_problems, &constraint1, variable1);
|
let content1 = infer_expr(&mut subs1, &mut unify_problems, &constraint1, variable1);
|
||||||
let content2 = infer_expr(&mut subs2, &mut unify_problems, &constraint2, variable2);
|
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(variable1, &mut subs1);
|
||||||
name_all_type_vars(variable2, &mut subs2);
|
name_all_type_vars(variable2, &mut subs2);
|
||||||
|
|
||||||
|
@ -514,15 +507,13 @@ mod test_infer_uniq {
|
||||||
infer_eq(
|
infer_eq(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
identity = \a -> a
|
identity = \a -> a
|
||||||
|
x = identity 5
|
||||||
|
|
||||||
x = identity 5
|
identity
|
||||||
|
"#
|
||||||
identity
|
|
||||||
"#
|
|
||||||
),
|
),
|
||||||
// TODO investigate why not shared
|
// TODO investigate why not shared?
|
||||||
// perhaps because `x` is DCE'd?
|
|
||||||
"Attr.Attr * (a -> a)",
|
"Attr.Attr * (a -> a)",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -637,20 +628,19 @@ mod test_infer_uniq {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[test]
|
#[test]
|
||||||
// TODO FIXME this should work, but instead causes a stack overflow!
|
fn recursive_identity() {
|
||||||
// fn recursive_identity() {
|
infer_eq(
|
||||||
// infer_eq(
|
indoc!(
|
||||||
// indoc!(
|
r#"
|
||||||
// r#"
|
identity = \val -> val
|
||||||
// identity = \val -> val
|
|
||||||
|
|
||||||
// identity identity
|
identity identity
|
||||||
// "#
|
"#
|
||||||
// ),
|
),
|
||||||
// "a -> a",
|
"Attr.Attr Attr.Shared (a -> a)",
|
||||||
// );
|
);
|
||||||
// }
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn identity_function() {
|
fn identity_function() {
|
||||||
|
@ -843,20 +833,20 @@ mod test_infer_uniq {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[test]
|
#[test]
|
||||||
// fn if_with_int_literals() {
|
fn if_with_int_literals() {
|
||||||
// infer_eq(
|
infer_eq(
|
||||||
// indoc!(
|
indoc!(
|
||||||
// r#"
|
r#"
|
||||||
// if 1 == 1 then
|
if True then
|
||||||
// 42
|
42
|
||||||
// else
|
else
|
||||||
// 24
|
24
|
||||||
// "#
|
"#
|
||||||
// ),
|
),
|
||||||
// "Int",
|
"Attr.Attr * Int",
|
||||||
// );
|
);
|
||||||
// }
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn when_with_int_literals() {
|
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]
|
#[test]
|
||||||
fn record() {
|
fn record() {
|
||||||
infer_eq("{ foo: 42 }", "Attr.Attr * { foo : (Attr.Attr * Int) }");
|
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");
|
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]
|
#[test]
|
||||||
fn empty_record_pattern() {
|
fn empty_record_pattern() {
|
||||||
infer_eq(
|
infer_eq(
|
||||||
|
@ -927,7 +899,226 @@ mod test_infer_uniq {
|
||||||
{ user & year: "foo" }
|
{ 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",
|
||||||
|
// );
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue