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
This commit is contained in:
Anton-4 2025-11-11 21:12:55 +01:00 committed by GitHub
parent 10fb170c2e
commit cea6d56329
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 351 additions and 138 deletions

View file

@ -15,8 +15,6 @@ const test_exclusions = [_][]const u8{
const test_file_exclusions = [_][]const u8{ const test_file_exclusions = [_][]const u8{
// TODO: This test got out of sync and is not straightforward to fix // TODO: This test got out of sync and is not straightforward to fix
"src/eval/test/low_level_interp_test.zig", "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 { const TermColor = struct {

View file

@ -337,6 +337,7 @@ fn unifyWithCtx(self: *Self, a: Var, b: Var, env: *Env, ctx: unifier.Conf.Ctx) s
&self.snapshots, &self.snapshots,
&self.unify_scratch, &self.unify_scratch,
&self.occurs_scratch, &self.occurs_scratch,
unifier.ModuleEnvLookup{ .auto_imported = self.module_envs },
a, a,
b, b,
unifier.Conf{ unifier.Conf{

View file

@ -40,5 +40,7 @@ test "check tests" {
std.testing.refAllDecls(@import("test/let_polymorphism_integration_test.zig")); 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_inference_test.zig"));
std.testing.refAllDecls(@import("test/num_type_requirements_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/builtin_scope_test.zig"));
std.testing.refAllDecls(@import("test/custom_num_type_test.zig"));
} }

View file

@ -111,6 +111,7 @@ const TestEnv = struct {
&self.snapshots, &self.snapshots,
&self.scratch, &self.scratch,
&self.occurs_scratch, &self.occurs_scratch,
unify_mod.ModuleEnvLookup{},
a, a,
b, b,
); );

View file

@ -46,6 +46,7 @@ const tracy = @import("tracy");
const collections = @import("collections"); const collections = @import("collections");
const types_mod = @import("types"); const types_mod = @import("types");
const can = @import("can"); const can = @import("can");
const copy_import = @import("copy_import.zig");
const Check = @import("check").Check; const Check = @import("check").Check;
const problem_mod = @import("problem.zig"); const problem_mod = @import("problem.zig");
@ -53,6 +54,8 @@ const occurs = @import("occurs.zig");
const snapshot_mod = @import("snapshot.zig"); const snapshot_mod = @import("snapshot.zig");
const ModuleEnv = can.ModuleEnv; const ModuleEnv = can.ModuleEnv;
const AutoImportedType = can.Can.AutoImportedType;
const CIR = can.CIR;
const Region = base.Region; const Region = base.Region;
const Ident = base.Ident; const Ident = base.Ident;
@ -133,6 +136,7 @@ pub fn unify(
snapshots: *snapshot_mod.Store, snapshots: *snapshot_mod.Store,
unify_scratch: *Scratch, unify_scratch: *Scratch,
occurs_scratch: *occurs.Scratch, occurs_scratch: *occurs.Scratch,
module_lookup: ModuleEnvLookup,
/// The "expected" variable /// The "expected" variable
a: Var, a: Var,
/// The "actual" variable /// The "actual" variable
@ -145,6 +149,7 @@ pub fn unify(
snapshots, snapshots,
unify_scratch, unify_scratch,
occurs_scratch, occurs_scratch,
module_lookup,
a, a,
b, b,
Conf{ .ctx = .anon, .constraint_origin_var = null }, Conf{ .ctx = .anon, .constraint_origin_var = null },
@ -160,6 +165,29 @@ pub const Conf = struct {
pub const Ctx = enum { anon, anno }; 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 /// Unify two type variables
/// ///
/// This function /// This function
@ -175,6 +203,7 @@ pub fn unifyWithConf(
snapshots: *snapshot_mod.Store, snapshots: *snapshot_mod.Store,
unify_scratch: *Scratch, unify_scratch: *Scratch,
occurs_scratch: *occurs.Scratch, occurs_scratch: *occurs.Scratch,
module_lookup: ModuleEnvLookup,
/// The "expected" variable /// The "expected" variable
a: Var, a: Var,
/// The "actual" variable /// The "actual" variable
@ -188,7 +217,7 @@ pub fn unifyWithConf(
unify_scratch.reset(); unify_scratch.reset();
// Unify // 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| { unifier.unifyGuarded(a, b) catch |err| {
const problem: Problem = blk: { const problem: Problem = blk: {
switch (err) { switch (err) {
@ -366,6 +395,7 @@ const Unifier = struct {
types_store: *types_mod.Store, types_store: *types_mod.Store,
scratch: *Scratch, scratch: *Scratch,
occurs_scratch: *occurs.Scratch, occurs_scratch: *occurs.Scratch,
module_lookup: ModuleEnvLookup,
depth: u8, depth: u8,
skip_depth_check: bool, skip_depth_check: bool,
@ -375,12 +405,14 @@ const Unifier = struct {
types_store: *types_mod.Store, types_store: *types_mod.Store,
scratch: *Scratch, scratch: *Scratch,
occurs_scratch: *occurs.Scratch, occurs_scratch: *occurs.Scratch,
module_lookup: ModuleEnvLookup,
) Unifier { ) Unifier {
return .{ return .{
.module_env = module_env, .module_env = module_env,
.types_store = types_store, .types_store = types_store,
.scratch = scratch, .scratch = scratch,
.occurs_scratch = occurs_scratch, .occurs_scratch = occurs_scratch,
.module_lookup = module_lookup,
.depth = 0, .depth = 0,
.skip_depth_check = false, .skip_depth_check = false,
}; };
@ -817,9 +849,16 @@ const Unifier = struct {
} }
}, },
.frac_unbound => { .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)) { 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); self.merge(vars, vars.b.desc.content);
} else { } else {
return error.TypeMismatch; return error.TypeMismatch;
@ -894,6 +933,14 @@ const Unifier = struct {
return error.TypeMismatch; 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 => { else => {
// Concrete numeric types don't unify with nominal types // Concrete numeric types don't unify with nominal types
return error.TypeMismatch; return error.TypeMismatch;
@ -1140,80 +1187,44 @@ const Unifier = struct {
self: *Self, self: *Self,
nominal_type: NominalType, nominal_type: NominalType,
) Error!bool { ) Error!bool {
// Get the backing var (record extension) of the nominal type const method_var = try self.getNominalMethodVar(nominal_type, self.module_env.from_int_digits_ident) orelse return false;
const backing_var = self.types_store.getNominalBackingVar(nominal_type); const resolved = self.types_store.resolveVar(method_var);
const backing_resolved = self.types_store.resolveVar(backing_var);
// Check if the backing is a record const func = switch (resolved.desc.content) {
const backing_record = backing_resolved.desc.content.unwrapRecord() orelse return false; .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 ret_desc = self.types_store.resolveVar(func.ret);
const from_int_digits_ident = self.module_env.from_int_digits_ident; 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 args_slice = self.types_store.sliceVars(func.args);
const fields_slice = self.types_store.getRecordFieldsSlice(backing_record.fields); if (args_slice.len != 1) return false;
const field_names = fields_slice.items(.name);
const field_vars = fields_slice.items(.var_);
for (field_names, 0..) |name_idx, i| { const list_u8_var = try self.createListU8Var();
if (name_idx == from_int_digits_ident) { self.unifyGuarded(args_slice[0], list_u8_var) catch return false;
// 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);
// Check that it's a function const self_ret_var = try self.createNominalInstanceVar(nominal_type);
switch (field_resolved.desc.content) { self.unifyGuarded(ret_args[1], self_ret_var) catch return false;
.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;
// Create List(U8) type and unify with the argument const try_error_var = try self.createOutOfRangeTagUnion();
const u8_var = self.types_store.register(.{ .content = .{ .structure = .{ .num = .{ .num_compact = .{ .int = .u8 } } } }, .rank = Rank.generalized, .mark = Mark.none }) catch return error.AllocatorError; self.unifyGuarded(ret_args[2], try_error_var) catch return false;
const list_u8_var = self.types_store.register(.{ .content = .{ .structure = .{ .list = u8_var } }, .rank = Rank.generalized, .mark = Mark.none }) catch return error.AllocatorError;
// Attempt to unify the argument with List(U8) return true;
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;
} }
/// Check if a nominal type has a from_dec_digits method by unifying its signature /// Check if a nominal type has a from_dec_digits method by unifying its signature
@ -1223,85 +1234,263 @@ const Unifier = struct {
self: *Self, self: *Self,
nominal_type: NominalType, nominal_type: NominalType,
) Error!bool { ) Error!bool {
// Get the backing var (record extension) of the nominal type const method_var = try self.getNominalMethodVar(nominal_type, self.module_env.from_dec_digits_ident) orelse return false;
const backing_var = self.types_store.getNominalBackingVar(nominal_type); const resolved = self.types_store.resolveVar(method_var);
const backing_resolved = self.types_store.resolveVar(backing_var);
// Check if the backing is a record const func = switch (resolved.desc.content) {
const backing_record = backing_resolved.desc.content.unwrapRecord() orelse return false; .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 ret_desc = self.types_store.resolveVar(func.ret);
const from_dec_digits_ident = self.module_env.from_dec_digits_ident; 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 args_slice = self.types_store.sliceVars(func.args);
const fields_slice = self.types_store.getRecordFieldsSlice(backing_record.fields); if (args_slice.len != 1) return false;
const field_names = fields_slice.items(.name);
const field_vars = fields_slice.items(.var_);
for (field_names, 0..) |name_idx, i| { const before_ident = self.module_env.common.findIdent("before_dot") orelse return false;
if (name_idx == from_dec_digits_ident) { const after_ident = self.module_env.common.findIdent("after_dot") orelse return false;
// 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);
// Check that it's a function const record_desc = self.types_store.resolveVar(args_slice[0]);
switch (field_resolved.desc.content) { const record = switch (record_desc.desc.content) {
.structure => |structure| { .structure => |structure| switch (structure) {
switch (structure) { .record => structure.record,
.fn_pure, .fn_effectful, .fn_unbound => |func| { else => return false,
// Check it takes exactly 2 arguments },
const args_slice = self.types_store.sliceVars(func.args); else => return false,
if (args_slice.len != 2) return false; };
// Create List(U8) type and unify with both arguments if (record.fields.len() != 2) return false;
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 fields_slice = self.types_store.getRecordFieldsSlice(record.fields);
const list_u8_var1 = self.types_store.register(.{ .content = .{ .structure = .{ .list = u8_var1 } }, .rank = Rank.generalized, .mark = Mark.none }) catch return error.AllocatorError; const names = fields_slice.items(.name);
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 vars = fields_slice.items(.var_);
const list_u8_var2 = self.types_store.register(.{ .content = .{ .structure = .{ .list = u8_var2 } }, .rank = Rank.generalized, .mark = Mark.none }) catch return error.AllocatorError;
// Attempt to unify both arguments with List(U8) var before_idx: ?usize = null;
self.unifyGuarded(args_slice[0], list_u8_var1) catch return false; var after_idx: ?usize = null;
self.unifyGuarded(args_slice[1], list_u8_var2) catch return false; for (names, 0..) |name, idx| {
if (name == before_ident) {
// For the return type, we need Try(Self, [OutOfRange]) before_idx = idx;
// Create a fresh var for Self (the nominal type) } else if (name == after_ident) {
const self_var = self.types_store.register(.{ .content = .{ .structure = .{ .nominal_type = nominal_type } }, .rank = Rank.generalized, .mark = Mark.none }) catch return error.AllocatorError; after_idx = idx;
// 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,
}
} }
} }
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; 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( fn unifyNum(
self: *Self, self: *Self,
vars: *const ResolvedVarDescs, vars: *const ResolvedVarDescs,

View file

@ -60,6 +60,20 @@ fn listElementDec(context_opaque: ?*anyopaque, elem_ptr: ?[*]u8) callconv(.c) vo
elem_value.decref(context.layout_store, context.roc_ops); 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. /// Interpreter that evaluates canonical Roc expressions against runtime types/layouts.
pub const Interpreter = struct { pub const Interpreter = struct {
pub const Error = error{ pub const Error = error{
@ -1801,6 +1815,10 @@ pub const Interpreter = struct {
&self.snapshots, &self.snapshots,
&self.unify_scratch, &self.unify_scratch,
&self.unify_scratch.occurs_scratch, &self.unify_scratch.occurs_scratch,
unify.ModuleEnvLookup{
.interpreter_lookup_ctx = @ptrCast(&self.module_envs),
.interpreter_lookup_fn = interpreterLookupModuleEnv,
},
call_ret_rt_var, call_ret_rt_var,
entry.return_var, entry.return_var,
unify.Conf{ .ctx = .anon, .constraint_origin_var = null }, unify.Conf{ .ctx = .anon, .constraint_origin_var = null },
@ -5118,6 +5136,10 @@ pub const Interpreter = struct {
&self.snapshots, &self.snapshots,
&self.unify_scratch, &self.unify_scratch,
&self.unify_scratch.occurs_scratch, &self.unify_scratch.occurs_scratch,
unify.ModuleEnvLookup{
.interpreter_lookup_ctx = @ptrCast(&self.module_envs),
.interpreter_lookup_fn = interpreterLookupModuleEnv,
},
params[i], params[i],
args[i], args[i],
unify.Conf{ .ctx = .anon, .constraint_origin_var = null }, unify.Conf{ .ctx = .anon, .constraint_origin_var = null },