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

Support for initialization of classes with abstract base classes. (#6802)

When initializing `.base` in class initialization, use `partial Base` as
the destination type rather than `Base`. Treat `partial Base` as not
being abstract even when `Base` is.

Allow conversion from a `partial T` initializer to a `T` initializer.
Store the vptr while performing the conversion. Do not store the vptr
when performing a `partial T` initialization, only when performing a
non-partial `T` initialization.
Richard Smith 2 месяцев назад
Родитель
Сommit
41dd256d56

+ 234 - 48
toolchain/check/convert.cpp

@@ -21,6 +21,7 @@
 #include "toolchain/check/impl_lookup.h"
 #include "toolchain/check/impl_lookup.h"
 #include "toolchain/check/import_ref.h"
 #include "toolchain/check/import_ref.h"
 #include "toolchain/check/inst.h"
 #include "toolchain/check/inst.h"
+#include "toolchain/check/member_access.h"
 #include "toolchain/check/operator.h"
 #include "toolchain/check/operator.h"
 #include "toolchain/check/pattern_match.h"
 #include "toolchain/check/pattern_match.h"
 #include "toolchain/check/type.h"
 #include "toolchain/check/type.h"
@@ -33,6 +34,7 @@
 #include "toolchain/sem_ir/generic.h"
 #include "toolchain/sem_ir/generic.h"
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/inst.h"
 #include "toolchain/sem_ir/inst.h"
+#include "toolchain/sem_ir/inst_kind.h"
 #include "toolchain/sem_ir/type.h"
 #include "toolchain/sem_ir/type.h"
 #include "toolchain/sem_ir/type_info.h"
 #include "toolchain/sem_ir/type_info.h"
 #include "toolchain/sem_ir/typed_insts.h"
 #include "toolchain/sem_ir/typed_insts.h"
@@ -206,8 +208,7 @@ static auto ConvertAggregateElement(
     llvm::ArrayRef<SemIR::InstId> src_literal_elems,
     llvm::ArrayRef<SemIR::InstId> src_literal_elems,
     ConversionTarget::Kind kind, SemIR::InstId target_id,
     ConversionTarget::Kind kind, SemIR::InstId target_id,
     SemIR::TypeInstId target_elem_type_inst, PendingBlock* target_block,
     SemIR::TypeInstId target_elem_type_inst, PendingBlock* target_block,
-    size_t src_field_index, size_t target_field_index,
-    SemIR::ClassType* vtable_class_type = nullptr) -> SemIR::InstId {
+    size_t src_field_index, size_t target_field_index) -> SemIR::InstId {
   auto src_elem_type =
   auto src_elem_type =
       context.types().GetTypeIdForTypeInstId(src_elem_type_inst);
       context.types().GetTypeIdForTypeInstId(src_elem_type_inst);
   auto target_elem_type =
   auto target_elem_type =
@@ -235,7 +236,7 @@ static auto ConvertAggregateElement(
   target.storage_id = MakeElementAccessInst<TargetAccessInstT>(
   target.storage_id = MakeElementAccessInst<TargetAccessInstT>(
       context, loc_id, target_id, target_elem_type, *target_block,
       context, loc_id, target_id, target_elem_type, *target_block,
       target_field_index);
       target_field_index);
-  return Convert(context, loc_id, src_elem_id, target, vtable_class_type);
+  return Convert(context, loc_id, src_elem_id, target);
 }
 }
 
 
 // Performs a conversion from a tuple to an array type. This function only
 // Performs a conversion from a tuple to an array type. This function only
@@ -473,6 +474,136 @@ static auto ConvertTupleToType(Context& context, SemIR::LocId loc_id,
   return context.types().GetTypeInstId(tuple_type_id);
   return context.types().GetTypeInstId(tuple_type_id);
 }
 }
 
 
+// Create a reference to the vtable pointer for a class. Returns None if the
+// class has no vptr.
+static auto CreateVtablePtrRef(Context& context, SemIR::LocId loc_id,
+                               SemIR::ClassType vtable_class_type)
+    -> SemIR::InstId {
+  auto vtable_decl_id =
+      context.classes().Get(vtable_class_type.class_id).vtable_decl_id;
+  if (!vtable_decl_id.has_value()) {
+    return SemIR::InstId::None;
+  }
+
+  LoadImportRef(context, vtable_decl_id);
+  auto canonical_vtable_decl_id =
+      context.constant_values().GetConstantInstId(vtable_decl_id);
+  return AddInst<SemIR::VtablePtr>(
+      context, loc_id,
+      {.type_id = GetPointerType(context, SemIR::VtableType::TypeInstId),
+       .vtable_id = context.insts()
+                        .GetAs<SemIR::VtableDecl>(canonical_vtable_decl_id)
+                        .vtable_id,
+       .specific_id = vtable_class_type.specific_id});
+}
+
+// Returns whether the given expression performs in-place initialization (or is
+// invalid).
+static auto IsInPlaceInitializing(Context& context, SemIR::InstId result_id) {
+  auto category = SemIR::GetExprCategory(context.sem_ir(), result_id);
+  return category == SemIR::ExprCategory::InPlaceInitializing ||
+         (category == SemIR::ExprCategory::ReprInitializing &&
+          SemIR::InitRepr::ForType(context.sem_ir(),
+                                   context.insts().Get(result_id).type_id())
+                  .kind == SemIR::InitRepr::InPlace) ||
+         category == SemIR::ExprCategory::Error;
+}
+
+// Returns the index of the vptr field in the given struct type fields, or
+// None if there is no vptr field.
+static auto GetVptrFieldIndex(llvm::ArrayRef<SemIR::StructTypeField> fields)
+    -> SemIR::ElementIndex {
+  // If the type introduces a vptr, it will always be the first field.
+  bool has_vptr =
+      !fields.empty() && fields.front().name_id == SemIR::NameId::Vptr;
+  return has_vptr ? SemIR::ElementIndex(0) : SemIR::ElementIndex::None;
+}
+
+// Builds a member access expression naming the vptr field of the given class
+// object. This is analogous to what `PerformMemberAccess` for `NameId::Vptr`
+// would return if the vptr could be found by name lookup.
+static auto PerformVptrAccess(Context& context, SemIR::LocId loc_id,
+                              SemIR::InstId class_ref_id) -> SemIR::InstId {
+  auto class_type_id = context.insts().Get(class_ref_id).type_id();
+  while (class_ref_id.has_value()) {
+    // The type of `ref_id` must be a class type.
+    if (class_type_id == SemIR::ErrorInst::TypeId) {
+      return SemIR::ErrorInst::InstId;
+    }
+    auto class_type = context.types().GetAs<SemIR::ClassType>(class_type_id);
+    auto& class_info = context.classes().Get(class_type.class_id);
+
+    // Get the object representation.
+    auto object_repr_id =
+        class_info.GetObjectRepr(context.sem_ir(), class_type.specific_id);
+    if (object_repr_id == SemIR::ErrorInst::TypeId) {
+      return SemIR::ErrorInst::InstId;
+    }
+    if (context.types().Is<SemIR::CustomLayoutType>(object_repr_id)) {
+      context.TODO(loc_id, "accessing vptr of custom layout class");
+      return SemIR::ErrorInst::InstId;
+    }
+
+    // Check to see if this class introduces the vptr.
+    auto repr_struct_type =
+        context.types().GetAs<SemIR::StructType>(object_repr_id);
+    auto repr_fields =
+        context.struct_type_fields().Get(repr_struct_type.fields_id);
+    if (auto vptr_field_index = GetVptrFieldIndex(repr_fields);
+        vptr_field_index.has_value()) {
+      return AddInst<SemIR::ClassElementAccess>(
+          context, loc_id,
+          {.type_id = context.types().GetTypeIdForTypeInstId(
+               repr_fields[vptr_field_index.index].type_inst_id),
+           .base_id = class_ref_id,
+           .index = vptr_field_index});
+    }
+
+    // Otherwise, step through to the base class and try again.
+    CARBON_CHECK(class_info.base_id.has_value(),
+                 "Could not find vptr for dynamic class");
+    auto base_decl = context.insts().GetAs<SemIR::BaseDecl>(class_info.base_id);
+    class_type_id = context.types().GetTypeIdForTypeInstId(
+        repr_fields[base_decl.index.index].type_inst_id);
+    class_ref_id =
+        AddInst<SemIR::ClassElementAccess>(context, loc_id,
+                                           {.type_id = class_type_id,
+                                            .base_id = class_ref_id,
+                                            .index = base_decl.index});
+  }
+  return class_ref_id;
+}
+
+// Converts an initializer for a type `partial T` to an initializer for `T` by
+// initializing the vptr if necessary.
+static auto ConvertPartialInitializerToNonPartial(
+    Context& context, ConversionTarget target,
+    SemIR::ClassType vtable_class_type, SemIR::InstId result_id)
+    -> SemIR::InstId {
+  auto loc_id = SemIR::LocId(result_id);
+  auto vptr_id = CreateVtablePtrRef(context, loc_id, vtable_class_type);
+  if (!vptr_id.has_value()) {
+    // No vtable pointer in this class, nothing to do.
+    return result_id;
+  }
+
+  CARBON_CHECK(
+      IsInPlaceInitializing(context, result_id),
+      "Type with vptr should have in-place initializing representation");
+
+  target.storage_access_block->InsertHere();
+  auto dest_id = PerformVptrAccess(context, loc_id, target.storage_id);
+  auto vptr_init_id = AddInst<SemIR::InPlaceInit>(
+      context, loc_id,
+      {.type_id = context.insts().Get(dest_id).type_id(),
+       .src_id = vptr_id,
+       .dest_id = dest_id});
+  return AddInst<SemIR::UpdateInit>(context, loc_id,
+                                    {.type_id = target.type_id,
+                                     .base_init_id = result_id,
+                                     .update_init_id = vptr_init_id});
+}
+
 // Common implementation for ConvertStructToStruct and ConvertStructToClass.
 // Common implementation for ConvertStructToStruct and ConvertStructToClass.
 template <typename TargetAccessInstT>
 template <typename TargetAccessInstT>
 static auto ConvertStructToStructOrClass(
 static auto ConvertStructToStructOrClass(
@@ -487,10 +618,9 @@ static auto ConvertStructToStructOrClass(
   auto& sem_ir = context.sem_ir();
   auto& sem_ir = context.sem_ir();
   auto src_elem_fields = sem_ir.struct_type_fields().Get(src_type.fields_id);
   auto src_elem_fields = sem_ir.struct_type_fields().Get(src_type.fields_id);
   auto dest_elem_fields = sem_ir.struct_type_fields().Get(dest_type.fields_id);
   auto dest_elem_fields = sem_ir.struct_type_fields().Get(dest_type.fields_id);
-  bool dest_has_vptr = !dest_elem_fields.empty() &&
-                       dest_elem_fields.front().name_id == SemIR::NameId::Vptr;
-  int dest_vptr_offset = (dest_has_vptr ? 1 : 0);
-  auto dest_elem_fields_size = dest_elem_fields.size() - dest_vptr_offset;
+  auto dest_vptr_index = GetVptrFieldIndex(dest_elem_fields);
+  auto dest_elem_fields_size =
+      dest_elem_fields.size() - (dest_vptr_index.has_value() ? 1 : 0);
 
 
   auto value = sem_ir.insts().Get(value_id);
   auto value = sem_ir.insts().Get(value_id);
   SemIR::LocId value_loc_id(value_id);
   SemIR::LocId value_loc_id(value_id);
@@ -541,7 +671,7 @@ static auto ConvertStructToStructOrClass(
   // of the source.
   // of the source.
   // TODO: Annotate diagnostics coming from here with the element index.
   // TODO: Annotate diagnostics coming from here with the element index.
   auto new_block =
   auto new_block =
-      literal_elems_id.has_value() && !dest_has_vptr
+      literal_elems_id.has_value() && !dest_vptr_index.has_value()
           ? SemIR::CopyOnWriteInstBlock(&sem_ir, literal_elems_id)
           ? SemIR::CopyOnWriteInstBlock(&sem_ir, literal_elems_id)
           : SemIR::CopyOnWriteInstBlock(
           : SemIR::CopyOnWriteInstBlock(
                 &sem_ir, SemIR::CopyOnWriteInstBlock::UninitializedBlock{
                 &sem_ir, SemIR::CopyOnWriteInstBlock::UninitializedBlock{
@@ -559,18 +689,20 @@ static auto ConvertStructToStructOrClass(
                                              {.type_id = vptr_type_id,
                                              {.type_id = vptr_type_id,
                                               .base_id = target.storage_id,
                                               .base_id = target.storage_id,
                                               .index = SemIR::ElementIndex(i)});
                                               .index = SemIR::ElementIndex(i)});
-      auto vtable_decl_id =
-          context.classes().Get(vtable_class_type->class_id).vtable_decl_id;
-      LoadImportRef(context, vtable_decl_id);
-      auto canonical_vtable_decl_id =
-          context.constant_values().GetConstantInstId(vtable_decl_id);
-      auto vtable_ptr_id = AddInst<SemIR::VtablePtr>(
-          context, value_loc_id,
-          {.type_id = GetPointerType(context, SemIR::VtableType::TypeInstId),
-           .vtable_id = context.insts()
-                            .GetAs<SemIR::VtableDecl>(canonical_vtable_decl_id)
-                            .vtable_id,
-           .specific_id = vtable_class_type->specific_id});
+      auto vtable_ptr_id = SemIR::InstId::None;
+      if (vtable_class_type) {
+        vtable_ptr_id =
+            CreateVtablePtrRef(context, value_loc_id, *vtable_class_type);
+        // Track that we initialized the vptr so we don't do it again.
+        vtable_class_type = nullptr;
+      } else {
+        // For a partial class type, we leave the vtable pointer uninitialized.
+        // TODO: Consider storing a specified value such as null for hardening.
+        vtable_ptr_id = AddInst<SemIR::UninitializedValue>(
+            context, value_loc_id,
+            {.type_id =
+                 GetPointerType(context, SemIR::VtableType::TypeInstId)});
+      }
       auto init_id = AddInst<SemIR::InPlaceInit>(context, value_loc_id,
       auto init_id = AddInst<SemIR::InPlaceInit>(context, value_loc_id,
                                                  {.type_id = vptr_type_id,
                                                  {.type_id = vptr_type_id,
                                                   .src_id = vtable_ptr_id,
                                                   .src_id = vtable_ptr_id,
@@ -609,18 +741,48 @@ static auto ConvertStructToStructOrClass(
     }
     }
     auto src_field = src_elem_fields[src_field_index];
     auto src_field = src_elem_fields[src_field_index];
 
 
+    // When initializing the `.base` field of a class, the destination type is
+    // `partial Base`, not `Base`.
+    // TODO: Skip this if the source field is an initializing expression of the
+    // non-partial type in order to produce smaller IR.
+    auto dest_field_type_inst_id = dest_field.type_inst_id;
+    if (dest_field.name_id == SemIR::NameId::Base) {
+      auto partial_type_id = GetQualifiedType(
+          context,
+          context.types().GetTypeIdForTypeInstId(dest_field.type_inst_id),
+          SemIR::TypeQualifiers::Partial);
+      dest_field_type_inst_id = context.types().GetTypeInstId(partial_type_id);
+    }
+
     // TODO: This call recurses back into conversion. Switch to an iterative
     // TODO: This call recurses back into conversion. Switch to an iterative
     // approach.
     // approach.
+    auto dest_field_index = src_field_index;
+    if (dest_vptr_index.has_value() &&
+        static_cast<int32_t>(src_field_index) >= dest_vptr_index.index) {
+      dest_field_index += 1;
+    }
     auto init_id =
     auto init_id =
         ConvertAggregateElement<SemIR::StructAccess, TargetAccessInstT>(
         ConvertAggregateElement<SemIR::StructAccess, TargetAccessInstT>(
             context, value_loc_id, value_id, src_field.type_inst_id,
             context, value_loc_id, value_id, src_field.type_inst_id,
             literal_elems, inner_kind, target.storage_id,
             literal_elems, inner_kind, target.storage_id,
-            dest_field.type_inst_id, target.storage_access_block,
-            src_field_index, src_field_index + dest_vptr_offset,
-            vtable_class_type);
+            dest_field_type_inst_id, target.storage_access_block,
+            src_field_index, dest_field_index);
     if (init_id == SemIR::ErrorInst::InstId) {
     if (init_id == SemIR::ErrorInst::InstId) {
       return SemIR::ErrorInst::InstId;
       return SemIR::ErrorInst::InstId;
     }
     }
+
+    // When initializing the base, adjust the type of the initializer from
+    // `partial Base` to `Base`. This isn't strictly correct, since we haven't
+    // finished initializing a `Base` until we store to the vptr, but is better
+    // than having an inconsistent type for the struct field initializer.
+    if (dest_field_type_inst_id != dest_field.type_inst_id) {
+      init_id = AddInst<SemIR::AsCompatible>(
+          context, value_loc_id,
+          {.type_id =
+               context.types().GetTypeIdForTypeInstId(dest_field.type_inst_id),
+           .source_id = init_id});
+    }
+
     new_block.Set(i, init_id);
     new_block.Set(i, init_id);
   }
   }
 
 
@@ -629,10 +791,15 @@ static auto ConvertStructToStructOrClass(
     target.storage_access_block->InsertHere();
     target.storage_access_block->InsertHere();
     CARBON_CHECK(is_init,
     CARBON_CHECK(is_init,
                  "Converting directly to a class value is not supported");
                  "Converting directly to a class value is not supported");
-    return AddInst<SemIR::ClassInit>(context, value_loc_id,
-                                     {.type_id = target.type_id,
-                                      .elements_id = new_block.id(),
-                                      .dest_id = target.storage_id});
+    auto result_id = AddInst<SemIR::ClassInit>(context, value_loc_id,
+                                               {.type_id = target.type_id,
+                                                .elements_id = new_block.id(),
+                                                .dest_id = target.storage_id});
+    if (vtable_class_type) {
+      result_id = ConvertPartialInitializerToNonPartial(
+          context, target, *vtable_class_type, result_id);
+    }
+    return result_id;
   } else if (is_init) {
   } else if (is_init) {
     target.storage_access_block->InsertHere();
     target.storage_access_block->InsertHere();
     return AddInst<SemIR::StructInit>(context, value_loc_id,
     return AddInst<SemIR::StructInit>(context, value_loc_id,
@@ -664,11 +831,11 @@ static auto ConvertStructToClass(Context& context, SemIR::StructType src_type,
                                  SemIR::ClassType dest_type,
                                  SemIR::ClassType dest_type,
                                  SemIR::InstId value_id,
                                  SemIR::InstId value_id,
                                  ConversionTarget target,
                                  ConversionTarget target,
-                                 SemIR::ClassType* vtable_class_type)
-    -> SemIR::InstId {
+                                 bool is_partial = false) -> SemIR::InstId {
   PendingBlock target_block(&context);
   PendingBlock target_block(&context);
   auto& dest_class_info = context.classes().Get(dest_type.class_id);
   auto& dest_class_info = context.classes().Get(dest_type.class_id);
-  CARBON_CHECK(dest_class_info.inheritance_kind != SemIR::Class::Abstract);
+  CARBON_CHECK(is_partial ||
+               dest_class_info.inheritance_kind != SemIR::Class::Abstract);
   auto object_repr_id =
   auto object_repr_id =
       dest_class_info.GetObjectRepr(context.sem_ir(), dest_type.specific_id);
       dest_class_info.GetObjectRepr(context.sem_ir(), dest_type.specific_id);
   if (object_repr_id == SemIR::ErrorInst::TypeId) {
   if (object_repr_id == SemIR::ErrorInst::TypeId) {
@@ -693,7 +860,7 @@ static auto ConvertStructToClass(Context& context, SemIR::StructType src_type,
 
 
   auto result_id = ConvertStructToStructOrClass<SemIR::ClassElementAccess>(
   auto result_id = ConvertStructToStructOrClass<SemIR::ClassElementAccess>(
       context, src_type, dest_struct_type, value_id, target,
       context, src_type, dest_struct_type, value_id, target,
-      vtable_class_type ? vtable_class_type : &dest_type);
+      is_partial ? nullptr : &dest_type);
 
 
   if (need_temporary) {
   if (need_temporary) {
     target_block.InsertHere();
     target_block.InsertHere();
@@ -894,11 +1061,11 @@ static auto CanRemoveQualifiers(SemIR::TypeQualifiers quals,
     return false;
     return false;
   }
   }
 
 
-  if (quals.HasAnyOf(SemIR::TypeQualifiers::Partial) &&
-      (!allow_unsafe || SemIR::IsInitializerCategory(cat))) {
-    // TODO: Allow removing `partial` for initializing expressions as a safe
-    // conversion. `PerformBuiltinConversion` will need to initialize the vptr
-    // as part of the conversion.
+  if (quals.HasAnyOf(SemIR::TypeQualifiers::Partial) && !allow_unsafe &&
+      !SemIR::IsInitializerCategory(cat)) {
+    // Removing `partial` is an unsafe conversion for a non-initializing
+    // expression. But it's OK for an initializing expression because we will
+    // initialize the vptr as part of the conversion.
     return false;
     return false;
   }
   }
 
 
@@ -939,10 +1106,9 @@ static auto DiagnoseConversionFailureToConstraintValue(
   }
   }
 }
 }
 
 
-static auto PerformBuiltinConversion(
-    Context& context, SemIR::LocId loc_id, SemIR::InstId value_id,
-    ConversionTarget target, SemIR::ClassType* vtable_class_type = nullptr)
-    -> SemIR::InstId {
+static auto PerformBuiltinConversion(Context& context, SemIR::LocId loc_id,
+                                     SemIR::InstId value_id,
+                                     ConversionTarget target) -> SemIR::InstId {
   auto& sem_ir = context.sem_ir();
   auto& sem_ir = context.sem_ir();
   auto value = sem_ir.insts().Get(value_id);
   auto value = sem_ir.insts().Get(value_id);
   auto value_type_id = value.type_id();
   auto value_type_id = value.type_id();
@@ -1112,6 +1278,19 @@ static auto PerformBuiltinConversion(
           }
           }
         }
         }
 
 
+        if ((removed_quals & SemIR::TypeQualifiers::Partial) !=
+                SemIR::TypeQualifiers::None &&
+            SemIR::IsInitializerCategory(category)) {
+          auto unqual_target_type_id =
+              context.types().GetUnqualifiedType(target.type_id);
+          if (auto target_class_type =
+                  context.types().TryGetAs<SemIR::ClassType>(
+                      unqual_target_type_id)) {
+            value_id = ConvertPartialInitializerToNonPartial(
+                context, target, *target_class_type, value_id);
+          }
+        }
+
         value_id = AddInst<SemIR::AsCompatible>(
         value_id = AddInst<SemIR::AsCompatible>(
             context, loc_id,
             context, loc_id,
             {.type_id = target.type_id, .source_id = value_id});
             {.type_id = target.type_id, .source_id = value_id});
@@ -1167,19 +1346,28 @@ static auto PerformBuiltinConversion(
     }
     }
   }
   }
 
 
+  // Split the qualifiers off the target type.
+  // TODO: Most conversions should probably be looking at the unqualified target
+  // type.
+  auto [target_unqual_type_id, target_quals] =
+      context.types().GetUnqualifiedTypeAndQualifiers(target.type_id);
+  auto target_unqual_type_inst =
+      sem_ir.types().GetAsInst(target_unqual_type_id);
+
   // A struct {.f_1: T_1, .f_2: T_2, ..., .f_n: T_n} converts to a class type
   // A struct {.f_1: T_1, .f_2: T_2, ..., .f_n: T_n} converts to a class type
   // if it converts to the struct type that is the class's representation type
   // if it converts to the struct type that is the class's representation type
   // (a struct with the same fields as the class, plus a base field where
   // (a struct with the same fields as the class, plus a base field where
   // relevant).
   // relevant).
-  if (auto target_class_type = target_type_inst.TryAs<SemIR::ClassType>()) {
+  if (auto target_class_type =
+          target_unqual_type_inst.TryAs<SemIR::ClassType>()) {
     if (auto src_struct_type =
     if (auto src_struct_type =
             sem_ir.types().TryGetAs<SemIR::StructType>(value_type_id)) {
             sem_ir.types().TryGetAs<SemIR::StructType>(value_type_id)) {
       if (!context.classes()
       if (!context.classes()
                .Get(target_class_type->class_id)
                .Get(target_class_type->class_id)
                .adapt_id.has_value()) {
                .adapt_id.has_value()) {
-        return ConvertStructToClass(context, *src_struct_type,
-                                    *target_class_type, value_id, target,
-                                    vtable_class_type);
+        return ConvertStructToClass(
+            context, *src_struct_type, *target_class_type, value_id, target,
+            target_quals.HasAnyOf(SemIR::TypeQualifiers::Partial));
       }
       }
     }
     }
 
 
@@ -1697,8 +1885,7 @@ auto CategoryConverter::DoStep(const SemIR::InstId expr_id,
 }
 }
 
 
 auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
 auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
-             ConversionTarget target, SemIR::ClassType* vtable_class_type)
-    -> SemIR::InstId {
+             ConversionTarget target) -> SemIR::InstId {
   auto& sem_ir = context.sem_ir();
   auto& sem_ir = context.sem_ir();
   auto orig_expr_id = expr_id;
   auto orig_expr_id = expr_id;
 
 
@@ -1798,8 +1985,7 @@ auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
   TryToCompleteType(context, context.insts().Get(expr_id).type_id(), loc_id);
   TryToCompleteType(context, context.insts().Get(expr_id).type_id(), loc_id);
 
 
   // Check whether any builtin conversion applies.
   // Check whether any builtin conversion applies.
-  expr_id = PerformBuiltinConversion(context, loc_id, expr_id, target,
-                                     vtable_class_type);
+  expr_id = PerformBuiltinConversion(context, loc_id, expr_id, target);
   if (expr_id == SemIR::ErrorInst::InstId) {
   if (expr_id == SemIR::ErrorInst::InstId) {
     return expr_id;
     return expr_id;
   }
   }

+ 1 - 7
toolchain/check/convert.h

@@ -85,14 +85,8 @@ struct ConversionTarget {
 };
 };
 
 
 // Convert a value to another type and expression category.
 // Convert a value to another type and expression category.
-// TODO: The `vtable_id` parameter is too much of a special case here, and
-// should be removed - once partial classes are implemented, the vtable pointer
-// initialization will be done not in this conversion, but during initialization
-// of the object of non-partial class type from the object of partial class
-// type.
 auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
 auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
-             ConversionTarget target,
-             SemIR::ClassType* vtable_class_type = nullptr) -> SemIR::InstId;
+             ConversionTarget target) -> SemIR::InstId;
 
 
 // Converts `value_id` to an initializing expression of the type of
 // Converts `value_id` to an initializing expression of the type of
 // `storage_id`, and returns the possibly-converted initializing expression.
 // `storage_id`, and returns the possibly-converted initializing expression.

+ 7 - 0
toolchain/check/eval_inst.cpp

@@ -773,6 +773,13 @@ auto EvalConstantInst(Context& context, SemIR::UnaryOperatorNot inst)
   return ConstantEvalResult::NotConstant;
   return ConstantEvalResult::NotConstant;
 }
 }
 
 
+auto EvalConstantInst(Context& /*context*/, SemIR::UpdateInit /*inst*/)
+    -> ConstantEvalResult {
+  // TODO: Support folding together a ClassInit with an update that sets the
+  // vptr.
+  return ConstantEvalResult::TODO;
+}
+
 auto EvalConstantInst(Context& context, SemIR::ValueOfInitializer inst)
 auto EvalConstantInst(Context& context, SemIR::ValueOfInitializer inst)
     -> ConstantEvalResult {
     -> ConstantEvalResult {
   // Values of value expressions and initializing expressions are represented in
   // Values of value expressions and initializing expressions are represented in

+ 31 - 50
toolchain/check/testdata/as/partial.carbon

@@ -31,7 +31,7 @@ fn Use() {
   //@dump-sem-ir-end
   //@dump-sem-ir-end
 }
 }
 
 
-// --- fail_todo_remove_partial_in_init.carbon
+// --- remove_partial_in_init.carbon
 
 
 library "[[@TEST_NAME]]";
 library "[[@TEST_NAME]]";
 
 
@@ -41,30 +41,8 @@ fn Init() -> partial X;
 
 
 fn Use() {
 fn Use() {
   //@dump-sem-ir-begin
   //@dump-sem-ir-begin
-  // TODO: This should be valid, and should initialize the vptr. The explicit `as` should probably not be necessary.
-  // CHECK:STDERR: fail_todo_remove_partial_in_init.carbon:[[@LINE+7]]:3: error: cannot implicitly convert expression of type `partial X` to `X` [ConversionFailure]
-  // CHECK:STDERR:   var unused i: X = Init();
-  // CHECK:STDERR:   ^~~~~~~~~~~~~~~
-  // CHECK:STDERR: fail_todo_remove_partial_in_init.carbon:[[@LINE+4]]:3: note: type `partial X` does not implement interface `Core.ImplicitAs(X)` [MissingImplInMemberAccessInContext]
-  // CHECK:STDERR:   var unused i: X = Init();
-  // CHECK:STDERR:   ^~~~~~~~~~~~~~~
-  // CHECK:STDERR:
   var unused i: X = Init();
   var unused i: X = Init();
-  // CHECK:STDERR: fail_todo_remove_partial_in_init.carbon:[[@LINE+7]]:21: error: cannot convert expression of type `partial X` to `X` with `as` [ConversionFailure]
-  // CHECK:STDERR:   var unused j: X = Init() as X;
-  // CHECK:STDERR:                     ^~~~~~~~~~~
-  // CHECK:STDERR: fail_todo_remove_partial_in_init.carbon:[[@LINE+4]]:21: note: type `partial X` does not implement interface `Core.As(X)` [MissingImplInMemberAccessInContext]
-  // CHECK:STDERR:   var unused j: X = Init() as X;
-  // CHECK:STDERR:                     ^~~~~~~~~~~
-  // CHECK:STDERR:
   var unused j: X = Init() as X;
   var unused j: X = Init() as X;
-  // CHECK:STDERR: fail_todo_remove_partial_in_init.carbon:[[@LINE+7]]:21: error: cannot convert expression of type `partial X` to `X` with `unsafe as` [ConversionFailure]
-  // CHECK:STDERR:   var unused k: X = Init() unsafe as X;
-  // CHECK:STDERR:                     ^~~~~~~~~~~~~~~~~~
-  // CHECK:STDERR: fail_todo_remove_partial_in_init.carbon:[[@LINE+4]]:21: note: type `partial X` does not implement interface `Core.UnsafeAs(X)` [MissingImplInMemberAccessInContext]
-  // CHECK:STDERR:   var unused k: X = Init() unsafe as X;
-  // CHECK:STDERR:                     ^~~~~~~~~~~~~~~~~~
-  // CHECK:STDERR:
   var unused k: X = Init() unsafe as X;
   var unused k: X = Init() unsafe as X;
   //@dump-sem-ir-end
   //@dump-sem-ir-end
 }
 }
@@ -221,7 +199,7 @@ fn Use() {
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_todo_remove_partial_in_init.carbon
+// CHECK:STDOUT: --- remove_partial_in_init.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %X: type = class_type @X [concrete]
 // CHECK:STDOUT:   %X: type = class_type @X [concrete]
@@ -241,45 +219,48 @@ fn Use() {
 // CHECK:STDOUT:     %i.var_patt: %pattern_type.05f = var_pattern %i.patt [concrete]
 // CHECK:STDOUT:     %i.var_patt: %pattern_type.05f = var_pattern %i.patt [concrete]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %i.var: ref %X = var %i.var_patt
 // CHECK:STDOUT:   %i.var: ref %X = var %i.var_patt
-// CHECK:STDOUT:   %Init.ref.loc18: %Init.type = name_ref Init, file.%Init.decl [concrete = constants.%Init]
-// CHECK:STDOUT:   %.loc18_26: ref %.4b5 = temporary_storage
-// CHECK:STDOUT:   %Init.call.loc18: init %.4b5 to %.loc18_26 = call %Init.ref.loc18()
-// CHECK:STDOUT:   %.loc18_3: %X = converted %Init.call.loc18, <error> [concrete = <error>]
-// CHECK:STDOUT:   assign %i.var, <error>
-// CHECK:STDOUT:   %X.ref.loc18: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:   %Init.ref.loc10: %Init.type = name_ref Init, file.%Init.decl [concrete = constants.%Init]
+// CHECK:STDOUT:   %.loc10_3.1: ref %X = splice_block %i.var {}
+// CHECK:STDOUT:   %Init.call.loc10: init %.4b5 to %.loc10_3.1 = call %Init.ref.loc10()
+// CHECK:STDOUT:   %.loc10_3.2: init %X = as_compatible %Init.call.loc10
+// CHECK:STDOUT:   %.loc10_3.3: init %X = converted %Init.call.loc10, %.loc10_3.2
+// CHECK:STDOUT:   assign %i.var, %.loc10_3.3
+// CHECK:STDOUT:   %X.ref.loc10: type = name_ref X, file.%X.decl [concrete = constants.%X]
 // CHECK:STDOUT:   %i: ref %X = ref_binding i, %i.var
 // CHECK:STDOUT:   %i: ref %X = ref_binding i, %i.var
 // CHECK:STDOUT:   name_binding_decl {
 // CHECK:STDOUT:   name_binding_decl {
 // CHECK:STDOUT:     %j.patt: %pattern_type.05f = ref_binding_pattern j [concrete]
 // CHECK:STDOUT:     %j.patt: %pattern_type.05f = ref_binding_pattern j [concrete]
 // CHECK:STDOUT:     %j.var_patt: %pattern_type.05f = var_pattern %j.patt [concrete]
 // CHECK:STDOUT:     %j.var_patt: %pattern_type.05f = var_pattern %j.patt [concrete]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %j.var: ref %X = var %j.var_patt
 // CHECK:STDOUT:   %j.var: ref %X = var %j.var_patt
-// CHECK:STDOUT:   %Init.ref.loc26: %Init.type = name_ref Init, file.%Init.decl [concrete = constants.%Init]
-// CHECK:STDOUT:   %.loc26_26: ref %.4b5 = temporary_storage
-// CHECK:STDOUT:   %Init.call.loc26: init %.4b5 to %.loc26_26 = call %Init.ref.loc26()
-// CHECK:STDOUT:   %X.ref.loc26_31: type = name_ref X, file.%X.decl [concrete = constants.%X]
-// CHECK:STDOUT:   %.loc26_28: %X = converted %Init.call.loc26, <error> [concrete = <error>]
-// CHECK:STDOUT:   assign %j.var, <error>
-// CHECK:STDOUT:   %X.ref.loc26_17: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:   %Init.ref.loc11: %Init.type = name_ref Init, file.%Init.decl [concrete = constants.%Init]
+// CHECK:STDOUT:   %.loc11_3: ref %X = splice_block %j.var {}
+// CHECK:STDOUT:   %Init.call.loc11: init %.4b5 to %.loc11_3 = call %Init.ref.loc11()
+// CHECK:STDOUT:   %X.ref.loc11_31: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:   %.loc11_28.1: init %X = as_compatible %Init.call.loc11
+// CHECK:STDOUT:   %.loc11_28.2: init %X = converted %Init.call.loc11, %.loc11_28.1
+// CHECK:STDOUT:   assign %j.var, %.loc11_28.2
+// CHECK:STDOUT:   %X.ref.loc11_17: type = name_ref X, file.%X.decl [concrete = constants.%X]
 // CHECK:STDOUT:   %j: ref %X = ref_binding j, %j.var
 // CHECK:STDOUT:   %j: ref %X = ref_binding j, %j.var
 // CHECK:STDOUT:   name_binding_decl {
 // CHECK:STDOUT:   name_binding_decl {
 // CHECK:STDOUT:     %k.patt: %pattern_type.05f = ref_binding_pattern k [concrete]
 // CHECK:STDOUT:     %k.patt: %pattern_type.05f = ref_binding_pattern k [concrete]
 // CHECK:STDOUT:     %k.var_patt: %pattern_type.05f = var_pattern %k.patt [concrete]
 // CHECK:STDOUT:     %k.var_patt: %pattern_type.05f = var_pattern %k.patt [concrete]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %k.var: ref %X = var %k.var_patt
 // CHECK:STDOUT:   %k.var: ref %X = var %k.var_patt
-// CHECK:STDOUT:   %Init.ref.loc34: %Init.type = name_ref Init, file.%Init.decl [concrete = constants.%Init]
-// CHECK:STDOUT:   %.loc34_26: ref %.4b5 = temporary_storage
-// CHECK:STDOUT:   %Init.call.loc34: init %.4b5 to %.loc34_26 = call %Init.ref.loc34()
-// CHECK:STDOUT:   %X.ref.loc34_38: type = name_ref X, file.%X.decl [concrete = constants.%X]
-// CHECK:STDOUT:   %.loc34_35: %X = converted %Init.call.loc34, <error> [concrete = <error>]
-// CHECK:STDOUT:   assign %k.var, <error>
-// CHECK:STDOUT:   %X.ref.loc34_17: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:   %Init.ref.loc12: %Init.type = name_ref Init, file.%Init.decl [concrete = constants.%Init]
+// CHECK:STDOUT:   %.loc12_3: ref %X = splice_block %k.var {}
+// CHECK:STDOUT:   %Init.call.loc12: init %.4b5 to %.loc12_3 = call %Init.ref.loc12()
+// CHECK:STDOUT:   %X.ref.loc12_38: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:   %.loc12_35.1: init %X = as_compatible %Init.call.loc12
+// CHECK:STDOUT:   %.loc12_35.2: init %X = converted %Init.call.loc12, %.loc12_35.1
+// CHECK:STDOUT:   assign %k.var, %.loc12_35.2
+// CHECK:STDOUT:   %X.ref.loc12_17: type = name_ref X, file.%X.decl [concrete = constants.%X]
 // CHECK:STDOUT:   %k: ref %X = ref_binding k, %k.var
 // CHECK:STDOUT:   %k: ref %X = ref_binding k, %k.var
-// CHECK:STDOUT:   %DestroyOp.bound.loc34: <bound method> = bound_method %k.var, constants.%DestroyOp
-// CHECK:STDOUT:   %DestroyOp.call.loc34: init %empty_tuple.type = call %DestroyOp.bound.loc34(%k.var)
-// CHECK:STDOUT:   %DestroyOp.bound.loc26: <bound method> = bound_method %j.var, constants.%DestroyOp
-// CHECK:STDOUT:   %DestroyOp.call.loc26: init %empty_tuple.type = call %DestroyOp.bound.loc26(%j.var)
-// CHECK:STDOUT:   %DestroyOp.bound.loc18: <bound method> = bound_method %i.var, constants.%DestroyOp
-// CHECK:STDOUT:   %DestroyOp.call.loc18: init %empty_tuple.type = call %DestroyOp.bound.loc18(%i.var)
+// CHECK:STDOUT:   %DestroyOp.bound.loc12: <bound method> = bound_method %k.var, constants.%DestroyOp
+// CHECK:STDOUT:   %DestroyOp.call.loc12: init %empty_tuple.type = call %DestroyOp.bound.loc12(%k.var)
+// CHECK:STDOUT:   %DestroyOp.bound.loc11: <bound method> = bound_method %j.var, constants.%DestroyOp
+// CHECK:STDOUT:   %DestroyOp.call.loc11: init %empty_tuple.type = call %DestroyOp.bound.loc11(%j.var)
+// CHECK:STDOUT:   %DestroyOp.bound.loc10: <bound method> = bound_method %i.var, constants.%DestroyOp
+// CHECK:STDOUT:   %DestroyOp.call.loc10: init %empty_tuple.type = call %DestroyOp.bound.loc10(%i.var)
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:

+ 43 - 36
toolchain/check/testdata/class/abstract/fail_abstract.carbon → toolchain/check/testdata/class/abstract/abstract.carbon

@@ -8,9 +8,9 @@
 //
 //
 // AUTOUPDATE
 // AUTOUPDATE
 // TIP: To test this file alone, run:
 // TIP: To test this file alone, run:
-// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/class/abstract/fail_abstract.carbon
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/class/abstract/abstract.carbon
 // TIP: To dump output, run:
 // TIP: To dump output, run:
-// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/class/abstract/fail_abstract.carbon
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/class/abstract/abstract.carbon
 
 
 // --- fail_abstract_field.carbon
 // --- fail_abstract_field.carbon
 library "[[@TEST_NAME]]";
 library "[[@TEST_NAME]]";
@@ -119,7 +119,7 @@ fn Call(p: Abstract) {
   Param(p);
   Param(p);
 }
 }
 
 
-// --- fail_todo_return_nonabstract_derived.carbon
+// --- return_nonabstract_derived.carbon
 library "[[@TEST_NAME]]";
 library "[[@TEST_NAME]]";
 
 
 abstract class Abstract {
 abstract class Abstract {
@@ -132,14 +132,6 @@ class Derived {
 }
 }
 
 
 fn Make() -> Derived {
 fn Make() -> Derived {
-  // TODO: This should be valid, and should construct an instance of `partial Abstract` as the base.
-  // CHECK:STDERR: fail_todo_return_nonabstract_derived.carbon:[[@LINE+7]]:10: error: initialization of abstract type `Abstract` [AbstractTypeInInit]
-  // CHECK:STDERR:   return {.base = {}, .d = {}};
-  // CHECK:STDERR:          ^~~~~~~~~~~~~~~~~~~~~
-  // CHECK:STDERR: fail_todo_return_nonabstract_derived.carbon:[[@LINE-14]]:1: note: class was declared abstract here [ClassAbstractHere]
-  // CHECK:STDERR: abstract class Abstract {
-  // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~
-  // CHECK:STDERR:
   return {.base = {}, .d = {}};
   return {.base = {}, .d = {}};
 }
 }
 
 
@@ -222,17 +214,10 @@ fn F() {
   // to the `Abstract` value since `Abstract` and `Derived` have pointer value
   // to the `Abstract` value since `Abstract` and `Derived` have pointer value
   // representations.
   // representations.
   //
   //
-  // CHECK:STDERR: fail_todo_abstract_let_temporary.carbon:[[@LINE+14]]:28: error: initialization of abstract type `Abstract` [AbstractTypeInInit]
-  // CHECK:STDERR:   let unused l: Abstract = {.base = {}} as Derived;
-  // CHECK:STDERR:                            ^~~~~~~~~~~~
-  // CHECK:STDERR: fail_todo_abstract_let_temporary.carbon:[[@LINE-15]]:1: note: class was declared abstract here [ClassAbstractHere]
-  // CHECK:STDERR: abstract class Abstract {
-  // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~
-  // CHECK:STDERR:
   // CHECK:STDERR: fail_todo_abstract_let_temporary.carbon:[[@LINE+7]]:28: error: initialization of abstract type `Abstract` [AbstractTypeInInit]
   // CHECK:STDERR: fail_todo_abstract_let_temporary.carbon:[[@LINE+7]]:28: error: initialization of abstract type `Abstract` [AbstractTypeInInit]
   // CHECK:STDERR:   let unused l: Abstract = {.base = {}} as Derived;
   // CHECK:STDERR:   let unused l: Abstract = {.base = {}} as Derived;
   // CHECK:STDERR:                            ^~~~~~~~~~~~~~~~~~~~~~~
   // CHECK:STDERR:                            ^~~~~~~~~~~~~~~~~~~~~~~
-  // CHECK:STDERR: fail_todo_abstract_let_temporary.carbon:[[@LINE-22]]:1: note: class was declared abstract here [ClassAbstractHere]
+  // CHECK:STDERR: fail_todo_abstract_let_temporary.carbon:[[@LINE-15]]:1: note: class was declared abstract here [ClassAbstractHere]
   // CHECK:STDERR: abstract class Abstract {
   // CHECK:STDERR: abstract class Abstract {
   // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~
   // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~
   // CHECK:STDERR:
   // CHECK:STDERR:
@@ -575,7 +560,7 @@ fn CallReturnAbstract() {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_todo_return_nonabstract_derived.carbon
+// CHECK:STDOUT: --- return_nonabstract_derived.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %Abstract: type = class_type @Abstract [concrete]
 // CHECK:STDOUT:   %Abstract: type = class_type @Abstract [concrete]
@@ -583,7 +568,7 @@ fn CallReturnAbstract() {
 // CHECK:STDOUT:   %complete_type.357: <witness> = complete_type_witness %empty_struct_type [concrete]
 // CHECK:STDOUT:   %complete_type.357: <witness> = complete_type_witness %empty_struct_type [concrete]
 // CHECK:STDOUT:   %Derived: type = class_type @Derived [concrete]
 // CHECK:STDOUT:   %Derived: type = class_type @Derived [concrete]
 // CHECK:STDOUT:   %Derived.elem.032: type = unbound_element_type %Derived, %Abstract [concrete]
 // CHECK:STDOUT:   %Derived.elem.032: type = unbound_element_type %Derived, %Abstract [concrete]
-// CHECK:STDOUT:   %empty_struct: %empty_struct_type = struct_value () [concrete]
+// CHECK:STDOUT:   %empty_struct.a40: %empty_struct_type = struct_value () [concrete]
 // CHECK:STDOUT:   %Derived.elem.87e: type = unbound_element_type %Derived, %empty_struct_type [concrete]
 // CHECK:STDOUT:   %Derived.elem.87e: type = unbound_element_type %Derived, %empty_struct_type [concrete]
 // CHECK:STDOUT:   %struct_type.base.d.be5: type = struct_type {.base: %Abstract, .d: %empty_struct_type} [concrete]
 // CHECK:STDOUT:   %struct_type.base.d.be5: type = struct_type {.base: %Abstract, .d: %empty_struct_type} [concrete]
 // CHECK:STDOUT:   %complete_type.840: <witness> = complete_type_witness %struct_type.base.d.be5 [concrete]
 // CHECK:STDOUT:   %complete_type.840: <witness> = complete_type_witness %struct_type.base.d.be5 [concrete]
@@ -592,7 +577,11 @@ fn CallReturnAbstract() {
 // CHECK:STDOUT:   %Make.type: type = fn_type @Make [concrete]
 // CHECK:STDOUT:   %Make.type: type = fn_type @Make [concrete]
 // CHECK:STDOUT:   %Make: %Make.type = struct_value () [concrete]
 // CHECK:STDOUT:   %Make: %Make.type = struct_value () [concrete]
 // CHECK:STDOUT:   %struct_type.base.d.e0f: type = struct_type {.base: %empty_struct_type, .d: %empty_struct_type} [concrete]
 // CHECK:STDOUT:   %struct_type.base.d.e0f: type = struct_type {.base: %empty_struct_type, .d: %empty_struct_type} [concrete]
-// CHECK:STDOUT:   %struct: %struct_type.base.d.e0f = struct_value (%empty_struct, %empty_struct) [concrete]
+// CHECK:STDOUT:   %struct: %struct_type.base.d.e0f = struct_value (%empty_struct.a40, %empty_struct.a40) [concrete]
+// CHECK:STDOUT:   %.ec6: type = partial_type %Abstract [concrete]
+// CHECK:STDOUT:   %empty_struct.8eb: %.ec6 = struct_value () [concrete]
+// CHECK:STDOUT:   %Abstract.val: %Abstract = struct_value () [concrete]
+// CHECK:STDOUT:   %Derived.val: %Derived = struct_value (%Abstract.val, %empty_struct.a40) [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT: imports {
@@ -634,7 +623,7 @@ fn CallReturnAbstract() {
 // CHECK:STDOUT: class @Derived {
 // CHECK:STDOUT: class @Derived {
 // CHECK:STDOUT:   %Abstract.ref: type = name_ref Abstract, file.%Abstract.decl [concrete = constants.%Abstract]
 // CHECK:STDOUT:   %Abstract.ref: type = name_ref Abstract, file.%Abstract.decl [concrete = constants.%Abstract]
 // CHECK:STDOUT:   %.loc7: %Derived.elem.032 = base_decl %Abstract.ref, element0 [concrete]
 // CHECK:STDOUT:   %.loc7: %Derived.elem.032 = base_decl %Abstract.ref, element0 [concrete]
-// CHECK:STDOUT:   %.loc9_11.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct]
+// CHECK:STDOUT:   %.loc9_11.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct.a40]
 // CHECK:STDOUT:   %.loc9_11.2: type = converted %.loc9_11.1, constants.%empty_struct_type [concrete = constants.%empty_struct_type]
 // CHECK:STDOUT:   %.loc9_11.2: type = converted %.loc9_11.1, constants.%empty_struct_type [concrete = constants.%empty_struct_type]
 // CHECK:STDOUT:   %.loc9_8: %Derived.elem.87e = field_decl d, element1 [concrete]
 // CHECK:STDOUT:   %.loc9_8: %Derived.elem.87e = field_decl d, element1 [concrete]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness constants.%struct_type.base.d.be5 [concrete = constants.%complete_type.840]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness constants.%struct_type.base.d.be5 [concrete = constants.%complete_type.840]
@@ -650,10 +639,19 @@ fn CallReturnAbstract() {
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Make() -> out %return.param: %Derived {
 // CHECK:STDOUT: fn @Make() -> out %return.param: %Derived {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %.loc21_20: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct]
-// CHECK:STDOUT:   %.loc21_29: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct]
-// CHECK:STDOUT:   %.loc21_30: %struct_type.base.d.e0f = struct_literal (%.loc21_20, %.loc21_29) [concrete = constants.%struct]
-// CHECK:STDOUT:   return <error> to %return.param
+// CHECK:STDOUT:   %.loc13_20.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct.a40]
+// CHECK:STDOUT:   %.loc13_29.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct.a40]
+// CHECK:STDOUT:   %.loc13_30.1: %struct_type.base.d.e0f = struct_literal (%.loc13_20.1, %.loc13_29.1) [concrete = constants.%struct]
+// CHECK:STDOUT:   %.loc13_30.2: ref %.ec6 = class_element_access %return.param, element0
+// CHECK:STDOUT:   %.loc13_20.2: init %.ec6 to %.loc13_30.2 = class_init () [concrete = constants.%empty_struct.8eb]
+// CHECK:STDOUT:   %.loc13_30.3: init %.ec6 = converted %.loc13_20.1, %.loc13_20.2 [concrete = constants.%empty_struct.8eb]
+// CHECK:STDOUT:   %.loc13_30.4: init %Abstract = as_compatible %.loc13_30.3 [concrete = constants.%Abstract.val]
+// CHECK:STDOUT:   %.loc13_30.5: ref %empty_struct_type = class_element_access %return.param, element1
+// CHECK:STDOUT:   %.loc13_29.2: init %empty_struct_type = struct_init () [concrete = constants.%empty_struct.a40]
+// CHECK:STDOUT:   %.loc13_30.6: init %empty_struct_type = converted %.loc13_29.1, %.loc13_29.2 [concrete = constants.%empty_struct.a40]
+// CHECK:STDOUT:   %.loc13_30.7: init %Derived to %return.param = class_init (%.loc13_30.4, %.loc13_30.6) [concrete = constants.%Derived.val]
+// CHECK:STDOUT:   %.loc13_31: init %Derived = converted %.loc13_30.1, %.loc13_30.7 [concrete = constants.%Derived.val]
+// CHECK:STDOUT:   return %.loc13_31 to %return.param
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_return_abstract.carbon
 // CHECK:STDOUT: --- fail_return_abstract.carbon
@@ -894,9 +892,13 @@ fn CallReturnAbstract() {
 // CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
 // CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
 // CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
 // CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
 // CHECK:STDOUT:   %pattern_type.a2e: type = pattern_type %Abstract [concrete]
 // CHECK:STDOUT:   %pattern_type.a2e: type = pattern_type %Abstract [concrete]
-// CHECK:STDOUT:   %empty_struct: %empty_struct_type = struct_value () [concrete]
+// CHECK:STDOUT:   %empty_struct.a40: %empty_struct_type = struct_value () [concrete]
 // CHECK:STDOUT:   %struct_type.base.f5e: type = struct_type {.base: %empty_struct_type} [concrete]
 // CHECK:STDOUT:   %struct_type.base.f5e: type = struct_type {.base: %empty_struct_type} [concrete]
-// CHECK:STDOUT:   %struct: %struct_type.base.f5e = struct_value (%empty_struct) [concrete]
+// CHECK:STDOUT:   %struct: %struct_type.base.f5e = struct_value (%empty_struct.a40) [concrete]
+// CHECK:STDOUT:   %.ec6: type = partial_type %Abstract [concrete]
+// CHECK:STDOUT:   %empty_struct.8eb: %.ec6 = struct_value () [concrete]
+// CHECK:STDOUT:   %Abstract.val: %Abstract = struct_value () [concrete]
+// CHECK:STDOUT:   %Derived.val: %Derived = struct_value (%Abstract.val) [concrete]
 // CHECK:STDOUT:   %Destroy.type: type = facet_type <@Destroy> [concrete]
 // CHECK:STDOUT:   %Destroy.type: type = facet_type <@Destroy> [concrete]
 // CHECK:STDOUT:   %DestroyOp.type: type = fn_type @DestroyOp [concrete]
 // CHECK:STDOUT:   %DestroyOp.type: type = fn_type @DestroyOp [concrete]
 // CHECK:STDOUT:   %DestroyOp: %DestroyOp.type = struct_value () [concrete]
 // CHECK:STDOUT:   %DestroyOp: %DestroyOp.type = struct_value () [concrete]
@@ -950,16 +952,21 @@ fn CallReturnAbstract() {
 // CHECK:STDOUT:   name_binding_decl {
 // CHECK:STDOUT:   name_binding_decl {
 // CHECK:STDOUT:     %l.patt: %pattern_type.a2e = value_binding_pattern l [concrete]
 // CHECK:STDOUT:     %l.patt: %pattern_type.a2e = value_binding_pattern l [concrete]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %.loc29_38: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct]
-// CHECK:STDOUT:   %.loc29_39.1: %struct_type.base.f5e = struct_literal (%.loc29_38) [concrete = constants.%struct]
+// CHECK:STDOUT:   %.loc22_38.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct.a40]
+// CHECK:STDOUT:   %.loc22_39.1: %struct_type.base.f5e = struct_literal (%.loc22_38.1) [concrete = constants.%struct]
 // CHECK:STDOUT:   %Derived.ref: type = name_ref Derived, file.%Derived.decl [concrete = constants.%Derived]
 // CHECK:STDOUT:   %Derived.ref: type = name_ref Derived, file.%Derived.decl [concrete = constants.%Derived]
-// CHECK:STDOUT:   %.loc29_39.2: ref %Derived = temporary_storage
-// CHECK:STDOUT:   %.loc29_39.3: ref %Derived = temporary %.loc29_39.2, <error>
-// CHECK:STDOUT:   %.loc29_41: ref %Derived = converted %.loc29_39.1, %.loc29_39.3
+// CHECK:STDOUT:   %.loc22_39.2: ref %Derived = temporary_storage
+// CHECK:STDOUT:   %.loc22_39.3: ref %.ec6 = class_element_access %.loc22_39.2, element0
+// CHECK:STDOUT:   %.loc22_38.2: init %.ec6 to %.loc22_39.3 = class_init () [concrete = constants.%empty_struct.8eb]
+// CHECK:STDOUT:   %.loc22_39.4: init %.ec6 = converted %.loc22_38.1, %.loc22_38.2 [concrete = constants.%empty_struct.8eb]
+// CHECK:STDOUT:   %.loc22_39.5: init %Abstract = as_compatible %.loc22_39.4 [concrete = constants.%Abstract.val]
+// CHECK:STDOUT:   %.loc22_39.6: init %Derived to %.loc22_39.2 = class_init (%.loc22_39.5) [concrete = constants.%Derived.val]
+// CHECK:STDOUT:   %.loc22_39.7: ref %Derived = temporary %.loc22_39.2, %.loc22_39.6
+// CHECK:STDOUT:   %.loc22_41: ref %Derived = converted %.loc22_39.1, %.loc22_39.7
 // CHECK:STDOUT:   %Abstract.ref: type = name_ref Abstract, file.%Abstract.decl [concrete = constants.%Abstract]
 // CHECK:STDOUT:   %Abstract.ref: type = name_ref Abstract, file.%Abstract.decl [concrete = constants.%Abstract]
 // CHECK:STDOUT:   %l: %Abstract = value_binding l, <error> [concrete = <error>]
 // CHECK:STDOUT:   %l: %Abstract = value_binding l, <error> [concrete = <error>]
-// CHECK:STDOUT:   %DestroyOp.bound: <bound method> = bound_method %.loc29_39.3, constants.%DestroyOp
-// CHECK:STDOUT:   %DestroyOp.call: init %empty_tuple.type = call %DestroyOp.bound(%.loc29_39.3)
+// CHECK:STDOUT:   %DestroyOp.bound: <bound method> = bound_method %.loc22_39.7, constants.%DestroyOp
+// CHECK:STDOUT:   %DestroyOp.call: init %empty_tuple.type = call %DestroyOp.bound(%.loc22_39.7)
 // CHECK:STDOUT:   return
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:

+ 11 - 8
toolchain/check/testdata/class/inheritance/base.carbon

@@ -76,6 +76,7 @@ class Derived {
 // CHECK:STDOUT:   %int_7.29f: Core.IntLiteral = int_value 7 [concrete]
 // CHECK:STDOUT:   %int_7.29f: Core.IntLiteral = int_value 7 [concrete]
 // CHECK:STDOUT:   %struct_type.base.d.a20: type = struct_type {.base: %struct_type.b.a15, .d: Core.IntLiteral} [concrete]
 // CHECK:STDOUT:   %struct_type.base.d.a20: type = struct_type {.base: %struct_type.b.a15, .d: Core.IntLiteral} [concrete]
 // CHECK:STDOUT:   %struct.ab7: %struct_type.base.d.a20 = struct_value (%struct.a2e, %int_7.29f) [concrete]
 // CHECK:STDOUT:   %struct.ab7: %struct_type.base.d.a20 = struct_value (%struct.a2e, %int_7.29f) [concrete]
+// CHECK:STDOUT:   %.dfa: type = partial_type %Base [concrete]
 // CHECK:STDOUT:   %ImplicitAs.type.cc7: type = generic_interface_type @ImplicitAs [concrete]
 // CHECK:STDOUT:   %ImplicitAs.type.cc7: type = generic_interface_type @ImplicitAs [concrete]
 // CHECK:STDOUT:   %ImplicitAs.generic: %ImplicitAs.type.cc7 = struct_value () [concrete]
 // CHECK:STDOUT:   %ImplicitAs.generic: %ImplicitAs.type.cc7 = struct_value () [concrete]
 // CHECK:STDOUT:   %ImplicitAs.type.e8c: type = facet_type <@ImplicitAs, @ImplicitAs(%i32)> [concrete]
 // CHECK:STDOUT:   %ImplicitAs.type.e8c: type = facet_type <@ImplicitAs, @ImplicitAs(%i32)> [concrete]
@@ -92,6 +93,7 @@ class Derived {
 // CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn: <specific function> = specific_function %Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5, @Core.IntLiteral.as.ImplicitAs.impl.Convert(%int_32) [concrete]
 // CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn: <specific function> = specific_function %Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5, @Core.IntLiteral.as.ImplicitAs.impl.Convert(%int_32) [concrete]
 // CHECK:STDOUT:   %bound_method.6d7: <bound method> = bound_method %int_4.0c1, %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn [concrete]
 // CHECK:STDOUT:   %bound_method.6d7: <bound method> = bound_method %int_4.0c1, %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn [concrete]
 // CHECK:STDOUT:   %int_4.940: %i32 = int_value 4 [concrete]
 // CHECK:STDOUT:   %int_4.940: %i32 = int_value 4 [concrete]
+// CHECK:STDOUT:   %struct.5f8: %.dfa = struct_value (%int_4.940) [concrete]
 // CHECK:STDOUT:   %Base.val: %Base = struct_value (%int_4.940) [concrete]
 // CHECK:STDOUT:   %Base.val: %Base = struct_value (%int_4.940) [concrete]
 // CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.bound.1e0: <bound method> = bound_method %int_7.29f, %Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5 [concrete]
 // CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.bound.1e0: <bound method> = bound_method %int_7.29f, %Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5 [concrete]
 // CHECK:STDOUT:   %bound_method.bf2: <bound method> = bound_method %int_7.29f, %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn [concrete]
 // CHECK:STDOUT:   %bound_method.bf2: <bound method> = bound_method %int_7.29f, %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn [concrete]
@@ -211,21 +213,22 @@ class Derived {
 // CHECK:STDOUT:   %bound_method.loc14_26.2: <bound method> = bound_method %int_4, %specific_fn.loc14_26 [concrete = constants.%bound_method.6d7]
 // CHECK:STDOUT:   %bound_method.loc14_26.2: <bound method> = bound_method %int_4, %specific_fn.loc14_26 [concrete = constants.%bound_method.6d7]
 // CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc14_26: init %i32 = call %bound_method.loc14_26.2(%int_4) [concrete = constants.%int_4.940]
 // CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc14_26: init %i32 = call %bound_method.loc14_26.2(%int_4) [concrete = constants.%int_4.940]
 // CHECK:STDOUT:   %.loc14_26.2: init %i32 = converted %int_4, %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc14_26 [concrete = constants.%int_4.940]
 // CHECK:STDOUT:   %.loc14_26.2: init %i32 = converted %int_4, %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc14_26 [concrete = constants.%int_4.940]
-// CHECK:STDOUT:   %.loc14_35.2: ref %Base = class_element_access %return.param, element0
+// CHECK:STDOUT:   %.loc14_35.2: ref %.dfa = class_element_access %return.param, element0
 // CHECK:STDOUT:   %.loc14_26.3: ref %i32 = class_element_access %.loc14_35.2, element0
 // CHECK:STDOUT:   %.loc14_26.3: ref %i32 = class_element_access %.loc14_35.2, element0
 // CHECK:STDOUT:   %.loc14_26.4: init %i32 to %.loc14_26.3 = in_place_init %.loc14_26.2 [concrete = constants.%int_4.940]
 // CHECK:STDOUT:   %.loc14_26.4: init %i32 to %.loc14_26.3 = in_place_init %.loc14_26.2 [concrete = constants.%int_4.940]
-// CHECK:STDOUT:   %.loc14_26.5: init %Base to %.loc14_35.2 = class_init (%.loc14_26.4) [concrete = constants.%Base.val]
-// CHECK:STDOUT:   %.loc14_35.3: init %Base = converted %.loc14_26.1, %.loc14_26.5 [concrete = constants.%Base.val]
+// CHECK:STDOUT:   %.loc14_26.5: init %.dfa to %.loc14_35.2 = class_init (%.loc14_26.4) [concrete = constants.%struct.5f8]
+// CHECK:STDOUT:   %.loc14_35.3: init %.dfa = converted %.loc14_26.1, %.loc14_26.5 [concrete = constants.%struct.5f8]
+// CHECK:STDOUT:   %.loc14_35.4: init %Base = as_compatible %.loc14_35.3 [concrete = constants.%Base.val]
 // CHECK:STDOUT:   %impl.elem0.loc14_35: %.545 = impl_witness_access constants.%ImplicitAs.impl_witness.6bc, element0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5]
 // CHECK:STDOUT:   %impl.elem0.loc14_35: %.545 = impl_witness_access constants.%ImplicitAs.impl_witness.6bc, element0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5]
 // CHECK:STDOUT:   %bound_method.loc14_35.1: <bound method> = bound_method %int_7, %impl.elem0.loc14_35 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.bound.1e0]
 // CHECK:STDOUT:   %bound_method.loc14_35.1: <bound method> = bound_method %int_7, %impl.elem0.loc14_35 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.bound.1e0]
 // CHECK:STDOUT:   %specific_fn.loc14_35: <specific function> = specific_function %impl.elem0.loc14_35, @Core.IntLiteral.as.ImplicitAs.impl.Convert(constants.%int_32) [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn]
 // CHECK:STDOUT:   %specific_fn.loc14_35: <specific function> = specific_function %impl.elem0.loc14_35, @Core.IntLiteral.as.ImplicitAs.impl.Convert(constants.%int_32) [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn]
 // CHECK:STDOUT:   %bound_method.loc14_35.2: <bound method> = bound_method %int_7, %specific_fn.loc14_35 [concrete = constants.%bound_method.bf2]
 // CHECK:STDOUT:   %bound_method.loc14_35.2: <bound method> = bound_method %int_7, %specific_fn.loc14_35 [concrete = constants.%bound_method.bf2]
 // CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc14_35: init %i32 = call %bound_method.loc14_35.2(%int_7) [concrete = constants.%int_7.0b1]
 // CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc14_35: init %i32 = call %bound_method.loc14_35.2(%int_7) [concrete = constants.%int_7.0b1]
-// CHECK:STDOUT:   %.loc14_35.4: init %i32 = converted %int_7, %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc14_35 [concrete = constants.%int_7.0b1]
-// CHECK:STDOUT:   %.loc14_35.5: ref %i32 = class_element_access %return.param, element1
-// CHECK:STDOUT:   %.loc14_35.6: init %i32 to %.loc14_35.5 = in_place_init %.loc14_35.4 [concrete = constants.%int_7.0b1]
-// CHECK:STDOUT:   %.loc14_35.7: init %Derived to %return.param = class_init (%.loc14_35.3, %.loc14_35.6) [concrete = constants.%Derived.val]
-// CHECK:STDOUT:   %.loc14_36: init %Derived = converted %.loc14_35.1, %.loc14_35.7 [concrete = constants.%Derived.val]
+// CHECK:STDOUT:   %.loc14_35.5: init %i32 = converted %int_7, %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc14_35 [concrete = constants.%int_7.0b1]
+// CHECK:STDOUT:   %.loc14_35.6: ref %i32 = class_element_access %return.param, element1
+// CHECK:STDOUT:   %.loc14_35.7: init %i32 to %.loc14_35.6 = in_place_init %.loc14_35.5 [concrete = constants.%int_7.0b1]
+// CHECK:STDOUT:   %.loc14_35.8: init %Derived to %return.param = class_init (%.loc14_35.4, %.loc14_35.7) [concrete = constants.%Derived.val]
+// CHECK:STDOUT:   %.loc14_36: init %Derived = converted %.loc14_35.1, %.loc14_35.8 [concrete = constants.%Derived.val]
 // CHECK:STDOUT:   return %.loc14_36 to %return.param
 // CHECK:STDOUT:   return %.loc14_36 to %return.param
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:

+ 23 - 17
toolchain/check/testdata/class/inheritance/derived_to_base.carbon

@@ -174,6 +174,8 @@ fn PassConstB(p: const B) {
 // CHECK:STDOUT:   %int_3.1ba: Core.IntLiteral = int_value 3 [concrete]
 // CHECK:STDOUT:   %int_3.1ba: Core.IntLiteral = int_value 3 [concrete]
 // CHECK:STDOUT:   %struct_type.base.c.136: type = struct_type {.base: %struct_type.base.b.bf0, .c: Core.IntLiteral} [concrete]
 // CHECK:STDOUT:   %struct_type.base.c.136: type = struct_type {.base: %struct_type.base.b.bf0, .c: Core.IntLiteral} [concrete]
 // CHECK:STDOUT:   %struct.2aa: %struct_type.base.c.136 = struct_value (%struct.ff9, %int_3.1ba) [concrete]
 // CHECK:STDOUT:   %struct.2aa: %struct_type.base.c.136 = struct_value (%struct.ff9, %int_3.1ba) [concrete]
+// CHECK:STDOUT:   %.eaa: type = partial_type %B [concrete]
+// CHECK:STDOUT:   %.157: type = partial_type %A [concrete]
 // CHECK:STDOUT:   %ImplicitAs.type.e8c: type = facet_type <@ImplicitAs, @ImplicitAs(%i32)> [concrete]
 // CHECK:STDOUT:   %ImplicitAs.type.e8c: type = facet_type <@ImplicitAs, @ImplicitAs(%i32)> [concrete]
 // CHECK:STDOUT:   %To: Core.IntLiteral = symbolic_binding To, 0 [symbolic]
 // CHECK:STDOUT:   %To: Core.IntLiteral = symbolic_binding To, 0 [symbolic]
 // CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.4e6: type = fn_type @Core.IntLiteral.as.ImplicitAs.impl.Convert, @Core.IntLiteral.as.ImplicitAs.impl(%To) [symbolic]
 // CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.4e6: type = fn_type @Core.IntLiteral.as.ImplicitAs.impl.Convert, @Core.IntLiteral.as.ImplicitAs.impl(%To) [symbolic]
@@ -188,10 +190,12 @@ fn PassConstB(p: const B) {
 // CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn: <specific function> = specific_function %Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5, @Core.IntLiteral.as.ImplicitAs.impl.Convert(%int_32) [concrete]
 // CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn: <specific function> = specific_function %Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5, @Core.IntLiteral.as.ImplicitAs.impl.Convert(%int_32) [concrete]
 // CHECK:STDOUT:   %bound_method.38b: <bound method> = bound_method %int_1.5b8, %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn [concrete]
 // CHECK:STDOUT:   %bound_method.38b: <bound method> = bound_method %int_1.5b8, %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn [concrete]
 // CHECK:STDOUT:   %int_1.5d2: %i32 = int_value 1 [concrete]
 // CHECK:STDOUT:   %int_1.5d2: %i32 = int_value 1 [concrete]
+// CHECK:STDOUT:   %struct.e01: %.157 = struct_value (%int_1.5d2) [concrete]
 // CHECK:STDOUT:   %A.val: %A = struct_value (%int_1.5d2) [concrete]
 // CHECK:STDOUT:   %A.val: %A = struct_value (%int_1.5d2) [concrete]
 // CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.bound.4e5: <bound method> = bound_method %int_2.ecc, %Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5 [concrete]
 // CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.bound.4e5: <bound method> = bound_method %int_2.ecc, %Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5 [concrete]
 // CHECK:STDOUT:   %bound_method.646: <bound method> = bound_method %int_2.ecc, %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn [concrete]
 // CHECK:STDOUT:   %bound_method.646: <bound method> = bound_method %int_2.ecc, %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn [concrete]
 // CHECK:STDOUT:   %int_2.ef8: %i32 = int_value 2 [concrete]
 // CHECK:STDOUT:   %int_2.ef8: %i32 = int_value 2 [concrete]
+// CHECK:STDOUT:   %struct.bc2: %.eaa = struct_value (%A.val, %int_2.ef8) [concrete]
 // CHECK:STDOUT:   %B.val: %B = struct_value (%A.val, %int_2.ef8) [concrete]
 // CHECK:STDOUT:   %B.val: %B = struct_value (%A.val, %int_2.ef8) [concrete]
 // CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.bound.061: <bound method> = bound_method %int_3.1ba, %Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5 [concrete]
 // CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.bound.061: <bound method> = bound_method %int_3.1ba, %Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5 [concrete]
 // CHECK:STDOUT:   %bound_method.fa7: <bound method> = bound_method %int_3.1ba, %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn [concrete]
 // CHECK:STDOUT:   %bound_method.fa7: <bound method> = bound_method %int_3.1ba, %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn [concrete]
@@ -389,41 +393,43 @@ fn PassConstB(p: const B) {
 // CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc32_46: init %i32 = call %bound_method.loc32_46.2(%int_1) [concrete = constants.%int_1.5d2]
 // CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc32_46: init %i32 = call %bound_method.loc32_46.2(%int_1) [concrete = constants.%int_1.5d2]
 // CHECK:STDOUT:   %.loc32_46.2: init %i32 = converted %int_1, %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc32_46 [concrete = constants.%int_1.5d2]
 // CHECK:STDOUT:   %.loc32_46.2: init %i32 = converted %int_1, %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc32_46 [concrete = constants.%int_1.5d2]
 // CHECK:STDOUT:   %.loc32_64.2: ref %C = temporary_storage
 // CHECK:STDOUT:   %.loc32_64.2: ref %C = temporary_storage
-// CHECK:STDOUT:   %.loc32_64.3: ref %B = class_element_access %.loc32_64.2, element0
-// CHECK:STDOUT:   %.loc32_55.2: ref %A = class_element_access %.loc32_64.3, element0
+// CHECK:STDOUT:   %.loc32_64.3: ref %.eaa = class_element_access %.loc32_64.2, element0
+// CHECK:STDOUT:   %.loc32_55.2: ref %.157 = class_element_access %.loc32_64.3, element0
 // CHECK:STDOUT:   %.loc32_46.3: ref %i32 = class_element_access %.loc32_55.2, element0
 // CHECK:STDOUT:   %.loc32_46.3: ref %i32 = class_element_access %.loc32_55.2, element0
 // CHECK:STDOUT:   %.loc32_46.4: init %i32 to %.loc32_46.3 = in_place_init %.loc32_46.2 [concrete = constants.%int_1.5d2]
 // CHECK:STDOUT:   %.loc32_46.4: init %i32 to %.loc32_46.3 = in_place_init %.loc32_46.2 [concrete = constants.%int_1.5d2]
-// CHECK:STDOUT:   %.loc32_46.5: init %A to %.loc32_55.2 = class_init (%.loc32_46.4) [concrete = constants.%A.val]
-// CHECK:STDOUT:   %.loc32_55.3: init %A = converted %.loc32_46.1, %.loc32_46.5 [concrete = constants.%A.val]
+// CHECK:STDOUT:   %.loc32_46.5: init %.157 to %.loc32_55.2 = class_init (%.loc32_46.4) [concrete = constants.%struct.e01]
+// CHECK:STDOUT:   %.loc32_55.3: init %.157 = converted %.loc32_46.1, %.loc32_46.5 [concrete = constants.%struct.e01]
+// CHECK:STDOUT:   %.loc32_55.4: init %A = as_compatible %.loc32_55.3 [concrete = constants.%A.val]
 // CHECK:STDOUT:   %impl.elem0.loc32_55: %.545 = impl_witness_access constants.%ImplicitAs.impl_witness.6bc, element0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5]
 // CHECK:STDOUT:   %impl.elem0.loc32_55: %.545 = impl_witness_access constants.%ImplicitAs.impl_witness.6bc, element0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5]
 // CHECK:STDOUT:   %bound_method.loc32_55.1: <bound method> = bound_method %int_2, %impl.elem0.loc32_55 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.bound.4e5]
 // CHECK:STDOUT:   %bound_method.loc32_55.1: <bound method> = bound_method %int_2, %impl.elem0.loc32_55 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.bound.4e5]
 // CHECK:STDOUT:   %specific_fn.loc32_55: <specific function> = specific_function %impl.elem0.loc32_55, @Core.IntLiteral.as.ImplicitAs.impl.Convert(constants.%int_32) [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn]
 // CHECK:STDOUT:   %specific_fn.loc32_55: <specific function> = specific_function %impl.elem0.loc32_55, @Core.IntLiteral.as.ImplicitAs.impl.Convert(constants.%int_32) [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn]
 // CHECK:STDOUT:   %bound_method.loc32_55.2: <bound method> = bound_method %int_2, %specific_fn.loc32_55 [concrete = constants.%bound_method.646]
 // CHECK:STDOUT:   %bound_method.loc32_55.2: <bound method> = bound_method %int_2, %specific_fn.loc32_55 [concrete = constants.%bound_method.646]
 // CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc32_55: init %i32 = call %bound_method.loc32_55.2(%int_2) [concrete = constants.%int_2.ef8]
 // CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc32_55: init %i32 = call %bound_method.loc32_55.2(%int_2) [concrete = constants.%int_2.ef8]
-// CHECK:STDOUT:   %.loc32_55.4: init %i32 = converted %int_2, %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc32_55 [concrete = constants.%int_2.ef8]
-// CHECK:STDOUT:   %.loc32_55.5: ref %i32 = class_element_access %.loc32_64.3, element1
-// CHECK:STDOUT:   %.loc32_55.6: init %i32 to %.loc32_55.5 = in_place_init %.loc32_55.4 [concrete = constants.%int_2.ef8]
-// CHECK:STDOUT:   %.loc32_55.7: init %B to %.loc32_64.3 = class_init (%.loc32_55.3, %.loc32_55.6) [concrete = constants.%B.val]
-// CHECK:STDOUT:   %.loc32_64.4: init %B = converted %.loc32_55.1, %.loc32_55.7 [concrete = constants.%B.val]
+// CHECK:STDOUT:   %.loc32_55.5: init %i32 = converted %int_2, %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc32_55 [concrete = constants.%int_2.ef8]
+// CHECK:STDOUT:   %.loc32_55.6: ref %i32 = class_element_access %.loc32_64.3, element1
+// CHECK:STDOUT:   %.loc32_55.7: init %i32 to %.loc32_55.6 = in_place_init %.loc32_55.5 [concrete = constants.%int_2.ef8]
+// CHECK:STDOUT:   %.loc32_55.8: init %.eaa to %.loc32_64.3 = class_init (%.loc32_55.4, %.loc32_55.7) [concrete = constants.%struct.bc2]
+// CHECK:STDOUT:   %.loc32_64.4: init %.eaa = converted %.loc32_55.1, %.loc32_55.8 [concrete = constants.%struct.bc2]
+// CHECK:STDOUT:   %.loc32_64.5: init %B = as_compatible %.loc32_64.4 [concrete = constants.%B.val]
 // CHECK:STDOUT:   %impl.elem0.loc32_64: %.545 = impl_witness_access constants.%ImplicitAs.impl_witness.6bc, element0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5]
 // CHECK:STDOUT:   %impl.elem0.loc32_64: %.545 = impl_witness_access constants.%ImplicitAs.impl_witness.6bc, element0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5]
 // CHECK:STDOUT:   %bound_method.loc32_64.1: <bound method> = bound_method %int_3, %impl.elem0.loc32_64 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.bound.061]
 // CHECK:STDOUT:   %bound_method.loc32_64.1: <bound method> = bound_method %int_3, %impl.elem0.loc32_64 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.bound.061]
 // CHECK:STDOUT:   %specific_fn.loc32_64: <specific function> = specific_function %impl.elem0.loc32_64, @Core.IntLiteral.as.ImplicitAs.impl.Convert(constants.%int_32) [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn]
 // CHECK:STDOUT:   %specific_fn.loc32_64: <specific function> = specific_function %impl.elem0.loc32_64, @Core.IntLiteral.as.ImplicitAs.impl.Convert(constants.%int_32) [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn]
 // CHECK:STDOUT:   %bound_method.loc32_64.2: <bound method> = bound_method %int_3, %specific_fn.loc32_64 [concrete = constants.%bound_method.fa7]
 // CHECK:STDOUT:   %bound_method.loc32_64.2: <bound method> = bound_method %int_3, %specific_fn.loc32_64 [concrete = constants.%bound_method.fa7]
 // CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc32_64: init %i32 = call %bound_method.loc32_64.2(%int_3) [concrete = constants.%int_3.822]
 // CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc32_64: init %i32 = call %bound_method.loc32_64.2(%int_3) [concrete = constants.%int_3.822]
-// CHECK:STDOUT:   %.loc32_64.5: init %i32 = converted %int_3, %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc32_64 [concrete = constants.%int_3.822]
-// CHECK:STDOUT:   %.loc32_64.6: ref %i32 = class_element_access %.loc32_64.2, element1
-// CHECK:STDOUT:   %.loc32_64.7: init %i32 to %.loc32_64.6 = in_place_init %.loc32_64.5 [concrete = constants.%int_3.822]
-// CHECK:STDOUT:   %.loc32_64.8: init %C to %.loc32_64.2 = class_init (%.loc32_64.4, %.loc32_64.7) [concrete = constants.%C.val]
-// CHECK:STDOUT:   %.loc32_64.9: ref %C = temporary %.loc32_64.2, %.loc32_64.8
-// CHECK:STDOUT:   %.loc32_66.1: ref %C = converted %.loc32_64.1, %.loc32_64.9
+// CHECK:STDOUT:   %.loc32_64.6: init %i32 = converted %int_3, %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc32_64 [concrete = constants.%int_3.822]
+// CHECK:STDOUT:   %.loc32_64.7: ref %i32 = class_element_access %.loc32_64.2, element1
+// CHECK:STDOUT:   %.loc32_64.8: init %i32 to %.loc32_64.7 = in_place_init %.loc32_64.6 [concrete = constants.%int_3.822]
+// CHECK:STDOUT:   %.loc32_64.9: init %C to %.loc32_64.2 = class_init (%.loc32_64.5, %.loc32_64.8) [concrete = constants.%C.val]
+// CHECK:STDOUT:   %.loc32_64.10: ref %C = temporary %.loc32_64.2, %.loc32_64.9
+// CHECK:STDOUT:   %.loc32_66.1: ref %C = converted %.loc32_64.1, %.loc32_64.10
 // CHECK:STDOUT:   %A.ref: type = name_ref A, file.%A.decl [concrete = constants.%A]
 // CHECK:STDOUT:   %A.ref: type = name_ref A, file.%A.decl [concrete = constants.%A]
 // CHECK:STDOUT:   %.loc32_66.2: ref %B = class_element_access %.loc32_66.1, element0
 // CHECK:STDOUT:   %.loc32_66.2: ref %B = class_element_access %.loc32_66.1, element0
 // CHECK:STDOUT:   %.loc32_66.3: ref %A = class_element_access %.loc32_66.2, element0
 // CHECK:STDOUT:   %.loc32_66.3: ref %A = class_element_access %.loc32_66.2, element0
 // CHECK:STDOUT:   %.loc32_66.4: ref %A = converted %.loc32_66.1, %.loc32_66.3
 // CHECK:STDOUT:   %.loc32_66.4: ref %A = converted %.loc32_66.1, %.loc32_66.3
 // CHECK:STDOUT:   %.loc32_66.5: %A = acquire_value %.loc32_66.4
 // CHECK:STDOUT:   %.loc32_66.5: %A = acquire_value %.loc32_66.4
 // CHECK:STDOUT:   %a: %A = value_binding a, %.loc32_66.5
 // CHECK:STDOUT:   %a: %A = value_binding a, %.loc32_66.5
-// CHECK:STDOUT:   %DestroyOp.bound: <bound method> = bound_method %.loc32_64.9, constants.%DestroyOp
-// CHECK:STDOUT:   %DestroyOp.call: init %empty_tuple.type = call %DestroyOp.bound(%.loc32_64.9)
+// CHECK:STDOUT:   %DestroyOp.bound: <bound method> = bound_method %.loc32_64.10, constants.%DestroyOp
+// CHECK:STDOUT:   %DestroyOp.call: init %empty_tuple.type = call %DestroyOp.bound(%.loc32_64.10)
 // CHECK:STDOUT:   return
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:

+ 8 - 5
toolchain/check/testdata/class/inheritance/import_base.carbon

@@ -152,6 +152,7 @@ fn Run() {
 // CHECK:STDOUT:   %struct.0bf: %struct_type.x.unused_y.76a = struct_value (%int_0.5c6, %int_1.5b8) [concrete]
 // CHECK:STDOUT:   %struct.0bf: %struct_type.x.unused_y.76a = struct_value (%int_0.5c6, %int_1.5b8) [concrete]
 // CHECK:STDOUT:   %struct_type.base.503: type = struct_type {.base: %struct_type.x.unused_y.76a} [concrete]
 // CHECK:STDOUT:   %struct_type.base.503: type = struct_type {.base: %struct_type.x.unused_y.76a} [concrete]
 // CHECK:STDOUT:   %struct.cb5: %struct_type.base.503 = struct_value (%struct.0bf) [concrete]
 // CHECK:STDOUT:   %struct.cb5: %struct_type.base.503 = struct_value (%struct.0bf) [concrete]
+// CHECK:STDOUT:   %.7a5: type = partial_type %Base [concrete]
 // CHECK:STDOUT:   %ImplicitAs.type.cc7: type = generic_interface_type @ImplicitAs [concrete]
 // CHECK:STDOUT:   %ImplicitAs.type.cc7: type = generic_interface_type @ImplicitAs [concrete]
 // CHECK:STDOUT:   %ImplicitAs.generic: %ImplicitAs.type.cc7 = struct_value () [concrete]
 // CHECK:STDOUT:   %ImplicitAs.generic: %ImplicitAs.type.cc7 = struct_value () [concrete]
 // CHECK:STDOUT:   %ImplicitAs.type.cf3: type = facet_type <@ImplicitAs, @ImplicitAs(%i32)> [concrete]
 // CHECK:STDOUT:   %ImplicitAs.type.cf3: type = facet_type <@ImplicitAs, @ImplicitAs(%i32)> [concrete]
@@ -171,6 +172,7 @@ fn Run() {
 // CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.bound.f07: <bound method> = bound_method %int_1.5b8, %Core.IntLiteral.as.ImplicitAs.impl.Convert.5f4 [concrete]
 // CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.bound.f07: <bound method> = bound_method %int_1.5b8, %Core.IntLiteral.as.ImplicitAs.impl.Convert.5f4 [concrete]
 // CHECK:STDOUT:   %bound_method.307: <bound method> = bound_method %int_1.5b8, %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn [concrete]
 // CHECK:STDOUT:   %bound_method.307: <bound method> = bound_method %int_1.5b8, %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn [concrete]
 // CHECK:STDOUT:   %int_1.47b: %i32 = int_value 1 [concrete]
 // CHECK:STDOUT:   %int_1.47b: %i32 = int_value 1 [concrete]
+// CHECK:STDOUT:   %struct.f56: %.7a5 = struct_value (%int_0.263, %int_1.47b) [concrete]
 // CHECK:STDOUT:   %Base.val: %Base = struct_value (%int_0.263, %int_1.47b) [concrete]
 // CHECK:STDOUT:   %Base.val: %Base = struct_value (%int_0.263, %int_1.47b) [concrete]
 // CHECK:STDOUT:   %Child.val: %Child = struct_value (%Base.val) [concrete]
 // CHECK:STDOUT:   %Child.val: %Child = struct_value (%Base.val) [concrete]
 // CHECK:STDOUT:   %Base.elem: type = unbound_element_type %Base, %i32 [concrete]
 // CHECK:STDOUT:   %Base.elem: type = unbound_element_type %Base, %i32 [concrete]
@@ -262,7 +264,7 @@ fn Run() {
 // CHECK:STDOUT:   %bound_method.loc7_49.2: <bound method> = bound_method %int_0, %specific_fn.loc7_49.1 [concrete = constants.%bound_method.9f1]
 // CHECK:STDOUT:   %bound_method.loc7_49.2: <bound method> = bound_method %int_0, %specific_fn.loc7_49.1 [concrete = constants.%bound_method.9f1]
 // CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc7_49.1: init %i32 = call %bound_method.loc7_49.2(%int_0) [concrete = constants.%int_0.263]
 // CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc7_49.1: init %i32 = call %bound_method.loc7_49.2(%int_0) [concrete = constants.%int_0.263]
 // CHECK:STDOUT:   %.loc7_49.2: init %i32 = converted %int_0, %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc7_49.1 [concrete = constants.%int_0.263]
 // CHECK:STDOUT:   %.loc7_49.2: init %i32 = converted %int_0, %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc7_49.1 [concrete = constants.%int_0.263]
-// CHECK:STDOUT:   %.loc7_50.2: ref %Base = class_element_access %a.var, element0
+// CHECK:STDOUT:   %.loc7_50.2: ref %.7a5 = class_element_access %a.var, element0
 // CHECK:STDOUT:   %.loc7_49.3: ref %i32 = class_element_access %.loc7_50.2, element0
 // CHECK:STDOUT:   %.loc7_49.3: ref %i32 = class_element_access %.loc7_50.2, element0
 // CHECK:STDOUT:   %.loc7_49.4: init %i32 to %.loc7_49.3 = in_place_init %.loc7_49.2 [concrete = constants.%int_0.263]
 // CHECK:STDOUT:   %.loc7_49.4: init %i32 to %.loc7_49.3 = in_place_init %.loc7_49.2 [concrete = constants.%int_0.263]
 // CHECK:STDOUT:   %impl.elem0.loc7_49.2: %.7c3 = impl_witness_access constants.%ImplicitAs.impl_witness.58d, element0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.5f4]
 // CHECK:STDOUT:   %impl.elem0.loc7_49.2: %.7c3 = impl_witness_access constants.%ImplicitAs.impl_witness.58d, element0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.5f4]
@@ -273,10 +275,11 @@ fn Run() {
 // CHECK:STDOUT:   %.loc7_49.5: init %i32 = converted %int_1, %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc7_49.2 [concrete = constants.%int_1.47b]
 // CHECK:STDOUT:   %.loc7_49.5: init %i32 = converted %int_1, %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc7_49.2 [concrete = constants.%int_1.47b]
 // CHECK:STDOUT:   %.loc7_49.6: ref %i32 = class_element_access %.loc7_50.2, element1
 // CHECK:STDOUT:   %.loc7_49.6: ref %i32 = class_element_access %.loc7_50.2, element1
 // CHECK:STDOUT:   %.loc7_49.7: init %i32 to %.loc7_49.6 = in_place_init %.loc7_49.5 [concrete = constants.%int_1.47b]
 // CHECK:STDOUT:   %.loc7_49.7: init %i32 to %.loc7_49.6 = in_place_init %.loc7_49.5 [concrete = constants.%int_1.47b]
-// CHECK:STDOUT:   %.loc7_49.8: init %Base to %.loc7_50.2 = class_init (%.loc7_49.4, %.loc7_49.7) [concrete = constants.%Base.val]
-// CHECK:STDOUT:   %.loc7_50.3: init %Base = converted %.loc7_49.1, %.loc7_49.8 [concrete = constants.%Base.val]
-// CHECK:STDOUT:   %.loc7_50.4: init %Child to %a.var = class_init (%.loc7_50.3) [concrete = constants.%Child.val]
-// CHECK:STDOUT:   %.loc7_3: init %Child = converted %.loc7_50.1, %.loc7_50.4 [concrete = constants.%Child.val]
+// CHECK:STDOUT:   %.loc7_49.8: init %.7a5 to %.loc7_50.2 = class_init (%.loc7_49.4, %.loc7_49.7) [concrete = constants.%struct.f56]
+// CHECK:STDOUT:   %.loc7_50.3: init %.7a5 = converted %.loc7_49.1, %.loc7_49.8 [concrete = constants.%struct.f56]
+// CHECK:STDOUT:   %.loc7_50.4: init %Base = as_compatible %.loc7_50.3 [concrete = constants.%Base.val]
+// CHECK:STDOUT:   %.loc7_50.5: init %Child to %a.var = class_init (%.loc7_50.4) [concrete = constants.%Child.val]
+// CHECK:STDOUT:   %.loc7_3: init %Child = converted %.loc7_50.1, %.loc7_50.5 [concrete = constants.%Child.val]
 // CHECK:STDOUT:   assign %a.var, %.loc7_3
 // CHECK:STDOUT:   assign %a.var, %.loc7_3
 // CHECK:STDOUT:   %Child.ref: type = name_ref Child, imports.%Main.Child [concrete = constants.%Child]
 // CHECK:STDOUT:   %Child.ref: type = name_ref Child, imports.%Main.Child [concrete = constants.%Child]
 // CHECK:STDOUT:   %a: ref %Child = ref_binding a, %a.var
 // CHECK:STDOUT:   %a: ref %Child = ref_binding a, %a.var

+ 67 - 0
toolchain/check/testdata/class/partial/init.carbon

@@ -0,0 +1,67 @@
+// 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
+//
+// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/convert.carbon
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/class/partial/init.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/class/partial/init.carbon
+
+// --- base.carbon
+
+library "[[@TEST_NAME]]";
+
+base class Base {
+  fn Make() -> partial Self {
+    return {};
+  }
+}
+
+class Derived {
+  extend base: Base;
+  fn Make() -> Self {
+    //@dump-sem-ir-begin
+    return {.base = Base.Make()};
+    //@dump-sem-ir-end
+  }
+}
+
+// CHECK:STDOUT: --- base.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Base: type = class_type @Base [concrete]
+// CHECK:STDOUT:   %.7a5: type = partial_type %Base [concrete]
+// CHECK:STDOUT:   %Base.Make.type: type = fn_type @Base.Make [concrete]
+// CHECK:STDOUT:   %Base.Make: %Base.Make.type = struct_value () [concrete]
+// CHECK:STDOUT:   %Derived: type = class_type @Derived [concrete]
+// CHECK:STDOUT:   %struct_type.base.cad: type = struct_type {.base: %.7a5} [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Derived {
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Derived
+// CHECK:STDOUT:   .Base = <poisoned>
+// CHECK:STDOUT:   .base = %.loc11
+// CHECK:STDOUT:   .Make = %Derived.Make.decl
+// CHECK:STDOUT:   extend %Base.ref
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Derived.Make() -> out %return.param: %Derived {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Base.ref: type = name_ref Base, file.%Base.decl [concrete = constants.%Base]
+// CHECK:STDOUT:   %Make.ref: %Base.Make.type = name_ref Make, @Base.%Base.Make.decl [concrete = constants.%Base.Make]
+// CHECK:STDOUT:   %.loc14_32.1: ref %.7a5 = class_element_access %return.param, element0
+// CHECK:STDOUT:   %Base.Make.call: init %.7a5 to %.loc14_32.1 = call %Make.ref()
+// CHECK:STDOUT:   %.loc14_32.2: %struct_type.base.cad = struct_literal (%Base.Make.call)
+// CHECK:STDOUT:   %.loc14_32.3: init %Base = as_compatible %Base.Make.call
+// CHECK:STDOUT:   %.loc14_32.4: init %Derived to %return.param = class_init (%.loc14_32.3)
+// CHECK:STDOUT:   %.loc14_33: init %Derived = converted %.loc14_32.2, %.loc14_32.4
+// CHECK:STDOUT:   return %.loc14_33 to %return.param
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 56 - 30
toolchain/check/testdata/class/virtual_modifiers.carbon

@@ -640,10 +640,13 @@ class T2(G2:! type) {
 // CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
 // CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
 // CHECK:STDOUT:   %empty_struct: %empty_struct_type = struct_value () [concrete]
 // CHECK:STDOUT:   %empty_struct: %empty_struct_type = struct_value () [concrete]
 // CHECK:STDOUT:   %struct_type.base.f5e: type = struct_type {.base: %empty_struct_type} [concrete]
 // CHECK:STDOUT:   %struct_type.base.f5e: type = struct_type {.base: %empty_struct_type} [concrete]
-// CHECK:STDOUT:   %struct: %struct_type.base.f5e = struct_value (%empty_struct) [concrete]
-// CHECK:STDOUT:   %Derived.vtable_ptr: ref %ptr.454 = vtable_ptr @Derived.vtable [concrete]
-// CHECK:STDOUT:   %Base.val: %Base = struct_value (%Derived.vtable_ptr) [concrete]
+// CHECK:STDOUT:   %struct.6b1: %struct_type.base.f5e = struct_value (%empty_struct) [concrete]
+// CHECK:STDOUT:   %.a5d: type = partial_type %Base [concrete]
+// CHECK:STDOUT:   %uninit: %ptr.454 = uninitialized_value [concrete]
+// CHECK:STDOUT:   %struct.4dc: %.a5d = struct_value (%uninit) [concrete]
+// CHECK:STDOUT:   %Base.val: %Base = struct_value (%uninit) [concrete]
 // CHECK:STDOUT:   %Derived.val: %Derived = struct_value (%Base.val) [concrete]
 // CHECK:STDOUT:   %Derived.val: %Derived = struct_value (%Base.val) [concrete]
+// CHECK:STDOUT:   %Derived.vtable_ptr: ref %ptr.454 = vtable_ptr @Derived.vtable [concrete]
 // CHECK:STDOUT:   %Destroy.type: type = facet_type <@Destroy> [concrete]
 // CHECK:STDOUT:   %Destroy.type: type = facet_type <@Destroy> [concrete]
 // CHECK:STDOUT:   %DestroyOp.type: type = fn_type @DestroyOp [concrete]
 // CHECK:STDOUT:   %DestroyOp.type: type = fn_type @DestroyOp [concrete]
 // CHECK:STDOUT:   %DestroyOp: %DestroyOp.type = struct_value () [concrete]
 // CHECK:STDOUT:   %DestroyOp: %DestroyOp.type = struct_value () [concrete]
@@ -734,15 +737,21 @@ class T2(G2:! type) {
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %d.var: ref %Derived = var %d.var_patt
 // CHECK:STDOUT:   %d.var: ref %Derived = var %d.var_patt
 // CHECK:STDOUT:   %.loc12_37.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct]
 // CHECK:STDOUT:   %.loc12_37.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct]
-// CHECK:STDOUT:   %.loc12_38.1: %struct_type.base.f5e = struct_literal (%.loc12_37.1) [concrete = constants.%struct]
-// CHECK:STDOUT:   %.loc12_38.2: ref %Base = class_element_access %d.var, element0
+// CHECK:STDOUT:   %.loc12_38.1: %struct_type.base.f5e = struct_literal (%.loc12_37.1) [concrete = constants.%struct.6b1]
+// CHECK:STDOUT:   %.loc12_38.2: ref %.a5d = class_element_access %d.var, element0
 // CHECK:STDOUT:   %.loc12_37.2: ref %ptr.454 = class_element_access %.loc12_38.2, element0
 // CHECK:STDOUT:   %.loc12_37.2: ref %ptr.454 = class_element_access %.loc12_38.2, element0
+// CHECK:STDOUT:   %uninit: %ptr.454 = uninitialized_value [concrete = constants.%uninit]
+// CHECK:STDOUT:   %.loc12_37.3: init %ptr.454 to %.loc12_37.2 = in_place_init %uninit [concrete = constants.%uninit]
+// CHECK:STDOUT:   %.loc12_37.4: init %.a5d to %.loc12_38.2 = class_init (%.loc12_37.3) [concrete = constants.%struct.4dc]
+// CHECK:STDOUT:   %.loc12_38.3: init %.a5d = converted %.loc12_37.1, %.loc12_37.4 [concrete = constants.%struct.4dc]
+// CHECK:STDOUT:   %.loc12_38.4: init %Base = as_compatible %.loc12_38.3 [concrete = constants.%Base.val]
+// CHECK:STDOUT:   %.loc12_38.5: init %Derived to %d.var = class_init (%.loc12_38.4) [concrete = constants.%Derived.val]
 // CHECK:STDOUT:   %Derived.vtable_ptr: ref %ptr.454 = vtable_ptr @Derived.vtable [concrete = constants.%Derived.vtable_ptr]
 // CHECK:STDOUT:   %Derived.vtable_ptr: ref %ptr.454 = vtable_ptr @Derived.vtable [concrete = constants.%Derived.vtable_ptr]
-// CHECK:STDOUT:   %.loc12_37.3: init %ptr.454 to %.loc12_37.2 = in_place_init %Derived.vtable_ptr [concrete = constants.%Derived.vtable_ptr]
-// CHECK:STDOUT:   %.loc12_37.4: init %Base to %.loc12_38.2 = class_init (%.loc12_37.3) [concrete = constants.%Base.val]
-// CHECK:STDOUT:   %.loc12_38.3: init %Base = converted %.loc12_37.1, %.loc12_37.4 [concrete = constants.%Base.val]
-// CHECK:STDOUT:   %.loc12_38.4: init %Derived to %d.var = class_init (%.loc12_38.3) [concrete = constants.%Derived.val]
-// CHECK:STDOUT:   %.loc12_3: init %Derived = converted %.loc12_38.1, %.loc12_38.4 [concrete = constants.%Derived.val]
+// CHECK:STDOUT:   %.loc12_38.6: ref %Base = class_element_access %d.var, element0
+// CHECK:STDOUT:   %.loc12_38.7: ref %ptr.454 = class_element_access %.loc12_38.6, element0
+// CHECK:STDOUT:   %.loc12_38.8: init %ptr.454 to %.loc12_38.7 = in_place_init %Derived.vtable_ptr [concrete = constants.%Derived.vtable_ptr]
+// CHECK:STDOUT:   %.loc12_38.9: init %Derived = update_init %.loc12_38.5, %.loc12_38.8
+// CHECK:STDOUT:   %.loc12_3: init %Derived = converted %.loc12_38.1, %.loc12_38.9
 // CHECK:STDOUT:   assign %d.var, %.loc12_3
 // CHECK:STDOUT:   assign %d.var, %.loc12_3
 // CHECK:STDOUT:   %Derived.ref: type = name_ref Derived, file.%Derived.decl [concrete = constants.%Derived]
 // CHECK:STDOUT:   %Derived.ref: type = name_ref Derived, file.%Derived.decl [concrete = constants.%Derived]
 // CHECK:STDOUT:   %d: ref %Derived = ref_binding d, %d.var
 // CHECK:STDOUT:   %d: ref %Derived = ref_binding d, %d.var
@@ -1070,15 +1079,18 @@ class T2(G2:! type) {
 // CHECK:STDOUT:   %B1.val.d17: %B1 = struct_value (%B1.vtable_ptr) [concrete]
 // CHECK:STDOUT:   %B1.val.d17: %B1 = struct_value (%B1.vtable_ptr) [concrete]
 // CHECK:STDOUT:   %struct_type.base.f5e: type = struct_type {.base: %empty_struct_type} [concrete]
 // CHECK:STDOUT:   %struct_type.base.f5e: type = struct_type {.base: %empty_struct_type} [concrete]
 // CHECK:STDOUT:   %struct.6b1: %struct_type.base.f5e = struct_value (%empty_struct) [concrete]
 // CHECK:STDOUT:   %struct.6b1: %struct_type.base.f5e = struct_value (%empty_struct) [concrete]
+// CHECK:STDOUT:   %.5b3: type = partial_type %B1 [concrete]
+// CHECK:STDOUT:   %uninit: %ptr.454 = uninitialized_value [concrete]
+// CHECK:STDOUT:   %struct.571: %.5b3 = struct_value (%uninit) [concrete]
+// CHECK:STDOUT:   %B1.val.fd5: %B1 = struct_value (%uninit) [concrete]
+// CHECK:STDOUT:   %B2.val: %B2 = struct_value (%B1.val.fd5) [concrete]
 // CHECK:STDOUT:   %B2.vtable_ptr: ref %ptr.454 = vtable_ptr @B2.vtable [concrete]
 // CHECK:STDOUT:   %B2.vtable_ptr: ref %ptr.454 = vtable_ptr @B2.vtable [concrete]
-// CHECK:STDOUT:   %B1.val.f0b: %B1 = struct_value (%B2.vtable_ptr) [concrete]
-// CHECK:STDOUT:   %B2.val.e28: %B2 = struct_value (%B1.val.f0b) [concrete]
 // CHECK:STDOUT:   %struct_type.base.a0c: type = struct_type {.base: %struct_type.base.f5e} [concrete]
 // CHECK:STDOUT:   %struct_type.base.a0c: type = struct_type {.base: %struct_type.base.f5e} [concrete]
 // CHECK:STDOUT:   %struct.83e: %struct_type.base.a0c = struct_value (%struct.6b1) [concrete]
 // CHECK:STDOUT:   %struct.83e: %struct_type.base.a0c = struct_value (%struct.6b1) [concrete]
+// CHECK:STDOUT:   %.312: type = partial_type %B2 [concrete]
+// CHECK:STDOUT:   %struct.0cd: %.312 = struct_value (%B1.val.fd5) [concrete]
+// CHECK:STDOUT:   %C.val: %C = struct_value (%B2.val) [concrete]
 // CHECK:STDOUT:   %C.vtable_ptr: ref %ptr.454 = vtable_ptr @C.vtable [concrete]
 // CHECK:STDOUT:   %C.vtable_ptr: ref %ptr.454 = vtable_ptr @C.vtable [concrete]
-// CHECK:STDOUT:   %B1.val.dd4: %B1 = struct_value (%C.vtable_ptr) [concrete]
-// CHECK:STDOUT:   %B2.val.b52: %B2 = struct_value (%B1.val.dd4) [concrete]
-// CHECK:STDOUT:   %C.val: %C = struct_value (%B2.val.b52) [concrete]
 // CHECK:STDOUT:   %Destroy.type: type = facet_type <@Destroy> [concrete]
 // CHECK:STDOUT:   %Destroy.type: type = facet_type <@Destroy> [concrete]
 // CHECK:STDOUT:   %DestroyOp.type.3e79c2.1: type = fn_type @DestroyOp.loc21 [concrete]
 // CHECK:STDOUT:   %DestroyOp.type.3e79c2.1: type = fn_type @DestroyOp.loc21 [concrete]
 // CHECK:STDOUT:   %DestroyOp.b0ebf8.1: %DestroyOp.type.3e79c2.1 = struct_value () [concrete]
 // CHECK:STDOUT:   %DestroyOp.b0ebf8.1: %DestroyOp.type.3e79c2.1 = struct_value () [concrete]
@@ -1220,14 +1232,20 @@ class T2(G2:! type) {
 // CHECK:STDOUT:   %b2.var: ref %B2 = var %b2.var_patt
 // CHECK:STDOUT:   %b2.var: ref %B2 = var %b2.var_patt
 // CHECK:STDOUT:   %.loc20_33.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct]
 // CHECK:STDOUT:   %.loc20_33.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct]
 // CHECK:STDOUT:   %.loc20_34.1: %struct_type.base.f5e = struct_literal (%.loc20_33.1) [concrete = constants.%struct.6b1]
 // CHECK:STDOUT:   %.loc20_34.1: %struct_type.base.f5e = struct_literal (%.loc20_33.1) [concrete = constants.%struct.6b1]
-// CHECK:STDOUT:   %.loc20_34.2: ref %B1 = class_element_access %b2.var, element0
+// CHECK:STDOUT:   %.loc20_34.2: ref %.5b3 = class_element_access %b2.var, element0
 // CHECK:STDOUT:   %.loc20_33.2: ref %ptr.454 = class_element_access %.loc20_34.2, element0
 // CHECK:STDOUT:   %.loc20_33.2: ref %ptr.454 = class_element_access %.loc20_34.2, element0
+// CHECK:STDOUT:   %uninit.loc20: %ptr.454 = uninitialized_value [concrete = constants.%uninit]
+// CHECK:STDOUT:   %.loc20_33.3: init %ptr.454 to %.loc20_33.2 = in_place_init %uninit.loc20 [concrete = constants.%uninit]
+// CHECK:STDOUT:   %.loc20_33.4: init %.5b3 to %.loc20_34.2 = class_init (%.loc20_33.3) [concrete = constants.%struct.571]
+// CHECK:STDOUT:   %.loc20_34.3: init %.5b3 = converted %.loc20_33.1, %.loc20_33.4 [concrete = constants.%struct.571]
+// CHECK:STDOUT:   %.loc20_34.4: init %B1 = as_compatible %.loc20_34.3 [concrete = constants.%B1.val.fd5]
+// CHECK:STDOUT:   %.loc20_34.5: init %B2 to %b2.var = class_init (%.loc20_34.4) [concrete = constants.%B2.val]
 // CHECK:STDOUT:   %B2.vtable_ptr: ref %ptr.454 = vtable_ptr @B2.vtable [concrete = constants.%B2.vtable_ptr]
 // CHECK:STDOUT:   %B2.vtable_ptr: ref %ptr.454 = vtable_ptr @B2.vtable [concrete = constants.%B2.vtable_ptr]
-// CHECK:STDOUT:   %.loc20_33.3: init %ptr.454 to %.loc20_33.2 = in_place_init %B2.vtable_ptr [concrete = constants.%B2.vtable_ptr]
-// CHECK:STDOUT:   %.loc20_33.4: init %B1 to %.loc20_34.2 = class_init (%.loc20_33.3) [concrete = constants.%B1.val.f0b]
-// CHECK:STDOUT:   %.loc20_34.3: init %B1 = converted %.loc20_33.1, %.loc20_33.4 [concrete = constants.%B1.val.f0b]
-// CHECK:STDOUT:   %.loc20_34.4: init %B2 to %b2.var = class_init (%.loc20_34.3) [concrete = constants.%B2.val.e28]
-// CHECK:STDOUT:   %.loc20_3: init %B2 = converted %.loc20_34.1, %.loc20_34.4 [concrete = constants.%B2.val.e28]
+// CHECK:STDOUT:   %.loc20_34.6: ref %B1 = class_element_access %b2.var, element0
+// CHECK:STDOUT:   %.loc20_34.7: ref %ptr.454 = class_element_access %.loc20_34.6, element0
+// CHECK:STDOUT:   %.loc20_34.8: init %ptr.454 to %.loc20_34.7 = in_place_init %B2.vtable_ptr [concrete = constants.%B2.vtable_ptr]
+// CHECK:STDOUT:   %.loc20_34.9: init %B2 = update_init %.loc20_34.5, %.loc20_34.8
+// CHECK:STDOUT:   %.loc20_3: init %B2 = converted %.loc20_34.1, %.loc20_34.9
 // CHECK:STDOUT:   assign %b2.var, %.loc20_3
 // CHECK:STDOUT:   assign %b2.var, %.loc20_3
 // CHECK:STDOUT:   %B2.ref: type = name_ref B2, file.%B2.decl [concrete = constants.%B2]
 // CHECK:STDOUT:   %B2.ref: type = name_ref B2, file.%B2.decl [concrete = constants.%B2]
 // CHECK:STDOUT:   %b2: ref %B2 = ref_binding b2, %b2.var
 // CHECK:STDOUT:   %b2: ref %B2 = ref_binding b2, %b2.var
@@ -1239,17 +1257,25 @@ class T2(G2:! type) {
 // CHECK:STDOUT:   %.loc21_40.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct]
 // CHECK:STDOUT:   %.loc21_40.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct]
 // CHECK:STDOUT:   %.loc21_41.1: %struct_type.base.f5e = struct_literal (%.loc21_40.1) [concrete = constants.%struct.6b1]
 // CHECK:STDOUT:   %.loc21_41.1: %struct_type.base.f5e = struct_literal (%.loc21_40.1) [concrete = constants.%struct.6b1]
 // CHECK:STDOUT:   %.loc21_42.1: %struct_type.base.a0c = struct_literal (%.loc21_41.1) [concrete = constants.%struct.83e]
 // CHECK:STDOUT:   %.loc21_42.1: %struct_type.base.a0c = struct_literal (%.loc21_41.1) [concrete = constants.%struct.83e]
-// CHECK:STDOUT:   %.loc21_42.2: ref %B2 = class_element_access %c.var, element0
-// CHECK:STDOUT:   %.loc21_41.2: ref %B1 = class_element_access %.loc21_42.2, element0
+// CHECK:STDOUT:   %.loc21_42.2: ref %.312 = class_element_access %c.var, element0
+// CHECK:STDOUT:   %.loc21_41.2: ref %.5b3 = class_element_access %.loc21_42.2, element0
 // CHECK:STDOUT:   %.loc21_40.2: ref %ptr.454 = class_element_access %.loc21_41.2, element0
 // CHECK:STDOUT:   %.loc21_40.2: ref %ptr.454 = class_element_access %.loc21_41.2, element0
+// CHECK:STDOUT:   %uninit.loc21: %ptr.454 = uninitialized_value [concrete = constants.%uninit]
+// CHECK:STDOUT:   %.loc21_40.3: init %ptr.454 to %.loc21_40.2 = in_place_init %uninit.loc21 [concrete = constants.%uninit]
+// CHECK:STDOUT:   %.loc21_40.4: init %.5b3 to %.loc21_41.2 = class_init (%.loc21_40.3) [concrete = constants.%struct.571]
+// CHECK:STDOUT:   %.loc21_41.3: init %.5b3 = converted %.loc21_40.1, %.loc21_40.4 [concrete = constants.%struct.571]
+// CHECK:STDOUT:   %.loc21_41.4: init %B1 = as_compatible %.loc21_41.3 [concrete = constants.%B1.val.fd5]
+// CHECK:STDOUT:   %.loc21_41.5: init %.312 to %.loc21_42.2 = class_init (%.loc21_41.4) [concrete = constants.%struct.0cd]
+// CHECK:STDOUT:   %.loc21_42.3: init %.312 = converted %.loc21_41.1, %.loc21_41.5 [concrete = constants.%struct.0cd]
+// CHECK:STDOUT:   %.loc21_42.4: init %B2 = as_compatible %.loc21_42.3 [concrete = constants.%B2.val]
+// CHECK:STDOUT:   %.loc21_42.5: init %C to %c.var = class_init (%.loc21_42.4) [concrete = constants.%C.val]
 // CHECK:STDOUT:   %C.vtable_ptr: ref %ptr.454 = vtable_ptr @C.vtable [concrete = constants.%C.vtable_ptr]
 // CHECK:STDOUT:   %C.vtable_ptr: ref %ptr.454 = vtable_ptr @C.vtable [concrete = constants.%C.vtable_ptr]
-// CHECK:STDOUT:   %.loc21_40.3: init %ptr.454 to %.loc21_40.2 = in_place_init %C.vtable_ptr [concrete = constants.%C.vtable_ptr]
-// CHECK:STDOUT:   %.loc21_40.4: init %B1 to %.loc21_41.2 = class_init (%.loc21_40.3) [concrete = constants.%B1.val.dd4]
-// CHECK:STDOUT:   %.loc21_41.3: init %B1 = converted %.loc21_40.1, %.loc21_40.4 [concrete = constants.%B1.val.dd4]
-// CHECK:STDOUT:   %.loc21_41.4: init %B2 to %.loc21_42.2 = class_init (%.loc21_41.3) [concrete = constants.%B2.val.b52]
-// CHECK:STDOUT:   %.loc21_42.3: init %B2 = converted %.loc21_41.1, %.loc21_41.4 [concrete = constants.%B2.val.b52]
-// CHECK:STDOUT:   %.loc21_42.4: init %C to %c.var = class_init (%.loc21_42.3) [concrete = constants.%C.val]
-// CHECK:STDOUT:   %.loc21_3: init %C = converted %.loc21_42.1, %.loc21_42.4 [concrete = constants.%C.val]
+// CHECK:STDOUT:   %.loc21_42.6: ref %B2 = class_element_access %c.var, element0
+// CHECK:STDOUT:   %.loc21_42.7: ref %B1 = class_element_access %.loc21_42.6, element0
+// CHECK:STDOUT:   %.loc21_42.8: ref %ptr.454 = class_element_access %.loc21_42.7, element0
+// CHECK:STDOUT:   %.loc21_42.9: init %ptr.454 to %.loc21_42.8 = in_place_init %C.vtable_ptr [concrete = constants.%C.vtable_ptr]
+// CHECK:STDOUT:   %.loc21_42.10: init %C = update_init %.loc21_42.5, %.loc21_42.9
+// CHECK:STDOUT:   %.loc21_3: init %C = converted %.loc21_42.1, %.loc21_42.10
 // CHECK:STDOUT:   assign %c.var, %.loc21_3
 // CHECK:STDOUT:   assign %c.var, %.loc21_3
 // CHECK:STDOUT:   %C.ref: type = name_ref C, file.%C.decl [concrete = constants.%C]
 // CHECK:STDOUT:   %C.ref: type = name_ref C, file.%C.decl [concrete = constants.%C]
 // CHECK:STDOUT:   %c: ref %C = ref_binding c, %c.var
 // CHECK:STDOUT:   %c: ref %C = ref_binding c, %c.var

+ 13 - 6
toolchain/check/testdata/interop/cpp/class/access.carbon

@@ -2026,6 +2026,7 @@ fn Call(var instance: Cpp.PublicPrivate) {
 // CHECK:STDOUT:   %ptr.d9e: type = ptr_type %C [concrete]
 // CHECK:STDOUT:   %ptr.d9e: type = ptr_type %C [concrete]
 // CHECK:STDOUT:   %C__carbon_thunk.type.65f120.1: type = fn_type @C__carbon_thunk.1 [concrete]
 // CHECK:STDOUT:   %C__carbon_thunk.type.65f120.1: type = fn_type @C__carbon_thunk.1 [concrete]
 // CHECK:STDOUT:   %C__carbon_thunk.d98342.1: %C__carbon_thunk.type.65f120.1 = struct_value () [concrete]
 // CHECK:STDOUT:   %C__carbon_thunk.d98342.1: %C__carbon_thunk.type.65f120.1 = struct_value () [concrete]
+// CHECK:STDOUT:   %.73a: type = partial_type %C [concrete]
 // CHECK:STDOUT:   %PublicCall.cpp_destructor.type: type = fn_type @PublicCall.cpp_destructor [concrete]
 // CHECK:STDOUT:   %PublicCall.cpp_destructor.type: type = fn_type @PublicCall.cpp_destructor [concrete]
 // CHECK:STDOUT:   %PublicCall.cpp_destructor: %PublicCall.cpp_destructor.type = struct_value () [concrete]
 // CHECK:STDOUT:   %PublicCall.cpp_destructor: %PublicCall.cpp_destructor.type = struct_value () [concrete]
 // CHECK:STDOUT:   %ProtectedCall: type = class_type @ProtectedCall [concrete]
 // CHECK:STDOUT:   %ProtectedCall: type = class_type @ProtectedCall [concrete]
@@ -2119,7 +2120,7 @@ fn Call(var instance: Cpp.PublicPrivate) {
 // CHECK:STDOUT:   %addr.loc11_49: %ptr.dfa = addr_of %.loc11_49.1
 // CHECK:STDOUT:   %addr.loc11_49: %ptr.dfa = addr_of %.loc11_49.1
 // CHECK:STDOUT:   %PublicCall__carbon_thunk.call: init %empty_tuple.type = call imports.%PublicCall__carbon_thunk.decl(%addr.loc11_49)
 // CHECK:STDOUT:   %PublicCall__carbon_thunk.call: init %empty_tuple.type = call imports.%PublicCall__carbon_thunk.decl(%addr.loc11_49)
 // CHECK:STDOUT:   %.loc11_49.2: init %PublicCall to %.loc11_49.1 = mark_in_place_init %PublicCall__carbon_thunk.call
 // CHECK:STDOUT:   %.loc11_49.2: init %PublicCall to %.loc11_49.1 = mark_in_place_init %PublicCall__carbon_thunk.call
-// CHECK:STDOUT:   %.loc11_51.1: ref %C = class_element_access %return.param, element0
+// CHECK:STDOUT:   %.loc11_51.1: ref %.73a = class_element_access %return.param, element0
 // CHECK:STDOUT:   %.loc11_49.3: ref %PublicCall = temporary %.loc11_49.1, %.loc11_49.2
 // CHECK:STDOUT:   %.loc11_49.3: ref %PublicCall = temporary %.loc11_49.1, %.loc11_49.2
 // CHECK:STDOUT:   %.loc11_49.4: %PublicCall = acquire_value %.loc11_49.3
 // CHECK:STDOUT:   %.loc11_49.4: %PublicCall = acquire_value %.loc11_49.3
 // CHECK:STDOUT:   %.loc11_49.5: ref %PublicCall = value_as_ref %.loc11_49.4
 // CHECK:STDOUT:   %.loc11_49.5: ref %PublicCall = value_as_ref %.loc11_49.4
@@ -2128,8 +2129,11 @@ fn Call(var instance: Cpp.PublicPrivate) {
 // CHECK:STDOUT:   %C__carbon_thunk.call: init %empty_tuple.type = call imports.%C__carbon_thunk.decl.8acdfe.1(%addr.loc11_50.1, %addr.loc11_50.2)
 // CHECK:STDOUT:   %C__carbon_thunk.call: init %empty_tuple.type = call imports.%C__carbon_thunk.decl.8acdfe.1(%addr.loc11_50.1, %addr.loc11_50.2)
 // CHECK:STDOUT:   %.loc11_50: init %C to %.loc11_51.1 = mark_in_place_init %C__carbon_thunk.call
 // CHECK:STDOUT:   %.loc11_50: init %C to %.loc11_51.1 = mark_in_place_init %C__carbon_thunk.call
 // CHECK:STDOUT:   %.loc11_51.2: %struct_type.base.7c3 = struct_literal (%.loc11_50)
 // CHECK:STDOUT:   %.loc11_51.2: %struct_type.base.7c3 = struct_literal (%.loc11_50)
-// CHECK:STDOUT:   %.loc11_51.3: init %D to %return.param = class_init (%.loc11_50)
-// CHECK:STDOUT:   %.loc11_52: init %D = converted %.loc11_51.2, %.loc11_51.3
+// CHECK:STDOUT:   %.loc11_51.3: init %.73a = as_compatible %.loc11_50
+// CHECK:STDOUT:   %.loc11_51.4: init %.73a = converted %.loc11_50, %.loc11_51.3
+// CHECK:STDOUT:   %.loc11_51.5: init %C = as_compatible %.loc11_51.4
+// CHECK:STDOUT:   %.loc11_51.6: init %D to %return.param = class_init (%.loc11_51.5)
+// CHECK:STDOUT:   %.loc11_52: init %D = converted %.loc11_51.2, %.loc11_51.6
 // CHECK:STDOUT:   %PublicCall.cpp_destructor.bound: <bound method> = bound_method %.loc11_49.3, constants.%PublicCall.cpp_destructor
 // CHECK:STDOUT:   %PublicCall.cpp_destructor.bound: <bound method> = bound_method %.loc11_49.3, constants.%PublicCall.cpp_destructor
 // CHECK:STDOUT:   %PublicCall.cpp_destructor.call: init %empty_tuple.type = call %PublicCall.cpp_destructor.bound(%.loc11_49.3)
 // CHECK:STDOUT:   %PublicCall.cpp_destructor.call: init %empty_tuple.type = call %PublicCall.cpp_destructor.bound(%.loc11_49.3)
 // CHECK:STDOUT:   return %.loc11_52 to %return.param
 // CHECK:STDOUT:   return %.loc11_52 to %return.param
@@ -2145,7 +2149,7 @@ fn Call(var instance: Cpp.PublicPrivate) {
 // CHECK:STDOUT:   %addr.loc17_55: %ptr.8e6 = addr_of %.loc17_55.1
 // CHECK:STDOUT:   %addr.loc17_55: %ptr.8e6 = addr_of %.loc17_55.1
 // CHECK:STDOUT:   %ProtectedCall__carbon_thunk.call: init %empty_tuple.type = call imports.%ProtectedCall__carbon_thunk.decl(%addr.loc17_55)
 // CHECK:STDOUT:   %ProtectedCall__carbon_thunk.call: init %empty_tuple.type = call imports.%ProtectedCall__carbon_thunk.decl(%addr.loc17_55)
 // CHECK:STDOUT:   %.loc17_55.2: init %ProtectedCall to %.loc17_55.1 = mark_in_place_init %ProtectedCall__carbon_thunk.call
 // CHECK:STDOUT:   %.loc17_55.2: init %ProtectedCall to %.loc17_55.1 = mark_in_place_init %ProtectedCall__carbon_thunk.call
-// CHECK:STDOUT:   %.loc17_57.1: ref %C = class_element_access %return.param, element0
+// CHECK:STDOUT:   %.loc17_57.1: ref %.73a = class_element_access %return.param, element0
 // CHECK:STDOUT:   %.loc17_55.3: ref %ProtectedCall = temporary %.loc17_55.1, %.loc17_55.2
 // CHECK:STDOUT:   %.loc17_55.3: ref %ProtectedCall = temporary %.loc17_55.1, %.loc17_55.2
 // CHECK:STDOUT:   %.loc17_55.4: %ProtectedCall = acquire_value %.loc17_55.3
 // CHECK:STDOUT:   %.loc17_55.4: %ProtectedCall = acquire_value %.loc17_55.3
 // CHECK:STDOUT:   %.loc17_55.5: ref %ProtectedCall = value_as_ref %.loc17_55.4
 // CHECK:STDOUT:   %.loc17_55.5: ref %ProtectedCall = value_as_ref %.loc17_55.4
@@ -2154,8 +2158,11 @@ fn Call(var instance: Cpp.PublicPrivate) {
 // CHECK:STDOUT:   %C__carbon_thunk.call: init %empty_tuple.type = call imports.%C__carbon_thunk.decl.8acdfe.2(%addr.loc17_56.1, %addr.loc17_56.2)
 // CHECK:STDOUT:   %C__carbon_thunk.call: init %empty_tuple.type = call imports.%C__carbon_thunk.decl.8acdfe.2(%addr.loc17_56.1, %addr.loc17_56.2)
 // CHECK:STDOUT:   %.loc17_56: init %C to %.loc17_57.1 = mark_in_place_init %C__carbon_thunk.call
 // CHECK:STDOUT:   %.loc17_56: init %C to %.loc17_57.1 = mark_in_place_init %C__carbon_thunk.call
 // CHECK:STDOUT:   %.loc17_57.2: %struct_type.base.7c3 = struct_literal (%.loc17_56)
 // CHECK:STDOUT:   %.loc17_57.2: %struct_type.base.7c3 = struct_literal (%.loc17_56)
-// CHECK:STDOUT:   %.loc17_57.3: init %D to %return.param = class_init (%.loc17_56)
-// CHECK:STDOUT:   %.loc17_58: init %D = converted %.loc17_57.2, %.loc17_57.3
+// CHECK:STDOUT:   %.loc17_57.3: init %.73a = as_compatible %.loc17_56
+// CHECK:STDOUT:   %.loc17_57.4: init %.73a = converted %.loc17_56, %.loc17_57.3
+// CHECK:STDOUT:   %.loc17_57.5: init %C = as_compatible %.loc17_57.4
+// CHECK:STDOUT:   %.loc17_57.6: init %D to %return.param = class_init (%.loc17_57.5)
+// CHECK:STDOUT:   %.loc17_58: init %D = converted %.loc17_57.2, %.loc17_57.6
 // CHECK:STDOUT:   %ProtectedCall.cpp_destructor.bound: <bound method> = bound_method %.loc17_55.3, constants.%ProtectedCall.cpp_destructor
 // CHECK:STDOUT:   %ProtectedCall.cpp_destructor.bound: <bound method> = bound_method %.loc17_55.3, constants.%ProtectedCall.cpp_destructor
 // CHECK:STDOUT:   %ProtectedCall.cpp_destructor.call: init %empty_tuple.type = call %ProtectedCall.cpp_destructor.bound(%.loc17_55.3)
 // CHECK:STDOUT:   %ProtectedCall.cpp_destructor.call: init %empty_tuple.type = call %ProtectedCall.cpp_destructor.bound(%.loc17_55.3)
 // CHECK:STDOUT:   return %.loc17_58 to %return.param
 // CHECK:STDOUT:   return %.loc17_58 to %return.param

+ 22 - 16
toolchain/check/testdata/interop/cpp/impls/destroy.carbon

@@ -311,9 +311,11 @@ fn EqualWitnesses(p: Wrap(Cpp.PublicDestructor)*) -> Wrap(Cpp.PublicDestructor)*
 // CHECK:STDOUT:   %ProtectedDestructor: type = class_type @ProtectedDestructor [concrete]
 // CHECK:STDOUT:   %ProtectedDestructor: type = class_type @ProtectedDestructor [concrete]
 // CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
 // CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
 // CHECK:STDOUT:   %pattern_type.9f6: type = pattern_type %Derived [concrete]
 // CHECK:STDOUT:   %pattern_type.9f6: type = pattern_type %Derived [concrete]
-// CHECK:STDOUT:   %empty_struct: %empty_struct_type = struct_value () [concrete]
+// CHECK:STDOUT:   %empty_struct.a40: %empty_struct_type = struct_value () [concrete]
 // CHECK:STDOUT:   %struct_type.base.f5e: type = struct_type {.base: %empty_struct_type} [concrete]
 // CHECK:STDOUT:   %struct_type.base.f5e: type = struct_type {.base: %empty_struct_type} [concrete]
-// CHECK:STDOUT:   %struct: %struct_type.base.f5e = struct_value (%empty_struct) [concrete]
+// CHECK:STDOUT:   %struct: %struct_type.base.f5e = struct_value (%empty_struct.a40) [concrete]
+// CHECK:STDOUT:   %.ce2: type = partial_type %ProtectedDestructor [concrete]
+// CHECK:STDOUT:   %empty_struct.377: %.ce2 = struct_value () [concrete]
 // CHECK:STDOUT:   %ProtectedDestructor.val: %ProtectedDestructor = struct_value () [concrete]
 // CHECK:STDOUT:   %ProtectedDestructor.val: %ProtectedDestructor = struct_value () [concrete]
 // CHECK:STDOUT:   %Derived.val: %Derived = struct_value (%ProtectedDestructor.val) [concrete]
 // CHECK:STDOUT:   %Derived.val: %Derived = struct_value (%ProtectedDestructor.val) [concrete]
 // CHECK:STDOUT:   %DestroyOp.type: type = fn_type @DestroyOp [concrete]
 // CHECK:STDOUT:   %DestroyOp.type: type = fn_type @DestroyOp [concrete]
@@ -327,13 +329,14 @@ fn EqualWitnesses(p: Wrap(Cpp.PublicDestructor)*) -> Wrap(Cpp.PublicDestructor)*
 // CHECK:STDOUT:     %a.var_patt: %pattern_type.9f6 = var_pattern %a.patt [concrete]
 // CHECK:STDOUT:     %a.var_patt: %pattern_type.9f6 = var_pattern %a.patt [concrete]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %a.var: ref %Derived = var %a.var_patt
 // CHECK:STDOUT:   %a.var: ref %Derived = var %a.var_patt
-// CHECK:STDOUT:   %.loc12_37.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct]
+// CHECK:STDOUT:   %.loc12_37.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct.a40]
 // CHECK:STDOUT:   %.loc12_38.1: %struct_type.base.f5e = struct_literal (%.loc12_37.1) [concrete = constants.%struct]
 // CHECK:STDOUT:   %.loc12_38.1: %struct_type.base.f5e = struct_literal (%.loc12_37.1) [concrete = constants.%struct]
-// CHECK:STDOUT:   %.loc12_38.2: ref %ProtectedDestructor = class_element_access %a.var, element0
-// CHECK:STDOUT:   %.loc12_37.2: init %ProtectedDestructor to %.loc12_38.2 = class_init () [concrete = constants.%ProtectedDestructor.val]
-// CHECK:STDOUT:   %.loc12_38.3: init %ProtectedDestructor = converted %.loc12_37.1, %.loc12_37.2 [concrete = constants.%ProtectedDestructor.val]
-// CHECK:STDOUT:   %.loc12_38.4: init %Derived to %a.var = class_init (%.loc12_38.3) [concrete = constants.%Derived.val]
-// CHECK:STDOUT:   %.loc12_3: init %Derived = converted %.loc12_38.1, %.loc12_38.4 [concrete = constants.%Derived.val]
+// CHECK:STDOUT:   %.loc12_38.2: ref %.ce2 = class_element_access %a.var, element0
+// CHECK:STDOUT:   %.loc12_37.2: init %.ce2 to %.loc12_38.2 = class_init () [concrete = constants.%empty_struct.377]
+// CHECK:STDOUT:   %.loc12_38.3: init %.ce2 = converted %.loc12_37.1, %.loc12_37.2 [concrete = constants.%empty_struct.377]
+// CHECK:STDOUT:   %.loc12_38.4: init %ProtectedDestructor = as_compatible %.loc12_38.3 [concrete = constants.%ProtectedDestructor.val]
+// CHECK:STDOUT:   %.loc12_38.5: init %Derived to %a.var = class_init (%.loc12_38.4) [concrete = constants.%Derived.val]
+// CHECK:STDOUT:   %.loc12_3: init %Derived = converted %.loc12_38.1, %.loc12_38.5 [concrete = constants.%Derived.val]
 // CHECK:STDOUT:   assign %a.var, %.loc12_3
 // CHECK:STDOUT:   assign %a.var, %.loc12_3
 // CHECK:STDOUT:   %Derived.ref: type = name_ref Derived, file.%Derived.decl [concrete = constants.%Derived]
 // CHECK:STDOUT:   %Derived.ref: type = name_ref Derived, file.%Derived.decl [concrete = constants.%Derived]
 // CHECK:STDOUT:   %a: ref %Derived = ref_binding a, %a.var
 // CHECK:STDOUT:   %a: ref %Derived = ref_binding a, %a.var
@@ -352,9 +355,11 @@ fn EqualWitnesses(p: Wrap(Cpp.PublicDestructor)*) -> Wrap(Cpp.PublicDestructor)*
 // CHECK:STDOUT:   %PrivateDestructor: type = class_type @PrivateDestructor [concrete]
 // CHECK:STDOUT:   %PrivateDestructor: type = class_type @PrivateDestructor [concrete]
 // CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
 // CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
 // CHECK:STDOUT:   %pattern_type.9f6: type = pattern_type %Derived [concrete]
 // CHECK:STDOUT:   %pattern_type.9f6: type = pattern_type %Derived [concrete]
-// CHECK:STDOUT:   %empty_struct: %empty_struct_type = struct_value () [concrete]
+// CHECK:STDOUT:   %empty_struct.a40: %empty_struct_type = struct_value () [concrete]
 // CHECK:STDOUT:   %struct_type.base.f5e: type = struct_type {.base: %empty_struct_type} [concrete]
 // CHECK:STDOUT:   %struct_type.base.f5e: type = struct_type {.base: %empty_struct_type} [concrete]
-// CHECK:STDOUT:   %struct: %struct_type.base.f5e = struct_value (%empty_struct) [concrete]
+// CHECK:STDOUT:   %struct: %struct_type.base.f5e = struct_value (%empty_struct.a40) [concrete]
+// CHECK:STDOUT:   %.b20: type = partial_type %PrivateDestructor [concrete]
+// CHECK:STDOUT:   %empty_struct.361: %.b20 = struct_value () [concrete]
 // CHECK:STDOUT:   %PrivateDestructor.val: %PrivateDestructor = struct_value () [concrete]
 // CHECK:STDOUT:   %PrivateDestructor.val: %PrivateDestructor = struct_value () [concrete]
 // CHECK:STDOUT:   %Derived.val: %Derived = struct_value (%PrivateDestructor.val) [concrete]
 // CHECK:STDOUT:   %Derived.val: %Derived = struct_value (%PrivateDestructor.val) [concrete]
 // CHECK:STDOUT:   %DestroyOp.type: type = fn_type @DestroyOp [concrete]
 // CHECK:STDOUT:   %DestroyOp.type: type = fn_type @DestroyOp [concrete]
@@ -368,13 +373,14 @@ fn EqualWitnesses(p: Wrap(Cpp.PublicDestructor)*) -> Wrap(Cpp.PublicDestructor)*
 // CHECK:STDOUT:     %a.var_patt: %pattern_type.9f6 = var_pattern %a.patt [concrete]
 // CHECK:STDOUT:     %a.var_patt: %pattern_type.9f6 = var_pattern %a.patt [concrete]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %a.var: ref %Derived = var %a.var_patt
 // CHECK:STDOUT:   %a.var: ref %Derived = var %a.var_patt
-// CHECK:STDOUT:   %.loc13_37.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct]
+// CHECK:STDOUT:   %.loc13_37.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct.a40]
 // CHECK:STDOUT:   %.loc13_38.1: %struct_type.base.f5e = struct_literal (%.loc13_37.1) [concrete = constants.%struct]
 // CHECK:STDOUT:   %.loc13_38.1: %struct_type.base.f5e = struct_literal (%.loc13_37.1) [concrete = constants.%struct]
-// CHECK:STDOUT:   %.loc13_38.2: ref %PrivateDestructor = class_element_access %a.var, element0
-// CHECK:STDOUT:   %.loc13_37.2: init %PrivateDestructor to %.loc13_38.2 = class_init () [concrete = constants.%PrivateDestructor.val]
-// CHECK:STDOUT:   %.loc13_38.3: init %PrivateDestructor = converted %.loc13_37.1, %.loc13_37.2 [concrete = constants.%PrivateDestructor.val]
-// CHECK:STDOUT:   %.loc13_38.4: init %Derived to %a.var = class_init (%.loc13_38.3) [concrete = constants.%Derived.val]
-// CHECK:STDOUT:   %.loc13_3: init %Derived = converted %.loc13_38.1, %.loc13_38.4 [concrete = constants.%Derived.val]
+// CHECK:STDOUT:   %.loc13_38.2: ref %.b20 = class_element_access %a.var, element0
+// CHECK:STDOUT:   %.loc13_37.2: init %.b20 to %.loc13_38.2 = class_init () [concrete = constants.%empty_struct.361]
+// CHECK:STDOUT:   %.loc13_38.3: init %.b20 = converted %.loc13_37.1, %.loc13_37.2 [concrete = constants.%empty_struct.361]
+// CHECK:STDOUT:   %.loc13_38.4: init %PrivateDestructor = as_compatible %.loc13_38.3 [concrete = constants.%PrivateDestructor.val]
+// CHECK:STDOUT:   %.loc13_38.5: init %Derived to %a.var = class_init (%.loc13_38.4) [concrete = constants.%Derived.val]
+// CHECK:STDOUT:   %.loc13_3: init %Derived = converted %.loc13_38.1, %.loc13_38.5 [concrete = constants.%Derived.val]
 // CHECK:STDOUT:   assign %a.var, %.loc13_3
 // CHECK:STDOUT:   assign %a.var, %.loc13_3
 // CHECK:STDOUT:   %Derived.ref: type = name_ref Derived, file.%Derived.decl [concrete = constants.%Derived]
 // CHECK:STDOUT:   %Derived.ref: type = name_ref Derived, file.%Derived.decl [concrete = constants.%Derived]
 // CHECK:STDOUT:   %a: ref %Derived = ref_binding a, %a.var
 // CHECK:STDOUT:   %a: ref %Derived = ref_binding a, %a.var

+ 7 - 2
toolchain/check/type_completion.cpp

@@ -741,8 +741,13 @@ auto TypeCompleter::BuildInfoForInst(SemIR::TypeId /*type_id*/,
                                      SemIR::PartialType inst) const
                                      SemIR::PartialType inst) const
     -> SemIR::CompleteTypeInfo {
     -> SemIR::CompleteTypeInfo {
   // The value representation of `partial T` is the same as that of `T`.
   // The value representation of `partial T` is the same as that of `T`.
-  // Objects are not modifiable through their value representations.
-  return GetNestedInfo(context_->types().GetTypeIdForTypeInstId(inst.inner_id));
+  // Objects are not modifiable through their value representations. However,
+  // `partial T` is never abstract.
+  return {
+      .value_repr =
+          GetNestedInfo(context_->types().GetTypeIdForTypeInstId(inst.inner_id))
+              .value_repr,
+      .abstract_class_id = SemIR::ClassId::None};
 }
 }
 
 
 auto TypeCompleter::BuildInfoForInst(
 auto TypeCompleter::BuildInfoForInst(

+ 7 - 0
toolchain/lower/function_context.cpp

@@ -8,6 +8,7 @@
 #include "common/vlog.h"
 #include "common/vlog.h"
 #include "toolchain/base/kind_switch.h"
 #include "toolchain/base/kind_switch.h"
 #include "toolchain/sem_ir/diagnostic_loc_converter.h"
 #include "toolchain/sem_ir/diagnostic_loc_converter.h"
+#include "toolchain/sem_ir/expr_info.h"
 #include "toolchain/sem_ir/file.h"
 #include "toolchain/sem_ir/file.h"
 #include "toolchain/sem_ir/generic.h"
 #include "toolchain/sem_ir/generic.h"
 
 
@@ -293,6 +294,12 @@ auto FunctionContext::InitializeStorage(TypeInFile type, SemIR::InstId dest_id,
   }
   }
 }
 }
 
 
+auto FunctionContext::InitializeStorage(SemIR::InstId init_id) -> void {
+  InitializeStorage(GetTypeIdOfInst(init_id),
+                    SemIR::FindStorageArgForInitializer(sem_ir(), init_id),
+                    init_id);
+}
+
 auto FunctionContext::GetTypeIdOfInst(SemIR::InstId inst_id) -> TypeInFile {
 auto FunctionContext::GetTypeIdOfInst(SemIR::InstId inst_id) -> TypeInFile {
   auto [file, type_id] = SemIR::GetTypeOfInstInSpecific(
   auto [file, type_id] = SemIR::GetTypeOfInstInSpecific(
       specific_sem_ir(), specific_id(), sem_ir(), inst_id);
       specific_sem_ir(), specific_id(), sem_ir(), inst_id);

+ 4 - 0
toolchain/lower/function_context.h

@@ -186,6 +186,10 @@ class FunctionContext {
   auto InitializeStorage(TypeInFile type, SemIR::InstId dest_id,
   auto InitializeStorage(TypeInFile type, SemIR::InstId dest_id,
                          SemIR::InstId source_id) -> void;
                          SemIR::InstId source_id) -> void;
 
 
+  // Emits the instructions necessary to perform the initialization described by
+  // `init_id` in-place in its storage.
+  auto InitializeStorage(SemIR::InstId init_id) -> void;
+
   // When fingerprinting for a specific, adds the call, found in the function
   // When fingerprinting for a specific, adds the call, found in the function
   // body, to <function_id, specific_id>. `function_id` and `specific_id` are
   // body, to <function_id, specific_id>. `function_id` and `specific_id` are
   // IDs within the file identified by `function_file_id`.
   // IDs within the file identified by `function_file_id`.

+ 12 - 0
toolchain/lower/handle.cpp

@@ -350,6 +350,18 @@ auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
       inst_id, context.builder().CreateNot(context.GetValue(inst.operand_id)));
       inst_id, context.builder().CreateNot(context.GetValue(inst.operand_id)));
 }
 }
 
 
+auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
+                SemIR::UpdateInit inst) -> void {
+  // Ensure that our subordinate initializations have been performed. They may
+  // have been skipped if they were constant.
+  context.InitializeStorage(inst.base_init_id);
+  context.InitializeStorage(inst.update_init_id);
+
+  // TODO: Add a helper to poison a value slot.
+  context.SetLocal(inst_id,
+                   llvm::PoisonValue::get(context.GetTypeOfInst(inst_id)));
+}
+
 auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
 auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
                 SemIR::VarStorage /* inst */) -> void {
                 SemIR::VarStorage /* inst */) -> void {
   context.SetLocal(inst_id,
   context.SetLocal(inst_id,

+ 1 - 1
toolchain/lower/testdata/class/base.carbon

@@ -42,7 +42,7 @@ fn Convert(p: Derived*) -> Base* {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %.loc24_35.2.base = getelementptr inbounds nuw { { i32 }, i32 }, ptr %return, i32 0, i32 0, !dbg !8
 // CHECK:STDOUT:   %.loc24_35.2.base = getelementptr inbounds nuw { { i32 }, i32 }, ptr %return, i32 0, i32 0, !dbg !8
 // CHECK:STDOUT:   %.loc24_26.3.b = getelementptr inbounds nuw { i32 }, ptr %.loc24_35.2.base, i32 0, i32 0, !dbg !9
 // CHECK:STDOUT:   %.loc24_26.3.b = getelementptr inbounds nuw { i32 }, ptr %.loc24_35.2.base, i32 0, i32 0, !dbg !9
-// CHECK:STDOUT:   %.loc24_35.5.d = getelementptr inbounds nuw { { i32 }, i32 }, ptr %return, i32 0, i32 1, !dbg !8
+// CHECK:STDOUT:   %.loc24_35.6.d = getelementptr inbounds nuw { { i32 }, i32 }, ptr %return, i32 0, i32 1, !dbg !8
 // CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %return, ptr align 4 @Derived.val.loc24_36, i64 8, i1 false), !dbg !10
 // CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %return, ptr align 4 @Derived.val.loc24_36, i64 8, i1 false), !dbg !10
 // CHECK:STDOUT:   ret void, !dbg !10
 // CHECK:STDOUT:   ret void, !dbg !10
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }

+ 1 - 1
toolchain/lower/testdata/class/generic.carbon

@@ -122,7 +122,7 @@ fn AccessTuple() -> (i32, i32, i32) {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %.loc7_35.2.base = getelementptr inbounds nuw { { i32 }, i32 }, ptr %return, i32 0, i32 0, !dbg !8
 // CHECK:STDOUT:   %.loc7_35.2.base = getelementptr inbounds nuw { { i32 }, i32 }, ptr %return, i32 0, i32 0, !dbg !8
 // CHECK:STDOUT:   %.loc7_26.3.b = getelementptr inbounds nuw { i32 }, ptr %.loc7_35.2.base, i32 0, i32 0, !dbg !9
 // CHECK:STDOUT:   %.loc7_26.3.b = getelementptr inbounds nuw { i32 }, ptr %.loc7_35.2.base, i32 0, i32 0, !dbg !9
-// CHECK:STDOUT:   %.loc7_35.5.d = getelementptr inbounds nuw { { i32 }, i32 }, ptr %return, i32 0, i32 1, !dbg !8
+// CHECK:STDOUT:   %.loc7_35.6.d = getelementptr inbounds nuw { { i32 }, i32 }, ptr %return, i32 0, i32 1, !dbg !8
 // CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %return, ptr align 4 @Derived.val.loc7_36, i64 8, i1 false), !dbg !10
 // CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %return, ptr align 4 @Derived.val.loc7_36, i64 8, i1 false), !dbg !10
 // CHECK:STDOUT:   ret void, !dbg !10
 // CHECK:STDOUT:   ret void, !dbg !10
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }

+ 147 - 6
toolchain/lower/testdata/class/virtual.carbon

@@ -46,6 +46,36 @@ fn Use(v: Classes.Intermediate*) {
   v->Fn();
   v->Fn();
 }
 }
 
 
+// --- create_named_ctor.carbon
+
+library "[[@TEST_NAME]]";
+
+import Classes;
+
+fn MakePartialBase() -> partial Classes.Base {
+  return {};
+}
+
+fn MakePartialIntermediate() -> partial Classes.Intermediate {
+  return {.base = MakePartialBase()};
+}
+
+fn MakeBase() -> Classes.Base {
+  return MakePartialBase();
+}
+
+fn MakeIntermediate() -> Classes.Intermediate {
+  return MakePartialIntermediate();
+}
+
+fn MakeDerivedFromPartial() -> Classes.Derived {
+  return {.base = MakePartialIntermediate()};
+}
+
+fn MakeDerivedFromNonPartial() -> Classes.Derived {
+  return {.base = MakeIntermediate()};
+}
+
 // --- member_init.carbon
 // --- member_init.carbon
 
 
 package MemberInit;
 package MemberInit;
@@ -199,7 +229,7 @@ fn Make() {
 // CHECK:STDOUT: @"_CDerived.Classes.$vtable" = external unnamed_addr constant ptr
 // CHECK:STDOUT: @"_CDerived.Classes.$vtable" = external unnamed_addr constant ptr
 // CHECK:STDOUT: @Base.val.loc7_3 = internal constant {} zeroinitializer
 // CHECK:STDOUT: @Base.val.loc7_3 = internal constant {} zeroinitializer
 // CHECK:STDOUT: @Intermediate.val.ec2.loc8_3 = internal constant { ptr, {} } { ptr @"_CIntermediate.Classes.$vtable", {} zeroinitializer }
 // CHECK:STDOUT: @Intermediate.val.ec2.loc8_3 = internal constant { ptr, {} } { ptr @"_CIntermediate.Classes.$vtable", {} zeroinitializer }
-// CHECK:STDOUT: @Derived.val.loc9_3 = internal constant { { ptr, {} } } { { ptr, {} } { ptr @"_CDerived.Classes.$vtable", {} zeroinitializer } }
+// CHECK:STDOUT: @Derived.val.loc9_49.5 = internal constant { { ptr, {} } } { { ptr, {} } { ptr poison, {} zeroinitializer } }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: nounwind
 // CHECK:STDOUT: ; Function Attrs: nounwind
 // CHECK:STDOUT: define void @_CCreate.Create() #0 !dbg !4 {
 // CHECK:STDOUT: define void @_CCreate.Create() #0 !dbg !4 {
@@ -218,7 +248,10 @@ fn Make() {
 // CHECK:STDOUT:   %.loc9_49.2.base = getelementptr inbounds nuw { { ptr, {} } }, ptr %_.var.loc9, i32 0, i32 0, !dbg !12
 // CHECK:STDOUT:   %.loc9_49.2.base = getelementptr inbounds nuw { { ptr, {} } }, ptr %_.var.loc9, i32 0, i32 0, !dbg !12
 // CHECK:STDOUT:   %.loc9_48.2.vptr = getelementptr inbounds nuw { ptr, {} }, ptr %.loc9_49.2.base, i32 0, i32 0, !dbg !13
 // CHECK:STDOUT:   %.loc9_48.2.vptr = getelementptr inbounds nuw { ptr, {} }, ptr %.loc9_49.2.base, i32 0, i32 0, !dbg !13
 // CHECK:STDOUT:   %.loc9_48.4.base = getelementptr inbounds nuw { ptr, {} }, ptr %.loc9_49.2.base, i32 0, i32 1, !dbg !13
 // CHECK:STDOUT:   %.loc9_48.4.base = getelementptr inbounds nuw { ptr, {} }, ptr %.loc9_49.2.base, i32 0, i32 1, !dbg !13
-// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 8 %_.var.loc9, ptr align 8 @Derived.val.loc9_3, i64 8, i1 false), !dbg !9
+// CHECK:STDOUT:   %.loc9_49.6.base = getelementptr inbounds nuw { { ptr, {} } }, ptr %_.var.loc9, i32 0, i32 0, !dbg !12
+// CHECK:STDOUT:   %.loc9_49.7.vptr = getelementptr inbounds nuw { ptr, {} }, ptr %.loc9_49.6.base, i32 0, i32 0, !dbg !12
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 8 %_.var.loc9, ptr align 8 @Derived.val.loc9_49.5, i64 8, i1 false), !dbg !12
+// CHECK:STDOUT:   store ptr @"_CDerived.Classes.$vtable", ptr %.loc9_49.7.vptr, align 8, !dbg !12
 // CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %_.var.loc12), !dbg !10
 // CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %_.var.loc12), !dbg !10
 // CHECK:STDOUT:   ret void, !dbg !14
 // CHECK:STDOUT:   ret void, !dbg !14
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
@@ -280,6 +313,108 @@ fn Make() {
 // CHECK:STDOUT: !20 = !DILocalVariable(arg: 1, scope: !15, type: !18)
 // CHECK:STDOUT: !20 = !DILocalVariable(arg: 1, scope: !15, type: !18)
 // CHECK:STDOUT: !21 = !DILocation(line: 16, column: 3, scope: !15)
 // CHECK:STDOUT: !21 = !DILocation(line: 16, column: 3, scope: !15)
 // CHECK:STDOUT: !22 = !DILocation(line: 15, column: 1, scope: !15)
 // CHECK:STDOUT: !22 = !DILocation(line: 15, column: 1, scope: !15)
+// CHECK:STDOUT: ; ModuleID = 'create_named_ctor.carbon'
+// CHECK:STDOUT: source_filename = "create_named_ctor.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: @"_CIntermediate.Classes.$vtable" = external unnamed_addr constant ptr
+// CHECK:STDOUT: @"_CDerived.Classes.$vtable" = external unnamed_addr constant ptr
+// CHECK:STDOUT: @empty_struct.d84.loc7_12 = internal constant {} zeroinitializer
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define void @_CMakePartialBase.Main(ptr sret({}) %return) #0 !dbg !4 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 1 %return, ptr align 1 @empty_struct.d84.loc7_12, i64 0, i1 false), !dbg !8
+// CHECK:STDOUT:   ret void, !dbg !8
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define void @_CMakePartialIntermediate.Main(ptr sret({ ptr, {} }) %return) #0 !dbg !9 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %.loc11_36.1.base = getelementptr inbounds nuw { ptr, {} }, ptr %return, i32 0, i32 1, !dbg !10
+// CHECK:STDOUT:   call void @_CMakePartialBase.Main(ptr %.loc11_36.1.base), !dbg !11
+// CHECK:STDOUT:   %.loc11_36.3.vptr = getelementptr inbounds nuw { ptr, {} }, ptr %return, i32 0, i32 0, !dbg !10
+// CHECK:STDOUT:   store ptr poison, ptr %.loc11_36.3.vptr, align 8, !dbg !10
+// CHECK:STDOUT:   ret void, !dbg !12
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define void @_CMakeBase.Main(ptr sret({}) %return) #0 !dbg !13 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   call void @_CMakePartialBase.Main(ptr %return), !dbg !14
+// CHECK:STDOUT:   ret void, !dbg !15
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define void @_CMakeIntermediate.Main(ptr sret({ ptr, {} }) %return) #0 !dbg !16 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   call void @_CMakePartialIntermediate.Main(ptr %return), !dbg !17
+// CHECK:STDOUT:   %.loc19_34.1.vptr = getelementptr inbounds nuw { ptr, {} }, ptr %return, i32 0, i32 0, !dbg !17
+// CHECK:STDOUT:   store ptr @"_CIntermediate.Classes.$vtable", ptr %.loc19_34.1.vptr, align 8, !dbg !17
+// CHECK:STDOUT:   ret void, !dbg !18
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare void @_CFn.Intermediate.Classes(ptr)
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define void @_CMakeDerivedFromPartial.Main(ptr sret({ { ptr, {} } }) %return) #0 !dbg !19 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %.loc23_44.1.base = getelementptr inbounds nuw { { ptr, {} } }, ptr %return, i32 0, i32 0, !dbg !20
+// CHECK:STDOUT:   call void @_CMakePartialIntermediate.Main(ptr %.loc23_44.1.base), !dbg !21
+// CHECK:STDOUT:   %.loc23_44.5.base = getelementptr inbounds nuw { { ptr, {} } }, ptr %return, i32 0, i32 0, !dbg !20
+// CHECK:STDOUT:   %.loc23_44.6.vptr = getelementptr inbounds nuw { ptr, {} }, ptr %.loc23_44.5.base, i32 0, i32 0, !dbg !20
+// CHECK:STDOUT:   store ptr @"_CDerived.Classes.$vtable", ptr %.loc23_44.6.vptr, align 8, !dbg !20
+// CHECK:STDOUT:   ret void, !dbg !22
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare void @_CFn.Derived.Classes(ptr)
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define void @_CMakeDerivedFromNonPartial.Main(ptr sret({ { ptr, {} } }) %return) #0 !dbg !23 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %.loc27_37.1.base = getelementptr inbounds nuw { { ptr, {} } }, ptr %return, i32 0, i32 0, !dbg !24
+// CHECK:STDOUT:   call void @_CMakeIntermediate.Main(ptr %.loc27_37.1.base), !dbg !25
+// CHECK:STDOUT:   %.loc27_37.7.base = getelementptr inbounds nuw { { ptr, {} } }, ptr %return, i32 0, i32 0, !dbg !24
+// CHECK:STDOUT:   %.loc27_37.8.vptr = getelementptr inbounds nuw { ptr, {} }, ptr %.loc27_37.7.base, i32 0, i32 0, !dbg !24
+// CHECK:STDOUT:   store ptr @"_CDerived.Classes.$vtable", ptr %.loc27_37.8.vptr, align 8, !dbg !24
+// CHECK:STDOUT:   ret void, !dbg !26
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.memcpy.p0.p0.i64(ptr noalias writeonly captures(none), ptr noalias readonly captures(none), i64, i1 immarg) #1
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nounwind }
+// CHECK:STDOUT: attributes #1 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }
+// CHECK:STDOUT:
+// CHECK:STDOUT: !llvm.module.flags = !{!0, !1}
+// CHECK:STDOUT: !llvm.dbg.cu = !{!2}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
+// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
+// CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !3 = !DIFile(filename: "create_named_ctor.carbon", directory: "")
+// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "MakePartialBase", linkageName: "_CMakePartialBase.Main", scope: null, file: !3, line: 6, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
+// CHECK:STDOUT: !6 = !{!7}
+// CHECK:STDOUT: !7 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: null, size: 8)
+// CHECK:STDOUT: !8 = !DILocation(line: 7, column: 3, scope: !4)
+// CHECK:STDOUT: !9 = distinct !DISubprogram(name: "MakePartialIntermediate", linkageName: "_CMakePartialIntermediate.Main", scope: null, file: !3, line: 10, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !10 = !DILocation(line: 11, column: 10, scope: !9)
+// CHECK:STDOUT: !11 = !DILocation(line: 11, column: 19, scope: !9)
+// CHECK:STDOUT: !12 = !DILocation(line: 11, column: 3, scope: !9)
+// CHECK:STDOUT: !13 = distinct !DISubprogram(name: "MakeBase", linkageName: "_CMakeBase.Main", scope: null, file: !3, line: 14, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !14 = !DILocation(line: 15, column: 10, scope: !13)
+// CHECK:STDOUT: !15 = !DILocation(line: 15, column: 3, scope: !13)
+// CHECK:STDOUT: !16 = distinct !DISubprogram(name: "MakeIntermediate", linkageName: "_CMakeIntermediate.Main", scope: null, file: !3, line: 18, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !17 = !DILocation(line: 19, column: 10, scope: !16)
+// CHECK:STDOUT: !18 = !DILocation(line: 19, column: 3, scope: !16)
+// CHECK:STDOUT: !19 = distinct !DISubprogram(name: "MakeDerivedFromPartial", linkageName: "_CMakeDerivedFromPartial.Main", scope: null, file: !3, line: 22, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !20 = !DILocation(line: 23, column: 10, scope: !19)
+// CHECK:STDOUT: !21 = !DILocation(line: 23, column: 19, scope: !19)
+// CHECK:STDOUT: !22 = !DILocation(line: 23, column: 3, scope: !19)
+// CHECK:STDOUT: !23 = distinct !DISubprogram(name: "MakeDerivedFromNonPartial", linkageName: "_CMakeDerivedFromNonPartial.Main", scope: null, file: !3, line: 26, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !24 = !DILocation(line: 27, column: 10, scope: !23)
+// CHECK:STDOUT: !25 = !DILocation(line: 27, column: 19, scope: !23)
+// CHECK:STDOUT: !26 = !DILocation(line: 27, column: 3, scope: !23)
 // CHECK:STDOUT: ; ModuleID = 'member_init.carbon'
 // CHECK:STDOUT: ; ModuleID = 'member_init.carbon'
 // CHECK:STDOUT: source_filename = "member_init.carbon"
 // CHECK:STDOUT: source_filename = "member_init.carbon"
 // CHECK:STDOUT:
 // CHECK:STDOUT:
@@ -358,7 +493,7 @@ fn Make() {
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: @"_CBase.Main.$vtable" = unnamed_addr constant [1 x i32] [i32 trunc (i64 sub (i64 ptrtoint (ptr @_CF.Base.Main to i64), i64 ptrtoint (ptr @"_CBase.Main.$vtable" to i64)) to i32)]
 // CHECK:STDOUT: @"_CBase.Main.$vtable" = unnamed_addr constant [1 x i32] [i32 trunc (i64 sub (i64 ptrtoint (ptr @_CF.Base.Main to i64), i64 ptrtoint (ptr @"_CBase.Main.$vtable" to i64)) to i32)]
 // CHECK:STDOUT: @"_CDerived.Main.$vtable" = unnamed_addr constant [1 x i32] [i32 trunc (i64 sub (i64 ptrtoint (ptr @_CF.Base.Main to i64), i64 ptrtoint (ptr @"_CDerived.Main.$vtable" to i64)) to i32)]
 // CHECK:STDOUT: @"_CDerived.Main.$vtable" = unnamed_addr constant [1 x i32] [i32 trunc (i64 sub (i64 ptrtoint (ptr @_CF.Base.Main to i64), i64 ptrtoint (ptr @"_CDerived.Main.$vtable" to i64)) to i32)]
-// CHECK:STDOUT: @Derived.val.loc13_3 = internal constant { { ptr } } { { ptr } { ptr @"_CDerived.Main.$vtable" } }
+// CHECK:STDOUT: @Derived.val.loc13_32.5 = internal constant { { ptr } } poison
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: declare void @_CF.Base.Main(ptr)
 // CHECK:STDOUT: declare void @_CF.Base.Main(ptr)
 // CHECK:STDOUT:
 // CHECK:STDOUT:
@@ -369,7 +504,10 @@ fn Make() {
 // CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %_.var), !dbg !7
 // CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %_.var), !dbg !7
 // CHECK:STDOUT:   %.loc13_32.2.base = getelementptr inbounds nuw { { ptr } }, ptr %_.var, i32 0, i32 0, !dbg !8
 // CHECK:STDOUT:   %.loc13_32.2.base = getelementptr inbounds nuw { { ptr } }, ptr %_.var, i32 0, i32 0, !dbg !8
 // CHECK:STDOUT:   %.loc13_31.2.vptr = getelementptr inbounds nuw { ptr }, ptr %.loc13_32.2.base, i32 0, i32 0, !dbg !9
 // CHECK:STDOUT:   %.loc13_31.2.vptr = getelementptr inbounds nuw { ptr }, ptr %.loc13_32.2.base, i32 0, i32 0, !dbg !9
-// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 8 %_.var, ptr align 8 @Derived.val.loc13_3, i64 8, i1 false), !dbg !7
+// CHECK:STDOUT:   %.loc13_32.6.base = getelementptr inbounds nuw { { ptr } }, ptr %_.var, i32 0, i32 0, !dbg !8
+// CHECK:STDOUT:   %.loc13_32.7.vptr = getelementptr inbounds nuw { ptr }, ptr %.loc13_32.6.base, i32 0, i32 0, !dbg !8
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 8 %_.var, ptr align 8 @Derived.val.loc13_32.5, i64 8, i1 false), !dbg !8
+// CHECK:STDOUT:   store ptr @"_CDerived.Main.$vtable", ptr %.loc13_32.7.vptr, align 8, !dbg !8
 // CHECK:STDOUT:   ret void, !dbg !10
 // CHECK:STDOUT:   ret void, !dbg !10
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
@@ -439,7 +577,7 @@ fn Make() {
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: @"_CBase.Main.$vtable" = unnamed_addr constant [1 x i32] [i32 trunc (i64 sub (i64 ptrtoint (ptr @_CF.Base.Main to i64), i64 ptrtoint (ptr @"_CBase.Main.$vtable" to i64)) to i32)]
 // CHECK:STDOUT: @"_CBase.Main.$vtable" = unnamed_addr constant [1 x i32] [i32 trunc (i64 sub (i64 ptrtoint (ptr @_CF.Base.Main to i64), i64 ptrtoint (ptr @"_CBase.Main.$vtable" to i64)) to i32)]
 // CHECK:STDOUT: @"_CDerived.Main.$vtable" = unnamed_addr constant [1 x i32] [i32 trunc (i64 sub (i64 ptrtoint (ptr @_CF.Derived.Main to i64), i64 ptrtoint (ptr @"_CDerived.Main.$vtable" to i64)) to i32)]
 // CHECK:STDOUT: @"_CDerived.Main.$vtable" = unnamed_addr constant [1 x i32] [i32 trunc (i64 sub (i64 ptrtoint (ptr @_CF.Derived.Main to i64), i64 ptrtoint (ptr @"_CDerived.Main.$vtable" to i64)) to i32)]
-// CHECK:STDOUT: @Derived.val.loc14_3 = internal constant { { ptr } } { { ptr } { ptr @"_CDerived.Main.$vtable" } }
+// CHECK:STDOUT: @Derived.val.loc14_32.5 = internal constant { { ptr } } poison
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: declare void @_CF.Base.Main(ptr)
 // CHECK:STDOUT: declare void @_CF.Base.Main(ptr)
 // CHECK:STDOUT:
 // CHECK:STDOUT:
@@ -452,7 +590,10 @@ fn Make() {
 // CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %v.var), !dbg !7
 // CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %v.var), !dbg !7
 // CHECK:STDOUT:   %.loc14_32.2.base = getelementptr inbounds nuw { { ptr } }, ptr %v.var, i32 0, i32 0, !dbg !8
 // CHECK:STDOUT:   %.loc14_32.2.base = getelementptr inbounds nuw { { ptr } }, ptr %v.var, i32 0, i32 0, !dbg !8
 // CHECK:STDOUT:   %.loc14_31.2.vptr = getelementptr inbounds nuw { ptr }, ptr %.loc14_32.2.base, i32 0, i32 0, !dbg !9
 // CHECK:STDOUT:   %.loc14_31.2.vptr = getelementptr inbounds nuw { ptr }, ptr %.loc14_32.2.base, i32 0, i32 0, !dbg !9
-// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 8 %v.var, ptr align 8 @Derived.val.loc14_3, i64 8, i1 false), !dbg !7
+// CHECK:STDOUT:   %.loc14_32.6.base = getelementptr inbounds nuw { { ptr } }, ptr %v.var, i32 0, i32 0, !dbg !8
+// CHECK:STDOUT:   %.loc14_32.7.vptr = getelementptr inbounds nuw { ptr }, ptr %.loc14_32.6.base, i32 0, i32 0, !dbg !8
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 8 %v.var, ptr align 8 @Derived.val.loc14_32.5, i64 8, i1 false), !dbg !8
+// CHECK:STDOUT:   store ptr @"_CDerived.Main.$vtable", ptr %.loc14_32.7.vptr, align 8, !dbg !8
 // CHECK:STDOUT:   %Derived.F.call.vtable = load ptr, ptr %v.var, align 8, !dbg !10
 // CHECK:STDOUT:   %Derived.F.call.vtable = load ptr, ptr %v.var, align 8, !dbg !10
 // CHECK:STDOUT:   %Derived.F.call = call ptr @llvm.load.relative.i32(ptr %Derived.F.call.vtable, i32 0), !dbg !10
 // CHECK:STDOUT:   %Derived.F.call = call ptr @llvm.load.relative.i32(ptr %Derived.F.call.vtable, i32 0), !dbg !10
 // CHECK:STDOUT:   call void %Derived.F.call(ptr %v.var), !dbg !10
 // CHECK:STDOUT:   call void %Derived.F.call(ptr %v.var), !dbg !10

+ 7 - 0
toolchain/sem_ir/expr_info.cpp

@@ -178,6 +178,13 @@ auto FindStorageArgForInitializer(const File& sem_ir, InstId init_id,
         init_id = init.result_id;
         init_id = init.result_id;
         continue;
         continue;
       }
       }
+      case CARBON_KIND(UpdateInit init): {
+        if (!allow_transitive) {
+          return InstId::None;
+        }
+        init_id = init.base_init_id;
+        continue;
+      }
       case CARBON_KIND(ArrayInit init): {
       case CARBON_KIND(ArrayInit init): {
         return init.dest_id;
         return init.dest_id;
       }
       }

+ 1 - 0
toolchain/sem_ir/inst_kind.def

@@ -161,6 +161,7 @@ CARBON_SEM_IR_INST_KIND(TypeType)
 CARBON_SEM_IR_INST_KIND(UnaryOperatorNot)
 CARBON_SEM_IR_INST_KIND(UnaryOperatorNot)
 CARBON_SEM_IR_INST_KIND(UnboundElementType)
 CARBON_SEM_IR_INST_KIND(UnboundElementType)
 CARBON_SEM_IR_INST_KIND(UninitializedValue)
 CARBON_SEM_IR_INST_KIND(UninitializedValue)
+CARBON_SEM_IR_INST_KIND(UpdateInit)
 CARBON_SEM_IR_INST_KIND(ValueAsRef)
 CARBON_SEM_IR_INST_KIND(ValueAsRef)
 CARBON_SEM_IR_INST_KIND(ValueBinding)
 CARBON_SEM_IR_INST_KIND(ValueBinding)
 CARBON_SEM_IR_INST_KIND(ValueBindingPattern)
 CARBON_SEM_IR_INST_KIND(ValueBindingPattern)

+ 14 - 0
toolchain/sem_ir/typed_insts.h

@@ -2092,6 +2092,20 @@ struct UninitializedValue {
   TypeId type_id;
   TypeId type_id;
 };
 };
 
 
+// Initializes an object by performing a base initialization followed by an
+// update step.
+struct UpdateInit {
+  static constexpr auto Kind = InstKind::UpdateInit.Define<Parse::NodeId>(
+      {.ir_name = "update_init",
+       .expr_category = ExprCategory::InPlaceInitializing});
+
+  TypeId type_id;
+  // The base initializer. Always an in-place initializer.
+  InstId base_init_id;
+  // The update expression. Always an in-place initializer.
+  InstId update_init_id;
+};
+
 // Converts from a value expression to an ephemeral reference expression, in
 // Converts from a value expression to an ephemeral reference expression, in
 // the case where the value representation of the type is a pointer. For
 // the case where the value representation of the type is a pointer. For
 // example, when indexing a value expression of array type, this is used to
 // example, when indexing a value expression of array type, this is used to