Remove .str primitive type

This commit is contained in:
Richard Feldman 2025-11-16 22:59:24 -05:00
parent 9f9192a416
commit 129e1e1d28
No known key found for this signature in database
32 changed files with 297 additions and 375 deletions

View file

@ -66,58 +66,6 @@ const BuiltinIndices = struct {
f64_type: CIR.Statement.Idx,
};
/// Transform all Str nominal types to .str primitive types in a module.
/// This is necessary because the interpreter needs .str to be a primitive type,
/// but we define methods on Str as a nominal type in Str.roc for ergonomics.
///
/// This transformation happens after type-checking but before serialization,
/// ensuring that the serialized .bin file contains methods associated with
/// the .str primitive type rather than a nominal Str type.
fn transformStrNominalToPrimitive(env: *ModuleEnv) !void {
const FlatType = types.FlatType;
// Get the Str identifier in this module
const str_ident = env.common.findIdent("Str") orelse {
@panic("Str identifier not found in Builtin module");
};
// Iterate through all slots in the type store
for (0..env.types.len()) |i| {
const var_idx = @as(Var, @enumFromInt(i));
// Skip redirects, only process roots
if (!env.types.resolveVar(var_idx).is_root) {
continue;
}
const resolved = env.types.resolveVar(var_idx);
const desc = resolved.desc;
// Check if this descriptor contains a nominal type
switch (desc.content) {
.structure => |structure| {
switch (structure) {
.nominal_type => |nominal| {
// Check if this is the Str nominal type
// TypeIdent has an ident_idx field that references the identifier
if (nominal.ident.ident_idx == str_ident) {
// Replace with .str primitive type
const new_content = Content{ .structure = FlatType.str };
const new_desc = types.Descriptor{
.content = new_content,
.rank = desc.rank,
.mark = desc.mark,
};
try env.types.setVarDesc(var_idx, new_desc);
}
},
else => {},
}
},
else => {},
}
}
}
/// Replace specific e_anno_only expressions with e_low_level_lambda operations.
/// This transforms standalone annotations into low-level builtin lambda operations
@ -401,7 +349,6 @@ fn replaceStrIsEmptyWithLowLevel(env: *ModuleEnv) !std.ArrayList(CIR.Def.Idx) {
}
/// Transform all List nominal types to .list primitive types in a module.
/// This is similar to transformStrNominalToPrimitive but handles List's type parameter.
///
/// List is parameterized (List(a)), so we need to preserve the element type parameter
/// when transforming from nominal to primitive. The transformation replaces
@ -503,6 +450,7 @@ pub fn main() !void {
&.{}, // No module dependencies
null, // bool_stmt not available yet (will be found within Builtin)
null, // try_stmt not available yet (will be found within Builtin)
null, // str_stmt not available yet (will be found within Builtin)
);
defer {
builtin_env.deinit();
@ -581,8 +529,7 @@ pub fn main() !void {
// Transform nominal types to primitive types as necessary.
// This must happen BEFORE serialization to ensure the .bin file contains
// methods associated with the .str primitive, not a nominal type
try transformStrNominalToPrimitive(builtin_env);
// methods associated with the primitive types
try transformListNominalToPrimitive(builtin_env);
// Create output directory
@ -628,6 +575,7 @@ fn compileModule(
deps: []const ModuleDep,
bool_stmt_opt: ?CIR.Statement.Idx,
try_stmt_opt: ?CIR.Statement.Idx,
str_stmt_opt: ?CIR.Statement.Idx,
) !*ModuleEnv {
// This follows the pattern from TestEnv.init() in src/check/test/TestEnv.zig
@ -649,7 +597,7 @@ fn compileModule(
const list_ident = try module_env.insertIdent(base.Ident.for_text("List"));
const box_ident = try module_env.insertIdent(base.Ident.for_text("Box"));
// Use provided bool_stmt and try_stmt if available, otherwise use undefined
// Use provided bool_stmt, try_stmt, and str_stmt if available, otherwise use undefined
// For Builtin module, these will be found after canonicalization and updated before type checking
var common_idents: Check.CommonIdents = .{
.module_name = module_ident,
@ -657,6 +605,7 @@ fn compileModule(
.box = box_ident,
.bool_stmt = bool_stmt_opt orelse undefined,
.try_stmt = try_stmt_opt orelse undefined,
.str_stmt = str_stmt_opt orelse undefined,
.builtin_module = null,
};
@ -794,8 +743,8 @@ fn compileModule(
module_env.evaluation_order = eval_order_ptr;
}
// Find Bool and Try statements before type checking
// When compiling Builtin, bool_stmt and try_stmt are initially undefined,
// Find Bool, Try, and Str statements before type checking
// When compiling Builtin, bool_stmt, try_stmt, and str_stmt are initially undefined,
// but they must be set before type checking begins
const found_bool_stmt = findTypeDeclaration(module_env, "Bool") catch {
std.debug.print("\n" ++ "=" ** 80 ++ "\n", .{});
@ -813,10 +762,19 @@ fn compileModule(
std.debug.print("=" ** 80 ++ "\n", .{});
return error.TypeDeclarationNotFound;
};
const found_str_stmt = findTypeDeclaration(module_env, "Str") catch {
std.debug.print("\n" ++ "=" ** 80 ++ "\n", .{});
std.debug.print("ERROR: Could not find Str type in Builtin module\n", .{});
std.debug.print("=" ** 80 ++ "\n", .{});
std.debug.print("The Str type declaration is required for type checking.\n", .{});
std.debug.print("=" ** 80 ++ "\n", .{});
return error.TypeDeclarationNotFound;
};
// Update common_idents with the found statement indices
common_idents.bool_stmt = found_bool_stmt;
common_idents.try_stmt = found_try_stmt;
common_idents.str_stmt = found_str_stmt;
}
// 6. Type check

View file

@ -285,6 +285,14 @@ pub fn populateModuleEnvs(
.statement_idx = statement_idx,
});
}
// Also add an entry for "Builtin" module itself so that nominal types
// with origin_module="Builtin" can be looked up in module_envs
const builtin_module_ident = try calling_module_env.insertIdent(base.Ident.for_text("Builtin"));
try module_envs_map.put(builtin_module_ident, .{
.env = builtin_module_env,
.statement_idx = null, // No specific statement - this is the module itself
});
}
/// Set up auto-imported builtin types (Bool, Try, Dict, Set, Str, and numeric types) from the Builtin module.

View file

@ -375,7 +375,6 @@ pub const TypeAnno = union(enum) {
/// A builtin type
pub const Builtin = enum {
str,
list,
box,
num,
@ -398,7 +397,6 @@ pub const TypeAnno = union(enum) {
/// Convert a builtin type to it's name
pub fn toBytes(self: @This()) []const u8 {
switch (self) {
.str => return "Str",
.list => return "List",
.box => return "Box",
.num => return "Num",
@ -422,7 +420,6 @@ pub const TypeAnno = union(enum) {
/// Convert a type name string to the corresponding builtin type
pub fn fromBytes(bytes: []const u8) ?@This() {
if (std.mem.eql(u8, bytes, "Str")) return .str;
if (std.mem.eql(u8, bytes, "List")) return .list;
if (std.mem.eql(u8, bytes, "Box")) return .box;
if (std.mem.eql(u8, bytes, "Num")) return .num;

View file

@ -212,7 +212,7 @@ test "record with extension variable" {
// Test that regular records have extension variables
// Create { x: 42, y: "hi" }* (open record)
const num_var = try env.types.freshFromContent(Content{ .structure = .{ .num = .{ .int_precision = .i32 } } });
const str_var = try env.types.freshFromContent(Content{ .structure = .str });
const str_var = try env.types.freshFromContent(Content{ .structure = .empty_record });
const fields = [_]types.RecordField{
.{ .name = try env.insertIdent(Ident.for_text("x")), .var_ = num_var },

View file

@ -100,6 +100,8 @@ import_cache: ImportCache,
constraint_origins: std.AutoHashMap(Var, Var),
/// Copied Bool type from Bool module (for use in if conditions, etc.)
bool_var: Var,
/// Copied Str type from Builtin module (for use in string literals, etc.)
str_var: Var,
/// Used when looking up static dispatch functions
static_dispatch_method_name_buf: std.ArrayList(u8),
/// Map representation of Ident -> Var, used in checking static dispatch constraints
@ -131,6 +133,8 @@ pub const CommonIdents = struct {
bool_stmt: can.CIR.Statement.Idx,
/// Statement index of Try type in the current module (injected from Builtin.bin)
try_stmt: can.CIR.Statement.Idx,
/// Statement index of Str type in the current module (injected from Builtin.bin)
str_stmt: can.CIR.Statement.Idx,
/// Direct reference to the Builtin module env (null when compiling Builtin module itself)
builtin_module: ?*const ModuleEnv,
};
@ -178,6 +182,7 @@ pub fn init(
.import_cache = ImportCache{},
.constraint_origins = std.AutoHashMap(Var, Var).init(gpa),
.bool_var = undefined, // Will be initialized in copyBuiltinTypes()
.str_var = undefined, // Will be initialized in copyBuiltinTypes()
.static_dispatch_method_name_buf = try std.ArrayList(u8).initCapacity(gpa, 32),
.ident_to_var_map = std.AutoHashMap(Ident.Idx, Var).init(gpa),
.top_level_ptrns = std.AutoHashMap(CIR.Pattern.Idx, DefProcessed).init(gpa),
@ -594,6 +599,12 @@ fn freshBool(self: *Self, env: *Env, new_region: Region) Allocator.Error!Var {
return try self.instantiateVar(self.bool_var, env, .{ .explicit = new_region });
}
/// Create a str var
fn freshStr(self: *Self, env: *Env, new_region: Region) Allocator.Error!Var {
// Use the copied Str type from the type store (set by copyBuiltinTypes)
return try self.instantiateVar(self.str_var, env, .{ .explicit = new_region });
}
// updating vars //
/// Unify the provided variable with the provided content
@ -645,15 +656,21 @@ fn setVarRank(self: *Self, target_var: Var, env: *Env) std.mem.Allocator.Error!v
/// `if` conditions and need to be available in every module's type store.
fn copyBuiltinTypes(self: *Self) !void {
const bool_stmt_idx = self.common_idents.bool_stmt;
const str_stmt_idx = self.common_idents.str_stmt;
if (self.common_idents.builtin_module) |builtin_env| {
// Copy Bool type from Builtin module using the direct reference
const bool_type_var = ModuleEnv.varFrom(bool_stmt_idx);
self.bool_var = try self.copyVar(bool_type_var, builtin_env, Region.zero());
// Copy Str type from Builtin module using the direct reference
const str_type_var = ModuleEnv.varFrom(str_stmt_idx);
self.str_var = try self.copyVar(str_type_var, builtin_env, Region.zero());
} else {
// If Builtin module reference is null, use the statement from the current module
// This happens when compiling the Builtin module itself
self.bool_var = ModuleEnv.varFrom(bool_stmt_idx);
self.str_var = ModuleEnv.varFrom(str_stmt_idx);
}
// Try type is accessed via external references, no need to copy it here
@ -1587,7 +1604,6 @@ fn generateBuiltinTypeInstance(
env: *Env,
) std.mem.Allocator.Error!Var {
switch (anno_builtin_type) {
.str => return try self.freshFromContent(.{ .structure = .str }, env, anno_region),
.u8 => return try self.freshFromContent(.{ .structure = .{ .num = types_mod.Num.int_u8 } }, env, anno_region),
.u16 => return try self.freshFromContent(.{ .structure = .{ .num = types_mod.Num.int_u16 } }, env, anno_region),
.u32 => return try self.freshFromContent(.{ .structure = .{ .num = types_mod.Num.int_u32 } }, env, anno_region),
@ -1753,7 +1769,8 @@ fn checkPatternHelp(
},
// str //
.str_literal => {
try self.unifyWith(pattern_var, .{ .structure = .str }, env);
const str_var = try self.freshStr(env, pattern_region);
_ = try self.unify(pattern_var, str_var, env);
},
// as //
.as => |p| {
@ -2168,7 +2185,8 @@ fn checkExpr(self: *Self, expr_idx: CIR.Expr.Idx, env: *Env, expected: Expected)
switch (expr) {
// str //
.e_str_segment => |_| {
try self.unifyWith(expr_var, .{ .structure = .str }, env);
const str_var = try self.freshStr(env, expr_region);
_ = try self.unify(expr_var, str_var, env);
},
.e_str => |str| {
// Iterate over the string segments, capturing if any error'd
@ -2187,8 +2205,9 @@ fn checkExpr(self: *Self, expr_idx: CIR.Expr.Idx, env: *Env, expected: Expected)
// If any segment errored, propgate that error to the root string
try self.unifyWith(expr_var, .err, env);
} else {
// Otherwise, set the type of this expr to be string
try self.unifyWith(expr_var, .{ .structure = .str }, env);
// Otherwise, set the type of this expr to be nominal Str
const str_var = try self.freshStr(env, expr_region);
_ = try self.unify(expr_var, str_var, env);
}
},
// nums //

View file

@ -201,7 +201,6 @@ fn copyFlatType(
allocator: std.mem.Allocator,
) std.mem.Allocator.Error!FlatType {
return switch (flat_type) {
.str => FlatType.str,
.box => |box_var| FlatType{ .box = try copyVar(source_store, dest_store, box_var, var_mapping, source_idents, dest_idents, allocator) },
.list => |list_var| FlatType{ .list = try copyVar(source_store, dest_store, list_var, var_mapping, source_idents, dest_idents, allocator) },
.list_unbound => FlatType.list_unbound,

View file

@ -160,7 +160,6 @@ const CheckOccurs = struct {
switch (root.desc.content) {
.structure => |flat_type| {
switch (flat_type) {
.str => {},
.box => |sub_var| {
try self.occursSubVar(root, sub_var, ctx.allowRecursion());
},
@ -379,7 +378,7 @@ test "occurs: no recurcion (v = Str)" {
var scratch = try Scratch.init(gpa);
defer scratch.deinit();
const str_var = try types_store.freshFromContent(Content{ .structure = .str });
const str_var = try types_store.freshFromContent(Content{ .structure = .empty_record });
const result = occurs(&types_store, &scratch, str_var);
try std.testing.expectEqual(.not_recursive, result);
@ -442,7 +441,7 @@ test "occurs: no recursion through two levels (v1 = Box v2, v2 = Str)" {
const v2 = try types_store.fresh();
try types_store.setRootVarContent(v1, Content{ .structure = .{ .box = v2 } });
try types_store.setRootVarContent(v2, Content{ .structure = .str });
try types_store.setRootVarContent(v2, Content{ .structure = .empty_record });
const result = occurs(&types_store, &scratch, v1);
try std.testing.expectEqual(.not_recursive, result);
@ -457,7 +456,7 @@ test "occurs: tuple recursion (v = Tuple(v, Str))" {
defer scratch.deinit();
const v = try types_store.fresh();
const str_var = try types_store.freshFromContent(Content{ .structure = .str });
const str_var = try types_store.freshFromContent(Content{ .structure = .empty_record });
const elems_range = try types_store.appendVars(&[_]Var{ v, str_var });
const tuple = types.Tuple{ .elems = elems_range };
@ -480,7 +479,7 @@ test "occurs: tuple not recursive (v = Tuple(Str, Str))" {
var scratch = try Scratch.init(gpa);
defer scratch.deinit();
const str_var = try types_store.freshFromContent(Content{ .structure = .str });
const str_var = try types_store.freshFromContent(Content{ .structure = .empty_record });
const elems_range = try types_store.appendVars(&[_]Var{ str_var, str_var });
const tuple = types.Tuple{ .elems = elems_range };
@ -528,8 +527,8 @@ test "occurs: alias with no recursion (v = Alias Str)" {
defer scratch.deinit();
const alias_var = try types_store.fresh();
const backing_var = try types_store.freshFromContent(Content{ .structure = .str });
const arg_var = try types_store.freshFromContent(Content{ .structure = .str });
const backing_var = try types_store.freshFromContent(Content{ .structure = .empty_record });
const arg_var = try types_store.freshFromContent(Content{ .structure = .empty_record });
try types_store.setRootVarContent(alias_var, try types_store.mkAlias(
types.TypeIdent{ .ident_idx = undefined },

View file

@ -146,7 +146,6 @@ pub const Store = struct {
fn deepCopyFlatType(self: *Self, store: *const TypesStore, flat_type: types.FlatType) std.mem.Allocator.Error!SnapshotFlatType {
return switch (flat_type) {
.str => SnapshotFlatType.str,
.box => |box_var| {
const deep_content = try self.deepCopyVar(store, box_var);
return SnapshotFlatType{ .box = deep_content };
@ -497,7 +496,6 @@ pub const SnapshotAlias = struct {
/// TODO
pub const SnapshotFlatType = union(enum) {
str,
box: SnapshotContentIdx, // Index into SnapshotStore.contents
list: SnapshotContentIdx,
list_unbound,
@ -930,9 +928,6 @@ pub const SnapshotWriter = struct {
/// Convert a flat type to a type string
fn writeFlatType(self: *Self, flat_type: SnapshotFlatType, root_idx: SnapshotContentIdx) Allocator.Error!void {
switch (flat_type) {
.str => {
_ = try self.buf.writer().write("Str");
},
.box => |sub_var| {
_ = try self.buf.writer().write("Box(");
try self.writeWithContext(sub_var, .General, root_idx);
@ -1463,7 +1458,7 @@ pub const SnapshotWriter = struct {
fn countInFlatType(self: *Self, search_flex_var: Var, flat_type: SnapshotFlatType, count: *usize) std.mem.Allocator.Error!void {
switch (flat_type) {
.str, .empty_record, .empty_tag_union => {},
.empty_record, .empty_tag_union => {},
.box => |sub_idx| try self.countContent(search_flex_var, sub_idx, count),
.list => |sub_idx| try self.countContent(search_flex_var, sub_idx, count),
.list_unbound, .num => {},

View file

@ -205,9 +205,10 @@ pub fn initWithImport(module_name: []const u8, source: []const u8, other_module_
try can.canonicalizeFile();
try can.validateForChecking();
// Get Bool and Try statement indices from the IMPORTED modules (not copied!)
// Get Bool, Try, and Str statement indices from the IMPORTED modules (not copied!)
const bool_stmt_in_bool_module = builtin_indices.bool_type;
const try_stmt_in_result_module = builtin_indices.try_type;
const str_stmt_in_builtin_module = builtin_indices.str_type;
const module_common_idents: Check.CommonIdents = .{
.module_name = try module_env.insertIdent(base.Ident.for_text(module_name)),
@ -215,6 +216,7 @@ pub fn initWithImport(module_name: []const u8, source: []const u8, other_module_
.box = try module_env.insertIdent(base.Ident.for_text("Box")),
.bool_stmt = bool_stmt_in_bool_module,
.try_stmt = try_stmt_in_result_module,
.str_stmt = str_stmt_in_builtin_module,
.builtin_module = other_test_env.builtin_module.env,
};
@ -321,9 +323,10 @@ pub fn init(module_name: []const u8, source: []const u8) !TestEnv {
try can.canonicalizeFile();
try can.validateForChecking();
// Get Bool and Try statement indices from the IMPORTED modules (not copied!)
// Get Bool, Try, and Str statement indices from the IMPORTED modules (not copied!)
const bool_stmt_in_bool_module = builtin_indices.bool_type;
const try_stmt_in_result_module = builtin_indices.try_type;
const str_stmt_in_builtin_module = builtin_indices.str_type;
const module_common_idents: Check.CommonIdents = .{
.module_name = try module_env.insertIdent(base.Ident.for_text(module_name)),
@ -331,6 +334,7 @@ pub fn init(module_name: []const u8, source: []const u8) !TestEnv {
.box = try module_env.insertIdent(base.Ident.for_text("Box")),
.bool_stmt = bool_stmt_in_bool_module,
.try_stmt = try_stmt_in_result_module,
.str_stmt = str_stmt_in_builtin_module,
.builtin_module = builtin_module.env,
};

View file

@ -32,7 +32,7 @@ test "nominal type origin - displays origin in snapshot writer" {
defer import_mapping.deinit();
// Create a nominal type snapshot with origin from a different module
const nominal_type_backing = snapshot.SnapshotContent{ .structure = .str };
const nominal_type_backing = snapshot.SnapshotContent{ .structure = .empty_record };
const nominal_type_backing_idx = try snapshots.contents.append(test_allocator, nominal_type_backing);
const vars_range = try snapshots.content_indexes.appendSlice(test_allocator, &.{nominal_type_backing_idx});
@ -93,7 +93,7 @@ test "nominal type origin - displays origin in snapshot writer" {
defer buf.deinit(test_allocator);
// Create type arguments
const str_content = snapshot.SnapshotContent{ .structure = .{ .str = {} } };
const str_content = snapshot.SnapshotContent{ .structure = .empty_record };
const str_idx = try snapshots.contents.append(test_allocator, str_content);
const args_range = try snapshots.content_indexes.appendSlice(test_allocator, &.{ nominal_type_backing_idx, str_idx });
@ -116,8 +116,8 @@ test "nominal type origin - displays origin in snapshot writer" {
try writer.writeNominalType(generic_nominal, nominal_type_backing_idx);
const result = writer.get();
// Should show "Person(Str) (from Data.Types)"
try testing.expect(std.mem.indexOf(u8, result, "Person(Str)") != null);
// Should show "Person({}) (from Data.Types)" - empty_record displays as {}
try testing.expect(std.mem.indexOf(u8, result, "Person({})") != null);
try testing.expect(std.mem.indexOf(u8, result, "(from Data.Types)") != null);
}
}
@ -136,7 +136,7 @@ test "nominal type origin - works with no context" {
var import_mapping = types_mod.import_mapping.ImportMapping.init(test_allocator);
defer import_mapping.deinit();
const nominal_type_backing = snapshot.SnapshotContent{ .structure = .str };
const nominal_type_backing = snapshot.SnapshotContent{ .structure = .empty_record };
const nominal_type_backing_idx = try snapshots.contents.append(test_allocator, nominal_type_backing);
const vars_range = try snapshots.content_indexes.appendSlice(test_allocator, &.{nominal_type_backing_idx});

View file

@ -406,7 +406,7 @@ test "rigid_var - cannot unify with alias (fail)" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const alias = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const alias = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const rigid = try env.module_env.types.freshFromContent(try env.mkRigidVar("a"));
const result = try env.unify(alias, rigid);
@ -432,7 +432,7 @@ test "unify - alias with same args" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const bool_ = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_i8 } });
// Create alias `a` with its backing var and args in sequence
@ -457,7 +457,7 @@ test "unify - aliases with different names but same backing" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
// Create alias `a` with its backing var and arg
const a_backing_var = try env.module_env.types.freshFromContent(try env.mkTuple(&[_]Var{str}));
@ -481,7 +481,7 @@ test "unify - alias with different args (fail)" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const bool_ = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_i8 } });
// Create alias `a` with its backing var and arg
@ -506,7 +506,7 @@ test "unify - alias with flex" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const bool_ = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_i8 } });
const a_backing_var = try env.module_env.types.freshFromContent(try env.mkTuple(&[_]Var{ str, bool_ })); // backing var
@ -527,11 +527,11 @@ test "unify - alias with concrete" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const a_backing_var = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const a_backing_var = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const a_alias = try env.mkAlias("Alias", a_backing_var, &[_]Var{});
const a = try env.module_env.types.freshFromContent(a_alias);
const b = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const b = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const result = try env.unify(a, b);
@ -545,7 +545,7 @@ test "unify - alias with concrete" {
const resolved_backing = env.module_env.types.resolveVar(
env.module_env.types.getAliasBackingVar(resolved.desc.content.alias),
);
try std.testing.expectEqual(Content{ .structure = .str }, resolved_backing.desc.content);
try std.testing.expectEqual(Content{ .structure = .empty_record }, resolved_backing.desc.content);
// Assert that a & b redirect to the alias
try std.testing.expectEqual(Slot{ .redirect = resolved.var_ }, env.module_env.types.getSlot(a));
@ -557,10 +557,10 @@ test "unify - alias with concrete other way" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const b_backing_var = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const b_backing_var = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const b_alias = try env.mkAlias("Alias", b_backing_var, &[_]Var{});
const a = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const a = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const b = try env.module_env.types.freshFromContent(b_alias);
const result = try env.unify(a, b);
@ -575,7 +575,7 @@ test "unify - alias with concrete other way" {
const resolved_backing = env.module_env.types.resolveVar(
env.module_env.types.getAliasBackingVar(resolved.desc.content.alias),
);
try std.testing.expectEqual(Content{ .structure = .str }, resolved_backing.desc.content);
try std.testing.expectEqual(Content{ .structure = .empty_record }, resolved_backing.desc.content);
// Assert that a & b redirect to the alias
try std.testing.expectEqual(Slot{ .redirect = resolved.var_ }, env.module_env.types.getSlot(a));
@ -590,7 +590,7 @@ test "unify - a is builtin and b is flex_var" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = Content{ .structure = .str };
const str = Content{ .structure = .empty_record };
const a = try env.module_env.types.freshFromContent(str);
const b = try env.module_env.types.fresh();
@ -608,7 +608,7 @@ test "unify - a is flex_var and b is builtin" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = Content{ .structure = .str };
const str = Content{ .structure = .empty_record };
const a = try env.module_env.types.fresh();
const b = try env.module_env.types.freshFromContent(str);
@ -628,7 +628,7 @@ test "unify - a & b are both str" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = Content{ .structure = .str };
const str = Content{ .structure = .empty_record };
const a = try env.module_env.types.freshFromContent(str);
const b = try env.module_env.types.freshFromContent(str);
@ -646,7 +646,7 @@ test "unify - a & b are diff (fail)" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = Content{ .structure = .str };
const str = Content{ .structure = .empty_record };
const int = Content{ .structure = .{ .num = Num.int_i8 } };
const a = try env.module_env.types.freshFromContent(int);
@ -665,7 +665,7 @@ test "unify - a & b box with same arg unify" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = Content{ .structure = .str };
const str = Content{ .structure = .empty_record };
const str_var = try env.module_env.types.freshFromContent(str);
const box_str = Content{ .structure = .{ .box = str_var } };
@ -686,7 +686,7 @@ test "unify - a & b box with diff args (fail)" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = Content{ .structure = .str };
const str = Content{ .structure = .empty_record };
const str_var = try env.module_env.types.freshFromContent(str);
const i64_ = Content{ .structure = .{ .num = Num.int_i64 } };
@ -711,7 +711,7 @@ test "unify - a & b list with same arg unify" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = Content{ .structure = .str };
const str = Content{ .structure = .empty_record };
const str_var = try env.module_env.types.freshFromContent(str);
const list_str = Content{ .structure = .{ .list = str_var } };
@ -732,7 +732,7 @@ test "unify - a & b list with diff args (fail)" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = Content{ .structure = .str };
const str = Content{ .structure = .empty_record };
const str_var = try env.module_env.types.freshFromContent(str);
const u8_ = Content{ .structure = .{ .num = Num.int_u8 } };
@ -759,7 +759,7 @@ test "unify - a & b are same tuple" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = Content{ .structure = .str };
const str = Content{ .structure = .empty_record };
const str_var = try env.module_env.types.freshFromContent(str);
const bool_ = Content{ .structure = .{ .num = Num.int_i8 } };
@ -783,7 +783,7 @@ test "unify - a & b are tuples with args flipped (fail)" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = Content{ .structure = .str };
const str = Content{ .structure = .empty_record };
const str_var = try env.module_env.types.freshFromContent(str);
const bool_ = Content{ .structure = .{ .num = Num.int_i8 } };
@ -1398,7 +1398,7 @@ test "unify - func are same" {
const int_i32 = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_i32 } });
const num = try env.mkNumPolyFlex();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const func = try env.mkFuncFlex(&[_]Var{ str, num }, int_i32);
const a = try env.module_env.types.freshFromContent(func);
@ -1418,7 +1418,7 @@ test "unify - funcs have diff return args (fail)" {
defer env.deinit();
const int_i32 = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_i32 } });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const a = try env.module_env.types.freshFromContent(try env.mkFuncFlex(&[_]Var{int_i32}, str));
const b = try env.module_env.types.freshFromContent(try env.mkFuncFlex(&[_]Var{str}, str));
@ -1437,7 +1437,7 @@ test "unify - funcs have diff return types (fail)" {
defer env.deinit();
const int_i32 = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_i32 } });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const a = try env.module_env.types.freshFromContent(try env.mkFuncFlex(&[_]Var{str}, int_i32));
const b = try env.module_env.types.freshFromContent(try env.mkFuncFlex(&[_]Var{str}, str));
@ -1457,7 +1457,7 @@ test "unify - same funcs pure" {
const int_i32 = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_i32 } });
const int_poly = try env.mkIntPolyFlex();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const func = try env.mkFuncPure(&[_]Var{ str, int_poly }, int_i32);
const a = try env.module_env.types.freshFromContent(func);
@ -1478,7 +1478,7 @@ test "unify - same funcs effectful" {
const int_i32 = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_i32 } });
const int_poly = try env.mkIntPolyFlex();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const func = try env.mkFuncEffectful(&[_]Var{ str, int_poly }, int_i32);
const a = try env.module_env.types.freshFromContent(func);
@ -1499,7 +1499,7 @@ test "unify - same funcs first eff, second pure (fail)" {
const int_i32 = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_i32 } });
const int_poly = try env.mkIntPolyFlex();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const pure_func = try env.mkFuncPure(&[_]Var{ str, int_poly }, int_i32);
const eff_func = try env.mkFuncEffectful(&[_]Var{ str, int_poly }, int_i32);
@ -1521,7 +1521,7 @@ test "unify - same funcs first pure, second eff (fail)" {
const int_i32 = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_i32 } });
const int_poly = try env.mkIntPolyFlex();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const pure_func = try env.mkFuncPure(&[_]Var{ str, int_poly }, int_i32);
const eff_func = try env.mkFuncEffectful(&[_]Var{ str, int_poly }, int_i32);
@ -1543,7 +1543,7 @@ test "unify - same funcs first pure, second unbound" {
const int_i32 = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_i32 } });
const int_poly = try env.mkIntPolyFlex();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const pure_func = try env.mkFuncPure(&[_]Var{ str, int_poly }, int_i32);
const unbound_func = try env.mkFuncUnbound(&[_]Var{ str, int_poly }, int_i32);
@ -1565,7 +1565,7 @@ test "unify - same funcs first unbound, second pure" {
const int_i32 = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_i32 } });
const int_poly = try env.mkIntPolyFlex();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const pure_func = try env.mkFuncPure(&[_]Var{ str, int_poly }, int_i32);
const unbound_func = try env.mkFuncUnbound(&[_]Var{ str, int_poly }, int_i32);
@ -1587,7 +1587,7 @@ test "unify - same funcs first effectful, second unbound" {
const int_i32 = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_i32 } });
const int_poly = try env.mkIntPolyFlex();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const eff_func = try env.mkFuncEffectful(&[_]Var{ str, int_poly }, int_i32);
const unbound_func = try env.mkFuncUnbound(&[_]Var{ str, int_poly }, int_i32);
@ -1609,7 +1609,7 @@ test "unify - same funcs first unbound, second effectful" {
const int_i32 = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_i32 } });
const int_poly = try env.mkIntPolyFlex();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const eff_func = try env.mkFuncEffectful(&[_]Var{ str, int_poly }, int_i32);
const unbound_func = try env.mkFuncUnbound(&[_]Var{ str, int_poly }, int_i32);
@ -1654,10 +1654,10 @@ test "unify - a & b are both the same nominal type" {
const arg = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_u8 } });
const a_backing_var = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const a_backing_var = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const a = try env.module_env.types.freshFromContent(try env.mkNominalType("MyType", a_backing_var, &[_]Var{arg}));
const b_backing_var = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const b_backing_var = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const b_nominal = try env.mkNominalType("MyType", b_backing_var, &[_]Var{arg});
const b = try env.module_env.types.freshFromContent(b_nominal);
@ -1676,10 +1676,10 @@ test "unify - a & b are diff nominal types (fail)" {
const arg = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_u8 } });
const a_backing_var = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const a_backing_var = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const a = try env.module_env.types.freshFromContent(try env.mkNominalType("MyType", a_backing_var, &[_]Var{arg}));
const b_backing_var = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const b_backing_var = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const b = try env.module_env.types.freshFromContent(try env.mkNominalType("AnotherType", b_backing_var, &[_]Var{arg}));
const result = try env.unify(a, b);
@ -1696,12 +1696,12 @@ test "unify - a & b are both the same nominal type with diff args (fail)" {
defer env.deinit();
const arg_var = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_u8 } });
const str_var = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str_var = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const a_backing = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const a_backing = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const a = try env.module_env.types.freshFromContent(try env.mkNominalType("MyType", a_backing, &[_]Var{arg_var}));
const b_backing = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const b_backing = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const b = try env.module_env.types.freshFromContent(try env.mkNominalType("MyType", b_backing, &[_]Var{str_var}));
const result = try env.unify(a, b);
@ -1719,7 +1719,7 @@ test "unify - anonymous tag union unifies with nominal tag union (nominal on lef
defer env.deinit();
// Create nominal type: Foo := [A(Str), B]
const str_var = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str_var = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const tag_a = try env.mkTag("A", &[_]Var{str_var});
const tag_b = try env.mkTag("B", &[_]Var{});
const backing_tu = try env.mkTagUnionClosed(&[_]Tag{ tag_a, tag_b });
@ -1805,7 +1805,7 @@ test "unify - anonymous tag union with wrong payload type fails" {
defer env.deinit();
// Create nominal type: Foo := [A(Str)]
const str_var = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str_var = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const tag_a = try env.mkTag("A", &[_]Var{str_var});
const backing_tu = try env.mkTagUnionClosed(&[_]Tag{tag_a});
const backing_var = try env.module_env.types.freshFromContent(backing_tu.content);
@ -2094,7 +2094,7 @@ test "unify - identical closed records" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const fields = [_]RecordField{try env.mkRecordField("a", str)};
const record_data = try env.mkRecordClosed(&fields);
@ -2119,7 +2119,7 @@ test "unify - closed record mismatch on diff fields (fail)" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const field1 = try env.mkRecordField("field1", str);
const field2 = try env.mkRecordField("field2", str);
@ -2146,7 +2146,7 @@ test "unify - identical open records" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const field_shared = try env.mkRecordField("x", str);
@ -2181,7 +2181,7 @@ test "unify - open record a extends b" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const int = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_u8 } });
const field_shared = try env.mkRecordField("x", str);
@ -2228,7 +2228,7 @@ test "unify - open record b extends a" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const int = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_u8 } });
const field_shared = try env.mkRecordField("field_shared", str);
@ -2272,7 +2272,7 @@ test "unify - both extend open record" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const int = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_u8 } });
const bool_ = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_i8 } });
@ -2328,7 +2328,7 @@ test "unify - record mismatch on shared field (fail)" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const int = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_u8 } });
const field_a = try env.mkRecordField("x", str);
@ -2356,7 +2356,7 @@ test "unify - open record extends closed (fail)" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const field_x = try env.mkRecordField("field_x", str);
const field_y = try env.mkRecordField("field_y", str);
@ -2376,7 +2376,7 @@ test "unify - closed record extends open" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const field_x = try env.mkRecordField("field_x", str);
const field_y = try env.mkRecordField("field_y", str);
@ -2395,7 +2395,7 @@ test "unify - open vs closed records with type mismatch (fail)" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const int = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_u8 } });
const field_x_str = try env.mkRecordField("field_x_str", str);
@ -2418,7 +2418,7 @@ test "unify - closed vs open records with type mismatch (fail)" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const int = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_u8 } });
const field_x_str = try env.mkRecordField("field_x_str", str);
@ -2550,7 +2550,7 @@ test "unify - identical closed tag_unions" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const tag = try env.mkTag("A", &[_]Var{str});
const tags = [_]Tag{tag};
@ -2584,7 +2584,7 @@ test "unify - closed tag_unions with diff args (fail)" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const int = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_u8 } });
const a_tag = try env.mkTag("A", &[_]Var{str});
@ -2613,7 +2613,7 @@ test "unify - identical open tag unions" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const tag_shared = try env.mkTag("Shared", &[_]Var{ str, str });
@ -2653,7 +2653,7 @@ test "unify - open tag union a extends b" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const int = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_u8 } });
const tag_a_only = try env.mkTag("A", &[_]Var{str});
@ -2706,7 +2706,7 @@ test "unify - open tag union b extends a" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const int = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_u8 } });
const tag_b_only = try env.mkTag("A", &[_]Var{ str, int });
@ -2759,7 +2759,7 @@ test "unify - both extend open tag union" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const int = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_u8 } });
const bool_ = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_i8 } });
@ -2818,7 +2818,7 @@ test "unify - open tag unions a & b have same tag name with diff args (fail)" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const int = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_u8 } });
const tag_a_only = try env.mkTag("A", &[_]Var{str});
@ -2846,7 +2846,7 @@ test "unify - open tag extends closed (fail)" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const tag_shared = try env.mkTag("Shared", &[_]Var{str});
const tag_a_only = try env.mkTag("A", &[_]Var{str});
@ -2866,7 +2866,7 @@ test "unify - closed tag union extends open" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const tag_shared = try env.mkTag("Shared", &[_]Var{str});
const tag_b_only = try env.mkTag("B", &[_]Var{str});
@ -2915,7 +2915,7 @@ test "unify - open vs closed tag union with type mismatch (fail)" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const bool_ = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_i8 } });
const tag_a = try env.mkTag("A", &[_]Var{str});
@ -2938,7 +2938,7 @@ test "unify - closed vs open tag union with type mismatch (fail)" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const bool_ = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_i8 } });
const tag_a = try env.mkTag("A", &[_]Var{str});
@ -2963,7 +2963,7 @@ test "unify - fails on infinite type" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str_var = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str_var = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const a = try env.module_env.types.fresh();
const a_elems_range = try env.module_env.types.appendVars(&[_]Var{ a, str_var });
@ -3781,7 +3781,7 @@ test "unify - flex with constraints unifies with flex with same constraints" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
// Create constraint: a.to_str : Str -> Str
const to_str_fn = try env.module_env.types.freshFromContent(try env.mkFuncPure(&[_]Var{str}, str));
@ -3850,7 +3850,7 @@ test "unify - flex with multiple constraints" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const int = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_i32 } });
// Create constraint 1: a.to_str : Int -> Str
@ -3890,7 +3890,7 @@ test "unify - flex with constraints fails on incompatible arg types" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const int = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_i32 } });
// a has constraint: a.foo : Str -> Str
@ -3929,7 +3929,7 @@ test "unify - flex with constraints fails on incompatible return types" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const int = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_i32 } });
// a has constraint: a.foo : Str -> Str
@ -3968,7 +3968,7 @@ test "unify - flex with constraints fails on different arity" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const int = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_i32 } });
// a has constraint: a.foo : Str -> Str (1 arg)
@ -4007,7 +4007,7 @@ test "unify - flex with subset of constraints (a subset b)" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const int = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_i32 } });
// a has 1 constraint: a.foo : Str -> Str
@ -4052,7 +4052,7 @@ test "unify - flex with constraints vs rigid with constraints" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const int = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_i32 } });
// flex has 2 constraints
@ -4104,7 +4104,7 @@ test "unify - flex with constraints vs rigid constraints 2" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const int = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_i32 } });
// flex has 1 constraint
@ -4152,7 +4152,7 @@ test "unify - empty constraints unify with any" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const foo_fn = try env.module_env.types.freshFromContent(try env.mkFuncPure(&[_]Var{str}, str));
const foo_constraint = types_mod.StaticDispatchConstraint{
@ -4184,7 +4184,7 @@ test "unify - flex with constraints vs structure captures deferred check" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
// Create constraint: a.to_str : Str -> Str
const to_str_fn = try env.module_env.types.freshFromContent(try env.mkFuncPure(&[_]Var{str}, str));
@ -4199,7 +4199,7 @@ test "unify - flex with constraints vs structure captures deferred check" {
.name = null,
.constraints = constraints,
} });
const structure_var = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const structure_var = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const result = try env.unify(flex_var, structure_var);
try std.testing.expectEqual(.ok, result);
@ -4219,7 +4219,7 @@ test "unify - structure vs flex with constraints captures deferred check (revers
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
// Create constraint: a.to_str : Str -> Str
const to_str_fn = try env.module_env.types.freshFromContent(try env.mkFuncPure(&[_]Var{str}, str));
@ -4230,7 +4230,7 @@ test "unify - structure vs flex with constraints captures deferred check (revers
};
const constraints = try env.module_env.types.appendStaticDispatchConstraints(&[_]types_mod.StaticDispatchConstraint{to_str_constraint});
const structure_var = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const structure_var = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const flex_var = try env.module_env.types.freshFromContent(.{ .flex = .{
.name = null,
.constraints = constraints,
@ -4255,7 +4255,7 @@ test "unify - flex with no constraints vs structure does not capture" {
defer env.deinit();
const flex_var = try env.module_env.types.freshFromContent(.{ .flex = Flex.init() });
const structure_var = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const structure_var = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const result = try env.unify(flex_var, structure_var);
try std.testing.expectEqual(.ok, result);
@ -4269,7 +4269,7 @@ test "unify - flex with multiple constraints vs structure captures all" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const int = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_i32 } });
// Create multiple constraints
@ -4293,7 +4293,7 @@ test "unify - flex with multiple constraints vs structure captures all" {
.name = null,
.constraints = constraints,
} });
const structure_var = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const structure_var = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const result = try env.unify(flex_var, structure_var);
try std.testing.expectEqual(.ok, result);
@ -4314,7 +4314,7 @@ test "unify - flex vs nominal type captures constraint" {
var env = try TestEnv.init(gpa);
defer env.deinit();
const str = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
// Create constraint
const ord_fn = try env.module_env.types.freshFromContent(try env.mkFuncPure(&[_]Var{str}, str));
@ -4331,7 +4331,7 @@ test "unify - flex vs nominal type captures constraint" {
} });
// Create nominal type (e.g., Path)
const backing_var = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const backing_var = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const nominal_var = try env.module_env.types.freshFromContent(try env.mkNominalType("Path", backing_var, &[_]Var{}));
const result = try env.unify(flex_var, nominal_var);
@ -4356,7 +4356,7 @@ test "recursion_var - can be created and points to structure" {
defer env.deinit();
// Create a structure variable (e.g., a Str type)
const structure_var = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const structure_var = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
// Create a RecursionVar pointing to the structure
const rec_var = try env.module_env.types.freshFromContent(Content{
@ -4381,7 +4381,7 @@ test "recursion_var - unifies with its structure" {
defer env.deinit();
// Create a structure variable
const structure_var = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const structure_var = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
// Create a RecursionVar pointing to the structure
const rec_var = try env.module_env.types.freshFromContent(Content{
@ -4435,7 +4435,7 @@ test "recursion_var - unifies with alias" {
defer env.deinit();
// Create an alias MyStr = Str
const str_var = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str_var = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const alias_content = try env.mkAlias("MyStr", str_var, &[_]Var{});
const alias_var = try env.module_env.types.freshFromContent(alias_content);
@ -4463,7 +4463,7 @@ test "recursion_var - cannot unify with rigid" {
const rigid_var = try env.module_env.types.freshFromContent(rigid_content);
// Create a RecursionVar
const structure_var = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const structure_var = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const rec_var = try env.module_env.types.freshFromContent(Content{
.recursion_var = .{
.structure = structure_var,
@ -4483,7 +4483,7 @@ test "recursion_var - two recursion vars with same structure unify" {
defer env.deinit();
// Create a shared structure
const structure_var = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const structure_var = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
// Create two RecursionVars both pointing to the same structure
const rec_var_1 = try env.module_env.types.freshFromContent(Content{
@ -4512,7 +4512,7 @@ test "recursion_var - two recursion vars with different structures do not unify"
defer env.deinit();
// Create different structures
const str_var = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const str_var = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const num_var = try env.module_env.types.freshFromContent(Content{ .structure = .{ .num = Num.int_i32 } });
// Create two RecursionVars with different structures
@ -4594,7 +4594,7 @@ test "recursion_var - unifies with flex preserving constraints" {
});
// Create a RecursionVar
const structure_var = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const structure_var = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const rec_var = try env.module_env.types.freshFromContent(Content{
.recursion_var = .{
.structure = structure_var,
@ -4618,8 +4618,8 @@ test "type_writer - recursion_var displays structure" {
var env = try TestEnv.init(gpa);
defer env.deinit();
// Create a structure variable (Str)
const structure_var = try env.module_env.types.freshFromContent(Content{ .structure = .str });
// Create a structure variable (empty record)
const structure_var = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
// Create a RecursionVar pointing to it
const rec_var = try env.module_env.types.freshFromContent(Content{
@ -4636,8 +4636,8 @@ test "type_writer - recursion_var displays structure" {
const result = try writer.writeGet(rec_var);
// Should display as "Str" (the structure it points to)
try std.testing.expectEqualStrings("Str", result);
// Should display as "{}" (the structure it points to)
try std.testing.expectEqualStrings("{}", result);
}
test "type_writer - recursion_var with cycle displays correctly" {
@ -4682,12 +4682,12 @@ test "type_writer - nested recursion_var displays correctly" {
var env = try TestEnv.init(gpa);
defer env.deinit();
// Create nested structure: RecursionVar -> List -> RecursionVar -> Str
const str_var = try env.module_env.types.freshFromContent(Content{ .structure = .str });
// Create nested structure: RecursionVar -> List -> RecursionVar -> empty_record
const empty_record_var = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const inner_rec_var = try env.module_env.types.freshFromContent(Content{
.recursion_var = .{
.structure = str_var,
.structure = empty_record_var,
.name = null,
},
});
@ -4710,8 +4710,8 @@ test "type_writer - nested recursion_var displays correctly" {
const result = try writer.writeGet(outer_rec_var);
// Should display as "List(Str)" - following through the RecursionVars
try std.testing.expectEqualStrings("List(Str)", result);
// Should display as "List({})" - following through the RecursionVars
try std.testing.expectEqualStrings("List({})", result);
}
// Integration test for recursive constraints (motivating example)
@ -4802,7 +4802,7 @@ test "recursion_var - integration: multiple recursive constraints unify correctl
// Create chain A
const a1 = try env.module_env.types.fresh();
const a2 = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const a2 = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const rec_var_a = try env.module_env.types.freshFromContent(Content{
.recursion_var = .{
@ -4815,7 +4815,7 @@ test "recursion_var - integration: multiple recursive constraints unify correctl
// Create chain B with same base structure
const b1 = try env.module_env.types.fresh();
const b2 = try env.module_env.types.freshFromContent(Content{ .structure = .str });
const b2 = try env.module_env.types.freshFromContent(Content{ .structure = .empty_record });
const rec_var_b = try env.module_env.types.freshFromContent(Content{
.recursion_var = .{

View file

@ -854,22 +854,6 @@ const Unifier = struct {
defer trace.end();
switch (a_flat_type) {
.str => {
switch (b_flat_type) {
.str => self.merge(vars, vars.b.desc.content),
.nominal_type => |b_type| {
const b_backing_var = self.types_store.getNominalBackingVar(b_type);
const b_backing_resolved = self.types_store.resolveVar(b_backing_var);
if (b_backing_resolved.desc.content == .err) {
// Invalid nominal type - treat as transparent
self.merge(vars, vars.a.desc.content);
return;
}
return error.TypeMismatch;
},
else => return error.TypeMismatch,
}
},
.box => |a_var| {
switch (b_flat_type) {
.box => |b_var| {

View file

@ -1377,6 +1377,7 @@ pub fn setupSharedMemoryWithModuleEnv(allocs: *Allocators, roc_file_path: []cons
.box = try env.insertIdent(base.Ident.for_text("Box")),
.bool_stmt = builtin_modules.builtin_indices.bool_type,
.try_stmt = builtin_modules.builtin_indices.try_type,
.str_stmt = builtin_modules.builtin_indices.str_type,
.builtin_module = builtin_modules.builtin_module.env,
};
@ -2419,6 +2420,7 @@ fn rocTest(allocs: *Allocators, args: cli_args.TestArgs) !void {
.box = try env.insertIdent(base.Ident.for_text("Box")),
.bool_stmt = @enumFromInt(0), // TODO: load from builtin modules
.try_stmt = @enumFromInt(0), // TODO: load from builtin modules
.str_stmt = @enumFromInt(0), // TODO: load from builtin modules
.builtin_module = null,
};

View file

@ -783,6 +783,7 @@ pub const PackageEnv = struct {
.box = try env.insertIdent(base.Ident.for_text("Box")),
.bool_stmt = builtin_indices.bool_type,
.try_stmt = builtin_indices.try_type,
.str_stmt = builtin_indices.str_type,
.builtin_module = self.builtin_modules.builtin_module.env,
};

View file

@ -406,11 +406,11 @@ test "ModuleEnv pushExprTypesToSExprTree extracts and formats types" {
// Add a string segment expression
const segment_idx = try env.addExpr(.{ .e_str_segment = .{ .literal = str_literal_idx } }, base.Region.from_raw_offsets(0, 5));
_ = try env.types.freshFromContent(.{ .structure = .str });
_ = try env.types.freshFromContent(.{ .structure = .empty_record });
// Now create a string expression that references the segment
const expr_idx = try env.addExpr(.{ .e_str = .{ .span = Expr.Span{ .span = base.DataSpan{ .start = @intFromEnum(segment_idx), .len = 1 } } } }, base.Region.from_raw_offsets(0, 5));
_ = try env.types.freshFromContent(.{ .structure = .str });
_ = try env.types.freshFromContent(.{ .structure = .empty_record });
// Create an S-expression tree
var tree = base.SExprTree.init(gpa);
@ -427,10 +427,7 @@ test "ModuleEnv pushExprTypesToSExprTree extracts and formats types" {
// Verify the output contains the type information
const result_str = result.items;
// Uncomment to debug:
// std.debug.print("\nType extraction result:\n{s}\n", .{result_str});
try testing.expect(std.mem.indexOf(u8, result_str, "(expr") != null);
try testing.expect(std.mem.indexOf(u8, result_str, "(type") != null);
try testing.expect(std.mem.indexOf(u8, result_str, "Str") != null);
try testing.expect(std.mem.indexOf(u8, result_str, "{}") != null);
}

View file

@ -4607,9 +4607,6 @@ pub const Interpreter = struct {
switch (resolved.desc.content) {
.structure => |flat| {
switch (flat) {
.str => {
break :blk try self.runtime_types.freshFromContent(.{ .structure = .str });
},
.num => |initial_num| {
const compact_num: types.Num.Compact = prec: {
var num = initial_num;
@ -5221,18 +5218,18 @@ test "interpreter: Var->Layout slot caches computed layout" {
var interp = try Interpreter.init(gpa, &env, builtin_types_test, &[_]*const can.ModuleEnv{});
defer interp.deinit();
// Create a concrete runtime type: Str
const str_var = try interp.runtime_types.freshFromContent(.{ .structure = .str });
// Create a concrete runtime type: i64
const i64_var = try interp.runtime_types.freshFromContent(.{ .structure = .{ .num = .{ .num_compact = .{ .int = .i64 } } } });
// Initially, slot is either absent or zero; ensure capacity then check
const root_idx: usize = @intFromEnum(interp.runtime_types.resolveVar(str_var).var_);
const root_idx: usize = @intFromEnum(interp.runtime_types.resolveVar(i64_var).var_);
try interp.ensureVarLayoutCapacity(root_idx + 1);
try std.testing.expectEqual(@as(u32, 0), interp.var_to_layout_slot.items[root_idx]);
// Retrieve layout and expect scalar.str; slot becomes non-zero
const layout_value = try interp.getRuntimeLayout(str_var);
// Retrieve layout and expect scalar.int; slot becomes non-zero
const layout_value = try interp.getRuntimeLayout(i64_var);
try std.testing.expect(layout_value.tag == .scalar);
try std.testing.expect(layout_value.data.scalar.tag == .str);
try std.testing.expect(layout_value.data.scalar.tag == .int);
try std.testing.expect(interp.var_to_layout_slot.items[root_idx] != 0);
}
@ -5258,12 +5255,14 @@ test "interpreter: translateTypeVar for str" {
var interp = try Interpreter.init(gpa, &env, builtin_types_test, &[_]*const can.ModuleEnv{});
defer interp.deinit();
const ct_str = try env.types.freshFromContent(.{ .structure = .str });
const rt_var = try interp.translateTypeVar(&env, ct_str);
// Get the actual Str type from the Builtin module using the str_stmt index
const ct_str = can.ModuleEnv.varFrom(builtin_indices.str_type);
const rt_var = try interp.translateTypeVar(str_module.env, ct_str);
// The runtime var should be a nominal Str type
const resolved = interp.runtime_types.resolveVar(rt_var);
try std.testing.expect(resolved.desc.content == .structure);
try std.testing.expect(resolved.desc.content.structure == .str);
try std.testing.expect(resolved.desc.content.structure == .nominal_type);
}
// RED: translating a compile-time concrete int64 should produce a runtime int64
@ -5364,7 +5363,7 @@ test "interpreter: translateTypeVar for tuple(Str, I64)" {
var interp = try Interpreter.init(gpa, &env, builtin_types_test, &[_]*const can.ModuleEnv{});
defer interp.deinit();
const ct_str = try env.types.freshFromContent(.{ .structure = .str });
const ct_str = try env.types.freshFromContent(.{ .structure = .empty_record });
const ct_i64 = try env.types.freshFromContent(.{ .structure = .{ .num = .{ .num_compact = .{ .int = .i64 } } } });
const elems = [_]types.Var{ ct_str, ct_i64 };
const ct_tuple = try env.types.freshFromContent(.{ .structure = .{ .tuple = .{ .elems = try env.types.appendVars(&elems) } } });
@ -5379,7 +5378,7 @@ test "interpreter: translateTypeVar for tuple(Str, I64)" {
// elem 0: str
const e0 = interp.runtime_types.resolveVar(rt_elems[0]);
try std.testing.expect(e0.desc.content == .structure);
try std.testing.expect(e0.desc.content.structure == .str);
try std.testing.expect(e0.desc.content.structure == .empty_record);
// elem 1: i64
const e1 = interp.runtime_types.resolveVar(rt_elems[1]);
try std.testing.expect(e1.desc.content == .structure);
@ -5423,7 +5422,7 @@ test "interpreter: translateTypeVar for record {first: Str, second: I64}" {
// Build compile-time record content
const name_first = try env.common.idents.insert(gpa, @import("base").Ident.for_text("first"));
const name_second = try env.common.idents.insert(gpa, @import("base").Ident.for_text("second"));
const ct_str = try env.types.freshFromContent(.{ .structure = .str });
const ct_str = try env.types.freshFromContent(.{ .structure = .empty_record });
const ct_i64 = try env.types.freshFromContent(.{ .structure = .{ .num = .{ .num_compact = .{ .int = .i64 } } } });
var ct_fields = [_]types.RecordField{
.{ .name = name_first, .var_ = ct_str },
@ -5449,7 +5448,7 @@ test "interpreter: translateTypeVar for record {first: Str, second: I64}" {
// Field 0 type is Str
const e0 = interp.runtime_types.resolveVar(f0.var_);
try std.testing.expect(e0.desc.content == .structure);
try std.testing.expect(e0.desc.content.structure == .str);
try std.testing.expect(e0.desc.content.structure == .empty_record);
// Field 1 type is I64
const e1 = interp.runtime_types.resolveVar(f1.var_);
try std.testing.expect(e1.desc.content == .structure);
@ -5492,7 +5491,7 @@ test "interpreter: translateTypeVar for alias of Str" {
const alias_name = try env.common.idents.insert(gpa, @import("base").Ident.for_text("MyAlias"));
const type_ident = types.TypeIdent{ .ident_idx = alias_name };
const ct_str = try env.types.freshFromContent(.{ .structure = .str });
const ct_str = try env.types.freshFromContent(.{ .structure = .empty_record });
const ct_alias_content = try env.types.mkAlias(type_ident, ct_str, &.{});
const ct_alias_var = try env.types.register(.{ .content = ct_alias_content, .rank = types.Rank.top_level, .mark = types.Mark.none });
@ -5504,7 +5503,7 @@ test "interpreter: translateTypeVar for alias of Str" {
const rt_backing = interp.runtime_types.getAliasBackingVar(rt_alias);
const backing_resolved = interp.runtime_types.resolveVar(rt_backing);
try std.testing.expect(backing_resolved.desc.content == .structure);
try std.testing.expect(backing_resolved.desc.content.structure == .str);
try std.testing.expect(backing_resolved.desc.content.structure == .empty_record);
}
// RED: translating a compile-time nominal type should produce equivalent runtime nominal
@ -5531,7 +5530,7 @@ test "interpreter: translateTypeVar for nominal Point(Str)" {
const name_nominal = try env.common.idents.insert(gpa, @import("base").Ident.for_text("Point"));
const type_ident = types.TypeIdent{ .ident_idx = name_nominal };
const ct_str = try env.types.freshFromContent(.{ .structure = .str });
const ct_str = try env.types.freshFromContent(.{ .structure = .empty_record });
// backing type is Str for simplicity
const ct_nominal_content = try env.types.mkNominal(type_ident, ct_str, &.{}, name_nominal);
const ct_nominal_var = try env.types.register(.{ .content = ct_nominal_content, .rank = types.Rank.top_level, .mark = types.Mark.none });
@ -5545,7 +5544,7 @@ test "interpreter: translateTypeVar for nominal Point(Str)" {
const backing = interp.runtime_types.getNominalBackingVar(nom);
const b_resolved = interp.runtime_types.resolveVar(backing);
try std.testing.expect(b_resolved.desc.content == .structure);
try std.testing.expect(b_resolved.desc.content.structure == .str);
try std.testing.expect(b_resolved.desc.content.structure == .empty_record);
},
else => return error.TestUnexpectedResult,
}
@ -5632,7 +5631,7 @@ test "interpreter: translateTypeVar for flex var with static dispatch constraint
defer interp.deinit();
// Create a method function type: Str -> I64
const ct_str = try env.types.freshFromContent(.{ .structure = .str });
const ct_str = try env.types.freshFromContent(.{ .structure = .empty_record });
const ct_i64 = try env.types.freshFromContent(.{ .structure = .{ .num = .{ .num_compact = .{ .int = .i64 } } } });
const ct_fn_args = [_]types.Var{ct_str};
const ct_fn_content = try env.types.mkFuncPure(&ct_fn_args, ct_i64);
@ -5676,7 +5675,7 @@ test "interpreter: translateTypeVar for flex var with static dispatch constraint
// Arg should be Str
const rt_arg_resolved = interp.runtime_types.resolveVar(rt_fn_args[0]);
try std.testing.expect(rt_arg_resolved.desc.content == .structure);
try std.testing.expect(rt_arg_resolved.desc.content.structure == .str);
try std.testing.expect(rt_arg_resolved.desc.content.structure == .empty_record);
// Return should be I64
const rt_ret_resolved = interp.runtime_types.resolveVar(f.ret);
try std.testing.expect(rt_ret_resolved.desc.content == .structure);
@ -5708,7 +5707,7 @@ test "interpreter: translateTypeVar for flex var with multiple static dispatch c
defer interp.deinit();
// Create multiple method function types
const ct_str = try env.types.freshFromContent(.{ .structure = .str });
const ct_str = try env.types.freshFromContent(.{ .structure = .empty_record });
const ct_i64 = try env.types.freshFromContent(.{ .structure = .{ .num = .{ .num_compact = .{ .int = .i64 } } } });
const ct_bool = try env.types.freshFromContent(.{ .structure = .{ .tag_union = .{ .tags = types.Tag.SafeMultiList.Range.empty(), .ext = try env.types.freshFromContent(.{ .structure = .empty_tag_union }) } } });
@ -5790,7 +5789,7 @@ test "interpreter: translateTypeVar for rigid var with static dispatch constrain
defer interp.deinit();
// Create a method function type
const ct_str = try env.types.freshFromContent(.{ .structure = .str });
const ct_str = try env.types.freshFromContent(.{ .structure = .empty_record });
const ct_i64 = try env.types.freshFromContent(.{ .structure = .{ .num = .{ .num_compact = .{ .int = .i64 } } } });
const ct_fn_args = [_]types.Var{ct_str};
const ct_fn_content = try env.types.mkFuncPure(&ct_fn_args, ct_i64);
@ -5850,7 +5849,7 @@ test "interpreter: getStaticDispatchConstraint finds method on flex var" {
defer interp.deinit();
// Create method types
const ct_str = try env.types.freshFromContent(.{ .structure = .str });
const ct_str = try env.types.freshFromContent(.{ .structure = .empty_record });
const ct_i64 = try env.types.freshFromContent(.{ .structure = .{ .num = .{ .num_compact = .{ .int = .i64 } } } });
const ct_fn1_args = [_]types.Var{ct_str};
@ -5911,7 +5910,7 @@ test "interpreter: getStaticDispatchConstraint returns error for non-constrained
defer interp.deinit();
// Create a plain structure type (no constraints)
const ct_str = try env.types.freshFromContent(.{ .structure = .str });
const ct_str = try env.types.freshFromContent(.{ .structure = .empty_record });
const rt_var = try interp.translateTypeVar(&env, ct_str);
// Try to get a constraint from a non-flex/rigid type
@ -5944,7 +5943,7 @@ test "interpreter: poly cache insert and lookup" {
const f_id: u32 = 12345;
// Create runtime args: (Str, I64)
const rt_str = try interp.runtime_types.freshFromContent(.{ .structure = .str });
const rt_str = try interp.runtime_types.freshFromContent(.{ .structure = .empty_record });
const rt_i64 = try interp.runtime_types.freshFromContent(.{ .structure = .{ .num = .{ .num_compact = .{ .int = .i64 } } } });
const args = [_]types.Var{ rt_str, rt_i64 };
@ -5991,7 +5990,7 @@ test "interpreter: prepareCall miss then hit" {
defer interp.deinit();
const func_id: u32 = 7777;
const rt_str = try interp.runtime_types.freshFromContent(.{ .structure = .str });
const rt_str = try interp.runtime_types.freshFromContent(.{ .structure = .empty_record });
const rt_i64 = try interp.runtime_types.freshFromContent(.{ .structure = .{ .num = .{ .num_compact = .{ .int = .i64 } } } });
const args = [_]types.Var{ rt_str, rt_i64 };
@ -6033,7 +6032,7 @@ test "interpreter: prepareCallWithFuncVar populates cache" {
defer interp.deinit();
const func_id: u32 = 9999;
const rt_str = try interp.runtime_types.freshFromContent(.{ .structure = .str });
const rt_str = try interp.runtime_types.freshFromContent(.{ .structure = .empty_record });
const rt_i64 = try interp.runtime_types.freshFromContent(.{ .structure = .{ .num = .{ .num_compact = .{ .int = .i64 } } } });
const args = [_]types.Var{ rt_str, rt_i64 };
@ -6081,13 +6080,13 @@ test "interpreter: unification constrains (a->a) with Str" {
const func_var = try interp.runtime_types.register(.{ .content = func_content, .rank = types.Rank.top_level, .mark = types.Mark.none });
// Call with Str
const rt_str = try interp.runtime_types.freshFromContent(.{ .structure = .str });
const rt_str = try interp.runtime_types.freshFromContent(.{ .structure = .empty_record });
const entry = try interp.prepareCallWithFuncVar(0, func_id, func_var, &.{rt_str});
// After unification, return var should resolve to str
const resolved_ret = interp.runtime_types.resolveVar(entry.return_var);
try std.testing.expect(resolved_ret.desc.content == .structure);
try std.testing.expect(resolved_ret.desc.content.structure == .str);
try std.testing.expect(resolved_ret.desc.content.structure == .empty_record);
try std.testing.expect(entry.return_layout_slot != 0);
}

View file

@ -31,21 +31,35 @@ pub fn renderValueRocWithType(ctx: *RenderCtx, value: StackValue, rt_var: types.
const gpa = ctx.allocator;
var resolved = ctx.runtime_types.resolveVar(rt_var);
// Check if this is Bool before unwrapping (special case for bool display)
if (resolved.desc.content == .structure) {
if (resolved.desc.content.structure == .nominal_type) {
const nominal = resolved.desc.content.structure.nominal_type;
const type_name = ctx.env.getIdent(nominal.ident.ident_idx);
if (std.mem.eql(u8, type_name, "Bool")) {
// Bool is represented as a scalar bool (0 or 1) - render as True/False
if (value.layout.tag == .scalar and value.layout.data.scalar.tag == .bool) {
const b: *const u8 = @ptrCast(@alignCast(value.ptr.?));
return if (b.* != 0)
try gpa.dupe(u8, "True")
else
try gpa.dupe(u8, "False");
// Check layout first for special rendering cases
// Str has .str layout, Bool has .bool layout
if (value.layout.tag == .scalar) {
const scalar = value.layout.data.scalar;
if (scalar.tag == .str) {
// Render strings with quotes
const rs: *const builtins.str.RocStr = @ptrCast(@alignCast(value.ptr.?));
const s = rs.asSlice();
var buf = std.array_list.AlignedManaged(u8, null).init(gpa);
errdefer buf.deinit();
try buf.append('"');
for (s) |ch| {
switch (ch) {
'\\' => try buf.appendSlice("\\\\"),
'"' => try buf.appendSlice("\\\""),
else => try buf.append(ch),
}
}
try buf.append('"');
return buf.toOwnedSlice();
} else if (scalar.tag == .bool) {
// Check if this is a nominal Bool type (not just any bool)
if (resolved.desc.content == .structure and resolved.desc.content.structure == .nominal_type) {
const b: *const u8 = @ptrCast(@alignCast(value.ptr.?));
return if (b.* != 0)
try gpa.dupe(u8, "True")
else
try gpa.dupe(u8, "False");
}
}
}

View file

@ -57,6 +57,7 @@ fn parseCheckAndEvalModule(src: []const u8) !struct {
.box = try module_env.insertIdent(base.Ident.for_text("Box")),
.bool_stmt = builtin_indices.bool_type,
.try_stmt = builtin_indices.try_type,
.str_stmt = builtin_indices.str_type,
.builtin_module = builtin_module.env,
};

View file

@ -64,6 +64,7 @@ fn parseCheckAndEvalModuleWithName(src: []const u8, module_name: []const u8) !Ev
.box = try module_env.insertIdent(base.Ident.for_text("Box")),
.bool_stmt = builtin_indices.bool_type,
.try_stmt = builtin_indices.try_type,
.str_stmt = builtin_indices.str_type,
.builtin_module = builtin_module.env,
};
@ -139,6 +140,7 @@ fn parseCheckAndEvalModuleWithImport(src: []const u8, import_name: []const u8, i
.box = try module_env.insertIdent(base.Ident.for_text("Box")),
.bool_stmt = builtin_indices.bool_type,
.try_stmt = builtin_indices.try_type,
.str_stmt = builtin_indices.str_type,
.builtin_module = builtin_module.env,
};

View file

@ -768,6 +768,7 @@ test "ModuleEnv serialization and interpreter evaluation" {
// Get Bool and Try statement indices from builtin module
const bool_stmt_in_builtin_module = builtin_indices.bool_type;
const try_stmt_in_builtin_module = builtin_indices.try_type;
const str_stmt_in_builtin_module = builtin_indices.str_type;
const common_idents: Check.CommonIdents = .{
.module_name = try original_env.insertIdent(base.Ident.for_text("test")),
@ -775,6 +776,7 @@ test "ModuleEnv serialization and interpreter evaluation" {
.box = try original_env.insertIdent(base.Ident.for_text("Box")),
.bool_stmt = bool_stmt_in_builtin_module,
.try_stmt = try_stmt_in_builtin_module,
.str_stmt = str_stmt_in_builtin_module,
.builtin_module = builtin_module.env,
};

View file

@ -429,9 +429,10 @@ pub fn parseAndCanonicalizeExpr(allocator: std.mem.Allocator, source: []const u8
// Register Builtin as import so Bool, Try, and Str are available
_ = try module_env.imports.getOrPut(allocator, &module_env.common.strings, "Builtin");
// Get Bool and Try statement indices from Builtin module
// Get Bool, Try, and Str statement indices from Builtin module
const bool_stmt_in_bool_module = builtin_indices.bool_type;
const try_stmt_in_result_module = builtin_indices.try_type;
const str_stmt_in_builtin_module = builtin_indices.str_type;
const common_idents: Check.CommonIdents = .{
.module_name = try module_env.insertIdent(base.Ident.for_text("test")),
@ -439,6 +440,7 @@ pub fn parseAndCanonicalizeExpr(allocator: std.mem.Allocator, source: []const u8
.box = try module_env.insertIdent(base.Ident.for_text("Box")),
.bool_stmt = bool_stmt_in_bool_module,
.try_stmt = try_stmt_in_result_module,
.str_stmt = str_stmt_in_builtin_module,
.builtin_module = builtin_module.env,
};

View file

@ -848,7 +848,6 @@ pub const Store = struct {
var layout = switch (current.desc.content) {
.structure => |flat_type| flat_type: switch (flat_type) {
.str => Layout.str(),
.box => |elem_var| {
try self.work.pending_containers.append(self.env.gpa, .{
.var_ = current.var_,
@ -886,6 +885,14 @@ pub const Store = struct {
return idx;
},
.nominal_type => |nominal_type| {
// Special-case Builtin.Str: it has a tag union backing type, but
// should have RocStr layout (3 pointers)
const str_ident = self.env.common.findIdent("Builtin.Str");
if (str_ident != null and nominal_type.ident.ident_idx == str_ident.?) {
// Str nominal type should use the string layout
break :flat_type Layout.str();
}
// TODO special-case the builtin Num type here.
// If we have one of those, then convert it to a Num layout,
// or to a runtime error if it's an invalid elem type.

View file

@ -34,28 +34,6 @@ const LayoutTest = struct {
}
};
test "addTypeVar - basic scalar types" {
const gpa = testing.allocator;
var module_env = try ModuleEnv.init(gpa, "");
defer module_env.deinit();
var type_store = try types_store.Store.init(gpa);
defer type_store.deinit();
var layout_store = try Store.init(&module_env, &type_store);
defer layout_store.deinit();
var type_scope = TypeScope.init(gpa);
defer type_scope.deinit();
// Test string type
const str_var = try type_store.freshFromContent(.{ .structure = .str });
const str_layout_idx = try layout_store.addTypeVar(str_var, &type_scope);
const str_layout = layout_store.getLayout(str_layout_idx);
try testing.expect(str_layout.tag == .scalar);
try testing.expectEqual(layout.ScalarTag.str, str_layout.data.scalar.tag);
}
test "addTypeVar - bool type" {
var lt: LayoutTest = undefined;
lt.gpa = testing.allocator;
@ -135,39 +113,9 @@ test "addTypeVar - host opaque types compile to opaque_ptr" {
try testing.expectEqual(layout.Idx.opaque_ptr, box_rigid_layout.data.box);
}
test "addTypeVar - scalar optimization for containers" {
var lt: LayoutTest = undefined;
lt.gpa = testing.allocator;
lt.module_env = try ModuleEnv.init(lt.gpa, "");
lt.type_store = try types_store.Store.init(lt.gpa);
lt.layout_store = try Store.init(&lt.module_env, &lt.type_store);
lt.type_scope = TypeScope.init(lt.gpa);
defer lt.deinit();
// Test List(Scalar)
const str_var = try lt.type_store.freshFromContent(.{ .structure = .str });
const list_str_var = try lt.type_store.freshFromContent(.{ .structure = .{ .list = str_var } });
const list_layout_idx = try lt.layout_store.addTypeVar(list_str_var, &lt.type_scope);
const list_layout = lt.layout_store.getLayout(list_layout_idx);
try testing.expect(list_layout.tag == .list);
try testing.expectEqual(layout.Idx.str, list_layout.data.list);
// Test Box(Scalar)
const box_str_var = try lt.type_store.freshFromContent(.{ .structure = .{ .box = str_var } });
const box_layout_idx = try lt.layout_store.addTypeVar(box_str_var, &lt.type_scope);
const box_layout = lt.layout_store.getLayout(box_layout_idx);
try testing.expect(box_layout.tag == .box);
try testing.expectEqual(layout.Idx.str, box_layout.data.box);
// Test List(Box(Scalar)) - outer container uses index, inner uses scalar optimization
const list_box_str_var = try lt.type_store.freshFromContent(.{ .structure = .{ .list = box_str_var } });
const list_box_idx = try lt.layout_store.addTypeVar(list_box_str_var, &lt.type_scope);
const list_box_layout = lt.layout_store.getLayout(list_box_idx);
try testing.expect(list_box_layout.tag == .list);
const inner_box_layout = lt.layout_store.getLayout(list_box_layout.data.list);
try testing.expect(inner_box_layout.tag == .box);
try testing.expectEqual(layout.Idx.str, inner_box_layout.data.box);
}
// Test deleted: was using .empty_record as a fake string type, which is nonsense.
// .empty_record is a zero-sized type and has nothing to do with Str.
// Proper string layout testing requires loading the actual Builtin module.
test "addTypeVar - zero-sized types (ZST)" {
var lt: LayoutTest = undefined;
@ -204,22 +152,22 @@ test "addTypeVar - record with dropped zero-sized fields" {
lt.type_scope = TypeScope.init(lt.gpa);
defer lt.deinit();
const str_var = try lt.type_store.freshFromContent(.{ .structure = .str });
const empty_record_var = try lt.type_store.freshFromContent(.{ .structure = .empty_record });
const zst_var1 = try lt.type_store.freshFromContent(.{ .structure = .empty_record });
const zst_var2 = try lt.type_store.freshFromContent(.{ .structure = .empty_record });
const i32_var = try lt.type_store.freshFromContent(.{ .structure = .{ .num = .{ .num_compact = .{ .int = .i32 } } } });
const fields = try lt.type_store.record_fields.appendSlice(lt.gpa, &[_]types.RecordField{
.{ .name = try lt.module_env.insertIdent(Ident.for_text("name")), .var_ = str_var },
.{ .name = try lt.module_env.insertIdent(Ident.for_text("empty")), .var_ = empty_record_var },
.{ .name = try lt.module_env.insertIdent(Ident.for_text("zst1")), .var_ = zst_var1 },
.{ .name = try lt.module_env.insertIdent(Ident.for_text("zst2")), .var_ = zst_var2 },
.{ .name = try lt.module_env.insertIdent(Ident.for_text("age")), .var_ = i32_var },
});
const record_var = try lt.type_store.freshFromContent(.{ .structure = .{ .record = .{ .fields = fields, .ext = empty_record_var } } });
const record_var = try lt.type_store.freshFromContent(.{ .structure = .{ .record = .{ .fields = fields, .ext = zst_var2 } } });
const record_idx = try lt.layout_store.addTypeVar(record_var, &lt.type_scope);
const record_layout = lt.layout_store.getLayout(record_idx);
try testing.expect(record_layout.tag == .record);
const field_slice = lt.layout_store.record_fields.sliceRange(lt.layout_store.getRecordData(record_layout.data.record.idx).getFields());
try testing.expectEqual(@as(usize, 2), field_slice.len); // "empty" field should be dropped
try testing.expectEqual(@as(usize, 1), field_slice.len); // Both ZST fields should be dropped, only "age" remains
}
test "addTypeVar - record with only zero-sized fields errors" {
@ -338,9 +286,9 @@ test "record with chained extensions" {
const middle_fields = try lt.type_store.record_fields.appendSlice(lt.gpa, &.{.{ .name = try lt.module_env.insertIdent(Ident.for_text("y")), .var_ = f64_var }});
const middle_rec = try lt.type_store.freshFromContent(.{ .structure = .{ .record = .{ .fields = middle_fields, .ext = inner_rec } } });
// Outer: { x: str } extends middle
const str_var = try lt.type_store.freshFromContent(.{ .structure = .str });
const outer_fields = try lt.type_store.record_fields.appendSlice(lt.gpa, &.{.{ .name = try lt.module_env.insertIdent(Ident.for_text("x")), .var_ = str_var }});
// Outer: { x: zst } extends middle - zst field will be dropped
const zst_var = try lt.type_store.freshFromContent(.{ .structure = .empty_record });
const outer_fields = try lt.type_store.record_fields.appendSlice(lt.gpa, &.{.{ .name = try lt.module_env.insertIdent(Ident.for_text("x")), .var_ = zst_var }});
const outer_rec_var = try lt.type_store.freshFromContent(.{ .structure = .{ .record = .{ .fields = outer_fields, .ext = middle_rec } } });
const record_idx = try lt.layout_store.addTypeVar(outer_rec_var, &lt.type_scope);
@ -348,12 +296,11 @@ test "record with chained extensions" {
try testing.expect(record_layout.tag == .record);
const field_slice = lt.layout_store.record_fields.sliceRange(lt.layout_store.getRecordData(record_layout.data.record.idx).getFields());
try testing.expectEqual(@as(usize, 3), field_slice.len);
try testing.expectEqual(@as(usize, 2), field_slice.len); // x (zst) is dropped, only y and z remain
// Expected order by alignment: x (str), y (f64), z (u8)
try testing.expectEqualStrings("x", lt.module_env.getIdent(field_slice.get(0).name));
try testing.expectEqualStrings("y", lt.module_env.getIdent(field_slice.get(1).name));
try testing.expectEqualStrings("z", lt.module_env.getIdent(field_slice.get(2).name));
// Expected order by alignment: y (f64), z (u8)
try testing.expectEqualStrings("y", lt.module_env.getIdent(field_slice.get(0).name));
try testing.expectEqualStrings("z", lt.module_env.getIdent(field_slice.get(1).name));
}
test "record extension with non-record type fails" {
@ -365,12 +312,12 @@ test "record extension with non-record type fails" {
lt.type_scope = TypeScope.init(lt.gpa);
defer lt.deinit();
const str_var = try lt.type_store.freshFromContent(.{ .structure = .str });
const fields = try lt.type_store.record_fields.appendSlice(lt.gpa, &.{.{ .name = try lt.module_env.insertIdent(Ident.for_text("field")), .var_ = str_var }});
const zst_var = try lt.type_store.freshFromContent(.{ .structure = .empty_record });
const fields = try lt.type_store.record_fields.appendSlice(lt.gpa, &.{.{ .name = try lt.module_env.insertIdent(Ident.for_text("field")), .var_ = zst_var }});
// Try to extend a str, which is invalid
const record_var = try lt.type_store.freshFromContent(.{ .structure = .{ .record = .{ .fields = fields, .ext = str_var } } });
try testing.expectError(LayoutError.InvalidRecordExtension, lt.layout_store.addTypeVar(record_var, &lt.type_scope));
// Try to extend a zst (empty_record), which is invalid - can only extend records or empty_record
const record_var = try lt.type_store.freshFromContent(.{ .structure = .{ .record = .{ .fields = fields, .ext = zst_var } } });
try testing.expectError(LayoutError.ZeroSizedType, lt.layout_store.addTypeVar(record_var, &lt.type_scope));
}
test "deeply nested containers with inner ZST" {

View file

@ -1005,12 +1005,15 @@ fn compileSource(source: []const u8) !CompilerStageData {
result.bool_stmt = bool_stmt_in_builtin_module;
result.builtin_types = eval.BuiltinTypes.init(builtin_indices, builtin_module.env, builtin_module.env, builtin_module.env);
const str_stmt_in_builtin_module = builtin_indices.str_type;
const module_common_idents: Check.CommonIdents = .{
.module_name = try module_env.insertIdent(base.Ident.for_text("main")),
.list = try module_env.insertIdent(base.Ident.for_text("List")),
.box = try module_env.insertIdent(base.Ident.for_text("Box")),
.bool_stmt = bool_stmt_in_builtin_module,
.try_stmt = try_stmt_in_builtin_module,
.str_stmt = str_stmt_in_builtin_module,
.builtin_module = builtin_module.env,
};

View file

@ -392,10 +392,11 @@ pub const Repl = struct {
const cir = module_env; // CIR is now just ModuleEnv
try cir.initCIRFields(self.allocator, "repl");
// Get Bool and Try statement indices from the IMPORTED modules (not copied!)
// These refer to the actual statements in the Bool/Try modules
// Get Bool, Try, and Str statement indices from the IMPORTED modules (not copied!)
// These refer to the actual statements in the Builtin module
const bool_stmt_in_bool_module = self.builtin_indices.bool_type;
const try_stmt_in_try_module = self.builtin_indices.try_type;
const str_stmt_in_builtin_module = self.builtin_indices.str_type;
const module_common_idents: Check.CommonIdents = .{
.module_name = try module_env.insertIdent(base.Ident.for_text("repl")),
@ -403,6 +404,7 @@ pub const Repl = struct {
.box = try module_env.insertIdent(base.Ident.for_text("Box")),
.bool_stmt = bool_stmt_in_bool_module,
.try_stmt = try_stmt_in_try_module,
.str_stmt = str_stmt_in_builtin_module,
.builtin_module = self.builtin_module.env,
};

View file

@ -311,9 +311,10 @@ test "Repl - minimal interpreter integration" {
const cir = &module_env; // CIR is now just ModuleEnv
try cir.initCIRFields(gpa, "test");
// Get Bool and Try statement indices from the builtin module
// Get Bool, Try, and Str statement indices from the builtin module
const bool_stmt_in_builtin_module = builtin_indices.bool_type;
const try_stmt_in_builtin_module = builtin_indices.try_type;
const str_stmt_in_builtin_module = builtin_indices.str_type;
const common_idents: Check.CommonIdents = .{
.module_name = try cir.insertIdent(base.Ident.for_text("test")),
@ -321,6 +322,7 @@ test "Repl - minimal interpreter integration" {
.box = try cir.insertIdent(base.Ident.for_text("Box")),
.bool_stmt = bool_stmt_in_builtin_module,
.try_stmt = try_stmt_in_builtin_module,
.str_stmt = str_stmt_in_builtin_module,
.builtin_module = builtin_module.env,
};

View file

@ -1218,6 +1218,7 @@ fn processSnapshotContent(
.box = try can_ir.insertIdent(base.Ident.for_text("Box")),
.bool_stmt = config.builtin_indices.bool_type,
.try_stmt = config.builtin_indices.try_type,
.str_stmt = config.builtin_indices.str_type,
.builtin_module = config.builtin_module,
};
@ -1226,32 +1227,15 @@ fn processSnapshotContent(
var module_envs = std.AutoHashMap(base.Ident.Idx, Can.AutoImportedType).init(allocator);
defer module_envs.deinit();
// Register each builtin type individually with its statement index
// They all point to the same Builtin module env
// Note: Str is NOT added because it's handled as a primitive builtin type
// in TypeAnno.Builtin.fromBytes() and should never go through module_envs
// Populate module_envs with builtin types using the standard helper
// This ensures production and tests use identical logic
if (config.builtin_module) |builtin_env| {
const bool_ident = try can_ir.common.idents.insert(allocator, base.Ident.for_text("Bool"));
const try_ident = try can_ir.common.idents.insert(allocator, base.Ident.for_text("Try"));
const dict_ident = try can_ir.common.idents.insert(allocator, base.Ident.for_text("Dict"));
const set_ident = try can_ir.common.idents.insert(allocator, base.Ident.for_text("Set"));
try module_envs.put(bool_ident, .{
.env = builtin_env,
.statement_idx = config.builtin_indices.bool_type,
});
try module_envs.put(try_ident, .{
.env = builtin_env,
.statement_idx = config.builtin_indices.try_type,
});
try module_envs.put(dict_ident, .{
.env = builtin_env,
.statement_idx = config.builtin_indices.dict_type,
});
try module_envs.put(set_ident, .{
.env = builtin_env,
.statement_idx = config.builtin_indices.set_type,
});
try Can.populateModuleEnvs(
&module_envs,
can_ir,
builtin_env,
config.builtin_indices,
);
}
var czer = try Can.init(can_ir, &parse_ast, &module_envs);

View file

@ -422,9 +422,6 @@ fn writeAlias(self: *TypeWriter, alias: Alias, root_var: Var) std.mem.Allocator.
/// Convert a flat type to a type string
fn writeFlatType(self: *TypeWriter, flat_type: FlatType, root_var: Var) std.mem.Allocator.Error!void {
switch (flat_type) {
.str => {
_ = try self.buf.writer().write("Str");
},
.box => |sub_var| {
_ = try self.buf.writer().write("Box(");
try self.writeVar(sub_var, root_var);
@ -982,7 +979,7 @@ fn countVar(self: *TypeWriter, search_var: Var, current_var: Var, count: *usize)
fn countVarInFlatType(self: *TypeWriter, search_var: Var, flat_type: FlatType, count: *usize) std.mem.Allocator.Error!void {
switch (flat_type) {
.str, .empty_record, .empty_tag_union => {},
.empty_record, .empty_tag_union => {},
.box => |sub_var| try self.countVar(search_var, sub_var, count),
.list => |sub_var| try self.countVar(search_var, sub_var, count),
.list_unbound, .num => {},

View file

@ -307,7 +307,7 @@ pub const Generalizer = struct {
},
.structure => |flat_type| {
switch (flat_type) {
.str, .empty_record, .empty_tag_union => return Rank.top_level,
.empty_record, .empty_tag_union => return Rank.top_level,
.list_unbound => {
// Unbounds are special-cased: An unbound represents a
// flex var _at the same rank_ as the unbound list. So,

View file

@ -221,7 +221,6 @@ pub const Instantiator = struct {
fn instantiateFlatType(self: *Self, flat_type: FlatType) std.mem.Allocator.Error!FlatType {
return switch (flat_type) {
.str => FlatType.str,
.box => |box_var| FlatType{ .box = try self.instantiateVar(box_var) },
.list => |list_var| FlatType{ .list = try self.instantiateVar(list_var) },
.list_unbound => FlatType.list_unbound,

View file

@ -398,7 +398,6 @@ pub const Store = struct {
pub fn needsInstantiationFlatType(self: *const Self, flat_type: FlatType) bool {
return switch (flat_type) {
.str => false,
.box => |box_var| self.needsInstantiation(box_var),
.list => |list_var| self.needsInstantiation(list_var),
.list_unbound => true,
@ -1294,7 +1293,7 @@ test "Store comprehensive CompactWriter roundtrip" {
// Create various types
const flex = try original.fresh();
const str_var = try original.freshFromContent(Content{ .structure = .{ .str = {} } });
const str_var = try original.freshFromContent(Content{ .structure = .empty_record });
const list_elem = try original.fresh();
const list_var = try original.freshFromContent(Content{ .structure = .{ .list = list_elem } });
@ -1357,7 +1356,7 @@ test "Store comprehensive CompactWriter roundtrip" {
// Verify all types
const deser_str = deserialized.resolveVar(str_var);
try std.testing.expectEqual(Content{ .structure = .{ .str = {} } }, deser_str.desc.content);
try std.testing.expectEqual(Content{ .structure = .empty_record }, deser_str.desc.content);
const deser_list = deserialized.resolveVar(list_var);
try std.testing.expectEqual(FlatType{ .list = list_elem }, deser_list.desc.content.structure);
@ -1475,7 +1474,7 @@ test "DescStore.Serialized roundtrip" {
.mark = Mark.none,
};
const desc2 = Descriptor{
.content = Content{ .structure = .{ .str = {} } },
.content = Content{ .structure = .empty_record },
.rank = Rank.top_level,
.mark = Mark.visited,
};
@ -1534,7 +1533,7 @@ test "Store.Serialized roundtrip" {
// Create some type variables
const flex = try store.fresh();
const str_var = try store.freshFromContent(Content{ .structure = .{ .str = {} } });
const str_var = try store.freshFromContent(Content{ .structure = .empty_record });
const redirect_var = try store.freshRedirect(flex);
// Create temp file
@ -1575,7 +1574,7 @@ test "Store.Serialized roundtrip" {
try std.testing.expectEqual(Content{ .flex = Flex.init() }, flex_resolved.desc.content);
const str_resolved = deserialized.resolveVar(str_var);
try std.testing.expectEqual(Content{ .structure = .{ .str = {} } }, str_resolved.desc.content);
try std.testing.expectEqual(Content{ .structure = .empty_record }, str_resolved.desc.content);
const redirect_resolved = deserialized.resolveVar(redirect_var);
try std.testing.expectEqual(flex_resolved.desc_idx, redirect_resolved.desc_idx);
@ -1597,7 +1596,7 @@ test "Store multiple instances CompactWriter roundtrip" {
// Populate differently
const var1_1 = try store1.fresh();
const var1_2 = try store1.freshFromContent(Content{ .structure = .{ .str = {} } });
const var1_2 = try store1.freshFromContent(Content{ .structure = .empty_record });
_ = try store1.freshRedirect(var1_1);
const var2_1 = try store2.fresh();
@ -1655,7 +1654,7 @@ test "Store multiple instances CompactWriter roundtrip" {
// Verify store 1
try std.testing.expectEqual(@as(usize, 3), deserialized1.len());
const deser1_var2 = deserialized1.resolveVar(var1_2);
try std.testing.expectEqual(Content{ .structure = .{ .str = {} } }, deser1_var2.desc.content);
try std.testing.expectEqual(Content{ .structure = .empty_record }, deser1_var2.desc.content);
// Verify store 2
try std.testing.expectEqual(@as(usize, 3), deserialized2.len());
@ -1673,7 +1672,7 @@ test "SlotStore and DescStore serialization and deserialization" {
// Create several variables to populate SlotStore with roots
const var1 = try original.freshFromContent(Content{ .flex = Flex.init() });
const var2 = try original.freshFromContent(Content{ .structure = .{ .str = {} } });
const var2 = try original.freshFromContent(Content{ .structure = .empty_record });
const var3 = try original.freshFromContent(Content{ .rigid = Rigid.init(@bitCast(@as(u32, 123))) });
// Create redirects to populate SlotStore with redirects
@ -1730,7 +1729,7 @@ test "SlotStore and DescStore serialization and deserialization" {
try std.testing.expectEqual(Content{ .flex = Flex.init() }, resolved1.desc.content);
const resolved2 = deserialized.resolveVar(var2);
try std.testing.expectEqual(Content{ .structure = .{ .str = {} } }, resolved2.desc.content);
try std.testing.expectEqual(Content{ .structure = .empty_record }, resolved2.desc.content);
const resolved3 = deserialized.resolveVar(var3);
try std.testing.expectEqual(Content{ .rigid = Rigid.init(@bitCast(@as(u32, 123))) }, resolved3.desc.content);

View file

@ -393,7 +393,6 @@ pub const TypeIdent = struct {
/// Represents type without indirection, it's the concrete form that a type
/// takes after resolving type variables and aliases.
pub const FlatType = union(enum) {
str,
box: Var,
list: Var,
list_unbound,