Merge branch 'main' into main

This commit is contained in:
shamsartem 2023-08-01 14:02:34 +02:00 committed by GitHub
commit dbd1100a20
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
243 changed files with 29812 additions and 3149 deletions

View file

@ -1,4 +1,5 @@
on: [pull_request]
on:
pull_request:
name: Benchmarks

View file

@ -1,4 +1,5 @@
on: [pull_request]
on:
pull_request:
name: Macos x86-64 rust tests

View file

@ -0,0 +1,46 @@
on:
workflow_dispatch:
schedule:
- cron: '0 9 * * *'
name: Nightly Release Linux arm64/aarch64
jobs:
build:
name: build and package nightly release
runs-on: [self-hosted, Linux, ARM64]
timeout-minutes: 90
steps:
- uses: actions/checkout@v3
- name: create version.txt
run: ./ci/write_version.sh
- name: build release with lto
run: cargo build --profile=release-with-lto --locked --bin roc
- name: get commit SHA
run: echo "SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV
- name: get date
run: echo "DATE=$(date "+%Y-%m-%d")" >> $GITHUB_ENV
- name: build file name
env:
DATE: ${{ env.DATE }}
SHA: ${{ env.SHA }}
run: echo "RELEASE_FOLDER_NAME=roc_nightly-linux_arm64-$DATE-$SHA" >> $GITHUB_ENV
# this makes the roc binary a lot smaller
- name: strip debug info
run: strip ./target/release-with-lto/roc
- name: Make nightly release tar archive
run: ./ci/package_release.sh ${{ env.RELEASE_FOLDER_NAME }}
- name: Upload roc nightly tar. Actually uploading to github releases has to be done manually.
uses: actions/upload-artifact@v3
with:
name: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
path: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
retention-days: 4

View file

@ -0,0 +1,38 @@
on:
workflow_dispatch:
schedule:
- cron: '0 9 * * *'
name: Nightly Release Old Linux arm64 using Earthly
jobs:
build:
name: build and package nightly release
runs-on: [self-hosted, Linux, ARM64]
timeout-minutes: 180
steps:
- uses: actions/checkout@v3
- name: get commit SHA
run: echo "SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV
- name: get date
run: echo "DATE=$(date "+%Y-%m-%d")" >> $GITHUB_ENV
- name: build file name
env:
DATE: ${{ env.DATE }}
SHA: ${{ env.SHA }}
run: echo "RELEASE_FOLDER_NAME=roc_nightly-old_linux_arm64-$DATE-$SHA" >> $GITHUB_ENV
- run: earthly --version
- name: build release with earthly
run: earthly +build-nightly-release --RELEASE_FOLDER_NAME=${{ env.RELEASE_FOLDER_NAME }} --ZIG_ARCH=aarch64
- name: Upload roc nightly tar. Actually uploading to github releases has to be done manually.
uses: actions/upload-artifact@v3
with:
name: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
path: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
retention-days: 4

View file

@ -0,0 +1,41 @@
on:
workflow_dispatch:
schedule:
- cron: '0 9 * * *'
name: Nightly Release Old Linux x86_64 using Earthly
jobs:
build:
name: build and package nightly release
runs-on: [ubuntu-20.04]
timeout-minutes: 90
steps:
- uses: actions/checkout@v3
- name: get commit SHA
run: echo "SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV
- name: get date
run: echo "DATE=$(date "+%Y-%m-%d")" >> $GITHUB_ENV
- name: build file name
env:
DATE: ${{ env.DATE }}
SHA: ${{ env.SHA }}
run: echo "RELEASE_FOLDER_NAME=roc_nightly-old_linux_x86_64-$DATE-$SHA" >> $GITHUB_ENV
- name: install earthly
run: sudo /bin/sh -c 'wget https://github.com/earthly/earthly/releases/latest/download/earthly-linux-amd64 -O /usr/local/bin/earthly && chmod +x /usr/local/bin/earthly && /usr/local/bin/earthly bootstrap --with-autocomplete'
- run: earthly --version
- name: build release with earthly
run: earthly +build-nightly-release --RELEASE_FOLDER_NAME=${{ env.RELEASE_FOLDER_NAME }} --RUSTFLAGS="-C target-cpu=x86-64"
- name: Upload roc nightly tar. Actually uploading to github releases has to be done manually.
uses: actions/upload-artifact@v3
with:
name: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
path: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
retention-days: 4

View file

@ -1,4 +1,5 @@
on: [pull_request]
on:
pull_request:
name: Nix linux x86_64 cargo test

View file

@ -1,4 +1,5 @@
on: [pull_request]
on:
pull_request:
name: Nix apple silicon cargo test

View file

@ -1,4 +1,5 @@
on: [pull_request]
on:
pull_request:
name: Nix macOS x86_64 cargo test

View file

@ -1,4 +1,5 @@
on: [pull_request]
on:
pull_request:
name: SpellCheck

View file

@ -1,11 +1,9 @@
on:
#pull_request:
workflow_dispatch:
name: Test latest nightly release for macOS, ubu 20.04, ubu 22.04 x86_64
env:
ZIG_VERSION: 0.9.1
jobs:
test-nightly:
name: test nightly macos 11, macos 12, ubu 20.04, ubu 22.04
@ -17,6 +15,9 @@ jobs:
timeout-minutes: 90
steps:
- uses: actions/checkout@v3
- uses: goto-bus-stop/setup-zig@v2
with:
version: 0.9.1
- name: get the latest release archive for linux (x86_64)
if: startsWith(matrix.os, 'ubuntu')
@ -26,44 +27,19 @@ jobs:
if: startsWith(matrix.os, 'macos')
run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_x86_64-latest.tar.gz
- name: remove everything in this dir except the tar # we want to test like a user who would have downloaded the release, so we clean up all files from the repo checkout
run: ls | grep -v "roc_nightly.*tar\.gz" | xargs rm -rf
- run: zig version
- name: decompress the tar
run: ls | grep "roc_nightly.*tar\.gz" | xargs tar -xzvf
- name: delete tar
run: ls | grep "roc_nightly.*tar\.gz" | xargs rm -rf
- name: rename nightly folder
run: mv roc_nightly* roc_nightly
- name: test roc hello world
run: cd roc_nightly && ./roc examples/helloWorld.roc
- name: test platform switching rust
run: cd roc_nightly && ./roc examples/platform-switching/rocLovesRust.roc
- name: get OS to use for zig download
if: startsWith(matrix.os, 'ubuntu')
run: echo "OS_TYPE=linux" >> $GITHUB_ENV
- name: get OS to use for zig download
if: startsWith(matrix.os, 'macos')
run: echo "OS_TYPE=macos" >> $GITHUB_ENV
- name: Install zig
- name: prep and run basic tests
run: |
curl -fL -o zig.tar.xz https://ziglang.org/download/${ZIG_VERSION}/zig-${{env.OS_TYPE}}-x86_64-${ZIG_VERSION}.tar.xz && tar -xf zig.tar.xz
echo "${GITHUB_WORKSPACE}/zig-${{env.OS_TYPE}}-x86_64-${ZIG_VERSION}" >> $GITHUB_PATH
- name: zig version
run: zig version
./ci/basic_nightly_test.sh
- name: clean up, get old linux release (x86_64), run tests
if: startsWith(matrix.os, 'ubuntu')
run: |
rm -rf roc_nightly
curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-old_linux_x86_64-latest.tar.gz
./ci/basic_nightly_test.sh
- name: test platform switching zig
run: cd roc_nightly && ./roc examples/platform-switching/rocLovesZig.roc
- name: test platform switching c
run: cd roc_nightly && ./roc examples/platform-switching/rocLovesC.roc

View file

@ -1,4 +1,5 @@
on: [pull_request]
on:
pull_request:
name: CI
@ -61,7 +62,6 @@ jobs:
run: ./ci/www-repl.sh && sccache --show-stats
#TODO i386 (32-bit linux) cli tests
#TODO verify-no-git-changes
- name: test website build script

View file

@ -1,4 +1,5 @@
on: [pull_request]
on:
pull_request:
name: windows - release build
@ -32,8 +33,8 @@ jobs:
- name: zig version
run: zig version
- name: install rust nightly 1.67
run: rustup install nightly-2022-12-09
- name: install rust nightly 1.70.0
run: rustup install nightly-2023-04-15
- name: set up llvm 13
run: |

View file

@ -1,4 +1,5 @@
on: [pull_request]
on:
pull_request:
name: windows - subset of tests
@ -36,8 +37,8 @@ jobs:
- name: zig version
run: zig version
- name: install rust nightly 1.67
run: rustup install nightly-2022-12-09
- name: install rust nightly 1.70.0
run: rustup install nightly-2023-04-15
- name: set up llvm 13
run: |

3
.gitignore vendored
View file

@ -85,3 +85,6 @@ www/src/roc-tutorial
# snapshot tests temp file
*.pending-snap
# checkmate
checkmate_*.json

179
Cargo.lock generated
View file

@ -88,6 +88,21 @@ dependencies = [
"alloc-no-stdlib",
]
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "anstream"
version = "0.3.2"
@ -473,6 +488,21 @@ dependencies = [
"num-traits",
]
[[package]]
name = "chrono"
version = "0.4.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"time 0.1.45",
"wasm-bindgen",
"winapi",
]
[[package]]
name = "clap"
version = "2.34.0"
@ -1094,6 +1124,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
[[package]]
name = "dyn-clone"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "304e6508efa593091e97a9abbc10f90aa7ca635b6d2784feff3c89d41dd12272"
[[package]]
name = "either"
version = "1.8.1"
@ -1394,7 +1430,7 @@ dependencies = [
"cfg-if 1.0.0",
"js-sys",
"libc",
"wasi",
"wasi 0.11.0+wasi-snapshot-preview1",
"wasm-bindgen",
]
@ -1653,6 +1689,29 @@ dependencies = [
"tokio-rustls",
]
[[package]]
name = "iana-time-zone"
version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613"
dependencies = [
"android_system_properties",
"core-foundation-sys 0.8.4",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "iced-x86"
version = "1.18.0"
@ -1975,7 +2034,7 @@ dependencies = [
"libc",
"log",
"thiserror",
"time",
"time 0.3.21",
"uuid",
]
@ -2106,7 +2165,7 @@ checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
dependencies = [
"libc",
"log",
"wasi",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.45.0",
]
@ -3095,6 +3154,7 @@ dependencies = [
"page_size",
"roc_builtins",
"roc_can",
"roc_checkmate",
"roc_collections",
"roc_error_macros",
"roc_load",
@ -3105,6 +3165,7 @@ dependencies = [
"roc_region",
"roc_reporting",
"roc_solve",
"roc_solve_schema",
"roc_target",
"roc_types",
"roc_unify",
@ -3203,6 +3264,27 @@ dependencies = [
"ven_pretty",
]
[[package]]
name = "roc_checkmate"
version = "0.0.1"
dependencies = [
"chrono",
"roc_checkmate_schema",
"roc_module",
"roc_solve_schema",
"roc_types",
"serde_json",
]
[[package]]
name = "roc_checkmate_schema"
version = "0.0.1"
dependencies = [
"schemars",
"serde",
"serde_json",
]
[[package]]
name = "roc_cli"
version = "0.0.1"
@ -3311,11 +3393,13 @@ version = "0.0.1"
dependencies = [
"bumpalo",
"roc_can",
"roc_checkmate",
"roc_collections",
"roc_derive_key",
"roc_error_macros",
"roc_module",
"roc_region",
"roc_solve_schema",
"roc_types",
"roc_unify",
]
@ -3351,6 +3435,7 @@ dependencies = [
"roc_parse",
"roc_region",
"roc_reporting",
"roc_solve",
"roc_target",
"roc_types",
"snafu",
@ -3563,11 +3648,13 @@ version = "0.0.1"
dependencies = [
"bumpalo",
"roc_can",
"roc_checkmate",
"roc_collections",
"roc_derive",
"roc_error_macros",
"roc_module",
"roc_solve",
"roc_solve_schema",
"roc_types",
"roc_unify",
]
@ -3591,6 +3678,7 @@ dependencies = [
"roc_mono",
"roc_packaging",
"roc_reporting",
"roc_solve",
"roc_target",
"serde",
"serial_test",
@ -3611,6 +3699,7 @@ dependencies = [
"roc_module",
"roc_packaging",
"roc_reporting",
"roc_solve",
"roc_target",
"roc_types",
]
@ -3627,6 +3716,7 @@ dependencies = [
"pretty_assertions",
"roc_builtins",
"roc_can",
"roc_checkmate",
"roc_collections",
"roc_constrain",
"roc_debug_flags",
@ -3686,10 +3776,12 @@ dependencies = [
"roc_module",
"roc_problem",
"roc_region",
"roc_solve_schema",
"roc_std",
"roc_target",
"roc_tracing",
"roc_types",
"roc_unify",
"static_assertions",
"ven_pretty",
]
@ -3794,6 +3886,7 @@ dependencies = [
"roc_problem",
"roc_region",
"roc_reporting",
"roc_solve",
"roc_std",
"roc_target",
"roc_types",
@ -3849,6 +3942,7 @@ dependencies = [
"roc_parse",
"roc_repl_eval",
"roc_reporting",
"roc_solve",
"roc_target",
"roc_types",
"tempfile",
@ -3912,6 +4006,7 @@ dependencies = [
"regex",
"roc_builtins",
"roc_can",
"roc_checkmate",
"roc_collections",
"roc_debug_flags",
"roc_derive",
@ -3927,6 +4022,7 @@ dependencies = [
"roc_reporting",
"roc_solve",
"roc_solve_problem",
"roc_solve_schema",
"roc_target",
"roc_types",
"roc_unify",
@ -3947,6 +4043,13 @@ dependencies = [
"roc_types",
]
[[package]]
name = "roc_solve_schema"
version = "0.0.1"
dependencies = [
"bitflags",
]
[[package]]
name = "roc_std"
version = "0.0.1"
@ -4006,11 +4109,12 @@ dependencies = [
name = "roc_unify"
version = "0.0.1"
dependencies = [
"bitflags",
"roc_checkmate",
"roc_collections",
"roc_debug_flags",
"roc_error_macros",
"roc_module",
"roc_solve_schema",
"roc_tracing",
"roc_types",
]
@ -4156,6 +4260,30 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "schemars"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f"
dependencies = [
"dyn-clone",
"schemars_derive",
"serde",
"serde_json",
]
[[package]]
name = "schemars_derive"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c"
dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"syn 1.0.109",
]
[[package]]
name = "scoped-tls"
version = "1.0.1"
@ -4244,6 +4372,17 @@ dependencies = [
"syn 2.0.16",
]
[[package]]
name = "serde_derive_internals"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "serde_json"
version = "1.0.96"
@ -4798,6 +4937,17 @@ dependencies = [
"num_cpus",
]
[[package]]
name = "time"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
dependencies = [
"libc",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi",
]
[[package]]
name = "time"
version = "0.3.21"
@ -4942,7 +5092,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e"
dependencies = [
"crossbeam-channel",
"time",
"time 0.3.21",
"tracing-subscriber",
]
@ -5252,6 +5402,12 @@ dependencies = [
"try-lock",
]
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
@ -5576,6 +5732,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
dependencies = [
"windows-targets 0.48.0",
]
[[package]]
name = "windows-sys"
version = "0.42.0"
@ -5860,9 +6025,9 @@ checksum = "a67300977d3dc3f8034dae89778f502b6ba20b269527b3223ba59c0cf393bb8a"
[[package]]
name = "xml-rs"
version = "0.8.11"
version = "0.8.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1690519550bfa95525229b9ca2350c63043a4857b3b0013811b2ccf4a2420b01"
checksum = "47430998a7b5d499ccee752b41567bc3afc57e1327dc855b1a2aa44ce29b5fa1"
[[package]]
name = "yaml-rust"

View file

@ -82,6 +82,7 @@ bumpalo = { version = "3.12.0", features = ["collections"] }
bytemuck = { version = "1.13.1", features = ["derive"] }
capstone = { version = "0.11.0", default-features = false }
cgmath = "0.18.0"
chrono = "0.4.26"
clap = { version = "4.2.7", default-features = false, features = ["std", "color", "suggestions", "help", "usage", "error-context"] }
colored = "2.0.0"
console_error_panic_hook = "0.1.7"
@ -141,6 +142,7 @@ reqwest = { version = "0.11.14", default-features = false, features = ["blocking
rlimit = "0.9.1"
rustyline = { git = "https://github.com/roc-lang/rustyline", rev = "e74333c" }
rustyline-derive = { git = "https://github.com/roc-lang/rustyline", rev = "e74333c" }
schemars = "0.8.12"
serde = { version = "1.0.153", features = ["derive"] } # update roc_std/Cargo.toml on change
serde-xml-rs = "0.6.0"
serde_json = "1.0.94" # update roc_std/Cargo.toml on change

60
Earthfile Normal file
View file

@ -0,0 +1,60 @@
VERSION 0.6
FROM rust:1.70.0-slim-buster # make sure to update rust-toolchain.toml too so that everything uses the same rust version
WORKDIR /earthbuild
prep-debian:
RUN apt -y update
install-other-libs:
FROM +prep-debian
RUN apt -y install wget git
RUN apt -y install libunwind-dev pkg-config zlib1g-dev
RUN apt -y install unzip # for www/build.sh
install-zig-llvm:
ARG ZIG_ARCH
FROM +install-other-libs
# zig
RUN wget -c https://ziglang.org/download/0.9.1/zig-linux-$ZIG_ARCH-0.9.1.tar.xz --no-check-certificate
RUN tar -xf zig-linux-$ZIG_ARCH-0.9.1.tar.xz
RUN ln -s /earthbuild/zig-linux-$ZIG_ARCH-0.9.1/zig /bin/zig
# zig builtins wasm tests
RUN apt -y install build-essential
# llvm
RUN apt -y install lsb-release software-properties-common gnupg
RUN wget https://apt.llvm.org/llvm.sh
RUN chmod +x llvm.sh
RUN ./llvm.sh 13
RUN ln -s /usr/bin/clang-13 /usr/bin/clang
# use lld as linker
RUN ln -s /usr/bin/lld-13 /usr/bin/ld.lld
ENV RUSTFLAGS="-C link-arg=-fuse-ld=lld -C target-cpu=native"
RUN apt -y install libssl-dev
RUN OPENSSL_NO_VENDOR=1 cargo install wasm-pack
# sccache
RUN cargo install sccache
RUN sccache -V
ENV RUSTC_WRAPPER=/usr/local/cargo/bin/sccache
ENV SCCACHE_DIR=/earthbuild/sccache_dir
ENV CARGO_INCREMENTAL=0 # no need to recompile package when using new function
copy-dirs:
ARG ZIG_ARCH
FROM +install-zig-llvm --ZIG_ARCH=$ZIG_ARCH
COPY --dir crates examples Cargo.toml Cargo.lock version.txt .cargo www rust-toolchain.toml ./
build-nightly-release:
ARG RELEASE_FOLDER_NAME
ARG RUSTFLAGS
ARG ZIG_ARCH=x86_64
FROM +copy-dirs --ZIG_ARCH=$ZIG_ARCH
COPY --dir .git LICENSE LEGAL_DETAILS ci ./
# version.txt is used by the CLI: roc --version
RUN ./ci/write_version.sh
RUN RUSTFLAGS=$RUSTFLAGS cargo build --profile=release-with-lto --locked --bin roc
# strip debug info
RUN strip ./target/release-with-lto/roc
RUN ./ci/package_release.sh $RELEASE_FOLDER_NAME
RUN ls
SAVE ARTIFACT ./$RELEASE_FOLDER_NAME.tar.gz AS LOCAL $RELEASE_FOLDER_NAME.tar.gz

View file

@ -29,6 +29,7 @@ If you would like your company to become a corporate sponsor of Roc's developmen
We'd also like to express our gratitude to each and every one of our fantastic [GitHub sponsors](https://github.com/sponsors/roc-lang/)! A special thanks to those sponsoring $25/month or more:
* [Ivo Balbaert](https://github.com/Ivo-Balbaert)
* [Lucas Rosa](https://github.com/rvcas)
* [Jonas Schell](https://github.com/Ocupe)
* [Christopher Dolan](https://github.com/cdolan)

40
ci/basic_nightly_test.sh Executable file
View file

@ -0,0 +1,40 @@
#!/usr/bin/env bash
# https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/
set -euxo pipefail
# if to prevent unset vars errror
if [ -n "$(ls | grep -v "roc_nightly.*tar\.gz" | grep -v "^ci$")" ]; then
# Remove everything in this dir except the tar and ci folder.
# We want to test like a user who would have downloaded the release, so we clean up all files from the repo checkout.
to_delete=$(ls | grep -v "roc_nightly.*tar\.gz" | grep -v "^ci$")
for file_or_dir in $to_delete
do
echo "Removing: $file_or_dir"
rm -rf "$file_or_dir"
done
fi
# decompress the tar
ls | grep "roc_nightly.*tar\.gz" | xargs tar -xzvf
# delete tar
ls | grep "roc_nightly.*tar\.gz" | xargs rm -rf
# rename nightly folder
mv roc_nightly* roc_nightly
cd roc_nightly
# test roc hello world
./roc examples/helloWorld.roc
./roc examples/platform-switching/rocLovesRust.roc
./roc examples/platform-switching/rocLovesZig.roc
./roc examples/platform-switching/rocLovesC.roc
cd ..

View file

@ -10,6 +10,7 @@ version.workspace = true
[dependencies]
roc_builtins = { path = "../compiler/builtins" }
roc_can = { path = "../compiler/can" }
roc_checkmate = { path = "../compiler/checkmate" }
roc_collections = { path = "../compiler/collections" }
roc_error_macros = { path = "../error_macros" }
roc_load = { path = "../compiler/load" }
@ -20,6 +21,7 @@ roc_problem = { path = "../compiler/problem" }
roc_region = { path = "../compiler/region" }
roc_reporting = { path = "../reporting" }
roc_solve = { path = "../compiler/solve" }
roc_solve_schema = { path = "../compiler/solve_schema" }
roc_target = { path = "../compiler/roc_target" }
roc_types = { path = "../compiler/types" }
roc_unify = { path = "../compiler/unify" }

View file

@ -68,7 +68,7 @@ impl<T> Clone for NodeId<T> {
fn clone(&self) -> Self {
NodeId {
index: self.index,
_phantom: PhantomData::default(),
_phantom: PhantomData,
}
}
}
@ -176,7 +176,7 @@ impl Pool {
NodeId {
index,
_phantom: PhantomData::default(),
_phantom: PhantomData,
}
} else {
todo!("pool ran out of capacity. TODO reallocate the nodes pointer to map to a bigger space. Can use mremap on Linux, but must memcpy lots of bytes on macOS and Windows.");

View file

@ -45,7 +45,7 @@ impl PoolStr {
PoolStr {
first_node_id: NodeId {
index: 0,
_phantom: PhantomData::default(),
_phantom: PhantomData,
},
len: 0,
}

View file

@ -72,7 +72,7 @@ impl<'a, T: 'a + Sized> PoolVec<T> {
PoolVec {
first_node_id: NodeId {
index: 0,
_phantom: PhantomData::default(),
_phantom: PhantomData,
},
len: 0,
}
@ -179,7 +179,7 @@ where
// Advance the node pointer to the next node in the current page
self.current_node_id = NodeId {
index: index + 1,
_phantom: PhantomData::default(),
_phantom: PhantomData,
};
self.len_remaining = len_remaining - 1;
@ -237,7 +237,7 @@ where
// Advance the node pointer to the next node in the current page
self.current_node_id = NodeId {
index: index + 1,
_phantom: PhantomData::default(),
_phantom: PhantomData,
};
self.len_remaining = len_remaining - 1;
@ -288,7 +288,7 @@ impl<T> Iterator for PoolVecIterNodeIds<T> {
// Advance the node pointer to the next node in the current page
self.current_node_id = NodeId {
index: index + 1,
_phantom: PhantomData::default(),
_phantom: PhantomData,
};
self.len_remaining = len_remaining - 1;

View file

@ -12,6 +12,7 @@ pub fn load_module(
) -> LoadedModule {
let load_config = LoadConfig {
target_info: TargetInfo::default_x86_64(), // editor only needs type info, so this is unused
function_kind: roc_solve::FunctionKind::LambdaSet, // TODO the editor may need to dynamically change this
render: roc_reporting::report::RenderTarget::ColorTerminal,
palette: DEFAULT_PALETTE,
threading,

View file

@ -2,12 +2,14 @@
#![allow(dead_code)]
use bumpalo::Bump;
use roc_can::expected::{Expected, PExpected};
use roc_checkmate::with_checkmate;
use roc_collections::all::{BumpMap, BumpMapDefault, MutMap};
use roc_error_macros::internal_error;
use roc_module::ident::TagName;
use roc_module::symbol::Symbol;
use roc_region::all::{Loc, Region};
use roc_solve::module::Solved;
use roc_solve_schema::UnificationMode;
use roc_types::subs::{
self, AliasVariables, Content, Descriptor, FlatType, Mark, OptVariable, Rank, RecordFields,
Subs, SubsSlice, TagExt, UnionLambdas, UnionTags, Variable, VariableSubsSlice,
@ -17,9 +19,8 @@ use roc_types::types::{
RecordField,
};
use roc_unify::unify::unify;
use roc_unify::unify::Env as UEnv;
use roc_unify::unify::Mode;
use roc_unify::unify::Unified::*;
use roc_unify::Env as UEnv;
use crate::constrain::{Constraint, PresenceConstraint};
use crate::lang::core::types::Type2;
@ -228,10 +229,13 @@ fn solve<'a>(
);
match unify(
&mut UEnv::new(subs),
&mut with_checkmate!({
on => UEnv::new(subs, None),
off => UEnv::new(subs),
}),
actual,
expected,
Mode::EQ,
UnificationMode::EQ,
Polarity::OF_VALUE,
) {
Success {
@ -326,10 +330,13 @@ fn solve<'a>(
);
match unify(
&mut UEnv::new(subs),
&mut with_checkmate!({
on => UEnv::new(subs, None),
off => UEnv::new(subs),
}),
actual,
expected,
Mode::EQ,
UnificationMode::EQ,
Polarity::OF_VALUE,
) {
Success {
@ -402,10 +409,13 @@ fn solve<'a>(
// TODO(ayazhafiz): presence constraints for Expr2/Type2
match unify(
&mut UEnv::new(subs),
&mut with_checkmate!({
on => UEnv::new(subs, None),
off => UEnv::new(subs),
}),
actual,
expected,
Mode::EQ,
UnificationMode::EQ,
Polarity::OF_PATTERN,
) {
Success {
@ -718,10 +728,13 @@ fn solve<'a>(
let includes = type_to_var(arena, mempool, subs, rank, pools, cached_aliases, &tag_ty);
match unify(
&mut UEnv::new(subs),
&mut with_checkmate!({
on => UEnv::new(subs, None),
off => UEnv::new(subs),
}),
actual,
includes,
Mode::PRESENT,
UnificationMode::PRESENT,
Polarity::OF_PATTERN,
) {
Success {
@ -1489,6 +1502,8 @@ fn adjust_rank_content(
rank
}
ErasedLambda => group_rank,
RangedNumber(_vars) => group_rank,
}
}
@ -1669,6 +1684,7 @@ fn instantiate_rigids_help(
}
}
ErasedLambda => {}
RangedNumber(_vars) => {}
}
@ -1981,6 +1997,12 @@ fn deep_copy_var_help(
copy
}
ErasedLambda => {
subs.set(copy, make_descriptor(ErasedLambda));
copy
}
RangedNumber(vars) => {
let new_content = RangedNumber(vars);

View file

@ -385,7 +385,7 @@ pub fn test(_matches: &ArgMatches, _triple: Triple) -> io::Result<i32> {
#[cfg(not(windows))]
pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
use roc_build::program::report_problems_monomorphized;
use roc_load::{ExecutionMode, LoadConfig, LoadMonomorphizedError};
use roc_load::{ExecutionMode, FunctionKind, LoadConfig, LoadMonomorphizedError};
use roc_packaging::cache;
use roc_target::TargetInfo;
@ -427,10 +427,13 @@ pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
let target = &triple;
let opt_level = opt_level;
let target_info = TargetInfo::from(target);
// TODO may need to determine this dynamically based on dev builds.
let function_kind = FunctionKind::LambdaSet;
// Step 1: compile the app and generate the .o file
let load_config = LoadConfig {
target_info,
function_kind,
// TODO: expose this from CLI?
render: roc_reporting::report::RenderTarget::ColorTerminal,
palette: roc_reporting::report::DEFAULT_PALETTE,
@ -1169,7 +1172,7 @@ fn roc_run_executable_file_path(binary_bytes: &[u8]) -> std::io::Result<Executab
Ok(ExecutableFile::OnDisk(temp_dir, app_path_buf))
}
#[cfg(all(target_family = "windows"))]
#[cfg(target_family = "windows")]
fn roc_run_executable_file_path(binary_bytes: &[u8]) -> std::io::Result<ExecutableFile> {
use std::fs::OpenOptions;
use std::io::Write;

View file

@ -11,7 +11,7 @@ use roc_docs::generate_docs_html;
use roc_error_macros::user_error;
use roc_gen_dev::AssemblyBackendMode;
use roc_gen_llvm::llvm::build::LlvmBackendMode;
use roc_load::{LoadingProblem, Threading};
use roc_load::{FunctionKind, LoadingProblem, Threading};
use roc_packaging::cache::{self, RocCacheDir};
use roc_target::Target;
use std::fs::{self, FileType};
@ -123,10 +123,12 @@ fn main() -> io::Result<()> {
.get_one::<String>(FLAG_TARGET)
.and_then(|s| Target::from_str(s).ok())
.unwrap_or_default();
let function_kind = FunctionKind::LambdaSet;
roc_linker::generate_stub_lib(
input_path,
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),
&target.to_triple(),
function_kind,
)
}
Some((CMD_BUILD, matches)) => {

View file

@ -24,19 +24,15 @@ main =
Err GetIntError ->
Task.putLine "Error: Failed to get Integer from stdin."
nestHelp : I64, (I64, Expr -> IO Expr), I64, Expr -> IO Expr
nestHelp = \s, f, m, x -> when m is
0 -> Task.succeed x
_ ->
w <- Task.after (f (s - m) x)
nestHelp s f (m - 1) w
nest : (I64, Expr -> IO Expr), I64, Expr -> IO Expr
nest = \f, n, e -> Task.loop { s: n, f, m: n, x: e } nestHelp
State : { s : I64, f : I64, Expr -> IO Expr, m : I64, x : Expr }
nestHelp : State -> IO [Step State, Done Expr]
nestHelp = \{ s, f, m, x } ->
when m is
0 -> Task.succeed (Done x)
_ ->
w <- Task.after (f (s - m) x)
Task.succeed (Step { s, f, m: (m - 1), x: w })
nest = \f, n, e -> nestHelp n f n e
Expr : [Val I64, Var Str, Add Expr Expr, Mul Expr Expr, Pow Expr Expr, Ln Expr]

View file

@ -14,8 +14,8 @@ use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol;
use roc_mono::ir::{
Call, CallType, EntryPoint, Expr, HigherOrderLowLevel, HostExposedLayouts, ListLiteralElement,
Literal, ModifyRc, OptLevel, Proc, ProcLayout, SingleEntryPoint, Stmt,
Call, CallType, EntryPoint, ErasedField, Expr, HigherOrderLowLevel, HostExposedLayouts,
ListLiteralElement, Literal, ModifyRc, OptLevel, Proc, ProcLayout, SingleEntryPoint, Stmt,
};
use roc_mono::layout::{
Builtin, InLayout, Layout, LayoutInterner, LayoutRepr, Niche, RawFunctionLayout,
@ -181,6 +181,7 @@ where
let mut type_definitions = MutSet::default();
let mut host_exposed_functions = Vec::new();
let mut erased_functions = Vec::new();
// all other functions
for proc in procs {
@ -201,6 +202,17 @@ where
host_exposed_functions.push((bytes, hels.proc_layout.arguments));
}
RawFunctionLayout::ErasedFunction(..) => {
let it = hels.proc_layout.arguments.iter().copied();
let bytes = func_name_bytes_help(
hels.symbol,
it,
Niche::NONE,
hels.proc_layout.result,
);
host_exposed_functions.push((bytes, hels.proc_layout.arguments));
}
RawFunctionLayout::ZeroArgumentThunk(_) => {
let bytes = func_name_bytes_help(
hels.symbol,
@ -226,6 +238,11 @@ where
let (spec, type_names) = proc_spec(arena, interner, proc)?;
if proc.is_erased {
let args = &*arena.alloc_slice_fill_iter(proc.args.iter().map(|(lay, _)| *lay));
erased_functions.push((bytes, args));
}
type_definitions.extend(type_names);
m.add_func(func_name, spec)?;
@ -253,6 +270,7 @@ where
entry_point_layout,
Some(roc_main),
&host_exposed_functions,
&erased_functions,
)?;
type_definitions.extend(env.type_names);
@ -279,8 +297,14 @@ where
.collect();
let mut env = Env::new();
let entry_point_function =
build_entry_point(&mut env, interner, layout, None, &host_exposed)?;
let entry_point_function = build_entry_point(
&mut env,
interner,
layout,
None,
&host_exposed,
&erased_functions,
)?;
type_definitions.extend(env.type_names);
@ -364,6 +388,7 @@ fn build_entry_point<'a>(
layout: roc_mono::ir::ProcLayout<'a>,
entry_point_function: Option<FuncName>,
host_exposed_functions: &[([u8; SIZE], &'a [InLayout<'a>])],
erased_functions: &[([u8; SIZE], &'a [InLayout<'a>])],
) -> Result<FuncDef> {
let mut builder = FuncDefBuilder::new();
let outer_block = builder.add_block();
@ -394,7 +419,7 @@ fn build_entry_point<'a>(
}
// add fake calls to host-exposed functions so they are specialized
for (name_bytes, layouts) in host_exposed_functions {
for (name_bytes, layouts) in host_exposed_functions.iter().chain(erased_functions) {
let host_exposed_func_name = FuncName(name_bytes);
if Some(host_exposed_func_name) == entry_point_function {
@ -788,6 +813,16 @@ fn call_spec<'a>(
let module = MOD_APP;
builder.add_call(block, spec_var, module, name, arg_value_id)
}
ByPointer {
pointer,
ret_layout,
arg_layouts: _,
} => {
let result_type = layout_spec(env, builder, interner, interner.get_repr(*ret_layout))?;
let fnptr = env.symbols[pointer];
let arg_value_id = build_tuple_value(builder, env, block, call.arguments)?;
builder.add_unknown_with(block, &[fnptr, arg_value_id], result_type)
}
Foreign {
foreign_symbol: _,
ret_layout,
@ -1517,6 +1552,28 @@ fn expr_spec<'a>(
let value = with_new_heap_cell(builder, block, union_data)?;
builder.add_make_named(block, MOD_APP, type_name, value)
}
FunctionPointer { .. } => {
let pointer_type = layout_spec(env, builder, interner, interner.get_repr(layout))?;
builder.add_unknown_with(block, &[], pointer_type)
}
ErasedMake { callee, value } => {
let value = match value {
Some(v) => box_erasure_value_unknown(builder, block, env.symbols[v]),
// model nullptr
None => box_erasure_value_unknown_nullptr(builder, block),
}?;
let callee = env.symbols[callee];
erasure_make(builder, block, value, callee)
}
ErasedLoad { symbol, field } => {
let value = env.symbols[symbol];
let loaded_type = layout_spec(env, builder, interner, interner.get_repr(layout))?;
erasure_load(builder, block, value, *field, loaded_type)
}
RuntimeErrorFunction(_) => {
let type_id = layout_spec(env, builder, interner, interner.get_repr(layout))?;
@ -1527,6 +1584,16 @@ fn expr_spec<'a>(
builder.add_make_tuple(block, &[])
}
Alloca { initializer, .. } => {
let initializer = &initializer.as_ref().map(|s| env.symbols[s]);
let values = match initializer {
Some(initializer) => std::slice::from_ref(initializer),
None => &[],
};
let type_id = layout_spec(env, builder, interner, interner.get_repr(layout))?;
builder.add_unknown_with(block, values, type_id)
}
}
}
@ -1631,6 +1698,9 @@ fn layout_spec_help<'a>(
}
_ => internal_error!("somehow, a non-recursive layout is under a recursive pointer"),
},
FunctionPointer(_) => function_pointer_type(builder),
Erased(_) => erasure_type(builder),
}
}
@ -1707,3 +1777,78 @@ fn new_num(builder: &mut FuncDefBuilder, block: BlockId) -> Result<ValueId> {
// we model all our numbers as unit values
builder.add_make_tuple(block, &[])
}
fn function_pointer_type<TC: TypeContext>(builder: &mut TC) -> Result<TypeId> {
builder.add_tuple_type(&[])
}
const ERASURE_CALEE_INDEX: u32 = 0;
const ERASURE_VALUE_INDEX: u32 = 1;
/// Erasure type modeled as
///
/// ```text
/// Tuple(callee: FnPtr, value: HeapCell)
/// ```
fn erasure_type<TC: TypeContext>(builder: &mut TC) -> Result<TypeId> {
let value_cell_id = builder.add_heap_cell_type();
let callee_id = function_pointer_type(builder)?;
builder.add_tuple_type(&[value_cell_id, callee_id])
}
fn erasure_box_value_type<TC: TypeContext>(builder: &mut TC) -> TypeId {
builder.add_heap_cell_type()
}
fn box_erasure_value_unknown(
builder: &mut FuncDefBuilder,
block: BlockId,
value: ValueId,
) -> Result<ValueId> {
let heap_cell = erasure_box_value_type(builder);
builder.add_unknown_with(block, &[value], heap_cell)
}
fn box_erasure_value_unknown_nullptr(
builder: &mut FuncDefBuilder,
block: BlockId,
) -> Result<ValueId> {
let heap_cell = erasure_box_value_type(builder);
builder.add_unknown_with(block, &[], heap_cell)
}
/// Erasure value modeled as
///
/// ```text
/// callee = make_tuple(&[])
/// value = unknown(make_tuple(...captures))
///
/// x : Tuple(callee: FnPtr, value: HeapCell)
/// x = make_tuple(callee, value)
/// ```
fn erasure_make(
builder: &mut FuncDefBuilder,
block: BlockId,
value: ValueId,
callee: ValueId,
) -> Result<ValueId> {
builder.add_make_tuple(block, &[value, callee])
}
fn erasure_load(
builder: &mut FuncDefBuilder,
block: BlockId,
value: ValueId,
field: ErasedField,
loaded_type: TypeId,
) -> Result<ValueId> {
match field {
ErasedField::Callee => builder.add_get_tuple_field(block, value, ERASURE_CALEE_INDEX),
ErasedField::Value | ErasedField::ValuePtr => {
let unknown_heap_cell_value =
builder.add_get_tuple_field(block, value, ERASURE_VALUE_INDEX)?;
// Cast the unknown cell to the wanted type
builder.add_unknown_with(block, &[unknown_heap_cell_value], loaded_type)
}
}
}

View file

@ -530,7 +530,7 @@ pub fn rebuild_host(
// on windows, we need the nightly toolchain so we can use `-Z export-executable-symbols`
// using `+nightly` only works when running cargo through rustup
let mut cmd = rustup();
cmd.args(["run", "nightly-2022-12-09", "cargo"]);
cmd.args(["run", "nightly-2023-04-15", "cargo"]);
cmd
} else {
@ -1078,6 +1078,12 @@ fn link_macos(
// "--gc-sections",
"-arch",
&arch,
// Suppress warnings, because otherwise it prints:
//
// ld: warning: -undefined dynamic_lookup may not work with chained fixups
//
// We can't disable that option without breaking either x64 mac or ARM mac
"-w",
"-macos_version_min",
&get_macos_version(),
])

View file

@ -8,8 +8,8 @@ use roc_gen_dev::AssemblyBackendMode;
use roc_gen_llvm::llvm::build::{module_from_builtins, LlvmBackendMode};
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
use roc_load::{
EntryPoint, ExecutionMode, ExpectMetadata, LoadConfig, LoadMonomorphizedError, LoadedModule,
LoadingProblem, MonomorphizedModule, Threading,
EntryPoint, ExecutionMode, ExpectMetadata, FunctionKind, LoadConfig, LoadMonomorphizedError,
LoadedModule, LoadingProblem, MonomorphizedModule, Threading,
};
use roc_mono::ir::{OptLevel, SingleEntryPoint};
use roc_packaging::cache::RocCacheDir;
@ -740,8 +740,20 @@ pub fn standard_load_config(
BuildOrdering::AlwaysBuild => ExecutionMode::Executable,
};
// UNSTABLE(lambda-erasure)
let function_kind = if cfg!(debug_assertions) {
if std::env::var("EXPERIMENTAL_ROC_ERASE").is_ok() {
FunctionKind::Erased
} else {
FunctionKind::LambdaSet
}
} else {
FunctionKind::LambdaSet
};
LoadConfig {
target_info,
function_kind,
render: RenderTarget::ColorTerminal,
palette: DEFAULT_PALETTE,
threading,
@ -1205,6 +1217,8 @@ pub fn check_file<'a>(
let load_config = LoadConfig {
target_info,
// TODO: we may not want this for just checking.
function_kind: FunctionKind::LambdaSet,
// TODO: expose this from CLI?
render: RenderTarget::ColorTerminal,
palette: DEFAULT_PALETTE,

View file

@ -1182,8 +1182,17 @@ mapTry = \list, toResult ->
Result.map (toResult elem) \ok ->
List.append state ok
## This is the same as `iterate` but with [Result] instead of `[Continue, Break]`.
## Using `Result` saves a conditional in `mapTry`.
## Same as [List.walk], except you can stop walking early by returning `Err`.
##
## ## Performance Details
##
## Compared to [List.walk], this can potentially visit fewer elements (which can
## improve performance) at the cost of making each step take longer.
## However, the added cost to each step is extremely small, and can easily
## be outweighed if it results in skipping even a small number of elements.
##
## As such, it is typically better for performance to use this over [List.walk]
## if returning `Break` earlier than the last element is expected to be common.
walkTry : List elem, state, (state, elem -> Result state err) -> Result state err
walkTry = \list, init, func ->
walkTryHelp list init func 0 (List.len list)

View file

@ -638,12 +638,13 @@ countUtf8Bytes : Str -> Nat
substringUnsafe : Str, Nat, Nat -> Str
## Returns the given [Str] with each occurrence of a substring replaced.
## Returns [Err NotFound] if the substring is not found.
## If the substring is not found, returns the original string.
##
## ```
## expect Str.replaceEach "foo/bar/baz" "/" "_" == Ok "foo_bar_baz"
## expect Str.replaceEach "not here" "/" "_" == Err NotFound
## expect Str.replaceEach "foo/bar/baz" "/" "_" == "foo_bar_baz"
## expect Str.replaceEach "not here" "/" "_" == "not here"
## ```
replaceEach : Str, Str, Str -> Result Str [NotFound]
replaceEach : Str, Str, Str -> Str
replaceEach = \haystack, needle, flower ->
when splitFirst haystack needle is
Ok { before, after } ->
@ -653,9 +654,8 @@ replaceEach = \haystack, needle, flower ->
|> Str.concat before
|> Str.concat flower
|> replaceEachHelp after needle flower
|> Ok
Err err -> Err err
Err NotFound -> haystack
replaceEachHelp : Str, Str, Str, Str -> Str
replaceEachHelp = \buf, haystack, needle, flower ->
@ -668,39 +668,44 @@ replaceEachHelp = \buf, haystack, needle, flower ->
Err NotFound -> Str.concat buf haystack
expect Str.replaceEach "abXdeXghi" "X" "_" == Ok "ab_de_ghi"
expect Str.replaceEach "abXdeXghi" "X" "_" == "ab_de_ghi"
expect Str.replaceEach "abcdefg" "nothing" "_" == "abcdefg"
## Returns the given [Str] with the first occurrence of a substring replaced.
## Returns [Err NotFound] if the substring is not found.
## If the substring is not found, returns the original string.
##
## ```
## expect Str.replaceFirst "foo/bar/baz" "/" "_" == Ok "foo_bar/baz"
## expect Str.replaceFirst "no slashes here" "/" "_" == Err NotFound
## expect Str.replaceFirst "foo/bar/baz" "/" "_" == "foo_bar/baz"
## expect Str.replaceFirst "no slashes here" "/" "_" == "no slashes here"
## ```
replaceFirst : Str, Str, Str -> Result Str [NotFound]
replaceFirst : Str, Str, Str -> Str
replaceFirst = \haystack, needle, flower ->
when splitFirst haystack needle is
Ok { before, after } ->
Ok "\(before)\(flower)\(after)"
"\(before)\(flower)\(after)"
Err err -> Err err
Err NotFound -> haystack
expect Str.replaceFirst "abXdeXghi" "X" "_" == Ok "ab_deXghi"
expect Str.replaceFirst "abXdeXghi" "X" "_" == "ab_deXghi"
expect Str.replaceFirst "abcdefg" "nothing" "_" == "abcdefg"
## Returns the given [Str] with the last occurrence of a substring replaced.
## Returns [Err NotFound] if the substring is not found.
## If the substring is not found, returns the original string.
##
## ```
## expect Str.replaceLast "foo/bar/baz" "/" "_" == Ok "foo/bar_baz"
## expect Str.replaceLast "no slashes here" "/" "_" == Err NotFound
## expect Str.replaceLast "foo/bar/baz" "/" "_" == "foo/bar_baz"
## expect Str.replaceLast "no slashes here" "/" "_" == "no slashes here"
## ```
replaceLast : Str, Str, Str -> Result Str [NotFound]
replaceLast : Str, Str, Str -> Str
replaceLast = \haystack, needle, flower ->
when splitLast haystack needle is
Ok { before, after } ->
Ok "\(before)\(flower)\(after)"
"\(before)\(flower)\(after)"
Err err -> Err err
Err NotFound -> haystack
expect Str.replaceLast "abXdeXghi" "X" "_" == Ok "abXde_ghi"
expect Str.replaceLast "abXdeXghi" "X" "_" == "abXde_ghi"
expect Str.replaceLast "abcdefg" "nothing" "_" == "abcdefg"
## Returns the given [Str] before the first occurrence of a [delimiter](https://www.computerhope.com/jargon/d/delimite.htm), as well
## as the rest of the string after that occurrence.

View file

@ -88,13 +88,16 @@ macro_rules! map_symbol_to_lowlevel_and_arity {
LowLevel::PtrStore => unimplemented!(),
LowLevel::PtrLoad => unimplemented!(),
LowLevel::PtrClearTagId => unimplemented!(),
LowLevel::Alloca => unimplemented!(),
LowLevel::RefCountIncRcPtr => unimplemented!(),
LowLevel::RefCountDecRcPtr=> unimplemented!(),
LowLevel::RefCountIncDataPtr => unimplemented!(),
LowLevel::RefCountDecDataPtr=> unimplemented!(),
LowLevel::RefCountIsUnique => unimplemented!(),
LowLevel::SetJmp => unimplemented!(),
LowLevel::LongJmp => unimplemented!(),
LowLevel::SetLongJmpBuffer => unimplemented!(),
// these are not implemented, not sure why
LowLevel::StrFromInt => unimplemented!(),
LowLevel::StrFromFloat => unimplemented!(),

View file

@ -64,26 +64,6 @@ pub type PExpectedTypeIndex = Index<PExpected<TypeOrVar>>;
pub type TypeOrVar = EitherIndex<TypeTag, Variable>;
impl Constraints {
pub fn empty() -> Self {
Self {
constraints: Default::default(),
type_slices: Default::default(),
variables: Default::default(),
loc_symbols: Default::default(),
let_constraints: Default::default(),
categories: Default::default(),
pattern_categories: Default::default(),
expectations: Default::default(),
pattern_expectations: Default::default(),
includes_tags: Default::default(),
strings: Default::default(),
sketched_rows: Default::default(),
eq: Default::default(),
pattern_eq: Default::default(),
cycles: Default::default(),
}
}
pub fn new() -> Self {
let constraints = Vec::new();
let type_slices = Vec::with_capacity(16);

View file

@ -1148,6 +1148,7 @@ fn deep_copy_type_vars<C: CopyEnv>(
})
})
}
ErasedLambda => ErasedLambda,
RangedNumber(range) => {
perform_clone!(RangedNumber(range))

View file

@ -88,6 +88,23 @@ pub struct Annotation {
pub region: Region,
}
impl Annotation {
fn freshen(mut self, var_store: &mut VarStore) -> Self {
let mut substitutions = MutMap::default();
for v in self.introduced_variables.lambda_sets.iter_mut() {
let new = var_store.fresh();
substitutions.insert(*v, new);
*v = new;
}
self.signature.substitute_variables(&substitutions);
self
}
}
#[derive(Debug)]
pub(crate) struct CanDefs {
defs: Vec<Option<Def>>,
@ -1638,6 +1655,14 @@ pub(crate) fn sort_can_defs_new(
}
};
let host_annotation = if exposed_symbols.contains(&symbol) {
def.annotation
.clone()
.map(|a| (var_store.fresh(), a.freshen(var_store)))
} else {
None
};
if is_initial && !exposed_symbols.contains(&symbol) {
env.problem(Problem::DefsOnlyUsedInRecursion(1, def.region()));
}
@ -1649,6 +1674,7 @@ pub(crate) fn sort_can_defs_new(
Loc::at(def.loc_expr.region, closure_data),
def.expr_var,
def.annotation,
host_annotation,
specializes,
);
}
@ -1658,55 +1684,80 @@ pub(crate) fn sort_can_defs_new(
def.loc_expr,
def.expr_var,
def.annotation,
host_annotation,
specializes,
);
}
}
} else {
match def.loc_pattern.value {
Pattern::Identifier(symbol) => match def.loc_expr.value {
Closure(closure_data) => {
declarations.push_function_def(
Loc::at(def.loc_pattern.region, symbol),
Loc::at(def.loc_expr.region, closure_data),
def.expr_var,
def.annotation,
None,
);
Pattern::Identifier(symbol) => {
let host_annotation = if exposed_symbols.contains(&symbol) {
def.annotation
.clone()
.map(|a| (var_store.fresh(), a.freshen(var_store)))
} else {
None
};
match def.loc_expr.value {
Closure(closure_data) => {
declarations.push_function_def(
Loc::at(def.loc_pattern.region, symbol),
Loc::at(def.loc_expr.region, closure_data),
def.expr_var,
def.annotation,
host_annotation,
None,
);
}
_ => {
declarations.push_value_def(
Loc::at(def.loc_pattern.region, symbol),
def.loc_expr,
def.expr_var,
def.annotation,
host_annotation,
None,
);
}
}
_ => {
declarations.push_value_def(
Loc::at(def.loc_pattern.region, symbol),
def.loc_expr,
def.expr_var,
def.annotation,
None,
);
}
},
}
Pattern::AbilityMemberSpecialization {
ident: symbol,
specializes,
} => match def.loc_expr.value {
Closure(closure_data) => {
declarations.push_function_def(
Loc::at(def.loc_pattern.region, symbol),
Loc::at(def.loc_expr.region, closure_data),
def.expr_var,
def.annotation,
Some(specializes),
);
} => {
let host_annotation = if exposed_symbols.contains(&symbol) {
def.annotation
.clone()
.map(|a| (var_store.fresh(), a.freshen(var_store)))
} else {
None
};
match def.loc_expr.value {
Closure(closure_data) => {
declarations.push_function_def(
Loc::at(def.loc_pattern.region, symbol),
Loc::at(def.loc_expr.region, closure_data),
def.expr_var,
def.annotation,
host_annotation,
Some(specializes),
);
}
_ => {
declarations.push_value_def(
Loc::at(def.loc_pattern.region, symbol),
def.loc_expr,
def.expr_var,
def.annotation,
host_annotation,
Some(specializes),
);
}
}
_ => {
declarations.push_value_def(
Loc::at(def.loc_pattern.region, symbol),
def.loc_expr,
def.expr_var,
def.annotation,
Some(specializes),
);
}
},
}
_ => {
declarations.push_destructure_def(
def.loc_pattern,
@ -1749,6 +1800,14 @@ pub(crate) fn sort_can_defs_new(
Some(r) => Some(Region::span_across(&r, &def.region())),
};
let host_annotation = if exposed_symbols.contains(&symbol) {
def.annotation
.clone()
.map(|a| (var_store.fresh(), a.freshen(var_store)))
} else {
None
};
match def.loc_expr.value {
Closure(closure_data) => {
declarations.push_recursive_def(
@ -1756,6 +1815,7 @@ pub(crate) fn sort_can_defs_new(
Loc::at(def.loc_expr.region, closure_data),
def.expr_var,
def.annotation,
host_annotation,
specializes,
);
}
@ -1765,6 +1825,7 @@ pub(crate) fn sort_can_defs_new(
def.loc_expr,
def.expr_var,
def.annotation,
host_annotation,
specializes,
);
}

View file

@ -140,6 +140,7 @@ fn index_var(
| Content::FlexAbleVar(_, _)
| Content::RigidAbleVar(_, _)
| Content::LambdaSet(_)
| Content::ErasedLambda
| Content::RangedNumber(..) => return Err(TypeError),
Content::Error => return Err(TypeError),
Content::RecursionVar {

View file

@ -18,7 +18,7 @@ use roc_module::called_via::CalledVia;
use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol;
use roc_parse::ast::{self, Defs, StrLiteral};
use roc_parse::ast::{self, Defs, PrecedenceConflict, StrLiteral};
use roc_parse::ident::Accessor;
use roc_parse::pattern::PatternType::*;
use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError};
@ -2376,11 +2376,115 @@ fn flatten_str_literal<'a>(
}
}
/// Comments, newlines, and nested interpolation are disallowed inside interpolation
pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
match expr {
ast::Expr::Var { .. } => true,
ast::Expr::RecordAccess(sub_expr, _) => is_valid_interpolation(sub_expr),
_ => false,
// These definitely contain neither comments nor newlines, so they are valid
ast::Expr::Var { .. }
| ast::Expr::SingleQuote(_)
| ast::Expr::Str(StrLiteral::PlainLine(_))
| ast::Expr::Float(_)
| ast::Expr::Num(_)
| ast::Expr::NonBase10Int { .. }
| ast::Expr::AccessorFunction(_)
| ast::Expr::Crash
| ast::Expr::Underscore(_)
| ast::Expr::MalformedIdent(_, _)
| ast::Expr::Tag(_)
| ast::Expr::OpaqueRef(_)
| ast::Expr::MalformedClosure => true,
// Newlines are disallowed inside interpolation, and these all require newlines
ast::Expr::Dbg(_, _)
| ast::Expr::Defs(_, _)
| ast::Expr::Expect(_, _)
| ast::Expr::When(_, _)
| ast::Expr::Backpassing(_, _, _)
| ast::Expr::IngestedFile(_, _)
| ast::Expr::SpaceBefore(_, _)
| ast::Expr::Str(StrLiteral::Block(_))
| ast::Expr::SpaceAfter(_, _) => false,
// These can contain subexpressions, so we need to recursively check those
ast::Expr::Str(StrLiteral::Line(segments)) => {
segments.iter().all(|segment| match segment {
ast::StrSegment::EscapedChar(_)
| ast::StrSegment::Unicode(_)
| ast::StrSegment::Plaintext(_) => true,
// Disallow nested interpolation. Alternatively, we could allow it but require
// a comment above it apologizing to the next person who has to read the code.
ast::StrSegment::Interpolated(_) => false,
})
}
ast::Expr::Record(fields) => fields.iter().all(|loc_field| match loc_field.value {
ast::AssignedField::RequiredValue(_label, loc_comments, loc_val)
| ast::AssignedField::OptionalValue(_label, loc_comments, loc_val) => {
loc_comments.is_empty() && is_valid_interpolation(&loc_val.value)
}
ast::AssignedField::Malformed(_) | ast::AssignedField::LabelOnly(_) => true,
ast::AssignedField::SpaceBefore(_, _) | ast::AssignedField::SpaceAfter(_, _) => false,
}),
ast::Expr::Tuple(fields) => fields
.iter()
.all(|loc_field| is_valid_interpolation(&loc_field.value)),
ast::Expr::MultipleRecordBuilders(loc_expr)
| ast::Expr::UnappliedRecordBuilder(loc_expr)
| ast::Expr::PrecedenceConflict(PrecedenceConflict { expr: loc_expr, .. })
| ast::Expr::UnaryOp(loc_expr, _)
| ast::Expr::Closure(_, loc_expr) => is_valid_interpolation(&loc_expr.value),
ast::Expr::TupleAccess(sub_expr, _)
| ast::Expr::ParensAround(sub_expr)
| ast::Expr::RecordAccess(sub_expr, _) => is_valid_interpolation(sub_expr),
ast::Expr::Apply(loc_expr, args, _called_via) => {
is_valid_interpolation(&loc_expr.value)
&& args
.iter()
.all(|loc_arg| is_valid_interpolation(&loc_arg.value))
}
ast::Expr::BinOps(loc_exprs, loc_expr) => {
is_valid_interpolation(&loc_expr.value)
&& loc_exprs
.iter()
.all(|(loc_expr, _binop)| is_valid_interpolation(&loc_expr.value))
}
ast::Expr::If(branches, final_branch) => {
is_valid_interpolation(&final_branch.value)
&& branches.iter().all(|(loc_before, loc_after)| {
is_valid_interpolation(&loc_before.value)
&& is_valid_interpolation(&loc_after.value)
})
}
ast::Expr::List(elems) => elems
.iter()
.all(|loc_expr| is_valid_interpolation(&loc_expr.value)),
ast::Expr::RecordUpdate { update, fields } => {
is_valid_interpolation(&update.value)
&& fields.iter().all(|loc_field| match loc_field.value {
ast::AssignedField::RequiredValue(_label, loc_comments, loc_val)
| ast::AssignedField::OptionalValue(_label, loc_comments, loc_val) => {
loc_comments.is_empty() && is_valid_interpolation(&loc_val.value)
}
ast::AssignedField::Malformed(_) | ast::AssignedField::LabelOnly(_) => true,
ast::AssignedField::SpaceBefore(_, _)
| ast::AssignedField::SpaceAfter(_, _) => false,
})
}
ast::Expr::RecordBuilder(fields) => fields.iter().all(|loc_field| match loc_field.value {
ast::RecordBuilderField::Value(_label, comments, loc_expr) => {
comments.is_empty() && is_valid_interpolation(&loc_expr.value)
}
ast::RecordBuilderField::ApplyValue(
_label,
comments_before,
comments_after,
loc_expr,
) => {
comments_before.is_empty()
&& comments_after.is_empty()
&& is_valid_interpolation(&loc_expr.value)
}
ast::RecordBuilderField::Malformed(_) | ast::RecordBuilderField::LabelOnly(_) => true,
ast::RecordBuilderField::SpaceBefore(_, _)
| ast::RecordBuilderField::SpaceAfter(_, _) => false,
}),
}
}
@ -2535,6 +2639,8 @@ pub struct Declarations {
// used for ability member specializatons.
pub specializes: VecMap<usize, Symbol>,
pub host_exposed_annotations: VecMap<usize, (Variable, crate::def::Annotation)>,
pub function_bodies: Vec<Loc<FunctionDef>>,
pub expressions: Vec<Loc<Expr>>,
pub destructs: Vec<DestructureDef>,
@ -2556,6 +2662,7 @@ impl Declarations {
variables: Vec::with_capacity(capacity),
symbols: Vec::with_capacity(capacity),
annotations: Vec::with_capacity(capacity),
host_exposed_annotations: VecMap::new(),
function_bodies: Vec::with_capacity(capacity),
expressions: Vec::with_capacity(capacity),
specializes: VecMap::default(), // number of specializations is probably low
@ -2586,6 +2693,7 @@ impl Declarations {
loc_closure_data: Loc<ClosureData>,
expr_var: Variable,
annotation: Option<Annotation>,
host_annotation: Option<(Variable, Annotation)>,
specializes: Option<Symbol>,
) -> usize {
let index = self.declarations.len();
@ -2608,6 +2716,11 @@ impl Declarations {
Recursive::TailRecursive => DeclarationTag::TailRecursive(function_def_index),
};
if let Some(annotation) = host_annotation {
self.host_exposed_annotations
.insert(self.declarations.len(), annotation);
}
self.declarations.push(tag);
self.variables.push(expr_var);
self.symbols.push(symbol);
@ -2628,6 +2741,7 @@ impl Declarations {
loc_closure_data: Loc<ClosureData>,
expr_var: Variable,
annotation: Option<Annotation>,
host_annotation: Option<(Variable, Annotation)>,
specializes: Option<Symbol>,
) -> usize {
let index = self.declarations.len();
@ -2643,6 +2757,11 @@ impl Declarations {
let function_def_index = Index::push_new(&mut self.function_bodies, loc_function_def);
if let Some(annotation) = host_annotation {
self.host_exposed_annotations
.insert(self.declarations.len(), annotation);
}
self.declarations
.push(DeclarationTag::Function(function_def_index));
self.variables.push(expr_var);
@ -2700,10 +2819,16 @@ impl Declarations {
loc_expr: Loc<Expr>,
expr_var: Variable,
annotation: Option<Annotation>,
host_annotation: Option<(Variable, Annotation)>,
specializes: Option<Symbol>,
) -> usize {
let index = self.declarations.len();
if let Some(annotation) = host_annotation {
self.host_exposed_annotations
.insert(self.declarations.len(), annotation);
}
self.declarations.push(DeclarationTag::Value);
self.variables.push(expr_var);
self.symbols.push(symbol);
@ -2758,6 +2883,7 @@ impl Declarations {
def.expr_var,
def.annotation,
None,
None,
);
}
@ -2768,6 +2894,7 @@ impl Declarations {
def.expr_var,
def.annotation,
None,
None,
);
}
},
@ -2778,6 +2905,7 @@ impl Declarations {
def.expr_var,
def.annotation,
None,
None,
);
}
},

View file

@ -7,7 +7,9 @@ use roc_module::called_via::BinOp::Pizza;
use roc_module::called_via::{BinOp, CalledVia};
use roc_module::ident::ModuleName;
use roc_parse::ast::Expr::{self, *};
use roc_parse::ast::{AssignedField, Collection, RecordBuilderField, ValueDef, WhenBranch};
use roc_parse::ast::{
AssignedField, Collection, RecordBuilderField, StrLiteral, StrSegment, ValueDef, WhenBranch,
};
use roc_region::all::{Loc, Region};
// BinOp precedence logic adapted from Gluon by Markus Westerlind
@ -129,7 +131,6 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
Float(..)
| Num(..)
| NonBase10Int { .. }
| Str(_)
| SingleQuote(_)
| AccessorFunction(_)
| Var { .. }
@ -144,6 +145,28 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
| IngestedFile(_, _)
| Crash => loc_expr,
Str(str_literal) => match str_literal {
StrLiteral::PlainLine(_) => loc_expr,
StrLiteral::Line(segments) => {
let region = loc_expr.region;
let value = Str(StrLiteral::Line(desugar_str_segments(arena, segments)));
arena.alloc(Loc { region, value })
}
StrLiteral::Block(lines) => {
let region = loc_expr.region;
let new_lines = Vec::from_iter_in(
lines
.iter()
.map(|segments| desugar_str_segments(arena, segments)),
arena,
);
let value = Str(StrLiteral::Block(new_lines.into_bump_slice()));
arena.alloc(Loc { region, value })
}
},
TupleAccess(sub_expr, paths) => {
let region = loc_expr.region;
let loc_sub_expr = Loc {
@ -444,6 +467,34 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
}
}
fn desugar_str_segments<'a>(
arena: &'a Bump,
segments: &'a [StrSegment<'a>],
) -> &'a [StrSegment<'a>] {
Vec::from_iter_in(
segments.iter().map(|segment| match segment {
StrSegment::Plaintext(_) | StrSegment::Unicode(_) | StrSegment::EscapedChar(_) => {
*segment
}
StrSegment::Interpolated(loc_expr) => {
let loc_desugared = desugar_expr(
arena,
arena.alloc(Loc {
region: loc_expr.region,
value: *loc_expr.value,
}),
);
StrSegment::Interpolated(Loc {
region: loc_desugared.region,
value: arena.alloc(loc_desugared.value),
})
}
}),
arena,
)
.into_bump_slice()
}
fn desugar_field<'a>(
arena: &'a Bump,
field: &'a AssignedField<'a, Expr<'a>>,

View file

@ -0,0 +1,19 @@
[package]
name = "roc_checkmate"
description = "A framework for debugging the solver."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies]
roc_checkmate_schema = { path = "../checkmate_schema" }
roc_module = { path = "../module" }
roc_solve_schema = { path = "../solve_schema" }
roc_types = { path = "../types" }
chrono.workspace = true
[build-dependencies]
roc_checkmate_schema = { path = "../checkmate_schema" }
serde_json.workspace = true

View file

@ -0,0 +1,5 @@
# `checkmate`
A tool to debug the solver (checker + inference + specialization engine).
See [the document](https://rwx.notion.site/Type-debugging-tools-de42260060784cacbaf08ea4d61e0eb9?pvs=4).

View file

@ -0,0 +1,13 @@
use std::fs;
use roc_checkmate_schema::AllEvents;
fn main() {
println!("cargo:rerun-if-changed=../checkmate_schema");
let schema = AllEvents::schema();
fs::write(
"schema.json",
serde_json::to_string_pretty(&schema).unwrap(),
)
.unwrap();
}

View file

@ -0,0 +1,907 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "AllEvents",
"type": "array",
"items": {
"$ref": "#/definitions/Event"
},
"definitions": {
"AliasKind": {
"oneOf": [
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Structural"
]
}
}
},
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Opaque"
]
}
}
}
]
},
"AliasTypeVariables": {
"type": "object",
"required": [
"infer_ext_in_output_position_variables",
"lambda_set_variables",
"type_variables"
],
"properties": {
"infer_ext_in_output_position_variables": {
"type": "array",
"items": {
"$ref": "#/definitions/Variable"
}
},
"lambda_set_variables": {
"type": "array",
"items": {
"$ref": "#/definitions/Variable"
}
},
"type_variables": {
"type": "array",
"items": {
"$ref": "#/definitions/Variable"
}
}
}
},
"ClosureType": {
"type": "object",
"required": [
"environment",
"function"
],
"properties": {
"environment": {
"type": "array",
"items": {
"$ref": "#/definitions/Variable"
}
},
"function": {
"$ref": "#/definitions/Symbol"
}
}
},
"Content": {
"oneOf": [
{
"type": "object",
"required": [
"type"
],
"properties": {
"name": {
"type": [
"string",
"null"
]
},
"type": {
"type": "string",
"enum": [
"Flex"
]
}
}
},
{
"type": "object",
"required": [
"name",
"type"
],
"properties": {
"name": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"Rigid"
]
}
}
},
{
"type": "object",
"required": [
"abilities",
"type"
],
"properties": {
"abilities": {
"type": "array",
"items": {
"$ref": "#/definitions/Symbol"
}
},
"name": {
"type": [
"string",
"null"
]
},
"type": {
"type": "string",
"enum": [
"FlexAble"
]
}
}
},
{
"type": "object",
"required": [
"abilities",
"name",
"type"
],
"properties": {
"abilities": {
"type": "array",
"items": {
"$ref": "#/definitions/Symbol"
}
},
"name": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"RigidAble"
]
}
}
},
{
"type": "object",
"required": [
"structure",
"type"
],
"properties": {
"name": {
"type": [
"string",
"null"
]
},
"structure": {
"$ref": "#/definitions/Variable"
},
"type": {
"type": "string",
"enum": [
"Recursive"
]
}
}
},
{
"type": "object",
"required": [
"ambient_function",
"solved",
"type",
"unspecialized"
],
"properties": {
"ambient_function": {
"$ref": "#/definitions/Variable"
},
"recursion_var": {
"anyOf": [
{
"$ref": "#/definitions/Variable"
},
{
"type": "null"
}
]
},
"solved": {
"type": "array",
"items": {
"$ref": "#/definitions/ClosureType"
}
},
"type": {
"type": "string",
"enum": [
"LambdaSet"
]
},
"unspecialized": {
"type": "array",
"items": {
"$ref": "#/definitions/UnspecializedClosureType"
}
}
}
},
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"ErasedLambda"
]
}
}
},
{
"type": "object",
"required": [
"kind",
"name",
"real_variable",
"type",
"variables"
],
"properties": {
"kind": {
"$ref": "#/definitions/AliasKind"
},
"name": {
"$ref": "#/definitions/Symbol"
},
"real_variable": {
"$ref": "#/definitions/Variable"
},
"type": {
"type": "string",
"enum": [
"Alias"
]
},
"variables": {
"$ref": "#/definitions/AliasTypeVariables"
}
}
},
{
"type": "object",
"required": [
"symbol",
"type",
"variables"
],
"properties": {
"symbol": {
"$ref": "#/definitions/Symbol"
},
"type": {
"type": "string",
"enum": [
"Apply"
]
},
"variables": {
"type": "array",
"items": {
"$ref": "#/definitions/Variable"
}
}
}
},
{
"type": "object",
"required": [
"arguments",
"lambda_type",
"ret",
"type"
],
"properties": {
"arguments": {
"type": "array",
"items": {
"$ref": "#/definitions/Variable"
}
},
"lambda_type": {
"$ref": "#/definitions/Variable"
},
"ret": {
"$ref": "#/definitions/Variable"
},
"type": {
"type": "string",
"enum": [
"Function"
]
}
}
},
{
"type": "object",
"required": [
"extension",
"fields",
"type"
],
"properties": {
"extension": {
"$ref": "#/definitions/Variable"
},
"fields": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/RecordField"
}
},
"type": {
"type": "string",
"enum": [
"Record"
]
}
}
},
{
"type": "object",
"required": [
"elements",
"extension",
"type"
],
"properties": {
"elements": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/Variable"
}
},
"extension": {
"$ref": "#/definitions/Variable"
},
"type": {
"type": "string",
"enum": [
"Tuple"
]
}
}
},
{
"type": "object",
"required": [
"extension",
"tags",
"type"
],
"properties": {
"extension": {
"$ref": "#/definitions/TagUnionExtension"
},
"tags": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"$ref": "#/definitions/Variable"
}
}
},
"type": {
"type": "string",
"enum": [
"TagUnion"
]
}
}
},
{
"type": "object",
"required": [
"extension",
"functions",
"tags",
"type"
],
"properties": {
"extension": {
"$ref": "#/definitions/TagUnionExtension"
},
"functions": {
"type": "array",
"items": {
"$ref": "#/definitions/Symbol"
}
},
"tags": {
"type": "array",
"items": {
"type": "string"
}
},
"type": {
"type": "string",
"enum": [
"FunctionOrTagUnion"
]
}
}
},
{
"type": "object",
"required": [
"extension",
"recursion_var",
"tags",
"type"
],
"properties": {
"extension": {
"$ref": "#/definitions/TagUnionExtension"
},
"recursion_var": {
"$ref": "#/definitions/Variable"
},
"tags": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"$ref": "#/definitions/Variable"
}
}
},
"type": {
"type": "string",
"enum": [
"RecursiveTagUnion"
]
}
}
},
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"EmptyRecord"
]
}
}
},
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"EmptyTuple"
]
}
}
},
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"EmptyTagUnion"
]
}
}
},
{
"type": "object",
"required": [
"range",
"type"
],
"properties": {
"range": {
"$ref": "#/definitions/NumericRange"
},
"type": {
"type": "string",
"enum": [
"RangedNumber"
]
}
}
},
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Error"
]
}
}
}
]
},
"Event": {
"oneOf": [
{
"type": "object",
"required": [
"left",
"mode",
"right",
"subevents",
"type"
],
"properties": {
"left": {
"$ref": "#/definitions/Variable"
},
"mode": {
"$ref": "#/definitions/UnificationMode"
},
"right": {
"$ref": "#/definitions/Variable"
},
"subevents": {
"type": "array",
"items": {
"$ref": "#/definitions/Event"
}
},
"success": {
"type": [
"boolean",
"null"
]
},
"type": {
"type": "string",
"enum": [
"Unification"
]
}
}
},
{
"type": "object",
"required": [
"from",
"to",
"type"
],
"properties": {
"from": {
"$ref": "#/definitions/Variable"
},
"to": {
"$ref": "#/definitions/Variable"
},
"type": {
"type": "string",
"enum": [
"VariableUnified"
]
}
}
},
{
"type": "object",
"required": [
"type",
"variable"
],
"properties": {
"content": {
"anyOf": [
{
"$ref": "#/definitions/Content"
},
{
"type": "null"
}
]
},
"rank": {
"anyOf": [
{
"$ref": "#/definitions/Rank"
},
{
"type": "null"
}
]
},
"type": {
"type": "string",
"enum": [
"VariableSetDescriptor"
]
},
"variable": {
"$ref": "#/definitions/Variable"
}
}
}
]
},
"NumericRange": {
"type": "object",
"required": [
"kind",
"min_width",
"signed"
],
"properties": {
"kind": {
"$ref": "#/definitions/NumericRangeKind"
},
"min_width": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"signed": {
"type": "boolean"
}
}
},
"NumericRangeKind": {
"oneOf": [
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Int"
]
}
}
},
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"AnyNum"
]
}
}
}
]
},
"Rank": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"RecordField": {
"type": "object",
"required": [
"field_type",
"kind"
],
"properties": {
"field_type": {
"$ref": "#/definitions/Variable"
},
"kind": {
"$ref": "#/definitions/RecordFieldKind"
}
}
},
"RecordFieldKind": {
"oneOf": [
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Demanded"
]
}
}
},
{
"type": "object",
"required": [
"rigid",
"type"
],
"properties": {
"rigid": {
"type": "boolean"
},
"type": {
"type": "string",
"enum": [
"Required"
]
}
}
},
{
"type": "object",
"required": [
"rigid",
"type"
],
"properties": {
"rigid": {
"type": "boolean"
},
"type": {
"type": "string",
"enum": [
"Optional"
]
}
}
}
]
},
"Symbol": {
"type": "string"
},
"TagUnionExtension": {
"oneOf": [
{
"type": "object",
"required": [
"type",
"variable"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Openness"
]
},
"variable": {
"$ref": "#/definitions/Variable"
}
}
},
{
"type": "object",
"required": [
"type",
"variable"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Any"
]
},
"variable": {
"$ref": "#/definitions/Variable"
}
}
}
]
},
"UnificationMode": {
"oneOf": [
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Eq"
]
}
}
},
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Present"
]
}
}
},
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"LambdaSetSpecialization"
]
}
}
}
]
},
"UnspecializedClosureType": {
"type": "object",
"required": [
"ability_member",
"lambda_set_region",
"specialization"
],
"properties": {
"ability_member": {
"$ref": "#/definitions/Symbol"
},
"lambda_set_region": {
"type": "integer",
"format": "uint8",
"minimum": 0.0
},
"specialization": {
"$ref": "#/definitions/Variable"
}
}
},
"Variable": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
}
}

View file

@ -0,0 +1,175 @@
use std::error::Error;
use roc_checkmate_schema::{AllEvents, Event};
use roc_types::subs as s;
use crate::convert::AsSchema;
#[derive(Debug)]
pub struct Collector {
events: AllEvents,
current_event_path: Vec<usize>,
}
impl Default for Collector {
fn default() -> Self {
Self::new()
}
}
impl Collector {
pub fn new() -> Self {
Self {
events: AllEvents(Vec::new()),
current_event_path: Vec::new(),
}
}
pub fn unify(&mut self, subs: &s::Subs, from: s::Variable, to: s::Variable) {
let to = to.as_schema(subs);
let from = from.as_schema(subs);
self.add_event(Event::VariableUnified { to, from });
}
pub fn set_content(&mut self, subs: &s::Subs, var: s::Variable, content: s::Content) {
let variable = var.as_schema(subs);
let content = content.as_schema(subs);
self.add_event(Event::VariableSetDescriptor {
variable,
content: Some(content),
rank: None,
});
}
pub fn set_rank(&mut self, subs: &s::Subs, var: s::Variable, rank: s::Rank) {
let variable = var.as_schema(subs);
let rank = rank.as_schema(subs);
self.add_event(Event::VariableSetDescriptor {
variable,
rank: Some(rank),
content: None,
});
}
pub fn set_descriptor(&mut self, subs: &s::Subs, var: s::Variable, descriptor: s::Descriptor) {
let variable = var.as_schema(subs);
let rank = descriptor.rank.as_schema(subs);
let content = descriptor.content.as_schema(subs);
self.add_event(Event::VariableSetDescriptor {
variable,
rank: Some(rank),
content: Some(content),
});
}
pub fn start_unification(
&mut self,
subs: &s::Subs,
left: s::Variable,
right: s::Variable,
mode: roc_solve_schema::UnificationMode,
) {
let left = left.as_schema(subs);
let right = right.as_schema(subs);
let mode = mode.as_schema(subs);
let subevents = Vec::new();
self.add_event(Event::Unification {
left,
right,
mode,
subevents,
success: None,
});
}
pub fn end_unification(
&mut self,
subs: &s::Subs,
left: s::Variable,
right: s::Variable,
success: bool,
) {
let current_event = self.get_path_event();
match current_event {
EventW::Sub(Event::Unification {
left: l,
right: r,
success: s,
..
}) => {
assert_eq!(left.as_schema(subs), *l);
assert_eq!(right.as_schema(subs), *r);
assert!(s.is_none());
*s = Some(success);
}
_ => panic!("end_unification called when not in a unification"),
}
self.current_event_path.pop();
}
pub fn write(&self, writer: impl std::io::Write) -> Result<(), Box<dyn Error>> {
self.events.write(writer)?;
Ok(())
}
fn add_event(&mut self, event: impl Into<Event>) {
let mut event = event.into();
let is_appendable = EventW::Sub(&mut event).appendable();
let event = event;
let path_event = self.get_path_event();
let new_event_index = path_event.append(event);
if is_appendable {
self.current_event_path.push(new_event_index);
}
}
fn get_path_event(&mut self) -> EventW {
let mut event = EventW::Top(&mut self.events);
for i in &self.current_event_path {
event = event.index(*i);
}
event
}
}
enum EventW<'a> {
Top(&'a mut AllEvents),
Sub(&'a mut Event),
}
impl<'a> EventW<'a> {
fn append(self, event: Event) -> usize {
let list = self.subevents_mut().unwrap();
let index = list.len();
list.push(event);
index
}
fn appendable(self) -> bool {
self.subevents().is_some()
}
fn index(self, index: usize) -> EventW<'a> {
Self::Sub(&mut self.subevents_mut().unwrap()[index])
}
}
impl<'a> EventW<'a> {
fn subevents(self) -> Option<&'a Vec<Event>> {
use EventW::*;
match self {
Top(events) => Some(&events.0),
Sub(Event::Unification { subevents, .. }) => Some(subevents),
Sub(Event::VariableUnified { .. } | Event::VariableSetDescriptor { .. }) => None,
}
}
fn subevents_mut(self) -> Option<&'a mut Vec<Event>> {
use EventW::*;
match self {
Top(events) => Some(&mut events.0),
Sub(Event::Unification { subevents, .. }) => Some(subevents),
Sub(Event::VariableUnified { .. } | Event::VariableSetDescriptor { .. }) => None,
}
}
}

View file

@ -0,0 +1,323 @@
use std::collections::HashMap;
use roc_module::{ident, symbol};
use roc_types::{
num,
subs::{self, GetSubsSlice, Subs, SubsIndex, SubsSlice, UnionLabels},
types,
};
use roc_checkmate_schema::{
AliasKind, AliasTypeVariables, ClosureType, Content, NumericRange, NumericRangeKind, Rank,
RecordField, RecordFieldKind, Symbol, TagUnionExtension, UnificationMode,
UnspecializedClosureType, Variable,
};
pub trait AsSchema<T> {
fn as_schema(&self, subs: &Subs) -> T;
}
impl<T, U> AsSchema<Option<U>> for Option<T>
where
T: AsSchema<U>,
T: Copy,
{
fn as_schema(&self, subs: &Subs) -> Option<U> {
self.map(|i| i.as_schema(subs))
}
}
impl<T, U> AsSchema<Vec<U>> for &[T]
where
T: AsSchema<U>,
{
fn as_schema(&self, subs: &Subs) -> Vec<U> {
self.iter().map(|i| i.as_schema(subs)).collect()
}
}
impl<T, U> AsSchema<U> for SubsIndex<T>
where
Subs: std::ops::Index<SubsIndex<T>, Output = T>,
T: AsSchema<U>,
{
fn as_schema(&self, subs: &Subs) -> U {
subs[*self].as_schema(subs)
}
}
impl<T, U> AsSchema<Vec<U>> for SubsSlice<T>
where
Subs: GetSubsSlice<T>,
T: AsSchema<U>,
{
fn as_schema(&self, subs: &Subs) -> Vec<U> {
subs.get_subs_slice(*self)
.iter()
.map(|i| i.as_schema(subs))
.collect()
}
}
impl AsSchema<Content> for subs::Content {
fn as_schema(&self, subs: &Subs) -> Content {
use {subs::Content as A, Content as B};
match self {
A::FlexVar(name) => B::Flex(name.as_schema(subs)),
A::RigidVar(name) => B::Rigid(name.as_schema(subs)),
A::FlexAbleVar(name, abilities) => {
B::FlexAble(name.as_schema(subs), abilities.as_schema(subs))
}
A::RigidAbleVar(name, abilities) => {
B::RigidAble(name.as_schema(subs), abilities.as_schema(subs))
}
A::RecursionVar {
structure,
opt_name,
} => B::Recursive(opt_name.as_schema(subs), structure.as_schema(subs)),
A::LambdaSet(lambda_set) => lambda_set.as_schema(subs),
A::ErasedLambda => B::ErasedLambda(),
A::Structure(flat_type) => flat_type.as_schema(subs),
A::Alias(name, type_vars, real_var, kind) => B::Alias(
name.as_schema(subs),
type_vars.as_schema(subs),
real_var.as_schema(subs),
kind.as_schema(subs),
),
A::RangedNumber(range) => B::RangedNumber(range.as_schema(subs)),
A::Error => B::Error(),
}
}
}
impl AsSchema<Content> for subs::FlatType {
fn as_schema(&self, subs: &Subs) -> Content {
match self {
subs::FlatType::Apply(symbol, variables) => {
Content::Apply(symbol.as_schema(subs), variables.as_schema(subs))
}
subs::FlatType::Func(arguments, closure, ret) => Content::Function(
arguments.as_schema(subs),
closure.as_schema(subs),
ret.as_schema(subs),
),
subs::FlatType::Record(fields, ext) => {
Content::Record(fields.as_schema(subs), ext.as_schema(subs))
}
subs::FlatType::Tuple(elems, ext) => {
Content::Tuple(elems.as_schema(subs), ext.as_schema(subs))
}
subs::FlatType::TagUnion(tags, ext) => {
Content::TagUnion(tags.as_schema(subs), ext.as_schema(subs))
}
subs::FlatType::FunctionOrTagUnion(tags, functions, ext) => {
Content::FunctionOrTagUnion(
functions.as_schema(subs),
tags.as_schema(subs),
ext.as_schema(subs),
)
}
subs::FlatType::RecursiveTagUnion(rec_var, tags, ext) => Content::RecursiveTagUnion(
rec_var.as_schema(subs),
tags.as_schema(subs),
ext.as_schema(subs),
),
subs::FlatType::EmptyRecord => Content::EmptyRecord(),
subs::FlatType::EmptyTuple => Content::EmptyTuple(),
subs::FlatType::EmptyTagUnion => Content::EmptyTagUnion(),
}
}
}
impl AsSchema<Content> for subs::LambdaSet {
fn as_schema(&self, subs: &Subs) -> Content {
let subs::LambdaSet {
solved,
unspecialized,
recursion_var,
ambient_function,
} = self;
Content::LambdaSet(
solved.as_schema(subs),
unspecialized.as_schema(subs),
recursion_var.as_schema(subs),
ambient_function.as_schema(subs),
)
}
}
impl AsSchema<String> for ident::Lowercase {
fn as_schema(&self, _subs: &Subs) -> String {
self.to_string()
}
}
impl AsSchema<Symbol> for symbol::Symbol {
fn as_schema(&self, _subs: &Subs) -> Symbol {
Symbol(format!("{:#?}", self))
}
}
impl AsSchema<Variable> for subs::Variable {
fn as_schema(&self, _subs: &Subs) -> Variable {
Variable(self.index())
}
}
impl AsSchema<Option<Variable>> for subs::OptVariable {
fn as_schema(&self, _subs: &Subs) -> Option<Variable> {
self.into_variable().map(|i| i.as_schema(_subs))
}
}
impl AsSchema<Vec<ClosureType>> for UnionLabels<symbol::Symbol> {
fn as_schema(&self, subs: &Subs) -> Vec<ClosureType> {
self.iter_from_subs(subs)
.map(|(function, environment)| ClosureType {
function: function.as_schema(subs),
environment: environment.as_schema(subs),
})
.collect()
}
}
impl AsSchema<UnspecializedClosureType> for types::Uls {
fn as_schema(&self, subs: &Subs) -> UnspecializedClosureType {
let types::Uls(specialization, ability_member, lambda_set_region) = self;
UnspecializedClosureType {
specialization: specialization.as_schema(subs),
ability_member: ability_member.as_schema(subs),
lambda_set_region: *lambda_set_region,
}
}
}
impl AsSchema<AliasTypeVariables> for subs::AliasVariables {
fn as_schema(&self, subs: &Subs) -> AliasTypeVariables {
let type_variables = self.type_variables().as_schema(subs);
let lambda_set_variables = self.lambda_set_variables().as_schema(subs);
let infer_ext_in_output_position_variables =
self.infer_ext_in_output_variables().as_schema(subs);
AliasTypeVariables {
type_variables,
lambda_set_variables,
infer_ext_in_output_position_variables,
}
}
}
impl AsSchema<AliasKind> for types::AliasKind {
fn as_schema(&self, _subs: &Subs) -> AliasKind {
match self {
types::AliasKind::Structural => AliasKind::Structural,
types::AliasKind::Opaque => AliasKind::Opaque,
}
}
}
impl AsSchema<HashMap<String, RecordField>> for subs::RecordFields {
fn as_schema(&self, subs: &Subs) -> HashMap<String, RecordField> {
let mut map = HashMap::new();
for (name, var, field) in self.iter_all() {
let name = name.as_schema(subs);
let field_type = var.as_schema(subs);
let kind = field.as_schema(subs);
map.insert(name, RecordField { field_type, kind });
}
map
}
}
impl AsSchema<RecordFieldKind> for types::RecordField<()> {
fn as_schema(&self, _subs: &Subs) -> RecordFieldKind {
match self {
types::RecordField::Demanded(_) => RecordFieldKind::Demanded,
types::RecordField::Required(_) => RecordFieldKind::Required { rigid: false },
types::RecordField::Optional(_) => RecordFieldKind::Optional { rigid: false },
types::RecordField::RigidRequired(_) => RecordFieldKind::Required { rigid: true },
types::RecordField::RigidOptional(_) => RecordFieldKind::Optional { rigid: true },
}
}
}
impl AsSchema<HashMap<u32, Variable>> for subs::TupleElems {
fn as_schema(&self, subs: &Subs) -> HashMap<u32, Variable> {
let mut map = HashMap::new();
for (index, var) in self.iter_all() {
let name = subs[index] as _;
let var = var.as_schema(subs);
map.insert(name, var);
}
map
}
}
impl AsSchema<HashMap<String, Vec<Variable>>> for subs::UnionTags {
fn as_schema(&self, subs: &Subs) -> HashMap<String, Vec<Variable>> {
let mut map = HashMap::new();
for (tag, payloads) in self.iter_from_subs(subs) {
map.insert(tag.as_schema(subs), payloads.as_schema(subs));
}
map
}
}
impl AsSchema<TagUnionExtension> for subs::TagExt {
fn as_schema(&self, subs: &Subs) -> TagUnionExtension {
match self {
subs::TagExt::Openness(var) => TagUnionExtension::Openness(var.as_schema(subs)),
subs::TagExt::Any(var) => TagUnionExtension::Any(var.as_schema(subs)),
}
}
}
impl AsSchema<NumericRange> for num::NumericRange {
fn as_schema(&self, _subs: &Subs) -> NumericRange {
let kind =
match self {
num::NumericRange::IntAtLeastSigned(_)
| num::NumericRange::IntAtLeastEitherSign(_) => NumericRangeKind::Int,
num::NumericRange::NumAtLeastSigned(_)
| num::NumericRange::NumAtLeastEitherSign(_) => NumericRangeKind::AnyNum,
};
let min_width = self.min_width();
let (signedness, width) = min_width.signedness_and_width();
let signed = signedness.is_signed();
NumericRange {
kind,
signed,
min_width: width,
}
}
}
impl AsSchema<String> for ident::TagName {
fn as_schema(&self, _subs: &Subs) -> String {
self.0.to_string()
}
}
impl AsSchema<Rank> for subs::Rank {
fn as_schema(&self, _subs: &Subs) -> Rank {
Rank(self.into_usize() as _)
}
}
impl AsSchema<UnificationMode> for roc_solve_schema::UnificationMode {
fn as_schema(&self, _subs: &Subs) -> UnificationMode {
if self.is_eq() {
UnificationMode::Eq
} else if self.is_present() {
UnificationMode::Present
} else if self.is_lambda_set_specialization() {
UnificationMode::LambdaSetSpecialization
} else {
unreachable!()
}
}
}

View file

@ -0,0 +1,62 @@
mod collector;
mod convert;
pub use collector::Collector;
pub fn is_checkmate_enabled() -> bool {
#[cfg(debug_assertions)]
{
let flag = std::env::var("ROC_CHECKMATE");
flag.as_deref() == Ok("1")
}
#[cfg(not(debug_assertions))]
{
false
}
}
#[macro_export]
macro_rules! debug_checkmate {
($opt_collector:expr, $cm:ident => $expr:expr) => {
#[cfg(debug_assertions)]
{
if let Some($cm) = $opt_collector.as_mut() {
$expr
}
}
};
}
#[macro_export]
macro_rules! dump_checkmate {
($opt_collector:expr) => {
#[cfg(debug_assertions)]
{
if let Some(cm) = $opt_collector.as_ref() {
$crate::dump_checkmate(cm);
}
}
};
}
pub fn dump_checkmate(collector: &Collector) {
let timestamp = chrono::Local::now().format("%Y%m%d_%H-%M-%S");
let filename = format!("checkmate_{timestamp}.json");
let fi = std::fs::File::create(&filename).unwrap();
collector.write(fi).unwrap();
eprintln!("Wrote checkmate output to {filename}");
}
#[macro_export]
macro_rules! with_checkmate {
({ on => $on:expr, off => $off:expr, }) => {{
#[cfg(debug_assertions)]
{
$on
}
#[cfg(not(debug_assertions))]
{
$off
}
}};
}

View file

@ -0,0 +1,17 @@
/node_modules
/.pnp
.pnp.js
/coverage
/build
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View file

@ -0,0 +1,4 @@
{
"tsserver.useLocalTsdk": true,
"tsserver.tsdk": "${workspaceFolder}/node_modules/typescript/lib"
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,54 @@
{
"name": "checkmate",
"version": "0.1.0",
"private": true,
"dependencies": {
"@dagrejs/dagre": "^1.0.2",
"elkjs": "^0.8.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.14.2",
"react-router-hash-link": "^2.4.3",
"reactflow": "^11.7.4"
},
"scripts": {
"start": "react-scripts start",
"build": "build:codegen && react-scripts build",
"build:codegen": "node ./scripts/gen_schema_dts.js",
"check": "tsc",
"lint": "eslint src/ scripts/",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@types/jest": "^27.5.2",
"@types/node": "^16.18.38",
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@types/react-router-hash-link": "^2.4.6",
"clsx": "^2.0.0",
"json-schema-to-typescript": "^13.0.2",
"react-scripts": "5.0.1",
"tailwindcss": "^3.3.3",
"tiny-typed-emitter": "^2.1.0",
"typescript": "^4.9.5"
}
}

View file

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="checkmate in N" />
<title>Checkmate</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>

View file

@ -0,0 +1,24 @@
const {compileFromFile} = require("json-schema-to-typescript");
const fs = require("node:fs/promises");
const path = require("node:path");
const SCHEMA_PATH = path.resolve(
__dirname,
"..",
"..",
"schema.json"
);
const DTS_PATH = path.resolve(
__dirname,
"..",
"src",
"schema.d.ts"
);
async function main() {
const result = await compileFromFile(SCHEMA_PATH);
await fs.writeFile(DTS_PATH, result);
}
main().catch(console.error);

View file

@ -0,0 +1,42 @@
import React from "react";
import FileInput, { LoadedEvents } from "./components/FileInput";
import Ui from "./components/Ui";
import data from "./checkmate.json";
import { AllEvents } from "./schema";
import { BrowserRouter } from "react-router-dom";
export default function App() {
const [events, setEvents] = React.useState<LoadedEvents | null>({
kind: "ok",
events: data as AllEvents,
});
return (
<BrowserRouter>
<div className="w-screen h-screen p-2 bg-gray-100 flex flex-col">
<div>
<FileInput setResult={setEvents} />
</div>
<div className="flex-1 overflow-hidden">
<EventsWrapper events={events} />
</div>
</div>
</BrowserRouter>
);
}
interface EventsWrapperProps {
events: LoadedEvents | null;
}
function EventsWrapper({ events }: EventsWrapperProps): JSX.Element {
if (events === null) {
return <div></div>;
}
switch (events.kind) {
case "ok":
return <Ui events={events.events} />;
case "err":
return <div className="text-red-400 text-lg">{events.error}</div>;
}
}

View file

@ -0,0 +1,545 @@
[
{
"type": "Unification",
"left": 1540,
"right": 71,
"mode": { "type": "Eq" },
"success": true,
"subevents": [
{
"type": "VariableSetDescriptor",
"variable": 71,
"rank": 1,
"content": {
"type": "Function",
"arguments": [1543, 1544],
"lambda_type": 1542,
"ret": 1541
}
},
{ "type": "VariableUnified", "from": 1540, "to": 71 },
{ "type": "VariableUnified", "from": 71, "to": 71 }
]
},
{
"type": "Unification",
"left": 71,
"right": 1546,
"mode": { "type": "Eq" },
"success": true,
"subevents": [
{
"type": "Unification",
"left": 1543,
"right": 67,
"mode": { "type": "Eq" },
"success": true,
"subevents": [
{
"type": "VariableSetDescriptor",
"variable": 67,
"rank": 1,
"content": {
"type": "Alias",
"name": "`Num.Num`",
"variables": {
"type_variables": [1545],
"lambda_set_variables": [],
"infer_ext_in_output_position_variables": []
},
"real_variable": 1545,
"kind": { "type": "Opaque" }
}
},
{ "type": "VariableUnified", "from": 1543, "to": 67 },
{ "type": "VariableUnified", "from": 67, "to": 67 }
]
},
{
"type": "Unification",
"left": 1544,
"right": 69,
"mode": { "type": "Eq" },
"success": true,
"subevents": [
{
"type": "VariableSetDescriptor",
"variable": 69,
"rank": 1,
"content": {
"type": "Alias",
"name": "`Num.Num`",
"variables": {
"type_variables": [1545],
"lambda_set_variables": [],
"infer_ext_in_output_position_variables": []
},
"real_variable": 1545,
"kind": { "type": "Opaque" }
}
},
{ "type": "VariableUnified", "from": 1544, "to": 69 },
{ "type": "VariableUnified", "from": 69, "to": 69 }
]
},
{
"type": "Unification",
"left": 1541,
"right": 73,
"mode": { "type": "Eq" },
"success": true,
"subevents": [
{
"type": "VariableSetDescriptor",
"variable": 73,
"rank": 1,
"content": {
"type": "Alias",
"name": "`Num.Num`",
"variables": {
"type_variables": [1545],
"lambda_set_variables": [],
"infer_ext_in_output_position_variables": []
},
"real_variable": 1545,
"kind": { "type": "Opaque" }
}
},
{ "type": "VariableUnified", "from": 1541, "to": 73 },
{ "type": "VariableUnified", "from": 73, "to": 73 }
]
},
{
"type": "Unification",
"left": 1542,
"right": 72,
"mode": { "type": "Eq" },
"success": true,
"subevents": [
{
"type": "VariableSetDescriptor",
"variable": 72,
"rank": 1,
"content": {
"type": "LambdaSet",
"solved": [{ "function": "`Num.add`", "environment": [] }],
"unspecialized": [],
"recursion_var": null,
"ambient_function": 1540
}
},
{ "type": "VariableUnified", "from": 1542, "to": 72 },
{ "type": "VariableUnified", "from": 72, "to": 72 }
]
},
{
"type": "VariableSetDescriptor",
"variable": 1546,
"rank": 1,
"content": {
"type": "Function",
"arguments": [67, 69],
"lambda_type": 1542,
"ret": 73
}
},
{ "type": "VariableUnified", "from": 71, "to": 1546 },
{ "type": "VariableUnified", "from": 1546, "to": 1546 }
]
},
{
"type": "Unification",
"left": 66,
"right": 1547,
"mode": { "type": "Eq" },
"success": true,
"subevents": [
{
"type": "VariableSetDescriptor",
"variable": 1547,
"rank": 1,
"content": {
"type": "RangedNumber",
"range": {
"kind": { "type": "AnyNum" },
"signed": true,
"min_width": 8
}
}
},
{ "type": "VariableUnified", "from": 66, "to": 1547 },
{ "type": "VariableUnified", "from": 1547, "to": 1547 }
]
},
{
"type": "Unification",
"left": 1548,
"right": 67,
"mode": { "type": "Eq" },
"success": true,
"subevents": [
{
"type": "Unification",
"left": 66,
"right": 1545,
"mode": { "type": "Eq" },
"success": true,
"subevents": [
{
"type": "VariableSetDescriptor",
"variable": 1545,
"rank": 1,
"content": {
"type": "RangedNumber",
"range": {
"kind": { "type": "AnyNum" },
"signed": true,
"min_width": 8
}
}
},
{ "type": "VariableUnified", "from": 1547, "to": 1545 },
{ "type": "VariableUnified", "from": 1545, "to": 1545 }
]
},
{
"type": "VariableSetDescriptor",
"variable": 67,
"rank": 1,
"content": {
"type": "Alias",
"name": "`Num.Num`",
"variables": {
"type_variables": [66],
"lambda_set_variables": [],
"infer_ext_in_output_position_variables": []
},
"real_variable": 66,
"kind": { "type": "Opaque" }
}
},
{ "type": "VariableUnified", "from": 1548, "to": 67 },
{ "type": "VariableUnified", "from": 67, "to": 67 }
]
},
{
"type": "Unification",
"left": 68,
"right": 1549,
"mode": { "type": "Eq" },
"success": true,
"subevents": [
{
"type": "VariableSetDescriptor",
"variable": 1549,
"rank": 1,
"content": {
"type": "RangedNumber",
"range": {
"kind": { "type": "AnyNum" },
"signed": true,
"min_width": 8
}
}
},
{ "type": "VariableUnified", "from": 68, "to": 1549 },
{ "type": "VariableUnified", "from": 1549, "to": 1549 }
]
},
{
"type": "Unification",
"left": 1550,
"right": 69,
"mode": { "type": "Eq" },
"success": true,
"subevents": [
{
"type": "Unification",
"left": 68,
"right": 1545,
"mode": { "type": "Eq" },
"success": true,
"subevents": [
{
"type": "VariableSetDescriptor",
"variable": 1545,
"rank": 1,
"content": {
"type": "RangedNumber",
"range": {
"kind": { "type": "AnyNum" },
"signed": true,
"min_width": 8
}
}
},
{ "type": "VariableUnified", "from": 1549, "to": 1545 },
{ "type": "VariableUnified", "from": 1545, "to": 1545 }
]
},
{
"type": "VariableSetDescriptor",
"variable": 69,
"rank": 1,
"content": {
"type": "Alias",
"name": "`Num.Num`",
"variables": {
"type_variables": [68],
"lambda_set_variables": [],
"infer_ext_in_output_position_variables": []
},
"real_variable": 68,
"kind": { "type": "Opaque" }
}
},
{ "type": "VariableUnified", "from": 1550, "to": 69 },
{ "type": "VariableUnified", "from": 69, "to": 69 }
]
},
{
"type": "Unification",
"left": 73,
"right": 1551,
"mode": { "type": "Eq" },
"success": true,
"subevents": [
{
"type": "Unification",
"left": 73,
"right": 1552,
"mode": { "type": "Eq" },
"success": true,
"subevents": [
{
"type": "Unification",
"left": 1545,
"right": 1553,
"mode": { "type": "Eq" },
"success": true,
"subevents": [
{
"type": "Unification",
"left": 1556,
"right": 1554,
"mode": { "type": "Eq" },
"success": true,
"subevents": [
{
"type": "VariableSetDescriptor",
"variable": 1554,
"rank": 1,
"content": {
"type": "Alias",
"name": "`Num.Unsigned8`",
"variables": {
"type_variables": [],
"lambda_set_variables": [],
"infer_ext_in_output_position_variables": []
},
"real_variable": 1555,
"kind": { "type": "Opaque" }
}
},
{ "type": "VariableUnified", "from": 1556, "to": 1554 },
{ "type": "VariableUnified", "from": 1554, "to": 1554 }
]
}
]
},
{
"type": "Unification",
"left": 1545,
"right": 1553,
"mode": { "type": "Eq" },
"success": true,
"subevents": [
{
"type": "VariableSetDescriptor",
"variable": 1553,
"rank": 1,
"content": {
"type": "Alias",
"name": "`Num.Integer`",
"variables": {
"type_variables": [1556],
"lambda_set_variables": [],
"infer_ext_in_output_position_variables": []
},
"real_variable": 1556,
"kind": { "type": "Opaque" }
}
},
{ "type": "VariableUnified", "from": 1545, "to": 1553 },
{ "type": "VariableUnified", "from": 1553, "to": 1553 }
]
},
{
"type": "VariableSetDescriptor",
"variable": 1552,
"rank": 1,
"content": {
"type": "Alias",
"name": "`Num.Num`",
"variables": {
"type_variables": [1545],
"lambda_set_variables": [],
"infer_ext_in_output_position_variables": []
},
"real_variable": 1545,
"kind": { "type": "Opaque" }
}
},
{ "type": "VariableUnified", "from": 73, "to": 1552 },
{ "type": "VariableUnified", "from": 1552, "to": 1552 }
]
}
]
},
{
"type": "Unification",
"left": 1551,
"right": 75,
"mode": { "type": "Eq" },
"success": true,
"subevents": [
{
"type": "VariableSetDescriptor",
"variable": 75,
"rank": 1,
"content": {
"type": "Alias",
"name": "`Num.U8`",
"variables": {
"type_variables": [],
"lambda_set_variables": [],
"infer_ext_in_output_position_variables": []
},
"real_variable": 1552,
"kind": { "type": "Structural" }
}
},
{ "type": "VariableUnified", "from": 1551, "to": 75 },
{ "type": "VariableUnified", "from": 75, "to": 75 }
]
},
{
"type": "Unification",
"left": 80,
"right": 75,
"mode": { "type": "Eq" },
"success": true,
"subevents": [
{
"type": "VariableSetDescriptor",
"variable": 75,
"rank": 1,
"content": {
"type": "Alias",
"name": "`Num.U8`",
"variables": {
"type_variables": [],
"lambda_set_variables": [],
"infer_ext_in_output_position_variables": []
},
"real_variable": 1552,
"kind": { "type": "Structural" }
}
},
{ "type": "VariableUnified", "from": 80, "to": 75 },
{ "type": "VariableUnified", "from": 75, "to": 75 }
]
},
{
"type": "Unification",
"left": 1557,
"right": 79,
"mode": { "type": "Eq" },
"success": true,
"subevents": [
{
"type": "VariableSetDescriptor",
"variable": 79,
"rank": 1,
"content": {
"type": "Alias",
"name": "`Bool.Bool`",
"variables": {
"type_variables": [],
"lambda_set_variables": [],
"infer_ext_in_output_position_variables": []
},
"real_variable": 1558,
"kind": { "type": "Opaque" }
}
},
{ "type": "VariableUnified", "from": 1557, "to": 79 },
{ "type": "VariableUnified", "from": 79, "to": 79 }
]
},
{
"type": "Unification",
"left": 79,
"right": 5,
"mode": { "type": "Eq" },
"success": true,
"subevents": [
{
"type": "Unification",
"left": 1558,
"right": 4,
"mode": { "type": "Eq" },
"success": true,
"subevents": [
{
"type": "Unification",
"left": 84,
"right": 3,
"mode": { "type": "Eq" },
"success": true,
"subevents": [
{
"type": "VariableSetDescriptor",
"variable": 3,
"rank": 0,
"content": { "type": "EmptyTagUnion" }
},
{ "type": "VariableUnified", "from": 84, "to": 3 },
{ "type": "VariableUnified", "from": 3, "to": 3 }
]
},
{
"type": "VariableSetDescriptor",
"variable": 4,
"rank": 0,
"content": {
"type": "TagUnion",
"tags": { "False": [], "True": [] },
"extension": { "type": "Any", "variable": 84 }
}
},
{ "type": "VariableUnified", "from": 1558, "to": 4 },
{ "type": "VariableUnified", "from": 4, "to": 4 }
]
},
{
"type": "VariableSetDescriptor",
"variable": 5,
"rank": 0,
"content": {
"type": "Alias",
"name": "`Bool.Bool`",
"variables": {
"type_variables": [],
"lambda_set_variables": [],
"infer_ext_in_output_position_variables": []
},
"real_variable": 1558,
"kind": { "type": "Opaque" }
}
},
{ "type": "VariableUnified", "from": 79, "to": 5 },
{ "type": "VariableUnified", "from": 5, "to": 5 }
]
}
]

View file

@ -0,0 +1,60 @@
import clsx from "clsx";
import { EventEpoch } from "../../engine/engine";
import { HashLink } from "react-router-hash-link";
export enum EpochCellView {
Events,
Graph,
}
function invert(cell: EpochCellView): EpochCellView {
if (cell === EpochCellView.Events) {
return EpochCellView.Graph;
}
return EpochCellView.Events;
}
function asStr(cell: EpochCellView): string {
switch (cell) {
case EpochCellView.Events:
return "events";
case EpochCellView.Graph:
return "graph";
}
}
interface EpochCellProps {
view: EpochCellView;
epoch: EventEpoch;
className?: string;
}
const EPOCH_STYLES_ARRAY = [
"text-slate-900",
"font-mono",
"bg-slate-200",
"p-1",
"py-0",
"rounded-sm",
"ring-1",
"ring-slate-500",
"text-sm",
];
export const EPOCH_STYLES = clsx(...EPOCH_STYLES_ARRAY);
export default function EpochCell({ epoch, className, view }: EpochCellProps) {
const invertedView = invert(view);
return (
<HashLink smooth to={`#${asStr(invertedView)}-${epoch}`}>
<div
id={`${asStr(view)}-${epoch}`}
className={clsx(EPOCH_STYLES, className)}
>
{view === EpochCellView.Graph ? "Epoch " : ""}
{epoch}
</div>
</HashLink>
);
}

View file

@ -0,0 +1,83 @@
import { ComponentProps } from "react";
import clsx from "clsx";
import { QuerySubs, TypeDescriptor } from "../../engine/subs";
import { Variable } from "../../schema";
import DrawHeadConstructor from "../Content/HeadConstructor";
import { contentStyles } from "./../Content";
interface VariableElProps {
variable: Variable;
subs: QuerySubs;
onClick?: (variable: Variable) => void;
nested?: boolean;
raw?: boolean;
}
export function VariableElPretty(props: VariableElProps): JSX.Element {
const { variable, subs } = props;
const desc = subs.get_root(variable);
const content = (
<DrawHeadConstructor
desc={desc}
drawVariablePretty={(variable) => (
<VariableElPretty {...props} variable={variable} nested />
)}
drawVariableRaw={(variable) => (
<VariableElRaw {...props} variable={variable} nested raw />
)}
/>
);
return (
<Helper {...props} desc={desc}>
{content}
</Helper>
);
}
function VariableElRaw(props: VariableElProps): JSX.Element {
const desc = props.subs.get_root(props.variable);
return <Helper {...props} desc={desc}></Helper>;
}
function Helper({
children,
variable,
desc,
onClick,
nested,
raw,
}: VariableElProps &
Pick<ComponentProps<"div">, "children"> & {
desc: TypeDescriptor | undefined;
}): JSX.Element {
const { bg } = contentStyles(desc);
const varHeader =
!nested || raw ? (
<span
className={clsx(
"ring-1 ring-inset ring-black-100 px-1 bg-white rounded-md cursor",
nested ? "text-md" : "p-0.5"
)}
onClick={(e) => {
e.stopPropagation();
onClick?.(variable);
}}
>
{variable}
</span>
) : (
<></>
);
return (
<span
className={clsx(
"rounded-md whitespace-nowrap",
bg,
nested ? "text-sm" : "p-0.5 pl-0 text-base"
)}
>
{varHeader}
{children ? <span className="px-1">{children}</span> : <></>}
</span>
);
}

View file

@ -0,0 +1,344 @@
import { TypeDescriptor } from "../../engine/subs";
import {
ClosureType,
RecordFieldKind,
UnspecializedClosureType,
Variable,
} from "../../schema";
type DrawVariable = (variable: Variable) => JSX.Element;
export interface DrawHeadConstructorProps {
desc: TypeDescriptor | undefined;
drawVariablePretty: DrawVariable;
drawVariableRaw: DrawVariable;
}
export default function DrawHeadConstructor({
desc,
drawVariablePretty,
drawVariableRaw,
}: DrawHeadConstructorProps): JSX.Element {
if (!desc) {
return <>???</>;
}
const content = desc.content;
switch (content.type) {
case "Flex":
case "Rigid": {
const { name } = content;
return name ? <>{name}</> : <>_</>;
}
case "FlexAble":
case "RigidAble": {
const { name, abilities } = content;
const nameEl = name ? <>{name}</> : <>_</>;
return (
<>
{nameEl} has {abilities.join(", ")}
</>
);
}
case "Recursive": {
const { name, structure } = content;
const structureEl = drawVariableRaw(structure);
const nameEl = name ? <>{name} to </> : <></>;
return (
<>
&lt;{nameEl}
{structureEl}&gt;
</>
);
}
case "LambdaSet": {
const { ambient_function, solved, unspecialized, recursion_var } =
content;
const ambientFunctionEl = drawVariableRaw(ambient_function);
const solvedEl = (
<DrawSolved drawVariableRaw={drawVariableRaw} solved={solved} />
);
const unspecializedEl = (
<DrawUnspecialized
drawVariableRaw={drawVariableRaw}
unspecialized={unspecialized}
/>
);
const recursionVarEl = recursion_var ? (
<> as &lt;{drawVariableRaw(recursion_var)}&gt;</>
) : (
<></>
);
return (
<>
[{solvedEl}
{unspecializedEl}]{recursionVarEl} ^{ambientFunctionEl}
</>
);
}
case "ErasedLambda": {
return <>?</>;
}
case "Alias": {
const { kind, name, variables } = content;
const prefix = kind.type === "Opaque" ? "@" : "";
const variablesEl = (
<DrawVarArgumentsList
drawVariableRaw={drawVariableRaw}
variables={variables.type_variables}
/>
);
return (
<span>
{prefix}
{sym(name)}
{variablesEl}
</span>
);
}
case "Apply": {
const { symbol, variables } = content;
const variablesEl = (
<DrawVarArgumentsList
drawVariableRaw={drawVariableRaw}
variables={variables}
/>
);
return (
<>
{sym(symbol)}
{variablesEl}
</>
);
}
case "Function": {
const { arguments: args, lambda_type, ret } = content;
const argsEl = args.map((arg, i) => (
<span key={i}>
{i !== 0 ? ", " : ""}
{drawVariablePretty(arg)}
</span>
));
const lambdaTypeEl = drawVariablePretty(lambda_type);
const retEl = drawVariablePretty(ret);
return (
<>
{argsEl}
{" -"}
{lambdaTypeEl}
{"-> "}
{retEl}
</>
);
}
case "Record": {
const { fields, extension } = content;
const fieldsEl = Object.entries(fields).map(([key, value], i) => {
const { field_type, kind } = value;
return (
<span key={i}>
{i !== 0 ? ", " : ""}
{key} {<DrawFieldKind kind={kind} />}{" "}
{drawVariablePretty(field_type)}
</span>
);
});
return (
<>
{"{"}
{fieldsEl}
{"}"}
{drawVariablePretty(extension)}
</>
);
}
case "Tuple": {
const { elements, extension } = content;
const elemsEl = Object.entries(elements).map(([key, value], i) => {
return (
<span key={i}>
{i !== 0 ? ", " : ""}
{key}: {drawVariablePretty(value)}
</span>
);
});
return (
<>
({elemsEl}){drawVariablePretty(extension)}
</>
);
}
case "TagUnion": {
const { tags, extension } = content;
return (
<>
<DrawTags tags={tags} drawVariableRaw={drawVariableRaw} />
{drawVariablePretty(extension.variable)}
</>
);
}
case "RecursiveTagUnion": {
const { tags, extension, recursion_var } = content;
return (
<>
(<DrawTags tags={tags} drawVariableRaw={drawVariableRaw} />
{drawVariablePretty(extension.variable)} as &lt;
{drawVariableRaw(recursion_var)}&gt;)
</>
);
}
case "FunctionOrTagUnion": {
const { functions, tags, extension } = content;
const functionsEl = functions.map((f, i) => (
<span key={i}>
{i !== 0 ? ", " : ""}
{sym(f)}
</span>
));
const tagsEl = tags.map((t, i) => (
<span key={i}>
{i !== 0 ? ", " : ""}
{t}
</span>
));
return (
<>
[{functionsEl} | {tagsEl}]{drawVariablePretty(extension.variable)}
</>
);
}
case "RangedNumber": {
const {
range: { kind, min_width, signed },
} = content;
switch (kind.type) {
case "AnyNum":
return <>{min_width}+</>;
case "Int":
return signed ? <>{min_width}+</> : <>{min_width}+</>;
}
break;
}
case "EmptyRecord": {
return <>{"{}"}</>;
}
case "EmptyTuple": {
return <>()</>;
}
case "EmptyTagUnion": {
return <>[]</>;
}
case "Error": {
return <></>;
}
}
}
function DrawVarArgumentsList({
variables,
drawVariableRaw,
}: {
variables: Variable[];
drawVariableRaw: DrawVariable;
}): JSX.Element {
return variables.length !== 0 ? (
<>
{" "}
{variables.map((v, i) => (
<span key={i}>{drawVariableRaw(v)}</span>
))}
</>
) : (
<></>
);
}
function DrawSolved({
solved,
drawVariableRaw,
}: {
solved: ClosureType[];
drawVariableRaw: DrawVariable;
}): JSX.Element {
const tags = solved.map(({ environment, function: fn }, i) => (
<span key={i}>
{i !== 0 ? ", " : ""}
<DrawTag
tag={sym(fn)}
variables={environment}
drawVariableRaw={drawVariableRaw}
/>
</span>
));
return <>[{tags}]</>;
}
function DrawTags({
tags,
drawVariableRaw,
}: {
tags: Record<string, Variable[]>;
drawVariableRaw: DrawVariable;
}): JSX.Element {
const tagsEl = Object.entries(tags).map(([tag, vars], i) => (
<span key={i}>
{i !== 0 ? ", " : ""}
<DrawTag tag={tag} variables={vars} drawVariableRaw={drawVariableRaw} />
</span>
));
return <>[{tagsEl}]</>;
}
function DrawUnspecialized({
unspecialized,
drawVariableRaw,
}: {
unspecialized: UnspecializedClosureType[];
drawVariableRaw: DrawVariable;
}): JSX.Element {
const unspecs = unspecialized.map(
({ ability_member, lambda_set_region, specialization }, i) => (
<span key={i}>
{" + "}
{drawVariableRaw(specialization)}:{sym(ability_member)}:
{lambda_set_region}
</span>
)
);
return <>{unspecs}</>;
}
function DrawTag({
tag,
variables,
drawVariableRaw,
}: {
tag: string;
variables: Variable[];
drawVariableRaw: DrawVariable;
}): JSX.Element {
return (
<>
{tag}
<DrawVarArgumentsList
drawVariableRaw={drawVariableRaw}
variables={variables}
/>
</>
);
}
function DrawFieldKind({ kind }: { kind: RecordFieldKind }): JSX.Element {
switch (kind.type) {
case "Required":
case "Demanded":
return <>:</>;
case "Optional":
return <>?</>;
}
}
function sym(symbol: string): string {
if (symbol.startsWith("`")) symbol = symbol.slice(1);
if (symbol.endsWith("`")) symbol = symbol.slice(0, -1);
return symbol.split(".").at(-1)!;
}

View file

@ -0,0 +1,66 @@
import { TypeDescriptor } from "../../engine/subs";
import { assertExhaustive } from "../../utils/exhaustive";
export interface ContentStyles {
name: string;
bg: string;
}
export function contentStyles(desc: TypeDescriptor | undefined): ContentStyles {
if (!desc) {
return { name: "???", bg: "bg-red-500" };
}
const content = desc.content;
switch (content.type) {
case "Flex":
return { name: "Flex", bg: "bg-blue-300" };
case "FlexAble":
return { name: "FlexAble", bg: "bg-blue-400" };
case "Rigid":
return { name: "Rigid", bg: "bg-indigo-300" };
case "RigidAble":
return { name: "RigidAble", bg: "bg-indigo-400" };
case "Recursive":
return { name: "Rec", bg: "bg-blue-grey-500" };
case "LambdaSet":
return { name: "LambdaSet", bg: "bg-green-500" };
case "ErasedLambda":
return { name: "ErasedLambda", bg: "bg-green-700" };
case "Alias": {
switch (content.kind.type) {
case "Structural":
return { name: "Alias", bg: "bg-yellow-300" };
case "Opaque":
return { name: "Opaque", bg: "bg-amber-400" };
default:
assertExhaustive(content.kind);
}
break;
}
case "Apply":
return { name: "Apply", bg: "bg-orange-500" };
case "Function":
return { name: "Func", bg: "bg-teal-400" };
case "Record":
return { name: "Record", bg: "bg-purple-400" };
case "Tuple":
return { name: "Tuple", bg: "bg-deep-purple-400" };
case "TagUnion":
return { name: "Tags", bg: "bg-cyan-200" };
case "FunctionOrTagUnion":
return { name: "Func|Tags", bg: "bg-cyan-300" };
case "RecursiveTagUnion":
return { name: "RecTags", bg: "bg-cyan-400" };
case "RangedNumber":
return { name: "", bg: "bg-lime-400" };
case "EmptyRecord":
return { name: "{}", bg: "bg-purple-400" };
case "EmptyTuple":
return { name: "()", bg: "bg-deep-purple-400" };
case "EmptyTagUnion":
return { name: "[]", bg: "bg-cyan-200" };
case "Error":
return { name: "Error", bg: "bg-red-400" };
}
}

View file

@ -0,0 +1,27 @@
import { EventEpoch } from "../../engine/engine";
import { Variable } from "../../schema";
import { VariableElPretty } from "../Common/Variable";
import { CommonProps } from "./types";
interface VariableProps extends CommonProps {
epoch: EventEpoch;
variable: Variable;
}
export function VariableEl({
engine,
toggleVariableVis,
epoch,
variable,
}: VariableProps): JSX.Element {
engine.stepTo(epoch);
return (
<VariableElPretty
variable={variable}
subs={engine.subs}
onClick={(variable: Variable) => {
toggleVariableVis(variable);
}}
></VariableElPretty>
);
}

View file

@ -0,0 +1,8 @@
import type { Engine, EventEpoch } from "../../engine/engine";
import type { Variable } from "../../schema";
export interface CommonProps {
currentEpoch: EventEpoch;
engine: Engine;
toggleVariableVis: (variable: Variable) => void;
}

View file

@ -0,0 +1,234 @@
import clsx from "clsx";
import React, { useCallback, useMemo, useState } from "react";
import { EventEpoch } from "../engine/engine";
import { lastSubEvent } from "../engine/event_util";
import { UnificationMode, Event } from "../schema";
import { Refine } from "../utils/refine";
import EpochCell, { EpochCellView } from "./Common/EpochCell";
import { CommonProps } from "./EventItem/types";
import { VariableEl } from "./EventItem/Variable";
interface EventListProps extends CommonProps {
events: Event[];
root?: boolean;
}
const MT = "mt-2.5";
const UNFOCUSED = "opacity-40";
export default function EventList(props: EventListProps): JSX.Element {
const { events, root } = props;
return (
<ul className={clsx(MT, root ? "ml-2" : "ml-[1.5em]")}>
{events.map((event, i) => (
<li key={i} className={MT}>
<OneEvent {...props} event={event} />
</li>
))}
</ul>
);
}
interface OneEventProps extends CommonProps {
event: Event;
}
function OneEvent(props: OneEventProps): JSX.Element {
const { event } = props;
switch (event.type) {
case "Unification":
return <Unification {...props} event={event} />;
case "VariableUnified":
return <></>;
case "VariableSetDescriptor":
return <></>;
}
}
const DROPDOWN_CLOSED = "▶";
const DROPDOWN_OPEN = "▼";
const UN_UNKNOWN = "💭";
const UN_SUCCESS = "✅";
const UN_FAILURE = "❌";
function epochInRange(
epoch: EventEpoch,
[start, end]: [EventEpoch, EventEpoch]
): boolean {
return epoch >= start && epoch <= end;
}
interface UnificationProps extends CommonProps {
event: Refine<Event, "Unification">;
}
function Unification(props: UnificationProps): JSX.Element {
const { engine, event, currentEpoch } = props;
const { mode, subevents, success } = event;
const beforeUnificationEpoch = engine.getEventIndex(event);
const afterUnificationEpoch = engine.getEventIndex(lastSubEvent(event));
const containsCurrentEpoch = epochInRange(currentEpoch, [
beforeUnificationEpoch,
afterUnificationEpoch,
]);
const leftVar = useMemo(
() => (epoch: EventEpoch) =>
<VariableEl {...props} epoch={epoch} variable={event.left} />,
[event.left, props]
);
const rightVar = useMemo(
() => (epoch: EventEpoch) =>
<VariableEl {...props} epoch={epoch} variable={event.right} />,
[event.right, props]
);
const [isOpen, setIsOpen] = useState(false);
const modeIcon = useMemo(() => <UnificationModeIcon mode={mode} />, [mode]);
const resultIcon = success ? UN_SUCCESS : UN_FAILURE;
const resultHeadline = <Headline icon={resultIcon}></Headline>;
const epochCell = useMemo(() => {
if (!containsCurrentEpoch) return null;
return (
<EpochCell
view={EpochCellView.Events}
epoch={currentEpoch}
className="inline-block align-middle mr-2"
></EpochCell>
);
}, [containsCurrentEpoch, currentEpoch]);
const getHeadline = useCallback(
({
epoch,
includeEpochIfInRange,
}: {
epoch: EventEpoch;
includeEpochIfInRange: boolean;
}) => {
const topHeadline = (
<Headline icon={isOpen ? UN_UNKNOWN : resultIcon}></Headline>
);
const optEpochCell =
includeEpochIfInRange && containsCurrentEpoch && epochCell;
return (
<button
onClick={() => setIsOpen(!isOpen)}
className="w-full text-left whitespace-nowrap h-full"
>
{optEpochCell}
<span
className={clsx(
"mr-2",
isOpen ? "text-slate-500" : "text-slate-400"
)}
>
{isOpen ? DROPDOWN_OPEN : DROPDOWN_CLOSED}
</span>
{topHeadline} {leftVar(epoch)} {modeIcon} {rightVar(epoch)}
</button>
);
},
[
isOpen,
resultIcon,
containsCurrentEpoch,
epochCell,
leftVar,
modeIcon,
rightVar,
]
);
if (!isOpen) {
const headLine = getHeadline({
epoch: afterUnificationEpoch,
includeEpochIfInRange: true,
});
return (
<div className={clsx(!containsCurrentEpoch && UNFOCUSED)}>{headLine}</div>
);
} else {
const optEpochCellAfter =
afterUnificationEpoch === currentEpoch && epochCell;
const optEpochCellBefore =
beforeUnificationEpoch === currentEpoch && epochCell;
const headlineBefore = getHeadline({
epoch: beforeUnificationEpoch,
includeEpochIfInRange: false,
});
const dropdownTransparent = (
<span className="text-transparent mr-2">{DROPDOWN_OPEN}</span>
);
const headlineAfter = (
<div className={clsx("whitespace-nowrap")}>
{dropdownTransparent}
{resultHeadline} {leftVar(afterUnificationEpoch)} {modeIcon}{" "}
{rightVar(afterUnificationEpoch)}
</div>
);
return (
<div className="grid gap-0 grid-cols-[min-content_min-content_auto]">
{/* Row 1: unification start */}
<div className="row-start-1 col-start-1">{optEpochCellBefore}</div>
<div className="row-start-1 col-start-3">{headlineBefore}</div>
{/* Row 2: inner traces */}
<div className="row-start-2 col-start-1"></div>
<div className="row-start-2 col-start-3">
<EventList
{...props}
root={false}
engine={engine}
events={subevents}
/>
</div>
{/* Row 3: inner traces */}
<div className="row-start-3 col-start-1">{optEpochCellAfter}</div>
<div className="row-start-3 col-start-3">{headlineAfter}</div>
{/* Col 2: dropdown line */}
<div
className={clsx(
"row-start-1 row-end-4 col-start-2 h-full",
"relative z-[1] h-full",
"before:content-[''] before:border-l before:border-slate-500 before:z-[-1]",
"before:absolute before:w-0 before:h-[calc(100%-1.5rem)] before:top-[1rem] before:left-[0.3rem]"
)}
></div>
</div>
);
}
}
function Headline({ icon }: { icon: string }): JSX.Element {
return (
<div className="inline-block align-middle">
<div className="">{icon}</div>
</div>
);
}
function UnificationModeIcon({ mode }: { mode: UnificationMode }): JSX.Element {
switch (mode.type) {
case "Eq":
return <>~</>;
case "Present":
return <>+=</>;
case "LambdaSetSpecialization":
return <>|~|</>;
}
}

View file

@ -0,0 +1,4 @@
import { Variable } from "../../schema";
export type ToggleVariableHandler = (variable: Variable) => void;
export type KeydownHandler = (key: string) => Promise<void>;

View file

@ -0,0 +1,48 @@
import { AllEvents } from "../schema";
export type EventsOk = {
kind: "ok";
events: AllEvents;
};
export type EventsErr = {
kind: "err";
error: string;
};
export type LoadedEvents = EventsOk | EventsErr;
interface FileInputProps {
setResult(result: LoadedEvents): void;
}
export default function FileInput({ setResult }: FileInputProps) {
async function setFile(e: React.ChangeEvent<HTMLInputElement>) {
e.preventDefault();
const files = e.target.files;
if (!files) {
setResult({ kind: "err", error: "Please choose a checkmate file." });
return;
}
const file = files[0];
const buf = await file.text();
try {
const events: AllEvents = JSON.parse(buf);
setResult({ kind: "ok", events });
} catch (e) {
setResult({ kind: "err", error: "Invalid checkmate file." });
return;
}
}
return (
<input
type="file"
name="small-file-input"
id="small-file-input"
onChange={(e) => setFile(e)}
className="block w-full border border-gray-200 shadow-sm rounded-md text-sm
file:bg-roc-purple-bg file:border-0 file:mr-4 file:py-2 file:px-4 cursor-pointer"
></input>
);
}

View file

@ -0,0 +1,244 @@
import clsx from "clsx";
import { Handle, Position } from "reactflow";
import { Variable } from "../../schema";
import { assertExhaustive } from "../../utils/exhaustive";
import { contentStyles } from "../Content";
import { VariableElPretty } from "../Common/Variable";
import { SubsSnapshot, TypeDescriptor } from "../../engine/subs";
import { useEffect, useState } from "react";
import { TypedEmitter } from "tiny-typed-emitter";
type AddSubVariableLink = (from: Variable, subVariable: Variable) => void;
export interface VariableMessageEvents {
focus: (variable: Variable) => void;
}
export interface VariableNodeProps {
data: {
subs: SubsSnapshot;
variable: Variable;
addSubVariableLink: AddSubVariableLink;
isOutlined: boolean;
ee: TypedEmitter<VariableMessageEvents>;
};
targetPosition?: Position;
sourcePosition?: Position;
}
export default function VariableNode({
data,
targetPosition,
sourcePosition,
}: VariableNodeProps): JSX.Element {
const {
variable,
subs,
addSubVariableLink,
isOutlined: isOutlinedProp,
ee: eeProp,
} = data;
const [isOutlined, setIsOutlined] = useState(isOutlinedProp);
useEffect(() => {
eeProp.on("focus", (focusVar: Variable) => {
if (focusVar !== variable) return;
setIsOutlined(true);
});
}, [eeProp, variable]);
useEffect(() => {
if (!isOutlined) return;
const timer = setTimeout(() => {
setIsOutlined(false);
}, 500);
return () => {
clearTimeout(timer);
};
}, [isOutlined]);
const desc = subs.get_root(variable);
const styles = contentStyles(desc);
const basis: BasisProps = {
subs,
origin: variable,
addSubVariableLink,
};
const content = Object.entries(
VariableNodeContent(variable, desc, basis)
).filter((el): el is [string, JSX.Element] => !!el[1]);
let expandedContent = <></>;
if (content.length > 0) {
expandedContent = (
<ul className="text-sm text-left mt-2 space-y-1">
{content.map(([key, value], i) => (
<li key={i} className="space-x-2">
{key}: {value}
</li>
))}
</ul>
);
}
return (
<div
className={clsx(
styles.bg,
"bg-opacity-50 py-2 px-4 rounded-lg border transition ease-in-out duration-700",
isOutlined && "ring-2 ring-blue-500",
"text-center font-mono"
)}
>
<Handle
type="target"
position={targetPosition ?? Position.Top}
isConnectable={false}
/>
<div>
<VariableElPretty variable={variable} subs={subs} />
</div>
{expandedContent}
<Handle
type="source"
position={sourcePosition ?? Position.Bottom}
isConnectable={false}
/>
</div>
);
}
function VariableNodeContent(
variable: Variable,
desc: TypeDescriptor | undefined,
basis: BasisProps
): Record<string, JSX.Element | null> {
if (!desc) return {};
const { content } = desc;
switch (content.type) {
case "Flex":
case "Rigid": {
const { name } = content;
return { name: name ? <>{name}</> : null };
}
case "FlexAble":
case "RigidAble": {
const { name, abilities } = content;
return {
name: <>{name}</>,
abilities: <>[{abilities.join(", ")}]</>,
};
}
case "Recursive": {
const { name, structure } = content;
return {
name: <>{name}</>,
structure: <SubVariable {...basis} variable={structure} />,
};
}
case "LambdaSet": {
const { ambient_function, solved, unspecialized, recursion_var } =
content;
return {
"^": <SubVariable {...basis} variable={ambient_function} />,
as: recursion_var ? (
<SubVariable {...basis} variable={recursion_var} />
) : null,
};
}
case "ErasedLambda": {
return {};
}
case "Alias": {
const { name, real_variable, variables } = content;
return {
name: <>{name}</>,
};
}
case "Apply": {
const { name, variables } = content;
return {
name: <>{name}</>,
};
}
case "Function": {
const { arguments: args, lambda_type, ret } = content;
return {
args: (
<>
{args.map((arg, i) => (
<SubVariable key={i} {...basis} variable={arg} />
))}
</>
),
"||": <SubVariable {...basis} variable={lambda_type} />,
ret: <SubVariable {...basis} variable={ret} />,
};
}
case "FunctionOrTagUnion": {
const { tags, functions, extension } = content;
return {
tags: <>[{tags.join(", ")}]</>,
fns: <>[{functions.join(", ")}]</>,
};
}
case "TagUnion": {
const { tags, extension } = content;
return {};
}
case "RecursiveTagUnion": {
const { recursion_var, extension, tags } = content;
return {
as: <SubVariable {...basis} variable={recursion_var} />,
};
}
case "Record": {
const { fields, extension } = content;
return {};
}
case "Tuple": {
const { elements, extension } = content;
return {};
}
case "RangedNumber": {
const { range } = content;
return {};
}
case "EmptyRecord":
case "EmptyTuple":
case "EmptyTagUnion":
case "Error": {
return {};
}
default: {
return assertExhaustive(content);
}
}
}
interface BasisProps {
subs: SubsSnapshot;
origin: Variable;
addSubVariableLink: AddSubVariableLink;
}
function SubVariable({
subs,
origin,
variable,
addSubVariableLink,
}: {
variable: Variable;
} & BasisProps): JSX.Element {
return (
<VariableElPretty
variable={variable}
subs={subs}
onClick={() => addSubVariableLink(origin, variable)}
/>
);
}

View file

@ -0,0 +1,502 @@
import ELK, {
type ElkNode,
type LayoutOptions,
} from "elkjs/lib/elk.bundled.js";
import ReactFlow, {
Node,
Edge,
Background,
BackgroundVariant,
useReactFlow,
ReactFlowProvider,
NodeChange,
applyNodeChanges,
EdgeChange,
applyEdgeChanges,
Panel,
NodeTypes,
useStore,
ReactFlowState,
Position,
} from "reactflow";
import { useCallback, useEffect, useRef, useState } from "react";
import { Variable } from "../../schema";
import "reactflow/dist/style.css";
import clsx from "clsx";
import VariableNode, {
VariableMessageEvents,
VariableNodeProps,
} from "./VariableNode";
import { SubsSnapshot } from "../../engine/subs";
import { KeydownHandler } from "../Events";
import { TypedEmitter } from "tiny-typed-emitter";
import EpochCell, { EpochCellView } from "../Common/EpochCell";
export interface VariablesGraphProps {
subs: SubsSnapshot;
onVariable: (handler: (variable: Variable) => void) => void;
onKeydown: (handler: KeydownHandler) => void;
}
function horizontalityToPositions(isHorizontal: boolean): {
targetPosition: Position;
sourcePosition: Position;
} {
return {
targetPosition: isHorizontal ? Position.Left : Position.Top,
sourcePosition: isHorizontal ? Position.Right : Position.Bottom,
};
}
interface LayoutedElements {
nodes: Node[];
edges: Edge[];
}
const LAYOUT_CONFIG_DOWN = {
keypress: "j",
emoji: "⬇️",
elkLayoutOptions: {
"elk.algorithm": "layered",
"elk.direction": "DOWN",
},
isHorizontal: false,
} as const;
const LAYOUT_CONFIG_RIGHT = {
keypress: "l",
emoji: "➡️",
elkLayoutOptions: {
"elk.algorithm": "layered",
"elk.direction": "RIGHT",
},
isHorizontal: true,
} as const;
const LAYOUT_CONFIG_RADIAL = {
keypress: "r",
emoji: "🌐",
elkLayoutOptions: {
"elk.algorithm": "radial",
},
isHorizontal: false,
} as const;
const LAYOUT_CONFIG_FORCE = {
keypress: "f",
emoji: "🧲",
elkLayoutOptions: {
"elk.algorithm": "force",
},
isHorizontal: false,
} as const;
type LayoutConfiguration =
| typeof LAYOUT_CONFIG_DOWN
| typeof LAYOUT_CONFIG_RIGHT
| typeof LAYOUT_CONFIG_RADIAL
| typeof LAYOUT_CONFIG_FORCE;
const LAYOUT_CONFIGURATIONS: LayoutConfiguration[] = [
LAYOUT_CONFIG_DOWN,
LAYOUT_CONFIG_RIGHT,
LAYOUT_CONFIG_RADIAL,
LAYOUT_CONFIG_FORCE,
];
type ComputeElkLayoutOptions = Pick<
LayoutConfiguration,
"elkLayoutOptions" | "isHorizontal"
>;
interface ComputeLayoutedElementsProps
extends LayoutedElements,
ComputeElkLayoutOptions {}
// Elk has a *huge* amount of options to configure. To see everything you can
// tweak check out:
//
// - https://www.eclipse.org/elk/reference/algorithms.html
// - https://www.eclipse.org/elk/reference/options.html
const baseElkOptions: LayoutOptions = {
"elk.layered.spacing.nodeNodeBetweenLayers": "100",
"elk.spacing.nodeNode": "80",
};
async function computeLayoutedElements({
nodes,
edges,
elkLayoutOptions,
isHorizontal,
}: ComputeLayoutedElementsProps): Promise<LayoutedElements> {
if (nodes.length === 0) {
return Promise.resolve({
nodes: [],
edges: [],
});
}
const elk = new ELK();
const graph: ElkNode = {
id: "root",
layoutOptions: {
...baseElkOptions,
...elkLayoutOptions,
},
//@ts-ignore
children: nodes.map((node) => ({
...node,
// Adjust the target and source handle positions based on the layout
// direction.
targetPosition: isHorizontal ? "left" : "top",
sourcePosition: isHorizontal ? "right" : "bottom",
// Hardcode a width and height for elk to use when layouting.
//width: 150,
//height: 50,
})),
//@ts-ignore
edges: edges,
};
const layoutedGraph = await elk.layout(graph);
if (!layoutedGraph.children || !layoutedGraph.edges) {
throw new Error("Elk did not return a valid graph");
}
return {
//@ts-ignore
nodes: layoutedGraph.children.map((node) => ({
...node,
// React Flow expects a position property on the node instead of `x`
// and `y` fields.
position: { x: node.x, y: node.y },
})),
//@ts-ignore
edges: layoutedGraph.edges,
};
}
const NODE_TYPES: NodeTypes = {
variable: VariableNode,
};
type VariableNodeData = VariableNodeProps["data"];
type RFVariableNode = Node<VariableNodeData>;
function newVariable(
id: string,
data: VariableNodeData,
isHorizontal: boolean
): RFVariableNode {
return {
id,
position: { x: 0, y: 0 },
type: "variable",
data,
...horizontalityToPositions(isHorizontal),
};
}
function addNodeChange(node: Node, existingNodes: Node[]): NodeChange | null {
if (existingNodes.some((n) => n.id === node.id)) {
return null;
}
return {
type: "add",
item: node,
};
}
function addEdgeChange(edge: Edge, existingEdges: Edge[]): EdgeChange | null {
if (existingEdges.some((e) => e.id === edge.id)) {
return null;
}
return {
type: "add",
item: edge,
};
}
// Auto-layout logic due in part to the `feldera/dbsp` project, licensed under
// the MIT license.
//
// The source code for the original project can be found at
// https://github.com/feldera/dbsp/blob/585a1926a6d3a0f8176dc80db5e906ec7d095400/web-ui/src/streaming/builder/hooks/useAutoLayout.ts#L215
// and its license at
// https://github.com/feldera/dbsp/blob/585a1926a6d3a0f8176dc80db5e906ec7d095400/LICENSE
const nodeCountSelector = (state: ReactFlowState) =>
state.nodeInternals.size + state.edges.length;
const nodesSetInViewSelector = (state: ReactFlowState) =>
Array.from(state.nodeInternals.values()).every(
(node) => node.width && node.height
);
type RedoLayoutFn = () => Promise<void>;
function useRedoLayout(options: ComputeElkLayoutOptions): RedoLayoutFn {
const nodeCount = useStore(nodeCountSelector);
const nodesInitialized = useStore(nodesSetInViewSelector);
const { getNodes, setNodes, getEdges } = useReactFlow();
const instance = useReactFlow();
return useCallback(async () => {
if (!nodeCount || !nodesInitialized) {
return;
}
const { nodes } = await computeLayoutedElements({
nodes: getNodes(),
edges: getEdges(),
...options,
});
setNodes(nodes);
window.requestAnimationFrame(() => {
instance.fitView();
});
}, [
nodeCount,
nodesInitialized,
getNodes,
getEdges,
options,
setNodes,
instance,
]);
}
// Does positioning of the nodes in the graph.
function useAutoLayout(options: ComputeElkLayoutOptions) {
const redoLayout = useRedoLayout(options);
useEffect(() => {
// This wrapping is of course redundant, but exercised for the purpose of
// explicitness.
async function inner() {
await redoLayout();
}
inner();
}, [redoLayout]);
}
function useKeydown({
layoutConfig,
setLayoutConfig,
onKeydown,
}: {
layoutConfig: LayoutConfiguration;
setLayoutConfig: React.Dispatch<React.SetStateAction<LayoutConfiguration>>;
onKeydown: (handler: KeydownHandler) => void;
}) {
const redoLayout = useRedoLayout(layoutConfig);
const keyDownHandler = useCallback(
async (key: string) => {
switch (key) {
case "c": {
await redoLayout();
return;
}
default: {
break;
}
}
for (const config of LAYOUT_CONFIGURATIONS) {
if (key === config.keypress) {
setLayoutConfig(config);
return;
}
}
},
[redoLayout, setLayoutConfig]
);
onKeydown(async (key) => {
await keyDownHandler(key);
});
}
function Graph({
subs,
onVariable,
onKeydown,
}: VariablesGraphProps): JSX.Element {
const initialNodes: Node[] = [];
const initialEdges: Edge[] = [];
const ee = useRef(new TypedEmitter<VariableMessageEvents>());
const [layoutConfig, setLayoutConfig] =
useState<LayoutConfiguration>(LAYOUT_CONFIG_DOWN);
const [elements, setElements] = useState<LayoutedElements>({
nodes: initialNodes,
edges: initialEdges,
});
useAutoLayout(layoutConfig);
useKeydown({
layoutConfig,
setLayoutConfig,
onKeydown,
});
const onNodesChange = useCallback((changes: NodeChange[]) => {
setElements(({ nodes, edges }) => {
return {
nodes: applyNodeChanges(changes, nodes),
edges,
};
});
}, []);
const onEdgesChange = useCallback((changes: EdgeChange[]) => {
setElements(({ nodes, edges }) => {
return {
nodes,
edges: applyEdgeChanges(changes, edges),
};
});
}, []);
const addSubVariableLink = useCallback(
(fromN: Variable, subLinkN: Variable) => {
fromN = subs.get_root_key(fromN);
subLinkN = subs.get_root_key(subLinkN);
const from = fromN.toString();
const to = subLinkN.toString();
setElements(({ nodes, edges }) => {
const optNewNode = addNodeChange(
newVariable(
to,
{
subs,
variable: subLinkN,
addSubVariableLink,
isOutlined: true,
ee: ee.current,
},
layoutConfig.isHorizontal
),
nodes
);
const newNodes = optNewNode
? applyNodeChanges([optNewNode], nodes)
: nodes;
const optNewEdge = addEdgeChange(
{ id: `${from}->${to}`, source: from, target: to },
edges
);
const newEdges = optNewEdge
? applyEdgeChanges([optNewEdge], edges)
: edges;
return { nodes: newNodes, edges: newEdges };
});
ee.current.emit("focus", subLinkN);
},
[layoutConfig, subs]
);
const addNode = useCallback(
(variableN: Variable) => {
variableN = subs.get_root_key(variableN);
const variable = variableN.toString();
setElements(({ nodes, edges }) => {
const optNewNode = addNodeChange(
newVariable(
variable,
{
subs,
variable: variableN,
addSubVariableLink,
isOutlined: true,
ee: ee.current,
},
layoutConfig.isHorizontal
),
nodes
);
const newNodes = optNewNode
? applyNodeChanges([optNewNode], nodes)
: nodes;
return { nodes: newNodes, edges: edges };
});
ee.current.emit("focus", variableN);
},
[subs, addSubVariableLink, layoutConfig]
);
onVariable(addNode);
return (
<ReactFlow
nodes={elements.nodes}
edges={elements.edges}
onNodesChange={(e) => onNodesChange(e)}
onEdgesChange={(e) => onEdgesChange(e)}
fitView
nodesDraggable
nodesConnectable={false}
nodeTypes={NODE_TYPES}
proOptions={{
// https://reactflow.dev/docs/guides/remove-attribution/
hideAttribution: true,
}}
>
<Panel position="top-left">
<EpochCell view={EpochCellView.Graph} epoch={subs.epoch}></EpochCell>
</Panel>
<Panel position="top-right">
<LayoutPanel
layoutConfig={layoutConfig}
setLayoutConfig={setLayoutConfig}
/>
</Panel>
<Background variant={BackgroundVariant.Dots} />
</ReactFlow>
);
}
interface LayoutPanelProps {
layoutConfig: LayoutConfiguration;
setLayoutConfig: React.Dispatch<React.SetStateAction<LayoutConfiguration>>;
}
function LayoutPanel({
layoutConfig,
setLayoutConfig,
}: LayoutPanelProps): JSX.Element {
const commonStyle = "rounded cursor-pointer text-2xl select-none";
return (
<>
{LAYOUT_CONFIGURATIONS.map((config, i) => (
<span
key={i}
className={clsx(
commonStyle,
i !== 0 ? "ml-2" : "",
config !== layoutConfig ? "opacity-50" : ""
)}
onClick={() => {
setLayoutConfig(config);
}}
>
{config.emoji}
</span>
))}
</>
);
}
export default function VariablesGraph(props: VariablesGraphProps) {
return (
<ReactFlowProvider>
<Graph {...props} />
</ReactFlowProvider>
);
}

View file

@ -0,0 +1,68 @@
import React, { useState } from "react";
import { AllEvents, Variable } from "../schema";
import { Engine } from "../engine/engine";
import EventList from "./EventList";
import VariablesGraph from "./Graph/VariablesGraph";
import { TypedEmitter } from "tiny-typed-emitter";
import { KeydownHandler, ToggleVariableHandler } from "./Events";
interface UiProps {
events: AllEvents;
}
interface MessageEvents {
toggleVariable: ToggleVariableHandler;
keydown: KeydownHandler;
}
export default function Ui({ events }: UiProps): JSX.Element {
const engine = new Engine(events);
const ee = new TypedEmitter<MessageEvents>();
const toggleVariableHandlers: ToggleVariableHandler[] = [];
const keydownHandlers: KeydownHandler[] = [];
ee.on("toggleVariable", (variable: Variable) => {
toggleVariableHandlers.forEach((handler) => handler(variable));
});
ee.on("keydown", async (key: string) => {
await Promise.all(keydownHandlers.map((handler) => handler(key)));
});
engine.stepTo(engine.lastEventIndex());
const subs = engine.subsSnapshot();
// _setEpoch to be used in the future!
const [epoch, _setEpoch] = useState(subs.epoch);
return (
<div
className="flex flex-col md:flex-row gap-0 w-full h-full"
onKeyDown={(e) => {
ee.emit("keydown", e.key);
}}
>
<div className="font-mono mt-2 text-lg md:flex-1 overflow-scroll">
<EventList
engine={engine}
root
events={events}
toggleVariableVis={(variable: Variable) =>
ee.emit("toggleVariable", variable)
}
currentEpoch={epoch}
/>
</div>
<div className="flex-1 min-h-[50%] h-full">
<VariablesGraph
subs={subs}
onVariable={(handler) => {
toggleVariableHandlers.push(handler);
}}
onKeydown={(handler) => {
keydownHandlers.push(handler);
}}
/>
</div>
</div>
);
}

View file

@ -0,0 +1,157 @@
import { Event, Variable } from "../schema";
import { assertExhaustive } from "../utils/exhaustive";
import {
ChangeEvent,
makeDeleteVariable,
makeRevertVariable,
RollbackChange,
Subs,
SubsSnapshot,
} from "./subs";
export type EventEpoch = number & { __eventIndex: never };
function* flattenEvents(events: Event[]): Generator<Event> {
for (const event of events) {
yield event;
switch (event.type) {
case "Unification": {
yield* flattenEvents(event.subevents);
break;
}
case "VariableUnified":
case "VariableSetDescriptor":
break;
default:
assertExhaustive(event);
}
}
}
function getFlatEvents(events: Event[]): {
flatEvents: Event[];
map: Map<Event, EventEpoch>;
} {
const map = new Map<Event, EventEpoch>();
const flatEvents = Array.from(flattenEvents(events));
let i = 0;
for (const event of flatEvents) {
map.set(event, i as EventEpoch);
i++;
}
return { flatEvents, map };
}
export class Engine {
#eventIndexMap: Map<Event, EventEpoch>;
#events: Event[];
#subs: Subs = Subs.new();
#reverseEvents: Map<EventEpoch, RollbackChange> = new Map();
#nextIndexForward: EventEpoch = 0 as EventEpoch;
constructor(events: Event[]) {
const { flatEvents, map } = getFlatEvents(events);
this.#eventIndexMap = map;
this.#events = flatEvents;
}
getEventIndex(event: Event): EventEpoch {
const index = this.#eventIndexMap.get(event);
if (index === undefined) {
throw new Error("Event not found");
}
return index;
}
get step(): EventEpoch {
return this.#nextIndexForward;
}
stepTo(eventIndex: EventEpoch): void {
while (this.#nextIndexForward <= eventIndex) {
this.stepForward(this.#nextIndexForward);
++this.#nextIndexForward;
}
while (this.#nextIndexForward > eventIndex + 1) {
--this.#nextIndexForward;
this.stepBackward(this.#nextIndexForward);
}
if (this.#nextIndexForward !== eventIndex + 1) {
throw new Error("Invalid event index");
}
}
get subs(): Readonly<Subs> {
return this.#subs;
}
subsSnapshot(): SubsSnapshot {
return this.#subs.snapshot({
epoch: (this.#nextIndexForward - 1) as EventEpoch,
});
}
lastEventIndex(): EventEpoch {
return (this.#events.length - 1) as EventEpoch;
}
private stepForward(eventIndex: EventEpoch): void {
const event = this.#events[eventIndex];
if (!isApplicable(event)) {
return;
}
if (!this.#reverseEvents.has(eventIndex)) {
const variable = applicableVariable(event);
const current = this.#subs.get(variable);
let revert: RollbackChange;
if (!current) {
revert = makeDeleteVariable({ variable });
} else {
revert = makeRevertVariable({ variable, to: current });
}
this.#reverseEvents.set(eventIndex, revert);
}
this.#subs.apply(event);
}
private stepBackward(eventIndex: EventEpoch): void {
const event = this.#events[eventIndex];
if (!isApplicable(event)) {
return;
}
const revert = this.#reverseEvents.get(eventIndex);
if (!revert) {
throw new Error("No revert found");
}
this.#subs.apply(revert);
}
}
function isApplicable(event: Event): event is ChangeEvent {
switch (event.type) {
case "VariableUnified":
case "VariableSetDescriptor":
return true;
case "Unification":
return false;
default:
assertExhaustive(event);
}
}
function applicableVariable(event: ChangeEvent): Variable {
switch (event.type) {
case "VariableUnified":
return event.from;
case "VariableSetDescriptor":
return event.variable;
default:
assertExhaustive(event);
}
}

View file

@ -0,0 +1,19 @@
import { Event } from "../schema";
export function lastSubEvent(event: Event): Event {
switch (event.type) {
case "Unification": {
const subevents = event.subevents;
if (subevents.length === 0) {
return event;
}
return lastSubEvent(event.subevents[event.subevents.length - 1]);
}
case "VariableUnified": {
return event;
}
case "VariableSetDescriptor": {
return event;
}
}
}

View file

@ -0,0 +1,179 @@
import { Content, Rank, Variable, Event } from "../schema";
import { assertExhaustive } from "../utils/exhaustive";
import { Refine } from "../utils/refine";
import { EventEpoch } from "./engine";
export type TypeLink = {
type: "link";
to: Variable;
};
function link({ to }: Omit<TypeLink, "type">): TypeLink {
return { type: "link", to };
}
export type TypeDescriptor = {
type: "descriptor";
rank: Rank;
content: Content;
};
function descriptor({
rank,
content,
}: Omit<TypeDescriptor, "type">): TypeDescriptor {
return { type: "descriptor", rank, content };
}
export type VarType = TypeLink | TypeDescriptor;
export type RevertVariableChange = {
type: "revertTo";
variable: Variable;
to: VarType;
};
export type DeleteVariableChange = {
type: "delete";
variable: Variable;
};
export type RollbackChange = RevertVariableChange | DeleteVariableChange;
export function makeRevertVariable({
variable,
to,
}: Omit<RevertVariableChange, "type">): RevertVariableChange {
return { type: "revertTo", variable, to: { ...to } };
}
export function makeDeleteVariable({
variable,
}: Omit<DeleteVariableChange, "type">): DeleteVariableChange {
return { type: "delete", variable };
}
export type ChangeEvent =
| Refine<Event, "VariableUnified">
| Refine<Event, "VariableSetDescriptor">;
export type Change = ChangeEvent | RollbackChange;
export class Subs implements QuerySubs {
#map: Map<Variable, VarType>;
private constructor(map: Map<Variable, VarType>) {
this.#map = map;
}
static new(): Subs {
return new Subs(new Map());
}
get(variable: Variable): VarType | undefined {
return this.#map.get(variable);
}
get_root(variable: Variable): TypeDescriptor | undefined {
const type = this.get(variable);
if (type === undefined) {
return undefined;
}
switch (type.type) {
case "descriptor":
return type;
case "link":
return this.get_root(type.to);
default:
assertExhaustive(type);
}
}
get_root_key(variable: Variable): Variable {
const type = this.get(variable);
if (type === undefined) {
return variable;
}
switch (type.type) {
case "descriptor":
return variable;
case "link":
return this.get_root_key(type.to);
default:
assertExhaustive(type);
}
}
snapshot({ epoch }: { epoch: EventEpoch }): SubsSnapshot {
const snapshotMap = new Map<Variable, VarType>();
for (const [key, value] of this.#map) {
snapshotMap.set(key, { ...value });
}
const snapshot = new Subs(snapshotMap);
return {
epoch,
get(variable: Variable): VarType | undefined {
return snapshot.get(variable);
},
get_root(variable: Variable): TypeDescriptor | undefined {
return snapshot.get_root(variable);
},
get_root_key(variable: Variable): Variable {
return snapshot.get_root_key(variable);
},
__snapshot__: SnapshotSymbol,
};
}
apply(change: Change): void {
switch (change.type) {
case "VariableUnified": {
const { from, to } = change;
if (from !== to) {
this.#map.set(from, link({ to }));
}
break;
}
case "VariableSetDescriptor": {
const { variable, rank, content } = change;
const existing = this.get_root(variable);
if (existing !== undefined) {
const nu = descriptor({ ...existing });
if (rank) nu.rank = rank;
if (content) nu.content = content;
this.#map.set(variable, nu);
} else {
if (typeof rank !== "number") throw new Error("rank is required");
if (!content) throw new Error("content is required");
this.#map.set(variable, descriptor({ rank, content }));
}
break;
}
case "revertTo": {
const { variable, to } = change;
this.#map.set(variable, { ...to });
break;
}
case "delete": {
const { variable } = change;
this.#map.delete(variable);
break;
}
default:
assertExhaustive(change);
}
}
}
const SnapshotSymbol = Symbol("Snapshot");
export interface QuerySubs {
get(variable: Variable): VarType | undefined;
get_root(variable: Variable): TypeDescriptor | undefined;
get_root_key(variable: Variable): Variable;
}
export interface SubsSnapshot extends QuerySubs {
readonly epoch: EventEpoch;
__snapshot__: typeof SnapshotSymbol;
}

View file

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View file

@ -0,0 +1,13 @@
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View file

@ -0,0 +1,243 @@
/* eslint-disable */
/**
* This file was automatically generated by json-schema-to-typescript.
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
* and run json-schema-to-typescript to regenerate this file.
*/
export type Event =
| {
left: Variable;
mode: UnificationMode;
right: Variable;
subevents: Event[];
success?: boolean | null;
type: "Unification";
[k: string]: unknown;
}
| {
from: Variable;
to: Variable;
type: "VariableUnified";
[k: string]: unknown;
}
| {
content?: Content | null;
rank?: Rank | null;
type: "VariableSetDescriptor";
variable: Variable;
[k: string]: unknown;
};
export type Variable = number;
export type UnificationMode =
| {
type: "Eq";
[k: string]: unknown;
}
| {
type: "Present";
[k: string]: unknown;
}
| {
type: "LambdaSetSpecialization";
[k: string]: unknown;
};
export type Content =
| {
name?: string | null;
type: "Flex";
[k: string]: unknown;
}
| {
name: string;
type: "Rigid";
[k: string]: unknown;
}
| {
abilities: Symbol[];
name?: string | null;
type: "FlexAble";
[k: string]: unknown;
}
| {
abilities: Symbol[];
name: string;
type: "RigidAble";
[k: string]: unknown;
}
| {
name?: string | null;
structure: Variable;
type: "Recursive";
[k: string]: unknown;
}
| {
ambient_function: Variable;
recursion_var?: Variable | null;
solved: ClosureType[];
type: "LambdaSet";
unspecialized: UnspecializedClosureType[];
[k: string]: unknown;
}
| {
type: "ErasedLambda";
[k: string]: unknown;
}
| {
kind: AliasKind;
name: Symbol;
real_variable: Variable;
type: "Alias";
variables: AliasTypeVariables;
[k: string]: unknown;
}
| {
symbol: Symbol;
type: "Apply";
variables: Variable[];
[k: string]: unknown;
}
| {
arguments: Variable[];
lambda_type: Variable;
ret: Variable;
type: "Function";
[k: string]: unknown;
}
| {
extension: Variable;
fields: {
[k: string]: RecordField;
};
type: "Record";
[k: string]: unknown;
}
| {
elements: {
[k: string]: Variable;
};
extension: Variable;
type: "Tuple";
[k: string]: unknown;
}
| {
extension: TagUnionExtension;
tags: {
[k: string]: Variable[];
};
type: "TagUnion";
[k: string]: unknown;
}
| {
extension: TagUnionExtension;
functions: Symbol[];
tags: string[];
type: "FunctionOrTagUnion";
[k: string]: unknown;
}
| {
extension: TagUnionExtension;
recursion_var: Variable;
tags: {
[k: string]: Variable[];
};
type: "RecursiveTagUnion";
[k: string]: unknown;
}
| {
type: "EmptyRecord";
[k: string]: unknown;
}
| {
type: "EmptyTuple";
[k: string]: unknown;
}
| {
type: "EmptyTagUnion";
[k: string]: unknown;
}
| {
range: NumericRange;
type: "RangedNumber";
[k: string]: unknown;
}
| {
type: "Error";
[k: string]: unknown;
};
export type Symbol = string;
export type AliasKind =
| {
type: "Structural";
[k: string]: unknown;
}
| {
type: "Opaque";
[k: string]: unknown;
};
export type RecordFieldKind =
| {
type: "Demanded";
[k: string]: unknown;
}
| {
rigid: boolean;
type: "Required";
[k: string]: unknown;
}
| {
rigid: boolean;
type: "Optional";
[k: string]: unknown;
};
export type TagUnionExtension =
| {
type: "Openness";
variable: Variable;
[k: string]: unknown;
}
| {
type: "Any";
variable: Variable;
[k: string]: unknown;
};
export type NumericRangeKind =
| {
type: "Int";
[k: string]: unknown;
}
| {
type: "AnyNum";
[k: string]: unknown;
};
export type Rank = number;
export type AllEvents = Event[];
export interface ClosureType {
environment: Variable[];
function: Symbol;
[k: string]: unknown;
}
export interface UnspecializedClosureType {
ability_member: Symbol;
lambda_set_region: number;
specialization: Variable;
[k: string]: unknown;
}
export interface AliasTypeVariables {
infer_ext_in_output_position_variables: Variable[];
lambda_set_variables: Variable[];
type_variables: Variable[];
[k: string]: unknown;
}
export interface RecordField {
field_type: Variable;
kind: RecordFieldKind;
[k: string]: unknown;
}
export interface NumericRange {
kind: NumericRangeKind;
min_width: number;
signed: boolean;
[k: string]: unknown;
}

View file

@ -0,0 +1,3 @@
export function assertExhaustive(_: never): never {
throw new Error("Exhaustive switch");
}

View file

@ -0,0 +1,3 @@
export type Refine<T extends { type: string }, Type extends string> = T & {
type: Type;
};

View file

@ -0,0 +1,17 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
mode: 'jit',
content: [
"./src/**/*.{js,jsx,ts,tsx}",
],
theme: {
extend: {
colors: {
'roc-purple': '#7c38f5',
'roc-purple-bg': '#ece2fd',
},
},
},
plugins: [],
}

View file

@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "es2015",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"]
}

View file

@ -0,0 +1,13 @@
[package]
name = "roc_checkmate_schema"
description = "Schema for checkmate."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies]
serde.workspace = true
serde_json.workspace = true
schemars.workspace = true

View file

@ -0,0 +1,225 @@
use std::collections::HashMap;
use schemars::{schema::RootSchema, schema_for, JsonSchema};
use serde::Serialize;
#[derive(Serialize, JsonSchema, Debug)]
#[serde(tag = "type")]
pub enum Constraint {}
#[derive(Serialize, JsonSchema, Debug, PartialEq)]
pub struct Variable(pub u32);
macro_rules! impl_content {
($($name:ident { $($arg:ident: $ty:ty,)* },)*) => {
#[derive(Serialize, JsonSchema, Debug)]
#[serde(tag = "type")]
pub enum Content {
$(
$name {
$($arg: $ty),*
},
)*
}
impl Content {
$(
#[allow(non_snake_case)]
pub fn $name($($arg: $ty),*) -> Self {
Self::$name { $($arg),* }
}
)*
}
};
}
impl_content! {
Flex {
name: Option<String>,
},
Rigid {
name: String,
},
FlexAble {
name: Option<String>,
abilities: Vec<Symbol>,
},
RigidAble {
name: String,
abilities: Vec<Symbol>,
},
Recursive {
name: Option<String>,
structure: Variable,
},
LambdaSet {
solved: Vec<ClosureType>,
unspecialized: Vec<UnspecializedClosureType>,
recursion_var: Option<Variable>,
ambient_function: Variable,
},
ErasedLambda {},
Alias {
name: Symbol,
variables: AliasTypeVariables,
real_variable: Variable,
kind: AliasKind,
},
Apply {
symbol: Symbol,
variables: Vec<Variable>,
},
Function {
arguments: Vec<Variable>,
lambda_type: Variable,
ret: Variable,
},
Record {
fields: HashMap<String, RecordField>,
extension: Variable,
},
Tuple {
elements: HashMap<u32, Variable>,
extension: Variable,
},
TagUnion {
tags: HashMap<String, Vec<Variable>>,
extension: TagUnionExtension,
},
FunctionOrTagUnion {
functions: Vec<Symbol>,
tags: Vec<String>,
extension: TagUnionExtension,
},
RecursiveTagUnion {
recursion_var: Variable,
tags: HashMap<String, Vec<Variable>>,
extension: TagUnionExtension,
},
EmptyRecord {},
EmptyTuple {},
EmptyTagUnion {},
RangedNumber {
range: NumericRange,
},
Error {},
}
#[derive(Serialize, JsonSchema, Debug)]
pub struct ClosureType {
pub function: Symbol,
pub environment: Vec<Variable>,
}
#[derive(Serialize, JsonSchema, Debug)]
pub struct UnspecializedClosureType {
pub specialization: Variable,
pub ability_member: Symbol,
pub lambda_set_region: u8,
}
#[derive(Serialize, JsonSchema, Debug)]
#[serde(tag = "type")]
pub enum AliasKind {
Structural,
Opaque,
}
#[derive(Serialize, JsonSchema, Debug)]
pub struct AliasTypeVariables {
pub type_variables: Vec<Variable>,
pub lambda_set_variables: Vec<Variable>,
pub infer_ext_in_output_position_variables: Vec<Variable>,
}
#[derive(Serialize, JsonSchema, Debug)]
pub struct RecordField {
pub kind: RecordFieldKind,
pub field_type: Variable,
}
#[derive(Serialize, JsonSchema, Debug)]
#[serde(tag = "type")]
pub enum RecordFieldKind {
Demanded,
Required { rigid: bool },
Optional { rigid: bool },
}
#[derive(Serialize, JsonSchema, Debug)]
#[serde(tag = "type", content = "variable")]
pub enum TagUnionExtension {
Openness(Variable),
Any(Variable),
}
#[derive(Serialize, JsonSchema, Debug)]
pub struct NumericRange {
pub kind: NumericRangeKind,
pub signed: bool,
pub min_width: u32,
}
#[derive(Serialize, JsonSchema, Debug)]
#[serde(tag = "type")]
pub enum NumericRangeKind {
Int,
AnyNum,
}
#[derive(Serialize, JsonSchema, Debug)]
pub struct Rank(pub u32);
#[derive(Serialize, JsonSchema, Debug)]
pub struct Descriptor {
pub content: Content,
pub rank: Rank,
}
#[derive(Serialize, JsonSchema, Debug)]
pub struct Symbol(
// TODO: should this be module ID + symbol?
pub String,
);
#[derive(Serialize, JsonSchema, Debug)]
#[serde(tag = "type")]
pub enum UnificationMode {
Eq,
Present,
LambdaSetSpecialization,
}
#[derive(Serialize, JsonSchema, Debug)]
#[serde(tag = "type")]
pub enum Event {
Unification {
left: Variable,
right: Variable,
mode: UnificationMode,
success: Option<bool>,
subevents: Vec<Event>,
},
VariableUnified {
from: Variable,
to: Variable,
},
VariableSetDescriptor {
variable: Variable,
rank: Option<Rank>,
content: Option<Content>,
},
}
#[derive(Serialize, JsonSchema, Debug)]
pub struct AllEvents(pub Vec<Event>);
impl AllEvents {
pub fn schema() -> RootSchema {
schema_for!(AllEvents)
}
pub fn write(&self, writer: impl std::io::Write) -> Result<(), serde_json::Error> {
serde_json::to_writer(writer, self)
}
}

View file

@ -2472,6 +2472,26 @@ fn constrain_empty_record(
constraints.equal_types(record_type_index, expected, Category::Record, region)
}
fn add_host_annotation(
types: &mut Types,
constraints: &mut Constraints,
host_exposed_annotation: Option<&(Variable, roc_can::def::Annotation)>,
constraint: Constraint,
) -> Constraint {
if let Some((var, ann)) = host_exposed_annotation {
let host_annotation = {
let type_index = types.from_old_type(&ann.signature);
constraints.push_type(types, type_index)
};
let store_constr = constraints.store(host_annotation, *var, file!(), line!());
constraints.and_constraint([store_constr, constraint])
} else {
constraint
}
}
/// Constrain top-level module declarations
#[inline(always)]
pub fn constrain_decls(
@ -2498,6 +2518,7 @@ pub fn constrain_decls(
use roc_can::expr::DeclarationTag::*;
let tag = declarations.declarations[index];
match tag {
Value => {
constraint = constrain_value_def(
@ -2508,6 +2529,77 @@ pub fn constrain_decls(
index,
constraint,
);
constraint = add_host_annotation(
types,
constraints,
declarations.host_exposed_annotations.get(&index),
constraint,
);
}
Function(function_def_index) => {
constraint = constrain_function_def(
types,
constraints,
&mut env,
declarations,
index,
function_def_index,
constraint,
);
constraint = add_host_annotation(
types,
constraints,
declarations.host_exposed_annotations.get(&index),
constraint,
);
}
Recursive(_) | TailRecursive(_) => {
constraint = add_host_annotation(
types,
constraints,
declarations.host_exposed_annotations.get(&index),
constraint,
);
// for the type it does not matter that a recursive call is a tail call
constraint = constrain_recursive_declarations(
types,
constraints,
&mut env,
declarations,
index..index + 1,
constraint,
IllegalCycleMark::empty(),
);
}
Destructure(destructure_def_index) => {
constraint = constrain_destructure_def(
types,
constraints,
&mut env,
declarations,
index,
destructure_def_index,
constraint,
);
}
MutualRecursion { length, cycle_mark } => {
// the next `length` defs belong to this group
let length = length as usize;
constraint = constrain_recursive_declarations(
types,
constraints,
&mut env,
declarations,
index + 1..index + 1 + length,
constraint,
cycle_mark,
);
index += length;
}
Expectation => {
let loc_expr = &declarations.expressions[index];
@ -2565,56 +2657,6 @@ pub fn constrain_decls(
Generalizable(false),
)
}
Function(function_def_index) => {
constraint = constrain_function_def(
types,
constraints,
&mut env,
declarations,
index,
function_def_index,
constraint,
);
}
Recursive(_) | TailRecursive(_) => {
// for the type it does not matter that a recursive call is a tail call
constraint = constrain_recursive_declarations(
types,
constraints,
&mut env,
declarations,
index..index + 1,
constraint,
IllegalCycleMark::empty(),
);
}
Destructure(destructure_def_index) => {
constraint = constrain_destructure_def(
types,
constraints,
&mut env,
declarations,
index,
destructure_def_index,
constraint,
);
}
MutualRecursion { length, cycle_mark } => {
// the next `length` defs belong to this group
let length = length as usize;
constraint = constrain_recursive_declarations(
types,
constraints,
&mut env,
declarations,
index + 1..index + 1 + length,
constraint,
cycle_mark,
);
index += length;
}
}
index += 1;

View file

@ -9,11 +9,13 @@ version.workspace = true
[dependencies]
roc_can = { path = "../can" }
roc_checkmate = { path = "../checkmate" }
roc_collections = { path = "../collections" }
roc_derive_key = { path = "../derive_key" }
roc_error_macros = { path = "../../error_macros" }
roc_module = { path = "../module" }
roc_region = { path = "../region" }
roc_solve_schema = { path = "../solve_schema" }
roc_types = { path = "../types" }
roc_unify = { path = "../unify" }

View file

@ -1,6 +1,8 @@
use roc_can::{abilities::SpecializationLambdaSets, module::ExposedByModule};
use roc_checkmate::with_checkmate;
use roc_error_macros::internal_error;
use roc_module::symbol::{IdentIds, Symbol};
use roc_solve_schema::UnificationMode;
use roc_types::{
subs::{instantiate_rigids, Subs, Variable},
types::Polarity,
@ -71,13 +73,20 @@ impl Env<'_> {
}
pub fn unify(&mut self, left: Variable, right: Variable) {
use roc_unify::unify::{unify, Env, Mode, Unified};
use roc_unify::{
unify::{unify, Unified},
Env,
};
let unified = unify(
&mut Env::new(self.subs),
// TODO(checkmate): pass checkmate through
&mut with_checkmate!({
on => Env::new(self.subs, None),
off => Env::new(self.subs),
}),
left,
right,
Mode::EQ,
UnificationMode::EQ,
Polarity::OF_PATTERN,
);
@ -103,15 +112,22 @@ impl Env<'_> {
specialization_type: Variable,
ability_member: Symbol,
) -> SpecializationLambdaSets {
use roc_unify::unify::{unify_introduced_ability_specialization, Env, Mode, Unified};
use roc_unify::{
unify::{unify_introduced_ability_specialization, Unified},
Env,
};
let member_signature = self.import_builtin_symbol_var(ability_member);
let unified = unify_introduced_ability_specialization(
&mut Env::new(self.subs),
// TODO(checkmate): pass checkmate through
&mut with_checkmate!({
on => Env::new(self.subs, None),
off => Env::new(self.subs),
}),
member_signature,
specialization_type,
Mode::EQ,
UnificationMode::EQ,
);
match unified {

View file

@ -103,7 +103,7 @@ impl FlatDecodable {
| Content::RigidVar(_)
| Content::FlexAbleVar(_, _)
| Content::RigidAbleVar(_, _) => Err(UnboundVar),
Content::LambdaSet(_) => Err(Underivable),
Content::LambdaSet(_) | Content::ErasedLambda => Err(Underivable),
}
}

View file

@ -137,7 +137,7 @@ impl FlatEncodable {
| Content::RigidVar(_)
| Content::FlexAbleVar(_, _)
| Content::RigidAbleVar(_, _) => Err(UnboundVar),
Content::LambdaSet(_) => Err(Underivable),
Content::LambdaSet(_) | Content::ErasedLambda => Err(Underivable),
}
}

View file

@ -148,7 +148,7 @@ impl FlatHash {
| Content::RigidVar(_)
| Content::FlexAbleVar(_, _)
| Content::RigidAbleVar(_, _) => Err(UnboundVar),
Content::LambdaSet(_) => Err(Underivable),
Content::LambdaSet(_) | Content::ErasedLambda => Err(Underivable),
}
}

View file

@ -241,7 +241,7 @@ fn is_exhaustive(matrix: &RefPatternMatrix, n: usize) -> PatternMatrix {
.collect();
let rest: Vec<Vec<Pattern>> = is_exhaustive(&new_matrix, n - 1);
let last: _ = alt_list
let last = alt_list
.iter()
.filter_map(|r| is_missing(alts.clone(), &ctors, r));

View file

@ -5,7 +5,7 @@ use crate::{
use bumpalo::collections::{CollectIn, Vec};
use roc_builtins::bitcode::{self, FloatWidth, IntWidth};
use roc_collections::all::MutMap;
use roc_error_macros::internal_error;
use roc_error_macros::{internal_error, todo_lambda_erasure};
use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::code_gen_help::{CallerProc, CodeGenHelp, HelperOp};
use roc_mono::ir::{
@ -3007,11 +3007,17 @@ impl<
ASM::and_reg64_reg64_reg64(buf, sym_reg, sym_reg, ptr_reg);
}
fn build_alloca(&mut self, sym: Symbol, value: Symbol, element_layout: InLayout<'a>) {
fn build_alloca(&mut self, sym: Symbol, value: Option<Symbol>, element_layout: InLayout<'a>) {
// 1. acquire some stack space
let element_width = self.interner().stack_size(element_layout);
let allocation = self.debug_symbol("stack_allocation");
let ptr = self.debug_symbol("ptr");
if element_width == 0 {
self.storage_manager.claim_pointer_stack_area(sym);
return;
}
let base_offset = self
.storage_manager
.claim_stack_area(&allocation, element_width);
@ -3021,7 +3027,13 @@ impl<
ASM::mov_reg64_reg64(&mut self.buf, ptr_reg, CC::BASE_PTR_REG);
ASM::add_reg64_reg64_imm32(&mut self.buf, ptr_reg, ptr_reg, base_offset);
self.build_ptr_store(sym, ptr, value, element_layout);
if let Some(value) = value {
self.build_ptr_store(sym, ptr, value, element_layout);
} else {
// this is now a pointer to uninitialized memory!
let r = self.storage_manager.claim_general_reg(&mut self.buf, &sym);
ASM::mov_reg64_reg64(&mut self.buf, r, ptr_reg);
}
}
fn expr_box(
@ -3630,7 +3642,8 @@ impl<
}
LayoutRepr::Union(UnionLayout::NonRecursive(_))
| LayoutRepr::Builtin(_)
| LayoutRepr::Struct(_) => {
| LayoutRepr::Struct(_)
| LayoutRepr::Erased(_) => {
internal_error!("All primitive values should fit in a single register");
}
}
@ -4306,6 +4319,8 @@ impl<
dst,
);
}
LayoutRepr::Erased(_) => todo_lambda_erasure!(),
}
}
@ -4527,5 +4542,6 @@ macro_rules! pointer_layouts {
| UnionLayout::NullableWrapped { .. }
| UnionLayout::NullableUnwrapped { .. },
)
| LayoutRepr::FunctionPointer(_)
};
}

View file

@ -6,7 +6,7 @@ use crate::{
use bumpalo::collections::Vec;
use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_collections::all::{MutMap, MutSet};
use roc_error_macros::internal_error;
use roc_error_macros::{internal_error, todo_lambda_erasure};
use roc_module::symbol::Symbol;
use roc_mono::{
ir::{JoinPointId, Param},
@ -830,6 +830,7 @@ impl<
self.copy_to_stack_offset(buf, size, from_offset, to_offset)
}
LayoutRepr::Erased(_) => todo_lambda_erasure!(),
pointer_layouts!() => {
// like a 64-bit integer
debug_assert_eq!(to_offset % 8, 0);
@ -1168,12 +1169,7 @@ impl<
) {
let mut param_storage = bumpalo::vec![in self.env.arena];
param_storage.reserve(params.len());
for Param {
symbol,
ownership: _,
layout,
} in params
{
for Param { symbol, layout } in params {
// Claim a location for every join point parameter to be loaded at.
// Put everything on the stack for simplicity.
self.joinpoint_argument_stack_storage(layout_interner, *symbol, *layout);

View file

@ -10,7 +10,7 @@ use std::collections::hash_map::Entry;
use bumpalo::{collections::Vec, Bump};
use roc_builtins::bitcode::{self, FloatWidth, IntWidth};
use roc_collections::all::{MutMap, MutSet};
use roc_error_macros::internal_error;
use roc_error_macros::{internal_error, todo_lambda_erasure};
use roc_module::ident::ModuleName;
use roc_module::low_level::{LowLevel, LowLevelWrapperType};
use roc_module::symbol::{Interns, ModuleId, Symbol};
@ -149,6 +149,13 @@ impl<'a> LastSeenMap<'a> {
self.set_last_seen(*sym, stmt);
}
}
Expr::ErasedMake { value, callee } => {
value.map(|v| self.set_last_seen(v, stmt));
self.set_last_seen(*callee, stmt);
}
Expr::ErasedLoad { symbol, field: _ } => {
self.set_last_seen(*symbol, stmt);
}
Expr::Struct(syms) => {
for sym in *syms {
self.set_last_seen(*sym, stmt);
@ -176,8 +183,14 @@ impl<'a> LastSeenMap<'a> {
Expr::Reset { symbol, .. } | Expr::ResetRef { symbol, .. } => {
self.set_last_seen(*symbol, stmt);
}
Expr::EmptyArray => {}
Expr::Alloca { initializer, .. } => {
if let Some(initializer) = initializer {
self.set_last_seen(*initializer, stmt);
}
}
Expr::RuntimeErrorFunction(_) => {}
Expr::FunctionPointer { .. } => todo_lambda_erasure!(),
Expr::EmptyArray => {}
}
self.scan_ast_help(following);
}
@ -265,6 +278,7 @@ impl<'a> LastSeenMap<'a> {
match call_type {
CallType::ByName { .. } => {}
CallType::ByPointer { .. } => {}
CallType::LowLevel { .. } => {}
CallType::HigherOrder { .. } => {}
CallType::Foreign { .. } => {}
@ -729,6 +743,10 @@ trait Backend<'a> {
self.build_fn_call(sym, fn_name, arguments, arg_layouts, ret_layout)
}
CallType::ByPointer { .. } => {
todo_lambda_erasure!()
}
CallType::LowLevel { op: lowlevel, .. } => {
let mut arg_layouts: bumpalo::collections::Vec<InLayout<'a>> =
bumpalo::vec![in self.env().arena];
@ -839,6 +857,9 @@ trait Backend<'a> {
Expr::NullPointer => {
self.load_literal_i64(sym, 0);
}
Expr::FunctionPointer { .. } => todo_lambda_erasure!(),
Expr::ErasedMake { .. } => todo_lambda_erasure!(),
Expr::ErasedLoad { .. } => todo_lambda_erasure!(),
Expr::Reset { symbol, .. } => {
let layout = *self.layout_map().get(symbol).unwrap();
@ -879,6 +900,12 @@ trait Backend<'a> {
self.build_expr(sym, &new_expr, &Layout::BOOL)
}
Expr::Alloca {
initializer,
element_layout,
} => {
self.build_alloca(*sym, *initializer, *element_layout);
}
Expr::RuntimeErrorFunction(_) => todo!(),
}
}
@ -1599,10 +1626,6 @@ trait Backend<'a> {
self.build_ptr_clear_tag_id(*sym, args[0]);
}
LowLevel::Alloca => {
self.build_alloca(*sym, args[0], arg_layouts[0]);
}
LowLevel::RefCountDecRcPtr => self.build_fn_call(
sym,
bitcode::UTILS_DECREF_RC_PTR.to_string(),
@ -2243,7 +2266,7 @@ trait Backend<'a> {
fn build_ptr_clear_tag_id(&mut self, sym: Symbol, ptr: Symbol);
fn build_alloca(&mut self, sym: Symbol, value: Symbol, element_layout: InLayout<'a>);
fn build_alloca(&mut self, sym: Symbol, value: Option<Symbol>, element_layout: InLayout<'a>);
/// literal_map gets the map from symbol to literal and layout, used for lazy loading and literal folding.
fn literal_map(&mut self) -> &mut MutMap<Symbol, (*const Literal<'a>, *const InLayout<'a>)>;

View file

@ -444,6 +444,7 @@ fn build_exposed_proc<'a, B: Backend<'a>>(backend: &mut B, proc: &Proc<'a>) -> P
ret_layout: proc.ret_layout,
is_self_recursive: roc_mono::ir::SelfRecursive::NotSelfRecursive,
host_exposed_layouts: roc_mono::ir::HostExposedLayouts::NotHostExposed,
is_erased: proc.is_erased,
}
}
@ -525,6 +526,7 @@ fn build_exposed_generic_proc<'a, B: Backend<'a>>(backend: &mut B, proc: &Proc<'
ret_layout: roc_mono::layout::Layout::UNIT,
is_self_recursive: roc_mono::ir::SelfRecursive::NotSelfRecursive,
host_exposed_layouts: roc_mono::ir::HostExposedLayouts::NotHostExposed,
is_erased: proc.is_erased,
}
}

View file

@ -119,8 +119,9 @@ impl<'a> LlvmAlignment<'a> for LayoutRepr<'a> {
.runtime_representation()
.llvm_alignment_bytes(interner),
Builtin(builtin) => builtin.llvm_alignment_bytes(interner),
RecursivePointer(_) => interner.target_info().ptr_width() as u32,
Ptr(_) => interner.target_info().ptr_width() as u32,
RecursivePointer(_) | Ptr(_) | FunctionPointer(_) | Erased(_) => {
interner.target_info().ptr_width() as u32
}
}
}
}

View file

@ -296,7 +296,7 @@ fn build_transform_caller_help<'a, 'ctx>(
}
}
let result = crate::llvm::build::call_roc_function(
let result = crate::llvm::build::call_direct_roc_function(
env,
layout_interner,
roc_function,

View file

@ -9,6 +9,7 @@ use crate::llvm::refcounting::{
build_reset, decrement_refcount_layout, increment_refcount_layout, PointerToRefcount,
};
use crate::llvm::struct_::{struct_from_fields, RocStruct};
use crate::llvm::{erased, fn_ptr};
use bumpalo::collections::Vec;
use bumpalo::Bump;
use inkwell::attributes::{Attribute, AttributeLoc};
@ -23,11 +24,11 @@ use inkwell::passes::{PassManager, PassManagerBuilder};
use inkwell::types::{
AnyType, BasicMetadataTypeEnum, BasicType, BasicTypeEnum, FunctionType, IntType, StructType,
};
use inkwell::values::BasicValueEnum;
use inkwell::values::{
BasicMetadataValueEnum, CallSiteValue, FunctionValue, InstructionValue, IntValue, PointerValue,
StructValue,
};
use inkwell::values::{BasicValueEnum, CallableValue};
use inkwell::OptimizationLevel;
use inkwell::{AddressSpace, IntPredicate};
use morphic_lib::{
@ -38,6 +39,7 @@ use roc_collections::all::{MutMap, MutSet};
use roc_debug_flags::dbg_do;
#[cfg(debug_assertions)]
use roc_debug_flags::ROC_PRINT_LLVM_FN_VERIFICATION;
use roc_error_macros::{internal_error, todo_lambda_erasure};
use roc_module::symbol::{Interns, Symbol};
use roc_mono::ir::{
BranchInfo, CallType, CrashTag, EntryPoint, GlueLayouts, HostExposedLambdaSet,
@ -604,8 +606,7 @@ fn promote_to_main_function<'a, 'ctx>(
);
// NOTE fake layout; it is only used for debug prints
let roc_main_fn =
function_value_by_func_spec(env, *func_spec, symbol, &[], Niche::NONE, Layout::UNIT);
let roc_main_fn = function_value_by_func_spec(env, FuncBorrowSpec::Some(*func_spec), symbol);
let main_fn_name = "$Test.main";
@ -654,8 +655,7 @@ fn promote_to_wasm_test_wrapper<'a, 'ctx>(
);
// NOTE fake layout; it is only used for debug prints
let roc_main_fn =
function_value_by_func_spec(env, *func_spec, symbol, &[], Niche::NONE, Layout::UNIT);
let roc_main_fn = function_value_by_func_spec(env, FuncBorrowSpec::Some(*func_spec), symbol);
let output_type = match roc_main_fn.get_type().get_return_type() {
Some(return_type) => {
@ -690,7 +690,7 @@ fn promote_to_wasm_test_wrapper<'a, 'ctx>(
let entry = context.append_basic_block(c_function, "entry");
builder.position_at_end(entry);
let roc_main_fn_result = call_roc_function(
let roc_main_fn_result = call_direct_roc_function(
env,
layout_interner,
roc_main_fn,
@ -912,7 +912,6 @@ pub(crate) fn build_exp_call<'a, 'ctx>(
CallType::ByName {
name,
specialization_id,
arg_layouts,
ret_layout,
..
} => {
@ -927,17 +926,39 @@ pub(crate) fn build_exp_call<'a, 'ctx>(
let callee_var = CalleeSpecVar(&bytes);
let func_spec = func_spec_solutions.callee_spec(callee_var).unwrap();
roc_call_with_args(
roc_call_direct_with_args(
env,
layout_interner,
arg_layouts,
*ret_layout,
*name,
func_spec,
FuncBorrowSpec::Some(func_spec),
arg_tuples.into_bump_slice(),
)
}
CallType::ByPointer {
pointer,
arg_layouts,
ret_layout,
} => {
let mut args: Vec<BasicValueEnum> = Vec::with_capacity_in(arguments.len(), env.arena);
for symbol in arguments.iter() {
args.push(scope.load_symbol(symbol));
}
let pointer = scope.load_symbol(pointer).into_pointer_value();
roc_call_erased_with_args(
env,
layout_interner,
pointer,
arg_layouts,
*ret_layout,
args.into_bump_slice(),
)
}
CallType::LowLevel { op, update_mode } => {
let bytes = update_mode.to_bytes();
let update_var = UpdateModeVar(&bytes);
@ -1085,6 +1106,24 @@ pub(crate) fn build_exp_expr<'a, 'ctx>(
)
}
FunctionPointer { lambda_name } => {
let alloca = fn_ptr::build(env, *lambda_name);
alloca.into()
}
ErasedMake { value, callee } => {
let value = value.map(|sym| scope.load_symbol(&sym).into_pointer_value());
let callee = scope.load_symbol(callee).into_pointer_value();
erased::build(env, value, callee).into()
}
ErasedLoad { symbol, field } => {
let value = scope.load_symbol(symbol).into_struct_value();
let wanted_llvm_type =
basic_type_from_layout(env, layout_interner, layout_interner.get_repr(layout))
.into_pointer_type();
erased::load(env, value, *field, wanted_llvm_type).into()
}
Reset {
symbol,
update_mode,
@ -1561,6 +1600,24 @@ pub(crate) fn build_exp_expr<'a, 'ctx>(
get_tag_id(env, layout_interner, parent, union_layout, argument).into()
}
Alloca {
initializer,
element_layout,
} => {
let element_type = basic_type_from_layout(
env,
layout_interner,
layout_interner.get_repr(*element_layout),
);
let ptr = entry_block_alloca_zerofill(env, element_type, "stack_value");
if let Some(initializer) = initializer {
env.builder.build_store(ptr, scope.load_symbol(initializer));
}
ptr.into()
}
}
}
@ -1851,7 +1908,7 @@ fn build_tag<'a, 'ctx>(
let roc_union =
RocUnion::untagged_from_slices(layout_interner, env.context, &[other_fields]);
if tag_id == *nullable_id as _ {
if tag_id == *nullable_id as u16 {
let output_type = roc_union.struct_type().ptr_type(AddressSpace::default());
return output_type.const_null().into();
@ -2833,8 +2890,7 @@ pub(crate) fn build_exp_stmt<'a, 'ctx>(
DecRef(symbol) => {
let (value, layout) = scope.load_symbol_and_layout(symbol);
let lay = layout_interner.get_repr(layout);
match lay {
match layout_interner.runtime_representation(layout) {
LayoutRepr::Builtin(Builtin::Str) => todo!(),
LayoutRepr::Builtin(Builtin::List(element_layout)) => {
debug_assert!(value.is_struct_value());
@ -2844,16 +2900,19 @@ pub(crate) fn build_exp_stmt<'a, 'ctx>(
build_list::decref(env, value.into_struct_value(), alignment);
}
_ if lay.is_refcounted() => {
other_layout if other_layout.is_refcounted(layout_interner) => {
if value.is_pointer_value() {
let value_ptr = match lay {
LayoutRepr::Union(union_layout)
if union_layout
.stores_tag_id_in_pointer(env.target_info) =>
{
tag_pointer_clear_tag_id(env, value.into_pointer_value())
let clear_tag_id = match other_layout {
LayoutRepr::Union(union_layout) => {
union_layout.stores_tag_id_in_pointer(env.target_info)
}
_ => value.into_pointer_value(),
_ => false,
};
let value_ptr = if clear_tag_id {
tag_pointer_clear_tag_id(env, value.into_pointer_value())
} else {
value.into_pointer_value()
};
let then_block = env.context.append_basic_block(parent, "then");
@ -2906,7 +2965,7 @@ pub(crate) fn build_exp_stmt<'a, 'ctx>(
debug_assert!(value.is_pointer_value());
let value = value.into_pointer_value();
let clear_tag_id = match layout_interner.chase_recursive(layout) {
let clear_tag_id = match layout_interner.runtime_representation(layout) {
LayoutRepr::Union(union) => union.stores_tag_id_in_pointer(env.target_info),
_ => false,
};
@ -3849,7 +3908,7 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx>(
builder.position_at_end(entry);
let wrapped_layout = roc_call_result_layout(env.arena, return_layout);
call_roc_function(
call_direct_roc_function(
env,
layout_interner,
roc_function,
@ -3857,7 +3916,7 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx>(
arguments_for_call,
)
} else {
call_roc_function(
call_direct_roc_function(
env,
layout_interner,
roc_function,
@ -3995,7 +4054,7 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx>(
let wrapper_result = roc_call_result_layout(env.arena, return_layout);
let roc_value = call_roc_function(
let roc_value = call_direct_roc_function(
env,
layout_interner,
roc_wrapper_function,
@ -4248,7 +4307,7 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx>(
let arguments = Vec::from_iter_in(it, env.arena);
let value = call_roc_function(
let value = call_direct_roc_function(
env,
layout_interner,
roc_function,
@ -4558,7 +4617,7 @@ fn set_jump_and_catch_long_jump<'a, 'ctx>(
{
builder.position_at_end(then_block);
let call_result = call_roc_function(
let call_result = call_direct_roc_function(
env,
layout_interner,
roc_function,
@ -4826,15 +4885,19 @@ pub(crate) fn build_proc_headers<'a, 'r, 'ctx>(
let it = func_solutions.specs();
let mut function_values = std::vec::Vec::with_capacity(it.size_hint().0);
let is_erased = proc.is_erased;
debug_assert!(!is_erased || func_solutions.specs().count() == 1);
for specialization in it {
let fn_val = build_proc_header(
env,
layout_interner,
*specialization,
symbol,
&proc,
layout_ids,
);
let func_spec = if is_erased {
FuncBorrowSpec::Erased
} else {
FuncBorrowSpec::Some(*specialization)
};
let fn_val =
build_proc_header(env, layout_interner, func_spec, symbol, &proc, layout_ids);
if proc.args.is_empty() {
// this is a 0-argument thunk, i.e. a top-level constant definition
@ -4889,8 +4952,7 @@ pub fn build_procedures<'a>(
);
// NOTE fake layout; it is only used for debug prints
let getter_fn =
function_value_by_func_spec(env, *func_spec, symbol, &[], niche, Layout::UNIT);
let getter_fn = function_value_by_func_spec(env, FuncBorrowSpec::Some(*func_spec), symbol);
let name = getter_fn.get_name().to_str().unwrap();
let getter_name = symbol.as_str(&env.interns);
@ -5006,7 +5068,7 @@ pub fn build_procedures_expose_expects<'a>(
// NOTE fake layout; it is only used for debug prints
let roc_main_fn =
function_value_by_func_spec(env, *func_spec, symbol, &[], captures_niche, Layout::UNIT);
function_value_by_func_spec(env, FuncBorrowSpec::Some(*func_spec), symbol);
let name = roc_main_fn.get_name().to_str().unwrap();
@ -5133,11 +5195,19 @@ fn build_procedures_help<'a>(
mod_solutions
}
pub enum FuncBorrowSpec {
/// This function has an specialization due to alias analysis.
Some(FuncSpec),
/// This function does not have a specialization due to alias analysis,
/// because it is type-erased, and thus has no statically determined AA specialization.
Erased,
}
fn func_spec_name<'a>(
arena: &'a Bump,
interns: &Interns,
symbol: Symbol,
func_spec: FuncSpec,
func_spec: FuncBorrowSpec,
) -> bumpalo::collections::String<'a> {
use std::fmt::Write;
@ -5147,8 +5217,13 @@ fn func_spec_name<'a>(
let module_string = interns.module_ids.get_name(symbol.module_id()).unwrap();
write!(buf, "{module_string}_{ident_string}_").unwrap();
for byte in func_spec.0.iter() {
write!(buf, "{byte:x?}").unwrap();
match func_spec {
FuncBorrowSpec::Some(func_spec) => {
for byte in func_spec.0.iter() {
write!(buf, "{byte:x?}").unwrap();
}
}
FuncBorrowSpec::Erased => write!(buf, "erased").unwrap(),
}
buf
@ -5157,7 +5232,7 @@ fn func_spec_name<'a>(
fn build_proc_header<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
layout_interner: &STLayoutInterner<'a>,
func_spec: FuncSpec,
func_spec: FuncBorrowSpec,
symbol: Symbol,
proc: &roc_mono::ir::Proc<'a>,
layout_ids: &mut LayoutIds<'a>,
@ -5260,14 +5335,7 @@ fn expose_alias_to_host<'a>(
"we expect only one specialization of this symbol"
);
function_value_by_func_spec(
env,
*func_spec,
hels.symbol,
hels.proc_layout.arguments,
Niche::NONE,
hels.proc_layout.result,
)
function_value_by_func_spec(env, FuncBorrowSpec::Some(*func_spec), hels.symbol)
}
None => {
// morphic did not generate a specialization for this function,
@ -5290,6 +5358,7 @@ fn expose_alias_to_host<'a>(
)
}
RawFunctionLayout::ErasedFunction(..) => todo_lambda_erasure!(),
RawFunctionLayout::ZeroArgumentThunk(result) => {
// Define only the return value size, since this is a thunk
//
@ -5400,7 +5469,7 @@ fn build_closure_caller<'a, 'ctx>(
builder.build_store(output, call_result);
} else {
let call_result = call_roc_function(
let call_result = call_direct_roc_function(
env,
layout_interner,
evaluator,
@ -5571,73 +5640,43 @@ pub fn verify_fn(fn_val: FunctionValue<'_>) {
}
}
pub(crate) fn function_value_by_func_spec<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
func_spec: FuncSpec,
pub(crate) fn function_value_by_func_spec<'ctx>(
env: &Env<'_, 'ctx, '_>,
func_spec: FuncBorrowSpec,
symbol: Symbol,
arguments: &[InLayout<'a>],
niche: Niche<'a>,
result: InLayout<'a>,
) -> FunctionValue<'ctx> {
let fn_name = func_spec_name(env.arena, &env.interns, symbol, func_spec);
let fn_name = fn_name.as_str();
function_value_by_name_help(env, arguments, niche, result, symbol, fn_name)
function_value_by_name_help(env, symbol, fn_name)
}
fn function_value_by_name_help<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
arguments: &[InLayout<'a>],
_niche: Niche<'a>,
result: InLayout<'a>,
fn function_value_by_name_help<'ctx>(
env: &Env<'_, 'ctx, '_>,
symbol: Symbol,
fn_name: &str,
) -> FunctionValue<'ctx> {
env.module.get_function(fn_name).unwrap_or_else(|| {
if symbol.is_builtin() {
eprintln!(
"Unrecognized builtin function: {:?}\nLayout: {:?}\n",
fn_name,
(arguments, result)
);
eprintln!("Is the function defined? If so, maybe there is a problem with the layout");
panic!("Unrecognized builtin function: {fn_name:?} (symbol: {symbol:?})",)
panic!("Unrecognized builtin function: {fn_name:?} (symbol: {symbol:?})")
} else {
// Unrecognized non-builtin function:
eprintln!(
"Unrecognized non-builtin function: {:?}\n\nSymbol: {:?}\nLayout: {:?}\n",
fn_name,
symbol,
(arguments, result)
);
eprintln!("Is the function defined? If so, maybe there is a problem with the layout");
panic!("Unrecognized non-builtin function: {fn_name:?} (symbol: {symbol:?})",)
panic!("Unrecognized non-builtin function: {fn_name:?} (symbol: {symbol:?})")
}
})
}
#[inline(always)]
fn roc_call_with_args<'a, 'ctx>(
fn roc_call_direct_with_args<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
layout_interner: &STLayoutInterner<'a>,
argument_layouts: &[InLayout<'a>],
result_layout: InLayout<'a>,
name: LambdaName<'a>,
func_spec: FuncSpec,
func_spec: FuncBorrowSpec,
arguments: &[BasicValueEnum<'ctx>],
) -> BasicValueEnum<'ctx> {
let fn_val = function_value_by_func_spec(
env,
func_spec,
name.name(),
argument_layouts,
name.niche(),
result_layout,
);
let fn_val = function_value_by_func_spec(env, func_spec, name.name());
call_roc_function(
call_direct_roc_function(
env,
layout_interner,
fn_val,
@ -5646,14 +5685,70 @@ fn roc_call_with_args<'a, 'ctx>(
)
}
pub fn call_roc_function<'a, 'ctx>(
#[inline(always)]
fn roc_call_erased_with_args<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
layout_interner: &STLayoutInterner<'a>,
pointer: PointerValue<'ctx>,
argument_layouts: &[InLayout<'a>],
result_layout: InLayout<'a>,
arguments: &[BasicValueEnum<'ctx>],
) -> BasicValueEnum<'ctx> {
let function_type =
fn_ptr::function_type(env, layout_interner, argument_layouts, result_layout);
let function_ptr_type = function_type.ptr_type(AddressSpace::default());
let function_pointer = fn_ptr::cast_to_function_ptr_type(env, pointer, function_ptr_type);
let callable_function_pointer = CallableValue::try_from(function_pointer).unwrap();
let build_call = |arguments: &[BasicMetadataValueEnum<'ctx>]| {
env.builder
.build_call(callable_function_pointer, arguments, "call")
};
call_roc_function_help(
env,
layout_interner,
build_call,
function_type,
layout_interner.get_repr(result_layout),
arguments,
)
}
pub(crate) fn call_direct_roc_function<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
layout_interner: &STLayoutInterner<'a>,
roc_function: FunctionValue<'ctx>,
result_layout: LayoutRepr<'a>,
arguments: &[BasicValueEnum<'ctx>],
) -> BasicValueEnum<'ctx> {
let pass_by_pointer = roc_function.get_type().get_param_types().len() == arguments.len() + 1;
let function_type = roc_function.get_type();
let build_call = |arguments: &[BasicMetadataValueEnum<'ctx>]| {
env.builder.build_call(roc_function, arguments, "call")
};
debug_assert_eq!(roc_function.get_call_conventions(), FAST_CALL_CONV);
call_roc_function_help(
env,
layout_interner,
build_call,
function_type,
result_layout,
arguments,
)
}
fn call_roc_function_help<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
layout_interner: &STLayoutInterner<'a>,
build_call: impl FnOnce(&[BasicMetadataValueEnum<'ctx>]) -> CallSiteValue<'ctx>,
roc_function_type: FunctionType<'ctx>,
result_layout: LayoutRepr<'a>,
arguments: &[BasicValueEnum<'ctx>],
) -> BasicValueEnum<'ctx> {
let pass_by_pointer = roc_function_type.get_param_types().len() == arguments.len() + 1;
match RocReturn::from_layout(layout_interner, result_layout) {
RocReturn::ByPointer if !pass_by_pointer => {
@ -5667,14 +5762,10 @@ pub fn call_roc_function<'a, 'ctx>(
arguments.push(result_alloca.into());
debug_assert_eq!(
roc_function.get_type().get_param_types().len(),
arguments.len()
);
let call = env.builder.build_call(roc_function, &arguments, "call");
debug_assert_eq!(roc_function_type.get_param_types().len(), arguments.len());
let call = build_call(&arguments);
// roc functions should have the fast calling convention
debug_assert_eq!(roc_function.get_call_conventions(), FAST_CALL_CONV);
call.set_call_convention(FAST_CALL_CONV);
env.builder
@ -5689,14 +5780,10 @@ pub fn call_roc_function<'a, 'ctx>(
arguments.push(result_alloca.into());
debug_assert_eq!(
roc_function.get_type().get_param_types().len(),
arguments.len()
);
let call = env.builder.build_call(roc_function, &arguments, "call");
debug_assert_eq!(roc_function_type.get_param_types().len(), arguments.len());
let call = build_call(&arguments);
// roc functions should have the fast calling convention
debug_assert_eq!(roc_function.get_call_conventions(), FAST_CALL_CONV);
call.set_call_convention(FAST_CALL_CONV);
if result_layout.is_passed_by_reference(layout_interner) {
@ -5710,25 +5797,18 @@ pub fn call_roc_function<'a, 'ctx>(
}
}
RocReturn::Return => {
debug_assert_eq!(
roc_function.get_type().get_param_types().len(),
arguments.len()
);
debug_assert_eq!(roc_function_type.get_param_types().len(), arguments.len());
let it = arguments.iter().map(|x| (*x).into());
let arguments = Vec::from_iter_in(it, env.arena);
let call = env.builder.build_call(roc_function, &arguments, "call");
let call = build_call(&arguments);
// roc functions should have the fast calling convention
debug_assert_eq!(roc_function.get_call_conventions(), FAST_CALL_CONV);
call.set_call_convention(FAST_CALL_CONV);
call.try_as_basic_value().left().unwrap_or_else(|| {
panic!(
"LLVM error: Invalid call by name for name {:?}",
roc_function.get_name()
)
})
call.try_as_basic_value()
.left()
.unwrap_or_else(|| internal_error!("LLVM error: Invalid call by name",))
}
}
}
@ -5905,7 +5985,7 @@ pub enum CCReturn {
}
#[derive(Debug, Clone, Copy)]
pub struct FunctionSpec<'ctx> {
pub(crate) struct FunctionSpec<'ctx> {
/// The function type
pub typ: FunctionType<'ctx>,
call_conv: u32,
@ -5975,7 +6055,7 @@ impl<'ctx> FunctionSpec<'ctx> {
}
/// Fastcc calling convention
fn fastcc<'a, 'env>(
pub fn fastcc<'a, 'env>(
env: &Env<'a, 'ctx, 'env>,
roc_return: RocReturn,
return_type: BasicTypeEnum<'ctx>,
@ -6223,7 +6303,7 @@ fn build_foreign_symbol<'a, 'ctx>(
}
};
call_roc_function(
call_direct_roc_function(
env,
layout_interner,
fastcc_function,
@ -6366,7 +6446,7 @@ fn get_foreign_symbol<'ctx>(
/// Add a function to a module, after asserting that the function is unique.
/// We never want to define the same function twice in the same module!
/// The result can be bugs that are difficult to track down.
pub fn add_func<'ctx>(
pub(crate) fn add_func<'ctx>(
ctx: &Context,
module: &Module<'ctx>,
name: &str,

View file

@ -168,6 +168,8 @@ fn build_eq<'a, 'ctx>(
),
LayoutRepr::LambdaSet(_) => unreachable!("cannot compare closures"),
LayoutRepr::FunctionPointer(_) => unreachable!("cannot compare function pointers"),
LayoutRepr::Erased(_) => unreachable!("cannot compare erased types"),
LayoutRepr::Union(union_layout) => build_tag_eq(
env,
@ -399,6 +401,8 @@ fn build_neq<'a, 'ctx>(
unreachable!("recursion pointers should never be compared directly")
}
LayoutRepr::LambdaSet(_) => unreachable!("cannot compare closure"),
LayoutRepr::FunctionPointer(_) => unreachable!("cannot compare function pointers"),
LayoutRepr::Erased(_) => unreachable!("cannot compare erased types"),
}
}
@ -1364,7 +1368,7 @@ fn build_box_eq<'a, 'ctx>(
env.builder.set_current_debug_location(di_location);
let call = env
.builder
.build_call(function, &[tag1.into(), tag2.into()], "tag_eq");
.build_call(function, &[tag1.into(), tag2.into()], "box_eq");
call.set_call_convention(FAST_CALL_CONV);
@ -1429,12 +1433,47 @@ fn build_box_eq_help<'a, 'ctx>(
"compare_pointers",
);
let compare_inner_value = ctx.append_basic_block(parent, "compare_inner_value");
let check_null_then_compare_inner_values =
ctx.append_basic_block(parent, "check_null_then_compare_inner_values");
env.builder.build_conditional_branch(
ptr_equal,
return_true,
check_null_then_compare_inner_values,
);
env.builder
.build_conditional_branch(ptr_equal, return_true, compare_inner_value);
.position_at_end(check_null_then_compare_inner_values);
env.builder.position_at_end(compare_inner_value);
// Check for nullability, then compare inner values
let box1_is_null = env
.builder
.build_is_null(box1.into_pointer_value(), "box1_is_null");
let check_box2_is_null = ctx.append_basic_block(parent, "check_if_box2_is_null");
let return_false = ctx.append_basic_block(parent, "return_false");
let compare_inner_values = ctx.append_basic_block(parent, "compare_inner_values");
env.builder
.build_conditional_branch(box1_is_null, return_false, check_box2_is_null);
{
env.builder.position_at_end(check_box2_is_null);
let box2_is_null = env
.builder
.build_is_null(box2.into_pointer_value(), "box2_is_null");
env.builder
.build_conditional_branch(box2_is_null, return_false, compare_inner_values);
}
{
env.builder.position_at_end(return_false);
env.builder
.build_return(Some(&env.context.bool_type().const_zero()));
}
// Compare the inner values.
env.builder.position_at_end(compare_inner_values);
// clear the tag_id so we get a pointer to the actual data
let box1 = box1.into_pointer_value();

View file

@ -1,14 +1,15 @@
use crate::llvm::build::{BuilderExt, Env};
use crate::llvm::build::{BuilderExt, Env, FunctionSpec, RocReturn};
use crate::llvm::erased;
use crate::llvm::memcpy::build_memcpy;
use bumpalo::collections::Vec as AVec;
use bumpalo::collections::{CollectIn, Vec as AVec};
use inkwell::context::Context;
use inkwell::types::{BasicType, BasicTypeEnum, FloatType, IntType, StructType};
use inkwell::values::PointerValue;
use inkwell::AddressSpace;
use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_mono::layout::{
round_up_to_alignment, Builtin, InLayout, Layout, LayoutInterner, LayoutRepr, STLayoutInterner,
UnionLayout,
round_up_to_alignment, Builtin, FunctionPointer, InLayout, Layout, LayoutInterner, LayoutRepr,
STLayoutInterner, UnionLayout,
};
use roc_target::TargetInfo;
@ -48,6 +49,22 @@ pub fn basic_type_from_layout<'a, 'ctx>(
.ptr_type(AddressSpace::default())
.as_basic_type_enum(),
FunctionPointer(self::FunctionPointer { args, ret }) => {
let args = args.iter().map(|arg| {
basic_type_from_layout(env, layout_interner, layout_interner.get_repr(*arg))
});
let ret_repr = layout_interner.get_repr(ret);
let ret = basic_type_from_layout(env, layout_interner, ret_repr);
let roc_return = RocReturn::from_layout(layout_interner, ret_repr);
let fn_spec = FunctionSpec::fastcc(env, roc_return, ret, args.collect_in(env.arena));
fn_spec.typ.ptr_type(AddressSpace::default()).into()
}
Erased(_) => erased::basic_type(env).into(),
Builtin(builtin) => basic_type_from_builtin(env, &builtin),
}
}

View file

@ -0,0 +1,131 @@
use inkwell::{
types::{PointerType, StructType},
values::{PointerValue, StructValue},
AddressSpace,
};
use roc_mono::ir::ErasedField;
use super::build::Env;
pub fn opaque_ptr_type<'ctx>(env: &Env<'_, 'ctx, '_>) -> PointerType<'ctx> {
env.context.i8_type().ptr_type(AddressSpace::default())
}
fn refcounter_type<'ctx>(env: &Env<'_, 'ctx, '_>) -> PointerType<'ctx> {
let return_void = env.context.void_type();
let arg_ty = opaque_ptr_type(env);
return_void
.fn_type(&[arg_ty.into()], false)
.ptr_type(AddressSpace::default())
}
/// Erased is laid out like
///
/// ```text
/// struct Erased {
/// value: void*,
/// callee: void*,
/// refcounter_inc: (void* -> void) *,
/// refcounter_dec: (void* -> void) *,
/// }
/// ```
pub fn basic_type<'ctx>(env: &Env<'_, 'ctx, '_>) -> StructType<'ctx> {
let opaque_ptr_ty = opaque_ptr_type(env);
let refcounter_ptr_ty = refcounter_type(env);
env.context.struct_type(
&[
opaque_ptr_ty.into(),
opaque_ptr_ty.into(),
refcounter_ptr_ty.into(),
refcounter_ptr_ty.into(),
],
false,
)
}
fn bitcast_to_opaque_ptr<'ctx>(
env: &Env<'_, 'ctx, '_>,
value: PointerValue<'ctx>,
) -> PointerValue<'ctx> {
env.builder
.build_bitcast(
value,
env.context.i8_type().ptr_type(AddressSpace::default()),
"to_opaque_ptr",
)
.into_pointer_value()
}
pub fn build<'ctx>(
env: &Env<'_, 'ctx, '_>,
value: Option<PointerValue<'ctx>>,
callee: PointerValue<'ctx>,
) -> StructValue<'ctx> {
let struct_type = basic_type(env);
let struct_value = struct_type.const_zero().into();
let struct_value = match value {
Some(value) => {
let value = bitcast_to_opaque_ptr(env, value);
env.builder
.build_insert_value(struct_value, value, 0, "insert_value")
.unwrap()
}
None => struct_value,
};
let callee = bitcast_to_opaque_ptr(env, callee);
let struct_value = env
.builder
.build_insert_value(struct_value, callee, 1, "insert_callee")
.unwrap();
// TODO: insert refcounter
struct_value.into_struct_value()
}
pub fn load<'ctx>(
env: &Env<'_, 'ctx, '_>,
erasure: StructValue<'ctx>,
field: ErasedField,
as_type: PointerType<'ctx>,
) -> PointerValue<'ctx> {
let index = match field {
ErasedField::Value => 0,
ErasedField::ValuePtr => 0,
ErasedField::Callee => 1,
};
let value = env
.builder
.build_extract_value(erasure, index, "extract_erased_value")
.unwrap()
.into_pointer_value();
let value = env
.builder
.build_bitcast(value, as_type, "bitcast_to_type")
.into_pointer_value();
value
}
pub fn load_refcounter<'ctx>(
env: &Env<'_, 'ctx, '_>,
erasure: StructValue<'ctx>,
mode: super::refcounting::Mode,
) -> PointerValue<'ctx> {
let index = match mode {
super::refcounting::Mode::Inc => 2,
super::refcounting::Mode::Dec => 3,
};
env.builder
.build_extract_value(erasure, index, "extract_refcounter")
.unwrap()
.into_pointer_value()
}

View file

@ -9,7 +9,7 @@ use inkwell::types::{BasicMetadataTypeEnum, BasicType, BasicTypeEnum};
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue};
use inkwell::AddressSpace;
use roc_builtins::bitcode;
use roc_error_macros::internal_error;
use roc_error_macros::{internal_error, todo_lambda_erasure};
use roc_module::symbol::Symbol;
use roc_mono::ir::LookupType;
use roc_mono::layout::{
@ -387,6 +387,8 @@ fn build_clone<'a, 'ctx>(
union_layout,
)
}
LayoutRepr::FunctionPointer(_) => todo_lambda_erasure!(),
LayoutRepr::Erased(_) => todo_lambda_erasure!(),
}
}
@ -798,7 +800,7 @@ fn build_clone_tag_help<'a, 'ctx>(
let mut cases = Vec::with_capacity_in(other_tags.len(), env.arena);
for i in 0..other_tags.len() + 1 {
if i == nullable_id as _ {
if i == nullable_id as usize {
continue;
}

View file

@ -0,0 +1,48 @@
use bumpalo::collections::CollectIn;
use inkwell::{
types::{FunctionType, PointerType},
values::{FunctionValue, PointerValue},
};
use roc_mono::layout::{InLayout, LambdaName, LayoutInterner, STLayoutInterner};
use super::{
build::{function_value_by_func_spec, Env, FuncBorrowSpec, FunctionSpec, RocReturn},
convert::{argument_type_from_layout, basic_type_from_layout},
};
pub fn function_type<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
layout_interner: &STLayoutInterner<'a>,
arguments: &[InLayout<'a>],
return_type: InLayout<'a>,
) -> FunctionType<'ctx> {
let args = arguments
.iter()
.map(|arg| argument_type_from_layout(env, layout_interner, layout_interner.get_repr(*arg)));
let ret_repr = layout_interner.get_repr(return_type);
let ret = basic_type_from_layout(env, layout_interner, ret_repr);
let roc_return = RocReturn::from_layout(layout_interner, ret_repr);
let fn_spec = FunctionSpec::fastcc(env, roc_return, ret, args.collect_in(env.arena));
fn_spec.typ
}
pub fn build<'a, 'ctx>(env: &Env<'a, 'ctx, '_>, lambda_name: LambdaName<'a>) -> PointerValue<'ctx> {
let func_value: FunctionValue<'ctx> =
function_value_by_func_spec(env, FuncBorrowSpec::Erased, lambda_name.name());
func_value.as_global_value().as_pointer_value()
}
pub fn cast_to_function_ptr_type<'ctx>(
env: &Env<'_, 'ctx, '_>,
pointer: PointerValue<'ctx>,
function_pointer_type: PointerType<'ctx>,
) -> PointerValue<'ctx> {
env.builder
.build_bitcast(pointer, function_pointer_type, "cast_to_function_ptr")
.into_pointer_value()
}

View file

@ -30,8 +30,8 @@ use crate::llvm::{
},
build::{
cast_basic_basic, complex_bitcast_check_size, create_entry_block_alloca,
entry_block_alloca_zerofill, function_value_by_func_spec, load_roc_value,
roc_function_call, tag_pointer_clear_tag_id, BuilderExt, RocReturn,
function_value_by_func_spec, load_roc_value, roc_function_call, tag_pointer_clear_tag_id,
BuilderExt, FuncBorrowSpec, RocReturn,
},
build_list::{
list_append_unsafe, list_concat, list_drop_at, list_get_unsafe, list_len, list_map,
@ -1331,16 +1331,6 @@ pub(crate) fn run_low_level<'a, 'ctx>(
tag_pointer_clear_tag_id(env, ptr.into_pointer_value()).into()
}
Alloca => {
arguments!(initial_value);
let ptr = entry_block_alloca_zerofill(env, initial_value.get_type(), "stack_value");
env.builder.build_store(ptr, initial_value);
ptr.into()
}
RefCountIncRcPtr | RefCountDecRcPtr | RefCountIncDataPtr | RefCountDecDataPtr => {
unreachable!("Not used in LLVM backend: {:?}", op);
}
@ -1354,7 +1344,7 @@ pub(crate) fn run_low_level<'a, 'ctx>(
"cast_to_i8_ptr",
);
let value_ptr = match layout_interner.get_repr(data_layout) {
let value_ptr = match layout_interner.runtime_representation(data_layout) {
LayoutRepr::Union(union_layout)
if union_layout.stores_tag_id_in_pointer(env.target_info) =>
{
@ -1396,6 +1386,8 @@ pub(crate) fn run_low_level<'a, 'ctx>(
call_bitcode_fn(env, &[], bitcode::UTILS_DICT_PSEUDO_SEED)
}
SetJmp | LongJmp | SetLongJmpBuffer => unreachable!("only inserted in dev backend codegen"),
}
}
@ -2611,7 +2603,7 @@ pub(crate) fn run_higher_order_low_level<'a, 'ctx>(
} = higher_order;
let PassedFunction {
argument_layouts,
argument_layouts: _,
return_layout: result_layout,
owns_captured_environment: function_owns_closure_data,
name: function_name,
@ -2624,11 +2616,8 @@ pub(crate) fn run_higher_order_low_level<'a, 'ctx>(
() => {{
let function = function_value_by_func_spec(
env,
func_spec,
FuncBorrowSpec::Some(func_spec),
function_name.name(),
argument_layouts,
function_name.niche(),
return_layout,
);
let (closure, closure_layout) =

View file

@ -11,6 +11,8 @@ mod lowlevel;
pub mod refcounting;
mod align;
mod erased;
mod fn_ptr;
mod memcpy;
mod scope;
mod struct_;

Some files were not shown because too many files have changed in this diff Show more