diff --git a/common/error.h b/common/error.h index 6db614e78324..7b206a3456ed 100644 --- a/common/error.h +++ b/common/error.h @@ -62,10 +62,14 @@ class [[nodiscard]] ErrorOr { // Returns the contained error. // REQUIRES: `ok()` is false. - auto error() const -> const Error& { + auto error() const& -> const Error& { CHECK(!ok()); return std::get(val_); } + auto error() && -> Error { + CHECK(!ok()); + return std::get(std::move(val_)); + } // Returns the contained value. // REQUIRES: `ok()` is true. diff --git a/executable_semantics/BUILD b/executable_semantics/BUILD index 437990e88c09..8b5b11fd4d59 100644 --- a/executable_semantics/BUILD +++ b/executable_semantics/BUILD @@ -14,6 +14,7 @@ cc_binary( srcs = ["main.cpp"], data = ["data/prelude.carbon"], deps = [ + "//common:error", "//executable_semantics/common:arena", "//executable_semantics/common:nonnull", "//executable_semantics/interpreter:exec_program", diff --git a/executable_semantics/ast/BUILD b/executable_semantics/ast/BUILD index 96228b229c8a..24572db010db 100644 --- a/executable_semantics/ast/BUILD +++ b/executable_semantics/ast/BUILD @@ -203,8 +203,10 @@ cc_library( ":source_location", ":value_category", "//common:check", + "//common:error", "//executable_semantics/common:error", "//executable_semantics/common:nonnull", + "@llvm-project//llvm:Support", ], ) diff --git a/executable_semantics/ast/declaration.cpp b/executable_semantics/ast/declaration.cpp index d52e11fdef17..1a316058e44e 100644 --- a/executable_semantics/ast/declaration.cpp +++ b/executable_semantics/ast/declaration.cpp @@ -112,29 +112,38 @@ void ReturnTerm::Print(llvm::raw_ostream& out) const { } } -// Look for the `me` parameter in the `deduced_parameters_` -// and put it in the `me_pattern_`. -void FunctionDeclaration::ResolveDeducedAndReceiver( - const std::vector>& deduced_params) { +auto FunctionDeclaration::Create( + Nonnull arena, SourceLocation source_loc, std::string name, + std::vector> deduced_params, + std::optional> me_pattern, + Nonnull param_pattern, ReturnTerm return_term, + std::optional> body) + -> ErrorOr> { + std::vector> resolved_params; + // Look for the `me` parameter in the `deduced_parameters` + // and put it in the `me_pattern`. for (Nonnull param : deduced_params) { switch (param->kind()) { case AstNodeKind::GenericBinding: - deduced_parameters_.push_back(&cast(*param)); + resolved_params.push_back(&cast(*param)); break; case AstNodeKind::BindingPattern: { Nonnull bp = &cast(*param); - if (me_pattern_.has_value() || bp->name() != "me") { - FATAL_COMPILATION_ERROR(source_loc()) - << "illegal binding pattern in implicit parameter list"; + if (me_pattern.has_value() || bp->name() != "me") { + return FATAL_COMPILATION_ERROR(source_loc) + << "illegal binding pattern in implicit parameter list"; } - me_pattern_ = bp; + me_pattern = bp; break; } default: - FATAL_COMPILATION_ERROR(source_loc()) - << "illegal AST node in implicit parameter list"; + return FATAL_COMPILATION_ERROR(source_loc) + << "illegal AST node in implicit parameter list"; } } + return arena->New(source_loc, name, resolved_params, + me_pattern, param_pattern, return_term, + body); } void FunctionDeclaration::PrintDepth(int depth, llvm::raw_ostream& out) const { diff --git a/executable_semantics/ast/declaration.h b/executable_semantics/ast/declaration.h index 12b44efff363..b83b1d036c53 100644 --- a/executable_semantics/ast/declaration.h +++ b/executable_semantics/ast/declaration.h @@ -82,20 +82,29 @@ class FunctionDeclaration : public Declaration { public: using ImplementsCarbonValueNode = void; + static auto Create(Nonnull arena, SourceLocation source_loc, + std::string name, + std::vector> deduced_params, + std::optional> me_pattern, + Nonnull param_pattern, + ReturnTerm return_term, + std::optional> body) + -> ErrorOr>; + + // Use `Create()` instead. This is public only so Arena::New() can call it. FunctionDeclaration(SourceLocation source_loc, std::string name, - std::vector> deduced_params, + std::vector> deduced_params, std::optional> me_pattern, Nonnull param_pattern, ReturnTerm return_term, std::optional> body) : Declaration(AstNodeKind::FunctionDeclaration, source_loc), name_(std::move(name)), + deduced_parameters_(std::move(deduced_params)), me_pattern_(me_pattern), param_pattern_(param_pattern), return_term_(return_term), - body_(body) { - ResolveDeducedAndReceiver(deduced_params); - } + body_(body) {} static auto classof(const AstNode* node) -> bool { return InheritsFromFunctionDeclaration(node->kind()); @@ -135,7 +144,6 @@ class FunctionDeclaration : public Declaration { auto is_method() const -> bool { return me_pattern_.has_value(); } private: - void ResolveDeducedAndReceiver(const std::vector>&); std::string name_; std::vector> deduced_parameters_; std::optional> me_pattern_; diff --git a/executable_semantics/ast/expression.cpp b/executable_semantics/ast/expression.cpp index 7595efb8b2b5..64f6bf969907 100644 --- a/executable_semantics/ast/expression.cpp +++ b/executable_semantics/ast/expression.cpp @@ -20,13 +20,14 @@ using llvm::isa; auto IntrinsicExpression::FindIntrinsic(std::string_view name, SourceLocation source_loc) - -> Intrinsic { + -> ErrorOr { static const auto& intrinsic_map = *new std::map({{"print", Intrinsic::Print}}); name.remove_prefix(std::strlen("__intrinsic_")); auto it = intrinsic_map.find(name); if (it == intrinsic_map.end()) { - FATAL_COMPILATION_ERROR(source_loc) << "Unknown intrinsic '" << name << "'"; + return FATAL_COMPILATION_ERROR(source_loc) + << "Unknown intrinsic '" << name << "'"; } return it->second; } diff --git a/executable_semantics/ast/expression.h b/executable_semantics/ast/expression.h index b9cf7c65fc81..80e362aed781 100644 --- a/executable_semantics/ast/expression.h +++ b/executable_semantics/ast/expression.h @@ -465,11 +465,15 @@ class IntrinsicExpression : public Expression { Print, }; - explicit IntrinsicExpression(std::string_view intrinsic_name, - Nonnull args, + // Returns the enumerator corresponding to the intrinsic named `name`, + // or raises a fatal compile error if there is no such enumerator. + static auto FindIntrinsic(std::string_view name, SourceLocation source_loc) + -> ErrorOr; + + explicit IntrinsicExpression(Intrinsic intrinsic, Nonnull args, SourceLocation source_loc) : Expression(AstNodeKind::IntrinsicExpression, source_loc), - intrinsic_(FindIntrinsic(intrinsic_name, source_loc)), + intrinsic_(intrinsic), args_(args) {} static auto classof(const AstNode* node) -> bool { @@ -481,11 +485,6 @@ class IntrinsicExpression : public Expression { auto args() -> TupleLiteral& { return *args_; } private: - // Returns the enumerator corresponding to the intrinsic named `name`, - // or raises a fatal compile error if there is no such enumerator. - static auto FindIntrinsic(std::string_view name, SourceLocation source_loc) - -> Intrinsic; - Intrinsic intrinsic_; Nonnull args_; }; diff --git a/executable_semantics/ast/pattern.cpp b/executable_semantics/ast/pattern.cpp index 19a4f39a8468..20cf9cab963e 100644 --- a/executable_semantics/ast/pattern.cpp +++ b/executable_semantics/ast/pattern.cpp @@ -108,23 +108,15 @@ auto TuplePatternFromParenContents(Nonnull arena, // Used by AlternativePattern for constructor initialization. Produces a helpful // error for incorrect expressions, rather than letting a default cast error // apply. -static auto RequireFieldAccess(Nonnull alternative) - -> FieldAccessExpression& { +auto AlternativePattern::RequireFieldAccess(Nonnull alternative) + -> ErrorOr> { if (alternative->kind() != ExpressionKind::FieldAccessExpression) { - FATAL_PROGRAM_ERROR(alternative->source_loc()) - << "Alternative pattern must have the form of a field access."; + return FATAL_PROGRAM_ERROR(alternative->source_loc()) + << "Alternative pattern must have the form of a field access."; } - return cast(*alternative); + return &cast(*alternative); } -AlternativePattern::AlternativePattern(SourceLocation source_loc, - Nonnull alternative, - Nonnull arguments) - : Pattern(AstNodeKind::AlternativePattern, source_loc), - choice_type_(&RequireFieldAccess(alternative).aggregate()), - alternative_name_(RequireFieldAccess(alternative).field()), - arguments_(arguments) {} - auto ParenExpressionToParenPattern(Nonnull arena, const ParenContents& contents) -> ParenContents { diff --git a/executable_semantics/ast/pattern.h b/executable_semantics/ast/pattern.h index a16a8d84ff3f..5238263335ea 100644 --- a/executable_semantics/ast/pattern.h +++ b/executable_semantics/ast/pattern.h @@ -218,6 +218,19 @@ auto ParenExpressionToParenPattern(Nonnull arena, // A pattern that matches an alternative of a choice type. class AlternativePattern : public Pattern { public: + // Constructs an AlternativePattern that matches the alternative specified + // by `alternative`, if its arguments match `arguments`. + static auto Create(Nonnull arena, SourceLocation source_loc, + Nonnull alternative, + Nonnull arguments) + -> ErrorOr> { + ASSIGN_OR_RETURN(Nonnull field_access, + RequireFieldAccess(alternative)); + return arena->New(source_loc, + &field_access->aggregate(), + field_access->field(), arguments); + } + // Constructs an AlternativePattern that matches a value of the type // specified by choice_type if it represents an alternative named // alternative_name, and its arguments match `arguments`. @@ -230,12 +243,6 @@ class AlternativePattern : public Pattern { alternative_name_(std::move(alternative_name)), arguments_(arguments) {} - // Constructs an AlternativePattern that matches the alternative specified - // by `alternative`, if its arguments match `arguments`. - AlternativePattern(SourceLocation source_loc, - Nonnull alternative, - Nonnull arguments); - static auto classof(const AstNode* node) -> bool { return InheritsFromAlternativePattern(node->kind()); } @@ -249,6 +256,9 @@ class AlternativePattern : public Pattern { auto arguments() -> TuplePattern& { return *arguments_; } private: + static auto RequireFieldAccess(Nonnull alternative) + -> ErrorOr>; + Nonnull choice_type_; std::string alternative_name_; Nonnull arguments_; diff --git a/executable_semantics/ast/static_scope.cpp b/executable_semantics/ast/static_scope.cpp index f10458f11c79..da45837c18df 100644 --- a/executable_semantics/ast/static_scope.cpp +++ b/executable_semantics/ast/static_scope.cpp @@ -5,43 +5,50 @@ #include "executable_semantics/ast/static_scope.h" #include "executable_semantics/common/error.h" +#include "llvm/Support/Error.h" namespace Carbon { -void StaticScope::Add(std::string name, ValueNodeView entity) { +auto StaticScope::Add(std::string name, ValueNodeView entity) + -> ErrorOr { auto [it, success] = declared_names_.insert({name, entity}); if (!success && it->second != entity) { - FATAL_COMPILATION_ERROR(entity.base().source_loc()) - << "Duplicate name `" << name << "` also found at " - << it->second.base().source_loc(); + return FATAL_COMPILATION_ERROR(entity.base().source_loc()) + << "Duplicate name `" << name << "` also found at " + << it->second.base().source_loc(); } + return Success(); } auto StaticScope::Resolve(const std::string& name, - SourceLocation source_loc) const -> ValueNodeView { - std::optional result = TryResolve(name, source_loc); - if (!result.has_value()) { - FATAL_COMPILATION_ERROR(source_loc) << "could not resolve '" << name << "'"; + SourceLocation source_loc) const + -> ErrorOr { + ASSIGN_OR_RETURN(std::optional result, + TryResolve(name, source_loc)); + if (!result) { + return FATAL_COMPILATION_ERROR(source_loc) + << "could not resolve '" << name << "'"; } return *result; } auto StaticScope::TryResolve(const std::string& name, SourceLocation source_loc) const - -> std::optional { + -> ErrorOr> { auto it = declared_names_.find(name); if (it != declared_names_.end()) { - return it->second; + return std::make_optional(it->second); } std::optional result; for (Nonnull parent : parent_scopes_) { - auto parent_result = parent->TryResolve(name, source_loc); + ASSIGN_OR_RETURN(std::optional parent_result, + parent->TryResolve(name, source_loc)); if (parent_result.has_value() && result.has_value() && *parent_result != *result) { - FATAL_COMPILATION_ERROR(source_loc) - << "'" << name << "' is ambiguous between " - << result->base().source_loc() << " and " - << parent_result->base().source_loc(); + return FATAL_COMPILATION_ERROR(source_loc) + << "'" << name << "' is ambiguous between " + << result->base().source_loc() << " and " + << parent_result->base().source_loc(); } result = parent_result; } diff --git a/executable_semantics/ast/static_scope.h b/executable_semantics/ast/static_scope.h index dab72c2d2d89..a669a29b9490 100644 --- a/executable_semantics/ast/static_scope.h +++ b/executable_semantics/ast/static_scope.h @@ -12,10 +12,12 @@ #include #include "common/check.h" +#include "common/error.h" #include "executable_semantics/ast/ast_node.h" #include "executable_semantics/ast/source_location.h" #include "executable_semantics/ast/value_category.h" #include "executable_semantics/common/nonnull.h" +#include "llvm/Support/Error.h" namespace Carbon { @@ -133,7 +135,7 @@ class StaticScope { public: // Defines `name` to be `entity` in this scope, or reports a compilation error // if `name` is already defined to be a different entity in this scope. - void Add(std::string name, ValueNodeView entity); + auto Add(std::string name, ValueNodeView entity) -> ErrorOr; // Make `parent` a parent of this scope. // REQUIRES: `parent` is not already a parent of this scope. @@ -145,14 +147,14 @@ class StaticScope { // scope, or reports a compilation error at `source_loc` there isn't exactly // one such definition. auto Resolve(const std::string& name, SourceLocation source_loc) const - -> ValueNodeView; + -> ErrorOr; private: // Equivalent to Resolve, but returns `nullopt` instead of raising an error // if no definition can be found. Still raises a compilation error if more // than one definition is found. auto TryResolve(const std::string& name, SourceLocation source_loc) const - -> std::optional; + -> ErrorOr>; // Maps locally declared names to their entities. std::unordered_map declared_names_; diff --git a/executable_semantics/common/BUILD b/executable_semantics/common/BUILD index 565774afbaeb..85677f60fd29 100644 --- a/executable_semantics/common/BUILD +++ b/executable_semantics/common/BUILD @@ -17,6 +17,9 @@ cc_library( hdrs = ["error.h"], deps = [ "//common:check", + "//common:error", + "//executable_semantics/ast:source_location", + "@llvm-project//llvm:Support", ], ) @@ -26,6 +29,7 @@ cc_test( deps = [ ":error", "@com_google_googletest//:gtest_main", + "@llvm-project//llvm:Support", ], ) diff --git a/executable_semantics/common/error.h b/executable_semantics/common/error.h index 9d4e483fe35c..15edc78230f9 100644 --- a/executable_semantics/common/error.h +++ b/executable_semantics/common/error.h @@ -5,38 +5,102 @@ #ifndef EXECUTABLE_SEMANTICS_COMMON_ERROR_H_ #define EXECUTABLE_SEMANTICS_COMMON_ERROR_H_ +#include + #include "common/check.h" +#include "common/error.h" +#include "executable_semantics/ast/source_location.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. +// A helper class for accumulating error message and converting to +// `Carbon::Error`/`Carbon::ErrorOr`. +class ErrorBuilder { + public: + explicit ErrorBuilder(std::optional loc = std::nullopt) + : out_(message_) { + if (loc.has_value()) { + out_ << *loc << ": "; + } + } + + // Accumulates string message. + template + [[nodiscard]] auto operator<<(const T& message) -> ErrorBuilder& { + out_ << message; + return *this; + } + + operator Error() { return Error(message_); } + + template + operator ErrorOr() { + return Error(message_); + } + + std::string message_; + llvm::raw_string_ostream out_; +}; + +// Builds a Carbon::Error instance with the specified message. This should be +// used for non-recoverable errors with user input. // // For example: -// FATAL_PROGRAM_ERROR(line_num) << "Line is bad!"; -// FATAL_PROGRAM_ERROR_NO_LINE() << "Application is bad!"; +// return FATAL_PROGRAM_ERROR(line_num) << "Line is bad!"; +// return FATAL_PROGRAM_ERROR_NO_LINE() << "Application is bad!"; // // Where possible, try to identify the error as a compilation or -// runtime error. Use CHECK/FATAL for internal errors. The generic program error -// option is provided as a fallback for cases that don't fit those +// runtime error. Use CHECK/FATAL for internal errors. The generic program +// error option is provided as a fallback for cases that don't fit those // classifications. +// +// TODO: replace below macro invocations with direct `return ErrorBuilder() << +// xx` calls. -#define FATAL_PROGRAM_ERROR_NO_LINE() RAW_EXITING_STREAM() << "PROGRAM ERROR: " +#define FATAL_PROGRAM_ERROR_NO_LINE() \ + Carbon::ErrorBuilder() << "PROGRAM ERROR: " #define FATAL_PROGRAM_ERROR(line) \ FATAL_PROGRAM_ERROR_NO_LINE() << (line) << ": " #define FATAL_COMPILATION_ERROR_NO_LINE() \ - RAW_EXITING_STREAM() << "COMPILATION ERROR: " + Carbon::ErrorBuilder() << "COMPILATION ERROR: " #define FATAL_COMPILATION_ERROR(line) \ FATAL_COMPILATION_ERROR_NO_LINE() << (line) << ": " -#define FATAL_RUNTIME_ERROR_NO_LINE() RAW_EXITING_STREAM() << "RUNTIME ERROR: " +#define FATAL_RUNTIME_ERROR_NO_LINE() \ + Carbon::ErrorBuilder() << "RUNTIME ERROR: " #define FATAL_RUNTIME_ERROR(line) \ FATAL_RUNTIME_ERROR_NO_LINE() << (line) << ": " +// Macro hackery to get a unique variable name. +#define MAKE_UNIQUE_NAME_IMPL(a, b, c) a##b##c +#define MAKE_UNIQUE_NAME(a, b, c) MAKE_UNIQUE_NAME_IMPL(a, b, c) + +#define RETURN_IF_ERROR_IMPL(unique_name, expr) \ + if (auto unique_name = (expr); !unique_name.ok()) { \ + return std::move(unique_name).error(); \ + } + +#define RETURN_IF_ERROR(expr) \ + RETURN_IF_ERROR_IMPL( \ + MAKE_UNIQUE_NAME(_llvm_error_line, __LINE__, __COUNTER__), expr) + +#define ASSIGN_OR_RETURN_IMPL(unique_name, var, expr) \ + auto unique_name = (expr); \ + if (!unique_name.ok()) { \ + return std::move(unique_name).error(); \ + } \ + var = std::move(*unique_name); + +#define ASSIGN_OR_RETURN(var, expr) \ + ASSIGN_OR_RETURN_IMPL( \ + MAKE_UNIQUE_NAME(_llvm_expected_line, __LINE__, __COUNTER__), var, expr) + } // namespace Carbon #endif // EXECUTABLE_SEMANTICS_COMMON_ERROR_H_ diff --git a/executable_semantics/common/error_test.cpp b/executable_semantics/common/error_test.cpp index 1d4d9bce3a49..dcc3e131343b 100644 --- a/executable_semantics/common/error_test.cpp +++ b/executable_semantics/common/error_test.cpp @@ -9,24 +9,94 @@ namespace Carbon::Testing { namespace { +using ::testing::Eq; + +auto MakeSuccess() -> ErrorOr { return Success(); } + +auto MakeError(std::string_view message) -> ErrorOr { + return Error(message); +} + +auto MakeInt(int value) -> ErrorOr { return value; } + +auto MakeFailedInt(std::string_view message) -> ErrorOr { + return Error(message); +} + +auto ErrorToString(const Error& e) -> std::string { return e.message(); } + +template +auto ErrorToString(const ErrorOr& e) -> std::string { + return e.error().message(); +} + TEST(ErrorTest, FatalProgramError) { - ASSERT_DEATH({ FATAL_PROGRAM_ERROR_NO_LINE() << "test"; }, - "^PROGRAM ERROR: test\n"); + EXPECT_EQ(ErrorToString(FATAL_PROGRAM_ERROR_NO_LINE() << "test"), + "PROGRAM ERROR: test"); } TEST(ErrorTest, FatalRuntimeError) { - ASSERT_DEATH({ FATAL_RUNTIME_ERROR_NO_LINE() << "test"; }, - "^RUNTIME ERROR: test\n"); + EXPECT_EQ(ErrorToString(FATAL_RUNTIME_ERROR_NO_LINE() << "test"), + "RUNTIME ERROR: test"); } TEST(ErrorTest, FatalCompilationError) { - ASSERT_DEATH({ FATAL_COMPILATION_ERROR_NO_LINE() << "test"; }, - "^COMPILATION ERROR: test\n"); + EXPECT_EQ(ErrorToString(FATAL_COMPILATION_ERROR_NO_LINE() << "test"), + "COMPILATION ERROR: test"); } TEST(ErrorTest, FatalProgramErrorLine) { - ASSERT_DEATH({ FATAL_PROGRAM_ERROR(1) << "test"; }, - "^PROGRAM ERROR: 1: test\n"); + EXPECT_EQ(ErrorToString(FATAL_PROGRAM_ERROR(1) << "test"), + "PROGRAM ERROR: 1: test"); +} + +TEST(ErrorTest, ReturnIfErrorNoError) { + auto result = []() -> ErrorOr { + RETURN_IF_ERROR(MakeSuccess()); + RETURN_IF_ERROR(MakeSuccess()); + return Success(); + }(); + EXPECT_TRUE(result.ok()); +} + +TEST(ErrorTest, ReturnIfErrorHasError) { + auto result = []() -> ErrorOr { + RETURN_IF_ERROR(MakeSuccess()); + RETURN_IF_ERROR(MakeError("error")); + return Success(); + }(); + ASSERT_FALSE(result.ok()); + EXPECT_EQ(ErrorToString(result), "error"); +} + +TEST(ErrorTest, AssignOrReturnNoError) { + auto result = []() -> ErrorOr { + RETURN_IF_ERROR(MakeSuccess()); + ASSIGN_OR_RETURN(int a, MakeInt(1)); + ASSIGN_OR_RETURN(const int b, MakeInt(2)); + int c = 0; + ASSIGN_OR_RETURN(c, MakeInt(3)); + return a + b + c; + }(); + ASSERT_TRUE(result.ok()); + EXPECT_EQ(6, *result); +} + +TEST(ErrorTest, AssignOrReturnHasDirectError) { + auto result = []() -> ErrorOr { + RETURN_IF_ERROR(MakeError("error")); + return 0; + }(); + ASSERT_FALSE(result.ok()); +} + +TEST(ErrorTest, AssignOrReturnHasErrorInExpected) { + auto result = []() -> ErrorOr { + ASSIGN_OR_RETURN(int a, MakeFailedInt("error")); + return a; + }(); + ASSERT_FALSE(result.ok()); + EXPECT_EQ(ErrorToString(result), "error"); } } // namespace diff --git a/executable_semantics/interpreter/BUILD b/executable_semantics/interpreter/BUILD index 45c0fe1afddb..412992e61c8e 100644 --- a/executable_semantics/interpreter/BUILD +++ b/executable_semantics/interpreter/BUILD @@ -77,6 +77,7 @@ cc_library( "//common:ostream", "//executable_semantics/ast", "//executable_semantics/common:arena", + "@llvm-project//llvm:Support", ], ) diff --git a/executable_semantics/interpreter/action_stack.cpp b/executable_semantics/interpreter/action_stack.cpp index fb10f4475b59..a9b9f2fc144d 100644 --- a/executable_semantics/interpreter/action_stack.cpp +++ b/executable_semantics/interpreter/action_stack.cpp @@ -7,6 +7,7 @@ #include "executable_semantics/interpreter/action.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/Casting.h" +#include "llvm/Support/Error.h" namespace Carbon { @@ -49,7 +50,7 @@ void ActionStack::Initialize(ValueNodeView value_node, auto ActionStack::ValueOfNode(ValueNodeView value_node, SourceLocation source_loc) const - -> Nonnull { + -> ErrorOr> { if (std::optional> constant_value = value_node.constant_value(); constant_value.has_value()) { @@ -74,9 +75,9 @@ auto ActionStack::ValueOfNode(ValueNodeView value_node, return *result; } } - // TODO: Move these errors to name resolution and explain them more clearly. - FATAL_RUNTIME_ERROR(source_loc) - << "could not find `" << value_node.base() << "`"; + // TODO: Move these errors to compile time and explain them more clearly. + return FATAL_RUNTIME_ERROR(source_loc) + << "could not find `" << value_node.base() << "`"; } void ActionStack::MergeScope(RuntimeScope scope) { @@ -110,7 +111,7 @@ void ActionStack::InitializeFragment(ContinuationValue::StackFragment& fragment, fragment.StoreReversed(std::move(reversed_todo)); } -void ActionStack::FinishAction() { +auto ActionStack::FinishAction() -> ErrorOr { std::unique_ptr act = todo_.Pop(); switch (act->kind()) { case Action::Kind::ExpressionAction: @@ -123,9 +124,11 @@ void ActionStack::FinishAction() { case Action::Kind::DeclarationAction: PopScopes(); } + return Success(); } -void ActionStack::FinishAction(Nonnull result) { +auto ActionStack::FinishAction(Nonnull result) + -> ErrorOr { std::unique_ptr act = todo_.Pop(); switch (act->kind()) { case Action::Kind::StatementAction: @@ -139,27 +142,33 @@ void ActionStack::FinishAction(Nonnull result) { PopScopes(); SetResult(result); } + return Success(); } -void ActionStack::Spawn(std::unique_ptr child) { +auto ActionStack::Spawn(std::unique_ptr child) -> ErrorOr { Action& action = *todo_.Top(); action.set_pos(action.pos() + 1); todo_.Push(std::move(child)); + return Success(); } -void ActionStack::Spawn(std::unique_ptr child, RuntimeScope scope) { +auto ActionStack::Spawn(std::unique_ptr child, RuntimeScope scope) + -> ErrorOr { Action& action = *todo_.Top(); action.set_pos(action.pos() + 1); todo_.Push(std::make_unique(std::move(scope))); todo_.Push(std::move(child)); + return Success(); } -void ActionStack::RunAgain() { +auto ActionStack::RunAgain() -> ErrorOr { Action& action = *todo_.Top(); action.set_pos(action.pos() + 1); + return Success(); } -void ActionStack::UnwindTo(Nonnull ast_node) { +auto ActionStack::UnwindTo(Nonnull ast_node) + -> ErrorOr { while (true) { if (const auto* statement_action = llvm::dyn_cast(todo_.Top().get()); @@ -169,24 +178,30 @@ void ActionStack::UnwindTo(Nonnull ast_node) { } todo_.Pop(); } + return Success(); } -void ActionStack::UnwindPast(Nonnull ast_node) { - UnwindTo(ast_node); +auto ActionStack::UnwindPast(Nonnull ast_node) + -> ErrorOr { + RETURN_IF_ERROR(UnwindTo(ast_node)); todo_.Pop(); PopScopes(); + return Success(); } -void ActionStack::UnwindPast(Nonnull ast_node, - Nonnull result) { - UnwindPast(ast_node); +auto ActionStack::UnwindPast(Nonnull ast_node, + Nonnull result) -> ErrorOr { + RETURN_IF_ERROR(UnwindPast(ast_node)); SetResult(result); + return Success(); } -void ActionStack::Resume(Nonnull continuation) { +auto ActionStack::Resume(Nonnull continuation) + -> ErrorOr { Action& action = *todo_.Top(); action.set_pos(action.pos() + 1); continuation->stack().RestoreTo(todo_); + return Success(); } static auto IsRunAction(const Action& action) -> bool { @@ -194,7 +209,7 @@ static auto IsRunAction(const Action& action) -> bool { return statement != nullptr && llvm::isa(statement->statement()); } -void ActionStack::Suspend() { +auto ActionStack::Suspend() -> ErrorOr { // Pause the current continuation todo_.Pop(); std::vector> paused; @@ -205,6 +220,7 @@ void ActionStack::Suspend() { llvm::cast(*todo_.Top()->results()[0]); // Update the continuation with the paused stack. continuation.stack().StoreReversed(std::move(paused)); + return Success(); } void ActionStack::PopScopes() { diff --git a/executable_semantics/interpreter/action_stack.h b/executable_semantics/interpreter/action_stack.h index 228ca595e660..67234e5b5478 100644 --- a/executable_semantics/interpreter/action_stack.h +++ b/executable_semantics/interpreter/action_stack.h @@ -49,7 +49,7 @@ class ActionStack { // Returns the value bound to `value_node`. If `value_node` is a local // variable, this will be an LValue. auto ValueOfNode(ValueNodeView value_node, SourceLocation source_loc) const - -> Nonnull; + -> ErrorOr>; // Merges `scope` into the innermost scope currently on the stack. void MergeScope(RuntimeScope scope); @@ -70,40 +70,42 @@ class ActionStack { // invoke exactly one transition method, as the very last operation. This is a // matter of safety as well as convention: most transition methods modify the // state of the current action, and some of them destroy it. To help enforce - // this requirement, we have a convention of calling these methods as part of - // return statements, e.g. `return todo_.FinishAction()`, even though they - // return void. + // this requirement, we have a convention of making these methods return an + // ErrorOr even when a method can't actually fail, and calling the + // methods as part of return statements, e.g. `return todo_.FinishAction()`. // Finishes execution of the current Action. If `result` is specified, it // represents the result of that Action. - void FinishAction(); - void FinishAction(Nonnull result); + auto FinishAction() -> ErrorOr; + auto FinishAction(Nonnull result) -> ErrorOr; // Advances the current action one step, and push `child` onto the stack. // If `scope` is specified, `child` will be executed in that scope. - void Spawn(std::unique_ptr child); - void Spawn(std::unique_ptr child, RuntimeScope scope); + auto Spawn(std::unique_ptr child) -> ErrorOr; + auto Spawn(std::unique_ptr child, RuntimeScope scope) + -> ErrorOr; // Advances the current action one step. - void RunAgain(); + auto RunAgain() -> ErrorOr; // Unwinds Actions from the stack until the StatementAction associated with // `ast_node` is at the top of the stack. - void UnwindTo(Nonnull ast_node); + auto UnwindTo(Nonnull ast_node) -> ErrorOr; // Unwinds Actions from the stack until the StatementAction associated with // `ast_node` has been removed from the stack. If `result` is specified, // it represents the result of that Action (StatementActions normally cannot // produce results, but the body of a function can). - void UnwindPast(Nonnull ast_node); - void UnwindPast(Nonnull ast_node, - Nonnull result); + auto UnwindPast(Nonnull ast_node) -> ErrorOr; + auto UnwindPast(Nonnull ast_node, + Nonnull result) -> ErrorOr; // Resumes execution of a suspended continuation. - void Resume(Nonnull continuation); + auto Resume(Nonnull continuation) + -> ErrorOr; // Suspends execution of the currently-executing continuation. - void Suspend(); + auto Suspend() -> ErrorOr; private: // Pop any ScopeActions from the top of the stack, propagating results as diff --git a/executable_semantics/interpreter/exec_program.cpp b/executable_semantics/interpreter/exec_program.cpp index 987da6a01f74..41e23eb1180e 100644 --- a/executable_semantics/interpreter/exec_program.cpp +++ b/executable_semantics/interpreter/exec_program.cpp @@ -13,10 +13,11 @@ #include "executable_semantics/interpreter/resolve_control_flow.h" #include "executable_semantics/interpreter/resolve_names.h" #include "executable_semantics/interpreter/type_checker.h" +#include "llvm/Support/Error.h" namespace Carbon { -void ExecProgram(Nonnull arena, AST ast, bool trace) { +auto ExecProgram(Nonnull arena, AST ast, bool trace) -> ErrorOr { if (trace) { llvm::outs() << "********** source program **********\n"; for (const auto decl : ast.declarations) { @@ -32,15 +33,15 @@ void ExecProgram(Nonnull arena, AST ast, bool trace) { if (trace) { llvm::outs() << "********** resolving names **********\n"; } - ResolveNames(ast); + RETURN_IF_ERROR(ResolveNames(ast)); if (trace) { llvm::outs() << "********** resolving control flow **********\n"; } - ResolveControlFlow(ast); + RETURN_IF_ERROR(ResolveControlFlow(ast)); if (trace) { llvm::outs() << "********** type checking **********\n"; } - TypeChecker(arena, trace).TypeCheck(ast); + RETURN_IF_ERROR(TypeChecker(arena, trace).TypeCheck(ast)); if (trace) { llvm::outs() << "\n"; llvm::outs() << "********** type checking complete **********\n"; @@ -49,8 +50,9 @@ void ExecProgram(Nonnull arena, AST ast, bool trace) { } llvm::outs() << "********** starting execution **********\n"; } - int result = InterpProgram(ast, arena, trace); + ASSIGN_OR_RETURN(const int result, InterpProgram(ast, arena, trace)); llvm::outs() << "result: " << result << "\n"; + return result; } } // namespace Carbon diff --git a/executable_semantics/interpreter/exec_program.h b/executable_semantics/interpreter/exec_program.h index 1731b9a89389..31d0869e7e32 100644 --- a/executable_semantics/interpreter/exec_program.h +++ b/executable_semantics/interpreter/exec_program.h @@ -14,7 +14,7 @@ namespace Carbon { // Runs the top-level declaration list. -void ExecProgram(Nonnull arena, AST ast, bool trace); +auto ExecProgram(Nonnull arena, AST ast, bool trace) -> ErrorOr; } // namespace Carbon diff --git a/executable_semantics/interpreter/heap.cpp b/executable_semantics/interpreter/heap.cpp index f1b33641e1b0..63f19174c679 100644 --- a/executable_semantics/interpreter/heap.cpp +++ b/executable_semantics/interpreter/heap.cpp @@ -6,6 +6,7 @@ #include "executable_semantics/common/error.h" #include "llvm/ADT/StringExtras.h" +#include "llvm/Support/Error.h" namespace Carbon { @@ -21,26 +22,29 @@ auto Heap::AllocateValue(Nonnull v) -> AllocationId { } auto Heap::Read(const Address& a, SourceLocation source_loc) const - -> Nonnull { - this->CheckAlive(a.allocation_, source_loc); + -> ErrorOr> { + RETURN_IF_ERROR(this->CheckAlive(a.allocation_, source_loc)); return values_[a.allocation_.index_]->GetField(arena_, a.field_path_, source_loc); } -void Heap::Write(const Address& a, Nonnull v, - SourceLocation source_loc) { - this->CheckAlive(a.allocation_, source_loc); - values_[a.allocation_.index_] = values_[a.allocation_.index_]->SetField( - arena_, a.field_path_, v, source_loc); +auto Heap::Write(const Address& a, Nonnull v, + SourceLocation source_loc) -> ErrorOr { + RETURN_IF_ERROR(this->CheckAlive(a.allocation_, source_loc)); + ASSIGN_OR_RETURN(values_[a.allocation_.index_], + values_[a.allocation_.index_]->SetField( + arena_, a.field_path_, v, source_loc)); + return Success(); } -void Heap::CheckAlive(AllocationId allocation, - SourceLocation source_loc) const { +auto Heap::CheckAlive(AllocationId allocation, SourceLocation source_loc) const + -> ErrorOr { if (!alive_[allocation.index_]) { - FATAL_RUNTIME_ERROR(source_loc) - << "undefined behavior: access to dead value " - << *values_[allocation.index_]; + return FATAL_RUNTIME_ERROR(source_loc) + << "undefined behavior: access to dead value " + << *values_[allocation.index_]; } + return Success(); } void Heap::Deallocate(AllocationId allocation) { diff --git a/executable_semantics/interpreter/heap.h b/executable_semantics/interpreter/heap.h index c9bcbee64f35..60aa13ad537f 100644 --- a/executable_semantics/interpreter/heap.h +++ b/executable_semantics/interpreter/heap.h @@ -28,12 +28,12 @@ class Heap : public HeapAllocationInterface { // Returns the value at the given address in the heap after // checking that it is alive. auto Read(const Address& a, SourceLocation source_loc) const - -> Nonnull; + -> ErrorOr>; // Writes the given value at the address in the heap after // checking that the address is alive. - void Write(const Address& a, Nonnull v, - SourceLocation source_loc); + auto Write(const Address& a, Nonnull v, + SourceLocation source_loc) -> ErrorOr; // Put the given value on the heap and mark it as alive. auto AllocateValue(Nonnull v) -> AllocationId override; @@ -50,7 +50,8 @@ class Heap : public HeapAllocationInterface { private: // Signal an error if the allocation is no longer alive. - void CheckAlive(AllocationId allocation, SourceLocation source_loc) const; + auto CheckAlive(AllocationId allocation, SourceLocation source_loc) const + -> ErrorOr; Nonnull arena_; std::vector> values_; diff --git a/executable_semantics/interpreter/impl_scope.cpp b/executable_semantics/interpreter/impl_scope.cpp index d49949af39dc..e809c4652135 100644 --- a/executable_semantics/interpreter/impl_scope.cpp +++ b/executable_semantics/interpreter/impl_scope.cpp @@ -23,12 +23,14 @@ void ImplScope::AddParent(Nonnull parent) { auto ImplScope::Resolve(Nonnull iface_type, Nonnull type, - SourceLocation source_loc) const -> ValueNodeView { - std::optional result = - TryResolve(iface_type, type, source_loc); + SourceLocation source_loc) const + -> ErrorOr { + ASSIGN_OR_RETURN(std::optional result, + TryResolve(iface_type, type, source_loc)); if (!result.has_value()) { - FATAL_COMPILATION_ERROR(source_loc) << "could not find implementation of " - << *iface_type << " for " << *type; + return FATAL_COMPILATION_ERROR(source_loc) + << "could not find implementation of " << *iface_type << " for " + << *type; } return *result; } @@ -36,18 +38,20 @@ auto ImplScope::Resolve(Nonnull iface_type, auto ImplScope::TryResolve(Nonnull iface_type, Nonnull type, SourceLocation source_loc) const - -> std::optional { + -> ErrorOr> { std::optional result = ResolveHere(iface_type, type, source_loc); if (result.has_value()) { return result; } for (Nonnull parent : parent_scopes_) { - auto parent_result = parent->TryResolve(iface_type, type, source_loc); + ASSIGN_OR_RETURN(auto parent_result, + parent->TryResolve(iface_type, type, source_loc)); if (parent_result.has_value() && result.has_value() && *parent_result != *result) { - FATAL_COMPILATION_ERROR(source_loc) - << "ambiguous implementations of " << *iface_type << " for " << *type; + return FATAL_COMPILATION_ERROR(source_loc) + << "ambiguous implementations of " << *iface_type << " for " + << *type; } result = parent_result; } diff --git a/executable_semantics/interpreter/impl_scope.h b/executable_semantics/interpreter/impl_scope.h index 1b8e607e7f44..d60549702151 100644 --- a/executable_semantics/interpreter/impl_scope.h +++ b/executable_semantics/interpreter/impl_scope.h @@ -50,12 +50,12 @@ class ImplScope { // the ancestor graph of this scope, or reports a compilation error // at `source_loc` there isn't exactly one matching impl. auto Resolve(Nonnull iface, Nonnull type, - SourceLocation source_loc) const -> ValueNodeView; + SourceLocation source_loc) const -> ErrorOr; private: auto TryResolve(Nonnull iface_type, Nonnull type, SourceLocation source_loc) const - -> std::optional; + -> ErrorOr>; auto ResolveHere(Nonnull iface_type, Nonnull impl_type, SourceLocation source_loc) const diff --git a/executable_semantics/interpreter/interpreter.cpp b/executable_semantics/interpreter/interpreter.cpp index ba3aea6de40c..f3a42087da97 100644 --- a/executable_semantics/interpreter/interpreter.cpp +++ b/executable_semantics/interpreter/interpreter.cpp @@ -21,6 +21,7 @@ #include "executable_semantics/interpreter/stack.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/Casting.h" +#include "llvm/Support/Error.h" using llvm::cast; using llvm::dyn_cast; @@ -58,7 +59,8 @@ class Interpreter { ~Interpreter(); // Runs all the steps of `action`. - void RunAllSteps(std::unique_ptr action); + // It's not safe to call `RunAllSteps()` or `result()` after an error. + auto RunAllSteps(std::unique_ptr action) -> ErrorOr; // The result produced by the `action` argument of the most recent // RunAllSteps call. Cannot be called if `action` was an action that doesn't @@ -66,25 +68,25 @@ class Interpreter { auto result() const -> Nonnull { return todo_.result(); } private: - void Step(); + auto Step() -> ErrorOr; // State transitions for expressions. - void StepExp(); + auto StepExp() -> ErrorOr; // State transitions for lvalues. - void StepLvalue(); + auto StepLvalue() -> ErrorOr; // State transitions for patterns. - void StepPattern(); + auto StepPattern() -> ErrorOr; // State transition for statements. - void StepStmt(); + auto StepStmt() -> ErrorOr; // State transition for declarations. - void StepDeclaration(); + auto StepDeclaration() -> ErrorOr; auto CreateStruct(const std::vector& fields, const std::vector>& values) -> Nonnull; auto EvalPrim(Operator op, const std::vector>& args, - SourceLocation source_loc) -> Nonnull; + SourceLocation source_loc) -> ErrorOr>; // Returns the result of converting `value` to type `destination_type`. auto Convert(Nonnull value, @@ -129,7 +131,8 @@ void Interpreter::PrintState(llvm::raw_ostream& out) { auto Interpreter::EvalPrim(Operator op, const std::vector>& args, - SourceLocation source_loc) -> Nonnull { + SourceLocation source_loc) + -> ErrorOr> { switch (op) { case Operator::Neg: return arena_->New(-cast(*args[0]).value()); @@ -257,7 +260,7 @@ auto PatternMatch(Nonnull p, Nonnull v, } } -void Interpreter::StepLvalue() { +auto Interpreter::StepLvalue() -> ErrorOr { Action& act = todo_.CurrentAction(); const Expression& exp = cast(act).expression(); if (trace_) { @@ -268,8 +271,10 @@ void Interpreter::StepLvalue() { case ExpressionKind::IdentifierExpression: { // { {x :: C, E, F} :: S, H} // -> { {E(x) :: C, E, F} :: S, H} - Nonnull value = todo_.ValueOfNode( - cast(exp).value_node(), exp.source_loc()); + ASSIGN_OR_RETURN( + Nonnull value, + todo_.ValueOfNode(cast(exp).value_node(), + exp.source_loc())); CHECK(isa(value)) << *value; return todo_.FinishAction(value); } @@ -418,7 +423,7 @@ auto Interpreter::Convert(Nonnull value, } } -void Interpreter::StepExp() { +auto Interpreter::StepExp() -> ErrorOr { Action& act = todo_.CurrentAction(); const Expression& exp = cast(act).expression(); if (trace_) { @@ -441,8 +446,8 @@ void Interpreter::StepExp() { const auto& tuple = cast(*act.results()[0]); int i = cast(*act.results()[1]).value(); if (i < 0 || i >= static_cast(tuple.elements().size())) { - FATAL_RUNTIME_ERROR_NO_LINE() - << "index " << i << " out of range in " << tuple; + return FATAL_RUNTIME_ERROR_NO_LINE() + << "index " << i << " out of range in " << tuple; } return todo_.FinishAction(tuple.elements()[i]); } @@ -495,15 +500,19 @@ void Interpreter::StepExp() { // -> { { v_f :: C, E, F} : S, H} std::optional> witness = std::nullopt; if (access.impl().has_value()) { - auto witness_addr = - todo_.ValueOfNode(*access.impl(), access.source_loc()); - witness = cast( + ASSIGN_OR_RETURN( + auto witness_addr, + todo_.ValueOfNode(*access.impl(), access.source_loc())); + ASSIGN_OR_RETURN( + Nonnull witness_value, heap_.Read(llvm::cast(witness_addr)->address(), access.source_loc())); + witness = cast(witness_value); } FieldPath::Component field(access.field(), witness); - Nonnull member = act.results()[0]->GetField( - arena_, FieldPath(field), exp.source_loc()); + ASSIGN_OR_RETURN(Nonnull member, + act.results()[0]->GetField(arena_, FieldPath(field), + exp.source_loc())); return todo_.FinishAction(member); } } @@ -511,10 +520,12 @@ void Interpreter::StepExp() { CHECK(act.pos() == 0); const auto& ident = cast(exp); // { {x :: C, E, F} :: S, H} -> { {H(E(x)) :: C, E, F} :: S, H} - Nonnull value = - todo_.ValueOfNode(ident.value_node(), ident.source_loc()); + ASSIGN_OR_RETURN( + Nonnull value, + todo_.ValueOfNode(ident.value_node(), ident.source_loc())); if (const auto* lvalue = dyn_cast(value)) { - value = heap_.Read(lvalue->address(), exp.source_loc()); + ASSIGN_OR_RETURN(value, + heap_.Read(lvalue->address(), exp.source_loc())); } return todo_.FinishAction(value); } @@ -542,8 +553,9 @@ void Interpreter::StepExp() { } else { // { {v :: op(vs,[]) :: C, E, F} :: S, H} // -> { {eval_prim(op, (vs,v)) :: C, E, F} :: S, H} - return todo_.FinishAction( - EvalPrim(op.op(), act.results(), exp.source_loc())); + ASSIGN_OR_RETURN(Nonnull value, + EvalPrim(op.op(), act.results(), exp.source_loc())); + return todo_.FinishAction(value); } } case ExpressionKind::CallExpression: @@ -576,11 +588,12 @@ void Interpreter::StepExp() { // Bring the impl witness tables into scope. for (const auto& [impl_bind, impl_node] : cast(exp).impls()) { - Nonnull witness = - todo_.ValueOfNode(impl_node, exp.source_loc()); + ASSIGN_OR_RETURN(Nonnull witness, + todo_.ValueOfNode(impl_node, exp.source_loc())); if (witness->kind() == Value::Kind::LValue) { const auto& lval = cast(*witness); - witness = heap_.Read(lval.address(), exp.source_loc()); + ASSIGN_OR_RETURN(witness, + heap_.Read(lval.address(), exp.source_loc())); } function_scope.Initialize(impl_bind, witness); } @@ -610,8 +623,8 @@ void Interpreter::StepExp() { std::move(method_scope)); } default: - FATAL_RUNTIME_ERROR(exp.source_loc()) - << "in call, expected a function, not " << *act.results()[0]; + return FATAL_RUNTIME_ERROR(exp.source_loc()) + << "in call, expected a function, not " << *act.results()[0]; } } else if (act.pos() == 3) { if (act.results().size() < 3) { @@ -701,7 +714,7 @@ void Interpreter::StepExp() { } // switch (exp->kind) } -void Interpreter::StepPattern() { +auto Interpreter::StepPattern() -> ErrorOr { Action& act = todo_.CurrentAction(); const Pattern& pattern = cast(act).pattern(); if (trace_) { @@ -768,7 +781,7 @@ void Interpreter::StepPattern() { } } -void Interpreter::StepStmt() { +auto Interpreter::StepStmt() -> ErrorOr { Action& act = todo_.CurrentAction(); const Statement& stmt = cast(act).statement(); if (trace_) { @@ -901,7 +914,7 @@ void Interpreter::StepStmt() { const auto& lval = cast(*act.results()[0]); Nonnull rval = Convert(act.results()[1], &assign.lhs().static_type()); - heap_.Write(lval.address(), rval, stmt.source_loc()); + RETURN_IF_ERROR(heap_.Write(lval.address(), rval, stmt.source_loc())); return todo_.FinishAction(); } } @@ -977,7 +990,7 @@ void Interpreter::StepStmt() { } } -void Interpreter::StepDeclaration() { +auto Interpreter::StepDeclaration() -> ErrorOr { Action& act = todo_.CurrentAction(); const Declaration& decl = cast(act).declaration(); if (trace_) { @@ -1009,72 +1022,79 @@ void Interpreter::StepDeclaration() { } // State transition. -void Interpreter::Step() { +auto Interpreter::Step() -> ErrorOr { Action& act = todo_.CurrentAction(); switch (act.kind()) { case Action::Kind::LValAction: - StepLvalue(); + RETURN_IF_ERROR(StepLvalue()); break; case Action::Kind::ExpressionAction: - StepExp(); + RETURN_IF_ERROR(StepExp()); break; case Action::Kind::PatternAction: - StepPattern(); + RETURN_IF_ERROR(StepPattern()); break; case Action::Kind::StatementAction: - StepStmt(); + RETURN_IF_ERROR(StepStmt()); break; case Action::Kind::DeclarationAction: - StepDeclaration(); + RETURN_IF_ERROR(StepDeclaration()); break; case Action::Kind::ScopeAction: FATAL() << "ScopeAction escaped ActionStack"; } // switch + return Success(); } -void Interpreter::RunAllSteps(std::unique_ptr action) { +auto Interpreter::RunAllSteps(std::unique_ptr action) + -> ErrorOr { if (trace_) { PrintState(llvm::outs()); } todo_.Start(std::move(action)); while (!todo_.IsEmpty()) { - Step(); + RETURN_IF_ERROR(Step()); if (trace_) { PrintState(llvm::outs()); } } + return Success(); } -auto InterpProgram(const AST& ast, Nonnull arena, bool trace) -> int { +auto InterpProgram(const AST& ast, Nonnull arena, bool trace) + -> ErrorOr { Interpreter interpreter(Phase::RunTime, arena, trace); if (trace) { llvm::outs() << "********** initializing globals **********\n"; } for (Nonnull declaration : ast.declarations) { - interpreter.RunAllSteps(std::make_unique(declaration)); + RETURN_IF_ERROR(interpreter.RunAllSteps( + std::make_unique(declaration))); } if (trace) { llvm::outs() << "********** calling main function **********\n"; } - interpreter.RunAllSteps(std::make_unique(*ast.main_call)); + RETURN_IF_ERROR(interpreter.RunAllSteps( + std::make_unique(*ast.main_call))); return cast(*interpreter.result()).value(); } auto InterpExp(Nonnull e, Nonnull arena, bool trace) - -> Nonnull { + -> ErrorOr> { Interpreter interpreter(Phase::CompileTime, arena, trace); - interpreter.RunAllSteps(std::make_unique(e)); + RETURN_IF_ERROR( + interpreter.RunAllSteps(std::make_unique(e))); return interpreter.result(); } auto InterpPattern(Nonnull p, Nonnull arena, bool trace) - -> Nonnull { + -> ErrorOr> { Interpreter interpreter(Phase::CompileTime, arena, trace); - interpreter.RunAllSteps(std::make_unique(p)); + RETURN_IF_ERROR(interpreter.RunAllSteps(std::make_unique(p))); return interpreter.result(); } diff --git a/executable_semantics/interpreter/interpreter.h b/executable_semantics/interpreter/interpreter.h index 31d7457ad055..15497595f8fb 100644 --- a/executable_semantics/interpreter/interpreter.h +++ b/executable_semantics/interpreter/interpreter.h @@ -23,19 +23,20 @@ namespace Carbon { // Interprets the program defined by `ast`, allocating values on `arena` and // printing traces if `trace` is true. -auto InterpProgram(const AST& ast, Nonnull arena, bool trace) -> int; +auto InterpProgram(const AST& ast, Nonnull arena, bool trace) + -> ErrorOr; // Interprets `e` at compile-time, allocating values on `arena` and // printing traces if `trace` is true. The caller must ensure that all the // code this evaluates has been typechecked. auto InterpExp(Nonnull e, Nonnull arena, bool trace) - -> Nonnull; + -> ErrorOr>; // Interprets `p` at compile-time, allocating values on `arena` and // printing traces if `trace` is true. The caller must ensure that all the // code this evaluates has been typechecked. auto InterpPattern(Nonnull p, Nonnull arena, bool trace) - -> Nonnull; + -> ErrorOr>; // Attempts to match `v` against the pattern `p`, returning whether matching // is successful. If it is, populates **bindings with the variables bound by diff --git a/executable_semantics/interpreter/resolve_control_flow.cpp b/executable_semantics/interpreter/resolve_control_flow.cpp index c7c1e59953c4..293a0f23013e 100644 --- a/executable_semantics/interpreter/resolve_control_flow.cpp +++ b/executable_semantics/interpreter/resolve_control_flow.cpp @@ -9,6 +9,7 @@ #include "executable_semantics/ast/statement.h" #include "executable_semantics/common/error.h" #include "llvm/Support/Casting.h" +#include "llvm/Support/Error.h" using llvm::cast; @@ -31,114 +32,121 @@ struct FunctionData { // belongs to, and that information may be updated by this call. `function` // can be nullopt if `statement` does not belong to a function body, for // example if it is part of a continuation body instead. -static void ResolveControlFlow(Nonnull statement, +static auto ResolveControlFlow(Nonnull statement, std::optional> loop, - std::optional> function) { + std::optional> function) + -> ErrorOr { switch (statement->kind()) { case StatementKind::Return: { if (!function.has_value()) { - FATAL_COMPILATION_ERROR(statement->source_loc()) - << "return is not within a function body"; + return FATAL_COMPILATION_ERROR(statement->source_loc()) + << "return is not within a function body"; } const ReturnTerm& function_return = (*function)->declaration->return_term(); if (function_return.is_auto()) { if ((*function)->saw_return_in_auto) { - FATAL_COMPILATION_ERROR(statement->source_loc()) - << "Only one return is allowed in a function with an `auto` " - "return type."; + return FATAL_COMPILATION_ERROR(statement->source_loc()) + << "Only one return is allowed in a function with an `auto` " + "return type."; } (*function)->saw_return_in_auto = true; } auto& ret = cast(*statement); ret.set_function((*function)->declaration); if (ret.is_omitted_expression() != function_return.is_omitted()) { - FATAL_COMPILATION_ERROR(ret.source_loc()) - << ret << " should" << (function_return.is_omitted() ? " not" : "") - << " provide a return value, to match the function's signature."; + return FATAL_COMPILATION_ERROR(ret.source_loc()) + << ret << " should" + << (function_return.is_omitted() ? " not" : "") + << " provide a return value, to match the function's signature."; } - return; + return Success(); } case StatementKind::Break: if (!loop.has_value()) { - FATAL_COMPILATION_ERROR(statement->source_loc()) - << "break is not within a loop body"; + return FATAL_COMPILATION_ERROR(statement->source_loc()) + << "break is not within a loop body"; } cast(*statement).set_loop(*loop); - return; + return Success(); case StatementKind::Continue: if (!loop.has_value()) { - FATAL_COMPILATION_ERROR(statement->source_loc()) - << "continue is not within a loop body"; + return FATAL_COMPILATION_ERROR(statement->source_loc()) + << "continue is not within a loop body"; } cast(*statement).set_loop(*loop); - return; + return Success(); case StatementKind::If: { auto& if_stmt = cast(*statement); - ResolveControlFlow(&if_stmt.then_block(), loop, function); + RETURN_IF_ERROR( + ResolveControlFlow(&if_stmt.then_block(), loop, function)); if (if_stmt.else_block().has_value()) { - ResolveControlFlow(*if_stmt.else_block(), loop, function); + RETURN_IF_ERROR( + ResolveControlFlow(*if_stmt.else_block(), loop, function)); } - return; + return Success(); } case StatementKind::Block: { auto& block = cast(*statement); for (auto* block_statement : block.statements()) { - ResolveControlFlow(block_statement, loop, function); + RETURN_IF_ERROR(ResolveControlFlow(block_statement, loop, function)); } - return; + return Success(); } case StatementKind::While: - ResolveControlFlow(&cast(*statement).body(), statement, function); - return; + RETURN_IF_ERROR(ResolveControlFlow(&cast(*statement).body(), + statement, function)); + return Success(); case StatementKind::Match: { auto& match = cast(*statement); for (Match::Clause& clause : match.clauses()) { - ResolveControlFlow(&clause.statement(), loop, function); + RETURN_IF_ERROR( + ResolveControlFlow(&clause.statement(), loop, function)); } - return; + return Success(); } case StatementKind::Continuation: - ResolveControlFlow(&cast(*statement).body(), std::nullopt, - std::nullopt); - return; + RETURN_IF_ERROR(ResolveControlFlow(&cast(*statement).body(), + std::nullopt, std::nullopt)); + return Success(); case StatementKind::ExpressionStatement: case StatementKind::Assign: case StatementKind::VariableDefinition: case StatementKind::Run: case StatementKind::Await: - return; + return Success(); } } -void ResolveControlFlow(Nonnull declaration) { +auto ResolveControlFlow(Nonnull declaration) -> ErrorOr { switch (declaration->kind()) { case DeclarationKind::FunctionDeclaration: { auto& function = cast(*declaration); if (function.body().has_value()) { FunctionData data = {.declaration = &function}; - ResolveControlFlow(*function.body(), std::nullopt, &data); + RETURN_IF_ERROR( + ResolveControlFlow(*function.body(), std::nullopt, &data)); } break; } case DeclarationKind::ClassDeclaration: { auto& class_decl = cast(*declaration); for (Nonnull member : class_decl.members()) { - ResolveControlFlow(member); + RETURN_IF_ERROR(ResolveControlFlow(member)); } break; } case DeclarationKind::InterfaceDeclaration: { auto& iface_decl = cast(*declaration); for (Nonnull member : iface_decl.members()) { - ResolveControlFlow(member); + RETURN_IF_ERROR(ResolveControlFlow(member)); } break; } case DeclarationKind::ImplDeclaration: { auto& impl_decl = cast(*declaration); for (Nonnull member : impl_decl.members()) { - ResolveControlFlow(member); + RETURN_IF_ERROR(ResolveControlFlow(member)); } break; } @@ -147,12 +155,14 @@ void ResolveControlFlow(Nonnull declaration) { // do nothing break; } + return Success(); } -void ResolveControlFlow(AST& ast) { +auto ResolveControlFlow(AST& ast) -> ErrorOr { for (auto declaration : ast.declarations) { - ResolveControlFlow(declaration); + RETURN_IF_ERROR(ResolveControlFlow(declaration)); } + return Success(); } } // namespace Carbon diff --git a/executable_semantics/interpreter/resolve_control_flow.h b/executable_semantics/interpreter/resolve_control_flow.h index fd78167a730c..fb903e1074e4 100644 --- a/executable_semantics/interpreter/resolve_control_flow.h +++ b/executable_semantics/interpreter/resolve_control_flow.h @@ -12,7 +12,9 @@ namespace Carbon { // Resolves non-local control-flow edges, such as `break` and `return`, in the // given AST. -void ResolveControlFlow(AST& ast); +// On failure, `ast` is left in a partial state and should not be further +// processed. +auto ResolveControlFlow(AST& ast) -> ErrorOr; } // namespace Carbon diff --git a/executable_semantics/interpreter/resolve_names.cpp b/executable_semantics/interpreter/resolve_names.cpp index 4c3c30a28768..cf9e8649fd9a 100644 --- a/executable_semantics/interpreter/resolve_names.cpp +++ b/executable_semantics/interpreter/resolve_names.cpp @@ -12,21 +12,22 @@ #include "executable_semantics/ast/statement.h" #include "executable_semantics/ast/static_scope.h" #include "llvm/Support/Casting.h" +#include "llvm/Support/Error.h" using llvm::cast; namespace Carbon { // Adds the names exposed by the given AST node to enclosing_scope. -static void AddExposedNames(const Declaration& declaration, - StaticScope& enclosing_scope); +static auto AddExposedNames(const Declaration& declaration, + StaticScope& enclosing_scope) -> ErrorOr; -static void AddExposedNames(const Declaration& declaration, - StaticScope& enclosing_scope) { +static auto AddExposedNames(const Declaration& declaration, + StaticScope& enclosing_scope) -> ErrorOr { switch (declaration.kind()) { case DeclarationKind::InterfaceDeclaration: { auto& iface_decl = cast(declaration); - enclosing_scope.Add(iface_decl.name(), &iface_decl); + RETURN_IF_ERROR(enclosing_scope.Add(iface_decl.name(), &iface_decl)); break; } case DeclarationKind::ImplDeclaration: { @@ -35,26 +36,28 @@ static void AddExposedNames(const Declaration& declaration, } case DeclarationKind::FunctionDeclaration: { auto& func = cast(declaration); - enclosing_scope.Add(func.name(), &func); + RETURN_IF_ERROR(enclosing_scope.Add(func.name(), &func)); break; } case DeclarationKind::ClassDeclaration: { auto& class_decl = cast(declaration); - enclosing_scope.Add(class_decl.name(), &class_decl); + RETURN_IF_ERROR(enclosing_scope.Add(class_decl.name(), &class_decl)); break; } case DeclarationKind::ChoiceDeclaration: { auto& choice = cast(declaration); - enclosing_scope.Add(choice.name(), &choice); + RETURN_IF_ERROR(enclosing_scope.Add(choice.name(), &choice)); break; } case DeclarationKind::VariableDeclaration: auto& var = cast(declaration); if (var.binding().name() != AnonymousName) { - enclosing_scope.Add(var.binding().name(), &var.binding()); + RETURN_IF_ERROR( + enclosing_scope.Add(var.binding().name(), &var.binding())); } - return; + break; } + return Success(); } // Traverses the sub-AST rooted at the given node, resolving all names within @@ -67,76 +70,85 @@ static void AddExposedNames(const Declaration& declaration, // calling AddExposedNames on each element of the scope to populate a // StaticScope, and then calling ResolveNames on each element, passing it the // already-populated StaticScope. -static void ResolveNames(Expression& expression, - const StaticScope& enclosing_scope); -static void ResolveNames(Pattern& pattern, StaticScope& enclosing_scope); -static void ResolveNames(Statement& statement, StaticScope& enclosing_scope); -static void ResolveNames(Declaration& declaration, - StaticScope& enclosing_scope); +static auto ResolveNames(Expression& expression, + const StaticScope& enclosing_scope) + -> ErrorOr; +static auto ResolveNames(Pattern& pattern, StaticScope& enclosing_scope) + -> ErrorOr; +static auto ResolveNames(Statement& statement, StaticScope& enclosing_scope) + -> ErrorOr; +static auto ResolveNames(Declaration& declaration, StaticScope& enclosing_scope) + -> ErrorOr; -static void ResolveNames(Expression& expression, - const StaticScope& enclosing_scope) { +static auto ResolveNames(Expression& expression, + const StaticScope& enclosing_scope) + -> ErrorOr { switch (expression.kind()) { case ExpressionKind::CallExpression: { auto& call = cast(expression); - ResolveNames(call.function(), enclosing_scope); - ResolveNames(call.argument(), enclosing_scope); + RETURN_IF_ERROR(ResolveNames(call.function(), enclosing_scope)); + RETURN_IF_ERROR(ResolveNames(call.argument(), enclosing_scope)); break; } case ExpressionKind::FunctionTypeLiteral: { auto& fun_type = cast(expression); - ResolveNames(fun_type.parameter(), enclosing_scope); - ResolveNames(fun_type.return_type(), enclosing_scope); + RETURN_IF_ERROR(ResolveNames(fun_type.parameter(), enclosing_scope)); + RETURN_IF_ERROR(ResolveNames(fun_type.return_type(), enclosing_scope)); break; } case ExpressionKind::FieldAccessExpression: - ResolveNames(cast(expression).aggregate(), - enclosing_scope); + RETURN_IF_ERROR( + ResolveNames(cast(expression).aggregate(), + enclosing_scope)); break; case ExpressionKind::IndexExpression: { auto& index = cast(expression); - ResolveNames(index.aggregate(), enclosing_scope); - ResolveNames(index.offset(), enclosing_scope); + RETURN_IF_ERROR(ResolveNames(index.aggregate(), enclosing_scope)); + RETURN_IF_ERROR(ResolveNames(index.offset(), enclosing_scope)); break; } case ExpressionKind::PrimitiveOperatorExpression: for (Nonnull operand : cast(expression).arguments()) { - ResolveNames(*operand, enclosing_scope); + RETURN_IF_ERROR(ResolveNames(*operand, enclosing_scope)); } break; case ExpressionKind::TupleLiteral: for (Nonnull field : cast(expression).fields()) { - ResolveNames(*field, enclosing_scope); + RETURN_IF_ERROR(ResolveNames(*field, enclosing_scope)); } break; case ExpressionKind::StructLiteral: for (FieldInitializer& init : cast(expression).fields()) { - ResolveNames(init.expression(), enclosing_scope); + RETURN_IF_ERROR(ResolveNames(init.expression(), enclosing_scope)); } break; case ExpressionKind::StructTypeLiteral: for (FieldInitializer& init : cast(expression).fields()) { - ResolveNames(init.expression(), enclosing_scope); + RETURN_IF_ERROR(ResolveNames(init.expression(), enclosing_scope)); } break; case ExpressionKind::IdentifierExpression: { auto& identifier = cast(expression); - identifier.set_value_node( + ASSIGN_OR_RETURN( + const auto value_node, enclosing_scope.Resolve(identifier.name(), identifier.source_loc())); + identifier.set_value_node(value_node); break; } case ExpressionKind::IntrinsicExpression: - ResolveNames(cast(expression).args(), - enclosing_scope); + RETURN_IF_ERROR(ResolveNames(cast(expression).args(), + enclosing_scope)); break; case ExpressionKind::IfExpression: { auto& if_expr = cast(expression); - ResolveNames(*if_expr.condition(), enclosing_scope); - ResolveNames(*if_expr.then_expression(), enclosing_scope); - ResolveNames(*if_expr.else_expression(), enclosing_scope); + RETURN_IF_ERROR(ResolveNames(*if_expr.condition(), enclosing_scope)); + RETURN_IF_ERROR( + ResolveNames(*if_expr.then_expression(), enclosing_scope)); + RETURN_IF_ERROR( + ResolveNames(*if_expr.else_expression(), enclosing_scope)); break; } case ExpressionKind::BoolTypeLiteral: @@ -151,140 +163,149 @@ static void ResolveNames(Expression& expression, case ExpressionKind::UnimplementedExpression: FATAL() << "Unimplemented"; } + return Success(); } -static void ResolveNames(Pattern& pattern, StaticScope& enclosing_scope) { +static auto ResolveNames(Pattern& pattern, StaticScope& enclosing_scope) + -> ErrorOr { switch (pattern.kind()) { case PatternKind::BindingPattern: { auto& binding = cast(pattern); - ResolveNames(binding.type(), enclosing_scope); + RETURN_IF_ERROR(ResolveNames(binding.type(), enclosing_scope)); if (binding.name() != AnonymousName) { - enclosing_scope.Add(binding.name(), &binding); + RETURN_IF_ERROR(enclosing_scope.Add(binding.name(), &binding)); } break; } case PatternKind::TuplePattern: for (Nonnull field : cast(pattern).fields()) { - ResolveNames(*field, enclosing_scope); + RETURN_IF_ERROR(ResolveNames(*field, enclosing_scope)); } break; case PatternKind::AlternativePattern: { auto& alternative = cast(pattern); - ResolveNames(alternative.choice_type(), enclosing_scope); - ResolveNames(alternative.arguments(), enclosing_scope); + RETURN_IF_ERROR(ResolveNames(alternative.choice_type(), enclosing_scope)); + RETURN_IF_ERROR(ResolveNames(alternative.arguments(), enclosing_scope)); break; } case PatternKind::ExpressionPattern: - ResolveNames(cast(pattern).expression(), - enclosing_scope); + RETURN_IF_ERROR(ResolveNames( + cast(pattern).expression(), enclosing_scope)); break; case PatternKind::AutoPattern: break; case PatternKind::VarPattern: - ResolveNames(cast(pattern).pattern(), enclosing_scope); + RETURN_IF_ERROR( + ResolveNames(cast(pattern).pattern(), enclosing_scope)); break; } + return Success(); } -static void ResolveNames(Statement& statement, StaticScope& enclosing_scope) { +static auto ResolveNames(Statement& statement, StaticScope& enclosing_scope) + -> ErrorOr { switch (statement.kind()) { case StatementKind::ExpressionStatement: - ResolveNames(cast(statement).expression(), - enclosing_scope); + RETURN_IF_ERROR(ResolveNames( + cast(statement).expression(), enclosing_scope)); break; case StatementKind::Assign: { auto& assign = cast(statement); - ResolveNames(assign.lhs(), enclosing_scope); - ResolveNames(assign.rhs(), enclosing_scope); + RETURN_IF_ERROR(ResolveNames(assign.lhs(), enclosing_scope)); + RETURN_IF_ERROR(ResolveNames(assign.rhs(), enclosing_scope)); break; } case StatementKind::VariableDefinition: { auto& def = cast(statement); - ResolveNames(def.init(), enclosing_scope); - ResolveNames(def.pattern(), enclosing_scope); + RETURN_IF_ERROR(ResolveNames(def.init(), enclosing_scope)); + RETURN_IF_ERROR(ResolveNames(def.pattern(), enclosing_scope)); break; } case StatementKind::If: { auto& if_stmt = cast(statement); - ResolveNames(if_stmt.condition(), enclosing_scope); - ResolveNames(if_stmt.then_block(), enclosing_scope); + RETURN_IF_ERROR(ResolveNames(if_stmt.condition(), enclosing_scope)); + RETURN_IF_ERROR(ResolveNames(if_stmt.then_block(), enclosing_scope)); if (if_stmt.else_block().has_value()) { - ResolveNames(**if_stmt.else_block(), enclosing_scope); + RETURN_IF_ERROR(ResolveNames(**if_stmt.else_block(), enclosing_scope)); } break; } case StatementKind::Return: - ResolveNames(cast(statement).expression(), enclosing_scope); + RETURN_IF_ERROR( + ResolveNames(cast(statement).expression(), enclosing_scope)); break; case StatementKind::Block: { auto& block = cast(statement); StaticScope block_scope; block_scope.AddParent(&enclosing_scope); for (Nonnull sub_statement : block.statements()) { - ResolveNames(*sub_statement, block_scope); + RETURN_IF_ERROR(ResolveNames(*sub_statement, block_scope)); } break; } case StatementKind::While: { auto& while_stmt = cast(statement); - ResolveNames(while_stmt.condition(), enclosing_scope); - ResolveNames(while_stmt.body(), enclosing_scope); + RETURN_IF_ERROR(ResolveNames(while_stmt.condition(), enclosing_scope)); + RETURN_IF_ERROR(ResolveNames(while_stmt.body(), enclosing_scope)); break; } case StatementKind::Match: { auto& match = cast(statement); - ResolveNames(match.expression(), enclosing_scope); + RETURN_IF_ERROR(ResolveNames(match.expression(), enclosing_scope)); for (Match::Clause& clause : match.clauses()) { StaticScope clause_scope; clause_scope.AddParent(&enclosing_scope); - ResolveNames(clause.pattern(), clause_scope); - ResolveNames(clause.statement(), clause_scope); + RETURN_IF_ERROR(ResolveNames(clause.pattern(), clause_scope)); + RETURN_IF_ERROR(ResolveNames(clause.statement(), clause_scope)); } break; } case StatementKind::Continuation: { auto& continuation = cast(statement); - enclosing_scope.Add(continuation.name(), &continuation); + RETURN_IF_ERROR(enclosing_scope.Add(continuation.name(), &continuation)); StaticScope continuation_scope; continuation_scope.AddParent(&enclosing_scope); - ResolveNames(cast(statement).body(), continuation_scope); + RETURN_IF_ERROR(ResolveNames(cast(statement).body(), + continuation_scope)); break; } case StatementKind::Run: - ResolveNames(cast(statement).argument(), enclosing_scope); + RETURN_IF_ERROR( + ResolveNames(cast(statement).argument(), enclosing_scope)); break; case StatementKind::Await: case StatementKind::Break: case StatementKind::Continue: break; } + return Success(); } -static void ResolveNames(Declaration& declaration, - StaticScope& enclosing_scope) { +static auto ResolveNames(Declaration& declaration, StaticScope& enclosing_scope) + -> ErrorOr { switch (declaration.kind()) { case DeclarationKind::InterfaceDeclaration: { auto& iface = cast(declaration); StaticScope iface_scope; iface_scope.AddParent(&enclosing_scope); - iface_scope.Add("Self", iface.self()); + RETURN_IF_ERROR(iface_scope.Add("Self", iface.self())); for (Nonnull member : iface.members()) { - AddExposedNames(*member, iface_scope); + RETURN_IF_ERROR(AddExposedNames(*member, iface_scope)); } for (Nonnull member : iface.members()) { - ResolveNames(*member, iface_scope); + RETURN_IF_ERROR(ResolveNames(*member, iface_scope)); } break; } case DeclarationKind::ImplDeclaration: { auto& impl = cast(declaration); - ResolveNames(impl.interface(), enclosing_scope); - ResolveNames(*impl.impl_type(), enclosing_scope); + RETURN_IF_ERROR(ResolveNames(impl.interface(), enclosing_scope)); + RETURN_IF_ERROR(ResolveNames(*impl.impl_type(), enclosing_scope)); for (Nonnull member : impl.members()) { - AddExposedNames(*member, enclosing_scope); + RETURN_IF_ERROR(AddExposedNames(*member, enclosing_scope)); } for (Nonnull member : impl.members()) { - ResolveNames(*member, enclosing_scope); + RETURN_IF_ERROR(ResolveNames(*member, enclosing_scope)); } break; } @@ -293,19 +314,19 @@ static void ResolveNames(Declaration& declaration, StaticScope function_scope; function_scope.AddParent(&enclosing_scope); for (Nonnull binding : function.deduced_parameters()) { - function_scope.Add(binding->name(), binding); - ResolveNames(binding->type(), function_scope); + RETURN_IF_ERROR(function_scope.Add(binding->name(), binding)); + RETURN_IF_ERROR(ResolveNames(binding->type(), function_scope)); } if (function.is_method()) { - ResolveNames(function.me_pattern(), function_scope); + RETURN_IF_ERROR(ResolveNames(function.me_pattern(), function_scope)); } - ResolveNames(function.param_pattern(), function_scope); + RETURN_IF_ERROR(ResolveNames(function.param_pattern(), function_scope)); if (function.return_term().type_expression().has_value()) { - ResolveNames(**function.return_term().type_expression(), - function_scope); + RETURN_IF_ERROR(ResolveNames(**function.return_term().type_expression(), + function_scope)); } if (function.body().has_value()) { - ResolveNames(**function.body(), function_scope); + RETURN_IF_ERROR(ResolveNames(**function.body(), function_scope)); } break; } @@ -313,12 +334,12 @@ static void ResolveNames(Declaration& declaration, auto& class_decl = cast(declaration); StaticScope class_scope; class_scope.AddParent(&enclosing_scope); - class_scope.Add(class_decl.name(), &class_decl); + RETURN_IF_ERROR(class_scope.Add(class_decl.name(), &class_decl)); for (Nonnull member : class_decl.members()) { - AddExposedNames(*member, class_scope); + RETURN_IF_ERROR(AddExposedNames(*member, class_scope)); } for (Nonnull member : class_decl.members()) { - ResolveNames(*member, class_scope); + RETURN_IF_ERROR(ResolveNames(*member, class_scope)); } break; } @@ -329,35 +350,37 @@ static void ResolveNames(Declaration& declaration, // need to check for duplicates. std::set alternative_names; for (Nonnull alternative : choice.alternatives()) { - ResolveNames(alternative->signature(), enclosing_scope); + RETURN_IF_ERROR( + ResolveNames(alternative->signature(), enclosing_scope)); if (!alternative_names.insert(alternative->name()).second) { - FATAL_COMPILATION_ERROR(alternative->source_loc()) - << "Duplicate name `" << alternative->name() - << "` in choice type"; + return FATAL_COMPILATION_ERROR(alternative->source_loc()) + << "Duplicate name `" << alternative->name() + << "` in choice type"; } } break; } case DeclarationKind::VariableDeclaration: { auto& var = cast(declaration); - ResolveNames(var.binding(), enclosing_scope); + RETURN_IF_ERROR(ResolveNames(var.binding(), enclosing_scope)); if (var.has_initializer()) { - ResolveNames(var.initializer(), enclosing_scope); + RETURN_IF_ERROR(ResolveNames(var.initializer(), enclosing_scope)); } break; } } + return Success(); } -void ResolveNames(AST& ast) { +auto ResolveNames(AST& ast) -> ErrorOr { StaticScope file_scope; for (auto declaration : ast.declarations) { - AddExposedNames(*declaration, file_scope); + RETURN_IF_ERROR(AddExposedNames(*declaration, file_scope)); } for (auto declaration : ast.declarations) { - ResolveNames(*declaration, file_scope); + RETURN_IF_ERROR(ResolveNames(*declaration, file_scope)); } - ResolveNames(**ast.main_call, file_scope); + return ResolveNames(**ast.main_call, file_scope); } } // namespace Carbon diff --git a/executable_semantics/interpreter/resolve_names.h b/executable_semantics/interpreter/resolve_names.h index 80e3b9938424..c66ec9cfde8e 100644 --- a/executable_semantics/interpreter/resolve_names.h +++ b/executable_semantics/interpreter/resolve_names.h @@ -11,7 +11,9 @@ namespace Carbon { // Resolves names (IdentifierExpressions) in the AST. -void ResolveNames(AST& ast); +// On failure, `ast` is left in a partial state and should not be further +// processed. +auto ResolveNames(AST& ast) -> ErrorOr; } // namespace Carbon diff --git a/executable_semantics/interpreter/type_checker.cpp b/executable_semantics/interpreter/type_checker.cpp index 9284886859c0..b1dddeec8dd2 100644 --- a/executable_semantics/interpreter/type_checker.cpp +++ b/executable_semantics/interpreter/type_checker.cpp @@ -19,6 +19,7 @@ #include "executable_semantics/interpreter/value.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/Casting.h" +#include "llvm/Support/Error.h" using llvm::cast; using llvm::dyn_cast; @@ -36,25 +37,30 @@ static void SetValue(Nonnull pattern, Nonnull value) { } } -static void ExpectExactType(SourceLocation source_loc, +static auto ExpectExactType(SourceLocation source_loc, const std::string& context, Nonnull expected, - Nonnull actual) { + Nonnull actual) -> ErrorOr { if (!TypeEqual(expected, actual)) { - FATAL_COMPILATION_ERROR(source_loc) << "type error in " << context << "\n" - << "expected: " << *expected << "\n" - << "actual: " << *actual; + return FATAL_COMPILATION_ERROR(source_loc) + << "type error in " << context << "\n" + << "expected: " << *expected << "\n" + << "actual: " << *actual; } + return Success(); } -static void ExpectPointerType(SourceLocation source_loc, +static auto ExpectPointerType(SourceLocation source_loc, const std::string& context, - Nonnull actual) { + Nonnull actual) + -> ErrorOr { if (actual->kind() != Value::Kind::PointerType) { - FATAL_COMPILATION_ERROR(source_loc) << "type error in " << context << "\n" - << "expected a pointer type\n" - << "actual: " << *actual; + return FATAL_COMPILATION_ERROR(source_loc) + << "type error in " << context << "\n" + << "expected a pointer type\n" + << "actual: " << *actual; } + return Success(); } // Returns whether *value represents a concrete type, as opposed to a @@ -105,11 +111,14 @@ static auto IsConcreteType(Nonnull value) -> bool { } } -void TypeChecker::ExpectIsConcreteType(SourceLocation source_loc, - Nonnull value) { +auto TypeChecker::ExpectIsConcreteType(SourceLocation source_loc, + Nonnull value) + -> ErrorOr { if (!IsConcreteType(value)) { - FATAL_COMPILATION_ERROR(source_loc) - << "Expected a type, but got " << *value; + return FATAL_COMPILATION_ERROR(source_loc) + << "Expected a type, but got " << *value; + } else { + return Success(); } } @@ -188,108 +197,114 @@ static auto IsImplicitlyConvertible(Nonnull source, } } -static void ExpectType(SourceLocation source_loc, const std::string& context, +static auto ExpectType(SourceLocation source_loc, const std::string& context, Nonnull expected, - Nonnull actual) { + Nonnull actual) -> ErrorOr { if (!IsImplicitlyConvertible(actual, expected)) { - FATAL_COMPILATION_ERROR(source_loc) - << "type error in " << context << ": " - << "'" << *actual << "' is not implicitly convertible to '" << *expected - << "'"; + return FATAL_COMPILATION_ERROR(source_loc) + << "type error in " << context << ": " + << "'" << *actual << "' is not implicitly convertible to '" + << *expected << "'"; + } else { + return Success(); } } -void TypeChecker::ArgumentDeduction(SourceLocation source_loc, +auto TypeChecker::ArgumentDeduction(SourceLocation source_loc, BindingMap& deduced, Nonnull param, - Nonnull arg) { + Nonnull arg) + -> ErrorOr { switch (param->kind()) { case Value::Kind::VariableType: { const auto& var_type = cast(*param); auto [it, success] = deduced.insert({&var_type.binding(), arg}); if (!success) { // TODO: can we allow implicit conversions here? - ExpectExactType(source_loc, "argument deduction", it->second, arg); + RETURN_IF_ERROR( + ExpectExactType(source_loc, "argument deduction", it->second, arg)); } - return; + return Success(); } case Value::Kind::TupleValue: { if (arg->kind() != Value::Kind::TupleValue) { - FATAL_COMPILATION_ERROR(source_loc) - << "type error in argument deduction\n" - << "expected: " << *param << "\n" - << "actual: " << *arg; + return FATAL_COMPILATION_ERROR(source_loc) + << "type error in argument deduction\n" + << "expected: " << *param << "\n" + << "actual: " << *arg; } const auto& param_tup = cast(*param); const auto& arg_tup = cast(*arg); if (param_tup.elements().size() != arg_tup.elements().size()) { - FATAL_COMPILATION_ERROR(source_loc) - << "mismatch in tuple sizes, expected " - << param_tup.elements().size() << " but got " - << arg_tup.elements().size(); + return FATAL_COMPILATION_ERROR(source_loc) + << "mismatch in tuple sizes, expected " + << param_tup.elements().size() << " but got " + << arg_tup.elements().size(); } for (size_t i = 0; i < param_tup.elements().size(); ++i) { - ArgumentDeduction(source_loc, deduced, param_tup.elements()[i], - arg_tup.elements()[i]); + RETURN_IF_ERROR(ArgumentDeduction(source_loc, deduced, + param_tup.elements()[i], + arg_tup.elements()[i])); } - return; + return Success(); } case Value::Kind::StructType: { if (arg->kind() != Value::Kind::StructType) { - FATAL_COMPILATION_ERROR(source_loc) - << "type error in argument deduction\n" - << "expected: " << *param << "\n" - << "actual: " << *arg; + return FATAL_COMPILATION_ERROR(source_loc) + << "type error in argument deduction\n" + << "expected: " << *param << "\n" + << "actual: " << *arg; } const auto& param_struct = cast(*param); const auto& arg_struct = cast(*arg); if (param_struct.fields().size() != arg_struct.fields().size()) { - FATAL_COMPILATION_ERROR(source_loc) - << "mismatch in struct field counts, expected " - << param_struct.fields().size() << " but got " - << arg_struct.fields().size(); + return FATAL_COMPILATION_ERROR(source_loc) + << "mismatch in struct field counts, expected " + << param_struct.fields().size() << " but got " + << arg_struct.fields().size(); } for (size_t i = 0; i < param_struct.fields().size(); ++i) { if (param_struct.fields()[i].name != arg_struct.fields()[i].name) { - FATAL_COMPILATION_ERROR(source_loc) - << "mismatch in field names, " << param_struct.fields()[i].name - << " != " << arg_struct.fields()[i].name; + return FATAL_COMPILATION_ERROR(source_loc) + << "mismatch in field names, " << param_struct.fields()[i].name + << " != " << arg_struct.fields()[i].name; } - ArgumentDeduction(source_loc, deduced, param_struct.fields()[i].value, - arg_struct.fields()[i].value); + RETURN_IF_ERROR(ArgumentDeduction(source_loc, deduced, + param_struct.fields()[i].value, + arg_struct.fields()[i].value)); } - return; + return Success(); } case Value::Kind::FunctionType: { if (arg->kind() != Value::Kind::FunctionType) { - FATAL_COMPILATION_ERROR(source_loc) - << "type error in argument deduction\n" - << "expected: " << *param << "\n" - << "actual: " << *arg; + return FATAL_COMPILATION_ERROR(source_loc) + << "type error in argument deduction\n" + << "expected: " << *param << "\n" + << "actual: " << *arg; } const auto& param_fn = cast(*param); const auto& arg_fn = cast(*arg); // TODO: handle situation when arg has deduced parameters. - ArgumentDeduction(source_loc, deduced, ¶m_fn.parameters(), - &arg_fn.parameters()); - ArgumentDeduction(source_loc, deduced, ¶m_fn.return_type(), - &arg_fn.return_type()); - return; + RETURN_IF_ERROR(ArgumentDeduction( + source_loc, deduced, ¶m_fn.parameters(), &arg_fn.parameters())); + RETURN_IF_ERROR(ArgumentDeduction( + source_loc, deduced, ¶m_fn.return_type(), &arg_fn.return_type())); + return Success(); } case Value::Kind::PointerType: { if (arg->kind() != Value::Kind::PointerType) { - FATAL_COMPILATION_ERROR(source_loc) - << "type error in argument deduction\n" - << "expected: " << *param << "\n" - << "actual: " << *arg; + return FATAL_COMPILATION_ERROR(source_loc) + << "type error in argument deduction\n" + << "expected: " << *param << "\n" + << "actual: " << *arg; } - ArgumentDeduction(source_loc, deduced, &cast(*param).type(), - &cast(*arg).type()); - return; + return ArgumentDeduction(source_loc, deduced, + &cast(*param).type(), + &cast(*arg).type()); } // Nothing to do in the case for `auto`. case Value::Kind::AutoType: { - return; + return Success(); } // For the following cases, we check for type convertability. case Value::Kind::ContinuationType: @@ -303,8 +318,7 @@ void TypeChecker::ArgumentDeduction(SourceLocation source_loc, case Value::Kind::TypeOfClassType: case Value::Kind::TypeOfInterfaceType: case Value::Kind::TypeOfChoiceType: - ExpectType(source_loc, "argument deduction", param, arg); - return; + return ExpectType(source_loc, "argument deduction", param, arg); // The rest of these cases should never happen. case Value::Kind::Witness: case Value::Kind::IntValue: @@ -395,8 +409,9 @@ auto TypeChecker::Substitute( } } -void TypeChecker::TypeCheckExp(Nonnull e, - const ImplScope& impl_scope) { +auto TypeChecker::TypeCheckExp(Nonnull e, + const ImplScope& impl_scope) + -> ErrorOr { if (trace_) { llvm::outs() << "checking expression " << *e; llvm::outs() << "\nconstants: "; @@ -406,51 +421,55 @@ void TypeChecker::TypeCheckExp(Nonnull e, switch (e->kind()) { case ExpressionKind::IndexExpression: { auto& index = cast(*e); - TypeCheckExp(&index.aggregate(), impl_scope); + RETURN_IF_ERROR(TypeCheckExp(&index.aggregate(), impl_scope)); const Value& aggregate_type = index.aggregate().static_type(); switch (aggregate_type.kind()) { case Value::Kind::TupleValue: { const auto& tuple_type = cast(aggregate_type); - int i = cast(*InterpExp(&index.offset(), arena_, trace_)) - .value(); + ASSIGN_OR_RETURN(auto offset_value, + InterpExp(&index.offset(), arena_, trace_)); + int i = cast(*offset_value).value(); if (i < 0 || i >= static_cast(tuple_type.elements().size())) { - FATAL_COMPILATION_ERROR(e->source_loc()) - << "index " << i << " is out of range for type " << tuple_type; + return FATAL_COMPILATION_ERROR(e->source_loc()) + << "index " << i << " is out of range for type " + << tuple_type; } index.set_static_type(tuple_type.elements()[i]); index.set_value_category(index.aggregate().value_category()); - return; + return Success(); } default: - FATAL_COMPILATION_ERROR(e->source_loc()) << "expected a tuple"; + return FATAL_COMPILATION_ERROR(e->source_loc()) << "expected a tuple"; } } case ExpressionKind::TupleLiteral: { std::vector> arg_types; for (auto& arg : cast(*e).fields()) { - TypeCheckExp(arg, impl_scope); + RETURN_IF_ERROR(TypeCheckExp(arg, impl_scope)); arg_types.push_back(&arg->static_type()); } e->set_static_type(arena_->New(std::move(arg_types))); e->set_value_category(ValueCategory::Let); - return; + return Success(); } case ExpressionKind::StructLiteral: { std::vector arg_types; for (auto& arg : cast(*e).fields()) { - TypeCheckExp(&arg.expression(), impl_scope); + RETURN_IF_ERROR(TypeCheckExp(&arg.expression(), impl_scope)); arg_types.push_back({arg.name(), &arg.expression().static_type()}); } e->set_static_type(arena_->New(std::move(arg_types))); e->set_value_category(ValueCategory::Let); - return; + return Success(); } case ExpressionKind::StructTypeLiteral: { auto& struct_type = cast(*e); for (auto& arg : struct_type.fields()) { - TypeCheckExp(&arg.expression(), impl_scope); - ExpectIsConcreteType(arg.expression().source_loc(), - InterpExp(&arg.expression(), arena_, trace_)); + RETURN_IF_ERROR(TypeCheckExp(&arg.expression(), impl_scope)); + ASSIGN_OR_RETURN(auto value, + InterpExp(&arg.expression(), arena_, trace_)); + RETURN_IF_ERROR( + ExpectIsConcreteType(arg.expression().source_loc(), value)); } if (struct_type.fields().empty()) { // `{}` is the type of `{}`, just as `()` is the type of `()`. @@ -462,11 +481,11 @@ void TypeChecker::TypeCheckExp(Nonnull e, struct_type.set_static_type(arena_->New()); } e->set_value_category(ValueCategory::Let); - return; + return Success(); } case ExpressionKind::FieldAccessExpression: { auto& access = cast(*e); - TypeCheckExp(&access.aggregate(), impl_scope); + RETURN_IF_ERROR(TypeCheckExp(&access.aggregate(), impl_scope)); const Value& aggregate_type = access.aggregate().static_type(); switch (aggregate_type.kind()) { case Value::Kind::StructType: { @@ -475,12 +494,12 @@ void TypeChecker::TypeCheckExp(Nonnull e, if (access.field() == field_name) { access.set_static_type(field_type); access.set_value_category(access.aggregate().value_category()); - return; + return Success(); } } - FATAL_COMPILATION_ERROR(access.source_loc()) - << "struct " << struct_type << " does not have a field named " - << access.field(); + return FATAL_COMPILATION_ERROR(access.source_loc()) + << "struct " << struct_type << " does not have a field named " + << access.field(); } case Value::Kind::NominalClassType: { const auto& t_class = cast(aggregate_type); @@ -500,11 +519,11 @@ void TypeChecker::TypeCheckExp(Nonnull e, << " is not a field or method"; break; } - return; + return Success(); } else { - FATAL_COMPILATION_ERROR(e->source_loc()) - << "class " << t_class.declaration().name() - << " does not have a field named " << access.field(); + return FATAL_COMPILATION_ERROR(e->source_loc()) + << "class " << t_class.declaration().name() + << " does not have a field named " << access.field(); } } case Value::Kind::TypeOfChoiceType: { @@ -513,15 +532,15 @@ void TypeChecker::TypeCheckExp(Nonnull e, std::optional> parameter_types = choice.FindAlternative(access.field()); if (!parameter_types.has_value()) { - FATAL_COMPILATION_ERROR(e->source_loc()) - << "choice " << choice.name() << " does not have a field named " - << access.field(); + return FATAL_COMPILATION_ERROR(e->source_loc()) + << "choice " << choice.name() + << " does not have a field named " << access.field(); } access.set_static_type(arena_->New( std::vector>(), *parameter_types, &aggregate_type, std::vector>())); access.set_value_category(ValueCategory::Let); - return; + return Success(); } case Value::Kind::TypeOfClassType: { const NominalClassType& class_type = @@ -537,17 +556,17 @@ void TypeChecker::TypeCheckExp(Nonnull e, } access.set_static_type(&(*member)->static_type()); access.set_value_category(ValueCategory::Let); - return; + return Success(); } default: break; } - FATAL_COMPILATION_ERROR(access.source_loc()) - << access.field() << " is not a class function"; + return FATAL_COMPILATION_ERROR(access.source_loc()) + << access.field() << " is not a class function"; } else { - FATAL_COMPILATION_ERROR(access.source_loc()) - << class_type << " does not have a class function named " - << access.field(); + return FATAL_COMPILATION_ERROR(access.source_loc()) + << class_type << " does not have a class function named " + << access.field(); } } case Value::Kind::VariableType: { @@ -568,24 +587,26 @@ void TypeChecker::TypeCheckExp(Nonnull e, Substitute(self_map, &member_type); access.set_static_type(inst_member_type); access.set_impl(*var_type.binding().impl_binding()); - return; + return Success(); } else { - FATAL_COMPILATION_ERROR(e->source_loc()) - << "field access, " << access.field() << " not in " - << iface_decl.name(); + return FATAL_COMPILATION_ERROR(e->source_loc()) + << "field access, " << access.field() << " not in " + << iface_decl.name(); } break; } default: break; } - FATAL_COMPILATION_ERROR(e->source_loc()) - << "field access, unexpected " << aggregate_type << " in " << *e; + return FATAL_COMPILATION_ERROR(e->source_loc()) + << "field access, unexpected " << aggregate_type << " in " + << *e; break; } default: - FATAL_COMPILATION_ERROR(e->source_loc()) - << "field access, unexpected " << aggregate_type << " in " << *e; + return FATAL_COMPILATION_ERROR(e->source_loc()) + << "field access, unexpected " << aggregate_type << " in " + << *e; } } case ExpressionKind::IdentifierExpression: { @@ -596,130 +617,133 @@ void TypeChecker::TypeCheckExp(Nonnull e, cast(ident.value_node().base()); if (!function.has_static_type()) { CHECK(function.return_term().is_auto()); - FATAL_COMPILATION_ERROR(ident.source_loc()) - << "Function calls itself, but has a deduced return type"; + return FATAL_COMPILATION_ERROR(ident.source_loc()) + << "Function calls itself, but has a deduced return type"; } } ident.set_static_type(&ident.value_node().static_type()); ident.set_value_category(ident.value_node().value_category()); - return; + return Success(); } case ExpressionKind::IntLiteral: e->set_value_category(ValueCategory::Let); e->set_static_type(arena_->New()); - return; + return Success(); case ExpressionKind::BoolLiteral: e->set_value_category(ValueCategory::Let); e->set_static_type(arena_->New()); - return; + return Success(); case ExpressionKind::PrimitiveOperatorExpression: { auto& op = cast(*e); std::vector> ts; for (Nonnull argument : op.arguments()) { - TypeCheckExp(argument, impl_scope); + RETURN_IF_ERROR(TypeCheckExp(argument, impl_scope)); ts.push_back(&argument->static_type()); } switch (op.op()) { case Operator::Neg: - ExpectExactType(e->source_loc(), "negation", arena_->New(), - ts[0]); + RETURN_IF_ERROR(ExpectExactType(e->source_loc(), "negation", + arena_->New(), ts[0])); op.set_static_type(arena_->New()); op.set_value_category(ValueCategory::Let); - return; + return Success(); case Operator::Add: - ExpectExactType(e->source_loc(), "addition(1)", - arena_->New(), ts[0]); - ExpectExactType(e->source_loc(), "addition(2)", - arena_->New(), ts[1]); + RETURN_IF_ERROR(ExpectExactType(e->source_loc(), "addition(1)", + arena_->New(), ts[0])); + RETURN_IF_ERROR(ExpectExactType(e->source_loc(), "addition(2)", + arena_->New(), ts[1])); op.set_static_type(arena_->New()); op.set_value_category(ValueCategory::Let); - return; + return Success(); case Operator::Sub: - ExpectExactType(e->source_loc(), "subtraction(1)", - arena_->New(), ts[0]); - ExpectExactType(e->source_loc(), "subtraction(2)", - arena_->New(), ts[1]); + RETURN_IF_ERROR(ExpectExactType(e->source_loc(), "subtraction(1)", + arena_->New(), ts[0])); + RETURN_IF_ERROR(ExpectExactType(e->source_loc(), "subtraction(2)", + arena_->New(), ts[1])); op.set_static_type(arena_->New()); op.set_value_category(ValueCategory::Let); - return; + return Success(); case Operator::Mul: - ExpectExactType(e->source_loc(), "multiplication(1)", - arena_->New(), ts[0]); - ExpectExactType(e->source_loc(), "multiplication(2)", - arena_->New(), ts[1]); + RETURN_IF_ERROR(ExpectExactType(e->source_loc(), "multiplication(1)", + arena_->New(), ts[0])); + RETURN_IF_ERROR(ExpectExactType(e->source_loc(), "multiplication(2)", + arena_->New(), ts[1])); op.set_static_type(arena_->New()); op.set_value_category(ValueCategory::Let); - return; + return Success(); case Operator::And: - ExpectExactType(e->source_loc(), "&&(1)", arena_->New(), - ts[0]); - ExpectExactType(e->source_loc(), "&&(2)", arena_->New(), - ts[1]); + RETURN_IF_ERROR(ExpectExactType(e->source_loc(), "&&(1)", + arena_->New(), ts[0])); + RETURN_IF_ERROR(ExpectExactType(e->source_loc(), "&&(2)", + arena_->New(), ts[1])); op.set_static_type(arena_->New()); op.set_value_category(ValueCategory::Let); - return; + return Success(); case Operator::Or: - ExpectExactType(e->source_loc(), "||(1)", arena_->New(), - ts[0]); - ExpectExactType(e->source_loc(), "||(2)", arena_->New(), - ts[1]); + RETURN_IF_ERROR(ExpectExactType(e->source_loc(), "||(1)", + arena_->New(), ts[0])); + RETURN_IF_ERROR(ExpectExactType(e->source_loc(), "||(2)", + arena_->New(), ts[1])); op.set_static_type(arena_->New()); op.set_value_category(ValueCategory::Let); - return; + return Success(); case Operator::Not: - ExpectExactType(e->source_loc(), "!", arena_->New(), ts[0]); + RETURN_IF_ERROR(ExpectExactType(e->source_loc(), "!", + arena_->New(), ts[0])); op.set_static_type(arena_->New()); op.set_value_category(ValueCategory::Let); - return; + return Success(); case Operator::Eq: - ExpectExactType(e->source_loc(), "==", ts[0], ts[1]); + RETURN_IF_ERROR(ExpectExactType(e->source_loc(), "==", ts[0], ts[1])); op.set_static_type(arena_->New()); op.set_value_category(ValueCategory::Let); - return; + return Success(); case Operator::Deref: - ExpectPointerType(e->source_loc(), "*", ts[0]); + RETURN_IF_ERROR(ExpectPointerType(e->source_loc(), "*", ts[0])); op.set_static_type(&cast(*ts[0]).type()); op.set_value_category(ValueCategory::Var); - return; + return Success(); case Operator::Ptr: - ExpectExactType(e->source_loc(), "*", arena_->New(), ts[0]); + RETURN_IF_ERROR(ExpectExactType(e->source_loc(), "*", + arena_->New(), ts[0])); op.set_static_type(arena_->New()); op.set_value_category(ValueCategory::Let); - return; + return Success(); case Operator::AddressOf: if (op.arguments()[0]->value_category() != ValueCategory::Var) { - FATAL_COMPILATION_ERROR(op.arguments()[0]->source_loc()) - << "Argument to " << ToString(op.op()) - << " should be an lvalue."; + return FATAL_COMPILATION_ERROR(op.arguments()[0]->source_loc()) + << "Argument to " << ToString(op.op()) + << " should be an lvalue."; } op.set_static_type(arena_->New(ts[0])); op.set_value_category(ValueCategory::Let); - return; + return Success(); } break; } case ExpressionKind::CallExpression: { auto& call = cast(*e); - TypeCheckExp(&call.function(), impl_scope); + RETURN_IF_ERROR(TypeCheckExp(&call.function(), impl_scope)); switch (call.function().static_type().kind()) { case Value::Kind::FunctionType: { const auto& fun_t = cast(call.function().static_type()); - TypeCheckExp(&call.argument(), impl_scope); + RETURN_IF_ERROR(TypeCheckExp(&call.argument(), impl_scope)); Nonnull parameters = &fun_t.parameters(); Nonnull return_type = &fun_t.return_type(); if (!fun_t.deduced().empty()) { BindingMap deduced_args; - ArgumentDeduction(e->source_loc(), deduced_args, parameters, - &call.argument().static_type()); + RETURN_IF_ERROR(ArgumentDeduction(e->source_loc(), deduced_args, + parameters, + &call.argument().static_type())); for (Nonnull deduced_param : fun_t.deduced()) { // TODO: change the following to a CHECK once the real checking // has been added to the type checking of function signatures. if (auto it = deduced_args.find(deduced_param); it == deduced_args.end()) { - FATAL_COMPILATION_ERROR(e->source_loc()) - << "could not deduce type argument for type parameter " - << deduced_param->name(); + return FATAL_COMPILATION_ERROR(e->source_loc()) + << "could not deduce type argument for type parameter " + << deduced_param->name(); } } parameters = Substitute(deduced_args, parameters); @@ -730,66 +754,73 @@ void TypeChecker::TypeCheckExp(Nonnull e, fun_t.impl_bindings()) { switch (impl_binding->interface()->kind()) { case Value::Kind::InterfaceType: { - ValueNodeView impl = impl_scope.Resolve( - impl_binding->interface(), - deduced_args[impl_binding->type_var()], e->source_loc()); + ASSIGN_OR_RETURN( + ValueNodeView impl, + impl_scope.Resolve(impl_binding->interface(), + deduced_args[impl_binding->type_var()], + e->source_loc())); impls.emplace(impl_binding, impl); break; } case Value::Kind::TypeType: break; default: - FATAL_COMPILATION_ERROR(e->source_loc()) - << "unexpected type of deduced parameter " - << *impl_binding->interface(); + return FATAL_COMPILATION_ERROR(e->source_loc()) + << "unexpected type of deduced parameter " + << *impl_binding->interface(); } } call.set_impls(impls); } else { - ExpectType(e->source_loc(), "call", parameters, - &call.argument().static_type()); + RETURN_IF_ERROR(ExpectType(e->source_loc(), "call", parameters, + &call.argument().static_type())); } call.set_static_type(return_type); call.set_value_category(ValueCategory::Let); - return; + return Success(); } default: { - FATAL_COMPILATION_ERROR(e->source_loc()) - << "in call, expected a function\n" - << *e; + return FATAL_COMPILATION_ERROR(e->source_loc()) + << "in call, expected a function\n" + << *e; } } break; } case ExpressionKind::FunctionTypeLiteral: { auto& fn = cast(*e); - ExpectIsConcreteType(fn.parameter().source_loc(), - InterpExp(&fn.parameter(), arena_, trace_)); - ExpectIsConcreteType(fn.return_type().source_loc(), - InterpExp(&fn.return_type(), arena_, trace_)); + ASSIGN_OR_RETURN(Nonnull param_type, + InterpExp(&fn.parameter(), arena_, trace_)); + RETURN_IF_ERROR( + ExpectIsConcreteType(fn.parameter().source_loc(), param_type)); + ASSIGN_OR_RETURN(Nonnull ret_type, + InterpExp(&fn.return_type(), arena_, trace_)); + RETURN_IF_ERROR( + ExpectIsConcreteType(fn.return_type().source_loc(), ret_type)); fn.set_static_type(arena_->New()); fn.set_value_category(ValueCategory::Let); - return; + return Success(); } case ExpressionKind::StringLiteral: e->set_static_type(arena_->New()); e->set_value_category(ValueCategory::Let); - return; + return Success(); case ExpressionKind::IntrinsicExpression: { auto& intrinsic_exp = cast(*e); - TypeCheckExp(&intrinsic_exp.args(), impl_scope); + RETURN_IF_ERROR(TypeCheckExp(&intrinsic_exp.args(), impl_scope)); switch (cast(*e).intrinsic()) { case IntrinsicExpression::Intrinsic::Print: if (intrinsic_exp.args().fields().size() != 1) { - FATAL_COMPILATION_ERROR(e->source_loc()) - << "__intrinsic_print takes 1 argument"; + return FATAL_COMPILATION_ERROR(e->source_loc()) + << "__intrinsic_print takes 1 argument"; } - ExpectType(e->source_loc(), "__intrinsic_print argument", - arena_->New(), - &intrinsic_exp.args().fields()[0]->static_type()); + RETURN_IF_ERROR( + ExpectType(e->source_loc(), "__intrinsic_print argument", + arena_->New(), + &intrinsic_exp.args().fields()[0]->static_type())); e->set_static_type(TupleValue::Empty()); e->set_value_category(ValueCategory::Let); - return; + return Success(); } } case ExpressionKind::IntTypeLiteral: @@ -799,31 +830,34 @@ void TypeChecker::TypeCheckExp(Nonnull e, case ExpressionKind::ContinuationTypeLiteral: e->set_value_category(ValueCategory::Let); e->set_static_type(arena_->New()); - return; + return Success(); case ExpressionKind::IfExpression: { auto& if_expr = cast(*e); - TypeCheckExp(if_expr.condition(), impl_scope); - ExpectType(if_expr.source_loc(), "condition of `if`", - arena_->New(), &if_expr.condition()->static_type()); + RETURN_IF_ERROR(TypeCheckExp(if_expr.condition(), impl_scope)); + RETURN_IF_ERROR(ExpectType(if_expr.source_loc(), "condition of `if`", + arena_->New(), + &if_expr.condition()->static_type())); // TODO: Compute the common type and convert both operands to it. - TypeCheckExp(if_expr.then_expression(), impl_scope); - TypeCheckExp(if_expr.else_expression(), impl_scope); - ExpectExactType(e->source_loc(), "expression of `if` expression", - &if_expr.then_expression()->static_type(), - &if_expr.else_expression()->static_type()); + RETURN_IF_ERROR(TypeCheckExp(if_expr.then_expression(), impl_scope)); + RETURN_IF_ERROR(TypeCheckExp(if_expr.else_expression(), impl_scope)); + RETURN_IF_ERROR( + ExpectExactType(e->source_loc(), "expression of `if` expression", + &if_expr.then_expression()->static_type(), + &if_expr.else_expression()->static_type())); e->set_static_type(&if_expr.then_expression()->static_type()); e->set_value_category(ValueCategory::Let); - return; + return Success(); } case ExpressionKind::UnimplementedExpression: FATAL() << "Unimplemented: " << *e; } } -void TypeChecker::TypeCheckPattern( +auto TypeChecker::TypeCheckPattern( Nonnull p, std::optional> expected, - const ImplScope& impl_scope, ValueCategory enclosing_value_category) { + const ImplScope& impl_scope, ValueCategory enclosing_value_category) + -> ErrorOr { if (trace_) { llvm::outs() << "checking pattern " << *p; if (expected) { @@ -836,50 +870,54 @@ void TypeChecker::TypeCheckPattern( switch (p->kind()) { case PatternKind::AutoPattern: { p->set_static_type(arena_->New()); - return; + return Success(); } case PatternKind::BindingPattern: { auto& binding = cast(*p); if (!GetBindings(binding.type()).empty()) { - FATAL_COMPILATION_ERROR(binding.type().source_loc()) - << "The type of a binding pattern cannot contain bindings."; + return FATAL_COMPILATION_ERROR(binding.type().source_loc()) + << "The type of a binding pattern cannot contain bindings."; } - TypeCheckPattern(&binding.type(), std::nullopt, impl_scope, - enclosing_value_category); - Nonnull type = - InterpPattern(&binding.type(), arena_, trace_); + RETURN_IF_ERROR(TypeCheckPattern(&binding.type(), std::nullopt, + impl_scope, enclosing_value_category)); + ASSIGN_OR_RETURN(Nonnull type, + InterpPattern(&binding.type(), arena_, trace_)); if (expected) { if (IsConcreteType(type)) { - ExpectType(p->source_loc(), "name binding", type, *expected); + RETURN_IF_ERROR( + ExpectType(p->source_loc(), "name binding", type, *expected)); } else { if (!PatternMatch(type, *expected, binding.type().source_loc(), std::nullopt)) { - FATAL_COMPILATION_ERROR(binding.type().source_loc()) - << "Type pattern '" << *type << "' does not match actual type '" - << **expected << "'"; + return FATAL_COMPILATION_ERROR(binding.type().source_loc()) + << "Type pattern '" << *type + << "' does not match actual type '" << **expected << "'"; } type = *expected; } } - ExpectIsConcreteType(binding.source_loc(), type); + RETURN_IF_ERROR(ExpectIsConcreteType(binding.source_loc(), type)); binding.set_static_type(type); - SetValue(&binding, InterpPattern(&binding, arena_, trace_)); + ASSIGN_OR_RETURN(Nonnull binding_value, + InterpPattern(&binding, arena_, trace_)); + SetValue(&binding, binding_value); if (!binding.has_value_category()) { binding.set_value_category(enclosing_value_category); } - return; + return Success(); } case PatternKind::TuplePattern: { auto& tuple = cast(*p); std::vector> field_types; if (expected && (*expected)->kind() != Value::Kind::TupleValue) { - FATAL_COMPILATION_ERROR(p->source_loc()) << "didn't expect a tuple"; + return FATAL_COMPILATION_ERROR(p->source_loc()) + << "didn't expect a tuple"; } if (expected && tuple.fields().size() != cast(**expected).elements().size()) { - FATAL_COMPILATION_ERROR(tuple.source_loc()) - << "tuples of different length"; + return FATAL_COMPILATION_ERROR(tuple.source_loc()) + << "tuples of different length"; } for (size_t i = 0; i < tuple.fields().size(); ++i) { Nonnull field = tuple.fields()[i]; @@ -887,25 +925,28 @@ void TypeChecker::TypeCheckPattern( if (expected) { expected_field_type = cast(**expected).elements()[i]; } - TypeCheckPattern(field, expected_field_type, impl_scope, - enclosing_value_category); + RETURN_IF_ERROR(TypeCheckPattern(field, expected_field_type, impl_scope, + enclosing_value_category)); field_types.push_back(&field->static_type()); } tuple.set_static_type(arena_->New(std::move(field_types))); - SetValue(&tuple, InterpPattern(&tuple, arena_, trace_)); - return; + ASSIGN_OR_RETURN(Nonnull tuple_value, + InterpPattern(&tuple, arena_, trace_)); + SetValue(&tuple, tuple_value); + return Success(); } case PatternKind::AlternativePattern: { auto& alternative = cast(*p); - TypeCheckExp(&alternative.choice_type(), impl_scope); + RETURN_IF_ERROR(TypeCheckExp(&alternative.choice_type(), impl_scope)); if (alternative.choice_type().static_type().kind() != Value::Kind::TypeOfChoiceType) { - FATAL_COMPILATION_ERROR(alternative.source_loc()) - << "alternative pattern does not name a choice type."; + return FATAL_COMPILATION_ERROR(alternative.source_loc()) + << "alternative pattern does not name a choice type."; } if (expected) { - ExpectExactType(alternative.source_loc(), "alternative pattern", - *expected, &alternative.choice_type().static_type()); + RETURN_IF_ERROR(ExpectExactType( + alternative.source_loc(), "alternative pattern", *expected, + &alternative.choice_type().static_type())); } const ChoiceType& choice_type = cast(alternative.choice_type().static_type()) @@ -914,134 +955,148 @@ void TypeChecker::TypeCheckPattern( cast(choice_type) .FindAlternative(alternative.alternative_name()); if (parameter_types == std::nullopt) { - FATAL_COMPILATION_ERROR(alternative.source_loc()) - << "'" << alternative.alternative_name() - << "' is not an alternative of " << choice_type; + return FATAL_COMPILATION_ERROR(alternative.source_loc()) + << "'" << alternative.alternative_name() + << "' is not an alternative of " << choice_type; } - TypeCheckPattern(&alternative.arguments(), *parameter_types, impl_scope, - enclosing_value_category); + RETURN_IF_ERROR(TypeCheckPattern(&alternative.arguments(), + *parameter_types, impl_scope, + enclosing_value_category)); alternative.set_static_type(&choice_type); - SetValue(&alternative, InterpPattern(&alternative, arena_, trace_)); - return; + ASSIGN_OR_RETURN(Nonnull alternative_value, + InterpPattern(&alternative, arena_, trace_)); + SetValue(&alternative, alternative_value); + return Success(); } case PatternKind::ExpressionPattern: { auto& expression = cast(*p).expression(); - TypeCheckExp(&expression, impl_scope); + RETURN_IF_ERROR(TypeCheckExp(&expression, impl_scope)); p->set_static_type(&expression.static_type()); - SetValue(p, InterpPattern(p, arena_, trace_)); - return; + ASSIGN_OR_RETURN(Nonnull expr_value, + InterpPattern(p, arena_, trace_)); + SetValue(p, expr_value); + return Success(); } case PatternKind::VarPattern: auto& let_var_pattern = cast(*p); - TypeCheckPattern(&let_var_pattern.pattern(), expected, impl_scope, - let_var_pattern.value_category()); + RETURN_IF_ERROR(TypeCheckPattern(&let_var_pattern.pattern(), expected, + impl_scope, + let_var_pattern.value_category())); let_var_pattern.set_static_type(&let_var_pattern.pattern().static_type()); - SetValue(&let_var_pattern, - InterpPattern(&let_var_pattern, arena_, trace_)); + ASSIGN_OR_RETURN(Nonnull pattern_value, + InterpPattern(&let_var_pattern, arena_, trace_)); + SetValue(&let_var_pattern, pattern_value); + return Success(); } } -void TypeChecker::TypeCheckStmt(Nonnull s, - const ImplScope& impl_scope) { +auto TypeChecker::TypeCheckStmt(Nonnull s, + const ImplScope& impl_scope) + -> ErrorOr { if (trace_) { llvm::outs() << "checking statement " << *s << "\n"; } switch (s->kind()) { case StatementKind::Match: { auto& match = cast(*s); - TypeCheckExp(&match.expression(), impl_scope); + RETURN_IF_ERROR(TypeCheckExp(&match.expression(), impl_scope)); std::vector new_clauses; for (auto& clause : match.clauses()) { - TypeCheckPattern(&clause.pattern(), &match.expression().static_type(), - impl_scope, ValueCategory::Let); - TypeCheckStmt(&clause.statement(), impl_scope); + RETURN_IF_ERROR(TypeCheckPattern(&clause.pattern(), + &match.expression().static_type(), + impl_scope, ValueCategory::Let)); + RETURN_IF_ERROR(TypeCheckStmt(&clause.statement(), impl_scope)); } - return; + return Success(); } case StatementKind::While: { auto& while_stmt = cast(*s); - TypeCheckExp(&while_stmt.condition(), impl_scope); - ExpectType(s->source_loc(), "condition of `while`", - arena_->New(), - &while_stmt.condition().static_type()); - TypeCheckStmt(&while_stmt.body(), impl_scope); - return; + RETURN_IF_ERROR(TypeCheckExp(&while_stmt.condition(), impl_scope)); + RETURN_IF_ERROR(ExpectType(s->source_loc(), "condition of `while`", + arena_->New(), + &while_stmt.condition().static_type())); + RETURN_IF_ERROR(TypeCheckStmt(&while_stmt.body(), impl_scope)); + return Success(); } case StatementKind::Break: case StatementKind::Continue: - return; + return Success(); case StatementKind::Block: { auto& block = cast(*s); for (auto* block_statement : block.statements()) { - TypeCheckStmt(block_statement, impl_scope); + RETURN_IF_ERROR(TypeCheckStmt(block_statement, impl_scope)); } - return; + return Success(); } case StatementKind::VariableDefinition: { auto& var = cast(*s); - TypeCheckExp(&var.init(), impl_scope); + RETURN_IF_ERROR(TypeCheckExp(&var.init(), impl_scope)); const Value& rhs_ty = var.init().static_type(); - TypeCheckPattern(&var.pattern(), &rhs_ty, impl_scope, - var.value_category()); - return; + RETURN_IF_ERROR(TypeCheckPattern(&var.pattern(), &rhs_ty, impl_scope, + var.value_category())); + return Success(); } case StatementKind::Assign: { auto& assign = cast(*s); - TypeCheckExp(&assign.rhs(), impl_scope); - TypeCheckExp(&assign.lhs(), impl_scope); - ExpectType(s->source_loc(), "assign", &assign.lhs().static_type(), - &assign.rhs().static_type()); + RETURN_IF_ERROR(TypeCheckExp(&assign.rhs(), impl_scope)); + RETURN_IF_ERROR(TypeCheckExp(&assign.lhs(), impl_scope)); + RETURN_IF_ERROR(ExpectType(s->source_loc(), "assign", + &assign.lhs().static_type(), + &assign.rhs().static_type())); if (assign.lhs().value_category() != ValueCategory::Var) { - FATAL_COMPILATION_ERROR(assign.source_loc()) - << "Cannot assign to rvalue '" << assign.lhs() << "'"; + return FATAL_COMPILATION_ERROR(assign.source_loc()) + << "Cannot assign to rvalue '" << assign.lhs() << "'"; } - return; + return Success(); } case StatementKind::ExpressionStatement: { - TypeCheckExp(&cast(*s).expression(), impl_scope); - return; + RETURN_IF_ERROR(TypeCheckExp(&cast(*s).expression(), + impl_scope)); + return Success(); } case StatementKind::If: { auto& if_stmt = cast(*s); - TypeCheckExp(&if_stmt.condition(), impl_scope); - ExpectType(s->source_loc(), "condition of `if`", arena_->New(), - &if_stmt.condition().static_type()); - TypeCheckStmt(&if_stmt.then_block(), impl_scope); + RETURN_IF_ERROR(TypeCheckExp(&if_stmt.condition(), impl_scope)); + RETURN_IF_ERROR(ExpectType(s->source_loc(), "condition of `if`", + arena_->New(), + &if_stmt.condition().static_type())); + RETURN_IF_ERROR(TypeCheckStmt(&if_stmt.then_block(), impl_scope)); if (if_stmt.else_block()) { - TypeCheckStmt(*if_stmt.else_block(), impl_scope); + RETURN_IF_ERROR(TypeCheckStmt(*if_stmt.else_block(), impl_scope)); } - return; + return Success(); } case StatementKind::Return: { auto& ret = cast(*s); - TypeCheckExp(&ret.expression(), impl_scope); + RETURN_IF_ERROR(TypeCheckExp(&ret.expression(), impl_scope)); ReturnTerm& return_term = ret.function().return_term(); if (return_term.is_auto()) { return_term.set_static_type(&ret.expression().static_type()); } else { - ExpectType(s->source_loc(), "return", &return_term.static_type(), - &ret.expression().static_type()); + RETURN_IF_ERROR(ExpectType(s->source_loc(), "return", + &return_term.static_type(), + &ret.expression().static_type())); } - return; + return Success(); } case StatementKind::Continuation: { auto& cont = cast(*s); - TypeCheckStmt(&cont.body(), impl_scope); + RETURN_IF_ERROR(TypeCheckStmt(&cont.body(), impl_scope)); cont.set_static_type(arena_->New()); - return; + return Success(); } case StatementKind::Run: { auto& run = cast(*s); - TypeCheckExp(&run.argument(), impl_scope); - ExpectType(s->source_loc(), "argument of `run`", - arena_->New(), - &run.argument().static_type()); - return; + RETURN_IF_ERROR(TypeCheckExp(&run.argument(), impl_scope)); + RETURN_IF_ERROR(ExpectType(s->source_loc(), "argument of `run`", + arena_->New(), + &run.argument().static_type())); + return Success(); } case StatementKind::Await: { // nothing to do here - return; + return Success(); } } // switch } @@ -1061,84 +1116,94 @@ static auto IsExhaustive(const Match& match) -> bool { return false; } -void TypeChecker::ExpectReturnOnAllPaths( - std::optional> opt_stmt, SourceLocation source_loc) { +auto TypeChecker::ExpectReturnOnAllPaths( + std::optional> opt_stmt, SourceLocation source_loc) + -> ErrorOr { if (!opt_stmt) { - FATAL_COMPILATION_ERROR(source_loc) - << "control-flow reaches end of function that provides a `->` return " - "type without reaching a return statement"; + return FATAL_COMPILATION_ERROR(source_loc) + << "control-flow reaches end of function that provides a `->` " + "return " + "type without reaching a return statement"; } Nonnull stmt = *opt_stmt; switch (stmt->kind()) { case StatementKind::Match: { auto& match = cast(*stmt); if (!IsExhaustive(match)) { - FATAL_COMPILATION_ERROR(source_loc) - << "non-exhaustive match may allow control-flow to reach the end " - "of a function that provides a `->` return type"; + return FATAL_COMPILATION_ERROR(source_loc) + << "non-exhaustive match may allow control-flow to reach the " + "end " + "of a function that provides a `->` return type"; } std::vector new_clauses; for (auto& clause : match.clauses()) { - ExpectReturnOnAllPaths(&clause.statement(), stmt->source_loc()); + RETURN_IF_ERROR( + ExpectReturnOnAllPaths(&clause.statement(), stmt->source_loc())); } - return; + return Success(); } case StatementKind::Block: { auto& block = cast(*stmt); if (block.statements().empty()) { - FATAL_COMPILATION_ERROR(stmt->source_loc()) - << "control-flow reaches end of function that provides a `->` " - "return type without reaching a return statement"; + return FATAL_COMPILATION_ERROR(stmt->source_loc()) + << "control-flow reaches end of function that provides a `->` " + "return type without reaching a return statement"; } - ExpectReturnOnAllPaths(block.statements()[block.statements().size() - 1], - block.source_loc()); - return; + RETURN_IF_ERROR(ExpectReturnOnAllPaths( + block.statements()[block.statements().size() - 1], + block.source_loc())); + return Success(); } case StatementKind::If: { auto& if_stmt = cast(*stmt); - ExpectReturnOnAllPaths(&if_stmt.then_block(), stmt->source_loc()); - ExpectReturnOnAllPaths(if_stmt.else_block(), stmt->source_loc()); - return; + RETURN_IF_ERROR( + ExpectReturnOnAllPaths(&if_stmt.then_block(), stmt->source_loc())); + RETURN_IF_ERROR( + ExpectReturnOnAllPaths(if_stmt.else_block(), stmt->source_loc())); + return Success(); } case StatementKind::Return: - return; + return Success(); case StatementKind::Continuation: case StatementKind::Run: case StatementKind::Await: - return; + return Success(); case StatementKind::Assign: case StatementKind::ExpressionStatement: case StatementKind::While: case StatementKind::Break: case StatementKind::Continue: case StatementKind::VariableDefinition: - FATAL_COMPILATION_ERROR(stmt->source_loc()) - << "control-flow reaches end of function that provides a `->` " - "return type without reaching a return statement"; + return FATAL_COMPILATION_ERROR(stmt->source_loc()) + << "control-flow reaches end of function that provides a `->` " + "return type without reaching a return statement"; } } // TODO: Add checking to function definitions to ensure that // all deduced type parameters will be deduced. -void TypeChecker::DeclareFunctionDeclaration(Nonnull f, - const ImplScope& impl_scope) { +auto TypeChecker::DeclareFunctionDeclaration(Nonnull f, + const ImplScope& impl_scope) + -> ErrorOr { if (trace_) { llvm::outs() << "** declaring function " << f->name() << "\n"; } // Bring the deduced parameters into scope for (Nonnull deduced : f->deduced_parameters()) { - TypeCheckExp(&deduced->type(), impl_scope); + RETURN_IF_ERROR(TypeCheckExp(&deduced->type(), impl_scope)); SetConstantValue(deduced, arena_->New(deduced)); - deduced->set_static_type(InterpExp(&deduced->type(), arena_, trace_)); + ASSIGN_OR_RETURN(Nonnull deduced_type, + InterpExp(&deduced->type(), arena_, trace_)); + deduced->set_static_type(deduced_type); } // Type check the receiver pattern if (f->is_method()) { - TypeCheckPattern(&f->me_pattern(), std::nullopt, impl_scope, - ValueCategory::Let); + RETURN_IF_ERROR(TypeCheckPattern(&f->me_pattern(), std::nullopt, impl_scope, + ValueCategory::Let)); } // Type check the parameter pattern - TypeCheckPattern(&f->param_pattern(), std::nullopt, impl_scope, - ValueCategory::Let); + RETURN_IF_ERROR(TypeCheckPattern(&f->param_pattern(), std::nullopt, + impl_scope, ValueCategory::Let)); // Create the impl_bindings std::vector> impl_bindings; @@ -1156,18 +1221,19 @@ void TypeChecker::DeclareFunctionDeclaration(Nonnull f, return_expression.has_value()) { // We ignore the return value because return type expressions can't bring // new types into scope. - TypeCheckExp(*return_expression, impl_scope); + RETURN_IF_ERROR(TypeCheckExp(*return_expression, impl_scope)); // Should we be doing SetConstantValue instead? -Jeremy // And shouldn't the type of this be Type? - f->return_term().set_static_type( - InterpExp(*return_expression, arena_, trace_)); + ASSIGN_OR_RETURN(Nonnull ret_type, + InterpExp(*return_expression, arena_, trace_)); + f->return_term().set_static_type(ret_type); } else if (f->return_term().is_omitted()) { f->return_term().set_static_type(TupleValue::Empty()); } else { // We have to type-check the body in order to determine the return type. if (!f->body().has_value()) { - FATAL_COMPILATION_ERROR(f->return_term().source_loc()) - << "Function declaration has deduced return type but no body"; + return FATAL_COMPILATION_ERROR(f->return_term().source_loc()) + << "Function declaration has deduced return type but no body"; } // Bring the impl bindings into scope ImplScope function_scope; @@ -1177,13 +1243,14 @@ void TypeChecker::DeclareFunctionDeclaration(Nonnull f, *impl_binding->type_var()->constant_value(), impl_binding); } - TypeCheckStmt(*f->body(), impl_scope); + RETURN_IF_ERROR(TypeCheckStmt(*f->body(), impl_scope)); if (!f->return_term().is_omitted()) { - ExpectReturnOnAllPaths(f->body(), f->source_loc()); + RETURN_IF_ERROR(ExpectReturnOnAllPaths(f->body(), f->source_loc())); } } - ExpectIsConcreteType(f->source_loc(), &f->return_term().static_type()); + RETURN_IF_ERROR( + ExpectIsConcreteType(f->source_loc(), &f->return_term().static_type())); f->set_static_type(arena_->New( f->deduced_parameters(), &f->param_pattern().static_type(), &f->return_term().static_type(), impl_bindings)); @@ -1191,22 +1258,24 @@ void TypeChecker::DeclareFunctionDeclaration(Nonnull f, if (f->name() == "Main") { if (!f->return_term().type_expression().has_value()) { - FATAL_COMPILATION_ERROR(f->return_term().source_loc()) - << "`Main` must have an explicit return type"; + return FATAL_COMPILATION_ERROR(f->return_term().source_loc()) + << "`Main` must have an explicit return type"; } - ExpectExactType(f->return_term().source_loc(), "return type of `Main`", - arena_->New(), &f->return_term().static_type()); + RETURN_IF_ERROR(ExpectExactType( + f->return_term().source_loc(), "return type of `Main`", + arena_->New(), &f->return_term().static_type())); // TODO: Check that main doesn't have any parameters. } if (trace_) { llvm::outs() << "** finished declaring function " << f->name() << "\n"; } - return; + return Success(); } -void TypeChecker::TypeCheckFunctionDeclaration(Nonnull f, - const ImplScope& impl_scope) { +auto TypeChecker::TypeCheckFunctionDeclaration(Nonnull f, + const ImplScope& impl_scope) + -> ErrorOr { if (trace_) { llvm::outs() << "** checking function " << f->name() << "\n"; } @@ -1222,19 +1291,20 @@ void TypeChecker::TypeCheckFunctionDeclaration(Nonnull f, *impl_binding->type_var()->constant_value(), impl_binding); } - TypeCheckStmt(*f->body(), function_scope); + RETURN_IF_ERROR(TypeCheckStmt(*f->body(), function_scope)); if (!f->return_term().is_omitted()) { - ExpectReturnOnAllPaths(f->body(), f->source_loc()); + RETURN_IF_ERROR(ExpectReturnOnAllPaths(f->body(), f->source_loc())); } } if (trace_) { llvm::outs() << "** finished checking function " << f->name() << "\n"; } - return; + return Success(); } -void TypeChecker::DeclareClassDeclaration(Nonnull class_decl, - ImplScope& enclosing_scope) { +auto TypeChecker::DeclareClassDeclaration(Nonnull class_decl, + ImplScope& enclosing_scope) + -> ErrorOr { // The declarations of the members may refer to the class, so we // must set the constant value of the class and its static type // before we start processing the members. @@ -1244,59 +1314,67 @@ void TypeChecker::DeclareClassDeclaration(Nonnull class_decl, class_decl->set_static_type(arena_->New(class_type)); for (Nonnull m : class_decl->members()) { - DeclareDeclaration(m, enclosing_scope); + RETURN_IF_ERROR(DeclareDeclaration(m, enclosing_scope)); } + return Success(); } -void TypeChecker::TypeCheckClassDeclaration( - Nonnull class_decl, const ImplScope& impl_scope) { +auto TypeChecker::TypeCheckClassDeclaration( + Nonnull class_decl, const ImplScope& impl_scope) + -> ErrorOr { for (Nonnull m : class_decl->members()) { - TypeCheckDeclaration(m, impl_scope); + RETURN_IF_ERROR(TypeCheckDeclaration(m, impl_scope)); } + return Success(); } -void TypeChecker::DeclareInterfaceDeclaration( - Nonnull iface_decl, ImplScope& enclosing_scope) { +auto TypeChecker::DeclareInterfaceDeclaration( + Nonnull iface_decl, ImplScope& enclosing_scope) + -> ErrorOr { Nonnull iface_type = arena_->New(iface_decl); SetConstantValue(iface_decl, iface_type); iface_decl->set_static_type(arena_->New(iface_type)); // Process the Self parameter. - TypeCheckExp(&iface_decl->self()->type(), enclosing_scope); + RETURN_IF_ERROR(TypeCheckExp(&iface_decl->self()->type(), enclosing_scope)); iface_decl->self()->set_static_type( arena_->New(iface_decl->self())); SetConstantValue(iface_decl->self(), &iface_decl->self()->static_type()); for (Nonnull m : iface_decl->members()) { - DeclareDeclaration(m, enclosing_scope); + RETURN_IF_ERROR(DeclareDeclaration(m, enclosing_scope)); } + return Success(); } -void TypeChecker::TypeCheckInterfaceDeclaration( - Nonnull iface_decl, const ImplScope& impl_scope) { +auto TypeChecker::TypeCheckInterfaceDeclaration( + Nonnull iface_decl, const ImplScope& impl_scope) + -> ErrorOr { for (Nonnull m : iface_decl->members()) { - TypeCheckDeclaration(m, impl_scope); + RETURN_IF_ERROR(TypeCheckDeclaration(m, impl_scope)); } + return Success(); } -void TypeChecker::DeclareImplDeclaration(Nonnull impl_decl, - ImplScope& enclosing_scope) { +auto TypeChecker::DeclareImplDeclaration(Nonnull impl_decl, + ImplScope& enclosing_scope) + -> ErrorOr { if (trace_) { llvm::outs() << "declaring " << *impl_decl << "\n"; } - TypeCheckExp(&impl_decl->interface(), enclosing_scope); - Nonnull iface_type = - InterpExp(&impl_decl->interface(), arena_, trace_); + RETURN_IF_ERROR(TypeCheckExp(&impl_decl->interface(), enclosing_scope)); + ASSIGN_OR_RETURN(Nonnull iface_type, + InterpExp(&impl_decl->interface(), arena_, trace_)); const auto& iface_decl = cast(*iface_type).declaration(); impl_decl->set_interface_type(iface_type); - TypeCheckExp(impl_decl->impl_type(), enclosing_scope); - Nonnull impl_type_value = - InterpExp(impl_decl->impl_type(), arena_, trace_); + RETURN_IF_ERROR(TypeCheckExp(impl_decl->impl_type(), enclosing_scope)); + ASSIGN_OR_RETURN(Nonnull impl_type_value, + InterpExp(impl_decl->impl_type(), arena_, trace_)); enclosing_scope.Add(iface_type, impl_type_value, impl_decl); for (Nonnull m : impl_decl->members()) { - DeclareDeclaration(m, enclosing_scope); + RETURN_IF_ERROR(DeclareDeclaration(m, enclosing_scope)); } // Check that the interface is satisfied by the impl members for (Nonnull m : iface_decl.members()) { @@ -1310,132 +1388,150 @@ void TypeChecker::DeclareImplDeclaration(Nonnull impl_decl, self_map[iface_decl.self()] = impl_type_value; Nonnull iface_mem_type = Substitute(self_map, &m->static_type()); - ExpectType((*mem)->source_loc(), "member of implementation", - iface_mem_type, &(*mem)->static_type()); + RETURN_IF_ERROR(ExpectType((*mem)->source_loc(), + "member of implementation", iface_mem_type, + &(*mem)->static_type())); } else { - FATAL_COMPILATION_ERROR(impl_decl->source_loc()) - << "implementation missing " << *mem_name; + return FATAL_COMPILATION_ERROR(impl_decl->source_loc()) + << "implementation missing " << *mem_name; } } } impl_decl->set_constant_value(arena_->New(impl_decl)); + return Success(); } -void TypeChecker::TypeCheckImplDeclaration(Nonnull impl_decl, - const ImplScope& impl_scope) { +auto TypeChecker::TypeCheckImplDeclaration(Nonnull impl_decl, + const ImplScope& impl_scope) + -> ErrorOr { if (trace_) { llvm::outs() << "checking " << *impl_decl << "\n"; } for (Nonnull m : impl_decl->members()) { - TypeCheckDeclaration(m, impl_scope); + RETURN_IF_ERROR(TypeCheckDeclaration(m, impl_scope)); } if (trace_) { llvm::outs() << "finished checking impl\n"; } + return Success(); } -void TypeChecker::DeclareChoiceDeclaration(Nonnull choice, - const ImplScope& impl_scope) { +auto TypeChecker::DeclareChoiceDeclaration(Nonnull choice, + const ImplScope& impl_scope) + -> ErrorOr { std::vector alternatives; for (Nonnull alternative : choice->alternatives()) { - TypeCheckExp(&alternative->signature(), impl_scope); - auto signature = InterpExp(&alternative->signature(), arena_, trace_); + RETURN_IF_ERROR(TypeCheckExp(&alternative->signature(), impl_scope)); + ASSIGN_OR_RETURN(auto signature, + InterpExp(&alternative->signature(), arena_, trace_)); alternatives.push_back({.name = alternative->name(), .value = signature}); } auto ct = arena_->New(choice->name(), std::move(alternatives)); SetConstantValue(choice, ct); choice->set_static_type(arena_->New(ct)); + return Success(); } -void TypeChecker::TypeCheckChoiceDeclaration(Nonnull choice, - const ImplScope& impl_scope) { +auto TypeChecker::TypeCheckChoiceDeclaration(Nonnull choice, + const ImplScope& impl_scope) + -> ErrorOr { // Nothing to do here, but perhaps that will change in the future? + return Success(); } -void TypeChecker::TypeCheck(AST& ast) { +auto TypeChecker::TypeCheck(AST& ast) -> ErrorOr { ImplScope impl_scope; for (Nonnull declaration : ast.declarations) { - DeclareDeclaration(declaration, impl_scope); + RETURN_IF_ERROR(DeclareDeclaration(declaration, impl_scope)); } for (Nonnull decl : ast.declarations) { - TypeCheckDeclaration(decl, impl_scope); + RETURN_IF_ERROR(TypeCheckDeclaration(decl, impl_scope)); } - TypeCheckExp(*ast.main_call, impl_scope); + RETURN_IF_ERROR(TypeCheckExp(*ast.main_call, impl_scope)); + return Success(); } -void TypeChecker::TypeCheckDeclaration(Nonnull d, - const ImplScope& impl_scope) { +auto TypeChecker::TypeCheckDeclaration(Nonnull d, + const ImplScope& impl_scope) + -> ErrorOr { switch (d->kind()) { case DeclarationKind::InterfaceDeclaration: { - TypeCheckInterfaceDeclaration(&cast(*d), - impl_scope); + RETURN_IF_ERROR(TypeCheckInterfaceDeclaration( + &cast(*d), impl_scope)); break; } case DeclarationKind::ImplDeclaration: { - TypeCheckImplDeclaration(&cast(*d), impl_scope); + RETURN_IF_ERROR( + TypeCheckImplDeclaration(&cast(*d), impl_scope)); break; } case DeclarationKind::FunctionDeclaration: - TypeCheckFunctionDeclaration(&cast(*d), impl_scope); - return; + RETURN_IF_ERROR(TypeCheckFunctionDeclaration( + &cast(*d), impl_scope)); + return Success(); case DeclarationKind::ClassDeclaration: - TypeCheckClassDeclaration(&cast(*d), impl_scope); - return; + RETURN_IF_ERROR( + TypeCheckClassDeclaration(&cast(*d), impl_scope)); + return Success(); case DeclarationKind::ChoiceDeclaration: - TypeCheckChoiceDeclaration(&cast(*d), impl_scope); - return; + RETURN_IF_ERROR( + TypeCheckChoiceDeclaration(&cast(*d), impl_scope)); + return Success(); case DeclarationKind::VariableDeclaration: { auto& var = cast(*d); // Signals a type error if the initializing expression does not have // the declared type of the variable, otherwise returns this // declaration with annotated types. if (var.has_initializer()) { - TypeCheckExp(&var.initializer(), impl_scope); + RETURN_IF_ERROR(TypeCheckExp(&var.initializer(), impl_scope)); } const auto* binding_type = dyn_cast(&var.binding().type()); if (binding_type == nullptr) { // TODO: consider adding support for `auto` - FATAL_COMPILATION_ERROR(var.source_loc()) - << "Type of a top-level variable must be an expression."; + return FATAL_COMPILATION_ERROR(var.source_loc()) + << "Type of a top-level variable must be an expression."; } if (var.has_initializer()) { - ExpectType(var.source_loc(), "initializer of variable", - &var.static_type(), &var.initializer().static_type()); + RETURN_IF_ERROR(ExpectType(var.source_loc(), "initializer of variable", + &var.static_type(), + &var.initializer().static_type())); } - return; + return Success(); } } + return Success(); } -void TypeChecker::DeclareDeclaration(Nonnull d, - ImplScope& impl_scope) { +auto TypeChecker::DeclareDeclaration(Nonnull d, + ImplScope& impl_scope) + -> ErrorOr { switch (d->kind()) { case DeclarationKind::InterfaceDeclaration: { auto& iface_decl = cast(*d); - DeclareInterfaceDeclaration(&iface_decl, impl_scope); + RETURN_IF_ERROR(DeclareInterfaceDeclaration(&iface_decl, impl_scope)); break; } case DeclarationKind::ImplDeclaration: { auto& impl_decl = cast(*d); - DeclareImplDeclaration(&impl_decl, impl_scope); + RETURN_IF_ERROR(DeclareImplDeclaration(&impl_decl, impl_scope)); break; } case DeclarationKind::FunctionDeclaration: { auto& func_def = cast(*d); - DeclareFunctionDeclaration(&func_def, impl_scope); + RETURN_IF_ERROR(DeclareFunctionDeclaration(&func_def, impl_scope)); break; } case DeclarationKind::ClassDeclaration: { auto& class_decl = cast(*d); - DeclareClassDeclaration(&class_decl, impl_scope); + RETURN_IF_ERROR(DeclareClassDeclaration(&class_decl, impl_scope)); break; } case DeclarationKind::ChoiceDeclaration: { auto& choice = cast(*d); - DeclareChoiceDeclaration(&choice, impl_scope); + RETURN_IF_ERROR(DeclareChoiceDeclaration(&choice, impl_scope)); break; } @@ -1444,18 +1540,20 @@ void TypeChecker::DeclareDeclaration(Nonnull d, // Associate the variable name with it's declared type in the // compile-time symbol table. if (!llvm::isa(var.binding().type())) { - FATAL_COMPILATION_ERROR(var.binding().type().source_loc()) - << "Expected expression for variable type"; + return FATAL_COMPILATION_ERROR(var.binding().type().source_loc()) + << "Expected expression for variable type"; } Expression& type = cast(var.binding().type()).expression(); - TypeCheckPattern(&var.binding(), std::nullopt, impl_scope, - var.value_category()); - Nonnull declared_type = InterpExp(&type, arena_, trace_); + RETURN_IF_ERROR(TypeCheckPattern(&var.binding(), std::nullopt, impl_scope, + var.value_category())); + ASSIGN_OR_RETURN(Nonnull declared_type, + InterpExp(&type, arena_, trace_)); var.set_static_type(declared_type); break; } } + return Success(); } template diff --git a/executable_semantics/interpreter/type_checker.h b/executable_semantics/interpreter/type_checker.h index 210f9fe74e52..4569e4365970 100644 --- a/executable_semantics/interpreter/type_checker.h +++ b/executable_semantics/interpreter/type_checker.h @@ -23,7 +23,11 @@ class TypeChecker { explicit TypeChecker(Nonnull arena, bool trace) : arena_(arena), trace_(trace) {} - void TypeCheck(AST& ast); + // Type-checks `ast` and sets properties such as `static_type`, as documented + // on the individual nodes. + // On failure, `ast` is left in a partial state and should not be further + // processed. + auto TypeCheck(AST& ast) -> ErrorOr; private: // Perform type argument deduction, matching the parameter type `param` @@ -32,32 +36,35 @@ class TypeChecker { // inside the argument type. // The `deduced` parameter is an accumulator, that is, it holds the // results so-far. - static void ArgumentDeduction(SourceLocation source_loc, BindingMap& deduced, + static auto ArgumentDeduction(SourceLocation source_loc, BindingMap& deduced, Nonnull param, - Nonnull arg); + Nonnull arg) -> ErrorOr; // Traverses the AST rooted at `e`, populating the static_type() of all nodes // and ensuring they follow Carbon's typing rules. // // `values` maps variable names to their compile-time values. It is not // directly used in this function but is passed to InterExp. - void TypeCheckExp(Nonnull e, const ImplScope& impl_scope); + auto TypeCheckExp(Nonnull e, const ImplScope& impl_scope) + -> ErrorOr; // Equivalent to TypeCheckExp, but operates on the AST rooted at `p`. // // `expected` is the type that this pattern is expected to have, if the // surrounding context gives us that information. Otherwise, it is // nullopt. - void TypeCheckPattern(Nonnull p, + auto TypeCheckPattern(Nonnull p, std::optional> expected, const ImplScope& impl_scope, - ValueCategory enclosing_value_category); + ValueCategory enclosing_value_category) + -> ErrorOr; // Equivalent to TypeCheckExp, but operates on the AST rooted at `s`. // // REQUIRES: f.return_term().has_static_type() || f.return_term().is_auto(), // where `f` is nearest enclosing FunctionDeclaration of `s`. - void TypeCheckStmt(Nonnull s, const ImplScope& impl_scope); + auto TypeCheckStmt(Nonnull s, const ImplScope& impl_scope) + -> ErrorOr; // Establish the `static_type` and `constant_value` of the // declaration and all of its nested declarations. This involves the @@ -65,59 +72,68 @@ class TypeChecker { // declaration. It does not involve type checking statements and // (runtime) expressions, as in the body of a function or a method. // Dispatches to one of the following functions. - void DeclareDeclaration(Nonnull d, ImplScope& enclosing_scope); + auto DeclareDeclaration(Nonnull d, ImplScope& enclosing_scope) + -> ErrorOr; - void DeclareFunctionDeclaration(Nonnull f, - const ImplScope& enclosing_scope); + auto DeclareFunctionDeclaration(Nonnull f, + const ImplScope& enclosing_scope) + -> ErrorOr; - void DeclareClassDeclaration(Nonnull class_decl, - ImplScope& enclosing_scope); + auto DeclareClassDeclaration(Nonnull class_decl, + ImplScope& enclosing_scope) -> ErrorOr; - void DeclareInterfaceDeclaration(Nonnull iface_decl, - ImplScope& enclosing_scope); + auto DeclareInterfaceDeclaration(Nonnull iface_decl, + ImplScope& enclosing_scope) + -> ErrorOr; - void DeclareImplDeclaration(Nonnull impl_decl, - ImplScope& enclosing_scope); + auto DeclareImplDeclaration(Nonnull impl_decl, + ImplScope& enclosing_scope) -> ErrorOr; - void DeclareChoiceDeclaration(Nonnull choice, - const ImplScope& enclosing_scope); + auto DeclareChoiceDeclaration(Nonnull choice, + const ImplScope& enclosing_scope) + -> ErrorOr; // Checks the statements and (runtime) expressions within the // declaration, such as the body of a function. // Dispatches to one of the following functions. // Assumes that DeclareDeclaration has already been invoked on `d`. - void TypeCheckDeclaration(Nonnull d, - const ImplScope& impl_scope); + auto TypeCheckDeclaration(Nonnull d, + const ImplScope& impl_scope) -> ErrorOr; // Type check the body of the function. - void TypeCheckFunctionDeclaration(Nonnull f, - const ImplScope& impl_scope); + auto TypeCheckFunctionDeclaration(Nonnull f, + const ImplScope& impl_scope) + -> ErrorOr; // Type check all the members of the class. - void TypeCheckClassDeclaration(Nonnull class_decl, - const ImplScope& impl_scope); + auto TypeCheckClassDeclaration(Nonnull class_decl, + const ImplScope& impl_scope) + -> ErrorOr; // Type check all the members of the interface. - void TypeCheckInterfaceDeclaration(Nonnull iface_decl, - const ImplScope& impl_scope); + auto TypeCheckInterfaceDeclaration(Nonnull iface_decl, + const ImplScope& impl_scope) + -> ErrorOr; // Type check all the members of the implementation. - void TypeCheckImplDeclaration(Nonnull impl_decl, - const ImplScope& impl_scope); + auto TypeCheckImplDeclaration(Nonnull impl_decl, + const ImplScope& impl_scope) + -> ErrorOr; // This currently does nothing, but perhaps that will change in the future. - void TypeCheckChoiceDeclaration(Nonnull choice, - const ImplScope& impl_scope); + auto TypeCheckChoiceDeclaration(Nonnull choice, + const ImplScope& impl_scope) + -> ErrorOr; // Verifies that opt_stmt holds a statement, and it is structurally impossible // for control flow to leave that statement except via a `return`. - void ExpectReturnOnAllPaths(std::optional> opt_stmt, - SourceLocation source_loc); + auto ExpectReturnOnAllPaths(std::optional> opt_stmt, + SourceLocation source_loc) -> ErrorOr; // Verifies that *value represents a concrete type, as opposed to a // type pattern or a non-type value. - void ExpectIsConcreteType(SourceLocation source_loc, - Nonnull value); + auto ExpectIsConcreteType(SourceLocation source_loc, + Nonnull value) -> ErrorOr; auto Substitute(const std::map, Nonnull>& dict, diff --git a/executable_semantics/interpreter/value.cpp b/executable_semantics/interpreter/value.cpp index 56eea8da3f07..82ef26e16522 100644 --- a/executable_semantics/interpreter/value.cpp +++ b/executable_semantics/interpreter/value.cpp @@ -12,6 +12,7 @@ #include "executable_semantics/interpreter/action.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/Casting.h" +#include "llvm/Support/Error.h" namespace Carbon { @@ -29,7 +30,8 @@ auto StructValue::FindField(const std::string& name) const static auto GetMember(Nonnull arena, Nonnull v, const FieldPath::Component& field, - SourceLocation source_loc) -> Nonnull { + SourceLocation source_loc) + -> ErrorOr> { const std::string& f = field.name(); if (field.witness().has_value()) { @@ -42,8 +44,8 @@ static auto GetMember(Nonnull arena, Nonnull v, const auto& fun_decl = cast(**mem_decl); return arena->New(&fun_decl, v); } else { - FATAL_COMPILATION_ERROR(source_loc) - << "member " << f << " not in " << *witness; + return FATAL_COMPILATION_ERROR(source_loc) + << "member " << f << " not in " << *witness; } } default: @@ -55,7 +57,8 @@ static auto GetMember(Nonnull arena, Nonnull v, std::optional> field = cast(*v).FindField(f); if (field == std::nullopt) { - FATAL_RUNTIME_ERROR(source_loc) << "member " << f << " not in " << *v; + return FATAL_RUNTIME_ERROR(source_loc) + << "member " << f << " not in " << *v; } return *field; } @@ -70,8 +73,9 @@ static auto GetMember(Nonnull arena, Nonnull v, std::optional> func = class_type.FindFunction(f); if (func == std::nullopt) { - FATAL_RUNTIME_ERROR(source_loc) - << "member " << f << " not in " << *v << " or its " << class_type; + return FATAL_RUNTIME_ERROR(source_loc) + << "member " << f << " not in " << *v << " or its " + << class_type; } else if ((*func)->declaration().is_method()) { // Found a method. Turn it into a bound method. const auto& m = cast(**func); @@ -86,8 +90,8 @@ static auto GetMember(Nonnull arena, Nonnull v, case Value::Kind::ChoiceType: { const auto& choice = cast(*v); if (!choice.FindAlternative(f)) { - FATAL_RUNTIME_ERROR(source_loc) - << "alternative " << f << " not in " << *v; + return FATAL_RUNTIME_ERROR(source_loc) + << "alternative " << f << " not in " << *v; } return arena->New(f, choice.name()); } @@ -96,8 +100,8 @@ static auto GetMember(Nonnull arena, Nonnull v, std::optional> fun = class_type.FindFunction(f); if (fun == std::nullopt) { - FATAL_RUNTIME_ERROR(source_loc) - << "class function " << f << " not in " << *v; + return FATAL_RUNTIME_ERROR(source_loc) + << "class function " << f << " not in " << *v; } return *fun; } @@ -107,10 +111,11 @@ static auto GetMember(Nonnull arena, Nonnull v, } auto Value::GetField(Nonnull arena, const FieldPath& path, - SourceLocation source_loc) const -> Nonnull { + SourceLocation source_loc) const + -> ErrorOr> { Nonnull value(this); for (const FieldPath::Component& field : path.components_) { - value = GetMember(arena, value, field, source_loc); + ASSIGN_OR_RETURN(value, GetMember(arena, value, field, source_loc)); } return value; } @@ -120,7 +125,7 @@ static auto SetFieldImpl( std::vector::const_iterator path_begin, std::vector::const_iterator path_end, Nonnull field_value, SourceLocation source_loc) - -> Nonnull { + -> ErrorOr> { if (path_begin == path_end) { return field_value; } @@ -132,11 +137,12 @@ static auto SetFieldImpl( return element.name == (*path_begin).name(); }); if (it == elements.end()) { - FATAL_RUNTIME_ERROR(source_loc) - << "field " << (*path_begin).name() << " not in " << *value; + return FATAL_RUNTIME_ERROR(source_loc) + << "field " << (*path_begin).name() << " not in " << *value; } - it->value = SetFieldImpl(arena, it->value, path_begin + 1, path_end, - field_value, source_loc); + ASSIGN_OR_RETURN(it->value, + SetFieldImpl(arena, it->value, path_begin + 1, path_end, + field_value, source_loc)); return arena->New(elements); } case Value::Kind::NominalClassValue: { @@ -149,11 +155,13 @@ static auto SetFieldImpl( // TODO(geoffromer): update FieldPath to hold integers as well as strings. int index = std::stoi((*path_begin).name()); if (index < 0 || static_cast(index) >= elements.size()) { - FATAL_RUNTIME_ERROR(source_loc) << "index " << (*path_begin).name() - << " out of range in " << *value; + return FATAL_RUNTIME_ERROR(source_loc) + << "index " << (*path_begin).name() << " out of range in " + << *value; } - elements[index] = SetFieldImpl(arena, elements[index], path_begin + 1, - path_end, field_value, source_loc); + ASSIGN_OR_RETURN(elements[index], + SetFieldImpl(arena, elements[index], path_begin + 1, + path_end, field_value, source_loc)); return arena->New(elements); } default: @@ -163,7 +171,8 @@ static auto SetFieldImpl( auto Value::SetField(Nonnull arena, const FieldPath& path, Nonnull field_value, - SourceLocation source_loc) const -> Nonnull { + SourceLocation source_loc) const + -> ErrorOr> { return SetFieldImpl(arena, Nonnull(this), path.components_.begin(), path.components_.end(), field_value, source_loc); diff --git a/executable_semantics/interpreter/value.h b/executable_semantics/interpreter/value.h index a5631f3e1a99..693af7e17511 100644 --- a/executable_semantics/interpreter/value.h +++ b/executable_semantics/interpreter/value.h @@ -76,13 +76,15 @@ class Value { // Returns the sub-Value specified by `path`, which must be a valid field // path for *this. auto GetField(Nonnull arena, const FieldPath& path, - SourceLocation source_loc) const -> Nonnull; + SourceLocation source_loc) const + -> ErrorOr>; // Returns a copy of *this, but with the sub-Value specified by `path` // set to `field_value`. `path` must be a valid field path for *this. auto SetField(Nonnull arena, const FieldPath& path, Nonnull field_value, - SourceLocation source_loc) const -> Nonnull; + SourceLocation source_loc) const + -> ErrorOr>; // Returns the enumerator corresponding to the most-derived type of this // object. diff --git a/executable_semantics/main.cpp b/executable_semantics/main.cpp index 552bd716c84d..61e9a14ef1bc 100644 --- a/executable_semantics/main.cpp +++ b/executable_semantics/main.cpp @@ -10,6 +10,7 @@ #include #include +#include "common/error.h" #include "executable_semantics/common/arena.h" #include "executable_semantics/common/nonnull.h" #include "executable_semantics/interpreter/exec_program.h" @@ -21,18 +22,26 @@ static void AddPrelude( std::string_view prelude_file_name, Carbon::Nonnull arena, std::vector>* declarations) { - std::variant parse_result = + Carbon::ErrorOr parse_result = Carbon::Parse(arena, prelude_file_name, false); - if (std::holds_alternative(parse_result)) { + if (!parse_result.ok()) { // Try again with tracing, to help diagnose the problem. - Carbon::Parse(arena, prelude_file_name, true); - FATAL() << "Failed to parse prelude."; + Carbon::ErrorOr trace_parse_result = + Carbon::Parse(arena, prelude_file_name, true); + FATAL() << "Failed to parse prelude: " + << trace_parse_result.error().message(); } - const auto& prelude = std::get(parse_result); + const auto& prelude = *parse_result; declarations->insert(declarations->begin(), prelude.declarations.begin(), prelude.declarations.end()); } +// Prints an error message and returns error code value. +auto PrintError(const Carbon::Error& error) -> int { + llvm::errs() << error.message() << "\n"; + return EXIT_FAILURE; +} + auto main(int argc, char* argv[]) -> int { llvm::setBugReportMsg( "Please report issues to " @@ -56,18 +65,16 @@ auto main(int argc, char* argv[]) -> int { llvm::cl::ParseCommandLineOptions(argc, argv); Carbon::Arena arena; - std::variant ast_or_error = + Carbon::ErrorOr ast = Carbon::Parse(&arena, input_file_name, trace_option); - - if (auto* error = std::get_if(&ast_or_error)) { - // Diagnostic already reported to std::cerr; this is just a return code. - return *error; + if (!ast.ok()) { + return PrintError(ast.error()); } - auto& ast = std::get(ast_or_error); - - AddPrelude(prelude_file_name, &arena, &ast.declarations); + AddPrelude(prelude_file_name, &arena, &ast->declarations); // Typecheck and run the parsed program. - Carbon::ExecProgram(&arena, std::get(ast_or_error), - trace_option); + Carbon::ErrorOr result = Carbon::ExecProgram(&arena, *ast, trace_option); + if (!result.ok()) { + return PrintError(result.error()); + } } diff --git a/executable_semantics/syntax/BUILD b/executable_semantics/syntax/BUILD index 0a8c85ca92ee..5c6200365ae9 100644 --- a/executable_semantics/syntax/BUILD +++ b/executable_semantics/syntax/BUILD @@ -30,6 +30,7 @@ cc_library( deps = [ ":syntax", "@com_google_googletest//:gtest", + "@llvm-project//llvm:Support", ], ) @@ -57,6 +58,7 @@ cc_library( deps = [ ":bison_wrap", "//common:check", + "//common:error", "//common:ostream", "//common:string_helpers", "//executable_semantics/ast", diff --git a/executable_semantics/syntax/lexer.lpp b/executable_semantics/syntax/lexer.lpp index 0ade76991de8..fe42fef07867 100644 --- a/executable_semantics/syntax/lexer.lpp +++ b/executable_semantics/syntax/lexer.lpp @@ -8,20 +8,15 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include #include "common/check.h" + #include "common/error.h" #include "common/string_helpers.h" #include "executable_semantics/syntax/parse_and_lex_context.h" #include "executable_semantics/syntax/parser.h" #include "llvm/ADT/StringExtras.h" + #include "llvm/Support/FormatVariadic.h" - // Prints a newline in trace mode because trace prints an incomplete line - // "Reading a token: " which can prevent LIT from finding expected patterns. - #define FATAL_SYNTAX_ERROR(context) \ - RAW_EXITING_STREAM() << (context.trace() ? "\n" : "") \ - << "COMPILATION ERROR: " << (context.source_loc()) \ - << ": " - - // Reads and returns a single character. Fails on EOF. - char ReadChar(yyscan_t yyscanner, const Carbon::ParseAndLexContext& context); + // Reads and returns a single character. Reports an error on EOF. + auto ReadChar(yyscan_t yyscanner, Carbon::ParseAndLexContext& context) -> int; %} /* Turn off legacy bits we don't need. */ @@ -258,7 +253,13 @@ string_literal \"([^\\\"\n\v\f\r]|\\.)*\" {intrinsic_identifier} { BEGIN(AFTER_OPERAND); - return ARG_TOKEN(intrinsic_identifier, yytext); + Carbon::ErrorOr intrinsic = + Carbon::IntrinsicExpression::FindIntrinsic(yytext, context.source_loc()); + if (intrinsic.ok()) { + return ARG_TOKEN(intrinsic_identifier, *intrinsic); + } else { + return context.RecordSyntaxError(intrinsic.error().message()); + } } {identifier} { @@ -270,7 +271,8 @@ string_literal \"([^\\\"\n\v\f\r]|\\.)*\" BEGIN(AFTER_OPERAND); int val = 0; if (!llvm::to_integer(yytext, val)) { - FATAL_SYNTAX_ERROR(context) << "Invalid integer literal: " << yytext; + return context.RecordSyntaxError( + llvm::formatv("Invalid integer literal: {0}", yytext)); } return ARG_TOKEN(integer_literal, val); } @@ -280,7 +282,8 @@ string_literal \"([^\\\"\n\v\f\r]|\\.)*\" CHECK(str.consume_front("\"") && str.consume_back("\"")); std::optional unescaped = Carbon::UnescapeStringLiteral(str); if (unescaped == std::nullopt) { - FATAL_SYNTAX_ERROR(context) << "Invalid escaping in string: " << yytext; + return context.RecordSyntaxError( + llvm::formatv("Invalid escaping in string: {0}", yytext)); } return ARG_TOKEN(string_literal, *unescaped); } @@ -291,24 +294,37 @@ string_literal \"([^\\\"\n\v\f\r]|\\.)*\" // Scans for the closing """, checking for possible escape sequences // like \""". for (;;) { - char c = ReadChar(yyscanner, context); + int c = ReadChar(yyscanner, context); + if (c <= 0) { + return SIMPLE_TOKEN(END_OF_FILE); + } s.push_back(c); if (c != '"' && c != '\\') { continue; } if (c == '\\') { // \" in \""" is not a terminator. - s.push_back(ReadChar(yyscanner, context)); + c = ReadChar(yyscanner, context); + if (c <= 0) { + return SIMPLE_TOKEN(END_OF_FILE); + } + s.push_back(c); continue; } c = ReadChar(yyscanner, context); + if (c <= 0) { + return SIMPLE_TOKEN(END_OF_FILE); + } s.push_back(c); if (c != '"') { continue; } c = ReadChar(yyscanner, context); + if (c <= 0) { + return SIMPLE_TOKEN(END_OF_FILE); + } s.push_back(c); if (c == '"') { break; @@ -317,8 +333,8 @@ string_literal \"([^\\\"\n\v\f\r]|\\.)*\" Carbon::ErrorOr block_string = Carbon::ParseBlockStringLiteral(s); if (!block_string.ok()) { - FATAL_SYNTAX_ERROR(context) - << "Invalid block string: " << block_string.error(); + return context.RecordSyntaxError(llvm::formatv( + "Invalid block string: {0}", block_string.error().message())); } return ARG_TOKEN(string_literal, *block_string); } @@ -345,17 +361,17 @@ string_literal \"([^\\\"\n\v\f\r]|\\.)*\" } . { - FATAL_SYNTAX_ERROR(context) - << "invalid character '\\x" << llvm::toHex(llvm::StringRef(yytext, 1)) - << "' in source file."; + return context.RecordSyntaxError( + llvm::formatv("invalid character '\\x{0}' in source file.", + llvm::toHex(llvm::StringRef(yytext, 1)))); } %% -char ReadChar(yyscan_t yyscanner, const Carbon::ParseAndLexContext& context) { +auto ReadChar(yyscan_t yyscanner, Carbon::ParseAndLexContext& context) -> int { const int c = yyinput(yyscanner); - if (c == EOF) { - FATAL_SYNTAX_ERROR(context) << "Unexpected end of file"; + if (c <= 0) { + context.RecordSyntaxError("Unexpected end of file"); } return c; } diff --git a/executable_semantics/syntax/parse.cpp b/executable_semantics/syntax/parse.cpp index dfc451d1d4df..dcd40fb4401e 100644 --- a/executable_semantics/syntax/parse.cpp +++ b/executable_semantics/syntax/parse.cpp @@ -5,16 +5,17 @@ #include "executable_semantics/syntax/parse.h" #include "common/check.h" +#include "common/error.h" #include "executable_semantics/common/error.h" #include "executable_semantics/syntax/lexer.h" #include "executable_semantics/syntax/parse_and_lex_context.h" #include "executable_semantics/syntax/parser.h" +#include "llvm/Support/Error.h" namespace Carbon { auto ParseImpl(yyscan_t scanner, Nonnull arena, - std::string_view input_file_name, bool trace) - -> std::variant { + std::string_view input_file_name, bool trace) -> ErrorOr { // Prepare other parser arguments. std::optional ast = std::nullopt; ParseAndLexContext context(arena->New(input_file_name), trace); @@ -24,11 +25,12 @@ auto ParseImpl(yyscan_t scanner, Nonnull arena, if (trace) { parser.set_debug_level(1); } - auto syntax_error_code = parser(); - // Return an error if appropriate. - if (syntax_error_code != 0) { - return syntax_error_code; + if (auto syntax_error_code = parser(); syntax_error_code != 0) { + const std::string error_message = context.error_messages().empty() + ? "Unknown parser error" + : context.error_messages()[0]; + return Error(error_message); } // Return parse results. @@ -38,11 +40,11 @@ auto ParseImpl(yyscan_t scanner, Nonnull arena, } auto Parse(Nonnull arena, std::string_view input_file_name, bool trace) - -> std::variant { + -> ErrorOr { FILE* input_file = fopen(std::string(input_file_name).c_str(), "r"); if (input_file == nullptr) { - FATAL_PROGRAM_ERROR_NO_LINE() << "Error opening '" << input_file_name - << "': " << std::strerror(errno); + return FATAL_PROGRAM_ERROR_NO_LINE() << "Error opening '" << input_file_name + << "': " << std::strerror(errno); } // Prepare the lexer. @@ -51,8 +53,7 @@ auto Parse(Nonnull arena, std::string_view input_file_name, bool trace) auto buffer = yy_create_buffer(input_file, YY_BUF_SIZE, scanner); yy_switch_to_buffer(buffer, scanner); - std::variant result = - ParseImpl(scanner, arena, input_file_name, trace); + ErrorOr result = ParseImpl(scanner, arena, input_file_name, trace); // Clean up the lexer. yy_delete_buffer(buffer, scanner); @@ -64,7 +65,7 @@ auto Parse(Nonnull arena, std::string_view input_file_name, bool trace) auto ParseFromString(Nonnull arena, std::string_view input_file_name, std::string_view file_contents, bool trace) - -> std::variant { + -> ErrorOr { // Prepare the lexer. yyscan_t scanner; yylex_init(&scanner); @@ -72,8 +73,7 @@ auto ParseFromString(Nonnull arena, std::string_view input_file_name, yy_scan_bytes(file_contents.data(), file_contents.size(), scanner); yy_switch_to_buffer(buffer, scanner); - std::variant result = - ParseImpl(scanner, arena, input_file_name, trace); + ErrorOr result = ParseImpl(scanner, arena, input_file_name, trace); // Clean up the lexer. yy_delete_buffer(buffer, scanner); diff --git a/executable_semantics/syntax/parse.h b/executable_semantics/syntax/parse.h index 9038fcabe02c..cf3f0e7d96d7 100644 --- a/executable_semantics/syntax/parse.h +++ b/executable_semantics/syntax/parse.h @@ -13,20 +13,17 @@ namespace Carbon { -// This is the code given us by Bison, for now. -using SyntaxErrorCode = int; - // Returns the AST representing the contents of the named file, or an error code // if parsing fails. Allocations go into the provided arena. auto Parse(Nonnull arena, std::string_view input_file_name, bool trace) - -> std::variant; + -> ErrorOr; // Equivalent to `Parse`, but parses the contents of `file_contents`. // `input_file_name` is used only for reporting source locations, and does // not need to name a real file. auto ParseFromString(Nonnull arena, std::string_view input_file_name, std::string_view file_contents, bool trace) - -> std::variant; + -> ErrorOr; } // namespace Carbon diff --git a/executable_semantics/syntax/parse_and_lex_context.cpp b/executable_semantics/syntax/parse_and_lex_context.cpp index b4d26de184be..868bbe62fd5a 100644 --- a/executable_semantics/syntax/parse_and_lex_context.cpp +++ b/executable_semantics/syntax/parse_and_lex_context.cpp @@ -6,11 +6,21 @@ namespace Carbon { -auto ParseAndLexContext::PrintDiagnostic(const std::string& message) -> void { - // TODO: Do we really want this to be fatal? It makes the comment and the - // name a lie, and renders some of the other yyparse() result propagation code - // moot. - FATAL_COMPILATION_ERROR(source_loc()) << message; +auto ParseAndLexContext::RecordSyntaxError(const std::string& message, + bool prefix_with_newline) + -> Parser::symbol_type { + // Optionally adds a newline in trace mode because trace prints an incomplete + // line "Reading a token: " which can prevent LIT from finding expected + // patterns. + // TODO: support formatting of `SourceLocation` instances with formatv(). + std::string full_message; + llvm::raw_string_ostream(full_message) + << (prefix_with_newline && trace() ? "\n" : "") + << "COMPILATION ERROR: " << source_loc() << ": " << message; + error_messages_.push_back(full_message); + + // TODO: use `YYerror` token once bison is upgraded to at least 3.5. + return Parser::make_END_OF_FILE(current_token_position); } } // namespace Carbon diff --git a/executable_semantics/syntax/parse_and_lex_context.h b/executable_semantics/syntax/parse_and_lex_context.h index f8d8c0ad1155..87bb5807b786 100644 --- a/executable_semantics/syntax/parse_and_lex_context.h +++ b/executable_semantics/syntax/parse_and_lex_context.h @@ -20,8 +20,11 @@ class ParseAndLexContext { ParseAndLexContext(Nonnull input_file_name, bool trace) : input_file_name_(input_file_name), trace_(trace) {} - // Writes a syntax error diagnostic containing message to standard error. - auto PrintDiagnostic(const std::string& message) -> void; + // Formats ands records a lexer error. Returns an error token as a + // convenience. + auto RecordSyntaxError(const std::string& message, + bool prefix_with_newline = false) + -> Parser::symbol_type; auto source_loc() const -> SourceLocation { return SourceLocation(input_file_name_, @@ -33,12 +36,18 @@ class ParseAndLexContext { // The source range of the token being (or just) lex'd. location current_token_position; + auto error_messages() const -> const std::vector { + return error_messages_; + } + private: // A path to the file processed, relative to the current working directory // when *this is called. Nonnull input_file_name_; bool trace_; + + std::vector error_messages_; }; } // namespace Carbon diff --git a/executable_semantics/syntax/parse_test.cpp b/executable_semantics/syntax/parse_test.cpp index 9c196ff0dd2f..904c40354643 100644 --- a/executable_semantics/syntax/parse_test.cpp +++ b/executable_semantics/syntax/parse_test.cpp @@ -23,10 +23,10 @@ fn Foo() {} TEST(ParseTest, ParseFromString) { Arena arena; - std::variant parse_result = + ErrorOr parse_result = ParseFromString(&arena, "file.carbon", FileContents, /*trace=*/false); - ASSERT_TRUE(std::holds_alternative(parse_result)); - EXPECT_EQ(std::get(parse_result).declarations.size(), 1); + ASSERT_TRUE(parse_result.ok()); + EXPECT_EQ(parse_result->declarations.size(), 1); } } // namespace diff --git a/executable_semantics/syntax/parse_test_matchers_internal.h b/executable_semantics/syntax/parse_test_matchers_internal.h index 7713d9f36f1f..0e443408b902 100644 --- a/executable_semantics/syntax/parse_test_matchers_internal.h +++ b/executable_semantics/syntax/parse_test_matchers_internal.h @@ -12,6 +12,8 @@ #include #include "executable_semantics/syntax/parse.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/raw_ostream.h" namespace Carbon::TestingInternal { @@ -32,14 +34,14 @@ class ParsedAsMatcher { DescribeToImpl(out, /*negated=*/true); } - auto MatchAndExplain(const std::variant& result, + auto MatchAndExplain(const ErrorOr& result, ::testing::MatchResultListener* listener) const -> bool { - if (std::holds_alternative(result)) { - *listener << "holds error code " << std::get(result); + if (!result.ok()) { + *listener << "is a failed parse with error: " << result.error().message(); return false; } else { *listener << "is a successful parse whose "; - return ast_matcher_.MatchAndExplain(std::get(result), listener); + return ast_matcher_.MatchAndExplain(*result, listener); } } diff --git a/executable_semantics/syntax/parser.ypp b/executable_semantics/syntax/parser.ypp index cedd8eaddab0..70a6f324df29 100644 --- a/executable_semantics/syntax/parser.ypp +++ b/executable_semantics/syntax/parser.ypp @@ -62,6 +62,7 @@ #include "common/check.h" #include "executable_semantics/syntax/parse_and_lex_context.h" #include "llvm/ADT/StringExtras.h" + #include "llvm/Support/raw_ostream.h" } // %code top %code requires { @@ -86,13 +87,13 @@ %code { void Carbon::Parser::error(const location_type&, const std::string& message) { - context.PrintDiagnostic(message); + context.RecordSyntaxError(message); } } // %code %token integer_literal %token identifier -%token intrinsic_identifier +%token intrinsic_identifier %token sized_type_literal %token string_literal %type designator @@ -582,7 +583,16 @@ non_expression_pattern: | paren_pattern { $$ = $1; } | postfix_expression tuple_pattern - { $$ = arena->New(context.source_loc(), $1, $2); } + { + ErrorOr> alternative_pattern = + AlternativePattern::Create(arena, context.source_loc(), $1, $2); + if (alternative_pattern.ok()) { + $$ = *alternative_pattern; + } else { + context.RecordSyntaxError(alternative_pattern.error().message()); + YYERROR; + } + } | VAR non_expression_pattern { $$ = arena->New(context.source_loc(), $2); } ; @@ -804,13 +814,25 @@ receiver: function_declaration: FN identifier deduced_params receiver maybe_empty_tuple_pattern return_term block { - $$ = arena->New(context.source_loc(), $2, $3, $4, $5, - $6, $7); + ErrorOr fn = FunctionDeclaration::Create( + arena, context.source_loc(), $2, $3, $4, $5, $6, $7); + if (fn.ok()) { + $$ = *fn; + } else { + context.RecordSyntaxError(fn.error().message()); + YYERROR; + } } | FN identifier deduced_params receiver maybe_empty_tuple_pattern return_term SEMICOLON { - $$ = arena->New(context.source_loc(), $2, $3, $4, $5, - $6, std::nullopt); + ErrorOr fn = FunctionDeclaration::Create( + arena, context.source_loc(), $2, $3, $4, $5, $6, std::nullopt); + if (fn.ok()) { + $$ = *fn; + } else { + context.RecordSyntaxError(fn.error().message()); + YYERROR; + } } ; variable_declaration: identifier COLON pattern