diff --git a/executable_semantics/ast/declaration.h b/executable_semantics/ast/declaration.h index 47c9194ad82a..11263dd21444 100644 --- a/executable_semantics/ast/declaration.h +++ b/executable_semantics/ast/declaration.h @@ -108,11 +108,22 @@ class GenericBinding : public AstNode { auto has_static_type() const -> bool { return static_type_.has_value(); } auto value_category() const -> ValueCategory { return ValueCategory::Let; } + auto constant_value() const -> std::optional> { + return constant_value_; + } + + // Sets the value returned by constant_value(). Can only be called once, + // during typechecking. + void set_constant_value(Nonnull value) { + CHECK(!constant_value_.has_value()); + constant_value_ = value; + } private: std::string name_; Nonnull type_; std::optional> static_type_; + std::optional> constant_value_; }; // The syntactic representation of a function declaration's return type. @@ -232,6 +243,16 @@ class FunctionDeclaration : public Declaration { auto body() -> std::optional> { return body_; } auto value_category() const -> ValueCategory { return ValueCategory::Let; } + auto constant_value() const -> std::optional> { + return constant_value_; + } + + // Sets the value returned by constant_value(). Can only be called once, + // during typechecking. + void set_constant_value(Nonnull value) { + CHECK(!constant_value_.has_value()); + constant_value_ = value; + } private: std::string name_; @@ -239,6 +260,7 @@ class FunctionDeclaration : public Declaration { Nonnull param_pattern_; ReturnTerm return_term_; std::optional> body_; + std::optional> constant_value_; }; class ClassDeclaration : public Declaration { @@ -259,10 +281,21 @@ class ClassDeclaration : public Declaration { auto members() const -> llvm::ArrayRef> { return members_; } auto value_category() const -> ValueCategory { return ValueCategory::Let; } + auto constant_value() const -> std::optional> { + return constant_value_; + } + + // Sets the value returned by constant_value(). Can only be called once, + // during typechecking. + void set_constant_value(Nonnull value) { + CHECK(!constant_value_.has_value()); + constant_value_ = value; + } private: std::string name_; std::vector> members_; + std::optional> constant_value_; }; class AlternativeSignature : public AstNode { @@ -312,10 +345,21 @@ class ChoiceDeclaration : public Declaration { } auto value_category() const -> ValueCategory { return ValueCategory::Let; } + auto constant_value() const -> std::optional> { + return constant_value_; + } + + // Sets the value returned by constant_value(). Can only be called once, + // during typechecking. + void set_constant_value(Nonnull value) { + CHECK(!constant_value_.has_value()); + constant_value_ = value; + } private: std::string name_; std::vector> alternatives_; + std::optional> constant_value_; }; // Global variable definition implements the Declaration concept. diff --git a/executable_semantics/ast/pattern.h b/executable_semantics/ast/pattern.h index bd6bb51ae8f4..acb0f2ca80b9 100644 --- a/executable_semantics/ast/pattern.h +++ b/executable_semantics/ast/pattern.h @@ -61,6 +61,7 @@ class Pattern : public AstNode { auto has_static_type() const -> bool { return static_type_.has_value(); } // The value of this pattern. Cannot be called before typechecking. + // TODO rename to avoid confusion with BindingPattern::constant_value auto value() const -> const Value& { return **value_; } // Sets the value of this pattern. Can only be called once, during @@ -122,6 +123,10 @@ class BindingPattern : public Pattern { auto value_category() const -> ValueCategory { return ValueCategory::Var; } + auto constant_value() const -> std::optional> { + return std::nullopt; + } + private: std::string name_; Nonnull type_; diff --git a/executable_semantics/ast/statement.h b/executable_semantics/ast/statement.h index 46815d8d72e6..fbf5e6fd30ef 100644 --- a/executable_semantics/ast/statement.h +++ b/executable_semantics/ast/statement.h @@ -344,6 +344,9 @@ class Continuation : public Statement { auto has_static_type() const -> bool { return static_type_.has_value(); } auto value_category() const -> ValueCategory { return ValueCategory::Var; } + auto constant_value() const -> std::optional> { + return std::nullopt; + } private: std::string name_; diff --git a/executable_semantics/ast/static_scope.h b/executable_semantics/ast/static_scope.h index 77dbc6323889..fd9918ecd25b 100644 --- a/executable_semantics/ast/static_scope.h +++ b/executable_semantics/ast/static_scope.h @@ -37,6 +37,10 @@ static constexpr std::string_view AnonymousName = "_"; // Returns the name of an IdentifierExpression that names *this. If *this // is anonymous, returns AnonymousName. auto name() const -> std::string_view; + + // If *this names a compile-time constant whose value is known, returns that + // value. Otherwise returns std::nullopt. + auto constant_value() const -> std::optional>; */ // NodeType must be derived from AstNode. // @@ -70,7 +74,11 @@ class NamedEntityView { }), value_category_([](const AstNode& base) -> ValueCategory { return llvm::cast(base).value_category(); - }) { + }), + constant_value_( + [](const AstNode& base) -> std::optional> { + return llvm::cast(base).constant_value(); + }) { CHECK(node->name() != AnonymousName) << "Entity with no name used as NamedEntity: " << *node; } @@ -94,6 +102,11 @@ class NamedEntityView { return value_category_(*base_); } + // Returns node->constant_value() + auto constant_value() const -> std::optional> { + return constant_value_(*base_); + } + friend auto operator==(const NamedEntityView& lhs, const NamedEntityView& rhs) -> bool { return lhs.base_ == rhs.base_; @@ -114,6 +127,8 @@ class NamedEntityView { std::function name_; std::function static_type_; std::function value_category_; + std::function>(const AstNode&)> + constant_value_; }; // Maps the names visible in a given scope to the entities they name. diff --git a/executable_semantics/interpreter/BUILD b/executable_semantics/interpreter/BUILD index ca7edfdea234..d74345734c06 100644 --- a/executable_semantics/interpreter/BUILD +++ b/executable_semantics/interpreter/BUILD @@ -130,6 +130,7 @@ cc_library( ":stack", "//common:check", "//common:ostream", + "//executable_semantics/ast", "//executable_semantics/ast:declaration", "//executable_semantics/ast:expression", "//executable_semantics/ast:pattern", diff --git a/executable_semantics/interpreter/action.cpp b/executable_semantics/interpreter/action.cpp index 4306c90a030c..9d7e8f9624ad 100644 --- a/executable_semantics/interpreter/action.cpp +++ b/executable_semantics/interpreter/action.cpp @@ -55,6 +55,9 @@ void Action::Print(llvm::raw_ostream& out) const { case Action::Kind::StatementAction: cast(*this).statement().PrintDepth(1, out); break; + case Action::Kind::DeclarationAction: + cast(*this).declaration().Print(out); + break; case Action::Kind::ScopeAction: out << "ScopeAction"; } diff --git a/executable_semantics/interpreter/action.h b/executable_semantics/interpreter/action.h index 2718abefd353..5c99e83df0c4 100644 --- a/executable_semantics/interpreter/action.h +++ b/executable_semantics/interpreter/action.h @@ -65,6 +65,7 @@ class Action { ExpressionAction, PatternAction, StatementAction, + DeclarationAction, ScopeAction, }; @@ -203,6 +204,24 @@ class StatementAction : public Action { Nonnull statement_; }; +// Action which implements the run-time effects of executing a Declaration. +// Does not produce a result. +class DeclarationAction : public Action { + public: + explicit DeclarationAction(Nonnull declaration) + : Action(Kind::DeclarationAction), declaration_(declaration) {} + + static auto classof(const Action* action) -> bool { + return action->kind() == Kind::DeclarationAction; + } + + // The Declaration this Action executes. + auto declaration() const -> const Declaration& { return *declaration_; } + + private: + Nonnull declaration_; +}; + // Action which does nothing except introduce a new scope into the action // stack. This is useful when a distinct scope doesn't otherwise have an // Action it can naturally be associated with. ScopeActions are not associated diff --git a/executable_semantics/interpreter/action_stack.cpp b/executable_semantics/interpreter/action_stack.cpp index 8cf48f61f288..459a5d4e1463 100644 --- a/executable_semantics/interpreter/action_stack.cpp +++ b/executable_semantics/interpreter/action_stack.cpp @@ -17,10 +17,10 @@ void ActionStack::Print(llvm::raw_ostream& out) const { } } -void ActionStack::Start(std::unique_ptr action, Scope scope) { +void ActionStack::Start(std::unique_ptr action) { result_ = std::nullopt; + CHECK(todo_.IsEmpty()); todo_ = {}; - todo_.Push(std::make_unique(std::move(scope))); todo_.Push(std::move(action)); } @@ -30,7 +30,7 @@ auto ActionStack::CurrentScope() const -> Scope& { return *action->scope(); } } - FATAL() << "No current scope"; + return globals_; } void ActionStack::FinishAction() { @@ -39,12 +39,12 @@ void ActionStack::FinishAction() { case Action::Kind::ExpressionAction: case Action::Kind::LValAction: case Action::Kind::PatternAction: - FATAL() << "This kind of action must produce a result."; + FATAL() << "This kind of action must produce a result: " << *act; case Action::Kind::ScopeAction: FATAL() << "ScopeAction at top of stack"; case Action::Kind::StatementAction: + case Action::Kind::DeclarationAction: PopScopes(); - CHECK(!IsEmpty()); } } @@ -52,7 +52,8 @@ void ActionStack::FinishAction(Nonnull result) { std::unique_ptr act = todo_.Pop(); switch (act->kind()) { case Action::Kind::StatementAction: - FATAL() << "Statements cannot produce results."; + case Action::Kind::DeclarationAction: + FATAL() << "This kind of Action cannot produce results: " << *act; case Action::Kind::ScopeAction: FATAL() << "ScopeAction at top of stack"; case Action::Kind::ExpressionAction: diff --git a/executable_semantics/interpreter/action_stack.h b/executable_semantics/interpreter/action_stack.h index 18be5c53217a..6e24218d921e 100644 --- a/executable_semantics/interpreter/action_stack.h +++ b/executable_semantics/interpreter/action_stack.h @@ -19,14 +19,17 @@ namespace Carbon { class ActionStack { public: // Constructs an empty ActionStack - ActionStack() = default; + explicit ActionStack(Scope globals) : globals_(std::move(globals)) {} void Print(llvm::raw_ostream& out) const; LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); } - // Starts execution with `action` at the top of the stack, in the given scope. - // `action` must be an `ExpressionAction` or `PatternAction`. - void Start(std::unique_ptr action, Scope scope); + // Returns an Env containing the currently-defined global variables. + auto GlobalEnv() const -> Env { return globals_.values(); } + + // Starts execution with `action` at the top of the stack. Cannot be called + // when IsEmpty() is false. + void Start(std::unique_ptr action); // True if the stack is empty. auto IsEmpty() const -> bool { return todo_.IsEmpty(); } @@ -97,6 +100,7 @@ class ActionStack { // TODO: consider defining a non-nullable unique_ptr-like type to use here. Stack> todo_; std::optional> result_; + mutable Scope globals_; }; } // namespace Carbon diff --git a/executable_semantics/interpreter/exec_program.cpp b/executable_semantics/interpreter/exec_program.cpp index dea3b1544302..c72d484c1361 100644 --- a/executable_semantics/interpreter/exec_program.cpp +++ b/executable_semantics/interpreter/exec_program.cpp @@ -41,8 +41,7 @@ void ExecProgram(Nonnull arena, AST ast, bool trace) { } llvm::outs() << "********** starting execution **********\n"; } - int result = - Interpreter(arena, trace).InterpProgram(ast.declarations, *ast.main_call); + int result = Interpreter(arena, trace).InterpProgram(ast); llvm::outs() << "result: " << result << "\n"; } diff --git a/executable_semantics/interpreter/interpreter.cpp b/executable_semantics/interpreter/interpreter.cpp index 68a84844a13b..ebdfd7376d96 100644 --- a/executable_semantics/interpreter/interpreter.cpp +++ b/executable_semantics/interpreter/interpreter.cpp @@ -96,80 +96,6 @@ auto Interpreter::EvalPrim(Operator op, } } -void Interpreter::InitEnv(const Declaration& d, Env* env) { - switch (d.kind()) { - case DeclarationKind::FunctionDeclaration: { - const auto& func_def = cast(d); - Env new_env = *env; - // Bring the deduced parameters into scope. - for (Nonnull deduced : - func_def.deduced_parameters()) { - AllocationId a = - heap_.AllocateValue(arena_->New(deduced)); - new_env.Set(deduced->name(), a); - } - Nonnull f = arena_->New(&func_def); - AllocationId a = heap_.AllocateValue(f); - env->Set(func_def.name(), a); - break; - } - - case DeclarationKind::ClassDeclaration: { - const auto& class_decl = cast(d); - std::vector fields; - std::vector methods; - for (Nonnull m : class_decl.members()) { - switch (m->kind()) { - case MemberKind::FieldMember: { - const BindingPattern& binding = cast(*m).binding(); - const Expression& type_expression = - cast(binding.type()).expression(); - auto type = InterpExp(Env(arena_), &type_expression); - fields.push_back({.name = binding.name(), .value = type}); - break; - } - } - } - auto st = arena_->New( - class_decl.name(), std::move(fields), std::move(methods)); - AllocationId a = heap_.AllocateValue(st); - env->Set(class_decl.name(), a); - break; - } - - case DeclarationKind::ChoiceDeclaration: { - const auto& choice = cast(d); - std::vector alts; - for (Nonnull alternative : - choice.alternatives()) { - auto t = InterpExp(Env(arena_), &alternative->signature()); - alts.push_back({.name = alternative->name(), .value = t}); - } - auto ct = arena_->New(choice.name(), std::move(alts)); - AllocationId a = heap_.AllocateValue(ct); - env->Set(choice.name(), a); - break; - } - - case DeclarationKind::VariableDeclaration: { - const auto& var = cast(d); - // Adds an entry in `globals` mapping the variable's name to the - // result of evaluating the initializer. - Nonnull v = - Convert(InterpExp(*env, &var.initializer()), &var.static_type()); - AllocationId a = heap_.AllocateValue(v); - env->Set(var.binding().name(), a); - break; - } - } -} - -void Interpreter::InitGlobals(llvm::ArrayRef> fs) { - for (const auto d : fs) { - InitEnv(*d, &globals_); - } -} - auto Interpreter::CreateStruct(const std::vector& fields, const std::vector>& values) -> Nonnull { @@ -521,6 +447,11 @@ void Interpreter::StepExp() { << "Identifier '" << exp << "' at " << exp.source_loc() << " was not resolved"; // { {x :: C, E, F} :: S, H} -> { {H(E(x)) :: C, E, F} :: S, H} + if (std::optional> value = + ident.named_entity().constant_value(); + value.has_value()) { + return todo_.FinishAction(*value); + } Address pointer = GetFromEnv(exp.source_loc(), ident.name()); return todo_.FinishAction(heap_.Read(pointer, exp.source_loc())); } @@ -579,7 +510,7 @@ void Interpreter::StepExp() { exp.source_loc()); CHECK(matches.has_value()) << "internal error in call_function, pattern match failed"; - Scope new_scope(globals_, &heap_); + Scope new_scope(todo_.GlobalEnv(), &heap_); for (const auto& [name, value] : *matches) { new_scope.AddLocal(name, value); } @@ -948,6 +879,32 @@ void Interpreter::StepStmt() { } } +void Interpreter::StepDeclaration() { + Action& act = todo_.CurrentAction(); + const Declaration& decl = cast(act).declaration(); + if (trace_) { + llvm::outs() << "--- step declaration (" << decl.source_loc() << ") --->\n"; + } + switch (decl.kind()) { + case DeclarationKind::VariableDeclaration: { + const auto& var_decl = cast(decl); + if (act.pos() == 0) { + return todo_.Spawn( + std::make_unique(&var_decl.initializer())); + } else { + todo_.CurrentScope().AddLocal(var_decl.binding().name(), + heap_.AllocateValue(act.results()[0])); + return todo_.FinishAction(); + } + } + case DeclarationKind::FunctionDeclaration: + case DeclarationKind::ClassDeclaration: + case DeclarationKind::ChoiceDeclaration: + // These declarations have no run-time effects. + return todo_.FinishAction(); + } +} + // State transition. void Interpreter::Step() { Action& act = todo_.CurrentAction(); @@ -964,62 +921,65 @@ void Interpreter::Step() { case Action::Kind::StatementAction: StepStmt(); break; + case Action::Kind::DeclarationAction: + StepDeclaration(); + break; case Action::Kind::ScopeAction: FATAL() << "ScopeAction escaped ActionStack"; } // switch } -auto Interpreter::ExecuteAction(std::unique_ptr action, Env values, - bool trace_steps) -> Nonnull { - todo_.Start(std::move(action), Scope(values, &heap_)); - +void Interpreter::RunAllSteps(bool trace_steps) { while (!todo_.IsEmpty()) { Step(); if (trace_steps) { PrintState(llvm::outs()); } } - - // Clean up any remaining suspended continuations. - for (Nonnull fragment : stack_fragments_) { - fragment->Clear(); - } - - return todo_.result(); } -auto Interpreter::InterpProgram(llvm::ArrayRef> fs, - Nonnull call_main) -> int { - // Check that the interpreter is in a clean state. - CHECK(globals_.IsEmpty()); - CHECK(todo_.IsEmpty()); - +auto Interpreter::InterpProgram(const AST& ast) -> int { if (trace_) { llvm::outs() << "********** initializing globals **********\n"; } - InitGlobals(fs); + + for (Nonnull declaration : ast.declarations) { + todo_.Start(std::make_unique(declaration)); + RunAllSteps(trace_); + } if (trace_) { llvm::outs() << "********** calling main function **********\n"; PrintState(llvm::outs()); } - return cast( - *ExecuteAction(std::make_unique(call_main), - globals_, trace_)) - .value(); + todo_.Start(std::make_unique(*ast.main_call)); + RunAllSteps(trace_); + + // Clean up any remaining suspended continuations. + for (Nonnull fragment : stack_fragments_) { + fragment->Clear(); + } + + return cast(*todo_.result()).value(); } -auto Interpreter::InterpExp(Env values, Nonnull e) +auto Interpreter::RunCompileTimeAction(std::unique_ptr action) -> Nonnull { - return ExecuteAction(std::make_unique(e), values, - /*trace_steps=*/false); + todo_.Start(std::move(action)); + RunAllSteps(/*trace_steps=*/false); + CHECK(stack_fragments_.empty()); + return todo_.result(); } -auto Interpreter::InterpPattern(Env values, Nonnull p) +auto Interpreter::InterpExp(Nonnull e) -> Nonnull { - return ExecuteAction(std::make_unique(p), values, - /*trace_steps=*/false); + return RunCompileTimeAction(std::make_unique(e)); +} + +auto Interpreter::InterpPattern(Nonnull p) + -> Nonnull { + return RunCompileTimeAction(std::make_unique(p)); } } // namespace Carbon diff --git a/executable_semantics/interpreter/interpreter.h b/executable_semantics/interpreter/interpreter.h index db279e2235df..e943518fd785 100644 --- a/executable_semantics/interpreter/interpreter.h +++ b/executable_semantics/interpreter/interpreter.h @@ -10,6 +10,7 @@ #include #include "common/ostream.h" +#include "executable_semantics/ast/ast.h" #include "executable_semantics/ast/declaration.h" #include "executable_semantics/ast/expression.h" #include "executable_semantics/ast/pattern.h" @@ -24,19 +25,19 @@ namespace Carbon { class Interpreter { public: explicit Interpreter(Nonnull arena, bool trace) - : arena_(arena), globals_(arena), heap_(arena), trace_(trace) {} + : arena_(arena), + heap_(arena), + todo_(Scope(Env(arena_), &heap_)), + trace_(trace) {} // Interpret the whole program. - auto InterpProgram(llvm::ArrayRef> fs, - Nonnull call_main) -> int; + auto InterpProgram(const AST& ast) -> int; // Interpret an expression at compile-time. - auto InterpExp(Env values, Nonnull e) - -> Nonnull; + auto InterpExp(Nonnull e) -> Nonnull; // Interpret a pattern at compile-time. - auto InterpPattern(Env values, Nonnull p) - -> Nonnull; + auto InterpPattern(Nonnull p) -> Nonnull; // Attempts to match `v` against the pattern `p`. If matching succeeds, // returns the bindings of pattern variables to their matched values. @@ -48,7 +49,6 @@ class Interpreter { return heap_.AllocateValue(v); } - void InitEnv(const Declaration& d, Env* env); void PrintEnv(Env values, llvm::raw_ostream& out); private: @@ -62,12 +62,17 @@ class Interpreter { void StepPattern(); // State transition for statements. void StepStmt(); + // State transition for declarations. + void StepDeclaration(); - void InitGlobals(llvm::ArrayRef> fs); auto CurrentEnv() -> Env; auto GetFromEnv(SourceLocation source_loc, const std::string& name) -> Address; + // Calls Step() repeatedly until there are no steps left to execute. Produces + // trace output if trace_steps is true. + void RunAllSteps(bool trace_steps); + auto CreateStruct(const std::vector& fields, const std::vector>& values) -> Nonnull; @@ -82,22 +87,17 @@ class Interpreter { void PrintState(llvm::raw_ostream& out); - // Runs `action` in a scope consisting of `values`, and returns the result. - // `action` must produce a result. In other words, it must not be a - // StatementAction or ScopeAction. - // - // TODO: consider whether to use this->trace_ rather than a separate - // trace_steps parameter. - auto ExecuteAction(std::unique_ptr action, Env values, - bool trace_steps) -> Nonnull; + // Runs `action` in an environment where the given constants are defined, and + // returns the result. `action` must produce a result. In other words, it must + // not be a StatementAction, ScopeAction, or DeclarationAction. Can only be + // called at compile time (before InterpProgram), and while `todo_` is empty. + auto RunCompileTimeAction(std::unique_ptr action) + -> Nonnull; Nonnull arena_; - // Globally-defined entities, such as functions, structs, or choices. - Env globals_; - - ActionStack todo_; Heap heap_; + ActionStack todo_; // The underlying states of continuation values. All StackFragments created // during execution are tracked here, in order to safely deallocate the diff --git a/executable_semantics/interpreter/type_checker.cpp b/executable_semantics/interpreter/type_checker.cpp index 5d335ddf1ed7..34d917384358 100644 --- a/executable_semantics/interpreter/type_checker.cpp +++ b/executable_semantics/interpreter/type_checker.cpp @@ -390,23 +390,22 @@ auto TypeChecker::Substitute( } } -void TypeChecker::TypeCheckExp(Nonnull e, Env values) { +void TypeChecker::TypeCheckExp(Nonnull e) { if (trace_) { - llvm::outs() << "checking expression " << *e << "\nvalues: "; - interpreter_.PrintEnv(values, llvm::outs()); + llvm::outs() << "checking expression " << *e << "\nconstants: "; + PrintConstants(llvm::outs()); llvm::outs() << "\n"; } switch (e->kind()) { case ExpressionKind::IndexExpression: { auto& index = cast(*e); - TypeCheckExp(&index.aggregate(), values); + TypeCheckExp(&index.aggregate()); const Value& aggregate_type = index.aggregate().static_type(); switch (aggregate_type.kind()) { case Value::Kind::TupleValue: { const auto& tuple_type = cast(aggregate_type); int i = - cast(*interpreter_.InterpExp(values, &index.offset())) - .value(); + cast(*interpreter_.InterpExp(&index.offset())).value(); if (i < 0 || i >= static_cast(tuple_type.elements().size())) { FATAL_COMPILATION_ERROR(e->source_loc()) << "index " << i << " is out of range for type " << tuple_type; @@ -422,7 +421,7 @@ void TypeChecker::TypeCheckExp(Nonnull e, Env values) { case ExpressionKind::TupleLiteral: { std::vector> arg_types; for (auto& arg : cast(*e).fields()) { - TypeCheckExp(arg, values); + TypeCheckExp(arg); arg_types.push_back(&arg->static_type()); } SetStaticType(e, arena_->New(std::move(arg_types))); @@ -432,7 +431,7 @@ void TypeChecker::TypeCheckExp(Nonnull e, Env values) { case ExpressionKind::StructLiteral: { std::vector arg_types; for (auto& arg : cast(*e).fields()) { - TypeCheckExp(&arg.expression(), values); + TypeCheckExp(&arg.expression()); arg_types.push_back({arg.name(), &arg.expression().static_type()}); } SetStaticType(e, arena_->New(std::move(arg_types))); @@ -442,9 +441,9 @@ void TypeChecker::TypeCheckExp(Nonnull e, Env values) { case ExpressionKind::StructTypeLiteral: { auto& struct_type = cast(*e); for (auto& arg : struct_type.fields()) { - TypeCheckExp(&arg.expression(), values); + TypeCheckExp(&arg.expression()); ExpectIsConcreteType(arg.expression().source_loc(), - interpreter_.InterpExp(values, &arg.expression())); + interpreter_.InterpExp(&arg.expression())); } if (struct_type.fields().empty()) { // `{}` is the type of `{}`, just as `()` is the type of `()`. @@ -460,7 +459,7 @@ void TypeChecker::TypeCheckExp(Nonnull e, Env values) { } case ExpressionKind::FieldAccessExpression: { auto& access = cast(*e); - TypeCheckExp(&access.aggregate(), values); + TypeCheckExp(&access.aggregate()); const Value& aggregate_type = access.aggregate().static_type(); switch (aggregate_type.kind()) { case Value::Kind::StructType: { @@ -549,7 +548,7 @@ void TypeChecker::TypeCheckExp(Nonnull e, Env values) { auto& op = cast(*e); std::vector> ts; for (Nonnull argument : op.arguments()) { - TypeCheckExp(argument, values); + TypeCheckExp(argument); ts.push_back(&argument->static_type()); } switch (op.op()) { @@ -624,11 +623,11 @@ void TypeChecker::TypeCheckExp(Nonnull e, Env values) { } case ExpressionKind::CallExpression: { auto& call = cast(*e); - TypeCheckExp(&call.function(), values); + TypeCheckExp(&call.function()); switch (call.function().static_type().kind()) { case Value::Kind::FunctionType: { const auto& fun_t = cast(call.function().static_type()); - TypeCheckExp(&call.argument(), values); + TypeCheckExp(&call.argument()); Nonnull parameters = &fun_t.parameters(); Nonnull return_type = &fun_t.return_type(); if (!fun_t.deduced().empty()) { @@ -668,9 +667,9 @@ void TypeChecker::TypeCheckExp(Nonnull e, Env values) { case ExpressionKind::FunctionTypeLiteral: { auto& fn = cast(*e); ExpectIsConcreteType(fn.parameter().source_loc(), - interpreter_.InterpExp(values, &fn.parameter())); + interpreter_.InterpExp(&fn.parameter())); ExpectIsConcreteType(fn.return_type().source_loc(), - interpreter_.InterpExp(values, &fn.return_type())); + interpreter_.InterpExp(&fn.return_type())); SetStaticType(&fn, arena_->New()); fn.set_value_category(ValueCategory::Let); return; @@ -681,7 +680,7 @@ void TypeChecker::TypeCheckExp(Nonnull e, Env values) { return; case ExpressionKind::IntrinsicExpression: { auto& intrinsic_exp = cast(*e); - TypeCheckExp(&intrinsic_exp.args(), values); + TypeCheckExp(&intrinsic_exp.args()); switch (cast(*e).intrinsic()) { case IntrinsicExpression::Intrinsic::Print: if (intrinsic_exp.args().fields().size() != 1) { @@ -710,15 +709,14 @@ void TypeChecker::TypeCheckExp(Nonnull e, Env values) { } void TypeChecker::TypeCheckPattern( - Nonnull p, Env values, - std::optional> expected) { + Nonnull p, std::optional> expected) { if (trace_) { llvm::outs() << "checking pattern " << *p; if (expected) { llvm::outs() << ", expecting " << **expected; } - llvm::outs() << "\nvalues: "; - interpreter_.PrintEnv(values, llvm::outs()); + llvm::outs() << "\nconstants: "; + PrintConstants(llvm::outs()); llvm::outs() << "\n"; } switch (p->kind()) { @@ -728,9 +726,8 @@ void TypeChecker::TypeCheckPattern( } case PatternKind::BindingPattern: { auto& binding = cast(*p); - TypeCheckPattern(&binding.type(), values, std::nullopt); - Nonnull type = - interpreter_.InterpPattern(values, &binding.type()); + TypeCheckPattern(&binding.type(), std::nullopt); + Nonnull type = interpreter_.InterpPattern(&binding.type()); if (expected) { if (IsConcreteType(type)) { ExpectType(p->source_loc(), "name binding", type, *expected); @@ -742,14 +739,12 @@ void TypeChecker::TypeCheckPattern( << "Type pattern '" << *type << "' does not match actual type '" << **expected << "'"; } - CHECK(values->begin() == values->end()) - << "Name bindings within type patterns are unsupported"; type = *expected; } } ExpectIsConcreteType(binding.source_loc(), type); SetStaticType(&binding, type); - SetValue(&binding, interpreter_.InterpPattern(values, &binding)); + SetValue(&binding, interpreter_.InterpPattern(&binding)); return; } case PatternKind::TuplePattern: { @@ -769,16 +764,16 @@ void TypeChecker::TypeCheckPattern( if (expected) { expected_field_type = cast(**expected).elements()[i]; } - TypeCheckPattern(field, values, expected_field_type); + TypeCheckPattern(field, expected_field_type); field_types.push_back(&field->static_type()); } SetStaticType(&tuple, arena_->New(std::move(field_types))); - SetValue(&tuple, interpreter_.InterpPattern(values, &tuple)); + SetValue(&tuple, interpreter_.InterpPattern(&tuple)); return; } case PatternKind::AlternativePattern: { auto& alternative = cast(*p); - TypeCheckExp(&alternative.choice_type(), values); + TypeCheckExp(&alternative.choice_type()); if (alternative.choice_type().static_type().kind() != Value::Kind::TypeOfChoiceType) { FATAL_COMPILATION_ERROR(alternative.source_loc()) @@ -799,41 +794,40 @@ void TypeChecker::TypeCheckPattern( << "'" << alternative.alternative_name() << "' is not an alternative of " << choice_type; } - TypeCheckPattern(&alternative.arguments(), values, *parameter_types); + TypeCheckPattern(&alternative.arguments(), *parameter_types); SetStaticType(&alternative, &choice_type); - SetValue(&alternative, interpreter_.InterpPattern(values, &alternative)); + SetValue(&alternative, interpreter_.InterpPattern(&alternative)); return; } case PatternKind::ExpressionPattern: { auto& expression = cast(*p).expression(); - TypeCheckExp(&expression, values); + TypeCheckExp(&expression); SetStaticType(p, &expression.static_type()); - SetValue(p, interpreter_.InterpPattern(values, p)); + SetValue(p, interpreter_.InterpPattern(p)); return; } } } -void TypeChecker::TypeCheckStmt(Nonnull s, Env values) { +void TypeChecker::TypeCheckStmt(Nonnull s) { switch (s->kind()) { case StatementKind::Match: { auto& match = cast(*s); - TypeCheckExp(&match.expression(), values); + TypeCheckExp(&match.expression()); std::vector new_clauses; for (auto& clause : match.clauses()) { - TypeCheckPattern(&clause.pattern(), values, - &match.expression().static_type()); - TypeCheckStmt(&clause.statement(), values); + TypeCheckPattern(&clause.pattern(), &match.expression().static_type()); + TypeCheckStmt(&clause.statement()); } return; } case StatementKind::While: { auto& while_stmt = cast(*s); - TypeCheckExp(&while_stmt.condition(), values); + TypeCheckExp(&while_stmt.condition()); ExpectType(s->source_loc(), "condition of `while`", arena_->New(), &while_stmt.condition().static_type()); - TypeCheckStmt(&while_stmt.body(), values); + TypeCheckStmt(&while_stmt.body()); return; } case StatementKind::Break: @@ -842,21 +836,21 @@ void TypeChecker::TypeCheckStmt(Nonnull s, Env values) { case StatementKind::Block: { auto& block = cast(*s); for (auto* block_statement : block.statements()) { - TypeCheckStmt(block_statement, values); + TypeCheckStmt(block_statement); } return; } case StatementKind::VariableDefinition: { auto& var = cast(*s); - TypeCheckExp(&var.init(), values); + TypeCheckExp(&var.init()); const Value& rhs_ty = var.init().static_type(); - TypeCheckPattern(&var.pattern(), values, &rhs_ty); + TypeCheckPattern(&var.pattern(), &rhs_ty); return; } case StatementKind::Assign: { auto& assign = cast(*s); - TypeCheckExp(&assign.rhs(), values); - TypeCheckExp(&assign.lhs(), values); + TypeCheckExp(&assign.rhs()); + TypeCheckExp(&assign.lhs()); ExpectType(s->source_loc(), "assign", &assign.lhs().static_type(), &assign.rhs().static_type()); if (assign.lhs().value_category() != ValueCategory::Var) { @@ -866,23 +860,23 @@ void TypeChecker::TypeCheckStmt(Nonnull s, Env values) { return; } case StatementKind::ExpressionStatement: { - TypeCheckExp(&cast(*s).expression(), values); + TypeCheckExp(&cast(*s).expression()); return; } case StatementKind::If: { auto& if_stmt = cast(*s); - TypeCheckExp(&if_stmt.condition(), values); + TypeCheckExp(&if_stmt.condition()); ExpectType(s->source_loc(), "condition of `if`", arena_->New(), &if_stmt.condition().static_type()); - TypeCheckStmt(&if_stmt.then_block(), values); + TypeCheckStmt(&if_stmt.then_block()); if (if_stmt.else_block()) { - TypeCheckStmt(*if_stmt.else_block(), values); + TypeCheckStmt(*if_stmt.else_block()); } return; } case StatementKind::Return: { auto& ret = cast(*s); - TypeCheckExp(&ret.expression(), values); + TypeCheckExp(&ret.expression()); ReturnTerm& return_term = ret.function().return_term(); if (return_term.is_auto()) { SetStaticType(&return_term, &ret.expression().static_type()); @@ -894,13 +888,13 @@ void TypeChecker::TypeCheckStmt(Nonnull s, Env values) { } case StatementKind::Continuation: { auto& cont = cast(*s); - TypeCheckStmt(&cont.body(), values); + TypeCheckStmt(&cont.body()); SetStaticType(&cont, arena_->New()); return; } case StatementKind::Run: { auto& run = cast(*s); - TypeCheckExp(&run.argument(), values); + TypeCheckExp(&run.argument()); ExpectType(s->source_loc(), "argument of `run`", arena_->New(), &run.argument().static_type()); @@ -988,17 +982,16 @@ void TypeChecker::ExpectReturnOnAllPaths( // TODO: Add checking to function definitions to ensure that // all deduced type parameters will be deduced. void TypeChecker::TypeCheckFunctionDeclaration(Nonnull f, - Env values, bool check_body) { + bool check_body) { // Bring the deduced parameters into scope for (Nonnull deduced : f->deduced_parameters()) { - TypeCheckExp(&deduced->type(), values); + TypeCheckExp(&deduced->type()); // auto t = interpreter_.InterpExp(values, deduced.type); SetStaticType(deduced, arena_->New(deduced)); - AllocationId a = interpreter_.AllocateValue(&deduced->static_type()); - values.Set(deduced->name(), a); + SetConstantValue(deduced, &deduced->static_type()); } // Type check the parameter pattern - TypeCheckPattern(&f->param_pattern(), values, std::nullopt); + TypeCheckPattern(&f->param_pattern(), std::nullopt); // Evaluate the return type, if we can do so without examining the body. if (std::optional> return_expression = @@ -1006,9 +999,9 @@ void TypeChecker::TypeCheckFunctionDeclaration(Nonnull f, return_expression.has_value()) { // We ignore the return value because return type expressions can't bring // new types into scope. - TypeCheckExp(*return_expression, values); + TypeCheckExp(*return_expression); SetStaticType(&f->return_term(), - interpreter_.InterpExp(values, *return_expression)); + interpreter_.InterpExp(*return_expression)); } else if (f->return_term().is_omitted()) { SetStaticType(&f->return_term(), TupleValue::Empty()); } else { @@ -1021,7 +1014,7 @@ void TypeChecker::TypeCheckFunctionDeclaration(Nonnull f, } if (f->body().has_value() && check_body) { - TypeCheckStmt(*f->body(), values); + TypeCheckStmt(*f->body()); if (!f->return_term().is_omitted()) { ExpectReturnOnAllPaths(f->body(), f->source_loc()); } @@ -1044,7 +1037,7 @@ void TypeChecker::TypeCheckFunctionDeclaration(Nonnull f, } void TypeChecker::TypeCheckClassDeclaration( - Nonnull class_decl, Env ct_top) { + Nonnull class_decl) { std::vector fields; std::vector methods; for (Nonnull m : class_decl->members()) { @@ -1055,7 +1048,7 @@ void TypeChecker::TypeCheckClassDeclaration( FATAL_COMPILATION_ERROR(binding.source_loc()) << "Struct members must have names"; } - TypeCheckPattern(&binding, ct_top, std::nullopt); + TypeCheckPattern(&binding, std::nullopt); fields.push_back( {.name = binding.name(), .value = &binding.static_type()}); break; @@ -1068,12 +1061,12 @@ void TypeChecker::TypeCheckClassDeclaration( class_decl->name(), std::move(fields), std::move(methods)))); } -void TypeChecker::TypeCheckChoiceDeclaration(Nonnull choice, - Env ct_top) { +void TypeChecker::TypeCheckChoiceDeclaration( + Nonnull choice) { std::vector alternatives; for (Nonnull alternative : choice->alternatives()) { - TypeCheckExp(&alternative->signature(), ct_top); - auto signature = interpreter_.InterpExp(ct_top, &alternative->signature()); + TypeCheckExp(&alternative->signature()); + auto signature = interpreter_.InterpExp(&alternative->signature()); alternatives.push_back({.name = alternative->name(), .value = signature}); } auto ct = arena_->New(choice->name(), std::move(alternatives)); @@ -1081,35 +1074,33 @@ void TypeChecker::TypeCheckChoiceDeclaration(Nonnull choice, } void TypeChecker::TypeCheck(AST& ast) { - Env values(arena_); for (Nonnull declaration : ast.declarations) { - TopLevel(declaration, &values); + TopLevel(declaration); } for (Nonnull decl : ast.declarations) { - TypeCheckDeclaration(decl, values); + TypeCheckDeclaration(decl); } - TypeCheckExp(*ast.main_call, values); + TypeCheckExp(*ast.main_call); } -void TypeChecker::TypeCheckDeclaration(Nonnull d, - const Env& values) { +void TypeChecker::TypeCheckDeclaration(Nonnull d) { switch (d->kind()) { case DeclarationKind::FunctionDeclaration: - TypeCheckFunctionDeclaration(&cast(*d), values, + TypeCheckFunctionDeclaration(&cast(*d), /*check_body=*/true); return; case DeclarationKind::ClassDeclaration: - TypeCheckClassDeclaration(&cast(*d), values); + TypeCheckClassDeclaration(&cast(*d)); return; case DeclarationKind::ChoiceDeclaration: - TypeCheckChoiceDeclaration(&cast(*d), values); + TypeCheckChoiceDeclaration(&cast(*d)); return; case DeclarationKind::VariableDeclaration: { auto& var = cast(*d); // Signals a type error if the initializing expression does not have // the declared type of the variable, otherwise returns this // declaration with annotated types. - TypeCheckExp(&var.initializer(), values); + TypeCheckExp(&var.initializer()); const auto* binding_type = dyn_cast(&var.binding().type()); if (binding_type == nullptr) { @@ -1118,7 +1109,7 @@ void TypeChecker::TypeCheckDeclaration(Nonnull d, << "Type of a top-level variable must be an expression."; } Nonnull declared_type = - interpreter_.InterpExp(values, &binding_type->expression()); + interpreter_.InterpExp(&binding_type->expression()); SetStaticType(&var, declared_type); ExpectType(var.source_loc(), "initializer of variable", declared_type, &var.initializer().static_type()); @@ -1127,33 +1118,28 @@ void TypeChecker::TypeCheckDeclaration(Nonnull d, } } -void TypeChecker::TopLevel(Nonnull d, Nonnull values) { +void TypeChecker::TopLevel(Nonnull d) { switch (d->kind()) { case DeclarationKind::FunctionDeclaration: { auto& func_def = cast(*d); - TypeCheckFunctionDeclaration(&func_def, *values, - /*check_body=*/false); - interpreter_.InitEnv(*d, values); + TypeCheckFunctionDeclaration(&func_def, /*check_body=*/false); + SetConstantValue(&func_def, arena_->New(&func_def)); break; } case DeclarationKind::ClassDeclaration: { auto& class_decl = cast(*d); - TypeCheckClassDeclaration(&class_decl, *values); + TypeCheckClassDeclaration(&class_decl); const auto& type = cast(class_decl.static_type()); - const NominalClassType& value = type.class_type(); - AllocationId a = interpreter_.AllocateValue(&value); - values->Set(class_decl.name(), a); // Is this obsolete? + SetConstantValue(&class_decl, &type.class_type()); break; } case DeclarationKind::ChoiceDeclaration: { auto& choice = cast(*d); - TypeCheckChoiceDeclaration(&choice, *values); + TypeCheckChoiceDeclaration(&choice); const auto& type = cast(choice.static_type()); - const ChoiceType& value = type.choice_type(); - AllocationId a = interpreter_.AllocateValue(&value); - values->Set(choice.name(), a); // Is this obsolete? + SetConstantValue(&choice, &type.choice_type()); break; } @@ -1163,13 +1149,33 @@ void TypeChecker::TopLevel(Nonnull d, Nonnull values) { // compile-time symbol table. Expression& type = cast(var.binding().type()).expression(); - TypeCheckPattern(&var.binding(), *values, std::nullopt); - Nonnull declared_type = - interpreter_.InterpExp(*values, &type); + TypeCheckPattern(&var.binding(), std::nullopt); + Nonnull declared_type = interpreter_.InterpExp(&type); SetStaticType(&var, declared_type); break; } } } +template +void TypeChecker::SetConstantValue(Nonnull named_entity, + Nonnull value) { + std::optional> old_value = + named_entity->constant_value(); + if (old_value.has_value()) { + CHECK(ValueEqual(*old_value, value)); + } else { + named_entity->set_constant_value(value); + CHECK(constants_.insert(named_entity).second); + } +} + +void TypeChecker::PrintConstants(llvm::raw_ostream& out) { + llvm::ListSeparator sep; + for (const auto& named_entity : constants_) { + out << sep << named_entity.name() << ": " + << **named_entity.constant_value(); + } +} + } // namespace Carbon diff --git a/executable_semantics/interpreter/type_checker.h b/executable_semantics/interpreter/type_checker.h index f03dfbb08058..5194309099b8 100644 --- a/executable_semantics/interpreter/type_checker.h +++ b/executable_semantics/interpreter/type_checker.h @@ -42,39 +42,37 @@ class TypeChecker { // // `values` maps variable names to their compile-time values. It is not // directly used in this function but is passed to InterExp. - void TypeCheckExp(Nonnull e, Env values); + void TypeCheckExp(Nonnull e); // Equivalent to TypeCheckExp, but operates on the AST rooted at `p`. // // `expected` is the type that this pattern is expected to have, if the // surrounding context gives us that information. Otherwise, it is // nullopt. - void TypeCheckPattern(Nonnull p, Env values, + void TypeCheckPattern(Nonnull p, std::optional> expected); // Equivalent to TypeCheckExp, but operates on the AST rooted at `d`. - void TypeCheckDeclaration(Nonnull d, const Env& values); + void TypeCheckDeclaration(Nonnull d); // Equivalent to TypeCheckExp, but operates on the AST rooted at `s`. // // REQUIRES: f.return_term().has_static_type() || f.return_term().is_auto(), // where `f` is nearest enclosing FunctionDeclaration of `s`. - void TypeCheckStmt(Nonnull s, Env values); + void TypeCheckStmt(Nonnull s); // Equivalent to TypeCheckExp, but operates on the AST rooted at `f`, // and may not traverse f->body() if `check_body` is false. - void TypeCheckFunctionDeclaration(Nonnull f, Env values, + void TypeCheckFunctionDeclaration(Nonnull f, bool check_body); // Equivalent to TypeCheckExp, but operates on the AST rooted at class_decl. - void TypeCheckClassDeclaration(Nonnull class_decl, - Env ct_top); + void TypeCheckClassDeclaration(Nonnull class_decl); // Equivalent to TypeCheckExp, but operates on the AST rooted at choice_decl. - void TypeCheckChoiceDeclaration(Nonnull choice, - Env ct_top); + void TypeCheckChoiceDeclaration(Nonnull choice); - void TopLevel(Nonnull d, Nonnull values); + void TopLevel(Nonnull d); // Verifies that opt_stmt holds a statement, and it is structurally impossible // for control flow to leave that statement except via a `return`. @@ -90,8 +88,17 @@ class TypeChecker { Nonnull>& dict, Nonnull type) -> Nonnull; + // Sets named_entity.constant_value() to `value`. Can be called multiple + // times on the same named_entity, so long as it is always called with + // the same value. + template + void SetConstantValue(Nonnull named_entity, Nonnull value); + + void PrintConstants(llvm::raw_ostream& out); + Nonnull arena_; Interpreter interpreter_; + std::set constants_; bool trace_; }; diff --git a/executable_semantics/testdata/basic_syntax/trace.carbon b/executable_semantics/testdata/basic_syntax/trace.carbon index 7402d21f7938..16937731d4f7 100644 --- a/executable_semantics/testdata/basic_syntax/trace.carbon +++ b/executable_semantics/testdata/basic_syntax/trace.carbon @@ -13,7 +13,7 @@ // CHECK: fn Print (format_str: String) { // CHECK: ********** type checking ********** // CHECK: checking pattern (format_str: String) -// CHECK: values: Print: fun +// CHECK: constants: Main: fun
, Print: fun // CHECK: ********** type checking complete ********** // CHECK: fn Print (format_str: String) { // CHECK: ********** starting execution ********** @@ -21,7 +21,7 @@ // CHECK: ********** calling main function ********** // CHECK: { // CHECK: stack: -// CHECK: heap: fun, fun
+// CHECK: heap: // CHECK: } // CHECK: --- step exp Main() (:0) ---> // CHECK: result: 0