mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 23:31:12 +00:00
Merge remote-tracking branch 'origin/can-builtins-simplify' into list-range
This commit is contained in:
commit
f2c144f58c
55 changed files with 2303 additions and 1059 deletions
|
@ -4,6 +4,8 @@ const RocResult = utils.RocResult;
|
|||
const mem = std.mem;
|
||||
const Allocator = mem.Allocator;
|
||||
|
||||
const TAG_WIDTH = 8;
|
||||
|
||||
const EqFn = fn (?[*]u8, ?[*]u8) callconv(.C) bool;
|
||||
const Opaque = ?[*]u8;
|
||||
|
||||
|
@ -502,6 +504,49 @@ pub fn listWalkBackwards(list: RocList, stepper: Opaque, stepper_caller: Caller2
|
|||
utils.decref(std.heap.c_allocator, alignment, list.bytes, data_bytes);
|
||||
}
|
||||
|
||||
pub fn listWalkUntil(list: RocList, stepper: Opaque, stepper_caller: Caller2, accum: Opaque, alignment: usize, element_width: usize, accum_width: usize, dec: Dec, output: Opaque) callconv(.C) void {
|
||||
// [ Continue a, Stop a ]
|
||||
const CONTINUE: usize = 0;
|
||||
|
||||
if (accum_width == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (list.isEmpty()) {
|
||||
@memcpy(output orelse unreachable, accum orelse unreachable, accum_width);
|
||||
return;
|
||||
}
|
||||
|
||||
const alloc: [*]u8 = @ptrCast([*]u8, std.heap.c_allocator.alloc(u8, TAG_WIDTH + accum_width) catch unreachable);
|
||||
|
||||
@memcpy(alloc + TAG_WIDTH, accum orelse unreachable, accum_width);
|
||||
|
||||
if (list.bytes) |source_ptr| {
|
||||
var i: usize = 0;
|
||||
const size = list.len();
|
||||
while (i < size) : (i += 1) {
|
||||
const element = source_ptr + i * element_width;
|
||||
stepper_caller(stepper, element, alloc + TAG_WIDTH, alloc);
|
||||
|
||||
const usizes: [*]usize = @ptrCast([*]usize, @alignCast(8, alloc));
|
||||
if (usizes[0] != 0) {
|
||||
// decrement refcount of the remaining items
|
||||
i += 1;
|
||||
while (i < size) : (i += 1) {
|
||||
dec(source_ptr + i * element_width);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@memcpy(output orelse unreachable, alloc + TAG_WIDTH, accum_width);
|
||||
std.heap.c_allocator.free(alloc[0 .. TAG_WIDTH + accum_width]);
|
||||
|
||||
const data_bytes = list.len() * element_width;
|
||||
utils.decref(std.heap.c_allocator, alignment, list.bytes, data_bytes);
|
||||
}
|
||||
|
||||
// List.contains : List k, k -> Bool
|
||||
pub fn listContains(list: RocList, key: Opaque, key_width: usize, is_eq: EqFn) callconv(.C) bool {
|
||||
if (list.bytes) |source_ptr| {
|
||||
|
|
|
@ -12,6 +12,7 @@ comptime {
|
|||
exportListFn(list.listMapWithIndex, "map_with_index");
|
||||
exportListFn(list.listKeepIf, "keep_if");
|
||||
exportListFn(list.listWalk, "walk");
|
||||
exportListFn(list.listWalkUntil, "walkUntil");
|
||||
exportListFn(list.listWalkBackwards, "walk_backwards");
|
||||
exportListFn(list.listKeepOks, "keep_oks");
|
||||
exportListFn(list.listKeepErrs, "keep_errs");
|
||||
|
|
|
@ -195,7 +195,7 @@ interface List2
|
|||
## * Even when copying is faster, other list operations may still be slightly slower with persistent data structures. For example, even if it were a persistent data structure, #List.map, #List.fold, and #List.keepIf would all need to traverse every element in the list and build up the result from scratch. These operations are all
|
||||
## * Roc's compiler optimizes many list operations into in-place mutations behind the scenes, depending on how the list is being used. For example, #List.map, #List.keepIf, and #List.set can all be optimized to perform in-place mutations.
|
||||
## * If possible, it is usually best for performance to use large lists in a way where the optimizer can turn them into in-place mutations. If this is not possible, a persistent data structure might be faster - but this is a rare enough scenario that it would not be good for the average Roc program's performance if this were the way #List worked by default. Instead, you can look outside Roc's standard modules for an implementation of a persistent data structure - likely built using #List under the hood!
|
||||
List elem : @List elem
|
||||
List elem : [ @List elem ]
|
||||
|
||||
## Initialize
|
||||
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
interface Set2
|
||||
exposes [ empty, isEmpty, len, add, drop, map ]
|
||||
interface Set
|
||||
exposes [ Set, empty, isEmpty, len, add, drop, map ]
|
||||
imports []
|
||||
|
||||
## Set
|
||||
|
||||
## A Set is an unordered collection of unique elements.
|
||||
Set elem : [ @Set elem ]
|
||||
|
||||
## An empty set.
|
||||
empty : Set *
|
||||
|
|
|
@ -70,6 +70,7 @@ pub const LIST_KEEP_IF: &str = "roc_builtins.list.keep_if";
|
|||
pub const LIST_KEEP_OKS: &str = "roc_builtins.list.keep_oks";
|
||||
pub const LIST_KEEP_ERRS: &str = "roc_builtins.list.keep_errs";
|
||||
pub const LIST_WALK: &str = "roc_builtins.list.walk";
|
||||
pub const LIST_WALK_UNTIL: &str = "roc_builtins.list.walkUntil";
|
||||
pub const LIST_WALK_BACKWARDS: &str = "roc_builtins.list.walk_backwards";
|
||||
pub const LIST_CONTAINS: &str = "roc_builtins.list.contains";
|
||||
pub const LIST_REPEAT: &str = "roc_builtins.list.repeat";
|
||||
|
|
|
@ -771,6 +771,34 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
|||
),
|
||||
);
|
||||
|
||||
fn until_type(content: SolvedType) -> SolvedType {
|
||||
// [ LT, EQ, GT ]
|
||||
SolvedType::TagUnion(
|
||||
vec![
|
||||
(TagName::Global("Continue".into()), vec![content.clone()]),
|
||||
(TagName::Global("Stop".into()), vec![content]),
|
||||
],
|
||||
Box::new(SolvedType::EmptyTagUnion),
|
||||
)
|
||||
}
|
||||
|
||||
// walkUntil : List elem, (elem -> accum -> [ Continue accum, Stop accum ]), accum -> accum
|
||||
add_type(
|
||||
Symbol::LIST_WALK_UNTIL,
|
||||
top_level_function(
|
||||
vec![
|
||||
list_type(flex(TVAR1)),
|
||||
closure(
|
||||
vec![flex(TVAR1), flex(TVAR2)],
|
||||
TVAR3,
|
||||
Box::new(until_type(flex(TVAR2))),
|
||||
),
|
||||
flex(TVAR2),
|
||||
],
|
||||
Box::new(flex(TVAR2)),
|
||||
),
|
||||
);
|
||||
|
||||
// keepIf : List elem, (elem -> Bool) -> List elem
|
||||
add_type(
|
||||
Symbol::LIST_KEEP_IF,
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::def::Def;
|
|||
use crate::expr::Expr::*;
|
||||
use crate::expr::{Expr, Recursive, WhenBranch};
|
||||
use crate::pattern::Pattern;
|
||||
use roc_collections::all::{MutMap, SendMap};
|
||||
use roc_collections::all::SendMap;
|
||||
use roc_module::ident::TagName;
|
||||
use roc_module::low_level::LowLevel;
|
||||
use roc_module::operator::CalledVia;
|
||||
|
@ -10,23 +10,6 @@ use roc_module::symbol::Symbol;
|
|||
use roc_region::all::{Located, Region};
|
||||
use roc_types::subs::{VarStore, Variable};
|
||||
|
||||
macro_rules! defs {
|
||||
(@single $($x:tt)*) => (());
|
||||
(@count $($rest:expr),*) => (<[()]>::len(&[$(defs!(@single $rest)),*]));
|
||||
|
||||
($var_store:expr; $($key:expr => $func:expr,)+) => { defs!($var_store; $($key => $func),+) };
|
||||
($var_store:expr; $($key:expr => $func:expr),*) => {
|
||||
{
|
||||
let _cap = defs!(@count $($key),*);
|
||||
let mut _map = ::std::collections::HashMap::with_capacity_and_hasher(_cap, roc_collections::all::default_hasher());
|
||||
$(
|
||||
let _ = _map.insert($key, $func($key, $var_store));
|
||||
)*
|
||||
_map
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! macro_magic {
|
||||
(@single $($x:tt)*) => (());
|
||||
(@count $($rest:expr),*) => (<[()]>::len(&[$(matches!(@single $rest)),*]));
|
||||
|
@ -44,6 +27,23 @@ macro_rules! macro_magic {
|
|||
};
|
||||
}
|
||||
|
||||
/// Some builtins cannot be constructed in code gen alone, and need to be defined
|
||||
/// as separate Roc defs. For example, List.get has this type:
|
||||
///
|
||||
/// List.get : List elem, Int -> Result elem [ OutOfBounds ]*
|
||||
///
|
||||
/// Because this returns an open tag union for its Err type, it's not possible
|
||||
/// for code gen to return a hardcoded value for OutOfBounds. For example,
|
||||
/// if this Result unifies to [ Foo, OutOfBounds ] then OutOfBOunds will
|
||||
/// get assigned the number 1 (because Foo got 0 alphabetically), whereas
|
||||
/// if it unifies to [ OutOfBounds, Qux ] then OutOfBounds will get the number 0.
|
||||
///
|
||||
/// Getting these numbers right requires having List.get participate in the
|
||||
/// normal type-checking and monomorphization processes. As such, this function
|
||||
/// returns a normal def for List.get, which performs a bounds check and then
|
||||
/// delegates to the compiler-internal List.getUnsafe function to do the actual
|
||||
/// lookup (if the bounds check passed). That internal function is hardcoded in code gen,
|
||||
/// which works fine because it doesn't involve any open tag unions.
|
||||
pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def> {
|
||||
debug_assert!(symbol.is_builtin());
|
||||
|
||||
|
@ -90,6 +90,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
|
|||
LIST_RANGE => list_range,
|
||||
LIST_WALK => list_walk,
|
||||
LIST_WALK_BACKWARDS => list_walk_backwards,
|
||||
LIST_WALK_UNTIL => list_walk_until,
|
||||
DICT_TEST_HASH => dict_hash_test_only,
|
||||
DICT_LEN => dict_len,
|
||||
DICT_EMPTY => dict_empty,
|
||||
|
@ -172,145 +173,6 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
|
|||
}
|
||||
}
|
||||
|
||||
/// Some builtins cannot be constructed in code gen alone, and need to be defined
|
||||
/// as separate Roc defs. For example, List.get has this type:
|
||||
///
|
||||
/// List.get : List elem, Int -> Result elem [ OutOfBounds ]*
|
||||
///
|
||||
/// Because this returns an open tag union for its Err type, it's not possible
|
||||
/// for code gen to return a hardcoded value for OutOfBounds. For example,
|
||||
/// if this Result unifies to [ Foo, OutOfBounds ] then OutOfBOunds will
|
||||
/// get assigned the number 1 (because Foo got 0 alphabetically), whereas
|
||||
/// if it unifies to [ OutOfBounds, Qux ] then OutOfBounds will get the number 0.
|
||||
///
|
||||
/// Getting these numbers right requires having List.get participate in the
|
||||
/// normal type-checking and monomorphization processes. As such, this function
|
||||
/// returns a normal def for List.get, which performs a bounds check and then
|
||||
/// delegates to the compiler-internal List.getUnsafe function to do the actual
|
||||
/// lookup (if the bounds check passed). That internal function is hardcoded in code gen,
|
||||
/// which works fine because it doesn't involve any open tag unions.
|
||||
pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
|
||||
defs! { var_store;
|
||||
Symbol::BOOL_EQ => bool_eq,
|
||||
Symbol::BOOL_NEQ => bool_neq,
|
||||
Symbol::BOOL_AND => bool_and,
|
||||
Symbol::BOOL_OR => bool_or,
|
||||
Symbol::BOOL_NOT => bool_not,
|
||||
Symbol::STR_CONCAT => str_concat,
|
||||
Symbol::STR_JOIN_WITH => str_join_with,
|
||||
Symbol::STR_SPLIT => str_split,
|
||||
Symbol::STR_IS_EMPTY => str_is_empty,
|
||||
Symbol::STR_STARTS_WITH => str_starts_with,
|
||||
Symbol::STR_ENDS_WITH => str_ends_with,
|
||||
Symbol::STR_COUNT_GRAPHEMES => str_count_graphemes,
|
||||
Symbol::STR_FROM_INT => str_from_int,
|
||||
Symbol::STR_FROM_UTF8 => str_from_utf8,
|
||||
Symbol::STR_TO_BYTES => str_to_bytes,
|
||||
Symbol::STR_FROM_FLOAT=> str_from_float,
|
||||
Symbol::LIST_LEN => list_len,
|
||||
Symbol::LIST_GET => list_get,
|
||||
Symbol::LIST_SET => list_set,
|
||||
Symbol::LIST_APPEND => list_append,
|
||||
Symbol::LIST_FIRST => list_first,
|
||||
Symbol::LIST_LAST => list_last,
|
||||
Symbol::LIST_IS_EMPTY => list_is_empty,
|
||||
Symbol::LIST_SINGLE => list_single,
|
||||
Symbol::LIST_REPEAT => list_repeat,
|
||||
Symbol::LIST_REVERSE => list_reverse,
|
||||
Symbol::LIST_CONCAT => list_concat,
|
||||
Symbol::LIST_CONTAINS => list_contains,
|
||||
Symbol::LIST_SUM => list_sum,
|
||||
Symbol::LIST_PRODUCT => list_product,
|
||||
Symbol::LIST_PREPEND => list_prepend,
|
||||
Symbol::LIST_JOIN => list_join,
|
||||
Symbol::LIST_MAP => list_map,
|
||||
Symbol::LIST_MAP2 => list_map2,
|
||||
Symbol::LIST_MAP3 => list_map3,
|
||||
Symbol::LIST_MAP_WITH_INDEX => list_map_with_index,
|
||||
Symbol::LIST_KEEP_IF => list_keep_if,
|
||||
Symbol::LIST_KEEP_OKS => list_keep_oks,
|
||||
Symbol::LIST_KEEP_ERRS=> list_keep_errs,
|
||||
Symbol::LIST_RANGE => list_range,
|
||||
Symbol::LIST_WALK => list_walk,
|
||||
Symbol::LIST_WALK_BACKWARDS => list_walk_backwards,
|
||||
Symbol::DICT_TEST_HASH => dict_hash_test_only,
|
||||
Symbol::DICT_LEN => dict_len,
|
||||
Symbol::DICT_EMPTY => dict_empty,
|
||||
Symbol::DICT_SINGLE => dict_single,
|
||||
Symbol::DICT_INSERT => dict_insert,
|
||||
Symbol::DICT_REMOVE => dict_remove,
|
||||
Symbol::DICT_GET => dict_get,
|
||||
Symbol::DICT_CONTAINS => dict_contains,
|
||||
Symbol::DICT_KEYS => dict_keys,
|
||||
Symbol::DICT_VALUES => dict_values,
|
||||
Symbol::DICT_UNION=> dict_union,
|
||||
Symbol::DICT_INTERSECTION=> dict_intersection,
|
||||
Symbol::DICT_DIFFERENCE=> dict_difference,
|
||||
Symbol::DICT_WALK=> dict_walk,
|
||||
Symbol::SET_EMPTY => set_empty,
|
||||
Symbol::SET_LEN => set_len,
|
||||
Symbol::SET_SINGLE => set_single,
|
||||
Symbol::SET_UNION=> set_union,
|
||||
Symbol::SET_INTERSECTION=> set_intersection,
|
||||
Symbol::SET_DIFFERENCE=> set_difference,
|
||||
Symbol::SET_TO_LIST => set_to_list,
|
||||
Symbol::SET_FROM_LIST => set_from_list,
|
||||
Symbol::SET_INSERT => set_insert,
|
||||
Symbol::SET_REMOVE => set_remove,
|
||||
Symbol::SET_CONTAINS => set_contains,
|
||||
Symbol::SET_WALK => set_walk,
|
||||
Symbol::NUM_ADD => num_add,
|
||||
Symbol::NUM_ADD_CHECKED => num_add_checked,
|
||||
Symbol::NUM_ADD_WRAP => num_add_wrap,
|
||||
Symbol::NUM_SUB => num_sub,
|
||||
Symbol::NUM_MUL => num_mul,
|
||||
Symbol::NUM_GT => num_gt,
|
||||
Symbol::NUM_GTE => num_gte,
|
||||
Symbol::NUM_LT => num_lt,
|
||||
Symbol::NUM_LTE => num_lte,
|
||||
Symbol::NUM_COMPARE => num_compare,
|
||||
Symbol::NUM_SIN => num_sin,
|
||||
Symbol::NUM_COS => num_cos,
|
||||
Symbol::NUM_TAN => num_tan,
|
||||
Symbol::NUM_DIV_FLOAT => num_div_float,
|
||||
Symbol::NUM_DIV_INT => num_div_int,
|
||||
Symbol::NUM_ABS => num_abs,
|
||||
Symbol::NUM_NEG => num_neg,
|
||||
Symbol::NUM_REM => num_rem,
|
||||
Symbol::NUM_IS_MULTIPLE_OF => num_is_multiple_of,
|
||||
Symbol::NUM_SQRT => num_sqrt,
|
||||
Symbol::NUM_LOG => num_log,
|
||||
Symbol::NUM_ROUND => num_round,
|
||||
Symbol::NUM_IS_ODD => num_is_odd,
|
||||
Symbol::NUM_IS_EVEN => num_is_even,
|
||||
Symbol::NUM_IS_ZERO => num_is_zero,
|
||||
Symbol::NUM_IS_POSITIVE => num_is_positive,
|
||||
Symbol::NUM_IS_NEGATIVE => num_is_negative,
|
||||
Symbol::NUM_TO_FLOAT => num_to_float,
|
||||
Symbol::NUM_POW => num_pow,
|
||||
Symbol::NUM_CEILING => num_ceiling,
|
||||
Symbol::NUM_POW_INT => num_pow_int,
|
||||
Symbol::NUM_FLOOR => num_floor,
|
||||
Symbol::NUM_ATAN => num_atan,
|
||||
Symbol::NUM_ACOS => num_acos,
|
||||
Symbol::NUM_ASIN => num_asin,
|
||||
Symbol::NUM_MAX_INT => num_max_int,
|
||||
Symbol::NUM_MIN_INT => num_min_int,
|
||||
Symbol::NUM_BITWISE_AND => num_bitwise_and,
|
||||
Symbol::NUM_BITWISE_XOR => num_bitwise_xor,
|
||||
Symbol::NUM_BITWISE_OR => num_bitwise_or,
|
||||
Symbol::NUM_SHIFT_LEFT => num_shift_left_by,
|
||||
Symbol::NUM_SHIFT_RIGHT => num_shift_right_by,
|
||||
Symbol::NUM_SHIFT_RIGHT_ZERO_FILL => num_shift_right_zf_by,
|
||||
Symbol::NUM_INT_CAST=> num_int_cast,
|
||||
Symbol::NUM_MAX_I128=> num_max_i128,
|
||||
Symbol::RESULT_MAP => result_map,
|
||||
Symbol::RESULT_MAP_ERR => result_map_err,
|
||||
Symbol::RESULT_AFTER => result_after,
|
||||
Symbol::RESULT_WITH_DEFAULT => result_with_default,
|
||||
}
|
||||
}
|
||||
|
||||
fn lowlevel_1(symbol: Symbol, op: LowLevel, var_store: &mut VarStore) -> Def {
|
||||
let arg1_var = var_store.fresh();
|
||||
let ret_var = var_store.fresh();
|
||||
|
@ -2096,70 +1958,101 @@ fn list_join(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
|
||||
/// List.walk : List elem, (elem -> accum -> accum), accum -> accum
|
||||
fn list_walk(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
lowlevel_3(symbol, LowLevel::ListWalk, var_store)
|
||||
}
|
||||
|
||||
/// List.walkBackwards : List elem, (elem -> accum -> accum), accum -> accum
|
||||
fn list_walk_backwards(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
lowlevel_3(symbol, LowLevel::ListWalkBackwards, var_store)
|
||||
}
|
||||
|
||||
/// List.walkUntil : List elem, (elem, accum -> [ Continue accum, Stop accum ]), accum -> accum
|
||||
fn list_walk_until(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
lowlevel_3(symbol, LowLevel::ListWalkUntil, var_store)
|
||||
}
|
||||
|
||||
/// List.sum : List (Num a) -> Num a
|
||||
fn list_sum(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let num_var = var_store.fresh();
|
||||
let ret_var = num_var;
|
||||
let list_var = var_store.fresh();
|
||||
let func_var = var_store.fresh();
|
||||
let accum_var = var_store.fresh();
|
||||
let closure_var = var_store.fresh();
|
||||
|
||||
let body = RunLowLevel {
|
||||
op: LowLevel::ListWalk,
|
||||
args: vec![
|
||||
(list_var, Var(Symbol::ARG_1)),
|
||||
(func_var, Var(Symbol::ARG_2)),
|
||||
(accum_var, Var(Symbol::ARG_3)),
|
||||
(closure_var, list_sum_add(num_var, var_store)),
|
||||
(num_var, Num(var_store.fresh(), 0)),
|
||||
],
|
||||
ret_var: accum_var,
|
||||
ret_var,
|
||||
};
|
||||
|
||||
defn(
|
||||
symbol,
|
||||
vec![
|
||||
(list_var, Symbol::ARG_1),
|
||||
(func_var, Symbol::ARG_2),
|
||||
(accum_var, Symbol::ARG_3),
|
||||
],
|
||||
vec![(list_var, Symbol::ARG_1)],
|
||||
var_store,
|
||||
body,
|
||||
accum_var,
|
||||
ret_var,
|
||||
)
|
||||
}
|
||||
|
||||
/// List.walkBackwards : List elem, (elem -> accum -> accum), accum -> accum
|
||||
fn list_walk_backwards(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let list_var = var_store.fresh();
|
||||
let func_var = var_store.fresh();
|
||||
let accum_var = var_store.fresh();
|
||||
|
||||
fn list_sum_add(num_var: Variable, var_store: &mut VarStore) -> Expr {
|
||||
let body = RunLowLevel {
|
||||
op: LowLevel::ListWalkBackwards,
|
||||
args: vec![
|
||||
(list_var, Var(Symbol::ARG_1)),
|
||||
(func_var, Var(Symbol::ARG_2)),
|
||||
(accum_var, Var(Symbol::ARG_3)),
|
||||
],
|
||||
ret_var: accum_var,
|
||||
op: LowLevel::NumAdd,
|
||||
args: vec![(num_var, Var(Symbol::ARG_3)), (num_var, Var(Symbol::ARG_4))],
|
||||
ret_var: num_var,
|
||||
};
|
||||
|
||||
defn(
|
||||
symbol,
|
||||
vec![
|
||||
(list_var, Symbol::ARG_1),
|
||||
(func_var, Symbol::ARG_2),
|
||||
(accum_var, Symbol::ARG_3),
|
||||
],
|
||||
defn_help(
|
||||
Symbol::LIST_SUM_ADD,
|
||||
vec![(num_var, Symbol::ARG_3), (num_var, Symbol::ARG_4)],
|
||||
var_store,
|
||||
body,
|
||||
accum_var,
|
||||
num_var,
|
||||
)
|
||||
}
|
||||
|
||||
/// List.sum : List (Num a) -> Num a
|
||||
fn list_sum(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
lowlevel_1(symbol, LowLevel::ListSum, var_store)
|
||||
}
|
||||
|
||||
/// List.product : List (Num a) -> Num a
|
||||
fn list_product(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
lowlevel_1(symbol, LowLevel::ListProduct, var_store)
|
||||
let num_var = var_store.fresh();
|
||||
let ret_var = num_var;
|
||||
let list_var = var_store.fresh();
|
||||
let closure_var = var_store.fresh();
|
||||
|
||||
let body = RunLowLevel {
|
||||
op: LowLevel::ListWalk,
|
||||
args: vec![
|
||||
(list_var, Var(Symbol::ARG_1)),
|
||||
(closure_var, list_product_mul(num_var, var_store)),
|
||||
(num_var, Num(var_store.fresh(), 1)),
|
||||
],
|
||||
ret_var,
|
||||
};
|
||||
|
||||
defn(
|
||||
symbol,
|
||||
vec![(list_var, Symbol::ARG_1)],
|
||||
var_store,
|
||||
body,
|
||||
ret_var,
|
||||
)
|
||||
}
|
||||
|
||||
fn list_product_mul(num_var: Variable, var_store: &mut VarStore) -> Expr {
|
||||
let body = RunLowLevel {
|
||||
op: LowLevel::NumMul,
|
||||
args: vec![(num_var, Var(Symbol::ARG_3)), (num_var, Var(Symbol::ARG_4))],
|
||||
ret_var: num_var,
|
||||
};
|
||||
|
||||
defn_help(
|
||||
Symbol::LIST_PRODUCT_MUL,
|
||||
vec![(num_var, Symbol::ARG_3), (num_var, Symbol::ARG_4)],
|
||||
var_store,
|
||||
body,
|
||||
num_var,
|
||||
)
|
||||
}
|
||||
|
||||
/// List.keepIf : List elem, (elem -> Bool) -> List elem
|
||||
|
@ -3388,24 +3281,7 @@ fn defn(
|
|||
body: Expr,
|
||||
ret_var: Variable,
|
||||
) -> Def {
|
||||
use crate::pattern::Pattern::*;
|
||||
|
||||
let closure_args = args
|
||||
.into_iter()
|
||||
.map(|(var, symbol)| (var, no_region(Identifier(symbol))))
|
||||
.collect();
|
||||
|
||||
let expr = Closure {
|
||||
function_type: var_store.fresh(),
|
||||
closure_type: var_store.fresh(),
|
||||
closure_ext_var: var_store.fresh(),
|
||||
return_type: ret_var,
|
||||
name: fn_name,
|
||||
captured_symbols: Vec::new(),
|
||||
recursive: Recursive::NotRecursive,
|
||||
arguments: closure_args,
|
||||
loc_body: Box::new(no_region(body)),
|
||||
};
|
||||
let expr = defn_help(fn_name, args, var_store, body, ret_var);
|
||||
|
||||
Def {
|
||||
loc_pattern: Located {
|
||||
|
@ -3421,3 +3297,31 @@ fn defn(
|
|||
annotation: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn defn_help(
|
||||
fn_name: Symbol,
|
||||
args: Vec<(Variable, Symbol)>,
|
||||
var_store: &mut VarStore,
|
||||
body: Expr,
|
||||
ret_var: Variable,
|
||||
) -> Expr {
|
||||
use crate::pattern::Pattern::*;
|
||||
|
||||
let closure_args = args
|
||||
.into_iter()
|
||||
.map(|(var, symbol)| (var, no_region(Identifier(symbol))))
|
||||
.collect();
|
||||
|
||||
Closure {
|
||||
function_type: var_store.fresh(),
|
||||
closure_type: var_store.fresh(),
|
||||
closure_ext_var: var_store.fresh(),
|
||||
return_type: ret_var,
|
||||
name: fn_name,
|
||||
captured_symbols: Vec::new(),
|
||||
recursive: Recursive::NotRecursive,
|
||||
arguments: closure_args,
|
||||
loc_body: Box::new(no_region(body)),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::annotation::IntroducedVariables;
|
||||
use crate::builtins::builtin_defs;
|
||||
use crate::builtins::builtin_defs_map;
|
||||
use crate::def::{can_defs_with_return, Def};
|
||||
use crate::env::Env;
|
||||
use crate::num::{
|
||||
|
@ -1380,7 +1380,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
|
|||
let (fn_var, loc_expr, closure_var, expr_var) = *boxed_tuple;
|
||||
|
||||
match loc_expr.value {
|
||||
Var(symbol) if symbol.is_builtin() => match builtin_defs(var_store).get(&symbol) {
|
||||
Var(symbol) if symbol.is_builtin() => match builtin_defs_map(symbol, var_store) {
|
||||
Some(Def {
|
||||
loc_expr:
|
||||
Located {
|
||||
|
@ -1395,7 +1395,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
|
|||
},
|
||||
..
|
||||
}) => {
|
||||
debug_assert_eq!(*recursive, Recursive::NotRecursive);
|
||||
debug_assert_eq!(recursive, Recursive::NotRecursive);
|
||||
|
||||
// Since this is a canonicalized Expr, we should have
|
||||
// already detected any arity mismatches and replaced this
|
||||
|
@ -1403,7 +1403,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
|
|||
debug_assert_eq!(params.len(), args.len());
|
||||
|
||||
// Start with the function's body as the answer.
|
||||
let mut loc_answer = *boxed_body.clone();
|
||||
let mut loc_answer = *boxed_body;
|
||||
|
||||
// Wrap the body in one LetNonRec for each argument,
|
||||
// such that at the end we have all the arguments in
|
||||
|
|
|
@ -7,8 +7,8 @@ use crate::llvm::build_hash::generic_hash;
|
|||
use crate::llvm::build_list::{
|
||||
allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, list_contains,
|
||||
list_get_unsafe, list_join, list_keep_errs, list_keep_if, list_keep_oks, list_len, list_map,
|
||||
list_map2, list_map3, list_map_with_index, list_prepend, list_product, list_range, list_repeat,
|
||||
list_reverse, list_set, list_single, list_sum, list_walk, list_walk_backwards,
|
||||
list_map2, list_map3, list_map_with_index, list_prepend, list_range, list_repeat, list_reverse,
|
||||
list_set, list_single, list_walk_help,
|
||||
};
|
||||
use crate::llvm::build_str::{
|
||||
str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, str_from_utf8,
|
||||
|
@ -3893,71 +3893,30 @@ fn run_low_level<'a, 'ctx, 'env>(
|
|||
|
||||
list_range(env, *builtin, low.into_int_value(), high.into_int_value())
|
||||
}
|
||||
ListWalk => {
|
||||
debug_assert_eq!(args.len(), 3);
|
||||
|
||||
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
|
||||
|
||||
let (func, func_layout) = load_symbol_and_layout(scope, &args[1]);
|
||||
|
||||
let (default, default_layout) = load_symbol_and_layout(scope, &args[2]);
|
||||
|
||||
match list_layout {
|
||||
Layout::Builtin(Builtin::EmptyList) => default,
|
||||
Layout::Builtin(Builtin::List(_, element_layout)) => list_walk(
|
||||
env,
|
||||
layout_ids,
|
||||
parent,
|
||||
list,
|
||||
element_layout,
|
||||
func,
|
||||
func_layout,
|
||||
default,
|
||||
default_layout,
|
||||
),
|
||||
_ => unreachable!("invalid list layout"),
|
||||
}
|
||||
}
|
||||
ListWalkBackwards => {
|
||||
// List.walkBackwards : List elem, (elem -> accum -> accum), accum -> accum
|
||||
debug_assert_eq!(args.len(), 3);
|
||||
|
||||
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
|
||||
|
||||
let (func, func_layout) = load_symbol_and_layout(scope, &args[1]);
|
||||
|
||||
let (default, default_layout) = load_symbol_and_layout(scope, &args[2]);
|
||||
|
||||
match list_layout {
|
||||
Layout::Builtin(Builtin::EmptyList) => default,
|
||||
Layout::Builtin(Builtin::List(_, element_layout)) => list_walk_backwards(
|
||||
env,
|
||||
layout_ids,
|
||||
parent,
|
||||
list,
|
||||
element_layout,
|
||||
func,
|
||||
func_layout,
|
||||
default,
|
||||
default_layout,
|
||||
),
|
||||
_ => unreachable!("invalid list layout"),
|
||||
}
|
||||
}
|
||||
ListSum => {
|
||||
debug_assert_eq!(args.len(), 1);
|
||||
|
||||
let list = load_symbol(scope, &args[0]);
|
||||
|
||||
list_sum(env, parent, list, layout)
|
||||
}
|
||||
ListProduct => {
|
||||
debug_assert_eq!(args.len(), 1);
|
||||
|
||||
let list = load_symbol(scope, &args[0]);
|
||||
|
||||
list_product(env, parent, list, layout)
|
||||
}
|
||||
ListWalk => list_walk_help(
|
||||
env,
|
||||
layout_ids,
|
||||
scope,
|
||||
parent,
|
||||
args,
|
||||
crate::llvm::build_list::ListWalk::Walk,
|
||||
),
|
||||
ListWalkUntil => list_walk_help(
|
||||
env,
|
||||
layout_ids,
|
||||
scope,
|
||||
parent,
|
||||
args,
|
||||
crate::llvm::build_list::ListWalk::WalkUntil,
|
||||
),
|
||||
ListWalkBackwards => list_walk_help(
|
||||
env,
|
||||
layout_ids,
|
||||
scope,
|
||||
parent,
|
||||
args,
|
||||
crate::llvm::build_list::ListWalk::WalkBackwards,
|
||||
),
|
||||
ListAppend => {
|
||||
// List.append : List elem, elem -> List elem
|
||||
debug_assert_eq!(args.len(), 2);
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::llvm::bitcode::{
|
|||
call_bitcode_fn, call_void_bitcode_fn,
|
||||
};
|
||||
use crate::llvm::build::{
|
||||
allocate_with_refcount_help, build_num_binop, cast_basic_basic, complex_bitcast, Env, InPlace,
|
||||
allocate_with_refcount_help, cast_basic_basic, complex_bitcast, Env, InPlace,
|
||||
};
|
||||
use crate::llvm::convert::{basic_type_from_layout, collection, get_ptr_type};
|
||||
use crate::llvm::refcounting::{
|
||||
|
@ -713,206 +713,47 @@ pub fn list_len<'ctx>(
|
|||
.into_int_value()
|
||||
}
|
||||
|
||||
/// List.sum : List (Num a) -> Num a
|
||||
pub fn list_sum<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
list: BasicValueEnum<'ctx>,
|
||||
default_layout: &Layout<'a>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let ctx = env.context;
|
||||
let builder = env.builder;
|
||||
|
||||
let list_wrapper = list.into_struct_value();
|
||||
let len = list_len(env.builder, list_wrapper);
|
||||
|
||||
let accum_type = basic_type_from_layout(env.arena, ctx, default_layout, env.ptr_bytes);
|
||||
let accum_alloca = builder.build_alloca(accum_type, "alloca_walk_right_accum");
|
||||
|
||||
let default: BasicValueEnum = match accum_type {
|
||||
BasicTypeEnum::IntType(int_type) => int_type.const_zero().into(),
|
||||
BasicTypeEnum::FloatType(float_type) => float_type.const_zero().into(),
|
||||
_ => unreachable!(""),
|
||||
};
|
||||
|
||||
builder.build_store(accum_alloca, default);
|
||||
|
||||
let then_block = ctx.append_basic_block(parent, "then");
|
||||
let cont_block = ctx.append_basic_block(parent, "branchcont");
|
||||
|
||||
let condition = builder.build_int_compare(
|
||||
IntPredicate::UGT,
|
||||
len,
|
||||
ctx.i64_type().const_zero(),
|
||||
"list_non_empty",
|
||||
);
|
||||
|
||||
builder.build_conditional_branch(condition, then_block, cont_block);
|
||||
|
||||
builder.position_at_end(then_block);
|
||||
|
||||
let elem_ptr_type = get_ptr_type(&accum_type, AddressSpace::Generic);
|
||||
let list_ptr = load_list_ptr(builder, list_wrapper, elem_ptr_type);
|
||||
|
||||
let walk_right_loop = |_, elem: BasicValueEnum<'ctx>| {
|
||||
// load current accumulator
|
||||
let current = builder.build_load(accum_alloca, "retrieve_accum");
|
||||
|
||||
let new_current = build_num_binop(
|
||||
env,
|
||||
parent,
|
||||
current,
|
||||
default_layout,
|
||||
elem,
|
||||
default_layout,
|
||||
roc_module::low_level::LowLevel::NumAdd,
|
||||
);
|
||||
|
||||
builder.build_store(accum_alloca, new_current);
|
||||
};
|
||||
|
||||
incrementing_elem_loop(
|
||||
builder,
|
||||
ctx,
|
||||
parent,
|
||||
list_ptr,
|
||||
len,
|
||||
"#index",
|
||||
walk_right_loop,
|
||||
);
|
||||
|
||||
builder.build_unconditional_branch(cont_block);
|
||||
|
||||
builder.position_at_end(cont_block);
|
||||
|
||||
builder.build_load(accum_alloca, "load_final_acum")
|
||||
pub enum ListWalk {
|
||||
Walk,
|
||||
WalkBackwards,
|
||||
WalkUntil,
|
||||
WalkBackwardsUntil,
|
||||
}
|
||||
|
||||
/// List.product : List (Num a) -> Num a
|
||||
pub fn list_product<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
list: BasicValueEnum<'ctx>,
|
||||
default_layout: &Layout<'a>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let ctx = env.context;
|
||||
let builder = env.builder;
|
||||
|
||||
let list_wrapper = list.into_struct_value();
|
||||
let len = list_len(env.builder, list_wrapper);
|
||||
|
||||
let accum_type = basic_type_from_layout(env.arena, ctx, default_layout, env.ptr_bytes);
|
||||
let accum_alloca = builder.build_alloca(accum_type, "alloca_walk_right_accum");
|
||||
|
||||
let default: BasicValueEnum = match accum_type {
|
||||
BasicTypeEnum::IntType(int_type) => int_type.const_int(1, false).into(),
|
||||
BasicTypeEnum::FloatType(float_type) => float_type.const_float(1.0).into(),
|
||||
_ => unreachable!(""),
|
||||
};
|
||||
|
||||
builder.build_store(accum_alloca, default);
|
||||
|
||||
let then_block = ctx.append_basic_block(parent, "then");
|
||||
let cont_block = ctx.append_basic_block(parent, "branchcont");
|
||||
|
||||
let condition = builder.build_int_compare(
|
||||
IntPredicate::UGT,
|
||||
len,
|
||||
ctx.i64_type().const_zero(),
|
||||
"list_non_empty",
|
||||
);
|
||||
|
||||
builder.build_conditional_branch(condition, then_block, cont_block);
|
||||
|
||||
builder.position_at_end(then_block);
|
||||
|
||||
let elem_ptr_type = get_ptr_type(&accum_type, AddressSpace::Generic);
|
||||
let list_ptr = load_list_ptr(builder, list_wrapper, elem_ptr_type);
|
||||
|
||||
let walk_right_loop = |_, elem: BasicValueEnum<'ctx>| {
|
||||
// load current accumulator
|
||||
let current = builder.build_load(accum_alloca, "retrieve_accum");
|
||||
|
||||
let new_current = build_num_binop(
|
||||
env,
|
||||
parent,
|
||||
current,
|
||||
default_layout,
|
||||
elem,
|
||||
default_layout,
|
||||
roc_module::low_level::LowLevel::NumMul,
|
||||
);
|
||||
|
||||
builder.build_store(accum_alloca, new_current);
|
||||
};
|
||||
|
||||
incrementing_elem_loop(
|
||||
builder,
|
||||
ctx,
|
||||
parent,
|
||||
list_ptr,
|
||||
len,
|
||||
"#index",
|
||||
walk_right_loop,
|
||||
);
|
||||
|
||||
builder.build_unconditional_branch(cont_block);
|
||||
|
||||
builder.position_at_end(cont_block);
|
||||
|
||||
builder.build_load(accum_alloca, "load_final_acum")
|
||||
}
|
||||
|
||||
/// List.walk : List elem, (elem -> accum -> accum), accum -> accum
|
||||
pub fn list_walk<'a, 'ctx, 'env>(
|
||||
pub fn list_walk_help<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout_ids: &mut LayoutIds<'a>,
|
||||
scope: &crate::llvm::build::Scope<'a, 'ctx>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
list: BasicValueEnum<'ctx>,
|
||||
element_layout: &Layout<'a>,
|
||||
func: BasicValueEnum<'ctx>,
|
||||
func_layout: &Layout<'a>,
|
||||
default: BasicValueEnum<'ctx>,
|
||||
default_layout: &Layout<'a>,
|
||||
args: &[roc_module::symbol::Symbol],
|
||||
variant: ListWalk,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
list_walk_generic(
|
||||
env,
|
||||
layout_ids,
|
||||
parent,
|
||||
list,
|
||||
element_layout,
|
||||
func,
|
||||
func_layout,
|
||||
default,
|
||||
default_layout,
|
||||
&bitcode::LIST_WALK,
|
||||
)
|
||||
}
|
||||
use crate::llvm::build::load_symbol_and_layout;
|
||||
|
||||
/// List.walkBackwards : List elem, (elem -> accum -> accum), accum -> accum
|
||||
pub fn list_walk_backwards<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout_ids: &mut LayoutIds<'a>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
list: BasicValueEnum<'ctx>,
|
||||
element_layout: &Layout<'a>,
|
||||
func: BasicValueEnum<'ctx>,
|
||||
func_layout: &Layout<'a>,
|
||||
default: BasicValueEnum<'ctx>,
|
||||
default_layout: &Layout<'a>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
list_walk_generic(
|
||||
env,
|
||||
layout_ids,
|
||||
parent,
|
||||
list,
|
||||
element_layout,
|
||||
func,
|
||||
func_layout,
|
||||
default,
|
||||
default_layout,
|
||||
&bitcode::LIST_WALK_BACKWARDS,
|
||||
)
|
||||
debug_assert_eq!(args.len(), 3);
|
||||
|
||||
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
|
||||
|
||||
let (func, func_layout) = load_symbol_and_layout(scope, &args[1]);
|
||||
|
||||
let (default, default_layout) = load_symbol_and_layout(scope, &args[2]);
|
||||
|
||||
match list_layout {
|
||||
Layout::Builtin(Builtin::EmptyList) => default,
|
||||
Layout::Builtin(Builtin::List(_, element_layout)) => list_walk_generic(
|
||||
env,
|
||||
layout_ids,
|
||||
parent,
|
||||
list,
|
||||
element_layout,
|
||||
func,
|
||||
func_layout,
|
||||
default,
|
||||
default_layout,
|
||||
variant,
|
||||
),
|
||||
_ => unreachable!("invalid list layout"),
|
||||
}
|
||||
}
|
||||
|
||||
fn list_walk_generic<'a, 'ctx, 'env>(
|
||||
|
@ -925,10 +766,17 @@ fn list_walk_generic<'a, 'ctx, 'env>(
|
|||
func_layout: &Layout<'a>,
|
||||
default: BasicValueEnum<'ctx>,
|
||||
default_layout: &Layout<'a>,
|
||||
zig_function: &str,
|
||||
variant: ListWalk,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let builder = env.builder;
|
||||
|
||||
let zig_function = match variant {
|
||||
ListWalk::Walk => bitcode::LIST_WALK,
|
||||
ListWalk::WalkBackwards => bitcode::LIST_WALK_BACKWARDS,
|
||||
ListWalk::WalkUntil => bitcode::LIST_WALK_UNTIL,
|
||||
ListWalk::WalkBackwardsUntil => todo!(),
|
||||
};
|
||||
|
||||
let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic);
|
||||
|
||||
let list_i128 = complex_bitcast(env.builder, list, env.context.i128_type().into(), "to_i128");
|
||||
|
@ -961,21 +809,44 @@ fn list_walk_generic<'a, 'ctx, 'env>(
|
|||
|
||||
let result_ptr = env.builder.build_alloca(default.get_type(), "result");
|
||||
|
||||
call_void_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
list_i128,
|
||||
env.builder
|
||||
.build_bitcast(transform_ptr, u8_ptr, "to_opaque"),
|
||||
stepper_caller.into(),
|
||||
env.builder.build_bitcast(default_ptr, u8_ptr, "to_u8_ptr"),
|
||||
alignment_iv.into(),
|
||||
element_width.into(),
|
||||
default_width.into(),
|
||||
env.builder.build_bitcast(result_ptr, u8_ptr, "to_opaque"),
|
||||
],
|
||||
zig_function,
|
||||
);
|
||||
match variant {
|
||||
ListWalk::Walk | ListWalk::WalkBackwards => {
|
||||
call_void_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
list_i128,
|
||||
env.builder
|
||||
.build_bitcast(transform_ptr, u8_ptr, "to_opaque"),
|
||||
stepper_caller.into(),
|
||||
env.builder.build_bitcast(default_ptr, u8_ptr, "to_u8_ptr"),
|
||||
alignment_iv.into(),
|
||||
element_width.into(),
|
||||
default_width.into(),
|
||||
env.builder.build_bitcast(result_ptr, u8_ptr, "to_opaque"),
|
||||
],
|
||||
zig_function,
|
||||
);
|
||||
}
|
||||
ListWalk::WalkUntil | ListWalk::WalkBackwardsUntil => {
|
||||
let dec_element_fn = build_dec_wrapper(env, layout_ids, element_layout);
|
||||
call_void_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
list_i128,
|
||||
env.builder
|
||||
.build_bitcast(transform_ptr, u8_ptr, "to_opaque"),
|
||||
stepper_caller.into(),
|
||||
env.builder.build_bitcast(default_ptr, u8_ptr, "to_u8_ptr"),
|
||||
alignment_iv.into(),
|
||||
element_width.into(),
|
||||
default_width.into(),
|
||||
dec_element_fn.as_global_value().as_pointer_value().into(),
|
||||
env.builder.build_bitcast(result_ptr, u8_ptr, "to_opaque"),
|
||||
],
|
||||
zig_function,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
env.builder.build_load(result_ptr, "load_result")
|
||||
}
|
||||
|
|
|
@ -111,12 +111,16 @@ fn generate_module_doc<'a>(
|
|||
},
|
||||
|
||||
Alias {
|
||||
name: _,
|
||||
name,
|
||||
vars: _,
|
||||
ann: _,
|
||||
} =>
|
||||
// TODO
|
||||
{
|
||||
} => {
|
||||
let entry = DocEntry {
|
||||
name: name.value.to_string(),
|
||||
docs: before_comments_or_new_lines.and_then(comments_or_new_lines_to_docs),
|
||||
};
|
||||
acc.push(entry);
|
||||
|
||||
(acc, None)
|
||||
}
|
||||
|
||||
|
@ -139,7 +143,9 @@ fn comments_or_new_lines_to_docs<'a>(
|
|||
docs.push_str(doc_str);
|
||||
docs.push('\n');
|
||||
}
|
||||
Newline | LineComment(_) => {}
|
||||
Newline | LineComment(_) => {
|
||||
docs = String::new();
|
||||
}
|
||||
}
|
||||
}
|
||||
if docs.is_empty() {
|
||||
|
|
|
@ -3773,12 +3773,7 @@ fn make_specializations<'a>(
|
|||
// TODO: for now this final specialization pass is sequential,
|
||||
// with no parallelization at all. We should try to parallelize
|
||||
// this, but doing so will require a redesign of Procs.
|
||||
procs = roc_mono::ir::specialize_all(
|
||||
&mut mono_env,
|
||||
procs,
|
||||
&mut layout_cache,
|
||||
// &finished_info.vars_by_symbol,
|
||||
);
|
||||
procs = roc_mono::ir::specialize_all(&mut mono_env, procs, &mut layout_cache);
|
||||
|
||||
let external_specializations_requested = procs.externals_we_need.clone();
|
||||
let procedures = procs.get_specialized_procs_without_rc(mono_env.arena);
|
||||
|
|
|
@ -33,9 +33,8 @@ pub enum LowLevel {
|
|||
ListMapWithIndex,
|
||||
ListKeepIf,
|
||||
ListWalk,
|
||||
ListWalkUntil,
|
||||
ListWalkBackwards,
|
||||
ListSum,
|
||||
ListProduct,
|
||||
ListKeepOks,
|
||||
ListKeepErrs,
|
||||
DictSize,
|
||||
|
|
|
@ -915,7 +915,10 @@ define_builtins! {
|
|||
24 LIST_MAP2: "map2"
|
||||
25 LIST_MAP3: "map3"
|
||||
26 LIST_PRODUCT: "product"
|
||||
27 LIST_RANGE: "range"
|
||||
27 LIST_SUM_ADD: "#sumadd"
|
||||
28 LIST_PRODUCT_MUL: "#productmul"
|
||||
29 LIST_WALK_UNTIL: "walkUntil"
|
||||
30 LIST_RANGE: "range"
|
||||
}
|
||||
5 RESULT: "Result" => {
|
||||
0 RESULT_RESULT: "Result" imported // the Result.Result type alias
|
||||
|
|
|
@ -655,10 +655,10 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
|
|||
ListMap3 => arena.alloc_slice_copy(&[owned, owned, owned, irrelevant]),
|
||||
ListKeepIf | ListKeepOks | ListKeepErrs => arena.alloc_slice_copy(&[owned, borrowed]),
|
||||
ListContains => arena.alloc_slice_copy(&[borrowed, irrelevant]),
|
||||
ListWalk => arena.alloc_slice_copy(&[owned, irrelevant, owned]),
|
||||
ListWalkBackwards => arena.alloc_slice_copy(&[owned, irrelevant, owned]),
|
||||
ListRange => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
|
||||
ListSum | ListProduct => arena.alloc_slice_copy(&[borrowed]),
|
||||
ListWalk | ListWalkUntil | ListWalkBackwards => {
|
||||
arena.alloc_slice_copy(&[owned, irrelevant, owned])
|
||||
}
|
||||
|
||||
// TODO when we have lists with capacity (if ever)
|
||||
// List.append should own its first argument
|
||||
|
|
|
@ -441,7 +441,7 @@ fn expand_and_cancel<'a>(env: &mut Env<'a, '_>, stmt: &'a Stmt<'a>) -> &'a Stmt<
|
|||
structure,
|
||||
index,
|
||||
field_layouts,
|
||||
..
|
||||
wrapped,
|
||||
} => {
|
||||
let entry = env
|
||||
.alias_map
|
||||
|
@ -450,6 +450,15 @@ fn expand_and_cancel<'a>(env: &mut Env<'a, '_>, stmt: &'a Stmt<'a>) -> &'a Stmt<
|
|||
|
||||
entry.insert(*index, symbol);
|
||||
|
||||
// fixes https://github.com/rtfeldman/roc/issues/1099
|
||||
if matches!(
|
||||
wrapped,
|
||||
Wrapped::SingleElementRecord | Wrapped::RecordOrSingleTagUnion
|
||||
) {
|
||||
env.layout_map
|
||||
.insert(*structure, Layout::Struct(field_layouts));
|
||||
}
|
||||
|
||||
// if the field is a struct, we know its constructor too!
|
||||
let field_layout = &field_layouts[*index as usize];
|
||||
env.try_insert_struct_info(symbol, field_layout);
|
||||
|
|
|
@ -558,13 +558,7 @@ impl<'a> Procs<'a> {
|
|||
}
|
||||
}
|
||||
Err(error) => {
|
||||
let error_msg = format!(
|
||||
"TODO generate a RuntimeError message for {:?}",
|
||||
error
|
||||
);
|
||||
self.runtime_errors
|
||||
.insert(symbol, env.arena.alloc(error_msg));
|
||||
panic!();
|
||||
panic!("TODO generate a RuntimeError message for {:?}", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -671,11 +665,7 @@ impl<'a> Procs<'a> {
|
|||
self.specialized.insert((symbol, layout), Done(proc));
|
||||
}
|
||||
Err(error) => {
|
||||
let error_msg =
|
||||
format!("TODO generate a RuntimeError message for {:?}", error);
|
||||
self.runtime_errors
|
||||
.insert(symbol, env.arena.alloc(error_msg));
|
||||
panic!();
|
||||
panic!("TODO generate a RuntimeError message for {:?}", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -699,7 +689,6 @@ fn add_pending<'a>(
|
|||
#[derive(Default)]
|
||||
pub struct Specializations<'a> {
|
||||
by_symbol: MutMap<Symbol, MutMap<Layout<'a>, Proc<'a>>>,
|
||||
runtime_errors: MutSet<Symbol>,
|
||||
}
|
||||
|
||||
impl<'a> Specializations<'a> {
|
||||
|
@ -715,32 +704,15 @@ impl<'a> Specializations<'a> {
|
|||
!procs_by_layout.contains_key(&layout) || procs_by_layout.get(&layout) == Some(&proc)
|
||||
);
|
||||
|
||||
// We shouldn't already have a runtime error recorded for this symbol
|
||||
debug_assert!(!self.runtime_errors.contains(&symbol));
|
||||
|
||||
procs_by_layout.insert(layout, proc);
|
||||
}
|
||||
|
||||
pub fn runtime_error(&mut self, symbol: Symbol) {
|
||||
// We shouldn't already have a normal proc recorded for this symbol
|
||||
debug_assert!(!self.by_symbol.contains_key(&symbol));
|
||||
|
||||
self.runtime_errors.insert(symbol);
|
||||
}
|
||||
|
||||
pub fn into_owned(self) -> (MutMap<Symbol, MutMap<Layout<'a>, Proc<'a>>>, MutSet<Symbol>) {
|
||||
(self.by_symbol, self.runtime_errors)
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
let runtime_errors: usize = self.runtime_errors.len();
|
||||
let specializations: usize = self.by_symbol.len();
|
||||
|
||||
runtime_errors + specializations
|
||||
self.by_symbol.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
self.by_symbol.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1696,13 +1668,15 @@ pub fn specialize_all<'a>(
|
|||
Ok((proc, layout)) => {
|
||||
procs.specialized.insert((name, layout), Done(proc));
|
||||
}
|
||||
Err(error) => {
|
||||
let error_msg = env.arena.alloc(format!(
|
||||
"TODO generate a RuntimeError message for {:?}",
|
||||
error
|
||||
));
|
||||
Err(SpecializeFailure {
|
||||
problem: _,
|
||||
attempted_layout,
|
||||
}) => {
|
||||
let proc = generate_runtime_error_function(env, name, attempted_layout);
|
||||
|
||||
procs.runtime_errors.insert(name, error_msg);
|
||||
procs
|
||||
.specialized
|
||||
.insert((name, attempted_layout), Done(proc));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1758,13 +1732,14 @@ pub fn specialize_all<'a>(
|
|||
procs.specialized.insert((name, layout), Done(proc));
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
let error_msg = env.arena.alloc(format!(
|
||||
"TODO generate a RuntimeError message for {:?}",
|
||||
error
|
||||
));
|
||||
Err(SpecializeFailure {
|
||||
attempted_layout, ..
|
||||
}) => {
|
||||
let proc = generate_runtime_error_function(env, name, attempted_layout);
|
||||
|
||||
procs.runtime_errors.insert(name, error_msg);
|
||||
procs
|
||||
.specialized
|
||||
.insert((name, attempted_layout), Done(proc));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1774,6 +1749,47 @@ pub fn specialize_all<'a>(
|
|||
procs
|
||||
}
|
||||
|
||||
fn generate_runtime_error_function<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
name: Symbol,
|
||||
layout: Layout<'a>,
|
||||
) -> Proc<'a> {
|
||||
let (arg_layouts, ret_layout) = match layout {
|
||||
Layout::FunctionPointer(a, r) => (a, *r),
|
||||
_ => (&[] as &[_], layout),
|
||||
};
|
||||
|
||||
let mut args = Vec::with_capacity_in(arg_layouts.len(), env.arena);
|
||||
|
||||
for arg in arg_layouts {
|
||||
args.push((*arg, env.unique_symbol()));
|
||||
}
|
||||
|
||||
let mut msg = bumpalo::collections::string::String::with_capacity_in(80, env.arena);
|
||||
use std::fmt::Write;
|
||||
write!(
|
||||
&mut msg,
|
||||
"The {:?} function could not be generated, likely due to a type error.",
|
||||
name
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
eprintln!("emitted runtime error function {:?}", &msg);
|
||||
|
||||
let runtime_error = Stmt::RuntimeError(msg.into_bump_str());
|
||||
|
||||
Proc {
|
||||
name,
|
||||
args: args.into_bump_slice(),
|
||||
body: runtime_error,
|
||||
closure_data_layout: None,
|
||||
ret_layout,
|
||||
is_self_recursive: SelfRecursive::NotSelfRecursive,
|
||||
must_own_arguments: false,
|
||||
host_exposed_layouts: HostExposedLayouts::NotHostExposed,
|
||||
}
|
||||
}
|
||||
|
||||
fn specialize_external<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
procs: &mut Procs<'a>,
|
||||
|
@ -2270,6 +2286,14 @@ fn build_specialized_proc<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SpecializeFailure<'a> {
|
||||
/// The layout we attempted to create
|
||||
attempted_layout: Layout<'a>,
|
||||
/// The problem we ran into while creating it
|
||||
problem: LayoutProblem,
|
||||
}
|
||||
|
||||
fn specialize<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
procs: &mut Procs<'a>,
|
||||
|
@ -2277,7 +2301,7 @@ fn specialize<'a>(
|
|||
layout_cache: &mut LayoutCache<'a>,
|
||||
pending: PendingSpecialization,
|
||||
partial_proc: PartialProc<'a>,
|
||||
) -> Result<(Proc<'a>, Layout<'a>), LayoutProblem> {
|
||||
) -> Result<(Proc<'a>, Layout<'a>), SpecializeFailure<'a>> {
|
||||
let PendingSpecialization {
|
||||
solved_type,
|
||||
host_exposed_aliases,
|
||||
|
@ -2321,7 +2345,7 @@ fn specialize_solved_type<'a>(
|
|||
solved_type: SolvedType,
|
||||
host_exposed_aliases: MutMap<Symbol, SolvedType>,
|
||||
partial_proc: PartialProc<'a>,
|
||||
) -> Result<(Proc<'a>, Layout<'a>), LayoutProblem> {
|
||||
) -> Result<(Proc<'a>, Layout<'a>), SpecializeFailure<'a>> {
|
||||
// add the specializations that other modules require of us
|
||||
use roc_solve::solve::instantiate_rigids;
|
||||
|
||||
|
@ -2330,6 +2354,10 @@ fn specialize_solved_type<'a>(
|
|||
|
||||
let fn_var = introduce_solved_type_to_subs(env, &solved_type);
|
||||
|
||||
let attempted_layout = layout_cache
|
||||
.from_var(&env.arena, fn_var, env.subs)
|
||||
.unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err));
|
||||
|
||||
// make sure rigid variables in the annotation are converted to flex variables
|
||||
instantiate_rigids(env.subs, partial_proc.annotation);
|
||||
|
||||
|
@ -2353,17 +2381,25 @@ fn specialize_solved_type<'a>(
|
|||
|
||||
match specialized {
|
||||
Ok(proc) => {
|
||||
let layout = layout_cache
|
||||
.from_var(&env.arena, fn_var, env.subs)
|
||||
.unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err));
|
||||
// when successful, the layout after unification should be the layout before unification
|
||||
debug_assert_eq!(
|
||||
attempted_layout,
|
||||
layout_cache
|
||||
.from_var(&env.arena, fn_var, env.subs)
|
||||
.unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err))
|
||||
);
|
||||
|
||||
env.subs.rollback_to(snapshot);
|
||||
layout_cache.rollback_to(cache_snapshot);
|
||||
Ok((proc, layout))
|
||||
Ok((proc, attempted_layout))
|
||||
}
|
||||
Err(error) => {
|
||||
env.subs.rollback_to(snapshot);
|
||||
layout_cache.rollback_to(cache_snapshot);
|
||||
Err(error)
|
||||
Err(SpecializeFailure {
|
||||
problem: error,
|
||||
attempted_layout,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2803,11 +2839,69 @@ pub fn with_hole<'a>(
|
|||
variant_var,
|
||||
name: tag_name,
|
||||
arguments: args,
|
||||
..
|
||||
ext_var,
|
||||
} => {
|
||||
use crate::layout::UnionVariant::*;
|
||||
let arena = env.arena;
|
||||
|
||||
let desc = env.subs.get_without_compacting(variant_var);
|
||||
|
||||
if let Content::Structure(FlatType::Func(arg_vars, _, ret_var)) = desc.content {
|
||||
let mut loc_pattern_args = vec![];
|
||||
let mut loc_expr_args = vec![];
|
||||
|
||||
let proc_symbol = env.unique_symbol();
|
||||
|
||||
for arg_var in arg_vars {
|
||||
let arg_symbol = env.unique_symbol();
|
||||
|
||||
let loc_pattern =
|
||||
Located::at_zero(roc_can::pattern::Pattern::Identifier(arg_symbol));
|
||||
|
||||
let loc_expr = Located::at_zero(roc_can::expr::Expr::Var(arg_symbol));
|
||||
|
||||
loc_pattern_args.push((arg_var, loc_pattern));
|
||||
loc_expr_args.push((arg_var, loc_expr));
|
||||
}
|
||||
|
||||
let loc_body = Located::at_zero(roc_can::expr::Expr::Tag {
|
||||
variant_var: ret_var,
|
||||
name: tag_name,
|
||||
arguments: loc_expr_args,
|
||||
ext_var,
|
||||
});
|
||||
|
||||
let inserted = procs.insert_anonymous(
|
||||
env,
|
||||
proc_symbol,
|
||||
variant_var,
|
||||
loc_pattern_args,
|
||||
loc_body,
|
||||
CapturedSymbols::None,
|
||||
ret_var,
|
||||
layout_cache,
|
||||
);
|
||||
|
||||
match inserted {
|
||||
Ok(layout) => {
|
||||
return Stmt::Let(
|
||||
assigned,
|
||||
call_by_pointer(env, procs, proc_symbol, layout),
|
||||
layout,
|
||||
hole,
|
||||
);
|
||||
}
|
||||
Err(runtime_error) => {
|
||||
return Stmt::RuntimeError(env.arena.alloc(format!(
|
||||
"RuntimeError {} line {} {:?}",
|
||||
file!(),
|
||||
line!(),
|
||||
runtime_error,
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let res_variant = crate::layout::union_sorted_tags(env.arena, variant_var, env.subs);
|
||||
|
||||
let variant = match res_variant {
|
||||
|
@ -5893,6 +5987,20 @@ fn call_by_name<'a>(
|
|||
|
||||
// Register a pending_specialization for this function
|
||||
match layout_cache.from_var(env.arena, fn_var, env.subs) {
|
||||
Err(LayoutProblem::UnresolvedTypeVar(var)) => {
|
||||
let msg = format!(
|
||||
"Hit an unresolved type variable {:?} when creating a layout for {:?} (var {:?})",
|
||||
var, proc_name, fn_var
|
||||
);
|
||||
Stmt::RuntimeError(env.arena.alloc(msg))
|
||||
}
|
||||
Err(LayoutProblem::Erroneous) => {
|
||||
let msg = format!(
|
||||
"Hit an erroneous type when creating a layout for {:?}",
|
||||
proc_name
|
||||
);
|
||||
Stmt::RuntimeError(env.arena.alloc(msg))
|
||||
}
|
||||
Ok(layout) => {
|
||||
// Build the CallByName node
|
||||
let arena = env.arena;
|
||||
|
@ -6028,123 +6136,42 @@ fn call_by_name<'a>(
|
|||
"\n\n{:?}\n\n{:?}",
|
||||
full_layout, layout
|
||||
);
|
||||
let function_layout =
|
||||
FunctionLayouts::from_layout(env.arena, layout);
|
||||
|
||||
procs.specialized.remove(&(proc_name, full_layout));
|
||||
|
||||
procs
|
||||
.specialized
|
||||
.insert((proc_name, function_layout.full), Done(proc));
|
||||
|
||||
if field_symbols.is_empty() {
|
||||
debug_assert!(loc_args.is_empty());
|
||||
|
||||
// This happens when we return a function, e.g.
|
||||
//
|
||||
// foo = Num.add
|
||||
//
|
||||
// Even though the layout (and type) are functions,
|
||||
// there are no arguments. This confuses our IR,
|
||||
// and we have to fix it here.
|
||||
match full_layout {
|
||||
Layout::Closure(_, closure_layout, _) => {
|
||||
let call = self::Call {
|
||||
call_type: CallType::ByName {
|
||||
name: proc_name,
|
||||
ret_layout: function_layout.result,
|
||||
full_layout: function_layout.full,
|
||||
arg_layouts: function_layout.arguments,
|
||||
},
|
||||
arguments: field_symbols,
|
||||
};
|
||||
|
||||
// in the case of a closure specifically, we
|
||||
// have to create a custom layout, to make sure
|
||||
// the closure data is part of the layout
|
||||
let closure_struct_layout = Layout::Struct(
|
||||
env.arena.alloc([
|
||||
function_layout.full,
|
||||
closure_layout
|
||||
.as_block_of_memory_layout(),
|
||||
]),
|
||||
);
|
||||
|
||||
build_call(
|
||||
env,
|
||||
call,
|
||||
assigned,
|
||||
closure_struct_layout,
|
||||
hole,
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
let call = self::Call {
|
||||
call_type: CallType::ByName {
|
||||
name: proc_name,
|
||||
ret_layout: function_layout.result,
|
||||
full_layout: function_layout.full,
|
||||
arg_layouts: function_layout.arguments,
|
||||
},
|
||||
arguments: field_symbols,
|
||||
};
|
||||
|
||||
build_call(
|
||||
env,
|
||||
call,
|
||||
assigned,
|
||||
function_layout.full,
|
||||
hole,
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debug_assert_eq!(
|
||||
function_layout.arguments.len(),
|
||||
field_symbols.len(),
|
||||
"scroll up a bit for background"
|
||||
);
|
||||
let call = self::Call {
|
||||
call_type: CallType::ByName {
|
||||
name: proc_name,
|
||||
ret_layout: function_layout.result,
|
||||
full_layout: function_layout.full,
|
||||
arg_layouts: function_layout.arguments,
|
||||
},
|
||||
arguments: field_symbols,
|
||||
};
|
||||
|
||||
let iter = loc_args
|
||||
.into_iter()
|
||||
.rev()
|
||||
.zip(field_symbols.iter().rev());
|
||||
|
||||
let result = build_call(
|
||||
env,
|
||||
call,
|
||||
assigned,
|
||||
function_layout.result,
|
||||
hole,
|
||||
);
|
||||
|
||||
assign_to_symbols(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
iter,
|
||||
result,
|
||||
)
|
||||
}
|
||||
call_specialized_proc(
|
||||
env,
|
||||
procs,
|
||||
proc_name,
|
||||
proc,
|
||||
layout,
|
||||
field_symbols,
|
||||
loc_args,
|
||||
layout_cache,
|
||||
assigned,
|
||||
hole,
|
||||
)
|
||||
}
|
||||
Err(error) => {
|
||||
let error_msg = env.arena.alloc(format!(
|
||||
"TODO generate a RuntimeError message for {:?}",
|
||||
error
|
||||
));
|
||||
Err(SpecializeFailure {
|
||||
attempted_layout,
|
||||
problem: _,
|
||||
}) => {
|
||||
let proc = generate_runtime_error_function(
|
||||
env,
|
||||
proc_name,
|
||||
attempted_layout,
|
||||
);
|
||||
|
||||
procs.runtime_errors.insert(proc_name, error_msg);
|
||||
|
||||
Stmt::RuntimeError(error_msg)
|
||||
call_specialized_proc(
|
||||
env,
|
||||
procs,
|
||||
proc_name,
|
||||
proc,
|
||||
layout,
|
||||
field_symbols,
|
||||
loc_args,
|
||||
layout_cache,
|
||||
assigned,
|
||||
hole,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6191,33 +6218,104 @@ fn call_by_name<'a>(
|
|||
}
|
||||
|
||||
None => {
|
||||
// This must have been a runtime error.
|
||||
match procs.runtime_errors.get(&proc_name) {
|
||||
Some(error) => Stmt::RuntimeError(
|
||||
env.arena.alloc(format!("runtime error {:?}", error)),
|
||||
),
|
||||
None => unreachable!("Proc name {:?} is invalid", proc_name),
|
||||
}
|
||||
unreachable!("Proc name {:?} is invalid", proc_name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(LayoutProblem::UnresolvedTypeVar(var)) => {
|
||||
let msg = format!(
|
||||
"Hit an unresolved type variable {:?} when creating a layout for {:?} (var {:?})",
|
||||
var, proc_name, fn_var
|
||||
);
|
||||
Stmt::RuntimeError(env.arena.alloc(msg))
|
||||
}
|
||||
Err(LayoutProblem::Erroneous) => {
|
||||
let msg = format!(
|
||||
"Hit an erroneous type when creating a layout for {:?}",
|
||||
proc_name
|
||||
);
|
||||
Stmt::RuntimeError(env.arena.alloc(msg))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn call_specialized_proc<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
procs: &mut Procs<'a>,
|
||||
proc_name: Symbol,
|
||||
proc: Proc<'a>,
|
||||
layout: Layout<'a>,
|
||||
field_symbols: &'a [Symbol],
|
||||
loc_args: std::vec::Vec<(Variable, Located<roc_can::expr::Expr>)>,
|
||||
layout_cache: &mut LayoutCache<'a>,
|
||||
assigned: Symbol,
|
||||
hole: &'a Stmt<'a>,
|
||||
) -> Stmt<'a> {
|
||||
let function_layout = FunctionLayouts::from_layout(env.arena, layout);
|
||||
|
||||
procs.specialized.remove(&(proc_name, layout));
|
||||
|
||||
procs
|
||||
.specialized
|
||||
.insert((proc_name, function_layout.full), Done(proc));
|
||||
|
||||
if field_symbols.is_empty() {
|
||||
debug_assert!(loc_args.is_empty());
|
||||
|
||||
// This happens when we return a function, e.g.
|
||||
//
|
||||
// foo = Num.add
|
||||
//
|
||||
// Even though the layout (and type) are functions,
|
||||
// there are no arguments. This confuses our IR,
|
||||
// and we have to fix it here.
|
||||
match layout {
|
||||
Layout::Closure(_, closure_layout, _) => {
|
||||
let call = self::Call {
|
||||
call_type: CallType::ByName {
|
||||
name: proc_name,
|
||||
ret_layout: function_layout.result,
|
||||
full_layout: function_layout.full,
|
||||
arg_layouts: function_layout.arguments,
|
||||
},
|
||||
arguments: field_symbols,
|
||||
};
|
||||
|
||||
// in the case of a closure specifically, we
|
||||
// have to create a custom layout, to make sure
|
||||
// the closure data is part of the layout
|
||||
let closure_struct_layout = Layout::Struct(env.arena.alloc([
|
||||
function_layout.full,
|
||||
closure_layout.as_block_of_memory_layout(),
|
||||
]));
|
||||
|
||||
build_call(env, call, assigned, closure_struct_layout, hole)
|
||||
}
|
||||
_ => {
|
||||
let call = self::Call {
|
||||
call_type: CallType::ByName {
|
||||
name: proc_name,
|
||||
ret_layout: function_layout.result,
|
||||
full_layout: function_layout.full,
|
||||
arg_layouts: function_layout.arguments,
|
||||
},
|
||||
arguments: field_symbols,
|
||||
};
|
||||
|
||||
build_call(env, call, assigned, function_layout.full, hole)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debug_assert_eq!(
|
||||
function_layout.arguments.len(),
|
||||
field_symbols.len(),
|
||||
"scroll up a bit for background"
|
||||
);
|
||||
let call = self::Call {
|
||||
call_type: CallType::ByName {
|
||||
name: proc_name,
|
||||
ret_layout: function_layout.result,
|
||||
full_layout: function_layout.full,
|
||||
arg_layouts: function_layout.arguments,
|
||||
},
|
||||
arguments: field_symbols,
|
||||
};
|
||||
|
||||
let iter = loc_args.into_iter().rev().zip(field_symbols.iter().rev());
|
||||
|
||||
let result = build_call(env, call, assigned, function_layout.result, hole);
|
||||
|
||||
assign_to_symbols(env, procs, layout_cache, iter, result)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -341,13 +341,13 @@ mod test_mono {
|
|||
"#,
|
||||
indoc!(
|
||||
r#"
|
||||
procedure Num.46 (#Attr.2):
|
||||
procedure Num.47 (#Attr.2):
|
||||
let Test.3 = lowlevel NumRound #Attr.2;
|
||||
ret Test.3;
|
||||
|
||||
procedure Test.0 ():
|
||||
let Test.2 = 3.6f64;
|
||||
let Test.1 = CallByName Num.46 Test.2;
|
||||
let Test.1 = CallByName Num.47 Test.2;
|
||||
ret Test.1;
|
||||
"#
|
||||
),
|
||||
|
|
|
@ -250,10 +250,7 @@ fn loc_possibly_negative_or_negated_term<'a>(
|
|||
]
|
||||
}
|
||||
|
||||
fn fail_expr_start_e<'a, T>() -> impl Parser<'a, T, EExpr<'a>>
|
||||
where
|
||||
T: 'a,
|
||||
{
|
||||
fn fail_expr_start_e<'a, T: 'a>() -> impl Parser<'a, T, EExpr<'a>> {
|
||||
|_arena, state: State<'a>| Err((NoProgress, EExpr::Start(state.line, state.column), state))
|
||||
}
|
||||
|
||||
|
@ -1027,7 +1024,7 @@ fn parse_expr_operator<'a>(
|
|||
|
||||
match expr_to_pattern_help(arena, &call.value) {
|
||||
Ok(good) => {
|
||||
let (_, mut ann_type, state) = specialize(
|
||||
let parser = specialize(
|
||||
EExpr::Type,
|
||||
space0_before_e(
|
||||
type_annotation::located_help(indented_more),
|
||||
|
@ -1035,21 +1032,28 @@ fn parse_expr_operator<'a>(
|
|||
Type::TSpace,
|
||||
Type::TIndentStart,
|
||||
),
|
||||
)
|
||||
.parse(arena, state)?;
|
||||
);
|
||||
|
||||
// put the spaces from after the operator in front of the call
|
||||
if !spaces_after_operator.is_empty() {
|
||||
ann_type = arena
|
||||
.alloc(ann_type.value)
|
||||
.with_spaces_before(spaces_after_operator, ann_type.region);
|
||||
match parser.parse(arena, state) {
|
||||
Err((_, fail, state)) => return Err((MadeProgress, fail, state)),
|
||||
Ok((_, mut ann_type, state)) => {
|
||||
// put the spaces from after the operator in front of the call
|
||||
if !spaces_after_operator.is_empty() {
|
||||
ann_type = arena.alloc(ann_type.value).with_spaces_before(
|
||||
spaces_after_operator,
|
||||
ann_type.region,
|
||||
);
|
||||
}
|
||||
|
||||
let alias_region =
|
||||
Region::span_across(&call.region, &ann_type.region);
|
||||
|
||||
let alias =
|
||||
Def::Annotation(Located::at(expr_region, good), ann_type);
|
||||
|
||||
(&*arena.alloc(Located::at(alias_region, alias)), state)
|
||||
}
|
||||
}
|
||||
|
||||
let alias_region = Region::span_across(&call.region, &ann_type.region);
|
||||
|
||||
let alias = Def::Annotation(Located::at(expr_region, good), ann_type);
|
||||
|
||||
(&*arena.alloc(Located::at(alias_region, alias)), state)
|
||||
}
|
||||
Err(_) => {
|
||||
// this `:` likely occured inline; treat it as an invalid operator
|
||||
|
|
|
@ -45,6 +45,10 @@ fn tag_union_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, TT
|
|||
}
|
||||
}
|
||||
|
||||
fn fail_type_start<'a, T: 'a>() -> impl Parser<'a, T, Type<'a>> {
|
||||
|_arena, state: State<'a>| Err((NoProgress, Type::TStart(state.line, state.column), state))
|
||||
}
|
||||
|
||||
fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, Type<'a>> {
|
||||
map_with_arena!(
|
||||
and!(
|
||||
|
@ -54,7 +58,8 @@ fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, Typ
|
|||
loc!(specialize(Type::TRecord, record_type(min_indent))),
|
||||
loc!(specialize(Type::TTagUnion, tag_union_type(min_indent))),
|
||||
loc!(applied_type(min_indent)),
|
||||
loc!(parse_type_variable)
|
||||
loc!(parse_type_variable),
|
||||
fail_type_start(),
|
||||
),
|
||||
// Inline alias notation, e.g. [ Nil, Cons a (List a) ] as List a
|
||||
one_of![
|
||||
|
|
|
@ -1559,6 +1559,9 @@ fn to_pattern_report<'a>(
|
|||
EPattern::Record(record, row, col) => {
|
||||
to_precord_report(alloc, filename, &record, *row, *col)
|
||||
}
|
||||
EPattern::PInParens(inparens, row, col) => {
|
||||
to_pattern_in_parens_report(alloc, filename, &inparens, *row, *col)
|
||||
}
|
||||
_ => todo!("unhandled parse error: {:?}", parse_problem),
|
||||
}
|
||||
}
|
||||
|
@ -1808,6 +1811,143 @@ fn to_precord_report<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
fn to_pattern_in_parens_report<'a>(
|
||||
alloc: &'a RocDocAllocator<'a>,
|
||||
filename: PathBuf,
|
||||
parse_problem: &roc_parse::parser::PInParens<'a>,
|
||||
start_row: Row,
|
||||
start_col: Col,
|
||||
) -> Report<'a> {
|
||||
use roc_parse::parser::PInParens;
|
||||
|
||||
match *parse_problem {
|
||||
PInParens::Open(row, col) => {
|
||||
// `Open` case is for exhaustiveness, this case shouldn not be reachable practically.
|
||||
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
|
||||
let region = Region::from_row_col(row, col);
|
||||
|
||||
let doc = alloc.stack(vec![
|
||||
alloc.reflow(
|
||||
r"I just started parsing a pattern in parentheses, but I got stuck here:",
|
||||
),
|
||||
alloc.region_with_subregion(surroundings, region),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow(r"A pattern in parentheses looks like "),
|
||||
alloc.parser_suggestion("(Ok 32)"),
|
||||
alloc.reflow(r" or "),
|
||||
alloc.parser_suggestion("(\"hello\")"),
|
||||
alloc.reflow(" so I was expecting to see an expression next."),
|
||||
]),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
doc,
|
||||
title: "UNFINISHED PARENTHESES".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
PInParens::End(row, col) => {
|
||||
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
|
||||
let region = Region::from_row_col(row, col);
|
||||
|
||||
let doc = alloc.stack(vec![
|
||||
alloc.reflow("I am partway through parsing a pattern in parentheses, but I got stuck here:"),
|
||||
alloc.region_with_subregion(surroundings, region),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow(
|
||||
r"I was expecting to see a closing parenthesis before this, so try adding a ",
|
||||
),
|
||||
alloc.parser_suggestion(")"),
|
||||
alloc.reflow(" and see if that helps?"),
|
||||
]),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
doc,
|
||||
title: "UNFINISHED PARENTHESES".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
PInParens::Pattern(pattern, row, col) => {
|
||||
to_pattern_report(alloc, filename, pattern, row, col)
|
||||
}
|
||||
|
||||
PInParens::IndentOpen(row, col) => {
|
||||
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
|
||||
let region = Region::from_row_col(row, col);
|
||||
|
||||
let doc = alloc.stack(vec![
|
||||
alloc.reflow(
|
||||
r"I just started parsing a pattern in parentheses, but I got stuck here:",
|
||||
),
|
||||
alloc.region_with_subregion(surroundings, region),
|
||||
record_patterns_look_like(alloc),
|
||||
note_for_record_pattern_indent(alloc),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
doc,
|
||||
title: "UNFINISHED PARENTHESES".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
PInParens::IndentEnd(row, col) => {
|
||||
match next_line_starts_with_close_parenthesis(alloc.src_lines, row.saturating_sub(1)) {
|
||||
Some((curly_row, curly_col)) => {
|
||||
let surroundings =
|
||||
Region::from_rows_cols(start_row, start_col, curly_row, curly_col);
|
||||
let region = Region::from_row_col(curly_row, curly_col);
|
||||
|
||||
let doc = alloc.stack(vec![
|
||||
alloc.reflow(
|
||||
"I am partway through parsing a pattern in parentheses, but I got stuck here:",
|
||||
),
|
||||
alloc.region_with_subregion(surroundings, region),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("I need this parenthesis to be indented more. Try adding more spaces before it!"),
|
||||
]),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
doc,
|
||||
title: "NEED MORE INDENTATION".to_string(),
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
|
||||
let region = Region::from_row_col(row, col);
|
||||
|
||||
let doc = alloc.stack(vec![
|
||||
alloc.reflow(
|
||||
r"I am partway through parsing a pattern in parentheses, but I got stuck here:",
|
||||
),
|
||||
alloc.region_with_subregion(surroundings, region),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("I was expecting to see a closing parenthesis "),
|
||||
alloc.reflow("before this, so try adding a "),
|
||||
alloc.parser_suggestion(")"),
|
||||
alloc.reflow(" and see if that helps?"),
|
||||
]),
|
||||
note_for_record_pattern_indent(alloc),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
doc,
|
||||
title: "UNFINISHED PARENTHESES".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PInParens::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_type_report<'a>(
|
||||
alloc: &'a RocDocAllocator<'a>,
|
||||
filename: PathBuf,
|
||||
|
@ -1854,7 +1994,13 @@ fn to_type_report<'a>(
|
|||
let doc = alloc.stack(vec![
|
||||
alloc.reflow(r"I just started parsing a type, but I got stuck here:"),
|
||||
alloc.region_with_subregion(surroundings, region),
|
||||
alloc.note("I may be confused by indentation"),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow(r"I am expecting a type next, like "),
|
||||
alloc.parser_suggestion("Bool"),
|
||||
alloc.reflow(r" or "),
|
||||
alloc.parser_suggestion("List a"),
|
||||
alloc.reflow("."),
|
||||
]),
|
||||
]);
|
||||
|
||||
Report {
|
||||
|
@ -3253,22 +3399,24 @@ pub fn starts_with_keyword(rest_of_line: &str, keyword: &str) -> bool {
|
|||
}
|
||||
|
||||
fn next_line_starts_with_close_curly(source_lines: &[&str], row: Row) -> Option<(Row, Col)> {
|
||||
match source_lines.get(row as usize + 1) {
|
||||
None => None,
|
||||
next_line_starts_with_char(source_lines, row, '}')
|
||||
}
|
||||
|
||||
Some(line) => {
|
||||
let spaces_dropped = line.trim_start_matches(' ');
|
||||
match spaces_dropped.chars().next() {
|
||||
Some('}') => Some((row + 1, (line.len() - spaces_dropped.len()) as u16)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
fn next_line_starts_with_close_parenthesis(source_lines: &[&str], row: Row) -> Option<(Row, Col)> {
|
||||
next_line_starts_with_char(source_lines, row, ')')
|
||||
}
|
||||
|
||||
fn next_line_starts_with_close_square_bracket(
|
||||
source_lines: &[&str],
|
||||
row: Row,
|
||||
) -> Option<(Row, Col)> {
|
||||
next_line_starts_with_char(source_lines, row, ']')
|
||||
}
|
||||
|
||||
fn next_line_starts_with_char(
|
||||
source_lines: &[&str],
|
||||
row: Row,
|
||||
character: char,
|
||||
) -> Option<(Row, Col)> {
|
||||
match source_lines.get(row as usize + 1) {
|
||||
None => None,
|
||||
|
@ -3276,7 +3424,9 @@ fn next_line_starts_with_close_square_bracket(
|
|||
Some(line) => {
|
||||
let spaces_dropped = line.trim_start_matches(' ');
|
||||
match spaces_dropped.chars().next() {
|
||||
Some(']') => Some((row + 1, (line.len() - spaces_dropped.len()) as u16)),
|
||||
Some(c) if c == character => {
|
||||
Some((row + 1, (line.len() - spaces_dropped.len()) as u16))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4502,12 +4502,14 @@ mod test_reporting {
|
|||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── BAD TYPE VARIABLE ───────────────────────────────────────────────────────────
|
||||
── UNFINISHED TYPE ─────────────────────────────────────────────────────────────
|
||||
|
||||
I am expecting a type variable, but I got stuck here:
|
||||
I just started parsing a type, but I got stuck here:
|
||||
|
||||
1│ f : (
|
||||
^
|
||||
|
||||
I am expecting a type next, like Bool or List a.
|
||||
"#
|
||||
),
|
||||
)
|
||||
|
@ -4610,9 +4612,7 @@ mod test_reporting {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn type_apply_stray_dot() {
|
||||
// TODO good message
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
|
@ -4621,16 +4621,14 @@ mod test_reporting {
|
|||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── UNFINISHED PARENTHESES ──────────────────────────────────────────────────────
|
||||
── UNFINISHED TYPE ─────────────────────────────────────────────────────────────
|
||||
|
||||
I am partway through parsing a type in parentheses, but I got stuck
|
||||
here:
|
||||
I just started parsing a type, but I got stuck here:
|
||||
|
||||
1│ f : ( I64
|
||||
^
|
||||
1│ f : .
|
||||
^
|
||||
|
||||
I was expecting to see a closing parenthesis before this, so try
|
||||
adding a ) and see if that helps?
|
||||
I am expecting a type next, like Bool or List a.
|
||||
"#
|
||||
),
|
||||
)
|
||||
|
@ -6052,4 +6050,162 @@ mod test_reporting {
|
|||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn applied_tag_function() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
x : List [ Foo Str ]
|
||||
x = List.map [ 1, 2 ] Foo
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||||
|
||||
Something is off with the body of the `x` definition:
|
||||
|
||||
1│ x : List [ Foo Str ]
|
||||
2│ x = List.map [ 1, 2 ] Foo
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This `map` call produces:
|
||||
|
||||
List [ Foo Num a ]
|
||||
|
||||
But the type annotation on `x` says it should be:
|
||||
|
||||
List [ Foo Str ]
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pattern_in_parens_open() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
\( a
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── UNFINISHED PARENTHESES ──────────────────────────────────────────────────────
|
||||
|
||||
I am partway through parsing a pattern in parentheses, but I got stuck
|
||||
here:
|
||||
|
||||
1│ \( a
|
||||
^
|
||||
|
||||
I was expecting to see a closing parenthesis before this, so try
|
||||
adding a ) and see if that helps?
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pattern_in_parens_end_comma() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
\( a,
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── UNFINISHED PARENTHESES ──────────────────────────────────────────────────────
|
||||
|
||||
I am partway through parsing a pattern in parentheses, but I got stuck
|
||||
here:
|
||||
|
||||
1│ \( a,
|
||||
^
|
||||
|
||||
I was expecting to see a closing parenthesis before this, so try
|
||||
adding a ) and see if that helps?
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pattern_in_parens_end() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
\( a
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── UNFINISHED PARENTHESES ──────────────────────────────────────────────────────
|
||||
|
||||
I am partway through parsing a pattern in parentheses, but I got stuck
|
||||
here:
|
||||
|
||||
1│ \( a
|
||||
^
|
||||
|
||||
I was expecting to see a closing parenthesis before this, so try
|
||||
adding a ) and see if that helps?
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pattern_in_parens_indent_end() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
x = \( a
|
||||
)
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── NEED MORE INDENTATION ───────────────────────────────────────────────────────
|
||||
|
||||
I am partway through parsing a pattern in parentheses, but I got stuck
|
||||
here:
|
||||
|
||||
1│ x = \( a
|
||||
2│ )
|
||||
^
|
||||
|
||||
I need this parenthesis to be indented more. Try adding more spaces
|
||||
before it!
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pattern_in_parens_indent_open() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
\(
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── UNFINISHED PATTERN ──────────────────────────────────────────────────────────
|
||||
|
||||
I just started parsing a pattern, but I got stuck here:
|
||||
|
||||
1│ \(
|
||||
^
|
||||
|
||||
Note: I may be confused by indentation
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -566,6 +566,107 @@ mod solve_expr {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn applied_tag() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
List.map [ "a", "b" ] \elem -> Foo elem
|
||||
"#
|
||||
),
|
||||
"List [ Foo Str ]*",
|
||||
)
|
||||
}
|
||||
|
||||
// Tests (TagUnion, Func)
|
||||
#[test]
|
||||
fn applied_tag_function() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
foo = Foo
|
||||
|
||||
foo "hi"
|
||||
"#
|
||||
),
|
||||
"[ Foo Str ]*",
|
||||
)
|
||||
}
|
||||
|
||||
// Tests (TagUnion, Func)
|
||||
#[test]
|
||||
fn applied_tag_function_list_map() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
List.map [ "a", "b" ] Foo
|
||||
"#
|
||||
),
|
||||
"List [ Foo Str ]*",
|
||||
)
|
||||
}
|
||||
|
||||
// Tests (TagUnion, Func)
|
||||
#[test]
|
||||
fn applied_tag_function_list() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
[ \x -> Bar x, Foo ]
|
||||
"#
|
||||
),
|
||||
"List (a -> [ Bar a, Foo a ]*)",
|
||||
)
|
||||
}
|
||||
|
||||
// Tests (Func, TagUnion)
|
||||
#[test]
|
||||
fn applied_tag_function_list_other_way() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
[ Foo, \x -> Bar x ]
|
||||
"#
|
||||
),
|
||||
"List (a -> [ Bar a, Foo a ]*)",
|
||||
)
|
||||
}
|
||||
|
||||
// Tests (Func, TagUnion)
|
||||
#[test]
|
||||
fn applied_tag_function_record() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
foo = Foo
|
||||
|
||||
{
|
||||
x: [ foo, Foo ],
|
||||
y: [ foo, \x -> Foo x ],
|
||||
z: [ foo, \x,y -> Foo x y ]
|
||||
}
|
||||
"#
|
||||
),
|
||||
"{ x : List [ Foo ]*, y : List (a -> [ Foo a ]*), z : List (b, c -> [ Foo b c ]*) }",
|
||||
)
|
||||
}
|
||||
|
||||
// Tests (TagUnion, Func)
|
||||
#[test]
|
||||
fn applied_tag_function_with_annotation() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
x : List [ Foo I64 ]
|
||||
x = List.map [ 1, 2 ] Foo
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
"List [ Foo I64 ]",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn def_2_arg_closure() {
|
||||
infer_eq(
|
||||
|
|
|
@ -320,6 +320,32 @@ fn list_walk_substraction() {
|
|||
assert_evals_to!(r#"List.walk [ 1, 2 ] Num.sub 1"#, 2, i64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_walk_until_sum() {
|
||||
assert_evals_to!(
|
||||
r#"List.walkUntil [ 1, 2 ] (\a,b -> Continue (a + b)) 0"#,
|
||||
3,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_walk_until_even_prefix_sum() {
|
||||
assert_evals_to!(
|
||||
r#"
|
||||
helper = \a, b ->
|
||||
if Num.isEven a then
|
||||
Continue (a + b)
|
||||
|
||||
else
|
||||
Stop b
|
||||
|
||||
List.walkUntil [ 2, 4, 8, 9 ] helper 0"#,
|
||||
2 + 4 + 8,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_keep_if_empty_list_of_int() {
|
||||
assert_evals_to!(
|
||||
|
|
|
@ -404,6 +404,7 @@ mod gen_num {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f64_sqrt_zero() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
|
|
@ -2276,3 +2276,23 @@ fn function_malformed_pattern() {
|
|||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Hit an erroneous type when creating a layout for")]
|
||||
fn call_invalid_layout() {
|
||||
assert_llvm_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
f : I64 -> I64
|
||||
f = \x -> x
|
||||
|
||||
f {}
|
||||
"#
|
||||
),
|
||||
3,
|
||||
i64,
|
||||
|x| x,
|
||||
false,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
use crate::assert_evals_to;
|
||||
use crate::assert_llvm_evals_to;
|
||||
use indoc::indoc;
|
||||
use roc_std::{RocList, RocStr};
|
||||
|
||||
#[test]
|
||||
fn applied_tag_nothing_ir() {
|
||||
|
@ -974,3 +975,61 @@ fn newtype_wrapper() {
|
|||
|x: &i64| *x
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn applied_tag_function() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
x : List [ Foo Str ]
|
||||
x = List.map [ "a", "b" ] Foo
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
RocList::from_slice(&[
|
||||
RocStr::from_slice("a".as_bytes()),
|
||||
RocStr::from_slice("b".as_bytes())
|
||||
]),
|
||||
RocList<RocStr>
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn applied_tag_function_result() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
x : List (Result Str *)
|
||||
x = List.map [ "a", "b" ] Ok
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
RocList::from_slice(&[
|
||||
(1, RocStr::from_slice("a".as_bytes())),
|
||||
(1, RocStr::from_slice("b".as_bytes()))
|
||||
]),
|
||||
RocList<(i64, RocStr)>
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn applied_tag_function_linked_list() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
ConsList a : [ Nil, Cons a (ConsList a) ]
|
||||
|
||||
x : List (ConsList Str)
|
||||
x = List.map2 [ "a", "b" ] [ Nil, Cons "c" Nil ] Cons
|
||||
|
||||
when List.first x is
|
||||
Ok (Cons "a" Nil) -> 1
|
||||
_ -> 0
|
||||
"#
|
||||
),
|
||||
1,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ pub fn helper<'a>(
|
|||
src: &str,
|
||||
stdlib: &'a roc_builtins::std::StdLib,
|
||||
leak: bool,
|
||||
ignore_problems: bool,
|
||||
context: &'a inkwell::context::Context,
|
||||
) -> (&'static str, String, Library) {
|
||||
use roc_gen::llvm::build::{build_proc, build_proc_header, Scope};
|
||||
|
@ -170,7 +171,7 @@ pub fn helper<'a>(
|
|||
println!("{}", lines.join("\n"));
|
||||
|
||||
// only crash at this point if there were no delayed_errors
|
||||
if delayed_errors.is_empty() {
|
||||
if delayed_errors.is_empty() && !ignore_problems {
|
||||
assert_eq!(0, 1, "Mistakes were made");
|
||||
}
|
||||
}
|
||||
|
@ -331,7 +332,7 @@ pub fn helper<'a>(
|
|||
|
||||
#[macro_export]
|
||||
macro_rules! assert_llvm_evals_to {
|
||||
($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => {
|
||||
($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr, $ignore_problems:expr) => {
|
||||
use bumpalo::Bump;
|
||||
use inkwell::context::Context;
|
||||
use roc_gen::run_jit_function;
|
||||
|
@ -343,7 +344,7 @@ macro_rules! assert_llvm_evals_to {
|
|||
let stdlib = arena.alloc(roc_builtins::std::standard_stdlib());
|
||||
|
||||
let (main_fn_name, errors, lib) =
|
||||
$crate::helpers::eval::helper(&arena, $src, stdlib, $leak, &context);
|
||||
$crate::helpers::eval::helper(&arena, $src, stdlib, $leak, $ignore_problems, &context);
|
||||
|
||||
let transform = |success| {
|
||||
let expected = $expected;
|
||||
|
@ -354,7 +355,7 @@ macro_rules! assert_llvm_evals_to {
|
|||
};
|
||||
|
||||
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
|
||||
assert_llvm_evals_to!($src, $expected, $ty, $transform, true);
|
||||
assert_llvm_evals_to!($src, $expected, $ty, $transform, true, false);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -375,7 +376,7 @@ macro_rules! assert_evals_to {
|
|||
// parsing the source, so that there's no chance their passing
|
||||
// or failing depends on leftover state from the previous one.
|
||||
{
|
||||
assert_llvm_evals_to!($src, $expected, $ty, $transform, $leak);
|
||||
assert_llvm_evals_to!($src, $expected, $ty, $transform, $leak, false);
|
||||
}
|
||||
{
|
||||
// NOTE at the moment, the optimized tests do the same thing
|
||||
|
@ -392,7 +393,7 @@ macro_rules! assert_non_opt_evals_to {
|
|||
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
|
||||
// Same as above, except with an additional transformation argument.
|
||||
{
|
||||
assert_llvm_evals_to!($src, $expected, $ty, $transform, true);
|
||||
assert_llvm_evals_to!($src, $expected, $ty, $transform, true, false);
|
||||
}
|
||||
};
|
||||
($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => {{
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use roc_collections::all::{get_shared, relative_complement, union, MutMap, SendSet};
|
||||
use roc_collections::all::{
|
||||
default_hasher, get_shared, relative_complement, union, MutMap, SendSet,
|
||||
};
|
||||
use roc_module::ident::{Lowercase, TagName};
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_types::boolean_algebra::Bool;
|
||||
|
@ -1069,6 +1071,12 @@ fn unify_flat_type(
|
|||
problems
|
||||
}
|
||||
}
|
||||
(TagUnion(tags, ext), Func(args, closure, ret)) if tags.len() == 1 => {
|
||||
unify_tag_union_and_func(tags, args, subs, pool, ctx, ext, ret, closure, true)
|
||||
}
|
||||
(Func(args, closure, ret), TagUnion(tags, ext)) if tags.len() == 1 => {
|
||||
unify_tag_union_and_func(tags, args, subs, pool, ctx, ext, ret, closure, false)
|
||||
}
|
||||
(other1, other2) => mismatch!(
|
||||
"Trying to unify two flat types that are incompatible: {:?} ~ {:?}",
|
||||
other1,
|
||||
|
@ -1250,3 +1258,54 @@ fn is_recursion_var(subs: &Subs, var: Variable) -> bool {
|
|||
Content::RecursionVar { .. }
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments, clippy::ptr_arg)]
|
||||
fn unify_tag_union_and_func(
|
||||
tags: &MutMap<TagName, Vec<Variable>>,
|
||||
args: &Vec<Variable>,
|
||||
subs: &mut Subs,
|
||||
pool: &mut Pool,
|
||||
ctx: &Context,
|
||||
ext: &Variable,
|
||||
ret: &Variable,
|
||||
closure: &Variable,
|
||||
left: bool,
|
||||
) -> Outcome {
|
||||
use FlatType::*;
|
||||
|
||||
let (tag_name, payload) = tags.iter().next().unwrap();
|
||||
|
||||
if payload.is_empty() {
|
||||
let mut new_tags = MutMap::with_capacity_and_hasher(1, default_hasher());
|
||||
|
||||
new_tags.insert(tag_name.clone(), args.to_owned());
|
||||
|
||||
let content = Structure(TagUnion(new_tags, *ext));
|
||||
|
||||
let new_tag_union_var = fresh(subs, pool, ctx, content);
|
||||
|
||||
let problems = if left {
|
||||
unify_pool(subs, pool, new_tag_union_var, *ret)
|
||||
} else {
|
||||
unify_pool(subs, pool, *ret, new_tag_union_var)
|
||||
};
|
||||
|
||||
if problems.is_empty() {
|
||||
let desc = if left {
|
||||
subs.get(ctx.second)
|
||||
} else {
|
||||
subs.get(ctx.first)
|
||||
};
|
||||
|
||||
subs.union(ctx.first, ctx.second, desc);
|
||||
}
|
||||
|
||||
problems
|
||||
} else {
|
||||
mismatch!(
|
||||
"Trying to unify two flat types that are incompatible: {:?} ~ {:?}",
|
||||
TagUnion(tags.clone(), *ext),
|
||||
Func(args.to_owned(), *closure, *ret)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue