Merging in main

This commit is contained in:
Edwin Santos 2025-11-30 10:52:09 -05:00
commit 24ec833d3b
75 changed files with 2356 additions and 500 deletions

View file

@ -268,4 +268,4 @@ jobs:
# Test cross-compilation with Roc's cross-compilation system (musl + glibc)
roc-cross-compile:
uses: ./.github/workflows/ci_cross_compile.yml
uses: ./.github/workflows/ci_cross_compile.yml

View file

@ -20,8 +20,9 @@ fn configureBackend(step: *Step.Compile, target: ResolvedTarget) void {
}
}
fn isNativeOrMusl(target: ResolvedTarget) bool {
return target.query.isNativeCpu() and target.query.isNativeOs() and
fn isNativeishOrMusl(target: ResolvedTarget) bool {
return target.result.cpu.arch == builtin.target.cpu.arch and
target.query.isNativeOs() and
(target.query.isNativeAbi() or target.result.abi.isMusl());
}
@ -1081,13 +1082,25 @@ pub fn build(b: *std.Build) void {
const is_windows = target.result.os.tag == .windows;
// fx platform effectful functions test - only run when not cross-compiling
if (isNativeOrMusl(target)) {
if (isNativeishOrMusl(target)) {
// Determine the appropriate target for the fx platform host library.
// On Linux, we need to use musl explicitly because the CLI's findHostLibrary
// looks for targets/x64musl/libhost.a first, and musl produces proper static binaries.
const fx_host_target, const fx_host_target_dir: ?[]const u8 = switch (target.result.os.tag) {
.linux => switch (target.result.cpu.arch) {
.x86_64 => .{ b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .musl }), "x64musl" },
.aarch64 => .{ b.resolveTargetQuery(.{ .cpu_arch = .aarch64, .os_tag = .linux, .abi = .musl }), "arm64musl" },
else => .{ target, null },
},
else => .{ target, null },
};
// Create fx test platform host static library
const test_platform_fx_host_lib = createTestPlatformHostLib(
b,
"test_platform_fx_host",
"test/fx/platform/host.zig",
target,
fx_host_target,
optimize,
roc_modules,
);
@ -1098,6 +1111,14 @@ pub fn build(b: *std.Build) void {
copy_test_fx_host.addCopyFileToSource(test_platform_fx_host_lib.getEmittedBin(), b.pathJoin(&.{ "test/fx/platform", test_fx_host_filename }));
b.getInstallStep().dependOn(&copy_test_fx_host.step);
// On Linux, also copy to the target-specific directory so findHostLibrary finds it
if (fx_host_target_dir) |target_dir| {
copy_test_fx_host.addCopyFileToSource(
test_platform_fx_host_lib.getEmittedBin(),
b.pathJoin(&.{ "test/fx/platform/targets", target_dir, "libhost.a" }),
);
}
const fx_platform_test = b.addTest(.{
.name = "fx_platform_test",
.root_module = b.createModule(.{
@ -1118,7 +1139,7 @@ pub fn build(b: *std.Build) void {
}
var build_afl = false;
if (!isNativeOrMusl(target)) {
if (!isNativeishOrMusl(target)) {
std.log.warn("Cross compilation does not support fuzzing (Only building repro executables)", .{});
} else if (is_windows) {
// Windows does not support fuzzing - only build repro executables

View file

@ -157,6 +157,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.append")) |list_append_ident| {
try low_level_map.put(list_append_ident, .list_append);
}
if (env.common.findIdent("Builtin.List.with_capacity")) |list_with_capacity_ident| {
try low_level_map.put(list_with_capacity_ident, .list_with_capacity);
}

View file

@ -66,6 +66,8 @@ Builtin :: [].{
True
}
append : List(a), a -> List(a)
first : List(item) -> Try(item, [ListWasEmpty])
first = |list| List.get(list, 0)
@ -77,10 +79,22 @@ Builtin :: [].{
}
map : List(a), (a -> b) -> List(b)
map = |_, _| []
map = |list, transform|
# Implement using fold + concat for now
# TODO: Optimize with in-place update when list is unique and element sizes match
List.fold(list, [], |acc, item| List.concat(acc, [transform(item)]))
keep_if : List(a), (a -> Bool) -> List(a)
keep_if = |_, _| []
keep_if = |list, predicate|
List.fold(list, [], |acc, elem|
if predicate(elem) { List.concat(acc, [elem]) } else { acc }
)
drop_if : List(a), (a -> Bool) -> List(a)
drop_if = |list, predicate|
List.fold(list, [], |acc, elem|
if predicate(elem) { acc } else { List.concat(acc, [elem]) }
)
fold : List(item), state, (state, item -> state) -> state
fold = |list, init, step| {

View file

@ -11,10 +11,13 @@ const RocOps = @import("host_abi.zig").RocOps;
const RocStr = @import("str.zig").RocStr;
const increfDataPtrC = utils.increfDataPtrC;
const Opaque = ?[*]u8;
/// Pointer to the bytes of a list element or similar data
pub const Opaque = ?[*]u8;
const EqFn = *const fn (Opaque, Opaque) callconv(.c) bool;
const CompareFn = *const fn (Opaque, Opaque, Opaque) callconv(.c) u8;
const CopyFn = *const fn (Opaque, Opaque) callconv(.c) void;
/// Function copying data between 2 Opaques with a slot for the element's width
pub const CopyFallbackFn = *const fn (Opaque, Opaque, usize) callconv(.c) void;
const Inc = *const fn (?*anyopaque, ?[*]u8) callconv(.c) void;
const IncN = *const fn (?*anyopaque, ?[*]u8, usize) callconv(.c) void;
@ -531,7 +534,7 @@ pub fn listAppendUnsafe(
list: RocList,
element: Opaque,
element_width: usize,
copy: CopyFn,
copy: CopyFallbackFn,
) callconv(.c) RocList {
const old_length = list.len();
var output = list;
@ -540,22 +543,24 @@ pub fn listAppendUnsafe(
if (output.bytes) |bytes| {
if (element) |source| {
const target = bytes + old_length * element_width;
copy(target, source);
copy(target, source, element_width);
}
}
return output;
}
fn listAppend(
/// Add element to end of list. Will reserve additional space or reallocate if necessary beforehand.
pub fn listAppend(
list: RocList,
alignment: u32,
element: Opaque,
element_width: usize,
elements_refcounted: bool,
inc_context: ?*anyopaque,
inc: Inc,
update_mode: UpdateMode,
copy: CopyFn,
copy_fn: CopyFallbackFn,
roc_ops: *RocOps,
) callconv(.c) RocList {
const with_capacity = listReserve(
@ -564,11 +569,12 @@ fn listAppend(
1,
element_width,
elements_refcounted,
inc_context,
inc,
update_mode,
roc_ops,
);
return listAppendUnsafe(with_capacity, element, element_width, copy);
return listAppendUnsafe(with_capacity, element, element_width, copy_fn);
}
/// Directly mutate the given list to push an element onto the end, and then return it.
@ -1098,8 +1104,18 @@ pub fn listConcat(
dec: Dec,
roc_ops: *RocOps,
) callconv(.c) RocList {
// NOTE we always use list_a! because it is owned, we must consume it, and it may have unused capacity
if (list_b.isEmpty()) {
// Early return for empty lists - avoid unnecessary allocations
if (list_a.isEmpty()) {
if (list_b.getCapacity() == 0) {
// b could be a seamless slice, so we still need to decref.
list_b.decref(alignment, element_width, elements_refcounted, dec_context, dec, roc_ops);
return list_a;
} else {
// list_b has capacity, return it and consume list_a
list_a.decref(alignment, element_width, elements_refcounted, dec_context, dec, roc_ops);
return list_b;
}
} else if (list_b.isEmpty()) {
if (list_a.getCapacity() == 0) {
// a could be a seamless slice, so we still need to decref.
list_a.decref(alignment, element_width, elements_refcounted, dec_context, dec, roc_ops);
@ -1350,6 +1366,117 @@ pub fn listConcatUtf8(
}
}
/// Specialized copy fn which takes pointers as pointers to U8 and copies from src to dest.
pub fn copy_u8(dest: Opaque, src: Opaque, _: usize) callconv(.c) void {
const dest_ptr = @as(*u8, @ptrCast(@alignCast(dest.?)));
const src_ptr = @as(*u8, @ptrCast(@alignCast(src.?)));
dest_ptr.* = src_ptr.*;
}
/// Specialized copy fn which takes pointers as pointers to I8 and copies from src to dest.
pub fn copy_i8(dest: Opaque, src: Opaque, _: usize) callconv(.c) void {
const dest_ptr = @as(*i8, @ptrCast(@alignCast(dest.?)));
const src_ptr = @as(*i8, @ptrCast(@alignCast(src.?)));
dest_ptr.* = src_ptr.*;
}
/// Specialized copy fn which takes pointers as pointers to U16 and copies from src to dest.
pub fn copy_u16(dest: Opaque, src: Opaque, _: usize) callconv(.c) void {
const dest_ptr = @as(*u16, @ptrCast(@alignCast(dest.?)));
const src_ptr = @as(*u16, @ptrCast(@alignCast(src.?)));
dest_ptr.* = src_ptr.*;
}
/// Specialized copy fn which takes pointers as pointers to I16 and copies from src to dest.
pub fn copy_i16(dest: Opaque, src: Opaque, _: usize) callconv(.c) void {
const dest_ptr = @as(*i16, @ptrCast(@alignCast(dest.?)));
const src_ptr = @as(*i16, @ptrCast(@alignCast(src.?)));
dest_ptr.* = src_ptr.*;
}
/// Specialized copy fn which takes pointers as pointers to U32 and copies from src to dest.
pub fn copy_u32(dest: Opaque, src: Opaque, _: usize) callconv(.c) void {
const dest_ptr = @as(*u32, @ptrCast(@alignCast(dest.?)));
const src_ptr = @as(*u32, @ptrCast(@alignCast(src.?)));
dest_ptr.* = src_ptr.*;
}
/// Specialized copy fn which takes pointers as pointers to I32 and copies from src to dest.
pub fn copy_i32(dest: Opaque, src: Opaque, _: usize) callconv(.c) void {
const dest_ptr = @as(*i32, @ptrCast(@alignCast(dest.?)));
const src_ptr = @as(*i32, @ptrCast(@alignCast(src.?)));
dest_ptr.* = src_ptr.*;
}
/// Specialized copy fn which takes pointers as pointers to U64 and copies from src to dest.
pub fn copy_u64(dest: Opaque, src: Opaque, _: usize) callconv(.c) void {
const dest_ptr = @as(*u64, @ptrCast(@alignCast(dest.?)));
const src_ptr = @as(*u64, @ptrCast(@alignCast(src.?)));
dest_ptr.* = src_ptr.*;
}
/// Specialized copy fn which takes pointers as pointers to I64 and copies from src to dest.
pub fn copy_i64(dest: Opaque, src: Opaque, _: usize) callconv(.c) void {
const dest_ptr = @as(*i64, @ptrCast(@alignCast(dest.?)));
const src_ptr = @as(*i64, @ptrCast(@alignCast(src.?)));
dest_ptr.* = src_ptr.*;
}
/// Specialized copy fn which takes pointers as pointers to U128 and copies from src to dest.
pub fn copy_u128(dest: Opaque, src: Opaque, _: usize) callconv(.c) void {
const dest_ptr = @as(*u128, @ptrCast(@alignCast(dest.?)));
const src_ptr = @as(*u128, @ptrCast(@alignCast(src.?)));
dest_ptr.* = src_ptr.*;
}
/// Specialized copy fn which takes pointers as pointers to I128 and copies from src to dest.
pub fn copy_i128(dest: Opaque, src: Opaque, _: usize) callconv(.c) void {
const dest_ptr = @as(*i128, @ptrCast(@alignCast(dest.?)));
const src_ptr = @as(*i128, @ptrCast(@alignCast(src.?)));
dest_ptr.* = src_ptr.*;
}
/// Specialized copy fn which takes pointers as pointers to Boxes and copies from src to dest.
pub fn copy_box(dest: Opaque, src: Opaque, _: usize) callconv(.c) void {
const dest_ptr = @as(*usize, @ptrCast(@alignCast(dest)));
const src_ptr = @as(*usize, @ptrCast(@alignCast(src)));
dest_ptr.* = src_ptr.*;
}
/// Specialized copy fn which takes pointers as pointers to ZST Boxes and copies from src to dest.
pub fn copy_box_zst(dest: Opaque, _: Opaque, _: usize) callconv(.c) void {
const dest_ptr = @as(*usize, @ptrCast(@alignCast(dest.?)));
dest_ptr.* = 0;
}
/// Specialized copy fn which takes pointers as pointers to Lists and copies from src to dest.
pub fn copy_list(dest: Opaque, src: Opaque, _: usize) callconv(.c) void {
const dest_ptr = @as(*RocList, @ptrCast(@alignCast(dest.?)));
const src_ptr = @as(*RocList, @ptrCast(@alignCast(src.?)));
dest_ptr.* = src_ptr.*;
}
/// Specialized copy fn which takes pointers as pointers to ZST Lists and copies from src to dest.
pub fn copy_list_zst(dest: Opaque, src: Opaque, _: usize) callconv(.c) void {
const dest_ptr = @as(*RocList, @ptrCast(@alignCast(dest.?)));
const src_ptr = @as(*RocList, @ptrCast(@alignCast(src.?)));
dest_ptr.* = src_ptr.*;
}
/// Specialized copy fn which takes pointers as pointers to a RocStr and copies from src to dest.
pub fn copy_str(dest: Opaque, src: Opaque, _: usize) callconv(.c) void {
const dest_ptr = @as(*RocStr, @ptrCast(@alignCast(dest.?)));
const src_ptr = @as(*RocStr, @ptrCast(@alignCast(src.?)));
dest_ptr.* = src_ptr.*;
}
/// Specialized copy fn which takes pointers as pointers to u8 and copies from src to dest.
pub fn copy_fallback(dest: Opaque, source: Opaque, width: usize) callconv(.c) void {
const src: []u8 = source.?[0..width];
const dst: []u8 = dest.?[0..width];
@memmove(dst, src);
}
test "listConcat: non-unique with unique overlapping" {
var test_env = TestEnv.init(std.testing.allocator);
defer test_env.deinit();
@ -1693,26 +1820,15 @@ test "listAppendUnsafe basic functionality" {
var test_env = TestEnv.init(std.testing.allocator);
defer test_env.deinit();
// Copy function for u8 elements
const copy_fn = struct {
fn copy(dest: ?[*]u8, src: ?[*]u8) callconv(.c) void {
if (dest != null and src != null) {
const dest_ptr = @as(*u8, @ptrCast(@alignCast(dest)));
const src_ptr = @as(*u8, @ptrCast(@alignCast(src)));
dest_ptr.* = src_ptr.*;
}
}
}.copy;
// Create a list with some capacity
var list = listWithCapacity(10, @alignOf(u8), @sizeOf(u8), false, null, rcNone, test_env.getOps());
// Add some initial elements using listAppendUnsafe
const element1: u8 = 42;
list = listAppendUnsafe(list, @as(?[*]u8, @ptrCast(@constCast(&element1))), @sizeOf(u8), copy_fn);
list = listAppendUnsafe(list, @as(?[*]u8, @ptrCast(@constCast(&element1))), @sizeOf(u8), &copy_fallback);
const element2: u8 = 84;
list = listAppendUnsafe(list, @as(?[*]u8, @ptrCast(@constCast(&element2))), @sizeOf(u8), copy_fn);
list = listAppendUnsafe(list, @as(?[*]u8, @ptrCast(@constCast(&element2))), @sizeOf(u8), &copy_fallback);
defer list.decref(@alignOf(u8), @sizeOf(u8), false, null, rcNone, test_env.getOps());
@ -1729,22 +1845,11 @@ test "listAppendUnsafe with different types" {
var test_env = TestEnv.init(std.testing.allocator);
defer test_env.deinit();
// Copy function for i32 elements
const copy_fn = struct {
fn copy(dest: ?[*]u8, src: ?[*]u8) callconv(.c) void {
if (dest != null and src != null) {
const dest_ptr = @as(*i32, @ptrCast(@alignCast(dest)));
const src_ptr = @as(*i32, @ptrCast(@alignCast(src)));
dest_ptr.* = src_ptr.*;
}
}
}.copy;
// Test with i32
var int_list = listWithCapacity(5, @alignOf(i32), @sizeOf(i32), false, null, rcNone, test_env.getOps());
const int_val: i32 = -123;
int_list = listAppendUnsafe(int_list, @as(?[*]u8, @ptrCast(@constCast(&int_val))), @sizeOf(i32), copy_fn);
int_list = listAppendUnsafe(int_list, @as(?[*]u8, @ptrCast(@constCast(&int_val))), @sizeOf(i32), &copy_fallback);
defer int_list.decref(@alignOf(i32), @sizeOf(i32), false, null, rcNone, test_env.getOps());
@ -1760,22 +1865,11 @@ test "listAppendUnsafe with pre-allocated capacity" {
var test_env = TestEnv.init(std.testing.allocator);
defer test_env.deinit();
// Copy function for u16 elements
const copy_fn = struct {
fn copy(dest: ?[*]u8, src: ?[*]u8) callconv(.c) void {
if (dest != null and src != null) {
const dest_ptr = @as(*u16, @ptrCast(@alignCast(dest)));
const src_ptr = @as(*u16, @ptrCast(@alignCast(src)));
dest_ptr.* = src_ptr.*;
}
}
}.copy;
// Create a list with capacity (listAppendUnsafe requires pre-allocated space)
var list_with_capacity = listWithCapacity(5, @alignOf(u16), @sizeOf(u16), false, null, rcNone, test_env.getOps());
const element: u16 = 9999;
list_with_capacity = listAppendUnsafe(list_with_capacity, @as(?[*]u8, @ptrCast(@constCast(&element))), @sizeOf(u16), copy_fn);
list_with_capacity = listAppendUnsafe(list_with_capacity, @as(?[*]u8, @ptrCast(@constCast(&element))), @sizeOf(u16), &copy_fallback);
defer list_with_capacity.decref(@alignOf(u16), @sizeOf(u16), false, null, rcNone, test_env.getOps());
@ -2293,29 +2387,18 @@ test "edge case: listAppendUnsafe multiple times" {
var test_env = TestEnv.init(std.testing.allocator);
defer test_env.deinit();
// Copy function for u8 elements
const copy_fn = struct {
fn copy(dest: ?[*]u8, src: ?[*]u8) callconv(.c) void {
if (dest != null and src != null) {
const dest_ptr = @as(*u8, @ptrCast(@alignCast(dest)));
const src_ptr = @as(*u8, @ptrCast(@alignCast(src)));
dest_ptr.* = src_ptr.*;
}
}
}.copy;
// Create a list with sufficient capacity
var list = listWithCapacity(5, @alignOf(u8), @sizeOf(u8), false, null, rcNone, test_env.getOps());
// Append multiple elements
const element1: u8 = 10;
list = listAppendUnsafe(list, @as(?[*]u8, @ptrCast(@constCast(&element1))), @sizeOf(u8), copy_fn);
list = listAppendUnsafe(list, @as(?[*]u8, @ptrCast(@constCast(&element1))), @sizeOf(u8), &copy_fallback);
const element2: u8 = 20;
list = listAppendUnsafe(list, @as(?[*]u8, @ptrCast(@constCast(&element2))), @sizeOf(u8), copy_fn);
list = listAppendUnsafe(list, @as(?[*]u8, @ptrCast(@constCast(&element2))), @sizeOf(u8), &copy_fallback);
const element3: u8 = 30;
list = listAppendUnsafe(list, @as(?[*]u8, @ptrCast(@constCast(&element3))), @sizeOf(u8), copy_fn);
list = listAppendUnsafe(list, @as(?[*]u8, @ptrCast(@constCast(&element3))), @sizeOf(u8), &copy_fallback);
defer list.decref(@alignOf(u8), @sizeOf(u8), false, null, rcNone, test_env.getOps());
@ -2871,24 +2954,13 @@ test "stress: many small operations" {
var test_env = TestEnv.init(std.testing.allocator);
defer test_env.deinit();
// Copy function for u8 elements
const copy_fn = struct {
fn copy(dest: ?[*]u8, src: ?[*]u8) callconv(.c) void {
if (dest != null and src != null) {
const dest_ptr = @as(*u8, @ptrCast(@alignCast(dest)));
const src_ptr = @as(*u8, @ptrCast(@alignCast(src)));
dest_ptr.* = src_ptr.*;
}
}
}.copy;
// Start with a list with some capacity
var list = listWithCapacity(50, @alignOf(u8), @sizeOf(u8), false, null, rcNone, test_env.getOps());
// Add many elements using listAppendUnsafe
var i: u8 = 0;
while (i < 20) : (i += 1) {
list = listAppendUnsafe(list, @as(?[*]u8, @ptrCast(@constCast(&i))), @sizeOf(u8), copy_fn);
list = listAppendUnsafe(list, @as(?[*]u8, @ptrCast(@constCast(&i))), @sizeOf(u8), &copy_fallback);
}
try std.testing.expectEqual(@as(usize, 20), list.len());

View file

@ -387,18 +387,21 @@ pub fn decref(
inline fn free_ptr_to_refcount(
refcount_ptr: [*]isize,
alignment: u32,
element_alignment: u32,
elements_refcounted: bool,
roc_ops: *RocOps,
) void {
if (RC_TYPE == .none) return;
const ptr_width = @sizeOf(usize);
const required_space: usize = if (elements_refcounted) (2 * ptr_width) else ptr_width;
const extra_bytes = @max(required_space, alignment);
const extra_bytes = @max(required_space, element_alignment);
const allocation_ptr = @as([*]u8, @ptrCast(refcount_ptr)) - (extra_bytes - @sizeOf(usize));
// Use the same alignment calculation as allocateWithRefcount
const allocation_alignment = @max(ptr_width, element_alignment);
var roc_dealloc_args = RocDealloc{
.alignment = alignment,
.alignment = allocation_alignment,
.ptr = allocation_ptr,
};
@ -598,7 +601,7 @@ pub const CSlice = extern struct {
/// Returns a pointer to the data portion, not the allocation start
pub fn unsafeReallocate(
source_ptr: [*]u8,
alignment: u32,
element_alignment: u32,
old_length: usize,
new_length: usize,
element_width: usize,
@ -607,7 +610,7 @@ pub fn unsafeReallocate(
) [*]u8 {
const ptr_width: usize = @sizeOf(usize);
const required_space: usize = if (elements_refcounted) (2 * ptr_width) else ptr_width;
const extra_bytes = @max(required_space, alignment);
const extra_bytes = @max(required_space, element_alignment);
const old_width = extra_bytes + old_length * element_width;
const new_width = extra_bytes + new_length * element_width;
@ -618,8 +621,11 @@ pub fn unsafeReallocate(
const old_allocation = source_ptr - extra_bytes;
// Use the same alignment calculation as allocateWithRefcount
const allocation_alignment = @max(ptr_width, element_alignment);
var roc_realloc_args = RocRealloc{
.alignment = alignment,
.alignment = allocation_alignment,
.new_length = new_width,
.answer = old_allocation,
};

View file

@ -1753,6 +1753,10 @@ pub fn canonicalizeFile(
.platform => |h| {
self.env.module_kind = .platform;
try self.createExposedScope(h.exposes);
// Also add the 'provides' items (what platform provides to the host, e.g., main_for_host!)
// These need to be in the exposed scope so they become exports
// Platform provides uses curly braces { main_for_host! } so it's parsed as record fields
try self.addPlatformProvidesItems(h.provides);
// Extract required type signatures for type checking
// This stores the types in env.requires_types without creating local definitions
// Pass requires_rigids so R1, R2, etc. are in scope when processing signatures
@ -2531,6 +2535,17 @@ fn createExposedScope(
self.exposed_scope.deinit(gpa);
self.exposed_scope = Scope.init(false);
try self.addToExposedScope(exposes);
}
/// Add items to the exposed scope without resetting it.
/// Used for platforms which have both 'exposes' (for apps) and 'provides' (for the host).
fn addToExposedScope(
self: *Self,
exposes: AST.Collection.Idx,
) std.mem.Allocator.Error!void {
const gpa = self.env.gpa;
const collection = self.parse_ir.store.getCollection(exposes);
const exposed_items = self.parse_ir.store.exposedItemSlice(.{ .span = collection.span });
@ -2654,6 +2669,42 @@ fn createExposedScope(
}
}
/// Add platform provides items to the exposed scope.
/// Platform provides uses curly braces { main_for_host!: "main" } so it's parsed as record fields.
/// The string value is the FFI symbol name exported to the host (becomes roc__<symbol>).
fn addPlatformProvidesItems(
self: *Self,
provides: AST.Collection.Idx,
) std.mem.Allocator.Error!void {
const gpa = self.env.gpa;
const collection = self.parse_ir.store.getCollection(provides);
const record_fields = self.parse_ir.store.recordFieldSlice(.{ .span = collection.span });
for (record_fields) |field_idx| {
const field = self.parse_ir.store.getRecordField(field_idx);
// Get the identifier text from the field name token
if (self.parse_ir.tokens.resolveIdentifier(field.name)) |ident_idx| {
// Add to exposed_items for permanent storage
try self.env.addExposedById(ident_idx);
// Add to exposed_scope so it becomes an export
const dummy_idx = @as(Pattern.Idx, @enumFromInt(0));
try self.exposed_scope.put(gpa, .ident, ident_idx, dummy_idx);
// Also track in exposed_ident_texts
const token_region = self.parse_ir.tokens.resolve(@intCast(field.name));
const ident_text = self.parse_ir.env.source[token_region.start.offset..token_region.end.offset];
const region = self.parse_ir.tokenizedRegionToRegion(field.region);
_ = try self.exposed_ident_texts.getOrPut(gpa, ident_text);
if (self.exposed_ident_texts.getPtr(ident_text)) |ptr| {
ptr.* = region;
}
}
}
}
/// Process the requires_signatures from a platform header.
///
/// This extracts the required type signatures (like `main! : () => {}`) from the platform
@ -4843,13 +4894,128 @@ pub fn canonicalizeExpr(
.free_vars = null,
};
},
.local_dispatch => |_| {
const feature = try self.env.insertString("canonicalize local_dispatch expression");
const expr_idx = try self.env.pushMalformed(Expr.Idx, Diagnostic{ .not_implemented = .{
.feature = feature,
.region = Region.zero(),
} });
return CanonicalizedExpr{ .idx = expr_idx, .free_vars = null };
.local_dispatch => |local_dispatch| {
// Desugar `arg1->fn(arg2, arg3)` to `fn(arg1, arg2, arg3)`
// and `arg1->fn` to `fn(arg1)`
const region = self.parse_ir.tokenizedRegionToRegion(local_dispatch.region);
const free_vars_start = self.scratch_free_vars.top();
// Canonicalize the left expression (first argument)
const can_first_arg = try self.canonicalizeExpr(local_dispatch.left) orelse return null;
// Get the right expression to determine the function and additional args
const right_expr = self.parse_ir.store.getExpr(local_dispatch.right);
switch (right_expr) {
.apply => |apply| {
// Case: `arg1->fn(arg2, arg3)` - function call with additional args
// Check if this is a tag application
const ast_fn = self.parse_ir.store.getExpr(apply.@"fn");
if (ast_fn == .tag) {
// Tag application: `arg1->Tag(arg2)` becomes `Tag(arg1, arg2)`
const tag_expr = ast_fn.tag;
const tag_name = self.parse_ir.tokens.resolveIdentifier(tag_expr.token) orelse @panic("tag token is not an ident");
// Build args: first_arg followed by apply.args
const scratch_top = self.env.store.scratchExprTop();
try self.env.store.addScratchExpr(can_first_arg.idx);
const additional_args = self.parse_ir.store.exprSlice(apply.args);
for (additional_args) |arg| {
if (try self.canonicalizeExpr(arg)) |can_arg| {
try self.env.store.addScratchExpr(can_arg.idx);
}
}
const args_span = try self.env.store.exprSpanFrom(scratch_top);
const expr_idx = try self.env.addExpr(CIR.Expr{
.e_tag = .{
.name = tag_name,
.args = args_span,
},
}, region);
const free_vars_span = self.scratch_free_vars.spanFrom(free_vars_start);
return CanonicalizedExpr{ .idx = expr_idx, .free_vars = if (free_vars_span.len > 0) free_vars_span else null };
}
// Normal function call
const can_fn_expr = try self.canonicalizeExpr(apply.@"fn") orelse return null;
// Build args: first_arg followed by apply.args
const scratch_top = self.env.store.scratchExprTop();
try self.env.store.addScratchExpr(can_first_arg.idx);
const additional_args = self.parse_ir.store.exprSlice(apply.args);
for (additional_args) |arg| {
if (try self.canonicalizeExpr(arg)) |can_arg| {
try self.env.store.addScratchExpr(can_arg.idx);
}
}
const args_span = try self.env.store.exprSpanFrom(scratch_top);
const expr_idx = try self.env.addExpr(CIR.Expr{
.e_call = .{
.func = can_fn_expr.idx,
.args = args_span,
.called_via = CalledVia.apply,
},
}, region);
const free_vars_span = self.scratch_free_vars.spanFrom(free_vars_start);
return CanonicalizedExpr{ .idx = expr_idx, .free_vars = if (free_vars_span.len > 0) free_vars_span else null };
},
.ident, .tag => {
// Case: `arg1->fn` or `arg1->Tag` - simple function/tag call with single arg
if (right_expr == .tag) {
const tag_expr = right_expr.tag;
const tag_name = self.parse_ir.tokens.resolveIdentifier(tag_expr.token) orelse @panic("tag token is not an ident");
const scratch_top = self.env.store.scratchExprTop();
try self.env.store.addScratchExpr(can_first_arg.idx);
const args_span = try self.env.store.exprSpanFrom(scratch_top);
const expr_idx = try self.env.addExpr(CIR.Expr{
.e_tag = .{
.name = tag_name,
.args = args_span,
},
}, region);
const free_vars_span = self.scratch_free_vars.spanFrom(free_vars_start);
return CanonicalizedExpr{ .idx = expr_idx, .free_vars = if (free_vars_span.len > 0) free_vars_span else null };
}
// It's an ident
const can_fn_expr = try self.canonicalizeExpr(local_dispatch.right) orelse return null;
const scratch_top = self.env.store.scratchExprTop();
try self.env.store.addScratchExpr(can_first_arg.idx);
const args_span = try self.env.store.exprSpanFrom(scratch_top);
const expr_idx = try self.env.addExpr(CIR.Expr{
.e_call = .{
.func = can_fn_expr.idx,
.args = args_span,
.called_via = CalledVia.apply,
},
}, region);
const free_vars_span = self.scratch_free_vars.spanFrom(free_vars_start);
return CanonicalizedExpr{ .idx = expr_idx, .free_vars = if (free_vars_span.len > 0) free_vars_span else null };
},
else => {
// Unexpected expression type on right side of arrow
const feature = try self.env.insertString("arrow with complex expression");
const expr_idx = try self.env.pushMalformed(Expr.Idx, Diagnostic{ .not_implemented = .{
.feature = feature,
.region = region,
} });
return CanonicalizedExpr{ .idx = expr_idx, .free_vars = null };
},
}
},
.bin_op => |e| {
const region = self.parse_ir.tokenizedRegionToRegion(e.region);
@ -8123,7 +8289,6 @@ fn canonicalizeBlock(self: *Self, e: AST.Block) std.mem.Allocator.Error!Canonica
// canonicalize the expr directly without adding it as a statement
switch (ast_stmt) {
.expr => |expr_stmt| {
//
last_expr = try self.canonicalizeExprOrMalformed(expr_stmt.expr);
},
.dbg => |dbg_stmt| {

View file

@ -477,6 +477,10 @@ pub const Expr = union(enum) {
list_sort_with,
list_drop_at,
list_sublist,
list_append,
// Set operations
// set_is_empty,
// Bool operations
bool_is_eq,

View file

@ -440,7 +440,7 @@ pub fn init(gpa: std.mem.Allocator, source: []const u8) std.mem.Allocator.Error!
.external_decls = try CIR.ExternalDecl.SafeList.initCapacity(gpa, 16),
.imports = CIR.Import.Store.init(),
.module_name = undefined, // Will be set later during canonicalization
.module_name_idx = undefined, // Will be set later during canonicalization
.module_name_idx = Ident.Idx.NONE, // Will be set later during canonicalization
.diagnostics = CIR.Diagnostic.Span{ .span = base.DataSpan{ .start = 0, .len = 0 } },
.store = try NodeStore.initCapacity(gpa, 10_000), // Default node store capacity
.evaluation_order = null, // Will be set after canonicalization completes
@ -2624,12 +2624,19 @@ pub fn getMethodIdent(self: *const Self, type_name: []const u8, method_name: []c
const qualified = std.fmt.bufPrint(&buf, "{s}.{s}", .{ type_name, method_name }) catch return null;
return self.getIdentStoreConst().findByString(qualified);
} else {
// Need to add module prefix
// Try module-qualified name first (e.g., "Builtin.Num.U64.from_numeral")
const qualified = std.fmt.bufPrint(&buf, "{s}.{s}.{s}", .{ self.module_name, type_name, method_name }) catch return null;
return self.getIdentStoreConst().findByString(qualified);
if (self.getIdentStoreConst().findByString(qualified)) |idx| {
return idx;
}
// Fallback: try without module prefix (e.g., "Color.as_str" for app-defined types)
// This handles the case where methods are registered with just the type-qualified name
const simple_qualified = std.fmt.bufPrint(&buf, "{s}.{s}", .{ type_name, method_name }) catch return null;
return self.getIdentStoreConst().findByString(simple_qualified);
}
} else {
// Use heap allocation for large identifiers (rare case)
// Try module-qualified name first
const qualified = if (type_name.len > self.module_name.len and
std.mem.startsWith(u8, type_name, self.module_name) and
type_name[self.module_name.len] == '.')
@ -2639,7 +2646,19 @@ pub fn getMethodIdent(self: *const Self, type_name: []const u8, method_name: []c
else
std.fmt.allocPrint(self.gpa, "{s}.{s}.{s}", .{ self.module_name, type_name, method_name }) catch return null;
defer self.gpa.free(qualified);
return self.getIdentStoreConst().findByString(qualified);
if (self.getIdentStoreConst().findByString(qualified)) |idx| {
return idx;
}
// Fallback for the module-qualified case
if (type_name.len <= self.module_name.len or
!std.mem.startsWith(u8, type_name, self.module_name) or
type_name[self.module_name.len] != '.')
{
const simple_qualified = std.fmt.allocPrint(self.gpa, "{s}.{s}", .{ type_name, method_name }) catch return null;
defer self.gpa.free(simple_qualified);
return self.getIdentStoreConst().findByString(simple_qualified);
}
return null;
}
}

View file

@ -1121,15 +1121,10 @@ pub fn checkPlatformRequirements(
// Instantiate the copied variable before unifying (to avoid poisoning the cached copy)
const instantiated_required_var = try self.instantiateVar(copied_required_var, &env, .{ .explicit = required_type.region });
// Create a copy of the export's type for unification.
// This prevents unification failure from corrupting the app's actual types
// (which would cause the interpreter to fail when trying to get layouts).
const export_copy = try self.copyVar(export_var, self.cir, required_type.region);
const instantiated_export_copy = try self.instantiateVar(export_copy, &env, .{ .explicit = required_type.region });
// Unify the platform's required type with the COPY of the app's export type.
// The platform type is the "expected" type, app export copy is "actual".
_ = try self.unifyFromAnno(instantiated_required_var, instantiated_export_copy, &env);
// Unify the platform's required type with the app's export type.
// This constrains type variables in the export (e.g., closure params)
// to match the platform's expected types.
_ = try self.unifyFromAnno(instantiated_required_var, export_var, &env);
}
// Note: If the export is not found, the canonicalizer should have already reported an error
}

View file

@ -161,12 +161,12 @@ fn addRocCallAbiStub(
wip.cursor = .{ .block = entry };
// Generate actual implementation based on function name
if (std.mem.eql(u8, name, "addInts")) {
if (std.mem.eql(u8, name, "add_ints")) {
try addIntsImplementation(&wip, llvm_builder);
} else if (std.mem.eql(u8, name, "multiplyInts")) {
} else if (std.mem.eql(u8, name, "multiply_ints")) {
try multiplyIntsImplementation(&wip, llvm_builder);
} else if (std.mem.eql(u8, name, "processString")) {
// processString not supported in cross-compilation stubs - only int platform supported
} else if (std.mem.eql(u8, name, "process_string")) {
// process_string not supported in cross-compilation stubs - only int platform supported
_ = try wip.retVoid();
} else {
// Default: just return void for unknown functions
@ -180,11 +180,11 @@ fn addRocCallAbiStub(
pub fn getTestPlatformEntrypoints(allocator: Allocator, platform_type: []const u8) ![]PlatformEntrypoint {
if (std.mem.eql(u8, platform_type, "int")) {
// Based on test/int/platform/host.zig:
// extern fn roc__addInts(ops: *builtins.host_abi.RocOps, ret_ptr: *anyopaque, arg_ptr: ?*anyopaque) callconv(.c) void;
// extern fn roc__multiplyInts(ops: *builtins.host_abi.RocOps, ret_ptr: *anyopaque, arg_ptr: ?*anyopaque) callconv(.c) void;
// extern fn roc__add_ints(ops: *builtins.host_abi.RocOps, ret_ptr: *anyopaque, arg_ptr: ?*anyopaque) callconv(.c) void;
// extern fn roc__multiply_ints(ops: *builtins.host_abi.RocOps, ret_ptr: *anyopaque, arg_ptr: ?*anyopaque) callconv(.c) void;
const entrypoints = try allocator.alloc(PlatformEntrypoint, 2);
entrypoints[0] = PlatformEntrypoint{ .name = "addInts" };
entrypoints[1] = PlatformEntrypoint{ .name = "multiplyInts" };
entrypoints[0] = PlatformEntrypoint{ .name = "add_ints" };
entrypoints[1] = PlatformEntrypoint{ .name = "multiply_ints" };
return entrypoints;
}

View file

@ -181,6 +181,7 @@ const main_help =
\\Options:
\\ --opt=<size|speed|dev> Optimize the build process for binary size, execution speed, or compilation speed. Defaults to compilation speed (dev)
\\ --target=<target> Target to compile for (e.g., x64musl, x64glibc, arm64musl). Defaults to native target with musl for static linking
\\ --no-cache Force a rebuild of the interpreted host (useful for compiler and platform developers)
\\
;

View file

@ -625,7 +625,8 @@ fn generatePlatformHostShim(allocs: *Allocators, cache_dir: []const u8, entrypoi
}
// Create the complete platform shim
platform_host_shim.createInterpreterShim(&llvm_builder, entrypoints.items) catch |err| {
// Note: Symbol names include platform-specific prefixes (underscore for macOS)
platform_host_shim.createInterpreterShim(&llvm_builder, entrypoints.items, target) catch |err| {
std.log.err("Failed to create interpreter shim: {}", .{err});
return err;
};
@ -1103,15 +1104,16 @@ fn runWithWindowsHandleInheritance(allocs: *Allocators, exe_path: []const u8, sh
_ = ipc.platform.windows.CloseHandle(process_info.hProcess);
_ = ipc.platform.windows.CloseHandle(process_info.hThread);
// Check exit code
// Check exit code and propagate to parent
if (exit_code != 0) {
std.log.err("Child process {s} exited with code: {}", .{ exe_path, exit_code });
std.log.debug("Child process {s} exited with code: {}", .{ exe_path, exit_code });
if (exit_code == 0xC0000005) { // STATUS_ACCESS_VIOLATION
std.log.err("Child process crashed with access violation (segfault)", .{});
} else if (exit_code >= 0xC0000000) { // NT status codes for exceptions
std.log.err("Child process crashed with exception code: 0x{X}", .{exit_code});
}
return error.ProcessExitedWithError;
// Propagate the exit code (truncated to u8 for compatibility)
std.process.exit(@truncate(exit_code));
}
std.log.debug("Child process completed successfully", .{});
@ -1198,9 +1200,9 @@ fn runWithPosixFdInheritance(allocs: *Allocators, exe_path: []const u8, shm_hand
if (exit_code == 0) {
std.log.debug("Child process completed successfully", .{});
} else {
// The host exited with an error - it should have printed any error messages
// Propagate the exit code from the child process to our parent
std.log.debug("Child process {s} exited with code: {}", .{ temp_exe_path, exit_code });
return error.ProcessExitedWithError;
std.process.exit(exit_code);
}
},
.Signal => |signal| {
@ -1212,7 +1214,8 @@ fn runWithPosixFdInheritance(allocs: *Allocators, exe_path: []const u8, shm_hand
} else if (signal == 9) { // SIGKILL
std.log.err("Child process was killed (SIGKILL)", .{});
}
return error.ProcessKilledBySignal;
// Standard POSIX convention: exit with 128 + signal number
std.process.exit(128 +| @as(u8, @truncate(signal)));
},
.Stopped => |signal| {
std.log.err("Child process {s} stopped by signal: {}", .{ temp_exe_path, signal });
@ -1358,6 +1361,10 @@ pub fn setupSharedMemoryWithModuleEnv(allocs: *Allocators, roc_file_path: []cons
entry_count: u32,
def_indices_offset: u64,
module_envs_offset: u64,
/// Offset to platform's main.roc env (0 if no platform, entry points are in app)
platform_main_env_offset: u64,
/// Offset to app env (always present, used for e_lookup_required resolution)
app_env_offset: u64,
};
const header_ptr = try shm_allocator.create(Header);
@ -1624,7 +1631,24 @@ pub fn setupSharedMemoryWithModuleEnv(allocs: *Allocators, roc_file_path: []cons
// Store app env at the last index (N-1, after platform modules at 0..N-2)
module_env_offsets_ptr[total_module_count - 1] = @intFromPtr(app_env_ptr) - @intFromPtr(shm.base_ptr);
const exports_slice = app_env.store.sliceDefs(app_env.exports);
// Store app env offset for e_lookup_required resolution
header_ptr.app_env_offset = @intFromPtr(app_env_ptr) - @intFromPtr(shm.base_ptr);
// Entry points are defined in the platform's `provides` section.
// The platform wraps app-provided functions (from `requires`) and exports them for the host.
// For example: `provides { main_for_host!: "main" }` where `main_for_host! = main!`
const platform_env = platform_main_env orelse {
std.log.err("No platform found. Every Roc app requires a platform.", .{});
return error.NoPlatformFound;
};
const exports_slice = platform_env.store.sliceDefs(platform_env.exports);
if (exports_slice.len == 0) {
std.log.err("Platform has no exports in `provides` clause.", .{});
return error.NoEntrypointFound;
}
// Store platform env offset for entry point lookups
header_ptr.platform_main_env_offset = @intFromPtr(platform_env) - @intFromPtr(shm.base_ptr);
header_ptr.entry_count = @intCast(exports_slice.len);
const def_indices_ptr = try shm_allocator.alloc(u32, exports_slice.len);
@ -2347,15 +2371,39 @@ fn extractEntrypointsFromPlatform(allocs: *Allocators, roc_file_path: []const u8
const provides_coll = parse_ast.store.getCollection(platform_header.provides);
const provides_fields = parse_ast.store.recordFieldSlice(.{ .span = provides_coll.span });
// Extract all field names as entrypoints
// Extract FFI symbol names from provides clause
// Format: `provides { roc_identifier: "ffi_symbol_name" }`
// The string value specifies the symbol name exported to the host (becomes roc__<symbol>)
for (provides_fields) |field_idx| {
const field = parse_ast.store.getRecordField(field_idx);
const field_name = parse_ast.resolve(field.name);
// Strip trailing '!' from effectful function names for the exported symbol
const symbol_name = if (std.mem.endsWith(u8, field_name, "!"))
field_name[0 .. field_name.len - 1]
else
field_name;
// Require explicit string value for symbol name
const symbol_name = if (field.value) |value_idx| blk: {
const value_expr = parse_ast.store.getExpr(value_idx);
switch (value_expr) {
.string => |str_like| {
const parts = parse_ast.store.exprSlice(str_like.parts);
if (parts.len > 0) {
const first_part = parse_ast.store.getExpr(parts[0]);
switch (first_part) {
.string_part => |sp| break :blk parse_ast.resolve(sp.token),
else => {},
}
}
std.log.err("Invalid provides entry: string value is empty", .{});
return error.InvalidProvidesEntry;
},
.string_part => |str_part| break :blk parse_ast.resolve(str_part.token),
else => {
std.log.err("Invalid provides entry: expected string value for symbol name", .{});
return error.InvalidProvidesEntry;
},
}
} else {
const field_name = parse_ast.resolve(field.name);
std.log.err("Provides entry '{s}' missing symbol name. Use format: {{ {s}: \"symbol_name\" }}", .{ field_name, field_name });
return error.InvalidProvidesEntry;
};
try entrypoints.append(try allocs.arena.dupe(u8, symbol_name));
}

View file

@ -1,10 +1,13 @@
//! Helpers for using Zig's LLVM Builder API to generate a shim library for the
//! Roc interpreter that translates from the platform host API.
//!
//! Note: Symbol names in LLVM IR need platform-specific prefixes for macOS.
//! MachO format requires underscore prefix on all C symbols.
const std = @import("std");
const Builder = std.zig.llvm.Builder;
const WipFunction = Builder.WipFunction;
const builtin = @import("builtin");
const RocTarget = @import("target.zig").RocTarget;
/// Represents a single entrypoint that a Roc platform host expects to call.
/// Each entrypoint corresponds to a specific function the host can invoke,
@ -27,7 +30,7 @@ pub const EntryPoint = struct {
/// Roc platform functions will delegate to. The Roc interpreter provides
/// the actual implementation of this function, which acts as a dispatcher
/// based on the entry_idx parameter.
fn addRocEntrypoint(builder: *Builder) !Builder.Function.Index {
fn addRocEntrypoint(builder: *Builder, target: RocTarget) !Builder.Function.Index {
// Create pointer type for generic pointers (i8* in LLVM)
const ptr_type = try builder.ptrType(.default);
@ -36,14 +39,14 @@ fn addRocEntrypoint(builder: *Builder) !Builder.Function.Index {
const entrypoint_params = [_]Builder.Type{ .i32, ptr_type, ptr_type, ptr_type };
const entrypoint_type = try builder.fnType(.void, &entrypoint_params, .normal);
// Create function name with platform-specific prefix
// Add underscore prefix for macOS (required for MachO symbol names)
const base_name = "roc_entrypoint";
const fn_name_str = if (builtin.target.os.tag == .macos)
const full_name = if (target.isMacOS())
try std.fmt.allocPrint(builder.gpa, "_{s}", .{base_name})
else
try builder.gpa.dupe(u8, base_name);
defer builder.gpa.free(fn_name_str);
const fn_name = try builder.strtabString(fn_name_str);
defer builder.gpa.free(full_name);
const fn_name = try builder.strtabString(full_name);
// Add the extern function declaration (no body)
const entrypoint_fn = try builder.addFunction(entrypoint_type, fn_name, .default);
@ -72,7 +75,7 @@ fn addRocEntrypoint(builder: *Builder) !Builder.Function.Index {
/// 2. The pre-built Roc interpreter to handle all calls through a single dispatch mechanism
/// 3. Efficient code generation since each wrapper is just a simple function call
/// 4. Easy addition/removal of platform functions without changing the pre-built interpreter binary which is embedded in the roc cli executable.
fn addRocExportedFunction(builder: *Builder, entrypoint_fn: Builder.Function.Index, name: []const u8, entry_idx: u32) !Builder.Function.Index {
fn addRocExportedFunction(builder: *Builder, entrypoint_fn: Builder.Function.Index, name: []const u8, entry_idx: u32, target: RocTarget) !Builder.Function.Index {
// Create pointer type for generic pointers
const ptr_type = try builder.ptrType(.default);
@ -81,10 +84,11 @@ fn addRocExportedFunction(builder: *Builder, entrypoint_fn: Builder.Function.Ind
const roc_fn_params = [_]Builder.Type{ ptr_type, ptr_type, ptr_type };
const roc_fn_type = try builder.fnType(.void, &roc_fn_params, .normal);
// Create function name with roc__ prefix and platform-specific prefix
// Create function name with roc__ prefix.
// Add underscore prefix for macOS (required for MachO symbol names)
const base_name = try std.fmt.allocPrint(builder.gpa, "roc__{s}", .{name});
defer builder.gpa.free(base_name);
const full_name = if (builtin.target.os.tag == .macos)
const full_name = if (target.isMacOS())
try std.fmt.allocPrint(builder.gpa, "_{s}", .{base_name})
else
try builder.gpa.dupe(u8, base_name);
@ -153,12 +157,12 @@ fn addRocExportedFunction(builder: *Builder, entrypoint_fn: Builder.Function.Ind
///
/// The generated library is then compiled using LLVM to an object file and linked with
/// both the host and the Roc interpreter to create a dev build executable.
pub fn createInterpreterShim(builder: *Builder, entrypoints: []const EntryPoint) !void {
pub fn createInterpreterShim(builder: *Builder, entrypoints: []const EntryPoint, target: RocTarget) !void {
// Add the extern roc_entrypoint declaration
const entrypoint_fn = try addRocEntrypoint(builder);
const entrypoint_fn = try addRocEntrypoint(builder, target);
// Add each exported entrypoint function
for (entrypoints) |entry| {
_ = try addRocExportedFunction(builder, entrypoint_fn, entry.name, entry.idx);
_ = try addRocExportedFunction(builder, entrypoint_fn, entry.name, entry.idx, target);
}
}

View file

@ -121,18 +121,8 @@ test "integration - shared memory setup and parsing" {
allocs.initInPlace(gpa_impl.allocator());
defer allocs.deinit();
// Create a temporary Roc file with simple arithmetic
var temp_dir = testing.tmpDir(.{});
defer temp_dir.cleanup();
const roc_content = "app [main] { pf: platform \"test\" }\n\nmain = 42 + 58";
var roc_file = temp_dir.dir.createFile("test.roc", .{}) catch unreachable;
defer roc_file.close();
roc_file.writeAll(roc_content) catch unreachable;
const roc_path = try temp_dir.dir.realpathAlloc(allocs.gpa, "test.roc");
defer allocs.gpa.free(roc_path);
// Use the real int test platform
const roc_path = "test/int/app.roc";
// Test that we can set up shared memory with ModuleEnv
const shm_handle = try main.setupSharedMemoryWithModuleEnv(&allocs, roc_path);
@ -159,7 +149,7 @@ test "integration - shared memory setup and parsing" {
std.log.debug("Integration test: Successfully set up shared memory with size: {} bytes\n", .{shm_handle.size});
}
test "integration - compilation pipeline for different expressions" {
test "integration - compilation pipeline for different platforms" {
if (builtin.os.tag == .windows) {
return;
}
@ -170,30 +160,17 @@ test "integration - compilation pipeline for different expressions" {
allocs.initInPlace(gpa_impl.allocator());
defer allocs.deinit();
const test_cases = [_][]const u8{
"100 - 58",
"7 * 6",
"15 / 3",
"42 + 0",
// Test with our real test platforms
const test_apps = [_][]const u8{
"test/int/app.roc",
"test/str/app.roc",
"test/fx/app.roc",
};
for (test_cases) |expression| {
// Prepend boilerplate to make a complete Roc app
const roc_content = try std.fmt.allocPrint(allocs.gpa, "app [main] {{ pf: platform \"test\" }}\n\nmain = {s}", .{expression});
defer allocs.gpa.free(roc_content);
var temp_dir = testing.tmpDir(.{});
defer temp_dir.cleanup();
var roc_file = temp_dir.dir.createFile("test.roc", .{}) catch unreachable;
defer roc_file.close();
roc_file.writeAll(roc_content) catch unreachable;
const roc_path = try temp_dir.dir.realpathAlloc(allocs.gpa, "test.roc");
defer allocs.gpa.free(roc_path);
for (test_apps) |roc_path| {
// Test the full compilation pipeline (parse -> canonicalize -> typecheck)
const shm_handle = main.setupSharedMemoryWithModuleEnv(&allocs, roc_path) catch |err| {
std.log.warn("Failed to set up shared memory for expression: {s}, error: {}\n", .{ roc_content, err });
std.log.warn("Failed to set up shared memory for {s}: {}\n", .{ roc_path, err });
continue;
};
@ -214,11 +191,11 @@ test "integration - compilation pipeline for different expressions" {
// Verify shared memory was set up successfully
try testing.expect(shm_handle.size > 0);
std.log.debug("Successfully compiled expression: '{s}' (shared memory size: {} bytes)\n", .{ roc_content, shm_handle.size });
std.log.debug("Successfully compiled {s} (shared memory size: {} bytes)\n", .{ roc_path, shm_handle.size });
}
}
test "integration - error handling in compilation" {
test "integration - error handling for non-existent file" {
if (builtin.os.tag == .windows) {
return;
}
@ -229,26 +206,15 @@ test "integration - error handling in compilation" {
allocs.initInPlace(gpa_impl.allocator());
defer allocs.deinit();
var temp_dir = testing.tmpDir(.{});
defer temp_dir.cleanup();
// Test with a non-existent file path
const roc_path = "test/nonexistent/app.roc";
// Test with invalid syntax
const invalid_roc_content = "app [main] { pf: platform \"test\" }\n\nmain = 42 + + 58"; // Invalid syntax
var roc_file = temp_dir.dir.createFile("test.roc", .{}) catch unreachable;
defer roc_file.close();
roc_file.writeAll(invalid_roc_content) catch unreachable;
const roc_path = try temp_dir.dir.realpathAlloc(allocs.gpa, "test.roc");
defer allocs.gpa.free(roc_path);
// This should fail during parsing/compilation
// This should fail because the file doesn't exist
const result = main.setupSharedMemoryWithModuleEnv(&allocs, roc_path);
// We expect this to either fail or succeed (depending on parser error handling)
// The important thing is that it doesn't crash
// We expect this to fail - the important thing is that it doesn't crash
if (result) |shm_handle| {
// Clean up shared memory resources if successful
// Clean up shared memory resources if somehow successful
defer {
if (comptime builtin.os.tag == .windows) {
_ = @import("ipc").platform.windows.UnmapViewOfFile(shm_handle.ptr);
@ -262,8 +228,10 @@ test "integration - error handling in compilation" {
_ = posix.close(shm_handle.fd);
}
}
std.log.debug("Compilation succeeded even with invalid syntax (size: {} bytes)\n", .{shm_handle.size});
// This shouldn't happen with a non-existent file
return error.UnexpectedSuccess;
} else |err| {
// Expected to fail
std.log.debug("Compilation failed as expected with error: {}\n", .{err});
}
}

View file

@ -184,7 +184,7 @@ pub const ComptimeEvaluator = struct {
builtin_module_env: ?*const ModuleEnv,
import_mapping: *const import_mapping_mod.ImportMapping,
) !ComptimeEvaluator {
const interp = try Interpreter.init(allocator, cir, builtin_types, builtin_module_env, other_envs, import_mapping);
const interp = try Interpreter.init(allocator, cir, builtin_types, builtin_module_env, other_envs, import_mapping, null);
return ComptimeEvaluator{
.allocator = allocator,

View file

@ -203,10 +203,21 @@ pub const Interpreter = struct {
/// Root module used for method idents (is_lt, is_eq, etc.) - never changes during execution
root_env: *can.ModuleEnv,
builtin_module_env: ?*const can.ModuleEnv,
/// App module for resolving e_lookup_required (platform requires clause)
/// When the primary env is the platform, this points to the app that provides required values.
app_env: ?*can.ModuleEnv,
/// Array of all module environments, indexed by resolved module index
/// Used to resolve imports via pre-resolved indices in env.imports.resolved_modules
all_module_envs: []const *const can.ModuleEnv,
module_envs: std.AutoHashMapUnmanaged(base_pkg.Ident.Idx, *const can.ModuleEnv),
/// Module envs keyed by translated idents (in runtime_layout_store.env's ident space)
/// Used for method lookup on nominal types whose origin_module was translated
translated_module_envs: std.AutoHashMapUnmanaged(base_pkg.Ident.Idx, *const can.ModuleEnv),
/// Pre-translated module name idents for comparison in getModuleEnvForOrigin
/// These are in runtime_layout_store.env's ident space
translated_builtin_module: base_pkg.Ident.Idx,
translated_env_module: base_pkg.Ident.Idx,
translated_app_module: base_pkg.Ident.Idx,
module_ids: std.AutoHashMapUnmanaged(base_pkg.Ident.Idx, u32),
import_envs: std.AutoHashMapUnmanaged(can.CIR.Import.Idx, *const can.ModuleEnv),
current_module_id: u32,
@ -234,7 +245,7 @@ pub const Interpreter = struct {
/// Value being returned early from a function (set by s_return, consumed at function boundaries)
early_return_value: ?StackValue,
pub fn init(allocator: std.mem.Allocator, env: *can.ModuleEnv, builtin_types: BuiltinTypes, builtin_module_env: ?*const can.ModuleEnv, other_envs: []const *const can.ModuleEnv, import_mapping: *const import_mapping_mod.ImportMapping) !Interpreter {
pub fn init(allocator: std.mem.Allocator, env: *can.ModuleEnv, builtin_types: BuiltinTypes, builtin_module_env: ?*const can.ModuleEnv, other_envs: []const *const can.ModuleEnv, import_mapping: *const import_mapping_mod.ImportMapping, app_env: ?*can.ModuleEnv) !Interpreter {
// Build maps from Ident.Idx to ModuleEnv and module ID
var module_envs = std.AutoHashMapUnmanaged(base_pkg.Ident.Idx, *const can.ModuleEnv){};
errdefer module_envs.deinit(allocator);
@ -251,13 +262,17 @@ pub const Interpreter = struct {
else
0;
if (other_envs.len > 0 and import_count > 0) {
// Calculate total import count including app imports
const app_import_count: usize = if (app_env) |a_env| a_env.imports.imports.items.items.len else 0;
const total_import_count = import_count + app_import_count;
if (other_envs.len > 0 and total_import_count > 0) {
// Allocate capacity for all imports (even if some are duplicates)
try module_envs.ensureTotalCapacity(allocator, @intCast(other_envs.len));
try module_ids.ensureTotalCapacity(allocator, @intCast(other_envs.len));
try import_envs.ensureTotalCapacity(allocator, @intCast(import_count));
try import_envs.ensureTotalCapacity(allocator, @intCast(total_import_count));
// Process ALL imports using pre-resolved module indices
// Process ALL imports from primary env using pre-resolved module indices
// Note: Some imports may be unresolved (e.g., platform modules in test context).
// We skip unresolved imports here - errors will occur at point-of-use if the
// code actually tries to access an unresolved import.
@ -286,9 +301,30 @@ pub const Interpreter = struct {
}
}
}
// Also process app env imports if app_env is different from primary env
// This is needed when the platform calls the app's main! via e_lookup_required
if (app_env) |a_env| {
if (a_env != env) {
for (0..app_import_count) |i| {
const import_idx: can.CIR.Import.Idx = @enumFromInt(i);
// Use pre-resolved module index - skip if not resolved
const resolved_idx = a_env.imports.getResolvedModule(import_idx) orelse continue;
if (resolved_idx >= other_envs.len) continue;
const module_env = other_envs[resolved_idx];
// Store in import_envs for app's imports
// Use put instead of putAssumeCapacity since we may have overlapping indices
try import_envs.put(allocator, import_idx, module_env);
}
}
}
}
return initWithModuleEnvs(allocator, env, other_envs, module_envs, module_ids, import_envs, next_id, builtin_types, builtin_module_env, import_mapping);
return initWithModuleEnvs(allocator, env, other_envs, module_envs, module_ids, import_envs, next_id, builtin_types, builtin_module_env, import_mapping, app_env);
}
/// Deinit the interpreter and also free the module maps if they were allocated by init()
@ -307,6 +343,7 @@ pub const Interpreter = struct {
builtin_types: BuiltinTypes,
builtin_module_env: ?*const can.ModuleEnv,
import_mapping: *const import_mapping_mod.ImportMapping,
app_env: ?*can.ModuleEnv,
) !Interpreter {
const rt_types_ptr = try allocator.create(types.store.Store);
rt_types_ptr.* = try types.store.Store.initCapacity(allocator, 1024, 512);
@ -325,8 +362,13 @@ pub const Interpreter = struct {
.env = env,
.root_env = env, // Root env is the original env passed to init - used for method idents
.builtin_module_env = builtin_module_env,
.app_env = app_env,
.all_module_envs = all_module_envs,
.module_envs = module_envs,
.translated_module_envs = undefined, // Set after runtime_layout_store init
.translated_builtin_module = base_pkg.Ident.Idx.NONE,
.translated_env_module = base_pkg.Ident.Idx.NONE,
.translated_app_module = base_pkg.Ident.Idx.NONE,
.module_ids = module_ids,
.import_envs = import_envs,
.current_module_id = 0, // Current module always gets ID 0
@ -350,6 +392,79 @@ pub const Interpreter = struct {
// Use the pre-interned "Builtin.Str" identifier from the module env
result.runtime_layout_store = try layout.Store.init(env, result.runtime_types, env.idents.builtin_str);
// Build translated_module_envs for runtime method lookups
// This maps module names in runtime_layout_store.env's ident space to their ModuleEnvs
var translated_module_envs = std.AutoHashMapUnmanaged(base_pkg.Ident.Idx, *const can.ModuleEnv){};
errdefer translated_module_envs.deinit(allocator);
const layout_env = result.runtime_layout_store.env;
// Helper to check if a module has a valid module_name_idx
// (handles both unset NONE and corrupted undefined values from deserialized data)
const hasValidModuleName = struct {
fn check(mod_env: *const can.ModuleEnv) bool {
// Check for NONE sentinel
if (mod_env.module_name_idx.isNone()) return false;
// Bounds check - module_name_idx.idx must be within the ident store
const ident_store_size = mod_env.common.idents.interner.bytes.items.items.len;
return mod_env.module_name_idx.idx < ident_store_size;
}
}.check;
// Add current/root module (skip if module_name_idx is unset, e.g., in tests)
if (hasValidModuleName(env)) {
const current_name_str = env.getIdent(env.module_name_idx);
const translated_current = try @constCast(layout_env).insertIdent(base_pkg.Ident.for_text(current_name_str));
try translated_module_envs.put(allocator, translated_current, env);
}
// Add app module if different from env
if (app_env) |a_env| {
if (a_env != env and hasValidModuleName(a_env)) {
const app_name_str = a_env.getIdent(a_env.module_name_idx);
const translated_app = try @constCast(layout_env).insertIdent(base_pkg.Ident.for_text(app_name_str));
try translated_module_envs.put(allocator, translated_app, a_env);
}
}
// Add builtin module
if (builtin_module_env) |bme| {
if (hasValidModuleName(bme)) {
const builtin_name_str = bme.getIdent(bme.module_name_idx);
const translated_builtin = try @constCast(layout_env).insertIdent(base_pkg.Ident.for_text(builtin_name_str));
try translated_module_envs.put(allocator, translated_builtin, bme);
}
}
// Add all other modules
for (all_module_envs) |mod_env| {
if (hasValidModuleName(mod_env)) {
const mod_name_str = mod_env.getIdent(mod_env.module_name_idx);
const translated_mod = try @constCast(layout_env).insertIdent(base_pkg.Ident.for_text(mod_name_str));
// Use put to handle potential duplicates (same module might be in multiple places)
try translated_module_envs.put(allocator, translated_mod, mod_env);
}
}
result.translated_module_envs = translated_module_envs;
// Pre-translate module names for comparison in getModuleEnvForOrigin
// All translated idents are in runtime_layout_store.env's ident space
result.translated_builtin_module = try @constCast(layout_env).insertIdent(base_pkg.Ident.for_text("Builtin"));
// Translate env's module name
if (hasValidModuleName(env)) {
const env_name_str = env.getIdent(env.module_name_idx);
result.translated_env_module = try @constCast(layout_env).insertIdent(base_pkg.Ident.for_text(env_name_str));
}
// Translate app's module name
if (app_env) |a_env| {
if (a_env != env and hasValidModuleName(a_env)) {
const app_name_str = a_env.getIdent(a_env.module_name_idx);
result.translated_app_module = try @constCast(layout_env).insertIdent(base_pkg.Ident.for_text(app_name_str));
}
}
return result;
}
@ -458,6 +573,9 @@ pub const Interpreter = struct {
temp_binds.items.len = 0;
}
// Decref args after body evaluation (caller transfers ownership)
defer if (params.len > 0) args_tuple_value.decref(&self.runtime_layout_store, roc_ops);
defer self.trimBindingList(&self.bindings, base_binding_len, roc_ops);
// Evaluate body, handling early returns at function boundary
@ -1684,16 +1802,130 @@ 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);
// Get element layout - handle list_of_zst by checking both lists for a proper element layout.
// When concatenating a list_of_zst (e.g., empty list []) with a regular list,
// we need to use the element layout from the regular list.
const elem_layout_result: struct { elem_layout: Layout, result_layout: Layout } = blk: {
// Try to get element layout from list_a first
if (list_a_arg.layout.tag == .list) {
const elem_idx = list_a_arg.layout.data.list;
const elem_lay = self.runtime_layout_store.getLayout(elem_idx);
// Check if this is actually a non-ZST element
if (self.runtime_layout_store.layoutSize(elem_lay) > 0) {
break :blk .{ .elem_layout = elem_lay, .result_layout = list_a_arg.layout };
}
}
// Try list_b
if (list_b_arg.layout.tag == .list) {
const elem_idx = list_b_arg.layout.data.list;
const elem_lay = self.runtime_layout_store.getLayout(elem_idx);
if (self.runtime_layout_store.layoutSize(elem_lay) > 0) {
break :blk .{ .elem_layout = elem_lay, .result_layout = list_b_arg.layout };
}
}
// Both are ZST - use ZST layout
break :blk .{ .elem_layout = Layout.zst(), .result_layout = list_a_arg.layout };
};
const elem_layout = elem_layout_result.elem_layout;
const result_layout = elem_layout_result.result_layout;
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);
// If either list is empty, just return a copy of the other (avoid allocation)
if (list_a.len() == 0) {
return try self.pushCopy(list_b_arg, roc_ops);
}
if (list_b.len() == 0) {
return try self.pushCopy(list_a_arg, roc_ops);
}
// Determine if elements are refcounted
const elements_refcounted = elem_layout.isRefcounted();
// Create a fresh list by allocating and copying elements.
// We can't use the builtin listConcat here because it consumes its input lists
// (handles refcounting internally), but we're working with StackValues that
// have their own lifetime management - the caller will decref the args.
const total_count = list_a.len() + list_b.len();
var out = try self.pushRaw(result_layout, 0);
out.is_initialized = false;
const header: *builtins.list.RocList = @ptrCast(@alignCast(out.ptr.?));
const runtime_list = builtins.list.RocList.allocateExact(
elem_alignment_u32,
total_count,
elem_size,
elements_refcounted,
roc_ops,
);
if (elem_size > 0) {
if (runtime_list.bytes) |buffer| {
// Copy elements from list_a
if (list_a.bytes) |src_a| {
@memcpy(buffer[0 .. list_a.len() * elem_size], src_a[0 .. list_a.len() * elem_size]);
}
// Copy elements from list_b
if (list_b.bytes) |src_b| {
const offset = list_a.len() * elem_size;
@memcpy(buffer[offset .. offset + list_b.len() * elem_size], src_b[0 .. list_b.len() * elem_size]);
}
}
}
header.* = runtime_list;
out.is_initialized = true;
// Handle refcounting for copied elements - increment refcount for each element
// since we copied them (the elements are now shared with the original lists)
if (elements_refcounted) {
var refcount_context = RefcountContext{
.layout_store = &self.runtime_layout_store,
.elem_layout = elem_layout,
.roc_ops = roc_ops,
};
if (runtime_list.bytes) |buffer| {
var i: usize = 0;
while (i < total_count) : (i += 1) {
listElementInc(@ptrCast(&refcount_context), buffer + i * elem_size);
}
}
}
return out;
},
.list_append => {
// List.append: List(a), a -> List(a)
std.debug.assert(args.len == 2); // low-level .list_append expects 2 arguments
const roc_list_arg = args[0];
const elt_arg = args[1];
std.debug.assert(roc_list_arg.ptr != null); // low-level .list_append expects non-null list pointer
std.debug.assert(elt_arg.ptr != null); // low-level .list_append expects non-null 2nd argument
// Extract element layout from List(a)
std.debug.assert(roc_list_arg.layout.tag == .list or roc_list_arg.layout.tag == .list_of_zst); // low-level .list_append expects list layout
// Format arguments into proper types
const roc_list: *const builtins.list.RocList = @ptrCast(@alignCast(roc_list_arg.ptr.?));
const non_null_bytes: [*]u8 = @ptrCast(elt_arg.ptr.?);
const append_elt: builtins.list.Opaque = non_null_bytes;
// Get element layout
const elem_layout_idx = roc_list_arg.layout.data.list;
const elem_layout = self.runtime_layout_store.getLayout(elem_layout_idx);
const elem_size: u32 = 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);
// Determine if elements are refcounted
const elements_refcounted = elem_layout.isRefcounted();
// Determine if list can be mutated in place
const update_mode = if (roc_list.isUnique()) builtins.utils.UpdateMode.InPlace else builtins.utils.UpdateMode.Immutable;
// Set up context for refcount callbacks
var refcount_context = RefcountContext{
.layout_store = &self.runtime_layout_store,
@ -1701,24 +1933,38 @@ pub const Interpreter = struct {
.roc_ops = 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,
);
const copy_fn: builtins.list.CopyFallbackFn = copy: switch (elem_layout.tag) {
.scalar => {
switch (elem_layout.data.scalar.tag) {
.str => break :copy &builtins.list.copy_str,
.int => {
switch (elem_layout.data.scalar.data.int) {
.u8 => break :copy &builtins.list.copy_u8,
.u16 => break :copy &builtins.list.copy_u16,
.u32 => break :copy &builtins.list.copy_u32,
.u64 => break :copy &builtins.list.copy_u64,
.u128 => break :copy &builtins.list.copy_u128,
.i8 => break :copy &builtins.list.copy_i8,
.i16 => break :copy &builtins.list.copy_i16,
.i32 => break :copy &builtins.list.copy_i32,
.i64 => break :copy &builtins.list.copy_i64,
.i128 => break :copy &builtins.list.copy_i128,
}
},
else => break :copy &builtins.list.copy_fallback,
}
},
.box => break :copy &builtins.list.copy_box,
.box_of_zst => break :copy &builtins.list.copy_box_zst,
.list => break :copy &builtins.list.copy_list,
.list_of_zst => break :copy &builtins.list.copy_list_zst,
else => break :copy &builtins.list.copy_fallback,
};
const result_list = builtins.list.listAppend(roc_list.*, elem_alignment_u32, append_elt, elem_size, elements_refcounted, if (elements_refcounted) @ptrCast(&refcount_context) else null, if (elements_refcounted) &listElementInc else &builtins.list.rcNone, update_mode, copy_fn, roc_ops);
// Allocate space for the result list
const result_layout = list_a_arg.layout; // Same layout as input
const result_layout = roc_list_arg.layout; // Same layout as input
var out = try self.pushRaw(result_layout, 0);
out.is_initialized = false;
@ -1844,6 +2090,11 @@ pub const Interpreter = struct {
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);
// return error.Crash;
// },
// Bool operations
.bool_is_eq => {
// Bool.is_eq : Bool, Bool -> Bool
@ -5154,10 +5405,17 @@ pub const Interpreter = struct {
}
},
.record_destructure => |rec_pat| {
const destructs = self.env.store.sliceRecordDestructs(rec_pat.destructs);
// Empty record pattern {} matches zero-sized types
if (destructs.len == 0) {
// No fields to destructure - matches any empty record (including zst)
return value.layout.tag == .record or value.layout.tag == .zst;
}
if (value.layout.tag != .record) return false;
var accessor = try value.asRecord(&self.runtime_layout_store);
const destructs = self.env.store.sliceRecordDestructs(rec_pat.destructs);
for (destructs) |destruct_idx| {
const destruct = self.env.store.getRecordDestruct(destruct_idx);
@ -5277,6 +5535,7 @@ pub const Interpreter = struct {
}
self.poly_cache.deinit();
self.module_envs.deinit(self.allocator);
self.translated_module_envs.deinit(self.allocator);
self.module_ids.deinit(self.allocator);
self.import_envs.deinit(self.allocator);
self.var_to_layout_slot.deinit();
@ -5296,20 +5555,46 @@ pub const Interpreter = struct {
/// Get the module environment for a given origin module identifier.
/// Returns the current module's env if the identifier matches, otherwise looks it up in the module map.
/// Note: origin_module may be in runtime_layout_store.env's ident space (after translateTypeVar),
/// or in the original ident space (for direct lookups), so we check both maps.
fn getModuleEnvForOrigin(self: *const Interpreter, origin_module: base_pkg.Ident.Idx) ?*const can.ModuleEnv {
// Check if it's the Builtin module
// Use root_env.idents for consistent module comparison across all contexts
if (origin_module == self.root_env.idents.builtin_module) {
// Check if it's the Builtin module (using pre-translated ident for runtime-translated case)
if (origin_module.idx == self.translated_builtin_module.idx) {
// In shim context, builtins are embedded in the main module env
// (builtin_module_env is null), so fall back to self.env
return self.builtin_module_env orelse self.env;
}
// Check if it's the current module
// Also check original builtin ident for non-translated case
if (origin_module == self.root_env.idents.builtin_module) {
return self.builtin_module_env orelse self.env;
}
// Check if it's the current module (both translated and original idents)
if (!self.translated_env_module.isNone() and origin_module.idx == self.translated_env_module.idx) {
return self.env;
}
if (self.env.module_name_idx == origin_module) {
return self.env;
}
// Look up in imported modules
return self.module_envs.get(origin_module);
// Check if it's the app module (both translated and original idents)
if (self.app_env) |a_env| {
if (!self.translated_app_module.isNone() and origin_module.idx == self.translated_app_module.idx) {
return a_env;
}
if (a_env.module_name_idx == origin_module) {
return a_env;
}
}
// Look up in imported modules (original idents)
if (self.module_envs.get(origin_module)) |env| {
return env;
}
// Look up in translated module envs (for runtime-translated idents)
// This handles the case where origin_module comes from runtime_layout_store.env's ident space
return self.translated_module_envs.get(origin_module);
}
/// Get the numeric module ID for a given origin module identifier.
@ -6312,6 +6597,9 @@ pub const Interpreter = struct {
final_expr: can.CIR.Expr.Idx,
/// Bindings length at block start (for cleanup)
bindings_start: usize,
/// True if this block_continue was scheduled after an s_expr statement,
/// meaning we should pop and discard the expression's result value
should_discard_value: bool = false,
};
pub const BindDecl = struct {
@ -6767,13 +7055,19 @@ pub const Interpreter = struct {
const should_continue = try self.applyContinuation(&work_stack, &value_stack, cont, roc_ops);
if (!should_continue) {
// return_result continuation signals completion
return value_stack.pop() orelse return error.Crash;
if (value_stack.pop()) |val| {
return val;
} else {
self.triggerCrash("eval: value_stack empty after return_result", false, roc_ops);
return error.Crash;
}
}
},
}
}
// Should never reach here - return_result should have exited the loop
self.triggerCrash("eval: should never reach here - return_result should have exited the loop", false, roc_ops);
return error.Crash;
}
@ -6947,11 +7241,57 @@ pub const Interpreter = struct {
try value_stack.push(value);
},
.e_lookup_required => {
.e_lookup_required => |lookup| {
// Required lookups reference values from the app that provides values to the
// platform's `requires` clause. These are not available during compile-time
// evaluation.
return error.TypeMismatch;
// platform's `requires` clause.
if (self.app_env) |app_env| {
// Get the required type info from the platform's requires_types
const requires_items = self.env.requires_types.items.items;
const requires_idx_val = @intFromEnum(lookup.requires_idx);
if (requires_idx_val >= requires_items.len) {
return error.TypeMismatch;
}
const required_type = requires_items[requires_idx_val];
// Translate the required ident from platform's store to app's store (once, outside loop)
const required_ident_str = self.env.getIdent(required_type.ident);
const app_required_ident = try @constCast(app_env).insertIdent(base_pkg.Ident.for_text(required_ident_str));
// Find the matching export in the app
const exports = app_env.store.sliceDefs(app_env.exports);
var found_expr: ?can.CIR.Expr.Idx = null;
for (exports) |def_idx| {
const def = app_env.store.getDef(def_idx);
// Get the def's identifier from its pattern
const pattern = app_env.store.getPattern(def.pattern);
if (pattern == .assign) {
// Compare ident indices directly (O(1) instead of string comparison)
if (pattern.assign.ident == app_required_ident) {
found_expr = def.expr;
break;
}
}
}
if (found_expr) |app_expr_idx| {
// Switch to app env for evaluation (like evalLookupExternal)
const saved_env = self.env;
const saved_bindings_len = self.bindings.items.len;
self.env = @constCast(app_env);
defer {
self.env = saved_env;
self.bindings.shrinkRetainingCapacity(saved_bindings_len);
}
// Evaluate the app's exported expression synchronously
const result = try self.evalWithExpectedType(app_expr_idx, roc_ops, expected_rt_var);
try value_stack.push(result);
} else {
return error.TypeMismatch;
}
} else {
// No app_env - can't resolve required lookups
return error.TypeMismatch;
}
},
.e_runtime_error => {
@ -8482,15 +8822,18 @@ pub const Interpreter = struct {
},
.s_expr => |sx| {
// Evaluate expression, discard result, continue with remaining
// Push block_continue for remaining statements
try work_stack.push(.{ .apply_continuation = .{ .block_continue = .{
.remaining_stmts = remaining_stmts,
.final_expr = final_expr,
.bindings_start = bindings_start,
} } });
// Push decref to clean up the expression result
// We'll handle this by pushing a special continuation or just evaluating and discarding
// For now, we'll just evaluate and let the block_continue handle cleanup
// Push block_continue for remaining statements (with should_discard_value=true)
try work_stack.push(.{
.apply_continuation = .{
.block_continue = .{
.remaining_stmts = remaining_stmts,
.final_expr = final_expr,
.bindings_start = bindings_start,
.should_discard_value = true, // s_expr result should be discarded
},
},
});
// Evaluate the expression; block_continue will discard its result
try work_stack.push(.{ .eval_expr = .{
.expr_idx = sx.expr,
.expected_rt_var = null,
@ -8725,10 +9068,9 @@ pub const Interpreter = struct {
},
.block_continue => |bc| {
// For s_expr statements, we need to pop and discard the value
// Check if there's a value to discard (from s_expr)
if (value_stack.items.items.len > 0) {
// Pop and discard any value left from s_expr
const val = value_stack.pop().?;
// Only pop if should_discard_value is set (meaning this was scheduled after an s_expr)
if (bc.should_discard_value) {
const val = value_stack.pop() orelse return error.Crash;
val.decref(&self.runtime_layout_store, roc_ops);
}
@ -9609,11 +9951,17 @@ pub const Interpreter = struct {
var i: usize = arg_count;
while (i > 0) {
i -= 1;
arg_values[i] = value_stack.pop() orelse return error.Crash;
arg_values[i] = value_stack.pop() orelse {
self.triggerCrash("call_invoke_closure: value_stack empty when popping arguments", false, roc_ops);
return error.Crash;
};
}
// Pop function value
const func_val = value_stack.pop() orelse return error.Crash;
const func_val = value_stack.pop() orelse {
self.triggerCrash("call_invoke_closure: value_stack empty when popping function", false, roc_ops);
return error.Crash;
};
// Handle closure invocation
if (func_val.layout.tag == .closure) {
@ -9789,11 +10137,9 @@ pub const Interpreter = struct {
var result = try self.callLowLevelBuiltin(low_level.op, arg_values, roc_ops, ci.call_ret_rt_var);
// Decref args (except for list_concat which handles its own refcounting)
if (low_level.op != .list_concat) {
for (arg_values) |arg| {
arg.decref(&self.runtime_layout_store, roc_ops);
}
// Decref args after builtin completes
for (arg_values) |arg| {
arg.decref(&self.runtime_layout_store, roc_ops);
}
// Restore environment and free arg_rt_vars
@ -10541,7 +10887,10 @@ pub const Interpreter = struct {
},
.for_loop_iterate => |fl_in| {
// For loop iteration: list has been evaluated, start iterating
const list_value = value_stack.pop() orelse return error.Crash;
const list_value = value_stack.pop() orelse {
self.triggerCrash("for_loop_iterate: value_stack empty", false, roc_ops);
return error.Crash;
};
// Get the list layout
if (list_value.layout.tag != .list) {
@ -10630,7 +10979,10 @@ pub const Interpreter = struct {
},
.for_loop_body_done => |fl| {
// For loop body completed, clean up and continue to next iteration
const body_result = value_stack.pop() orelse return error.Crash;
const body_result = value_stack.pop() orelse {
self.triggerCrash("for_loop_body_done: value_stack empty", false, roc_ops);
return error.Crash;
};
body_result.decref(&self.runtime_layout_store, roc_ops);
// Clean up bindings for this iteration
@ -10784,7 +11136,10 @@ pub const Interpreter = struct {
},
.reassign_value => |rv| {
// Reassign statement: update binding
const new_val = value_stack.pop() orelse return error.Crash;
const new_val = value_stack.pop() orelse {
self.triggerCrash("reassign_value: value_stack empty", false, roc_ops);
return error.Crash;
};
// Search through all bindings and reassign
var j: usize = self.bindings.items.len;
while (j > 0) {
@ -11087,7 +11442,7 @@ test "interpreter: translateTypeVar for str" {
defer str_module.deinit();
const builtin_types_test = BuiltinTypes.init(builtin_indices, bool_module.env, result_module.env, str_module.env);
var interp = try Interpreter.init(gpa, &env, builtin_types_test, null, &[_]*const can.ModuleEnv{}, &empty_import_mapping);
var interp = try Interpreter.init(gpa, &env, builtin_types_test, null, &[_]*const can.ModuleEnv{}, &empty_import_mapping, null);
defer interp.deinit();
// Get the actual Str type from the Builtin module using the str_stmt index
@ -11124,7 +11479,7 @@ test "interpreter: translateTypeVar for alias of Str" {
defer str_module.deinit();
const builtin_types_test = BuiltinTypes.init(builtin_indices, bool_module.env, result_module.env, str_module.env);
var interp = try Interpreter.init(gpa, &env, builtin_types_test, null, &[_]*const can.ModuleEnv{}, &empty_import_mapping);
var interp = try Interpreter.init(gpa, &env, builtin_types_test, null, &[_]*const can.ModuleEnv{}, &empty_import_mapping, null);
defer interp.deinit();
const alias_name = try env.common.idents.insert(gpa, @import("base").Ident.for_text("MyAlias"));
@ -11176,7 +11531,7 @@ test "interpreter: translateTypeVar for nominal Point(Str)" {
defer str_module.deinit();
const builtin_types_test = BuiltinTypes.init(builtin_indices, bool_module.env, result_module.env, str_module.env);
var interp = try Interpreter.init(gpa, &env, builtin_types_test, null, &[_]*const can.ModuleEnv{}, &empty_import_mapping);
var interp = try Interpreter.init(gpa, &env, builtin_types_test, null, &[_]*const can.ModuleEnv{}, &empty_import_mapping, null);
defer interp.deinit();
const name_nominal = try env.common.idents.insert(gpa, @import("base").Ident.for_text("Point"));
@ -11233,7 +11588,7 @@ test "interpreter: translateTypeVar for flex var" {
defer str_module.deinit();
const builtin_types_test = BuiltinTypes.init(builtin_indices, bool_module.env, result_module.env, str_module.env);
var interp = try Interpreter.init(gpa, &env, builtin_types_test, null, &[_]*const can.ModuleEnv{}, &empty_import_mapping);
var interp = try Interpreter.init(gpa, &env, builtin_types_test, null, &[_]*const can.ModuleEnv{}, &empty_import_mapping, null);
defer interp.deinit();
const ct_flex = try env.types.freshFromContent(.{ .flex = types.Flex.init() });
@ -11261,7 +11616,7 @@ test "interpreter: translateTypeVar for rigid var" {
defer str_module.deinit();
const builtin_types_test = BuiltinTypes.init(builtin_indices, bool_module.env, result_module.env, str_module.env);
var interp = try Interpreter.init(gpa, &env, builtin_types_test, null, &[_]*const can.ModuleEnv{}, &empty_import_mapping);
var interp = try Interpreter.init(gpa, &env, builtin_types_test, null, &[_]*const can.ModuleEnv{}, &empty_import_mapping, null);
defer interp.deinit();
const name_a = try env.common.idents.insert(gpa, @import("base").Ident.for_text("A"));
@ -11299,7 +11654,7 @@ test "interpreter: getStaticDispatchConstraint returns error for non-constrained
defer str_module.deinit();
const builtin_types_test = BuiltinTypes.init(builtin_indices, bool_module.env, result_module.env, str_module.env);
var interp = try Interpreter.init(gpa, &env, builtin_types_test, null, &[_]*const can.ModuleEnv{}, &empty_import_mapping);
var interp = try Interpreter.init(gpa, &env, builtin_types_test, null, &[_]*const can.ModuleEnv{}, &empty_import_mapping, null);
defer interp.deinit();
// Create nominal Str type (no constraints)
@ -11347,7 +11702,7 @@ test "interpreter: unification constrains (a->a) with Str" {
defer str_module.deinit();
const builtin_types_test = BuiltinTypes.init(builtin_indices, bool_module.env, result_module.env, str_module.env);
var interp = try Interpreter.init(gpa, &env, builtin_types_test, null, &[_]*const can.ModuleEnv{}, &empty_import_mapping);
var interp = try Interpreter.init(gpa, &env, builtin_types_test, null, &[_]*const can.ModuleEnv{}, &empty_import_mapping, null);
defer interp.deinit();
const func_id: u32 = 42;
@ -11397,7 +11752,7 @@ test "interpreter: cross-module method resolution should find methods in origin
defer str_module.deinit();
const builtin_types_test = BuiltinTypes.init(builtin_indices, bool_module.env, result_module.env, str_module.env);
var interp = try Interpreter.init(gpa, &module_b, builtin_types_test, null, &[_]*const can.ModuleEnv{}, &empty_import_mapping);
var interp = try Interpreter.init(gpa, &module_b, builtin_types_test, null, &[_]*const can.ModuleEnv{}, &empty_import_mapping, null);
defer interp.deinit();
// Register module A as an imported module
@ -11453,7 +11808,7 @@ test "interpreter: transitive module method resolution (A imports B imports C)"
const builtin_types_test = BuiltinTypes.init(builtin_indices, bool_module.env, result_module.env, str_module.env);
// Use module_a as the current module
var interp = try Interpreter.init(gpa, &module_a, builtin_types_test, null, &[_]*const can.ModuleEnv{}, &empty_import_mapping);
var interp = try Interpreter.init(gpa, &module_a, builtin_types_test, null, &[_]*const can.ModuleEnv{}, &empty_import_mapping, null);
defer interp.deinit();
// Register module B

View file

@ -423,8 +423,40 @@ pub fn renderValueRoc(ctx: *RenderCtx, value: StackValue) ![]u8 {
try out.append(')');
return out.toOwnedSlice();
}
if (value.layout.tag == .list) {
var out = std.array_list.AlignedManaged(u8, null).init(gpa);
errdefer out.deinit();
const roc_list: *const builtins.list.RocList = @ptrCast(@alignCast(value.ptr.?));
const len = roc_list.len();
try out.append('[');
if (len > 0) {
const elem_layout_idx = value.layout.data.list;
const elem_layout = ctx.layout_store.getLayout(elem_layout_idx);
const elem_size = ctx.layout_store.layoutSize(elem_layout);
var i: usize = 0;
while (i < len) : (i += 1) {
if (roc_list.bytes) |bytes| {
const elem_ptr: *anyopaque = @ptrCast(bytes + i * elem_size);
const elem_val = StackValue{ .layout = elem_layout, .ptr = elem_ptr, .is_initialized = true };
const rendered = try renderValueRoc(ctx, elem_val);
defer gpa.free(rendered);
try out.appendSlice(rendered);
if (i + 1 < len) try out.appendSlice(", ");
}
}
}
try out.append(']');
return out.toOwnedSlice();
}
if (value.layout.tag == .list_of_zst) {
return try gpa.dupe(u8, "<list_of_zst>");
// list_of_zst is used for empty lists - render as []
const roc_list: *const builtins.list.RocList = @ptrCast(@alignCast(value.ptr.?));
const len = roc_list.len();
if (len == 0) {
return try gpa.dupe(u8, "[]");
}
// Non-empty list of ZST - show count
return try std.fmt.allocPrint(gpa, "[<{d} zero-sized elements>]", .{len});
}
if (value.layout.tag == .record) {
var out = std.array_list.AlignedManaged(u8, null).init(gpa);

View file

@ -118,18 +118,30 @@ fn testRocRealloc(realloc_args: *RocRealloc, env: *anyopaque) callconv(.c) void
// Calculate new total size needed
const new_total_size = realloc_args.new_length + size_storage_bytes;
// Perform reallocation
const old_slice = @as([*]u8, @ptrCast(old_base_ptr))[0..old_total_size];
const new_slice = test_env.allocator.realloc(old_slice, new_total_size) catch {
// Get the alignment enum from the passed alignment
const align_enum = std.mem.Alignment.fromByteUnits(@as(usize, @intCast(realloc_args.alignment)));
// Perform reallocation using rawFree + rawAlloc to handle alignment correctly
// (Zig's realloc doesn't let us specify alignment for the old slice)
const new_result = test_env.allocator.rawAlloc(new_total_size, align_enum, @returnAddress());
const new_base_ptr = new_result orelse {
std.debug.panic("Out of memory during testRocRealloc", .{});
};
// Copy the old data to the new allocation
const copy_size = @min(old_total_size, new_total_size);
@memcpy(new_base_ptr[0..copy_size], old_base_ptr[0..copy_size]);
// Free the old allocation
const old_slice = @as([*]u8, @ptrCast(old_base_ptr))[0..old_total_size];
test_env.allocator.rawFree(old_slice, align_enum, @returnAddress());
// Store the new total size in the metadata
const new_size_ptr: *usize = @ptrFromInt(@intFromPtr(new_slice.ptr) + size_storage_bytes - @sizeOf(usize));
const new_size_ptr: *usize = @ptrFromInt(@intFromPtr(new_base_ptr) + size_storage_bytes - @sizeOf(usize));
new_size_ptr.* = new_total_size;
// Return pointer to the user data (after the size metadata)
realloc_args.answer = @ptrFromInt(@intFromPtr(new_slice.ptr) + size_storage_bytes);
realloc_args.answer = @ptrFromInt(@intFromPtr(new_base_ptr) + size_storage_bytes);
}
fn testRocDbg(dbg_args: *const RocDbg, env: *anyopaque) callconv(.c) void {

View file

@ -28,6 +28,7 @@ const runExpectBool = helpers.runExpectBool;
const runExpectError = helpers.runExpectError;
const runExpectStr = helpers.runExpectStr;
const runExpectRecord = helpers.runExpectRecord;
const runExpectListI64 = helpers.runExpectListI64;
const ExpectedField = helpers.ExpectedField;
const TraceWriterState = struct {
@ -399,7 +400,7 @@ fn runExpectSuccess(src: []const u8, should_trace: enum { trace, no_trace }) !vo
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interpreter = try Interpreter.init(testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interpreter = try Interpreter.init(testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interpreter.deinit();
const enable_trace = should_trace == .trace;
@ -757,7 +758,7 @@ test "ModuleEnv serialization and interpreter evaluation" {
// Test 1: Evaluate with the original ModuleEnv
{
const builtin_types_local = BuiltinTypes.init(builtin_indices, builtin_module.env, builtin_module.env, builtin_module.env);
var interpreter = try Interpreter.init(gpa, &original_env, builtin_types_local, builtin_module.env, &[_]*const can.ModuleEnv{}, &checker.import_mapping);
var interpreter = try Interpreter.init(gpa, &original_env, builtin_types_local, builtin_module.env, &[_]*const can.ModuleEnv{}, &checker.import_mapping, null);
defer interpreter.deinit();
const ops = test_env_instance.get_ops();
@ -832,7 +833,7 @@ test "ModuleEnv serialization and interpreter evaluation" {
// The original expression index should still be valid since the NodeStore structure is preserved
{
const builtin_types_local = BuiltinTypes.init(builtin_indices, builtin_module.env, builtin_module.env, builtin_module.env);
var interpreter = try Interpreter.init(gpa, deserialized_env, builtin_types_local, builtin_module.env, &[_]*const can.ModuleEnv{}, &checker.import_mapping);
var interpreter = try Interpreter.init(gpa, deserialized_env, builtin_types_local, builtin_module.env, &[_]*const can.ModuleEnv{}, &checker.import_mapping, null);
defer interpreter.deinit();
const ops = test_env_instance.get_ops();
@ -1253,3 +1254,52 @@ test "List.fold with record accumulator - nested list and record" {
.no_trace,
);
}
// ============================================================================
// Tests for List.map
// ============================================================================
test "List.map - basic identity" {
// Map with identity function
try runExpectListI64(
"List.map([1i64, 2i64, 3i64], |x| x)",
&[_]i64{ 1, 2, 3 },
.no_trace,
);
}
test "List.map - single element" {
// Map on single element list
try runExpectListI64(
"List.map([42i64], |x| x)",
&[_]i64{42},
.no_trace,
);
}
test "List.map - longer list with squaring" {
// Check that map on a longer list with squaring works
try runExpectListI64(
"List.map([1i64, 2i64, 3i64, 4i64, 5i64], |x| x * x)",
&[_]i64{ 1, 4, 9, 16, 25 },
.no_trace,
);
}
test "List.map - doubling" {
// Map with doubling function
try runExpectListI64(
"List.map([1i64, 2i64, 3i64], |x| x * 2i64)",
&[_]i64{ 2, 4, 6 },
.no_trace,
);
}
test "List.map - adding" {
// Map with adding function
try runExpectListI64(
"List.map([10i64, 20i64], |x| x + 5i64)",
&[_]i64{ 15, 25 },
.no_trace,
);
}

View file

@ -52,7 +52,7 @@ pub fn runExpectError(src: []const u8, expected_error: anyerror, should_trace: e
const builtin_types = BuiltinTypes.init(resources.builtin_indices, resources.builtin_module.env, resources.builtin_module.env, resources.builtin_module.env);
const imported_envs = [_]*const can.ModuleEnv{resources.builtin_module.env};
var interpreter = try Interpreter.init(test_allocator, resources.module_env, builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping);
var interpreter = try Interpreter.init(test_allocator, resources.module_env, builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping, null);
defer interpreter.deinit();
const enable_trace = should_trace == .trace;
@ -81,7 +81,7 @@ pub fn runExpectInt(src: []const u8, expected_int: i128, should_trace: enum { tr
const builtin_types = BuiltinTypes.init(resources.builtin_indices, resources.builtin_module.env, resources.builtin_module.env, resources.builtin_module.env);
const imported_envs = [_]*const can.ModuleEnv{resources.builtin_module.env};
var interpreter = try Interpreter.init(test_allocator, resources.module_env, builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping);
var interpreter = try Interpreter.init(test_allocator, resources.module_env, builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping, null);
defer interpreter.deinit();
const enable_trace = should_trace == .trace;
@ -119,7 +119,7 @@ pub fn runExpectBool(src: []const u8, expected_bool: bool, should_trace: enum {
const builtin_types = BuiltinTypes.init(resources.builtin_indices, resources.builtin_module.env, resources.builtin_module.env, resources.builtin_module.env);
const imported_envs = [_]*const can.ModuleEnv{resources.builtin_module.env};
var interpreter = try Interpreter.init(test_allocator, resources.module_env, builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping);
var interpreter = try Interpreter.init(test_allocator, resources.module_env, builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping, null);
defer interpreter.deinit();
const enable_trace = should_trace == .trace;
@ -158,7 +158,7 @@ pub fn runExpectF32(src: []const u8, expected_f32: f32, should_trace: enum { tra
const builtin_types = BuiltinTypes.init(resources.builtin_indices, resources.builtin_module.env, resources.builtin_module.env, resources.builtin_module.env);
const imported_envs = [_]*const can.ModuleEnv{resources.builtin_module.env};
var interpreter = try Interpreter.init(test_allocator, resources.module_env, builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping);
var interpreter = try Interpreter.init(test_allocator, resources.module_env, builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping, null);
defer interpreter.deinit();
const enable_trace = should_trace == .trace;
@ -191,7 +191,7 @@ pub fn runExpectF64(src: []const u8, expected_f64: f64, should_trace: enum { tra
const builtin_types = BuiltinTypes.init(resources.builtin_indices, resources.builtin_module.env, resources.builtin_module.env, resources.builtin_module.env);
const imported_envs = [_]*const can.ModuleEnv{resources.builtin_module.env};
var interpreter = try Interpreter.init(test_allocator, resources.module_env, builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping);
var interpreter = try Interpreter.init(test_allocator, resources.module_env, builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping, null);
defer interpreter.deinit();
const enable_trace = should_trace == .trace;
@ -226,7 +226,7 @@ pub fn runExpectDec(src: []const u8, expected_dec_num: i128, should_trace: enum
const builtin_types = BuiltinTypes.init(resources.builtin_indices, resources.builtin_module.env, resources.builtin_module.env, resources.builtin_module.env);
const imported_envs = [_]*const can.ModuleEnv{resources.builtin_module.env};
var interpreter = try Interpreter.init(test_allocator, resources.module_env, builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping);
var interpreter = try Interpreter.init(test_allocator, resources.module_env, builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping, null);
defer interpreter.deinit();
const enable_trace = should_trace == .trace;
@ -257,7 +257,7 @@ pub fn runExpectStr(src: []const u8, expected_str: []const u8, should_trace: enu
const builtin_types = BuiltinTypes.init(resources.builtin_indices, resources.builtin_module.env, resources.builtin_module.env, resources.builtin_module.env);
const imported_envs = [_]*const can.ModuleEnv{resources.builtin_module.env};
var interpreter = try Interpreter.init(test_allocator, resources.module_env, builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping);
var interpreter = try Interpreter.init(test_allocator, resources.module_env, builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping, null);
defer interpreter.deinit();
const enable_trace = should_trace == .trace;
@ -307,7 +307,7 @@ pub fn runExpectTuple(src: []const u8, expected_elements: []const ExpectedElemen
const builtin_types = BuiltinTypes.init(resources.builtin_indices, resources.builtin_module.env, resources.builtin_module.env, resources.builtin_module.env);
const imported_envs = [_]*const can.ModuleEnv{resources.builtin_module.env};
var interpreter = try Interpreter.init(test_allocator, resources.module_env, builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping);
var interpreter = try Interpreter.init(test_allocator, resources.module_env, builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping, null);
defer interpreter.deinit();
const enable_trace = should_trace == .trace;
@ -358,7 +358,7 @@ pub fn runExpectRecord(src: []const u8, expected_fields: []const ExpectedField,
const builtin_types = BuiltinTypes.init(resources.builtin_indices, resources.builtin_module.env, resources.builtin_module.env, resources.builtin_module.env);
const imported_envs = [_]*const can.ModuleEnv{resources.builtin_module.env};
var interpreter = try Interpreter.init(test_allocator, resources.module_env, builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping);
var interpreter = try Interpreter.init(test_allocator, resources.module_env, builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping, null);
defer interpreter.deinit();
const enable_trace = should_trace == .trace;
@ -416,6 +416,53 @@ pub fn runExpectRecord(src: []const u8, expected_fields: []const ExpectedField,
}
}
/// Helpers to setup and run an interpreter expecting a list of i64 result.
pub fn runExpectListI64(src: []const u8, expected_elements: []const i64, should_trace: enum { trace, no_trace }) !void {
const resources = try parseAndCanonicalizeExpr(test_allocator, src);
defer cleanupParseAndCanonical(test_allocator, resources);
var test_env_instance = TestEnv.init(test_allocator);
defer test_env_instance.deinit();
const builtin_types = BuiltinTypes.init(resources.builtin_indices, resources.builtin_module.env, resources.builtin_module.env, resources.builtin_module.env);
const imported_envs = [_]*const can.ModuleEnv{resources.builtin_module.env};
var interpreter = try Interpreter.init(test_allocator, resources.module_env, builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping);
defer interpreter.deinit();
const enable_trace = should_trace == .trace;
if (enable_trace) {
interpreter.startTrace();
}
defer if (enable_trace) interpreter.endTrace();
const ops = test_env_instance.get_ops();
const result = try interpreter.eval(resources.expr_idx, ops);
const layout_cache = &interpreter.runtime_layout_store;
defer result.decref(layout_cache, ops);
// Verify we got a list layout
try std.testing.expect(result.layout.tag == .list or result.layout.tag == .list_of_zst);
// Get the element layout
const elem_layout_idx = result.layout.data.list;
const elem_layout = layout_cache.getLayout(elem_layout_idx);
// Use the ListAccessor to safely access list elements
const list_accessor = try result.asList(layout_cache, elem_layout);
try std.testing.expectEqual(expected_elements.len, list_accessor.len());
for (expected_elements, 0..) |expected_val, i| {
const element = try list_accessor.getElement(i);
// Check if this is an integer
try std.testing.expect(element.layout.tag == .scalar);
try std.testing.expect(element.layout.data.scalar.tag == .int);
const int_val = element.asI128();
try std.testing.expectEqual(@as(i128, expected_val), int_val);
}
}
/// Parse and canonicalize an expression.
/// Rewrite deferred numeric literals to match their inferred types
/// This is similar to what ComptimeEvaluator does but for test expressions
@ -694,7 +741,7 @@ test "eval tag - already primitive" {
const builtin_types = BuiltinTypes.init(resources.builtin_indices, resources.builtin_module.env, resources.builtin_module.env, resources.builtin_module.env);
const imported_envs = [_]*const can.ModuleEnv{resources.builtin_module.env};
var interpreter = try Interpreter.init(test_allocator, resources.module_env, builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping);
var interpreter = try Interpreter.init(test_allocator, resources.module_env, builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping, null);
defer interpreter.deinit();
const ops = test_env_instance.get_ops();
@ -723,7 +770,7 @@ test "interpreter reuse across multiple evaluations" {
var test_env_instance = TestEnv.init(test_allocator);
defer test_env_instance.deinit();
var interpreter = try Interpreter.init(test_allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interpreter = try Interpreter.init(test_allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interpreter.deinit();
const ops = test_env_instance.get_ops();

View file

@ -89,7 +89,7 @@ test "interpreter poly: return a function then call (int)" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost{ .allocator = std.testing.allocator };
@ -110,7 +110,7 @@ test "interpreter poly: return a function then call (string)" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost{ .allocator = std.testing.allocator };
@ -134,7 +134,7 @@ test "interpreter captures (monomorphic): adder" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost{ .allocator = std.testing.allocator };
@ -155,7 +155,7 @@ test "interpreter captures (monomorphic): constant function" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost{ .allocator = std.testing.allocator };
@ -179,7 +179,7 @@ test "interpreter captures (polymorphic): capture id and apply to int" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost{ .allocator = std.testing.allocator };
@ -200,7 +200,7 @@ test "interpreter captures (polymorphic): capture id and apply to string" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost{ .allocator = std.testing.allocator };
@ -225,7 +225,7 @@ test "interpreter higher-order: apply f then call with 41" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost{ .allocator = std.testing.allocator };
@ -245,7 +245,7 @@ test "interpreter higher-order: apply f twice" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost{ .allocator = std.testing.allocator };
@ -265,7 +265,7 @@ test "interpreter higher-order: pass constructed closure and apply" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost{ .allocator = std.testing.allocator };
@ -285,7 +285,7 @@ test "interpreter higher-order: construct then pass then call" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost{ .allocator = std.testing.allocator };
@ -305,7 +305,7 @@ test "interpreter higher-order: compose id with +1" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost{ .allocator = std.testing.allocator };
@ -325,7 +325,7 @@ test "interpreter higher-order: return poly fn using captured +n" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost{ .allocator = std.testing.allocator };
@ -345,7 +345,7 @@ test "interpreter recursion: simple countdown" {
// const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
// defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
// var host = TestHost{ .allocator = std.testing.allocator };
@ -364,7 +364,7 @@ test "interpreter if: else-if chain selects middle branch" {
// const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
// defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
// var host = TestHost{ .allocator = std.testing.allocator };
@ -386,7 +386,7 @@ test "interpreter var and reassign" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost{ .allocator = std.testing.allocator };
@ -405,7 +405,7 @@ test "interpreter logical or is short-circuiting" {
// const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
// defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
// var host = TestHost{ .allocator = std.testing.allocator };
@ -427,7 +427,7 @@ test "interpreter logical and is short-circuiting" {
// const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
// defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
// var host = TestHost{ .allocator = std.testing.allocator };
@ -449,7 +449,7 @@ test "interpreter recursion: factorial 5 -> 120" {
// const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
// defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
// var host = TestHost{ .allocator = std.testing.allocator };
@ -472,7 +472,7 @@ test "interpreter recursion: fibonacci 5 -> 5" {
// const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
// defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
// var host = TestHost{ .allocator = std.testing.allocator };
@ -493,7 +493,7 @@ test "interpreter tag union: one-arg tag Ok(42)" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost{ .allocator = std.testing.allocator };
@ -517,7 +517,7 @@ test "interpreter tag union: multi-arg tag Point(1, 2)" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost{ .allocator = std.testing.allocator };

View file

@ -141,7 +141,7 @@ test "interpreter: (|x| x)(\"Hello\") yields \"Hello\"" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -159,7 +159,7 @@ test "interpreter: (|n| n + 1)(41) yields 42" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -177,7 +177,7 @@ test "interpreter: (|a, b| a + b)(40, 2) yields 42" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -197,7 +197,7 @@ test "interpreter: 6 / 3 yields 2" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -217,7 +217,7 @@ test "interpreter: 7 % 3 yields 1" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -235,7 +235,7 @@ test "interpreter: 0.2 + 0.3 yields 0.5" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -253,7 +253,7 @@ test "interpreter: 0.5 / 2 yields 0.25" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -307,7 +307,7 @@ test "interpreter: literal True renders True" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -326,7 +326,7 @@ test "interpreter: True == False yields False" {
// const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
// defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
// var host = TestHost.init(std.testing.allocator);
@ -347,7 +347,7 @@ test "interpreter: \"hi\" == \"hi\" yields True" {
//
// try helpers.runExpectBool(roc_src, true, .no_trace);
//
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
//
// var host = TestHost.init(std.testing.allocator);
@ -366,7 +366,7 @@ test "interpreter: (1, 2) == (1, 2) yields True" {
// const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
// defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
// var host = TestHost.init(std.testing.allocator);
@ -385,7 +385,7 @@ test "interpreter: (1, 2) == (2, 1) yields False" {
// const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
// defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
// var host = TestHost.init(std.testing.allocator);
@ -404,7 +404,7 @@ test "interpreter: { x: 1, y: 2 } == { y: 2, x: 1 } yields True" {
// const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
// defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
// var host = TestHost.init(std.testing.allocator);
@ -423,7 +423,7 @@ test "interpreter: { x: 1, y: 2 } == { x: 1, y: 3 } yields False" {
// const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
// defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
// var host = TestHost.init(std.testing.allocator);
@ -442,7 +442,7 @@ test "interpreter: record update copies base fields" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -460,7 +460,7 @@ test "interpreter: record update overrides field" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -478,7 +478,7 @@ test "interpreter: record update expression can reference base" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -497,7 +497,7 @@ test "interpreter: record update expression can reference base" {
// const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
// defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
// var host = TestHost.init(std.testing.allocator);
@ -516,7 +516,7 @@ test "interpreter: record update expression can reference base" {
// const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
// defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
// var host = TestHost.init(std.testing.allocator);
@ -535,7 +535,7 @@ test "interpreter: record update expression can reference base" {
// const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
// defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
// var host = TestHost.init(std.testing.allocator);
@ -553,7 +553,7 @@ test "interpreter: [1, 2, 3] == [1, 2, 3] yields True" {
// const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
// defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
// var host = TestHost.init(std.testing.allocator);
@ -572,7 +572,7 @@ test "interpreter: [1, 2, 3] == [1, 3, 2] yields False" {
// const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
// defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
// var host = TestHost.init(std.testing.allocator);
@ -591,7 +591,7 @@ test "interpreter: Ok(1) == Ok(1) yields True" {
// const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
// defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
// var host = TestHost.init(std.testing.allocator);
@ -610,7 +610,7 @@ test "interpreter: Ok(1) == Err(1) yields False" {
// const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
// defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
// var host = TestHost.init(std.testing.allocator);
@ -629,7 +629,7 @@ test "interpreter: match tuple pattern destructures" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -647,7 +647,7 @@ test "interpreter: match bool patterns" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -665,7 +665,7 @@ test "interpreter: match result tag payload" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -683,7 +683,7 @@ test "interpreter: match record destructures fields" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -701,7 +701,7 @@ test "interpreter: render Try.Ok literal" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -720,7 +720,7 @@ test "interpreter: render Try.Err string" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -739,7 +739,7 @@ test "interpreter: render Try.Ok tuple payload" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -758,7 +758,7 @@ test "interpreter: match tuple payload tag" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -776,7 +776,7 @@ test "interpreter: match record payload tag" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -794,7 +794,7 @@ test "interpreter: match list pattern destructures" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -816,7 +816,7 @@ test "interpreter: match list rest binds slice" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -834,7 +834,7 @@ test "interpreter: match empty list branch" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -853,7 +853,7 @@ test "interpreter: simple for loop sum" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -871,7 +871,7 @@ test "interpreter: List.fold sum with inline lambda" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -889,7 +889,7 @@ test "interpreter: List.fold product with inline lambda" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -907,7 +907,7 @@ test "interpreter: List.fold empty list with inline lambda" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -925,7 +925,7 @@ test "interpreter: List.fold count elements with inline lambda" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -944,7 +944,7 @@ test "interpreter: List.fold from Builtin using numbers" {
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
const imported_envs = [_]*const can.ModuleEnv{resources.builtin_module.env};
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -963,7 +963,7 @@ test "interpreter: List.any True on integers" {
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
const imported_envs = [_]*const can.ModuleEnv{resources.builtin_module.env};
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -983,7 +983,7 @@ test "interpreter: List.any False on unsigned integers" {
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
const imported_envs = [_]*const can.ModuleEnv{resources.builtin_module.env};
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -1003,7 +1003,7 @@ test "interpreter: List.any False on empty list" {
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
const imported_envs = [_]*const can.ModuleEnv{resources.builtin_module.env};
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -1023,7 +1023,7 @@ test "interpreter: List.all False when some elements are False" {
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
const imported_envs = [_]*const can.ModuleEnv{resources.builtin_module.env};
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -1043,7 +1043,7 @@ test "interpreter: List.all True on small integers" {
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
const imported_envs = [_]*const can.ModuleEnv{resources.builtin_module.env};
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -1063,7 +1063,7 @@ test "interpreter: List.all False on empty list" {
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
const imported_envs = [_]*const can.ModuleEnv{resources.builtin_module.env};
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -1083,7 +1083,7 @@ test "interpreter: List.contains is False for a missing element" {
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
const imported_envs = [_]*const can.ModuleEnv{resources.builtin_module.env};
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -1103,7 +1103,7 @@ test "interpreter: List.contains is True when element is found" {
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
const imported_envs = [_]*const can.ModuleEnv{resources.builtin_module.env};
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -1123,7 +1123,7 @@ test "interpreter: List.contains is False on empty list" {
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
const imported_envs = [_]*const can.ModuleEnv{resources.builtin_module.env};
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -1142,7 +1142,7 @@ test "interpreter: crash statement triggers crash error and message" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -1161,7 +1161,7 @@ test "interpreter: expect expression succeeds" {
// const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
// defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
// var host = TestHost.init(std.testing.allocator);
@ -1181,7 +1181,7 @@ test "interpreter: expect expression failure crashes with message" {
// const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
// defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
// var host = TestHost.init(std.testing.allocator);
@ -1200,7 +1200,7 @@ test "interpreter: empty record expression renders {}" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -1229,7 +1229,7 @@ test "interpreter: decimal literal renders 0.125" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -1247,7 +1247,7 @@ test "interpreter: f64 equality True" {
// const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
// defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
// var host = TestHost.init(std.testing.allocator);
@ -1266,7 +1266,7 @@ test "interpreter: decimal equality True" {
// const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
// defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
// var host = TestHost.init(std.testing.allocator);
@ -1285,7 +1285,7 @@ test "interpreter: int and f64 equality True" {
// const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
// defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
// const binop_expr = resources.module_env.store.getExpr(resources.expr_idx);
@ -1314,7 +1314,7 @@ test "interpreter: int and decimal equality True" {
// const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
// defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
// const binop_expr = resources.module_env.store.getExpr(resources.expr_idx);
@ -1343,7 +1343,7 @@ test "interpreter: int less-than yields True" {
// const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
// defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
// var host = TestHost.init(std.testing.allocator);
@ -1362,7 +1362,7 @@ test "interpreter: int greater-than yields False" {
// const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
// defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
// var host = TestHost.init(std.testing.allocator);
@ -1381,7 +1381,7 @@ test "interpreter: 0.1 + 0.2 yields 0.3" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -1400,7 +1400,7 @@ test "interpreter: f64 greater-than yields True" {
// const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
// defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
// var host = TestHost.init(std.testing.allocator);
@ -1419,7 +1419,7 @@ test "interpreter: decimal less-than-or-equal yields True" {
// const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
// defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
// var host = TestHost.init(std.testing.allocator);
@ -1438,7 +1438,7 @@ test "interpreter: int and f64 less-than yields True" {
// const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
// defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
// var host = TestHost.init(std.testing.allocator);
@ -1457,7 +1457,7 @@ test "interpreter: int and decimal greater-than yields False" {
// const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
// defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
// var host = TestHost.init(std.testing.allocator);
@ -1476,7 +1476,7 @@ test "interpreter: bool inequality yields True" {
// const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
// defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
// var host = TestHost.init(std.testing.allocator);
@ -1495,7 +1495,7 @@ test "interpreter: decimal inequality yields False" {
// const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
// defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
// var host = TestHost.init(std.testing.allocator);
@ -1514,7 +1514,7 @@ test "interpreter: f64 equality False" {
// const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
// defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
// var host = TestHost.init(std.testing.allocator);
@ -1533,7 +1533,7 @@ test "interpreter: decimal equality False" {
// const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
// defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
// var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
// defer interp2.deinit();
// var host = TestHost.init(std.testing.allocator);
@ -1552,7 +1552,7 @@ test "interpreter: tuples and records" {
const src_tuple = "(1, 2)";
const res_t = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, src_tuple);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, res_t);
var it = try Interpreter.init(std.testing.allocator, res_t.module_env, res_t.builtin_types, res_t.builtin_module.env, &[_]*const can.ModuleEnv{}, &res_t.checker.import_mapping);
var it = try Interpreter.init(std.testing.allocator, res_t.module_env, res_t.builtin_types, res_t.builtin_module.env, &[_]*const can.ModuleEnv{}, &res_t.checker.import_mapping, null);
defer it.deinit();
var host_t = TestHost.init(std.testing.allocator);
defer host_t.deinit();
@ -1566,7 +1566,7 @@ test "interpreter: tuples and records" {
const src_rec = "{ x: 1, y: 2 }";
const res_r = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, src_rec);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, res_r);
var ir = try Interpreter.init(std.testing.allocator, res_r.module_env, res_r.builtin_types, res_r.builtin_module.env, &[_]*const can.ModuleEnv{}, &res_r.checker.import_mapping);
var ir = try Interpreter.init(std.testing.allocator, res_r.module_env, res_r.builtin_types, res_r.builtin_module.env, &[_]*const can.ModuleEnv{}, &res_r.checker.import_mapping, null);
defer ir.deinit();
var host_r = TestHost.init(std.testing.allocator);
defer host_r.deinit();
@ -1584,7 +1584,7 @@ test "interpreter: empty list [] has list_of_zst layout" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -1603,7 +1603,7 @@ test "interpreter: singleton list [1] has list of Dec layout" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp2 = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp2.deinit();
var host = TestHost.init(std.testing.allocator);
@ -1633,7 +1633,7 @@ test "interpreter: dbg statement in block" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp.deinit();
var host = TestHost.init(std.testing.allocator);
@ -1665,7 +1665,7 @@ test "interpreter: dbg statement with string" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp.deinit();
var host = TestHost.init(std.testing.allocator);
@ -1697,7 +1697,7 @@ test "interpreter: simple early return from function" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp.deinit();
var host = TestHost.init(std.testing.allocator);
@ -1731,7 +1731,7 @@ test "interpreter: any function with early return in for loop" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp.deinit();
var host = TestHost.init(std.testing.allocator);
@ -1764,7 +1764,7 @@ test "interpreter: crash at end of block in if branch" {
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping);
var interp = try Interpreter.init(std.testing.allocator, resources.module_env, resources.builtin_types, resources.builtin_module.env, &[_]*const can.ModuleEnv{}, &resources.checker.import_mapping, null);
defer interp.deinit();
var host = TestHost.init(std.testing.allocator);

View file

@ -63,6 +63,13 @@ test "list refcount builtins - phase 12 limitation documented" {
// - "e_low_level_lambda - List.sublist on non-empty list"
// - "e_low_level_lambda - List.sublist start out of bounds"
// - "e_low_level_lambda - List.sublist requesting beyond end of list gives you input list"
// - "e_low_level_lambda - List.append on non-empty list"
// - "e_low_level_lambda - List.append on empty list"
// - "e_low_level_lambda - List.append a list on empty list"
// - "e_low_level_lambda - List.append for strings"
// - "e_low_level_lambda - List.append for list of lists"
// - "e_low_level_lambda - List.append for already refcounted elt"
//
// interpreter_style_test.zig:
// - "interpreter: match list pattern destructures"

View file

@ -755,6 +755,100 @@ test "e_low_level_lambda - List.with_capacity of zero-sized type creates empty l
try testing.expectEqual(@as(i128, 0), len_value);
}
test "e_low_level_lambda - List.append on non-empty list" {
const src =
\\x = List.append([0, 1, 2, 3], 4)
\\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.append on empty list" {
const src =
\\x = List.append([], 0)
\\got = List.get(x, 0)
;
const get_value = try evalModuleAndGetString(src, 1, test_allocator);
defer test_allocator.free(get_value);
try testing.expectEqualStrings("Ok(0)", get_value);
}
test "e_low_level_lambda - List.append a list on empty list" {
const src =
\\x = List.append([], [])
\\len = List.len(x)
\\got = List.get(x, 0)
;
const len_value = try evalModuleAndGetInt(src, 1);
try testing.expectEqual(@as(i128, 1), len_value);
}
test "e_low_level_lambda - List.append for strings" {
const src =
\\x = List.append(["cat", "chases"], "rat")
\\len = List.len(x)
\\got = List.get(x, 2)
;
const len_value = try evalModuleAndGetInt(src, 1);
try testing.expectEqual(@as(i128, 3), len_value);
const get_value = try evalModuleAndGetString(src, 2, test_allocator);
defer test_allocator.free(get_value);
try testing.expectEqualStrings("Ok(\"rat\")", get_value);
}
test "e_low_level_lambda - List.append for list of lists" {
const src =
\\x = List.append([[0, 1], [2, 3, 4], [5, 6, 7]], [8,9])
\\len = List.len(x)
;
const len_value = try evalModuleAndGetInt(src, 1);
try testing.expectEqual(@as(i128, 4), len_value);
}
test "e_low_level_lambda - List.append for list of tuples" {
const src =
\\x = List.append([(-1, 0, 1), (2, 3, 4), (5, 6, 7)], (-2, -3, -4))
\\len = List.len(x)
;
const len_value = try evalModuleAndGetInt(src, 1);
try testing.expectEqual(@as(i128, 4), len_value);
}
test "e_low_level_lambda - List.append for list of records" {
const src =
\\x = List.append([{x:"1", y: "1"}, {x: "2", y: "4"}, {x: "5", y: "7"}], {x: "2", y: "4"})
\\len = List.len(x)
\\tail = match List.get(x, 3) { Ok(rec) => rec.x, _ => "wrong"}
;
const len_value = try evalModuleAndGetInt(src, 1);
try testing.expectEqual(@as(i128, 4), len_value);
const get_value = try evalModuleAndGetString(src, 2, test_allocator);
defer test_allocator.free(get_value);
try testing.expectEqualStrings("\"2\"", get_value);
}
test "e_low_level_lambda - List.append for already refcounted elt" {
const src =
\\new = [8, 9]
\\w = [new, new, new, [10, 11]]
\\x = List.append([[0, 1], [2, 3, 4], [5, 6, 7]], new)
\\len = List.len(x)
;
const len_value = try evalModuleAndGetInt(src, 3);
try testing.expectEqual(@as(i128, 4), len_value);
}
test "e_low_level_lambda - List.drop_at on an empty list at index 0" {
const src =
\\x = List.drop_at([], 0)
@ -2064,6 +2158,13 @@ test "e_low_level_lambda - List.sort_with single element" {
}
test "e_low_level_lambda - List.sort_with with duplicates" {
// TODO: This test is skipped due to stack memory accumulation across eval() calls.
// The interpreter stores return values in stack memory, and bindings hold references
// to this memory. When evaluating multiple declarations (x, then first, then len),
// the stack grows without being freed, eventually causing overflow.
// See: https://github.com/roc-lang/roc/issues/XXXX (architectural issue)
if (true) return error.SkipZigTest;
const src =
\\x = List.sort_with([3, 1, 2, 1, 3], |a, b| if a < b LT else if a > b GT else EQ)
\\first = List.first(x)

View file

@ -158,7 +158,7 @@ pub const TestRunner = struct {
return TestRunner{
.allocator = allocator,
.env = cir,
.interpreter = try Interpreter.init(allocator, cir, builtin_types_param, builtin_module_env, other_modules, import_mapping),
.interpreter = try Interpreter.init(allocator, cir, builtin_types_param, builtin_module_env, other_modules, import_mapping, null),
.crash = CrashContext.init(allocator),
.roc_ops = null,
.test_results = std.array_list.Managed(TestResult).init(allocator),

View file

@ -953,7 +953,19 @@ const Formatter = struct {
if (multiline and try fmt.flushCommentsAfter(ld.operator)) {
try fmt.pushIndent();
}
_ = try fmt.formatExprInner(ld.right, .no_indent_on_access);
// For arrow syntax, omit empty parens: `foo->bar()` becomes `foo->bar`
const right_expr = fmt.ast.store.getExpr(ld.right);
if (right_expr == .apply) {
const apply = right_expr.apply;
if (fmt.ast.store.exprSlice(apply.args).len == 0) {
// Zero-arg apply: just format the function, not the empty parens
_ = try fmt.formatExprInner(apply.@"fn", .no_indent_on_access);
} else {
_ = try fmt.formatExprInner(ld.right, .no_indent_on_access);
}
} else {
_ = try fmt.formatExprInner(ld.right, .no_indent_on_access);
}
},
.int => |i| {
try fmt.pushTokenText(i.token);

View file

@ -19,7 +19,8 @@ const SharedMemoryAllocator = ipc.SharedMemoryAllocator;
// Global state for shared memory - initialized once per process
var shared_memory_initialized: std.atomic.Value(bool) = std.atomic.Value(bool).init(false);
var global_shm: ?SharedMemoryAllocator = null;
var global_env_ptr: ?*ModuleEnv = null;
var global_env_ptr: ?*ModuleEnv = null; // Primary env for entry point lookups (platform or app)
var global_app_env_ptr: ?*ModuleEnv = null; // App env for e_lookup_required resolution
var global_builtin_modules: ?eval.BuiltinModules = null;
var global_imported_envs: ?[]*const ModuleEnv = null;
var shm_mutex: std.Thread.Mutex = .{};
@ -40,6 +41,8 @@ const Header = struct {
entry_count: u32,
def_indices_offset: u64,
module_envs_offset: u64, // Offset to array of module env offsets
platform_main_env_offset: u64, // 0 if no platform, entry points are in app
app_env_offset: u64, // Always present, used for e_lookup_required resolution
};
/// Comprehensive error handling for the shim
@ -106,7 +109,7 @@ fn initializeSharedMemoryOnce(roc_ops: *RocOps) ShimError!void {
};
// Set up ModuleEnv from shared memory
const env_ptr = try setupModuleEnv(&shm, roc_ops);
const setup_result = try setupModuleEnv(&shm, roc_ops);
// Load builtin modules from compiled binary (same as CLI does)
const builtin_modules = eval.BuiltinModules.init(allocator) catch |err| {
@ -117,7 +120,8 @@ fn initializeSharedMemoryOnce(roc_ops: *RocOps) ShimError!void {
// Store globals
global_shm = shm;
global_env_ptr = env_ptr;
global_env_ptr = setup_result.primary_env;
global_app_env_ptr = setup_result.app_env;
global_builtin_modules = builtin_modules;
// Mark as initialized (release semantics ensure all writes above are visible)
@ -132,12 +136,13 @@ fn evaluateFromSharedMemory(entry_idx: u32, roc_ops: *RocOps, ret_ptr: *anyopaqu
// Use the global shared memory and environment
const shm = global_shm.?;
const env_ptr = global_env_ptr.?;
const app_env = global_app_env_ptr;
// Get builtin modules
const builtin_modules = &global_builtin_modules.?;
// Set up interpreter infrastructure (per-call, as it's lightweight)
var interpreter = try createInterpreter(env_ptr, builtin_modules, roc_ops);
var interpreter = try createInterpreter(env_ptr, app_env, builtin_modules, roc_ops);
defer interpreter.deinit();
// Get expression info from shared memory using entry_idx
@ -169,8 +174,14 @@ fn evaluateFromSharedMemory(entry_idx: u32, roc_ops: *RocOps, ret_ptr: *anyopaqu
try interpreter.evaluateExpression(expr_idx, ret_ptr, roc_ops, arg_ptr);
}
/// Result of setting up module environments
const SetupResult = struct {
primary_env: *ModuleEnv, // Platform main env or app env (for entry points)
app_env: *ModuleEnv, // App env (for e_lookup_required resolution)
};
/// Set up ModuleEnv from shared memory with proper relocation (multi-module format)
fn setupModuleEnv(shm: *SharedMemoryAllocator, roc_ops: *RocOps) ShimError!*ModuleEnv {
fn setupModuleEnv(shm: *SharedMemoryAllocator, roc_ops: *RocOps) ShimError!SetupResult {
// Validate memory layout - we need at least space for the header
const min_required_size = FIRST_ALLOC_OFFSET + @sizeOf(Header);
if (shm.total_size < min_required_size) {
@ -227,20 +238,29 @@ fn setupModuleEnv(shm: *SharedMemoryAllocator, roc_ops: *RocOps) ShimError!*Modu
// Store imported envs globally
global_imported_envs = imported_envs;
// Get and relocate the app module (last in the array)
const app_module_offset = module_env_offsets[module_count - 1];
const app_env_addr = @intFromPtr(base_ptr) + @as(usize, @intCast(app_module_offset));
// Get and relocate the app module using the header's app_env_offset
const app_env_addr = @intFromPtr(base_ptr) + @as(usize, @intCast(header_ptr.app_env_offset));
const app_env_ptr: *ModuleEnv = @ptrFromInt(app_env_addr);
// Relocate all pointers in the app ModuleEnv
app_env_ptr.relocate(@intCast(offset));
app_env_ptr.gpa = allocator;
return app_env_ptr;
// Determine primary env: platform main if available, otherwise app
const primary_env: *ModuleEnv = if (header_ptr.platform_main_env_offset != 0) blk: {
const platform_env_addr = @intFromPtr(base_ptr) + @as(usize, @intCast(header_ptr.platform_main_env_offset));
const platform_env_ptr: *ModuleEnv = @ptrFromInt(platform_env_addr);
platform_env_ptr.relocate(@intCast(offset));
platform_env_ptr.gpa = allocator;
break :blk platform_env_ptr;
} else app_env_ptr;
return SetupResult{
.primary_env = primary_env,
.app_env = app_env_ptr,
};
}
/// Create and initialize interpreter with heap-allocated stable objects
fn createInterpreter(env_ptr: *ModuleEnv, builtin_modules: *const eval.BuiltinModules, roc_ops: *RocOps) ShimError!Interpreter {
fn createInterpreter(env_ptr: *ModuleEnv, app_env: ?*ModuleEnv, builtin_modules: *const eval.BuiltinModules, roc_ops: *RocOps) ShimError!Interpreter {
const allocator = std.heap.page_allocator;
// Use builtin types from the loaded builtin modules
@ -281,7 +301,15 @@ fn createInterpreter(env_ptr: *ModuleEnv, builtin_modules: *const eval.BuiltinMo
// Resolve imports - map each import name to its index in imported_envs
env_ptr.imports.resolveImports(env_ptr, imported_envs);
const interpreter = eval.Interpreter.init(allocator, env_ptr, builtin_types, builtin_module_env, imported_envs, &shim_import_mapping) catch {
// Also resolve imports for the app env if it's different from the primary env
// This is needed when the platform calls the app's main! via e_lookup_required
if (app_env) |a_env| {
if (a_env != env_ptr) {
a_env.imports.resolveImports(a_env, imported_envs);
}
}
const interpreter = eval.Interpreter.init(allocator, env_ptr, builtin_types, builtin_module_env, imported_envs, &shim_import_mapping, app_env) catch {
roc_ops.crash("INTERPRETER SHIM: Interpreter initialization failed");
return error.InterpreterSetupFailed;
};

View file

@ -4,14 +4,24 @@ written in Zig as part of the Rust to Zig rewrite.
## Current state
The experimental LSP currently only holds the scaffolding for the incoming implementation.
It doesn't implement any LSP capabilities yet except `initialized` and `exit` which allows it
to be connected to an editor and verify it's actually running.
It doesn't provide any features yet, but it does connect to your editor, detect file change
and store the buffer in memory.
The following request have been handled :
- `initialize`
- `shutdown`
The following notifications have been handled :
- `initialized`
- `exit`
- `didOpen` (stores the buffer into a `StringHashMap`, but doesn't do any action on it)
- `didChange` (same as `didOpen`, but also supports incremental changes)
## How to implement new LSP capabilities
The core functionalities of the LSP have been implemented in a way so that `transport.zig` and
`protocol.zig` shouldn't have to be modified as more capabilities are added. When handling a new
LSP method, like `textDocument/completion` for example, the handler should be added in the `handlers`
directory and its call should be added in `server.zig` like this :
directory and its call should be added either in `request` (if it expects a response) or `notification`
(if it doesn't expect a response). `textDocument/completion` for example would go here :
```zig
const request_handlers = std.StaticStringMap(HandlerPtr).initComptime(.{
.{ "initialize", &InitializeHandler.call },
@ -19,8 +29,23 @@ const request_handlers = std.StaticStringMap(HandlerPtr).initComptime(.{
.{ "textDocument/completion", &CompletionHandler.call },
});
```
The `Server` holds the state so it will be responsible of knowing the project and how different parts
interact. This is then accessible by every handler.
When adding a new capability, if the server is ready to support it, you need to add the capabilities to
the `capabilities.zig` file for the `initialize` response to tell the client the capabilities is available :
```zig
pub fn buildCapabilities() ServerCapabilities {
return .{
.textDocumentSync = .{
.openClose = true,
.change = @intFromEnum(ServerCapabilities.TextDocumentSyncKind.incremental),
},
};
}
```
Here we tell the client that `textDocumentSync` is available in accordance to the LSP specifications data
structure. The `Server` struct holds the state, meaning in has the knowledge of the project files, the
documentation, the type inference, the syntax, etc. Every handler has access to it. These points of knowledge
are ideally separated in different fields of the server. For example, the opened buffer and other desired files
are stored in a `DocumentStore` which is a struct containing a `StringHashMap`, accessible through the `Server`.
## Starting the server
Build the Roc toolchain and run:
@ -37,7 +62,7 @@ roc experimental-lsp --debug-transport
Passing the `--debug-transport` flag will create a log file in your OS tmp folder (`/tmp` on Unix
systems). A mirror of the raw JSON-RPC traffic will be appended to the log file. Watching the file
will allow an user to see incoming and outgoing message between the server and the editor
will allow a user to see incoming and outgoing message between the server and the editor
```bash
tail -f /tmp/roc-lsp-debug.log
---

28
src/lsp/capabilities.zig Normal file
View file

@ -0,0 +1,28 @@
const std = @import("std");
/// Aggregates all server capabilities supported by the Roc LSP.
pub const ServerCapabilities = struct {
positionEncoding: []const u8 = "utf-16",
textDocumentSync: ?TextDocumentSyncOptions = null,
pub const TextDocumentSyncOptions = struct {
openClose: bool = false,
change: u32 = @intFromEnum(TextDocumentSyncKind.none),
};
pub const TextDocumentSyncKind = enum(u32) {
none = 0,
full = 1,
incremental = 2,
};
};
/// Returns the server capabilities currently implemented.
pub fn buildCapabilities() ServerCapabilities {
return .{
.textDocumentSync = .{
.openClose = true,
.change = @intFromEnum(ServerCapabilities.TextDocumentSyncKind.incremental),
},
};
}

108
src/lsp/document_store.zig Normal file
View file

@ -0,0 +1,108 @@
const std = @import("std");
/// Stores the latest contents of each open text document.
pub const DocumentStore = struct {
allocator: std.mem.Allocator,
entries: std.StringHashMap(Document),
/// Snapshot of a document's contents and version.
pub const Document = struct {
text: []u8,
version: i64,
};
pub const Range = struct {
start_line: usize,
start_character: usize,
end_line: usize,
end_character: usize,
};
/// Creates an empty store backed by the provided allocator.
pub fn init(allocator: std.mem.Allocator) DocumentStore {
return .{ .allocator = allocator, .entries = std.StringHashMap(Document).init(allocator) };
}
/// Releases all tracked documents and frees associated memory.
pub fn deinit(self: *DocumentStore) void {
var it = self.entries.iterator();
while (it.next()) |entry| {
self.allocator.free(entry.key_ptr.*);
self.allocator.free(entry.value_ptr.text);
}
self.entries.deinit();
self.* = undefined;
}
/// Inserts or replaces the document at `uri` with the given text and version.
pub fn upsert(self: *DocumentStore, uri: []const u8, version: i64, text: []const u8) !void {
const gop = try self.entries.getOrPut(uri);
if (!gop.found_existing) {
gop.key_ptr.* = try self.allocator.dupe(u8, uri);
} else {
self.allocator.free(gop.value_ptr.text);
}
gop.value_ptr.* = .{
.text = try self.allocator.dupe(u8, text),
.version = version,
};
}
/// Removes a document from the store, if present.
pub fn remove(self: *DocumentStore, uri: []const u8) void {
if (self.entries.fetchRemove(uri)) |removed| {
self.allocator.free(removed.key);
self.allocator.free(removed.value.text);
}
}
/// Returns the stored document (if any). The returned slice references memory owned by the store.
pub fn get(self: *DocumentStore, uri: []const u8) ?Document {
if (self.entries.get(uri)) |doc| {
return doc;
}
return null;
}
/// Applies a range replacement to an existing document using UTF-16 positions.
pub fn applyRangeReplacement(self: *DocumentStore, uri: []const u8, version: i64, range: Range, new_text: []const u8) !void {
const entry = self.entries.getPtr(uri) orelse return error.DocumentNotFound;
const start_offset = try positionToOffset(entry.text, range.start_line, range.start_character);
const end_offset = try positionToOffset(entry.text, range.end_line, range.end_character);
if (start_offset > end_offset or end_offset > entry.text.len) return error.InvalidRange;
const replaced = end_offset - start_offset;
const new_len = entry.text.len - replaced + new_text.len;
var buffer = try self.allocator.alloc(u8, new_len);
errdefer self.allocator.free(buffer);
@memcpy(buffer[0..start_offset], entry.text[0..start_offset]);
@memcpy(buffer[start_offset .. start_offset + new_text.len], new_text);
@memcpy(buffer[start_offset + new_text.len ..], entry.text[end_offset..]);
self.allocator.free(entry.text);
entry.text = buffer;
entry.version = version;
}
fn positionToOffset(text: []const u8, line: usize, character_utf16: usize) !usize {
var current_line: usize = 0;
var index: usize = 0;
while (current_line < line) : (current_line += 1) {
const newline_index = std.mem.indexOfScalarPos(u8, text, index, '\n') orelse return error.InvalidPosition;
index = newline_index + 1;
}
var utf16_units: usize = 0;
var it = std.unicode.Utf8Iterator{ .bytes = text[index..], .i = 0 };
while (utf16_units < character_utf16) {
const slice = it.nextCodepointSlice() orelse return error.InvalidPosition;
const cp = std.unicode.utf8Decode(slice) catch return error.InvalidPosition;
utf16_units += if (cp <= 0xFFFF) 1 else 2;
}
if (utf16_units != character_utf16) return error.InvalidPosition;
return index + it.i;
}
};

View file

@ -0,0 +1,95 @@
const std = @import("std");
const DocumentStore = @import("../document_store.zig").DocumentStore;
/// Handler for `textDocument/didChange` notifications (supports incremental edits).
pub fn handler(comptime ServerType: type) type {
return struct {
pub fn call(self: *ServerType, params_value: ?std.json.Value) !void {
const params = params_value orelse return;
const obj = switch (params) {
.object => |o| o,
else => return,
};
const text_doc_value = obj.get("textDocument") orelse return;
const text_doc = switch (text_doc_value) {
.object => |o| o,
else => return,
};
const uri_value = text_doc.get("uri") orelse return;
const uri = switch (uri_value) {
.string => |s| s,
else => return,
};
const version_value = text_doc.get("version") orelse std.json.Value{ .integer = 0 };
const version: i64 = switch (version_value) {
.integer => |v| v,
.float => |f| @intFromFloat(f),
else => 0,
};
const changes_value = obj.get("contentChanges") orelse return;
const changes = switch (changes_value) {
.array => |arr| arr,
else => return,
};
if (changes.items.len == 0) return;
const last_change = changes.items[changes.items.len - 1];
const change_obj = switch (last_change) {
.object => |o| o,
else => return,
};
const text_value = change_obj.get("text") orelse return;
const text = switch (text_value) {
.string => |s| s,
else => return,
};
if (change_obj.get("range")) |range_value| {
const range = parseRange(range_value) catch |err| {
std.log.err("invalid range for {s}: {s}", .{ uri, @errorName(err) });
return;
};
self.doc_store.applyRangeReplacement(uri, version, range, text) catch |err| {
std.log.err("failed to apply incremental change for {s}: {s}", .{ uri, @errorName(err) });
};
} else {
self.doc_store.upsert(uri, version, text) catch |err| {
std.log.err("failed to apply full change for {s}: {s}", .{ uri, @errorName(err) });
};
}
}
fn parseRange(value: std.json.Value) !DocumentStore.Range {
const range_obj = switch (value) {
.object => |o| o,
else => return error.InvalidRange,
};
const start_obj = switch (range_obj.get("start") orelse return error.InvalidRange) {
.object => |o| o,
else => return error.InvalidRange,
};
const end_obj = switch (range_obj.get("end") orelse return error.InvalidRange) {
.object => |o| o,
else => return error.InvalidRange,
};
return DocumentStore.Range{
.start_line = parseIndex(start_obj, "line") catch return error.InvalidRange,
.start_character = parseIndex(start_obj, "character") catch return error.InvalidRange,
.end_line = parseIndex(end_obj, "line") catch return error.InvalidRange,
.end_character = parseIndex(end_obj, "character") catch return error.InvalidRange,
};
}
fn parseIndex(obj: std.json.ObjectMap, field: []const u8) !usize {
const value = obj.get(field) orelse return error.MissingField;
return switch (value) {
.integer => |v| if (v < 0) error.InvalidField else @intCast(v),
.float => |f| if (f < 0) error.InvalidField else @intFromFloat(f),
else => return error.InvalidField,
};
}
};
}

View file

@ -0,0 +1,43 @@
const std = @import("std");
/// Handler for `textDocument/didOpen` notifications.
pub fn handler(comptime ServerType: type) type {
return struct {
pub fn call(self: *ServerType, params_value: ?std.json.Value) !void {
const params = params_value orelse return;
const obj = switch (params) {
.object => |o| o,
else => return,
};
const text_doc_value = obj.get("textDocument") orelse return;
const text_doc = switch (text_doc_value) {
.object => |o| o,
else => return,
};
const uri_value = text_doc.get("uri") orelse return;
const uri = switch (uri_value) {
.string => |s| s,
else => return,
};
const text_value = text_doc.get("text") orelse return;
const text = switch (text_value) {
.string => |s| s,
else => return,
};
const version_value = text_doc.get("version") orelse std.json.Value{ .integer = 0 };
const version: i64 = switch (version_value) {
.integer => |v| v,
.float => |f| @intFromFloat(f),
else => 0,
};
self.doc_store.upsert(uri, version, text) catch |err| {
std.log.err("failed to open {s}: {s}", .{ uri, @errorName(err) });
};
}
};
}

View file

@ -1,5 +1,6 @@
const std = @import("std");
const protocol = @import("../protocol.zig");
const capabilities = @import("../capabilities.zig");
/// Returns the `initialize` method handler for the LSP.
pub fn handler(comptime ServerType: type) type {
@ -20,10 +21,10 @@ pub fn handler(comptime ServerType: type) type {
self.state = .waiting_for_initialized;
const response = protocol.InitializeResult{
.capabilities = .{},
.capabilities = capabilities.buildCapabilities(),
.serverInfo = .{
.name = ServerType.server_name,
.version = "0.1",
.version = ServerType.version,
},
};

View file

@ -12,4 +12,5 @@ test "lsp tests" {
std.testing.refAllDecls(@import("test/protocol_test.zig"));
std.testing.refAllDecls(@import("test/server_test.zig"));
std.testing.refAllDecls(@import("test/transport_test.zig"));
std.testing.refAllDecls(@import("test/document_store_test.zig"));
}

View file

@ -191,9 +191,7 @@ pub const ServerInfo = struct {
};
/// Capabilities advertised back to the editor.
pub const ServerCapabilities = struct {
positionEncoding: []const u8 = "utf-16",
};
pub const ServerCapabilities = @import("capabilities.zig").ServerCapabilities;
/// Response body returned after a successful initialization.
pub const InitializeResult = struct {

View file

@ -2,8 +2,11 @@ const std = @import("std");
const builtin = @import("builtin");
const protocol = @import("protocol.zig");
const makeTransport = @import("transport.zig").Transport;
const DocumentStore = @import("document_store.zig").DocumentStore;
const initialize_handler_mod = @import("handlers/initialize.zig");
const shutdown_handler_mod = @import("handlers/shutdown.zig");
const did_open_handler_mod = @import("handlers/did_open.zig");
const did_change_handler_mod = @import("handlers/did_change.zig");
const log = std.log.scoped(.roc_lsp_server);
@ -14,19 +17,29 @@ pub fn Server(comptime ReaderType: type, comptime WriterType: type) type {
const TransportType = makeTransport(ReaderType, WriterType);
const HandlerFn = fn (*Self, *protocol.JsonId, ?std.json.Value) anyerror!void;
const HandlerPtr = *const HandlerFn;
const NotificationFn = fn (*Self, ?std.json.Value) anyerror!void;
const NotificationPtr = *const NotificationFn;
const InitializeHandler = initialize_handler_mod.handler(Self);
const ShutdownHandler = shutdown_handler_mod.handler(Self);
const request_handlers = std.StaticStringMap(HandlerPtr).initComptime(.{
.{ "initialize", &InitializeHandler.call },
.{ "shutdown", &ShutdownHandler.call },
});
const DidOpenHandler = did_open_handler_mod.handler(Self);
const DidChangeHandler = did_change_handler_mod.handler(Self);
const notification_handlers = std.StaticStringMap(NotificationPtr).initComptime(.{
.{ "textDocument/didOpen", &DidOpenHandler.call },
.{ "textDocument/didChange", &DidChangeHandler.call },
});
allocator: std.mem.Allocator,
transport: TransportType,
client: protocol.ClientState = .{},
state: State = .waiting_for_initialize,
doc_store: DocumentStore,
pub const server_name = "roc-lsp";
pub const version = "0.1";
pub const State = enum {
waiting_for_initialize,
@ -41,12 +54,14 @@ pub fn Server(comptime ReaderType: type, comptime WriterType: type) type {
return .{
.allocator = allocator,
.transport = TransportType.init(allocator, reader, writer, log_file),
.doc_store = DocumentStore.init(allocator),
};
}
pub fn deinit(self: *Self) void {
self.client.deinit(self.allocator);
self.transport.deinit();
self.doc_store.deinit();
}
pub fn run(self: *Self) !void {
@ -112,7 +127,7 @@ pub fn Server(comptime ReaderType: type, comptime WriterType: type) type {
try self.sendError(id, .method_not_found, "method not implemented");
}
fn handleNotification(self: *Self, method: []const u8, _: ?std.json.Value) !void {
fn handleNotification(self: *Self, method: []const u8, params: ?std.json.Value) !void {
if (std.mem.eql(u8, method, "initialized")) {
if (self.state == .waiting_for_initialized) {
self.state = .running;
@ -125,6 +140,13 @@ pub fn Server(comptime ReaderType: type, comptime WriterType: type) type {
return;
}
if (notification_handlers.get(method)) |handler| {
handler(self, params) catch |err| {
log.err("notification handler {s} failed: {s}", .{ method, @errorName(err) });
};
return;
}
// Other notifications are ignored until server capabilities are implemented.
}
@ -166,6 +188,12 @@ pub fn Server(comptime ReaderType: type, comptime WriterType: type) type {
.result = result,
});
}
/// Returns the stored document (testing helper; returns null outside tests).
pub fn getDocumentForTesting(self: *Self, uri: []const u8) ?DocumentStore.Document {
if (!builtin.is_test) return null;
return self.doc_store.get(uri);
}
};
}

View file

@ -4,4 +4,5 @@ comptime {
_ = @import("test/transport_test.zig");
_ = @import("test/server_test.zig");
_ = @import("test/protocol_test.zig");
_ = @import("test/document_store_test.zig");
}

View file

@ -0,0 +1,31 @@
const std = @import("std");
const DocumentStore = @import("../document_store.zig").DocumentStore;
test "document store upserts and retrieves documents" {
const allocator = std.testing.allocator;
var store = DocumentStore.init(allocator);
defer store.deinit();
try store.upsert("file:///test", 1, "hello");
const doc = store.get("file:///test") orelse return error.MissingDocument;
try std.testing.expectEqual(@as(i64, 1), doc.version);
try std.testing.expectEqualStrings("hello", doc.text);
}
test "document store applies incremental changes" {
const allocator = std.testing.allocator;
var store = DocumentStore.init(allocator);
defer store.deinit();
try store.upsert("file:///test", 1, "hello world");
try store.applyRangeReplacement(
"file:///test",
2,
.{ .start_line = 0, .start_character = 6, .end_line = 0, .end_character = 11 },
"roc",
);
const doc = store.get("file:///test") orelse return error.MissingDocument;
try std.testing.expectEqual(@as(i64, 2), doc.version);
try std.testing.expectEqualStrings("hello roc", doc.text);
}

View file

@ -150,3 +150,39 @@ test "server rejects re-initialization requests" {
const error_obj = parsed_error.value.object.get("error") orelse return error.ExpectedError;
try std.testing.expect(error_obj.object.get("code").?.integer == @intFromEnum(protocol.ErrorCode.invalid_request));
}
test "server tracks documents on didOpen/didChange" {
const allocator = std.testing.allocator;
const open_msg = try frame(allocator,
\\{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///test.roc","version":1,"text":"app main = 0"}}}
);
defer allocator.free(open_msg);
const change_msg = try frame(allocator,
\\{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///test.roc","version":2},"contentChanges":[{"text":"app main = 42","range":{"start":{"line":0,"character":0},"end":{"line":0,"character":12}}}]}}
);
defer allocator.free(change_msg);
var builder = std.ArrayList(u8){};
defer builder.deinit(allocator);
try builder.ensureTotalCapacity(allocator, open_msg.len + change_msg.len);
try builder.appendSlice(allocator, open_msg);
try builder.appendSlice(allocator, change_msg);
const combined = try builder.toOwnedSlice(allocator);
defer allocator.free(combined);
var reader_stream = std.io.fixedBufferStream(combined);
var writer_buffer: [32]u8 = undefined;
var writer_stream = std.io.fixedBufferStream(&writer_buffer);
const ReaderType = @TypeOf(reader_stream.reader());
const WriterType = @TypeOf(writer_stream.writer());
var server = try server_module.Server(ReaderType, WriterType).init(allocator, reader_stream.reader(), writer_stream.writer(), null);
defer server.deinit();
try server.run();
const maybe_doc = server.getDocumentForTesting("file:///test.roc");
try std.testing.expect(maybe_doc != null);
const doc = maybe_doc.?;
try std.testing.expectEqualStrings("app main = 42", doc.text);
try std.testing.expectEqual(@as(i64, 2), doc.version);
}

View file

@ -2199,30 +2199,34 @@ pub fn parseExprWithBp(self: *Parser, min_bp: u8) Error!AST.Expr.Idx {
} else if (self.peek() == .OpArrow) {
const s = self.pos;
self.advance();
if (self.peek() == .LowerIdent) {
const empty_qualifiers = try self.store.tokenSpanFrom(self.store.scratchTokenTop());
const ident = try self.store.addExpr(.{ .ident = .{
.region = .{ .start = self.pos, .end = self.pos },
.token = self.pos,
.qualifiers = empty_qualifiers,
} });
self.advance();
const ident_suffixed = try self.parseExprSuffix(s, ident);
expression = try self.store.addExpr(.{ .local_dispatch = .{
.region = .{ .start = start, .end = self.pos },
.operator = s,
.left = expression,
.right = ident_suffixed,
} });
} else if (self.peek() == .UpperIdent) { // UpperIdent - should be a tag
const empty_qualifiers = try self.store.tokenSpanFrom(self.store.scratchTokenTop());
const tag = try self.store.addExpr(.{ .tag = .{
.region = .{ .start = self.pos, .end = self.pos },
.token = self.pos,
.qualifiers = empty_qualifiers,
} });
self.advance();
const ident_suffixed = try self.parseExprSuffix(s, tag);
const first_token_tag = self.peek();
if (first_token_tag == .LowerIdent or first_token_tag == .UpperIdent) {
const ident_start = self.pos;
const qual_result = try self.parseQualificationChain();
// Use final token as end position to avoid newline tokens
self.pos = qual_result.final_token + 1;
// Determine if final token is a tag (UpperIdent or ends with NoSpaceDotUpperIdent)
// For unqualified names, check the original token; for qualified names, use is_upper
const is_tag = if (qual_result.qualifiers.span.len == 0)
first_token_tag == .UpperIdent
else
qual_result.is_upper;
const expr_node = if (is_tag)
try self.store.addExpr(.{ .tag = .{
.region = .{ .start = ident_start, .end = self.pos },
.token = qual_result.final_token,
.qualifiers = qual_result.qualifiers,
} })
else
try self.store.addExpr(.{ .ident = .{
.region = .{ .start = ident_start, .end = self.pos },
.token = qual_result.final_token,
.qualifiers = qual_result.qualifiers,
} });
const ident_suffixed = try self.parseExprSuffix(s, expr_node);
expression = try self.store.addExpr(.{ .local_dispatch = .{
.region = .{ .start = start, .end = self.pos },
.operator = s,

View file

@ -1913,14 +1913,28 @@ fn rebuildBufferForTesting(buf: []const u8, tokens: *TokenizedBuffer, alloc: std
},
.UpperIdent => {
try buf2.append('Z');
for (1..length) |_| {
try buf2.append('z');
if (length > 0 and buf[region.start.offset] == '$') {
try buf2.append('$');
for (1..length) |_| {
try buf2.append('Z');
}
} else {
try buf2.append('Z');
for (1..length) |_| {
try buf2.append('z');
}
}
},
.LowerIdent => {
for (0..length) |_| {
try buf2.append('z');
if (length > 0 and buf[region.start.offset] == '$') {
try buf2.append('$');
for (1..length) |_| {
try buf2.append('z');
}
} else {
for (0..length) |_| {
try buf2.append('z');
}
}
},
.Underscore => {

View file

@ -638,7 +638,7 @@ pub const Repl = struct {
// Create interpreter instance with BuiltinTypes containing real Builtin module
const builtin_types_for_eval = BuiltinTypes.init(self.builtin_indices, self.builtin_module.env, self.builtin_module.env, self.builtin_module.env);
var interpreter = eval_mod.Interpreter.init(self.allocator, module_env, builtin_types_for_eval, self.builtin_module.env, &imported_modules, &checker.import_mapping) catch |err| {
var interpreter = eval_mod.Interpreter.init(self.allocator, module_env, builtin_types_for_eval, self.builtin_module.env, &imported_modules, &checker.import_mapping, null) catch |err| {
return try std.fmt.allocPrint(self.allocator, "Interpreter init error: {}", .{err});
};
defer interpreter.deinitAndFreeOtherEnvs();
@ -823,7 +823,7 @@ pub const Repl = struct {
};
const builtin_types_for_eval = BuiltinTypes.init(self.builtin_indices, self.builtin_module.env, self.builtin_module.env, self.builtin_module.env);
var interpreter = eval_mod.Interpreter.init(self.allocator, module_env, builtin_types_for_eval, self.builtin_module.env, &imported_modules, &checker.import_mapping) catch |err| {
var interpreter = eval_mod.Interpreter.init(self.allocator, module_env, builtin_types_for_eval, self.builtin_module.env, &imported_modules, &checker.import_mapping, null) catch |err| {
return .{ .eval_error = try std.fmt.allocPrint(self.allocator, "Interpreter init error: {}", .{err}) };
};
defer interpreter.deinitAndFreeOtherEnvs();

View file

@ -346,7 +346,7 @@ test "Repl - minimal interpreter integration" {
// Step 6: Create interpreter
const builtin_types = eval.BuiltinTypes.init(builtin_indices, builtin_module.env, builtin_module.env, builtin_module.env);
var interpreter = try Interpreter.init(gpa, &module_env, builtin_types, builtin_module.env, &imported_envs, &checker.import_mapping);
var interpreter = try Interpreter.init(gpa, &module_env, builtin_types, builtin_module.env, &imported_envs, &checker.import_mapping, null);
defer interpreter.deinitAndFreeOtherEnvs();
// Step 7: Evaluate

View file

@ -142,19 +142,24 @@ pub const DirExtractWriter = struct {
const file = self.dir.createFile(path, .{}) catch return error.FileCreateFailed;
var entry = FileWriterEntry{
// Append entry first to get stable memory in the array list.
// We must initialize the writer AFTER appending, because the writer
// stores a pointer to the buffer, and if we initialized it on a stack
// variable before copying into the array, the pointer would be stale.
self.open_files.append(.{
.file = file,
.buffer = undefined,
.writer = undefined,
};
entry.writer = file.writer(&entry.buffer);
self.open_files.append(entry) catch {
}) catch {
file.close();
return error.OutOfMemory;
};
return &self.open_files.items[self.open_files.items.len - 1].writer.interface;
// Now initialize the writer with the buffer in the array (stable memory)
const entry = &self.open_files.items[self.open_files.items.len - 1];
entry.writer = file.writer(&entry.buffer);
return &entry.writer.interface;
}
fn finishFile(ptr: *anyopaque, writer: *std.Io.Writer) void {

View file

@ -61,7 +61,7 @@ fn rocCrashedFn(roc_crashed: *const builtins.host_abi.RocCrashed, env: *anyopaqu
// External symbols provided by the Roc runtime object file
// Follows RocCall ABI: ops, ret_ptr, then argument pointers
extern fn roc__main_for_host(ops: *builtins.host_abi.RocOps, ret_ptr: *anyopaque, arg_ptr: ?*anyopaque) callconv(.c) void;
extern fn roc__main(ops: *builtins.host_abi.RocOps, ret_ptr: *anyopaque, arg_ptr: ?*anyopaque) callconv(.c) void;
// OS-specific entry point handling
comptime {
@ -199,5 +199,5 @@ fn platform_main() !void {
// currently dereference both of these eagerly even though it won't use either,
// causing a segfault if you pass null. This should be changed! Dereferencing
// garbage memory is obviously pointless, and there's no reason we should do it.
roc__main_for_host(&roc_ops, @as(*anyopaque, @ptrCast(&ret)), @as(*anyopaque, @ptrCast(&args)));
roc__main(&roc_ops, @as(*anyopaque, @ptrCast(&ret)), @as(*anyopaque, @ptrCast(&args)));
}

View file

@ -2,7 +2,7 @@ platform ""
requires {} { main! : () => {} }
exposes [Stdout, Stderr, Stdin]
packages {}
provides { main_for_host! }
provides { main_for_host!: "main" }
import Stdout
import Stderr

View file

@ -1,7 +1,7 @@
app [addInts, multiplyInts] { pf: platform "./platform/main.roc" }
app [add_ints, multiply_ints] { pf: platform "./platform/main.roc" }
addInts : I64, I64 -> I64
addInts = |a, b| a + b
add_ints : I64, I64 -> I64
add_ints = |a, b| a + b
multiplyInts : I64, I64 -> I64
multiplyInts = |a, b| a * b
multiply_ints : I64, I64 -> I64
multiply_ints = |a, b| a * b

View file

@ -61,8 +61,8 @@ fn rocCrashedFn(roc_crashed: *const builtins.host_abi.RocCrashed, env: *anyopaqu
// External symbols provided by the Roc runtime object file
// Follows RocCall ABI: ops, ret_ptr, then argument pointers
extern fn roc__addInts(ops: *builtins.host_abi.RocOps, ret_ptr: *anyopaque, arg_ptr: ?*anyopaque) callconv(.c) void;
extern fn roc__multiplyInts(ops: *builtins.host_abi.RocOps, ret_ptr: *anyopaque, arg_ptr: ?*anyopaque) callconv(.c) void;
extern fn roc__add_ints(ops: *builtins.host_abi.RocOps, ret_ptr: *anyopaque, arg_ptr: ?*anyopaque) callconv(.c) void;
extern fn roc__multiply_ints(ops: *builtins.host_abi.RocOps, ret_ptr: *anyopaque, arg_ptr: ?*anyopaque) callconv(.c) void;
// OS-specific entry point handling
comptime {
@ -122,11 +122,11 @@ fn platform_main() !void {
try stdout.print("Generated numbers: a = {}, b = {}\n", .{ a, b });
// Test first entrypoint: addInts (entry_idx = 0)
try stdout.print("\n=== Testing addInts (entry_idx = 0) ===\n", .{});
// Test first entrypoint: add_ints (entry_idx = 0)
try stdout.print("\n=== Testing add_ints (entry_idx = 0) ===\n", .{});
var add_result: i64 = undefined;
roc__addInts(&roc_ops, @as(*anyopaque, @ptrCast(&add_result)), @as(*anyopaque, @ptrCast(&args)));
roc__add_ints(&roc_ops, @as(*anyopaque, @ptrCast(&add_result)), @as(*anyopaque, @ptrCast(&args)));
const expected_add = a +% b; // Use wrapping addition to match Roc behavior
try stdout.print("Expected add result: {}\n", .{expected_add});
@ -134,27 +134,27 @@ fn platform_main() !void {
var success_count: u32 = 0;
if (add_result == expected_add) {
try stdout.print("\x1b[32mSUCCESS\x1b[0m: addInts results match!\n", .{});
try stdout.print("\x1b[32mSUCCESS\x1b[0m: add_ints results match!\n", .{});
success_count += 1;
} else {
try stdout.print("\x1b[31mFAIL\x1b[0m: addInts results differ!\n", .{});
try stdout.print("\x1b[31mFAIL\x1b[0m: add_ints results differ!\n", .{});
}
// Test second entrypoint: multiplyInts (entry_idx = 1)
try stdout.print("\n=== Testing multiplyInts (entry_idx = 1) ===\n", .{});
// Test second entrypoint: multiply_ints (entry_idx = 1)
try stdout.print("\n=== Testing multiply_ints (entry_idx = 1) ===\n", .{});
var multiply_result: i64 = undefined;
roc__multiplyInts(&roc_ops, @as(*anyopaque, @ptrCast(&multiply_result)), @as(*anyopaque, @ptrCast(&args)));
roc__multiply_ints(&roc_ops, @as(*anyopaque, @ptrCast(&multiply_result)), @as(*anyopaque, @ptrCast(&args)));
const expected_multiply = a *% b; // Use wrapping multiplication to match Roc behavior
try stdout.print("Expected multiply result: {}\n", .{expected_multiply});
try stdout.print("Roc computed multiply: {}\n", .{multiply_result});
if (multiply_result == expected_multiply) {
try stdout.print("\x1b[32mSUCCESS\x1b[0m: multiplyInts results match!\n", .{});
try stdout.print("\x1b[32mSUCCESS\x1b[0m: multiply_ints results match!\n", .{});
success_count += 1;
} else {
try stdout.print("\x1b[31mFAIL\x1b[0m: multiplyInts results differ!\n", .{});
try stdout.print("\x1b[31mFAIL\x1b[0m: multiply_ints results differ!\n", .{});
}
// Final summary

View file

@ -1,9 +1,11 @@
platform ""
requires {} { addInts : I64, I64 -> I64, multiplyInts : I64, I64 -> I64 }
requires {} { add_ints : I64, I64 -> I64, multiply_ints : I64, I64 -> I64 }
exposes []
packages {}
provides { addInts: "addInts", multiplyInts: "multiplyInts" }
provides { add_ints_for_host: "add_ints", multiply_ints_for_host: "multiply_ints" }
addInts : I64, I64 -> I64
add_ints_for_host : I64, I64 -> I64
add_ints_for_host = add_ints
multiplyInts : I64, I64 -> I64
multiply_ints_for_host : I64, I64 -> I64
multiply_ints_for_host = multiply_ints

View file

@ -0,0 +1,187 @@
# META
~~~ini
description=Arrow syntax with qualified functions and formatter dropping empty parens
type=snippet
~~~
# SOURCE
~~~roc
# Test qualified function calls with arrow syntax
test1 = "hello"->Str.is_empty
test2 = "hello"->Str.is_empty()
test3 = "hello"->Str.concat("bar")
# Test unqualified function calls
fn0 = |a| a
test4 = 10->fn0
test5 = 10->fn0()
# Test tag syntax
test6 = 42->Ok
test7 = 42->Ok()
~~~
# EXPECTED
NIL
# PROBLEMS
NIL
# TOKENS
~~~zig
LowerIdent,OpAssign,StringStart,StringPart,StringEnd,OpArrow,UpperIdent,NoSpaceDotLowerIdent,
LowerIdent,OpAssign,StringStart,StringPart,StringEnd,OpArrow,UpperIdent,NoSpaceDotLowerIdent,NoSpaceOpenRound,CloseRound,
LowerIdent,OpAssign,StringStart,StringPart,StringEnd,OpArrow,UpperIdent,NoSpaceDotLowerIdent,NoSpaceOpenRound,StringStart,StringPart,StringEnd,CloseRound,
LowerIdent,OpAssign,OpBar,LowerIdent,OpBar,LowerIdent,
LowerIdent,OpAssign,Int,OpArrow,LowerIdent,
LowerIdent,OpAssign,Int,OpArrow,LowerIdent,NoSpaceOpenRound,CloseRound,
LowerIdent,OpAssign,Int,OpArrow,UpperIdent,
LowerIdent,OpAssign,Int,OpArrow,UpperIdent,NoSpaceOpenRound,CloseRound,
EndOfFile,
~~~
# PARSE
~~~clojure
(file
(type-module)
(statements
(s-decl
(p-ident (raw "test1"))
(e-local-dispatch
(e-string
(e-string-part (raw "hello")))
(e-ident (raw "Str.is_empty"))))
(s-decl
(p-ident (raw "test2"))
(e-local-dispatch
(e-string
(e-string-part (raw "hello")))
(e-apply
(e-ident (raw "Str.is_empty")))))
(s-decl
(p-ident (raw "test3"))
(e-local-dispatch
(e-string
(e-string-part (raw "hello")))
(e-apply
(e-ident (raw "Str.concat"))
(e-string
(e-string-part (raw "bar"))))))
(s-decl
(p-ident (raw "fn0"))
(e-lambda
(args
(p-ident (raw "a")))
(e-ident (raw "a"))))
(s-decl
(p-ident (raw "test4"))
(e-local-dispatch
(e-int (raw "10"))
(e-ident (raw "fn0"))))
(s-decl
(p-ident (raw "test5"))
(e-local-dispatch
(e-int (raw "10"))
(e-apply
(e-ident (raw "fn0")))))
(s-decl
(p-ident (raw "test6"))
(e-local-dispatch
(e-int (raw "42"))
(e-tag (raw "Ok"))))
(s-decl
(p-ident (raw "test7"))
(e-local-dispatch
(e-int (raw "42"))
(e-apply
(e-tag (raw "Ok")))))))
~~~
# FORMATTED
~~~roc
# Test qualified function calls with arrow syntax
test1 = "hello"->Str.is_empty
test2 = "hello"->Str.is_empty
test3 = "hello"->Str.concat("bar")
# Test unqualified function calls
fn0 = |a| a
test4 = 10->fn0
test5 = 10->fn0
# Test tag syntax
test6 = 42->Ok
test7 = 42->Ok
~~~
# CANONICALIZE
~~~clojure
(can-ir
(d-let
(p-assign (ident "test1"))
(e-call
(e-lookup-external
(builtin))
(e-string
(e-literal (string "hello")))))
(d-let
(p-assign (ident "test2"))
(e-call
(e-lookup-external
(builtin))
(e-string
(e-literal (string "hello")))))
(d-let
(p-assign (ident "test3"))
(e-call
(e-lookup-external
(builtin))
(e-string
(e-literal (string "hello")))
(e-string
(e-literal (string "bar")))))
(d-let
(p-assign (ident "fn0"))
(e-lambda
(args
(p-assign (ident "a")))
(e-lookup-local
(p-assign (ident "a")))))
(d-let
(p-assign (ident "test4"))
(e-call
(e-lookup-local
(p-assign (ident "fn0")))
(e-num (value "10"))))
(d-let
(p-assign (ident "test5"))
(e-call
(e-lookup-local
(p-assign (ident "fn0")))
(e-num (value "10"))))
(d-let
(p-assign (ident "test6"))
(e-tag (name "Ok")
(args
(e-num (value "42")))))
(d-let
(p-assign (ident "test7"))
(e-tag (name "Ok")
(args
(e-num (value "42"))))))
~~~
# TYPES
~~~clojure
(inferred-types
(defs
(patt (type "Bool"))
(patt (type "Bool"))
(patt (type "Str"))
(patt (type "b -> b"))
(patt (type "b where [b.from_numeral : Numeral -> Try(b, [InvalidNumeral(Str)])]"))
(patt (type "b where [b.from_numeral : Numeral -> Try(b, [InvalidNumeral(Str)])]"))
(patt (type "[Ok(b)]_others where [b.from_numeral : Numeral -> Try(b, [InvalidNumeral(Str)])]"))
(patt (type "[Ok(b)]_others where [b.from_numeral : Numeral -> Try(b, [InvalidNumeral(Str)])]")))
(expressions
(expr (type "Bool"))
(expr (type "Bool"))
(expr (type "Str"))
(expr (type "b -> b"))
(expr (type "b where [b.from_numeral : Numeral -> Try(b, [InvalidNumeral(Str)])]"))
(expr (type "b where [b.from_numeral : Numeral -> Try(b, [InvalidNumeral(Str)])]"))
(expr (type "[Ok(b)]_others where [b.from_numeral : Numeral -> Try(b, [InvalidNumeral(Str)])]"))
(expr (type "[Ok(b)]_others where [b.from_numeral : Numeral -> Try(b, [InvalidNumeral(Str)])]"))))
~~~

View file

@ -28,9 +28,31 @@ platform "pf"
}
~~~
# EXPECTED
EXPOSED BUT NOT DEFINED - platform.md:19:3:19:25
EXPOSED BUT NOT DEFINED - platform.md:20:3:20:25
EXPOSED BUT NOT DEFINED - platform.md:10:3:10:5
EXPOSED BUT NOT DEFINED - platform.md:11:3:11:5
# PROBLEMS
**EXPOSED BUT NOT DEFINED**
The module header says that `pr1` is exposed, but it is not defined anywhere in this module.
**platform.md:19:3:19:25:**
```roc
pr1: "not implemented",
```
^^^^^^^^^^^^^^^^^^^^^^
You can fix this by either defining `pr1` in this module, or by removing it from the list of exposed values.
**EXPOSED BUT NOT DEFINED**
The module header says that `pr2` is exposed, but it is not defined anywhere in this module.
**platform.md:20:3:20:25:**
```roc
pr2: "not implemented",
```
^^^^^^^^^^^^^^^^^^^^^^
You can fix this by either defining `pr2` in this module, or by removing it from the list of exposed values.
**EXPOSED BUT NOT DEFINED**
The module header says that `E1` is exposed, but it is not defined anywhere in this module.

View file

@ -28,9 +28,31 @@ platform "pf"
}
~~~
# EXPECTED
EXPOSED BUT NOT DEFINED - platform.md:19:3:19:25
EXPOSED BUT NOT DEFINED - platform.md:20:3:20:25
EXPOSED BUT NOT DEFINED - platform.md:10:3:10:5
EXPOSED BUT NOT DEFINED - platform.md:11:3:11:5
# PROBLEMS
**EXPOSED BUT NOT DEFINED**
The module header says that `pr1` is exposed, but it is not defined anywhere in this module.
**platform.md:19:3:19:25:**
```roc
pr1: "not implemented",
```
^^^^^^^^^^^^^^^^^^^^^^
You can fix this by either defining `pr1` in this module, or by removing it from the list of exposed values.
**EXPOSED BUT NOT DEFINED**
The module header says that `pr2` is exposed, but it is not defined anywhere in this module.
**platform.md:20:3:20:25:**
```roc
pr2: "not implemented",
```
^^^^^^^^^^^^^^^^^^^^^^
You can fix this by either defining `pr2` in this module, or by removing it from the list of exposed values.
**EXPOSED BUT NOT DEFINED**
The module header says that `E1` is exposed, but it is not defined anywhere in this module.

View file

@ -13,9 +13,31 @@ platform "pf"
provides { pr1: "not implemented", pr2: "not implemented" }
~~~
# EXPECTED
EXPOSED BUT NOT DEFINED - platform.md:6:13:6:35
EXPOSED BUT NOT DEFINED - platform.md:6:37:6:59
EXPOSED BUT NOT DEFINED - platform.md:3:11:3:13
EXPOSED BUT NOT DEFINED - platform.md:3:15:3:17
# PROBLEMS
**EXPOSED BUT NOT DEFINED**
The module header says that `pr1` is exposed, but it is not defined anywhere in this module.
**platform.md:6:13:6:35:**
```roc
provides { pr1: "not implemented", pr2: "not implemented" }
```
^^^^^^^^^^^^^^^^^^^^^^
You can fix this by either defining `pr1` in this module, or by removing it from the list of exposed values.
**EXPOSED BUT NOT DEFINED**
The module header says that `pr2` is exposed, but it is not defined anywhere in this module.
**platform.md:6:37:6:59:**
```roc
provides { pr1: "not implemented", pr2: "not implemented" }
```
^^^^^^^^^^^^^^^^^^^^^^
You can fix this by either defining `pr2` in this module, or by removing it from the list of exposed values.
**EXPOSED BUT NOT DEFINED**
The module header says that `E1` is exposed, but it is not defined anywhere in this module.

View file

@ -13,9 +13,31 @@ platform "pf"
provides { pr1: "not implemented", pr2: "not implemented", }
~~~
# EXPECTED
EXPOSED BUT NOT DEFINED - platform.md:6:13:6:35
EXPOSED BUT NOT DEFINED - platform.md:6:37:6:59
EXPOSED BUT NOT DEFINED - platform.md:3:11:3:13
EXPOSED BUT NOT DEFINED - platform.md:3:15:3:17
# PROBLEMS
**EXPOSED BUT NOT DEFINED**
The module header says that `pr1` is exposed, but it is not defined anywhere in this module.
**platform.md:6:13:6:35:**
```roc
provides { pr1: "not implemented", pr2: "not implemented", }
```
^^^^^^^^^^^^^^^^^^^^^^
You can fix this by either defining `pr1` in this module, or by removing it from the list of exposed values.
**EXPOSED BUT NOT DEFINED**
The module header says that `pr2` is exposed, but it is not defined anywhere in this module.
**platform.md:6:37:6:59:**
```roc
provides { pr1: "not implemented", pr2: "not implemented", }
```
^^^^^^^^^^^^^^^^^^^^^^
You can fix this by either defining `pr2` in this module, or by removing it from the list of exposed values.
**EXPOSED BUT NOT DEFINED**
The module header says that `E1` is exposed, but it is not defined anywhere in this module.

View file

@ -243,7 +243,7 @@ UNUSED VARIABLE - fuzz_crash_023.md:1:1:1:1
NOT IMPLEMENTED - :0:0:0:0
UNUSED VARIABLE - fuzz_crash_023.md:1:1:1:1
NOT IMPLEMENTED - :0:0:0:0
NOT IMPLEMENTED - :0:0:0:0
UNDEFINED VARIABLE - fuzz_crash_023.md:121:37:121:40
UNUSED VARIABLE - fuzz_crash_023.md:121:21:121:27
UNUSED VARIABLE - fuzz_crash_023.md:127:4:128:9
NOT IMPLEMENTED - :0:0:0:0
@ -590,10 +590,16 @@ This feature is not yet implemented: alternatives pattern outside match expressi
This error doesn't have a proper diagnostic report yet. Let us know if you want to help improve Roc's error messages!
**NOT IMPLEMENTED**
This feature is not yet implemented: canonicalize local_dispatch expression
**UNDEFINED VARIABLE**
Nothing is named `add` in this scope.
Is there an `import` or `exposing` missing up-top?
**fuzz_crash_023.md:121:37:121:40:**
```roc
{ foo: 1, bar: 2, ..rest } => 12->add(34)
```
^^^
This error doesn't have a proper diagnostic report yet. Let us know if you want to help improve Roc's error messages!
**UNUSED VARIABLE**
Variable `rest` is not used anywhere in your code.
@ -2248,7 +2254,10 @@ expect {
(required
(p-assign (ident "rest"))))))))
(value
(e-runtime-error (tag "not_implemented"))))
(e-call
(e-runtime-error (tag "ident_not_in_scope"))
(e-num (value "12"))
(e-num (value "34")))))
(branch
(patterns
(pattern (degenerate false)

View file

@ -199,7 +199,7 @@ NOT IMPLEMENTED - :0:0:0:0
UNUSED VARIABLE - fuzz_crash_027.md:1:1:1:1
UNUSED VARIABLE - fuzz_crash_027.md:76:1:76:4
NOT IMPLEMENTED - :0:0:0:0
NOT IMPLEMENTED - :0:0:0:0
UNDEFINED VARIABLE - fuzz_crash_027.md:82:37:82:40
UNUSED VARIABLE - fuzz_crash_027.md:82:21:82:27
NOT IMPLEMENTED - :0:0:0:0
NOT IMPLEMENTED - :0:0:0:0
@ -599,10 +599,16 @@ This feature is not yet implemented: alternatives pattern outside match expressi
This error doesn't have a proper diagnostic report yet. Let us know if you want to help improve Roc's error messages!
**NOT IMPLEMENTED**
This feature is not yet implemented: canonicalize local_dispatch expression
**UNDEFINED VARIABLE**
Nothing is named `add` in this scope.
Is there an `import` or `exposing` missing up-top?
**fuzz_crash_027.md:82:37:82:40:**
```roc
{ foo: 1, bar: 2, ..rest } => 12->add(34)
```
^^^
This error doesn't have a proper diagnostic report yet. Let us know if you want to help improve Roc's error messages!
**UNUSED VARIABLE**
Variable `rest` is not used anywhere in your code.
@ -2019,7 +2025,10 @@ expect {
(required
(p-assign (ident "rest"))))))))
(value
(e-runtime-error (tag "not_implemented"))))
(e-call
(e-runtime-error (tag "ident_not_in_scope"))
(e-num (value "12"))
(e-num (value "34")))))
(branch
(patterns
(pattern (degenerate false)

View file

@ -14,9 +14,18 @@ platform ""
multiplyInts : I64, I64 -> I64
~~~
# EXPECTED
NIL
EXPOSED BUT NOT DEFINED - platform_int.md:5:16:5:44
# PROBLEMS
NIL
**EXPOSED BUT NOT DEFINED**
The module header says that `multiplyInts` is exposed, but it is not defined anywhere in this module.
**platform_int.md:5:16:5:44:**
```roc
provides { multiplyInts: "multiplyInts" }
```
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
You can fix this by either defining `multiplyInts` in this module, or by removing it from the list of exposed values.
# TOKENS
~~~zig
KwPlatform,StringStart,StringPart,StringEnd,

View file

@ -14,9 +14,18 @@ platform ""
processString : Str -> Str
~~~
# EXPECTED
NIL
EXPOSED BUT NOT DEFINED - platform_str.md:5:16:5:46
# PROBLEMS
NIL
**EXPOSED BUT NOT DEFINED**
The module header says that `processString` is exposed, but it is not defined anywhere in this module.
**platform_str.md:5:16:5:46:**
```roc
provides { processString: "processString" }
```
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
You can fix this by either defining `processString` in this module, or by removing it from the list of exposed values.
# TOKENS
~~~zig
KwPlatform,StringStart,StringPart,StringEnd,

View file

@ -0,0 +1,34 @@
# META
~~~ini
description=Arrow syntax desugaring (arg->fn to fn(arg))
type=repl
~~~
# SOURCE
~~~roc
» fn0 = |a| a + 1
» fn1 = |a, b| a + b
» fn2 = |a, b, c| a + b + c
» fn3 = |a, b, c, d| a + b + c + d
» 10->fn0
» 10->fn1(20)
» 10->fn2(20, 30)
» 10->fn3(20, 30, 40)
~~~
# OUTPUT
assigned `fn0`
---
assigned `fn1`
---
assigned `fn2`
---
assigned `fn3`
---
11
---
30
---
60
---
100
# PROBLEMS
NIL

View file

@ -0,0 +1,13 @@
# META
~~~ini
description=Basic List.concat test
type=repl
~~~
# SOURCE
~~~roc
» List.len(List.concat([1, 2], [3, 4]))
~~~
# OUTPUT
4
# PROBLEMS
NIL

View file

@ -0,0 +1,13 @@
# META
~~~ini
description=List.concat with empty list
type=repl
~~~
# SOURCE
~~~roc
» List.len(List.concat([], [1, 2, 3]))
~~~
# OUTPUT
3
# PROBLEMS
NIL

View file

@ -0,0 +1,13 @@
# META
~~~ini
description=List.concat with empty list as second argument
type=repl
~~~
# SOURCE
~~~roc
» List.len(List.concat([1, 2, 3], []))
~~~
# OUTPUT
3
# PROBLEMS
NIL

View file

@ -0,0 +1,13 @@
# META
~~~ini
description=List.drop_if filters elements where predicate returns false
type=repl
~~~
# SOURCE
~~~roc
» List.drop_if([1, 2, 3, 4, 5], |x| x > 2)
~~~
# OUTPUT
[1, 2]
# PROBLEMS
NIL

View file

@ -0,0 +1,13 @@
# META
~~~ini
description=List.fold with concat - tests nested function calls
type=repl
~~~
# SOURCE
~~~roc
» List.len(List.fold([1, 2, 3], [], |acc, x| List.concat(acc, [x])))
~~~
# OUTPUT
3
# PROBLEMS
NIL

View file

@ -0,0 +1,13 @@
# META
~~~ini
description=List.len on List.fold result - tests nested function calls
type=repl
~~~
# SOURCE
~~~roc
» List.len(List.fold([1, 2, 3, 4, 5], [0], |acc, _| acc))
~~~
# OUTPUT
1
# PROBLEMS
NIL

View file

@ -0,0 +1,13 @@
# META
~~~ini
description=List.keep_if filters elements where predicate returns true
type=repl
~~~
# SOURCE
~~~roc
» List.keep_if([1, 2, 3, 4, 5], |x| x > 2)
~~~
# OUTPUT
[3, 4, 5]
# PROBLEMS
NIL

View file

@ -0,0 +1,13 @@
# META
~~~ini
description=List.keep_if on empty list returns empty list
type=repl
~~~
# SOURCE
~~~roc
» List.keep_if([1, 2, 3], |_| Bool.False)
~~~
# OUTPUT
[]
# PROBLEMS
NIL

View file

@ -0,0 +1,13 @@
# META
~~~ini
description=List.keep_if that keeps no elements returns empty list
type=repl
~~~
# SOURCE
~~~roc
» List.keep_if([1, 2, 3], |x| x > 10)
~~~
# OUTPUT
[]
# PROBLEMS
NIL

View file

@ -17,6 +17,6 @@ type=repl
---
"Hello, World!"
---
<list_of_zst>
[]
# PROBLEMS
NIL

View file

@ -238,7 +238,7 @@ UNUSED VARIABLE - syntax_grab_bag.md:1:1:1:1
NOT IMPLEMENTED - :0:0:0:0
UNUSED VARIABLE - syntax_grab_bag.md:1:1:1:1
NOT IMPLEMENTED - :0:0:0:0
NOT IMPLEMENTED - :0:0:0:0
UNDEFINED VARIABLE - syntax_grab_bag.md:121:37:121:40
UNUSED VARIABLE - syntax_grab_bag.md:121:21:121:27
UNUSED VARIABLE - syntax_grab_bag.md:127:4:128:9
NOT IMPLEMENTED - :0:0:0:0
@ -525,10 +525,16 @@ This feature is not yet implemented: alternatives pattern outside match expressi
This error doesn't have a proper diagnostic report yet. Let us know if you want to help improve Roc's error messages!
**NOT IMPLEMENTED**
This feature is not yet implemented: canonicalize local_dispatch expression
**UNDEFINED VARIABLE**
Nothing is named `add` in this scope.
Is there an `import` or `exposing` missing up-top?
**syntax_grab_bag.md:121:37:121:40:**
```roc
{ foo: 1, bar: 2, ..rest } => 12->add(34)
```
^^^
This error doesn't have a proper diagnostic report yet. Let us know if you want to help improve Roc's error messages!
**UNUSED VARIABLE**
Variable `rest` is not used anywhere in your code.
@ -2133,7 +2139,10 @@ expect {
(required
(p-assign (ident "rest"))))))))
(value
(e-runtime-error (tag "not_implemented"))))
(e-call
(e-runtime-error (tag "ident_not_in_scope"))
(e-num (value "12"))
(e-num (value "34")))))
(branch
(patterns
(pattern (degenerate false)

View file

@ -1,5 +1,5 @@
app [processString] { pf: platform "./platform/main.roc" }
app [process_string] { pf: platform "./platform/main.roc" }
processString : Str -> Str
processString = |input|
process_string : Str -> Str
process_string = |input|
"Got the following from the host: ${input}"

View file

@ -81,7 +81,7 @@ fn rocCrashedFn(roc_crashed: *const RocCrashed, env: *anyopaque) callconv(.c) no
// External symbol provided by the Roc runtime object file
// Follows RocCall ABI: ops, ret_ptr, then argument pointers
extern fn roc__processString(ops: *RocOps, ret_ptr: *anyopaque, arg_ptr: ?*anyopaque) callconv(.c) void;
extern fn roc__process_string(ops: *RocOps, ret_ptr: *anyopaque, arg_ptr: ?*anyopaque) callconv(.c) void;
// OS-specific entry point handling
comptime {
@ -143,7 +143,7 @@ fn platform_main() !void {
// Call the Roc entrypoint - pass argument pointer for functions, null for values
var roc_str: RocStr = undefined;
roc__processString(&roc_ops, @as(*anyopaque, @ptrCast(&roc_str)), @as(*anyopaque, @ptrCast(&args)));
roc__process_string(&roc_ops, @as(*anyopaque, @ptrCast(&roc_str)), @as(*anyopaque, @ptrCast(&args)));
defer roc_str.decref(&roc_ops);
// Get the string as a slice and print it

View file

@ -1,7 +1,8 @@
platform ""
requires {} { processString : Str -> Str }
requires {} { process_string : Str -> Str }
exposes []
packages {}
provides { processString: "processString" }
provides { process_string_for_host: "process_string" }
processString : Str -> Str
process_string_for_host : Str -> Str
process_string_for_host = process_string