Merge remote-tracking branch 'origin/can-builtins-simplify' into list-range

This commit is contained in:
Folkert 2021-03-31 11:34:32 +02:00
commit f2c144f58c
55 changed files with 2303 additions and 1059 deletions

View file

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

View file

@ -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");

View file

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

View file

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

View file

@ -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";

View file

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

View file

@ -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)),
}
}

View file

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

View file

@ -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);

View file

@ -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")
}

View file

@ -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() {

View file

@ -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);

View file

@ -33,9 +33,8 @@ pub enum LowLevel {
ListMapWithIndex,
ListKeepIf,
ListWalk,
ListWalkUntil,
ListWalkBackwards,
ListSum,
ListProduct,
ListKeepOks,
ListKeepErrs,
DictSize,

View file

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

View file

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

View file

@ -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);

View file

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

View file

@ -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;
"#
),

View file

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

View file

@ -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![

View file

@ -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,
}
}

View file

@ -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
"#
),
)
}
}

View file

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

View file

@ -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!(

View file

@ -404,6 +404,7 @@ mod gen_num {
);
}
#[test]
fn f64_sqrt_zero() {
assert_evals_to!(
indoc!(

View file

@ -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
);
}

View file

@ -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
);
}

View file

@ -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) => {{

View file

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