check for parse/token errors and print them on run (#8586)

* check for parse/token errors and print them on run

* return early on parse fail if errors are not allowed

* fix merge issue
This commit is contained in:
Ameen Radwan 2025-12-08 15:10:16 -05:00 committed by GitHub
parent c1b088e870
commit ea76b7b1ee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 67 additions and 9 deletions

View file

@ -1106,7 +1106,7 @@ fn rocRun(allocs: *Allocators, args: cli_args.RunArgs) !void {
// Set up shared memory with ModuleEnv
std.log.debug("Setting up shared memory for Roc file: {s}", .{args.path});
const shm_result = setupSharedMemoryWithModuleEnv(allocs, args.path) catch |err| {
const shm_result = setupSharedMemoryWithModuleEnv(allocs, args.path, args.allow_errors) catch |err| {
std.log.err("Failed to set up shared memory with ModuleEnv: {}", .{err});
return err;
};
@ -1461,7 +1461,7 @@ fn writeToWindowsSharedMemory(data: []const u8, total_size: usize) !SharedMemory
/// This parses, canonicalizes, and type-checks all modules, with the resulting ModuleEnvs
/// ending up in shared memory because all allocations were done into shared memory.
/// Platform type modules have their e_anno_only expressions converted to e_hosted_lambda.
pub fn setupSharedMemoryWithModuleEnv(allocs: *Allocators, roc_file_path: []const u8) !SharedMemoryResult {
pub fn setupSharedMemoryWithModuleEnv(allocs: *Allocators, roc_file_path: []const u8, allow_errors: bool) !SharedMemoryResult {
// Create shared memory with SharedMemoryAllocator
const page_size = try SharedMemoryAllocator.getSystemPageSize();
var shm = try SharedMemoryAllocator.create(SHARED_MEMORY_SIZE, page_size);
@ -1692,10 +1692,39 @@ pub fn setupSharedMemoryWithModuleEnv(allocs: *Allocators, roc_file_path: []cons
app_env.module_name = app_module_name;
try app_env.common.calcLineStarts(shm_allocator);
var error_count: usize = 0;
var app_parse_ast = try parse.parse(&app_env.common, allocs.gpa);
defer app_parse_ast.deinit(allocs.gpa);
app_parse_ast.store.emptyScratch();
if (app_parse_ast.hasErrors()) {
const stderr = stderrWriter();
defer stderr.flush() catch {};
for (app_parse_ast.tokenize_diagnostics.items) |diagnostic| {
error_count += 1;
var report = app_parse_ast.tokenizeDiagnosticToReport(diagnostic, allocs.gpa, roc_file_path) catch continue;
defer report.deinit();
reporting.renderReportToTerminal(&report, stderr, ColorPalette.ANSI, reporting.ReportingConfig.initColorTerminal()) catch continue;
}
for (app_parse_ast.parse_diagnostics.items) |diagnostic| {
error_count += 1;
var report = app_parse_ast.parseDiagnosticToReport(&app_env.common, diagnostic, allocs.gpa, roc_file_path) catch continue;
defer report.deinit();
reporting.renderReportToTerminal(&report, stderr, ColorPalette.ANSI, reporting.ReportingConfig.initColorTerminal()) catch continue;
}
// If errors are not allowed then we should not move past parsing. return early and let caller handle error/exit
if (!allow_errors) {
return SharedMemoryResult{
.handle = SharedMemoryHandle{
.fd = shm.handle,
.ptr = shm.base_ptr,
.size = shm.getUsedSize(),
},
.error_count = error_count,
};
}
}
app_parse_ast.store.emptyScratch();
try app_env.initCIRFields(app_module_name);
var app_module_envs_map = std.AutoHashMap(base.Ident.Idx, Can.AutoImportedType).init(allocs.gpa);
@ -1851,7 +1880,7 @@ pub fn setupSharedMemoryWithModuleEnv(allocs: *Allocators, roc_file_path: []cons
// Render all type problems (errors and warnings) exactly as roc check would
// Count errors so the caller can decide whether to proceed with execution
// Skip rendering in test mode to avoid polluting test output
const error_count = if (!builtin.is_test)
error_count += if (!builtin.is_test)
renderTypeProblems(allocs.gpa, &app_checker, &app_env, roc_file_path)
else
0;

View file

@ -1018,7 +1018,7 @@ test "fx platform issue8433" {
}
}
test "run aborts on errors by default" {
test "run aborts on type errors by default" {
// Tests that roc run aborts when there are type errors (without --allow-errors)
const allocator = testing.allocator;
@ -1039,7 +1039,28 @@ test "run aborts on errors by default" {
try testing.expect(std.mem.indexOf(u8, run_result.stderr, "UNDEFINED VARIABLE") != null);
}
test "run with --allow-errors attempts execution despite errors" {
test "run aborts on parse errors by default" {
// Tests that roc run aborts when there are parse errors (without --allow-errors)
const allocator = testing.allocator;
const run_result = try std.process.Child.run(.{
.allocator = allocator,
.argv = &[_][]const u8{
"./zig-out/bin/roc",
"test/fx/parse_error.roc",
},
});
defer allocator.free(run_result.stdout);
defer allocator.free(run_result.stderr);
// Should fail with type errors
try checkFailure(run_result);
// Should show the errors
try testing.expect(std.mem.indexOf(u8, run_result.stderr, "PARSE ERROR") != null);
}
test "run with --allow-errors attempts execution despite type errors" {
// Tests that roc run --allow-errors attempts to execute even with type errors
const allocator = testing.allocator;

View file

@ -125,7 +125,7 @@ test "integration - shared memory setup and parsing" {
const roc_path = "test/int/app.roc";
// Test that we can set up shared memory with ModuleEnv
const shm_result = try main.setupSharedMemoryWithModuleEnv(&allocs, roc_path);
const shm_result = try main.setupSharedMemoryWithModuleEnv(&allocs, roc_path, true);
const shm_handle = shm_result.handle;
// Clean up shared memory resources
@ -170,7 +170,7 @@ test "integration - compilation pipeline for different platforms" {
for (test_apps) |roc_path| {
// Test the full compilation pipeline (parse -> canonicalize -> typecheck)
const shm_result = main.setupSharedMemoryWithModuleEnv(&allocs, roc_path) catch |err| {
const shm_result = main.setupSharedMemoryWithModuleEnv(&allocs, roc_path, true) catch |err| {
std.log.warn("Failed to set up shared memory for {s}: {}\n", .{ roc_path, err });
continue;
};
@ -212,7 +212,7 @@ test "integration - error handling for non-existent file" {
const roc_path = "test/nonexistent/app.roc";
// This should fail because the file doesn't exist
const result = main.setupSharedMemoryWithModuleEnv(&allocs, roc_path);
const result = main.setupSharedMemoryWithModuleEnv(&allocs, roc_path, true);
// We expect this to fail - the important thing is that it doesn't crash
if (result) |shm_result| {

8
test/fx/parse_error.roc Normal file
View file

@ -0,0 +1,8 @@
app [main!] {
pf: platform "./platform/main.roc",
}
import pf.Stdout
main! = |_args| {
Stdout.line!("Hello world")
Ok({})
}}