Просмотр исходного кода

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.
Geoff Romer 4 лет назад
Родитель
Сommit
fd45b089e9

+ 2 - 0
executable_semantics/interpreter/interpreter.cpp

@@ -392,6 +392,8 @@ auto Interpreter::Convert(Nonnull<const Value*> 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;

+ 58 - 43
executable_semantics/interpreter/type_checker.cpp

@@ -102,6 +102,8 @@ static auto IsConcreteType(Nonnull<const Value*> 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<Expression*> e, TypeEnv types,
               << "class " << t_class.name() << " does not have a field named "
               << access.field();
         }
-        case Value::Kind::ChoiceType: {
-          const auto& choice = cast<ChoiceType>(aggregate_type);
+        case Value::Kind::TypeOfChoiceType: {
+          const ChoiceType& choice =
+              cast<TypeOfChoiceType>(aggregate_type).choice_type();
           std::optional<Nonnull<const Value*>> parameter_types =
               choice.FindAlternative(access.field());
           if (!parameter_types.has_value()) {
@@ -803,27 +810,30 @@ auto TypeChecker::TypeCheckPattern(
     }
     case PatternKind::AlternativePattern: {
       auto& alternative = cast<AlternativePattern>(*p);
-      Nonnull<const Value*> 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<TypeOfChoiceType>(alternative.choice_type().static_type())
+              .choice_type();
       std::optional<Nonnull<const Value*>> parameter_types =
-          cast<ChoiceType>(*choice_type)
+          cast<ChoiceType>(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<FunctionDeclaration*> f,
   return TCResult(types);
 }
 
-auto TypeChecker::TypeOfClassDecl(ClassDeclaration& class_decl,
-                                  TypeEnv /*types*/, Env ct_top)
-    -> Nonnull<const Value*> {
+auto TypeChecker::TypeCheckClassDeclaration(
+    Nonnull<ClassDeclaration*> class_decl, TypeEnv types, Env ct_top)
+    -> TCResult {
   std::vector<NamedValue> fields;
   std::vector<NamedValue> methods;
-  for (Nonnull<const Member*> m : class_decl.members()) {
+  for (Nonnull<Member*> m : class_decl->members()) {
     switch (m->kind()) {
       case MemberKind::FieldMember: {
-        const BindingPattern& binding = cast<FieldMember>(*m).binding();
+        BindingPattern& binding = cast<FieldMember>(*m).binding();
         if (!binding.name().has_value()) {
           FATAL_COMPILATION_ERROR(binding.source_loc())
               << "Struct members must have names";
         }
-        const auto* binding_type = dyn_cast<ExpressionPattern>(&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<NominalClassType>(
-                    class_decl.name(), std::move(fields), std::move(methods)));
-  return &class_decl.static_type();
+  SetStaticType(
+      class_decl,
+      arena_->New<TypeOfClassType>(arena_->New<NominalClassType>(
+          class_decl->name(), std::move(fields), std::move(methods))));
+  return TCResult(types);
+}
+
+auto TypeChecker::TypeCheckChoiceDeclaration(Nonnull<ChoiceDeclaration*> choice,
+                                             TypeEnv types, Env ct_top)
+    -> TCResult {
+  std::vector<NamedValue> alternatives;
+  for (Nonnull<AlternativeSignature*> 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<ChoiceType>(choice->name(), std::move(alternatives));
+  SetStaticType(choice, arena_->New<TypeOfChoiceType>(ct));
+  return TCResult(types);
 }
 
 static auto GetName(const Declaration& d) -> const std::string& {
@@ -1150,13 +1171,11 @@ void TypeChecker::TypeCheckDeclaration(Nonnull<Declaration*> d,
                                    values, /*check_body=*/true);
       return;
     case DeclarationKind::ClassDeclaration:
-      // TODO
+      TypeCheckClassDeclaration(&cast<ClassDeclaration>(*d), types, values);
       return;
-
     case DeclarationKind::ChoiceDeclaration:
-      // TODO
+      TypeCheckChoiceDeclaration(&cast<ChoiceDeclaration>(*d), types, values);
       return;
-
     case DeclarationKind::VariableDeclaration: {
       auto& var = cast<VariableDeclaration>(*d);
       // Signals a type error if the initializing expression does not have
@@ -1193,27 +1212,23 @@ void TypeChecker::TopLevel(Nonnull<Declaration*> d, TypeCheckContext* tops) {
 
     case DeclarationKind::ClassDeclaration: {
       auto& class_decl = cast<ClassDeclaration>(*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<TypeOfClassType>(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<ChoiceDeclaration>(*d);
-      std::vector<NamedValue> alts;
-      for (Nonnull<const AlternativeSignature*> alternative :
-           choice.alternatives()) {
-        auto t =
-            interpreter_.InterpExp(tops->values, &alternative->signature());
-        alts.push_back({.name = alternative->name(), .value = t});
-      }
-      auto ct = arena_->New<ChoiceType>(choice.name(), std::move(alts));
-      SetStaticType(&choice, ct);
-      AllocationId a = interpreter_.AllocateValue(ct);
+      TypeCheckChoiceDeclaration(&choice, tops->types, tops->values);
+      const auto& type = cast<TypeOfChoiceType>(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;
     }
 

+ 7 - 2
executable_semantics/interpreter/type_checker.h

@@ -96,8 +96,13 @@ class TypeChecker {
                      Nonnull<Statement*> body, TypeEnv types, Env values)
       -> Match::Clause;
 
-  auto TypeOfClassDecl(ClassDeclaration& class_decl, TypeEnv /*types*/,
-                       Env ct_top) -> Nonnull<const Value*>;
+  // Equivalent to TypeCheckExp, but operates on the AST rooted at class_decl.
+  auto TypeCheckClassDeclaration(Nonnull<ClassDeclaration*> class_decl,
+                                 TypeEnv types, Env ct_top) -> TCResult;
+
+  // Equivalent to TypeCheckExp, but operates on the AST rooted at choice_decl.
+  auto TypeCheckChoiceDeclaration(Nonnull<ChoiceDeclaration*> choice,
+                                  TypeEnv types, Env ct_top) -> TCResult;
 
   auto TopLevel(std::vector<Nonnull<Declaration*>>* fs) -> TypeCheckContext;
   void TopLevel(Nonnull<Declaration*> d, TypeCheckContext* tops);

+ 16 - 0
executable_semantics/interpreter/value.cpp

@@ -255,6 +255,14 @@ void Value::Print(llvm::raw_ostream& out) const {
       out.write_escaped(cast<StringValue>(*this).value());
       out << "\"";
       break;
+    case Value::Kind::TypeOfClassType:
+      out << "typeof(" << cast<TypeOfClassType>(*this).class_type().name()
+          << ")";
+      break;
+    case Value::Kind::TypeOfChoiceType:
+      out << "typeof(" << cast<TypeOfChoiceType>(*this).choice_type().name()
+          << ")";
+      break;
   }
 }
 
@@ -350,6 +358,12 @@ auto TypeEqual(Nonnull<const Value*> t1, Nonnull<const Value*> t2) -> bool {
     case Value::Kind::VariableType:
       return &cast<VariableType>(*t1).binding() ==
              &cast<VariableType>(*t2).binding();
+    case Value::Kind::TypeOfClassType:
+      return TypeEqual(&cast<TypeOfClassType>(*t1).class_type(),
+                       &cast<TypeOfClassType>(*t2).class_type());
+    case Value::Kind::TypeOfChoiceType:
+      return TypeEqual(&cast<TypeOfChoiceType>(*t1).choice_type(),
+                       &cast<TypeOfChoiceType>(*t2).choice_type());
     default:
       FATAL() << "TypeEqual used to compare non-type values\n"
               << *t1 << "\n"
@@ -420,6 +434,8 @@ auto ValueEqual(Nonnull<const Value*> v1, Nonnull<const Value*> 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:

+ 40 - 0
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<const NominalClassType*> 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<const NominalClassType*> 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<const ChoiceType*> 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<const ChoiceType*> choice_type_;
+};
+
 auto TypeEqual(Nonnull<const Value*> t1, Nonnull<const Value*> t2) -> bool;
 auto ValueEqual(Nonnull<const Value*> v1, Nonnull<const Value*> v2,
                 SourceLocation source_loc) -> bool;