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{
|
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 {
|
||||||
|
|
|
||||||
|
|
@ -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{
|
||||||
|
|
|
||||||
|
|
@ -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"));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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 },
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue