Merge branch 'main' into builtin-json

This commit is contained in:
Luke Boswell 2023-04-04 17:21:08 +10:00
commit dc43290647
No known key found for this signature in database
GPG key ID: F6DB3C9DB47377B0
199 changed files with 10195 additions and 3810 deletions

View file

@ -3,6 +3,7 @@ test-gen-llvm = "test -p test_gen"
test-gen-dev = "test -p roc_gen_dev -p test_gen --no-default-features --features gen-dev"
test-gen-wasm = "test -p roc_gen_wasm -p test_gen --no-default-features --features gen-wasm"
test-gen-llvm-wasm = "test -p roc_gen_wasm -p test_gen --no-default-features --features gen-llvm-wasm"
uitest = "test -p uitest"
[target.wasm32-unknown-unknown]
# Rust compiler flags for minimum-sized .wasm binary in the web REPL

250
Cargo.lock generated
View file

@ -232,6 +232,12 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "487f1e0fcbe47deb8b0574e646def1c903389d95241dd1bbcc6ce4a715dfc0c1"
[[package]]
name = "bitmaps"
version = "2.1.0"
@ -420,7 +426,7 @@ version = "2.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"textwrap 0.11.0",
"unicode-width",
]
@ -432,14 +438,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5"
dependencies = [
"atty",
"bitflags",
"clap_lex",
"bitflags 1.3.2",
"clap_lex 0.2.4",
"indexmap",
"strsim",
"termcolor",
"textwrap 0.16.0",
]
[[package]]
name = "clap"
version = "4.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42dfd32784433290c51d92c438bb72ea5063797fc3cc9a21a8c4346bebbb2098"
dependencies = [
"bitflags 2.0.2",
"clap_derive",
"clap_lex 0.3.3",
"is-terminal",
"once_cell",
"strsim",
"termcolor",
]
[[package]]
name = "clap_derive"
version = "4.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fddf67631444a3a3e3e5ac51c36a5e01335302de677bd78759eaa90ab1f46644"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.2.4"
@ -449,6 +483,15 @@ dependencies = [
"os_str_bytes",
]
[[package]]
name = "clap_lex"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "033f6b7a4acb1f358c742aaca805c939ee73b4c6209ae4318ec7aca81c42e646"
dependencies = [
"os_str_bytes",
]
[[package]]
name = "cli_utils"
version = "0.0.1"
@ -493,7 +536,7 @@ version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"block",
"cocoa-foundation",
"core-foundation 0.9.3",
@ -509,7 +552,7 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"block",
"core-foundation 0.9.3",
"core-graphics-types",
@ -656,7 +699,7 @@ version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3889374e6ea6ab25dba90bb5d96202f61108058361f6dc72e8b03e6f8bbe923"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"core-foundation 0.7.0",
"foreign-types",
"libc",
@ -668,7 +711,7 @@ version = "0.22.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"core-foundation 0.9.3",
"core-graphics-types",
"foreign-types",
@ -681,7 +724,7 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"core-foundation 0.9.3",
"foreign-types",
"libc",
@ -881,7 +924,7 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2daefd788d1e96e0a9d66dee4b828b883509bc3ea9ce30665f04c3246372690c"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"libloading",
"winapi",
]
@ -1134,7 +1177,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ef1a30ae415c3a691a4f41afddc2dbcd6d70baf338368d85ebc1e8ed92cedb9"
dependencies = [
"cfg-if 1.0.0",
"rustix",
"rustix 0.36.9",
"windows-sys 0.45.0",
]
@ -1419,7 +1462,7 @@ version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fc59e5f710e310e76e6707f86c561dd646f69a8876da9131703b2f717de818d"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"gpu-alloc-types",
]
@ -1429,7 +1472,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54804d0d6bc9d7f26db4eaec1ad10def69b599315f487d32c334a80d1efe67a5"
dependencies = [
"bitflags",
"bitflags 1.3.2",
]
[[package]]
@ -1438,7 +1481,7 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b0c02e1ba0bdb14e965058ca34e09c020f8e507a760df1121728e0aef68d57a"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"gpu-descriptor-types",
"hashbrown 0.12.3",
]
@ -1449,7 +1492,7 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "363e3677e55ad168fef68cf9de3a4a310b53124c5e784c53a1d70e92d23f2126"
dependencies = [
"bitflags",
"bitflags 1.3.2",
]
[[package]]
@ -1520,6 +1563,12 @@ dependencies = [
"libc",
]
[[package]]
name = "hermit-abi"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
[[package]]
name = "hexf-parse"
version = "0.2.1"
@ -1745,6 +1794,18 @@ version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146"
[[package]]
name = "is-terminal"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "256017f749ab3117e93acb91063009e1f1bb56d03965b14c2c8df4eb02c524d8"
dependencies = [
"hermit-abi 0.3.1",
"io-lifetimes",
"rustix 0.37.3",
"windows-sys 0.45.0",
]
[[package]]
name = "itertools"
version = "0.9.0"
@ -1848,6 +1909,17 @@ dependencies = [
"libc",
]
[[package]]
name = "libtest-mimic"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7b603516767d1ab23d0de09d023e62966c3322f7148297c35cf3d97aa8b37fa"
dependencies = [
"clap 4.1.11",
"termcolor",
"threadpool",
]
[[package]]
name = "linked-hash-map"
version = "0.5.6"
@ -1860,6 +1932,12 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
[[package]]
name = "linux-raw-sys"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f"
[[package]]
name = "llvm-sys"
version = "130.0.7"
@ -1898,7 +1976,7 @@ version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b6f2d7176b94027af58085a2c9d27c4e416586caba409c314569213901d6068"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"byteorder",
"lazy_static",
"libc",
@ -1980,7 +2058,7 @@ version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0514f491f4cc03632ab399ee01e2c1c1b12d3e1cf2d667c1ff5f87d6dcd2084"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"block",
"core-graphics-types",
"foreign-types",
@ -2048,7 +2126,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3012f2dbcc79e8e0b5825a4836a7106a75dd9b2fe42c528163be0f572538c705"
dependencies = [
"bit-set",
"bitflags",
"bitflags 1.3.2",
"codespan-reporting",
"hexf-parse",
"indexmap",
@ -2065,7 +2143,7 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d868f654c72e75f8687572699cdabe755f03effbb62542768e995d5b8d699d"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"jni-sys",
"ndk-sys",
"num_enum",
@ -2127,7 +2205,7 @@ version = "0.22.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"cc",
"cfg-if 1.0.0",
"libc",
@ -2140,7 +2218,7 @@ version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"cc",
"cfg-if 1.0.0",
"libc",
@ -2153,7 +2231,7 @@ version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"cfg-if 1.0.0",
"libc",
"memoffset 0.6.5",
@ -2165,7 +2243,7 @@ version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"cfg-if 1.0.0",
"libc",
"static_assertions",
@ -2646,6 +2724,30 @@ dependencies = [
"toml_edit",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.51"
@ -2668,7 +2770,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29f1b898011ce9595050a68e60f90bad083ff2987a695a42357134c8381fba70"
dependencies = [
"bit-set",
"bitflags",
"bitflags 1.3.2",
"byteorder",
"lazy_static",
"num-traits",
@ -2688,7 +2790,7 @@ version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d9cc634bc78768157b5cbfe988ffcd1dcba95cd2b2f03a88316c08c6d00ed63"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"memchr",
"unicase",
]
@ -2843,7 +2945,7 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
"bitflags 1.3.2",
]
[[package]]
@ -3193,6 +3295,7 @@ dependencies = [
"hashbrown 0.13.2",
"im",
"im-rc",
"smallvec",
"wyhash",
]
@ -3427,17 +3530,23 @@ dependencies = [
name = "roc_glue"
version = "0.0.1"
dependencies = [
"backtrace",
"bumpalo",
"cli_utils",
"dircpy",
"fnv",
"indexmap",
"indoc",
"libc",
"libloading",
"pretty_assertions",
"roc_build",
"roc_builtins",
"roc_can",
"roc_collections",
"roc_error_macros",
"roc_gen_llvm",
"roc_linker",
"roc_load",
"roc_module",
"roc_mono",
@ -3811,6 +3920,7 @@ dependencies = [
"indoc",
"insta",
"lazy_static",
"libtest-mimic",
"pretty_assertions",
"regex",
"roc_builtins",
@ -3834,6 +3944,7 @@ dependencies = [
"roc_types",
"roc_unify",
"tempfile",
"test_solve_helpers",
]
[[package]]
@ -3908,7 +4019,7 @@ dependencies = [
name = "roc_unify"
version = "0.0.1"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"roc_collections",
"roc_debug_flags",
"roc_error_macros",
@ -3964,11 +4075,25 @@ version = "0.36.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"errno 0.2.8",
"io-lifetimes",
"libc",
"linux-raw-sys",
"linux-raw-sys 0.1.4",
"windows-sys 0.45.0",
]
[[package]]
name = "rustix"
version = "0.37.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b24138615de35e32031d041a09032ef3487a616d901ca4db224e7d557efae2"
dependencies = [
"bitflags 1.3.2",
"errno 0.3.0",
"io-lifetimes",
"libc",
"linux-raw-sys 0.3.1",
"windows-sys 0.45.0",
]
@ -4016,7 +4141,7 @@ name = "rustyline"
version = "9.1.1"
source = "git+https://github.com/roc-lang/rustyline?rev=e74333c#e74333c0d618896b88175bf06645108f996fe6d0"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"cfg-if 1.0.0",
"clipboard-win 4.5.0",
"dirs-next",
@ -4308,7 +4433,7 @@ version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a28f16a97fa0e8ce563b2774d1e732dd5d4025d2772c5dba0a41a0f90a29da3"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"calloop",
"dlib",
"lazy_static",
@ -4327,7 +4452,7 @@ version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f307c47d32d2715eb2e0ece5589057820e0e5e70d07c247d1063e844e107f454"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"dlib",
"lazy_static",
"log",
@ -4394,7 +4519,7 @@ version = "0.2.0+1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "246bfa38fe3db3f1dfc8ca5a2cdeb7348c78be2112740cc0ec8ef18b6d94f830"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"num-traits",
]
@ -4611,6 +4736,32 @@ dependencies = [
"syn",
]
[[package]]
name = "test_solve_helpers"
version = "0.0.1"
dependencies = [
"bumpalo",
"indoc",
"insta",
"lazy_static",
"pretty_assertions",
"regex",
"roc_can",
"roc_derive",
"roc_late_solve",
"roc_load",
"roc_module",
"roc_packaging",
"roc_problem",
"roc_region",
"roc_reporting",
"roc_solve",
"roc_solve_problem",
"roc_target",
"roc_types",
"tempfile",
]
[[package]]
name = "test_syntax"
version = "0.0.1"
@ -4921,6 +5072,29 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81"
[[package]]
name = "uitest"
version = "0.0.1"
dependencies = [
"bumpalo",
"indoc",
"insta",
"lazy_static",
"libtest-mimic",
"pretty_assertions",
"regex",
"roc_builtins",
"roc_derive",
"roc_load",
"roc_parse",
"roc_problem",
"roc_reporting",
"roc_solve",
"roc_target",
"tempfile",
"test_solve_helpers",
]
[[package]]
name = "unarray"
version = "0.1.4"
@ -5194,7 +5368,7 @@ version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"downcast-rs",
"libc",
"nix 0.24.3",
@ -5233,7 +5407,7 @@ version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"wayland-client",
"wayland-commons",
"wayland-scanner",
@ -5318,7 +5492,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4688c000eb841ca55f7b35db659b78d6e1cd77d7caf8fb929f4e181f754047d"
dependencies = [
"arrayvec 0.7.2",
"bitflags",
"bitflags 1.3.2",
"cfg_aliases",
"codespan-reporting",
"copyless",
@ -5343,7 +5517,7 @@ dependencies = [
"arrayvec 0.7.2",
"ash",
"bit-set",
"bitflags",
"bitflags 1.3.2",
"block",
"core-graphics-types",
"d3d12",
@ -5378,7 +5552,7 @@ version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "549533d9e1cdd4b4cda7718d33ff500fc4c34b5467b71d76b547ae0324f3b2a2"
dependencies = [
"bitflags",
"bitflags 1.3.2",
]
[[package]]
@ -5520,7 +5694,7 @@ version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b43cc931d58b99461188607efd7acb2a093e65fc621f54cad78517a6063e73a"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"cocoa",
"core-foundation 0.9.3",
"core-graphics 0.22.3",

View file

@ -71,6 +71,7 @@ version = "0.0.1"
inkwell = { git = "https://github.com/roc-lang/inkwell", branch = "inkwell-llvm-15", features = ["llvm13-0"] }
arrayvec = "0.7.2" # update roc_std/Cargo.toml on change
backtrace = "0.3.67"
base64-url = "1.4.13"
bincode = "1.3.3"
bitflags = "1.3.2"
@ -111,6 +112,7 @@ lazy_static = "1.4.0"
libc = "0.2.139" # update roc_std/Cargo.toml on change
libfuzzer-sys = "0.4"
libloading = "0.7.4"
libtest-mimic = "0.6.0"
log = "0.4.17"
mach_object = "0.1"
maplit = "1.0.2"
@ -145,6 +147,7 @@ serde-xml-rs = "0.6.0"
serde_json = "1.0.94" # update roc_std/Cargo.toml on change
serial_test = "1.0.0"
signal-hook = "0.3.15"
smallvec = { version = "1.10.0", features = ["const_generics", "const_new"] }
snafu = { version = "0.7.4", features = ["backtraces"] }
static_assertions = "1.1.0" # update roc_std/Cargo.toml on change
strip-ansi-escapes = "0.1.1"

View file

@ -24,6 +24,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:
* [Jonas Schell](https://github.com/Ocupe)
* [Christopher Dolan](https://github.com/cdolan)
* [Nick Gravgaard](https://github.com/nickgravgaard)
* [Aaron White](https://github.com/aaronwhite)

View file

@ -7,10 +7,12 @@ use bumpalo::Bump;
use clap::{Arg, ArgMatches, Command, ValueSource};
use roc_build::link::{LinkType, LinkingStrategy};
use roc_build::program::{
standard_load_config, BuildFileError, BuildOrdering, BuiltFile, CodeGenBackend, CodeGenOptions,
handle_error_module, handle_loading_problem, standard_load_config, BuildFileError,
BuildOrdering, BuiltFile, CodeGenBackend, CodeGenOptions, DEFAULT_ROC_FILENAME,
};
use roc_error_macros::{internal_error, user_error};
use roc_load::{ExpectMetadata, LoadingProblem, Threading};
use roc_gen_llvm::llvm::build::LlvmBackendMode;
use roc_load::{ExpectMetadata, Threading};
use roc_mono::ir::OptLevel;
use roc_packaging::cache::RocCacheDir;
use roc_packaging::tarball::Compression;
@ -33,8 +35,6 @@ use tempfile::TempDir;
mod format;
pub use format::format;
const DEFAULT_ROC_FILENAME: &str = "main.roc";
pub const CMD_BUILD: &str = "build";
pub const CMD_RUN: &str = "run";
pub const CMD_DEV: &str = "dev";
@ -64,7 +64,8 @@ pub const FLAG_CHECK: &str = "check";
pub const FLAG_WASM_STACK_SIZE_KB: &str = "wasm-stack-size-kb";
pub const ROC_FILE: &str = "ROC_FILE";
pub const ROC_DIR: &str = "ROC_DIR";
pub const GLUE_FILE: &str = "GLUE_FILE";
pub const GLUE_DIR: &str = "GLUE_DIR";
pub const GLUE_SPEC: &str = "GLUE_SPEC";
pub const DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES";
pub const ARGS_FOR_APP: &str = "ARGS_FOR_APP";
@ -279,17 +280,24 @@ pub fn build_app<'a>() -> Command<'a> {
.subcommand(Command::new(CMD_GLUE)
.about("Generate glue code between a platform's Roc API and its host language")
.arg(
Arg::new(ROC_FILE)
.help("The .roc file for the platform module")
Arg::new(GLUE_SPEC)
.help("The specification for how to translate Roc types into output files.")
.allow_invalid_utf8(true)
.required(true)
)
.arg(
Arg::new(GLUE_FILE)
.help("The filename for the generated glue code\n(Currently, this must be a .rs file because only Rust glue generation is supported so far.)")
Arg::new(GLUE_DIR)
.help("The directory for the generated glue code.\nNote: The implementation can write to any file in this directory.")
.allow_invalid_utf8(true)
.required(true)
)
.arg(
Arg::new(ROC_FILE)
.help("The .roc file whose exposed types should be translated.")
.allow_invalid_utf8(true)
.required(false)
.default_value(DEFAULT_ROC_FILENAME)
)
)
.subcommand(Command::new(CMD_GEN_STUB_LIB)
.about("Generate a stubbed shared library that can be used for linking a platform binary.\nThe stubbed library has prototypes, but no function bodies.\n\nNote: This command will be removed in favor of just using `roc build` once all platforms support the surgical linker")
@ -359,7 +367,6 @@ 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_gen_llvm::llvm::build::LlvmBackendMode;
use roc_load::{ExecutionMode, LoadConfig, LoadMonomorphizedError};
use roc_packaging::cache;
use roc_target::TargetInfo;
@ -593,15 +600,6 @@ pub fn build(
// so we don't want to spend time freeing these values
let arena = ManuallyDrop::new(Bump::new());
let code_gen_backend = if matches!(triple.architecture, Architecture::Wasm32) {
CodeGenBackend::Wasm
} else {
match matches.is_present(FLAG_DEV) {
true => CodeGenBackend::Assembly,
false => CodeGenBackend::Llvm,
}
};
let opt_level = if let BuildConfig::BuildAndRunIfNoErrors = config {
OptLevel::Development
} else {
@ -617,6 +615,25 @@ pub fn build(
}
}
};
let code_gen_backend = if matches!(triple.architecture, Architecture::Wasm32) {
CodeGenBackend::Wasm
} else {
match matches.is_present(FLAG_DEV) {
true => CodeGenBackend::Assembly,
false => {
let backend_mode = match opt_level {
OptLevel::Development => LlvmBackendMode::BinaryDev,
OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => {
LlvmBackendMode::Binary
}
};
CodeGenBackend::Llvm(backend_mode)
}
}
};
let emit_debug_info = matches.is_present(FLAG_DEBUG);
let emit_timings = matches.is_present(FLAG_TIME);
@ -759,48 +776,6 @@ pub fn build(
}
}
fn handle_error_module(
mut module: roc_load::LoadedModule,
total_time: std::time::Duration,
filename: &OsStr,
print_run_anyway_hint: bool,
) -> io::Result<i32> {
debug_assert!(module.total_problems() > 0);
let problems = roc_build::program::report_problems_typechecked(&mut module);
problems.print_to_stdout(total_time);
if print_run_anyway_hint {
// If you're running "main.roc" then you can just do `roc run`
// to re-run the program.
print!(".\n\nYou can run the program anyway with \x1B[32mroc run");
if filename != DEFAULT_ROC_FILENAME {
print!(" {}", &filename.to_string_lossy());
}
println!("\x1B[39m");
}
Ok(problems.exit_code())
}
fn handle_loading_problem(problem: LoadingProblem) -> io::Result<i32> {
match problem {
LoadingProblem::FormattedReport(report) => {
print!("{}", report);
Ok(1)
}
_ => {
// TODO: tighten up the types here, we should always end up with a
// formatted report from load.
print!("Failed with error: {:?}", problem);
Ok(1)
}
}
}
fn roc_run<'a, I: IntoIterator<Item = &'a OsStr>>(
arena: &Bump,
opt_level: OptLevel,
@ -1322,40 +1297,3 @@ impl std::str::FromStr for Target {
}
}
}
// These functions don't end up in the final Roc binary but Windows linker needs a definition inside the crate.
// On Windows, there seems to be less dead-code-elimination than on Linux or MacOS, or maybe it's done later.
#[cfg(windows)]
#[allow(unused_imports)]
use windows_roc_platform_functions::*;
#[cfg(windows)]
mod windows_roc_platform_functions {
use core::ffi::c_void;
/// # Safety
/// The Roc application needs this.
#[no_mangle]
pub unsafe fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
libc::malloc(size)
}
/// # Safety
/// The Roc application needs this.
#[no_mangle]
pub unsafe fn roc_realloc(
c_ptr: *mut c_void,
new_size: usize,
_old_size: usize,
_alignment: u32,
) -> *mut c_void {
libc::realloc(c_ptr, new_size)
}
/// # Safety
/// The Roc application needs this.
#[no_mangle]
pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
libc::free(c_ptr)
}
}

View file

@ -5,7 +5,7 @@ use roc_cli::{
build_app, format, test, BuildConfig, FormatMode, Target, CMD_BUILD, CMD_CHECK, CMD_DEV,
CMD_DOCS, CMD_EDIT, CMD_FORMAT, CMD_GEN_STUB_LIB, CMD_GLUE, CMD_REPL, CMD_RUN, CMD_TEST,
CMD_VERSION, DIRECTORY_OR_FILES, FLAG_CHECK, FLAG_LIB, FLAG_NO_LINK, FLAG_TARGET, FLAG_TIME,
GLUE_FILE, ROC_FILE,
GLUE_DIR, GLUE_SPEC, ROC_FILE,
};
use roc_docs::generate_docs_html;
use roc_error_macros::user_error;
@ -88,12 +88,13 @@ fn main() -> io::Result<()> {
}
Some((CMD_GLUE, matches)) => {
let input_path = Path::new(matches.value_of_os(ROC_FILE).unwrap());
let output_path = Path::new(matches.value_of_os(GLUE_FILE).unwrap());
let output_path = Path::new(matches.value_of_os(GLUE_DIR).unwrap());
let spec_path = Path::new(matches.value_of_os(GLUE_SPEC).unwrap());
if Some("rs") == output_path.extension().and_then(OsStr::to_str) {
roc_glue::generate(input_path, output_path)
if !output_path.exists() || output_path.is_dir() {
roc_glue::generate(input_path, output_path, spec_path)
} else {
eprintln!("Currently, `roc glue` only supports generating Rust glue files (with the .rs extension). In the future, the plan is to decouple `roc glue` from any particular output format, by having it accept a second .roc file which gets executed as a plugin to generate glue code for any desired language. However, this has not yet been implemented, and for now only .rs is supported.");
eprintln!("`roc glue` must be given a directory to output into, because the glue might generate multiple files.");
Ok(1)
}

View file

@ -14,6 +14,7 @@ use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::process::{Command, ExitStatus, Stdio};
use std::sync::Mutex;
use tempfile::NamedTempFile;
#[derive(Debug)]
@ -87,11 +88,19 @@ pub fn build_roc_bin(extra_args: &[&str]) -> PathBuf {
roc_binary_path
}
// Since glue is always compiling the same plugin, it can not be run in parallel.
// That would lead to a race condition in writing the output shared library.
// Thus, all calls to glue in a test are made sequential.
// TODO: In the future, look into compiling the shared libary once and then caching it.
static GLUE_LOCK: Mutex<()> = Mutex::new(());
pub fn run_glue<I, S>(args: I) -> Out
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
let _guard = GLUE_LOCK.lock().unwrap();
run_roc_with_stdin(&path_to_roc_binary(), args, &[])
}

View file

@ -305,8 +305,8 @@ where
debug_assert_eq!(variant_types.len(), 1);
variant_types[0]
} else {
let data_type = builder.add_union_type(&variant_types)?;
let cell_type = builder.add_heap_cell_type();
let data_type = builder.add_union_type(&variant_types)?;
builder.add_tuple_type(&[cell_type, data_type])?
};
@ -1289,6 +1289,11 @@ fn expr_spec<'a>(
match expr {
Literal(literal) => literal_spec(builder, block, literal),
NullPointer => {
let pointer_type = layout_spec(env, builder, interner, layout)?;
builder.add_unknown_with(block, &[], pointer_type)
}
Call(call) => call_spec(builder, interner, env, block, layout, call),
Reuse {
tag_layout,
@ -1467,7 +1472,8 @@ fn expr_spec<'a>(
let _unit = builder.add_update(block, update_mode_var, heap_cell)?;
with_new_heap_cell(builder, block, union_data)
let value = with_new_heap_cell(builder, block, union_data)?;
builder.add_make_named(block, MOD_APP, type_name, value)
}
RuntimeErrorFunction(_) => {
let type_id = layout_spec(env, builder, interner, layout)?;

View file

@ -17,6 +17,7 @@ use roc_reporting::{
report::{RenderTarget, DEFAULT_PALETTE},
};
use roc_target::TargetInfo;
use std::ffi::OsStr;
use std::ops::Deref;
use std::{
path::{Path, PathBuf},
@ -28,6 +29,8 @@ use target_lexicon::Triple;
#[cfg(feature = "target-wasm32")]
use roc_collections::all::MutSet;
pub const DEFAULT_ROC_FILENAME: &str = "main.roc";
#[derive(Debug, Clone, Copy, Default)]
pub struct CodeGenTiming {
pub code_gen: Duration,
@ -72,7 +75,7 @@ impl Deref for CodeObject {
#[derive(Debug, Clone, Copy)]
pub enum CodeGenBackend {
Assembly,
Llvm,
Llvm(LlvmBackendMode),
Wasm,
}
@ -95,6 +98,10 @@ pub fn gen_from_mono_module<'a>(
preprocessed_host_path: &Path,
wasm_dev_stack_bytes: Option<u32>,
) -> GenFromMono<'a> {
let path = roc_file_path;
let debug = code_gen_options.emit_debug_info;
let opt = code_gen_options.opt_level;
match code_gen_options.backend {
CodeGenBackend::Assembly => gen_from_mono_module_dev(
arena,
@ -103,12 +110,18 @@ pub fn gen_from_mono_module<'a>(
preprocessed_host_path,
wasm_dev_stack_bytes,
),
CodeGenBackend::Llvm => {
gen_from_mono_module_llvm(arena, loaded, roc_file_path, target, code_gen_options)
CodeGenBackend::Llvm(backend_mode) => {
gen_from_mono_module_llvm(arena, loaded, path, target, opt, backend_mode, debug)
}
CodeGenBackend::Wasm => {
// emit wasm via the llvm backend
gen_from_mono_module_llvm(arena, loaded, roc_file_path, target, code_gen_options)
let backend_mode = match code_gen_options.opt_level {
OptLevel::Development => LlvmBackendMode::BinaryDev,
OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => LlvmBackendMode::Binary,
};
gen_from_mono_module_llvm(arena, loaded, path, target, opt, backend_mode, debug)
}
}
}
@ -121,7 +134,9 @@ fn gen_from_mono_module_llvm<'a>(
mut loaded: MonomorphizedModule<'a>,
roc_file_path: &Path,
target: &target_lexicon::Triple,
code_gen_options: CodeGenOptions,
opt_level: OptLevel,
backend_mode: LlvmBackendMode,
emit_debug_info: bool,
) -> GenFromMono<'a> {
use crate::target::{self, convert_opt_level};
use inkwell::attributes::{Attribute, AttributeLoc};
@ -171,12 +186,6 @@ fn gen_from_mono_module_llvm<'a>(
}
}
let CodeGenOptions {
backend: _,
opt_level,
emit_debug_info,
} = code_gen_options;
let builder = context.create_builder();
let (dibuilder, compile_unit) = roc_gen_llvm::llvm::build::Env::new_debug_info(module);
let (mpm, _fpm) = roc_gen_llvm::llvm::build::construct_optimization_passes(module, opt_level);
@ -191,10 +200,7 @@ fn gen_from_mono_module_llvm<'a>(
interns: loaded.interns,
module,
target_info,
mode: match opt_level {
OptLevel::Development => LlvmBackendMode::BinaryDev,
OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => LlvmBackendMode::Binary,
},
mode: backend_mode,
exposed_to_host: loaded
.exposed_to_host
@ -652,6 +658,48 @@ impl<'a> BuildFileError<'a> {
}
}
pub fn handle_error_module(
mut module: roc_load::LoadedModule,
total_time: std::time::Duration,
filename: &OsStr,
print_run_anyway_hint: bool,
) -> std::io::Result<i32> {
debug_assert!(module.total_problems() > 0);
let problems = report_problems_typechecked(&mut module);
problems.print_to_stdout(total_time);
if print_run_anyway_hint {
// If you're running "main.roc" then you can just do `roc run`
// to re-run the program.
print!(".\n\nYou can run the program anyway with \x1B[32mroc run");
if filename != DEFAULT_ROC_FILENAME {
print!(" {}", &filename.to_string_lossy());
}
println!("\x1B[39m");
}
Ok(problems.exit_code())
}
pub fn handle_loading_problem(problem: LoadingProblem) -> std::io::Result<i32> {
match problem {
LoadingProblem::FormattedReport(report) => {
print!("{}", report);
Ok(1)
}
_ => {
// TODO: tighten up the types here, we should always end up with a
// formatted report from load.
print!("Failed with error: {:?}", problem);
Ok(1)
}
}
}
pub fn standard_load_config(
target: &Triple,
order: BuildOrdering,
@ -1188,7 +1236,7 @@ pub fn build_str_test<'a>(
let triple = target_lexicon::Triple::host();
let code_gen_options = CodeGenOptions {
backend: CodeGenBackend::Llvm,
backend: CodeGenBackend::Llvm(LlvmBackendMode::Binary),
opt_level: OptLevel::Normal,
emit_debug_info: false,
};

View file

@ -1921,7 +1921,7 @@ pub fn fromUtf8Range(arg: RocList, start: usize, count: usize, update_mode: Upda
.problem_code = Utf8ByteProblem.InvalidStartByte,
};
}
const bytes = @ptrCast([*]const u8, arg.bytes)[start..count];
const bytes = @ptrCast([*]const u8, arg.bytes)[start .. start + count];
if (isValidUnicode(bytes)) {
// Make a seamless slice of the input.

View file

@ -1,3 +1,21 @@
## Roc strings are sequences of text values. This module includes functions for combining strings,
## as well as breaking them up into smaller units—most commonly [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster)
## (referred to in this module's documentation as "graphemes" rather than "characters" for clarity;
## "characters" can mean very different things in different languages).
##
## This module focuses on graphemes (as opposed to, say, Unicode code points or LATIN-1 bytes)
## because graphemes avoid common classes of bugs. Breaking strings up using code points often
## leads to bugs around things like emoji, where multiple code points combine to form to a
## single rendered glyph. Graphemes avoid these bugs by treating multi-code-point things like
## emojis as indivisible units.
##
## Because graphemes can have variable length (there's no upper limit on how many code points one
## grapheme can represent), it takes linear time to count the number of graphemes in a string,
## and also linear time to find an individual grapheme within a string by its position (or "index")
## among the string's other graphemes. The only way to get constant-time access to these is in a way
## that can result in bugs if the string contains multi-code-point things like emojis, which is why
## this module does not offer those.
##
##
## ## Working with Unicode strings in Roc
##
@ -107,6 +125,7 @@ interface Str
replaceLast,
splitFirst,
splitLast,
walkUtf8,
walkUtf8WithIndex,
reserve,
releaseExcessCapacity,
@ -151,8 +170,89 @@ isEmpty : Str -> Bool
concat : Str, Str -> Str
## Returns a string of the specified capacity without any content.
##
## This is a performance optimization tool that's like calling [Str.reserve] on an empty string.
## It's useful when you plan to build up a string incrementally, for example by calling [Str.concat] on it:
##
## ```
## greeting = "Hello and welcome to Roc"
## subject = "Awesome Programmer"
##
## # Evaluates to "Hello and welcome to Roc, Awesome Programmer!"
## helloWorld =
## Str.withCapacity 45
## |> Str.concat greeting
## |> Str.concat ", "
## |> Str.concat subject
## |> Str.concat "!"
## ```
##
## In general, if you plan to use [Str.concat] on an empty string, it will be faster to start with
## [Str.withCapacity] than with `""`. Even if you don't know the exact capacity of the string, giving [withCapacity]
## a higher value than ends up being necessary can help prevent reallocation and copying—at
## the cost of using more memory than is necessary.
##
## For more details on how the performance optimization works, see [Str.reserve].
withCapacity : Nat -> Str
## Increase a string's capacity by at least the given number of additional bytes.
##
## This can improve the performance of string concatenation operations like [Str.concat] by
## allocating extra capacity up front, which can prevent the need for reallocations and copies.
## Consider the following example which does not use [Str.reserve]:
##
## ```
## greeting = "Hello and welcome to Roc"
## subject = "Awesome Programmer"
##
## # Evaluates to "Hello and welcome to Roc, Awesome Programmer!"
## helloWorld =
## greeting
## |> Str.concat ", "
## |> Str.concat subject
## |> Str.concat "!"
## ```
##
## In this example:
## 1. We start with `greeting`, which has both a length and capacity of 24 (bytes).
## 2. `|> Str.concat ", "` will see that there isn't enough capacity to add 2 more bytes for the `", "`, so it will create a new heap allocation with enough bytes to hold both. (This probably will be more than 7 bytes, because when [Str] functions reallocate, they apply a multiplier to the exact capacity required. This makes it less likely that future realloctions will be needed. The multiplier amount is not specified, because it may change in future releases of Roc, but it will likely be around 1.5 to 2 times the exact capacity required.) Then it will copy the current bytes (`"Hello"`) into the new allocation, and finally concatenate the `", "` into the new allocation. The old allocation will then be deallocated because it's no longer referenced anywhere in the program.
## 3. `|> Str.concat subject` will again check if there is enough capacity in the string. If it doesn't find enough capacity once again, it will make a third allocation, copy the existing bytes (`"Hello, "`) into that third allocation, and then deallocate the second allocation because it's already no longer being referenced anywhere else in the program. (It may find enough capacity in this prticular case, because the previous [Str.concat] allocated something like 1.5 to 2 times the necessary capacity in order to anticipate future concatenations like this...but if something longer than `"World"` were being concatenated here, it might still require further reallocation and copying.)
## 4. `|> Str.concat "!\n"` will repeat this process once more.
##
## This process can have significant performance costs due to multiple reallocation of new strings, copying between old strings and new strings, and deallocation of immediately obsolete strings.
##
## Here's a modified example which uses [Str.reserve] to eliminate the need for all that reallocation, copying, and deallocation.
##
## ```
## helloWorld =
## greeting
## |> Str.reserve 21
## |> Str.concat ", "
## |> Str.concat subject
## |> Str.concat "!"
## ```
##
## In this example:
## 1. We again start with `greeting`, which has both a length and capacity of 24 bytes.
## 2. `|> Str.reserve 21` will ensure that there is enough capacity in the string for an additional 21 bytes (to make room for `", "`, `"Awesome Programmer"`, and `"!"`). Since the current capacity is only 24, it will create a new 45-byte (24 + 21) heap allocation and copy the contents of the existing allocation (`greeting`) into it.
## 3. `|> Str.concat ", "` will concatenate `, ` to the string. No reallocation, copying, or deallocation will be necessary, because the string already has a capacity of 45 btytes, and `greeting` will only use 24 of them.
## 4. `|> Str.concat subject` will concatenate `subject` (`"Awesome Programmer"`) to the string. Again, no reallocation, copying, or deallocation will be necessary.
## 5. `|> Str.concat "!\n"` will concatenate `"!\n"` to the string, still without any reallocation, copying, or deallocation.
##
## Here, [Str.reserve] prevented multiple reallocations, copies, and deallocations during the
## [Str.concat] calls. Notice that it did perform a heap allocation before any [Str.concat] calls
## were made, which means that using [Str.reserve] is not free! You should only use it if you actually
## expect to make use of the extra capacity.
##
## Ideally, you'd be able to predict exactly how many extra bytes of capacity will be needed, but this
## may not always be knowable. When you don't know exactly how many bytes to reserve, you can often get better
## performance by choosing a number of bytes that's too high, because a number that's too low could lead to reallocations. There's a limit to
## this, of course; if you always give it ten times what it turns out to need, that could prevent
## reallocations but will also waste a lot of memory!
##
## If you plan to use [Str.reserve] on an empty string, it's generally better to use [Str.withCapacity] instead.
reserve : Str, Nat -> Str
## Combines a [List] of strings into a single string, with a separator
## string in between each.
## ```
@ -200,7 +300,21 @@ repeat : Str, Nat -> Str
## using a single Unicode code point.
countGraphemes : Str -> Nat
## Split a string into its constituent grapheme clusters
## Split a string into its constituent graphemes.
##
## This function breaks a string into its individual [graphemes](https://stackoverflow.com/a/27331885/4200103),
## returning them as a list of strings. This is useful for working with text that
## contains complex characters, such as emojis.
##
## Examples:
## ```
## expect Str.graphemes "Roc" == ["R", "o", "c"]
## expect Str.graphemes "नमस्ते" == ["न", "म", "स्", "ते"]
## expect Str.graphemes "👩‍👩‍👦‍👦" == ["👩‍", "👩‍", "👦‍", "👦"]
## ```
##
## Note that the "👩‍👩‍👦‍👦" example consists of 4 grapheme clusters, although it visually
## appears as a single glyph. This is because it uses an emoji modifier sequence.
graphemes : Str -> List Str
## If the string begins with a [Unicode code point](http://www.unicode.org/glossary/#code_point)
@ -273,6 +387,12 @@ fromUtf8 = \bytes ->
else
Err (BadUtf8 result.dProblemCode result.aByteIndex)
expect (Str.fromUtf8 [82, 111, 99]) == Ok "Roc"
expect (Str.fromUtf8 [224, 174, 154, 224, 174, 191]) == Ok "சி"
expect (Str.fromUtf8 [240, 159, 144, 166]) == Ok "🐦"
expect (Str.fromUtf8 []) == Ok ""
expect (Str.fromUtf8 [255]) |> Result.isErr
## Encode part of a [List] of [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit)
## into a [Str]
## ```
@ -290,6 +410,12 @@ fromUtf8Range = \bytes, config ->
else
Err OutOfBounds
expect (Str.fromUtf8Range [72, 105, 80, 103] { start: 0, count: 2 }) == Ok "Hi"
expect (Str.fromUtf8Range [233, 185, 143, 224, 174, 154, 224, 174, 191] { start: 3, count: 3 }) == Ok "ச"
expect (Str.fromUtf8Range [240, 159, 144, 166] { start: 0, count: 4 }) == Ok "🐦"
expect (Str.fromUtf8Range [] { start: 0, count: 0 }) == Ok ""
expect (Str.fromUtf8Range [72, 105, 80, 103] { start: 2, count: 3 }) |> Result.isErr
FromUtf8Result : {
aByteIndex : Nat,
bString : Str,
@ -744,8 +870,29 @@ walkUtf8WithIndexHelp = \string, state, step, index, length ->
else
state
## Enlarge a string for at least the given number additional bytes.
reserve : Str, Nat -> Str
## Walks over the `UTF-8` bytes of the given [Str] and calls a function to update
## state for each byte.
##
## ```
## result = walkUtf8 "hello, world!" "" (\state, byte -> state ++ String.fromCodePoint byte)
## expect result == Ok "hello, world!"
## ```
walkUtf8 : Str, state, (state, U8 -> state) -> state
walkUtf8 = \str, initial, step ->
walkUtf8Help str initial step 0 (Str.countUtf8Bytes str)
walkUtf8Help : Str, state, (state, U8 -> state), Nat, Nat -> state
walkUtf8Help = \str, state, step, index, length ->
if index < length then
byte = Str.getUnsafe str index
newState = step state byte
walkUtf8Help str newState step (index + 1) length
else
state
expect (walkUtf8 "ABC" [] List.append) == [65, 66, 67]
expect (walkUtf8 "鹏" [] List.append) == [233, 185, 143]
## Shrink the memory footprint of a str such that it's capacity and length are equal.
## Note: This will also convert seamless slices to regular lists.

View file

@ -2,4 +2,5 @@ mod pretty_print;
pub use pretty_print::pretty_print_declarations;
pub use pretty_print::pretty_print_def;
pub use pretty_print::pretty_write_declarations;
pub use pretty_print::Ctx as PPCtx;

View file

@ -19,18 +19,45 @@ pub struct Ctx<'a> {
pub fn pretty_print_declarations(c: &Ctx, declarations: &Declarations) -> String {
let f = Arena::new();
print_declarations_help(c, &f, declarations)
.1
.pretty(80)
.to_string()
}
pub fn pretty_write_declarations(
writer: &mut impl std::io::Write,
c: &Ctx,
declarations: &Declarations,
) -> std::io::Result<()> {
let f = Arena::new();
print_declarations_help(c, &f, declarations)
.1
.render(80, writer)
}
pub fn pretty_print_def(c: &Ctx, d: &Def) -> String {
let f = Arena::new();
def(c, &f, d).append(f.hardline()).1.pretty(80).to_string()
}
fn print_declarations_help<'a>(
c: &Ctx,
f: &'a Arena<'a>,
declarations: &'a Declarations,
) -> DocBuilder<'a, Arena<'a>> {
let mut defs = Vec::with_capacity(declarations.len());
for (index, tag) in declarations.iter_bottom_up() {
let symbol = declarations.symbols[index].value;
let body = &declarations.expressions[index];
let def = match tag {
DeclarationTag::Value => def_symbol_help(c, &f, symbol, &body.value),
DeclarationTag::Value => def_symbol_help(c, f, symbol, &body.value),
DeclarationTag::Function(f_index)
| DeclarationTag::Recursive(f_index)
| DeclarationTag::TailRecursive(f_index) => {
let function_def = &declarations.function_bodies[f_index.index()].value;
toplevel_function(c, &f, symbol, function_def, &body.value)
toplevel_function(c, f, symbol, function_def, &body.value)
}
DeclarationTag::Expectation => todo!(),
DeclarationTag::ExpectationFx => todo!(),
@ -45,14 +72,6 @@ pub fn pretty_print_declarations(c: &Ctx, declarations: &Declarations) -> String
}
f.intersperse(defs, f.hardline().append(f.hardline()))
.1
.pretty(80)
.to_string()
}
pub fn pretty_print_def(c: &Ctx, d: &Def) -> String {
let f = Arena::new();
def(c, &f, d).append(f.hardline()).1.pretty(80).to_string()
}
macro_rules! maybe_paren {

View file

@ -6,7 +6,7 @@ use roc_types::{subs::Variable, types::MemberImpl};
use crate::{
abilities::AbilitiesStore,
def::{Annotation, Declaration, Def},
def::{Annotation, Def},
expr::{
self, AnnotatedMark, ClosureData, Declarations, Expr, Field, OpaqueWrapFunctionData,
StructAccessorData,
@ -14,19 +14,71 @@ use crate::{
pattern::{DestructType, Pattern, RecordDestruct, TupleDestruct},
};
macro_rules! visit_list {
($visitor:ident, $walk:ident, $list:expr) => {
for elem in $list {
$visitor.$walk(elem)
pub enum DeclarationInfo<'a> {
Value {
loc_symbol: Loc<Symbol>,
loc_expr: &'a Loc<Expr>,
expr_var: Variable,
pattern: Pattern,
annotation: Option<&'a Annotation>,
},
Expectation {
loc_condition: &'a Loc<Expr>,
},
Function {
loc_symbol: Loc<Symbol>,
loc_body: &'a Loc<Expr>,
expr_var: Variable,
pattern: Pattern,
function: &'a Loc<expr::FunctionDef>,
},
Destructure {
loc_pattern: &'a Loc<Pattern>,
opt_pattern_var: Option<Variable>,
loc_expr: &'a Loc<Expr>,
expr_var: Variable,
annotation: Option<&'a Annotation>,
},
}
impl<'a> DeclarationInfo<'a> {
pub fn region(&self) -> Region {
use DeclarationInfo::*;
match self {
Value {
loc_symbol,
loc_expr,
..
} => Region::span_across(&loc_symbol.region, &loc_expr.region),
Expectation { loc_condition } => loc_condition.region,
Function {
loc_symbol,
function,
..
} => Region::span_across(&loc_symbol.region, &function.region),
Destructure {
loc_pattern,
loc_expr,
..
} => Region::span_across(&loc_pattern.region, &loc_expr.region),
}
}
fn var(&self) -> Variable {
match self {
DeclarationInfo::Value { expr_var, .. } => *expr_var,
DeclarationInfo::Expectation { .. } => Variable::BOOL,
DeclarationInfo::Function { expr_var, .. } => *expr_var,
DeclarationInfo::Destructure { expr_var, .. } => *expr_var,
}
}
};
}
pub fn walk_decls<V: Visitor>(visitor: &mut V, decls: &Declarations) {
use crate::expr::DeclarationTag::*;
for (index, tag) in decls.declarations.iter().enumerate() {
match tag {
let info = match tag {
Value => {
let loc_expr = &decls.expressions[index];
@ -40,22 +92,19 @@ pub fn walk_decls<V: Visitor>(visitor: &mut V, decls: &Declarations) {
},
None => Pattern::Identifier(loc_symbol.value),
};
visitor.visit_pattern(&pattern, loc_symbol.region, Some(expr_var));
visitor.visit_expr(&loc_expr.value, loc_expr.region, expr_var);
if let Some(annot) = &decls.annotations[index] {
visitor.visit_annotation(annot);
DeclarationInfo::Value {
loc_symbol,
loc_expr,
expr_var,
pattern,
annotation: decls.annotations[index].as_ref(),
}
}
Expectation => {
Expectation | ExpectationFx => {
let loc_condition = &decls.expressions[index];
visitor.visit_expr(&loc_condition.value, loc_condition.region, Variable::BOOL);
}
ExpectationFx => {
let loc_condition = &decls.expressions[index];
visitor.visit_expr(&loc_condition.value, loc_condition.region, Variable::BOOL);
DeclarationInfo::Expectation { loc_condition }
}
Function(function_index)
| Recursive(function_index)
@ -72,16 +121,16 @@ pub fn walk_decls<V: Visitor>(visitor: &mut V, decls: &Declarations) {
},
None => Pattern::Identifier(loc_symbol.value),
};
visitor.visit_pattern(&pattern, loc_symbol.region, Some(expr_var));
let function_def = &decls.function_bodies[function_index.index() as usize];
walk_closure_help(
visitor,
&function_def.value.arguments,
DeclarationInfo::Function {
loc_symbol,
loc_body,
function_def.value.return_type,
)
expr_var,
pattern,
function: function_def,
}
}
Destructure(destructure_index) => {
let destructure = &decls.destructs[destructure_index.index() as usize];
@ -90,51 +139,83 @@ pub fn walk_decls<V: Visitor>(visitor: &mut V, decls: &Declarations) {
let loc_expr = &decls.expressions[index];
let expr_var = decls.variables[index];
let opt_var = match loc_pattern.value {
let opt_pattern_var = match loc_pattern.value {
Pattern::Identifier(..) | Pattern::AbilityMemberSpecialization { .. } => {
Some(expr_var)
}
_ => loc_pattern.value.opt_var(),
};
visitor.visit_pattern(&loc_pattern.value, loc_pattern.region, opt_var);
visitor.visit_expr(&loc_expr.value, loc_expr.region, expr_var);
DeclarationInfo::Destructure {
loc_pattern,
opt_pattern_var,
loc_expr,
expr_var,
annotation: decls.annotations[index].as_ref(),
}
}
MutualRecursion { .. } => {
// The actual declarations involved in the mutual recursion will come next.
continue;
}
};
if let Some(annot) = &decls.annotations[index] {
visitor.visit_annotation(annot);
}
}
MutualRecursion { .. } => { /* ignore */ }
}
visitor.visit_decl(info);
}
}
fn walk_decl<V: Visitor>(visitor: &mut V, decl: &Declaration) {
match decl {
Declaration::Declare(def) => {
visitor.visit_def(def);
}
Declaration::DeclareRec(defs, _cycle_mark) => {
visit_list!(visitor, visit_def, defs)
}
fn walk_decl<V: Visitor>(visitor: &mut V, decl: DeclarationInfo<'_>) {
use DeclarationInfo::*;
Declaration::Expects(expects) => {
let it = expects.regions.iter().zip(expects.conditions.iter());
for (region, condition) in it {
visitor.visit_expr(condition, *region, Variable::BOOL);
match decl {
Value {
loc_symbol,
loc_expr,
expr_var,
pattern,
annotation,
} => {
visitor.visit_pattern(&pattern, loc_symbol.region, Some(expr_var));
visitor.visit_expr(&loc_expr.value, loc_expr.region, expr_var);
if let Some(annot) = annotation {
visitor.visit_annotation(annot);
}
}
Declaration::ExpectsFx(expects) => {
let it = expects.regions.iter().zip(expects.conditions.iter());
for (region, condition) in it {
visitor.visit_expr(condition, *region, Variable::BOOL);
}
}
Declaration::Builtin(def) => visitor.visit_def(def),
Declaration::InvalidCycle(_cycles) => {
// ignore
Expectation { loc_condition } => {
visitor.visit_expr(&loc_condition.value, loc_condition.region, Variable::BOOL);
}
Function {
loc_symbol,
loc_body,
expr_var,
pattern,
function,
} => {
visitor.visit_pattern(&pattern, loc_symbol.region, Some(expr_var));
walk_closure_help(
visitor,
&function.value.arguments,
loc_body,
function.value.return_type,
)
}
Destructure {
loc_pattern,
opt_pattern_var,
loc_expr,
expr_var,
annotation,
} => {
visitor.visit_pattern(&loc_pattern.value, loc_pattern.region, opt_pattern_var);
visitor.visit_expr(&loc_expr.value, loc_expr.region, expr_var);
if let Some(annot) = annotation {
visitor.visit_annotation(annot);
}
}
};
}
pub fn walk_def<V: Visitor>(visitor: &mut V, def: &Def) {
@ -449,7 +530,7 @@ pub trait Visitor: Sized {
walk_decls(self, decls);
}
fn visit_decl(&mut self, decl: &Declaration) {
fn visit_decl(&mut self, decl: DeclarationInfo<'_>) {
if self.should_visit(decl.region()) {
walk_decl(self, decl);
}
@ -582,14 +663,24 @@ pub fn find_type_at(region: Region, decls: &Declarations) -> Option<Variable> {
visitor.typ
}
#[derive(Debug)]
pub enum FoundSymbol {
/// Specialization(T, foo1) is the specialization of foo for T.
Specialization(Symbol, Symbol),
/// AbilityMember(Foo, foo) is the ability member foo of Foo.
AbilityMember(Symbol, Symbol),
/// Raw symbol, not specialized to anything.
Symbol(Symbol),
}
/// Given an ability Foo has foo : ..., returns (T, foo1) if the symbol at the given region is a
/// symbol foo1 that specializes foo for T. Otherwise if the symbol is foo but the specialization
/// is unknown, (Foo, foo) is returned. Otherwise [None] is returned.
pub fn find_ability_member_and_owning_type_at(
pub fn find_symbol_at(
region: Region,
decls: &Declarations,
abilities_store: &AbilitiesStore,
) -> Option<(Symbol, Symbol)> {
) -> Option<FoundSymbol> {
let mut visitor = Finder {
region,
found: None,
@ -601,7 +692,7 @@ pub fn find_ability_member_and_owning_type_at(
struct Finder<'a> {
region: Region,
abilities_store: &'a AbilitiesStore,
found: Option<(Symbol, Symbol)>,
found: Option<FoundSymbol>,
}
impl Visitor for Finder<'_> {
@ -611,16 +702,19 @@ pub fn find_ability_member_and_owning_type_at(
fn visit_pattern(&mut self, pattern: &Pattern, region: Region, _opt_var: Option<Variable>) {
if region == self.region {
if let Pattern::AbilityMemberSpecialization {
match pattern {
Pattern::AbilityMemberSpecialization {
ident: spec_symbol,
specializes: _,
} = pattern
{
} => {
debug_assert!(self.found.is_none());
let spec_type =
find_specialization_type_of_symbol(*spec_symbol, self.abilities_store)
.unwrap();
self.found = Some((spec_type, *spec_symbol))
self.found = Some(FoundSymbol::Specialization(spec_type, *spec_symbol))
}
Pattern::Identifier(symbol) => self.found = Some(FoundSymbol::Symbol(*symbol)),
_ => {}
}
}
@ -629,7 +723,8 @@ pub fn find_ability_member_and_owning_type_at(
fn visit_expr(&mut self, expr: &Expr, region: Region, var: Variable) {
if region == self.region {
if let &Expr::AbilityMember(member_symbol, specialization_id, _var) = expr {
match expr {
&Expr::AbilityMember(member_symbol, specialization_id, _var) => {
debug_assert!(self.found.is_none());
self.found = match specialization_id
.and_then(|id| self.abilities_store.get_resolved(id))
@ -640,7 +735,7 @@ pub fn find_ability_member_and_owning_type_at(
self.abilities_store,
)
.unwrap();
Some((spec_type, spec_symbol))
Some(FoundSymbol::Specialization(spec_type, spec_symbol))
}
None => {
let parent_ability = self
@ -648,11 +743,14 @@ pub fn find_ability_member_and_owning_type_at(
.member_def(member_symbol)
.unwrap()
.parent_ability;
Some((parent_ability, member_symbol))
Some(FoundSymbol::AbilityMember(parent_ability, member_symbol))
}
};
return;
}
Expr::Var(symbol, _var) => self.found = Some(FoundSymbol::Symbol(*symbol)),
_ => {}
}
}
walk_expr(self, expr, var);
@ -705,3 +803,75 @@ pub fn symbols_introduced_from_pattern(
}
}
}
pub enum FoundDeclaration<'a> {
Decl(DeclarationInfo<'a>),
Def(&'a Def),
}
impl<'a> FoundDeclaration<'a> {
pub fn region(&self) -> Region {
match self {
FoundDeclaration::Decl(decl) => decl.region(),
FoundDeclaration::Def(def) => def.region(),
}
}
pub fn var(&self) -> Variable {
match self {
FoundDeclaration::Decl(decl) => decl.var(),
FoundDeclaration::Def(def) => def.expr_var,
}
}
}
/// Finds the declaration of `symbol`.
pub fn find_declaration(symbol: Symbol, decls: &'_ Declarations) -> Option<FoundDeclaration<'_>> {
let mut visitor = Finder {
symbol,
found: None,
};
visitor.visit_decls(decls);
return visitor.found;
struct Finder<'a> {
symbol: Symbol,
found: Option<FoundDeclaration<'a>>,
}
impl Visitor for Finder<'_> {
fn should_visit(&mut self, _region: Region) -> bool {
true
}
fn visit_decl(&mut self, decl: DeclarationInfo<'_>) {
match decl {
DeclarationInfo::Value { loc_symbol, .. }
| DeclarationInfo::Function { loc_symbol, .. }
if loc_symbol.value == self.symbol =>
{
self.found = Some(FoundDeclaration::Decl(unsafe { std::mem::transmute(decl) }));
}
DeclarationInfo::Destructure { .. } => {
// TODO destructures
walk_decl(self, decl);
}
_ => {
walk_decl(self, decl);
}
}
}
fn visit_def(&mut self, def: &Def) {
if matches!(def.loc_pattern.value, Pattern::Identifier(s) if s == self.symbol) {
debug_assert!(self.found.is_none());
// Safety: the def can't escape the passed in `decls`, and the visitor does not
// synthesize defs.
self.found = Some(FoundDeclaration::Def(unsafe { std::mem::transmute(def) }));
return;
}
walk_def(self, def)
}
}
}

View file

@ -15,3 +15,4 @@ hashbrown.workspace = true
im-rc.workspace = true
im.workspace = true
wyhash.workspace = true
smallvec.workspace = true

View file

@ -6,6 +6,7 @@
pub mod all;
mod reference_matrix;
mod small_string_interner;
mod small_vec;
pub mod soa;
mod vec_map;
mod vec_set;
@ -13,5 +14,6 @@ mod vec_set;
pub use all::{default_hasher, BumpMap, ImEntry, ImMap, ImSet, MutMap, MutSet, SendMap};
pub use reference_matrix::{ReferenceMatrix, Sccs, TopologicalSort};
pub use small_string_interner::SmallStringInterner;
pub use small_vec::SmallVec;
pub use vec_map::VecMap;
pub use vec_set::VecSet;

View file

@ -0,0 +1,61 @@
use std::fmt::Debug;
use smallvec::SmallVec as Vec;
#[derive(Default)]
pub struct SmallVec<T, const N: usize>(Vec<[T; N]>);
impl<T, const N: usize> SmallVec<T, N> {
pub const fn new() -> Self {
Self(Vec::new_const())
}
pub fn push(&mut self, value: T) {
self.0.push(value)
}
pub fn pop(&mut self) -> Option<T> {
self.0.pop()
}
}
impl<T, const N: usize> std::ops::Deref for SmallVec<T, N> {
type Target = [T];
fn deref(&self) -> &Self::Target {
self.0.deref()
}
}
impl<T, const N: usize> Debug for SmallVec<T, N>
where
T: Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl<T, const N: usize> Clone for SmallVec<T, N>
where
T: Clone,
{
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<T, const N: usize> IntoIterator for SmallVec<T, N> {
type Item = T;
type IntoIter = <Vec<[T; N]> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<T, const N: usize> FromIterator<T> for SmallVec<T, N> {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
Self(Vec::from_iter(iter))
}
}

View file

@ -1501,6 +1501,7 @@ trait Backend<'a> {
self.set_last_seen(*sym, stmt);
match expr {
Expr::Literal(_) => {}
Expr::NullPointer => {}
Expr::Call(call) => self.scan_ast_call(call, stmt),

View file

@ -200,6 +200,7 @@ pub enum LlvmBackendMode {
BinaryDev,
/// Creates a test wrapper around the main roc function to catch and report panics.
/// Provides a testing implementation of primitives (roc_alloc, roc_panic, etc)
BinaryGlue,
GenTest,
WasmGenTest,
CliTest,
@ -210,6 +211,7 @@ impl LlvmBackendMode {
match self {
LlvmBackendMode::Binary => true,
LlvmBackendMode::BinaryDev => true,
LlvmBackendMode::BinaryGlue => false,
LlvmBackendMode::GenTest => false,
LlvmBackendMode::WasmGenTest => true,
LlvmBackendMode::CliTest => false,
@ -221,6 +223,7 @@ impl LlvmBackendMode {
match self {
LlvmBackendMode::Binary => false,
LlvmBackendMode::BinaryDev => false,
LlvmBackendMode::BinaryGlue => false,
LlvmBackendMode::GenTest => true,
LlvmBackendMode::WasmGenTest => true,
LlvmBackendMode::CliTest => true,
@ -231,6 +234,7 @@ impl LlvmBackendMode {
match self {
LlvmBackendMode::Binary => false,
LlvmBackendMode::BinaryDev => true,
LlvmBackendMode::BinaryGlue => false,
LlvmBackendMode::GenTest => false,
LlvmBackendMode::WasmGenTest => false,
LlvmBackendMode::CliTest => true,
@ -1078,6 +1082,12 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
match expr {
Literal(literal) => build_exp_literal(env, layout_interner, parent, layout, literal),
NullPointer => {
let basic_type = basic_type_from_layout(env, layout_interner, layout);
debug_assert!(basic_type.is_pointer_type());
basic_type.into_pointer_type().const_zero().into()
}
Call(call) => build_exp_call(
env,
@ -4116,7 +4126,7 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
)
}
LlvmBackendMode::Binary | LlvmBackendMode::BinaryDev => {}
LlvmBackendMode::Binary | LlvmBackendMode::BinaryDev | LlvmBackendMode::BinaryGlue => {}
}
// a generic version that writes the result into a passed *u8 pointer
@ -4169,7 +4179,7 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
roc_call_result_type(env, roc_function.get_type().get_return_type().unwrap()).into()
}
LlvmBackendMode::Binary | LlvmBackendMode::BinaryDev => {
LlvmBackendMode::Binary | LlvmBackendMode::BinaryDev | LlvmBackendMode::BinaryGlue => {
basic_type_from_layout(env, layout_interner, return_layout)
}
};
@ -5250,7 +5260,7 @@ fn build_proc<'a, 'ctx, 'env>(
GenTest | WasmGenTest | CliTest => {
/* no host, or exposing types is not supported */
}
Binary | BinaryDev => {
Binary | BinaryDev | BinaryGlue => {
for (alias_name, hels) in aliases.iter() {
let ident_string = proc.name.name().as_str(&env.interns);
let fn_name: String = format!("{}_{}", ident_string, hels.id.0);

View file

@ -41,6 +41,8 @@ pub fn add_default_roc_externs(env: &Env<'_, '_, '_>) {
// The type of this function (but not the implementation) should have
// already been defined by the builtins, which rely on it.
let fn_val = module.get_function("roc_alloc").unwrap();
fn_val.set_linkage(Linkage::Internal);
let mut params = fn_val.get_param_iter();
let size_arg = params.next().unwrap();
let _alignment_arg = params.next().unwrap();
@ -135,6 +137,8 @@ pub fn add_default_roc_externs(env: &Env<'_, '_, '_>) {
// The type of this function (but not the implementation) should have
// already been defined by the builtins, which rely on it.
let fn_val = module.get_function("roc_dealloc").unwrap();
fn_val.set_linkage(Linkage::Internal);
let mut params = fn_val.get_param_iter();
let ptr_arg = params.next().unwrap();
let _alignment_arg = params.next().unwrap();
@ -197,6 +201,11 @@ pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) {
let mut params = fn_val.get_param_iter();
let roc_str_arg = params.next().unwrap();
// normally, roc_panic is marked as external so it can be provided by the host. But when we
// define it here in LLVM IR, we never want it to be linked by the host (that would
// overwrite which implementation is used.
fn_val.set_linkage(Linkage::Internal);
let tag_id_arg = params.next().unwrap();
debug_assert!(params.next().is_none());

View file

@ -1041,6 +1041,8 @@ impl<'a, 'r> WasmBackend<'a, 'r> {
match expr {
Expr::Literal(lit) => self.expr_literal(lit, storage),
Expr::NullPointer => self.expr_null_pointer(),
Expr::Call(roc_mono::ir::Call {
call_type,
arguments,
@ -1232,6 +1234,10 @@ impl<'a, 'r> WasmBackend<'a, 'r> {
elements_addr
}
fn expr_null_pointer(&mut self) {
self.code_builder.i32_const(0);
}
/*******************************************************************
* Call expressions
*******************************************************************/

View file

@ -992,7 +992,6 @@ struct State<'a> {
/// From now on, these will be used by multiple threads; time to make an Arc<Mutex<_>>!
pub arc_modules: Arc<Mutex<PackageModuleIds<'a>>>,
pub arc_shorthands: Arc<Mutex<MutMap<&'a str, ShorthandPath>>>,
#[allow(unused)]
pub derived_module: SharedDerivedModule,
pub ident_ids_by_module: SharedIdentIdsByModule,

View file

@ -1,7 +1,79 @@
use self::Associativity::*;
use self::BinOp::*;
use std::cmp::Ordering;
use std::fmt;
const PRECEDENCES: [(BinOp, u8); 20] = [
(Caret, 7),
(Star, 6),
(Slash, 6),
(DoubleSlash, 5),
(Percent, 5),
(Plus, 4),
(Minus, 4),
(Pizza, 3),
(Equals, 2),
(NotEquals, 2),
(LessThan, 1),
(GreaterThan, 1),
(LessThanOrEq, 1),
(GreaterThanOrEq, 1),
(And, 0),
(Or, 0),
// These should never come up
(Assignment, 255),
(IsAliasType, 255),
(IsOpaqueType, 255),
(Backpassing, 255),
];
const ASSOCIATIVITIES: [(BinOp, Associativity); 20] = [
(Caret, RightAssociative),
(Star, LeftAssociative),
(Slash, LeftAssociative),
(DoubleSlash, LeftAssociative),
(Percent, LeftAssociative),
(Plus, LeftAssociative),
(Minus, LeftAssociative),
(Pizza, LeftAssociative),
(Equals, NonAssociative),
(NotEquals, NonAssociative),
(LessThan, NonAssociative),
(GreaterThan, NonAssociative),
(LessThanOrEq, NonAssociative),
(GreaterThanOrEq, NonAssociative),
(And, RightAssociative),
(Or, RightAssociative),
// These should never come up
(Assignment, LeftAssociative),
(IsAliasType, LeftAssociative),
(IsOpaqueType, LeftAssociative),
(Backpassing, LeftAssociative),
];
const DISPLAY_STRINGS: [(BinOp, &str); 20] = [
(Caret, "^"),
(Star, "*"),
(Slash, "/"),
(DoubleSlash, "//"),
(Percent, "%"),
(Plus, "+"),
(Minus, "-"),
(Pizza, "|>"),
(Equals, "=="),
(NotEquals, "!="),
(LessThan, "<"),
(GreaterThan, ">"),
(LessThanOrEq, "<="),
(GreaterThanOrEq, ">="),
(And, "&&"),
(Or, "||"),
(Assignment, "="),
(IsAliasType, ":"),
(IsOpaqueType, ":="),
(Backpassing, "<-"),
];
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CalledVia {
/// Calling with space, e.g. (foo bar)
@ -36,6 +108,7 @@ pub enum BinOp {
Percent,
Plus,
Minus,
Pizza,
Equals,
NotEquals,
LessThan,
@ -44,7 +117,6 @@ pub enum BinOp {
GreaterThanOrEq,
And,
Or,
Pizza,
Assignment,
IsAliasType,
IsOpaqueType,
@ -93,29 +165,27 @@ pub enum Associativity {
impl BinOp {
pub fn associativity(self) -> Associativity {
use self::Associativity::*;
// The compiler should never pass any of these to this function!
debug_assert_ne!(self, Assignment);
debug_assert_ne!(self, IsAliasType);
debug_assert_ne!(self, IsOpaqueType);
debug_assert_ne!(self, Backpassing);
match self {
Pizza | Star | Slash | DoubleSlash | Percent | Plus | Minus => LeftAssociative,
And | Or | Caret => RightAssociative,
Equals | NotEquals | LessThan | GreaterThan | LessThanOrEq | GreaterThanOrEq => {
NonAssociative
}
Assignment | IsAliasType | IsOpaqueType | Backpassing => unreachable!(),
}
const ASSOCIATIVITY_TABLE: [Associativity; 20] = generate_associativity_table();
ASSOCIATIVITY_TABLE[self as usize]
}
fn precedence(self) -> u8 {
match self {
Caret => 7,
Star | Slash | DoubleSlash | Percent => 6,
Plus | Minus => 5,
Equals | NotEquals | LessThan | GreaterThan | LessThanOrEq | GreaterThanOrEq => 4,
And => 3,
Or => 2,
Pizza => 1,
Assignment | IsAliasType | IsOpaqueType | Backpassing => unreachable!(),
}
// The compiler should never pass any of these to this function!
debug_assert_ne!(self, Assignment);
debug_assert_ne!(self, IsAliasType);
debug_assert_ne!(self, IsOpaqueType);
debug_assert_ne!(self, Backpassing);
const PRECEDENCE_TABLE: [u8; 20] = generate_precedence_table();
PRECEDENCE_TABLE[self as usize]
}
}
@ -133,29 +203,75 @@ impl Ord for BinOp {
impl std::fmt::Display for BinOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let as_str = match self {
Caret => "^",
Star => "*",
Slash => "/",
DoubleSlash => "//",
Percent => "%",
Plus => "+",
Minus => "-",
Equals => "==",
NotEquals => "!=",
LessThan => "<",
GreaterThan => ">",
LessThanOrEq => "<=",
GreaterThanOrEq => ">=",
And => "&&",
Or => "||",
Pizza => "|>",
Assignment => "=",
IsAliasType => ":",
IsOpaqueType => ":=",
Backpassing => "<-",
};
debug_assert_ne!(*self, Assignment);
debug_assert_ne!(*self, IsAliasType);
debug_assert_ne!(*self, IsOpaqueType);
debug_assert_ne!(*self, Backpassing);
write!(f, "{}", as_str)
const DISPLAY_TABLE: [&str; 20] = generate_display_table();
write!(f, "{}", DISPLAY_TABLE[*self as usize])
}
}
const fn generate_precedence_table() -> [u8; 20] {
let mut table = [0u8; 20];
let mut i = 0;
while i < PRECEDENCES.len() {
table[(PRECEDENCES[i].0) as usize] = PRECEDENCES[i].1;
i += 1;
}
table
}
const fn generate_associativity_table() -> [Associativity; 20] {
let mut table = [NonAssociative; 20];
let mut i = 0;
while i < ASSOCIATIVITIES.len() {
table[(ASSOCIATIVITIES[i].0) as usize] = ASSOCIATIVITIES[i].1;
i += 1;
}
table
}
const fn generate_display_table() -> [&'static str; 20] {
let mut table = [""; 20];
let mut i = 0;
while i < DISPLAY_STRINGS.len() {
table[(DISPLAY_STRINGS[i].0) as usize] = DISPLAY_STRINGS[i].1;
i += 1;
}
table
}
#[cfg(test)]
mod tests {
use super::{BinOp, ASSOCIATIVITIES, DISPLAY_STRINGS, PRECEDENCES};
fn index_is_binop_u8(iter: impl Iterator<Item = BinOp>, table_name: &'static str) {
for (index, op) in iter.enumerate() {
assert_eq!(op as usize, index, "{op} was found at index {index} in {table_name}, but it should have been at index {} instead.", op as usize);
}
}
#[test]
fn indices_are_correct_in_precedences() {
index_is_binop_u8(PRECEDENCES.iter().map(|(op, _)| *op), "PRECEDENCES")
}
#[test]
fn indices_are_correct_in_associativities() {
index_is_binop_u8(ASSOCIATIVITIES.iter().map(|(op, _)| *op), "ASSOCIATIVITIES")
}
#[test]
fn indices_are_correct_in_display_string() {
index_is_binop_u8(DISPLAY_STRINGS.iter().map(|(op, _)| *op), "DISPLAY_STRINGS")
}
}

View file

@ -1328,6 +1328,7 @@ define_builtins! {
55 STR_GRAPHEMES: "graphemes"
56 STR_IS_VALID_SCALAR: "isValidScalar"
57 STR_RELEASE_EXCESS_CAPACITY: "releaseExcessCapacity"
58 STR_WALK_UTF8: "walkUtf8"
}
6 LIST: "List" => {
0 LIST_LIST: "List" exposed_apply_type=true // the List.List type alias

View file

@ -761,7 +761,7 @@ impl<'a> BorrowInfState<'a> {
Call(call) => self.collect_call(interner, param_map, z, call),
Literal(_) | RuntimeErrorFunction(_) => {}
Literal(_) | NullPointer | RuntimeErrorFunction(_) => {}
StructAtIndex { structure: x, .. } => {
// if the structure (record/tag/array) is owned, the extracted value is

View file

@ -390,6 +390,7 @@ impl<'a, 'r> Ctx<'a, 'r> {
fn check_expr(&mut self, e: &Expr<'a>) -> Option<InLayout<'a>> {
match e {
Expr::Literal(_) => None,
Expr::NullPointer => None,
Expr::Call(call) => self.check_call(call),
&Expr::Tag {
tag_layout,

View file

@ -213,7 +213,7 @@ pub fn occurring_variables_expr(expr: &Expr<'_>, result: &mut MutSet<Symbol>) {
result.insert(*symbol);
}
EmptyArray | RuntimeErrorFunction(_) | Literal(_) => {}
EmptyArray | RuntimeErrorFunction(_) | Literal(_) | NullPointer => {}
GetTagId {
structure: symbol, ..
@ -946,7 +946,7 @@ impl<'a, 'i> Context<'a, 'i> {
self.arena.alloc(Stmt::Let(z, v, l, b))
}
EmptyArray | Literal(_) | Reset { .. } | RuntimeErrorFunction(_) => {
EmptyArray | Literal(_) | Reset { .. } | NullPointer | RuntimeErrorFunction(_) => {
// EmptyArray is always stack-allocated function pointers are persistent
self.arena.alloc(Stmt::Let(z, v, l, b))
}

View file

@ -1878,6 +1878,7 @@ pub enum Expr<'a> {
arguments: &'a [Symbol],
},
Struct(&'a [Symbol]),
NullPointer,
StructAtIndex {
index: u64,
@ -2016,6 +2017,7 @@ impl<'a> Expr<'a> {
.append(alloc.space())
.append(alloc.intersperse(it, " "))
}
NullPointer => alloc.text("NullPointer"),
Reuse {
symbol,
tag_id,
@ -4963,9 +4965,6 @@ pub fn with_hole<'a>(
_ => arena.alloc([record_layout]),
};
debug_assert_eq!(field_layouts.len(), symbols.len());
debug_assert_eq!(fields.len(), symbols.len());
if symbols.len() == 1 {
// TODO we can probably special-case this more, skippiing the generation of
// UpdateExisting
@ -7465,6 +7464,8 @@ fn substitute_in_expr<'a>(
}
}
NullPointer => None,
Reuse { .. } | Reset { .. } => unreachable!("reset/reuse have not been introduced yet"),
Struct(args) => {

View file

@ -5,7 +5,7 @@ use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_collections::all::{default_hasher, FnvMap, MutMap};
use roc_collections::VecSet;
use roc_collections::{SmallVec, VecSet};
use roc_error_macros::{internal_error, todo_abilities};
use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::{Interns, Symbol};
@ -52,22 +52,22 @@ roc_error_macros::assert_sizeof_default!(LambdaSet, 5 * 8);
type LayoutResult<'a> = Result<InLayout<'a>, LayoutProblem>;
type RawFunctionLayoutResult<'a> = Result<RawFunctionLayout<'a>, LayoutProblem>;
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone)]
struct CacheMeta {
/// Does this cache entry include a recursive structure? If so, what's the recursion variable
/// of that structure?
has_recursive_structure: OptVariable,
recursive_structures: SmallVec<Variable, 2>,
}
impl CacheMeta {
#[inline(always)]
fn to_criteria(self) -> CacheCriteria {
fn into_criteria(self) -> CacheCriteria {
let CacheMeta {
has_recursive_structure,
recursive_structures,
} = self;
CacheCriteria {
has_naked_recursion_pointer: false,
has_recursive_structure,
recursive_structures,
}
}
}
@ -208,7 +208,7 @@ impl<'a> LayoutCache<'a> {
// TODO: it's possible that after unification, roots in earlier cache layers changed...
// how often does that happen?
if let Some(result) = layer.0.get(&root) {
return Some(*result);
return Some(result.clone());
}
}
None
@ -342,24 +342,24 @@ pub struct CacheSnapshot {
layer: usize,
}
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Debug)]
struct CacheCriteria {
/// Whether there is a naked recursion pointer in this layout, that doesn't pass through a
/// recursive structure.
has_naked_recursion_pointer: bool,
/// Whether this layout contains a recursive structure. If `Some`, contains the variable of the
/// recursion variable of that structure.
has_recursive_structure: OptVariable,
/// Recursive structures this layout contains, if any.
// Typically at most 1 recursive structure is contained, but there may be more.
recursive_structures: SmallVec<Variable, 2>,
}
const CACHEABLE: CacheCriteria = CacheCriteria {
has_naked_recursion_pointer: false,
has_recursive_structure: OptVariable::NONE,
recursive_structures: SmallVec::new(),
};
const NAKED_RECURSION_PTR: CacheCriteria = CacheCriteria {
has_naked_recursion_pointer: true,
has_recursive_structure: OptVariable::NONE,
recursive_structures: SmallVec::new(),
};
impl CacheCriteria {
@ -371,25 +371,32 @@ impl CacheCriteria {
/// Makes `self` cacheable iff self and other are cacheable.
#[inline(always)]
fn and(&mut self, other: Self) {
fn and(&mut self, other: Self, subs: &Subs) {
self.has_naked_recursion_pointer =
self.has_naked_recursion_pointer || other.has_naked_recursion_pointer;
// TODO: can these ever conflict?
self.has_recursive_structure = self
.has_recursive_structure
.or(other.has_recursive_structure);
for &other_rec in other.recursive_structures.iter() {
if self
.recursive_structures
.iter()
.any(|rec| subs.equivalent_without_compacting(*rec, other_rec))
{
continue;
}
self.recursive_structures.push(other_rec);
}
}
#[inline(always)]
fn pass_through_recursive_union(&mut self, recursion_var: Variable) {
self.has_naked_recursion_pointer = false;
self.has_recursive_structure = OptVariable::some(recursion_var);
self.recursive_structures.push(recursion_var);
}
#[inline(always)]
fn cache_metadata(&self) -> CacheMeta {
CacheMeta {
has_recursive_structure: self.has_recursive_structure,
recursive_structures: self.recursive_structures.clone(),
}
}
}
@ -404,9 +411,9 @@ impl<T> Cacheable<T> {
}
#[inline(always)]
fn decompose(self, and_with: &mut CacheCriteria) -> T {
fn decompose(self, and_with: &mut CacheCriteria, subs: &Subs) -> T {
let Self(value, criteria) = self;
and_with.and(criteria);
and_with.and(criteria, subs);
value
}
@ -438,10 +445,10 @@ fn cacheable<T>(v: T) -> Cacheable<T> {
/// If the layout is not an error, the cache policy is `and`ed with `total_criteria`, and the layout
/// is passed back.
macro_rules! cached {
($expr:expr, $total_criteria:expr) => {
($expr:expr, $total_criteria:expr, $subs:expr) => {
match $expr {
Cacheable(Ok(v), criteria) => {
$total_criteria.and(criteria);
$total_criteria.and(criteria, $subs);
v
}
Cacheable(Err(v), criteria) => return Cacheable(Err(v), criteria),
@ -583,17 +590,18 @@ impl<'a> RawFunctionLayout<'a> {
for index in args.into_iter() {
let arg_var = env.subs[index];
let layout = cached!(Layout::from_var(env, arg_var), cache_criteria);
let layout = cached!(Layout::from_var(env, arg_var), cache_criteria, env.subs);
fn_args.push(layout);
}
let ret = cached!(Layout::from_var(env, ret_var), cache_criteria);
let ret = cached!(Layout::from_var(env, ret_var), cache_criteria, env.subs);
let fn_args = fn_args.into_bump_slice();
let lambda_set = cached!(
LambdaSet::from_var(env, args, closure_var, ret_var),
cache_criteria
cache_criteria,
env.subs
);
Cacheable(Ok(Self::Function(fn_args, lambda_set, ret)), cache_criteria)
@ -617,7 +625,7 @@ impl<'a> RawFunctionLayout<'a> {
}
_ => {
let mut criteria = CACHEABLE;
let layout = cached!(layout_from_flat_type(env, flat_type), criteria);
let layout = cached!(layout_from_flat_type(env, flat_type), criteria, env.subs);
Cacheable(Ok(Self::ZeroArgumentThunk(layout)), criteria)
}
}
@ -1803,11 +1811,11 @@ impl<'a> LambdaSet<'a> {
for index in args.into_iter() {
let arg_var = env.subs[index];
let layout = cached!(Layout::from_var(env, arg_var), cache_criteria);
let layout = cached!(Layout::from_var(env, arg_var), cache_criteria, env.subs);
fn_args.push(layout);
}
let ret = cached!(Layout::from_var(env, ret_var), cache_criteria);
let ret = cached!(Layout::from_var(env, ret_var), cache_criteria, env.subs);
let fn_args = env.arena.alloc(fn_args.into_bump_slice());
@ -1837,7 +1845,7 @@ impl<'a> LambdaSet<'a> {
// We determine cacheability of the lambda set based on the runtime
// representation, so here the criteria doesn't matter.
let mut criteria = CACHEABLE;
let arg = cached!(Layout::from_var(env, *var), criteria);
let arg = cached!(Layout::from_var(env, *var), criteria, env.subs);
arguments.push(arg);
set_captures_have_naked_rec_ptr =
set_captures_have_naked_rec_ptr || criteria.has_naked_recursion_pointer;
@ -1899,7 +1907,7 @@ impl<'a> LambdaSet<'a> {
set_with_variables,
opt_recursion_var.into_variable(),
);
cache_criteria.and(criteria);
cache_criteria.and(criteria, env.subs);
let needs_recursive_fixup = NeedsRecursionPointerFixup(
opt_recursion_var.is_some() && set_captures_have_naked_rec_ptr,
@ -2213,11 +2221,11 @@ impl<'a, 'b> Env<'a, 'b> {
}
#[inline(always)]
fn can_reuse_cached(&self, var: Variable, cache_metadata: CacheMeta) -> bool {
fn can_reuse_cached(&self, var: Variable, cache_metadata: &CacheMeta) -> bool {
let CacheMeta {
has_recursive_structure,
recursive_structures,
} = cache_metadata;
if let Some(recursive_structure) = has_recursive_structure.into_variable() {
for &recursive_structure in recursive_structures.iter() {
if self.is_seen(recursive_structure) {
// If the cached entry references a recursive structure that we're in the process
// of visiting currently, we can't use the cached entry, and instead must
@ -2255,9 +2263,9 @@ macro_rules! cached_or_impl {
// cache HIT
inc_stat!($self.cache.$stats, hits);
if $self.can_reuse_cached($var, metadata) {
if $self.can_reuse_cached($var, &metadata) {
// Happy path - the cached layout can be reused, return it immediately.
return Cacheable(result, metadata.to_criteria());
return Cacheable(result, metadata.into_criteria());
} else {
// Although we have a cached layout, we cannot readily reuse it at this time. We'll
// need to recompute the layout, as done below.
@ -3139,7 +3147,8 @@ fn layout_from_flat_type<'a>(
let mut criteria = CACHEABLE;
let inner_var = args[0];
let inner_layout = cached!(Layout::from_var(env, inner_var), criteria);
let inner_layout =
cached!(Layout::from_var(env, inner_var), criteria, env.subs);
let boxed_layout = env.cache.put_in(Layout::Boxed(inner_layout));
Cacheable(Ok(boxed_layout), criteria)
@ -3163,7 +3172,8 @@ fn layout_from_flat_type<'a>(
let lambda_set = cached!(
LambdaSet::from_var(env, args, closure_var, ret_var),
criteria
criteria,
env.subs
);
let lambda_set = lambda_set.full_layout;
@ -3187,7 +3197,8 @@ fn layout_from_flat_type<'a>(
RecordField::Required(field_var)
| RecordField::Demanded(field_var)
| RecordField::RigidRequired(field_var) => {
let field_layout = cached!(Layout::from_var(env, field_var), criteria);
let field_layout =
cached!(Layout::from_var(env, field_var), criteria, env.subs);
sortables.push((label, field_layout));
}
RecordField::Optional(_) | RecordField::RigidOptional(_) => {
@ -3239,7 +3250,7 @@ fn layout_from_flat_type<'a>(
};
for (index, elem) in it {
let elem_layout = cached!(Layout::from_var(env, elem), criteria);
let elem_layout = cached!(Layout::from_var(env, elem), criteria, env.subs);
sortables.push((index, elem_layout));
}
@ -3652,7 +3663,7 @@ where
for &var in arguments {
let Cacheable(result, criteria) = Layout::from_var(env, var);
cache_criteria.and(criteria);
cache_criteria.and(criteria, env.subs);
match result {
Ok(layout) => {
layouts.push(layout);
@ -3708,7 +3719,7 @@ where
for &var in arguments {
let Cacheable(result, criteria) = Layout::from_var(env, var);
cache_criteria.and(criteria);
cache_criteria.and(criteria, env.subs);
match result {
Ok(layout) => {
has_any_arguments = true;
@ -3830,7 +3841,7 @@ where
for var in arguments {
let Cacheable(result, criteria) = Layout::from_var(env, var);
cache_criteria.and(criteria);
cache_criteria.and(criteria, env.subs);
match result {
Ok(layout) => {
layouts.push(layout);
@ -3904,7 +3915,7 @@ where
for var in arguments {
let Cacheable(result, criteria) = Layout::from_var(env, var);
cache_criteria.and(criteria);
cache_criteria.and(criteria, env.subs);
match result {
Ok(in_layout) => {
has_any_arguments = true;
@ -4077,7 +4088,8 @@ where
let mut criteria = CACHEABLE;
let variant = union_sorted_non_recursive_tags_help(env, tags_vec).decompose(&mut criteria);
let variant =
union_sorted_non_recursive_tags_help(env, tags_vec).decompose(&mut criteria, env.subs);
let result = match variant {
Never => Layout::VOID,
@ -4188,10 +4200,11 @@ where
// The naked pointer will get fixed-up to loopback to the union below when we
// intern the union.
tag_layout.push(Layout::NAKED_RECURSIVE_PTR);
criteria.and(NAKED_RECURSION_PTR, env.subs);
continue;
}
let payload = cached!(Layout::from_var(env, var), criteria);
let payload = cached!(Layout::from_var(env, var), criteria, env.subs);
tag_layout.push(payload);
}
@ -4228,12 +4241,17 @@ where
} else {
UnionLayout::Recursive(tag_layouts.into_bump_slice())
};
criteria.pass_through_recursive_union(rec_var);
let union_layout = env
.cache
let union_layout = if criteria.has_naked_recursion_pointer {
env.cache
.interner
.insert_recursive(env.arena, Layout::Union(union_layout));
.insert_recursive(env.arena, Layout::Union(union_layout))
} else {
// There are no naked recursion pointers, so we can insert the layout as-is.
env.cache.interner.insert(Layout::Union(union_layout))
};
criteria.pass_through_recursive_union(rec_var);
Cacheable(Ok(union_layout), criteria)
}
@ -4360,7 +4378,7 @@ pub(crate) fn list_layout_from_elem<'a>(
} else {
// NOTE: cannot re-use Content, because it may be recursive
// then some state is not correctly kept, we have to go through from_var
cached!(Layout::from_var(env, element_var), criteria)
cached!(Layout::from_var(env, element_var), criteria, env.subs)
};
let list_layout = env

View file

@ -319,6 +319,7 @@ fn insert_reset<'a>(
}
Literal(_)
| NullPointer
| Call(_)
| Tag { .. }
| Struct(_)
@ -839,6 +840,7 @@ fn has_live_var<'a>(jp_live_vars: &JPLiveVarMap, stmt: &'a Stmt<'a>, needle: Sym
fn has_live_var_expr<'a>(expr: &'a Expr<'a>, needle: Symbol) -> bool {
match expr {
Expr::Literal(_) => false,
Expr::NullPointer => false,
Expr::Call(call) => has_live_var_call(call, needle),
Expr::Array { elems: fields, .. } => {
for element in fields.iter() {

View file

@ -199,6 +199,10 @@ impl LineColumnRegion {
}
}
pub fn includes(&self, lc: LineColumn) -> bool {
self.contains(&Self::from_pos(lc))
}
pub const fn from_pos(pos: LineColumn) -> Self {
Self {
start: pos,

View file

@ -35,6 +35,7 @@ roc_problem = { path = "../problem" }
roc_reporting = { path = "../../reporting" }
roc_solve = { path = "../solve" }
roc_target = { path = "../roc_target" }
test_solve_helpers = { path = "../test_solve_helpers" }
bumpalo.workspace = true
indoc.workspace = true
@ -43,3 +44,4 @@ lazy_static.workspace = true
pretty_assertions.workspace = true
regex.workspace = true
tempfile.workspace = true
libtest-mimic.workspace = true

View file

@ -1,41 +0,0 @@
extern crate bumpalo;
/// Used in the with_larger_debug_stack() function, for tests that otherwise
/// run out of stack space in debug builds (but don't in --release builds)
#[allow(dead_code)]
const EXPANDED_STACK_SIZE: usize = 8 * 1024 * 1024;
/// Without this, some tests pass in `cargo test --release` but fail without
/// the --release flag because they run out of stack space. This increases
/// stack size for debug builds only, while leaving the stack space at the default
/// amount for release builds.
#[allow(dead_code)]
#[cfg(debug_assertions)]
pub fn with_larger_debug_stack<F>(run_test: F)
where
F: FnOnce(),
F: Send,
F: 'static,
{
std::thread::Builder::new()
.stack_size(EXPANDED_STACK_SIZE)
.spawn(run_test)
.expect("Error while spawning expanded dev stack size thread")
.join()
.expect("Error while joining expanded dev stack size thread")
}
/// In --release builds, don't increase the stack size. Run the test normally.
/// This way, we find out if any of our tests are blowing the stack even after
/// optimizations in release builds.
#[allow(dead_code)]
#[cfg(not(debug_assertions))]
#[inline(always)]
pub fn with_larger_debug_stack<F>(run_test: F)
where
F: FnOnce(),
F: Send,
F: 'static,
{
run_test()
}

File diff suppressed because it is too large Load diff

View file

@ -4386,3 +4386,77 @@ fn recursive_lambda_set_resolved_only_upon_specialization() {
u64
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn layout_cache_structure_with_multiple_recursive_structures() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
Chain : [
End,
Link Chain,
]
LinkedList : [Nil, Cons { first : Chain, rest : LinkedList }]
main =
base : LinkedList
base = Nil
walker : LinkedList, Chain -> LinkedList
walker = \rest, first -> Cons { first, rest }
list : List Chain
list = []
r = List.walk list base walker
if r == base then 11u8 else 22u8
"#
),
11,
u8
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn reset_recursive_type_wraps_in_named_type() {
assert_evals_to!(
indoc!(
r###"
app "test" provides [main] to "./platform"
main : Str
main =
newList = mapLinkedList (Cons 1 (Cons 2 (Cons 3 Nil))) (\x -> x + 1)
printLinkedList newList Num.toStr
LinkedList a : [Cons a (LinkedList a), Nil]
mapLinkedList : LinkedList a, (a -> b) -> LinkedList b
mapLinkedList = \linkedList, f -> when linkedList is
Nil -> Nil
Cons x xs ->
s = if Bool.true then "true" else "false"
expect s == "true"
Cons (f x) (mapLinkedList xs f)
printLinkedList : LinkedList a, (a -> Str) -> Str
printLinkedList = \linkedList, f ->
when linkedList is
Nil -> "Nil"
Cons x xs ->
strX = f x
strXs = printLinkedList xs f
"Cons \(strX) (\(strXs))"
"###
),
RocStr::from("Cons 2 (Cons 3 (Cons 4 (Nil)))"),
RocStr
);
}

View file

@ -1822,6 +1822,33 @@ fn str_split_overlapping_substring_2() {
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
fn str_walk_utf8() {
#[cfg(not(feature = "gen-llvm-wasm"))]
assert_evals_to!(
// Reverse the bytes
indoc!(
r#"
Str.walkUtf8 "abcd" [] (\list, byte -> List.prepend list byte)
"#
),
RocList::from_slice(&[b'd', b'c', b'b', b'a']),
RocList<u8>
);
#[cfg(feature = "gen-llvm-wasm")]
assert_evals_to!(
indoc!(
r#"
Str.walkUtf8WithIndex "abcd" [] (\list, byte, index -> List.append list (Pair index byte))
"#
),
RocList::from_slice(&[(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd')]),
RocList<(u32, char)>
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
fn str_walk_utf8_with_index() {

View file

@ -253,6 +253,7 @@ fn create_llvm_module<'a>(
let (main_fn_name, main_fn) = match config.mode {
LlvmBackendMode::Binary => unreachable!(),
LlvmBackendMode::BinaryDev => unreachable!(),
LlvmBackendMode::BinaryGlue => unreachable!(),
LlvmBackendMode::CliTest => unreachable!(),
LlvmBackendMode::WasmGenTest => roc_gen_llvm::llvm::build::build_wasm_test_wrapper(
&env,

View file

@ -47,8 +47,8 @@ procedure Num.22 (#Attr.2, #Attr.3):
ret Num.276;
procedure Str.3 (#Attr.2, #Attr.3):
let Str.268 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.268;
let Str.300 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.300;
procedure Test.1 (Test.5):
ret Test.5;

View file

@ -319,31 +319,31 @@ procedure Num.24 (#Attr.2, #Attr.3):
ret Num.303;
procedure Str.12 (#Attr.2):
let Str.283 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.283;
let Str.315 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.315;
procedure Str.48 (#Attr.2, #Attr.3, #Attr.4):
let Str.275 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4;
ret Str.275;
let Str.307 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4;
ret Str.307;
procedure Str.9 (Str.77):
let Str.273 : U64 = 0i64;
let Str.274 : U64 = CallByName List.6 Str.77;
let Str.78 : {U64, Str, Int1, U8} = CallByName Str.48 Str.77 Str.273 Str.274;
let Str.270 : Int1 = StructAtIndex 2 Str.78;
if Str.270 then
let Str.272 : Str = StructAtIndex 1 Str.78;
inc Str.272;
dec Str.78;
let Str.271 : [C {U64, U8}, C Str] = TagId(1) Str.272;
ret Str.271;
procedure Str.9 (Str.79):
let Str.305 : U64 = 0i64;
let Str.306 : U64 = CallByName List.6 Str.79;
let Str.80 : {U64, Str, Int1, U8} = CallByName Str.48 Str.79 Str.305 Str.306;
let Str.302 : Int1 = StructAtIndex 2 Str.80;
if Str.302 then
let Str.304 : Str = StructAtIndex 1 Str.80;
inc Str.304;
dec Str.80;
let Str.303 : [C {U64, U8}, C Str] = TagId(1) Str.304;
ret Str.303;
else
let Str.268 : U8 = StructAtIndex 3 Str.78;
let Str.269 : U64 = StructAtIndex 0 Str.78;
dec Str.78;
let Str.267 : {U64, U8} = Struct {Str.269, Str.268};
let Str.266 : [C {U64, U8}, C Str] = TagId(0) Str.267;
ret Str.266;
let Str.300 : U8 = StructAtIndex 3 Str.80;
let Str.301 : U64 = StructAtIndex 0 Str.80;
dec Str.80;
let Str.299 : {U64, U8} = Struct {Str.301, Str.300};
let Str.298 : [C {U64, U8}, C Str] = TagId(0) Str.299;
ret Str.298;
procedure Test.0 ():
let Test.12 : Str = "bar";

View file

@ -196,31 +196,31 @@ procedure Num.24 (#Attr.2, #Attr.3):
ret Num.284;
procedure Str.12 (#Attr.2):
let Str.281 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.281;
let Str.313 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.313;
procedure Str.48 (#Attr.2, #Attr.3, #Attr.4):
let Str.275 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4;
ret Str.275;
let Str.307 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4;
ret Str.307;
procedure Str.9 (Str.77):
let Str.273 : U64 = 0i64;
let Str.274 : U64 = CallByName List.6 Str.77;
let Str.78 : {U64, Str, Int1, U8} = CallByName Str.48 Str.77 Str.273 Str.274;
let Str.270 : Int1 = StructAtIndex 2 Str.78;
if Str.270 then
let Str.272 : Str = StructAtIndex 1 Str.78;
inc Str.272;
dec Str.78;
let Str.271 : [C {U64, U8}, C Str] = TagId(1) Str.272;
ret Str.271;
procedure Str.9 (Str.79):
let Str.305 : U64 = 0i64;
let Str.306 : U64 = CallByName List.6 Str.79;
let Str.80 : {U64, Str, Int1, U8} = CallByName Str.48 Str.79 Str.305 Str.306;
let Str.302 : Int1 = StructAtIndex 2 Str.80;
if Str.302 then
let Str.304 : Str = StructAtIndex 1 Str.80;
inc Str.304;
dec Str.80;
let Str.303 : [C {U64, U8}, C Str] = TagId(1) Str.304;
ret Str.303;
else
let Str.268 : U8 = StructAtIndex 3 Str.78;
let Str.269 : U64 = StructAtIndex 0 Str.78;
dec Str.78;
let Str.267 : {U64, U8} = Struct {Str.269, Str.268};
let Str.266 : [C {U64, U8}, C Str] = TagId(0) Str.267;
ret Str.266;
let Str.300 : U8 = StructAtIndex 3 Str.80;
let Str.301 : U64 = StructAtIndex 0 Str.80;
dec Str.80;
let Str.299 : {U64, U8} = Struct {Str.301, Str.300};
let Str.298 : [C {U64, U8}, C Str] = TagId(0) Str.299;
ret Str.298;
procedure Test.0 ():
let Test.11 : Str = "foo";

View file

@ -204,31 +204,31 @@ procedure Num.24 (#Attr.2, #Attr.3):
ret Num.284;
procedure Str.12 (#Attr.2):
let Str.281 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.281;
let Str.313 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.313;
procedure Str.48 (#Attr.2, #Attr.3, #Attr.4):
let Str.275 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4;
ret Str.275;
let Str.307 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4;
ret Str.307;
procedure Str.9 (Str.77):
let Str.273 : U64 = 0i64;
let Str.274 : U64 = CallByName List.6 Str.77;
let Str.78 : {U64, Str, Int1, U8} = CallByName Str.48 Str.77 Str.273 Str.274;
let Str.270 : Int1 = StructAtIndex 2 Str.78;
if Str.270 then
let Str.272 : Str = StructAtIndex 1 Str.78;
inc Str.272;
dec Str.78;
let Str.271 : [C {U64, U8}, C Str] = TagId(1) Str.272;
ret Str.271;
procedure Str.9 (Str.79):
let Str.305 : U64 = 0i64;
let Str.306 : U64 = CallByName List.6 Str.79;
let Str.80 : {U64, Str, Int1, U8} = CallByName Str.48 Str.79 Str.305 Str.306;
let Str.302 : Int1 = StructAtIndex 2 Str.80;
if Str.302 then
let Str.304 : Str = StructAtIndex 1 Str.80;
inc Str.304;
dec Str.80;
let Str.303 : [C {U64, U8}, C Str] = TagId(1) Str.304;
ret Str.303;
else
let Str.268 : U8 = StructAtIndex 3 Str.78;
let Str.269 : U64 = StructAtIndex 0 Str.78;
dec Str.78;
let Str.267 : {U64, U8} = Struct {Str.269, Str.268};
let Str.266 : [C {U64, U8}, C Str] = TagId(0) Str.267;
ret Str.266;
let Str.300 : U8 = StructAtIndex 3 Str.80;
let Str.301 : U64 = StructAtIndex 0 Str.80;
dec Str.80;
let Str.299 : {U64, U8} = Struct {Str.301, Str.300};
let Str.298 : [C {U64, U8}, C Str] = TagId(0) Str.299;
ret Str.298;
procedure Test.0 ():
let Test.11 : Str = "foo";

View file

@ -57,31 +57,31 @@ procedure Num.127 (#Attr.2):
ret Num.276;
procedure Str.12 (#Attr.2):
let Str.280 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.280;
let Str.312 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.312;
procedure Str.48 (#Attr.2, #Attr.3, #Attr.4):
let Str.275 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4;
ret Str.275;
let Str.307 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4;
ret Str.307;
procedure Str.9 (Str.77):
let Str.273 : U64 = 0i64;
let Str.274 : U64 = CallByName List.6 Str.77;
let Str.78 : {U64, Str, Int1, U8} = CallByName Str.48 Str.77 Str.273 Str.274;
let Str.270 : Int1 = StructAtIndex 2 Str.78;
if Str.270 then
let Str.272 : Str = StructAtIndex 1 Str.78;
inc Str.272;
dec Str.78;
let Str.271 : [C {U64, U8}, C Str] = TagId(1) Str.272;
ret Str.271;
procedure Str.9 (Str.79):
let Str.305 : U64 = 0i64;
let Str.306 : U64 = CallByName List.6 Str.79;
let Str.80 : {U64, Str, Int1, U8} = CallByName Str.48 Str.79 Str.305 Str.306;
let Str.302 : Int1 = StructAtIndex 2 Str.80;
if Str.302 then
let Str.304 : Str = StructAtIndex 1 Str.80;
inc Str.304;
dec Str.80;
let Str.303 : [C {U64, U8}, C Str] = TagId(1) Str.304;
ret Str.303;
else
let Str.268 : U8 = StructAtIndex 3 Str.78;
let Str.269 : U64 = StructAtIndex 0 Str.78;
dec Str.78;
let Str.267 : {U64, U8} = Struct {Str.269, Str.268};
let Str.266 : [C {U64, U8}, C Str] = TagId(0) Str.267;
ret Str.266;
let Str.300 : U8 = StructAtIndex 3 Str.80;
let Str.301 : U64 = StructAtIndex 0 Str.80;
dec Str.80;
let Str.299 : {U64, U8} = Struct {Str.301, Str.300};
let Str.298 : [C {U64, U8}, C Str] = TagId(0) Str.299;
ret Str.298;
procedure Test.0 ():
let Test.9 : Str = "abc";

View file

@ -205,31 +205,31 @@ procedure Num.24 (#Attr.2, #Attr.3):
ret Num.286;
procedure Str.12 (#Attr.2):
let Str.281 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.281;
let Str.313 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.313;
procedure Str.48 (#Attr.2, #Attr.3, #Attr.4):
let Str.275 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4;
ret Str.275;
let Str.307 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4;
ret Str.307;
procedure Str.9 (Str.77):
let Str.273 : U64 = 0i64;
let Str.274 : U64 = CallByName List.6 Str.77;
let Str.78 : {U64, Str, Int1, U8} = CallByName Str.48 Str.77 Str.273 Str.274;
let Str.270 : Int1 = StructAtIndex 2 Str.78;
if Str.270 then
let Str.272 : Str = StructAtIndex 1 Str.78;
inc Str.272;
dec Str.78;
let Str.271 : [C {U64, U8}, C Str] = TagId(1) Str.272;
ret Str.271;
procedure Str.9 (Str.79):
let Str.305 : U64 = 0i64;
let Str.306 : U64 = CallByName List.6 Str.79;
let Str.80 : {U64, Str, Int1, U8} = CallByName Str.48 Str.79 Str.305 Str.306;
let Str.302 : Int1 = StructAtIndex 2 Str.80;
if Str.302 then
let Str.304 : Str = StructAtIndex 1 Str.80;
inc Str.304;
dec Str.80;
let Str.303 : [C {U64, U8}, C Str] = TagId(1) Str.304;
ret Str.303;
else
let Str.268 : U8 = StructAtIndex 3 Str.78;
let Str.269 : U64 = StructAtIndex 0 Str.78;
dec Str.78;
let Str.267 : {U64, U8} = Struct {Str.269, Str.268};
let Str.266 : [C {U64, U8}, C Str] = TagId(0) Str.267;
ret Str.266;
let Str.300 : U8 = StructAtIndex 3 Str.80;
let Str.301 : U64 = StructAtIndex 0 Str.80;
dec Str.80;
let Str.299 : {U64, U8} = Struct {Str.301, Str.300};
let Str.298 : [C {U64, U8}, C Str] = TagId(0) Str.299;
ret Str.298;
procedure Test.0 ():
let Test.12 : Str = "foo";

View file

@ -211,31 +211,31 @@ procedure Num.24 (#Attr.2, #Attr.3):
ret Num.286;
procedure Str.12 (#Attr.2):
let Str.281 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.281;
let Str.313 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.313;
procedure Str.48 (#Attr.2, #Attr.3, #Attr.4):
let Str.275 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4;
ret Str.275;
let Str.307 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4;
ret Str.307;
procedure Str.9 (Str.77):
let Str.273 : U64 = 0i64;
let Str.274 : U64 = CallByName List.6 Str.77;
let Str.78 : {U64, Str, Int1, U8} = CallByName Str.48 Str.77 Str.273 Str.274;
let Str.270 : Int1 = StructAtIndex 2 Str.78;
if Str.270 then
let Str.272 : Str = StructAtIndex 1 Str.78;
inc Str.272;
dec Str.78;
let Str.271 : [C {U64, U8}, C Str] = TagId(1) Str.272;
ret Str.271;
procedure Str.9 (Str.79):
let Str.305 : U64 = 0i64;
let Str.306 : U64 = CallByName List.6 Str.79;
let Str.80 : {U64, Str, Int1, U8} = CallByName Str.48 Str.79 Str.305 Str.306;
let Str.302 : Int1 = StructAtIndex 2 Str.80;
if Str.302 then
let Str.304 : Str = StructAtIndex 1 Str.80;
inc Str.304;
dec Str.80;
let Str.303 : [C {U64, U8}, C Str] = TagId(1) Str.304;
ret Str.303;
else
let Str.268 : U8 = StructAtIndex 3 Str.78;
let Str.269 : U64 = StructAtIndex 0 Str.78;
dec Str.78;
let Str.267 : {U64, U8} = Struct {Str.269, Str.268};
let Str.266 : [C {U64, U8}, C Str] = TagId(0) Str.267;
ret Str.266;
let Str.300 : U8 = StructAtIndex 3 Str.80;
let Str.301 : U64 = StructAtIndex 0 Str.80;
dec Str.80;
let Str.299 : {U64, U8} = Struct {Str.301, Str.300};
let Str.298 : [C {U64, U8}, C Str] = TagId(0) Str.299;
ret Str.298;
procedure Test.0 ():
let Test.13 : Str = "foo";

View file

@ -45,27 +45,27 @@ procedure Num.22 (#Attr.2, #Attr.3):
let Num.275 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
ret Num.275;
procedure Str.27 (Str.97):
let Str.266 : [C Int1, C I64] = CallByName Str.70 Str.97;
ret Str.266;
procedure Str.27 (Str.99):
let Str.298 : [C Int1, C I64] = CallByName Str.72 Str.99;
ret Str.298;
procedure Str.47 (#Attr.2):
let Str.274 : {I64, U8} = lowlevel StrToNum #Attr.2;
ret Str.274;
let Str.306 : {I64, U8} = lowlevel StrToNum #Attr.2;
ret Str.306;
procedure Str.70 (Str.232):
let Str.233 : {I64, U8} = CallByName Str.47 Str.232;
let Str.272 : U8 = StructAtIndex 1 Str.233;
let Str.273 : U8 = 0i64;
let Str.269 : Int1 = CallByName Bool.11 Str.272 Str.273;
if Str.269 then
let Str.271 : I64 = StructAtIndex 0 Str.233;
let Str.270 : [C Int1, C I64] = TagId(1) Str.271;
ret Str.270;
procedure Str.72 (Str.244):
let Str.245 : {I64, U8} = CallByName Str.47 Str.244;
let Str.304 : U8 = StructAtIndex 1 Str.245;
let Str.305 : U8 = 0i64;
let Str.301 : Int1 = CallByName Bool.11 Str.304 Str.305;
if Str.301 then
let Str.303 : I64 = StructAtIndex 0 Str.245;
let Str.302 : [C Int1, C I64] = TagId(1) Str.303;
ret Str.302;
else
let Str.268 : Int1 = false;
let Str.267 : [C Int1, C I64] = TagId(0) Str.268;
ret Str.267;
let Str.300 : Int1 = false;
let Str.299 : [C Int1, C I64] = TagId(0) Str.300;
ret Str.299;
procedure Test.0 ():
let Test.3 : Int1 = CallByName Bool.2;

View file

@ -759,27 +759,27 @@ procedure Num.77 (#Attr.2, #Attr.3):
ret Num.321;
procedure Str.48 (#Attr.2, #Attr.3, #Attr.4):
let Str.275 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4;
ret Str.275;
let Str.307 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4;
ret Str.307;
procedure Str.9 (Str.77):
let Str.273 : U64 = 0i64;
let Str.274 : U64 = CallByName List.6 Str.77;
let Str.78 : {U64, Str, Int1, U8} = CallByName Str.48 Str.77 Str.273 Str.274;
let Str.270 : Int1 = StructAtIndex 2 Str.78;
if Str.270 then
let Str.272 : Str = StructAtIndex 1 Str.78;
inc Str.272;
dec Str.78;
let Str.271 : [C {U64, U8}, C Str] = TagId(1) Str.272;
ret Str.271;
procedure Str.9 (Str.79):
let Str.305 : U64 = 0i64;
let Str.306 : U64 = CallByName List.6 Str.79;
let Str.80 : {U64, Str, Int1, U8} = CallByName Str.48 Str.79 Str.305 Str.306;
let Str.302 : Int1 = StructAtIndex 2 Str.80;
if Str.302 then
let Str.304 : Str = StructAtIndex 1 Str.80;
inc Str.304;
dec Str.80;
let Str.303 : [C {U64, U8}, C Str] = TagId(1) Str.304;
ret Str.303;
else
let Str.268 : U8 = StructAtIndex 3 Str.78;
let Str.269 : U64 = StructAtIndex 0 Str.78;
dec Str.78;
let Str.267 : {U64, U8} = Struct {Str.269, Str.268};
let Str.266 : [C {U64, U8}, C Str] = TagId(0) Str.267;
ret Str.266;
let Str.300 : U8 = StructAtIndex 3 Str.80;
let Str.301 : U64 = StructAtIndex 0 Str.80;
dec Str.80;
let Str.299 : {U64, U8} = Struct {Str.301, Str.300};
let Str.298 : [C {U64, U8}, C Str] = TagId(0) Str.299;
ret Str.298;
procedure Test.3 ():
let Test.0 : List U8 = Array [82i64, 111i64, 99i64];

View file

@ -0,0 +1,13 @@
procedure Test.1 (Test.2):
dec Test.2;
let Test.7 : Str = "ux";
let Test.8 : Str = "uy";
let Test.6 : {Str, Str} = Struct {Test.7, Test.8};
ret Test.6;
procedure Test.0 ():
let Test.10 : Str = "x";
let Test.11 : Str = "y";
let Test.9 : {Str, Str} = Struct {Test.10, Test.11};
let Test.3 : {Str, Str} = CallByName Test.1 Test.9;
ret Test.3;

View file

@ -729,53 +729,53 @@ procedure Num.77 (#Attr.2, #Attr.3):
ret Num.321;
procedure Str.12 (#Attr.2):
let Str.275 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.275;
let Str.307 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.307;
procedure Str.27 (Str.97):
let Str.266 : [C {}, C I64] = CallByName Str.70 Str.97;
ret Str.266;
procedure Str.27 (Str.99):
let Str.298 : [C {}, C I64] = CallByName Str.72 Str.99;
ret Str.298;
procedure Str.47 (#Attr.2):
let Str.274 : {I64, U8} = lowlevel StrToNum #Attr.2;
ret Str.274;
let Str.306 : {I64, U8} = lowlevel StrToNum #Attr.2;
ret Str.306;
procedure Str.48 (#Attr.2, #Attr.3, #Attr.4):
let Str.289 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4;
ret Str.289;
let Str.321 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4;
ret Str.321;
procedure Str.70 (Str.232):
let Str.233 : {I64, U8} = CallByName Str.47 Str.232;
let Str.272 : U8 = StructAtIndex 1 Str.233;
let Str.273 : U8 = 0i64;
let Str.269 : Int1 = CallByName Bool.11 Str.272 Str.273;
if Str.269 then
let Str.271 : I64 = StructAtIndex 0 Str.233;
let Str.270 : [C {}, C I64] = TagId(1) Str.271;
ret Str.270;
procedure Str.72 (Str.244):
let Str.245 : {I64, U8} = CallByName Str.47 Str.244;
let Str.304 : U8 = StructAtIndex 1 Str.245;
let Str.305 : U8 = 0i64;
let Str.301 : Int1 = CallByName Bool.11 Str.304 Str.305;
if Str.301 then
let Str.303 : I64 = StructAtIndex 0 Str.245;
let Str.302 : [C {}, C I64] = TagId(1) Str.303;
ret Str.302;
else
let Str.268 : {} = Struct {};
let Str.267 : [C {}, C I64] = TagId(0) Str.268;
ret Str.267;
let Str.300 : {} = Struct {};
let Str.299 : [C {}, C I64] = TagId(0) Str.300;
ret Str.299;
procedure Str.9 (Str.77):
let Str.287 : U64 = 0i64;
let Str.288 : U64 = CallByName List.6 Str.77;
let Str.78 : {U64, Str, Int1, U8} = CallByName Str.48 Str.77 Str.287 Str.288;
let Str.284 : Int1 = StructAtIndex 2 Str.78;
if Str.284 then
let Str.286 : Str = StructAtIndex 1 Str.78;
inc Str.286;
dec Str.78;
let Str.285 : [C {U64, U8}, C Str] = TagId(1) Str.286;
ret Str.285;
procedure Str.9 (Str.79):
let Str.319 : U64 = 0i64;
let Str.320 : U64 = CallByName List.6 Str.79;
let Str.80 : {U64, Str, Int1, U8} = CallByName Str.48 Str.79 Str.319 Str.320;
let Str.316 : Int1 = StructAtIndex 2 Str.80;
if Str.316 then
let Str.318 : Str = StructAtIndex 1 Str.80;
inc Str.318;
dec Str.80;
let Str.317 : [C {U64, U8}, C Str] = TagId(1) Str.318;
ret Str.317;
else
let Str.282 : U8 = StructAtIndex 3 Str.78;
let Str.283 : U64 = StructAtIndex 0 Str.78;
dec Str.78;
let Str.281 : {U64, U8} = Struct {Str.283, Str.282};
let Str.280 : [C {U64, U8}, C Str] = TagId(0) Str.281;
ret Str.280;
let Str.314 : U8 = StructAtIndex 3 Str.80;
let Str.315 : U64 = StructAtIndex 0 Str.80;
dec Str.80;
let Str.313 : {U64, U8} = Struct {Str.315, Str.314};
let Str.312 : [C {U64, U8}, C Str] = TagId(0) Str.313;
ret Str.312;
procedure Test.0 ():
let Test.37 : Str = "-1234";

View file

@ -0,0 +1,56 @@
procedure List.139 (List.140, List.141, List.138):
let List.513 : [<rnu><null>, C {[<rnu>C *self, <null>], *self}] = CallByName Test.7 List.140 List.141;
ret List.513;
procedure List.18 (List.136, List.137, List.138):
let List.494 : [<rnu><null>, C {[<rnu>C *self, <null>], *self}] = CallByName List.92 List.136 List.137 List.138;
ret List.494;
procedure List.6 (#Attr.2):
let List.511 : U64 = lowlevel ListLen #Attr.2;
ret List.511;
procedure List.66 (#Attr.2, #Attr.3):
let List.510 : [<rnu>C *self, <null>] = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.510;
procedure List.80 (List.517, List.518, List.519, List.520, List.521):
joinpoint List.500 List.433 List.434 List.435 List.436 List.437:
let List.502 : Int1 = CallByName Num.22 List.436 List.437;
if List.502 then
let List.509 : [<rnu>C *self, <null>] = CallByName List.66 List.433 List.436;
let List.503 : [<rnu><null>, C {[<rnu>C *self, <null>], *self}] = CallByName List.139 List.434 List.509 List.435;
let List.506 : U64 = 1i64;
let List.505 : U64 = CallByName Num.19 List.436 List.506;
jump List.500 List.433 List.503 List.435 List.505 List.437;
else
ret List.434;
in
jump List.500 List.517 List.518 List.519 List.520 List.521;
procedure List.92 (List.430, List.431, List.432):
let List.498 : U64 = 0i64;
let List.499 : U64 = CallByName List.6 List.430;
let List.497 : [<rnu><null>, C {[<rnu>C *self, <null>], *self}] = CallByName List.80 List.430 List.431 List.432 List.498 List.499;
ret List.497;
procedure Num.19 (#Attr.2, #Attr.3):
let Num.275 : U64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.275;
procedure Num.22 (#Attr.2, #Attr.3):
let Num.276 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
ret Num.276;
procedure Test.7 (Test.11, Test.12):
let Test.17 : {[<rnu>C *self, <null>], [<rnu><null>, C {[<rnu>C *self, <null>], *self}]} = Struct {Test.12, Test.11};
let Test.16 : [<rnu><null>, C {[<rnu>C *self, <null>], *self}] = TagId(0) Test.17;
ret Test.16;
procedure Test.0 ():
let Test.6 : [<rnu><null>, C {[<rnu>C *self, <null>], *self}] = TagId(1) ;
let Test.8 : List [<rnu>C *self, <null>] = Array [];
let Test.15 : {} = Struct {};
let Test.9 : [<rnu><null>, C {[<rnu>C *self, <null>], *self}] = CallByName List.18 Test.8 Test.6 Test.15;
dec Test.8;
ret Test.9;

View file

@ -27,12 +27,12 @@ procedure Num.22 (#Attr.2, #Attr.3):
ret Num.275;
procedure Str.16 (#Attr.2, #Attr.3):
let Str.266 : Str = lowlevel StrRepeat #Attr.2 #Attr.3;
ret Str.266;
let Str.298 : Str = lowlevel StrRepeat #Attr.2 #Attr.3;
ret Str.298;
procedure Str.3 (#Attr.2, #Attr.3):
let Str.267 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.267;
let Str.299 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.299;
procedure Test.1 ():
let Test.21 : Str = "lllllllllllllllllllllooooooooooong";

View file

@ -28,8 +28,8 @@ procedure Num.22 (#Attr.2, #Attr.3):
ret Num.275;
procedure Str.3 (#Attr.2, #Attr.3):
let Str.267 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.267;
let Str.299 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.299;
procedure Test.1 ():
let Test.21 : Str = "lllllllllllllllllllllooooooooooong";

View file

@ -1,6 +1,6 @@
procedure Str.3 (#Attr.2, #Attr.3):
let Str.267 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.267;
let Str.299 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.299;
procedure Test.2 (Test.4):
let Test.16 : U8 = GetTagId Test.4;

View file

@ -3,8 +3,8 @@ procedure Bool.11 (#Attr.2, #Attr.3):
ret Bool.23;
procedure Str.3 (#Attr.2, #Attr.3):
let Str.267 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.267;
let Str.299 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.299;
procedure Test.2 (Test.7):
let Test.24 : Str = ".trace(\"";

View file

@ -3,8 +3,8 @@ procedure Num.20 (#Attr.2, #Attr.3):
ret Num.275;
procedure Str.3 (#Attr.2, #Attr.3):
let Str.268 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.268;
let Str.300 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.300;
procedure Test.11 (Test.29, #Attr.12):
let Test.10 : {} = UnionAtIndex (Id 0) (Index 0) #Attr.12;

View file

@ -190,8 +190,8 @@ procedure Num.24 (#Attr.2, #Attr.3):
ret Num.286;
procedure Str.12 (#Attr.2):
let Str.267 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.267;
let Str.299 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.299;
procedure Test.2 (Test.10):
let Test.15 : {Str, Str} = CallByName Encode.23 Test.10;

View file

@ -330,8 +330,8 @@ procedure Num.24 (#Attr.2, #Attr.3):
ret Num.305;
procedure Str.12 (#Attr.2):
let Str.268 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.268;
let Str.300 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.300;
procedure Test.2 (Test.11):
let Test.18 : {{}, {}} = CallByName Encode.23 Test.11;

View file

@ -2845,3 +2845,47 @@ fn compose_recursive_lambda_set_productive_nullable_wrapped() {
"#
)
}
#[mono_test]
fn issue_4759() {
indoc!(
r#"
app "test" provides [main] to "./platform"
main =
update { a : { x : "x", y: "y" } }
update = \state -> { state & a : { x : "ux", y: "uy" } }
"#
)
}
#[mono_test]
fn layout_cache_structure_with_multiple_recursive_structures() {
indoc!(
r#"
app "test" provides [main] to "./platform"
Chain : [
End,
Link Chain,
]
LinkedList : [Nil, Cons { first : Chain, rest : LinkedList }]
main =
base : LinkedList
base = Nil
walker : LinkedList, Chain -> LinkedList
walker = \rest, first -> Cons { first, rest }
list : List Chain
list = []
r = List.walk list base walker
r
"#
)
}

View file

@ -0,0 +1,31 @@
[package]
name = "test_solve_helpers"
description = "Utilities for testing the solver. This should eventually made into the roc_solve crate, but currently, solve tests have multiple harnesses."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies]
roc_can = { path = "../can" }
roc_derive = { path = "../derive" }
roc_load = { path = "../load" }
roc_module = { path = "../module" }
roc_packaging = { path = "../../packaging" }
roc_problem = { path = "../problem" }
roc_region = { path = "../region" }
roc_reporting = { path = "../../reporting" }
roc_solve = { path = "../solve" }
roc_late_solve = { path = "../late_solve" }
roc_solve_problem = { path = "../solve_problem" }
roc_target = { path = "../roc_target" }
roc_types = { path = "../types" }
bumpalo.workspace = true
indoc.workspace = true
insta.workspace = true
lazy_static.workspace = true
pretty_assertions.workspace = true
regex.workspace = true
tempfile.workspace = true

View file

@ -0,0 +1,599 @@
use std::{error::Error, io, path::PathBuf};
use bumpalo::Bump;
use lazy_static::lazy_static;
use regex::Regex;
use roc_can::{
abilities::AbilitiesStore,
expr::Declarations,
module::ExposedByModule,
traverse::{find_declaration, find_symbol_at, find_type_at, FoundSymbol},
};
use roc_derive::SharedDerivedModule;
use roc_late_solve::AbilitiesView;
use roc_load::LoadedModule;
use roc_module::symbol::{Interns, ModuleId};
use roc_packaging::cache::RocCacheDir;
use roc_problem::can::Problem;
use roc_region::all::{LineColumn, LineColumnRegion, LineInfo, Region};
use roc_reporting::report::{can_problem, type_problem, RocDocAllocator};
use roc_solve_problem::TypeError;
use roc_types::{
pretty_print::{name_and_print_var, DebugPrint},
subs::{Subs, Variable},
};
fn promote_expr_to_module(src: &str) -> String {
let mut buffer = String::from(indoc::indoc!(
r#"
app "test"
imports []
provides [main] to "./platform"
main =
"#
));
for line in src.lines() {
// indent the body!
buffer.push_str(" ");
buffer.push_str(line);
buffer.push('\n');
}
buffer
}
pub fn run_load_and_infer(
src: &str,
no_promote: bool,
) -> Result<(LoadedModule, String), std::io::Error> {
use tempfile::tempdir;
let arena = &Bump::new();
let module_src;
let temp;
if src.starts_with("app") || no_promote {
// this is already a module
module_src = src;
} else {
// this is an expression, promote it to a module
temp = promote_expr_to_module(src);
module_src = &temp;
}
let loaded = {
let dir = tempdir()?;
let filename = PathBuf::from("Test.roc");
let file_path = dir.path().join(filename);
let result = roc_load::load_and_typecheck_str(
arena,
file_path,
module_src,
dir.path().to_path_buf(),
roc_target::TargetInfo::default_x86_64(),
roc_reporting::report::RenderTarget::Generic,
RocCacheDir::Disallowed,
roc_reporting::report::DEFAULT_PALETTE,
);
dir.close()?;
result
};
let loaded = loaded.expect("failed to load module");
Ok((loaded, module_src.to_string()))
}
pub fn format_problems(
src: &str,
home: ModuleId,
interns: &Interns,
can_problems: Vec<Problem>,
type_problems: Vec<TypeError>,
) -> (String, String) {
let filename = PathBuf::from("test.roc");
let src_lines: Vec<&str> = src.split('\n').collect();
let lines = LineInfo::new(src);
let alloc = RocDocAllocator::new(&src_lines, home, interns);
let mut can_reports = vec![];
let mut type_reports = vec![];
for problem in can_problems {
let report = can_problem(&alloc, &lines, filename.clone(), problem.clone());
can_reports.push(report.pretty(&alloc));
}
for problem in type_problems {
if let Some(report) = type_problem(&alloc, &lines, filename.clone(), problem.clone()) {
type_reports.push(report.pretty(&alloc));
}
}
let mut can_reports_buf = String::new();
let mut type_reports_buf = String::new();
use roc_reporting::report::CiWrite;
alloc
.stack(can_reports)
.1
.render_raw(70, &mut CiWrite::new(&mut can_reports_buf))
.unwrap();
alloc
.stack(type_reports)
.1
.render_raw(70, &mut CiWrite::new(&mut type_reports_buf))
.unwrap();
(can_reports_buf, type_reports_buf)
}
lazy_static! {
/// Queries of the form
///
/// ```text
/// ^^^{(directive),*}?
///
/// directive :=
/// -\d+ # shift the query left by N columns
/// inst # instantiate the given generic instance
/// ```
static ref RE_TYPE_QUERY: Regex =
Regex::new(r#"(?P<where>\^+)(?:\{(?P<directives>.*?)\})?"#).unwrap();
static ref RE_DIRECTIVE : Regex =
Regex::new(r#"(?:-(?P<sub>\d+))|(?P<inst>inst)"#).unwrap();
}
/// Markers of nested query lines, that should be skipped.
pub const MUTLILINE_MARKER: &str = "";
#[derive(Debug, Clone)]
pub struct TypeQuery {
query_region: Region,
/// If true, the query is under a function call, which should be instantiated with the present
/// value and have its nested queries printed.
instantiate: bool,
source: String,
comment_column: u32,
source_line_column: LineColumn,
}
/// Parse inference queries in a Roc program.
/// See [RE_TYPE_QUERY].
fn parse_queries(src: &str, line_info: &LineInfo) -> Vec<TypeQuery> {
let mut queries = vec![];
let mut consecutive_query_lines = 0;
for (i, line) in src.lines().enumerate() {
// If this is a query line, it should start with a comment somewhere before the query
// lines.
let comment_column = match line.find('#') {
Some(i) => i as _,
None => {
consecutive_query_lines = 0;
continue;
}
};
let mut queries_on_line = RE_TYPE_QUERY.captures_iter(line).into_iter().peekable();
if queries_on_line.peek().is_none() || line.contains(MUTLILINE_MARKER) {
consecutive_query_lines = 0;
continue;
} else {
consecutive_query_lines += 1;
}
for capture in queries_on_line {
let source = capture
.get(0)
.expect("full capture must always exist")
.as_str()
.to_string();
let wher = capture.name("where").unwrap();
let mut subtract_col = 0u32;
let mut instantiate = false;
if let Some(directives) = capture.name("directives") {
for directive in directives.as_str().split(',') {
let directive = RE_DIRECTIVE
.captures(directive)
.unwrap_or_else(|| panic!("directive {directive} must match RE_DIRECTIVE"));
if let Some(sub) = directive.name("sub") {
subtract_col += sub.as_str().parse::<u32>().expect("must be a number");
}
if directive.name("inst").is_some() {
instantiate = true;
}
}
}
let (source_start, source_end) = (wher.start() as u32, wher.end() as u32);
let (query_start, query_end) = (source_start - subtract_col, source_end - subtract_col);
let source_line_column = LineColumn {
line: i as u32,
column: source_start,
};
let query_region = {
let last_line = i as u32 - consecutive_query_lines;
let query_start_lc = LineColumn {
line: last_line,
column: query_start,
};
let query_end_lc = LineColumn {
line: last_line,
column: query_end,
};
let query_lc_region = LineColumnRegion::new(query_start_lc, query_end_lc);
line_info.convert_line_column_region(query_lc_region)
};
queries.push(TypeQuery {
query_region,
source,
comment_column,
source_line_column,
instantiate,
});
}
}
queries
}
#[derive(Default, Clone, Copy)]
pub struct InferOptions {
pub print_can_decls: bool,
pub print_only_under_alias: bool,
pub allow_errors: bool,
pub no_promote: bool,
}
#[derive(Debug)]
pub enum Elaboration {
Specialization {
specialized_name: String,
typ: String,
},
Source {
source: String,
typ: String,
},
Instantiation {
typ: String,
source: String,
offset_line: u32,
queries_in_instantiation: InferredQueries,
},
}
#[derive(Debug)]
pub struct InferredQuery {
pub elaboration: Elaboration,
/// Where the comment before the query string was written in the source.
pub comment_column: u32,
/// Where the query string "^^^" itself was written in the source.
pub source_line_column: LineColumn,
/// The content of the query string.
pub source: String,
}
pub struct Program {
home: ModuleId,
interns: Interns,
declarations: Declarations,
}
impl Program {
pub fn write_can_decls(&self, writer: &mut impl io::Write) -> io::Result<()> {
use roc_can::debug::{pretty_write_declarations, PPCtx};
let ctx = PPCtx {
home: self.home,
interns: &self.interns,
print_lambda_names: true,
};
pretty_write_declarations(writer, &ctx, &self.declarations)
}
}
#[derive(Debug)]
pub struct InferredQueries(Vec<InferredQuery>);
impl InferredQueries {
/// Returns all inferred queries, sorted by
/// - increasing source line
/// - on ties, decreasing source column
pub fn into_sorted(self) -> Vec<InferredQuery> {
let mut queries = self.0;
queries.sort_by(|lc1, lc2| {
let line1 = lc1.source_line_column.line;
let line2 = lc2.source_line_column.line;
let col1 = lc1.source_line_column.column;
let col2 = lc2.source_line_column.column;
line1.cmp(&line2).then(col2.cmp(&col1))
});
queries
}
}
pub struct InferredProgram {
program: Program,
inferred_queries: Vec<InferredQuery>,
}
impl InferredProgram {
/// Decomposes the program and inferred queries.
pub fn decompose(self) -> (InferredQueries, Program) {
let Self {
program,
inferred_queries,
} = self;
(InferredQueries(inferred_queries), program)
}
}
pub fn infer_queries(src: &str, options: InferOptions) -> Result<InferredProgram, Box<dyn Error>> {
let (
LoadedModule {
module_id: home,
mut can_problems,
mut type_problems,
mut declarations_by_id,
mut solved,
interns,
abilities_store,
..
},
src,
) = run_load_and_infer(src, options.no_promote)?;
let declarations = declarations_by_id.remove(&home).unwrap();
let subs = solved.inner_mut();
let can_problems = can_problems.remove(&home).unwrap_or_default();
let type_problems = type_problems.remove(&home).unwrap_or_default();
if !options.allow_errors {
let (can_problems, type_problems) =
format_problems(&src, home, &interns, can_problems, type_problems);
if !can_problems.is_empty() {
return Err(format!("Canonicalization problems: {can_problems}",).into());
}
if !type_problems.is_empty() {
return Err(format!("Type problems: {type_problems}",).into());
}
}
let line_info = LineInfo::new(&src);
let queries = parse_queries(&src, &line_info);
if queries.is_empty() {
return Err("No queries provided!".into());
}
let mut inferred_queries = Vec::with_capacity(queries.len());
let exposed_by_module = ExposedByModule::default();
let arena = Bump::new();
let mut ctx = QueryCtx {
all_queries: &queries,
arena: &arena,
source: &src,
declarations: &declarations,
subs,
abilities_store: &abilities_store,
home,
interns: &interns,
line_info,
derived_module: Default::default(),
exposed_by_module,
options,
};
for query in queries.iter() {
let answer = ctx.answer(query)?;
inferred_queries.push(answer);
}
Ok(InferredProgram {
program: Program {
home,
interns,
declarations,
},
inferred_queries,
})
}
struct QueryCtx<'a> {
all_queries: &'a [TypeQuery],
arena: &'a Bump,
source: &'a str,
declarations: &'a Declarations,
subs: &'a mut Subs,
abilities_store: &'a AbilitiesStore,
home: ModuleId,
interns: &'a Interns,
line_info: LineInfo,
derived_module: SharedDerivedModule,
exposed_by_module: ExposedByModule,
options: InferOptions,
}
impl<'a> QueryCtx<'a> {
fn answer(&mut self, query: &TypeQuery) -> Result<InferredQuery, Box<dyn Error>> {
let TypeQuery {
query_region,
source,
comment_column,
source_line_column,
instantiate,
} = query;
let start = query_region.start().offset;
let end = query_region.end().offset;
let text = &self.source[start as usize..end as usize];
let var = find_type_at(*query_region, self.declarations).ok_or_else(|| {
format!(
"No type for {:?} ({:?})!",
&text,
self.line_info.convert_region(*query_region)
)
})?;
let snapshot = self.subs.snapshot();
let type_string = name_and_print_var(
var,
self.subs,
self.home,
self.interns,
DebugPrint {
print_lambda_sets: true,
print_only_under_alias: self.options.print_only_under_alias,
ignore_polarity: true,
print_weakened_vars: true,
},
);
let elaboration = if *instantiate {
self.infer_instantiated(var, type_string, *query_region, text)?
} else {
self.infer_direct(type_string, *query_region, text)
};
self.subs.rollback_to(snapshot);
Ok(InferredQuery {
elaboration,
comment_column: *comment_column,
source_line_column: *source_line_column,
source: source.to_string(),
})
}
fn infer_direct(&mut self, typ: String, query_region: Region, text: &str) -> Elaboration {
match find_symbol_at(query_region, self.declarations, self.abilities_store) {
Some(found_symbol) => match found_symbol {
FoundSymbol::Specialization(spec_type, spec_symbol)
| FoundSymbol::AbilityMember(spec_type, spec_symbol) => {
Elaboration::Specialization {
specialized_name: format!(
"{}#{}({})",
spec_type.as_str(self.interns),
text,
spec_symbol.ident_id().index(),
),
typ,
}
}
FoundSymbol::Symbol(symbol) => Elaboration::Source {
source: symbol.as_str(self.interns).to_owned(),
typ,
},
},
None => Elaboration::Source {
source: text.to_owned(),
typ,
},
}
}
fn infer_instantiated(
&mut self,
var: Variable,
typ: String,
query_region: Region,
text: &str,
) -> Result<Elaboration, Box<dyn Error>> {
let symbol = match find_symbol_at(query_region, self.declarations, self.abilities_store) {
Some(FoundSymbol::Symbol(symbol) | FoundSymbol::Specialization(_, symbol)) => symbol,
_ => return Err(format!("No symbol under {text:?}",).into()),
};
let def = find_declaration(symbol, self.declarations)
.ok_or_else(|| format!("No def found for {text:?}"))?;
let region = def.region();
let LineColumnRegion { start, end } = self.line_info.convert_region(region);
let start_pos = self.line_info.convert_line_column(LineColumn {
line: start.line,
column: 0,
});
let end_pos = self.line_info.convert_line_column(LineColumn {
line: end.line + 1,
column: 0,
});
let def_region = Region::new(start_pos, end_pos);
let def_source = &self.source[start_pos.offset as usize..end_pos.offset as usize];
roc_late_solve::unify(
self.home,
self.arena,
self.subs,
&AbilitiesView::Module(self.abilities_store),
&self.derived_module,
&self.exposed_by_module,
var,
def.var(),
)
.map_err(|_| "does not unify")?;
let queries_in_instantiation = self
.all_queries
.iter()
.filter(|query| def_region.contains(&query.query_region))
.map(|query| self.answer(query))
.collect::<Result<Vec<_>, _>>()?;
Ok(Elaboration::Instantiation {
typ,
source: def_source.to_owned(),
offset_line: start.line,
queries_in_instantiation: InferredQueries(queries_in_instantiation),
})
}
}
pub fn infer_queries_help(src: &str, expected: impl FnOnce(&str), options: InferOptions) {
let InferredProgram {
program,
inferred_queries,
} = infer_queries(src, options).unwrap();
let mut output_parts = Vec::with_capacity(inferred_queries.len() + 2);
if options.print_can_decls {
use roc_can::debug::{pretty_print_declarations, PPCtx};
let ctx = PPCtx {
home: program.home,
interns: &program.interns,
print_lambda_names: true,
};
let pretty_decls = pretty_print_declarations(&ctx, &program.declarations);
output_parts.push(pretty_decls);
output_parts.push("\n".to_owned());
}
for InferredQuery { elaboration, .. } in inferred_queries {
let output_part = match elaboration {
Elaboration::Specialization {
specialized_name,
typ,
} => format!("{specialized_name} : {typ}"),
Elaboration::Source { source, typ } => format!("{source} : {typ}"),
Elaboration::Instantiation { .. } => panic!("Use uitest instead"),
};
output_parts.push(output_part);
}
let pretty_output = output_parts.join("\n");
expected(&pretty_output);
}

View file

@ -4607,18 +4607,9 @@ pub fn gather_tags_unsorted_iter(
let mut stack = vec![other_fields];
#[cfg(debug_assertions)]
let mut seen_head_union = false;
loop {
match subs.get_content_without_compacting(ext.var()) {
Structure(TagUnion(sub_fields, sub_ext)) => {
#[cfg(debug_assertions)]
{
assert!(!seen_head_union, "extension variable is another tag union, but I expected it to be either open or closed!");
seen_head_union = true;
}
stack.push(*sub_fields);
ext = *sub_ext;

View file

@ -0,0 +1,33 @@
[package]
name = "uitest"
description = "Integration tests for the solver."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[[test]]
name = "uitest"
path = "src/uitest.rs"
harness = false
[dev-dependencies]
roc_builtins = { path = "../builtins" }
roc_derive = { path = "../derive", features = ["debug-derived-symbols"] }
roc_load = { path = "../load" }
roc_parse = { path = "../parse" }
roc_problem = { path = "../problem" }
roc_reporting = { path = "../../reporting" }
roc_solve = { path = "../solve" }
roc_target = { path = "../roc_target" }
test_solve_helpers = { path = "../test_solve_helpers" }
bumpalo.workspace = true
indoc.workspace = true
insta.workspace = true
lazy_static.workspace = true
pretty_assertions.workspace = true
regex.workspace = true
tempfile.workspace = true
libtest-mimic.workspace = true

View file

@ -0,0 +1,448 @@
use std::{
error::Error,
ffi::OsStr,
fs, io,
path::{Path, PathBuf},
process::Command,
};
use lazy_static::lazy_static;
use libtest_mimic::{run, Arguments, Failed, Trial};
use regex::Regex;
use test_solve_helpers::{
infer_queries, Elaboration, InferOptions, InferredProgram, InferredQuery, MUTLILINE_MARKER,
};
fn main() -> Result<(), Box<dyn Error>> {
let args = Arguments::from_args();
let test_files = collect_uitest_files()?;
let tests = test_files
.into_iter()
.map(into_test)
.collect::<Result<_, _>>()?;
run(&args, tests).exit()
}
lazy_static! {
static ref UITEST_PATH: PathBuf = PathBuf::from(std::env!("ROC_WORKSPACE_DIR"))
.join("crates")
.join("compiler")
.join("uitest")
.join("tests");
/// # +opt infer:<opt>
static ref RE_OPT_INFER: Regex =
Regex::new(r#"# \+opt infer:(?P<opt>.*)"#).unwrap();
/// # +opt print:<opt>
static ref RE_OPT_PRINT: Regex =
Regex::new(r#"# \+opt print:(?P<opt>.*)"#).unwrap();
}
fn collect_uitest_files() -> io::Result<Vec<PathBuf>> {
let mut tests = Vec::with_capacity(200);
let mut dirs_to_visit = vec![UITEST_PATH.clone()];
while let Some(dir) = dirs_to_visit.pop() {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
let entry_type = entry.file_type()?;
if entry_type.is_dir() {
dirs_to_visit.push(path);
continue;
}
if path.extension() == Some(OsStr::new("txt")) {
tests.push(path);
}
}
}
Ok(tests)
}
fn into_test(path: PathBuf) -> io::Result<Trial> {
let name = path
.strip_prefix(UITEST_PATH.as_path().parent().unwrap())
.expect("collected path does not have uitest prefix")
.display()
.to_string();
let trial = Trial::test(name, move || run_test(path));
Ok(trial)
}
fn run_test(path: PathBuf) -> Result<(), Failed> {
let data = std::fs::read_to_string(&path)?;
let TestCase {
infer_options,
print_options,
source,
} = TestCase::parse(data)?;
let inferred_program = infer_queries(&source, infer_options)?;
{
let mut fd = fs::OpenOptions::new()
.write(true)
.truncate(true)
.open(&path)?;
assemble_query_output(&mut fd, &source, inferred_program, print_options)?;
}
check_for_changes(&path)?;
Ok(())
}
const EMIT_HEADER: &str = "# -emit:";
struct TestCase {
infer_options: InferOptions,
print_options: PrintOptions,
source: String,
}
#[derive(Default)]
struct PrintOptions {
can_decls: bool,
}
impl TestCase {
fn parse(mut data: String) -> Result<Self, Failed> {
// Drop anything following `# -emit:` header lines; that's the output.
if let Some(drop_at) = data.find(EMIT_HEADER) {
data.truncate(drop_at);
data.truncate(data.trim_end().len());
}
Ok(TestCase {
infer_options: Self::parse_infer_options(&data)?,
print_options: Self::parse_print_options(&data)?,
source: data,
})
}
fn parse_infer_options(data: &str) -> Result<InferOptions, Failed> {
let mut infer_opts = InferOptions {
no_promote: true,
..Default::default()
};
let found_infer_opts = RE_OPT_INFER.captures_iter(data);
for infer_opt in found_infer_opts {
let opt = infer_opt.name("opt").unwrap().as_str();
match opt.trim() {
"allow_errors" => infer_opts.allow_errors = true,
"print_only_under_alias" => infer_opts.print_only_under_alias = true,
other => return Err(format!("unknown infer option: {other:?}").into()),
}
}
Ok(infer_opts)
}
fn parse_print_options(data: &str) -> Result<PrintOptions, Failed> {
let mut print_opts = PrintOptions::default();
let found_infer_opts = RE_OPT_PRINT.captures_iter(data);
for infer_opt in found_infer_opts {
let opt = infer_opt.name("opt").unwrap().as_str();
match opt.trim() {
"can_decls" => print_opts.can_decls = true,
other => return Err(format!("unknown print option: {other:?}").into()),
}
}
Ok(print_opts)
}
}
fn check_for_changes(path: &Path) -> Result<(), Failed> {
Command::new("git").args(["add", "-N"]).arg(path).output()?;
let has_changes = Command::new("git")
.args(["diff", "--color=always"])
.arg(path)
.output()?;
if !has_changes.stdout.is_empty() {
return Err(format!(
"{}\nOutput has changed. If it looks okay, `git` add the file.",
std::str::from_utf8(&has_changes.stdout)?
)
.into());
}
Ok(())
}
/// Assemble the output for a test, with queries elaborated in-line.
fn assemble_query_output(
writer: &mut impl io::Write,
source: &str,
inferred_program: InferredProgram,
print_options: PrintOptions,
) -> io::Result<()> {
// Reverse the queries so that we can pop them off the end as we pass through the lines.
let (queries, program) = inferred_program.decompose();
let mut sorted_queries = queries.into_sorted();
sorted_queries.reverse();
let mut reflow = Reflow::new_unindented(writer);
write_source_with_answers(&mut reflow, source, sorted_queries, 0)?;
// Finish up with any remaining print options we were asked to provide.
let PrintOptions { can_decls } = print_options;
if can_decls {
writeln!(writer, "\n{EMIT_HEADER}can_decls")?;
program.write_can_decls(writer)?;
}
Ok(())
}
fn write_source_with_answers<W: io::Write>(
reflow: &mut Reflow<'_, W>,
source: &str,
mut sorted_queries: Vec<InferredQuery>,
offset_line: usize,
) -> io::Result<()> {
for (i, line) in source.lines().enumerate() {
let i = i + offset_line;
let mut is_query_line = false;
// Write all elaborated query lines if applicable.
while matches!(
sorted_queries.last(),
Some(InferredQuery {
source_line_column,
..
}) if source_line_column.line == i as _
) {
let inferred = sorted_queries.pop().unwrap();
reflow.scoped(|reflow| reconstruct_comment_line(reflow, inferred))?;
reflow.write("\n")?;
is_query_line = true;
}
// If this was previously a multi-line query output line, skip it, since we already wrote
// the new output above.
if line.contains(MUTLILINE_MARKER) {
continue;
}
// Otherwise, write the Roc source line.
if !is_query_line {
reflow.write(line.trim_end())?;
reflow.write("\n")?;
}
}
let mut sorted_queries = sorted_queries.into_iter().peekable();
while let Some(sorted_query) = sorted_queries.next() {
reflow.scoped(|reflow| reconstruct_comment_line(reflow, sorted_query))?;
// Only write a newline if we're not yet at the end of the source.
// Otherwise, a newline will be written for us after exiting the reconstruction of the
// comment line, since this must happen in the reconsutrction of a multi-line query.
if sorted_queries.peek().is_some() {
reflow.write("\n")?;
}
}
Ok(())
}
fn reconstruct_comment_line<W: io::Write>(
reflow: &mut Reflow<'_, W>,
inferred: InferredQuery,
) -> io::Result<()> {
let InferredQuery {
comment_column,
source_line_column,
source,
elaboration,
} = inferred;
reflow.add_layer(comment_column as _, source_line_column.column as _);
reflow.write_and_bump(&format!("{source} "))?;
match elaboration {
Elaboration::Specialization {
specialized_name,
typ,
} => {
reflow.write_and_bump(&format!("{specialized_name}: "))?;
reflow.write(&typ)
}
Elaboration::Source { source: _, typ } => reflow.write(&typ),
Elaboration::Instantiation {
typ,
source,
offset_line,
queries_in_instantiation,
} => {
reflow.write(&typ)?;
// Write the source on new line, but at the reflow column the comment is aligned at.
reflow.set_content(source_line_column.column as _);
reflow.write("\n")?;
let queries = queries_in_instantiation.into_sorted();
write_source_with_answers(reflow, source.trim_end(), queries, offset_line as _)
}
}
}
struct Reflow<'a, W: io::Write> {
writer: &'a mut W,
state: ReflowState,
}
#[derive(Clone, Debug)]
struct ReflowState {
/// true if the first line of the elaboration comment has been written.
top_line_written: bool,
/// Number of `content columns` prefixes written.
/// If this equals the number of content columns, the whole prefix for a line has been written.
content_prefixes_written: usize,
/// The column at which to insert the comment prefix "#".
comment_column: usize,
/// The columns at which content occurs.
/// If the stack is >1, then
/// - at the first content column, the [MUTLILINE_MARKER] may be written as appropriate
/// - for subsequent columns, spaces are inserted until the column is reached.
content_columns: Vec<usize>,
}
impl<'a, W: io::Write> std::ops::Deref for Reflow<'a, W> {
type Target = ReflowState;
fn deref(&self) -> &Self::Target {
&self.state
}
}
impl<'a, W: io::Write> std::ops::DerefMut for Reflow<'a, W> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.state
}
}
impl<'a, W: io::Write> Reflow<'a, W> {
fn new_unindented(writer: &'a mut W) -> Self {
Self {
writer,
state: ReflowState {
top_line_written: false,
content_prefixes_written: 0,
comment_column: 0,
content_columns: vec![],
},
}
}
fn scoped<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
let state = self.state.clone();
let result = f(self);
self.state = state;
result
}
fn add_layer(&mut self, comment_column: usize, content_column: usize) {
if self.comment_column == 0 {
// If the comment column is not yet set, this is the top-level and we should update the
// state; otherwise, we already have a comment column, only add to the content-ful
// layer.
self.comment_column = comment_column;
}
self.content_columns.push(content_column);
}
fn set_content(&mut self, content_column: usize) {
let latest_column = self
.content_columns
.last_mut()
.expect("cannot set content before adding a layer");
*latest_column = content_column;
}
fn write(&mut self, content: &str) -> io::Result<()> {
for (i, content_line) in content.split('\n').enumerate() {
if i > 0 {
// new line
writeln!(self.writer)?;
self.content_prefixes_written = 0;
}
// If the content columns are empty, this is top-level and we
// have no prefix to write.
if self.content_prefixes_written != self.content_columns.len() {
if self.content_prefixes_written == 0 {
self.write_n_spaces(self.comment_column)?;
write!(self.writer, "#")?;
// For the first column content - write spaces up to the column, and then if we are
// in a multiline context, add the multi-line marker.
{
self.write_n_spaces(self.content_columns[0] - self.comment_column - 1)?;
if self.top_line_written {
write!(self.writer, "{MUTLILINE_MARKER} ")?;
}
}
self.content_prefixes_written = 1;
}
// For all remaining content columns, fill them in with spaces.
let remaining_content_columns = self
.content_columns
.iter()
.skip(self.content_prefixes_written);
self.write_n_spaces(remaining_content_columns.sum())?;
self.content_prefixes_written = self.content_columns.len();
self.top_line_written = true;
}
write!(self.writer, "{content_line}")?;
}
Ok(())
}
fn write_and_bump(&mut self, content: &str) -> io::Result<()> {
assert!(
content.lines().count() == 1,
"cannot bump with multi-line content"
);
self.write(content)?;
let column = self
.content_columns
.last_mut()
.expect("cannot write_and_bump before adding layer");
*column += content.len();
Ok(())
}
fn write_n_spaces(&mut self, n: usize) -> io::Result<()> {
for _ in 0..n {
write!(self.writer, " ")?;
}
Ok(())
}
}

View file

@ -0,0 +1,7 @@
# +opt infer:print_only_under_alias
app "test" provides [main] to "./platform"
F a : a | a has Hash
main : F a -> F a
#^^^^{-1} a -[[main(0)]]-> a | a has Hash

View file

@ -0,0 +1,7 @@
# +opt infer:print_only_under_alias
app "test" provides [main] to "./platform"
F a : a | a has Hash & Eq & Decoding
main : F a -> F a
#^^^^{-1} a -[[main(0)]]-> a | a has Hash & Decoding & Eq

View file

@ -0,0 +1,8 @@
app "test" provides [main] to "./platform"
f : x -> x | x has Hash
g : x -> x | x has Decoding & Encoding
main : x -> x | x has Hash & Decoding & Encoding
main = \x -> x |> f |> g
#^^^^{-1} x -[[main(0)]]-> x | x has Hash & Encoding & Decoding

View file

@ -0,0 +1,12 @@
app "test" provides [top] to "./platform"
MDict u := (List u) | u has Hash & Eq
bot : MDict k -> MDict k
bot = \@MDict data ->
when {} is
{} -> @MDict data
top : MDict v -> MDict v
top = \x -> bot x
#^^^{-1} MDict v -[[top(0)]]-> MDict v | v has Hash & Eq

View file

@ -0,0 +1,9 @@
app "test" provides [isEqQ] to "./platform"
Q := [ F (Str -> Str), G ] has [Eq { isEq: isEqQ }]
isEqQ = \@Q q1, @Q q2 -> when T q1 q2 is
#^^^^^{-1} Q, Q -[[isEqQ(0)]]-> Bool
T (F _) (F _) -> Bool.true
T G G -> Bool.true
_ -> Bool.false

View file

@ -0,0 +1,10 @@
# +opt infer:print_only_under_alias
app "test" provides [main] to "./platform"
Q := ({} -> Str) has [Eq {isEq: isEqQ}]
isEqQ = \@Q f1, @Q f2 -> (f1 {} == f2 {})
#^^^^^{-1} ({} -[[]]-> Str), ({} -[[]]-> Str) -[[isEqQ(2)]]-> [False, True]
main = isEqQ (@Q \{} -> "a") (@Q \{} -> "a")
# ^^^^^ ({} -[[6, 7]]-> Str), ({} -[[6, 7]]-> Str) -[[isEqQ(2)]]-> [False, True]

View file

@ -0,0 +1,44 @@
app "test" provides [myU8] to "./platform"
MDecodeError : [TooShort, Leftover (List U8)]
MDecoder val fmt := List U8, fmt -> { result: Result val MDecodeError, rest: List U8 } | fmt has MDecoderFormatting
MDecoding has
decoder : MDecoder val fmt | val has MDecoding, fmt has MDecoderFormatting
MDecoderFormatting has
u8 : MDecoder U8 fmt | fmt has MDecoderFormatting
decodeWith : List U8, MDecoder val fmt, fmt -> { result: Result val MDecodeError, rest: List U8 } | fmt has MDecoderFormatting
decodeWith = \lst, (@MDecoder doDecode), fmt -> doDecode lst fmt
fromBytes : List U8, fmt -> Result val MDecodeError
| fmt has MDecoderFormatting, val has MDecoding
fromBytes = \lst, fmt ->
when decodeWith lst decoder fmt is
{ result, rest } ->
when result is
Ok val -> if List.isEmpty rest then Ok val else Err (Leftover rest)
Err e -> Err e
Linear := {} has [MDecoderFormatting {u8}]
u8 = @MDecoder \lst, @Linear {} ->
#^^{-1} Linear#u8(11): MDecoder U8 Linear
when List.first lst is
Ok n -> { result: Ok n, rest: List.dropFirst lst }
Err _ -> { result: Err TooShort, rest: [] }
MyU8 := U8 has [MDecoding {decoder}]
decoder = @MDecoder \lst, fmt ->
#^^^^^^^{-1} MyU8#decoder(12): MDecoder MyU8 fmt | fmt has MDecoderFormatting
when decodeWith lst u8 fmt is
{ result, rest } ->
{ result: Result.map result (\n -> @MyU8 n), rest }
myU8 : Result MyU8 _
myU8 = fromBytes [15] (@Linear {})
#^^^^{-1} Result MyU8 MDecodeError

View file

@ -0,0 +1,29 @@
app "test" provides [myU8Bytes] to "./platform"
MEncoder fmt := List U8, fmt -> List U8 | fmt has Format
MEncoding has
toEncoder : val -> MEncoder fmt | val has MEncoding, fmt has Format
Format has
u8 : U8 -> MEncoder fmt | fmt has Format
appendWith : List U8, MEncoder fmt, fmt -> List U8 | fmt has Format
appendWith = \lst, (@MEncoder doFormat), fmt -> doFormat lst fmt
toBytes : val, fmt -> List U8 | val has MEncoding, fmt has Format
toBytes = \val, fmt -> appendWith [] (toEncoder val) fmt
Linear := {} has [Format {u8}]
u8 = \n -> @MEncoder (\lst, @Linear {} -> List.append lst n)
#^^{-1} Linear#u8(10): U8 -[[u8(10)]]-> MEncoder Linear
MyU8 := U8 has [MEncoding {toEncoder}]
toEncoder = \@MyU8 n -> u8 n
#^^^^^^^^^{-1} MyU8#toEncoder(11): MyU8 -[[toEncoder(11)]]-> MEncoder fmt | fmt has Format
myU8Bytes = toBytes (@MyU8 15) (@Linear {})
#^^^^^^^^^{-1} List U8

View file

@ -0,0 +1,6 @@
app "test" provides [main] to "./platform"
main : Decoder Bool _
main = Decode.custom \bytes, fmt ->
Decode.decodeWith bytes Decode.decoder fmt
# ^^^^^^^^^^^^^^ Decoding#Decode.decoder(4): Decoder Bool fmt | fmt has DecoderFormatting

View file

@ -0,0 +1,4 @@
app "test" provides [main] to "./platform"
main = Bool.isEq Bool.true Bool.false
# ^^^^^^^^^ Eq#Bool.isEq(9): Bool, Bool -[[Bool.structuralEq(11)]]-> Bool

View file

@ -0,0 +1,5 @@
app "test" provides [main] to "./platform"
main =
\h -> Hash.hash h Bool.true
# ^^^^^^^^^ Hash#Hash.hash(1): a, Bool -[[Hash.hashBool(9)]]-> a | a has Hasher

View file

@ -0,0 +1,4 @@
app "test" provides [main] to "./platform"
main = Encode.toEncoder Bool.true
# ^^^^^^^^^^^^^^^^ Encoding#Encode.toEncoder(2): Bool -[[] + fmt:Encode.bool(17):1]-> Encoder fmt | fmt has EncoderFormatting

View file

@ -0,0 +1,6 @@
app "test" provides [main] to "./platform"
n : Num *
main = n == 1.
# ^ Dec

View file

@ -0,0 +1,9 @@
# +opt infer:print_only_under_alias
app "test" provides [main] to "./platform"
N := U8 has [Decoding]
main : Decoder N _
main = Decode.custom \bytes, fmt ->
Decode.decodeWith bytes Decode.decoder fmt
# ^^^^^^^^^^^^^^ N#Decode.decoder(3): List U8, fmt -[[7]]-> { rest : List U8, result : [Err [TooShort], Ok U8] } | fmt has DecoderFormatting

View file

@ -0,0 +1,6 @@
app "test" provides [main] to "./platform"
N := U8 has [Encoding]
main = Encode.toEncoder (@N 15)
# ^^^^^^^^^^^^^^^^ N#Encode.toEncoder(3): N -[[#N_toEncoder(3)]]-> Encoder fmt | fmt has EncoderFormatting

View file

@ -0,0 +1,8 @@
app "test" provides [main] to "./platform"
Trivial := {} has [Eq {isEq}]
isEq = \@Trivial {}, @Trivial {} -> Bool.true
main = Bool.isEq (@Trivial {}) (@Trivial {})
# ^^^^^^^^^ Trivial#Bool.isEq(2): Trivial, Trivial -[[isEq(2)]]-> Bool

View file

@ -0,0 +1,6 @@
app "test" provides [main] to "./platform"
N := U8 has [Eq]
main = Bool.isEq (@N 15) (@N 23)
# ^^^^^^^^^ N#Bool.isEq(3): N, N -[[#N_isEq(3)]]-> Bool

View file

@ -0,0 +1,8 @@
app "test" provides [main] to "./platform"
Noop := {} has [Hash {hash}]
hash = \hasher, @Noop {} -> hasher
main = \hasher -> hash hasher (@Noop {})
#^^^^{-1} hasher -[[main(0)]]-> hasher | hasher has Hasher

View file

@ -0,0 +1,6 @@
app "test" provides [main] to "./platform"
N := U8 has [Hash]
main = \hasher, @N n -> Hash.hash hasher (@N n)
# ^^^^^^^^^ N#Hash.hash(3): a, N -[[#N_hash(3)]]-> a | a has Hasher

View file

@ -0,0 +1,16 @@
app "test" provides [main] to "./platform"
F has f : a -> (b -> {}) | a has F, b has G
G has g : b -> {} | b has G
Fo := {} has [F {f}]
f = \@Fo {} -> g
#^{-1} Fo#f(7): Fo -[[f(7)]]-> (b -[[] + b:g(4):1]-> {}) | b has G
Go := {} has [G {g}]
g = \@Go {} -> {}
#^{-1} Go#g(8): Go -[[g(8)]]-> {}
main = (f (@Fo {})) (@Go {})
# ^ Fo#f(7): Fo -[[f(7)]]-> (Go -[[g(8)]]-> {})
# ^^^^^^^^^^ Go -[[g(8)]]-> {}

View file

@ -0,0 +1,20 @@
app "test" provides [main] to "./platform"
F has f : a -> ({} -> b) | a has F, b has G
G has g : {} -> b | b has G
Fo := {} has [F {f}]
f = \@Fo {} -> g
#^{-1} Fo#f(7): Fo -[[f(7)]]-> ({} -[[] + b:g(4):1]-> b) | b has G
Go := {} has [G {g}]
g = \{} -> @Go {}
#^{-1} Go#g(8): {} -[[g(8)]]-> Go
main =
foo = 1
@Go it = (f (@Fo {})) {}
# ^ Fo#f(7): Fo -[[f(7)]]-> ({} -[[g(8)]]-> Go)
# ^^^^^^^^^^ {} -[[g(8)]]-> Go
{foo, it}

View file

@ -0,0 +1,22 @@
app "test" provides [f] to "./platform"
J has j : j -> (k -> {}) | j has J, k has K
K has k : k -> {} | k has K
C := {} has [J {j: jC}]
jC = \@C _ -> k
D := {} has [J {j: jD}]
jD = \@D _ -> k
E := {} has [K {k}]
k = \@E _ -> {}
f = \flag, a, c ->
it =
when flag is
A -> j a
B -> j a
it c
# ^ k | k has K
# ^^ k -[[] + j:j(2):2]-> {} | j has J, k has K

View file

@ -0,0 +1,34 @@
app "test" provides [main] to "./platform"
J has j : j -> (k -> {}) | j has J, k has K
K has k : k -> {} | k has K
C := {} has [J {j: jC}]
jC = \@C _ -> k
#^^{-1} C -[[jC(8)]]-> (k -[[] + k:k(4):1]-> {}) | k has K
D := {} has [J {j: jD}]
jD = \@D _ -> k
#^^{-1} D -[[jD(9)]]-> (k -[[] + k:k(4):1]-> {}) | k has K
E := {} has [K {k}]
k = \@E _ -> {}
#^{-1} E#k(10): E -[[k(10)]]-> {}
f = \flag, a, b ->
# ^ j | j has J
# ^ j | j has J
it =
# ^^ k -[[] + j:j(2):2 + j1:j(2):2]-> {} | j has J, j1 has J, k has K
when flag is
A -> j a
# ^ J#j(2): j -[[] + j:j(2):1]-> (k -[[] + j:j(2):2 + j1:j(2):2]-> {}) | j has J, j1 has J, k has K
B -> j b
# ^ J#j(2): j -[[] + j:j(2):1]-> (k -[[] + j1:j(2):2 + j:j(2):2]-> {}) | j has J, j1 has J, k has K
it
# ^^ k -[[] + j:j(2):2 + j1:j(2):2]-> {} | j has J, j1 has J, k has K
main = (f A (@C {}) (@D {})) (@E {})
# ^ [A, B], C, D -[[f(11)]]-> (E -[[k(10)]]-> {})
# ^^^^^^^^^^^^^^^^^^^ E -[[k(10)]]-> {}
#^^^^{-1} {}

View file

@ -0,0 +1,45 @@
app "test" provides [main] to "./platform"
J has j : j -> (k -> {}) | j has J, k has K
K has k : k -> {} | k has K
C := {} has [J {j: jC}]
jC = \@C _ -> k
#^^{-1} C -[[jC(9)]]-> (k -[[] + k:k(4):1]-> {}) | k has K
D := {} has [J {j: jD}]
jD = \@D _ -> k
#^^{-1} D -[[jD(10)]]-> (k -[[] + k:k(4):1]-> {}) | k has K
E := {} has [K {k: kE}]
kE = \@E _ -> {}
#^^{-1} E -[[kE(11)]]-> {}
F := {} has [K {k: kF}]
kF = \@F _ -> {}
#^^{-1} F -[[kF(12)]]-> {}
f = \flag, a, b ->
# ^ j | j has J
# ^ j | j has J
it =
# ^^ k -[[] + j:j(2):2 + j1:j(2):2]-> {} | j has J, j1 has J, k has K
when flag is
A -> j a
# ^ J#j(2): j -[[] + j:j(2):1]-> (k -[[] + j:j(2):2 + j1:j(2):2]-> {}) | j has J, j1 has J, k has K
B -> j b
# ^ J#j(2): j -[[] + j:j(2):1]-> (k -[[] + j1:j(2):2 + j:j(2):2]-> {}) | j has J, j1 has J, k has K
it
# ^^ k -[[] + j:j(2):2 + j1:j(2):2]-> {} | j has J, j1 has J, k has K
main =
#^^^^{-1} {}
it = \x ->
# ^^ k -[[it(21)]]-> {} | k has K
(f A (@C {}) (@D {})) x
# ^ [A, B], C, D -[[f(13)]]-> (k -[[] + k:k(4):1]-> {}) | k has K
if Bool.true
then it (@E {})
# ^^ E -[[it(21)]]-> {}
else it (@F {})
# ^^ F -[[it(21)]]-> {}

View file

@ -0,0 +1,16 @@
app "test" provides [main] to "./platform"
F has f : a, b -> ({} -> ({} -> {})) | a has F, b has G
G has g : b -> ({} -> {}) | b has G
Fo := {} has [F {f}]
f = \@Fo {}, b -> \{} -> g b
#^{-1} Fo#f(7): Fo, b -[[f(7)]]-> ({} -[[13 b]]-> ({} -[[] + b:g(4):2]-> {})) | b has G
Go := {} has [G {g}]
g = \@Go {} -> \{} -> {}
#^{-1} Go#g(8): Go -[[g(8)]]-> ({} -[[14]]-> {})
main =
(f (@Fo {}) (@Go {})) {}
# ^ Fo#f(7): Fo, Go -[[f(7)]]-> ({} -[[13 Go]]-> ({} -[[14]]-> {}))

View file

@ -0,0 +1,20 @@
app "test" provides [main] to "./platform"
F has f : a -> (b -> {}) | a has F, b has G
G has g : b -> {} | b has G
Fo := {} has [F {f}]
f = \@Fo {} -> g
#^{-1} Fo#f(7): Fo -[[f(7)]]-> (b -[[] + b:g(4):1]-> {}) | b has G
Go := {} has [G {g}]
g = \@Go {} -> {}
#^{-1} Go#g(8): Go -[[g(8)]]-> {}
main =
# h should get weakened
h = f (@Fo {})
# ^ Fo#f(7): Fo -[[f(7)]]-> (Go -[[g(8)]]-> {})
# ^ Go -[[g(8)]]-> {}
h (@Go {})
# ^ Go -[[g(8)]]-> {}

View file

@ -0,0 +1,19 @@
app "test" provides [main] to "./platform"
F has f : a -> (b -> {}) | a has F, b has G
G has g : b -> {} | b has G
Fo := {} has [F {f}]
f = \@Fo {} -> g
#^{-1} Fo#f(7): Fo -[[f(7)]]-> (b -[[] + b:g(4):1]-> {}) | b has G
Go := {} has [G {g}]
g = \@Go {} -> {}
#^{-1} Go#g(8): Go -[[g(8)]]-> {}
main =
#^^^^{-1} b -[[] + b:g(4):1]-> {} | b has G
h = f (@Fo {})
# ^ Fo#f(7): Fo -[[f(7)]]-> (b -[[] + b:g(4):1]-> {}) | b has G
# ^ b -[[] + b:g(4):1]-> {} | b has G
h

View file

@ -0,0 +1,5 @@
app "test" provides [main] to "./platform"
main =
\h -> Hash.hash h 7
# ^^^^^^^^^ Hash#Hash.hash(1): a, I64 -[[Hash.hashI64(13)]]-> a | a has Hasher

View file

@ -0,0 +1,6 @@
app "test"
imports [Encode.{ toEncoder }]
provides [main] to "./platform"
main = toEncoder { a: "" }
# ^^^^^^^^^ Encoding#toEncoder(2): { a : Str } -[[#Derived.toEncoder_{a}(0)]]-> Encoder fmt | fmt has EncoderFormatting

View file

@ -0,0 +1,9 @@
app "test"
imports [Encode.{ toEncoder, custom }]
provides [main] to "./platform"
A := {} has [Encoding {toEncoder}]
toEncoder = \@A _ -> custom \b, _ -> b
main = toEncoder { a: @A {} }
# ^^^^^^^^^ Encoding#toEncoder(2): { a : A } -[[#Derived.toEncoder_{a}(0)]]-> Encoder fmt | fmt has EncoderFormatting

View file

@ -0,0 +1,19 @@
app "test" provides [main] to "./platform"
Id1 has id1 : a -> a | a has Id1
Id2 has id2 : a -> a | a has Id2
A := {} has [Id1 {id1}, Id2 {id2}]
id1 = \@A {} -> @A {}
#^^^{-1} A#id1(6): A -[[id1(6)]]-> A
id2 = \@A {} -> id1 (@A {})
# ^^^ A#id1(6): A -[[id1(6)]]-> A
#^^^{-1} A#id2(7): A -[[id2(7)]]-> A
main =
a : A
a = id2 (@A {})
# ^^^ A#id2(7): A -[[id2(7)]]-> A
a

View file

@ -0,0 +1,23 @@
app "test" provides [main] to "./platform"
Id has id : a -> a | a has Id
A := {} has [Id {id}]
id = \@A {} -> @A {}
#^^{-1} A#id(4): A -[[id(4)]]-> A
idNotAbility = \x -> x
#^^^^^^^^^^^^{-1} a -[[idNotAbility(5)]]-> a
main =
choice : [T, U]
# Should not get generalized
idChoice =
#^^^^^^^^{-1} A -[[id(4), idNotAbility(5)]]-> A
when choice is
T -> id
U -> idNotAbility
idChoice (@A {})
#^^^^^^^^{-1} A -[[id(4), idNotAbility(5)]]-> A

View file

@ -0,0 +1,20 @@
app "test" provides [main] to "./platform"
Id has id : a -> a | a has Id
A := {} has [Id {id}]
id = \@A {} -> @A {}
#^^{-1} A#id(4): A -[[id(4)]]-> A
main =
choice : [T, U]
# Should not get generalized
idChoice =
#^^^^^^^^{-1} A -[[id(4)]]-> A
when choice is
T -> id
U -> id
idChoice (@A {})
#^^^^^^^^{-1} A -[[id(4)]]-> A

View file

@ -0,0 +1,19 @@
app "test" provides [main] to "./platform"
Id has id : a -> a | a has Id
A := {} has [Id {id}]
id = \@A {} -> @A {}
#^^{-1} A#id(4): A -[[id(4)]]-> A
main =
alias1 = \x -> id x
# ^^ Id#id(2): a -[[] + a:id(2):1]-> a | a has Id
alias2 = \x -> alias1 x
# ^^^^^^ a -[[alias1(6)]]-> a | a has Id
a : A
a = alias2 (@A {})
# ^^^^^^ A -[[alias2(7)]]-> A
a

View file

@ -0,0 +1,20 @@
app "test" provides [main] to "./platform"
Id has id : a -> a | a has Id
A := {} has [Id {id}]
id = \@A {} -> @A {}
#^^{-1} A#id(4): A -[[id(4)]]-> A
main =
# Both alias1, alias2 should get weakened
alias1 = id
# ^^ Id#id(2): A -[[id(4)]]-> A
alias2 = alias1
# ^^^^^^ A -[[id(4)]]-> A
a : A
a = alias2 (@A {})
# ^^^^^^ A -[[id(4)]]-> A
a

View file

@ -0,0 +1,22 @@
app "test" provides [main] to "./platform"
Bounce has
ping : a -> a | a has Bounce
pong : a -> a | a has Bounce
A := {} has [Bounce {ping: pingA, pong: pongA}]
pingA = \@A {} -> pong (@A {})
# ^^^^ A#pong(6): A -[[pongA(6)]]-> A
#^^^^^{-1} A -[[pingA(5)]]-> A
pongA = \@A {} -> ping (@A {})
# ^^^^ A#ping(5): A -[[pingA(5)]]-> A
#^^^^^{-1} A -[[pongA(6)]]-> A
main =
a : A
a = ping (@A {})
# ^^^^ A#ping(5): A -[[pingA(5)]]-> A
a

View file

@ -0,0 +1,22 @@
app "test" provides [main] to "./platform"
Bounce has
ping : a -> a | a has Bounce
pong : a -> a | a has Bounce
A := {} has [Bounce {ping, pong}]
ping = \@A {} -> pong (@A {})
# ^^^^ A#pong(6): A -[[pong(6)]]-> A
#^^^^{-1} A#ping(5): A -[[ping(5)]]-> A
pong = \@A {} -> ping (@A {})
# ^^^^ A#ping(5): A -[[ping(5)]]-> A
#^^^^{-1} A#pong(6): A -[[pong(6)]]-> A
main =
a : A
a = ping (@A {})
# ^^^^ A#ping(5): A -[[ping(5)]]-> A
a

View file

@ -0,0 +1,17 @@
app "test" provides [main] to "./platform"
Diverge has diverge : a -> a | a has Diverge
A := {} has [Diverge {diverge}]
diverge : A -> A
diverge = \@A {} -> diverge (@A {})
# ^^^^^^^ A#diverge(4): A -[[diverge(4)]]-> A
#^^^^^^^{-1} A#diverge(4): A -[[diverge(4)]]-> A
main =
a : A
a = diverge (@A {})
# ^^^^^^^ A#diverge(4): A -[[diverge(4)]]-> A
a

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