mirror of
https://github.com/roc-lang/roc.git
synced 2025-12-23 08:48:03 +00:00
Merging in main
This commit is contained in:
commit
24ec833d3b
75 changed files with 2356 additions and 500 deletions
2
.github/workflows/ci_zig.yml
vendored
2
.github/workflows/ci_zig.yml
vendored
|
|
@ -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
|
||||
31
build.zig
31
build.zig
|
|
@ -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(©_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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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| {
|
||||
|
|
|
|||
|
|
@ -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), ©_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), ©_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), ©_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), ©_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), ©_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), ©_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), ©_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), ©_fallback);
|
||||
}
|
||||
|
||||
try std.testing.expectEqual(@as(usize, 20), list.len());
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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| {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
\\
|
||||
;
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
28
src/lsp/capabilities.zig
Normal 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
108
src/lsp/document_store.zig
Normal 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;
|
||||
}
|
||||
};
|
||||
95
src/lsp/handlers/did_change.zig
Normal file
95
src/lsp/handlers/did_change.zig
Normal 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,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
43
src/lsp/handlers/did_open.zig
Normal file
43
src/lsp/handlers/did_open.zig
Normal 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) });
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
31
src/lsp/test/document_store_test.zig
Normal file
31
src/lsp/test/document_store_test.zig
Normal 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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 => {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
187
test/snapshots/arrow_qualified_functions.md
Normal file
187
test/snapshots/arrow_qualified_functions.md
Normal 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)])]"))))
|
||||
~~~
|
||||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
34
test/snapshots/repl/arrow_syntax_desugaring.md
Normal file
34
test/snapshots/repl/arrow_syntax_desugaring.md
Normal 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
|
||||
13
test/snapshots/repl/list_concat_basic.md
Normal file
13
test/snapshots/repl/list_concat_basic.md
Normal 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
|
||||
13
test/snapshots/repl/list_concat_empty.md
Normal file
13
test/snapshots/repl/list_concat_empty.md
Normal 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
|
||||
13
test/snapshots/repl/list_concat_empty_second.md
Normal file
13
test/snapshots/repl/list_concat_empty_second.md
Normal 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
|
||||
13
test/snapshots/repl/list_drop_if.md
Normal file
13
test/snapshots/repl/list_drop_if.md
Normal 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
|
||||
13
test/snapshots/repl/list_fold_concat.md
Normal file
13
test/snapshots/repl/list_fold_concat.md
Normal 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
|
||||
13
test/snapshots/repl/list_fold_sum_nested.md
Normal file
13
test/snapshots/repl/list_fold_sum_nested.md
Normal 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
|
||||
13
test/snapshots/repl/list_keep_if.md
Normal file
13
test/snapshots/repl/list_keep_if.md
Normal 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
|
||||
13
test/snapshots/repl/list_keep_if_empty.md
Normal file
13
test/snapshots/repl/list_keep_if_empty.md
Normal 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
|
||||
13
test/snapshots/repl/list_keep_if_none.md
Normal file
13
test/snapshots/repl/list_keep_if_none.md
Normal 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
|
||||
|
|
@ -17,6 +17,6 @@ type=repl
|
|||
---
|
||||
"Hello, World!"
|
||||
---
|
||||
<list_of_zst>
|
||||
[]
|
||||
# PROBLEMS
|
||||
NIL
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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}"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue