Эх сурвалжийг харах

Explorer: support `.base` to initialize parent class from struct (#2361)

Relates to https://github.com/carbon-language/carbon-lang/issues/1881

- Add support for `.base` field in structs for [parent class initialization](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/classes.md#constructors)
    - Disabling base class initialization without `.base`
- Support class constructors (`Create() -> Self`) for base classes
- Direct access to base class(es) attributes with `object.var` remains unaffected

Changes:
- Add `TypeChecker::FieldTypesWithBase` to help assessing if a struct with `base` fields can be converted to a class
- Add a new `base_type()` attribute+getter to `NominalClassDeclaration` to as a first step to allow resolving parametrized classes
- Add a new `base` attribute+getter to `NominalClassValue` that contains the base class `NominalClassValue`. It is currently used mainly to get and set members of a class object.
- Add `Interpreter::ConvertClassWithBase` to build `NominalClassValue` from a init struct, that contains `.base` fields with either `NominalClassValue` or `StructValue`
- Add `FindClassField` to find a field in a class or its base classes
- Remove superfluous `ClassDeclaration::base()` in favor of `ClassDeclaration::base_type()`

Limitations;
- Though some work is done in that direction, parametrized base class where time is not know at the declaration site are not supported. Namely the example below does not compile
```
base class A(T:! Type) {}
class B(T:! Type) extends A(T) {}
```
But this one is functional already
```
base class A(T:! Type) {}
class B extends A(i32) {}
```

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
Adrien Leravat 3 жил өмнө
parent
commit
46f4887cf7

+ 13 - 7
explorer/ast/declaration.h

@@ -28,6 +28,7 @@ namespace Carbon {
 
 
 class MixinPseudoType;
 class MixinPseudoType;
 class ConstraintType;
 class ConstraintType;
+class NominalClassType;
 
 
 // Abstract base class of all AST nodes representing patterns.
 // Abstract base class of all AST nodes representing patterns.
 //
 //
@@ -275,9 +276,6 @@ class ClassDeclaration : public Declaration {
   auto type_params() -> std::optional<Nonnull<TuplePattern*>> {
   auto type_params() -> std::optional<Nonnull<TuplePattern*>> {
     return type_params_;
     return type_params_;
   }
   }
-  auto base_expr() const -> std::optional<Nonnull<Expression*>> {
-    return base_expr_;
-  }
   auto self() const -> Nonnull<const SelfDeclaration*> { return self_decl_; }
   auto self() const -> Nonnull<const SelfDeclaration*> { return self_decl_; }
   auto self() -> Nonnull<SelfDeclaration*> { return self_decl_; }
   auto self() -> Nonnull<SelfDeclaration*> { return self_decl_; }
 
 
@@ -295,11 +293,18 @@ class ClassDeclaration : public Declaration {
 
 
   auto value_category() const -> ValueCategory { return ValueCategory::Let; }
   auto value_category() const -> ValueCategory { return ValueCategory::Let; }
 
 
-  auto base() const -> std::optional<Nonnull<const ClassDeclaration*>> {
-    return base_;
+  auto base_expr() const -> std::optional<Nonnull<Expression*>> {
+    return base_expr_;
+  }
+
+  // Returns the original base type, before instantiation & substitutions
+  // Use `NominalClassType::base()` to get the instantiated type.
+  auto base_type() const -> std::optional<Nonnull<const NominalClassType*>> {
+    return base_type_;
   }
   }
-  void set_base(Nonnull<const ClassDeclaration*> base_decl) {
-    base_ = base_decl;
+  void set_base_type(
+      std::optional<Nonnull<const NominalClassType*>> base_type) {
+    base_type_ = base_type;
   }
   }
 
 
  private:
  private:
@@ -311,6 +316,7 @@ class ClassDeclaration : public Declaration {
   std::vector<Nonnull<Declaration*>> members_;
   std::vector<Nonnull<Declaration*>> members_;
   std::optional<Nonnull<FunctionDeclaration*>> destructor_;
   std::optional<Nonnull<FunctionDeclaration*>> destructor_;
   std::optional<Nonnull<const ClassDeclaration*>> base_;
   std::optional<Nonnull<const ClassDeclaration*>> base_;
+  std::optional<Nonnull<const NominalClassType*>> base_type_;
 };
 };
 
 
 // EXPERIMENTAL MIXIN FEATURE
 // EXPERIMENTAL MIXIN FEATURE

+ 2 - 0
explorer/interpreter/BUILD

@@ -23,6 +23,7 @@ cc_library(
         ":heap_allocation_interface",
         ":heap_allocation_interface",
         ":stack",
         ":stack",
         "//common:check",
         "//common:check",
+        "//common:error",
         "//common:ostream",
         "//common:ostream",
         "//explorer/ast",
         "//explorer/ast",
         "//explorer/common:arena",
         "//explorer/common:arena",
@@ -193,6 +194,7 @@ cc_library(
         "@llvm-project//llvm:Support",
         "@llvm-project//llvm:Support",
     ],
     ],
 )
 )
+
 cc_library(
 cc_library(
     name = "type_checker",
     name = "type_checker",
     srcs = [
     srcs = [

+ 53 - 8
explorer/interpreter/interpreter.cpp

@@ -22,6 +22,7 @@
 #include "explorer/interpreter/action.h"
 #include "explorer/interpreter/action.h"
 #include "explorer/interpreter/action_stack.h"
 #include "explorer/interpreter/action_stack.h"
 #include "explorer/interpreter/stack.h"
 #include "explorer/interpreter/stack.h"
+#include "explorer/interpreter/value.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/Support/Casting.h"
 #include "llvm/Support/Casting.h"
 #include "llvm/Support/Error.h"
 #include "llvm/Support/Error.h"
@@ -104,6 +105,12 @@ class Interpreter {
                Nonnull<const Value*> destination_type,
                Nonnull<const Value*> destination_type,
                SourceLocation source_loc) -> ErrorOr<Nonnull<const Value*>>;
                SourceLocation source_loc) -> ErrorOr<Nonnull<const Value*>>;
 
 
+  // Create a class value and its base class(es) from an init struct.
+  auto ConvertStructToClass(Nonnull<const StructValue*> init,
+                            Nonnull<const NominalClassType*> class_type,
+                            SourceLocation source_loc)
+      -> ErrorOr<Nonnull<NominalClassValue*>>;
+
   // Evaluate an expression immediately, recursively, and return its result.
   // Evaluate an expression immediately, recursively, and return its result.
   //
   //
   // TODO: Stop using this.
   // TODO: Stop using this.
@@ -610,10 +617,17 @@ auto Interpreter::InstantiateType(Nonnull<const Value*> type,
     }
     }
     case Value::Kind::NominalClassType: {
     case Value::Kind::NominalClassType: {
       const auto& class_type = cast<NominalClassType>(*type);
       const auto& class_type = cast<NominalClassType>(*type);
+      std::optional<Nonnull<const NominalClassType*>> base = class_type.base();
+      if (base.has_value()) {
+        CARBON_ASSIGN_OR_RETURN(const auto inst_base,
+                                InstantiateType(base.value(), source_loc));
+        base = cast<NominalClassType>(inst_base);
+      }
       CARBON_ASSIGN_OR_RETURN(
       CARBON_ASSIGN_OR_RETURN(
           Nonnull<const Bindings*> bindings,
           Nonnull<const Bindings*> bindings,
           InstantiateBindings(&class_type.bindings(), source_loc));
           InstantiateBindings(&class_type.bindings(), source_loc));
-      return arena_->New<NominalClassType>(&class_type.declaration(), bindings);
+      return arena_->New<NominalClassType>(&class_type.declaration(), bindings,
+                                           base);
     }
     }
     case Value::Kind::ChoiceType: {
     case Value::Kind::ChoiceType: {
       const auto& choice_type = cast<ChoiceType>(*type);
       const auto& choice_type = cast<ChoiceType>(*type);
@@ -661,6 +675,35 @@ auto Interpreter::InstantiateWitness(Nonnull<const Witness*> witness)
   return cast<Witness>(value);
   return cast<Witness>(value);
 }
 }
 
 
+auto Interpreter::ConvertStructToClass(
+    Nonnull<const StructValue*> init_struct,
+    Nonnull<const NominalClassType*> class_type, SourceLocation source_loc)
+    -> ErrorOr<Nonnull<NominalClassValue*>> {
+  std::vector<NamedValue> struct_values;
+  std::optional<Nonnull<const NominalClassValue*>> base_instance;
+  // Instantiate the `destination_type` to obtain the runtime
+  // type of the object.
+  CARBON_ASSIGN_OR_RETURN(Nonnull<const Value*> inst_class,
+                          InstantiateType(class_type, source_loc));
+  for (const auto& field : init_struct->elements()) {
+    if (field.name == NominalClassValue::BaseField) {
+      CARBON_CHECK(class_type->base().has_value())
+          << "Invalid 'base' field for class '"
+          << class_type->declaration().name() << "' without base class.";
+      CARBON_ASSIGN_OR_RETURN(
+          auto base,
+          Convert(field.value, class_type->base().value(), source_loc));
+      base_instance = cast<NominalClassValue>(base);
+    } else {
+      struct_values.push_back(field);
+    }
+  }
+  auto* converted_init_struct =
+      arena_->New<StructValue>(std::move(struct_values));
+  return arena_->New<NominalClassValue>(inst_class, converted_init_struct,
+                                        base_instance);
+}
+
 auto Interpreter::Convert(Nonnull<const Value*> value,
 auto Interpreter::Convert(Nonnull<const Value*> value,
                           Nonnull<const Value*> destination_type,
                           Nonnull<const Value*> destination_type,
                           SourceLocation source_loc)
                           SourceLocation source_loc)
@@ -730,12 +773,12 @@ auto Interpreter::Convert(Nonnull<const Value*> value,
           return arena_->New<StructValue>(std::move(new_elements));
           return arena_->New<StructValue>(std::move(new_elements));
         }
         }
         case Value::Kind::NominalClassType: {
         case Value::Kind::NominalClassType: {
-          // Instantiate the `destination_type` to obtain the runtime
-          // type of the object.
           CARBON_ASSIGN_OR_RETURN(
           CARBON_ASSIGN_OR_RETURN(
-              Nonnull<const Value*> inst_dest,
-              InstantiateType(destination_type, source_loc));
-          return arena_->New<NominalClassValue>(inst_dest, value);
+              auto class_value,
+              ConvertStructToClass(cast<StructValue>(value),
+                                   cast<NominalClassType>(destination_type),
+                                   source_loc));
+          return class_value;
         }
         }
         case Value::Kind::TypeType:
         case Value::Kind::TypeType:
         case Value::Kind::ConstraintType:
         case Value::Kind::ConstraintType:
@@ -962,9 +1005,11 @@ auto Interpreter::CallFunction(const CallExpression& call,
       Nonnull<const Bindings*> bindings =
       Nonnull<const Bindings*> bindings =
           arena_->New<Bindings>(std::move(generic_args), std::move(witnesses));
           arena_->New<Bindings>(std::move(generic_args), std::move(witnesses));
       switch (decl.kind()) {
       switch (decl.kind()) {
-        case DeclarationKind::ClassDeclaration:
+        case DeclarationKind::ClassDeclaration: {
+          const auto& class_decl = cast<ClassDeclaration>(decl);
           return todo_.FinishAction(arena_->New<NominalClassType>(
           return todo_.FinishAction(arena_->New<NominalClassType>(
-              &cast<ClassDeclaration>(decl), bindings));
+              &class_decl, bindings, class_decl.base_type()));
+        }
         case DeclarationKind::InterfaceDeclaration:
         case DeclarationKind::InterfaceDeclaration:
           return todo_.FinishAction(arena_->New<InterfaceType>(
           return todo_.FinishAction(arena_->New<InterfaceType>(
               &cast<InterfaceDeclaration>(decl), bindings));
               &cast<InterfaceDeclaration>(decl), bindings));

+ 60 - 58
explorer/interpreter/type_checker.cpp

@@ -12,6 +12,7 @@
 #include <set>
 #include <set>
 #include <string>
 #include <string>
 #include <string_view>
 #include <string_view>
+#include <tuple>
 #include <unordered_set>
 #include <unordered_set>
 #include <vector>
 #include <vector>
 
 
@@ -426,40 +427,37 @@ auto TypeChecker::FieldTypesImplicitlyConvertible(
   return true;
   return true;
 }
 }
 
 
-// Returns all class members from class and its parent classes.
-static auto GetClassHierarchy(const NominalClassType& class_type)
-    -> std::vector<Nonnull<const NominalClassType*>> {
-  Nonnull<const NominalClassType*> curr_class_type = &class_type;
-  std::vector<Nonnull<const NominalClassType*>> all_classes{curr_class_type};
-  while (curr_class_type->base().has_value()) {
-    curr_class_type = curr_class_type->base().value();
-    all_classes.push_back(curr_class_type);
-  }
-  return all_classes;
-}
-
 auto TypeChecker::FieldTypes(const NominalClassType& class_type) const
 auto TypeChecker::FieldTypes(const NominalClassType& class_type) const
     -> std::vector<NamedValue> {
     -> std::vector<NamedValue> {
   std::vector<NamedValue> field_types;
   std::vector<NamedValue> field_types;
-  for (const auto class_type : GetClassHierarchy(class_type)) {
-    for (Nonnull<Declaration*> m : class_type->declaration().members()) {
-      switch (m->kind()) {
-        case DeclarationKind::VariableDeclaration: {
-          const auto& var = cast<VariableDeclaration>(*m);
-          Nonnull<const Value*> field_type =
-              Substitute(class_type->bindings(), &var.binding().static_type());
-          field_types.push_back(
-              {.name = var.binding().name(), .value = field_type});
-          break;
-        }
-        default:
-          break;
+  for (Nonnull<Declaration*> m : class_type.declaration().members()) {
+    switch (m->kind()) {
+      case DeclarationKind::VariableDeclaration: {
+        const auto& var = cast<VariableDeclaration>(*m);
+        Nonnull<const Value*> field_type =
+            Substitute(class_type.bindings(), &var.binding().static_type());
+        field_types.push_back(
+            {.name = var.binding().name(), .value = field_type});
+        break;
       }
       }
+      default:
+        break;
     }
     }
   }
   }
   return field_types;
   return field_types;
 }
 }
 
 
+auto TypeChecker::FieldTypesWithBase(const NominalClassType& class_type) const
+    -> std::vector<NamedValue> {
+  auto fields = FieldTypes(class_type);
+  if (const auto base_type = class_type.base()) {
+    auto base_fields = FieldTypesWithBase(*base_type.value());
+    fields.emplace_back(NamedValue{std::string(NominalClassValue::BaseField),
+                                   base_type.value()});
+  }
+  return fields;
+}
+
 auto TypeChecker::IsImplicitlyConvertible(
 auto TypeChecker::IsImplicitlyConvertible(
     Nonnull<const Value*> source, Nonnull<const Value*> destination,
     Nonnull<const Value*> source, Nonnull<const Value*> destination,
     const ImplScope& impl_scope, bool allow_user_defined_conversions) const
     const ImplScope& impl_scope, bool allow_user_defined_conversions) const
@@ -484,10 +482,11 @@ auto TypeChecker::IsImplicitlyConvertible(
           }
           }
           break;
           break;
         case Value::Kind::NominalClassType:
         case Value::Kind::NominalClassType:
-          if (FieldTypesImplicitlyConvertible(
-                  cast<StructType>(*source).fields(),
-                  FieldTypes(cast<NominalClassType>(*destination)),
-                  impl_scope)) {
+          if (IsImplicitlyConvertible(
+                  source,
+                  arena_->New<StructType>(
+                      FieldTypesWithBase(cast<NominalClassType>(*destination))),
+                  impl_scope, allow_user_defined_conversions)) {
             return true;
             return true;
           }
           }
           break;
           break;
@@ -1897,10 +1896,15 @@ auto TypeChecker::SubstituteImpl(const Bindings& bindings,
     }
     }
     case Value::Kind::NominalClassType: {
     case Value::Kind::NominalClassType: {
       const auto& class_type = cast<NominalClassType>(*type);
       const auto& class_type = cast<NominalClassType>(*type);
+      auto base_type = class_type.base();
+      if (base_type.has_value()) {
+        base_type = cast<NominalClassType>(
+            SubstituteImpl(base_type.value()->bindings(), base_type.value()));
+      }
       Nonnull<const NominalClassType*> new_class_type =
       Nonnull<const NominalClassType*> new_class_type =
           arena_->New<NominalClassType>(
           arena_->New<NominalClassType>(
               &class_type.declaration(),
               &class_type.declaration(),
-              substitute_into_bindings(&class_type.bindings()));
+              substitute_into_bindings(&class_type.bindings()), base_type);
       return new_class_type;
       return new_class_type;
     }
     }
     case Value::Kind::InterfaceType: {
     case Value::Kind::InterfaceType: {
@@ -2567,9 +2571,9 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
               const auto res,
               const auto res,
               FindMemberWithParents(access.member_name(), &t_class));
               FindMemberWithParents(access.member_name(), &t_class));
           if (res.has_value()) {
           if (res.has_value()) {
-            auto [member_type, member] = res.value();
+            auto [member_type, member, member_t_class] = res.value();
             Nonnull<const Value*> field_type =
             Nonnull<const Value*> field_type =
-                Substitute(t_class.bindings(), member_type);
+                Substitute(member_t_class->bindings(), member_type);
             access.set_member(Member(member));
             access.set_member(Member(member));
             access.set_static_type(field_type);
             access.set_static_type(field_type);
             access.set_is_type_access(!IsInstanceMember(access.member()));
             access.set_is_type_access(!IsInstanceMember(access.member()));
@@ -4510,27 +4514,24 @@ auto TypeChecker::DeclareClassDeclaration(Nonnull<ClassDeclaration*> class_decl,
     Nonnull<Expression*> base_class_expr = *class_decl->base_expr();
     Nonnull<Expression*> base_class_expr = *class_decl->base_expr();
     CARBON_ASSIGN_OR_RETURN(const auto base_type,
     CARBON_ASSIGN_OR_RETURN(const auto base_type,
                             TypeCheckTypeExp(base_class_expr, class_scope));
                             TypeCheckTypeExp(base_class_expr, class_scope));
-    switch (base_type->kind()) {
-      case Value::Kind::NominalClassType:
-        base_class = cast<NominalClassType>(base_type);
-        if (base_class.value()->declaration().extensibility() ==
-            ClassExtensibility::None) {
-          return ProgramError(class_decl->source_loc())
-                 << "Base class `" << base_class.value()->declaration().name()
-                 << "` is `final` and cannot inherited. Add the `base` or "
-                    "`abstract` class prefix to `"
-                 << base_class.value()->declaration().name()
-                 << "` to allow it to be inherited";
-        }
-        class_decl->set_base(&base_class.value()->declaration());
-        break;
-      default:
-        return ProgramError(class_decl->source_loc())
-               << "Unsupported base class type for class `"
-               << class_decl->name()
-               << "`. Only simple classes are currently supported as base "
-                  "class.";
+    if (base_type->kind() != Value::Kind::NominalClassType) {
+      return ProgramError(class_decl->source_loc())
+             << "Unsupported base class type for class `" << class_decl->name()
+             << "`. Only simple classes are currently supported as base "
+                "class.";
     }
     }
+
+    base_class = cast<NominalClassType>(base_type);
+    if (base_class.value()->declaration().extensibility() ==
+        ClassExtensibility::None) {
+      return ProgramError(class_decl->source_loc())
+             << "Base class `" << base_class.value()->declaration().name()
+             << "` is `final` and cannot inherited. Add the `base` or "
+                "`abstract` class prefix to `"
+             << base_class.value()->declaration().name()
+             << "` to allow it to be inherited";
+    }
+    class_decl->set_base_type(base_class);
   }
   }
 
 
   std::vector<Nonnull<const GenericBinding*>> bindings = scope_info.bindings;
   std::vector<Nonnull<const GenericBinding*>> bindings = scope_info.bindings;
@@ -5599,17 +5600,18 @@ auto TypeChecker::DeclareDeclaration(Nonnull<Declaration*> d,
 }
 }
 
 
 auto TypeChecker::FindMemberWithParents(
 auto TypeChecker::FindMemberWithParents(
-    std::string_view name, Nonnull<const NominalClassType*> class_type)
+    std::string_view name, Nonnull<const NominalClassType*> enclosing_class)
     -> ErrorOr<std::optional<
     -> ErrorOr<std::optional<
-        std::pair<Nonnull<const Value*>, Nonnull<const Declaration*>>>> {
+        std::tuple<Nonnull<const Value*>, Nonnull<const Declaration*>,
+                   Nonnull<const NominalClassType*>>>> {
   CARBON_ASSIGN_OR_RETURN(
   CARBON_ASSIGN_OR_RETURN(
       const auto res,
       const auto res,
-      FindMixedMemberAndType(name, class_type->declaration().members(),
-                             class_type));
+      FindMixedMemberAndType(name, enclosing_class->declaration().members(),
+                             enclosing_class));
   if (res.has_value()) {
   if (res.has_value()) {
-    return res;
+    return {std::make_tuple(res->first, res->second, enclosing_class)};
   }
   }
-  if (const auto base = class_type->base(); base.has_value()) {
+  if (const auto base = enclosing_class->base(); base.has_value()) {
     return FindMemberWithParents(name, base.value());
     return FindMemberWithParents(name, base.value());
   }
   }
   return {std::nullopt};
   return {std::nullopt};

+ 9 - 4
explorer/interpreter/type_checker.h

@@ -60,12 +60,13 @@ class TypeChecker {
                  SourceLocation source_loc) const
                  SourceLocation source_loc) const
       -> std::optional<Nonnull<const Witness*>>;
       -> std::optional<Nonnull<const Witness*>>;
 
 
-  // Return the declaration of the member with the given name, from the class
-  // and its parents
+  // Return the declaration of the member with the given name and the class type
+  // that owns it, from the class and its parents
   auto FindMemberWithParents(std::string_view name,
   auto FindMemberWithParents(std::string_view name,
-                             Nonnull<const NominalClassType*> enclosing_type)
+                             Nonnull<const NominalClassType*> enclosing_class)
       -> ErrorOr<std::optional<
       -> ErrorOr<std::optional<
-          std::pair<Nonnull<const Value*>, Nonnull<const Declaration*>>>>;
+          std::tuple<Nonnull<const Value*>, Nonnull<const Declaration*>,
+                     Nonnull<const NominalClassType*>>>>;
 
 
   // Finds the direct or indirect member of a class or mixin by its name and
   // Finds the direct or indirect member of a class or mixin by its name and
   // returns the member's declaration and type. Indirect members are members of
   // returns the member's declaration and type. Indirect members are members of
@@ -345,6 +346,10 @@ class TypeChecker {
   auto FieldTypes(const NominalClassType& class_type) const
   auto FieldTypes(const NominalClassType& class_type) const
       -> std::vector<NamedValue>;
       -> std::vector<NamedValue>;
 
 
+  // Returns the field names and types of the class and its parents.
+  auto FieldTypesWithBase(const NominalClassType& class_type) const
+      -> std::vector<NamedValue>;
+
   // Returns true if source_fields and destination_fields contain the same set
   // Returns true if source_fields and destination_fields contain the same set
   // of names, and each value in source_fields is implicitly convertible to
   // of names, and each value in source_fields is implicitly convertible to
   // the corresponding value in destination_fields. All values in both arguments
   // the corresponding value in destination_fields. All values in both arguments

+ 38 - 7
explorer/interpreter/value.cpp

@@ -5,8 +5,11 @@
 #include "explorer/interpreter/value.h"
 #include "explorer/interpreter/value.h"
 
 
 #include <algorithm>
 #include <algorithm>
+#include <optional>
+#include <string_view>
 
 
 #include "common/check.h"
 #include "common/check.h"
+#include "common/error.h"
 #include "explorer/ast/declaration.h"
 #include "explorer/ast/declaration.h"
 #include "explorer/common/arena.h"
 #include "explorer/common/arena.h"
 #include "explorer/common/error_builders.h"
 #include "explorer/common/error_builders.h"
@@ -33,6 +36,18 @@ auto StructValue::FindField(std::string_view name) const
   return std::nullopt;
   return std::nullopt;
 }
 }
 
 
+static auto FindClassField(Nonnull<const NominalClassValue*> object,
+                           std::string_view name)
+    -> std::optional<Nonnull<const Value*>> {
+  if (auto field = cast<StructValue>(object->inits()).FindField(name)) {
+    return field;
+  }
+  if (object->base().has_value()) {
+    return FindClassField(object->base().value(), name);
+  }
+  return std::nullopt;
+}
+
 static auto GetMember(Nonnull<Arena*> arena, Nonnull<const Value*> v,
 static auto GetMember(Nonnull<Arena*> arena, Nonnull<const Value*> v,
                       const FieldPath::Component& field,
                       const FieldPath::Component& field,
                       SourceLocation source_loc, Nonnull<const Value*> me_value)
                       SourceLocation source_loc, Nonnull<const Value*> me_value)
@@ -89,7 +104,7 @@ static auto GetMember(Nonnull<Arena*> arena, Nonnull<const Value*> v,
       const auto& object = cast<NominalClassValue>(*v);
       const auto& object = cast<NominalClassValue>(*v);
       // Look for a field.
       // Look for a field.
       if (std::optional<Nonnull<const Value*>> field =
       if (std::optional<Nonnull<const Value*>> field =
-              cast<StructValue>(object.inits()).FindField(f)) {
+              FindClassField(&object, f)) {
         return *field;
         return *field;
       } else {
       } else {
         // Look for a method in the object's class
         // Look for a method in the object's class
@@ -176,10 +191,23 @@ static auto SetFieldImpl(
     }
     }
     case Value::Kind::NominalClassValue: {
     case Value::Kind::NominalClassValue: {
       const auto& object = cast<NominalClassValue>(*value);
       const auto& object = cast<NominalClassValue>(*value);
-      CARBON_ASSIGN_OR_RETURN(Nonnull<const Value*> inits,
-                              SetFieldImpl(arena, &object.inits(), path_begin,
-                                           path_end, field_value, source_loc));
-      return arena->New<NominalClassValue>(&object.type(), inits);
+      if (auto inits = SetFieldImpl(arena, &object.inits(), path_begin,
+                                    path_end, field_value, source_loc);
+          inits.ok()) {
+        return arena->New<NominalClassValue>(&object.type(), *inits,
+                                             object.base());
+      } else if (object.base().has_value()) {
+        auto new_base = SetFieldImpl(arena, object.base().value(), path_begin,
+                                     path_end, field_value, source_loc);
+        if (new_base.ok()) {
+          return arena->New<NominalClassValue>(
+              &object.type(), &object.inits(),
+              cast<NominalClassValue>(*new_base));
+        }
+      }
+      // Failed to match, show full object content
+      return ProgramError(source_loc)
+             << "field " << (*path_begin).name() << " not in " << *value;
     }
     }
     case Value::Kind::TupleType:
     case Value::Kind::TupleType:
     case Value::Kind::TupleValue: {
     case Value::Kind::TupleValue: {
@@ -271,6 +299,9 @@ void Value::Print(llvm::raw_ostream& out) const {
     case Value::Kind::NominalClassValue: {
     case Value::Kind::NominalClassValue: {
       const auto& s = cast<NominalClassValue>(*this);
       const auto& s = cast<NominalClassValue>(*this);
       out << cast<NominalClassType>(s.type()).declaration().name() << s.inits();
       out << cast<NominalClassType>(s.type()).declaration().name() << s.inits();
+      if (s.base().has_value()) {
+        out << " base " << *s.base().value();
+      }
       break;
       break;
     }
     }
     case Value::Kind::TupleType:
     case Value::Kind::TupleType:
@@ -1066,8 +1097,8 @@ auto FindFunctionWithParents(std::string_view name,
   if (auto fun = FindFunction(name, class_decl.members()); fun.has_value()) {
   if (auto fun = FindFunction(name, class_decl.members()); fun.has_value()) {
     return fun;
     return fun;
   }
   }
-  if (class_decl.base().has_value()) {
-    return FindFunctionWithParents(name, *class_decl.base().value());
+  if (const auto base_type = class_decl.base_type(); base_type.has_value()) {
+    return FindFunctionWithParents(name, base_type.value()->declaration());
   }
   }
   return std::nullopt;
   return std::nullopt;
 }
 }

+ 13 - 3
explorer/interpreter/value.h

@@ -326,8 +326,14 @@ class StructValue : public Value {
 // A value of a nominal class type, i.e., an object.
 // A value of a nominal class type, i.e., an object.
 class NominalClassValue : public Value {
 class NominalClassValue : public Value {
  public:
  public:
-  NominalClassValue(Nonnull<const Value*> type, Nonnull<const Value*> inits)
-      : Value(Kind::NominalClassValue), type_(type), inits_(inits) {}
+  static constexpr llvm::StringLiteral BaseField{"base"};
+
+  NominalClassValue(Nonnull<const Value*> type, Nonnull<const Value*> inits,
+                    std::optional<Nonnull<const NominalClassValue*>> base)
+      : Value(Kind::NominalClassValue),
+        type_(type),
+        inits_(inits),
+        base_(base) {}
 
 
   static auto classof(const Value* value) -> bool {
   static auto classof(const Value* value) -> bool {
     return value->kind() == Kind::NominalClassValue;
     return value->kind() == Kind::NominalClassValue;
@@ -335,10 +341,14 @@ class NominalClassValue : public Value {
 
 
   auto type() const -> const Value& { return *type_; }
   auto type() const -> const Value& { return *type_; }
   auto inits() const -> const Value& { return *inits_; }
   auto inits() const -> const Value& { return *inits_; }
+  auto base() const -> std::optional<Nonnull<const NominalClassValue*>> {
+    return base_;
+  }
 
 
  private:
  private:
   Nonnull<const Value*> type_;
   Nonnull<const Value*> type_;
   Nonnull<const Value*> inits_;  // The initializing StructValue.
   Nonnull<const Value*> inits_;  // The initializing StructValue.
+  std::optional<Nonnull<const NominalClassValue*>> base_;
 };
 };
 
 
 // An alternative constructor value.
 // An alternative constructor value.
@@ -649,7 +659,7 @@ class NominalClassType : public Value {
   explicit NominalClassType(
   explicit NominalClassType(
       Nonnull<const ClassDeclaration*> declaration,
       Nonnull<const ClassDeclaration*> declaration,
       Nonnull<const Bindings*> bindings,
       Nonnull<const Bindings*> bindings,
-      std::optional<Nonnull<const NominalClassType*>> base = std::nullopt)
+      std::optional<Nonnull<const NominalClassType*>> base)
       : Value(Kind::NominalClassType),
       : Value(Kind::NominalClassType),
         declaration_(declaration),
         declaration_(declaration),
         bindings_(bindings),
         bindings_(bindings),

+ 3 - 1
explorer/syntax/parser.ypp

@@ -724,7 +724,9 @@ if_expression:
 expression:
 expression:
   if_expression
   if_expression
 ;
 ;
-designator: PERIOD identifier { $$ = $2; }
+designator:
+  PERIOD identifier { $$ = $2; }
+| PERIOD BASE { $$ = "base"; }
 ;
 ;
 paren_expression: paren_expression_base
 paren_expression: paren_expression_base
     { $$ = ExpressionFromParenContents(arena, context.source_loc(), $1); }
     { $$ = ExpressionFromParenContents(arena, context.source_loc(), $1); }

+ 33 - 0
explorer/testdata/class/base_constructor.carbon

@@ -0,0 +1,33 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// RUN: %{explorer-run}
+// RUN: %{explorer-run-trace}
+// CHECK:STDOUT: 1
+// CHECK:STDOUT: 2
+// CHECK:STDOUT: result: 0
+
+package ExplorerTest api;
+
+base class A {
+  fn Create() -> Self {
+    return {.value_a = 1};
+  }
+  var value_a: i32;
+}
+
+class B extends A {
+  fn Create() -> Self {
+    return {.base = A.Create(), .value_b = 2};
+  }
+  var value_b: i32;
+}
+
+fn Main() -> i32 {
+  var b: B = B.Create();
+  Print("{0}", b.value_a);
+  Print("{0}", b.value_b);
+  return 0;
+}

+ 1 - 1
explorer/testdata/class/class_inheritance_function_call.carbon

@@ -33,7 +33,7 @@ fn Main() -> i32 {
   // Note: D.FunctionC(0); is not accessible
   // Note: D.FunctionC(0); is not accessible
 
 
   // Calling function from class object
   // Calling function from class object
-  var d: D = {};
+  var d: D = {.base={}};
   d.FunctionC(1);
   d.FunctionC(1);
   d.FunctionD(2);
   d.FunctionD(2);
   return 0;
   return 0;

+ 1 - 1
explorer/testdata/class/class_inheritance_methods.carbon

@@ -31,7 +31,7 @@ class D extends C {
 }
 }
 
 
 fn Main() -> i32 {
 fn Main() -> i32 {
-  var d: D = {.value_c = 1, .value_d = 2};
+  var d: D = {.base = {.value_c = 1}, .value_d = 2};
   d.Method1();
   d.Method1();
   d.Method2();
   d.Method2();
   return 0;
   return 0;

+ 13 - 4
explorer/testdata/class/class_inheritance_multiple.carbon

@@ -5,29 +5,38 @@
 // AUTOUPDATE
 // AUTOUPDATE
 // RUN: %{explorer-run}
 // RUN: %{explorer-run}
 // RUN: %{explorer-run-trace}
 // RUN: %{explorer-run-trace}
+// CHECK:STDOUT: A
+// CHECK:STDOUT: c.a: 1
+// CHECK:STDOUT: c.b: 2
+// CHECK:STDOUT: c.c: 3
 // CHECK:STDOUT: result: 0
 // CHECK:STDOUT: result: 0
 
 
 package ExplorerTest api;
 package ExplorerTest api;
 
 
 base class A {
 base class A {
   fn FunctionA() {}
   fn FunctionA() {}
-  var var_a: i32;
+  var a: i32;
+  var aa: String;
 }
 }
 
 
 base class B extends A {
 base class B extends A {
   fn FunctionB() {}
   fn FunctionB() {}
-  var var_b: i32;
+  var b: i32;
 }
 }
 
 
 class C extends B {
 class C extends B {
   fn FunctionC() {}
   fn FunctionC() {}
-  var var_c: i32;
+  var c: i32;
 }
 }
 
 
 fn Main() -> i32 {
 fn Main() -> i32 {
-  var c: C = {.var_a=1, .var_b=2, .var_c=3};
+  var c: C = {.base={.base={.aa="A", .a=1}, .b=2, }, .c=3};
   c.FunctionA();
   c.FunctionA();
   c.FunctionB();
   c.FunctionB();
   c.FunctionC();
   c.FunctionC();
+  Print(c.aa);
+  Print("c.a: {0}", c.a);
+  Print("c.b: {0}", c.b);
+  Print("c.c: {0}", c.c);
   return 0;
   return 0;
 }
 }

+ 1 - 1
explorer/testdata/class/class_inheritance_shadow.carbon

@@ -31,7 +31,7 @@ class D extends C {
 
 
 fn Main() -> i32 {
 fn Main() -> i32 {
   // Initialize derived value first, base value second
   // Initialize derived value first, base value second
-  var d: D = {.value = 2, .value = 1};
+  var d: D = {.value = 2, .base={.value = 1}};
   d.Method1();
   d.Method1();
 
 
   Print("d.value = {0}", d.value);
   Print("d.value = {0}", d.value);

+ 1 - 1
explorer/testdata/class/class_inheritance_variables.carbon

@@ -23,7 +23,7 @@ class D extends C {
 
 
 fn Main() -> i32 {
 fn Main() -> i32 {
   // Initialization
   // Initialization
-  var d: D = {.var_c= 1, .var_d= 2};
+  var d: D = {.base = {.var_c= 1}, .var_d= 2};
 
 
   // Read
   // Read
   Print("Read var_c={0}", d.var_c);
   Print("Read var_c={0}", d.var_c);

+ 23 - 0
explorer/testdata/class/fail_direct_base_class_init.carbon

@@ -0,0 +1,23 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// RUN: %{not} %{explorer-run}
+// RUN: %{not} %{explorer-run-trace}
+
+package ExplorerTest api;
+
+base class A {
+  var a: i32;
+}
+
+class B extends A {
+  var b: i32;
+}
+
+fn Main() -> i32 {
+  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_direct_base_class_init.carbon:[[@LINE+1]]: type error in name binding: '{.a: i32, .b: i32}' is not implicitly convertible to 'class B'
+  var b: B = {.a=0, .b=1};
+  return 0;
+}

+ 24 - 0
explorer/testdata/class/fail_field_access_mismatch_base.carbon

@@ -0,0 +1,24 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// RUN: %{not} %{explorer-run}
+// RUN: %{not} %{explorer-run-trace}
+
+package ExplorerTest api;
+
+base class Point {
+  var x: i32;
+  var y: i32;
+}
+
+class Point3D extends Point {
+  var z: i32;
+}
+
+fn Main() -> i32 {
+  var p: Point3D = {.base={.x = 1, .y = 2}, .z = 3};
+  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_field_access_mismatch_base.carbon:[[@LINE+1]]: class Point3D does not have a field named a
+  return p.a;
+}

+ 25 - 0
explorer/testdata/class/fail_field_assign_mismatch_base.carbon

@@ -0,0 +1,25 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// RUN: %{not} %{explorer-run}
+// RUN: %{not} %{explorer-run-trace}
+
+package ExplorerTest api;
+
+base class Point {
+  var x: i32;
+  var y: i32;
+}
+
+class Point3D extends Point {
+  var z: i32;
+}
+
+fn Main() -> i32 {
+  var p: Point3D = {.base={.x = 1, .y = 2}, .z = 3};
+  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_field_assign_mismatch_base.carbon:[[@LINE+1]]: class Point3D does not have a field named a
+  p.a = 0;
+  return 0;
+}

+ 37 - 0
explorer/testdata/class/parametrized_base_class.carbon

@@ -0,0 +1,37 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// RUN: %{explorer-run}
+// RUN: %{explorer-run-trace}
+// CHECK:STDOUT: 2
+// CHECK:STDOUT: 1
+// CHECK:STDOUT: result: 0
+
+package ExplorerTest api;
+
+interface Number {
+  fn Zero() -> Self;
+  fn Add[me: Self](other: Self) -> Self;
+}
+
+external impl i32 as Number {
+  fn Zero() -> i32 { return 0; }
+  fn Add[me: i32](other: i32) -> i32 { return me + other; }
+}
+
+base class A(T:! Number) {
+  var value_a: T;
+}
+
+class B extends A(i32) {
+  var value_b: i32;
+}
+
+fn Main() -> i32 {
+  var b: B = {.base = {.value_a = 1}, .value_b = 2};
+  Print("{0}", b.value_b);
+  Print("{0}", b.value_a);
+  return 0;
+}