diff --git a/src/check/test/type_checking_integration.zig b/src/check/test/type_checking_integration.zig index 2d14b005f5..0103843297 100644 --- a/src/check/test/type_checking_integration.zig +++ b/src/check/test/type_checking_integration.zig @@ -2061,3 +2061,11 @@ fn checkTypesExpr( return test_env.assertLastDefType(expected); } + +test "check type - List.first returns Try" { + const source = + \\x = [10, 20, 30, 40, 50] + \\first = List.first(x) + ; + try checkTypesModule(source, .{ .pass = .last_def }, "Try(Num(_size), [ListWasEmpty])"); +} diff --git a/src/eval/comptime_evaluator.zig b/src/eval/comptime_evaluator.zig index aaa940629d..24840849b4 100644 --- a/src/eval/comptime_evaluator.zig +++ b/src/eval/comptime_evaluator.zig @@ -33,8 +33,8 @@ fn comptimeRocAlloc(alloc_args: *RocAlloc, env: *anyopaque) callconv(.c) void { const evaluator: *ComptimeEvaluator = @ptrCast(@alignCast(env)); const align_enum = std.mem.Alignment.fromByteUnits(@as(usize, @intCast(alloc_args.alignment))); - // Use C allocator for Roc's allocations to bypass GPA canary checks - const c_alloc = std.heap.c_allocator; + // Use page_allocator for Roc's allocations to isolate them from GPA + const c_alloc = std.heap.page_allocator; // Add padding in debug builds to detect buffer overflows const debug_padding = if (std.debug.runtime_safety) 16 else 0; @@ -59,11 +59,6 @@ fn comptimeRocAlloc(alloc_args: *RocAlloc, env: *anyopaque) callconv(.c) void { if (std.debug.runtime_safety) { const padding_start = base_ptr + alloc_args.length; @memset(padding_start[0..debug_padding], 0xAA); - - // Log allocations to help debug - if (alloc_args.length == 48) { - std.debug.print("[ALLOC 48 bytes] ptr=0x{x}\n", .{ptr_addr}); - } } // Return the allocation start @@ -121,8 +116,8 @@ fn comptimeRocDealloc(dealloc_args: *RocDealloc, env: *anyopaque) callconv(.c) v // Remove from tracking map _ = evaluator.roc_allocations.remove(ptr_addr); - // Free the memory using c_allocator (including padding in debug builds) - const c_alloc = std.heap.c_allocator; + // Free the memory using page_allocator (including padding in debug builds) + const c_alloc = std.heap.page_allocator; const align_enum = std.mem.Alignment.fromByteUnits(@as(usize, @intCast(dealloc_args.alignment))); const ptr: [*]u8 = @ptrFromInt(ptr_addr); const debug_padding = if (std.debug.runtime_safety) 16 else 0; @@ -134,7 +129,7 @@ fn comptimeRocRealloc(realloc_args: *RocRealloc, env: *anyopaque) callconv(.c) v const evaluator: *ComptimeEvaluator = @ptrCast(@alignCast(env)); const old_ptr_addr = @intFromPtr(realloc_args.answer); - const c_alloc = std.heap.c_allocator; + const c_alloc = std.heap.page_allocator; const debug_padding = if (std.debug.runtime_safety) 16 else 0; // Look up the old allocation size @@ -178,10 +173,9 @@ fn comptimeRocRealloc(realloc_args: *RocRealloc, env: *anyopaque) callconv(.c) v } } - // Realloc using c_allocator (include padding in both old and new sizes) - const old_ptr: [*]u8 = @ptrFromInt(old_ptr_addr); - const old_slice = old_ptr[0 .. old_size + debug_padding]; - const new_slice = c_alloc.realloc(old_slice, realloc_args.new_length + debug_padding) catch { + // Manually realloc using rawAlloc + copy + rawFree to preserve alignment + const align_enum = std.mem.Alignment.fromByteUnits(@as(usize, @intCast(realloc_args.alignment))); + const new_ptr = c_alloc.rawAlloc(realloc_args.new_length + debug_padding, align_enum, @returnAddress()) orelse { const msg = "Out of memory during compile-time evaluation (realloc)"; const crashed = RocCrashed{ .utf8_bytes = @ptrCast(@constCast(msg.ptr)), @@ -192,14 +186,22 @@ fn comptimeRocRealloc(realloc_args: *RocRealloc, env: *anyopaque) callconv(.c) v return; }; + // Copy old data to new location (NOT including padding - that's for canaries) + const old_ptr: [*]u8 = @ptrFromInt(old_ptr_addr); + const copy_size = @min(old_size, realloc_args.new_length); + @memcpy(new_ptr[0..copy_size], old_ptr[0..copy_size]); + + // Free old memory + const old_slice = old_ptr[0 .. old_size + debug_padding]; + c_alloc.rawFree(old_slice, align_enum, @returnAddress()); + // Update tracking map with new pointer and size _ = evaluator.roc_allocations.remove(old_ptr_addr); - const new_ptr_addr = @intFromPtr(new_slice.ptr); + const new_ptr_addr = @intFromPtr(new_ptr); evaluator.roc_allocations.put(new_ptr_addr, realloc_args.new_length) catch {}; // Set canary in the new allocation's padding if (std.debug.runtime_safety) { - const new_ptr = new_slice.ptr; const padding_start = new_ptr + realloc_args.new_length; @memset(padding_start[0..debug_padding], 0xAA); } diff --git a/src/eval/interpreter.zig b/src/eval/interpreter.zig index ad3856f090..b59bc07a42 100644 --- a/src/eval/interpreter.zig +++ b/src/eval/interpreter.zig @@ -1871,9 +1871,17 @@ pub const Interpreter = struct { const low_level = lambda_expr.e_low_level_lambda; const result = try self.callLowLevelBuiltin(low_level.op, arg_values, roc_ops); - // Decref all args - for (arg_values) |arg| { - arg.decref(&self.runtime_layout_store, roc_ops); + // Decref all args UNLESS the operation takes ownership of them. + // List.concat takes ownership of its arguments and manages their refcounts internally. + const takes_ownership = switch (low_level.op) { + .list_concat => true, + else => false, + }; + + if (!takes_ownership) { + for (arg_values) |arg| { + arg.decref(&self.runtime_layout_store, roc_ops); + } } return result; @@ -2115,9 +2123,17 @@ pub const Interpreter = struct { // Dispatch to actual low-level builtin implementation const result = try self.callLowLevelBuiltin(low_level.op, all_args, roc_ops); - // Decref all args - for (all_args) |arg| { - arg.decref(&self.runtime_layout_store, roc_ops); + // Decref all args UNLESS the operation takes ownership of them. + // List.concat takes ownership of its arguments and manages their refcounts internally. + const takes_ownership = switch (low_level.op) { + .list_concat => true, + else => false, + }; + + if (!takes_ownership) { + for (all_args) |arg| { + arg.decref(&self.runtime_layout_store, roc_ops); + } } return result; diff --git a/src/eval/render_helpers.zig b/src/eval/render_helpers.zig index 02cf936d70..1644a7813d 100644 --- a/src/eval/render_helpers.zig +++ b/src/eval/render_helpers.zig @@ -140,24 +140,33 @@ pub fn renderValueRocWithType(ctx: *RenderCtx, value: StackValue, rt_var: types. var out = std.array_list.AlignedManaged(u8, null).init(gpa); errdefer out.deinit(); try out.appendSlice(tag_name); + std.debug.print("[BUG] Appended tag name: {s}\n", .{tag_name}); if (acc.findFieldIndex(ctx.env, "payload")) |pidx| { const payload = try acc.getFieldByIndex(pidx); const args_range = tags.items(.args)[tag_index]; const arg_vars = ctx.runtime_types.sliceVars(toVarRange(args_range)); + std.debug.print("[BUG] Tag {s}: arg_vars.len = {}\n", .{tag_name, arg_vars.len}); if (arg_vars.len > 0) { + std.debug.print("[BUG] Entering arg_vars > 0 block\n", .{}); try out.append('('); if (arg_vars.len == 1) { + std.debug.print("[BUG] arg_vars.len == 1, rendering single arg\n", .{}); const arg_var = arg_vars[0]; const layout_idx = try ctx.layout_store.addTypeVar(arg_var, ctx.type_scope); + std.debug.print("[BUG] addTypeVar succeeded, getting layout\n", .{}); const arg_layout = ctx.layout_store.getLayout(layout_idx); + std.debug.print("[BUG] Got arg_layout, creating payload_value\n", .{}); const payload_value = StackValue{ .layout = arg_layout, .ptr = payload.ptr, .is_initialized = payload.is_initialized, }; + std.debug.print("[BUG] About to recursively render payload\n", .{}); const rendered = try renderValueRocWithType(ctx, payload_value, arg_var); defer gpa.free(rendered); + std.debug.print("[BUG] Payload rendered as: {s}, appending\n", .{rendered}); try out.appendSlice(rendered); + std.debug.print("[BUG] Appended payload\n", .{}); } else { var elem_layouts = try ctx.allocator.alloc(layout.Layout, arg_vars.len); defer ctx.allocator.free(elem_layouts); @@ -203,10 +212,17 @@ pub fn renderValueRocWithType(ctx: *RenderCtx, value: StackValue, rt_var: types. } } } + std.debug.print("[BUG] About to append closing paren\n", .{}); try out.append(')'); + std.debug.print("[BUG] Appended closing paren\n", .{}); } + } else { + std.debug.print("[BUG] No payload field found\n", .{}); } - return out.toOwnedSlice(); + std.debug.print("[BUG] Returning: {s}\n", .{out.items}); + const result = try out.toOwnedSlice(); + std.debug.print("[BUG] toOwnedSlice returned: {s} (len={})\n", .{result, result.len}); + return result; } } }, diff --git a/src/layout/store.zig b/src/layout/store.zig index 97d86fb187..4cac751739 100644 --- a/src/layout/store.zig +++ b/src/layout/store.zig @@ -537,7 +537,9 @@ pub const Store = struct { const resolved_ext = self.types_store.resolveVar(current_ext); switch (resolved_ext.desc.content) { .structure => |ext_flat_type| switch (ext_flat_type) { - .empty_tag_union => break, + .empty_tag_union => { + break; + }, .tag_union => |ext_tag_union| { if (ext_tag_union.tags.len() > 0) { num_tags += ext_tag_union.tags.len();