Merge branch 'trunk' into fix-when-branch-solving

This commit is contained in:
Ayaz 2022-04-24 12:19:22 -04:00 committed by GitHub
commit 64ed7eea95
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
85 changed files with 6707 additions and 941 deletions

View File

@ -77,3 +77,4 @@ Cai Bingjun <62678643+C-BJ@users.noreply.github.com>
Jared Cone <jared.cone@gmail.com>
Sean Hagstrom <sean@seanhagstrom.com>
Kas Buunk <kasbuunk@icloud.com>
Oskar Hahn <mail@oshahn.de>

4
Cargo.lock generated
View File

@ -4491,9 +4491,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "target-lexicon"
version = "0.12.2"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9bffcddbc2458fa3e6058414599e3c838a022abae82e5c67b4f7f80298d5bff"
checksum = "d7fa7e55043acb85fca6b3c01485a2eeb6b69c5d21002e273c79e465f43b7ac1"
[[package]]
name = "tempfile"

View File

@ -115,18 +115,19 @@ Create a new file called `Hello.roc` and put this inside it:
```coffee
app "hello"
packages { pf: "examples/cli/platform" }
packages { pf: "examples/interactive/cli-platform" }
imports [ pf.Stdout ]
provides [ main ] to pf
main = Stdout.line "I'm a Roc application!"
```
> **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
> `"examples/cli/"` with the path to the `examples/cli/` folder in
> that source code. In the future, Roc will have the tutorial built in, and this
> aside will no longer be necessary!
> **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
> `"examples/interactive/cli-platform"` with the path to the
> `examples/interactive/cli-platform` folder in that source code. In the future,
> Roc will have the tutorial built in, and this aside will no longer be
> necessary!
Try running this with:
@ -1236,7 +1237,7 @@ Let's take a closer look at the part of `Hello.roc` above `main`:
```coffee
app "hello"
packages { pf: "examples/cli/platform" }
packages { pf: "examples/interactive/cli-platform" }
imports [ pf.Stdout ]
provides main to pf
```
@ -1254,14 +1255,14 @@ without running it by running `roc build Hello.roc`.
The remaining lines all involve the *platform* this application is built on:
```coffee
packages { pf: "examples/cli/platform" }
packages { pf: "examples/interactive/cli-platform" }
imports [ pf.Stdout ]
provides main to pf
```
The `packages { pf: "examples/cli/platform" }` part says two things:
The `packages { pf: "examples/interactive/cli-platform" }` part says two things:
- We're going to be using a *package* (that is, a collection of modules) called `"examples/cli/platform"`
- We're going to be using a *package* (that is, a collection of modules) called `"examples/interactive/cli-platform"`
- We're going to name that package `pf` so we can refer to it more concisely in the future.
The `imports [ pf.Stdout ]` line says that we want to import the `Stdout` module
@ -1281,17 +1282,18 @@ calling a function named `line` which is exposed by a module named
When we write `imports [ pf.Stdout ]`, it specifies that the `Stdout`
module comes from the `pf` package.
Since `pf` was the name we chose for the `examples/cli/platform` package
(when we wrote `packages { pf: "examples/cli/platform" }`), 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 `examples/cli/platform` package.
Since `pf` was the name we chose for the `examples/interactive/cli-platform`
package (when we wrote `packages { pf: "examples/interactive/cli-platform" }`),
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
`examples/interactive/cli-platform` package.
# Building a Command-Line Interface (CLI)
## Tasks
Tasks are technically not part of the Roc language, but they're very common in
platforms. Let's use the CLI platform in `examples/cli` as an example!
platforms. Let's use the CLI platform in `examples/interactive/cli-platform` as an example!
In the CLI platform, we have four operations we can do:
@ -1306,7 +1308,7 @@ First, let's do a basic "Hello World" using the tutorial app.
```coffee
app "cli-tutorial"
packages { pf: "examples/cli/platform" }
packages { pf: "examples/interactive/cli-platform" }
imports [ pf.Stdout ]
provides [ main ] to pf
@ -1343,7 +1345,7 @@ Let's change `main` to read a line from `stdin`, and then print it back out agai
```swift
app "cli-tutorial"
packages { pf: "examples/cli/platform" }
packages { pf: "examples/interactive/cli-platform" }
imports [ pf.Stdout, pf.Stdin, pf.Task ]
provides [ main ] to pf
@ -1393,7 +1395,7 @@ This works, but we can make it a little nicer to read. Let's change it to the fo
```haskell
app "cli-tutorial"
packages { pf: "examples/cli/platform" }
packages { pf: "examples/interactive/cli-platform" }
imports [ pf.Stdout, pf.Stdin, pf.Task.{ await } ]
provides [ main ] to pf
@ -1944,7 +1946,6 @@ Here are various Roc expressions involving operators, and what they desugar to.
| `a // b` | `Num.divTrunc a b` |
| `a ^ b` | `Num.pow a b` |
| `a % b` | `Num.rem a b` |
| `a %% b` | `Num.mod a b` |
| `a >> b` | `Num.shr a b` |
| `a << b` | `Num.shl a b` |
| `-a` | `Num.neg a` |

View File

@ -66,7 +66,7 @@ const_format = "0.2.22"
bumpalo = { version = "3.8.0", features = ["collections"] }
mimalloc = { version = "0.1.26", default-features = false }
target-lexicon = "0.12.2"
target-lexicon = "0.12.3"
tempfile = "3.2.0"
wasmer-wasi = { version = "2.0.0", optional = true }

View File

@ -256,8 +256,8 @@ mod cli_run {
return;
}
}
"hello-gui" => {
// Since this one requires opening a window, we do `roc build` on it but don't run it.
"hello-gui" | "breakout" => {
// Since these require opening a window, we do `roc build` on them but don't run them.
build_example(&file_name, &["--optimize"]);
return;
@ -394,6 +394,14 @@ mod cli_run {
expected_ending: "",
use_valgrind: false,
},
breakout:"breakout" => Example {
filename: "breakout.roc",
executable_filename: "breakout",
stdin: &[],
input_file: None,
expected_ending: "",
use_valgrind: false,
},
quicksort:"algorithms" => Example {
filename: "quicksort.roc",
executable_filename: "quicksort",

View File

@ -167,6 +167,20 @@ For a more detailed understanding of the compilation phases, see the `Phase`, `B
## Debugging intermediate representations
### Debugging the typechecker
Setting the following environment variables:
- `ROC_PRINT_UNIFICATIONS` prints all type unifications that are done,
before and after the unification.
- `ROC_PRINT_MISMATCHES` prints all type mismatches hit during unification.
- `ROC_PRETTY_PRINT_ALIAS_CONTENTS` expands the contents of aliases during
pretty-printing of types.
Note that this is only relevant during debug builds. Eventually we should have
some better debugging tools here, see https://github.com/rtfeldman/roc/issues/2486
for one.
### The mono IR
If you observe a miscomplication, you may first want to check the generated mono

View File

@ -279,7 +279,8 @@ fn build_entry_point(
let block = builder.add_block();
// to the modelling language, the arguments appear out of thin air
let argument_type = build_tuple_type(&mut builder, layout.arguments)?;
let argument_type =
build_tuple_type(&mut builder, layout.arguments, &WhenRecursive::Unreachable)?;
// does not make any assumptions about the input
// let argument = builder.add_unknown_with(block, &[], argument_type)?;
@ -308,7 +309,11 @@ fn build_entry_point(
let block = builder.add_block();
let type_id = layout_spec(&mut builder, &Layout::struct_no_name_order(layouts))?;
let type_id = layout_spec(
&mut builder,
&Layout::struct_no_name_order(layouts),
&WhenRecursive::Unreachable,
)?;
let argument = builder.add_unknown_with(block, &[], type_id)?;
@ -352,8 +357,9 @@ fn proc_spec<'a>(proc: &Proc<'a>) -> Result<(FuncDef, MutSet<UnionLayout<'a>>)>
let arg_type_id = layout_spec(
&mut builder,
&Layout::struct_no_name_order(&argument_layouts),
&WhenRecursive::Unreachable,
)?;
let ret_type_id = layout_spec(&mut builder, &proc.ret_layout)?;
let ret_type_id = layout_spec(&mut builder, &proc.ret_layout, &WhenRecursive::Unreachable)?;
let spec = builder.build(arg_type_id, ret_type_id, root)?;
@ -457,10 +463,14 @@ fn stmt_spec<'a>(
let mut type_ids = Vec::new();
for p in parameters.iter() {
type_ids.push(layout_spec(builder, &p.layout)?);
type_ids.push(layout_spec(
builder,
&p.layout,
&WhenRecursive::Unreachable,
)?);
}
let ret_type_id = layout_spec(builder, layout)?;
let ret_type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?;
let jp_arg_type_id = builder.add_tuple_type(&type_ids)?;
@ -500,14 +510,14 @@ fn stmt_spec<'a>(
builder.add_sub_block(block, BlockExpr(cont_block, cont_value_id))
}
Jump(id, symbols) => {
let ret_type_id = layout_spec(builder, layout)?;
let ret_type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?;
let argument = build_tuple_value(builder, env, block, symbols)?;
let jpid = env.join_points[id];
builder.add_jump(block, jpid, argument, ret_type_id)
}
RuntimeError(_) => {
let type_id = layout_spec(builder, layout)?;
let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?;
builder.add_terminate(block, type_id)
}
@ -556,11 +566,15 @@ fn build_recursive_tuple_type(
builder.add_tuple_type(&field_types)
}
fn build_tuple_type(builder: &mut impl TypeContext, layouts: &[Layout]) -> Result<TypeId> {
fn build_tuple_type(
builder: &mut impl TypeContext,
layouts: &[Layout],
when_recursive: &WhenRecursive,
) -> Result<TypeId> {
let mut field_types = Vec::new();
for field in layouts.iter() {
field_types.push(layout_spec(builder, field)?);
field_types.push(layout_spec(builder, field, when_recursive)?);
}
builder.add_tuple_type(&field_types)
@ -691,7 +705,7 @@ fn call_spec(
.map(|symbol| env.symbols[symbol])
.collect();
let result_type = layout_spec(builder, ret_layout)?;
let result_type = layout_spec(builder, ret_layout, &WhenRecursive::Unreachable)?;
builder.add_unknown_with(block, &arguments, result_type)
}
@ -761,7 +775,8 @@ fn call_spec(
};
let state_layout = argument_layouts[0];
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = state;
add_loop(builder, block, state_type, init_state, loop_body)
@ -782,7 +797,8 @@ fn call_spec(
};
let state_layout = argument_layouts[0];
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = state;
add_loop(builder, block, state_type, init_state, loop_body)
@ -806,7 +822,8 @@ fn call_spec(
};
let state_layout = argument_layouts[0];
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = state;
add_loop(builder, block, state_type, init_state, loop_body)
@ -828,10 +845,12 @@ fn call_spec(
list_append(builder, block, update_mode_var, state, new_element)
};
let output_element_type = layout_spec(builder, return_layout)?;
let output_element_type =
layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?;
let state_layout = Layout::Builtin(Builtin::List(return_layout));
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = new_list(builder, block, output_element_type)?;
@ -851,10 +870,12 @@ fn call_spec(
list_append(builder, block, update_mode_var, state, new_element)
};
let output_element_type = layout_spec(builder, return_layout)?;
let output_element_type =
layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?;
let state_layout = Layout::Builtin(Builtin::List(return_layout));
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = new_list(builder, block, output_element_type)?;
@ -879,7 +900,8 @@ fn call_spec(
};
let state_layout = Layout::Builtin(Builtin::List(&argument_layouts[0]));
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = list;
add_loop(builder, block, state_type, init_state, loop_body)
@ -903,10 +925,12 @@ fn call_spec(
list_append(builder, block, update_mode_var, state, new_element)
};
let output_element_type = layout_spec(builder, return_layout)?;
let output_element_type =
layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?;
let state_layout = Layout::Builtin(Builtin::List(return_layout));
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = new_list(builder, block, output_element_type)?;
@ -936,10 +960,12 @@ fn call_spec(
list_append(builder, block, update_mode_var, state, new_element)
};
let output_element_type = layout_spec(builder, return_layout)?;
let output_element_type =
layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?;
let state_layout = Layout::Builtin(Builtin::List(return_layout));
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = new_list(builder, block, output_element_type)?;
@ -975,10 +1001,12 @@ fn call_spec(
list_append(builder, block, update_mode_var, state, new_element)
};
let output_element_type = layout_spec(builder, return_layout)?;
let output_element_type =
layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?;
let state_layout = Layout::Builtin(Builtin::List(return_layout));
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = new_list(builder, block, output_element_type)?;
@ -1010,7 +1038,8 @@ fn call_spec(
};
let state_layout = Layout::Builtin(Builtin::List(&argument_layouts[0]));
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = list;
add_loop(builder, block, state_type, init_state, loop_body)
@ -1087,11 +1116,13 @@ fn call_spec(
)
};
let output_element_type = layout_spec(builder, &output_element_layout)?;
let output_element_type =
layout_spec(builder, &output_element_layout, &WhenRecursive::Unreachable)?;
let init_state = new_list(builder, block, output_element_type)?;
let state_layout = Layout::Builtin(Builtin::List(&output_element_layout));
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
add_loop(builder, block, state_type, init_state, loop_body)
}
@ -1108,7 +1139,8 @@ fn call_spec(
};
let state_layout = Layout::Builtin(Builtin::Bool);
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = new_num(builder, block)?;
@ -1127,7 +1159,8 @@ fn call_spec(
};
let state_layout = Layout::Builtin(Builtin::Bool);
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = new_num(builder, block)?;
@ -1139,7 +1172,8 @@ fn call_spec(
// ListFindUnsafe returns { value: v, found: Bool=Int1 }
let output_layouts = vec![argument_layouts[0], Layout::Builtin(Builtin::Bool)];
let output_layout = Layout::struct_no_name_order(&output_layouts);
let output_type = layout_spec(builder, &output_layout)?;
let output_type =
layout_spec(builder, &output_layout, &WhenRecursive::Unreachable)?;
let loop_body = |builder: &mut FuncDefBuilder, block, output| {
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
@ -1201,7 +1235,7 @@ fn lowlevel_spec(
) -> Result<ValueId> {
use LowLevel::*;
let type_id = layout_spec(builder, layout)?;
let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?;
let mode = update_mode.to_bytes();
let update_mode_var = UpdateModeVar(&mode);
@ -1323,8 +1357,8 @@ fn lowlevel_spec(
}
DictEmpty => match layout {
Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => {
let key_id = layout_spec(builder, key_layout)?;
let value_id = layout_spec(builder, value_layout)?;
let key_id = layout_spec(builder, key_layout, &WhenRecursive::Unreachable)?;
let value_id = layout_spec(builder, value_layout, &WhenRecursive::Unreachable)?;
new_dict(builder, block, key_id, value_id)
}
_ => unreachable!("empty array does not have a list layout"),
@ -1367,7 +1401,7 @@ fn lowlevel_spec(
// TODO overly pessimstic
let arguments: Vec<_> = arguments.iter().map(|symbol| env.symbols[symbol]).collect();
let result_type = layout_spec(builder, layout)?;
let result_type = layout_spec(builder, layout, &WhenRecursive::Unreachable)?;
builder.add_unknown_with(block, &arguments, result_type)
}
@ -1478,7 +1512,8 @@ fn expr_spec<'a>(
let value_id = match tag_layout {
UnionLayout::NonRecursive(tags) => {
let variant_types = non_recursive_variant_types(builder, tags)?;
let variant_types =
non_recursive_variant_types(builder, tags, &WhenRecursive::Unreachable)?;
let value_id = build_tuple_value(builder, env, block, arguments)?;
return builder.add_make_union(block, &variant_types, *tag_id as u32, value_id);
}
@ -1592,7 +1627,7 @@ fn expr_spec<'a>(
builder.add_get_tuple_field(block, value_id, *index as u32)
}
Array { elem_layout, elems } => {
let type_id = layout_spec(builder, elem_layout)?;
let type_id = layout_spec(builder, elem_layout, &WhenRecursive::Unreachable)?;
let list = new_list(builder, block, type_id)?;
@ -1619,19 +1654,19 @@ fn expr_spec<'a>(
EmptyArray => match layout {
Layout::Builtin(Builtin::List(element_layout)) => {
let type_id = layout_spec(builder, element_layout)?;
let type_id = layout_spec(builder, element_layout, &WhenRecursive::Unreachable)?;
new_list(builder, block, type_id)
}
_ => unreachable!("empty array does not have a list layout"),
},
Reset { symbol, .. } => {
let type_id = layout_spec(builder, layout)?;
let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?;
let value_id = env.symbols[symbol];
builder.add_unknown_with(block, &[value_id], type_id)
}
RuntimeErrorFunction(_) => {
let type_id = layout_spec(builder, layout)?;
let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?;
builder.add_terminate(block, type_id)
}
@ -1658,18 +1693,24 @@ fn literal_spec(
}
}
fn layout_spec(builder: &mut impl TypeContext, layout: &Layout) -> Result<TypeId> {
layout_spec_help(builder, layout, &WhenRecursive::Unreachable)
fn layout_spec(
builder: &mut impl TypeContext,
layout: &Layout,
when_recursive: &WhenRecursive,
) -> Result<TypeId> {
layout_spec_help(builder, layout, when_recursive)
}
fn non_recursive_variant_types(
builder: &mut impl TypeContext,
tags: &[&[Layout]],
// If there is a recursive pointer latent within this layout, coming from a containing layout.
when_recursive: &WhenRecursive,
) -> Result<Vec<TypeId>> {
let mut result = Vec::with_capacity(tags.len());
for tag in tags.iter() {
result.push(build_tuple_type(builder, tag)?);
result.push(build_tuple_type(builder, tag, when_recursive)?);
}
Ok(result)
@ -1701,7 +1742,7 @@ fn layout_spec_help(
builder.add_tuple_type(&[])
}
UnionLayout::NonRecursive(tags) => {
let variant_types = non_recursive_variant_types(builder, tags)?;
let variant_types = non_recursive_variant_types(builder, tags, when_recursive)?;
builder.add_union_type(&variant_types)
}
UnionLayout::Recursive(_)

View File

@ -30,7 +30,7 @@ bumpalo = { version = "3.8.0", features = ["collections"] }
libloading = "0.7.1"
tempfile = "3.2.0"
inkwell = { path = "../../vendor/inkwell", optional = true }
target-lexicon = "0.12.2"
target-lexicon = "0.12.3"
[target.'cfg(target_os = "macos")'.dependencies]
serde_json = "1.0.69"

View File

@ -4,7 +4,7 @@ Builtins are the functions and modules that are implicitly imported into every m
### module/src/symbol.rs
Towards the bottom of `symbol.rs` there is a `define_builtins!` macro being used that takes many modules and function names. The first level (`List`, `Int` ..) is the module name, and the second level is the function or value name (`reverse`, `mod` ..). If you wanted to add a `Int` function called `addTwo` go to `2 Int: "Int" => {` and inside that case add to the bottom `38 INT_ADD_TWO: "addTwo"` (assuming there are 37 existing ones).
Towards the bottom of `symbol.rs` there is a `define_builtins!` macro being used that takes many modules and function names. The first level (`List`, `Int` ..) is the module name, and the second level is the function or value name (`reverse`, `rem` ..). If you wanted to add a `Int` function called `addTwo` go to `2 Int: "Int" => {` and inside that case add to the bottom `38 INT_ADD_TWO: "addTwo"` (assuming there are 37 existing ones).
Some of these have `#` inside their name (`first#list`, `#lt` ..). This is a trick we are doing to hide implementation details from Roc programmers. To a Roc programmer, a name with `#` in it is invalid, because `#` means everything after it is parsed to a comment. We are constructing these functions manually, so we are circumventing the parsing step and dont have such restrictions. We get to make functions and values with `#` which as a consequence are not accessible to Roc programmers. Roc programmers simply cannot reference them.

View File

@ -61,6 +61,7 @@ interface Num
isPositive,
isZero,
log,
logChecked,
maxFloat,
maxI8,
maxU8,
@ -88,6 +89,7 @@ interface Num
pow,
powInt,
rem,
remChecked,
round,
shiftLeftBy,
shiftRightBy,
@ -97,6 +99,7 @@ interface Num
subChecked,
subWrap,
sqrt,
sqrtChecked,
tan,
toI8,
toI8Checked,
@ -802,27 +805,18 @@ toDec : Num * -> Dec
## This is the same as the #// operator.
divTrunc : Int a, Int a -> Int a
## Perform flooring modulo on two integers.
## Obtain the remainder (truncating modulo) from the division of two integers.
##
## Modulo is the same as remainder when working with positive numbers,
## but if either number is negative, then modulo works differently.
## `a % b` is shorthand for `Num.rem a b`.
##
## Additionally, flooring modulo uses [Float].floor on the result.
## >>> 5 % 7
##
## (Use [Float].mod for non-flooring modulo.)
## >>> Num.rem 5 7
##
## Return `Err DivByZero` if the second integer is zero, because division by zero is undefined in mathematics.
## >>> -8 % -3
##
## `a %% b` is shorthand for `Int.modFloor a b`.
##
## >>> 5 %% 7
##
## >>> Int.modFloor 5 7
##
## >>> -8 %% -3
##
## >>> Int.modFloor -8 -3
#modFloor : Int a, Int a -> Result (Int a) [ DivByZero ]*
## >>> Num.rem -8 -3
rem : Int a, Int a -> Int a
## Bitwise
@ -1094,31 +1088,6 @@ atan : Float a -> Float a
## >>> |> Num.div 2.0
div : Float a, Float a -> Float a
## Perform modulo on two [Float]s.
##
## Modulo is the same as remainder when working with positive numbers,
## but if either number is negative, then modulo works differently.
##
## `a % b` is shorthand for `Num.mod a b`.
##
## [Division by zero is undefined in mathematics](https://en.wikipedia.org/wiki/Division_by_zero),
## and as such, so is modulo by zero. Because of this, you should make sure never
## to pass zero for the second argument to this function!
##
## Passing [mod] a [Dec] value of zero for its second argument will cause a panic.
## Passing [mod] a [F32] and [F64] value for its second argument will cause it
## to return [*NaN*](Num.isNaN).
##
## >>> 5.0 % 7.0
##
## >>> Num.mod 5 7
##
## `Num.mod` can be convenient in pipelines.
##
## >>> Num.pi
## >>> |> Num.mod 2.0
mod : Float a, Float a -> Float a
## Raises a [Float] to the power of another [Float].
##
## `
@ -1314,7 +1283,7 @@ isInfinite : Float * -> Bool
##
## >>> Num.isNaN 12.3
##
## >>> Num.isNaN (Num.sqrt -2)
## >>> Num.isNaN (Num.pow -1 0.5)
##
## *NaN* is unusual from other numberic values in that:
## * *NaN* is not equal to any other number, even itself. [Bool.isEq] always returns `False` if either argument is *NaN*.

View File

@ -66,10 +66,13 @@ interface Num
isPositive,
isNegative,
rem,
remChecked,
div,
divChecked,
sqrt,
sqrtChecked,
log,
logChecked,
round,
ceiling,
floor,
@ -239,19 +242,22 @@ asin : Float a -> Float a
acos : Float a -> Float a
atan : Float a -> Float a
sqrt : Float a -> Result (Float a) [ SqrtOfNegative ]*
log : Float a -> Result (Float a) [ LogNeedsPositive ]*
sqrt : Float a -> Float a
sqrtChecked : Float a -> Result (Float a) [ SqrtOfNegative ]*
log : Float a -> Float a
logChecked : Float a -> Result (Float a) [ LogNeedsPositive ]*
div : Float a, Float a -> Float a
divChecked : Float a, Float a -> Result (Float a) [ DivByZero ]*
divCeil : Int a, Int a -> Int a
divCeilChecked : Int a, Int a -> Result (Int a) [ DivByZero ]*
divTrunc : Int a, Int a -> Int a
divTruncChecked : Int a, Int a -> Result (Int a) [ DivByZero ]*
# mod : Float a, Float a -> Result (Float a) [ DivByZero ]*
rem : Int a, Int a -> Result (Int a) [ DivByZero ]*
# mod : Int a, Int a -> Result (Int a) [ DivByZero ]*
rem : Int a, Int a -> Int a
remChecked : Int a, Int a -> Result (Int a) [ DivByZero ]*
isMultipleOf : Int a, Int a -> Bool
bitwiseAnd : Int a, Int a -> Int a

View File

@ -393,16 +393,16 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(int_type(flex(TVAR2)))
);
// rem : Int a, Int a -> Result (Int a) [ DivByZero ]*
// rem : Int a, Int a -> Int a
add_top_level_function_type!(
Symbol::NUM_REM,
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(result_type(int_type(flex(TVAR1)), div_by_zero.clone())),
Box::new(int_type(flex(TVAR1))),
);
// mod : Int a, Int a -> Result (Int a) [ DivByZero ]*
// remChecked : Int a, Int a -> Result (Int a) [ DivByZero ]*
add_top_level_function_type!(
Symbol::NUM_MOD_INT,
Symbol::NUM_REM_CHECKED,
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(result_type(int_type(flex(TVAR1)), div_by_zero.clone())),
);
@ -680,36 +680,43 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
add_top_level_function_type!(
Symbol::NUM_DIV_FLOAT_CHECKED,
vec![float_type(flex(TVAR1)), float_type(flex(TVAR1))],
Box::new(result_type(float_type(flex(TVAR1)), div_by_zero.clone())),
);
// mod : Float a, Float a -> Result (Float a) [ DivByZero ]*
add_top_level_function_type!(
Symbol::NUM_MOD_FLOAT,
vec![float_type(flex(TVAR1)), float_type(flex(TVAR1))],
Box::new(result_type(float_type(flex(TVAR1)), div_by_zero)),
);
// sqrt : Float a -> Float a
add_top_level_function_type!(
Symbol::NUM_SQRT,
vec![float_type(flex(TVAR1))],
Box::new(float_type(flex(TVAR1))),
);
// sqrtChecked : Float a -> Result (Float a) [ SqrtOfNegative ]*
let sqrt_of_negative = SolvedType::TagUnion(
vec![(TagName::Global("SqrtOfNegative".into()), vec![])],
Box::new(SolvedType::Wildcard),
);
add_top_level_function_type!(
Symbol::NUM_SQRT,
Symbol::NUM_SQRT_CHECKED,
vec![float_type(flex(TVAR1))],
Box::new(result_type(float_type(flex(TVAR1)), sqrt_of_negative)),
);
// log : Float a -> Float a
add_top_level_function_type!(
Symbol::NUM_LOG,
vec![float_type(flex(TVAR1))],
Box::new(float_type(flex(TVAR1))),
);
// logChecked : Float a -> Result (Float a) [ LogNeedsPositive ]*
let log_needs_positive = SolvedType::TagUnion(
vec![(TagName::Global("LogNeedsPositive".into()), vec![])],
Box::new(SolvedType::Wildcard),
);
add_top_level_function_type!(
Symbol::NUM_LOG,
Symbol::NUM_LOG_CHECKED,
vec![float_type(flex(TVAR1))],
Box::new(result_type(float_type(flex(TVAR1)), log_needs_positive)),
);

View File

@ -204,9 +204,12 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
NUM_ABS => num_abs,
NUM_NEG => num_neg,
NUM_REM => num_rem,
NUM_REM_CHECKED => num_rem_checked,
NUM_IS_MULTIPLE_OF => num_is_multiple_of,
NUM_SQRT => num_sqrt,
NUM_SQRT_CHECKED => num_sqrt_checked,
NUM_LOG => num_log,
NUM_LOG_CHECKED => num_log_checked,
NUM_ROUND => num_round,
NUM_IS_ODD => num_is_odd,
NUM_IS_EVEN => num_is_even,
@ -713,6 +716,23 @@ fn bool_and(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
fn num_unaryop(symbol: Symbol, var_store: &mut VarStore, op: LowLevel) -> Def {
let num_var = var_store.fresh();
let body = RunLowLevel {
op,
args: vec![(num_var, Var(Symbol::ARG_1))],
ret_var: num_var,
};
defn(
symbol,
vec![(num_var, Symbol::ARG_1)],
var_store,
body,
num_var,
)
}
/// Num a, Num a -> Num a
fn num_binop(symbol: Symbol, var_store: &mut VarStore, op: LowLevel) -> Def {
let num_var = var_store.fresh();
@ -1152,8 +1172,13 @@ fn num_to_float(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// Num.sqrt : Float -> Result Float [ SqrtOfNegative ]*
/// Num.sqrt : Float a -> Float a
fn num_sqrt(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_unaryop(symbol, var_store, LowLevel::NumSqrtUnchecked)
}
/// Num.sqrtChecked : Float a -> Result (Float a) [ SqrtOfNegative ]*
fn num_sqrt_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
let bool_var = var_store.fresh();
let float_var = var_store.fresh();
let unbound_zero_var = var_store.fresh();
@ -1201,8 +1226,13 @@ fn num_sqrt(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// Num.log : Float -> Result Float [ LogNeedsPositive ]*
/// Num.log : Float a -> Float a
fn num_log(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_unaryop(symbol, var_store, LowLevel::NumLogUnchecked)
}
/// Num.logChecked : Float a -> Result (Float a) [ LogNeedsPositive ]*
fn num_log_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
let bool_var = var_store.fresh();
let float_var = var_store.fresh();
let unbound_zero_var = var_store.fresh();
@ -4084,8 +4114,13 @@ fn set_walk(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// Num.rem : Int a, Int a -> Result (Int a) [ DivByZero ]*
/// Num.rem : Int a, Int a -> Int a
fn num_rem(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumRemUnchecked)
}
/// Num.remChecked : Int a, Int a -> Result (Int a) [ DivByZero ]*
fn num_rem_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
let num_var = var_store.fresh();
let unbound_zero_var = var_store.fresh();
let bool_var = var_store.fresh();

File diff suppressed because it is too large Load Diff

View File

@ -182,8 +182,4 @@ impl<'a> Env<'a> {
pub fn problem(&mut self, problem: Problem) {
self.problems.push(problem)
}
pub fn register_closure(&mut self, symbol: Symbol, references: References) {
self.closures.insert(symbol, references);
}
}

View File

@ -716,7 +716,9 @@ pub fn canonicalize_expr<'a>(
}
}
env.register_closure(symbol, output.references.clone());
// store the references of this function in the Env. This information is used
// when we canonicalize a surrounding def (if it exists)
env.closures.insert(symbol, output.references.clone());
let mut captured_symbols: Vec<_> = captured_symbols
.into_iter()

View File

@ -425,7 +425,6 @@ fn binop_to_function(binop: BinOp) -> (&'static str, &'static str) {
Slash => (ModuleName::NUM, "div"),
DoubleSlash => (ModuleName::NUM, "divTrunc"),
Percent => (ModuleName::NUM, "rem"),
DoublePercent => (ModuleName::NUM, "mod"),
Plus => (ModuleName::NUM, "add"),
Minus => (ModuleName::NUM, "sub"),
Equals => (ModuleName::BOOL, "isEq"),

View File

@ -39,6 +39,31 @@ impl ReferenceMatrix {
pub fn get(&self, index: usize) -> bool {
self.bitvec[index]
}
pub fn is_recursive(&self, index: usize) -> bool {
let mut scheduled = self.row_slice(index).to_bitvec();
let mut visited = self.row_slice(index).to_bitvec();
// yes this is a bit inefficient because rows are visited repeatedly.
while scheduled.any() {
for one in scheduled.iter_ones() {
if one == index {
return true;
}
visited |= self.row_slice(one)
}
// i.e. visited did not change
if visited.count_ones() == scheduled.count_ones() {
break;
}
scheduled |= &visited;
}
false
}
}
// Topological sort and strongly-connected components

View File

@ -372,7 +372,6 @@ fn push_op(buf: &mut Buf, op: BinOp) {
called_via::BinOp::Slash => buf.push('/'),
called_via::BinOp::DoubleSlash => buf.push_str("//"),
called_via::BinOp::Percent => buf.push('%'),
called_via::BinOp::DoublePercent => buf.push_str("%%"),
called_via::BinOp::Plus => buf.push('+'),
called_via::BinOp::Minus => buf.push('-'),
called_via::BinOp::Equals => buf.push_str("=="),
@ -1104,7 +1103,6 @@ fn sub_expr_requests_parens(expr: &Expr<'_>) -> bool {
| BinOp::Slash
| BinOp::DoubleSlash
| BinOp::Percent
| BinOp::DoublePercent
| BinOp::Plus
| BinOp::Minus
| BinOp::Equals

View File

@ -2537,7 +2537,7 @@ mod test_fmt {
indoc!(
r#"
2 % 3
%% 5
// 5
+ 7
"#
),
@ -2545,7 +2545,7 @@ mod test_fmt {
r#"
2
% 3
%% 5
// 5
+ 7
"#
),

View File

@ -19,7 +19,7 @@ roc_mono = { path = "../mono" }
roc_target = { path = "../roc_target" }
roc_error_macros = { path = "../../error_macros" }
bumpalo = { version = "3.8.0", features = ["collections"] }
target-lexicon = "0.12.2"
target-lexicon = "0.12.3"
# TODO: Deal with the update of object to 0.27.
# It looks like it breaks linking the generated objects.
# Probably just need to specify an extra field that used to be implicit or something.

View File

@ -18,4 +18,4 @@ roc_std = { path = "../../roc_std", default-features = false }
morphic_lib = { path = "../../vendor/morphic_lib" }
bumpalo = { version = "3.8.0", features = ["collections"] }
inkwell = { path = "../../vendor/inkwell" }
target-lexicon = "0.12.2"
target-lexicon = "0.12.3"

View File

@ -6915,7 +6915,6 @@ fn build_float_binop<'a, 'ctx, 'env>(
NumGte => bd.build_float_compare(OGE, lhs, rhs, "float_gte").into(),
NumLt => bd.build_float_compare(OLT, lhs, rhs, "float_lt").into(),
NumLte => bd.build_float_compare(OLE, lhs, rhs, "float_lte").into(),
NumRemUnchecked => bd.build_float_rem(lhs, rhs, "rem_float").into(),
NumDivUnchecked => bd.build_float_div(lhs, rhs, "div_float").into(),
NumPow => env.call_intrinsic(&LLVM_POW[float_width], &[lhs.into(), rhs.into()]),
_ => {

View File

@ -1809,7 +1809,39 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
for (i, field_layout) in field_layouts.iter().enumerate() {
if let Layout::RecursivePointer = field_layout {
panic!("non-recursive tag unions cannot contain naked recursion pointers!");
let recursive_union_layout = match when_recursive {
WhenRecursive::Unreachable => {
panic!("non-recursive tag unions cannot contain naked recursion pointers!");
}
WhenRecursive::Loop(recursive_union_layout) => recursive_union_layout,
};
// This field is a pointer to the recursive pointer.
let field_ptr = env
.builder
.build_struct_gep(cast_tag_data_pointer, i as u32, "modify_tag_field")
.unwrap();
// This is the actual pointer to the recursive data.
let field_value = env.builder.build_load(field_ptr, "load_recursive_pointer");
debug_assert!(field_value.is_pointer_value());
// therefore we must cast it to our desired type
let union_type =
basic_type_from_layout(env, &Layout::Union(*recursive_union_layout));
let recursive_ptr_field_value =
cast_basic_basic(env.builder, field_value, union_type);
modify_refcount_layout_help(
env,
parent,
layout_ids,
mode.to_call_mode(fn_val),
when_recursive,
recursive_ptr_field_value,
&Layout::RecursivePointer,
)
} else if field_layout.contains_refcounted() {
let field_ptr = env
.builder

View File

@ -34,7 +34,6 @@ pub enum BinOp {
Slash,
DoubleSlash,
Percent,
DoublePercent,
Plus,
Minus,
Equals,
@ -58,8 +57,8 @@ impl BinOp {
pub fn width(self) -> u16 {
match self {
Caret | Star | Slash | Percent | Plus | Minus | LessThan | GreaterThan => 1,
DoubleSlash | DoublePercent | Equals | NotEquals | LessThanOrEq | GreaterThanOrEq
| And | Or | Pizza => 2,
DoubleSlash | Equals | NotEquals | LessThanOrEq | GreaterThanOrEq | And | Or
| Pizza => 2,
Assignment | IsAliasType | IsOpaqueType | Backpassing => unreachable!(),
}
}
@ -97,9 +96,7 @@ impl BinOp {
use self::Associativity::*;
match self {
Pizza | Star | Slash | DoubleSlash | DoublePercent | Percent | Plus | Minus => {
LeftAssociative
}
Pizza | Star | Slash | DoubleSlash | Percent | Plus | Minus => LeftAssociative,
And | Or | Caret => RightAssociative,
Equals | NotEquals | LessThan | GreaterThan | LessThanOrEq | GreaterThanOrEq => {
NonAssociative
@ -111,7 +108,7 @@ impl BinOp {
fn precedence(self) -> u8 {
match self {
Caret => 7,
Star | Slash | DoubleSlash | DoublePercent | Percent => 6,
Star | Slash | DoubleSlash | Percent => 6,
Plus | Minus => 5,
Equals | NotEquals | LessThan | GreaterThan | LessThanOrEq | GreaterThanOrEq => 4,
And => 3,
@ -142,7 +139,6 @@ impl std::fmt::Display for BinOp {
Slash => "/",
DoubleSlash => "//",
Percent => "%",
DoublePercent => "%%",
Plus => "+",
Minus => "-",
Equals => "==",

View File

@ -294,14 +294,17 @@ impl LowLevelWrapperType {
Symbol::NUM_DIV_FLOAT_CHECKED => WrapperIsRequired,
Symbol::NUM_DIV_CEIL => CanBeReplacedBy(NumDivCeilUnchecked),
Symbol::NUM_DIV_CEIL_CHECKED => WrapperIsRequired,
Symbol::NUM_REM => WrapperIsRequired,
Symbol::NUM_REM => CanBeReplacedBy(NumRemUnchecked),
Symbol::NUM_REM_CHECKED => WrapperIsRequired,
Symbol::NUM_IS_MULTIPLE_OF => CanBeReplacedBy(NumIsMultipleOf),
Symbol::NUM_ABS => CanBeReplacedBy(NumAbs),
Symbol::NUM_NEG => CanBeReplacedBy(NumNeg),
Symbol::NUM_SIN => CanBeReplacedBy(NumSin),
Symbol::NUM_COS => CanBeReplacedBy(NumCos),
Symbol::NUM_SQRT => WrapperIsRequired,
Symbol::NUM_LOG => WrapperIsRequired,
Symbol::NUM_SQRT => CanBeReplacedBy(NumSqrtUnchecked),
Symbol::NUM_SQRT_CHECKED => WrapperIsRequired,
Symbol::NUM_LOG => CanBeReplacedBy(NumLogUnchecked),
Symbol::NUM_LOG_CHECKED => WrapperIsRequired,
Symbol::NUM_ROUND => CanBeReplacedBy(NumRound),
Symbol::NUM_TO_FLOAT => CanBeReplacedBy(NumToFloat),
Symbol::NUM_POW => CanBeReplacedBy(NumPow),

View File

@ -950,121 +950,117 @@ define_builtins! {
41 NUM_DIV_FLOAT_CHECKED: "divChecked"
42 NUM_DIV_TRUNC: "divTrunc"
43 NUM_DIV_TRUNC_CHECKED: "divTruncChecked"
44 NUM_MOD_INT: "modInt"
45 NUM_MOD_INT_CHECKED: "modIntChecked"
46 NUM_MOD_FLOAT: "modFloat"
47 NUM_MOD_FLOAT_CHECKED: "modFloatChecked"
48 NUM_SQRT: "sqrt"
49 NUM_SQRT_CHECKED: "sqrtChecked"
50 NUM_LOG: "log"
51 NUM_LOG_CHECKED: "logChecked"
52 NUM_ROUND: "round"
53 NUM_COMPARE: "compare"
54 NUM_POW: "pow"
55 NUM_CEILING: "ceiling"
56 NUM_POW_INT: "powInt"
57 NUM_FLOOR: "floor"
58 NUM_ADD_WRAP: "addWrap"
59 NUM_ADD_CHECKED: "addChecked"
60 NUM_ADD_SATURATED: "addSaturated"
61 NUM_ATAN: "atan"
62 NUM_ACOS: "acos"
63 NUM_ASIN: "asin"
64 NUM_AT_SIGNED128: "@Signed128"
65 NUM_SIGNED128: "Signed128"
66 NUM_AT_SIGNED64: "@Signed64"
67 NUM_SIGNED64: "Signed64"
68 NUM_AT_SIGNED32: "@Signed32"
69 NUM_SIGNED32: "Signed32"
70 NUM_AT_SIGNED16: "@Signed16"
71 NUM_SIGNED16: "Signed16"
72 NUM_AT_SIGNED8: "@Signed8"
73 NUM_SIGNED8: "Signed8"
74 NUM_AT_UNSIGNED128: "@Unsigned128"
75 NUM_UNSIGNED128: "Unsigned128"
76 NUM_AT_UNSIGNED64: "@Unsigned64"
77 NUM_UNSIGNED64: "Unsigned64"
78 NUM_AT_UNSIGNED32: "@Unsigned32"
79 NUM_UNSIGNED32: "Unsigned32"
80 NUM_AT_UNSIGNED16: "@Unsigned16"
81 NUM_UNSIGNED16: "Unsigned16"
82 NUM_AT_UNSIGNED8: "@Unsigned8"
83 NUM_UNSIGNED8: "Unsigned8"
84 NUM_AT_BINARY64: "@Binary64"
85 NUM_BINARY64: "Binary64"
86 NUM_AT_BINARY32: "@Binary32"
87 NUM_BINARY32: "Binary32"
88 NUM_BITWISE_AND: "bitwiseAnd"
89 NUM_BITWISE_XOR: "bitwiseXor"
90 NUM_BITWISE_OR: "bitwiseOr"
91 NUM_SHIFT_LEFT: "shiftLeftBy"
92 NUM_SHIFT_RIGHT: "shiftRightBy"
93 NUM_SHIFT_RIGHT_ZERO_FILL: "shiftRightZfBy"
94 NUM_SUB_WRAP: "subWrap"
95 NUM_SUB_CHECKED: "subChecked"
96 NUM_SUB_SATURATED: "subSaturated"
97 NUM_MUL_WRAP: "mulWrap"
98 NUM_MUL_CHECKED: "mulChecked"
99 NUM_INT: "Int"
100 NUM_FLOAT: "Float"
101 NUM_AT_NATURAL: "@Natural"
102 NUM_NATURAL: "Natural"
103 NUM_NAT: "Nat"
104 NUM_INT_CAST: "intCast"
105 NUM_IS_MULTIPLE_OF: "isMultipleOf"
106 NUM_AT_DECIMAL: "@Decimal"
107 NUM_DECIMAL: "Decimal"
108 NUM_DEC: "Dec" // the Num.Dectype alias
109 NUM_BYTES_TO_U16: "bytesToU16"
110 NUM_BYTES_TO_U32: "bytesToU32"
111 NUM_CAST_TO_NAT: "#castToNat"
112 NUM_DIV_CEIL: "divCeil"
113 NUM_DIV_CEIL_CHECKED: "divCeilChecked"
114 NUM_TO_STR: "toStr"
115 NUM_MIN_I8: "minI8"
116 NUM_MAX_I8: "maxI8"
117 NUM_MIN_U8: "minU8"
118 NUM_MAX_U8: "maxU8"
119 NUM_MIN_I16: "minI16"
120 NUM_MAX_I16: "maxI16"
121 NUM_MIN_U16: "minU16"
122 NUM_MAX_U16: "maxU16"
123 NUM_MIN_I32: "minI32"
124 NUM_MAX_I32: "maxI32"
125 NUM_MIN_U32: "minU32"
126 NUM_MAX_U32: "maxU32"
127 NUM_MIN_I64: "minI64"
128 NUM_MAX_I64: "maxI64"
129 NUM_MIN_U64: "minU64"
130 NUM_MAX_U64: "maxU64"
131 NUM_MIN_I128: "minI128"
132 NUM_MAX_I128: "maxI128"
133 NUM_TO_I8: "toI8"
134 NUM_TO_I8_CHECKED: "toI8Checked"
135 NUM_TO_I16: "toI16"
136 NUM_TO_I16_CHECKED: "toI16Checked"
137 NUM_TO_I32: "toI32"
138 NUM_TO_I32_CHECKED: "toI32Checked"
139 NUM_TO_I64: "toI64"
140 NUM_TO_I64_CHECKED: "toI64Checked"
141 NUM_TO_I128: "toI128"
142 NUM_TO_I128_CHECKED: "toI128Checked"
143 NUM_TO_U8: "toU8"
144 NUM_TO_U8_CHECKED: "toU8Checked"
145 NUM_TO_U16: "toU16"
146 NUM_TO_U16_CHECKED: "toU16Checked"
147 NUM_TO_U32: "toU32"
148 NUM_TO_U32_CHECKED: "toU32Checked"
149 NUM_TO_U64: "toU64"
150 NUM_TO_U64_CHECKED: "toU64Checked"
151 NUM_TO_U128: "toU128"
152 NUM_TO_U128_CHECKED: "toU128Checked"
153 NUM_TO_NAT: "toNat"
154 NUM_TO_NAT_CHECKED: "toNatChecked"
155 NUM_TO_F32: "toF32"
156 NUM_TO_F32_CHECKED: "toF32Checked"
157 NUM_TO_F64: "toF64"
158 NUM_TO_F64_CHECKED: "toF64Checked"
44 NUM_SQRT: "sqrt"
45 NUM_SQRT_CHECKED: "sqrtChecked"
46 NUM_LOG: "log"
47 NUM_LOG_CHECKED: "logChecked"
48 NUM_ROUND: "round"
49 NUM_COMPARE: "compare"
50 NUM_POW: "pow"
51 NUM_CEILING: "ceiling"
52 NUM_POW_INT: "powInt"
53 NUM_FLOOR: "floor"
54 NUM_ADD_WRAP: "addWrap"
55 NUM_ADD_CHECKED: "addChecked"
56 NUM_ADD_SATURATED: "addSaturated"
57 NUM_ATAN: "atan"
58 NUM_ACOS: "acos"
59 NUM_ASIN: "asin"
60 NUM_AT_SIGNED128: "@Signed128"
61 NUM_SIGNED128: "Signed128"
62 NUM_AT_SIGNED64: "@Signed64"
63 NUM_SIGNED64: "Signed64"
64 NUM_AT_SIGNED32: "@Signed32"
65 NUM_SIGNED32: "Signed32"
66 NUM_AT_SIGNED16: "@Signed16"
67 NUM_SIGNED16: "Signed16"
68 NUM_AT_SIGNED8: "@Signed8"
69 NUM_SIGNED8: "Signed8"
70 NUM_AT_UNSIGNED128: "@Unsigned128"
71 NUM_UNSIGNED128: "Unsigned128"
72 NUM_AT_UNSIGNED64: "@Unsigned64"
73 NUM_UNSIGNED64: "Unsigned64"
74 NUM_AT_UNSIGNED32: "@Unsigned32"
75 NUM_UNSIGNED32: "Unsigned32"
76 NUM_AT_UNSIGNED16: "@Unsigned16"
77 NUM_UNSIGNED16: "Unsigned16"
78 NUM_AT_UNSIGNED8: "@Unsigned8"
79 NUM_UNSIGNED8: "Unsigned8"
80 NUM_AT_BINARY64: "@Binary64"
81 NUM_BINARY64: "Binary64"
82 NUM_AT_BINARY32: "@Binary32"
83 NUM_BINARY32: "Binary32"
84 NUM_BITWISE_AND: "bitwiseAnd"
85 NUM_BITWISE_XOR: "bitwiseXor"
86 NUM_BITWISE_OR: "bitwiseOr"
87 NUM_SHIFT_LEFT: "shiftLeftBy"
88 NUM_SHIFT_RIGHT: "shiftRightBy"
89 NUM_SHIFT_RIGHT_ZERO_FILL: "shiftRightZfBy"
90 NUM_SUB_WRAP: "subWrap"
91 NUM_SUB_CHECKED: "subChecked"
92 NUM_SUB_SATURATED: "subSaturated"
93 NUM_MUL_WRAP: "mulWrap"
94 NUM_MUL_CHECKED: "mulChecked"
95 NUM_INT: "Int"
96 NUM_FLOAT: "Float"
97 NUM_AT_NATURAL: "@Natural"
98 NUM_NATURAL: "Natural"
99 NUM_NAT: "Nat"
100 NUM_INT_CAST: "intCast"
101 NUM_IS_MULTIPLE_OF: "isMultipleOf"
102 NUM_AT_DECIMAL: "@Decimal"
103 NUM_DECIMAL: "Decimal"
104 NUM_DEC: "Dec" // the Num.Dectype alias
105 NUM_BYTES_TO_U16: "bytesToU16"
106 NUM_BYTES_TO_U32: "bytesToU32"
107 NUM_CAST_TO_NAT: "#castToNat"
108 NUM_DIV_CEIL: "divCeil"
109 NUM_DIV_CEIL_CHECKED: "divCeilChecked"
110 NUM_TO_STR: "toStr"
111 NUM_MIN_I8: "minI8"
112 NUM_MAX_I8: "maxI8"
113 NUM_MIN_U8: "minU8"
114 NUM_MAX_U8: "maxU8"
115 NUM_MIN_I16: "minI16"
116 NUM_MAX_I16: "maxI16"
117 NUM_MIN_U16: "minU16"
118 NUM_MAX_U16: "maxU16"
119 NUM_MIN_I32: "minI32"
120 NUM_MAX_I32: "maxI32"
121 NUM_MIN_U32: "minU32"
122 NUM_MAX_U32: "maxU32"
123 NUM_MIN_I64: "minI64"
124 NUM_MAX_I64: "maxI64"
125 NUM_MIN_U64: "minU64"
126 NUM_MAX_U64: "maxU64"
127 NUM_MIN_I128: "minI128"
128 NUM_MAX_I128: "maxI128"
129 NUM_TO_I8: "toI8"
130 NUM_TO_I8_CHECKED: "toI8Checked"
131 NUM_TO_I16: "toI16"
132 NUM_TO_I16_CHECKED: "toI16Checked"
133 NUM_TO_I32: "toI32"
134 NUM_TO_I32_CHECKED: "toI32Checked"
135 NUM_TO_I64: "toI64"
136 NUM_TO_I64_CHECKED: "toI64Checked"
137 NUM_TO_I128: "toI128"
138 NUM_TO_I128_CHECKED: "toI128Checked"
139 NUM_TO_U8: "toU8"
140 NUM_TO_U8_CHECKED: "toU8Checked"
141 NUM_TO_U16: "toU16"
142 NUM_TO_U16_CHECKED: "toU16Checked"
143 NUM_TO_U32: "toU32"
144 NUM_TO_U32_CHECKED: "toU32Checked"
145 NUM_TO_U64: "toU64"
146 NUM_TO_U64_CHECKED: "toU64Checked"
147 NUM_TO_U128: "toU128"
148 NUM_TO_U128_CHECKED: "toU128Checked"
149 NUM_TO_NAT: "toNat"
150 NUM_TO_NAT_CHECKED: "toNatChecked"
151 NUM_TO_F32: "toF32"
152 NUM_TO_F32_CHECKED: "toF32Checked"
153 NUM_TO_F64: "toF64"
154 NUM_TO_F64_CHECKED: "toF64Checked"
}
2 BOOL: "Bool" => {
0 BOOL_BOOL: "Bool" // the Bool.Bool type alias

View File

@ -3499,15 +3499,24 @@ pub fn with_hole<'a>(
OpaqueRef { argument, .. } => {
let (arg_var, loc_arg_expr) = *argument;
with_hole(
env,
loc_arg_expr.value,
arg_var,
procs,
layout_cache,
assigned,
hole,
)
match can_reuse_symbol(env, procs, &loc_arg_expr.value) {
// Opaques decay to their argument.
ReuseSymbol::Value(real_name) => {
let mut result = hole.clone();
substitute_in_exprs(arena, &mut result, assigned, real_name);
result
}
_ => with_hole(
env,
loc_arg_expr.value,
arg_var,
procs,
layout_cache,
assigned,
hole,
),
}
}
Record {

View File

@ -311,6 +311,50 @@ impl<'a> UnionLayout<'a> {
.append(alloc.intersperse(tags_doc, ", "))
.append(alloc.text("]"))
}
Recursive(tags) => {
let tags_doc = tags.iter().map(|fields| {
alloc.text("C ").append(alloc.intersperse(
fields.iter().map(|x| x.to_doc(alloc, Parens::InTypeParam)),
" ",
))
});
alloc
.text("[<r>")
.append(alloc.intersperse(tags_doc, ", "))
.append(alloc.text("]"))
}
NonNullableUnwrapped(fields) => {
let fields_doc = alloc.text("C ").append(alloc.intersperse(
fields.iter().map(|x| x.to_doc(alloc, Parens::InTypeParam)),
" ",
));
alloc
.text("[<rnnu>")
.append(fields_doc)
.append(alloc.text("]"))
}
NullableUnwrapped {
nullable_id,
other_fields,
} => {
let fields_doc = alloc.text("C ").append(
alloc.intersperse(
other_fields
.iter()
.map(|x| x.to_doc(alloc, Parens::InTypeParam)),
" ",
),
);
let tags_doc = if nullable_id {
alloc.concat(vec![alloc.text("<null>, "), fields_doc])
} else {
alloc.concat(vec![fields_doc, alloc.text(", <null>")])
};
alloc
.text("[<rnu>")
.append(tags_doc)
.append(alloc.text("]"))
}
_ => alloc.text("TODO"),
}
}
@ -1731,7 +1775,7 @@ fn layout_from_flat_type<'a>(
debug_assert!(ext_var_is_empty_tag_union(subs, ext_var));
Ok(layout_from_tag_union(arena, &tags, subs, env.target_info))
Ok(layout_from_tag_union(env, &tags))
}
FunctionOrTagUnion(tag_name, _, ext_var) => {
debug_assert!(
@ -1742,7 +1786,7 @@ fn layout_from_flat_type<'a>(
let union_tags = UnionTags::from_tag_name_index(tag_name);
let (tags, _) = union_tags.unsorted_tags_and_ext(subs, ext_var);
Ok(layout_from_tag_union(arena, &tags, subs, env.target_info))
Ok(layout_from_tag_union(env, &tags))
}
RecursiveTagUnion(rec_var, tags, ext_var) => {
let (tags, ext_var) = tags.unsorted_tags_and_ext(subs, ext_var);
@ -2071,23 +2115,14 @@ fn is_recursive_tag_union(layout: &Layout) -> bool {
}
fn union_sorted_tags_help_new<'a>(
arena: &'a Bump,
env: &mut Env<'a, '_>,
tags_list: &[(&'_ TagName, &[Variable])],
opt_rec_var: Option<Variable>,
subs: &Subs,
target_info: TargetInfo,
) -> UnionVariant<'a> {
// sort up front; make sure the ordering stays intact!
let mut tags_list = Vec::from_iter_in(tags_list.iter(), arena);
let mut tags_list = Vec::from_iter_in(tags_list.iter(), env.arena);
tags_list.sort_unstable_by(|(a, _), (b, _)| a.cmp(b));
let mut env = Env {
arena,
subs,
seen: Vec::new_in(arena),
target_info,
};
match tags_list.len() {
0 => {
// trying to instantiate a type with no values
@ -2098,18 +2133,19 @@ fn union_sorted_tags_help_new<'a>(
let tag_name = tag_name.clone();
// just one tag in the union (but with arguments) can be a struct
let mut layouts = Vec::with_capacity_in(tags_list.len(), arena);
let mut layouts = Vec::with_capacity_in(tags_list.len(), env.arena);
// special-case NUM_AT_NUM: if its argument is a FlexVar, make it Int
match tag_name {
TagName::Private(Symbol::NUM_AT_NUM) => {
let var = arguments[0];
layouts
.push(unwrap_num_tag(subs, var, target_info).expect("invalid num layout"));
layouts.push(
unwrap_num_tag(env.subs, var, env.target_info).expect("invalid num layout"),
);
}
_ => {
for &var in arguments {
match Layout::from_var(&mut env, var) {
match Layout::from_var(env, var) {
Ok(layout) => {
layouts.push(layout);
}
@ -2129,8 +2165,8 @@ fn union_sorted_tags_help_new<'a>(
}
layouts.sort_by(|layout1, layout2| {
let size1 = layout1.alignment_bytes(target_info);
let size2 = layout2.alignment_bytes(target_info);
let size1 = layout1.alignment_bytes(env.target_info);
let size2 = layout2.alignment_bytes(env.target_info);
size2.cmp(&size1)
});
@ -2151,7 +2187,7 @@ fn union_sorted_tags_help_new<'a>(
}
num_tags => {
// default path
let mut answer = Vec::with_capacity_in(tags_list.len(), arena);
let mut answer = Vec::with_capacity_in(tags_list.len(), env.arena);
let mut has_any_arguments = false;
let mut nullable: Option<(TagIdIntType, TagName)> = None;
@ -2174,17 +2210,19 @@ fn union_sorted_tags_help_new<'a>(
continue;
}
let mut arg_layouts = Vec::with_capacity_in(arguments.len() + 1, arena);
let mut arg_layouts = Vec::with_capacity_in(arguments.len() + 1, env.arena);
for &var in arguments {
match Layout::from_var(&mut env, var) {
match Layout::from_var(env, var) {
Ok(layout) => {
has_any_arguments = true;
// make sure to not unroll recursive types!
let self_recursion = opt_rec_var.is_some()
&& subs.get_root_key_without_compacting(var)
== subs.get_root_key_without_compacting(opt_rec_var.unwrap())
&& env.subs.get_root_key_without_compacting(var)
== env
.subs
.get_root_key_without_compacting(opt_rec_var.unwrap())
&& is_recursive_tag_union(&layout);
if self_recursion {
@ -2207,8 +2245,8 @@ fn union_sorted_tags_help_new<'a>(
}
arg_layouts.sort_by(|layout1, layout2| {
let size1 = layout1.alignment_bytes(target_info);
let size2 = layout2.alignment_bytes(target_info);
let size1 = layout1.alignment_bytes(env.target_info);
let size2 = layout2.alignment_bytes(env.target_info);
size2.cmp(&size1)
});
@ -2229,7 +2267,7 @@ fn union_sorted_tags_help_new<'a>(
3..=MAX_ENUM_SIZE if !has_any_arguments => {
// type can be stored in a byte
// needs the sorted tag names to determine the tag_id
let mut tag_names = Vec::with_capacity_in(answer.len(), arena);
let mut tag_names = Vec::with_capacity_in(answer.len(), env.arena);
for (tag_name, _) in answer {
tag_names.push(tag_name);
@ -2488,27 +2526,15 @@ pub fn union_sorted_tags_help<'a>(
}
}
fn layout_from_newtype<'a>(
arena: &'a Bump,
tags: &UnsortedUnionTags,
subs: &Subs,
target_info: TargetInfo,
) -> Layout<'a> {
debug_assert!(tags.is_newtype_wrapper(subs));
fn layout_from_newtype<'a>(env: &mut Env<'a, '_>, tags: &UnsortedUnionTags) -> Layout<'a> {
debug_assert!(tags.is_newtype_wrapper(env.subs));
let (tag_name, var) = tags.get_newtype(subs);
let (tag_name, var) = tags.get_newtype(env.subs);
if tag_name == &TagName::Private(Symbol::NUM_AT_NUM) {
unwrap_num_tag(subs, var, target_info).expect("invalid Num argument")
unwrap_num_tag(env.subs, var, env.target_info).expect("invalid Num argument")
} else {
let mut env = Env {
arena,
subs,
seen: Vec::new_in(arena),
target_info,
};
match Layout::from_var(&mut env, var) {
match Layout::from_var(env, var) {
Ok(layout) => layout,
Err(LayoutProblem::UnresolvedTypeVar(_)) => {
// If we encounter an unbound type var (e.g. `Ok *`)
@ -2525,16 +2551,11 @@ fn layout_from_newtype<'a>(
}
}
fn layout_from_tag_union<'a>(
arena: &'a Bump,
tags: &UnsortedUnionTags,
subs: &Subs,
target_info: TargetInfo,
) -> Layout<'a> {
fn layout_from_tag_union<'a>(env: &mut Env<'a, '_>, tags: &UnsortedUnionTags) -> Layout<'a> {
use UnionVariant::*;
if tags.is_newtype_wrapper(subs) {
return layout_from_newtype(arena, tags, subs, target_info);
if tags.is_newtype_wrapper(env.subs) {
return layout_from_newtype(env, tags);
}
let tags_vec = &tags.tags;
@ -2545,12 +2566,11 @@ fn layout_from_tag_union<'a>(
let &var = arguments.iter().next().unwrap();
unwrap_num_tag(subs, var, target_info).expect("invalid Num argument")
unwrap_num_tag(env.subs, var, env.target_info).expect("invalid Num argument")
}
_ => {
let opt_rec_var = None;
let variant =
union_sorted_tags_help_new(arena, tags_vec, opt_rec_var, subs, target_info);
let variant = union_sorted_tags_help_new(env, tags_vec, opt_rec_var);
match variant {
Never => Layout::VOID,
@ -2576,7 +2596,7 @@ fn layout_from_tag_union<'a>(
NonRecursive {
sorted_tag_layouts: tags,
} => {
let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena);
let mut tag_layouts = Vec::with_capacity_in(tags.len(), env.arena);
tag_layouts.extend(tags.iter().map(|r| r.1));
Layout::Union(UnionLayout::NonRecursive(tag_layouts.into_bump_slice()))
@ -2585,7 +2605,7 @@ fn layout_from_tag_union<'a>(
Recursive {
sorted_tag_layouts: tags,
} => {
let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena);
let mut tag_layouts = Vec::with_capacity_in(tags.len(), env.arena);
tag_layouts.extend(tags.iter().map(|r| r.1));
debug_assert!(tag_layouts.len() > 1);
@ -2597,7 +2617,7 @@ fn layout_from_tag_union<'a>(
nullable_name: _,
sorted_tag_layouts: tags,
} => {
let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena);
let mut tag_layouts = Vec::with_capacity_in(tags.len(), env.arena);
tag_layouts.extend(tags.iter().map(|r| r.1));
Layout::Union(UnionLayout::NullableWrapped {

View File

@ -30,7 +30,6 @@
">="
">"
"^"
"%%"
"%"
"->"

View File

@ -2762,7 +2762,6 @@ where
"&&" => good!(BinOp::And, 2),
"||" => good!(BinOp::Or, 2),
"//" => good!(BinOp::DoubleSlash, 2),
"%%" => good!(BinOp::DoublePercent, 2),
"->" => {
// makes no progress, so it does not interfere with `_ if isGood -> ...`
Err((NoProgress, to_error("->", state.pos()), state))

View File

@ -6,4 +6,4 @@ license = "UPL-1.0"
edition = "2018"
[dependencies]
target-lexicon = "0.12.2"
target-lexicon = "0.12.3"

View File

@ -37,7 +37,7 @@ bumpalo = { version = "3.8.0", features = ["collections"] }
either = "1.6.1"
libc = "0.2.106"
inkwell = { path = "../../vendor/inkwell" }
target-lexicon = "0.12.2"
target-lexicon = "0.12.3"
libloading = "0.7.1"
wasmer-wasi = "2.0.0"
tempfile = "3.2.0"

View File

@ -2534,7 +2534,7 @@ fn list_keep_oks() {
RocList<i64>
);
assert_evals_to!(
"List.keepOks [1,2] (\\x -> x % 2)",
"List.keepOks [1,2] (\\x -> Num.remChecked x 2)",
RocList::from_slice(&[1, 0]),
RocList<i64>
);
@ -2561,7 +2561,7 @@ fn list_keep_errs() {
assert_evals_to!(
indoc!(
r#"
List.keepErrs [0,1,2] (\x -> x % 0 |> Result.mapErr (\_ -> 32))
List.keepErrs [0,1,2] (\x -> Num.remChecked x 0 |> Result.mapErr (\_ -> 32))
"#
),
RocList::from_slice(&[32, 32, 32]),

View File

@ -473,7 +473,7 @@ fn f64_sqrt() {
assert_evals_to!(
indoc!(
r#"
when Num.sqrt 100 is
when Num.sqrtChecked 100 is
Ok val -> val
Err _ -> -1
"#
@ -489,9 +489,7 @@ fn f64_log() {
assert_evals_to!(
indoc!(
r#"
when Num.log 7.38905609893 is
Ok val -> val
Err _ -> -1
Num.log 7.38905609893
"#
),
1.999999999999912,
@ -501,11 +499,11 @@ fn f64_log() {
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn f64_log_one() {
fn f64_log_checked_one() {
assert_evals_to!(
indoc!(
r#"
when Num.log 1 is
when Num.logChecked 1 is
Ok val -> val
Err _ -> -1
"#
@ -521,7 +519,7 @@ fn f64_sqrt_zero() {
assert_evals_to!(
indoc!(
r#"
when Num.sqrt 0 is
when Num.sqrtChecked 0 is
Ok val -> val
Err _ -> -1
"#
@ -533,11 +531,11 @@ fn f64_sqrt_zero() {
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn f64_sqrt_negative() {
fn f64_sqrt_checked_negative() {
assert_evals_to!(
indoc!(
r#"
when Num.sqrt -1 is
when Num.sqrtChecked -1 is
Err _ -> 42
Ok val -> val
"#
@ -549,11 +547,11 @@ fn f64_sqrt_negative() {
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn f64_log_zero() {
fn f64_log_checked_zero() {
assert_evals_to!(
indoc!(
r#"
when Num.log 0 is
when Num.logChecked 0 is
Err _ -> 42
Ok val -> val
"#
@ -569,13 +567,12 @@ fn f64_log_negative() {
assert_evals_to!(
indoc!(
r#"
when Num.log -1 is
Err _ -> 42
Ok val -> val
Num.log -1
"#
),
42.0,
f64
true,
f64,
|f: f64| f.is_nan()
);
}
@ -1082,9 +1079,7 @@ fn gen_rem_i64() {
assert_evals_to!(
indoc!(
r#"
when Num.rem 8 3 is
Ok val -> val
Err _ -> -1
Num.rem 8 3
"#
),
2,
@ -1094,11 +1089,11 @@ fn gen_rem_i64() {
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn gen_rem_div_by_zero_i64() {
fn gen_rem_checked_div_by_zero_i64() {
assert_evals_to!(
indoc!(
r#"
when Num.rem 8 0 is
when Num.remChecked 8 0 is
Err DivByZero -> 4
Ok _ -> -23
"#

View File

@ -593,6 +593,27 @@ fn top_level_constant() {
);
}
#[test]
#[ignore]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
fn top_level_destructure() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
{a, b} = { a: 1, b: 2 }
main =
a + b
"#
),
3,
i64
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn linked_list_len_0() {
@ -2972,6 +2993,7 @@ fn mix_function_and_closure_level_of_indirection() {
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg_attr(debug_assertions, ignore)] // this test stack-overflows the compiler in debug mode
fn do_pass_bool_byte_closure_layout() {
// see https://github.com/rtfeldman/roc/pull/1706
// the distinction is actually important, dropping that info means some functions just get

View File

@ -1580,3 +1580,28 @@ fn issue_2725_alias_polymorphic_lambda() {
i64
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn opaque_assign_to_symbol() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [ out ] to "./platform"
Variable := U8
fromUtf8 : U8 -> Result Variable [ InvalidVariableUtf8 ]
fromUtf8 = \char ->
Ok ($Variable char)
out =
when fromUtf8 98 is
Ok ($Variable n) -> n
_ -> 1
"#
),
98,
u8
)
}

View File

@ -0,0 +1,6 @@
procedure Test.0 ():
let Test.16 : [C TODO, C ] = SystemTool ;
let Test.14 : TODO = Job Test.16;
let Test.13 : [C TODO, C ] = FromJob Test.14;
let Test.4 : TODO = Job Test.13;
ret Test.4;

View File

@ -0,0 +1,10 @@
procedure : `#UserApp.fromUtf8` [C {}, C U8]
procedure = `#UserApp.fromUtf8` (`#UserApp.char`):
let `#UserApp.3` : [C {}, C U8] = Ok `#UserApp.4`;
ret `#UserApp.3`;
procedure : `#UserApp.out` [C {}, C U8]
procedure = `#UserApp.out` ():
let `#UserApp.2` : U8 = 98i64;
let `#UserApp.1` : [C {}, C U8] = CallByName `#UserApp.fromUtf8` `#UserApp.2`;
ret `#UserApp.1`;

View File

@ -1283,6 +1283,23 @@ fn issue_2583_specialize_errors_behind_unified_branches() {
)
}
#[mono_test]
fn issue_2810() {
indoc!(
r#"
Command : [ Command Tool ]
Job : [ Job Command ]
Tool : [ SystemTool, FromJob Job ]
a : Job
a = Job (Command (FromJob (Job (Command SystemTool))))
a
"#
)
}
#[mono_test]
fn issue_2811() {
indoc!(
@ -1313,6 +1330,23 @@ fn specialize_ability_call() {
)
}
#[mono_test]
fn opaque_assign_to_symbol() {
indoc!(
r#"
app "test" provides [ out ] to "./platform"
Variable := U8
fromUtf8 : U8 -> Result Variable [ InvalidVariableUtf8 ]
fromUtf8 = \char ->
Ok ($Variable char)
out = fromUtf8 98
"#
)
}
// #[ignore]
// #[mono_test]
// fn static_str_closure() {

View File

@ -450,7 +450,9 @@ fn write_content<'a>(
}
// useful for debugging
if false {
if cfg!(debug_assertions)
&& std::env::var("ROC_PRETTY_PRINT_ALIAS_CONTENTS").is_ok()
{
buf.push_str("[[ but really ");
let content = subs.get_content_without_compacting(*_actual);
write_content(env, ctx, content, subs, buf, parens);

View File

@ -772,7 +772,15 @@ fn subs_fmt_content(this: &Content, subs: &Subs, f: &mut fmt::Formatter) -> fmt:
AliasKind::Opaque => "Opaque",
};
write!(f, "{}({:?}, {:?}, {:?})", wrap, name, slice, actual)
write!(
f,
"{}({:?}, {:?}, <{:?}>{:?})",
wrap,
name,
slice,
actual,
SubsFmtContent(subs.get_content_without_compacting(*actual), subs)
)
}
Content::RangedNumber(typ, range) => {
let slice = subs.get_subs_slice(*range);
@ -833,7 +841,16 @@ fn subs_fmt_flat_type(this: &FlatType, subs: &Subs, f: &mut fmt::Formatter) -> f
let (it, new_ext) = tags.sorted_iterator_and_ext(subs, *ext);
for (name, slice) in it {
write!(f, "{:?} {:?}, ", name, slice)?;
write!(f, "{:?} ", name)?;
for var in slice {
write!(
f,
"<{:?}>{:?} ",
var,
SubsFmtContent(subs.get_content_without_compacting(*var), subs)
)?;
}
write!(f, ", ")?;
}
write!(f, "]<{:?}>", new_ext)
@ -1889,7 +1906,14 @@ impl Subs {
}
pub fn occurs(&self, var: Variable) -> Result<(), (Variable, Vec<Variable>)> {
occurs(self, &[], var)
occurs(self, &[], var, false)
}
pub fn occurs_including_recursion_vars(
&self,
var: Variable,
) -> Result<(), (Variable, Vec<Variable>)> {
occurs(self, &[], var, true)
}
pub fn mark_tag_union_recursive(
@ -2862,6 +2886,7 @@ fn occurs(
subs: &Subs,
seen: &[Variable],
input_var: Variable,
include_recursion_var: bool,
) -> Result<(), (Variable, Vec<Variable>)> {
use self::Content::*;
use self::FlatType::*;
@ -2885,47 +2910,77 @@ fn occurs(
new_seen.push(root_var);
match flat_type {
Apply(_, args) => {
short_circuit(subs, root_var, &new_seen, subs.get_subs_slice(*args).iter())
}
Apply(_, args) => short_circuit(
subs,
root_var,
&new_seen,
subs.get_subs_slice(*args).iter(),
include_recursion_var,
),
Func(arg_vars, closure_var, ret_var) => {
let it = once(ret_var)
.chain(once(closure_var))
.chain(subs.get_subs_slice(*arg_vars).iter());
short_circuit(subs, root_var, &new_seen, it)
short_circuit(subs, root_var, &new_seen, it, include_recursion_var)
}
Record(vars_by_field, ext_var) => {
let slice =
SubsSlice::new(vars_by_field.variables_start, vars_by_field.length);
let it = once(ext_var).chain(subs.get_subs_slice(slice).iter());
short_circuit(subs, root_var, &new_seen, it)
short_circuit(subs, root_var, &new_seen, it, include_recursion_var)
}
TagUnion(tags, ext_var) => {
for slice_index in tags.variables() {
let slice = subs[slice_index];
for var_index in slice {
let var = subs[var_index];
short_circuit_help(subs, root_var, &new_seen, var)?;
short_circuit_help(
subs,
root_var,
&new_seen,
var,
include_recursion_var,
)?;
}
}
short_circuit_help(subs, root_var, &new_seen, *ext_var)
short_circuit_help(
subs,
root_var,
&new_seen,
*ext_var,
include_recursion_var,
)
}
FunctionOrTagUnion(_, _, ext_var) => {
let it = once(ext_var);
short_circuit(subs, root_var, &new_seen, it)
short_circuit(subs, root_var, &new_seen, it, include_recursion_var)
}
RecursiveTagUnion(_rec_var, tags, ext_var) => {
// TODO rec_var is excluded here, verify that this is correct
RecursiveTagUnion(rec_var, tags, ext_var) => {
if include_recursion_var {
new_seen.push(subs.get_root_key_without_compacting(*rec_var));
}
for slice_index in tags.variables() {
let slice = subs[slice_index];
for var_index in slice {
let var = subs[var_index];
short_circuit_help(subs, root_var, &new_seen, var)?;
short_circuit_help(
subs,
root_var,
&new_seen,
var,
include_recursion_var,
)?;
}
}
short_circuit_help(subs, root_var, &new_seen, *ext_var)
short_circuit_help(
subs,
root_var,
&new_seen,
*ext_var,
include_recursion_var,
)
}
EmptyRecord | EmptyTagUnion | Erroneous(_) => Ok(()),
}
@ -2936,7 +2991,7 @@ fn occurs(
for var_index in args.into_iter() {
let var = subs[var_index];
short_circuit_help(subs, root_var, &new_seen, var)?;
short_circuit_help(subs, root_var, &new_seen, var, include_recursion_var)?;
}
Ok(())
@ -2945,7 +3000,7 @@ fn occurs(
let mut new_seen = seen.to_owned();
new_seen.push(root_var);
short_circuit_help(subs, root_var, &new_seen, *typ)?;
short_circuit_help(subs, root_var, &new_seen, *typ, include_recursion_var)?;
// _range_vars excluded because they are not explicitly part of the type.
Ok(())
@ -2960,12 +3015,13 @@ fn short_circuit<'a, T>(
root_key: Variable,
seen: &[Variable],
iter: T,
include_recursion_var: bool,
) -> Result<(), (Variable, Vec<Variable>)>
where
T: Iterator<Item = &'a Variable>,
{
for var in iter {
short_circuit_help(subs, root_key, seen, *var)?;
short_circuit_help(subs, root_key, seen, *var, include_recursion_var)?;
}
Ok(())
@ -2977,8 +3033,9 @@ fn short_circuit_help(
root_key: Variable,
seen: &[Variable],
var: Variable,
include_recursion_var: bool,
) -> Result<(), (Variable, Vec<Variable>)> {
if let Err((v, mut vec)) = occurs(subs, seen, var) {
if let Err((v, mut vec)) = occurs(subs, seen, var, include_recursion_var) {
vec.push(root_key);
return Err((v, vec));
}

View File

@ -5,7 +5,8 @@ use roc_module::symbol::Symbol;
use roc_types::subs::Content::{self, *};
use roc_types::subs::{
AliasVariables, Descriptor, ErrorTypeContext, FlatType, GetSubsSlice, Mark, OptVariable,
RecordFields, Subs, SubsIndex, SubsSlice, UnionTags, Variable, VariableSubsSlice,
RecordFields, Subs, SubsFmtContent, SubsIndex, SubsSlice, UnionTags, Variable,
VariableSubsSlice,
};
use roc_types::types::{AliasKind, DoesNotImplementAbility, ErrorType, Mismatch, RecordField};
@ -316,10 +317,10 @@ fn debug_print_unified_types(subs: &mut Subs, ctx: &Context, opt_outcome: Option
ctx.first,
ctx.second,
ctx.first,
roc_types::subs::SubsFmtContent(&content_1, subs),
SubsFmtContent(&content_1, subs),
mode,
ctx.second,
roc_types::subs::SubsFmtContent(&content_2, subs),
SubsFmtContent(&content_2, subs),
);
unsafe { UNIFICATION_DEPTH = new_depth };
@ -576,7 +577,15 @@ fn unify_structure(
RecursionVar { structure, .. } => match flat_type {
FlatType::TagUnion(_, _) => {
// unify the structure with this unrecursive tag union
unify_pool(subs, pool, ctx.first, *structure, ctx.mode)
let mut outcome = unify_pool(subs, pool, ctx.first, *structure, ctx.mode);
if outcome.mismatches.is_empty() {
outcome.union(fix_tag_union_recursion_variable(
subs, ctx, ctx.first, other,
));
}
outcome
}
FlatType::RecursiveTagUnion(rec, _, _) => {
debug_assert!(is_recursion_var(subs, *rec));
@ -585,7 +594,15 @@ fn unify_structure(
}
FlatType::FunctionOrTagUnion(_, _, _) => {
// unify the structure with this unrecursive tag union
unify_pool(subs, pool, ctx.first, *structure, ctx.mode)
let mut outcome = unify_pool(subs, pool, ctx.first, *structure, ctx.mode);
if outcome.mismatches.is_empty() {
outcome.union(fix_tag_union_recursion_variable(
subs, ctx, ctx.first, other,
));
}
outcome
}
// Only tag unions can be recursive; everything else is an error.
_ => mismatch!(
@ -643,6 +660,57 @@ fn unify_structure(
}
}
/// Ensures that a non-recursive tag union, when unified with a recursion var to become a recursive
/// tag union, properly contains a recursion variable that recurses on itself.
//
// When might this not be the case? For example, in the code
//
// Indirect : [ Indirect ConsList ]
//
// ConsList : [ Nil, Cons Indirect ]
//
// l : ConsList
// l = Cons (Indirect (Cons (Indirect Nil)))
// # ^^^^^^^^^^^^^^^~~~~~~~~~~~~~~~~~~~~~^ region-a
// # ~~~~~~~~~~~~~~~~~~~~~ region-b
// l
//
// Suppose `ConsList` has the expanded type `[ Nil, Cons [ Indirect <rec> ] ] as <rec>`.
// After unifying the tag application annotated "region-b" with the recursion variable `<rec>`,
// the tentative total-type of the application annotated "region-a" would be
// `<v> = [ Nil, Cons [ Indirect <v> ] ] as <rec>`. That is, the type of the recursive tag union
// would be inlined at the site "v", rather than passing through the correct recursion variable
// "rec" first.
//
// This is not incorrect from a type perspective, but causes problems later on for e.g. layout
// determination, which expects recursion variables to be placed correctly. Attempting to detect
// this during layout generation does not work so well because it may be that there *are* recursive
// tag unions that should be inlined, and not pass through recursion variables. So instead, try to
// resolve these cases here.
//
// See tests labeled "issue_2810" for more examples.
fn fix_tag_union_recursion_variable(
subs: &mut Subs,
ctx: &Context,
tag_union_promoted_to_recursive: Variable,
recursion_var: &Content,
) -> Outcome {
debug_assert!(matches!(
subs.get_content_without_compacting(tag_union_promoted_to_recursive),
Structure(FlatType::RecursiveTagUnion(..))
));
let has_recursing_recursive_variable = subs
.occurs_including_recursion_vars(tag_union_promoted_to_recursive)
.is_err();
if !has_recursing_recursive_variable {
merge(subs, ctx, *recursion_var)
} else {
Outcome::default()
}
}
fn unify_record(
subs: &mut Subs,
pool: &mut Pool,

View File

@ -41,8 +41,8 @@ Expr : [ Val I64, Var Str, Add Expr Expr, Mul Expr Expr, Pow Expr Expr, Ln Expr
divmod : I64, I64 -> Result { div : I64, mod : I64 } [ DivByZero ]*
divmod = \l, r ->
when Pair (l // r) (l % r) is
Pair div (Ok mod) ->
when Pair (Num.divTruncChecked l r) (Num.remChecked l r) is
Pair (Ok div) (Ok mod) ->
Ok { div, mod }
_ ->

View File

@ -23,12 +23,12 @@ makeMapHelp = \freq, n, m, acc ->
_ ->
powerOf10 =
(n % 10 |> resultWithDefault 0) == 0
n % 10 == 0
m1 = insert m n powerOf10
isFrequency =
(n % freq |> resultWithDefault 0) == 0
n % freq == 0
x = (if isFrequency then Cons m1 acc else acc)
@ -43,15 +43,6 @@ fold = \f, tree, b ->
Node _ l k v r ->
fold f r (f k v (fold f l b))
resultWithDefault : Result a e, a -> a
resultWithDefault = \res, default ->
when res is
Ok v ->
v
Err _ ->
default
main : Task.Task {} []
main =
Task.after

1
examples/breakout/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
breakout

View File

@ -0,0 +1,168 @@
app "breakout"
packages { pf: "platform" }
imports [ pf.Game.{ Bounds, Elem, Event } ]
provides [ program ] { Model } to pf
paddleWidth = 0.2# width of the paddle, as a % of screen width
paddleHeight = 50# height of the paddle, in pixels
paddleSpeed = 65# how many pixels the paddle moves per keypress
blockHeight = 80# height of a block, in pixels
blockBorder = 0.025# border of a block, as a % of its width
ballSize = 55
numRows = 4
numCols = 8
numBlocks = numRows * numCols
Model : {
# Screen height and width
height : F32,
width : F32,
# Paddle X-coordinate
paddleX : F32,
# Ball coordinates
ballX : F32,
ballY : F32,
dBallX : F32,
# delta x - how much it moves per tick
dBallY : F32,
# delta y - how much it moves per tick
}
init : Bounds -> Model
init = \{ width, height } ->
{
# Screen height and width
width,
height,
# Paddle X-coordinate
paddleX: (width * 0.5) - (paddleWidth * width * 0.5),
# Ball coordinates
ballX: width * 0.5,
ballY: height * 0.4,
# Delta - how much ball moves in each tick
dBallX: 4,
dBallY: 4,
}
update : Model, Event -> Model
update = \model, event ->
when event is
Resize size ->
{ model & width: size.width, height: size.height }
KeyDown Left ->
{ model & paddleX: model.paddleX - paddleSpeed }
KeyDown Right ->
{ model & paddleX: model.paddleX + paddleSpeed }
Tick _ ->
tick model
_ ->
model
tick : Model -> Model
tick = \model ->
model
|> moveBall
moveBall : Model -> Model
moveBall = \model ->
ballX = model.ballX + model.dBallX
ballY = model.ballY + model.dBallY
paddleTop = model.height - blockHeight - (paddleHeight * 2)
paddleLeft = model.paddleX
paddleRight = paddleLeft + (model.width * paddleWidth)
# If its y used to be less than the paddle, and now it's greater than or equal,
# then this is the frame where the ball collided with it.
crossingPaddle = model.ballY < paddleTop && ballY >= paddleTop
# If it collided with the paddle, bounce off.
directionChange =
if crossingPaddle && (ballX >= paddleLeft && ballX <= paddleRight) then
-1f32
else
1f32
dBallX = model.dBallX * directionChange
dBallY = model.dBallY * directionChange
{ model & ballX, ballY, dBallX, dBallY }
render : Model -> List Elem
render = \model ->
blocks = List.map
(List.range 0 numBlocks)
\index ->
col =
Num.rem index numCols
|> Num.toF32
row =
index
// numCols
|> Num.toF32
red = col / Num.toF32 numCols
green = row / Num.toF32 numRows
blue = Num.toF32 index / Num.toF32 numBlocks
color = { r: red * 0.8, g: 0.2 + green * 0.6, b: 0.2 + blue * 0.8, a: 1 }
{ row, col, color }
blockWidth = model.width / numCols
rects =
List.joinMap
blocks
\{ row, col, color } ->
left = Num.toF32 col * blockWidth
top = Num.toF32 (row * blockHeight)
border = blockBorder * blockWidth
outer = Rect
{
left,
top,
width: blockWidth,
height: blockHeight,
color: { r: color.r * 0.8, g: color.g * 0.8, b: color.b * 0.8, a: 1 },
}
inner = Rect
{
left: left + border,
top: top + border,
width: blockWidth - (border * 2),
height: blockHeight - (border * 2),
color,
}
[ outer, inner ]
ball =
color = { r: 0.7, g: 0.3, b: 0.9, a: 1.0 }
width = ballSize
height = ballSize
left = model.ballX
top = model.ballY
Rect { left, top, width, height, color }
paddle =
color = { r: 0.8, g: 0.8, b: 0.8, a: 1.0 }
width = model.width * paddleWidth
height = paddleHeight
left = model.paddleX
top = model.height - blockHeight - height
Rect { left, top, width, height, color }
List.concat rects [ paddle, ball ]
program = { init, update, render }

View File

@ -0,0 +1,17 @@
app "breakout"
packages { pf: "platform" }
imports [ pf.Game.{ Bounds, Elem, Event } ]
provides [ program ] { Model } to pf
Model : { text : Str }
init : Bounds -> Model
init = \_ -> { text: "Hello, World!" }
update : Model, Event -> Model
update = \model, _ -> model
render : Model -> List Elem
render = \model -> [ Text model.text ]
program = { init, update, render }

1
examples/breakout/platform/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
target

View File

@ -0,0 +1,20 @@
interface Action
exposes [ Action, none, update, map ]
imports []
Action state : [ None, Update state ]
none : Action *
none = None
update : state -> Action state
update = Update
map : Action a, (a -> b) -> Action b
map = \action, transform ->
when action is
None ->
None
Update state ->
Update (transform state)

2835
examples/breakout/platform/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,75 @@
[package]
name = "host"
version = "0.1.0"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2018"
# Needed to be able to run on non-Windows systems for some reason. Without this, cargo panics with:
#
# error: DX12 API enabled on non-Windows OS. If your project is not using resolver="2" in Cargo.toml, it should.
resolver = "2"
[lib]
name = "host"
path = "src/lib.rs"
crate-type = ["staticlib", "rlib"]
[[bin]]
name = "host"
path = "src/main.rs"
[dependencies]
roc_std = { path = "../../../roc_std" }
libc = "0.2"
arrayvec = "0.7.2"
page_size = "0.4.2"
# when changing winit version, check if copypasta can be updated simultaneously so they use the same versions for their dependencies. This will save build time.
winit = "0.26.1"
wgpu = { git = "https://github.com/gfx-rs/wgpu", rev = "0545e36" }
wgpu_glyph = { git = "https://github.com/Anton-4/wgpu_glyph", rev = "257d109" }
glyph_brush = "0.7.2"
log = "0.4.14"
env_logger = "0.9.0"
futures = "0.3.17"
cgmath = "0.18.0"
snafu = { version = "0.6.10", features = ["backtraces"] }
colored = "2.0.0"
pest = "2.1.3"
pest_derive = "2.1.0"
copypasta = "0.7.1"
palette = "0.6.0"
confy = { git = 'https://github.com/rust-cli/confy', features = [
"yaml_conf"
], default-features = false }
serde = { version = "1.0.130", features = ["derive"] }
nonempty = "0.7.0"
fs_extra = "1.2.0"
rodio = { version = "0.14.0", optional = true } # to play sounds
threadpool = "1.8.1"
[package.metadata.cargo-udeps.ignore]
# confy is currently unused but should not be removed
normal = ["confy"]
#development = []
#build = []
[features]
default = []
with_sound = ["rodio"]
[dependencies.bytemuck]
version = "1.7.2"
features = ["derive"]
[workspace]
# Optimizations based on https://deterministic.space/high-performance-rust.html
[profile.release]
lto = "fat"
codegen-units = 1
# debug = true # enable when profiling
[profile.bench]
lto = "thin"
codegen-units = 1

View File

@ -0,0 +1,193 @@
interface Elem
exposes [ Elem, PressEvent, row, col, text, button, none, translate, list ]
imports [ Action.{ Action } ]
Elem state :
# PERFORMANCE NOTE:
# If there are 8 or fewer tags here, then on a 64-bit system, the tag can be stored
# in the pointer - for massive memory savings. Try extremely hard to always limit the number
# of tags in this union to 8 or fewer!
[
Button (ButtonConfig state) (Elem state),
Text Str,
Col (List (Elem state)),
Row (List (Elem state)),
Lazy (Result { state, elem : Elem state } [ NotCached ] -> { state, elem : Elem state }),
# TODO FIXME: using this definition of Lazy causes a stack overflow in the compiler!
# Lazy (Result (Cached state) [ NotCached ] -> Cached state),
None,
]
## Used internally in the type definition of Lazy
Cached state : { state, elem : Elem state }
ButtonConfig state : { onPress : state, PressEvent -> Action state }
PressEvent : { button : [ Touch, Mouse [ Left, Right, Middle ] ] }
text : Str -> Elem *
text = \str ->
Text str
button : { onPress : state, PressEvent -> Action state }, Elem state -> Elem state
button = \config, label ->
Button config label
row : List (Elem state) -> Elem state
row = \children ->
Row children
col : List (Elem state) -> Elem state
col = \children ->
Col children
lazy : state, (state -> Elem state) -> Elem state
lazy = \state, render ->
# This function gets called by the host during rendering. It will
# receive the cached state and element (wrapped in Ok) if we've
# ever rendered this before, and Err otherwise.
Lazy
\result ->
when result is
Ok cached if cached.state == state ->
# If we have a cached value, and the new state is the
# same as the cached one, then we can return exactly
# what we had cached.
cached
_ ->
# Either the state changed or else we didn't have a
# cached value to use. Either way, we need to render
# with the new state and store that for future use.
{ state, elem: render state }
none : Elem *
none = None# I've often wanted this in elm/html. Usually end up resorting to (Html.text "") - this seems nicer.
## Change an element's state type.
##
## TODO: indent the following once https://github.com/rtfeldman/roc/issues/2585 is fixed.
## State : { photo : Photo }
##
## render : State -> Elem State
## render = \state ->
## child : Elem State
## child =
## Photo.render state.photo
## |> Elem.translate .photo &photo
##
## col {} [ child, otherElems ]
##
translate = \child, toChild, toParent ->
when child is
Text str ->
Text str
Col elems ->
Col (List.map elems \elem -> translate elem toChild toParent)
Row elems ->
Row (List.map elems \elem -> translate elem toChild toParent)
Button config label ->
onPress = \parentState, event ->
toChild parentState
|> config.onPress event
|> Action.map \c -> toParent parentState c
Button { onPress } (translate label toChild toParent)
Lazy renderChild ->
Lazy
\parentState ->
{ elem, state } = renderChild (toChild parentState)
{
elem: translate toChild toParent newChild,
state: toParent parentState state,
}
None ->
None
## Render a list of elements, using [Elem.translate] on each of them.
##
## Convenient when you have a [List] in your state and want to make
## a [List] of child elements out of it.
##
## TODO: indent the following once https://github.com/rtfeldman/roc/issues/2585 is fixed.
## State : { photos : List Photo }
##
## render : State -> Elem State
## render = \state ->
## children : List (Elem State)
## children =
## Elem.list Photo.render state .photos &photos
##
## col {} children
## TODO: format as multiline type annotation once https://github.com/rtfeldman/roc/issues/2586 is fixed
list : (child -> Elem child), parent, (parent -> List child), (parent, List child -> parent) -> List (Elem parent)
list = \renderChild, parent, toChildren, toParent ->
List.mapWithIndex
(toChildren parent)
\index, child ->
toChild = \par -> List.get (toChildren par) index
newChild = translateOrDrop
child
toChild
\par, ch ->
toChildren par
|> List.set ch index
|> toParent
renderChild newChild
## Internal helper function for Elem.list
##
## Tries to translate a child to a parent, but
## if the child has been removed from the parent,
## drops it.
##
## TODO: format as multiline type annotation once https://github.com/rtfeldman/roc/issues/2586 is fixed
translateOrDrop : Elem child, (parent -> Result child *), (parent, child -> parent) -> Elem parent
translateOrDrop = \child, toChild, toParent ->
when child is
Text str ->
Text str
Col elems ->
Col (List.map elems \elem -> translateOrDrop elem toChild toParent)
Row elems ->
Row (List.map elems \elem -> translateOrDrop elem toChild toParent)
Button config label ->
onPress = \parentState, event ->
when toChild parentState is
Ok newChild ->
newChild
|> config.onPress event
|> Action.map \c -> toParent parentState c
Err _ ->
# The child was removed from the list before this onPress handler resolved.
# (For example, by a previous event handler that fired simultaneously.)
Action.none
Button { onPress } (translateOrDrop label toChild toParent)
Lazy childState renderChild ->
Lazy
(toParent childState)
\parentState ->
when toChild parentState is
Ok newChild ->
renderChild newChild
|> translateOrDrop toChild toParent
Err _ ->
None
# I don't think this should ever happen in practice.
None ->
None

View File

@ -0,0 +1,13 @@
interface Game
exposes [ Bounds, Elem, Event ]
imports []
Rgba : { r : F32, g : F32, b : F32, a : F32 }
Bounds : { height : F32, width : F32 }
Elem : [ Rect { color : Rgba, left : F32, top : F32, width : F32, height : F32 }, Text Str ]
KeyCode : [ Left, Right, Other ]
Event : [ Resize { width : F32, height : F32 }, KeyDown KeyCode, KeyUp KeyCode, Tick U128 ]

View File

@ -0,0 +1,14 @@
platform "gui"
requires { Model } { program : _ }
exposes [ Game ]
packages {}
imports [ Game.{ Bounds, Elem, Event } ]
provides [ programForHost ]
# TODO allow changing the window title - maybe via a Task, since that shouldn't happen all the time
programForHost : {
init : (Bounds -> Model) as Init,
update : (Model, Event -> Model) as Update,
render : (Model -> List Elem) as Render,
}
programForHost = program

View File

@ -0,0 +1,4 @@
fn main() {
println!("cargo:rustc-link-lib=dylib=app");
println!("cargo:rustc-link-search=.");
}

View File

@ -0,0 +1,3 @@
extern int rust_main();
int main() { return rust_main(); }

View File

@ -0,0 +1,50 @@
use cgmath::Vector4;
use palette::{FromColor, Hsv, Srgb};
/// This order is optimized for what Roc will send
#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq, Default)]
pub struct Rgba {
a: f32,
b: f32,
g: f32,
r: f32,
}
impl Rgba {
pub const WHITE: Self = Self::new(1.0, 1.0, 1.0, 1.0);
pub const fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
Self { r, g, b, a }
}
pub const fn to_array(self) -> [f32; 4] {
[self.r, self.g, self.b, self.a]
}
pub fn from_hsb(hue: usize, saturation: usize, brightness: usize) -> Self {
Self::from_hsba(hue, saturation, brightness, 1.0)
}
pub fn from_hsba(hue: usize, saturation: usize, brightness: usize, alpha: f32) -> Self {
let rgb = Srgb::from_color(Hsv::new(
hue as f32,
(saturation as f32) / 100.0,
(brightness as f32) / 100.0,
));
Self::new(rgb.red, rgb.green, rgb.blue, alpha)
}
}
impl From<Rgba> for [f32; 4] {
fn from(rgba: Rgba) -> Self {
rgba.to_array()
}
}
impl From<Rgba> for Vector4<f32> {
fn from(rgba: Rgba) -> Self {
Vector4::new(rgba.r, rgba.b, rgba.g, rgba.a)
}
}

View File

@ -0,0 +1,96 @@
// Contains parts of https://github.com/sotrh/learn-wgpu
// by Benjamin Hansen - license information can be found in the LEGAL_DETAILS
// file in the root directory of this distribution.
//
// Thank you, Benjamin!
// Contains parts of https://github.com/iced-rs/iced/blob/adce9e04213803bd775538efddf6e7908d1c605e/wgpu/src/shader/quad.wgsl
// By Héctor Ramón, Iced contributors Licensed under the MIT license.
// The license is included in the LEGAL_DETAILS file in the root directory of this distribution.
// Thank you Héctor Ramón and Iced contributors!
use std::mem;
use super::{quad::Quad, vertex::Vertex};
use crate::graphics::primitives::rect::RectElt;
use wgpu::util::DeviceExt;
pub struct RectBuffers {
pub vertex_buffer: wgpu::Buffer,
pub index_buffer: wgpu::Buffer,
pub quad_buffer: wgpu::Buffer,
}
pub const QUAD_INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3];
const QUAD_VERTS: [Vertex; 4] = [
Vertex {
_position: [0.0, 0.0],
},
Vertex {
_position: [1.0, 0.0],
},
Vertex {
_position: [1.0, 1.0],
},
Vertex {
_position: [0.0, 1.0],
},
];
pub const MAX_QUADS: usize = 1_000;
pub fn create_rect_buffers(
gpu_device: &wgpu::Device,
cmd_encoder: &mut wgpu::CommandEncoder,
rects: &[RectElt],
) -> RectBuffers {
let vertex_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: None,
contents: bytemuck::cast_slice(&QUAD_VERTS),
usage: wgpu::BufferUsages::VERTEX,
});
let index_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: None,
contents: bytemuck::cast_slice(&QUAD_INDICES),
usage: wgpu::BufferUsages::INDEX,
});
let quad_buffer = gpu_device.create_buffer(&wgpu::BufferDescriptor {
label: None,
size: mem::size_of::<Quad>() as u64 * MAX_QUADS as u64,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let quads: Vec<Quad> = rects.iter().map(|rect| to_quad(rect)).collect();
let buffer_size = (quads.len() as u64) * Quad::SIZE;
let staging_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: None,
contents: bytemuck::cast_slice(&quads),
usage: wgpu::BufferUsages::COPY_SRC,
});
cmd_encoder.copy_buffer_to_buffer(&staging_buffer, 0, &quad_buffer, 0, buffer_size);
RectBuffers {
vertex_buffer,
index_buffer,
quad_buffer,
}
}
pub fn to_quad(rect_elt: &RectElt) -> Quad {
Quad {
pos: rect_elt.rect.pos.into(),
width: rect_elt.rect.width,
height: rect_elt.rect.height,
color: (rect_elt.color.to_array()),
border_color: rect_elt.border_color.into(),
border_width: rect_elt.border_width,
}
}

View File

@ -0,0 +1,5 @@
pub mod buffer;
pub mod ortho;
pub mod pipelines;
pub mod vertex;
pub mod quad;

View File

@ -0,0 +1,118 @@
use cgmath::{Matrix4, Ortho};
use wgpu::util::DeviceExt;
use wgpu::{
BindGroup, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, Buffer,
ShaderStages,
};
// orthographic projection is used to transform pixel coords to the coordinate system used by wgpu
#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct Uniforms {
// We can't use cgmath with bytemuck directly so we'll have
// to convert the Matrix4 into a 4x4 f32 array
ortho: [[f32; 4]; 4],
}
impl Uniforms {
fn new(w: u32, h: u32) -> Self {
let ortho: Matrix4<f32> = Ortho::<f32> {
left: 0.0,
right: w as f32,
bottom: h as f32,
top: 0.0,
near: -1.0,
far: 1.0,
}
.into();
Self {
ortho: ortho.into(),
}
}
}
// update orthographic buffer according to new window size
pub fn update_ortho_buffer(
inner_width: u32,
inner_height: u32,
gpu_device: &wgpu::Device,
ortho_buffer: &Buffer,
cmd_queue: &wgpu::Queue,
) {
let new_uniforms = Uniforms::new(inner_width, inner_height);
let new_ortho_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Ortho uniform buffer"),
contents: bytemuck::cast_slice(&[new_uniforms]),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_SRC,
});
// get a command encoder for the current frame
let mut encoder = gpu_device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Resize"),
});
// overwrite the new buffer over the old one
encoder.copy_buffer_to_buffer(
&new_ortho_buffer,
0,
ortho_buffer,
0,
(std::mem::size_of::<Uniforms>() * vec![new_uniforms].as_slice().len())
as wgpu::BufferAddress,
);
cmd_queue.submit(Some(encoder.finish()));
}
#[derive(Debug)]
pub struct OrthoResources {
pub buffer: Buffer,
pub bind_group_layout: BindGroupLayout,
pub bind_group: BindGroup,
}
pub fn init_ortho(
inner_width: u32,
inner_height: u32,
gpu_device: &wgpu::Device,
) -> OrthoResources {
let uniforms = Uniforms::new(inner_width, inner_height);
let ortho_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Ortho uniform buffer"),
contents: bytemuck::cast_slice(&[uniforms]),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
});
// bind groups consist of extra resources that are provided to the shaders
let ortho_bind_group_layout = gpu_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
label: Some("Ortho bind group layout"),
});
let ortho_bind_group = gpu_device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &ortho_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: ortho_buffer.as_entire_binding(),
}],
label: Some("Ortho bind group"),
});
OrthoResources {
buffer: ortho_buffer,
bind_group_layout: ortho_bind_group_layout,
bind_group: ortho_bind_group,
}
}

View File

@ -0,0 +1,72 @@
use super::ortho::{init_ortho, OrthoResources};
use super::quad::Quad;
use super::vertex::Vertex;
use std::borrow::Cow;
pub struct RectResources {
pub pipeline: wgpu::RenderPipeline,
pub ortho: OrthoResources,
}
pub fn make_rect_pipeline(
gpu_device: &wgpu::Device,
surface_config: &wgpu::SurfaceConfiguration,
) -> RectResources {
let ortho = init_ortho(surface_config.width, surface_config.height, gpu_device);
let pipeline_layout = gpu_device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: None,
bind_group_layouts: &[&ortho.bind_group_layout],
push_constant_ranges: &[],
});
let pipeline = create_render_pipeline(
gpu_device,
&pipeline_layout,
surface_config.format,
&wgpu::ShaderModuleDescriptor {
label: None,
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("../shaders/quad.wgsl"))),
},
);
RectResources { pipeline, ortho }
}
pub fn create_render_pipeline(
device: &wgpu::Device,
layout: &wgpu::PipelineLayout,
color_format: wgpu::TextureFormat,
shader_module_desc: &wgpu::ShaderModuleDescriptor,
) -> wgpu::RenderPipeline {
let shader = device.create_shader_module(shader_module_desc);
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Render pipeline"),
layout: Some(layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
buffers: &[Vertex::DESC, Quad::DESC],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
targets: &[wgpu::ColorTargetState {
format: color_format,
blend: Some(wgpu::BlendState {
color: wgpu::BlendComponent {
operation: wgpu::BlendOperation::Add,
src_factor: wgpu::BlendFactor::SrcAlpha,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
},
alpha: wgpu::BlendComponent::REPLACE,
}),
write_mask: wgpu::ColorWrites::ALL,
}],
}),
primitive: wgpu::PrimitiveState::default(),
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview: None,
})
}

View File

@ -0,0 +1,31 @@
/// A polygon with 4 corners
#[derive(Copy, Clone)]
pub struct Quad {
pub pos: [f32; 2],
pub width: f32,
pub height: f32,
pub color: [f32; 4],
pub border_color: [f32; 4],
pub border_width: f32,
}
unsafe impl bytemuck::Pod for Quad {}
unsafe impl bytemuck::Zeroable for Quad {}
impl Quad {
pub const SIZE: wgpu::BufferAddress = std::mem::size_of::<Self>() as wgpu::BufferAddress;
pub const DESC: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout {
array_stride: Self::SIZE,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &wgpu::vertex_attr_array!(
1 => Float32x2,
2 => Float32,
3 => Float32,
4 => Float32x4,
5 => Float32x4,
6 => Float32,
),
};
}

View File

@ -0,0 +1,35 @@
// Inspired by https://github.com/sotrh/learn-wgpu
// by Benjamin Hansen - license information can be found in the LEGAL_DETAILS
// file in the root directory of this distribution.
//
// Thank you, Benjamin!
// Inspired by https://github.com/iced-rs/iced/blob/adce9e04213803bd775538efddf6e7908d1c605e/wgpu/src/shader/quad.wgsl
// By Héctor Ramón, Iced contributors Licensed under the MIT license.
// The license is included in the LEGAL_DETAILS file in the root directory of this distribution.
// Thank you Héctor Ramón and Iced contributors!
use bytemuck::{Pod, Zeroable};
#[repr(C)]
#[derive(Copy, Clone, Zeroable, Pod)]
pub struct Vertex {
pub _position: [f32; 2],
}
impl Vertex {
pub const SIZE: wgpu::BufferAddress = std::mem::size_of::<Self>() as wgpu::BufferAddress;
pub const DESC: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout {
array_stride: Self::SIZE,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
// position
wgpu::VertexAttribute {
offset: 0,
shader_location: 0,
format: wgpu::VertexFormat::Float32x2,
},
],
};
}

View File

@ -0,0 +1,4 @@
pub mod colors;
pub mod lowlevel;
pub mod primitives;
pub mod style;

View File

@ -0,0 +1,2 @@
pub mod rect;
pub mod text;

View File

@ -0,0 +1,27 @@
use crate::graphics::colors::Rgba;
use cgmath::Vector2;
#[derive(Debug, Copy, Clone)]
pub struct RectElt {
pub rect: Rect,
pub color: Rgba,
pub border_width: f32,
pub border_color: Rgba,
}
/// These fields are ordered this way because in Roc, the corresponding stuct is:
///
/// { top : F32, left : F32, width : F32, height : F32 }
///
/// alphabetically, that's { height, left, top, width } - which works out to the same as:
///
/// struct Rect { height: f32, pos: Vector2<f32>, width: f32 }
///
/// ...because Vector2<f32> is a repr(C) struct of { x: f32, y: f32 }
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct Rect {
pub height: f32,
pub pos: Vector2<f32>,
pub width: f32,
}

View File

@ -0,0 +1,134 @@
// Adapted from https://github.com/sotrh/learn-wgpu
// by Benjamin Hansen - license information can be found in the COPYRIGHT
// file in the root directory of this distribution.
//
// Thank you, Benjamin!
use crate::graphics::colors::Rgba;
use crate::graphics::style::DEFAULT_FONT_SIZE;
use ab_glyph::{FontArc, InvalidFont};
use cgmath::Vector2;
use wgpu_glyph::{ab_glyph, GlyphBrush, GlyphBrushBuilder};
#[derive(Debug)]
pub struct Text<'a> {
pub position: Vector2<f32>,
pub area_bounds: Vector2<f32>,
pub color: Rgba,
pub text: &'a str,
pub size: f32,
pub visible: bool,
pub centered: bool,
}
impl<'a> Default for Text<'a> {
fn default() -> Self {
Self {
position: (0.0, 0.0).into(),
area_bounds: (std::f32::INFINITY, std::f32::INFINITY).into(),
color: Rgba::WHITE,
text: "",
size: DEFAULT_FONT_SIZE,
visible: true,
centered: false,
}
}
}
// pub fn layout_from_text(text: &Text) -> wgpu_glyph::Layout<wgpu_glyph::BuiltInLineBreaker> {
// wgpu_glyph::Layout::default().h_align(if text.centered {
// wgpu_glyph::HorizontalAlign::Center
// } else {
// wgpu_glyph::HorizontalAlign::Left
// })
// }
// fn section_from_text<'a>(
// text: &'a Text,
// layout: wgpu_glyph::Layout<wgpu_glyph::BuiltInLineBreaker>,
// ) -> wgpu_glyph::Section<'a> {
// Section {
// screen_position: text.position.into(),
// bounds: text.area_bounds.into(),
// layout,
// ..Section::default()
// }
// .add_text(
// wgpu_glyph::Text::new(text.text)
// .with_color(text.color)
// .with_scale(text.size),
// )
// }
// pub fn owned_section_from_text(text: &Text) -> OwnedSection {
// let layout = layout_from_text(text);
// OwnedSection {
// screen_position: text.position.into(),
// bounds: text.area_bounds.into(),
// layout,
// ..OwnedSection::default()
// }
// .add_text(
// glyph_brush::OwnedText::new(text.text)
// .with_color(Vector4::from(text.color))
// .with_scale(text.size),
// )
// }
// pub fn owned_section_from_glyph_texts(
// text: Vec<glyph_brush::OwnedText>,
// screen_position: (f32, f32),
// area_bounds: (f32, f32),
// layout: wgpu_glyph::Layout<wgpu_glyph::BuiltInLineBreaker>,
// ) -> glyph_brush::OwnedSection {
// glyph_brush::OwnedSection {
// screen_position,
// bounds: area_bounds,
// layout,
// text,
// }
// }
// pub fn queue_text_draw(text: &Text, glyph_brush: &mut GlyphBrush<()>) {
// let layout = layout_from_text(text);
// let section = section_from_text(text, layout);
// glyph_brush.queue(section.clone());
// }
// fn glyph_to_rect(glyph: &wgpu_glyph::SectionGlyph) -> Rect {
// let position = glyph.glyph.position;
// let px_scale = glyph.glyph.scale;
// let width = glyph_width(&glyph.glyph);
// let height = px_scale.y;
// let top_y = glyph_top_y(&glyph.glyph);
// Rect {
// pos: [position.x, top_y].into(),
// width,
// height,
// }
// }
// pub fn glyph_top_y(glyph: &Glyph) -> f32 {
// let height = glyph.scale.y;
// glyph.position.y - height * 0.75
// }
// pub fn glyph_width(glyph: &Glyph) -> f32 {
// glyph.scale.x * 0.4765
// }
pub fn build_glyph_brush(
gpu_device: &wgpu::Device,
render_format: wgpu::TextureFormat,
) -> Result<GlyphBrush<()>, InvalidFont> {
let inconsolata = FontArc::try_from_slice(include_bytes!(
"../../../../../../editor/Inconsolata-Regular.ttf"
))?;
Ok(GlyphBrushBuilder::using_font(inconsolata).build(gpu_device, render_format))
}

View File

@ -0,0 +1,60 @@
struct Globals {
ortho: mat4x4<f32>;
};
@group(0)
@binding(0)
var<uniform> globals: Globals;
struct VertexInput {
@location(0) position: vec2<f32>;
};
struct Quad {
@location(1) pos: vec2<f32>; // can't use the name "position" twice for compatibility with metal on MacOS
@location(2) width: f32;
@location(3) height: f32;
@location(4) color: vec4<f32>;
@location(5) border_color: vec4<f32>;
@location(6) border_width: f32;
};
struct VertexOutput {
@builtin(position) position: vec4<f32>;
@location(0) color: vec4<f32>;
@location(1) border_color: vec4<f32>;
@location(2) border_width: f32;
};
@stage(vertex)
fn vs_main(
input: VertexInput,
quad: Quad
) -> VertexOutput {
var transform: mat4x4<f32> = mat4x4<f32>(
vec4<f32>(quad.width, 0.0, 0.0, 0.0),
vec4<f32>(0.0, quad.height, 0.0, 0.0),
vec4<f32>(0.0, 0.0, 1.0, 0.0),
vec4<f32>(quad.pos, 0.0, 1.0)
);
var out: VertexOutput;
out.position = globals.ortho * transform * vec4<f32>(input.position, 0.0, 1.0);;
out.color = quad.color;
out.border_color = quad.border_color;
out.border_width = quad.border_width;
return out;
}
@stage(fragment)
fn fs_main(
input: VertexOutput
) -> @location(0) vec4<f32> {
return input.color;
}

View File

@ -0,0 +1 @@
pub const DEFAULT_FONT_SIZE: f32 = 30.0;

View File

@ -0,0 +1,513 @@
use crate::{
graphics::{
colors::Rgba,
lowlevel::buffer::create_rect_buffers,
lowlevel::{buffer::MAX_QUADS, ortho::update_ortho_buffer},
lowlevel::{buffer::QUAD_INDICES, pipelines},
primitives::{
rect::{Rect, RectElt},
text::build_glyph_brush,
},
},
roc::{self, Bounds, RocElem, RocElemTag, RocEvent},
};
use cgmath::{Vector2, Vector4};
use glyph_brush::{GlyphCruncher, OwnedSection};
use pipelines::RectResources;
use std::{
error::Error,
time::{Duration, Instant},
};
use wgpu::{CommandEncoder, LoadOp, RenderPass, TextureView};
use wgpu_glyph::GlyphBrush;
use winit::{
dpi::PhysicalSize,
event,
event::{ElementState, Event, ModifiersState, StartCause},
event_loop::ControlFlow,
platform::run_return::EventLoopExtRunReturn,
};
// Inspired by:
// https://github.com/sotrh/learn-wgpu by Benjamin Hansen, which is licensed under the MIT license
// https://github.com/cloudhead/rgx by Alexis Sellier, which is licensed under the MIT license
//
// See this link to learn wgpu: https://sotrh.github.io/learn-wgpu/
const TIME_BETWEEN_TICKS: Duration = Duration::new(0, 1000 / 60);
pub fn run_event_loop(title: &str, window_bounds: Bounds) -> Result<(), Box<dyn Error>> {
let (mut model, mut elems) = roc::init_and_render(window_bounds);
// Open window and create a surface
let mut event_loop = winit::event_loop::EventLoop::new();
let window = winit::window::WindowBuilder::new()
.with_inner_size(PhysicalSize::new(window_bounds.width, window_bounds.height))
.with_title(title)
.build(&event_loop)
.unwrap();
macro_rules! update_and_rerender {
($event:expr) => {
// TODO use (model, elems) = ... once we've upgraded rust versions
let pair = roc::update_and_render(model, $event);
model = pair.0;
elems = pair.1;
window.request_redraw();
};
}
let instance = wgpu::Instance::new(wgpu::Backends::all());
let surface = unsafe { instance.create_surface(&window) };
// Initialize GPU
let (gpu_device, cmd_queue) = futures::executor::block_on(async {
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: Some(&surface),
force_fallback_adapter: false,
})
.await
.expect(r#"Request adapter
If you're running this from inside nix, follow the instructions here to resolve this: https://github.com/rtfeldman/roc/blob/trunk/BUILDING_FROM_SOURCE.md#editor
"#);
adapter
.request_device(
&wgpu::DeviceDescriptor {
label: None,
features: wgpu::Features::empty(),
limits: wgpu::Limits::default(),
},
None,
)
.await
.expect("Request device")
});
// Create staging belt and a local pool
let mut staging_belt = wgpu::util::StagingBelt::new(1024);
let mut local_pool = futures::executor::LocalPool::new();
let local_spawner = local_pool.spawner();
// Prepare swap chain
let render_format = wgpu::TextureFormat::Bgra8Unorm;
let mut size = window.inner_size();
let surface_config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: render_format,
width: size.width,
height: size.height,
present_mode: wgpu::PresentMode::Mailbox,
};
surface.configure(&gpu_device, &surface_config);
let rect_resources = pipelines::make_rect_pipeline(&gpu_device, &surface_config);
let mut glyph_brush = build_glyph_brush(&gpu_device, render_format)?;
let mut keyboard_modifiers = ModifiersState::empty();
// Render loop
let app_start_time = Instant::now();
let mut next_tick = app_start_time + TIME_BETWEEN_TICKS;
window.request_redraw();
event_loop.run_return(|event, _, control_flow| {
match event {
// Close
Event::WindowEvent {
event: event::WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
// Resize
Event::WindowEvent {
event: event::WindowEvent::Resized(new_size),
..
} => {
size = new_size;
surface.configure(
&gpu_device,
&wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: render_format,
width: size.width,
height: size.height,
present_mode: wgpu::PresentMode::Mailbox,
},
);
update_ortho_buffer(
size.width,
size.height,
&gpu_device,
&rect_resources.ortho.buffer,
&cmd_queue,
);
update_and_rerender!(RocEvent::Resize(Bounds {
height: size.height as f32,
width: size.width as f32,
}));
}
// Keyboard input
Event::WindowEvent {
event:
event::WindowEvent::KeyboardInput {
input:
event::KeyboardInput {
virtual_keycode: Some(keycode),
state: input_state,
..
},
..
},
..
} => {
let roc_event = match input_state {
ElementState::Pressed => RocEvent::KeyDown(keycode.into()),
ElementState::Released => RocEvent::KeyUp(keycode.into()),
};
model = roc::update(model, roc_event);
}
// Modifiers Changed
Event::WindowEvent {
event: event::WindowEvent::ModifiersChanged(modifiers),
..
} => {
keyboard_modifiers = modifiers;
}
Event::RedrawRequested { .. } => {
// Get a command cmd_encoder for the current frame
let mut cmd_encoder =
gpu_device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Redraw"),
});
let surface_texture = surface
.get_current_texture()
.expect("Failed to acquire next SwapChainTexture");
let view = surface_texture
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
for elem in elems.iter() {
let (_bounds, drawable) = to_drawable(
elem,
Bounds {
width: size.width as f32,
height: size.height as f32,
},
&mut glyph_brush,
);
process_drawable(
drawable,
&mut staging_belt,
&mut glyph_brush,
&mut cmd_encoder,
&view,
&gpu_device,
&rect_resources,
wgpu::LoadOp::Load,
Bounds {
width: size.width as f32,
height: size.height as f32,
},
);
}
staging_belt.finish();
cmd_queue.submit(Some(cmd_encoder.finish()));
surface_texture.present();
// Recall unused staging buffers
use futures::task::SpawnExt;
local_spawner
.spawn(staging_belt.recall())
.expect("Recall staging belt");
local_pool.run_until_stalled();
}
Event::NewEvents(StartCause::ResumeTimeReached {
requested_resume, ..
}) => {
// Only run this logic if this is the tick we originally requested.
if requested_resume == next_tick {
let now = Instant::now();
// Set a new next_tick *before* running update and rerender,
// so their runtime isn't factored into when we want to render next.
next_tick = now + TIME_BETWEEN_TICKS;
let tick = now.saturating_duration_since(app_start_time);
update_and_rerender!(RocEvent::Tick(tick));
*control_flow = winit::event_loop::ControlFlow::WaitUntil(next_tick);
}
}
_ => {
// Keep waiting until the next tick.
*control_flow = winit::event_loop::ControlFlow::WaitUntil(next_tick);
}
}
});
Ok(())
}
fn draw_rects(
all_rects: &[RectElt],
cmd_encoder: &mut CommandEncoder,
texture_view: &TextureView,
gpu_device: &wgpu::Device,
rect_resources: &RectResources,
load_op: LoadOp<wgpu::Color>,
) {
let rect_buffers = create_rect_buffers(gpu_device, cmd_encoder, all_rects);
let mut render_pass = begin_render_pass(cmd_encoder, texture_view, load_op);
render_pass.set_pipeline(&rect_resources.pipeline);
render_pass.set_bind_group(0, &rect_resources.ortho.bind_group, &[]);
render_pass.set_vertex_buffer(0, rect_buffers.vertex_buffer.slice(..));
render_pass.set_vertex_buffer(1, rect_buffers.quad_buffer.slice(..));
render_pass.set_index_buffer(
rect_buffers.index_buffer.slice(..),
wgpu::IndexFormat::Uint16,
);
render_pass.draw_indexed(0..QUAD_INDICES.len() as u32, 0, 0..MAX_QUADS as u32);
}
fn begin_render_pass<'a>(
cmd_encoder: &'a mut CommandEncoder,
texture_view: &'a TextureView,
load_op: LoadOp<wgpu::Color>,
) -> RenderPass<'a> {
cmd_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
color_attachments: &[wgpu::RenderPassColorAttachment {
view: texture_view,
resolve_target: None,
ops: wgpu::Operations {
load: load_op,
store: true,
},
}],
depth_stencil_attachment: None,
label: None,
})
}
#[derive(Clone, Debug)]
struct Drawable {
pos: Vector2<f32>,
bounds: Bounds,
content: DrawableContent,
}
#[derive(Clone, Debug)]
enum DrawableContent {
/// This stores an actual Section because an earlier step needs to know the bounds of
/// the text, and making a Section is a convenient way to compute those bounds.
Text(OwnedSection, Vector2<f32>),
FillRect {
color: Rgba,
border_width: f32,
border_color: Rgba,
},
}
fn process_drawable(
drawable: Drawable,
staging_belt: &mut wgpu::util::StagingBelt,
glyph_brush: &mut GlyphBrush<()>,
cmd_encoder: &mut CommandEncoder,
texture_view: &TextureView,
gpu_device: &wgpu::Device,
rect_resources: &RectResources,
load_op: LoadOp<wgpu::Color>,
texture_size: Bounds,
) {
draw(
drawable.bounds,
drawable.content,
drawable.pos,
staging_belt,
glyph_brush,
cmd_encoder,
texture_view,
gpu_device,
rect_resources,
load_op,
texture_size,
);
}
fn draw(
bounds: Bounds,
content: DrawableContent,
pos: Vector2<f32>,
staging_belt: &mut wgpu::util::StagingBelt,
glyph_brush: &mut GlyphBrush<()>,
cmd_encoder: &mut CommandEncoder,
texture_view: &TextureView,
gpu_device: &wgpu::Device,
rect_resources: &RectResources,
load_op: LoadOp<wgpu::Color>,
texture_size: Bounds,
) {
use DrawableContent::*;
match content {
Text(section, offset) => {
glyph_brush.queue(section.with_screen_position(pos + offset).to_borrowed());
glyph_brush
.draw_queued(
gpu_device,
staging_belt,
cmd_encoder,
texture_view,
texture_size.width as u32, // TODO why do we make these be u32 and then cast to f32 in orthorgraphic_projection?
texture_size.height as u32,
)
.expect("Failed to draw text element");
}
FillRect {
color,
border_width,
border_color,
} => {
// TODO store all these colors and things in FillRect
let rect_elt = RectElt {
rect: Rect {
pos,
width: bounds.width,
height: bounds.height,
},
color,
border_width,
border_color,
};
// TODO inline draw_rects into here!
draw_rects(
&[rect_elt],
cmd_encoder,
texture_view,
gpu_device,
rect_resources,
load_op,
);
}
}
}
/// focused_elem is the currently-focused element (or NULL if nothing has the focus)
fn to_drawable(
elem: &RocElem,
bounds: Bounds,
glyph_brush: &mut GlyphBrush<()>,
) -> (Bounds, Drawable) {
use RocElemTag::*;
match elem.tag() {
Rect => {
let rect = unsafe { &elem.entry().rect };
let bounds = Bounds {
width: rect.width,
height: rect.height,
};
let drawable = Drawable {
pos: (rect.left, rect.top).into(),
bounds,
content: DrawableContent::FillRect {
color: rect.color,
border_width: 1.0,
border_color: rect.color,
},
};
(bounds, drawable)
}
Text => {
let text = unsafe { &elem.entry().text };
let is_centered = true; // TODO don't hardcode this
let layout = wgpu_glyph::Layout::default().h_align(if is_centered {
wgpu_glyph::HorizontalAlign::Center
} else {
wgpu_glyph::HorizontalAlign::Left
});
let section = owned_section_from_str(text.as_str(), bounds, layout);
// Calculate the bounds and offset by measuring glyphs
let text_bounds;
let offset;
match glyph_brush.glyph_bounds(section.to_borrowed()) {
Some(glyph_bounds) => {
text_bounds = Bounds {
width: glyph_bounds.max.x - glyph_bounds.min.x,
height: glyph_bounds.max.y - glyph_bounds.min.y,
};
offset = (-glyph_bounds.min.x, -glyph_bounds.min.y).into();
}
None => {
text_bounds = Bounds {
width: 0.0,
height: 0.0,
};
offset = (0.0, 0.0).into();
}
}
let drawable = Drawable {
pos: (0.0, 0.0).into(), // TODO store the pos in Text and read it here
bounds: text_bounds,
content: DrawableContent::Text(section, offset),
};
(text_bounds, drawable)
}
}
}
fn owned_section_from_str(
string: &str,
bounds: Bounds,
layout: wgpu_glyph::Layout<wgpu_glyph::BuiltInLineBreaker>,
) -> OwnedSection {
// TODO don't hardcode any of this!
let color = Rgba::WHITE;
let size: f32 = 40.0;
OwnedSection {
bounds: (bounds.width, bounds.height),
layout,
..OwnedSection::default()
}
.add_text(
glyph_brush::OwnedText::new(string)
.with_color(Vector4::from(color))
.with_scale(size),
)
}

View File

@ -0,0 +1,16 @@
mod graphics;
mod gui;
mod roc;
#[no_mangle]
pub extern "C" fn rust_main() -> i32 {
let bounds = roc::Bounds {
width: 1900.0,
height: 1000.0,
};
gui::run_event_loop("RocOut!", bounds).expect("Error running event loop");
// Exit code
0
}

View File

@ -0,0 +1,3 @@
fn main() {
std::process::exit(host::rust_main());
}

View File

@ -0,0 +1,435 @@
use crate::graphics::colors::Rgba;
use core::alloc::Layout;
use core::ffi::c_void;
use core::mem::ManuallyDrop;
use roc_std::{ReferenceCount, RocList, RocStr};
use std::ffi::CStr;
use std::fmt::Debug;
use std::mem::MaybeUninit;
use std::os::raw::c_char;
use std::time::Duration;
use winit::event::VirtualKeyCode;
extern "C" {
// program
#[link_name = "roc__programForHost_1_exposed_generic"]
fn roc_program() -> ();
#[link_name = "roc__programForHost_size"]
fn roc_program_size() -> i64;
// init
#[link_name = "roc__programForHost_1_Init_caller"]
fn call_init(size: *const Bounds, closure_data: *const u8, output: *mut Model);
#[link_name = "roc__programForHost_1_Init_size"]
fn init_size() -> i64;
#[link_name = "roc__programForHost_1_Init_result_size"]
fn init_result_size() -> i64;
// update
#[link_name = "roc__programForHost_1_Update_caller"]
fn call_update(
model: *const Model,
event: *const RocEvent,
closure_data: *const u8,
output: *mut Model,
);
#[link_name = "roc__programForHost_1_Update_size"]
fn update_size() -> i64;
#[link_name = "roc__programForHost_1_Update_result_size"]
fn update_result_size() -> i64;
// render
#[link_name = "roc__programForHost_1_Render_caller"]
fn call_render(model: *const Model, closure_data: *const u8, output: *mut RocList<RocElem>);
#[link_name = "roc__programForHost_1_Render_size"]
fn roc_render_size() -> i64;
}
#[repr(C)]
pub union RocEventEntry {
pub key_down: RocKeyCode,
pub key_up: RocKeyCode,
pub resize: Bounds,
pub tick: [u8; 16], // u128 is unsupported in repr(C)
}
#[repr(u8)]
#[allow(unused)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RocEventTag {
KeyDown = 0,
KeyUp,
Resize,
Tick,
}
#[repr(C)]
#[cfg(target_pointer_width = "64")] // on a 64-bit system, the tag fits in this pointer's spare 3 bits
pub struct RocEvent {
entry: RocEventEntry,
tag: RocEventTag,
}
impl Debug for RocEvent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use RocEventTag::*;
match self.tag() {
KeyDown => unsafe { self.entry().key_down }.fmt(f),
KeyUp => unsafe { self.entry().key_up }.fmt(f),
Resize => unsafe { self.entry().resize }.fmt(f),
Tick => unsafe { self.entry().tick }.fmt(f),
}
}
}
impl RocEvent {
#[cfg(target_pointer_width = "64")]
pub fn tag(&self) -> RocEventTag {
self.tag
}
pub fn entry(&self) -> &RocEventEntry {
&self.entry
}
#[allow(non_snake_case)]
pub fn Resize(size: Bounds) -> Self {
Self {
tag: RocEventTag::Resize,
entry: RocEventEntry { resize: size },
}
}
#[allow(non_snake_case)]
pub fn KeyDown(keycode: RocKeyCode) -> Self {
Self {
tag: RocEventTag::KeyDown,
entry: RocEventEntry { key_down: keycode },
}
}
#[allow(non_snake_case)]
pub fn KeyUp(keycode: RocKeyCode) -> Self {
Self {
tag: RocEventTag::KeyUp,
entry: RocEventEntry { key_up: keycode },
}
}
#[allow(non_snake_case)]
pub fn Tick(duration: Duration) -> Self {
Self {
tag: RocEventTag::Tick,
entry: RocEventEntry {
tick: duration.as_nanos().to_ne_bytes(),
},
}
}
}
#[repr(u8)]
#[allow(unused)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RocKeyCode {
Left = 0,
Other,
Right,
}
impl From<VirtualKeyCode> for RocKeyCode {
fn from(keycode: VirtualKeyCode) -> Self {
use VirtualKeyCode::*;
match keycode {
Left => RocKeyCode::Left,
Right => RocKeyCode::Right,
_ => RocKeyCode::Other,
}
}
}
#[no_mangle]
pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
return libc::malloc(size);
}
#[no_mangle]
pub unsafe extern "C" fn roc_realloc(
c_ptr: *mut c_void,
new_size: usize,
_old_size: usize,
_alignment: u32,
) -> *mut c_void {
return libc::realloc(c_ptr, new_size);
}
#[no_mangle]
pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
return libc::free(c_ptr);
}
#[no_mangle]
pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
match tag_id {
0 => {
let slice = CStr::from_ptr(c_ptr as *const c_char);
let string = slice.to_str().unwrap();
eprintln!("Roc hit a panic: {}", string);
std::process::exit(1);
}
_ => todo!(),
}
}
#[no_mangle]
pub unsafe extern "C" fn roc_memcpy(dst: *mut c_void, src: *mut c_void, n: usize) -> *mut c_void {
libc::memcpy(dst, src, n)
}
#[no_mangle]
pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void {
libc::memset(dst, c, n)
}
#[repr(transparent)]
#[cfg(target_pointer_width = "64")] // on a 64-bit system, the tag fits in this pointer's spare 3 bits
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct ElemId(*const RocElemEntry);
#[repr(C)]
pub union RocElemEntry {
pub rect: ManuallyDrop<RocRect>,
pub text: ManuallyDrop<RocStr>,
}
#[repr(u8)]
#[allow(unused)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RocElemTag {
Rect = 0,
Text = 1,
}
#[repr(C)]
#[cfg(target_pointer_width = "64")] // on a 64-bit system, the tag fits in this pointer's spare 3 bits
pub struct RocElem {
entry: RocElemEntry,
tag: RocElemTag,
}
impl Debug for RocElem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use RocElemTag::*;
match self.tag() {
Rect => unsafe { &*self.entry().rect }.fmt(f),
Text => unsafe { &*self.entry().text }.fmt(f),
}
}
}
impl RocElem {
#[cfg(target_pointer_width = "64")]
pub fn tag(&self) -> RocElemTag {
self.tag
}
#[allow(unused)]
pub fn entry(&self) -> &RocElemEntry {
&self.entry
}
#[allow(unused)]
pub fn rect(styles: ButtonStyles) -> RocElem {
todo!("restore rect() method")
// let rect = RocRect { styles };
// let entry = RocElemEntry {
// rect: ManuallyDrop::new(rect),
// };
// Self::elem_from_tag(entry, RocElemTag::Rect)
}
#[allow(unused)]
pub fn text<T: Into<RocStr>>(into_roc_str: T) -> RocElem {
todo!("TODO restore text method")
// let entry = RocElemEntry {
// text: ManuallyDrop::new(into_roc_str.into()),
// };
// Self::elem_from_tag(entry, RocElemTag::Text)
}
}
#[repr(C)]
#[derive(Debug)]
pub struct RocRect {
pub color: Rgba,
// These must be in this order for alphabetization!
pub height: f32,
pub left: f32,
pub top: f32,
pub width: f32,
}
unsafe impl ReferenceCount for RocElem {
/// Increment the reference count.
fn increment(&self) {
use RocElemTag::*;
match self.tag() {
Rect => { /* nothing to increment! */ }
Text => unsafe { &*self.entry().text }.increment(),
}
}
/// Decrement the reference count.
///
/// # Safety
///
/// The caller must ensure that `ptr` points to a value with a non-zero
/// reference count.
unsafe fn decrement(ptr: *const Self) {
use RocElemTag::*;
let elem = &*ptr;
match elem.tag() {
Rect => { /* nothing to decrement! */ }
Text => ReferenceCount::decrement(&*elem.entry().text),
}
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
pub struct ButtonStyles {
pub bg_color: Rgba,
pub border_color: Rgba,
pub border_width: f32,
pub text_color: Rgba,
}
#[derive(Copy, Clone, Debug, Default)]
#[repr(C)]
pub struct Bounds {
pub height: f32,
pub width: f32,
}
type Model = c_void;
/// Call the app's init function, then render and return that result
pub fn init_and_render(bounds: Bounds) -> (*const Model, RocList<RocElem>) {
let closure_data_buf;
let closure_layout;
// Call init to get the initial model
let model = unsafe {
let ret_val_layout = Layout::array::<u8>(init_result_size() as usize).unwrap();
// TODO allocate on the stack if it's under a certain size
let ret_val_buf = std::alloc::alloc(ret_val_layout) as *mut Model;
closure_layout = Layout::array::<u8>(init_size() as usize).unwrap();
// TODO allocate on the stack if it's under a certain size
closure_data_buf = std::alloc::alloc(closure_layout);
call_init(&bounds, closure_data_buf, ret_val_buf);
ret_val_buf
};
// Call render passing the model to get the initial Elems
let elems = unsafe {
let mut ret_val: MaybeUninit<RocList<RocElem>> = MaybeUninit::uninit();
// Reuse the buffer from the previous closure if possible
let closure_data_buf =
std::alloc::realloc(closure_data_buf, closure_layout, roc_render_size() as usize);
call_render(model, closure_data_buf, ret_val.as_mut_ptr());
std::alloc::dealloc(closure_data_buf, closure_layout);
ret_val.assume_init()
};
(model, elems)
}
/// Call the app's update function, then render and return that result
pub fn update(model: *const Model, event: RocEvent) -> *const Model {
let closure_data_buf;
let closure_layout;
// Call update to get the new model
unsafe {
let ret_val_layout = Layout::array::<u8>(update_result_size() as usize).unwrap();
// TODO allocate on the stack if it's under a certain size
let ret_val_buf = std::alloc::alloc(ret_val_layout) as *mut Model;
closure_layout = Layout::array::<u8>(update_size() as usize).unwrap();
// TODO allocate on the stack if it's under a certain size
closure_data_buf = std::alloc::alloc(closure_layout);
call_update(model, &event, closure_data_buf, ret_val_buf);
ret_val_buf
}
}
/// Call the app's update function, then render and return that result
pub fn update_and_render(model: *const Model, event: RocEvent) -> (*const Model, RocList<RocElem>) {
let closure_data_buf;
let closure_layout;
// Call update to get the new model
let model = unsafe {
let ret_val_layout = Layout::array::<u8>(update_result_size() as usize).unwrap();
// TODO allocate on the stack if it's under a certain size
let ret_val_buf = std::alloc::alloc(ret_val_layout) as *mut Model;
closure_layout = Layout::array::<u8>(update_size() as usize).unwrap();
// TODO allocate on the stack if it's under a certain size
closure_data_buf = std::alloc::alloc(closure_layout);
call_update(model, &event, closure_data_buf, ret_val_buf);
ret_val_buf
};
// Call render passing the model to get the initial Elems
let elems = unsafe {
let mut ret_val: MaybeUninit<RocList<RocElem>> = MaybeUninit::uninit();
// Reuse the buffer from the previous closure if possible
let closure_data_buf =
std::alloc::realloc(closure_data_buf, closure_layout, roc_render_size() as usize);
call_render(model, closure_data_buf, ret_val.as_mut_ptr());
std::alloc::dealloc(closure_data_buf, closure_layout);
ret_val.assume_init()
};
(model, elems)
}

1
examples/gui/platform/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
target

View File

@ -0,0 +1,172 @@
use crate::roc::{ElemId, RocElem, RocElemTag};
#[derive(Debug, PartialEq, Eq)]
pub struct Focus {
focused: Option<ElemId>,
focused_ancestors: Vec<(ElemId, usize)>,
}
impl Default for Focus {
fn default() -> Self {
Self {
focused: None,
focused_ancestors: Vec::new(),
}
}
}
impl Focus {
pub fn focused_elem(&self) -> Option<ElemId> {
self.focused
}
/// e.g. the user pressed Tab.
///
/// This is in contrast to next_local, which advances within a button group.
/// For example, if I have three radio buttons in a group, pressing the
/// arrow keys will cycle through them over and over without exiting the group -
/// whereas pressing Tab will cycle through them once and then exit the group.
pub fn next_global(&mut self, root: &RocElem) {
match self.focused {
Some(focused) => {
// while let Some((ancestor_id, index)) = self.focused_ancestors.pop() {
// let ancestor = ancestor_id.elem();
// // TODO FIXME - right now this will re-traverse a lot of ground! To prevent this,
// // we should remember past indices searched, and tell the ancestors "hey stop searching when"
// // you reach these indices, because they were already covered previously.
// // One potentially easy way to do this: pass a min_index and max_index, and only look between those!
// //
// // Related idea: instead of doing .pop() here, iterate normally so we can `break;` after storing
// // `new_ancestors = Some(next_ancestors);` - this way, we still have access to the full ancestry, and
// // can maybe even pass it in to make it clear what work has already been done!
// if let Some((new_id, new_ancestors)) =
// Self::next_focusable_sibling(focused, Some(ancestor), Some(index))
// {
// // We found the next element to focus, so record that.
// self.focused = Some(new_id);
// // We got a path to the new focusable's ancestor(s), so add them to the path.
// // (This may restore some of the ancestors we've been .pop()-ing as we iterated.)
// self.focused_ancestors.extend(new_ancestors);
// return;
// }
// // Need to write a bunch of tests for this, especially tests of focus wrapping around - e.g.
// // what happens if it wraps around to a sibling? What happens if it wraps around to something
// // higher up the tree? Lower down the tree? What if nothing is focusable?
// // A separate question: what if we should have a separate text-to-speech concept separate from focus?
// }
}
None => {
// Nothing was focused in the first place, so try to focus the root.
if root.is_focusable() {
self.focused = Some(root.id());
self.focused_ancestors = Vec::new();
} else if let Some((new_id, new_ancestors)) =
Self::next_focusable_sibling(root, None, None)
{
// If the root itself is not focusable, use its next focusable sibling.
self.focused = Some(new_id);
self.focused_ancestors = new_ancestors;
}
// Regardless of whether we found a focusable Elem, we're done.
return;
}
}
}
/// Return the next focusable sibling element after this one.
/// If this element has no siblings, or no *next* sibling after the given index
/// (e.g. the given index refers to the last element in a Row element), return None.
fn next_focusable_sibling(
elem: &RocElem,
ancestor: Option<&RocElem>,
opt_index: Option<usize>,
) -> Option<(ElemId, Vec<(ElemId, usize)>)> {
use RocElemTag::*;
match elem.tag() {
Button | Text => None,
Row | Col => {
let children = unsafe { &elem.entry().row_or_col.children.as_slice() };
let iter = match opt_index {
Some(focus_index) => children[0..focus_index].iter(),
None => children.iter(),
};
for child in iter {
if let Some(focused) = Self::next_focusable_sibling(child, ancestor, None) {
return Some(focused);
}
}
None
}
}
}
}
#[test]
fn next_global_button_root() {
use crate::roc::{ButtonStyles, RocElem};
let child = RocElem::text("");
let root = RocElem::button(ButtonStyles::default(), child);
let mut focus = Focus::default();
// At first, nothing should be focused.
assert_eq!(focus.focused_elem(), None);
focus.next_global(&root);
// Buttons should be focusable, so advancing focus should give the button focus.
assert_eq!(focus.focused_elem(), Some(root.id()));
// Since the button is at the root, advancing again should maintain focus on it.
focus.next_global(&root);
assert_eq!(focus.focused_elem(), Some(root.id()));
}
#[test]
fn next_global_text_root() {
let root = RocElem::text("");
let mut focus = Focus::default();
// At first, nothing should be focused.
assert_eq!(focus.focused_elem(), None);
focus.next_global(&root);
// Text should not be focusable, so advancing focus should have no effect here.
assert_eq!(focus.focused_elem(), None);
// Just to double-check, advancing a second time should not change this.
focus.next_global(&root);
assert_eq!(focus.focused_elem(), None);
}
#[test]
fn next_global_row() {
use crate::roc::{ButtonStyles, RocElem};
let child = RocElem::text("");
let button = RocElem::button(ButtonStyles::default(), child);
let button_id = button.id();
let root = RocElem::row(&[button] as &[_]);
let mut focus = Focus::default();
// At first, nothing should be focused.
assert_eq!(focus.focused_elem(), None);
focus.next_global(&root);
// Buttons should be focusable, so advancing focus should give the first button in the row focus.
assert_eq!(focus.focused_elem(), Some(button_id));
// Since the button is the only element in the row, advancing again should maintain focus on it.
focus.next_global(&root);
assert_eq!(focus.focused_elem(), Some(button_id));
}

View File

@ -63,7 +63,6 @@ pub enum Token {
OpAnd = 0b_0110_1101,
OpOr = 0b_0110_1110,
OpDoubleSlash = 0b_0110_1111,
OpDoublePercent = 0b_0111_0001,
OpBackpassing = 0b_0111_1010,
TodoNextThing = 0b_1000_0000,
@ -395,7 +394,6 @@ fn lex_operator(bytes: &[u8]) -> (Token, usize) {
b"&" => Token::Ampersand,
b"||" => Token::OpOr,
b"//" => Token::OpDoubleSlash,
b"%%" => Token::OpDoublePercent,
b"->" => Token::Arrow,
b"<-" => Token::OpBackpassing,
op => {

View File

@ -368,7 +368,6 @@ mod test_peg_grammar {
/ [T::OpSlash]
/ [T::OpDoubleSlash]
/ [T::OpPercent]
/ [T::OpDoublePercent]
rule mul_level_expr() =
unary_expr() (mul_level_op() unary_expr())*

View File

@ -28,5 +28,5 @@ memmap2 = "0.5.3"
object = { version = "0.26.2", features = ["read", "write"] }
serde = { version = "1.0.130", features = ["derive"] }
bincode = "1.3.3"
target-lexicon = "0.12.2"
target-lexicon = "0.12.3"
tempfile = "3.2.0"

View File

@ -86,12 +86,17 @@ enum NewtypeKind<'a> {
///
/// The returned list of newtype containers is ordered by increasing depth. As an example,
/// `A ({b : C 123})` will have the unrolled list `[Tag(A), RecordField(b), Tag(C)]`.
fn unroll_newtypes<'a>(
///
/// If we pass through aliases, the top-level alias that should be displayed to the user is passed
/// back as an option.
///
/// Returns (new type containers, optional alias content, real content).
fn unroll_newtypes_and_aliases<'a>(
env: &Env<'a, 'a>,
mut content: &'a Content,
) -> (Vec<'a, NewtypeKind<'a>>, &'a Content) {
) -> (Vec<'a, NewtypeKind<'a>>, Option<&'a Content>, &'a Content) {
let mut newtype_containers = Vec::with_capacity_in(1, env.arena);
let mut force_alias_content = None;
let mut alias_content = None;
loop {
match content {
Content::Structure(FlatType::TagUnion(tags, _))
@ -118,18 +123,19 @@ fn unroll_newtypes<'a>(
}
Content::Alias(_, _, real_var, _) => {
// We need to pass through aliases too, because their underlying types may have
// unrolled newtypes. In such cases return the list of unrolled newtypes, but keep
// the content as the alias for readability. For example,
// unrolled newtypes. For example,
// T : { a : Str }
// v : T
// v = { a : "value" }
// v
// Here we need the newtype container to be `[RecordField(a)]`, but the content to
// remain as the alias `T`.
force_alias_content = Some(content);
// Here we need the newtype container to be `[RecordField(a)]`.
//
// At the end of the day what we should show to the user is the alias content, not
// what's inside, so keep that around too.
alias_content = Some(content);
content = env.subs.get_content_without_compacting(*real_var);
}
_ => return (newtype_containers, force_alias_content.unwrap_or(content)),
_ => return (newtype_containers, alias_content, content),
}
}
}
@ -140,8 +146,8 @@ fn apply_newtypes<'a>(
mut expr: Expr<'a>,
) -> Expr<'a> {
let arena = env.arena;
// Reverse order of what we receieve from `unroll_newtypes` since we want the deepest
// container applied first.
// Reverse order of what we receieve from `unroll_newtypes_and_aliases` since
// we want the deepest container applied first.
for container in newtype_containers.into_iter().rev() {
match container {
NewtypeKind::Tag(tag_name) => {
@ -162,13 +168,6 @@ fn apply_newtypes<'a>(
expr
}
fn unroll_aliases<'a>(env: &Env<'a, 'a>, mut content: &'a Content) -> &'a Content {
while let Content::Alias(_, _, real, _) = content {
content = env.subs.get_content_without_compacting(*real);
}
content
}
fn unroll_recursion_var<'a>(env: &Env<'a, 'a>, mut content: &'a Content) -> &'a Content {
while let Content::RecursionVar { structure, .. } = content {
content = env.subs.get_content_without_compacting(*structure);
@ -278,13 +277,18 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
layout: &Layout<'a>,
content: &'a Content,
) -> Result<Expr<'a>, ToAstProblem> {
let (newtype_containers, content) = unroll_newtypes(env, content);
let content = unroll_aliases(env, content);
let (newtype_containers, alias_content, raw_content) =
unroll_newtypes_and_aliases(env, content);
macro_rules! num_helper {
($ty:ty) => {
app.call_function(main_fn_name, |_, num: $ty| {
num_to_ast(env, number_literal_to_ast(env.arena, num), content)
num_to_ast(
env,
number_literal_to_ast(env.arena, num),
// We determine the number from what the alias looks like.
alias_content.unwrap_or(raw_content),
)
})
};
}
@ -292,17 +296,17 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
let result = match layout {
Layout::Builtin(Builtin::Bool) => Ok(app
.call_function(main_fn_name, |mem: &A::Memory, num: bool| {
bool_to_ast(env, mem, num, content)
bool_to_ast(env, mem, num, raw_content)
})),
Layout::Builtin(Builtin::Int(int_width)) => {
use IntWidth::*;
let result = match (content, int_width) {
let result = match (raw_content, int_width) {
(Content::Structure(FlatType::Apply(Symbol::NUM_NUM, _)), U8) => num_helper!(u8),
(_, U8) => {
// This is not a number, it's a tag union or something else
app.call_function(main_fn_name, |mem: &A::Memory, num: u8| {
byte_to_ast(env, mem, num, content)
byte_to_ast(env, mem, num, raw_content)
})
}
// The rest are numbers... for now
@ -344,14 +348,14 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
Layout::Builtin(Builtin::List(elem_layout)) => Ok(app.call_function(
main_fn_name,
|mem: &A::Memory, (addr, len): (usize, usize)| {
list_to_ast(env, mem, addr, len, elem_layout, content)
list_to_ast(env, mem, addr, len, elem_layout, raw_content)
},
)),
Layout::Builtin(other) => {
todo!("add support for rendering builtin {:?} to the REPL", other)
}
Layout::Struct { field_layouts, .. } => {
let struct_addr_to_ast = |mem: &'a A::Memory, addr: usize| match content {
let struct_addr_to_ast = |mem: &'a A::Memory, addr: usize| match raw_content {
Content::Structure(FlatType::Record(fields, _)) => {
Ok(struct_to_ast(env, mem, addr, *fields))
}
@ -413,7 +417,14 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
main_fn_name,
size as usize,
|mem: &'a A::Memory, addr: usize| {
addr_to_ast(env, mem, addr, layout, WhenRecursive::Unreachable, content)
addr_to_ast(
env,
mem,
addr,
layout,
WhenRecursive::Unreachable,
raw_content,
)
},
))
}
@ -432,7 +443,7 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
addr,
layout,
WhenRecursive::Loop(*layout),
content,
raw_content,
)
},
))
@ -447,7 +458,14 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
main_fn_name,
size as usize,
|mem: &A::Memory, addr| {
addr_to_ast(env, mem, addr, layout, WhenRecursive::Unreachable, content)
addr_to_ast(
env,
mem,
addr,
layout,
WhenRecursive::Unreachable,
raw_content,
)
},
))
}
@ -493,9 +511,10 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
}};
}
let (newtype_containers, content) = unroll_newtypes(env, content);
let content = unroll_aliases(env, content);
let expr = match (content, layout) {
let (newtype_containers, _alias_content, raw_content) =
unroll_newtypes_and_aliases(env, content);
let expr = match (raw_content, layout) {
(Content::Structure(FlatType::Func(_, _, _)), _)
| (_, Layout::LambdaSet(_)) => OPAQUE_FUNCTION,
(_, Layout::Builtin(Builtin::Bool)) => {
@ -503,7 +522,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
// num is always false at the moment.
let num: bool = mem.deref_bool(addr);
bool_to_ast(env, mem, num, content)
bool_to_ast(env, mem, num, raw_content)
}
(_, Layout::Builtin(Builtin::Int(int_width))) => {
use IntWidth::*;
@ -534,14 +553,14 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
let elem_addr = mem.deref_usize(addr);
let len = mem.deref_usize(addr + env.target_info.ptr_width() as usize);
list_to_ast(env, mem, elem_addr, len, elem_layout, content)
list_to_ast(env, mem, elem_addr, len, elem_layout, raw_content)
}
(_, Layout::Builtin(Builtin::Str)) => {
let string = mem.deref_str(addr);
let arena_str = env.arena.alloc_str(string);
Expr::Str(StrLiteral::PlainLine(arena_str))
}
(_, Layout::Struct{field_layouts, ..}) => match content {
(_, Layout::Struct{field_layouts, ..}) => match raw_content {
Content::Structure(FlatType::Record(fields, _)) => {
struct_to_ast(env, mem, addr, *fields)
}
@ -566,7 +585,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
}
},
(_, Layout::RecursivePointer) => {
match (content, when_recursive) {
match (raw_content, when_recursive) {
(Content::RecursionVar {
structure,
opt_name: _,
@ -580,7 +599,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
(_, Layout::Union(UnionLayout::NonRecursive(union_layouts))) => {
let union_layout = UnionLayout::NonRecursive(union_layouts);
let tags = match content {
let tags = match raw_content {
Content::Structure(FlatType::TagUnion(tags, _)) => tags,
other => unreachable!("Weird content for nonrecursive Union layout: {:?}", other),
};
@ -614,7 +633,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
)
}
(_, Layout::Union(union_layout @ UnionLayout::Recursive(union_layouts))) => {
let (rec_var, tags) = match content {
let (rec_var, tags) = match raw_content {
Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags),
_ => unreachable!("any other content would have a different layout"),
};
@ -644,7 +663,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
)
}
(_, Layout::Union(UnionLayout::NonNullableUnwrapped(_))) => {
let (rec_var, tags) = match unroll_recursion_var(env, content) {
let (rec_var, tags) = match unroll_recursion_var(env, raw_content) {
Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags),
other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other),
};
@ -672,7 +691,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
)
}
(_, Layout::Union(UnionLayout::NullableUnwrapped { .. })) => {
let (rec_var, tags) = match unroll_recursion_var(env, content) {
let (rec_var, tags) = match unroll_recursion_var(env, raw_content) {
Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags),
other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other),
};
@ -706,7 +725,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
}
}
(_, Layout::Union(union_layout @ UnionLayout::NullableWrapped { .. })) => {
let (rec_var, tags) = match unroll_recursion_var(env, content) {
let (rec_var, tags) = match unroll_recursion_var(env, raw_content) {
Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags),
other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other),
};
@ -803,7 +822,8 @@ fn list_to_ast<'a, M: ReplAppMemory>(
for index in 0..len {
let offset_bytes = index * elem_size;
let elem_addr = addr + offset_bytes;
let (newtype_containers, elem_content) = unroll_newtypes(env, elem_content);
let (newtype_containers, _alias_content, elem_content) =
unroll_newtypes_and_aliases(env, elem_content);
let expr = addr_to_ast(
env,
mem,

View File

@ -56,7 +56,7 @@ fn float_addition() {
#[cfg(not(feature = "wasm"))]
#[test]
fn num_rem() {
expect_success("299 % 10", "Ok 9 : Result (Int *) [ DivByZero ]*");
expect_success("299 % 10", "9 : Int *");
}
#[cfg(not(feature = "wasm"))]
@ -1145,3 +1145,42 @@ fn issue_2818() {
r"<function> : {} -> List Str",
)
}
#[test]
fn issue_2810_recursive_layout_inside_nonrecursive() {
expect_success(
indoc!(
r#"
Command : [ Command Tool ]
Job : [ Job Command ]
Tool : [ SystemTool, FromJob Job ]
a : Job
a = Job (Command (FromJob (Job (Command SystemTool))))
a
"#
),
"Job (Command (FromJob (Job (Command SystemTool)))) : Job",
)
}
#[test]
fn render_nullable_unwrapped_passing_through_alias() {
expect_success(
indoc!(
r#"
Deep : [ L DeepList ]
DeepList : [ Nil, Cons Deep ]
v : DeepList
v = (Cons (L (Cons (L (Cons (L Nil))))))
v
"#
),
"Cons (L (Cons (L (Cons (L Nil))))) : DeepList",
)
}

View File

@ -7128,26 +7128,23 @@ I need all branches in an `if` to have the same type!
indoc!(
r#"
TYPE MISMATCH /code/proj/Main.roc
Something is off with the body of the `f` definition:
1 f : a, b, * -> *
2 f = \_, _, x2 ->
Something is off with the body of the `inner` definition:
3 inner : * -> *
4 inner = \y -> y
5 inner x2
^^^^^^^^
The type annotation on `f` says this `inner` call should have the type:
^
The type annotation on `inner` says this `y` value should have the type:
*
However, the type of this `inner` call is connected to another type in a
However, the type of this `y` value is connected to another type in a
way that isn't reflected in this annotation.
Tip: Any connection between types must use a named type variable, not
a `*`! Maybe the annotation on `f` should have a named type variable in
place of the `*`?
a `*`! Maybe the annotation on `inner` should have a named type variable
in place of the `*`?
"#
),
)
@ -10039,4 +10036,23 @@ I need all branches in an `if` to have the same type!
),
)
}
#[test]
fn always_function() {
// from https://github.com/rtfeldman/roc/commit/1372737f5e53ee5bb96d7e1b9593985e5537023a
// There was a bug where this reported UnusedArgument("val")
// since it was used only in the returned function only.
//
// we want this to not give any warnings/errors!
report_problem_as(
indoc!(
r#"
always = \val -> \_ -> val
always
"#
),
"",
)
}
}

View File

@ -1297,7 +1297,6 @@ Here are various Roc expressions involving operators, and what they desugar to.
| `a // b` | `Num.divTrunc a b` |
| `a ^ b` | `Num.pow a b` |
| `a % b` | `Num.rem a b` |
| `a %% b` | `Num.mod a b` |
| `a >> b` | `Num.shr a b` |
| `a << b` | `Num.shl a b` |
| `-a` | `Num.neg a` |

252
roc_std/Cargo.lock generated Normal file
View File

@ -0,0 +1,252 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "ctor"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "diff"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
[[package]]
name = "env_logger"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3"
dependencies = [
"log",
"regex",
]
[[package]]
name = "getrandom"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "indoc"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7906a9fababaeacb774f72410e497a1d18de916322e33797bb2cd29baa23c9e"
dependencies = [
"unindent",
]
[[package]]
name = "libc"
version = "0.2.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "output_vt100"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66"
dependencies = [
"winapi",
]
[[package]]
name = "pretty_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76d5b548b725018ab5496482b45cb8bef21e9fed1858a6d674e3a8a0f0bb5d50"
dependencies = [
"ansi_term",
"ctor",
"diff",
"output_vt100",
]
[[package]]
name = "proc-macro2"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quickcheck"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6"
dependencies = [
"env_logger",
"log",
"rand",
]
[[package]]
name = "quickcheck_macros"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b22a693222d716a9587786f37ac3f6b4faedb5b80c23914e7303ff5a1d8016e9"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "quote"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [
"getrandom",
]
[[package]]
name = "regex"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "roc_std"
version = "0.1.0"
dependencies = [
"indoc",
"libc",
"pretty_assertions",
"quickcheck",
"quickcheck_macros",
]
[[package]]
name = "syn"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "unindent"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "514672a55d7380da379785a4d70ca8386c8883ff7eaae877be4d2081cebe73d8"
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@ -63,7 +63,7 @@ where
let new_size = elements_offset + core::mem::size_of::<T>() * (self.len() + slice.len());
let new_ptr = if let Some((elements, storage)) = self.elements_and_storage() {
// Decrement the lists refence count.
// Decrement the list's refence count.
let mut copy = storage.get();
let is_unique = copy.decrease();
@ -275,6 +275,15 @@ where
}
}
impl<T> From<&[T]> for RocList<T>
where
T: ReferenceCount,
{
fn from(slice: &[T]) -> Self {
Self::from_slice(slice)
}
}
impl<T> IntoIterator for RocList<T>
where
T: ReferenceCount,