diff --git a/src/cli/main.zig b/src/cli/main.zig index a21157ef99e..9a3245f1794 100644 --- a/src/cli/main.zig +++ b/src/cli/main.zig @@ -1103,7 +1103,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; }; @@ -1458,7 +1458,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); @@ -1686,10 +1686,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); @@ -1845,7 +1874,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; diff --git a/src/cli/test/fx_platform_test.zig b/src/cli/test/fx_platform_test.zig index abcad2dab1c..c985c9131c2 100644 --- a/src/cli/test/fx_platform_test.zig +++ b/src/cli/test/fx_platform_test.zig @@ -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; diff --git a/src/cli/test_shared_memory_system.zig b/src/cli/test_shared_memory_system.zig index acdb478fa81..ba17b61c159 100644 --- a/src/cli/test_shared_memory_system.zig +++ b/src/cli/test_shared_memory_system.zig @@ -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| { diff --git a/test/fx/parse_error.roc b/test/fx/parse_error.roc new file mode 100644 index 00000000000..948495a2d72 --- /dev/null +++ b/test/fx/parse_error.roc @@ -0,0 +1,8 @@ +app [main!] { + pf: platform "./platform/main.roc", +} +import pf.Stdout +main! = |_args| { + Stdout.line!("Hello world") + Ok({}) +}}