Răsfoiți Sursa

Add an associated entity instruction and corresponding type for interface elements. (#3730)

When declaring an associated entity in an interface -- just associated
functions for now -- create an associated entity value and corresponding
type to represent a "slot in a witness table". Also track the list of
associated entities on the interface so that we will eventually be able
to check impls against them.

Associated entities are represented as the integer index of their slot
in a witness table.

---------

Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
Richard Smith 2 ani în urmă
părinte
comite
33c1e9ca95
48 a modificat fișierele cu 522 adăugiri și 72 ștergeri
  1. 15 0
      toolchain/check/BUILD
  2. 10 5
      toolchain/check/context.cpp
  3. 4 0
      toolchain/check/context.h
  4. 4 0
      toolchain/check/eval.cpp
  5. 20 1
      toolchain/check/handle_function.cpp
  6. 7 2
      toolchain/check/handle_interface.cpp
  7. 33 12
      toolchain/check/import_ref.cpp
  8. 36 0
      toolchain/check/interface.cpp
  9. 22 0
      toolchain/check/interface.h
  10. 6 2
      toolchain/check/testdata/impl/basic.carbon
  11. 1 0
      toolchain/check/testdata/impl/declaration.carbon
  12. 1 0
      toolchain/check/testdata/impl/empty.carbon
  13. 7 3
      toolchain/check/testdata/impl/fail_extend_impl_forall.carbon
  14. 1 0
      toolchain/check/testdata/impl/fail_extend_impl_scope.carbon
  15. 1 0
      toolchain/check/testdata/impl/fail_extend_impl_type_as.carbon
  16. 1 0
      toolchain/check/testdata/impl/fail_extend_partially_defined_interface.carbon
  17. 6 2
      toolchain/check/testdata/impl/fail_impl_as_scope.carbon
  18. 1 0
      toolchain/check/testdata/impl/fail_impl_bad_type.carbon
  19. 1 0
      toolchain/check/testdata/impl/fail_redefinition.carbon
  20. 20 12
      toolchain/check/testdata/impl/fail_todo_extend_impl.carbon
  21. 7 3
      toolchain/check/testdata/impl/impl_as.carbon
  22. 6 2
      toolchain/check/testdata/impl/impl_forall.carbon
  23. 1 0
      toolchain/check/testdata/impl/redeclaration.carbon
  24. 1 0
      toolchain/check/testdata/interface/as_type.carbon
  25. 6 1
      toolchain/check/testdata/interface/basic.carbon
  26. 3 0
      toolchain/check/testdata/interface/fail_add_member_outside_definition.carbon
  27. 1 0
      toolchain/check/testdata/interface/fail_as_type_of_type.carbon
  28. 3 1
      toolchain/check/testdata/interface/fail_duplicate.carbon
  29. 5 1
      toolchain/check/testdata/interface/fail_lookup_undefined.carbon
  30. 10 6
      toolchain/check/testdata/interface/fail_member_lookup.carbon
  31. 2 0
      toolchain/check/testdata/interface/fail_modifiers.carbon
  32. 44 0
      toolchain/check/testdata/interface/fail_redeclare_member.carbon
  33. 51 0
      toolchain/check/testdata/interface/fail_todo_define_out_of_line.carbon
  34. 6 2
      toolchain/check/testdata/interface/fail_todo_facet_lookup.carbon
  35. 11 4
      toolchain/check/testdata/interface/fail_todo_modifiers.carbon
  36. 16 8
      toolchain/check/testdata/interface/import.carbon
  37. 9 0
      toolchain/lower/file_context.cpp
  38. 9 3
      toolchain/lower/handle.cpp
  39. 6 0
      toolchain/lower/handle_type.cpp
  40. 20 0
      toolchain/lower/testdata/interface/assoc.carbon
  41. 31 0
      toolchain/lower/testdata/interface/basic.carbon
  42. 19 0
      toolchain/sem_ir/file.cpp
  43. 2 0
      toolchain/sem_ir/formatter.cpp
  44. 10 0
      toolchain/sem_ir/inst.h
  45. 2 0
      toolchain/sem_ir/inst_kind.def
  46. 8 2
      toolchain/sem_ir/interface.h
  47. 10 0
      toolchain/sem_ir/name_scope.h
  48. 26 0
      toolchain/sem_ir/typed_insts.h

+ 15 - 0
toolchain/check/BUILD

@@ -102,6 +102,7 @@ cc_library(
     deps = [
         ":context",
         ":import",
+        ":interface",
         "//common:check",
         "//common:ostream",
         "//toolchain/base:pretty_stack_trace_function",
@@ -147,6 +148,20 @@ cc_library(
     ],
 )
 
+cc_library(
+    name = "interface",
+    srcs = ["interface.cpp"],
+    hdrs = ["interface.h"],
+    deps = [
+        ":context",
+        "//common:check",
+        "//toolchain/sem_ir:file",
+        "//toolchain/sem_ir:ids",
+        "//toolchain/sem_ir:inst",
+        "//toolchain/sem_ir:inst_kind",
+    ],
+)
+
 glob_sh_run(
     args = [
         "$(location //toolchain/driver:carbon)",

+ 10 - 5
toolchain/check/context.cpp

@@ -173,7 +173,7 @@ auto Context::NoteUndefinedInterface(SemIR::InterfaceId interface_id,
                                      DiagnosticBuilder& builder) -> void {
   const auto& interface_info = interfaces().Get(interface_id);
   CARBON_CHECK(!interface_info.is_defined()) << "Interface is not incomplete";
-  if (interface_info.definition_id.is_valid()) {
+  if (interface_info.is_being_defined()) {
     CARBON_DIAGNOSTIC(InterfaceUndefinedWithinDefinition, Note,
                       "Interface is currently being defined.");
     builder.Note(interface_info.definition_id,
@@ -813,16 +813,13 @@ class TypeCompleter {
   // types, as found by AddNestedIncompleteTypes, are known to be complete.
   auto BuildValueRepr(SemIR::TypeId type_id, SemIR::Inst inst) const
       -> SemIR::ValueRepr {
-    // TODO: This can emit new SemIR instructions. Consider emitting them into a
-    // dedicated file-scope instruction block where possible, or somewhere else
-    // that better reflects the definition of the type, rather than wherever the
-    // type happens to first be required to be complete.
     switch (inst.kind()) {
       case SemIR::AddrOf::Kind:
       case SemIR::AddrPattern::Kind:
       case SemIR::ArrayIndex::Kind:
       case SemIR::ArrayInit::Kind:
       case SemIR::Assign::Kind:
+      case SemIR::AssociatedEntity::Kind:
       case SemIR::BaseDecl::Kind:
       case SemIR::BindAlias::Kind:
       case SemIR::BindName::Kind:
@@ -908,6 +905,7 @@ class TypeCompleter {
       case SemIR::Builtin::Kind:
         return BuildBuiltinValueRepr(type_id, inst.As<SemIR::Builtin>());
 
+      case SemIR::AssociatedEntityType::Kind:
       case SemIR::BindSymbolicName::Kind:
       case SemIR::PointerType::Kind:
       case SemIR::UnboundElementType::Kind:
@@ -980,6 +978,13 @@ auto Context::GetTupleType(llvm::ArrayRef<SemIR::TypeId> type_ids)
   return GetTypeImpl<SemIR::TupleType>(*this, type_blocks().Add(type_ids));
 }
 
+auto Context::GetAssociatedEntityType(SemIR::InterfaceId interface_id,
+                                      SemIR::TypeId entity_type_id)
+    -> SemIR::TypeId {
+  return GetTypeImpl<SemIR::AssociatedEntityType>(*this, interface_id,
+                                                  entity_type_id);
+}
+
 auto Context::GetBuiltinType(SemIR::BuiltinKind kind) -> SemIR::TypeId {
   CARBON_CHECK(kind != SemIR::BuiltinKind::Invalid);
   auto type_id = GetTypeIdForTypeConstant(

+ 4 - 0
toolchain/check/context.h

@@ -234,6 +234,10 @@ class Context {
 
   // TODO: Consider moving these `Get*Type` functions to a separate class.
 
+  // Gets the type for the name of an associated entity.
+  auto GetAssociatedEntityType(SemIR::InterfaceId interface_id,
+                               SemIR::TypeId entity_type_id) -> SemIR::TypeId;
+
   // Gets a builtin type. The returned type will be complete.
   auto GetBuiltinType(SemIR::BuiltinKind kind) -> SemIR::TypeId;
 

+ 4 - 0
toolchain/check/eval.cpp

@@ -322,6 +322,9 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
             return true;
           },
           &SemIR::ArrayType::bound_id, &SemIR::ArrayType::element_type_id);
+    case SemIR::AssociatedEntityType::Kind:
+      return RebuildIfFieldsAreConstant(
+          context, inst, &SemIR::AssociatedEntityType::entity_type_id);
     case SemIR::BoundMethod::Kind:
       return RebuildIfFieldsAreConstant(context, inst,
                                         &SemIR::BoundMethod::object_id,
@@ -363,6 +366,7 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
     case SemIR::TupleInit::Kind:
       return RebuildInitAsValue(context, inst, SemIR::TupleValue::Kind);
 
+    case SemIR::AssociatedEntity::Kind:
     case SemIR::Builtin::Kind:
       // Builtins are always template constants.
       return MakeConstantResult(context, inst, Phase::Template);

+ 20 - 1
toolchain/check/handle_function.cpp

@@ -5,6 +5,7 @@
 #include "toolchain/check/context.h"
 #include "toolchain/check/convert.h"
 #include "toolchain/check/decl_name_stack.h"
+#include "toolchain/check/interface.h"
 #include "toolchain/check/modifiers.h"
 #include "toolchain/parse/tree_node_location_translator.h"
 #include "toolchain/sem_ir/entry_point.h"
@@ -126,9 +127,24 @@ static auto BuildFunctionDecl(Context& context,
   auto function_decl_id =
       context.AddPlaceholderInst({parse_node, function_decl});
 
+  // At interface scope, a function declaration introduces an associated
+  // function.
+  auto lookup_result_id = function_decl_id;
+  if (name_context.enclosing_scope_id_for_new_inst().is_valid() &&
+      !name_context.has_qualifiers) {
+    auto scope_inst_id = context.name_scopes().GetInstIdIfValid(
+        name_context.enclosing_scope_id_for_new_inst());
+    if (auto interface_scope =
+            context.insts().TryGetAsIfValid<SemIR::InterfaceDecl>(
+                scope_inst_id)) {
+      lookup_result_id = BuildAssociatedEntity(
+          context, interface_scope->interface_id, function_decl_id);
+    }
+  }
+
   // Check whether this is a redeclaration.
   auto existing_id =
-      context.decl_name_stack().LookupOrAddName(name_context, function_decl_id);
+      context.decl_name_stack().LookupOrAddName(name_context, lookup_result_id);
   if (existing_id.is_valid()) {
     if (auto existing_function_decl =
             context.insts().Get(existing_id).TryAs<SemIR::FunctionDecl>()) {
@@ -136,6 +152,7 @@ static auto BuildFunctionDecl(Context& context,
       function_decl.function_id = existing_function_decl->function_id;
 
       // TODO: Check that the signature matches!
+      // TODO: Disallow redeclarations within classes?
 
       // Track the signature from the definition, so that IDs in the body match
       // IDs in the signature.
@@ -149,6 +166,8 @@ static auto BuildFunctionDecl(Context& context,
       }
     } else {
       // This is a redeclaration of something other than a function.
+      // This includes the case where an associated function redeclares another
+      // associated function.
       context.DiagnoseDuplicateName(function_decl_id, existing_id);
     }
   }

+ 7 - 2
toolchain/check/handle_interface.cpp

@@ -130,7 +130,9 @@ auto HandleInterfaceDefinitionStart(
 
   context.inst_block_stack().Push();
   context.node_stack().Push(parse_node, interface_id);
-  // TODO: Perhaps use the args_type_info_stack for a witness table.
+
+  // We use the arg stack to build the witness table type.
+  context.args_type_info_stack().Push();
 
   // TODO: Handle the case where there's control flow in the interface body. For
   // example:
@@ -153,10 +155,13 @@ auto HandleInterfaceDefinition(Context& context,
   context.inst_block_stack().Pop();
   context.scope_stack().Pop();
   context.decl_name_stack().PopScope();
+  auto associated_entities_id = context.args_type_info_stack().Pop();
 
   // The interface type is now fully defined.
   auto& interface_info = context.interfaces().Get(interface_id);
-  interface_info.defined = true;
+  if (!interface_info.associated_entities_id.is_valid()) {
+    interface_info.associated_entities_id = associated_entities_id;
+  }
   return true;
 }
 

+ 33 - 12
toolchain/check/import_ref.cpp

@@ -237,13 +237,9 @@ class ImportRefResolver {
   }
 
   // Adds ImportRefUnused entries for members of the imported scope, for name
-  // lookup. Returns the block used for the refs, primarily for textual IR
-  // formatting.
+  // lookup.
   auto AddNameScopeImportRefs(const SemIR::NameScope& import_scope,
-                              SemIR::NameScope& new_scope)
-      -> SemIR::InstBlockId {
-    // Push a block so that we can add scoped instructions to it.
-    context_.inst_block_stack().Push();
+                              SemIR::NameScope& new_scope) -> void {
     for (auto [entry_name_id, entry_inst_id] : import_scope.names) {
       auto ref_id = context_.AddPlaceholderInst(
           SemIR::ImportRefUnused{import_ir_id_, entry_inst_id});
@@ -251,7 +247,24 @@ class ImportRefResolver {
           new_scope.names.insert({GetLocalNameId(entry_name_id), ref_id})
               .second);
     }
-    return context_.inst_block_stack().Pop();
+  }
+
+  // Given a block ID for a list of associated entities of a witness, returns a
+  // version localized to the current IR.
+  auto AddAssociatedEntities(SemIR::InstBlockId associated_entities_id)
+      -> SemIR::InstBlockId {
+    if (associated_entities_id == SemIR::InstBlockId::Empty) {
+      return SemIR::InstBlockId::Empty;
+    }
+    auto associated_entities =
+        import_ir_.inst_blocks().Get(associated_entities_id);
+    llvm::SmallVector<SemIR::InstId> new_associated_entities;
+    new_associated_entities.reserve(associated_entities.size());
+    for (auto inst_id : associated_entities) {
+      new_associated_entities.push_back(context_.AddPlaceholderInst(
+          SemIR::ImportRefUnused{import_ir_id_, inst_id}));
+    }
+    return context_.inst_blocks().Add(new_associated_entities);
   }
 
   // Tries to resolve the InstId, returning a constant when ready, or Invalid if
@@ -411,7 +424,11 @@ class ImportRefResolver {
     auto& new_scope = context_.name_scopes().Get(new_class.scope_id);
     const auto& import_scope =
         import_ir_.name_scopes().Get(import_class.scope_id);
-    new_class.body_block_id = AddNameScopeImportRefs(import_scope, new_scope);
+
+    // Push a block so that we can add scoped instructions to it.
+    context_.inst_block_stack().Push();
+    AddNameScopeImportRefs(import_scope, new_scope);
+    new_class.body_block_id = context_.inst_block_stack().Pop();
 
     if (import_class.base_id.is_valid()) {
       new_class.base_id = base_const_id.inst_id();
@@ -591,12 +608,16 @@ class ImportRefResolver {
       auto& new_scope = context_.name_scopes().Get(new_interface.scope_id);
       const auto& import_scope =
           import_ir_.name_scopes().Get(import_interface.scope_id);
-      new_interface.body_block_id =
-          AddNameScopeImportRefs(import_scope, new_scope);
+
+      // Push a block so that we can add scoped instructions to it.
+      context_.inst_block_stack().Push();
+      AddNameScopeImportRefs(import_scope, new_scope);
+      new_interface.associated_entities_id =
+          AddAssociatedEntities(import_interface.associated_entities_id);
+      new_interface.body_block_id = context_.inst_block_stack().Pop();
+
       CARBON_CHECK(import_scope.extended_scopes.empty())
           << "Interfaces don't currently have extended scopes to support.";
-
-      new_interface.defined = true;
     }
 
     // Write the interface ID into the InterfaceDecl.

+ 36 - 0
toolchain/check/interface.cpp

@@ -0,0 +1,36 @@
+// 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 "toolchain/check/interface.h"
+
+#include "toolchain/check/context.h"
+#include "toolchain/sem_ir/ids.h"
+#include "toolchain/sem_ir/typed_insts.h"
+
+namespace Carbon::Check {
+
+auto BuildAssociatedEntity(Context& context, SemIR::InterfaceId interface_id,
+                           SemIR::InstId decl_id) -> SemIR::InstId {
+  auto& interface_info = context.interfaces().Get(interface_id);
+  if (!interface_info.is_being_defined()) {
+    // This should only happen if the interface is erroneously defined more than
+    // once.
+    // TODO: Find a way to CHECK this.
+    return SemIR::InstId::BuiltinError;
+  }
+
+  // Register this declaration as declaring an associated entity.
+  auto index = SemIR::ElementIndex(
+      context.args_type_info_stack().PeekCurrentBlockContents().size());
+  context.args_type_info_stack().AddInstId(decl_id);
+
+  // Name lookup for the declaration's name should name the associated entity,
+  // not the declaration itself.
+  auto type_id = context.GetAssociatedEntityType(
+      interface_id, context.insts().Get(decl_id).type_id());
+  return context.AddInst({context.insts().GetParseNode(decl_id),
+                          SemIR::AssociatedEntity{type_id, index, decl_id}});
+}
+
+}  // namespace Carbon::Check

+ 22 - 0
toolchain/check/interface.h

@@ -0,0 +1,22 @@
+// 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
+
+#ifndef CARBON_TOOLCHAIN_CHECK_INTERFACE_H_
+#define CARBON_TOOLCHAIN_CHECK_INTERFACE_H_
+
+#include "toolchain/check/context.h"
+#include "toolchain/sem_ir/ids.h"
+
+namespace Carbon::Check {
+
+// Builds and returns an associated entity for `interface_id` corresponding to
+// the declaration `decl_id`, which can be an associated function or an
+// associated constant. Registers the associated entity in the list for the
+// interface.
+auto BuildAssociatedEntity(Context& context, SemIR::InterfaceId interface_id,
+                           SemIR::InstId decl_id) -> SemIR::InstId;
+
+}  // namespace Carbon::Check
+
+#endif  // CARBON_TOOLCHAIN_CHECK_INTERFACE_H_

+ 6 - 2
toolchain/check/testdata/impl/basic.carbon

@@ -16,19 +16,23 @@ impl i32 as Simple {
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %.1: type = interface_type @Simple [template]
+// CHECK:STDOUT:   %.2: type = assoc_entity_type @Simple, <function> [template]
+// CHECK:STDOUT:   %.3: <associated <function> in Simple> = assoc_entity element0, @Simple.%F [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace {.Simple = %Simple.decl} [template]
 // CHECK:STDOUT:   %Simple.decl = interface_decl @Simple, () [template = constants.%.1]
-// CHECK:STDOUT:   impl_decl @impl, (<unexpected instref inst+4>)
+// CHECK:STDOUT:   impl_decl @impl, (<unexpected instref inst+7>)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Simple {
 // CHECK:STDOUT:   %F: <function> = fn_decl @F.1 [template]
+// CHECK:STDOUT:   %.loc8: <associated <function> in Simple> = assoc_entity element0, %F [template = constants.%.3]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .F = %F
+// CHECK:STDOUT:   .F = %.loc8
+// CHECK:STDOUT:   witness = (%F)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl: i32 as Simple {

+ 1 - 0
toolchain/check/testdata/impl/declaration.carbon

@@ -23,6 +23,7 @@ impl i32 as I;
 // CHECK:STDOUT: interface @I {
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl: i32 as I;

+ 1 - 0
toolchain/check/testdata/impl/empty.carbon

@@ -25,6 +25,7 @@ impl i32 as Empty {
 // CHECK:STDOUT: interface @Empty {
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl: i32 as Empty {

+ 7 - 3
toolchain/check/testdata/impl/fail_extend_impl_forall.carbon

@@ -27,8 +27,10 @@ class C {
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %.1: type = interface_type @GenericInterface [template]
+// CHECK:STDOUT:   %.2: type = assoc_entity_type @GenericInterface, <function> [template]
+// CHECK:STDOUT:   %.3: <associated <function> in GenericInterface> = assoc_entity element0, @GenericInterface.%F [template]
 // CHECK:STDOUT:   %C: type = class_type @C [template]
-// CHECK:STDOUT:   %.2: type = struct_type {} [template]
+// CHECK:STDOUT:   %.4: type = struct_type {} [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -39,9 +41,11 @@ class C {
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @GenericInterface {
 // CHECK:STDOUT:   %F: <function> = fn_decl @F.1 [template]
+// CHECK:STDOUT:   %.loc11: <associated <function> in GenericInterface> = assoc_entity element0, %F [template = constants.%.3]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .F = %F
+// CHECK:STDOUT:   .F = %.loc11
+// CHECK:STDOUT:   witness = (%F)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl: C as <error> {
@@ -52,7 +56,7 @@ class C {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @C {
-// CHECK:STDOUT:   impl_decl @impl, (<unexpected instref inst+11>, <unexpected instref inst+12>, <unexpected instref inst+13>, <unexpected instref inst+14>)
+// CHECK:STDOUT:   impl_decl @impl, (<unexpected instref inst+14>, <unexpected instref inst+15>, <unexpected instref inst+16>, <unexpected instref inst+17>)
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   has_error

+ 1 - 0
toolchain/check/testdata/impl/fail_extend_impl_scope.carbon

@@ -26,6 +26,7 @@ extend impl i32 as I {}
 // CHECK:STDOUT: interface @I {
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl: i32 as I {

+ 1 - 0
toolchain/check/testdata/impl/fail_extend_impl_type_as.carbon

@@ -54,6 +54,7 @@ class E {
 // CHECK:STDOUT: interface @I {
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl.1: i32 as I {

+ 1 - 0
toolchain/check/testdata/impl/fail_extend_partially_defined_interface.carbon

@@ -34,6 +34,7 @@ interface I {
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .C = %C.decl
+// CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl: C as I;

+ 6 - 2
toolchain/check/testdata/impl/fail_impl_as_scope.carbon

@@ -19,19 +19,23 @@ impl as Simple {
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %.1: type = interface_type @Simple [template]
+// CHECK:STDOUT:   %.2: type = assoc_entity_type @Simple, <function> [template]
+// CHECK:STDOUT:   %.3: <associated <function> in Simple> = assoc_entity element0, @Simple.%F [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace {.Simple = %Simple.decl} [template]
 // CHECK:STDOUT:   %Simple.decl = interface_decl @Simple, () [template = constants.%.1]
-// CHECK:STDOUT:   impl_decl @impl, (<unexpected instref inst+4>)
+// CHECK:STDOUT:   impl_decl @impl, (<unexpected instref inst+7>)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Simple {
 // CHECK:STDOUT:   %F: <function> = fn_decl @F.1 [template]
+// CHECK:STDOUT:   %.loc8: <associated <function> in Simple> = assoc_entity element0, %F [template = constants.%.3]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .F = %F
+// CHECK:STDOUT:   .F = %.loc8
+// CHECK:STDOUT:   witness = (%F)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl: <error> as Simple {

+ 1 - 0
toolchain/check/testdata/impl/fail_impl_bad_type.carbon

@@ -27,6 +27,7 @@ impl true as I {}
 // CHECK:STDOUT: interface @I {
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl: <error> as I {

+ 1 - 0
toolchain/check/testdata/impl/fail_redefinition.carbon

@@ -32,6 +32,7 @@ impl i32 as I {}
 // CHECK:STDOUT: interface @I {
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl: i32 as I {

+ 20 - 12
toolchain/check/testdata/impl/extend_impl.carbon → toolchain/check/testdata/impl/fail_todo_extend_impl.carbon

@@ -15,20 +15,28 @@ class C {
 }
 
 fn G(c: C) {
-  // TODO: These don't do impl lookup, and so refer to the interface members
-  // rather than to the impl members.
+  // TODO: These should do impl lookup. Because that's not implemented yet, they
+  // refer to the interface members rather than to the impl members.
+  // CHECK:STDERR: fail_todo_extend_impl.carbon:[[@LINE+3]]:3: ERROR: Value of type `<associated <function> in HasF>` is not callable.
+  // CHECK:STDERR:   C.F();
+  // CHECK:STDERR:   ^~~~
   C.F();
+  // CHECK:STDERR: fail_todo_extend_impl.carbon:[[@LINE+3]]:3: ERROR: Value of type `<associated <function> in HasF>` is not callable.
+  // CHECK:STDERR:   c.F();
+  // CHECK:STDERR:   ^~~~
   c.F();
 }
 
-// CHECK:STDOUT: --- extend_impl.carbon
+// CHECK:STDOUT: --- fail_todo_extend_impl.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %.1: type = interface_type @HasF [template]
+// CHECK:STDOUT:   %.2: type = assoc_entity_type @HasF, <function> [template]
+// CHECK:STDOUT:   %.3: <associated <function> in HasF> = assoc_entity element0, @HasF.%F [template]
 // CHECK:STDOUT:   %C: type = class_type @C [template]
-// CHECK:STDOUT:   %.2: type = struct_type {} [template]
-// CHECK:STDOUT:   %.3: type = tuple_type () [template]
-// CHECK:STDOUT:   %.4: type = ptr_type {} [template]
+// CHECK:STDOUT:   %.4: type = struct_type {} [template]
+// CHECK:STDOUT:   %.5: type = tuple_type () [template]
+// CHECK:STDOUT:   %.6: type = ptr_type {} [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -40,9 +48,11 @@ fn G(c: C) {
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @HasF {
 // CHECK:STDOUT:   %F: <function> = fn_decl @F.1 [template]
+// CHECK:STDOUT:   %.loc8: <associated <function> in HasF> = assoc_entity element0, %F [template = constants.%.3]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .F = %F
+// CHECK:STDOUT:   .F = %.loc8
+// CHECK:STDOUT:   witness = (%F)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl: C as HasF {
@@ -53,7 +63,7 @@ fn G(c: C) {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @C {
-// CHECK:STDOUT:   impl_decl @impl, (<unexpected instref inst+6>)
+// CHECK:STDOUT:   impl_decl @impl, (<unexpected instref inst+9>)
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   extend name_scope1
@@ -69,11 +79,9 @@ fn G(c: C) {
 // CHECK:STDOUT: fn @G(%c: C) {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %C.ref: type = name_ref C, file.%C.decl [template = constants.%C]
-// CHECK:STDOUT:   %F.ref.loc20: <function> = name_ref F, @HasF.%F [template = @HasF.%F]
-// CHECK:STDOUT:   %.loc20: init () = call %F.ref.loc20()
+// CHECK:STDOUT:   %F.ref.loc23: <associated <function> in HasF> = name_ref F, @HasF.%.loc8 [template = constants.%.3]
 // CHECK:STDOUT:   %c.ref: C = name_ref c, %c
-// CHECK:STDOUT:   %F.ref.loc21: <function> = name_ref F, @HasF.%F [template = @HasF.%F]
-// CHECK:STDOUT:   %.loc21: init () = call %F.ref.loc21()
+// CHECK:STDOUT:   %F.ref.loc27: <associated <function> in HasF> = name_ref F, @HasF.%.loc8 [template = constants.%.3]
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 7 - 3
toolchain/check/testdata/impl/impl_as.carbon

@@ -18,8 +18,10 @@ class C {
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %.1: type = interface_type @Simple [template]
+// CHECK:STDOUT:   %.2: type = assoc_entity_type @Simple, <function> [template]
+// CHECK:STDOUT:   %.3: <associated <function> in Simple> = assoc_entity element0, @Simple.%F [template]
 // CHECK:STDOUT:   %C: type = class_type @C [template]
-// CHECK:STDOUT:   %.2: type = struct_type {} [template]
+// CHECK:STDOUT:   %.4: type = struct_type {} [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -30,9 +32,11 @@ class C {
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Simple {
 // CHECK:STDOUT:   %F: <function> = fn_decl @F.1 [template]
+// CHECK:STDOUT:   %.loc8: <associated <function> in Simple> = assoc_entity element0, %F [template = constants.%.3]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .F = %F
+// CHECK:STDOUT:   .F = %.loc8
+// CHECK:STDOUT:   witness = (%F)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl: C as Simple {
@@ -43,7 +47,7 @@ class C {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @C {
-// CHECK:STDOUT:   impl_decl @impl, (<unexpected instref inst+6>)
+// CHECK:STDOUT:   impl_decl @impl, (<unexpected instref inst+9>)
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: }

+ 6 - 2
toolchain/check/testdata/impl/impl_forall.carbon

@@ -16,19 +16,23 @@ impl forall [T:! type] T as Simple {
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %.1: type = interface_type @Simple [template]
+// CHECK:STDOUT:   %.2: type = assoc_entity_type @Simple, <function> [template]
+// CHECK:STDOUT:   %.3: <associated <function> in Simple> = assoc_entity element0, @Simple.%F [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace {.Simple = %Simple.decl} [template]
 // CHECK:STDOUT:   %Simple.decl = interface_decl @Simple, () [template = constants.%.1]
-// CHECK:STDOUT:   impl_decl @impl, (<unexpected instref inst+4>, <unexpected instref inst+5>, <unexpected instref inst+6>, <unexpected instref inst+7>)
+// CHECK:STDOUT:   impl_decl @impl, (<unexpected instref inst+7>, <unexpected instref inst+8>, <unexpected instref inst+9>, <unexpected instref inst+10>)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Simple {
 // CHECK:STDOUT:   %F: <function> = fn_decl @F.1 [template]
+// CHECK:STDOUT:   %.loc8: <associated <function> in Simple> = assoc_entity element0, %F [template = constants.%.3]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .F = %F
+// CHECK:STDOUT:   .F = %.loc8
+// CHECK:STDOUT:   witness = (%F)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl: T as Simple {

+ 1 - 0
toolchain/check/testdata/impl/redeclaration.carbon

@@ -33,6 +33,7 @@ impl i32 as I {}
 // CHECK:STDOUT: interface @I {
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl: i32 as I {

+ 1 - 0
toolchain/check/testdata/interface/as_type.carbon

@@ -24,6 +24,7 @@ fn F(e: Empty) {}
 // CHECK:STDOUT: interface @Empty {
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F(%e: Empty) {

+ 6 - 1
toolchain/check/testdata/interface/basic.carbon

@@ -18,6 +18,8 @@ interface ForwardDeclared {
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %.1: type = interface_type @Empty [template]
 // CHECK:STDOUT:   %.2: type = interface_type @ForwardDeclared [template]
+// CHECK:STDOUT:   %.3: type = assoc_entity_type @ForwardDeclared, <function> [template]
+// CHECK:STDOUT:   %.4: <associated <function> in ForwardDeclared> = assoc_entity element0, @ForwardDeclared.%F [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -30,13 +32,16 @@ interface ForwardDeclared {
 // CHECK:STDOUT: interface @Empty {
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @ForwardDeclared {
 // CHECK:STDOUT:   %F: <function> = fn_decl @F [template]
+// CHECK:STDOUT:   %.loc13: <associated <function> in ForwardDeclared> = assoc_entity element0, %F [template = constants.%.4]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .F = %F
+// CHECK:STDOUT:   .F = %.loc13
+// CHECK:STDOUT:   witness = (%F)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F();

+ 3 - 0
toolchain/check/testdata/interface/fail_add_member_outside_definition.carbon

@@ -44,6 +44,7 @@ interface Outer {
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .F = file.%F
+// CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Outer {
@@ -52,6 +53,7 @@ interface Outer {
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Inner = %Inner.decl
+// CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Inner {
@@ -59,6 +61,7 @@ interface Outer {
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .F = @Outer.%F
+// CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.1() {

+ 1 - 0
toolchain/check/testdata/interface/fail_as_type_of_type.carbon

@@ -30,6 +30,7 @@ fn F(T:! Empty) {
 // CHECK:STDOUT: interface @Empty {
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F(%T: Empty) {

+ 3 - 1
toolchain/check/testdata/interface/fail_duplicate.carbon

@@ -59,7 +59,8 @@ interface Class { }
 // CHECK:STDOUT:   %F: <function> = fn_decl @F [template]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .F = %F
+// CHECK:STDOUT:   .F = <error>
+// CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @.1;
@@ -67,6 +68,7 @@ interface Class { }
 // CHECK:STDOUT: interface @.2 {
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @Class;

+ 5 - 1
toolchain/check/testdata/interface/fail_lookup_undefined.carbon

@@ -46,6 +46,8 @@ interface BeingDefined {
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %.1: type = interface_type @Undefined [template]
 // CHECK:STDOUT:   %.2: type = interface_type @BeingDefined [template]
+// CHECK:STDOUT:   %.3: type = assoc_entity_type @BeingDefined, <function> [template]
+// CHECK:STDOUT:   %.4: <associated <function> in BeingDefined> = assoc_entity element0, @BeingDefined.%H [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -60,10 +62,12 @@ interface BeingDefined {
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @BeingDefined {
 // CHECK:STDOUT:   %H: <function> = fn_decl @H [template]
+// CHECK:STDOUT:   %.loc37: <associated <function> in BeingDefined> = assoc_entity element0, %H [template = constants.%.4]
 // CHECK:STDOUT:   %.loc41: <function> = fn_decl @.2 [template]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .H = %H
+// CHECK:STDOUT:   .H = %.loc37
+// CHECK:STDOUT:   witness = (%H)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @.1();

+ 10 - 6
toolchain/check/testdata/interface/member_lookup.carbon → toolchain/check/testdata/interface/fail_member_lookup.carbon

@@ -7,15 +7,18 @@
 interface Interface { fn F(); }
 
 fn F() {
-  // TODO: This should not be valid by itself.
+  // CHECK:STDERR: fail_member_lookup.carbon:[[@LINE+3]]:3: ERROR: Value of type `<associated <function> in Interface>` is not callable.
+  // CHECK:STDERR:   Interface.F();
+  // CHECK:STDERR:   ^~~~~~~~~~~~
   Interface.F();
 }
 
-// CHECK:STDOUT: --- member_lookup.carbon
+// CHECK:STDOUT: --- fail_member_lookup.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %.1: type = interface_type @Interface [template]
-// CHECK:STDOUT:   %.2: type = tuple_type () [template]
+// CHECK:STDOUT:   %.2: type = assoc_entity_type @Interface, <function> [template]
+// CHECK:STDOUT:   %.3: <associated <function> in Interface> = assoc_entity element0, @Interface.%F [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -26,9 +29,11 @@ fn F() {
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Interface {
 // CHECK:STDOUT:   %F: <function> = fn_decl @F.1 [template]
+// CHECK:STDOUT:   %.loc7: <associated <function> in Interface> = assoc_entity element0, %F [template = constants.%.3]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .F = %F
+// CHECK:STDOUT:   .F = %.loc7
+// CHECK:STDOUT:   witness = (%F)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.1();
@@ -36,8 +41,7 @@ fn F() {
 // CHECK:STDOUT: fn @F.2() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %Interface.ref: type = name_ref Interface, file.%Interface.decl [template = constants.%.1]
-// CHECK:STDOUT:   %F.ref: <function> = name_ref F, @Interface.%F [template = @Interface.%F]
-// CHECK:STDOUT:   %.loc11: init () = call %F.ref()
+// CHECK:STDOUT:   %F.ref: <associated <function> in Interface> = name_ref F, @Interface.%.loc7 [template = constants.%.3]
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 2 - 0
toolchain/check/testdata/interface/fail_modifiers.carbon

@@ -46,6 +46,7 @@ protected interface Protected;
 // CHECK:STDOUT: interface @Abstract {
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Default;
@@ -53,6 +54,7 @@ protected interface Protected;
 // CHECK:STDOUT: interface @Virtual {
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Protected;

+ 44 - 0
toolchain/check/testdata/interface/fail_redeclare_member.carbon

@@ -0,0 +1,44 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+
+interface Interface {
+  fn F();
+  // CHECK:STDERR: fail_redeclare_member.carbon:[[@LINE+6]]:3: ERROR: Duplicate name being declared in the same scope.
+  // CHECK:STDERR:   fn F();
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR: fail_redeclare_member.carbon:[[@LINE-4]]:3: Name is previously declared here.
+  // CHECK:STDERR:   fn F();
+  // CHECK:STDERR:   ^~~~~~~
+  fn F();
+}
+
+// CHECK:STDOUT: --- fail_redeclare_member.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: type = interface_type @Interface [template]
+// CHECK:STDOUT:   %.2: type = assoc_entity_type @Interface, <function> [template]
+// CHECK:STDOUT:   %.3: <associated <function> in Interface> = assoc_entity element0, @Interface.%F [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.Interface = %Interface.decl} [template]
+// CHECK:STDOUT:   %Interface.decl = interface_decl @Interface, () [template = constants.%.1]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @Interface {
+// CHECK:STDOUT:   %F: <function> = fn_decl @F [template]
+// CHECK:STDOUT:   %.loc8: <associated <function> in Interface> = assoc_entity element0, %F [template = constants.%.3]
+// CHECK:STDOUT:   %.loc15: <function> = fn_decl @.1 [template]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .F = %.loc8
+// CHECK:STDOUT:   witness = (%F)
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F();
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @.1();
+// CHECK:STDOUT:

+ 51 - 0
toolchain/check/testdata/interface/fail_todo_define_out_of_line.carbon

@@ -0,0 +1,51 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+
+interface Interface {
+  // CHECK:STDERR: fail_todo_define_out_of_line.carbon:[[@LINE+3]]:3: ERROR: Semantics TODO: `interface modifier`.
+  // CHECK:STDERR:   default fn F();
+  // CHECK:STDERR:   ^~~~~~~
+  default fn F();
+}
+
+// CHECK:STDERR: fail_todo_define_out_of_line.carbon:[[@LINE+6]]:1: ERROR: Duplicate name being declared in the same scope.
+// CHECK:STDERR: fn Interface.F() {}
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~
+// CHECK:STDERR: fail_todo_define_out_of_line.carbon:[[@LINE-6]]:3: Name is previously declared here.
+// CHECK:STDERR:   default fn F();
+// CHECK:STDERR:   ^~~~~~~~~~~~~~~
+fn Interface.F() {}
+
+// CHECK:STDOUT: --- fail_todo_define_out_of_line.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: type = interface_type @Interface [template]
+// CHECK:STDOUT:   %.2: type = assoc_entity_type @Interface, <function> [template]
+// CHECK:STDOUT:   %.3: <associated <function> in Interface> = assoc_entity element0, @Interface.%F [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.Interface = %Interface.decl} [template]
+// CHECK:STDOUT:   %Interface.decl = interface_decl @Interface, () [template = constants.%.1]
+// CHECK:STDOUT:   %.loc20: <function> = fn_decl @.1 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @Interface {
+// CHECK:STDOUT:   %F: <function> = fn_decl @F [template]
+// CHECK:STDOUT:   %.loc11: <associated <function> in Interface> = assoc_entity element0, %F [template = constants.%.3]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .F = %.loc11
+// CHECK:STDOUT:   witness = (%F)
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F();
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @.1() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 6 - 2
toolchain/check/testdata/interface/fail_todo_facet_lookup.carbon

@@ -17,7 +17,9 @@ fn CallStatic(T:! Interface) {
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %.1: type = interface_type @Interface [template]
-// CHECK:STDOUT:   %.2: type = tuple_type () [template]
+// CHECK:STDOUT:   %.2: type = assoc_entity_type @Interface, <function> [template]
+// CHECK:STDOUT:   %.3: <associated <function> in Interface> = assoc_entity element0, @Interface.%F [template]
+// CHECK:STDOUT:   %.4: type = tuple_type () [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -28,9 +30,11 @@ fn CallStatic(T:! Interface) {
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Interface {
 // CHECK:STDOUT:   %F: <function> = fn_decl @F [template]
+// CHECK:STDOUT:   %.loc7: <associated <function> in Interface> = assoc_entity element0, %F [template = constants.%.3]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .F = %F
+// CHECK:STDOUT:   .F = %.loc7
+// CHECK:STDOUT:   witness = (%F)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F();

+ 11 - 4
toolchain/check/testdata/interface/fail_todo_modifiers.carbon

@@ -25,27 +25,34 @@ private interface Private {
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %.1: type = interface_type @Modifiers [template]
-// CHECK:STDOUT:   %.2: type = interface_type @Private [template]
+// CHECK:STDOUT:   %.2: type = assoc_entity_type @Modifiers, <function> [template]
+// CHECK:STDOUT:   %.3: <associated <function> in Modifiers> = assoc_entity element0, @Modifiers.%Final [template]
+// CHECK:STDOUT:   %.4: <associated <function> in Modifiers> = assoc_entity element1, @Modifiers.%Default [template]
+// CHECK:STDOUT:   %.5: type = interface_type @Private [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace {.Modifiers = %Modifiers.decl, .Private = %Private.decl} [template]
 // CHECK:STDOUT:   %Modifiers.decl = interface_decl @Modifiers, () [template = constants.%.1]
-// CHECK:STDOUT:   %Private.decl = interface_decl @Private, () [template = constants.%.2]
+// CHECK:STDOUT:   %Private.decl = interface_decl @Private, () [template = constants.%.5]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Modifiers {
 // CHECK:STDOUT:   %Final: <function> = fn_decl @Final [template]
+// CHECK:STDOUT:   %.loc11: <associated <function> in Modifiers> = assoc_entity element0, %Final [template = constants.%.3]
 // CHECK:STDOUT:   %Default: <function> = fn_decl @Default [template]
+// CHECK:STDOUT:   %.loc15: <associated <function> in Modifiers> = assoc_entity element1, %Default [template = constants.%.4]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Final = %Final
-// CHECK:STDOUT:   .Default = %Default
+// CHECK:STDOUT:   .Final = %.loc11
+// CHECK:STDOUT:   .Default = %.loc15
+// CHECK:STDOUT:   witness = (%Final, %Default)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Private {
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Final() {

+ 16 - 8
toolchain/check/testdata/interface/import.carbon

@@ -35,9 +35,11 @@ var f: ForwardDeclared* = &f_ref.f;
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %.1: type = interface_type @Empty [template]
 // CHECK:STDOUT:   %.2: type = interface_type @ForwardDeclared [template]
-// CHECK:STDOUT:   %.3: type = struct_type {.f: ForwardDeclared} [template]
-// CHECK:STDOUT:   %.4: type = tuple_type () [template]
-// CHECK:STDOUT:   %.5: type = struct_type {.f: ()} [template]
+// CHECK:STDOUT:   %.3: type = assoc_entity_type @ForwardDeclared, <function> [template]
+// CHECK:STDOUT:   %.4: <associated <function> in ForwardDeclared> = assoc_entity element0, @ForwardDeclared.%F [template]
+// CHECK:STDOUT:   %.5: type = struct_type {.f: ForwardDeclared} [template]
+// CHECK:STDOUT:   %.6: type = tuple_type () [template]
+// CHECK:STDOUT:   %.7: type = struct_type {.f: ()} [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -46,7 +48,7 @@ var f: ForwardDeclared* = &f_ref.f;
 // CHECK:STDOUT:   %ForwardDeclared.decl.loc7 = interface_decl @ForwardDeclared, () [template = constants.%.2]
 // CHECK:STDOUT:   %ForwardDeclared.decl.loc9 = interface_decl @ForwardDeclared, () [template = constants.%.2]
 // CHECK:STDOUT:   %ForwardDeclared.ref: type = name_ref ForwardDeclared, %ForwardDeclared.decl.loc7 [template = constants.%.2]
-// CHECK:STDOUT:   %.loc13: type = struct_type {.f: ForwardDeclared} [template = constants.%.3]
+// CHECK:STDOUT:   %.loc13: type = struct_type {.f: ForwardDeclared} [template = constants.%.5]
 // CHECK:STDOUT:   %f_ref.var: ref {.f: ForwardDeclared} = var f_ref
 // CHECK:STDOUT:   %f_ref: ref {.f: ForwardDeclared} = bind_name f_ref, %f_ref.var
 // CHECK:STDOUT: }
@@ -54,13 +56,16 @@ var f: ForwardDeclared* = &f_ref.f;
 // CHECK:STDOUT: interface @Empty {
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @ForwardDeclared {
 // CHECK:STDOUT:   %F: <function> = fn_decl @F [template]
+// CHECK:STDOUT:   %.loc10: <associated <function> in ForwardDeclared> = assoc_entity element0, %F [template = constants.%.4]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .F = %F
+// CHECK:STDOUT:   .F = %.loc10
+// CHECK:STDOUT:   witness = (%F)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F();
@@ -80,7 +85,7 @@ var f: ForwardDeclared* = &f_ref.f;
 // CHECK:STDOUT:   package: <namespace> = namespace {.Empty = %import_ref.1, .ForwardDeclared = %import_ref.2, .f_ref = %import_ref.3, .UseEmpty = %UseEmpty, .UseForwardDeclared = %UseForwardDeclared, .f = %f} [template]
 // CHECK:STDOUT:   %import_ref.1: type = import_ref ir1, inst+1, used [template = constants.%.1]
 // CHECK:STDOUT:   %import_ref.2: type = import_ref ir1, inst+3, used [template = constants.%.3]
-// CHECK:STDOUT:   %import_ref.3: ref {.f: ForwardDeclared} = import_ref ir1, inst+16, used
+// CHECK:STDOUT:   %import_ref.3: ref {.f: ForwardDeclared} = import_ref ir1, inst+19, used
 // CHECK:STDOUT:   %UseEmpty: <function> = fn_decl @UseEmpty [template]
 // CHECK:STDOUT:   %UseForwardDeclared: <function> = fn_decl @UseForwardDeclared [template]
 // CHECK:STDOUT:   %ForwardDeclared.ref: type = name_ref ForwardDeclared, %import_ref.2 [template = constants.%.3]
@@ -92,13 +97,16 @@ var f: ForwardDeclared* = &f_ref.f;
 // CHECK:STDOUT: interface @Empty {
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @ForwardDeclared {
-// CHECK:STDOUT:   %import_ref = import_ref ir1, inst+6, unused
+// CHECK:STDOUT:   %import_ref.1 = import_ref ir1, inst+8, unused
+// CHECK:STDOUT:   %import_ref.2 = import_ref ir1, inst+6, unused
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .F = %import_ref
+// CHECK:STDOUT:   .F = %import_ref.1
+// CHECK:STDOUT:   witness = (%import_ref.2)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @UseEmpty(%e: Empty) {

+ 9 - 0
toolchain/lower/file_context.cpp

@@ -11,6 +11,7 @@
 #include "toolchain/sem_ir/entry_point.h"
 #include "toolchain/sem_ir/file.h"
 #include "toolchain/sem_ir/inst.h"
+#include "toolchain/sem_ir/typed_insts.h"
 
 namespace Carbon::Lower {
 
@@ -66,6 +67,10 @@ auto FileContext::GetGlobal(SemIR::InstId inst_id) -> llvm::Value* {
     return GetFunction(function_decl->function_id);
   }
 
+  if (target.Is<SemIR::AssociatedEntity>()) {
+    return llvm::ConstantStruct::getAnon(llvm_context(), {});
+  }
+
   if (target.type_id() == SemIR::TypeId::TypeType) {
     return GetTypeAsValue();
   }
@@ -274,6 +279,10 @@ auto FileContext::BuildType(SemIR::InstId inst_id) -> llvm::Type* {
           GetType(array_type.element_type_id),
           sem_ir_->GetArrayBoundValue(array_type.bound_id));
     }
+    case SemIR::AssociatedEntityType::Kind:
+      // No runtime operations are provided on an associated entity name, so use
+      // an empty representation.
+      return llvm::StructType::get(*llvm_context_);
     case SemIR::BindSymbolicName::Kind:
       // Treat non-monomorphized type bindings as opaque.
       return llvm::StructType::get(*llvm_context_);

+ 9 - 3
toolchain/lower/handle.cpp

@@ -60,6 +60,12 @@ auto HandleAssign(FunctionContext& context, SemIR::InstId /*inst_id*/,
   context.FinishInit(storage_type_id, inst.lhs_id, inst.rhs_id);
 }
 
+auto HandleAssociatedEntity(FunctionContext& /*context*/,
+                            SemIR::InstId /*inst_id*/,
+                            SemIR::AssociatedEntity inst) -> void {
+  FatalErrorIfEncountered(inst);
+}
+
 auto HandleBindAlias(FunctionContext& context, SemIR::InstId inst_id,
                      SemIR::BindAlias inst) -> void {
   auto type_inst_id = context.sem_ir().types().GetInstId(inst.type_id);
@@ -229,9 +235,9 @@ auto HandleInitializeFrom(FunctionContext& context, SemIR::InstId /*inst_id*/,
 }
 
 auto HandleInterfaceDecl(FunctionContext& /*context*/,
-                         SemIR::InstId /*inst_id*/,
-                         SemIR::InterfaceDecl /*inst*/) -> void {
-  // No action to perform.
+                         SemIR::InstId /*inst_id*/, SemIR::InterfaceDecl inst)
+    -> void {
+  FatalErrorIfEncountered(inst);
 }
 
 auto HandleIntLiteral(FunctionContext& context, SemIR::InstId inst_id,

+ 6 - 0
toolchain/lower/handle_type.cpp

@@ -3,6 +3,7 @@
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
 #include "toolchain/lower/function_context.h"
+#include "toolchain/sem_ir/typed_insts.h"
 
 namespace Carbon::Lower {
 
@@ -11,6 +12,11 @@ auto HandleArrayType(FunctionContext& context, SemIR::InstId inst_id,
   context.SetLocal(inst_id, context.GetTypeAsValue());
 }
 
+auto HandleAssociatedEntityType(FunctionContext& context, SemIR::InstId inst_id,
+                                SemIR::AssociatedEntityType /*inst*/) -> void {
+  context.SetLocal(inst_id, context.GetTypeAsValue());
+}
+
 auto HandleClassType(FunctionContext& context, SemIR::InstId inst_id,
                      SemIR::ClassType /*inst*/) -> void {
   context.SetLocal(inst_id, context.GetTypeAsValue());

+ 20 - 0
toolchain/lower/testdata/interface/assoc.carbon

@@ -0,0 +1,20 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+
+interface I {
+  fn Assoc();
+}
+
+fn F() { I.Assoc; }
+
+// CHECK:STDOUT: ; ModuleID = 'assoc.carbon'
+// CHECK:STDOUT: source_filename = "assoc.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare void @Assoc()
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @F() {
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }

+ 31 - 0
toolchain/lower/testdata/interface/basic.carbon

@@ -0,0 +1,31 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+
+interface I {
+  fn Assoc();
+}
+
+// There are no interesting runtime operations here, but there's no rule saying
+// you can't pass a facet around at runtime, so make sure it works.
+fn F(T: I) -> I { return T; }
+
+interface J;
+
+// Declared-but-not-defined interfaces are still complete types.
+fn G(T: J) {}
+
+// CHECK:STDOUT: ; ModuleID = 'basic.carbon'
+// CHECK:STDOUT: source_filename = "basic.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare void @Assoc()
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @F() {
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @G() {
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }

+ 19 - 0
toolchain/sem_ir/file.cpp

@@ -191,6 +191,7 @@ auto File::OutputYaml(bool include_builtins) const -> Yaml::OutputMapping {
 static auto GetTypePrecedence(InstKind kind) -> int {
   switch (kind) {
     case ArrayType::Kind:
+    case AssociatedEntityType::Kind:
     case BindAlias::Kind:
     case BindSymbolicName::Kind:
     case Builtin::Kind:
@@ -212,6 +213,7 @@ static auto GetTypePrecedence(InstKind kind) -> int {
     case ArrayIndex::Kind:
     case ArrayInit::Kind:
     case Assign::Kind:
+    case AssociatedEntity::Kind:
     case BaseDecl::Kind:
     case BindName::Kind:
     case BindValue::Kind:
@@ -316,6 +318,20 @@ static auto StringifyTypeExprImpl(const SemIR::File& outer_sem_ir,
         }
         break;
       }
+      case AssociatedEntityType::Kind: {
+        auto assoc = inst.As<AssociatedEntityType>();
+        if (step.index == 0) {
+          out << "<associated ";
+          steps.push_back(step.Next());
+          push_inst_id(sem_ir.types().GetInstId(assoc.entity_type_id));
+        } else {
+          auto interface_name_id =
+              sem_ir.interfaces().Get(assoc.interface_id).name_id;
+          out << " in " << sem_ir.names().GetFormatted(interface_name_id)
+              << ">";
+        }
+        break;
+      }
       case BindAlias::Kind:
       case BindSymbolicName::Kind: {
         auto name_id = inst.As<AnyBindName>().bind_name_id;
@@ -437,6 +453,7 @@ static auto StringifyTypeExprImpl(const SemIR::File& outer_sem_ir,
       case ArrayIndex::Kind:
       case ArrayInit::Kind:
       case Assign::Kind:
+      case AssociatedEntity::Kind:
       case BaseDecl::Kind:
       case BindName::Kind:
       case BindValue::Kind:
@@ -552,6 +569,8 @@ auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory {
       case AddrOf::Kind:
       case AddrPattern::Kind:
       case ArrayType::Kind:
+      case AssociatedEntity::Kind:
+      case AssociatedEntityType::Kind:
       case BindSymbolicName::Kind:
       case BindValue::Kind:
       case BlockArg::Kind:

+ 2 - 0
toolchain/sem_ir/formatter.cpp

@@ -649,6 +649,8 @@ class Formatter {
       FormatCodeBlock(interface_info.body_block_id);
       out_ << "\n!members:";
       FormatNameScope(interface_info.scope_id, "", "\n  ");
+      out_ << "\n  witness = ";
+      FormatArg(interface_info.associated_entities_id);
       out_ << "\n}\n";
     } else {
       out_ << ";\n";

+ 10 - 0
toolchain/sem_ir/inst.h

@@ -343,6 +343,16 @@ class InstStore {
     return Get(inst_id).TryAs<InstT>();
   }
 
+  // Returns the requested instruction as the specified type, if it is valid and
+  // of that type. Otherwise returns nullopt.
+  template <typename InstT>
+  auto TryGetAsIfValid(InstId inst_id) const -> std::optional<InstT> {
+    if (!inst_id.is_valid()) {
+      return std::nullopt;
+    }
+    return TryGetAs<InstT>(inst_id);
+  }
+
   auto GetParseNode(InstId inst_id) const -> Parse::NodeId {
     return parse_nodes_[inst_id.index];
   }

+ 2 - 0
toolchain/sem_ir/inst_kind.def

@@ -23,6 +23,8 @@ CARBON_SEM_IR_INST_KIND(ArrayIndex)
 CARBON_SEM_IR_INST_KIND(ArrayInit)
 CARBON_SEM_IR_INST_KIND(ArrayType)
 CARBON_SEM_IR_INST_KIND(Assign)
+CARBON_SEM_IR_INST_KIND(AssociatedEntity)
+CARBON_SEM_IR_INST_KIND(AssociatedEntityType)
 CARBON_SEM_IR_INST_KIND(BaseDecl)
 CARBON_SEM_IR_INST_KIND(BindAlias)
 CARBON_SEM_IR_INST_KIND(BindName)

+ 8 - 2
toolchain/sem_ir/interface.h

@@ -18,7 +18,13 @@ struct Interface : public Printable<Interface> {
 
   // Determines whether this interface has been fully defined. This is false
   // until we reach the `}` of the interface definition.
-  auto is_defined() const -> bool { return defined; }
+  auto is_defined() const -> bool { return associated_entities_id.is_valid(); }
+
+  // Determines whether we're currently defining the interface. This is true
+  // between the braces of the interface.
+  auto is_being_defined() const -> bool {
+    return definition_id.is_valid() && !is_defined();
+  }
 
   // The following members always have values, and do not change throughout the
   // lifetime of the interface.
@@ -41,7 +47,7 @@ struct Interface : public Printable<Interface> {
   InstBlockId body_block_id = InstBlockId::Invalid;
 
   // The following members are set at the `}` of the interface definition.
-  bool defined = false;
+  InstBlockId associated_entities_id = InstBlockId::Invalid;
 };
 
 }  // namespace Carbon::SemIR

+ 10 - 0
toolchain/sem_ir/name_scope.h

@@ -95,6 +95,16 @@ class NameScopeStore {
     return values_.Get(scope_id);
   }
 
+  // Returns the instruction owning the requested name scope, or an invalid
+  // instruction if the scope is either invalid or has no associated
+  // instruction.
+  auto GetInstIdIfValid(NameScopeId scope_id) const -> InstId {
+    if (!scope_id.is_valid()) {
+      return InstId::Invalid;
+    }
+    return Get(scope_id).inst_id;
+  }
+
   auto OutputYaml() const -> Yaml::OutputMapping {
     return values_.OutputYaml();
   }

+ 26 - 0
toolchain/sem_ir/typed_insts.h

@@ -154,6 +154,32 @@ struct Assign {
   InstId rhs_id;
 };
 
+// An associated entity declared in an interface. This is either an associated
+// function or a non-function associated constant such as an associated type.
+// This represents the entity before impl lookup is performed, and identifies
+// the slot within a witness where the constant value will be found.
+struct AssociatedEntity {
+  static constexpr auto Kind =
+      InstKind::AssociatedEntity.Define<Parse::NodeId>("assoc_entity");
+
+  // The type of the associated entity. This is an AssociatedEntityType.
+  TypeId type_id;
+  ElementIndex index;
+  InstId decl_id;
+};
+
+// The type of an expression that names an associated entity, such as
+// `InterfaceName.Function`.
+struct AssociatedEntityType {
+  static constexpr auto Kind =
+      InstKind::AssociatedEntityType.Define<Parse::InvalidNodeId>(
+          "assoc_entity_type");
+
+  TypeId type_id;
+  InterfaceId interface_id;
+  TypeId entity_type_id;
+};
+
 // A base in a class, of the form `base: base_type;`. A base class is an
 // element of the derived class, and the type of the `BaseDecl` instruction is
 // an `UnboundElementType`.