Sfoglia il codice sorgente

Support extended scopes that are parameterized types (#4524)

* The `extended_scopes` in a `NameScope` were represented by a
`NameScopeId`. Replace that with an `InstId` of an instruction returning
the type that is extending this name scope.
* `Context::LookupQualifiedName` now can take multiple scopes to look
in.
* `GetAsLookupScope` was moved out of `member_access.cpp` and is now
`Context::AppendLookupScopesForConstant`

This PR also fixes some existing issues that were revealed as part of
writing and testing this PR:
* Additional validation and handling of invalid ids.
* `extend impl` in a class is not properly imported yet, but at least
now it doesn't crash.

The change to use an `InstId` also allowed some diagnostics and
formatting to be improved.

---------

Co-authored-by: Josh L <josh11b@users.noreply.github.com>
josh11b 1 anno fa
parent
commit
abd12c18c7
46 ha cambiato i file con 2056 aggiunte e 209 eliminazioni
  1. 76 11
      toolchain/check/context.cpp
  2. 12 3
      toolchain/check/context.h
  3. 3 0
      toolchain/check/deduce.cpp
  4. 13 9
      toolchain/check/handle_class.cpp
  5. 20 20
      toolchain/check/handle_impl.cpp
  6. 4 5
      toolchain/check/import_ref.cpp
  7. 32 82
      toolchain/check/member_access.cpp
  8. 1 1
      toolchain/check/testdata/class/base.carbon
  9. 1 1
      toolchain/check/testdata/class/base_field.carbon
  10. 1 1
      toolchain/check/testdata/class/base_function_unqualified.carbon
  11. 1 1
      toolchain/check/testdata/class/base_method.carbon
  12. 1 1
      toolchain/check/testdata/class/base_method_qualified.carbon
  13. 3 3
      toolchain/check/testdata/class/base_method_shadow.carbon
  14. 1 1
      toolchain/check/testdata/class/compound_field.carbon
  15. 2 2
      toolchain/check/testdata/class/derived_to_base.carbon
  16. 3 3
      toolchain/check/testdata/class/extend_adapt.carbon
  17. 3 3
      toolchain/check/testdata/class/fail_abstract.carbon
  18. 2 2
      toolchain/check/testdata/class/fail_adapt_modifiers.carbon
  19. 2 2
      toolchain/check/testdata/class/fail_adapt_with_subobjects.carbon
  20. 1 1
      toolchain/check/testdata/class/fail_base_bad_type.carbon
  21. 1 1
      toolchain/check/testdata/class/fail_base_method_define.carbon
  22. 3 3
      toolchain/check/testdata/class/fail_base_modifiers.carbon
  23. 2 2
      toolchain/check/testdata/class/fail_base_repeated.carbon
  24. 1 1
      toolchain/check/testdata/class/fail_base_unbound.carbon
  25. 1 1
      toolchain/check/testdata/class/fail_derived_to_base.carbon
  26. 2 2
      toolchain/check/testdata/class/fail_extend_cycle.carbon
  27. 1056 0
      toolchain/check/testdata/class/generic/base_is_generic.carbon
  28. 2 2
      toolchain/check/testdata/class/import_base.carbon
  29. 9 9
      toolchain/check/testdata/class/inheritance_access.carbon
  30. 1 1
      toolchain/check/testdata/class/self_conversion.carbon
  31. 1 1
      toolchain/check/testdata/class/virtual_modifiers.carbon
  32. 1 1
      toolchain/check/testdata/impl/extend_impl.carbon
  33. 550 0
      toolchain/check/testdata/impl/extend_impl_generic.carbon
  34. 1 0
      toolchain/check/testdata/impl/fail_extend_impl_forall.carbon
  35. 2 2
      toolchain/check/testdata/impl/fail_extend_impl_type_as.carbon
  36. 4 1
      toolchain/check/testdata/impl/fail_extend_partially_defined_interface.carbon
  37. 50 2
      toolchain/check/testdata/impl/fail_extend_undefined_interface.carbon
  38. 1 1
      toolchain/check/testdata/impl/lookup/fail_todo_undefined_impl.carbon
  39. 1 1
      toolchain/check/testdata/impl/lookup/instance_method.carbon
  40. 11 11
      toolchain/check/testdata/impl/multiple_extend.carbon
  41. 164 0
      toolchain/check/testdata/impl/no_prelude/import_extend_impl.carbon
  42. 2 1
      toolchain/check/testdata/interface/no_prelude/fail_lookup_in_type_type.carbon
  43. 1 0
      toolchain/diagnostics/diagnostic_kind.def
  44. 2 1
      toolchain/sem_ir/facet_type_info.h
  45. 3 2
      toolchain/sem_ir/formatter.cpp
  46. 2 11
      toolchain/sem_ir/name_scope.h

+ 76 - 11
toolchain/check/context.cpp

@@ -321,10 +321,11 @@ auto Context::LookupUnqualifiedName(Parse::NodeId node_id,
   // Walk the non-lexical scopes and perform lookups into each of them.
   // Walk the non-lexical scopes and perform lookups into each of them.
   for (auto [index, lookup_scope_id, specific_id] :
   for (auto [index, lookup_scope_id, specific_id] :
        llvm::reverse(non_lexical_scopes)) {
        llvm::reverse(non_lexical_scopes)) {
-    if (auto non_lexical_result = LookupQualifiedName(
-            node_id, name_id,
-            {.name_scope_id = lookup_scope_id, .specific_id = specific_id},
-            /*required=*/false);
+    if (auto non_lexical_result =
+            LookupQualifiedName(node_id, name_id,
+                                LookupScope{.name_scope_id = lookup_scope_id,
+                                            .specific_id = specific_id},
+                                /*required=*/false);
         non_lexical_result.inst_id.is_valid()) {
         non_lexical_result.inst_id.is_valid()) {
       return non_lexical_result;
       return non_lexical_result;
     }
     }
@@ -440,11 +441,59 @@ struct ProhibitedAccessInfo {
   bool is_parent_access;
   bool is_parent_access;
 };
 };
 
 
+auto Context::AppendLookupScopesForConstant(
+    SemIRLoc loc, SemIR::ConstantId base_const_id,
+    llvm::SmallVector<LookupScope>* scopes) -> bool {
+  auto base_id = constant_values().GetInstId(base_const_id);
+  auto base = insts().Get(base_id);
+  if (auto base_as_namespace = base.TryAs<SemIR::Namespace>()) {
+    scopes->push_back(
+        LookupScope{.name_scope_id = base_as_namespace->name_scope_id,
+                    .specific_id = SemIR::SpecificId::Invalid});
+    return true;
+  }
+  if (auto base_as_class = base.TryAs<SemIR::ClassType>()) {
+    TryToDefineType(GetTypeIdForTypeConstant(base_const_id), [&] {
+      CARBON_DIAGNOSTIC(QualifiedExprInIncompleteClassScope, Error,
+                        "member access into incomplete class {0}",
+                        InstIdAsType);
+      return emitter().Build(loc, QualifiedExprInIncompleteClassScope, base_id);
+    });
+    auto& class_info = classes().Get(base_as_class->class_id);
+    scopes->push_back(LookupScope{.name_scope_id = class_info.scope_id,
+                                  .specific_id = base_as_class->specific_id});
+    return true;
+  }
+  if (auto base_as_facet_type = base.TryAs<SemIR::FacetType>()) {
+    TryToDefineType(GetTypeIdForTypeConstant(base_const_id), [&] {
+      CARBON_DIAGNOSTIC(QualifiedExprInUndefinedInterfaceScope, Error,
+                        "member access into undefined interface {0}",
+                        InstIdAsType);
+      return emitter().Build(loc, QualifiedExprInUndefinedInterfaceScope,
+                             base_id);
+    });
+    const auto& facet_type_info =
+        facet_types().Get(base_as_facet_type->facet_type_id);
+    for (auto interface : facet_type_info.impls_constraints) {
+      auto& interface_info = interfaces().Get(interface.interface_id);
+      scopes->push_back(LookupScope{.name_scope_id = interface_info.scope_id,
+                                    .specific_id = interface.specific_id});
+    }
+    return true;
+  }
+  // TODO: Per the design, if `base_id` is any kind of type, then lookup should
+  // treat it as a name scope, even if it doesn't have members. For example,
+  // `(i32*).X` should fail because there's no name `X` in `i32*`, not because
+  // there's no name `X` in `type`.
+  return false;
+}
+
 auto Context::LookupQualifiedName(SemIRLoc loc, SemIR::NameId name_id,
 auto Context::LookupQualifiedName(SemIRLoc loc, SemIR::NameId name_id,
-                                  LookupScope scope, bool required,
+                                  llvm::ArrayRef<LookupScope> lookup_scopes,
+                                  bool required,
                                   std::optional<AccessInfo> access_info)
                                   std::optional<AccessInfo> access_info)
     -> LookupResult {
     -> LookupResult {
-  llvm::SmallVector<LookupScope> scopes = {scope};
+  llvm::SmallVector<LookupScope> scopes(lookup_scopes);
 
 
   // TODO: Support reporting of multiple prohibited access.
   // TODO: Support reporting of multiple prohibited access.
   llvm::SmallVector<ProhibitedAccessInfo> prohibited_accesses;
   llvm::SmallVector<ProhibitedAccessInfo> prohibited_accesses;
@@ -457,6 +506,10 @@ auto Context::LookupQualifiedName(SemIRLoc loc, SemIR::NameId name_id,
   // Walk this scope and, if nothing is found here, the scopes it extends.
   // Walk this scope and, if nothing is found here, the scopes it extends.
   while (!scopes.empty()) {
   while (!scopes.empty()) {
     auto [scope_id, specific_id] = scopes.pop_back_val();
     auto [scope_id, specific_id] = scopes.pop_back_val();
+    if (!scope_id.is_valid()) {
+      has_error = true;
+      continue;
+    }
     const auto& name_scope = name_scopes().Get(scope_id);
     const auto& name_scope = name_scopes().Get(scope_id);
     has_error |= name_scope.has_error;
     has_error |= name_scope.has_error;
 
 
@@ -479,13 +532,25 @@ auto Context::LookupQualifiedName(SemIRLoc loc, SemIR::NameId name_id,
     if (!scope_result_id.is_valid() || is_access_prohibited) {
     if (!scope_result_id.is_valid() || is_access_prohibited) {
       // If nothing is found in this scope or if we encountered an invalid
       // If nothing is found in this scope or if we encountered an invalid
       // access, look in its extended scopes.
       // access, look in its extended scopes.
-      auto extended = name_scope.extended_scopes;
+      const auto& extended = name_scope.extended_scopes;
       scopes.reserve(scopes.size() + extended.size());
       scopes.reserve(scopes.size() + extended.size());
       for (auto extended_id : llvm::reverse(extended)) {
       for (auto extended_id : llvm::reverse(extended)) {
-        // TODO: Track a constant describing the extended scope, and substitute
-        // into it to determine its corresponding specific.
-        scopes.push_back({.name_scope_id = extended_id,
-                          .specific_id = SemIR::SpecificId::Invalid});
+        // Substitute into the constant describing the extended scope to
+        // determine its corresponding specific.
+        CARBON_CHECK(extended_id.is_valid());
+        SemIR::ConstantId const_id =
+            GetConstantValueInSpecific(sem_ir(), specific_id, extended_id);
+
+        DiagnosticAnnotationScope annotate_diagnostics(
+            &emitter(), [&](auto& builder) {
+              CARBON_DIAGNOSTIC(FromExtendHere, Note,
+                                "declared as an extended scope here");
+              builder.Note(extended_id, FromExtendHere);
+            });
+        if (!AppendLookupScopesForConstant(loc, const_id, &scopes)) {
+          // TODO: Handle case where we have a symbolic type and instead should
+          // look in its type.
+        }
       }
       }
       is_parent_access |= !extended.empty();
       is_parent_access |= !extended.empty();
       continue;
       continue;

+ 12 - 3
toolchain/check/context.h

@@ -225,10 +225,19 @@ class Context {
                               const SemIR::NameScope& scope)
                               const SemIR::NameScope& scope)
       -> std::pair<SemIR::InstId, SemIR::AccessKind>;
       -> std::pair<SemIR::InstId, SemIR::AccessKind>;
 
 
-  // Performs a qualified name lookup in a specified scope and in scopes that
-  // it extends, returning the referenced instruction.
+  // Appends the lookup scopes corresponding to `base_const_id` to `*scopes`.
+  // Returns `false` if not a scope. On invalid scopes, prints a diagnostic, but
+  // still updates `*scopes` and returns `true`.
+  auto AppendLookupScopesForConstant(SemIRLoc loc,
+                                     SemIR::ConstantId base_const_id,
+                                     llvm::SmallVector<LookupScope>* scopes)
+      -> bool;
+
+  // Performs a qualified name lookup in a specified scopes and in scopes that
+  // they extend, returning the referenced instruction.
   auto LookupQualifiedName(SemIRLoc loc, SemIR::NameId name_id,
   auto LookupQualifiedName(SemIRLoc loc, SemIR::NameId name_id,
-                           LookupScope scope, bool required = true,
+                           llvm::ArrayRef<LookupScope> lookup_scopes,
+                           bool required = true,
                            std::optional<AccessInfo> access_info = std::nullopt)
                            std::optional<AccessInfo> access_info = std::nullopt)
       -> LookupResult;
       -> LookupResult;
 
 

+ 3 - 0
toolchain/check/deduce.cpp

@@ -50,6 +50,9 @@ class DeductionWorklist {
   // Adds a single (param, arg) deduction of a specific.
   // Adds a single (param, arg) deduction of a specific.
   auto Add(SemIR::SpecificId param, SemIR::SpecificId arg,
   auto Add(SemIR::SpecificId param, SemIR::SpecificId arg,
            bool needs_substitution) -> void {
            bool needs_substitution) -> void {
+    if (!param.is_valid() || !arg.is_valid()) {
+      return;
+    }
     auto& param_specific = context_.specifics().Get(param);
     auto& param_specific = context_.specifics().Get(param);
     auto& arg_specific = context_.specifics().Get(arg);
     auto& arg_specific = context_.specifics().Get(arg);
     if (param_specific.generic_id != arg_specific.generic_id) {
     if (param_specific.generic_id != arg_specific.generic_id) {

+ 13 - 9
toolchain/check/handle_class.cpp

@@ -380,8 +380,8 @@ auto HandleParseNode(Context& context, Parse::AdaptDeclId node_id) -> bool {
     return true;
     return true;
   }
   }
 
 
-  auto adapted_type_id =
-      ExprAsType(context, node_id, adapted_type_expr_id).type_id;
+  auto [adapted_inst_id, adapted_type_id] =
+      ExprAsType(context, node_id, adapted_type_expr_id);
   adapted_type_id = context.AsCompleteType(
   adapted_type_id = context.AsCompleteType(
       adapted_type_id,
       adapted_type_id,
       [&] {
       [&] {
@@ -405,13 +405,13 @@ auto HandleParseNode(Context& context, Parse::AdaptDeclId node_id) -> bool {
 
 
   // Extend the class scope with the adapted type's scope if requested.
   // Extend the class scope with the adapted type's scope if requested.
   if (introducer.modifier_set.HasAnyOf(KeywordModifierSet::Extend)) {
   if (introducer.modifier_set.HasAnyOf(KeywordModifierSet::Extend)) {
-    auto extended_scope_id = SemIR::NameScopeId::Invalid;
+    auto extended_scope_inst_id = SemIR::InstId::Invalid;
     if (adapted_type_id == SemIR::TypeId::Error) {
     if (adapted_type_id == SemIR::TypeId::Error) {
       // Recover by not extending any scope. We instead set has_error to true
       // Recover by not extending any scope. We instead set has_error to true
       // below.
       // below.
     } else if (auto* adapted_class_info =
     } else if (auto* adapted_class_info =
                    TryGetAsClass(context, adapted_type_id)) {
                    TryGetAsClass(context, adapted_type_id)) {
-      extended_scope_id = adapted_class_info->scope_id;
+      extended_scope_inst_id = adapted_inst_id;
       CARBON_CHECK(adapted_class_info->scope_id.is_valid(),
       CARBON_CHECK(adapted_class_info->scope_id.is_valid(),
                    "Complete class should have a scope");
                    "Complete class should have a scope");
     } else {
     } else {
@@ -420,8 +420,8 @@ auto HandleParseNode(Context& context, Parse::AdaptDeclId node_id) -> bool {
     }
     }
 
 
     auto& class_scope = context.name_scopes().Get(class_info.scope_id);
     auto& class_scope = context.name_scopes().Get(class_info.scope_id);
-    if (extended_scope_id.is_valid()) {
-      class_scope.extended_scopes.push_back(extended_scope_id);
+    if (extended_scope_inst_id.is_valid()) {
+      class_scope.extended_scopes.push_back(extended_scope_inst_id);
     } else {
     } else {
       class_scope.has_error = true;
       class_scope.has_error = true;
     }
     }
@@ -448,9 +448,11 @@ struct BaseInfo {
 
 
   SemIR::TypeId type_id;
   SemIR::TypeId type_id;
   SemIR::NameScopeId scope_id;
   SemIR::NameScopeId scope_id;
+  SemIR::InstId inst_id;
 };
 };
 constexpr BaseInfo BaseInfo::Error = {.type_id = SemIR::TypeId::Error,
 constexpr BaseInfo BaseInfo::Error = {.type_id = SemIR::TypeId::Error,
-                                      .scope_id = SemIR::NameScopeId::Invalid};
+                                      .scope_id = SemIR::NameScopeId::Invalid,
+                                      .inst_id = SemIR::InstId::Invalid};
 }  // namespace
 }  // namespace
 
 
 // Diagnoses an attempt to derive from a final type.
 // Diagnoses an attempt to derive from a final type.
@@ -496,7 +498,9 @@ static auto CheckBaseType(Context& context, Parse::NodeId node_id,
 
 
   CARBON_CHECK(base_class_info->scope_id.is_valid(),
   CARBON_CHECK(base_class_info->scope_id.is_valid(),
                "Complete class should have a scope");
                "Complete class should have a scope");
-  return {.type_id = base_type_id, .scope_id = base_class_info->scope_id};
+  return {.type_id = base_type_id,
+          .scope_id = base_class_info->scope_id,
+          .inst_id = base_type_inst_id};
 }
 }
 
 
 auto HandleParseNode(Context& context, Parse::BaseDeclId node_id) -> bool {
 auto HandleParseNode(Context& context, Parse::BaseDeclId node_id) -> bool {
@@ -560,7 +564,7 @@ auto HandleParseNode(Context& context, Parse::BaseDeclId node_id) -> bool {
   if (introducer.modifier_set.HasAnyOf(KeywordModifierSet::Extend)) {
   if (introducer.modifier_set.HasAnyOf(KeywordModifierSet::Extend)) {
     auto& class_scope = context.name_scopes().Get(class_info.scope_id);
     auto& class_scope = context.name_scopes().Get(class_info.scope_id);
     if (base_info.scope_id.is_valid()) {
     if (base_info.scope_id.is_valid()) {
-      class_scope.extended_scopes.push_back(base_info.scope_id);
+      class_scope.extended_scopes.push_back(base_info.inst_id);
     } else {
     } else {
       class_scope.has_error = true;
       class_scope.has_error = true;
     }
     }

+ 20 - 20
toolchain/check/handle_impl.cpp

@@ -12,6 +12,7 @@
 #include "toolchain/check/modifiers.h"
 #include "toolchain/check/modifiers.h"
 #include "toolchain/check/pattern_match.h"
 #include "toolchain/check/pattern_match.h"
 #include "toolchain/parse/typed_nodes.h"
 #include "toolchain/parse/typed_nodes.h"
+#include "toolchain/sem_ir/generic.h"
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/typed_insts.h"
 #include "toolchain/sem_ir/typed_insts.h"
 
 
@@ -173,30 +174,20 @@ static auto ExtendImpl(Context& context, Parse::NodeId extend_node,
     diag.Emit();
     diag.Emit();
   }
   }
 
 
-  auto facet_type = context.types().TryGetAs<SemIR::FacetType>(constraint_id);
-  if (!facet_type) {
+  if (!context.types().Is<SemIR::FacetType>(constraint_id)) {
     context.TODO(node_id, "extending non-facet-type constraint");
     context.TODO(node_id, "extending non-facet-type constraint");
     parent_scope.has_error = true;
     parent_scope.has_error = true;
     return;
     return;
   }
   }
-  const SemIR::FacetTypeInfo& info =
-      context.facet_types().Get(facet_type->facet_type_id);
-  for (auto interface_type : info.impls_constraints) {
-    auto& interface = context.interfaces().Get(interface_type.interface_id);
-    if (!interface.is_defined()) {
-      CARBON_DIAGNOSTIC(ExtendUndefinedInterface, Error,
-                        "`extend impl` requires a definition for interface {0}",
-                        InstIdAsType);
-      auto diag = context.emitter().Build(node_id, ExtendUndefinedInterface,
-                                          constraint_inst_id);
-      context.NoteUndefinedInterface(interface_type.interface_id, diag);
-      diag.Emit();
-      parent_scope.has_error = true;
-      return;
-    }
-
-    parent_scope.extended_scopes.push_back(interface.scope_id);
-  }
+  parent_scope.has_error |= !context.TryToDefineType(constraint_id, [&] {
+    CARBON_DIAGNOSTIC(ExtendUndefinedInterface, Error,
+                      "`extend impl` requires a definition for facet type {0}",
+                      InstIdAsType);
+    return context.emitter().Build(node_id, ExtendUndefinedInterface,
+                                   constraint_inst_id);
+  });
+
+  parent_scope.extended_scopes.push_back(constraint_inst_id);
 }
 }
 
 
 // Pops the parameters of an `impl`, forming a `NameComponent` with no
 // Pops the parameters of an `impl`, forming a `NameComponent` with no
@@ -333,6 +324,15 @@ static auto BuildImplDecl(Context& context, Parse::AnyImplDeclId node_id,
   // For an `extend impl` declaration, mark the impl as extending this `impl`.
   // For an `extend impl` declaration, mark the impl as extending this `impl`.
   if (introducer.modifier_set.HasAnyOf(KeywordModifierSet::Extend)) {
   if (introducer.modifier_set.HasAnyOf(KeywordModifierSet::Extend)) {
     auto extend_node = introducer.modifier_node_id(ModifierOrder::Decl);
     auto extend_node = introducer.modifier_node_id(ModifierOrder::Decl);
+    if (impl_info.generic_id.is_valid()) {
+      SemIR::TypeId type_id = context.insts().Get(constraint_inst_id).type_id();
+      constraint_inst_id = context.AddInst<SemIR::SpecificConstant>(
+          context.insts().GetLocId(constraint_inst_id),
+          {.type_id = type_id,
+           .inst_id = constraint_inst_id,
+           .specific_id =
+               context.generics().GetSelfSpecific(impl_info.generic_id)});
+    }
     ExtendImpl(context, extend_node, node_id, self_type_node, self_type_id,
     ExtendImpl(context, extend_node, node_id, self_type_node, self_type_id,
                name.implicit_params_loc_id, constraint_inst_id,
                name.implicit_params_loc_id, constraint_inst_id,
                constraint_type_id);
                constraint_type_id);

+ 4 - 5
toolchain/check/import_ref.cpp

@@ -1460,12 +1460,11 @@ class ImportRefResolver {
           context_.insts()
           context_.insts()
               .GetAs<SemIR::BaseDecl>(new_class.base_id)
               .GetAs<SemIR::BaseDecl>(new_class.base_id)
               .base_type_id);
               .base_type_id);
-      const auto& base_class = context_.classes().Get(
-          context_.insts().GetAs<SemIR::ClassType>(base_inst_id).class_id);
-      new_scope.extended_scopes.push_back(base_class.scope_id);
+      new_scope.extended_scopes.push_back(base_inst_id);
     }
     }
-    CARBON_CHECK(new_scope.extended_scopes.size() ==
-                 import_scope.extended_scopes.size());
+    // TODO: `extended_scopes` from `extend impl` are currently not imported.
+    // CARBON_CHECK(new_scope.extended_scopes.size() ==
+    //              import_scope.extended_scopes.size());
   }
   }
 
 
   auto TryResolveTypedInst(SemIR::ClassDecl inst,
   auto TryResolveTypedInst(SemIR::ClassDecl inst,

+ 32 - 82
toolchain/check/member_access.cpp

@@ -20,58 +20,6 @@
 
 
 namespace Carbon::Check {
 namespace Carbon::Check {
 
 
-// Returns the lookup scope corresponding to base_id, or nullopt if not a scope.
-// On invalid scopes, prints a diagnostic and still returns the scope.
-static auto GetAsLookupScope(Context& context, SemIR::LocId loc_id,
-                             SemIR::ConstantId base_const_id)
-    -> std::optional<LookupScope> {
-  auto base_id = context.constant_values().GetInstId(base_const_id);
-  auto base = context.insts().Get(base_id);
-  if (auto base_as_namespace = base.TryAs<SemIR::Namespace>()) {
-    return LookupScope{.name_scope_id = base_as_namespace->name_scope_id,
-                       .specific_id = SemIR::SpecificId::Invalid};
-  }
-  // TODO: Consider refactoring the near-identical class and interface support
-  // below.
-  if (auto base_as_class = base.TryAs<SemIR::ClassType>()) {
-    context.TryToDefineType(
-        context.GetTypeIdForTypeConstant(base_const_id), [&] {
-          CARBON_DIAGNOSTIC(QualifiedExprInIncompleteClassScope, Error,
-                            "member access into incomplete class {0}",
-                            InstIdAsType);
-          return context.emitter().Build(
-              loc_id, QualifiedExprInIncompleteClassScope, base_id);
-        });
-    auto& class_info = context.classes().Get(base_as_class->class_id);
-    return LookupScope{.name_scope_id = class_info.scope_id,
-                       .specific_id = base_as_class->specific_id};
-  }
-  if (auto base_as_facet_type = base.TryAs<SemIR::FacetType>()) {
-    context.TryToDefineType(
-        context.GetTypeIdForTypeConstant(base_const_id), [&] {
-          CARBON_DIAGNOSTIC(QualifiedExprInUndefinedInterfaceScope, Error,
-                            "member access into undefined interface {0}",
-                            InstIdAsType);
-          return context.emitter().Build(
-              loc_id, QualifiedExprInUndefinedInterfaceScope, base_id);
-        });
-    const auto& facet_type_info =
-        context.facet_types().Get(base_as_facet_type->facet_type_id);
-    auto base_as_interface = facet_type_info.TryAsSingleInterface();
-    if (base_as_interface) {
-      auto& interface_info =
-          context.interfaces().Get(base_as_interface->interface_id);
-      return LookupScope{.name_scope_id = interface_info.scope_id,
-                         .specific_id = base_as_interface->specific_id};
-    }
-  }
-  // TODO: Per the design, if `base_id` is any kind of type, then lookup should
-  // treat it as a name scope, even if it doesn't have members. For example,
-  // `(i32*).X` should fail because there's no name `X` in `i32*`, not because
-  // there's no name `X` in `type`.
-  return std::nullopt;
-}
-
 // Returns the index of the specified class element within the class's
 // Returns the index of the specified class element within the class's
 // representation.
 // representation.
 static auto GetClassElementIndex(Context& context, SemIR::InstId element_id)
 static auto GetClassElementIndex(Context& context, SemIR::InstId element_id)
@@ -152,18 +100,20 @@ static auto GetHighestAllowedAccess(Context& context, SemIR::LocId loc_id,
 
 
 // Returns whether `scope` is a scope for which impl lookup should be performed
 // Returns whether `scope` is a scope for which impl lookup should be performed
 // if we find an associated entity.
 // if we find an associated entity.
-static auto ScopeNeedsImplLookup(Context& context, LookupScope scope) -> bool {
-  auto [_, inst] = context.name_scopes().GetInstIfValid(scope.name_scope_id);
-  if (!inst) {
-    return false;
-  }
-
-  if (inst->Is<SemIR::InterfaceDecl>()) {
+static auto ScopeNeedsImplLookup(Context& context,
+                                 SemIR::ConstantId name_scope_const_id)
+    -> bool {
+  SemIR::InstId inst_id =
+      context.constant_values().GetInstId(name_scope_const_id);
+  CARBON_CHECK(inst_id.is_valid());
+  SemIR::Inst inst = context.insts().Get(inst_id);
+
+  if (inst.Is<SemIR::FacetType>()) {
     // Don't perform impl lookup if an associated entity is named as a member of
     // Don't perform impl lookup if an associated entity is named as a member of
     // a facet type.
     // a facet type.
     return false;
     return false;
   }
   }
-  if (inst->Is<SemIR::Namespace>()) {
+  if (inst.Is<SemIR::Namespace>()) {
     // Don't perform impl lookup if an associated entity is named as a namespace
     // Don't perform impl lookup if an associated entity is named as a namespace
     // member.
     // member.
     // TODO: This case is not yet listed in the design.
     // TODO: This case is not yet listed in the design.
@@ -255,22 +205,19 @@ static auto LookupMemberNameInScope(Context& context, SemIR::LocId loc_id,
                                     SemIR::InstId /*base_id*/,
                                     SemIR::InstId /*base_id*/,
                                     SemIR::NameId name_id,
                                     SemIR::NameId name_id,
                                     SemIR::ConstantId name_scope_const_id,
                                     SemIR::ConstantId name_scope_const_id,
-                                    LookupScope lookup_scope) -> SemIR::InstId {
-  LookupResult result = {.specific_id = SemIR::SpecificId::Invalid,
-                         .inst_id = SemIR::InstId::BuiltinError};
-  if (lookup_scope.name_scope_id.is_valid()) {
-    AccessInfo access_info = {
-        .constant_id = name_scope_const_id,
-        .highest_allowed_access =
-            GetHighestAllowedAccess(context, loc_id, name_scope_const_id),
-    };
-
-    result = context.LookupQualifiedName(loc_id, name_id, lookup_scope,
-                                         /*required=*/true, access_info);
-
-    if (!result.inst_id.is_valid()) {
-      return SemIR::InstId::BuiltinError;
-    }
+                                    llvm::ArrayRef<LookupScope> lookup_scopes)
+    -> SemIR::InstId {
+  AccessInfo access_info = {
+      .constant_id = name_scope_const_id,
+      .highest_allowed_access =
+          GetHighestAllowedAccess(context, loc_id, name_scope_const_id),
+  };
+  LookupResult result =
+      context.LookupQualifiedName(loc_id, name_id, lookup_scopes,
+                                  /*required=*/true, access_info);
+
+  if (!result.inst_id.is_valid()) {
+    return SemIR::InstId::BuiltinError;
   }
   }
 
 
   // TODO: This duplicates the work that HandleNameAsExpr does. Factor this out.
   // TODO: This duplicates the work that HandleNameAsExpr does. Factor this out.
@@ -303,7 +250,7 @@ static auto LookupMemberNameInScope(Context& context, SemIR::LocId loc_id,
   // impl member is not supposed to be treated as ambiguous.
   // impl member is not supposed to be treated as ambiguous.
   if (auto assoc_type =
   if (auto assoc_type =
           context.types().TryGetAs<SemIR::AssociatedEntityType>(type_id)) {
           context.types().TryGetAs<SemIR::AssociatedEntityType>(type_id)) {
-    if (ScopeNeedsImplLookup(context, lookup_scope)) {
+    if (ScopeNeedsImplLookup(context, name_scope_const_id)) {
       member_id = PerformImplLookup(context, loc_id, name_scope_const_id,
       member_id = PerformImplLookup(context, loc_id, name_scope_const_id,
                                     *assoc_type, member_id);
                                     *assoc_type, member_id);
     }
     }
@@ -391,9 +338,11 @@ auto PerformMemberAccess(Context& context, SemIR::LocId loc_id,
   // into that scope.
   // into that scope.
   if (auto base_const_id = context.constant_values().Get(base_id);
   if (auto base_const_id = context.constant_values().Get(base_id);
       base_const_id.is_constant()) {
       base_const_id.is_constant()) {
-    if (auto lookup_scope = GetAsLookupScope(context, loc_id, base_const_id)) {
+    llvm::SmallVector<LookupScope> lookup_scopes;
+    if (context.AppendLookupScopesForConstant(loc_id, base_const_id,
+                                              &lookup_scopes)) {
       return LookupMemberNameInScope(context, loc_id, base_id, name_id,
       return LookupMemberNameInScope(context, loc_id, base_id, name_id,
-                                     base_const_id, *lookup_scope);
+                                     base_const_id, lookup_scopes);
     }
     }
   }
   }
 
 
@@ -415,8 +364,9 @@ auto PerformMemberAccess(Context& context, SemIR::LocId loc_id,
   auto base_type_const_id = context.types().GetConstantId(base_type_id);
   auto base_type_const_id = context.types().GetConstantId(base_type_id);
 
 
   // Find the scope corresponding to the base type.
   // Find the scope corresponding to the base type.
-  auto lookup_scope = GetAsLookupScope(context, loc_id, base_type_const_id);
-  if (!lookup_scope) {
+  llvm::SmallVector<LookupScope> lookup_scopes;
+  if (!context.AppendLookupScopesForConstant(loc_id, base_type_const_id,
+                                             &lookup_scopes)) {
     // The base type is not a name scope. Try some fallback options.
     // The base type is not a name scope. Try some fallback options.
     if (auto struct_type = context.insts().TryGetAs<SemIR::StructType>(
     if (auto struct_type = context.insts().TryGetAs<SemIR::StructType>(
             context.constant_values().GetInstId(base_type_const_id))) {
             context.constant_values().GetInstId(base_type_const_id))) {
@@ -451,7 +401,7 @@ auto PerformMemberAccess(Context& context, SemIR::LocId loc_id,
 
 
   // Perform lookup into the base type.
   // Perform lookup into the base type.
   auto member_id = LookupMemberNameInScope(context, loc_id, base_id, name_id,
   auto member_id = LookupMemberNameInScope(context, loc_id, base_id, name_id,
-                                           base_type_const_id, *lookup_scope);
+                                           base_type_const_id, lookup_scopes);
 
 
   // Perform instance binding if we found an instance member.
   // Perform instance binding if we found an instance member.
   member_id = PerformInstanceBinding(context, loc_id, base_id, member_id);
   member_id = PerformInstanceBinding(context, loc_id, base_id, member_id);

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

@@ -134,7 +134,7 @@ fn Access(d: Derived) -> (i32, i32) {
 // CHECK:STDOUT:   .Self = constants.%Derived
 // CHECK:STDOUT:   .Self = constants.%Derived
 // CHECK:STDOUT:   .base = %.loc16
 // CHECK:STDOUT:   .base = %.loc16
 // CHECK:STDOUT:   .d = %.loc18_8
 // CHECK:STDOUT:   .d = %.loc18_8
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %Base.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";

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

@@ -128,7 +128,7 @@ fn Access(p: Derived*) -> i32* {
 // CHECK:STDOUT:   .base = %.loc18
 // CHECK:STDOUT:   .base = %.loc18
 // CHECK:STDOUT:   .d = %.loc20_8
 // CHECK:STDOUT:   .d = %.loc20_8
 // CHECK:STDOUT:   .e = %.loc21_8
 // CHECK:STDOUT:   .e = %.loc21_8
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %Base.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";

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

@@ -83,7 +83,7 @@ fn Derived.H() {
 // CHECK:STDOUT:   .base = %.loc16
 // CHECK:STDOUT:   .base = %.loc16
 // CHECK:STDOUT:   .G = %G.decl
 // CHECK:STDOUT:   .G = %G.decl
 // CHECK:STDOUT:   .H = %H.decl
 // CHECK:STDOUT:   .H = %H.decl
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %Base.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F();
 // CHECK:STDOUT: fn @F();

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

@@ -123,7 +123,7 @@ fn Call(p: Derived*) {
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%Derived
 // CHECK:STDOUT:   .Self = constants.%Derived
 // CHECK:STDOUT:   .base = %.loc22
 // CHECK:STDOUT:   .base = %.loc22
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %Base.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";

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

@@ -186,7 +186,7 @@ fn PassDerivedToBaseIndirect(p: Derived*) -> i32 {
 // CHECK:STDOUT:   .base = %.loc19
 // CHECK:STDOUT:   .base = %.loc19
 // CHECK:STDOUT:   .F = %F.decl
 // CHECK:STDOUT:   .F = %F.decl
 // CHECK:STDOUT:   .G = %G.decl
 // CHECK:STDOUT:   .G = %G.decl
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %Base.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @Base {
 // CHECK:STDOUT: class @Base {

+ 3 - 3
toolchain/check/testdata/class/base_method_shadow.carbon

@@ -156,7 +156,7 @@ fn Call(a: A*, b: B*, c: C*, d: D*) {
 // CHECK:STDOUT:   .Self = constants.%B
 // CHECK:STDOUT:   .Self = constants.%B
 // CHECK:STDOUT:   .base = %.loc16
 // CHECK:STDOUT:   .base = %.loc16
 // CHECK:STDOUT:   .F = %F.decl
 // CHECK:STDOUT:   .F = %F.decl
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %A.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @C {
 // CHECK:STDOUT: class @C {
@@ -178,7 +178,7 @@ fn Call(a: A*, b: B*, c: C*, d: D*) {
 // CHECK:STDOUT:   .Self = constants.%C
 // CHECK:STDOUT:   .Self = constants.%C
 // CHECK:STDOUT:   .base = %.loc21
 // CHECK:STDOUT:   .base = %.loc21
 // CHECK:STDOUT:   .F = %F.decl
 // CHECK:STDOUT:   .F = %F.decl
-// CHECK:STDOUT:   extend name_scope3
+// CHECK:STDOUT:   extend %B.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @D {
 // CHECK:STDOUT: class @D {
@@ -189,7 +189,7 @@ fn Call(a: A*, b: B*, c: C*, d: D*) {
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%D
 // CHECK:STDOUT:   .Self = constants.%D
 // CHECK:STDOUT:   .base = %.loc26
 // CHECK:STDOUT:   .base = %.loc26
-// CHECK:STDOUT:   extend name_scope3
+// CHECK:STDOUT:   extend %B.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.1[addr %self.param_patt: %.1]();
 // CHECK:STDOUT: fn @F.1[addr %self.param_patt: %.1]();

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

@@ -196,7 +196,7 @@ fn AccessBaseIndirect(p: Derived*) -> i32* {
 // CHECK:STDOUT:   .base = %.loc18
 // CHECK:STDOUT:   .base = %.loc18
 // CHECK:STDOUT:   .d = %.loc20_8
 // CHECK:STDOUT:   .d = %.loc20_8
 // CHECK:STDOUT:   .e = %.loc21_8
 // CHECK:STDOUT:   .e = %.loc21_8
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %Base.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";

+ 2 - 2
toolchain/check/testdata/class/derived_to_base.carbon

@@ -212,7 +212,7 @@ fn ConvertInit() {
 // CHECK:STDOUT:   .Self = constants.%B
 // CHECK:STDOUT:   .Self = constants.%B
 // CHECK:STDOUT:   .base = %.loc16
 // CHECK:STDOUT:   .base = %.loc16
 // CHECK:STDOUT:   .b = %.loc17_8
 // CHECK:STDOUT:   .b = %.loc17_8
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %A.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @C {
 // CHECK:STDOUT: class @C {
@@ -228,7 +228,7 @@ fn ConvertInit() {
 // CHECK:STDOUT:   .Self = constants.%C
 // CHECK:STDOUT:   .Self = constants.%C
 // CHECK:STDOUT:   .base = %.loc21
 // CHECK:STDOUT:   .base = %.loc21
 // CHECK:STDOUT:   .c = %.loc22_8
 // CHECK:STDOUT:   .c = %.loc22_8
-// CHECK:STDOUT:   extend name_scope3
+// CHECK:STDOUT:   extend %B.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";

+ 3 - 3
toolchain/check/testdata/class/extend_adapt.carbon

@@ -165,7 +165,7 @@ class StructAdapter {
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%SomeClassAdapter
 // CHECK:STDOUT:   .Self = constants.%SomeClassAdapter
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %SomeClass.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @SomeClass {
 // CHECK:STDOUT: class @SomeClass {
@@ -329,7 +329,7 @@ class StructAdapter {
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%SomeClassAdapter
 // CHECK:STDOUT:   .Self = constants.%SomeClassAdapter
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %SomeClass.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.1[%self.param_patt: %SomeClass]();
 // CHECK:STDOUT: fn @F.1[%self.param_patt: %SomeClass]();
@@ -507,7 +507,7 @@ class StructAdapter {
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%SomeClassAdapter
 // CHECK:STDOUT:   .Self = constants.%SomeClassAdapter
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %SomeClass.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";

+ 3 - 3
toolchain/check/testdata/class/fail_abstract.carbon

@@ -516,7 +516,7 @@ fn CallReturnAbstract() {
 // CHECK:STDOUT:   .Self = constants.%Derived
 // CHECK:STDOUT:   .Self = constants.%Derived
 // CHECK:STDOUT:   .base = %.loc8
 // CHECK:STDOUT:   .base = %.loc8
 // CHECK:STDOUT:   .d = %.loc10_8
 // CHECK:STDOUT:   .d = %.loc10_8
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %Abstract.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
@@ -603,7 +603,7 @@ fn CallReturnAbstract() {
 // CHECK:STDOUT:   .Self = constants.%Derived
 // CHECK:STDOUT:   .Self = constants.%Derived
 // CHECK:STDOUT:   .base = %.loc8
 // CHECK:STDOUT:   .base = %.loc8
 // CHECK:STDOUT:   .d = %.loc10_8
 // CHECK:STDOUT:   .d = %.loc10_8
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %Abstract.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
@@ -698,7 +698,7 @@ fn CallReturnAbstract() {
 // CHECK:STDOUT:   .Self = constants.%Derived
 // CHECK:STDOUT:   .Self = constants.%Derived
 // CHECK:STDOUT:   .base = %.loc9
 // CHECK:STDOUT:   .base = %.loc9
 // CHECK:STDOUT:   .d = %.loc11_8
 // CHECK:STDOUT:   .d = %.loc11_8
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %Abstract.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";

+ 2 - 2
toolchain/check/testdata/class/fail_adapt_modifiers.carbon

@@ -138,7 +138,7 @@ class C5 {
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%C4
 // CHECK:STDOUT:   .Self = constants.%C4
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %B.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @C5 {
 // CHECK:STDOUT: class @C5 {
@@ -148,6 +148,6 @@ class C5 {
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%C5
 // CHECK:STDOUT:   .Self = constants.%C5
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %B.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:

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

@@ -126,7 +126,7 @@ class AdaptWithBaseAndFields {
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%AdaptWithBase
 // CHECK:STDOUT:   .Self = constants.%AdaptWithBase
 // CHECK:STDOUT:   .base = %.loc15
 // CHECK:STDOUT:   .base = %.loc15
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %Base.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
@@ -262,7 +262,7 @@ class AdaptWithBaseAndFields {
 // CHECK:STDOUT:   .Self = constants.%AdaptWithBaseAndFields
 // CHECK:STDOUT:   .Self = constants.%AdaptWithBaseAndFields
 // CHECK:STDOUT:   .base = %.loc7
 // CHECK:STDOUT:   .base = %.loc7
 // CHECK:STDOUT:   .n = %.loc8_8
 // CHECK:STDOUT:   .n = %.loc8_8
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %Base.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";

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

@@ -1281,7 +1281,7 @@ fn AccessMemberWithInvalidBaseFinal_NoMember(p: DeriveFromFinal*) -> i32 {
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%DeriveFromFinal
 // CHECK:STDOUT:   .Self = constants.%DeriveFromFinal
 // CHECK:STDOUT:   .base = %.loc11
 // CHECK:STDOUT:   .base = %.loc11
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %Final.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";

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

@@ -103,7 +103,7 @@ fn D.C.F() {}
 // CHECK:STDOUT:   .Self = constants.%D
 // CHECK:STDOUT:   .Self = constants.%D
 // CHECK:STDOUT:   .base = %.loc20
 // CHECK:STDOUT:   .base = %.loc20
 // CHECK:STDOUT:   .F = file.%F.decl
 // CHECK:STDOUT:   .F = file.%F.decl
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %B.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.1();
 // CHECK:STDOUT: fn @F.1();

+ 3 - 3
toolchain/check/testdata/class/fail_base_modifiers.carbon

@@ -110,7 +110,7 @@ class C4 {
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%C1
 // CHECK:STDOUT:   .Self = constants.%C1
 // CHECK:STDOUT:   .base = %.loc18
 // CHECK:STDOUT:   .base = %.loc18
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %B.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @C2 {
 // CHECK:STDOUT: class @C2 {
@@ -131,7 +131,7 @@ class C4 {
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%C3
 // CHECK:STDOUT:   .Self = constants.%C3
 // CHECK:STDOUT:   .base = %.loc41
 // CHECK:STDOUT:   .base = %.loc41
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %B.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @C4 {
 // CHECK:STDOUT: class @C4 {
@@ -142,6 +142,6 @@ class C4 {
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%C4
 // CHECK:STDOUT:   .Self = constants.%C4
 // CHECK:STDOUT:   .base = %.loc51
 // CHECK:STDOUT:   .base = %.loc51
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %B.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:

+ 2 - 2
toolchain/check/testdata/class/fail_base_repeated.carbon

@@ -97,7 +97,7 @@ class D {
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%C
 // CHECK:STDOUT:   .Self = constants.%C
 // CHECK:STDOUT:   .base = %.loc15
 // CHECK:STDOUT:   .base = %.loc15
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %B1.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @D {
 // CHECK:STDOUT: class @D {
@@ -109,6 +109,6 @@ class D {
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%D
 // CHECK:STDOUT:   .Self = constants.%D
 // CHECK:STDOUT:   .base = %.loc28
 // CHECK:STDOUT:   .base = %.loc28
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %B1.ref.loc28
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:

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

@@ -70,7 +70,7 @@ let b: B = C.base;
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%C
 // CHECK:STDOUT:   .Self = constants.%C
 // CHECK:STDOUT:   .base = %.loc14
 // CHECK:STDOUT:   .base = %.loc14
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %B.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @__global_init() {
 // CHECK:STDOUT: fn @__global_init() {

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

@@ -214,7 +214,7 @@ fn ConvertIncomplete(p: Incomplete*) -> A2* { return p; }
 // CHECK:STDOUT:   .Self = constants.%B2
 // CHECK:STDOUT:   .Self = constants.%B2
 // CHECK:STDOUT:   .base = %.loc20
 // CHECK:STDOUT:   .base = %.loc20
 // CHECK:STDOUT:   .b = %.loc21_8
 // CHECK:STDOUT:   .b = %.loc21_8
-// CHECK:STDOUT:   extend name_scope3
+// CHECK:STDOUT:   extend %A2.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @Incomplete;
 // CHECK:STDOUT: class @Incomplete;

+ 2 - 2
toolchain/check/testdata/class/fail_extend_cycle.carbon

@@ -81,7 +81,7 @@ base class A {
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%B
 // CHECK:STDOUT:   .Self = constants.%B
 // CHECK:STDOUT:   .base = %.loc16
 // CHECK:STDOUT:   .base = %.loc16
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %A.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @.1 {
 // CHECK:STDOUT: class @.1 {
@@ -95,6 +95,6 @@ base class A {
 // CHECK:STDOUT:   .Self = constants.%.7
 // CHECK:STDOUT:   .Self = constants.%.7
 // CHECK:STDOUT:   .base = %.loc27
 // CHECK:STDOUT:   .base = %.loc27
 // CHECK:STDOUT:   .c = %.loc31
 // CHECK:STDOUT:   .c = %.loc31
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %A.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:

+ 1056 - 0
toolchain/check/testdata/class/generic/base_is_generic.carbon

@@ -0,0 +1,1056 @@
+// 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
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/class/generic/base_is_generic.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/class/generic/base_is_generic.carbon
+
+// --- extend_generic_base.carbon
+
+library "[[@TEST_NAME]]";
+
+base class Base(T:! type) {
+  var x: T;
+}
+
+class Param {
+  var y: i32;
+}
+
+class Derived {
+  extend base: Base(Param);
+}
+
+fn DoubleFieldAccess(d: Derived) -> i32 {
+  return d.x.y;
+}
+
+// --- import.carbon
+
+library "[[@TEST_NAME]]";
+
+import library "extend_generic_base";
+
+fn ImportedDoubleFieldAccess(d: Derived) -> i32 {
+  return d.x.y;
+}
+
+// --- fail_todo_extend_symbolic_base.carbon
+
+library "[[@TEST_NAME]]";
+
+class C(T:! type) {
+  // CHECK:STDERR: fail_todo_extend_symbolic_base.carbon:[[@LINE+4]]:16: error: deriving from final type `T`; base type must be an `abstract` or `base` class [BaseIsFinal]
+  // CHECK:STDERR:   extend base: T;
+  // CHECK:STDERR:                ^
+  // CHECK:STDERR:
+  extend base: T;
+}
+
+base class X {
+  fn G() {}
+}
+
+fn F() {
+  C(X).G();
+}
+
+// --- extend_generic_symbolic_base.carbon
+
+library "[[@TEST_NAME]]";
+
+base class X(U:! type) {
+  fn G() -> U { return G(); }
+}
+
+class C(T:! type) {
+  extend base: X(T);
+}
+
+fn F() {
+  let i: i32 = C(i32).G();
+}
+
+
+// --- fail_todo_import_extend_generic_symbolic_base.carbon
+
+library "[[@TEST_NAME]]";
+
+import library "extend_generic_symbolic_base";
+
+fn H() {
+  // CHECK:STDERR: fail_todo_import_extend_generic_symbolic_base.carbon:[[@LINE+6]]:3: error: cannot implicitly convert from `T` to `i32` [ImplicitAsConversionFailure]
+  // CHECK:STDERR:   let j: i32 = C(i32).G();
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR: fail_todo_import_extend_generic_symbolic_base.carbon:[[@LINE+3]]:3: note: type `T` does not implement interface `ImplicitAs(i32)` [MissingImplInMemberAccessNote]
+  // CHECK:STDERR:   let j: i32 = C(i32).G();
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~~
+  let j: i32 = C(i32).G();
+}
+
+// CHECK:STDOUT: --- extend_generic_base.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic]
+// CHECK:STDOUT:   %T.patt: type = symbolic_binding_pattern T, 0 [symbolic]
+// CHECK:STDOUT:   %Base.type: type = generic_class_type @Base [template]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [template]
+// CHECK:STDOUT:   %Base.1: %Base.type = struct_value () [template]
+// CHECK:STDOUT:   %Base.2: type = class_type @Base, @Base(%T) [symbolic]
+// CHECK:STDOUT:   %.1: type = unbound_element_type %Base.2, %T [symbolic]
+// CHECK:STDOUT:   %.2: type = struct_type {.x: %T} [symbolic]
+// CHECK:STDOUT:   %.3: <witness> = complete_type_witness %.2 [symbolic]
+// CHECK:STDOUT:   %Param: type = class_type @Param [template]
+// CHECK:STDOUT:   %Int32.type: type = fn_type @Int32 [template]
+// CHECK:STDOUT:   %Int32: %Int32.type = struct_value () [template]
+// CHECK:STDOUT:   %.4: type = unbound_element_type %Param, i32 [template]
+// CHECK:STDOUT:   %.5: type = struct_type {.y: i32} [template]
+// CHECK:STDOUT:   %.6: <witness> = complete_type_witness %.5 [template]
+// CHECK:STDOUT:   %Derived: type = class_type @Derived [template]
+// CHECK:STDOUT:   %Base.3: type = class_type @Base, @Base(%Param) [template]
+// CHECK:STDOUT:   %.7: type = unbound_element_type %Base.3, %Param [template]
+// CHECK:STDOUT:   %.8: type = struct_type {.x: %Param} [template]
+// CHECK:STDOUT:   %.9: <witness> = complete_type_witness %.8 [template]
+// CHECK:STDOUT:   %.10: type = ptr_type %.5 [template]
+// CHECK:STDOUT:   %.11: type = struct_type {.x: %.10} [template]
+// CHECK:STDOUT:   %.12: type = ptr_type %.8 [template]
+// CHECK:STDOUT:   %.13: type = unbound_element_type %Derived, %Base.3 [template]
+// CHECK:STDOUT:   %.14: type = struct_type {.base: %Base.3} [template]
+// CHECK:STDOUT:   %.15: <witness> = complete_type_witness %.14 [template]
+// CHECK:STDOUT:   %DoubleFieldAccess.type: type = fn_type @DoubleFieldAccess [template]
+// CHECK:STDOUT:   %DoubleFieldAccess: %DoubleFieldAccess.type = struct_value () [template]
+// CHECK:STDOUT:   %.16: type = struct_type {.base: %.12} [template]
+// CHECK:STDOUT:   %.17: type = ptr_type %.14 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [template] {
+// CHECK:STDOUT:     .Int32 = %import_ref
+// CHECK:STDOUT:     import Core//prelude
+// CHECK:STDOUT:     import Core//prelude/...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref: %Int32.type = import_ref Core//prelude/types, inst+15, loaded [template = constants.%Int32]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = imports.%Core
+// CHECK:STDOUT:     .Base = %Base.decl
+// CHECK:STDOUT:     .Param = %Param.decl
+// CHECK:STDOUT:     .Derived = %Derived.decl
+// CHECK:STDOUT:     .DoubleFieldAccess = %DoubleFieldAccess.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %Base.decl: %Base.type = class_decl @Base [template = constants.%Base.1] {
+// CHECK:STDOUT:     %T.patt.loc4_17.1: type = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc4_17.2 (constants.%T.patt)]
+// CHECK:STDOUT:     %T.param_patt: type = value_param_pattern %T.patt.loc4_17.1, runtime_param<invalid> [symbolic = %T.patt.loc4_17.2 (constants.%T.patt)]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %T.param: type = value_param runtime_param<invalid>
+// CHECK:STDOUT:     %T.loc4_17.1: type = bind_symbolic_name T, 0, %T.param [symbolic = %T.loc4_17.2 (constants.%T)]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Param.decl: type = class_decl @Param [template = constants.%Param] {} {}
+// CHECK:STDOUT:   %Derived.decl: type = class_decl @Derived [template = constants.%Derived] {} {}
+// CHECK:STDOUT:   %DoubleFieldAccess.decl: %DoubleFieldAccess.type = fn_decl @DoubleFieldAccess [template = constants.%DoubleFieldAccess] {
+// CHECK:STDOUT:     %d.patt: %Derived = binding_pattern d
+// CHECK:STDOUT:     %d.param_patt: %Derived = value_param_pattern %d.patt, runtime_param0
+// CHECK:STDOUT:     %return.patt: i32 = return_slot_pattern
+// CHECK:STDOUT:     %return.param_patt: i32 = out_param_pattern %return.patt, runtime_param1
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %Derived.ref: type = name_ref Derived, file.%Derived.decl [template = constants.%Derived]
+// CHECK:STDOUT:     %int.make_type_32: init type = call constants.%Int32() [template = i32]
+// CHECK:STDOUT:     %.loc16_37.1: type = value_of_initializer %int.make_type_32 [template = i32]
+// CHECK:STDOUT:     %.loc16_37.2: type = converted %int.make_type_32, %.loc16_37.1 [template = i32]
+// CHECK:STDOUT:     %d.param: %Derived = value_param runtime_param0
+// CHECK:STDOUT:     %d: %Derived = bind_name d, %d.param
+// CHECK:STDOUT:     %return.param: ref i32 = out_param runtime_param1
+// CHECK:STDOUT:     %return: ref i32 = return_slot %return.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic class @Base(%T.loc4_17.1: type) {
+// CHECK:STDOUT:   %T.loc4_17.2: type = bind_symbolic_name T, 0 [symbolic = %T.loc4_17.2 (constants.%T)]
+// CHECK:STDOUT:   %T.patt.loc4_17.2: type = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc4_17.2 (constants.%T.patt)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %Base: type = class_type @Base, @Base(%T.loc4_17.2) [symbolic = %Base (constants.%Base.2)]
+// CHECK:STDOUT:   %.loc5_8.2: type = unbound_element_type @Base.%Base (%Base.2), @Base.%T.loc4_17.2 (%T) [symbolic = %.loc5_8.2 (constants.%.1)]
+// CHECK:STDOUT:   %.loc6_1.2: type = struct_type {.x: @Base.%T.loc4_17.2 (%T)} [symbolic = %.loc6_1.2 (constants.%.2)]
+// CHECK:STDOUT:   %.loc6_1.3: <witness> = complete_type_witness @Base.%.loc6_1.2 (%.2) [symbolic = %.loc6_1.3 (constants.%.3)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class {
+// CHECK:STDOUT:     %T.ref: type = name_ref T, %T.loc4_17.1 [symbolic = %T.loc4_17.2 (constants.%T)]
+// CHECK:STDOUT:     %.loc5_8.1: @Base.%.loc5_8.2 (%.1) = field_decl x, element0 [template]
+// CHECK:STDOUT:     %.loc6_1.1: <witness> = complete_type_witness %.2 [symbolic = %.loc6_1.3 (constants.%.3)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = constants.%Base.2
+// CHECK:STDOUT:     .x = %.loc5_8.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Param {
+// CHECK:STDOUT:   %int.make_type_32: init type = call constants.%Int32() [template = i32]
+// CHECK:STDOUT:   %.loc9_10.1: type = value_of_initializer %int.make_type_32 [template = i32]
+// CHECK:STDOUT:   %.loc9_10.2: type = converted %int.make_type_32, %.loc9_10.1 [template = i32]
+// CHECK:STDOUT:   %.loc9_8: %.4 = field_decl y, element0 [template]
+// CHECK:STDOUT:   %.loc10: <witness> = complete_type_witness %.5 [template = constants.%.6]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Param
+// CHECK:STDOUT:   .y = %.loc9_8
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Derived {
+// CHECK:STDOUT:   %Base.ref: %Base.type = name_ref Base, file.%Base.decl [template = constants.%Base.1]
+// CHECK:STDOUT:   %Param.ref: type = name_ref Param, file.%Param.decl [template = constants.%Param]
+// CHECK:STDOUT:   %Base: type = class_type @Base, @Base(constants.%Param) [template = constants.%Base.3]
+// CHECK:STDOUT:   %.loc13: %.13 = base_decl %Base.3, element0 [template]
+// CHECK:STDOUT:   %.loc14: <witness> = complete_type_witness %.14 [template = constants.%.15]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Derived
+// CHECK:STDOUT:   .base = %.loc13
+// CHECK:STDOUT:   extend %Base
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @DoubleFieldAccess(%d.param_patt: %Derived) -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %d.ref: %Derived = name_ref d, %d
+// CHECK:STDOUT:   %x.ref: %.7 = name_ref x, @Base.%.loc5_8.1 [template = @Base.%.loc5_8.1]
+// CHECK:STDOUT:   %.loc17_11.1: ref %Base.3 = class_element_access %d.ref, element0
+// CHECK:STDOUT:   %.loc17_11.2: ref %Base.3 = converted %d.ref, %.loc17_11.1
+// CHECK:STDOUT:   %.loc17_11.3: ref %Param = class_element_access %.loc17_11.2, element0
+// CHECK:STDOUT:   %y.ref: %.4 = name_ref y, @Param.%.loc9_8 [template = @Param.%.loc9_8]
+// CHECK:STDOUT:   %.loc17_13.1: ref i32 = class_element_access %.loc17_11.3, element0
+// CHECK:STDOUT:   %.loc17_13.2: i32 = bind_value %.loc17_13.1
+// CHECK:STDOUT:   return %.loc17_13.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Base(constants.%T) {
+// CHECK:STDOUT:   %T.loc4_17.2 => constants.%T
+// CHECK:STDOUT:   %T.patt.loc4_17.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Base(@Base.%T.loc4_17.2) {
+// CHECK:STDOUT:   %T.loc4_17.2 => constants.%T
+// CHECK:STDOUT:   %T.patt.loc4_17.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Base(constants.%Param) {
+// CHECK:STDOUT:   %T.loc4_17.2 => constants.%Param
+// CHECK:STDOUT:   %T.patt.loc4_17.2 => constants.%Param
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %Base => constants.%Base.3
+// CHECK:STDOUT:   %.loc5_8.2 => constants.%.7
+// CHECK:STDOUT:   %.loc6_1.2 => constants.%.8
+// CHECK:STDOUT:   %.loc6_1.3 => constants.%.9
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- import.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Derived: type = class_type @Derived [template]
+// CHECK:STDOUT:   %Param: type = class_type @Param [template]
+// CHECK:STDOUT:   %.1: type = struct_type {.y: i32} [template]
+// CHECK:STDOUT:   %.2: <witness> = complete_type_witness %.1 [template]
+// CHECK:STDOUT:   %Base.type: type = generic_class_type @Base [template]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [template]
+// CHECK:STDOUT:   %Base.1: %Base.type = struct_value () [template]
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic]
+// CHECK:STDOUT:   %.3: type = struct_type {.x: %T} [symbolic]
+// CHECK:STDOUT:   %.4: <witness> = complete_type_witness %.3 [symbolic]
+// CHECK:STDOUT:   %Base.2: type = class_type @Base, @Base(%T) [symbolic]
+// CHECK:STDOUT:   %T.patt: type = symbolic_binding_pattern T, 0 [symbolic]
+// CHECK:STDOUT:   %Base.3: type = class_type @Base, @Base(%Param) [template]
+// CHECK:STDOUT:   %.5: type = unbound_element_type %Derived, %Base.3 [template]
+// CHECK:STDOUT:   %.6: type = struct_type {.base: %Base.3} [template]
+// CHECK:STDOUT:   %.7: <witness> = complete_type_witness %.6 [template]
+// CHECK:STDOUT:   %.8: type = unbound_element_type %Base.2, %T [symbolic]
+// CHECK:STDOUT:   %.9: type = unbound_element_type %Base.3, %Param [template]
+// CHECK:STDOUT:   %.10: type = struct_type {.x: %Param} [template]
+// CHECK:STDOUT:   %.11: <witness> = complete_type_witness %.10 [template]
+// CHECK:STDOUT:   %Int32.type: type = fn_type @Int32 [template]
+// CHECK:STDOUT:   %Int32: %Int32.type = struct_value () [template]
+// CHECK:STDOUT:   %ImportedDoubleFieldAccess.type: type = fn_type @ImportedDoubleFieldAccess [template]
+// CHECK:STDOUT:   %ImportedDoubleFieldAccess: %ImportedDoubleFieldAccess.type = struct_value () [template]
+// CHECK:STDOUT:   %.12: type = ptr_type %.3 [symbolic]
+// CHECK:STDOUT:   %.13: type = struct_type {.base: %.12} [symbolic]
+// CHECK:STDOUT:   %.14: type = ptr_type %.6 [template]
+// CHECK:STDOUT:   %.15: type = ptr_type %.1 [template]
+// CHECK:STDOUT:   %.16: type = unbound_element_type %Param, i32 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %import_ref.1 = import_ref Main//extend_generic_base, inst+9, unloaded
+// CHECK:STDOUT:   %import_ref.2 = import_ref Main//extend_generic_base, inst+26, unloaded
+// CHECK:STDOUT:   %import_ref.3: type = import_ref Main//extend_generic_base, inst+43, loaded [template = constants.%Derived]
+// CHECK:STDOUT:   %import_ref.4 = import_ref Main//extend_generic_base, inst+72, unloaded
+// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [template] {
+// CHECK:STDOUT:     .Int32 = %import_ref.11
+// CHECK:STDOUT:     import Core//prelude
+// CHECK:STDOUT:     import Core//prelude/...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref.5 = import_ref Main//extend_generic_base, inst+27, unloaded
+// CHECK:STDOUT:   %import_ref.6: %.16 = import_ref Main//extend_generic_base, inst+39, loaded [template = %.1]
+// CHECK:STDOUT:   %import_ref.7 = import_ref Main//extend_generic_base, inst+15, unloaded
+// CHECK:STDOUT:   %import_ref.8: @Base.%.1 (%.8) = import_ref Main//extend_generic_base, inst+18, loaded [template = %.2]
+// CHECK:STDOUT:   %import_ref.9 = import_ref Main//extend_generic_base, inst+44, unloaded
+// CHECK:STDOUT:   %import_ref.10 = import_ref Main//extend_generic_base, inst+56, unloaded
+// CHECK:STDOUT:   %import_ref.11: %Int32.type = import_ref Core//prelude/types, inst+15, loaded [template = constants.%Int32]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Base = imports.%import_ref.1
+// CHECK:STDOUT:     .Param = imports.%import_ref.2
+// CHECK:STDOUT:     .Derived = imports.%import_ref.3
+// CHECK:STDOUT:     .DoubleFieldAccess = imports.%import_ref.4
+// CHECK:STDOUT:     .Core = imports.%Core
+// CHECK:STDOUT:     .ImportedDoubleFieldAccess = %ImportedDoubleFieldAccess.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %default.import = import <invalid>
+// CHECK:STDOUT:   %ImportedDoubleFieldAccess.decl: %ImportedDoubleFieldAccess.type = fn_decl @ImportedDoubleFieldAccess [template = constants.%ImportedDoubleFieldAccess] {
+// CHECK:STDOUT:     %d.patt: %Derived = binding_pattern d
+// CHECK:STDOUT:     %d.param_patt: %Derived = value_param_pattern %d.patt, runtime_param0
+// CHECK:STDOUT:     %return.patt: i32 = return_slot_pattern
+// CHECK:STDOUT:     %return.param_patt: i32 = out_param_pattern %return.patt, runtime_param1
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %Derived.ref: type = name_ref Derived, imports.%import_ref.3 [template = constants.%Derived]
+// CHECK:STDOUT:     %int.make_type_32: init type = call constants.%Int32() [template = i32]
+// CHECK:STDOUT:     %.loc6_45.1: type = value_of_initializer %int.make_type_32 [template = i32]
+// CHECK:STDOUT:     %.loc6_45.2: type = converted %int.make_type_32, %.loc6_45.1 [template = i32]
+// CHECK:STDOUT:     %d.param: %Derived = value_param runtime_param0
+// CHECK:STDOUT:     %d: %Derived = bind_name d, %d.param
+// CHECK:STDOUT:     %return.param: ref i32 = out_param runtime_param1
+// CHECK:STDOUT:     %return: ref i32 = return_slot %return.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Derived {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = imports.%import_ref.9
+// CHECK:STDOUT:   .base = imports.%import_ref.10
+// CHECK:STDOUT:   extend constants.%Base.3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Param {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = imports.%import_ref.5
+// CHECK:STDOUT:   .y = imports.%import_ref.6
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic class @Base(constants.%T: type) {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic = %T (constants.%T)]
+// CHECK:STDOUT:   %T.patt: type = symbolic_binding_pattern T, 0 [symbolic = %T.patt (constants.%T.patt)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %Base: type = class_type @Base, @Base(%T) [symbolic = %Base (constants.%Base.2)]
+// CHECK:STDOUT:   %.1: type = unbound_element_type @Base.%Base (%Base.2), @Base.%T (%T) [symbolic = %.1 (constants.%.8)]
+// CHECK:STDOUT:   %.2: type = struct_type {.x: @Base.%T (%T)} [symbolic = %.2 (constants.%.3)]
+// CHECK:STDOUT:   %.3: <witness> = complete_type_witness @Base.%.2 (%.3) [symbolic = %.3 (constants.%.4)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class {
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = imports.%import_ref.7
+// CHECK:STDOUT:     .x = imports.%import_ref.8
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @ImportedDoubleFieldAccess(%d.param_patt: %Derived) -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %d.ref: %Derived = name_ref d, %d
+// CHECK:STDOUT:   %x.ref: %.9 = name_ref x, imports.%import_ref.8 [template = imports.%.2]
+// CHECK:STDOUT:   %.loc7_11.1: ref %Base.3 = class_element_access %d.ref, element0
+// CHECK:STDOUT:   %.loc7_11.2: ref %Base.3 = converted %d.ref, %.loc7_11.1
+// CHECK:STDOUT:   %.loc7_11.3: ref %Param = class_element_access %.loc7_11.2, element0
+// CHECK:STDOUT:   %y.ref: %.16 = name_ref y, imports.%import_ref.6 [template = imports.%.1]
+// CHECK:STDOUT:   %.loc7_13.1: ref i32 = class_element_access %.loc7_11.3, element0
+// CHECK:STDOUT:   %.loc7_13.2: i32 = bind_value %.loc7_13.1
+// CHECK:STDOUT:   return %.loc7_13.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Base(constants.%T) {
+// CHECK:STDOUT:   %T => constants.%T
+// CHECK:STDOUT:   %T.patt => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Base(constants.%Param) {
+// CHECK:STDOUT:   %T => constants.%Param
+// CHECK:STDOUT:   %T.patt => constants.%Param
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %Base => constants.%Base.3
+// CHECK:STDOUT:   %.1 => constants.%.9
+// CHECK:STDOUT:   %.2 => constants.%.10
+// CHECK:STDOUT:   %.3 => constants.%.11
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Base(@Base.%T) {
+// CHECK:STDOUT:   %T => constants.%T
+// CHECK:STDOUT:   %T.patt => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_extend_symbolic_base.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic]
+// CHECK:STDOUT:   %T.patt: type = symbolic_binding_pattern T, 0 [symbolic]
+// CHECK:STDOUT:   %C.type: type = generic_class_type @C [template]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [template]
+// CHECK:STDOUT:   %C.1: %C.type = struct_value () [template]
+// CHECK:STDOUT:   %C.2: type = class_type @C, @C(%T) [symbolic]
+// CHECK:STDOUT:   %X: type = class_type @X [template]
+// CHECK:STDOUT:   %G.type: type = fn_type @G [template]
+// CHECK:STDOUT:   %G: %G.type = struct_value () [template]
+// CHECK:STDOUT:   %.1: type = struct_type {} [template]
+// CHECK:STDOUT:   %.2: <witness> = complete_type_witness %.1 [template]
+// CHECK:STDOUT:   %F.type: type = fn_type @F [template]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [template]
+// CHECK:STDOUT:   %C.3: type = class_type @C, @C(%X) [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [template] {
+// CHECK:STDOUT:     import Core//prelude
+// CHECK:STDOUT:     import Core//prelude/...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = imports.%Core
+// CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:     .X = %X.decl
+// CHECK:STDOUT:     .F = %F.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %C.decl: %C.type = class_decl @C [template = constants.%C.1] {
+// CHECK:STDOUT:     %T.patt.loc4_9.1: type = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc4_9.2 (constants.%T.patt)]
+// CHECK:STDOUT:     %T.param_patt: type = value_param_pattern %T.patt.loc4_9.1, runtime_param<invalid> [symbolic = %T.patt.loc4_9.2 (constants.%T.patt)]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %T.param: type = value_param runtime_param<invalid>
+// CHECK:STDOUT:     %T.loc4_9.1: type = bind_symbolic_name T, 0, %T.param [symbolic = %T.loc4_9.2 (constants.%T)]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %X.decl: type = class_decl @X [template = constants.%X] {} {}
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [template = constants.%F] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic class @C(%T.loc4_9.1: type) {
+// CHECK:STDOUT:   %T.loc4_9.2: type = bind_symbolic_name T, 0 [symbolic = %T.loc4_9.2 (constants.%T)]
+// CHECK:STDOUT:   %T.patt.loc4_9.2: type = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc4_9.2 (constants.%T.patt)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class {
+// CHECK:STDOUT:     %T.ref: type = name_ref T, %T.loc4_9.1 [symbolic = %T.loc4_9.2 (constants.%T)]
+// CHECK:STDOUT:     %.loc9: <error> = base_decl <error>, element0 [template]
+// CHECK:STDOUT:     %.loc10: <witness> = complete_type_witness <error> [template = <error>]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = constants.%C.2
+// CHECK:STDOUT:     .base = %.loc9
+// CHECK:STDOUT:     has_error
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @X {
+// CHECK:STDOUT:   %G.decl: %G.type = fn_decl @G [template = constants.%G] {} {}
+// CHECK:STDOUT:   %.loc14: <witness> = complete_type_witness %.1 [template = constants.%.2]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%X
+// CHECK:STDOUT:   .G = %G.decl
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @G() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %C.ref: %C.type = name_ref C, file.%C.decl [template = constants.%C.1]
+// CHECK:STDOUT:   %X.ref: type = name_ref X, file.%X.decl [template = constants.%X]
+// CHECK:STDOUT:   %C: type = class_type @C, @C(constants.%X) [template = constants.%C.3]
+// CHECK:STDOUT:   %G.ref: <error> = name_ref G, <error> [template = <error>]
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @C(constants.%T) {
+// CHECK:STDOUT:   %T.loc4_9.2 => constants.%T
+// CHECK:STDOUT:   %T.patt.loc4_9.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @C(constants.%X) {
+// CHECK:STDOUT:   %T.loc4_9.2 => constants.%X
+// CHECK:STDOUT:   %T.patt.loc4_9.2 => constants.%X
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- extend_generic_symbolic_base.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %U: type = bind_symbolic_name U, 0 [symbolic]
+// CHECK:STDOUT:   %U.patt: type = symbolic_binding_pattern U, 0 [symbolic]
+// CHECK:STDOUT:   %X.type: type = generic_class_type @X [template]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [template]
+// CHECK:STDOUT:   %X.1: %X.type = struct_value () [template]
+// CHECK:STDOUT:   %X.2: type = class_type @X, @X(%U) [symbolic]
+// CHECK:STDOUT:   %G.type.1: type = fn_type @G, @X(%U) [symbolic]
+// CHECK:STDOUT:   %G.1: %G.type.1 = struct_value () [symbolic]
+// CHECK:STDOUT:   %.1: type = struct_type {} [template]
+// CHECK:STDOUT:   %.2: <witness> = complete_type_witness %.1 [template]
+// CHECK:STDOUT:   %.3: <specific function> = specific_function %G.1, @G(%U) [symbolic]
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic]
+// CHECK:STDOUT:   %T.patt: type = symbolic_binding_pattern T, 0 [symbolic]
+// CHECK:STDOUT:   %C.type: type = generic_class_type @C [template]
+// CHECK:STDOUT:   %C.1: %C.type = struct_value () [template]
+// CHECK:STDOUT:   %C.2: type = class_type @C, @C(%T) [symbolic]
+// CHECK:STDOUT:   %X.3: type = class_type @X, @X(%T) [symbolic]
+// CHECK:STDOUT:   %G.type.2: type = fn_type @G, @X(%T) [symbolic]
+// CHECK:STDOUT:   %G.2: %G.type.2 = struct_value () [symbolic]
+// CHECK:STDOUT:   %.4: type = ptr_type %.1 [template]
+// CHECK:STDOUT:   %.5: type = unbound_element_type %C.2, %X.3 [symbolic]
+// CHECK:STDOUT:   %.6: type = struct_type {.base: %X.3} [symbolic]
+// CHECK:STDOUT:   %.7: <witness> = complete_type_witness %.6 [symbolic]
+// CHECK:STDOUT:   %F.type: type = fn_type @F [template]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [template]
+// CHECK:STDOUT:   %Int32.type: type = fn_type @Int32 [template]
+// CHECK:STDOUT:   %Int32: %Int32.type = struct_value () [template]
+// CHECK:STDOUT:   %C.3: type = class_type @C, @C(i32) [template]
+// CHECK:STDOUT:   %X.4: type = class_type @X, @X(i32) [template]
+// CHECK:STDOUT:   %.8: type = unbound_element_type %C.3, %X.4 [template]
+// CHECK:STDOUT:   %.9: type = struct_type {.base: %X.4} [template]
+// CHECK:STDOUT:   %.10: <witness> = complete_type_witness %.9 [template]
+// CHECK:STDOUT:   %G.type.3: type = fn_type @G, @X(i32) [template]
+// CHECK:STDOUT:   %G.3: %G.type.3 = struct_value () [template]
+// CHECK:STDOUT:   %.11: type = struct_type {.base: %.4} [template]
+// CHECK:STDOUT:   %.12: type = ptr_type %.9 [template]
+// CHECK:STDOUT:   %.13: <specific function> = specific_function %G.3, @G(i32) [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [template] {
+// CHECK:STDOUT:     .Int32 = %import_ref
+// CHECK:STDOUT:     import Core//prelude
+// CHECK:STDOUT:     import Core//prelude/...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref: %Int32.type = import_ref Core//prelude/types, inst+15, loaded [template = constants.%Int32]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = imports.%Core
+// CHECK:STDOUT:     .X = %X.decl
+// CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:     .F = %F.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %X.decl: %X.type = class_decl @X [template = constants.%X.1] {
+// CHECK:STDOUT:     %U.patt.loc4_14.1: type = symbolic_binding_pattern U, 0 [symbolic = %U.patt.loc4_14.2 (constants.%U.patt)]
+// CHECK:STDOUT:     %U.param_patt: type = value_param_pattern %U.patt.loc4_14.1, runtime_param<invalid> [symbolic = %U.patt.loc4_14.2 (constants.%U.patt)]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %U.param: type = value_param runtime_param<invalid>
+// CHECK:STDOUT:     %U.loc4_14.1: type = bind_symbolic_name U, 0, %U.param [symbolic = %U.loc4_14.2 (constants.%U)]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.decl: %C.type = class_decl @C [template = constants.%C.1] {
+// CHECK:STDOUT:     %T.patt.loc8_9.1: type = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc8_9.2 (constants.%T.patt)]
+// CHECK:STDOUT:     %T.param_patt: type = value_param_pattern %T.patt.loc8_9.1, runtime_param<invalid> [symbolic = %T.patt.loc8_9.2 (constants.%T.patt)]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %T.param: type = value_param runtime_param<invalid>
+// CHECK:STDOUT:     %T.loc8_9.1: type = bind_symbolic_name T, 0, %T.param [symbolic = %T.loc8_9.2 (constants.%T)]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [template = constants.%F] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic class @X(%U.loc4_14.1: type) {
+// CHECK:STDOUT:   %U.loc4_14.2: type = bind_symbolic_name U, 0 [symbolic = %U.loc4_14.2 (constants.%U)]
+// CHECK:STDOUT:   %U.patt.loc4_14.2: type = symbolic_binding_pattern U, 0 [symbolic = %U.patt.loc4_14.2 (constants.%U.patt)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %G.type: type = fn_type @G, @X(%U.loc4_14.2) [symbolic = %G.type (constants.%G.type.1)]
+// CHECK:STDOUT:   %G: @X.%G.type (%G.type.1) = struct_value () [symbolic = %G (constants.%G.1)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class {
+// CHECK:STDOUT:     %G.decl: @X.%G.type (%G.type.1) = fn_decl @G [symbolic = @X.%G (constants.%G.1)] {
+// CHECK:STDOUT:       %return.patt: @G.%U (%U) = return_slot_pattern
+// CHECK:STDOUT:       %return.param_patt: @G.%U (%U) = out_param_pattern %return.patt, runtime_param0
+// CHECK:STDOUT:     } {
+// CHECK:STDOUT:       %U.ref: type = name_ref U, @X.%U.loc4_14.1 [symbolic = %U (constants.%U)]
+// CHECK:STDOUT:       %return.param: ref @G.%U (%U) = out_param runtime_param0
+// CHECK:STDOUT:       %return: ref @G.%U (%U) = return_slot %return.param
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %.loc6: <witness> = complete_type_witness %.1 [template = constants.%.2]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = constants.%X.2
+// CHECK:STDOUT:     .G = %G.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic class @C(%T.loc8_9.1: type) {
+// CHECK:STDOUT:   %T.loc8_9.2: type = bind_symbolic_name T, 0 [symbolic = %T.loc8_9.2 (constants.%T)]
+// CHECK:STDOUT:   %T.patt.loc8_9.2: type = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc8_9.2 (constants.%T.patt)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %X.loc9_17.2: type = class_type @X, @X(%T.loc8_9.2) [symbolic = %X.loc9_17.2 (constants.%X.3)]
+// CHECK:STDOUT:   %C: type = class_type @C, @C(%T.loc8_9.2) [symbolic = %C (constants.%C.2)]
+// CHECK:STDOUT:   %.loc9_20.2: type = unbound_element_type @C.%C (%C.2), @C.%X.loc9_17.2 (%X.3) [symbolic = %.loc9_20.2 (constants.%.5)]
+// CHECK:STDOUT:   %.loc10_1.2: type = struct_type {.base: @C.%X.loc9_17.2 (%X.3)} [symbolic = %.loc10_1.2 (constants.%.6)]
+// CHECK:STDOUT:   %.loc10_1.3: <witness> = complete_type_witness @C.%.loc10_1.2 (%.6) [symbolic = %.loc10_1.3 (constants.%.7)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class {
+// CHECK:STDOUT:     %X.ref: %X.type = name_ref X, file.%X.decl [template = constants.%X.1]
+// CHECK:STDOUT:     %T.ref: type = name_ref T, %T.loc8_9.1 [symbolic = %T.loc8_9.2 (constants.%T)]
+// CHECK:STDOUT:     %X.loc9_17.1: type = class_type @X, @X(constants.%T) [symbolic = %X.loc9_17.2 (constants.%X.3)]
+// CHECK:STDOUT:     %.loc9_20.1: @C.%.loc9_20.2 (%.5) = base_decl %X.3, element0 [template]
+// CHECK:STDOUT:     %.loc10_1.1: <witness> = complete_type_witness %.6 [symbolic = %.loc10_1.3 (constants.%.7)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = constants.%C.2
+// CHECK:STDOUT:     .base = %.loc9_20.1
+// CHECK:STDOUT:     extend %X.loc9_17.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic fn @G(@X.%U.loc4_14.1: type) {
+// CHECK:STDOUT:   %U: type = bind_symbolic_name U, 0 [symbolic = %U (constants.%U)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %G.type: type = fn_type @G, @X(%U) [symbolic = %G.type (constants.%G.type.1)]
+// CHECK:STDOUT:   %G: @G.%G.type (%G.type.1) = struct_value () [symbolic = %G (constants.%G.1)]
+// CHECK:STDOUT:   %.loc5_24.3: <specific function> = specific_function %G, @G(%U) [symbolic = %.loc5_24.3 (constants.%.3)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn() -> @G.%U (%U) {
+// CHECK:STDOUT:   !entry:
+// CHECK:STDOUT:     %.loc5_24.1: @G.%G.type (%G.type.1) = specific_constant @X.%G.decl, @X(constants.%U) [symbolic = %G (constants.%G.1)]
+// CHECK:STDOUT:     %G.ref: @G.%G.type (%G.type.1) = name_ref G, %.loc5_24.1 [symbolic = %G (constants.%G.1)]
+// CHECK:STDOUT:     %.loc5_24.2: <specific function> = specific_function %G.ref, @G(constants.%U) [symbolic = %.loc5_24.3 (constants.%.3)]
+// CHECK:STDOUT:     %G.call: init @G.%U (%U) = call %.loc5_24.2()
+// CHECK:STDOUT:     %.loc5_27.1: @G.%U (%U) = value_of_initializer %G.call
+// CHECK:STDOUT:     %.loc5_27.2: @G.%U (%U) = converted %G.call, %.loc5_27.1
+// CHECK:STDOUT:     return %.loc5_27.2
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %int.make_type_32.loc13_10: init type = call constants.%Int32() [template = i32]
+// CHECK:STDOUT:   %.loc13_10.1: type = value_of_initializer %int.make_type_32.loc13_10 [template = i32]
+// CHECK:STDOUT:   %.loc13_10.2: type = converted %int.make_type_32.loc13_10, %.loc13_10.1 [template = i32]
+// CHECK:STDOUT:   %C.ref: %C.type = name_ref C, file.%C.decl [template = constants.%C.1]
+// CHECK:STDOUT:   %int.make_type_32.loc13_18: init type = call constants.%Int32() [template = i32]
+// CHECK:STDOUT:   %.loc13_17.1: type = value_of_initializer %int.make_type_32.loc13_18 [template = i32]
+// CHECK:STDOUT:   %.loc13_17.2: type = converted %int.make_type_32.loc13_18, %.loc13_17.1 [template = i32]
+// CHECK:STDOUT:   %C: type = class_type @C, @C(i32) [template = constants.%C.3]
+// CHECK:STDOUT:   %.loc13_22.1: %G.type.3 = specific_constant @X.%G.decl, @X(i32) [template = constants.%G.3]
+// CHECK:STDOUT:   %G.ref: %G.type.3 = name_ref G, %.loc13_22.1 [template = constants.%G.3]
+// CHECK:STDOUT:   %.loc13_22.2: <specific function> = specific_function %G.ref, @G(i32) [template = constants.%.13]
+// CHECK:STDOUT:   %G.call: init i32 = call %.loc13_22.2()
+// CHECK:STDOUT:   %.loc13_26.1: i32 = value_of_initializer %G.call
+// CHECK:STDOUT:   %.loc13_26.2: i32 = converted %G.call, %.loc13_26.1
+// CHECK:STDOUT:   %i: i32 = bind_name i, %.loc13_26.2
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @X(constants.%U) {
+// CHECK:STDOUT:   %U.loc4_14.2 => constants.%U
+// CHECK:STDOUT:   %U.patt.loc4_14.2 => constants.%U
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %G.type => constants.%G.type.1
+// CHECK:STDOUT:   %G => constants.%G.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @G(constants.%U) {
+// CHECK:STDOUT:   %U => constants.%U
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %G.type => constants.%G.type.1
+// CHECK:STDOUT:   %G => constants.%G.1
+// CHECK:STDOUT:   %.loc5_24.3 => constants.%.3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @X(@X.%U.loc4_14.2) {
+// CHECK:STDOUT:   %U.loc4_14.2 => constants.%U
+// CHECK:STDOUT:   %U.patt.loc4_14.2 => constants.%U
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @X(@G.%U) {
+// CHECK:STDOUT:   %U.loc4_14.2 => constants.%U
+// CHECK:STDOUT:   %U.patt.loc4_14.2 => constants.%U
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @G(@G.%U) {
+// CHECK:STDOUT:   %U => constants.%U
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @C(constants.%T) {
+// CHECK:STDOUT:   %T.loc8_9.2 => constants.%T
+// CHECK:STDOUT:   %T.patt.loc8_9.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @X(constants.%T) {
+// CHECK:STDOUT:   %U.loc4_14.2 => constants.%T
+// CHECK:STDOUT:   %U.patt.loc4_14.2 => constants.%T
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %G.type => constants.%G.type.2
+// CHECK:STDOUT:   %G => constants.%G.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @X(@C.%T.loc8_9.2) {
+// CHECK:STDOUT:   %U.loc4_14.2 => constants.%T
+// CHECK:STDOUT:   %U.patt.loc4_14.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @C(@C.%T.loc8_9.2) {
+// CHECK:STDOUT:   %T.loc8_9.2 => constants.%T
+// CHECK:STDOUT:   %T.patt.loc8_9.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @C(i32) {
+// CHECK:STDOUT:   %T.loc8_9.2 => i32
+// CHECK:STDOUT:   %T.patt.loc8_9.2 => i32
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %X.loc9_17.2 => constants.%X.4
+// CHECK:STDOUT:   %C => constants.%C.3
+// CHECK:STDOUT:   %.loc9_20.2 => constants.%.8
+// CHECK:STDOUT:   %.loc10_1.2 => constants.%.9
+// CHECK:STDOUT:   %.loc10_1.3 => constants.%.10
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @X(i32) {
+// CHECK:STDOUT:   %U.loc4_14.2 => i32
+// CHECK:STDOUT:   %U.patt.loc4_14.2 => i32
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %G.type => constants.%G.type.3
+// CHECK:STDOUT:   %G => constants.%G.3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @G(i32) {
+// CHECK:STDOUT:   %U => i32
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %G.type => constants.%G.type.3
+// CHECK:STDOUT:   %G => constants.%G.3
+// CHECK:STDOUT:   %.loc5_24.3 => constants.%.13
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_import_extend_generic_symbolic_base.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %H.type: type = fn_type @H [template]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [template]
+// CHECK:STDOUT:   %H: %H.type = struct_value () [template]
+// CHECK:STDOUT:   %Int32.type: type = fn_type @Int32 [template]
+// CHECK:STDOUT:   %Int32: %Int32.type = struct_value () [template]
+// CHECK:STDOUT:   %C.type: type = generic_class_type @C [template]
+// CHECK:STDOUT:   %C.1: %C.type = struct_value () [template]
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic]
+// CHECK:STDOUT:   %X.type: type = generic_class_type @X [template]
+// CHECK:STDOUT:   %X.1: %X.type = struct_value () [template]
+// CHECK:STDOUT:   %.1: type = struct_type {} [template]
+// CHECK:STDOUT:   %.2: <witness> = complete_type_witness %.1 [template]
+// CHECK:STDOUT:   %U: type = bind_symbolic_name U, 0 [symbolic]
+// CHECK:STDOUT:   %X.2: type = class_type @X, @X(%U) [symbolic]
+// CHECK:STDOUT:   %U.patt: type = symbolic_binding_pattern U, 0 [symbolic]
+// CHECK:STDOUT:   %X.3: type = class_type @X, @X(%T) [symbolic]
+// CHECK:STDOUT:   %C.2: type = class_type @C, @C(%T) [symbolic]
+// CHECK:STDOUT:   %.3: type = unbound_element_type %C.2, %X.3 [symbolic]
+// CHECK:STDOUT:   %.4: type = struct_type {.base: %X.3} [symbolic]
+// CHECK:STDOUT:   %.5: <witness> = complete_type_witness %.4 [symbolic]
+// CHECK:STDOUT:   %T.patt: type = symbolic_binding_pattern T, 0 [symbolic]
+// CHECK:STDOUT:   %G.type.1: type = fn_type @G, @X(%U) [symbolic]
+// CHECK:STDOUT:   %G.1: %G.type.1 = struct_value () [symbolic]
+// CHECK:STDOUT:   %.6: <specific function> = specific_function %G.1, @G(%U) [symbolic]
+// CHECK:STDOUT:   %G.type.2: type = fn_type @G, @X(%T) [symbolic]
+// CHECK:STDOUT:   %G.2: %G.type.2 = struct_value () [symbolic]
+// CHECK:STDOUT:   %C.3: type = class_type @C, @C(i32) [template]
+// CHECK:STDOUT:   %X.4: type = class_type @X, @X(i32) [template]
+// CHECK:STDOUT:   %.7: type = unbound_element_type %C.3, %X.4 [template]
+// CHECK:STDOUT:   %.8: type = struct_type {.base: %X.4} [template]
+// CHECK:STDOUT:   %.9: <witness> = complete_type_witness %.8 [template]
+// CHECK:STDOUT:   %.10: type = ptr_type %.1 [template]
+// CHECK:STDOUT:   %.11: type = struct_type {.base: %.10} [template]
+// CHECK:STDOUT:   %.12: type = ptr_type %.4 [symbolic]
+// CHECK:STDOUT:   %.13: <specific function> = specific_function %G.2, @G(%T) [symbolic]
+// CHECK:STDOUT:   %ImplicitAs.type.1: type = generic_interface_type @ImplicitAs [template]
+// CHECK:STDOUT:   %ImplicitAs: %ImplicitAs.type.1 = struct_value () [template]
+// CHECK:STDOUT:   %Dest: type = bind_symbolic_name Dest, 0 [symbolic]
+// CHECK:STDOUT:   %ImplicitAs.type.2: type = facet_type <@ImplicitAs, @ImplicitAs(%Dest)> [symbolic]
+// CHECK:STDOUT:   %Self.1: @ImplicitAs.%ImplicitAs.type (%ImplicitAs.type.2) = bind_symbolic_name Self, 1 [symbolic]
+// CHECK:STDOUT:   %Dest.patt: type = symbolic_binding_pattern Dest, 0 [symbolic]
+// CHECK:STDOUT:   %Self.2: %ImplicitAs.type.2 = bind_symbolic_name Self, 1 [symbolic]
+// CHECK:STDOUT:   %Convert.type.1: type = fn_type @Convert, @ImplicitAs(%Dest) [symbolic]
+// CHECK:STDOUT:   %Convert.1: %Convert.type.1 = struct_value () [symbolic]
+// CHECK:STDOUT:   %.14: type = assoc_entity_type %ImplicitAs.type.2, %Convert.type.1 [symbolic]
+// CHECK:STDOUT:   %.15: %.14 = assoc_entity element0, imports.%import_ref.13 [symbolic]
+// CHECK:STDOUT:   %ImplicitAs.type.3: type = facet_type <@ImplicitAs, @ImplicitAs(i32)> [template]
+// CHECK:STDOUT:   %Convert.type.2: type = fn_type @Convert, @ImplicitAs(i32) [template]
+// CHECK:STDOUT:   %Convert.2: %Convert.type.2 = struct_value () [template]
+// CHECK:STDOUT:   %.16: type = assoc_entity_type %ImplicitAs.type.3, %Convert.type.2 [template]
+// CHECK:STDOUT:   %.17: %.16 = assoc_entity element0, imports.%import_ref.13 [template]
+// CHECK:STDOUT:   %.18: %.14 = assoc_entity element0, imports.%import_ref.14 [symbolic]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %import_ref.1 = import_ref Main//extend_generic_symbolic_base, inst+9, unloaded
+// CHECK:STDOUT:   %import_ref.2: %C.type = import_ref Main//extend_generic_symbolic_base, inst+47, loaded [template = constants.%C.1]
+// CHECK:STDOUT:   %import_ref.3 = import_ref Main//extend_generic_symbolic_base, inst+70, unloaded
+// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [template] {
+// CHECK:STDOUT:     .Int32 = %import_ref.4
+// CHECK:STDOUT:     .ImplicitAs = %import_ref.9
+// CHECK:STDOUT:     import Core//prelude
+// CHECK:STDOUT:     import Core//prelude/...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref.4: %Int32.type = import_ref Core//prelude/types, inst+15, loaded [template = constants.%Int32]
+// CHECK:STDOUT:   %import_ref.5 = import_ref Main//extend_generic_symbolic_base, inst+15, unloaded
+// CHECK:STDOUT:   %import_ref.6: @X.%G.type (%G.type.1) = import_ref Main//extend_generic_symbolic_base, inst+21, loaded [symbolic = @X.%G (constants.%G.1)]
+// CHECK:STDOUT:   %import_ref.7 = import_ref Main//extend_generic_symbolic_base, inst+52, unloaded
+// CHECK:STDOUT:   %import_ref.8 = import_ref Main//extend_generic_symbolic_base, inst+61, unloaded
+// CHECK:STDOUT:   %import_ref.9: %ImplicitAs.type.1 = import_ref Core//prelude/operators/as, inst+48, loaded [template = constants.%ImplicitAs]
+// CHECK:STDOUT:   %import_ref.10 = import_ref Core//prelude/operators/as, inst+54, unloaded
+// CHECK:STDOUT:   %import_ref.11: @ImplicitAs.%.1 (%.14) = import_ref Core//prelude/operators/as, inst+76, loaded [symbolic = @ImplicitAs.%.2 (constants.%.18)]
+// CHECK:STDOUT:   %import_ref.12 = import_ref Core//prelude/operators/as, inst+69, unloaded
+// CHECK:STDOUT:   %import_ref.13 = import_ref Core//prelude/operators/as, inst+69, unloaded
+// CHECK:STDOUT:   %import_ref.14 = import_ref Core//prelude/operators/as, inst+69, unloaded
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .X = imports.%import_ref.1
+// CHECK:STDOUT:     .C = imports.%import_ref.2
+// CHECK:STDOUT:     .F = imports.%import_ref.3
+// CHECK:STDOUT:     .Core = imports.%Core
+// CHECK:STDOUT:     .H = %H.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %default.import = import <invalid>
+// CHECK:STDOUT:   %H.decl: %H.type = fn_decl @H [template = constants.%H] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic interface @ImplicitAs(constants.%Dest: type) {
+// CHECK:STDOUT:   %Dest: type = bind_symbolic_name Dest, 0 [symbolic = %Dest (constants.%Dest)]
+// CHECK:STDOUT:   %Dest.patt: type = symbolic_binding_pattern Dest, 0 [symbolic = %Dest.patt (constants.%Dest.patt)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %ImplicitAs.type: type = facet_type <@ImplicitAs, @ImplicitAs(%Dest)> [symbolic = %ImplicitAs.type (constants.%ImplicitAs.type.2)]
+// CHECK:STDOUT:   %Self: %ImplicitAs.type.2 = bind_symbolic_name Self, 1 [symbolic = %Self (constants.%Self.2)]
+// CHECK:STDOUT:   %Convert.type: type = fn_type @Convert, @ImplicitAs(%Dest) [symbolic = %Convert.type (constants.%Convert.type.1)]
+// CHECK:STDOUT:   %Convert: @ImplicitAs.%Convert.type (%Convert.type.1) = struct_value () [symbolic = %Convert (constants.%Convert.1)]
+// CHECK:STDOUT:   %.1: type = assoc_entity_type @ImplicitAs.%ImplicitAs.type (%ImplicitAs.type.2), @ImplicitAs.%Convert.type (%Convert.type.1) [symbolic = %.1 (constants.%.14)]
+// CHECK:STDOUT:   %.2: @ImplicitAs.%.1 (%.14) = assoc_entity element0, imports.%import_ref.13 [symbolic = %.2 (constants.%.15)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   interface {
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = imports.%import_ref.10
+// CHECK:STDOUT:     .Convert = imports.%import_ref.11
+// CHECK:STDOUT:     witness = (imports.%import_ref.12)
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic class @C(constants.%T: type) {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic = %T (constants.%T)]
+// CHECK:STDOUT:   %T.patt: type = symbolic_binding_pattern T, 0 [symbolic = %T.patt (constants.%T.patt)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %X: type = class_type @X, @X(%T) [symbolic = %X (constants.%X.3)]
+// CHECK:STDOUT:   %C: type = class_type @C, @C(%T) [symbolic = %C (constants.%C.2)]
+// CHECK:STDOUT:   %.1: type = unbound_element_type @C.%C (%C.2), @C.%X (%X.3) [symbolic = %.1 (constants.%.3)]
+// CHECK:STDOUT:   %.2: type = struct_type {.base: @C.%X (%X.3)} [symbolic = %.2 (constants.%.4)]
+// CHECK:STDOUT:   %.3: <witness> = complete_type_witness @C.%.2 (%.4) [symbolic = %.3 (constants.%.5)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class {
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = imports.%import_ref.7
+// CHECK:STDOUT:     .base = imports.%import_ref.8
+// CHECK:STDOUT:     extend constants.%X.3
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic class @X(constants.%U: type) {
+// CHECK:STDOUT:   %U: type = bind_symbolic_name U, 0 [symbolic = %U (constants.%U)]
+// CHECK:STDOUT:   %U.patt: type = symbolic_binding_pattern U, 0 [symbolic = %U.patt (constants.%U.patt)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %G.type: type = fn_type @G, @X(%U) [symbolic = %G.type (constants.%G.type.1)]
+// CHECK:STDOUT:   %G: @X.%G.type (%G.type.1) = struct_value () [symbolic = %G (constants.%G.1)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class {
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = imports.%import_ref.5
+// CHECK:STDOUT:     .G = imports.%import_ref.6
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @H() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %int.make_type_32.loc13_10: init type = call constants.%Int32() [template = i32]
+// CHECK:STDOUT:   %.loc13_10.1: type = value_of_initializer %int.make_type_32.loc13_10 [template = i32]
+// CHECK:STDOUT:   %.loc13_10.2: type = converted %int.make_type_32.loc13_10, %.loc13_10.1 [template = i32]
+// CHECK:STDOUT:   %C.ref: %C.type = name_ref C, imports.%import_ref.2 [template = constants.%C.1]
+// CHECK:STDOUT:   %int.make_type_32.loc13_18: init type = call constants.%Int32() [template = i32]
+// CHECK:STDOUT:   %.loc13_17.1: type = value_of_initializer %int.make_type_32.loc13_18 [template = i32]
+// CHECK:STDOUT:   %.loc13_17.2: type = converted %int.make_type_32.loc13_18, %.loc13_17.1 [template = i32]
+// CHECK:STDOUT:   %C: type = class_type @C, @C(i32) [template = constants.%C.3]
+// CHECK:STDOUT:   %.loc13_22.1: %G.type.2 = specific_constant imports.%import_ref.6, @X(constants.%T) [symbolic = constants.%G.2]
+// CHECK:STDOUT:   %G.ref: %G.type.2 = name_ref G, %.loc13_22.1 [symbolic = constants.%G.2]
+// CHECK:STDOUT:   %.loc13_22.2: <specific function> = specific_function %G.ref, @G(constants.%T) [symbolic = constants.%.13]
+// CHECK:STDOUT:   %G.call: init %T = call %.loc13_22.2()
+// CHECK:STDOUT:   %.loc13_26: i32 = converted %G.call, <error> [template = <error>]
+// CHECK:STDOUT:   %j: i32 = bind_name j, <error>
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic fn @G(constants.%U: type) {
+// CHECK:STDOUT:   %U: type = bind_symbolic_name U, 0 [symbolic = %U (constants.%U)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %G.type: type = fn_type @G, @X(%U) [symbolic = %G.type (constants.%G.type.1)]
+// CHECK:STDOUT:   %G: @G.%G.type (%G.type.1) = struct_value () [symbolic = %G (constants.%G.1)]
+// CHECK:STDOUT:   %.1: <specific function> = specific_function %G, @G(%U) [symbolic = %.1 (constants.%.6)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn() -> @G.%U (%U);
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic fn @Convert(constants.%Dest: type, constants.%Self.1: @ImplicitAs.%ImplicitAs.type (%ImplicitAs.type.2)) {
+// CHECK:STDOUT:   %Dest: type = bind_symbolic_name Dest, 0 [symbolic = %Dest (constants.%Dest)]
+// CHECK:STDOUT:   %ImplicitAs.type: type = facet_type <@ImplicitAs, @ImplicitAs(%Dest)> [symbolic = %ImplicitAs.type (constants.%ImplicitAs.type.2)]
+// CHECK:STDOUT:   %Self: %ImplicitAs.type.2 = bind_symbolic_name Self, 1 [symbolic = %Self (constants.%Self.2)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[%self.param_patt: @Convert.%Self (%Self.2)]() -> @Convert.%Dest (%Dest);
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @X(constants.%U) {
+// CHECK:STDOUT:   %U => constants.%U
+// CHECK:STDOUT:   %U.patt => constants.%U
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %G.type => constants.%G.type.1
+// CHECK:STDOUT:   %G => constants.%G.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @X(constants.%T) {
+// CHECK:STDOUT:   %U => constants.%T
+// CHECK:STDOUT:   %U.patt => constants.%T
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %G.type => constants.%G.type.2
+// CHECK:STDOUT:   %G => constants.%G.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @C(constants.%T) {
+// CHECK:STDOUT:   %T => constants.%T
+// CHECK:STDOUT:   %T.patt => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @X(@X.%U) {
+// CHECK:STDOUT:   %U => constants.%U
+// CHECK:STDOUT:   %U.patt => constants.%U
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @X(@C.%T) {
+// CHECK:STDOUT:   %U => constants.%T
+// CHECK:STDOUT:   %U.patt => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @C(@C.%T) {
+// CHECK:STDOUT:   %T => constants.%T
+// CHECK:STDOUT:   %T.patt => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @G(constants.%U) {
+// CHECK:STDOUT:   %U => constants.%U
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %G.type => constants.%G.type.1
+// CHECK:STDOUT:   %G => constants.%G.1
+// CHECK:STDOUT:   %.1 => constants.%.6
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @X(@G.%U) {
+// CHECK:STDOUT:   %U => constants.%U
+// CHECK:STDOUT:   %U.patt => constants.%U
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @G(@G.%U) {
+// CHECK:STDOUT:   %U => constants.%U
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @C(i32) {
+// CHECK:STDOUT:   %T => i32
+// CHECK:STDOUT:   %T.patt => i32
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %X => constants.%X.4
+// CHECK:STDOUT:   %C => constants.%C.3
+// CHECK:STDOUT:   %.1 => constants.%.7
+// CHECK:STDOUT:   %.2 => constants.%.8
+// CHECK:STDOUT:   %.3 => constants.%.9
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @X(i32) {
+// CHECK:STDOUT:   %U => i32
+// CHECK:STDOUT:   %U.patt => i32
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @G(constants.%T) {
+// CHECK:STDOUT:   %U => constants.%T
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %G.type => constants.%G.type.2
+// CHECK:STDOUT:   %G => constants.%G.2
+// CHECK:STDOUT:   %.1 => constants.%.13
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @ImplicitAs(constants.%Dest) {
+// CHECK:STDOUT:   %Dest => constants.%Dest
+// CHECK:STDOUT:   %Dest.patt => constants.%Dest
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @ImplicitAs(@ImplicitAs.%Dest) {
+// CHECK:STDOUT:   %Dest => constants.%Dest
+// CHECK:STDOUT:   %Dest.patt => constants.%Dest
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @ImplicitAs(@Convert.%Dest) {
+// CHECK:STDOUT:   %Dest => constants.%Dest
+// CHECK:STDOUT:   %Dest.patt => constants.%Dest
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Convert(constants.%Dest, constants.%Self.1) {
+// CHECK:STDOUT:   %Dest => constants.%Dest
+// CHECK:STDOUT:   %ImplicitAs.type => constants.%ImplicitAs.type.2
+// CHECK:STDOUT:   %Self => constants.%Self.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @ImplicitAs(i32) {
+// CHECK:STDOUT:   %Dest => i32
+// CHECK:STDOUT:   %Dest.patt => i32
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %ImplicitAs.type => constants.%ImplicitAs.type.3
+// CHECK:STDOUT:   %Self => constants.%Self.2
+// CHECK:STDOUT:   %Convert.type => constants.%Convert.type.2
+// CHECK:STDOUT:   %Convert => constants.%Convert.2
+// CHECK:STDOUT:   %.1 => constants.%.16
+// CHECK:STDOUT:   %.2 => constants.%.17
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 2 - 2
toolchain/check/testdata/class/import_base.carbon

@@ -120,7 +120,7 @@ fn Run() {
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%Child
 // CHECK:STDOUT:   .Self = constants.%Child
 // CHECK:STDOUT:   .base = %.loc13
 // CHECK:STDOUT:   .base = %.loc13
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %Base.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F[%self.param_patt: %Base]();
 // CHECK:STDOUT: fn @F[%self.param_patt: %Base]();
@@ -188,7 +188,7 @@ fn Run() {
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = imports.%import_ref.8
 // CHECK:STDOUT:   .Self = imports.%import_ref.8
 // CHECK:STDOUT:   .base = imports.%import_ref.9
 // CHECK:STDOUT:   .base = imports.%import_ref.9
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend constants.%Base
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @Base {
 // CHECK:STDOUT: class @Base {

+ 9 - 9
toolchain/check/testdata/class/inheritance_access.carbon

@@ -316,7 +316,7 @@ class B {
 // CHECK:STDOUT:   .Self = constants.%Circle
 // CHECK:STDOUT:   .Self = constants.%Circle
 // CHECK:STDOUT:   .base = %.loc10
 // CHECK:STDOUT:   .base = %.loc10
 // CHECK:STDOUT:   .GetPosition = %GetPosition.decl
 // CHECK:STDOUT:   .GetPosition = %GetPosition.decl
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %Shape.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
@@ -426,7 +426,7 @@ class B {
 // CHECK:STDOUT:   .Self = constants.%B
 // CHECK:STDOUT:   .Self = constants.%B
 // CHECK:STDOUT:   .base = %.loc9
 // CHECK:STDOUT:   .base = %.loc9
 // CHECK:STDOUT:   .F [private] = %F.decl
 // CHECK:STDOUT:   .F [private] = %F.decl
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %A.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @C {
 // CHECK:STDOUT: class @C {
@@ -452,7 +452,7 @@ class B {
 // CHECK:STDOUT:   .Self = constants.%C
 // CHECK:STDOUT:   .Self = constants.%C
 // CHECK:STDOUT:   .base = %.loc14
 // CHECK:STDOUT:   .base = %.loc14
 // CHECK:STDOUT:   .G = %G.decl
 // CHECK:STDOUT:   .G = %G.decl
-// CHECK:STDOUT:   extend name_scope3
+// CHECK:STDOUT:   extend %B.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.1();
 // CHECK:STDOUT: fn @F.1();
@@ -570,7 +570,7 @@ class B {
 // CHECK:STDOUT:   .base = %.loc12
 // CHECK:STDOUT:   .base = %.loc12
 // CHECK:STDOUT:   .G = %G.decl
 // CHECK:STDOUT:   .G = %G.decl
 // CHECK:STDOUT:   .H = %H.decl
 // CHECK:STDOUT:   .H = %H.decl
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %A.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
@@ -675,7 +675,7 @@ class B {
 // CHECK:STDOUT:   .Self = constants.%Square
 // CHECK:STDOUT:   .Self = constants.%Square
 // CHECK:STDOUT:   .base = %.loc9
 // CHECK:STDOUT:   .base = %.loc9
 // CHECK:STDOUT:   .GetPosition = %GetPosition.decl
 // CHECK:STDOUT:   .GetPosition = %GetPosition.decl
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %Shape.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
@@ -852,7 +852,7 @@ class B {
 // CHECK:STDOUT:   .Self = constants.%C
 // CHECK:STDOUT:   .Self = constants.%C
 // CHECK:STDOUT:   .base = %.loc9
 // CHECK:STDOUT:   .base = %.loc9
 // CHECK:STDOUT:   .G = %G.decl
 // CHECK:STDOUT:   .G = %G.decl
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %B.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F() {
 // CHECK:STDOUT: fn @F() {
@@ -924,7 +924,7 @@ class B {
 // CHECK:STDOUT:   .Self = constants.%C
 // CHECK:STDOUT:   .Self = constants.%C
 // CHECK:STDOUT:   .base = %.loc9
 // CHECK:STDOUT:   .base = %.loc9
 // CHECK:STDOUT:   .G = %G.decl
 // CHECK:STDOUT:   .G = %G.decl
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %B.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F() {
 // CHECK:STDOUT: fn @F() {
@@ -1146,7 +1146,7 @@ class B {
 // CHECK:STDOUT:   .Self = constants.%B
 // CHECK:STDOUT:   .Self = constants.%B
 // CHECK:STDOUT:   .base = %.loc9
 // CHECK:STDOUT:   .base = %.loc9
 // CHECK:STDOUT:   .F = %F.decl
 // CHECK:STDOUT:   .F = %F.decl
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %A.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
@@ -1229,7 +1229,7 @@ class B {
 // CHECK:STDOUT:   .Self = constants.%B
 // CHECK:STDOUT:   .Self = constants.%B
 // CHECK:STDOUT:   .base = %.loc9
 // CHECK:STDOUT:   .base = %.loc9
 // CHECK:STDOUT:   .F = %F.decl
 // CHECK:STDOUT:   .F = %F.decl
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %A.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";

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

@@ -169,7 +169,7 @@ fn Call(p: Derived*) -> i32 {
 // CHECK:STDOUT:   .base = %.loc16
 // CHECK:STDOUT:   .base = %.loc16
 // CHECK:STDOUT:   .SelfBase = %SelfBase.decl
 // CHECK:STDOUT:   .SelfBase = %SelfBase.decl
 // CHECK:STDOUT:   .AddrSelfBase = %AddrSelfBase.decl
 // CHECK:STDOUT:   .AddrSelfBase = %AddrSelfBase.decl
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %Base.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";

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

@@ -174,7 +174,7 @@ fn F() {
 // CHECK:STDOUT:   .Self = constants.%Derived
 // CHECK:STDOUT:   .Self = constants.%Derived
 // CHECK:STDOUT:   .F = %F.decl
 // CHECK:STDOUT:   .F = %F.decl
 // CHECK:STDOUT:   .base = %.loc8
 // CHECK:STDOUT:   .base = %.loc8
-// CHECK:STDOUT:   extend name_scope4
+// CHECK:STDOUT:   extend %Base.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @Base {
 // CHECK:STDOUT: class @Base {

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

@@ -100,7 +100,7 @@ fn G(c: C) {
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%C
 // CHECK:STDOUT:   .Self = constants.%C
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend @impl.%HasF.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: generic fn @F.1(@HasF.%Self: %HasF.type) {
 // CHECK:STDOUT: generic fn @F.1(@HasF.%Self: %HasF.type) {

+ 550 - 0
toolchain/check/testdata/impl/extend_impl_generic.carbon

@@ -0,0 +1,550 @@
+// 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
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/impl/extend_impl_generic.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/impl/extend_impl_generic.carbon
+
+// --- extend_impl_generic_interface.carbon
+
+library "[[@TEST_NAME]]";
+
+interface HasF(T:! type) {
+  fn F() -> T;
+}
+
+class Param {
+  var x: i32;
+}
+
+class C {
+  extend impl as HasF(Param) {
+    fn F() -> Param {
+      return {.x = 2};
+    }
+  }
+}
+
+fn G(c: C) {
+  let a: i32 = C.F().x;
+  var b: i32 = c.F().x;
+}
+
+// --- extend_impl_generic_class.carbon
+
+library "[[@TEST_NAME]]";
+
+interface I(T:! type) {
+  fn F[self: Self](t: T);
+}
+
+class X(U:! type) {
+  extend impl as I(U) {
+    fn F[self: Self](t: U) { }
+  }
+}
+
+// CHECK:STDOUT: --- extend_impl_generic_interface.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic]
+// CHECK:STDOUT:   %T.patt: type = symbolic_binding_pattern T, 0 [symbolic]
+// CHECK:STDOUT:   %HasF.type.1: type = generic_interface_type @HasF [template]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [template]
+// CHECK:STDOUT:   %HasF: %HasF.type.1 = struct_value () [template]
+// CHECK:STDOUT:   %HasF.type.2: type = facet_type <@HasF, @HasF(%T)> [symbolic]
+// CHECK:STDOUT:   %Self: %HasF.type.2 = bind_symbolic_name Self, 1 [symbolic]
+// CHECK:STDOUT:   %F.type.1: type = fn_type @F.1, @HasF(%T) [symbolic]
+// CHECK:STDOUT:   %F.1: %F.type.1 = struct_value () [symbolic]
+// CHECK:STDOUT:   %.1: type = assoc_entity_type %HasF.type.2, %F.type.1 [symbolic]
+// CHECK:STDOUT:   %.2: %.1 = assoc_entity element0, @HasF.%F.decl [symbolic]
+// CHECK:STDOUT:   %Param: type = class_type @Param [template]
+// CHECK:STDOUT:   %Int32.type: type = fn_type @Int32 [template]
+// CHECK:STDOUT:   %Int32: %Int32.type = struct_value () [template]
+// CHECK:STDOUT:   %.3: type = unbound_element_type %Param, i32 [template]
+// CHECK:STDOUT:   %.4: type = struct_type {.x: i32} [template]
+// CHECK:STDOUT:   %.5: <witness> = complete_type_witness %.4 [template]
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %HasF.type.3: type = facet_type <@HasF, @HasF(%Param)> [template]
+// CHECK:STDOUT:   %F.type.2: type = fn_type @F.1, @HasF(%Param) [template]
+// CHECK:STDOUT:   %F.2: %F.type.2 = struct_value () [template]
+// CHECK:STDOUT:   %.6: type = assoc_entity_type %HasF.type.3, %F.type.2 [template]
+// CHECK:STDOUT:   %.7: %.6 = assoc_entity element0, @HasF.%F.decl [template]
+// CHECK:STDOUT:   %F.type.3: type = fn_type @F.2 [template]
+// CHECK:STDOUT:   %F.3: %F.type.3 = struct_value () [template]
+// CHECK:STDOUT:   %.8: <witness> = interface_witness (%F.3) [template]
+// CHECK:STDOUT:   %.9: type = struct_type {} [template]
+// CHECK:STDOUT:   %.10: <witness> = complete_type_witness %.9 [template]
+// CHECK:STDOUT:   %.11: type = ptr_type %.4 [template]
+// CHECK:STDOUT:   %.12: i32 = int_value 2 [template]
+// CHECK:STDOUT:   %struct: %Param = struct_value (%.12) [template]
+// CHECK:STDOUT:   %G.type: type = fn_type @G [template]
+// CHECK:STDOUT:   %G: %G.type = struct_value () [template]
+// CHECK:STDOUT:   %.13: type = ptr_type %.9 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [template] {
+// CHECK:STDOUT:     .Int32 = %import_ref
+// CHECK:STDOUT:     import Core//prelude
+// CHECK:STDOUT:     import Core//prelude/...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref: %Int32.type = import_ref Core//prelude/types, inst+15, loaded [template = constants.%Int32]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = imports.%Core
+// CHECK:STDOUT:     .HasF = %HasF.decl
+// CHECK:STDOUT:     .Param = %Param.decl
+// CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:     .G = %G.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %HasF.decl: %HasF.type.1 = interface_decl @HasF [template = constants.%HasF] {
+// CHECK:STDOUT:     %T.patt.loc4_16.1: type = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc4_16.2 (constants.%T.patt)]
+// CHECK:STDOUT:     %T.param_patt: type = value_param_pattern %T.patt.loc4_16.1, runtime_param<invalid> [symbolic = %T.patt.loc4_16.2 (constants.%T.patt)]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %T.param: type = value_param runtime_param<invalid>
+// CHECK:STDOUT:     %T.loc4_16.1: type = bind_symbolic_name T, 0, %T.param [symbolic = %T.loc4_16.2 (constants.%T)]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Param.decl: type = class_decl @Param [template = constants.%Param] {} {}
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [template = constants.%C] {} {}
+// CHECK:STDOUT:   %G.decl: %G.type = fn_decl @G [template = constants.%G] {
+// CHECK:STDOUT:     %c.patt: %C = binding_pattern c
+// CHECK:STDOUT:     %c.param_patt: %C = value_param_pattern %c.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %C.ref.loc20: type = name_ref C, file.%C.decl [template = constants.%C]
+// CHECK:STDOUT:     %c.param: %C = value_param runtime_param0
+// CHECK:STDOUT:     %c: %C = bind_name c, %c.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic interface @HasF(%T.loc4_16.1: type) {
+// CHECK:STDOUT:   %T.loc4_16.2: type = bind_symbolic_name T, 0 [symbolic = %T.loc4_16.2 (constants.%T)]
+// CHECK:STDOUT:   %T.patt.loc4_16.2: type = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc4_16.2 (constants.%T.patt)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %HasF.type: type = facet_type <@HasF, @HasF(%T.loc4_16.2)> [symbolic = %HasF.type (constants.%HasF.type.2)]
+// CHECK:STDOUT:   %Self.2: %HasF.type.2 = bind_symbolic_name Self, 1 [symbolic = %Self.2 (constants.%Self)]
+// CHECK:STDOUT:   %F.type: type = fn_type @F.1, @HasF(%T.loc4_16.2) [symbolic = %F.type (constants.%F.type.1)]
+// CHECK:STDOUT:   %F: @HasF.%F.type (%F.type.1) = struct_value () [symbolic = %F (constants.%F.1)]
+// CHECK:STDOUT:   %.loc5_14.2: type = assoc_entity_type @HasF.%HasF.type (%HasF.type.2), @HasF.%F.type (%F.type.1) [symbolic = %.loc5_14.2 (constants.%.1)]
+// CHECK:STDOUT:   %.loc5_14.3: @HasF.%.loc5_14.2 (%.1) = assoc_entity element0, %F.decl [symbolic = %.loc5_14.3 (constants.%.2)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   interface {
+// CHECK:STDOUT:     %Self.1: @HasF.%HasF.type (%HasF.type.2) = bind_symbolic_name Self, 1 [symbolic = %Self.2 (constants.%Self)]
+// CHECK:STDOUT:     %F.decl: @HasF.%F.type (%F.type.1) = fn_decl @F.1 [symbolic = @HasF.%F (constants.%F.1)] {
+// CHECK:STDOUT:       %return.patt: @F.1.%T (%T) = return_slot_pattern
+// CHECK:STDOUT:       %return.param_patt: @F.1.%T (%T) = out_param_pattern %return.patt, runtime_param0
+// CHECK:STDOUT:     } {
+// CHECK:STDOUT:       %T.ref: type = name_ref T, @HasF.%T.loc4_16.1 [symbolic = %T (constants.%T)]
+// CHECK:STDOUT:       %return.param: ref @F.1.%T (%T) = out_param runtime_param0
+// CHECK:STDOUT:       %return: ref @F.1.%T (%T) = return_slot %return.param
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %.loc5_14.1: @HasF.%.loc5_14.2 (%.1) = assoc_entity element0, %F.decl [symbolic = %.loc5_14.3 (constants.%.2)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = %Self.1
+// CHECK:STDOUT:     .F = %.loc5_14.1
+// CHECK:STDOUT:     witness = (%F.decl)
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: impl @impl: %Self.ref as %HasF.type {
+// CHECK:STDOUT:   %F.decl: %F.type.3 = fn_decl @F.2 [template = constants.%F.3] {
+// CHECK:STDOUT:     %return.patt: %Param = return_slot_pattern
+// CHECK:STDOUT:     %return.param_patt: %Param = out_param_pattern %return.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %Param.ref: type = name_ref Param, file.%Param.decl [template = constants.%Param]
+// CHECK:STDOUT:     %return.param: ref %Param = out_param runtime_param0
+// CHECK:STDOUT:     %return: ref %Param = return_slot %return.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.loc13: <witness> = interface_witness (%F.decl) [template = constants.%.8]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .F = %F.decl
+// CHECK:STDOUT:   witness = %.loc13
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Param {
+// CHECK:STDOUT:   %int.make_type_32: init type = call constants.%Int32() [template = i32]
+// CHECK:STDOUT:   %.loc9_10.1: type = value_of_initializer %int.make_type_32 [template = i32]
+// CHECK:STDOUT:   %.loc9_10.2: type = converted %int.make_type_32, %.loc9_10.1 [template = i32]
+// CHECK:STDOUT:   %.loc9_8: %.3 = field_decl x, element0 [template]
+// CHECK:STDOUT:   %.loc10: <witness> = complete_type_witness %.4 [template = constants.%.5]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Param
+// CHECK:STDOUT:   .x = %.loc9_8
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT:   impl_decl @impl [template] {} {
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%C [template = constants.%C]
+// CHECK:STDOUT:     %HasF.ref: %HasF.type.1 = name_ref HasF, file.%HasF.decl [template = constants.%HasF]
+// CHECK:STDOUT:     %Param.ref: type = name_ref Param, file.%Param.decl [template = constants.%Param]
+// CHECK:STDOUT:     %HasF.type: type = facet_type <@HasF, @HasF(constants.%Param)> [template = constants.%HasF.type.3]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.loc18: <witness> = complete_type_witness %.9 [template = constants.%.10]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%C
+// CHECK:STDOUT:   extend @impl.%HasF.type
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic fn @F.1(@HasF.%T.loc4_16.1: type, @HasF.%Self.1: @HasF.%HasF.type (%HasF.type.2)) {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic = %T (constants.%T)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn() -> @F.1.%T (%T);
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F.2() -> %return: %Param {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc15_20: i32 = int_value 2 [template = constants.%.12]
+// CHECK:STDOUT:   %.loc15_21.1: %.4 = struct_literal (%.loc15_20)
+// CHECK:STDOUT:   %.loc15_21.2: ref i32 = class_element_access %return, element0
+// CHECK:STDOUT:   %.loc15_21.3: init i32 = initialize_from %.loc15_20 to %.loc15_21.2 [template = constants.%.12]
+// CHECK:STDOUT:   %.loc15_21.4: init %Param = class_init (%.loc15_21.3), %return [template = constants.%struct]
+// CHECK:STDOUT:   %.loc15_22: init %Param = converted %.loc15_21.1, %.loc15_21.4 [template = constants.%struct]
+// CHECK:STDOUT:   return %.loc15_22 to %return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @G(%c.param_patt: %C) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %int.make_type_32.loc21: init type = call constants.%Int32() [template = i32]
+// CHECK:STDOUT:   %.loc21_10.1: type = value_of_initializer %int.make_type_32.loc21 [template = i32]
+// CHECK:STDOUT:   %.loc21_10.2: type = converted %int.make_type_32.loc21, %.loc21_10.1 [template = i32]
+// CHECK:STDOUT:   %C.ref.loc21: type = name_ref C, file.%C.decl [template = constants.%C]
+// CHECK:STDOUT:   %.loc21_17.1: %.6 = specific_constant @HasF.%.loc5_14.1, @HasF(constants.%Param) [template = constants.%.7]
+// CHECK:STDOUT:   %F.ref.loc21: %.6 = name_ref F, %.loc21_17.1 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc21_17.2: %F.type.2 = interface_witness_access constants.%.8, element0 [template = constants.%F.3]
+// CHECK:STDOUT:   %.loc21_19.1: ref %Param = temporary_storage
+// CHECK:STDOUT:   %F.call.loc21: init %Param = call %.loc21_17.2() to %.loc21_19.1
+// CHECK:STDOUT:   %.loc21_19.2: ref %Param = temporary %.loc21_19.1, %F.call.loc21
+// CHECK:STDOUT:   %x.ref.loc21: %.3 = name_ref x, @Param.%.loc9_8 [template = @Param.%.loc9_8]
+// CHECK:STDOUT:   %.loc21_21.1: ref i32 = class_element_access %.loc21_19.2, element0
+// CHECK:STDOUT:   %.loc21_21.2: i32 = bind_value %.loc21_21.1
+// CHECK:STDOUT:   %a: i32 = bind_name a, %.loc21_21.2
+// CHECK:STDOUT:   %int.make_type_32.loc22: init type = call constants.%Int32() [template = i32]
+// CHECK:STDOUT:   %.loc22_10.1: type = value_of_initializer %int.make_type_32.loc22 [template = i32]
+// CHECK:STDOUT:   %.loc22_10.2: type = converted %int.make_type_32.loc22, %.loc22_10.1 [template = i32]
+// CHECK:STDOUT:   %b.var: ref i32 = var b
+// CHECK:STDOUT:   %b: ref i32 = bind_name b, %b.var
+// CHECK:STDOUT:   %c.ref: %C = name_ref c, %c
+// CHECK:STDOUT:   %.loc22_17.1: %.6 = specific_constant @HasF.%.loc5_14.1, @HasF(constants.%Param) [template = constants.%.7]
+// CHECK:STDOUT:   %F.ref.loc22: %.6 = name_ref F, %.loc22_17.1 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc22_17.2: %F.type.2 = interface_witness_access constants.%.8, element0 [template = constants.%F.3]
+// CHECK:STDOUT:   %.loc22_19.1: ref %Param = temporary_storage
+// CHECK:STDOUT:   %F.call.loc22: init %Param = call %.loc22_17.2() to %.loc22_19.1
+// CHECK:STDOUT:   %.loc22_19.2: ref %Param = temporary %.loc22_19.1, %F.call.loc22
+// CHECK:STDOUT:   %x.ref.loc22: %.3 = name_ref x, @Param.%.loc9_8 [template = @Param.%.loc9_8]
+// CHECK:STDOUT:   %.loc22_21.1: ref i32 = class_element_access %.loc22_19.2, element0
+// CHECK:STDOUT:   %.loc22_21.2: i32 = bind_value %.loc22_21.1
+// CHECK:STDOUT:   assign %b.var, %.loc22_21.2
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @HasF(constants.%T) {
+// CHECK:STDOUT:   %T.loc4_16.2 => constants.%T
+// CHECK:STDOUT:   %T.patt.loc4_16.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @F.1(constants.%T, constants.%Self) {
+// CHECK:STDOUT:   %T => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @HasF(@HasF.%T.loc4_16.2) {
+// CHECK:STDOUT:   %T.loc4_16.2 => constants.%T
+// CHECK:STDOUT:   %T.patt.loc4_16.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @HasF(constants.%Param) {
+// CHECK:STDOUT:   %T.loc4_16.2 => constants.%Param
+// CHECK:STDOUT:   %T.patt.loc4_16.2 => constants.%Param
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %HasF.type => constants.%HasF.type.3
+// CHECK:STDOUT:   %Self.2 => constants.%Self
+// CHECK:STDOUT:   %F.type => constants.%F.type.2
+// CHECK:STDOUT:   %F => constants.%F.2
+// CHECK:STDOUT:   %.loc5_14.2 => constants.%.6
+// CHECK:STDOUT:   %.loc5_14.3 => constants.%.7
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @F.1(constants.%Param, constants.%C) {
+// CHECK:STDOUT:   %T => constants.%Param
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- extend_impl_generic_class.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic]
+// CHECK:STDOUT:   %T.patt: type = symbolic_binding_pattern T, 0 [symbolic]
+// CHECK:STDOUT:   %I.type.1: type = generic_interface_type @I [template]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [template]
+// CHECK:STDOUT:   %I: %I.type.1 = struct_value () [template]
+// CHECK:STDOUT:   %I.type.2: type = facet_type <@I, @I(%T)> [symbolic]
+// CHECK:STDOUT:   %Self: %I.type.2 = bind_symbolic_name Self, 1 [symbolic]
+// CHECK:STDOUT:   %F.type.1: type = fn_type @F.1, @I(%T) [symbolic]
+// CHECK:STDOUT:   %F.1: %F.type.1 = struct_value () [symbolic]
+// CHECK:STDOUT:   %.1: type = assoc_entity_type %I.type.2, %F.type.1 [symbolic]
+// CHECK:STDOUT:   %.2: %.1 = assoc_entity element0, @I.%F.decl [symbolic]
+// CHECK:STDOUT:   %U: type = bind_symbolic_name U, 0 [symbolic]
+// CHECK:STDOUT:   %U.patt: type = symbolic_binding_pattern U, 0 [symbolic]
+// CHECK:STDOUT:   %X.type: type = generic_class_type @X [template]
+// CHECK:STDOUT:   %X.1: %X.type = struct_value () [template]
+// CHECK:STDOUT:   %X.2: type = class_type @X, @X(%U) [symbolic]
+// CHECK:STDOUT:   %I.type.3: type = facet_type <@I, @I(%U)> [symbolic]
+// CHECK:STDOUT:   %F.type.2: type = fn_type @F.1, @I(%U) [symbolic]
+// CHECK:STDOUT:   %F.2: %F.type.2 = struct_value () [symbolic]
+// CHECK:STDOUT:   %.3: type = assoc_entity_type %I.type.3, %F.type.2 [symbolic]
+// CHECK:STDOUT:   %.4: %.3 = assoc_entity element0, @I.%F.decl [symbolic]
+// CHECK:STDOUT:   %F.type.3: type = fn_type @F.2, @impl(%U) [symbolic]
+// CHECK:STDOUT:   %F.3: %F.type.3 = struct_value () [symbolic]
+// CHECK:STDOUT:   %.5: <witness> = interface_witness (%F.3) [symbolic]
+// CHECK:STDOUT:   %.6: type = struct_type {} [template]
+// CHECK:STDOUT:   %.7: <witness> = complete_type_witness %.6 [template]
+// CHECK:STDOUT:   %.8: type = ptr_type %.6 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [template] {
+// CHECK:STDOUT:     import Core//prelude
+// CHECK:STDOUT:     import Core//prelude/...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = imports.%Core
+// CHECK:STDOUT:     .I = %I.decl
+// CHECK:STDOUT:     .X = %X.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %I.decl: %I.type.1 = interface_decl @I [template = constants.%I] {
+// CHECK:STDOUT:     %T.patt.loc4_13.1: type = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc4_13.2 (constants.%T.patt)]
+// CHECK:STDOUT:     %T.param_patt: type = value_param_pattern %T.patt.loc4_13.1, runtime_param<invalid> [symbolic = %T.patt.loc4_13.2 (constants.%T.patt)]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %T.param: type = value_param runtime_param<invalid>
+// CHECK:STDOUT:     %T.loc4_13.1: type = bind_symbolic_name T, 0, %T.param [symbolic = %T.loc4_13.2 (constants.%T)]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %X.decl: %X.type = class_decl @X [template = constants.%X.1] {
+// CHECK:STDOUT:     %U.patt.loc8_9.1: type = symbolic_binding_pattern U, 0 [symbolic = %U.patt.loc8_9.2 (constants.%U.patt)]
+// CHECK:STDOUT:     %U.param_patt: type = value_param_pattern %U.patt.loc8_9.1, runtime_param<invalid> [symbolic = %U.patt.loc8_9.2 (constants.%U.patt)]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %U.param: type = value_param runtime_param<invalid>
+// CHECK:STDOUT:     %U.loc8_9.1: type = bind_symbolic_name U, 0, %U.param [symbolic = %U.loc8_9.2 (constants.%U)]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic interface @I(%T.loc4_13.1: type) {
+// CHECK:STDOUT:   %T.loc4_13.2: type = bind_symbolic_name T, 0 [symbolic = %T.loc4_13.2 (constants.%T)]
+// CHECK:STDOUT:   %T.patt.loc4_13.2: type = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc4_13.2 (constants.%T.patt)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %I.type: type = facet_type <@I, @I(%T.loc4_13.2)> [symbolic = %I.type (constants.%I.type.2)]
+// CHECK:STDOUT:   %Self.2: %I.type.2 = bind_symbolic_name Self, 1 [symbolic = %Self.2 (constants.%Self)]
+// CHECK:STDOUT:   %F.type: type = fn_type @F.1, @I(%T.loc4_13.2) [symbolic = %F.type (constants.%F.type.1)]
+// CHECK:STDOUT:   %F: @I.%F.type (%F.type.1) = struct_value () [symbolic = %F (constants.%F.1)]
+// CHECK:STDOUT:   %.loc5_25.2: type = assoc_entity_type @I.%I.type (%I.type.2), @I.%F.type (%F.type.1) [symbolic = %.loc5_25.2 (constants.%.1)]
+// CHECK:STDOUT:   %.loc5_25.3: @I.%.loc5_25.2 (%.1) = assoc_entity element0, %F.decl [symbolic = %.loc5_25.3 (constants.%.2)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   interface {
+// CHECK:STDOUT:     %Self.1: @I.%I.type (%I.type.2) = bind_symbolic_name Self, 1 [symbolic = %Self.2 (constants.%Self)]
+// CHECK:STDOUT:     %F.decl: @I.%F.type (%F.type.1) = fn_decl @F.1 [symbolic = @I.%F (constants.%F.1)] {
+// CHECK:STDOUT:       %self.patt: @F.1.%Self (%Self) = binding_pattern self
+// CHECK:STDOUT:       %self.param_patt: @F.1.%Self (%Self) = value_param_pattern %self.patt, runtime_param0
+// CHECK:STDOUT:       %t.patt: @F.1.%T (%T) = binding_pattern t
+// CHECK:STDOUT:       %t.param_patt: @F.1.%T (%T) = value_param_pattern %t.patt, runtime_param1
+// CHECK:STDOUT:     } {
+// CHECK:STDOUT:       %.loc5_14.1: @F.1.%I.type (%I.type.2) = specific_constant @I.%Self.1, @I(constants.%T) [symbolic = %Self (constants.%Self)]
+// CHECK:STDOUT:       %Self.ref: @F.1.%I.type (%I.type.2) = name_ref Self, %.loc5_14.1 [symbolic = %Self (constants.%Self)]
+// CHECK:STDOUT:       %.loc5_14.2: type = facet_type_access %Self.ref [symbolic = %Self (constants.%Self)]
+// CHECK:STDOUT:       %.loc5_14.3: type = converted %Self.ref, %.loc5_14.2 [symbolic = %Self (constants.%Self)]
+// CHECK:STDOUT:       %T.ref: type = name_ref T, @I.%T.loc4_13.1 [symbolic = %T (constants.%T)]
+// CHECK:STDOUT:       %self.param: @F.1.%Self (%Self) = value_param runtime_param0
+// CHECK:STDOUT:       %self: @F.1.%Self (%Self) = bind_name self, %self.param
+// CHECK:STDOUT:       %t.param: @F.1.%T (%T) = value_param runtime_param1
+// CHECK:STDOUT:       %t: @F.1.%T (%T) = bind_name t, %t.param
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %.loc5_25.1: @I.%.loc5_25.2 (%.1) = assoc_entity element0, %F.decl [symbolic = %.loc5_25.3 (constants.%.2)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = %Self.1
+// CHECK:STDOUT:     .F = %.loc5_25.1
+// CHECK:STDOUT:     witness = (%F.decl)
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic impl @impl(@X.%U.loc8_9.1: type) {
+// CHECK:STDOUT:   %U: type = bind_symbolic_name U, 0 [symbolic = %U (constants.%U)]
+// CHECK:STDOUT:   %X: type = class_type @X, @X(%U) [symbolic = %X (constants.%X.2)]
+// CHECK:STDOUT:   %I.type.loc9_19.2: type = facet_type <@I, @I(%U)> [symbolic = %I.type.loc9_19.2 (constants.%I.type.3)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %F.type: type = fn_type @F.2, @impl(%U) [symbolic = %F.type (constants.%F.type.3)]
+// CHECK:STDOUT:   %F: @impl.%F.type (%F.type.3) = struct_value () [symbolic = %F (constants.%F.3)]
+// CHECK:STDOUT:   %.loc9_23.2: <witness> = interface_witness (%F) [symbolic = %.loc9_23.2 (constants.%.5)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   impl: %Self.ref as %I.type.loc9_19.1 {
+// CHECK:STDOUT:     %F.decl: @impl.%F.type (%F.type.3) = fn_decl @F.2 [symbolic = @impl.%F (constants.%F.3)] {
+// CHECK:STDOUT:       %self.patt: @F.2.%X (%X.2) = binding_pattern self
+// CHECK:STDOUT:       %self.param_patt: @F.2.%X (%X.2) = value_param_pattern %self.patt, runtime_param0
+// CHECK:STDOUT:       %t.patt: @F.2.%U (%U) = binding_pattern t
+// CHECK:STDOUT:       %t.param_patt: @F.2.%U (%U) = value_param_pattern %t.patt, runtime_param1
+// CHECK:STDOUT:     } {
+// CHECK:STDOUT:       %.loc10: type = specific_constant constants.%X.2, @X(constants.%U) [symbolic = %X (constants.%X.2)]
+// CHECK:STDOUT:       %Self.ref: type = name_ref Self, %.loc10 [symbolic = %X (constants.%X.2)]
+// CHECK:STDOUT:       %U.ref: type = name_ref U, @X.%U.loc8_9.1 [symbolic = %U (constants.%U)]
+// CHECK:STDOUT:       %self.param: @F.2.%X (%X.2) = value_param runtime_param0
+// CHECK:STDOUT:       %self: @F.2.%X (%X.2) = bind_name self, %self.param
+// CHECK:STDOUT:       %t.param: @F.2.%U (%U) = value_param runtime_param1
+// CHECK:STDOUT:       %t: @F.2.%U (%U) = bind_name t, %t.param
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %.loc9_23.1: <witness> = interface_witness (%F.decl) [symbolic = %.loc9_23.2 (constants.%.5)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .F = %F.decl
+// CHECK:STDOUT:     witness = %.loc9_23.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic class @X(%U.loc8_9.1: type) {
+// CHECK:STDOUT:   %U.loc8_9.2: type = bind_symbolic_name U, 0 [symbolic = %U.loc8_9.2 (constants.%U)]
+// CHECK:STDOUT:   %U.patt.loc8_9.2: type = symbolic_binding_pattern U, 0 [symbolic = %U.patt.loc8_9.2 (constants.%U.patt)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %I.type: type = facet_type <@I, @I(%U.loc8_9.2)> [symbolic = %I.type (constants.%I.type.3)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class {
+// CHECK:STDOUT:     impl_decl @impl [template] {} {
+// CHECK:STDOUT:       %Self.ref: type = name_ref Self, constants.%X.2 [symbolic = %X (constants.%X.2)]
+// CHECK:STDOUT:       %I.ref: %I.type.1 = name_ref I, file.%I.decl [template = constants.%I]
+// CHECK:STDOUT:       %U.ref: type = name_ref U, @X.%U.loc8_9.1 [symbolic = %U (constants.%U)]
+// CHECK:STDOUT:       %I.type.loc9_19.1: type = facet_type <@I, @I(constants.%U)> [symbolic = %I.type.loc9_19.2 (constants.%I.type.3)]
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %.loc9: type = specific_constant @impl.%I.type.loc9_19.1, @impl(constants.%U) [symbolic = %I.type (constants.%I.type.3)]
+// CHECK:STDOUT:     %.loc12: <witness> = complete_type_witness %.6 [template = constants.%.7]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = constants.%X.2
+// CHECK:STDOUT:     extend %.loc9
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic fn @F.1(@I.%T.loc4_13.1: type, @I.%Self.1: @I.%I.type (%I.type.2)) {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic = %T (constants.%T)]
+// CHECK:STDOUT:   %I.type: type = facet_type <@I, @I(%T)> [symbolic = %I.type (constants.%I.type.2)]
+// CHECK:STDOUT:   %Self: %I.type.2 = bind_symbolic_name Self, 1 [symbolic = %Self (constants.%Self)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[%self.param_patt: @F.1.%Self (%Self)](%t.param_patt: @F.1.%T (%T));
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic fn @F.2(@X.%U.loc8_9.1: type) {
+// CHECK:STDOUT:   %U: type = bind_symbolic_name U, 0 [symbolic = %U (constants.%U)]
+// CHECK:STDOUT:   %X: type = class_type @X, @X(%U) [symbolic = %X (constants.%X.2)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[%self.param_patt: @F.2.%X (%X.2)](%t.param_patt: @F.2.%U (%U)) {
+// CHECK:STDOUT:   !entry:
+// CHECK:STDOUT:     return
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @I(constants.%T) {
+// CHECK:STDOUT:   %T.loc4_13.2 => constants.%T
+// CHECK:STDOUT:   %T.patt.loc4_13.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @I(@F.1.%T) {
+// CHECK:STDOUT:   %T.loc4_13.2 => constants.%T
+// CHECK:STDOUT:   %T.patt.loc4_13.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @F.1(constants.%T, constants.%Self) {
+// CHECK:STDOUT:   %T => constants.%T
+// CHECK:STDOUT:   %I.type => constants.%I.type.2
+// CHECK:STDOUT:   %Self => constants.%Self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @I(@I.%T.loc4_13.2) {
+// CHECK:STDOUT:   %T.loc4_13.2 => constants.%T
+// CHECK:STDOUT:   %T.patt.loc4_13.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @X(constants.%U) {
+// CHECK:STDOUT:   %U.loc8_9.2 => constants.%U
+// CHECK:STDOUT:   %U.patt.loc8_9.2 => constants.%U
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %I.type => constants.%I.type.3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @I(constants.%U) {
+// CHECK:STDOUT:   %T.loc4_13.2 => constants.%U
+// CHECK:STDOUT:   %T.patt.loc4_13.2 => constants.%U
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %I.type => constants.%I.type.3
+// CHECK:STDOUT:   %Self.2 => constants.%Self
+// CHECK:STDOUT:   %F.type => constants.%F.type.2
+// CHECK:STDOUT:   %F => constants.%F.2
+// CHECK:STDOUT:   %.loc5_25.2 => constants.%.3
+// CHECK:STDOUT:   %.loc5_25.3 => constants.%.4
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @X(@impl.%U) {
+// CHECK:STDOUT:   %U.loc8_9.2 => constants.%U
+// CHECK:STDOUT:   %U.patt.loc8_9.2 => constants.%U
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @I(@impl.%U) {
+// CHECK:STDOUT:   %T.loc4_13.2 => constants.%U
+// CHECK:STDOUT:   %T.patt.loc4_13.2 => constants.%U
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @impl(constants.%U) {
+// CHECK:STDOUT:   %U => constants.%U
+// CHECK:STDOUT:   %X => constants.%X.2
+// CHECK:STDOUT:   %I.type.loc9_19.2 => constants.%I.type.3
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %F.type => constants.%F.type.3
+// CHECK:STDOUT:   %F => constants.%F.3
+// CHECK:STDOUT:   %.loc9_23.2 => constants.%.5
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @X(@F.2.%U) {
+// CHECK:STDOUT:   %U.loc8_9.2 => constants.%U
+// CHECK:STDOUT:   %U.patt.loc8_9.2 => constants.%U
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @F.2(constants.%U) {
+// CHECK:STDOUT:   %U => constants.%U
+// CHECK:STDOUT:   %X => constants.%X.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @F.1(constants.%U, constants.%X.2) {
+// CHECK:STDOUT:   %T => constants.%U
+// CHECK:STDOUT:   %I.type => constants.%I.type.3
+// CHECK:STDOUT:   %Self => constants.%X.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @impl(@impl.%U) {
+// CHECK:STDOUT:   %U => constants.%U
+// CHECK:STDOUT:   %X => constants.%X.2
+// CHECK:STDOUT:   %I.type.loc9_19.2 => constants.%I.type.3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @I(@X.%U.loc8_9.2) {
+// CHECK:STDOUT:   %T.loc4_13.2 => constants.%U
+// CHECK:STDOUT:   %T.patt.loc4_13.2 => constants.%U
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

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

@@ -137,6 +137,7 @@ class C {
 // CHECK:STDOUT:     %T.param: type = value_param runtime_param<invalid>
 // CHECK:STDOUT:     %T.param: type = value_param runtime_param<invalid>
 // CHECK:STDOUT:     %T.loc19_23.1: type = bind_symbolic_name T, 0, %T.param [symbolic = %T.loc19_23.2 (constants.%T)]
 // CHECK:STDOUT:     %T.loc19_23.1: type = bind_symbolic_name T, 0, %T.param [symbolic = %T.loc19_23.2 (constants.%T)]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.loc19: type = specific_constant @impl.%GenericInterface.type.loc19_52.1, @impl(constants.%T) [symbolic = constants.%GenericInterface.type.2]
 // CHECK:STDOUT:   %.loc22: <witness> = complete_type_witness %.4 [template = constants.%.5]
 // CHECK:STDOUT:   %.loc22: <witness> = complete_type_witness %.4 [template = constants.%.5]
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: !members:

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

@@ -126,7 +126,7 @@ class E {
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%D
 // CHECK:STDOUT:   .Self = constants.%D
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend @impl.2.%I.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @E {
 // CHECK:STDOUT: class @E {
@@ -138,7 +138,7 @@ class E {
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%E
 // CHECK:STDOUT:   .Self = constants.%E
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend @impl.3.%I.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";

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

@@ -10,7 +10,7 @@
 
 
 interface I {
 interface I {
   class C {
   class C {
-    // CHECK:STDERR: fail_extend_partially_defined_interface.carbon:[[@LINE+6]]:5: error: `extend impl` requires a definition for interface `I` [ExtendUndefinedInterface]
+    // CHECK:STDERR: fail_extend_partially_defined_interface.carbon:[[@LINE+6]]:5: error: `extend impl` requires a definition for facet type `I` [ExtendUndefinedInterface]
     // CHECK:STDERR:     extend impl as I;
     // CHECK:STDERR:     extend impl as I;
     // CHECK:STDERR:     ^~~~~~~~~~~~~~~~~
     // CHECK:STDERR:     ^~~~~~~~~~~~~~~~~
     // CHECK:STDERR: fail_extend_partially_defined_interface.carbon:[[@LINE-5]]:1: note: interface is currently being defined [InterfaceUndefinedWithinDefinition]
     // CHECK:STDERR: fail_extend_partially_defined_interface.carbon:[[@LINE-5]]:1: note: interface is currently being defined [InterfaceUndefinedWithinDefinition]
@@ -27,6 +27,7 @@ interface I {
 // CHECK:STDOUT:   %Self: %I.type = bind_symbolic_name Self, 0 [symbolic]
 // CHECK:STDOUT:   %Self: %I.type = bind_symbolic_name Self, 0 [symbolic]
 // CHECK:STDOUT:   %C.1: type = class_type @C [template]
 // CHECK:STDOUT:   %C.1: type = class_type @C [template]
 // CHECK:STDOUT:   %C.2: type = class_type @C, @C(%Self) [symbolic]
 // CHECK:STDOUT:   %C.2: type = class_type @C, @C(%Self) [symbolic]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [template]
 // CHECK:STDOUT:   %.1: type = struct_type {} [template]
 // CHECK:STDOUT:   %.1: type = struct_type {} [template]
 // CHECK:STDOUT:   %.2: <witness> = complete_type_witness %.1 [template]
 // CHECK:STDOUT:   %.2: <witness> = complete_type_witness %.1 [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
@@ -72,10 +73,12 @@ interface I {
 // CHECK:STDOUT:       %Self.ref: type = name_ref Self, constants.%C.2 [symbolic = %C (constants.%C.2)]
 // CHECK:STDOUT:       %Self.ref: type = name_ref Self, constants.%C.2 [symbolic = %C (constants.%C.2)]
 // CHECK:STDOUT:       %I.ref: type = name_ref I, file.%I.decl [template = constants.%I.type]
 // CHECK:STDOUT:       %I.ref: type = name_ref I, file.%I.decl [template = constants.%I.type]
 // CHECK:STDOUT:     }
 // CHECK:STDOUT:     }
+// CHECK:STDOUT:     %.loc19: type = specific_constant @impl.%I.ref, @impl(constants.%Self) [template = constants.%I.type]
 // CHECK:STDOUT:     %.loc20: <witness> = complete_type_witness %.1 [template = constants.%.2]
 // CHECK:STDOUT:     %.loc20: <witness> = complete_type_witness %.1 [template = constants.%.2]
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT:   !members:
 // CHECK:STDOUT:   !members:
 // CHECK:STDOUT:     .Self = constants.%C.2
 // CHECK:STDOUT:     .Self = constants.%C.2
+// CHECK:STDOUT:     extend %.loc19
 // CHECK:STDOUT:     has_error
 // CHECK:STDOUT:     has_error
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }

+ 50 - 2
toolchain/check/testdata/impl/fail_extend_undefined_interface.carbon

@@ -11,22 +11,51 @@
 interface I;
 interface I;
 
 
 class C {
 class C {
-  // CHECK:STDERR: fail_extend_undefined_interface.carbon:[[@LINE+6]]:3: error: `extend impl` requires a definition for interface `I` [ExtendUndefinedInterface]
+  // CHECK:STDERR: fail_extend_undefined_interface.carbon:[[@LINE+7]]:3: error: `extend impl` requires a definition for facet type `I` [ExtendUndefinedInterface]
   // CHECK:STDERR:   extend impl as I;
   // CHECK:STDERR:   extend impl as I;
   // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~
   // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~
   // CHECK:STDERR: fail_extend_undefined_interface.carbon:[[@LINE-6]]:1: note: interface was forward declared here [InterfaceForwardDeclaredHere]
   // CHECK:STDERR: fail_extend_undefined_interface.carbon:[[@LINE-6]]:1: note: interface was forward declared here [InterfaceForwardDeclaredHere]
   // CHECK:STDERR: interface I;
   // CHECK:STDERR: interface I;
   // CHECK:STDERR: ^~~~~~~~~~~~
   // CHECK:STDERR: ^~~~~~~~~~~~
+  // CHECK:STDERR:
   extend impl as I;
   extend impl as I;
 }
 }
 
 
+fn F(c: C) {
+  // CHECK:STDERR: fail_extend_undefined_interface.carbon:[[@LINE+10]]:3: error: member access into undefined interface `I` [QualifiedExprInUndefinedInterfaceScope]
+  // CHECK:STDERR:   C.F();
+  // CHECK:STDERR:   ^~~
+  // CHECK:STDERR: fail_extend_undefined_interface.carbon:[[@LINE-17]]:1: note: interface was forward declared here [InterfaceForwardDeclaredHere]
+  // CHECK:STDERR: interface I;
+  // CHECK:STDERR: ^~~~~~~~~~~~
+  // CHECK:STDERR: fail_extend_undefined_interface.carbon:[[@LINE-10]]:18: note: declared as an extended scope here [FromExtendHere]
+  // CHECK:STDERR:   extend impl as I;
+  // CHECK:STDERR:                  ^
+  // CHECK:STDERR:
+  C.F();
+  // CHECK:STDERR: fail_extend_undefined_interface.carbon:[[@LINE+9]]:3: error: member access into undefined interface `I` [QualifiedExprInUndefinedInterfaceScope]
+  // CHECK:STDERR:   c.F();
+  // CHECK:STDERR:   ^~~
+  // CHECK:STDERR: fail_extend_undefined_interface.carbon:[[@LINE-28]]:1: note: interface was forward declared here [InterfaceForwardDeclaredHere]
+  // CHECK:STDERR: interface I;
+  // CHECK:STDERR: ^~~~~~~~~~~~
+  // CHECK:STDERR: fail_extend_undefined_interface.carbon:[[@LINE-21]]:18: note: declared as an extended scope here [FromExtendHere]
+  // CHECK:STDERR:   extend impl as I;
+  // CHECK:STDERR:                  ^
+  c.F();
+}
+
 // CHECK:STDOUT: --- fail_extend_undefined_interface.carbon
 // CHECK:STDOUT: --- fail_extend_undefined_interface.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %I.type: type = facet_type <@I> [template]
 // CHECK:STDOUT:   %I.type: type = facet_type <@I> [template]
 // CHECK:STDOUT:   %C: type = class_type @C [template]
 // CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [template]
 // CHECK:STDOUT:   %.1: type = struct_type {} [template]
 // CHECK:STDOUT:   %.1: type = struct_type {} [template]
 // CHECK:STDOUT:   %.2: <witness> = complete_type_witness %.1 [template]
 // CHECK:STDOUT:   %.2: <witness> = complete_type_witness %.1 [template]
+// CHECK:STDOUT:   %F.type: type = fn_type @F [template]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [template]
+// CHECK:STDOUT:   %.3: type = ptr_type %.1 [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT: imports {
@@ -41,10 +70,19 @@ class C {
 // CHECK:STDOUT:     .Core = imports.%Core
 // CHECK:STDOUT:     .Core = imports.%Core
 // CHECK:STDOUT:     .I = %I.decl
 // CHECK:STDOUT:     .I = %I.decl
 // CHECK:STDOUT:     .C = %C.decl
 // CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:     .F = %F.decl
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Core.import = import Core
 // CHECK:STDOUT:   %Core.import = import Core
 // CHECK:STDOUT:   %I.decl: type = interface_decl @I [template = constants.%I.type] {} {}
 // CHECK:STDOUT:   %I.decl: type = interface_decl @I [template = constants.%I.type] {} {}
 // CHECK:STDOUT:   %C.decl: type = class_decl @C [template = constants.%C] {} {}
 // CHECK:STDOUT:   %C.decl: type = class_decl @C [template = constants.%C] {} {}
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [template = constants.%F] {
+// CHECK:STDOUT:     %c.patt: %C = binding_pattern c
+// CHECK:STDOUT:     %c.param_patt: %C = value_param_pattern %c.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %C.ref.loc24: type = name_ref C, file.%C.decl [template = constants.%C]
+// CHECK:STDOUT:     %c.param: %C = value_param runtime_param0
+// CHECK:STDOUT:     %c: %C = bind_name c, %c.param
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @I;
 // CHECK:STDOUT: interface @I;
@@ -56,10 +94,20 @@ class C {
 // CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%C [template = constants.%C]
 // CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%C [template = constants.%C]
 // CHECK:STDOUT:     %I.ref: type = name_ref I, file.%I.decl [template = constants.%I.type]
 // CHECK:STDOUT:     %I.ref: type = name_ref I, file.%I.decl [template = constants.%I.type]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %.loc21: <witness> = complete_type_witness %.1 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc22: <witness> = complete_type_witness %.1 [template = constants.%.2]
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%C
 // CHECK:STDOUT:   .Self = constants.%C
+// CHECK:STDOUT:   extend @impl.%I.ref
 // CHECK:STDOUT:   has_error
 // CHECK:STDOUT:   has_error
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
+// CHECK:STDOUT: fn @F(%c.param_patt: %C) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %C.ref.loc35: type = name_ref C, file.%C.decl [template = constants.%C]
+// CHECK:STDOUT:   %F.ref.loc35: <error> = name_ref F, <error> [template = <error>]
+// CHECK:STDOUT:   %c.ref: %C = name_ref c, %c
+// CHECK:STDOUT:   %F.ref.loc45: <error> = name_ref F, <error> [template = <error>]
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 1 - 1
toolchain/check/testdata/impl/lookup/fail_todo_undefined_impl.carbon

@@ -117,7 +117,7 @@ impl C as I {
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%C
 // CHECK:STDOUT:   .Self = constants.%C
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend @impl.1.%I.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: generic fn @F.1(@I.%Self: %I.type) {
 // CHECK:STDOUT: generic fn @F.1(@I.%Self: %I.type) {

+ 1 - 1
toolchain/check/testdata/impl/lookup/instance_method.carbon

@@ -143,7 +143,7 @@ fn F(c: C) -> i32 {
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%C
 // CHECK:STDOUT:   .Self = constants.%C
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend @impl.%I.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";

+ 11 - 11
toolchain/check/testdata/impl/multiple_extend.carbon

@@ -271,8 +271,8 @@ fn P(o: O) {
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%C
 // CHECK:STDOUT:   .Self = constants.%C
-// CHECK:STDOUT:   extend name_scope2
-// CHECK:STDOUT:   extend name_scope3
+// CHECK:STDOUT:   extend @impl.1.%HasF.ref
+// CHECK:STDOUT:   extend @impl.2.%HasG.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: generic fn @F.1(@HasF.%Self: %HasF.type) {
 // CHECK:STDOUT: generic fn @F.1(@HasF.%Self: %HasF.type) {
@@ -436,8 +436,8 @@ fn P(o: O) {
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%D
 // CHECK:STDOUT:   .Self = constants.%D
-// CHECK:STDOUT:   extend name_scope2
-// CHECK:STDOUT:   extend name_scope3
+// CHECK:STDOUT:   extend @impl.1.%HasA1.ref
+// CHECK:STDOUT:   extend @impl.2.%HasA2.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: generic fn @A.1(@HasA1.%Self: %HasA1.type) {
 // CHECK:STDOUT: generic fn @A.1(@HasA1.%Self: %HasA1.type) {
@@ -576,8 +576,8 @@ fn P(o: O) {
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%E
 // CHECK:STDOUT:   .Self = constants.%E
 // CHECK:STDOUT:   .base = %.loc13
 // CHECK:STDOUT:   .base = %.loc13
-// CHECK:STDOUT:   extend name_scope3
-// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT:   extend %B.ref
+// CHECK:STDOUT:   extend @impl.%HasI.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: generic fn @I.1(@HasI.%Self: %HasI.type) {
 // CHECK:STDOUT: generic fn @I.1(@HasI.%Self: %HasI.type) {
@@ -717,8 +717,8 @@ fn P(o: O) {
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%L
 // CHECK:STDOUT:   .Self = constants.%L
 // CHECK:STDOUT:   .base = %.loc13
 // CHECK:STDOUT:   .base = %.loc13
-// CHECK:STDOUT:   extend name_scope2
-// CHECK:STDOUT:   extend name_scope3
+// CHECK:STDOUT:   extend %Base.ref
+// CHECK:STDOUT:   extend @impl.%HasK.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @K.1() {
 // CHECK:STDOUT: fn @K.1() {
@@ -887,9 +887,9 @@ fn P(o: O) {
 // CHECK:STDOUT:   .Self = constants.%O
 // CHECK:STDOUT:   .Self = constants.%O
 // CHECK:STDOUT:   .base = %.loc17
 // CHECK:STDOUT:   .base = %.loc17
 // CHECK:STDOUT:   .N = %N.decl
 // CHECK:STDOUT:   .N = %N.decl
-// CHECK:STDOUT:   extend name_scope2
-// CHECK:STDOUT:   extend name_scope3
-// CHECK:STDOUT:   extend name_scope4
+// CHECK:STDOUT:   extend %NBase.ref
+// CHECK:STDOUT:   extend @impl.1.%HasN1.ref
+// CHECK:STDOUT:   extend @impl.2.%HasN2.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @N.1() {
 // CHECK:STDOUT: fn @N.1() {

+ 164 - 0
toolchain/check/testdata/impl/no_prelude/import_extend_impl.carbon

@@ -0,0 +1,164 @@
+// 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
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/impl/no_prelude/import_extend_impl.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/impl/no_prelude/import_extend_impl.carbon
+
+// --- extend_impl_library.carbon
+
+library "[[@TEST_NAME]]";
+
+interface I {
+  fn F();
+}
+
+class C {
+  extend impl as I {
+    fn F() {}
+  }
+}
+
+// --- fail_todo_use_imported_class_extend_impl.carbon
+
+library "[[@TEST_NAME]]";
+
+import library "extend_impl_library";
+
+fn G(c: C) {
+  // CHECK:STDERR: fail_todo_use_imported_class_extend_impl.carbon:[[@LINE+4]]:3: error: name `F` not found [NameNotFound]
+  // CHECK:STDERR:   C.F();
+  // CHECK:STDERR:   ^~~
+  // CHECK:STDERR:
+  C.F();
+  // CHECK:STDERR: fail_todo_use_imported_class_extend_impl.carbon:[[@LINE+3]]:3: error: name `F` not found [NameNotFound]
+  // CHECK:STDERR:   c.F();
+  // CHECK:STDERR:   ^~~
+  c.F();
+}
+
+// CHECK:STDOUT: --- extend_impl_library.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %I.type: type = facet_type <@I> [template]
+// CHECK:STDOUT:   %Self: %I.type = bind_symbolic_name Self, 0 [symbolic]
+// CHECK:STDOUT:   %F.type.1: type = fn_type @F.1 [template]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [template]
+// CHECK:STDOUT:   %F.1: %F.type.1 = struct_value () [template]
+// CHECK:STDOUT:   %.1: type = assoc_entity_type %I.type, %F.type.1 [template]
+// CHECK:STDOUT:   %.2: %.1 = assoc_entity element0, @I.%F.decl [template]
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %F.type.2: type = fn_type @F.2 [template]
+// CHECK:STDOUT:   %F.2: %F.type.2 = struct_value () [template]
+// CHECK:STDOUT:   %.3: <witness> = interface_witness (%F.2) [template]
+// CHECK:STDOUT:   %.4: type = struct_type {} [template]
+// CHECK:STDOUT:   %.5: <witness> = complete_type_witness %.4 [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:   }
+// CHECK:STDOUT:   %I.decl: type = interface_decl @I [template = constants.%I.type] {} {}
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [template = constants.%C] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @I {
+// CHECK:STDOUT:   %Self: %I.type = bind_symbolic_name Self, 0 [symbolic = constants.%Self]
+// CHECK:STDOUT:   %F.decl: %F.type.1 = fn_decl @F.1 [template = constants.%F.1] {} {}
+// CHECK:STDOUT:   %.loc5: %.1 = assoc_entity element0, %F.decl [template = constants.%.2]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
+// CHECK:STDOUT:   .F = %.loc5
+// CHECK:STDOUT:   witness = (%F.decl)
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: impl @impl: %Self.ref as %I.ref {
+// CHECK:STDOUT:   %F.decl: %F.type.2 = fn_decl @F.2 [template = constants.%F.2] {} {}
+// CHECK:STDOUT:   %.loc9: <witness> = interface_witness (%F.decl) [template = constants.%.3]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .F = %F.decl
+// CHECK:STDOUT:   witness = %.loc9
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT:   impl_decl @impl [template] {} {
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%C [template = constants.%C]
+// CHECK:STDOUT:     %I.ref: type = name_ref I, file.%I.decl [template = constants.%I.type]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.loc12: <witness> = complete_type_witness %.4 [template = constants.%.5]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%C
+// CHECK:STDOUT:   extend @impl.%I.ref
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic fn @F.1(@I.%Self: %I.type) {
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn();
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F.2() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @F.1(constants.%Self) {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @F.1(constants.%C) {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_use_imported_class_extend_impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %.1: type = struct_type {} [template]
+// CHECK:STDOUT:   %.2: <witness> = complete_type_witness %.1 [template]
+// CHECK:STDOUT:   %G.type: type = fn_type @G [template]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [template]
+// CHECK:STDOUT:   %G: %G.type = struct_value () [template]
+// CHECK:STDOUT:   %.3: type = ptr_type %.1 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %import_ref.1 = import_ref Main//extend_impl_library, inst+1, unloaded
+// CHECK:STDOUT:   %import_ref.2: type = import_ref Main//extend_impl_library, inst+12, loaded [template = constants.%C]
+// CHECK:STDOUT:   %import_ref.3 = import_ref Main//extend_impl_library, inst+13, unloaded
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .I = imports.%import_ref.1
+// CHECK:STDOUT:     .C = imports.%import_ref.2
+// CHECK:STDOUT:     .G = %G.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %default.import = import <invalid>
+// CHECK:STDOUT:   %G.decl: %G.type = fn_decl @G [template = constants.%G] {
+// CHECK:STDOUT:     %c.patt: %C = binding_pattern c
+// CHECK:STDOUT:     %c.param_patt: %C = value_param_pattern %c.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %C.ref.loc6: type = name_ref C, imports.%import_ref.2 [template = constants.%C]
+// CHECK:STDOUT:     %c.param: %C = value_param runtime_param0
+// CHECK:STDOUT:     %c: %C = bind_name c, %c.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = imports.%import_ref.3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @G(%c.param_patt: %C) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %C.ref.loc11: type = name_ref C, imports.%import_ref.2 [template = constants.%C]
+// CHECK:STDOUT:   %F.ref.loc11: <error> = name_ref F, <error> [template = <error>]
+// CHECK:STDOUT:   %c.ref: %C = name_ref c, %c
+// CHECK:STDOUT:   %F.ref.loc15: <error> = name_ref F, <error> [template = <error>]
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 2 - 1
toolchain/check/testdata/interface/no_prelude/fail_lookup_in_type_type.carbon

@@ -22,7 +22,7 @@ let T: type.not_found = {};
 
 
 library "[[@TEST_NAME]]";
 library "[[@TEST_NAME]]";
 
 
-// CHECK:STDERR: fail_lookup_type_where.carbon:[[@LINE+3]]:8: error: type `type` does not support qualified expressions [QualifiedExprUnsupported]
+// CHECK:STDERR: fail_lookup_type_where.carbon:[[@LINE+3]]:8: error: name `missing` not found [NameNotFound]
 // CHECK:STDERR: let U: (type where .Self impls type).missing = {};
 // CHECK:STDERR: let U: (type where .Self impls type).missing = {};
 // CHECK:STDERR:        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 // CHECK:STDERR:        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 let U: (type where .Self impls type).missing = {};
 let U: (type where .Self impls type).missing = {};
@@ -64,6 +64,7 @@ let U: (type where .Self impls type).missing = {};
 // CHECK:STDOUT:   %.loc7: type = where_expr %.Self [template = constants.%type_where] {
 // CHECK:STDOUT:   %.loc7: type = where_expr %.Self [template = constants.%type_where] {
 // CHECK:STDOUT:     requirement_impls %.Self.ref, type
 // CHECK:STDOUT:     requirement_impls %.Self.ref, type
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   }
+// CHECK:STDOUT:   %missing.ref: <error> = name_ref missing, <error> [template = <error>]
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @__global_init() {
 // CHECK:STDOUT: fn @__global_init() {

+ 1 - 0
toolchain/diagnostics/diagnostic_kind.def

@@ -272,6 +272,7 @@ CARBON_DIAGNOSTIC_KIND(QualifiedDeclInIncompleteClassScope)
 CARBON_DIAGNOSTIC_KIND(QualifiedDeclInUndefinedInterfaceScope)
 CARBON_DIAGNOSTIC_KIND(QualifiedDeclInUndefinedInterfaceScope)
 
 
 // Name lookup.
 // Name lookup.
+CARBON_DIAGNOSTIC_KIND(FromExtendHere)
 CARBON_DIAGNOSTIC_KIND(InNameLookup)
 CARBON_DIAGNOSTIC_KIND(InNameLookup)
 CARBON_DIAGNOSTIC_KIND(NameAmbiguousDueToExtend)
 CARBON_DIAGNOSTIC_KIND(NameAmbiguousDueToExtend)
 CARBON_DIAGNOSTIC_KIND(NameNotFound)
 CARBON_DIAGNOSTIC_KIND(NameNotFound)

+ 2 - 1
toolchain/sem_ir/facet_type_info.h

@@ -32,11 +32,12 @@ struct FacetTypeInfo : Printable<FacetTypeInfo> {
 
 
     // TODO: extend this so it can represent named constraint requirements
     // TODO: extend this so it can represent named constraint requirements
     // and requirements on members, not just `.Self`.
     // and requirements on members, not just `.Self`.
+    // TODO: Add whether this is a lookup context. Those that are should sort
+    // first for easy access. Right now, all are assumed to be lookup contexts.
     InterfaceId interface_id;
     InterfaceId interface_id;
     SpecificId specific_id;
     SpecificId specific_id;
   };
   };
   llvm::SmallVector<ImplsConstraint> impls_constraints;
   llvm::SmallVector<ImplsConstraint> impls_constraints;
-  // TODO: Add lookup contexts.
   // TODO: Add rewrite constraints.
   // TODO: Add rewrite constraints.
   // TODO: Add same-type constraints.
   // TODO: Add same-type constraints.
   // TODO: Remove `requirement_block_id`.
   // TODO: Remove `requirement_block_id`.

+ 3 - 2
toolchain/sem_ir/formatter.cpp

@@ -501,9 +501,10 @@ class FormatterImpl {
     }
     }
 
 
     for (auto extended_scope_id : scope.extended_scopes) {
     for (auto extended_scope_id : scope.extended_scopes) {
-      // TODO: Print this scope in a better way.
       Indent();
       Indent();
-      out_ << "extend " << extended_scope_id << "\n";
+      out_ << "extend ";
+      FormatName(extended_scope_id);
+      out_ << "\n";
     }
     }
 
 
     // This is used to cluster all "Core//prelude/..." imports, but not
     // This is used to cluster all "Core//prelude/..." imports, but not

+ 2 - 11
toolchain/sem_ir/name_scope.h

@@ -75,22 +75,13 @@ struct NameScope : Printable<NameScope> {
   llvm::SmallVector<Entry> names;
   llvm::SmallVector<Entry> names;
   Map<NameId, int> name_map;
   Map<NameId, int> name_map;
 
 
-  // Scopes extended by this scope.
-  //
-  // TODO: A `NameScopeId` is currently insufficient to describe an extended
-  // scope in general. For example:
-  //
-  //   class A(T:! type) {
-  //     extend base: B(T*);
-  //   }
-  //
-  // needs to describe the `T*` argument.
+  // Instructions returning values that are extended by this scope.
   //
   //
   // Small vector size is set to 1: we expect that there will rarely be more
   // Small vector size is set to 1: we expect that there will rarely be more
   // than a single extended scope.
   // than a single extended scope.
   // TODO: Revisit this once we have more kinds of extended scope and data.
   // TODO: Revisit this once we have more kinds of extended scope and data.
   // TODO: Consider using something like `TinyPtrVector` for this.
   // TODO: Consider using something like `TinyPtrVector` for this.
-  llvm::SmallVector<NameScopeId, 1> extended_scopes;
+  llvm::SmallVector<InstId, 1> extended_scopes;
 
 
   // The instruction which owns the scope.
   // The instruction which owns the scope.
   InstId inst_id;
   InstId inst_id;