Merge remote-tracking branch 'upstream/main' into str-withprefix

This commit is contained in:
Anton-4 2022-10-08 19:58:51 +02:00
commit 54fb1bc32f
No known key found for this signature in database
GPG key ID: A13F4A6E21141925
222 changed files with 1198 additions and 1070 deletions

View file

@ -6,7 +6,7 @@ name: Nightly Release macOS x86_64
env: env:
ZIG_VERSION: 0.9.1 ZIG_VERSION: 0.9.1
LLVM_SYS_130_PREFIX: /usr/local/opt/llvm LLVM_SYS_130_PREFIX: /usr/local/opt/llvm@13
jobs: jobs:
test-build-upload: test-build-upload:

View file

@ -28,7 +28,7 @@ jobs:
run: ls | grep "roc_nightly.*tar\.gz" | xargs tar -xzvf run: ls | grep "roc_nightly.*tar\.gz" | xargs tar -xzvf
- name: test roc hello world - name: test roc hello world
run: ./roc examples/hello-world/main.roc run: ./roc examples/helloWorld.roc

View file

@ -41,7 +41,7 @@ jobs:
run: ls | grep "roc_nightly.*tar\.gz" | xargs tar -xzvf run: ls | grep "roc_nightly.*tar\.gz" | xargs tar -xzvf
- name: test roc hello world - name: test roc hello world
run: ./roc examples/hello-world/main.roc run: ./roc examples/helloWorld.roc

View file

@ -28,13 +28,7 @@ sh <(curl -L https://nixos.org/nix/install) --no-daemon
sh <(curl -L https://nixos.org/nix/install) --daemon sh <(curl -L https://nixos.org/nix/install) --daemon
``` ```
Open a new terminal and install nixFlakes in your environment: Open a new terminal and edit either `~/.config/nix/nix.conf` or `/etc/nix/nix.conf` and add:
```sh
nix-env -iA nixpkgs.nixFlakes
```
Edit either `~/.config/nix/nix.conf` or `/etc/nix/nix.conf` and add:
```text ```text
experimental-features = nix-command flakes experimental-features = nix-command flakes

35
Cargo.lock generated
View file

@ -620,10 +620,10 @@ dependencies = [
[[package]] [[package]]
name = "confy" name = "confy"
version = "0.4.0" version = "0.5.0"
source = "git+https://github.com/rust-cli/confy#c6b62039281b8643539b436440bcea1b0d634bc7" source = "git+https://github.com/rust-cli/confy#fd069f062aa3373c846f0d8c6e3b5e2a5cd0096b"
dependencies = [ dependencies = [
"directories-next", "directories",
"serde", "serde",
"serde_yaml", "serde_yaml",
"thiserror", "thiserror",
@ -1160,13 +1160,13 @@ dependencies = [
] ]
[[package]] [[package]]
name = "directories-next" name = "directories"
version = "2.0.0" version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" checksum = "551a778172a450d7fc12e629ca3b0428d00f6afa9a43da1b630d54604e97371c"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 0.1.10",
"dirs-sys-next", "dirs-sys",
] ]
[[package]] [[package]]
@ -1179,6 +1179,17 @@ dependencies = [
"dirs-sys-next", "dirs-sys-next",
] ]
[[package]]
name = "dirs-sys"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]] [[package]]
name = "dirs-sys-next" name = "dirs-sys-next"
version = "0.1.2" version = "0.1.2"
@ -3466,8 +3477,6 @@ dependencies = [
"roc_tracing", "roc_tracing",
"serial_test", "serial_test",
"signal-hook", "signal-hook",
"strum",
"strum_macros",
"target-lexicon", "target-lexicon",
"tempfile", "tempfile",
"ven_pretty", "ven_pretty",
@ -4766,9 +4775,9 @@ checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
[[package]] [[package]]
name = "strum_macros" name = "strum_macros"
version = "0.24.2" version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4faebde00e8ff94316c01800f9054fd2ba77d30d9e922541913051d1d978918b" checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
@ -5154,7 +5163,7 @@ version = "1.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 0.1.10",
"rand", "rand",
"static_assertions", "static_assertions",
] ]

View file

@ -57,9 +57,10 @@ members = [
"crates/wasi-libc-sys", "crates/wasi-libc-sys",
] ]
exclude = [ exclude = [
# Examples sometimes have Rust hosts in their platforms. The compiler should ignore those.
"examples",
"ci/bench-runner", "ci/bench-runner",
# Examples sometimes have Rust hosts in their platforms. The compiler should ignore those.
"crates/cli_testing_examples",
"examples",
# Ignore building these normally. They are only imported by tests. # Ignore building these normally. They are only imported by tests.
# The tests will still correctly build them. # The tests will still correctly build them.
"crates/cli_utils", "crates/cli_utils",

View file

@ -50,7 +50,7 @@ install-zig-llvm-valgrind:
copy-dirs: copy-dirs:
FROM +install-zig-llvm-valgrind FROM +install-zig-llvm-valgrind
COPY --dir crates examples Cargo.toml Cargo.lock version.txt www ./ COPY --dir crates Cargo.toml Cargo.lock version.txt www ./
# compile everything needed for benchmarks and output a self-contained dir from which benchmarks can be run. # compile everything needed for benchmarks and output a self-contained dir from which benchmarks can be run.
prep-bench-folder: prep-bench-folder:
@ -60,11 +60,11 @@ prep-bench-folder:
ARG BENCH_SUFFIX=branch ARG BENCH_SUFFIX=branch
RUN cargo criterion -V RUN cargo criterion -V
RUN --mount=type=cache,target=$SCCACHE_DIR cd crates/cli && cargo criterion --no-run RUN --mount=type=cache,target=$SCCACHE_DIR cd crates/cli && cargo criterion --no-run
RUN mkdir -p bench-folder/crates/cli_testing_examples/benchmarks
RUN mkdir -p bench-folder/crates/compiler/builtins/bitcode/src RUN mkdir -p bench-folder/crates/compiler/builtins/bitcode/src
RUN mkdir -p bench-folder/target/release/deps RUN mkdir -p bench-folder/target/release/deps
RUN mkdir -p bench-folder/examples/benchmarks RUN cp crates/cli_testing_examples/benchmarks/*.roc bench-folder/crates/cli_testing_examples/benchmarks/
RUN cp examples/benchmarks/*.roc bench-folder/examples/benchmarks/ RUN cp -r crates/cli_testing_examples/benchmarks/platform bench-folder/crates/cli_testing_examples/benchmarks/
RUN cp -r examples/benchmarks/platform bench-folder/examples/benchmarks/
RUN cp crates/compiler/builtins/bitcode/src/str.zig bench-folder/crates/compiler/builtins/bitcode/src RUN cp crates/compiler/builtins/bitcode/src/str.zig bench-folder/crates/compiler/builtins/bitcode/src
RUN cp target/release/roc bench-folder/target/release RUN cp target/release/roc bench-folder/target/release
# copy the most recent time bench to bench-folder # copy the most recent time bench to bench-folder

View file

@ -115,7 +115,7 @@ Create a new file called `Hello.roc` and put this inside it:
```coffee ```coffee
app "hello" app "hello"
packages { pf: "examples/interactive/cli-platform/main.roc" } packages { pf: "examples/cli/cli-platform/main.roc" }
imports [pf.Stdout, pf.Program] imports [pf.Stdout, pf.Program]
provides [main] to pf provides [main] to pf
@ -124,8 +124,8 @@ main = Stdout.line "I'm a Roc application!" |> Program.quick
> **NOTE:** This assumes you've put Hello.roc in the root directory of the Roc > **NOTE:** This assumes you've put Hello.roc in the root directory of the Roc
> source code. If you'd like to put it somewhere else, you'll need to replace > source code. If you'd like to put it somewhere else, you'll need to replace
> `"examples/interactive/cli-platform/main.roc"` with the path to the > `"examples/cli/cli-platform/main.roc"` with the path to the
> `examples/interactive/cli-platform/main.roc` file in that source code. In the future, > `examples/cli/cli-platform/main.roc` file in that source code. In the future,
> Roc will have the tutorial built in, and this aside will no longer be > Roc will have the tutorial built in, and this aside will no longer be
> necessary! > necessary!
@ -1273,7 +1273,7 @@ Let's take a closer look at the part of `Hello.roc` above `main`:
```coffee ```coffee
app "hello" app "hello"
packages { pf: "examples/interactive/cli-platform/main.roc" } packages { pf: "examples/cli/cli-platform/main.roc" }
imports [pf.Stdout, pf.Program] imports [pf.Stdout, pf.Program]
provides main to pf provides main to pf
``` ```
@ -1291,14 +1291,14 @@ without running it by running `roc build Hello.roc`.
The remaining lines all involve the *platform* this application is built on: The remaining lines all involve the *platform* this application is built on:
```coffee ```coffee
packages { pf: "examples/interactive/cli-platform/main.roc" } packages { pf: "examples/cli/cli-platform/main.roc" }
imports [pf.Stdout, pf.Program] imports [pf.Stdout, pf.Program]
provides main to pf provides main to pf
``` ```
The `packages { pf: "examples/interactive/cli-platform/main.roc" }` part says two things: The `packages { pf: "examples/cli/cli-platform/main.roc" }` part says two things:
- We're going to be using a *package* (that is, a collection of modules) called `"examples/interactive/cli-platform/main.roc"` - We're going to be using a *package* (that is, a collection of modules) called `"examples/cli/cli-platform/main.roc"`
- We're going to name that package `pf` so we can refer to it more concisely in the future. - We're going to name that package `pf` so we can refer to it more concisely in the future.
The `imports [pf.Stdout, pf.Program]` line says that we want to import the `Stdout` and `Program` modules The `imports [pf.Stdout, pf.Program]` line says that we want to import the `Stdout` and `Program` modules
@ -1320,16 +1320,16 @@ which effectively makes it a simple Roc program.
When we write `imports [pf.Stdout, pf.Program]`, it specifies that the `Stdout` When we write `imports [pf.Stdout, pf.Program]`, it specifies that the `Stdout`
and `Program` modules come from the `pf` package. and `Program` modules come from the `pf` package.
Since `pf` was the name we chose for the `examples/interactive/cli-platform/main.roc` Since `pf` was the name we chose for the `examples/cli/cli-platform/main.roc`
package (when we wrote `packages { pf: "examples/interactive/cli-platform/main.roc" }`), package (when we wrote `packages { pf: "examples/cli/cli-platform/main.roc" }`),
this `imports` line tells the Roc compiler that when we call `Stdout.line`, it this `imports` line tells the Roc compiler that when we call `Stdout.line`, it
should look for that `line` function in the `Stdout` module of the should look for that `line` function in the `Stdout` module of the
`examples/interactive/cli-platform/main.roc` package. `examples/cli/cli-platform/main.roc` package.
## Tasks ## Tasks
Tasks are technically not part of the Roc language, but they're very common in Tasks are technically not part of the Roc language, but they're very common in
platforms. Let's use the CLI platform in `examples/interactive/cli-platform/main.roc` as an example! platforms. Let's use the CLI platform in `examples/cli/cli-platform/main.roc` as an example!
In the CLI platform, we have four operations we can do: In the CLI platform, we have four operations we can do:
@ -1344,7 +1344,7 @@ First, let's do a basic "Hello World" using the tutorial app.
```coffee ```coffee
app "cli-tutorial" app "cli-tutorial"
packages { pf: "examples/interactive/cli-platform/main.roc" } packages { pf: "examples/cli/cli-platform/main.roc" }
imports [pf.Stdout, pf.Program] imports [pf.Stdout, pf.Program]
provides [main] to pf provides [main] to pf
@ -1382,7 +1382,7 @@ Let's change `main` to read a line from `stdin`, and then print it back out agai
```swift ```swift
app "cli-tutorial" app "cli-tutorial"
packages { pf: "examples/interactive/cli-platform/main.roc" } packages { pf: "examples/cli/cli-platform/main.roc" }
imports [pf.Stdout, pf.Stdin, pf.Task, pf.Program] imports [pf.Stdout, pf.Stdin, pf.Task, pf.Program]
provides [main] to pf provides [main] to pf
@ -1434,7 +1434,7 @@ This works, but we can make it a little nicer to read. Let's change it to the fo
```haskell ```haskell
app "cli-tutorial" app "cli-tutorial"
packages { pf: "examples/interactive/cli-platform/main.roc" } packages { pf: "examples/cli/cli-platform/main.roc" }
imports [pf.Stdout, pf.Stdin, pf.Task.{ await }, pf.Program] imports [pf.Stdout, pf.Stdin, pf.Task.{ await }, pf.Program]
provides [main] to pf provides [main] to pf

View file

@ -227,9 +227,10 @@ fn calc_hashes_for_folder(benches_path_str: &str) -> HashMap<String, String> {
} }
fn check_if_bench_executables_changed() -> bool { fn check_if_bench_executables_changed() -> bool {
let bench_folder_str = "/examples/benchmarks/"; let bench_folder_str = "/crates/cli_testing_examples/benchmarks/";
let main_benches_path_str = [BENCH_FOLDER_MAIN, bench_folder_str].join(""); let main_benches_path_str = [BENCH_FOLDER_MAIN, bench_folder_str].join("");
let main_bench_hashes = calc_hashes_for_folder(&main_benches_path_str); let main_bench_hashes = calc_hashes_for_folder(&main_benches_path_str);
let branch_benches_path_str = [BENCH_FOLDER_BRANCH, bench_folder_str].join(""); let branch_benches_path_str = [BENCH_FOLDER_BRANCH, bench_folder_str].join("");

View file

@ -1,4 +1,4 @@
#!/usr/bin/env bash #!/usr/bin/env bash
cp target/release/roc ./roc # to be able to exclude "target" later in the tar command cp target/release/roc ./roc # to be able to exclude "target" later in the tar command
cp -r target/release/lib ./lib cp -r target/release/lib ./lib
tar -czvf $1 --exclude="target" --exclude="zig-cache" roc lib LICENSE LEGAL_DETAILS examples/hello-world crates/roc_std tar -czvf $1 --exclude="target" --exclude="zig-cache" roc lib LICENSE LEGAL_DETAILS examples/helloWorld.roc examples/cli crates/roc_std

View file

@ -99,8 +99,6 @@ indoc = "1.0.7"
serial_test = "0.9.0" serial_test = "0.9.0"
criterion = { git = "https://github.com/Anton-4/criterion.rs"} criterion = { git = "https://github.com/Anton-4/criterion.rs"}
cli_utils = { path = "../cli_utils" } cli_utils = { path = "../cli_utils" }
strum = "0.24.0"
strum_macros = "0.24"
once_cell = "1.14.0" once_cell = "1.14.0"
parking_lot = "0.12" parking_lot = "0.12"

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
app "type-error" app "type-error"
packages { pf: "../../../../examples/interactive/cli-platform/main.roc" } packages { pf: "../../../../examples/cli/cli-platform/main.roc" }
imports [pf.Stdout.{ line }, pf.Task.{ await }, pf.Program] imports [pf.Stdout.{ line }, pf.Task.{ await }, pf.Program]
provides [main] to pf provides [main] to pf

View file

@ -0,0 +1,6 @@
*.dSYM
libhost.a
libapp.so
dynhost
preprocessedhost
metadata

View file

@ -16,7 +16,7 @@ name = "host"
path = "src/main.rs" path = "src/main.rs"
[dependencies] [dependencies]
roc_std = { path = "../../../crates/roc_std" } roc_std = { path = "../../../../crates/roc_std" }
libc = "0.2" libc = "0.2"
[workspace] [workspace]

View file

@ -3,12 +3,12 @@
To run this website, first compile either of these identical apps: To run this website, first compile either of these identical apps:
```bash ```bash
# Option A: Compile examples/platform-switching/rocLovesWebAssembly.roc # Option A: Compile crates/cli_testing_examples/platform-switching/rocLovesWebAssembly.roc
cargo run -- build --target=wasm32 examples/platform-switching/rocLovesWebAssembly.roc cargo run -- build --target=wasm32 crates/cli_testing_examples/platform-switching/rocLovesWebAssembly.roc
# Option B: Compile examples/platform-switching/main.roc with `pf: "web-assembly-platform/main.roc"` and move the result # Option B: Compile crates/cli_testing_examples/platform-switching/main.roc with `pf: "web-assembly-platform/main.roc"` and move the result
cargo run -- build --target=wasm32 examples/platform-switching/main.roc cargo run -- build --target=wasm32 crates/cli_testing_examples/platform-switching/main.roc
(cd examples/platform-switching && mv rocLovesPlatforms.wasm web-assembly-platform/rocLovesWebAssembly.wasm) (cd crates/cli_testing_examples/platform-switching && mv rocLovesPlatforms.wasm web-assembly-platform/rocLovesWebAssembly.wasm)
``` ```
Then `cd` into the website directory Then `cd` into the website directory
@ -16,7 +16,7 @@ and run any web server that can handle WebAssembly.
For example, with `http-server`: For example, with `http-server`:
```bash ```bash
cd examples/platform-switching/web-assembly-platform cd crates/cli_testing_examples/platform-switching/web-assembly-platform
npm install -g http-server npm install -g http-server
http-server http-server
``` ```

File diff suppressed because one or more lines are too long

View file

@ -381,20 +381,31 @@ pub fn root_dir() -> PathBuf {
path path
} }
// start the dir with crates/cli_testing_examples
#[allow(dead_code)] #[allow(dead_code)]
pub fn examples_dir(dir_name: &str) -> PathBuf { pub fn cli_testing_dir(dir_name: &str) -> PathBuf {
let mut path = root_dir(); let mut path = root_dir();
// Descend into examples/{dir_name} // Descend into examples/{dir_name}
path.push("examples"); path.push("crates");
path.push("cli_testing_examples");
path.extend(dir_name.split("/")); // Make slashes cross-target path.extend(dir_name.split("/")); // Make slashes cross-target
path path
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn example_file(dir_name: &str, file_name: &str) -> PathBuf { pub fn dir_path_from_root(dir_name: &str) -> PathBuf {
let mut path = examples_dir(dir_name); let mut path = root_dir();
path.extend(dir_name.split("/")); // Make slashes cross-target
path
}
#[allow(dead_code)]
pub fn file_path_from_root(dir_name: &str, file_name: &str) -> PathBuf {
let mut path = dir_path_from_root(dir_name);
path.push(file_name); path.push(file_name);

View file

@ -374,7 +374,7 @@ pub fn build_zig_host_wasm32(
"c", "c",
"-target", "-target",
zig_target, zig_target,
// "-femit-llvm-ir=/home/folkertdev/roc/roc/examples/benchmarks/platform/host.ll", // "-femit-llvm-ir=/home/folkertdev/roc/roc/crates/cli_testing_examples/benchmarks/platform/host.ll",
"-fPIC", "-fPIC",
"--strip", "--strip",
]; ];
@ -635,6 +635,7 @@ pub fn rebuild_host(
} else if cargo_host_src.exists() { } else if cargo_host_src.exists() {
// Compile and link Cargo.toml, if it exists // Compile and link Cargo.toml, if it exists
let cargo_dir = host_input_path.parent().unwrap(); let cargo_dir = host_input_path.parent().unwrap();
let cargo_out_dir = cargo_dir.join("target").join( let cargo_out_dir = cargo_dir.join("target").join(
if matches!(opt_level, OptLevel::Optimize | OptLevel::Size) { if matches!(opt_level, OptLevel::Optimize | OptLevel::Size) {
"release" "release"
@ -1215,7 +1216,7 @@ fn link_wasm32(
"-O", "-O",
"ReleaseSmall", "ReleaseSmall",
// useful for debugging // useful for debugging
// "-femit-llvm-ir=/home/folkertdev/roc/roc/examples/benchmarks/platform/host.ll", // "-femit-llvm-ir=/home/folkertdev/roc/roc/crates/cli_testing_examples/benchmarks/platform/host.ll",
]) ])
.spawn()?; .spawn()?;

View file

@ -204,9 +204,8 @@ takeWhile = \list, predicate ->
helper { taken: [], rest: list } helper { taken: [], rest: list }
asciiByte = \b -> Num.toU8 b digits : List U8
digits = List.range '0' ('9' + 1)
digits = List.range (asciiByte '0') (asciiByte '9' + 1)
takeDigits = \bytes -> takeDigits = \bytes ->
takeWhile bytes \n -> List.contains digits n takeWhile bytes \n -> List.contains digits n
@ -215,10 +214,10 @@ takeFloat = \bytes ->
{ taken: intPart, rest } = takeDigits bytes { taken: intPart, rest } = takeDigits bytes
when List.get rest 0 is when List.get rest 0 is
Ok 46 -> # 46 = . Ok '.' ->
{ taken: floatPart, rest: afterAll } = takeDigits (List.split rest 1).others { taken: floatPart, rest: afterAll } = takeDigits (List.split rest 1).others
builtFloat = builtFloat =
List.concat (List.append intPart (asciiByte '.')) floatPart List.concat (List.append intPart '.') floatPart
{ taken: builtFloat, rest: afterAll } { taken: builtFloat, rest: afterAll }
@ -322,14 +321,14 @@ decodeBool = Decode.custom \bytes, @Json {} ->
# Note: this could be more performant by traversing both branches char-by-char. # Note: this could be more performant by traversing both branches char-by-char.
# Doing that would also make `rest` more correct in the erroring case. # Doing that would also make `rest` more correct in the erroring case.
if if
maybeFalse == [asciiByte 'f', asciiByte 'a', asciiByte 'l', asciiByte 's', asciiByte 'e'] maybeFalse == ['f', 'a', 'l', 's', 'e']
then then
{ result: Ok Bool.false, rest: afterFalse } { result: Ok Bool.false, rest: afterFalse }
else else
{ before: maybeTrue, others: afterTrue } = List.split bytes 4 { before: maybeTrue, others: afterTrue } = List.split bytes 4
if if
maybeTrue == [asciiByte 't', asciiByte 'r', asciiByte 'u', asciiByte 'e'] maybeTrue == ['t', 'r', 'u', 'e']
then then
{ result: Ok Bool.true, rest: afterTrue } { result: Ok Bool.true, rest: afterTrue }
else else
@ -340,10 +339,10 @@ jsonString = \bytes ->
{ before, others: afterStartingQuote } = List.split bytes 1 { before, others: afterStartingQuote } = List.split bytes 1
if if
before == [asciiByte '"'] before == ['"']
then then
# TODO: handle escape sequences # TODO: handle escape sequences
{ taken: strSequence, rest } = takeWhile afterStartingQuote \n -> n != asciiByte '"' { taken: strSequence, rest } = takeWhile afterStartingQuote \n -> n != '"'
when Str.fromUtf8 strSequence is when Str.fromUtf8 strSequence is
Ok s -> Ok s ->
@ -368,7 +367,7 @@ decodeList = \decodeElem -> Decode.custom \bytes, @Json {} ->
{ before: afterElem, others } = List.split rest 1 { before: afterElem, others } = List.split rest 1
if if
afterElem == [asciiByte ','] afterElem == [',']
then then
decodeElems others (List.append accum val) decodeElems others (List.append accum val)
else else
@ -379,7 +378,7 @@ decodeList = \decodeElem -> Decode.custom \bytes, @Json {} ->
{ before, others: afterStartingBrace } = List.split bytes 1 { before, others: afterStartingBrace } = List.split bytes 1
if if
before == [asciiByte '['] before == ['[']
then then
# TODO: empty lists # TODO: empty lists
when decodeElems afterStartingBrace [] is when decodeElems afterStartingBrace [] is
@ -388,7 +387,7 @@ decodeList = \decodeElem -> Decode.custom \bytes, @Json {} ->
{ before: maybeEndingBrace, others: afterEndingBrace } = List.split rest 1 { before: maybeEndingBrace, others: afterEndingBrace } = List.split rest 1
if if
maybeEndingBrace == [asciiByte ']'] maybeEndingBrace == [']']
then then
{ result: Ok vals, rest: afterEndingBrace } { result: Ok vals, rest: afterEndingBrace }
else else
@ -410,10 +409,10 @@ parseExactChar = \bytes, char ->
Err _ -> { result: Err TooShort, rest: bytes } Err _ -> { result: Err TooShort, rest: bytes }
openBrace : List U8 -> DecodeResult {} openBrace : List U8 -> DecodeResult {}
openBrace = \bytes -> parseExactChar bytes (asciiByte '{') openBrace = \bytes -> parseExactChar bytes '{'
closingBrace : List U8 -> DecodeResult {} closingBrace : List U8 -> DecodeResult {}
closingBrace = \bytes -> parseExactChar bytes (asciiByte '}') closingBrace = \bytes -> parseExactChar bytes '}'
recordKey : List U8 -> DecodeResult Str recordKey : List U8 -> DecodeResult Str
recordKey = \bytes -> jsonString bytes recordKey = \bytes -> jsonString bytes
@ -422,10 +421,10 @@ anything : List U8 -> DecodeResult {}
anything = \bytes -> { result: Err TooShort, rest: bytes } anything = \bytes -> { result: Err TooShort, rest: bytes }
colon : List U8 -> DecodeResult {} colon : List U8 -> DecodeResult {}
colon = \bytes -> parseExactChar bytes (asciiByte ':') colon = \bytes -> parseExactChar bytes ':'
comma : List U8 -> DecodeResult {} comma : List U8 -> DecodeResult {}
comma = \bytes -> parseExactChar bytes (asciiByte ',') comma = \bytes -> parseExactChar bytes ','
tryDecode : DecodeResult a, ({ val : a, rest : List U8 } -> DecodeResult b) -> DecodeResult b tryDecode : DecodeResult a, ({ val : a, rest : List U8 } -> DecodeResult b) -> DecodeResult b
tryDecode = \{ result, rest }, mapper -> tryDecode = \{ result, rest }, mapper ->

View file

@ -39,6 +39,7 @@ interface List
max, max,
map4, map4,
mapTry, mapTry,
walkTry,
dropFirst, dropFirst,
joinMap, joinMap,
any, any,
@ -959,9 +960,8 @@ mapTry = \list, toResult ->
Result.map (toResult elem) \ok -> Result.map (toResult elem) \ok ->
List.append state ok List.append state ok
## This is the same as `iterate` but with Result instead of [Continue, Break]. ## This is the same as `iterate` but with [Result] instead of `[Continue, Break]`.
## Using `Result` saves a conditional in `mapTry`. ## Using `Result` saves a conditional in `mapTry`.
## It might be useful to expose this in userspace?
walkTry : List elem, state, (state, elem -> Result state err) -> Result state err walkTry : List elem, state, (state, elem -> Result state err) -> Result state err
walkTry = \list, init, func -> walkTry = \list, init, func ->
walkTryHelp list init func 0 (List.len list) walkTryHelp list init func 0 (List.len list)

View file

@ -575,7 +575,6 @@ isGte : Num a, Num a -> Bool
## Returns `Bool.true` if the number is `0`, and `Bool.false` otherwise. ## Returns `Bool.true` if the number is `0`, and `Bool.false` otherwise.
isZero : Num a -> Bool isZero : Num a -> Bool
isZero = \x -> x == 0
## A number is even if dividing it by 2 gives a remainder of 0. ## A number is even if dividing it by 2 gives a remainder of 0.
## ##

View file

@ -55,6 +55,8 @@ macro_rules! map_symbol_to_lowlevel_and_arity {
Symbol::NUM_TO_F32_CHECKED => Some(to_num_checked(Symbol::NUM_TO_F32_CHECKED, var_store, LowLevel::NumToFloatChecked)), Symbol::NUM_TO_F32_CHECKED => Some(to_num_checked(Symbol::NUM_TO_F32_CHECKED, var_store, LowLevel::NumToFloatChecked)),
Symbol::NUM_TO_F64_CHECKED => Some(to_num_checked(Symbol::NUM_TO_F64_CHECKED, var_store, LowLevel::NumToFloatChecked)), Symbol::NUM_TO_F64_CHECKED => Some(to_num_checked(Symbol::NUM_TO_F64_CHECKED, var_store, LowLevel::NumToFloatChecked)),
Symbol::NUM_IS_ZERO => Some(to_num_is_zero(Symbol::NUM_IS_ZERO, var_store)),
_ => None, _ => None,
} }
} }
@ -535,3 +537,33 @@ fn to_num_checked(symbol: Symbol, var_store: &mut VarStore, lowlevel: LowLevel)
ret_var, ret_var,
) )
} }
fn to_num_is_zero(symbol: Symbol, var_store: &mut VarStore) -> Def {
let bool_var = var_store.fresh();
let num_var = var_store.fresh();
let body = Expr::RunLowLevel {
op: LowLevel::Eq,
args: vec![
(num_var, Var(Symbol::ARG_1)),
(
num_var,
Num(
var_store.fresh(),
"0".to_string().into_boxed_str(),
crate::expr::IntValue::I128(0i128.to_ne_bytes()),
roc_types::num::NumBound::None,
),
),
],
ret_var: bool_var,
};
defn(
symbol,
vec![(num_var, Symbol::ARG_1)],
var_store,
body,
bool_var,
)
}

View file

@ -259,7 +259,7 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
Int(v1, v2, str, val, bound) => Int(sub!(*v1), sub!(*v2), str.clone(), *val, *bound), Int(v1, v2, str, val, bound) => Int(sub!(*v1), sub!(*v2), str.clone(), *val, *bound),
Float(v1, v2, str, val, bound) => Float(sub!(*v1), sub!(*v2), str.clone(), *val, *bound), Float(v1, v2, str, val, bound) => Float(sub!(*v1), sub!(*v2), str.clone(), *val, *bound),
Str(str) => Str(str.clone()), Str(str) => Str(str.clone()),
SingleQuote(char) => SingleQuote(*char), SingleQuote(v1, v2, char, bound) => SingleQuote(sub!(*v1), sub!(*v2), *char, *bound),
List { List {
elem_var, elem_var,
loc_elems, loc_elems,
@ -725,7 +725,7 @@ fn deep_copy_pattern_help<C: CopyEnv>(
FloatLiteral(sub!(*v1), sub!(*v2), s.clone(), *n, *bound) FloatLiteral(sub!(*v1), sub!(*v2), s.clone(), *n, *bound)
} }
StrLiteral(s) => StrLiteral(s.clone()), StrLiteral(s) => StrLiteral(s.clone()),
SingleQuote(c) => SingleQuote(*c), SingleQuote(v1, v2, c, bound) => SingleQuote(sub!(*v1), sub!(*v2), *c, *bound),
Underscore => Underscore, Underscore => Underscore,
AbilityMemberSpecialization { ident, specializes } => AbilityMemberSpecialization { AbilityMemberSpecialization { ident, specializes } => AbilityMemberSpecialization {
ident: *ident, ident: *ident,

View file

@ -1884,7 +1884,7 @@ fn pattern_to_vars_by_symbol(
| IntLiteral(..) | IntLiteral(..)
| FloatLiteral(..) | FloatLiteral(..)
| StrLiteral(_) | StrLiteral(_)
| SingleQuote(_) | SingleQuote(..)
| Underscore | Underscore
| MalformedPattern(_, _) | MalformedPattern(_, _)
| UnsupportedPattern(_) | UnsupportedPattern(_)

View file

@ -253,7 +253,7 @@ fn sketch_pattern(pattern: &crate::pattern::Pattern) -> SketchedPattern {
} }
&FloatLiteral(_, _, _, f, _) => SP::Literal(Literal::Float(f64::to_bits(f))), &FloatLiteral(_, _, _, f, _) => SP::Literal(Literal::Float(f64::to_bits(f))),
StrLiteral(v) => SP::Literal(Literal::Str(v.clone())), StrLiteral(v) => SP::Literal(Literal::Str(v.clone())),
&SingleQuote(c) => SP::Literal(Literal::Byte(c as u8)), &SingleQuote(_, _, c, _) => SP::Literal(Literal::Byte(c as u8)),
RecordDestructure { destructs, .. } => { RecordDestructure { destructs, .. } => {
let tag_id = TagId(0); let tag_id = TagId(0);
let mut patterns = std::vec::Vec::with_capacity(destructs.len()); let mut patterns = std::vec::Vec::with_capacity(destructs.len());

View file

@ -22,6 +22,7 @@ use roc_parse::ast::{self, Defs, EscapedChar, StrLiteral};
use roc_parse::pattern::PatternType::*; use roc_parse::pattern::PatternType::*;
use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError}; use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError};
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
use roc_types::num::SingleQuoteBound;
use roc_types::subs::{ExhaustiveMark, IllegalCycleMark, RedundantMark, VarStore, Variable}; use roc_types::subs::{ExhaustiveMark, IllegalCycleMark, RedundantMark, VarStore, Variable};
use roc_types::types::{Alias, Category, LambdaSet, OptAbleVar, Type}; use roc_types::types::{Alias, Category, LambdaSet, OptAbleVar, Type};
use std::fmt::{Debug, Display}; use std::fmt::{Debug, Display};
@ -91,7 +92,8 @@ pub enum Expr {
Int(Variable, Variable, Box<str>, IntValue, IntBound), Int(Variable, Variable, Box<str>, IntValue, IntBound),
Float(Variable, Variable, Box<str>, f64, FloatBound), Float(Variable, Variable, Box<str>, f64, FloatBound),
Str(Box<str>), Str(Box<str>),
SingleQuote(char), // Number variable, precision variable, value, bound
SingleQuote(Variable, Variable, char, SingleQuoteBound),
List { List {
elem_var: Variable, elem_var: Variable,
loc_elems: Vec<Loc<Expr>>, loc_elems: Vec<Loc<Expr>>,
@ -637,7 +639,15 @@ pub fn canonicalize_expr<'a>(
let mut it = string.chars().peekable(); let mut it = string.chars().peekable();
if let Some(char) = it.next() { if let Some(char) = it.next() {
if it.peek().is_none() { if it.peek().is_none() {
(Expr::SingleQuote(char), Output::default()) (
Expr::SingleQuote(
var_store.fresh(),
var_store.fresh(),
char,
SingleQuoteBound::from_char(char),
),
Output::default(),
)
} else { } else {
// multiple chars is found // multiple chars is found
let error = roc_problem::can::RuntimeError::MultipleCharsInSingleQuote(region); let error = roc_problem::can::RuntimeError::MultipleCharsInSingleQuote(region);
@ -1642,7 +1652,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
| other @ Int(..) | other @ Int(..)
| other @ Float(..) | other @ Float(..)
| other @ Str { .. } | other @ Str { .. }
| other @ SingleQuote(_) | other @ SingleQuote(..)
| other @ RuntimeError(_) | other @ RuntimeError(_)
| other @ EmptyRecord | other @ EmptyRecord
| other @ Accessor { .. } | other @ Accessor { .. }
@ -2703,7 +2713,7 @@ fn get_lookup_symbols(expr: &Expr, var_store: &mut VarStore) -> Vec<(Symbol, Var
| Expr::Str(_) | Expr::Str(_)
| Expr::ZeroArgumentTag { .. } | Expr::ZeroArgumentTag { .. }
| Expr::Accessor(_) | Expr::Accessor(_)
| Expr::SingleQuote(_) | Expr::SingleQuote(..)
| Expr::EmptyRecord | Expr::EmptyRecord
| Expr::TypedHole(_) | Expr::TypedHole(_)
| Expr::RuntimeError(_) | Expr::RuntimeError(_)

View file

@ -899,7 +899,7 @@ fn fix_values_captured_in_closure_pattern(
| IntLiteral(..) | IntLiteral(..)
| FloatLiteral(..) | FloatLiteral(..)
| StrLiteral(_) | StrLiteral(_)
| SingleQuote(_) | SingleQuote(..)
| Underscore | Underscore
| Shadowed(..) | Shadowed(..)
| MalformedPattern(_, _) | MalformedPattern(_, _)
@ -1038,7 +1038,7 @@ fn fix_values_captured_in_closure_expr(
| Int(..) | Int(..)
| Float(..) | Float(..)
| Str(_) | Str(_)
| SingleQuote(_) | SingleQuote(..)
| Var(_) | Var(_)
| AbilityMember(..) | AbilityMember(..)
| EmptyRecord | EmptyRecord

View file

@ -12,6 +12,7 @@ use roc_parse::ast::{self, StrLiteral, StrSegment};
use roc_parse::pattern::PatternType; use roc_parse::pattern::PatternType;
use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError, ShadowKind}; use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError, ShadowKind};
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
use roc_types::num::SingleQuoteBound;
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
use roc_types::types::{LambdaSet, OptAbleVar, PatternCategory, Type}; use roc_types::types::{LambdaSet, OptAbleVar, PatternCategory, Type};
@ -59,7 +60,7 @@ pub enum Pattern {
IntLiteral(Variable, Variable, Box<str>, IntValue, IntBound), IntLiteral(Variable, Variable, Box<str>, IntValue, IntBound),
FloatLiteral(Variable, Variable, Box<str>, f64, FloatBound), FloatLiteral(Variable, Variable, Box<str>, f64, FloatBound),
StrLiteral(Box<str>), StrLiteral(Box<str>),
SingleQuote(char), SingleQuote(Variable, Variable, char, SingleQuoteBound),
Underscore, Underscore,
/// An identifier that marks a specialization of an ability member. /// An identifier that marks a specialization of an ability member.
@ -95,7 +96,7 @@ impl Pattern {
IntLiteral(var, ..) => Some(*var), IntLiteral(var, ..) => Some(*var),
FloatLiteral(var, ..) => Some(*var), FloatLiteral(var, ..) => Some(*var),
StrLiteral(_) => None, StrLiteral(_) => None,
SingleQuote(_) => None, SingleQuote(..) => None,
Underscore => None, Underscore => None,
AbilityMemberSpecialization { .. } => None, AbilityMemberSpecialization { .. } => None,
@ -148,7 +149,7 @@ impl Pattern {
IntLiteral(..) => C::Int, IntLiteral(..) => C::Int,
FloatLiteral(..) => C::Float, FloatLiteral(..) => C::Float,
StrLiteral(_) => C::Str, StrLiteral(_) => C::Str,
SingleQuote(_) => C::Character, SingleQuote(..) => C::Character,
Underscore => C::PatternDefault, Underscore => C::PatternDefault,
AbilityMemberSpecialization { .. } => C::PatternDefault, AbilityMemberSpecialization { .. } => C::PatternDefault,
@ -456,7 +457,12 @@ pub fn canonicalize_pattern<'a>(
let mut it = string.chars().peekable(); let mut it = string.chars().peekable();
if let Some(char) = it.next() { if let Some(char) = it.next() {
if it.peek().is_none() { if it.peek().is_none() {
Pattern::SingleQuote(char) Pattern::SingleQuote(
var_store.fresh(),
var_store.fresh(),
char,
SingleQuoteBound::from_char(char),
)
} else { } else {
// multiple chars is found // multiple chars is found
let problem = MalformedPatternProblem::MultipleCharsInSingleQuote; let problem = MalformedPatternProblem::MultipleCharsInSingleQuote;
@ -724,7 +730,7 @@ impl<'a> BindingsFromPattern<'a> {
| IntLiteral(..) | IntLiteral(..)
| FloatLiteral(..) | FloatLiteral(..)
| StrLiteral(_) | StrLiteral(_)
| SingleQuote(_) | SingleQuote(..)
| Underscore | Underscore
| Shadowed(_, _, _) | Shadowed(_, _, _)
| MalformedPattern(_, _) | MalformedPattern(_, _)

View file

@ -4,7 +4,7 @@ use roc_can::expected::Expected::{self, *};
use roc_can::num::{FloatBound, FloatWidth, IntBound, IntLitWidth, NumBound, SignDemand}; use roc_can::num::{FloatBound, FloatWidth, IntBound, IntLitWidth, NumBound, SignDemand};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_region::all::Region; use roc_region::all::Region;
use roc_types::num::NumericRange; use roc_types::num::{NumericRange, SingleQuoteBound};
use roc_types::subs::Variable; use roc_types::subs::Variable;
use roc_types::types::Type::{self, *}; use roc_types::types::Type::{self, *};
use roc_types::types::{AliasKind, Category}; use roc_types::types::{AliasKind, Category};
@ -99,6 +99,42 @@ pub fn int_literal(
constraints.exists([num_var], and_constraint) constraints.exists([num_var], and_constraint)
} }
pub fn single_quote_literal(
constraints: &mut Constraints,
num_var: Variable,
precision_var: Variable,
expected: Expected<Type>,
region: Region,
bound: SingleQuoteBound,
) -> Constraint {
let reason = Reason::IntLiteral;
// Always add the bound first; this improves the resolved type quality in case it's an alias like "U8".
let mut constrs = ArrayVec::<_, 3>::new();
let num_type = add_numeric_bound_constr(
constraints,
&mut constrs,
num_var,
precision_var,
bound,
region,
Category::Character,
);
constrs.extend([
constraints.equal_types(
num_type.clone(),
ForReason(reason, num_int(Type::Variable(precision_var)), region),
Category::Character,
region,
),
constraints.equal_types(num_type, expected, Category::Character, region),
]);
let and_constraint = constraints.and_constraint(constrs);
constraints.exists([num_var], and_constraint)
}
#[inline(always)] #[inline(always)]
pub fn float_literal( pub fn float_literal(
constraints: &mut Constraints, constraints: &mut Constraints,
@ -332,6 +368,16 @@ impl TypedNumericBound for NumBound {
} }
} }
impl TypedNumericBound for SingleQuoteBound {
fn numeric_bound(&self) -> NumericBound {
match self {
&SingleQuoteBound::AtLeast { width } => {
NumericBound::Range(NumericRange::IntAtLeastEitherSign(width))
}
}
}
}
/// A bound placed on a number because of its literal value. /// A bound placed on a number because of its literal value.
/// e.g. `-5` cannot be unsigned, and 300 does not fit in a U8 /// e.g. `-5` cannot be unsigned, and 300 does not fit in a U8
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]

View file

@ -1,7 +1,8 @@
use std::ops::Range; use std::ops::Range;
use crate::builtins::{ use crate::builtins::{
empty_list_type, float_literal, int_literal, list_type, num_literal, num_u32, str_type, empty_list_type, float_literal, int_literal, list_type, num_literal, single_quote_literal,
str_type,
}; };
use crate::pattern::{constrain_pattern, PatternState}; use crate::pattern::{constrain_pattern, PatternState};
use roc_can::annotation::IntroducedVariables; use roc_can::annotation::IntroducedVariables;
@ -292,7 +293,14 @@ pub fn constrain_expr(
constraints.exists(vars, and_constraint) constraints.exists(vars, and_constraint)
} }
Str(_) => constraints.equal_types(str_type(), expected, Category::Str, region), Str(_) => constraints.equal_types(str_type(), expected, Category::Str, region),
SingleQuote(_) => constraints.equal_types(num_u32(), expected, Category::Character, region), SingleQuote(num_var, precision_var, _, bound) => single_quote_literal(
constraints,
*num_var,
*precision_var,
expected,
region,
*bound,
),
List { List {
elem_var, elem_var,
loc_elems, loc_elems,

View file

@ -71,7 +71,7 @@ fn headers_from_annotation_help(
| NumLiteral(..) | NumLiteral(..)
| IntLiteral(..) | IntLiteral(..)
| FloatLiteral(..) | FloatLiteral(..)
| SingleQuote(_) | SingleQuote(..)
| StrLiteral(_) => true, | StrLiteral(_) => true,
RecordDestructure { destructs, .. } => match annotation.value.shallow_dealias() { RecordDestructure { destructs, .. } => match annotation.value.shallow_dealias() {
@ -320,9 +320,32 @@ pub fn constrain_pattern(
)); ));
} }
SingleQuote(_) => { &SingleQuote(num_var, precision_var, _, bound) => {
// First constraint on the free num var; this improves the resolved type quality in
// case the bound is an alias.
let num_type = builtins::add_numeric_bound_constr(
constraints,
&mut state.constraints,
num_var,
num_var,
bound,
region,
Category::Int,
);
// Link the free num var with the int var and our expectation.
let int_type = builtins::num_int(Type::Variable(precision_var));
state.constraints.push(constraints.equal_types(
num_type.clone(), // TODO check me if something breaks!
Expected::NoExpectation(int_type),
Category::Int,
region,
));
// Also constrain the pattern against the num var, again to reuse aliases if they're present.
state.constraints.push(constraints.equal_pattern_types( state.constraints.push(constraints.equal_pattern_types(
builtins::num_u32(), num_type,
expected, expected,
PatternCategory::Character, PatternCategory::Character,
region, region,

View file

@ -1374,6 +1374,7 @@ define_builtins! {
72 LIST_SUBLIST_LOWLEVEL: "sublistLowlevel" 72 LIST_SUBLIST_LOWLEVEL: "sublistLowlevel"
73 LIST_CAPACITY: "capacity" 73 LIST_CAPACITY: "capacity"
74 LIST_MAP_TRY: "mapTry" 74 LIST_MAP_TRY: "mapTry"
75 LIST_WALK_TRY: "walkTry"
} }
7 RESULT: "Result" => { 7 RESULT: "Result" => {
0 RESULT_RESULT: "Result" exposed_type=true // the Result.Result type alias 0 RESULT_RESULT: "Result" exposed_type=true // the Result.Result type alias

View file

@ -4027,12 +4027,18 @@ pub fn with_hole<'a>(
hole, hole,
), ),
SingleQuote(character) => Stmt::Let( SingleQuote(_, _, character, _) => {
let layout = layout_cache
.from_var(env.arena, variable, env.subs)
.unwrap();
Stmt::Let(
assigned, assigned,
Expr::Literal(Literal::Int((character as i128).to_ne_bytes())), Expr::Literal(Literal::Int((character as i128).to_ne_bytes())),
Layout::int_width(IntWidth::I32), layout,
hole, hole,
), )
}
LetNonRec(def, cont) => from_can_let( LetNonRec(def, cont) => from_can_let(
env, env,
procs, procs,
@ -8883,10 +8889,12 @@ fn from_can_pattern_help<'a>(
IntOrFloatValue::Float(*float), IntOrFloatValue::Float(*float),
)), )),
StrLiteral(v) => Ok(Pattern::StrLiteral(v.clone())), StrLiteral(v) => Ok(Pattern::StrLiteral(v.clone())),
SingleQuote(c) => Ok(Pattern::IntLiteral( SingleQuote(var, _, c, _) => match layout_cache.from_var(env.arena, *var, env.subs) {
(*c as i128).to_ne_bytes(), Ok(Layout::Builtin(Builtin::Int(width))) => {
IntWidth::I32, Ok(Pattern::IntLiteral((*c as i128).to_ne_bytes(), width))
)), }
o => internal_error!("an integer width was expected, but we found {:?}", o),
},
Shadowed(region, ident, _new_symbol) => Err(RuntimeError::Shadowing { Shadowed(region, ident, _new_symbol) => Err(RuntimeError::Shadowing {
original_region: *region, original_region: *region,
shadow: ident.clone(), shadow: ident.clone(),

View file

@ -7841,4 +7841,103 @@ mod solve_expr {
"hasher -> hasher | hasher has Hasher", "hasher -> hasher | hasher has Hasher",
); );
} }
#[test]
fn check_char_as_u8() {
infer_eq_without_problem(
indoc!(
r#"
x : U8
x = '.'
x
"#
),
"U8",
);
}
#[test]
fn check_char_as_u16() {
infer_eq_without_problem(
indoc!(
r#"
x : U16
x = '.'
x
"#
),
"U16",
);
}
#[test]
fn check_char_as_u32() {
infer_eq_without_problem(
indoc!(
r#"
x : U32
x = '.'
x
"#
),
"U32",
);
}
#[test]
fn check_char_pattern_as_u8() {
infer_eq_without_problem(
indoc!(
r#"
f : U8 -> _
f = \c ->
when c is
'.' -> 'A'
c1 -> c1
f
"#
),
"U8 -> U8",
);
}
#[test]
fn check_char_pattern_as_u16() {
infer_eq_without_problem(
indoc!(
r#"
f : U16 -> _
f = \c ->
when c is
'.' -> 'A'
c1 -> c1
f
"#
),
"U16 -> U16",
);
}
#[test]
fn check_char_pattern_as_u32() {
infer_eq_without_problem(
indoc!(
r#"
f : U32 -> _
f = \c ->
when c is
'.' -> 'A'
c1 -> c1
f
"#
),
"U32 -> U32",
);
}
} }

View file

@ -58,7 +58,7 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
match e { match e {
Num(_, n, _, _) | Int(_, _, n, _, _) | Float(_, _, n, _, _) => f.text(&**n), Num(_, n, _, _) | Int(_, _, n, _, _) | Float(_, _, n, _, _) => f.text(&**n),
Str(s) => f.text(format!(r#""{}""#, s)), Str(s) => f.text(format!(r#""{}""#, s)),
SingleQuote(c) => f.text(format!("'{}'", c)), SingleQuote(_, _, c, _) => f.text(format!("'{}'", c)),
List { List {
elem_var: _, elem_var: _,
loc_elems, loc_elems,
@ -366,7 +366,7 @@ fn pattern<'a>(
f.text(&**n) f.text(&**n)
} }
StrLiteral(s) => f.text(format!(r#""{}""#, s)), StrLiteral(s) => f.text(format!(r#""{}""#, s)),
SingleQuote(c) => f.text(format!("'{}'", c)), SingleQuote(_, _, c, _) => f.text(format!("'{}'", c)),
Underscore => f.text("_"), Underscore => f.text("_"),
Shadowed(_, _, _) => todo!(), Shadowed(_, _, _) => todo!(),

View file

@ -4067,3 +4067,21 @@ fn int_let_generalization() {
RocStr RocStr
); );
} }
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn pattern_match_char() {
assert_evals_to!(
indoc!(
r#"
c = 'A'
when c is
'A' -> "okay"
_ -> "FAIL"
"#
),
RocStr::from("okay"),
RocStr
);
}

View file

@ -328,6 +328,26 @@ pub enum NumBound {
}, },
} }
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum SingleQuoteBound {
AtLeast { width: IntLitWidth },
}
impl SingleQuoteBound {
pub fn from_char(c: char) -> Self {
let n = c as u32;
let width = if n > u16::MAX as _ {
IntLitWidth::U32
} else if n > u8::MAX as _ {
IntLitWidth::U16
} else {
IntLitWidth::U8
};
Self::AtLeast { width }
}
}
pub const fn int_lit_width_to_variable(w: IntLitWidth) -> Variable { pub const fn int_lit_width_to_variable(w: IntLitWidth) -> Variable {
match w { match w {
IntLitWidth::U8 => Variable::U8, IntLitWidth::U8 => Variable::U8,

View file

@ -521,7 +521,7 @@ fn read_main_roc_file(project_dir_path_opt: Option<&Path>) -> (PathBuf, String)
// returns path and content of app file // returns path and content of app file
fn init_new_roc_project(project_dir_path: &Path) -> (PathBuf, String) { fn init_new_roc_project(project_dir_path: &Path) -> (PathBuf, String) {
let orig_platform_path = Path::new("./examples/hello-world").join(PLATFORM_DIR_NAME); let orig_platform_path = Path::new("./examples/cli").join(PLATFORM_DIR_NAME);
let roc_file_path = Path::new("./new-roc-project/main.roc"); let roc_file_path = Path::new("./new-roc-project/main.roc");

View file

@ -730,16 +730,24 @@ test1 =
file_to_string(&file_path) file_to_string(&file_path)
} }
fn cli_testing_path(sub_path: &str) -> String {
let examples_dir = "../cli_testing_examples/".to_string();
let file_path = examples_dir + sub_path;
file_to_string(&file_path)
}
#[test] #[test]
fn test_hello() { fn test_hello() {
let tokens = tokenize(&example_path("hello-world/main.roc")); let tokens = tokenize(&example_path("helloWorld.roc"));
assert_eq!(tokenparser::module(&tokens), Ok(())); assert_eq!(tokenparser::module(&tokens), Ok(()));
} }
#[test] #[test]
fn test_fibo() { fn test_fibo() {
let tokens = tokenize(&example_path("algorithms/fibonacci.roc")); let tokens = tokenize(&cli_testing_path("algorithms/fibonacci.roc"));
assert_eq!(tokenparser::module(&tokens), Ok(())); assert_eq!(tokenparser::module(&tokens), Ok(()));
} }
@ -831,14 +839,14 @@ test1 =
#[test] #[test]
fn test_base64() { fn test_base64() {
let tokens = tokenize(&example_path("benchmarks/Base64.roc")); let tokens = tokenize(&cli_testing_path("benchmarks/Base64.roc"));
assert_eq!(tokenparser::module(&tokens), Ok(())); assert_eq!(tokenparser::module(&tokens), Ok(()));
} }
#[test] #[test]
fn test_base64_test() { fn test_base64_test() {
let tokens = tokenize(&example_path("benchmarks/TestBase64.roc")); let tokens = tokenize(&cli_testing_path("benchmarks/TestBase64.roc"));
assert_eq!(tokenparser::module(&tokens), Ok(())); assert_eq!(tokenparser::module(&tokens), Ok(()));
} }
@ -874,14 +882,14 @@ test1 =
#[test] #[test]
fn test_astar_test() { fn test_astar_test() {
let tokens = tokenize(&example_path("benchmarks/TestAStar.roc")); let tokens = tokenize(&cli_testing_path("benchmarks/TestAStar.roc"));
assert_eq!(tokenparser::module(&tokens), Ok(())); assert_eq!(tokenparser::module(&tokens), Ok(()));
} }
#[test] #[test]
fn test_cli_echo() { fn test_cli_echo() {
let tokens = tokenize(&example_path("interactive/echo.roc")); let tokens = tokenize(&example_path("cli/echo.roc"));
assert_eq!(tokenparser::module(&tokens), Ok(())); assert_eq!(tokenparser::module(&tokens), Ok(()));
} }
@ -1106,7 +1114,7 @@ test1 =
#[test] #[test]
fn test_closure_file() { fn test_closure_file() {
let tokens = tokenize(&example_path("benchmarks/Closure.roc")); let tokens = tokenize(&cli_testing_path("benchmarks/Closure.roc"));
assert_eq!(tokenparser::module(&tokens), Ok(())); assert_eq!(tokenparser::module(&tokens), Ok(()));
} }
@ -1126,7 +1134,7 @@ test1 =
#[test] #[test]
fn test_nqueens() { fn test_nqueens() {
let tokens = tokenize(&example_path("benchmarks/NQueens.roc")); let tokens = tokenize(&cli_testing_path("benchmarks/NQueens.roc"));
assert_eq!(tokenparser::module(&tokens), Ok(())); assert_eq!(tokenparser::module(&tokens), Ok(()));
} }
@ -1149,7 +1157,7 @@ test1 =
#[test] #[test]
fn test_quicksort() { fn test_quicksort() {
let tokens = tokenize(&example_path("benchmarks/Quicksort.roc")); let tokens = tokenize(&cli_testing_path("benchmarks/Quicksort.roc"));
assert_eq!(tokenparser::module(&tokens), Ok(())); assert_eq!(tokenparser::module(&tokens), Ok(()));
} }
@ -1176,7 +1184,7 @@ test1 =
#[test] #[test]
fn test_task() { fn test_task() {
let tokens = tokenize(&example_path("benchmarks/platform/Task.roc")); let tokens = tokenize(&cli_testing_path("benchmarks/platform/Task.roc"));
assert_eq!(tokenparser::module(&tokens), Ok(())); assert_eq!(tokenparser::module(&tokens), Ok(()));
} }
@ -1221,7 +1229,7 @@ test1 =
#[test] #[test]
fn test_cfold() { fn test_cfold() {
let tokens = tokenize(&example_path("benchmarks/CFold.roc")); let tokens = tokenize(&cli_testing_path("benchmarks/CFold.roc"));
assert_eq!(tokenparser::module(&tokens), Ok(())); assert_eq!(tokenparser::module(&tokens), Ok(()));
} }
@ -1291,7 +1299,7 @@ balance = \color ->
#[test] #[test]
fn test_rbtree_insert() { fn test_rbtree_insert() {
let tokens = tokenize(&example_path("benchmarks/RBTreeInsert.roc")); let tokens = tokenize(&cli_testing_path("benchmarks/RBTreeInsert.roc"));
assert_eq!(tokenparser::module(&tokens), Ok(())); assert_eq!(tokenparser::module(&tokens), Ok(()));
} }
@ -1357,7 +1365,7 @@ balance = \color ->
#[test] #[test]
fn test_rbtree_ck() { fn test_rbtree_ck() {
let tokens = tokenize(&example_path("benchmarks/RBTreeCk.roc")); let tokens = tokenize(&cli_testing_path("benchmarks/RBTreeCk.roc"));
assert_eq!(tokenparser::module(&tokens), Ok(())); assert_eq!(tokenparser::module(&tokens), Ok(()));
} }
@ -1390,7 +1398,7 @@ balance = \color ->
#[test] #[test]
fn test_astar() { fn test_astar() {
let tokens = tokenize(&example_path("benchmarks/AStar.roc")); let tokens = tokenize(&cli_testing_path("benchmarks/AStar.roc"));
assert_eq!(tokenparser::module(&tokens), Ok(())); assert_eq!(tokenparser::module(&tokens), Ok(()));
} }
@ -1409,7 +1417,7 @@ balance = \color ->
#[test] #[test]
fn test_false_interpreter_context() { fn test_false_interpreter_context() {
let tokens = tokenize(&example_path("false-interpreter/Context.roc")); let tokens = tokenize(&example_path("cli/false-interpreter/Context.roc"));
assert_eq!(tokenparser::module(&tokens), Ok(())); assert_eq!(tokenparser::module(&tokens), Ok(()));
} }
@ -1437,7 +1445,7 @@ balance = \color ->
#[test] #[test]
fn test_deriv() { fn test_deriv() {
let tokens = tokenize(&example_path("benchmarks/Deriv.roc")); let tokens = tokenize(&cli_testing_path("benchmarks/Deriv.roc"));
assert_eq!(tokenparser::module(&tokens), Ok(())); assert_eq!(tokenparser::module(&tokens), Ok(()));
} }

View file

@ -11072,4 +11072,64 @@ All branches in an `if` must have the same type!
U8 U8
"### "###
); );
test_report!(
big_char_does_not_fit_in_u8,
indoc!(
r#"
digits : List U8
digits = List.range '0' '9'
List.contains digits '☃'
"#
),
@r###"
TYPE MISMATCH /code/proj/Main.roc
This 2nd argument to `contains` has an unexpected type:
7 List.contains digits '☃'
^^^^^
The argument is a Unicode scalar value of type:
U16, I32, U32, I64, Nat, U64, I128, or U128
But `contains` needs its 2nd argument to be:
U8
"###
);
test_report!(
big_char_does_not_fit_in_u8_pattern,
indoc!(
r#"
x : U8
when x is
'☃' -> ""
_ -> ""
"#
),
@r###"
TYPE MISMATCH /code/proj/Main.roc
The branches of this `when` expression don't match the condition:
6> when x is
7 '☃' -> ""
8 _ -> ""
This `x` value is a:
U8
But the branch patterns have type:
U16, I32, U32, I64, Nat, U64, I128, or U128
The branches must be cases of the `when` condition's type!
"###
);
} }

2
examples/.gitignore vendored
View file

@ -4,3 +4,5 @@ libapp.so
dynhost dynhost
preprocessedhost preprocessedhost
metadata metadata
helloWorld

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