mirror of
https://github.com/roc-lang/roc.git
synced 2025-12-23 08:48:03 +00:00
Share more code
This commit is contained in:
parent
0c214a044c
commit
35de2e6f75
3 changed files with 95 additions and 161 deletions
|
|
@ -4056,6 +4056,9 @@ fn handleRecursiveConstraint(
|
|||
/// process that, we then have to check `Test.to_str`.
|
||||
fn checkDeferredStaticDispatchConstraints(self: *Self, env: *Env) std.mem.Allocator.Error!void {
|
||||
var deferred_constraint_len = env.deferred_static_dispatch_constraints.items.items.len;
|
||||
if (deferred_constraint_len > 0) {
|
||||
std.debug.print("DEBUG: checkDeferredStaticDispatchConstraints called with {} constraints\n", .{deferred_constraint_len});
|
||||
}
|
||||
var deferred_constraint_index: usize = 0;
|
||||
while (deferred_constraint_index < deferred_constraint_len) : ({
|
||||
deferred_constraint_index += 1;
|
||||
|
|
@ -4196,6 +4199,12 @@ fn checkDeferredStaticDispatchConstraints(self: *Self, env: *Env) std.mem.Alloca
|
|||
const region = self.getRegionAt(deferred_constraint.var_);
|
||||
const type_name_bytes = self.cir.getIdent(nominal_type.ident.ident_idx);
|
||||
|
||||
std.debug.print("DEBUG Check: type_name_bytes='{s}', original_module_ident={}, original_env.module_name='{s}'\n", .{
|
||||
type_name_bytes,
|
||||
original_module_ident,
|
||||
original_env.module_name,
|
||||
});
|
||||
|
||||
// Iterate over the constraints
|
||||
const constraints = self.types.sliceStaticDispatchConstraints(deferred_constraint.constraints);
|
||||
for (constraints) |constraint| {
|
||||
|
|
@ -4211,27 +4220,10 @@ fn checkDeferredStaticDispatchConstraints(self: *Self, env: *Env) std.mem.Alloca
|
|||
|
||||
// Calculate the name of the static dispatch function
|
||||
//
|
||||
// For builtin types like "Builtin.Try", the type ident includes the module prefix,
|
||||
// but methods are registered as "Try.ok_or" not "Builtin.Builtin.Try.ok_or".
|
||||
// We need to strip the "Builtin." prefix for method lookup.
|
||||
//
|
||||
// TODO: This works for top-level types, but not for deeply
|
||||
// nested types like: MyModule.A.B.C.my_func
|
||||
self.static_dispatch_method_name_buf.clearRetainingCapacity();
|
||||
|
||||
const is_builtin_nested_type = original_module_ident == self.cir.builtin_module_ident and
|
||||
std.mem.startsWith(u8, type_name_bytes, "Builtin.");
|
||||
|
||||
if (is_builtin_nested_type) {
|
||||
// For nested builtin types like "Builtin.Try", strip the "Builtin." prefix
|
||||
// Method is registered as "Try.ok_or"
|
||||
const short_name = type_name_bytes[8..]; // Skip "Builtin."
|
||||
try self.static_dispatch_method_name_buf.print(
|
||||
self.gpa,
|
||||
"{s}.{s}",
|
||||
.{ short_name, constraint_fn_name_bytes },
|
||||
);
|
||||
} else if (std.mem.eql(u8, type_name_bytes, original_env.module_name)) {
|
||||
if (std.mem.eql(u8, type_name_bytes, original_env.module_name)) {
|
||||
try self.static_dispatch_method_name_buf.print(
|
||||
self.gpa,
|
||||
"{s}.{s}",
|
||||
|
|
|
|||
|
|
@ -770,12 +770,17 @@ pub const PackageEnv = struct {
|
|||
}
|
||||
}
|
||||
|
||||
fn doTypeCheck(self: *PackageEnv, module_id: ModuleId) !void {
|
||||
var st = &self.modules.items[module_id];
|
||||
var env = &st.env.?;
|
||||
|
||||
/// Standalone type checking function that can be called from other tools (e.g., snapshot tool)
|
||||
/// This ensures all tools use the exact same type checking logic as production builds
|
||||
pub fn typeCheckModule(
|
||||
gpa: Allocator,
|
||||
env: *ModuleEnv,
|
||||
builtin_module_env: *const ModuleEnv,
|
||||
imported_envs: []const *ModuleEnv,
|
||||
) !Check {
|
||||
std.debug.print("DEBUG: typeCheckModule called\n", .{});
|
||||
// Load builtin indices from the binary data generated at build time
|
||||
const builtin_indices = try builtin_loading.deserializeBuiltinIndices(self.gpa, compiled_builtins.builtin_indices_bin);
|
||||
const builtin_indices = try builtin_loading.deserializeBuiltinIndices(gpa, compiled_builtins.builtin_indices_bin);
|
||||
|
||||
const module_common_idents: Check.CommonIdents = .{
|
||||
.module_name = try env.insertIdent(base.Ident.for_text("test")),
|
||||
|
|
@ -783,21 +788,48 @@ 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,
|
||||
.builtin_module = self.builtin_modules.builtin_module.env,
|
||||
.builtin_module = builtin_module_env,
|
||||
};
|
||||
|
||||
// Create module_envs map for auto-importing builtin types
|
||||
var module_envs_map = std.AutoHashMap(base.Ident.Idx, Can.AutoImportedType).init(self.gpa);
|
||||
defer module_envs_map.deinit();
|
||||
var module_envs_map = std.AutoHashMap(base.Ident.Idx, Can.AutoImportedType).init(gpa);
|
||||
errdefer module_envs_map.deinit();
|
||||
|
||||
// Populate module_envs with Bool, Try, Dict, Set using shared function
|
||||
try Can.populateModuleEnvs(
|
||||
&module_envs_map,
|
||||
env,
|
||||
self.builtin_modules.builtin_module.env,
|
||||
builtin_module_env,
|
||||
builtin_indices,
|
||||
);
|
||||
|
||||
var checker = try Check.init(
|
||||
gpa,
|
||||
&env.types,
|
||||
env,
|
||||
imported_envs,
|
||||
&module_envs_map,
|
||||
&env.store.regions,
|
||||
module_common_idents,
|
||||
);
|
||||
errdefer checker.deinit();
|
||||
|
||||
try checker.checkFile();
|
||||
|
||||
// After type checking, evaluate top-level declarations at compile time
|
||||
const builtin_types_for_eval = BuiltinTypes.init(builtin_indices, builtin_module_env, builtin_module_env, builtin_module_env);
|
||||
var comptime_evaluator = try eval.ComptimeEvaluator.init(gpa, env, imported_envs, &checker.problems, builtin_types_for_eval);
|
||||
_ = try comptime_evaluator.evalAll();
|
||||
|
||||
module_envs_map.deinit();
|
||||
|
||||
return checker;
|
||||
}
|
||||
|
||||
fn doTypeCheck(self: *PackageEnv, module_id: ModuleId) !void {
|
||||
var st = &self.modules.items[module_id];
|
||||
var env = &st.env.?;
|
||||
|
||||
// Build other_modules array according to env.imports order
|
||||
const import_count = env.imports.imports.items.items.len;
|
||||
var imported_envs = try std.ArrayList(*ModuleEnv).initCapacity(self.gpa, import_count);
|
||||
|
|
@ -826,34 +858,15 @@ pub const PackageEnv = struct {
|
|||
}
|
||||
}
|
||||
|
||||
var checker = try Check.init(
|
||||
self.gpa,
|
||||
&env.types,
|
||||
env,
|
||||
imported_envs.items,
|
||||
&module_envs_map,
|
||||
&env.store.regions,
|
||||
module_common_idents,
|
||||
);
|
||||
defer checker.deinit();
|
||||
// Note: checkDefs runs type checking for module
|
||||
std.debug.print("DEBUG doTypeCheck: calling typeCheckModule\n", .{});
|
||||
const check_start = if (@import("builtin").target.cpu.arch != .wasm32) std.time.nanoTimestamp() else 0;
|
||||
try checker.checkFile();
|
||||
var checker = try typeCheckModule(self.gpa, env, self.builtin_modules.builtin_module.env, imported_envs.items);
|
||||
defer checker.deinit();
|
||||
const check_end = if (@import("builtin").target.cpu.arch != .wasm32) std.time.nanoTimestamp() else 0;
|
||||
if (@import("builtin").target.cpu.arch != .wasm32) {
|
||||
self.total_type_checking_ns += @intCast(check_end - check_start);
|
||||
}
|
||||
|
||||
// After type checking, evaluate top-level declarations at compile time
|
||||
// Load builtin module required by the interpreter (reuse builtin_indices from above)
|
||||
const builtin_source = compiled_builtins.builtin_source;
|
||||
var builtin_module = try builtin_loading.loadCompiledModule(self.gpa, compiled_builtins.builtin_bin, "Builtin", builtin_source);
|
||||
defer builtin_module.deinit();
|
||||
|
||||
const builtin_types_for_eval = BuiltinTypes.init(builtin_indices, builtin_module.env, builtin_module.env, builtin_module.env);
|
||||
var comptime_evaluator = try eval.ComptimeEvaluator.init(self.gpa, env, imported_envs.items, &checker.problems, builtin_types_for_eval);
|
||||
_ = try comptime_evaluator.evalAll();
|
||||
|
||||
// Build reports from problems
|
||||
const check_diag_start = if (@import("builtin").target.cpu.arch != .wasm32) std.time.nanoTimestamp() else 0;
|
||||
var rb = ReportBuilder.init(self.gpa, env, env, &checker.snapshots, st.path, imported_envs.items, &checker.import_mapping);
|
||||
|
|
@ -867,8 +880,7 @@ pub const PackageEnv = struct {
|
|||
self.total_check_diagnostics_ns += @intCast(check_diag_end - check_diag_start);
|
||||
}
|
||||
|
||||
// Clean up comptime evaluator AFTER building reports (crash messages must stay alive until reports are built)
|
||||
comptime_evaluator.deinit();
|
||||
// Comptime evaluator is managed inside typeCheckModule, no need to deinit here
|
||||
|
||||
// Now we can safely deinit the 'imported_envs' ArrayList
|
||||
imported_envs.deinit(self.gpa);
|
||||
|
|
|
|||
|
|
@ -648,87 +648,6 @@ fn extractSectionInfo(content: []const u8, section_name: []const u8) ?struct { s
|
|||
return .{ .start = start_idx, .end = next_section_idx };
|
||||
}
|
||||
|
||||
/// Wrapper for a loaded compiled builtin module that tracks the buffer
|
||||
const LoadedModule = struct {
|
||||
env: *ModuleEnv,
|
||||
buffer: []align(collections.CompactWriter.SERIALIZATION_ALIGNMENT.toByteUnits()) u8,
|
||||
gpa: std.mem.Allocator,
|
||||
|
||||
fn deinit(self: *LoadedModule) void {
|
||||
// Only free the hashmap that was allocated during deserialization
|
||||
// Most other data (like the SafeList contents) points into the buffer
|
||||
self.env.imports.map.deinit(self.gpa);
|
||||
|
||||
// Free the buffer (the env points into this buffer for most data)
|
||||
self.gpa.free(self.buffer);
|
||||
// Free the env struct itself
|
||||
self.gpa.destroy(self.env);
|
||||
}
|
||||
};
|
||||
|
||||
/// Load a compiled ModuleEnv from embedded binary data
|
||||
fn loadCompiledModule(gpa: std.mem.Allocator, bin_data: []const u8, module_name: []const u8, source: []const u8) !LoadedModule {
|
||||
// Copy the embedded data to properly aligned memory
|
||||
// CompactWriter requires specific alignment for serialization
|
||||
const CompactWriter = collections.CompactWriter;
|
||||
const buffer = try gpa.alignedAlloc(u8, CompactWriter.SERIALIZATION_ALIGNMENT, bin_data.len);
|
||||
@memcpy(buffer, bin_data);
|
||||
|
||||
// Cast to the serialized structure
|
||||
const serialized_ptr = @as(
|
||||
*ModuleEnv.Serialized,
|
||||
@ptrCast(@alignCast(buffer.ptr)),
|
||||
);
|
||||
|
||||
const env = try gpa.create(ModuleEnv);
|
||||
errdefer gpa.destroy(env);
|
||||
|
||||
// Deserialize
|
||||
const base_ptr = @intFromPtr(buffer.ptr);
|
||||
|
||||
// Deserialize common env first so we can look up identifiers
|
||||
const common = serialized_ptr.common.deserialize(@as(i64, @intCast(base_ptr)), source).*;
|
||||
|
||||
env.* = ModuleEnv{
|
||||
.gpa = gpa,
|
||||
.common = common,
|
||||
.types = serialized_ptr.types.deserialize(@as(i64, @intCast(base_ptr)), gpa).*,
|
||||
.module_kind = serialized_ptr.module_kind,
|
||||
.all_defs = serialized_ptr.all_defs,
|
||||
.all_statements = serialized_ptr.all_statements,
|
||||
.exports = serialized_ptr.exports,
|
||||
.builtin_statements = serialized_ptr.builtin_statements,
|
||||
.external_decls = serialized_ptr.external_decls.deserialize(@as(i64, @intCast(base_ptr))).*,
|
||||
.imports = (try serialized_ptr.imports.deserialize(@as(i64, @intCast(base_ptr)), gpa)).*,
|
||||
.module_name = module_name,
|
||||
.module_name_idx = undefined, // Not used for deserialized modules (only needed during fresh canonicalization)
|
||||
.diagnostics = serialized_ptr.diagnostics,
|
||||
.store = serialized_ptr.store.deserialize(@as(i64, @intCast(base_ptr)), gpa).*,
|
||||
.evaluation_order = null,
|
||||
.from_int_digits_ident = common.findIdent(base.Ident.FROM_INT_DIGITS_METHOD_NAME) orelse unreachable,
|
||||
.from_dec_digits_ident = common.findIdent(base.Ident.FROM_DEC_DIGITS_METHOD_NAME) orelse unreachable,
|
||||
.try_ident = common.findIdent("Try") orelse unreachable,
|
||||
.out_of_range_ident = common.findIdent("OutOfRange") orelse unreachable,
|
||||
.builtin_module_ident = common.findIdent("Builtin") orelse unreachable,
|
||||
.plus_ident = common.findIdent(base.Ident.PLUS_METHOD_NAME) orelse unreachable,
|
||||
};
|
||||
|
||||
return LoadedModule{
|
||||
.env = env,
|
||||
.buffer = buffer,
|
||||
.gpa = gpa,
|
||||
};
|
||||
}
|
||||
|
||||
/// Deserialize BuiltinIndices from the binary data generated at build time
|
||||
fn deserializeBuiltinIndices(gpa: Allocator, bin_data: []const u8) !CIR.BuiltinIndices {
|
||||
const aligned_buffer = try gpa.alignedAlloc(u8, @enumFromInt(@alignOf(CIR.BuiltinIndices)), bin_data.len);
|
||||
defer gpa.free(aligned_buffer);
|
||||
@memcpy(aligned_buffer, bin_data);
|
||||
const indices_ptr = @as(*const CIR.BuiltinIndices, @ptrCast(aligned_buffer.ptr));
|
||||
return indices_ptr.*;
|
||||
}
|
||||
|
||||
var debug_allocator: std.heap.DebugAllocator(.{}) = .{
|
||||
.backing_allocator = std.heap.c_allocator,
|
||||
};
|
||||
|
|
@ -868,12 +787,12 @@ pub fn main() !void {
|
|||
}
|
||||
}
|
||||
|
||||
// Load compiled Builtin module (contains nested Bool, Try, Str, Dict, Set)
|
||||
const builtin_source = compiled_builtins.builtin_source;
|
||||
var builtin_loaded = try loadCompiledModule(gpa, compiled_builtins.builtin_bin, "Builtin", builtin_source);
|
||||
defer builtin_loaded.deinit();
|
||||
// Load builtin modules using the same code path as roc check
|
||||
const builtin_modules_ptr = try gpa.create(eval_mod.BuiltinModules);
|
||||
defer gpa.destroy(builtin_modules_ptr);
|
||||
|
||||
const builtin_indices = try deserializeBuiltinIndices(gpa, compiled_builtins.builtin_indices_bin);
|
||||
builtin_modules_ptr.* = try eval_mod.BuiltinModules.init(gpa);
|
||||
defer builtin_modules_ptr.deinit();
|
||||
|
||||
const config = Config{
|
||||
.maybe_fuzz_corpus_path = maybe_fuzz_corpus_path,
|
||||
|
|
@ -882,8 +801,8 @@ pub fn main() !void {
|
|||
.output_section_command = output_section_command,
|
||||
.trace_eval = trace_eval,
|
||||
.linecol_mode = linecol_mode,
|
||||
.builtin_module = builtin_loaded.env,
|
||||
.builtin_indices = builtin_indices,
|
||||
.builtin_module = builtin_modules_ptr.builtin_module.env,
|
||||
.builtin_indices = builtin_modules_ptr.builtin_indices,
|
||||
};
|
||||
|
||||
if (config.maybe_fuzz_corpus_path != null) {
|
||||
|
|
@ -921,12 +840,12 @@ pub fn main() !void {
|
|||
}
|
||||
|
||||
fn checkSnapshotExpectations(gpa: Allocator) !bool {
|
||||
// Load compiled Builtin module (contains nested Bool, Try, Str, Dict, Set)
|
||||
const builtin_source = compiled_builtins.builtin_source;
|
||||
var builtin_loaded = try loadCompiledModule(gpa, compiled_builtins.builtin_bin, "Builtin", builtin_source);
|
||||
defer builtin_loaded.deinit();
|
||||
// Load builtin modules using the same code path as roc check
|
||||
const builtin_modules_ptr = try gpa.create(eval_mod.BuiltinModules);
|
||||
defer gpa.destroy(builtin_modules_ptr);
|
||||
|
||||
const builtin_indices = try deserializeBuiltinIndices(gpa, compiled_builtins.builtin_indices_bin);
|
||||
builtin_modules_ptr.* = try eval_mod.BuiltinModules.init(gpa);
|
||||
defer builtin_modules_ptr.deinit();
|
||||
|
||||
const config = Config{
|
||||
.maybe_fuzz_corpus_path = null,
|
||||
|
|
@ -934,8 +853,8 @@ fn checkSnapshotExpectations(gpa: Allocator) !bool {
|
|||
.expected_section_command = .check,
|
||||
.output_section_command = .check,
|
||||
.disable_updates = true,
|
||||
.builtin_module = builtin_loaded.env,
|
||||
.builtin_indices = builtin_indices,
|
||||
.builtin_module = builtin_modules_ptr.builtin_module.env,
|
||||
.builtin_indices = builtin_modules_ptr.builtin_indices,
|
||||
};
|
||||
const snapshots_dir = "test/snapshots";
|
||||
var work_list = WorkList.init(gpa);
|
||||
|
|
@ -1308,22 +1227,33 @@ fn processSnapshotContent(
|
|||
}
|
||||
}
|
||||
|
||||
var solver = try Check.init(
|
||||
allocator,
|
||||
&can_ir.types,
|
||||
can_ir,
|
||||
builtin_modules.items,
|
||||
&module_envs,
|
||||
&can_ir.store.regions,
|
||||
common_idents,
|
||||
);
|
||||
defer solver.deinit();
|
||||
// Use the shared type checking function to ensure identical behavior with roc check
|
||||
std.debug.print("DEBUG snapshot: maybe_expr_idx = {}\n", .{maybe_expr_idx != null});
|
||||
var solver = if (maybe_expr_idx) |expr_idx| blk: {
|
||||
std.debug.print("DEBUG snapshot: Using REPL path\n", .{});
|
||||
// For REPL/expr tests, use the old flow for now
|
||||
var checker = try Check.init(
|
||||
allocator,
|
||||
&can_ir.types,
|
||||
can_ir,
|
||||
builtin_modules.items,
|
||||
&module_envs,
|
||||
&can_ir.store.regions,
|
||||
common_idents,
|
||||
);
|
||||
_ = try checker.checkExprRepl(expr_idx.idx);
|
||||
break :blk checker;
|
||||
} else blk: {
|
||||
std.debug.print("DEBUG snapshot: Using FILE path (shared typeCheckModule)\n", .{});
|
||||
// For file tests, use the shared function from compile_package
|
||||
// Use the SAME builtin module that was used during canonicalization
|
||||
const builtin_env = config.builtin_module orelse unreachable;
|
||||
|
||||
if (maybe_expr_idx) |expr_idx| {
|
||||
_ = try solver.checkExprRepl(expr_idx.idx);
|
||||
} else {
|
||||
try solver.checkFile();
|
||||
}
|
||||
// Cast the slice type - we know the data is compatible
|
||||
const imported_envs_const: []const *ModuleEnv = @ptrCast(builtin_modules.items);
|
||||
break :blk try compile.PackageEnv.typeCheckModule(allocator, can_ir, builtin_env, imported_envs_const);
|
||||
};
|
||||
defer solver.deinit();
|
||||
|
||||
// Assert that we have regions for every type variable
|
||||
solver.debugAssertArraysInSync();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue