diff --git a/src/build/builtin_compiler/main.zig b/src/build/builtin_compiler/main.zig index fa6cee12bf..b3159cd2a0 100644 --- a/src/build/builtin_compiler/main.zig +++ b/src/build/builtin_compiler/main.zig @@ -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); } diff --git a/src/build/roc/Builtin.roc b/src/build/roc/Builtin.roc index 9fc188f3a1..0fab8b8a84 100644 --- a/src/build/roc/Builtin.roc +++ b/src/build/roc/Builtin.roc @@ -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] diff --git a/src/canonicalize/Expression.zig b/src/canonicalize/Expression.zig index d610521143..3d4bf95d91 100644 --- a/src/canonicalize/Expression.zig +++ b/src/canonicalize/Expression.zig @@ -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, diff --git a/src/eval/interpreter.zig b/src/eval/interpreter.zig index 19d6d15137..c5c5d707b0 100644 --- a/src/eval/interpreter.zig +++ b/src/eval/interpreter.zig @@ -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); diff --git a/src/eval/test/list_refcount_builtins.zig b/src/eval/test/list_refcount_builtins.zig index 710039aabb..33b95f3678 100644 --- a/src/eval/test/list_refcount_builtins.zig +++ b/src/eval/test/list_refcount_builtins.zig @@ -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" diff --git a/src/eval/test/low_level_interp_test.zig b/src/eval/test/low_level_interp_test.zig index ca6ccd0735..a5660acd2e 100644 --- a/src/eval/test/low_level_interp_test.zig +++ b/src/eval/test/low_level_interp_test.zig @@ -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