mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 07:14:46 +00:00
Merge branch 'trunk' of github.com:rtfeldman/roc into format-precedence-conflict
This commit is contained in:
commit
73744b3b1d
74 changed files with 3308 additions and 919 deletions
335
Cargo.lock
generated
335
Cargo.lock
generated
|
@ -15,9 +15,15 @@ version = "0.11.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
"winapi 0.3.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d663a8e9a99154b5fb793032533f6328da35e23aac63d5c152279aa8ba356825"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.5.1"
|
||||
|
@ -32,7 +38,7 @@ checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
|||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
"winapi 0.3.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -124,6 +130,35 @@ dependencies = [
|
|||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.0.0-beta.1"
|
||||
source = "git+https://github.com/rtfeldman/clap#e1d83a78804a271b053d4d21f69b67f7eb01ad01"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags",
|
||||
"clap_derive",
|
||||
"indexmap",
|
||||
"lazy_static",
|
||||
"strsim",
|
||||
"termcolor",
|
||||
"textwrap",
|
||||
"unicode-width",
|
||||
"vec_map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "3.0.0-beta.1"
|
||||
source = "git+https://github.com/rtfeldman/clap#e1d83a78804a271b053d4d21f69b67f7eb01ad01"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-error",
|
||||
"proc-macro2 1.0.10",
|
||||
"quote 1.0.3",
|
||||
"syn 1.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cloudabi"
|
||||
version = "0.0.3"
|
||||
|
@ -141,7 +176,7 @@ checksum = "1fc755679c12bda8e5523a71e4d654b6bf2e14bd838dfc48cde6559a05caf7d1"
|
|||
dependencies = [
|
||||
"atty",
|
||||
"cast",
|
||||
"clap",
|
||||
"clap 2.33.0",
|
||||
"criterion-plot",
|
||||
"csv",
|
||||
"itertools",
|
||||
|
@ -277,6 +312,22 @@ version = "0.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-zircon"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"fuchsia-zircon-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-zircon-sys"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.1.14"
|
||||
|
@ -288,6 +339,15 @@ dependencies = [
|
|||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.10"
|
||||
|
@ -325,6 +385,15 @@ dependencies = [
|
|||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292"
|
||||
dependencies = [
|
||||
"autocfg 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indoc"
|
||||
version = "0.3.5"
|
||||
|
@ -378,6 +447,15 @@ version = "0.1.11"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb6ee2a7da03bfc3b66ca47c92c2e392fcc053ea040a85561749b026f7aad09a"
|
||||
|
||||
[[package]]
|
||||
name = "iovec"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.8.2"
|
||||
|
@ -402,6 +480,16 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kernel32-sys"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
|
||||
dependencies = [
|
||||
"winapi 0.2.8",
|
||||
"winapi-build",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
|
@ -410,9 +498,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.68"
|
||||
version = "0.2.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0"
|
||||
checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005"
|
||||
|
||||
[[package]]
|
||||
name = "llvm-sys"
|
||||
|
@ -429,9 +517,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.3.3"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79b2de95ecb4691949fea4716ca53cdbcfccb2c612e19644a8bad05edcf9f47b"
|
||||
checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75"
|
||||
dependencies = [
|
||||
"scopeguard",
|
||||
]
|
||||
|
@ -472,6 +560,81 @@ dependencies = [
|
|||
"autocfg 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.6.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fuchsia-zircon",
|
||||
"fuchsia-zircon-sys",
|
||||
"iovec",
|
||||
"kernel32-sys",
|
||||
"libc",
|
||||
"log",
|
||||
"miow 0.2.1",
|
||||
"net2",
|
||||
"slab",
|
||||
"winapi 0.2.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio-named-pipes"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5e374eff525ce1c5b7687c4cef63943e7686524a387933ad27ca7ec43779cb3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"mio",
|
||||
"miow 0.3.3",
|
||||
"winapi 0.3.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio-uds"
|
||||
version = "0.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125"
|
||||
dependencies = [
|
||||
"iovec",
|
||||
"libc",
|
||||
"mio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miow"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
|
||||
dependencies = [
|
||||
"kernel32-sys",
|
||||
"net2",
|
||||
"winapi 0.2.8",
|
||||
"ws2_32-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miow"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "396aa0f2003d7df8395cb93e09871561ccc3e785f0acb369170e8cc74ddf9226"
|
||||
dependencies = [
|
||||
"socket2",
|
||||
"winapi 0.3.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "net2"
|
||||
version = "0.2.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"winapi 0.3.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.11"
|
||||
|
@ -505,9 +668,9 @@ checksum = "ebcec7c9c2a95cacc7cd0ecb89d8a8454eca13906f6deb55258ffff0adeb9405"
|
|||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.10.0"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92e98c49ab0b7ce5b222f2cc9193fc4efe11c6d0bd4f648e374684a6857b1cfc"
|
||||
checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
|
@ -515,16 +678,16 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.7.0"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7582838484df45743c8434fbff785e8edf260c28748353d44bc0da32e0ceabf1"
|
||||
checksum = "0e136c1904604defe99ce5fd71a28d473fa60a12255d511aa78a9ddf11237aeb"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cloudabi",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"winapi",
|
||||
"winapi 0.3.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -561,6 +724,32 @@ dependencies = [
|
|||
"difference",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18f33027081eba0a6d8aba6d1b1c3a3be58cbb12106341c2d5759fcd9b5277e7"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr",
|
||||
"proc-macro2 1.0.10",
|
||||
"quote 1.0.3",
|
||||
"syn 1.0.17",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a5b4b77fdb63c1eca72173d68d24501c54ab1269409f6b672c85deb18af69de"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.10",
|
||||
"quote 1.0.3",
|
||||
"syn 1.0.17",
|
||||
"syn-mid",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-hack"
|
||||
version = "0.5.15"
|
||||
|
@ -642,7 +831,7 @@ dependencies = [
|
|||
"rand_os",
|
||||
"rand_pcg",
|
||||
"rand_xorshift",
|
||||
"winapi",
|
||||
"winapi 0.3.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -737,7 +926,7 @@ checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"
|
|||
dependencies = [
|
||||
"libc",
|
||||
"rand_core 0.4.2",
|
||||
"winapi",
|
||||
"winapi 0.3.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -751,7 +940,7 @@ dependencies = [
|
|||
"libc",
|
||||
"rand_core 0.4.2",
|
||||
"rdrand",
|
||||
"winapi",
|
||||
"winapi 0.3.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -854,7 +1043,7 @@ version = "0.5.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
"winapi 0.3.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -862,15 +1051,22 @@ name = "roc-cli"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"clap 3.0.0-beta.1",
|
||||
"im",
|
||||
"im-rc",
|
||||
"indoc",
|
||||
"inkwell",
|
||||
"inlinable_string",
|
||||
"maplit",
|
||||
"pretty_assertions",
|
||||
"quickcheck",
|
||||
"quickcheck_macros",
|
||||
"roc_builtins",
|
||||
"roc_can",
|
||||
"roc_collections",
|
||||
"roc_constrain",
|
||||
"roc_gen",
|
||||
"roc_load",
|
||||
"roc_module",
|
||||
"roc_mono",
|
||||
"roc_parse",
|
||||
|
@ -882,6 +1078,7 @@ dependencies = [
|
|||
"roc_unify",
|
||||
"roc_uniq",
|
||||
"target-lexicon",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1286,6 +1483,16 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sized-chunks"
|
||||
version = "0.5.3"
|
||||
|
@ -1297,10 +1504,34 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.2.0"
|
||||
name = "slab"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c2fb2ec9bcd216a5b0d0ccf31ab17b5ed1d627960edff65bbe95d3ce221cefc"
|
||||
checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05720e22615919e4734f6a99ceae50d00226c3c5aca406e102ebc33298214e0a"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"winapi 0.3.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
|
@ -1324,6 +1555,17 @@ dependencies = [
|
|||
"unicode-xid 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn-mid"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.10",
|
||||
"quote 1.0.3",
|
||||
"syn 1.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "target-lexicon"
|
||||
version = "0.10.0"
|
||||
|
@ -1341,7 +1583,7 @@ dependencies = [
|
|||
"rand 0.7.3",
|
||||
"redox_syscall",
|
||||
"remove_dir_all",
|
||||
"winapi",
|
||||
"winapi 0.3.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1383,15 +1625,22 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "0.2.16"
|
||||
version = "0.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee5a0dd887e37d37390c13ff8ac830f992307fe30a1fff0ab8427af67211ba28"
|
||||
checksum = "34ef16d072d2b6dc8b4a56c70f5c5ced1a37752116f8e7c1e80c659aa7cb6713"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"memchr",
|
||||
"mio",
|
||||
"mio-named-pipes",
|
||||
"mio-uds",
|
||||
"num_cpus",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"winapi 0.3.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1402,9 +1651,15 @@ checksum = "0685c84d5d54d1c26f7d3eb96cd41550adb97baed141a761cf335d3d33bcd0ae"
|
|||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.11.2"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9"
|
||||
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
|
@ -1430,6 +1685,12 @@ version = "0.1.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63f18aa3b0e35fed5a0048f029558b1518095ffe2a0a31fb87c93dece93a4993"
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
|
||||
|
||||
[[package]]
|
||||
name = "ven_ena"
|
||||
version = "0.13.1"
|
||||
|
@ -1469,7 +1730,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi",
|
||||
"winapi 0.3.8",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
|
@ -1543,6 +1804,12 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.8"
|
||||
|
@ -1553,6 +1820,12 @@ dependencies = [
|
|||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-build"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
|
@ -1565,7 +1838,7 @@ version = "0.1.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa515c5163a99cc82bab70fd3bfdd36d827be85de63737b40fcef2ce084a436e"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
"winapi 0.3.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1574,6 +1847,16 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "ws2_32-sys"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
|
||||
dependencies = [
|
||||
"winapi 0.2.8",
|
||||
"winapi-build",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wyhash"
|
||||
version = "0.3.0"
|
||||
|
|
|
@ -16,6 +16,21 @@ path = "src/main.rs"
|
|||
test = false
|
||||
bench = false
|
||||
|
||||
[features]
|
||||
default = ["target-x86"]
|
||||
|
||||
target-x86 = []
|
||||
|
||||
# arm and wasm give linker errors on some platforms
|
||||
target-arm = []
|
||||
target-webassembly = []
|
||||
|
||||
target-all = [
|
||||
"target-x86",
|
||||
"target-arm",
|
||||
"target-webassembly"
|
||||
]
|
||||
|
||||
|
||||
[dependencies]
|
||||
roc_collections = { path = "../compiler/collections" }
|
||||
|
@ -31,12 +46,16 @@ roc_uniq = { path = "../compiler/uniq" }
|
|||
roc_unify = { path = "../compiler/unify" }
|
||||
roc_solve = { path = "../compiler/solve" }
|
||||
roc_mono = { path = "../compiler/mono" }
|
||||
roc_load = { path = "../compiler/load", version = "0.1.0" }
|
||||
roc_gen = { path = "../compiler/gen", version = "0.1.0" }
|
||||
roc_reporting = { path = "../compiler/reporting", version = "0.1.0" }
|
||||
# TODO switch to clap 3.0.0 once it's out. Tried adding clap = "~3.0.0-beta.1" and cargo wouldn't accept it
|
||||
clap = { git = "https://github.com/rtfeldman/clap", branch = "master" }
|
||||
im = "14" # im and im-rc should always have the same version!
|
||||
im-rc = "14" # im and im-rc should always have the same version!
|
||||
bumpalo = { version = "3.2", features = ["collections"] }
|
||||
inlinable_string = "0.1.0"
|
||||
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded", "process", "io-driver"] }
|
||||
# NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything.
|
||||
#
|
||||
# The reason for this fork is that the way Inkwell is designed, you have to use
|
||||
|
@ -56,3 +75,10 @@ inlinable_string = "0.1.0"
|
|||
# This way, GitHub Actions works and nobody's builds get broken.
|
||||
inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release1" }
|
||||
target-lexicon = "0.10"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
quickcheck_macros = "0.8"
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
# Interpreter
|
||||
|
||||
Usage:
|
||||
|
||||
```
|
||||
$ roc FILENAME.roc
|
||||
```
|
||||
|
||||
When building from Rust source, use `cargo run -- FILENAME.roc` instead of `roc FILENAME.roc`.
|
||||
|
||||
For example, here's how to run the "EchoName" example this way:
|
||||
|
||||
```
|
||||
$ cargo run -- examples/EchoName.roc
|
||||
```
|
361
cli/src/main.rs
361
cli/src/main.rs
|
@ -1,7 +1,7 @@
|
|||
extern crate roc_gen;
|
||||
extern crate roc_reporting;
|
||||
|
||||
use crate::helpers::{infer_expr, uniq_expr_with};
|
||||
#[macro_use]
|
||||
extern crate clap;
|
||||
use bumpalo::Bump;
|
||||
use inkwell::context::Context;
|
||||
use inkwell::module::Linkage;
|
||||
|
@ -9,118 +9,236 @@ use inkwell::passes::PassManager;
|
|||
use inkwell::types::BasicType;
|
||||
use inkwell::OptimizationLevel;
|
||||
use roc_collections::all::ImMap;
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_gen::llvm::build::{
|
||||
build_proc, build_proc_header, get_call_conventions, module_from_builtins,
|
||||
build_proc, build_proc_header, get_call_conventions, module_from_builtins, OptLevel,
|
||||
};
|
||||
use roc_gen::llvm::convert::basic_type_from_layout;
|
||||
use roc_load::file::{LoadedModule, LoadingProblem};
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::expr::{Expr, Procs};
|
||||
use roc_mono::layout::Layout;
|
||||
use std::time::SystemTime;
|
||||
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use inkwell::targets::{
|
||||
CodeModel, FileType, InitializationConfig, RelocMode, Target, TargetTriple,
|
||||
};
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::io::prelude::*;
|
||||
use std::io::{self, ErrorKind};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::process;
|
||||
use target_lexicon::{Architecture, OperatingSystem, Triple, Vendor};
|
||||
use tokio::process::Command;
|
||||
use tokio::runtime::Builder;
|
||||
|
||||
pub mod helpers;
|
||||
pub mod repl;
|
||||
|
||||
pub static FLAG_OPTIMIZE: &str = "optimize";
|
||||
pub static FLAG_ROC_FILE: &str = "ROC_FILE";
|
||||
|
||||
pub fn build_app<'a>() -> App<'a> {
|
||||
App::new("roc")
|
||||
.version(crate_version!())
|
||||
.subcommand(App::new("build")
|
||||
.about("Build a program")
|
||||
.arg(
|
||||
Arg::with_name(FLAG_ROC_FILE)
|
||||
.help("The .roc file to build")
|
||||
.required(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(FLAG_OPTIMIZE)
|
||||
.long(FLAG_OPTIMIZE)
|
||||
.help("Optimize the compiled program to run faster. (Optimization takes time to complete.)")
|
||||
.required(false),
|
||||
)
|
||||
)
|
||||
.subcommand(App::new("run")
|
||||
.about("Build and run a program")
|
||||
.arg(
|
||||
Arg::with_name(FLAG_ROC_FILE)
|
||||
.help("The .roc file to build and run")
|
||||
.required(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(FLAG_OPTIMIZE)
|
||||
.long(FLAG_OPTIMIZE)
|
||||
.help("Optimize the compiled program to run faster. (Optimization takes time to complete.)")
|
||||
.required(false),
|
||||
)
|
||||
)
|
||||
.subcommand(App::new("repl")
|
||||
.about("Launch the interactive Read Eval Print Loop (REPL)")
|
||||
)
|
||||
}
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
let now = SystemTime::now();
|
||||
let argv = std::env::args().collect::<Vec<String>>();
|
||||
let matches = build_app().get_matches();
|
||||
|
||||
match argv.get(1) {
|
||||
Some(filename) => {
|
||||
let mut path = Path::new(filename).canonicalize().unwrap();
|
||||
|
||||
if !path.is_absolute() {
|
||||
path = std::env::current_dir()?.join(path).canonicalize().unwrap();
|
||||
}
|
||||
|
||||
// Step 1: build the .o file for the app
|
||||
let mut file = File::open(path.clone())?;
|
||||
let mut contents = String::new();
|
||||
|
||||
file.read_to_string(&mut contents)?;
|
||||
|
||||
let dest_filename = path.with_extension("o");
|
||||
|
||||
gen(
|
||||
Path::new(filename).to_path_buf(),
|
||||
contents.as_str(),
|
||||
Triple::host(),
|
||||
&dest_filename,
|
||||
);
|
||||
|
||||
let end_time = now.elapsed().unwrap();
|
||||
|
||||
println!(
|
||||
"Finished compilation and code gen in {} ms\n",
|
||||
end_time.as_millis()
|
||||
);
|
||||
|
||||
let cwd = dest_filename.parent().unwrap();
|
||||
let lib_path = dest_filename.with_file_name("libroc_app.a");
|
||||
|
||||
// Step 2: turn the .o file into a .a static library
|
||||
Command::new("ar") // TODO on Windows, use `link`
|
||||
.args(&[
|
||||
"rcs",
|
||||
lib_path.to_str().unwrap(),
|
||||
dest_filename.to_str().unwrap(),
|
||||
])
|
||||
.spawn()
|
||||
.expect("`ar` failed to run");
|
||||
|
||||
// Step 3: have rustc compile the host and link in the .a file
|
||||
Command::new("rustc")
|
||||
.args(&["-L", ".", "host.rs", "-o", "app"])
|
||||
.current_dir(cwd)
|
||||
.spawn()
|
||||
.expect("rustc failed to run");
|
||||
|
||||
// Step 4: Run the compiled app
|
||||
Command::new(cwd.join("app")).spawn().unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"{} failed to run: {:?}",
|
||||
cwd.join("app").to_str().unwrap(),
|
||||
err
|
||||
)
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
None => {
|
||||
println!("Usage: roc FILENAME.roc");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
match matches.subcommand_name() {
|
||||
Some("build") => build(matches.subcommand_matches("build").unwrap(), false),
|
||||
Some("run") => build(matches.subcommand_matches("run").unwrap(), true),
|
||||
Some("repl") => repl::main(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn gen(filename: PathBuf, src: &str, target: Triple, dest_filename: &Path) {
|
||||
use roc_reporting::report::{can_problem, RocDocAllocator, DEFAULT_PALETTE};
|
||||
use roc_reporting::type_error::type_problem;
|
||||
pub fn build(matches: &ArgMatches, run_after_build: bool) -> io::Result<()> {
|
||||
let filename = matches.value_of(FLAG_ROC_FILE).unwrap();
|
||||
let opt_level = if matches.is_present(FLAG_OPTIMIZE) {
|
||||
OptLevel::Optimize
|
||||
} else {
|
||||
OptLevel::Normal
|
||||
};
|
||||
let path = Path::new(filename);
|
||||
let src_dir = path.parent().unwrap().canonicalize().unwrap();
|
||||
|
||||
// Build the expr
|
||||
// Create the runtime
|
||||
let mut rt = Builder::new()
|
||||
.thread_name("roc")
|
||||
.threaded_scheduler()
|
||||
.enable_io()
|
||||
.build()
|
||||
.expect("Error spawning initial compiler thread."); // TODO make this error nicer.
|
||||
|
||||
// Spawn the root task
|
||||
let path = path.canonicalize().unwrap_or_else(|err| {
|
||||
use ErrorKind::*;
|
||||
|
||||
match err.kind() {
|
||||
NotFound => {
|
||||
match path.to_str() {
|
||||
Some(path_str) => println!("File not found: {}", path_str),
|
||||
None => println!("Malformed file path : {:?}", path),
|
||||
}
|
||||
|
||||
process::exit(1);
|
||||
}
|
||||
_ => {
|
||||
todo!("TODO Gracefully handle opening {:?} - {:?}", path, err);
|
||||
}
|
||||
}
|
||||
});
|
||||
let binary_path = rt
|
||||
.block_on(build_file(src_dir, path, opt_level))
|
||||
.expect("TODO gracefully handle block_on failing");
|
||||
|
||||
if run_after_build {
|
||||
// Run the compiled app
|
||||
rt.block_on(async {
|
||||
Command::new(binary_path)
|
||||
.spawn()
|
||||
.unwrap_or_else(|err| panic!("Failed to run app after building it: {:?}", err))
|
||||
.await
|
||||
.map_err(|_| {
|
||||
todo!("gracefully handle error after `app` spawned");
|
||||
})
|
||||
})
|
||||
.expect("TODO gracefully handle block_on failing");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn build_file(
|
||||
src_dir: PathBuf,
|
||||
filename: PathBuf,
|
||||
opt_level: OptLevel,
|
||||
) -> Result<PathBuf, LoadingProblem> {
|
||||
let compilation_start = SystemTime::now();
|
||||
let arena = Bump::new();
|
||||
|
||||
let (loc_expr, _output, can_problems, subs, var, constraint, home, interns) =
|
||||
uniq_expr_with(&arena, src, &ImMap::default());
|
||||
// Step 1: compile the app and generate the .o file
|
||||
let subs_by_module = MutMap::default();
|
||||
|
||||
let mut type_problems = Vec::new();
|
||||
let (content, mut subs) = infer_expr(subs, &mut type_problems, &constraint, var);
|
||||
// Release builds use uniqueness optimizations
|
||||
let stdlib = match opt_level {
|
||||
OptLevel::Normal => roc_builtins::std::standard_stdlib(),
|
||||
OptLevel::Optimize => roc_builtins::unique::uniq_stdlib(),
|
||||
};
|
||||
let loaded = roc_load::file::load(&stdlib, src_dir, filename.clone(), subs_by_module).await?;
|
||||
let dest_filename = filename.with_extension("o");
|
||||
|
||||
gen(
|
||||
&arena,
|
||||
loaded,
|
||||
filename,
|
||||
Triple::host(),
|
||||
&dest_filename,
|
||||
opt_level,
|
||||
);
|
||||
|
||||
let compilation_end = compilation_start.elapsed().unwrap();
|
||||
|
||||
println!(
|
||||
"Finished compilation and code gen in {} ms\n",
|
||||
compilation_end.as_millis()
|
||||
);
|
||||
|
||||
let cwd = dest_filename.parent().unwrap();
|
||||
let lib_path = dest_filename.with_file_name("libroc_app.a");
|
||||
|
||||
// Step 2: turn the .o file into a .a static library
|
||||
Command::new("ar") // TODO on Windows, use `link`
|
||||
.args(&[
|
||||
"rcs",
|
||||
lib_path.to_str().unwrap(),
|
||||
dest_filename.to_str().unwrap(),
|
||||
])
|
||||
.spawn()
|
||||
.map_err(|_| {
|
||||
todo!("gracefully handle `ar` failing to spawn.");
|
||||
})?
|
||||
.await
|
||||
.map_err(|_| {
|
||||
todo!("gracefully handle error after `ar` spawned");
|
||||
})?;
|
||||
|
||||
// Step 3: have rustc compile the host and link in the .a file
|
||||
let binary_path = cwd.join("app");
|
||||
|
||||
Command::new("rustc")
|
||||
.args(&[
|
||||
"-L",
|
||||
".",
|
||||
"--crate-type",
|
||||
"bin",
|
||||
"host.rs",
|
||||
"-o",
|
||||
binary_path.as_path().to_str().unwrap(),
|
||||
])
|
||||
.current_dir(cwd)
|
||||
.spawn()
|
||||
.map_err(|_| {
|
||||
todo!("gracefully handle `rustc` failing to spawn.");
|
||||
})?
|
||||
.await
|
||||
.map_err(|_| {
|
||||
todo!("gracefully handle error after `rustc` spawned");
|
||||
})?;
|
||||
|
||||
Ok(binary_path)
|
||||
}
|
||||
|
||||
fn gen(
|
||||
arena: &Bump,
|
||||
loaded: LoadedModule,
|
||||
filename: PathBuf,
|
||||
target: Triple,
|
||||
dest_filename: &Path,
|
||||
opt_level: OptLevel,
|
||||
) {
|
||||
use roc_reporting::report::{can_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE};
|
||||
|
||||
let src = loaded.src;
|
||||
let home = loaded.module_id;
|
||||
let src_lines: Vec<&str> = src.split('\n').collect();
|
||||
let palette = DEFAULT_PALETTE;
|
||||
|
||||
// Report parsing and canonicalization problems
|
||||
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
|
||||
let alloc = RocDocAllocator::new(&src_lines, home, &loaded.interns);
|
||||
|
||||
for problem in can_problems.into_iter() {
|
||||
for problem in loaded.can_problems.into_iter() {
|
||||
let report = can_problem(&alloc, filename.clone(), problem);
|
||||
let mut buf = String::new();
|
||||
|
||||
|
@ -129,7 +247,7 @@ fn gen(filename: PathBuf, src: &str, target: Triple, dest_filename: &Path) {
|
|||
println!("\n{}\n", buf);
|
||||
}
|
||||
|
||||
for problem in type_problems.into_iter() {
|
||||
for problem in loaded.type_problems.into_iter() {
|
||||
let report = type_problem(&alloc, filename.clone(), problem);
|
||||
let mut buf = String::new();
|
||||
|
||||
|
@ -138,6 +256,63 @@ fn gen(filename: PathBuf, src: &str, target: Triple, dest_filename: &Path) {
|
|||
println!("\n{}\n", buf);
|
||||
}
|
||||
|
||||
// Look up the types and expressions of the `provided` values
|
||||
|
||||
// TODO instead of hardcoding this to `main`, use the `provided` list and gen all of them.
|
||||
let ident_ids = loaded.interns.all_ident_ids.get(&home).unwrap();
|
||||
let main_ident_id = *ident_ids.get_id(&"main".into()).unwrap_or_else(|| {
|
||||
todo!("TODO gracefully handle the case where `main` wasn't declared in the app")
|
||||
});
|
||||
let main_symbol = Symbol::new(home, main_ident_id);
|
||||
let mut main_var = None;
|
||||
let mut main_expr = None;
|
||||
|
||||
for (symbol, var) in loaded.exposed_vars_by_symbol {
|
||||
if symbol == main_symbol {
|
||||
main_var = Some(var);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// We use a loop label here so we can break all the way out of a nested
|
||||
// loop inside DeclareRec if we find the expr there.
|
||||
//
|
||||
// https://doc.rust-lang.org/1.30.0/book/first-edition/loops.html#loop-labels
|
||||
'find_expr: for decl in loaded.declarations {
|
||||
use roc_can::def::Declaration::*;
|
||||
|
||||
match decl {
|
||||
Declare(def) => {
|
||||
if def.pattern_vars.contains_key(&main_symbol) {
|
||||
main_expr = Some(def.loc_expr);
|
||||
|
||||
break 'find_expr;
|
||||
}
|
||||
}
|
||||
|
||||
DeclareRec(defs) => {
|
||||
for def in defs {
|
||||
if def.pattern_vars.contains_key(&main_symbol) {
|
||||
main_expr = Some(def.loc_expr);
|
||||
|
||||
break 'find_expr;
|
||||
}
|
||||
}
|
||||
}
|
||||
InvalidCycle(_, _) => {}
|
||||
}
|
||||
}
|
||||
|
||||
let loc_expr = main_expr.unwrap_or_else(|| {
|
||||
panic!("TODO gracefully handle the case where `main` was declared but not exposed")
|
||||
});
|
||||
let mut subs = loaded.solved.into_inner();
|
||||
let content = match main_var {
|
||||
Some(var) => subs.get_without_compacting(var).content,
|
||||
None => todo!("TODO gracefully handle the case where `main` was declared but not exposed"),
|
||||
};
|
||||
|
||||
// Generate the binary
|
||||
|
||||
let context = Context::create();
|
||||
|
@ -145,7 +320,7 @@ fn gen(filename: PathBuf, src: &str, target: Triple, dest_filename: &Path) {
|
|||
let builder = context.create_builder();
|
||||
let fpm = PassManager::create(&module);
|
||||
|
||||
roc_gen::llvm::build::add_passes(&fpm);
|
||||
roc_gen::llvm::build::add_passes(&fpm, opt_level);
|
||||
|
||||
fpm.initialize();
|
||||
|
||||
|
@ -160,14 +335,14 @@ fn gen(filename: PathBuf, src: &str, target: Triple, dest_filename: &Path) {
|
|||
|
||||
let main_fn_type =
|
||||
basic_type_from_layout(&arena, &context, &layout, ptr_bytes).fn_type(&[], false);
|
||||
let main_fn_name = "$Test.main";
|
||||
let main_fn_name = "$main";
|
||||
|
||||
// Compile and add all the Procs before adding main
|
||||
let mut env = roc_gen::llvm::build::Env {
|
||||
arena: &arena,
|
||||
builder: &builder,
|
||||
context: &context,
|
||||
interns,
|
||||
interns: loaded.interns,
|
||||
module: arena.alloc(module),
|
||||
ptr_bytes,
|
||||
};
|
||||
|
@ -271,12 +446,16 @@ fn gen(filename: PathBuf, src: &str, target: Triple, dest_filename: &Path) {
|
|||
|
||||
"x86-64"
|
||||
}
|
||||
Architecture::Arm(_) => {
|
||||
Architecture::Arm(_) if cfg!(feature = "target-arm") => {
|
||||
// NOTE: why not enable arm and wasm by default?
|
||||
//
|
||||
// We had some trouble getting them to link properly. This may be resolved in the
|
||||
// future, or maybe it was just some weird configuration on one machine.
|
||||
Target::initialize_arm(&InitializationConfig::default());
|
||||
|
||||
"arm"
|
||||
}
|
||||
Architecture::Wasm32 => {
|
||||
Architecture::Wasm32 if cfg!(feature = "target-webassembly") => {
|
||||
Target::initialize_webassembly(&InitializationConfig::default());
|
||||
|
||||
"wasm32"
|
||||
|
|
|
@ -1,28 +1,278 @@
|
|||
use bumpalo::Bump;
|
||||
use inkwell::context::Context;
|
||||
use inkwell::execution_engine::JitFunction;
|
||||
use inkwell::passes::PassManager;
|
||||
use inkwell::types::BasicType;
|
||||
use inkwell::OptimizationLevel;
|
||||
use roc_builtins::unique::uniq_stdlib;
|
||||
use roc_can::constraint::Constraint;
|
||||
use roc_can::env::Env;
|
||||
use roc_can::expected::Expected;
|
||||
use roc_can::expr::{canonicalize_expr, Expr, Output};
|
||||
use roc_can::expr::{canonicalize_expr, Output};
|
||||
use roc_can::operator;
|
||||
use roc_can::scope::Scope;
|
||||
use roc_collections::all::{ImMap, ImSet, MutMap, SendMap, SendSet};
|
||||
use roc_constrain::expr::constrain_expr;
|
||||
use roc_constrain::module::{constrain_imported_values, load_builtin_aliases, Import};
|
||||
use roc_gen::llvm::build::{build_proc, build_proc_header, OptLevel};
|
||||
use roc_gen::llvm::convert::basic_type_from_layout;
|
||||
use roc_module::ident::Ident;
|
||||
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol};
|
||||
use roc_mono::expr::Procs;
|
||||
use roc_mono::layout::Layout;
|
||||
use roc_parse::ast::{self, Attempting};
|
||||
use roc_parse::blankspace::space0_before;
|
||||
use roc_parse::parser::{loc, Fail, Parser, State};
|
||||
use roc_problem::can::Problem;
|
||||
use roc_region::all::{Located, Region};
|
||||
use roc_solve::solve;
|
||||
use roc_types::pretty_print::{content_to_string, name_all_type_vars};
|
||||
use roc_types::subs::{Content, Subs, VarStore, Variable};
|
||||
use roc_types::types::Type;
|
||||
use std::hash::Hash;
|
||||
use std::io::{self, Write};
|
||||
use std::path::PathBuf;
|
||||
use target_lexicon::Triple;
|
||||
|
||||
pub fn test_home() -> ModuleId {
|
||||
ModuleIds::default().get_or_insert(&"Test".into())
|
||||
pub fn main() -> io::Result<()> {
|
||||
use std::io::BufRead;
|
||||
|
||||
println!(
|
||||
"\n The rockin’ \u{001b}[36mroc repl\u{001b}[0m\n\u{001b}[35m────────────────────────\u{001b}[0m\n\n{}",
|
||||
WELCOME_MESSAGE
|
||||
);
|
||||
|
||||
// Loop
|
||||
|
||||
loop {
|
||||
print!("\n\u{001b}[36m▶\u{001b}[0m ");
|
||||
|
||||
io::stdout().flush().unwrap();
|
||||
|
||||
let stdin = io::stdin();
|
||||
let line = stdin
|
||||
.lock()
|
||||
.lines()
|
||||
.next()
|
||||
.expect("there was no next line")
|
||||
.expect("the line could not be read");
|
||||
|
||||
match line.trim() {
|
||||
":help" => {
|
||||
println!("Use :exit to exit.");
|
||||
}
|
||||
"" => {
|
||||
println!("\n{}", WELCOME_MESSAGE);
|
||||
}
|
||||
":exit" => {
|
||||
break;
|
||||
}
|
||||
line => {
|
||||
let (answer, answer_type) = gen(line, Triple::host(), OptLevel::Normal);
|
||||
|
||||
println!("\n{} \u{001b}[35m:\u{001b}[0m {}", answer, answer_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const WELCOME_MESSAGE: &str =
|
||||
"Enter an expression, or :help for a list of commands, or :exit to exit.";
|
||||
|
||||
pub fn repl_home() -> ModuleId {
|
||||
ModuleIds::default().get_or_insert(&"REPL".into())
|
||||
}
|
||||
|
||||
pub fn gen(src: &str, target: Triple, opt_level: OptLevel) -> (String, String) {
|
||||
use roc_reporting::report::{can_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE};
|
||||
|
||||
// Look up the types and expressions of the `provided` values
|
||||
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
|
||||
let arena = Bump::new();
|
||||
let CanExprOut {
|
||||
loc_expr,
|
||||
var_store,
|
||||
var,
|
||||
constraint,
|
||||
home,
|
||||
interns,
|
||||
problems: can_problems,
|
||||
..
|
||||
} = can_expr(src);
|
||||
let subs = Subs::new(var_store.into());
|
||||
let mut type_problems = Vec::new();
|
||||
let (content, mut subs) = infer_expr(subs, &mut type_problems, &constraint, var);
|
||||
|
||||
// Report problems
|
||||
let src_lines: Vec<&str> = src.split('\n').collect();
|
||||
let palette = DEFAULT_PALETTE;
|
||||
|
||||
// Report parsing and canonicalization problems
|
||||
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
|
||||
|
||||
// Used for reporting where an error came from.
|
||||
//
|
||||
// TODO: maybe Reporting should have this be an Option?
|
||||
let path = PathBuf::new();
|
||||
|
||||
for problem in can_problems.into_iter() {
|
||||
let report = can_problem(&alloc, path.clone(), problem);
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
||||
println!("\n{}\n", buf);
|
||||
}
|
||||
|
||||
for problem in type_problems.into_iter() {
|
||||
let report = type_problem(&alloc, path.clone(), problem);
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
||||
println!("\n{}\n", buf);
|
||||
}
|
||||
|
||||
let context = Context::create();
|
||||
let module = roc_gen::llvm::build::module_from_builtins(&context, "app");
|
||||
let builder = context.create_builder();
|
||||
let fpm = PassManager::create(&module);
|
||||
|
||||
roc_gen::llvm::build::add_passes(&fpm, opt_level);
|
||||
|
||||
fpm.initialize();
|
||||
|
||||
// pretty-print the expr type string for later.
|
||||
name_all_type_vars(var, &mut subs);
|
||||
|
||||
let expr_type_str = content_to_string(content.clone(), &subs, home, &interns);
|
||||
|
||||
// Compute main_fn_type before moving subs to Env
|
||||
let layout = Layout::from_content(&arena, content, &subs, ptr_bytes).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}",
|
||||
err, subs
|
||||
)
|
||||
});
|
||||
let execution_engine = module
|
||||
.create_jit_execution_engine(OptimizationLevel::None)
|
||||
.expect("Error creating JIT execution engine for test");
|
||||
|
||||
let main_fn_type =
|
||||
basic_type_from_layout(&arena, &context, &layout, ptr_bytes).fn_type(&[], false);
|
||||
let main_fn_name = "$Test.main";
|
||||
|
||||
// Compile and add all the Procs before adding main
|
||||
let mut env = roc_gen::llvm::build::Env {
|
||||
arena: &arena,
|
||||
builder: &builder,
|
||||
context: &context,
|
||||
interns,
|
||||
module: arena.alloc(module),
|
||||
ptr_bytes,
|
||||
};
|
||||
let mut procs = Procs::default();
|
||||
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
|
||||
|
||||
// Populate Procs and get the low-level Expr from the canonical Expr
|
||||
let mut mono_problems = Vec::new();
|
||||
let main_body = roc_mono::expr::Expr::new(
|
||||
&arena,
|
||||
&mut subs,
|
||||
&mut mono_problems,
|
||||
loc_expr.value,
|
||||
&mut procs,
|
||||
home,
|
||||
&mut ident_ids,
|
||||
ptr_bytes,
|
||||
);
|
||||
|
||||
// Put this module's ident_ids back in the interns, so we can use them in Env.
|
||||
env.interns.all_ident_ids.insert(home, ident_ids);
|
||||
|
||||
let mut headers = Vec::with_capacity(procs.len());
|
||||
|
||||
// Add all the Proc headers to the module.
|
||||
// We have to do this in a separate pass first,
|
||||
// because their bodies may reference each other.
|
||||
for (symbol, opt_proc) in procs.as_map().into_iter() {
|
||||
if let Some(proc) = opt_proc {
|
||||
let (fn_val, arg_basic_types) = build_proc_header(&env, symbol, &proc);
|
||||
|
||||
headers.push((proc, fn_val, arg_basic_types));
|
||||
}
|
||||
}
|
||||
|
||||
// Build each proc using its header info.
|
||||
for (proc, fn_val, arg_basic_types) in headers {
|
||||
// NOTE: This is here to be uncommented in case verification fails.
|
||||
// (This approach means we don't have to defensively clone name here.)
|
||||
//
|
||||
// println!("\n\nBuilding and then verifying function {}\n\n", name);
|
||||
build_proc(&env, proc, &procs, fn_val, arg_basic_types);
|
||||
|
||||
if fn_val.verify(true) {
|
||||
fpm.run_on(&fn_val);
|
||||
} else {
|
||||
// NOTE: If this fails, uncomment the above println to debug.
|
||||
panic!(
|
||||
"Non-main function failed LLVM verification. Uncomment the above println to debug!"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Add main to the module.
|
||||
let main_fn = env.module.add_function(main_fn_name, main_fn_type, None);
|
||||
let cc =
|
||||
roc_gen::llvm::build::get_call_conventions(target.default_calling_convention().unwrap());
|
||||
|
||||
main_fn.set_call_conventions(cc);
|
||||
|
||||
// Add main's body
|
||||
let basic_block = context.append_basic_block(main_fn, "entry");
|
||||
|
||||
builder.position_at_end(basic_block);
|
||||
|
||||
let ret = roc_gen::llvm::build::build_expr(
|
||||
&env,
|
||||
&ImMap::default(),
|
||||
main_fn,
|
||||
&main_body,
|
||||
&Procs::default(),
|
||||
);
|
||||
|
||||
builder.build_return(Some(&ret));
|
||||
|
||||
// Uncomment this to see the module's un-optimized LLVM instruction output:
|
||||
// env.module.print_to_stderr();
|
||||
|
||||
if main_fn.verify(true) {
|
||||
fpm.run_on(&main_fn);
|
||||
} else {
|
||||
panic!("Function {} failed LLVM verification.", main_fn_name);
|
||||
}
|
||||
|
||||
// Verify the module
|
||||
if let Err(errors) = env.module.verify() {
|
||||
panic!("Errors defining module: {:?}", errors);
|
||||
}
|
||||
|
||||
// Uncomment this to see the module's optimized LLVM instruction output:
|
||||
// env.module.print_to_stderr();
|
||||
|
||||
unsafe {
|
||||
let main: JitFunction<
|
||||
unsafe extern "C" fn() -> i64, /* TODO have this return Str, and in the generated code make sure to call the appropriate string conversion function on the return val based on its type! */
|
||||
> = execution_engine
|
||||
.get_function(main_fn_name)
|
||||
.ok()
|
||||
.ok_or(format!("Unable to JIT compile `{}`", main_fn_name))
|
||||
.expect("errored");
|
||||
|
||||
(format!("{}", main.call()), expr_type_str)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn infer_expr(
|
||||
|
@ -57,13 +307,13 @@ pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast
|
|||
}
|
||||
|
||||
pub fn can_expr(expr_str: &str) -> CanExprOut {
|
||||
can_expr_with(&Bump::new(), test_home(), expr_str)
|
||||
can_expr_with(&Bump::new(), repl_home(), expr_str)
|
||||
}
|
||||
|
||||
pub fn uniq_expr(
|
||||
expr_str: &str,
|
||||
) -> (
|
||||
Located<Expr>,
|
||||
Located<roc_can::expr::Expr>,
|
||||
Output,
|
||||
Vec<Problem>,
|
||||
Subs,
|
||||
|
@ -82,7 +332,7 @@ pub fn uniq_expr_with(
|
|||
expr_str: &str,
|
||||
declared_idents: &ImMap<Ident, (Symbol, Region)>,
|
||||
) -> (
|
||||
Located<Expr>,
|
||||
Located<roc_can::expr::Expr>,
|
||||
Output,
|
||||
Vec<Problem>,
|
||||
Subs,
|
||||
|
@ -91,7 +341,7 @@ pub fn uniq_expr_with(
|
|||
ModuleId,
|
||||
Interns,
|
||||
) {
|
||||
let home = test_home();
|
||||
let home = repl_home();
|
||||
let CanExprOut {
|
||||
loc_expr,
|
||||
output,
|
||||
|
@ -145,7 +395,7 @@ pub fn uniq_expr_with(
|
|||
}
|
||||
|
||||
pub struct CanExprOut {
|
||||
pub loc_expr: Located<Expr>,
|
||||
pub loc_expr: Located<roc_can::expr::Expr>,
|
||||
pub output: Output,
|
||||
pub problems: Vec<Problem>,
|
||||
pub home: ModuleId,
|
104
cli/tests/cli_run.rs
Normal file
104
cli/tests/cli_run.rs
Normal file
|
@ -0,0 +1,104 @@
|
|||
#[macro_use]
|
||||
extern crate pretty_assertions;
|
||||
|
||||
extern crate bumpalo;
|
||||
extern crate inlinable_string;
|
||||
extern crate roc_collections;
|
||||
extern crate roc_load;
|
||||
extern crate roc_module;
|
||||
|
||||
#[cfg(test)]
|
||||
mod cli_run {
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use std::process::{Command, ExitStatus};
|
||||
|
||||
// HELPERS
|
||||
|
||||
pub struct Out {
|
||||
pub stdout: String,
|
||||
pub stderr: String,
|
||||
pub status: ExitStatus,
|
||||
}
|
||||
|
||||
pub fn path_to_roc_binary() -> PathBuf {
|
||||
// Adapted from https://github.com/volta-cli/volta/blob/cefdf7436a15af3ce3a38b8fe53bb0cfdb37d3dd/tests/acceptance/support/sandbox.rs#L680 - BSD-2-Clause licensed
|
||||
let mut path = env::var_os("CARGO_BIN_PATH")
|
||||
.map(PathBuf::from)
|
||||
.or_else(|| {
|
||||
env::current_exe().ok().map(|mut path| {
|
||||
path.pop();
|
||||
if path.ends_with("deps") { path.pop();
|
||||
}
|
||||
path
|
||||
})
|
||||
})
|
||||
.unwrap_or_else(|| panic!("CARGO_BIN_PATH wasn't set, and couldn't be inferred from context. Can't run CLI tests."));
|
||||
|
||||
path.push("roc");
|
||||
|
||||
path
|
||||
}
|
||||
|
||||
pub fn run_roc(args: &[&str]) -> Out {
|
||||
let mut cmd = Command::new(path_to_roc_binary());
|
||||
|
||||
for arg in args {
|
||||
cmd.arg(arg);
|
||||
}
|
||||
|
||||
let output = cmd
|
||||
.output()
|
||||
.expect("failed to execute compiled `roc` binary in CLI test");
|
||||
|
||||
Out {
|
||||
stdout: String::from_utf8(output.stdout).unwrap(),
|
||||
stderr: String::from_utf8(output.stderr).unwrap(),
|
||||
status: output.status,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn example_dir(dir_name: &str) -> PathBuf {
|
||||
let mut path = env::current_exe().ok().unwrap();
|
||||
|
||||
// Get rid of the filename in target/debug/deps/cli_run-99c65e4e9a1fbd06
|
||||
path.pop();
|
||||
|
||||
// If we're in deps/ get rid of deps/ in target/debug/deps/
|
||||
if path.ends_with("deps") {
|
||||
path.pop();
|
||||
}
|
||||
|
||||
// Get rid of target/debug/ so we're back at the project root
|
||||
path.pop();
|
||||
path.pop();
|
||||
|
||||
// Descend into examples/{dir_name}
|
||||
path.push("examples");
|
||||
path.push(dir_name);
|
||||
|
||||
path
|
||||
}
|
||||
|
||||
pub fn example_file(dir_name: &str, file_name: &str) -> PathBuf {
|
||||
let mut path = example_dir(dir_name);
|
||||
|
||||
path.push(file_name);
|
||||
|
||||
path
|
||||
}
|
||||
|
||||
// TESTS
|
||||
|
||||
#[test]
|
||||
fn run_hello_world() {
|
||||
let out = run_roc(&[
|
||||
"run",
|
||||
example_file("hello-world", "Hello.roc").to_str().unwrap(),
|
||||
]);
|
||||
|
||||
assert_eq!(&out.stderr, "");
|
||||
assert!(&out.stdout.ends_with("Hello, World!\n"));
|
||||
assert!(out.status.success());
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ roc_types = { path = "../types" }
|
|||
roc_can = { path = "../can" }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1 "
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
|
|
|
@ -18,7 +18,7 @@ bumpalo = { version = "3.2", features = ["collections"] }
|
|||
inlinable_string = "0.1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1 "
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::env::Env;
|
||||
use crate::scope::Scope;
|
||||
use roc_collections::all::{MutSet, SendMap};
|
||||
use roc_collections::all::{MutMap, MutSet, SendMap};
|
||||
use roc_module::ident::Ident;
|
||||
use roc_module::ident::{Lowercase, TagName};
|
||||
use roc_module::symbol::Symbol;
|
||||
|
@ -292,9 +292,10 @@ fn can_annotation_help(
|
|||
|
||||
Record { fields, ext } => {
|
||||
let mut field_types = SendMap::default();
|
||||
let mut seen = MutMap::default();
|
||||
|
||||
for field in fields.iter() {
|
||||
can_assigned_field(
|
||||
let opt_field_name = can_assigned_field(
|
||||
env,
|
||||
&field.value,
|
||||
region,
|
||||
|
@ -305,6 +306,17 @@ fn can_annotation_help(
|
|||
&mut field_types,
|
||||
references,
|
||||
);
|
||||
|
||||
if let Some(added) = opt_field_name {
|
||||
if let Some(replaced_region) = seen.insert(added.clone(), field.region) {
|
||||
env.problem(roc_problem::can::Problem::DuplicateRecordFieldType {
|
||||
field_name: added.clone(),
|
||||
field_region: field.region,
|
||||
record_region: region,
|
||||
replaced_region,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let ext_type = match ext {
|
||||
|
@ -325,9 +337,10 @@ fn can_annotation_help(
|
|||
}
|
||||
TagUnion { tags, ext } => {
|
||||
let mut tag_types = Vec::with_capacity(tags.len());
|
||||
let mut seen = MutMap::default();
|
||||
|
||||
for tag in tags.iter() {
|
||||
can_tag(
|
||||
let opt_tag_name = can_tag(
|
||||
env,
|
||||
&tag.value,
|
||||
region,
|
||||
|
@ -338,6 +351,17 @@ fn can_annotation_help(
|
|||
&mut tag_types,
|
||||
references,
|
||||
);
|
||||
|
||||
if let Some(added) = opt_tag_name {
|
||||
if let Some(replaced_region) = seen.insert(added.clone(), tag.region) {
|
||||
env.problem(roc_problem::can::Problem::DuplicateTag {
|
||||
tag_name: added.clone(),
|
||||
tag_region: tag.region,
|
||||
tag_union_region: region,
|
||||
replaced_region,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let ext_type = match ext {
|
||||
|
@ -388,7 +412,7 @@ fn can_assigned_field<'a>(
|
|||
local_aliases: &mut SendMap<Symbol, Alias>,
|
||||
field_types: &mut SendMap<Lowercase, Type>,
|
||||
references: &mut MutSet<Symbol>,
|
||||
) {
|
||||
) -> Option<Lowercase> {
|
||||
use roc_parse::ast::AssignedField::*;
|
||||
|
||||
match field {
|
||||
|
@ -396,16 +420,18 @@ fn can_assigned_field<'a>(
|
|||
let field_type = can_annotation_help(
|
||||
env,
|
||||
&annotation.value,
|
||||
region,
|
||||
annotation.region,
|
||||
scope,
|
||||
var_store,
|
||||
introduced_variables,
|
||||
local_aliases,
|
||||
references,
|
||||
);
|
||||
let label = Lowercase::from(field_name.value);
|
||||
|
||||
field_types.insert(label, field_type);
|
||||
let label = Lowercase::from(field_name.value);
|
||||
field_types.insert(label.clone(), field_type);
|
||||
|
||||
Some(label)
|
||||
}
|
||||
LabelOnly(loc_field_name) => {
|
||||
// Interpret { a, b } as { a : a, b : b }
|
||||
|
@ -420,7 +446,9 @@ fn can_assigned_field<'a>(
|
|||
}
|
||||
};
|
||||
|
||||
field_types.insert(field_name, field_type);
|
||||
field_types.insert(field_name.clone(), field_type);
|
||||
|
||||
Some(field_name)
|
||||
}
|
||||
SpaceBefore(nested, _) | SpaceAfter(nested, _) => can_assigned_field(
|
||||
env,
|
||||
|
@ -433,7 +461,7 @@ fn can_assigned_field<'a>(
|
|||
field_types,
|
||||
references,
|
||||
),
|
||||
Malformed(_) => (),
|
||||
Malformed(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -449,7 +477,7 @@ fn can_tag<'a>(
|
|||
local_aliases: &mut SendMap<Symbol, Alias>,
|
||||
tag_types: &mut Vec<(TagName, Vec<Type>)>,
|
||||
references: &mut MutSet<Symbol>,
|
||||
) {
|
||||
) -> Option<TagName> {
|
||||
match tag {
|
||||
Tag::Global { name, args } => {
|
||||
let name = name.value.into();
|
||||
|
@ -470,7 +498,10 @@ fn can_tag<'a>(
|
|||
arg_types.push(ann);
|
||||
}
|
||||
|
||||
tag_types.push((TagName::Global(name), arg_types));
|
||||
let tag_name = TagName::Global(name);
|
||||
tag_types.push((tag_name.clone(), arg_types));
|
||||
|
||||
Some(tag_name)
|
||||
}
|
||||
Tag::Private { name, args } => {
|
||||
let ident_id = env.ident_ids.get_or_insert(&name.value.into());
|
||||
|
@ -492,7 +523,10 @@ fn can_tag<'a>(
|
|||
arg_types.push(ann);
|
||||
}
|
||||
|
||||
tag_types.push((TagName::Private(symbol), arg_types));
|
||||
let tag_name = TagName::Private(symbol);
|
||||
tag_types.push((tag_name.clone(), arg_types));
|
||||
|
||||
Some(tag_name)
|
||||
}
|
||||
Tag::SpaceBefore(nested, _) | Tag::SpaceAfter(nested, _) => can_tag(
|
||||
env,
|
||||
|
@ -505,6 +539,6 @@ fn can_tag<'a>(
|
|||
tag_types,
|
||||
references,
|
||||
),
|
||||
Tag::Malformed(_) => (),
|
||||
Tag::Malformed(_) => None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,24 +32,32 @@ impl Constraint {
|
|||
match self {
|
||||
True | SaveTheEnvironment => {}
|
||||
|
||||
Eq(typ, expected, _, _) => {
|
||||
expected
|
||||
.get_type_mut_ref()
|
||||
.instantiate_aliases(aliases, var_store, introduced);
|
||||
typ.instantiate_aliases(aliases, var_store, introduced);
|
||||
Eq(typ, expected, _, region) => {
|
||||
let expected_region = expected.get_annotation_region().unwrap_or(*region);
|
||||
expected.get_type_mut_ref().instantiate_aliases(
|
||||
expected_region,
|
||||
aliases,
|
||||
var_store,
|
||||
introduced,
|
||||
);
|
||||
typ.instantiate_aliases(*region, aliases, var_store, introduced);
|
||||
}
|
||||
|
||||
Lookup(_, expected, _) => {
|
||||
expected
|
||||
.get_type_mut_ref()
|
||||
.instantiate_aliases(aliases, var_store, introduced);
|
||||
Lookup(_, expected, region) => {
|
||||
let expected_region = expected.get_annotation_region().unwrap_or(*region);
|
||||
expected.get_type_mut_ref().instantiate_aliases(
|
||||
expected_region,
|
||||
aliases,
|
||||
var_store,
|
||||
introduced,
|
||||
);
|
||||
}
|
||||
|
||||
Pattern(_, _, typ, pexpected) => {
|
||||
Pattern(region, _, typ, pexpected) => {
|
||||
pexpected
|
||||
.get_type_mut_ref()
|
||||
.instantiate_aliases(aliases, var_store, introduced);
|
||||
typ.instantiate_aliases(aliases, var_store, introduced);
|
||||
.instantiate_aliases(*region, aliases, var_store, introduced);
|
||||
typ.instantiate_aliases(*region, aliases, var_store, introduced);
|
||||
}
|
||||
|
||||
And(nested) => {
|
||||
|
@ -65,8 +73,8 @@ impl Constraint {
|
|||
}
|
||||
|
||||
let mut introduced = ImSet::default();
|
||||
for Located { value: typ, .. } in letcon.def_types.iter_mut() {
|
||||
typ.instantiate_aliases(&new_aliases, var_store, &mut introduced);
|
||||
for Located { region, value: typ } in letcon.def_types.iter_mut() {
|
||||
typ.instantiate_aliases(*region, &new_aliases, var_store, &mut introduced);
|
||||
}
|
||||
|
||||
letcon.defs_constraint.instantiate_aliases_help(
|
||||
|
|
|
@ -201,6 +201,7 @@ pub fn canonicalize_defs<'a>(
|
|||
let mut can_vars: Vec<Located<(Lowercase, Variable)>> =
|
||||
Vec::with_capacity(vars.len());
|
||||
|
||||
let mut is_phantom = false;
|
||||
for loc_lowercase in vars {
|
||||
if let Some(var) = can_ann
|
||||
.introduced_variables
|
||||
|
@ -212,12 +213,31 @@ pub fn canonicalize_defs<'a>(
|
|||
region: loc_lowercase.region,
|
||||
});
|
||||
} else {
|
||||
panic!("TODO handle phantom type variables, they are not allowed!\nThe {:?} variable in the definition of {:?} gives trouble", loc_lowercase, symbol);
|
||||
is_phantom = true;
|
||||
|
||||
env.problems.push(Problem::PhantomTypeArgument {
|
||||
alias: symbol,
|
||||
variable_region: loc_lowercase.region,
|
||||
variable_name: loc_lowercase.value.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if is_phantom {
|
||||
// Bail out
|
||||
continue;
|
||||
}
|
||||
|
||||
if can_ann.typ.contains_symbol(symbol) {
|
||||
make_tag_union_recursive(symbol, &mut can_ann.typ, var_store);
|
||||
make_tag_union_recursive(
|
||||
env,
|
||||
symbol,
|
||||
name.region,
|
||||
vec![],
|
||||
&mut can_ann.typ,
|
||||
var_store,
|
||||
&mut false,
|
||||
);
|
||||
}
|
||||
|
||||
let alias = roc_types::types::Alias {
|
||||
|
@ -231,7 +251,7 @@ pub fn canonicalize_defs<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
correct_mutual_recursive_type_alias(&mut aliases, &var_store);
|
||||
correct_mutual_recursive_type_alias(env, &mut aliases, &var_store);
|
||||
|
||||
// Now that we have the scope completely assembled, and shadowing resolved,
|
||||
// we're ready to canonicalize any body exprs.
|
||||
|
@ -836,7 +856,11 @@ fn canonicalize_pending_def<'a>(
|
|||
region: loc_lowercase.region,
|
||||
});
|
||||
} else {
|
||||
panic!("TODO handle phantom type variables, they are not allowed!");
|
||||
env.problems.push(Problem::PhantomTypeArgument {
|
||||
alias: symbol,
|
||||
variable_region: loc_lowercase.region,
|
||||
variable_name: loc_lowercase.value.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -852,7 +876,9 @@ fn canonicalize_pending_def<'a>(
|
|||
|
||||
scope.add_alias(symbol, name.region, can_vars, rec_type_union);
|
||||
} else {
|
||||
panic!("recursion in type alias that is not behind a Tag");
|
||||
env.problems
|
||||
.push(Problem::CyclicAlias(symbol, name.region, vec![]));
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1385,7 +1411,11 @@ fn pending_typed_body<'a>(
|
|||
}
|
||||
|
||||
/// Make aliases recursive
|
||||
fn correct_mutual_recursive_type_alias(aliases: &mut SendMap<Symbol, Alias>, var_store: &VarStore) {
|
||||
fn correct_mutual_recursive_type_alias<'a>(
|
||||
env: &mut Env<'a>,
|
||||
aliases: &mut SendMap<Symbol, Alias>,
|
||||
var_store: &VarStore,
|
||||
) {
|
||||
let mut symbols_introduced = ImSet::default();
|
||||
|
||||
for (key, _) in aliases.iter() {
|
||||
|
@ -1434,11 +1464,17 @@ fn correct_mutual_recursive_type_alias(aliases: &mut SendMap<Symbol, Alias>, var
|
|||
&mutually_recursive_symbols,
|
||||
all_successors_without_self,
|
||||
) {
|
||||
// make sure we report only one error for the cycle, not an error for every
|
||||
// alias in the cycle.
|
||||
let mut can_still_report_error = true;
|
||||
|
||||
// TODO use itertools to be more efficient here
|
||||
for rec in &cycle {
|
||||
let mut to_instantiate = ImMap::default();
|
||||
let mut others = Vec::with_capacity(cycle.len() - 1);
|
||||
for other in &cycle {
|
||||
if rec != other {
|
||||
others.push(*other);
|
||||
if let Some(alias) = originals.get(other) {
|
||||
to_instantiate.insert(*other, alias.clone());
|
||||
}
|
||||
|
@ -1447,11 +1483,20 @@ fn correct_mutual_recursive_type_alias(aliases: &mut SendMap<Symbol, Alias>, var
|
|||
|
||||
if let Some(alias) = aliases.get_mut(rec) {
|
||||
alias.typ.instantiate_aliases(
|
||||
alias.region,
|
||||
&to_instantiate,
|
||||
var_store,
|
||||
&mut ImSet::default(),
|
||||
);
|
||||
make_tag_union_recursive(*rec, &mut alias.typ, var_store);
|
||||
make_tag_union_recursive(
|
||||
env,
|
||||
*rec,
|
||||
alias.region,
|
||||
others,
|
||||
&mut alias.typ,
|
||||
var_store,
|
||||
&mut can_still_report_error,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1459,14 +1504,41 @@ fn correct_mutual_recursive_type_alias(aliases: &mut SendMap<Symbol, Alias>, var
|
|||
}
|
||||
}
|
||||
|
||||
fn make_tag_union_recursive(symbol: Symbol, typ: &mut Type, var_store: &VarStore) {
|
||||
fn make_tag_union_recursive<'a>(
|
||||
env: &mut Env<'a>,
|
||||
symbol: Symbol,
|
||||
region: Region,
|
||||
others: Vec<Symbol>,
|
||||
typ: &mut Type,
|
||||
var_store: &VarStore,
|
||||
can_report_error: &mut bool,
|
||||
) {
|
||||
match typ {
|
||||
Type::TagUnion(tags, ext) => {
|
||||
let rec_var = var_store.fresh();
|
||||
*typ = Type::RecursiveTagUnion(rec_var, tags.to_vec(), ext.clone());
|
||||
typ.substitute_alias(symbol, &Type::Variable(rec_var));
|
||||
}
|
||||
Type::Alias(_, _, actual) => make_tag_union_recursive(symbol, actual, var_store),
|
||||
_ => panic!("recursion in type alias is not behind a Tag"),
|
||||
Type::Alias(_, _, actual) => make_tag_union_recursive(
|
||||
env,
|
||||
symbol,
|
||||
region,
|
||||
others,
|
||||
actual,
|
||||
var_store,
|
||||
can_report_error,
|
||||
),
|
||||
_ => {
|
||||
let problem = roc_types::types::Problem::CyclicAlias(symbol, region, others.clone());
|
||||
*typ = Type::Erroneous(problem);
|
||||
|
||||
// ensure cyclic error is only reported for one element of the cycle
|
||||
if *can_report_error {
|
||||
*can_report_error = false;
|
||||
|
||||
let problem = Problem::CyclicAlias(symbol, region, others);
|
||||
env.problems.push(problem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,6 +71,15 @@ impl<T> Expected<T> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_annotation_region(&self) -> Option<Region> {
|
||||
match self {
|
||||
Expected::FromAnnotation(_, _, AnnotationSource::TypedBody { region }, _) => {
|
||||
Some(*region)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn replace<U>(self, new: U) -> Expected<U> {
|
||||
match self {
|
||||
Expected::NoExpectation(_val) => Expected::NoExpectation(new),
|
||||
|
|
|
@ -193,7 +193,8 @@ pub fn canonicalize_expr<'a>(
|
|||
let (can_update, update_out) =
|
||||
canonicalize_expr(env, var_store, scope, loc_update.region, &loc_update.value);
|
||||
if let Var(symbol) = &can_update.value {
|
||||
let (can_fields, mut output) = canonicalize_fields(env, var_store, scope, fields);
|
||||
let (can_fields, mut output) =
|
||||
canonicalize_fields(env, var_store, scope, region, fields);
|
||||
|
||||
output.references = output.references.union(update_out.references);
|
||||
|
||||
|
@ -219,7 +220,8 @@ pub fn canonicalize_expr<'a>(
|
|||
if fields.is_empty() {
|
||||
(EmptyRecord, Output::default())
|
||||
} else {
|
||||
let (can_fields, output) = canonicalize_fields(env, var_store, scope, fields);
|
||||
let (can_fields, output) =
|
||||
canonicalize_fields(env, var_store, scope, region, fields);
|
||||
|
||||
(
|
||||
Record {
|
||||
|
@ -885,6 +887,7 @@ fn canonicalize_fields<'a>(
|
|||
env: &mut Env<'a>,
|
||||
var_store: &VarStore,
|
||||
scope: &mut Scope,
|
||||
region: Region,
|
||||
fields: &'a [Located<ast::AssignedField<'a, ast::Expr<'a>>>],
|
||||
) -> (SendMap<Lowercase, Field>, Output) {
|
||||
let mut can_fields = SendMap::default();
|
||||
|
@ -900,7 +903,16 @@ fn canonicalize_fields<'a>(
|
|||
loc_expr: Box::new(field_expr),
|
||||
};
|
||||
|
||||
can_fields.insert(label, field);
|
||||
let replaced = can_fields.insert(label.clone(), field);
|
||||
|
||||
if let Some(old) = replaced {
|
||||
env.problems.push(Problem::DuplicateRecordFieldValue {
|
||||
field_name: label,
|
||||
field_region: loc_field.region,
|
||||
record_region: region,
|
||||
replaced_region: old.region,
|
||||
});
|
||||
}
|
||||
|
||||
output.references = output.references.union(field_out.references);
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ roc_builtins = { path = "../builtins" }
|
|||
roc_uniq = { path = "../uniq" }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1 "
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
|
|
|
@ -277,9 +277,7 @@ fn to_type(solved_type: &SolvedType, free_vars: &mut FreeVars, var_store: &VarSt
|
|||
|
||||
Type::Alias(*symbol, type_variables, Box::new(actual))
|
||||
}
|
||||
Error => {
|
||||
panic!("TODO convert from SolvedType::Error to Type somehow");
|
||||
}
|
||||
Error => Type::Erroneous(roc_types::types::Problem::SolvedTypeError),
|
||||
Erroneous(problem) => Type::Erroneous(problem.clone()),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ bumpalo = { version = "3.2", features = ["collections"] }
|
|||
inlinable_string = "0.1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1 "
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
|
|
|
@ -43,7 +43,7 @@ target-lexicon = "0.10"
|
|||
[dev-dependencies]
|
||||
roc_can = { path = "../can" }
|
||||
roc_parse = { path = "../parse" }
|
||||
pretty_assertions = "0.5.1 "
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
|
|
|
@ -4,11 +4,11 @@ use inkwell::builder::Builder;
|
|||
use inkwell::context::Context;
|
||||
use inkwell::memory_buffer::MemoryBuffer;
|
||||
use inkwell::module::{Linkage, Module};
|
||||
use inkwell::passes::PassManager;
|
||||
use inkwell::passes::{PassManager, PassManagerBuilder};
|
||||
use inkwell::types::{BasicTypeEnum, IntType, StructType};
|
||||
use inkwell::values::BasicValueEnum::{self, *};
|
||||
use inkwell::values::{FunctionValue, IntValue, PointerValue, StructValue};
|
||||
use inkwell::{FloatPredicate, IntPredicate};
|
||||
use inkwell::{FloatPredicate, IntPredicate, OptimizationLevel};
|
||||
|
||||
use crate::llvm::convert::{
|
||||
basic_type_from_layout, collection_wrapper, empty_collection, get_fn_type, ptr_int,
|
||||
|
@ -31,6 +31,11 @@ const PRINT_FN_VERIFICATION_OUTPUT: bool = false;
|
|||
// TODO: experiment with different internal calling conventions, e.g. "fast"
|
||||
const DEFAULT_CALLING_CONVENTION: u32 = 0;
|
||||
|
||||
pub enum OptLevel {
|
||||
Normal,
|
||||
Optimize,
|
||||
}
|
||||
|
||||
type Scope<'a, 'ctx> = ImMap<Symbol, (Layout<'a>, PointerValue<'ctx>)>;
|
||||
|
||||
pub struct Env<'a, 'ctx, 'env> {
|
||||
|
@ -56,23 +61,41 @@ pub fn module_from_builtins<'ctx>(ctx: &'ctx Context, module_name: &str) -> Modu
|
|||
.unwrap_or_else(|err| panic!("Unable to import builtins bitcode. LLVM error: {:?}", err))
|
||||
}
|
||||
|
||||
pub fn add_passes(fpm: &PassManager<FunctionValue<'_>>) {
|
||||
pub fn add_passes(fpm: &PassManager<FunctionValue<'_>>, opt_level: OptLevel) {
|
||||
// tail-call elimination is always on
|
||||
fpm.add_instruction_combining_pass();
|
||||
fpm.add_tail_call_elimination_pass();
|
||||
|
||||
let pmb = PassManagerBuilder::create();
|
||||
|
||||
// Enable more optimizations when running cargo test --release
|
||||
if !cfg!(debug_assertions) {
|
||||
fpm.add_reassociate_pass();
|
||||
fpm.add_basic_alias_analysis_pass();
|
||||
fpm.add_promote_memory_to_register_pass();
|
||||
fpm.add_cfg_simplification_pass();
|
||||
fpm.add_gvn_pass();
|
||||
// TODO figure out why enabling any of these (even alone) causes LLVM to segfault
|
||||
// fpm.add_strip_dead_prototypes_pass();
|
||||
// fpm.add_dead_arg_elimination_pass();
|
||||
// fpm.add_function_inlining_pass();
|
||||
match opt_level {
|
||||
OptLevel::Normal => {
|
||||
pmb.set_optimization_level(OptimizationLevel::None);
|
||||
}
|
||||
OptLevel::Optimize => {
|
||||
// Default is O2, Aggressive is O3
|
||||
//
|
||||
// See https://llvm.org/doxygen/CodeGen_8h_source.html
|
||||
pmb.set_optimization_level(OptimizationLevel::Aggressive);
|
||||
|
||||
// TODO figure out how enabling these individually differs from
|
||||
// the broad "aggressive optimizations" setting.
|
||||
|
||||
// fpm.add_reassociate_pass();
|
||||
// fpm.add_basic_alias_analysis_pass();
|
||||
// fpm.add_promote_memory_to_register_pass();
|
||||
// fpm.add_cfg_simplification_pass();
|
||||
// fpm.add_gvn_pass();
|
||||
// TODO figure out why enabling any of these (even alone) causes LLVM to segfault
|
||||
// fpm.add_strip_dead_prototypes_pass();
|
||||
// fpm.add_dead_arg_elimination_pass();
|
||||
// fpm.add_function_inlining_pass();
|
||||
// pmb.set_inliner_with_threshold(4);
|
||||
}
|
||||
}
|
||||
|
||||
pmb.populate_function_pass_manager(&fpm);
|
||||
}
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
|
@ -1006,6 +1029,17 @@ fn call_with_args<'a, 'ctx, 'env>(
|
|||
|
||||
BasicValueEnum::FloatValue(float_val)
|
||||
}
|
||||
Symbol::FLOAT_DIV => {
|
||||
debug_assert!(args.len() == 2);
|
||||
|
||||
let float_val = env.builder.build_float_div(
|
||||
args[0].0.into_float_value(),
|
||||
args[1].0.into_float_value(),
|
||||
"div_f64",
|
||||
);
|
||||
|
||||
BasicValueEnum::FloatValue(float_val)
|
||||
}
|
||||
Symbol::NUM_MUL => {
|
||||
debug_assert!(args.len() == 2);
|
||||
|
||||
|
|
|
@ -84,6 +84,19 @@ mod gen_builtins {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_div_f64() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
48 / 2
|
||||
"#
|
||||
),
|
||||
24.0,
|
||||
f64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_add_i64() {
|
||||
assert_evals_to!(
|
||||
|
@ -148,6 +161,20 @@ mod gen_builtins {
|
|||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_order_of_arithmetic_ops_complex_float() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
48 / 2 + 3
|
||||
"#
|
||||
),
|
||||
27.0,
|
||||
f64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_guard_bind_variable() {
|
||||
assert_evals_to!(
|
||||
|
|
|
@ -12,9 +12,14 @@ macro_rules! assert_llvm_evals_to {
|
|||
let context = Context::create();
|
||||
let module = roc_gen::llvm::build::module_from_builtins(&context, "app");
|
||||
let builder = context.create_builder();
|
||||
let fpm = inkwell::passes::PassManager::create(&module);
|
||||
let opt_level = if cfg!(debug_assertions) {
|
||||
roc_gen::llvm::build::OptLevel::Normal
|
||||
} else {
|
||||
roc_gen::llvm::build::OptLevel::Optimize
|
||||
};
|
||||
let fpm = PassManager::create(&module);
|
||||
|
||||
roc_gen::llvm::build::add_passes(&fpm);
|
||||
roc_gen::llvm::build::add_passes(&fpm, opt_level);
|
||||
|
||||
fpm.initialize();
|
||||
|
||||
|
@ -142,12 +147,19 @@ macro_rules! assert_opt_evals_to {
|
|||
let mut unify_problems = Vec::new();
|
||||
let (content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var);
|
||||
|
||||
assert_eq!(unify_problems, Vec::new(), "Encountered one or more type mismatches: {:?}", unify_problems);
|
||||
|
||||
let context = Context::create();
|
||||
let module = roc_gen::llvm::build::module_from_builtins(&context, "app");
|
||||
let builder = context.create_builder();
|
||||
let opt_level = if cfg!(debug_assertions) {
|
||||
roc_gen::llvm::build::OptLevel::Normal
|
||||
} else {
|
||||
roc_gen::llvm::build::OptLevel::Optimize
|
||||
};
|
||||
let fpm = PassManager::create(&module);
|
||||
|
||||
roc_gen::llvm::build::add_passes(&fpm);
|
||||
roc_gen::llvm::build::add_passes(&fpm, opt_level);
|
||||
|
||||
fpm.initialize();
|
||||
|
||||
|
@ -271,12 +283,19 @@ macro_rules! emit_expr {
|
|||
let mut unify_problems = Vec::new();
|
||||
let (content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var);
|
||||
|
||||
assert_eq!(unify_problems, Vec::new(), "Encountered one or more type mismatches: {:?}", unify_problems);
|
||||
|
||||
let context = Context::create();
|
||||
let module = context.create_module("app");
|
||||
let builder = context.create_builder();
|
||||
let opt_level = if cfg!(debug_assertions) {
|
||||
roc_gen::llvm::build::OptLevel::Normal
|
||||
} else {
|
||||
roc_gen::llvm::build::OptLevel::Optimize
|
||||
};
|
||||
let fpm = PassManager::create(&module);
|
||||
|
||||
roc_gen::llvm::build::add_passes(&fpm);
|
||||
roc_gen::llvm::build::add_passes(&fpm, opt_level);
|
||||
|
||||
fpm.initialize();
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ inlinable_string = "0.1.0"
|
|||
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded"] }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1 "
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
|
|
|
@ -12,7 +12,7 @@ use roc_constrain::module::{
|
|||
};
|
||||
use roc_module::ident::{Ident, Lowercase, ModuleName};
|
||||
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol};
|
||||
use roc_parse::ast::{self, Attempting, ExposesEntry, ImportsEntry, InterfaceHeader};
|
||||
use roc_parse::ast::{self, Attempting, ExposesEntry, ImportsEntry};
|
||||
use roc_parse::module::module_defs;
|
||||
use roc_parse::parser::{Fail, Parser, State};
|
||||
use roc_region::all::{Located, Region};
|
||||
|
@ -44,6 +44,7 @@ pub struct Module {
|
|||
pub aliases: MutMap<Symbol, Alias>,
|
||||
pub rigid_variables: MutMap<Variable, Lowercase>,
|
||||
pub imported_modules: MutSet<ModuleId>,
|
||||
pub src: Box<str>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -54,6 +55,8 @@ pub struct LoadedModule {
|
|||
pub can_problems: Vec<roc_problem::can::Problem>,
|
||||
pub type_problems: Vec<solve::TypeError>,
|
||||
pub declarations: Vec<Declaration>,
|
||||
pub exposed_vars_by_symbol: Vec<(Symbol, Variable)>,
|
||||
pub src: Box<str>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -89,10 +92,12 @@ enum Msg {
|
|||
var_store: VarStore,
|
||||
},
|
||||
Solved {
|
||||
src: Box<str>,
|
||||
module_id: ModuleId,
|
||||
solved_types: MutMap<Symbol, SolvedType>,
|
||||
aliases: MutMap<Symbol, Alias>,
|
||||
subs: Arc<Solved<Subs>>,
|
||||
exposed_vars_by_symbol: Vec<(Symbol, Variable)>,
|
||||
problems: Vec<solve::TypeError>,
|
||||
},
|
||||
}
|
||||
|
@ -108,6 +113,7 @@ pub enum LoadingProblem {
|
|||
fail: Fail,
|
||||
},
|
||||
MsgChannelDied,
|
||||
TriedToImportAppModule,
|
||||
}
|
||||
|
||||
enum MaybeShared<'a, 'b, A, B> {
|
||||
|
@ -391,8 +397,9 @@ pub async fn load<'a>(
|
|||
solved_types,
|
||||
subs,
|
||||
problems,
|
||||
exposed_vars_by_symbol,
|
||||
aliases,
|
||||
..
|
||||
src,
|
||||
} => {
|
||||
type_problems.extend(problems);
|
||||
|
||||
|
@ -427,6 +434,8 @@ pub async fn load<'a>(
|
|||
can_problems,
|
||||
type_problems,
|
||||
declarations,
|
||||
exposed_vars_by_symbol,
|
||||
src,
|
||||
});
|
||||
} else {
|
||||
// This was a dependency. Write it down and keep processing messages.
|
||||
|
@ -515,13 +524,36 @@ fn load_filename(
|
|||
#[allow(clippy::let_and_return)]
|
||||
let answer = match roc_parse::module::header().parse(&arena, state) {
|
||||
Ok((ast::Module::Interface { header }, state)) => {
|
||||
let module_id = send_interface_header(header, state, module_ids, msg_tx);
|
||||
let module_id = send_header(
|
||||
header.name,
|
||||
header.exposes.into_bump_slice(),
|
||||
header.imports.into_bump_slice(),
|
||||
state,
|
||||
module_ids,
|
||||
msg_tx,
|
||||
);
|
||||
|
||||
Ok(module_id)
|
||||
}
|
||||
Ok((ast::Module::App { .. }, _)) => {
|
||||
panic!("TODO finish loading an App module");
|
||||
}
|
||||
Ok((ast::Module::App { header }, state)) => match module_ids {
|
||||
MaybeShared::Shared(_, _) => {
|
||||
// If this is Shared, it means we're trying to import
|
||||
// an app module which is not the root. Not alllowed!
|
||||
Err(LoadingProblem::TriedToImportAppModule)
|
||||
}
|
||||
unique_modules @ MaybeShared::Unique(_, _) => {
|
||||
let module_id = send_header(
|
||||
header.name,
|
||||
header.provides.into_bump_slice(),
|
||||
header.imports.into_bump_slice(),
|
||||
state,
|
||||
unique_modules,
|
||||
msg_tx,
|
||||
);
|
||||
|
||||
Ok(module_id)
|
||||
}
|
||||
},
|
||||
Err((fail, _)) => Err(LoadingProblem::ParsingFailed { filename, fail }),
|
||||
};
|
||||
|
||||
|
@ -534,36 +566,37 @@ fn load_filename(
|
|||
}
|
||||
}
|
||||
|
||||
fn send_interface_header<'a>(
|
||||
header: InterfaceHeader<'a>,
|
||||
fn send_header<'a>(
|
||||
name: Located<roc_parse::header::ModuleName<'a>>,
|
||||
exposes: &'a [Located<ExposesEntry<'a>>],
|
||||
imports: &'a [Located<ImportsEntry<'a>>],
|
||||
state: State<'a>,
|
||||
shared_modules: SharedModules<'_, '_>,
|
||||
msg_tx: MsgSender,
|
||||
) -> ModuleId {
|
||||
use MaybeShared::*;
|
||||
|
||||
let declared_name: ModuleName = header.name.value.as_str().into();
|
||||
let declared_name: ModuleName = name.value.as_str().into();
|
||||
|
||||
// TODO check to see if declared_name is consistent with filename.
|
||||
// If it isn't, report a problem!
|
||||
|
||||
let mut imports: Vec<(ModuleName, Vec<Ident>, Region)> =
|
||||
Vec::with_capacity(header.imports.len());
|
||||
let mut imported: Vec<(ModuleName, Vec<Ident>, Region)> = Vec::with_capacity(imports.len());
|
||||
let mut imported_modules: MutSet<ModuleId> = MutSet::default();
|
||||
let mut scope_size = 0;
|
||||
|
||||
for loc_entry in header.imports {
|
||||
for loc_entry in imports {
|
||||
let (module_name, exposed) = exposed_from_import(&loc_entry.value);
|
||||
|
||||
scope_size += exposed.len();
|
||||
|
||||
imports.push((module_name, exposed, loc_entry.region));
|
||||
imported.push((module_name, exposed, loc_entry.region));
|
||||
}
|
||||
|
||||
let num_exposes = header.exposes.len();
|
||||
let num_exposes = exposes.len();
|
||||
let mut deps_by_name: MutMap<ModuleName, ModuleId> =
|
||||
HashMap::with_capacity_and_hasher(num_exposes, default_hasher());
|
||||
let mut exposes: Vec<Symbol> = Vec::with_capacity(num_exposes);
|
||||
let mut exposed: Vec<Symbol> = Vec::with_capacity(num_exposes);
|
||||
|
||||
// Make sure the module_ids has ModuleIds for all our deps,
|
||||
// then record those ModuleIds in can_module_ids for later.
|
||||
|
@ -592,7 +625,7 @@ fn send_interface_header<'a>(
|
|||
// For each of our imports, add an entry to deps_by_name
|
||||
//
|
||||
// e.g. for `imports [ Foo.{ bar } ]`, add `Foo` to deps_by_name
|
||||
for (module_name, exposed, region) in imports.into_iter() {
|
||||
for (module_name, exposed, region) in imported.into_iter() {
|
||||
let cloned_module_name = module_name.clone();
|
||||
let module_id = module_ids.get_or_insert(&module_name.into());
|
||||
|
||||
|
@ -618,7 +651,7 @@ fn send_interface_header<'a>(
|
|||
//
|
||||
// We must *not* add them to scope yet, or else the Defs will
|
||||
// incorrectly think they're shadowing them!
|
||||
for loc_exposed in header.exposes.iter() {
|
||||
for loc_exposed in exposes.iter() {
|
||||
// Use get_or_insert here because the ident_ids may already
|
||||
// created an IdentId for this, when it was imported exposed
|
||||
// in a dependent module.
|
||||
|
@ -629,7 +662,7 @@ fn send_interface_header<'a>(
|
|||
let ident_id = ident_ids.get_or_insert(&loc_exposed.value.as_str().into());
|
||||
let symbol = Symbol::new(home, ident_id);
|
||||
|
||||
exposes.push(symbol);
|
||||
exposed.push(symbol);
|
||||
}
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
|
@ -648,7 +681,7 @@ fn send_interface_header<'a>(
|
|||
// and also add any exposed values to scope.
|
||||
//
|
||||
// e.g. for `imports [ Foo.{ bar } ]`, add `Foo` to deps_by_name and `bar` to scope.
|
||||
for (module_name, exposed, region) in imports.into_iter() {
|
||||
for (module_name, exposed, region) in imported.into_iter() {
|
||||
let module_id = module_ids.get_or_insert(&module_name.clone().into());
|
||||
|
||||
deps_by_name.insert(module_name, module_id);
|
||||
|
@ -672,11 +705,11 @@ fn send_interface_header<'a>(
|
|||
//
|
||||
// We must *not* add them to scope yet, or else the Defs will
|
||||
// incorrectly think they're shadowing them!
|
||||
for loc_exposed in header.exposes.iter() {
|
||||
for loc_exposed in exposes.iter() {
|
||||
let ident_id = ident_ids.add(loc_exposed.value.as_str().into());
|
||||
let symbol = Symbol::new(home, ident_id);
|
||||
|
||||
exposes.push(symbol);
|
||||
exposed.push(symbol);
|
||||
}
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
|
@ -712,7 +745,7 @@ fn send_interface_header<'a>(
|
|||
module_name: declared_name,
|
||||
imported_modules,
|
||||
deps_by_name,
|
||||
exposes,
|
||||
exposes: exposed,
|
||||
src,
|
||||
exposed_imports: scope,
|
||||
}))
|
||||
|
@ -860,6 +893,7 @@ fn solve_module(
|
|||
aliases: module.aliases,
|
||||
};
|
||||
|
||||
let src = module.src;
|
||||
let mut subs = Subs::new(var_store.into());
|
||||
|
||||
for (var, name) in module.rigid_variables {
|
||||
|
@ -898,10 +932,10 @@ fn solve_module(
|
|||
// annotations which are decoupled from our Subs, because that's how
|
||||
// other modules will generate constraints for imported values
|
||||
// within the context of their own Subs.
|
||||
for (symbol, var) in exposed_vars_by_symbol {
|
||||
let solved_type = SolvedType::new(&solved_subs, var);
|
||||
for (symbol, var) in exposed_vars_by_symbol.iter() {
|
||||
let solved_type = SolvedType::new(&solved_subs, *var);
|
||||
|
||||
solved_types.insert(symbol, solved_type);
|
||||
solved_types.insert(*symbol, solved_type);
|
||||
}
|
||||
|
||||
tokio::spawn(async move {
|
||||
|
@ -909,8 +943,10 @@ fn solve_module(
|
|||
|
||||
// Send the subs to the main thread for processing,
|
||||
tx.send(Msg::Solved {
|
||||
src,
|
||||
module_id: home,
|
||||
subs: Arc::new(solved_subs),
|
||||
exposed_vars_by_symbol,
|
||||
solved_types,
|
||||
problems,
|
||||
aliases: env.aliases,
|
||||
|
@ -1044,6 +1080,7 @@ fn parse_and_constrain(
|
|||
aliases,
|
||||
rigid_variables,
|
||||
imported_modules: header.imported_modules,
|
||||
src: header.src,
|
||||
};
|
||||
|
||||
(module, ident_ids, constraint, problems)
|
||||
|
|
111
compiler/load/tests/fixtures/build/app_with_deps/AStar.roc
vendored
Normal file
111
compiler/load/tests/fixtures/build/app_with_deps/AStar.roc
vendored
Normal file
|
@ -0,0 +1,111 @@
|
|||
interface AStar
|
||||
exposes [ initialModel, reconstructPath, updateCost, cheapestOpen, astar, findPath ]
|
||||
imports []
|
||||
|
||||
|
||||
# a port of https://github.com/krisajenkins/elm-astar/blob/2.1.3/src/AStar/Generalised.elm
|
||||
|
||||
Model position :
|
||||
{ evaluated : Set position
|
||||
, openSet : Set position
|
||||
, costs : Map.Map position Float
|
||||
, cameFrom : Map.Map position position
|
||||
}
|
||||
|
||||
|
||||
initialModel : position -> Model position
|
||||
initialModel = \start ->
|
||||
{ evaluated : Set.empty
|
||||
, openSet : Set.singleton start
|
||||
, costs : Map.singleton start 0.0
|
||||
, cameFrom : Map.empty
|
||||
}
|
||||
|
||||
|
||||
cheapestOpen : (position -> Float), Model position -> Result position [ KeyNotFound ]*
|
||||
cheapestOpen = \costFunction, model ->
|
||||
|
||||
folder = \position, resSmallestSoFar ->
|
||||
when Map.get model.costs position is
|
||||
Err e ->
|
||||
Err e
|
||||
|
||||
Ok cost ->
|
||||
positionCost = costFunction position
|
||||
|
||||
when resSmallestSoFar is
|
||||
Err _ -> Ok { position, cost: cost + positionCost }
|
||||
Ok smallestSoFar ->
|
||||
if positionCost + cost < smallestSoFar.cost then
|
||||
Ok { position, cost: cost + positionCost }
|
||||
|
||||
else
|
||||
Ok smallestSoFar
|
||||
|
||||
Set.foldl model.openSet folder (Err KeyNotFound)
|
||||
|> Result.map (\x -> x.position)
|
||||
|
||||
|
||||
|
||||
reconstructPath : Map position position, position -> List position
|
||||
reconstructPath = \cameFrom, goal ->
|
||||
when Map.get cameFrom goal is
|
||||
Err KeyNotFound ->
|
||||
[]
|
||||
|
||||
Ok next ->
|
||||
List.push (reconstructPath cameFrom next) goal
|
||||
|
||||
updateCost : position, position, Model position -> Model position
|
||||
updateCost = \current, neighbour, model ->
|
||||
newCameFrom = Map.insert model.cameFrom neighbour current
|
||||
|
||||
newCosts = Map.insert model.costs neighbour distanceTo
|
||||
|
||||
distanceTo = reconstructPath newCameFrom neighbour
|
||||
|> List.len
|
||||
|> Num.toFloat
|
||||
|
||||
newModel = { model & costs : newCosts , cameFrom : newCameFrom }
|
||||
|
||||
when Map.get model.costs neighbour is
|
||||
Err KeyNotFound ->
|
||||
newModel
|
||||
|
||||
Ok previousDistance ->
|
||||
if distanceTo < previousDistance then
|
||||
newModel
|
||||
|
||||
else
|
||||
model
|
||||
|
||||
|
||||
findPath : { costFunction: (position, position -> Float), moveFunction: (position -> Set position), start : position, end : position } -> Result (List position) [ KeyNotFound ]*
|
||||
findPath = \{ costFunction, moveFunction, start, end } ->
|
||||
astar costFunction moveFunction end (initialModel start)
|
||||
|
||||
|
||||
astar : (position, position -> Float), (position -> Set position), position, Model position -> [ Err [ KeyNotFound ]*, Ok (List position) ]*
|
||||
astar = \costFn, moveFn, goal, model ->
|
||||
when cheapestOpen (\position -> costFn goal position) model is
|
||||
Err _ ->
|
||||
Err KeyNotFound
|
||||
|
||||
Ok current ->
|
||||
if current == goal then
|
||||
Ok (reconstructPath model.cameFrom goal)
|
||||
|
||||
else
|
||||
|
||||
modelPopped = { model & openSet : Set.remove model.openSet current, evaluated : Set.insert model.evaluated current }
|
||||
|
||||
neighbours = moveFn current
|
||||
|
||||
newNeighbours = Set.diff neighbours modelPopped.evaluated
|
||||
|
||||
modelWithNeighbours = { modelPopped & openSet : Set.union modelPopped.openSet newNeighbours }
|
||||
|
||||
modelWithCosts = Set.foldl newNeighbours (\nb, md -> updateCost current nb md) modelWithNeighbours
|
||||
|
||||
astar costFn moveFn goal modelWithCosts
|
||||
|
15
compiler/load/tests/fixtures/build/app_with_deps/Dep1.roc
vendored
Normal file
15
compiler/load/tests/fixtures/build/app_with_deps/Dep1.roc
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
interface Dep1
|
||||
exposes [ three, str, Unit, Identity, one, two ]
|
||||
imports [ Dep3.Blah.{ foo } ]
|
||||
|
||||
one = 1
|
||||
|
||||
two = foo
|
||||
|
||||
three = 3.0
|
||||
|
||||
str = "string!"
|
||||
|
||||
Unit : [ Unit ]
|
||||
|
||||
Identity a : [ Identity a ]
|
10
compiler/load/tests/fixtures/build/app_with_deps/Dep2.roc
vendored
Normal file
10
compiler/load/tests/fixtures/build/app_with_deps/Dep2.roc
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
interface Dep2
|
||||
exposes [ one, two, blah ]
|
||||
imports [ Dep3.Blah.{ foo, bar } ]
|
||||
|
||||
one = 1
|
||||
|
||||
blah = foo
|
||||
|
||||
two = 2.0
|
||||
|
10
compiler/load/tests/fixtures/build/app_with_deps/Dep3/Blah.roc
vendored
Normal file
10
compiler/load/tests/fixtures/build/app_with_deps/Dep3/Blah.roc
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
interface Dep3.Blah
|
||||
exposes [ one, two, foo, bar ]
|
||||
imports []
|
||||
|
||||
one = 1
|
||||
|
||||
two = 2
|
||||
|
||||
foo = "foo from Dep3"
|
||||
bar = "bar from Dep3"
|
7
compiler/load/tests/fixtures/build/app_with_deps/ImportAlias.roc
vendored
Normal file
7
compiler/load/tests/fixtures/build/app_with_deps/ImportAlias.roc
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
interface ImportAlias
|
||||
exposes [ unit ]
|
||||
imports [ Dep1 ]
|
||||
|
||||
unit : Dep1.Unit
|
||||
unit = Unit
|
||||
|
18
compiler/load/tests/fixtures/build/app_with_deps/ManualAttr.roc
vendored
Normal file
18
compiler/load/tests/fixtures/build/app_with_deps/ManualAttr.roc
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
interface ManualAttr
|
||||
exposes []
|
||||
imports []
|
||||
|
||||
# manually replicates the Attr wrapping that uniqueness inference uses, to try and find out why they are different
|
||||
# It is very important that there are no signatures here! elm uses an optimization that leads to less copying when
|
||||
# signatures are given.
|
||||
|
||||
map =
|
||||
unAttr = \Attr _ foobar -> foobar
|
||||
|
||||
r = Attr unknown "bar"
|
||||
|
||||
s = Attr unknown2 { left : Attr Shared "foo" }
|
||||
|
||||
when True is
|
||||
_ -> { y : r }
|
||||
_ -> { y : (unAttr s).left }
|
5
compiler/load/tests/fixtures/build/app_with_deps/OneDep.roc
vendored
Normal file
5
compiler/load/tests/fixtures/build/app_with_deps/OneDep.roc
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
interface OneDep
|
||||
exposes [ str ]
|
||||
imports [ Dep3.Blah.{ foo } ]
|
||||
|
||||
str = foo
|
31
compiler/load/tests/fixtures/build/app_with_deps/Primary.roc
vendored
Normal file
31
compiler/load/tests/fixtures/build/app_with_deps/Primary.roc
vendored
Normal file
|
@ -0,0 +1,31 @@
|
|||
app Primary
|
||||
provides [ blah2, blah3, str, alwaysThree, identity, z, w, succeed, withDefault, yay ]
|
||||
imports [ Dep1, Dep2.{ two, foo }, Dep3.Blah.{ bar }, Res ]
|
||||
|
||||
blah2 = Dep2.two
|
||||
blah3 = bar
|
||||
|
||||
str = Dep1.str
|
||||
|
||||
# alwaysThree = \_ -> Dep1.three # TODO FIXME for some reason this infers as a circular type
|
||||
alwaysThree = \_ -> "foo"
|
||||
|
||||
identity = \a -> a
|
||||
|
||||
z = identity (alwaysThree {})
|
||||
|
||||
w : Dep1.Identity {}
|
||||
w = Identity {}
|
||||
|
||||
succeed : a -> Dep1.Identity a
|
||||
succeed = \x -> Identity x
|
||||
|
||||
withDefault = Res.withDefault
|
||||
|
||||
yay : Res.Res {} err
|
||||
yay =
|
||||
ok = Ok "foo"
|
||||
|
||||
f = \_ -> {}
|
||||
|
||||
Res.map ok f
|
|
@ -1,3 +1,7 @@
|
|||
app Quicksort
|
||||
provides [ swap, partition, quicksort ]
|
||||
imports []
|
||||
|
||||
quicksort : List (Num a), Int, Int -> List (Num a)
|
||||
quicksort = \list, low, high ->
|
||||
when partition low high list is
|
||||
|
@ -43,5 +47,3 @@ partition = \low, high, initialList ->
|
|||
|
||||
Err _ ->
|
||||
Pair (low - 1) initialList
|
||||
|
||||
quicksort [ 7, 4, 9 ]
|
8
compiler/load/tests/fixtures/build/app_with_deps/Records.roc
vendored
Normal file
8
compiler/load/tests/fixtures/build/app_with_deps/Records.roc
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
interface Records
|
||||
exposes [ intVal ]
|
||||
imports []
|
||||
|
||||
intVal =
|
||||
foo = \{ x } -> x
|
||||
|
||||
foo { x: 5 }
|
32
compiler/load/tests/fixtures/build/app_with_deps/Res.roc
vendored
Normal file
32
compiler/load/tests/fixtures/build/app_with_deps/Res.roc
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
interface Res
|
||||
exposes [ Res, withDefault, map, andThen, ConsList ]
|
||||
imports []
|
||||
|
||||
Res ok err : [ Ok ok, Err err ]
|
||||
|
||||
ConsList a : [ Cons a (ConsList a), Nil ]
|
||||
|
||||
# TODO FIXME for some reason, exposing this causes a stack overflow
|
||||
# listMap : ConsList a, (a -> b) -> ConsList b
|
||||
# listMap = \list, f ->
|
||||
# when list is
|
||||
# Nil -> Nil
|
||||
# Cons x xs -> Cons (f x) (listMap xs f)
|
||||
|
||||
map : Res a err, (a -> b) -> Res b err
|
||||
map = \result, transform ->
|
||||
when result is
|
||||
Ok ok -> Ok (transform ok)
|
||||
Err err -> Err err
|
||||
|
||||
withDefault : Res a err, a -> a
|
||||
withDefault = \result, default ->
|
||||
when result is
|
||||
Ok ok -> ok
|
||||
Err _ -> default
|
||||
|
||||
andThen : Res a err, (a -> Res b err) -> Res b err
|
||||
andThen = \result, transform ->
|
||||
when result is
|
||||
Ok ok -> transform ok
|
||||
Err err -> Err err
|
19
compiler/load/tests/fixtures/build/app_with_deps/WithBuiltins.roc
vendored
Normal file
19
compiler/load/tests/fixtures/build/app_with_deps/WithBuiltins.roc
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
interface WithBuiltins
|
||||
exposes [ floatTest, divisionFn, divisionTest, intTest, constantNum, fromDep2, divDep1ByDep2 ]
|
||||
imports [ Dep1, Dep2.{ two } ]
|
||||
|
||||
floatTest = Float.highest
|
||||
|
||||
divisionFn = Float.div
|
||||
|
||||
x = 5.0
|
||||
|
||||
divisionTest = Float.highest / x
|
||||
|
||||
intTest = Int.highest
|
||||
|
||||
constantNum = 5
|
||||
|
||||
fromDep2 = Dep2.two
|
||||
|
||||
divDep1ByDep2 = Dep1.three / fromDep2
|
|
@ -3,7 +3,7 @@ interface Primary
|
|||
imports [ Dep1, Dep2.{ two, foo }, Dep3.Blah.{ bar }, Res ]
|
||||
|
||||
blah2 = Dep2.two
|
||||
blah3 = bar # TODO FIXME does work as Dep3.Blah.bar, some scoping issue
|
||||
blah3 = bar
|
||||
|
||||
str = Dep1.str
|
||||
|
||||
|
@ -12,11 +12,7 @@ alwaysThree = \_ -> "foo"
|
|||
|
||||
identity = \a -> a
|
||||
|
||||
# z = identity (alwaysThree {}) # TODO FIXME for some reason this infers as a circular type
|
||||
# z = identity 3 # TODO FIXME for some reason this also infers as a circular type
|
||||
|
||||
z : Dep1.Unit
|
||||
z = Unit
|
||||
z = identity (alwaysThree {})
|
||||
|
||||
w : Dep1.Identity {}
|
||||
w = Identity {}
|
||||
|
|
|
@ -212,7 +212,7 @@ mod test_load {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn load_and_typecheck_quicksort() {
|
||||
fn iface_quicksort() {
|
||||
test_async(async {
|
||||
let subs_by_module = MutMap::default();
|
||||
let loaded_module =
|
||||
|
@ -229,6 +229,23 @@ mod test_load {
|
|||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn app_quicksort() {
|
||||
test_async(async {
|
||||
let subs_by_module = MutMap::default();
|
||||
let loaded_module = load_fixture("app_with_deps", "Quicksort", subs_by_module).await;
|
||||
|
||||
expect_types(
|
||||
loaded_module,
|
||||
hashmap! {
|
||||
"swap" => "Int, Int, List a -> List a",
|
||||
"partition" => "Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]",
|
||||
"quicksort" => "List (Num a), Int, Int -> List (Num a)",
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_astar() {
|
||||
test_async(async {
|
||||
|
@ -266,7 +283,7 @@ mod test_load {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn load_dep_types() {
|
||||
fn iface_dep_types() {
|
||||
test_async(async {
|
||||
let subs_by_module = MutMap::default();
|
||||
let loaded_module =
|
||||
|
@ -280,7 +297,31 @@ mod test_load {
|
|||
"str" => "Str",
|
||||
"alwaysThree" => "* -> Str",
|
||||
"identity" => "a -> a",
|
||||
"z" => "Dep1.Unit",
|
||||
"z" => "Str",
|
||||
"w" => "Dep1.Identity {}",
|
||||
"succeed" => "a -> Dep1.Identity a",
|
||||
"yay" => "Res.Res {} err",
|
||||
"withDefault" => "Res.Res a *, a -> a",
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn app_dep_types() {
|
||||
test_async(async {
|
||||
let subs_by_module = MutMap::default();
|
||||
let loaded_module = load_fixture("app_with_deps", "Primary", subs_by_module).await;
|
||||
|
||||
expect_types(
|
||||
loaded_module,
|
||||
hashmap! {
|
||||
"blah2" => "Float",
|
||||
"blah3" => "Str",
|
||||
"str" => "Str",
|
||||
"alwaysThree" => "* -> Str",
|
||||
"identity" => "a -> a",
|
||||
"z" => "Str",
|
||||
"w" => "Dep1.Identity {}",
|
||||
"succeed" => "a -> Dep1.Identity a",
|
||||
"yay" => "Res.Res {} err",
|
||||
|
|
|
@ -275,7 +275,7 @@ mod test_uniq_load {
|
|||
"str" => "Attr * Str",
|
||||
"alwaysThree" => "Attr * (* -> Attr * Str)",
|
||||
"identity" => "Attr * (a -> a)",
|
||||
"z" => "Attr * Dep1.Unit",
|
||||
"z" => "Attr * Str",
|
||||
"w" => "Attr * (Dep1.Identity (Attr * {}))",
|
||||
"succeed" => "Attr * (Attr b a -> Attr * (Dep1.Identity (Attr b a)))",
|
||||
"yay" => "Attr * (Res.Res (Attr * {}) (Attr * err))",
|
||||
|
|
|
@ -12,6 +12,6 @@ inlinable_string = "0.1.0"
|
|||
lazy_static = "1.4"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1 "
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
|
|
|
@ -19,7 +19,7 @@ roc_constrain = { path = "../constrain" }
|
|||
roc_builtins = { path = "../builtins" }
|
||||
roc_parse = { path = "../parse" }
|
||||
roc_solve = { path = "../solve" }
|
||||
pretty_assertions = "0.5.1 "
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
|
|
|
@ -10,10 +10,27 @@ use roc_region::all::{Located, Region};
|
|||
use roc_types::subs::{Content, ContentHash, FlatType, Subs, Variable};
|
||||
use std::hash::Hash;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct PartialProc<'a> {
|
||||
pub annotation: Variable,
|
||||
pub patterns: Vec<'a, Symbol>,
|
||||
pub body: roc_can::expr::Expr,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Proc<'a> {
|
||||
pub name: Symbol,
|
||||
pub args: &'a [(Layout<'a>, Symbol)],
|
||||
pub body: Expr<'a>,
|
||||
pub closes_over: Layout<'a>,
|
||||
pub ret_layout: Layout<'a>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Default)]
|
||||
pub struct Procs<'a> {
|
||||
user_defined: MutMap<Symbol, PartialProc<'a>>,
|
||||
anonymous: MutMap<Symbol, Option<Proc<'a>>>,
|
||||
specializations: MutMap<ContentHash, (Symbol, Option<Proc<'a>>)>,
|
||||
builtin: MutSet<Symbol>,
|
||||
}
|
||||
|
||||
|
@ -28,14 +45,11 @@ impl<'a> Procs<'a> {
|
|||
|
||||
fn insert_specialization(
|
||||
&mut self,
|
||||
symbol: Symbol,
|
||||
hash: ContentHash,
|
||||
spec_name: Symbol,
|
||||
proc: Option<Proc<'a>>,
|
||||
) {
|
||||
self.user_defined
|
||||
.get_mut(&symbol)
|
||||
.map(|partial_proc| partial_proc.specializations.insert(hash, (spec_name, proc)));
|
||||
self.specializations.insert(hash, (spec_name, proc));
|
||||
}
|
||||
|
||||
fn get_user_defined(&self, symbol: Symbol) -> Option<&PartialProc<'a>> {
|
||||
|
@ -44,11 +58,7 @@ impl<'a> Procs<'a> {
|
|||
|
||||
pub fn len(&self) -> usize {
|
||||
let anonymous: usize = self.anonymous.len();
|
||||
let user_defined: usize = self
|
||||
.user_defined
|
||||
.values()
|
||||
.map(|v| v.specializations.len())
|
||||
.sum();
|
||||
let user_defined: usize = self.specializations.len();
|
||||
|
||||
anonymous + user_defined
|
||||
}
|
||||
|
@ -64,10 +74,8 @@ impl<'a> Procs<'a> {
|
|||
pub fn as_map(&self) -> MutMap<Symbol, Option<Proc<'a>>> {
|
||||
let mut result = MutMap::default();
|
||||
|
||||
for partial_proc in self.user_defined.values() {
|
||||
for (_, (symbol, opt_proc)) in partial_proc.specializations.clone().into_iter() {
|
||||
result.insert(symbol, opt_proc);
|
||||
}
|
||||
for (symbol, opt_proc) in self.specializations.values() {
|
||||
result.insert(*symbol, opt_proc.clone());
|
||||
}
|
||||
|
||||
for (symbol, proc) in self.anonymous.clone().into_iter() {
|
||||
|
@ -82,23 +90,6 @@ impl<'a> Procs<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct PartialProc<'a> {
|
||||
pub annotation: Variable,
|
||||
pub patterns: Vec<'a, Symbol>,
|
||||
pub body: roc_can::expr::Expr,
|
||||
pub specializations: MutMap<ContentHash, (Symbol, Option<Proc<'a>>)>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Proc<'a> {
|
||||
pub name: Symbol,
|
||||
pub args: &'a [(Layout<'a>, Symbol)],
|
||||
pub body: Expr<'a>,
|
||||
pub closes_over: Layout<'a>,
|
||||
pub ret_layout: Layout<'a>,
|
||||
}
|
||||
|
||||
pub struct Env<'a, 'i> {
|
||||
pub arena: &'a Bump,
|
||||
pub subs: &'a mut Subs,
|
||||
|
@ -458,7 +449,6 @@ fn from_can<'a>(
|
|||
annotation,
|
||||
patterns: arg_symbols,
|
||||
body: body.value,
|
||||
specializations: MutMap::default(),
|
||||
},
|
||||
);
|
||||
symbol
|
||||
|
@ -831,8 +821,9 @@ fn from_can<'a>(
|
|||
elems: elems.into_bump_slice(),
|
||||
}
|
||||
}
|
||||
Accessor { .. } => todo!("record accessor"),
|
||||
Update { .. } => todo!("record update"),
|
||||
RuntimeError(error) => Expr::RuntimeError(env.arena.alloc(format!("{:?}", error))),
|
||||
other => panic!("TODO convert canonicalized {:?} to mono::Expr", other),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1304,36 +1295,42 @@ fn call_by_name<'a>(
|
|||
Vec<'a, Symbol>,
|
||||
)>;
|
||||
|
||||
let specialized_proc_name = if let Some(partial_proc) = procs.get_user_defined(proc_name) {
|
||||
let content_hash = ContentHash::from_var(fn_var, env.subs);
|
||||
let specialized_proc_name = match procs.get_user_defined(proc_name) {
|
||||
Some(partial_proc) => {
|
||||
let content_hash = ContentHash::from_var(fn_var, env.subs);
|
||||
|
||||
if let Some(specialization) = partial_proc.specializations.get(&content_hash) {
|
||||
match procs.specializations.get(&content_hash) {
|
||||
Some(specialization) => {
|
||||
opt_specialize_body = None;
|
||||
|
||||
// a specialization with this type hash already exists, use its symbol
|
||||
specialization.0
|
||||
}
|
||||
None => {
|
||||
opt_specialize_body = Some((
|
||||
content_hash,
|
||||
partial_proc.annotation,
|
||||
partial_proc.body.clone(),
|
||||
partial_proc.patterns.clone(),
|
||||
));
|
||||
|
||||
// generate a symbol for this specialization
|
||||
env.fresh_symbol()
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
opt_specialize_body = None;
|
||||
|
||||
// a specialization with this type hash already exists, use its symbol
|
||||
specialization.0
|
||||
} else {
|
||||
opt_specialize_body = Some((
|
||||
content_hash,
|
||||
partial_proc.annotation,
|
||||
partial_proc.body.clone(),
|
||||
partial_proc.patterns.clone(),
|
||||
));
|
||||
|
||||
// generate a symbol for this specialization
|
||||
env.fresh_symbol()
|
||||
// This happens for built-in symbols (they are never defined as a Closure)
|
||||
procs.insert_builtin(proc_name);
|
||||
proc_name
|
||||
}
|
||||
} else {
|
||||
opt_specialize_body = None;
|
||||
|
||||
// This happens for built-in symbols (they are never defined as a Closure)
|
||||
procs.insert_builtin(proc_name);
|
||||
proc_name
|
||||
};
|
||||
|
||||
if let Some((content_hash, annotation, body, loc_patterns)) = opt_specialize_body {
|
||||
// register proc, so specialization doesn't loop infinitely
|
||||
procs.insert_specialization(proc_name, content_hash, specialized_proc_name, None);
|
||||
procs.insert_specialization(content_hash, specialized_proc_name, None);
|
||||
|
||||
let arg_vars = loc_args.iter().map(|v| v.0).collect::<std::vec::Vec<_>>();
|
||||
|
||||
|
@ -1350,7 +1347,7 @@ fn call_by_name<'a>(
|
|||
)
|
||||
.ok();
|
||||
|
||||
procs.insert_specialization(proc_name, content_hash, specialized_proc_name, proc);
|
||||
procs.insert_specialization(content_hash, specialized_proc_name, proc);
|
||||
}
|
||||
|
||||
// generate actual call
|
||||
|
|
|
@ -12,7 +12,7 @@ bumpalo = { version = "3.2", features = ["collections"] }
|
|||
inlinable_string = "0.1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1 "
|
||||
pretty_assertions = "0.5.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
quickcheck_macros = "0.8"
|
||||
|
|
|
@ -36,9 +36,14 @@ pub struct WhenBranch<'a> {
|
|||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct AppHeader<'a> {
|
||||
pub name: Loc<ModuleName<'a>>,
|
||||
pub provides: Vec<'a, Loc<ExposesEntry<'a>>>,
|
||||
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>,
|
||||
|
||||
// Potential comments and newlines - these will typically all be empty.
|
||||
pub after_interface: &'a [CommentOrNewline<'a>],
|
||||
pub before_provides: &'a [CommentOrNewline<'a>],
|
||||
pub after_provides: &'a [CommentOrNewline<'a>],
|
||||
pub before_imports: &'a [CommentOrNewline<'a>],
|
||||
pub after_imports: &'a [CommentOrNewline<'a>],
|
||||
}
|
||||
|
|
|
@ -1244,10 +1244,14 @@ pub fn ident_etc<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
|
|||
move |arena, state, (loc_ident, opt_extras)| {
|
||||
// This appears to be a var, keyword, or function application.
|
||||
match opt_extras {
|
||||
(Some(_loc_args), Some((_spaces_before_equals, Either::First(_equals_indent)))) => {
|
||||
// We got args with an '=' after them, e.g. `foo a b = ...`
|
||||
// This is a syntax error!
|
||||
panic!("TODO gracefully handle parse error for defs like `foo a b = ...`");
|
||||
(Some(loc_args), Some((_spaces_before_equals, Either::First(_equals_indent)))) => {
|
||||
// We got args with an '=' after them, e.g. `foo a b = ...` This is a syntax error!
|
||||
let region = Region::across_all(loc_args.iter().map(|v| &v.region));
|
||||
let fail = Fail {
|
||||
attempting: state.attempting,
|
||||
reason: FailReason::ArgumentsBeforeEquals(region),
|
||||
};
|
||||
Err((fail, state))
|
||||
}
|
||||
(None, Some((spaces_before_equals, Either::First(equals_indent)))) => {
|
||||
// We got '=' with no args before it
|
||||
|
|
|
@ -127,9 +127,30 @@ pub fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>> {
|
|||
|
||||
#[inline(always)]
|
||||
fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>> {
|
||||
move |_, _| {
|
||||
panic!("TODO parse app header");
|
||||
}
|
||||
parser::map(
|
||||
and!(
|
||||
skip_first!(string("app"), and!(space1(1), loc!(module_name()))),
|
||||
and!(provides(), imports())
|
||||
),
|
||||
|(
|
||||
(after_interface, name),
|
||||
(
|
||||
((before_provides, after_provides), provides),
|
||||
((before_imports, after_imports), imports),
|
||||
),
|
||||
)| {
|
||||
AppHeader {
|
||||
name,
|
||||
provides,
|
||||
imports,
|
||||
after_interface,
|
||||
before_provides,
|
||||
after_provides,
|
||||
before_imports,
|
||||
after_imports,
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -137,6 +158,20 @@ pub fn module_defs<'a>() -> impl Parser<'a, Vec<'a, Located<Def<'a>>>> {
|
|||
zero_or_more!(space0_around(loc(def(0)), 0))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn provides<'a>() -> impl Parser<
|
||||
'a,
|
||||
(
|
||||
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
|
||||
Vec<'a, Located<ExposesEntry<'a>>>,
|
||||
),
|
||||
> {
|
||||
and!(
|
||||
and!(skip_second!(space1(1), string("provides")), space1(1)),
|
||||
collection!(char('['), loc!(exposes_entry()), char(','), char(']'), 1)
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn exposes<'a>() -> impl Parser<
|
||||
'a,
|
||||
|
|
|
@ -190,6 +190,7 @@ pub enum FailReason {
|
|||
Eof(Region),
|
||||
InvalidPattern,
|
||||
ReservedKeyword(Region),
|
||||
ArgumentsBeforeEquals(Region),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
|
|
|
@ -12,7 +12,7 @@ roc_parse = { path = "../parse" }
|
|||
inlinable_string = "0.1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1 "
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use inlinable_string::InlinableString;
|
||||
use roc_collections::all::MutSet;
|
||||
use roc_module::ident::Ident;
|
||||
use roc_module::ident::{Ident, Lowercase, TagName};
|
||||
use roc_module::symbol::{ModuleId, Symbol};
|
||||
use roc_parse::operator::BinOp;
|
||||
use roc_parse::pattern::PatternType;
|
||||
|
@ -21,6 +21,30 @@ pub enum Problem {
|
|||
original_region: Region,
|
||||
shadow: Located<Ident>,
|
||||
},
|
||||
CyclicAlias(Symbol, Region, Vec<Symbol>),
|
||||
PhantomTypeArgument {
|
||||
alias: Symbol,
|
||||
variable_region: Region,
|
||||
variable_name: Lowercase,
|
||||
},
|
||||
DuplicateRecordFieldValue {
|
||||
field_name: Lowercase,
|
||||
record_region: Region,
|
||||
field_region: Region,
|
||||
replaced_region: Region,
|
||||
},
|
||||
DuplicateRecordFieldType {
|
||||
field_name: Lowercase,
|
||||
record_region: Region,
|
||||
field_region: Region,
|
||||
replaced_region: Region,
|
||||
},
|
||||
DuplicateTag {
|
||||
tag_name: TagName,
|
||||
tag_union_region: Region,
|
||||
tag_region: Region,
|
||||
replaced_region: Region,
|
||||
},
|
||||
RuntimeError(RuntimeError),
|
||||
}
|
||||
|
||||
|
|
|
@ -47,6 +47,10 @@ impl Region {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.end_line == self.start_line && self.start_col == self.end_col
|
||||
}
|
||||
|
||||
pub fn span_across(start: &Region, end: &Region) -> Self {
|
||||
Region {
|
||||
start_line: start.start_line,
|
||||
|
|
|
@ -27,7 +27,7 @@ roc_constrain = { path = "../constrain" }
|
|||
roc_builtins = { path = "../builtins" }
|
||||
roc_problem = { path = "../problem" }
|
||||
roc_parse = { path = "../parse" }
|
||||
pretty_assertions = "0.5.1 "
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
|
|
397
compiler/reporting/src/error/canonicalize.rs
Normal file
397
compiler/reporting/src/error/canonicalize.rs
Normal file
|
@ -0,0 +1,397 @@
|
|||
use roc_collections::all::MutSet;
|
||||
use roc_problem::can::PrecedenceProblem::BothNonAssociative;
|
||||
use roc_problem::can::{Problem, RuntimeError};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder};
|
||||
use ven_pretty::DocAllocator;
|
||||
|
||||
pub fn can_problem<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
filename: PathBuf,
|
||||
problem: Problem,
|
||||
) -> Report<'b> {
|
||||
let doc = match problem {
|
||||
Problem::UnusedDef(symbol, region) => {
|
||||
let line =
|
||||
r#" then remove it so future readers of your code don't wonder why it is there."#;
|
||||
|
||||
alloc.stack(vec![
|
||||
alloc
|
||||
.symbol_unqualified(symbol)
|
||||
.append(alloc.reflow(" is not used anywhere in your code.")),
|
||||
alloc.region(region),
|
||||
alloc
|
||||
.reflow("If you didn't intend on using ")
|
||||
.append(alloc.symbol_unqualified(symbol))
|
||||
.append(alloc.reflow(line)),
|
||||
])
|
||||
}
|
||||
Problem::UnusedImport(module_id, region) => alloc.concat(vec![
|
||||
alloc.reflow("Nothing from "),
|
||||
alloc.module(module_id),
|
||||
alloc.reflow(" is used in this module."),
|
||||
alloc.region(region),
|
||||
alloc.reflow("Since "),
|
||||
alloc.module(module_id),
|
||||
alloc.reflow(" isn't used, you don't need to import it."),
|
||||
]),
|
||||
Problem::UnusedArgument(closure_symbol, argument_symbol, region) => {
|
||||
let line = "\". Adding an underscore at the start of a variable name is a way of saying that the variable is not used.";
|
||||
|
||||
alloc.concat(vec![
|
||||
alloc.symbol_unqualified(closure_symbol),
|
||||
alloc.reflow(" doesn't use "),
|
||||
alloc.symbol_unqualified(argument_symbol),
|
||||
alloc.reflow("."),
|
||||
alloc.region(region),
|
||||
alloc.reflow("If you don't need "),
|
||||
alloc.symbol_unqualified(argument_symbol),
|
||||
alloc.reflow(", then you can just remove it. However, if you really do need "),
|
||||
alloc.symbol_unqualified(argument_symbol),
|
||||
alloc.reflow(" as an argument of "),
|
||||
alloc.symbol_unqualified(closure_symbol),
|
||||
alloc.reflow(", prefix it with an underscore, like this: \"_"),
|
||||
alloc.symbol_unqualified(argument_symbol),
|
||||
alloc.reflow(line),
|
||||
])
|
||||
}
|
||||
Problem::PrecedenceProblem(BothNonAssociative(region, left_bin_op, right_bin_op)) => alloc
|
||||
.stack(vec![
|
||||
if left_bin_op.value == right_bin_op.value {
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("Using more than one "),
|
||||
alloc.binop(left_bin_op.value),
|
||||
alloc.reflow(concat!(
|
||||
" like this requires parentheses,",
|
||||
" to clarify how things should be grouped.",
|
||||
)),
|
||||
])
|
||||
} else {
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("Using "),
|
||||
alloc.binop(left_bin_op.value),
|
||||
alloc.reflow(" and "),
|
||||
alloc.binop(right_bin_op.value),
|
||||
alloc.reflow(concat!(
|
||||
" together requires parentheses, ",
|
||||
"to clarify how they should be grouped."
|
||||
)),
|
||||
])
|
||||
},
|
||||
alloc.region(region),
|
||||
]),
|
||||
Problem::UnsupportedPattern(pattern_type, region) => {
|
||||
use roc_parse::pattern::PatternType::*;
|
||||
|
||||
let this_thing = match pattern_type {
|
||||
TopLevelDef => "a top-level definition:",
|
||||
DefExpr => "a value definition:",
|
||||
FunctionArg => "function arguments:",
|
||||
WhenBranch => unreachable!("all patterns are allowed in a When"),
|
||||
};
|
||||
|
||||
let suggestion = vec![
|
||||
alloc.reflow(
|
||||
"Patterns like this don't cover all possible shapes of the input type. Use a ",
|
||||
),
|
||||
alloc.keyword("when"),
|
||||
alloc.reflow(" ... "),
|
||||
alloc.keyword("is"),
|
||||
alloc.reflow(" instead."),
|
||||
];
|
||||
|
||||
alloc.stack(vec![
|
||||
alloc
|
||||
.reflow("This pattern is not allowed in ")
|
||||
.append(alloc.reflow(this_thing)),
|
||||
alloc.region(region),
|
||||
alloc.concat(suggestion),
|
||||
])
|
||||
}
|
||||
Problem::ShadowingInAnnotation {
|
||||
original_region,
|
||||
shadow,
|
||||
} => pretty_runtime_error(
|
||||
alloc,
|
||||
RuntimeError::Shadowing {
|
||||
original_region,
|
||||
shadow,
|
||||
},
|
||||
),
|
||||
Problem::CyclicAlias(symbol, region, others) => {
|
||||
let (doc, title) = crate::error::r#type::cyclic_alias(alloc, symbol, region, others);
|
||||
|
||||
return Report {
|
||||
filename,
|
||||
title,
|
||||
doc,
|
||||
};
|
||||
}
|
||||
Problem::PhantomTypeArgument {
|
||||
alias,
|
||||
variable_region,
|
||||
variable_name,
|
||||
} => alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("The "),
|
||||
alloc.type_variable(variable_name),
|
||||
alloc.reflow(" type variable is not used in the "),
|
||||
alloc.symbol_unqualified(alias),
|
||||
alloc.reflow(" alias definition:"),
|
||||
]),
|
||||
alloc.region(variable_region),
|
||||
alloc.reflow("Roc does not allow unused type parameters!"),
|
||||
// TODO add link to this guide section
|
||||
alloc.hint().append(alloc.reflow(
|
||||
"If you want an unused type parameter (a so-called \"phantom type\"), \
|
||||
read the guide section on phantom data.",
|
||||
)),
|
||||
]),
|
||||
Problem::DuplicateRecordFieldValue {
|
||||
field_name,
|
||||
field_region,
|
||||
record_region,
|
||||
replaced_region,
|
||||
} => alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("This record defines the "),
|
||||
alloc.record_field(field_name.clone()),
|
||||
alloc.reflow(" field twice!"),
|
||||
]),
|
||||
alloc.region_all_the_things(
|
||||
record_region,
|
||||
replaced_region,
|
||||
field_region,
|
||||
Annotation::Error,
|
||||
),
|
||||
alloc.reflow("In the rest of the program, I will only use the latter definition:"),
|
||||
alloc.region_all_the_things(
|
||||
record_region,
|
||||
field_region,
|
||||
field_region,
|
||||
Annotation::TypoSuggestion,
|
||||
),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("For clarity, remove the previous "),
|
||||
alloc.record_field(field_name),
|
||||
alloc.reflow(" definitions from this record."),
|
||||
]),
|
||||
]),
|
||||
Problem::DuplicateRecordFieldType {
|
||||
field_name,
|
||||
field_region,
|
||||
record_region,
|
||||
replaced_region,
|
||||
} => alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("This record type defines the "),
|
||||
alloc.record_field(field_name.clone()),
|
||||
alloc.reflow(" field twice!"),
|
||||
]),
|
||||
alloc.region_all_the_things(
|
||||
record_region,
|
||||
replaced_region,
|
||||
field_region,
|
||||
Annotation::Error,
|
||||
),
|
||||
alloc.reflow("In the rest of the program, I will only use the latter definition:"),
|
||||
alloc.region_all_the_things(
|
||||
record_region,
|
||||
field_region,
|
||||
field_region,
|
||||
Annotation::TypoSuggestion,
|
||||
),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("For clarity, remove the previous "),
|
||||
alloc.record_field(field_name),
|
||||
alloc.reflow(" definitions from this record type."),
|
||||
]),
|
||||
]),
|
||||
Problem::DuplicateTag {
|
||||
tag_name,
|
||||
tag_union_region,
|
||||
tag_region,
|
||||
replaced_region,
|
||||
} => alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("This tag union type defines the "),
|
||||
alloc.tag_name(tag_name.clone()),
|
||||
alloc.reflow(" tag twice!"),
|
||||
]),
|
||||
alloc.region_all_the_things(
|
||||
tag_union_region,
|
||||
replaced_region,
|
||||
tag_region,
|
||||
Annotation::Error,
|
||||
),
|
||||
alloc.reflow("In the rest of the program, I will only use the latter definition:"),
|
||||
alloc.region_all_the_things(
|
||||
tag_union_region,
|
||||
tag_region,
|
||||
tag_region,
|
||||
Annotation::TypoSuggestion,
|
||||
),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("For clarity, remove the previous "),
|
||||
alloc.tag_name(tag_name),
|
||||
alloc.reflow(" definitions from this tag union type."),
|
||||
]),
|
||||
]),
|
||||
Problem::RuntimeError(runtime_error) => pretty_runtime_error(alloc, runtime_error),
|
||||
};
|
||||
|
||||
Report {
|
||||
title: "SYNTAX PROBLEM".to_string(),
|
||||
filename,
|
||||
doc,
|
||||
}
|
||||
}
|
||||
|
||||
fn pretty_runtime_error<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
runtime_error: RuntimeError,
|
||||
) -> RocDocBuilder<'b> {
|
||||
match runtime_error {
|
||||
RuntimeError::Shadowing {
|
||||
original_region,
|
||||
shadow,
|
||||
} => {
|
||||
let line = r#"Since these variables have the same name, it's easy to use the wrong one on accident. Give one of them a new name."#;
|
||||
|
||||
alloc.stack(vec![
|
||||
alloc
|
||||
.text("The ")
|
||||
.append(alloc.ident(shadow.value))
|
||||
.append(alloc.reflow(" name is first defined here:")),
|
||||
alloc.region(original_region),
|
||||
alloc.reflow("But then it's defined a second time here:"),
|
||||
alloc.region(shadow.region),
|
||||
alloc.reflow(line),
|
||||
])
|
||||
}
|
||||
|
||||
RuntimeError::LookupNotInScope(loc_name, options) => {
|
||||
not_found(alloc, loc_name.region, &loc_name.value, "value", options)
|
||||
}
|
||||
RuntimeError::CircularDef(mut idents, regions) => {
|
||||
let first = idents.remove(0);
|
||||
|
||||
if idents.is_empty() {
|
||||
alloc
|
||||
.reflow("The ")
|
||||
.append(alloc.ident(first.value.clone()))
|
||||
.append(alloc.reflow(
|
||||
" value is defined directly in terms of itself, causing an infinite loop.",
|
||||
))
|
||||
// TODO "are you trying to mutate a variable?
|
||||
// TODO hint?
|
||||
} else {
|
||||
alloc.stack(vec![
|
||||
alloc
|
||||
.reflow("The ")
|
||||
.append(alloc.ident(first.value.clone()))
|
||||
.append(
|
||||
alloc.reflow(" definition is causing a very tricky infinite loop:"),
|
||||
),
|
||||
alloc.region(regions[0].0),
|
||||
alloc
|
||||
.reflow("The ")
|
||||
.append(alloc.ident(first.value.clone()))
|
||||
.append(alloc.reflow(
|
||||
" value depends on itself through the following chain of definitions:",
|
||||
)),
|
||||
crate::report::cycle(
|
||||
alloc,
|
||||
4,
|
||||
alloc.ident(first.value),
|
||||
idents
|
||||
.into_iter()
|
||||
.map(|ident| alloc.ident(ident.value))
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
// TODO hint?
|
||||
])
|
||||
}
|
||||
}
|
||||
other => {
|
||||
// // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
|
||||
// UnsupportedPattern(Region),
|
||||
// UnrecognizedFunctionName(Located<InlinableString>),
|
||||
// SymbolNotExposed {
|
||||
// module_name: InlinableString,
|
||||
// ident: InlinableString,
|
||||
// region: Region,
|
||||
// },
|
||||
// ModuleNotImported {
|
||||
// module_name: InlinableString,
|
||||
// ident: InlinableString,
|
||||
// region: Region,
|
||||
// },
|
||||
// InvalidPrecedence(PrecedenceProblem, Region),
|
||||
// MalformedIdentifier(Box<str>, Region),
|
||||
// MalformedClosure(Region),
|
||||
// FloatOutsideRange(Box<str>),
|
||||
// IntOutsideRange(Box<str>),
|
||||
// InvalidHex(std::num::ParseIntError, Box<str>),
|
||||
// InvalidOctal(std::num::ParseIntError, Box<str>),
|
||||
// InvalidBinary(std::num::ParseIntError, Box<str>),
|
||||
// QualifiedPatternIdent(InlinableString),
|
||||
// CircularDef(
|
||||
// Vec<Located<Ident>>,
|
||||
// Vec<(Region /* pattern */, Region /* expr */)>,
|
||||
// ),
|
||||
//
|
||||
// /// When the author specifies a type annotation but no implementation
|
||||
// NoImplementation,
|
||||
todo!("TODO implement run time error reporting for {:?}", other)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn not_found<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
region: roc_region::all::Region,
|
||||
name: &str,
|
||||
thing: &'b str,
|
||||
options: MutSet<Box<str>>,
|
||||
) -> RocDocBuilder<'b> {
|
||||
use crate::error::r#type::suggest;
|
||||
|
||||
let mut suggestions = suggest::sort(name, options.iter().map(|v| v.as_ref()).collect());
|
||||
suggestions.truncate(4);
|
||||
|
||||
let default_no = alloc.concat(vec![
|
||||
alloc.reflow("Is there an "),
|
||||
alloc.keyword("import"),
|
||||
alloc.reflow(" or "),
|
||||
alloc.keyword("exposing"),
|
||||
alloc.reflow(" missing up-top"),
|
||||
]);
|
||||
|
||||
let default_yes = alloc.reflow("these names seem close though:");
|
||||
|
||||
let to_details = |no_suggestion_details, yes_suggestion_details| {
|
||||
if suggestions.is_empty() {
|
||||
no_suggestion_details
|
||||
} else {
|
||||
alloc.stack(vec![
|
||||
yes_suggestion_details,
|
||||
alloc
|
||||
.vcat(suggestions.into_iter().map(|v| alloc.string(v.to_string())))
|
||||
.indent(4),
|
||||
])
|
||||
}
|
||||
};
|
||||
|
||||
alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("I cannot find a `"),
|
||||
alloc.string(name.to_string()),
|
||||
alloc.reflow("` "),
|
||||
alloc.reflow(thing),
|
||||
]),
|
||||
alloc.region(region),
|
||||
to_details(default_no, default_yes),
|
||||
])
|
||||
}
|
4
compiler/reporting/src/error/mod.rs
Normal file
4
compiler/reporting/src/error/mod.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
pub mod canonicalize;
|
||||
pub mod mono;
|
||||
pub mod parse;
|
||||
pub mod r#type;
|
147
compiler/reporting/src/error/mono.rs
Normal file
147
compiler/reporting/src/error/mono.rs
Normal file
|
@ -0,0 +1,147 @@
|
|||
use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder};
|
||||
use std::path::PathBuf;
|
||||
use ven_pretty::DocAllocator;
|
||||
|
||||
pub fn mono_problem<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
filename: PathBuf,
|
||||
problem: roc_mono::expr::MonoProblem,
|
||||
) -> Report<'b> {
|
||||
use roc_mono::expr::MonoProblem::*;
|
||||
use roc_mono::pattern::Context::*;
|
||||
use roc_mono::pattern::Error::*;
|
||||
|
||||
match problem {
|
||||
PatternProblem(Incomplete(region, context, missing)) => match context {
|
||||
BadArg => {
|
||||
let doc = alloc.stack(vec![
|
||||
alloc.reflow("This pattern does not cover all the possibilities:"),
|
||||
alloc.region(region),
|
||||
alloc.reflow("Other possibilities include:"),
|
||||
unhandled_patterns_to_doc_block(alloc, missing),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow(
|
||||
"I would have to crash if I saw one of those! \
|
||||
So rather than pattern matching in function arguments, put a ",
|
||||
),
|
||||
alloc.keyword("when"),
|
||||
alloc.reflow(" in the function body to account for all possibilities."),
|
||||
]),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
title: "UNSAFE PATTERN".to_string(),
|
||||
doc,
|
||||
}
|
||||
}
|
||||
BadDestruct => {
|
||||
let doc = alloc.stack(vec![
|
||||
alloc.reflow("This pattern does not cover all the possibilities:"),
|
||||
alloc.region(region),
|
||||
alloc.reflow("Other possibilities include:"),
|
||||
unhandled_patterns_to_doc_block(alloc, missing),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow(
|
||||
"I would have to crash if I saw one of those! \
|
||||
You can use a binding to deconstruct a value if there is only ONE possibility. \
|
||||
Use a "
|
||||
),
|
||||
alloc.keyword("when"),
|
||||
alloc.reflow(" to account for all possibilities."),
|
||||
]),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
title: "UNSAFE PATTERN".to_string(),
|
||||
doc,
|
||||
}
|
||||
}
|
||||
BadCase => {
|
||||
let doc = alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("This "),
|
||||
alloc.keyword("when"),
|
||||
alloc.reflow(" does not cover all the possibilities:"),
|
||||
]),
|
||||
alloc.region(region),
|
||||
alloc.reflow("Other possibilities include:"),
|
||||
unhandled_patterns_to_doc_block(alloc, missing),
|
||||
alloc.reflow(
|
||||
"I would have to crash if I saw one of those! \
|
||||
Add branches for them!",
|
||||
),
|
||||
// alloc.hint().append(alloc.reflow("or use a hole.")),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
title: "UNSAFE PATTERN".to_string(),
|
||||
doc,
|
||||
}
|
||||
}
|
||||
},
|
||||
PatternProblem(Redundant {
|
||||
overall_region,
|
||||
branch_region,
|
||||
index,
|
||||
}) => {
|
||||
let doc = alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("The "),
|
||||
alloc.string(index.ordinal()),
|
||||
alloc.reflow(" pattern is redundant:"),
|
||||
]),
|
||||
alloc.region_with_subregion(overall_region, branch_region),
|
||||
alloc.reflow(
|
||||
"Any value of this shape will be handled by \
|
||||
a previous pattern, so this one should be removed.",
|
||||
),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
title: "REDUNDANT PATTERN".to_string(),
|
||||
doc,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unhandled_patterns_to_doc_block<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
patterns: Vec<roc_mono::pattern::Pattern>,
|
||||
) -> RocDocBuilder<'b> {
|
||||
alloc
|
||||
.vcat(patterns.into_iter().map(|v| pattern_to_doc(alloc, v)))
|
||||
.indent(4)
|
||||
.annotate(Annotation::TypeBlock)
|
||||
}
|
||||
|
||||
fn pattern_to_doc<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
pattern: roc_mono::pattern::Pattern,
|
||||
) -> RocDocBuilder<'b> {
|
||||
use roc_mono::pattern::Literal::*;
|
||||
use roc_mono::pattern::Pattern::*;
|
||||
|
||||
match pattern {
|
||||
Anything => alloc.text("_"),
|
||||
Literal(l) => match l {
|
||||
Int(i) => alloc.text(i.to_string()),
|
||||
Bit(true) => alloc.text("True"),
|
||||
Bit(false) => alloc.text("False"),
|
||||
Byte(b) => alloc.text(b.to_string()),
|
||||
Float(f) => alloc.text(f.to_string()),
|
||||
Str(s) => alloc.string(s.into()),
|
||||
},
|
||||
Ctor(_, tag_name, args) => {
|
||||
let arg_docs = args.into_iter().map(|v| pattern_to_doc(alloc, v));
|
||||
|
||||
let docs = std::iter::once(alloc.tag_name(tag_name)).chain(arg_docs);
|
||||
|
||||
alloc.intersperse(docs, alloc.space())
|
||||
}
|
||||
}
|
||||
}
|
42
compiler/reporting/src/error/parse.rs
Normal file
42
compiler/reporting/src/error/parse.rs
Normal file
|
@ -0,0 +1,42 @@
|
|||
use roc_parse::parser::{Fail, FailReason};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::report::{Report, RocDocAllocator};
|
||||
use ven_pretty::DocAllocator;
|
||||
|
||||
pub fn parse_problem<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
filename: PathBuf,
|
||||
problem: Fail,
|
||||
) -> Report<'b> {
|
||||
use FailReason::*;
|
||||
|
||||
match problem.reason {
|
||||
ArgumentsBeforeEquals(region) => {
|
||||
let doc = alloc.stack(vec![
|
||||
alloc.reflow("Unexpected tokens in front of the `=` symbol:"),
|
||||
alloc.region(region),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
doc,
|
||||
title: "PARSE PROBLEM".to_string(),
|
||||
}
|
||||
}
|
||||
other => {
|
||||
//
|
||||
// Unexpected(char, Region),
|
||||
// OutdentedTooFar,
|
||||
// ConditionFailed,
|
||||
// LineTooLong(u32 /* which line was too long */),
|
||||
// TooManyLines,
|
||||
// Eof(Region),
|
||||
// InvalidPattern,
|
||||
// ReservedKeyword(Region),
|
||||
// ArgumentsBeforeEquals,
|
||||
//}
|
||||
todo!("unhandled parse error: {:?}", other)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -29,9 +29,111 @@ pub fn type_problem<'b>(
|
|||
CircularType(region, symbol, overall_type) => {
|
||||
to_circular_report(alloc, filename, region, symbol, overall_type)
|
||||
}
|
||||
BadType(type_problem) => {
|
||||
use roc_types::types::Problem::*;
|
||||
match type_problem {
|
||||
BadTypeArguments {
|
||||
symbol,
|
||||
region,
|
||||
type_got,
|
||||
alias_needs,
|
||||
} => {
|
||||
let needed_arguments = if alias_needs == 1 {
|
||||
alloc.reflow("1 type argument")
|
||||
} else {
|
||||
alloc
|
||||
.text(alias_needs.to_string())
|
||||
.append(alloc.reflow(" type arguments"))
|
||||
};
|
||||
|
||||
let found_arguments = alloc.text(type_got.to_string());
|
||||
|
||||
let doc = alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("The "),
|
||||
alloc.symbol_unqualified(symbol),
|
||||
alloc.reflow(" alias expects "),
|
||||
needed_arguments,
|
||||
alloc.reflow(", but it got "),
|
||||
found_arguments,
|
||||
alloc.reflow(" instead:"),
|
||||
]),
|
||||
alloc.region(region),
|
||||
alloc.reflow("Are there missing parentheses?"),
|
||||
]);
|
||||
|
||||
let title = if type_got > alias_needs {
|
||||
"TOO MANY TYPE ARGUMENTS".to_string()
|
||||
} else {
|
||||
"TOO FEW TYPE ARGUMENTS".to_string()
|
||||
};
|
||||
|
||||
Report {
|
||||
filename,
|
||||
title,
|
||||
doc,
|
||||
}
|
||||
}
|
||||
CyclicAlias(symbol, region, others) => {
|
||||
let (doc, title) = cyclic_alias(alloc, symbol, region, others);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
title,
|
||||
doc,
|
||||
}
|
||||
}
|
||||
|
||||
other => panic!("unhandled bad type: {:?}", other),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cyclic_alias<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
symbol: Symbol,
|
||||
region: roc_region::all::Region,
|
||||
others: Vec<Symbol>,
|
||||
) -> (RocDocBuilder<'b>, String) {
|
||||
let doc = if others.is_empty() {
|
||||
alloc.stack(vec![
|
||||
alloc
|
||||
.reflow("The ")
|
||||
.append(alloc.symbol_unqualified(symbol))
|
||||
.append(alloc.reflow(" alias is self-recursive in an invalid way:")),
|
||||
alloc.region(region),
|
||||
alloc.reflow("Recursion in aliases is only allowed if recursion happens behind a tag."),
|
||||
])
|
||||
} else {
|
||||
alloc.stack(vec![
|
||||
alloc
|
||||
.reflow("The ")
|
||||
.append(alloc.symbol_unqualified(symbol))
|
||||
.append(alloc.reflow(" alias is recursive in an invalid way:")),
|
||||
alloc.region(region),
|
||||
alloc
|
||||
.reflow("The ")
|
||||
.append(alloc.symbol_unqualified(symbol))
|
||||
.append(alloc.reflow(
|
||||
" alias depends on itself through the following chain of definitions:",
|
||||
)),
|
||||
crate::report::cycle(
|
||||
alloc,
|
||||
4,
|
||||
alloc.symbol_unqualified(symbol),
|
||||
others
|
||||
.into_iter()
|
||||
.map(|other| alloc.symbol_unqualified(other))
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
alloc.reflow("Recursion in aliases is only allowed if recursion happens behind a tag."),
|
||||
])
|
||||
};
|
||||
|
||||
(doc, "CYCLIC ALIAS".to_string())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn report_mismatch<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
|
@ -1849,7 +1951,7 @@ mod report_text {
|
|||
fs: Vec<(Lowercase, ErrorType)>,
|
||||
ext: TypeExt,
|
||||
) -> RocDocBuilder<'b> {
|
||||
use crate::type_error::{ext_to_doc, to_doc};
|
||||
use crate::error::r#type::{ext_to_doc, to_doc};
|
||||
|
||||
let entry_to_doc = |(name, tipe): (Lowercase, ErrorType)| {
|
||||
(
|
||||
|
@ -2027,9 +2129,9 @@ mod report_text {
|
|||
|
||||
fn type_problem_to_pretty<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
problem: crate::type_error::Problem,
|
||||
problem: crate::error::r#type::Problem,
|
||||
) -> RocDocBuilder<'b> {
|
||||
use crate::type_error::Problem::*;
|
||||
use crate::error::r#type::Problem::*;
|
||||
|
||||
match problem {
|
||||
FieldTypo(typo, possibilities) => {
|
|
@ -11,5 +11,5 @@
|
|||
// re-enable this when working on performance optimizations than have it block PRs.
|
||||
#![allow(clippy::large_enum_variant)]
|
||||
|
||||
pub mod error;
|
||||
pub mod report;
|
||||
pub mod type_error;
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
use roc_collections::all::MutSet;
|
||||
use roc_module::ident::Ident;
|
||||
use roc_module::ident::{Lowercase, TagName, Uppercase};
|
||||
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
||||
use roc_problem::can::PrecedenceProblem::BothNonAssociative;
|
||||
use roc_problem::can::{Problem, RuntimeError};
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder, Render, RenderAnnotated};
|
||||
|
||||
pub use crate::error::canonicalize::can_problem;
|
||||
pub use crate::error::mono::mono_problem;
|
||||
pub use crate::error::parse::parse_problem;
|
||||
pub use crate::error::r#type::type_problem;
|
||||
|
||||
// const IS_WINDOWS: bool = std::env::consts::OS == "windows";
|
||||
const IS_WINDOWS: bool = false;
|
||||
|
||||
|
@ -17,7 +19,9 @@ const CYCLE_LN: &str = ["| ", "│ "][!IS_WINDOWS as usize];
|
|||
const CYCLE_MID: &str = ["| |", "│ ↓"][!IS_WINDOWS as usize];
|
||||
const CYCLE_END: &str = ["+-<---+", "└─────┘"][!IS_WINDOWS as usize];
|
||||
|
||||
fn cycle<'b>(
|
||||
const GUTTER_BAR: &str = " ┆";
|
||||
|
||||
pub fn cycle<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
indent: usize,
|
||||
name: RocDocBuilder<'b>,
|
||||
|
@ -142,417 +146,6 @@ pub const UNDERLINE_CODE: &str = "\u{001b}[4m";
|
|||
|
||||
pub const RESET_CODE: &str = "\u{001b}[0m";
|
||||
|
||||
pub fn mono_problem<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
filename: PathBuf,
|
||||
problem: roc_mono::expr::MonoProblem,
|
||||
) -> Report<'b> {
|
||||
use roc_mono::expr::MonoProblem::*;
|
||||
use roc_mono::pattern::Context::*;
|
||||
use roc_mono::pattern::Error::*;
|
||||
|
||||
match problem {
|
||||
PatternProblem(Incomplete(region, context, missing)) => match context {
|
||||
BadArg => {
|
||||
let doc = alloc.stack(vec![
|
||||
alloc.reflow("This pattern does not cover all the possibilities:"),
|
||||
alloc.region(region),
|
||||
alloc.reflow("Other possibilities include:"),
|
||||
unhandled_patterns_to_doc_block(alloc, missing),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow(
|
||||
"I would have to crash if I saw one of those! \
|
||||
So rather than pattern matching in function arguments, put a ",
|
||||
),
|
||||
alloc.keyword("when"),
|
||||
alloc.reflow(" in the function body to account for all possibilities."),
|
||||
]),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
title: "UNSAFE PATTERN".to_string(),
|
||||
doc,
|
||||
}
|
||||
}
|
||||
BadDestruct => {
|
||||
let doc = alloc.stack(vec![
|
||||
alloc.reflow("This pattern does not cover all the possibilities:"),
|
||||
alloc.region(region),
|
||||
alloc.reflow("Other possibilities include:"),
|
||||
unhandled_patterns_to_doc_block(alloc, missing),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow(
|
||||
"I would have to crash if I saw one of those! \
|
||||
You can use a binding to deconstruct a value if there is only ONE possibility. \
|
||||
Use a "
|
||||
),
|
||||
alloc.keyword("when"),
|
||||
alloc.reflow(" to account for all possibilities."),
|
||||
]),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
title: "UNSAFE PATTERN".to_string(),
|
||||
doc,
|
||||
}
|
||||
}
|
||||
BadCase => {
|
||||
let doc = alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("This "),
|
||||
alloc.keyword("when"),
|
||||
alloc.reflow(" does not cover all the possibilities:"),
|
||||
]),
|
||||
alloc.region(region),
|
||||
alloc.reflow("Other possibilities include:"),
|
||||
unhandled_patterns_to_doc_block(alloc, missing),
|
||||
alloc.reflow(
|
||||
"I would have to crash if I saw one of those! \
|
||||
Add branches for them!",
|
||||
),
|
||||
// alloc.hint().append(alloc.reflow("or use a hole.")),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
title: "UNSAFE PATTERN".to_string(),
|
||||
doc,
|
||||
}
|
||||
}
|
||||
},
|
||||
PatternProblem(Redundant {
|
||||
overall_region,
|
||||
branch_region,
|
||||
index,
|
||||
}) => {
|
||||
let doc = alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("The "),
|
||||
alloc.string(index.ordinal()),
|
||||
alloc.reflow(" pattern is redundant:"),
|
||||
]),
|
||||
alloc.region_with_subregion(overall_region, branch_region),
|
||||
alloc.reflow(
|
||||
"Any value of this shape will be handled by \
|
||||
a previous pattern, so this one should be removed.",
|
||||
),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
title: "REDUNDANT PATTERN".to_string(),
|
||||
doc,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unhandled_patterns_to_doc_block<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
patterns: Vec<roc_mono::pattern::Pattern>,
|
||||
) -> RocDocBuilder<'b> {
|
||||
alloc
|
||||
.vcat(patterns.into_iter().map(|v| pattern_to_doc(alloc, v)))
|
||||
.indent(4)
|
||||
.annotate(Annotation::TypeBlock)
|
||||
}
|
||||
|
||||
fn pattern_to_doc<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
pattern: roc_mono::pattern::Pattern,
|
||||
) -> RocDocBuilder<'b> {
|
||||
use roc_mono::pattern::Literal::*;
|
||||
use roc_mono::pattern::Pattern::*;
|
||||
// Anything,
|
||||
// Literal(Literal),
|
||||
// Ctor(Union, TagName, std::vec::Vec<Pattern>),
|
||||
match pattern {
|
||||
Anything => alloc.text("_"),
|
||||
Literal(l) => match l {
|
||||
Int(i) => alloc.text(i.to_string()),
|
||||
Bit(true) => alloc.text("True"),
|
||||
Bit(false) => alloc.text("False"),
|
||||
Byte(b) => alloc.text(b.to_string()),
|
||||
Float(f) => alloc.text(f.to_string()),
|
||||
Str(s) => alloc.string(s.into()),
|
||||
},
|
||||
Ctor(_, tag_name, args) => {
|
||||
let arg_docs = args.into_iter().map(|v| pattern_to_doc(alloc, v));
|
||||
|
||||
let docs = std::iter::once(alloc.tag_name(tag_name)).chain(arg_docs);
|
||||
|
||||
alloc.intersperse(docs, alloc.space())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn can_problem<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
filename: PathBuf,
|
||||
problem: Problem,
|
||||
) -> Report<'b> {
|
||||
let doc = match problem {
|
||||
Problem::UnusedDef(symbol, region) => {
|
||||
let line =
|
||||
r#" then remove it so future readers of your code don't wonder why it is there."#;
|
||||
|
||||
alloc.stack(vec![
|
||||
alloc
|
||||
.symbol_unqualified(symbol)
|
||||
.append(alloc.reflow(" is not used anywhere in your code.")),
|
||||
alloc.region(region),
|
||||
alloc
|
||||
.reflow("If you didn't intend on using ")
|
||||
.append(alloc.symbol_unqualified(symbol))
|
||||
.append(alloc.reflow(line)),
|
||||
])
|
||||
}
|
||||
Problem::UnusedImport(module_id, region) => alloc.concat(vec![
|
||||
alloc.reflow("Nothing from "),
|
||||
alloc.module(module_id),
|
||||
alloc.reflow(" is used in this module."),
|
||||
alloc.region(region),
|
||||
alloc.reflow("Since "),
|
||||
alloc.module(module_id),
|
||||
alloc.reflow(" isn't used, you don't need to import it."),
|
||||
]),
|
||||
Problem::UnusedArgument(closure_symbol, argument_symbol, region) => {
|
||||
let line = "\". Adding an underscore at the start of a variable name is a way of saying that the variable is not used.";
|
||||
|
||||
alloc.concat(vec![
|
||||
alloc.symbol_unqualified(closure_symbol),
|
||||
alloc.reflow(" doesn't use "),
|
||||
alloc.symbol_unqualified(argument_symbol),
|
||||
alloc.reflow("."),
|
||||
alloc.region(region),
|
||||
alloc.reflow("If you don't need "),
|
||||
alloc.symbol_unqualified(argument_symbol),
|
||||
alloc.reflow(", then you can just remove it. However, if you really do need "),
|
||||
alloc.symbol_unqualified(argument_symbol),
|
||||
alloc.reflow(" as an argument of "),
|
||||
alloc.symbol_unqualified(closure_symbol),
|
||||
alloc.reflow(", prefix it with an underscore, like this: \"_"),
|
||||
alloc.symbol_unqualified(argument_symbol),
|
||||
alloc.reflow(line),
|
||||
])
|
||||
}
|
||||
Problem::PrecedenceProblem(BothNonAssociative(region, left_bin_op, right_bin_op)) => alloc
|
||||
.stack(vec![
|
||||
if left_bin_op.value == right_bin_op.value {
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("Using more than one "),
|
||||
alloc.binop(left_bin_op.value),
|
||||
alloc.reflow(concat!(
|
||||
" like this requires parentheses,",
|
||||
" to clarify how things should be grouped.",
|
||||
)),
|
||||
])
|
||||
} else {
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("Using "),
|
||||
alloc.binop(left_bin_op.value),
|
||||
alloc.reflow(" and "),
|
||||
alloc.binop(right_bin_op.value),
|
||||
alloc.reflow(concat!(
|
||||
" together requires parentheses, ",
|
||||
"to clarify how they should be grouped."
|
||||
)),
|
||||
])
|
||||
},
|
||||
alloc.region(region),
|
||||
]),
|
||||
Problem::UnsupportedPattern(pattern_type, region) => {
|
||||
use roc_parse::pattern::PatternType::*;
|
||||
|
||||
let this_thing = match pattern_type {
|
||||
TopLevelDef => "a top-level definition:",
|
||||
DefExpr => "a value definition:",
|
||||
FunctionArg => "function arguments:",
|
||||
WhenBranch => unreachable!("all patterns are allowed in a When"),
|
||||
};
|
||||
|
||||
let suggestion = vec![
|
||||
alloc.reflow(
|
||||
"Patterns like this don't cover all possible shapes of the input type. Use a ",
|
||||
),
|
||||
alloc.keyword("when"),
|
||||
alloc.reflow(" ... "),
|
||||
alloc.keyword("is"),
|
||||
alloc.reflow(" instead."),
|
||||
];
|
||||
|
||||
alloc.stack(vec![
|
||||
alloc
|
||||
.reflow("This pattern is not allowed in ")
|
||||
.append(alloc.reflow(this_thing)),
|
||||
alloc.region(region),
|
||||
alloc.concat(suggestion),
|
||||
])
|
||||
}
|
||||
Problem::ShadowingInAnnotation {
|
||||
original_region,
|
||||
shadow,
|
||||
} => pretty_runtime_error(
|
||||
alloc,
|
||||
RuntimeError::Shadowing {
|
||||
original_region,
|
||||
shadow,
|
||||
},
|
||||
),
|
||||
Problem::RuntimeError(runtime_error) => pretty_runtime_error(alloc, runtime_error),
|
||||
};
|
||||
|
||||
Report {
|
||||
title: "SYNTAX PROBLEM".to_string(),
|
||||
filename,
|
||||
doc,
|
||||
}
|
||||
}
|
||||
|
||||
fn not_found<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
region: roc_region::all::Region,
|
||||
name: &str,
|
||||
thing: &str,
|
||||
options: MutSet<Box<str>>,
|
||||
) -> RocDocBuilder<'b> {
|
||||
use crate::type_error::suggest;
|
||||
|
||||
let mut suggestions = suggest::sort(name, options.iter().map(|v| v.as_ref()).collect());
|
||||
suggestions.truncate(4);
|
||||
|
||||
let default_no = alloc.concat(vec![
|
||||
alloc.reflow("Is there an "),
|
||||
alloc.keyword("import"),
|
||||
alloc.reflow(" or "),
|
||||
alloc.keyword("exposing"),
|
||||
alloc.reflow(" missing up-top"),
|
||||
]);
|
||||
|
||||
let default_yes = alloc.reflow("these names seem close though:");
|
||||
|
||||
let to_details = |no_suggestion_details, yes_suggestion_details| {
|
||||
if suggestions.is_empty() {
|
||||
no_suggestion_details
|
||||
} else {
|
||||
alloc.stack(vec![
|
||||
yes_suggestion_details,
|
||||
alloc
|
||||
.vcat(suggestions.into_iter().map(|v| alloc.string(v.to_string())))
|
||||
.indent(4),
|
||||
])
|
||||
}
|
||||
};
|
||||
|
||||
alloc.stack(vec![
|
||||
alloc.string(format!("I cannot find a `{}` {}", name, thing)),
|
||||
alloc.region(region),
|
||||
to_details(default_no, default_yes),
|
||||
])
|
||||
}
|
||||
fn pretty_runtime_error<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
runtime_error: RuntimeError,
|
||||
) -> RocDocBuilder<'b> {
|
||||
match runtime_error {
|
||||
RuntimeError::Shadowing {
|
||||
original_region,
|
||||
shadow,
|
||||
} => {
|
||||
let line = r#"Since these variables have the same name, it's easy to use the wrong one on accident. Give one of them a new name."#;
|
||||
|
||||
alloc.stack(vec![
|
||||
alloc
|
||||
.text("The ")
|
||||
.append(alloc.ident(shadow.value))
|
||||
.append(alloc.reflow(" name is first defined here:")),
|
||||
alloc.region(original_region),
|
||||
alloc.reflow("But then it's defined a second time here:"),
|
||||
alloc.region(shadow.region),
|
||||
alloc.reflow(line),
|
||||
])
|
||||
}
|
||||
|
||||
RuntimeError::LookupNotInScope(loc_name, options) => {
|
||||
not_found(alloc, loc_name.region, &loc_name.value, "value", options)
|
||||
}
|
||||
RuntimeError::CircularDef(mut idents, regions) => {
|
||||
let first = idents.remove(0);
|
||||
|
||||
if idents.is_empty() {
|
||||
alloc
|
||||
.reflow("The ")
|
||||
.append(alloc.ident(first.value.clone()))
|
||||
.append(alloc.reflow(
|
||||
" value is defined directly in terms of itself, causing an infinite loop.",
|
||||
))
|
||||
// TODO "are you trying to mutate a variable?
|
||||
// TODO hint?
|
||||
} else {
|
||||
alloc.stack(vec![
|
||||
alloc
|
||||
.reflow("The ")
|
||||
.append(alloc.ident(first.value.clone()))
|
||||
.append(
|
||||
alloc.reflow(" definition is causing a very tricky infinite loop:"),
|
||||
),
|
||||
alloc.region(regions[0].0),
|
||||
alloc
|
||||
.reflow("The ")
|
||||
.append(alloc.ident(first.value.clone()))
|
||||
.append(alloc.reflow(
|
||||
" value depends on itself through the following chain of definitions:",
|
||||
)),
|
||||
cycle(
|
||||
alloc,
|
||||
4,
|
||||
alloc.ident(first.value),
|
||||
idents
|
||||
.into_iter()
|
||||
.map(|ident| alloc.ident(ident.value))
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
// TODO hint?
|
||||
])
|
||||
}
|
||||
}
|
||||
other => {
|
||||
// // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
|
||||
// UnsupportedPattern(Region),
|
||||
// UnrecognizedFunctionName(Located<InlinableString>),
|
||||
// SymbolNotExposed {
|
||||
// module_name: InlinableString,
|
||||
// ident: InlinableString,
|
||||
// region: Region,
|
||||
// },
|
||||
// ModuleNotImported {
|
||||
// module_name: InlinableString,
|
||||
// ident: InlinableString,
|
||||
// region: Region,
|
||||
// },
|
||||
// InvalidPrecedence(PrecedenceProblem, Region),
|
||||
// MalformedIdentifier(Box<str>, Region),
|
||||
// MalformedClosure(Region),
|
||||
// FloatOutsideRange(Box<str>),
|
||||
// IntOutsideRange(Box<str>),
|
||||
// InvalidHex(std::num::ParseIntError, Box<str>),
|
||||
// InvalidOctal(std::num::ParseIntError, Box<str>),
|
||||
// InvalidBinary(std::num::ParseIntError, Box<str>),
|
||||
// QualifiedPatternIdent(InlinableString),
|
||||
// CircularDef(
|
||||
// Vec<Located<Ident>>,
|
||||
// Vec<(Region /* pattern */, Region /* expr */)>,
|
||||
// ),
|
||||
//
|
||||
// /// When the author specifies a type annotation but no implementation
|
||||
// NoImplementation,
|
||||
todo!("TODO implement run time error reporting for {:?}", other)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// define custom allocator struct so we can `impl RocDocAllocator` custom helpers
|
||||
pub struct RocDocAllocator<'a> {
|
||||
upstream: BoxAllocator,
|
||||
|
@ -711,6 +304,7 @@ impl<'a> RocDocAllocator<'a> {
|
|||
self.text(content.to_string()).annotate(Annotation::BinOp)
|
||||
}
|
||||
|
||||
/// Turns of backticks/colors in a block
|
||||
pub fn type_block(
|
||||
&'a self,
|
||||
content: DocBuilder<'a, Self, Annotation>,
|
||||
|
@ -724,6 +318,107 @@ impl<'a> RocDocAllocator<'a> {
|
|||
.annotate(Annotation::Hint)
|
||||
}
|
||||
|
||||
pub fn region_all_the_things(
|
||||
&'a self,
|
||||
region: roc_region::all::Region,
|
||||
sub_region1: roc_region::all::Region,
|
||||
sub_region2: roc_region::all::Region,
|
||||
error_annotation: Annotation,
|
||||
) -> DocBuilder<'a, Self, Annotation> {
|
||||
debug_assert!(region.contains(&sub_region1));
|
||||
debug_assert!(region.contains(&sub_region2));
|
||||
|
||||
// if true, the final line of the snippet will be some ^^^ that point to the region where
|
||||
// the problem is. Otherwise, the snippet will have a > on the lines that are in the regon
|
||||
// where the problem is.
|
||||
let error_highlight_line = region.start_line == region.end_line;
|
||||
|
||||
let max_line_number_length = (region.end_line + 1).to_string().len();
|
||||
let indent = 2;
|
||||
|
||||
let mut result = self.nil();
|
||||
for i in region.start_line..=region.end_line {
|
||||
let line_number_string = (i + 1).to_string();
|
||||
let line_number = line_number_string;
|
||||
let this_line_number_length = line_number.len();
|
||||
|
||||
let line = self.src_lines[i as usize];
|
||||
|
||||
let rest_of_line = if !line.trim().is_empty() {
|
||||
self.text(line).indent(indent)
|
||||
} else {
|
||||
self.nil()
|
||||
};
|
||||
|
||||
let highlight = !error_highlight_line
|
||||
&& ((i >= sub_region1.start_line && i <= sub_region1.end_line)
|
||||
|| (i >= sub_region2.start_line && i <= sub_region2.end_line));
|
||||
|
||||
let source_line = if highlight {
|
||||
self.text(" ".repeat(max_line_number_length - this_line_number_length))
|
||||
.append(self.text(line_number).annotate(Annotation::LineNumber))
|
||||
.append(self.text(GUTTER_BAR).annotate(Annotation::GutterBar))
|
||||
.append(self.text(">").annotate(error_annotation))
|
||||
.append(rest_of_line)
|
||||
} else if error_highlight_line {
|
||||
self.text(" ".repeat(max_line_number_length - this_line_number_length))
|
||||
.append(self.text(line_number).annotate(Annotation::LineNumber))
|
||||
.append(self.text(GUTTER_BAR).annotate(Annotation::GutterBar))
|
||||
.append(rest_of_line)
|
||||
} else {
|
||||
self.text(" ".repeat(max_line_number_length - this_line_number_length))
|
||||
.append(self.text(line_number).annotate(Annotation::LineNumber))
|
||||
.append(self.text(GUTTER_BAR).annotate(Annotation::GutterBar))
|
||||
.append(self.text(" "))
|
||||
.append(rest_of_line)
|
||||
};
|
||||
|
||||
result = result.append(source_line);
|
||||
|
||||
if i != region.end_line {
|
||||
result = result.append(self.line())
|
||||
}
|
||||
}
|
||||
|
||||
if error_highlight_line {
|
||||
let overlapping = sub_region2.start_col < sub_region1.end_col;
|
||||
|
||||
let highlight = if overlapping {
|
||||
self.text("^".repeat((sub_region2.end_col - sub_region1.start_col) as usize))
|
||||
} else {
|
||||
let highlight1 = "^".repeat((sub_region1.end_col - sub_region1.start_col) as usize);
|
||||
let highlight2 = if sub_region1 == sub_region2 {
|
||||
"".repeat(0)
|
||||
} else {
|
||||
"^".repeat((sub_region2.end_col - sub_region2.start_col) as usize)
|
||||
};
|
||||
let inbetween = " "
|
||||
.repeat((sub_region2.start_col.saturating_sub(sub_region1.end_col)) as usize);
|
||||
|
||||
self.text(highlight1)
|
||||
.append(self.text(inbetween))
|
||||
.append(self.text(highlight2))
|
||||
};
|
||||
|
||||
let highlight_line = self
|
||||
.line()
|
||||
.append(self.text(" ".repeat(max_line_number_length)))
|
||||
.append(self.text(GUTTER_BAR).annotate(Annotation::GutterBar))
|
||||
.append(if sub_region1.is_empty() && sub_region2.is_empty() {
|
||||
self.nil()
|
||||
} else {
|
||||
self.text(" ".repeat(sub_region1.start_col as usize))
|
||||
.indent(indent)
|
||||
.append(highlight)
|
||||
.annotate(error_annotation)
|
||||
});
|
||||
|
||||
result = result.append(highlight_line);
|
||||
}
|
||||
|
||||
result.annotate(Annotation::CodeBlock)
|
||||
}
|
||||
|
||||
pub fn region_with_subregion(
|
||||
&'a self,
|
||||
region: roc_region::all::Region,
|
||||
|
@ -731,6 +426,11 @@ impl<'a> RocDocAllocator<'a> {
|
|||
) -> DocBuilder<'a, Self, Annotation> {
|
||||
debug_assert!(region.contains(&sub_region));
|
||||
|
||||
// If the outer region takes more than 1 full screen (~60 lines), only show the inner region
|
||||
if region.end_line - region.start_line > 60 {
|
||||
return self.region_with_subregion(sub_region, sub_region);
|
||||
}
|
||||
|
||||
// if true, the final line of the snippet will be some ^^^ that point to the region where
|
||||
// the problem is. Otherwise, the snippet will have a > on the lines that are in the regon
|
||||
// where the problem is.
|
||||
|
@ -761,18 +461,18 @@ impl<'a> RocDocAllocator<'a> {
|
|||
{
|
||||
self.text(" ".repeat(max_line_number_length - this_line_number_length))
|
||||
.append(self.text(line_number).annotate(Annotation::LineNumber))
|
||||
.append(self.text(" ┆").annotate(Annotation::GutterBar))
|
||||
.append(self.text(GUTTER_BAR).annotate(Annotation::GutterBar))
|
||||
.append(self.text(">").annotate(Annotation::Error))
|
||||
.append(rest_of_line)
|
||||
} else if error_highlight_line {
|
||||
self.text(" ".repeat(max_line_number_length - this_line_number_length))
|
||||
.append(self.text(line_number).annotate(Annotation::LineNumber))
|
||||
.append(self.text(" ┆").annotate(Annotation::GutterBar))
|
||||
.append(self.text(GUTTER_BAR).annotate(Annotation::GutterBar))
|
||||
.append(rest_of_line)
|
||||
} else {
|
||||
self.text(" ".repeat(max_line_number_length - this_line_number_length))
|
||||
.append(self.text(line_number).annotate(Annotation::LineNumber))
|
||||
.append(self.text(" ┆").annotate(Annotation::GutterBar))
|
||||
.append(self.text(GUTTER_BAR).annotate(Annotation::GutterBar))
|
||||
.append(self.text(" "))
|
||||
.append(rest_of_line)
|
||||
};
|
||||
|
@ -789,7 +489,7 @@ impl<'a> RocDocAllocator<'a> {
|
|||
let highlight_line = self
|
||||
.line()
|
||||
.append(self.text(" ".repeat(max_line_number_length)))
|
||||
.append(self.text(" ┆").annotate(Annotation::GutterBar))
|
||||
.append(self.text(GUTTER_BAR).annotate(Annotation::GutterBar))
|
||||
.append(if highlight_text.is_empty() {
|
||||
self.nil()
|
||||
} else {
|
||||
|
@ -808,6 +508,40 @@ impl<'a> RocDocAllocator<'a> {
|
|||
self.region_with_subregion(region, region)
|
||||
}
|
||||
|
||||
pub fn region_without_error(
|
||||
&'a self,
|
||||
region: roc_region::all::Region,
|
||||
) -> DocBuilder<'a, Self, Annotation> {
|
||||
let mut result = self.nil();
|
||||
for i in region.start_line..=region.end_line {
|
||||
let line = if i == region.start_line {
|
||||
if i == region.end_line {
|
||||
&self.src_lines[i as usize][region.start_col as usize..region.end_col as usize]
|
||||
} else {
|
||||
&self.src_lines[i as usize][region.start_col as usize..]
|
||||
}
|
||||
} else if i == region.end_line {
|
||||
&self.src_lines[i as usize][0..region.end_col as usize]
|
||||
} else {
|
||||
self.src_lines[i as usize]
|
||||
};
|
||||
|
||||
let rest_of_line = if !line.trim().is_empty() {
|
||||
self.text(line).annotate(Annotation::CodeBlock)
|
||||
} else {
|
||||
self.nil()
|
||||
};
|
||||
|
||||
result = result.append(rest_of_line);
|
||||
|
||||
if i != region.end_line {
|
||||
result = result.append(self.line())
|
||||
}
|
||||
}
|
||||
|
||||
result.indent(4)
|
||||
}
|
||||
|
||||
pub fn ident(&'a self, ident: Ident) -> DocBuilder<'a, Self, Annotation> {
|
||||
self.text(format!("{}", ident.as_inline_str()))
|
||||
.annotate(Annotation::Symbol)
|
||||
|
@ -843,6 +577,7 @@ pub enum Annotation {
|
|||
pub struct CiWrite<W> {
|
||||
style_stack: Vec<Annotation>,
|
||||
in_type_block: bool,
|
||||
in_code_block: bool,
|
||||
upstream: W,
|
||||
}
|
||||
|
||||
|
@ -851,6 +586,7 @@ impl<W> CiWrite<W> {
|
|||
CiWrite {
|
||||
style_stack: vec![],
|
||||
in_type_block: false,
|
||||
in_code_block: false,
|
||||
upstream,
|
||||
}
|
||||
}
|
||||
|
@ -898,6 +634,9 @@ where
|
|||
TypeBlock => {
|
||||
self.in_type_block = true;
|
||||
}
|
||||
CodeBlock => {
|
||||
self.in_code_block = true;
|
||||
}
|
||||
Emphasized => {
|
||||
self.write_str("*")?;
|
||||
}
|
||||
|
@ -906,7 +645,7 @@ where
|
|||
}
|
||||
GlobalTag | PrivateTag | Keyword | RecordField | Symbol | Typo | TypoSuggestion
|
||||
| TypeVariable
|
||||
if !self.in_type_block =>
|
||||
if !self.in_type_block && !self.in_code_block =>
|
||||
{
|
||||
self.write_str("`")?;
|
||||
}
|
||||
|
@ -926,6 +665,9 @@ where
|
|||
TypeBlock => {
|
||||
self.in_type_block = false;
|
||||
}
|
||||
CodeBlock => {
|
||||
self.in_code_block = false;
|
||||
}
|
||||
Emphasized => {
|
||||
self.write_str("*")?;
|
||||
}
|
||||
|
@ -934,7 +676,7 @@ where
|
|||
}
|
||||
GlobalTag | PrivateTag | Keyword | RecordField | Symbol | Typo | TypoSuggestion
|
||||
| TypeVariable
|
||||
if !self.in_type_block =>
|
||||
if !self.in_type_block && !self.in_code_block =>
|
||||
{
|
||||
self.write_str("`")?;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
extern crate bumpalo;
|
||||
|
||||
use self::bumpalo::Bump;
|
||||
use roc_builtins::unique::uniq_stdlib;
|
||||
use roc_can::constraint::Constraint;
|
||||
use roc_can::env::Env;
|
||||
use roc_can::expected::Expected;
|
||||
|
@ -11,13 +10,12 @@ use roc_can::scope::Scope;
|
|||
use roc_collections::all::{ImMap, MutMap, SendMap, SendSet};
|
||||
use roc_constrain::expr::constrain_expr;
|
||||
use roc_constrain::module::{constrain_imported_values, load_builtin_aliases, Import};
|
||||
use roc_module::ident::Ident;
|
||||
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol};
|
||||
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds};
|
||||
use roc_parse::ast::{self, Attempting};
|
||||
use roc_parse::blankspace::space0_before;
|
||||
use roc_parse::parser::{loc, Fail, Parser, State};
|
||||
use roc_problem::can::Problem;
|
||||
use roc_region::all::{Located, Region};
|
||||
use roc_region::all::Located;
|
||||
use roc_solve::solve;
|
||||
use roc_types::subs::{Content, Subs, VarStore, Variable};
|
||||
use roc_types::types::Type;
|
||||
|
@ -103,96 +101,10 @@ pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast
|
|||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn can_expr(expr_str: &str) -> CanExprOut {
|
||||
pub fn can_expr(expr_str: &str) -> Result<CanExprOut, ParseErrOut> {
|
||||
can_expr_with(&Bump::new(), test_home(), expr_str)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn uniq_expr(
|
||||
expr_str: &str,
|
||||
) -> (
|
||||
Located<Expr>,
|
||||
Output,
|
||||
Vec<Problem>,
|
||||
Subs,
|
||||
Variable,
|
||||
Constraint,
|
||||
ModuleId,
|
||||
Interns,
|
||||
) {
|
||||
let declared_idents: &ImMap<Ident, (Symbol, Region)> = &ImMap::default();
|
||||
|
||||
uniq_expr_with(&Bump::new(), expr_str, declared_idents)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn uniq_expr_with(
|
||||
arena: &Bump,
|
||||
expr_str: &str,
|
||||
declared_idents: &ImMap<Ident, (Symbol, Region)>,
|
||||
) -> (
|
||||
Located<Expr>,
|
||||
Output,
|
||||
Vec<Problem>,
|
||||
Subs,
|
||||
Variable,
|
||||
Constraint,
|
||||
ModuleId,
|
||||
Interns,
|
||||
) {
|
||||
let home = test_home();
|
||||
let CanExprOut {
|
||||
loc_expr,
|
||||
output,
|
||||
problems,
|
||||
var_store: old_var_store,
|
||||
var,
|
||||
interns,
|
||||
..
|
||||
} = can_expr_with(arena, home, expr_str);
|
||||
|
||||
// double check
|
||||
let var_store = VarStore::new(old_var_store.fresh());
|
||||
|
||||
let expected2 = Expected::NoExpectation(Type::Variable(var));
|
||||
let constraint = roc_constrain::uniq::constrain_declaration(
|
||||
home,
|
||||
&var_store,
|
||||
Region::zero(),
|
||||
&loc_expr,
|
||||
declared_idents,
|
||||
expected2,
|
||||
);
|
||||
|
||||
let stdlib = uniq_stdlib();
|
||||
|
||||
let types = stdlib.types;
|
||||
let imports: Vec<_> = types
|
||||
.iter()
|
||||
.map(|(symbol, (solved_type, region))| Import {
|
||||
loc_symbol: Located::at(*region, *symbol),
|
||||
solved_type: solved_type,
|
||||
})
|
||||
.collect();
|
||||
|
||||
// load builtin values
|
||||
|
||||
// TODO what to do with those rigids?
|
||||
let (_introduced_rigids, constraint) =
|
||||
constrain_imported_values(imports, constraint, &var_store);
|
||||
|
||||
// load builtin types
|
||||
let mut constraint = load_builtin_aliases(&stdlib.aliases, constraint, &var_store);
|
||||
|
||||
constraint.instantiate_aliases(&var_store);
|
||||
|
||||
let subs2 = Subs::new(var_store.into());
|
||||
|
||||
(
|
||||
loc_expr, output, problems, subs2, var, constraint, home, interns,
|
||||
)
|
||||
}
|
||||
|
||||
pub struct CanExprOut {
|
||||
pub loc_expr: Located<Expr>,
|
||||
pub output: Output,
|
||||
|
@ -204,14 +116,34 @@ pub struct CanExprOut {
|
|||
pub constraint: Constraint,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParseErrOut {
|
||||
pub fail: Fail,
|
||||
pub home: ModuleId,
|
||||
pub interns: Interns,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut {
|
||||
let loc_expr = parse_loc_with(&arena, expr_str).unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"can_expr_with() got a parse error when attempting to canonicalize:\n\n{:?} {:?}",
|
||||
expr_str, e
|
||||
)
|
||||
});
|
||||
pub fn can_expr_with(
|
||||
arena: &Bump,
|
||||
home: ModuleId,
|
||||
expr_str: &str,
|
||||
) -> Result<CanExprOut, ParseErrOut> {
|
||||
let loc_expr = match parse_loc_with(&arena, expr_str) {
|
||||
Ok(e) => e,
|
||||
Err(fail) => {
|
||||
let interns = Interns {
|
||||
module_ids: ModuleIds::default(),
|
||||
all_ident_ids: MutMap::default(),
|
||||
};
|
||||
|
||||
return Err(ParseErrOut {
|
||||
fail,
|
||||
interns,
|
||||
home,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let var_store = VarStore::default();
|
||||
let var = var_store.fresh();
|
||||
|
@ -283,7 +215,7 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut
|
|||
all_ident_ids,
|
||||
};
|
||||
|
||||
CanExprOut {
|
||||
Ok(CanExprOut {
|
||||
loc_expr,
|
||||
output,
|
||||
problems: env.problems,
|
||||
|
@ -292,7 +224,7 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut
|
|||
interns,
|
||||
var,
|
||||
constraint,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
|
|
@ -14,15 +14,15 @@ mod test_reporting {
|
|||
use roc_module::symbol::{Interns, ModuleId};
|
||||
use roc_mono::expr::{Expr, Procs};
|
||||
use roc_reporting::report::{
|
||||
can_problem, mono_problem, Report, BLUE_CODE, BOLD_CODE, CYAN_CODE, DEFAULT_PALETTE,
|
||||
GREEN_CODE, MAGENTA_CODE, RED_CODE, RESET_CODE, UNDERLINE_CODE, WHITE_CODE, YELLOW_CODE,
|
||||
can_problem, mono_problem, parse_problem, type_problem, Report, BLUE_CODE, BOLD_CODE,
|
||||
CYAN_CODE, DEFAULT_PALETTE, GREEN_CODE, MAGENTA_CODE, RED_CODE, RESET_CODE, UNDERLINE_CODE,
|
||||
WHITE_CODE, YELLOW_CODE,
|
||||
};
|
||||
use roc_reporting::type_error::type_problem;
|
||||
use roc_types::pretty_print::name_all_type_vars;
|
||||
use roc_types::subs::Subs;
|
||||
use std::path::PathBuf;
|
||||
// use roc_region::all;
|
||||
use crate::helpers::{can_expr, infer_expr, CanExprOut};
|
||||
use crate::helpers::{can_expr, infer_expr, CanExprOut, ParseErrOut};
|
||||
use roc_reporting::report::{RocDocAllocator, RocDocBuilder};
|
||||
use roc_solve::solve;
|
||||
|
||||
|
@ -43,13 +43,16 @@ mod test_reporting {
|
|||
|
||||
fn infer_expr_help(
|
||||
expr_src: &str,
|
||||
) -> (
|
||||
Vec<solve::TypeError>,
|
||||
Vec<roc_problem::can::Problem>,
|
||||
Vec<roc_mono::expr::MonoProblem>,
|
||||
ModuleId,
|
||||
Interns,
|
||||
) {
|
||||
) -> Result<
|
||||
(
|
||||
Vec<solve::TypeError>,
|
||||
Vec<roc_problem::can::Problem>,
|
||||
Vec<roc_mono::expr::MonoProblem>,
|
||||
ModuleId,
|
||||
Interns,
|
||||
),
|
||||
ParseErrOut,
|
||||
> {
|
||||
let CanExprOut {
|
||||
loc_expr,
|
||||
output,
|
||||
|
@ -60,7 +63,7 @@ mod test_reporting {
|
|||
mut interns,
|
||||
problems: can_problems,
|
||||
..
|
||||
} = can_expr(expr_src);
|
||||
} = can_expr(expr_src)?;
|
||||
let mut subs = Subs::new(var_store.into());
|
||||
|
||||
for (var, name) in output.introduced_variables.name_by_var {
|
||||
|
@ -99,7 +102,7 @@ mod test_reporting {
|
|||
);
|
||||
}
|
||||
|
||||
(unify_problems, can_problems, mono_problems, home, interns)
|
||||
Ok((unify_problems, can_problems, mono_problems, home, interns))
|
||||
}
|
||||
|
||||
fn list_reports<F>(src: &str, buf: &mut String, callback: F)
|
||||
|
@ -108,40 +111,57 @@ mod test_reporting {
|
|||
{
|
||||
use ven_pretty::DocAllocator;
|
||||
|
||||
let (type_problems, can_problems, mono_problems, home, interns) = infer_expr_help(src);
|
||||
|
||||
let src_lines: Vec<&str> = src.split('\n').collect();
|
||||
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
|
||||
|
||||
let filename = filename_from_string(r"\code\proj\Main.roc");
|
||||
let mut reports = Vec::new();
|
||||
|
||||
for problem in can_problems {
|
||||
let report = can_problem(&alloc, filename.clone(), problem.clone());
|
||||
reports.push(report);
|
||||
match infer_expr_help(src) {
|
||||
Err(parse_err) => {
|
||||
let ParseErrOut {
|
||||
fail,
|
||||
home,
|
||||
interns,
|
||||
} = parse_err;
|
||||
|
||||
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
|
||||
|
||||
let doc = parse_problem(&alloc, filename, fail);
|
||||
|
||||
callback(doc.pretty(&alloc).append(alloc.line()), buf)
|
||||
}
|
||||
Ok((type_problems, can_problems, mono_problems, home, interns)) => {
|
||||
let mut reports = Vec::new();
|
||||
|
||||
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
|
||||
|
||||
for problem in can_problems {
|
||||
let report = can_problem(&alloc, filename.clone(), problem.clone());
|
||||
reports.push(report);
|
||||
}
|
||||
|
||||
for problem in type_problems {
|
||||
let report = type_problem(&alloc, filename.clone(), problem.clone());
|
||||
reports.push(report);
|
||||
}
|
||||
|
||||
for problem in mono_problems {
|
||||
let report = mono_problem(&alloc, filename.clone(), problem.clone());
|
||||
reports.push(report);
|
||||
}
|
||||
|
||||
let has_reports = !reports.is_empty();
|
||||
|
||||
let doc = alloc
|
||||
.stack(reports.into_iter().map(|v| v.pretty(&alloc)))
|
||||
.append(if has_reports {
|
||||
alloc.line()
|
||||
} else {
|
||||
alloc.nil()
|
||||
});
|
||||
|
||||
callback(doc, buf)
|
||||
}
|
||||
}
|
||||
|
||||
for problem in type_problems {
|
||||
let report = type_problem(&alloc, filename.clone(), problem.clone());
|
||||
reports.push(report);
|
||||
}
|
||||
|
||||
for problem in mono_problems {
|
||||
let report = mono_problem(&alloc, filename.clone(), problem.clone());
|
||||
reports.push(report);
|
||||
}
|
||||
|
||||
let has_reports = !reports.is_empty();
|
||||
|
||||
let doc = alloc
|
||||
.stack(reports.into_iter().map(|v| v.pretty(&alloc)))
|
||||
.append(if has_reports {
|
||||
alloc.line()
|
||||
} else {
|
||||
alloc.nil()
|
||||
});
|
||||
|
||||
callback(doc, buf)
|
||||
}
|
||||
|
||||
fn report_problem_as(src: &str, expected_rendering: &str) {
|
||||
|
@ -491,7 +511,8 @@ mod test_reporting {
|
|||
"#
|
||||
);
|
||||
|
||||
let (_type_problems, _can_problems, _mono_problems, home, interns) = infer_expr_help(src);
|
||||
let (_type_problems, _can_problems, _mono_problems, home, interns) =
|
||||
infer_expr_help(src).expect("parse error");
|
||||
|
||||
let mut buf = String::new();
|
||||
let src_lines: Vec<&str> = src.split('\n').collect();
|
||||
|
@ -521,7 +542,7 @@ mod test_reporting {
|
|||
);
|
||||
|
||||
let (_type_problems, _can_problems, _mono_problems, home, mut interns) =
|
||||
infer_expr_help(src);
|
||||
infer_expr_help(src).expect("parse error");
|
||||
|
||||
let mut buf = String::new();
|
||||
let src_lines: Vec<&str> = src.split('\n').collect();
|
||||
|
@ -2257,4 +2278,439 @@ mod test_reporting {
|
|||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn circular_alias() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
Foo : { x: Bar }
|
||||
Bar : { y : Foo }
|
||||
|
||||
f : Foo
|
||||
|
||||
f
|
||||
"#
|
||||
),
|
||||
// should not report Bar as unused!
|
||||
indoc!(
|
||||
r#"
|
||||
-- CYCLIC ALIAS ----------------------------------------------------------------
|
||||
|
||||
The `Bar` alias is recursive in an invalid way:
|
||||
|
||||
2 ┆ Bar : { y : Foo }
|
||||
┆ ^^^^^^^^^^^
|
||||
|
||||
The `Bar` alias depends on itself through the following chain of
|
||||
definitions:
|
||||
|
||||
┌─────┐
|
||||
│ Bar
|
||||
│ ↓
|
||||
│ Foo
|
||||
└─────┘
|
||||
|
||||
Recursion in aliases is only allowed if recursion happens behind a
|
||||
tag.
|
||||
|
||||
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||
|
||||
`Bar` is not used anywhere in your code.
|
||||
|
||||
2 ┆ Bar : { y : Foo }
|
||||
┆ ^^^^^^^^^^^^^^^^^
|
||||
|
||||
If you didn't intend on using `Bar` then remove it so future readers of
|
||||
your code don't wonder why it is there.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn self_recursive_alias() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
Foo : { x : Foo }
|
||||
|
||||
f : Foo
|
||||
f = 3
|
||||
|
||||
f
|
||||
"#
|
||||
),
|
||||
// should not report Bar as unused!
|
||||
indoc!(
|
||||
r#"
|
||||
-- CYCLIC ALIAS ----------------------------------------------------------------
|
||||
|
||||
The `Foo` alias is self-recursive in an invalid way:
|
||||
|
||||
1 ┆ Foo : { x : Foo }
|
||||
┆ ^^^
|
||||
|
||||
Recursion in aliases is only allowed if recursion happens behind a
|
||||
tag.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_duplicate_field_same_type() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
{ x: 4, y: 3, x: 4 }
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||
|
||||
This record defines the `.x` field twice!
|
||||
|
||||
1 ┆ { x: 4, y: 3, x: 4 }
|
||||
┆ ^^^^ ^^^^
|
||||
|
||||
In the rest of the program, I will only use the latter definition:
|
||||
|
||||
1 ┆ { x: 4, y: 3, x: 4 }
|
||||
┆ ^^^^
|
||||
|
||||
For clarity, remove the previous `.x` definitions from this record.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_duplicate_field_different_types() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
{ x: 4, y: 3, x: "foo" }
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||
|
||||
This record defines the `.x` field twice!
|
||||
|
||||
1 ┆ { x: 4, y: 3, x: "foo" }
|
||||
┆ ^^^^ ^^^^^^^^
|
||||
|
||||
In the rest of the program, I will only use the latter definition:
|
||||
|
||||
1 ┆ { x: 4, y: 3, x: "foo" }
|
||||
┆ ^^^^^^^^
|
||||
|
||||
For clarity, remove the previous `.x` definitions from this record.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_duplicate_field_multiline() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
{
|
||||
x: 4,
|
||||
y: 3,
|
||||
x: "foo"
|
||||
}
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||
|
||||
This record defines the `.x` field twice!
|
||||
|
||||
1 ┆ {
|
||||
2 ┆> x: 4,
|
||||
3 ┆ y: 3,
|
||||
4 ┆> x: "foo"
|
||||
5 ┆ }
|
||||
|
||||
In the rest of the program, I will only use the latter definition:
|
||||
|
||||
1 ┆ {
|
||||
2 ┆ x: 4,
|
||||
3 ┆ y: 3,
|
||||
4 ┆> x: "foo"
|
||||
5 ┆ }
|
||||
|
||||
For clarity, remove the previous `.x` definitions from this record.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_update_duplicate_field_multiline() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
\r ->
|
||||
{ r &
|
||||
x: 4,
|
||||
y: 3,
|
||||
x: "foo"
|
||||
}
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||
|
||||
This record defines the `.x` field twice!
|
||||
|
||||
2 ┆ { r &
|
||||
3 ┆> x: 4,
|
||||
4 ┆ y: 3,
|
||||
5 ┆> x: "foo"
|
||||
6 ┆ }
|
||||
|
||||
In the rest of the program, I will only use the latter definition:
|
||||
|
||||
2 ┆ { r &
|
||||
3 ┆ x: 4,
|
||||
4 ┆ y: 3,
|
||||
5 ┆> x: "foo"
|
||||
6 ┆ }
|
||||
|
||||
For clarity, remove the previous `.x` definitions from this record.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_type_duplicate_field() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
a : { foo : Int, bar : Float, foo : Str }
|
||||
a = { bar: 3.0, foo: "foo" }
|
||||
|
||||
a
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||
|
||||
This record type defines the `.foo` field twice!
|
||||
|
||||
1 ┆ a : { foo : Int, bar : Float, foo : Str }
|
||||
┆ ^^^^^^^^^ ^^^^^^^^^
|
||||
|
||||
In the rest of the program, I will only use the latter definition:
|
||||
|
||||
1 ┆ a : { foo : Int, bar : Float, foo : Str }
|
||||
┆ ^^^^^^^^^
|
||||
|
||||
For clarity, remove the previous `.foo` definitions from this record
|
||||
type.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tag_union_duplicate_tag() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
a : [ Foo Int, Bar Float, Foo Str ]
|
||||
a = Foo "foo"
|
||||
|
||||
a
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||
|
||||
This tag union type defines the `Foo` tag twice!
|
||||
|
||||
1 ┆ a : [ Foo Int, Bar Float, Foo Str ]
|
||||
┆ ^^^^^^^ ^^^^^^^
|
||||
|
||||
In the rest of the program, I will only use the latter definition:
|
||||
|
||||
1 ┆ a : [ Foo Int, Bar Float, Foo Str ]
|
||||
┆ ^^^^^^^
|
||||
|
||||
For clarity, remove the previous `Foo` definitions from this tag union
|
||||
type.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_num() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
a : Num Int Float
|
||||
a = 3
|
||||
|
||||
a
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
-- TOO MANY TYPE ARGUMENTS -----------------------------------------------------
|
||||
|
||||
The `Num` alias expects 1 type argument, but it got 2 instead:
|
||||
|
||||
1 ┆ a : Num Int Float
|
||||
┆ ^^^^^^^^^^^^^
|
||||
|
||||
Are there missing parentheses?
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_num_fn() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
f : Bool -> Num Int Float
|
||||
f = \_ -> 3
|
||||
|
||||
f
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
-- TOO MANY TYPE ARGUMENTS -----------------------------------------------------
|
||||
|
||||
The `Num` alias expects 1 type argument, but it got 2 instead:
|
||||
|
||||
1 ┆ f : Bool -> Num Int Float
|
||||
┆ ^^^^^^^^^^^^^
|
||||
|
||||
Are there missing parentheses?
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn too_few_type_arguments() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
Pair a b : [ Pair a b ]
|
||||
|
||||
x : Pair Int
|
||||
x = 3
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
-- TOO FEW TYPE ARGUMENTS ------------------------------------------------------
|
||||
|
||||
The `Pair` alias expects 2 type arguments, but it got 1 instead:
|
||||
|
||||
3 ┆ x : Pair Int
|
||||
┆ ^^^^^^^^
|
||||
|
||||
Are there missing parentheses?
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn too_many_type_arguments() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
Pair a b : [ Pair a b ]
|
||||
|
||||
x : Pair Int Int Int
|
||||
x = 3
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
-- TOO MANY TYPE ARGUMENTS -----------------------------------------------------
|
||||
|
||||
The `Pair` alias expects 2 type arguments, but it got 3 instead:
|
||||
|
||||
3 ┆ x : Pair Int Int Int
|
||||
┆ ^^^^^^^^^^^^^^^^
|
||||
|
||||
Are there missing parentheses?
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn phantom_type_variable() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
Foo a : [ Foo ]
|
||||
|
||||
f : Foo Int
|
||||
|
||||
f
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||
|
||||
The `a` type variable is not used in the `Foo` alias definition:
|
||||
|
||||
1 ┆ Foo a : [ Foo ]
|
||||
┆ ^
|
||||
|
||||
Roc does not allow unused type parameters!
|
||||
|
||||
Hint: If you want an unused type parameter (a so-called "phantom
|
||||
type"), read the guide section on phantom data.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn elm_function_syntax() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
f x y = x
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
-- PARSE PROBLEM ---------------------------------------------------------------
|
||||
|
||||
Unexpected tokens in front of the `=` symbol:
|
||||
|
||||
1 ┆ f x y = x
|
||||
┆ ^^^
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ roc_builtins = { path = "../builtins" }
|
|||
roc_problem = { path = "../problem" }
|
||||
roc_parse = { path = "../parse" }
|
||||
roc_solve = { path = "../solve" }
|
||||
pretty_assertions = "0.5.1 "
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
|
|
|
@ -21,6 +21,7 @@ pub enum TypeError {
|
|||
BadExpr(Region, Category, ErrorType, Expected<ErrorType>),
|
||||
BadPattern(Region, PatternCategory, ErrorType, PExpected<ErrorType>),
|
||||
CircularType(Region, Symbol, ErrorType),
|
||||
BadType(roc_types::types::Problem),
|
||||
}
|
||||
|
||||
pub type SubsByModule = MutMap<ModuleId, ExposedModuleTypes>;
|
||||
|
@ -166,6 +167,13 @@ fn solve(
|
|||
|
||||
problems.push(problem);
|
||||
|
||||
state
|
||||
}
|
||||
BadType(vars, problem) => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
problems.push(TypeError::BadType(problem));
|
||||
|
||||
state
|
||||
}
|
||||
}
|
||||
|
@ -228,6 +236,13 @@ fn solve(
|
|||
|
||||
problems.push(problem);
|
||||
|
||||
state
|
||||
}
|
||||
BadType(vars, problem) => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
problems.push(TypeError::BadType(problem));
|
||||
|
||||
state
|
||||
}
|
||||
}
|
||||
|
@ -278,6 +293,13 @@ fn solve(
|
|||
|
||||
problems.push(problem);
|
||||
|
||||
state
|
||||
}
|
||||
BadType(vars, problem) => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
problems.push(TypeError::BadType(problem));
|
||||
|
||||
state
|
||||
}
|
||||
}
|
||||
|
@ -829,7 +851,7 @@ fn circular_error(
|
|||
loc_var: &Located<Variable>,
|
||||
) {
|
||||
let var = loc_var.value;
|
||||
let error_type = subs.var_to_error_type(var);
|
||||
let (error_type, _) = subs.var_to_error_type(var);
|
||||
let problem = TypeError::CircularType(loc_var.region, symbol, error_type);
|
||||
|
||||
subs.set_content(var, Content::Error);
|
||||
|
|
|
@ -14,7 +14,7 @@ ven_ena = { path = "../../vendor/ena" }
|
|||
inlinable_string = "0.1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1 "
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
|
|
|
@ -37,9 +37,10 @@ impl fmt::Debug for Mark {
|
|||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct NameState {
|
||||
struct ErrorTypeState {
|
||||
taken: MutSet<Lowercase>,
|
||||
normals: u32,
|
||||
problems: Vec<crate::types::Problem>,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
|
@ -363,7 +364,7 @@ impl Subs {
|
|||
explicit_substitute(self, x, y, z, &mut seen)
|
||||
}
|
||||
|
||||
pub fn var_to_error_type(&mut self, var: Variable) -> ErrorType {
|
||||
pub fn var_to_error_type(&mut self, var: Variable) -> (ErrorType, Vec<crate::types::Problem>) {
|
||||
let names = get_var_names(self, var, ImMap::default());
|
||||
let mut taken = MutSet::default();
|
||||
|
||||
|
@ -371,9 +372,13 @@ impl Subs {
|
|||
taken.insert(name);
|
||||
}
|
||||
|
||||
let mut state = NameState { taken, normals: 0 };
|
||||
let mut state = ErrorTypeState {
|
||||
taken,
|
||||
normals: 0,
|
||||
problems: Vec::new(),
|
||||
};
|
||||
|
||||
var_to_err_type(self, &mut state, var)
|
||||
(var_to_err_type(self, &mut state, var), state.problems)
|
||||
}
|
||||
|
||||
pub fn restore(&mut self, var: Variable) {
|
||||
|
@ -1114,7 +1119,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn var_to_err_type(subs: &mut Subs, state: &mut NameState, var: Variable) -> ErrorType {
|
||||
fn var_to_err_type(subs: &mut Subs, state: &mut ErrorTypeState, var: Variable) -> ErrorType {
|
||||
let desc = subs.get(var);
|
||||
|
||||
if desc.mark == Mark::OCCURS {
|
||||
|
@ -1132,7 +1137,7 @@ fn var_to_err_type(subs: &mut Subs, state: &mut NameState, var: Variable) -> Err
|
|||
|
||||
fn content_to_err_type(
|
||||
subs: &mut Subs,
|
||||
state: &mut NameState,
|
||||
state: &mut ErrorTypeState,
|
||||
var: Variable,
|
||||
content: Content,
|
||||
) -> ErrorType {
|
||||
|
@ -1174,7 +1179,11 @@ fn content_to_err_type(
|
|||
}
|
||||
}
|
||||
|
||||
fn flat_type_to_err_type(subs: &mut Subs, state: &mut NameState, flat_type: FlatType) -> ErrorType {
|
||||
fn flat_type_to_err_type(
|
||||
subs: &mut Subs,
|
||||
state: &mut ErrorTypeState,
|
||||
flat_type: FlatType,
|
||||
) -> ErrorType {
|
||||
use self::FlatType::*;
|
||||
|
||||
match flat_type {
|
||||
|
@ -1296,11 +1305,15 @@ fn flat_type_to_err_type(subs: &mut Subs, state: &mut NameState, flat_type: Flat
|
|||
|
||||
Boolean(b) => ErrorType::Boolean(b),
|
||||
|
||||
Erroneous(_) => ErrorType::Error,
|
||||
Erroneous(problem) => {
|
||||
state.problems.push(problem);
|
||||
|
||||
ErrorType::Error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_fresh_var_name(state: &mut NameState) -> Lowercase {
|
||||
fn get_fresh_var_name(state: &mut ErrorTypeState) -> Lowercase {
|
||||
let (name, new_index) = name_type_var(state.normals, &mut state.taken);
|
||||
|
||||
state.normals = new_index;
|
||||
|
|
|
@ -395,6 +395,7 @@ impl Type {
|
|||
|
||||
pub fn instantiate_aliases(
|
||||
&mut self,
|
||||
region: Region,
|
||||
aliases: &ImMap<Symbol, Alias>,
|
||||
var_store: &VarStore,
|
||||
introduced: &mut ImSet<Variable>,
|
||||
|
@ -404,34 +405,44 @@ impl Type {
|
|||
match self {
|
||||
Function(args, ret) => {
|
||||
for arg in args {
|
||||
arg.instantiate_aliases(aliases, var_store, introduced);
|
||||
arg.instantiate_aliases(region, aliases, var_store, introduced);
|
||||
}
|
||||
ret.instantiate_aliases(aliases, var_store, introduced);
|
||||
ret.instantiate_aliases(region, aliases, var_store, introduced);
|
||||
}
|
||||
RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => {
|
||||
for (_, args) in tags {
|
||||
for x in args {
|
||||
x.instantiate_aliases(aliases, var_store, introduced);
|
||||
x.instantiate_aliases(region, aliases, var_store, introduced);
|
||||
}
|
||||
}
|
||||
ext.instantiate_aliases(aliases, var_store, introduced);
|
||||
ext.instantiate_aliases(region, aliases, var_store, introduced);
|
||||
}
|
||||
Record(fields, ext) => {
|
||||
for x in fields.iter_mut() {
|
||||
x.instantiate_aliases(aliases, var_store, introduced);
|
||||
x.instantiate_aliases(region, aliases, var_store, introduced);
|
||||
}
|
||||
ext.instantiate_aliases(aliases, var_store, introduced);
|
||||
ext.instantiate_aliases(region, aliases, var_store, introduced);
|
||||
}
|
||||
Alias(_, type_args, actual_type) => {
|
||||
for arg in type_args {
|
||||
arg.1.instantiate_aliases(aliases, var_store, introduced);
|
||||
arg.1
|
||||
.instantiate_aliases(region, aliases, var_store, introduced);
|
||||
}
|
||||
|
||||
actual_type.instantiate_aliases(aliases, var_store, introduced);
|
||||
actual_type.instantiate_aliases(region, aliases, var_store, introduced);
|
||||
}
|
||||
Apply(symbol, args) => {
|
||||
if let Some(alias) = aliases.get(symbol) {
|
||||
debug_assert!(args.len() == alias.vars.len());
|
||||
if args.len() != alias.vars.len() {
|
||||
*self = Type::Erroneous(Problem::BadTypeArguments {
|
||||
symbol: *symbol,
|
||||
region,
|
||||
type_got: args.len() as u8,
|
||||
alias_needs: alias.vars.len() as u8,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let mut actual = alias.typ.clone();
|
||||
|
||||
let mut named_args = Vec::with_capacity(args.len());
|
||||
|
@ -447,7 +458,7 @@ impl Type {
|
|||
) in alias.vars.iter().zip(args.iter())
|
||||
{
|
||||
let mut filler = filler.clone();
|
||||
filler.instantiate_aliases(aliases, var_store, introduced);
|
||||
filler.instantiate_aliases(region, aliases, var_store, introduced);
|
||||
named_args.push((lowercase.clone(), filler.clone()));
|
||||
substitution.insert(*placeholder, filler);
|
||||
}
|
||||
|
@ -463,7 +474,7 @@ impl Type {
|
|||
}
|
||||
|
||||
actual.substitute(&substitution);
|
||||
actual.instantiate_aliases(aliases, var_store, introduced);
|
||||
actual.instantiate_aliases(region, aliases, var_store, introduced);
|
||||
|
||||
// instantiate recursion variable!
|
||||
if let Type::RecursiveTagUnion(rec_var, mut tags, mut ext) = actual {
|
||||
|
@ -487,7 +498,7 @@ impl Type {
|
|||
} else {
|
||||
// one of the special-cased Apply types.
|
||||
for x in args {
|
||||
x.instantiate_aliases(aliases, var_store, introduced);
|
||||
x.instantiate_aliases(region, aliases, var_store, introduced);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -691,11 +702,18 @@ pub struct Alias {
|
|||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub enum Problem {
|
||||
CanonicalizationProblem,
|
||||
Mismatch(Mismatch, ErrorType, ErrorType),
|
||||
CircularType(Symbol, ErrorType, Region),
|
||||
CyclicAlias(Symbol, Region, Vec<Symbol>),
|
||||
UnrecognizedIdent(InlinableString),
|
||||
Shadowed(Region, Located<Ident>),
|
||||
BadTypeArguments {
|
||||
symbol: Symbol,
|
||||
region: Region,
|
||||
type_got: u8,
|
||||
alias_needs: u8,
|
||||
},
|
||||
InvalidModule,
|
||||
SolvedTypeError,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
|
|
|
@ -10,7 +10,7 @@ roc_module = { path = "../module" }
|
|||
roc_types = { path = "../types" }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1 "
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
|
|
|
@ -73,6 +73,7 @@ struct Context {
|
|||
pub enum Unified {
|
||||
Success(Pool),
|
||||
Failure(Pool, ErrorType, ErrorType),
|
||||
BadType(Pool, roc_types::types::Problem),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -91,11 +92,18 @@ pub fn unify(subs: &mut Subs, var1: Variable, var2: Variable) -> Unified {
|
|||
if mismatches.is_empty() {
|
||||
Unified::Success(vars)
|
||||
} else {
|
||||
let type1 = subs.var_to_error_type(var1);
|
||||
let type2 = subs.var_to_error_type(var2);
|
||||
let (type1, mut problems) = subs.var_to_error_type(var1);
|
||||
let (type2, problems2) = subs.var_to_error_type(var2);
|
||||
|
||||
problems.extend(problems2);
|
||||
|
||||
subs.union(var1, var2, Content::Error.into());
|
||||
Unified::Failure(vars, type1, type2)
|
||||
|
||||
if !problems.is_empty() {
|
||||
Unified::BadType(vars, problems.remove(0))
|
||||
} else {
|
||||
Unified::Failure(vars, type1, type2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ roc_builtins = { path = "../builtins" }
|
|||
roc_problem = { path = "../problem" }
|
||||
roc_parse = { path = "../parse" }
|
||||
roc_solve = { path = "../solve" }
|
||||
pretty_assertions = "0.5.1 "
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
|
|
3
examples/hello-world/Hello.roc
Normal file
3
examples/hello-world/Hello.roc
Normal file
|
@ -0,0 +1,3 @@
|
|||
app Hello provides [ main ] imports []
|
||||
|
||||
main = "Hello, World!"
|
|
@ -3,13 +3,13 @@
|
|||
To run:
|
||||
|
||||
```bash
|
||||
$ cargo run hello.roc
|
||||
$ cargo run run Hello.roc
|
||||
```
|
||||
|
||||
To run in release mode instead, do:
|
||||
|
||||
```bash
|
||||
$ cargo run --release hello.roc
|
||||
$ cargo run --release run Hello.roc
|
||||
```
|
||||
|
||||
## Design Notes
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
"Hello, World!"
|
|
@ -3,7 +3,7 @@ use std::os::raw::c_char;
|
|||
|
||||
#[link(name = "roc_app", kind = "static")]
|
||||
extern "C" {
|
||||
#[link_name = "$Test.main"]
|
||||
#[link_name = "$main"]
|
||||
fn str_from_roc() -> *const c_char;
|
||||
}
|
||||
|
||||
|
|
3
examples/quicksort/.gitignore
vendored
3
examples/quicksort/.gitignore
vendored
|
@ -1,3 +0,0 @@
|
|||
app
|
||||
*.o
|
||||
*.a
|
55
examples/quicksort/Quicksort.roc
Normal file
55
examples/quicksort/Quicksort.roc
Normal file
|
@ -0,0 +1,55 @@
|
|||
app Quicksort provides [ main ] imports []
|
||||
|
||||
main = quicksort [ 7, 19, 4, 21 ]
|
||||
|
||||
quicksort : List (Num a) -> List (Num a)
|
||||
quicksort = \list ->
|
||||
quicksortHelp list 0 (List.len list - 1)
|
||||
|
||||
|
||||
quicksortHelp : List (Num a), Int, Int -> List (Num a)
|
||||
quicksortHelp = \list, low, high ->
|
||||
when partition low high list is
|
||||
Pair partitionIndex partitioned ->
|
||||
partitioned
|
||||
|> quicksortHelp low (partitionIndex - 1)
|
||||
|> quicksortHelp (partitionIndex + 1) high
|
||||
|
||||
|
||||
swap : Int, Int, List a -> List a
|
||||
swap = \i, j, list ->
|
||||
when Pair (List.get list i) (List.get list j) is
|
||||
Pair (Ok atI) (Ok atJ) ->
|
||||
list
|
||||
|> List.set i atJ
|
||||
|> List.set j atI
|
||||
|
||||
_ ->
|
||||
[]
|
||||
|
||||
|
||||
partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]
|
||||
partition = \low, high, initialList ->
|
||||
when List.get initialList high is
|
||||
Ok pivot ->
|
||||
go = \i, j, list ->
|
||||
if j < high then
|
||||
when List.get list j is
|
||||
Ok value ->
|
||||
if value <= pivot then
|
||||
go (i + 1) (j + 1) (swap (i + 1) j list)
|
||||
else
|
||||
go i (j + 1) list
|
||||
|
||||
Err _ ->
|
||||
Pair i list
|
||||
else
|
||||
Pair i list
|
||||
|
||||
when go (low - 1) low initialList is
|
||||
Pair newI newList ->
|
||||
Pair (newI + 1) (swap (newI + 1) high newList)
|
||||
|
||||
Err _ ->
|
||||
Pair (low - 1) initialList
|
||||
|
|
@ -3,11 +3,11 @@
|
|||
To run:
|
||||
|
||||
```bash
|
||||
$ cargo run qs.roc
|
||||
$ cargo run run Quicksort.roc
|
||||
```
|
||||
|
||||
To run in release mode instead, do:
|
||||
|
||||
```bash
|
||||
$ cargo run --release qs.roc
|
||||
$ cargo run --release run Quicksort.roc
|
||||
```
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue