Merge branch 'docs-type-aliases' of github.com:rtfeldman/roc into docs-type-aliases

This commit is contained in:
Chadtech 2021-04-05 19:40:05 -04:00
commit b5656328e6
44 changed files with 961 additions and 197 deletions

View file

@ -7,6 +7,7 @@ const Allocator = mem.Allocator;
const TAG_WIDTH = 8;
const EqFn = fn (?[*]u8, ?[*]u8) callconv(.C) bool;
const CompareFn = fn (?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) u8;
const Opaque = ?[*]u8;
const Inc = fn (?[*]u8) callconv(.C) void;
@ -118,6 +119,59 @@ const Caller1 = fn (?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
const Caller2 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
const Caller3 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
pub fn listReverse(list: RocList, alignment: usize, element_width: usize) callconv(.C) RocList {
if (list.bytes) |source_ptr| {
const size = list.len();
var i: usize = 0;
var end: usize = size - 1;
if (list.isUnique()) {
const temp: [*]u8 = @ptrCast([*]u8, std.heap.c_allocator.alloc(u8, element_width) catch unreachable);
// Working from the front and back so
// we only need to go ~(n / 2) iterations.
// If the length is an odd number the middle
// element stays in the same place anyways.
while (i < (end - i)) : (i += 1) {
const last_position = end - i;
const last_element = source_ptr + (last_position * element_width);
const first_element = source_ptr + (i * element_width);
// Store Last Element in temp
@memcpy(temp, last_element, element_width);
// Swap Last Element with First Element
@memcpy(last_element, first_element, element_width);
// Swap First Element with temp
@memcpy(first_element, temp, element_width);
}
std.heap.c_allocator.free(temp[0..element_width]);
return list;
} else {
const output = RocList.allocate(std.heap.c_allocator, alignment, size, element_width);
const target_ptr = output.bytes orelse unreachable;
while (i < size) : (i += 1) {
const last_position = end - i;
@memcpy(target_ptr + (i * element_width), source_ptr + (last_position * element_width), element_width);
}
utils.decref(std.heap.c_allocator, alignment, list.bytes, size * element_width);
return output;
}
} else {
return RocList.empty();
}
}
pub fn listMap(list: RocList, transform: Opaque, caller: Caller1, alignment: usize, old_element_width: usize, new_element_width: usize) callconv(.C) RocList {
if (list.bytes) |source_ptr| {
const size = list.len();
@ -688,3 +742,81 @@ fn listRangeHelp(allocator: *Allocator, comptime T: type, low: T, high: T) RocLi
},
}
}
inline fn swapHelp(width: usize, temporary: [*]u8, ptr1: [*]u8, ptr2: [*]u8) void {
@memcpy(temporary, ptr1, width);
@memcpy(ptr1, ptr2, width);
@memcpy(ptr2, temporary, width);
}
fn swap(source_ptr: [*]u8, element_width_initial: usize, index_1: usize, index_2: usize) void {
const threshold: comptime usize = 64;
var element_width = element_width_initial;
var buffer_actual: [threshold]u8 = undefined;
var buffer: [*]u8 = buffer_actual[0..];
var element_at_i = source_ptr + (index_1 * element_width);
var element_at_j = source_ptr + (index_2 * element_width);
while (true) {
if (element_width < threshold) {
swapHelp(element_width, buffer, element_at_i, element_at_j);
return;
} else {
swapHelp(threshold, buffer, element_at_i, element_at_j);
element_at_i += threshold;
element_at_j += threshold;
element_width -= threshold;
}
}
}
fn partition(source_ptr: [*]u8, transform: Opaque, wrapper: CompareFn, element_width: usize, low: isize, high: isize) isize {
const pivot = source_ptr + (@intCast(usize, high) * element_width);
var i = (low - 1); // Index of smaller element and indicates the right position of pivot found so far
var j = low;
while (j <= high - 1) : (j += 1) {
const current_elem = source_ptr + (@intCast(usize, j) * element_width);
const ordering = wrapper(transform, current_elem, pivot);
const order = @intToEnum(utils.Ordering, ordering);
switch (order) {
utils.Ordering.LT => {
// the current element is smaller than the pivot; swap it
i += 1;
swap(source_ptr, element_width, @intCast(usize, i), @intCast(usize, j));
},
utils.Ordering.EQ, utils.Ordering.GT => {},
}
}
swap(source_ptr, element_width, @intCast(usize, i + 1), @intCast(usize, high));
return (i + 1);
}
fn quicksort(source_ptr: [*]u8, transform: Opaque, wrapper: CompareFn, element_width: usize, low: isize, high: isize) void {
if (low < high) {
// partition index
const pi = partition(source_ptr, transform, wrapper, element_width, low, high);
_ = quicksort(source_ptr, transform, wrapper, element_width, low, pi - 1); // before pi
_ = quicksort(source_ptr, transform, wrapper, element_width, pi + 1, high); // after pi
}
}
pub fn listSortWith(input: RocList, transform: Opaque, wrapper: CompareFn, alignment: usize, element_width: usize) callconv(.C) RocList {
var list = input.makeUnique(std.heap.c_allocator, alignment, element_width);
if (list.bytes) |source_ptr| {
const low = 0;
const high: isize = @intCast(isize, list.len()) - 1;
quicksort(source_ptr, transform, wrapper, element_width, low, high);
}
return list;
}

View file

@ -20,6 +20,8 @@ comptime {
exportListFn(list.listRepeat, "repeat");
exportListFn(list.listAppend, "append");
exportListFn(list.listRange, "range");
exportListFn(list.listReverse, "reverse");
exportListFn(list.listSortWith, "sort_with");
}
// Dict Module

View file

@ -157,3 +157,9 @@ pub const RocResult = extern struct {
return !self.isOk();
}
};
pub const Ordering = packed enum(u8) {
EQ = 0,
GT = 1,
LT = 2,
};

View file

@ -76,3 +76,5 @@ pub const LIST_CONTAINS: &str = "roc_builtins.list.contains";
pub const LIST_REPEAT: &str = "roc_builtins.list.repeat";
pub const LIST_APPEND: &str = "roc_builtins.list.append";
pub const LIST_RANGE: &str = "roc_builtins.list.range";
pub const LIST_REVERSE: &str = "roc_builtins.list.reverse";
pub const LIST_SORT_WITH: &str = "roc_builtins.list.sort_with";

View file

@ -958,6 +958,22 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
top_level_function(vec![list_type(flex(TVAR1))], Box::new(bool_type())),
);
// sortWith : List a, (a, a -> Ordering) -> List a
add_type(
Symbol::LIST_SORT_WITH,
top_level_function(
vec![
list_type(flex(TVAR1)),
closure(
vec![flex(TVAR1), flex(TVAR1)],
TVAR2,
Box::new(ordering_type()),
),
],
Box::new(list_type(flex(TVAR1))),
),
);
// Dict module
// Dict.hashTestOnly : Nat, v -> Nat

View file

@ -91,6 +91,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
LIST_WALK => list_walk,
LIST_WALK_BACKWARDS => list_walk_backwards,
LIST_WALK_UNTIL => list_walk_until,
LIST_SORT_WITH => list_sort_with,
DICT_TEST_HASH => dict_hash_test_only,
DICT_LEN => dict_len,
DICT_EMPTY => dict_empty,
@ -2118,6 +2119,11 @@ fn list_map3(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_4(symbol, LowLevel::ListMap3, var_store)
}
/// List.sortWith : List a, (a, a -> Ordering) -> List a
fn list_sort_with(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::ListSortWith, var_store)
}
/// Dict.hashTestOnly : k, v -> Nat
fn dict_hash_test_only(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::Hash, var_store)

View file

@ -1,9 +1,9 @@
/// Helpers for interacting with the zig that generates bitcode
use crate::debug_info_init;
use crate::llvm::build::{set_name, Env, FAST_CALL_CONV};
use crate::llvm::build::{set_name, Env, C_CALL_CONV, FAST_CALL_CONV};
use crate::llvm::convert::basic_type_from_layout;
use crate::llvm::refcounting::{decrement_refcount_layout, increment_refcount_layout, Mode};
use inkwell::attributes::{Attribute, AttributeLoc};
/// Helpers for interacting with the zig that generates bitcode
use inkwell::types::{BasicType, BasicTypeEnum};
use inkwell::values::{BasicValueEnum, CallSiteValue, FunctionValue, InstructionValue};
use inkwell::AddressSpace;
@ -383,3 +383,101 @@ pub fn build_eq_wrapper<'a, 'ctx, 'env>(
function_value
}
pub fn build_compare_wrapper<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
layout: &Layout<'a>,
) -> FunctionValue<'ctx> {
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let symbol = Symbol::GENERIC_COMPARE_REF;
let fn_name = layout_ids
.get(symbol, &layout)
.to_symbol_string(symbol, &env.interns);
let function_value = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => {
let arg_type = env.context.i8_type().ptr_type(AddressSpace::Generic);
let function_value = crate::llvm::refcounting::build_header_help(
env,
&fn_name,
env.context.i8_type().into(),
&[arg_type.into(), arg_type.into(), arg_type.into()],
);
// we expose this function to zig; must use c calling convention
function_value.set_call_conventions(C_CALL_CONV);
let kind_id = Attribute::get_named_enum_kind_id("alwaysinline");
debug_assert!(kind_id > 0);
let attr = env.context.create_enum_attribute(kind_id, 1);
function_value.add_attribute(AttributeLoc::Function, attr);
let entry = env.context.append_basic_block(function_value, "entry");
env.builder.position_at_end(entry);
debug_info_init!(env, function_value);
let mut it = function_value.get_param_iter();
let function_ptr = it.next().unwrap().into_pointer_value();
let value_ptr1 = it.next().unwrap().into_pointer_value();
let value_ptr2 = it.next().unwrap().into_pointer_value();
set_name(
function_ptr.into(),
Symbol::ARG_1.ident_string(&env.interns),
);
set_name(value_ptr1.into(), Symbol::ARG_2.ident_string(&env.interns));
set_name(value_ptr2.into(), Symbol::ARG_3.ident_string(&env.interns));
let value_type = basic_type_from_layout(env.arena, env.context, layout, env.ptr_bytes);
let function_type = env
.context
.i8_type()
.fn_type(&[value_type, value_type], false)
.ptr_type(AddressSpace::Generic);
let value_ptr_type = value_type.ptr_type(AddressSpace::Generic);
let function_cast =
env.builder
.build_bitcast(function_ptr, function_type, "load_opaque");
let value_cast1 = env
.builder
.build_bitcast(value_ptr1, value_ptr_type, "load_opaque")
.into_pointer_value();
let value_cast2 = env
.builder
.build_bitcast(value_ptr2, value_ptr_type, "load_opaque")
.into_pointer_value();
let value1 = env.builder.build_load(value_cast1, "load_opaque");
let value2 = env.builder.build_load(value_cast2, "load_opaque");
let call = env.builder.build_call(
function_cast.into_pointer_value(),
&[value1, value2],
"call_user_defined_function",
);
let result = call.try_as_basic_value().left().unwrap();
// IMPORTANT! we call a user function, so it has the fast calling convention
call.set_call_convention(FAST_CALL_CONV);
env.builder.build_return(Some(&result));
function_value
}
};
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
function_value
}

View file

@ -8,7 +8,7 @@ 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_range, list_repeat, list_reverse,
list_set, list_single, list_walk_help,
list_set, list_single, list_sort_with, 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,
@ -3693,7 +3693,7 @@ fn run_low_level<'a, 'ctx, 'env>(
let inplace = get_inplace_from_layout(layout);
list_reverse(env, parent, inplace, list, list_layout)
list_reverse(env, inplace, list, list_layout)
}
ListConcat => {
debug_assert_eq!(args.len(), 2);
@ -3949,6 +3949,22 @@ fn run_low_level<'a, 'ctx, 'env>(
list_join(env, inplace, parent, list, outer_list_layout)
}
ListSortWith => {
// List.sortWith : List a, (a, a -> Ordering) -> List a
debug_assert_eq!(args.len(), 2);
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
let func = load_symbol(scope, &args[1]);
match list_layout {
Layout::Builtin(Builtin::EmptyList) => empty_list(env),
Layout::Builtin(Builtin::List(_, element_layout)) => {
list_sort_with(env, layout_ids, func, list, element_layout)
}
_ => unreachable!("invalid list layout"),
}
}
NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumLogUnchecked | NumSin | NumCos
| NumCeiling | NumFloor | NumToFloat | NumIsFinite | NumAtan | NumAcos | NumAsin => {
debug_assert_eq!(args.len(), 1);

View file

@ -1,7 +1,7 @@
#![allow(clippy::too_many_arguments)]
use crate::llvm::bitcode::{
build_dec_wrapper, build_eq_wrapper, build_inc_wrapper, build_transform_caller,
call_bitcode_fn, call_void_bitcode_fn,
build_compare_wrapper, build_dec_wrapper, build_eq_wrapper, build_inc_wrapper,
build_transform_caller, call_bitcode_fn, call_void_bitcode_fn,
};
use crate::llvm::build::{
allocate_with_refcount_help, cast_basic_basic, complex_bitcast, Env, InPlace,
@ -12,7 +12,6 @@ use crate::llvm::refcounting::{
};
use inkwell::builder::Builder;
use inkwell::context::Context;
use inkwell::types::BasicType;
use inkwell::types::{BasicTypeEnum, PointerType};
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue};
use inkwell::{AddressSpace, IntPredicate};
@ -337,93 +336,14 @@ pub fn list_join<'a, 'ctx, 'env>(
}
}
/// List.reverse : List elem -> List elem
pub fn list_reverse_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
inplace: InPlace,
length: IntValue<'ctx>,
source_ptr: PointerValue<'ctx>,
dest_ptr: PointerValue<'ctx>,
) {
let builder = env.builder;
let ctx = env.context;
// constant 1i64
let one = ctx.i64_type().const_int(1, false);
let low_alloca = builder.build_alloca(ctx.i64_type(), "low");
let high_alloca = builder.build_alloca(ctx.i64_type(), "high");
let high_val = builder.build_int_sub(length, one, "subtract 1");
builder.build_store(low_alloca, ctx.i64_type().const_zero());
builder.build_store(high_alloca, high_val);
// while (high > low)
let condition_bb = ctx.append_basic_block(parent, "condition");
builder.build_unconditional_branch(condition_bb);
builder.position_at_end(condition_bb);
let high = builder.build_load(high_alloca, "high").into_int_value();
let low = builder.build_load(low_alloca, "low").into_int_value();
// if updating in-place, then the "middle element" can be left untouched
// otherwise, the middle element needs to be copied over from the source to the target
let predicate = match inplace {
InPlace::InPlace => IntPredicate::SGT,
InPlace::Clone => IntPredicate::SGE,
};
let condition = builder.build_int_compare(predicate, high, low, "loopcond");
let body_bb = ctx.append_basic_block(parent, "body");
let cont_bb = ctx.append_basic_block(parent, "cont");
builder.build_conditional_branch(condition, body_bb, cont_bb);
// loop body
builder.position_at_end(body_bb);
// assumption: calculating pointer offsets for both the source and target is
let mut low_ptr = unsafe { builder.build_in_bounds_gep(source_ptr, &[low], "low_ptr") };
let mut high_ptr = unsafe { builder.build_in_bounds_gep(source_ptr, &[high], "high_ptr") };
// TODO use memmove?
let low_value = builder.build_load(low_ptr, "load_low");
let high_value = builder.build_load(high_ptr, "load_high");
// swap the two values
if let InPlace::Clone = inplace {
low_ptr = unsafe { builder.build_in_bounds_gep(dest_ptr, &[low], "low_ptr") };
high_ptr = unsafe { builder.build_in_bounds_gep(dest_ptr, &[high], "high_ptr") };
}
builder.build_store(high_ptr, low_value);
builder.build_store(low_ptr, high_value);
builder.build_store(low_alloca, builder.build_int_add(low, one, "increment"));
builder.build_store(high_alloca, builder.build_int_sub(high, one, "decrement"));
builder.build_unconditional_branch(condition_bb);
// continuation
builder.position_at_end(cont_bb);
}
/// List.reverse : List elem -> List elem
pub fn list_reverse<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
output_inplace: InPlace,
_output_inplace: InPlace,
list: BasicValueEnum<'ctx>,
list_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let ctx = env.context;
let wrapper_struct = list.into_struct_value();
let (input_inplace, element_layout) = match *list_layout {
let (_, element_layout) = match *list_layout {
Layout::Builtin(Builtin::EmptyList) => (
InPlace::InPlace,
// this pointer will never actually be dereferenced
@ -440,74 +360,27 @@ pub fn list_reverse<'a, 'ctx, 'env>(
_ => unreachable!("Invalid layout {:?} in List.reverse", list_layout),
};
let list_type = basic_type_from_layout(env.arena, env.context, &element_layout, env.ptr_bytes);
let ptr_type = list_type.ptr_type(AddressSpace::Generic);
let list_i128 = complex_bitcast(env.builder, list, env.context.i128_type().into(), "to_i128");
let list_ptr = load_list_ptr(builder, wrapper_struct, ptr_type);
let length = list_len(builder, list.into_struct_value());
let element_width = env
.ptr_int()
.const_int(element_layout.stack_size(env.ptr_bytes) as u64, false);
match input_inplace {
InPlace::InPlace => {
list_reverse_help(env, parent, input_inplace, length, list_ptr, list_ptr);
let alignment = element_layout.alignment_bytes(env.ptr_bytes);
let alignment_iv = env.ptr_int().const_int(alignment as u64, false);
list
}
let output = call_bitcode_fn(
env,
&[list_i128, alignment_iv.into(), element_width.into()],
&bitcode::LIST_REVERSE,
);
InPlace::Clone => {
let len_0_block = ctx.append_basic_block(parent, "len_0_block");
let len_1_block = ctx.append_basic_block(parent, "len_1_block");
let len_n_block = ctx.append_basic_block(parent, "len_n_block");
let cont_block = ctx.append_basic_block(parent, "cont_block");
let one = ctx.i64_type().const_int(1, false);
let zero = ctx.i64_type().const_zero();
let result = builder.build_alloca(ptr_type, "result");
builder.build_switch(
length,
len_n_block,
&[(zero, len_0_block), (one, len_1_block)],
);
// build block for length 0
{
builder.position_at_end(len_0_block);
// store NULL pointer there
builder.build_store(result, ptr_type.const_zero());
builder.build_unconditional_branch(cont_block);
}
// build block for length 1
{
builder.position_at_end(len_1_block);
let new_list_ptr = clone_list(env, output_inplace, &element_layout, one, list_ptr);
builder.build_store(result, new_list_ptr);
builder.build_unconditional_branch(cont_block);
}
// build block for length > 1
{
builder.position_at_end(len_n_block);
let new_list_ptr = allocate_list(env, output_inplace, &element_layout, length);
list_reverse_help(env, parent, InPlace::Clone, length, list_ptr, new_list_ptr);
// store new list pointer there
builder.build_store(result, new_list_ptr);
builder.build_unconditional_branch(cont_block);
}
builder.position_at_end(cont_block);
let new_list_ptr = builder.build_load(result, "result").into_pointer_value();
store_list(env, new_list_ptr, length)
}
}
complex_bitcast(
env.builder,
output,
collection(env.context, env.ptr_bytes).into(),
"from_i128",
)
}
pub fn list_get_unsafe<'a, 'ctx, 'env>(
@ -1132,6 +1005,52 @@ pub fn list_keep_result<'a, 'ctx, 'env>(
)
}
/// List.sortWith : List a, (a, a -> Ordering) -> List a
pub fn list_sort_with<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
transform: BasicValueEnum<'ctx>,
list: BasicValueEnum<'ctx>,
element_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
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");
let transform_ptr = transform.into_pointer_value();
let compare_wrapper = build_compare_wrapper(env, layout_ids, element_layout)
.as_global_value()
.as_pointer_value();
let element_width = env
.ptr_int()
.const_int(element_layout.stack_size(env.ptr_bytes) as u64, false);
let alignment = element_layout.alignment_bytes(env.ptr_bytes);
let alignment_iv = env.ptr_int().const_int(alignment as u64, false);
let output = call_bitcode_fn(
env,
&[
list_i128,
env.builder
.build_bitcast(transform_ptr, u8_ptr, "to_opaque"),
compare_wrapper.into(),
alignment_iv.into(),
element_width.into(),
],
bitcode::LIST_SORT_WITH,
);
complex_bitcast(
env.builder,
output,
collection(env.context, env.ptr_bytes).into(),
"from_i128",
)
}
/// List.map : List before, (before -> after) -> List after
pub fn list_map<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,

View file

@ -37,6 +37,7 @@ pub enum LowLevel {
ListWalkBackwards,
ListKeepOks,
ListKeepErrs,
ListSortWith,
DictSize,
DictEmpty,
DictInsert,

View file

@ -756,6 +756,9 @@ define_builtins! {
// A caller (wrapper) that we pass to zig for it to be able to call Roc functions
20 ZIG_FUNCTION_CALLER: "#zig_function_caller"
// a caller (wrapper) for comparison
21 GENERIC_COMPARE_REF: "#generic_compare_ref"
}
1 NUM: "Num" => {
0 NUM_NUM: "Num" imported // the Num.Num type alias
@ -919,6 +922,7 @@ define_builtins! {
28 LIST_PRODUCT_MUL: "#productmul"
29 LIST_WALK_UNTIL: "walkUntil"
30 LIST_RANGE: "range"
31 LIST_SORT_WITH: "sortWith"
}
5 RESULT: "Result" => {
0 RESULT_RESULT: "Result" imported // the Result.Result type alias

View file

@ -659,6 +659,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
ListWalk | ListWalkUntil | ListWalkBackwards => {
arena.alloc_slice_copy(&[owned, irrelevant, owned])
}
ListSortWith => arena.alloc_slice_copy(&[owned, irrelevant]),
// TODO when we have lists with capacity (if ever)
// List.append should own its first argument

View file

@ -1839,19 +1839,30 @@ fn cleanup_because_exception() {
#[test]
fn list_range() {
assert_evals_to!("List.range 0 -1", RocList::from_slice(&[]), RocList<i64>);
assert_evals_to!("List.range 0 0", RocList::from_slice(&[0]), RocList<i64>);
assert_evals_to!(
indoc!("List.range 0 -1"),
RocList::from_slice(&[]),
RocList<i64>
);
assert_evals_to!(
indoc!("List.range 0 0"),
RocList::from_slice(&[0]),
RocList<i64>
);
assert_evals_to!(
indoc!("List.range 0 5"),
"List.range 0 5",
RocList::from_slice(&[0, 1, 2, 3, 4]),
RocList<i64>
);
}
#[test]
fn list_sort_with() {
assert_evals_to!(
"List.sortWith [] Num.compare",
RocList::from_slice(&[]),
RocList<i64>
);
assert_evals_to!(
"List.sortWith [ 4,3,2,1 ] Num.compare",
RocList::from_slice(&[1, 2, 3, 4]),
RocList<i64>
);
assert_evals_to!(
"List.sortWith [ 1,2,3,4] (\\a,b -> Num.compare b a)",
RocList::from_slice(&[4, 3, 2, 1]),
RocList<i64>
);
}