mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-05 22:02:55 +01:00
Replaced std::exit() with return Carbon::ErrorOr for expected errors like invalid syntax (#1120)
* Replaced std::exit() with return llvm::Expected/llvm::Error<T> for expected errors like invalid syntax. * Use llvm::formatv() for formatting lexer error messages. x * Addresed merge errors. * Fixed impl scope. * Made ErrorBuilder::operator<< nodiscard, to catch code forgetting 'return' in 'return FATAL_COMPILATION_ERROR()'. * FatalComplationError() -> ParseAndLexContext::RecordLexerError(). Other usages of ERROR_TOKEN in lexer.lpp were actually supposed to be END_OF_FILE. * Update executable_semantics/syntax/parse_and_lex_context.h Co-authored-by: Jon Meow <jperkins@google.com> * Code review fixes. * Update executable_semantics/syntax/parser.ypp Co-authored-by: Jon Meow <jperkins@google.com> * More code review fixes. * Update executable_semantics/interpreter/type_checker.h Co-authored-by: Jon Meow <jperkins@google.com> * Yet more code review fixes... * Update executable_semantics/syntax/lexer.lpp Co-authored-by: Jon Meow <jperkins@google.com> * code review comments * Update executable_semantics/interpreter/interpreter.cpp Co-authored-by: Geoff Romer <gromer@google.com> * Apply suggestions from code review Co-authored-by: Jon Meow <jperkins@google.com> * Update executable_semantics/syntax/lexer.lpp Co-authored-by: Jon Meow <jperkins@google.com> * code review * code review * Apply suggestions from code review Co-authored-by: Jon Meow <jperkins@google.com> * formatted code * review comments * Switched to the new ErrorOr<V> error implementation * code review comments * fixed comment * restored ostream.h as #976 makes the change unnecesary * review comments Co-authored-by: Jon Meow <jperkins@google.com> Co-authored-by: Geoff Romer <gromer@google.com>
This commit is contained in:
committed by
GitHub
co-authored by
Jon Meow
Geoff Romer
parent
e5a87af6fe
commit
aa8a5f174d
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -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<Nonnull<AstNode*>>& deduced_params) {
|
||||
auto FunctionDeclaration::Create(
|
||||
Nonnull<Arena*> arena, SourceLocation source_loc, std::string name,
|
||||
std::vector<Nonnull<AstNode*>> deduced_params,
|
||||
std::optional<Nonnull<BindingPattern*>> me_pattern,
|
||||
Nonnull<TuplePattern*> param_pattern, ReturnTerm return_term,
|
||||
std::optional<Nonnull<Block*>> body)
|
||||
-> ErrorOr<Nonnull<FunctionDeclaration*>> {
|
||||
std::vector<Nonnull<GenericBinding*>> resolved_params;
|
||||
// Look for the `me` parameter in the `deduced_parameters`
|
||||
// and put it in the `me_pattern`.
|
||||
for (Nonnull<AstNode*> param : deduced_params) {
|
||||
switch (param->kind()) {
|
||||
case AstNodeKind::GenericBinding:
|
||||
deduced_parameters_.push_back(&cast<GenericBinding>(*param));
|
||||
resolved_params.push_back(&cast<GenericBinding>(*param));
|
||||
break;
|
||||
case AstNodeKind::BindingPattern: {
|
||||
Nonnull<BindingPattern*> bp = &cast<BindingPattern>(*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<FunctionDeclaration>(source_loc, name, resolved_params,
|
||||
me_pattern, param_pattern, return_term,
|
||||
body);
|
||||
}
|
||||
|
||||
void FunctionDeclaration::PrintDepth(int depth, llvm::raw_ostream& out) const {
|
||||
|
||||
@@ -82,20 +82,29 @@ class FunctionDeclaration : public Declaration {
|
||||
public:
|
||||
using ImplementsCarbonValueNode = void;
|
||||
|
||||
static auto Create(Nonnull<Arena*> arena, SourceLocation source_loc,
|
||||
std::string name,
|
||||
std::vector<Nonnull<AstNode*>> deduced_params,
|
||||
std::optional<Nonnull<BindingPattern*>> me_pattern,
|
||||
Nonnull<TuplePattern*> param_pattern,
|
||||
ReturnTerm return_term,
|
||||
std::optional<Nonnull<Block*>> body)
|
||||
-> ErrorOr<Nonnull<FunctionDeclaration*>>;
|
||||
|
||||
// Use `Create()` instead. This is public only so Arena::New() can call it.
|
||||
FunctionDeclaration(SourceLocation source_loc, std::string name,
|
||||
std::vector<Nonnull<AstNode*>> deduced_params,
|
||||
std::vector<Nonnull<GenericBinding*>> deduced_params,
|
||||
std::optional<Nonnull<BindingPattern*>> me_pattern,
|
||||
Nonnull<TuplePattern*> param_pattern,
|
||||
ReturnTerm return_term,
|
||||
std::optional<Nonnull<Block*>> 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<Nonnull<AstNode*>>&);
|
||||
std::string name_;
|
||||
std::vector<Nonnull<GenericBinding*>> deduced_parameters_;
|
||||
std::optional<Nonnull<BindingPattern*>> me_pattern_;
|
||||
|
||||
@@ -20,13 +20,14 @@ using llvm::isa;
|
||||
|
||||
auto IntrinsicExpression::FindIntrinsic(std::string_view name,
|
||||
SourceLocation source_loc)
|
||||
-> Intrinsic {
|
||||
-> ErrorOr<Intrinsic> {
|
||||
static const auto& intrinsic_map =
|
||||
*new std::map<std::string_view, Intrinsic>({{"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;
|
||||
}
|
||||
|
||||
@@ -465,11 +465,15 @@ class IntrinsicExpression : public Expression {
|
||||
Print,
|
||||
};
|
||||
|
||||
explicit IntrinsicExpression(std::string_view intrinsic_name,
|
||||
Nonnull<TupleLiteral*> 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<Intrinsic>;
|
||||
|
||||
explicit IntrinsicExpression(Intrinsic intrinsic, Nonnull<TupleLiteral*> 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<TupleLiteral*> args_;
|
||||
};
|
||||
|
||||
@@ -108,23 +108,15 @@ auto TuplePatternFromParenContents(Nonnull<Arena*> 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<Expression*> alternative)
|
||||
-> FieldAccessExpression& {
|
||||
auto AlternativePattern::RequireFieldAccess(Nonnull<Expression*> alternative)
|
||||
-> ErrorOr<Nonnull<FieldAccessExpression*>> {
|
||||
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<FieldAccessExpression>(*alternative);
|
||||
return &cast<FieldAccessExpression>(*alternative);
|
||||
}
|
||||
|
||||
AlternativePattern::AlternativePattern(SourceLocation source_loc,
|
||||
Nonnull<Expression*> alternative,
|
||||
Nonnull<TuplePattern*> arguments)
|
||||
: Pattern(AstNodeKind::AlternativePattern, source_loc),
|
||||
choice_type_(&RequireFieldAccess(alternative).aggregate()),
|
||||
alternative_name_(RequireFieldAccess(alternative).field()),
|
||||
arguments_(arguments) {}
|
||||
|
||||
auto ParenExpressionToParenPattern(Nonnull<Arena*> arena,
|
||||
const ParenContents<Expression>& contents)
|
||||
-> ParenContents<Pattern> {
|
||||
|
||||
@@ -218,6 +218,19 @@ auto ParenExpressionToParenPattern(Nonnull<Arena*> 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*> arena, SourceLocation source_loc,
|
||||
Nonnull<Expression*> alternative,
|
||||
Nonnull<TuplePattern*> arguments)
|
||||
-> ErrorOr<Nonnull<AlternativePattern*>> {
|
||||
ASSIGN_OR_RETURN(Nonnull<FieldAccessExpression*> field_access,
|
||||
RequireFieldAccess(alternative));
|
||||
return arena->New<AlternativePattern>(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<Expression*> alternative,
|
||||
Nonnull<TuplePattern*> 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<Expression*> alternative)
|
||||
-> ErrorOr<Nonnull<FieldAccessExpression*>>;
|
||||
|
||||
Nonnull<Expression*> choice_type_;
|
||||
std::string alternative_name_;
|
||||
Nonnull<TuplePattern*> arguments_;
|
||||
|
||||
@@ -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<Success> {
|
||||
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<ValueNodeView> result = TryResolve(name, source_loc);
|
||||
if (!result.has_value()) {
|
||||
FATAL_COMPILATION_ERROR(source_loc) << "could not resolve '" << name << "'";
|
||||
SourceLocation source_loc) const
|
||||
-> ErrorOr<ValueNodeView> {
|
||||
ASSIGN_OR_RETURN(std::optional<ValueNodeView> 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<ValueNodeView> {
|
||||
-> ErrorOr<std::optional<ValueNodeView>> {
|
||||
auto it = declared_names_.find(name);
|
||||
if (it != declared_names_.end()) {
|
||||
return it->second;
|
||||
return std::make_optional(it->second);
|
||||
}
|
||||
std::optional<ValueNodeView> result;
|
||||
for (Nonnull<const StaticScope*> parent : parent_scopes_) {
|
||||
auto parent_result = parent->TryResolve(name, source_loc);
|
||||
ASSIGN_OR_RETURN(std::optional<ValueNodeView> 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;
|
||||
}
|
||||
|
||||
@@ -12,10 +12,12 @@
|
||||
#include <vector>
|
||||
|
||||
#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<Success>;
|
||||
|
||||
// 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<ValueNodeView>;
|
||||
|
||||
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<ValueNodeView>;
|
||||
-> ErrorOr<std::optional<ValueNodeView>>;
|
||||
|
||||
// Maps locally declared names to their entities.
|
||||
std::unordered_map<std::string, ValueNodeView> declared_names_;
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -5,38 +5,102 @@
|
||||
#ifndef EXECUTABLE_SEMANTICS_COMMON_ERROR_H_
|
||||
#define EXECUTABLE_SEMANTICS_COMMON_ERROR_H_
|
||||
|
||||
#include <optional>
|
||||
|
||||
#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<T>`.
|
||||
class ErrorBuilder {
|
||||
public:
|
||||
explicit ErrorBuilder(std::optional<SourceLocation> loc = std::nullopt)
|
||||
: out_(message_) {
|
||||
if (loc.has_value()) {
|
||||
out_ << *loc << ": ";
|
||||
}
|
||||
}
|
||||
|
||||
// Accumulates string message.
|
||||
template <typename T>
|
||||
[[nodiscard]] auto operator<<(const T& message) -> ErrorBuilder& {
|
||||
out_ << message;
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator Error() { return Error(message_); }
|
||||
|
||||
template <typename V>
|
||||
operator ErrorOr<V>() {
|
||||
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_
|
||||
|
||||
@@ -9,24 +9,94 @@
|
||||
namespace Carbon::Testing {
|
||||
namespace {
|
||||
|
||||
using ::testing::Eq;
|
||||
|
||||
auto MakeSuccess() -> ErrorOr<Success> { return Success(); }
|
||||
|
||||
auto MakeError(std::string_view message) -> ErrorOr<Success> {
|
||||
return Error(message);
|
||||
}
|
||||
|
||||
auto MakeInt(int value) -> ErrorOr<int> { return value; }
|
||||
|
||||
auto MakeFailedInt(std::string_view message) -> ErrorOr<int> {
|
||||
return Error(message);
|
||||
}
|
||||
|
||||
auto ErrorToString(const Error& e) -> std::string { return e.message(); }
|
||||
|
||||
template <typename V>
|
||||
auto ErrorToString(const ErrorOr<V>& 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<Success> {
|
||||
RETURN_IF_ERROR(MakeSuccess());
|
||||
RETURN_IF_ERROR(MakeSuccess());
|
||||
return Success();
|
||||
}();
|
||||
EXPECT_TRUE(result.ok());
|
||||
}
|
||||
|
||||
TEST(ErrorTest, ReturnIfErrorHasError) {
|
||||
auto result = []() -> ErrorOr<Success> {
|
||||
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<int> {
|
||||
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<int> {
|
||||
RETURN_IF_ERROR(MakeError("error"));
|
||||
return 0;
|
||||
}();
|
||||
ASSERT_FALSE(result.ok());
|
||||
}
|
||||
|
||||
TEST(ErrorTest, AssignOrReturnHasErrorInExpected) {
|
||||
auto result = []() -> ErrorOr<int> {
|
||||
ASSIGN_OR_RETURN(int a, MakeFailedInt("error"));
|
||||
return a;
|
||||
}();
|
||||
ASSERT_FALSE(result.ok());
|
||||
EXPECT_EQ(ErrorToString(result), "error");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -77,6 +77,7 @@ cc_library(
|
||||
"//common:ostream",
|
||||
"//executable_semantics/ast",
|
||||
"//executable_semantics/common:arena",
|
||||
"@llvm-project//llvm:Support",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -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<const Value*> {
|
||||
-> ErrorOr<Nonnull<const Value*>> {
|
||||
if (std::optional<Nonnull<const Value*>> 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<Success> {
|
||||
std::unique_ptr<Action> 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<const Value*> result) {
|
||||
auto ActionStack::FinishAction(Nonnull<const Value*> result)
|
||||
-> ErrorOr<Success> {
|
||||
std::unique_ptr<Action> act = todo_.Pop();
|
||||
switch (act->kind()) {
|
||||
case Action::Kind::StatementAction:
|
||||
@@ -139,27 +142,33 @@ void ActionStack::FinishAction(Nonnull<const Value*> result) {
|
||||
PopScopes();
|
||||
SetResult(result);
|
||||
}
|
||||
return Success();
|
||||
}
|
||||
|
||||
void ActionStack::Spawn(std::unique_ptr<Action> child) {
|
||||
auto ActionStack::Spawn(std::unique_ptr<Action> child) -> ErrorOr<Success> {
|
||||
Action& action = *todo_.Top();
|
||||
action.set_pos(action.pos() + 1);
|
||||
todo_.Push(std::move(child));
|
||||
return Success();
|
||||
}
|
||||
|
||||
void ActionStack::Spawn(std::unique_ptr<Action> child, RuntimeScope scope) {
|
||||
auto ActionStack::Spawn(std::unique_ptr<Action> child, RuntimeScope scope)
|
||||
-> ErrorOr<Success> {
|
||||
Action& action = *todo_.Top();
|
||||
action.set_pos(action.pos() + 1);
|
||||
todo_.Push(std::make_unique<ScopeAction>(std::move(scope)));
|
||||
todo_.Push(std::move(child));
|
||||
return Success();
|
||||
}
|
||||
|
||||
void ActionStack::RunAgain() {
|
||||
auto ActionStack::RunAgain() -> ErrorOr<Success> {
|
||||
Action& action = *todo_.Top();
|
||||
action.set_pos(action.pos() + 1);
|
||||
return Success();
|
||||
}
|
||||
|
||||
void ActionStack::UnwindTo(Nonnull<const Statement*> ast_node) {
|
||||
auto ActionStack::UnwindTo(Nonnull<const Statement*> ast_node)
|
||||
-> ErrorOr<Success> {
|
||||
while (true) {
|
||||
if (const auto* statement_action =
|
||||
llvm::dyn_cast<StatementAction>(todo_.Top().get());
|
||||
@@ -169,24 +178,30 @@ void ActionStack::UnwindTo(Nonnull<const Statement*> ast_node) {
|
||||
}
|
||||
todo_.Pop();
|
||||
}
|
||||
return Success();
|
||||
}
|
||||
|
||||
void ActionStack::UnwindPast(Nonnull<const Statement*> ast_node) {
|
||||
UnwindTo(ast_node);
|
||||
auto ActionStack::UnwindPast(Nonnull<const Statement*> ast_node)
|
||||
-> ErrorOr<Success> {
|
||||
RETURN_IF_ERROR(UnwindTo(ast_node));
|
||||
todo_.Pop();
|
||||
PopScopes();
|
||||
return Success();
|
||||
}
|
||||
|
||||
void ActionStack::UnwindPast(Nonnull<const Statement*> ast_node,
|
||||
Nonnull<const Value*> result) {
|
||||
UnwindPast(ast_node);
|
||||
auto ActionStack::UnwindPast(Nonnull<const Statement*> ast_node,
|
||||
Nonnull<const Value*> result) -> ErrorOr<Success> {
|
||||
RETURN_IF_ERROR(UnwindPast(ast_node));
|
||||
SetResult(result);
|
||||
return Success();
|
||||
}
|
||||
|
||||
void ActionStack::Resume(Nonnull<const ContinuationValue*> continuation) {
|
||||
auto ActionStack::Resume(Nonnull<const ContinuationValue*> continuation)
|
||||
-> ErrorOr<Success> {
|
||||
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<Run>(statement->statement());
|
||||
}
|
||||
|
||||
void ActionStack::Suspend() {
|
||||
auto ActionStack::Suspend() -> ErrorOr<Success> {
|
||||
// Pause the current continuation
|
||||
todo_.Pop();
|
||||
std::vector<std::unique_ptr<Action>> paused;
|
||||
@@ -205,6 +220,7 @@ void ActionStack::Suspend() {
|
||||
llvm::cast<const ContinuationValue>(*todo_.Top()->results()[0]);
|
||||
// Update the continuation with the paused stack.
|
||||
continuation.stack().StoreReversed(std::move(paused));
|
||||
return Success();
|
||||
}
|
||||
|
||||
void ActionStack::PopScopes() {
|
||||
|
||||
@@ -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<const Value*>;
|
||||
-> ErrorOr<Nonnull<const Value*>>;
|
||||
|
||||
// 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<Success> 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<const Value*> result);
|
||||
auto FinishAction() -> ErrorOr<Success>;
|
||||
auto FinishAction(Nonnull<const Value*> result) -> ErrorOr<Success>;
|
||||
|
||||
// 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<Action> child);
|
||||
void Spawn(std::unique_ptr<Action> child, RuntimeScope scope);
|
||||
auto Spawn(std::unique_ptr<Action> child) -> ErrorOr<Success>;
|
||||
auto Spawn(std::unique_ptr<Action> child, RuntimeScope scope)
|
||||
-> ErrorOr<Success>;
|
||||
|
||||
// Advances the current action one step.
|
||||
void RunAgain();
|
||||
auto RunAgain() -> ErrorOr<Success>;
|
||||
|
||||
// Unwinds Actions from the stack until the StatementAction associated with
|
||||
// `ast_node` is at the top of the stack.
|
||||
void UnwindTo(Nonnull<const Statement*> ast_node);
|
||||
auto UnwindTo(Nonnull<const Statement*> ast_node) -> ErrorOr<Success>;
|
||||
|
||||
// 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<const Statement*> ast_node);
|
||||
void UnwindPast(Nonnull<const Statement*> ast_node,
|
||||
Nonnull<const Value*> result);
|
||||
auto UnwindPast(Nonnull<const Statement*> ast_node) -> ErrorOr<Success>;
|
||||
auto UnwindPast(Nonnull<const Statement*> ast_node,
|
||||
Nonnull<const Value*> result) -> ErrorOr<Success>;
|
||||
|
||||
// Resumes execution of a suspended continuation.
|
||||
void Resume(Nonnull<const ContinuationValue*> continuation);
|
||||
auto Resume(Nonnull<const ContinuationValue*> continuation)
|
||||
-> ErrorOr<Success>;
|
||||
|
||||
// Suspends execution of the currently-executing continuation.
|
||||
void Suspend();
|
||||
auto Suspend() -> ErrorOr<Success>;
|
||||
|
||||
private:
|
||||
// Pop any ScopeActions from the top of the stack, propagating results as
|
||||
|
||||
@@ -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*> arena, AST ast, bool trace) {
|
||||
auto ExecProgram(Nonnull<Arena*> arena, AST ast, bool trace) -> ErrorOr<int> {
|
||||
if (trace) {
|
||||
llvm::outs() << "********** source program **********\n";
|
||||
for (const auto decl : ast.declarations) {
|
||||
@@ -32,15 +33,15 @@ void ExecProgram(Nonnull<Arena*> 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*> 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
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
namespace Carbon {
|
||||
|
||||
// Runs the top-level declaration list.
|
||||
void ExecProgram(Nonnull<Arena*> arena, AST ast, bool trace);
|
||||
auto ExecProgram(Nonnull<Arena*> arena, AST ast, bool trace) -> ErrorOr<int>;
|
||||
|
||||
} // namespace Carbon
|
||||
|
||||
|
||||
@@ -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<const Value*> v) -> AllocationId {
|
||||
}
|
||||
|
||||
auto Heap::Read(const Address& a, SourceLocation source_loc) const
|
||||
-> Nonnull<const Value*> {
|
||||
this->CheckAlive(a.allocation_, source_loc);
|
||||
-> ErrorOr<Nonnull<const Value*>> {
|
||||
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<const Value*> 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<const Value*> v,
|
||||
SourceLocation source_loc) -> ErrorOr<Success> {
|
||||
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<Success> {
|
||||
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) {
|
||||
|
||||
@@ -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<const Value*>;
|
||||
-> ErrorOr<Nonnull<const Value*>>;
|
||||
|
||||
// Writes the given value at the address in the heap after
|
||||
// checking that the address is alive.
|
||||
void Write(const Address& a, Nonnull<const Value*> v,
|
||||
SourceLocation source_loc);
|
||||
auto Write(const Address& a, Nonnull<const Value*> v,
|
||||
SourceLocation source_loc) -> ErrorOr<Success>;
|
||||
|
||||
// Put the given value on the heap and mark it as alive.
|
||||
auto AllocateValue(Nonnull<const Value*> 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<Success>;
|
||||
|
||||
Nonnull<Arena*> arena_;
|
||||
std::vector<Nonnull<const Value*>> values_;
|
||||
|
||||
@@ -23,12 +23,14 @@ void ImplScope::AddParent(Nonnull<const ImplScope*> parent) {
|
||||
|
||||
auto ImplScope::Resolve(Nonnull<const Value*> iface_type,
|
||||
Nonnull<const Value*> type,
|
||||
SourceLocation source_loc) const -> ValueNodeView {
|
||||
std::optional<ValueNodeView> result =
|
||||
TryResolve(iface_type, type, source_loc);
|
||||
SourceLocation source_loc) const
|
||||
-> ErrorOr<ValueNodeView> {
|
||||
ASSIGN_OR_RETURN(std::optional<ValueNodeView> 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<const Value*> iface_type,
|
||||
auto ImplScope::TryResolve(Nonnull<const Value*> iface_type,
|
||||
Nonnull<const Value*> type,
|
||||
SourceLocation source_loc) const
|
||||
-> std::optional<ValueNodeView> {
|
||||
-> ErrorOr<std::optional<ValueNodeView>> {
|
||||
std::optional<ValueNodeView> result =
|
||||
ResolveHere(iface_type, type, source_loc);
|
||||
if (result.has_value()) {
|
||||
return result;
|
||||
}
|
||||
for (Nonnull<const ImplScope*> 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;
|
||||
}
|
||||
|
||||
@@ -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<const Value*> iface, Nonnull<const Value*> type,
|
||||
SourceLocation source_loc) const -> ValueNodeView;
|
||||
SourceLocation source_loc) const -> ErrorOr<ValueNodeView>;
|
||||
|
||||
private:
|
||||
auto TryResolve(Nonnull<const Value*> iface_type, Nonnull<const Value*> type,
|
||||
SourceLocation source_loc) const
|
||||
-> std::optional<ValueNodeView>;
|
||||
-> ErrorOr<std::optional<ValueNodeView>>;
|
||||
auto ResolveHere(Nonnull<const Value*> iface_type,
|
||||
Nonnull<const Value*> impl_type,
|
||||
SourceLocation source_loc) const
|
||||
|
||||
@@ -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> action);
|
||||
// It's not safe to call `RunAllSteps()` or `result()` after an error.
|
||||
auto RunAllSteps(std::unique_ptr<Action> action) -> ErrorOr<Success>;
|
||||
|
||||
// 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<const Value*> { return todo_.result(); }
|
||||
|
||||
private:
|
||||
void Step();
|
||||
auto Step() -> ErrorOr<Success>;
|
||||
|
||||
// State transitions for expressions.
|
||||
void StepExp();
|
||||
auto StepExp() -> ErrorOr<Success>;
|
||||
// State transitions for lvalues.
|
||||
void StepLvalue();
|
||||
auto StepLvalue() -> ErrorOr<Success>;
|
||||
// State transitions for patterns.
|
||||
void StepPattern();
|
||||
auto StepPattern() -> ErrorOr<Success>;
|
||||
// State transition for statements.
|
||||
void StepStmt();
|
||||
auto StepStmt() -> ErrorOr<Success>;
|
||||
// State transition for declarations.
|
||||
void StepDeclaration();
|
||||
auto StepDeclaration() -> ErrorOr<Success>;
|
||||
|
||||
auto CreateStruct(const std::vector<FieldInitializer>& fields,
|
||||
const std::vector<Nonnull<const Value*>>& values)
|
||||
-> Nonnull<const Value*>;
|
||||
|
||||
auto EvalPrim(Operator op, const std::vector<Nonnull<const Value*>>& args,
|
||||
SourceLocation source_loc) -> Nonnull<const Value*>;
|
||||
SourceLocation source_loc) -> ErrorOr<Nonnull<const Value*>>;
|
||||
|
||||
// Returns the result of converting `value` to type `destination_type`.
|
||||
auto Convert(Nonnull<const Value*> value,
|
||||
@@ -129,7 +131,8 @@ void Interpreter::PrintState(llvm::raw_ostream& out) {
|
||||
|
||||
auto Interpreter::EvalPrim(Operator op,
|
||||
const std::vector<Nonnull<const Value*>>& args,
|
||||
SourceLocation source_loc) -> Nonnull<const Value*> {
|
||||
SourceLocation source_loc)
|
||||
-> ErrorOr<Nonnull<const Value*>> {
|
||||
switch (op) {
|
||||
case Operator::Neg:
|
||||
return arena_->New<IntValue>(-cast<IntValue>(*args[0]).value());
|
||||
@@ -257,7 +260,7 @@ auto PatternMatch(Nonnull<const Value*> p, Nonnull<const Value*> v,
|
||||
}
|
||||
}
|
||||
|
||||
void Interpreter::StepLvalue() {
|
||||
auto Interpreter::StepLvalue() -> ErrorOr<Success> {
|
||||
Action& act = todo_.CurrentAction();
|
||||
const Expression& exp = cast<LValAction>(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<const Value*> value = todo_.ValueOfNode(
|
||||
cast<IdentifierExpression>(exp).value_node(), exp.source_loc());
|
||||
ASSIGN_OR_RETURN(
|
||||
Nonnull<const Value*> value,
|
||||
todo_.ValueOfNode(cast<IdentifierExpression>(exp).value_node(),
|
||||
exp.source_loc()));
|
||||
CHECK(isa<LValue>(value)) << *value;
|
||||
return todo_.FinishAction(value);
|
||||
}
|
||||
@@ -418,7 +423,7 @@ auto Interpreter::Convert(Nonnull<const Value*> value,
|
||||
}
|
||||
}
|
||||
|
||||
void Interpreter::StepExp() {
|
||||
auto Interpreter::StepExp() -> ErrorOr<Success> {
|
||||
Action& act = todo_.CurrentAction();
|
||||
const Expression& exp = cast<ExpressionAction>(act).expression();
|
||||
if (trace_) {
|
||||
@@ -441,8 +446,8 @@ void Interpreter::StepExp() {
|
||||
const auto& tuple = cast<TupleValue>(*act.results()[0]);
|
||||
int i = cast<IntValue>(*act.results()[1]).value();
|
||||
if (i < 0 || i >= static_cast<int>(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<Nonnull<const Witness*>> witness = std::nullopt;
|
||||
if (access.impl().has_value()) {
|
||||
auto witness_addr =
|
||||
todo_.ValueOfNode(*access.impl(), access.source_loc());
|
||||
witness = cast<Witness>(
|
||||
ASSIGN_OR_RETURN(
|
||||
auto witness_addr,
|
||||
todo_.ValueOfNode(*access.impl(), access.source_loc()));
|
||||
ASSIGN_OR_RETURN(
|
||||
Nonnull<const Value*> witness_value,
|
||||
heap_.Read(llvm::cast<LValue>(witness_addr)->address(),
|
||||
access.source_loc()));
|
||||
witness = cast<Witness>(witness_value);
|
||||
}
|
||||
FieldPath::Component field(access.field(), witness);
|
||||
Nonnull<const Value*> member = act.results()[0]->GetField(
|
||||
arena_, FieldPath(field), exp.source_loc());
|
||||
ASSIGN_OR_RETURN(Nonnull<const Value*> 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<IdentifierExpression>(exp);
|
||||
// { {x :: C, E, F} :: S, H} -> { {H(E(x)) :: C, E, F} :: S, H}
|
||||
Nonnull<const Value*> value =
|
||||
todo_.ValueOfNode(ident.value_node(), ident.source_loc());
|
||||
ASSIGN_OR_RETURN(
|
||||
Nonnull<const Value*> value,
|
||||
todo_.ValueOfNode(ident.value_node(), ident.source_loc()));
|
||||
if (const auto* lvalue = dyn_cast<LValue>(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<const Value*> 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<CallExpression>(exp).impls()) {
|
||||
Nonnull<const Value*> witness =
|
||||
todo_.ValueOfNode(impl_node, exp.source_loc());
|
||||
ASSIGN_OR_RETURN(Nonnull<const Value*> witness,
|
||||
todo_.ValueOfNode(impl_node, exp.source_loc()));
|
||||
if (witness->kind() == Value::Kind::LValue) {
|
||||
const auto& lval = cast<LValue>(*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<Success> {
|
||||
Action& act = todo_.CurrentAction();
|
||||
const Pattern& pattern = cast<PatternAction>(act).pattern();
|
||||
if (trace_) {
|
||||
@@ -768,7 +781,7 @@ void Interpreter::StepPattern() {
|
||||
}
|
||||
}
|
||||
|
||||
void Interpreter::StepStmt() {
|
||||
auto Interpreter::StepStmt() -> ErrorOr<Success> {
|
||||
Action& act = todo_.CurrentAction();
|
||||
const Statement& stmt = cast<StatementAction>(act).statement();
|
||||
if (trace_) {
|
||||
@@ -901,7 +914,7 @@ void Interpreter::StepStmt() {
|
||||
const auto& lval = cast<LValue>(*act.results()[0]);
|
||||
Nonnull<const Value*> 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<Success> {
|
||||
Action& act = todo_.CurrentAction();
|
||||
const Declaration& decl = cast<DeclarationAction>(act).declaration();
|
||||
if (trace_) {
|
||||
@@ -1009,72 +1022,79 @@ void Interpreter::StepDeclaration() {
|
||||
}
|
||||
|
||||
// State transition.
|
||||
void Interpreter::Step() {
|
||||
auto Interpreter::Step() -> ErrorOr<Success> {
|
||||
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> action) {
|
||||
auto Interpreter::RunAllSteps(std::unique_ptr<Action> action)
|
||||
-> ErrorOr<Success> {
|
||||
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*> arena, bool trace) -> int {
|
||||
auto InterpProgram(const AST& ast, Nonnull<Arena*> arena, bool trace)
|
||||
-> ErrorOr<int> {
|
||||
Interpreter interpreter(Phase::RunTime, arena, trace);
|
||||
if (trace) {
|
||||
llvm::outs() << "********** initializing globals **********\n";
|
||||
}
|
||||
|
||||
for (Nonnull<Declaration*> declaration : ast.declarations) {
|
||||
interpreter.RunAllSteps(std::make_unique<DeclarationAction>(declaration));
|
||||
RETURN_IF_ERROR(interpreter.RunAllSteps(
|
||||
std::make_unique<DeclarationAction>(declaration)));
|
||||
}
|
||||
|
||||
if (trace) {
|
||||
llvm::outs() << "********** calling main function **********\n";
|
||||
}
|
||||
|
||||
interpreter.RunAllSteps(std::make_unique<ExpressionAction>(*ast.main_call));
|
||||
RETURN_IF_ERROR(interpreter.RunAllSteps(
|
||||
std::make_unique<ExpressionAction>(*ast.main_call)));
|
||||
|
||||
return cast<IntValue>(*interpreter.result()).value();
|
||||
}
|
||||
|
||||
auto InterpExp(Nonnull<const Expression*> e, Nonnull<Arena*> arena, bool trace)
|
||||
-> Nonnull<const Value*> {
|
||||
-> ErrorOr<Nonnull<const Value*>> {
|
||||
Interpreter interpreter(Phase::CompileTime, arena, trace);
|
||||
interpreter.RunAllSteps(std::make_unique<ExpressionAction>(e));
|
||||
RETURN_IF_ERROR(
|
||||
interpreter.RunAllSteps(std::make_unique<ExpressionAction>(e)));
|
||||
return interpreter.result();
|
||||
}
|
||||
|
||||
auto InterpPattern(Nonnull<const Pattern*> p, Nonnull<Arena*> arena, bool trace)
|
||||
-> Nonnull<const Value*> {
|
||||
-> ErrorOr<Nonnull<const Value*>> {
|
||||
Interpreter interpreter(Phase::CompileTime, arena, trace);
|
||||
interpreter.RunAllSteps(std::make_unique<PatternAction>(p));
|
||||
RETURN_IF_ERROR(interpreter.RunAllSteps(std::make_unique<PatternAction>(p)));
|
||||
return interpreter.result();
|
||||
}
|
||||
|
||||
|
||||
@@ -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*> arena, bool trace) -> int;
|
||||
auto InterpProgram(const AST& ast, Nonnull<Arena*> arena, bool trace)
|
||||
-> ErrorOr<int>;
|
||||
|
||||
// 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<const Expression*> e, Nonnull<Arena*> arena, bool trace)
|
||||
-> Nonnull<const Value*>;
|
||||
-> ErrorOr<Nonnull<const Value*>>;
|
||||
|
||||
// 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<const Pattern*> p, Nonnull<Arena*> arena, bool trace)
|
||||
-> Nonnull<const Value*>;
|
||||
-> ErrorOr<Nonnull<const Value*>>;
|
||||
|
||||
// Attempts to match `v` against the pattern `p`, returning whether matching
|
||||
// is successful. If it is, populates **bindings with the variables bound by
|
||||
|
||||
@@ -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*> statement,
|
||||
static auto ResolveControlFlow(Nonnull<Statement*> statement,
|
||||
std::optional<Nonnull<const Statement*>> loop,
|
||||
std::optional<Nonnull<FunctionData*>> function) {
|
||||
std::optional<Nonnull<FunctionData*>> function)
|
||||
-> ErrorOr<Success> {
|
||||
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<Return>(*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<Break>(*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<Continue>(*statement).set_loop(*loop);
|
||||
return;
|
||||
return Success();
|
||||
case StatementKind::If: {
|
||||
auto& if_stmt = cast<If>(*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<Block>(*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<While>(*statement).body(), statement, function);
|
||||
return;
|
||||
RETURN_IF_ERROR(ResolveControlFlow(&cast<While>(*statement).body(),
|
||||
statement, function));
|
||||
return Success();
|
||||
case StatementKind::Match: {
|
||||
auto& match = cast<Match>(*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<Continuation>(*statement).body(), std::nullopt,
|
||||
std::nullopt);
|
||||
return;
|
||||
RETURN_IF_ERROR(ResolveControlFlow(&cast<Continuation>(*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*> declaration) {
|
||||
auto ResolveControlFlow(Nonnull<Declaration*> declaration) -> ErrorOr<Success> {
|
||||
switch (declaration->kind()) {
|
||||
case DeclarationKind::FunctionDeclaration: {
|
||||
auto& function = cast<FunctionDeclaration>(*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<ClassDeclaration>(*declaration);
|
||||
for (Nonnull<Declaration*> member : class_decl.members()) {
|
||||
ResolveControlFlow(member);
|
||||
RETURN_IF_ERROR(ResolveControlFlow(member));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DeclarationKind::InterfaceDeclaration: {
|
||||
auto& iface_decl = cast<InterfaceDeclaration>(*declaration);
|
||||
for (Nonnull<Declaration*> member : iface_decl.members()) {
|
||||
ResolveControlFlow(member);
|
||||
RETURN_IF_ERROR(ResolveControlFlow(member));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DeclarationKind::ImplDeclaration: {
|
||||
auto& impl_decl = cast<ImplDeclaration>(*declaration);
|
||||
for (Nonnull<Declaration*> member : impl_decl.members()) {
|
||||
ResolveControlFlow(member);
|
||||
RETURN_IF_ERROR(ResolveControlFlow(member));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -147,12 +155,14 @@ void ResolveControlFlow(Nonnull<Declaration*> declaration) {
|
||||
// do nothing
|
||||
break;
|
||||
}
|
||||
return Success();
|
||||
}
|
||||
|
||||
void ResolveControlFlow(AST& ast) {
|
||||
auto ResolveControlFlow(AST& ast) -> ErrorOr<Success> {
|
||||
for (auto declaration : ast.declarations) {
|
||||
ResolveControlFlow(declaration);
|
||||
RETURN_IF_ERROR(ResolveControlFlow(declaration));
|
||||
}
|
||||
return Success();
|
||||
}
|
||||
|
||||
} // namespace Carbon
|
||||
|
||||
@@ -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<Success>;
|
||||
|
||||
} // namespace Carbon
|
||||
|
||||
|
||||
@@ -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<Success>;
|
||||
|
||||
static void AddExposedNames(const Declaration& declaration,
|
||||
StaticScope& enclosing_scope) {
|
||||
static auto AddExposedNames(const Declaration& declaration,
|
||||
StaticScope& enclosing_scope) -> ErrorOr<Success> {
|
||||
switch (declaration.kind()) {
|
||||
case DeclarationKind::InterfaceDeclaration: {
|
||||
auto& iface_decl = cast<InterfaceDeclaration>(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<FunctionDeclaration>(declaration);
|
||||
enclosing_scope.Add(func.name(), &func);
|
||||
RETURN_IF_ERROR(enclosing_scope.Add(func.name(), &func));
|
||||
break;
|
||||
}
|
||||
case DeclarationKind::ClassDeclaration: {
|
||||
auto& class_decl = cast<ClassDeclaration>(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<ChoiceDeclaration>(declaration);
|
||||
enclosing_scope.Add(choice.name(), &choice);
|
||||
RETURN_IF_ERROR(enclosing_scope.Add(choice.name(), &choice));
|
||||
break;
|
||||
}
|
||||
case DeclarationKind::VariableDeclaration:
|
||||
auto& var = cast<VariableDeclaration>(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<Success>;
|
||||
static auto ResolveNames(Pattern& pattern, StaticScope& enclosing_scope)
|
||||
-> ErrorOr<Success>;
|
||||
static auto ResolveNames(Statement& statement, StaticScope& enclosing_scope)
|
||||
-> ErrorOr<Success>;
|
||||
static auto ResolveNames(Declaration& declaration, StaticScope& enclosing_scope)
|
||||
-> ErrorOr<Success>;
|
||||
|
||||
static void ResolveNames(Expression& expression,
|
||||
const StaticScope& enclosing_scope) {
|
||||
static auto ResolveNames(Expression& expression,
|
||||
const StaticScope& enclosing_scope)
|
||||
-> ErrorOr<Success> {
|
||||
switch (expression.kind()) {
|
||||
case ExpressionKind::CallExpression: {
|
||||
auto& call = cast<CallExpression>(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<FunctionTypeLiteral>(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<FieldAccessExpression>(expression).aggregate(),
|
||||
enclosing_scope);
|
||||
RETURN_IF_ERROR(
|
||||
ResolveNames(cast<FieldAccessExpression>(expression).aggregate(),
|
||||
enclosing_scope));
|
||||
break;
|
||||
case ExpressionKind::IndexExpression: {
|
||||
auto& index = cast<IndexExpression>(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<Expression*> operand :
|
||||
cast<PrimitiveOperatorExpression>(expression).arguments()) {
|
||||
ResolveNames(*operand, enclosing_scope);
|
||||
RETURN_IF_ERROR(ResolveNames(*operand, enclosing_scope));
|
||||
}
|
||||
break;
|
||||
case ExpressionKind::TupleLiteral:
|
||||
for (Nonnull<Expression*> field :
|
||||
cast<TupleLiteral>(expression).fields()) {
|
||||
ResolveNames(*field, enclosing_scope);
|
||||
RETURN_IF_ERROR(ResolveNames(*field, enclosing_scope));
|
||||
}
|
||||
break;
|
||||
case ExpressionKind::StructLiteral:
|
||||
for (FieldInitializer& init : cast<StructLiteral>(expression).fields()) {
|
||||
ResolveNames(init.expression(), enclosing_scope);
|
||||
RETURN_IF_ERROR(ResolveNames(init.expression(), enclosing_scope));
|
||||
}
|
||||
break;
|
||||
case ExpressionKind::StructTypeLiteral:
|
||||
for (FieldInitializer& init :
|
||||
cast<StructTypeLiteral>(expression).fields()) {
|
||||
ResolveNames(init.expression(), enclosing_scope);
|
||||
RETURN_IF_ERROR(ResolveNames(init.expression(), enclosing_scope));
|
||||
}
|
||||
break;
|
||||
case ExpressionKind::IdentifierExpression: {
|
||||
auto& identifier = cast<IdentifierExpression>(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<IntrinsicExpression>(expression).args(),
|
||||
enclosing_scope);
|
||||
RETURN_IF_ERROR(ResolveNames(cast<IntrinsicExpression>(expression).args(),
|
||||
enclosing_scope));
|
||||
break;
|
||||
case ExpressionKind::IfExpression: {
|
||||
auto& if_expr = cast<IfExpression>(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<Success> {
|
||||
switch (pattern.kind()) {
|
||||
case PatternKind::BindingPattern: {
|
||||
auto& binding = cast<BindingPattern>(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<Pattern*> field : cast<TuplePattern>(pattern).fields()) {
|
||||
ResolveNames(*field, enclosing_scope);
|
||||
RETURN_IF_ERROR(ResolveNames(*field, enclosing_scope));
|
||||
}
|
||||
break;
|
||||
case PatternKind::AlternativePattern: {
|
||||
auto& alternative = cast<AlternativePattern>(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<ExpressionPattern>(pattern).expression(),
|
||||
enclosing_scope);
|
||||
RETURN_IF_ERROR(ResolveNames(
|
||||
cast<ExpressionPattern>(pattern).expression(), enclosing_scope));
|
||||
break;
|
||||
case PatternKind::AutoPattern:
|
||||
break;
|
||||
case PatternKind::VarPattern:
|
||||
ResolveNames(cast<VarPattern>(pattern).pattern(), enclosing_scope);
|
||||
RETURN_IF_ERROR(
|
||||
ResolveNames(cast<VarPattern>(pattern).pattern(), enclosing_scope));
|
||||
break;
|
||||
}
|
||||
return Success();
|
||||
}
|
||||
|
||||
static void ResolveNames(Statement& statement, StaticScope& enclosing_scope) {
|
||||
static auto ResolveNames(Statement& statement, StaticScope& enclosing_scope)
|
||||
-> ErrorOr<Success> {
|
||||
switch (statement.kind()) {
|
||||
case StatementKind::ExpressionStatement:
|
||||
ResolveNames(cast<ExpressionStatement>(statement).expression(),
|
||||
enclosing_scope);
|
||||
RETURN_IF_ERROR(ResolveNames(
|
||||
cast<ExpressionStatement>(statement).expression(), enclosing_scope));
|
||||
break;
|
||||
case StatementKind::Assign: {
|
||||
auto& assign = cast<Assign>(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<VariableDefinition>(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<If>(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<Return>(statement).expression(), enclosing_scope);
|
||||
RETURN_IF_ERROR(
|
||||
ResolveNames(cast<Return>(statement).expression(), enclosing_scope));
|
||||
break;
|
||||
case StatementKind::Block: {
|
||||
auto& block = cast<Block>(statement);
|
||||
StaticScope block_scope;
|
||||
block_scope.AddParent(&enclosing_scope);
|
||||
for (Nonnull<Statement*> 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<While>(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<Match>(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<Continuation>(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<Continuation>(statement).body(), continuation_scope);
|
||||
RETURN_IF_ERROR(ResolveNames(cast<Continuation>(statement).body(),
|
||||
continuation_scope));
|
||||
break;
|
||||
}
|
||||
case StatementKind::Run:
|
||||
ResolveNames(cast<Run>(statement).argument(), enclosing_scope);
|
||||
RETURN_IF_ERROR(
|
||||
ResolveNames(cast<Run>(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<Success> {
|
||||
switch (declaration.kind()) {
|
||||
case DeclarationKind::InterfaceDeclaration: {
|
||||
auto& iface = cast<InterfaceDeclaration>(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<Declaration*> member : iface.members()) {
|
||||
AddExposedNames(*member, iface_scope);
|
||||
RETURN_IF_ERROR(AddExposedNames(*member, iface_scope));
|
||||
}
|
||||
for (Nonnull<Declaration*> member : iface.members()) {
|
||||
ResolveNames(*member, iface_scope);
|
||||
RETURN_IF_ERROR(ResolveNames(*member, iface_scope));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DeclarationKind::ImplDeclaration: {
|
||||
auto& impl = cast<ImplDeclaration>(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<Declaration*> member : impl.members()) {
|
||||
AddExposedNames(*member, enclosing_scope);
|
||||
RETURN_IF_ERROR(AddExposedNames(*member, enclosing_scope));
|
||||
}
|
||||
for (Nonnull<Declaration*> 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<GenericBinding*> 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<ClassDeclaration>(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<Declaration*> member : class_decl.members()) {
|
||||
AddExposedNames(*member, class_scope);
|
||||
RETURN_IF_ERROR(AddExposedNames(*member, class_scope));
|
||||
}
|
||||
for (Nonnull<Declaration*> 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<std::string_view> alternative_names;
|
||||
for (Nonnull<AlternativeSignature*> 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<VariableDeclaration>(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<Success> {
|
||||
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
|
||||
|
||||
@@ -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<Success>;
|
||||
|
||||
} // namespace Carbon
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -23,7 +23,11 @@ class TypeChecker {
|
||||
explicit TypeChecker(Nonnull<Arena*> 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<Success>;
|
||||
|
||||
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<const Value*> param,
|
||||
Nonnull<const Value*> arg);
|
||||
Nonnull<const Value*> arg) -> ErrorOr<Success>;
|
||||
|
||||
// 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<Expression*> e, const ImplScope& impl_scope);
|
||||
auto TypeCheckExp(Nonnull<Expression*> e, const ImplScope& impl_scope)
|
||||
-> ErrorOr<Success>;
|
||||
|
||||
// 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<Pattern*> p,
|
||||
auto TypeCheckPattern(Nonnull<Pattern*> p,
|
||||
std::optional<Nonnull<const Value*>> expected,
|
||||
const ImplScope& impl_scope,
|
||||
ValueCategory enclosing_value_category);
|
||||
ValueCategory enclosing_value_category)
|
||||
-> ErrorOr<Success>;
|
||||
|
||||
// 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<Statement*> s, const ImplScope& impl_scope);
|
||||
auto TypeCheckStmt(Nonnull<Statement*> s, const ImplScope& impl_scope)
|
||||
-> ErrorOr<Success>;
|
||||
|
||||
// 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<Declaration*> d, ImplScope& enclosing_scope);
|
||||
auto DeclareDeclaration(Nonnull<Declaration*> d, ImplScope& enclosing_scope)
|
||||
-> ErrorOr<Success>;
|
||||
|
||||
void DeclareFunctionDeclaration(Nonnull<FunctionDeclaration*> f,
|
||||
const ImplScope& enclosing_scope);
|
||||
auto DeclareFunctionDeclaration(Nonnull<FunctionDeclaration*> f,
|
||||
const ImplScope& enclosing_scope)
|
||||
-> ErrorOr<Success>;
|
||||
|
||||
void DeclareClassDeclaration(Nonnull<ClassDeclaration*> class_decl,
|
||||
ImplScope& enclosing_scope);
|
||||
auto DeclareClassDeclaration(Nonnull<ClassDeclaration*> class_decl,
|
||||
ImplScope& enclosing_scope) -> ErrorOr<Success>;
|
||||
|
||||
void DeclareInterfaceDeclaration(Nonnull<InterfaceDeclaration*> iface_decl,
|
||||
ImplScope& enclosing_scope);
|
||||
auto DeclareInterfaceDeclaration(Nonnull<InterfaceDeclaration*> iface_decl,
|
||||
ImplScope& enclosing_scope)
|
||||
-> ErrorOr<Success>;
|
||||
|
||||
void DeclareImplDeclaration(Nonnull<ImplDeclaration*> impl_decl,
|
||||
ImplScope& enclosing_scope);
|
||||
auto DeclareImplDeclaration(Nonnull<ImplDeclaration*> impl_decl,
|
||||
ImplScope& enclosing_scope) -> ErrorOr<Success>;
|
||||
|
||||
void DeclareChoiceDeclaration(Nonnull<ChoiceDeclaration*> choice,
|
||||
const ImplScope& enclosing_scope);
|
||||
auto DeclareChoiceDeclaration(Nonnull<ChoiceDeclaration*> choice,
|
||||
const ImplScope& enclosing_scope)
|
||||
-> ErrorOr<Success>;
|
||||
|
||||
// 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<Declaration*> d,
|
||||
const ImplScope& impl_scope);
|
||||
auto TypeCheckDeclaration(Nonnull<Declaration*> d,
|
||||
const ImplScope& impl_scope) -> ErrorOr<Success>;
|
||||
|
||||
// Type check the body of the function.
|
||||
void TypeCheckFunctionDeclaration(Nonnull<FunctionDeclaration*> f,
|
||||
const ImplScope& impl_scope);
|
||||
auto TypeCheckFunctionDeclaration(Nonnull<FunctionDeclaration*> f,
|
||||
const ImplScope& impl_scope)
|
||||
-> ErrorOr<Success>;
|
||||
|
||||
// Type check all the members of the class.
|
||||
void TypeCheckClassDeclaration(Nonnull<ClassDeclaration*> class_decl,
|
||||
const ImplScope& impl_scope);
|
||||
auto TypeCheckClassDeclaration(Nonnull<ClassDeclaration*> class_decl,
|
||||
const ImplScope& impl_scope)
|
||||
-> ErrorOr<Success>;
|
||||
|
||||
// Type check all the members of the interface.
|
||||
void TypeCheckInterfaceDeclaration(Nonnull<InterfaceDeclaration*> iface_decl,
|
||||
const ImplScope& impl_scope);
|
||||
auto TypeCheckInterfaceDeclaration(Nonnull<InterfaceDeclaration*> iface_decl,
|
||||
const ImplScope& impl_scope)
|
||||
-> ErrorOr<Success>;
|
||||
|
||||
// Type check all the members of the implementation.
|
||||
void TypeCheckImplDeclaration(Nonnull<ImplDeclaration*> impl_decl,
|
||||
const ImplScope& impl_scope);
|
||||
auto TypeCheckImplDeclaration(Nonnull<ImplDeclaration*> impl_decl,
|
||||
const ImplScope& impl_scope)
|
||||
-> ErrorOr<Success>;
|
||||
|
||||
// This currently does nothing, but perhaps that will change in the future.
|
||||
void TypeCheckChoiceDeclaration(Nonnull<ChoiceDeclaration*> choice,
|
||||
const ImplScope& impl_scope);
|
||||
auto TypeCheckChoiceDeclaration(Nonnull<ChoiceDeclaration*> choice,
|
||||
const ImplScope& impl_scope)
|
||||
-> ErrorOr<Success>;
|
||||
|
||||
// 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<Nonnull<Statement*>> opt_stmt,
|
||||
SourceLocation source_loc);
|
||||
auto ExpectReturnOnAllPaths(std::optional<Nonnull<Statement*>> opt_stmt,
|
||||
SourceLocation source_loc) -> ErrorOr<Success>;
|
||||
|
||||
// Verifies that *value represents a concrete type, as opposed to a
|
||||
// type pattern or a non-type value.
|
||||
void ExpectIsConcreteType(SourceLocation source_loc,
|
||||
Nonnull<const Value*> value);
|
||||
auto ExpectIsConcreteType(SourceLocation source_loc,
|
||||
Nonnull<const Value*> value) -> ErrorOr<Success>;
|
||||
|
||||
auto Substitute(const std::map<Nonnull<const GenericBinding*>,
|
||||
Nonnull<const Value*>>& dict,
|
||||
|
||||
@@ -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*> arena, Nonnull<const Value*> v,
|
||||
const FieldPath::Component& field,
|
||||
SourceLocation source_loc) -> Nonnull<const Value*> {
|
||||
SourceLocation source_loc)
|
||||
-> ErrorOr<Nonnull<const Value*>> {
|
||||
const std::string& f = field.name();
|
||||
|
||||
if (field.witness().has_value()) {
|
||||
@@ -42,8 +44,8 @@ static auto GetMember(Nonnull<Arena*> arena, Nonnull<const Value*> v,
|
||||
const auto& fun_decl = cast<FunctionDeclaration>(**mem_decl);
|
||||
return arena->New<BoundMethodValue>(&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*> arena, Nonnull<const Value*> v,
|
||||
std::optional<Nonnull<const Value*>> field =
|
||||
cast<StructValue>(*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*> arena, Nonnull<const Value*> v,
|
||||
std::optional<Nonnull<const FunctionValue*>> 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<FunctionValue>(**func);
|
||||
@@ -86,8 +90,8 @@ static auto GetMember(Nonnull<Arena*> arena, Nonnull<const Value*> v,
|
||||
case Value::Kind::ChoiceType: {
|
||||
const auto& choice = cast<ChoiceType>(*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<AlternativeConstructorValue>(f, choice.name());
|
||||
}
|
||||
@@ -96,8 +100,8 @@ static auto GetMember(Nonnull<Arena*> arena, Nonnull<const Value*> v,
|
||||
std::optional<Nonnull<const FunctionValue*>> 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*> arena, Nonnull<const Value*> v,
|
||||
}
|
||||
|
||||
auto Value::GetField(Nonnull<Arena*> arena, const FieldPath& path,
|
||||
SourceLocation source_loc) const -> Nonnull<const Value*> {
|
||||
SourceLocation source_loc) const
|
||||
-> ErrorOr<Nonnull<const Value*>> {
|
||||
Nonnull<const Value*> 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<FieldPath::Component>::const_iterator path_begin,
|
||||
std::vector<FieldPath::Component>::const_iterator path_end,
|
||||
Nonnull<const Value*> field_value, SourceLocation source_loc)
|
||||
-> Nonnull<const Value*> {
|
||||
-> ErrorOr<Nonnull<const Value*>> {
|
||||
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<StructValue>(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<size_t>(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<TupleValue>(elements);
|
||||
}
|
||||
default:
|
||||
@@ -163,7 +171,8 @@ static auto SetFieldImpl(
|
||||
|
||||
auto Value::SetField(Nonnull<Arena*> arena, const FieldPath& path,
|
||||
Nonnull<const Value*> field_value,
|
||||
SourceLocation source_loc) const -> Nonnull<const Value*> {
|
||||
SourceLocation source_loc) const
|
||||
-> ErrorOr<Nonnull<const Value*>> {
|
||||
return SetFieldImpl(arena, Nonnull<const Value*>(this),
|
||||
path.components_.begin(), path.components_.end(),
|
||||
field_value, source_loc);
|
||||
|
||||
@@ -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*> arena, const FieldPath& path,
|
||||
SourceLocation source_loc) const -> Nonnull<const Value*>;
|
||||
SourceLocation source_loc) const
|
||||
-> ErrorOr<Nonnull<const Value*>>;
|
||||
|
||||
// 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*> arena, const FieldPath& path,
|
||||
Nonnull<const Value*> field_value,
|
||||
SourceLocation source_loc) const -> Nonnull<const Value*>;
|
||||
SourceLocation source_loc) const
|
||||
-> ErrorOr<Nonnull<const Value*>>;
|
||||
|
||||
// Returns the enumerator corresponding to the most-derived type of this
|
||||
// object.
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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<Carbon::Arena*> arena,
|
||||
std::vector<Carbon::Nonnull<Carbon::Declaration*>>* declarations) {
|
||||
std::variant<Carbon::AST, Carbon::SyntaxErrorCode> parse_result =
|
||||
Carbon::ErrorOr<Carbon::AST> parse_result =
|
||||
Carbon::Parse(arena, prelude_file_name, false);
|
||||
if (std::holds_alternative<Carbon::SyntaxErrorCode>(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<Carbon::AST> 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<Carbon::AST>(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<Carbon::AST, Carbon::SyntaxErrorCode> ast_or_error =
|
||||
Carbon::ErrorOr<Carbon::AST> ast =
|
||||
Carbon::Parse(&arena, input_file_name, trace_option);
|
||||
|
||||
if (auto* error = std::get_if<Carbon::SyntaxErrorCode>(&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<Carbon::AST>(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<Carbon::AST>(ast_or_error),
|
||||
trace_option);
|
||||
Carbon::ErrorOr<int> result = Carbon::ExecProgram(&arena, *ast, trace_option);
|
||||
if (!result.ok()) {
|
||||
return PrintError(result.error());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -8,20 +8,15 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
#include <cstdlib>
|
||||
|
||||
#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<Carbon::IntrinsicExpression::Intrinsic> 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<std::string> 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<std::string> 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;
|
||||
}
|
||||
|
||||
@@ -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*> arena,
|
||||
std::string_view input_file_name, bool trace)
|
||||
-> std::variant<AST, SyntaxErrorCode> {
|
||||
std::string_view input_file_name, bool trace) -> ErrorOr<AST> {
|
||||
// Prepare other parser arguments.
|
||||
std::optional<AST> ast = std::nullopt;
|
||||
ParseAndLexContext context(arena->New<std::string>(input_file_name), trace);
|
||||
@@ -24,11 +25,12 @@ auto ParseImpl(yyscan_t scanner, Nonnull<Arena*> 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*> arena,
|
||||
}
|
||||
|
||||
auto Parse(Nonnull<Arena*> arena, std::string_view input_file_name, bool trace)
|
||||
-> std::variant<AST, SyntaxErrorCode> {
|
||||
-> ErrorOr<AST> {
|
||||
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*> 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<AST, SyntaxErrorCode> result =
|
||||
ParseImpl(scanner, arena, input_file_name, trace);
|
||||
ErrorOr<AST> 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*> arena, std::string_view input_file_name, bool trace)
|
||||
|
||||
auto ParseFromString(Nonnull<Arena*> arena, std::string_view input_file_name,
|
||||
std::string_view file_contents, bool trace)
|
||||
-> std::variant<Carbon::AST, SyntaxErrorCode> {
|
||||
-> ErrorOr<AST> {
|
||||
// Prepare the lexer.
|
||||
yyscan_t scanner;
|
||||
yylex_init(&scanner);
|
||||
@@ -72,8 +73,7 @@ auto ParseFromString(Nonnull<Arena*> 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<AST, SyntaxErrorCode> result =
|
||||
ParseImpl(scanner, arena, input_file_name, trace);
|
||||
ErrorOr<AST> result = ParseImpl(scanner, arena, input_file_name, trace);
|
||||
|
||||
// Clean up the lexer.
|
||||
yy_delete_buffer(buffer, scanner);
|
||||
|
||||
@@ -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*> arena, std::string_view input_file_name, bool trace)
|
||||
-> std::variant<Carbon::AST, SyntaxErrorCode>;
|
||||
-> ErrorOr<Carbon::AST>;
|
||||
|
||||
// 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*> arena, std::string_view input_file_name,
|
||||
std::string_view file_contents, bool trace)
|
||||
-> std::variant<Carbon::AST, SyntaxErrorCode>;
|
||||
-> ErrorOr<Carbon::AST>;
|
||||
|
||||
} // namespace Carbon
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -20,8 +20,11 @@ class ParseAndLexContext {
|
||||
ParseAndLexContext(Nonnull<const std::string*> 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<std::string> {
|
||||
return error_messages_;
|
||||
}
|
||||
|
||||
private:
|
||||
// A path to the file processed, relative to the current working directory
|
||||
// when *this is called.
|
||||
Nonnull<const std::string*> input_file_name_;
|
||||
|
||||
bool trace_;
|
||||
|
||||
std::vector<std::string> error_messages_;
|
||||
};
|
||||
|
||||
} // namespace Carbon
|
||||
|
||||
@@ -23,10 +23,10 @@ fn Foo() {}
|
||||
|
||||
TEST(ParseTest, ParseFromString) {
|
||||
Arena arena;
|
||||
std::variant<AST, SyntaxErrorCode> parse_result =
|
||||
ErrorOr<AST> parse_result =
|
||||
ParseFromString(&arena, "file.carbon", FileContents, /*trace=*/false);
|
||||
ASSERT_TRUE(std::holds_alternative<AST>(parse_result));
|
||||
EXPECT_EQ(std::get<AST>(parse_result).declarations.size(), 1);
|
||||
ASSERT_TRUE(parse_result.ok());
|
||||
EXPECT_EQ(parse_result->declarations.size(), 1);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#include <variant>
|
||||
|
||||
#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<AST, SyntaxErrorCode>& result,
|
||||
auto MatchAndExplain(const ErrorOr<AST>& result,
|
||||
::testing::MatchResultListener* listener) const -> bool {
|
||||
if (std::holds_alternative<SyntaxErrorCode>(result)) {
|
||||
*listener << "holds error code " << std::get<SyntaxErrorCode>(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<AST>(result), listener);
|
||||
return ast_matcher_.MatchAndExplain(*result, listener);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 <int> integer_literal
|
||||
%token <std::string> identifier
|
||||
%token <std::string> intrinsic_identifier
|
||||
%token <IntrinsicExpression::Intrinsic> intrinsic_identifier
|
||||
%token <std::string> sized_type_literal
|
||||
%token <std::string> string_literal
|
||||
%type <std::string> designator
|
||||
@@ -582,7 +583,16 @@ non_expression_pattern:
|
||||
| paren_pattern
|
||||
{ $$ = $1; }
|
||||
| postfix_expression tuple_pattern
|
||||
{ $$ = arena->New<AlternativePattern>(context.source_loc(), $1, $2); }
|
||||
{
|
||||
ErrorOr<Nonnull<AlternativePattern*>> 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<VarPattern>(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<FunctionDeclaration>(context.source_loc(), $2, $3, $4, $5,
|
||||
$6, $7);
|
||||
ErrorOr<FunctionDeclaration*> 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<FunctionDeclaration>(context.source_loc(), $2, $3, $4, $5,
|
||||
$6, std::nullopt);
|
||||
ErrorOr<FunctionDeclaration*> 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
|
||||
|
||||
Reference in New Issue
Block a user