mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 15:21:12 +00:00
Merge branch 'trunk' into str-join
This commit is contained in:
commit
4bd84f4b6f
10 changed files with 351 additions and 85 deletions
|
@ -224,6 +224,7 @@ fn jit_to_ast_help<'a>(
|
||||||
Layout::Union(UnionLayout::Recursive(_))
|
Layout::Union(UnionLayout::Recursive(_))
|
||||||
| Layout::Union(UnionLayout::NullableWrapped { .. })
|
| Layout::Union(UnionLayout::NullableWrapped { .. })
|
||||||
| Layout::Union(UnionLayout::NullableUnwrapped { .. })
|
| Layout::Union(UnionLayout::NullableUnwrapped { .. })
|
||||||
|
| Layout::Union(UnionLayout::NonNullableUnwrapped(_))
|
||||||
| Layout::RecursivePointer => {
|
| Layout::RecursivePointer => {
|
||||||
todo!("add support for rendering recursive tag unions in the REPL")
|
todo!("add support for rendering recursive tag unions in the REPL")
|
||||||
}
|
}
|
||||||
|
@ -305,6 +306,9 @@ fn ptr_to_ast<'a>(
|
||||||
let (tag_name, payload_vars) = tags.iter().next().unwrap();
|
let (tag_name, payload_vars) = tags.iter().next().unwrap();
|
||||||
single_tag_union_to_ast(env, ptr, field_layouts, tag_name.clone(), payload_vars)
|
single_tag_union_to_ast(env, ptr, field_layouts, tag_name.clone(), payload_vars)
|
||||||
}
|
}
|
||||||
|
Content::Structure(FlatType::EmptyRecord) => {
|
||||||
|
struct_to_ast(env, ptr, &[], &MutMap::default())
|
||||||
|
}
|
||||||
other => {
|
other => {
|
||||||
unreachable!(
|
unreachable!(
|
||||||
"Something had a Struct layout, but instead of a Record type, it had: {:?}",
|
"Something had a Struct layout, but instead of a Record type, it had: {:?}",
|
||||||
|
|
|
@ -199,6 +199,11 @@ mod repl_eval {
|
||||||
expect_success("[]", "[] : List *");
|
expect_success("[]", "[] : List *");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn literal_empty_list_empty_record() {
|
||||||
|
expect_success("[ {} ]", "[ {} ] : List {}");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn literal_num_list() {
|
fn literal_num_list() {
|
||||||
expect_success("[ 1, 2, 3 ]", "[ 1, 2, 3 ] : List (Num *)");
|
expect_success("[ 1, 2, 3 ]", "[ 1, 2, 3 ] : List (Num *)");
|
||||||
|
|
|
@ -805,8 +805,11 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
|
||||||
Tag {
|
Tag {
|
||||||
union_size,
|
union_size,
|
||||||
arguments,
|
arguments,
|
||||||
|
tag_layout,
|
||||||
..
|
..
|
||||||
} if *union_size == 1 => {
|
} if *union_size == 1
|
||||||
|
&& matches!(tag_layout, Layout::Union(UnionLayout::NonRecursive(_))) =>
|
||||||
|
{
|
||||||
let it = arguments.iter();
|
let it = arguments.iter();
|
||||||
|
|
||||||
let ctx = env.context;
|
let ctx = env.context;
|
||||||
|
@ -1037,6 +1040,83 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
|
||||||
data_ptr.into()
|
data_ptr.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Tag {
|
||||||
|
arguments,
|
||||||
|
tag_layout: Layout::Union(UnionLayout::NonNullableUnwrapped(fields)),
|
||||||
|
union_size,
|
||||||
|
tag_id,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
debug_assert_eq!(*union_size, 1);
|
||||||
|
debug_assert_eq!(*tag_id, 0);
|
||||||
|
debug_assert_eq!(arguments.len(), fields.len());
|
||||||
|
|
||||||
|
let struct_layout =
|
||||||
|
Layout::Union(UnionLayout::NonRecursive(env.arena.alloc([*fields])));
|
||||||
|
|
||||||
|
let ptr_size = env.ptr_bytes;
|
||||||
|
let ctx = env.context;
|
||||||
|
let builder = env.builder;
|
||||||
|
|
||||||
|
// Determine types
|
||||||
|
let num_fields = arguments.len() + 1;
|
||||||
|
let mut field_types = Vec::with_capacity_in(num_fields, env.arena);
|
||||||
|
let mut field_vals = Vec::with_capacity_in(num_fields, env.arena);
|
||||||
|
|
||||||
|
for (field_symbol, tag_field_layout) in arguments.iter().zip(fields.iter()) {
|
||||||
|
let val = load_symbol(env, scope, field_symbol);
|
||||||
|
|
||||||
|
// Zero-sized fields have no runtime representation.
|
||||||
|
// The layout of the struct expects them to be dropped!
|
||||||
|
if !tag_field_layout.is_dropped_because_empty() {
|
||||||
|
let field_type =
|
||||||
|
basic_type_from_layout(env.arena, env.context, tag_field_layout, ptr_size);
|
||||||
|
|
||||||
|
field_types.push(field_type);
|
||||||
|
|
||||||
|
if let Layout::RecursivePointer = tag_field_layout {
|
||||||
|
debug_assert!(val.is_pointer_value());
|
||||||
|
|
||||||
|
// we store recursive pointers as `i64*`
|
||||||
|
let ptr = env.builder.build_bitcast(
|
||||||
|
val,
|
||||||
|
ctx.i64_type().ptr_type(AddressSpace::Generic),
|
||||||
|
"cast_recursive_pointer",
|
||||||
|
);
|
||||||
|
|
||||||
|
field_vals.push(ptr);
|
||||||
|
} else {
|
||||||
|
// this check fails for recursive tag unions, but can be helpful while debugging
|
||||||
|
|
||||||
|
field_vals.push(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the struct_type
|
||||||
|
let data_ptr = reserve_with_refcount(env, &struct_layout);
|
||||||
|
let struct_type = ctx.struct_type(field_types.into_bump_slice(), false);
|
||||||
|
let struct_ptr = env
|
||||||
|
.builder
|
||||||
|
.build_bitcast(
|
||||||
|
data_ptr,
|
||||||
|
struct_type.ptr_type(AddressSpace::Generic),
|
||||||
|
"block_of_memory_to_tag",
|
||||||
|
)
|
||||||
|
.into_pointer_value();
|
||||||
|
|
||||||
|
// Insert field exprs into struct_val
|
||||||
|
for (index, field_val) in field_vals.into_iter().enumerate() {
|
||||||
|
let field_ptr = builder
|
||||||
|
.build_struct_gep(struct_ptr, index as u32, "struct_gep")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
builder.build_store(field_ptr, field_val);
|
||||||
|
}
|
||||||
|
|
||||||
|
data_ptr.into()
|
||||||
|
}
|
||||||
|
|
||||||
Tag {
|
Tag {
|
||||||
arguments,
|
arguments,
|
||||||
tag_layout:
|
tag_layout:
|
||||||
|
@ -1266,15 +1346,17 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
|
||||||
} => {
|
} => {
|
||||||
// extract field from a record
|
// extract field from a record
|
||||||
match load_symbol_and_layout(env, scope, structure) {
|
match load_symbol_and_layout(env, scope, structure) {
|
||||||
(StructValue(argument), Layout::Struct(fields)) if fields.len() > 1 => env
|
(StructValue(argument), Layout::Struct(fields)) => {
|
||||||
.builder
|
debug_assert!(fields.len() > 1);
|
||||||
|
env.builder
|
||||||
.build_extract_value(
|
.build_extract_value(
|
||||||
argument,
|
argument,
|
||||||
*index as u32,
|
*index as u32,
|
||||||
env.arena
|
env.arena
|
||||||
.alloc(format!("struct_field_access_record_{}", index)),
|
.alloc(format!("struct_field_access_record_{}", index)),
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap()
|
||||||
|
}
|
||||||
(StructValue(argument), Layout::Closure(_, _, _)) => env
|
(StructValue(argument), Layout::Closure(_, _, _)) => env
|
||||||
.builder
|
.builder
|
||||||
.build_extract_value(
|
.build_extract_value(
|
||||||
|
@ -1283,6 +1365,38 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
|
||||||
env.arena.alloc(format!("closure_field_access_{}_", index)),
|
env.arena.alloc(format!("closure_field_access_{}_", index)),
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
(
|
||||||
|
PointerValue(argument),
|
||||||
|
Layout::Union(UnionLayout::NonNullableUnwrapped(fields)),
|
||||||
|
) => {
|
||||||
|
let struct_layout = Layout::Struct(fields);
|
||||||
|
let struct_type = basic_type_from_layout(
|
||||||
|
env.arena,
|
||||||
|
env.context,
|
||||||
|
&struct_layout,
|
||||||
|
env.ptr_bytes,
|
||||||
|
);
|
||||||
|
|
||||||
|
let cast_argument = env
|
||||||
|
.builder
|
||||||
|
.build_bitcast(
|
||||||
|
argument,
|
||||||
|
struct_type.ptr_type(AddressSpace::Generic),
|
||||||
|
"cast_rosetree_like",
|
||||||
|
)
|
||||||
|
.into_pointer_value();
|
||||||
|
|
||||||
|
let ptr = env
|
||||||
|
.builder
|
||||||
|
.build_struct_gep(
|
||||||
|
cast_argument,
|
||||||
|
*index as u32,
|
||||||
|
env.arena.alloc(format!("non_nullable_unwrapped_{}", index)),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
env.builder.build_load(ptr, "load_rosetree_like")
|
||||||
|
}
|
||||||
(other, layout) => unreachable!(
|
(other, layout) => unreachable!(
|
||||||
"can only index into struct layout\nValue: {:?}\nLayout: {:?}\nIndex: {:?}",
|
"can only index into struct layout\nValue: {:?}\nLayout: {:?}\nIndex: {:?}",
|
||||||
other, layout, index
|
other, layout, index
|
||||||
|
@ -1814,6 +1928,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
|
||||||
} else {
|
} else {
|
||||||
basic_type_from_layout(env.arena, context, &layout, env.ptr_bytes)
|
basic_type_from_layout(env.arena, context, &layout, env.ptr_bytes)
|
||||||
};
|
};
|
||||||
|
|
||||||
let alloca = create_entry_block_alloca(
|
let alloca = create_entry_block_alloca(
|
||||||
env,
|
env,
|
||||||
parent,
|
parent,
|
||||||
|
@ -2317,6 +2432,7 @@ fn build_switch_ir<'a, 'ctx, 'env>(
|
||||||
debug_assert!(cond_value.is_pointer_value());
|
debug_assert!(cond_value.is_pointer_value());
|
||||||
extract_tag_discriminant_ptr(env, cond_value.into_pointer_value())
|
extract_tag_discriminant_ptr(env, cond_value.into_pointer_value())
|
||||||
}
|
}
|
||||||
|
NonNullableUnwrapped(_) => unreachable!("there is no tag to switch on"),
|
||||||
NullableWrapped { nullable_id, .. } => {
|
NullableWrapped { nullable_id, .. } => {
|
||||||
// we match on the discriminant, not the whole Tag
|
// we match on the discriminant, not the whole Tag
|
||||||
cond_layout = Layout::Builtin(Builtin::Int64);
|
cond_layout = Layout::Builtin(Builtin::Int64);
|
||||||
|
|
|
@ -146,6 +146,10 @@ pub fn basic_type_from_layout<'ctx>(
|
||||||
let block = block_of_memory_slices(context, &[&other_fields[1..]], ptr_bytes);
|
let block = block_of_memory_slices(context, &[&other_fields[1..]], ptr_bytes);
|
||||||
block.ptr_type(AddressSpace::Generic).into()
|
block.ptr_type(AddressSpace::Generic).into()
|
||||||
}
|
}
|
||||||
|
NonNullableUnwrapped(fields) => {
|
||||||
|
let block = block_of_memory_slices(context, &[fields], ptr_bytes);
|
||||||
|
block.ptr_type(AddressSpace::Generic).into()
|
||||||
|
}
|
||||||
NonRecursive(_) => block_of_memory(context, layout, ptr_bytes),
|
NonRecursive(_) => block_of_memory(context, layout, ptr_bytes),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::llvm::build::{
|
use crate::llvm::build::{
|
||||||
cast_basic_basic, cast_block_of_memory_to_tag, create_entry_block_alloca, set_name, Env, Scope,
|
cast_basic_basic, cast_block_of_memory_to_tag, set_name, Env, FAST_CALL_CONV,
|
||||||
FAST_CALL_CONV, LLVM_SADD_WITH_OVERFLOW_I64,
|
LLVM_SADD_WITH_OVERFLOW_I64,
|
||||||
};
|
};
|
||||||
use crate::llvm::build_list::{incrementing_elem_loop, list_len, load_list};
|
use crate::llvm::build_list::{incrementing_elem_loop, list_len, load_list};
|
||||||
use crate::llvm::convert::{
|
use crate::llvm::convert::{
|
||||||
|
@ -455,6 +455,19 @@ fn modify_refcount_layout<'a, 'ctx, 'env>(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NonNullableUnwrapped(fields) => {
|
||||||
|
debug_assert!(value.is_pointer_value());
|
||||||
|
|
||||||
|
build_rec_union(
|
||||||
|
env,
|
||||||
|
layout_ids,
|
||||||
|
mode,
|
||||||
|
&*env.arena.alloc([*fields]),
|
||||||
|
value.into_pointer_value(),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Recursive(tags) => {
|
Recursive(tags) => {
|
||||||
debug_assert!(value.is_pointer_value());
|
debug_assert!(value.is_pointer_value());
|
||||||
build_rec_union(
|
build_rec_union(
|
||||||
|
@ -578,25 +591,12 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>(
|
||||||
);
|
);
|
||||||
builder.set_current_debug_location(&ctx, loc);
|
builder.set_current_debug_location(&ctx, loc);
|
||||||
|
|
||||||
let mut scope = Scope::default();
|
|
||||||
|
|
||||||
// Add args to scope
|
// Add args to scope
|
||||||
let arg_symbol = Symbol::ARG_1;
|
let arg_symbol = Symbol::ARG_1;
|
||||||
let arg_val = fn_val.get_param_iter().next().unwrap();
|
let arg_val = fn_val.get_param_iter().next().unwrap();
|
||||||
|
|
||||||
set_name(arg_val, arg_symbol.ident_string(&env.interns));
|
set_name(arg_val, arg_symbol.ident_string(&env.interns));
|
||||||
|
|
||||||
let alloca = create_entry_block_alloca(
|
|
||||||
env,
|
|
||||||
fn_val,
|
|
||||||
arg_val.get_type(),
|
|
||||||
arg_symbol.ident_string(&env.interns),
|
|
||||||
);
|
|
||||||
|
|
||||||
builder.build_store(alloca, arg_val);
|
|
||||||
|
|
||||||
scope.insert(arg_symbol, (layout.clone(), alloca));
|
|
||||||
|
|
||||||
let parent = fn_val;
|
let parent = fn_val;
|
||||||
let original_wrapper = arg_val.into_struct_value();
|
let original_wrapper = arg_val.into_struct_value();
|
||||||
|
|
||||||
|
@ -698,25 +698,12 @@ fn modify_refcount_str_help<'a, 'ctx, 'env>(
|
||||||
);
|
);
|
||||||
builder.set_current_debug_location(&ctx, loc);
|
builder.set_current_debug_location(&ctx, loc);
|
||||||
|
|
||||||
let mut scope = Scope::default();
|
|
||||||
|
|
||||||
// Add args to scope
|
// Add args to scope
|
||||||
let arg_symbol = Symbol::ARG_1;
|
let arg_symbol = Symbol::ARG_1;
|
||||||
let arg_val = fn_val.get_param_iter().next().unwrap();
|
let arg_val = fn_val.get_param_iter().next().unwrap();
|
||||||
|
|
||||||
set_name(arg_val, arg_symbol.ident_string(&env.interns));
|
set_name(arg_val, arg_symbol.ident_string(&env.interns));
|
||||||
|
|
||||||
let alloca = create_entry_block_alloca(
|
|
||||||
env,
|
|
||||||
fn_val,
|
|
||||||
arg_val.get_type(),
|
|
||||||
arg_symbol.ident_string(&env.interns),
|
|
||||||
);
|
|
||||||
|
|
||||||
builder.build_store(alloca, arg_val);
|
|
||||||
|
|
||||||
scope.insert(arg_symbol, (layout.clone(), alloca));
|
|
||||||
|
|
||||||
let parent = fn_val;
|
let parent = fn_val;
|
||||||
|
|
||||||
let str_wrapper = arg_val.into_struct_value();
|
let str_wrapper = arg_val.into_struct_value();
|
||||||
|
@ -731,7 +718,7 @@ fn modify_refcount_str_help<'a, 'ctx, 'env>(
|
||||||
IntPredicate::SGT,
|
IntPredicate::SGT,
|
||||||
len,
|
len,
|
||||||
ptr_int(ctx, env.ptr_bytes).const_zero(),
|
ptr_int(ctx, env.ptr_bytes).const_zero(),
|
||||||
"len > 0",
|
"is_big_str",
|
||||||
);
|
);
|
||||||
|
|
||||||
// the block we'll always jump to when we're done
|
// the block we'll always jump to when we're done
|
||||||
|
@ -926,6 +913,10 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
|
||||||
false
|
false
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
// to increment/decrement the cons-cell itself
|
||||||
|
let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, value_ptr);
|
||||||
|
let call_mode = mode_to_call_mode(fn_val, mode);
|
||||||
|
|
||||||
let ctx = env.context;
|
let ctx = env.context;
|
||||||
let cont_block = ctx.append_basic_block(parent, "cont");
|
let cont_block = ctx.append_basic_block(parent, "cont");
|
||||||
if is_nullable {
|
if is_nullable {
|
||||||
|
@ -950,10 +941,6 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
|
||||||
// next, make a jump table for all possible values of the tag_id
|
// next, make a jump table for all possible values of the tag_id
|
||||||
let mut cases = Vec::with_capacity_in(tags.len(), env.arena);
|
let mut cases = Vec::with_capacity_in(tags.len(), env.arena);
|
||||||
|
|
||||||
let merge_block = env
|
|
||||||
.context
|
|
||||||
.append_basic_block(parent, pick("increment_merge", "decrement_merge"));
|
|
||||||
|
|
||||||
builder.set_current_debug_location(&context, loc);
|
builder.set_current_debug_location(&context, loc);
|
||||||
|
|
||||||
for (tag_id, field_layouts) in tags.iter().enumerate() {
|
for (tag_id, field_layouts) in tags.iter().enumerate() {
|
||||||
|
@ -988,6 +975,11 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
|
||||||
)
|
)
|
||||||
.into_pointer_value();
|
.into_pointer_value();
|
||||||
|
|
||||||
|
// defer actually performing the refcount modifications until after the current cell has
|
||||||
|
// been decremented, see below
|
||||||
|
let mut deferred_rec = Vec::new_in(env.arena);
|
||||||
|
let mut deferred_nonrec = Vec::new_in(env.arena);
|
||||||
|
|
||||||
for (i, field_layout) in field_layouts.iter().enumerate() {
|
for (i, field_layout) in field_layouts.iter().enumerate() {
|
||||||
if let Layout::RecursivePointer = field_layout {
|
if let Layout::RecursivePointer = field_layout {
|
||||||
// this field has type `*i64`, but is really a pointer to the data we want
|
// this field has type `*i64`, but is really a pointer to the data we want
|
||||||
|
@ -1010,13 +1002,8 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
|
||||||
union_type.ptr_type(AddressSpace::Generic).into(),
|
union_type.ptr_type(AddressSpace::Generic).into(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// recursively decrement the field
|
deferred_rec.push(recursive_field_ptr);
|
||||||
let call_name = pick("recursive_tag_increment", "recursive_tag_decrement");
|
|
||||||
call_help(env, fn_val, mode, recursive_field_ptr, call_name);
|
|
||||||
} else if field_layout.contains_refcounted() {
|
} else if field_layout.contains_refcounted() {
|
||||||
// TODO this loads the whole field onto the stack;
|
|
||||||
// that's wasteful if e.g. the field is a big record, where only
|
|
||||||
// some fields are actually refcounted.
|
|
||||||
let elem_pointer = env
|
let elem_pointer = env
|
||||||
.builder
|
.builder
|
||||||
.build_struct_gep(struct_ptr, i as u32, "gep_recursive_pointer")
|
.build_struct_gep(struct_ptr, i as u32, "gep_recursive_pointer")
|
||||||
|
@ -1027,11 +1014,31 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
|
||||||
pick("increment_struct_field", "decrement_struct_field"),
|
pick("increment_struct_field", "decrement_struct_field"),
|
||||||
);
|
);
|
||||||
|
|
||||||
modify_refcount_layout(env, parent, layout_ids, mode, field, field_layout);
|
deferred_nonrec.push((field, field_layout));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
env.builder.build_unconditional_branch(merge_block);
|
// OPTIMIZATION
|
||||||
|
//
|
||||||
|
// We really would like `inc/dec` to be tail-recursive; it gives roughly a 2X speedup on linked
|
||||||
|
// lists. To achieve it, we must first load all fields that we want to inc/dec (done above)
|
||||||
|
// and store them on the stack, then modify (and potentially free) the current cell, then
|
||||||
|
// actually inc/dec the fields.
|
||||||
|
refcount_ptr.modify(call_mode, &layout, env);
|
||||||
|
|
||||||
|
for (field, field_layout) in deferred_nonrec {
|
||||||
|
modify_refcount_layout(env, parent, layout_ids, mode, field, field_layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
let call_name = pick("recursive_tag_increment", "recursive_tag_decrement");
|
||||||
|
for ptr in deferred_rec {
|
||||||
|
// recursively decrement the field
|
||||||
|
let call = call_help(env, fn_val, mode, ptr, call_name);
|
||||||
|
call.set_tail_call(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// this function returns void
|
||||||
|
builder.build_return(None);
|
||||||
|
|
||||||
cases.push((
|
cases.push((
|
||||||
env.context.i64_type().const_int(tag_id as u64, false),
|
env.context.i64_type().const_int(tag_id as u64, false),
|
||||||
|
@ -1054,20 +1061,22 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
|
||||||
// read the tag_id
|
// read the tag_id
|
||||||
let current_tag_id = rec_union_read_tag(env, value_ptr);
|
let current_tag_id = rec_union_read_tag(env, value_ptr);
|
||||||
|
|
||||||
|
let merge_block = env
|
||||||
|
.context
|
||||||
|
.append_basic_block(parent, pick("increment_merge", "decrement_merge"));
|
||||||
|
|
||||||
// switch on it
|
// switch on it
|
||||||
env.builder
|
env.builder
|
||||||
.build_switch(current_tag_id, merge_block, &cases);
|
.build_switch(current_tag_id, merge_block, &cases);
|
||||||
}
|
|
||||||
|
|
||||||
env.builder.position_at_end(merge_block);
|
env.builder.position_at_end(merge_block);
|
||||||
|
|
||||||
// increment/decrement the cons-cell itself
|
// increment/decrement the cons-cell itself
|
||||||
let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, value_ptr);
|
|
||||||
let call_mode = mode_to_call_mode(fn_val, mode);
|
|
||||||
refcount_ptr.modify(call_mode, &layout, env);
|
refcount_ptr.modify(call_mode, &layout, env);
|
||||||
|
|
||||||
// this function returns void
|
// this function returns void
|
||||||
builder.build_return(None);
|
builder.build_return(None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rec_union_read_tag<'a, 'ctx, 'env>(
|
fn rec_union_read_tag<'a, 'ctx, 'env>(
|
||||||
|
@ -1093,7 +1102,7 @@ fn call_help<'a, 'ctx, 'env>(
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
value: BasicValueEnum<'ctx>,
|
value: BasicValueEnum<'ctx>,
|
||||||
call_name: &str,
|
call_name: &str,
|
||||||
) {
|
) -> inkwell::values::CallSiteValue<'ctx> {
|
||||||
let call = match mode {
|
let call = match mode {
|
||||||
Mode::Inc(inc_amount) => {
|
Mode::Inc(inc_amount) => {
|
||||||
let rc_increment = ptr_int(env.context, env.ptr_bytes).const_int(inc_amount, false);
|
let rc_increment = ptr_int(env.context, env.ptr_bytes).const_int(inc_amount, false);
|
||||||
|
@ -1105,6 +1114,8 @@ fn call_help<'a, 'ctx, 'env>(
|
||||||
};
|
};
|
||||||
|
|
||||||
call.set_call_convention(FAST_CALL_CONV);
|
call.set_call_convention(FAST_CALL_CONV);
|
||||||
|
|
||||||
|
call
|
||||||
}
|
}
|
||||||
|
|
||||||
fn modify_refcount_union<'a, 'ctx, 'env>(
|
fn modify_refcount_union<'a, 'ctx, 'env>(
|
||||||
|
|
|
@ -27,6 +27,11 @@ mod gen_list {
|
||||||
assert_evals_to!("[]", RocList::from_slice(&[]), RocList<i64>);
|
assert_evals_to!("[]", RocList::from_slice(&[]), RocList<i64>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_literal_empty_record() {
|
||||||
|
assert_evals_to!("[{}]", RocList::from_slice(&[()]), RocList<()>);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn int_singleton_list_literal() {
|
fn int_singleton_list_literal() {
|
||||||
assert_evals_to!("[1, 2]", RocList::from_slice(&[1, 2]), RocList<i64>);
|
assert_evals_to!("[1, 2]", RocList::from_slice(&[1, 2]), RocList<i64>);
|
||||||
|
|
|
@ -1936,29 +1936,23 @@ mod gen_primitives {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore]
|
|
||||||
fn rosetree_basic() {
|
fn rosetree_basic() {
|
||||||
assert_non_opt_evals_to!(
|
assert_non_opt_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
app "test" provides [ main ] to "./platform"
|
app "test" provides [ main ] to "./platform"
|
||||||
|
|
||||||
# RoseTree
|
|
||||||
Tree a : [ Tree a (List (Tree a)) ]
|
Tree a : [ Tree a (List (Tree a)) ]
|
||||||
|
|
||||||
tree : a, List (Tree a) -> Tree a
|
|
||||||
tree = \a, t -> Tree a t
|
|
||||||
|
|
||||||
singleton : a -> Tree a
|
singleton : a -> Tree a
|
||||||
singleton = \x -> Tree x []
|
singleton = \x -> Tree x []
|
||||||
|
|
||||||
main : Bool
|
main : Bool
|
||||||
main =
|
main =
|
||||||
x : I64
|
x : Tree F64
|
||||||
x = 1
|
x = singleton 3
|
||||||
|
when x is
|
||||||
when tree x [ singleton 5, singleton 3 ] is
|
Tree 3.0 _ -> True
|
||||||
Tree 0x1 _ -> True
|
|
||||||
_ -> False
|
_ -> False
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
|
|
|
@ -602,7 +602,21 @@ fn to_relevant_branch_help<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Wrapped::RecordOrSingleTagUnion => {
|
Wrapped::RecordOrSingleTagUnion => {
|
||||||
todo!("this should need a special index, right?")
|
let sub_positions = arguments.into_iter().enumerate().map(
|
||||||
|
|(index, (pattern, _))| {
|
||||||
|
(
|
||||||
|
Path::Index {
|
||||||
|
index: index as u64,
|
||||||
|
tag_id,
|
||||||
|
path: Box::new(path.clone()),
|
||||||
|
},
|
||||||
|
Guard::NoGuard,
|
||||||
|
pattern,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
start.extend(sub_positions);
|
||||||
|
start.extend(end);
|
||||||
}
|
}
|
||||||
Wrapped::MultiTagUnion => {
|
Wrapped::MultiTagUnion => {
|
||||||
let sub_positions = arguments.into_iter().enumerate().map(
|
let sub_positions = arguments.into_iter().enumerate().map(
|
||||||
|
@ -1037,6 +1051,10 @@ fn path_to_expr_help<'a>(
|
||||||
|
|
||||||
match variant {
|
match variant {
|
||||||
NonRecursive(layouts) | Recursive(layouts) => layouts[*tag_id as usize],
|
NonRecursive(layouts) | Recursive(layouts) => layouts[*tag_id as usize],
|
||||||
|
NonNullableUnwrapped(fields) => {
|
||||||
|
debug_assert_eq!(*tag_id, 0);
|
||||||
|
fields
|
||||||
|
}
|
||||||
NullableWrapped {
|
NullableWrapped {
|
||||||
nullable_id,
|
nullable_id,
|
||||||
other_tags: layouts,
|
other_tags: layouts,
|
||||||
|
|
|
@ -834,6 +834,8 @@ impl Wrapped {
|
||||||
},
|
},
|
||||||
_ => Some(Wrapped::MultiTagUnion),
|
_ => Some(Wrapped::MultiTagUnion),
|
||||||
},
|
},
|
||||||
|
NonNullableUnwrapped(_) => Some(Wrapped::RecordOrSingleTagUnion),
|
||||||
|
|
||||||
NullableWrapped { .. } | NullableUnwrapped { .. } => {
|
NullableWrapped { .. } | NullableUnwrapped { .. } => {
|
||||||
Some(Wrapped::MultiTagUnion)
|
Some(Wrapped::MultiTagUnion)
|
||||||
}
|
}
|
||||||
|
@ -1127,9 +1129,11 @@ impl<'a> Stmt<'a> {
|
||||||
use Stmt::*;
|
use Stmt::*;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Let(symbol, expr, _, cont) => alloc
|
Let(symbol, expr, _layout, cont) => alloc
|
||||||
.text("let ")
|
.text("let ")
|
||||||
.append(symbol_to_doc(alloc, *symbol))
|
.append(symbol_to_doc(alloc, *symbol))
|
||||||
|
//.append(" : ")
|
||||||
|
//.append(alloc.text(format!("{:?}", layout)))
|
||||||
.append(" = ")
|
.append(" = ")
|
||||||
.append(expr.to_doc(alloc))
|
.append(expr.to_doc(alloc))
|
||||||
.append(";")
|
.append(";")
|
||||||
|
@ -2738,6 +2742,7 @@ pub fn with_hole<'a>(
|
||||||
use WrappedVariant::*;
|
use WrappedVariant::*;
|
||||||
let (tag, layout) = match variant {
|
let (tag, layout) = match variant {
|
||||||
Recursive { sorted_tag_layouts } => {
|
Recursive { sorted_tag_layouts } => {
|
||||||
|
debug_assert!(sorted_tag_layouts.len() > 1);
|
||||||
let tag_id_symbol = env.unique_symbol();
|
let tag_id_symbol = env.unique_symbol();
|
||||||
opt_tag_id_symbol = Some(tag_id_symbol);
|
opt_tag_id_symbol = Some(tag_id_symbol);
|
||||||
|
|
||||||
|
@ -2758,6 +2763,7 @@ pub fn with_hole<'a>(
|
||||||
layouts.push(arg_layouts);
|
layouts.push(arg_layouts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug_assert!(layouts.len() > 1);
|
||||||
let layout =
|
let layout =
|
||||||
Layout::Union(UnionLayout::Recursive(layouts.into_bump_slice()));
|
Layout::Union(UnionLayout::Recursive(layouts.into_bump_slice()));
|
||||||
|
|
||||||
|
@ -2771,6 +2777,35 @@ pub fn with_hole<'a>(
|
||||||
|
|
||||||
(tag, layout)
|
(tag, layout)
|
||||||
}
|
}
|
||||||
|
NonNullableUnwrapped {
|
||||||
|
fields,
|
||||||
|
tag_name: wrapped_tag_name,
|
||||||
|
} => {
|
||||||
|
debug_assert_eq!(tag_name, wrapped_tag_name);
|
||||||
|
|
||||||
|
opt_tag_id_symbol = None;
|
||||||
|
|
||||||
|
field_symbols = {
|
||||||
|
let mut temp =
|
||||||
|
Vec::with_capacity_in(field_symbols_temp.len(), arena);
|
||||||
|
|
||||||
|
temp.extend(field_symbols_temp.iter().map(|r| r.1));
|
||||||
|
|
||||||
|
temp.into_bump_slice()
|
||||||
|
};
|
||||||
|
|
||||||
|
let layout = Layout::Union(UnionLayout::NonNullableUnwrapped(fields));
|
||||||
|
|
||||||
|
let tag = Expr::Tag {
|
||||||
|
tag_layout: layout.clone(),
|
||||||
|
tag_name,
|
||||||
|
tag_id: tag_id as u8,
|
||||||
|
union_size,
|
||||||
|
arguments: field_symbols,
|
||||||
|
};
|
||||||
|
|
||||||
|
(tag, layout)
|
||||||
|
}
|
||||||
NonRecursive { sorted_tag_layouts } => {
|
NonRecursive { sorted_tag_layouts } => {
|
||||||
let tag_id_symbol = env.unique_symbol();
|
let tag_id_symbol = env.unique_symbol();
|
||||||
opt_tag_id_symbol = Some(tag_id_symbol);
|
opt_tag_id_symbol = Some(tag_id_symbol);
|
||||||
|
@ -6138,6 +6173,7 @@ fn from_can_pattern_help<'a>(
|
||||||
temp
|
temp
|
||||||
};
|
};
|
||||||
|
|
||||||
|
debug_assert!(layouts.len() > 1);
|
||||||
let layout =
|
let layout =
|
||||||
Layout::Union(UnionLayout::Recursive(layouts.into_bump_slice()));
|
Layout::Union(UnionLayout::Recursive(layouts.into_bump_slice()));
|
||||||
|
|
||||||
|
@ -6150,6 +6186,51 @@ fn from_can_pattern_help<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NonNullableUnwrapped {
|
||||||
|
tag_name: w_tag_name,
|
||||||
|
fields,
|
||||||
|
} => {
|
||||||
|
debug_assert_eq!(&w_tag_name, tag_name);
|
||||||
|
|
||||||
|
ctors.push(Ctor {
|
||||||
|
tag_id: TagId(0_u8),
|
||||||
|
name: tag_name.clone(),
|
||||||
|
arity: fields.len(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let union = crate::exhaustive::Union {
|
||||||
|
render_as: RenderAs::Tag,
|
||||||
|
alternatives: ctors,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena);
|
||||||
|
|
||||||
|
debug_assert_eq!(arguments.len(), argument_layouts.len());
|
||||||
|
let it = argument_layouts.iter();
|
||||||
|
|
||||||
|
for ((_, loc_pat), layout) in arguments.iter().zip(it) {
|
||||||
|
mono_args.push((
|
||||||
|
from_can_pattern_help(
|
||||||
|
env,
|
||||||
|
layout_cache,
|
||||||
|
&loc_pat.value,
|
||||||
|
assignments,
|
||||||
|
)?,
|
||||||
|
layout.clone(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let layout = Layout::Union(UnionLayout::NonNullableUnwrapped(fields));
|
||||||
|
|
||||||
|
Pattern::AppliedTag {
|
||||||
|
tag_name: tag_name.clone(),
|
||||||
|
tag_id: tag_id as u8,
|
||||||
|
arguments: mono_args,
|
||||||
|
union,
|
||||||
|
layout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
NullableWrapped {
|
NullableWrapped {
|
||||||
sorted_tag_layouts: ref tags,
|
sorted_tag_layouts: ref tags,
|
||||||
nullable_id,
|
nullable_id,
|
||||||
|
|
|
@ -43,8 +43,11 @@ pub enum UnionLayout<'a> {
|
||||||
/// e.g. `Result a e : [ Ok a, Err e ]`
|
/// e.g. `Result a e : [ Ok a, Err e ]`
|
||||||
NonRecursive(&'a [&'a [Layout<'a>]]),
|
NonRecursive(&'a [&'a [Layout<'a>]]),
|
||||||
/// A recursive tag union
|
/// A recursive tag union
|
||||||
/// e.g. `RoseTree a : [ Tree a (List (RoseTree a)) ]`
|
/// e.g. `Expr : [ Sym Str, Add Expr Expr ]`
|
||||||
Recursive(&'a [&'a [Layout<'a>]]),
|
Recursive(&'a [&'a [Layout<'a>]]),
|
||||||
|
/// A recursive tag union with just one constructor
|
||||||
|
/// e.g. `RoseTree a : [ Tree a (List (RoseTree a)) ]`
|
||||||
|
NonNullableUnwrapped(&'a [Layout<'a>]),
|
||||||
/// A recursive tag union where the non-nullable variant(s) store the tag id
|
/// A recursive tag union where the non-nullable variant(s) store the tag id
|
||||||
/// e.g. `FingerTree a : [ Empty, Single a, More (Some a) (FingerTree (Tuple a)) (Some a) ]`
|
/// e.g. `FingerTree a : [ Empty, Single a, More (Some a) (FingerTree (Tuple a)) (Some a) ]`
|
||||||
/// see also: https://youtu.be/ip92VMpf_-A?t=164
|
/// see also: https://youtu.be/ip92VMpf_-A?t=164
|
||||||
|
@ -485,7 +488,10 @@ impl<'a> Layout<'a> {
|
||||||
NonRecursive(tags) => tags
|
NonRecursive(tags) => tags
|
||||||
.iter()
|
.iter()
|
||||||
.all(|tag_layout| tag_layout.iter().all(|field| field.safe_to_memcpy())),
|
.all(|tag_layout| tag_layout.iter().all(|field| field.safe_to_memcpy())),
|
||||||
Recursive(_) | NullableWrapped { .. } | NullableUnwrapped { .. } => {
|
Recursive(_)
|
||||||
|
| NullableWrapped { .. }
|
||||||
|
| NullableUnwrapped { .. }
|
||||||
|
| NonNullableUnwrapped(_) => {
|
||||||
// a recursive union will always contain a pointer, and is thus not safe to memcpy
|
// a recursive union will always contain a pointer, and is thus not safe to memcpy
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
@ -549,9 +555,10 @@ impl<'a> Layout<'a> {
|
||||||
.max()
|
.max()
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
|
|
||||||
Recursive(_) | NullableWrapped { .. } | NullableUnwrapped { .. } => {
|
Recursive(_)
|
||||||
pointer_size
|
| NullableWrapped { .. }
|
||||||
}
|
| NullableUnwrapped { .. }
|
||||||
|
| NonNullableUnwrapped(_) => pointer_size,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Closure(_, closure_layout, _) => pointer_size + closure_layout.stack_size(pointer_size),
|
Closure(_, closure_layout, _) => pointer_size + closure_layout.stack_size(pointer_size),
|
||||||
|
@ -580,9 +587,10 @@ impl<'a> Layout<'a> {
|
||||||
.map(|x| x.alignment_bytes(pointer_size))
|
.map(|x| x.alignment_bytes(pointer_size))
|
||||||
.max()
|
.max()
|
||||||
.unwrap_or(0),
|
.unwrap_or(0),
|
||||||
Recursive(_) | NullableWrapped { .. } | NullableUnwrapped { .. } => {
|
Recursive(_)
|
||||||
pointer_size
|
| NullableWrapped { .. }
|
||||||
}
|
| NullableUnwrapped { .. }
|
||||||
|
| NonNullableUnwrapped(_) => pointer_size,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Layout::Builtin(builtin) => builtin.alignment_bytes(pointer_size),
|
Layout::Builtin(builtin) => builtin.alignment_bytes(pointer_size),
|
||||||
|
@ -634,7 +642,10 @@ impl<'a> Layout<'a> {
|
||||||
.map(|ls| ls.iter())
|
.map(|ls| ls.iter())
|
||||||
.flatten()
|
.flatten()
|
||||||
.any(|f| f.contains_refcounted()),
|
.any(|f| f.contains_refcounted()),
|
||||||
Recursive(_) | NullableWrapped { .. } | NullableUnwrapped { .. } => true,
|
Recursive(_)
|
||||||
|
| NullableWrapped { .. }
|
||||||
|
| NullableUnwrapped { .. }
|
||||||
|
| NonNullableUnwrapped(_) => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Closure(_, closure_layout, _) => closure_layout.contains_refcounted(),
|
Closure(_, closure_layout, _) => closure_layout.contains_refcounted(),
|
||||||
|
@ -1116,6 +1127,9 @@ fn layout_from_flat_type<'a>(
|
||||||
other_tags: many,
|
other_tags: many,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
} else if tag_layouts.len() == 1 {
|
||||||
|
// drop the tag id
|
||||||
|
UnionLayout::NonNullableUnwrapped(&tag_layouts.pop().unwrap()[1..])
|
||||||
} else {
|
} else {
|
||||||
UnionLayout::Recursive(tag_layouts.into_bump_slice())
|
UnionLayout::Recursive(tag_layouts.into_bump_slice())
|
||||||
};
|
};
|
||||||
|
@ -1220,6 +1234,10 @@ pub enum WrappedVariant<'a> {
|
||||||
nullable_name: TagName,
|
nullable_name: TagName,
|
||||||
sorted_tag_layouts: Vec<'a, (TagName, &'a [Layout<'a>])>,
|
sorted_tag_layouts: Vec<'a, (TagName, &'a [Layout<'a>])>,
|
||||||
},
|
},
|
||||||
|
NonNullableUnwrapped {
|
||||||
|
tag_name: TagName,
|
||||||
|
fields: &'a [Layout<'a>],
|
||||||
|
},
|
||||||
NullableUnwrapped {
|
NullableUnwrapped {
|
||||||
nullable_id: bool,
|
nullable_id: bool,
|
||||||
nullable_name: TagName,
|
nullable_name: TagName,
|
||||||
|
@ -1281,6 +1299,7 @@ impl<'a> WrappedVariant<'a> {
|
||||||
(!*nullable_id as u8, *other_fields)
|
(!*nullable_id as u8, *other_fields)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
NonNullableUnwrapped { fields, .. } => (0, fields),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1299,6 +1318,7 @@ impl<'a> WrappedVariant<'a> {
|
||||||
sorted_tag_layouts.len() + 1
|
sorted_tag_layouts.len() + 1
|
||||||
}
|
}
|
||||||
NullableUnwrapped { .. } => 2,
|
NullableUnwrapped { .. } => 2,
|
||||||
|
NonNullableUnwrapped { .. } => 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1409,6 +1429,11 @@ pub fn union_sorted_tags_help<'a>(
|
||||||
} else {
|
} else {
|
||||||
UnionVariant::Unit
|
UnionVariant::Unit
|
||||||
}
|
}
|
||||||
|
} else if opt_rec_var.is_some() {
|
||||||
|
UnionVariant::Wrapped(WrappedVariant::NonNullableUnwrapped {
|
||||||
|
tag_name,
|
||||||
|
fields: layouts.into_bump_slice(),
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
UnionVariant::Unwrapped(layouts)
|
UnionVariant::Unwrapped(layouts)
|
||||||
}
|
}
|
||||||
|
@ -1517,6 +1542,7 @@ pub fn union_sorted_tags_help<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if is_recursive {
|
} else if is_recursive {
|
||||||
|
debug_assert!(answer.len() > 1);
|
||||||
WrappedVariant::Recursive {
|
WrappedVariant::Recursive {
|
||||||
sorted_tag_layouts: answer,
|
sorted_tag_layouts: answer,
|
||||||
}
|
}
|
||||||
|
@ -1585,6 +1611,7 @@ pub fn layout_from_tag_union<'a>(
|
||||||
let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena);
|
let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena);
|
||||||
tag_layouts.extend(tags.iter().map(|r| r.1));
|
tag_layouts.extend(tags.iter().map(|r| r.1));
|
||||||
|
|
||||||
|
debug_assert!(tag_layouts.len() > 1);
|
||||||
Layout::Union(UnionLayout::Recursive(tag_layouts.into_bump_slice()))
|
Layout::Union(UnionLayout::Recursive(tag_layouts.into_bump_slice()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1603,6 +1630,7 @@ pub fn layout_from_tag_union<'a>(
|
||||||
}
|
}
|
||||||
|
|
||||||
NullableUnwrapped { .. } => todo!(),
|
NullableUnwrapped { .. } => todo!(),
|
||||||
|
NonNullableUnwrapped { .. } => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1778,8 +1806,8 @@ pub fn list_layout_from_elem<'a>(
|
||||||
// If this was still a (List *) then it must have been an empty list
|
// If this was still a (List *) then it must have been an empty list
|
||||||
Ok(Layout::Builtin(Builtin::EmptyList))
|
Ok(Layout::Builtin(Builtin::EmptyList))
|
||||||
}
|
}
|
||||||
content => {
|
_ => {
|
||||||
let elem_layout = Layout::new_help(env, elem_var, content)?;
|
let elem_layout = Layout::from_var(env, elem_var)?;
|
||||||
|
|
||||||
// This is a normal list.
|
// This is a normal list.
|
||||||
Ok(Layout::Builtin(Builtin::List(
|
Ok(Layout::Builtin(Builtin::List(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue