Merge pull request #8474 from avillega/list-with-capacity-builtin

Adds List.with_capacity builtin
This commit is contained in:
Luke Boswell 2025-11-28 10:13:10 +11:00 committed by GitHub
commit cdcd9dd05e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 188 additions and 29 deletions

View file

@ -154,6 +154,9 @@ fn replaceStrIsEmptyWithLowLevel(env: *ModuleEnv) !std.ArrayList(CIR.Def.Idx) {
if (env.common.findIdent("Builtin.List.concat")) |list_concat_ident| {
try low_level_map.put(list_concat_ident, .list_concat);
}
if (env.common.findIdent("Builtin.List.with_capacity")) |list_with_capacity_ident| {
try low_level_map.put(list_with_capacity_ident, .list_with_capacity);
}
if (env.common.findIdent("list_get_unsafe")) |list_get_unsafe_ident| {
try low_level_map.put(list_get_unsafe_ident, .list_get_unsafe);
}

View file

@ -31,6 +31,7 @@ Builtin :: [].{
len : List(_item) -> U64
is_empty : List(_item) -> Bool
concat : List(item), List(item) -> List(item)
with_capacity: U64 -> List(item)
is_eq : List(item), List(item) -> Bool
where [item.is_eq : item, item -> Bool]

View file

@ -472,6 +472,7 @@ pub const Expr = union(enum) {
list_is_empty,
list_get_unsafe,
list_concat,
list_with_capacity,
// Set operations
set_is_empty,

View file

@ -3571,39 +3571,47 @@ pub const Interpreter = struct {
const list_a: *const builtins.list.RocList = @ptrCast(@alignCast(list_a_arg.ptr.?));
const list_b: *const builtins.list.RocList = @ptrCast(@alignCast(list_b_arg.ptr.?));
// Get element layout
const elem_layout_idx = list_a_arg.layout.data.list;
const elem_layout = self.runtime_layout_store.getLayout(elem_layout_idx);
const elem_size = self.runtime_layout_store.layoutSize(elem_layout);
const elem_alignment = elem_layout.alignment(self.runtime_layout_store.targetUsize()).toByteUnits();
const elem_alignment_u32: u32 = @intCast(elem_alignment);
// Call listConcat, handling zero-sized types specially
const result_list = if (list_a_arg.layout.tag == .list_of_zst)
builtins.list.listConcat(
list_a.*,
list_b.*,
1,
0,
false,
null,
&builtins.list.rcNone,
null,
&builtins.list.rcNone,
roc_ops,
)
else blk: {
const elem_layout_idx = list_a_arg.layout.data.list;
const elem_layout = self.runtime_layout_store.getLayout(elem_layout_idx);
const elem_size = self.runtime_layout_store.layoutSize(elem_layout);
const elem_alignment: u32 = @intCast(elem_layout.alignment(self.runtime_layout_store.targetUsize()).toByteUnits());
const elements_refcounted = elem_layout.isRefcounted();
// Determine if elements are refcounted
const elements_refcounted = elem_layout.isRefcounted();
var refcount_context = RefcountContext{
.layout_store = &self.runtime_layout_store,
.elem_layout = elem_layout,
.roc_ops = roc_ops,
};
// Set up context for refcount callbacks
var refcount_context = RefcountContext{
.layout_store = &self.runtime_layout_store,
.elem_layout = elem_layout,
.roc_ops = roc_ops,
break :blk builtins.list.listConcat(
list_a.*,
list_b.*,
elem_alignment,
elem_size,
elements_refcounted,
if (elements_refcounted) @ptrCast(&refcount_context) else null,
if (elements_refcounted) &listElementInc else &builtins.list.rcNone,
if (elements_refcounted) @ptrCast(&refcount_context) else null,
if (elements_refcounted) &listElementDec else &builtins.list.rcNone,
roc_ops,
);
};
// Call listConcat with proper inc/dec callbacks.
// If elements are refcounted, pass callbacks that will inc/dec each element.
// Otherwise, pass no-op callbacks.
const result_list = builtins.list.listConcat(
list_a.*,
list_b.*,
elem_alignment_u32,
elem_size,
elements_refcounted,
if (elements_refcounted) @ptrCast(&refcount_context) else null,
if (elements_refcounted) &listElementInc else &builtins.list.rcNone,
if (elements_refcounted) @ptrCast(&refcount_context) else null,
if (elements_refcounted) &listElementDec else &builtins.list.rcNone,
roc_ops,
);
// Allocate space for the result list
const result_layout = list_a_arg.layout; // Same layout as input
var out = try self.pushRaw(result_layout, 0);
@ -3616,6 +3624,64 @@ pub const Interpreter = struct {
out.is_initialized = true;
return out;
},
.list_with_capacity => {
// List.with_capacity : U64 -> List(a)
std.debug.assert(args.len == 1);
const capacity_arg = args[0];
const capacity_value = try self.extractNumericValue(capacity_arg);
const capacity: u64 = @intCast(capacity_value.int);
std.debug.assert(return_rt_var != null);
const list_layout = try self.getRuntimeLayout(return_rt_var.?);
std.debug.assert(list_layout.tag == .list or list_layout.tag == .list_of_zst);
// Call listWithCapacity, handling zero-sized types specially
const result_list = if (list_layout.tag == .list_of_zst)
builtins.list.listWithCapacity(
@intCast(capacity),
1,
0,
false,
null,
&builtins.list.rcNone,
roc_ops,
)
else blk: {
const elem_layout_idx = list_layout.data.list;
const elem_layout = self.runtime_layout_store.getLayout(elem_layout_idx);
const elem_width = self.runtime_layout_store.layoutSize(elem_layout);
const elem_alignment = elem_layout.alignment(self.runtime_layout_store.targetUsize()).toByteUnits();
const elements_refcounted = elem_layout.isRefcounted();
var refcount_context = RefcountContext{
.layout_store = &self.runtime_layout_store,
.elem_layout = elem_layout,
.roc_ops = roc_ops,
};
break :blk builtins.list.listWithCapacity(
@intCast(capacity),
@intCast(elem_alignment),
elem_width,
elements_refcounted,
if (elements_refcounted) @ptrCast(&refcount_context) else null,
if (elements_refcounted) &listElementInc else &builtins.list.rcNone,
roc_ops,
);
};
// Allocate space for the result list
var out = try self.pushRaw(list_layout, 0);
out.is_initialized = false;
// Copy the result list structure to the output
const result_ptr: *builtins.list.RocList = @ptrCast(@alignCast(out.ptr.?));
result_ptr.* = result_list;
out.is_initialized = true;
return out;
},
.set_is_empty => {
// TODO: implement Set.is_empty
self.triggerCrash("Set.is_empty not yet implemented", false, roc_ops);

View file

@ -43,6 +43,14 @@ test "list refcount builtins - phase 12 limitation documented" {
// - "e_low_level_lambda - List.concat with strings (refcounted elements)"
// - "e_low_level_lambda - List.concat with nested lists (refcounted elements)"
// - "e_low_level_lambda - List.concat with empty string list"
// - "e_low_level_lambda - List.concat with zero-sized type"
//
// - "e_low_level_lambda - List.with_capacity of non refcounted elements creates empty list"
// - "e_low_level_lambda - List.with_capacity of str (refcounted elements) creates empty list"
// - "e_low_level_lambda - List.with_capacity of non refcounted elements can concat"
// - "e_low_level_lambda - List.with_capacity of str (refcounted elements) can concat"
// - "e_low_level_lambda - List.with_capacity without capacity, of str (refcounted elements) can concat"
// - "e_low_level_lambda - List.with_capacity of zero-sized type creates empty list"
//
// interpreter_style_test.zig:
// - "interpreter: match list pattern destructures"

View file

@ -665,6 +665,86 @@ test "e_low_level_lambda - List.concat with empty string list" {
try testing.expectEqual(@as(i128, 3), len_value);
}
test "e_low_level_lambda - List.concat with zero-sized type" {
const src =
\\x : List({})
\\x = List.concat([{}, {}], [{}, {}, {}])
\\len = List.len(x)
;
const len_value = try evalModuleAndGetInt(src, 1);
try testing.expectEqual(@as(i128, 5), len_value);
}
test "e_low_level_lambda - List.with_capacity of non refcounted elements creates empty list" {
const src =
\\x : List(U64)
\\x = List.with_capacity(10)
\\len = List.len(x)
;
const len_value = try evalModuleAndGetInt(src, 1);
try testing.expectEqual(@as(i128, 0), len_value);
}
test "e_low_level_lambda - List.with_capacity of str (refcounted elements) creates empty list" {
const src =
\\x : List(Str)
\\x = List.with_capacity(10)
\\len = List.len(x)
;
const len_value = try evalModuleAndGetInt(src, 1);
try testing.expectEqual(@as(i128, 0), len_value);
}
test "e_low_level_lambda - List.with_capacity of non refcounted elements can concat" {
const src =
\\y : List(U64)
\\y = List.with_capacity(10)
\\x = List.concat(y, [1])
\\len = List.len(x)
;
const len_value = try evalModuleAndGetInt(src, 2);
try testing.expectEqual(@as(i128, 1), len_value);
}
test "e_low_level_lambda - List.with_capacity of str (refcounted elements) can concat" {
const src =
\\y : List(Str)
\\y = List.with_capacity(10)
\\x = List.concat(y, ["hello", "world"])
\\len = List.len(x)
;
const len_value = try evalModuleAndGetInt(src, 2);
try testing.expectEqual(@as(i128, 2), len_value);
}
test "e_low_level_lambda - List.with_capacity without capacity, of str (refcounted elements) can concat" {
const src =
\\y : List(Str)
\\y = List.with_capacity(0)
\\x = List.concat(y, ["hello", "world"])
\\len = List.len(x)
;
const len_value = try evalModuleAndGetInt(src, 2);
try testing.expectEqual(@as(i128, 2), len_value);
}
test "e_low_level_lambda - List.with_capacity of zero-sized type creates empty list" {
const src =
\\x : List({})
\\x = List.with_capacity(10)
\\len = List.len(x)
;
const len_value = try evalModuleAndGetInt(src, 1);
try testing.expectEqual(@as(i128, 0), len_value);
}
test "e_low_level_lambda - Dec.to_str returns string representation of decimal" {
const src =
\\a : Dec