Merge branch 'trunk' of github.com:rtfeldman/roc into format-precedence-conflict

This commit is contained in:
Chad Stearns 2020-04-21 23:40:13 -04:00
commit 73744b3b1d
74 changed files with 3308 additions and 919 deletions

335
Cargo.lock generated
View file

@ -15,9 +15,15 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
dependencies = [ 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]] [[package]]
name = "arrayvec" name = "arrayvec"
version = "0.5.1" version = "0.5.1"
@ -32,7 +38,7 @@ checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [ dependencies = [
"hermit-abi", "hermit-abi",
"libc", "libc",
"winapi", "winapi 0.3.8",
] ]
[[package]] [[package]]
@ -124,6 +130,35 @@ dependencies = [
"unicode-width", "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]] [[package]]
name = "cloudabi" name = "cloudabi"
version = "0.0.3" version = "0.0.3"
@ -141,7 +176,7 @@ checksum = "1fc755679c12bda8e5523a71e4d654b6bf2e14bd838dfc48cde6559a05caf7d1"
dependencies = [ dependencies = [
"atty", "atty",
"cast", "cast",
"clap", "clap 2.33.0",
"criterion-plot", "criterion-plot",
"csv", "csv",
"itertools", "itertools",
@ -277,6 +312,22 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 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]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.1.14" version = "0.1.14"
@ -288,6 +339,15 @@ dependencies = [
"wasi", "wasi",
] ]
[[package]]
name = "heck"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
dependencies = [
"unicode-segmentation",
]
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.1.10" version = "0.1.10"
@ -325,6 +385,15 @@ dependencies = [
"version_check", "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]] [[package]]
name = "indoc" name = "indoc"
version = "0.3.5" version = "0.3.5"
@ -378,6 +447,15 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb6ee2a7da03bfc3b66ca47c92c2e392fcc053ea040a85561749b026f7aad09a" checksum = "cb6ee2a7da03bfc3b66ca47c92c2e392fcc053ea040a85561749b026f7aad09a"
[[package]]
name = "iovec"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.8.2" version = "0.8.2"
@ -402,6 +480,16 @@ dependencies = [
"wasm-bindgen", "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]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.4.0" version = "1.4.0"
@ -410,9 +498,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.68" version = "0.2.69"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0" checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005"
[[package]] [[package]]
name = "llvm-sys" name = "llvm-sys"
@ -429,9 +517,9 @@ dependencies = [
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.3.3" version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79b2de95ecb4691949fea4716ca53cdbcfccb2c612e19644a8bad05edcf9f47b" checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75"
dependencies = [ dependencies = [
"scopeguard", "scopeguard",
] ]
@ -472,6 +560,81 @@ dependencies = [
"autocfg 1.0.0", "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]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.11" version = "0.2.11"
@ -505,9 +668,9 @@ checksum = "ebcec7c9c2a95cacc7cd0ecb89d8a8454eca13906f6deb55258ffff0adeb9405"
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.10.0" version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92e98c49ab0b7ce5b222f2cc9193fc4efe11c6d0bd4f648e374684a6857b1cfc" checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e"
dependencies = [ dependencies = [
"lock_api", "lock_api",
"parking_lot_core", "parking_lot_core",
@ -515,16 +678,16 @@ dependencies = [
[[package]] [[package]]
name = "parking_lot_core" name = "parking_lot_core"
version = "0.7.0" version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7582838484df45743c8434fbff785e8edf260c28748353d44bc0da32e0ceabf1" checksum = "0e136c1904604defe99ce5fd71a28d473fa60a12255d511aa78a9ddf11237aeb"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cloudabi", "cloudabi",
"libc", "libc",
"redox_syscall", "redox_syscall",
"smallvec", "smallvec",
"winapi", "winapi 0.3.8",
] ]
[[package]] [[package]]
@ -561,6 +724,32 @@ dependencies = [
"difference", "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]] [[package]]
name = "proc-macro-hack" name = "proc-macro-hack"
version = "0.5.15" version = "0.5.15"
@ -642,7 +831,7 @@ dependencies = [
"rand_os", "rand_os",
"rand_pcg", "rand_pcg",
"rand_xorshift", "rand_xorshift",
"winapi", "winapi 0.3.8",
] ]
[[package]] [[package]]
@ -737,7 +926,7 @@ checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"
dependencies = [ dependencies = [
"libc", "libc",
"rand_core 0.4.2", "rand_core 0.4.2",
"winapi", "winapi 0.3.8",
] ]
[[package]] [[package]]
@ -751,7 +940,7 @@ dependencies = [
"libc", "libc",
"rand_core 0.4.2", "rand_core 0.4.2",
"rdrand", "rdrand",
"winapi", "winapi 0.3.8",
] ]
[[package]] [[package]]
@ -854,7 +1043,7 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" checksum = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
dependencies = [ dependencies = [
"winapi", "winapi 0.3.8",
] ]
[[package]] [[package]]
@ -862,15 +1051,22 @@ name = "roc-cli"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"clap 3.0.0-beta.1",
"im", "im",
"im-rc", "im-rc",
"indoc",
"inkwell", "inkwell",
"inlinable_string", "inlinable_string",
"maplit",
"pretty_assertions",
"quickcheck",
"quickcheck_macros",
"roc_builtins", "roc_builtins",
"roc_can", "roc_can",
"roc_collections", "roc_collections",
"roc_constrain", "roc_constrain",
"roc_gen", "roc_gen",
"roc_load",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
"roc_parse", "roc_parse",
@ -882,6 +1078,7 @@ dependencies = [
"roc_unify", "roc_unify",
"roc_uniq", "roc_uniq",
"target-lexicon", "target-lexicon",
"tokio",
] ]
[[package]] [[package]]
@ -1286,6 +1483,16 @@ dependencies = [
"serde", "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]] [[package]]
name = "sized-chunks" name = "sized-chunks"
version = "0.5.3" version = "0.5.3"
@ -1297,10 +1504,34 @@ dependencies = [
] ]
[[package]] [[package]]
name = "smallvec" name = "slab"
version = "1.2.0" version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "syn" name = "syn"
@ -1324,6 +1555,17 @@ dependencies = [
"unicode-xid 0.2.0", "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]] [[package]]
name = "target-lexicon" name = "target-lexicon"
version = "0.10.0" version = "0.10.0"
@ -1341,7 +1583,7 @@ dependencies = [
"rand 0.7.3", "rand 0.7.3",
"redox_syscall", "redox_syscall",
"remove_dir_all", "remove_dir_all",
"winapi", "winapi 0.3.8",
] ]
[[package]] [[package]]
@ -1383,15 +1625,22 @@ dependencies = [
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "0.2.16" version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee5a0dd887e37d37390c13ff8ac830f992307fe30a1fff0ab8427af67211ba28" checksum = "34ef16d072d2b6dc8b4a56c70f5c5ced1a37752116f8e7c1e80c659aa7cb6713"
dependencies = [ dependencies = [
"bytes", "bytes",
"fnv", "fnv",
"lazy_static",
"libc",
"memchr", "memchr",
"mio",
"mio-named-pipes",
"mio-uds",
"num_cpus", "num_cpus",
"pin-project-lite", "pin-project-lite",
"signal-hook-registry",
"winapi 0.3.8",
] ]
[[package]] [[package]]
@ -1402,9 +1651,15 @@ checksum = "0685c84d5d54d1c26f7d3eb96cd41550adb97baed141a761cf335d3d33bcd0ae"
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.11.2" version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "unicode-width" name = "unicode-width"
@ -1430,6 +1685,12 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63f18aa3b0e35fed5a0048f029558b1518095ffe2a0a31fb87c93dece93a4993" checksum = "63f18aa3b0e35fed5a0048f029558b1518095ffe2a0a31fb87c93dece93a4993"
[[package]]
name = "vec_map"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
[[package]] [[package]]
name = "ven_ena" name = "ven_ena"
version = "0.13.1" version = "0.13.1"
@ -1469,7 +1730,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d"
dependencies = [ dependencies = [
"same-file", "same-file",
"winapi", "winapi 0.3.8",
"winapi-util", "winapi-util",
] ]
@ -1543,6 +1804,12 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "winapi"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.8" version = "0.3.8"
@ -1553,6 +1820,12 @@ dependencies = [
"winapi-x86_64-pc-windows-gnu", "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]] [[package]]
name = "winapi-i686-pc-windows-gnu" name = "winapi-i686-pc-windows-gnu"
version = "0.4.0" version = "0.4.0"
@ -1565,7 +1838,7 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa515c5163a99cc82bab70fd3bfdd36d827be85de63737b40fcef2ce084a436e" checksum = "fa515c5163a99cc82bab70fd3bfdd36d827be85de63737b40fcef2ce084a436e"
dependencies = [ dependencies = [
"winapi", "winapi 0.3.8",
] ]
[[package]] [[package]]
@ -1574,6 +1847,16 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 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]] [[package]]
name = "wyhash" name = "wyhash"
version = "0.3.0" version = "0.3.0"

View file

@ -16,6 +16,21 @@ path = "src/main.rs"
test = false test = false
bench = 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] [dependencies]
roc_collections = { path = "../compiler/collections" } roc_collections = { path = "../compiler/collections" }
@ -31,12 +46,16 @@ roc_uniq = { path = "../compiler/uniq" }
roc_unify = { path = "../compiler/unify" } roc_unify = { path = "../compiler/unify" }
roc_solve = { path = "../compiler/solve" } roc_solve = { path = "../compiler/solve" }
roc_mono = { path = "../compiler/mono" } roc_mono = { path = "../compiler/mono" }
roc_load = { path = "../compiler/load", version = "0.1.0" }
roc_gen = { path = "../compiler/gen", version = "0.1.0" } roc_gen = { path = "../compiler/gen", version = "0.1.0" }
roc_reporting = { path = "../compiler/reporting", 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 = "14" # im and im-rc should always have the same version!
im-rc = "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"] } bumpalo = { version = "3.2", features = ["collections"] }
inlinable_string = "0.1.0" 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. # 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 # 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. # This way, GitHub Actions works and nobody's builds get broken.
inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release1" } inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release1" }
target-lexicon = "0.10" 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"

View file

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

View file

@ -1,7 +1,7 @@
extern crate roc_gen; extern crate roc_gen;
extern crate roc_reporting; extern crate roc_reporting;
#[macro_use]
use crate::helpers::{infer_expr, uniq_expr_with}; extern crate clap;
use bumpalo::Bump; use bumpalo::Bump;
use inkwell::context::Context; use inkwell::context::Context;
use inkwell::module::Linkage; use inkwell::module::Linkage;
@ -9,58 +9,170 @@ use inkwell::passes::PassManager;
use inkwell::types::BasicType; use inkwell::types::BasicType;
use inkwell::OptimizationLevel; use inkwell::OptimizationLevel;
use roc_collections::all::ImMap; use roc_collections::all::ImMap;
use roc_collections::all::MutMap;
use roc_gen::llvm::build::{ 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_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::expr::{Expr, Procs};
use roc_mono::layout::Layout; use roc_mono::layout::Layout;
use std::time::SystemTime; use std::time::SystemTime;
use clap::{App, Arg, ArgMatches};
use inkwell::targets::{ use inkwell::targets::{
CodeModel, FileType, InitializationConfig, RelocMode, Target, TargetTriple, CodeModel, FileType, InitializationConfig, RelocMode, Target, TargetTriple,
}; };
use std::fs::File; use std::io::{self, ErrorKind};
use std::io;
use std::io::prelude::*;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::Command; use std::process;
use target_lexicon::{Architecture, OperatingSystem, Triple, Vendor}; 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<()> { fn main() -> io::Result<()> {
let now = SystemTime::now(); let matches = build_app().get_matches();
let argv = std::env::args().collect::<Vec<String>>();
match argv.get(1) { match matches.subcommand_name() {
Some(filename) => { Some("build") => build(matches.subcommand_matches("build").unwrap(), false),
let mut path = Path::new(filename).canonicalize().unwrap(); Some("run") => build(matches.subcommand_matches("run").unwrap(), true),
Some("repl") => repl::main(),
_ => unreachable!(),
}
}
if !path.is_absolute() { pub fn build(matches: &ArgMatches, run_after_build: bool) -> io::Result<()> {
path = std::env::current_dir()?.join(path).canonicalize().unwrap(); 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();
// 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),
} }
// Step 1: build the .o file for the app process::exit(1);
let mut file = File::open(path.clone())?; }
let mut contents = String::new(); _ => {
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");
file.read_to_string(&mut contents)?; 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");
}
let dest_filename = path.with_extension("o"); 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();
// Step 1: compile the app and generate the .o file
let subs_by_module = MutMap::default();
// 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( gen(
Path::new(filename).to_path_buf(), &arena,
contents.as_str(), loaded,
filename,
Triple::host(), Triple::host(),
&dest_filename, &dest_filename,
opt_level,
); );
let end_time = now.elapsed().unwrap(); let compilation_end = compilation_start.elapsed().unwrap();
println!( println!(
"Finished compilation and code gen in {} ms\n", "Finished compilation and code gen in {} ms\n",
end_time.as_millis() compilation_end.as_millis()
); );
let cwd = dest_filename.parent().unwrap(); let cwd = dest_filename.parent().unwrap();
@ -74,53 +186,59 @@ fn main() -> io::Result<()> {
dest_filename.to_str().unwrap(), dest_filename.to_str().unwrap(),
]) ])
.spawn() .spawn()
.expect("`ar` failed to run"); .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 // Step 3: have rustc compile the host and link in the .a file
let binary_path = cwd.join("app");
Command::new("rustc") Command::new("rustc")
.args(&["-L", ".", "host.rs", "-o", "app"]) .args(&[
"-L",
".",
"--crate-type",
"bin",
"host.rs",
"-o",
binary_path.as_path().to_str().unwrap(),
])
.current_dir(cwd) .current_dir(cwd)
.spawn() .spawn()
.expect("rustc failed to run"); .map_err(|_| {
todo!("gracefully handle `rustc` failing to spawn.");
})?
.await
.map_err(|_| {
todo!("gracefully handle error after `rustc` spawned");
})?;
// Step 4: Run the compiled app Ok(binary_path)
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(())
}
}
} }
fn gen(filename: PathBuf, src: &str, target: Triple, dest_filename: &Path) { fn gen(
use roc_reporting::report::{can_problem, RocDocAllocator, DEFAULT_PALETTE}; arena: &Bump,
use roc_reporting::type_error::type_problem; loaded: LoadedModule,
filename: PathBuf,
// Build the expr target: Triple,
let arena = Bump::new(); dest_filename: &Path,
opt_level: OptLevel,
let (loc_expr, _output, can_problems, subs, var, constraint, home, interns) = ) {
uniq_expr_with(&arena, src, &ImMap::default()); use roc_reporting::report::{can_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE};
let mut type_problems = Vec::new();
let (content, mut subs) = infer_expr(subs, &mut type_problems, &constraint, var);
let src = loaded.src;
let home = loaded.module_id;
let src_lines: Vec<&str> = src.split('\n').collect(); let src_lines: Vec<&str> = src.split('\n').collect();
let palette = DEFAULT_PALETTE; let palette = DEFAULT_PALETTE;
// Report parsing and canonicalization problems // 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 report = can_problem(&alloc, filename.clone(), problem);
let mut buf = String::new(); let mut buf = String::new();
@ -129,7 +247,7 @@ fn gen(filename: PathBuf, src: &str, target: Triple, dest_filename: &Path) {
println!("\n{}\n", buf); 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 report = type_problem(&alloc, filename.clone(), problem);
let mut buf = String::new(); let mut buf = String::new();
@ -138,6 +256,63 @@ fn gen(filename: PathBuf, src: &str, target: Triple, dest_filename: &Path) {
println!("\n{}\n", buf); 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 // Generate the binary
let context = Context::create(); 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 builder = context.create_builder();
let fpm = PassManager::create(&module); let fpm = PassManager::create(&module);
roc_gen::llvm::build::add_passes(&fpm); roc_gen::llvm::build::add_passes(&fpm, opt_level);
fpm.initialize(); fpm.initialize();
@ -160,14 +335,14 @@ fn gen(filename: PathBuf, src: &str, target: Triple, dest_filename: &Path) {
let main_fn_type = let main_fn_type =
basic_type_from_layout(&arena, &context, &layout, ptr_bytes).fn_type(&[], false); 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 // Compile and add all the Procs before adding main
let mut env = roc_gen::llvm::build::Env { let mut env = roc_gen::llvm::build::Env {
arena: &arena, arena: &arena,
builder: &builder, builder: &builder,
context: &context, context: &context,
interns, interns: loaded.interns,
module: arena.alloc(module), module: arena.alloc(module),
ptr_bytes, ptr_bytes,
}; };
@ -271,12 +446,16 @@ fn gen(filename: PathBuf, src: &str, target: Triple, dest_filename: &Path) {
"x86-64" "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()); Target::initialize_arm(&InitializationConfig::default());
"arm" "arm"
} }
Architecture::Wasm32 => { Architecture::Wasm32 if cfg!(feature = "target-webassembly") => {
Target::initialize_webassembly(&InitializationConfig::default()); Target::initialize_webassembly(&InitializationConfig::default());
"wasm32" "wasm32"

View file

@ -1,28 +1,278 @@
use bumpalo::Bump; 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_builtins::unique::uniq_stdlib;
use roc_can::constraint::Constraint; use roc_can::constraint::Constraint;
use roc_can::env::Env; use roc_can::env::Env;
use roc_can::expected::Expected; 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::operator;
use roc_can::scope::Scope; use roc_can::scope::Scope;
use roc_collections::all::{ImMap, ImSet, MutMap, SendMap, SendSet}; use roc_collections::all::{ImMap, ImSet, MutMap, SendMap, SendSet};
use roc_constrain::expr::constrain_expr; use roc_constrain::expr::constrain_expr;
use roc_constrain::module::{constrain_imported_values, load_builtin_aliases, Import}; 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::ident::Ident;
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol}; 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::ast::{self, Attempting};
use roc_parse::blankspace::space0_before; use roc_parse::blankspace::space0_before;
use roc_parse::parser::{loc, Fail, Parser, State}; use roc_parse::parser::{loc, Fail, Parser, State};
use roc_problem::can::Problem; use roc_problem::can::Problem;
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_solve::solve; 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::subs::{Content, Subs, VarStore, Variable};
use roc_types::types::Type; use roc_types::types::Type;
use std::hash::Hash; use std::hash::Hash;
use std::io::{self, Write};
use std::path::PathBuf;
use target_lexicon::Triple;
pub fn test_home() -> ModuleId { pub fn main() -> io::Result<()> {
ModuleIds::default().get_or_insert(&"Test".into()) 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( 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 { 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( pub fn uniq_expr(
expr_str: &str, expr_str: &str,
) -> ( ) -> (
Located<Expr>, Located<roc_can::expr::Expr>,
Output, Output,
Vec<Problem>, Vec<Problem>,
Subs, Subs,
@ -82,7 +332,7 @@ pub fn uniq_expr_with(
expr_str: &str, expr_str: &str,
declared_idents: &ImMap<Ident, (Symbol, Region)>, declared_idents: &ImMap<Ident, (Symbol, Region)>,
) -> ( ) -> (
Located<Expr>, Located<roc_can::expr::Expr>,
Output, Output,
Vec<Problem>, Vec<Problem>,
Subs, Subs,
@ -91,7 +341,7 @@ pub fn uniq_expr_with(
ModuleId, ModuleId,
Interns, Interns,
) { ) {
let home = test_home(); let home = repl_home();
let CanExprOut { let CanExprOut {
loc_expr, loc_expr,
output, output,
@ -145,7 +395,7 @@ pub fn uniq_expr_with(
} }
pub struct CanExprOut { pub struct CanExprOut {
pub loc_expr: Located<Expr>, pub loc_expr: Located<roc_can::expr::Expr>,
pub output: Output, pub output: Output,
pub problems: Vec<Problem>, pub problems: Vec<Problem>,
pub home: ModuleId, pub home: ModuleId,

104
cli/tests/cli_run.rs Normal file
View 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());
}
}

View file

@ -12,7 +12,7 @@ roc_types = { path = "../types" }
roc_can = { path = "../can" } roc_can = { path = "../can" }
[dev-dependencies] [dev-dependencies]
pretty_assertions = "0.5.1 " pretty_assertions = "0.5.1"
maplit = "1.0.1" maplit = "1.0.1"
indoc = "0.3.3" indoc = "0.3.3"
quickcheck = "0.8" quickcheck = "0.8"

View file

@ -18,7 +18,7 @@ bumpalo = { version = "3.2", features = ["collections"] }
inlinable_string = "0.1.0" inlinable_string = "0.1.0"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "0.5.1 " pretty_assertions = "0.5.1"
maplit = "1.0.1" maplit = "1.0.1"
indoc = "0.3.3" indoc = "0.3.3"
quickcheck = "0.8" quickcheck = "0.8"

View file

@ -1,6 +1,6 @@
use crate::env::Env; use crate::env::Env;
use crate::scope::Scope; 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::Ident;
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
@ -292,9 +292,10 @@ fn can_annotation_help(
Record { fields, ext } => { Record { fields, ext } => {
let mut field_types = SendMap::default(); let mut field_types = SendMap::default();
let mut seen = MutMap::default();
for field in fields.iter() { for field in fields.iter() {
can_assigned_field( let opt_field_name = can_assigned_field(
env, env,
&field.value, &field.value,
region, region,
@ -305,6 +306,17 @@ fn can_annotation_help(
&mut field_types, &mut field_types,
references, 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 { let ext_type = match ext {
@ -325,9 +337,10 @@ fn can_annotation_help(
} }
TagUnion { tags, ext } => { TagUnion { tags, ext } => {
let mut tag_types = Vec::with_capacity(tags.len()); let mut tag_types = Vec::with_capacity(tags.len());
let mut seen = MutMap::default();
for tag in tags.iter() { for tag in tags.iter() {
can_tag( let opt_tag_name = can_tag(
env, env,
&tag.value, &tag.value,
region, region,
@ -338,6 +351,17 @@ fn can_annotation_help(
&mut tag_types, &mut tag_types,
references, 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 { let ext_type = match ext {
@ -388,7 +412,7 @@ fn can_assigned_field<'a>(
local_aliases: &mut SendMap<Symbol, Alias>, local_aliases: &mut SendMap<Symbol, Alias>,
field_types: &mut SendMap<Lowercase, Type>, field_types: &mut SendMap<Lowercase, Type>,
references: &mut MutSet<Symbol>, references: &mut MutSet<Symbol>,
) { ) -> Option<Lowercase> {
use roc_parse::ast::AssignedField::*; use roc_parse::ast::AssignedField::*;
match field { match field {
@ -396,16 +420,18 @@ fn can_assigned_field<'a>(
let field_type = can_annotation_help( let field_type = can_annotation_help(
env, env,
&annotation.value, &annotation.value,
region, annotation.region,
scope, scope,
var_store, var_store,
introduced_variables, introduced_variables,
local_aliases, local_aliases,
references, 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) => { LabelOnly(loc_field_name) => {
// Interpret { a, b } as { a : a, b : b } // 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( SpaceBefore(nested, _) | SpaceAfter(nested, _) => can_assigned_field(
env, env,
@ -433,7 +461,7 @@ fn can_assigned_field<'a>(
field_types, field_types,
references, references,
), ),
Malformed(_) => (), Malformed(_) => None,
} }
} }
@ -449,7 +477,7 @@ fn can_tag<'a>(
local_aliases: &mut SendMap<Symbol, Alias>, local_aliases: &mut SendMap<Symbol, Alias>,
tag_types: &mut Vec<(TagName, Vec<Type>)>, tag_types: &mut Vec<(TagName, Vec<Type>)>,
references: &mut MutSet<Symbol>, references: &mut MutSet<Symbol>,
) { ) -> Option<TagName> {
match tag { match tag {
Tag::Global { name, args } => { Tag::Global { name, args } => {
let name = name.value.into(); let name = name.value.into();
@ -470,7 +498,10 @@ fn can_tag<'a>(
arg_types.push(ann); 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 } => { Tag::Private { name, args } => {
let ident_id = env.ident_ids.get_or_insert(&name.value.into()); let ident_id = env.ident_ids.get_or_insert(&name.value.into());
@ -492,7 +523,10 @@ fn can_tag<'a>(
arg_types.push(ann); 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( Tag::SpaceBefore(nested, _) | Tag::SpaceAfter(nested, _) => can_tag(
env, env,
@ -505,6 +539,6 @@ fn can_tag<'a>(
tag_types, tag_types,
references, references,
), ),
Tag::Malformed(_) => (), Tag::Malformed(_) => None,
} }
} }

View file

@ -32,24 +32,32 @@ impl Constraint {
match self { match self {
True | SaveTheEnvironment => {} True | SaveTheEnvironment => {}
Eq(typ, expected, _, _) => { Eq(typ, expected, _, region) => {
expected let expected_region = expected.get_annotation_region().unwrap_or(*region);
.get_type_mut_ref() expected.get_type_mut_ref().instantiate_aliases(
.instantiate_aliases(aliases, var_store, introduced); expected_region,
typ.instantiate_aliases(aliases, var_store, introduced); aliases,
var_store,
introduced,
);
typ.instantiate_aliases(*region, aliases, var_store, introduced);
} }
Lookup(_, expected, _) => { Lookup(_, expected, region) => {
expected let expected_region = expected.get_annotation_region().unwrap_or(*region);
.get_type_mut_ref() expected.get_type_mut_ref().instantiate_aliases(
.instantiate_aliases(aliases, var_store, introduced); expected_region,
aliases,
var_store,
introduced,
);
} }
Pattern(_, _, typ, pexpected) => { Pattern(region, _, typ, pexpected) => {
pexpected pexpected
.get_type_mut_ref() .get_type_mut_ref()
.instantiate_aliases(aliases, var_store, introduced); .instantiate_aliases(*region, aliases, var_store, introduced);
typ.instantiate_aliases(aliases, var_store, introduced); typ.instantiate_aliases(*region, aliases, var_store, introduced);
} }
And(nested) => { And(nested) => {
@ -65,8 +73,8 @@ impl Constraint {
} }
let mut introduced = ImSet::default(); let mut introduced = ImSet::default();
for Located { value: typ, .. } in letcon.def_types.iter_mut() { for Located { region, value: typ } in letcon.def_types.iter_mut() {
typ.instantiate_aliases(&new_aliases, var_store, &mut introduced); typ.instantiate_aliases(*region, &new_aliases, var_store, &mut introduced);
} }
letcon.defs_constraint.instantiate_aliases_help( letcon.defs_constraint.instantiate_aliases_help(

View file

@ -201,6 +201,7 @@ pub fn canonicalize_defs<'a>(
let mut can_vars: Vec<Located<(Lowercase, Variable)>> = let mut can_vars: Vec<Located<(Lowercase, Variable)>> =
Vec::with_capacity(vars.len()); Vec::with_capacity(vars.len());
let mut is_phantom = false;
for loc_lowercase in vars { for loc_lowercase in vars {
if let Some(var) = can_ann if let Some(var) = can_ann
.introduced_variables .introduced_variables
@ -212,12 +213,31 @@ pub fn canonicalize_defs<'a>(
region: loc_lowercase.region, region: loc_lowercase.region,
}); });
} else { } 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) { 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 { 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, // Now that we have the scope completely assembled, and shadowing resolved,
// we're ready to canonicalize any body exprs. // we're ready to canonicalize any body exprs.
@ -836,7 +856,11 @@ fn canonicalize_pending_def<'a>(
region: loc_lowercase.region, region: loc_lowercase.region,
}); });
} else { } 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); scope.add_alias(symbol, name.region, can_vars, rec_type_union);
} else { } 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 /// 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(); let mut symbols_introduced = ImSet::default();
for (key, _) in aliases.iter() { for (key, _) in aliases.iter() {
@ -1434,11 +1464,17 @@ fn correct_mutual_recursive_type_alias(aliases: &mut SendMap<Symbol, Alias>, var
&mutually_recursive_symbols, &mutually_recursive_symbols,
all_successors_without_self, 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 // TODO use itertools to be more efficient here
for rec in &cycle { for rec in &cycle {
let mut to_instantiate = ImMap::default(); let mut to_instantiate = ImMap::default();
let mut others = Vec::with_capacity(cycle.len() - 1);
for other in &cycle { for other in &cycle {
if rec != other { if rec != other {
others.push(*other);
if let Some(alias) = originals.get(other) { if let Some(alias) = originals.get(other) {
to_instantiate.insert(*other, alias.clone()); 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) { if let Some(alias) = aliases.get_mut(rec) {
alias.typ.instantiate_aliases( alias.typ.instantiate_aliases(
alias.region,
&to_instantiate, &to_instantiate,
var_store, var_store,
&mut ImSet::default(), &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 { match typ {
Type::TagUnion(tags, ext) => { Type::TagUnion(tags, ext) => {
let rec_var = var_store.fresh(); let rec_var = var_store.fresh();
*typ = Type::RecursiveTagUnion(rec_var, tags.to_vec(), ext.clone()); *typ = Type::RecursiveTagUnion(rec_var, tags.to_vec(), ext.clone());
typ.substitute_alias(symbol, &Type::Variable(rec_var)); typ.substitute_alias(symbol, &Type::Variable(rec_var));
} }
Type::Alias(_, _, actual) => make_tag_union_recursive(symbol, actual, var_store), Type::Alias(_, _, actual) => make_tag_union_recursive(
_ => panic!("recursion in type alias is not behind a Tag"), 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);
}
}
} }
} }

View file

@ -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> { pub fn replace<U>(self, new: U) -> Expected<U> {
match self { match self {
Expected::NoExpectation(_val) => Expected::NoExpectation(new), Expected::NoExpectation(_val) => Expected::NoExpectation(new),

View file

@ -193,7 +193,8 @@ pub fn canonicalize_expr<'a>(
let (can_update, update_out) = let (can_update, update_out) =
canonicalize_expr(env, var_store, scope, loc_update.region, &loc_update.value); canonicalize_expr(env, var_store, scope, loc_update.region, &loc_update.value);
if let Var(symbol) = &can_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); output.references = output.references.union(update_out.references);
@ -219,7 +220,8 @@ pub fn canonicalize_expr<'a>(
if fields.is_empty() { if fields.is_empty() {
(EmptyRecord, Output::default()) (EmptyRecord, Output::default())
} else { } 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 { Record {
@ -885,6 +887,7 @@ fn canonicalize_fields<'a>(
env: &mut Env<'a>, env: &mut Env<'a>,
var_store: &VarStore, var_store: &VarStore,
scope: &mut Scope, scope: &mut Scope,
region: Region,
fields: &'a [Located<ast::AssignedField<'a, ast::Expr<'a>>>], fields: &'a [Located<ast::AssignedField<'a, ast::Expr<'a>>>],
) -> (SendMap<Lowercase, Field>, Output) { ) -> (SendMap<Lowercase, Field>, Output) {
let mut can_fields = SendMap::default(); let mut can_fields = SendMap::default();
@ -900,7 +903,16 @@ fn canonicalize_fields<'a>(
loc_expr: Box::new(field_expr), 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); output.references = output.references.union(field_out.references);
} }

View file

@ -15,7 +15,7 @@ roc_builtins = { path = "../builtins" }
roc_uniq = { path = "../uniq" } roc_uniq = { path = "../uniq" }
[dev-dependencies] [dev-dependencies]
pretty_assertions = "0.5.1 " pretty_assertions = "0.5.1"
maplit = "1.0.1" maplit = "1.0.1"
indoc = "0.3.3" indoc = "0.3.3"
quickcheck = "0.8" quickcheck = "0.8"

View file

@ -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)) Type::Alias(*symbol, type_variables, Box::new(actual))
} }
Error => { Error => Type::Erroneous(roc_types::types::Problem::SolvedTypeError),
panic!("TODO convert from SolvedType::Error to Type somehow");
}
Erroneous(problem) => Type::Erroneous(problem.clone()), Erroneous(problem) => Type::Erroneous(problem.clone()),
} }
} }

View file

@ -17,7 +17,7 @@ bumpalo = { version = "3.2", features = ["collections"] }
inlinable_string = "0.1.0" inlinable_string = "0.1.0"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "0.5.1 " pretty_assertions = "0.5.1"
maplit = "1.0.1" maplit = "1.0.1"
indoc = "0.3.3" indoc = "0.3.3"
quickcheck = "0.8" quickcheck = "0.8"

View file

@ -43,7 +43,7 @@ target-lexicon = "0.10"
[dev-dependencies] [dev-dependencies]
roc_can = { path = "../can" } roc_can = { path = "../can" }
roc_parse = { path = "../parse" } roc_parse = { path = "../parse" }
pretty_assertions = "0.5.1 " pretty_assertions = "0.5.1"
maplit = "1.0.1" maplit = "1.0.1"
indoc = "0.3.3" indoc = "0.3.3"
quickcheck = "0.8" quickcheck = "0.8"

View file

@ -4,11 +4,11 @@ use inkwell::builder::Builder;
use inkwell::context::Context; use inkwell::context::Context;
use inkwell::memory_buffer::MemoryBuffer; use inkwell::memory_buffer::MemoryBuffer;
use inkwell::module::{Linkage, Module}; use inkwell::module::{Linkage, Module};
use inkwell::passes::PassManager; use inkwell::passes::{PassManager, PassManagerBuilder};
use inkwell::types::{BasicTypeEnum, IntType, StructType}; use inkwell::types::{BasicTypeEnum, IntType, StructType};
use inkwell::values::BasicValueEnum::{self, *}; use inkwell::values::BasicValueEnum::{self, *};
use inkwell::values::{FunctionValue, IntValue, PointerValue, StructValue}; use inkwell::values::{FunctionValue, IntValue, PointerValue, StructValue};
use inkwell::{FloatPredicate, IntPredicate}; use inkwell::{FloatPredicate, IntPredicate, OptimizationLevel};
use crate::llvm::convert::{ use crate::llvm::convert::{
basic_type_from_layout, collection_wrapper, empty_collection, get_fn_type, ptr_int, 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" // TODO: experiment with different internal calling conventions, e.g. "fast"
const DEFAULT_CALLING_CONVENTION: u32 = 0; const DEFAULT_CALLING_CONVENTION: u32 = 0;
pub enum OptLevel {
Normal,
Optimize,
}
type Scope<'a, 'ctx> = ImMap<Symbol, (Layout<'a>, PointerValue<'ctx>)>; type Scope<'a, 'ctx> = ImMap<Symbol, (Layout<'a>, PointerValue<'ctx>)>;
pub struct Env<'a, 'ctx, 'env> { 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)) .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 // tail-call elimination is always on
fpm.add_instruction_combining_pass(); fpm.add_instruction_combining_pass();
fpm.add_tail_call_elimination_pass(); fpm.add_tail_call_elimination_pass();
let pmb = PassManagerBuilder::create();
// Enable more optimizations when running cargo test --release // Enable more optimizations when running cargo test --release
if !cfg!(debug_assertions) { match opt_level {
fpm.add_reassociate_pass(); OptLevel::Normal => {
fpm.add_basic_alias_analysis_pass(); pmb.set_optimization_level(OptimizationLevel::None);
fpm.add_promote_memory_to_register_pass(); }
fpm.add_cfg_simplification_pass(); OptLevel::Optimize => {
fpm.add_gvn_pass(); // 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 // TODO figure out why enabling any of these (even alone) causes LLVM to segfault
// fpm.add_strip_dead_prototypes_pass(); // fpm.add_strip_dead_prototypes_pass();
// fpm.add_dead_arg_elimination_pass(); // fpm.add_dead_arg_elimination_pass();
// fpm.add_function_inlining_pass(); // fpm.add_function_inlining_pass();
// pmb.set_inliner_with_threshold(4);
} }
}
pmb.populate_function_pass_manager(&fpm);
} }
#[allow(clippy::cognitive_complexity)] #[allow(clippy::cognitive_complexity)]
@ -1006,6 +1029,17 @@ fn call_with_args<'a, 'ctx, 'env>(
BasicValueEnum::FloatValue(float_val) 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 => { Symbol::NUM_MUL => {
debug_assert!(args.len() == 2); debug_assert!(args.len() == 2);

View file

@ -84,6 +84,19 @@ mod gen_builtins {
); );
} }
#[test]
fn gen_div_f64() {
assert_evals_to!(
indoc!(
r#"
48 / 2
"#
),
24.0,
f64
);
}
#[test] #[test]
fn gen_add_i64() { fn gen_add_i64() {
assert_evals_to!( assert_evals_to!(
@ -148,6 +161,20 @@ mod gen_builtins {
i64 i64
); );
} }
#[test]
fn gen_order_of_arithmetic_ops_complex_float() {
assert_evals_to!(
indoc!(
r#"
48 / 2 + 3
"#
),
27.0,
f64
);
}
#[test] #[test]
fn if_guard_bind_variable() { fn if_guard_bind_variable() {
assert_evals_to!( assert_evals_to!(

View file

@ -12,9 +12,14 @@ macro_rules! assert_llvm_evals_to {
let context = Context::create(); let context = Context::create();
let module = roc_gen::llvm::build::module_from_builtins(&context, "app"); let module = roc_gen::llvm::build::module_from_builtins(&context, "app");
let builder = context.create_builder(); 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(); fpm.initialize();
@ -142,12 +147,19 @@ macro_rules! assert_opt_evals_to {
let mut unify_problems = Vec::new(); let mut unify_problems = Vec::new();
let (content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var); 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 context = Context::create();
let module = roc_gen::llvm::build::module_from_builtins(&context, "app"); let module = roc_gen::llvm::build::module_from_builtins(&context, "app");
let builder = context.create_builder(); 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); let fpm = PassManager::create(&module);
roc_gen::llvm::build::add_passes(&fpm); roc_gen::llvm::build::add_passes(&fpm, opt_level);
fpm.initialize(); fpm.initialize();
@ -271,12 +283,19 @@ macro_rules! emit_expr {
let mut unify_problems = Vec::new(); let mut unify_problems = Vec::new();
let (content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var); 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 context = Context::create();
let module = context.create_module("app"); let module = context.create_module("app");
let builder = context.create_builder(); 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); let fpm = PassManager::create(&module);
roc_gen::llvm::build::add_passes(&fpm); roc_gen::llvm::build::add_passes(&fpm, opt_level);
fpm.initialize(); fpm.initialize();

View file

@ -21,7 +21,7 @@ inlinable_string = "0.1.0"
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded"] } tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded"] }
[dev-dependencies] [dev-dependencies]
pretty_assertions = "0.5.1 " pretty_assertions = "0.5.1"
maplit = "1.0.1" maplit = "1.0.1"
indoc = "0.3.3" indoc = "0.3.3"
quickcheck = "0.8" quickcheck = "0.8"

View file

@ -12,7 +12,7 @@ use roc_constrain::module::{
}; };
use roc_module::ident::{Ident, Lowercase, ModuleName}; use roc_module::ident::{Ident, Lowercase, ModuleName};
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol}; 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::module::module_defs;
use roc_parse::parser::{Fail, Parser, State}; use roc_parse::parser::{Fail, Parser, State};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
@ -44,6 +44,7 @@ pub struct Module {
pub aliases: MutMap<Symbol, Alias>, pub aliases: MutMap<Symbol, Alias>,
pub rigid_variables: MutMap<Variable, Lowercase>, pub rigid_variables: MutMap<Variable, Lowercase>,
pub imported_modules: MutSet<ModuleId>, pub imported_modules: MutSet<ModuleId>,
pub src: Box<str>,
} }
#[derive(Debug)] #[derive(Debug)]
@ -54,6 +55,8 @@ pub struct LoadedModule {
pub can_problems: Vec<roc_problem::can::Problem>, pub can_problems: Vec<roc_problem::can::Problem>,
pub type_problems: Vec<solve::TypeError>, pub type_problems: Vec<solve::TypeError>,
pub declarations: Vec<Declaration>, pub declarations: Vec<Declaration>,
pub exposed_vars_by_symbol: Vec<(Symbol, Variable)>,
pub src: Box<str>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -89,10 +92,12 @@ enum Msg {
var_store: VarStore, var_store: VarStore,
}, },
Solved { Solved {
src: Box<str>,
module_id: ModuleId, module_id: ModuleId,
solved_types: MutMap<Symbol, SolvedType>, solved_types: MutMap<Symbol, SolvedType>,
aliases: MutMap<Symbol, Alias>, aliases: MutMap<Symbol, Alias>,
subs: Arc<Solved<Subs>>, subs: Arc<Solved<Subs>>,
exposed_vars_by_symbol: Vec<(Symbol, Variable)>,
problems: Vec<solve::TypeError>, problems: Vec<solve::TypeError>,
}, },
} }
@ -108,6 +113,7 @@ pub enum LoadingProblem {
fail: Fail, fail: Fail,
}, },
MsgChannelDied, MsgChannelDied,
TriedToImportAppModule,
} }
enum MaybeShared<'a, 'b, A, B> { enum MaybeShared<'a, 'b, A, B> {
@ -391,8 +397,9 @@ pub async fn load<'a>(
solved_types, solved_types,
subs, subs,
problems, problems,
exposed_vars_by_symbol,
aliases, aliases,
.. src,
} => { } => {
type_problems.extend(problems); type_problems.extend(problems);
@ -427,6 +434,8 @@ pub async fn load<'a>(
can_problems, can_problems,
type_problems, type_problems,
declarations, declarations,
exposed_vars_by_symbol,
src,
}); });
} else { } else {
// This was a dependency. Write it down and keep processing messages. // This was a dependency. Write it down and keep processing messages.
@ -515,13 +524,36 @@ fn load_filename(
#[allow(clippy::let_and_return)] #[allow(clippy::let_and_return)]
let answer = match roc_parse::module::header().parse(&arena, state) { let answer = match roc_parse::module::header().parse(&arena, state) {
Ok((ast::Module::Interface { header }, 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(module_id)
} }
Ok((ast::Module::App { .. }, _)) => { Ok((ast::Module::App { header }, state)) => match module_ids {
panic!("TODO finish loading an App module"); 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 }), Err((fail, _)) => Err(LoadingProblem::ParsingFailed { filename, fail }),
}; };
@ -534,36 +566,37 @@ fn load_filename(
} }
} }
fn send_interface_header<'a>( fn send_header<'a>(
header: InterfaceHeader<'a>, name: Located<roc_parse::header::ModuleName<'a>>,
exposes: &'a [Located<ExposesEntry<'a>>],
imports: &'a [Located<ImportsEntry<'a>>],
state: State<'a>, state: State<'a>,
shared_modules: SharedModules<'_, '_>, shared_modules: SharedModules<'_, '_>,
msg_tx: MsgSender, msg_tx: MsgSender,
) -> ModuleId { ) -> ModuleId {
use MaybeShared::*; 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. // TODO check to see if declared_name is consistent with filename.
// If it isn't, report a problem! // If it isn't, report a problem!
let mut imports: Vec<(ModuleName, Vec<Ident>, Region)> = let mut imported: Vec<(ModuleName, Vec<Ident>, Region)> = Vec::with_capacity(imports.len());
Vec::with_capacity(header.imports.len());
let mut imported_modules: MutSet<ModuleId> = MutSet::default(); let mut imported_modules: MutSet<ModuleId> = MutSet::default();
let mut scope_size = 0; 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); let (module_name, exposed) = exposed_from_import(&loc_entry.value);
scope_size += exposed.len(); 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> = let mut deps_by_name: MutMap<ModuleName, ModuleId> =
HashMap::with_capacity_and_hasher(num_exposes, default_hasher()); 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, // Make sure the module_ids has ModuleIds for all our deps,
// then record those ModuleIds in can_module_ids for later. // 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 // For each of our imports, add an entry to deps_by_name
// //
// e.g. for `imports [ Foo.{ bar } ]`, add `Foo` 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 cloned_module_name = module_name.clone();
let module_id = module_ids.get_or_insert(&module_name.into()); 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 // We must *not* add them to scope yet, or else the Defs will
// incorrectly think they're shadowing them! // 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 // Use get_or_insert here because the ident_ids may already
// created an IdentId for this, when it was imported exposed // created an IdentId for this, when it was imported exposed
// in a dependent module. // 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 ident_id = ident_ids.get_or_insert(&loc_exposed.value.as_str().into());
let symbol = Symbol::new(home, ident_id); let symbol = Symbol::new(home, ident_id);
exposes.push(symbol); exposed.push(symbol);
} }
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
@ -648,7 +681,7 @@ fn send_interface_header<'a>(
// and also add any exposed values to scope. // and also add any exposed values to scope.
// //
// e.g. for `imports [ Foo.{ bar } ]`, add `Foo` to deps_by_name and `bar` 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()); let module_id = module_ids.get_or_insert(&module_name.clone().into());
deps_by_name.insert(module_name, module_id); 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 // We must *not* add them to scope yet, or else the Defs will
// incorrectly think they're shadowing them! // 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 ident_id = ident_ids.add(loc_exposed.value.as_str().into());
let symbol = Symbol::new(home, ident_id); let symbol = Symbol::new(home, ident_id);
exposes.push(symbol); exposed.push(symbol);
} }
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
@ -712,7 +745,7 @@ fn send_interface_header<'a>(
module_name: declared_name, module_name: declared_name,
imported_modules, imported_modules,
deps_by_name, deps_by_name,
exposes, exposes: exposed,
src, src,
exposed_imports: scope, exposed_imports: scope,
})) }))
@ -860,6 +893,7 @@ fn solve_module(
aliases: module.aliases, aliases: module.aliases,
}; };
let src = module.src;
let mut subs = Subs::new(var_store.into()); let mut subs = Subs::new(var_store.into());
for (var, name) in module.rigid_variables { 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 // annotations which are decoupled from our Subs, because that's how
// other modules will generate constraints for imported values // other modules will generate constraints for imported values
// within the context of their own Subs. // within the context of their own Subs.
for (symbol, var) in exposed_vars_by_symbol { for (symbol, var) in exposed_vars_by_symbol.iter() {
let solved_type = SolvedType::new(&solved_subs, var); let solved_type = SolvedType::new(&solved_subs, *var);
solved_types.insert(symbol, solved_type); solved_types.insert(*symbol, solved_type);
} }
tokio::spawn(async move { tokio::spawn(async move {
@ -909,8 +943,10 @@ fn solve_module(
// Send the subs to the main thread for processing, // Send the subs to the main thread for processing,
tx.send(Msg::Solved { tx.send(Msg::Solved {
src,
module_id: home, module_id: home,
subs: Arc::new(solved_subs), subs: Arc::new(solved_subs),
exposed_vars_by_symbol,
solved_types, solved_types,
problems, problems,
aliases: env.aliases, aliases: env.aliases,
@ -1044,6 +1080,7 @@ fn parse_and_constrain(
aliases, aliases,
rigid_variables, rigid_variables,
imported_modules: header.imported_modules, imported_modules: header.imported_modules,
src: header.src,
}; };
(module, ident_ids, constraint, problems) (module, ident_ids, constraint, problems)

View 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

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

View file

@ -0,0 +1,10 @@
interface Dep2
exposes [ one, two, blah ]
imports [ Dep3.Blah.{ foo, bar } ]
one = 1
blah = foo
two = 2.0

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

View file

@ -0,0 +1,7 @@
interface ImportAlias
exposes [ unit ]
imports [ Dep1 ]
unit : Dep1.Unit
unit = Unit

View 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 }

View file

@ -0,0 +1,5 @@
interface OneDep
exposes [ str ]
imports [ Dep3.Blah.{ foo } ]
str = foo

View 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

View file

@ -1,3 +1,7 @@
app Quicksort
provides [ swap, partition, quicksort ]
imports []
quicksort : List (Num a), Int, Int -> List (Num a) quicksort : List (Num a), Int, Int -> List (Num a)
quicksort = \list, low, high -> quicksort = \list, low, high ->
when partition low high list is when partition low high list is
@ -43,5 +47,3 @@ partition = \low, high, initialList ->
Err _ -> Err _ ->
Pair (low - 1) initialList Pair (low - 1) initialList
quicksort [ 7, 4, 9 ]

View file

@ -0,0 +1,8 @@
interface Records
exposes [ intVal ]
imports []
intVal =
foo = \{ x } -> x
foo { x: 5 }

View 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

View 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

View file

@ -3,7 +3,7 @@ interface Primary
imports [ Dep1, Dep2.{ two, foo }, Dep3.Blah.{ bar }, Res ] imports [ Dep1, Dep2.{ two, foo }, Dep3.Blah.{ bar }, Res ]
blah2 = Dep2.two blah2 = Dep2.two
blah3 = bar # TODO FIXME does work as Dep3.Blah.bar, some scoping issue blah3 = bar
str = Dep1.str str = Dep1.str
@ -12,11 +12,7 @@ alwaysThree = \_ -> "foo"
identity = \a -> a identity = \a -> a
# z = identity (alwaysThree {}) # TODO FIXME for some reason this infers as a circular type z = identity (alwaysThree {})
# z = identity 3 # TODO FIXME for some reason this also infers as a circular type
z : Dep1.Unit
z = Unit
w : Dep1.Identity {} w : Dep1.Identity {}
w = Identity {} w = Identity {}

View file

@ -212,7 +212,7 @@ mod test_load {
} }
#[test] #[test]
fn load_and_typecheck_quicksort() { fn iface_quicksort() {
test_async(async { test_async(async {
let subs_by_module = MutMap::default(); let subs_by_module = MutMap::default();
let loaded_module = 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] #[test]
fn load_astar() { fn load_astar() {
test_async(async { test_async(async {
@ -266,7 +283,7 @@ mod test_load {
} }
#[test] #[test]
fn load_dep_types() { fn iface_dep_types() {
test_async(async { test_async(async {
let subs_by_module = MutMap::default(); let subs_by_module = MutMap::default();
let loaded_module = let loaded_module =
@ -280,7 +297,31 @@ mod test_load {
"str" => "Str", "str" => "Str",
"alwaysThree" => "* -> Str", "alwaysThree" => "* -> Str",
"identity" => "a -> a", "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 {}", "w" => "Dep1.Identity {}",
"succeed" => "a -> Dep1.Identity a", "succeed" => "a -> Dep1.Identity a",
"yay" => "Res.Res {} err", "yay" => "Res.Res {} err",

View file

@ -275,7 +275,7 @@ mod test_uniq_load {
"str" => "Attr * Str", "str" => "Attr * Str",
"alwaysThree" => "Attr * (* -> Attr * Str)", "alwaysThree" => "Attr * (* -> Attr * Str)",
"identity" => "Attr * (a -> a)", "identity" => "Attr * (a -> a)",
"z" => "Attr * Dep1.Unit", "z" => "Attr * Str",
"w" => "Attr * (Dep1.Identity (Attr * {}))", "w" => "Attr * (Dep1.Identity (Attr * {}))",
"succeed" => "Attr * (Attr b a -> Attr * (Dep1.Identity (Attr b a)))", "succeed" => "Attr * (Attr b a -> Attr * (Dep1.Identity (Attr b a)))",
"yay" => "Attr * (Res.Res (Attr * {}) (Attr * err))", "yay" => "Attr * (Res.Res (Attr * {}) (Attr * err))",

View file

@ -12,6 +12,6 @@ inlinable_string = "0.1.0"
lazy_static = "1.4" lazy_static = "1.4"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "0.5.1 " pretty_assertions = "0.5.1"
maplit = "1.0.1" maplit = "1.0.1"
indoc = "0.3.3" indoc = "0.3.3"

View file

@ -19,7 +19,7 @@ roc_constrain = { path = "../constrain" }
roc_builtins = { path = "../builtins" } roc_builtins = { path = "../builtins" }
roc_parse = { path = "../parse" } roc_parse = { path = "../parse" }
roc_solve = { path = "../solve" } roc_solve = { path = "../solve" }
pretty_assertions = "0.5.1 " pretty_assertions = "0.5.1"
maplit = "1.0.1" maplit = "1.0.1"
indoc = "0.3.3" indoc = "0.3.3"
quickcheck = "0.8" quickcheck = "0.8"

View file

@ -10,10 +10,27 @@ use roc_region::all::{Located, Region};
use roc_types::subs::{Content, ContentHash, FlatType, Subs, Variable}; use roc_types::subs::{Content, ContentHash, FlatType, Subs, Variable};
use std::hash::Hash; 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)] #[derive(Clone, Debug, PartialEq, Default)]
pub struct Procs<'a> { pub struct Procs<'a> {
user_defined: MutMap<Symbol, PartialProc<'a>>, user_defined: MutMap<Symbol, PartialProc<'a>>,
anonymous: MutMap<Symbol, Option<Proc<'a>>>, anonymous: MutMap<Symbol, Option<Proc<'a>>>,
specializations: MutMap<ContentHash, (Symbol, Option<Proc<'a>>)>,
builtin: MutSet<Symbol>, builtin: MutSet<Symbol>,
} }
@ -28,14 +45,11 @@ impl<'a> Procs<'a> {
fn insert_specialization( fn insert_specialization(
&mut self, &mut self,
symbol: Symbol,
hash: ContentHash, hash: ContentHash,
spec_name: Symbol, spec_name: Symbol,
proc: Option<Proc<'a>>, proc: Option<Proc<'a>>,
) { ) {
self.user_defined self.specializations.insert(hash, (spec_name, proc));
.get_mut(&symbol)
.map(|partial_proc| partial_proc.specializations.insert(hash, (spec_name, proc)));
} }
fn get_user_defined(&self, symbol: Symbol) -> Option<&PartialProc<'a>> { fn get_user_defined(&self, symbol: Symbol) -> Option<&PartialProc<'a>> {
@ -44,11 +58,7 @@ impl<'a> Procs<'a> {
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
let anonymous: usize = self.anonymous.len(); let anonymous: usize = self.anonymous.len();
let user_defined: usize = self let user_defined: usize = self.specializations.len();
.user_defined
.values()
.map(|v| v.specializations.len())
.sum();
anonymous + user_defined anonymous + user_defined
} }
@ -64,10 +74,8 @@ impl<'a> Procs<'a> {
pub fn as_map(&self) -> MutMap<Symbol, Option<Proc<'a>>> { pub fn as_map(&self) -> MutMap<Symbol, Option<Proc<'a>>> {
let mut result = MutMap::default(); let mut result = MutMap::default();
for partial_proc in self.user_defined.values() { for (symbol, opt_proc) in self.specializations.values() {
for (_, (symbol, opt_proc)) in partial_proc.specializations.clone().into_iter() { result.insert(*symbol, opt_proc.clone());
result.insert(symbol, opt_proc);
}
} }
for (symbol, proc) in self.anonymous.clone().into_iter() { 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 struct Env<'a, 'i> {
pub arena: &'a Bump, pub arena: &'a Bump,
pub subs: &'a mut Subs, pub subs: &'a mut Subs,
@ -458,7 +449,6 @@ fn from_can<'a>(
annotation, annotation,
patterns: arg_symbols, patterns: arg_symbols,
body: body.value, body: body.value,
specializations: MutMap::default(),
}, },
); );
symbol symbol
@ -831,8 +821,9 @@ fn from_can<'a>(
elems: elems.into_bump_slice(), elems: elems.into_bump_slice(),
} }
} }
Accessor { .. } => todo!("record accessor"),
Update { .. } => todo!("record update"),
RuntimeError(error) => Expr::RuntimeError(env.arena.alloc(format!("{:?}", error))), RuntimeError(error) => Expr::RuntimeError(env.arena.alloc(format!("{:?}", error))),
other => panic!("TODO convert canonicalized {:?} to mono::Expr", other),
} }
} }
@ -1304,15 +1295,18 @@ fn call_by_name<'a>(
Vec<'a, Symbol>, Vec<'a, Symbol>,
)>; )>;
let specialized_proc_name = if let Some(partial_proc) = procs.get_user_defined(proc_name) { let specialized_proc_name = match procs.get_user_defined(proc_name) {
Some(partial_proc) => {
let content_hash = ContentHash::from_var(fn_var, env.subs); 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; opt_specialize_body = None;
// a specialization with this type hash already exists, use its symbol // a specialization with this type hash already exists, use its symbol
specialization.0 specialization.0
} else { }
None => {
opt_specialize_body = Some(( opt_specialize_body = Some((
content_hash, content_hash,
partial_proc.annotation, partial_proc.annotation,
@ -1323,17 +1317,20 @@ fn call_by_name<'a>(
// generate a symbol for this specialization // generate a symbol for this specialization
env.fresh_symbol() env.fresh_symbol()
} }
} else { }
}
None => {
opt_specialize_body = None; opt_specialize_body = None;
// This happens for built-in symbols (they are never defined as a Closure) // This happens for built-in symbols (they are never defined as a Closure)
procs.insert_builtin(proc_name); procs.insert_builtin(proc_name);
proc_name proc_name
}
}; };
if let Some((content_hash, annotation, body, loc_patterns)) = opt_specialize_body { if let Some((content_hash, annotation, body, loc_patterns)) = opt_specialize_body {
// register proc, so specialization doesn't loop infinitely // 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<_>>(); let arg_vars = loc_args.iter().map(|v| v.0).collect::<std::vec::Vec<_>>();
@ -1350,7 +1347,7 @@ fn call_by_name<'a>(
) )
.ok(); .ok();
procs.insert_specialization(proc_name, content_hash, specialized_proc_name, proc); procs.insert_specialization(content_hash, specialized_proc_name, proc);
} }
// generate actual call // generate actual call

View file

@ -12,7 +12,7 @@ bumpalo = { version = "3.2", features = ["collections"] }
inlinable_string = "0.1.0" inlinable_string = "0.1.0"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "0.5.1 " pretty_assertions = "0.5.1"
indoc = "0.3.3" indoc = "0.3.3"
quickcheck = "0.8" quickcheck = "0.8"
quickcheck_macros = "0.8" quickcheck_macros = "0.8"

View file

@ -36,9 +36,14 @@ pub struct WhenBranch<'a> {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct AppHeader<'a> { pub struct AppHeader<'a> {
pub name: Loc<ModuleName<'a>>,
pub provides: Vec<'a, Loc<ExposesEntry<'a>>>,
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>, pub imports: Vec<'a, Loc<ImportsEntry<'a>>>,
// Potential comments and newlines - these will typically all be empty. // 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 before_imports: &'a [CommentOrNewline<'a>],
pub after_imports: &'a [CommentOrNewline<'a>], pub after_imports: &'a [CommentOrNewline<'a>],
} }

View file

@ -1244,10 +1244,14 @@ pub fn ident_etc<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
move |arena, state, (loc_ident, opt_extras)| { move |arena, state, (loc_ident, opt_extras)| {
// This appears to be a var, keyword, or function application. // This appears to be a var, keyword, or function application.
match opt_extras { match opt_extras {
(Some(_loc_args), Some((_spaces_before_equals, Either::First(_equals_indent)))) => { (Some(loc_args), Some((_spaces_before_equals, Either::First(_equals_indent)))) => {
// We got args with an '=' after them, e.g. `foo a b = ...` // We got args with an '=' after them, e.g. `foo a b = ...` This is a syntax error!
// This is a syntax error! let region = Region::across_all(loc_args.iter().map(|v| &v.region));
panic!("TODO gracefully handle parse error for defs like `foo a b = ...`"); let fail = Fail {
attempting: state.attempting,
reason: FailReason::ArgumentsBeforeEquals(region),
};
Err((fail, state))
} }
(None, Some((spaces_before_equals, Either::First(equals_indent)))) => { (None, Some((spaces_before_equals, Either::First(equals_indent)))) => {
// We got '=' with no args before it // We got '=' with no args before it

View file

@ -127,9 +127,30 @@ pub fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>> {
#[inline(always)] #[inline(always)]
fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>> { fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>> {
move |_, _| { parser::map(
panic!("TODO parse app header"); 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)] #[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)) 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)] #[inline(always)]
fn exposes<'a>() -> impl Parser< fn exposes<'a>() -> impl Parser<
'a, 'a,

View file

@ -190,6 +190,7 @@ pub enum FailReason {
Eof(Region), Eof(Region),
InvalidPattern, InvalidPattern,
ReservedKeyword(Region), ReservedKeyword(Region),
ArgumentsBeforeEquals(Region),
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]

View file

@ -12,7 +12,7 @@ roc_parse = { path = "../parse" }
inlinable_string = "0.1.0" inlinable_string = "0.1.0"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "0.5.1 " pretty_assertions = "0.5.1"
maplit = "1.0.1" maplit = "1.0.1"
indoc = "0.3.3" indoc = "0.3.3"
quickcheck = "0.8" quickcheck = "0.8"

View file

@ -1,6 +1,6 @@
use inlinable_string::InlinableString; use inlinable_string::InlinableString;
use roc_collections::all::MutSet; 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_module::symbol::{ModuleId, Symbol};
use roc_parse::operator::BinOp; use roc_parse::operator::BinOp;
use roc_parse::pattern::PatternType; use roc_parse::pattern::PatternType;
@ -21,6 +21,30 @@ pub enum Problem {
original_region: Region, original_region: Region,
shadow: Located<Ident>, 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), RuntimeError(RuntimeError),
} }

View file

@ -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 { pub fn span_across(start: &Region, end: &Region) -> Self {
Region { Region {
start_line: start.start_line, start_line: start.start_line,

View file

@ -27,7 +27,7 @@ roc_constrain = { path = "../constrain" }
roc_builtins = { path = "../builtins" } roc_builtins = { path = "../builtins" }
roc_problem = { path = "../problem" } roc_problem = { path = "../problem" }
roc_parse = { path = "../parse" } roc_parse = { path = "../parse" }
pretty_assertions = "0.5.1 " pretty_assertions = "0.5.1"
maplit = "1.0.1" maplit = "1.0.1"
indoc = "0.3.3" indoc = "0.3.3"
quickcheck = "0.8" quickcheck = "0.8"

View 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),
])
}

View file

@ -0,0 +1,4 @@
pub mod canonicalize;
pub mod mono;
pub mod parse;
pub mod r#type;

View 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())
}
}
}

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

View file

@ -29,7 +29,109 @@ pub fn type_problem<'b>(
CircularType(region, symbol, overall_type) => { CircularType(region, symbol, overall_type) => {
to_circular_report(alloc, filename, 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)] #[allow(clippy::too_many_arguments)]
@ -1849,7 +1951,7 @@ mod report_text {
fs: Vec<(Lowercase, ErrorType)>, fs: Vec<(Lowercase, ErrorType)>,
ext: TypeExt, ext: TypeExt,
) -> RocDocBuilder<'b> { ) -> 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)| { let entry_to_doc = |(name, tipe): (Lowercase, ErrorType)| {
( (
@ -2027,9 +2129,9 @@ mod report_text {
fn type_problem_to_pretty<'b>( fn type_problem_to_pretty<'b>(
alloc: &'b RocDocAllocator<'b>, alloc: &'b RocDocAllocator<'b>,
problem: crate::type_error::Problem, problem: crate::error::r#type::Problem,
) -> RocDocBuilder<'b> { ) -> RocDocBuilder<'b> {
use crate::type_error::Problem::*; use crate::error::r#type::Problem::*;
match problem { match problem {
FieldTypo(typo, possibilities) => { FieldTypo(typo, possibilities) => {

View file

@ -11,5 +11,5 @@
// re-enable this when working on performance optimizations than have it block PRs. // re-enable this when working on performance optimizations than have it block PRs.
#![allow(clippy::large_enum_variant)] #![allow(clippy::large_enum_variant)]
pub mod error;
pub mod report; pub mod report;
pub mod type_error;

View file

@ -1,13 +1,15 @@
use roc_collections::all::MutSet;
use roc_module::ident::Ident; use roc_module::ident::Ident;
use roc_module::ident::{Lowercase, TagName, Uppercase}; use roc_module::ident::{Lowercase, TagName, Uppercase};
use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_problem::can::PrecedenceProblem::BothNonAssociative;
use roc_problem::can::{Problem, RuntimeError};
use std::fmt; use std::fmt;
use std::path::PathBuf; use std::path::PathBuf;
use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder, Render, RenderAnnotated}; 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 = std::env::consts::OS == "windows";
const IS_WINDOWS: bool = false; 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_MID: &str = ["| |", "│ ↓"][!IS_WINDOWS as usize];
const CYCLE_END: &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>, alloc: &'b RocDocAllocator<'b>,
indent: usize, indent: usize,
name: RocDocBuilder<'b>, name: RocDocBuilder<'b>,
@ -142,417 +146,6 @@ pub const UNDERLINE_CODE: &str = "\u{001b}[4m";
pub const RESET_CODE: &str = "\u{001b}[0m"; 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 // define custom allocator struct so we can `impl RocDocAllocator` custom helpers
pub struct RocDocAllocator<'a> { pub struct RocDocAllocator<'a> {
upstream: BoxAllocator, upstream: BoxAllocator,
@ -711,6 +304,7 @@ impl<'a> RocDocAllocator<'a> {
self.text(content.to_string()).annotate(Annotation::BinOp) self.text(content.to_string()).annotate(Annotation::BinOp)
} }
/// Turns of backticks/colors in a block
pub fn type_block( pub fn type_block(
&'a self, &'a self,
content: DocBuilder<'a, Self, Annotation>, content: DocBuilder<'a, Self, Annotation>,
@ -724,6 +318,107 @@ impl<'a> RocDocAllocator<'a> {
.annotate(Annotation::Hint) .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( pub fn region_with_subregion(
&'a self, &'a self,
region: roc_region::all::Region, region: roc_region::all::Region,
@ -731,6 +426,11 @@ impl<'a> RocDocAllocator<'a> {
) -> DocBuilder<'a, Self, Annotation> { ) -> DocBuilder<'a, Self, Annotation> {
debug_assert!(region.contains(&sub_region)); 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 // 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 // the problem is. Otherwise, the snippet will have a > on the lines that are in the regon
// where the problem is. // where the problem is.
@ -761,18 +461,18 @@ impl<'a> RocDocAllocator<'a> {
{ {
self.text(" ".repeat(max_line_number_length - this_line_number_length)) self.text(" ".repeat(max_line_number_length - this_line_number_length))
.append(self.text(line_number).annotate(Annotation::LineNumber)) .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(self.text(">").annotate(Annotation::Error))
.append(rest_of_line) .append(rest_of_line)
} else if error_highlight_line { } else if error_highlight_line {
self.text(" ".repeat(max_line_number_length - this_line_number_length)) self.text(" ".repeat(max_line_number_length - this_line_number_length))
.append(self.text(line_number).annotate(Annotation::LineNumber)) .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) .append(rest_of_line)
} else { } else {
self.text(" ".repeat(max_line_number_length - this_line_number_length)) self.text(" ".repeat(max_line_number_length - this_line_number_length))
.append(self.text(line_number).annotate(Annotation::LineNumber)) .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(self.text(" "))
.append(rest_of_line) .append(rest_of_line)
}; };
@ -789,7 +489,7 @@ impl<'a> RocDocAllocator<'a> {
let highlight_line = self let highlight_line = self
.line() .line()
.append(self.text(" ".repeat(max_line_number_length))) .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() { .append(if highlight_text.is_empty() {
self.nil() self.nil()
} else { } else {
@ -808,6 +508,40 @@ impl<'a> RocDocAllocator<'a> {
self.region_with_subregion(region, region) 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> { pub fn ident(&'a self, ident: Ident) -> DocBuilder<'a, Self, Annotation> {
self.text(format!("{}", ident.as_inline_str())) self.text(format!("{}", ident.as_inline_str()))
.annotate(Annotation::Symbol) .annotate(Annotation::Symbol)
@ -843,6 +577,7 @@ pub enum Annotation {
pub struct CiWrite<W> { pub struct CiWrite<W> {
style_stack: Vec<Annotation>, style_stack: Vec<Annotation>,
in_type_block: bool, in_type_block: bool,
in_code_block: bool,
upstream: W, upstream: W,
} }
@ -851,6 +586,7 @@ impl<W> CiWrite<W> {
CiWrite { CiWrite {
style_stack: vec![], style_stack: vec![],
in_type_block: false, in_type_block: false,
in_code_block: false,
upstream, upstream,
} }
} }
@ -898,6 +634,9 @@ where
TypeBlock => { TypeBlock => {
self.in_type_block = true; self.in_type_block = true;
} }
CodeBlock => {
self.in_code_block = true;
}
Emphasized => { Emphasized => {
self.write_str("*")?; self.write_str("*")?;
} }
@ -906,7 +645,7 @@ where
} }
GlobalTag | PrivateTag | Keyword | RecordField | Symbol | Typo | TypoSuggestion GlobalTag | PrivateTag | Keyword | RecordField | Symbol | Typo | TypoSuggestion
| TypeVariable | TypeVariable
if !self.in_type_block => if !self.in_type_block && !self.in_code_block =>
{ {
self.write_str("`")?; self.write_str("`")?;
} }
@ -926,6 +665,9 @@ where
TypeBlock => { TypeBlock => {
self.in_type_block = false; self.in_type_block = false;
} }
CodeBlock => {
self.in_code_block = false;
}
Emphasized => { Emphasized => {
self.write_str("*")?; self.write_str("*")?;
} }
@ -934,7 +676,7 @@ where
} }
GlobalTag | PrivateTag | Keyword | RecordField | Symbol | Typo | TypoSuggestion GlobalTag | PrivateTag | Keyword | RecordField | Symbol | Typo | TypoSuggestion
| TypeVariable | TypeVariable
if !self.in_type_block => if !self.in_type_block && !self.in_code_block =>
{ {
self.write_str("`")?; self.write_str("`")?;
} }

View file

@ -1,7 +1,6 @@
extern crate bumpalo; extern crate bumpalo;
use self::bumpalo::Bump; use self::bumpalo::Bump;
use roc_builtins::unique::uniq_stdlib;
use roc_can::constraint::Constraint; use roc_can::constraint::Constraint;
use roc_can::env::Env; use roc_can::env::Env;
use roc_can::expected::Expected; use roc_can::expected::Expected;
@ -11,13 +10,12 @@ use roc_can::scope::Scope;
use roc_collections::all::{ImMap, MutMap, SendMap, SendSet}; use roc_collections::all::{ImMap, MutMap, SendMap, SendSet};
use roc_constrain::expr::constrain_expr; use roc_constrain::expr::constrain_expr;
use roc_constrain::module::{constrain_imported_values, load_builtin_aliases, Import}; use roc_constrain::module::{constrain_imported_values, load_builtin_aliases, Import};
use roc_module::ident::Ident; use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds};
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol};
use roc_parse::ast::{self, Attempting}; use roc_parse::ast::{self, Attempting};
use roc_parse::blankspace::space0_before; use roc_parse::blankspace::space0_before;
use roc_parse::parser::{loc, Fail, Parser, State}; use roc_parse::parser::{loc, Fail, Parser, State};
use roc_problem::can::Problem; use roc_problem::can::Problem;
use roc_region::all::{Located, Region}; use roc_region::all::Located;
use roc_solve::solve; use roc_solve::solve;
use roc_types::subs::{Content, Subs, VarStore, Variable}; use roc_types::subs::{Content, Subs, VarStore, Variable};
use roc_types::types::Type; 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)] #[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) 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 struct CanExprOut {
pub loc_expr: Located<Expr>, pub loc_expr: Located<Expr>,
pub output: Output, pub output: Output,
@ -204,14 +116,34 @@ pub struct CanExprOut {
pub constraint: Constraint, pub constraint: Constraint,
} }
#[derive(Debug)]
pub struct ParseErrOut {
pub fail: Fail,
pub home: ModuleId,
pub interns: Interns,
}
#[allow(dead_code)] #[allow(dead_code)]
pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut { pub fn can_expr_with(
let loc_expr = parse_loc_with(&arena, expr_str).unwrap_or_else(|e| { arena: &Bump,
panic!( home: ModuleId,
"can_expr_with() got a parse error when attempting to canonicalize:\n\n{:?} {:?}", expr_str: &str,
expr_str, e ) -> 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_store = VarStore::default();
let var = var_store.fresh(); 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, all_ident_ids,
}; };
CanExprOut { Ok(CanExprOut {
loc_expr, loc_expr,
output, output,
problems: env.problems, problems: env.problems,
@ -292,7 +224,7 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut
interns, interns,
var, var,
constraint, constraint,
} })
} }
#[allow(dead_code)] #[allow(dead_code)]

View file

@ -14,15 +14,15 @@ mod test_reporting {
use roc_module::symbol::{Interns, ModuleId}; use roc_module::symbol::{Interns, ModuleId};
use roc_mono::expr::{Expr, Procs}; use roc_mono::expr::{Expr, Procs};
use roc_reporting::report::{ use roc_reporting::report::{
can_problem, mono_problem, Report, BLUE_CODE, BOLD_CODE, CYAN_CODE, DEFAULT_PALETTE, can_problem, mono_problem, parse_problem, type_problem, Report, BLUE_CODE, BOLD_CODE,
GREEN_CODE, MAGENTA_CODE, RED_CODE, RESET_CODE, UNDERLINE_CODE, WHITE_CODE, YELLOW_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::pretty_print::name_all_type_vars;
use roc_types::subs::Subs; use roc_types::subs::Subs;
use std::path::PathBuf; use std::path::PathBuf;
// use roc_region::all; // 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_reporting::report::{RocDocAllocator, RocDocBuilder};
use roc_solve::solve; use roc_solve::solve;
@ -43,13 +43,16 @@ mod test_reporting {
fn infer_expr_help( fn infer_expr_help(
expr_src: &str, expr_src: &str,
) -> ( ) -> Result<
(
Vec<solve::TypeError>, Vec<solve::TypeError>,
Vec<roc_problem::can::Problem>, Vec<roc_problem::can::Problem>,
Vec<roc_mono::expr::MonoProblem>, Vec<roc_mono::expr::MonoProblem>,
ModuleId, ModuleId,
Interns, Interns,
) { ),
ParseErrOut,
> {
let CanExprOut { let CanExprOut {
loc_expr, loc_expr,
output, output,
@ -60,7 +63,7 @@ mod test_reporting {
mut interns, mut interns,
problems: can_problems, problems: can_problems,
.. ..
} = can_expr(expr_src); } = can_expr(expr_src)?;
let mut subs = Subs::new(var_store.into()); let mut subs = Subs::new(var_store.into());
for (var, name) in output.introduced_variables.name_by_var { 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) fn list_reports<F>(src: &str, buf: &mut String, callback: F)
@ -108,14 +111,29 @@ mod test_reporting {
{ {
use ven_pretty::DocAllocator; 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 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 filename = filename_from_string(r"\code\proj\Main.roc");
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 mut reports = Vec::new();
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
for problem in can_problems { for problem in can_problems {
let report = can_problem(&alloc, filename.clone(), problem.clone()); let report = can_problem(&alloc, filename.clone(), problem.clone());
reports.push(report); reports.push(report);
@ -143,6 +161,8 @@ mod test_reporting {
callback(doc, buf) callback(doc, buf)
} }
}
}
fn report_problem_as(src: &str, expected_rendering: &str) { fn report_problem_as(src: &str, expected_rendering: &str) {
let mut buf: String = String::new(); let mut buf: String = String::new();
@ -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 mut buf = String::new();
let src_lines: Vec<&str> = src.split('\n').collect(); 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) = 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 mut buf = String::new();
let src_lines: Vec<&str> = src.split('\n').collect(); 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
^^^
"#
),
)
}
} }

View file

@ -18,7 +18,7 @@ roc_builtins = { path = "../builtins" }
roc_problem = { path = "../problem" } roc_problem = { path = "../problem" }
roc_parse = { path = "../parse" } roc_parse = { path = "../parse" }
roc_solve = { path = "../solve" } roc_solve = { path = "../solve" }
pretty_assertions = "0.5.1 " pretty_assertions = "0.5.1"
maplit = "1.0.1" maplit = "1.0.1"
indoc = "0.3.3" indoc = "0.3.3"
quickcheck = "0.8" quickcheck = "0.8"

View file

@ -21,6 +21,7 @@ pub enum TypeError {
BadExpr(Region, Category, ErrorType, Expected<ErrorType>), BadExpr(Region, Category, ErrorType, Expected<ErrorType>),
BadPattern(Region, PatternCategory, ErrorType, PExpected<ErrorType>), BadPattern(Region, PatternCategory, ErrorType, PExpected<ErrorType>),
CircularType(Region, Symbol, ErrorType), CircularType(Region, Symbol, ErrorType),
BadType(roc_types::types::Problem),
} }
pub type SubsByModule = MutMap<ModuleId, ExposedModuleTypes>; pub type SubsByModule = MutMap<ModuleId, ExposedModuleTypes>;
@ -166,6 +167,13 @@ fn solve(
problems.push(problem); problems.push(problem);
state
}
BadType(vars, problem) => {
introduce(subs, rank, pools, &vars);
problems.push(TypeError::BadType(problem));
state state
} }
} }
@ -228,6 +236,13 @@ fn solve(
problems.push(problem); problems.push(problem);
state
}
BadType(vars, problem) => {
introduce(subs, rank, pools, &vars);
problems.push(TypeError::BadType(problem));
state state
} }
} }
@ -278,6 +293,13 @@ fn solve(
problems.push(problem); problems.push(problem);
state
}
BadType(vars, problem) => {
introduce(subs, rank, pools, &vars);
problems.push(TypeError::BadType(problem));
state state
} }
} }
@ -829,7 +851,7 @@ fn circular_error(
loc_var: &Located<Variable>, loc_var: &Located<Variable>,
) { ) {
let var = loc_var.value; 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); let problem = TypeError::CircularType(loc_var.region, symbol, error_type);
subs.set_content(var, Content::Error); subs.set_content(var, Content::Error);

View file

@ -14,7 +14,7 @@ ven_ena = { path = "../../vendor/ena" }
inlinable_string = "0.1.0" inlinable_string = "0.1.0"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "0.5.1 " pretty_assertions = "0.5.1"
maplit = "1.0.1" maplit = "1.0.1"
indoc = "0.3.3" indoc = "0.3.3"
quickcheck = "0.8" quickcheck = "0.8"

View file

@ -37,9 +37,10 @@ impl fmt::Debug for Mark {
} }
#[derive(Default)] #[derive(Default)]
struct NameState { struct ErrorTypeState {
taken: MutSet<Lowercase>, taken: MutSet<Lowercase>,
normals: u32, normals: u32,
problems: Vec<crate::types::Problem>,
} }
#[derive(Default, Clone)] #[derive(Default, Clone)]
@ -363,7 +364,7 @@ impl Subs {
explicit_substitute(self, x, y, z, &mut seen) 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 names = get_var_names(self, var, ImMap::default());
let mut taken = MutSet::default(); let mut taken = MutSet::default();
@ -371,9 +372,13 @@ impl Subs {
taken.insert(name); 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) { 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); let desc = subs.get(var);
if desc.mark == Mark::OCCURS { 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( fn content_to_err_type(
subs: &mut Subs, subs: &mut Subs,
state: &mut NameState, state: &mut ErrorTypeState,
var: Variable, var: Variable,
content: Content, content: Content,
) -> ErrorType { ) -> 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::*; use self::FlatType::*;
match flat_type { 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), 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); let (name, new_index) = name_type_var(state.normals, &mut state.taken);
state.normals = new_index; state.normals = new_index;

View file

@ -395,6 +395,7 @@ impl Type {
pub fn instantiate_aliases( pub fn instantiate_aliases(
&mut self, &mut self,
region: Region,
aliases: &ImMap<Symbol, Alias>, aliases: &ImMap<Symbol, Alias>,
var_store: &VarStore, var_store: &VarStore,
introduced: &mut ImSet<Variable>, introduced: &mut ImSet<Variable>,
@ -404,34 +405,44 @@ impl Type {
match self { match self {
Function(args, ret) => { Function(args, ret) => {
for arg in args { 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) => { RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => {
for (_, args) in tags { for (_, args) in tags {
for x in args { 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) => { Record(fields, ext) => {
for x in fields.iter_mut() { 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) => { Alias(_, type_args, actual_type) => {
for arg in type_args { 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) => { Apply(symbol, args) => {
if let Some(alias) = aliases.get(symbol) { 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 actual = alias.typ.clone();
let mut named_args = Vec::with_capacity(args.len()); let mut named_args = Vec::with_capacity(args.len());
@ -447,7 +458,7 @@ impl Type {
) in alias.vars.iter().zip(args.iter()) ) in alias.vars.iter().zip(args.iter())
{ {
let mut filler = filler.clone(); 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())); named_args.push((lowercase.clone(), filler.clone()));
substitution.insert(*placeholder, filler); substitution.insert(*placeholder, filler);
} }
@ -463,7 +474,7 @@ impl Type {
} }
actual.substitute(&substitution); actual.substitute(&substitution);
actual.instantiate_aliases(aliases, var_store, introduced); actual.instantiate_aliases(region, aliases, var_store, introduced);
// instantiate recursion variable! // instantiate recursion variable!
if let Type::RecursiveTagUnion(rec_var, mut tags, mut ext) = actual { if let Type::RecursiveTagUnion(rec_var, mut tags, mut ext) = actual {
@ -487,7 +498,7 @@ impl Type {
} else { } else {
// one of the special-cased Apply types. // one of the special-cased Apply types.
for x in args { 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)] #[derive(PartialEq, Eq, Debug, Clone)]
pub enum Problem { pub enum Problem {
CanonicalizationProblem, CanonicalizationProblem,
Mismatch(Mismatch, ErrorType, ErrorType),
CircularType(Symbol, ErrorType, Region), CircularType(Symbol, ErrorType, Region),
CyclicAlias(Symbol, Region, Vec<Symbol>),
UnrecognizedIdent(InlinableString), UnrecognizedIdent(InlinableString),
Shadowed(Region, Located<Ident>), Shadowed(Region, Located<Ident>),
BadTypeArguments {
symbol: Symbol,
region: Region,
type_got: u8,
alias_needs: u8,
},
InvalidModule, InvalidModule,
SolvedTypeError,
} }
#[derive(PartialEq, Eq, Debug, Clone)] #[derive(PartialEq, Eq, Debug, Clone)]

View file

@ -10,7 +10,7 @@ roc_module = { path = "../module" }
roc_types = { path = "../types" } roc_types = { path = "../types" }
[dev-dependencies] [dev-dependencies]
pretty_assertions = "0.5.1 " pretty_assertions = "0.5.1"
maplit = "1.0.1" maplit = "1.0.1"
indoc = "0.3.3" indoc = "0.3.3"
quickcheck = "0.8" quickcheck = "0.8"

View file

@ -73,6 +73,7 @@ struct Context {
pub enum Unified { pub enum Unified {
Success(Pool), Success(Pool),
Failure(Pool, ErrorType, ErrorType), Failure(Pool, ErrorType, ErrorType),
BadType(Pool, roc_types::types::Problem),
} }
#[derive(Debug)] #[derive(Debug)]
@ -91,12 +92,19 @@ pub fn unify(subs: &mut Subs, var1: Variable, var2: Variable) -> Unified {
if mismatches.is_empty() { if mismatches.is_empty() {
Unified::Success(vars) Unified::Success(vars)
} else { } else {
let type1 = subs.var_to_error_type(var1); let (type1, mut problems) = subs.var_to_error_type(var1);
let type2 = subs.var_to_error_type(var2); let (type2, problems2) = subs.var_to_error_type(var2);
problems.extend(problems2);
subs.union(var1, var2, Content::Error.into()); subs.union(var1, var2, Content::Error.into());
if !problems.is_empty() {
Unified::BadType(vars, problems.remove(0))
} else {
Unified::Failure(vars, type1, type2) Unified::Failure(vars, type1, type2)
} }
}
} }
#[inline(always)] #[inline(always)]

View file

@ -19,7 +19,7 @@ roc_builtins = { path = "../builtins" }
roc_problem = { path = "../problem" } roc_problem = { path = "../problem" }
roc_parse = { path = "../parse" } roc_parse = { path = "../parse" }
roc_solve = { path = "../solve" } roc_solve = { path = "../solve" }
pretty_assertions = "0.5.1 " pretty_assertions = "0.5.1"
maplit = "1.0.1" maplit = "1.0.1"
indoc = "0.3.3" indoc = "0.3.3"
quickcheck = "0.8" quickcheck = "0.8"

View file

@ -0,0 +1,3 @@
app Hello provides [ main ] imports []
main = "Hello, World!"

View file

@ -3,13 +3,13 @@
To run: To run:
```bash ```bash
$ cargo run hello.roc $ cargo run run Hello.roc
``` ```
To run in release mode instead, do: To run in release mode instead, do:
```bash ```bash
$ cargo run --release hello.roc $ cargo run --release run Hello.roc
``` ```
## Design Notes ## Design Notes

View file

@ -1 +0,0 @@
"Hello, World!"

View file

@ -3,7 +3,7 @@ use std::os::raw::c_char;
#[link(name = "roc_app", kind = "static")] #[link(name = "roc_app", kind = "static")]
extern "C" { extern "C" {
#[link_name = "$Test.main"] #[link_name = "$main"]
fn str_from_roc() -> *const c_char; fn str_from_roc() -> *const c_char;
} }

View file

@ -1,3 +0,0 @@
app
*.o
*.a

View 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

View file

@ -3,11 +3,11 @@
To run: To run:
```bash ```bash
$ cargo run qs.roc $ cargo run run Quicksort.roc
``` ```
To run in release mode instead, do: To run in release mode instead, do:
```bash ```bash
$ cargo run --release qs.roc $ cargo run --release run Quicksort.roc
``` ```