mirror of
https://github.com/roc-lang/roc.git
synced 2025-11-17 02:53:30 +00:00
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:
parent
10fb170c2e
commit
cea6d56329
6 changed files with 351 additions and 138 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ const TestEnv = struct {
|
|||
&self.snapshots,
|
||||
&self.scratch,
|
||||
&self.occurs_scratch,
|
||||
unify_mod.ModuleEnvLookup{},
|
||||
a,
|
||||
b,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue