From fd45b089e9424e8763da3ac151b4ce4c1ad0c634 Mon Sep 17 00:00:00 2001 From: Geoff Romer Date: Mon, 13 Dec 2021 16:19:52 -0800 Subject: [PATCH] Add typechecking of type declarations (#984) Also explicitly model the types of expressions whose values are class or choice types, and make those be the static_type of the type declaration. This ensures that the static_type of a NamedEntity consistently corresponds to the static type of an IdentifierExpression using that name. --- .../interpreter/interpreter.cpp | 2 + .../interpreter/type_checker.cpp | 101 ++++++++++-------- .../interpreter/type_checker.h | 9 +- executable_semantics/interpreter/value.cpp | 16 +++ executable_semantics/interpreter/value.h | 40 +++++++ 5 files changed, 123 insertions(+), 45 deletions(-) diff --git a/executable_semantics/interpreter/interpreter.cpp b/executable_semantics/interpreter/interpreter.cpp index 71f0c86dd113..1c78bd69f08d 100644 --- a/executable_semantics/interpreter/interpreter.cpp +++ b/executable_semantics/interpreter/interpreter.cpp @@ -392,6 +392,8 @@ auto Interpreter::Convert(Nonnull value, case Value::Kind::ContinuationValue: case Value::Kind::StringType: case Value::Kind::StringValue: + case Value::Kind::TypeOfClassType: + case Value::Kind::TypeOfChoiceType: // TODO: add `CHECK(TypeEqual(type, value->dynamic_type()))`, once we // have Value::dynamic_type. return value; diff --git a/executable_semantics/interpreter/type_checker.cpp b/executable_semantics/interpreter/type_checker.cpp index 60129a3b0080..757f4f9ccae2 100644 --- a/executable_semantics/interpreter/type_checker.cpp +++ b/executable_semantics/interpreter/type_checker.cpp @@ -102,6 +102,8 @@ static auto IsConcreteType(Nonnull value) -> bool { case Value::Kind::ContinuationType: case Value::Kind::VariableType: case Value::Kind::StringType: + case Value::Kind::TypeOfClassType: + case Value::Kind::TypeOfChoiceType: return true; case Value::Kind::AutoType: // `auto` isn't a concrete type, it's a pattern that matches types. @@ -310,6 +312,8 @@ void TypeChecker::ArgumentDeduction( case Value::Kind::BoolType: case Value::Kind::TypeType: case Value::Kind::StringType: + case Value::Kind::TypeOfClassType: + case Value::Kind::TypeOfChoiceType: ExpectType(source_loc, "argument deduction", param, arg); return; // The rest of these cases should never happen. @@ -374,6 +378,8 @@ auto TypeChecker::Substitute( case Value::Kind::ChoiceType: case Value::Kind::ContinuationType: case Value::Kind::StringType: + case Value::Kind::TypeOfClassType: + case Value::Kind::TypeOfChoiceType: return type; // The rest of these cases should never happen. case Value::Kind::IntValue: @@ -512,8 +518,9 @@ auto TypeChecker::TypeCheckExp(Nonnull e, TypeEnv types, << "class " << t_class.name() << " does not have a field named " << access.field(); } - case Value::Kind::ChoiceType: { - const auto& choice = cast(aggregate_type); + case Value::Kind::TypeOfChoiceType: { + const ChoiceType& choice = + cast(aggregate_type).choice_type(); std::optional> parameter_types = choice.FindAlternative(access.field()); if (!parameter_types.has_value()) { @@ -803,27 +810,30 @@ auto TypeChecker::TypeCheckPattern( } case PatternKind::AlternativePattern: { auto& alternative = cast(*p); - Nonnull choice_type = - interpreter_.InterpExp(values, &alternative.choice_type()); - if (choice_type->kind() != Value::Kind::ChoiceType) { + TypeCheckExp(&alternative.choice_type(), types, values); + if (alternative.choice_type().static_type().kind() != + Value::Kind::TypeOfChoiceType) { FATAL_COMPILATION_ERROR(alternative.source_loc()) << "alternative pattern does not name a choice type."; } if (expected) { ExpectExactType(alternative.source_loc(), "alternative pattern", - *expected, choice_type); + *expected, &alternative.choice_type().static_type()); } + const ChoiceType& choice_type = + cast(alternative.choice_type().static_type()) + .choice_type(); std::optional> parameter_types = - cast(*choice_type) + cast(choice_type) .FindAlternative(alternative.alternative_name()); if (parameter_types == std::nullopt) { FATAL_COMPILATION_ERROR(alternative.source_loc()) << "'" << alternative.alternative_name() - << "' is not an alternative of " << *choice_type; + << "' is not an alternative of " << choice_type; } TCResult arg_results = TypeCheckPattern(&alternative.arguments(), types, values, *parameter_types); - SetStaticType(&alternative, choice_type); + SetStaticType(&alternative, &choice_type); SetValue(&alternative, interpreter_.InterpPattern(values, &alternative)); return TCResult(arg_results.types); } @@ -1080,34 +1090,45 @@ auto TypeChecker::TypeCheckFunctionDeclaration(Nonnull f, return TCResult(types); } -auto TypeChecker::TypeOfClassDecl(ClassDeclaration& class_decl, - TypeEnv /*types*/, Env ct_top) - -> Nonnull { +auto TypeChecker::TypeCheckClassDeclaration( + Nonnull class_decl, TypeEnv types, Env ct_top) + -> TCResult { std::vector fields; std::vector methods; - for (Nonnull m : class_decl.members()) { + for (Nonnull m : class_decl->members()) { switch (m->kind()) { case MemberKind::FieldMember: { - const BindingPattern& binding = cast(*m).binding(); + BindingPattern& binding = cast(*m).binding(); if (!binding.name().has_value()) { FATAL_COMPILATION_ERROR(binding.source_loc()) << "Struct members must have names"; } - const auto* binding_type = dyn_cast(&binding.type()); - if (binding_type == nullptr) { - FATAL_COMPILATION_ERROR(binding.source_loc()) - << "Struct members must have explicit types"; - } - auto type = interpreter_.InterpExp(ct_top, &binding_type->expression()); - fields.push_back({.name = *binding.name(), .value = type}); + types = TypeCheckPattern(&binding, types, ct_top, std::nullopt).types; + fields.push_back( + {.name = *binding.name(), .value = &binding.static_type()}); break; } } } - SetStaticType(&class_decl, - arena_->New( - class_decl.name(), std::move(fields), std::move(methods))); - return &class_decl.static_type(); + SetStaticType( + class_decl, + arena_->New(arena_->New( + class_decl->name(), std::move(fields), std::move(methods)))); + return TCResult(types); +} + +auto TypeChecker::TypeCheckChoiceDeclaration(Nonnull choice, + TypeEnv types, Env ct_top) + -> TCResult { + std::vector alternatives; + for (Nonnull alternative : choice->alternatives()) { + types = TypeCheckExp(&alternative->signature(), types, ct_top).types; + auto signature = interpreter_.InterpExp(ct_top, &alternative->signature()); + alternatives.push_back({.name = alternative->name(), .value = signature}); + } + auto ct = arena_->New(choice->name(), std::move(alternatives)); + SetStaticType(choice, arena_->New(ct)); + return TCResult(types); } static auto GetName(const Declaration& d) -> const std::string& { @@ -1150,13 +1171,11 @@ void TypeChecker::TypeCheckDeclaration(Nonnull d, values, /*check_body=*/true); return; case DeclarationKind::ClassDeclaration: - // TODO + TypeCheckClassDeclaration(&cast(*d), types, values); return; - case DeclarationKind::ChoiceDeclaration: - // TODO + TypeCheckChoiceDeclaration(&cast(*d), types, values); return; - case DeclarationKind::VariableDeclaration: { auto& var = cast(*d); // Signals a type error if the initializing expression does not have @@ -1193,27 +1212,23 @@ void TypeChecker::TopLevel(Nonnull d, TypeCheckContext* tops) { case DeclarationKind::ClassDeclaration: { auto& class_decl = cast(*d); - auto st = TypeOfClassDecl(class_decl, tops->types, tops->values); - AllocationId a = interpreter_.AllocateValue(st); + TypeCheckClassDeclaration(&class_decl, tops->types, tops->values); + const auto& type = cast(class_decl.static_type()); + const NominalClassType& value = type.class_type(); + AllocationId a = interpreter_.AllocateValue(&value); tops->values.Set(class_decl.name(), a); // Is this obsolete? - tops->types.Set(class_decl.name(), st); + tops->types.Set(class_decl.name(), &type); break; } case DeclarationKind::ChoiceDeclaration: { auto& choice = cast(*d); - std::vector alts; - for (Nonnull alternative : - choice.alternatives()) { - auto t = - interpreter_.InterpExp(tops->values, &alternative->signature()); - alts.push_back({.name = alternative->name(), .value = t}); - } - auto ct = arena_->New(choice.name(), std::move(alts)); - SetStaticType(&choice, ct); - AllocationId a = interpreter_.AllocateValue(ct); + TypeCheckChoiceDeclaration(&choice, tops->types, tops->values); + const auto& type = cast(choice.static_type()); + const ChoiceType& value = type.choice_type(); + AllocationId a = interpreter_.AllocateValue(&value); tops->values.Set(choice.name(), a); // Is this obsolete? - tops->types.Set(choice.name(), ct); + tops->types.Set(choice.name(), &type); break; } diff --git a/executable_semantics/interpreter/type_checker.h b/executable_semantics/interpreter/type_checker.h index 166e82b82b13..46dbfca8f609 100644 --- a/executable_semantics/interpreter/type_checker.h +++ b/executable_semantics/interpreter/type_checker.h @@ -96,8 +96,13 @@ class TypeChecker { Nonnull body, TypeEnv types, Env values) -> Match::Clause; - auto TypeOfClassDecl(ClassDeclaration& class_decl, TypeEnv /*types*/, - Env ct_top) -> Nonnull; + // Equivalent to TypeCheckExp, but operates on the AST rooted at class_decl. + auto TypeCheckClassDeclaration(Nonnull class_decl, + TypeEnv types, Env ct_top) -> TCResult; + + // Equivalent to TypeCheckExp, but operates on the AST rooted at choice_decl. + auto TypeCheckChoiceDeclaration(Nonnull choice, + TypeEnv types, Env ct_top) -> TCResult; auto TopLevel(std::vector>* fs) -> TypeCheckContext; void TopLevel(Nonnull d, TypeCheckContext* tops); diff --git a/executable_semantics/interpreter/value.cpp b/executable_semantics/interpreter/value.cpp index 4ffec94b7151..8789b1045a9d 100644 --- a/executable_semantics/interpreter/value.cpp +++ b/executable_semantics/interpreter/value.cpp @@ -255,6 +255,14 @@ void Value::Print(llvm::raw_ostream& out) const { out.write_escaped(cast(*this).value()); out << "\""; break; + case Value::Kind::TypeOfClassType: + out << "typeof(" << cast(*this).class_type().name() + << ")"; + break; + case Value::Kind::TypeOfChoiceType: + out << "typeof(" << cast(*this).choice_type().name() + << ")"; + break; } } @@ -350,6 +358,12 @@ auto TypeEqual(Nonnull t1, Nonnull t2) -> bool { case Value::Kind::VariableType: return &cast(*t1).binding() == &cast(*t2).binding(); + case Value::Kind::TypeOfClassType: + return TypeEqual(&cast(*t1).class_type(), + &cast(*t2).class_type()); + case Value::Kind::TypeOfChoiceType: + return TypeEqual(&cast(*t1).choice_type(), + &cast(*t2).choice_type()); default: FATAL() << "TypeEqual used to compare non-type values\n" << *t1 << "\n" @@ -420,6 +434,8 @@ auto ValueEqual(Nonnull v1, Nonnull v2, case Value::Kind::ContinuationType: case Value::Kind::VariableType: case Value::Kind::StringType: + case Value::Kind::TypeOfClassType: + case Value::Kind::TypeOfChoiceType: return TypeEqual(v1, v2); case Value::Kind::NominalClassValue: case Value::Kind::AlternativeValue: diff --git a/executable_semantics/interpreter/value.h b/executable_semantics/interpreter/value.h index b247bb4a6b20..0d6b707fa8f0 100644 --- a/executable_semantics/interpreter/value.h +++ b/executable_semantics/interpreter/value.h @@ -58,6 +58,8 @@ class Value { ContinuationValue, // A first-class continuation value. StringType, StringValue, + TypeOfClassType, + TypeOfChoiceType, }; Value(const Value&) = delete; @@ -560,6 +562,44 @@ class StringValue : public Value { std::string value_; }; +// The type of an expression whose value is a class type. Currently there is no +// way to explicitly name such a type in Carbon code, but we are tentatively +// using `typeof(ClassName)` as the debug-printing format, in anticipation of +// something like that becoming valid Carbon syntax. +class TypeOfClassType : public Value { + public: + explicit TypeOfClassType(Nonnull class_type) + : Value(Kind::TypeOfClassType), class_type_(class_type) {} + + static auto classof(const Value* value) -> bool { + return value->kind() == Kind::TypeOfClassType; + } + + auto class_type() const -> const NominalClassType& { return *class_type_; } + + private: + Nonnull class_type_; +}; + +// The type of an expression whose value is a choice type. Currently there is no +// way to explicitly name such a type in Carbon code, but we are tentatively +// using `typeof(ChoiceName)` as the debug-printing format, in anticipation of +// something like that becoming valid Carbon syntax. +class TypeOfChoiceType : public Value { + public: + explicit TypeOfChoiceType(Nonnull choice_type) + : Value(Kind::TypeOfChoiceType), choice_type_(choice_type) {} + + static auto classof(const Value* value) -> bool { + return value->kind() == Kind::TypeOfChoiceType; + } + + auto choice_type() const -> const ChoiceType& { return *choice_type_; } + + private: + Nonnull choice_type_; +}; + auto TypeEqual(Nonnull t1, Nonnull t2) -> bool; auto ValueEqual(Nonnull v1, Nonnull v2, SourceLocation source_loc) -> bool;