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

Add `Self` into scope in interface definitions and using facets as types. (#3740)

`Self` is modeled as a `bind_symbolic_name` with no corresponding value,
for now at least. In the future it might make sense to model it as a new
kind of instruction, or as a `bind_symbolic_name` whose value is a
`param`, but for now we just want it to introduce a symbolic constant.

In order to convert a value like the `Self` of an interface to a type, a
new instruction `facet_type_access` is introduced. This notionally
accesses the "type" field within a facet, converting it from a pair of
(type, witness) into just the type.
Richard Smith 2 лет назад
Родитель
Сommit
f5a3a9a7e0
47 измененных файлов с 331 добавлено и 54 удалено
  1. 1 0
      toolchain/check/context.cpp
  2. 12 0
      toolchain/check/convert.cpp
  3. 5 0
      toolchain/check/eval.cpp
  4. 25 2
      toolchain/check/handle_interface.cpp
  5. 9 2
      toolchain/check/import_ref.cpp
  6. 2 0
      toolchain/check/testdata/impl/basic.carbon
  7. 3 0
      toolchain/check/testdata/impl/declaration.carbon
  8. 3 0
      toolchain/check/testdata/impl/empty.carbon
  9. 2 0
      toolchain/check/testdata/impl/fail_extend_impl_forall.carbon
  10. 3 0
      toolchain/check/testdata/impl/fail_extend_impl_scope.carbon
  11. 3 0
      toolchain/check/testdata/impl/fail_extend_impl_type_as.carbon
  12. 2 0
      toolchain/check/testdata/impl/fail_extend_partially_defined_interface.carbon
  13. 2 0
      toolchain/check/testdata/impl/fail_impl_as_scope.carbon
  14. 3 0
      toolchain/check/testdata/impl/fail_impl_bad_type.carbon
  15. 3 0
      toolchain/check/testdata/impl/fail_redefinition.carbon
  16. 2 0
      toolchain/check/testdata/impl/fail_todo_extend_impl.carbon
  17. 2 0
      toolchain/check/testdata/impl/impl_as.carbon
  18. 2 0
      toolchain/check/testdata/impl/impl_forall.carbon
  19. 3 0
      toolchain/check/testdata/impl/redeclaration.carbon
  20. 3 0
      toolchain/check/testdata/interface/as_type.carbon
  21. 8 7
      toolchain/check/testdata/interface/as_type_of_type.carbon
  22. 2 0
      toolchain/check/testdata/interface/assoc_const.carbon
  23. 5 0
      toolchain/check/testdata/interface/basic.carbon
  24. 7 0
      toolchain/check/testdata/interface/fail_add_member_outside_definition.carbon
  25. 2 0
      toolchain/check/testdata/interface/fail_assoc_const_bad_default.carbon
  26. 1 0
      toolchain/check/testdata/interface/fail_assoc_const_not_binding.carbon
  27. 3 0
      toolchain/check/testdata/interface/fail_assoc_const_not_constant.carbon
  28. 1 0
      toolchain/check/testdata/interface/fail_assoc_const_template.carbon
  29. 4 0
      toolchain/check/testdata/interface/fail_duplicate.carbon
  30. 2 0
      toolchain/check/testdata/interface/fail_lookup_undefined.carbon
  31. 2 0
      toolchain/check/testdata/interface/fail_member_lookup.carbon
  32. 6 0
      toolchain/check/testdata/interface/fail_modifiers.carbon
  33. 2 0
      toolchain/check/testdata/interface/fail_redeclare_member.carbon
  34. 2 0
      toolchain/check/testdata/interface/fail_todo_assoc_const_default.carbon
  35. 2 0
      toolchain/check/testdata/interface/fail_todo_define_out_of_line.carbon
  36. 27 1
      toolchain/check/testdata/interface/fail_todo_facet_lookup.carbon
  37. 39 25
      toolchain/check/testdata/interface/fail_todo_import.carbon
  38. 5 0
      toolchain/check/testdata/interface/fail_todo_modifiers.carbon
  39. 31 17
      toolchain/check/testdata/interface/import.carbon
  40. 48 0
      toolchain/check/testdata/interface/self.carbon
  41. 5 0
      toolchain/lower/handle_type.cpp
  42. 7 0
      toolchain/sem_ir/file.cpp
  43. 10 0
      toolchain/sem_ir/formatter.cpp
  44. 1 0
      toolchain/sem_ir/inst_kind.def
  45. 2 0
      toolchain/sem_ir/interface.h
  46. 7 0
      toolchain/sem_ir/type.h
  47. 10 0
      toolchain/sem_ir/typed_insts.h

+ 1 - 0
toolchain/check/context.cpp

@@ -837,6 +837,7 @@ class TypeCompleter {
       case SemIR::ClassInit::Kind:
       case SemIR::Converted::Kind:
       case SemIR::Deref::Kind:
+      case SemIR::FacetTypeAccess::Kind:
       case SemIR::FieldDecl::Kind:
       case SemIR::FunctionDecl::Kind:
       case SemIR::ImplDecl::Kind:

+ 12 - 0
toolchain/check/convert.cpp

@@ -833,6 +833,18 @@ static auto PerformBuiltinConversion(Context& context, Parse::NodeId parse_node,
         struct_literal->elements_id == SemIR::InstBlockId::Empty) {
       value_id = sem_ir.types().GetInstId(value_type_id);
     }
+
+    // Facet type conversions: a value T of facet type F1 can be implicitly
+    // converted to facet type F2 if T satisfies the requirements of F2.
+    //
+    // TODO: Support this conversion in general. For now we only support it in
+    // the case where F1 is an interface type and F2 is `type`.
+    // TODO: Support converting tuple and struct values to facet types,
+    // combining the above conversions and this one in a single conversion.
+    if (sem_ir.types().Is<SemIR::InterfaceType>(value_type_id)) {
+      return context.AddInst(
+          {parse_node, SemIR::FacetTypeAccess{target.type_id, value_id}});
+    }
   }
 
   // No builtin conversion applies.

+ 5 - 0
toolchain/check/eval.cpp

@@ -456,6 +456,11 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
     case SemIR::ValueOfInitializer::Kind:
       return context.constant_values().Get(
           inst.As<SemIR::ValueOfInitializer>().init_id);
+    case SemIR::FacetTypeAccess::Kind:
+      // TODO: Once we start tracking the witness in the facet value, remove it
+      // here. For now, we model a facet value as just a type.
+      return context.constant_values().Get(
+          inst.As<SemIR::FacetTypeAccess>().facet_id);
 
     // `not true` -> `false`, `not false` -> `true`.
     // All other uses of unary `not` are non-constant.

+ 25 - 2
toolchain/check/handle_interface.cpp

@@ -3,7 +3,9 @@
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
 #include "toolchain/check/context.h"
+#include "toolchain/check/interface.h"
 #include "toolchain/check/modifiers.h"
+#include "toolchain/sem_ir/typed_insts.h"
 
 namespace Carbon::Check {
 
@@ -126,14 +128,35 @@ auto HandleInterfaceDefinitionStart(
   // Enter the interface scope.
   context.scope_stack().Push(interface_decl_id, interface_info.scope_id);
 
-  // TODO: Introduce `Self`.
-
   context.inst_block_stack().Push();
   context.node_stack().Push(parse_node, interface_id);
 
   // We use the arg stack to build the witness table type.
   context.args_type_info_stack().Push();
 
+  // Declare and introduce `Self`.
+  if (!interface_info.is_defined()) {
+    // TODO: Once we support parameterized interfaces, this won't be the right
+    // type. For `interface X(T:! type)`, the type of `Self` is `X(T)`, whereas
+    // this will be simply `X`.
+    auto self_type_id = context.GetTypeIdForTypeConstant(
+        context.constant_values().Get(interface_decl_id));
+
+    // We model `Self` as a symbolic binding whose type is the interface.
+    // Because there is no equivalent non-symbolic value, we use `Invalid` as
+    // the `value_id` on the `BindSymbolicName`.
+    auto bind_name_id = context.bind_names().Add(
+        {.name_id = SemIR::NameId::SelfType,
+         .enclosing_scope_id = interface_info.scope_id});
+    interface_info.self_param_id =
+        context.AddInst({Parse::NodeId::Invalid,
+                         SemIR::BindSymbolicName{self_type_id, bind_name_id,
+                                                 SemIR::InstId::Invalid}});
+    context.name_scopes()
+        .Get(interface_info.scope_id)
+        .names.insert({SemIR::NameId::SelfType, interface_info.self_param_id});
+  }
+
   // TODO: Handle the case where there's control flow in the interface body. For
   // example:
   //

+ 9 - 2
toolchain/check/import_ref.cpp

@@ -330,9 +330,15 @@ class ImportRefResolver {
         return TryResolveTypedInst(inst.As<SemIR::UnboundElementType>());
 
       case SemIR::InstKind::BindName:
+        // TODO: This always returns `ConstantId::NotConstant`.
+        return TryEvalInst(context_, inst_id, inst);
+
       case SemIR::InstKind::BindSymbolicName:
-        // Can use TryEvalInst because the resulting constant doesn't really use
-        // `inst`.
+        // TODO: This will return a `ConstantId` referring to the `inst_id` from
+        // the import IR. But we need all references to the same
+        // `BindSymbolicName` in the imported IR to resolve to the same constant
+        // value in the local IR, so we can't just create a new local
+        // `BindSymbolicName` here.
         return TryEvalInst(context_, inst_id, inst);
 
       default:
@@ -615,6 +621,7 @@ class ImportRefResolver {
       new_interface.associated_entities_id =
           AddAssociatedEntities(import_interface.associated_entities_id);
       new_interface.body_block_id = context_.inst_block_stack().Pop();
+      // TODO: Import new_interface.self_param_id.
 
       CARBON_CHECK(import_scope.extended_scopes.empty())
           << "Interfaces don't currently have extended scopes to support.";

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

@@ -31,10 +31,12 @@ impl i32 as Simple {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Simple {
+// CHECK:STDOUT:   %Self: Simple = bind_symbolic_name Self [symbolic]
 // 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:   .Self = %Self
 // CHECK:STDOUT:   .F = %.loc8
 // CHECK:STDOUT:   witness = (%F)
 // CHECK:STDOUT: }

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

@@ -25,7 +25,10 @@ impl i32 as I;
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @I {
+// CHECK:STDOUT:   %Self: I = bind_symbolic_name Self [symbolic]
+// CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
 // CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

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

@@ -27,7 +27,10 @@ impl i32 as Empty {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Empty {
+// CHECK:STDOUT:   %Self: Empty = bind_symbolic_name Self [symbolic]
+// CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
 // CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 2 - 0
toolchain/check/testdata/impl/fail_extend_impl_forall.carbon

@@ -46,6 +46,7 @@ class C {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @GenericInterface {
+// CHECK:STDOUT:   %Self: GenericInterface = bind_symbolic_name Self [symbolic]
 // CHECK:STDOUT:   %F: <function> = fn_decl @F.1 [template] {
 // CHECK:STDOUT:     %T.ref: type = name_ref T, file.%T.loc10_28.2 [symbolic = file.%T.loc10_28.2]
 // CHECK:STDOUT:     %x.loc11_8.1: T = param x
@@ -54,6 +55,7 @@ class C {
 // CHECK:STDOUT:   %.loc11: <associated <function> in GenericInterface> = 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: }

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

@@ -28,7 +28,10 @@ extend impl i32 as I {}
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @I {
+// CHECK:STDOUT:   %Self: I = bind_symbolic_name Self [symbolic]
+// CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
 // CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

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

@@ -57,7 +57,10 @@ class E {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @I {
+// CHECK:STDOUT:   %Self: I = bind_symbolic_name Self [symbolic]
+// CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
 // CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

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

@@ -32,9 +32,11 @@ interface I {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @I {
+// CHECK:STDOUT:   %Self: I = bind_symbolic_name Self [symbolic]
 // CHECK:STDOUT:   %C.decl = class_decl @C [template = constants.%C] {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
 // CHECK:STDOUT:   .C = %C.decl
 // CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }

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

@@ -34,10 +34,12 @@ impl as Simple {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Simple {
+// CHECK:STDOUT:   %Self: Simple = bind_symbolic_name Self [symbolic]
 // 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:   .Self = %Self
 // CHECK:STDOUT:   .F = %.loc8
 // CHECK:STDOUT:   witness = (%F)
 // CHECK:STDOUT: }

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

@@ -30,7 +30,10 @@ impl true as I {}
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @I {
+// CHECK:STDOUT:   %Self: I = bind_symbolic_name Self [symbolic]
+// CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
 // CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

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

@@ -36,7 +36,10 @@ impl i32 as I {}
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @I {
+// CHECK:STDOUT:   %Self: I = bind_symbolic_name Self [symbolic]
+// CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
 // CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 2 - 0
toolchain/check/testdata/impl/fail_todo_extend_impl.carbon

@@ -55,10 +55,12 @@ fn G(c: C) {
 // 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: }

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

@@ -34,10 +34,12 @@ class C {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Simple {
+// CHECK:STDOUT:   %Self: Simple = bind_symbolic_name Self [symbolic]
 // 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:   .Self = %Self
 // CHECK:STDOUT:   .F = %.loc8
 // CHECK:STDOUT:   witness = (%F)
 // CHECK:STDOUT: }

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

@@ -34,10 +34,12 @@ impl forall [T:! type] T as Simple {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Simple {
+// CHECK:STDOUT:   %Self: Simple = bind_symbolic_name Self [symbolic]
 // 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:   .Self = %Self
 // CHECK:STDOUT:   .F = %.loc8
 // CHECK:STDOUT:   witness = (%F)
 // CHECK:STDOUT: }

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

@@ -38,7 +38,10 @@ impl i32 as I {}
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @I {
+// CHECK:STDOUT:   %Self: I = bind_symbolic_name Self [symbolic]
+// CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
 // CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

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

@@ -29,7 +29,10 @@ fn F(e: Empty) {}
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Empty {
+// CHECK:STDOUT:   %Self: Empty = bind_symbolic_name Self [symbolic]
+// CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
 // CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 8 - 7
toolchain/check/testdata/interface/fail_as_type_of_type.carbon → toolchain/check/testdata/interface/as_type_of_type.carbon

@@ -7,14 +7,10 @@
 interface Empty {}
 
 fn F(T:! Empty) {
-  // TODO: Support conversion from `T` to `type`.
-  // CHECK:STDERR: fail_as_type_of_type.carbon:[[@LINE+3]]:10: ERROR: Cannot implicitly convert from `Empty` to `type`.
-  // CHECK:STDERR:   var x: T;
-  // CHECK:STDERR:          ^
   var x: T;
 }
 
-// CHECK:STDOUT: --- fail_as_type_of_type.carbon
+// CHECK:STDOUT: --- as_type_of_type.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %.1: type = interface_type @Empty [template]
@@ -35,15 +31,20 @@ fn F(T:! Empty) {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Empty {
+// CHECK:STDOUT:   %Self: Empty = bind_symbolic_name Self [symbolic]
+// CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
 // CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F(%T: Empty) {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %T.ref: Empty = name_ref T, %T [symbolic = %T]
-// CHECK:STDOUT:   %x.var: ref <error> = var x
-// CHECK:STDOUT:   %x: ref <error> = bind_name x, %x.var
+// CHECK:STDOUT:   %.loc10_10.1: type = facet_type_access %T.ref [symbolic = %T]
+// CHECK:STDOUT:   %.loc10_10.2: type = converted %T.ref, %.loc10_10.1 [symbolic = %T]
+// CHECK:STDOUT:   %x.var: ref T = var x
+// CHECK:STDOUT:   %x: ref T = bind_name x, %x.var
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

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

@@ -27,12 +27,14 @@ interface I {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @I {
+// CHECK:STDOUT:   %Self: I = bind_symbolic_name Self [symbolic]
 // CHECK:STDOUT:   %T: type = assoc_const_decl T [template]
 // CHECK:STDOUT:   %.loc8: <associated type in I> = assoc_entity element0, %T [template = constants.%.3]
 // CHECK:STDOUT:   %N: i32 = assoc_const_decl N [template]
 // CHECK:STDOUT:   %.loc9: <associated i32 in I> = assoc_entity element1, %N [template = constants.%.5]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
 // CHECK:STDOUT:   .T = %.loc8
 // CHECK:STDOUT:   .N = %.loc9
 // CHECK:STDOUT:   witness = (%T, %N)

+ 5 - 0
toolchain/check/testdata/interface/basic.carbon

@@ -33,15 +33,20 @@ interface ForwardDeclared {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Empty {
+// CHECK:STDOUT:   %Self: Empty = bind_symbolic_name Self [symbolic]
+// CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
 // CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @ForwardDeclared {
+// CHECK:STDOUT:   %Self: ForwardDeclared = bind_symbolic_name Self [symbolic]
 // 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:   .Self = %Self
 // CHECK:STDOUT:   .F = %.loc13
 // CHECK:STDOUT:   witness = (%F)
 // CHECK:STDOUT: }

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

@@ -44,24 +44,31 @@ interface Outer {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Interface {
+// CHECK:STDOUT:   %Self: Interface = bind_symbolic_name Self [symbolic]
+// CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
 // CHECK:STDOUT:   .F = file.%F
 // CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Outer {
+// CHECK:STDOUT:   %Self: Outer = bind_symbolic_name Self [symbolic]
 // CHECK:STDOUT:   %Inner.decl = interface_decl @Inner [template = constants.%.3] {}
 // CHECK:STDOUT:   %F: <function> = fn_decl @F.2 [template] {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
 // CHECK:STDOUT:   .Inner = %Inner.decl
 // CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Inner {
+// CHECK:STDOUT:   %Self: Inner = bind_symbolic_name Self [symbolic]
 // CHECK:STDOUT:   %.loc20: <function> = fn_decl @.1 [template] {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
 // CHECK:STDOUT:   .F = @Outer.%F
 // CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }

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

@@ -28,11 +28,13 @@ interface I {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @I {
+// CHECK:STDOUT:   %Self: I = bind_symbolic_name Self [symbolic]
 // CHECK:STDOUT:   %.loc11_18: i32 = int_literal 42 [template = constants.%.2]
 // CHECK:STDOUT:   %T: type = assoc_const_decl T [template]
 // CHECK:STDOUT:   %.loc11_20: <associated type in I> = assoc_entity element0, %T [template = constants.%.4]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
 // CHECK:STDOUT:   .T = %.loc11_20
 // CHECK:STDOUT:   witness = (%T)
 // CHECK:STDOUT: }

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

@@ -21,6 +21,7 @@ interface I {
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @I {
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = <unexpected instref inst+3>
 // CHECK:STDOUT:   witness = invalid
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

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

@@ -39,7 +39,10 @@ alias UseOther = I.other;
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @I {
+// CHECK:STDOUT:   %Self: I = bind_symbolic_name Self [symbolic]
+// CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
 // CHECK:STDOUT:   has_error
 // CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }

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

@@ -21,6 +21,7 @@ interface I {
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @I {
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = <unexpected instref inst+3>
 // CHECK:STDOUT:   witness = invalid
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 4 - 0
toolchain/check/testdata/interface/fail_duplicate.carbon

@@ -64,13 +64,17 @@ interface Class { }
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .F = <error>
+// CHECK:STDOUT:   .Self = <unexpected instref inst+3>
 // CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @.1;
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @.2 {
+// CHECK:STDOUT:   %Self: <invalid> = bind_symbolic_name Self [symbolic]
+// CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
 // CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

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

@@ -65,6 +65,7 @@ interface BeingDefined {
 // CHECK:STDOUT: interface @Undefined;
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @BeingDefined {
+// CHECK:STDOUT:   %Self: BeingDefined = bind_symbolic_name Self [symbolic]
 // CHECK:STDOUT:   %H: <function> = fn_decl @H [template] {
 // CHECK:STDOUT:     %BeingDefined.ref: type = name_ref BeingDefined, file.%BeingDefined.decl [template = constants.%.2]
 // CHECK:STDOUT:     %T.ref: <error> = name_ref T, <error> [template = <error>]
@@ -74,6 +75,7 @@ interface BeingDefined {
 // CHECK:STDOUT:   %.loc41: <function> = fn_decl @.2 [template] {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
 // CHECK:STDOUT:   .H = %.loc37
 // CHECK:STDOUT:   witness = (%H)
 // CHECK:STDOUT: }

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

@@ -42,12 +42,14 @@ fn F() {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Interface {
+// CHECK:STDOUT:   %Self: Interface = bind_symbolic_name Self [symbolic]
 // CHECK:STDOUT:   %F: <function> = fn_decl @F.1 [template] {}
 // CHECK:STDOUT:   %.loc8: <associated <function> in Interface> = assoc_entity element0, %F [template = constants.%.3]
 // CHECK:STDOUT:   %T: type = assoc_const_decl T [template]
 // CHECK:STDOUT:   %.loc10: <associated type in Interface> = assoc_entity element1, %T [template = constants.%.5]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
 // CHECK:STDOUT:   .F = %.loc8
 // CHECK:STDOUT:   .T = %.loc10
 // CHECK:STDOUT:   witness = (%F, %T)

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

@@ -49,14 +49,20 @@ protected interface Protected;
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Abstract {
+// CHECK:STDOUT:   %Self: Abstract = bind_symbolic_name Self [symbolic]
+// CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
 // CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Default;
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Virtual {
+// CHECK:STDOUT:   %Self: Virtual = bind_symbolic_name Self [symbolic]
+// CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
 // CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

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

@@ -31,11 +31,13 @@ interface Interface {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Interface {
+// CHECK:STDOUT:   %Self: Interface = bind_symbolic_name Self [symbolic]
 // 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:   .Self = %Self
 // CHECK:STDOUT:   .F = %.loc8
 // CHECK:STDOUT:   witness = (%F)
 // CHECK:STDOUT: }

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

@@ -36,6 +36,7 @@ interface I {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @I {
+// CHECK:STDOUT:   %Self: I = bind_symbolic_name Self [symbolic]
 // CHECK:STDOUT:   %.loc11_35.1: (type, type) = tuple_literal (i32, i32)
 // CHECK:STDOUT:   %.loc11_35.2: type = converted %.loc11_35.1, constants.%.3 [template = constants.%.3]
 // CHECK:STDOUT:   %T: type = assoc_const_decl T [template]
@@ -45,6 +46,7 @@ interface I {
 // CHECK:STDOUT:   %.loc15_27: <associated i32 in I> = assoc_entity element1, %N [template = constants.%.8]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
 // CHECK:STDOUT:   .T = %.loc11_36
 // CHECK:STDOUT:   .N = %.loc15_27
 // CHECK:STDOUT:   witness = (%T, %N)

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

@@ -36,10 +36,12 @@ fn Interface.F() {}
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Interface {
+// CHECK:STDOUT:   %Self: Interface = bind_symbolic_name Self [symbolic]
 // 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:   .Self = %Self
 // CHECK:STDOUT:   .F = %.loc11
 // CHECK:STDOUT:   witness = (%F)
 // CHECK:STDOUT: }

+ 27 - 1
toolchain/check/testdata/interface/fail_todo_facet_lookup.carbon

@@ -13,6 +13,13 @@ fn CallStatic(T:! Interface) {
   T.F();
 }
 
+fn CallFacet(T:! Interface, x: T) {
+  // CHECK:STDERR: fail_todo_facet_lookup.carbon:[[@LINE+3]]:3: ERROR: Type `T` does not support qualified expressions.
+  // CHECK:STDERR:   x.F();
+  // CHECK:STDERR:   ^~~
+  x.F();
+}
+
 // CHECK:STDOUT: --- fail_todo_facet_lookup.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -26,20 +33,33 @@ fn CallStatic(T:! Interface) {
 // CHECK:STDOUT:   package: <namespace> = namespace [template] {
 // CHECK:STDOUT:     .Interface = %Interface.decl
 // CHECK:STDOUT:     .CallStatic = %CallStatic
+// CHECK:STDOUT:     .CallFacet = %CallFacet
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Interface.decl = interface_decl @Interface [template = constants.%.1] {}
 // CHECK:STDOUT:   %CallStatic: <function> = fn_decl @CallStatic [template] {
-// CHECK:STDOUT:     %Interface.ref: type = name_ref Interface, %Interface.decl [template = constants.%.1]
+// CHECK:STDOUT:     %Interface.ref.loc9: type = name_ref Interface, %Interface.decl [template = constants.%.1]
 // CHECK:STDOUT:     %T.loc9_15.1: Interface = param T
 // CHECK:STDOUT:     @CallStatic.%T: Interface = bind_symbolic_name T, %T.loc9_15.1 [symbolic]
 // CHECK:STDOUT:   }
+// CHECK:STDOUT:   %CallFacet: <function> = fn_decl @CallFacet [template] {
+// CHECK:STDOUT:     %Interface.ref.loc16: type = name_ref Interface, %Interface.decl [template = constants.%.1]
+// CHECK:STDOUT:     %T.loc16_14.1: Interface = param T
+// CHECK:STDOUT:     @CallFacet.%T: Interface = bind_symbolic_name T, %T.loc16_14.1 [symbolic]
+// CHECK:STDOUT:     %T.ref: Interface = name_ref T, @CallFacet.%T [symbolic = @CallFacet.%T]
+// CHECK:STDOUT:     %.loc16_32.1: type = facet_type_access %T.ref [symbolic = @CallFacet.%T]
+// CHECK:STDOUT:     %.loc16_32.2: type = converted %T.ref, %.loc16_32.1 [symbolic = @CallFacet.%T]
+// CHECK:STDOUT:     %x.loc16_29.1: T = param x
+// CHECK:STDOUT:     @CallFacet.%x: T = bind_name x, %x.loc16_29.1
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Interface {
+// CHECK:STDOUT:   %Self: Interface = bind_symbolic_name Self [symbolic]
 // 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:   .Self = %Self
 // CHECK:STDOUT:   .F = %.loc7
 // CHECK:STDOUT:   witness = (%F)
 // CHECK:STDOUT: }
@@ -52,3 +72,9 @@ fn CallStatic(T:! Interface) {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: fn @CallFacet(%T: Interface, %x: T) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %x.ref: T = name_ref x, %x
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 39 - 25
toolchain/check/testdata/interface/fail_todo_import.carbon

@@ -88,29 +88,36 @@ var f: ForwardDeclared* = &f_ref.f;
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Empty {
+// CHECK:STDOUT:   %Self: Empty = bind_symbolic_name Self [symbolic]
+// CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
 // CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Basic {
+// CHECK:STDOUT:   %Self: Basic = bind_symbolic_name Self [symbolic]
 // CHECK:STDOUT:   %T: type = assoc_const_decl T [template]
 // CHECK:STDOUT:   %.loc8: <associated type in Basic> = assoc_entity element0, %T [template = constants.%.4]
 // CHECK:STDOUT:   %F: <function> = fn_decl @F.1 [template] {}
 // CHECK:STDOUT:   %.loc9: <associated <function> in Basic> = assoc_entity element1, %F [template = constants.%.6]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
 // CHECK:STDOUT:   .T = %.loc8
 // CHECK:STDOUT:   .F = %.loc9
 // CHECK:STDOUT:   witness = (%T, %F)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @ForwardDeclared {
+// CHECK:STDOUT:   %Self: ForwardDeclared = bind_symbolic_name Self [symbolic]
 // CHECK:STDOUT:   %T: type = assoc_const_decl T [template]
 // CHECK:STDOUT:   %.loc15: <associated type in ForwardDeclared> = assoc_entity element0, %T [template = constants.%.9]
 // CHECK:STDOUT:   %F: <function> = fn_decl @F.2 [template] {}
 // CHECK:STDOUT:   %.loc16: <associated <function> in ForwardDeclared> = assoc_entity element1, %F [template = constants.%.11]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
 // CHECK:STDOUT:   .T = %.loc15
 // CHECK:STDOUT:   .F = %.loc16
 // CHECK:STDOUT:   witness = (%T, %F)
@@ -148,9 +155,9 @@ var f: ForwardDeclared* = &f_ref.f;
 // CHECK:STDOUT:     .f = %f.loc16
 // CHECK:STDOUT:   }
 // 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: type = import_ref ir1, inst+13, used [template = constants.%.4]
-// CHECK:STDOUT:   %import_ref.4: ref {.f: ForwardDeclared} = import_ref ir1, inst+33, used
+// CHECK:STDOUT:   %import_ref.2: type = import_ref ir1, inst+4, used [template = constants.%.3]
+// CHECK:STDOUT:   %import_ref.3: type = import_ref ir1, inst+15, used [template = constants.%.4]
+// CHECK:STDOUT:   %import_ref.4: ref {.f: ForwardDeclared} = import_ref ir1, inst+36, used
 // CHECK:STDOUT:   %UseEmpty: <function> = fn_decl @UseEmpty [template] {
 // CHECK:STDOUT:     %Empty.decl = interface_decl @Empty [template = constants.%.1] {}
 // CHECK:STDOUT:     %Empty.ref: type = name_ref Empty, %import_ref.1 [template = constants.%.1]
@@ -170,17 +177,17 @@ var f: ForwardDeclared* = &f_ref.f;
 // CHECK:STDOUT:     @UseForwardDeclared.%f: ForwardDeclared = bind_name f, %f.loc8_23.1
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Basic.ref.loc10: type = name_ref Basic, %import_ref.2 [template = constants.%.3]
-// CHECK:STDOUT:   %T.ref.loc10: <error> = name_ref T, @Basic.%import_ref.2 [template = <error>]
-// CHECK:STDOUT:   %UseBasicT: <error> = bind_alias UseBasicT, @Basic.%import_ref.2 [template = <error>]
+// CHECK:STDOUT:   %T.ref.loc10: <error> = name_ref T, @Basic.%import_ref.3 [template = <error>]
+// CHECK:STDOUT:   %UseBasicT: <error> = bind_alias UseBasicT, @Basic.%import_ref.3 [template = <error>]
 // CHECK:STDOUT:   %Basic.ref.loc11: type = name_ref Basic, %import_ref.2 [template = constants.%.3]
-// CHECK:STDOUT:   %F.ref.loc11: <error> = name_ref F, @Basic.%import_ref.1 [template = <error>]
-// CHECK:STDOUT:   %UseBasicF: <error> = bind_alias UseBasicF, @Basic.%import_ref.1 [template = <error>]
+// CHECK:STDOUT:   %F.ref.loc11: <error> = name_ref F, @Basic.%import_ref.2 [template = <error>]
+// CHECK:STDOUT:   %UseBasicF: <error> = bind_alias UseBasicF, @Basic.%import_ref.2 [template = <error>]
 // CHECK:STDOUT:   %ForwardDeclared.ref.loc13: type = name_ref ForwardDeclared, %import_ref.3 [template = constants.%.4]
-// CHECK:STDOUT:   %T.ref.loc13: <error> = name_ref T, @ForwardDeclared.%import_ref.2 [template = <error>]
-// CHECK:STDOUT:   %UseForwardDeclaredT: <error> = bind_alias UseForwardDeclaredT, @ForwardDeclared.%import_ref.2 [template = <error>]
+// CHECK:STDOUT:   %T.ref.loc13: <error> = name_ref T, @ForwardDeclared.%import_ref.3 [template = <error>]
+// CHECK:STDOUT:   %UseForwardDeclaredT: <error> = bind_alias UseForwardDeclaredT, @ForwardDeclared.%import_ref.3 [template = <error>]
 // CHECK:STDOUT:   %ForwardDeclared.ref.loc14: type = name_ref ForwardDeclared, %import_ref.3 [template = constants.%.4]
-// CHECK:STDOUT:   %F.ref.loc14: <error> = name_ref F, @ForwardDeclared.%import_ref.1 [template = <error>]
-// CHECK:STDOUT:   %UseForwardDeclaredF: <error> = bind_alias UseForwardDeclaredF, @ForwardDeclared.%import_ref.1 [template = <error>]
+// CHECK:STDOUT:   %F.ref.loc14: <error> = name_ref F, @ForwardDeclared.%import_ref.2 [template = <error>]
+// CHECK:STDOUT:   %UseForwardDeclaredF: <error> = bind_alias UseForwardDeclaredF, @ForwardDeclared.%import_ref.2 [template = <error>]
 // CHECK:STDOUT:   %ForwardDeclared.ref.loc16: type = name_ref ForwardDeclared, %import_ref.3 [template = constants.%.4]
 // CHECK:STDOUT:   %.loc16: type = ptr_type ForwardDeclared [template = constants.%.5]
 // CHECK:STDOUT:   %f.var: ref ForwardDeclared* = var f
@@ -188,32 +195,39 @@ var f: ForwardDeclared* = &f_ref.f;
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Empty {
+// CHECK:STDOUT:   %import_ref = import_ref ir1, inst+3, unused
+// CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %import_ref
 // CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Basic {
-// CHECK:STDOUT:   %import_ref.1: <error> = import_ref ir1, inst+11, used [template = <error>]
-// CHECK:STDOUT:   %import_ref.2: <error> = import_ref ir1, inst+7, used [template = <error>]
-// CHECK:STDOUT:   %import_ref.3 = import_ref ir1, inst+5, unused
-// CHECK:STDOUT:   %import_ref.4 = import_ref ir1, inst+9, unused
+// CHECK:STDOUT:   %import_ref.1 = import_ref ir1, inst+6, unused
+// CHECK:STDOUT:   %import_ref.2: <error> = import_ref ir1, inst+13, used [template = <error>]
+// CHECK:STDOUT:   %import_ref.3: <error> = import_ref ir1, inst+9, used [template = <error>]
+// CHECK:STDOUT:   %import_ref.4 = import_ref ir1, inst+7, unused
+// CHECK:STDOUT:   %import_ref.5 = import_ref ir1, inst+11, unused
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .F = %import_ref.1
-// CHECK:STDOUT:   .T = %import_ref.2
-// CHECK:STDOUT:   witness = (%import_ref.3, %import_ref.4)
+// CHECK:STDOUT:   .Self = %import_ref.1
+// CHECK:STDOUT:   .F = %import_ref.2
+// CHECK:STDOUT:   .T = %import_ref.3
+// CHECK:STDOUT:   witness = (%import_ref.4, %import_ref.5)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @ForwardDeclared {
-// CHECK:STDOUT:   %import_ref.1: <error> = import_ref ir1, inst+22, used [template = <error>]
-// CHECK:STDOUT:   %import_ref.2: <error> = import_ref ir1, inst+18, used [template = <error>]
-// CHECK:STDOUT:   %import_ref.3 = import_ref ir1, inst+16, unused
-// CHECK:STDOUT:   %import_ref.4 = import_ref ir1, inst+20, unused
+// CHECK:STDOUT:   %import_ref.1 = import_ref ir1, inst+18, unused
+// CHECK:STDOUT:   %import_ref.2: <error> = import_ref ir1, inst+25, used [template = <error>]
+// CHECK:STDOUT:   %import_ref.3: <error> = import_ref ir1, inst+21, used [template = <error>]
+// CHECK:STDOUT:   %import_ref.4 = import_ref ir1, inst+19, unused
+// CHECK:STDOUT:   %import_ref.5 = import_ref ir1, inst+23, unused
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .F = %import_ref.1
-// CHECK:STDOUT:   .T = %import_ref.2
-// CHECK:STDOUT:   witness = (%import_ref.3, %import_ref.4)
+// CHECK:STDOUT:   .Self = %import_ref.1
+// CHECK:STDOUT:   .F = %import_ref.2
+// CHECK:STDOUT:   .T = %import_ref.3
+// CHECK:STDOUT:   witness = (%import_ref.4, %import_ref.5)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @UseEmpty(%e: Empty) {

+ 5 - 0
toolchain/check/testdata/interface/fail_todo_modifiers.carbon

@@ -41,19 +41,24 @@ private interface Private {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Modifiers {
+// CHECK:STDOUT:   %Self: Modifiers = bind_symbolic_name Self [symbolic]
 // 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:   .Self = %Self
 // CHECK:STDOUT:   .Final = %.loc11
 // CHECK:STDOUT:   .Default = %.loc15
 // CHECK:STDOUT:   witness = (%Final, %Default)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Private {
+// CHECK:STDOUT:   %Self: Private = bind_symbolic_name Self [symbolic]
+// CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
 // CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 31 - 17
toolchain/check/testdata/interface/import.carbon

@@ -74,29 +74,36 @@ var f: ForwardDeclared* = &f_ref.f;
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Empty {
+// CHECK:STDOUT:   %Self: Empty = bind_symbolic_name Self [symbolic]
+// CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
 // CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Basic {
+// CHECK:STDOUT:   %Self: Basic = bind_symbolic_name Self [symbolic]
 // CHECK:STDOUT:   %T: type = assoc_const_decl T [template]
 // CHECK:STDOUT:   %.loc8: <associated type in Basic> = assoc_entity element0, %T [template = constants.%.4]
 // CHECK:STDOUT:   %F: <function> = fn_decl @F.1 [template] {}
 // CHECK:STDOUT:   %.loc9: <associated <function> in Basic> = assoc_entity element1, %F [template = constants.%.6]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
 // CHECK:STDOUT:   .T = %.loc8
 // CHECK:STDOUT:   .F = %.loc9
 // CHECK:STDOUT:   witness = (%T, %F)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @ForwardDeclared {
+// CHECK:STDOUT:   %Self: ForwardDeclared = bind_symbolic_name Self [symbolic]
 // CHECK:STDOUT:   %T: type = assoc_const_decl T [template]
 // CHECK:STDOUT:   %.loc15: <associated type in ForwardDeclared> = assoc_entity element0, %T [template = constants.%.9]
 // CHECK:STDOUT:   %F: <function> = fn_decl @F.2 [template] {}
 // CHECK:STDOUT:   %.loc16: <associated <function> in ForwardDeclared> = assoc_entity element1, %F [template = constants.%.11]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
 // CHECK:STDOUT:   .T = %.loc15
 // CHECK:STDOUT:   .F = %.loc16
 // CHECK:STDOUT:   witness = (%T, %F)
@@ -130,9 +137,9 @@ var f: ForwardDeclared* = &f_ref.f;
 // CHECK:STDOUT:     .f = %f.loc10
 // CHECK:STDOUT:   }
 // 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: type = import_ref ir1, inst+13, used [template = constants.%.4]
-// CHECK:STDOUT:   %import_ref.4: ref {.f: ForwardDeclared} = import_ref ir1, inst+33, used
+// CHECK:STDOUT:   %import_ref.2: type = import_ref ir1, inst+4, used [template = constants.%.3]
+// CHECK:STDOUT:   %import_ref.3: type = import_ref ir1, inst+15, used [template = constants.%.4]
+// CHECK:STDOUT:   %import_ref.4: ref {.f: ForwardDeclared} = import_ref ir1, inst+36, used
 // CHECK:STDOUT:   %UseEmpty: <function> = fn_decl @UseEmpty [template] {
 // CHECK:STDOUT:     %Empty.decl = interface_decl @Empty [template = constants.%.1] {}
 // CHECK:STDOUT:     %Empty.ref: type = name_ref Empty, %import_ref.1 [template = constants.%.1]
@@ -158,32 +165,39 @@ var f: ForwardDeclared* = &f_ref.f;
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Empty {
+// CHECK:STDOUT:   %import_ref = import_ref ir1, inst+3, unused
+// CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %import_ref
 // CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Basic {
-// CHECK:STDOUT:   %import_ref.1 = import_ref ir1, inst+11, unused
-// CHECK:STDOUT:   %import_ref.2 = import_ref ir1, inst+7, unused
-// CHECK:STDOUT:   %import_ref.3 = import_ref ir1, inst+5, unused
-// CHECK:STDOUT:   %import_ref.4 = import_ref ir1, inst+9, unused
+// CHECK:STDOUT:   %import_ref.1 = import_ref ir1, inst+6, unused
+// CHECK:STDOUT:   %import_ref.2 = import_ref ir1, inst+13, unused
+// CHECK:STDOUT:   %import_ref.3 = import_ref ir1, inst+9, unused
+// CHECK:STDOUT:   %import_ref.4 = import_ref ir1, inst+7, unused
+// CHECK:STDOUT:   %import_ref.5 = import_ref ir1, inst+11, unused
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .F = %import_ref.1
-// CHECK:STDOUT:   .T = %import_ref.2
-// CHECK:STDOUT:   witness = (%import_ref.3, %import_ref.4)
+// CHECK:STDOUT:   .Self = %import_ref.1
+// CHECK:STDOUT:   .F = %import_ref.2
+// CHECK:STDOUT:   .T = %import_ref.3
+// CHECK:STDOUT:   witness = (%import_ref.4, %import_ref.5)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @ForwardDeclared {
-// CHECK:STDOUT:   %import_ref.1 = import_ref ir1, inst+22, unused
-// CHECK:STDOUT:   %import_ref.2 = import_ref ir1, inst+18, unused
-// CHECK:STDOUT:   %import_ref.3 = import_ref ir1, inst+16, unused
-// CHECK:STDOUT:   %import_ref.4 = import_ref ir1, inst+20, unused
+// CHECK:STDOUT:   %import_ref.1 = import_ref ir1, inst+18, unused
+// CHECK:STDOUT:   %import_ref.2 = import_ref ir1, inst+25, unused
+// CHECK:STDOUT:   %import_ref.3 = import_ref ir1, inst+21, unused
+// CHECK:STDOUT:   %import_ref.4 = import_ref ir1, inst+19, unused
+// CHECK:STDOUT:   %import_ref.5 = import_ref ir1, inst+23, unused
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .F = %import_ref.1
-// CHECK:STDOUT:   .T = %import_ref.2
-// CHECK:STDOUT:   witness = (%import_ref.3, %import_ref.4)
+// CHECK:STDOUT:   .Self = %import_ref.1
+// CHECK:STDOUT:   .F = %import_ref.2
+// CHECK:STDOUT:   .T = %import_ref.3
+// CHECK:STDOUT:   witness = (%import_ref.4, %import_ref.5)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @UseEmpty(%e: Empty) {

+ 48 - 0
toolchain/check/testdata/interface/self.carbon

@@ -0,0 +1,48 @@
+// 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 UseSelf {
+  fn F[self: Self]() -> Self;
+}
+
+// CHECK:STDOUT: --- self.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: type = interface_type @UseSelf [template]
+// CHECK:STDOUT:   %.2: type = assoc_entity_type @UseSelf, <function> [template]
+// CHECK:STDOUT:   %.3: <associated <function> in UseSelf> = assoc_entity element0, @UseSelf.%F [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .UseSelf = %UseSelf.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %UseSelf.decl = interface_decl @UseSelf [template = constants.%.1] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @UseSelf {
+// CHECK:STDOUT:   %Self: UseSelf = bind_symbolic_name Self [symbolic]
+// CHECK:STDOUT:   %F: <function> = fn_decl @F [template] {
+// CHECK:STDOUT:     %Self.ref.loc8_14: UseSelf = name_ref Self, %Self [symbolic = %Self]
+// CHECK:STDOUT:     %.loc8_14.1: type = facet_type_access %Self.ref.loc8_14 [symbolic = %Self]
+// CHECK:STDOUT:     %.loc8_14.2: type = converted %Self.ref.loc8_14, %.loc8_14.1 [symbolic = %Self]
+// CHECK:STDOUT:     %self.loc8_8.1: Self = param self
+// CHECK:STDOUT:     %self.loc8_8.2: Self = bind_name self, %self.loc8_8.1
+// CHECK:STDOUT:     %Self.ref.loc8_25: UseSelf = name_ref Self, %Self [symbolic = %Self]
+// CHECK:STDOUT:     %.loc8_25.1: type = facet_type_access %Self.ref.loc8_25 [symbolic = %Self]
+// CHECK:STDOUT:     %.loc8_25.2: type = converted %Self.ref.loc8_25, %.loc8_25.1 [symbolic = %Self]
+// CHECK:STDOUT:     %return.var: ref Self = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.loc8_29: <associated <function> in UseSelf> = assoc_entity element0, %F [template = constants.%.3]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
+// CHECK:STDOUT:   .F = %.loc8_29
+// CHECK:STDOUT:   witness = (%F)
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F[@UseSelf.%self.loc8_8.2: Self]() -> Self;
+// CHECK:STDOUT:

+ 5 - 0
toolchain/lower/handle_type.cpp

@@ -27,6 +27,11 @@ auto HandleConstType(FunctionContext& context, SemIR::InstId inst_id,
   context.SetLocal(inst_id, context.GetTypeAsValue());
 }
 
+auto HandleFacetTypeAccess(FunctionContext& context, SemIR::InstId inst_id,
+                           SemIR::FacetTypeAccess /*inst*/) -> void {
+  context.SetLocal(inst_id, context.GetTypeAsValue());
+}
+
 auto HandleInterfaceType(FunctionContext& context, SemIR::InstId inst_id,
                          SemIR::InterfaceType /*inst*/) -> void {
   context.SetLocal(inst_id, context.GetTypeAsValue());

+ 7 - 0
toolchain/sem_ir/file.cpp

@@ -196,6 +196,7 @@ static auto GetTypePrecedence(InstKind kind) -> int {
     case BindSymbolicName::Kind:
     case Builtin::Kind:
     case ClassType::Kind:
+    case FacetTypeAccess::Kind:
     case ImportRefUsed::Kind:
     case InterfaceType::Kind:
     case NameRef::Kind:
@@ -365,6 +366,11 @@ static auto StringifyTypeExprImpl(const SemIR::File& outer_sem_ir,
         }
         break;
       }
+      case FacetTypeAccess::Kind: {
+        // Print `T as type` as simply `T`.
+        push_inst_id(inst.As<FacetTypeAccess>().facet_id);
+        break;
+      }
       case ImportRefUsed::Kind: {
         auto import_ref = inst.As<ImportRefUsed>();
         steps.push_back({.sem_ir = *sem_ir.import_irs().Get(import_ref.ir_id),
@@ -582,6 +588,7 @@ auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory {
       case ClassDecl::Kind:
       case ClassType::Kind:
       case ConstType::Kind:
+      case FacetTypeAccess::Kind:
       case InterfaceDecl::Kind:
       case InterfaceType::Kind:
       case IntLiteral::Kind:

+ 10 - 0
toolchain/sem_ir/formatter.cpp

@@ -1013,6 +1013,16 @@ class Formatter {
     }
   }
 
+  auto FormatInstructionRHS(BindSymbolicName inst) -> void {
+    // A BindSymbolicName with no value is a purely symbolic binding, such as
+    // the `Self` in an interface. Don't print out `invalid` for the value.
+    if (inst.value_id.is_valid()) {
+      FormatArgs(inst.bind_name_id, inst.value_id);
+    } else {
+      FormatArgs(inst.bind_name_id);
+    }
+  }
+
   auto FormatInstructionRHS(BlockArg inst) -> void {
     out_ << " ";
     FormatLabel(inst.block_id);

+ 1 - 0
toolchain/sem_ir/inst_kind.def

@@ -46,6 +46,7 @@ CARBON_SEM_IR_INST_KIND(ClassType)
 CARBON_SEM_IR_INST_KIND(ConstType)
 CARBON_SEM_IR_INST_KIND(Converted)
 CARBON_SEM_IR_INST_KIND(Deref)
+CARBON_SEM_IR_INST_KIND(FacetTypeAccess)
 CARBON_SEM_IR_INST_KIND(FieldDecl)
 CARBON_SEM_IR_INST_KIND(FunctionDecl)
 CARBON_SEM_IR_INST_KIND(ImplDecl)

+ 2 - 0
toolchain/sem_ir/interface.h

@@ -45,6 +45,8 @@ struct Interface : public Printable<Interface> {
   // The first block of the interface body.
   // TODO: Handle control flow in the interface body, such as if-expressions.
   InstBlockId body_block_id = InstBlockId::Invalid;
+  // The implicit `Self` parameter. This is a BindSymbolicName instruction.
+  InstId self_param_id = InstId::Invalid;
 
   // The following members are set at the `}` of the interface definition.
   InstBlockId associated_entities_id = InstBlockId::Invalid;

+ 7 - 0
toolchain/sem_ir/type.h

@@ -41,6 +41,13 @@ class TypeStore : public ValueStore<TypeId> {
     return insts_->Get(GetInstId(type_id));
   }
 
+  // Returns whether the specified kind of instruction was used to define the
+  // type.
+  template <typename InstT>
+  auto Is(TypeId type_id) const -> bool {
+    return GetAsInst(type_id).Is<InstT>();
+  }
+
   // Returns the instruction used to define the specified type, which is known
   // to be a particular kind of instruction.
   template <typename InstT>

+ 10 - 0
toolchain/sem_ir/typed_insts.h

@@ -414,6 +414,16 @@ struct Deref {
   InstId pointer_id;
 };
 
+// Represents accessing the `type` field in a facet value, which is notionally a
+// pair of a type and a witness.
+struct FacetTypeAccess {
+  static constexpr auto Kind =
+      InstKind::FacetTypeAccess.Define<Parse::NodeId>("facet_type_access");
+
+  TypeId type_id;
+  InstId facet_id;
+};
+
 // A field in a class, of the form `var field: field_type;`. The type of the
 // `FieldDecl` instruction is an `UnboundElementType`.
 struct FieldDecl {