diff --git a/common/check.h b/common/check.h index 33bc11b53de0..2adb5a18ac82 100644 --- a/common/check.h +++ b/common/check.h @@ -53,7 +53,10 @@ class ExitWrapper { } // namespace CheckInternal -// Checks the given condition, and if it's false, prints an error and exits. +// Checks the given condition, and if it's false, prints a stack, streams the +// error message, then exits. This should be used for unexpected errors, such as +// a bug in the application. +// // For example: // CHECK(is_valid) << "Data is not valid!"; #define CHECK(condition) \ diff --git a/executable_semantics/ast/BUILD b/executable_semantics/ast/BUILD index 20febeee1632..e443dd91dcbb 100644 --- a/executable_semantics/ast/BUILD +++ b/executable_semantics/ast/BUILD @@ -30,6 +30,7 @@ cc_library( deps = [ "//common:indirect_value", "//common:ostream", + "//executable_semantics/common:error", ], ) diff --git a/executable_semantics/ast/expression.cpp b/executable_semantics/ast/expression.cpp index c903c1283f65..8ed510b346c6 100644 --- a/executable_semantics/ast/expression.cpp +++ b/executable_semantics/ast/expression.cpp @@ -4,6 +4,8 @@ #include "executable_semantics/ast/expression.h" +#include "executable_semantics/common/error.h" + namespace Carbon { auto Expression::GetIdentifierExpression() const @@ -166,9 +168,9 @@ auto Expression::MakeTupleLiteral(int line_num, for (auto& arg : args) { if (arg.name == "") { if (seen_named_member) { - llvm::errs() << line_num - << ": positional members must come before named members\n"; - exit(-1); + FatalUserError() + << line_num + << ": positional members must come before named members"; } arg.name = std::to_string(i); ++i; diff --git a/executable_semantics/common/BUILD b/executable_semantics/common/BUILD index 4c4d91e9b6ac..cab45763bf8b 100644 --- a/executable_semantics/common/BUILD +++ b/executable_semantics/common/BUILD @@ -4,6 +4,24 @@ package(default_visibility = ["//executable_semantics:__subpackages__"]) +cc_library( + name = "error", + hdrs = ["error.h"], + deps = [ + "@llvm-project//llvm:Support", + ], +) + +cc_test( + name = "error_test", + srcs = ["error_test.cpp"], + deps = [ + ":error", + "@llvm-project//llvm:gtest", + "@llvm-project//llvm:gtest_main", + ], +) + cc_library( name = "tracing_flag", srcs = ["tracing_flag.cpp"], diff --git a/executable_semantics/common/error.h b/executable_semantics/common/error.h new file mode 100644 index 000000000000..8134083c95be --- /dev/null +++ b/executable_semantics/common/error.h @@ -0,0 +1,38 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef EXECUTABLE_SEMANTICS_COMMON_ERROR_H_ +#define EXECUTABLE_SEMANTICS_COMMON_ERROR_H_ + +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/Signals.h" +#include "llvm/Support/raw_ostream.h" + +namespace Carbon { + +// Prints an error and exits. This should be used for non-recoverable errors +// with user input. +// +// For example: +// FatalUserError() << "Input is not valid!"; +class FatalUserError { + public: + FatalUserError() { llvm::errs() << "ERROR: "; } + ~FatalUserError() { + // Finish with a newline. + llvm::errs() << "\n"; + exit(-1); + } + + // Forward output to llvm::errs. + template + FatalUserError& operator<<(const T& message) { + llvm::errs() << message; + return *this; + } +}; + +} // namespace Carbon + +#endif // EXECUTABLE_SEMANTICS_COMMON_ERROR_H_ diff --git a/executable_semantics/common/error_test.cpp b/executable_semantics/common/error_test.cpp new file mode 100644 index 000000000000..a481da2d3da6 --- /dev/null +++ b/executable_semantics/common/error_test.cpp @@ -0,0 +1,15 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include "executable_semantics/common/error.h" + +#include "gtest/gtest.h" + +namespace Carbon { + +TEST(ErrorTest, FatalUserError) { + ASSERT_DEATH({ FatalUserError() << "test"; }, "ERROR: test\n"); +} + +} // namespace Carbon diff --git a/executable_semantics/interpreter/typecheck.cpp b/executable_semantics/interpreter/typecheck.cpp index 6e33ddaf0e4f..a65e2cf5864a 100644 --- a/executable_semantics/interpreter/typecheck.cpp +++ b/executable_semantics/interpreter/typecheck.cpp @@ -11,6 +11,7 @@ #include #include "executable_semantics/ast/function_definition.h" +#include "executable_semantics/common/error.h" #include "executable_semantics/common/tracing_flag.h" #include "executable_semantics/interpreter/interpreter.h" @@ -19,20 +20,18 @@ namespace Carbon { void ExpectType(int line_num, const std::string& context, const Value* expected, const Value* actual) { if (!TypeEqual(expected, actual)) { - llvm::errs() << line_num << ": type error in " << context << "\n" - << "expected: " << *expected << "\n" - << "actual: " << *actual << "\n"; - exit(-1); + FatalUserError() << line_num << ": type error in " << context << "\n" + << "expected: " << *expected << "\n" + << "actual: " << *actual; } } void ExpectPointerType(int line_num, const std::string& context, const Value* actual) { if (actual->tag() != ValKind::PointerType) { - llvm::errs() << line_num << ": type error in " << context << "\n" - << "expected a pointer type\n" - << "actual: " << *actual << "\n"; - exit(-1); + FatalUserError() << line_num << ": type error in " << context << "\n" + << "expected a pointer type\n" + << "actual: " << *actual; } } diff --git a/executable_semantics/syntax/BUILD b/executable_semantics/syntax/BUILD index b317a01db2fe..0cd2cd24c09f 100644 --- a/executable_semantics/syntax/BUILD +++ b/executable_semantics/syntax/BUILD @@ -29,9 +29,11 @@ cc_library( ], deps = [ ":paren_contents", + "//common:check", "//common:ostream", "//executable_semantics/ast:declaration", "//executable_semantics/ast:expression", + "//executable_semantics/common:error", "//executable_semantics/common:tracing_flag", "//executable_semantics/interpreter", "//executable_semantics/interpreter:typecheck", diff --git a/executable_semantics/syntax/parse.cpp b/executable_semantics/syntax/parse.cpp index 2516156f5280..e7f96296e737 100644 --- a/executable_semantics/syntax/parse.cpp +++ b/executable_semantics/syntax/parse.cpp @@ -6,6 +6,8 @@ #include +#include "common/check.h" +#include "executable_semantics/common/error.h" #include "executable_semantics/common/tracing_flag.h" #include "executable_semantics/syntax/parse_and_lex_context.h" #include "executable_semantics/syntax/parser.h" @@ -21,9 +23,8 @@ auto parse(const std::string& input_file_name) -> std::variant { yyin = fopen(input_file_name.c_str(), "r"); if (yyin == nullptr) { - std::cerr << "Error opening '" << input_file_name - << "': " << std::strerror(errno) << std::endl; - exit(1); + FatalUserError() << "Error opening '" << input_file_name + << "': " << std::strerror(errno); } std::optional parsed_input = std::nullopt; @@ -38,11 +39,8 @@ auto parse(const std::string& input_file_name) return syntax_error_code; } - if (parsed_input == std::nullopt) { - std::cerr << "Internal error: parser validated syntax yet didn't produce " - "an AST.\n"; - exit(1); - } + CHECK(parsed_input != std::nullopt) + << "parser validated syntax yet didn't produce an AST."; return *parsed_input; } diff --git a/executable_semantics/testdata/fun_named_params2.golden b/executable_semantics/testdata/fun_named_params2.golden index bc8b77c483eb..3819aa14060d 100644 --- a/executable_semantics/testdata/fun_named_params2.golden +++ b/executable_semantics/testdata/fun_named_params2.golden @@ -1,2 +1,2 @@ -5: positional members must come before named members +ERROR: 5: positional members must come before named members EXIT CODE: 255 diff --git a/executable_semantics/testdata/global_variable3.golden b/executable_semantics/testdata/global_variable3.golden index ad7d101f148b..15c5091d1cc1 100644 --- a/executable_semantics/testdata/global_variable3.golden +++ b/executable_semantics/testdata/global_variable3.golden @@ -1,4 +1,4 @@ -7: type error in initializer of variable +ERROR: 7: type error in initializer of variable expected: Int actual: Bool EXIT CODE: 255 diff --git a/executable_semantics/testdata/global_variable5.golden b/executable_semantics/testdata/global_variable5.golden index 95848156efd3..bde9af24c422 100644 --- a/executable_semantics/testdata/global_variable5.golden +++ b/executable_semantics/testdata/global_variable5.golden @@ -1,4 +1,4 @@ -10: type error in return +ERROR: 10: type error in return expected: () actual: Int EXIT CODE: 255 diff --git a/executable_semantics/testdata/tuple4.golden b/executable_semantics/testdata/tuple4.golden index 6bb96d92a524..f4d1819a3592 100644 --- a/executable_semantics/testdata/tuple4.golden +++ b/executable_semantics/testdata/tuple4.golden @@ -1,2 +1,2 @@ -6: positional members must come before named members +ERROR: 6: positional members must come before named members EXIT CODE: 255 diff --git a/executable_semantics/testdata/tuple5.golden b/executable_semantics/testdata/tuple5.golden index 4085611ccce4..382e3643637f 100644 --- a/executable_semantics/testdata/tuple5.golden +++ b/executable_semantics/testdata/tuple5.golden @@ -1,4 +1,4 @@ -8: type error in pattern variable +ERROR: 8: type error in pattern variable expected: (x = Int, y = Int) actual: (y = Int, x = Int) EXIT CODE: 255 diff --git a/executable_semantics/testdata/tuple_equality3.golden b/executable_semantics/testdata/tuple_equality3.golden index f68b9333947a..cbaaabc06399 100644 --- a/executable_semantics/testdata/tuple_equality3.golden +++ b/executable_semantics/testdata/tuple_equality3.golden @@ -1,4 +1,4 @@ -8: type error in == +ERROR: 8: type error in == expected: (0 = Int, 1 = Int) actual: (0 = Int) EXIT CODE: 255