From cea6d56329a52c941f752055b48c785655a17fdd Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Tue, 11 Nov 2025 21:12:55 +0100 Subject: [PATCH] WIP Fix unification custom type + actually run tests (#8376) * initial check wiring script * use wiring script + fixes * add assertNoErrors * test fixes * fix unification for types --- ci/check_test_wiring.zig | 2 - src/check/Check.zig | 1 + src/check/mod.zig | 2 + src/check/test/unify_test.zig | 1 + src/check/unify.zig | 461 ++++++++++++++++++++++++---------- src/eval/interpreter.zig | 22 ++ 6 files changed, 351 insertions(+), 138 deletions(-) diff --git a/ci/check_test_wiring.zig b/ci/check_test_wiring.zig index 1abd306b19..acfe9fb2d1 100644 --- a/ci/check_test_wiring.zig +++ b/ci/check_test_wiring.zig @@ -15,8 +15,6 @@ const test_exclusions = [_][]const u8{ const test_file_exclusions = [_][]const u8{ // TODO: This test got out of sync and is not straightforward to fix "src/eval/test/low_level_interp_test.zig", - // TODO: remove this, fix is in another PR - "src/check/test/custom_num_type_test.zig", }; const TermColor = struct { diff --git a/src/check/Check.zig b/src/check/Check.zig index f1f3f2d3f4..79647b0365 100644 --- a/src/check/Check.zig +++ b/src/check/Check.zig @@ -337,6 +337,7 @@ fn unifyWithCtx(self: *Self, a: Var, b: Var, env: *Env, ctx: unifier.Conf.Ctx) s &self.snapshots, &self.unify_scratch, &self.occurs_scratch, + unifier.ModuleEnvLookup{ .auto_imported = self.module_envs }, a, b, unifier.Conf{ diff --git a/src/check/mod.zig b/src/check/mod.zig index 6f62c93860..367859ea58 100644 --- a/src/check/mod.zig +++ b/src/check/mod.zig @@ -40,5 +40,7 @@ test "check tests" { std.testing.refAllDecls(@import("test/let_polymorphism_integration_test.zig")); std.testing.refAllDecls(@import("test/num_type_inference_test.zig")); std.testing.refAllDecls(@import("test/num_type_requirements_test.zig")); + std.testing.refAllDecls(@import("test/custom_num_type_test.zig")); std.testing.refAllDecls(@import("test/builtin_scope_test.zig")); + std.testing.refAllDecls(@import("test/custom_num_type_test.zig")); } diff --git a/src/check/test/unify_test.zig b/src/check/test/unify_test.zig index 82618b1ddb..2ba15386cb 100644 --- a/src/check/test/unify_test.zig +++ b/src/check/test/unify_test.zig @@ -111,6 +111,7 @@ const TestEnv = struct { &self.snapshots, &self.scratch, &self.occurs_scratch, + unify_mod.ModuleEnvLookup{}, a, b, ); diff --git a/src/check/unify.zig b/src/check/unify.zig index 29e9149a97..021ff407e8 100644 --- a/src/check/unify.zig +++ b/src/check/unify.zig @@ -46,6 +46,7 @@ const tracy = @import("tracy"); const collections = @import("collections"); const types_mod = @import("types"); const can = @import("can"); +const copy_import = @import("copy_import.zig"); const Check = @import("check").Check; const problem_mod = @import("problem.zig"); @@ -53,6 +54,8 @@ const occurs = @import("occurs.zig"); const snapshot_mod = @import("snapshot.zig"); const ModuleEnv = can.ModuleEnv; +const AutoImportedType = can.Can.AutoImportedType; +const CIR = can.CIR; const Region = base.Region; const Ident = base.Ident; @@ -133,6 +136,7 @@ pub fn unify( snapshots: *snapshot_mod.Store, unify_scratch: *Scratch, occurs_scratch: *occurs.Scratch, + module_lookup: ModuleEnvLookup, /// The "expected" variable a: Var, /// The "actual" variable @@ -145,6 +149,7 @@ pub fn unify( snapshots, unify_scratch, occurs_scratch, + module_lookup, a, b, Conf{ .ctx = .anon, .constraint_origin_var = null }, @@ -160,6 +165,29 @@ pub const Conf = struct { pub const Ctx = enum { anon, anno }; }; +/// Provides access to module environments needed for cross-module method lookups +pub const ModuleEnvLookup = struct { + /// Auto-imported modules available during type checking (e.g. Bool, Result, Builtin) + auto_imported: ?*const std.AutoHashMap(Ident.Idx, AutoImportedType) = null, + /// Optional interpreter callback for resolving module envs at runtime + interpreter_lookup_ctx: ?*const anyopaque = null, + interpreter_lookup_fn: ?*const fn (?*const anyopaque, Ident.Idx) ?*const ModuleEnv = null, + + pub fn get(self: ModuleEnvLookup, module_ident: Ident.Idx) ?*const ModuleEnv { + if (self.auto_imported) |map| { + if (map.get(module_ident)) |entry| { + return entry.env; + } + } + if (self.interpreter_lookup_fn) |getter| { + if (getter(self.interpreter_lookup_ctx, module_ident)) |env| { + return env; + } + } + return null; + } +}; + /// Unify two type variables /// /// This function @@ -175,6 +203,7 @@ pub fn unifyWithConf( snapshots: *snapshot_mod.Store, unify_scratch: *Scratch, occurs_scratch: *occurs.Scratch, + module_lookup: ModuleEnvLookup, /// The "expected" variable a: Var, /// The "actual" variable @@ -188,7 +217,7 @@ pub fn unifyWithConf( unify_scratch.reset(); // Unify - var unifier = Unifier.init(module_env, types, unify_scratch, occurs_scratch); + var unifier = Unifier.init(module_env, types, unify_scratch, occurs_scratch, module_lookup); unifier.unifyGuarded(a, b) catch |err| { const problem: Problem = blk: { switch (err) { @@ -366,6 +395,7 @@ const Unifier = struct { types_store: *types_mod.Store, scratch: *Scratch, occurs_scratch: *occurs.Scratch, + module_lookup: ModuleEnvLookup, depth: u8, skip_depth_check: bool, @@ -375,12 +405,14 @@ const Unifier = struct { types_store: *types_mod.Store, scratch: *Scratch, occurs_scratch: *occurs.Scratch, + module_lookup: ModuleEnvLookup, ) Unifier { return .{ .module_env = module_env, .types_store = types_store, .scratch = scratch, .occurs_scratch = occurs_scratch, + .module_lookup = module_lookup, .depth = 0, .skip_depth_check = false, }; @@ -817,9 +849,16 @@ const Unifier = struct { } }, .frac_unbound => { - // Decimal literal - unify with from_dec_digits: (List(U8), List(U8)) -> Try(Self, [OutOfRange]) + // Decimal literal - unify with from_dec_digits if (try self.nominalTypeHasFromDecDigits(b_nominal)) { - // Unification succeeded - the nominal type can accept decimal literals + self.merge(vars, vars.b.desc.content); + } else { + return error.TypeMismatch; + } + }, + .num_poly => |poly_var| { + const resolved_poly = self.resolvePolyNum(poly_var, .inside_num); + if (try self.nominalSupportsResolvedNum(resolved_poly, b_nominal)) { self.merge(vars, vars.b.desc.content); } else { return error.TypeMismatch; @@ -894,6 +933,14 @@ const Unifier = struct { return error.TypeMismatch; } }, + .num_poly => |poly_var| { + const resolved_poly = self.resolvePolyNum(poly_var, .inside_num); + if (try self.nominalSupportsResolvedNum(resolved_poly, a_type)) { + self.merge(vars, vars.a.desc.content); + } else { + return error.TypeMismatch; + } + }, else => { // Concrete numeric types don't unify with nominal types return error.TypeMismatch; @@ -1140,80 +1187,44 @@ const Unifier = struct { self: *Self, nominal_type: NominalType, ) Error!bool { - // Get the backing var (record extension) of the nominal type - const backing_var = self.types_store.getNominalBackingVar(nominal_type); - const backing_resolved = self.types_store.resolveVar(backing_var); + const method_var = try self.getNominalMethodVar(nominal_type, self.module_env.from_int_digits_ident) orelse return false; + const resolved = self.types_store.resolveVar(method_var); - // Check if the backing is a record - const backing_record = backing_resolved.desc.content.unwrapRecord() orelse return false; + const func = switch (resolved.desc.content) { + .structure => |structure| switch (structure) { + .fn_pure => structure.fn_pure, + .fn_effectful => structure.fn_effectful, + .fn_unbound => structure.fn_unbound, + else => return false, + }, + else => return false, + }; - // Use the pre-interned identifier from ModuleEnv for fast integer comparison - const from_int_digits_ident = self.module_env.from_int_digits_ident; + const ret_desc = self.types_store.resolveVar(func.ret); + const ret_nominal = switch (ret_desc.desc.content) { + .structure => |structure| switch (structure) { + .nominal_type => structure.nominal_type, + else => return false, + }, + else => return false, + }; + if (!self.isBuiltinTryNominal(ret_nominal)) return false; + const ret_args = self.types_store.sliceVars(ret_nominal.vars.nonempty); + if (ret_args.len < 3) return false; - // Look for the from_int_digits field using fast integer comparison - const fields_slice = self.types_store.getRecordFieldsSlice(backing_record.fields); - const field_names = fields_slice.items(.name); - const field_vars = fields_slice.items(.var_); + const args_slice = self.types_store.sliceVars(func.args); + if (args_slice.len != 1) return false; - for (field_names, 0..) |name_idx, i| { - if (name_idx == from_int_digits_ident) { - // Found the method - now check it's a function and unify with expected signature - const field_var = field_vars[i]; - const field_resolved = self.types_store.resolveVar(field_var); + const list_u8_var = try self.createListU8Var(); + self.unifyGuarded(args_slice[0], list_u8_var) catch return false; - // Check that it's a function - switch (field_resolved.desc.content) { - .structure => |structure| { - switch (structure) { - .fn_pure, .fn_effectful, .fn_unbound => |func| { - // Check it takes exactly 1 argument - const args_slice = self.types_store.sliceVars(func.args); - if (args_slice.len != 1) return false; + const self_ret_var = try self.createNominalInstanceVar(nominal_type); + self.unifyGuarded(ret_args[1], self_ret_var) catch return false; - // Create List(U8) type and unify with the argument - const u8_var = self.types_store.register(.{ .content = .{ .structure = .{ .num = .{ .num_compact = .{ .int = .u8 } } } }, .rank = Rank.generalized, .mark = Mark.none }) catch return error.AllocatorError; - const list_u8_var = self.types_store.register(.{ .content = .{ .structure = .{ .list = u8_var } }, .rank = Rank.generalized, .mark = Mark.none }) catch return error.AllocatorError; + const try_error_var = try self.createOutOfRangeTagUnion(); + self.unifyGuarded(ret_args[2], try_error_var) catch return false; - // Attempt to unify the argument with List(U8) - self.unifyGuarded(args_slice[0], list_u8_var) catch return false; - - // For the return type, we need Try(Self, [OutOfRange]) - // Create a fresh var for Self (the nominal type) - const self_var = self.types_store.register(.{ .content = .{ .structure = .{ .nominal_type = nominal_type } }, .rank = Rank.generalized, .mark = Mark.none }) catch return error.AllocatorError; - - // Create [OutOfRange] tag union - const out_of_range_ident = self.module_env.common.insertIdent(self.module_env.gpa, base.Ident.for_text("OutOfRange")) catch return error.AllocatorError; - const empty_payload_range = self.types_store.appendTags(&[_]Tag{.{ .name = out_of_range_ident, .args = Var.SafeList.Range.empty() }}) catch return error.AllocatorError; - const empty_ext_var = self.types_store.register(.{ .content = .{ .structure = .empty_tag_union }, .rank = Rank.generalized, .mark = Mark.none }) catch return error.AllocatorError; - const error_tag_union_var = self.types_store.register(.{ .content = .{ .structure = .{ .tag_union = .{ .tags = empty_payload_range, .ext = empty_ext_var } } }, .rank = Rank.generalized, .mark = Mark.none }) catch return error.AllocatorError; - - // Create Try(Self, [OutOfRange]) - Try is a nominal type with 2 type params - const try_ident_text = self.module_env.common.insertIdent(self.module_env.gpa, base.Ident.for_text("Try")) catch return error.AllocatorError; - const try_type_ident = TypeIdent{ .ident_idx = try_ident_text }; - const builtin_module_ident = self.module_env.common.insertIdent(self.module_env.gpa, base.Ident.for_text("Builtin")) catch return error.AllocatorError; - const try_vars_range = self.types_store.appendVars(&[_]Var{ self_var, error_tag_union_var }) catch return error.AllocatorError; - const try_vars_nonempty = Var.SafeList.NonEmptyRange{ .nonempty = try_vars_range }; - const try_nominal = NominalType{ - .ident = try_type_ident, - .vars = try_vars_nonempty, - .origin_module = builtin_module_ident, - }; - const try_return_var = self.types_store.register(.{ .content = .{ .structure = .{ .nominal_type = try_nominal } }, .rank = Rank.generalized, .mark = Mark.none }) catch return error.AllocatorError; - - // Unify the return type with Try(Self, [OutOfRange]) - self.unifyGuarded(func.ret, try_return_var) catch return false; - - return true; - }, - else => return false, - } - }, - else => return false, - } - } - } - - return false; + return true; } /// Check if a nominal type has a from_dec_digits method by unifying its signature @@ -1223,85 +1234,263 @@ const Unifier = struct { self: *Self, nominal_type: NominalType, ) Error!bool { - // Get the backing var (record extension) of the nominal type - const backing_var = self.types_store.getNominalBackingVar(nominal_type); - const backing_resolved = self.types_store.resolveVar(backing_var); + const method_var = try self.getNominalMethodVar(nominal_type, self.module_env.from_dec_digits_ident) orelse return false; + const resolved = self.types_store.resolveVar(method_var); - // Check if the backing is a record - const backing_record = backing_resolved.desc.content.unwrapRecord() orelse return false; + const func = switch (resolved.desc.content) { + .structure => |structure| switch (structure) { + .fn_pure => structure.fn_pure, + .fn_effectful => structure.fn_effectful, + .fn_unbound => structure.fn_unbound, + else => return false, + }, + else => return false, + }; - // Use the pre-interned identifier from ModuleEnv for fast integer comparison - const from_dec_digits_ident = self.module_env.from_dec_digits_ident; + const ret_desc = self.types_store.resolveVar(func.ret); + const ret_nominal = switch (ret_desc.desc.content) { + .structure => |structure| switch (structure) { + .nominal_type => structure.nominal_type, + else => return false, + }, + else => return false, + }; + if (!self.isBuiltinTryNominal(ret_nominal)) return false; + const ret_args = self.types_store.sliceVars(ret_nominal.vars.nonempty); + if (ret_args.len < 3) return false; - // Look for the from_dec_digits field using fast integer comparison - const fields_slice = self.types_store.getRecordFieldsSlice(backing_record.fields); - const field_names = fields_slice.items(.name); - const field_vars = fields_slice.items(.var_); + const args_slice = self.types_store.sliceVars(func.args); + if (args_slice.len != 1) return false; - for (field_names, 0..) |name_idx, i| { - if (name_idx == from_dec_digits_ident) { - // Found the method - now check it's a function and unify with expected signature - const field_var = field_vars[i]; - const field_resolved = self.types_store.resolveVar(field_var); + const before_ident = self.module_env.common.findIdent("before_dot") orelse return false; + const after_ident = self.module_env.common.findIdent("after_dot") orelse return false; - // Check that it's a function - switch (field_resolved.desc.content) { - .structure => |structure| { - switch (structure) { - .fn_pure, .fn_effectful, .fn_unbound => |func| { - // Check it takes exactly 2 arguments - const args_slice = self.types_store.sliceVars(func.args); - if (args_slice.len != 2) return false; + const record_desc = self.types_store.resolveVar(args_slice[0]); + const record = switch (record_desc.desc.content) { + .structure => |structure| switch (structure) { + .record => structure.record, + else => return false, + }, + else => return false, + }; - // Create List(U8) type and unify with both arguments - const u8_var1 = self.types_store.register(.{ .content = .{ .structure = .{ .num = .{ .num_compact = .{ .int = .u8 } } } }, .rank = Rank.generalized, .mark = Mark.none }) catch return error.AllocatorError; - const list_u8_var1 = self.types_store.register(.{ .content = .{ .structure = .{ .list = u8_var1 } }, .rank = Rank.generalized, .mark = Mark.none }) catch return error.AllocatorError; - const u8_var2 = self.types_store.register(.{ .content = .{ .structure = .{ .num = .{ .num_compact = .{ .int = .u8 } } } }, .rank = Rank.generalized, .mark = Mark.none }) catch return error.AllocatorError; - const list_u8_var2 = self.types_store.register(.{ .content = .{ .structure = .{ .list = u8_var2 } }, .rank = Rank.generalized, .mark = Mark.none }) catch return error.AllocatorError; + if (record.fields.len() != 2) return false; + const fields_slice = self.types_store.getRecordFieldsSlice(record.fields); + const names = fields_slice.items(.name); + const vars = fields_slice.items(.var_); - // Attempt to unify both arguments with List(U8) - self.unifyGuarded(args_slice[0], list_u8_var1) catch return false; - self.unifyGuarded(args_slice[1], list_u8_var2) catch return false; - - // For the return type, we need Try(Self, [OutOfRange]) - // Create a fresh var for Self (the nominal type) - const self_var = self.types_store.register(.{ .content = .{ .structure = .{ .nominal_type = nominal_type } }, .rank = Rank.generalized, .mark = Mark.none }) catch return error.AllocatorError; - - // Create [OutOfRange] tag union - const out_of_range_ident = self.module_env.common.insertIdent(self.module_env.gpa, base.Ident.for_text("OutOfRange")) catch return error.AllocatorError; - const empty_payload_range = self.types_store.appendTags(&[_]Tag{.{ .name = out_of_range_ident, .args = Var.SafeList.Range.empty() }}) catch return error.AllocatorError; - const empty_ext_var = self.types_store.register(.{ .content = .{ .structure = .empty_tag_union }, .rank = Rank.generalized, .mark = Mark.none }) catch return error.AllocatorError; - const error_tag_union_var = self.types_store.register(.{ .content = .{ .structure = .{ .tag_union = .{ .tags = empty_payload_range, .ext = empty_ext_var } } }, .rank = Rank.generalized, .mark = Mark.none }) catch return error.AllocatorError; - - // Create Try(Self, [OutOfRange]) - Try is a nominal type with 2 type params - const try_ident_text = self.module_env.common.insertIdent(self.module_env.gpa, base.Ident.for_text("Try")) catch return error.AllocatorError; - const try_type_ident = TypeIdent{ .ident_idx = try_ident_text }; - const builtin_module_ident = self.module_env.common.insertIdent(self.module_env.gpa, base.Ident.for_text("Builtin")) catch return error.AllocatorError; - const try_vars_range = self.types_store.appendVars(&[_]Var{ self_var, error_tag_union_var }) catch return error.AllocatorError; - const try_vars_nonempty = Var.SafeList.NonEmptyRange{ .nonempty = try_vars_range }; - const try_nominal = NominalType{ - .ident = try_type_ident, - .vars = try_vars_nonempty, - .origin_module = builtin_module_ident, - }; - const try_return_var = self.types_store.register(.{ .content = .{ .structure = .{ .nominal_type = try_nominal } }, .rank = Rank.generalized, .mark = Mark.none }) catch return error.AllocatorError; - - // Unify the return type with Try(Self, [OutOfRange]) - self.unifyGuarded(func.ret, try_return_var) catch return false; - - return true; - }, - else => return false, - } - }, - else => return false, - } + var before_idx: ?usize = null; + var after_idx: ?usize = null; + for (names, 0..) |name, idx| { + if (name == before_ident) { + before_idx = idx; + } else if (name == after_ident) { + after_idx = idx; } } + if (before_idx == null or after_idx == null) return false; + + const list_u8_first = try self.createListU8Var(); + const list_u8_second = try self.createListU8Var(); + + self.unifyGuarded(vars[before_idx.?], list_u8_first) catch return false; + self.unifyGuarded(vars[after_idx.?], list_u8_second) catch return false; + + const self_ret_var = try self.createNominalInstanceVar(nominal_type); + self.unifyGuarded(ret_args[1], self_ret_var) catch return false; + + const try_error_var = try self.createOutOfRangeTagUnion(); + self.unifyGuarded(ret_args[2], try_error_var) catch return false; + + return true; + } + + fn nominalSupportsResolvedNum(self: *Self, resolved: ResolvedNum, nominal_type: NominalType) Error!bool { + return switch (resolved) { + .frac_unbound, .frac_resolved, .frac_flex, .frac_rigid => self.nominalTypeHasFromDecDigits(nominal_type), + .int_unbound, .int_resolved, .int_flex, .int_rigid => self.nominalTypeHasFromIntDigits(nominal_type), + else => false, + }; + } + + fn getNominalMethodVar( + self: *Self, + nominal_type: NominalType, + method_ident: Ident.Idx, + ) Error!?Var { + const origin_env = if (nominal_type.origin_module == self.module_env.module_name_idx) + self.module_env + else + self.module_lookup.get(nominal_type.origin_module) orelse return null; + + const method_name = self.module_env.common.getIdent(method_ident); + const type_name = self.module_env.common.getIdent(nominal_type.ident.ident_idx); + + const method_ident_in_origin = try self.findMethodIdent(origin_env, type_name, method_name) orelse return null; + + const method_def_idx: CIR.Def.Idx = blk: { + if (origin_env.getExposedNodeIndexById(method_ident_in_origin)) |node_idx| { + break :blk @enumFromInt(@as(u32, node_idx)); + } + + if (Self.findDefIdxByIdent(origin_env, method_ident_in_origin)) |def_idx| { + break :blk def_idx; + } + + return null; + }; + + const ident_store = origin_env.getIdentStoreConst(); + const origin_var = ModuleEnv.varFrom(method_def_idx); + + var mapping = std.AutoHashMap(Var, Var).init(self.module_env.gpa); + defer mapping.deinit(); + + const start_slots = self.types_store.len(); + const copied_var = copy_import.copyVar( + &origin_env.types, + self.types_store, + origin_var, + &mapping, + ident_store, + self.module_env.getIdentStore(), + self.module_env.gpa, + ) catch return error.AllocatorError; + + try self.trackNewVars(start_slots); + return copied_var; + } + + fn buildQualifiedMethodName( + self: *Self, + module_name: []const u8, + type_name: []const u8, + method_name: []const u8, + ) std.mem.Allocator.Error![]u8 { + if (std.mem.eql(u8, type_name, module_name)) { + return try std.fmt.allocPrint(self.scratch.gpa, "{s}.{s}", .{ type_name, method_name }); + } else { + return try std.fmt.allocPrint(self.scratch.gpa, "{s}.{s}.{s}", .{ module_name, type_name, method_name }); + } + } + + fn findMethodIdent( + self: *Self, + origin_env: *const ModuleEnv, + type_name: []const u8, + method_name: []const u8, + ) error{AllocatorError}!?Ident.Idx { + const ident_store = origin_env.getIdentStoreConst(); + + const primary = self.buildQualifiedMethodName(origin_env.module_name, type_name, method_name) catch return error.AllocatorError; + defer self.scratch.gpa.free(primary); + if (ident_store.findByString(primary)) |ident| return ident; + + const module_type = std.fmt.allocPrint(self.scratch.gpa, "{s}.{s}.{s}", .{ origin_env.module_name, type_name, method_name }) catch return error.AllocatorError; + defer self.scratch.gpa.free(module_type); + if (ident_store.findByString(module_type)) |ident| return ident; + + const module_method = std.fmt.allocPrint(self.scratch.gpa, "{s}.{s}", .{ origin_env.module_name, method_name }) catch return error.AllocatorError; + defer self.scratch.gpa.free(module_method); + if (ident_store.findByString(module_method)) |ident| return ident; + + return ident_store.findByString(method_name); + } + + fn findDefIdxByIdent(origin_env: *const ModuleEnv, ident_idx: Ident.Idx) ?CIR.Def.Idx { + const defs = origin_env.store.sliceDefs(origin_env.all_defs); + for (defs) |def_idx| { + const def = origin_env.store.getDef(def_idx); + const pattern = origin_env.store.getPattern(def.pattern); + + if (pattern == .assign and pattern.assign.ident == ident_idx) { + return def_idx; + } + } + + return null; + } + + fn createListU8Var(self: *Self) Error!Var { + const start_slots = self.types_store.len(); + const u8_var = self.types_store.register(.{ + .content = .{ .structure = .{ .num = .{ .num_compact = .{ .int = .u8 } } } }, + .rank = Rank.generalized, + .mark = Mark.none, + }) catch return error.AllocatorError; + + const list_var = self.types_store.register(.{ + .content = .{ .structure = .{ .list = u8_var } }, + .rank = Rank.generalized, + .mark = Mark.none, + }) catch return error.AllocatorError; + + try self.trackNewVars(start_slots); + return list_var; + } + + fn createNominalInstanceVar(self: *Self, nominal_type: NominalType) Error!Var { + const start_slots = self.types_store.len(); + + const self_var = self.types_store.register(.{ + .content = .{ .structure = .{ .nominal_type = nominal_type } }, + .rank = Rank.generalized, + .mark = Mark.none, + }) catch return error.AllocatorError; + + try self.trackNewVars(start_slots); + return self_var; + } + + fn isBuiltinTryNominal(self: *Self, nominal: NominalType) bool { + if (nominal.origin_module != self.module_env.builtin_module_ident) { + return false; + } + + if (nominal.ident.ident_idx == self.module_env.try_ident) { + return true; + } + + if (self.module_env.common.findIdent("Builtin.Try")) |builtin_try_ident| { + return nominal.ident.ident_idx == builtin_try_ident; + } + return false; } + fn createOutOfRangeTagUnion(self: *Self) Error!Var { + const start_slots = self.types_store.len(); + const tag = Tag{ + .name = self.module_env.out_of_range_ident, + .args = Var.SafeList.Range.empty(), + }; + const tags_range = self.types_store.appendTags(&[_]Tag{tag}) catch return error.AllocatorError; + const empty_ext = self.types_store.register(.{ + .content = .{ .structure = .empty_tag_union }, + .rank = Rank.generalized, + .mark = Mark.none, + }) catch return error.AllocatorError; + + const tag_union_var = self.types_store.register(.{ + .content = .{ .structure = .{ .tag_union = .{ .tags = tags_range, .ext = empty_ext } } }, + .rank = Rank.generalized, + .mark = Mark.none, + }) catch return error.AllocatorError; + + try self.trackNewVars(start_slots); + return tag_union_var; + } + + fn trackNewVars(self: *Self, start_slots: u64) error{AllocatorError}!void { + var slot = start_slots; + const end_slots = self.types_store.len(); + while (slot < end_slots) : (slot += 1) { + const new_var = @as(Var, @enumFromInt(@as(u32, @intCast(slot)))); + _ = self.scratch.fresh_vars.append(self.scratch.gpa, new_var) catch return error.AllocatorError; + } + } + fn unifyNum( self: *Self, vars: *const ResolvedVarDescs, diff --git a/src/eval/interpreter.zig b/src/eval/interpreter.zig index f4707387ca..63420461da 100644 --- a/src/eval/interpreter.zig +++ b/src/eval/interpreter.zig @@ -60,6 +60,20 @@ fn listElementDec(context_opaque: ?*anyopaque, elem_ptr: ?[*]u8) callconv(.c) vo elem_value.decref(context.layout_store, context.roc_ops); } +fn interpreterLookupModuleEnv( + ctx: ?*const anyopaque, + module_ident: base_pkg.Ident.Idx, +) ?*const can.ModuleEnv { + const map_ptr = ctx orelse return null; + const map: *const std.AutoHashMapUnmanaged(base_pkg.Ident.Idx, *const can.ModuleEnv) = + @ptrCast(@alignCast(map_ptr)); + + if (map.*.get(module_ident)) |entry| { + return entry; + } + return null; +} + /// Interpreter that evaluates canonical Roc expressions against runtime types/layouts. pub const Interpreter = struct { pub const Error = error{ @@ -1801,6 +1815,10 @@ pub const Interpreter = struct { &self.snapshots, &self.unify_scratch, &self.unify_scratch.occurs_scratch, + unify.ModuleEnvLookup{ + .interpreter_lookup_ctx = @ptrCast(&self.module_envs), + .interpreter_lookup_fn = interpreterLookupModuleEnv, + }, call_ret_rt_var, entry.return_var, unify.Conf{ .ctx = .anon, .constraint_origin_var = null }, @@ -5118,6 +5136,10 @@ pub const Interpreter = struct { &self.snapshots, &self.unify_scratch, &self.unify_scratch.occurs_scratch, + unify.ModuleEnvLookup{ + .interpreter_lookup_ctx = @ptrCast(&self.module_envs), + .interpreter_lookup_fn = interpreterLookupModuleEnv, + }, params[i], args[i], unify.Conf{ .ctx = .anon, .constraint_origin_var = null },