Browse Source

lazy field index (#4514)

We considered a couple of other options for this:
* https://github.com/carbon-language/carbon-lang/pull/4515 Keep the
`ElementIndex` numbering vptr-ignorant, and do +1 offsets as needed -
seems subtle/easy to miss
* https://github.com/carbon-language/carbon-lang/pull/4517 Always have a
zeroth element in the object representation, make it zero-size in the
case of no-vptr - @zygoloid was concerned this would add overhead
especially to stateless objects used in type-trait-like things.

But currently moving forward with this direction - of initializing field
indexes with an invalid value until the end of the class definition,
then assigning field indexes during construction of the class's object
representation struct type. This direction might reinforce/help avoid
premature access to the object representation before the class is
complete, and give a single place where class layout is done (at class
completion) if we want to add more options there, such as class layout
optimizations, etc.

This patch still has problems with object initialization (that #4515
does not have/does address) but does address normal `obj.member` access
correctly.

---------

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
David Blaikie 1 year ago
parent
commit
f921923b4b

+ 9 - 0
toolchain/check/context.cpp

@@ -199,6 +199,15 @@ auto Context::ReplaceInstBeforeConstantUse(SemIR::InstId inst_id,
   FinishInst(inst_id, inst);
   FinishInst(inst_id, inst);
 }
 }
 
 
+auto Context::ReplaceInstPreservingConstantValue(SemIR::InstId inst_id,
+                                                 SemIR::Inst inst) -> void {
+  auto old_const_id = sem_ir().constant_values().Get(inst_id);
+  sem_ir().insts().Set(inst_id, inst);
+  CARBON_VLOG("ReplaceInst: {0} -> {1}\n", inst_id, inst);
+  auto new_const_id = TryEvalInst(*this, inst_id, inst);
+  CARBON_CHECK(old_const_id == new_const_id);
+}
+
 auto Context::DiagnoseDuplicateName(SemIRLoc dup_def, SemIRLoc prev_def)
 auto Context::DiagnoseDuplicateName(SemIRLoc dup_def, SemIRLoc prev_def)
     -> void {
     -> void {
   CARBON_DIAGNOSTIC(NameDeclDuplicate, Error,
   CARBON_DIAGNOSTIC(NameDeclDuplicate, Error,

+ 13 - 2
toolchain/check/context.h

@@ -194,6 +194,11 @@ class Context {
   auto ReplaceInstBeforeConstantUse(SemIR::InstId inst_id, SemIR::Inst inst)
   auto ReplaceInstBeforeConstantUse(SemIR::InstId inst_id, SemIR::Inst inst)
       -> void;
       -> void;
 
 
+  // Replaces the instruction `inst_id` with `inst`, not affecting location.
+  // The instruction is required to not change its constant value.
+  auto ReplaceInstPreservingConstantValue(SemIR::InstId inst_id,
+                                          SemIR::Inst inst) -> void;
+
   // Sets only the parse node of an instruction. This is only used when setting
   // Sets only the parse node of an instruction. This is only used when setting
   // the parse node of an imported namespace. Versus
   // the parse node of an imported namespace. Versus
   // ReplaceInstBeforeConstantUse, it is safe to use after the namespace is used
   // ReplaceInstBeforeConstantUse, it is safe to use after the namespace is used
@@ -492,6 +497,10 @@ class Context {
     return struct_type_fields_stack_;
     return struct_type_fields_stack_;
   }
   }
 
 
+  auto field_decls_stack() -> ArrayStack<SemIR::InstId>& {
+    return field_decls_stack_;
+  }
+
   auto decl_name_stack() -> DeclNameStack& { return decl_name_stack_; }
   auto decl_name_stack() -> DeclNameStack& { return decl_name_stack_; }
 
 
   auto decl_introducer_state_stack() -> DeclIntroducerStateStack& {
   auto decl_introducer_state_stack() -> DeclIntroducerStateStack& {
@@ -648,10 +657,12 @@ class Context {
   // arguments.
   // arguments.
   InstBlockStack args_type_info_stack_;
   InstBlockStack args_type_info_stack_;
 
 
-  // The stack of StructTypeFields for in-progress StructTypeLiterals and Class
-  // object representations.
+  // The stack of StructTypeFields for in-progress StructTypeLiterals.
   ArrayStack<SemIR::StructTypeField> struct_type_fields_stack_;
   ArrayStack<SemIR::StructTypeField> struct_type_fields_stack_;
 
 
+  // The stack of FieldDecls for in-progress Class definitions.
+  ArrayStack<SemIR::InstId> field_decls_stack_;
+
   // The stack used for qualified declaration name construction.
   // The stack used for qualified declaration name construction.
   DeclNameStack decl_name_stack_;
   DeclNameStack decl_name_stack_;
 
 

+ 4 - 8
toolchain/check/handle_binding_pattern.cpp

@@ -128,15 +128,11 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id,
         auto field_type_id = context.GetUnboundElementType(
         auto field_type_id = context.GetUnboundElementType(
             class_info.self_type_id, cast_type_id);
             class_info.self_type_id, cast_type_id);
         auto field_id = context.AddInst<SemIR::FieldDecl>(
         auto field_id = context.AddInst<SemIR::FieldDecl>(
-            binding_id,
-            {.type_id = field_type_id,
-             .name_id = name_id,
-             .index = SemIR::ElementIndex(
-                 context.struct_type_fields_stack().PeekArray().size())});
+            binding_id, {.type_id = field_type_id,
+                         .name_id = name_id,
+                         .index = SemIR::ElementIndex::Invalid});
+        context.field_decls_stack().AppendToTop(field_id);
 
 
-        // Add a corresponding field to the object representation of the class.
-        context.struct_type_fields_stack().AppendToTop(
-            {.name_id = name_id, .type_id = cast_type_id});
         context.node_stack().Push(node_id, field_id);
         context.node_stack().Push(node_id, field_id);
         break;
         break;
       }
       }

+ 57 - 32
toolchain/check/handle_class.cpp

@@ -295,7 +295,7 @@ auto HandleParseNode(Context& context, Parse::ClassDefinitionStartId node_id)
 
 
   context.inst_block_stack().Push();
   context.inst_block_stack().Push();
   context.node_stack().Push(node_id, class_id);
   context.node_stack().Push(node_id, class_id);
-  context.struct_type_fields_stack().PushArray();
+  context.field_decls_stack().PushArray();
 
 
   // TODO: Handle the case where there's control flow in the class body. For
   // TODO: Handle the case where there's control flow in the class body. For
   // example:
   // example:
@@ -512,7 +512,7 @@ auto HandleParseNode(Context& context, Parse::BaseDeclId node_id) -> bool {
     return true;
     return true;
   }
   }
 
 
-  if (!context.struct_type_fields_stack().PeekArray().empty()) {
+  if (!context.field_decls_stack().PeekArray().empty()) {
     // TODO: Add note that includes the first field location as an example.
     // TODO: Add note that includes the first field location as an example.
     CARBON_DIAGNOSTIC(
     CARBON_DIAGNOSTIC(
         BaseDeclAfterFieldDecl, Error,
         BaseDeclAfterFieldDecl, Error,
@@ -523,6 +523,8 @@ auto HandleParseNode(Context& context, Parse::BaseDeclId node_id) -> bool {
 
 
   auto base_info = CheckBaseType(context, base_type_node_id, base_type_expr_id);
   auto base_info = CheckBaseType(context, base_type_node_id, base_type_expr_id);
 
 
+  // TODO: Should we diagnose if there are already any fields?
+
   // The `base` value in the class scope has an unbound element type. Instance
   // The `base` value in the class scope has an unbound element type. Instance
   // binding will be performed when it's found by name lookup into an instance.
   // binding will be performed when it's found by name lookup into an instance.
   auto field_type_id =
   auto field_type_id =
@@ -530,8 +532,7 @@ auto HandleParseNode(Context& context, Parse::BaseDeclId node_id) -> bool {
   class_info.base_id = context.AddInst<SemIR::BaseDecl>(
   class_info.base_id = context.AddInst<SemIR::BaseDecl>(
       node_id, {.type_id = field_type_id,
       node_id, {.type_id = field_type_id,
                 .base_type_id = base_info.type_id,
                 .base_type_id = base_info.type_id,
-                .index = SemIR::ElementIndex(
-                    context.struct_type_fields_stack().PeekArray().size())});
+                .index = SemIR::ElementIndex::Invalid});
 
 
   if (base_info.type_id != SemIR::TypeId::Error) {
   if (base_info.type_id != SemIR::TypeId::Error) {
     auto base_class_info = context.classes().Get(
     auto base_class_info = context.classes().Get(
@@ -539,12 +540,6 @@ auto HandleParseNode(Context& context, Parse::BaseDeclId node_id) -> bool {
     class_info.is_dynamic |= base_class_info.is_dynamic;
     class_info.is_dynamic |= base_class_info.is_dynamic;
   }
   }
 
 
-  // Add a corresponding field to the object representation of the class.
-  // TODO: Consider whether we want to use `partial T` here.
-  // TODO: Should we diagnose if there are already any fields?
-  context.struct_type_fields_stack().AppendToTop(
-      {.name_id = SemIR::NameId::Base, .type_id = base_info.type_id});
-
   // Bind the name `base` in the class to the base field.
   // Bind the name `base` in the class to the base field.
   context.decl_name_stack().AddNameOrDiagnoseDuplicate(
   context.decl_name_stack().AddNameOrDiagnoseDuplicate(
       context.decl_name_stack().MakeUnqualifiedName(node_id,
       context.decl_name_stack().MakeUnqualifiedName(node_id,
@@ -567,8 +562,7 @@ auto HandleParseNode(Context& context, Parse::BaseDeclId node_id) -> bool {
 // returns a corresponding complete type witness instruction.
 // returns a corresponding complete type witness instruction.
 static auto CheckCompleteAdapterClassType(Context& context,
 static auto CheckCompleteAdapterClassType(Context& context,
                                           Parse::NodeId node_id,
                                           Parse::NodeId node_id,
-                                          SemIR::ClassId class_id,
-                                          SemIR::StructTypeFieldsId fields_id)
+                                          SemIR::ClassId class_id)
     -> SemIR::InstId {
     -> SemIR::InstId {
   const auto& class_info = context.classes().Get(class_id);
   const auto& class_info = context.classes().Get(class_id);
   if (class_info.base_id.is_valid()) {
   if (class_info.base_id.is_valid()) {
@@ -581,17 +575,14 @@ static auto CheckCompleteAdapterClassType(Context& context,
     return SemIR::InstId::BuiltinErrorInst;
     return SemIR::InstId::BuiltinErrorInst;
   }
   }
 
 
-  if (auto fields = context.struct_type_fields().Get(fields_id);
-      !fields.empty()) {
-    auto [first_field_inst_id, _] = context.LookupNameInExactScope(
-        node_id, fields.front().name_id, class_info.scope_id,
-        context.name_scopes().Get(class_info.scope_id));
+  auto field_decls = context.field_decls_stack().PeekArray();
+  if (!field_decls.empty()) {
     CARBON_DIAGNOSTIC(AdaptWithFields, Error, "adapter with fields");
     CARBON_DIAGNOSTIC(AdaptWithFields, Error, "adapter with fields");
     CARBON_DIAGNOSTIC(AdaptWithFieldHere, Note,
     CARBON_DIAGNOSTIC(AdaptWithFieldHere, Note,
                       "first field declaration is here");
                       "first field declaration is here");
     context.emitter()
     context.emitter()
         .Build(class_info.adapt_id, AdaptWithFields)
         .Build(class_info.adapt_id, AdaptWithFields)
-        .Note(first_field_inst_id, AdaptWithFieldHere)
+        .Note(field_decls.front(), AdaptWithFieldHere)
         .Emit();
         .Emit();
     return SemIR::InstId::BuiltinErrorInst;
     return SemIR::InstId::BuiltinErrorInst;
   }
   }
@@ -637,6 +628,31 @@ static auto CheckCompleteAdapterClassType(Context& context,
       {.type_id = context.GetBuiltinType(SemIR::BuiltinInstKind::WitnessType),
       {.type_id = context.GetBuiltinType(SemIR::BuiltinInstKind::WitnessType),
        .object_repr_id = adapted_type_id});
        .object_repr_id = adapted_type_id});
 }
 }
+static auto AddStructTypeFields(
+    Context& context,
+    llvm::SmallVector<SemIR::StructTypeField>& struct_type_fields)
+    -> SemIR::StructTypeFieldsId {
+  for (auto field_decl_id : context.field_decls_stack().PeekArray()) {
+    auto field_decl = context.insts().GetAs<SemIR::FieldDecl>(field_decl_id);
+    field_decl.index =
+        SemIR::ElementIndex{static_cast<int>(struct_type_fields.size())};
+    context.ReplaceInstPreservingConstantValue(field_decl_id, field_decl);
+    if (field_decl.type_id == SemIR::TypeId::Error) {
+      struct_type_fields.push_back(
+          {.name_id = field_decl.name_id, .type_id = SemIR::TypeId::Error});
+      continue;
+    }
+    auto unbound_element_type =
+        context.sem_ir().types().GetAs<SemIR::UnboundElementType>(
+            field_decl.type_id);
+    struct_type_fields.push_back(
+        {.name_id = field_decl.name_id,
+         .type_id = unbound_element_type.element_type_id});
+  }
+  auto fields_id =
+      context.struct_type_fields().AddCanonical(struct_type_fields);
+  return fields_id;
+}
 
 
 // Checks that the specified finished class definition is valid and builds and
 // Checks that the specified finished class definition is valid and builds and
 // returns a corresponding complete type witness instruction.
 // returns a corresponding complete type witness instruction.
@@ -644,39 +660,47 @@ static auto CheckCompleteClassType(Context& context, Parse::NodeId node_id,
                                    SemIR::ClassId class_id) -> SemIR::InstId {
                                    SemIR::ClassId class_id) -> SemIR::InstId {
   auto& class_info = context.classes().Get(class_id);
   auto& class_info = context.classes().Get(class_id);
   if (class_info.adapt_id.is_valid()) {
   if (class_info.adapt_id.is_valid()) {
-    auto fields_id = context.struct_type_fields().AddCanonical(
-        context.struct_type_fields_stack().PeekArray());
-    context.struct_type_fields_stack().PopArray();
-
-    return CheckCompleteAdapterClassType(context, node_id, class_id, fields_id);
+    return CheckCompleteAdapterClassType(context, node_id, class_id);
   }
   }
 
 
-  bool defining_vtable_ptr = class_info.is_dynamic;
+  bool defining_vptr = class_info.is_dynamic;
   if (class_info.base_id.is_valid()) {
   if (class_info.base_id.is_valid()) {
     auto base_info = context.insts().GetAs<SemIR::BaseDecl>(class_info.base_id);
     auto base_info = context.insts().GetAs<SemIR::BaseDecl>(class_info.base_id);
     // TODO: If the base class is template dependent, we will need to decide
     // TODO: If the base class is template dependent, we will need to decide
     // whether to add a vptr as part of instantiation.
     // whether to add a vptr as part of instantiation.
     if (auto* base_class_info = TryGetAsClass(context, base_info.base_type_id);
     if (auto* base_class_info = TryGetAsClass(context, base_info.base_type_id);
         base_class_info && base_class_info->is_dynamic) {
         base_class_info && base_class_info->is_dynamic) {
-      defining_vtable_ptr = false;
+      defining_vptr = false;
     }
     }
   }
   }
 
 
-  if (defining_vtable_ptr) {
-    context.struct_type_fields_stack().PrependToTop(
+  auto field_decls = context.field_decls_stack().PeekArray();
+  llvm::SmallVector<SemIR::StructTypeField> struct_type_fields;
+  struct_type_fields.reserve(defining_vptr + class_info.base_id.is_valid() +
+                             field_decls.size());
+  if (defining_vptr) {
+    struct_type_fields.push_back(
         {.name_id = SemIR::NameId::Vptr,
         {.name_id = SemIR::NameId::Vptr,
          .type_id = context.GetPointerType(
          .type_id = context.GetPointerType(
              context.GetBuiltinType(SemIR::BuiltinInstKind::VtableType))});
              context.GetBuiltinType(SemIR::BuiltinInstKind::VtableType))});
   }
   }
-
-  auto fields_id = context.struct_type_fields().AddCanonical(
-      context.struct_type_fields_stack().PeekArray());
-  context.struct_type_fields_stack().PopArray();
+  if (class_info.base_id.is_valid()) {
+    auto base_decl = context.insts().GetAs<SemIR::BaseDecl>(class_info.base_id);
+    base_decl.index =
+        SemIR::ElementIndex{static_cast<int>(struct_type_fields.size())};
+    context.ReplaceInstPreservingConstantValue(class_info.base_id, base_decl);
+    struct_type_fields.push_back(
+        {.name_id = SemIR::NameId::Base,
+         .type_id = context.insts()
+                        .GetAs<SemIR::BaseDecl>(class_info.base_id)
+                        .base_type_id});
+  }
 
 
   return context.AddInst<SemIR::CompleteTypeWitness>(
   return context.AddInst<SemIR::CompleteTypeWitness>(
       node_id,
       node_id,
       {.type_id = context.GetBuiltinType(SemIR::BuiltinInstKind::WitnessType),
       {.type_id = context.GetBuiltinType(SemIR::BuiltinInstKind::WitnessType),
-       .object_repr_id = context.GetStructType(fields_id)});
+       .object_repr_id = context.GetStructType(
+           AddStructTypeFields(context, struct_type_fields))});
 }
 }
 
 
 auto HandleParseNode(Context& context, Parse::ClassDefinitionId node_id)
 auto HandleParseNode(Context& context, Parse::ClassDefinitionId node_id)
@@ -691,6 +715,7 @@ auto HandleParseNode(Context& context, Parse::ClassDefinitionId node_id)
   class_info.complete_type_witness_id = complete_type_witness_id;
   class_info.complete_type_witness_id = complete_type_witness_id;
 
 
   context.inst_block_stack().Pop();
   context.inst_block_stack().Pop();
+  context.field_decls_stack().PopArray();
 
 
   FinishGenericDefinition(context, class_info.generic_id);
   FinishGenericDefinition(context, class_info.generic_id);
 
 

+ 2 - 2
toolchain/check/handle_function.cpp

@@ -212,8 +212,8 @@ static auto BuildFunctionDecl(Context& context,
       parent_scope_inst) {
       parent_scope_inst) {
     if (auto class_decl = parent_scope_inst->TryAs<SemIR::ClassDecl>()) {
     if (auto class_decl = parent_scope_inst->TryAs<SemIR::ClassDecl>()) {
       auto& class_info = context.classes().Get(class_decl->class_id);
       auto& class_info = context.classes().Get(class_decl->class_id);
-      CARBON_CHECK(virtual_modifier != SemIR::Function::VirtualModifier::Impl ||
-                   class_info.is_dynamic);
+      // TODO: If this is an `impl` function, check there's a matching base
+      // function that's impl or virtual.
       class_info.is_dynamic = true;
       class_info.is_dynamic = true;
     }
     }
   }
   }

+ 7 - 7
toolchain/check/testdata/class/fail_adapt_with_subobjects.carbon

@@ -121,7 +121,7 @@ class AdaptWithBaseAndFields {
 // CHECK:STDOUT:   %.loc10_12.2: type = converted %int.make_type_signed, %.loc10_12.1 [template = constants.%i32]
 // CHECK:STDOUT:   %.loc10_12.2: type = converted %int.make_type_signed, %.loc10_12.1 [template = constants.%i32]
 // CHECK:STDOUT:   adapt_decl %i32
 // CHECK:STDOUT:   adapt_decl %i32
 // CHECK:STDOUT:   %Base.ref: type = name_ref Base, file.%Base.decl [template = constants.%Base]
 // CHECK:STDOUT:   %Base.ref: type = name_ref Base, file.%Base.decl [template = constants.%Base]
-// CHECK:STDOUT:   %.loc15: %.5 = base_decl %Base, element0 [template]
+// CHECK:STDOUT:   %.loc15: %.5 = base_decl %Base, element<invalid> [template]
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%AdaptWithBase
 // CHECK:STDOUT:   .Self = constants.%AdaptWithBase
@@ -171,7 +171,7 @@ class AdaptWithBaseAndFields {
 // CHECK:STDOUT:   %int.make_type_signed.loc13: init type = call constants.%Int(%.loc13_10.1) [template = constants.%i32]
 // CHECK:STDOUT:   %int.make_type_signed.loc13: init type = call constants.%Int(%.loc13_10.1) [template = constants.%i32]
 // CHECK:STDOUT:   %.loc13_10.2: type = value_of_initializer %int.make_type_signed.loc13 [template = constants.%i32]
 // CHECK:STDOUT:   %.loc13_10.2: type = value_of_initializer %int.make_type_signed.loc13 [template = constants.%i32]
 // CHECK:STDOUT:   %.loc13_10.3: type = converted %int.make_type_signed.loc13, %.loc13_10.2 [template = constants.%i32]
 // CHECK:STDOUT:   %.loc13_10.3: type = converted %int.make_type_signed.loc13, %.loc13_10.2 [template = constants.%i32]
-// CHECK:STDOUT:   %.loc13_8: %.2 = field_decl n, element0 [template]
+// CHECK:STDOUT:   %.loc13_8: %.2 = field_decl n, element<invalid> [template]
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%AdaptWithField
 // CHECK:STDOUT:   .Self = constants.%AdaptWithField
@@ -188,17 +188,17 @@ class AdaptWithBaseAndFields {
 // CHECK:STDOUT:   %int.make_type_signed.loc25: init type = call constants.%Int(%.loc25_10.1) [template = constants.%i32]
 // CHECK:STDOUT:   %int.make_type_signed.loc25: init type = call constants.%Int(%.loc25_10.1) [template = constants.%i32]
 // CHECK:STDOUT:   %.loc25_10.2: type = value_of_initializer %int.make_type_signed.loc25 [template = constants.%i32]
 // CHECK:STDOUT:   %.loc25_10.2: type = value_of_initializer %int.make_type_signed.loc25 [template = constants.%i32]
 // CHECK:STDOUT:   %.loc25_10.3: type = converted %int.make_type_signed.loc25, %.loc25_10.2 [template = constants.%i32]
 // CHECK:STDOUT:   %.loc25_10.3: type = converted %int.make_type_signed.loc25, %.loc25_10.2 [template = constants.%i32]
-// CHECK:STDOUT:   %.loc25_8: %.3 = field_decl a, element0 [template]
+// CHECK:STDOUT:   %.loc25_8: %.3 = field_decl a, element<invalid> [template]
 // CHECK:STDOUT:   %.loc26_10.1: Core.IntLiteral = int_value 32 [template = constants.%.1]
 // CHECK:STDOUT:   %.loc26_10.1: Core.IntLiteral = int_value 32 [template = constants.%.1]
 // CHECK:STDOUT:   %int.make_type_signed.loc26: init type = call constants.%Int(%.loc26_10.1) [template = constants.%i32]
 // CHECK:STDOUT:   %int.make_type_signed.loc26: init type = call constants.%Int(%.loc26_10.1) [template = constants.%i32]
 // CHECK:STDOUT:   %.loc26_10.2: type = value_of_initializer %int.make_type_signed.loc26 [template = constants.%i32]
 // CHECK:STDOUT:   %.loc26_10.2: type = value_of_initializer %int.make_type_signed.loc26 [template = constants.%i32]
 // CHECK:STDOUT:   %.loc26_10.3: type = converted %int.make_type_signed.loc26, %.loc26_10.2 [template = constants.%i32]
 // CHECK:STDOUT:   %.loc26_10.3: type = converted %int.make_type_signed.loc26, %.loc26_10.2 [template = constants.%i32]
-// CHECK:STDOUT:   %.loc26_8: %.3 = field_decl b, element1 [template]
+// CHECK:STDOUT:   %.loc26_8: %.3 = field_decl b, element<invalid> [template]
 // CHECK:STDOUT:   %.loc27_10.1: Core.IntLiteral = int_value 32 [template = constants.%.1]
 // CHECK:STDOUT:   %.loc27_10.1: Core.IntLiteral = int_value 32 [template = constants.%.1]
 // CHECK:STDOUT:   %int.make_type_signed.loc27: init type = call constants.%Int(%.loc27_10.1) [template = constants.%i32]
 // CHECK:STDOUT:   %int.make_type_signed.loc27: init type = call constants.%Int(%.loc27_10.1) [template = constants.%i32]
 // CHECK:STDOUT:   %.loc27_10.2: type = value_of_initializer %int.make_type_signed.loc27 [template = constants.%i32]
 // CHECK:STDOUT:   %.loc27_10.2: type = value_of_initializer %int.make_type_signed.loc27 [template = constants.%i32]
 // CHECK:STDOUT:   %.loc27_10.3: type = converted %int.make_type_signed.loc27, %.loc27_10.2 [template = constants.%i32]
 // CHECK:STDOUT:   %.loc27_10.3: type = converted %int.make_type_signed.loc27, %.loc27_10.2 [template = constants.%i32]
-// CHECK:STDOUT:   %.loc27_8: %.3 = field_decl c, element2 [template]
+// CHECK:STDOUT:   %.loc27_8: %.3 = field_decl c, element<invalid> [template]
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%AdaptWithFields
 // CHECK:STDOUT:   .Self = constants.%AdaptWithFields
@@ -250,12 +250,12 @@ class AdaptWithBaseAndFields {
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @AdaptWithBaseAndFields {
 // CHECK:STDOUT: class @AdaptWithBaseAndFields {
 // CHECK:STDOUT:   %Base.ref: type = name_ref Base, file.%Base.decl [template = constants.%Base]
 // CHECK:STDOUT:   %Base.ref: type = name_ref Base, file.%Base.decl [template = constants.%Base]
-// CHECK:STDOUT:   %.loc7: %.4 = base_decl %Base, element0 [template]
+// CHECK:STDOUT:   %.loc7: %.4 = base_decl %Base, element<invalid> [template]
 // CHECK:STDOUT:   %.loc8_10.1: Core.IntLiteral = int_value 32 [template = constants.%.5]
 // CHECK:STDOUT:   %.loc8_10.1: Core.IntLiteral = int_value 32 [template = constants.%.5]
 // CHECK:STDOUT:   %int.make_type_signed: init type = call constants.%Int(%.loc8_10.1) [template = constants.%i32]
 // CHECK:STDOUT:   %int.make_type_signed: init type = call constants.%Int(%.loc8_10.1) [template = constants.%i32]
 // CHECK:STDOUT:   %.loc8_10.2: type = value_of_initializer %int.make_type_signed [template = constants.%i32]
 // CHECK:STDOUT:   %.loc8_10.2: type = value_of_initializer %int.make_type_signed [template = constants.%i32]
 // CHECK:STDOUT:   %.loc8_10.3: type = converted %int.make_type_signed, %.loc8_10.2 [template = constants.%i32]
 // CHECK:STDOUT:   %.loc8_10.3: type = converted %int.make_type_signed, %.loc8_10.2 [template = constants.%i32]
-// CHECK:STDOUT:   %.loc8_8: %.6 = field_decl n, element1 [template]
+// CHECK:STDOUT:   %.loc8_8: %.6 = field_decl n, element<invalid> [template]
 // CHECK:STDOUT:   %.loc15_10: %.1 = struct_literal ()
 // CHECK:STDOUT:   %.loc15_10: %.1 = struct_literal ()
 // CHECK:STDOUT:   %.loc15_11: type = converted %.loc15_10, constants.%.1 [template = constants.%.1]
 // CHECK:STDOUT:   %.loc15_11: type = converted %.loc15_10, constants.%.1 [template = constants.%.1]
 // CHECK:STDOUT:   adapt_decl %.1
 // CHECK:STDOUT:   adapt_decl %.1

+ 164 - 47
toolchain/check/testdata/class/virtual_modifiers.carbon

@@ -52,13 +52,32 @@ fn F() {
 package InitMembers;
 package InitMembers;
 
 
 base class Base {
 base class Base {
-  virtual fn F();
   var m1: i32;
   var m1: i32;
   var m2: i32;
   var m2: i32;
+
+  virtual fn F();
 }
 }
 
 
 fn F() {
 fn F() {
-  var v: Base = {.m2 = 3, .m1 = 5};
+  var i: i32 = 3;
+  // TODO: These should initialize element1 (.m), not element0 (the vptr)
+  var b1: Base = {.m2 = i, .m1 = i};
+  var b2: Base = {.m2 = 3, .m1 = 5};
+
+  // This one is good, though.
+  b1.m2 = 4;
+}
+
+// --- todo_fail_impl_without_base_declaration.carbon
+
+package ImplWithoutBaseDeclaration;
+
+base class Base {
+}
+
+class Derived {
+  extend base: Base;
+  impl fn F();
 }
 }
 
 
 // CHECK:STDOUT: --- modifiers.carbon
 // CHECK:STDOUT: --- modifiers.carbon
@@ -253,31 +272,36 @@ fn F() {
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %Base: type = class_type @Base [template]
 // CHECK:STDOUT:   %Base: type = class_type @Base [template]
-// CHECK:STDOUT:   %F.type.1: type = fn_type @F.1 [template]
-// CHECK:STDOUT:   %F.1: %F.type.1 = struct_value () [template]
 // CHECK:STDOUT:   %.1: Core.IntLiteral = int_value 32 [template]
 // CHECK:STDOUT:   %.1: Core.IntLiteral = int_value 32 [template]
 // CHECK:STDOUT:   %Int.type: type = fn_type @Int [template]
 // CHECK:STDOUT:   %Int.type: type = fn_type @Int [template]
 // CHECK:STDOUT:   %Int: %Int.type = struct_value () [template]
 // CHECK:STDOUT:   %Int: %Int.type = struct_value () [template]
 // CHECK:STDOUT:   %i32: type = int_type signed, %.1 [template]
 // CHECK:STDOUT:   %i32: type = int_type signed, %.1 [template]
 // CHECK:STDOUT:   %.2: type = unbound_element_type %Base, %i32 [template]
 // CHECK:STDOUT:   %.2: type = unbound_element_type %Base, %i32 [template]
+// CHECK:STDOUT:   %F.type.1: type = fn_type @F.1 [template]
+// CHECK:STDOUT:   %F.1: %F.type.1 = struct_value () [template]
 // CHECK:STDOUT:   %.3: type = ptr_type <vtable> [template]
 // CHECK:STDOUT:   %.3: type = ptr_type <vtable> [template]
 // CHECK:STDOUT:   %.4: type = struct_type {.<vptr>: %.3, .m1: %i32, .m2: %i32} [template]
 // CHECK:STDOUT:   %.4: type = struct_type {.<vptr>: %.3, .m1: %i32, .m2: %i32} [template]
 // CHECK:STDOUT:   %.5: <witness> = complete_type_witness %.4 [template]
 // CHECK:STDOUT:   %.5: <witness> = complete_type_witness %.4 [template]
 // CHECK:STDOUT:   %F.type.2: type = fn_type @F.2 [template]
 // CHECK:STDOUT:   %F.type.2: type = fn_type @F.2 [template]
 // CHECK:STDOUT:   %F.2: %F.type.2 = struct_value () [template]
 // CHECK:STDOUT:   %F.2: %F.type.2 = struct_value () [template]
-// CHECK:STDOUT:   %.7: Core.IntLiteral = int_value 3 [template]
-// CHECK:STDOUT:   %.8: Core.IntLiteral = int_value 5 [template]
-// CHECK:STDOUT:   %.9: type = struct_type {.m2: Core.IntLiteral, .m1: Core.IntLiteral} [template]
+// CHECK:STDOUT:   %.6: Core.IntLiteral = int_value 3 [template]
 // CHECK:STDOUT:   %Convert.type.2: type = fn_type @Convert.1, @ImplicitAs(%i32) [template]
 // CHECK:STDOUT:   %Convert.type.2: type = fn_type @Convert.1, @ImplicitAs(%i32) [template]
 // CHECK:STDOUT:   %Convert.type.14: type = fn_type @Convert.2, @impl.1(%.1) [template]
 // CHECK:STDOUT:   %Convert.type.14: type = fn_type @Convert.2, @impl.1(%.1) [template]
 // CHECK:STDOUT:   %Convert.14: %Convert.type.14 = struct_value () [template]
 // CHECK:STDOUT:   %Convert.14: %Convert.type.14 = struct_value () [template]
-// CHECK:STDOUT:   %.33: <witness> = interface_witness (%Convert.14) [template]
-// CHECK:STDOUT:   %.34: <bound method> = bound_method %.8, %Convert.14 [template]
-// CHECK:STDOUT:   %.35: <specific function> = specific_function %.34, @Convert.2(%.1) [template]
-// CHECK:STDOUT:   %.36: %i32 = int_value 5 [template]
-// CHECK:STDOUT:   %.37: <bound method> = bound_method %.7, %Convert.14 [template]
-// CHECK:STDOUT:   %.38: <specific function> = specific_function %.37, @Convert.2(%.1) [template]
-// CHECK:STDOUT:   %.39: %i32 = int_value 3 [template]
+// CHECK:STDOUT:   %.30: <witness> = interface_witness (%Convert.14) [template]
+// CHECK:STDOUT:   %.31: <bound method> = bound_method %.6, %Convert.14 [template]
+// CHECK:STDOUT:   %.32: <specific function> = specific_function %.31, @Convert.2(%.1) [template]
+// CHECK:STDOUT:   %.33: %i32 = int_value 3 [template]
+// CHECK:STDOUT:   %.35: type = struct_type {.m2: %i32, .m1: %i32} [template]
+// CHECK:STDOUT:   %.36: Core.IntLiteral = int_value 5 [template]
+// CHECK:STDOUT:   %.37: type = struct_type {.m2: Core.IntLiteral, .m1: Core.IntLiteral} [template]
+// CHECK:STDOUT:   %.38: <bound method> = bound_method %.36, %Convert.14 [template]
+// CHECK:STDOUT:   %.39: <specific function> = specific_function %.38, @Convert.2(%.1) [template]
+// CHECK:STDOUT:   %.40: %i32 = int_value 5 [template]
+// CHECK:STDOUT:   %.41: Core.IntLiteral = int_value 4 [template]
+// CHECK:STDOUT:   %.42: <bound method> = bound_method %.41, %Convert.14 [template]
+// CHECK:STDOUT:   %.43: <specific function> = specific_function %.42, @Convert.2(%.1) [template]
+// CHECK:STDOUT:   %.44: %i32 = int_value 4 [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT: imports {
@@ -301,53 +325,146 @@ fn F() {
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @Base {
 // CHECK:STDOUT: class @Base {
-// CHECK:STDOUT:   %F.decl: %F.type.1 = fn_decl @F.1 [template = constants.%F.1] {} {}
+// CHECK:STDOUT:   %.loc5_11.1: Core.IntLiteral = int_value 32 [template = constants.%.1]
+// CHECK:STDOUT:   %int.make_type_signed.loc5: init type = call constants.%Int(%.loc5_11.1) [template = constants.%i32]
+// CHECK:STDOUT:   %.loc5_11.2: type = value_of_initializer %int.make_type_signed.loc5 [template = constants.%i32]
+// CHECK:STDOUT:   %.loc5_11.3: type = converted %int.make_type_signed.loc5, %.loc5_11.2 [template = constants.%i32]
+// CHECK:STDOUT:   %.loc5_9: %.2 = field_decl m1, element1 [template]
 // CHECK:STDOUT:   %.loc6_11.1: Core.IntLiteral = int_value 32 [template = constants.%.1]
 // CHECK:STDOUT:   %.loc6_11.1: Core.IntLiteral = int_value 32 [template = constants.%.1]
 // CHECK:STDOUT:   %int.make_type_signed.loc6: init type = call constants.%Int(%.loc6_11.1) [template = constants.%i32]
 // CHECK:STDOUT:   %int.make_type_signed.loc6: init type = call constants.%Int(%.loc6_11.1) [template = constants.%i32]
 // CHECK:STDOUT:   %.loc6_11.2: type = value_of_initializer %int.make_type_signed.loc6 [template = constants.%i32]
 // CHECK:STDOUT:   %.loc6_11.2: type = value_of_initializer %int.make_type_signed.loc6 [template = constants.%i32]
 // CHECK:STDOUT:   %.loc6_11.3: type = converted %int.make_type_signed.loc6, %.loc6_11.2 [template = constants.%i32]
 // CHECK:STDOUT:   %.loc6_11.3: type = converted %int.make_type_signed.loc6, %.loc6_11.2 [template = constants.%i32]
-// CHECK:STDOUT:   %.loc6_9: %.2 = field_decl m1, element0 [template]
-// CHECK:STDOUT:   %.loc7_11.1: Core.IntLiteral = int_value 32 [template = constants.%.1]
-// CHECK:STDOUT:   %int.make_type_signed.loc7: init type = call constants.%Int(%.loc7_11.1) [template = constants.%i32]
-// CHECK:STDOUT:   %.loc7_11.2: type = value_of_initializer %int.make_type_signed.loc7 [template = constants.%i32]
-// CHECK:STDOUT:   %.loc7_11.3: type = converted %int.make_type_signed.loc7, %.loc7_11.2 [template = constants.%i32]
-// CHECK:STDOUT:   %.loc7_9: %.2 = field_decl m2, element1 [template]
-// CHECK:STDOUT:   %.loc8: <witness> = complete_type_witness %.4 [template = constants.%.5]
+// CHECK:STDOUT:   %.loc6_9: %.2 = field_decl m2, element2 [template]
+// CHECK:STDOUT:   %F.decl: %F.type.1 = fn_decl @F.1 [template = constants.%F.1] {} {}
+// CHECK:STDOUT:   %.loc9: <witness> = complete_type_witness %.4 [template = constants.%.5]
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%Base
 // CHECK:STDOUT:   .Self = constants.%Base
+// CHECK:STDOUT:   .m1 = %.loc5_9
+// CHECK:STDOUT:   .m2 = %.loc6_9
 // CHECK:STDOUT:   .F = %F.decl
 // CHECK:STDOUT:   .F = %F.decl
-// CHECK:STDOUT:   .m1 = %.loc6_9
-// CHECK:STDOUT:   .m2 = %.loc7_9
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: virtual fn @F.1();
 // CHECK:STDOUT: virtual fn @F.1();
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.2() {
 // CHECK:STDOUT: fn @F.2() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %Base.ref: type = name_ref Base, file.%Base.decl [template = constants.%Base]
-// CHECK:STDOUT:   %v.var: ref %Base = var v
-// CHECK:STDOUT:   %v: ref %Base = bind_name v, %v.var
-// CHECK:STDOUT:   %.loc11_24: Core.IntLiteral = int_value 3 [template = constants.%.7]
-// CHECK:STDOUT:   %.loc11_33: Core.IntLiteral = int_value 5 [template = constants.%.8]
-// CHECK:STDOUT:   %.loc11_34.1: %.9 = struct_literal (%.loc11_24, %.loc11_33)
-// CHECK:STDOUT:   %.loc11_34.2: %Convert.type.2 = interface_witness_access constants.%.33, element0 [template = constants.%Convert.14]
-// CHECK:STDOUT:   %.loc11_34.3: <bound method> = bound_method %.loc11_33, %.loc11_34.2 [template = constants.%.34]
-// CHECK:STDOUT:   %.loc11_34.4: <specific function> = specific_function %.loc11_34.3, @Convert.2(constants.%.1) [template = constants.%.35]
-// CHECK:STDOUT:   %int.convert_checked.loc11_34.1: init %i32 = call %.loc11_34.4(%.loc11_33) [template = constants.%.36]
-// CHECK:STDOUT:   %.loc11_34.5: init %i32 = converted %.loc11_33, %int.convert_checked.loc11_34.1 [template = constants.%.36]
-// CHECK:STDOUT:   %.loc11_34.6: ref %i32 = class_element_access %v.var, element2
-// CHECK:STDOUT:   %.loc11_34.7: init %i32 = initialize_from %.loc11_34.5 to %.loc11_34.6 [template = constants.%.36]
-// CHECK:STDOUT:   %.loc11_34.8: %Convert.type.2 = interface_witness_access constants.%.33, element0 [template = constants.%Convert.14]
-// CHECK:STDOUT:   %.loc11_34.9: <bound method> = bound_method %.loc11_24, %.loc11_34.8 [template = constants.%.37]
-// CHECK:STDOUT:   %.loc11_34.10: <specific function> = specific_function %.loc11_34.9, @Convert.2(constants.%.1) [template = constants.%.38]
-// CHECK:STDOUT:   %int.convert_checked.loc11_34.2: init %i32 = call %.loc11_34.10(%.loc11_24) [template = constants.%.39]
-// CHECK:STDOUT:   %.loc11_34.11: init %i32 = converted %.loc11_24, %int.convert_checked.loc11_34.2 [template = constants.%.39]
-// CHECK:STDOUT:   %.loc11_34.12: ref %i32 = class_element_access %v.var, element1
-// CHECK:STDOUT:   %.loc11_34.13: init %i32 = initialize_from %.loc11_34.11 to %.loc11_34.12 [template = constants.%.39]
-// CHECK:STDOUT:   %.loc11_34.14: init %Base = class_init (<error>, %.loc11_34.7, %.loc11_34.13), %v.var [template = <error>]
-// CHECK:STDOUT:   %.loc11_35: init %Base = converted %.loc11_34.1, %.loc11_34.14 [template = <error>]
-// CHECK:STDOUT:   assign %v.var, %.loc11_35
+// CHECK:STDOUT:   %.loc12_10.1: Core.IntLiteral = int_value 32 [template = constants.%.1]
+// CHECK:STDOUT:   %int.make_type_signed: init type = call constants.%Int(%.loc12_10.1) [template = constants.%i32]
+// CHECK:STDOUT:   %.loc12_10.2: type = value_of_initializer %int.make_type_signed [template = constants.%i32]
+// CHECK:STDOUT:   %.loc12_10.3: type = converted %int.make_type_signed, %.loc12_10.2 [template = constants.%i32]
+// CHECK:STDOUT:   %i.var: ref %i32 = var i
+// CHECK:STDOUT:   %i: ref %i32 = bind_name i, %i.var
+// CHECK:STDOUT:   %.loc12_16: Core.IntLiteral = int_value 3 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc12_17.1: %Convert.type.2 = interface_witness_access constants.%.30, element0 [template = constants.%Convert.14]
+// CHECK:STDOUT:   %.loc12_17.2: <bound method> = bound_method %.loc12_16, %.loc12_17.1 [template = constants.%.31]
+// CHECK:STDOUT:   %.loc12_17.3: <specific function> = specific_function %.loc12_17.2, @Convert.2(constants.%.1) [template = constants.%.32]
+// CHECK:STDOUT:   %int.convert_checked.loc12: init %i32 = call %.loc12_17.3(%.loc12_16) [template = constants.%.33]
+// CHECK:STDOUT:   %.loc12_17.4: init %i32 = converted %.loc12_16, %int.convert_checked.loc12 [template = constants.%.33]
+// CHECK:STDOUT:   assign %i.var, %.loc12_17.4
+// CHECK:STDOUT:   %Base.ref.loc14: type = name_ref Base, file.%Base.decl [template = constants.%Base]
+// CHECK:STDOUT:   %b1.var: ref %Base = var b1
+// CHECK:STDOUT:   %b1: ref %Base = bind_name b1, %b1.var
+// CHECK:STDOUT:   %i.ref.loc14_25: ref %i32 = name_ref i, %i
+// CHECK:STDOUT:   %i.ref.loc14_34: ref %i32 = name_ref i, %i
+// CHECK:STDOUT:   %.loc14_35.1: %.35 = struct_literal (%i.ref.loc14_25, %i.ref.loc14_34)
+// CHECK:STDOUT:   %.loc14_34: %i32 = bind_value %i.ref.loc14_34
+// CHECK:STDOUT:   %.loc14_35.2: ref %i32 = class_element_access %b1.var, element2
+// CHECK:STDOUT:   %.loc14_35.3: init %i32 = initialize_from %.loc14_34 to %.loc14_35.2
+// CHECK:STDOUT:   %.loc14_25: %i32 = bind_value %i.ref.loc14_25
+// CHECK:STDOUT:   %.loc14_35.4: ref %i32 = class_element_access %b1.var, element1
+// CHECK:STDOUT:   %.loc14_35.5: init %i32 = initialize_from %.loc14_25 to %.loc14_35.4
+// CHECK:STDOUT:   %.loc14_35.6: init %Base = class_init (<error>, %.loc14_35.3, %.loc14_35.5), %b1.var
+// CHECK:STDOUT:   %.loc14_36: init %Base = converted %.loc14_35.1, %.loc14_35.6
+// CHECK:STDOUT:   assign %b1.var, %.loc14_36
+// CHECK:STDOUT:   %Base.ref.loc15: type = name_ref Base, file.%Base.decl [template = constants.%Base]
+// CHECK:STDOUT:   %b2.var: ref %Base = var b2
+// CHECK:STDOUT:   %b2: ref %Base = bind_name b2, %b2.var
+// CHECK:STDOUT:   %.loc15_25: Core.IntLiteral = int_value 3 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc15_34: Core.IntLiteral = int_value 5 [template = constants.%.36]
+// CHECK:STDOUT:   %.loc15_35.1: %.37 = struct_literal (%.loc15_25, %.loc15_34)
+// CHECK:STDOUT:   %.loc15_35.2: %Convert.type.2 = interface_witness_access constants.%.30, element0 [template = constants.%Convert.14]
+// CHECK:STDOUT:   %.loc15_35.3: <bound method> = bound_method %.loc15_34, %.loc15_35.2 [template = constants.%.38]
+// CHECK:STDOUT:   %.loc15_35.4: <specific function> = specific_function %.loc15_35.3, @Convert.2(constants.%.1) [template = constants.%.39]
+// CHECK:STDOUT:   %int.convert_checked.loc15_35.1: init %i32 = call %.loc15_35.4(%.loc15_34) [template = constants.%.40]
+// CHECK:STDOUT:   %.loc15_35.5: init %i32 = converted %.loc15_34, %int.convert_checked.loc15_35.1 [template = constants.%.40]
+// CHECK:STDOUT:   %.loc15_35.6: ref %i32 = class_element_access %b2.var, element2
+// CHECK:STDOUT:   %.loc15_35.7: init %i32 = initialize_from %.loc15_35.5 to %.loc15_35.6 [template = constants.%.40]
+// CHECK:STDOUT:   %.loc15_35.8: %Convert.type.2 = interface_witness_access constants.%.30, element0 [template = constants.%Convert.14]
+// CHECK:STDOUT:   %.loc15_35.9: <bound method> = bound_method %.loc15_25, %.loc15_35.8 [template = constants.%.31]
+// CHECK:STDOUT:   %.loc15_35.10: <specific function> = specific_function %.loc15_35.9, @Convert.2(constants.%.1) [template = constants.%.32]
+// CHECK:STDOUT:   %int.convert_checked.loc15_35.2: init %i32 = call %.loc15_35.10(%.loc15_25) [template = constants.%.33]
+// CHECK:STDOUT:   %.loc15_35.11: init %i32 = converted %.loc15_25, %int.convert_checked.loc15_35.2 [template = constants.%.33]
+// CHECK:STDOUT:   %.loc15_35.12: ref %i32 = class_element_access %b2.var, element1
+// CHECK:STDOUT:   %.loc15_35.13: init %i32 = initialize_from %.loc15_35.11 to %.loc15_35.12 [template = constants.%.33]
+// CHECK:STDOUT:   %.loc15_35.14: init %Base = class_init (<error>, %.loc15_35.7, %.loc15_35.13), %b2.var [template = <error>]
+// CHECK:STDOUT:   %.loc15_36: init %Base = converted %.loc15_35.1, %.loc15_35.14 [template = <error>]
+// CHECK:STDOUT:   assign %b2.var, %.loc15_36
+// CHECK:STDOUT:   %b1.ref: ref %Base = name_ref b1, %b1
+// CHECK:STDOUT:   %m2.ref: %.2 = name_ref m2, @Base.%.loc6_9 [template = @Base.%.loc6_9]
+// CHECK:STDOUT:   %.loc18_5: ref %i32 = class_element_access %b1.ref, element2
+// CHECK:STDOUT:   %.loc18_11: Core.IntLiteral = int_value 4 [template = constants.%.41]
+// CHECK:STDOUT:   %.loc18_9.1: %Convert.type.2 = interface_witness_access constants.%.30, element0 [template = constants.%Convert.14]
+// CHECK:STDOUT:   %.loc18_9.2: <bound method> = bound_method %.loc18_11, %.loc18_9.1 [template = constants.%.42]
+// CHECK:STDOUT:   %.loc18_9.3: <specific function> = specific_function %.loc18_9.2, @Convert.2(constants.%.1) [template = constants.%.43]
+// CHECK:STDOUT:   %int.convert_checked.loc18: init %i32 = call %.loc18_9.3(%.loc18_11) [template = constants.%.44]
+// CHECK:STDOUT:   %.loc18_9.4: init %i32 = converted %.loc18_11, %int.convert_checked.loc18 [template = constants.%.44]
+// CHECK:STDOUT:   assign %.loc18_5, %.loc18_9.4
 // CHECK:STDOUT:   return
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
+// CHECK:STDOUT: --- todo_fail_impl_without_base_declaration.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Base: type = class_type @Base [template]
+// CHECK:STDOUT:   %.1: type = struct_type {} [template]
+// CHECK:STDOUT:   %.2: <witness> = complete_type_witness %.1 [template]
+// CHECK:STDOUT:   %Derived: type = class_type @Derived [template]
+// CHECK:STDOUT:   %.4: type = unbound_element_type %Derived, %Base [template]
+// CHECK:STDOUT:   %F.type: type = fn_type @F [template]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [template]
+// CHECK:STDOUT:   %.5: type = ptr_type <vtable> [template]
+// CHECK:STDOUT:   %.6: type = struct_type {.<vptr>: %.5, .base: %Base} [template]
+// CHECK:STDOUT:   %.7: <witness> = complete_type_witness %.6 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [template] {
+// CHECK:STDOUT:     import Core//prelude
+// CHECK:STDOUT:     import Core//prelude/...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = imports.%Core
+// CHECK:STDOUT:     .Base = %Base.decl
+// CHECK:STDOUT:     .Derived = %Derived.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %Base.decl: type = class_decl @Base [template = constants.%Base] {} {}
+// CHECK:STDOUT:   %Derived.decl: type = class_decl @Derived [template = constants.%Derived] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Base {
+// CHECK:STDOUT:   %.loc5: <witness> = complete_type_witness %.1 [template = constants.%.2]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Base
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Derived {
+// CHECK:STDOUT:   %Base.ref: type = name_ref Base, file.%Base.decl [template = constants.%Base]
+// CHECK:STDOUT:   %.loc8: %.4 = base_decl %Base, element1 [template]
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [template = constants.%F] {} {}
+// CHECK:STDOUT:   %.loc10: <witness> = complete_type_witness %.6 [template = constants.%.7]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Derived
+// CHECK:STDOUT:   .base = %.loc8
+// CHECK:STDOUT:   .F = %F.decl
+// CHECK:STDOUT:   extend %Base.ref
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: impl fn @F();
+// CHECK:STDOUT:

+ 97 - 20
toolchain/lower/testdata/class/virtual.carbon

@@ -8,35 +8,75 @@
 // TIP: To dump output, run:
 // TIP: To dump output, run:
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/class/virtual.carbon
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/class/virtual.carbon
 
 
+// --- classes.carbon
+
+package Classes;
+
 base class Base {
 base class Base {
 }
 }
+
 base class Intermediate {
 base class Intermediate {
   extend base: Base;
   extend base: Base;
   virtual fn Fn();
   virtual fn Fn();
 }
 }
+
 class Derived {
 class Derived {
   extend base: Intermediate;
   extend base: Intermediate;
   impl fn Fn();
   impl fn Fn();
 }
 }
 
 
+// --- create.carbon
+
+package Create;
+
+import Classes;
+
 fn Create() {
 fn Create() {
-  var b: Base;
-  var i: Intermediate;
-  var d: Derived;
+  var b: Classes.Base;
+  var i: Classes.Intermediate;
+  var d: Classes.Derived;
 }
 }
 
 
-fn Use(v: Intermediate*) {
+fn Use(v: Classes.Intermediate*) {
   v->Fn();
   v->Fn();
 }
 }
 
 
-// CHECK:STDOUT: ; ModuleID = 'virtual.carbon'
-// CHECK:STDOUT: source_filename = "virtual.carbon"
+// --- member_init.carbon
+
+package MemberInit;
+
+base class Base {
+  var m: i32;
+  virtual fn Fn();
+}
+
+fn Fn() {
+  var i: i32 = 3;
+  // TODO: make `i` initialize `m` here (it currently initializes the vptr instead)
+  var v: Base = {.m = i};
+  // TODO: fix crash here
+  // var v: Base = {.m = 3};
+  v.m = 5;
+}
+
+// CHECK:STDOUT: ; ModuleID = 'classes.carbon'
+// CHECK:STDOUT: source_filename = "classes.carbon"
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: declare void @_CFn.Intermediate.Main()
+// CHECK:STDOUT: declare void @_CFn.Intermediate.Classes()
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: declare void @_CFn.Derived.Main()
+// CHECK:STDOUT: declare void @_CFn.Derived.Classes()
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: define void @_CCreate.Main() !dbg !4 {
+// 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, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !3 = !DIFile(filename: "classes.carbon", directory: "")
+// CHECK:STDOUT: ; ModuleID = 'create.carbon'
+// CHECK:STDOUT: source_filename = "create.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @_CCreate.Create() !dbg !4 {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %b.var = alloca {}, align 8, !dbg !7
 // CHECK:STDOUT:   %b.var = alloca {}, align 8, !dbg !7
 // CHECK:STDOUT:   %i.var = alloca { ptr, {} }, align 8, !dbg !8
 // CHECK:STDOUT:   %i.var = alloca { ptr, {} }, align 8, !dbg !8
@@ -44,9 +84,46 @@ fn Use(v: Intermediate*) {
 // CHECK:STDOUT:   ret void, !dbg !10
 // CHECK:STDOUT:   ret void, !dbg !10
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: define void @_CUse.Main(ptr %v) !dbg !11 {
+// CHECK:STDOUT: define void @_CUse.Create(ptr %v) !dbg !11 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   call void @_CFn.Intermediate.Classes(), !dbg !12
+// CHECK:STDOUT:   ret void, !dbg !13
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare void @_CFn.Intermediate.Classes()
+// 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, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !3 = !DIFile(filename: "create.carbon", directory: "")
+// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "Create", linkageName: "_CCreate.Create", scope: null, file: !3, line: 6, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
+// CHECK:STDOUT: !6 = !{}
+// CHECK:STDOUT: !7 = !DILocation(line: 7, column: 7, scope: !4)
+// CHECK:STDOUT: !8 = !DILocation(line: 8, column: 7, scope: !4)
+// CHECK:STDOUT: !9 = !DILocation(line: 9, column: 7, scope: !4)
+// CHECK:STDOUT: !10 = !DILocation(line: 6, column: 1, scope: !4)
+// CHECK:STDOUT: !11 = distinct !DISubprogram(name: "Use", linkageName: "_CUse.Create", scope: null, file: !3, line: 12, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !12 = !DILocation(line: 13, column: 3, scope: !11)
+// CHECK:STDOUT: !13 = !DILocation(line: 12, column: 1, scope: !11)
+// CHECK:STDOUT: ; ModuleID = 'member_init.carbon'
+// CHECK:STDOUT: source_filename = "member_init.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare void @_CFn.Base.MemberInit()
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @_CFn.MemberInit() !dbg !4 {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT: entry:
-// CHECK:STDOUT:   call void @_CFn.Intermediate.Main(), !dbg !12
+// CHECK:STDOUT:   %i.var = alloca i32, align 4, !dbg !7
+// CHECK:STDOUT:   store i32 3, ptr %i.var, align 4, !dbg !8
+// CHECK:STDOUT:   %v.var = alloca { ptr, i32 }, align 8, !dbg !9
+// CHECK:STDOUT:   %.loc12_23 = load i32, ptr %i.var, align 4, !dbg !10
+// CHECK:STDOUT:   %.loc12_24.2.m = getelementptr inbounds nuw { ptr, i32 }, ptr %v.var, i32 0, i32 1, !dbg !11
+// CHECK:STDOUT:   store i32 %.loc12_23, ptr %.loc12_24.2.m, align 4, !dbg !11
+// CHECK:STDOUT:   %.loc15_4.m = getelementptr inbounds nuw { ptr, i32 }, ptr %v.var, i32 0, i32 1, !dbg !12
+// CHECK:STDOUT:   store i32 5, ptr %.loc15_4.m, align 4, !dbg !12
 // CHECK:STDOUT:   ret void, !dbg !13
 // CHECK:STDOUT:   ret void, !dbg !13
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
@@ -56,14 +133,14 @@ fn Use(v: Intermediate*) {
 // CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
 // CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
 // CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
 // CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
 // CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
 // CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
-// CHECK:STDOUT: !3 = !DIFile(filename: "virtual.carbon", directory: "")
-// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "Create", linkageName: "_CCreate.Main", scope: null, file: !3, line: 22, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !3 = !DIFile(filename: "member_init.carbon", directory: "")
+// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "Fn", linkageName: "_CFn.MemberInit", scope: null, file: !3, line: 9, type: !5, spFlags: DISPFlagDefinition, unit: !2)
 // CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
 // CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
 // CHECK:STDOUT: !6 = !{}
 // CHECK:STDOUT: !6 = !{}
-// CHECK:STDOUT: !7 = !DILocation(line: 23, column: 7, scope: !4)
-// CHECK:STDOUT: !8 = !DILocation(line: 24, column: 7, scope: !4)
-// CHECK:STDOUT: !9 = !DILocation(line: 25, column: 7, scope: !4)
-// CHECK:STDOUT: !10 = !DILocation(line: 22, column: 1, scope: !4)
-// CHECK:STDOUT: !11 = distinct !DISubprogram(name: "Use", linkageName: "_CUse.Main", scope: null, file: !3, line: 28, type: !5, spFlags: DISPFlagDefinition, unit: !2)
-// CHECK:STDOUT: !12 = !DILocation(line: 29, column: 3, scope: !11)
-// CHECK:STDOUT: !13 = !DILocation(line: 28, column: 1, scope: !11)
+// CHECK:STDOUT: !7 = !DILocation(line: 10, column: 7, scope: !4)
+// CHECK:STDOUT: !8 = !DILocation(line: 10, column: 3, scope: !4)
+// CHECK:STDOUT: !9 = !DILocation(line: 12, column: 7, scope: !4)
+// CHECK:STDOUT: !10 = !DILocation(line: 12, column: 23, scope: !4)
+// CHECK:STDOUT: !11 = !DILocation(line: 12, column: 17, scope: !4)
+// CHECK:STDOUT: !12 = !DILocation(line: 15, column: 3, scope: !4)
+// CHECK:STDOUT: !13 = !DILocation(line: 9, column: 1, scope: !4)

+ 5 - 0
toolchain/sem_ir/ids.h

@@ -805,12 +805,17 @@ constexpr TypeBlockId TypeBlockId::Empty = TypeBlockId(0);
 struct ElementIndex : public IndexBase, public Printable<ElementIndex> {
 struct ElementIndex : public IndexBase, public Printable<ElementIndex> {
   using IndexBase::IndexBase;
   using IndexBase::IndexBase;
 
 
+  // An explicitly invalid ID.
+  static const ElementIndex Invalid;
+
   auto Print(llvm::raw_ostream& out) const -> void {
   auto Print(llvm::raw_ostream& out) const -> void {
     out << "element";
     out << "element";
     IndexBase::Print(out);
     IndexBase::Print(out);
   }
   }
 };
 };
 
 
+constexpr ElementIndex ElementIndex::Invalid = ElementIndex(InvalidIndex);
+
 // The ID of a library name. This is either a string literal or `default`.
 // The ID of a library name. This is either a string literal or `default`.
 struct LibraryNameId : public IdBase, public Printable<NameId> {
 struct LibraryNameId : public IdBase, public Printable<NameId> {
   using DiagnosticType = DiagnosticTypeInfo<std::string>;
   using DiagnosticType = DiagnosticTypeInfo<std::string>;