diff --git a/compiler/compiler.odin b/compiler/compiler.odin index e798a9a..08bad1e 100644 --- a/compiler/compiler.odin +++ b/compiler/compiler.odin @@ -29,6 +29,15 @@ write_escaped_c_string :: proc(builder: ^strings.Builder, value: string) { } } +has_errors :: proc(diagnostics: ^source.Diagnostics) -> bool { + for diagnostic in diagnostics.items { + if diagnostic.severity == .Error { + return true + } + } + return false +} + compile_package :: proc( input_path, output_path: string, link_arguments: []linker.Argument = nil, @@ -142,6 +151,10 @@ compile_package :: proc( vmem.arena_free_all(&lexer_arena) hir_module := checker.check(&ast_module, &diagnostics, &symbols, selected, vmem.arena_allocator(&checker_arena)) vmem.arena_free_all(&parser_arena) + if has_errors(&diagnostics) { + source.print_all(&diagnostics) + return 1 + } ir_module := lower.lower(&hir_module, vmem.arena_allocator(&lower_arena)) vmem.arena_free_all(&checker_arena) opt.run(&ir_module) @@ -149,6 +162,10 @@ compile_package :: proc( llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols) defer delete(llvm_text) vmem.arena_free_all(&lower_arena) + if has_errors(&diagnostics) { + source.print_all(&diagnostics) + return 1 + } llvm_path := fmt.tprintf("%s.brolang-%d.ll", output_path, os2.get_pid()) defer _ = os.remove(llvm_path) if err := os.write_entire_file_or_err(llvm_path, transmute([]byte)llvm_text); err != nil { diff --git a/compiler_tests.odin b/compiler_tests.odin index 854d616..5f10c50 100644 --- a/compiler_tests.odin +++ b/compiler_tests.odin @@ -5094,6 +5094,24 @@ run_executable :: proc(path: string) -> os2.Process_State { return state } +@(test) +checker_errors_skip_ir_output_and_backend :: proc(t: ^testing.T) { + directory := "/tmp/brolang-test-checker-error-stop" + main_path := "/tmp/brolang-test-checker-error-stop/main.bro" + text := `main func() i32 { + return missing +} +` + _ = os2.remove_all(directory) + defer _ = os2.remove_all(directory) + testing.expect(t, os.make_directory(directory) == nil) + testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text)) + // If compilation reaches temporary IR creation, this nonexistent parent changes + // the result to an infrastructure failure instead of the expected source error. + status := compiler_core.compile_package(directory, "/definitely/not/brolang-output") + testing.expect_value(t, status, 1) +} + @(test) valid_program_compiles_and_runs :: proc(t: ^testing.T) { output := "/tmp/brolang-test-valid" @@ -7459,8 +7477,7 @@ unsupported_c_header_members_diagnose_only_when_referenced :: proc(t: ^testing.T c_options := cimport.Options{include_paths=[]string{"examples/interop/header/include"}} status := compiler_core.compile_package("examples/interop/header_unsupported", output, nil, target.DEFAULT, c_options) testing.expect_value(t, status, 1) - state := run_executable(output) - testing.expect(t, !state.success) + testing.expect(t, !os.exists(output)) } @(test) @@ -7631,23 +7648,21 @@ checked_runtime_negation_traps_for_every_signed_width :: proc(t: ^testing.T) { } @(test) -constant_that_does_not_fit_context_produces_trap_executable :: proc(t: ^testing.T) { +constant_that_does_not_fit_context_stops_before_backend :: proc(t: ^testing.T) { output := "/tmp/brolang-test-constant-context-error" defer _ = os.remove(output) status := compiler_core.compile_package("examples/programs/constant_context_error", output) testing.expect_value(t, status, 1) - state := run_executable(output) - testing.expect(t, !state.success) + testing.expect(t, !os.exists(output)) } @(test) -constant_beyond_i64_produces_trap_executable :: proc(t: ^testing.T) { +constant_beyond_i64_stops_before_backend :: proc(t: ^testing.T) { output := "/tmp/brolang-test-constant-i64-overflow" defer _ = os.remove(output) status := compiler_core.compile_package("examples/programs/constant_i64_overflow", output) testing.expect_value(t, status, 1) - state := run_executable(output) - testing.expect(t, !state.success) + testing.expect(t, !os.exists(output)) } @(test) @@ -7666,13 +7681,12 @@ same_line_statements_are_diagnosed :: proc(t: ^testing.T) { } @(test) -missing_main_produces_trap_executable :: proc(t: ^testing.T) { +missing_main_stops_before_backend :: proc(t: ^testing.T) { output := "/tmp/brolang-test-missing-main" defer _ = os.remove(output) status := compiler_core.compile_package("examples/programs/missing_main", output) testing.expect_value(t, status, 1) - state := run_executable(output) - testing.expect(t, !state.success) + testing.expect(t, !os.exists(output)) } @(test) @@ -9093,13 +9107,12 @@ mutable_local_reassignment_uses_runtime_storage :: proc(t: ^testing.T) { } @(test) -implicit_narrowing_produces_trap_executable :: proc(t: ^testing.T) { +implicit_narrowing_stops_before_backend :: proc(t: ^testing.T) { output := "/tmp/brolang-test-narrowing" defer _ = os.remove(output) status := compiler_core.compile_package("examples/programs/narrowing_error", output) testing.expect_value(t, status, 1) - state := run_executable(output) - testing.expect(t, !state.success) + testing.expect(t, !os.exists(output)) } @(test) @@ -9287,8 +9300,7 @@ unused_global_cycle_is_deferred :: proc(t: ^testing.T) { defer _ = os.remove(output) status := compiler_core.compile_package("examples/programs/cycle_unused", output) testing.expect_value(t, status, 1) - state := run_executable(output) - testing.expect_value(t, state.exit_code, 0) + testing.expect(t, !os.exists(output)) } @(test) @@ -9297,18 +9309,16 @@ used_global_cycle_traps :: proc(t: ^testing.T) { defer _ = os.remove(output) status := compiler_core.compile_package("examples/programs/cycle_used", output) testing.expect_value(t, status, 1) - state := run_executable(output) - testing.expect(t, !state.success) + testing.expect(t, !os.exists(output)) } @(test) -malformed_typed_values_still_produce_executable :: proc(t: ^testing.T) { +malformed_typed_values_stop_before_backend :: proc(t: ^testing.T) { output := "/tmp/brolang-test-malformed-typed" defer _ = os.remove(output) status := compiler_core.compile_package("examples/programs/malformed_typed_recovery", output) testing.expect_value(t, status, 1) - state := run_executable(output) - testing.expect(t, !state.success) + testing.expect(t, !os.exists(output)) } @(test) @@ -9327,8 +9337,7 @@ used_invalid_function_is_diagnosed_and_traps :: proc(t: ^testing.T) { defer _ = os.remove(output) status := compiler_core.compile_package("examples/programs/invalid_used_function", output) testing.expect_value(t, status, 1) - state := run_executable(output) - testing.expect(t, !state.success) + testing.expect(t, !os.exists(output)) } @(test) @@ -9337,8 +9346,7 @@ function_mediated_problematic_global_is_deferred :: proc(t: ^testing.T) { defer _ = os.remove(output) status := compiler_core.compile_package("examples/programs/function_global_unused", output) testing.expect_value(t, status, 1) - state := run_executable(output) - testing.expect_value(t, state.exit_code, 0) + testing.expect(t, !os.exists(output)) } @(test) @@ -9347,8 +9355,7 @@ function_mediated_problematic_global_traps_when_used :: proc(t: ^testing.T) { defer _ = os.remove(output) status := compiler_core.compile_package("examples/programs/function_global_used", output) testing.expect_value(t, status, 1) - state := run_executable(output) - testing.expect(t, !state.success) + testing.expect(t, !os.exists(output)) } @(test) @@ -9404,24 +9411,16 @@ unused_missing_package_does_not_trap :: proc(t: ^testing.T) { defer _ = os.remove(output) status := compiler_core.compile_package("examples/packages/missing_unused/app", output) testing.expect_value(t, status, 1) - state := run_executable(output) - testing.expect_value(t, state.exit_code, 0) + testing.expect(t, !os.exists(output)) } @(test) -referenced_missing_package_traps_at_reference :: proc(t: ^testing.T) { +referenced_missing_package_stops_before_backend :: proc(t: ^testing.T) { output := "/tmp/brolang-test-package-missing-used" defer _ = os.remove(output) status := compiler_core.compile_package("examples/packages/missing_used/app", output) testing.expect_value(t, status, 1) - state, stdout, stderr, _ := os2.process_exec( - os2.Process_Desc{command=[]string{output}}, - context.allocator, - ) - defer delete(stdout) - defer delete(stderr) - testing.expect(t, !state.success) - testing.expect(t, strings.contains(string(stderr), "/missing_used/app/main.bro:4:6")) + testing.expect(t, !os.exists(output)) } @(test) @@ -9430,8 +9429,7 @@ imports_are_file_local :: proc(t: ^testing.T) { defer _ = os.remove(output) status := compiler_core.compile_package("examples/packages/file_local/app", output) testing.expect_value(t, status, 1) - state := run_executable(output) - testing.expect(t, !state.success) + testing.expect(t, !os.exists(output)) } @(test) @@ -9564,13 +9562,12 @@ same_package_can_be_imported_under_distinct_aliases :: proc(t: ^testing.T) { } @(test) -duplicate_same_file_import_keeps_first_binding :: proc(t: ^testing.T) { +duplicate_same_file_import_stops_before_backend :: proc(t: ^testing.T) { output := "/tmp/brolang-test-package-duplicate" defer _ = os.remove(output) status := compiler_core.compile_package("examples/packages/duplicate/app", output) testing.expect_value(t, status, 1) - state := run_executable(output) - testing.expect_value(t, state.exit_code, 2) + testing.expect(t, !os.exists(output)) } @(test) @@ -9589,8 +9586,7 @@ imports_do_not_reexport_members :: proc(t: ^testing.T) { defer _ = os.remove(output) status := compiler_core.compile_package("examples/packages/non_transitive/app", output) testing.expect_value(t, status, 1) - state := run_executable(output) - testing.expect(t, !state.success) + testing.expect(t, !os.exists(output)) } @(test) @@ -9750,13 +9746,12 @@ self_import_via_dot_is_valid :: proc(t: ^testing.T) { } @(test) -unused_absolute_import_is_diagnosed_without_trapping :: proc(t: ^testing.T) { +absolute_import_error_stops_before_backend :: proc(t: ^testing.T) { output := "/tmp/brolang-test-package-absolute" defer _ = os.remove(output) status := compiler_core.compile_package("examples/packages/absolute/app", output) testing.expect_value(t, status, 1) - state := run_executable(output) - testing.expect_value(t, state.exit_code, 0) + testing.expect(t, !os.exists(output)) } @(test) @@ -9765,8 +9760,7 @@ unreferenced_problematic_imported_global_is_deferred :: proc(t: ^testing.T) { defer _ = os.remove(output) status := compiler_core.compile_package("examples/packages/problematic_unused/app", output) testing.expect_value(t, status, 1) - state := run_executable(output) - testing.expect_value(t, state.exit_code, 0) + testing.expect(t, !os.exists(output)) } @(test) @@ -9785,8 +9779,7 @@ cross_package_global_initialization_cycle_traps_when_used :: proc(t: ^testing.T) defer _ = os.remove(output) status := compiler_core.compile_package("examples/packages/global_cycle/app", output) testing.expect_value(t, status, 1) - state := run_executable(output) - testing.expect(t, !state.success) + testing.expect(t, !os.exists(output)) } @(test) @@ -9940,8 +9933,7 @@ imported_main_does_not_satisfy_root_main_requirement :: proc(t: ^testing.T) { defer _ = os.remove(output) status := compiler_core.compile_package("examples/packages/missing_root_main/app", output) testing.expect_value(t, status, 1) - state := run_executable(output) - testing.expect(t, !state.success) + testing.expect(t, !os.exists(output)) } @(test) @@ -9977,8 +9969,7 @@ invalid_default_alias_requires_an_explicit_alias :: proc(t: ^testing.T) { defer _ = os.remove(output) status := compiler_core.compile_package("examples/packages/default_alias_invalid/app", output) testing.expect_value(t, status, 1) - state := run_executable(output) - testing.expect_value(t, state.exit_code, 0) + testing.expect(t, !os.exists(output)) } @(test) @@ -9992,13 +9983,12 @@ explicit_alias_allows_invalid_directory_basename :: proc(t: ^testing.T) { } @(test) -import_alias_conflict_is_diagnosed_without_trapping :: proc(t: ^testing.T) { +import_alias_conflict_stops_before_backend :: proc(t: ^testing.T) { output := "/tmp/brolang-test-package-alias-conflict" defer _ = os.remove(output) status := compiler_core.compile_package("examples/packages/alias_conflict/app", output) testing.expect_value(t, status, 1) - state := run_executable(output) - testing.expect_value(t, state.exit_code, 7) + testing.expect(t, !os.exists(output)) } @(test) @@ -10007,8 +9997,7 @@ empty_imported_package_is_a_source_diagnostic :: proc(t: ^testing.T) { defer _ = os.remove(output) status := compiler_core.compile_package("examples/packages/empty_import/app", output) testing.expect_value(t, status, 1) - state := run_executable(output) - testing.expect_value(t, state.exit_code, 0) + testing.expect(t, !os.exists(output)) } @(test) @@ -10017,8 +10006,7 @@ non_directory_import_is_a_source_diagnostic :: proc(t: ^testing.T) { defer _ = os.remove(output) status := compiler_core.compile_package("examples/packages/file_import/app", output) testing.expect_value(t, status, 1) - state := run_executable(output) - testing.expect_value(t, state.exit_code, 0) + testing.expect(t, !os.exists(output)) } @(test) @@ -13933,8 +13921,7 @@ main func() void { use(1, 2) } testing.expect(t, os.make_directory(directory) == nil) testing.expect(t, os.write_entire_file(main_path, transmute([]byte)cases[1].text)) testing.expect_value(t, compiler_core.compile_package(directory, output), 1) - state := run_executable(output) - testing.expect(t, !state.success) + testing.expect(t, !os.exists(output)) } @(test)