Selaa lähdekoodia

Basic support for looking up impl members when naming an associated entity. (#3776)

When a member access names an interface member, perform impl lookup to
find the impl and its corresponding member.

---------

Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
Richard Smith 2 vuotta sitten
vanhempi
sitoutus
1d720dc001
34 muutettua tiedostoa jossa 749 lisäystä ja 83 poistoa
  1. 1 0
      toolchain/check/BUILD
  2. 1 0
      toolchain/check/context.cpp
  3. 2 1
      toolchain/check/eval.cpp
  4. 1 2
      toolchain/check/impl.cpp
  5. 137 22
      toolchain/check/member_access.cpp
  6. 1 1
      toolchain/check/testdata/class/fail_incomplete.carbon
  7. 1 1
      toolchain/check/testdata/class/fail_reorder.carbon
  8. 2 2
      toolchain/check/testdata/impl/basic.carbon
  9. 2 2
      toolchain/check/testdata/impl/empty.carbon
  10. 9 13
      toolchain/check/testdata/impl/extend_impl.carbon
  11. 2 2
      toolchain/check/testdata/impl/fail_extend_impl_scope.carbon
  12. 3 3
      toolchain/check/testdata/impl/fail_extend_impl_type_as.carbon
  13. 2 2
      toolchain/check/testdata/impl/fail_impl_as_scope.carbon
  14. 13 13
      toolchain/check/testdata/impl/fail_impl_bad_assoc_fn.carbon
  15. 2 2
      toolchain/check/testdata/impl/fail_impl_bad_type.carbon
  16. 1 1
      toolchain/check/testdata/impl/fail_redefinition.carbon
  17. 1 1
      toolchain/check/testdata/impl/fail_todo_self_in_signature.carbon
  18. 2 2
      toolchain/check/testdata/impl/impl_as.carbon
  19. 2 2
      toolchain/check/testdata/impl/impl_forall.carbon
  20. 105 0
      toolchain/check/testdata/impl/lookup/alias.carbon
  21. 84 0
      toolchain/check/testdata/impl/lookup/fail_alias_impl_not_found.carbon
  22. 98 0
      toolchain/check/testdata/impl/lookup/fail_todo_undefined_impl.carbon
  23. 109 0
      toolchain/check/testdata/impl/lookup/instance_method.carbon
  24. 2 2
      toolchain/check/testdata/impl/redeclaration.carbon
  25. 2 2
      toolchain/check/testdata/interface/fail_lookup_undefined.carbon
  26. 3 0
      toolchain/diagnostics/diagnostic_kind.def
  27. 6 0
      toolchain/lower/file_context.cpp
  28. 10 0
      toolchain/lower/handle.cpp
  29. 42 0
      toolchain/lower/testdata/impl/assoc_fn_alias.carbon
  30. 44 0
      toolchain/lower/testdata/impl/extend_impl.carbon
  31. 38 0
      toolchain/lower/testdata/impl/instance_method.carbon
  32. 3 0
      toolchain/sem_ir/file.cpp
  33. 1 0
      toolchain/sem_ir/inst_kind.def
  34. 17 7
      toolchain/sem_ir/typed_insts.h

+ 1 - 0
toolchain/check/BUILD

@@ -199,6 +199,7 @@ cc_library(
     deps = [
         ":context",
         "//common:check",
+        "//toolchain/diagnostics:diagnostic_emitter",
         "//toolchain/sem_ir:file",
         "//toolchain/sem_ir:ids",
         "//toolchain/sem_ir:inst",

+ 1 - 0
toolchain/check/context.cpp

@@ -842,6 +842,7 @@ class TypeCompleter {
       case SemIR::InitializeFrom::Kind:
       case SemIR::InterfaceDecl::Kind:
       case SemIR::InterfaceWitness::Kind:
+      case SemIR::InterfaceWitnessAccess::Kind:
       case SemIR::IntLiteral::Kind:
       case SemIR::NameRef::Kind:
       case SemIR::Namespace::Kind:

+ 2 - 1
toolchain/check/eval.cpp

@@ -330,7 +330,7 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
                                         &SemIR::BoundMethod::function_id);
     case SemIR::InterfaceWitness::Kind:
       return RebuildIfFieldsAreConstant(context, inst,
-                                        &SemIR::InterfaceWitness::table_id);
+                                        &SemIR::InterfaceWitness::elements_id);
     case SemIR::PointerType::Kind:
       return RebuildIfFieldsAreConstant(context, inst,
                                         &SemIR::PointerType::pointee_id);
@@ -417,6 +417,7 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
 
     // The elements of a constant aggregate can be accessed.
     case SemIR::ClassElementAccess::Kind:
+    case SemIR::InterfaceWitnessAccess::Kind:
     case SemIR::StructAccess::Kind:
     case SemIR::TupleAccess::Kind:
       return PerformAggregateAccess(context, inst);

+ 1 - 2
toolchain/check/impl.cpp

@@ -114,8 +114,7 @@ static auto BuildInterfaceWitness(
 
   auto table_id = context.inst_blocks().Add(table);
   return context.AddInst(SemIR::InterfaceWitness{
-      context.GetBuiltinType(SemIR::BuiltinKind::WitnessType), interface_id,
-      table_id});
+      context.GetBuiltinType(SemIR::BuiltinKind::WitnessType), table_id});
 }
 
 auto BuildImplWitness(Context& context, SemIR::ImplId impl_id)

+ 137 - 22
toolchain/check/member_access.cpp

@@ -5,6 +5,7 @@
 #include "llvm/ADT/STLExtras.h"
 #include "toolchain/check/context.h"
 #include "toolchain/check/convert.h"
+#include "toolchain/diagnostics/diagnostic_emitter.h"
 #include "toolchain/sem_ir/inst.h"
 #include "toolchain/sem_ir/typed_insts.h"
 
@@ -12,13 +13,9 @@ namespace Carbon::Check {
 
 // Returns the name scope corresponding to base_id, or nullopt if not a scope.
 // On invalid scopes, prints a diagnostic and still returns the scope.
-static auto GetAsNameScope(Context& context, SemIR::InstId base_id)
+static auto GetAsNameScope(Context& context, Parse::NodeId node_id,
+                           SemIR::ConstantId base_const_id)
     -> std::optional<SemIR::NameScopeId> {
-  auto base_const_id = context.constant_values().Get(base_id);
-  if (!base_const_id.is_constant()) {
-    // A name scope must be a constant.
-    return std::nullopt;
-  }
   auto base = context.insts().Get(base_const_id.inst_id());
   if (auto base_as_namespace = base.TryAs<SemIR::Namespace>()) {
     return base_as_namespace->name_scope_id;
@@ -31,9 +28,9 @@ static auto GetAsNameScope(Context& context, SemIR::InstId base_id)
       CARBON_DIAGNOSTIC(QualifiedExprInIncompleteClassScope, Error,
                         "Member access into incomplete class `{0}`.",
                         std::string);
-      auto builder =
-          context.emitter().Build(base_id, QualifiedExprInIncompleteClassScope,
-                                  context.sem_ir().StringifyTypeExpr(base_id));
+      auto builder = context.emitter().Build(
+          node_id, QualifiedExprInIncompleteClassScope,
+          context.sem_ir().StringifyTypeExpr(base_const_id.inst_id()));
       context.NoteIncompleteClass(base_as_class->class_id, builder);
       builder.Emit();
     }
@@ -47,8 +44,8 @@ static auto GetAsNameScope(Context& context, SemIR::InstId base_id)
                         "Member access into undefined interface `{0}`.",
                         std::string);
       auto builder = context.emitter().Build(
-          base_id, QualifiedExprInUndefinedInterfaceScope,
-          context.sem_ir().StringifyTypeExpr(base_id));
+          node_id, QualifiedExprInUndefinedInterfaceScope,
+          context.sem_ir().StringifyTypeExpr(base_const_id.inst_id()));
       context.NoteUndefinedInterface(base_as_interface->interface_id, builder);
       builder.Emit();
     }
@@ -93,12 +90,111 @@ static auto IsInstanceMethod(const SemIR::File& sem_ir,
   return false;
 }
 
-// Performs a member name lookup into the specified scope. If the scope is
-// invalid, assume an error has already been diagnosed, and return BuiltinError.
+// Returns whether `name_scope_id` is a scope for which impl lookup should be
+// performed if we find an associated entity.
+static auto ScopeNeedsImplLookup(Context& context,
+                                 SemIR::NameScopeId name_scope_id) -> bool {
+  auto inst_id = context.name_scopes().GetInstIdIfValid(name_scope_id);
+  if (!inst_id.is_valid()) {
+    return false;
+  }
+
+  auto inst = context.insts().Get(inst_id);
+  if (inst.Is<SemIR::InterfaceDecl>()) {
+    // Don't perform impl lookup if an associated entity is named as a member of
+    // a facet type.
+    return false;
+  }
+  if (inst.Is<SemIR::Namespace>()) {
+    // Don't perform impl lookup if an associated entity is named as a namespace
+    // member.
+    // TODO: This case is not yet listed in the design.
+    return false;
+  }
+  // Any other kind of scope is assumed to be a type that implements the
+  // interface containing the associated entity, and impl lookup is performed.
+  return true;
+}
+
+// Given a type and an interface, searches for an impl that describes how that
+// type implements that interface, and returns the corresponding witness.
+// Returns an invalid InstId if no matching impl is found.
+static auto LookupInterfaceWitness(Context& context,
+                                   SemIR::ConstantId type_const_id,
+                                   SemIR::InterfaceId interface_id)
+    -> SemIR::InstId {
+  // TODO: Add a better impl lookup system. At the very least, we should only be
+  // considering impls that are for the same interface we're querying. We can
+  // also skip impls that mention any types that aren't part of our impl query.
+  for (const auto& impl : context.impls().array_ref()) {
+    if (context.types().GetInstId(impl.self_id) != type_const_id.inst_id()) {
+      continue;
+    }
+    auto interface_type =
+        context.types().TryGetAs<SemIR::InterfaceType>(impl.constraint_id);
+    if (!interface_type) {
+      // TODO: An impl of a constraint type should be treated as implementing
+      // the constraint's interfaces.
+      continue;
+    }
+    if (interface_type->interface_id != interface_id) {
+      continue;
+    }
+    // TODO: Diagnose if the impl isn't defined yet?
+    return impl.witness_id;
+  }
+  return SemIR::InstId::Invalid;
+}
+
+// Performs impl lookup for a member name expression. This finds the relevant
+// impl witness and extracts the corresponding impl member.
+static auto PerformImplLookup(Context& context, SemIR::ConstantId type_const_id,
+                              SemIR::AssociatedEntityType assoc_type,
+                              SemIR::InstId member_id) -> SemIR::InstId {
+  auto witness_id =
+      LookupInterfaceWitness(context, type_const_id, assoc_type.interface_id);
+  if (!witness_id.is_valid()) {
+    CARBON_DIAGNOSTIC(MissingImplInMemberAccess, Error,
+                      "Cannot access member of interface {0} in type {1} "
+                      "that does not implement that interface.",
+                      SemIR::NameId, std::string);
+    context.emitter().Emit(
+        member_id, MissingImplInMemberAccess,
+        context.interfaces().Get(assoc_type.interface_id).name_id,
+        context.sem_ir().StringifyTypeExpr(type_const_id.inst_id()));
+    return SemIR::InstId::BuiltinError;
+  }
+
+  auto const_id = context.constant_values().Get(member_id);
+  if (!const_id.is_constant()) {
+    if (const_id != SemIR::ConstantId::Error) {
+      context.TODO(context.insts().GetNodeId(member_id),
+                   "non-constant associated entity");
+    }
+    return SemIR::InstId::BuiltinError;
+  }
+
+  auto assoc_entity =
+      context.insts().TryGetAs<SemIR::AssociatedEntity>(const_id.inst_id());
+  if (!assoc_entity) {
+    context.TODO(context.insts().GetNodeId(member_id),
+                 "unexpected value for associated entity");
+    return SemIR::InstId::BuiltinError;
+  }
+
+  // TODO: Substitute interface arguments and `Self` into `entity_type_id`.
+  return context.AddInst(SemIR::InterfaceWitnessAccess{
+      assoc_type.entity_type_id, witness_id, assoc_entity->index});
+}
+
+// Performs a member name lookup into the specified scope, including performing
+// impl lookup if necessary. If the scope is invalid, assume an error has
+// already been diagnosed, and return BuiltinError.
 static auto LookupMemberNameInScope(Context& context,
                                     Parse::MemberAccessExprId node_id,
                                     SemIR::InstId /*base_id*/,
                                     SemIR::NameId name_id,
+                                    SemIR::ConstantId name_scope_const_id,
                                     SemIR::NameScopeId name_scope_id)
     -> SemIR::InstId {
   auto inst_id = name_scope_id.is_valid() ? context.LookupQualifiedName(
@@ -107,8 +203,24 @@ static auto LookupMemberNameInScope(Context& context,
   auto inst = context.insts().Get(inst_id);
   // TODO: Use a different kind of instruction that also references the
   // `base_id` so that `SemIR` consumers can find it.
-  return context.AddInst(
+  auto member_id = context.AddInst(
       {node_id, SemIR::NameRef{inst.type_id(), name_id, inst_id}});
+
+  // If member name lookup finds an associated entity name, and the scope is not
+  // a facet type, perform impl lookup.
+  //
+  // TODO: We need to do this as part of searching extended scopes, because a
+  // lookup that finds an associated entity and also finds the corresponding
+  // impl member is not supposed to be treated as ambiguous.
+  if (auto assoc_type = context.types().TryGetAs<SemIR::AssociatedEntityType>(
+          inst.type_id())) {
+    if (ScopeNeedsImplLookup(context, name_scope_id)) {
+      member_id = PerformImplLookup(context, name_scope_const_id, *assoc_type,
+                                    member_id);
+    }
+  }
+
+  return member_id;
 }
 
 // Performs the instance binding step in member access. If the found member is a
@@ -180,9 +292,12 @@ auto PerformMemberAccess(Context& context, Parse::MemberAccessExprId node_id,
     -> SemIR::InstId {
   // If the base is a name scope, such as a class or namespace, perform lookup
   // into that scope.
-  if (auto name_scope_id = GetAsNameScope(context, base_id)) {
-    return LookupMemberNameInScope(context, node_id, base_id, name_id,
-                                   *name_scope_id);
+  if (auto base_const_id = context.constant_values().Get(base_id);
+      base_const_id.is_constant()) {
+    if (auto name_scope_id = GetAsNameScope(context, node_id, base_const_id)) {
+      return LookupMemberNameInScope(context, node_id, base_id, name_id,
+                                     base_const_id, *name_scope_id);
+    }
   }
 
   // If the base isn't a scope, it must have a complete type.
@@ -200,14 +315,14 @@ auto PerformMemberAccess(Context& context, Parse::MemberAccessExprId node_id,
   // Materialize a temporary for the base expression if necessary.
   base_id = ConvertToValueOrRefExpr(context, base_id);
   base_type_id = context.insts().Get(base_id).type_id();
-  auto base_type_inst_id = context.types().GetInstId(base_type_id);
+  auto base_type_const_id = context.types().GetConstantId(base_type_id);
 
   // Find the scope corresponding to the base type.
-  auto name_scope_id = GetAsNameScope(context, base_type_inst_id);
+  auto name_scope_id = GetAsNameScope(context, node_id, base_type_const_id);
   if (!name_scope_id) {
     // The base type is not a name scope. Try some fallback options.
-    if (auto struct_type =
-            context.insts().TryGetAs<SemIR::StructType>(base_type_inst_id)) {
+    if (auto struct_type = context.insts().TryGetAs<SemIR::StructType>(
+            base_type_const_id.inst_id())) {
       // TODO: Do we need to optimize this with a lookup table for O(1)?
       for (auto [i, ref_id] :
            llvm::enumerate(context.inst_blocks().Get(struct_type->fields_id))) {
@@ -239,7 +354,7 @@ auto PerformMemberAccess(Context& context, Parse::MemberAccessExprId node_id,
 
   // Perform lookup into the base type.
   auto member_id = LookupMemberNameInScope(context, node_id, base_id, name_id,
-                                           *name_scope_id);
+                                           base_type_const_id, *name_scope_id);
 
   // Perform instance binding if we found an instance member.
   member_id = PerformInstanceBinding(context, node_id, base_id, member_id);

+ 1 - 1
toolchain/check/testdata/class/fail_incomplete.carbon

@@ -17,7 +17,7 @@ fn Class.Function() {}
 fn CallClassFunction() {
   // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+6]]:3: ERROR: Member access into incomplete class `Class`.
   // CHECK:STDERR:   Class.Function();
-  // CHECK:STDERR:   ^~~~~
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~
   // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-14]]:1: Class was forward declared here.
   // CHECK:STDERR: class Class;
   // CHECK:STDERR: ^~~~~~~~~~~~

+ 1 - 1
toolchain/check/testdata/class/fail_reorder.carbon

@@ -10,7 +10,7 @@ class Class {
     // later.
     // CHECK:STDERR: fail_reorder.carbon:[[@LINE+9]]:12: ERROR: Member access into incomplete class `Class`.
     // CHECK:STDERR:     return Class.F();
-    // CHECK:STDERR:            ^~~~~
+    // CHECK:STDERR:            ^~~~~~~
     // CHECK:STDERR: fail_reorder.carbon:[[@LINE-7]]:1: Class is incomplete within its definition.
     // CHECK:STDERR: class Class {
     // CHECK:STDERR: ^~~~~~~~~~~~~

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

@@ -18,7 +18,7 @@ impl i32 as Simple {
 // 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:   %.4: <witness> = interface_witness @Simple, (@impl.%F) [template]
+// CHECK:STDOUT:   %.4: <witness> = interface_witness (@impl.%F) [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -44,7 +44,7 @@ impl i32 as Simple {
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl: i32 as Simple {
 // CHECK:STDOUT:   %F: <function> = fn_decl @F.2 [template] {}
-// CHECK:STDOUT:   %.1: <witness> = interface_witness @Simple, (%F) [template = constants.%.4]
+// CHECK:STDOUT:   %.1: <witness> = interface_witness (%F) [template = constants.%.4]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .F = %F

+ 2 - 2
toolchain/check/testdata/impl/empty.carbon

@@ -14,7 +14,7 @@ impl i32 as Empty {
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %.1: type = interface_type @Empty [template]
-// CHECK:STDOUT:   %.2: <witness> = interface_witness @Empty, () [template]
+// CHECK:STDOUT:   %.2: <witness> = interface_witness () [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -36,7 +36,7 @@ impl i32 as Empty {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl: i32 as Empty {
-// CHECK:STDOUT:   %.1: <witness> = interface_witness @Empty, () [template = constants.%.2]
+// CHECK:STDOUT:   %.1: <witness> = interface_witness () [template = constants.%.2]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   witness = %.1

+ 9 - 13
toolchain/check/testdata/impl/fail_todo_extend_impl.carbon → toolchain/check/testdata/impl/extend_impl.carbon

@@ -15,26 +15,18 @@ class C {
 }
 
 fn G(c: C) {
-  // 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: --- fail_todo_extend_impl.carbon
+// CHECK:STDOUT: --- 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:   %.4: <witness> = interface_witness @HasF, (@impl.%F) [template]
+// CHECK:STDOUT:   %.4: <witness> = interface_witness (@impl.%F) [template]
 // CHECK:STDOUT:   %.5: type = struct_type {} [template]
 // CHECK:STDOUT:   %.6: type = tuple_type () [template]
 // CHECK:STDOUT:   %.7: type = ptr_type {} [template]
@@ -68,7 +60,7 @@ fn G(c: C) {
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl: C as HasF {
 // CHECK:STDOUT:   %F: <function> = fn_decl @F.2 [template] {}
-// CHECK:STDOUT:   %.1: <witness> = interface_witness @HasF, (%F) [template = constants.%.4]
+// CHECK:STDOUT:   %.1: <witness> = interface_witness (%F) [template = constants.%.4]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .F = %F
@@ -95,9 +87,13 @@ 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.loc23: <associated <function> in HasF> = name_ref F, @HasF.%.loc8 [template = constants.%.3]
+// CHECK:STDOUT:   %F.ref.loc18: <associated <function> in HasF> = name_ref F, @HasF.%.loc8 [template = constants.%.3]
+// CHECK:STDOUT:   %.1: <function> = interface_witness_access @impl.%.1, element0 [template = @impl.%F]
+// CHECK:STDOUT:   %.loc18: init () = call %.1()
 // CHECK:STDOUT:   %c.ref: C = name_ref c, %c
-// CHECK:STDOUT:   %F.ref.loc27: <associated <function> in HasF> = name_ref F, @HasF.%.loc8 [template = constants.%.3]
+// CHECK:STDOUT:   %F.ref.loc19: <associated <function> in HasF> = name_ref F, @HasF.%.loc8 [template = constants.%.3]
+// CHECK:STDOUT:   %.2: <function> = interface_witness_access @impl.%.1, element0 [template = @impl.%F]
+// CHECK:STDOUT:   %.loc19: init () = call %.2()
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 2 - 2
toolchain/check/testdata/impl/fail_extend_impl_scope.carbon

@@ -15,7 +15,7 @@ extend impl i32 as I {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %.1: type = interface_type @I [template]
-// CHECK:STDOUT:   %.2: <witness> = interface_witness @I, () [template]
+// CHECK:STDOUT:   %.2: <witness> = interface_witness () [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -37,7 +37,7 @@ extend impl i32 as I {}
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl: i32 as I {
-// CHECK:STDOUT:   %.1: <witness> = interface_witness @I, () [template = constants.%.2]
+// CHECK:STDOUT:   %.1: <witness> = interface_witness () [template = constants.%.2]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   witness = %.1

+ 3 - 3
toolchain/check/testdata/impl/fail_extend_impl_type_as.carbon

@@ -38,7 +38,7 @@ class E {
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %.1: type = interface_type @I [template]
 // CHECK:STDOUT:   %C: type = class_type @C [template]
-// CHECK:STDOUT:   %.2: <witness> = interface_witness @I, () [template]
+// CHECK:STDOUT:   %.2: <witness> = interface_witness () [template]
 // CHECK:STDOUT:   %.3: type = struct_type {} [template]
 // CHECK:STDOUT:   %D: type = class_type @D [template]
 // CHECK:STDOUT:   %E: type = class_type @E [template]
@@ -66,7 +66,7 @@ class E {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl.1: i32 as I {
-// CHECK:STDOUT:   %.1: <witness> = interface_witness @I, () [template = constants.%.2]
+// CHECK:STDOUT:   %.1: <witness> = interface_witness () [template = constants.%.2]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   witness = %.1
@@ -75,7 +75,7 @@ class E {
 // CHECK:STDOUT: impl @impl.2: D as I;
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl.3: E as I {
-// CHECK:STDOUT:   %.1: <witness> = interface_witness @I, () [template = constants.%.2]
+// CHECK:STDOUT:   %.1: <witness> = interface_witness () [template = constants.%.2]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   witness = %.1

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

@@ -21,7 +21,7 @@ impl as Simple {
 // 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:   %.4: <witness> = interface_witness @Simple, (@impl.%F) [template]
+// CHECK:STDOUT:   %.4: <witness> = interface_witness (@impl.%F) [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -47,7 +47,7 @@ impl as Simple {
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl: <error> as Simple {
 // CHECK:STDOUT:   %F: <function> = fn_decl @F.2 [template] {}
-// CHECK:STDOUT:   %.1: <witness> = interface_witness @Simple, (%F) [template = constants.%.4]
+// CHECK:STDOUT:   %.1: <witness> = interface_witness (%F) [template = constants.%.4]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .F = %F

+ 13 - 13
toolchain/check/testdata/impl/fail_impl_bad_assoc_fn.carbon

@@ -259,7 +259,7 @@ class FDifferentParamName {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl.1: NoF as I {
-// CHECK:STDOUT:   %.1: <witness> = interface_witness @I, (<error>) [template = <error>]
+// CHECK:STDOUT:   %.1: <witness> = interface_witness (<error>) [template = <error>]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   witness = %.1
@@ -267,7 +267,7 @@ class FDifferentParamName {
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl.2: FNotFunction as I {
 // CHECK:STDOUT:   %F.decl: type = class_decl @F.13 [template = constants.%F] {}
-// CHECK:STDOUT:   %.1: <witness> = interface_witness @I, (<error>) [template = <error>]
+// CHECK:STDOUT:   %.1: <witness> = interface_witness (<error>) [template = <error>]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .F = %F.decl
@@ -277,7 +277,7 @@ class FDifferentParamName {
 // CHECK:STDOUT: impl @impl.3: FAlias as I {
 // CHECK:STDOUT:   %PossiblyF.ref: <function> = name_ref PossiblyF, file.%PossiblyF [template = file.%PossiblyF]
 // CHECK:STDOUT:   %F: <function> = bind_alias F, file.%PossiblyF [template = file.%PossiblyF]
-// CHECK:STDOUT:   %.1: <witness> = interface_witness @I, (<error>) [template = <error>]
+// CHECK:STDOUT:   %.1: <witness> = interface_witness (<error>) [template = <error>]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .F = %F
@@ -289,7 +289,7 @@ class FDifferentParamName {
 // CHECK:STDOUT:     %b.loc54_10.1: bool = param b
 // CHECK:STDOUT:     %b.loc54_10.2: bool = bind_name b, %b.loc54_10.1
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %.1: <witness> = interface_witness @I, (<error>) [template = <error>]
+// CHECK:STDOUT:   %.1: <witness> = interface_witness (<error>) [template = <error>]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .F = %F
@@ -302,7 +302,7 @@ class FDifferentParamName {
 // CHECK:STDOUT:     %self.loc66_10.1: FExtraImplicitParam = param self
 // CHECK:STDOUT:     %self.loc66_10.2: FExtraImplicitParam = bind_name self, %self.loc66_10.1
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %.1: <witness> = interface_witness @I, (<error>) [template = <error>]
+// CHECK:STDOUT:   %.1: <witness> = interface_witness (<error>) [template = <error>]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .F = %F
@@ -313,7 +313,7 @@ class FDifferentParamName {
 // CHECK:STDOUT:   %F: <function> = fn_decl @F.4 [template] {
 // CHECK:STDOUT:     %return.var: ref bool = var <return slot>
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %.1: <witness> = interface_witness @I, (<error>) [template = <error>]
+// CHECK:STDOUT:   %.1: <witness> = interface_witness (<error>) [template = <error>]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .F = %F
@@ -326,7 +326,7 @@ class FDifferentParamName {
 // CHECK:STDOUT:     %self.loc93_10.2: bool = bind_name self, %self.loc93_10.1
 // CHECK:STDOUT:     %return.var: ref bool = var <return slot>
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %.1: <witness> = interface_witness @J, (<error>) [template = <error>]
+// CHECK:STDOUT:   %.1: <witness> = interface_witness (<error>) [template = <error>]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .F = %F
@@ -339,7 +339,7 @@ class FDifferentParamName {
 // CHECK:STDOUT:     %b.loc105_10.2: bool = bind_name b, %b.loc105_10.1
 // CHECK:STDOUT:     %return.var: ref bool = var <return slot>
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %.1: <witness> = interface_witness @J, (<error>) [template = <error>]
+// CHECK:STDOUT:   %.1: <witness> = interface_witness (<error>) [template = <error>]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .F = %F
@@ -353,7 +353,7 @@ class FDifferentParamName {
 // CHECK:STDOUT:     %b.loc117_22.1: bool = param b
 // CHECK:STDOUT:     %b.loc117_22.2: bool = bind_name b, %b.loc117_22.1
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %.1: <witness> = interface_witness @J, (<error>) [template = <error>]
+// CHECK:STDOUT:   %.1: <witness> = interface_witness (<error>) [template = <error>]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .F = %F
@@ -369,7 +369,7 @@ class FDifferentParamName {
 // CHECK:STDOUT:     %b.loc129_22.2: FDifferentParamType = bind_name b, %b.loc129_22.1
 // CHECK:STDOUT:     %return.var: ref bool = var <return slot>
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %.1: <witness> = interface_witness @J, (<error>) [template = <error>]
+// CHECK:STDOUT:   %.1: <witness> = interface_witness (<error>) [template = <error>]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .F = %F
@@ -385,7 +385,7 @@ class FDifferentParamName {
 // CHECK:STDOUT:     %b.loc141_22.2: bool = bind_name b, %b.loc141_22.1
 // CHECK:STDOUT:     %return.var: ref bool = var <return slot>
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %.1: <witness> = interface_witness @J, (<error>) [template = <error>]
+// CHECK:STDOUT:   %.1: <witness> = interface_witness (<error>) [template = <error>]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .F = %F
@@ -401,7 +401,7 @@ class FDifferentParamName {
 // CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%FDifferentReturnType [template = constants.%FDifferentReturnType]
 // CHECK:STDOUT:     %return.var: ref FDifferentReturnType = var <return slot>
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %.1: <witness> = interface_witness @J, (<error>) [template = <error>]
+// CHECK:STDOUT:   %.1: <witness> = interface_witness (<error>) [template = <error>]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .F = %F
@@ -416,7 +416,7 @@ class FDifferentParamName {
 // CHECK:STDOUT:     %not_b.loc166_22.2: bool = bind_name not_b, %not_b.loc166_22.1
 // CHECK:STDOUT:     %return.var: ref bool = var <return slot>
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %.1: <witness> = interface_witness @J, (<error>) [template = <error>]
+// CHECK:STDOUT:   %.1: <witness> = interface_witness (<error>) [template = <error>]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .F = %F

+ 2 - 2
toolchain/check/testdata/impl/fail_impl_bad_type.carbon

@@ -16,7 +16,7 @@ impl true as I {}
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %.1: type = interface_type @I [template]
 // CHECK:STDOUT:   %.2: bool = bool_literal true [template]
-// CHECK:STDOUT:   %.3: <witness> = interface_witness @I, () [template]
+// CHECK:STDOUT:   %.3: <witness> = interface_witness () [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -39,7 +39,7 @@ impl true as I {}
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl: <error> as I {
-// CHECK:STDOUT:   %.1: <witness> = interface_witness @I, () [template = constants.%.3]
+// CHECK:STDOUT:   %.1: <witness> = interface_witness () [template = constants.%.3]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   witness = %.1

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

@@ -20,7 +20,7 @@ impl i32 as I {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %.1: type = interface_type @I [template]
-// CHECK:STDOUT:   %.2: <witness> = interface_witness @I, () [template]
+// CHECK:STDOUT:   %.2: <witness> = interface_witness () [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {

+ 1 - 1
toolchain/check/testdata/impl/fail_todo_self_in_signature.carbon

@@ -84,7 +84,7 @@ impl C as UseSelf {
 // CHECK:STDOUT:     %C.ref.loc21_26: type = name_ref C, file.%C.decl [template = constants.%C]
 // CHECK:STDOUT:     %return.var: ref C = var <return slot>
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %.1: <witness> = interface_witness @UseSelf, (<error>) [template = <error>]
+// CHECK:STDOUT:   %.1: <witness> = interface_witness (<error>) [template = <error>]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .F = %F

+ 2 - 2
toolchain/check/testdata/impl/impl_as.carbon

@@ -21,7 +21,7 @@ class C {
 // 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:   %.4: <witness> = interface_witness @Simple, (@impl.%F) [template]
+// CHECK:STDOUT:   %.4: <witness> = interface_witness (@impl.%F) [template]
 // CHECK:STDOUT:   %.5: type = struct_type {} [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -47,7 +47,7 @@ class C {
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl: C as Simple {
 // CHECK:STDOUT:   %F: <function> = fn_decl @F.2 [template] {}
-// CHECK:STDOUT:   %.1: <witness> = interface_witness @Simple, (%F) [template = constants.%.4]
+// CHECK:STDOUT:   %.1: <witness> = interface_witness (%F) [template = constants.%.4]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .F = %F

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

@@ -18,7 +18,7 @@ impl forall [T:! type] T as Simple {
 // 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:   %.4: <witness> = interface_witness @Simple, (@impl.%F) [template]
+// CHECK:STDOUT:   %.4: <witness> = interface_witness (@impl.%F) [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -47,7 +47,7 @@ impl forall [T:! type] T as Simple {
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl: T as Simple {
 // CHECK:STDOUT:   %F: <function> = fn_decl @F.2 [template] {}
-// CHECK:STDOUT:   %.1: <witness> = interface_witness @Simple, (%F) [template = constants.%.4]
+// CHECK:STDOUT:   %.1: <witness> = interface_witness (%F) [template = constants.%.4]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .F = %F

+ 105 - 0
toolchain/check/testdata/impl/lookup/alias.carbon

@@ -0,0 +1,105 @@
+// 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 HasF {
+  fn F();
+}
+
+class C {
+  alias G = HasF.F;
+}
+
+impl C as HasF {
+  fn F() {}
+}
+
+fn G(c: C) {
+  C.G();
+  c.G();
+}
+
+// CHECK:STDOUT: --- alias.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:   %.4: type = struct_type {} [template]
+// CHECK:STDOUT:   %.5: <witness> = interface_witness (@impl.%F) [template]
+// CHECK:STDOUT:   %.6: type = tuple_type () [template]
+// CHECK:STDOUT:   %.7: type = ptr_type {} [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .HasF = %HasF.decl
+// CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:     .G = %G
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %HasF.decl: type = interface_decl @HasF [template = constants.%.1] {}
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [template = constants.%C] {}
+// CHECK:STDOUT:   impl_decl @impl {
+// CHECK:STDOUT:     %C.ref.loc15: type = name_ref C, %C.decl [template = constants.%C]
+// CHECK:STDOUT:     %HasF.ref: type = name_ref HasF, %HasF.decl [template = constants.%.1]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %G: <function> = fn_decl @G [template] {
+// CHECK:STDOUT:     %C.ref.loc19: type = name_ref C, %C.decl [template = constants.%C]
+// CHECK:STDOUT:     %c.loc19_6.1: C = param c
+// CHECK:STDOUT:     @G.%c: C = bind_name c, %c.loc19_6.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @HasF {
+// CHECK:STDOUT:   %Self: HasF = bind_symbolic_name Self [symbolic]
+// 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:   .Self = %Self
+// CHECK:STDOUT:   .F = %.loc8
+// CHECK:STDOUT:   witness = (%F)
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: impl @impl: C as HasF {
+// CHECK:STDOUT:   %F: <function> = fn_decl @F.2 [template] {}
+// CHECK:STDOUT:   %.1: <witness> = interface_witness (%F) [template = constants.%.5]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .F = %F
+// CHECK:STDOUT:   witness = %.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT:   %HasF.ref: type = name_ref HasF, file.%HasF.decl [template = constants.%.1]
+// CHECK:STDOUT:   %F.ref: <associated <function> in HasF> = name_ref F, @HasF.%.loc8 [template = constants.%.3]
+// CHECK:STDOUT:   %G: <associated <function> in HasF> = bind_alias G, @HasF.%.loc8 [template = constants.%.3]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%C
+// CHECK:STDOUT:   .G = %G
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F.1();
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F.2() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// 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:   %G.ref.loc20: <associated <function> in HasF> = name_ref G, @C.%G [template = constants.%.3]
+// CHECK:STDOUT:   %.1: <function> = interface_witness_access @impl.%.1, element0 [template = @impl.%F]
+// CHECK:STDOUT:   %.loc20: init () = call %.1()
+// CHECK:STDOUT:   %c.ref: C = name_ref c, %c
+// CHECK:STDOUT:   %G.ref.loc21: <associated <function> in HasF> = name_ref G, @C.%G [template = constants.%.3]
+// CHECK:STDOUT:   %.2: <function> = interface_witness_access @impl.%.1, element0 [template = @impl.%F]
+// CHECK:STDOUT:   %.loc21: init () = call %.2()
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 84 - 0
toolchain/check/testdata/impl/lookup/fail_alias_impl_not_found.carbon

@@ -0,0 +1,84 @@
+// 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 F();
+}
+
+class C {
+  alias F = I.F;
+}
+
+fn F(c: C) {
+  // CHECK:STDERR: fail_alias_impl_not_found.carbon:[[@LINE+3]]:3: ERROR: Cannot access member of interface I in type C that does not implement that interface.
+  // CHECK:STDERR:   C.F();
+  // CHECK:STDERR:   ^~~
+  C.F();
+  // CHECK:STDERR: fail_alias_impl_not_found.carbon:[[@LINE+3]]:3: ERROR: Cannot access member of interface I in type C that does not implement that interface.
+  // CHECK:STDERR:   c.F();
+  // CHECK:STDERR:   ^~~
+  c.F();
+}
+
+// CHECK:STDOUT: --- fail_alias_impl_not_found.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: type = interface_type @I [template]
+// CHECK:STDOUT:   %.2: type = assoc_entity_type @I, <function> [template]
+// CHECK:STDOUT:   %.3: <associated <function> in I> = assoc_entity element0, @I.%F [template]
+// CHECK:STDOUT:   %C: type = class_type @C [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 {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .I = %I.decl
+// CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:     .F = %F
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %I.decl: type = interface_decl @I [template = constants.%.1] {}
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [template = constants.%C] {}
+// CHECK:STDOUT:   %F: <function> = fn_decl @F.2 [template] {
+// CHECK:STDOUT:     %C.ref: type = name_ref C, %C.decl [template = constants.%C]
+// CHECK:STDOUT:     %c.loc15_6.1: C = param c
+// CHECK:STDOUT:     @F.2.%c: C = bind_name c, %c.loc15_6.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @I {
+// CHECK:STDOUT:   %Self: I = bind_symbolic_name Self [symbolic]
+// CHECK:STDOUT:   %F: <function> = fn_decl @F.1 [template] {}
+// CHECK:STDOUT:   %.loc8: <associated <function> in I> = assoc_entity element0, %F [template = constants.%.3]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
+// CHECK:STDOUT:   .F = %.loc8
+// CHECK:STDOUT:   witness = (%F)
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT:   %I.ref: type = name_ref I, file.%I.decl [template = constants.%.1]
+// CHECK:STDOUT:   %F.ref: <associated <function> in I> = name_ref F, @I.%.loc8 [template = constants.%.3]
+// CHECK:STDOUT:   %F: <associated <function> in I> = bind_alias F, @I.%.loc8 [template = constants.%.3]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%C
+// CHECK:STDOUT:   .F = %F
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F.1();
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F.2(%c: C) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %C.ref: type = name_ref C, file.%C.decl [template = constants.%C]
+// CHECK:STDOUT:   %F.ref.loc19: <associated <function> in I> = name_ref F, @C.%F [template = constants.%.3]
+// CHECK:STDOUT:   %c.ref: C = name_ref c, %c
+// CHECK:STDOUT:   %F.ref.loc23: <associated <function> in I> = name_ref F, @C.%F [template = constants.%.3]
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 98 - 0
toolchain/check/testdata/impl/lookup/fail_todo_undefined_impl.carbon

@@ -0,0 +1,98 @@
+// 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 F();
+}
+
+class C {
+  extend impl as I;
+}
+
+fn F() -> i32 {
+  // TODO: This should produce a more useful error message.
+  // CHECK:STDERR: fail_todo_undefined_impl.carbon:[[@LINE+3]]:10: ERROR: Cannot access member of interface I in type C that does not implement that interface.
+  // CHECK:STDERR:   return C.F();
+  // CHECK:STDERR:          ^~~
+  return C.F();
+}
+
+impl C as I {
+  fn F() {}
+}
+
+// CHECK:STDOUT: --- fail_todo_undefined_impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: type = interface_type @I [template]
+// CHECK:STDOUT:   %.2: type = assoc_entity_type @I, <function> [template]
+// CHECK:STDOUT:   %.3: <associated <function> in I> = assoc_entity element0, @I.%F [template]
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %.4: type = struct_type {} [template]
+// CHECK:STDOUT:   %.5: <witness> = interface_witness (@impl.%F) [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .I = %I.decl
+// CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:     .F = %F
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %I.decl: type = interface_decl @I [template = constants.%.1] {}
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [template = constants.%C] {}
+// CHECK:STDOUT:   %F: <function> = fn_decl @F.2 [template] {
+// CHECK:STDOUT:     %return.var: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   impl_decl @impl {
+// CHECK:STDOUT:     %C.ref: type = name_ref C, %C.decl [template = constants.%C]
+// CHECK:STDOUT:     %I.ref: type = name_ref I, %I.decl [template = constants.%.1]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @I {
+// CHECK:STDOUT:   %Self: I = bind_symbolic_name Self [symbolic]
+// CHECK:STDOUT:   %F: <function> = fn_decl @F.1 [template] {}
+// CHECK:STDOUT:   %.loc8: <associated <function> in I> = assoc_entity element0, %F [template = constants.%.3]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
+// CHECK:STDOUT:   .F = %.loc8
+// CHECK:STDOUT:   witness = (%F)
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: impl @impl: C as I {
+// CHECK:STDOUT:   %F: <function> = fn_decl @F.3 [template] {}
+// CHECK:STDOUT:   %.1: <witness> = interface_witness (%F) [template = constants.%.5]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .F = %F
+// CHECK:STDOUT:   witness = %.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT:   impl_decl @impl {
+// CHECK:STDOUT:     %I.ref: type = name_ref I, file.%I.decl [template = constants.%.1]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%C
+// CHECK:STDOUT:   extend name_scope1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F.1();
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F.2() -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %C.ref: type = name_ref C, file.%C.decl [template = constants.%C]
+// CHECK:STDOUT:   %F.ref: <associated <function> in I> = name_ref F, @I.%.loc8 [template = constants.%.3]
+// CHECK:STDOUT:   return <error>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F.3() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 109 - 0
toolchain/check/testdata/impl/lookup/instance_method.carbon

@@ -0,0 +1,109 @@
+// 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
+
+class C;
+
+interface I {
+  // TODO: Use `Self` once we perform substitution for it.
+  fn F[self: C]() -> i32;
+}
+
+class C {
+  extend impl as I {
+    fn F[self: C]() -> i32;
+  }
+}
+
+fn F(c: C) -> i32 {
+  return c.F();
+}
+
+// CHECK:STDOUT: --- instance_method.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %.1: type = interface_type @I [template]
+// CHECK:STDOUT:   %.2: type = assoc_entity_type @I, <function> [template]
+// CHECK:STDOUT:   %.3: <associated <function> in I> = assoc_entity element0, @I.%F [template]
+// CHECK:STDOUT:   %.4: <witness> = interface_witness (@impl.%F) [template]
+// CHECK:STDOUT:   %.5: type = struct_type {} [template]
+// CHECK:STDOUT:   %.6: type = tuple_type () [template]
+// CHECK:STDOUT:   %.7: type = ptr_type {} [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %C.decl.loc7
+// CHECK:STDOUT:     .I = %I.decl
+// CHECK:STDOUT:     .F = %F
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.decl.loc7: type = class_decl @C [template = constants.%C] {}
+// CHECK:STDOUT:   %I.decl: type = interface_decl @I [template = constants.%.1] {}
+// CHECK:STDOUT:   %C.decl.loc14: type = class_decl @C [template = constants.%C] {}
+// CHECK:STDOUT:   %F: <function> = fn_decl @F.3 [template] {
+// CHECK:STDOUT:     %C.ref: type = name_ref C, %C.decl.loc7 [template = constants.%C]
+// CHECK:STDOUT:     %c.loc20_6.1: C = param c
+// CHECK:STDOUT:     @F.3.%c: C = bind_name c, %c.loc20_6.1
+// CHECK:STDOUT:     %return.var: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @I {
+// CHECK:STDOUT:   %Self: I = bind_symbolic_name Self [symbolic]
+// CHECK:STDOUT:   %F: <function> = fn_decl @F.1 [template] {
+// CHECK:STDOUT:     %C.ref: type = name_ref C, file.%C.decl.loc7 [template = constants.%C]
+// CHECK:STDOUT:     %self.loc11_8.1: C = param self
+// CHECK:STDOUT:     %self.loc11_8.2: C = bind_name self, %self.loc11_8.1
+// CHECK:STDOUT:     %return.var: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.loc11: <associated <function> in I> = assoc_entity element0, %F [template = constants.%.3]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
+// CHECK:STDOUT:   .F = %.loc11
+// CHECK:STDOUT:   witness = (%F)
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: impl @impl: C as I {
+// CHECK:STDOUT:   %F: <function> = fn_decl @F.2 [template] {
+// CHECK:STDOUT:     %C.ref: type = name_ref C, file.%C.decl.loc7 [template = constants.%C]
+// CHECK:STDOUT:     %self.loc16_10.1: C = param self
+// CHECK:STDOUT:     %self.loc16_10.2: C = bind_name self, %self.loc16_10.1
+// CHECK:STDOUT:     %return.var: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.1: <witness> = interface_witness (%F) [template = constants.%.4]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .F = %F
+// CHECK:STDOUT:   witness = %.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT:   impl_decl @impl {
+// CHECK:STDOUT:     %I.ref: type = name_ref I, file.%I.decl [template = constants.%.1]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%C
+// CHECK:STDOUT:   extend name_scope1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F.1[@I.%self.loc11_8.2: C]() -> i32;
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F.2[@impl.%self.loc16_10.2: C]() -> i32;
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F.3(%c: C) -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %c.ref: C = name_ref c, %c
+// CHECK:STDOUT:   %F.ref: <associated <function> in I> = name_ref F, @I.%.loc11 [template = constants.%.3]
+// CHECK:STDOUT:   %.1: <function> = interface_witness_access @impl.%.1, element0 [template = @impl.%F]
+// CHECK:STDOUT:   %.loc21_11: <bound method> = bound_method %c.ref, %.1
+// CHECK:STDOUT:   %.loc21_13.1: init i32 = call %.loc21_11(%c.ref)
+// CHECK:STDOUT:   %.loc21_15: i32 = value_of_initializer %.loc21_13.1
+// CHECK:STDOUT:   %.loc21_13.2: i32 = converted %.loc21_13.1, %.loc21_15
+// CHECK:STDOUT:   return %.loc21_13.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 2 - 2
toolchain/check/testdata/impl/redeclaration.carbon

@@ -20,7 +20,7 @@ impl i32 as I {}
 // CHECK:STDOUT:   %.1: type = interface_type @I [template]
 // CHECK:STDOUT:   %X: type = class_type @X [template]
 // CHECK:STDOUT:   %.2: type = struct_type {} [template]
-// CHECK:STDOUT:   %.3: <witness> = interface_witness @I, () [template]
+// CHECK:STDOUT:   %.3: <witness> = interface_witness () [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -47,7 +47,7 @@ impl i32 as I {}
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl: i32 as I {
-// CHECK:STDOUT:   %.1: <witness> = interface_witness @I, () [template = constants.%.3]
+// CHECK:STDOUT:   %.1: <witness> = interface_witness () [template = constants.%.3]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   witness = %.1

+ 2 - 2
toolchain/check/testdata/interface/fail_lookup_undefined.carbon

@@ -17,7 +17,7 @@ fn Undefined.F();
 fn Test() {
   // CHECK:STDERR: fail_lookup_undefined.carbon:[[@LINE+6]]:3: ERROR: Member access into undefined interface `Undefined`.
   // CHECK:STDERR:   Undefined.G();
-  // CHECK:STDERR:   ^~~~~~~~~
+  // CHECK:STDERR:   ^~~~~~~~~~~
   // CHECK:STDERR: fail_lookup_undefined.carbon:[[@LINE-14]]:1: Interface was forward declared here.
   // CHECK:STDERR: interface Undefined;
   // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~
@@ -27,7 +27,7 @@ fn Test() {
 interface BeingDefined {
   // CHECK:STDERR: fail_lookup_undefined.carbon:[[@LINE+9]]:13: ERROR: Member access into undefined interface `BeingDefined`.
   // CHECK:STDERR:   fn H() -> BeingDefined.T;
-  // CHECK:STDERR:             ^~~~~~~~~~~~
+  // CHECK:STDERR:             ^~~~~~~~~~~~~~
   // CHECK:STDERR: fail_lookup_undefined.carbon:[[@LINE-4]]:1: Interface is currently being defined.
   // CHECK:STDERR: interface BeingDefined {
   // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~

+ 3 - 0
toolchain/diagnostics/diagnostic_kind.def

@@ -203,6 +203,9 @@ CARBON_DIAGNOSTIC_KIND(ImplFunctionWithNonFunction)
 CARBON_DIAGNOSTIC_KIND(ImplAssociatedFunctionHere)
 CARBON_DIAGNOSTIC_KIND(ImplOfUndefinedInterface)
 
+// Impl lookup.
+CARBON_DIAGNOSTIC_KIND(MissingImplInMemberAccess)
+
 // Let declaration checking.
 CARBON_DIAGNOSTIC_KIND(ExpectedInitializerAfterLet)
 CARBON_DIAGNOSTIC_KIND(ExpectedSymbolicBindingInAssociatedConstant)

+ 6 - 0
toolchain/lower/file_context.cpp

@@ -63,6 +63,11 @@ auto FileContext::GetGlobal(SemIR::InstId inst_id) -> llvm::Value* {
   }
 
   auto target = sem_ir().insts().Get(inst_id);
+  while (auto alias = target.TryAs<SemIR::BindAlias>()) {
+    inst_id = alias->value_id;
+    target = sem_ir().insts().Get(inst_id);
+  }
+
   if (auto function_decl = target.TryAs<SemIR::FunctionDecl>()) {
     return GetFunction(function_decl->function_id);
   }
@@ -265,6 +270,7 @@ auto FileContext::BuildType(SemIR::InstId inst_id) -> llvm::Type* {
     case SemIR::BuiltinKind::FunctionType.AsInt():
     case SemIR::BuiltinKind::BoundMethodType.AsInt():
     case SemIR::BuiltinKind::NamespaceType.AsInt():
+    case SemIR::BuiltinKind::WitnessType.AsInt():
       // Return an empty struct as a placeholder.
       return llvm::StructType::get(*llvm_context_);
     default:

+ 10 - 0
toolchain/lower/handle.cpp

@@ -252,6 +252,16 @@ auto HandleInterfaceWitness(FunctionContext& /*context*/,
   FatalErrorIfEncountered(inst);
 }
 
+auto HandleInterfaceWitnessAccess(FunctionContext& context,
+                                  SemIR::InstId inst_id,
+                                  SemIR::InterfaceWitnessAccess inst) -> void {
+  // TODO: Add general constant lowering.
+  auto const_id = context.sem_ir().constant_values().Get(inst_id);
+  CARBON_CHECK(const_id.is_constant())
+      << "Lowering non-constant witness access " << inst;
+  context.SetLocal(inst_id, context.GetValue(const_id.inst_id()));
+}
+
 auto HandleIntLiteral(FunctionContext& context, SemIR::InstId inst_id,
                       SemIR::IntLiteral inst) -> void {
   const llvm::APInt& i = context.sem_ir().ints().Get(inst.int_id);

+ 42 - 0
toolchain/lower/testdata/impl/assoc_fn_alias.carbon

@@ -0,0 +1,42 @@
+// 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
+
+class A;
+
+interface I {
+  fn F[self: A]() -> i32;
+}
+
+class A {
+  alias F = I.F;
+  var n: i32;
+}
+
+impl A as I {
+  fn F[self: A]() -> i32 {
+    return self.n;
+  }
+}
+
+fn Call(a: A) -> i32 {
+  return a.F();
+}
+
+// CHECK:STDOUT: ; ModuleID = 'assoc_fn_alias.carbon'
+// CHECK:STDOUT: source_filename = "assoc_fn_alias.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare i32 @F(ptr)
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @F.1(ptr %self) {
+// CHECK:STDOUT:   %n = getelementptr inbounds { i32 }, ptr %self, i32 0, i32 0
+// CHECK:STDOUT:   %1 = load i32, ptr %n, align 4
+// CHECK:STDOUT:   ret i32 %1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @Call(ptr %a) {
+// CHECK:STDOUT:   %F.1 = call i32 @F.1(ptr %a)
+// CHECK:STDOUT:   ret i32 %F.1
+// CHECK:STDOUT: }

+ 44 - 0
toolchain/lower/testdata/impl/extend_impl.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 I {
+  fn F() -> i32;
+}
+
+class A {
+  extend impl as I {
+    fn F() -> i32 {
+      return 0;
+    }
+  }
+}
+
+fn TypeAccess() -> i32 {
+  return A.F();
+}
+
+fn InstanceAccess(a: A) -> i32 {
+  return a.F();
+}
+
+// CHECK:STDOUT: ; ModuleID = 'extend_impl.carbon'
+// CHECK:STDOUT: source_filename = "extend_impl.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare i32 @F()
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @F.1() {
+// CHECK:STDOUT:   ret i32 0
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @TypeAccess() {
+// CHECK:STDOUT:   %F.1 = call i32 @F.1()
+// CHECK:STDOUT:   ret i32 %F.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @InstanceAccess(ptr %a) {
+// CHECK:STDOUT:   %F.1 = call i32 @F.1()
+// CHECK:STDOUT:   ret i32 %F.1
+// CHECK:STDOUT: }

+ 38 - 0
toolchain/lower/testdata/impl/instance_method.carbon

@@ -0,0 +1,38 @@
+// 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
+
+class A;
+
+interface GetSelf {
+  // TODO: Use `Self` once that works.
+  fn Get[addr self: A*]() -> A*;
+}
+
+class A {
+  extend impl as GetSelf {
+    fn Get[addr self: Self*]() -> Self* {
+      return self;
+    }
+  }
+}
+
+fn Call(a: A*) -> A* {
+  return (*a).Get();
+}
+
+// CHECK:STDOUT: ; ModuleID = 'instance_method.carbon'
+// CHECK:STDOUT: source_filename = "instance_method.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare ptr @Get(ptr)
+// CHECK:STDOUT:
+// CHECK:STDOUT: define ptr @Get.1(ptr %self) {
+// CHECK:STDOUT:   ret ptr %self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define ptr @Call(ptr %a) {
+// CHECK:STDOUT:   %Get.1 = call ptr @Get.1(ptr %a)
+// CHECK:STDOUT:   ret ptr %Get.1
+// CHECK:STDOUT: }

+ 3 - 0
toolchain/sem_ir/file.cpp

@@ -239,6 +239,7 @@ static auto GetTypePrecedence(InstKind kind) -> int {
     case InitializeFrom::Kind:
     case InterfaceDecl::Kind:
     case InterfaceWitness::Kind:
+    case InterfaceWitnessAccess::Kind:
     case IntLiteral::Kind:
     case Namespace::Kind:
     case Param::Kind:
@@ -487,6 +488,7 @@ static auto StringifyTypeExprImpl(const SemIR::File& outer_sem_ir,
       case InitializeFrom::Kind:
       case InterfaceDecl::Kind:
       case InterfaceWitness::Kind:
+      case InterfaceWitnessAccess::Kind:
       case IntLiteral::Kind:
       case Namespace::Kind:
       case Param::Kind:
@@ -594,6 +596,7 @@ auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory {
       case InterfaceDecl::Kind:
       case InterfaceType::Kind:
       case InterfaceWitness::Kind:
+      case InterfaceWitnessAccess::Kind:
       case IntLiteral::Kind:
       case Param::Kind:
       case PointerType::Kind:

+ 1 - 0
toolchain/sem_ir/inst_kind.def

@@ -57,6 +57,7 @@ CARBON_SEM_IR_INST_KIND(InitializeFrom)
 CARBON_SEM_IR_INST_KIND(InterfaceDecl)
 CARBON_SEM_IR_INST_KIND(InterfaceType)
 CARBON_SEM_IR_INST_KIND(InterfaceWitness)
+CARBON_SEM_IR_INST_KIND(InterfaceWitnessAccess)
 CARBON_SEM_IR_INST_KIND(IntLiteral)
 CARBON_SEM_IR_INST_KIND(NameRef)
 CARBON_SEM_IR_INST_KIND(Namespace)

+ 17 - 7
toolchain/sem_ir/typed_insts.h

@@ -75,9 +75,9 @@ struct ArrayIndex {
 // Common representation for aggregate access nodes, which access a fixed
 // element of an aggregate.
 struct AnyAggregateAccess {
-  static constexpr InstKind Kinds[] = {InstKind::StructAccess,
-                                       InstKind::TupleAccess,
-                                       InstKind::ClassElementAccess};
+  static constexpr InstKind Kinds[] = {
+      InstKind::StructAccess, InstKind::TupleAccess,
+      InstKind::ClassElementAccess, InstKind::InterfaceWitnessAccess};
 
   InstKind kind;
   TypeId type_id;
@@ -111,8 +111,8 @@ struct AnyAggregateInit {
 
 // Common representation for all kinds of aggregate value.
 struct AnyAggregateValue {
-  static constexpr InstKind Kinds[] = {InstKind::StructValue,
-                                       InstKind::TupleValue};
+  static constexpr InstKind Kinds[] = {
+      InstKind::StructValue, InstKind::TupleValue, InstKind::InterfaceWitness};
 
   InstKind kind;
   TypeId type_id;
@@ -547,8 +547,18 @@ struct InterfaceWitness {
           "interface_witness");
 
   TypeId type_id;
-  InterfaceId interface_id;
-  InstBlockId table_id;
+  InstBlockId elements_id;
+};
+
+// Accesses an element of an interface witness by index.
+struct InterfaceWitnessAccess {
+  static constexpr auto Kind =
+      InstKind::InterfaceWitnessAccess.Define<Parse::InvalidNodeId>(
+          "interface_witness_access");
+
+  TypeId type_id;
+  InstId witness_id;
+  ElementIndex index;
 };
 
 struct IntLiteral {